diff options
Diffstat (limited to 'mingling_cli/src/proj_mgr')
| -rw-r--r-- | mingling_cli/src/proj_mgr/cmd_class_add.rs | 63 | ||||
| -rw-r--r-- | mingling_cli/src/proj_mgr/cmd_proj_init.rs | 82 |
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, ¶ms, &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 } |
