diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..9741f11 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[alias] +xtask = "run --quiet --package xtask --" diff --git a/.gitignore b/.gitignore index e41e974..b6762ae 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,7 @@ tags -/.vscode/ \ No newline at end of file +/.vscode/ + +# Ignore generated manpages +*.1 diff --git a/Cargo.lock b/Cargo.lock index 09df59f..9398a7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -319,6 +319,16 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "clap_mangen" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48283ce8d5cd9513633949a674a0442bcb507ab61ed6533863437052ddbb494b" +dependencies = [ + "clap", + "roff", +] + [[package]] name = "clipboard" version = "0.5.0" @@ -2593,6 +2603,12 @@ dependencies = [ "cpal", ] +[[package]] +name = "roff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" + [[package]] name = "rspotify" version = "0.11.6" @@ -3856,6 +3872,15 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" +[[package]] +name = "xtask" +version = "0.1.0" +dependencies = [ + "clap", + "clap_mangen", + "ncspot", +] + [[package]] name = "zerocopy" version = "0.6.1" diff --git a/Cargo.toml b/Cargo.toml index c0a190e..6c73f85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,12 @@ version = "0.12.0" [badges] maintenance = {status = "actively-developed"} +[workspace] +members = [ + ".", + "xtask" +] + [dependencies] chrono = "0.4" clap = "4.1.7" diff --git a/README.md b/README.md index aba6659..72ce0fb 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ You **must** have an existing premium Spotify subscription to use `ncspot`. - [Debugging](#debugging) - [Compiling](#compiling) - [Building a Debian Package](#building-a-debian-package) + - [Packaging Information](#packaging-information) - [Audio Backends](#audio-backends) - [Other Features](#other-features) - [Key Bindings](#key-bindings) @@ -170,6 +171,14 @@ cargo deb You can find the package under `target/debian`. +#### Packaging Information +The following files are provided and should be bundled together with ncspot: +- LICENSE +- images/logo.svg (optional) +- misc/ncspot.desktop (for Linux systems) +- misc/ncspot.1 (for Linux systems) + +Some of these files have to be generated. Execute `cargo xtask --help` for more information. ### Audio Backends By default `ncspot` is built using the PulseAudio backend. To make it use the diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..41a32cc --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,41 @@ +use librespot_playback::audio_backend; + +pub const AUTHOR: &str = "Henrik Friedrichsen and contributors"; + +/// 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 man page 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"), + ) +} diff --git a/src/main.rs b/src/main.rs index 59bb854..5e932c2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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}; @@ -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 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::("debug") { setup_logging(filename).expect("can't setup logging"); diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 0000000..ac19eb5 --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,13 @@ +# 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 = "4.1.6" + +[dependencies.ncspot] +path = ".." diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 0000000..70a710d --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,92 @@ +use std::path::PathBuf; +use std::{env, fs}; + +use clap::builder::PathBufValueParser; +use clap::error::{Error, ErrorKind}; +use clap::ArgMatches; +use ncspot::AUTHOR; + +enum XTaskSubcommand { + GenerateManpage, +} + +impl TryFrom<&ArgMatches> for XTaskSubcommand { + type Error = clap::Error; + + fn try_from(value: &ArgMatches) -> Result { + if let Some(subcommand) = value.subcommand() { + match subcommand.0 { + "generate-manpage" => Ok(XTaskSubcommand::GenerateManpage), + _ => Err(Error::new(clap::error::ErrorKind::InvalidSubcommand)), + } + } else { + Err(Error::new(ErrorKind::MissingSubcommand)) + } + } +} + +type DynError = Box; + +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. + ", + ) + .subcommand( + clap::Command::new("generate-manpage") + .visible_alias("gm") + .args([clap::Arg::new("output") + .short('o') + .long("output") + .value_name("PATH") + .help("Output directory for the generated man page.") + .value_parser(PathBufValueParser::new())]) + .about("Automatic man page generation."), + ); + + let program_parsed_arguments = arguments_model.get_matches(); + + let parsed_subcommand = XTaskSubcommand::try_from(&program_parsed_arguments)?; + + match parsed_subcommand { + XTaskSubcommand::GenerateManpage => { + generate_manpage(program_parsed_arguments.subcommand().unwrap().1) + } + } +} + +fn generate_manpage(subcommand_arguments: &ArgMatches) -> Result<(), DynError> { + let output_directory = + if let Some(output_argument) = subcommand_arguments.get_one::("output") { + output_argument.clone() + } else { + fs::create_dir_all("misc")?; + PathBuf::from("misc") + }; + let cmd = ncspot::program_arguments(); + let man = clap_mangen::Man::new(cmd); + let mut buffer: Vec = Default::default(); + + man.render(&mut buffer)?; + + std::fs::write(output_directory.join("ncspot.1"), buffer)?; + + Ok(()) +}