Skip to main content

mxp/
lib.rs

1//! Batteries-included implementation of [MXP (MUD eXtension Protocol)].
2//!
3//! [MXP (MUD eXtension Protocol)]: https://www.zuggsoft.com/zmud/mxp.htm
4//!
5//! # Description
6//!
7//! [MXP (MUD eXtension Protocol)] is an open communication protocol for MUD servers and clients.
8//! The mxp library is a lightweight but robust implementation of the protocol in its entirety.
9//! It is geared toward client implementations, but it can also be used for server-side syntax
10//! handling.
11//!
12//! By default, mxp processes all tags described in the above MXP standard. To restrict which
13//! elements your client supports, send a [`SupportResponse`] to the MUD server.
14//!
15//! [`SupportResponse`]: crate::responses::SupportResponse
16//!
17//! # Examples
18//!
19//! ## Simple parsing
20//!
21//! mxp can be used to parse MXP strings directly:
22//!
23//! ```
24//! use mxp::{Dimension, FrameAction, FrameLayout};
25//!
26//! assert_eq!(
27//!     "<FRAME NAME=Map LEFT=-20c TOP=0 WIDTH=20c HEIGHT=20c>".parse::<mxp::Action>(),
28//!     Ok(mxp::Action::Frame(mxp::Frame {
29//!         name: "Map".into(),
30//!         action: FrameAction::Open,
31//!         title: "Map".into(),
32//!         scrolling: false,
33//!         persistent: false,
34//!         layout: FrameLayout::External {
35//!             left: Dimension::character_spacing(-20),
36//!             top: Dimension::pixels(0),
37//!             width: Some(Dimension::character_spacing(20)),
38//!             height: Some(Dimension::character_spacing(20)),
39//!             floating: false,
40//!         },
41//!     })),
42//! );
43//! ```
44//!
45//! Although useful for development, this approach lacks support for the most important aspect of
46//! MXP: defining custom entities and elements. (It's also inefficient, because it uses owned
47//! strings rather than borrowed string slices.) Instead, production environments should make use of
48//! mxp's state management system, provided by [`mxp::State`] and [`mxp::ModeState`].
49//!
50//! [`mxp::State`]: State
51//! [`mxp::ModeState`]: ModeState
52//!
53//! ## State management
54//!
55//! [`mxp::State`] stores definitions for custom [`Element`]s, [`Entity`]s, and [`LineTag`]s. Once
56//! the state receives a definition, it can be used in all subsequent parses. With this approach,
57//! rather than using [`FromStr`] to parse tags into owned strings, [`Tag::parse`] is used to
58//! deserialize tags in-place using borrowed string slices.
59//!
60//! Unlike everything else in MXP, which uses XML syntax, line modes are set by ANSI escape
61//! sequences. For example, to set the MXP mode to 20, the MUD server would send `<ESC>[20z`.
62//! As such, it is up to the client to recognize MXP mode changes and apply them with
63//! [`ModeState::set`] and [`ModeState::revert`].
64//!
65//! [`FromStr`]: std::str::FromStr
66//! [`Tag::parse`]: node::Tag::parse
67//!
68//! ```
69//! use std::borrow::Cow;
70//! // Alternatively:
71//! // - `use mxp::node;` for prefixed names, e.g. `node::TagOpen`
72//! // - `use mxp::node::{Tag as TagNode, TagOpen as TagOpenNode};`
73//! use mxp::node::{Tag, TagOpen};
74//!
75//! // Handler function for receiving tags from the MUD server. A tag is anything surrounded by
76//! // `<` and `>`. The `secure` flag indicates whether the current line mode is secure.
77//! fn handle_tag(mxp_state: &mut mxp::State, mut src: &str, secure: bool) -> mxp::Result<()> {
78//!     src = &src[1..src.len() - 1]; // strip < and > from the source
79//!     match Tag::parse(src, secure)? {
80//!         Tag::Definition(definition) => { // <!...>
81//!             mxp_state.define(definition)?;
82//!         }
83//!         Tag::Open(tag) => { // <...>
84//!             handle_open(tag, mxp_state, secure)?; // see below
85//!         }
86//!         Tag::Close(tag) => (), // </...>
87//!     }
88//!     Ok(())
89//! }
90//!
91//!
92//! // Handler function for receiving an opening tag. Called by `handle_tag`.
93//! fn handle_open(tag: TagOpen, mxp_state: &mxp::State, secure: bool) -> mxp::Result<()> {
94//!     match mxp_state.get_component(tag.name, secure)? {
95//!         // server sent a standard, atomic tag, such as <a> or <br>
96//!         mxp::Component::AtomicTag(atom) => {
97//!             let action = atom.decode(&tag.arguments, mxp_state)?;
98//!             handle_action(&action);
99//!         }
100//!         // server sent a previously-defined custom element
101//!         mxp::Component::Element(el) => {
102//!             for action in el.decode(&tag.arguments, mxp_state) {
103//!                 handle_action(&action?);
104//!             }
105//!         }
106//!     }
107//!     Ok(())
108//! }
109//!
110//! // Handler function for applying the action of an atomic tag.
111//! // This is where the actual client logic takes place.
112//! fn handle_action(action: &mxp::Action<Cow<str>>) {
113//!     use mxp::Action;
114//!
115//!     match action {
116//!         Action::Br => println!(),
117//!         Action::Hr => print!("----"),
118//!         _ => (),
119//!     }
120//! }
121//!
122//! // initialize state
123//! let mut mxp_state = mxp::State::with_globals();
124//! let mut mode = mxp::ModeState::new();
125//!
126//! mode.set(mxp::Mode::SECURE_ONCE);
127//! let secure = mode.use_secure();
128//! assert!(secure); // line mode is secure, so elements may be defined
129//! handle_tag(&mut mxp_state, "<!ELEMENT MyEl '<HR><BR>' EMPTY OPEN>", secure).unwrap();
130//! // The <MyEl> element has now been defined, and can be used in subsequent parses.
131//!
132//! let secure = mode.use_secure();
133//! assert!(!secure); // SECURE_ONCE reverts back to OPEN mode after use
134//! handle_tag(&mut mxp_state, "<myel>", secure).unwrap(); // prints "----\n" (<HR><BR>)
135//! ```
136//!
137//! ## Server-side usage
138//!
139//! All of the types exported by mxp can be serialized to MXP syntax with their [`Display`]
140//! implementation.
141//!
142//! [`Display`]: std::fmt::Display
143//!
144//! ```
145//! use mxp::entity::EntityKeyword;
146//!
147//! let entity = mxp::node::EntityDefinition {
148//!     name: "Guilds",
149//!     value: "Wizards",
150//!     desc: None,
151//!     keywords: EntityKeyword::Publish | EntityKeyword::Add,
152//! };
153//! assert_eq!(entity.to_string(), "<!EN Guilds \"Wizards\" PUBLISH ADD>");
154//! ```
155//!
156//! For advanced tag building, see [`TagBuilder`].
157//!
158//! [`TagBuilder`]: node::TagBuilder
159//!
160//! # Memory allocation
161//!
162//! [`Tag::parse`] allocates memory if it parses a custom element definition (as
163//! [`node::ElementDefinition`]), which needs to use owned strings because custom elements are
164//! stored long-term in state. Otherwise, it only allocates memory to parse arguments passed to
165//! an opening tag (as [`node::TagOpen`]), and most MXP tags do not have arguments, so no allocation
166//! occurs.
167//!
168//! Tag decoding (via [`AtomicTag::decode`] and [`Element::decode`]) uses [`Cow`]s because
169//! attributes may contain entities, in which case they must be decoded to owned strings in order to
170//! replace entities with their definitions (e.g. replacing `"&lt;"` with `"<"`). If the MXP string
171//! does not contain entities, no allocations are performed.
172//!
173//! [triple-r]: https://docs.rs/triple-r/latest/triple_r/index.html
174//! [`Cow`]: std::borrow::Cow
175
176#[macro_use]
177mod case_insensitive;
178
179pub mod arguments;
180pub use arguments::Arguments;
181
182mod case_fold_map;
183pub(crate) use case_fold_map::CaseFoldMap;
184
185pub mod color;
186pub use color::RgbColor;
187
188pub(crate) mod display;
189
190pub mod element;
191pub use element::{Action, ActionKind, AtomicTag, AttributeList, Element, ParseAs};
192
193pub mod elements;
194pub use elements::*;
195
196pub mod entity;
197pub use entity::Entity;
198
199pub mod escape;
200
201mod keyword;
202
203mod line;
204pub use line::{LineTag, LineTagProperties, Mode, ModeRangeError, ModeState};
205
206mod parse;
207pub use parse::{Decoder, Error, ErrorKind, is_valid, validate, validate_utf8};
208
209pub mod node;
210
211pub mod responses;
212
213mod screen;
214pub use screen::{Align, Dimension, DimensionUnit};
215
216mod state;
217pub use state::{Component, State};
218
219/// Type alias for `Result<T, mxp::Error>`.
220pub type Result<T> = std::result::Result<T, Error>;
221
222/// Reexport from the [`flagset`](flagset::FlagSet) package.
223pub use flagset::FlagSet;
224
225#[cfg(test)]
226mod test_utils;