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 /// Toggle an input between its active url and a stored alternate.
155 ///
156 /// Alternates live as commented copies of the url line next to the
157 /// active one. Flipping moves only the comment marker.
158 #[clap(alias = "t")]
159 Toggle {
160 /// An input id, or a flake reference to activate.
161 #[arg(value_name = "INPUT")]
162 input: Option<String>,
163 /// The variant to activate, stored as a new alternate when not
164 /// yet present.
165 #[arg(value_name = "REF")]
166 reference: Option<String>,
167 /// Remove a url instead of activating it. A bare input id removes the
168 /// active url and activates the stored alternate in its place. An
169 /// alternate's ref deletes that alternate and keeps the active url.
170 #[arg(short, long)]
171 remove: bool,
172 },
173 /// Automatically add and remove follows declarations.
174 ///
175 /// Analyzes the flake.lock to find nested inputs that match top-level inputs,
176 /// then adds appropriate follows declarations and removes stale ones.
177 ///
178 /// With file paths, processes multiple flakes in batch.
179 /// For every `flake.nix` file passed in it will assume a
180 /// `flake.lock` file exists in the same directory.
181 #[clap(alias = "f")]
182 Follow {
183 /// Enable transitive follows deduplication, promoting shared nested
184 /// inputs to top-level when they appear at least N times.
185 /// Defaults to 2 if no value is given. Overrides the config file's
186 /// `follow.transitive_min`.
187 #[arg(long, num_args = 0..=1, default_missing_value = "2")]
188 transitive: Option<usize>,
189 /// Maximum depth of follows declarations to write.
190 /// Omitting the flag writes follows at every depth the lockfile
191 /// graph supports. `--depth N` caps emission: 1 writes only
192 /// `parent.child.follows`, 2 also writes
193 /// `parent.child.grandchild.follows`, and so on. Overrides the
194 /// config file's `follow.max_depth`.
195 #[arg(long)]
196 depth: Option<usize>,
197 /// Show size gain estimates that follows deduplication brought.
198 #[arg(long, short = 's')]
199 stats: bool,
200 /// Preview what `--stats` would show without applying changes.
201 #[arg(long)]
202 dry_run: bool,
203 /// `flake.nix` files or directories containing one.
204 /// If empty, runs on current directory.
205 #[arg(trailing_var_arg = true, num_args = 0..)]
206 paths: Vec<std::path::PathBuf>,
207 },
208 /// Manually add a single follows declaration.
209 ///
210 /// Example: `flake-edit add-follow rust-overlay.nixpkgs nixpkgs`
211 ///
212 /// This creates: `rust-overlay.inputs.nixpkgs.follows = "nixpkgs";`
213 ///
214 /// Without arguments, starts an interactive selection.
215 #[clap(alias = "af")]
216 AddFollow {
217 /// The input path in dot notation (e.g., "rust-overlay.nixpkgs" means
218 /// the nixpkgs input of rust-overlay).
219 input: Option<String>,
220 /// The target input to follow (e.g., "nixpkgs").
221 target: Option<String>,
222 },
223 /// Manage flake-edit configuration.
224 #[clap(alias = "cfg", arg_required_else_help = true)]
225 Config {
226 /// Output the default configuration to stdout.
227 #[arg(long)]
228 print_default: bool,
229 /// Show where configuration would be loaded from.
230 #[arg(long)]
231 path: bool,
232 },
233}
234
235/// Output format for the `list` subcommand.
236#[derive(Debug, Clone, Default, ValueEnum)]
237pub enum ListFormat {
238 Simple,
239 Toplevel,
240 #[default]
241 Detailed,
242 Json,
243}