Making progress in the config parsing
This commit is contained in:
parent
e26a04de67
commit
2283cedbd3
75
Cargo.lock
generated
75
Cargo.lock
generated
|
@ -210,7 +210,7 @@ checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595"
|
|||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote 1.0.8",
|
||||
"quote 1.0.9",
|
||||
"syn 1.0.60",
|
||||
]
|
||||
|
||||
|
@ -235,6 +235,7 @@ dependencies = [
|
|||
"log",
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
"tempdir",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
|
@ -300,6 +301,12 @@ dependencies = [
|
|||
"widestring",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.16"
|
||||
|
@ -522,13 +529,50 @@ checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
|
|||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.8"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
|
||||
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
|
||||
dependencies = [
|
||||
"fuchsia-cprng",
|
||||
"libc",
|
||||
"rand_core 0.3.1",
|
||||
"rdrand",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
|
||||
dependencies = [
|
||||
"rand_core 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
||||
|
||||
[[package]]
|
||||
name = "rdrand"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
||||
dependencies = [
|
||||
"rand_core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.57"
|
||||
|
@ -564,6 +608,15 @@ version = "0.6.22"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-argon2"
|
||||
version = "0.8.3"
|
||||
|
@ -604,7 +657,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.8",
|
||||
"quote 1.0.9",
|
||||
"syn 1.0.60",
|
||||
]
|
||||
|
||||
|
@ -676,7 +729,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.8",
|
||||
"quote 1.0.9",
|
||||
"unicode-xid 0.2.1",
|
||||
]
|
||||
|
||||
|
@ -689,6 +742,16 @@ dependencies = [
|
|||
"unicode-xid 0.0.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempdir"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
|
||||
dependencies = [
|
||||
"rand",
|
||||
"remove_dir_all",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.2"
|
||||
|
@ -714,7 +777,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.8",
|
||||
"quote 1.0.9",
|
||||
"syn 1.0.60",
|
||||
]
|
||||
|
||||
|
|
|
@ -11,3 +11,6 @@ thiserror = "1.0.23"
|
|||
serde = { version = "1.0.123", features = ["derive"] }
|
||||
serde_yaml = "0.8.17"
|
||||
glob = "0.3.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tempdir = "0.3.7"
|
20
espanso-config/src/config/macro_util.rs
Normal file
20
espanso-config/src/config/macro_util.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
#[macro_export]
|
||||
macro_rules! merge {
|
||||
( $t:ident, $child:expr, $parent:expr, $( $x:ident ),* ) => {
|
||||
{
|
||||
$(
|
||||
if $child.$x.is_none() {
|
||||
$child.$x = $parent.$x.clone();
|
||||
}
|
||||
)*
|
||||
|
||||
// Build a temporary object to verify that all fields
|
||||
// are being used at compile time
|
||||
$t {
|
||||
$(
|
||||
$x: None,
|
||||
)*
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
|
@ -4,6 +4,7 @@ use anyhow::Result;
|
|||
|
||||
mod yaml;
|
||||
mod path;
|
||||
mod macro_util;
|
||||
|
||||
pub struct Config {
|
||||
pub label: Option<String>,
|
||||
|
|
|
@ -3,7 +3,6 @@ use std::collections::HashSet;
|
|||
use glob::glob;
|
||||
use log::error;
|
||||
|
||||
// TODO: test
|
||||
pub fn calculate_paths<'a>(glob_patterns: impl Iterator<Item = &'a String>) -> HashSet<String> {
|
||||
let mut path_set = HashSet::new();
|
||||
for glob_pattern in glob_patterns {
|
||||
|
@ -15,9 +14,10 @@ pub fn calculate_paths<'a>(glob_patterns: impl Iterator<Item = &'a String>) -> H
|
|||
Ok(path) => {
|
||||
path_set.insert(path.to_string_lossy().to_string());
|
||||
}
|
||||
Err(err) => {
|
||||
error!("glob error when processing pattern: {}, with error: {}", glob_pattern, err)
|
||||
}
|
||||
Err(err) => error!(
|
||||
"glob error when processing pattern: {}, with error: {}",
|
||||
glob_pattern, err
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,3 +32,41 @@ pub fn calculate_paths<'a>(glob_patterns: impl Iterator<Item = &'a String>) -> H
|
|||
|
||||
path_set
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fs::create_dir_all;
|
||||
|
||||
use super::*;
|
||||
use tempdir::TempDir;
|
||||
|
||||
#[test]
|
||||
fn calculate_paths_works_correctly() {
|
||||
let dir = TempDir::new("tempconfig").unwrap();
|
||||
let match_dir = dir.path().join("match");
|
||||
create_dir_all(&match_dir).unwrap();
|
||||
|
||||
let sub_dir = match_dir.join("sub");
|
||||
create_dir_all(&sub_dir).unwrap();
|
||||
|
||||
std::fs::write(match_dir.join("base.yml"), "test").unwrap();
|
||||
std::fs::write(match_dir.join("another.yml"), "test").unwrap();
|
||||
std::fs::write(match_dir.join("_sub.yml"), "test").unwrap();
|
||||
std::fs::write(sub_dir.join("sub.yml"), "test").unwrap();
|
||||
|
||||
let result = calculate_paths(vec![
|
||||
format!("{}/**/*.yml", dir.path().to_string_lossy()),
|
||||
format!("{}/match/sub/*.yml", dir.path().to_string_lossy()),
|
||||
// Invalid path
|
||||
"invalid".to_string(),
|
||||
].iter());
|
||||
|
||||
let mut expected = HashSet::new();
|
||||
expected.insert(format!("{}/match/base.yml", dir.path().to_string_lossy()));
|
||||
expected.insert(format!("{}/match/another.yml", dir.path().to_string_lossy()));
|
||||
expected.insert(format!("{}/match/_sub.yml", dir.path().to_string_lossy()));
|
||||
expected.insert(format!("{}/match/sub/sub.yml", dir.path().to_string_lossy()));
|
||||
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,67 +1,125 @@
|
|||
use std::collections::HashSet;
|
||||
use anyhow::{private::kind::TraitKind, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::iter::FromIterator;
|
||||
use std::{collections::HashSet, convert::TryFrom};
|
||||
|
||||
use crate::{merge, util::is_yaml_empty};
|
||||
|
||||
use super::path::calculate_paths;
|
||||
|
||||
const STANDARD_INCLUDES: &[&str] = &["match/**/*.yml"];
|
||||
const STANDARD_EXCLUDES: &[&str] = &["match/**/_*.yml"];
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct YAMLConfig {
|
||||
#[serde(default)]
|
||||
pub label: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub includes: Option<Vec<String>>,
|
||||
|
||||
#[serde(default)]
|
||||
pub excludes: Option<Vec<String>>,
|
||||
|
||||
#[serde(default)]
|
||||
pub extra_includes: Option<Vec<String>>,
|
||||
|
||||
#[serde(default)]
|
||||
pub extra_excludes: Option<Vec<String>>,
|
||||
|
||||
pub use_standard_includes: bool,
|
||||
#[serde(default)]
|
||||
pub use_standard_includes: Option<bool>,
|
||||
|
||||
// Filters
|
||||
|
||||
#[serde(default)]
|
||||
pub filter_title: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub filter_class: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub filter_exec: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub filter_os: Option<String>,
|
||||
}
|
||||
|
||||
impl YAMLConfig {
|
||||
// TODO test
|
||||
pub fn parse_from_str(yaml: &str) -> Result<Self> {
|
||||
// Because an empty string is not valid YAML but we want to support it anyway
|
||||
if is_yaml_empty(yaml) {
|
||||
return Ok(serde_yaml::from_str(
|
||||
"arbitrary_field_that_will_not_block_the_parser: true",
|
||||
)?);
|
||||
}
|
||||
|
||||
Ok(serde_yaml::from_str(yaml)?)
|
||||
}
|
||||
|
||||
pub fn merge_parent(&mut self, parent: &YAMLConfig) {
|
||||
// Override the None fields with the parent's value
|
||||
merge!(
|
||||
YAMLConfig,
|
||||
self,
|
||||
parent,
|
||||
|
||||
// Fields
|
||||
label,
|
||||
includes,
|
||||
excludes,
|
||||
extra_includes,
|
||||
extra_excludes,
|
||||
use_standard_includes,
|
||||
filter_title,
|
||||
filter_class,
|
||||
filter_exec,
|
||||
filter_os
|
||||
);
|
||||
}
|
||||
|
||||
pub fn aggregate_includes(&self) -> HashSet<String> {
|
||||
let mut includes = HashSet::new();
|
||||
|
||||
if self.use_standard_includes {
|
||||
STANDARD_INCLUDES.iter().for_each(|include| { includes.insert(include.to_string()); })
|
||||
if self.use_standard_includes.is_none() || self.use_standard_includes.unwrap() {
|
||||
STANDARD_INCLUDES.iter().for_each(|include| {
|
||||
includes.insert(include.to_string());
|
||||
})
|
||||
}
|
||||
|
||||
if let Some(yaml_includes) = self.includes.as_ref() {
|
||||
yaml_includes.iter().for_each(|include| { includes.insert(include.to_string()); })
|
||||
yaml_includes.iter().for_each(|include| {
|
||||
includes.insert(include.to_string());
|
||||
})
|
||||
}
|
||||
|
||||
if let Some(extra_includes) = self.extra_includes.as_ref() {
|
||||
extra_includes.iter().for_each(|include| { includes.insert(include.to_string()); })
|
||||
extra_includes.iter().for_each(|include| {
|
||||
includes.insert(include.to_string());
|
||||
})
|
||||
}
|
||||
|
||||
includes
|
||||
}
|
||||
|
||||
// TODO test
|
||||
pub fn aggregate_excludes(&self) -> HashSet<String> {
|
||||
let mut excludes = HashSet::new();
|
||||
|
||||
if self.use_standard_includes {
|
||||
STANDARD_EXCLUDES.iter().for_each(|exclude| { excludes.insert(exclude.to_string()); })
|
||||
if self.use_standard_includes.is_none() || self.use_standard_includes.unwrap() {
|
||||
STANDARD_EXCLUDES.iter().for_each(|exclude| {
|
||||
excludes.insert(exclude.to_string());
|
||||
})
|
||||
}
|
||||
|
||||
if let Some(yaml_excludes) = self.excludes.as_ref() {
|
||||
yaml_excludes.iter().for_each(|exclude| { excludes.insert(exclude.to_string()); })
|
||||
yaml_excludes.iter().for_each(|exclude| {
|
||||
excludes.insert(exclude.to_string());
|
||||
})
|
||||
}
|
||||
|
||||
if let Some(extra_excludes) = self.extra_excludes.as_ref() {
|
||||
extra_excludes.iter().for_each(|exclude| { excludes.insert(exclude.to_string()); })
|
||||
extra_excludes.iter().for_each(|exclude| {
|
||||
excludes.insert(exclude.to_string());
|
||||
})
|
||||
}
|
||||
|
||||
excludes
|
||||
|
@ -69,9 +127,11 @@ impl YAMLConfig {
|
|||
}
|
||||
|
||||
// TODO: convert to TryFrom (check the matches section for an example)
|
||||
impl From<YAMLConfig> for super::Config {
|
||||
impl TryFrom<YAMLConfig> for super::Config {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// TODO: test
|
||||
fn from(yaml_config: YAMLConfig) -> Self {
|
||||
fn try_from(yaml_config: YAMLConfig) -> Result<Self, Self::Error> {
|
||||
let includes = yaml_config.aggregate_includes();
|
||||
let excludes = yaml_config.aggregate_excludes();
|
||||
|
||||
|
@ -79,11 +139,169 @@ impl From<YAMLConfig> for super::Config {
|
|||
let exclude_paths = calculate_paths(excludes.iter());
|
||||
let include_paths = calculate_paths(includes.iter());
|
||||
|
||||
let match_files: Vec<String> = Vec::from_iter(include_paths.difference(&exclude_paths).cloned());
|
||||
let match_files: Vec<String> =
|
||||
Vec::from_iter(include_paths.difference(&exclude_paths).cloned());
|
||||
|
||||
Self {
|
||||
Ok(Self {
|
||||
label: yaml_config.label,
|
||||
match_files,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::config::Config;
|
||||
use std::iter::FromIterator;
|
||||
use std::{convert::TryInto, fs::create_dir_all};
|
||||
use tempdir::TempDir;
|
||||
|
||||
// fn create_config(yaml: &str) -> Result<Config> {
|
||||
// let yaml_config: YAMLConfig = serde_yaml::from_str(yaml)?;
|
||||
// let m: Config = yaml_config.try_into()?;
|
||||
// Ok(m)
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn aggregate_includes_empty_config() {
|
||||
assert_eq!(
|
||||
YAMLConfig::parse_from_str("").unwrap().aggregate_includes(),
|
||||
HashSet::from_iter(vec!["match/**/*.yml".to_string(),].iter().cloned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aggregate_includes_no_standard() {
|
||||
assert_eq!(
|
||||
YAMLConfig::parse_from_str("use_standard_includes: false").unwrap().aggregate_includes(),
|
||||
HashSet::new()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aggregate_includes_custom_includes() {
|
||||
assert_eq!(
|
||||
YAMLConfig::parse_from_str("includes: ['custom/*.yml']")
|
||||
.unwrap()
|
||||
.aggregate_includes(),
|
||||
HashSet::from_iter(
|
||||
vec!["match/**/*.yml".to_string(), "custom/*.yml".to_string()]
|
||||
.iter()
|
||||
.cloned()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aggregate_includes_extra_includes() {
|
||||
assert_eq!(
|
||||
YAMLConfig::parse_from_str("extra_includes: ['custom/*.yml']")
|
||||
.unwrap()
|
||||
.aggregate_includes(),
|
||||
HashSet::from_iter(
|
||||
vec!["match/**/*.yml".to_string(), "custom/*.yml".to_string()]
|
||||
.iter()
|
||||
.cloned()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aggregate_includes_includes_and_extra_includes() {
|
||||
assert_eq!(
|
||||
YAMLConfig::parse_from_str("includes: ['sub/*.yml']\nextra_includes: ['custom/*.yml']")
|
||||
.unwrap()
|
||||
.aggregate_includes(),
|
||||
HashSet::from_iter(
|
||||
vec!["match/**/*.yml".to_string(), "custom/*.yml".to_string(), "sub/*.yml".to_string()]
|
||||
.iter()
|
||||
.cloned()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aggregate_excludes_empty_config() {
|
||||
assert_eq!(
|
||||
YAMLConfig::parse_from_str("").unwrap().aggregate_excludes(),
|
||||
HashSet::from_iter(vec!["match/**/_*.yml".to_string(),].iter().cloned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aggregate_excludes_no_standard() {
|
||||
assert_eq!(
|
||||
YAMLConfig::parse_from_str("use_standard_includes: false").unwrap().aggregate_excludes(),
|
||||
HashSet::new()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aggregate_excludes_custom_excludes() {
|
||||
assert_eq!(
|
||||
YAMLConfig::parse_from_str("excludes: ['custom/*.yml']")
|
||||
.unwrap()
|
||||
.aggregate_excludes(),
|
||||
HashSet::from_iter(
|
||||
vec!["match/**/_*.yml".to_string(), "custom/*.yml".to_string()]
|
||||
.iter()
|
||||
.cloned()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aggregate_excludes_extra_excludes() {
|
||||
assert_eq!(
|
||||
YAMLConfig::parse_from_str("extra_excludes: ['custom/*.yml']")
|
||||
.unwrap()
|
||||
.aggregate_excludes(),
|
||||
HashSet::from_iter(
|
||||
vec!["match/**/_*.yml".to_string(), "custom/*.yml".to_string()]
|
||||
.iter()
|
||||
.cloned()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aggregate_excludes_excludes_and_extra_excludes() {
|
||||
assert_eq!(
|
||||
YAMLConfig::parse_from_str("excludes: ['sub/*.yml']\nextra_excludes: ['custom/*.yml']")
|
||||
.unwrap()
|
||||
.aggregate_excludes(),
|
||||
HashSet::from_iter(
|
||||
vec!["match/**/_*.yml".to_string(), "custom/*.yml".to_string(), "sub/*.yml".to_string()]
|
||||
.iter()
|
||||
.cloned()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_parent_field_parent_fallback() {
|
||||
let parent =
|
||||
YAMLConfig::parse_from_str("use_standard_includes: false").unwrap();
|
||||
let mut child =
|
||||
YAMLConfig::parse_from_str("").unwrap();
|
||||
assert_eq!(child.use_standard_includes, None);
|
||||
|
||||
child.merge_parent(&parent);
|
||||
assert_eq!(child.use_standard_includes, Some(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_parent_field_child_overwrite_parent() {
|
||||
let parent =
|
||||
YAMLConfig::parse_from_str("use_standard_includes: true").unwrap();
|
||||
let mut child =
|
||||
YAMLConfig::parse_from_str("use_standard_includes: false").unwrap();
|
||||
assert_eq!(child.use_standard_includes, Some(false));
|
||||
|
||||
child.merge_parent(&parent);
|
||||
assert_eq!(child.use_standard_includes, Some(false));
|
||||
}
|
||||
|
||||
// TODO: test conversion to Config (we need to test that the file match resolution works)
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
mod util;
|
||||
mod config;
|
||||
mod matches;
|
||||
|
||||
|
|
38
espanso-config/src/util.rs
Normal file
38
espanso-config/src/util.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
/// Check if the given string represents an empty YAML.
|
||||
/// In other words, it checks if the document is only composed
|
||||
/// of spaces and/or comments
|
||||
pub fn is_yaml_empty(yaml: &str) -> bool {
|
||||
for line in yaml.lines() {
|
||||
let trimmed_line = line.trim();
|
||||
if !trimmed_line.starts_with("#") && !trimmed_line.is_empty() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn is_yaml_empty_document_empty() {
|
||||
assert_eq!(is_yaml_empty(""), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_yaml_empty_document_with_comments() {
|
||||
assert_eq!(is_yaml_empty("\n#comment \n \n"), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_yaml_empty_document_with_comments_and_content() {
|
||||
assert_eq!(is_yaml_empty("\n#comment \n field: true\n"), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_yaml_empty_document_with_content() {
|
||||
assert_eq!(is_yaml_empty("\nfield: true\n"), false);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user