Oregami
Repositories/oxedyne/fe2o3

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)]
164pub mod apps;
165pub mod arg;
166pub mod argv;
167pub mod cmd;
168pub mod core;
169pub mod help;
170pub mod key;
171pub mod msg;
172pub mod opt;
173pub mod val;
174
175pub use core::{
176 Syntax,
177 SyntaxRef,
178};