Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions keyvalues-parser/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,8 @@ const LOGIN_USERS_VDF: &str = r#"
}
"#;

use keyvalues_parser::Vdf;

fn main() -> Result<(), Box<dyn std::error::Error>> {
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(),
Expand Down
4 changes: 2 additions & 2 deletions keyvalues-parser/examples/parse_mutate_render.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use keyvalues_parser::Vdf;
use keyvalues_parser::{parse, Vdf};

use std::{fs, path::Path};

Expand Down Expand Up @@ -34,7 +34,7 @@ fn update_version(controller_mappings: &mut Vdf, new_version: String) -> Option<

fn main() -> Result<(), Box<dyn std::error::Error>> {
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
Expand Down
105 changes: 82 additions & 23 deletions keyvalues-parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PartialVdf<'text>> {
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<PartialVdf<'text>> {
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<str>`
pub type Key<'text> = Cow<'text, str>;

Expand All @@ -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
///
Expand All @@ -43,8 +105,6 @@ pub type Key<'text> = Cow<'text, str>;
/// ## Example
///
/// ```
/// use keyvalues_parser::Vdf;
///
/// // Parse
/// let vdf_text = r#"
/// "Outer Key"
Expand All @@ -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
Expand All @@ -80,24 +140,6 @@ pub struct Vdf<'text> {
pub value: Value<'text>,
}

impl<'text> From<PartialVdf<'text>> 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<Cow<'text, str>>,
}

impl<'text> Vdf<'text> {
/// Creates a [`Vdf`] using a provided key and value
///
Expand All @@ -115,6 +157,23 @@ impl<'text> Vdf<'text> {
}
}

impl<'text> From<PartialVdf<'text>> 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<Cow<'text, str>>,
}

// 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<Key<'text>, Vec<Value<'text>>>;
type ObjInnerPair<'text> = (Key<'text>, Vec<Value<'text>>);
Expand Down
6 changes: 6 additions & 0 deletions keyvalues-parser/src/text/parse/escaped.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ type ParseResult<'a> = pest::ParseResult<BoxedState<'a>>;

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<PartialVdf<'_>> {
parse_(s)
}

#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Rule {
Expand Down
15 changes: 5 additions & 10 deletions keyvalues-parser/src/text/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<pest::ParserState<'a, R>>;
Expand Down Expand Up @@ -74,8 +68,7 @@ fn skip<R: RuleType>(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<PartialVdf<'_>> {
fn parse_(s: &str) -> Result<PartialVdf<'_>> {
let mut full_grammar = $parse_fn(s)?;

// There can be multiple base macros before the initial pair
Expand Down Expand Up @@ -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<Self> {
#[expect(deprecated)]
escaped_parse(s)
}

pub fn parse_raw(s: &'a str) -> Result<Self> {
#[expect(deprecated)]
raw_parse(s)
}
}
9 changes: 9 additions & 0 deletions keyvalues-parser/src/text/parse/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ type ParseResult<'a> = pest::ParseResult<BoxedState<'a>>;

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<PartialVdf<'_>> {
parse_(s)
}

#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Rule {
Expand Down