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}