fli 1.2.0

The commander.js like CLI Parser for rust
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
# FLI

A powerful, type-safe CLI library for Rust inspired by [commander.js](https://github.com/tj/commander.js), designed to make building command-line applications intuitive and ergonomic.

[![Crates.io](https://img.shields.io/crates/v/fli.svg)](https://crates.io/crates/fli)
[![Documentation](https://docs.rs/fli/badge.svg)](https://docs.rs/fli)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

> **⚠️ BREAKING CHANGE in v1.2.0**: `ValueTypes::None` has been removed! Use `ValueTypes::OptionalSingle(Some(Value::Bool(false)))` for flag options instead. See the [Migration Guide]#migration-from-valuetypesnone for details.

> **Note**: Version 1.0.0 included breaking changes. See the [Migration Guide]#migration-from-0x for upgrading from v0.x.

## Features

- 🎯 **Type-safe value parsing** with compile-time guarantees
- 🌲 **Hierarchical commands** with subcommand support
- 🎨 **Beautiful help output** with automatic formatting
- 🔧 **Flexible option types**: flags, required/optional values, single/multiple values
- 🚀 **Zero-cost abstractions** with minimal overhead
- 📦 **Cargo.toml integration** for automatic metadata
-**Error handling** with detailed, actionable messages

## Quick Start

```toml
[dependencies]
fli = "1.0"
```

```rust
use fli::{Fli, ValueTypes, Value};

fn main() {
    let mut app = Fli::new("myapp", "1.0.0", "A sample CLI app");

    // Add a simple flag
    app.add_option(
        "verbose",
        "Enable verbose output",
        "-v",
        "--verbose",
        ValueTypes::OptionalSingle(Some(Value::Bool(false)))
    );

    // Add an option that requires a value
    app.add_option(
        "name",
        "Your name",
        "-n",
        "--name",
        ValueTypes::RequiredSingle(Value::Str(String::new()))
    );

    // Set the callback
    app.set_callback(|data| {
        let name = data.get_option_value("name")
            .and_then(|v| v.as_str())
            .unwrap_or("World");

        let verbose = data.get_option_value("verbose").is_some();

        println!("Hello, {}!", name);
        if verbose {
            println!("Verbose mode enabled!");
        }
    });

    app.run();
}
```

Run it:

```bash
$ cargo run -- -n Alice -v
Hello, Alice!
Verbose mode enabled!
```

## Core Concepts

### Value Types

Fli provides explicit value types for type-safe option parsing:

```rust
use fli::{ValueTypes, Value};

// Flag option (no value)
ValueTypes::OptionalSingle(Some(Value::Bool(false)))

// Required single value
ValueTypes::RequiredSingle(Value::Str(String::new()))
ValueTypes::RequiredSingle(Value::Int(0))

// Optional single value with default
ValueTypes::OptionalSingle(Some(Value::Int(8080)))
ValueTypes::OptionalSingle(None)

// Required multiple values (at least 1)
ValueTypes::RequiredMultiple(vec![], None)

// Exactly N values required
ValueTypes::RequiredMultiple(vec![], Some(3))

// Optional multiple values
ValueTypes::OptionalMultiple(None, None)

// Up to N values
ValueTypes::OptionalMultiple(None, Some(5))
```

### Commands and Subcommands

Create hierarchical command structures:

```rust
use fli::{Fli, ValueTypes, Value};

fn main() {
    let mut app = Fli::new("git", "2.0.0", "Version control system");

    // Create a command
    let commit_cmd = app.command("commit", "Record changes").unwrap();

    // Add options to the command
    commit_cmd.add_option(
        "message",
        "Commit message",
        "-m",
        "--message",
        ValueTypes::RequiredSingle(Value::Str(String::new()))
    );

    commit_cmd.add_option(
        "all",
        "Commit all changes",
        "-a",
        "--all",
        ValueTypes::OptionalSingle(Some(Value::Bool(false)))
    );

    // Set the command callback
    commit_cmd.set_callback(|data| {
        let message = data.get_option_value("message")
            .and_then(|v| v.as_str())
            .unwrap_or("No message");

        let all = data.get_option_value("all").is_some();

        println!("Committing with message: {}", message);
        if all {
            println!("Including all changes");
        }
    });

    app.run();
}
```

### Accessing Values in Callbacks

The `FliCallbackData` provides convenient methods for accessing parsed values:

```rust
use fli::option_parser::{Value, ValueTypes};

fn my_callback(data: &FliCallbackData) {
    // Get a single string value
    let name = data.get_option_value("name")
        .and_then(|v| v.as_str())
        .unwrap_or("default");

    // Get multiple string values
    let files = data.get_option_value("files")
        .and_then(|v| v.as_strings())
        .unwrap_or_default();

    // Check if a flag was passed (NEW in v1.2.0)
    // Flags use Bool values: false = not passed, true = passed
    let verbose = data.get_option_value("verbose")
        .map(|v| matches!(v, ValueTypes::OptionalSingle(Some(Value::Bool(true)))))
        .unwrap_or(false);

    // Shorter alternative for flags:
    let is_flag_set = data.get_option_value("verbose").is_some();

    // Get positional arguments
    let first_arg = data.get_argument_at(0);
    let all_args = data.get_arguments();

    // Access the command
    let cmd_name = data.get_command().get_name();
}
```

## Examples

### Basic Calculator

```rust
use fli::{Fli, ValueTypes, Value};

fn main() {
    let mut app = Fli::new("calc", "1.0.0", "Simple calculator");

    let calc_cmd = app.command("calculate", "Perform calculation").unwrap();

    calc_cmd.add_option(
        "operation",
        "Operation to perform (add, sub, mul, div)",
        "-o",
        "--operation",
        ValueTypes::RequiredSingle(Value::Str(String::new()))
    );

    calc_cmd.set_expected_positional_args(2);

    calc_cmd.set_callback(|data| {
        let op = data.get_option_value("operation")
            .and_then(|v| v.as_str())
            .unwrap_or("add");

        let args = data.get_arguments();
        if args.len() < 2 {
            eprintln!("Error: Need two numbers");
            return;
        }

        let a: f64 = args[0].parse().unwrap_or(0.0);
        let b: f64 = args[1].parse().unwrap_or(0.0);

        let result = match op {
            "add" => a + b,
            "sub" => a - b,
            "mul" => a * b,
            "div" => a / b,
            _ => {
                eprintln!("Unknown operation: {}", op);
                return;
            }
        };

        println!("{} {} {} = {}", a, op, b, result);
    });

    app.run();
}
```

Usage:

```bash
$ calc calculate -o add 10 20
10 add 20 = 30
```

### File Operations with Subcommands

```rust
use fli::{Fli, ValueTypes, Value};

fn main() {
    let mut app = Fli::new("file-tool", "1.0.0", "File operations");

    // File command with subcommands
    let file_cmd = app.command("file", "File operations").unwrap();

    // Copy subcommand
    file_cmd.subcommand("copy", "Copy files")
        .add_option(
            "source",
            "Source files",
            "-s",
            "--source",
            ValueTypes::RequiredMultiple(vec![], None)
        )
        .add_option(
            "dest",
            "Destination directory",
            "-d",
            "--dest",
            ValueTypes::RequiredSingle(Value::Str(String::new()))
        )
        .set_callback(|data| {
            let sources = data.get_option_value("source")
                .and_then(|v| v.as_strings())
                .unwrap_or_default();

            let dest = data.get_option_value("dest")
                .and_then(|v| v.as_str())
                .unwrap_or(".");

            println!("Copying {:?} to {}", sources, dest);
        });

    // Move subcommand
    file_cmd.subcommand("move", "Move files")
        .add_option(
            "path",
            "Files to move",
            "-p",
            "--path",
            ValueTypes::RequiredMultiple(vec![], None)
        )
        .set_callback(|data| {
            let paths = data.get_option_value("path")
                .and_then(|v| v.as_strings())
                .unwrap_or_default();

            println!("Moving: {:?}", paths);
        });

    app.run();
}
```

Usage:

```bash
$ file-tool file copy -s file1.txt file2.txt -d /backup
Copying ["file1.txt", "file2.txt"] to /backup

$ file-tool file move -p old1.txt old2.txt
Moving: ["old1.txt", "old2.txt"]
```

### Greeting App with Options

```rust
use fli::{Fli, ValueTypes, Value};

fn main() {
    let mut app = Fli::new("greet", "1.0.0", "Greeting application");

    let greet_cmd = app.command("greet", "Greet someone").unwrap();

    greet_cmd.add_option(
        "name",
        "Name to greet",
        "-n",
        "--name",
        ValueTypes::RequiredSingle(Value::Str(String::new()))
    );

    greet_cmd.add_option(
        "time",
        "Time of day (morning, afternoon, evening)",
        "-t",
        "--time",
        ValueTypes::OptionalSingle(None)
    );

    greet_cmd.add_option(
        "repeat",
        "Number of times to repeat",
        "-r",
        "--repeat",
        ValueTypes::OptionalSingle(Some(Value::Int(1)))
    );

    greet_cmd.set_callback(|data| {
        let name = data.get_option_value("name")
            .and_then(|v| v.as_str())
            .unwrap_or("friend");

        let time = data.get_option_value("time")
            .and_then(|v| v.as_str())
            .unwrap_or("Hello");

        let greeting = match time {
            "morning" => "Good morning",
            "afternoon" => "Good afternoon",
            "evening" => "Good evening",
            _ => "Hello",
        };

        let repeat = data.get_option_value("repeat")
            .and_then(|v| match v {
                ValueTypes::OptionalSingle(Some(Value::Int(n))) => Some(*n),
                _ => None,
            })
            .unwrap_or(1);

        for _ in 0..repeat {
            println!("{}, {}!", greeting, name);
        }
    });

    app.run();
}
```

Usage:

```bash
$ greet greet -n Alice -t morning -r 2
Good morning, Alice!
Good morning, Alice!
```

## Advanced Features

### Custom Help Messages

Fli automatically generates beautiful help messages:

```bash
$ myapp --help
Command: myapp

A sample CLI application

Usage
  myapp [SUBCOMMANDS] [ARGUMENT] [ARGUMENT] [OPTIONS]
  [SUBCOMMANDS] [OPTIONS]  -- [ARGUMENT] [ARGUMENT]

Options
┌───────┬────────────┬──────────────────────┬─────────────────────┐
│ Flag  │ Long Form  │ Value Type           │ Description         │
├───────┼────────────┼──────────────────────┼─────────────────────┤
│ -v    │ --verbose  │ none                 │ Enable verbose      │
│ -n    │ --name     │ single (required)    │ Your name           │
└───────┴────────────┴──────────────────────┴─────────────────────┘
```

### Debug Mode

Enable debug output to see internal parsing details:

```rust
let app = Fli::new("myapp", "1.0.0", "Description")
    .with_debug();

// Or add a debug flag
app.add_debug_option();
```

### Error Handling

Commands return `Result<&mut FliCommand>` for better error handling:

```rust
use fli::Result;

fn main() -> Result<()> {
    let mut app = Fli::new("myapp", "1.0.0", "Description");

    let cmd = app.command("serve", "Start server")?;
    cmd.add_option(
        "port",
        "Port to bind",
        "-p",
        "--port",
        ValueTypes::RequiredSingle(Value::Int(8080))
    );

    app.run();
    Ok(())
}
```

### Cargo.toml Integration

Initialize from your `Cargo.toml` metadata:

```rust
use fli::init_fli_from_toml;

fn main() {
    let mut app = init_fli_from_toml!();

    // App name, version, and description are automatically loaded
    // from your Cargo.toml file

    app.run();
}
```

## Migration from 0.x

Version 1.0.0 includes significant improvements but breaks backwards compatibility. Key changes:

### Option Syntax

**Before (v0.x):**

```rust
app.option("-n --name, <>", "Your name", callback);
```

**After (v1.0):**

```rust
app.add_option(
    "name",
    "Your name",
    "-n",
    "--name",
    ValueTypes::RequiredSingle(Value::Str(String::new()))
);
app.set_callback(callback);
```

### Value Types

| v0.x        | v1.0                                                   |
| ----------- | ------------------------------------------------------ |
| (no symbol) | `ValueTypes::OptionalSingle(Some(Value::Bool(false)))` |
| `<>`        | `ValueTypes::RequiredSingle(_)`                        |
| `[]`        | `ValueTypes::OptionalSingle(_)`                        |
| `<...>`     | `ValueTypes::RequiredMultiple(vec![], None)`           |
| `[...]`     | `ValueTypes::OptionalMultiple(None, None)`             |

### Callbacks

**Before (v0.x):**

```rust
fn callback(app: &Fli) {
    let name = app.get_values("name".to_owned()).unwrap()[0];
}
```

**After (v1.0):**

```rust
fn callback(data: &FliCallbackData) {
    let name = data.get_option_value("name")
        .and_then(|v| v.as_str())
        .unwrap_or("default");
}
```

### Commands

**Before (v0.x):**

```rust
let cmd = app.command("serve", "Start server");
```

**After (v1.0):**

```rust
let cmd = app.command("serve", "Start server")?; // Returns Result
```

For a complete migration guide, see [MIGRATION.md](MIGRATION.md).

## Migration from ValueTypes::None

**⚠️ Breaking Change in v1.2.0**

`ValueTypes::None` has been removed to fix a critical design flaw where you couldn't distinguish between "flag was defined" and "flag was passed".

### Quick Fix

**Before (v1.1 and earlier):**

```rust
app.add_option(
    "verbose",
    "Enable verbose output",
    "-v",
    "--verbose",
    ValueTypes::None  // ❌ Removed in v1.2.0
);
```

**After (v1.2.0+):**

```rust
use fli::option_parser::{Value, ValueTypes};

app.add_option(
    "verbose",
    "Enable verbose output",
    "-v",
    "--verbose",
    ValueTypes::OptionalSingle(Some(Value::Bool(false)))  // ✅ Use Bool(false) as default
);
```

### Checking if a Flag was Passed

**Before (v1.1 - didn't work correctly):**

```rust
// This would always be true if the option was defined!
let verbose = data.get_option_value("verbose").is_some();
```

**After (v1.2.0 - works correctly):**

```rust
// Method 1: Check the boolean value
let verbose = data.get_option_value("verbose")
    .map(|v| matches!(v, ValueTypes::OptionalSingle(Some(Value::Bool(true)))))
    .unwrap_or(false);

// Method 2: Simpler (still works, checks if value was updated)
let verbose = data.get_option_value("verbose").is_some();
```

### Why the Change?

With `ValueTypes::None`:

- ❌ Could not tell if `-v` was passed or not
-`get_option_value()` always returned `Some` for defined options
- ❌ Had to search command chain to check flag usage

With `ValueTypes::OptionalSingle(Some(Value::Bool(...)))`:

- `Bool(false)` = flag not passed (default)
-`Bool(true)` = flag was passed
- ✅ Can query option parser directly
- ✅ Clear, idiomatic Rust

## Documentation

- [API Documentation]https://docs.rs/fli
- [Examples]https://github.com/codad5/fli/tree/master/examples
- [Changelog]CHANGELOG.md

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## Acknowledgments

- Inspired by [commander.js]https://github.com/tj/commander.js
- Built with ❤️ by [Chibueze Aniezeofor (codad5)]https://github.com/codad5