diff --git a/Cargo.toml b/Cargo.toml index f267735..017dad5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yaml-include" -version = "0.7.1" +version = "0.8.0" edition = "2021" authors = ["Merleur l'enchantin "] license = "GPL-3.0" diff --git a/README.md b/README.md index 1f12c9d..7caa855 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ cargo install yaml-include - include and parse recursively `yaml` (and `json`) files - include `markdown` and `txt` text files - include other types as `base64` encoded binary data. +- hint or force extension with `!include { "path": "", "extension": "txt"}` - by default handle gracefully circular references with `!circular` tag ## Usage @@ -57,6 +58,7 @@ turns this: data: - !include file_a.yml - !include file_b.yml + - !include { "path": "file_a.yml", "extension": "txt"} ``` `file_a.yml`: diff --git a/data/expected.yml b/data/expected.yml index 8e1a19f..267168a 100644 --- a/data/expected.yml +++ b/data/expected.yml @@ -8,6 +8,10 @@ Nested: - people - need to be recursive: yes whatever: CECI est un texte + - | + Bye: + - people + - !include c.yml - Bye: - people - need to be recursive: yes diff --git a/data/sample/file_a.yml b/data/sample/file_a.yml index 1e06cc2..c3338ad 100644 --- a/data/sample/file_a.yml +++ b/data/sample/file_a.yml @@ -1,3 +1,3 @@ something: - - this - - that + - this + - that diff --git a/data/sample/file_b.yml b/data/sample/file_b.yml index 2e6dacc..a77b432 100644 --- a/data/sample/file_b.yml +++ b/data/sample/file_b.yml @@ -1,3 +1,3 @@ other: - - text: !include file_c.txt - - markdown: !include file_d.md + - text: !include file_c.txt + - markdown: !include file_d.md diff --git a/data/sample/file_c.txt b/data/sample/file_c.txt index a6bc567..c90d8f8 100644 --- a/data/sample/file_c.txt +++ b/data/sample/file_c.txt @@ -1,3 +1,3 @@ This is some long multiline text i don't want to edit -inline in a long yaml file \ No newline at end of file +inline in a long yaml file diff --git a/data/sample/file_e.json b/data/sample/file_e.json index 964ff9d..5b62812 100644 --- a/data/sample/file_e.json +++ b/data/sample/file_e.json @@ -7,4 +7,4 @@ 3, 4 ] -} \ No newline at end of file +} diff --git a/data/sample/main.yml b/data/sample/main.yml index 776eec7..bb850e1 100644 --- a/data/sample/main.yml +++ b/data/sample/main.yml @@ -1,4 +1,4 @@ yaml: - - !include file_a.yml - - !include file_b.yml - - !include file_e.json \ No newline at end of file + - !include file_a.yml + - !include file_b.yml + - !include file_e.json diff --git a/data/simple/nested/a.yml b/data/simple/nested/a.yml index 6180000..fdbaf5f 100644 --- a/data/simple/nested/a.yml +++ b/data/simple/nested/a.yml @@ -3,3 +3,4 @@ World: - Tralala - 42 - !include b.yml + - !include { "path": "b.yml", "extension": "txt" } diff --git a/data/simple/other.json b/data/simple/other.json index 9d7118f..8cc4788 100644 --- a/data/simple/other.json +++ b/data/simple/other.json @@ -12,4 +12,4 @@ "this": "or that", "data": !include ../root.yml } -} \ No newline at end of file +} diff --git a/src/transformer.rs b/src/transformer.rs index 0f157b8..d5046aa 100644 --- a/src/transformer.rs +++ b/src/transformer.rs @@ -9,10 +9,79 @@ use std::{ fmt, fs::{canonicalize, read_to_string}, path::PathBuf, + str::FromStr, }; use crate::helpers::{load_as_base64, load_yaml}; +struct FilePath { + path: PathBuf, + extension: Extension, +} + +enum Extension { + Yaml, + Text, + Binary, +} + +#[derive(Debug)] +enum ParseError { + MissingPath, + MissingExtension, +} + +impl FromStr for Extension { + type Err = (); + + fn from_str(s: &str) -> std::result::Result { + match s { + "yaml" | "yml" | "json" => Ok(Self::Yaml), + "md" | "markdown" | "txt" => Ok(Self::Text), + _ => Ok(Self::Binary), + } + } +} + +impl TryFrom for FilePath { + type Error = ParseError; + + fn try_from(value: Mapping) -> Result { + let path = value + .get("path") + .and_then(|value| value.as_str()) + .ok_or(ParseError::MissingPath)? + .into(); + + let extension = Extension::from_str( + value + .get("extension") + .and_then(|value| value.as_str()) + .ok_or(ParseError::MissingExtension)?, + ) + .expect("Infaillible conversion"); + + Ok(Self { path, extension }) + } +} + +impl TryFrom for FilePath { + type Error = ParseError; + + fn try_from(value: String) -> Result { + let path: PathBuf = value.into(); + + let extension = Extension::from_str( + path.extension() + .and_then(|ext| ext.to_str()) + .ok_or(ParseError::MissingExtension)?, + ) + .expect("Infaillible conversion"); + + Ok(Self { path, extension }) + } +} + /// Processing yaml with include documents through `!include ` tag. /// /// ## Features @@ -116,8 +185,11 @@ impl Transformer { )), Value::Tagged(tagged_value) => match tagged_value.tag.to_string().as_str() { "!include" => { - let value = tagged_value.value.as_str().unwrap(); - let file_path = PathBuf::from(value); + let file_path: FilePath = match tagged_value.value { + Value::String(path) => path.try_into().unwrap(), + Value::Mapping(mapping) => mapping.try_into().unwrap(), + _ => panic!("Unsupported Value"), + }; self.handle_include_extension(file_path) } @@ -128,60 +200,54 @@ impl Transformer { } } - fn handle_include_extension(&self, file_path: PathBuf) -> Value { - let normalized_file_path = self.process_path(&file_path); - - let result = match normalized_file_path.extension() { - Some(os_str) => match os_str.to_str() { - Some("yaml") | Some("yml") | Some("json") => { - match Transformer::new_node( - normalized_file_path, - self.error_on_circular, - Some(self.seen_paths.clone()), - ) { - Ok(transformer) => transformer.parse(), - Err(e) => { - if self.error_on_circular { - // TODO: probably something better to do than panic ? - panic!("{:?}", e); - } + fn handle_include_extension(&self, file_path: FilePath) -> Value { + let normalized_file_path = self.process_path(&file_path.path); - return Value::Tagged( - TaggedValue { - tag: Tag::new("circular"), - value: Value::String(file_path.display().to_string()), - } - .into(), - ); + let result = match file_path.extension { + Extension::Yaml => { + match Transformer::new_node( + normalized_file_path, + self.error_on_circular, + Some(self.seen_paths.clone()), + ) { + Ok(transformer) => transformer.parse(), + Err(e) => { + if self.error_on_circular { + panic!("{:?}", e); } + + return Value::Tagged( + TaggedValue { + tag: Tag::new("circular"), + value: Value::String(file_path.path.display().to_string()), + } + .into(), + ); } } - // inlining markdow and text files - Some("txt") | Some("markdown") | Some("md") => { - Value::String(read_to_string(normalized_file_path).unwrap()) - } - // inlining other include as binary files - None | Some(&_) => Value::Tagged(Box::new(TaggedValue { - tag: Tag::new("binary"), - value: Value::Mapping(Mapping::from_iter([ - ( - Value::String("filename".into()), - Value::String( - normalized_file_path - .file_name() - .unwrap() - .to_string_lossy() - .to_string(), - ), - ), - ( - Value::String("base64".into()), - Value::String(load_as_base64(&normalized_file_path).unwrap()), + } + // inlining markdow and text files + Extension::Text => Value::String(read_to_string(normalized_file_path).unwrap()), + // inlining other include as binary files + Extension::Binary => Value::Tagged(Box::new(TaggedValue { + tag: Tag::new("binary"), + value: Value::Mapping(Mapping::from_iter([ + ( + Value::String("filename".into()), + Value::String( + normalized_file_path + .file_name() + .unwrap() + .to_string_lossy() + .to_string(), ), - ])), - })), - }, - _ => panic!("{:?} path missing file extension", normalized_file_path), + ), + ( + Value::String("base64".into()), + Value::String(load_as_base64(&normalized_file_path).unwrap()), + ), + ])), + })), }; result