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

Add config command #112

Draft
wants to merge 18 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 14 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
88 changes: 85 additions & 3 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 @@ -26,6 +26,7 @@ shellexpand = "2.*"
simplelog = "0.12.*"
tokio = "1.*"
toml = "0.4.*"
dialoguer = { git = "https://github.com/SIMULATAN/dialoguer", features = [] }
Copy link
Owner

@SuperCuber SuperCuber Feb 17, 2024

Choose a reason for hiding this comment

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

crates.io does not allow git dependencies :(
We might have to get your changes merged into dialoguer, is there a reason you haven't opened a PR?

Copy link
Author

@SIMULATAN SIMULATAN Feb 17, 2024

Choose a reason for hiding this comment

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

The main reason is that I kinda just haven't gotten around to it, especially since it wasn't fully approved yet.

However, that isn't the only problem:
My changes are vastly different from what the library usually stands for. While the other components aren't exactly configurable, this one very much is and thus seems a bit out of place (and therefore possibly out of scope).
Additionally, the code is kinda messy and the change is pretty large (~500 lines, although the majority of that is because I created a separate multi_select_plus component rather than editing the existing one).
Let's just say, I'm not exactly confident in my rust skills (I mean, just looking at your cleanup, it appears I made quite a few mistakes 😅)
To get an idea of the changes, see the diff

Considering that reasoning, I'll try to see if I can get something going that would have a chance at getting merged upstream, both in terms of code quality and "genericness". As I said a while back, right now, it's very opinionated, having only what's necessary for this feature to work (in particular, the callback feature).

Copy link
Owner

Choose a reason for hiding this comment

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

Sounds good, I'm sure dialoguer's maintainer will have comments to steer you in the right direction :)

Please link the PR here when you open one

watchexec = {version="=2.0.0-pre.14", optional = true}

[features]
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Usage: dotter [OPTIONS] [COMMAND]

Commands:
deploy Deploy the files to their respective targets. This is the default subcommand
config Interactively modify the local configuration file
undeploy Delete all deployed files from their target locations. Note that this operates on all files that are currently in cache
init Initialize global.toml with a single package containing all the files in the current directory pointing to a dummy value and a local.toml that selects that package
watch Run continuously, watching the repository for changes and deploying as soon as they happen. Can be ran with `--dry-run`
Expand Down
3 changes: 3 additions & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ pub enum Action {
#[default]
Deploy,

/// Interactively modify the local configuration file.
Config,

/// Delete all deployed files from their target locations.
/// Note that this operates on all files that are currently in cache.
Undeploy,
Expand Down
104 changes: 71 additions & 33 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::filesystem;
use core::fmt;
use std::collections::{BTreeMap, BTreeSet};
use std::fs;
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
Expand Down Expand Up @@ -92,68 +93,59 @@ pub struct Configuration {
pub recurse: bool,
}

#[derive(Debug, Deserialize, Serialize, Default)]
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
#[serde(deny_unknown_fields)]
pub struct Package {
#[serde(default)]
depends: Vec<String>,
pub depends: Vec<String>,
#[serde(default)]
files: Files,
#[serde(default)]
variables: Variables,
}

#[derive(Debug, Deserialize, Serialize)]
struct GlobalConfig {
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct GlobalConfig {
#[serde(default)]
#[cfg(feature = "scripting")]
helpers: Helpers,
#[serde(flatten)]
packages: BTreeMap<String, Package>,
pub packages: BTreeMap<String, Package>,
}

type IncludedConfig = BTreeMap<String, Package>;

#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(deny_unknown_fields)]
struct LocalConfig {
pub struct LocalConfig {
#[serde(default)]
includes: Vec<PathBuf>,
packages: Vec<String>,
pub packages: Vec<String>,
#[serde(default)]
files: Files,
#[serde(default)]
variables: Variables,
}

impl LocalConfig {
pub fn empty_config() -> Self {
Self {
includes: Vec::new(),
packages: Vec::new(),
files: Files::new(),
variables: Variables::new(),
}
}
}

pub fn load_configuration(
local_config: &Path,
global_config: &Path,
local_config_path: &Path,
global_config_path: &Path,
patch: Option<Package>,
) -> Result<Configuration> {
let global: GlobalConfig = filesystem::load_file(global_config)
.and_then(|c| c.ok_or_else(|| anyhow::anyhow!("file not found")))
.with_context(|| format!("load global config {:?}", global_config))?;
trace!("Global config: {:#?}", global);

// If local.toml can't be found, look for a file named <hostname>.toml instead
let mut local_config_buf = local_config.to_path_buf();
if !local_config_buf.exists() {
let hostname = hostname::get()
.context("failed to get the computer hostname")?
.into_string()
.expect("hostname cannot be converted to string");
info!(
"{:?} not found, using {}.toml instead (based on hostname)",
local_config, hostname
);
local_config_buf.set_file_name(&format!("{}.toml", hostname));
}
let global: GlobalConfig = load_global_config(global_config_path)?;

let local: LocalConfig = filesystem::load_file(local_config_buf.as_path())
.and_then(|c| c.ok_or_else(|| anyhow::anyhow!("file not found")))
.with_context(|| format!("load local config {:?}", local_config))?;
trace!("Local config: {:#?}", local);
let local: LocalConfig = load_local_config(local_config_path)?;

let mut merged_config =
merge_configuration_files(global, local, patch).context("merge configuration files")?;
Expand Down Expand Up @@ -185,6 +177,38 @@ pub fn load_configuration(
Ok(merged_config)
}

pub fn load_global_config(global_config: &Path) -> Result<GlobalConfig> {
let global: GlobalConfig = filesystem::load_file(global_config)
.and_then(|c| c.ok_or_else(|| anyhow::anyhow!("file not found")))
.with_context(|| format!("load global config {:?}", global_config))?;
trace!("Global config: {:#?}", global);

Ok(global)
}

pub fn load_local_config(local_config: &Path) -> Result<LocalConfig> {
// If local.toml can't be found, look for a file named <hostname>.toml instead
let mut local_config_buf = local_config.to_path_buf();
if !local_config_buf.exists() {
let hostname = hostname::get()
.context("failed to get the computer hostname")?
.into_string()
.expect("hostname cannot be converted to string");
info!(
"{:?} not found, using {}.toml instead (based on hostname)",
local_config, hostname
);
local_config_buf.set_file_name(&format!("{}.toml", hostname));
}

let local: LocalConfig = filesystem::load_file(local_config_buf.as_path())
.and_then(|c| c.ok_or_else(|| anyhow::anyhow!("file not found")))
.with_context(|| format!("load local config {:?}", local_config))?;
trace!("Local config: {:#?}", local);

Ok(local)
}

#[derive(Debug, Serialize, Deserialize, Default, Clone)]
#[serde(deny_unknown_fields)]
pub struct Cache {
Expand Down Expand Up @@ -258,7 +282,7 @@ fn recursive_extend_map(
}

#[allow(clippy::map_entry)]
fn merge_configuration_files(
pub fn merge_configuration_files(
mut global: GlobalConfig,
local: LocalConfig,
patch: Option<Package>,
Expand Down Expand Up @@ -637,3 +661,17 @@ mod tests {
);
}
}

impl Eq for Package {}

impl PartialEq for Package {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(self, other)
}
}

impl Hash for Package {
fn hash<H: Hasher>(&self, state: &mut H) {
std::ptr::hash(self, state);
}
}
Loading