Skip to main content

flake_edit/
cli.rs

1use clap::{Parser, Subcommand, ValueEnum};
2
3#[derive(Parser, Debug)]
4#[command(author, version = CliArgs::unstable_version(), about, long_about)]
5#[command(name = "flake-edit")]
6#[command(next_line_help = true)]
7/// Edit your flake inputs with ease
8pub struct CliArgs {
9    /// Path to `flake.nix`, or a directory containing `flake.nix`.
10    /// Defaults to `flake.nix` in the current directory.
11    #[arg(long)]
12    flake: Option<String>,
13    /// Location of the `flake.lock` file.
14    /// Defaults to `flake.lock` in the current directory.
15    #[arg(long)]
16    lock_file: Option<String>,
17    /// Print a diff of the changes instead of writing them to disk.
18    #[arg(long, default_value_t = false)]
19    diff: bool,
20    /// Skip updating the lockfile after editing flake.nix.
21    #[arg(long, default_value_t = false)]
22    no_lock: bool,
23    /// Disable interactive prompts.
24    #[arg(long, default_value_t = false)]
25    non_interactive: bool,
26    /// Disable reading from and writing to the completion cache.
27    #[arg(long, default_value_t = false)]
28    no_cache: bool,
29    /// Path to a custom cache file.
30    #[arg(long)]
31    cache: Option<String>,
32    /// Path to a custom configuration file.
33    #[arg(long, global = true)]
34    config: Option<String>,
35
36    #[command(subcommand)]
37    subcommand: Command,
38}
39
40impl CliArgs {
41    /// Version string with embedded git revision and date, when available.
42    fn unstable_version() -> &'static str {
43        const VERSION: &str = env!("CARGO_PKG_VERSION");
44        let date = option_env!("GIT_DATE").unwrap_or("no_date");
45        let rev = option_env!("GIT_REV").unwrap_or("no_rev");
46        // Leaks per call. Only invoked once at startup.
47        Box::leak(format!("{VERSION} - {date} - {rev}").into_boxed_str())
48    }
49
50    pub fn subcommand(&self) -> &Command {
51        &self.subcommand
52    }
53
54    pub fn flake(&self) -> Option<&String> {
55        self.flake.as_ref()
56    }
57
58    pub fn lock_file(&self) -> Option<&String> {
59        self.lock_file.as_ref()
60    }
61
62    pub fn diff(&self) -> bool {
63        self.diff
64    }
65
66    pub fn no_lock(&self) -> bool {
67        self.no_lock
68    }
69
70    pub fn non_interactive(&self) -> bool {
71        self.non_interactive
72    }
73
74    pub fn no_cache(&self) -> bool {
75        self.no_cache
76    }
77
78    pub fn cache(&self) -> Option<&String> {
79        self.cache.as_ref()
80    }
81
82    pub fn config(&self) -> Option<&String> {
83        self.config.as_ref()
84    }
85}
86
87#[derive(Subcommand, Debug)]
88pub enum Command {
89    /// Add a new flake reference.
90    #[clap(alias = "a")]
91    Add {
92        /// The name of an input attribute.
93        id: Option<String>,
94        /// The uri that should be added to the input.
95        uri: Option<String>,
96        #[arg(long)]
97        /// Pin to a specific ref_or_rev
98        ref_or_rev: Option<String>,
99        /// The input itself is not a flake.
100        #[arg(long, short)]
101        no_flake: bool,
102        /// Use shallow clone for the input.
103        #[arg(long, short)]
104        shallow: bool,
105    },
106    /// Remove a specific flake reference based on its id.
107    #[clap(alias = "rm")]
108    Remove { id: Option<String> },
109    /// Change an existing flake reference's URI.
110    #[clap(alias = "c")]
111    Change {
112        /// The name of an existing input attribute.
113        id: Option<String>,
114        /// The new URI for the input.
115        uri: Option<String>,
116        #[arg(long)]
117        /// Pin to a specific ref_or_rev
118        ref_or_rev: Option<String>,
119        /// Use shallow clone for the input.
120        #[arg(long, short)]
121        shallow: bool,
122    },
123    /// List flake inputs
124    #[clap(alias = "l")]
125    List {
126        #[arg(long, value_enum, default_value_t = ListFormat::default())]
127        format: ListFormat,
128    },
129    /// Update inputs to their latest specified release.
130    #[clap(alias = "u")]
131    Update {
132        /// The id of an input attribute.
133        /// If omitted will update all inputs.
134        id: Option<String>,
135        /// Whether the latest semver release of the remote should be used even thought the release
136        /// itself isn't yet pinned to a specific release.
137        #[arg(long)]
138        init: bool,
139    },
140    /// Pin inputs to their current or a specified rev.
141    #[clap(alias = "p")]
142    Pin {
143        /// The id of an input attribute.
144        id: Option<String>,
145        /// Optionally specify a rev for the inputs attribute.
146        rev: Option<String>,
147    },
148    /// Unpin an input so it tracks the upstream default again.
149    #[clap(alias = "up")]
150    Unpin {
151        /// The id of an input attribute.
152        id: Option<String>,
153    },
154    /// Automatically add and remove follows declarations.
155    ///
156    /// Analyzes the flake.lock to find nested inputs that match top-level inputs,
157    /// then adds appropriate follows declarations and removes stale ones.
158    ///
159    /// With file paths, processes multiple flakes in batch.
160    /// For every `flake.nix` file passed in it will assume a
161    /// `flake.lock` file exists in the same directory.
162    #[clap(alias = "f")]
163    Follow {
164        /// Enable transitive follows deduplication, promoting shared nested
165        /// inputs to top-level when they appear at least N times.
166        /// Defaults to 2 if no value is given. Overrides the config file's
167        /// `follow.transitive_min`.
168        #[arg(long, num_args = 0..=1, default_missing_value = "2")]
169        transitive: Option<usize>,
170        /// Maximum depth of follows declarations to write.
171        /// Omitting the flag writes follows at every depth the lockfile
172        /// graph supports. `--depth N` caps emission: 1 writes only
173        /// `parent.child.follows`, 2 also writes
174        /// `parent.child.grandchild.follows`, and so on. Overrides the
175        /// config file's `follow.max_depth`.
176        #[arg(long)]
177        depth: Option<usize>,
178        /// Flake.nix paths to process. If empty, runs on current directory.
179        #[arg(trailing_var_arg = true, num_args = 0..)]
180        paths: Vec<std::path::PathBuf>,
181    },
182    /// Manually add a single follows declaration.
183    ///
184    /// Example: `flake-edit add-follow rust-overlay.nixpkgs nixpkgs`
185    ///
186    /// This creates: `rust-overlay.inputs.nixpkgs.follows = "nixpkgs";`
187    ///
188    /// Without arguments, starts an interactive selection.
189    #[clap(alias = "af")]
190    AddFollow {
191        /// The input path in dot notation (e.g., "rust-overlay.nixpkgs" means
192        /// the nixpkgs input of rust-overlay).
193        input: Option<String>,
194        /// The target input to follow (e.g., "nixpkgs").
195        target: Option<String>,
196    },
197    #[clap(hide = true)]
198    #[command(name = "completion")]
199    /// Meant for shell completions.
200    Completion {
201        #[arg(long)]
202        inputs: bool,
203        #[arg(value_enum)]
204        mode: CompletionMode,
205    },
206    /// Manage flake-edit configuration.
207    #[clap(alias = "cfg", arg_required_else_help = true)]
208    Config {
209        /// Output the default configuration to stdout.
210        #[arg(long)]
211        print_default: bool,
212        /// Show where configuration would be loaded from.
213        #[arg(long)]
214        path: bool,
215    },
216}
217
218/// Which subcommand to complete.
219#[derive(Debug, Clone, ValueEnum)]
220pub enum CompletionMode {
221    Add,
222    Change,
223    Follow,
224}
225
226/// Output format for the `list` subcommand.
227#[derive(Debug, Clone, Default, ValueEnum)]
228pub enum ListFormat {
229    Simple,
230    Toplevel,
231    #[default]
232    Detailed,
233    Json,
234}