commit c654f6f8b1ab8033d096b4d2500b56511139e2b0
parent 15622e294abc79e212c237a8f39f117b9b9a1770
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date: Sun, 7 Jul 2024 13:05:30 -0700
Implement --list-tags flag
Diffstat:
5 files changed, 116 insertions(+), 19 deletions(-)
diff --git a/README.md b/README.md
@@ -36,6 +36,7 @@ coliru manifest.yml --tag-rules tag1 tag2,tag3 ^tag4
Some other helpful options include:
- `--help`, `-h`: Print full help information
+- `--list-tags`, `-l`: List the tags in the manifest and quit without installing
- `--dry-run`, `-n`: Do a trial run without any permanent changes
- `--copy`: Interpret link commands as copy commands
- `--host <HOST>`: Install dotfiles on another machine over SSH
diff --git a/src/cli.rs b/src/cli.rs
@@ -1,9 +1,11 @@
//! The coliru command line interface
+use anyhow::{Context, Result};
use colored::{Colorize, control::set_override};
use clap::{Parser, ColorChoice};
use std::path::Path;
-use super::core::execute_manifest_file;
+use super::core::{install_manifest, list_tags};
+use super::manifest::parse_manifest_file;
/// CLI about description
const HELP_ABOUT: &str = "A minimal, flexible, dotfile installer";
@@ -11,10 +13,16 @@ const HELP_ABOUT: &str = "A minimal, flexible, dotfile installer";
/// CLI examples to be appended to help output
const HELP_EXAMPLES: &str = "\
Examples:
- # Install dotfiles from manifest.yml with tags matching A && (B || C) && !D
+ # List tags in manifest
+ coliru manifest.yml --list-tags
+
+ # Preview installation steps with tags matching A && (B || C) && !D
+ coliru manifest.yml --tag-rules A B,C ^D --dry-run
+
+ # Install dotfiles on local machine
coliru manifest.yml --tag-rules A B,C ^D
- # Install dotfiles from manifest.yml to user@hostname over SSH
+ # Install dotfiles to user@hostname over SSH
coliru manifest.yml --tag-rules A B,C ^D --host user@hostname";
/// Arguments to the coliru CLI
@@ -29,6 +37,10 @@ struct Args {
#[arg(short, long, value_name="RULE", num_args=0..)]
pub tag_rules: Vec<String>,
+ /// List available tags and quit without installing
+ #[arg(short, long)]
+ pub list_tags: bool,
+
/// Do a trial run without any permanent changes
#[arg(short = 'n', long)]
pub dry_run: bool,
@@ -49,13 +61,8 @@ struct Args {
/// Runs the coliru CLI
pub fn run() {
let args = Args::parse();
- let manifest_path = Path::new(&args.manifest);
- if args.no_color {
- set_override(false);
- }
- match execute_manifest_file(&manifest_path, args.tag_rules, &args.host,
- args.dry_run, args.copy) {
+ match run_args(args) {
Err(why) => {
eprintln!("{} {:#}", "Error:".bold().red(), why);
std::process::exit(2);
@@ -65,3 +72,24 @@ pub fn run() {
},
}
}
+
+/// Runs the coliru CLI according to a set of arguments
+///
+/// Returns an Err if a critical occurs, Ok(true) if minor errors occurred, and
+/// Ok(false) if no errors occurred.
+fn run_args(args: Args) -> Result<bool> {
+ if args.no_color {
+ set_override(false);
+ }
+
+ let manifest = parse_manifest_file(Path::new(&args.manifest))
+ .context("Failed to parse manifest")?;
+
+ if args.list_tags {
+ list_tags(manifest);
+ Ok(false)
+ } else {
+ install_manifest(manifest, args.tag_rules, &args.host, args.dry_run,
+ args.copy)
+ }
+}
diff --git a/src/core.rs b/src/core.rs
@@ -1,10 +1,10 @@
-//! Manifest execution functions
+//! Core manifest operation functions
use anyhow::{Context, Result};
use colored::{Colorize, ColoredString};
use std::env::set_current_dir;
use std::path::Path;
-use super::manifest::{CopyLinkOptions, RunOptions, parse_manifest_file};
+use super::manifest::{Manifest, CopyLinkOptions, RunOptions, get_tags};
use super::tags::tags_match;
use super::local::{copy_file, link_file, run_command};
use super::ssh::{resolve_path, send_command, send_staged_files, stage_file};
@@ -37,15 +37,20 @@ fn handle_error(result: Result<()>) -> bool {
false
}
-/// Executes the steps in a coliru manifest file according to a set of tag rules
+/// Prints the available tags in a manifest
+pub fn list_tags(manifest: Manifest) {
+ for tag in get_tags(manifest) {
+ println!("{}", tag);
+ }
+}
+
+/// Executes the steps in a coliru manifest according to a set of tag rules
///
-/// Returns an Err if a critical err occurs and returns a bool indicating
+/// Returns an Err if a critical error occurs and returns a bool indicating
/// whether any minor errors occurred otherwise
-pub fn execute_manifest_file(path: &Path, tag_rules: Vec<String>, host: &str,
- dry_run: bool, copy: bool) -> Result<bool> {
+pub fn install_manifest(manifest: Manifest, tag_rules: Vec<String>, host: &str,
+ dry_run: bool, copy: bool) -> Result<bool> {
- let manifest = parse_manifest_file(path)
- .context("Failed to parse manifest")?;
let temp_dir = tempdir().context("Failed to create temporary directory")?;
set_current_dir(manifest.base_dir)
.context("Failed to set working directory")?;
diff --git a/src/manifest.rs b/src/manifest.rs
@@ -3,6 +3,7 @@
use anyhow::Result;
use serde::Deserialize;
use serde_yaml;
+use std::collections::HashSet;
use std::fs::read_to_string;
use std::path::{Path, PathBuf};
@@ -88,6 +89,21 @@ pub fn parse_manifest_file(path: &Path) -> Result<Manifest> {
})
}
+/// Returns a sorted, de-duplicated vector of all tags in a manifest
+pub fn get_tags(manifest: Manifest) -> Vec<String> {
+ let mut tag_set: HashSet<String> = HashSet::new();
+
+ for step in manifest.steps {
+ for tag in step.tags {
+ tag_set.insert(tag);
+ }
+ }
+
+ let mut tags: Vec<String> = tag_set.iter().map(|s| s.to_owned()).collect();
+ tags.sort();
+ tags
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -196,4 +212,28 @@ mod tests {
assert_eq!(actual.is_ok(), true);
assert_eq!(actual.unwrap(), expected);
}
+
+ #[test]
+ fn get_tags_basic() {
+ let manifest_path = Path::new("examples/test/manifest.yml");
+ let manifest = parse_manifest_file(manifest_path).unwrap();
+ let expected = vec![
+ String::from("linux"),
+ String::from("macos"),
+ String::from("windows"),
+ ];
+ let actual = get_tags(manifest);
+ assert_eq!(actual, expected);
+ }
+
+ #[test]
+ fn get_tags_empty() {
+ let manifest = Manifest {
+ steps: vec![],
+ base_dir: PathBuf::from("examples/test/empty.yml"),
+ };
+ let expected: Vec<String> = vec![];
+ let actual = get_tags(manifest);
+ assert_eq!(actual, expected);
+ }
}
diff --git a/tests/basic.rs b/tests/basic.rs
@@ -19,6 +19,7 @@ Arguments:
Options:
-t, --tag-rules [<RULE>...] The set of tag rules to enforce
+ -l, --list-tags List available tags and quit without installing
-n, --dry-run Do a trial run without any permanent changes
--host <HOST> Install dotfiles on another machine over SSH
--copy Interpret link commands as copy commands
@@ -27,10 +28,16 @@ Options:
-V, --version Print version
Examples:
- # Install dotfiles from manifest.yml with tags matching A && (B || C) && !D
+ # List tags in manifest
+ coliru manifest.yml --list-tags
+
+ # Preview installation steps with tags matching A && (B || C) && !D
+ coliru manifest.yml --tag-rules A B,C ^D --dry-run
+
+ # Install dotfiles on local machine
coliru manifest.yml --tag-rules A B,C ^D
- # Install dotfiles from manifest.yml to user@hostname over SSH
+ # Install dotfiles to user@hostname over SSH
coliru manifest.yml --tag-rules A B,C ^D --host user@hostname
");
let (stdout, stderr, exitcode) = run_command(&mut cmd);
@@ -167,3 +174,19 @@ fn test_basic_absolute_manifest() {
assert_eq!(foo_exists, true);
assert_eq!(log_exists, false);
}
+
+#[test]
+fn test_basic_list_tags() {
+ let (_dirs, mut cmd) = setup_e2e_local("test_basic_list_tags");
+ cmd.args(["manifest.yml", "--list-tags", "-t", "windows"]);
+
+ let expected = "\
+linux
+macos
+windows
+";
+ let (stdout, stderr, exitcode) = run_command(&mut cmd);
+ assert_eq!(&stderr, "");
+ assert_eq!(&stdout, expected);
+ assert_eq!(exitcode, Some(0));
+}