Add config file as cli option (#576)

* Parse cli arguments before configuration file

Parsing the cli arguments before the configuration file allows `--help`
and `--version` to be used even if the configuration file is broken.

* Add configuration file to command line arguments

This commit adds a new command line flag `--config-file` to override the default
configuration file location. If the specified file is unavailable,
Alacritty will quit instead of generating a fallback. If the specified
file is invalid, i.e. /dev/null, the compiled in defaults will be loaded
instead.
This commit is contained in:
Niklas Claesson 2017-05-29 02:42:36 +02:00 committed by Joe Wilm
parent 13eac6b673
commit 81116fb8a4
3 changed files with 74 additions and 44 deletions

View File

@ -15,7 +15,8 @@ extern crate log;
use clap::{Arg, App};
use index::{Line, Column};
use config::{Dimensions, Shell};
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::borrow::Cow;
const DEFAULT_TITLE: &'static str = "Alacritty";
@ -28,6 +29,7 @@ pub struct Options {
pub log_level: log::LogLevelFilter,
pub command: Option<Shell<'static>>,
pub working_dir: Option<PathBuf>,
pub config: Option<PathBuf>,
}
impl Default for Options {
@ -40,6 +42,7 @@ impl Default for Options {
log_level: log::LogLevelFilter::Warn,
command: None,
working_dir: None,
config: None,
}
}
}
@ -82,6 +85,10 @@ impl Options {
.long("working-directory")
.takes_value(true)
.help("Start the shell in the specified working directory"))
.arg(Arg::with_name("config-file")
.long("config-file")
.takes_value(true)
.help("Specify alternative configuration file [default: $XDG_CONFIG_HOME/alacritty/alacritty.yml]"))
.arg(Arg::with_name("command")
.short("e")
.multiple(true)
@ -128,6 +135,10 @@ impl Options {
options.working_dir = Some(PathBuf::from(dir.to_string()));
}
if let Some(path) = matches.value_of("config-file") {
options.config = Some(PathBuf::from(path.to_string()));
}
if let Some(mut args) = matches.values_of("command") {
// The following unwrap is guaranteed to succeed.
// If 'command' exists it must also have a first item since
@ -147,4 +158,8 @@ impl Options {
pub fn command(&self) -> Option<&Shell> {
self.command.as_ref()
}
pub fn config_path(&self) -> Option<Cow<Path>> {
self.config.as_ref().map(|p| Cow::Borrowed(p.as_path()))
}
}

View File

@ -721,6 +721,9 @@ pub enum Error {
/// Config file not found
NotFound,
/// Config file empty
Empty,
/// Couldn't read $HOME environment variable
ReadingEnvHome(env::VarError),
@ -923,6 +926,7 @@ impl ::std::error::Error for Error {
fn cause(&self) -> Option<&::std::error::Error> {
match *self {
Error::NotFound => None,
Error::Empty => None,
Error::ReadingEnvHome(ref err) => Some(err),
Error::Io(ref err) => Some(err),
Error::Yaml(ref err) => Some(err),
@ -932,6 +936,7 @@ impl ::std::error::Error for Error {
fn description(&self) -> &str {
match *self {
Error::NotFound => "could not locate config file",
Error::Empty => "empty config file",
Error::ReadingEnvHome(ref err) => err.description(),
Error::Io(ref err) => err.description(),
Error::Yaml(ref err) => err.description(),
@ -943,6 +948,7 @@ impl ::std::fmt::Display for Error {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match *self {
Error::NotFound => write!(f, "{}", ::std::error::Error::description(self)),
Error::Empty => write!(f, "{}", ::std::error::Error::description(self)),
Error::ReadingEnvHome(ref err) => {
write!(f, "could not read $HOME environment variable: {}", err)
},
@ -978,19 +984,16 @@ impl From<serde_yaml::Error> for Error {
pub type Result<T> = ::std::result::Result<T, Error>;
impl Config {
/// Attempt to load the config file
///
/// The config file is loaded from the first file it finds in this list of paths
/// Get the location of the first found default config file paths
/// according to the following order:
///
/// 1. $XDG_CONFIG_HOME/alacritty/alacritty.yml
/// 2. $XDG_CONFIG_HOME/alacritty.yml
/// 3. $HOME/.config/alacritty/alacritty.yml
/// 4. $HOME/.alacritty.yml
pub fn load() -> Result<Config> {
let home = env::var("HOME")?;
pub fn installed_config() -> Option<Cow<'static, Path>> {
// Try using XDG location by default
let path = ::xdg::BaseDirectories::with_prefix("alacritty")
::xdg::BaseDirectories::with_prefix("alacritty")
.ok()
.and_then(|xdg| xdg.find_config_file("alacritty.yml"))
.or_else(|| {
@ -999,27 +1002,29 @@ impl Config {
})
})
.or_else(|| {
// Fallback path: $HOME/.config/alacritty/alacritty.yml
let fallback = PathBuf::from(&home).join(".config/alacritty/alacritty.yml");
match fallback.exists() {
true => Some(fallback),
false => None
if let Ok(home) = env::var("HOME") {
// Fallback path: $HOME/.config/alacritty/alacritty.yml
let fallback = PathBuf::from(&home).join(".config/alacritty/alacritty.yml");
if fallback.exists() {
return Some(fallback);
}
// Fallback path: $HOME/.alacritty.yml
let fallback = PathBuf::from(&home).join(".alacritty.yml");
if fallback.exists() {
return Some(fallback);
}
}
None
})
.unwrap_or_else(|| {
// Fallback path: $HOME/.alacritty.yml
PathBuf::from(&home).join(".alacritty.yml")
});
Config::load_from(path)
.map(|path| path.into())
}
pub fn write_defaults() -> io::Result<PathBuf> {
pub fn write_defaults() -> io::Result<Cow<'static, Path>> {
let path = ::xdg::BaseDirectories::with_prefix("alacritty")
.map_err(|err| io::Error::new(io::ErrorKind::NotFound, ::std::error::Error::description(&err)))
.and_then(|p| p.place_config_file("alacritty.yml"))?;
File::create(&path)?.write_all(DEFAULT_ALACRITTY_CONFIG.as_bytes())?;
Ok(path)
Ok(path.into())
}
/// Get list of colors
@ -1116,7 +1121,7 @@ impl Config {
self.hide_cursor_when_typing
}
fn load_from<P: Into<PathBuf>>(path: P) -> Result<Config> {
pub fn load_from<P: Into<PathBuf>>(path: P) -> Result<Config> {
let path = path.into();
let raw = Config::read_file(path.as_path())?;
let mut config: Config = serde_yaml::from_str(&raw)?;
@ -1129,6 +1134,9 @@ impl Config {
let mut f = fs::File::open(path)?;
let mut contents = String::new();
f.read_to_string(&mut contents)?;
if contents.len() == 0 {
return Err(Error::Empty);
}
Ok(contents)
}
@ -1427,7 +1435,7 @@ impl Monitor {
// Reload file
path.map(|path| {
if path == config_path {
match Config::load() {
match Config::load_from(path) {
Ok(config) => {
let _ = config_tx.send(config);
handler.on_config_reload();

View File

@ -37,28 +37,9 @@ use alacritty::tty::{self, process_should_exit};
use alacritty::util::fmt::Red;
fn main() {
// Load configuration
let config = Config::load().unwrap_or_else(|err| {
match err {
// Use default config when not found
config::Error::NotFound => {
match Config::write_defaults() {
Ok(path) => err_println!("Config file not found; write defaults config to {:?}", path),
Err(err) => err_println!("Write defaults config failure: {}", err)
}
Config::load().unwrap()
},
// If there's a problem with the config file, print an error
// and exit.
_ => die!("{}", err),
}
});
// Load command line options
// Load command line options and config
let options = cli::Options::load();
let config = load_config(&options);
// Run alacritty
if let Err(err) = run(config, options) {
@ -68,6 +49,32 @@ fn main() {
info!("Goodbye.");
}
/// Load configuration
///
/// If a configuration file is given as a command line argument we don't
/// generate a default file. If an empty configuration file is given, i.e.
/// /dev/null, we load the compiled-in defaults.
fn load_config(options: &cli::Options) -> Config {
let config_path = options.config_path()
.or_else(|| Config::installed_config())
.unwrap_or_else(|| {
Config::write_defaults()
.unwrap_or_else(|err| die!("Write defaults config failure: {}", err))
});
Config::load_from(&*config_path).unwrap_or_else(|err| {
match err {
config::Error::NotFound => {
die!("Config file not found at: {}", config_path.display());
},
config::Error::Empty => {
err_println!("Empty config; Loading defaults");
Config::default()
},
_ => die!("{}", err),
}
})
}
/// Run Alacritty
///