feat: Replace cookiecutter with built-in MiniJinja templating
- Add MiniJinja dependency for Jinja2-compatible templating - Create new project_template.rs module for template rendering - Bundle snowflake template in templates/snowflake/ directory - Remove dependency on external cookiecutter Python tool - Handle Gitea publishing natively in Rust instead of Python hooks - Update default template path to use bundled template with fallback - Update README to reflect new built-in templating - All tests pass Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
+72
-36
@@ -23,6 +23,7 @@ impl App {
|
||||
/// The environment a template's hooks are given. Blank settings are left
|
||||
/// unset rather than exported empty, so a hook can tell "not configured"
|
||||
/// from "configured to nothing" and skip itself.
|
||||
#[allow(dead_code)]
|
||||
fn hook_env(&self) -> Vec<(String, String)> {
|
||||
[
|
||||
("GITEA_URL", self.config.gitea_url.trim()),
|
||||
@@ -61,32 +62,35 @@ impl App {
|
||||
return;
|
||||
}
|
||||
let template = self.config.project_template.clone();
|
||||
let Some(bin) = crate::cookiecutter::resolve_binary(
|
||||
&self.config.cookiecutter_bin,
|
||||
dirs::home_dir().as_deref(),
|
||||
) else {
|
||||
self.np_status = "Could not find the cookiecutter program — set its \
|
||||
path under Settings ▸ New project…"
|
||||
.to_string();
|
||||
|
||||
// Check that the template directory exists
|
||||
if !template.is_dir() {
|
||||
self.np_status = format!("Template directory {} does not exist.", template.display());
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
// Remember the choices that are worth prefilling next time.
|
||||
self.config.project_author = self.np_author.trim().to_string();
|
||||
self.config.projects_dir = parent.clone();
|
||||
self.config.save();
|
||||
|
||||
let request = crate::cookiecutter::Request {
|
||||
bin,
|
||||
template,
|
||||
// Gather Gitea settings for potential hook execution
|
||||
let gitea_url = self.config.gitea_url.clone();
|
||||
let gitea_user = self.config.gitea_user.clone();
|
||||
let gitea_token = self.config.gitea_token.clone();
|
||||
let gitea_private = self.config.gitea_private;
|
||||
let gitea_use_ssh = self.config.gitea_use_ssh;
|
||||
let run_hooks = self.config.project_run_hooks;
|
||||
|
||||
let config = crate::project_template::RenderConfig {
|
||||
template_dir: template,
|
||||
output_dir: parent,
|
||||
vars: vec![
|
||||
crate::cookiecutter::Var::new("project_name", &name),
|
||||
crate::cookiecutter::Var::new("author", self.np_author.trim()),
|
||||
crate::cookiecutter::Var::new("description", self.np_description.trim()),
|
||||
],
|
||||
env: self.hook_env(),
|
||||
run_hooks: self.config.project_run_hooks,
|
||||
variables: crate::project_template::TemplateVariables::new(
|
||||
&name,
|
||||
self.np_author.trim(),
|
||||
self.np_description.trim(),
|
||||
),
|
||||
run_hooks,
|
||||
};
|
||||
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
@@ -94,7 +98,49 @@ impl App {
|
||||
self.np_status = format!("Creating {name}…");
|
||||
let ctx = ctx.clone();
|
||||
std::thread::spawn(move || {
|
||||
let _ = tx.send(crate::cookiecutter::generate(&request));
|
||||
let result = crate::project_template::render_template(&config);
|
||||
let project = match result {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
let _ = tx.send(Err(e));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// If hooks are enabled and Gitea is configured, create the repo and set up git
|
||||
if run_hooks && !gitea_url.is_empty() && !gitea_user.is_empty() && !gitea_token.is_empty() {
|
||||
// Initialize git repository
|
||||
let init_outcome = crate::gitsync::init(&project);
|
||||
if !init_outcome.ok {
|
||||
let _ = tx.send(Err(format!("Failed to initialize git: {}", init_outcome.log)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the repository on Gitea
|
||||
let repo = crate::gitea::NewRepo {
|
||||
name: name.clone(),
|
||||
description: config.variables.description.clone(),
|
||||
private: gitea_private,
|
||||
};
|
||||
|
||||
match crate::gitea::create(&gitea_url, &gitea_user, &gitea_token, &repo) {
|
||||
Ok(gitea_repo) => {
|
||||
// Set the origin remote
|
||||
let remote_url = gitea_repo.remote_url(gitea_use_ssh);
|
||||
let outcome = crate::gitsync::set_origin(&project, remote_url);
|
||||
if !outcome.ok {
|
||||
// Log error but don't fail - the project was created
|
||||
eprintln!("Failed to set origin: {}", outcome.log);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// Log error but don't fail - the project was created
|
||||
eprintln!("Failed to create Gitea repo: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = tx.send(Ok(project));
|
||||
ctx.request_repaint();
|
||||
});
|
||||
}
|
||||
@@ -225,9 +271,8 @@ impl App {
|
||||
if self.config.project_run_hooks {
|
||||
ui.label(
|
||||
egui::RichText::new(
|
||||
"The template's hooks will run. The snowflake hook \
|
||||
publishes to Gitea when Settings ▸ New project… has \
|
||||
credentials, and skips otherwise.",
|
||||
"Project will be published to Gitea when Settings ▸ New project… \
|
||||
has credentials configured.",
|
||||
)
|
||||
.small()
|
||||
.weak(),
|
||||
@@ -467,24 +512,17 @@ impl App {
|
||||
save_now |= r.lost_focus();
|
||||
ui.end_row();
|
||||
|
||||
ui.label("cookiecutter path:");
|
||||
ui.label("Template path:");
|
||||
let r = ui.add(
|
||||
egui::TextEdit::singleline(&mut self.config.cookiecutter_bin)
|
||||
.hint_text("blank = search PATH and conda prefixes")
|
||||
.hint_text("no longer used - built-in MiniJinja")
|
||||
.desired_width(280.0),
|
||||
);
|
||||
save_now |= r.lost_focus();
|
||||
ui.end_row();
|
||||
});
|
||||
let found = crate::cookiecutter::resolve_binary(
|
||||
&self.config.cookiecutter_bin,
|
||||
dirs::home_dir().as_deref(),
|
||||
);
|
||||
ui.label(
|
||||
egui::RichText::new(match &found {
|
||||
Some(path) => format!("✔ {}", path.display()),
|
||||
None => "✖ cookiecutter not found".to_string(),
|
||||
})
|
||||
egui::RichText::new("✔ Built-in MiniJinja templating - no external tool needed")
|
||||
.small()
|
||||
.weak(),
|
||||
);
|
||||
@@ -493,12 +531,10 @@ impl App {
|
||||
if ui
|
||||
.checkbox(
|
||||
&mut self.config.project_run_hooks,
|
||||
"Run the template's hooks",
|
||||
"Publish to Gitea after creation",
|
||||
)
|
||||
.on_hover_text(
|
||||
"Templates can run scripts after generating. The snowflake \
|
||||
template's hook creates a Gitea repository and pushes the \
|
||||
new project to it.",
|
||||
"When enabled and Gitea credentials are configured below, a new repository will be created and the project pushed to it.",
|
||||
)
|
||||
.changed()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user