@@ -44,7 +44,8 @@ defmodule Opsm.CLI do
4444 json: :boolean ,
4545 limit: :integer ,
4646 native: :boolean ,
47- dev: :boolean
47+ dev: :boolean ,
48+ apply: :boolean
4849 ] ,
4950 aliases: [
5051 h: :help ,
@@ -69,9 +70,7 @@ defmodule Opsm.CLI do
6970 [ "repolist" | _ ] -> { :repolist , opts }
7071
7172 # Install/Remove
72- [ "install" , "@" <> forth , package | _ ] -> { :install , forth , package , opts }
73- [ "install" , package | _ ] -> { :install , nil , package , opts }
74- [ "install" ] -> { :error , "install requires a package argument" }
73+ [ "install" | rest ] -> parse_install_args ( rest , opts )
7574
7675 [ "remove" , package | _ ] -> { :remove , package , opts }
7776 [ "uninstall" , package | _ ] -> { :remove , package , opts }
@@ -143,6 +142,22 @@ defmodule Opsm.CLI do
143142 end
144143 end
145144
145+ defp parse_install_args ( [ ] , _opts ) , do: { :error , "install requires a package argument" }
146+
147+ defp parse_install_args ( [ "@" <> forth , package | _ ] , opts ) do
148+ { :install , forth , package , opts }
149+ end
150+
151+ defp parse_install_args ( [ package ] , opts ) do
152+ if String . contains? ( package , ":" ) or String . contains? ( package , "[" ) or String . contains? ( package , "]" ) do
153+ { :smart_install , [ package ] , opts }
154+ else
155+ { :install , nil , package , opts }
156+ end
157+ end
158+
159+ defp parse_install_args ( rest , opts ) , do: { :smart_install , rest , opts }
160+
146161 defp run ( { :help , _opts } ) do
147162 IO . puts ( """
148163 opsm - Odds-and-sods Package Manager
@@ -154,6 +169,7 @@ defmodule Opsm.CLI do
154169
155170 PACKAGE COMMANDS:
156171 install [@forth] <pkg> Install package (optionally from specific registry)
172+ install [backend:] ... Smart install with backend grouping
157173 remove <package> Remove an installed package
158174 reinstall <package> Reinstall a package
159175 update [packages...] Update packages (all if none specified)
@@ -198,6 +214,7 @@ defmodule Opsm.CLI do
198214 --user Install for current user (default)
199215 -g, --global Global install (native mode)
200216 -D, --dev Developsment dependency
217+ --apply Apply smart install plan (default is dry-run)
201218 --native Use native toolchain (npm, cargo, mix, pip)
202219 -y, --yes Assume yes to prompts
203220 -q, --quiet Minimal output
@@ -233,6 +250,7 @@ defmodule Opsm.CLI do
233250 EXAMPLES:
234251 opsm install lodash # From default/detected registry
235252 opsm install @npm lodash --version 4.17 # Specific version from npm
253+ opsm install rpm-ostree: gcc clang # Smart install grouping
236254 opsm install @cargo tokio --allow rc # Release candidate
237255 opsm install @hex phoenix --systemwide # System-wide
238256 opsm install @dnf htop # Via system package manager
@@ -325,6 +343,17 @@ defmodule Opsm.CLI do
325343 end
326344 end
327345
346+ defp run ( { :smart_install , rest , opts } ) do
347+ plan = parse_smart_install ( rest )
348+ print_smart_plan ( plan )
349+
350+ if opts [ :apply ] do
351+ IO . puts ( "Apply is not wired yet. This is a dry-run plan only." )
352+ else
353+ IO . puts ( "Dry-run only. Use --apply to execute when available." )
354+ end
355+ end
356+
328357 defp run ( { :remove , package , opts } ) do
329358 alias Opsm.Package.Installer
330359
@@ -1035,4 +1064,42 @@ defmodule Opsm.CLI do
10351064 IO . puts ( " opsm install @cargo #{ package } " )
10361065 System . halt ( 0 )
10371066 end
1067+
1068+ defp parse_smart_install ( tokens ) do
1069+ cleaned =
1070+ tokens
1071+ |> Enum . map ( & String . trim / 1 )
1072+ |> Enum . map ( & String . trim_leading ( & 1 , "[" ) )
1073+ |> Enum . map ( & String . trim_trailing ( & 1 , "]" ) )
1074+
1075+ backends = MapSet . new ( [ "rpm-ostree" , "toolbox" , "distrobox" , "container" , "native" , "git" , "source" , "auto" ] )
1076+
1077+ Enum . reduce ( cleaned , % { current: "auto" , plan: % { } } , fn token , acc ->
1078+ case String . split ( token , ":" , parts: 2 ) do
1079+ [ backend , "" ] ->
1080+ backend = String . downcase ( backend )
1081+ backend = if MapSet . member? ( backends , backend ) , do: backend , else: "auto"
1082+ % { acc | current: backend }
1083+
1084+ [ backend , rest ] when rest != "" ->
1085+ backend = String . downcase ( backend )
1086+ backend = if MapSet . member? ( backends , backend ) , do: backend , else: "auto"
1087+ pkg = String . trim ( rest )
1088+ plan = Map . update ( acc . plan , backend , [ pkg ] , fn pkgs -> pkgs ++ [ pkg ] end )
1089+ % { acc | current: backend , plan: plan }
1090+
1091+ [ pkg ] ->
1092+ plan = Map . update ( acc . plan , acc . current , [ pkg ] , fn pkgs -> pkgs ++ [ pkg ] end )
1093+ % { acc | plan: plan }
1094+ end
1095+ end )
1096+ |> Map . get ( :plan )
1097+ end
1098+
1099+ defp print_smart_plan ( plan ) do
1100+ IO . puts ( "Smart install plan (grouped by backend):" )
1101+ Enum . each ( plan , fn { backend , pkgs } ->
1102+ IO . puts ( " - #{ backend } : #{Enum . join ( pkgs , \", \" )}" )
1103+ end )
1104+ end
10381105end
0 commit comments