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}