-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Bit of a refactor after feature creep, prepping for much better desig…
…ned --light and --superlight modes to get around circular deps, replacing the pretty crappy 'initial' double render approach (#16)
- Loading branch information
Showing
30 changed files
with
1,094 additions
and
1,029 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
use std::{fs, path::Path}; | ||
|
||
use serde::{Deserialize, Serialize}; | ||
|
||
use super::{context::Context, engine::Engine, tasks::Tasks}; | ||
use crate::{init::update_schema_directive_if_needed, prelude::*}; | ||
|
||
#[derive(Clone, Debug, Deserialize, Serialize)] | ||
pub struct Config { | ||
// All should be optional to allow empty config file, even though it wouldn't make too much sense! | ||
#[serde(default = "Context::default")] | ||
pub context: Context, | ||
#[serde(default = "Vec::new")] | ||
pub exclude: Vec<String>, | ||
#[serde(default = "Engine::default")] | ||
pub engine: Engine, | ||
#[serde(default = "Vec::new")] | ||
pub ignore_files: Vec<String>, | ||
#[serde(default = "default_matchers")] | ||
pub matchers: Vec<String>, | ||
#[serde(default = "Tasks::default")] | ||
pub tasks: Tasks, | ||
} | ||
|
||
fn default_matchers() -> Vec<String> { | ||
vec!["zetch".into()] | ||
} | ||
|
||
impl Config { | ||
pub fn ctx_keys(&self) -> Vec<&str> { | ||
let mut keys = Vec::new(); | ||
|
||
for (key, _) in self.context.stat.iter() { | ||
keys.push(key.as_str()); | ||
} | ||
|
||
for (key, _) in self.context.env.iter() { | ||
keys.push(key.as_str()); | ||
} | ||
|
||
for (key, _) in self.context.cli.iter() { | ||
keys.push(key.as_str()); | ||
} | ||
|
||
keys | ||
} | ||
|
||
pub fn from_toml(config_path: &Path) -> Result<Self, Zerr> { | ||
Config::from_toml_inner(config_path).attach_printable_lazy(|| { | ||
format!( | ||
"Error reading config file from '{}'.", | ||
config_path.display() | ||
) | ||
}) | ||
} | ||
|
||
fn from_toml_inner(config_path: &Path) -> Result<Self, Zerr> { | ||
let contents = autoupdate(config_path)?; | ||
|
||
// Decode directly the toml directly into serde/json, using that internally: | ||
let json: serde_json::Value = match toml::from_str(&contents) { | ||
Ok(toml) => toml, | ||
Err(e) => { | ||
return Err(zerr!( | ||
Zerr::ConfigInvalid, | ||
"Invalid toml formatting: '{}'.", | ||
e | ||
)) | ||
} | ||
}; | ||
|
||
// This will check against the json schema, | ||
// can produce much better errors than the toml decoder can, so prevalidate first: | ||
super::validate::pre_validate(&json)?; | ||
|
||
// Now deserialize after validation: | ||
let mut config: Config = | ||
serde_json::from_value(json).change_context(Zerr::InternalError)?; | ||
|
||
super::validate::post_validate(&mut config, config_path)?; | ||
|
||
Ok(config) | ||
} | ||
} | ||
|
||
/// Reads & pre-parses the config and updates managed sections, returns updated to save and use if changes needed. | ||
/// | ||
/// E.g. currently just updates the schema directive if needs changing. | ||
fn autoupdate(config_path: &Path) -> Result<String, Zerr> { | ||
let mut contents = fs::read_to_string(config_path).change_context(Zerr::InternalError)?; | ||
let mut updated = false; | ||
|
||
// Handle the schema directive: | ||
if let Some(new_contents) = update_schema_directive_if_needed(&contents) { | ||
// Re-write schema to file first: | ||
contents = new_contents; | ||
updated = true; | ||
} | ||
|
||
if updated { | ||
fs::write(config_path, &contents).change_context(Zerr::InternalError)?; | ||
} | ||
|
||
Ok(contents) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
use std::{collections::HashMap, path::Path}; | ||
|
||
use bitbazaar::cli::{Bash, BashErr}; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
use crate::{ | ||
coerce::{coerce, Coerce}, | ||
prelude::*, | ||
}; | ||
|
||
#[derive(Clone, Debug, Deserialize, Serialize)] | ||
pub struct CtxStaticVar { | ||
pub value: serde_json::Value, | ||
pub coerce: Option<Coerce>, | ||
} | ||
|
||
impl CtxStaticVar { | ||
pub fn read(&self) -> Result<serde_json::Value, Zerr> { | ||
coerce(&self.value, &self.coerce) | ||
} | ||
} | ||
|
||
#[derive(Clone, Debug, Deserialize, Serialize)] | ||
pub struct CtxEnvVar { | ||
pub env_name: Option<String>, | ||
pub default: Option<serde_json::Value>, | ||
pub coerce: Option<Coerce>, | ||
} | ||
|
||
impl CtxEnvVar { | ||
pub fn read(&self, key_name: &str, default_banned: bool) -> Result<serde_json::Value, Zerr> { | ||
let env_name = match &self.env_name { | ||
Some(env_name) => env_name, | ||
None => key_name, | ||
}; | ||
|
||
let value = match std::env::var(env_name) { | ||
Ok(value) => value, | ||
Err(_) => { | ||
if self.default.is_some() && default_banned { | ||
return Err(zerr!( | ||
Zerr::ContextLoadError, | ||
"Could not find environment variable '{}' and the default has been banned using the 'ban-defaults' cli option.", | ||
env_name | ||
)); | ||
} else { | ||
match &self.default { | ||
Some(value) => return Ok(value.clone()), | ||
None => { | ||
return Err(zerr!( | ||
Zerr::ContextLoadError, | ||
"Could not find environment variable '{}' and no default provided.", | ||
env_name | ||
)) | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
|
||
coerce(&serde_json::Value::String(value), &self.coerce) | ||
} | ||
} | ||
|
||
#[derive(Clone, Debug, Deserialize, Serialize)] | ||
pub struct CtxCliVar { | ||
pub commands: Vec<String>, | ||
pub coerce: Option<Coerce>, | ||
pub initial: Option<serde_json::Value>, | ||
} | ||
|
||
impl CtxCliVar { | ||
pub fn read(&self, config_path: &Path) -> Result<serde_json::Value, Zerr> { | ||
let config_dir = config_path.parent().ok_or_else(|| { | ||
zerr!( | ||
Zerr::InternalError, | ||
"Failed to get parent dir of config file: {}", | ||
config_path.display() | ||
) | ||
})?; | ||
|
||
let mut bash = Bash::new().chdir(config_dir); | ||
for command in self.commands.iter() { | ||
bash = bash.cmd(command); | ||
} | ||
let cmd_out = match bash.run() { | ||
Ok(cmd_out) => Ok(cmd_out), | ||
Err(e) => match e.current_context() { | ||
BashErr::InternalError(_) => Err(e.change_context(Zerr::InternalError)), | ||
_ => Err(e.change_context(Zerr::UserCommandError)), | ||
}, | ||
}?; | ||
cmd_out.throw_on_bad_code(Zerr::UserCommandError)?; | ||
|
||
// Prevent empty output: | ||
let last_cmd_out = cmd_out.last_stdout(); | ||
if last_cmd_out.trim().is_empty() { | ||
return Err(zerr!( | ||
Zerr::UserCommandError, | ||
"Implicit None. Final cli command returned nothing.", | ||
) | ||
.attach_printable(cmd_out.fmt_attempted_commands())); | ||
} | ||
|
||
coerce(&serde_json::Value::String(last_cmd_out), &self.coerce) | ||
} | ||
} | ||
|
||
#[derive(Clone, Debug, Deserialize, Serialize, Default)] | ||
pub struct Context { | ||
#[serde(rename(deserialize = "static"))] | ||
#[serde(default = "HashMap::new")] | ||
pub stat: HashMap<String, CtxStaticVar>, | ||
|
||
#[serde(default = "HashMap::new")] | ||
pub env: HashMap<String, CtxEnvVar>, | ||
|
||
#[serde(default = "HashMap::new")] | ||
pub cli: HashMap<String, CtxCliVar>, | ||
} |
Oops, something went wrong.