up_rs/opts.rs
1//! CLI options passed to `up` commands.
2mod paths;
3pub(crate) mod start_time;
4
5use crate::opts::paths::TempDir;
6use crate::opts::start_time::StartTime;
7use camino::Utf8PathBuf;
8use clap::Parser;
9use clap::ValueEnum;
10use clap::ValueHint;
11use clap_complete::Shell;
12use serde_derive::Deserialize;
13use serde_derive::Serialize;
14
15/// The default fallback path inside a fallback repo to look for the up.yaml file in.
16pub(crate) const FALLBACK_CONFIG_PATH: &str = "dotfiles/.config/up/up.yaml";
17/// URL to use to find the latest version of up.
18pub(crate) const LATEST_RELEASE_URL: &str =
19 "https://api.github.com/repos/gibfahn/up/releases/latest";
20#[cfg(target_os = "linux")]
21/// URL to use to download the latest release of up for Linux.
22pub(crate) const SELF_UPDATE_URL: &str =
23 "https://github.com/gibfahn/up/releases/latest/download/up-linux";
24#[cfg(target_os = "macos")]
25/// URL to use to download the latest release of up for macOS.
26pub(crate) const SELF_UPDATE_URL: &str =
27 "https://github.com/gibfahn/up/releases/latest/download/up-darwin";
28
29/// Builds the Args struct from CLI input and from environment variable input.
30#[must_use]
31pub fn parse() -> Opts {
32 Opts::parse()
33}
34
35// Don't complain about bare links in my clap document output.
36#[allow(clippy::doc_markdown, rustdoc::bare_urls)]
37/**
38Up is a tool to help you manage your developer machine. `up run` runs the tasks defined in its
39config directory. It handles linking configuration files into the right locations, and running
40scripts to make sure the tools you need are installed and up to date. It is designed to complete
41common bootstrapping tasks without dependencies, so you can bootstrap a new machine by:
42
43❯ curl --create-dirs -Lo ~/bin/up https://github.com/gibfahn/up/releases/latest/download/up-$(uname) && chmod +x ~/bin/up
44
45❯ ~/bin/up run --bootstrap --fallback-url https://github.com/gibfahn/dot
46
47Running `up` without a subcommand runs `up run` with no parameters, which is useful for
48post-bootstrapping, when you want to just run all your setup steps again, to make sure
49everything is installed and up-to-date. For this reason it's important to make your up tasks
50idempotent, so they skip if nothing is needed.
51
52There are also a number of libraries built into up, that can be accessed directly as well as via
53up task configs, e.g. `up link` to link dotfiles.
54
55For debugging, run with `RUST_LIB_BACKTRACE=1` to show error/panic traces.
56Logs from the latest run are available at $TMPDIR/up/logs/up_latest.log by default.
57*/
58#[derive(Debug, Parser)]
59#[clap(version)]
60pub struct Opts {
61 /// Set the logging level explicitly (options: Off, Error, Warn, Info,
62 /// Debug, Trace).
63 #[clap(
64 long,
65 short = 'l',
66 default_value = "up=info",
67 env = "RUST_LOG",
68 alias = "log-level"
69 )]
70 pub log: String,
71
72 /**
73 Temporary directory to use for logs, fifos, and other intermediate artifacts.
74 */
75 #[clap(long, env = "UP_TEMP_DIR", default_value_t, value_hint = ValueHint::DirPath, alias = "up-dir")]
76 pub temp_dir: TempDir,
77
78 /// Set the file logging level explicitly (options: Off, Error, Warn, Info,
79 /// Debug, Trace).
80 #[clap(long, default_value = "trace", env = "FILE_RUST_LOG")]
81 pub file_log_level: String,
82
83 /// Whether to color terminal output.
84 #[clap(long, default_value = "auto", ignore_case = true, value_enum)]
85 pub color: Color,
86
87 /// Path to the up.yaml file for up.
88 #[clap(long, short = 'c', default_value = "$XDG_CONFIG_HOME/up/up.yaml", value_hint = ValueHint::FilePath)]
89 pub(crate) config: String,
90
91 /**
92 The timestamp where we started this action.
93
94 Hidden as users shouldn't normally be setting this.
95 */
96 #[clap(long, hide(true), default_value_t)]
97 pub start_time: StartTime,
98
99 /// Clap subcommand to run.
100 #[clap(subcommand)]
101 pub(crate) cmd: Option<SubCommand>,
102}
103
104/// Settings for colouring output.
105#[derive(Debug, ValueEnum, Clone)]
106pub enum Color {
107 /// Auto: Colour on if stderr isatty, else off.
108 Auto,
109 /// Always: Always enable colours.
110 Always,
111 /// Never: Never enable colours.
112 Never,
113}
114
115/// Optional subcommand (e.g. the "link" in "up link").
116#[derive(Debug, Parser)]
117pub(crate) enum SubCommand {
118 /// Run the update scripts. If you don't provide a subcommand this is the default action.
119 /// If you want to pass Run args you will need to specify the subcommand.
120 Run(RunOptions),
121 /// Symlink your dotfiles from a git repo to your home directory.
122 Link(LinkOptions),
123 /// Clone or update a repo at a path.
124 Git(GitOptions),
125 /// Set macOS defaults in plist files.
126 Defaults(DefaultsOptions),
127 /// Generate up config from current system state.
128 Generate(GenerateOptions),
129 /// Update the up CLI itself.
130 Self_(UpdateSelfOptions),
131 /// Generate shell completions to stdout.
132 Completions(CompletionsOptions),
133 /// List available tasks.
134 List(RunOptions),
135 /// Write the up yaml schema.
136 Schema(SchemaOptions),
137}
138
139/// CLI options passed to `up run`.
140#[derive(Debug, Parser, Default)]
141pub(crate) struct RunOptions {
142 /// Run the bootstrap list of tasks in series first, then run the rest in
143 /// parallel. Designed for first-time setup.
144 #[clap(short, long)]
145 pub(crate) bootstrap: bool,
146 /// Keep going even if a bootstrap task fails.
147 #[clap(short, long)]
148 pub(crate) keep_going: bool,
149 /// Fallback git repo URL to download to get the config.
150 #[clap(short = 'f', long, value_hint = ValueHint::Url)]
151 pub(crate) fallback_url: Option<String>,
152 /// Fallback path inside the git repo to get the config.
153 /// The default path assumes your `fallback_url` points to a dotfiles repo
154 /// that is linked into ~.
155 #[clap(
156 short = 'p',
157 long,
158 default_value = FALLBACK_CONFIG_PATH,
159 value_hint = ValueHint::FilePath
160 )]
161 pub(crate) fallback_path: Utf8PathBuf,
162 /**
163 Optionally pass one or more tasks to run. The default is to run all
164 tasks. This option can be provided multiple times, or use a comma-separated list of values.
165
166 EXAMPLES:
167
168 ❯ up run --tasks=rust,apt --tasks=otherslowtask
169 */
170 #[clap(short = 't', long, value_delimiter = ',')]
171 pub(crate) tasks: Option<Vec<String>>,
172
173 /**
174 Tasks stdout/stderr inherit from up's stdout/stderr.
175
176 By default this is true if only one task is executed, and false otherwise.
177 Piping multiple commands to the stdout/stderr of the process will cause task output to be interleaved, which is very confusing when many tasks are run.
178 */
179 #[clap(long)]
180 pub(crate) console: Option<bool>,
181
182 /**
183 Optionally pass one or more tasks to exclude. The default is to exclude no
184 tasks. Excluded tasks are not run even if specified in `--tasks` (excluding takes
185 priority). This option can be provided multiple times. Tasks specified do not have to exist.
186
187 EXAMPLES:
188
189 ❯ up run --exclude-tasks=brew,slowtask --exclude-tasks=otherslowtask
190 */
191 #[clap(long, value_delimiter = ',')]
192 pub(crate) exclude_tasks: Option<Vec<String>>,
193}
194
195/// CLI options passed to `up link`.
196#[derive(Debug, Parser, Default, Serialize, Deserialize)]
197pub(crate) struct LinkOptions {
198 /// Path where your dotfiles are kept (hopefully in source control).
199 #[clap(short = 'f', long = "from", default_value = "~/code/dotfiles", value_hint = ValueHint::DirPath)]
200 pub(crate) from_dir: String,
201 /// Path to link them to.
202 #[clap(short = 't', long = "to", default_value = "~", value_hint = ValueHint::DirPath)]
203 pub(crate) to_dir: String,
204}
205
206/// CLI options passed to `up git`.
207#[derive(Debug, Default, Parser)]
208pub struct GitOptions {
209 /// URL of git repo to download.
210 #[clap(long, value_hint = ValueHint::Url)]
211 pub git_url: String,
212 /// Path to download git repo to.
213 #[clap(long, value_hint = ValueHint::DirPath)]
214 pub git_path: Utf8PathBuf,
215 /// Remote to set/update.
216 #[clap(long, default_value = crate::tasks::git::DEFAULT_REMOTE_NAME)]
217 pub remote: String,
218 /// Branch to checkout when cloning/updating. Defaults to default branch for
219 /// cloning, and current branch for updating.
220 #[clap(long)]
221 pub branch: Option<String>,
222 /// Prune merged PR branches. Deletes local branches where the push branch
223 /// has been merged into the upstream branch, and the push branch has now
224 /// been deleted.
225 #[clap(long)]
226 pub prune: bool,
227}
228
229/// Options passed to `up generate`.
230#[derive(Debug, Parser)]
231pub(crate) struct GenerateOptions {
232 /// Lib to generate.
233 #[clap(subcommand)]
234 pub(crate) lib: Option<GenerateLib>,
235}
236
237/// Options passed to `up schema`.
238#[derive(Debug, Parser)]
239pub(crate) struct SchemaOptions {
240 /// Lib to generate. Defaults to writing to stdout.
241 pub(crate) path: Option<Utf8PathBuf>,
242}
243
244/// CLI options passed to `up self`.
245#[derive(Debug, Parser, Serialize, Deserialize)]
246pub(crate) struct UpdateSelfOptions {
247 /// URL to download update from.
248 #[clap(long, default_value = SELF_UPDATE_URL, value_hint = ValueHint::Url)]
249 pub(crate) url: String,
250 /// Set to update self even if it seems to be a development install.
251 /// Assumes a dev install when the realpath of the current binary is in a
252 /// subdirectory of the cargo root path that the binary was originally built in.
253 #[clap(long)]
254 pub(crate) always_update: bool,
255}
256
257/// CLI options passed to `up completions`.
258#[derive(Debug, Parser)]
259pub(crate) struct CompletionsOptions {
260 /// Shell for which to generate completions.
261 #[clap(value_enum)]
262 pub(crate) shell: Shell,
263}
264
265impl Default for UpdateSelfOptions {
266 fn default() -> Self {
267 Self {
268 url: SELF_UPDATE_URL.to_owned(),
269 always_update: false,
270 }
271 }
272}
273
274/// Subcommands supported by `up generate`.
275#[derive(Debug, Parser)]
276pub(crate) enum GenerateLib {
277 /// Generate a git repo.
278 Git(GenerateGitConfig),
279 /// Generate macOS defaults commands (not yet implemented).
280 Defaults(GenerateDefaultsConfig),
281}
282
283/// Options
284#[derive(Debug, Parser, Serialize, Deserialize)]
285pub struct GenerateGitConfig {
286 /// Path to yaml file to update.
287 #[clap(long, value_hint = ValueHint::FilePath)]
288 pub(crate) path: Utf8PathBuf,
289 /// Paths to search within.
290 #[clap(long, default_value = "~", value_hint = ValueHint::DirPath)]
291 pub(crate) search_paths: Vec<Utf8PathBuf>,
292 /// Exclude paths containing this value. e.g. '/tmp/' to exclude anything in
293 /// a tmp dir.
294 #[clap(long)]
295 pub(crate) excludes: Option<Vec<String>>,
296 /// Prune all repos for branches that have already been merged and deleted
297 /// upstream.
298 #[clap(long)]
299 pub(crate) prune: bool,
300 /// Order to save remotes, other remotes will be included after those listed here.
301 #[clap(long)]
302 pub(crate) remote_order: Vec<String>,
303}
304
305/// Options passed to `up generate defaults`.
306#[derive(Debug, Parser, Serialize, Deserialize)]
307pub struct GenerateDefaultsConfig {
308 /// Path to yaml file to update.
309 #[clap(long, value_hint = ValueHint::FilePath)]
310 pub(crate) path: Utf8PathBuf,
311}
312
313/// Options passed to `up defaults`.
314#[derive(Debug, Parser, Serialize, Deserialize)]
315pub struct DefaultsOptions {
316 /// Read from the current host, same as `defaults -currentHost`.
317 #[clap(long = "currentHost")]
318 pub(crate) current_host: bool,
319 /// Defaults action to take.
320 #[clap(subcommand)]
321 pub(crate) subcommand: DefaultsSubcommand,
322}
323
324/// Subcommands supported by `up defaults`.
325#[derive(Debug, Parser, Serialize, Deserialize)]
326pub enum DefaultsSubcommand {
327 /// Read a defaults option and print it to the stdout as yaml.
328 Read(DefaultsReadOptions),
329 /**
330 Write a yaml-encoded value to a defaults plist file.
331 A domain, key, and value must be provided (you can optionally use `-g` to specify the global domain).
332 */
333 Write(DefaultsWriteOptions),
334}
335
336/// CLI options passed to `up defaults read`.
337#[derive(Debug, Parser, Serialize, Deserialize)]
338pub struct DefaultsReadOptions {
339 /// Read from the global domain. If you set this, do not also pass a domain argument.
340 #[clap(short = 'g', long = "globalDomain")]
341 pub(crate) global_domain: bool,
342 /**
343 Defaults domain to print. Use `-` to read from stdin.
344 */
345 pub(crate) domain: Option<String>,
346 /// Defaults key to print.
347 pub(crate) key: Option<String>,
348}
349
350/// CLI options passed to `up defaults write`.
351#[derive(Debug, Parser, Serialize, Deserialize)]
352pub struct DefaultsWriteOptions {
353 /// Read from the global domain. If you set this, do not also pass a domain argument.
354 #[clap(short = 'g', long = "globalDomain")]
355 pub(crate) global_domain: bool,
356 /// Defaults domain to write to.
357 pub(crate) domain: String,
358 /// Defaults key to write to.
359 pub(crate) key: String,
360 /**
361 Value to write (as a yaml string).
362
363 If you want to append to an existing array or dictionary, use `...` as an array value, or `...:...` as a dictionary entry, to represent the existing items in the array.
364 If there are duplicates, the first entry will be preserved.
365
366 So if the array contained `["a", "foo", "b", "bar", "c"]`, and you write `["foo", "...", "bar", "baz"]`, you would end up with `["foo", "a", "b", "bar", "c", "baz"]`
367
368 Similarly if the dict contained `{"a": 1, "foo": 2, "b": 3, "bar": 4, "c": 5}`, and you write `{"foo": 6 "...":"...", "bar": 7, "baz": 8}`, you would end up with `{"a": 1, "foo": 6, "b": 3, "bar": 4, "c": 5, "baz": 8}`
369 */
370 pub(crate) value: Option<String>,
371}