aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGokul Soumya2021-11-21 18:55:08 +0000
committerBlaž Hrastnik2021-12-08 01:23:50 +0000
commita78b7894066b6ccf56c404b7543b45e2dfd99982 (patch)
tree5f1aa74aeffc173ed591f7f89f2b115cc1cbb036
parent71292f9f11bd2b50568efd111239f693be26a36a (diff)
Auto generate docs for language support
-rw-r--r--Cargo.lock2
-rw-r--r--book/src/SUMMARY.md1
-rw-r--r--book/src/generated/lang-support.md41
-rw-r--r--book/src/generated/typable-cmd.md2
-rw-r--r--book/src/lang-support.md10
-rw-r--r--docs/CONTRIBUTING.md2
-rw-r--r--helix-core/src/indent.rs1
-rw-r--r--helix-core/src/syntax.rs3
-rw-r--r--languages.toml40
-rw-r--r--xtask/Cargo.toml2
-rw-r--r--xtask/src/main.rs229
11 files changed, 311 insertions, 22 deletions
diff --git a/Cargo.lock b/Cargo.lock
index b3032465..25b4f6cd 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1264,5 +1264,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
name = "xtask"
version = "0.5.0"
dependencies = [
+ "helix-core",
"helix-term",
+ "toml",
]
diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md
index 4da79925..a8f165c0 100644
--- a/book/src/SUMMARY.md
+++ b/book/src/SUMMARY.md
@@ -4,6 +4,7 @@
- [Usage](./usage.md)
- [Keymap](./keymap.md)
- [Commands](./commands.md)
+ - [Language Support](./lang-support.md)
- [Migrating from Vim](./from-vim.md)
- [Configuration](./configuration.md)
- [Themes](./themes.md)
diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md
new file mode 100644
index 00000000..729801ad
--- /dev/null
+++ b/book/src/generated/lang-support.md
@@ -0,0 +1,41 @@
+| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default LSP |
+| --- | --- | --- | --- | --- |
+| Bash | ✓ | | | `bash-language-server` |
+| C | ✓ | | | `clangd` |
+| C# | ✓ | | | |
+| CMake | ✓ | | | `cmake-language-server` |
+| C++ | ✓ | | | `clangd` |
+| CSS | ✓ | | | |
+| Elixir | ✓ | | | `elixir-ls` |
+| GLSL | ✓ | | ✓ | |
+| Go | ✓ | ✓ | ✓ | `gopls` |
+| HTML | ✓ | | | |
+| Java | ✓ | | | |
+| JavaScript | ✓ | | ✓ | |
+| JSON | ✓ | | ✓ | |
+| Julia | ✓ | | | `julia` |
+| LaTeX | ✓ | | | |
+| Ledger | ✓ | | | |
+| LLVM | ✓ | | | |
+| Lua | ✓ | | ✓ | |
+| Mint | | | | `mint` |
+| Nix | ✓ | | ✓ | `rnix-lsp` |
+| OCaml | ✓ | | ✓ | |
+| OCaml-Interface | ✓ | | | |
+| Perl | ✓ | ✓ | | |
+| PHP | ✓ | | ✓ | |
+| Prolog | | | | `swipl` |
+| Protobuf | ✓ | | ✓ | |
+| Python | ✓ | ✓ | ✓ | `pylsp` |
+| Racket | | | | `racket` |
+| Ruby | ✓ | | | `solargraph` |
+| Rust | ✓ | ✓ | ✓ | `rust-analyzer` |
+| Svelte | ✓ | | ✓ | `svelteserver` |
+| TOML | ✓ | | | |
+| TSQ | ✓ | | | |
+| TSX | ✓ | | | `typescript-language-server` |
+| TypeScript | ✓ | | ✓ | `typescript-language-server` |
+| Vue | ✓ | | | |
+| WGSL | ✓ | | | |
+| YAML | ✓ | | ✓ | |
+| Zig | ✓ | | ✓ | `zls` |
diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md
index 5de5c787..bb21fd6b 100644
--- a/book/src/generated/typable-cmd.md
+++ b/book/src/generated/typable-cmd.md
@@ -1,5 +1,5 @@
| Name | Description |
-| --- | --- |
+| --- | --- |
| `:quit`, `:q` | Close the current view. |
| `:quit!`, `:q!` | Close the current view forcefully (ignoring unsaved changes). |
| `:open`, `:o` | Open a file from disk into the current view. |
diff --git a/book/src/lang-support.md b/book/src/lang-support.md
new file mode 100644
index 00000000..3920f342
--- /dev/null
+++ b/book/src/lang-support.md
@@ -0,0 +1,10 @@
+# Language Support
+
+For more information like arguments passed to default LSP server,
+extensions assosciated with a filetype, custom LSP settings, filetype
+specific indent settings, etc see the default
+[`languages.toml`][languages.toml] file.
+
+{{#include ./generated/lang-support.md}}
+
+[languages.toml]: https://github.com/helix-editor/helix/blob/master/languages.toml
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index 7b923db8..bdd771aa 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -23,7 +23,7 @@ like the list of `:commands` and supported languages. To generate these
files, run
```shell
-cargo xtask bookgen
+cargo xtask docgen
```
inside the project. We use [xtask][xtask] as an ad-hoc task runner and
diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs
index b6f5081a..3ce3620a 100644
--- a/helix-core/src/indent.rs
+++ b/helix-core/src/indent.rs
@@ -452,6 +452,7 @@ where
file_types: vec!["rs".to_string()],
shebangs: vec![],
language_id: "Rust".to_string(),
+ display_name: "Rust".to_string(),
highlight_config: OnceCell::new(),
config: None,
//
diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs
index ba78adaa..3c65ae33 100644
--- a/helix-core/src/syntax.rs
+++ b/helix-core/src/syntax.rs
@@ -50,7 +50,8 @@ pub struct Configuration {
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct LanguageConfiguration {
#[serde(rename = "name")]
- pub language_id: String,
+ pub language_id: String, // c-sharp, rust
+ pub display_name: String, // C#, Rust
pub scope: String, // source.rust
pub file_types: Vec<String>, // filename ends_with? <Gemfile, rb, etc>
#[serde(default)]
diff --git a/languages.toml b/languages.toml
index 4208e4b6..ca339c98 100644
--- a/languages.toml
+++ b/languages.toml
@@ -1,5 +1,6 @@
[[language]]
name = "rust"
+display-name = "Rust"
scope = "source.rust"
injection-regex = "rust"
file-types = ["rs"]
@@ -14,6 +15,7 @@ procMacro = { enable = false }
[[language]]
name = "toml"
+display-name = "TOML"
scope = "source.toml"
injection-regex = "toml"
file-types = ["toml"]
@@ -24,6 +26,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "protobuf"
+display-name = "Protobuf"
scope = "source.proto"
injection-regex = "protobuf"
file-types = ["proto"]
@@ -34,6 +37,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "elixir"
+display-name = "Elixir"
scope = "source.elixir"
injection-regex = "elixir"
file-types = ["ex", "exs"]
@@ -46,6 +50,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "mint"
+display-name = "Mint"
scope = "source.mint"
injection-regex = "mint"
file-types = ["mint"]
@@ -58,6 +63,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "json"
+display-name = "JSON"
scope = "source.json"
injection-regex = "json"
file-types = ["json"]
@@ -67,6 +73,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "c"
+display-name = "C"
scope = "source.c"
injection-regex = "c"
file-types = ["c"] # TODO: ["h"]
@@ -78,6 +85,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "cpp"
+display-name = "C++"
scope = "source.cpp"
injection-regex = "cpp"
file-types = ["cc", "hh", "cpp", "hpp", "h", "ipp", "tpp", "cxx", "hxx", "ixx", "txx", "ino"]
@@ -89,6 +97,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "c-sharp"
+display-name = "C#"
scope = "source.csharp"
injection-regex = "c-?sharp"
file-types = ["cs"]
@@ -99,6 +108,7 @@ indent = { tab-width = 4, unit = "\t" }
[[language]]
name = "go"
+display-name = "Go"
scope = "source.go"
injection-regex = "go"
file-types = ["go"]
@@ -112,6 +122,7 @@ indent = { tab-width = 4, unit = "\t" }
[[language]]
name = "javascript"
+display-name = "JavaScript"
scope = "source.js"
injection-regex = "^(js|javascript)$"
file-types = ["js", "mjs"]
@@ -124,6 +135,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "typescript"
+display-name = "TypeScript"
scope = "source.ts"
injection-regex = "^(ts|typescript)$"
file-types = ["ts"]
@@ -136,6 +148,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "tsx"
+display-name = "TSX"
scope = "source.tsx"
injection-regex = "^(tsx)$" # |typescript
file-types = ["tsx"]
@@ -147,6 +160,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "css"
+display-name = "CSS"
scope = "source.css"
injection-regex = "css"
file-types = ["css"]
@@ -156,6 +170,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "html"
+display-name = "HTML"
scope = "text.html.basic"
injection-regex = "html"
file-types = ["html"]
@@ -165,6 +180,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "python"
+display-name = "Python"
scope = "source.python"
injection-regex = "python"
file-types = ["py"]
@@ -178,6 +194,7 @@ indent = { tab-width = 4, unit = " " }
[[language]]
name = "nix"
+display-name = "Nix"
scope = "source.nix"
injection-regex = "nix"
file-types = ["nix"]
@@ -190,6 +207,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "ruby"
+display-name = "Ruby"
scope = "source.ruby"
injection-regex = "ruby"
file-types = ["rb"]
@@ -202,6 +220,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "bash"
+display-name = "Bash"
scope = "source.bash"
injection-regex = "bash"
file-types = ["sh", "bash"]
@@ -214,6 +233,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "php"
+display-name = "PHP"
scope = "source.php"
injection-regex = "php"
file-types = ["php"]
@@ -224,6 +244,7 @@ indent = { tab-width = 4, unit = " " }
[[language]]
name = "latex"
+display-name = "LaTeX"
scope = "source.tex"
injection-regex = "tex"
file-types = ["tex"]
@@ -234,6 +255,7 @@ indent = { tab-width = 4, unit = "\t" }
[[language]]
name = "julia"
+display-name = "Julia"
scope = "source.julia"
injection-regex = "julia"
file-types = ["jl"]
@@ -259,6 +281,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "java"
+display-name = "Java"
scope = "source.java"
injection-regex = "java"
file-types = ["java"]
@@ -267,6 +290,7 @@ indent = { tab-width = 4, unit = " " }
[[language]]
name = "ledger"
+display-name = "Ledger"
scope = "source.ledger"
injection-regex = "ledger"
file-types = ["ldg", "ledger", "journal"]
@@ -276,6 +300,7 @@ indent = { tab-width = 4, unit = " " }
[[language]]
name = "ocaml"
+display-name = "OCaml"
scope = "source.ocaml"
injection-regex = "ocaml"
file-types = ["ml"]
@@ -286,6 +311,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "ocaml-interface"
+display-name = "OCaml-Interface"
scope = "source.ocaml.interface"
file-types = ["mli"]
shebangs = []
@@ -295,6 +321,7 @@ indent = { tab-width = 2, unit = " "}
[[language]]
name = "lua"
+display-name = "Lua"
scope = "source.lua"
file-types = ["lua"]
shebangs = ["lua"]
@@ -304,6 +331,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "svelte"
+display-name = "Svelte"
scope = "source.svelte"
injection-regex = "svelte"
file-types = ["svelte"]
@@ -314,6 +342,7 @@ language-server = { command = "svelteserver", args = ["--stdio"] }
[[language]]
name = "vue"
+display-name = "Vue"
scope = "source.vue"
injection-regex = "vue"
file-types = ["vue"]
@@ -322,6 +351,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "yaml"
+display-name = "YAML"
scope = "source.yaml"
file-types = ["yml", "yaml"]
roots = []
@@ -330,6 +360,7 @@ indent = { tab-width = 2, unit = " " }
# [[language]]
# name = "haskell"
+# display-name = "Haskell"
# scope = "source.haskell"
# injection-regex = "haskell"
# file-types = ["hs"]
@@ -340,6 +371,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "zig"
+display-name = "Zig"
scope = "source.zig"
injection-regex = "zig"
file-types = ["zig"]
@@ -352,6 +384,7 @@ indent = { tab-width = 4, unit = " " }
[[language]]
name = "prolog"
+display-name = "Prolog"
scope = "source.prolog"
roots = []
file-types = ["pl", "prolog"]
@@ -365,6 +398,7 @@ language-server = { command = "swipl", args = [
[[language]]
name = "tsq"
+display-name = "TSQ"
scope = "source.tsq"
file-types = ["scm"]
roots = []
@@ -373,6 +407,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "cmake"
+display-name = "CMake"
scope = "source.cmake"
file-types = ["cmake", "CMakeLists.txt"]
roots = []
@@ -382,6 +417,7 @@ language-server = { command = "cmake-language-server" }
[[language]]
name = "glsl"
+display-name = "GLSL"
scope = "source.glsl"
file-types = ["glsl", "vert", "tesc", "tese", "geom", "frag", "comp" ]
roots = []
@@ -390,6 +426,7 @@ indent = { tab-width = 4, unit = " " }
[[language]]
name = "perl"
+display-name = "Perl"
scope = "source.perl"
file-types = ["pl", "pm"]
shebangs = ["perl"]
@@ -399,6 +436,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "racket"
+display-name = "Racket"
scope = "source.rkt"
roots = []
file-types = ["rkt"]
@@ -408,6 +446,7 @@ language-server = { command = "racket", args = ["-l", "racket-langserver"] }
[[language]]
name = "wgsl"
+display-name = "WGSL"
scope = "source.wgsl"
file-types = ["wgsl"]
roots = []
@@ -416,6 +455,7 @@ indent = { tab-width = 4, unit = " " }
[[language]]
name = "llvm"
+display-name = "LLVM"
scope = "source.llvm"
roots = []
file-types = ["ll"]
diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml
index cb890de9..fe5d55d4 100644
--- a/xtask/Cargo.toml
+++ b/xtask/Cargo.toml
@@ -7,3 +7,5 @@ edition = "2021"
[dependencies]
helix-term = { version = "0.5", path = "../helix-term" }
+helix-core = { version = "0.5", path = "../helix-core" }
+toml = "0.5"
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index 4bf0ae9f..37e70592 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -1,17 +1,138 @@
-use std::env;
+use std::{env, error::Error};
+
+type DynError = Box<dyn Error>;
+
+pub mod helpers {
+ use std::{
+ fmt::Display,
+ path::{Path, PathBuf},
+ };
+
+ use crate::path;
+ use helix_core::syntax::Configuration as LangConfig;
+
+ #[derive(Copy, Clone)]
+ pub enum TsFeature {
+ Highlight,
+ TextObjects,
+ AutoIndent,
+ }
+
+ impl TsFeature {
+ pub fn all() -> &'static [Self] {
+ &[Self::Highlight, Self::TextObjects, Self::AutoIndent]
+ }
+
+ pub fn runtime_filename(&self) -> &'static str {
+ match *self {
+ Self::Highlight => "highlights.scm",
+ Self::TextObjects => "textobjects.scm",
+ Self::AutoIndent => "indents.toml",
+ }
+ }
+ }
+
+ impl Display for TsFeature {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{}",
+ match *self {
+ Self::Highlight => "Syntax Highlighting",
+ Self::TextObjects => "Treesitter Textobjects",
+ Self::AutoIndent => "Auto Indent",
+ }
+ )
+ }
+ }
+
+ /// Get the list of languages that support a particular tree-sitter
+ /// based feature.
+ pub fn ts_lang_support(feat: TsFeature) -> Vec<String> {
+ let queries_dir = path::ts_queries();
+
+ find_files(&queries_dir, feat.runtime_filename())
+ .iter()
+ .map(|f| {
+ // .../helix/runtime/queries/python/highlights.scm
+ let tail = f.strip_prefix(&queries_dir).unwrap(); // python/highlights.scm
+ let lang = tail.components().next().unwrap(); // python
+ lang.as_os_str().to_string_lossy().to_string()
+ })
+ .collect()
+ }
+
+ /// Get the list of languages that have any form of tree-sitter
+ /// queries defined in the runtime directory.
+ pub fn langs_with_ts_queries() -> Vec<String> {
+ std::fs::read_dir(path::ts_queries())
+ .unwrap()
+ .filter_map(|entry| {
+ let entry = entry.ok()?;
+ entry
+ .file_type()
+ .ok()?
+ .is_dir()
+ .then(|| entry.file_name().to_string_lossy().to_string())
+ })
+ .collect()
+ }
+
+ // naive implementation, but suffices for our needs
+ pub fn find_files(dir: &Path, filename: &str) -> Vec<PathBuf> {
+ std::fs::read_dir(dir)
+ .unwrap()
+ .filter_map(|entry| {
+ let path = entry.ok()?.path();
+ if path.is_dir() {
+ Some(find_files(&path, filename))
+ } else {
+ (path.file_name()?.to_string_lossy() == filename).then(|| vec![path])
+ }
+ })
+ .flatten()
+ .collect()
+ }
+
+ pub fn lang_config() -> LangConfig {
+ let bytes = std::fs::read(path::lang_config()).unwrap();
+ toml::from_slice(&bytes).unwrap()
+ }
+}
pub mod md_gen {
- use super::path;
+ use crate::DynError;
+
+ use crate::helpers;
+ use crate::path;
use std::fs;
use helix_term::commands::cmd::TYPABLE_COMMAND_LIST;
pub const TYPABLE_COMMANDS_MD_OUTPUT: &str = "typable-cmd.md";
+ pub const LANG_SUPPORT_MD_OUTPUT: &str = "lang-support.md";
+
+ fn md_table_heading(cols: &[String]) -> String {
+ let mut header = String::new();
+ header += &md_table_row(cols);
+ header += &md_table_row(&vec!["---".to_string(); cols.len()]);
+ header
+ }
+
+ fn md_table_row(cols: &[String]) -> String {
+ "| ".to_owned() + &cols.join(" | ") + " |\n"
+ }
+
+ fn md_mono(s: &str) -> String {
+ format!("`{}`", s)
+ }
- pub fn typable_commands() -> String {
+ pub fn typable_commands() -> Result<String, DynError> {
let mut md = String::new();
- md.push_str("| Name | Description |\n");
- md.push_str("| --- | --- |\n");
+ md.push_str(&md_table_heading(&[
+ "Name".to_owned(),
+ "Description".to_owned(),
+ ]));
let cmdify = |s: &str| format!("`:{}`", s);
@@ -22,11 +143,72 @@ pub mod md_gen {
.collect::<Vec<_>>()
.join(", ");
- let entry = format!("| {} | {} |\n", names, cmd.doc);
- md.push_str(&entry);
+ md.push_str(&md_table_row(&[names.to_owned(), cmd.doc.to_owned()]));
+ }
+
+ Ok(md)
+ }
+
+ pub fn lang_features() -> Result<String, DynError> {
+ let mut md = String::new();
+ let ts_features = helpers::TsFeature::all();
+
+ let mut cols = vec!["Language".to_owned()];
+ cols.append(
+ &mut ts_features
+ .iter()
+ .map(|t| t.to_string())
+ .collect::<Vec<_>>(),
+ );
+ cols.push("Default LSP".to_owned());
+
+ md.push_str(&md_table_heading(&cols));
+ let config = helpers::lang_config();
+
+ let mut langs = config
+ .language
+ .iter()
+ .map(|l| l.language_id.clone())
+ .collect::<Vec<_>>();
+ langs.sort_unstable();
+
+ let mut ts_features_to_langs = Vec::new();
+ for &feat in ts_features {
+ ts_features_to_langs.push((feat, helpers::ts_lang_support(feat)));
}
- md
+ let mut row = Vec::new();
+ for lang in langs {
+ let lc = config
+ .language
+ .iter()
+ .find(|l| l.language_id == lang)
+ .unwrap(); // lang comes from config
+ row.push(lc.display_name.clone());
+
+ for (_feat, support_list) in &ts_features_to_langs {
+ row.push(
+ if support_list.contains(&lang) {
+ "✓"
+ } else {
+ ""
+ }
+ .to_owned(),
+ );
+ }
+ row.push(
+ lc.language_server
+ .as_ref()
+ .map(|s| s.command.clone())
+ .map(|c| md_mono(&c))
+ .unwrap_or_default(),
+ );
+
+ md.push_str(&md_table_row(&row));
+ row.clear();
+ }
+
+ Ok(md)
}
pub fn write(filename: &str, data: &str) {
@@ -49,37 +231,46 @@ pub mod path {
pub fn book_gen() -> PathBuf {
project_root().join("book/src/generated/")
}
+
+ pub fn ts_queries() -> PathBuf {
+ project_root().join("runtime/queries")
+ }
+
+ pub fn lang_config() -> PathBuf {
+ project_root().join("languages.toml")
+ }
}
pub mod tasks {
- use super::md_gen;
+ use crate::md_gen;
+ use crate::DynError;
- pub fn bookgen() {
- md_gen::write(
- md_gen::TYPABLE_COMMANDS_MD_OUTPUT,
- &md_gen::typable_commands(),
- );
+ pub fn docgen() -> Result<(), DynError> {
+ use md_gen::*;
+ write(TYPABLE_COMMANDS_MD_OUTPUT, &typable_commands()?);
+ write(LANG_SUPPORT_MD_OUTPUT, &lang_features()?);
+ Ok(())
}
pub fn print_help() {
println!(
"
-Usage: Run with `cargo xtask <task>`, eg. `cargo xtask bookgen`.
+Usage: Run with `cargo xtask <task>`, eg. `cargo xtask docgen`.
Tasks:
- bookgen: Generate files to be included in the mdbook output.
+ docgen: Generate files to be included in the mdbook output.
"
);
}
}
-fn main() -> Result<(), String> {
+fn main() -> Result<(), DynError> {
let task = env::args().nth(1);
match task {
None => tasks::print_help(),
Some(t) => match t.as_str() {
- "bookgen" => tasks::bookgen(),
- invalid => return Err(format!("Invalid task name: {}", invalid)),
+ "docgen" => tasks::docgen()?,
+ invalid => return Err(format!("Invalid task name: {}", invalid).into()),
},
};
Ok(())