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

Load config before start() #423

Merged
merged 1 commit into from
May 1, 2021
Merged
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
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ features = ["unstable"]

[dev-dependencies]
insta = "1.6.0"
tempfile = "3.2.0"

[build-dependencies]
structopt = "0.3"
Expand Down
135 changes: 82 additions & 53 deletions src/common/input/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ use std::io::{self, Read};
use std::path::{Path, PathBuf};

use super::keybinds::{Keybinds, KeybindsFromYaml};
use crate::cli::ConfigCli;
use crate::cli::{CliArgs, ConfigCli};
use crate::common::install;

use serde::Deserialize;
use std::convert::TryFrom;

const DEFAULT_CONFIG_FILE_NAME: &str = "config.yaml";
a-kenji marked this conversation as resolved.
Show resolved Hide resolved

type ConfigResult = Result<Config, ConfigError>;

/// Intermediate deserialisation config struct
/// Intermediate deserialization config struct
a-kenji marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Debug, Deserialize)]
pub struct ConfigFromYaml {
pub keybinds: Option<KeybindsFromYaml>,
Expand All @@ -27,13 +30,13 @@ pub struct Config {

#[derive(Debug)]
pub enum ConfigError {
// Deserialisation error
// Deserialization error
Serde(serde_yaml::Error),
// Io error
Io(io::Error),
// Io error with path context
IoPath(io::Error, PathBuf),
// Internal Deserialisation Error
// Internal Deserialization Error
FromUtf8(std::string::FromUtf8Error),
}

Expand All @@ -44,6 +47,35 @@ impl Default for Config {
}
}

impl TryFrom<&CliArgs> for Config {
a-kenji marked this conversation as resolved.
Show resolved Hide resolved
type Error = ConfigError;

fn try_from(opts: &CliArgs) -> ConfigResult {
if let Some(ref path) = opts.config {
return Config::new(&path);
}

if let Some(ConfigCli::Config { clean, .. }) = opts.option {
if clean {
return Config::from_default_assets();
}
}

let config_dir = opts.config_dir.clone().or_else(install::default_config_dir);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I misunderstood the behavior of default_config_dir, which I thought would create a directory.
default_config_dir just indicates a possible configuration directory, so it should be called here as the previous implementation.

By the way, as default_config_dir returns Option<PathBuf>, I made opts.config_dir call clone() to match these types. Maybe, I can delete this clone() by refactoring default_config_dir, but it should be done in another PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I misunderstood the behavior of default_config_dir, which I thought would create a directory.
default_config_dir just indicates a possible configuration directory, so it should be called here as the previous implementation.

Yeah, I glossed over that too now. The naming does suggest some sort of creation. I'll put it on my list to try to make the intention clearer.

By the way, as default_config_dir returns Option<PathBuf>, I made opts.config_dir call clone() to match these types. Maybe, I can delete this clone() by refactoring default_config_dir, but it should be done in another PR.

I would love that, as I am also unhappy about the state of default_config_dir, so feel free to open a PR on that, if you want to!


if let Some(ref config) = config_dir {
let path = config.join(DEFAULT_CONFIG_FILE_NAME);
if path.exists() {
Config::new(&path)
} else {
Config::from_default_assets()
}
} else {
Config::from_default_assets()
}
}
}

impl Config {
/// Uses defaults, but lets config override them.
pub fn from_yaml(yaml_config: &str) -> ConfigResult {
Expand Down Expand Up @@ -71,45 +103,6 @@ impl Config {
pub fn from_default_assets() -> ConfigResult {
Self::from_yaml(String::from_utf8(install::DEFAULT_CONFIG.to_vec())?.as_str())
}

/// Entry point of the configuration
#[cfg(not(test))]
pub fn from_cli_config(
location: Option<PathBuf>,
cli_config: Option<ConfigCli>,
config_dir: Option<PathBuf>,
) -> ConfigResult {
if let Some(path) = location {
return Config::new(&path);
}

if let Some(ConfigCli::Config { clean, .. }) = cli_config {
if clean {
return Config::from_default_assets();
}
}

if let Some(config) = config_dir {
let path = config.join("config.yaml");
if path.exists() {
Config::new(&path)
} else {
Config::from_default_assets()
}
} else {
Config::from_default_assets()
}
}

/// In order not to mess up tests from changing configurations
#[cfg(test)]
pub fn from_cli_config(
_: Option<PathBuf>,
_: Option<ConfigCli>,
_: Option<PathBuf>,
) -> ConfigResult {
Ok(Config::default())
}
}

impl Display for ConfigError {
Expand All @@ -119,7 +112,7 @@ impl Display for ConfigError {
ConfigError::IoPath(ref err, ref path) => {
write!(formatter, "IoError: {}, File: {}", err, path.display(),)
}
ConfigError::Serde(ref err) => write!(formatter, "Deserialisation error: {}", err),
ConfigError::Serde(ref err) => write!(formatter, "Deserialization error: {}", err),
ConfigError::FromUtf8(ref err) => write!(formatter, "FromUtf8Error: {}", err),
}
}
Expand Down Expand Up @@ -157,20 +150,56 @@ impl From<std::string::FromUtf8Error> for ConfigError {
// The unit test location.
#[cfg(test)]
mod config_test {
use std::io::Write;

use tempfile::tempdir;

use super::*;

#[test]
fn clean_option_equals_default_config() {
let cli_config = ConfigCli::Config { clean: true };
let config = Config::from_cli_config(None, Some(cli_config), None).unwrap();
let default = Config::default();
assert_eq!(config, default);
fn try_from_cli_args_with_config() {
let arbitrary_config = PathBuf::from("nonexistent.yaml");
let mut opts = CliArgs::default();
opts.config = Some(arbitrary_config);
println!("OPTS= {:?}", opts);
let result = Config::try_from(&opts);
assert!(result.is_err());
}

#[test]
fn try_from_cli_args_with_option_clean() {
let mut opts = CliArgs::default();
opts.option = Some(ConfigCli::Config { clean: true });
let result = Config::try_from(&opts);
assert!(result.is_ok());
}

#[test]
fn try_from_cli_args_with_config_dir() {
let mut opts = CliArgs::default();
let tmp = tempdir().unwrap();
File::create(tmp.path().join(DEFAULT_CONFIG_FILE_NAME))
.unwrap()
.write_all(b"keybinds: invalid\n")
.unwrap();
opts.config_dir = Some(tmp.path().to_path_buf());
let result = Config::try_from(&opts);
assert!(result.is_err());
}

#[test]
fn try_from_cli_args_with_config_dir_without_config() {
let mut opts = CliArgs::default();
let tmp = tempdir().unwrap();
opts.config_dir = Some(tmp.path().to_path_buf());
let result = Config::try_from(&opts);
assert_eq!(result.unwrap(), Config::default());
}

#[test]
fn no_config_option_file_equals_default_config() {
let config = Config::from_cli_config(None, None, None).unwrap();
let default = Config::default();
assert_eq!(config, default);
fn try_from_cli_args_default() {
let opts = CliArgs::default();
let result = Config::try_from(&opts);
assert_eq!(result.unwrap(), Config::default());
}
}
11 changes: 1 addition & 10 deletions src/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ pub enum AppInstruction {

/// Start Zellij with the specified [`OsApi`] and command-line arguments.
// FIXME this should definitely be modularized and split into different functions.
pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs, config: Config) {
let take_snapshot = "\u{1b}[?1049h";
os_input.unset_raw_mode(0);
let _ = os_input
Expand All @@ -130,15 +130,6 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {

env::set_var(&"ZELLIJ", "0");

let config_dir = opts.config_dir.or_else(install::default_config_dir);

let config = Config::from_cli_config(opts.config, opts.option, config_dir)
.map_err(|e| {
eprintln!("There was an error in the config file:\n{}", e);
std::process::exit(1);
})
.unwrap();

let command_is_executing = CommandIsExecuting::new();

let full_screen_ws = os_input.get_terminal_size_using_fd(0);
Expand Down
11 changes: 10 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod client;

use crate::cli::CliArgs;
use crate::command_is_executing::CommandIsExecuting;
use crate::common::input::config::Config;
use crate::os_input_output::get_os_input;
use crate::utils::{
consts::{ZELLIJ_IPC_PIPE, ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR},
Expand All @@ -17,12 +18,20 @@ use common::{
command_is_executing, errors, install, os_input_output, pty_bus, screen, start, utils, wasm_vm,
ApiCommand,
};
use std::convert::TryFrom;
use std::io::Write;
use std::os::unix::net::UnixStream;
use structopt::StructOpt;

pub fn main() {
let opts = CliArgs::from_args();
let config = match Config::try_from(&opts) {
Ok(config) => config,
Err(e) => {
eprintln!("There was an error in the config file:\n{}", e);
std::process::exit(1);
}
};
if let Some(split_dir) = opts.split {
match split_dir {
'h' => {
Expand Down Expand Up @@ -66,6 +75,6 @@ pub fn main() {
let os_input = get_os_input();
atomic_create_dir(ZELLIJ_TMP_DIR).unwrap();
atomic_create_dir(ZELLIJ_TMP_LOG_DIR).unwrap();
start(Box::new(os_input), opts);
start(Box::new(os_input), opts, config);
}
}
Loading