Skip to main content

pick/
lib.rs

1pub mod cli;
2pub mod detector;
3pub mod error;
4pub mod formats;
5pub mod output;
6pub mod selector;
7pub mod streaming;
8
9use cli::{Cli, InputFormat};
10use error::PickError;
11use selector::{Expression, execute};
12use serde_json::Value;
13
14pub fn run(cli: &Cli, input: &str) -> Result<String, PickError> {
15    if input.trim().is_empty() {
16        return Err(PickError::NoInput);
17    }
18
19    let selector_str = cli.selector.as_deref().unwrap_or("");
20    let expression = Expression::parse(selector_str)?;
21
22    // Determine format
23    let format = match cli.input {
24        InputFormat::Auto => detector::detect_format(input),
25        ref f => f.clone(),
26    };
27
28    // Parse and extract
29    let results = match parse_and_execute(input, &format, &expression, selector_str) {
30        Ok(r) => r,
31        Err(e) => {
32            if let Some(ref default) = cli.default {
33                return Ok(default.clone());
34            }
35            return Err(e);
36        }
37    };
38
39    // Handle empty results with --default
40    if results.is_empty() {
41        if let Some(ref default) = cli.default {
42            return Ok(default.clone());
43        }
44        return Err(PickError::KeyNotFound(selector_str.to_string()));
45    }
46
47    // --exists: just check, output nothing
48    if cli.exists {
49        return Ok(String::new());
50    }
51
52    // --count: output match count
53    if cli.count {
54        return Ok(results.len().to_string());
55    }
56
57    // --first: only first result
58    let results = if cli.first {
59        vec![results.into_iter().next().unwrap()]
60    } else {
61        results
62    };
63
64    Ok(output::format_output(&results, cli.json, cli.lines, &cli.output))
65}
66
67fn parse_and_execute(
68    input: &str,
69    format: &InputFormat,
70    expression: &Expression,
71    selector_str: &str,
72) -> Result<Vec<Value>, PickError> {
73    // Text format has a special fallback path
74    if *format == InputFormat::Text {
75        return parse_and_extract_text(input, expression, selector_str);
76    }
77
78    let value = parse_input(input, format)?;
79    execute(&value, expression)
80}
81
82fn parse_and_extract_text(
83    input: &str,
84    expression: &Expression,
85    selector_str: &str,
86) -> Result<Vec<Value>, PickError> {
87    let value = formats::text::parse(input)?;
88
89    // Try normal extraction first
90    if let Ok(results) = execute(&value, expression)
91        && !results.is_empty()
92    {
93        return Ok(results);
94    }
95
96    // Fallback: search for the full selector string in the text
97    if !selector_str.is_empty()
98        && let Some(found) = formats::text::search_text(input, selector_str)
99    {
100        return Ok(vec![found]);
101    }
102
103    Err(PickError::KeyNotFound(selector_str.to_string()))
104}
105
106fn parse_input(input: &str, format: &InputFormat) -> Result<Value, PickError> {
107    match format {
108        InputFormat::Json => formats::json::parse(input),
109        InputFormat::Yaml => formats::yaml::parse(input),
110        InputFormat::Toml => formats::toml_format::parse(input),
111        InputFormat::Env => formats::env::parse(input),
112        InputFormat::Headers => formats::headers::parse(input),
113        InputFormat::Logfmt => formats::logfmt::parse(input),
114        InputFormat::Csv => formats::csv_format::parse(input),
115        InputFormat::Text => formats::text::parse(input),
116        InputFormat::Auto => {
117            // Should not reach here; detect_format handles this
118            Err(PickError::UnknownFormat)
119        }
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126    use cli::OutputFormat;
127
128    fn make_cli(selector: Option<&str>) -> Cli {
129        Cli {
130            selector: selector.map(String::from),
131            input: InputFormat::Auto,
132            output: OutputFormat::Auto,
133            file: None,
134            json: false,
135            raw: false,
136            first: false,
137            lines: false,
138            default: None,
139            quiet: false,
140            exists: false,
141            count: false,
142            stream: false,
143        }
144    }
145
146    #[test]
147    fn run_json_simple() {
148        let cli = make_cli(Some("name"));
149        let result = run(&cli, r#"{"name": "Alice"}"#).unwrap();
150        assert_eq!(result, "Alice");
151    }
152
153    #[test]
154    fn run_json_nested() {
155        let cli = make_cli(Some("user.email"));
156        let result = run(&cli, r#"{"user": {"email": "a@b.com"}}"#).unwrap();
157        assert_eq!(result, "a@b.com");
158    }
159
160    #[test]
161    fn run_json_array_index() {
162        let cli = make_cli(Some("items[0]"));
163        let result = run(&cli, r#"{"items": ["first", "second"]}"#).unwrap();
164        assert_eq!(result, "first");
165    }
166
167    #[test]
168    fn run_json_wildcard() {
169        let cli = make_cli(Some("items[*].name"));
170        let result = run(&cli, r#"{"items": [{"name": "a"}, {"name": "b"}]}"#).unwrap();
171        assert_eq!(result, "a\nb");
172    }
173
174    #[test]
175    fn run_yaml() {
176        let mut cli = make_cli(Some("name"));
177        cli.input = InputFormat::Yaml;
178        let result = run(&cli, "name: Alice\nage: 30").unwrap();
179        assert_eq!(result, "Alice");
180    }
181
182    #[test]
183    fn run_toml() {
184        let mut cli = make_cli(Some("package.name"));
185        cli.input = InputFormat::Toml;
186        let result = run(&cli, "[package]\nname = \"pick\"").unwrap();
187        assert_eq!(result, "pick");
188    }
189
190    #[test]
191    fn run_env() {
192        let mut cli = make_cli(Some("PORT"));
193        cli.input = InputFormat::Env;
194        let result = run(&cli, "DATABASE_URL=pg://localhost\nPORT=3000").unwrap();
195        assert_eq!(result, "3000");
196    }
197
198    #[test]
199    fn run_headers() {
200        let mut cli = make_cli(Some("content-type"));
201        cli.input = InputFormat::Headers;
202        let result =
203            run(&cli, "Content-Type: application/json\nX-Request-Id: abc").unwrap();
204        assert_eq!(result, "application/json");
205    }
206
207    #[test]
208    fn run_logfmt() {
209        let mut cli = make_cli(Some("msg"));
210        cli.input = InputFormat::Logfmt;
211        let result = run(&cli, "level=info msg=hello status=200").unwrap();
212        assert_eq!(result, "hello");
213    }
214
215    #[test]
216    fn run_csv() {
217        let mut cli = make_cli(Some("[0].name"));
218        cli.input = InputFormat::Csv;
219        let result = run(&cli, "name,age\nAlice,30\nBob,25").unwrap();
220        assert_eq!(result, "Alice");
221    }
222
223    #[test]
224    fn run_no_selector_returns_whole() {
225        let cli = make_cli(None);
226        let result = run(&cli, r#"{"a": 1}"#).unwrap();
227        assert!(result.contains("\"a\""));
228    }
229
230    #[test]
231    fn run_empty_input() {
232        let cli = make_cli(Some("x"));
233        assert!(run(&cli, "").is_err());
234        assert!(run(&cli, "   ").is_err());
235    }
236
237    #[test]
238    fn run_key_not_found() {
239        let cli = make_cli(Some("missing"));
240        assert!(run(&cli, r#"{"a": 1}"#).is_err());
241    }
242
243    #[test]
244    fn run_default_on_missing() {
245        let mut cli = make_cli(Some("missing"));
246        cli.default = Some("fallback".into());
247        let result = run(&cli, r#"{"a": 1}"#).unwrap();
248        assert_eq!(result, "fallback");
249    }
250
251    #[test]
252    fn run_exists_found() {
253        let mut cli = make_cli(Some("a"));
254        cli.exists = true;
255        let result = run(&cli, r#"{"a": 1}"#).unwrap();
256        assert_eq!(result, "");
257    }
258
259    #[test]
260    fn run_exists_not_found() {
261        let mut cli = make_cli(Some("b"));
262        cli.exists = true;
263        assert!(run(&cli, r#"{"a": 1}"#).is_err());
264    }
265
266    #[test]
267    fn run_count() {
268        let mut cli = make_cli(Some("items[*]"));
269        cli.count = true;
270        let result = run(&cli, r#"{"items": [1, 2, 3]}"#).unwrap();
271        assert_eq!(result, "3");
272    }
273
274    #[test]
275    fn run_first() {
276        let mut cli = make_cli(Some("items[*]"));
277        cli.first = true;
278        let result = run(&cli, r#"{"items": [1, 2, 3]}"#).unwrap();
279        assert_eq!(result, "1");
280    }
281
282    #[test]
283    fn run_json_output() {
284        let mut cli = make_cli(Some("name"));
285        cli.json = true;
286        let result = run(&cli, r#"{"name": "Alice"}"#).unwrap();
287        assert_eq!(result, "\"Alice\"");
288    }
289
290    #[test]
291    fn run_lines_output() {
292        let mut cli = make_cli(Some("items"));
293        cli.lines = true;
294        let result = run(&cli, r#"{"items": ["a", "b", "c"]}"#).unwrap();
295        assert_eq!(result, "a\nb\nc");
296    }
297
298    #[test]
299    fn run_text_kv_fallback() {
300        let mut cli = make_cli(Some("name"));
301        cli.input = InputFormat::Text;
302        let result = run(&cli, "name=Alice\nage=30").unwrap();
303        assert_eq!(result, "Alice");
304    }
305
306    #[test]
307    fn run_text_search_fallback() {
308        let mut cli = make_cli(Some("error"));
309        cli.input = InputFormat::Text;
310        let result = run(&cli, "info: all good\nerror: something failed").unwrap();
311        assert_eq!(result, "something failed");
312    }
313
314    #[test]
315    fn run_auto_detect_json() {
316        let cli = make_cli(Some("x"));
317        let result = run(&cli, r#"{"x": 42}"#).unwrap();
318        assert_eq!(result, "42");
319    }
320
321    #[test]
322    fn run_auto_detect_env() {
323        let cli = make_cli(Some("PORT"));
324        let result = run(&cli, "PORT=3000\nHOST=localhost").unwrap();
325        assert_eq!(result, "3000");
326    }
327
328    #[test]
329    fn run_negative_index() {
330        let cli = make_cli(Some("[*][-1]"));
331        let result = run(&cli, "[[1,2],[3,4]]").unwrap();
332        assert_eq!(result, "2\n4");
333    }
334
335    // ── Phase 1 integration: slicing ──
336
337    #[test]
338    fn run_slice() {
339        let cli = make_cli(Some("items[1:3]"));
340        let result = run(&cli, r#"{"items": [10, 20, 30, 40, 50]}"#).unwrap();
341        assert_eq!(result, "20\n30");
342    }
343
344    // ── Phase 1 integration: builtins ──
345
346    #[test]
347    fn run_keys() {
348        let cli = make_cli(Some("keys()"));
349        let result = run(&cli, r#"{"b": 2, "a": 1}"#).unwrap();
350        assert!(result.contains("\"a\""));
351        assert!(result.contains("\"b\""));
352    }
353
354    #[test]
355    fn run_length() {
356        let cli = make_cli(Some("items.length()"));
357        let result = run(&cli, r#"{"items": [1, 2, 3]}"#).unwrap();
358        assert_eq!(result, "3");
359    }
360
361    // ── Phase 1 integration: recursive descent ──
362
363    #[test]
364    fn run_recursive() {
365        let cli = make_cli(Some("..name"));
366        let result = run(&cli, r#"{"a": {"name": "deep"}}"#).unwrap();
367        assert_eq!(result, "deep");
368    }
369
370    // ── Phase 1 integration: multi-selector ──
371
372    #[test]
373    fn run_multi_selector() {
374        let cli = make_cli(Some("name, age"));
375        let result = run(&cli, r#"{"name": "Alice", "age": 30}"#).unwrap();
376        assert_eq!(result, "Alice\n30");
377    }
378
379    // ── Phase 2 integration: pipeline ──
380
381    #[test]
382    fn run_pipeline_select() {
383        let cli = make_cli(Some("items[*] | select(.price > 100) | name"));
384        let input = r#"{"items": [{"name": "a", "price": 50}, {"name": "b", "price": 200}]}"#;
385        let result = run(&cli, input).unwrap();
386        assert_eq!(result, "b");
387    }
388
389    #[test]
390    fn run_pipeline_builtin() {
391        let cli = make_cli(Some("data | keys()"));
392        let result = run(&cli, r#"{"data": {"x": 1, "y": 2}}"#).unwrap();
393        assert!(result.contains("\"x\""));
394        assert!(result.contains("\"y\""));
395    }
396
397    // ── Phase 2 integration: regex ──
398
399    #[test]
400    fn run_pipeline_regex() {
401        let cli = make_cli(Some("items[*] | select(.name ~ \"^a\") | name"));
402        let input =
403            r#"{"items": [{"name": "apple"}, {"name": "banana"}, {"name": "avocado"}]}"#;
404        let result = run(&cli, input).unwrap();
405        assert_eq!(result, "apple\navocado");
406    }
407
408    // ── Phase 3 integration: set/del ──
409
410    #[test]
411    fn run_set() {
412        let mut cli = make_cli(Some("set(.name, \"Bob\")"));
413        cli.json = true;
414        let result = run(&cli, r#"{"name": "Alice", "age": 30}"#).unwrap();
415        assert!(result.contains("\"Bob\""));
416        assert!(result.contains("30"));
417    }
418
419    #[test]
420    fn run_del() {
421        let mut cli = make_cli(Some("del(.temp)"));
422        cli.json = true;
423        let result = run(&cli, r#"{"name": "Alice", "temp": "x"}"#).unwrap();
424        assert!(result.contains("Alice"));
425        assert!(!result.contains("temp"));
426    }
427
428    // ── Phase 3 integration: format-aware output ──
429
430    #[test]
431    fn run_output_yaml() {
432        let mut cli = make_cli(None);
433        cli.output = OutputFormat::Yaml;
434        let result = run(&cli, r#"{"name": "Alice"}"#).unwrap();
435        assert!(result.contains("name:"));
436    }
437
438    #[test]
439    fn run_output_toml() {
440        let mut cli = make_cli(None);
441        cli.output = OutputFormat::Toml;
442        let result = run(&cli, r#"{"name": "Alice"}"#).unwrap();
443        assert!(result.contains("name = "));
444    }
445
446    // ══════════════════════════════════════════════
447    // Additional coverage tests
448    // ══════════════════════════════════════════════
449
450    // ── Phase 1: Slice edge cases ──
451
452    #[test]
453    fn run_slice_from_start() {
454        let cli = make_cli(Some("items[:2]"));
455        let result = run(&cli, r#"{"items": [10, 20, 30, 40]}"#).unwrap();
456        assert_eq!(result, "10\n20");
457    }
458
459    #[test]
460    fn run_slice_to_end() {
461        let cli = make_cli(Some("items[2:]"));
462        let result = run(&cli, r#"{"items": [10, 20, 30, 40]}"#).unwrap();
463        assert_eq!(result, "30\n40");
464    }
465
466    #[test]
467    fn run_slice_negative() {
468        let cli = make_cli(Some("items[-2:]"));
469        let result = run(&cli, r#"{"items": [10, 20, 30, 40]}"#).unwrap();
470        assert_eq!(result, "30\n40");
471    }
472
473    #[test]
474    fn run_slice_all() {
475        let cli = make_cli(Some("items[:]"));
476        let result = run(&cli, r#"{"items": [1, 2, 3]}"#).unwrap();
477        assert_eq!(result, "1\n2\n3");
478    }
479
480    #[test]
481    fn run_slice_empty_result() {
482        let cli = make_cli(Some("items[10:20]"));
483        let result = run(&cli, r#"{"items": [1, 2, 3]}"#);
484        assert!(result.is_err());
485    }
486
487    #[test]
488    fn run_slice_deeply_nested() {
489        let cli = make_cli(Some("data[0].items[1:3]"));
490        let result = run(&cli, r#"{"data": [{"items": [10, 20, 30, 40]}]}"#).unwrap();
491        assert_eq!(result, "20\n30");
492    }
493
494    // ── Phase 1: Builtin edge cases ──
495
496    #[test]
497    fn run_values() {
498        let cli = make_cli(Some("values()"));
499        let result = run(&cli, r#"{"a": 1, "b": 2}"#).unwrap();
500        assert!(result.contains("1"));
501        assert!(result.contains("2"));
502    }
503
504    #[test]
505    fn run_length_string() {
506        let cli = make_cli(Some("name.length()"));
507        let result = run(&cli, r#"{"name": "Alice"}"#).unwrap();
508        assert_eq!(result, "5");
509    }
510
511    #[test]
512    fn run_length_object() {
513        let cli = make_cli(Some("length()"));
514        let result = run(&cli, r#"{"a": 1, "b": 2, "c": 3}"#).unwrap();
515        assert_eq!(result, "3");
516    }
517
518    #[test]
519    fn run_keys_on_array() {
520        let cli = make_cli(Some("keys()"));
521        let result = run(&cli, "[10, 20, 30]").unwrap();
522        assert!(result.contains("0"));
523        assert!(result.contains("1"));
524        assert!(result.contains("2"));
525    }
526
527    // ── Phase 1: Recursive descent edge cases ──
528
529    #[test]
530    fn run_recursive_multiple_matches() {
531        let cli = make_cli(Some("..id"));
532        let result = run(&cli, r#"{"a": {"id": 1}, "b": {"id": 2}}"#).unwrap();
533        assert!(result.contains("1"));
534        assert!(result.contains("2"));
535    }
536
537    #[test]
538    fn run_recursive_deep() {
539        let cli = make_cli(Some("..target"));
540        let result = run(&cli, r#"{"a": {"b": {"c": {"target": 42}}}}"#).unwrap();
541        assert_eq!(result, "42");
542    }
543
544    #[test]
545    fn run_recursive_not_found() {
546        let cli = make_cli(Some("..missing"));
547        assert!(run(&cli, r#"{"a": 1, "b": 2}"#).is_err());
548    }
549
550    // ── Phase 1: Multi-selector edge cases ──
551
552    #[test]
553    fn run_multi_selector_three() {
554        let cli = make_cli(Some("a, b, c"));
555        let result = run(&cli, r#"{"a": 1, "b": 2, "c": 3}"#).unwrap();
556        assert_eq!(result, "1\n2\n3");
557    }
558
559    #[test]
560    fn run_multi_selector_partial_missing() {
561        let cli = make_cli(Some("name, missing, age"));
562        let result = run(&cli, r#"{"name": "Alice", "age": 30}"#).unwrap();
563        assert_eq!(result, "Alice\n30");
564    }
565
566    #[test]
567    fn run_multi_selector_all_missing() {
568        let cli = make_cli(Some("x, y"));
569        let result = run(&cli, r#"{"a": 1}"#);
570        assert!(result.is_err());
571    }
572
573    // ── Phase 2: Pipeline edge cases ──
574
575    #[test]
576    fn run_pipeline_three_stages() {
577        let cli = make_cli(Some("items[*] | select(.active) | name"));
578        let input = r#"{"items": [{"name": "a", "active": true}, {"name": "b", "active": false}, {"name": "c", "active": true}]}"#;
579        let result = run(&cli, input).unwrap();
580        assert_eq!(result, "a\nc");
581    }
582
583    #[test]
584    fn run_pipeline_four_stages() {
585        let cli = make_cli(Some("items[*] | select(.active) | name | length()"));
586        let input = r#"{"items": [{"name": "ab", "active": true}, {"name": "cde", "active": false}, {"name": "fgh", "active": true}]}"#;
587        let result = run(&cli, input).unwrap();
588        assert_eq!(result, "2\n3");
589    }
590
591    #[test]
592    fn run_pipeline_keys_then_length() {
593        let cli = make_cli(Some("keys() | length()"));
594        let result = run(&cli, r#"{"a": 1, "b": 2, "c": 3}"#).unwrap();
595        assert_eq!(result, "3");
596    }
597
598    #[test]
599    fn run_pipeline_values_then_length() {
600        let cli = make_cli(Some("values() | length()"));
601        let result = run(&cli, r#"{"a": 1, "b": 2}"#).unwrap();
602        assert_eq!(result, "2");
603    }
604
605    // ── Phase 2: Select edge cases ──
606
607    #[test]
608    fn run_select_lt() {
609        let cli = make_cli(Some("[*] | select(. < 10)"));
610        let result = run(&cli, "[1, 5, 10, 15, 20]").unwrap();
611        assert_eq!(result, "1\n5");
612    }
613
614    #[test]
615    fn run_select_gte() {
616        let cli = make_cli(Some("[*] | select(. >= 10)"));
617        let result = run(&cli, "[1, 5, 10, 15, 20]").unwrap();
618        assert_eq!(result, "10\n15\n20");
619    }
620
621    #[test]
622    fn run_select_ne() {
623        let cli = make_cli(Some("[*] | select(.status != \"deleted\") | name"));
624        let input = r#"[{"name": "a", "status": "active"}, {"name": "b", "status": "deleted"}, {"name": "c", "status": "active"}]"#;
625        let result = run(&cli, input).unwrap();
626        assert_eq!(result, "a\nc");
627    }
628
629    #[test]
630    fn run_select_or() {
631        let cli = make_cli(Some("[*] | select(.price > 100 or .featured == true) | name"));
632        let input = r#"[{"name": "a", "price": 5, "featured": true}, {"name": "b", "price": 50, "featured": false}, {"name": "c", "price": 500, "featured": false}]"#;
633        let result = run(&cli, input).unwrap();
634        assert_eq!(result, "a\nc");
635    }
636
637    #[test]
638    fn run_select_not() {
639        let cli = make_cli(Some("[*] | select(not .active) | name"));
640        let input = r#"[{"name": "a", "active": true}, {"name": "b", "active": false}]"#;
641        let result = run(&cli, input).unwrap();
642        assert_eq!(result, "b");
643    }
644
645    #[test]
646    fn run_select_all_filtered_out() {
647        let cli = make_cli(Some("[*] | select(. > 100)"));
648        let result = run(&cli, "[1, 2, 3]");
649        assert!(result.is_err());
650    }
651
652    #[test]
653    fn run_select_eq_null() {
654        let cli = make_cli(Some("[*] | select(.email == null) | name"));
655        let input = r#"[{"name": "a", "email": null}, {"name": "b", "email": "b@x.com"}]"#;
656        let result = run(&cli, input).unwrap();
657        assert_eq!(result, "a");
658    }
659
660    #[test]
661    fn run_select_eq_bool() {
662        let cli = make_cli(Some("[*] | select(.done == true) | name"));
663        let input = r#"[{"name": "a", "done": true}, {"name": "b", "done": false}]"#;
664        let result = run(&cli, input).unwrap();
665        assert_eq!(result, "a");
666    }
667
668    #[test]
669    fn run_select_regex_case_insensitive() {
670        let cli = make_cli(Some("[*] | select(. ~ \"(?i)^hello$\")"));
671        let result = run(&cli, r#"["Hello", "hello", "HELLO", "world"]"#).unwrap();
672        assert_eq!(result, "Hello\nhello\nHELLO");
673    }
674
675    // ── Phase 3: set/del edge cases ──
676
677    #[test]
678    fn run_set_nested() {
679        let mut cli = make_cli(Some("set(.user.name, \"Bob\")"));
680        cli.json = true;
681        let result = run(&cli, r#"{"user": {"name": "Alice", "age": 30}}"#).unwrap();
682        assert!(result.contains("\"Bob\""));
683        assert!(result.contains("30"));
684    }
685
686    #[test]
687    fn run_set_new_key() {
688        let mut cli = make_cli(Some("set(.b, 2)"));
689        cli.json = true;
690        let result = run(&cli, r#"{"a": 1}"#).unwrap();
691        assert!(result.contains("\"a\": 1"));
692        assert!(result.contains("\"b\": 2"));
693    }
694
695    #[test]
696    fn run_set_number() {
697        let cli = make_cli(Some("set(.count, 42) | count"));
698        let result = run(&cli, r#"{"count": 0}"#).unwrap();
699        assert_eq!(result, "42");
700    }
701
702    #[test]
703    fn run_set_bool() {
704        let cli = make_cli(Some("set(.active, true) | active"));
705        let result = run(&cli, r#"{"active": false}"#).unwrap();
706        assert_eq!(result, "true");
707    }
708
709    #[test]
710    fn run_set_null() {
711        let cli = make_cli(Some("set(.temp, null) | temp"));
712        let result = run(&cli, r#"{"temp": "data"}"#).unwrap();
713        assert_eq!(result, "null");
714    }
715
716    #[test]
717    fn run_del_nested() {
718        let mut cli = make_cli(Some("del(.user.temp)"));
719        cli.json = true;
720        let result = run(&cli, r#"{"user": {"name": "Alice", "temp": "x"}}"#).unwrap();
721        assert!(result.contains("Alice"));
722        assert!(!result.contains("temp"));
723    }
724
725    #[test]
726    fn run_del_array_element() {
727        let mut cli = make_cli(Some("del(.items[1])"));
728        cli.json = true;
729        let result = run(&cli, r#"{"items": [1, 2, 3]}"#).unwrap();
730        assert!(result.contains("1"));
731        assert!(result.contains("3"));
732        assert!(!result.contains("  2")); // "2" not as standalone array element
733    }
734
735    #[test]
736    fn run_set_then_del() {
737        let mut cli = make_cli(Some("set(.c, 3) | del(.a)"));
738        cli.json = true;
739        let result = run(&cli, r#"{"a": 1, "b": 2}"#).unwrap();
740        assert!(result.contains("\"b\": 2"));
741        assert!(result.contains("\"c\": 3"));
742        assert!(!result.contains("\"a\""));
743    }
744
745    #[test]
746    fn run_multiple_set() {
747        let cli = make_cli(Some("set(.x, 1) | set(.y, 2) | keys() | length()"));
748        let result = run(&cli, r#"{"a": 0}"#).unwrap();
749        // keys: a, x, y → 3
750        assert_eq!(result, "3");
751    }
752
753    #[test]
754    fn run_multiple_del() {
755        let mut cli = make_cli(Some("del(.a) | del(.b)"));
756        cli.json = true;
757        let result = run(&cli, r#"{"a": 1, "b": 2, "c": 3}"#).unwrap();
758        assert!(result.contains("\"c\": 3"));
759        assert!(!result.contains("\"a\""));
760        assert!(!result.contains("\"b\""));
761    }
762
763    // ── Phase 3: Format output edge cases ──
764
765    #[test]
766    fn run_output_json_explicit() {
767        let mut cli = make_cli(Some("name"));
768        cli.output = OutputFormat::Json;
769        let result = run(&cli, r#"{"name": "Alice"}"#).unwrap();
770        assert_eq!(result, "\"Alice\"");
771    }
772
773    #[test]
774    fn run_output_yaml_nested() {
775        let mut cli = make_cli(Some("user"));
776        cli.output = OutputFormat::Yaml;
777        let result = run(&cli, r#"{"user": {"name": "Alice", "age": 30}}"#).unwrap();
778        assert!(result.contains("name:"));
779        assert!(result.contains("Alice"));
780    }
781
782    // ── Cross-phase combinations ──
783
784    #[test]
785    fn run_slice_then_select() {
786        let cli = make_cli(Some("items[1:4] | select(.price > 100) | name"));
787        let input = r#"{"items": [{"name": "a", "price": 10}, {"name": "b", "price": 200}, {"name": "c", "price": 50}, {"name": "d", "price": 300}]}"#;
788        let result = run(&cli, input).unwrap();
789        assert_eq!(result, "b\nd");
790    }
791
792    #[test]
793    fn run_recursive_then_select() {
794        let cli = make_cli(Some("..items[*] | select(.active) | name"));
795        let input = r#"{"data": {"items": [{"name": "a", "active": true}, {"name": "b", "active": false}]}}"#;
796        let result = run(&cli, input).unwrap();
797        assert_eq!(result, "a");
798    }
799
800    #[test]
801    fn run_wildcard_then_length() {
802        let cli = make_cli(Some("items[*].name | length()"));
803        let input = r#"{"items": [{"name": "ab"}, {"name": "cde"}]}"#;
804        let result = run(&cli, input).unwrap();
805        assert_eq!(result, "2\n3");
806    }
807
808    #[test]
809    fn run_select_then_set() {
810        let mut cli = make_cli(Some("[*] | select(.active) | set(.selected, true)"));
811        cli.json = true;
812        let input = r#"[{"name": "a", "active": true}, {"name": "b", "active": false}]"#;
813        let result = run(&cli, input).unwrap();
814        assert!(result.contains("selected"));
815        assert!(result.contains("\"a\""));
816    }
817
818    // ── Flags with pipeline features ──
819
820    #[test]
821    fn run_count_with_select() {
822        let mut cli = make_cli(Some("[*] | select(. > 10)"));
823        cli.count = true;
824        let result = run(&cli, "[1, 5, 15, 20, 25]").unwrap();
825        assert_eq!(result, "3");
826    }
827
828    #[test]
829    fn run_first_with_select() {
830        let mut cli = make_cli(Some("[*] | select(. > 10)"));
831        cli.first = true;
832        let result = run(&cli, "[1, 5, 15, 20, 25]").unwrap();
833        assert_eq!(result, "15");
834    }
835
836    #[test]
837    fn run_exists_with_pipeline() {
838        let mut cli = make_cli(Some("items[*] | select(.active)"));
839        cli.exists = true;
840        let input = r#"{"items": [{"active": true}]}"#;
841        let result = run(&cli, input).unwrap();
842        assert_eq!(result, "");
843    }
844
845    #[test]
846    fn run_default_with_pipeline() {
847        let mut cli = make_cli(Some("[*] | select(. > 100)"));
848        cli.default = Some("none".into());
849        let result = run(&cli, "[1, 2, 3]").unwrap();
850        assert_eq!(result, "none");
851    }
852
853    // ── Unicode handling ──
854
855    #[test]
856    fn run_unicode_key() {
857        let cli = make_cli(Some("\"名前\""));
858        let result = run(&cli, r#"{"名前": "太郎"}"#).unwrap();
859        assert_eq!(result, "太郎");
860    }
861
862    #[test]
863    fn run_unicode_value() {
864        let cli = make_cli(Some("emoji"));
865        let result = run(&cli, r#"{"emoji": "🎉🎊🎈"}"#).unwrap();
866        assert_eq!(result, "🎉🎊🎈");
867    }
868
869    // ── Default on parse error ──
870
871    #[test]
872    fn run_default_on_parse_error() {
873        let mut cli = make_cli(Some("name"));
874        cli.default = Some("fallback".into());
875        cli.input = InputFormat::Json;
876        let result = run(&cli, "not valid json").unwrap();
877        assert_eq!(result, "fallback");
878    }
879}