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
3 changes: 3 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Unreleased
- [add][minor] Allow re-use of parsed templates with `Template`, `TemplateBuf`, `ByteTemplate` and `ByteTemplateBuf`.

# Version 0.3.1 - 2024-06-09
- [fix][minor] Fix recursive substitution in braced default values.

Expand Down
34 changes: 30 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ Shell-like variable substitution for strings and byte strings.
Variable names can consist of alphanumeric characters and underscores.
They are allowed to start with numbers.

If you want to quickly perform substitution on a string, use [`substitute()`] or [`substitute_bytes()`].

It is also possible to use one of the template types.
The templates parse the source string or bytes once, and can be expanded as many times as you want.
There are four different template types to choose from:
* [`Template`]: borrows the source string.
* [`TemplateBuf`]: owns the source string.
* [`ByteTemplate`]: borrows the source bytes.
* [`ByteTemplateBuf`]: owns the source bytes.
Comment on lines +23 to +26
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to bikeshed over the naming.


## Examples

The [`substitute()`][substitute] function can be used to perform substitution on a `&str`.
Expand All @@ -26,7 +36,7 @@ variables.insert("name", "world");
assert_eq!(subst::substitute("Hello $name!", &variables)?, "Hello world!");
```

The variables can also be taken directly from the environment with the [`Env`][Env] map.
The variables can also be taken directly from the environment with the [`Env`] map.

```rust
assert_eq!(
Expand All @@ -43,8 +53,24 @@ variables.insert("name", b"world");
assert_eq!(subst::substitute_bytes(b"Hello $name!", &variables)?, b"Hello world!");
```

[substitute]: https://docs.rs/subst/latest/subst/fn.substitute.html
[substitute_bytes]: https://docs.rs/subst/latest/subst/fn.substitute_bytes.html
[Env]: https://docs.rs/subst/latest/subst/struct.Env.html
You can also parse a template once and expand it multiple times:

```rust
let mut variables = HashMap::new();
let template = subst::Template::from_str("Welcome to our hair salon, $name!")?;
for name in ["Scrappy", "Coco"] {
variables.insert("name", name);
let message = template.expand(&variables)?;
println!("{}", message);
}
```

[`substitute()`]: https://docs.rs/subst/latest/subst/fn.substitute.html
[`substitute_bytes()`]: https://docs.rs/subst/latest/subst/fn.substitute_bytes.html
[`Template`]: https://docs.rs/subst/latest/subst/struct.Template.html
[`TemplateBuf`]: https://docs.rs/subst/latest/subst/struct.TemplateBuf.html
[`ByteTemplate`]: https://docs.rs/subst/latest/subst/struct.ByteTemplate.html
[`ByteTemplateBuf`]: https://docs.rs/subst/latest/subst/struct.ByteTemplateBuf.html
[`Env`]: https://docs.rs/subst/latest/subst/struct.Env.html
[std::collections::HashMap]: https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html
[std::collections::BTreeMap]: https://doc.rust-lang.org/stable/std/collections/struct.BTreeMap.html
10 changes: 7 additions & 3 deletions README.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

{{readme}}

[substitute]: https://docs.rs/subst/latest/subst/fn.substitute.html
[substitute_bytes]: https://docs.rs/subst/latest/subst/fn.substitute_bytes.html
[Env]: https://docs.rs/subst/latest/subst/struct.Env.html
[`substitute()`]: https://docs.rs/subst/latest/subst/fn.substitute.html
[`substitute_bytes()`]: https://docs.rs/subst/latest/subst/fn.substitute_bytes.html
[`Template`]: https://docs.rs/subst/latest/subst/struct.Template.html
[`TemplateBuf`]: https://docs.rs/subst/latest/subst/struct.TemplateBuf.html
[`ByteTemplate`]: https://docs.rs/subst/latest/subst/struct.ByteTemplate.html
[`ByteTemplateBuf`]: https://docs.rs/subst/latest/subst/struct.ByteTemplateBuf.html
[`Env`]: https://docs.rs/subst/latest/subst/struct.Env.html
[std::collections::HashMap]: https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html
[std::collections::BTreeMap]: https://doc.rust-lang.org/stable/std/collections/struct.BTreeMap.html
106 changes: 106 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,31 @@ pub enum Error {
NoSuchVariable(NoSuchVariable),
}

/// An error that can occur while parsing a template.
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub enum ParseError {
/// The input string contains an invalid escape sequence.
InvalidEscapeSequence(InvalidEscapeSequence),

/// The input string contains a variable placeholder without a variable name (`"${}"`).
MissingVariableName(MissingVariableName),

/// The input string contains an unexpected character.
UnexpectedCharacter(UnexpectedCharacter),

/// The input string contains an unclosed variable placeholder.
MissingClosingBrace(MissingClosingBrace),
}

/// An error that can occur while expanding a template.
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub enum ExpandError {
/// The input string contains a placeholder for a variable that is not in the variable map.
NoSuchVariable(NoSuchVariable),
}

impl From<InvalidEscapeSequence> for Error {
#[inline]
fn from(other: InvalidEscapeSequence) -> Self {
Expand Down Expand Up @@ -55,6 +80,62 @@ impl From<NoSuchVariable> for Error {
}
}

impl From<ParseError> for Error {
#[inline]
fn from(other: ParseError) -> Self {
match other {
ParseError::InvalidEscapeSequence(e) => Self::InvalidEscapeSequence(e),
ParseError::MissingVariableName(e) => Self::MissingVariableName(e),
ParseError::UnexpectedCharacter(e) => Self::UnexpectedCharacter(e),
ParseError::MissingClosingBrace(e) => Self::MissingClosingBrace(e),
}
}
}

impl From<ExpandError> for Error {
#[inline]
fn from(other: ExpandError) -> Self {
match other {
ExpandError::NoSuchVariable(e) => Self::NoSuchVariable(e),
}
}
}

impl From<InvalidEscapeSequence> for ParseError {
#[inline]
fn from(other: InvalidEscapeSequence) -> Self {
Self::InvalidEscapeSequence(other)
}
}

impl From<MissingVariableName> for ParseError {
#[inline]
fn from(other: MissingVariableName) -> Self {
Self::MissingVariableName(other)
}
}

impl From<UnexpectedCharacter> for ParseError {
#[inline]
fn from(other: UnexpectedCharacter) -> Self {
Self::UnexpectedCharacter(other)
}
}

impl From<MissingClosingBrace> for ParseError {
#[inline]
fn from(other: MissingClosingBrace) -> Self {
Self::MissingClosingBrace(other)
}
}

impl From<NoSuchVariable> for ExpandError {
#[inline]
fn from(other: NoSuchVariable) -> Self {
Self::NoSuchVariable(other)
}
}

impl std::error::Error for Error {}

impl std::fmt::Display for Error {
Expand All @@ -70,6 +151,31 @@ impl std::fmt::Display for Error {
}
}

impl std::error::Error for ParseError {}

impl std::fmt::Display for ParseError {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::InvalidEscapeSequence(e) => e.fmt(f),
Self::MissingVariableName(e) => e.fmt(f),
Self::UnexpectedCharacter(e) => e.fmt(f),
Self::MissingClosingBrace(e) => e.fmt(f),
}
}
}

impl std::error::Error for ExpandError {}

impl std::fmt::Display for ExpandError {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::NoSuchVariable(e) => e.fmt(f),
}
}
}

/// A character or byte from the input.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum CharOrByte {
Expand Down
Loading