cmdtree/lib.rs
1//! [](https://travis-ci.com/kurtlawrence/cmdtree)
2//! [](https://crates.io/crates/cmdtree)
3//! [](https://docs.rs/cmdtree)
4//! [](https://codecov.io/gh/kurtlawrence/cmdtree)
5//!
6//! (Rust) commands tree.
7//!
8//! See the [rs docs](https://docs.rs/cmdtree/).
9//! Look at progress and contribute on [github.](https://github.com/kurtlawrence/cmdtree)
10//!
11//! # cmdtree
12//!
13//! Create a tree-like data structure of commands and actions to add an intuitive and interactive experience to an application.
14//! cmdtree uses a builder pattern to make constructing the tree ergonomic.
15//!
16//! # Example
17//!
18//! ```rust,no_run
19//! extern crate cmdtree;
20//! use cmdtree::*;
21//!
22//! fn main() {
23//! let cmder = Builder::default_config("cmdtree-example")
24//! .begin_class("class1", "class1 help message") // a class
25//! .begin_class("inner-class1", "nested class!") // can nest a class
26//! .add_action("name", "print class name", |mut wtr, _args| {
27//! writeln!(wtr, "inner-class1",).unwrap()
28//! })
29//! .end_class()
30//! .end_class() // closes out the classes
31//! .begin_class("print", "pertains to printing stuff") // start another class sibling to `class1`
32//! .add_action("echo", "repeat stuff", |mut wtr, args| {
33//! writeln!(wtr, "{}", args.join(" ")).unwrap()
34//! })
35//! .add_action("countdown", "countdown from a number", |mut wtr, args| {
36//! if args.len() != 1 {
37//! println!("need one number",);
38//! } else {
39//! match str::parse::<u32>(args[0]) {
40//! Ok(n) => {
41//! for i in (0..=n).rev() {
42//! writeln!(wtr, "{}", i).unwrap();
43//! }
44//! }
45//! Err(_) => writeln!(wtr, "expecting a number!",).unwrap(),
46//! }
47//! }
48//! })
49//! .into_commander() // can short-circuit the closing out of classes
50//! .unwrap();
51//!
52//! cmder.run(); // run interactively
53//! }
54//! ```
55//!
56//! Now run and in your shell:
57//!
58//! ```sh
59//! cmdtree-example=> help <-- Will print help messages
60//! help -- prints the help messages
61//! cancel | c -- returns to the root class
62//! exit -- sends the exit signal to end the interactive loop
63//! Classes:
64//! class1 -- class1 help message
65//! print -- pertains to printing stuff
66//! cmdtree-example=> print <-- Can navigate the tree
67//! cmdtree-example.print=> help
68//! help -- prints the help messages
69//! cancel | c -- returns to the root class
70//! exit -- sends the exit signal to end the interactive loop
71//! Actions:
72//! echo -- repeat stuff
73//! countdown -- countdown from a number
74//! cmdtree-example.print=> echo hello, world! <-- Call the actions
75//! hello, world!
76//! cmdtree-example.print=> countdown
77//! need one number
78//! cmdtree-example.print=> countdown 10
79//! 10
80//! 9
81//! 8
82//! 7
83//! 6
84//! 5
85//! 4
86//! 3
87//! 2
88//! 1
89//! 0
90//! cmdtree-example.print=> exit <-- exit the loop!
91//! ```
92
93#![warn(missing_docs)]
94
95use std::borrow::Cow;
96use std::cmp::Ordering;
97use std::collections::BTreeSet;
98use std::fmt;
99use std::io::Write;
100use std::ops::Deref;
101use std::sync::{Arc, Mutex};
102
103pub mod builder;
104pub mod completion;
105mod parse;
106
107pub use self::parse::LineResult;
108pub use builder::{BuildError, Builder, BuilderChain};
109
110/// A constructed command tree.
111///
112/// Most of the time a user will want to use `run()` which will handle all the parsing and navigating of the tree.
113/// Alternatively, `parse_line` can be used to simulate a read input and update the command tree position.
114///
115/// To construct a command tree, look at the [`builder` module](./builder/index.html).
116pub struct Commander<R> {
117 root: Arc<SubClass<R>>,
118 current: Arc<SubClass<R>>,
119 path: String,
120}
121
122impl<R> Commander<R> {
123 /// Return the root name.
124 ///
125 /// # Example
126 /// ```rust
127 /// # use cmdtree::*;
128 /// let mut cmder = Builder::default_config("base")
129 /// .begin_class("one", "")
130 /// .begin_class("two", "")
131 /// .into_commander().unwrap();
132 ///
133 /// assert_eq!(cmder.root_name(), "base");
134 /// ```
135 pub fn root_name(&self) -> &str {
136 &self.root.name
137 }
138
139 /// Return the path of the current class, separated by `.`.
140 ///
141 /// # Example
142 /// ```rust
143 /// # use cmdtree::*;
144 /// let mut cmder = Builder::default_config("base")
145 /// .begin_class("one", "")
146 /// .begin_class("two", "")
147 /// .into_commander().unwrap();
148 ///
149 /// assert_eq!(cmder.path(), "base");
150 /// cmder.parse_line("one two", true, &mut std::io::sink());
151 /// assert_eq!(cmder.path(), "base.one.two");
152 /// ```
153 pub fn path(&self) -> &str {
154 &self.path
155 }
156
157 /// Returns if the commander is sitting at the root class.
158 ///
159 /// # Example
160 /// ```rust
161 /// # use cmdtree::*;
162 /// let mut cmder = Builder::default_config("base")
163 /// .begin_class("one", "")
164 /// .begin_class("two", "")
165 /// .into_commander().unwrap();
166 ///
167 /// assert!(cmder.at_root());
168 /// cmder.parse_line("one two", true, &mut std::io::sink());
169 /// assert_eq!(cmder.at_root(), false);
170 /// ```
171 pub fn at_root(&self) -> bool {
172 self.current == self.root
173 }
174
175 /// Run the `Commander` interactively.
176 /// Consumes the instance, and blocks the thread until the loop is exited.
177 ///
178 /// This is the most simple way of using a `Commander`.
179 #[cfg(feature = "runnable")]
180 pub fn run(self) {
181 self.run_with_completion(|_| linefeed::complete::DummyCompleter)
182 }
183
184 /// Returns the command structure as a sorted set.
185 ///
186 /// Can return from the the current class or the root.
187 ///
188 /// Each item is a dot separated path, except for actions which are separated by a double dot.
189 ///
190 /// # Examples
191 /// ```rust
192 /// # use cmdtree::*;
193 /// let cmder = Builder::default_config("base")
194 /// .begin_class("one", "")
195 /// .begin_class("two", "")
196 /// .end_class()
197 /// .add_action("action", "", |_,_| ())
198 /// .end_class()
199 /// .add_action("action", "", |_,_| ())
200 /// .into_commander().unwrap();
201 ///
202 /// let structure = cmder.structure(true);
203 ///
204 /// assert_eq!(structure.iter().map(|x| x.path.as_str()).collect::<Vec<_>>(), vec![
205 /// "..action",
206 /// "one",
207 /// "one..action",
208 /// "one.two",
209 /// ]);
210 /// ```
211 pub fn structure(&self, from_root: bool) -> BTreeSet<StructureInfo> {
212 let mut set = BTreeSet::new();
213
214 let mut stack: Vec<(String, _)> = {
215 let r = if from_root { &self.root } else { &self.current };
216
217 for action in r.actions.iter() {
218 set.insert(StructureInfo {
219 path: format!("..{}", action.name),
220 itemtype: ItemType::Action,
221 help_msg: action.help.clone(),
222 });
223 }
224
225 r.classes.iter().map(|x| (x.name.clone(), x)).collect()
226 };
227
228 while let Some(item) = stack.pop() {
229 let (parent_path, parent) = item;
230
231 for action in parent.actions.iter() {
232 set.insert(StructureInfo {
233 path: format!("{}..{}", parent_path, action.name),
234 itemtype: ItemType::Action,
235 help_msg: action.help.clone(),
236 });
237 }
238
239 for class in parent.classes.iter() {
240 stack.push((format!("{}.{}", parent_path, class.name), class));
241 }
242
243 set.insert(StructureInfo {
244 path: parent_path,
245 itemtype: ItemType::Class,
246 help_msg: parent.help.clone(),
247 });
248 }
249
250 set
251 }
252}
253
254#[derive(Debug, Eq)]
255struct SubClass<R> {
256 name: String,
257 help: CmdStr,
258 classes: Vec<Arc<SubClass<R>>>,
259 actions: Vec<Action<R>>,
260}
261
262impl<R> SubClass<R> {
263 fn with_name<H: Into<CmdStr>>(name: &str, help_msg: H) -> Self {
264 SubClass {
265 name: name.to_lowercase(),
266 help: help_msg.into(),
267 classes: Vec::new(),
268 actions: Vec::new(),
269 }
270 }
271}
272
273impl<R> PartialEq for SubClass<R> {
274 fn eq(&self, other: &Self) -> bool {
275 self.name == other.name
276 && self.help == other.help
277 && self.classes == other.classes
278 && self.actions == other.actions
279 }
280}
281
282type ClosureFn<R> = Box<dyn FnMut(&mut dyn Write, &[&str]) -> R + Send>;
283struct Action<R> {
284 name: String,
285 help: CmdStr,
286 closure: Mutex<ClosureFn<R>>,
287}
288
289impl<R> Action<R> {
290 fn call<W: Write>(&self, wtr: &mut W, arguments: &[&str]) -> R {
291 let c = &mut *self.closure.lock().expect("locking command action failed");
292 c(wtr, arguments)
293 }
294}
295
296impl Action<()> {
297 #[cfg(test)]
298 fn blank_fn<H: Into<CmdStr>>(name: &str, help_msg: H) -> Self {
299 Action {
300 name: name.to_lowercase(),
301 help: help_msg.into(),
302 closure: Mutex::new(Box::new(|_, _| ())),
303 }
304 }
305}
306
307impl<R> PartialEq for Action<R> {
308 fn eq(&self, other: &Self) -> bool {
309 self.name == other.name && self.help == other.help
310 }
311}
312
313impl<R> Eq for Action<R> {}
314
315impl<R> fmt::Debug for Action<R> {
316 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
317 write!(f, "Action {{ name: {}, help: {} }}", self.name, self.help)
318 }
319}
320
321/// An item in the command tree.
322pub struct StructureInfo {
323 /// Period delimited path. Actions are double delimted.
324 ///
325 /// Eg.
326 /// - A class: `a.nested.class`
327 /// - An action: `a.nested.class..action`
328 pub path: String,
329 /// Class or Action.
330 pub itemtype: ItemType,
331 /// The help message.
332 pub help_msg: CmdStr,
333}
334
335impl PartialEq for StructureInfo {
336 fn eq(&self, other: &StructureInfo) -> bool {
337 self.path == other.path
338 }
339}
340
341impl Eq for StructureInfo {}
342
343impl PartialOrd for StructureInfo {
344 fn partial_cmp(&self, other: &StructureInfo) -> Option<Ordering> {
345 Some(self.cmp(other))
346 }
347}
348
349impl Ord for StructureInfo {
350 fn cmp(&self, other: &StructureInfo) -> Ordering {
351 self.path.cmp(&other.path)
352 }
353}
354
355/// A command type.
356#[derive(Debug, PartialEq)]
357pub enum ItemType {
358 /// Class type.
359 Class,
360 /// Action type.
361 Action,
362}
363
364/// A command string can be static or owned.
365///
366/// Wraps a `Cow<'static, str>`.
367/// Implements `From<&'static str>` and `From<String>`.
368#[derive(Debug, PartialEq, Eq, Clone)]
369pub struct CmdStr {
370 /// Wrapped inner `Cow<'static, str>`.
371 pub inner_cow: Cow<'static, str>,
372}
373
374impl CmdStr {
375 /// Represent a string.
376 pub fn as_str(&self) -> &str {
377 &self.inner_cow
378 }
379}
380
381impl Deref for CmdStr {
382 type Target = str;
383 fn deref(&self) -> &str {
384 self.inner_cow.deref()
385 }
386}
387
388impl fmt::Display for CmdStr {
389 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390 write!(f, "{}", self.as_str())
391 }
392}
393
394impl From<&'static str> for CmdStr {
395 fn from(s: &'static str) -> Self {
396 Self {
397 inner_cow: Cow::Borrowed(s),
398 }
399 }
400}
401
402impl From<String> for CmdStr {
403 fn from(s: String) -> Self {
404 Self {
405 inner_cow: Cow::Owned(s),
406 }
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use super::*;
413
414 #[test]
415 fn subclass_with_name_test() {
416 let sc = SubClass::<()>::with_name("NAME", "Help Message");
417 assert_eq!(&sc.name, "name");
418 assert_eq!(sc.help.as_str(), "Help Message");
419 }
420
421 #[test]
422 fn action_debug_test() {
423 let a = Action::blank_fn("action-name", "help me!");
424 assert_eq!(
425 &format!("{:?}", a),
426 "Action { name: action-name, help: help me! }"
427 );
428 }
429
430 #[test]
431 fn current_path_test() {
432 let mut cmder = Builder::default_config("base")
433 .begin_class("one", "")
434 .begin_class("two", "")
435 .into_commander()
436 .unwrap();
437
438 let w = &mut std::io::sink();
439
440 assert_eq!(cmder.path(), "base");
441
442 cmder.parse_line("one two", true, w);
443 assert_eq!(cmder.path(), "base.one.two");
444
445 cmder.parse_line("c", true, w);
446 assert_eq!(cmder.path(), "base");
447
448 cmder.parse_line("one", true, w);
449 assert_eq!(cmder.path(), "base.one");
450 }
451
452 #[test]
453 fn root_test() {
454 let mut cmder = Builder::default_config("base")
455 .begin_class("one", "")
456 .begin_class("two", "")
457 .into_commander()
458 .unwrap();
459
460 let w = &mut std::io::sink();
461
462 assert_eq!(cmder.at_root(), true);
463
464 cmder.parse_line("one two", true, w);
465 assert_eq!(cmder.at_root(), false);
466
467 cmder.parse_line("c", true, w);
468 assert_eq!(cmder.at_root(), true);
469 }
470
471 #[test]
472 fn structure_test() {
473 let mut cmder = Builder::default_config("base")
474 .begin_class("one", "")
475 .begin_class("two", "")
476 .end_class()
477 .add_action("action", "", |_, _| ())
478 .end_class()
479 .add_action("action", "", |_, _| ())
480 .into_commander()
481 .unwrap();
482
483 cmder.parse_line("one", false, &mut std::io::sink());
484
485 let structure = cmder.structure(true);
486
487 assert_eq!(
488 structure
489 .iter()
490 .map(|x| x.path.as_str())
491 .collect::<Vec<_>>(),
492 vec!["..action", "one", "one..action", "one.two",]
493 );
494
495 let structure = cmder.structure(false);
496
497 assert_eq!(
498 structure
499 .iter()
500 .map(|x| x.path.as_str())
501 .collect::<Vec<_>>(),
502 vec!["..action", "two",]
503 );
504 }
505}