Skip to main content

snapper_fmt/
cli.rs

1use std::path::PathBuf;
2
3use clap::{Parser, Subcommand, ValueEnum};
4
5#[derive(Debug, Clone, Copy, ValueEnum)]
6pub enum FormatArg {
7    Org,
8    Latex,
9    Markdown,
10    Plaintext,
11}
12
13#[derive(Debug, Clone, Copy, ValueEnum)]
14pub enum OutputFormat {
15    Text,
16    Json,
17    Sarif,
18}
19
20#[derive(Debug, Parser)]
21#[command(name = "snapper", version, about = "Semantic line break formatter")]
22pub struct Cli {
23    #[command(subcommand)]
24    pub command: Option<Commands>,
25
26    /// Input files. Reads stdin if omitted.
27    #[arg()]
28    pub files: Vec<PathBuf>,
29
30    /// Input format (auto-detected from extension if omitted).
31    #[arg(short, long)]
32    pub format: Option<FormatArg>,
33
34    /// Assume this filename when reading stdin (for format auto-detection).
35    #[arg(long)]
36    pub stdin_filepath: Option<PathBuf>,
37
38    /// Output file (stdout if omitted).
39    #[arg(short, long)]
40    pub output: Option<PathBuf>,
41
42    /// Modify files in place.
43    #[arg(short, long)]
44    pub in_place: bool,
45
46    /// Maximum line width (0 = unlimited).
47    #[arg(short = 'w', long, default_value_t = 0)]
48    pub max_width: usize,
49
50    /// Use neural sentence detection (requires neural feature).
51    #[arg(long)]
52    pub neural: bool,
53
54    /// Exit with code 1 if any file would change.
55    #[arg(long)]
56    pub check: bool,
57
58    /// Show a unified diff of what would change.
59    #[arg(long)]
60    pub diff: bool,
61
62    /// Path to config file (default: .snapperrc.toml in current or parent dirs).
63    #[arg(long)]
64    pub config: Option<PathBuf>,
65
66    /// Only format lines in this range (1-indexed, inclusive). Format: START:END.
67    #[arg(long)]
68    pub range: Option<String>,
69
70    /// Output format for --check mode.
71    #[arg(long, default_value = "text")]
72    pub output_format: OutputFormat,
73}
74
75#[derive(Debug, Subcommand)]
76pub enum Commands {
77    /// Initialize snapper for a project (generate config, pre-commit, gitattributes).
78    Init {
79        /// Preview what would be generated without writing files.
80        #[arg(long)]
81        dry_run: bool,
82    },
83}
84
85/// Parse a range string "START:END" into (start, end) 1-indexed inclusive.
86pub fn parse_range(s: &str) -> Option<(usize, usize)> {
87    let parts: Vec<&str> = s.split(':').collect();
88    if parts.len() != 2 {
89        return None;
90    }
91    let start = parts[0].parse::<usize>().ok()?;
92    let end = parts[1].parse::<usize>().ok()?;
93    if start == 0 || end == 0 || start > end {
94        return None;
95    }
96    Some((start, end))
97}