Skip to main content

bash_strings/
lib.rs

1//! Bash value strings — parse and emit the right-hand side of a bash
2//! assignment.
3//!
4//! A general utility about bash, standing on its own: nothing here knows about
5//! the rig, the wire, or any tool. Three levels, each publicly reachable.
6//!
7//! # The shapes bash prints
8//!
9//! One call each, no codec and no schema. Every parser is strict, accepting
10//! only what bash itself writes; every emitter produces the canonical
11//! single-quoted form.
12//!
13//! | form | wire shape | type | written in bash by |
14//! |---|---|---|---|
15//! | scalar | `'foo'` | `String` | `${x@Q}` |
16//! | q_words | `'a' 'b c'` | `Vec<String>` | `${x[*]@Q}` |
17//! | array | `('a' 'b c')` | `Vec<String>` | `"(${x[*]@Q})"` |
18//! | rows | `("'a' 'b'" "'c'")` | `Vec<Vec<String>>` | one level of nesting |
19//! | indexed | `([0]='a' [5]='b')` | `IndexMap<usize, String>` | `${x[*]@A}`, `declare -a` |
20//! | assoc | `(['k']='v')` | `IndexMap<String, String>` | `${x[*]@A}`, `declare -A` |
21//!
22//! ```
23//! use bash_strings::{emit_array, parse_array, ParseError};
24//!
25//! let words = vec!["compiled".to_string(), "a file.rs".to_string()];
26//!
27//! assert_eq!(emit_array(&words), "('compiled' 'a file.rs')");
28//! assert_eq!(parse_array("('compiled' 'a file.rs')")?, words);
29//! # Ok::<(), ParseError>(())
30//! ```
31//!
32//! # Any depth, either encoding
33//!
34//! Bash arrays are flat, so a value with structure is encoded textually.
35//! [`BashVal`] is one of any depth and [`Schema`] is how deep to read it back
36//! — which the text alone does not say. Two [`BashCodec`]s flatten one:
37//!
38//! - [`QuotedNest`] — each inner array is one bash-literal word at the outer
39//!   level: `[[a,b],[c]]` → `["('a' 'b')", "('c')"]`. The receiver unquotes
40//!   one layer per level. This is what `array` and `rows` above use.
41//!
42//! - [`LinkedArr`] — one flat word stream, each group prefixed by its width:
43//!   `[[a,b],[c]]` → `[2, a, b, 1, c]`. A bash-side walker reads it by
44//!   taking a width and shifting that many words, with no parser.
45//!
46//! ```
47//! use bash_strings::{BashCodec, BashVal, LinkedArr, ParseError, Schema};
48//!
49//! let value = BashVal::Arr(vec![BashVal::row(["a", "b"]), BashVal::row(["c"])]);
50//! let text = LinkedArr.emit_literal(&value);
51//!
52//! assert_eq!(text, "('2' 'a' 'b' '1' 'c')");
53//! assert_eq!(LinkedArr.parse_literal(&text, &Schema::n_d(2))?, value);
54//! # Ok::<(), ParseError>(())
55//! ```
56//!
57//! Emitting takes the depth from the value and so cannot fail; parsing takes
58//! it from the `Schema` the caller states.
59//!
60//! # A grammar over other syntax
61//!
62//! [`Cursor`] is the word lexer on its own — bash's quoting rules with the
63//! stop characters left to the caller — and [`parse_with`] runs a grammar over
64//! a whole input. See [`quoting`](self#a-grammar-over-other-syntax) for a
65//! worked one.
66
67mod codec;
68mod emit;
69mod error;
70mod parser;
71mod quoting;
72
73pub use codec::{BashCodec, BashVal, LinkedArr, QuotedNest, Schema};
74
75pub use emit::{emit_array, emit_assoc, emit_indexed, emit_q_words, emit_scalar};
76pub use error::ParseError;
77pub use parser::{parse_array, parse_assoc, parse_indexed, parse_scalar};
78pub use quoting::{Cursor, parse_with};