@@ -6,18 +6,20 @@ import (
66 "io/fs"
77 "os"
88 "path/filepath"
9+ "strings"
910 "time"
1011
1112 "github.com/spf13/cobra"
1213
1314 "github.com/atomikpanda/dotular/internal/ageutil"
14- "github.com/atomikpanda/dotular/internal/color"
1515 "github.com/atomikpanda/dotular/internal/audit"
16+ "github.com/atomikpanda/dotular/internal/color"
1617 "github.com/atomikpanda/dotular/internal/config"
1718 "github.com/atomikpanda/dotular/internal/platform"
1819 "github.com/atomikpanda/dotular/internal/registry"
1920 "github.com/atomikpanda/dotular/internal/runner"
2021 "github.com/atomikpanda/dotular/internal/tags"
22+ "github.com/atomikpanda/dotular/internal/ui"
2123)
2224
2325var (
@@ -87,7 +89,8 @@ func loadAndResolveConfig(ctx context.Context) (config.Config, error) {
8789 if err != nil {
8890 return config.Config {}, err
8991 }
90- return registry .Resolve (ctx , cfg , configFile , noCache )
92+ u := ui .New (os .Stdout , os .Stderr )
93+ return registry .Resolve (ctx , cfg , configFile , noCache , u )
9194}
9295
9396func newRunner (cfg config.Config ) * runner.Runner {
@@ -206,9 +209,10 @@ managed store and records it in the config YAML.`,
206209 if isDir {
207210 typeStr = "directory"
208211 }
209- fmt .Printf ("added %s %q to module %q\n " , typeStr , baseName , moduleName )
210- fmt .Printf (" store: %s\n " , dest )
211- fmt .Printf (" config: %s\n " , configFile )
212+ u := ui .New (os .Stdout , os .Stderr )
213+ u .Success (fmt .Sprintf ("added %s %q to module %q" , typeStr , baseName , moduleName ))
214+ u .Info (fmt .Sprintf (" store: %s" , dest ))
215+ u .Info (fmt .Sprintf (" config: %s" , configFile ))
212216 return nil
213217 },
214218 }
@@ -275,8 +279,9 @@ func applyCmd() *cobra.Command {
275279 if mod == nil {
276280 return fmt .Errorf ("module %q not found in config" , name )
277281 }
278- if err := r .ApplyModule (ctx , * mod ); err != nil {
279- return err
282+ result := r .ApplyModule (ctx , * mod )
283+ if result .Err != nil {
284+ return result .Err
280285 }
281286 }
282287 return nil
@@ -311,8 +316,9 @@ func directionCmd(direction, short string) *cobra.Command {
311316 if mod == nil {
312317 return fmt .Errorf ("module %q not found in config" , name )
313318 }
314- if err := r .ApplyModule (ctx , * mod ); err != nil {
315- return err
319+ result := r .ApplyModule (ctx , * mod )
320+ if result .Err != nil {
321+ return result .Err
316322 }
317323 }
318324 return nil
@@ -332,14 +338,39 @@ func listCmd() *cobra.Command {
332338 if err != nil {
333339 return err
334340 }
341+ u := ui .New (os .Stdout , os .Stderr )
335342 for _ , mod := range cfg .Modules {
336- fmt .Fprintf (os .Stdout , "%-30s %d item(s)\n " , mod .Name , len (mod .Items ))
343+ counts := make (map [string ]int )
344+ for _ , item := range mod .Items {
345+ counts [item .Type ()]++
346+ }
347+ total := len (mod .Items )
348+ breakdown := formatTypeCounts (counts )
349+ u .Info (fmt .Sprintf ("%s %s" ,
350+ color .Bold (fmt .Sprintf ("%-30s" , mod .Name )),
351+ color .Dim (fmt .Sprintf ("%d items (%s)" , total , breakdown ))))
337352 }
338353 return nil
339354 },
340355 }
341356}
342357
358+ // formatTypeCounts formats a map of item type counts into a human-readable string.
359+ func formatTypeCounts (counts map [string ]int ) string {
360+ types := []string {"package" , "file" , "directory" , "script" , "binary" , "run" , "setting" }
361+ var parts []string
362+ for _ , t := range types {
363+ if n , ok := counts [t ]; ok && n > 0 {
364+ label := t
365+ if n != 1 {
366+ label += "s"
367+ }
368+ parts = append (parts , fmt .Sprintf ("%d %s" , n , label ))
369+ }
370+ }
371+ return strings .Join (parts , ", " )
372+ }
373+
343374// --- status ------------------------------------------------------------------
344375
345376func statusCmd () * cobra.Command {
@@ -365,7 +396,8 @@ func platformCmd() *cobra.Command {
365396 Use : "platform" ,
366397 Short : "Print the detected platform (OS)" ,
367398 Run : func (cmd * cobra.Command , args []string ) {
368- fmt .Fprintf (os .Stdout , "os: %s\n " , platform .Current ())
399+ u := ui .New (os .Stdout , os .Stderr )
400+ u .Info (fmt .Sprintf ("os: %s" , platform .Current ()))
369401 },
370402 }
371403}
@@ -411,7 +443,8 @@ func verifyCmd() *cobra.Command {
411443 return err
412444 }
413445 if ! allPassed {
414- fmt .Fprintln (os .Stderr , color .BoldRed ("\n some verify checks failed" ))
446+ u := ui .New (os .Stdout , os .Stderr )
447+ u .Warn ("some verify checks failed" )
415448 os .Exit (1 )
416449 }
417450 return nil
@@ -433,7 +466,8 @@ func encryptCmd() *cobra.Command {
433466 }
434467 src := args [0 ]
435468 dst := ageutil .RepoPath (src )
436- fmt .Printf ("encrypting %s -> %s\n " , src , dst )
469+ u := ui .New (os .Stdout , os .Stderr )
470+ u .Info (fmt .Sprintf ("encrypting %s → %s" , src , dst ))
437471 return key .EncryptFile (src , dst )
438472 },
439473 }
@@ -454,7 +488,8 @@ func decryptCmd() *cobra.Command {
454488 if len (dst ) > 4 && dst [len (dst )- 4 :] == ".age" {
455489 dst = dst [:len (dst )- 4 ]
456490 }
457- fmt .Printf ("decrypting %s -> %s\n " , src , dst )
491+ u := ui .New (os .Stdout , os .Stderr )
492+ u .Info (fmt .Sprintf ("decrypting %s → %s" , src , dst ))
458493 return key .DecryptFile (src , dst )
459494 },
460495 }
@@ -493,13 +528,14 @@ func tagCmd() *cobra.Command {
493528 if err != nil {
494529 return err
495530 }
496- fmt .Printf ("machine config: %s\n " , tags .ConfigPath ())
531+ u := ui .New (os .Stdout , os .Stderr )
532+ u .Info (color .Bold (fmt .Sprintf ("machine config: %s" , tags .ConfigPath ())))
497533 if len (cfg .Tags ) == 0 {
498- fmt . Println ( "(no tags)" )
534+ u . Info ( color . Dim ( "(no tags)" ) )
499535 return nil
500536 }
501537 for _ , t := range cfg .Tags {
502- fmt .Printf (" - %s\n " , t )
538+ u . Info ( fmt .Sprintf (" · %s" , t ) )
503539 }
504540 return nil
505541 },
@@ -515,7 +551,8 @@ func tagCmd() *cobra.Command {
515551 if err := tags .Add (args [0 ]); err != nil {
516552 return err
517553 }
518- fmt .Printf ("added tag %q\n " , args [0 ])
554+ u := ui .New (os .Stdout , os .Stderr )
555+ u .Success (fmt .Sprintf ("added tag %q" , args [0 ]))
519556 return nil
520557 },
521558 },
@@ -540,33 +577,33 @@ func logCmd() *cobra.Command {
540577 if err != nil {
541578 return fmt .Errorf ("read audit log: %w" , err )
542579 }
580+ u := ui .New (os .Stdout , os .Stderr )
543581 if len (entries ) == 0 {
544- fmt . Println ("(no log entries)" )
582+ u . Info ("(no log entries)" )
545583 return nil
546584 }
547585
548- fmt .Println (color .Bold (fmt .Sprintf ("%-20s %-8s %-20s %-8s %s" ,
549- "TIME" , "COMMAND" , "MODULE" , "OUTCOME" , "ITEM" )))
550- fmt .Println (color .Dim (repeatStr ("-" , 90 )))
586+ headers := []string {"TIME" , "COMMAND" , "MODULE" , "OUTCOME" , "ITEM" }
587+ var rows [][]string
551588 for _ , e := range entries {
552589 ts := e .Time .Local ().Format (time .DateTime )
553590 outcome := e .Outcome
554591 if e .Error != "" {
555592 outcome += " (" + e .Error + ")"
556593 }
557- outcomePadded := fmt . Sprintf ( "%-8s" , outcome )
594+ // Pre-color outcome
558595 switch e .Outcome {
559596 case "success" :
560- outcomePadded = color .Green (outcomePadded )
597+ outcome = color .Green (outcome )
561598 case "failure" :
562- outcomePadded = color .BoldRed (outcomePadded )
599+ outcome = color .BoldRed (outcome )
563600 case "skipped" :
564- outcomePadded = color .Dim (outcomePadded )
601+ outcome = color .Dim (outcome )
565602 }
566- fmt .Printf ("%-20s %-8s %-20s %s %s\n " ,
567- ts , e .Command , e .Module , outcomePadded , e .Item )
603+ rows = append (rows , []string {ts , e .Command , e .Module , outcome , e .Item })
568604 }
569- fmt .Printf ("\n log: %s\n " , audit .LogPath ())
605+ u .Table (headers , rows , nil )
606+ u .Info (fmt .Sprintf ("\n log: %s" , audit .LogPath ()))
570607 return nil
571608 },
572609 }
@@ -589,7 +626,7 @@ func registryCmd() *cobra.Command {
589626 Use : "list" ,
590627 Short : "List cached registry modules" ,
591628 RunE : func (cmd * cobra.Command , args []string ) error {
592- cfg , err := loadConfig ()
629+ _ , err := loadConfig ()
593630 if err != nil {
594631 return err
595632 }
@@ -598,31 +635,32 @@ func registryCmd() *cobra.Command {
598635 if err != nil {
599636 return err
600637 }
638+ u := ui .New (os .Stdout , os .Stderr )
601639 if len (lock .Registry ) == 0 {
602- fmt . Println ("(no cached registry modules)" )
640+ u . Info ("(no cached registry modules)" )
603641 return nil
604642 }
605- fmt . Println ( color . Bold ( fmt . Sprintf ( "%-50s %-8s %s" , " REF" , "TRUST" , "FETCHED" )))
606- fmt . Println ( color . Dim ( repeatStr ( "-" , 80 )))
643+ headers := [] string { " REF" , "TRUST" , "FETCHED" }
644+ var rows [][] string
607645 for ref , entry := range lock .Registry {
608646 ref := registry .ParseRef (ref )
609647 trustStr := ref .Trust .String ()
610- trustPadded := fmt . Sprintf ( "%-8s" , trustStr )
648+ // Pre-color trust
611649 switch trustStr {
612650 case "official" :
613- trustPadded = color .BoldGreen (trustPadded )
651+ trustStr = color .BoldGreen (trustStr )
614652 case "community" :
615- trustPadded = color .Yellow (trustPadded )
653+ trustStr = color .Yellow (trustStr )
616654 default :
617- trustPadded = color .Dim (trustPadded )
655+ trustStr = color .Dim (trustStr )
618656 }
619- fmt . Printf ( "%-50s %s %s \n " ,
657+ rows = append ( rows , [] string {
620658 ref .Raw ,
621- trustPadded ,
659+ trustStr ,
622660 entry .FetchedAt .Local ().Format (time .DateTime ),
623- )
661+ } )
624662 }
625- _ = cfg
663+ u . Table ( headers , rows , nil )
626664 return nil
627665 },
628666 },
@@ -633,7 +671,8 @@ func registryCmd() *cobra.Command {
633671 if err := registry .ClearCache (); err != nil {
634672 return err
635673 }
636- fmt .Println ("registry cache cleared" )
674+ u := ui .New (os .Stdout , os .Stderr )
675+ u .Success ("registry cache cleared" )
637676 return nil
638677 },
639678 },
@@ -647,23 +686,15 @@ func registryCmd() *cobra.Command {
647686 if err != nil {
648687 return err
649688 }
650- _ , err = registry .Resolve (ctx , cfg , configFile , true )
689+ u := ui .New (os .Stdout , os .Stderr )
690+ _ , err = registry .Resolve (ctx , cfg , configFile , true , u )
651691 if err != nil {
652692 return err
653693 }
654- fmt . Println ("registry modules updated" )
694+ u . Success ("registry modules updated" )
655695 return nil
656696 },
657697 },
658698 )
659699 return cmd
660700}
661-
662- // repeatStr returns s repeated n times.
663- func repeatStr (s string , n int ) string {
664- b := make ([]byte , n * len (s ))
665- for i := range b {
666- b [i ] = s [i % len (s )]
667- }
668- return string (b )
669- }
0 commit comments