Skip to content

Commit

Permalink
✨ Add 'config show' and 'config edit' commands
Browse files Browse the repository at this point in the history
  • Loading branch information
mleduque committed Jun 1, 2024
1 parent 935b556 commit ad69a5f
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 14 deletions.
1 change: 1 addition & 0 deletions modda-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ handlebars = "5.1.2"
itertools = "0.13.0"
log = "0.4.21"
modda-lib = { path = "../modda-lib" }
open = "5.1.3"
serde_json = "1.0.117"
serde_yaml = "0.9.34-deprecated"
8 changes: 7 additions & 1 deletion modda-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use env_logger::{Env, Target};
use log::debug;

use log_settings::LogSettings;
use modda_lib::args::{ Cli, Commands };
use modda_lib::args::{ Cli, Commands, ConfigArgs };
use modda_lib::cache::Cache;
use modda_lib::canon_path::CanonPath;
use modda_lib::chitin::ensure_chitin_key;
Expand All @@ -19,6 +19,8 @@ use modda_lib::sub::append_mod::append_mod;
use modda_lib::sub::extract_manifest::extract_manifest;
use modda_lib::sub::install::install;
use modda_lib::sub::invalidate::invalidate;
use subcommands::config_show::open_global_config_dir;
use subcommands::config_edit::edit_global_config_dir;
use subcommands::discover::discover;
use subcommands::introspect::introspect;
use subcommands::list_components::sub_list_components;
Expand Down Expand Up @@ -66,5 +68,9 @@ fn main() -> Result<()> {
Commands::Introspect(ref params) => introspect(params, &settings, &current_dir,
&global_conf_dir(),
&log_settings),
Commands::GlobalConfig(sub) => match sub {
ConfigArgs::Show(_) => open_global_config_dir(),
ConfigArgs::Edit(_) => edit_global_config_dir(&config),
}
}
}
58 changes: 58 additions & 0 deletions modda-cli/src/subcommands/config_edit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@

use std::fs::OpenOptions;
use std::path::PathBuf;
use std::io::{BufWriter, Result as IoResult, Write};

use anyhow::{bail, Result};
use handlebars::Handlebars;
use modda_lib::config::{global_conf_dir, Config, Settings};
use modda_lib::progname::PROGNAME;

const CONFIG_TEMPLATE: &'static str = include_str!("config_template.yml");

pub fn edit_global_config_dir(config: &Config) -> Result<()> {
let directory = global_conf_dir()
.expect("Could not determine the global config location");
if !directory.exists() {
println!("Global config directory doesn't exist, creating {dir}",
dir = directory.as_os_str().to_string_lossy());
if let Err(error) = std::fs::create_dir_all(&directory) {
bail!("Could not create global config directory {dir}\n {error}",
dir = directory.as_os_str().to_string_lossy())
}
}
let config_path = match Settings::find_config_in_dir(&directory)? {
None => {
println!("Global config file doesn't exist, creating in {dir}",
dir = directory.as_os_str().to_string_lossy());
create_config_yml(&directory)?
}
Some(path) => path,
};
println!("Opening global config file {config_file_path}",
config_file_path = config_path.as_os_str().to_string_lossy());
match &config.code_editor {
Some(editor) => match open::with_detached(config_path, editor) {
IoResult::Ok(_) => Ok(()),
IoResult::Err(error) => bail!("Could not start editor program {editor}\n {error}")
}
None => match open::that_detached(config_path) {
IoResult::Ok(_) => Ok(()),
IoResult::Err(error) => bail!("Could not start system editor\n {error}"),
}
}
}

fn create_config_yml(directory: &PathBuf) -> Result<PathBuf> {
let registry = Handlebars::new();
let config_content = registry.render_template(CONFIG_TEMPLATE, &())?;

let config_path = directory.join(format!("{}.yml", PROGNAME));

let mut dest = match OpenOptions::new().create(true).write(true).open(&config_path) {
Err(err) => bail!("Could not create config file\n {}", err),
std::io::Result::Ok(file) => file,
};
dest.write_all(config_content.as_bytes())?;
Ok(config_path)
}
16 changes: 16 additions & 0 deletions modda-cli/src/subcommands/config_show.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

use anyhow::{bail, Result};
use modda_lib::config::global_conf_dir;

pub fn open_global_config_dir() -> Result<()> {
let directory = global_conf_dir()
.expect("Could not determine the global config location");
if !directory.exists() {
if let Err(error) = std::fs::create_dir_all(&directory) {
bail!("Could not create global config directory {dir}\n {error}",
dir = directory.as_os_str().to_string_lossy())
}
}
open::that_detached(directory)?;
Ok(())
}
37 changes: 37 additions & 0 deletions modda-cli/src/subcommands/config_template.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

# Modda config file
# All options are optional

#### Path to the location were the archive cache is.
#### After an archive is downloaded it will be stored in the cache.
#### Before trying to download an archive, the program will check in the archive
#### if it's already there (except if told otherwise).
#archive_cache: ~

#### Temporary location where the archive are extracted before being copied to the game
#### directory (and then installed).
#### That's kind of an expert (perf optimization) that most people will not need to care about.
#extract_location: ~

#### Enter the path to your weidu executable (`weidu` on linux or macos, `weidu.exe` on windows)
#### If this is not set, it will use either a weidu executable in the game directory (if any) or
#### try to use it from the path
#weidu_path: ~

#### If this is set, a weidu executable in the game directory will be _ignored_ and only
#### one defined in `weidu_path` or on the path will be used
#ignore_current_dir_weidu: ~

#### Configuration for external programs used for uncommon archive types (RAR, 7Z etc.)
#extractors:
# rar:
# command: unrar-nonfree
# args: [ "x", "${input}", "${target}" ]
# 7z:
# command: 7z
# args: [ "x", "${input}", "-o${target}" ]

#### Program used for code editing (for example vscode (`code`), notepad++ etc.)
#### This is used when calling `modda config edit`
#### If not set, this will let the OS decide what to open `yaml` files with.
#code_editor: code
2 changes: 1 addition & 1 deletion modda-cli/src/subcommands/introspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ max level: {{max_level}}
{{log_style_name}}="{{log_style_value}}"
"#;

pub fn introspect(params:&Introspect, settings: &Settings, game_dir: &CanonPath,
pub fn introspect(params: &Introspect, settings: &Settings, game_dir: &CanonPath,
global_conf_dir: &Option<PathBuf>, log_settings: &LogSettings) -> Result<()> {
let registry = Handlebars::new();

Expand Down
2 changes: 2 additions & 0 deletions modda-cli/src/subcommands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@

pub mod config_edit;
pub mod config_show;
pub mod discover;
pub mod introspect;
pub mod list_components;
Expand Down
21 changes: 20 additions & 1 deletion modda-lib/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ pub enum Commands {
/// Discovers mods in the game directory and builds a manifest skeleton.
Discover(Discover),
/// Show configuration/settings information.
Introspect(Introspect)
Introspect(Introspect),

/// Works with the global configuration
#[clap(subcommand)]
GlobalConfig(ConfigArgs)
}

#[derive(Args, Debug, Default)]
Expand Down Expand Up @@ -202,3 +206,18 @@ pub struct Introspect {
#[arg(long, short)]
pub show_config: bool,
}

#[derive(Debug, Subcommand)]
pub enum ConfigArgs {
/// Show the global configuration (opens the directory that contains the global configuration file)
Show(ConfigShow),
/// Open the global configuration file.<br>
/// If it doesn't exist yet, it will create an default configuration file.
Edit(ConfigEdit),
}

#[derive(Args, Debug)]
pub struct ConfigShow {}

#[derive(Args, Debug)]
pub struct ConfigEdit {}
37 changes: 26 additions & 11 deletions modda-lib/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use std::collections::HashMap;
use std::hash::Hash;
use std::io::BufReader;
use std::io::{BufReader, Result as IoResult};
use std::fs::File;
use std::path::{Path, PathBuf};

Expand All @@ -18,6 +18,7 @@ pub const ARCHIVE_CACHE_ENV_VAR: &'static str = "MODDA_ARCHIVE_CACHE";
pub const EXTRACT_LOCATION_ENV_VAR: &'static str = "MODDA_EXTRACT_LOCATION";
pub const WEIDU_PATH_ENV_VAR: &'static str = "MODDA_WEIDU_PATH";
pub const IGNORE_CURRENT_DIR_WEIDU_ENV_VAR: &'static str = "MODDA_IGNORE_CURRENT_DIR_WEIDU";
pub const CODE_EDITOR_ENV_VAR: &'static str = "MODDA_CODE_EDITOR";

#[derive(Deserialize, Serialize, Debug, Default, Clone)]
pub struct Config {
Expand Down Expand Up @@ -69,6 +70,10 @@ pub struct Config {
/// ```
#[serde(default)]
pub extractors: HashMap<LwcString, ExtractorCommand>,

/// Path to the code editor program.<br>
/// Used with the `config edit` subcommands.
pub code_editor: Option<String>,
}

#[derive(Deserialize, Serialize, Debug, Default, Clone)]
Expand Down Expand Up @@ -139,23 +144,31 @@ impl Settings {
})
}

pub fn read_config_in_dir(dir: &Path) -> Result<Option<ConfigSource>> {
pub fn find_config_in_dir(dir: &Path) -> Result<Option<PathBuf>> {
let yml_name = format!("{prog_name}.yml", prog_name = PROGNAME);
let yaml_name = format!("{prog_name}.yaml", prog_name = PROGNAME);
let yml_path = dir.join(yml_name.to_string());
let yaml_path = dir.join(yaml_name.to_string());
let yml_file = File::open(yml_path.to_owned()).ok();
let yaml_file = File::open(yaml_path.to_owned()).ok();
let candidate = match (yml_file, yaml_file) {
(None, None) => None,
(Some(file), None) => Some((yml_path, file)),
(None, Some(file)) => Some((yaml_path, file)),
(Some(_), Some(_)) =>
let yml_file_exists = yml_path.exists();
let yaml_file_exists = yaml_path.exists();
match (yml_file_exists, yaml_file_exists) {
(false, false) => Ok(None),
(true, false) => Ok(Some(yml_path)),
(false, true) => Ok(Some(yaml_path)),
(true, true) =>
bail!("Both {yml_name} and {yaml_name} files are present in {dir:?} and I can't choose.\nPlease delete one of those.")
};
}
}

pub fn read_config_in_dir(dir: &Path) -> Result<Option<ConfigSource>> {
let candidate = Settings::find_config_in_dir(dir)?;
match candidate {
None => Ok(None),
Some((path, file)) => {
Some(path) => {
let file = match File::open(&path) {
IoResult::Ok(file) => file,
Err(error) => bail!("Could not open config file at {:?}\n {error}", path),
};
let path_as_str = path.as_os_str().to_string_lossy().to_string();
debug!("found config file at {path_as_str}");

Expand Down Expand Up @@ -189,6 +202,7 @@ impl Settings {
ignore_current_dir_weidu,
// Setting extractor not supported for now
extractors: HashMap::new(),
code_editor: std::env::var(CODE_EDITOR_ENV_VAR).ok(),
})
})
}
Expand All @@ -205,6 +219,7 @@ fn combine(global: Option<Config>, local: Option<Config>, env_config: Option<Con
weidu_path: env_config.weidu_path.or(local.weidu_path).or(global.weidu_path),
ignore_current_dir_weidu: env_config.ignore_current_dir_weidu.or(local.ignore_current_dir_weidu).or(global.ignore_current_dir_weidu),
extractors: merge_maps(&global.extractors, &local.extractors, &env_config.extractors),
code_editor: env_config.code_editor.or(local.code_editor).or(global.code_editor),
}
}

Expand Down
3 changes: 3 additions & 0 deletions modda-lib/src/get_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ mod test_retrieve_location {
weidu_path: None,
ignore_current_dir_weidu: None,
extractors: HashMap::new(),
code_editor: None,
};

let expected_dest = PathBuf::from("/cache_path/http/example.com");
Expand Down Expand Up @@ -329,6 +330,7 @@ mod test_retrieve_location {
weidu_path: None,
ignore_current_dir_weidu: None,
extractors: HashMap::new(),
code_editor: None,
};


Expand Down Expand Up @@ -378,6 +380,7 @@ mod test_retrieve_location {
weidu_path: None,
ignore_current_dir_weidu: None,
extractors: HashMap::new(),
code_editor: None,
};


Expand Down

0 comments on commit ad69a5f

Please sign in to comment.