yerba 0.3.0-aarch64-linux-gnu → 0.4.1-aarch64-linux-gnu
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +93 -8
- data/exe/aarch64-linux-gnu/yerba +0 -0
- data/ext/yerba/extconf.rb +22 -3
- data/ext/yerba/include/yerba.h +32 -9
- data/ext/yerba/yerba.c +133 -25
- data/lib/yerba/3.2/yerba.so +0 -0
- data/lib/yerba/3.3/yerba.so +0 -0
- data/lib/yerba/3.4/yerba.so +0 -0
- data/lib/yerba/4.0/yerba.so +0 -0
- data/lib/yerba/collection.rb +35 -0
- data/lib/yerba/document.rb +45 -3
- data/lib/yerba/query_result.rb +50 -0
- data/lib/yerba/sequence.rb +187 -1
- data/lib/yerba/version.rb +1 -1
- data/lib/yerba/yerbafile.rb +45 -0
- data/lib/yerba.rb +2 -0
- data/rust/Cargo.lock +3 -1
- data/rust/Cargo.toml +3 -2
- data/rust/cbindgen.toml +1 -0
- data/rust/rustfmt.toml +1 -1
- data/rust/src/commands/apply.rs +11 -2
- data/rust/src/commands/blank_lines.rs +1 -4
- data/rust/src/commands/check.rs +11 -2
- data/rust/src/commands/directives.rs +61 -0
- data/rust/src/commands/get.rs +5 -22
- data/rust/src/commands/mod.rs +16 -18
- data/rust/src/commands/quote_style.rs +12 -6
- data/rust/src/commands/selectors.rs +3 -19
- data/rust/src/commands/sort.rs +22 -157
- data/rust/src/didyoumean.rs +2 -4
- data/rust/src/document/condition.rs +139 -0
- data/rust/src/document/delete.rs +91 -0
- data/rust/src/document/get.rs +262 -0
- data/rust/src/document/insert.rs +384 -0
- data/rust/src/document/mod.rs +784 -0
- data/rust/src/document/set.rs +100 -0
- data/rust/src/document/sort.rs +639 -0
- data/rust/src/document/style.rs +473 -0
- data/rust/src/error.rs +24 -6
- data/rust/src/ffi.rs +272 -518
- data/rust/src/json.rs +1 -7
- data/rust/src/lib.rs +88 -2
- data/rust/src/main.rs +2 -0
- data/rust/src/quote_style.rs +83 -7
- data/rust/src/selector.rs +2 -7
- data/rust/src/syntax.rs +41 -21
- data/rust/src/yerbafile.rs +86 -19
- metadata +13 -3
- data/rust/src/document.rs +0 -2304
data/rust/src/json.rs
CHANGED
|
@@ -117,12 +117,6 @@ pub fn select_field_key(field: &str) -> String {
|
|
|
117
117
|
parsed
|
|
118
118
|
.segments()
|
|
119
119
|
.iter()
|
|
120
|
-
.find_map(|segment| {
|
|
121
|
-
if let SelectorSegment::Key(key) = segment {
|
|
122
|
-
Some(key.clone())
|
|
123
|
-
} else {
|
|
124
|
-
None
|
|
125
|
-
}
|
|
126
|
-
})
|
|
120
|
+
.find_map(|segment| if let SelectorSegment::Key(key) = segment { Some(key.clone()) } else { None })
|
|
127
121
|
.unwrap_or_else(|| field.to_string())
|
|
128
122
|
}
|
data/rust/src/lib.rs
CHANGED
|
@@ -9,9 +9,9 @@ mod syntax;
|
|
|
9
9
|
mod yaml_writer;
|
|
10
10
|
pub mod yerbafile;
|
|
11
11
|
|
|
12
|
-
pub use document::{collect_selectors, Document, InsertPosition, SortField};
|
|
12
|
+
pub use document::{collect_selectors, Document, InsertPosition, Location, NodeInfo, NodeType, SortField};
|
|
13
13
|
pub use error::YerbaError;
|
|
14
|
-
pub use quote_style::QuoteStyle;
|
|
14
|
+
pub use quote_style::{KeyStyle, QuoteStyle};
|
|
15
15
|
pub use selector::Selector;
|
|
16
16
|
pub use syntax::{detect_yaml_type, ScalarValue, YerbaValueType};
|
|
17
17
|
pub use yaml_writer::json_to_yaml_text;
|
|
@@ -28,3 +28,89 @@ pub fn parse(source: &str) -> Result<Document, YerbaError> {
|
|
|
28
28
|
pub fn parse_file(path: impl AsRef<std::path::Path>) -> Result<Document, YerbaError> {
|
|
29
29
|
Document::parse_file(path)
|
|
30
30
|
}
|
|
31
|
+
|
|
32
|
+
pub fn glob_get(pattern: &str, selector: &str) -> Vec<ScalarValue> {
|
|
33
|
+
use rayon::prelude::*;
|
|
34
|
+
|
|
35
|
+
let parsed_selector = Selector::parse(selector);
|
|
36
|
+
|
|
37
|
+
let files = match glob::glob(pattern) {
|
|
38
|
+
Ok(paths) => paths.filter_map(|p| p.ok()).collect::<Vec<_>>(),
|
|
39
|
+
Err(_) => return vec![],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
files
|
|
43
|
+
.par_iter()
|
|
44
|
+
.flat_map(|file| {
|
|
45
|
+
let mut results = Vec::new();
|
|
46
|
+
|
|
47
|
+
if let Ok(document) = Document::parse_file(file) {
|
|
48
|
+
if parsed_selector.has_wildcard() {
|
|
49
|
+
results.extend(document.get_all_typed(selector));
|
|
50
|
+
} else if let Some(scalar) = document.get_typed(selector) {
|
|
51
|
+
results.push(scalar);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
results
|
|
56
|
+
})
|
|
57
|
+
.collect()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
pub fn glob_find(pattern: &str, selector: &str, condition: Option<&str>, select: Option<&str>) -> Vec<serde_json::Value> {
|
|
61
|
+
use rayon::prelude::*;
|
|
62
|
+
|
|
63
|
+
let files = match glob::glob(pattern) {
|
|
64
|
+
Ok(paths) => paths.filter_map(|p| p.ok()).collect::<Vec<_>>(),
|
|
65
|
+
Err(_) => return vec![],
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
let select_fields: Option<Vec<&str>> = select.map(|s| s.split(',').collect());
|
|
69
|
+
|
|
70
|
+
files
|
|
71
|
+
.par_iter()
|
|
72
|
+
.flat_map(|file| {
|
|
73
|
+
let mut file_results = Vec::new();
|
|
74
|
+
|
|
75
|
+
if let Ok(document) = Document::parse_file(file) {
|
|
76
|
+
let values = match condition {
|
|
77
|
+
Some(cond) => document.filter(selector, cond),
|
|
78
|
+
None => document.get_values(selector),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
let file_string = file.to_string_lossy().to_string();
|
|
82
|
+
|
|
83
|
+
for value in &values {
|
|
84
|
+
let mut result = serde_json::Map::new();
|
|
85
|
+
result.insert("__file".to_string(), serde_json::Value::String(file_string.clone()));
|
|
86
|
+
|
|
87
|
+
match &select_fields {
|
|
88
|
+
Some(fields) => {
|
|
89
|
+
for field in fields {
|
|
90
|
+
let json_value = json::resolve_select_field(value, field);
|
|
91
|
+
let json_key = json::select_field_key(field);
|
|
92
|
+
result.insert(json_key, json_value);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
None => {
|
|
96
|
+
if let serde_yaml::Value::Mapping(map) = value {
|
|
97
|
+
for (key, yaml_value) in map {
|
|
98
|
+
let json_key = match key {
|
|
99
|
+
serde_yaml::Value::String(string) => string.clone(),
|
|
100
|
+
_ => format!("{:?}", key),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
result.insert(json_key, json::yaml_to_json(yaml_value));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
file_results.push(serde_json::Value::Object(result));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
file_results
|
|
114
|
+
})
|
|
115
|
+
.collect()
|
|
116
|
+
}
|
data/rust/src/main.rs
CHANGED
|
@@ -31,7 +31,9 @@ static HELP: LazyLock<String> = LazyLock::new(|| {
|
|
|
31
31
|
Yerbafile:
|
|
32
32
|
yerba init Create a new Yerbafile in the current directory
|
|
33
33
|
yerba check Check if all files match the rules (exits 1 if not)
|
|
34
|
+
yerba check <file> Check a specific file against matching rules
|
|
34
35
|
yerba apply Apply all rules and write changes
|
|
36
|
+
yerba apply <file> Apply rules to a specific file
|
|
35
37
|
|
|
36
38
|
Examples:
|
|
37
39
|
yerba get config.yml "database.host"
|
data/rust/src/quote_style.rs
CHANGED
|
@@ -1,29 +1,85 @@
|
|
|
1
1
|
use clap::ValueEnum;
|
|
2
2
|
use yaml_parser::SyntaxKind;
|
|
3
3
|
|
|
4
|
+
#[derive(Debug, Clone, PartialEq, ValueEnum)]
|
|
5
|
+
pub enum KeyStyle {
|
|
6
|
+
/// Unquoted key (host:)
|
|
7
|
+
Plain,
|
|
8
|
+
/// Single-quoted key ('host':)
|
|
9
|
+
Single,
|
|
10
|
+
/// Double-quoted key ("host":)
|
|
11
|
+
Double,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
impl std::str::FromStr for KeyStyle {
|
|
15
|
+
type Err = String;
|
|
16
|
+
|
|
17
|
+
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
|
18
|
+
match string {
|
|
19
|
+
"plain" => Ok(KeyStyle::Plain),
|
|
20
|
+
"single" | "single-quoted" => Ok(KeyStyle::Single),
|
|
21
|
+
"double" | "double-quoted" => Ok(KeyStyle::Double),
|
|
22
|
+
_ => Err(format!("unknown key style: '{}'. Valid options: plain, single, double", string)),
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
impl KeyStyle {
|
|
28
|
+
pub(crate) fn to_syntax_kind(&self) -> SyntaxKind {
|
|
29
|
+
match self {
|
|
30
|
+
KeyStyle::Plain => SyntaxKind::PLAIN_SCALAR,
|
|
31
|
+
KeyStyle::Single => SyntaxKind::SINGLE_QUOTED_SCALAR,
|
|
32
|
+
KeyStyle::Double => SyntaxKind::DOUBLE_QUOTED_SCALAR,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
4
37
|
#[derive(Debug, Clone, PartialEq, ValueEnum)]
|
|
5
38
|
pub enum QuoteStyle {
|
|
39
|
+
/// Unquoted value (host: localhost)
|
|
6
40
|
Plain,
|
|
41
|
+
/// Single-quoted value (host: 'localhost')
|
|
7
42
|
#[value(alias = "single-quoted")]
|
|
8
43
|
Single,
|
|
44
|
+
/// Double-quoted value (host: "localhost")
|
|
9
45
|
#[value(alias = "double-quoted")]
|
|
10
46
|
Double,
|
|
11
|
-
|
|
47
|
+
/// Literal block scalar, strip trailing newline (|-)
|
|
48
|
+
#[value(alias = "block-literal", alias = "|-")]
|
|
12
49
|
Literal,
|
|
13
|
-
|
|
50
|
+
/// Literal block scalar, keep one trailing newline (|)
|
|
51
|
+
#[value(alias = "|")]
|
|
52
|
+
LiteralClip,
|
|
53
|
+
/// Literal block scalar, keep all trailing newlines (|+)
|
|
54
|
+
#[value(alias = "|+")]
|
|
55
|
+
LiteralKeep,
|
|
56
|
+
/// Folded block scalar, strip trailing newline (>-)
|
|
57
|
+
#[value(alias = "block-folded", alias = ">-")]
|
|
14
58
|
Folded,
|
|
59
|
+
/// Folded block scalar, keep one trailing newline (>)
|
|
60
|
+
#[value(alias = ">")]
|
|
61
|
+
FoldedClip,
|
|
62
|
+
/// Folded block scalar, keep all trailing newlines (>+)
|
|
63
|
+
#[value(alias = ">+")]
|
|
64
|
+
FoldedKeep,
|
|
15
65
|
}
|
|
16
66
|
|
|
17
67
|
impl std::str::FromStr for QuoteStyle {
|
|
18
68
|
type Err = String;
|
|
19
69
|
|
|
20
70
|
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
|
21
|
-
|
|
71
|
+
let normalized = string.replace('_', "-");
|
|
72
|
+
|
|
73
|
+
match normalized.as_str() {
|
|
22
74
|
"plain" => Ok(QuoteStyle::Plain),
|
|
23
75
|
"single" | "single-quoted" => Ok(QuoteStyle::Single),
|
|
24
76
|
"double" | "double-quoted" => Ok(QuoteStyle::Double),
|
|
25
|
-
"literal" | "block-literal" => Ok(QuoteStyle::Literal),
|
|
26
|
-
"
|
|
77
|
+
"literal" | "block-literal" | "|-" => Ok(QuoteStyle::Literal),
|
|
78
|
+
"literal-clip" | "|" => Ok(QuoteStyle::LiteralClip),
|
|
79
|
+
"literal-keep" | "|+" => Ok(QuoteStyle::LiteralKeep),
|
|
80
|
+
"folded" | "block-folded" | ">-" => Ok(QuoteStyle::Folded),
|
|
81
|
+
"folded-clip" | ">" => Ok(QuoteStyle::FoldedClip),
|
|
82
|
+
"folded-keep" | ">+" => Ok(QuoteStyle::FoldedKeep),
|
|
27
83
|
_ => Err(format!("unknown quote style: '{}'", string)),
|
|
28
84
|
}
|
|
29
85
|
}
|
|
@@ -35,8 +91,28 @@ impl QuoteStyle {
|
|
|
35
91
|
QuoteStyle::Plain => SyntaxKind::PLAIN_SCALAR,
|
|
36
92
|
QuoteStyle::Single => SyntaxKind::SINGLE_QUOTED_SCALAR,
|
|
37
93
|
QuoteStyle::Double => SyntaxKind::DOUBLE_QUOTED_SCALAR,
|
|
38
|
-
QuoteStyle::Literal =>
|
|
39
|
-
|
|
94
|
+
QuoteStyle::Literal | QuoteStyle::LiteralClip | QuoteStyle::LiteralKeep | QuoteStyle::Folded | QuoteStyle::FoldedClip | QuoteStyle::FoldedKeep => {
|
|
95
|
+
SyntaxKind::BLOCK_SCALAR_TEXT
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
pub(crate) fn is_block_scalar(&self) -> bool {
|
|
101
|
+
matches!(
|
|
102
|
+
self,
|
|
103
|
+
QuoteStyle::Literal | QuoteStyle::LiteralClip | QuoteStyle::LiteralKeep | QuoteStyle::Folded | QuoteStyle::FoldedClip | QuoteStyle::FoldedKeep
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
pub(crate) fn block_header(&self) -> &'static str {
|
|
108
|
+
match self {
|
|
109
|
+
QuoteStyle::Literal => "|-",
|
|
110
|
+
QuoteStyle::LiteralClip => "|",
|
|
111
|
+
QuoteStyle::LiteralKeep => "|+",
|
|
112
|
+
QuoteStyle::Folded => ">-",
|
|
113
|
+
QuoteStyle::FoldedClip => ">",
|
|
114
|
+
QuoteStyle::FoldedKeep => ">+",
|
|
115
|
+
_ => "",
|
|
40
116
|
}
|
|
41
117
|
}
|
|
42
118
|
}
|
data/rust/src/selector.rs
CHANGED
|
@@ -58,10 +58,7 @@ impl Selector {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
pub fn ends_with_bracket(&self) -> bool {
|
|
61
|
-
matches!(
|
|
62
|
-
self.segments().last(),
|
|
63
|
-
Some(SelectorSegment::AllItems | SelectorSegment::Index(_))
|
|
64
|
-
)
|
|
61
|
+
matches!(self.segments().last(), Some(SelectorSegment::AllItems | SelectorSegment::Index(_)))
|
|
65
62
|
}
|
|
66
63
|
|
|
67
64
|
pub fn has_brackets(&self) -> bool {
|
|
@@ -109,9 +106,7 @@ impl Selector {
|
|
|
109
106
|
pub fn split_at_first_bracket(&self) -> (Selector, Selector) {
|
|
110
107
|
let segments = self.segments();
|
|
111
108
|
|
|
112
|
-
let first_bracket = segments
|
|
113
|
-
.iter()
|
|
114
|
-
.position(|s| matches!(s, SelectorSegment::AllItems | SelectorSegment::Index(_)));
|
|
109
|
+
let first_bracket = segments.iter().position(|s| matches!(s, SelectorSegment::AllItems | SelectorSegment::Index(_)));
|
|
115
110
|
|
|
116
111
|
match first_bracket {
|
|
117
112
|
Some(position) => {
|
data/rust/src/syntax.rs
CHANGED
|
@@ -47,16 +47,11 @@ pub fn extract_scalar(node: &SyntaxNode) -> Option<ScalarValue> {
|
|
|
47
47
|
_ => return None,
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
-
Some(ScalarValue {
|
|
51
|
-
text,
|
|
52
|
-
kind: token.kind(),
|
|
53
|
-
})
|
|
50
|
+
Some(ScalarValue { text, kind: token.kind() })
|
|
54
51
|
}
|
|
55
52
|
|
|
56
53
|
pub fn is_map_key(token: &SyntaxToken) -> bool {
|
|
57
|
-
token
|
|
58
|
-
.parent_ancestors()
|
|
59
|
-
.any(|ancestor| ancestor.kind() == SyntaxKind::BLOCK_MAP_KEY)
|
|
54
|
+
token.parent_ancestors().any(|ancestor| ancestor.kind() == SyntaxKind::BLOCK_MAP_KEY)
|
|
60
55
|
}
|
|
61
56
|
|
|
62
57
|
pub fn find_entry_by_key(map: &BlockMap, key: &str) -> Option<BlockMapEntry> {
|
|
@@ -70,15 +65,12 @@ pub fn find_entry_by_key(map: &BlockMap, key: &str) -> Option<BlockMapEntry> {
|
|
|
70
65
|
}
|
|
71
66
|
|
|
72
67
|
pub fn find_scalar_token(node: &SyntaxNode) -> Option<SyntaxToken> {
|
|
73
|
-
node
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
SyntaxKind::PLAIN_SCALAR | SyntaxKind::DOUBLE_QUOTED_SCALAR | SyntaxKind::SINGLE_QUOTED_SCALAR
|
|
80
|
-
)
|
|
81
|
-
})
|
|
68
|
+
node.descendants_with_tokens().filter_map(|element| element.into_token()).find(|token| {
|
|
69
|
+
matches!(
|
|
70
|
+
token.kind(),
|
|
71
|
+
SyntaxKind::PLAIN_SCALAR | SyntaxKind::DOUBLE_QUOTED_SCALAR | SyntaxKind::SINGLE_QUOTED_SCALAR
|
|
72
|
+
)
|
|
73
|
+
})
|
|
82
74
|
}
|
|
83
75
|
|
|
84
76
|
pub fn format_scalar_value(value: &str, kind: SyntaxKind) -> String {
|
|
@@ -122,7 +114,38 @@ pub fn extract_scalar_text(node: &SyntaxNode) -> Option<String> {
|
|
|
122
114
|
}
|
|
123
115
|
|
|
124
116
|
pub fn unescape_double_quoted(text: &str) -> String {
|
|
125
|
-
text.
|
|
117
|
+
let mut result = String::with_capacity(text.len());
|
|
118
|
+
let mut chars = text.chars();
|
|
119
|
+
|
|
120
|
+
while let Some(character) = chars.next() {
|
|
121
|
+
if character == '\\' {
|
|
122
|
+
match chars.next() {
|
|
123
|
+
Some('n') => result.push('\n'),
|
|
124
|
+
Some('t') => result.push('\t'),
|
|
125
|
+
Some('r') => result.push('\r'),
|
|
126
|
+
Some('\\') => result.push('\\'),
|
|
127
|
+
Some('"') => result.push('"'),
|
|
128
|
+
Some('/') => result.push('/'),
|
|
129
|
+
Some('0') => result.push('\0'),
|
|
130
|
+
Some('a') => result.push('\u{07}'),
|
|
131
|
+
Some('b') => result.push('\u{08}'),
|
|
132
|
+
Some('e') => result.push('\u{1b}'),
|
|
133
|
+
Some('v') => result.push('\u{0b}'),
|
|
134
|
+
Some(' ') => result.push(' '),
|
|
135
|
+
Some('_') => result.push('\u{a0}'),
|
|
136
|
+
Some('\n') => {} // line continuation: skip newline and leading whitespace
|
|
137
|
+
Some(other) => {
|
|
138
|
+
result.push('\\');
|
|
139
|
+
result.push(other);
|
|
140
|
+
}
|
|
141
|
+
None => result.push('\\'),
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
result.push(character);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
result
|
|
126
149
|
}
|
|
127
150
|
|
|
128
151
|
pub fn unescape_single_quoted(text: &str) -> String {
|
|
@@ -194,10 +217,7 @@ pub fn is_yaml_non_string(value: &str) -> bool {
|
|
|
194
217
|
}
|
|
195
218
|
|
|
196
219
|
pub fn is_yaml_truthy(value: &str) -> bool {
|
|
197
|
-
matches!(
|
|
198
|
-
value,
|
|
199
|
-
"true" | "True" | "TRUE" | "yes" | "Yes" | "YES" | "on" | "On" | "ON" | "y" | "Y"
|
|
200
|
-
)
|
|
220
|
+
matches!(value, "true" | "True" | "TRUE" | "yes" | "Yes" | "YES" | "on" | "On" | "ON" | "y" | "Y")
|
|
201
221
|
}
|
|
202
222
|
|
|
203
223
|
pub fn detect_yaml_type_from_plain(value: &str) -> YerbaValueType {
|
data/rust/src/yerbafile.rs
CHANGED
|
@@ -30,6 +30,7 @@ pub enum PipelineStep {
|
|
|
30
30
|
Remove(RemoveConfig),
|
|
31
31
|
BlankLines(BlankLinesConfig),
|
|
32
32
|
Sort(SortConfig),
|
|
33
|
+
Directives(DirectivesConfig),
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
#[derive(Debug, Clone, Deserialize)]
|
|
@@ -49,6 +50,14 @@ pub struct BlankLinesConfig {
|
|
|
49
50
|
pub count: usize,
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
#[derive(Debug, Clone, Deserialize)]
|
|
54
|
+
pub struct DirectivesConfig {
|
|
55
|
+
#[serde(default)]
|
|
56
|
+
pub ensure: bool,
|
|
57
|
+
#[serde(default)]
|
|
58
|
+
pub remove: bool,
|
|
59
|
+
}
|
|
60
|
+
|
|
52
61
|
#[derive(Debug, Clone, Deserialize)]
|
|
53
62
|
pub struct RenameConfig {
|
|
54
63
|
pub from: String,
|
|
@@ -112,13 +121,18 @@ impl<'de> Deserialize<'de> for PipelineStep {
|
|
|
112
121
|
return Ok(PipelineStep::BlankLines(config));
|
|
113
122
|
}
|
|
114
123
|
|
|
124
|
+
if let Some(value) = mapping.get(serde_yaml::Value::String("directives".to_string())) {
|
|
125
|
+
let config: DirectivesConfig = serde_yaml::from_value(value.clone()).map_err(serde::de::Error::custom)?;
|
|
126
|
+
return Ok(PipelineStep::Directives(config));
|
|
127
|
+
}
|
|
128
|
+
|
|
115
129
|
if let Some(value) = mapping.get(serde_yaml::Value::String("sort".to_string())) {
|
|
116
130
|
let config: SortConfig = serde_yaml::from_value(value.clone()).map_err(serde::de::Error::custom)?;
|
|
117
131
|
return Ok(PipelineStep::Sort(config));
|
|
118
132
|
}
|
|
119
133
|
|
|
120
134
|
Err(serde::de::Error::custom(
|
|
121
|
-
"unknown pipeline step: expected sort_keys, quote_style, set, insert, delete, rename, remove, blank_lines, or
|
|
135
|
+
"unknown pipeline step: expected sort_keys, quote_style, set, insert, delete, rename, remove, blank_lines, sort, or directives",
|
|
122
136
|
))
|
|
123
137
|
}
|
|
124
138
|
}
|
|
@@ -176,15 +190,18 @@ pub struct RuleResult {
|
|
|
176
190
|
impl Yerbafile {
|
|
177
191
|
pub fn load(path: impl AsRef<Path>) -> Result<Self, YerbaError> {
|
|
178
192
|
let content = fs::read_to_string(path.as_ref())?;
|
|
179
|
-
let yerbafile: Yerbafile =
|
|
180
|
-
serde_yaml::from_str(&content).map_err(|error| YerbaError::ParseError(format!("{}", error)))?;
|
|
193
|
+
let yerbafile: Yerbafile = serde_yaml::from_str(&content).map_err(|error| YerbaError::ParseError(format!("{}", error)))?;
|
|
181
194
|
Ok(yerbafile)
|
|
182
195
|
}
|
|
183
196
|
|
|
184
197
|
pub fn find() -> Option<PathBuf> {
|
|
198
|
+
Self::find_from(std::env::current_dir().ok()?)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
pub fn find_from(start: impl AsRef<Path>) -> Option<PathBuf> {
|
|
185
202
|
let candidates = ["Yerbafile", "Yerbafile.yml", "Yerbafile.yaml", ".yerbafile"];
|
|
186
203
|
|
|
187
|
-
let mut directory =
|
|
204
|
+
let mut directory = start.as_ref().to_path_buf();
|
|
188
205
|
|
|
189
206
|
loop {
|
|
190
207
|
for candidate in &candidates {
|
|
@@ -291,10 +308,7 @@ impl Yerbafile {
|
|
|
291
308
|
continue;
|
|
292
309
|
}
|
|
293
310
|
|
|
294
|
-
let file_results: Vec<RuleResult> = file_strings
|
|
295
|
-
.par_iter()
|
|
296
|
-
.map(|file| self.apply_pipeline_to_file(rule, file, write))
|
|
297
|
-
.collect();
|
|
311
|
+
let file_results: Vec<RuleResult> = file_strings.par_iter().map(|file| self.apply_pipeline_to_file(rule, file, write)).collect();
|
|
298
312
|
|
|
299
313
|
results.extend(file_results);
|
|
300
314
|
}
|
|
@@ -346,6 +360,48 @@ impl Yerbafile {
|
|
|
346
360
|
error: None,
|
|
347
361
|
}
|
|
348
362
|
}
|
|
363
|
+
|
|
364
|
+
pub fn apply_file(&self, file: &str, write: bool) -> Vec<RuleResult> {
|
|
365
|
+
let mut results = Vec::new();
|
|
366
|
+
|
|
367
|
+
for rule in &self.rules {
|
|
368
|
+
if let Ok(pattern) = glob::Pattern::new(&rule.files) {
|
|
369
|
+
if !pattern.matches(file) && !pattern.matches_path(Path::new(file)) {
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
} else {
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
results.push(self.apply_pipeline_to_file(rule, file, write));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
results
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
pub fn apply_to_document(&self, document: &mut Document, file_path: &str) -> Result<bool, YerbaError> {
|
|
383
|
+
let original = document.to_string();
|
|
384
|
+
|
|
385
|
+
for rule in &self.rules {
|
|
386
|
+
if !file_path.is_empty() {
|
|
387
|
+
if let Ok(pattern) = glob::Pattern::new(&rule.files) {
|
|
388
|
+
if !pattern.matches(file_path) && !pattern.matches_path(Path::new(file_path)) {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
} else {
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
let base_path = rule.path.as_deref();
|
|
397
|
+
|
|
398
|
+
for step in &rule.pipeline {
|
|
399
|
+
execute_step(document, step, base_path)?;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
Ok(document.to_string() != original)
|
|
404
|
+
}
|
|
349
405
|
}
|
|
350
406
|
|
|
351
407
|
fn execute_step(document: &mut Document, step: &PipelineStep, base_path: Option<&str>) -> Result<(), YerbaError> {
|
|
@@ -353,15 +409,16 @@ fn execute_step(document: &mut Document, step: &PipelineStep, base_path: Option<
|
|
|
353
409
|
PipelineStep::QuoteStyle(config) => {
|
|
354
410
|
let dot_path = config.path.as_deref();
|
|
355
411
|
|
|
356
|
-
let key_style = config.key_style.parse::<
|
|
412
|
+
let key_style = config.key_style.parse::<crate::KeyStyle>().map_err(YerbaError::ParseError)?;
|
|
357
413
|
|
|
358
|
-
let value_style = config
|
|
359
|
-
.value_style
|
|
360
|
-
.parse::<QuoteStyle>()
|
|
361
|
-
.map_err(YerbaError::ParseError)?;
|
|
414
|
+
let value_style = config.value_style.parse::<QuoteStyle>().map_err(YerbaError::ParseError)?;
|
|
362
415
|
|
|
363
416
|
document.enforce_key_style(&key_style, dot_path)?;
|
|
364
|
-
document.enforce_quotes_at(&value_style, dot_path)?;
|
|
417
|
+
let warnings = document.enforce_quotes_at(&value_style, dot_path)?;
|
|
418
|
+
|
|
419
|
+
for warning in &warnings {
|
|
420
|
+
eprintln!(" warning: {}", warning);
|
|
421
|
+
}
|
|
365
422
|
|
|
366
423
|
Ok(())
|
|
367
424
|
}
|
|
@@ -451,14 +508,24 @@ fn execute_step(document: &mut Document, step: &PipelineStep, base_path: Option<
|
|
|
451
508
|
|
|
452
509
|
PipelineStep::Sort(config) => {
|
|
453
510
|
let full_path = resolve_step_path(base_path, config.path.as_deref());
|
|
454
|
-
let sort_fields = config
|
|
455
|
-
.by
|
|
456
|
-
.as_deref()
|
|
457
|
-
.map(crate::SortField::parse_list)
|
|
458
|
-
.unwrap_or_default();
|
|
511
|
+
let sort_fields = config.by.as_deref().map(crate::SortField::parse_list).unwrap_or_default();
|
|
459
512
|
|
|
460
513
|
document.sort_items(&full_path, &sort_fields, config.case_sensitive)
|
|
461
514
|
}
|
|
515
|
+
|
|
516
|
+
PipelineStep::Directives(config) => {
|
|
517
|
+
if config.ensure && config.remove {
|
|
518
|
+
return Err(YerbaError::ParseError("directives: ensure and remove are mutually exclusive".to_string()));
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if config.ensure {
|
|
522
|
+
document.ensure_directives()
|
|
523
|
+
} else if config.remove {
|
|
524
|
+
document.remove_directives()
|
|
525
|
+
} else {
|
|
526
|
+
Ok(())
|
|
527
|
+
}
|
|
528
|
+
}
|
|
462
529
|
}
|
|
463
530
|
}
|
|
464
531
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: yerba
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.1
|
|
5
5
|
platform: aarch64-linux-gnu
|
|
6
6
|
authors:
|
|
7
7
|
- Marco Roth
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-05-
|
|
11
|
+
date: 2026-05-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: A CLI tool for editing YAML while preserving structure, comments, and
|
|
14
14
|
format.
|
|
@@ -36,9 +36,11 @@ files:
|
|
|
36
36
|
- lib/yerba/formatting.rb
|
|
37
37
|
- lib/yerba/location.rb
|
|
38
38
|
- lib/yerba/map.rb
|
|
39
|
+
- lib/yerba/query_result.rb
|
|
39
40
|
- lib/yerba/scalar.rb
|
|
40
41
|
- lib/yerba/sequence.rb
|
|
41
42
|
- lib/yerba/version.rb
|
|
43
|
+
- lib/yerba/yerbafile.rb
|
|
42
44
|
- rust/Cargo.lock
|
|
43
45
|
- rust/Cargo.toml
|
|
44
46
|
- rust/build.rs
|
|
@@ -48,6 +50,7 @@ files:
|
|
|
48
50
|
- rust/src/commands/blank_lines.rs
|
|
49
51
|
- rust/src/commands/check.rs
|
|
50
52
|
- rust/src/commands/delete.rs
|
|
53
|
+
- rust/src/commands/directives.rs
|
|
51
54
|
- rust/src/commands/get.rs
|
|
52
55
|
- rust/src/commands/init.rs
|
|
53
56
|
- rust/src/commands/insert.rs
|
|
@@ -64,7 +67,14 @@ files:
|
|
|
64
67
|
- rust/src/commands/sort_keys.rs
|
|
65
68
|
- rust/src/commands/version.rs
|
|
66
69
|
- rust/src/didyoumean.rs
|
|
67
|
-
- rust/src/document.rs
|
|
70
|
+
- rust/src/document/condition.rs
|
|
71
|
+
- rust/src/document/delete.rs
|
|
72
|
+
- rust/src/document/get.rs
|
|
73
|
+
- rust/src/document/insert.rs
|
|
74
|
+
- rust/src/document/mod.rs
|
|
75
|
+
- rust/src/document/set.rs
|
|
76
|
+
- rust/src/document/sort.rs
|
|
77
|
+
- rust/src/document/style.rs
|
|
68
78
|
- rust/src/error.rs
|
|
69
79
|
- rust/src/ffi.rs
|
|
70
80
|
- rust/src/json.rs
|