diff --git a/README.md b/README.md index 1c04038..f82e712 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ step2 0 0 4 96 800 GPU-hours ``` ```bash -$ row show directories step1 -n 3 --value="/value" +$ row show directories --action step1 -n 3 --value="/value" Directory Status Job ID /value dir1 completed 116 dir10 completed 952 diff --git a/demo/demo.sh b/demo/demo.sh index ce9501a..631b1c3 100644 --- a/demo/demo.sh +++ b/demo/demo.sh @@ -16,4 +16,4 @@ row submit --action=step1 -n 1 --yes || exit 1 row show status || exit 1 -row show directories step1 -n 3 --value="/value" +row show directories --action step1 -n 3 --value="/value" diff --git a/doc/src/contributors.md b/doc/src/contributors.md index aac47b2..97321a6 100644 --- a/doc/src/contributors.md +++ b/doc/src/contributors.md @@ -5,3 +5,4 @@ The following people have contributed to the development of **row**: * Joshua A. Anderson, University of Michigan * Jen Bradley, University of Michigan * Joseph Burkhart, University of Michigan +* Tim Moore, University of Michigan diff --git a/doc/src/guide/tutorial/group.sh b/doc/src/guide/tutorial/group.sh index 94b61b7..d8232f2 100644 --- a/doc/src/guide/tutorial/group.sh +++ b/doc/src/guide/tutorial/group.sh @@ -20,23 +20,23 @@ cd .. cp ../group-workflow2.toml workflow.toml # ANCHOR: show_point1 -row show directories process_point --value /type --value /x --value /y +row show directories --action process_point --value /type --value /x --value /y # ANCHOR_END: show_point1 # ANCHOR: show_letter -row show directories process_letter --value /type --value /letter +row show directories --action process_letter --value /type --value /letter # ANCHOR_END: show_letter cp ../group-workflow3.toml workflow.toml # ANCHOR: show_point2 -row show directories process_point --value /type --value /x --value /y +row show directories --action process_point --value /type --value /x --value /y # ANCHOR_END: show_point2 cp ../group-workflow4.toml workflow.toml # ANCHOR: show_point3 -row show directories process_point --value /type --value /x --value /y +row show directories --action process_point --value /type --value /x --value /y # ANCHOR_END: show_point3 # ANCHOR: submit @@ -46,5 +46,5 @@ row submit -a process_point cp ../group-workflow5.toml workflow.toml # ANCHOR: show_point4 -row show directories process_point --value /type --value /x --value /y +row show directories --action process_point --value /type --value /x --value /y # ANCHOR_END: show_point4 diff --git a/doc/src/guide/tutorial/hello.sh b/doc/src/guide/tutorial/hello.sh index e73d4a5..417a548 100644 --- a/doc/src/guide/tutorial/hello.sh +++ b/doc/src/guide/tutorial/hello.sh @@ -26,7 +26,7 @@ row submit directory1 # ANCHOR_END: submit2 # ANCHOR: directories_hello -row show directories hello +row show directories --action hello # ANCHOR_END: directories_hello # ANCHOR: submit3 diff --git a/doc/src/guide/tutorial/submit.md b/doc/src/guide/tutorial/submit.md index 445d46e..31ff5b9 100644 --- a/doc/src/guide/tutorial/submit.md +++ b/doc/src/guide/tutorial/submit.md @@ -122,7 +122,7 @@ hello 0 3 0 0 3 CPU-hours Similarly, ```bash -row show directories hello +row show directories --action hello ``` will show something like: ```plaintext diff --git a/doc/src/release-notes.md b/doc/src/release-notes.md index 763f23e..3eff639 100644 --- a/doc/src/release-notes.md +++ b/doc/src/release-notes.md @@ -8,6 +8,7 @@ * New arguments to `show status` display actions that are in the requested states: `--completed`, `--eligible`, `--submitted`, and `--waiting`. * `cluster.submit_options` configuration option in `clusters.toml`. +* `--short` option to `show launchers` and `show directories`. *Changed:* @@ -18,10 +19,13 @@ * `clean` now cleans all caches by default. * Submit jobs with `--constraint="scratch"` by default on Delta. * Submit jobs with `--constraint="nvme"` by default on Frontier. +* Change `--name` option of `show cluster` to `--short`. +* `show directories` now accepts an optional `--action` argument. *Fixed:* * `submit_whole = true` checks only directories that match `group.include`. +* Do not print trailing spaces after the final column in tabular output. ## 0.2.0 (2024-06-18) diff --git a/doc/src/row/show/cluster.md b/doc/src/row/show/cluster.md index 98b6b4e..47d9280 100644 --- a/doc/src/row/show/cluster.md +++ b/doc/src/row/show/cluster.md @@ -13,9 +13,9 @@ Print the [current cluster configuration](../../clusters/index.md) in TOML forma Show the configuration of all clusters: both user-defined and built-in. -### `--name` +### `--short` -Show only the cluster's name. +Show only the name of the matching cluster(s). ## Examples diff --git a/doc/src/row/show/directories.md b/doc/src/row/show/directories.md index 0b79087..c8a0824 100644 --- a/doc/src/row/show/directories.md +++ b/doc/src/row/show/directories.md @@ -2,12 +2,14 @@ Usage: ```bash -row show directories [OPTIONS] [DIRECTORIES] +row show directories [OPTIONS] [DIRECTORIES] ``` -`row show directories` lists each selected directory with its +`row show directories` lists each selected directory. + +When provided an action, `row show directories` also shows each directory's [status](../../guide/concepts/status.md) and scheduler job ID (when submitted) for the -given ``. You can also show elements from the directory's value, accessed by +given action. You can also show elements from the directory's value, accessed by [JSON pointer](../../guide/concepts/json-pointers.md). Blank lines separate [groups](../../workflow/action/group.md). @@ -22,11 +24,15 @@ the action's [include condition](../../workflow/action/group.md#include) Pass a single `-` to read the directories from stdin (separated by newlines): ```bash -echo "dir1" | row show directories action - +echo "dir1" | row show directories [OPTIONS] - ``` ## `[OPTIONS]` +### `--action` + +Select directories that are included by the provided action. + ### `--completed` Show directories with the *completed* status. @@ -49,6 +55,10 @@ Hide the header in the output. Do not write blank lines between groups. +### `--short` + +Show only the directory names. + ### `--submitted` Show directories with the *submitted* status. @@ -67,17 +77,25 @@ Show directories with the *waiting* status. * Show all the directories for action `one`: ```bash - row show directories one + row show directories --action one ``` * Show the directory value element `/value`: ```bash - row show directories action --value=/value + row show directories --action action --value=/value ``` * Show specific directories: ```bash - row show directories action directory1 directory2 + row show directories --action action directory1 directory2 ``` * Show eligible directories ```bash - row show directories action --eligible + row show directories --action action --eligible + ``` +* Show the names of all directories + ```bash + row show directories + ``` +* Show the names of eligible directories + ```bash + row show directories --action action --eligible --short ``` diff --git a/doc/src/row/show/launchers.md b/doc/src/row/show/launchers.md index 0ade229..656d6ca 100644 --- a/doc/src/row/show/launchers.md +++ b/doc/src/row/show/launchers.md @@ -17,6 +17,10 @@ and the built-in launchers. Show the launcher configurations for all clusters. +### `--short` + +Show only the names of the launchers. + ## Examples * Show the launchers for the autodetected cluster: @@ -31,3 +35,7 @@ Show the launcher configurations for all clusters. ```bash row show launchers --all ``` +* Show only names of all launchers: + ```bash + row show launchers --all --short + ``` diff --git a/doc/src/row/submit.md b/doc/src/row/submit.md index 8b15404..b332501 100644 --- a/doc/src/row/submit.md +++ b/doc/src/row/submit.md @@ -10,7 +10,7 @@ row submit [OPTIONS] [DIRECTORIES] actions. Then it forms [groups](../workflow/action/group.md) and submits one job for each group. Pass `--dry-run` to see the script(s) that will be submitted. Execute ``` -row show directories action --eligible +row show directories --action action --eligible ``` to see the specific directory groups that will be submitted. diff --git a/doc/src/signac-flow.md b/doc/src/signac-flow.md index e36fc33..918c0ee 100644 --- a/doc/src/signac-flow.md +++ b/doc/src/signac-flow.md @@ -21,7 +21,7 @@ Commands: | flow | row | |------|-----| | `project.py status` | [`row show status`](row/show/status.md) | -| `project.py status --detailed` | [`row show directories `](row/show/directories.md) | +| `project.py status --detailed` | [`row show directories --action action`](row/show/directories.md) | | `project.py run` | [`row submit --cluster=none`](row/submit.md) | | `project.py run --parallel` | A command *may* execute [group members][group] in [parallel]. | | `project.py exec ...` | Execute your action's command in the shell. | diff --git a/doc/src/workflow/action/group.md b/doc/src/workflow/action/group.md index 40a414b..6b85c0e 100644 --- a/doc/src/workflow/action/group.md +++ b/doc/src/workflow/action/group.md @@ -17,7 +17,7 @@ condition = ["/subproject", "==", "project_one"] > Note: You may omit `[action.group]` entirely. -Execute [`row show directories `](../../row/show/directories.md) to display the +Execute [`row show directories --action action`](../../row/show/directories.md) to display the groups of directories included in a given action. ## include diff --git a/src/cli.rs b/src/cli.rs index e575bf9..9524da0 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -102,32 +102,42 @@ pub enum ShowCommands { /// List directories in the workspace. /// - /// `row show directories` lists each selected directory with its status - /// and scheduler job ID (when submitted). for the given `. You - /// can also show elements from the directory's value, accessed by JSON - /// pointer. Blank lines separate groups. + /// `row show directories` lists each selected directory. /// - /// By default, `row show status` displays directories with any status. Set - /// one or more of `--completed`, `--submitted`, `--eligible`, and - /// `--waiting` to show specific directories that have specific statuses. + /// When provided an action, `row show directories` also shows each + /// directory's status and scheduler job ID (when submitted) for the given + /// action. You can also show elements from the directory's value, accessed + /// by JSON pointer. Blank lines separate groups. + /// + /// By default, `row show status` displays directories with any status. Set one or more + /// of `--completed`, `--submitted`, `--eligible`, and `--waiting` to show specific + /// directories that have specific statuses. /// /// EXAMPLES /// /// * Show all the directories for action `one`: /// - /// row show directories one + /// row show directories --action one /// /// * Show the directory value element `/value`: /// - /// row show directories action --value=/value + /// row show directories --action action --value=/value /// /// * Show specific directories: /// - /// row show directories action directory1 directory2 + /// row show directories --action action directory1 directory2 /// /// * Show eligible directories /// - /// row show directories action --eligible + /// row show directories --action action --eligible + /// + /// * Show the names of all directories + /// + /// row show directories + /// + /// * Show the names of eligible directories + /// + /// row show directories --action action --eligible --short /// Directories(directories::Arguments), @@ -173,6 +183,9 @@ pub enum ShowCommands { /// /// row show launchers --all /// + ///* Show only names of all launchers: + /// + /// row show launchers --all --short Launchers(launchers::Arguments), } diff --git a/src/cli/cluster.rs b/src/cli/cluster.rs index b3f82e7..96a09bf 100644 --- a/src/cli/cluster.rs +++ b/src/cli/cluster.rs @@ -12,12 +12,12 @@ use row::cluster; #[derive(Args, Debug)] pub struct Arguments { /// Show all clusters. - #[arg(long, group = "select", display_order = 0)] + #[arg(long, display_order = 0)] all: bool, - /// Show only the autodetected cluster's name. - #[arg(long, group = "select", display_order = 0)] - name: bool, + /// Show only the cluster name(s). + #[arg(long, display_order = 0)] + short: bool, } /// Show the cluster. @@ -34,13 +34,19 @@ pub fn cluster( let clusters = cluster::Configuration::open()?; if args.all { - info!("All cluster configurations:"); - write!(output, "{}", &toml::to_string_pretty(&clusters)?)?; + if args.short { + for cluster in clusters.cluster { + writeln!(output, "{}", cluster.name)?; + } + } else { + info!("All cluster configurations:"); + write!(output, "{}", &toml::to_string_pretty(&clusters)?)?; + } } else { let cluster = clusters.identify(options.cluster.as_deref())?; info!("Cluster configurations for '{}':", cluster.name); - if args.name { + if args.short { writeln!(output, "{}", cluster.name)?; } else { write!(output, "{}", &toml::to_string_pretty(&cluster)?)?; diff --git a/src/cli/directories.rs b/src/cli/directories.rs index 38d030a..04c5a9f 100644 --- a/src/cli/directories.rs +++ b/src/cli/directories.rs @@ -17,50 +17,59 @@ use row::MultiProgressContainer; #[derive(Args, Debug)] #[allow(clippy::struct_excessive_bools)] pub struct Arguments { - /// Select the action to scan (defaults to all). - action: String, - /// Select directories to summarize (defaults to all). Use 'show directories -' to read from stdin. directories: Vec, + /// Select directories that are included by the provided action. + #[arg(long, short, display_order = 0)] + action: Option, + /// Hide the table header. - #[arg(long, display_order = 0)] + #[arg(long, display_order = 0, requires = "action")] no_header: bool, /// Do not separate groups with newlines. - #[arg(long, display_order = 0)] + #[arg(long, display_order = 0, requires = "action")] no_separate_groups: bool, /// Show an element of each directory's value (repeat to show multiple elements). - #[arg(long, value_name = "JSON POINTER", display_order = 0)] + #[arg( + long, + value_name = "JSON POINTER", + display_order = 0, + requires = "action" + )] value: Vec, /// Limit the number of groups displayed. - #[arg(short, long, display_order = 0)] + #[arg(short, long, display_order = 0, requires = "action")] n_groups: Option, /// Show completed directories. - #[arg(long, display_order = 0)] + #[arg(long, display_order = 0, requires = "action")] completed: bool, /// Show submitted directories. - #[arg(long, display_order = 0)] + #[arg(long, display_order = 0, requires = "action")] submitted: bool, /// Show eligible directories. - #[arg(long, display_order = 0)] + #[arg(long, display_order = 0, requires = "action")] eligible: bool, /// Show waiting directories. - #[arg(long, display_order = 0)] + #[arg(long, display_order = 0, requires = "action")] waiting: bool, + + /// Show only directory names. + #[arg(long, default_value_t = false, display_order = 0, requires = "action")] + short: bool, } /// Show directories that match an action. /// /// Print a human-readable list of directories, their status, job ID, and value(s). /// -#[allow(clippy::too_many_lines)] pub fn directories( options: &GlobalOptions, args: Arguments, @@ -68,7 +77,20 @@ pub fn directories( output: &mut W, ) -> Result<(), Box> { debug!("Showing directories."); + match &args.action { + Some(action) => print_matching(&action.clone(), options, args, multi_progress, output), + None => print_all(options, args, multi_progress, output), + } +} +#[allow(clippy::too_many_lines)] +pub fn print_matching( + action_name: &str, + options: &GlobalOptions, + args: Arguments, + multi_progress: &mut MultiProgressContainer, + output: &mut W, +) -> Result<(), Box> { // Show directories with selected statuses. let mut show_completed = args.completed; let mut show_submitted = args.submitted; @@ -88,10 +110,10 @@ pub fn directories( project .workflow() - .action_by_name(&args.action) - .ok_or_else(|| row::Error::ActionNotFound(args.action.clone()))?; + .action_by_name(action_name) + .ok_or_else(|| row::Error::ActionNotFound(action_name.to_string()))?; - let mut table = Table::new().with_hide_header(args.no_header); + let mut table = Table::new().with_hide_header(if args.short { true } else { args.no_header }); table.header = vec![ Item::new("Directory".to_string(), Style::new().underlined()), Item::new("Status".to_string(), Style::new().underlined()), @@ -108,7 +130,7 @@ pub fn directories( } for action in &project.workflow().action { - if action.name() != args.action { + if action.name() != action_name { continue; } @@ -166,6 +188,12 @@ pub fn directories( Style::new().bold(), )); + // Only show directory names when user requests short output. + if args.short { + table.rows.push(Row::Items(row)); + continue; + } + // Status row.push(status); @@ -201,12 +229,14 @@ pub fn directories( table.rows.push(Row::Items(row)); } - if !args.no_separate_groups && group_idx != groups.len() - 1 { + if !args.no_separate_groups && group_idx != groups.len() - 1 && !args.short { table.rows.push(Row::Separator); } } - table.rows.push(Row::Separator); + if !args.short { + table.rows.push(Row::Separator); + } } table.write(output)?; @@ -216,3 +246,31 @@ pub fn directories( Ok(()) } + +pub fn print_all( + options: &GlobalOptions, + args: Arguments, + multi_progress: &mut MultiProgressContainer, + output: &mut W, +) -> Result<(), Box> { + let project = Project::open(options.io_threads, &options.cluster, multi_progress)?; + + let all_directories = project.state().list_directories(); + let mut query_directories = + cli::parse_directories(args.directories, || Ok(all_directories.clone()))?; + query_directories.sort_unstable(); + let all_directories = HashSet::::from_iter(all_directories); + + for directory in &query_directories { + if !all_directories.contains(directory) { + warn!( + "Directory '{}' not found in workspace.", + directory.display() + ); + continue; + } + writeln!(output, "{}", directory.display())?; + } + + Ok(()) +} diff --git a/src/cli/launchers.rs b/src/cli/launchers.rs index 8988ee5..b3ace21 100644 --- a/src/cli/launchers.rs +++ b/src/cli/launchers.rs @@ -15,6 +15,10 @@ pub struct Arguments { /// Show all launchers. #[arg(long, display_order = 0)] all: bool, + + /// Show only launcher names. + #[arg(long, display_order = 0, conflicts_with = "all")] + short: bool, } /// Show the launchers. @@ -41,12 +45,18 @@ pub fn launchers( let clusters = cluster::Configuration::open()?; let cluster = clusters.identify(options.cluster.as_deref())?; - info!("Launcher configurations for cluster '{}':", cluster.name); - write!( - output, - "{}", - &toml::to_string_pretty(&launchers.by_cluster(&cluster.name))? - )?; + if args.short { + for launcher_name in launchers.by_cluster(&cluster.name).keys() { + writeln!(output, "{launcher_name}")?; + } + } else { + info!("Launcher configurations for cluster '{}':", cluster.name); + write!( + output, + "{}", + &toml::to_string_pretty(&launchers.by_cluster(&cluster.name))? + )?; + } } Ok(()) diff --git a/src/cluster.rs b/src/cluster.rs index d245eca..d8a1517 100644 --- a/src/cluster.rs +++ b/src/cluster.rs @@ -24,7 +24,7 @@ use crate::Error; pub struct Configuration { /// The cluster configurations. #[serde(default)] - pub(crate) cluster: Vec, + pub cluster: Vec, } /// Cluster diff --git a/src/lib.rs b/src/lib.rs index fef65c2..ce5ff8e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -182,7 +182,7 @@ pub enum Error { // cluster errors #[error( - "Cluster '{0}' not found: execute 'row show cluster --all' to see available clusters." + "Cluster '{0}' not found: execute 'row show cluster --all --short' to see available clusters." )] ClusterNameNotFound(String), diff --git a/src/ui.rs b/src/ui.rs index 02a3dd5..a849ded 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -132,7 +132,13 @@ impl Table { fn write_row(writer: &mut W, row: &[Item], column_width: &[usize]) -> io::Result<()> { for (i, item) in row.iter().enumerate() { let text = match item.alignment { - Alignment::Left => format!("{: { + if i == row.len() - 1 { + item.text.clone() + } else { + format!("{: format!("{:>width$}", &item.text, width = column_width[i]), }; diff --git a/tests/cli.rs b/tests/cli.rs index 0ec30b6..1156e46 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -542,7 +542,7 @@ fn submit() -> Result<(), Box> { #[parallel] fn directories_no_action() -> Result<(), Box> { let temp = TempDir::new()?; - let _ = setup_sample_workflow(&temp, 10); + let _ = setup_sample_workflow(&temp, 4); Command::cargo_bin("row")? .args(["show", "directories"]) @@ -552,8 +552,8 @@ fn directories_no_action() -> Result<(), Box> { .env("ROW_HOME", "/not/a/path") .current_dir(temp.path()) .assert() - .failure() - .stderr(predicate::str::contains("Usage")); + .success() + .stdout(predicates::str::diff("dir0\ndir1\ndir2\ndir3\n")); Ok(()) } @@ -567,7 +567,7 @@ fn directories() -> Result<(), Box> { Command::cargo_bin("row")? .args(["show", "directories"]) .args(["--cluster", "none"]) - .arg("one") + .args(["--action", "one"]) .current_dir(temp.path()) .env_remove("ROW_COLOR") .env_remove("CLICOLOR") @@ -598,7 +598,7 @@ fn directories_select_directories() -> Result<(), Box> { Command::cargo_bin("row")? .args(["show", "directories"]) .args(["--cluster", "none"]) - .arg("one") + .args(["--action", "one"]) .arg("dir3") .arg("dir9") .current_dir(temp.path()) @@ -631,7 +631,7 @@ fn directories_no_header() -> Result<(), Box> { Command::cargo_bin("row")? .args(["show", "directories"]) .args(["--cluster", "none"]) - .arg("one") + .args(["--action", "one"]) .arg("--no-header") .current_dir(temp.path()) .env_remove("ROW_COLOR") @@ -655,7 +655,7 @@ fn directories_value() -> Result<(), Box> { .args(["--cluster", "none"]) .args(["--value", "/v"]) .args(["--value", "/v2"]) - .arg("one") + .args(["--action", "one"]) .arg("dir3") .arg("dir9") .current_dir(temp.path()) @@ -673,6 +673,52 @@ fn directories_value() -> Result<(), Box> { Ok(()) } +#[test] +#[parallel] +fn directories_short() -> Result<(), Box> { + let temp = TempDir::new()?; + let _ = setup_sample_workflow(&temp, 4); + + Command::cargo_bin("row")? + .args(["show", "directories"]) + .args(["--cluster", "none"]) + .args(["--action", "one"]) + .arg("--short") + .current_dir(temp.path()) + .env_remove("ROW_COLOR") + .env_remove("CLICOLOR") + .env("ROW_HOME", "/not/a/path") + .assert() + .success() + .stdout(predicates::str::diff("dir0\ndir1\ndir2\ndir3\n")); + + Ok(()) +} + +#[test] +#[parallel] +fn directories_short_no_action() -> Result<(), Box> { + let temp = TempDir::new()?; + let _ = setup_sample_workflow(&temp, 10); + + Command::cargo_bin("row")? + .args(["show", "directories"]) + .args(["--cluster", "none"]) + .arg("--short") + .current_dir(temp.path()) + .env_remove("ROW_COLOR") + .env_remove("CLICOLOR") + .env("ROW_HOME", "/not/a/path") + .assert() + .failure() + .stderr(predicate::str::contains( + "following required arguments were not provided", + )) + .stderr(predicate::str::contains("--action")); + + Ok(()) +} + #[test] #[parallel] fn show_cluster() -> Result<(), Box> { @@ -691,6 +737,25 @@ fn show_cluster() -> Result<(), Box> { Ok(()) } +#[test] +#[parallel] +fn show_cluster_short() -> Result<(), Box> { + let temp = TempDir::new()?; + + Command::cargo_bin("row")? + .args(["show", "cluster"]) + .args(["--cluster", "none"]) + .arg("--short") + .current_dir(temp.path()) + .env_remove("ROW_COLOR") + .env_remove("CLICOLOR") + .env("ROW_HOME", "/not/a/path") + .assert() + .success() + .stdout(predicate::eq("none\n")); + + Ok(()) +} #[test] #[parallel] @@ -710,6 +775,26 @@ fn show_launchers() -> Result<(), Box> { Ok(()) } +#[test] +#[parallel] +fn show_launchers_short() -> Result<(), Box> { + let temp = TempDir::new()?; + + Command::cargo_bin("row")? + .args(["show", "launchers"]) + .args(["--cluster", "none"]) + .arg("--short") + .current_dir(temp.path()) + .env_remove("ROW_COLOR") + .env_remove("CLICOLOR") + .env("ROW_HOME", "/not/a/path") + .assert() + .success() + .stdout(predicate::str::contains("mpi")) + .stdout(predicate::str::contains("openmp\n")); + + Ok(()) +} #[test] #[parallel]