pub mod lib; use vtcol::{Console, Palette, Scheme}; use anyhow::{anyhow, Result}; use std::{io::{self, BufWriter}, sync::atomic::{AtomicBool, Ordering}}; static VERBOSITY: AtomicBool = AtomicBool::new(false); macro_rules! vrb { ( $( $e:expr ),* ) => {( if VERBOSITY.load(Ordering::SeqCst) { println!( $( $e ),* ) } )} } /* struct Job -- Runtime parameters. */ #[derive(Debug)] enum Job { /** List available schemes. */ List, /** Dump a scheme. */ Dump(Scheme), /** Switch to color scheme. */ Set(Scheme), /** Get currently active scheme. */ Get, /** Toggle between two schemes. */ Toggle(Scheme, Scheme), } impl<'a> Job { pub fn from_argv() -> Result { use clap::{App, Arg, SubCommand}; let app = App::new(clap::crate_name!()) .version(clap::crate_version!()) .author(clap::crate_authors!()) .about(clap::crate_description!()) .subcommand( SubCommand::with_name("dump").about("dump a color scheme").arg( Arg::with_name("scheme") .help("name of the scheme") .required(true) .value_name("NAME") .takes_value(true), ), ) .subcommand( SubCommand::with_name("list").about("list available schemes"), ) .subcommand( SubCommand::with_name("set") .about("apply color scheme to current terminal") .arg( Arg::with_name("scheme") .value_name("NAME") .help("predefined color scheme") .takes_value(true) .conflicts_with("file"), ) .arg( Arg::with_name("file") .short("f") .long("file") .value_name("PATH") .help("apply scheme from file") .takes_value(true), ), ) .subcommand( SubCommand::with_name("get").about("get current color scheme"), ) .subcommand( SubCommand::with_name("toggle") .about("toggle between two schemes") .arg( Arg::with_name("one") .value_name("NAME1") .help("predefined color scheme") .takes_value(true), ) .arg( Arg::with_name("two") .value_name("NAME2") .help("predefined color scheme") .takes_value(true), ), ) .arg( Arg::with_name("verbose") .short("v") .long("verbose") .help("enable extra diagnostics") .takes_value(false), ); let matches = app.get_matches(); if matches.is_present("verbose") { VERBOSITY.store(true, Ordering::SeqCst); } match matches.subcommand() { ("dump", Some(subm)) => { if let Some(name) = subm.value_of("scheme") { let scm = Scheme::from(name); return Ok(Self::Dump(scm)); } Err(anyhow!("dump requires an argument")) }, ("list", _) => Ok(Self::List), ("set", Some(subm)) => { let scheme = match subm.value_of("scheme") { Some("-") => Self::read_scheme_from_stdin(), Some(name) => { vrb!("pick predefined scheme [{}]", name); Scheme::from(name) }, None => match subm.value_of("file") { None | Some("-") => Self::read_scheme_from_stdin(), Some(fname) => { vrb!( "read custom scheme from file [{}]", fname ); Scheme::from_path(fname) }, }, }; Ok(Self::Set(scheme)) }, ("get", _) => Ok(Self::Get), ("toggle", Some(subm)) => { match (subm.value_of("one"), subm.value_of("two")) { (Some(one), Some(two)) => { vrb!("toggle schemes [{}] and [{}]", one, two); Ok(Self::Toggle(Scheme::from(one), Scheme::from(two))) }, _ => Err(anyhow!( "please supply two schemes to toggle between" )), } }, (junk, _) => Err(anyhow!( "invalid subcommand [{}]; try ``{} --help``", junk, clap::crate_name!() )), } } fn list_schemes() { println!("{} color schemes available:", vtcol::BUILTIN_SCHEMES.len()); for s in vtcol::BUILTIN_SCHEMES { println!(" * {}", s.name()); } } fn read_scheme_from_stdin() -> Scheme { vrb!("Go ahead, type your color scheme …"); vrb!("vtcol>"); Scheme::from_stdin() } fn dump(scm: Scheme) -> Result<()> { vrb!("Dumping color scheme {}", scm); let mut out = BufWriter::new(io::stdout()); match scm { Scheme::Builtin(bltn) => Palette::from(bltn.palette()).dump(&mut out).map_err(|e| { anyhow!( "error loading builtin scheme {}: {}", bltn.name(), e ) }), Scheme::Custom(None) => Palette::from_stdin()?.dump(&mut out).map_err(|e| { anyhow!("error loading palette from stdin: {}", e) }), Scheme::Custom(Some(fname)) => Palette::from_file(&fname)?.dump(&mut out).map_err(|e| { anyhow!( "error loading palette from file [{}]: {}", fname.display(), e ) }), Scheme::Palette(pal) => pal.dump(&mut out) .map_err(|e| anyhow!("error dumping palette: {}", e)), } } fn run(self) -> Result<()> { match self { Self::Dump(scm) => Self::dump(scm)?, Self::List => Self::list_schemes(), Self::Set(scm) => Self::set_scheme(scm)?, Self::Get => Self::get_scheme()?, Self::Toggle(one, two) => Self::toggle_scheme(one, two)?, } Ok(()) } fn set_scheme(scheme: Scheme) -> Result<()> { let con = Console::current()?; vrb!("console fd: {}", con); con.apply_scheme(&scheme)?; con.clear()?; vrb!("successfully enabled scheme {:?}", scheme); /* It’s fine to leak the fd, the kernel will clean up anyways. */ Ok(()) } fn get_scheme() -> Result<()> { let fd = Console::current()?; vrb!("console fd: {}", fd); let scm = fd.current_scheme()?; vrb!("active scheme:"); println!("{}", scm); Ok(()) } /** Toggle between two schemes. Defaults to ``one`` in case neither scheme is active. */ fn toggle_scheme(one: Scheme, two: Scheme) -> Result<()> { let fd = Console::current()?; vrb!("console fd: {}", fd); if fd.current_palette()? == Palette::try_from(&one)? { Self::set_scheme(two) } else { Self::set_scheme(one) } } } /* [impl Job] */ fn main() -> Result<()> { let job = Job::from_argv()?; vrb!("job parms: {:?}", job); job.run() }