@@ -31,6 +31,7 @@ import mcpp.pack;
3131import mcpp.config;
3232import mcpp.fetcher;
3333import mcpp.pm.resolver; // PR-R4: extracted from cli.cppm
34+ import mcpp.pm.commands; // PR-R5: cmd_add / cmd_remove / cmd_update live here now
3435import mcpp.ui;
3536import mcpp.bmi_cache;
3637import mcpp.dyndep;
@@ -1706,84 +1707,7 @@ int cmd_index_update(const mcpplibs::cmdline::ParsedArgs& /*parsed*/) {
17061707 return 0 ;
17071708}
17081709
1709- int cmd_add (const mcpplibs::cmdline::ParsedArgs& parsed) {
1710- std::string spec = parsed.positional (0 );
1711- if (spec.empty ()) {
1712- mcpp::ui::error (" usage: mcpp add [<ns>:]<pkg>[@<ver>]" );
1713- return 2 ;
1714- }
1715-
1716- // Split @<version> tail.
1717- std::string nameSpec, version;
1718- if (auto at = spec.find (' @' ); at == std::string::npos) {
1719- nameSpec = spec;
1720- } else {
1721- nameSpec = spec.substr (0 , at);
1722- version = spec.substr (at + 1 );
1723- }
1724-
1725- // Split <ns>:<name>. xpkg-style namespace separator. Bare `name` keeps
1726- // the default namespace (mcpp); legacy `ns.name` is also accepted on
1727- // input for ergonomics, but written out in the new subtable form.
1728- std::string ns, shortName;
1729- if (auto col = nameSpec.find (' :' ); col != std::string::npos) {
1730- ns = nameSpec.substr (0 , col);
1731- shortName = nameSpec.substr (col + 1 );
1732- } else if (auto dot = nameSpec.find (' .' ); dot != std::string::npos) {
1733- ns = nameSpec.substr (0 , dot);
1734- shortName = nameSpec.substr (dot + 1 );
1735- } else {
1736- ns = std::string{mcpp::manifest::kDefaultNamespace };
1737- shortName = nameSpec;
1738- }
1739- if (shortName.empty ()) {
1740- mcpp::ui::error (std::format (" invalid spec '{}': empty package name" , spec));
1741- return 2 ;
1742- }
1743-
1744- auto root = find_manifest_root (std::filesystem::current_path ());
1745- if (!root) { mcpp::ui::error (" no mcpp.toml in current dir or parents" ); return 2 ; }
1746- auto manifestPath = *root / " mcpp.toml" ;
1747-
1748- if (version.empty ()) {
1749- mcpp::ui::error (std::format (
1750- " package version required: `mcpp add {}@<version>` (M2 supports exact-version only)" ,
1751- spec));
1752- return 2 ;
1753- }
1754-
1755- std::ifstream in (manifestPath);
1756- std::stringstream ss; ss << in.rdbuf ();
1757- std::string text = ss.str ();
1758-
1759- // Insertion strategy:
1760- // - Default namespace → `[dependencies] ... name = "version"` (no quotes).
1761- // - Other namespace → `[dependencies.<ns>] ... name = "version"`,
1762- // creating the subtable if absent.
1763- const bool isDefaultNs = (ns == mcpp::manifest::kDefaultNamespace );
1764- const std::string section = isDefaultNs
1765- ? " [dependencies]"
1766- : std::format (" [dependencies.{}]" , ns);
1767- auto pos = text.find (section);
1768- if (pos == std::string::npos) {
1769- if (!text.empty () && text.back () != ' \n ' ) text += " \n " ;
1770- text += std::format (" \n {}\n {} = \" {}\"\n " , section, shortName, version);
1771- } else {
1772- auto nl = text.find (' \n ' , pos);
1773- if (nl == std::string::npos) nl = text.size ();
1774- text.insert (nl, std::format (" \n {} = \" {}\" " , shortName, version));
1775- }
1776- {
1777- std::ofstream os (manifestPath);
1778- os << text;
1779- }
1780-
1781- std::string display = isDefaultNs ? shortName : std::format (" {}:{}" , ns, shortName);
1782- mcpp::ui::status (" Adding" , std::format (" {} v{} to dependencies" , display, version));
1783- std::println (" " );
1784- std::println (" Run `mcpp build` to fetch and build with the new dependency." );
1785- return 0 ;
1786- }
1710+ // `cmd_add` has moved to `mcpp.pm.commands` (PR-R5).
17871711
17881712int cmd_test (const mcpplibs::cmdline::ParsedArgs& /* parsed*/ ,
17891713 std::span<const std::string> passthrough) {
@@ -2219,174 +2143,7 @@ int cmd_cache_clean(const mcpplibs::cmdline::ParsedArgs& /*parsed*/) {
22192143}
22202144
22212145// ─── M4 #3: mcpp remove / mcpp update ───────────────────────────────────
2222- int cmd_remove (const mcpplibs::cmdline::ParsedArgs& parsed) {
2223- std::string name = parsed.positional (0 );
2224- if (name.empty ()) {
2225- mcpp::ui::error (" usage: mcpp remove <pkg>" );
2226- return 2 ;
2227- }
2228-
2229- auto root = find_manifest_root (std::filesystem::current_path ());
2230- if (!root) { mcpp::ui::error (" no mcpp.toml in current dir or parents" ); return 2 ; }
2231- auto manifestPath = *root / " mcpp.toml" ;
2232-
2233- std::ifstream in (manifestPath);
2234- std::stringstream ss; ss << in.rdbuf ();
2235- std::string text = ss.str ();
2236-
2237- // Accept the same forms as `mcpp add`: bare `name` (default ns),
2238- // `<ns>:<name>`, or legacy `<ns>.<name>`. The line we want to delete
2239- // depends on which form the user wrote in mcpp.toml — try every one.
2240- std::string ns, shortName;
2241- if (auto col = name.find (' :' ); col != std::string::npos) {
2242- ns = name.substr (0 , col); shortName = name.substr (col + 1 );
2243- } else if (auto dot = name.find (' .' ); dot != std::string::npos) {
2244- ns = name.substr (0 , dot); shortName = name.substr (dot + 1 );
2245- } else {
2246- ns = std::string{mcpp::manifest::kDefaultNamespace };
2247- shortName = name;
2248- }
2249- const bool isDefaultNs = (ns == mcpp::manifest::kDefaultNamespace );
2250-
2251- bool changed = false ;
2252- auto erase_line_at = [&](std::size_t p) {
2253- auto bol = text.rfind (' \n ' , p);
2254- auto eol = text.find (' \n ' , p);
2255- if (bol == std::string::npos) bol = 0 ; else ++bol;
2256- if (eol == std::string::npos) eol = text.size ();
2257- text.erase (bol, (eol - bol) + (eol < text.size () ? 1 : 0 ));
2258- changed = true ;
2259- };
2260-
2261- // Try bare `<short> = ` and quoted `"<short>" = ` (default-ns flat form).
2262- if (isDefaultNs) {
2263- for (const auto & needle : {
2264- std::format (" \n {} = " , shortName),
2265- std::format (" \n\" {}\" = " , shortName),
2266- }) {
2267- if (auto p = text.find (needle); p != std::string::npos) {
2268- erase_line_at (p + 1 );
2269- break ;
2270- }
2271- }
2272- }
2273-
2274- // Try the namespaced subtable form `[dependencies.<ns>] <short> = `.
2275- // After deleting the dep line, prune the `[dependencies.<ns>]` header
2276- // if no entries remain under it.
2277- if (!isDefaultNs) {
2278- auto sectHeader = std::format (" [dependencies.{}]" , ns);
2279- if (auto sp = text.find (sectHeader); sp != std::string::npos) {
2280- auto bodyStart = text.find (' \n ' , sp);
2281- if (bodyStart == std::string::npos) bodyStart = text.size ();
2282- auto sectEnd = text.find (" \n [" , bodyStart);
2283- if (sectEnd == std::string::npos) sectEnd = text.size ();
2284- std::string section = text.substr (bodyStart, sectEnd - bodyStart);
2285- for (const auto & needle : {
2286- std::format (" \n {} = " , shortName),
2287- std::format (" \n\" {}\" = " , shortName),
2288- }) {
2289- if (auto p = section.find (needle); p != std::string::npos) {
2290- auto absStart = bodyStart + p + 1 ;
2291- erase_line_at (absStart);
2292- break ;
2293- }
2294- }
2295- // If the subtable now contains no `name = ...` lines, drop it.
2296- auto headerPos = text.find (sectHeader);
2297- if (changed && headerPos != std::string::npos) {
2298- auto bodyAfter = text.find (' \n ' , headerPos);
2299- auto endAfter = text.find (" \n [" , bodyAfter == std::string::npos ? headerPos : bodyAfter);
2300- if (endAfter == std::string::npos) endAfter = text.size ();
2301- std::string body = text.substr (bodyAfter == std::string::npos ? headerPos : bodyAfter,
2302- endAfter - (bodyAfter == std::string::npos ? headerPos : bodyAfter));
2303- bool hasEntry = false ;
2304- std::size_t i = 0 ;
2305- while (i < body.size ()) {
2306- auto j = body.find (' \n ' , i);
2307- auto line = body.substr (i, (j == std::string::npos ? body.size () : j) - i);
2308- auto first = line.find_first_not_of (" \t " );
2309- if (first != std::string::npos
2310- && line[first] != ' #' && line[first] != ' \n '
2311- && line[first] != ' [' ) {
2312- hasEntry = true ; break ;
2313- }
2314- if (j == std::string::npos) break ;
2315- i = j + 1 ;
2316- }
2317- if (!hasEntry) {
2318- auto headerLineStart = text.rfind (' \n ' , headerPos);
2319- if (headerLineStart == std::string::npos) headerLineStart = 0 ;
2320- text.erase (headerLineStart, endAfter - headerLineStart);
2321- }
2322- }
2323- }
2324- }
2325-
2326- // Legacy: `[dependencies.<name>] ...` — pre-namespace inline-spec subtable
2327- // shape (e.g. when path/git deps were authored as their own subtable). We
2328- // only honour this for the default-ns input form to avoid colliding with
2329- // the new `[dependencies.<ns>]` namespacing semantics.
2330- if (!changed && isDefaultNs) {
2331- auto block = std::format (" [dependencies.{}]" , shortName);
2332- if (auto p = text.find (block); p != std::string::npos) {
2333- auto bol = text.rfind (' \n ' , p);
2334- if (bol == std::string::npos) bol = 0 ; else ++bol;
2335- auto end = text.find (" \n [" , p + block.size ());
2336- if (end == std::string::npos) end = text.size ();
2337- else end += 1 ;
2338- text.erase (bol, end - bol);
2339- changed = true ;
2340- }
2341- }
2342-
2343- if (!changed) {
2344- mcpp::ui::error (std::format (" no dependency '{}' in mcpp.toml" , name));
2345- return 1 ;
2346- }
2347- std::ofstream os (manifestPath);
2348- os << text;
2349- mcpp::ui::status (" Removing" , std::format (" {} from dependencies" , name));
2350- // Also clean lockfile entry if present
2351- auto lockPath = *root / " mcpp.lock" ;
2352- if (std::filesystem::exists (lockPath)) {
2353- if (auto lock = mcpp::lockfile::load (lockPath); lock) {
2354- std::erase_if (lock->packages ,
2355- [&](const auto & p) { return p.name == name; });
2356- (void )mcpp::lockfile::write (*lock, lockPath);
2357- }
2358- }
2359- return 0 ;
2360- }
2361-
2362- int cmd_update (const mcpplibs::cmdline::ParsedArgs& parsed) {
2363- std::optional<std::string> only;
2364- if (parsed.positional_count () > 0 ) only = parsed.positional (0 );
2365-
2366- auto root = find_manifest_root (std::filesystem::current_path ());
2367- if (!root) { mcpp::ui::error (" no mcpp.toml in current dir or parents" ); return 2 ; }
2368- auto lockPath = *root / " mcpp.lock" ;
2369- if (only) {
2370- // Targeted update — drop just that lock entry; next build will refetch.
2371- if (std::filesystem::exists (lockPath)) {
2372- auto lock = mcpp::lockfile::load (lockPath);
2373- if (lock) {
2374- std::erase_if (lock->packages ,
2375- [&](const auto & p) { return p.name == *only; });
2376- (void )mcpp::lockfile::write (*lock, lockPath);
2377- }
2378- }
2379- mcpp::ui::status (" Updating" , std::format (" {} in mcpp.lock" , *only));
2380- } else {
2381- // Wholesale update — wipe the lockfile.
2382- std::error_code ec;
2383- std::filesystem::remove (lockPath, ec);
2384- mcpp::ui::status (" Updating" , " all dependencies (mcpp.lock cleared)" );
2385- }
2386- std::println (" " );
2387- std::println (" Run `mcpp build` to re-resolve and rewrite mcpp.lock." );
2388- return 0 ;
2389- }
2146+ // `cmd_remove` and `cmd_update` have moved to `mcpp.pm.commands` (PR-R5).
23902147
23912148// ─── M4 #2: mcpp publish ────────────────────────────────────────────────
23922149// ─── M5.5 #toolchain: mcpp toolchain install/list/default/remove ────────
@@ -3230,15 +2987,15 @@ int run(int argc, char** argv) {
32302987 .subcommand (cl::App (" add" )
32312988 .description (" Add a dependency to mcpp.toml" )
32322989 .arg (cl::Arg (" pkg" ).help (" Package spec, e.g. foo@1.0.0" ).required ())
3233- .action (wrap_rc (cmd_add)))
2990+ .action (wrap_rc (mcpp::pm::commands:: cmd_add)))
32342991 .subcommand (cl::App (" remove" )
32352992 .description (" Remove a dependency from mcpp.toml" )
32362993 .arg (cl::Arg (" pkg" ).help (" Package name" ).required ())
3237- .action (wrap_rc (cmd_remove)))
2994+ .action (wrap_rc (mcpp::pm::commands:: cmd_remove)))
32382995 .subcommand (cl::App (" update" )
32392996 .description (" Re-resolve dependencies and rewrite mcpp.lock" )
32402997 .arg (cl::Arg (" pkg" ).help (" If given, update only that package" ))
3241- .action (wrap_rc (cmd_update)))
2998+ .action (wrap_rc (mcpp::pm::commands:: cmd_update)))
32422999 .subcommand (cl::App (" search" )
32433000 .description (" Search packages in configured registries" )
32443001 .arg (cl::Arg (" keyword" ).help (" Search keyword (substring match)" ).required ())
0 commit comments