diff options
Diffstat (limited to 'xtask/src/codegen')
-rw-r--r-- | xtask/src/codegen/gen_assists_docs.rs | 83 | ||||
-rw-r--r-- | xtask/src/codegen/gen_feature_docs.rs | 75 |
2 files changed, 116 insertions, 42 deletions
diff --git a/xtask/src/codegen/gen_assists_docs.rs b/xtask/src/codegen/gen_assists_docs.rs index 20dcde820..6c1be5350 100644 --- a/xtask/src/codegen/gen_assists_docs.rs +++ b/xtask/src/codegen/gen_assists_docs.rs | |||
@@ -1,22 +1,28 @@ | |||
1 | //! Generates `assists.md` documentation. | 1 | //! Generates `assists.md` documentation. |
2 | 2 | ||
3 | use std::{fs, path::Path}; | 3 | use std::{fmt, fs, path::Path}; |
4 | 4 | ||
5 | use crate::{ | 5 | use crate::{ |
6 | codegen::{self, extract_comment_blocks_with_empty_lines, Mode}, | 6 | codegen::{self, extract_comment_blocks_with_empty_lines, Location, Mode}, |
7 | project_root, rust_files, Result, | 7 | project_root, rust_files, Result, |
8 | }; | 8 | }; |
9 | 9 | ||
10 | pub fn generate_assists_docs(mode: Mode) -> Result<()> { | 10 | pub fn generate_assists_docs(mode: Mode) -> Result<()> { |
11 | let assists = Assist::collect()?; | 11 | let assists = Assist::collect()?; |
12 | generate_tests(&assists, mode)?; | 12 | generate_tests(&assists, mode)?; |
13 | generate_docs(&assists, mode)?; | 13 | |
14 | let contents = assists.into_iter().map(|it| it.to_string()).collect::<Vec<_>>().join("\n\n"); | ||
15 | let contents = contents.trim().to_string() + "\n"; | ||
16 | let dst = project_root().join("docs/user/generated_assists.adoc"); | ||
17 | codegen::update(&dst, &contents, mode)?; | ||
18 | |||
14 | Ok(()) | 19 | Ok(()) |
15 | } | 20 | } |
16 | 21 | ||
17 | #[derive(Debug)] | 22 | #[derive(Debug)] |
18 | struct Assist { | 23 | struct Assist { |
19 | id: String, | 24 | id: String, |
25 | location: Location, | ||
20 | doc: String, | 26 | doc: String, |
21 | before: String, | 27 | before: String, |
22 | after: String, | 28 | after: String, |
@@ -33,22 +39,18 @@ impl Assist { | |||
33 | 39 | ||
34 | fn collect_file(acc: &mut Vec<Assist>, path: &Path) -> Result<()> { | 40 | fn collect_file(acc: &mut Vec<Assist>, path: &Path) -> Result<()> { |
35 | let text = fs::read_to_string(path)?; | 41 | let text = fs::read_to_string(path)?; |
36 | let comment_blocks = extract_comment_blocks_with_empty_lines(&text); | 42 | let comment_blocks = extract_comment_blocks_with_empty_lines("Assist", &text); |
37 | 43 | ||
38 | for block in comment_blocks { | 44 | for block in comment_blocks { |
39 | // FIXME: doesn't support blank lines yet, need to tweak | 45 | // FIXME: doesn't support blank lines yet, need to tweak |
40 | // `extract_comment_blocks` for that. | 46 | // `extract_comment_blocks` for that. |
41 | let mut lines = block.iter(); | 47 | let id = block.id; |
42 | let first_line = lines.next().unwrap(); | ||
43 | if !first_line.starts_with("Assist: ") { | ||
44 | continue; | ||
45 | } | ||
46 | let id = first_line["Assist: ".len()..].to_string(); | ||
47 | assert!( | 48 | assert!( |
48 | id.chars().all(|it| it.is_ascii_lowercase() || it == '_'), | 49 | id.chars().all(|it| it.is_ascii_lowercase() || it == '_'), |
49 | "invalid assist id: {:?}", | 50 | "invalid assist id: {:?}", |
50 | id | 51 | id |
51 | ); | 52 | ); |
53 | let mut lines = block.contents.iter(); | ||
52 | 54 | ||
53 | let doc = take_until(lines.by_ref(), "```").trim().to_string(); | 55 | let doc = take_until(lines.by_ref(), "```").trim().to_string(); |
54 | assert!( | 56 | assert!( |
@@ -62,7 +64,8 @@ impl Assist { | |||
62 | assert_eq!(lines.next().unwrap().as_str(), "->"); | 64 | assert_eq!(lines.next().unwrap().as_str(), "->"); |
63 | assert_eq!(lines.next().unwrap().as_str(), "```"); | 65 | assert_eq!(lines.next().unwrap().as_str(), "```"); |
64 | let after = take_until(lines.by_ref(), "```"); | 66 | let after = take_until(lines.by_ref(), "```"); |
65 | acc.push(Assist { id, doc, before, after }) | 67 | let location = Location::new(path.to_path_buf(), block.line); |
68 | acc.push(Assist { id, location, doc, before, after }) | ||
66 | } | 69 | } |
67 | 70 | ||
68 | fn take_until<'a>(lines: impl Iterator<Item = &'a String>, marker: &str) -> String { | 71 | fn take_until<'a>(lines: impl Iterator<Item = &'a String>, marker: &str) -> String { |
@@ -80,6 +83,33 @@ impl Assist { | |||
80 | } | 83 | } |
81 | } | 84 | } |
82 | 85 | ||
86 | impl fmt::Display for Assist { | ||
87 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
88 | let before = self.before.replace("<|>", "┃"); // Unicode pseudo-graphics bar | ||
89 | let after = self.after.replace("<|>", "┃"); | ||
90 | writeln!( | ||
91 | f, | ||
92 | "[discrete]\n=== `{}` | ||
93 | **Source:** {} | ||
94 | |||
95 | {} | ||
96 | |||
97 | .Before | ||
98 | ```rust | ||
99 | {}``` | ||
100 | |||
101 | .After | ||
102 | ```rust | ||
103 | {}```", | ||
104 | self.id, | ||
105 | self.location, | ||
106 | self.doc, | ||
107 | hide_hash_comments(&before), | ||
108 | hide_hash_comments(&after) | ||
109 | ) | ||
110 | } | ||
111 | } | ||
112 | |||
83 | fn generate_tests(assists: &[Assist], mode: Mode) -> Result<()> { | 113 | fn generate_tests(assists: &[Assist], mode: Mode) -> Result<()> { |
84 | let mut buf = String::from("use super::check_doc_test;\n"); | 114 | let mut buf = String::from("use super::check_doc_test;\n"); |
85 | 115 | ||
@@ -107,37 +137,6 @@ r#####" | |||
107 | codegen::update(&project_root().join(codegen::ASSISTS_TESTS), &buf, mode) | 137 | codegen::update(&project_root().join(codegen::ASSISTS_TESTS), &buf, mode) |
108 | } | 138 | } |
109 | 139 | ||
110 | fn generate_docs(assists: &[Assist], mode: Mode) -> Result<()> { | ||
111 | let mut buf = String::from( | ||
112 | "# Assists\n\nCursor position or selection is signified by `┃` character.\n\n", | ||
113 | ); | ||
114 | |||
115 | for assist in assists { | ||
116 | let before = assist.before.replace("<|>", "┃"); // Unicode pseudo-graphics bar | ||
117 | let after = assist.after.replace("<|>", "┃"); | ||
118 | let docs = format!( | ||
119 | " | ||
120 | ## `{}` | ||
121 | |||
122 | {} | ||
123 | |||
124 | ```rust | ||
125 | // BEFORE | ||
126 | {} | ||
127 | // AFTER | ||
128 | {}``` | ||
129 | ", | ||
130 | assist.id, | ||
131 | assist.doc, | ||
132 | hide_hash_comments(&before), | ||
133 | hide_hash_comments(&after) | ||
134 | ); | ||
135 | buf.push_str(&docs); | ||
136 | } | ||
137 | |||
138 | codegen::update(&project_root().join(codegen::ASSISTS_DOCS), &buf, mode) | ||
139 | } | ||
140 | |||
141 | fn hide_hash_comments(text: &str) -> String { | 140 | fn hide_hash_comments(text: &str) -> String { |
142 | text.split('\n') // want final newline | 141 | text.split('\n') // want final newline |
143 | .filter(|&it| !(it.starts_with("# ") || it == "#")) | 142 | .filter(|&it| !(it.starts_with("# ") || it == "#")) |
diff --git a/xtask/src/codegen/gen_feature_docs.rs b/xtask/src/codegen/gen_feature_docs.rs new file mode 100644 index 000000000..31bc3839d --- /dev/null +++ b/xtask/src/codegen/gen_feature_docs.rs | |||
@@ -0,0 +1,75 @@ | |||
1 | //! Generates `assists.md` documentation. | ||
2 | |||
3 | use std::{fmt, fs, path::PathBuf}; | ||
4 | |||
5 | use crate::{ | ||
6 | codegen::{self, extract_comment_blocks_with_empty_lines, Location, Mode}, | ||
7 | project_root, rust_files, Result, | ||
8 | }; | ||
9 | |||
10 | pub fn generate_feature_docs(mode: Mode) -> Result<()> { | ||
11 | let features = Feature::collect()?; | ||
12 | let contents = features.into_iter().map(|it| it.to_string()).collect::<Vec<_>>().join("\n\n"); | ||
13 | let contents = contents.trim().to_string() + "\n"; | ||
14 | let dst = project_root().join("docs/user/generated_features.adoc"); | ||
15 | codegen::update(&dst, &contents, mode)?; | ||
16 | Ok(()) | ||
17 | } | ||
18 | |||
19 | #[derive(Debug)] | ||
20 | struct Feature { | ||
21 | id: String, | ||
22 | location: Location, | ||
23 | doc: String, | ||
24 | } | ||
25 | |||
26 | impl Feature { | ||
27 | fn collect() -> Result<Vec<Feature>> { | ||
28 | let mut res = Vec::new(); | ||
29 | for path in rust_files(&project_root()) { | ||
30 | collect_file(&mut res, path)?; | ||
31 | } | ||
32 | res.sort_by(|lhs, rhs| lhs.id.cmp(&rhs.id)); | ||
33 | return Ok(res); | ||
34 | |||
35 | fn collect_file(acc: &mut Vec<Feature>, path: PathBuf) -> Result<()> { | ||
36 | let text = fs::read_to_string(&path)?; | ||
37 | let comment_blocks = extract_comment_blocks_with_empty_lines("Feature", &text); | ||
38 | |||
39 | for block in comment_blocks { | ||
40 | let id = block.id; | ||
41 | assert!(is_valid_feature_name(&id), "invalid feature name: {:?}", id); | ||
42 | let doc = block.contents.join("\n"); | ||
43 | let location = Location::new(path.clone(), block.line); | ||
44 | acc.push(Feature { id, location, doc }) | ||
45 | } | ||
46 | |||
47 | Ok(()) | ||
48 | } | ||
49 | } | ||
50 | } | ||
51 | |||
52 | fn is_valid_feature_name(feature: &str) -> bool { | ||
53 | 'word: for word in feature.split_whitespace() { | ||
54 | for &short in ["to", "and"].iter() { | ||
55 | if word == short { | ||
56 | continue 'word; | ||
57 | } | ||
58 | } | ||
59 | for &short in ["To", "And"].iter() { | ||
60 | if word == short { | ||
61 | return false; | ||
62 | } | ||
63 | } | ||
64 | if !word.starts_with(char::is_uppercase) { | ||
65 | return false; | ||
66 | } | ||
67 | } | ||
68 | true | ||
69 | } | ||
70 | |||
71 | impl fmt::Display for Feature { | ||
72 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
73 | writeln!(f, "=== {}\n**Source:** {}\n{}", self.id, self.location, self.doc) | ||
74 | } | ||
75 | } | ||