yerba 0.2.0-arm-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.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +528 -0
  4. data/exe/yerba +6 -0
  5. data/ext/yerba/extconf.rb +111 -0
  6. data/ext/yerba/yerba.c +752 -0
  7. data/lib/yerba/3.2/yerba.so +0 -0
  8. data/lib/yerba/3.3/yerba.so +0 -0
  9. data/lib/yerba/3.4/yerba.so +0 -0
  10. data/lib/yerba/4.0/yerba.so +0 -0
  11. data/lib/yerba/collection.rb +31 -0
  12. data/lib/yerba/document.rb +59 -0
  13. data/lib/yerba/formatting.rb +18 -0
  14. data/lib/yerba/location.rb +5 -0
  15. data/lib/yerba/map.rb +166 -0
  16. data/lib/yerba/scalar.rb +85 -0
  17. data/lib/yerba/sequence.rb +182 -0
  18. data/lib/yerba/version.rb +5 -0
  19. data/lib/yerba.rb +131 -0
  20. data/rust/Cargo.lock +805 -0
  21. data/rust/Cargo.toml +36 -0
  22. data/rust/build.rs +11 -0
  23. data/rust/cbindgen.toml +27 -0
  24. data/rust/rustfmt.toml +2 -0
  25. data/rust/src/commands/apply.rs +5 -0
  26. data/rust/src/commands/blank_lines.rs +58 -0
  27. data/rust/src/commands/check.rs +5 -0
  28. data/rust/src/commands/delete.rs +35 -0
  29. data/rust/src/commands/get.rs +194 -0
  30. data/rust/src/commands/init.rs +89 -0
  31. data/rust/src/commands/insert.rs +106 -0
  32. data/rust/src/commands/mate.rs +55 -0
  33. data/rust/src/commands/mod.rs +349 -0
  34. data/rust/src/commands/move_item.rs +54 -0
  35. data/rust/src/commands/move_key.rs +87 -0
  36. data/rust/src/commands/quote_style.rs +62 -0
  37. data/rust/src/commands/remove.rs +35 -0
  38. data/rust/src/commands/rename.rs +37 -0
  39. data/rust/src/commands/set.rs +59 -0
  40. data/rust/src/commands/sort.rs +52 -0
  41. data/rust/src/commands/sort_keys.rs +62 -0
  42. data/rust/src/commands/version.rs +8 -0
  43. data/rust/src/document.rs +2237 -0
  44. data/rust/src/error.rs +45 -0
  45. data/rust/src/ffi.rs +991 -0
  46. data/rust/src/json.rs +128 -0
  47. data/rust/src/lib.rs +29 -0
  48. data/rust/src/main.rs +72 -0
  49. data/rust/src/quote_style.rs +42 -0
  50. data/rust/src/selector.rs +241 -0
  51. data/rust/src/syntax.rs +262 -0
  52. data/rust/src/yaml_writer.rs +89 -0
  53. data/rust/src/yerbafile.rs +475 -0
  54. data/sig/yerba.rbs +3 -0
  55. data/yerba.gemspec +52 -0
  56. metadata +108 -0
data/rust/src/ffi.rs ADDED
@@ -0,0 +1,991 @@
1
+ //! FFI bindings for the Yerba C API.
2
+ //!
3
+ //! # Safety
4
+ //!
5
+ //! All functions in this module require that pointer arguments are either valid or null
6
+ //! (where documented as nullable). The caller is responsible for freeing returned pointers
7
+ //! using the corresponding `_free` functions.
8
+
9
+ #![allow(clippy::missing_safety_doc)]
10
+
11
+ use std::ffi::{CStr, CString};
12
+ use std::os::raw::c_char;
13
+ use std::ptr;
14
+
15
+ use yaml_parser::SyntaxKind;
16
+
17
+ use crate::selector::Selector;
18
+ use crate::syntax::{detect_yaml_type, YerbaValueType};
19
+ use crate::{Document, InsertPosition, QuoteStyle};
20
+
21
+ #[repr(C)]
22
+ pub struct YerbaResult {
23
+ pub success: bool,
24
+ pub error: *mut c_char,
25
+ }
26
+
27
+ impl YerbaResult {
28
+ fn ok() -> Self {
29
+ YerbaResult {
30
+ success: true,
31
+ error: ptr::null_mut(),
32
+ }
33
+ }
34
+
35
+ fn err(message: &str) -> Self {
36
+ YerbaResult {
37
+ success: false,
38
+ error: CString::new(message).unwrap_or_default().into_raw(),
39
+ }
40
+ }
41
+ }
42
+
43
+ #[repr(C)]
44
+ pub struct YerbaTypedValue {
45
+ pub text: *mut c_char,
46
+ pub value_type: YerbaValueType,
47
+ }
48
+
49
+ #[repr(C)]
50
+ pub struct YerbaTypedList {
51
+ pub json: *mut c_char,
52
+ pub length: usize,
53
+ }
54
+
55
+ #[repr(C)]
56
+ #[derive(Debug, Clone, Copy, PartialEq)]
57
+ pub enum YerbaNodeType {
58
+ Scalar = 0,
59
+ Map = 1,
60
+ Sequence = 2,
61
+ NotFound = 3,
62
+ }
63
+
64
+ #[repr(C)]
65
+ pub struct YerbaParseResult {
66
+ pub document: *mut Document,
67
+ pub error: *mut c_char,
68
+ }
69
+
70
+ #[repr(C)]
71
+ pub struct YerbaLocation {
72
+ pub start_offset: usize,
73
+ pub end_offset: usize,
74
+ pub start_line: usize,
75
+ pub start_column: usize,
76
+ pub end_line: usize,
77
+ pub end_column: usize,
78
+ }
79
+
80
+ #[repr(C)]
81
+ pub struct YerbaGetResult {
82
+ pub is_list: bool,
83
+ pub node_type: YerbaNodeType,
84
+ pub single: YerbaTypedValue,
85
+ pub list: YerbaTypedList,
86
+ pub location: YerbaLocation,
87
+ pub key_name: *mut c_char,
88
+ pub key_location: YerbaLocation,
89
+ pub error: *mut c_char,
90
+ }
91
+
92
+ #[no_mangle]
93
+ pub unsafe extern "C" fn yerba_document_parse_file(path: *const c_char) -> YerbaParseResult {
94
+ if path.is_null() {
95
+ return YerbaParseResult {
96
+ document: ptr::null_mut(),
97
+ error: CString::new("path is null").unwrap_or_default().into_raw(),
98
+ };
99
+ }
100
+
101
+ let path_string = match CStr::from_ptr(path).to_str() {
102
+ Ok(string) => string,
103
+ Err(e) => {
104
+ return YerbaParseResult {
105
+ document: ptr::null_mut(),
106
+ error: CString::new(format!("Invalid UTF-8 in path: {}", e))
107
+ .unwrap_or_default()
108
+ .into_raw(),
109
+ }
110
+ }
111
+ };
112
+
113
+ match Document::parse_file(path_string) {
114
+ Ok(document) => YerbaParseResult {
115
+ document: Box::into_raw(Box::new(document)),
116
+ error: ptr::null_mut(),
117
+ },
118
+
119
+ Err(e) => YerbaParseResult {
120
+ document: ptr::null_mut(),
121
+ error: CString::new(e.to_string()).unwrap_or_default().into_raw(),
122
+ },
123
+ }
124
+ }
125
+
126
+ #[no_mangle]
127
+ pub unsafe extern "C" fn yerba_document_parse(content: *const c_char) -> YerbaParseResult {
128
+ if content.is_null() {
129
+ return YerbaParseResult {
130
+ document: ptr::null_mut(),
131
+ error: CString::new("content is null").unwrap_or_default().into_raw(),
132
+ };
133
+ }
134
+
135
+ let content_string = match CStr::from_ptr(content).to_str() {
136
+ Ok(string) => string,
137
+ Err(e) => {
138
+ return YerbaParseResult {
139
+ document: ptr::null_mut(),
140
+ error: CString::new(format!("Invalid UTF-8: {}", e))
141
+ .unwrap_or_default()
142
+ .into_raw(),
143
+ }
144
+ }
145
+ };
146
+
147
+ match Document::parse(content_string) {
148
+ Ok(document) => YerbaParseResult {
149
+ document: Box::into_raw(Box::new(document)),
150
+ error: ptr::null_mut(),
151
+ },
152
+
153
+ Err(e) => YerbaParseResult {
154
+ document: ptr::null_mut(),
155
+ error: CString::new(e.to_string()).unwrap_or_default().into_raw(),
156
+ },
157
+ }
158
+ }
159
+
160
+ fn compute_location(source: &str, start_offset: usize, end_offset: usize) -> YerbaLocation {
161
+ let start = start_offset.min(source.len());
162
+ let end = end_offset.min(source.len());
163
+
164
+ let before_start = &source[..start];
165
+ let start_line = before_start.chars().filter(|c| *c == '\n').count() + 1;
166
+ let start_column = start - before_start.rfind('\n').map(|p| p + 1).unwrap_or(0);
167
+
168
+ let before_end = &source[..end];
169
+ let end_line = before_end.chars().filter(|c| *c == '\n').count() + 1;
170
+ let end_column = end - before_end.rfind('\n').map(|p| p + 1).unwrap_or(0);
171
+
172
+ YerbaLocation {
173
+ start_offset: start,
174
+ end_offset: end,
175
+ start_line,
176
+ start_column,
177
+ end_line,
178
+ end_column,
179
+ }
180
+ }
181
+
182
+ const EMPTY_LOCATION: YerbaLocation = YerbaLocation {
183
+ start_offset: 0,
184
+ end_offset: 0,
185
+ start_line: 0,
186
+ start_column: 0,
187
+ end_line: 0,
188
+ end_column: 0,
189
+ };
190
+
191
+ #[no_mangle]
192
+ pub unsafe extern "C" fn yerba_document_free(document: *mut Document) {
193
+ if !document.is_null() {
194
+ drop(Box::from_raw(document));
195
+ }
196
+ }
197
+
198
+ #[no_mangle]
199
+ pub unsafe extern "C" fn yerba_document_get(document: *const Document, path: *const c_char) -> YerbaGetResult {
200
+ let document = &*document;
201
+ let path_string = CStr::from_ptr(path).to_str().unwrap_or("");
202
+
203
+ let selector = Selector::parse(path_string);
204
+
205
+ if let Err(e) = Document::validate_path(path_string) {
206
+ return YerbaGetResult {
207
+ is_list: false,
208
+ node_type: YerbaNodeType::NotFound,
209
+ single: YerbaTypedValue {
210
+ text: ptr::null_mut(),
211
+ value_type: YerbaValueType::Null,
212
+ },
213
+ list: YerbaTypedList {
214
+ json: ptr::null_mut(),
215
+ length: 0,
216
+ },
217
+ key_name: ptr::null_mut(),
218
+ key_location: EMPTY_LOCATION,
219
+ location: EMPTY_LOCATION,
220
+ error: CString::new(e.to_string()).unwrap_or_default().into_raw(),
221
+ };
222
+ }
223
+
224
+ let source = document.to_string();
225
+
226
+ let (location, key_text, key_location) = match document.navigate(path_string) {
227
+ Ok(node) => {
228
+ let range = node.text_range();
229
+ let location = compute_location(&source, range.start().into(), range.end().into());
230
+
231
+ let (key_text, key_location) = node
232
+ .parent()
233
+ .and_then(|parent| {
234
+ use rowan::ast::AstNode;
235
+ use yaml_parser::ast::BlockMapEntry;
236
+
237
+ BlockMapEntry::cast(parent).and_then(|entry| {
238
+ entry.key().and_then(|key_node| {
239
+ let key_text = crate::syntax::extract_scalar_text(key_node.syntax())?;
240
+ let key_range = key_node.syntax().text_range();
241
+ let key_location = compute_location(&source, key_range.start().into(), key_range.end().into());
242
+
243
+ Some((key_text, key_location))
244
+ })
245
+ })
246
+ })
247
+ .map(|(name, location)| (Some(name), location))
248
+ .unwrap_or((None, EMPTY_LOCATION));
249
+
250
+ (location, key_text, key_location)
251
+ }
252
+ Err(_) => (EMPTY_LOCATION, None, EMPTY_LOCATION),
253
+ };
254
+
255
+ let key_name_pointer = key_text
256
+ .map(|name| CString::new(name).unwrap_or_default().into_raw())
257
+ .unwrap_or(ptr::null_mut());
258
+
259
+ if selector.has_wildcard() {
260
+ let values = document.get_all_typed(path_string);
261
+
262
+ let typed: Vec<serde_json::Value> = values
263
+ .iter()
264
+ .map(|serde_value| {
265
+ serde_json::json!({
266
+ "text": serde_value.text,
267
+ "type": detect_yaml_type(serde_value) as u8
268
+ })
269
+ })
270
+ .collect();
271
+
272
+ let json = serde_json::to_string(&typed).unwrap_or_else(|_| "[]".to_string());
273
+ let length = values.len();
274
+
275
+ YerbaGetResult {
276
+ is_list: true,
277
+ node_type: YerbaNodeType::Sequence,
278
+ single: YerbaTypedValue {
279
+ text: ptr::null_mut(),
280
+ value_type: YerbaValueType::Null,
281
+ },
282
+ list: YerbaTypedList {
283
+ json: CString::new(json).unwrap_or_default().into_raw(),
284
+ length,
285
+ },
286
+ location,
287
+ key_name: key_name_pointer,
288
+ key_location,
289
+ error: ptr::null_mut(),
290
+ }
291
+ } else {
292
+ match document.get_typed(path_string) {
293
+ Some(scalar) => {
294
+ let vtype = detect_yaml_type(&scalar);
295
+
296
+ YerbaGetResult {
297
+ is_list: false,
298
+ node_type: YerbaNodeType::Scalar,
299
+ single: YerbaTypedValue {
300
+ text: CString::new(scalar.text).unwrap_or_default().into_raw(),
301
+ value_type: vtype,
302
+ },
303
+ list: YerbaTypedList {
304
+ json: ptr::null_mut(),
305
+ length: 0,
306
+ },
307
+ location,
308
+ key_name: key_name_pointer,
309
+ key_location,
310
+ error: ptr::null_mut(),
311
+ }
312
+ }
313
+
314
+ None => {
315
+ use rowan::ast::AstNode;
316
+ use yaml_parser::ast::{BlockMap, BlockSeq};
317
+
318
+ let node_type = match document.navigate(path_string) {
319
+ Ok(node) => {
320
+ if node.children().any(|child| BlockSeq::can_cast(child.kind())) {
321
+ YerbaNodeType::Sequence
322
+ } else if node.children().any(|child| BlockMap::can_cast(child.kind())) {
323
+ YerbaNodeType::Map
324
+ } else if node.descendants().any(|child| BlockSeq::can_cast(child.kind())) {
325
+ YerbaNodeType::Sequence
326
+ } else if node.descendants().any(|child| BlockMap::can_cast(child.kind())) {
327
+ YerbaNodeType::Map
328
+ } else {
329
+ YerbaNodeType::NotFound
330
+ }
331
+ }
332
+ Err(_) => YerbaNodeType::NotFound,
333
+ };
334
+
335
+ YerbaGetResult {
336
+ is_list: false,
337
+ node_type,
338
+ single: YerbaTypedValue {
339
+ text: ptr::null_mut(),
340
+ value_type: YerbaValueType::Null,
341
+ },
342
+ list: YerbaTypedList {
343
+ json: ptr::null_mut(),
344
+ length: 0,
345
+ },
346
+ location,
347
+ key_name: key_name_pointer,
348
+ key_location,
349
+ error: ptr::null_mut(),
350
+ }
351
+ }
352
+ }
353
+ }
354
+ }
355
+
356
+ /// Caller must free with yerba_string_free.
357
+ #[no_mangle]
358
+ pub unsafe extern "C" fn yerba_document_get_value(document: *const Document, path: *const c_char) -> *mut c_char {
359
+ let document = &*document;
360
+ let path_string = CStr::from_ptr(path).to_str().unwrap_or("");
361
+
362
+ match document.get_value(path_string) {
363
+ Some(value) => {
364
+ let json = crate::json::yaml_to_json(&value);
365
+ let json_string = serde_json::to_string(&json).unwrap_or_else(|_| "null".to_string());
366
+ CString::new(json_string).unwrap_or_default().into_raw()
367
+ }
368
+ None => ptr::null_mut(),
369
+ }
370
+ }
371
+
372
+ /// Caller must free with yerba_string_free.
373
+ #[no_mangle]
374
+ pub unsafe extern "C" fn yerba_document_get_values(document: *const Document, path: *const c_char) -> *mut c_char {
375
+ let document = &*document;
376
+ let path_string = CStr::from_ptr(path).to_str().unwrap_or("");
377
+
378
+ let values = document.get_values(path_string);
379
+ let json_values: Vec<serde_json::Value> = values.iter().map(crate::json::yaml_to_json).collect();
380
+ let json_string = serde_json::to_string(&json_values).unwrap_or_else(|_| "[]".to_string());
381
+
382
+ CString::new(json_string).unwrap_or_default().into_raw()
383
+ }
384
+
385
+ #[no_mangle]
386
+ pub unsafe extern "C" fn yerba_document_get_quote_style(document: *const Document, path: *const c_char) -> i32 {
387
+ let document = &*document;
388
+ let path_string = CStr::from_ptr(path).to_str().unwrap_or("");
389
+
390
+ match document.get_typed(path_string) {
391
+ Some(scalar) => match scalar.kind {
392
+ SyntaxKind::PLAIN_SCALAR => 0,
393
+ SyntaxKind::SINGLE_QUOTED_SCALAR => 1,
394
+ SyntaxKind::DOUBLE_QUOTED_SCALAR => 2,
395
+ _ => -1,
396
+ },
397
+
398
+ None => -1,
399
+ }
400
+ }
401
+
402
+ #[no_mangle]
403
+ pub unsafe extern "C" fn yerba_document_set_quote_style(
404
+ document: *mut Document,
405
+ path: *const c_char,
406
+ style: i32,
407
+ ) -> YerbaResult {
408
+ let document = &mut *document;
409
+ let path_string = CStr::from_ptr(path).to_str().unwrap_or("");
410
+
411
+ let quote_style = match style {
412
+ 0 => QuoteStyle::Plain,
413
+ 1 => QuoteStyle::Single,
414
+ 2 => QuoteStyle::Double,
415
+ _ => return YerbaResult::err("Invalid quote style (use 0=plain, 1=single, 2=double)"),
416
+ };
417
+
418
+ match document.set_scalar_style(path_string, &quote_style) {
419
+ Ok(()) => YerbaResult::ok(),
420
+ Err(e) => YerbaResult::err(&e.to_string()),
421
+ }
422
+ }
423
+
424
+ #[no_mangle]
425
+ pub unsafe extern "C" fn yerba_document_evaluate_condition(
426
+ document: *const Document,
427
+ parent_path: *const c_char,
428
+ condition: *const c_char,
429
+ ) -> bool {
430
+ let document = &*document;
431
+ let parent = CStr::from_ptr(parent_path).to_str().unwrap_or("");
432
+ let cond = CStr::from_ptr(condition).to_str().unwrap_or("");
433
+ document.evaluate_condition(parent, cond)
434
+ }
435
+
436
+ #[no_mangle]
437
+ pub unsafe extern "C" fn yerba_document_exists(document: *const Document, path: *const c_char) -> bool {
438
+ let document = &*document;
439
+ let path_string = CStr::from_ptr(path).to_str().unwrap_or("");
440
+ document.exists(path_string)
441
+ }
442
+
443
+ #[no_mangle]
444
+ pub unsafe extern "C" fn yerba_document_find(
445
+ document: *const Document,
446
+ path: *const c_char,
447
+ condition: *const c_char,
448
+ select: *const c_char,
449
+ ) -> *mut c_char {
450
+ let document = &*document;
451
+ let path_string = CStr::from_ptr(path).to_str().unwrap_or("");
452
+
453
+ let condition_str = if condition.is_null() {
454
+ None
455
+ } else {
456
+ CStr::from_ptr(condition).to_str().ok()
457
+ };
458
+
459
+ let _select_string = if select.is_null() {
460
+ None
461
+ } else {
462
+ CStr::from_ptr(select).to_str().ok()
463
+ };
464
+
465
+ let values = match condition_str {
466
+ Some(cond) => document.filter(path_string, cond),
467
+ None => document.get_values(path_string),
468
+ };
469
+
470
+ let select_fields: Option<Vec<&str>> = _select_string.map(|s| s.split(',').collect());
471
+
472
+ let mut results: Vec<serde_json::Value> = Vec::new();
473
+
474
+ for value in &values {
475
+ match &select_fields {
476
+ Some(fields) => {
477
+ let mut result = serde_json::Map::new();
478
+
479
+ for field in fields {
480
+ let json_value = crate::json::resolve_select_field(value, field);
481
+ let json_key = crate::json::select_field_key(field);
482
+
483
+ result.insert(json_key, json_value);
484
+ }
485
+
486
+ results.push(serde_json::Value::Object(result));
487
+ }
488
+ None => {
489
+ results.push(crate::json::yaml_to_json(value));
490
+ }
491
+ }
492
+ }
493
+
494
+ let json = serde_json::to_string_pretty(&results).unwrap_or_else(|_| "[]".to_string());
495
+ CString::new(json).unwrap_or_default().into_raw()
496
+ }
497
+
498
+ #[no_mangle]
499
+ pub unsafe extern "C" fn yerba_document_set(
500
+ document: *mut Document,
501
+ path: *const c_char,
502
+ value: *const c_char,
503
+ value_type: YerbaValueType,
504
+ ) -> YerbaResult {
505
+ let document = &mut *document;
506
+ let path_string = CStr::from_ptr(path).to_str().unwrap_or("");
507
+ let value_string = CStr::from_ptr(value).to_str().unwrap_or("");
508
+
509
+ let result = match value_type {
510
+ YerbaValueType::String => document.set(path_string, value_string),
511
+ _ => document.set_plain(path_string, value_string),
512
+ };
513
+
514
+ match result {
515
+ Ok(()) => YerbaResult::ok(),
516
+ Err(e) => YerbaResult::err(&e.to_string()),
517
+ }
518
+ }
519
+
520
+ #[no_mangle]
521
+ pub unsafe extern "C" fn yerba_document_insert(
522
+ document: *mut Document,
523
+ path: *const c_char,
524
+ value: *const c_char,
525
+ before: *const c_char,
526
+ after: *const c_char,
527
+ at: i64,
528
+ ) -> YerbaResult {
529
+ let document = &mut *document;
530
+ let path_string = CStr::from_ptr(path).to_str().unwrap_or("");
531
+ let value_string = CStr::from_ptr(value).to_str().unwrap_or("");
532
+
533
+ let position = if at >= 0 {
534
+ InsertPosition::At(at as usize)
535
+ } else if !before.is_null() {
536
+ let target = CStr::from_ptr(before).to_str().unwrap_or("");
537
+ InsertPosition::Before(target.to_string())
538
+ } else if !after.is_null() {
539
+ let target = CStr::from_ptr(after).to_str().unwrap_or("");
540
+ InsertPosition::After(target.to_string())
541
+ } else {
542
+ InsertPosition::Last
543
+ };
544
+
545
+ match document.insert_into(path_string, value_string, position) {
546
+ Ok(()) => YerbaResult::ok(),
547
+ Err(e) => YerbaResult::err(&e.to_string()),
548
+ }
549
+ }
550
+
551
+ #[no_mangle]
552
+ pub unsafe extern "C" fn yerba_document_insert_object(
553
+ document: *mut Document,
554
+ path: *const c_char,
555
+ json: *const c_char,
556
+ before: *const c_char,
557
+ after: *const c_char,
558
+ at: i64,
559
+ ) -> YerbaResult {
560
+ let document = &mut *document;
561
+ let path_string = CStr::from_ptr(path).to_str().unwrap_or("");
562
+ let json_string = CStr::from_ptr(json).to_str().unwrap_or("");
563
+
564
+ let json_value: serde_json::Value = match serde_json::from_str(json_string) {
565
+ Ok(v) => v,
566
+ Err(e) => return YerbaResult::err(&format!("Invalid JSON: {}", e)),
567
+ };
568
+
569
+ let try_paths = if path_string.is_empty() {
570
+ vec!["[]".to_string(), "[0]".to_string()]
571
+ } else {
572
+ vec![format!("{}[]", path_string), format!("{}[0]", path_string)]
573
+ };
574
+
575
+ let mut quote_style = QuoteStyle::Plain;
576
+
577
+ 'outer: for try_path in &try_paths {
578
+ for scalar in document.get_all_typed(try_path) {
579
+ if scalar.kind == SyntaxKind::DOUBLE_QUOTED_SCALAR {
580
+ quote_style = QuoteStyle::Double;
581
+ break 'outer;
582
+ } else if scalar.kind == SyntaxKind::SINGLE_QUOTED_SCALAR {
583
+ quote_style = QuoteStyle::Single;
584
+ break 'outer;
585
+ }
586
+ }
587
+ }
588
+
589
+ if quote_style == QuoteStyle::Plain {
590
+ if let Some(serde_yaml::Value::Sequence(seq)) = document.get_value(path_string).as_ref() {
591
+ if let Some(serde_yaml::Value::Mapping(map)) = seq.first() {
592
+ if let Some((serde_yaml::Value::String(key_name), _)) = map.iter().next() {
593
+ let deep_path = if path_string.is_empty() {
594
+ format!("[].{}", key_name)
595
+ } else {
596
+ format!("{}[].{}", path_string, key_name)
597
+ };
598
+
599
+ for scalar in document.get_all_typed(&deep_path) {
600
+ if scalar.kind == SyntaxKind::DOUBLE_QUOTED_SCALAR {
601
+ quote_style = QuoteStyle::Double;
602
+ break;
603
+ } else if scalar.kind == SyntaxKind::SINGLE_QUOTED_SCALAR {
604
+ quote_style = QuoteStyle::Single;
605
+ break;
606
+ }
607
+ }
608
+ }
609
+ }
610
+ }
611
+ }
612
+
613
+ let yaml_text = crate::yaml_writer::json_to_yaml_text(&json_value, &quote_style, 0);
614
+
615
+ let position = if at >= 0 {
616
+ InsertPosition::At(at as usize)
617
+ } else if !before.is_null() {
618
+ let target = CStr::from_ptr(before).to_str().unwrap_or("");
619
+ InsertPosition::Before(target.to_string())
620
+ } else if !after.is_null() {
621
+ let target = CStr::from_ptr(after).to_str().unwrap_or("");
622
+ InsertPosition::After(target.to_string())
623
+ } else {
624
+ InsertPosition::Last
625
+ };
626
+
627
+ match document.insert_into(path_string, &yaml_text, position) {
628
+ Ok(()) => YerbaResult::ok(),
629
+ Err(e) => YerbaResult::err(&e.to_string()),
630
+ }
631
+ }
632
+
633
+ #[no_mangle]
634
+ pub unsafe extern "C" fn yerba_document_delete(document: *mut Document, path: *const c_char) -> YerbaResult {
635
+ let document = &mut *document;
636
+ let path_string = CStr::from_ptr(path).to_str().unwrap_or("");
637
+
638
+ match document.delete(path_string) {
639
+ Ok(()) => YerbaResult::ok(),
640
+ Err(e) => YerbaResult::err(&e.to_string()),
641
+ }
642
+ }
643
+
644
+ #[no_mangle]
645
+ pub unsafe extern "C" fn yerba_document_remove(
646
+ document: *mut Document,
647
+ path: *const c_char,
648
+ value: *const c_char,
649
+ ) -> YerbaResult {
650
+ let document = &mut *document;
651
+ let path_string = CStr::from_ptr(path).to_str().unwrap_or("");
652
+ let value_string = CStr::from_ptr(value).to_str().unwrap_or("");
653
+
654
+ match document.remove(path_string, value_string) {
655
+ Ok(()) => YerbaResult::ok(),
656
+ Err(e) => YerbaResult::err(&e.to_string()),
657
+ }
658
+ }
659
+
660
+ #[no_mangle]
661
+ pub unsafe extern "C" fn yerba_document_remove_at(
662
+ document: *mut Document,
663
+ path: *const c_char,
664
+ index: usize,
665
+ ) -> YerbaResult {
666
+ let document = &mut *document;
667
+ let path_string = CStr::from_ptr(path).to_str().unwrap_or("");
668
+
669
+ match document.remove_at(path_string, index) {
670
+ Ok(()) => YerbaResult::ok(),
671
+ Err(e) => YerbaResult::err(&e.to_string()),
672
+ }
673
+ }
674
+
675
+ #[no_mangle]
676
+ pub unsafe extern "C" fn yerba_document_rename(
677
+ document: *mut Document,
678
+ source: *const c_char,
679
+ dest: *const c_char,
680
+ ) -> YerbaResult {
681
+ let document = &mut *document;
682
+ let source_string = CStr::from_ptr(source).to_str().unwrap_or("");
683
+ let dest_string = CStr::from_ptr(dest).to_str().unwrap_or("");
684
+
685
+ match document.rename(source_string, dest_string) {
686
+ Ok(()) => YerbaResult::ok(),
687
+ Err(e) => YerbaResult::err(&e.to_string()),
688
+ }
689
+ }
690
+
691
+ #[no_mangle]
692
+ pub unsafe extern "C" fn yerba_document_sort(
693
+ document: *mut Document,
694
+ path: *const c_char,
695
+ by: *const c_char,
696
+ case_sensitive: bool,
697
+ ) -> YerbaResult {
698
+ let document = &mut *document;
699
+ let path_string = CStr::from_ptr(path).to_str().unwrap_or("");
700
+
701
+ let by_string = if by.is_null() {
702
+ None
703
+ } else {
704
+ CStr::from_ptr(by).to_str().ok()
705
+ };
706
+
707
+ let sort_fields: Vec<crate::SortField> = match by_string {
708
+ Some(fields) => fields
709
+ .split(',')
710
+ .map(|field| {
711
+ if let Some(name) = field.strip_suffix(":desc") {
712
+ crate::SortField {
713
+ path: name.to_string(),
714
+ ascending: false,
715
+ }
716
+ } else {
717
+ crate::SortField {
718
+ path: field.to_string(),
719
+ ascending: true,
720
+ }
721
+ }
722
+ })
723
+ .collect(),
724
+ None => vec![],
725
+ };
726
+
727
+ match document.sort_items(path_string, &sort_fields, case_sensitive) {
728
+ Ok(()) => YerbaResult::ok(),
729
+ Err(e) => YerbaResult::err(&e.to_string()),
730
+ }
731
+ }
732
+
733
+ #[no_mangle]
734
+ pub unsafe extern "C" fn yerba_document_sort_keys(
735
+ document: *mut Document,
736
+ path: *const c_char,
737
+ order: *const c_char,
738
+ ) -> YerbaResult {
739
+ let document = &mut *document;
740
+ let path_string = CStr::from_ptr(path).to_str().unwrap_or("");
741
+ let order_string = CStr::from_ptr(order).to_str().unwrap_or("");
742
+ let key_order: Vec<&str> = order_string.split(',').collect();
743
+
744
+ match document.sort_keys(path_string, &key_order) {
745
+ Ok(()) => YerbaResult::ok(),
746
+ Err(e) => YerbaResult::err(&e.to_string()),
747
+ }
748
+ }
749
+
750
+ #[no_mangle]
751
+ pub unsafe extern "C" fn yerba_document_quote_style(
752
+ document: *mut Document,
753
+ path: *const c_char,
754
+ key_style: *const c_char,
755
+ value_style: *const c_char,
756
+ ) -> YerbaResult {
757
+ let document = &mut *document;
758
+
759
+ let path_string = if path.is_null() {
760
+ None
761
+ } else {
762
+ CStr::from_ptr(path).to_str().ok()
763
+ };
764
+
765
+ let key_quote_style = if key_style.is_null() {
766
+ None
767
+ } else {
768
+ CStr::from_ptr(key_style)
769
+ .to_str()
770
+ .ok()
771
+ .and_then(|s| s.parse::<QuoteStyle>().ok())
772
+ };
773
+
774
+ let value_quote_style = if value_style.is_null() {
775
+ None
776
+ } else {
777
+ CStr::from_ptr(value_style)
778
+ .to_str()
779
+ .ok()
780
+ .and_then(|s| s.parse::<QuoteStyle>().ok())
781
+ };
782
+
783
+ if let Some(ref key_style) = key_quote_style {
784
+ if let Err(e) = document.enforce_key_style(key_style, path_string) {
785
+ return YerbaResult::err(&e.to_string());
786
+ }
787
+ }
788
+
789
+ if let Some(ref value_style) = value_quote_style {
790
+ if let Err(e) = document.enforce_quotes_at(value_style, path_string) {
791
+ return YerbaResult::err(&e.to_string());
792
+ }
793
+ }
794
+
795
+ YerbaResult::ok()
796
+ }
797
+
798
+ #[no_mangle]
799
+ pub unsafe extern "C" fn yerba_document_blank_lines(
800
+ document: *mut Document,
801
+ path: *const c_char,
802
+ count: usize,
803
+ ) -> YerbaResult {
804
+ let document = &mut *document;
805
+ let path_string = CStr::from_ptr(path).to_str().unwrap_or("");
806
+
807
+ match document.enforce_blank_lines(path_string, count) {
808
+ Ok(()) => YerbaResult::ok(),
809
+ Err(e) => YerbaResult::err(&e.to_string()),
810
+ }
811
+ }
812
+
813
+ #[no_mangle]
814
+ pub unsafe extern "C" fn yerba_document_to_string(document: *const Document) -> *mut c_char {
815
+ let document = &*document;
816
+ let content = document.to_string();
817
+
818
+ CString::new(content).unwrap_or_default().into_raw()
819
+ }
820
+
821
+ #[no_mangle]
822
+ pub unsafe extern "C" fn yerba_string_free(s: *mut c_char) {
823
+ if !s.is_null() {
824
+ drop(CString::from_raw(s));
825
+ }
826
+ }
827
+
828
+ #[no_mangle]
829
+ pub unsafe extern "C" fn yerba_result_free(result: YerbaResult) {
830
+ if !result.error.is_null() {
831
+ drop(CString::from_raw(result.error));
832
+ }
833
+ }
834
+
835
+ #[no_mangle]
836
+ pub unsafe extern "C" fn yerba_get_result_free(result: YerbaGetResult) {
837
+ if !result.single.text.is_null() {
838
+ drop(CString::from_raw(result.single.text));
839
+ }
840
+
841
+ if !result.list.json.is_null() {
842
+ drop(CString::from_raw(result.list.json));
843
+ }
844
+
845
+ if !result.key_name.is_null() {
846
+ drop(CString::from_raw(result.key_name));
847
+ }
848
+ }
849
+
850
+ #[no_mangle]
851
+ pub unsafe extern "C" fn yerba_glob_get(glob_pattern: *const c_char, path: *const c_char) -> YerbaTypedList {
852
+ let pattern = CStr::from_ptr(glob_pattern).to_str().unwrap_or("");
853
+ let path_string = CStr::from_ptr(path).to_str().unwrap_or("");
854
+ let selector = Selector::parse(path_string);
855
+
856
+ let files = match glob::glob(pattern) {
857
+ Ok(paths) => paths.filter_map(|p| p.ok()).collect::<Vec<_>>(),
858
+ Err(_) => {
859
+ return YerbaTypedList {
860
+ json: CString::new("[]").unwrap_or_default().into_raw(),
861
+ length: 0,
862
+ }
863
+ }
864
+ };
865
+
866
+ use rayon::prelude::*;
867
+
868
+ let results: Vec<serde_json::Value> = files
869
+ .par_iter()
870
+ .flat_map(|file| {
871
+ let mut file_results = Vec::new();
872
+
873
+ if let Ok(document) = Document::parse_file(file) {
874
+ if selector.has_wildcard() {
875
+ for scalar in document.get_all_typed(path_string) {
876
+ let value_type = detect_yaml_type(&scalar);
877
+
878
+ file_results.push(serde_json::json!({"text": scalar.text, "type": value_type as u8}));
879
+ }
880
+ } else if let Some(scalar) = document.get_typed(path_string) {
881
+ let value_type = detect_yaml_type(&scalar);
882
+
883
+ file_results.push(serde_json::json!({"text": scalar.text, "type": value_type as u8}));
884
+ }
885
+ }
886
+
887
+ file_results
888
+ })
889
+ .collect();
890
+
891
+ let length = results.len();
892
+ let json = serde_json::to_string(&results).unwrap_or_else(|_| "[]".to_string());
893
+
894
+ YerbaTypedList {
895
+ json: CString::new(json).unwrap_or_default().into_raw(),
896
+ length,
897
+ }
898
+ }
899
+
900
+ #[no_mangle]
901
+ pub unsafe extern "C" fn yerba_glob_find(
902
+ glob_pattern: *const c_char,
903
+ path: *const c_char,
904
+ condition: *const c_char,
905
+ select: *const c_char,
906
+ ) -> YerbaTypedList {
907
+ let pattern = CStr::from_ptr(glob_pattern).to_str().unwrap_or("");
908
+ let path_string = CStr::from_ptr(path).to_str().unwrap_or("");
909
+
910
+ let condition_string = if condition.is_null() {
911
+ None
912
+ } else {
913
+ CStr::from_ptr(condition).to_str().ok()
914
+ };
915
+
916
+ let _select_string = if select.is_null() {
917
+ None
918
+ } else {
919
+ CStr::from_ptr(select).to_str().ok()
920
+ };
921
+
922
+ let files = match glob::glob(pattern) {
923
+ Ok(paths) => paths.filter_map(|p| p.ok()).collect::<Vec<_>>(),
924
+ Err(_) => {
925
+ return YerbaTypedList {
926
+ json: CString::new("[]").unwrap_or_default().into_raw(),
927
+ length: 0,
928
+ }
929
+ }
930
+ };
931
+
932
+ use rayon::prelude::*;
933
+
934
+ let select_fields: Option<Vec<&str>> = _select_string.map(|s| s.split(',').collect());
935
+
936
+ let all_results: Vec<serde_json::Value> = files
937
+ .par_iter()
938
+ .flat_map(|file| {
939
+ let mut file_results = Vec::new();
940
+
941
+ if let Ok(document) = Document::parse_file(file) {
942
+ let values = match condition_string {
943
+ Some(cond) => document.filter(path_string, cond),
944
+ None => document.get_values(path_string),
945
+ };
946
+
947
+ let file_string = file.to_string_lossy().to_string();
948
+
949
+ for value in &values {
950
+ let mut result = serde_json::Map::new();
951
+ result.insert("__file".to_string(), serde_json::Value::String(file_string.clone()));
952
+
953
+ match &select_fields {
954
+ Some(fields) => {
955
+ for field in fields {
956
+ let json_value = crate::json::resolve_select_field(value, field);
957
+ let json_key = crate::json::select_field_key(field);
958
+ result.insert(json_key, json_value);
959
+ }
960
+ }
961
+
962
+ None => {
963
+ if let serde_yaml::Value::Mapping(map) = value {
964
+ for (key, yaml_value) in map {
965
+ let json_key = match key {
966
+ serde_yaml::Value::String(string) => string.clone(),
967
+ _ => format!("{:?}", key),
968
+ };
969
+
970
+ result.insert(json_key, crate::json::yaml_to_json(yaml_value));
971
+ }
972
+ }
973
+ }
974
+ }
975
+
976
+ file_results.push(serde_json::Value::Object(result));
977
+ }
978
+ }
979
+
980
+ file_results
981
+ })
982
+ .collect();
983
+
984
+ let length = all_results.len();
985
+ let json = serde_json::to_string_pretty(&all_results).unwrap_or_else(|_| "[]".to_string());
986
+
987
+ YerbaTypedList {
988
+ json: CString::new(json).unwrap_or_default().into_raw(),
989
+ length,
990
+ }
991
+ }