Skip to content

Commit 3035ba8

Browse files
authored
add shell completions (#45)
1 parent 74f41a7 commit 3035ba8

5 files changed

Lines changed: 118 additions & 42 deletions

File tree

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,29 @@ Upload a file:
225225
coman cscs file upload /my/local/file /capstor/scratch/cscs/your_user/your_file
226226
```
227227

228+
You can set up shell completions as follows:
229+
230+
```shell
231+
# Bash
232+
mkdir -p ~/.local/share/bash-completion/completions
233+
coman completions bash > ~/.local/share/bash-completion/completions/coman
234+
235+
# Bash (macOS/Homebrew)
236+
mkdir -p $(brew --prefix)/etc/bash_completion.d/
237+
coman completions bash > $(brew --prefix)/etc/bash_completion.d/coman.bash-completion
238+
239+
# Fish
240+
mkdir -p ~/.config/fish/completions
241+
coman completions fish > ~/.config/fish/completions/coman.fish
242+
243+
# Zsh
244+
mkdir ~/.zfunc
245+
# Then add the following lines to your `.zshrc` just before
246+
# `compinit`:
247+
#
248+
# fpath+=~/.zfunc
249+
coman completions zsh > ~/.zfunc/_coman
250+
```
228251
### TUI
229252

230253
To run the TUI, simply run `coman` without any arguments:

coman/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,11 @@ openssl = { version = "0.10.75", features = ["vendored"] }
7373
tui-realm-treeview = "3.0.0"
7474
aws-sdk-s3 = "1.115.0"
7575
toml_edit = "0.23.9"
76+
clap_complete = "4.5.61"
7677

7778
[build-dependencies]
7879
anyhow = "1.0.90"
80+
7981
vergen-gix = { version = "1.0.2", features = ["build", "cargo"] }
8082

8183
[dev-dependencies]

coman/src/cli.rs

Lines changed: 77 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::{error::Error, path::PathBuf};
22

3-
use clap::{Args, Parser, Subcommand, builder::TypedValueParser};
3+
use clap::{Args, Command, Parser, Subcommand, ValueHint, builder::TypedValueParser};
4+
use clap_complete::{Generator, Shell, generate};
45
use color_eyre::Result;
56
use strum::VariantNames;
67

@@ -10,6 +11,17 @@ use crate::{
1011
util::types::DockerImageUrl,
1112
};
1213

14+
#[derive(Parser, Debug)]
15+
#[command(author, version = version(), about)]
16+
pub struct Cli {
17+
/// Tick rate, i.e. number of ticks per second
18+
#[arg(short, long, value_name = "FLOAT", default_value_t = 4.0)]
19+
pub tick_rate: f64,
20+
21+
#[command(subcommand)]
22+
pub command: Option<CliCommands>,
23+
}
24+
1325
#[derive(Subcommand, Debug)]
1426
#[allow(clippy::large_enum_variant)]
1527
pub enum CliCommands {
@@ -19,25 +31,38 @@ pub enum CliCommands {
1931
Cscs {
2032
#[command(subcommand)]
2133
command: CscsCommands,
22-
#[clap(short, long, help = "override compute system (e.g. 'eiger', 'daint')")]
34+
#[clap(short, long, help = "override compute system (e.g. 'eiger', 'daint')", value_hint=ValueHint::Other)]
2335
system: Option<String>,
24-
#[clap(short, long, ignore_case=true, value_parser=clap::builder::PossibleValuesParser::new(ComputePlatform::VARIANTS).map(|s|s.parse::<ComputePlatform>().unwrap()),help = "override compute platform (one of 'hpc', 'ml' or 'cw')")]
36+
#[clap(
37+
short,
38+
long,
39+
ignore_case=true,
40+
value_parser=clap::builder::PossibleValuesParser::new(ComputePlatform::VARIANTS).map(
41+
|s|s.parse::<ComputePlatform>().unwrap()),
42+
help = "override compute platform (one of 'hpc', 'ml' or 'cw')",
43+
value_hint=ValueHint::Other)]
2544
platform: Option<ComputePlatform>,
26-
#[clap(short, long, help = "override compute account to use (project or user)")]
45+
#[clap(short, long, help = "override compute account to use (project or user)",value_hint=ValueHint::Other)]
2746
account: Option<String>,
2847
},
2948
#[clap(about = "Create a new project configuration file")]
3049
Init {
31-
#[clap(help = "destination folder to create config in (default = current directory)")]
50+
#[clap(help = "destination folder to create config in (default = current directory)",value_hint=ValueHint::DirPath)]
3251
destination: Option<PathBuf>,
33-
#[clap(help = "project name to use")]
52+
#[clap(help = "project name to use", value_hint=ValueHint::Other)]
3453
name: Option<String>,
3554
},
3655
#[clap(about = "Manage configuration")]
3756
Config {
3857
#[command(subcommand)]
3958
command: ConfigCommands,
4059
},
60+
#[clap(about = "Generate shell completions")]
61+
Completions {
62+
/// generate shell completions
63+
#[clap(value_enum)]
64+
generator: Shell,
65+
},
4166
}
4267

4368
#[derive(Subcommand, Debug)]
@@ -51,13 +76,13 @@ pub enum ConfigCommands {
5176
help = "whether to change the global config or the project local one"
5277
)]
5378
global: bool,
54-
#[clap(help = "Config key path, e.g. `cscs.current_system`")]
79+
#[clap(help = "Config key path, e.g. `cscs.current_system`", value_hint=ValueHint::Other)]
5580
key_path: String,
56-
#[clap(help = "Value to set", value_parser = parse_toml_value)]
81+
#[clap(help = "Value to set", value_parser = parse_toml_value, value_hint=ValueHint::Other)]
5782
value: toml_edit::Value,
5883
},
5984
Get {
60-
#[clap(help = "Config key path, e.g. `cscs.current_system`")]
85+
#[clap(help = "Config key path, e.g. `cscs.current_system`", value_hint=ValueHint::Other)]
6186
key_path: String,
6287
},
6388
}
@@ -95,9 +120,9 @@ pub struct ScriptSpec {
95120
help = "generate and upload script file based on template (on by default unless `--local` or `--remote` are passed)"
96121
)]
97122
generate_script: bool,
98-
#[arg(long, value_name = "PATH", help = "upload local script file")]
123+
#[arg(long, value_name = "PATH", help = "upload local script file", value_hint=ValueHint::FilePath)]
99124
local_script: Option<PathBuf>,
100-
#[arg(long, value_name = "PATH", help = "use script file already present on remote")]
125+
#[arg(long, value_name = "PATH", help = "use script file already present on remote", value_hint=ValueHint::Other)]
101126
remote_script: Option<PathBuf>,
102127
}
103128
impl Default for ScriptSpec {
@@ -130,9 +155,9 @@ pub struct EdfSpec {
130155
help = "generate and upload edf file based on template (on by default unless `--local` or `--remote` are passed)"
131156
)]
132157
generate_edf: bool,
133-
#[arg(long, value_name = "PATH", help = "upload local edf file")]
158+
#[arg(long, value_name = "PATH", help = "upload local edf file", value_hint=ValueHint::FilePath)]
134159
local_edf: Option<PathBuf>,
135-
#[arg(long, value_name = "PATH", help = "use edf file already present on remote")]
160+
#[arg(long, value_name = "PATH", help = "use edf file already present on remote", value_hint=ValueHint::Other)]
136161
remote_edf: Option<PathBuf>,
137162
}
138163

@@ -164,65 +189,84 @@ pub enum CscsJobCommands {
164189
#[clap(alias("ls"), about = "List all jobs [aliases: ls]")]
165190
List,
166191
#[clap(alias("g"), about = "Get metadata for a specific job [aliases: g]")]
167-
Get { job_id: i64 },
192+
Get {
193+
#[arg(help="id of the job", value_hint=ValueHint::Other)]
194+
job_id: i64,
195+
},
168196
#[clap(about = "Get the stdout of a job")]
169197
Log {
170198
#[clap(short, long, action, help = "whether to get stderr instead of stdout")]
171199
stderr: bool,
200+
#[arg(help="id of the job", value_hint=ValueHint::Other)]
172201
job_id: i64,
173202
},
174203

175204
#[clap(alias("s"), about = "Submit a new compute job [aliases: s]")]
176205
Submit {
177-
#[clap(short, long, help = "name of the job")]
206+
#[clap(short, long, help = "name of the job", value_hint=ValueHint::Other)]
178207
name: Option<String>,
179208
#[clap(
180209
short,
181210
long,
182-
help = "the working directory path inside the container (note this is different from the working directory that the srun command is executed from)"
211+
help = "the working directory path inside the container (note this is different from the working directory that the srun command is executed from)",
212+
value_hint=ValueHint::Other
183213
)]
184214
workdir: Option<String>,
185-
#[clap(short='E', value_name="KEY=VALUE", value_parser=parse_key_val::<String,String>, help="Environment variables to set in the container")]
215+
#[clap(short='E',
216+
value_name="KEY=VALUE",
217+
value_parser=parse_key_val::<String, String>,
218+
help="Environment variables to set in the container",
219+
value_hint=ValueHint::Other)]
186220
env: Vec<(String, String)>,
187-
#[clap(short='M', value_name="PATH:CONTAINER_PATH", value_parser=parse_key_val_colon::<String,String>, help="Paths to mount inside container")]
221+
#[clap(short='M',
222+
value_name="PATH:CONTAINER_PATH",
223+
value_parser=parse_key_val_colon::<String,String>,
224+
help="Paths to mount inside container",
225+
value_hint=ValueHint::Other)]
188226
mount: Vec<(String, String)>,
189-
#[clap(short, long, help = "The docker image to use")]
227+
#[clap(short, long, help = "The docker image to use", value_hint=ValueHint::Other)]
190228
image: Option<DockerImageUrl>,
191-
#[clap(long, help = "Path where stdout of the job gets written to")]
229+
#[clap(long, help = "Path where stdout of the job gets written to", value_hint=ValueHint::Other)]
192230
stdout: Option<PathBuf>,
193-
#[clap(long, help = "Path where stderr of the job gets written to")]
231+
#[clap(long, help = "Path where stderr of the job gets written to", value_hint=ValueHint::Other)]
194232
stderr: Option<PathBuf>,
195233
#[command(flatten)]
196234
edf_spec: Option<EdfSpec>,
197235
#[command(flatten)]
198236
script_spec: Option<ScriptSpec>,
199-
#[clap(trailing_var_arg = true, help = "The command to run in the container")]
237+
#[clap(trailing_var_arg = true, help = "The command to run in the container", value_hint=ValueHint::Other)]
200238
command: Option<Vec<String>>,
201239
},
202240
#[clap(
203241
alias("c"),
204242
about = "Cancel a running job, fails if the job isn't running [aliases: c]"
205243
)]
206-
Cancel { job_id: i64 },
244+
Cancel {
245+
#[clap(help="id of the job", value_hint=ValueHint::Other)]
246+
job_id: i64,
247+
},
207248
}
208249

209250
#[derive(Subcommand, Debug)]
210251
pub enum CscsFileCommands {
211252
#[clap(alias("ls"), about = "List folders and files in a remote path [aliases: ls]")]
212-
List { path: PathBuf },
253+
List {
254+
#[arg(help ="remote path to list", value_hint=ValueHint::Other)]
255+
path: PathBuf,
256+
},
213257
#[clap(alias("dl"), about = "Download a remote file [aliases: dl]")]
214258
Download {
215-
#[clap(help = "The path in the cluster to download")]
259+
#[clap(help = "The path in the cluster to download", value_hint=ValueHint::Other)]
216260
remote: PathBuf,
217-
#[clap(help = "The local path to download the file to")]
261+
#[clap(help = "The local path to download the file to", value_hint=ValueHint::AnyPath)]
218262
local: PathBuf,
219263
},
220264
#[clap(alias("ul"), about = "Upload a file to remote storage [aliases: ul]")]
221265
Upload {
222-
#[clap(help = "The local path to upload to the cluster")]
266+
#[clap(help = "The local path to upload to the cluster", value_hint=ValueHint::AnyPath)]
223267
local: PathBuf,
224268

225-
#[clap(help = "the path in the cluster to upload to")]
269+
#[clap(help = "the path in the cluster to upload to", value_hint=ValueHint::Other)]
226270
remote: PathBuf,
227271
},
228272
}
@@ -238,22 +282,11 @@ pub enum CscsSystemCommands {
238282
Set {
239283
#[clap(short, long, action, help = "set in global config instead of project-local one")]
240284
global: bool,
241-
#[clap(help = "System name to use")]
285+
#[clap(help = "System name to use", value_hint=ValueHint::Other)]
242286
system_name: String,
243287
},
244288
}
245289

246-
#[derive(Parser, Debug)]
247-
#[command(author, version = version(), about)]
248-
pub struct Cli {
249-
/// Tick rate, i.e. number of ticks per second
250-
#[arg(short, long, value_name = "FLOAT", default_value_t = 4.0)]
251-
pub tick_rate: f64,
252-
253-
#[command(subcommand)]
254-
pub command: Option<CliCommands>,
255-
}
256-
257290
const VERSION_MESSAGE: &str = concat!(
258291
env!("CARGO_PKG_VERSION"),
259292
"-",
@@ -335,3 +368,7 @@ fn is_bare_string(value_str: &str) -> bool {
335368
true // empty or whitespace only
336369
}
337370
}
371+
372+
pub fn print_completions<G: Generator>(generator: G, cmd: &mut Command) {
373+
generate(generator, cmd, cmd.get_name().to_string(), &mut std::io::stdout());
374+
}

coman/src/main.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::time::Duration;
22

3-
use clap::Parser;
3+
use clap::{CommandFactory, Parser};
44
use color_eyre::Result;
55
use keyring::set_global_service_name;
66
use tokio::{runtime::Handle, sync::mpsc};
@@ -17,7 +17,7 @@ use crate::{
1717
model::Model,
1818
user_events::{CscsEvent, FileEvent, StatusEvent, UserEvent},
1919
},
20-
cli::{Cli, get_config, set_config, version},
20+
cli::{Cli, get_config, print_completions, set_config, version},
2121
components::{
2222
file_tree::FileTree, global_listener::GlobalListener, status_bar::StatusBar, toolbar::Toolbar,
2323
workload_list::WorkloadList,
@@ -58,6 +58,10 @@ async fn main() -> Result<()> {
5858
match args.command {
5959
Some(command) => match command {
6060
cli::CliCommands::Version => println!("{}", version()),
61+
cli::CliCommands::Completions { generator } => {
62+
let mut cmd = Cli::command();
63+
print_completions(generator, &mut cmd);
64+
}
6165
cli::CliCommands::Config {
6266
command: config_command,
6367
} => match config_command {

0 commit comments

Comments
 (0)