@@ -38,6 +38,7 @@ import mcpp.fetcher;
3838import mcpp.pm.resolver; // PR-R4: extracted from cli.cppm
3939import mcpp.pm.commands; // PR-R5: cmd_add / cmd_remove / cmd_update live here now
4040import mcpp.pm.index_spec; // IndexSpec for [indices] support
41+ import mcpp.scaffold; // package-based project templates
4142import mcpp.pm.mangle; // Level 1 multi-version fallback (cross-major coexistence)
4243import mcpp.pm.compat; // 0.0.6: namespace field + dotted-name compat shims
4344import mcpp.pm.dep_spec;
@@ -1093,20 +1094,191 @@ void gcc_post_install_fixup(const mcpp::config::GlobalConfig& cfg,
10931094
10941095// --- Commands ---
10951096
1097+ // ─── Package-based templates (design v2: multi-level --template) ──────
1098+ //
1099+ // Resolve SPEC's package@version through the index, ensure the package
1100+ // sources are installed (same cache as dependencies), and return the
1101+ // package root (the directory containing mcpp.toml).
1102+ struct FetchedTemplatePackage {
1103+ std::filesystem::path root;
1104+ std::string name; // short package name (e.g. "imgui")
1105+ std::string version; // resolved exact version
1106+ };
1107+
1108+ std::expected<FetchedTemplatePackage, std::string>
1109+ fetch_template_package (const mcpp::scaffold::TemplateSpec& spec) {
1110+ auto cfg = mcpp::config::load_or_init (/* quiet=*/ false ,
1111+ make_bootstrap_progress_callback ());
1112+ if (!cfg) return std::unexpected (cfg.error ().message );
1113+ mcpp::pm::Fetcher fetcher (*cfg);
1114+
1115+ // Namespace candidates mirror dependency lookup: index root first,
1116+ // then the compat namespace.
1117+ std::string ns;
1118+ std::optional<std::string> lua;
1119+ for (std::string cand : {std::string{}, std::string{" compat" }}) {
1120+ if (auto l = fetcher.read_xpkg_lua (cand, spec.pkg )) {
1121+ ns = cand;
1122+ lua = std::move (*l);
1123+ break ;
1124+ }
1125+ }
1126+ if (!lua) {
1127+ return std::unexpected (std::format (
1128+ " template package '{}' not found in the index "
1129+ " (check the name, or run `mcpp index update`)" , spec.pkg ));
1130+ }
1131+
1132+ std::string version = spec.version ;
1133+ if (version.empty ()) {
1134+ auto v = mcpp::pm::resolve_semver (ns, spec.pkg , " *" , fetcher);
1135+ if (!v) return std::unexpected (v.error ());
1136+ version = *v;
1137+ }
1138+
1139+ auto installed = fetcher.install_path (ns, spec.pkg , version);
1140+ if (!installed) {
1141+ auto fq = ns.empty () ? spec.pkg : std::format (" {}.{}" , ns, spec.pkg );
1142+ mcpp::ui::info (" Downloading" , std::format (" {} v{}" , fq, version));
1143+ CliInstallProgress progress;
1144+ std::vector<std::string> targets{ std::format (" {}@{}" , fq, version) };
1145+ auto r = fetcher.install (targets, &progress);
1146+ if (!r) return std::unexpected (std::format (
1147+ " fetch '{}@{}': {}" , fq, version, r.error ().message ));
1148+ if (r->exitCode != 0 ) return std::unexpected (std::format (
1149+ " fetch '{}@{}' failed (exit {})" , fq, version, r->exitCode ));
1150+ installed = fetcher.install_path (ns, spec.pkg , version);
1151+ if (!installed) return std::unexpected (std::format (
1152+ " package '{}@{}' install path missing after fetch" , fq, version));
1153+ }
1154+
1155+ // Package root = the directory holding mcpp.toml (tarballs usually wrap
1156+ // everything in a single top-level directory).
1157+ std::filesystem::path root = *installed;
1158+ if (!std::filesystem::exists (root / " mcpp.toml" )) {
1159+ std::error_code ec;
1160+ for (auto & e : std::filesystem::directory_iterator (root, ec)) {
1161+ if (e.is_directory ()
1162+ && std::filesystem::exists (e.path () / " mcpp.toml" )) {
1163+ root = e.path ();
1164+ break ;
1165+ }
1166+ }
1167+ }
1168+ if (!std::filesystem::exists (root / " mcpp.toml" )) {
1169+ return std::unexpected (std::format (
1170+ " package '{}@{}' has no mcpp.toml" , spec.pkg , version));
1171+ }
1172+ return FetchedTemplatePackage{root, spec.pkg , version};
1173+ }
1174+
1175+ void print_template_listing (const FetchedTemplatePackage& pkg,
1176+ const std::vector<mcpp::scaffold::TemplateEntry>& entries) {
1177+ std::println (" Templates in {}@{}:" , pkg.name , pkg.version );
1178+ for (auto & t : entries) {
1179+ std::println (" {:<14}{}{}" , t.name ,
1180+ t.meta .isDefault ? " (default) " : " " ,
1181+ t.meta .description );
1182+ }
1183+ std::println (" " );
1184+ std::println (" usage: mcpp new <name> --template {}[@ver][:<template>]" , pkg.name );
1185+ }
1186+
1187+ int list_package_templates (const mcpp::scaffold::TemplateSpec& spec) {
1188+ auto pkg = fetch_template_package (spec);
1189+ if (!pkg) { mcpp::ui::error (pkg.error ()); return 1 ; }
1190+ auto entries = mcpp::scaffold::list_templates (pkg->root );
1191+ if (!entries) { mcpp::ui::error (entries.error ()); return 1 ; }
1192+ print_template_listing (*pkg, *entries);
1193+ return 0 ;
1194+ }
1195+
1196+ int new_from_package_template (const std::string& name, const std::string& specStr) {
1197+ auto spec = mcpp::scaffold::parse_spec (specStr);
1198+ if (spec.listOnly ) return list_package_templates (spec);
1199+
1200+ auto pkg = fetch_template_package (spec);
1201+ if (!pkg) { mcpp::ui::error (pkg.error ()); return 1 ; }
1202+ auto entries = mcpp::scaffold::list_templates (pkg->root );
1203+ if (!entries) { mcpp::ui::error (entries.error ()); return 1 ; }
1204+
1205+ const mcpp::scaffold::TemplateEntry* chosen = nullptr ;
1206+ if (spec.tmpl .empty ()) {
1207+ for (auto & t : *entries) if (t.meta .isDefault ) { chosen = &t; break ; }
1208+ if (!chosen) {
1209+ mcpp::ui::error (std::format (
1210+ " package '{}' declares no default template — pick one explicitly:" ,
1211+ pkg->name ));
1212+ print_template_listing (*pkg, *entries);
1213+ return 1 ;
1214+ }
1215+ } else {
1216+ for (auto & t : *entries) if (t.name == spec.tmpl ) { chosen = &t; break ; }
1217+ if (!chosen) {
1218+ mcpp::ui::error (std::format (
1219+ " package '{}@{}' has no template '{}'" ,
1220+ pkg->name , pkg->version , spec.tmpl ));
1221+ print_template_listing (*pkg, *entries);
1222+ return 1 ;
1223+ }
1224+ }
1225+
1226+ std::filesystem::path root = std::filesystem::current_path () / name;
1227+ if (std::filesystem::exists (root)) {
1228+ mcpp::ui::error (std::format (" '{}' already exists" , root.string ()));
1229+ return 1 ;
1230+ }
1231+ std::error_code ec;
1232+ std::filesystem::create_directories (root, ec);
1233+ if (ec) {
1234+ mcpp::ui::error (std::format (" cannot create '{}': {}" ,
1235+ root.string (), ec.message ()));
1236+ return 1 ;
1237+ }
1238+
1239+ mcpp::scaffold::RenderVars vars{name, pkg->name , pkg->version };
1240+ if (auto err = mcpp::scaffold::instantiate (
1241+ pkg->root / " templates" / chosen->name , root, vars)) {
1242+ mcpp::ui::error (*err);
1243+ return 1 ;
1244+ }
1245+ if (auto err = mcpp::scaffold::inject_self_dependency (
1246+ root / " mcpp.toml" , vars, chosen->meta .injectSelfFeatures )) {
1247+ mcpp::ui::error (*err);
1248+ return 1 ;
1249+ }
1250+
1251+ mcpp::ui::status (" Created" , std::format (
1252+ " {} (template {}@{}:{})" , name, pkg->name , pkg->version , chosen->name ));
1253+ if (!chosen->meta .postMessage .empty ())
1254+ std::println (" {}" , chosen->meta .postMessage );
1255+ return 0 ;
1256+ }
1257+
10961258int cmd_new (const mcpplibs::cmdline::ParsedArgs& parsed) {
1259+ // Discovery mode: `mcpp new --list-templates <pkg>[@ver]` — no project.
1260+ if (auto lt = parsed.value (" list-templates" )) {
1261+ return list_package_templates (mcpp::scaffold::parse_spec (*lt));
1262+ }
1263+
10971264 std::string name = parsed.positional (0 );
10981265 if (name.empty ()) {
10991266 std::println (stderr, " error: `mcpp new` requires a package name (e.g. `mcpp new hello`)" );
11001267 return 2 ;
11011268 }
11021269
1103- // `--template` selects the project skeleton: "bin" (default) or "gui"
1104- // (an imgui.app starter — Tier-0 zero-boilerplate window).
1270+ // `--template` multi-level SPEC (design v2):
1271+ // builtin registry (frozen: bin; gui = transitional alias), else a
1272+ // package template: pkg | pkg:tmpl | pkg@ver | pkg@ver:tmpl.
11051273 std::string tmpl = " bin" ;
11061274 if (auto t = parsed.value (" template" )) tmpl = *t;
1275+ if (tmpl == " gui" ) {
1276+ mcpp::ui::warning (
1277+ " --template gui is deprecated; use `--template imgui` "
1278+ " (the template then ships with — and version-tracks — the library)" );
1279+ }
11071280 if (tmpl != " bin" && tmpl != " gui" ) {
1108- std::println (stderr, " error: unknown --template '{}' (expected: bin | gui)" , tmpl);
1109- return 2 ;
1281+ return new_from_package_template (name, tmpl);
11101282 }
11111283 const bool gui = (tmpl == " gui" );
11121284
@@ -1130,7 +1302,7 @@ int cmd_new(const mcpplibs::cmdline::ParsedArgs& parsed) {
11301302 // The GUI template depends on the imgui module package. It does not
11311303 // pin a toolchain — mcpp resolves the environment/default toolchain
11321304 // and the GL runtime is closed by the ecosystem (compat.glx-runtime).
1133- os << " \n [dependencies]\n imgui = \" 0.0.2 \"\n " ;
1305+ os << " \n [dependencies]\n imgui = \" 0.0.5 \"\n " ;
11341306 }
11351307 }
11361308 // src/main.cpp — template with PROJECT placeholder, replaced with `name`.
@@ -5763,9 +5935,13 @@ int run(int argc, char** argv) {
57635935 // ─── project commands ──────────────────────────────────────────
57645936 .subcommand (cl::App (" new" )
57655937 .description (" Create a new mcpp package skeleton" )
5766- .arg (cl::Arg (" name" ).help (" Package directory name" ).required ())
5767- .option (cl::Option (" template" ).short_name (' t' ).takes_value ().value_name (" KIND" )
5768- .help (" Project template: bin (default) | gui (imgui.app window starter)" ))
5938+ // not .required(): `--list-templates` runs without a name
5939+ // (cmd_new validates presence for project creation itself).
5940+ .arg (cl::Arg (" name" ).help (" Package directory name" ))
5941+ .option (cl::Option (" template" ).short_name (' t' ).takes_value ().value_name (" SPEC" )
5942+ .help (" bin (default) | <pkg>[@ver][:<template>] — package-shipped template" ))
5943+ .option (cl::Option (" list-templates" ).takes_value ().value_name (" PKG" )
5944+ .help (" List the templates a package ships (PKG[@ver])" ))
57695945 .action (wrap_rc (cmd_new)))
57705946 .subcommand (cl::App (" build" )
57715947 .description (" Build the current package" )
0 commit comments