Skip to content

Commit

Permalink
Apply templates per plugin not per file
Browse files Browse the repository at this point in the history
The template now uses a for loop to loop over the files in the plugin.
the `each` field is no longer supported in the config file.
  • Loading branch information
rossmacarthur committed Oct 10, 2022
1 parent fb28374 commit 708bd9e
Show file tree
Hide file tree
Showing 65 changed files with 253 additions and 505 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -627,7 +627,7 @@ following.

```toml
[templates]
source = { value = 'source "{{ file }}"', each = true }
source = '{% for file in files %}source "{{ file }}"\n{% endfor %}'
PATH = 'export PATH="{{ dir }}:$PATH"'
path = 'path=( "{{ dir }}" $path )'
fpath = 'fpath=( "{{ dir }}" $fpath )'
Expand Down
2 changes: 1 addition & 1 deletion docs/src/Configuration.md
Expand Up @@ -236,7 +236,7 @@ following.

```toml
[templates]
source = { value = 'source "{{ file }}"', each = true }
source = '{% for file in files %}source "{{ file }}"\n{% endfor %}'
PATH = 'export PATH="{{ dir }}:$PATH"'
path = 'path=( "{{ dir }}" $path )'
fpath = 'fpath=( "{{ dir }}" $fpath )'
Expand Down
2 changes: 1 addition & 1 deletion docs/src/Examples.md
Expand Up @@ -27,7 +27,7 @@ Then add a template that calls `zsh-defer source` instead of just `source`.

```toml
[templates]
defer = { value = 'zsh-defer source "{{ file }}"', each = true }
defer = '{% for file in files %}zsh-defer source "{{ file }}"\n{% endfor %}'
```

Now any plugin that you want to defer you can apply the `defer` template. For
Expand Down
4 changes: 2 additions & 2 deletions src/config/edit.rs
Expand Up @@ -230,7 +230,7 @@ tag = '0.1.0'
apply = ["PATH", "source"]
[templates]
prompt = { value = 'ln -sf "{{ file }}" "{{ data_dir }}/functions/prompt_{{ name }}_setup"', each = true }
prompt = '{% for file in files %}ln -sf "{{ file }}" "{{ data_dir }}/functions/prompt_{{ name }}_setup"{% endfor %}'
# yes this is the pure plugin
[plugins.pure]
Expand All @@ -257,7 +257,7 @@ use = ["{{ name }}.zsh"]
apply = ["PATH", "source"]
[templates]
prompt = { value = 'ln -sf "{{ file }}" "{{ data_dir }}/functions/prompt_{{ name }}_setup"', each = true }
prompt = '{% for file in files %}ln -sf "{{ file }}" "{{ data_dir }}/functions/prompt_{{ name }}_setup"{% endfor %}'
# yes this is the pure plugin
[plugins.pure]
Expand Down
111 changes: 2 additions & 109 deletions src/config/file.rs
Expand Up @@ -14,7 +14,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
use thiserror::Error;
use url::Url;

use crate::config::{GitReference, Shell, Template};
use crate::config::{GitReference, Shell};

/// The contents of the configuration file.
#[derive(Debug, Default, Deserialize)]
Expand All @@ -28,7 +28,7 @@ pub struct RawConfig {
/// The default list of template names to apply to each matched file.
pub apply: Option<Vec<String>>,
/// A map of name to template string.
pub templates: IndexMap<String, Template>,
pub templates: IndexMap<String, String>,
/// A map of name to plugin.
pub plugins: IndexMap<String, RawPlugin>,
/// Any extra keys,
Expand Down Expand Up @@ -195,75 +195,6 @@ impl FromStr for Shell {
}
}

mod template {
use super::*;

struct Visitor;

/// The same as a [`Template`]. It is used to prevent recursion when
/// deserializing.
#[derive(Deserialize)]
struct TemplateAux {
value: String,
each: bool,
}

impl From<TemplateAux> for Template {
fn from(aux: TemplateAux) -> Self {
let TemplateAux { value, each } = aux;
Self { value, each }
}
}

impl From<&str> for Template {
fn from(s: &str) -> Self {
Self {
value: s.to_string(),
each: false,
}
}
}

impl<'de> de::Visitor<'de> for Visitor {
type Value = Template;

fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("string or map")
}

fn visit_str<E>(self, value: &str) -> result::Result<Self::Value, E>
where
E: de::Error,
{
Ok(From::from(value))
}

fn visit_map<M>(self, visitor: M) -> result::Result<Self::Value, M::Error>
where
M: de::MapAccess<'de>,
{
let aux: TemplateAux =
Deserialize::deserialize(de::value::MapAccessDeserializer::new(visitor))?;
Ok(aux.into())
}
}

/// Manually implement `Deserialize` for a `Template`.
///
/// Unfortunately we can not use [the recommended method][string-or-struct],
/// because we are storing `Template`s in a map.
///
/// [string-or-struct](https://serde.rs/string-or-struct.html)
impl<'de> Deserialize<'de> for Template {
fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(Visitor)
}
}
}

/// Produced when we fail to parse a Git protocol.
#[derive(Debug, Error)]
#[error("expected one of `git`, `https`, or `ssh`, got `{}`", self.0)]
Expand Down Expand Up @@ -438,44 +369,6 @@ mod tests {
)
}

#[derive(Debug, Deserialize)]
struct TemplateTest {
t: Template,
}

#[test]
fn template_deserialize_as_str() {
let test: TemplateTest = toml::from_str("t = 'test'").unwrap();
assert_eq!(
test.t,
Template {
value: "test".to_string(),
each: false
}
);
}

#[test]
fn template_deserialize_as_map() {
let test: TemplateTest = toml::from_str("t = { value = 'test', each = true }").unwrap();
assert_eq!(
test.t,
Template {
value: "test".to_string(),
each: true
}
);
}

#[test]
fn template_deserialize_invalid() {
let error = toml::from_str::<TemplateTest>("t = 0").unwrap_err();
assert_eq!(
error.to_string(),
"invalid type: integer `0`, expected string or map for key `t` at line 1 column 5"
);
}

#[derive(Debug, Deserialize)]
struct TestGitReference {
#[serde(flatten)]
Expand Down
11 changes: 1 addition & 10 deletions src/config/mod.rs
Expand Up @@ -30,7 +30,7 @@ pub struct Config {
/// The default list of template names to apply to each matched file.
pub apply: Option<Vec<String>>,
/// A map of name to template string.
pub templates: IndexMap<String, Template>,
pub templates: IndexMap<String, String>,
/// Each configured plugin.
pub plugins: Vec<Plugin>,
}
Expand All @@ -42,15 +42,6 @@ pub enum Shell {
Zsh,
}

/// A wrapper around a template string.
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct Template {
/// The actual template string.
pub value: String,
/// Whether this template should be applied to each file.
pub each: bool,
}

/// A configured plugin.
#[derive(Debug, PartialEq, Eq)]
pub enum Plugin {
Expand Down
8 changes: 4 additions & 4 deletions src/config/normalize.rs
Expand Up @@ -8,7 +8,7 @@ use indexmap::IndexMap;
use url::Url;

use crate::config::file::{GitProtocol, RawConfig, RawPlugin};
use crate::config::{Config, ExternalPlugin, InlinePlugin, Plugin, Shell, Source, Template};
use crate::config::{Config, ExternalPlugin, InlinePlugin, Plugin, Shell, Source};
use crate::util::TEMPLATE_ENGINE;

/// The Gist domain host.
Expand All @@ -35,7 +35,7 @@ pub fn normalize(raw_config: RawConfig, warnings: &mut Vec<Error>) -> Result<Con
// Check that the templates can be compiled.
for (name, template) in &templates {
TEMPLATE_ENGINE
.compile(&template.value)
.compile(template)
.with_context(s!("failed to compile template `{}`", name))?;
}

Expand Down Expand Up @@ -70,7 +70,7 @@ fn normalize_plugin(
raw_plugin: RawPlugin,
name: String,
shell: Shell,
templates: &IndexMap<String, Template>,
templates: &IndexMap<String, String>,
warnings: &mut Vec<Error>,
) -> Result<Plugin> {
enum TempSource {
Expand Down Expand Up @@ -249,7 +249,7 @@ where
fn validate_template_names(
shell: Shell,
apply: &Option<Vec<String>>,
templates: &IndexMap<String, Template>,
templates: &IndexMap<String, String>,
) -> Result<()> {
if let Some(apply) = apply {
for name in apply {
Expand Down
4 changes: 2 additions & 2 deletions src/lock/file.rs
Expand Up @@ -7,7 +7,7 @@ use anyhow::{Context as ResultExt, Error, Result};
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};

use crate::config::{InlinePlugin, Template};
use crate::config::InlinePlugin;
use crate::context::Context;

/// A locked `Config`.
Expand All @@ -22,7 +22,7 @@ pub struct LockedConfig {
///
/// Note: this field must come last in the struct for it to serialize
/// properly.
pub templates: IndexMap<String, Template>,
pub templates: IndexMap<String, String>,
/// Any errors that occurred while generating this `LockedConfig`.
#[serde(skip)]
pub errors: Vec<Error>,
Expand Down
32 changes: 12 additions & 20 deletions src/lock/mod.rs
Expand Up @@ -12,7 +12,7 @@ use itertools::{Either, Itertools};
use once_cell::sync::Lazy;
use rayon::prelude::*;

use crate::config::{Config, MatchesProfile, Plugin, Shell, Template};
use crate::config::{Config, MatchesProfile, Plugin, Shell};
use crate::context::Context;
pub use crate::lock::file::LockedConfig;
use crate::lock::file::{LockedExternalPlugin, LockedPlugin};
Expand Down Expand Up @@ -86,8 +86,9 @@ pub fn config(ctx: &Context, config: Config) -> Result<LockedConfig> {
.push((index, plugin));
}

let matches = &matches.as_ref().unwrap_or_else(|| shell.default_matches());
#[allow(clippy::redundant_closure)]
let matches = matches
.as_deref()
.unwrap_or_else(|| shell.default_matches());
let apply = apply.as_ref().unwrap_or_else(|| Shell::default_apply());
let count = map.len();
let mut errors = Vec::new();
Expand Down Expand Up @@ -117,9 +118,8 @@ pub fn config(ctx: &Context, config: Config) -> Result<LockedConfig> {
let mut locked = Vec::with_capacity(plugins.len());
for (index, plugin) in plugins {
let name = plugin.name.clone();
let plugin =
plugin::lock(ctx, &templates, source.clone(), matches, apply, plugin)
.with_context(s!("failed to install plugin `{}`", name));
let plugin = plugin::lock(ctx, source.clone(), matches, apply, plugin)
.with_context(s!("failed to install plugin `{}`", name));
locked.push((index, plugin));
}
Ok(locked)
Expand Down Expand Up @@ -171,7 +171,7 @@ pub fn config(ctx: &Context, config: Config) -> Result<LockedConfig> {

impl Shell {
/// The default files to match on for this shell.
fn default_matches(&self) -> &Vec<String> {
fn default_matches(&self) -> &[String] {
static DEFAULT_MATCHES_BASH: Lazy<Vec<String>> = Lazy::new(|| {
vec_into![
"{{ name }}.plugin.bash",
Expand Down Expand Up @@ -203,19 +203,19 @@ impl Shell {
}

/// The default templates for this shell.
pub fn default_templates(&self) -> &IndexMap<String, Template> {
static DEFAULT_TEMPLATES_BASH: Lazy<IndexMap<String, Template>> = Lazy::new(|| {
pub fn default_templates(&self) -> &IndexMap<String, String> {
static DEFAULT_TEMPLATES_BASH: Lazy<IndexMap<String, String>> = Lazy::new(|| {
indexmap_into! {
"PATH" => "export PATH=\"{{ dir }}:$PATH\"",
"source" => Template::from("source \"{{ file }}\"").each(true)
"source" => "{% for file in files %}source \"{{ file }}\"\n{% endfor %}"
}
});
static DEFAULT_TEMPLATES_ZSH: Lazy<IndexMap<String, Template>> = Lazy::new(|| {
static DEFAULT_TEMPLATES_ZSH: Lazy<IndexMap<String, String>> = Lazy::new(|| {
indexmap_into! {
"PATH" => "export PATH=\"{{ dir }}:$PATH\"",
"path" => "path=( \"{{ dir }}\" $path )",
"fpath" => "fpath=( \"{{ dir }}\" $fpath )",
"source" => Template::from("source \"{{ file }}\"").each(true)
"source" => "{% for file in files %}source \"{{ file }}\"\n{% endfor %}"
}
});
match self {
Expand All @@ -231,14 +231,6 @@ impl Shell {
}
}

impl Template {
/// Set whether this template should be applied to every file.
fn each(mut self, each: bool) -> Self {
self.each = each;
self
}
}

impl LockedConfig {
/// Verify that the `LockedConfig` is okay.
pub fn verify(&self, ctx: &Context) -> bool {
Expand Down

0 comments on commit 708bd9e

Please sign in to comment.