#![deny(missing_docs)]
#![allow(clippy::type_complexity)]
#![warn(clippy::unnecessary_to_owned)]
#![warn(clippy::redundant_clone)]
#![warn(clippy::inefficient_to_string)]
#![warn(clippy::manual_string_new)]
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
pub mod anchor_resolution;
#[doc(inline)]
pub use anchor_resolution::{
AnchorRegistry, DocumentMergedExt, DocumentResolvedExt, MappingMergedExt, MergedMapping,
MergedView, ResolveError,
};
pub mod mapping_view;
#[doc(inline)]
pub use mapping_view::MappingView;
mod as_yaml;
mod builder;
mod column;
pub mod custom_tags;
pub mod debug;
mod error;
pub mod error_recovery;
mod lex;
mod line_index;
pub mod migrate;
mod nodes;
mod parse;
mod parser;
pub mod path;
mod scalar;
mod schema;
mod tag;
pub mod validator;
mod value;
pub mod visitor;
mod yaml;
pub use as_yaml::{yaml_eq, yaml_hash, AsYaml, YamlKind, YamlNode};
pub use builder::{MappingBuilder, SequenceBuilder, YamlBuilder};
pub use error::{YamlError, YamlResult};
pub use lex::{
lex, lex_with_validation, lex_with_validation_config, SyntaxKind, ValidationConfig,
WhitespaceError, WhitespaceErrorCategory,
};
pub use parse::Parse;
pub use scalar::{CoreScalarType, ScalarStyle, ScalarType, ScalarValue};
pub use schema::{
CustomSchema, CustomValidationResult, Schema, SchemaValidator, ValidationError,
ValidationErrorKind, ValidationResult,
};
pub use value::YamlValue;
pub use yaml::{
Alias, Comment, CommentTextError, Directive, Document, Entry, Lang, Mapping, MappingEntry,
OccupiedEntry, Scalar, ScalarConversionError, Sequence, Set, TaggedNode, VacantEntry, YamlFile,
};
pub mod advanced {
pub use rowan::TextRange;
use crate::yaml::SyntaxNode;
use crate::TextPosition;
pub fn syntax_node_range(node: &SyntaxNode) -> TextRange {
node.text_range()
}
pub fn text_position_to_range(pos: TextPosition) -> TextRange {
pos.into()
}
pub fn text_range_to_position(range: TextRange) -> TextPosition {
range.into()
}
}
pub use custom_tags::{
CompressedBinaryHandler,
CustomTagError,
CustomTagHandler,
CustomTagParser,
CustomTagRegistry,
EnvVarHandler,
JsonHandler,
TimestampHandler,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TextPosition {
pub start: u32,
pub end: u32,
}
impl TextPosition {
pub fn new(start: u32, end: u32) -> Self {
Self { start, end }
}
pub fn len(&self) -> u32 {
self.end.checked_sub(self.start).unwrap_or_else(|| {
panic!(
"TextPosition end {} is before its start {}",
self.end, self.start
)
})
}
pub fn is_empty(&self) -> bool {
self.start == self.end
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct LineColumn {
pub line: usize,
pub column: usize,
}
impl LineColumn {
pub fn new(line: usize, column: usize) -> Self {
Self { line, column }
}
}
impl std::fmt::Display for LineColumn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.line, self.column)
}
}
pub fn byte_offset_to_line_column(text: &str, byte_offset: usize) -> LineColumn {
line_index::LineIndex::new(text).line_column(byte_offset)
}
impl From<rowan::TextRange> for TextPosition {
fn from(range: rowan::TextRange) -> Self {
Self {
start: u32::from(range.start()),
end: u32::from(range.end()),
}
}
}
impl From<TextPosition> for rowan::TextRange {
fn from(pos: TextPosition) -> Self {
rowan::TextRange::new(pos.start.into(), pos.end.into())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ParseErrorKind {
UnclosedFlowSequence,
UnclosedFlowMapping,
UnterminatedString,
Other,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PositionedParseError {
pub message: String,
pub range: TextPosition,
pub code: Option<String>,
pub kind: ParseErrorKind,
}
impl PositionedParseError {
pub fn start_position(&self, source_text: &str) -> LineColumn {
byte_offset_to_line_column(source_text, self.range.start as usize)
}
pub fn end_position(&self, source_text: &str) -> LineColumn {
byte_offset_to_line_column(source_text, self.range.end as usize)
}
}
impl std::fmt::Display for PositionedParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for PositionedParseError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Indentation {
FieldNameLength,
Spaces(u32),
}
impl Default for Indentation {
fn default() -> Self {
Indentation::Spaces(2)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
fn test_byte_offset_to_line_column_basic() {
let text = "line 1\nline 2\nline 3";
let pos = byte_offset_to_line_column(text, 0);
assert_eq!(pos.line, 1);
assert_eq!(pos.column, 1);
let pos = byte_offset_to_line_column(text, 7);
assert_eq!(pos.line, 2);
assert_eq!(pos.column, 1);
let pos = byte_offset_to_line_column(text, 10);
assert_eq!(pos.line, 2);
assert_eq!(pos.column, 4);
let pos = byte_offset_to_line_column(text, 14);
assert_eq!(pos.line, 3);
assert_eq!(pos.column, 1);
}
#[test]
fn test_byte_offset_to_line_column_unicode() {
let text = "hello\n世界\nworld";
let pos = byte_offset_to_line_column(text, 0);
assert_eq!(pos.line, 1);
assert_eq!(pos.column, 1);
let pos = byte_offset_to_line_column(text, 6);
assert_eq!(pos.line, 2);
assert_eq!(pos.column, 1);
let pos = byte_offset_to_line_column(text, 9);
assert_eq!(pos.line, 2);
assert_eq!(pos.column, 2);
}
#[test]
fn test_byte_offset_to_line_column_cr_line_endings() {
let text = "a: 1\rb: 2\r\nc: 3";
assert_eq!(byte_offset_to_line_column(text, 5), LineColumn::new(2, 1));
assert_eq!(byte_offset_to_line_column(text, 10), LineColumn::new(2, 6));
assert_eq!(byte_offset_to_line_column(text, 11), LineColumn::new(3, 1));
}
#[test]
fn test_byte_offset_to_line_column_bom() {
let text = "\u{FEFF}a: 1\n\u{FEFF}b: 2";
assert_eq!(byte_offset_to_line_column(text, 3), LineColumn::new(1, 1));
assert_eq!(byte_offset_to_line_column(text, 4), LineColumn::new(1, 2));
assert_eq!(byte_offset_to_line_column(text, 8), LineColumn::new(2, 1));
assert_eq!(byte_offset_to_line_column(text, 11), LineColumn::new(2, 1));
assert_eq!(byte_offset_to_line_column(text, 12), LineColumn::new(2, 2));
assert_eq!(
byte_offset_to_line_column("a\u{FEFF}b", 4),
LineColumn::new(1, 3)
);
}
#[test]
fn test_line_column_display() {
let pos = LineColumn::new(42, 17);
assert_eq!(format!("{}", pos), "42:17");
let pos2 = LineColumn::new(1, 1);
assert_eq!(format!("{}", pos2), "1:1");
}
#[test]
fn test_text_position() {
let pos = TextPosition::new(3, 7);
assert_eq!(pos.len(), 4);
assert!(!pos.is_empty());
assert!(TextPosition::new(5, 5).is_empty());
let range = advanced::text_position_to_range(pos);
assert_eq!(range, rowan::TextRange::new(3.into(), 7.into()));
assert_eq!(advanced::text_range_to_position(range), pos);
let doc = Document::from_str("a: 1\nb: 2\n").unwrap();
let value = doc.as_mapping().unwrap().get("b").unwrap();
assert_eq!(
advanced::syntax_node_range(value.syntax()),
rowan::TextRange::new(8.into(), 9.into())
);
}
#[test]
fn test_indentation_default() {
assert_eq!(Indentation::default(), Indentation::Spaces(2));
}
#[test]
fn test_text_position_len() {
assert_eq!(TextPosition::new(3, 7).len(), 4);
assert_eq!(TextPosition::new(3, 3).len(), 0);
}
#[test]
#[should_panic(expected = "TextPosition end 2 is before its start 5")]
fn test_text_position_len_end_before_start() {
TextPosition::new(5, 2).len();
}
#[test]
fn test_empty_lines_positions() {
let text = "a: 1\n\n\nb: 2";
let pos1 = byte_offset_to_line_column(text, 0);
assert_eq!(pos1.line, 1);
let pos2 = byte_offset_to_line_column(text, 7);
assert_eq!(pos2.line, 4);
}
}