hcl-rs 0.12.3

HCL parser and formatter for rust with serde support
Documentation
use crate::expr::Variable;
use crate::util::{is_id_continue, is_id_start, is_ident};
use crate::{Error, Result};
use serde::{Deserialize, Serialize};
use std::borrow::{Borrow, Cow};
use std::fmt;
use std::ops;

/// Represents an HCL identifier.
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(transparent)]
pub struct Identifier(String);

impl Identifier {
    /// Create a new `Identifier` after validating that it only contains characters that are
    /// allowed in HCL identifiers.
    ///
    /// See [`Identifier::sanitized`][Identifier::sanitized] for an infallible alternative to this
    /// function.
    ///
    /// # Example
    ///
    /// ```
    /// # use hcl::Identifier;
    /// assert!(Identifier::new("some_ident").is_ok());
    /// assert!(Identifier::new("").is_err());
    /// assert!(Identifier::new("1two3").is_err());
    /// assert!(Identifier::new("with whitespace").is_err());
    /// ```
    ///
    /// # Errors
    ///
    /// If `ident` contains characters that are not allowed in HCL identifiers or if it is empty an
    /// error will be returned.
    pub fn new<T>(ident: T) -> Result<Self>
    where
        T: Into<String>,
    {
        let ident = ident.into();

        if !is_ident(&ident) {
            return Err(Error::InvalidIdentifier(ident));
        }

        Ok(Identifier(ident))
    }

    /// Create a new `Identifier` after sanitizing the input if necessary.
    ///
    /// If `ident` contains characters that are not allowed in HCL identifiers will be sanitized
    /// according to the following rules:
    ///
    /// - An empty `ident` results in an identifier containing a single underscore.
    /// - Invalid characters in `ident` will be replaced with underscores.
    /// - If `ident` starts with a character that is invalid in the first position but would be
    ///   valid in the rest of an HCL identifier it is prefixed with an underscore.
    ///
    /// See [`Identifier::new`][Identifier::new] for a fallible alternative to this function if
    /// you prefer rejecting invalid identifiers instead of sanitizing them.
    ///
    /// # Example
    ///
    /// ```
    /// # use hcl::Identifier;
    /// assert_eq!(Identifier::sanitized("some_ident").as_str(), "some_ident");
    /// assert_eq!(Identifier::sanitized("").as_str(), "_");
    /// assert_eq!(Identifier::sanitized("1two3").as_str(), "_1two3");
    /// assert_eq!(Identifier::sanitized("with whitespace").as_str(), "with_whitespace");
    /// ```
    pub fn sanitized<T>(ident: T) -> Self
    where
        T: AsRef<str>,
    {
        let input = ident.as_ref();

        if input.is_empty() {
            return Identifier(String::from('_'));
        }

        let mut ident = String::with_capacity(input.len());

        for (i, ch) in input.chars().enumerate() {
            if i == 0 && is_id_start(ch) {
                ident.push(ch);
            } else if is_id_continue(ch) {
                if i == 0 {
                    ident.push('_');
                }
                ident.push(ch);
            } else {
                ident.push('_');
            }
        }

        Identifier(ident)
    }

    /// Create a new `Identifier` without checking if it is valid.
    ///
    /// It is the caller's responsibility to ensure that the identifier is valid.
    ///
    /// For most use cases [`Identifier::new`][Identifier::new] or
    /// [`Identifier::sanitized`][Identifier::sanitized] should be preferred.
    ///
    /// # Safety
    ///
    /// This function is not marked as unsafe because it does not cause undefined behaviour.
    /// However, attempting to serialize an invalid identifier to HCL will produce invalid output.
    pub fn unchecked<T>(ident: T) -> Self
    where
        T: Into<String>,
    {
        Identifier(ident.into())
    }

    /// Consume `self` and return the wrapped `String`.
    pub fn into_inner(self) -> String {
        self.0
    }

    /// Return a reference to the wrapped `str`.
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl From<String> for Identifier {
    fn from(s: String) -> Self {
        Identifier::sanitized(s)
    }
}

impl From<&str> for Identifier {
    fn from(s: &str) -> Self {
        Identifier::sanitized(s)
    }
}

impl<'a> From<Cow<'a, str>> for Identifier {
    fn from(s: Cow<'a, str>) -> Self {
        Identifier::sanitized(s)
    }
}

impl From<Variable> for Identifier {
    fn from(variable: Variable) -> Self {
        variable.into_inner()
    }
}

impl fmt::Display for Identifier {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self)
    }
}

impl ops::Deref for Identifier {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        self.as_str()
    }
}

impl AsRef<str> for Identifier {
    fn as_ref(&self) -> &str {
        self.as_str()
    }
}

impl Borrow<str> for Identifier {
    fn borrow(&self) -> &str {
        self.as_str()
    }
}