runtime-format-0.1.3/.cargo_vcs_info.json0000644000000001360000000000100140060ustar { "git": { "sha1": "5c0ac3835db774afb45842d5ca4e87383a2703d3" }, "path_in_vcs": "" }runtime-format-0.1.3/.gitignore000064400000000000000000000000221046102023000145600ustar 00000000000000target Cargo.lock runtime-format-0.1.3/Cargo.toml0000644000000016500000000000100120060ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "runtime-format" version = "0.1.3" authors = ["Conrad Ludgate "] description = "rust library for formatting dynamic strings" readme = "README.md" keywords = [ "format", "string", "str", "dynamic", "static", ] license = "MIT" repository = "https://github.com/conradludgate/strfmt" [dependencies.tinyvec] version = "1.6" optional = true [features] default = ["std"] std = ["tinyvec/alloc"] runtime-format-0.1.3/Cargo.toml.orig000064400000000000000000000007051046102023000154670ustar 00000000000000[package] name = "runtime-format" version = "0.1.3" description = "rust library for formatting dynamic strings" repository = "https://github.com/conradludgate/strfmt" readme = "README.md" keywords = ["format", "string", "str", "dynamic", "static"] authors = ["Conrad Ludgate "] license = "MIT" edition = "2021" [features] default = ["std"] std = ["tinyvec/alloc"] [dependencies] tinyvec = { version = "1.6", optional = true } runtime-format-0.1.3/LICENSE000064400000000000000000000020711046102023000136030ustar 00000000000000The MIT License (MIT) Copyright (c) 2023 Conrad Ludgate Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. runtime-format-0.1.3/README.md000064400000000000000000000021001046102023000140460ustar 00000000000000# runtime-format Formatting, but processed at runtime. ```rust use runtime_format::{FormatArgs, FormatKey, FormatKeyError}; use core::fmt; impl FormatKey for DateTime { fn fmt(&self, key: &str, f: &mut fmt::Formatter<'_>) -> Result<(), FormatKeyError> { use core::fmt::Write; match key { "year" => write!(f, "{}", self.year()).map_err(FormatKeyError::Fmt), "month" => write!(f, "{}", self.short_month_name()).map_err(FormatKeyError::Fmt), "day" => write!(f, "{}", self.day()).map_err(FormatKeyError::Fmt), "hours" => write!(f, "{}", self.hours()).map_err(FormatKeyError::Fmt), "minutes" => write!(f, "{}", self.minutes()).map_err(FormatKeyError::Fmt), "seconds" => write!(f, "{}", self.seconds()).map_err(FormatKeyError::Fmt), _ => Err(FormatKeyError::UnknownKey), } } } let now = DateTime::now(); let fmt = "{month} {day} {year} {hours}:{minutes}:{seconds}"; let args = FormatArgs::new(fmt, &now); // Outputs "Jan 25 2023 16:27:53" println!("{args}"); ``` runtime-format-0.1.3/src/alloc_impls.rs000064400000000000000000000015661046102023000162410ustar 00000000000000use core::{ borrow::Borrow, fmt, hash::{BuildHasher, Hash}, }; use std::collections::{BTreeMap, HashMap}; use crate::{FormatKey, FormatKeyError}; impl FormatKey for HashMap where K: Borrow + Eq + Hash, V: fmt::Display, S: BuildHasher, { fn fmt(&self, key: &str, f: &mut fmt::Formatter<'_>) -> Result<(), FormatKeyError> { match self.get(key) { Some(v) => v.fmt(f).map_err(FormatKeyError::Fmt), None => Err(FormatKeyError::UnknownKey), } } } impl FormatKey for BTreeMap where K: Borrow + Ord, V: fmt::Display, { fn fmt(&self, key: &str, f: &mut fmt::Formatter<'_>) -> Result<(), FormatKeyError> { match self.get(key) { Some(v) => v.fmt(f).map_err(FormatKeyError::Fmt), None => Err(FormatKeyError::UnknownKey), } } } runtime-format-0.1.3/src/compiled.rs000064400000000000000000000127531046102023000155370ustar 00000000000000use core::fmt; use crate::{parse::ParseSegment, FormatArgs, FormatError, FormatKey, ToFormatParser}; /// Preparsed formatting terms. /// /// This is faster if you will be using the same format string again and again with /// different inputs. /// /// ``` /// use runtime_format::{FormatArgs, FormatKey, FormatKeyError, ParsedFmt}; /// use core::fmt; /// # struct DateTime; /// # impl DateTime { fn now() -> Self { Self } } /// # impl DateTime { fn day(&self) -> i32 { 25 } fn short_month_name(&self) -> &'static str { "Jan" } fn year(&self) -> i32 { 2023 } } /// # impl DateTime { fn hours(&self) -> i32 { 16 } fn minutes(&self) -> i32 { 27 } fn seconds(&self) -> i32 { 53 } } /// impl FormatKey for DateTime { /// fn fmt(&self, key: &str, f: &mut fmt::Formatter<'_>) -> Result<(), FormatKeyError> { /// // ... /// # use core::fmt::Write; /// # match key { /// # "year" => write!(f, "{}", self.year()).map_err(FormatKeyError::Fmt), /// # "month" => write!(f, "{}", self.short_month_name()).map_err(FormatKeyError::Fmt), /// # "day" => write!(f, "{}", self.day()).map_err(FormatKeyError::Fmt), /// # "hours" => write!(f, "{}", self.hours()).map_err(FormatKeyError::Fmt), /// # "minutes" => write!(f, "{}", self.minutes()).map_err(FormatKeyError::Fmt), /// # "seconds" => write!(f, "{}", self.seconds()).map_err(FormatKeyError::Fmt), /// # _ => Err(FormatKeyError::UnknownKey), /// # } /// } /// } /// /// let now = DateTime::now(); /// let fmt = ParsedFmt::new("{month} {day} {year} {hours}:{minutes}:{seconds}").unwrap(); /// let args = FormatArgs::new(&fmt, &now); /// let expected = "Jan 25 2023 16:27:53"; /// assert_eq!(args.to_string(), expected); pub struct ParsedFmt<'a> { segments: tinyvec::TinyVec<[ParseSegment<'a>; 8]>, } impl<'a> ToFormatParser<'a> for ParsedFmt<'a> { type Parser = std::iter::Copied>>; fn to_parser(&'a self) -> Self::Parser { self.segments.iter().copied() } fn unparsed(_: Self::Parser) -> &'a str { "" } } impl fmt::Debug for ParsedFmt<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("CompiledFormatter") .field("segments", &self.segments) .finish() } } impl<'a> ParsedFmt<'a> { /// Parse the given format string. /// /// # Errors /// If the string could not be parsed, or there is a key that is unacceptable. pub fn new(s: &'a str) -> Result> { let mut segments = s.to_parser(); let this = Self { segments: segments.by_ref().collect(), }; if !segments.s.is_empty() { Err(FormatError::Parse(segments.s)) } else { Ok(this) } } /// Return the keys that will be used when formatting. /// /// ``` /// # use runtime_format::ParsedFmt; /// let fmt = "Hello, {recipient}. Hope you are having a nice {time_descriptor}."; /// let parsed = ParsedFmt::new(fmt).unwrap(); /// let keys: Vec<_> = parsed.keys().collect(); /// assert_eq!(keys, ["recipient", "time_descriptor"]); /// ``` pub fn keys(&self) -> impl Iterator { self.segments.iter().filter_map(|segment| match segment { ParseSegment::Literal(_) => None, ParseSegment::Key(key) => Some(*key), }) } /// Combine this parsed format with the given values into a [`FormatArgs`] pub fn with_args<'fs, 'fk, F: FormatKey>( &'fs self, fmt: &'fk F, ) -> FormatArgs<'fs, 'fk, Self, F> { FormatArgs::new(self, fmt) } } impl<'a> TryFrom<&'a str> for ParsedFmt<'a> { type Error = FormatError<'a>; fn try_from(value: &'a str) -> Result { Self::new(value) } } impl<'a> FromIterator> for ParsedFmt<'a> { fn from_iter>>(iter: T) -> Self { Self { segments: FromIterator::from_iter(iter), } } } #[cfg(test)] mod tests { use core::fmt; use crate::{FormatError, FormatKey, FormatKeyError}; use super::ParsedFmt; struct Message; impl FormatKey for Message { fn fmt(&self, key: &str, f: &mut fmt::Formatter<'_>) -> Result<(), FormatKeyError> { match key { "recipient" => f.write_str("World").map_err(FormatKeyError::Fmt), "time_descriptor" => f.write_str("morning").map_err(FormatKeyError::Fmt), _ => Err(FormatKeyError::UnknownKey), } } } #[test] fn compiled_happy_path() { let formatter = ParsedFmt::new("Hello, {recipient}. Hope you are having a nice {time_descriptor}.") .unwrap(); let expected = "Hello, World. Hope you are having a nice morning."; assert_eq!(formatter.with_args(&Message).to_string(), expected); } #[test] fn compiled_failed_parsing() { let err = ParsedFmt::new("Hello, {recipient}. Hope you are having a nice {time_descriptor.") .unwrap_err(); assert_eq!(err, FormatError::Parse("time_descriptor.")); } #[test] fn compiled_keys() { let parsed = ParsedFmt::new("Hello, {recipient}. Hope you are having a nice {time_descriptr}.") .unwrap(); let keys: Vec<_> = parsed.keys().collect(); assert_eq!(keys, ["recipient", "time_descriptr"]); } } runtime-format-0.1.3/src/lib.rs000064400000000000000000000252611046102023000145070ustar 00000000000000//! Formatting, but processed at runtime. //! //! ``` //! use runtime_format::{FormatArgs, FormatKey, FormatKeyError}; //! use core::fmt; //! # struct DateTime; //! # impl DateTime { fn now() -> Self { Self } } //! # impl DateTime { fn day(&self) -> i32 { 25 } fn short_month_name(&self) -> &'static str { "Jan" } fn year(&self) -> i32 { 2023 } } //! # impl DateTime { fn hours(&self) -> i32 { 16 } fn minutes(&self) -> i32 { 27 } fn seconds(&self) -> i32 { 53 } } //! impl FormatKey for DateTime { //! fn fmt(&self, key: &str, f: &mut fmt::Formatter<'_>) -> Result<(), FormatKeyError> { //! use core::fmt::Write; //! match key { //! "year" => write!(f, "{}", self.year()).map_err(FormatKeyError::Fmt), //! "month" => write!(f, "{}", self.short_month_name()).map_err(FormatKeyError::Fmt), //! "day" => write!(f, "{}", self.day()).map_err(FormatKeyError::Fmt), //! "hours" => write!(f, "{}", self.hours()).map_err(FormatKeyError::Fmt), //! "minutes" => write!(f, "{}", self.minutes()).map_err(FormatKeyError::Fmt), //! "seconds" => write!(f, "{}", self.seconds()).map_err(FormatKeyError::Fmt), //! _ => Err(FormatKeyError::UnknownKey), //! } //! } //! } //! //! let now = DateTime::now(); //! let fmt = "{month} {day} {year} {hours}:{minutes}:{seconds}"; //! let args = FormatArgs::new(fmt, &now); //! let expected = "Jan 25 2023 16:27:53"; //! assert_eq!(args.to_string(), expected); //! ``` //! //! See [`ParsedFmt`] if you need to repeatedly format a given string, but with //! different args. #![cfg_attr(not(feature = "std"), no_std)] #[cfg(feature = "std")] mod alloc_impls; #[cfg(feature = "std")] pub use alloc_impls::*; #[cfg(feature = "std")] mod compiled; #[cfg(feature = "std")] pub use compiled::ParsedFmt; mod parse; pub use parse::{FromStr, ParseSegment}; use core::cell::Cell; use core::fmt; #[derive(Debug, Clone, PartialEq)] /// Error produced when formatting pub enum FormatKeyError { /// The formatter had an error Fmt(fmt::Error), /// The requested key is unknown UnknownKey, } #[cfg(feature = "std")] impl std::error::Error for FormatKeyError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { FormatKeyError::Fmt(f) => Some(f), FormatKeyError::UnknownKey => None, } } } impl fmt::Display for FormatKeyError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { FormatKeyError::Fmt(_) => f.write_str("There was an error writing to the formatter"), FormatKeyError::UnknownKey => f.write_str("The requested key is unknown"), } } } impl From for FormatKeyError { fn from(value: fmt::Error) -> Self { FormatKeyError::Fmt(value) } } /// A trait like [`fmt::Display`] or [`fmt::Debug`] by with a keyed field. /// /// It has a `fmt` method that accepts a [`fmt::Formatter`] argument. The important feature is the /// `key` field which indicates what value should be written to the formatter. /// /// ``` /// use runtime_format::{FormatArgs, FormatKey, FormatKeyError}; /// use core::fmt; /// # struct DateTime; /// # impl DateTime { fn now() -> Self { Self } } /// # impl DateTime { fn day(&self) -> i32 { 25 } fn short_month_name(&self) -> &'static str { "Jan" } fn year(&self) -> i32 { 2023 } } /// # impl DateTime { fn hours(&self) -> i32 { 16 } fn minutes(&self) -> i32 { 27 } fn seconds(&self) -> i32 { 53 } } /// impl FormatKey for DateTime { /// fn fmt(&self, key: &str, f: &mut fmt::Formatter<'_>) -> Result<(), FormatKeyError> { /// use core::fmt::Write; /// match key { /// "year" => write!(f, "{}", self.year()).map_err(FormatKeyError::Fmt), /// "month" => write!(f, "{}", self.short_month_name()).map_err(FormatKeyError::Fmt), /// "day" => write!(f, "{}", self.day()).map_err(FormatKeyError::Fmt), /// "hours" => write!(f, "{}", self.hours()).map_err(FormatKeyError::Fmt), /// "minutes" => write!(f, "{}", self.minutes()).map_err(FormatKeyError::Fmt), /// "seconds" => write!(f, "{}", self.seconds()).map_err(FormatKeyError::Fmt), /// _ => Err(FormatKeyError::UnknownKey), /// } /// } /// } /// /// let now = DateTime::now(); /// let fmt = "{month} {day} {year} {hours}:{minutes}:{seconds}"; /// let args = FormatArgs::new(fmt, &now); /// let expected = "Jan 25 2023 16:27:53"; /// assert_eq!(args.to_string(), expected); /// ``` pub trait FormatKey { /// Write the value with the associated with the given `key` to the formatter. /// /// # Errors /// If the formatter returns an error, or if the key is unknown. fn fmt(&self, key: &str, f: &mut fmt::Formatter<'_>) -> Result<(), FormatKeyError>; } /// Turn a value into parsed formatting segments on the fly. pub trait ToFormatParser<'a> { /// The Parser type that returns the [`ParseSegment`]s type Parser: Iterator>; /// Turn this value into the parser fn to_parser(&'a self) -> Self::Parser; /// Get the unparsed str from this parser. /// Used to determine if there was an error while parsing. fn unparsed(iter: Self::Parser) -> &'a str; } #[derive(Debug, Clone, PartialEq)] #[non_exhaustive] /// Error returned when formatting or parsing. pub enum FormatError<'a> { /// The key was invalid Key(&'a str), /// Could not parse the string Parse(&'a str), } #[cfg(feature = "std")] impl std::error::Error for FormatError<'_> {} impl fmt::Display for FormatError<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { FormatError::Key(key) => write!(f, "The requested key {key:?} is unknown"), FormatError::Parse(rest) => write!(f, "Failed to parse {rest:?}"), } } } /// Performs formatting. pub struct FormatArgs<'fs, 'fk, FS: ?Sized, FK: ?Sized> { format_segments: &'fs FS, format_keys: &'fk FK, error: Cell>>, } impl<'fs, 'fk, FS: ?Sized, FK: ?Sized> FormatArgs<'fs, 'fk, FS, FK> { /// Create a new `FormatArgs` using the format specifier and the format keys pub fn new(format_specified: &'fs FS, format_keys: &'fk FK) -> Self { FormatArgs { format_segments: format_specified, format_keys, error: Cell::new(None), } } /// If there was an error when formatting, then that error is available here. pub fn status(&self) -> Result<(), FormatError<'fs>> { match self.error.take() { Some(err) => Err(err), None => Ok(()), } } } impl<'fs, 'fk, FS, FK> fmt::Display for FormatArgs<'fs, 'fk, FS, FK> where FS: ?Sized + ToFormatParser<'fs>, FK: ?Sized + FormatKey, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut segments = self.format_segments.to_parser(); for segment in &mut segments { match segment { ParseSegment::Literal(s) => f.write_str(s)?, ParseSegment::Key(key) => match self.format_keys.fmt(key, f) { Ok(_) => {} Err(FormatKeyError::Fmt(e)) => return Err(e), Err(FormatKeyError::UnknownKey) => { self.error.set(Some(FormatError::Key(key))); return Err(fmt::Error); } }, } } let remaining = FS::unparsed(segments); if !remaining.is_empty() { self.error.set(Some(FormatError::Parse(remaining))); Err(fmt::Error) } else { Ok(()) } } } impl<'fs, 'fk, FS, FK> fmt::Debug for FormatArgs<'fs, 'fk, FS, FK> where FS: ?Sized + ToFormatParser<'fs>, FK: ?Sized + FormatKey, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self, f) } } #[cfg(test)] mod tests { use core::fmt::{self, Write}; use crate::{FormatArgs, FormatError, FormatKey, FormatKeyError}; struct WriteShim<'a> { w: &'a mut [u8], n: usize, } impl fmt::Write for WriteShim<'_> { fn write_str(&mut self, s: &str) -> fmt::Result { let remaining = self.w.len() - self.n; if let Some(prefix) = s.as_bytes().get(..remaining) { self.w[self.n..].copy_from_slice(prefix); self.n = self.w.len(); Err(fmt::Error) } else { let n = self.n + s.len(); self.w[self.n..n].copy_from_slice(s.as_bytes()); self.n = n; Ok(()) } } } fn format<'a, F: FormatKey>( s: &'a str, fmt: &'a F, f: impl FnOnce(&[u8]), ) -> Result<(), FormatError<'a>> { let mut bytes = WriteShim { w: &mut [0; 1024], n: 0, }; let fmt = FormatArgs::new(s, fmt); let _ = write!(bytes, "{}", fmt); if let Some(err) = fmt.error.take() { return Err(err); } f(&bytes.w[..bytes.n]); Ok(()) } struct Message; impl FormatKey for Message { fn fmt(&self, key: &str, f: &mut fmt::Formatter<'_>) -> Result<(), FormatKeyError> { match key { "recipient" => f.write_str("World").map_err(FormatKeyError::Fmt), "time_descriptor" => f.write_str("morning").map_err(FormatKeyError::Fmt), _ => Err(FormatKeyError::UnknownKey), } } } #[test] fn happy_path() { let format_str = "Hello, {recipient}. Hope you are having a nice {time_descriptor}."; let expected = "Hello, World. Hope you are having a nice morning."; format(format_str, &Message, |output| { assert_eq!(output, expected.as_bytes()) }) .unwrap(); } #[test] fn missing_key() { let format_str = "Hello, {recipient}. Hope you are having a nice {time_descriptr}."; assert_eq!( format(format_str, &Message, |_| {}), Err(FormatError::Key("time_descriptr")) ); } #[test] fn failed_parsing() { let format_str = "Hello, {recipient}. Hope you are having a nice {time_descriptor."; assert_eq!( format(format_str, &Message, |_| {}), Err(FormatError::Parse("time_descriptor.")) ); } #[test] fn escape_brackets() { let format_str = "You can make custom formatting terms using {{foo}!"; let expected = "You can make custom formatting terms using {foo}!"; format(format_str, &Message, |output| { assert_eq!(output, expected.as_bytes()) }) .unwrap(); } } runtime-format-0.1.3/src/parse.rs000064400000000000000000000044301046102023000150460ustar 00000000000000use crate::ToFormatParser; #[derive(Debug, Clone, Copy)] /// An enum representing the parsed portion of a format string #[non_exhaustive] pub enum ParseSegment<'a> { /// A string literal to be included as is Literal(&'a str), /// A keyed value, that should be looked up using [`FormatKey`](crate::FormatKey) Key(&'a str), } impl Default for ParseSegment<'_> { fn default() -> Self { Self::Literal("") } } /// An [`Iterator`] of [`ParseSegment`]s. Returned by of [`str::to_parser`](ToFormatParser). pub struct FromStr<'a> { pub(crate) s: &'a str, pub(crate) is_key: bool, } impl<'a> Iterator for FromStr<'a> { type Item = ParseSegment<'a>; fn next(&mut self) -> Option { if self.s.is_empty() { None } else if self.is_key { match self.s.strip_prefix('{') { // escaped Some(rest) => match rest.split_once('{') { None => { self.is_key = false; Some(ParseSegment::Literal(core::mem::take(&mut self.s))) } Some((prefix, rest)) => { let x = &self.s[..prefix.len() + 1]; self.s = rest; Some(ParseSegment::Literal(x)) } }, None => match self.s.split_once('}') { Some((key, rest)) => { self.is_key = false; self.s = rest; Some(ParseSegment::Key(key)) } None => None, }, } } else { match self.s.split_once('{') { None => Some(ParseSegment::Literal(core::mem::take(&mut self.s))), Some((prefix, rest)) => { self.is_key = true; self.s = rest; Some(ParseSegment::Literal(prefix)) } } } } } impl<'a> ToFormatParser<'a> for str { type Parser = FromStr<'a>; fn to_parser(&'a self) -> Self::Parser { FromStr { s: self, is_key: false, } } fn unparsed(iter: Self::Parser) -> &'a str { iter.s } }