Skip to content

Commit

Permalink
Slight refactor, start writing some docs
Browse files Browse the repository at this point in the history
  • Loading branch information
elkowar committed Nov 24, 2024
1 parent 5168c50 commit fb9becf
Show file tree
Hide file tree
Showing 13 changed files with 199 additions and 94 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ foreground="#ebdbb2"
Yolk will now be able to run the corresponding modifications on the file itself, allowing you to set
templated values while keeping the template directly in the same file.

## User Configuration
### User Configuration
Yolk template expressions and configuration are written in the [Rhai](https://rhai.rs/) scripting language.
You can provide custom data to use in your templates via the `yolk.rhai` file in your yolk directory,
which allows you to fetch data dynamically from your system, or reference different static data depending on your system.
Expand Down
1 change: 1 addition & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
book
6 changes: 6 additions & 0 deletions docs/book.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[book]
authors = ["elkowar"]
language = "en"
multilingual = false
src = "src"
title = "Yolk"
4 changes: 4 additions & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Summary

- [Getting started](./getting_started.md)
- [Templates](./templates.md)
1 change: 1 addition & 0 deletions docs/src/getting_started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Getting started
47 changes: 47 additions & 0 deletions docs/src/templates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Templates in Yolk

Yolk allows you to use simple templates directly within your config files.
Those templates will be evaluated whenever you run `yolk sync`, use a new egg, or interact with git through yolk.

Expressions within these templates are written in the [Rhai](https://rhai.rs) scripting language,
and have access to a couple special variables that allow you to reference your configuration and system state.

## Conditional
Let's take a look at a simple example of conditional template syntax:
```toml
# {% if system.hostname == "epic-desktop" %}
displays = ["DP-1", "DP-2"]
# {% else %}
displays = ["eDP-1"]
# {% end %}
```
Once you run `yolk sync`, yolk will evaluate the condition and comment out the block that doesn't apply.
For example, on your laptop, this config would be turned into:
```toml
# {% if system.hostname == "epic-desktop" %}
#<yolk> displays = ["DP-1", "DP-2"]
# {% else %}
displays = ["eDP-1"]
# {% end %}
```

## Value replacement
In many cases, you'll want to make specific values, like colors or paths, be set through one central source, rather than specifying them in every config file.
Yolk allows you to do this (and more) by using the `replace` directive.
This directive takes a regex pattern and a Rhai expression, and replaces the matched pattern with the result of the expression.
```toml
# {% replace /".*"/ `"${colors.background}"`%}
background_color = "#000000"
# {% replace /".*"/ `"${colors.foreground}"`%}
foreground_color = "#ffffff"
```
After running `yolk sync`, yolk will replace the regex patterns with the corresponding result of the Rhai expression.
For example, depending on how you configured your `colors` in your `yolk.rhai`, this could turn into:
```toml
# {% replace /".*"/ `"${colors.background}"`%}
background_color = "#282828"
# {% replace /".*"/ `"${colors.foreground}"`%}
foreground_color = "#ebdbb2"
```
Note that the expression here still needs to contain the quotes, to continue returning valid toml.
Yolk will refuse to evaluate `replace` directives that are non-reversible (i.e. if you replaced `".*"` with `foo`, as `foo` will no longer match that regex pattern).
88 changes: 0 additions & 88 deletions src/eval_ctx.rs

This file was deleted.

5 changes: 3 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ use std::{io::Read as _, str::FromStr};

use anyhow::Result;
use clap::{Parser, Subcommand};
use script::eval_ctx;
use tracing_subscriber::{layer::SubscriberExt as _, util::SubscriberInitExt as _};
use yolk::{EvalMode, Yolk};

mod eval_ctx;
pub mod script;
mod templating;
mod util;
mod yolk;
Expand Down Expand Up @@ -108,7 +109,7 @@ pub(crate) fn main() -> Result<()> {
buffer
}
};
let engine = eval_ctx::make_engine();
let engine = script::make_engine();
let mut eval_ctx = yolk.prepare_eval_ctx(EvalMode::Local, &engine)?;
let result = yolk.eval_template(&mut eval_ctx, &text)?;
println!("{}", result);
Expand Down
32 changes: 32 additions & 0 deletions src/script/eval_ctx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use anyhow::Context;
use anyhow::Result;

use super::make_engine;

// TODO: Ensure an EvalCtx contains info about what file is being parsed,
// the egg name, etc etc
pub struct EvalCtx<'a> {
scope: rhai::Scope<'a>,
}

impl<'a> EvalCtx<'a> {
pub fn new() -> Self {
let scope = rhai::Scope::new();
Self { scope }
}

pub fn eval<T: Clone + 'static + Send + Sync>(&mut self, expr: &str) -> Result<T> {
let engine = make_engine();
engine
.eval_expression_with_scope::<T>(&mut self.scope, expr)
.with_context(|| format!("Failed to evaluate expression: {}", expr))
}

#[allow(unused)]
pub fn scope(&self) -> &rhai::Scope<'a> {
&self.scope
}
pub fn scope_mut(&mut self) -> &mut rhai::Scope<'a> {
&mut self.scope
}
}
15 changes: 15 additions & 0 deletions src/script/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use sysinfo::SystemInfo;

pub mod eval_ctx;
pub mod stdlib;
pub mod sysinfo;

pub fn make_engine() -> rhai::Engine {
let mut engine = rhai::Engine::new();
engine
.register_type::<SystemInfo>()
.build_type::<SystemInfo>();

stdlib::register(&mut engine);
engine
}
51 changes: 51 additions & 0 deletions src/script/stdlib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use anyhow::Result;
use rhai::EvalAltResult;
use std::path::PathBuf;

// TODO: Potentially turn this into a rhai module instead
pub fn register(engine: &mut rhai::Engine) {
engine
.register_fn("command_available", command_available)
.register_fn("env", |name: &str, default: String| {
std::env::var(name).unwrap_or(default)
})
.register_fn("path_exists", |path: &str| PathBuf::from(path).exists())
.register_fn("path_is_dir", |path: &str| {
fs_err::metadata(path).map(|m| m.is_dir()).unwrap_or(false)
})
.register_fn("path_is_file", |path: &str| {
fs_err::metadata(path).map(|m| m.is_file()).unwrap_or(false)
})
.register_fn("read_file", |path: &str| {
fs_err::read_to_string(path).unwrap_or_default()
})
.register_fn(
"regex_match",
|pattern: &str, haystack: &str| -> Result<bool, Box<EvalAltResult>> {
regex::Regex::new(pattern)
.map(|x| x.is_match(haystack))
.map_err(|err| err.to_string().into())
},
)
.register_fn(
"regex_replace",
|haystack: &str,
pattern: &str,
replacement: &str|
-> Result<String, Box<EvalAltResult>> {
regex::Regex::new(pattern)
.map(|x| x.replace_all(haystack, replacement))
.map(|x| x.to_string())
.map_err(|err| err.to_string().into())
},
);
}
pub fn command_available(command: &str) -> bool {
match which::which_all_global(command) {
Ok(mut iter) => iter.next().is_some(),
Err(err) => {
tracing::warn!("Error checking if command is available: {}", err);
false
}
}
}
34 changes: 34 additions & 0 deletions src/script/sysinfo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use rhai::CustomType;
use rhai::TypeBuilder;

#[derive(Debug, Clone, CustomType)]
pub struct SystemInfo {
hostname: String,
username: String,
}

impl SystemInfo {
#[cfg(test)]
pub fn generate() -> Self {
Self {
hostname: "test-hostname".to_string(),
username: "test-username".to_string(),
}
}

#[cfg(not(test))]
pub fn generate() -> Self {
// lmao make this not garbage
Self {
hostname: std::env::var("HOSTNAME").unwrap_or("no-hostname".to_string()),
username: std::env::var("USER").unwrap_or("no-username".to_string()),
}
}

pub fn canonical() -> Self {
Self {
hostname: "canonical-hostname".to_string(),
username: "canonical-username".to_string(),
}
}
}
7 changes: 4 additions & 3 deletions src/yolk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ use fs_err::PathExt;
use rhai::Dynamic;

use crate::{
eval_ctx::{self, EvalCtx, SystemInfo},
eval_ctx::EvalCtx,
script::{self, sysinfo::SystemInfo},
templating::document::Document,
util,
yolk_paths::YolkPaths,
Expand Down Expand Up @@ -107,7 +108,7 @@ impl Yolk {

pub fn sync_to_mode(&self, mode: EvalMode) -> Result<()> {
let egg_paths = self.list_egg_paths()?;
let engine = eval_ctx::make_engine();
let engine = script::make_engine();
let mut eval_ctx = self
.prepare_eval_ctx(mode, &engine)
.context("Failed to prepare eval_ctx")?;
Expand Down Expand Up @@ -163,7 +164,7 @@ impl Yolk {
}

pub fn eval_rhai(&self, mode: EvalMode, expr: &str) -> Result<String> {
let engine = eval_ctx::make_engine();
let engine = script::make_engine();
let mut eval_ctx = self
.prepare_eval_ctx(mode, &engine)
.context("Failed to prepare eval_ctx")?;
Expand Down

0 comments on commit fb9becf

Please sign in to comment.