Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatic shell completion generation using xtask. #1068

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[alias]
xtask = "run --quiet --package xtask --"
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@

tags

/.vscode/
/.vscode/

# Ignore generated manpages
*.1
35 changes: 35 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ version = "0.12.0"
[badges]
maintenance = {status = "actively-developed"}

[workspace]
members = [
".",
"xtask"
]

[dependencies]
chrono = "0.4"
clap = "4.0.0"
Expand Down
42 changes: 42 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use librespot_playback::audio_backend;

pub const AUTHOR: &str = "Henrik Friedrichsen <[email protected]> and contributors";
pub const BIN_NAME: &str = "ncspot";

/// Return the [Command](clap::Command) that models the program's command line arguments. The
/// command can be used to parse the actual arguments passed to the program, or to automatically
/// generate a manpage using clap's mangen package.
pub fn program_arguments() -> clap::Command {
let backends = {
let backends: Vec<&str> = audio_backend::BACKENDS.iter().map(|b| b.0).collect();
format!("Audio backends: {}", backends.join(", "))
};

clap::Command::new("ncspot")
.version(env!("CARGO_PKG_VERSION"))
.author(AUTHOR)
.about("cross-platform ncurses Spotify client")
.after_help(backends)
.arg(
clap::Arg::new("debug")
.short('d')
.long("debug")
.value_name("FILE")
.help("Enable debug logging to the specified file"),
)
.arg(
clap::Arg::new("basepath")
.short('b')
.long("basepath")
.value_name("PATH")
.help("custom basepath to config/cache files"),
)
.arg(
clap::Arg::new("config")
.short('c')
.long("config")
.value_name("FILE")
.help("Filename of config file in basepath")
.default_value("config.toml"),
)
}
36 changes: 2 additions & 34 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@ use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;

use clap::{Arg, Command as ClapCommand};
use cursive::event::EventTrigger;
use cursive::traits::Nameable;
use librespot_core::authentication::Credentials;
use librespot_core::cache::Cache;
use librespot_playback::audio_backend;
use log::{error, info, trace};

use ncspot::program_arguments;
#[cfg(unix)]
use signal_hook::{consts::SIGHUP, consts::SIGTERM, iterator::Signals};

Expand Down Expand Up @@ -130,38 +129,7 @@ lazy_static!(
fn main() -> Result<(), String> {
register_backtrace_panic_handler();

let backends = {
let backends: Vec<&str> = audio_backend::BACKENDS.iter().map(|b| b.0).collect();
format!("Audio backends: {}", backends.join(", "))
};
let matches = ClapCommand::new("ncspot")
.version(env!("CARGO_PKG_VERSION"))
.author("Henrik Friedrichsen <[email protected]> and contributors")
.about("cross-platform ncurses Spotify client")
.after_help(backends)
.arg(
Arg::new("debug")
.short('d')
.long("debug")
.value_name("FILE")
.help("Enable debug logging to the specified file"),
)
.arg(
Arg::new("basepath")
.short('b')
.long("basepath")
.value_name("PATH")
.help("custom basepath to config/cache files"),
)
.arg(
Arg::new("config")
.short('c')
.long("config")
.value_name("FILE")
.help("Filename of config file in basepath")
.default_value("config.toml"),
)
.get_matches();
let matches = program_arguments().get_matches();

if let Some(filename) = matches.get_one::<String>("debug") {
setup_logging(filename).expect("can't setup logging");
Expand Down
14 changes: 14 additions & 0 deletions xtask/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[package]
name = "xtask"
version = "0.1.0"
edition = "2021"

[dependencies]
clap_mangen = "0.2.8"
clap_complete = "4.1.4"
clap = "4.1.6"

[dependencies.ncspot]
path = ".."
128 changes: 128 additions & 0 deletions xtask/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use std::path::PathBuf;
use std::{env, io};

use clap::builder::PathBufValueParser;
use clap::error::{Error, ErrorKind};
use clap::ArgMatches;
use clap_complete::Shell;
use ncspot::{AUTHOR, BIN_NAME};

enum XTaskSubcommand {
GenerateManpage,
GenerateShellCompletionScript,
}

impl TryFrom<&ArgMatches> for XTaskSubcommand {
type Error = clap::Error;

fn try_from(value: &ArgMatches) -> Result<Self, Self::Error> {
if let Some(subcommand) = value.subcommand() {
match subcommand.0 {
"generate-manpage" => Ok(XTaskSubcommand::GenerateManpage),
"generate-shell-completion" => Ok(XTaskSubcommand::GenerateShellCompletionScript),
_ => Err(Error::new(clap::error::ErrorKind::InvalidSubcommand)),
}
} else {
Err(Error::new(ErrorKind::MissingSubcommand))
}
}
}

type DynError = Box<dyn std::error::Error>;

fn main() {
if let Err(e) = try_main() {
eprintln!("{}", e);
std::process::exit(-1);
}
}

fn try_main() -> Result<(), DynError> {
let arguments_model = clap::Command::new("cargo xtask")
.version(env!("CARGO_PKG_VERSION"))
.author(AUTHOR)
.about("Automation using the cargo xtask convention.")
.arg_required_else_help(true)
.bin_name("cargo xtask")
.disable_version_flag(true)
.long_about(
"
Cargo xtask is a convention that allows easy integration of third party commands into the regular
cargo workflox. Xtask's are defined as a separate package and can be used for all kinds of
automation.
",
)
.subcommands([
clap::Command::new("generate-manpage")
.visible_alias("gm")
.args([clap::Arg::new("output")
.short('o')
.long("output")
.value_name("PATH")
.help("The output path for the manpage.")
.value_parser(PathBufValueParser::new())
.required(true)])
.about("Automatic manpage generation"),
clap::Command::new("generate-shell-completion")
.visible_alias("gsc")
.args([clap::Arg::new("shell")
.short('s')
.long("shell")
.default_value("bash")
.help("The shell for which completion should be generated (default = bash).")])
.about("Automatic shell completion generation."),
]);

let program_parsed_arguments = arguments_model.get_matches();

let parsed_subcommand = XTaskSubcommand::try_from(&program_parsed_arguments)?;

let subcommand_parsed_arguments = program_parsed_arguments.subcommand().unwrap().1;

match parsed_subcommand {
XTaskSubcommand::GenerateManpage => generate_manpage(subcommand_parsed_arguments),
XTaskSubcommand::GenerateShellCompletionScript => {
generate_completion_script(subcommand_parsed_arguments)
}
}
}

fn generate_manpage(subcommand_arguments: &ArgMatches) -> Result<(), DynError> {
let cmd = ncspot::program_arguments();

let man = clap_mangen::Man::new(cmd);
let mut buffer: Vec<u8> = Default::default();
man.render(&mut buffer)?;

std::fs::write(
subcommand_arguments
.get_one::<PathBuf>("output")
.unwrap()
.join(format!("{}.1", BIN_NAME)),
buffer,
)?;

Ok(())
}

fn generate_completion_script(subcommand_arguments: &ArgMatches) -> Result<(), DynError> {
let shell = match subcommand_arguments
.get_one::<String>("shell")
.unwrap()
.as_str()
{
"bash" => Shell::Bash,
"zsh" => Shell::Zsh,
"fish" => Shell::Fish,
"elvish" => Shell::Elvish,
"powershell" => Shell::PowerShell,
_ => Shell::Bash,
};
clap_complete::generate(
shell,
&mut ncspot::program_arguments(),
ncspot::BIN_NAME,
&mut io::stdout(),
);
Ok(())
}