coding_tools/
completion.rs1use std::sync::Arc;
16
17use veks_completion::{CommandTree, Node, ValueProvider};
18
19use crate::{cli, rules};
20
21#[derive(Clone, Copy)]
23enum Field {
24 Id,
25 Tag,
26 Def,
27}
28
29fn store_values(field: Field) -> Vec<String> {
33 let Ok(cwd) = std::env::current_dir() else {
34 return Vec::new();
35 };
36 let Some(root) = rules::discover_root(&cwd) else {
37 return Vec::new();
38 };
39 let Ok(text) = std::fs::read_to_string(rules::store_path(&root)) else {
40 return Vec::new();
41 };
42 let Ok(store) = rules::parse_store(&text) else {
43 return Vec::new();
44 };
45 match field {
46 Field::Id => store.rules.iter().map(|r| r.id.clone()).collect(),
47 Field::Tag => {
48 let mut tags: Vec<String> = store
49 .rules
50 .iter()
51 .flat_map(|r| r.tags.iter().cloned())
52 .collect();
53 tags.sort();
54 tags.dedup();
55 tags
56 }
57 Field::Def => store.defs.keys().cloned().collect(),
58 }
59}
60
61fn store_provider(field: Field) -> ValueProvider {
63 Arc::new(move |partial: &str, _: &[&str]| {
64 store_values(field)
65 .into_iter()
66 .filter(|v| v.starts_with(partial))
67 .collect()
68 })
69}
70
71fn set_provider(values: Vec<String>) -> ValueProvider {
73 Arc::new(move |partial: &str, _: &[&str]| {
74 values
75 .iter()
76 .filter(|v| v.starts_with(partial))
77 .cloned()
78 .collect()
79 })
80}
81
82fn with_store_providers(sub: &str, node: Node) -> Node {
85 match sub {
86 "check" => node
87 .with_value_provider("--id", store_provider(Field::Id))
88 .with_value_provider("--tag", store_provider(Field::Tag)),
89 "rules" => node
90 .with_value_provider("--promote", store_provider(Field::Id))
91 .with_value_provider("--remove", store_provider(Field::Id))
92 .with_value_provider("--tag", store_provider(Field::Tag))
93 .with_value_provider("--def", store_provider(Field::Def)),
94 _ => node,
95 }
96}
97
98pub fn command_tree() -> CommandTree {
102 let mut tree = CommandTree::new("ct");
103 for (name, grammar) in cli::grammars() {
104 let sub = name.strip_prefix("ct-").unwrap_or(name);
105 let value_flags: Vec<String> = grammar
106 .flags
107 .iter()
108 .filter(|f| f.kind != "boolean")
109 .map(|f| format!("--{}", f.name))
110 .collect();
111 let bool_flags: Vec<String> = grammar
112 .flags
113 .iter()
114 .filter(|f| f.kind == "boolean")
115 .map(|f| format!("--{}", f.name))
116 .collect();
117 let vf: Vec<&str> = value_flags.iter().map(String::as_str).collect();
118 let bf: Vec<&str> = bool_flags.iter().map(String::as_str).collect();
119
120 let mut node = Node::leaf_with_flags(&vf, &bf);
121 for f in grammar
122 .flags
123 .iter()
124 .filter(|f| f.kind != "boolean" && !f.values.is_empty())
125 {
126 node =
127 node.with_value_provider(&format!("--{}", f.name), set_provider(f.values.clone()));
128 }
129 node = with_store_providers(sub, node);
130 tree = tree.command(sub, node);
131 }
132 let shells = ["bash", "zsh", "fish"]
135 .iter()
136 .map(|s| s.to_string())
137 .collect();
138 tree = tree.command(
139 "completions",
140 Node::leaf(&[]).with_positional_provider(set_provider(shells)),
141 );
142 tree
143}