Skip to content

Commit

Permalink
feat: codegen settings
Browse files Browse the repository at this point in the history
Fixes #2633
  • Loading branch information
jdx committed Sep 25, 2024
1 parent a8f7493 commit 0d889ae
Show file tree
Hide file tree
Showing 21 changed files with 1,341 additions and 559 deletions.
1 change: 1 addition & 0 deletions .markdownlintignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ registry/
target/
CHANGELOG.md
docs/node_modules/
node_modules/
1 change: 1 addition & 0 deletions .mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ jq = "latest"
"npm:prettier" = "3"
direnv = "latest"
actionlint = "latest"
"pipx:toml-sort" = "latest"
#python = { version = "latest", virtualenv = "{{env.HOME}}/.cache/venv" }
#ruby = "3.1"

Expand Down
1 change: 1 addition & 0 deletions .mise/tasks/lint-fix
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ shfmt -w -i 2 -ci -bn "${scripts_dirs[@]}"
prettier -w $(git ls-files '*.yml' '*.yaml')
markdownlint --fix .
actionlint
toml-sort -i settings.toml

cat >rustfmt.toml <<EOF
unstable_features = true
Expand Down
4 changes: 4 additions & 0 deletions .mise/tasks/lint/settings
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail

toml-sort --check settings.toml
72 changes: 72 additions & 0 deletions .mise/tasks/render/settings
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env node

const fs = require('fs');
const toml = require('toml');
const child_process = require('child_process');
const Handlebars = require('handlebars');

const doc = toml.parse(fs.readFileSync('settings.toml', 'utf-8'));
const settings = {};

function buildElement(key, props) {
let type = props.type;
if (type.startsWith('Option<')) {
type = type.slice(7, -1);
}
type = type.replaceAll('PathBuf', 'String');
if (type === 'bool') {
type = 'boolean';
} else if (type === "String" || type === "PathBuf") {
type = 'string';
} else if (type === "usize" || type === "u64") {
type = 'number';
} else if (type === "BTreeSet<String>" || type === "HashSet<String>" || type === "Vec<String>") {
type = 'string[]';
} else {
throw new Error(`Unknown type: ${type}`);
}
if (!props.description) {
console.error(`Missing description for ${key}`);
process.exit(1);
}
const ele = {
default: props.default,
description: props.description,
deprecated: props.deprecated,
type,
};
if (props.enum) {
ele.enum = props.enum.map((e) => e[0]);
}
if (type === 'string[]') {
ele.type = 'array';
ele.items = {
type: 'string',
};
}
return ele;
}

for (const key in doc) {
const props = doc[key];
if (props.type) {
settings[key] = buildElement(key, props);
} else {
for (const subkey in props) {
settings[key] = settings[key] || {
additionalProperties: false,
description: props.description,
properties: {},
};
settings[key].properties[subkey] = buildElement(`${key}.${subkey}`, props[subkey]);
}
}
}

const schema_tmpl = Handlebars.compile(fs.readFileSync('schema/mise.json.hbs', 'utf-8'));
fs.writeFileSync('schema/mise.json.tmp', schema_tmpl({
settings_json: new Handlebars.SafeString(JSON.stringify(settings, null, 2)),
}));

child_process.execSync('jq . < schema/mise.json.tmp > schema/mise.json');
fs.unlinkSync('schema/mise.json.tmp');
18 changes: 11 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@ license = "MIT"
keywords = ["mise"]
categories = ["command-line-utilities"]
include = [
"src/**/*.rs",
"src/plugins/core/assets/**",
"src/assets/**",
"/completions/*",
"/Cargo.lock",
"/LICENSE",
"/README.md",
"/build.rs",
"/completions/*",
"/settings.toml",
"/zipsign.pub",
"src/**/*.rs",
"src/assets/**",
"src/plugins/core/assets/**",
]
rust-version = "1.76.0"
build = "build.rs"
Expand Down Expand Up @@ -69,7 +70,7 @@ heck = "0.5"
home = "0.5.9"
humantime = "2"
indenter = "0.3.3"
indexmap = { version = "2.2.6", features = ["serde"] }
indexmap = { version = "2", features = ["serde"] }
indicatif = { version = "0.17.8", features = ["default", "improved_unicode"] }
indoc = "2.0.5"
itertools = "0.13"
Expand Down Expand Up @@ -111,8 +112,8 @@ tokio = { version = "1.37.0", features = [
"rt",
"time",
] }
toml = { version = "0.8.12", features = ["parse"] }
toml_edit = { version = "0.22.12", features = ["parse"] }
toml = { version = "0.8", features = ["parse"] }
toml_edit = { version = "0.22", features = ["parse"] }
url = "2.5.0"
usage-lib = { version = "0.3", features = ["clap"] }
versions = { version = "6.2.0", features = ["serde"] }
Expand Down Expand Up @@ -141,6 +142,9 @@ sevenz-rust = "0.6"
[build-dependencies]
built = { version = "0.7", features = ["chrono", "git2"] }
cfg_aliases = "0.2"
heck = "0.5"
toml = "0.8"
indexmap = "2"

[dev-dependencies]
assert_cmd = "2.0.14"
Expand Down
89 changes: 89 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,96 @@
use heck::ToUpperCamelCase;
use indexmap::IndexMap;
use std::path::Path;
use std::{env, fs};

fn main() {
cfg_aliases::cfg_aliases! {
vfox: { any(feature = "vfox", target_os = "windows") },
asdf: { any(feature = "asdf", not(target_os = "windows")) },
}
built::write_built_file().expect("Failed to acquire build-time information");

codegen_settings();
}

fn codegen_settings() {
let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("settings.rs");
let mut lines = vec![r#"
#[derive(Config, Default, Debug, Clone, Serialize)]
#[config(partial_attr(derive(Clone, Serialize, Default)))]
pub struct Settings {"#
.to_string()];

let settings: toml::Table = fs::read_to_string("settings.toml")
.unwrap()
.parse()
.unwrap();
let props_to_code = |key: &str, props: &toml::Value| {
let mut lines = vec![];
let props = props.as_table().unwrap();
if let Some(description) = props.get("description") {
lines.push(format!(" /// {}", description.as_str().unwrap()));
}
if let Some(type_) = props.get("type") {
let mut opts = IndexMap::new();
if let Some(env) = props.get("env") {
opts.insert("env".to_string(), env.to_string());
}
if let Some(default) = props.get("default") {
opts.insert("default".to_string(), default.to_string());
} else if type_.as_str().unwrap() == "bool" {
opts.insert("default".to_string(), "false".to_string());
}
if let Some(parse_env) = props.get("parse_env") {
opts.insert(
"parse_env".to_string(),
parse_env.as_str().unwrap().to_string(),
);
}
dbg!(&opts);
lines.push(format!(
" #[config({})]",
opts.iter()
.map(|(k, v)| format!("{k} = {v}"))
.collect::<Vec<_>>()
.join(", ")
));
lines.push(format!(" pub {}: {},", key, type_.as_str().unwrap()));
} else {
lines.push(" #[config(nested)]".to_string());
lines.push(format!(
" pub {}: Settings{},",
key,
key.to_upper_camel_case()
));
}
lines.join("\n")
};
for (key, props) in &settings {
lines.push(props_to_code(key, props));
}
lines.push("}".to_string());

let nested_settings = settings
.iter()
.filter(|(_, v)| !v.as_table().unwrap().contains_key("type"))
.collect::<Vec<_>>();
for (child, props) in nested_settings {
lines.push(format!(
r#"#[derive(Config, Default, Debug, Clone, Serialize)]
#[config(partial_attr(derive(Clone, Serialize, Default)))]
#[config(partial_attr(serde(deny_unknown_fields)))]
pub struct Settings{name} {{
"#,
name = child.to_upper_camel_case()
));

for (key, props) in props.as_table().unwrap() {
lines.push(props_to_code(key, props));
}
lines.push("}".to_string());
}

fs::write(&dest_path, lines.join("\n")).unwrap();
}
1 change: 1 addition & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export default defineConfig({
{text: 'IDE Integration', link: '/ide-integration'},
{text: 'Paranoid', link: '/paranoid'},
{text: 'Registry', link: '/registry'},
{text: 'Settings', link: '/settings'},
{text: 'Plugins', link: '/plugins'},
{text: 'Coming from rtx', link: '/rtx'},
{text: 'Team', link: '/team'},
Expand Down
Loading

0 comments on commit 0d889ae

Please sign in to comment.