yerba 0.8.0 → 0.8.1

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.
@@ -1,5 +1,13 @@
1
1
  use super::*;
2
2
 
3
+ fn scalar_replacement_text(value: &str, kind: SyntaxKind) -> String {
4
+ if kind == SyntaxKind::PLAIN_SCALAR && !crate::syntax::is_inline_scalar_safe(value) {
5
+ return format_scalar_value(value, SyntaxKind::DOUBLE_QUOTED_SCALAR);
6
+ }
7
+
8
+ format_scalar_value(value, kind)
9
+ }
10
+
3
11
  fn holds_collection(node: &SyntaxNode) -> bool {
4
12
  node.descendants().any(|descendant| {
5
13
  matches!(
@@ -84,7 +92,7 @@ impl Document {
84
92
 
85
93
  let scalar_token = find_scalar_token(&current_node).ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
86
94
 
87
- let new_text = format_scalar_value(value, scalar_token.kind());
95
+ let new_text = scalar_replacement_text(value, scalar_token.kind());
88
96
 
89
97
  self.replace_token(&scalar_token, &new_text)
90
98
  }
@@ -107,7 +115,7 @@ impl Document {
107
115
  edits.push((span.range, replacement_text(&span, value)));
108
116
  }
109
117
  } else if let Some(scalar_token) = find_scalar_token(&node) {
110
- edits.push((scalar_token.text_range(), format_scalar_value(value, scalar_token.kind())));
118
+ edits.push((scalar_token.text_range(), scalar_replacement_text(value, scalar_token.kind())));
111
119
  }
112
120
  }
113
121
 
data/rust/src/error.rs CHANGED
@@ -10,6 +10,7 @@ pub enum YerbaError {
10
10
  IndexOutOfBounds(usize, usize),
11
11
  UnknownKeys(Vec<String>),
12
12
  DuplicateValues(Vec<crate::DuplicateInfo>),
13
+ #[cfg(feature = "schema")]
13
14
  SchemaValidation(Vec<crate::schema::ValidationError>),
14
15
  DuplicateKey {
15
16
  key: String,
@@ -78,6 +79,7 @@ impl std::fmt::Display for YerbaError {
78
79
  write!(f, "index {} out of bounds (length {})", index, length)
79
80
  }
80
81
 
82
+ #[cfg(feature = "schema")]
81
83
  YerbaError::SchemaValidation(errors) => {
82
84
  let details: Vec<String> = errors.iter().map(|error| error.to_string()).collect();
83
85
 
@@ -137,6 +139,7 @@ impl GitHubAnnotations for YerbaError {
137
139
  })
138
140
  .collect(),
139
141
 
142
+ #[cfg(feature = "schema")]
140
143
  YerbaError::SchemaValidation(errors) => errors
141
144
  .iter()
142
145
  .map(|error| GitHubAnnotation {
data/rust/src/ffi.rs CHANGED
@@ -746,6 +746,7 @@ pub unsafe extern "C" fn yerba_document_blank_lines(document: *mut Document, pat
746
746
  }
747
747
 
748
748
  /// Caller must free with yerba_string_free.
749
+ #[cfg(feature = "schema")]
749
750
  #[no_mangle]
750
751
  pub unsafe extern "C" fn yerba_document_validate_schema(document: *const Document, schema_json: *const c_char, selector: *const c_char) -> *mut c_char {
751
752
  let document = &*document;
@@ -784,6 +785,7 @@ pub unsafe extern "C" fn yerba_document_validate_schema(document: *const Documen
784
785
  }
785
786
 
786
787
  /// Caller must free with yerba_string_free.
788
+ #[cfg(feature = "cli")]
787
789
  #[no_mangle]
788
790
  pub unsafe extern "C" fn yerba_yerbafile_find(directory: *const c_char) -> *mut c_char {
789
791
  let start = if directory.is_null() {
@@ -803,6 +805,7 @@ pub unsafe extern "C" fn yerba_yerbafile_find(directory: *const c_char) -> *mut
803
805
  }
804
806
  }
805
807
 
808
+ #[cfg(feature = "cli")]
806
809
  #[no_mangle]
807
810
  pub unsafe extern "C" fn yerba_document_apply_yerbafile(document: *mut Document, file_path: *const c_char, yerbafile_path: *const c_char) -> YerbaResult {
808
811
  let document = &mut *document;
@@ -924,6 +927,7 @@ fn located_nodes_to_list(nodes: &[crate::LocatedNode]) -> YerbaTypedList {
924
927
  }
925
928
  }
926
929
 
930
+ #[cfg(feature = "glob")]
927
931
  #[no_mangle]
928
932
  pub unsafe extern "C" fn yerba_glob_get(glob_pattern: *const c_char, path: *const c_char) -> YerbaTypedList {
929
933
  let pattern = CStr::from_ptr(glob_pattern).to_str().unwrap_or("");
@@ -940,6 +944,7 @@ pub unsafe extern "C" fn yerba_document_get_all(document: *const Document, path:
940
944
  located_nodes_to_list(&document.get_all_located(selector_string))
941
945
  }
942
946
 
947
+ #[cfg(feature = "glob")]
943
948
  #[no_mangle]
944
949
  pub unsafe extern "C" fn yerba_glob_find(glob_pattern: *const c_char, path: *const c_char, condition: *const c_char, select: *const c_char) -> YerbaTypedList {
945
950
  let pattern = CStr::from_ptr(glob_pattern).to_str().unwrap_or("");
data/rust/src/lib.rs CHANGED
@@ -4,10 +4,12 @@ pub mod error;
4
4
  pub mod ffi;
5
5
  pub mod json;
6
6
  mod quote_style;
7
+ #[cfg(feature = "schema")]
7
8
  pub mod schema;
8
9
  pub mod selector;
9
10
  mod syntax;
10
11
  mod yaml_writer;
12
+ #[cfg(feature = "cli")]
11
13
  pub mod yerbafile;
12
14
 
13
15
  pub use document::style::StyleEnforcement;
@@ -17,8 +19,12 @@ pub use document::{
17
19
  pub use error::YerbaError;
18
20
  pub use quote_style::{KeyStyle, QuoteStyle};
19
21
  pub use selector::Selector;
20
- pub use syntax::{detect_yaml_type, ScalarValue, YerbaValueType};
22
+ pub use syntax::{
23
+ detect_yaml_type, is_flow_collection, is_inline_scalar_safe, is_plain_safe, is_plain_safe_in_flow, is_quoted_scalar, is_raw_yaml_text, is_valid_inline_value,
24
+ needs_quoting, needs_quoting_in_flow, quote_if_needed, ScalarValue, YerbaValueType,
25
+ };
21
26
  pub use yaml_writer::json_to_yaml_text;
27
+ #[cfg(feature = "cli")]
22
28
  pub use yerbafile::Yerbafile;
23
29
 
24
30
  pub fn version() -> &'static str {
@@ -33,6 +39,7 @@ pub fn parse_file(path: impl AsRef<std::path::Path>) -> Result<Document, YerbaEr
33
39
  Document::parse_file(path)
34
40
  }
35
41
 
42
+ #[cfg(feature = "glob")]
36
43
  pub fn glob_get(pattern: &str, selector: &str) -> Vec<document::LocatedNode> {
37
44
  use rayon::prelude::*;
38
45
 
@@ -55,6 +62,7 @@ pub fn glob_get(pattern: &str, selector: &str) -> Vec<document::LocatedNode> {
55
62
  .collect()
56
63
  }
57
64
 
65
+ #[cfg(feature = "glob")]
58
66
  pub fn glob_find(pattern: &str, selector: &str, condition: Option<&str>, select: Option<&str>) -> Result<Vec<serde_json::Value>, YerbaError> {
59
67
  use rayon::prelude::*;
60
68
 
@@ -1,7 +1,7 @@
1
- use clap::ValueEnum;
2
1
  use yaml_parser::SyntaxKind;
3
2
 
4
- #[derive(Debug, Clone, PartialEq, ValueEnum)]
3
+ #[derive(Debug, Clone, PartialEq)]
4
+ #[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
5
5
  pub enum KeyStyle {
6
6
  /// Unquoted key (host:)
7
7
  Plain,
@@ -34,33 +34,34 @@ impl KeyStyle {
34
34
  }
35
35
  }
36
36
 
37
- #[derive(Debug, Clone, PartialEq, ValueEnum)]
37
+ #[derive(Debug, Clone, PartialEq)]
38
+ #[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
38
39
  pub enum QuoteStyle {
39
40
  /// Unquoted value (host: localhost)
40
41
  Plain,
41
42
  /// Single-quoted value (host: 'localhost')
42
- #[value(alias = "single-quoted")]
43
+ #[cfg_attr(feature = "cli", value(alias = "single-quoted"))]
43
44
  Single,
44
45
  /// Double-quoted value (host: "localhost")
45
- #[value(alias = "double-quoted")]
46
+ #[cfg_attr(feature = "cli", value(alias = "double-quoted"))]
46
47
  Double,
47
48
  /// Literal block scalar, strip trailing newline (|-)
48
- #[value(alias = "block-literal", alias = "|-")]
49
+ #[cfg_attr(feature = "cli", value(alias = "block-literal", alias = "|-"))]
49
50
  Literal,
50
51
  /// Literal block scalar, keep one trailing newline (|)
51
- #[value(alias = "|")]
52
+ #[cfg_attr(feature = "cli", value(alias = "|"))]
52
53
  LiteralClip,
53
54
  /// Literal block scalar, keep all trailing newlines (|+)
54
- #[value(alias = "|+")]
55
+ #[cfg_attr(feature = "cli", value(alias = "|+"))]
55
56
  LiteralKeep,
56
57
  /// Folded block scalar, strip trailing newline (>-)
57
- #[value(alias = "block-folded", alias = ">-")]
58
+ #[cfg_attr(feature = "cli", value(alias = "block-folded", alias = ">-"))]
58
59
  Folded,
59
60
  /// Folded block scalar, keep one trailing newline (>)
60
- #[value(alias = ">")]
61
+ #[cfg_attr(feature = "cli", value(alias = ">"))]
61
62
  FoldedClip,
62
63
  /// Folded block scalar, keep all trailing newlines (>+)
63
- #[value(alias = ">+")]
64
+ #[cfg_attr(feature = "cli", value(alias = ">+"))]
64
65
  FoldedKeep,
65
66
  }
66
67
 
data/rust/src/syntax.rs CHANGED
@@ -182,8 +182,139 @@ pub fn format_scalar_value(value: &str, kind: SyntaxKind) -> String {
182
182
  }
183
183
  }
184
184
 
185
+ const LEADING_INDICATORS: [char; 16] = ['#', '&', '*', '!', '|', '>', '\'', '"', '%', '@', '`', ',', '[', ']', '{', '}'];
186
+ const FLOW_INDICATORS: [char; 5] = [',', '[', ']', '{', '}'];
187
+
188
+ pub fn is_plain_safe(value: &str) -> bool {
189
+ if value.is_empty() || value != value.trim() {
190
+ return false;
191
+ }
192
+
193
+ if value.contains('\n') || value.contains('\t') {
194
+ return false;
195
+ }
196
+
197
+ if value.contains(": ") || value.ends_with(':') {
198
+ return false;
199
+ }
200
+
201
+ if value.contains(" #") {
202
+ return false;
203
+ }
204
+
205
+ if value.starts_with("---") || value.starts_with("...") {
206
+ return false;
207
+ }
208
+
209
+ let mut characters = value.chars();
210
+ let first = characters.next().expect("value is non-empty");
211
+
212
+ if LEADING_INDICATORS.contains(&first) {
213
+ return false;
214
+ }
215
+
216
+ if matches!(first, '-' | '?' | ':') {
217
+ return matches!(characters.next(), Some(next) if next != ' ');
218
+ }
219
+
220
+ true
221
+ }
222
+
223
+ pub fn is_plain_safe_in_flow(value: &str) -> bool {
224
+ is_plain_safe(value) && !value.contains(FLOW_INDICATORS)
225
+ }
226
+
227
+ pub fn is_quoted_scalar(value: &str) -> bool {
228
+ let bytes = value.as_bytes();
229
+
230
+ if bytes.len() < 2 {
231
+ return false;
232
+ }
233
+
234
+ match (bytes[0], bytes[bytes.len() - 1]) {
235
+ (b'"', b'"') => {
236
+ let interior = &value[1..value.len() - 1];
237
+ let mut escaped = false;
238
+
239
+ for character in interior.chars() {
240
+ if escaped {
241
+ escaped = false;
242
+ continue;
243
+ }
244
+
245
+ match character {
246
+ '\\' => escaped = true,
247
+ '"' => return false,
248
+ _ => {}
249
+ }
250
+ }
251
+
252
+ !escaped
253
+ }
254
+
255
+ (b'\'', b'\'') => {
256
+ let interior = &value[1..value.len() - 1];
257
+ let mut characters = interior.chars().peekable();
258
+
259
+ while let Some(character) = characters.next() {
260
+ if character == '\'' {
261
+ if characters.peek() == Some(&'\'') {
262
+ characters.next();
263
+ } else {
264
+ return false;
265
+ }
266
+ }
267
+ }
268
+
269
+ true
270
+ }
271
+
272
+ _ => false,
273
+ }
274
+ }
275
+
276
+ pub fn is_flow_collection(value: &str) -> bool {
277
+ (value.starts_with('[') && value.ends_with(']')) || (value.starts_with('{') && value.ends_with('}'))
278
+ }
279
+
280
+ pub fn is_raw_yaml_text(value: &str) -> bool {
281
+ value.contains('\n') || value.starts_with("- ") || is_quoted_scalar(value) || is_flow_collection(value)
282
+ }
283
+
284
+ pub fn is_valid_inline_value(value: &str) -> bool {
285
+ if value.is_empty() {
286
+ return true;
287
+ }
288
+
289
+ is_raw_yaml_text(value) || is_plain_safe(value)
290
+ }
291
+
292
+ pub fn is_inline_scalar_safe(value: &str) -> bool {
293
+ if value.is_empty() {
294
+ return true;
295
+ }
296
+
297
+ if value.contains('\n') || value.starts_with("- ") {
298
+ return false;
299
+ }
300
+
301
+ is_quoted_scalar(value) || is_flow_collection(value) || is_plain_safe(value)
302
+ }
303
+
304
+ pub fn needs_quoting(value: &str) -> bool {
305
+ is_yaml_non_string(value) || !is_plain_safe(value)
306
+ }
307
+
308
+ pub fn needs_quoting_in_flow(value: &str) -> bool {
309
+ is_yaml_non_string(value) || !is_plain_safe_in_flow(value)
310
+ }
311
+
185
312
  pub fn quote_if_needed(value: &str) -> String {
186
- if is_yaml_non_string(value) {
313
+ if is_raw_yaml_text(value) {
314
+ return value.to_string();
315
+ }
316
+
317
+ if needs_quoting(value) {
187
318
  format_scalar_value(value, SyntaxKind::DOUBLE_QUOTED_SCALAR)
188
319
  } else {
189
320
  value.to_string()
@@ -69,8 +69,8 @@ pub fn yaml_value_to_flow_text(value: &yaml_serde::Value) -> String {
69
69
  yaml_serde::Value::Number(number) => number.to_string(),
70
70
 
71
71
  yaml_serde::Value::String(string) => {
72
- if crate::syntax::is_yaml_non_string(string) {
73
- format!("\"{}\"", string.replace('"', "\\\""))
72
+ if crate::syntax::needs_quoting_in_flow(string) {
73
+ crate::syntax::format_scalar_value(string, yaml_parser::SyntaxKind::DOUBLE_QUOTED_SCALAR)
74
74
  } else {
75
75
  string.clone()
76
76
  }
@@ -162,8 +162,8 @@ fn format_yaml_scalar(value: &Value, quote_style: &QuoteStyle) -> String {
162
162
  }
163
163
 
164
164
  QuoteStyle::Plain => {
165
- if crate::syntax::is_yaml_non_string(string) {
166
- format!("\"{}\"", string.replace('"', "\\\""))
165
+ if crate::syntax::needs_quoting(string) {
166
+ crate::syntax::format_scalar_value(string, yaml_parser::SyntaxKind::DOUBLE_QUOTED_SCALAR)
167
167
  } else {
168
168
  string.clone()
169
169
  }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yerba
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marco Roth
@@ -63,6 +63,7 @@ files:
63
63
  - rust/src/commands/set.rs
64
64
  - rust/src/commands/sort.rs
65
65
  - rust/src/commands/sort_keys.rs
66
+ - rust/src/commands/ui.rs
66
67
  - rust/src/commands/unique.rs
67
68
  - rust/src/commands/version.rs
68
69
  - rust/src/didyoumean.rs