oxedyne/fe2o3/fe2o3_syntax/src/lib.rs
6.8 KiB, 46 runs
created by r1870400018:1053, 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
| 1 | //! A protocol-oriented syntax system for unified command handling across REPL and network interfaces. |
| 2 | //! |
| 3 | //! This crate provides tools for defining and processing commands in a structured way, whether they |
| 4 | //! originate from a text-based REPL or network messages. The core abstraction is the [`Syntax`] type, |
| 5 | //! which defines available commands, their arguments, and expected values. |
| 6 | //! |
| 7 | //! Rather than using callbacks or trait implementations, command handling is done through explicit |
| 8 | //! pattern matching, giving developers direct control over the command processing flow. This approach |
| 9 | //! favours simplicity and transparency over abstraction. |
| 10 | //! |
| 11 | //! # Example |
| 12 | //! |
| 13 | //! A syntax is built from configuration structs, a message is parsed against |
| 14 | //! it, and what the message said is read by matching rather than by dispatch. |
| 15 | //! |
| 16 | //! ```ignore |
| 17 | //! use oxedyne_fe2o3_syntax::{ |
| 18 | //! cmd::{Cmd, CmdConfig}, |
| 19 | //! core::{Syntax, SyntaxConfig, SyntaxRef}, |
| 20 | //! msg::Msg, |
| 21 | //! val::Val, |
| 22 | //! }; |
| 23 | //! use oxedyne_fe2o3_jdat::prelude::*; |
| 24 | //! use oxedyne_fe2o3_core::prelude::*; |
| 25 | //! |
| 26 | //! // A syntax, with one command that takes one string value. |
| 27 | //! let mut syntax = Syntax::from(SyntaxConfig { |
| 28 | //! name: fmt!("example"), |
| 29 | //! ..Default::default() |
| 30 | //! }); |
| 31 | //! let cmd = Cmd::from(CmdConfig { |
| 32 | //! name: fmt!("connect"), |
| 33 | //! vals: vec![Val::text("host").help("Host to connect to")], |
| 34 | //! help: Some(fmt!("Connect to a remote host")), |
| 35 | //! ..Default::default() |
| 36 | //! }); |
| 37 | //! syntax = res!(syntax.add_cmd(cmd)); |
| 38 | //! let syntax = SyntaxRef::new(syntax); |
| 39 | //! |
| 40 | //! // A message read against it. |
| 41 | //! let msg = res!(Msg::new(syntax).from_str("connect example.com", None)); |
| 42 | //! match msg.get_cmd("connect") { |
| 43 | //! Some(cmd) => match cmd.get_vals() { |
| 44 | //! Some(_vals) => (), // Handle the connect command. |
| 45 | //! None => (), // It named no host. |
| 46 | //! }, |
| 47 | //! None => (), // The message said something else. |
| 48 | //! } |
| 49 | //! ``` |
| 50 | //! |
| 51 | //! # Details |
| 52 | //! |
| 53 | //! A `Syntax` represents rules for communication in the Presentation Layer of the [OSI |
| 54 | //! Model](https://en.wikipedia.org/wiki/OSI_model). This generalises to a command line interface. |
| 55 | //! Messages are composed of one or more pre-defined commands. There can be a variable number of |
| 56 | //! arguments associated with the message and with each command. There can be a fixed number of |
| 57 | //! values ([Daticle](oxedyne_fe2o3_jdat::daticle::Daticle) of pre-defined `Kind`) for the message |
| 58 | //! and for each argument and command. |
| 59 | //! |
| 60 | //! Valid examples: |
| 61 | //! ```ignore |
| 62 | //! {invoc} v | 1 message val (required) |
| 63 | //! {invoc} v a v | 1 message val followed by 1 message arg and val |
| 64 | //! {invoc} a v a v v v |
| 65 | //! {invoc} a v a a c v v a v v a c v a a | multiple commands |
| 66 | //! ``` |
| 67 | //! |
| 68 | //! where |
| 69 | //! |
| 70 | //! ```ignore |
| 71 | //! {invoc} = invocation command (e.g. the program pathname when using a shell) |
| 72 | //! c = command |
| 73 | //! a = argument |
| 74 | //! v = value |
| 75 | //! ``` |
| 76 | //! |
| 77 | //! An argument comes in three possible versions, its prefixless name, or prefixed with one or two |
| 78 | //! hyphens; either hyphenated form may be absent. An argument without a value is an option (or |
| 79 | //! "switch"). Values from a REPL line or the wire are decoded as `Daticles` which protect single |
| 80 | //! and double quotes by default, and allow type specification, e.g. `(i16|-42)`. Arguments are |
| 81 | //! optional unless specified otherwise. Because values are daticles, you can use compound |
| 82 | //! daticles like `Kind::MAP` and `Kind::LIST` to embed a variable number of values. |
| 83 | //! |
| 84 | //! # Process command lines |
| 85 | //! |
| 86 | //! A syntax can also read a process's own arguments through [`argv::parse`], which answers |
| 87 | //! `help`, `--help` and `--version` itself. Set `SyntaxConfig::one_cmd` so that the command line |
| 88 | //! names exactly one command and its values may follow its options. There, a word the shell has |
| 89 | //! already unquoted is taken as it stands when a string is expected, a value may be optional or |
| 90 | //! repeated ([`val::Arity`]), and a command may take the words after `--` as its `rest`. |
| 91 | //! |
| 92 | //! `Syntax` attempts to unify: |
| 93 | //! - command line text interfaces (CLI or TUI) including one-time invocation with argument |
| 94 | //! passing, and interactive read-evaluate-print loops (REPLs), and |
| 95 | //! - over-the-wire (OTW) text and binary messages. |
| 96 | //! Multiple commands in a single message are permitted. A session begins when a user logs in, and |
| 97 | //! session state is maintained via a mapping of `Daticle`s to `Daticle`s. |
| 98 | //! |
| 99 | //! The API facilitates the use of a Builder Pattern, e.g. |
| 100 | //! ```ignore |
| 101 | //! let syntax = res!(res!(res!(Syntax::new("repl") |
| 102 | //! .with_default_help_cmd()) |
| 103 | //! .version("1") |
| 104 | //! .about("Demonstration REPL") |
| 105 | //! .add_cmd( |
| 106 | //! res!(Cmd::new("pwd")) |
| 107 | //! .help("Print path of current/working directory") |
| 108 | //! )) |
| 109 | //! .add_cmd( |
| 110 | //! res!(res!(Cmd::new("cd")) |
| 111 | //! .help("Change directory") |
| 112 | //! .add_arg(res!(res!(res!(Arg::new("dir")) |
| 113 | //! .hyph1("p")) |
| 114 | //! .hyph2("path")) |
| 115 | //! .required(true) |
| 116 | //! .expected_vals(vec![(Kind::Str, "Directory path")]) |
| 117 | //! .help("Directory path") |
| 118 | //! )) |
| 119 | //! )); |
| 120 | //! ``` |
| 121 | //! Since syntax definition is a once-off process, you may like to use `catch!` rather than `res!` |
| 122 | //! to catch a wide class of panics, but since the closure-based `catch!` doesn't nest well, the |
| 123 | //! definitions just need to be split up: |
| 124 | //! ```ignore |
| 125 | //! let mut p = Syntax::from(SyntaxConfig { |
| 126 | //! name: fmt!("repl"), |
| 127 | //! ver: Some(fmt!("1")), |
| 128 | //! about: Some(fmt!("Demonstration REPL")), |
| 129 | //! ..Default::default() |
| 130 | //! }); |
| 131 | //! |
| 132 | //! p = catch!(p.with_default_help_cmd()); |
| 133 | //! |
| 134 | //! let mut c = Cmd::from(CmdConfig { |
| 135 | //! name: fmt!("cd"), |
| 136 | //! help: Some(fmt!("Change directory")), |
| 137 | //! ..Default::default() |
| 138 | //! }); |
| 139 | //! let a = Arg::from(ArgConfig { |
| 140 | //! name: fmt!("dir"), |
| 141 | //! hyph1: Some(fmt!("p")), |
| 142 | //! hyph2: Some(fmt!("path")), |
| 143 | //! reqd: true, |
| 144 | //! vals: vec![Val::text("dir")], |
| 145 | //! help: Some(fmt!("Directory path")), |
| 146 | //! ..Default::default() |
| 147 | //! }); |
| 148 | //! c = catch!(c.add_arg(a)); |
| 149 | //! p = catch!(p.add_cmd(c)); |
| 150 | //! |
| 151 | //! let mut c = Cmd::from(CmdConfig { |
| 152 | //! name: fmt!("pwd"), |
| 153 | //! help: Some(fmt!("Print path of current/working directory")), |
| 154 | //! ..Default::default() |
| 155 | //! }); |
| 156 | //! p = catch!(p.add_cmd(c)); |
| 157 | //! ``` |
| 158 | //! The code defines `Arg` and `Cmd` which form parts of the static `Syntax`, while `Msg` |
| 159 | //! represents a message decoded using the syntax. A message can contain values, arguments (with |
| 160 | //! possible values), and commands, the latter represented by `MsgCmd`, which itself can contain |
| 161 | //! values and arguments (with possibe values). |
| 162 | //! |
| 163 | #![forbid(unsafe_code)] |
| 164 | pub mod apps; |
| 165 | pub mod arg; |
| 166 | pub mod argv; |
| 167 | pub mod cmd; |
| 168 | pub mod core; |
| 169 | pub mod help; |
| 170 | pub mod key; |
| 171 | pub mod msg; |
| 172 | pub mod opt; |
| 173 | pub mod val; |
| 174 | |
| 175 | pub use core::{ |
| 176 | Syntax, |
| 177 | SyntaxRef, |
| 178 | }; |