//! Various basic types for YAML handling
//!
use doc_comment::doc_comment;
use hashlink::LinkedHashMap;
use std::borrow::{Borrow, Cow};
use std::fmt::{self, Display};
use std::hash::{Hash, Hasher};
use std::ops::{Deref, DerefMut};
use yaml_rust::Yaml as YamlNode;
/// A displayable marker for a YAML node
///
/// While `Marker` can be `Display`'d it doesn't understand what its source
/// means. This struct is the result of asking a Marker to render itself.
///
/// ```
/// # use marked_yaml::*;
/// let filenames = vec!["examples/everything.yaml"];
/// let nodes: Vec<_> = filenames
/// .iter()
/// .enumerate()
/// .map(|(i, name)| parse_yaml(i, std::fs::read_to_string(name).unwrap()).unwrap())
/// .collect();
/// for (n, node) in nodes.iter().enumerate() {
/// let marker = node.span().start().unwrap();
/// let rendered = format!("{}", marker.render(|i| filenames[i]));
/// assert!(rendered.starts_with(filenames[n]));
/// }
/// ```
pub struct RenderedMarker<D> {
source: D,
line: usize,
column: usize,
}
impl<D> Display for RenderedMarker<D>
where
D: Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.source.fmt(f)?;
write!(f, ":{}:{}", self.line, self.column)
}
}
/// A marker for a YAML node
///
/// This indicates where a node started or ended.
///
/// ```
/// use marked_yaml::{parse_yaml, Marker};
/// let node = parse_yaml(100, "{foo: bar}").unwrap();
/// let map = node.as_mapping().unwrap();
/// let bar = map.get("foo").unwrap();
/// // the "bar" string started on line 1, column 7 of source ID 100.
/// assert_eq!(bar.span().start(), Some(&Marker::new(100, 1, 7)));
/// ```
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Marker {
source: usize,
line: usize,
column: usize,
}
impl Marker {
/// Create a new Marker
///
/// This will typically not be used because markers will come from
/// parsing YAML, however it is provided for completeness and in case
/// you need it for your own tests.
///
/// ```
/// # use marked_yaml::Marker;
/// let marker = Marker::new(0, 1, 2);
/// # assert_eq!(marker.source(), 0);
/// # assert_eq!(marker.line(), 1);
/// # assert_eq!(marker.column(), 2);
/// ```
pub fn new(source: usize, line: usize, column: usize) -> Self {
Self {
source,
line,
column,
}
}
/// The source index given for this marker.
///
/// When parsing YAML, we record where nodes start (and often finish).
/// This is the source index provided when parsing the YAML. Likely this
/// refers to a vector of PathBuf recording where input came from, but that
/// is entirely up to the user of this library crate.
///
/// This is likely most useful to computers, not humans.
///
/// ```
/// # use marked_yaml::Marker;
/// # let marker = Marker::new(0, 1, 2);
/// assert_eq!(marker.source(), 0);
/// ```
pub fn source(&self) -> usize {
self.source
}
/// The line number on which this marker resides, 1-indexed
///
/// When parsing YAML, we record where nodes start (and often finish).
/// This is the line number of where this marker resides. Line numbers
/// start with 1 to make them more useful to humans.
///
/// ```
/// # use marked_yaml::Marker;
/// # let marker = Marker::new(0, 1, 2);
/// assert_eq!(marker.line(), 1);
/// ```
pub fn line(&self) -> usize {
self.line
}
/// The column number at which this marker resides, 1-indexed
///
/// When parsing YAML, we record where nodes start (and often finish).
/// This is the column number of where this marker resides. Column numbers
/// start with 1 to make them more useful to humans.
///
/// ```
/// # use marked_yaml::Marker;
/// # let marker = Marker::new(0, 1, 2);
/// assert_eq!(marker.column(), 2);
/// ```
pub fn column(&self) -> usize {
self.column
}
/// Render this marker
///
/// Markers have a source identifier, typically as passed to `parse_yaml()`
/// but have no way in and of themselves to turn that into a useful name.
/// This function allows you to create a rendered marker which knows how
/// to show the source name.
///
/// ```
/// # use marked_yaml::Marker;
/// # let marker = Marker::new(0, 1, 2);
/// let rendered = marker.render(|_| "name");
/// assert_eq!(format!("{}", rendered), "name:1:2")
/// ```
pub fn render<D, F>(self, renderfn: F) -> RenderedMarker<D>
where
D: Display,
F: FnOnce(usize) -> D,
{
RenderedMarker {
source: renderfn(self.source),
line: self.line,
column: self.column,
}
}
/// Set the source index for this marker
///
/// ```
/// # use marked_yaml::Marker;
/// # let mut marker = Marker::new(0, 0, 0);
/// assert_ne!(marker.source(), 1);
/// marker.set_source(1);
/// assert_eq!(marker.source(), 1);
/// ```
pub fn set_source(&mut self, source: usize) {
self.source = source;
}
/// Set the line number for this marker
///
/// ```
/// # use marked_yaml::Marker;
/// # let mut marker = Marker::new(0, 0, 0);
/// assert_ne!(marker.line(), 1);
/// marker.set_line(1);
/// assert_eq!(marker.line(), 1);
/// ```
pub fn set_line(&mut self, line: usize) {
self.line = line;
}
/// Set the column number for this marker
///
/// ```
/// # use marked_yaml::Marker;
/// # let mut marker = Marker::new(0, 0, 0);
/// assert_ne!(marker.column(), 1);
/// marker.set_column(1);
/// assert_eq!(marker.column(), 1);
/// ```
pub fn set_column(&mut self, column: usize) {
self.column = column;
}
}
impl Display for Marker {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{}", self.line, self.column)
}
}
/// The span for a YAML marked node
///
/// ```
/// use marked_yaml::{parse_yaml, Marker, Span};
/// let node = parse_yaml(100, "{foo: bar}").unwrap();
/// let map = node.as_mapping().unwrap();
/// assert_eq!(map.span(), &Span::new_with_marks(Marker::new(100, 1, 1), Marker::new(100, 1, 10)));
/// ```
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Span {
start: Option<Marker>,
end: Option<Marker>,
}
impl Span {
/// Create a span with no marker information
///
/// Sometimes we simply do not know where information came from (for example
/// if it was created by software) and in that case we can create a blank
/// span.
///
/// ```
/// # use marked_yaml::Span;
/// let blank = Span::new_blank();
/// # assert_eq!(blank.start(), None);
/// # assert_eq!(blank.end(), None);
/// ```
pub fn new_blank() -> Self {
Self {
start: None,
end: None,
}
}
/// Create a span with only start information
///
/// Sometimes when creating a span we know where it started but not where
/// it ends. This might be during parsing, or for some other reason.
///
/// ```
/// # use marked_yaml::{Marker, Span};
/// let span = Span::new_start(Marker::new(0, 1, 2));
/// # assert_eq!(span.start().unwrap(), &Marker::new(0, 1, 2));
/// # assert_eq!(span.end(), None);
/// ```
pub fn new_start(start: Marker) -> Self {
Self {
start: Some(start),
end: None,
}
}
/// Create a span with both start and end markers
///
/// When we know both the start and end of a node, we can create a span
/// which has all that knowledge.
///
/// ```
/// # use marked_yaml::{Marker,Span};
/// let span = Span::new_with_marks(Marker::new(0, 1, 1), Marker::new(10, 2, 1));
/// # assert_eq!(span.start().unwrap(), &Marker::new(0, 1, 1));
/// # assert_eq!(span.end().unwrap(), &Marker::new(10, 2, 1));
/// ```
pub fn new_with_marks(start: Marker, end: Marker) -> Self {
Self {
start: Some(start),
end: Some(end),
}
}
/// The start of the span
///
/// ```
/// # use marked_yaml::{Marker, Span};
/// # let span = Span::new_with_marks(Marker::new(0, 1, 1), Marker::new(10, 2, 1));
/// assert_eq!(span.start(), Some(&Marker::new(0, 1, 1)));
/// ```
pub fn start(&self) -> Option<&Marker> {
self.start.as_ref()
}
/// The end of the span
///
/// ```
/// # use marked_yaml::{Marker, Span};
/// # let span = Span::new_with_marks(Marker::new(0, 1, 1), Marker::new(10, 2, 1));
/// assert_eq!(span.end(), Some(&Marker::new(10, 2, 1)));
/// ```
pub fn end(&self) -> Option<&Marker> {
self.end.as_ref()
}
/// The start of the span, mutably
///
/// ```
/// # use marked_yaml::{Marker, Span};
/// # let mut span = Span::new_with_marks(Marker::new(0, 1, 1), Marker::new(10, 2, 1));
/// span.start_mut().unwrap().set_line(5);
/// assert_eq!(span.start(), Some(&Marker::new(0, 5, 1)));
/// ```
pub fn start_mut(&mut self) -> Option<&mut Marker> {
self.start.as_mut()
}
/// The end of the span, mutably
///
/// ```
/// # use marked_yaml::{Marker, Span};
/// # let mut span = Span::new_with_marks(Marker::new(0, 1, 1), Marker::new(10, 2, 1));
/// span.end_mut().unwrap().set_line(5);
/// assert_eq!(span.end(), Some(&Marker::new(10, 5, 1)));
/// ```
pub fn end_mut(&mut self) -> Option<&mut Marker> {
self.end.as_mut()
}
/// Replace the start of the span
///
/// ```
/// # use marked_yaml::{Marker, Span};
/// # let mut span = Span::new_blank();
/// assert_eq!(span.start(), None);
/// span.set_start(Some(Marker::new(0, 1, 2)));
/// assert_eq!(span.start(), Some(&Marker::new(0, 1, 2)));
/// ```
pub fn set_start(&mut self, start: Option<Marker>) {
self.start = start;
}
/// Replace the end of the span
///
/// ```
/// # use marked_yaml::{Marker, Span};
/// # let mut span = Span::new_blank();
/// assert_eq!(span.end(), None);
/// span.set_end(Some(Marker::new(0, 1, 2)));
/// assert_eq!(span.end(), Some(&Marker::new(0, 1, 2)));
/// ```
pub fn set_end(&mut self, end: Option<Marker>) {
self.end = end;
}
}
/// A marked YAML node
///
/// **NOTE**: Nodes are considered equal even if they don't come from the
/// same place. *i.e. their spans are ignored for equality and hashing*
///
/// ```
/// use marked_yaml::parse_yaml;
/// let node = parse_yaml(100, "{foo: bar}").unwrap();
/// assert!(node.as_mapping().is_some());
/// ```
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Node {
/// A YAML scalar
///
/// You can test if a node is a scalar, and retrieve it as one if you
/// so wish.
Scalar(MarkedScalarNode),
/// A YAML mapping
///
/// You can test if a node is a mapping, and retrieve it as one if you
/// so wish.
Mapping(MarkedMappingNode),
/// A YAML sequence
///
/// You can test if a node is a sequence, and retrieve it as one if you
/// so wish.
Sequence(MarkedSequenceNode),
}
/// A marked scalar YAML node
///
/// Scalar nodes are treated by this crate as strings, though a few special
/// values are processed into the types which YAML would ascribe. In particular
/// strings of the value `null`, `true`, `false`, etc. are able to present as
/// their special values to make it a bit easier for users of the crate.
///
/// **NOTE**: Nodes are considered equal even if they don't come from the
/// same place. *i.e. their spans are ignored for equality and hashing*
///
/// ```
/// use marked_yaml::{parse_yaml, Marker};
/// let node = parse_yaml(100, "{foo: bar}").unwrap();
/// let map = node.as_mapping().unwrap();
/// let bar = map.get("foo").unwrap();
/// // the "bar" string started on line 1, column 7 of source ID 100.
/// assert_eq!(bar.span().start(), Some(&Marker::new(100, 1, 7)));
/// ```
#[derive(Clone, Debug)]
pub struct MarkedScalarNode {
span: Span,
value: String,
}
pub(crate) type MappingHash = LinkedHashMap<MarkedScalarNode, Node>;
/// A marked YAML mapping node
///
/// Mapping nodes in YAML are defined as a key/value mapping where the keys are
/// unique and always scalars, whereas values may be YAML nodes of any kind.
///
/// Because *some* users of this crate may need to care about insertion order
/// we use [`hashlink::LinkedHashMap`] for this.
///
/// **NOTE**: Nodes are considered equal even if they don't come from the
/// same place. *i.e. their spans are ignored for equality and hashing*
///
/// ```
/// use marked_yaml::{parse_yaml, Marker, Span};
/// let node = parse_yaml(100, "{foo: bar}").unwrap();
/// let map = node.as_mapping().unwrap();
/// assert_eq!(map.span(), &Span::new_with_marks(Marker::new(100, 1, 1), Marker::new(100, 1, 10)));
/// ```
#[derive(Clone, Debug)]
pub struct MarkedMappingNode {
span: Span,
value: MappingHash,
}
/// A marked YAML sequence node
///
/// Sequence nodes in YAML are simply ordered lists of YAML nodes.
///
/// **NOTE**: Nodes are considered equal even if they don't come from the
/// same place. *i.e. their spans are ignored for equality and hashing*
///
/// ```
/// use marked_yaml::{parse_yaml, Marker, Span};
/// let node = parse_yaml(100, "{foo: [bar]}").unwrap();
/// let map = node.as_mapping().unwrap();
/// let seq = map.get("foo").unwrap();
/// let seq = seq.as_sequence().unwrap();
/// assert_eq!(seq.span(), &Span::new_with_marks(Marker::new(100, 1, 7), Marker::new(100, 1, 11)));
/// ```
#[derive(Clone, Debug)]
pub struct MarkedSequenceNode {
span: Span,
value: Vec<Node>,
}
macro_rules! basic_traits {
($t:path) => {
impl PartialEq for $t {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
#[allow(clippy::partialeq_ne_impl)]
fn ne(&self, other: &Self) -> bool {
self.value != other.value
}
}
impl Eq for $t {}
impl Hash for $t {
fn hash<H: Hasher>(&self, state: &mut H) {
self.value.hash(state);
}
}
};
}
basic_traits!(MarkedScalarNode);
basic_traits!(MarkedSequenceNode);
basic_traits!(MarkedMappingNode);
impl<T> From<T> for Node
where
T: Into<MarkedScalarNode>,
{
fn from(value: T) -> Node {
Node::Scalar(value.into())
}
}
impl From<MarkedSequenceNode> for Node {
fn from(value: MarkedSequenceNode) -> Node {
Node::Sequence(value)
}
}
impl<T> From<Vec<T>> for Node
where
T: Into<Node>,
{
fn from(value: Vec<T>) -> Node {
Node::Sequence(value.into_iter().collect())
}
}
impl From<MarkedMappingNode> for Node {
fn from(value: MarkedMappingNode) -> Node {
Node::Mapping(value)
}
}
impl From<MappingHash> for Node {
fn from(value: MappingHash) -> Node {
Node::Mapping(value.into())
}
}
macro_rules! node_span {
($t:path) => {
impl $t {
doc_comment!(
concat!(
r#"Retrieve the Span from this node.
```
# use marked_yaml::types::*;
let node = "#,
stringify!($t),
r#"::new_empty(Span::new_blank());
assert_eq!(node.span(), &Span::new_blank());
```"#
),
pub fn span(&self) -> &Span {
&self.span
}
);
doc_comment!(
concat!(
r#"Retrieve the Span from this node mutably.
```
# use marked_yaml::types::*;
let mut node = "#,
stringify!($t),
r#"::new_empty(Span::new_blank());
node.span_mut().set_start(Some(Marker::new(0, 1, 0)));
assert_eq!(node.span().start(), Some(&Marker::new(0, 1, 0)));
```"#
),
pub fn span_mut(&mut self) -> &mut Span {
&mut self.span
}
);
}
};
}
node_span!(MarkedScalarNode);
node_span!(MarkedMappingNode);
node_span!(MarkedSequenceNode);
impl Node {
/// Retrieve the Span from the contained Node
///
/// ```
/// # use marked_yaml::types::*;
/// let node: Node = "foobar".into();
/// let span = node.span();
/// assert_eq!(span.start(), None);
/// ```
pub fn span(&self) -> &Span {
match self {
Node::Scalar(msn) => msn.span(),
Node::Sequence(msn) => msn.span(),
Node::Mapping(mmn) => mmn.span(),
}
}
/// Retrieve the Span from the contained Node, mutably
///
/// ```
/// # use marked_yaml::types::*;
/// let mut node: Node = "foobar".into();
/// let mut span = node.span_mut();
/// assert_eq!(span.start(), None);
/// span.set_start(Some(Marker::new(0, 1, 0)));
/// assert_eq!(span.start(), Some(&Marker::new(0, 1, 0)));
/// ```
pub fn span_mut(&mut self) -> &mut Span {
match self {
Node::Scalar(msn) => msn.span_mut(),
Node::Sequence(msn) => msn.span_mut(),
Node::Mapping(mmn) => mmn.span_mut(),
}
}
/// Retrieve the scalar from this node if there is one
///
/// ```
/// # use marked_yaml::types::*;
/// let node: Node = "foobar".into();
/// let scalar = node.as_scalar();
/// assert!(scalar.is_some());
/// ```
pub fn as_scalar(&self) -> Option<&MarkedScalarNode> {
match self {
Node::Scalar(msn) => Some(msn),
_ => None,
}
}
/// Retrieve the sequence from this node if there is one
///
/// ```
/// # use marked_yaml::types::*;
/// let node: Node = vec!["foobar"].into();
/// let sequence = node.as_sequence();
/// assert!(sequence.is_some());
/// ```
pub fn as_sequence(&self) -> Option<&MarkedSequenceNode> {
match self {
Node::Sequence(msn) => Some(msn),
_ => None,
}
}
/// Retrieve the mapping from this node if there is one
///
/// ```
/// # use marked_yaml::*;
/// let node: Node = parse_yaml(0, "{foobar: baz}").unwrap();
/// let mapping = node.as_mapping();
/// assert!(mapping.is_some());
/// ```
pub fn as_mapping(&self) -> Option<&MarkedMappingNode> {
match self {
Node::Mapping(mmn) => Some(mmn),
_ => None,
}
}
/// Retrieve the scalar from this node if there is one, mutably
///
/// ```
/// # use marked_yaml::types::*;
/// let mut node: Node = "foobar".into();
/// let mut scalar = node.as_scalar_mut();
/// assert!(scalar.is_some());
/// ```
pub fn as_scalar_mut(&mut self) -> Option<&mut MarkedScalarNode> {
match self {
Node::Scalar(msn) => Some(msn),
_ => None,
}
}
/// Retrieve the sequence from this node if there is one, mutably
///
/// ```
/// # use marked_yaml::types::*;
/// let mut node: Node = vec!["foobar"].into();
/// let mut sequence = node.as_sequence_mut();
/// assert!(sequence.is_some());
/// ```
pub fn as_sequence_mut(&mut self) -> Option<&mut MarkedSequenceNode> {
match self {
Node::Sequence(msn) => Some(msn),
_ => None,
}
}
/// Retrieve the mapping from this node if there is one, mutably
///
/// ```
/// # use marked_yaml::*;
/// let mut node: Node = parse_yaml(0, "{foobar: baz}").unwrap();
/// let mut mapping = node.as_mapping_mut();
/// assert!(mapping.is_some());
/// ```
pub fn as_mapping_mut(&mut self) -> Option<&mut MarkedMappingNode> {
match self {
Node::Mapping(mmn) => Some(mmn),
_ => None,
}
}
}
impl MarkedScalarNode {
/// Create a new scalar node with no value
///
/// ```
/// # use marked_yaml::types::*;
/// let node = MarkedScalarNode::new_empty(Span::new_blank());
/// ```
pub fn new_empty(span: Span) -> Self {
Self {
span,
value: String::new(),
}
}
/// Create a new scalar node
///
/// ```
/// # use marked_yaml::types::*;
/// let node = MarkedScalarNode::new(Span::new_blank(), "foobar");
/// ```
pub fn new<'a, S: Into<Cow<'a, str>>>(span: Span, content: S) -> Self {
Self {
span,
value: content.into().into_owned(),
}
}
/// Treat the scalar node as a string
///
/// Since scalars are always stringish, this is always safe.
///
/// ```
/// # use marked_yaml::types::*;
/// let node: MarkedScalarNode = "foobar".into();
/// assert_eq!(node.as_str(), "foobar");
/// ```
pub fn as_str(&self) -> &str {
self.value.as_str()
}
/// Treat the scalar node as a boolean
///
/// If the scalar contains any of the following then it is true:
///
/// * `true`
/// * `True`
/// * `TRUE`
///
/// The following are considered false:
///
/// * `false`
/// * `False`
/// * `FALSE`
///
/// Everything else is not a boolean and so will return None
///
/// ```
/// # use marked_yaml::types::*;
/// let node: MarkedScalarNode = "true".into();
/// assert_eq!(node.as_bool(), Some(true));
/// let node: MarkedScalarNode = "FALSE".into();
/// assert_eq!(node.as_bool(), Some(false));
/// let node: MarkedScalarNode = "NO".into(); // YAML boolean, but not for us
/// assert_eq!(node.as_bool(), None);
/// ```
pub fn as_bool(&self) -> Option<bool> {
match self.value.as_str() {
"true" | "True" | "TRUE" => Some(true),
"false" | "False" | "FALSE" => Some(false),
_ => None,
}
}
}
impl<'a> From<&'a str> for MarkedScalarNode {
/// Convert from any borrowed string into a node
///
/// ```
/// # use marked_yaml::types::*;
/// let node: MarkedScalarNode = "foobar".into();
/// ```
fn from(value: &'a str) -> Self {
Self::new(Span::new_blank(), value)
}
}
impl From<String> for MarkedScalarNode {
/// Convert from any owned string into a node
///
/// ```
/// # use marked_yaml::types::*;
/// let foobar = "foobar".to_string();
/// let node: MarkedScalarNode = foobar.into();
/// ```
fn from(value: String) -> Self {
Self::new(Span::new_blank(), value)
}
}
impl From<bool> for MarkedScalarNode {
/// Convert from a boolean into a node
///
/// ```
/// # use marked_yaml::types::*;
/// let node = MarkedScalarNode::from(true);
/// ```
fn from(value: bool) -> Self {
if value {
"true".into()
} else {
"false".into()
}
}
}
macro_rules! scalar_from_to_number {
($t:ident, $as:ident) => {
impl From<$t> for MarkedScalarNode {
doc_comment!(
concat!(
"Convert from ",
stringify!($t),
r#" into a node
```
# use marked_yaml::types::*;
let value: "#,
stringify!($t),
r#" = 0;
let node: MarkedScalarNode = value.into();
assert_eq!(&*node, "0");
```"#
),
fn from(value: $t) -> Self {
format!("{}", value).into()
}
);
}
impl MarkedScalarNode {
doc_comment!(
concat!(
"Treat the scalar node as ",
stringify!($t),
r#".
If this scalar node's value can be represented properly as
a number of the right kind then return it. This is essentially
a shortcut for using the `FromStr` trait on the return value of
`.as_str()`.
```
# use marked_yaml::types::*;
let node: MarkedScalarNode = "0".into();
assert_eq!(node.as_"#,
stringify!($t),
r#"(), Some(0"#,
stringify!($t),
r#"));
```"#
),
pub fn $as(&self) -> Option<$t> {
use std::str::FromStr;
$t::from_str(&self.value).ok()
}
);
}
};
}
scalar_from_to_number!(i8, as_i8);
scalar_from_to_number!(i16, as_i16);
scalar_from_to_number!(i32, as_i32);
scalar_from_to_number!(i64, as_i64);
scalar_from_to_number!(i128, as_i128);
scalar_from_to_number!(isize, as_isize);
scalar_from_to_number!(u8, as_u8);
scalar_from_to_number!(u16, as_u16);
scalar_from_to_number!(u32, as_u32);
scalar_from_to_number!(u64, as_u64);
scalar_from_to_number!(u128, as_u128);
scalar_from_to_number!(usize, as_usize);
impl Deref for MarkedScalarNode {
type Target = str;
/// Borrow the string value inside this scalar node
///
/// ```
/// # use marked_yaml::types::*;
/// # use std::str::FromStr;
/// let truth: MarkedScalarNode = "true".into();
/// assert!(bool::from_str(&truth).unwrap())
/// ```
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl Borrow<str> for MarkedScalarNode {
fn borrow(&self) -> &str {
&self.value
}
}
impl MarkedSequenceNode {
/// Create a new empty sequence node
///
/// ```
/// # use marked_yaml::types::*;
/// let node = MarkedSequenceNode::new_empty(Span::new_blank());
/// ```
pub fn new_empty(span: Span) -> Self {
Self {
span,
value: Vec::new(),
}
}
/// Create a new sequence node from a vector of nodes
///
/// ```
/// # use marked_yaml::types::*;
/// let node = MarkedSequenceNode::new(Span::new_blank(), Vec::new());
/// ```
pub fn new(span: Span, value: Vec<Node>) -> Self {
Self { span, value }
}
/// Get the node at the given index
///
/// If the index is invalid then None will be returned
///
/// ```
/// # use marked_yaml::types::*;
/// let seq: MarkedSequenceNode = vec!["foobar"].into_iter().collect();
/// assert_eq!(seq.get_node(0)
/// .and_then(Node::as_scalar)
/// .map(MarkedScalarNode::as_str)
/// .unwrap(),
/// "foobar");
pub fn get_node(&self, index: usize) -> Option<&Node> {
self.value.get(index)
}
/// Get the scalar at the given index
///
/// If the index is invalid, or the node at that index is not a scalar
/// node, then None will be returned.
///
/// ```
/// # use marked_yaml::types::*;
/// let seq: MarkedSequenceNode = vec!["foobar"].into_iter().collect();
/// assert_eq!(seq.get_scalar(0)
/// .map(MarkedScalarNode::as_str)
/// .unwrap(),
/// "foobar");
/// ```
pub fn get_scalar(&self, index: usize) -> Option<&MarkedScalarNode> {
self.get_node(index).and_then(Node::as_scalar)
}
/// Get the sequence at the given index
///
/// If the index is invalid, or the node at that index is not a sequence
/// node, then None will be returned.
///
/// ```
/// # use marked_yaml::types::*;
/// let seq: MarkedSequenceNode = vec![vec!["foobar"]].into_iter().collect();
/// assert_eq!(seq.get_sequence(0)
/// .and_then(|s| s.get_scalar(0))
/// .map(MarkedScalarNode::as_str)
/// .unwrap(),
/// "foobar");
/// ```
pub fn get_sequence(&self, index: usize) -> Option<&MarkedSequenceNode> {
self.get_node(index).and_then(Node::as_sequence)
}
/// Get the mapping at the given index
///
/// If the index is invalid, or the node at that index is not a mapping
/// node, then None will be returned.
///
/// ```
/// # use marked_yaml::types::*;
/// # use hashlink::LinkedHashMap;
/// # let map: LinkedHashMap<MarkedScalarNode, Node> = LinkedHashMap::new();
/// let seq: MarkedSequenceNode = vec![map].into_iter().collect();
/// assert!(seq.get_mapping(0).is_some());
/// ```
pub fn get_mapping(&self, index: usize) -> Option<&MarkedMappingNode> {
self.get_node(index).and_then(Node::as_mapping)
}
}
impl Deref for MarkedSequenceNode {
type Target = Vec<Node>;
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl DerefMut for MarkedSequenceNode {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.value
}
}
impl<T> FromIterator<T> for MarkedSequenceNode
where
T: Into<Node>,
{
/// Allow collecting things into a sequence node
///
/// ```
/// # use marked_yaml::types::*;
/// let node: MarkedSequenceNode = vec!["hello", "world"].into_iter().collect();
/// ```
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let value: Vec<Node> = iter.into_iter().map(Into::into).collect();
let span = match value.len() {
0 => Span::new_blank(),
1 => *value[0].span(),
_ => Span {
start: value[0].span().start,
end: value[value.len() - 1].span().end,
},
};
Self { span, value }
}
}
impl<T> From<Vec<T>> for MarkedSequenceNode
where
T: Into<Node>,
{
/// Allow converting from vectors of things to sequence nodes
///
/// ```
/// # use marked_yaml::types::*;
/// let node: MarkedSequenceNode = vec!["hello", "world"].into();
/// ```
fn from(value: Vec<T>) -> Self {
let value: Vec<Node> = value.into_iter().map(Into::into).collect();
let span = match value.len() {
0 => Span::new_blank(),
1 => *value[0].span(),
_ => {
let start = value[0].span().start;
let end = value[value.len() - 1].span().end;
Span { start, end }
}
};
Self { span, value }
}
}
impl MarkedMappingNode {
/// Create a new empty mapping node
///
/// ```
/// # use marked_yaml::types::*;
/// let node = MarkedMappingNode::new_empty(Span::new_blank());
/// ```
pub fn new_empty(span: Span) -> Self {
Self {
span,
value: LinkedHashMap::new(),
}
}
/// Create a new mapping node from the given hash table
///
/// ```
/// # use marked_yaml::types::*;
/// # use hashlink::LinkedHashMap;
/// let node = MarkedMappingNode::new(Span::new_blank(), LinkedHashMap::new());
/// ```
pub fn new(span: Span, value: MappingHash) -> Self {
Self { span, value }
}
/// Get the node for the given string key
///
/// If the index is not found then None is returned.
///
/// ```
/// # use marked_yaml::types::*;
/// # use marked_yaml::parse_yaml;
/// let node = parse_yaml(0, "{key: value}").unwrap();
/// let map = node.as_mapping().unwrap();
/// assert_eq!(map.get_node("key")
/// .and_then(Node::as_scalar)
/// .map(MarkedScalarNode::as_str)
/// .unwrap(),
/// "value");
/// ```
pub fn get_node(&self, index: &str) -> Option<&Node> {
self.value.get(index)
}
/// Get the scalar for the given string key
///
/// If the key is not found, or the node for that key is not a scalar
/// node, then None will be returned.
///
/// ```
/// # use marked_yaml::types::*;
/// # use marked_yaml::parse_yaml;
/// let node = parse_yaml(0, "{key: value}").unwrap();
/// let map = node.as_mapping().unwrap();
/// assert_eq!(map.get_scalar("key")
/// .map(MarkedScalarNode::as_str)
/// .unwrap(),
/// "value");
/// ```
pub fn get_scalar(&self, index: &str) -> Option<&MarkedScalarNode> {
self.get_node(index).and_then(Node::as_scalar)
}
/// Get the sequence at the given index
///
/// If the key is not found, or the node for that key is not a sequence
/// node, then None will be returned.
///
/// ```
/// # use marked_yaml::types::*;
/// # use marked_yaml::parse_yaml;
/// let node = parse_yaml(0, "{key: [value]}").unwrap();
/// let map = node.as_mapping().unwrap();
/// assert_eq!(map.get_sequence("key")
/// .and_then(|s| s.get_scalar(0))
/// .map(MarkedScalarNode::as_str)
/// .unwrap(),
/// "value");
/// ```
pub fn get_sequence(&self, index: &str) -> Option<&MarkedSequenceNode> {
self.get_node(index).and_then(Node::as_sequence)
}
/// Get the mapping at the given index
///
/// If the key is not found, or the node for that key is not a mapping
/// node, then None will be returned.
///
/// ```
/// # use marked_yaml::types::*;
/// # use marked_yaml::parse_yaml;
/// let node = parse_yaml(0, "{key: {inner: value}}").unwrap();
/// let map = node.as_mapping().unwrap();
/// assert_eq!(map.get_mapping("key")
/// .and_then(|m| m.get_scalar("inner"))
/// .map(MarkedScalarNode::as_str)
/// .unwrap(),
/// "value");
/// ```
pub fn get_mapping(&self, index: &str) -> Option<&MarkedMappingNode> {
self.get_node(index).and_then(Node::as_mapping)
}
}
impl Deref for MarkedMappingNode {
type Target = MappingHash;
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl DerefMut for MarkedMappingNode {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.value
}
}
impl From<MappingHash> for MarkedMappingNode {
fn from(value: MappingHash) -> Self {
Self::new(Span::new_blank(), value)
}
}
impl<T, U> FromIterator<(T, U)> for MarkedMappingNode
where
T: Into<MarkedScalarNode>,
U: Into<Node>,
{
/// Allow collecting into a mapping node
///
/// ```
/// # use marked_yaml::types::*;
/// # use std::collections::HashMap;
/// # let mut hashmap = HashMap::new();
/// hashmap.insert("hello", vec!["world".to_string()]);
/// hashmap.insert("key", vec!["value".to_string()]);
/// let node: MarkedMappingNode = hashmap.into_iter().collect();
/// ```
fn from_iter<I: IntoIterator<Item = (T, U)>>(iter: I) -> Self {
let value: MappingHash = iter
.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.collect();
let span = match value.len() {
0 => Span::new_blank(),
// Unwrap is safe because there's at least one span here
1 => {
let (k, v) = value.iter().next().unwrap();
Span {
start: k.span().start,
end: v.span().end,
}
}
_ => {
let mut iter = value.iter();
// Unwraps save because there's at least two spans here
let start = iter.next().unwrap().0.span().start;
let end = iter.last().unwrap().1.span().end;
Span { start, end }
}
};
Self { span, value }
}
}
/// Errors which could be encountered while converting from a `yaml_rust::Yaml`
#[derive(Debug, PartialEq, Eq)]
pub enum YamlConversionError {
/// An alias was encountered while converting
Alias,
/// A BadValue was encountered while converting
BadValue,
/// A non-scalar value was encountered when a scalar was expected
NonScalar,
}
impl TryFrom<YamlNode> for MarkedScalarNode {
type Error = YamlConversionError;
fn try_from(value: YamlNode) -> Result<Self, Self::Error> {
match value {
YamlNode::Alias(_) => Err(YamlConversionError::Alias),
YamlNode::Array(_) => Err(YamlConversionError::NonScalar),
YamlNode::BadValue => Err(YamlConversionError::BadValue),
YamlNode::Boolean(b) => Ok(b.into()),
YamlNode::Hash(_) => Err(YamlConversionError::NonScalar),
YamlNode::Integer(i) => Ok(i.into()),
YamlNode::Null => Ok("null".into()),
YamlNode::Real(s) => Ok(s.into()),
YamlNode::String(s) => Ok(s.into()),
}
}
}
impl TryFrom<YamlNode> for Node {
type Error = YamlConversionError;
/// Convert from any `yaml_rust::Yaml` to a Node
///
/// ```
/// # use yaml_rust::YamlLoader;
/// # use marked_yaml::types::*;
/// # use std::convert::TryFrom;
/// let docs = YamlLoader::load_from_str("[1, 2]").unwrap();
/// let yaml = docs.into_iter().next().unwrap();
/// let node = Node::try_from(yaml).unwrap();
/// ```
fn try_from(value: YamlNode) -> Result<Self, Self::Error> {
match value {
YamlNode::Array(arr) => Ok(Node::Sequence(
arr.into_iter()
.map(Node::try_from)
.collect::<Result<MarkedSequenceNode, Self::Error>>()?,
)),
YamlNode::Hash(h) => Ok(Node::Mapping(
h.into_iter()
.map(|(k, v)| Ok((MarkedScalarNode::try_from(k)?, Node::try_from(v)?)))
.collect::<Result<MarkedMappingNode, Self::Error>>()?,
)),
scalar => Ok(Node::Scalar(MarkedScalarNode::try_from(scalar)?)),
}
}
}
impl From<MarkedScalarNode> for YamlNode {
fn from(value: MarkedScalarNode) -> Self {
YamlNode::String(value.value)
}
}
impl From<MarkedSequenceNode> for YamlNode {
fn from(value: MarkedSequenceNode) -> Self {
YamlNode::Array(value.value.into_iter().map(Into::into).collect())
}
}
impl From<MarkedMappingNode> for YamlNode {
fn from(value: MarkedMappingNode) -> Self {
YamlNode::Hash(
value
.value
.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.collect(),
)
}
}
impl From<Node> for YamlNode {
fn from(value: Node) -> Self {
match value {
Node::Scalar(msn) => msn.into(),
Node::Sequence(msn) => msn.into(),
Node::Mapping(mmn) => mmn.into(),
}
}
}
#[cfg(test)]
mod test {
use super::super::*;
use super::*;
#[test]
fn basic_marker_checks() {
let marker = Marker::new(0, 1, 2);
assert_eq!(marker.source(), 0);
assert_eq!(marker.line(), 1);
assert_eq!(marker.column(), 2);
assert_eq!(format!("{}", marker), "1:2");
let rendered = marker.render(|n| {
assert_eq!(n, 0);
"name"
});
assert_eq!(format!("{}", rendered), "name:1:2");
}
#[test]
fn basic_span_checks() {
let span = Span::new_blank();
assert_eq!(span.start(), None);
assert_eq!(span.end(), None);
let mark = Marker::new(0, 1, 2);
let mark2 = Marker::new(3, 4, 5);
let span = Span::new_start(mark);
assert_eq!(span.start(), Some(&mark));
assert_eq!(span.end(), None);
let span = Span::new_with_marks(mark, mark2);
assert_eq!(span.start(), Some(&mark));
assert_eq!(span.end(), Some(&mark2));
}
#[test]
fn basic_explore_load_test() {
let node = parse_yaml(0, include_str!("../examples/everything.yaml")).unwrap();
let map = node.as_mapping().unwrap();
assert_eq!(node.as_scalar(), None);
assert_eq!(node.as_sequence(), None);
assert_eq!(map.get_node("XXX NOT PRESENT XXX"), None);
assert_eq!(map.get_scalar("mapping"), None);
assert_eq!(map.get_sequence("mapping"), None);
assert_eq!(map.get_mapping("simple"), None);
// This actually uses .eq()
assert_ne!(map.get_scalar("boolean1"), map.get_scalar("boolean2"));
// Whereas this uses .ne()
assert!(map.get_scalar("boolean1") != map.get_scalar("boolean2"));
// Now check the spans
assert_eq!(node.span(), map.span());
let seq = map.get_sequence("heterogenous").unwrap();
assert_eq!(seq.span().start(), Some(&Marker::new(0, 24, 3)));
assert_eq!(seq.span(), map.get_node("heterogenous").unwrap().span());
// Helpers for the sequence node
assert_eq!(seq.get_node(0), seq.first());
assert_ne!(seq.get_node(0), None);
assert_ne!(seq.get_scalar(0), None);
assert_ne!(seq.get_mapping(1), None);
assert_ne!(seq.get_sequence(2), None);
}
#[test]
fn basic_scalar_features() {
let scalar1 = MarkedScalarNode::new(Span::new_blank(), "");
let scalar2 = MarkedScalarNode::new_empty(Span::new_blank());
assert_eq!(scalar1, scalar2);
assert_eq!(scalar1.as_str(), "");
assert_eq!(scalar1.as_bool(), None);
assert_eq!(scalar1.as_usize(), None);
let truth: MarkedScalarNode = "true".into();
assert_eq!(truth.as_bool(), Some(true));
let falsehood: MarkedScalarNode = "false".to_string().into();
assert_eq!(falsehood.as_bool(), Some(false));
assert_eq!(truth, true.into());
assert_eq!(falsehood, false.into());
let zero: MarkedScalarNode = "0".into();
assert_eq!(zero.as_usize(), Some(0));
assert_eq!(zero, 0usize.into());
assert_eq!(&*zero, "0");
}
#[test]
fn basic_sequence_features() {
// For features not covered by other tests
let mut seq = MarkedSequenceNode::new_empty(Span::new_blank());
let seq2: MarkedSequenceNode = vec!["foo"].into_iter().collect();
let scalar: MarkedScalarNode = "foo".into();
seq.push(Node::from(scalar));
assert_eq!(seq, seq2);
assert_eq!(seq, vec!["foo"].into());
let seq3: MarkedSequenceNode = vec!["foo", "bar"].into_iter().collect();
seq.push(Node::from("bar"));
assert_eq!(seq, seq3);
assert_eq!(seq, vec!["foo", "bar"].into());
}
#[test]
fn basic_mapping_features() {
// For features not covered by other tests
let mut map = MarkedMappingNode::new_empty(Span::new_blank());
let mut hash = MappingHash::new();
hash.insert("foo".into(), "bar".into());
let map2 = MarkedMappingNode::from(hash);
map.insert("foo".into(), "bar".into());
assert_eq!(map, map2);
assert_eq!(map.get("foo").unwrap().as_scalar().unwrap().as_str(), "bar");
let map3: MarkedMappingNode = vec![("foo", "bar")].into_iter().collect();
assert_eq!(map, map3);
map.insert("baz".into(), "meta".into());
let map4: MarkedMappingNode = vec![("foo", "bar"), ("baz", "meta")].into_iter().collect();
assert_eq!(map, map4);
}
#[test]
fn extra_node_impls() {
let node = Node::from(vec!["foo"]);
assert_ne!(node.as_sequence(), None);
let node = Node::from(MappingHash::new());
assert!(node.as_mapping().unwrap().is_empty());
}
#[test]
fn yaml_conversions() {
use yaml_rust::YamlLoader;
let mut everything =
YamlLoader::load_from_str(include_str!("../examples/everything.yaml")).unwrap();
let everything = everything.pop().unwrap();
let node = Node::try_from(everything.clone()).unwrap();
assert!(node.as_mapping().is_some());
let badscalar = MarkedScalarNode::try_from(everything);
assert_eq!(badscalar, Err(YamlConversionError::NonScalar));
let badscalar = MarkedScalarNode::try_from(YamlNode::BadValue);
assert_eq!(badscalar, Err(YamlConversionError::BadValue));
let badscalar = MarkedScalarNode::try_from(YamlNode::Array(vec![]));
assert_eq!(badscalar, Err(YamlConversionError::NonScalar));
}
fn flatten(node: YamlNode) -> YamlNode {
match node {
YamlNode::Array(arr) => YamlNode::Array(arr.into_iter().map(flatten).collect()),
YamlNode::Boolean(b) => {
YamlNode::String((if b { "true" } else { "false" }).to_string())
}
YamlNode::Hash(h) => YamlNode::Hash(
h.into_iter()
.map(|(k, v)| (flatten(k), flatten(v)))
.collect(),
),
YamlNode::Integer(i) => YamlNode::String(format!("{}", i)),
YamlNode::Null => YamlNode::String("null".to_string()),
YamlNode::Real(r) => YamlNode::String(r),
other => other,
}
}
#[test]
fn back_yaml_conversion() {
use yaml_rust::YamlLoader;
let mut everything =
YamlLoader::load_from_str(include_str!("../examples/everything.yaml")).unwrap();
let everything = everything.pop().unwrap();
let node = Node::try_from(everything.clone()).unwrap();
let other: YamlNode = node.into();
let flat = flatten(everything);
assert_eq!(flat, other);
}
}