on e4e8a39 help sign in
helix/xtask/src/main.rs
242 lines 6.9 kB history raw
1use std::{env, error::Error};
3type DynError = Box<dyn Error>;
5pub mod helpers {
6 use std::path::{Path, PathBuf};
8 use crate::path;
9 use helix_core::syntax::Configuration as LangConfig;
10 use helix_term::health::TsFeature;
12 /// Get the list of languages that support a particular tree-sitter
13 /// based feature.
14 pub fn ts_lang_support(feat: TsFeature) -> Vec<String> {
15 let queries_dir = path::ts_queries();
17 find_files(&queries_dir, feat.runtime_filename())
18 .iter()
19 .map(|f| {
20 // .../helix/runtime/queries/python/highlights.scm
21 let tail = f.strip_prefix(&queries_dir).unwrap(); // python/highlights.scm
22 let lang = tail.components().next().unwrap(); // python
23 lang.as_os_str().to_string_lossy().to_string()
24 })
25 .collect()
26 }
28 /// Get the list of languages that have any form of tree-sitter
29 /// queries defined in the runtime directory.
30 pub fn langs_with_ts_queries() -> Vec<String> {
31 std::fs::read_dir(path::ts_queries())
32 .unwrap()
33 .filter_map(|entry| {
34 let entry = entry.ok()?;
35 entry
36 .file_type()
37 .ok()?
38 .is_dir()
39 .then(|| entry.file_name().to_string_lossy().to_string())
40 })
41 .collect()
42 }
44 // naive implementation, but suffices for our needs
45 pub fn find_files(dir: &Path, filename: &str) -> Vec<PathBuf> {
46 std::fs::read_dir(dir)
47 .unwrap()
48 .filter_map(|entry| {
49 let path = entry.ok()?.path();
50 if path.is_dir() {
51 Some(find_files(&path, filename))
52 } else {
53 (path.file_name()?.to_string_lossy() == filename).then(|| vec![path])
54 }
55 })
56 .flatten()
57 .collect()
58 }
60 pub fn lang_config() -> LangConfig {
61 let bytes = std::fs::read(path::lang_config()).unwrap();
62 toml::from_slice(&bytes).unwrap()
63 }
66pub mod md_gen {
67 use crate::DynError;
69 use crate::helpers;
70 use crate::path;
71 use helix_term::commands::TYPABLE_COMMAND_LIST;
72 use helix_term::health::TsFeature;
73 use std::fs;
75 pub const TYPABLE_COMMANDS_MD_OUTPUT: &str = "typable-cmd.md";
76 pub const LANG_SUPPORT_MD_OUTPUT: &str = "lang-support.md";
78 fn md_table_heading(cols: &[String]) -> String {
79 let mut header = String::new();
80 header += &md_table_row(cols);
81 header += &md_table_row(&vec!["---".to_string(); cols.len()]);
82 header
83 }
85 fn md_table_row(cols: &[String]) -> String {
86 format!("| {} |\n", cols.join(" | "))
87 }
89 fn md_mono(s: &str) -> String {
90 format!("`{}`", s)
91 }
93 pub fn typable_commands() -> Result<String, DynError> {
94 let mut md = String::new();
95 md.push_str(&md_table_heading(&[
96 "Name".to_owned(),
97 "Description".to_owned(),
98 ]));
100 let cmdify = |s: &str| format!("`:{}`", s);
102 for cmd in TYPABLE_COMMAND_LIST {
103 let names = std::iter::once(&cmd.name)
104 .chain(cmd.aliases.iter())
105 .map(|a| cmdify(a))
106 .collect::<Vec<_>>()
107 .join(", ");
109 let doc = cmd.doc.replace('\n', "<br>");
111 md.push_str(&md_table_row(&[names.to_owned(), doc.to_owned()]));
112 }
114 Ok(md)
115 }
117 pub fn lang_features() -> Result<String, DynError> {
118 let mut md = String::new();
119 let ts_features = TsFeature::all();
121 let mut cols = vec!["Language".to_owned()];
122 cols.append(
123 &mut ts_features
124 .iter()
125 .map(|t| t.long_title().to_string())
126 .collect::<Vec<_>>(),
127 );
128 cols.push("Default LSP".to_owned());
130 md.push_str(&md_table_heading(&cols));
131 let config = helpers::lang_config();
133 let mut langs = config
134 .language
135 .iter()
136 .map(|l| l.language_id.clone())
137 .collect::<Vec<_>>();
138 langs.sort_unstable();
140 let mut ts_features_to_langs = Vec::new();
141 for &feat in ts_features {
142 ts_features_to_langs.push((feat, helpers::ts_lang_support(feat)));
143 }
145 let mut row = Vec::new();
146 for lang in langs {
147 let lc = config
148 .language
149 .iter()
150 .find(|l| l.language_id == lang)
151 .unwrap(); // lang comes from config
152 row.push(lc.language_id.clone());
154 for (_feat, support_list) in &ts_features_to_langs {
155 row.push(
156 if support_list.contains(&lang) {
157 "✓"
158 } else {
159 ""
160 }
161 .to_owned(),
162 );
163 }
164 row.push(
165 lc.language_server
166 .as_ref()
167 .map(|s| s.command.clone())
168 .map(|c| md_mono(&c))
169 .unwrap_or_default(),
170 );
172 md.push_str(&md_table_row(&row));
173 row.clear();
174 }
176 Ok(md)
177 }
179 pub fn write(filename: &str, data: &str) {
180 let error = format!("Could not write to {}", filename);
181 let path = path::book_gen().join(filename);
182 fs::write(path, data).expect(&error);
183 }
186pub mod path {
187 use std::path::{Path, PathBuf};
189 pub fn project_root() -> PathBuf {
190 Path::new(env!("CARGO_MANIFEST_DIR"))
191 .parent()
192 .unwrap()
193 .to_path_buf()
194 }
196 pub fn book_gen() -> PathBuf {
197 project_root().join("book/src/generated/")
198 }
200 pub fn ts_queries() -> PathBuf {
201 project_root().join("runtime/queries")
202 }
204 pub fn lang_config() -> PathBuf {
205 project_root().join("languages.toml")
206 }
209pub mod tasks {
210 use crate::md_gen;
211 use crate::DynError;
213 pub fn docgen() -> Result<(), DynError> {
214 use md_gen::*;
215 write(TYPABLE_COMMANDS_MD_OUTPUT, &typable_commands()?);
216 write(LANG_SUPPORT_MD_OUTPUT, &lang_features()?);
217 Ok(())
218 }
220 pub fn print_help() {
221 println!(
222 "
223Usage: Run with `cargo xtask <task>`, eg. `cargo xtask docgen`.
225 Tasks:
226 docgen: Generate files to be included in the mdbook output.
228 );
229 }
232fn main() -> Result<(), DynError> {
233 let task = env::args().nth(1);
234 match task {
235 None => tasks::print_help(),
236 Some(t) => match t.as_str() {
237 "docgen" => tasks::docgen()?,
238 invalid => return Err(format!("Invalid task name: {}", invalid).into()),
239 },
240 };
241 Ok(())