Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_syntax/src/core.rs

8.0 KiB, 37 runs

created by r1870400018:1047, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1use crate::{
2 arg::{
3 Arg,
4 ArgConfig,
5 },
6 cmd::Cmd,
7 help::Topic,
8 key::Key,
9 val::Val,
10};
11
12use oxedyne_fe2o3_core::{
13 prelude::*,
14 map::{
15 Recursive,
16 MapRec,
17 },
18};
19use oxedyne_fe2o3_jdat::version::SemVer;
20
21use std::{
22 collections::BTreeMap,
23 fmt,
24 sync::Arc,
25};
26
27
28#[derive(Clone, Debug, Default)]
29pub struct Syntax {
30 pub cfg: SyntaxConfig,
31 pub args: BTreeMap<Key, Recursive<Key, Arg>>,
32 pub cmds: BTreeMap<Key, Recursive<Key, Cmd>>,
33 // Binary
34 pub next_arg_id: u16,
35 pub next_cmd_id: u16,
36}
37
38#[derive(Clone, Debug)]
39pub struct SyntaxPrefs {
40 pub arg_hyph1_pfx: String,
41 pub arg_hyph2_pfx: String,
42}
43
44impl Default for SyntaxPrefs {
45 fn default() -> Self {
46 Self {
47 arg_hyph1_pfx: fmt!("-"),
48 arg_hyph2_pfx: fmt!("--"),
49 }
50 }
51}
52
53#[derive(Clone, Debug, Default)]
54pub struct SyntaxConfig {
55 pub name: String,
56 pub ver: SemVer,
57 pub vals: Vec<Val>, // Expected message values.
58 pub rargs: Vec<String>, // Required arguments.
59 pub cmds: BTreeMap<Key, Recursive<Key, Cmd>>,
60 pub one_cmd:bool, // A message names exactly one command.
61 // CLI
62 pub author: Option<String>,
63 pub about: Option<String>,
64 pub width: usize, // text width for screen output
65 pub topics: Vec<Topic>, // Named prose pages for help.
66 pub footer: Option<String>, // Prose closing the top help page.
67 // Customisation.
68 pub prefs: SyntaxPrefs,
69}
70
71impl From<SyntaxConfig> for Syntax {
72 fn from(cfg: SyntaxConfig) -> Self {
73 Self {
74 cfg: cfg,
75 ..Default::default()
76 }
77 }
78}
79
80impl Syntax {
81
82 pub const HELP_COL1: usize = 5;
83 pub const HELP_COL2: usize = 15;
84 pub const HELP_COL4: usize = 50;
85
86 /// Its ok for the `Syntax` name to contain separator characters such as spaces.
87 pub fn new<S: Into<String>>(name: S) -> Self {
88 let cfg = SyntaxConfig {
89 name: name.into(),
90 width: 80,
91 ..Default::default()
92 };
93 Self {
94 cfg: cfg,
95 ..Default::default()
96 }
97 }
98
99 pub fn config(&self) -> &SyntaxConfig { &self.cfg }
100
101 pub fn inc_counter(counter: u16, desc: String) -> Outcome<u16> {
102 match counter.checked_add(1) {
103 Some(i) => Ok(i),
104 None => {
105 return Err(err!(
106 "The id counter for {} has reached its upper limit of {}", desc, u16::MAX;
107 Counter, Overflow));
108 },
109 }
110 }
111
112 pub fn add_arg(mut self, mut a: Arg) -> Outcome<Self> {
113 a.id = self.next_arg_id;
114 self.next_arg_id = res!(Syntax::inc_counter(
115 self.next_arg_id,
116 fmt!("syntax '{}' arguments", self),
117 ));
118 res!(a.attach_arg(
119 &mut self.args,
120 &mut self.cfg.rargs,
121 ));
122 Ok(self)
123 }
124
125 pub fn remove_arg<K: Into<Key>>(mut self, key: K) -> Self {
126 let key = key.into();
127 let mut id_opt = None;
128 if let Some(Recursive::Key(id)) = self.args.get(&key) {
129 id_opt = Some(id.clone());
130 }
131 if let Some(id) = id_opt {
132 self.args.remove(&id.clone());
133 }
134 self.args.remove(&key);
135 self
136 }
137
138 /// Add a command to a syntax.
139 pub fn add_cmd(mut self, mut c: Cmd) -> Outcome<Self> {
140 let owner = fmt!("command '{}'", c.config().name);
141 res!(Val::check_shape(&c.config().vals, &owner));
142 if let Some(rest) = &c.config().rest {
143 res!(Val::check_shape(std::slice::from_ref(rest), &owner));
144 if let Some(last) = c.config().vals.last() {
145 if last.verbatim && last.arity.repeats() {
146 return Err(err!(
147 "The {} ends on the verbatim value '{}', which takes every word \
148 left, so no word could reach its '--' rest.", owner, last.label();
149 Input, Invalid));
150 }
151 }
152 }
153 c.id = self.next_cmd_id;
154 self.next_cmd_id = res!(Syntax::inc_counter(
155 self.next_cmd_id,
156 fmt!("syntax '{}' commands", self),
157 ));
158 self.cmds.insert(Key::Str(c.config().name.clone()), Recursive::Key(Key::Id(c.id)));
159 self.cmds.insert(Key::Id(c.id), Recursive::Val(c));
160 Ok(self)
161 }
162
163 pub fn remove_cmd<K: Into<Key>>(mut self, key: K) -> Self {
164 let key = key.into();
165 let mut id_opt = None;
166 if let Some(Recursive::Key(id)) = self.cmds.get(&key) {
167 id_opt = Some(id.clone());
168 }
169 if let Some(id) = id_opt {
170 self.cmds.remove(&id);
171 }
172 self.cmds.remove(&key);
173 self
174 }
175
176 pub fn get_cmd<K: Into<Key>>(&self, key: K) -> Option<&Cmd> {
177 let key = key.into();
178 self.cmds.get_recursive(&key)
179 }
180
181 pub fn expected_vals<V: Into<Val>>(mut self, vals: Vec<V>) -> Self {
182 self.cfg.vals = vals.into_iter().map(|v| v.into()).collect();
183 self
184 }
185
186 /// The commands in the order they were added.
187 pub fn cmds_in_order(&self) -> Vec<&Cmd> {
188 self.cmds.iter().filter_map(|(k, v)| match (k, v) {
189 (Key::Id(_), Recursive::Val(cmd)) => Some(cmd),
190 _ => None,
191 }).collect()
192 }
193
194 /// The message arguments in the order they were added.
195 pub fn args_in_order(&self) -> Vec<&Arg> {
196 self.args.iter().filter_map(|(k, v)| match (k, v) {
197 (Key::Id(_), Recursive::Val(arg)) => Some(arg),
198 _ => None,
199 }).collect()
200 }
201
202 pub fn get_topic(&self, name: &str) -> Option<&Topic> {
203 self.cfg.topics.iter().find(|t| t.name == name)
204 }
205
206 pub fn add_topic(mut self, topic: Topic) -> Outcome<Self> {
207 if self.get_topic(&topic.name).is_some() {
208 return Err(err!(
209 "The syntax '{}' already has a help topic '{}'.", self, topic.name;
210 Input, Exists));
211 }
212 if self.get_cmd(topic.name.as_str()).is_some() {
213 return Err(err!(
214 "The help topic '{}' has the name of a command in the syntax '{}', so \
215 'help {}' could not tell them apart.", topic.name, self, topic.name;
216 Input, Exists));
217 }
218 self.cfg.topics.push(topic);
219 Ok(self)
220 }
221
222 pub fn one_cmd(mut self, b: bool) -> Self {
223 self.cfg.one_cmd = b;
224 self
225 }
226
227 /// Add help as a command.
228 pub fn with_default_help_cmd(self) -> Outcome<Self> {
229 let c = res!(Cmd::new("help"))
230 .help("Display helpful information");
231 self.add_cmd(c)
232 }
233
234 /// Add help as an argument.
235 pub fn with_default_help_arg(self) -> Outcome<Self> {
236 let cfg = ArgConfig {
237 name: fmt!("help"),
238 hyph1: Some(fmt!("h")),
239 hyph2: Some(fmt!("help")),
240 reqd: false,
241 help: Some(fmt!("Display helpful information")),
242 ..Default::default()
243 };
244 self.add_arg(Arg {
245 cfg: cfg,
246 ..Default::default()
247 })
248 }
249
250 pub fn ver(mut self, v: SemVer) -> Self {
251 self.cfg.ver = v;
252 self
253 }
254
255 pub fn about<S: Into<String>>(mut self, s: S) -> Self {
256 self.cfg.about = Some(s.into());
257 self
258 }
259
260 pub fn set_width(mut self, w: usize) {
261 self.cfg.width = w;
262 }
263
264}
265
266impl fmt::Display for Syntax {
267 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268 write!(f,
269 "{}V{}",
270 self.config().name,
271 self.config().ver,
272 )
273 }
274}
275
276new_type!(SyntaxRef, Arc<Syntax>, Clone, Debug, Default);
277
278/// This newtype makes it easier to give spawned `Msg` and `MsgCmd` objects their own immutable
279/// reference to `Syntax`. For a slight performance cost there is no need for lifetime
280/// annotation and certain compile time borrowing checks encountered during message decoding are
281/// pushed to runtime.
282impl SyntaxRef {
283
284 pub fn new(syntax: Syntax) -> Self {
285 Self(Arc::new(syntax))
286 }
287}