use crate::app_context::AppContext;
use crate::cli::{LanguageOption, ManifestTemplateOption, NewArgs, OutputContext};
use crate::cli_handlers::common::{prompt_language, require_interactive};
use anyhow::{Result, bail};
use handling_manifest::{
CreateDto, CreateLanguage, ManifestTemplate, handling_manifest_controller,
};
use std::io::{self, Write};
use std::sync::Arc;
pub fn execute(
app_context: &Arc<AppContext>,
args: &NewArgs,
output: &OutputContext,
) -> Result<()> {
let manifest_path = args.path.join("qleany.yaml");
if manifest_path.exists() && !args.force {
bail!(
"Manifest already exists at {}. Use --force to overwrite.",
manifest_path.display()
);
}
let language = match &args.language {
Some(lang) => *lang,
None => prompt_language()?,
};
let application_name = match &args.name {
Some(name) => name.clone(),
None => prompt_string("Application name (PascalCase, e.g. MyApp)", "MyApp")?,
};
let organization_name = match &args.org_name {
Some(name) => name.clone(),
None => prompt_string("Organisation name (e.g. FernTech)", "MyOrganization")?,
};
let template = match &args.template {
Some(t) => *t,
None => prompt_template()?,
};
let options = if args.options.is_empty() {
prompt_options(&language)?
} else {
args.options.clone()
};
output.verbose(&format!(
"Creating new manifest at {}",
manifest_path.display()
));
if let Some(parent) = manifest_path.parent() {
std::fs::create_dir_all(parent)?;
}
let create_dto = CreateDto {
manifest_path: manifest_path.to_string_lossy().to_string(),
language: match language {
LanguageOption::Rust => CreateLanguage::Rust,
LanguageOption::CppQt => CreateLanguage::CppQt,
},
application_name,
organization_name,
manifest_template: match template {
ManifestTemplateOption::Blank => ManifestTemplate::Blank,
ManifestTemplateOption::Minimal => ManifestTemplate::Minimal,
ManifestTemplateOption::DocumentEditor => ManifestTemplate::DocumentEditor,
ManifestTemplateOption::DataManagement => ManifestTemplate::DataManagement,
},
options,
};
let return_dto = handling_manifest_controller::create(&app_context.db_context, &create_dto)?;
output.success(&format!("Created {}", return_dto.manifest_path));
if !output.quiet {
output.info("");
output.info("If not already done, create a git repository and commit the initial manifest, and tag:");
output.info("'git init && git add . && git commit -m\"initial commit\" && git tag v0.0.1'");
output.info("(A git tag is mandatory with a C++/Qt manifest)");
output.info("");
output.info("Next steps:");
output.info(" 1. Edit qleany.yaml to define your entities and features");
output.info(" 2. Run 'qleany check' to validate the manifest");
output.info(" 3. Run 'qleany list' to see the list of files to be generated");
output.info(" 4. Run 'qleany generate --temp' to preview generated files in ./temp/");
output.info(" 5. Run 'qleany generate' to generate files");
output.info(" 6. Run 'qleany diff [file]' to see offered changes to specific files");
output.info(" 7. Run 'qleany prompt' if you are using LLMs");
output.info("");
output.info("Or run 'qleany' to start the UI");
}
Ok(())
}
fn prompt_string(prompt: &str, example: &str) -> Result<String> {
require_interactive(&format!("the appropriate CLI flag (default: {})", example))?;
print!("{} [{}]: ", prompt, example);
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let input = input.trim().to_string();
if input.is_empty() {
Ok(example.to_string())
} else {
Ok(input)
}
}
fn prompt_template() -> Result<ManifestTemplateOption> {
require_interactive("--template")?;
println!("Manifest template:");
println!(" 1. blank - EntityBase + empty Root");
println!(
" 2. minimal - Root with one entity (Item with title). Hello world equivalent"
);
println!(" 3. document-editor - Root > Documents > Sections with load/save use cases");
println!(" 4. data-management - Items, Categories, Tags with import/export use cases");
print!("Choose [1]: ");
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
match input.trim() {
"" | "1" | "blank" => Ok(ManifestTemplateOption::Blank),
"2" | "minimal" => Ok(ManifestTemplateOption::Minimal),
"3" | "document-editor" => Ok(ManifestTemplateOption::DocumentEditor),
"4" | "data-management" => Ok(ManifestTemplateOption::DataManagement),
other => bail!("Invalid template choice: '{}'", other),
}
}
fn prompt_options(language: &LanguageOption) -> Result<Vec<String>> {
require_interactive("--options")?;
let (available, default) = match language {
LanguageOption::Rust => (
vec!["rust_cli", "rust_slint", "rust_ios", "rust_android"],
"rust_cli",
),
LanguageOption::CppQt => (vec!["cpp_qt_qtquick", "cpp_qt_qtwidgets"], "cpp_qt_qtquick"),
};
println!("UI options (comma-separated, or 'none' for no UI):");
for opt in &available {
let marker = if *opt == default { " (default)" } else { "" };
println!(" - {}{}", opt, marker);
}
print!("Choose [{}]: ", default);
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let input = input.trim();
if input.is_empty() {
Ok(vec![default.to_string()])
} else if input.eq_ignore_ascii_case("none") {
Ok(vec![])
} else {
Ok(input
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect())
}
}