diff --git a/keyvalues-parser/README.md b/keyvalues-parser/README.md index ff6ba87..a6ae44b 100644 --- a/keyvalues-parser/README.md +++ b/keyvalues-parser/README.md @@ -41,10 +41,8 @@ const LOGIN_USERS_VDF: &str = r#" } "#; -use keyvalues_parser::Vdf; - -fn main() -> Result<(), Box> { - let vdf = Vdf::parse(LOGIN_USERS_VDF)?; +fn main() -> keyvalues_parser::error::Result<()> { + let vdf = keyvalues_parser::parse(LOGIN_USERS_VDF)?; assert_eq!( "12345678901234567", vdf.value.unwrap_obj().keys().next().unwrap(), diff --git a/keyvalues-parser/examples/parse_mutate_render.rs b/keyvalues-parser/examples/parse_mutate_render.rs index e215dd3..872e300 100644 --- a/keyvalues-parser/examples/parse_mutate_render.rs +++ b/keyvalues-parser/examples/parse_mutate_render.rs @@ -1,4 +1,4 @@ -use keyvalues_parser::Vdf; +use keyvalues_parser::{parse, Vdf}; use std::{fs, path::Path}; @@ -34,7 +34,7 @@ fn update_version(controller_mappings: &mut Vdf, new_version: String) -> Option< fn main() -> Result<(), Box> { let vdf_text = read_asset_file("parse_mutate_render.vdf")?; - let mut controller_mappings = Vdf::parse(&vdf_text)?; + let mut controller_mappings = Vdf::from(parse(&vdf_text)?); // Reading information from VDF: // This involves a lot of `Option`s so it's moved inside a function diff --git a/keyvalues-parser/src/lib.rs b/keyvalues-parser/src/lib.rs index dd41375..36f1188 100644 --- a/keyvalues-parser/src/lib.rs +++ b/keyvalues-parser/src/lib.rs @@ -19,6 +19,68 @@ pub mod text; /// `pest` re-exported for your convenience :) pub use pest; +/// Parse a KeyValues document to a loosely typed representation +/// +/// This is shorthand for parsing a document with default settings aka `Parser::new().parse(text)` +pub fn parse<'text>(text: &'text str) -> error::Result> { + Parser::new().parse(text) +} + +/// A configurable KeyValues parser allowing for adjusting settings before parsing +#[derive(Clone, Debug, Default)] +pub struct Parser { + literal_special_chars: bool, +} + +impl Parser { + /// Constructs a default parser + /// + /// Currently this consists of: + /// + /// | Toggle | Description | + /// | :---: | :--- | + /// | [`Parser::literal_special_chars()`] | Whether to interpret `\` in strings as the start of an escaped special character, or a literal `\` | + pub const fn new() -> Self { + // same as Default, but const 😏 + Self { + literal_special_chars: false, + } + } + + /// Toggle how to interpret `\` in strings + /// + /// By default (`false`) the parser will interpret backslashes (`\`) in strings as the start of + /// an escaped special character (e.g. `\\` -> `\`, `\"` -> `"`). When `true` the parser will + /// instead interpret backslashes (`\`) as a literal backslash. Commonly seen with + /// windows-paths, for instance + pub const fn literal_special_chars(mut self, yes: bool) -> Self { + self.literal_special_chars = yes; + self + } + + /// Parse a KeyValues document to a loosely typed representation + /// + /// # Example + /// + /// ``` + /// use keyvalues_parser::Parser; + /// let vdf = Parser::new() + /// .literal_special_chars(true) + /// .parse(r"InstallDir C:\You\Later") + /// .unwrap(); + /// assert_eq!(vdf.value.unwrap_str(), r"C:\You\Later"); + /// ``` + pub fn parse<'text>(&self, vdf: &'text str) -> error::Result> { + if self.literal_special_chars { + #[expect(deprecated)] // deprecated for thee, but not for me! + text::parse::raw_parse(vdf) + } else { + #[expect(deprecated)] // deprecated for thee, but not for me! + text::parse::escaped_parse(vdf) + } + } +} + /// A Key is simply an alias for `Cow` pub type Key<'text> = Cow<'text, str>; @@ -28,8 +90,8 @@ pub type Key<'text> = Cow<'text, str>; /// /// ## Parse /// -/// `Vdf`s will generally be created through the use of [`Vdf::parse()`] which takes a string -/// representing VDF text and attempts to parse it to a `Vdf` representation. +/// `Vdf`s will generally be created through the use of [`parse()`] or [`Parser::parse()`] which +/// takes a string representing VDF text and attempts to parse it to a `Vdf` representation. /// /// ## Mutate /// @@ -43,8 +105,6 @@ pub type Key<'text> = Cow<'text, str>; /// ## Example /// /// ``` -/// use keyvalues_parser::Vdf; -/// /// // Parse /// let vdf_text = r#" /// "Outer Key" @@ -55,7 +115,7 @@ pub type Key<'text> = Cow<'text, str>; /// } /// } /// "#; -/// let mut parsed = Vdf::parse(vdf_text)?; +/// let mut parsed = keyvalues_parser::parse(vdf_text)?; /// /// // Mutate: i.e. remove the last "Inner Key" pair /// parsed @@ -80,24 +140,6 @@ pub struct Vdf<'text> { pub value: Value<'text>, } -impl<'text> From> for Vdf<'text> { - fn from(partial: PartialVdf<'text>) -> Self { - Self { - key: partial.key, - value: partial.value, - } - } -} - -// TODO: Just store a `Vdf` internally? -// TODO: don't expose these publicly? -#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct PartialVdf<'text> { - pub key: Key<'text>, - pub value: Value<'text>, - pub bases: Vec>, -} - impl<'text> Vdf<'text> { /// Creates a [`Vdf`] using a provided key and value /// @@ -115,6 +157,23 @@ impl<'text> Vdf<'text> { } } +impl<'text> From> for Vdf<'text> { + fn from(partial: PartialVdf<'text>) -> Self { + Self { + key: partial.key, + value: partial.value, + } + } +} + +// TODO: Just store a `Vdf` internally? +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct PartialVdf<'text> { + pub key: Key<'text>, + pub value: Value<'text>, + pub bases: Vec>, +} + // TODO: why is this type alias a thing if it's not private but the usage of it inside `Obj` is? type ObjInner<'text> = BTreeMap, Vec>>; type ObjInnerPair<'text> = (Key<'text>, Vec>); diff --git a/keyvalues-parser/src/text/parse/escaped.rs b/keyvalues-parser/src/text/parse/escaped.rs index 4f86c35..75662be 100644 --- a/keyvalues-parser/src/text/parse/escaped.rs +++ b/keyvalues-parser/src/text/parse/escaped.rs @@ -6,6 +6,12 @@ type ParseResult<'a> = pest::ParseResult>; common_parsing!(pest_parse, Rule, true); +/// Attempts to parse VDF text to a [`Vdf`] +#[deprecated(since = "0.2.3", note = "Moved to `keyvalues_parser::parse()`")] +pub fn parse(s: &str) -> Result> { + parse_(s) +} + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum Rule { diff --git a/keyvalues-parser/src/text/parse/mod.rs b/keyvalues-parser/src/text/parse/mod.rs index fb12015..f8759ad 100644 --- a/keyvalues-parser/src/text/parse/mod.rs +++ b/keyvalues-parser/src/text/parse/mod.rs @@ -9,17 +9,11 @@ use pest::{iterators::Pair as PestPair, Atomicity, RuleType}; mod escaped; mod raw; +#[expect(deprecated)] pub use escaped::parse as escaped_parse; -#[deprecated( - since = "0.2.3", - note = "Moved to `keyvalues_parser::error::EscapedPestError`" -)] pub use escaped::PestError as EscapedPestError; +#[expect(deprecated)] pub use raw::parse as raw_parse; -#[deprecated( - since = "0.2.3", - note = "Moved to `keyvalues_parser::error::RawPestError`" -)] pub use raw::PestError as RawPestError; type BoxedState<'a, R> = Box>; @@ -74,8 +68,7 @@ fn skip(s: BoxedState<'_, R>) -> ParseResult<'_, R> { // separate grammars :/ macro_rules! common_parsing { ($parse_fn:ident, $rule:ty, $parse_escaped:expr) => { - /// Attempts to parse VDF text to a [`Vdf`] - pub fn parse(s: &str) -> Result> { + fn parse_(s: &str) -> Result> { let mut full_grammar = $parse_fn(s)?; // There can be multiple base macros before the initial pair @@ -218,10 +211,12 @@ impl<'a> Vdf<'a> { impl<'a> PartialVdf<'a> { /// Attempts to parse VDF text to a [`Vdf`] pub fn parse(s: &'a str) -> Result { + #[expect(deprecated)] escaped_parse(s) } pub fn parse_raw(s: &'a str) -> Result { + #[expect(deprecated)] raw_parse(s) } } diff --git a/keyvalues-parser/src/text/parse/raw.rs b/keyvalues-parser/src/text/parse/raw.rs index 2e873e6..cb43495 100644 --- a/keyvalues-parser/src/text/parse/raw.rs +++ b/keyvalues-parser/src/text/parse/raw.rs @@ -6,6 +6,15 @@ type ParseResult<'a> = pest::ParseResult>; common_parsing!(pest_parse, Rule, false); +/// Attempts to parse VDF text to a [`Vdf`] +#[deprecated( + since = "0.2.3", + note = "Please use `Parser::new().literal_special_chars(true).parse()` instead" +)] +pub fn parse(s: &str) -> Result> { + parse_(s) +} + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum Rule {