diff options
Diffstat (limited to 'alacritty/src/cli.rs')
-rw-r--r-- | alacritty/src/cli.rs | 150 |
1 files changed, 123 insertions, 27 deletions
diff --git a/alacritty/src/cli.rs b/alacritty/src/cli.rs index 5da6332b..418e3791 100644 --- a/alacritty/src/cli.rs +++ b/alacritty/src/cli.rs @@ -3,10 +3,12 @@ use std::path::PathBuf; use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg}; use log::{self, error, LevelFilter}; +use serde_yaml::Value; use alacritty_terminal::config::Program; use alacritty_terminal::index::{Column, Line}; +use crate::config::serde_utils; use crate::config::ui_config::Delta; use crate::config::window::{Dimensions, DEFAULT_NAME}; use crate::config::Config; @@ -32,9 +34,10 @@ pub struct Options { pub log_level: LevelFilter, pub command: Option<Program>, pub hold: bool, - pub working_dir: Option<PathBuf>, - pub config: Option<PathBuf>, + pub working_directory: Option<PathBuf>, + pub config_path: Option<PathBuf>, pub persistent_logging: bool, + pub config_options: Value, } impl Default for Options { @@ -52,9 +55,10 @@ impl Default for Options { log_level: LevelFilter::Warn, command: None, hold: false, - working_dir: None, - config: None, + working_directory: None, + config_path: None, persistent_logging: false, + config_options: Value::Null, } } } @@ -168,11 +172,18 @@ impl Options { .short("e") .multiple(true) .takes_value(true) - .min_values(1) .allow_hyphen_values(true) .help("Command and args to execute (must be last argument)"), ) .arg(Arg::with_name("hold").long("hold").help("Remain open after child process exits")) + .arg( + Arg::with_name("option") + .long("option") + .short("o") + .multiple(true) + .takes_value(true) + .help("Override configuration file options [example: window.title=Alacritty]"), + ) .get_matches(); if matches.is_present("ref-test") { @@ -231,11 +242,11 @@ impl Options { } if let Some(dir) = matches.value_of("working-directory") { - options.working_dir = Some(PathBuf::from(dir.to_string())); + options.working_directory = Some(PathBuf::from(dir.to_string())); } if let Some(path) = matches.value_of("config-file") { - options.config = Some(PathBuf::from(path.to_string())); + options.config_path = Some(PathBuf::from(path.to_string())); } if let Some(mut args) = matches.values_of("command") { @@ -251,23 +262,47 @@ impl Options { options.hold = true; } + if let Some(config_options) = matches.values_of("option") { + for option in config_options { + match option_as_value(option) { + Ok(value) => { + options.config_options = serde_utils::merge(options.config_options, value); + }, + Err(_) => eprintln!("Invalid CLI config option: {:?}", option), + } + } + } + options } + /// Configuration file path. pub fn config_path(&self) -> Option<PathBuf> { - self.config.clone() + self.config_path.clone() } - pub fn into_config(self, mut config: Config) -> Config { - match self.working_dir.or_else(|| config.working_directory.take()) { - Some(ref wd) if !wd.is_dir() => error!("Unable to set working directory to {:?}", wd), - wd => config.working_directory = wd, + /// CLI config options as deserializable serde value. + pub fn config_options(&self) -> &Value { + &self.config_options + } + + /// Override configuration file with options from the CLI. + pub fn override_config(&self, config: &mut Config) { + if let Some(working_directory) = &self.working_directory { + if working_directory.is_dir() { + config.working_directory = Some(working_directory.to_owned()); + } else { + error!("Invalid working directory: {:?}", working_directory); + } } if let Some(lcr) = self.live_config_reload { config.ui_config.set_live_config_reload(lcr); } - config.shell = self.command.or(config.shell); + + if let Some(command) = &self.command { + config.shell = Some(command.clone()); + } config.hold = self.hold; @@ -275,12 +310,12 @@ impl Options { config.ui_config.set_dynamic_title(dynamic_title); replace_if_some(&mut config.ui_config.window.dimensions, self.dimensions); - replace_if_some(&mut config.ui_config.window.title, self.title); - config.ui_config.window.position = self.position.or(config.ui_config.window.position); - config.ui_config.window.embed = self.embed.and_then(|embed| embed.parse().ok()); - replace_if_some(&mut config.ui_config.window.class.instance, self.class_instance); - replace_if_some(&mut config.ui_config.window.class.general, self.class_general); + replace_if_some(&mut config.ui_config.window.title, self.title.clone()); + replace_if_some(&mut config.ui_config.window.class.instance, self.class_instance.clone()); + replace_if_some(&mut config.ui_config.window.class.general, self.class_general.clone()); + config.ui_config.window.position = self.position.or(config.ui_config.window.position); + config.ui_config.window.embed = self.embed.as_ref().and_then(|embed| embed.parse().ok()); config.ui_config.debug.print_events |= self.print_events; config.ui_config.debug.log_level = max(config.ui_config.debug.log_level, self.log_level); config.ui_config.debug.ref_test |= self.ref_test; @@ -290,8 +325,6 @@ impl Options { config.ui_config.debug.log_level = max(config.ui_config.debug.log_level, LevelFilter::Info); } - - config } } @@ -301,28 +334,54 @@ fn replace_if_some<T>(option: &mut T, value: Option<T>) { } } +/// Format an option in the format of `parent.field=value` to a serde Value. +fn option_as_value(option: &str) -> Result<Value, serde_yaml::Error> { + let mut yaml_text = String::with_capacity(option.len()); + let mut closing_brackets = String::new(); + + for (i, c) in option.chars().enumerate() { + match c { + '=' => { + yaml_text.push_str(": "); + yaml_text.push_str(&option[i + 1..]); + break; + }, + '.' => { + yaml_text.push_str(": {"); + closing_brackets.push('}'); + }, + _ => yaml_text.push(c), + } + } + + yaml_text += &closing_brackets; + + serde_yaml::from_str(&yaml_text) +} + #[cfg(test)] mod tests { - use crate::cli::Options; - use crate::config::Config; + use super::*; + + use serde_yaml::mapping::Mapping; #[test] fn dynamic_title_ignoring_options_by_default() { - let config = Config::default(); + let mut config = Config::default(); let old_dynamic_title = config.ui_config.dynamic_title(); - let config = Options::default().into_config(config); + Options::default().override_config(&mut config); assert_eq!(old_dynamic_title, config.ui_config.dynamic_title()); } #[test] fn dynamic_title_overridden_by_options() { - let config = Config::default(); + let mut config = Config::default(); let mut options = Options::default(); options.title = Some("foo".to_owned()); - let config = options.into_config(config); + options.override_config(&mut config); assert!(!config.ui_config.dynamic_title()); } @@ -332,8 +391,45 @@ mod tests { let mut config = Config::default(); config.ui_config.window.title = "foo".to_owned(); - let config = Options::default().into_config(config); + Options::default().override_config(&mut config); assert!(config.ui_config.dynamic_title()); } + + #[test] + fn valid_option_as_value() { + // Test with a single field. + let value = option_as_value("field=true").unwrap(); + + let mut mapping = Mapping::new(); + mapping.insert(Value::String(String::from("field")), Value::Bool(true)); + + assert_eq!(value, Value::Mapping(mapping)); + + // Test with nested fields + let value = option_as_value("parent.field=true").unwrap(); + + let mut parent_mapping = Mapping::new(); + parent_mapping.insert(Value::String(String::from("field")), Value::Bool(true)); + let mut mapping = Mapping::new(); + mapping.insert(Value::String(String::from("parent")), Value::Mapping(parent_mapping)); + + assert_eq!(value, Value::Mapping(mapping)); + } + + #[test] + fn invalid_option_as_value() { + let value = option_as_value("}"); + assert!(value.is_err()); + } + + #[test] + fn float_option_as_value() { + let value = option_as_value("float=3.4").unwrap(); + + let mut expected = Mapping::new(); + expected.insert(Value::String(String::from("float")), Value::Number(3.4.into())); + + assert_eq!(value, Value::Mapping(expected)); + } } |