cmdtree/
lib.rs

1//! [![Build Status](https://travis-ci.com/kurtlawrence/cmdtree.svg?branch=master)](https://travis-ci.com/kurtlawrence/cmdtree)
2//! [![Latest Version](https://img.shields.io/crates/v/cmdtree.svg)](https://crates.io/crates/cmdtree)
3//! [![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/cmdtree)
4//! [![codecov](https://codecov.io/gh/kurtlawrence/cmdtree/branch/master/graph/badge.svg)](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}