aboutsummaryrefslogtreecommitdiff
path: root/mingling_cli/src/proj_mgr
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_cli/src/proj_mgr')
-rw-r--r--mingling_cli/src/proj_mgr/cmd_class_add.rs63
-rw-r--r--mingling_cli/src/proj_mgr/cmd_proj_init.rs82
2 files changed, 84 insertions, 61 deletions
diff --git a/mingling_cli/src/proj_mgr/cmd_class_add.rs b/mingling_cli/src/proj_mgr/cmd_class_add.rs
index 9c729a9..f42bbff 100644
--- a/mingling_cli/src/proj_mgr/cmd_class_add.rs
+++ b/mingling_cli/src/proj_mgr/cmd_class_add.rs
@@ -6,8 +6,8 @@ use std::{
use just_fmt::{camel_case, kebab_case, pascal_case, snake_case};
use just_template::Template;
use mingling::{
- Grouped, RenderResult, Routable, ShellContext, Suggest, SuggestItem,
- macros::{arg, chain, command, completion, metadata, pack, pack_err, renderer, routeify},
+ Grouped, RenderResult, Routable, ShellContext, Suggest, SuggestItem, Wrap,
+ macros::{arg, chain, command, completion, metadata, renderer, routeify},
metadata::Description,
picker::EntryPicker,
res::ResCurrentDir,
@@ -29,7 +29,8 @@ pub struct ClassEntry {
pub description: String,
}
-pack!(StateClassAdd = (String, String));
+#[derive(Grouped, Wrap)]
+pub struct StateClassAdd((String, String));
/// Result of adding a class: the generated file path.
#[derive(Debug, Default, Grouped)]
@@ -37,19 +38,28 @@ pub struct ResultClassAdd {
pub output: PathBuf,
}
-pack_err!(ErrorClassNameRequired = ());
-pack_err!(ErrorClassConfigMissing = String);
-pack_err!(ErrorClassNotFound = String);
-pack_err!(ErrorClassTemplateMissing = String);
-pack_err!(ErrorClassWriteFailed = String);
+#[derive(Grouped)]
+pub struct ErrorClassNameRequired;
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorClassConfigMissing(String);
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorClassNotFound(String);
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorClassTemplateMissing(String);
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorClassWriteFailed(String);
#[command(node = "class-add", routeify)]
pub fn class_add(args: EntryClassAdd) -> Next {
let (class_name, name) = args
- .pick_or_route(&arg![String], || ErrorClassNameRequired::new(()).to_chain())
- .pick_or_route(&arg![String], || ErrorClassNameRequired::new(()).to_chain())
+ .pick_or_route(&arg![String], || ErrorClassNameRequired.to_chain())
+ .pick_or_route(&arg![String], || ErrorClassNameRequired.to_chain())
.to_result()?;
- StateClassAdd::new((class_name, name)).to_chain()
+ StateClassAdd((class_name, name)).to_chain()
}
/// Walk upward from `start` to find the first directory containing `.mling`.
@@ -91,11 +101,11 @@ fn deverbatim(path: &Path) -> PathBuf {
/// name-derived parameters into `<output-dir>/<snake_case>.rs`.
#[chain(routeify)]
pub fn handle_state_class_add(state: StateClassAdd, cwd: &ResCurrentDir) -> Next {
- let (class_name, name) = state.inner;
+ let (class_name, name) = state.0;
// Resolve the project root: the nearest ancestor directory with `.mling`.
let Some(project_root) = find_project_root(cwd) else {
- return ErrorClassConfigMissing::new(format!(
+ return ErrorClassConfigMissing(format!(
"no `.mling` directory found from {} upward; run this inside a mingling project",
cwd.display()
))
@@ -105,14 +115,14 @@ pub fn handle_state_class_add(state: StateClassAdd, cwd: &ResCurrentDir) -> Next
// Read `.mling/classes.toml` (the class registry).
let classes_path = project_root.join(".mling").join("classes.toml");
let content = fs::read_to_string(&classes_path).map_err(|e| {
- ErrorClassConfigMissing::new(format!("failed to read {}: {e}", classes_path.display()))
+ ErrorClassConfigMissing(format!("failed to read {}: {e}", classes_path.display()))
})?;
let classes = parse_classes(&content)
- .map_err(|e| ErrorClassConfigMissing::new(format!("invalid classes.toml: {e}")))?;
+ .map_err(|e| ErrorClassConfigMissing(format!("invalid classes.toml: {e}")))?;
// Find the requested class type.
let Some(entry) = classes.iter().find(|c| c.name == class_name) else {
- return ErrorClassNotFound::new(format!(
+ return ErrorClassNotFound(format!(
"class `{class_name}` not found in {}",
classes_path.display()
))
@@ -122,7 +132,7 @@ pub fn handle_state_class_add(state: StateClassAdd, cwd: &ResCurrentDir) -> Next
// Read the class template (relative to `.mling/`).
let template_path = project_root.join(".mling").join(&entry.template);
let template_content = fs::read_to_string(&template_path).map_err(|e| {
- ErrorClassTemplateMissing::new(format!("failed to read {}: {e}", template_path.display()))
+ ErrorClassTemplateMissing(format!("failed to read {}: {e}", template_path.display()))
})?;
// Derive the name variants used by the template placeholders.
@@ -139,7 +149,7 @@ pub fn handle_state_class_add(state: StateClassAdd, cwd: &ResCurrentDir) -> Next
tmpl.insert_param("upper_snake_case".to_string(), upper_snake);
tmpl.insert_param("camel_case".to_string(), camel);
let expanded = tmpl.expand().ok_or_else(|| {
- ErrorClassWriteFailed::new(format!(
+ ErrorClassWriteFailed(format!(
"failed to expand class template: {}",
template_path.display()
))
@@ -149,11 +159,10 @@ pub fn handle_state_class_add(state: StateClassAdd, cwd: &ResCurrentDir) -> Next
let output_dir = project_root.join(&entry.output_dir);
let output = output_dir.join(format!("{snake}.rs"));
fs::create_dir_all(&output_dir).map_err(|e| {
- ErrorClassWriteFailed::new(format!("failed to create {}: {e}", output_dir.display()))
- })?;
- fs::write(&output, expanded).map_err(|e| {
- ErrorClassWriteFailed::new(format!("failed to write {}: {e}", output.display()))
+ ErrorClassWriteFailed(format!("failed to create {}: {e}", output_dir.display()))
})?;
+ fs::write(&output, expanded)
+ .map_err(|e| ErrorClassWriteFailed(format!("failed to write {}: {e}", output.display())))?;
ResultClassAdd { output }.to_chain()
}
@@ -215,33 +224,33 @@ pub fn render_error_class_name_required(_err: ErrorClassNameRequired) -> RenderR
#[renderer]
pub fn render_error_class_config_missing(err: ErrorClassConfigMissing) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "{}", err.info);
+ eprintln_cargo!(r, "{}", err.0);
r
}
#[renderer]
pub fn render_error_class_not_found(err: ErrorClassNotFound) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "{}", err.info);
+ eprintln_cargo!(r, "{}", err.0);
r
}
#[renderer]
pub fn render_error_class_template_missing(err: ErrorClassTemplateMissing) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "{}", err.info);
+ eprintln_cargo!(r, "{}", err.0);
r
}
#[renderer]
pub fn render_error_class_write_failed(err: ErrorClassWriteFailed) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "{}", err.info);
+ eprintln_cargo!(r, "{}", err.0);
r
}
#[completion(EntryClassAdd)]
-pub fn complete_class_add(ctx: &ShellContext, cwd: &ResCurrentDir) -> Suggest {
+pub fn complete_class_add(ctx: ShellContext, cwd: &ResCurrentDir) -> Suggest {
if ctx.previous_word != "class-add" {
return Suggest::file_comp();
}
diff --git a/mingling_cli/src/proj_mgr/cmd_proj_init.rs b/mingling_cli/src/proj_mgr/cmd_proj_init.rs
index 1ef8d1e..edad83c 100644
--- a/mingling_cli/src/proj_mgr/cmd_proj_init.rs
+++ b/mingling_cli/src/proj_mgr/cmd_proj_init.rs
@@ -7,8 +7,8 @@ use std::{
use just_fmt::snake_case;
use just_template::Template;
use mingling::{
- Grouped, LazyRes, RenderResult, Routable,
- macros::{arg, chain, command, metadata, pack, pack_err, r_println, renderer, routeify},
+ Grouped, LazyRes, RenderResult, Routable, Wrap,
+ macros::{arg, chain, command, metadata, r_println, renderer, routeify},
metadata::Description,
picker::EntryPicker,
res::ResCurrentDir,
@@ -30,8 +30,11 @@ const RULE_FILENAME: &str = "rule.toml";
/// The directory under the project root where the template cache lives.
const CACHE_DIR_NAME: &str = "tmpl-cache";
-pack!(StateProjectGenerate = ());
-pack!(StateProjectChecklistReady = Vec<String>);
+#[derive(Grouped)]
+pub struct StateProjectGenerate;
+
+#[derive(Grouped, Wrap)]
+pub struct StateProjectChecklistReady(Vec<String>);
/// Result of the checklist phase: the extracted checklist handed to the user.
#[derive(Debug, Default, Grouped)]
@@ -46,20 +49,31 @@ pub struct ResultProjectGenerate {
pub hidden: Vec<PathBuf>,
}
-pack_err!(ErrorTemplateNotProvided = ());
-pack_err!(ErrorTemplateCopyFailed = String);
-pack_err!(ErrorTemplateFetchFailed = String);
-pack_err!(ErrorChecklistMissing = String);
-pack_err!(ErrorRuleParseFailed = String);
-pack_err!(ErrorTemplateExpandFailed = String);
+#[derive(Grouped)]
+pub struct ErrorTemplateNotProvided;
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorTemplateCopyFailed(String);
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorTemplateFetchFailed(String);
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorChecklistMissing(String);
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorRuleParseFailed(String);
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorTemplateExpandFailed(String);
#[command(node = "proj-init", routeify)]
pub fn proj_init(args: Entry, cwd: &ResCurrentDir) -> Next {
// Check if the checklist.toml file exists in the current directory
if cwd.join(CHECKLIST_FILENAME).exists() {
- StateProjectGenerate::new(()).into()
+ StateProjectGenerate.into()
} else {
- StateProjectChecklistReady::new(args.inner).into()
+ StateProjectChecklistReady(args.0).into()
}
}
@@ -73,7 +87,7 @@ pub fn handle_state_proj_checklist_ready(
) -> Next {
let source: TemplateSource = args
.pick_or_route(&arg![TemplateSource], || {
- ErrorTemplateNotProvided::new(()).to_chain()
+ ErrorTemplateNotProvided.to_chain()
})
.to_result()?;
@@ -88,7 +102,7 @@ pub fn handle_state_proj_checklist_ready(
configured
});
resolve_git(&source_url, &reference, &variant, &cache_dir())
- .map_err(ErrorTemplateFetchFailed::new)?
+ .map_err(ErrorTemplateFetchFailed)?
}
};
@@ -96,15 +110,15 @@ pub fn handle_state_proj_checklist_ready(
// directory; create it if it doesn't exist
let tmpl_cache = cwd.join(".mling").join(CACHE_DIR_NAME);
fs::create_dir_all(&tmpl_cache).map_err(|e| {
- ErrorTemplateCopyFailed::new(format!("failed to create {}: {e}", tmpl_cache.display()))
+ ErrorTemplateCopyFailed(format!("failed to create {}: {e}", tmpl_cache.display()))
})?;
copy_dir_contents(&template_root, &tmpl_cache)
- .map_err(|e| ErrorTemplateCopyFailed::new(e.to_string()))?;
+ .map_err(|e| ErrorTemplateCopyFailed(e.to_string()))?;
// Move the internal checklist.toml to ./ for the user to fill in
let checklist_src = tmpl_cache.join(CHECKLIST_FILENAME);
if !checklist_src.is_file() {
- return ErrorChecklistMissing::new(format!(
+ return ErrorChecklistMissing(format!(
"no checklist.toml found inside {}",
template_root.display()
))
@@ -112,7 +126,7 @@ pub fn handle_state_proj_checklist_ready(
}
let checklist_dst = cwd.join(CHECKLIST_FILENAME);
fs::rename(&checklist_src, &checklist_dst).map_err(|e| {
- ErrorTemplateCopyFailed::new(format!(
+ ErrorTemplateCopyFailed(format!(
"failed to move checklist.toml to {}: {e}",
checklist_dst.display()
))
@@ -130,7 +144,7 @@ pub fn handle_state_proj_checklist_ready(
pub fn handle_state_project_generate(_: StateProjectGenerate, cwd: &ResCurrentDir) -> Next {
let tmpl_cache = cwd.join(".mling").join(CACHE_DIR_NAME);
if !tmpl_cache.is_dir() {
- return ErrorChecklistMissing::new(format!(
+ return ErrorChecklistMissing(format!(
"template cache not found at {}; run `mling proj-init` with a template directory first",
tmpl_cache.display()
))
@@ -140,22 +154,22 @@ pub fn handle_state_project_generate(_: StateProjectGenerate, cwd: &ResCurrentDi
// Read the user-filled checklist.toml
let checklist_path = cwd.join(CHECKLIST_FILENAME);
let checklist_content = fs::read_to_string(&checklist_path).map_err(|e| {
- ErrorChecklistMissing::new(format!("failed to read {}: {e}", checklist_path.display()))
+ ErrorChecklistMissing(format!("failed to read {}: {e}", checklist_path.display()))
})?;
let answers = parse_checklist(&checklist_content)
- .map_err(|e| ErrorRuleParseFailed::new(format!("invalid checklist.toml: {e}")))?;
+ .map_err(|e| ErrorRuleParseFailed(format!("invalid checklist.toml: {e}")))?;
// Read rule.toml (template rules)
let rule_content = fs::read_to_string(tmpl_cache.join(RULE_FILENAME))
- .map_err(|e| ErrorRuleParseFailed::new(format!("failed to read rule.toml: {e}")))?;
+ .map_err(|e| ErrorRuleParseFailed(format!("failed to read rule.toml: {e}")))?;
let rules = parse_rules(&rule_content)
- .map_err(|e| ErrorRuleParseFailed::new(format!("invalid rule.toml: {e}")))?;
+ .map_err(|e| ErrorRuleParseFailed(format!("invalid rule.toml: {e}")))?;
// Compute final answers from checklist values + defaults declared in rule.toml
let answers = resolve_answers(&answers, &rules);
// Mutually exclusive toggle groups must not both be enabled.
- validate_mutexes(&answers, &rules).map_err(ErrorRuleParseFailed::new)?;
+ validate_mutexes(&answers, &rules).map_err(ErrorRuleParseFailed)?;
// Derive the crate name from the program name (e.g. `my-cli` -> `my_cli`).
let mut params: HashMap<String, String> = answers.clone();
@@ -171,7 +185,7 @@ pub fn handle_state_project_generate(_: StateProjectGenerate, cwd: &ResCurrentDi
// Expand all template entries to the project root
let mut generated = Vec::new();
expand_tree(&tmpl_cache, cwd, &params, &mut generated, true)
- .map_err(ErrorTemplateExpandFailed::new)?;
+ .map_err(ErrorTemplateExpandFailed)?;
// hide-file: delete the corresponding generated file when the rule is true
@@ -182,7 +196,7 @@ pub fn handle_state_project_generate(_: StateProjectGenerate, cwd: &ResCurrentDi
}
let target = cwd.join(hide.file.trim_start_matches("./"));
remove_path(&target).map_err(|e| {
- ErrorTemplateExpandFailed::new(format!("failed to hide {}: {e}", target.display()))
+ ErrorTemplateExpandFailed(format!("failed to hide {}: {e}", target.display()))
})?;
hidden.push(target);
}
@@ -194,19 +208,19 @@ pub fn handle_state_project_generate(_: StateProjectGenerate, cwd: &ResCurrentDi
}
let target = cwd.join(hide.dir.trim_start_matches("./"));
remove_path(&target).map_err(|e| {
- ErrorTemplateExpandFailed::new(format!("failed to hide {}: {e}", target.display()))
+ ErrorTemplateExpandFailed(format!("failed to hide {}: {e}", target.display()))
})?;
hidden.push(target);
}
// Clean up the cache
fs::remove_dir_all(&tmpl_cache).map_err(|e| {
- ErrorTemplateExpandFailed::new(format!("failed to remove {}: {e}", tmpl_cache.display()))
+ ErrorTemplateExpandFailed(format!("failed to remove {}: {e}", tmpl_cache.display()))
})?;
// Project generated; remove the temporary checklist file
remove_path(&checklist_path).map_err(|e| {
- ErrorTemplateExpandFailed::new(format!(
+ ErrorTemplateExpandFailed(format!(
"failed to remove {}: {e}",
checklist_path.display()
))
@@ -327,35 +341,35 @@ pub fn render_error_template_not_provided(_err: ErrorTemplateNotProvided) -> Ren
#[renderer]
pub fn render_error_template_copy_failed(err: ErrorTemplateCopyFailed) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "failed to copy template: {}", err.info);
+ eprintln_cargo!(r, "failed to copy template: {}", err.0);
r
}
#[renderer]
pub fn render_error_template_fetch_failed(err: ErrorTemplateFetchFailed) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "failed to fetch template: {}", err.info);
+ eprintln_cargo!(r, "failed to fetch template: {}", err.0);
r
}
#[renderer]
pub fn render_error_checklist_missing(err: ErrorChecklistMissing) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "{}", err.info);
+ eprintln_cargo!(r, "{}", err.0);
r
}
#[renderer]
pub fn render_error_rule_parse_failed(err: ErrorRuleParseFailed) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "{}", err.info);
+ eprintln_cargo!(r, "{}", err.0);
r
}
#[renderer]
pub fn render_error_template_expand_failed(err: ErrorTemplateExpandFailed) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "{}", err.info);
+ eprintln_cargo!(r, "{}", err.0);
r
}