Skip to main content

one_brackets/
lib.rs

1//! Reader and writer for the 1C:Enterprise bracket format: `{1,"text",{nested,list}}`.
2//!
3//! Input is anything implementing [`std::io::Read`]; the result is a [`Node`] tree.
4//! The API is built on the standard conversion traits:
5//!
6//! ```
7//! use one_brackets::Node;
8//!
9//! # fn main() -> one_brackets::Result<()> {
10//! let node: Node = "\u{feff}{2,c2f34349-b1e0-4060-8abf-3c59f38ac69f,\"ок\"}".parse()?;
11//! assert_eq!(node.try_get(0)?.try_i64()?, 2);
12//! assert!(node[1].as_guid().is_some());
13//! assert_eq!(node[2].as_quoted(), Some("ок"));
14//!
15//! // Writing uses the platform layout.
16//! let text: String = (&node).into();
17//! assert_eq!(text, "{2,c2f34349-b1e0-4060-8abf-3c59f38ac69f,\"ок\"}");
18//!
19//! // Any `Read` source: file, socket, decompressed stream.
20//! let node = Node::from_reader(std::io::Cursor::new(b"{1,{}}"))?;
21//! assert_eq!(node, Node::list([Node::from(1), Node::list([])]));
22//!
23//! // Edit in place and write back: untouched parts keep their text.
24//! let mut node: Node = "{1,\"old\",{2}}".parse()?;
25//! *node.try_get_path_mut(&[2, 0])? = Node::from(3);
26//! node[1] = Node::string("new");
27//! assert_eq!(node.to_string(), "{1,\"new\",\r\n{3}\r\n}");
28//!
29//! // Errors carry a position.
30//! let err = Node::try_from("{1,\n{2").unwrap_err();
31//! assert_eq!(
32//!     err.to_string(),
33//!     "unexpected end of input at line 2, column 3: list opened at line 2, column 1 is not closed",
34//! );
35//! # Ok(())
36//! # }
37//! ```
38//!
39//! Map your own types with `impl TryFrom<&Node>` using the `try_*` accessors.
40//! They return an [`AccessError`] that names the expected type and points at the node:
41//!
42//! ```
43//! use one_brackets::{AccessError, Node};
44//!
45//! #[derive(Debug)]
46//! struct FormatVersion { major: u32, build: u64 }
47//!
48//! impl TryFrom<&Node> for FormatVersion {
49//!     type Error = AccessError;
50//!
51//!     fn try_from(node: &Node) -> Result<Self, AccessError> {
52//!         Ok(FormatVersion {
53//!             major: node.try_get_path(&[0, 0])?.try_parse()?,
54//!             build: node.try_get_path(&[0, 2, 0])?.try_parse()?,
55//!         })
56//!     }
57//! }
58//!
59//! let node: Node = "{\r\n{216,0,\r\n{x,0}\r\n}\r\n}".parse().unwrap();
60//! let err = FormatVersion::try_from(&node).unwrap_err();
61//! assert_eq!(
62//!     err.to_string(),
63//!     "expected u64, found value `x` at line 3, column 2: invalid digit found in string",
64//! );
65//! ```
66//!
67//! Parsing problems are reported as [`ParseError`], structure problems as [`AccessError`];
68//! [`Error`] wraps both, so `?` works for either in one function.
69//!
70//! With the `serde` feature user types map to lists by field order:
71//! see `from_node`, `to_node`, `Rest`, `Counted` and `atom`.
72//!
73//! # Performance
74//!
75//! Every string and value is a separate `String` and every list a `Vec`, so most of
76//! the parsing time of large files (event logs, big forms) goes to the memory allocator.
77//! The library does not pick an allocator; the application can switch to
78//! [mimalloc](https://crates.io/crates/mimalloc), which works on Windows, Linux and macOS.
79//! In the crate's measurements it made parsing 1.5–2.7 times faster on Windows,
80//! whose system allocator is the slowest, and 1.2–1.5 times faster on Linux (glibc):
81//!
82//! ```toml
83//! [dependencies]
84//! mimalloc = "0.1"
85//! ```
86//!
87//! ```ignore
88//! #[global_allocator]
89//! static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
90//! ```
91//!
92//! For large inputs prefer [`Values`] over parsing one huge list: it keeps only
93//! the current record in memory. `cargo bench` measures reading and writing speed.
94
95#![cfg_attr(docsrs, feature(doc_cfg))]
96
97/// Runs the README examples as doctests; some of them need the `serde` feature.
98#[cfg(all(doctest, feature = "serde"))]
99#[doc = include_str!("../README.md")]
100struct ReadmeDoctests;
101
102/// The Russian README carries the same examples.
103#[cfg(all(doctest, feature = "serde"))]
104#[doc = include_str!("../README.ru.md")]
105struct ReadmeRuDoctests;
106
107#[cfg(feature = "serde")]
108mod de;
109mod error;
110mod node;
111mod parser;
112#[cfg(feature = "serde")]
113mod ser;
114#[cfg(feature = "serde")]
115mod serde_impls;
116mod writer;
117
118#[cfg(feature = "serde")]
119#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
120pub use de::{from_node, from_reader, from_str};
121#[cfg(feature = "serde")]
122#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
123pub use error::SerializeError;
124pub use error::{AccessError, AccessErrorKind, Error, ParseError, Position, Result};
125pub use node::{Node, Value};
126pub use parser::Values;
127#[cfg(feature = "serde")]
128#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
129pub use ser::{to_node, to_string};
130#[cfg(feature = "serde")]
131#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
132pub use serde_impls::{Counted, Rest, atom};