diff --git a/cmd/dots/install.go b/cmd/dots/install.go index e8723f2..6d54660 100644 --- a/cmd/dots/install.go +++ b/cmd/dots/install.go @@ -4,6 +4,7 @@ import ( "github.com/spf13/cobra" "go.evanpurkhiser.com/dots/installer" + "go.evanpurkhiser.com/dots/output" "go.evanpurkhiser.com/dots/resolver" ) @@ -12,25 +13,35 @@ var installCmd = cobra.Command{ Short: "Install and compile dotfiles from sources", RunE: func(cmd *cobra.Command, args []string) error { forceReInstall, _ := cmd.Flags().GetBool("reinstall") + verbose, _ := cmd.Flags().GetBool("verbose") dryRun, _ := cmd.Flags().GetBool("dry-run") + info, _ := cmd.Flags().GetBool("info") dotfiles := resolver.ResolveDotfiles(*sourceConfig, *sourceLockfile).Filter(args) prepared := installer.PrepareDotfiles(dotfiles, *sourceConfig) - if dryRun { - // TODO: Do logging output - return nil - } - - config := installer.InstallConfig{ + installConfig := installer.InstallConfig{ SourceConfig: sourceConfig, SourceLockfile: sourceLockfile, ForceReinstall: forceReInstall, } - installed := installer.InstallDotfiles(prepared, config) - installer.RunInstallScripts(prepared, config) - installer.FinalizeInstall(installed, config) + installLogger := output.New(output.Config{ + SourceConfig: *sourceConfig, + InstallConfig: installConfig, + PreparedInstall: prepared, + IsVerbose: verbose, + IsInfo: info || verbose || dryRun, + }) + + if dryRun { + installLogger.DryrunInstall() + return nil + } + + installed := installer.InstallDotfiles(prepared, installConfig) + installer.RunInstallScripts(prepared, installConfig) + installer.FinalizeInstall(installed, installConfig) // TODO Needs some error handling clenaup @@ -44,7 +55,7 @@ func init() { flags.SortFlags = false flags.BoolP("reinstall", "r", false, "forces execution of all installation scripts") - flags.BoolP("dry-run", "n", false, "do not mutate any dotfiles, implies info") flags.BoolP("info", "i", false, "prints install operation details") flags.BoolP("verbose", "v", false, "prints debug data, implies info") + flags.BoolP("dry-run", "n", false, "do not mutate any dotfiles, implies info") } diff --git a/output/fmt.go b/output/fmt.go deleted file mode 100644 index 6a8cc22..0000000 --- a/output/fmt.go +++ /dev/null @@ -1,29 +0,0 @@ -package output - -// TODO: Output writer that looks somthing like -// -// source: /home/.local/etc -// install: /home/.config -// -// [=> base] -- bash/bashrc -// [=> composed] -- bash/environment -// -> composing from base and machines/desktop groups -// [=> removed] -- bash/bad-filemulti -// [=> compiled] -- bash/complex -// -> ignoring configs in base and common/work due to override -// -> override file present in common/work-vm -// -> composing from machines/crunchydev (spliced at common/work-vm:22) -// -// [=> install script] base/bash.install -// -> triggered by base/bash/bashrc -// -> triggered by base/bash/environment - -// CLI Interfaceo - -// dots {config, install, diff, files, help} - -// dots install [filter...] -// dots diff [filter...] -// dots files [filter...] - -// dots config {profiles, groups, use, override} diff --git a/output/logger.go b/output/logger.go new file mode 100644 index 0000000..051cc06 --- /dev/null +++ b/output/logger.go @@ -0,0 +1,149 @@ +package output + +import ( + "fmt" + "strings" + + "github.com/fatih/color" + "go.evanpurkhiser.com/dots/config" + "go.evanpurkhiser.com/dots/installer" +) + +// Config is a object used to configure the output logger. +type Config struct { + SourceConfig config.SourceConfig + InstallConfig installer.InstallConfig + PreparedInstall installer.PreparedInstall + IsVerbose bool + IsInfo bool +} + +// New creates a output logger given a configuration. +func New(config Config) *Output { + logger := &Output{ + Config: config, + } + + // Get the max length of the groups + maxDotfileLength := 0 + for _, d := range config.PreparedInstall.Dotfiles { + if logger.shouldLogDotfile(d) && len(d.Path) > maxDotfileLength { + maxDotfileLength = len(d.Path) + } + } + logger.maxDotfileLength = maxDotfileLength + + return logger +} + +// Output represents a service object used to output logging information about +// dotfile installation operations. +type Output struct { + Config + maxDotfileLength int +} + +// shouldLogDotfile indicates if the dotfile should be logged given the current +// Output configuration. +func (l *Output) shouldLogDotfile(dotfile *installer.PreparedDotfile) bool { + return dotfile.PrepareError != nil || + dotfile.IsChanged() || + l.InstallConfig.ForceReinstall +} + +// DryrunInstall outputs the logging of a dryrun of the prepared dotfiles +func (l *Output) DryrunInstall() { + l.InstallInfo() + fmt.Println() + + for _, dotfile := range l.PreparedInstall.Dotfiles { + l.DotfileInfo(dotfile) + } + + fmt.Println() +} + +// InstallInfo outputs details about the pending installation. Output is only +// performed when verbosity is enabled. +func (l *Output) InstallInfo() { + if !l.IsVerbose { + return + } + + fmt.Printf( + "%s %s added %s removed %s modified %s error\n", + color.HiBlackString("legend:"), + color.HiGreenString("◼️"), + color.HiYellowString("◼️"), + color.HiBlueString("◼️"), + color.HiRedString("◼️"), + ) + + fmt.Printf("%s %s\n", color.HiBlackString("source:"), l.SourceConfig.SourcePath) + fmt.Printf("%s %s\n", color.HiBlackString("target:"), l.SourceConfig.InstallPath) +} + +// DotfileInfo outputs information about a single prepared dotfile. Will not +// output anything without IsInfo. When IsVerbose is enabled additional +// information about the prepared dotfile will be included. +func (l *Output) DotfileInfo(dotfile *installer.PreparedDotfile) { + if !l.IsInfo { + return + } + + if !l.shouldLogDotfile(dotfile) { + return + } + + indicatorColor := color.New() + indicator := "◼️" + + switch { + case dotfile.PrepareError != nil: + indicatorColor.Add(color.FgRed) + case dotfile.IsNew: + indicatorColor.Add(color.FgHiGreen) + case dotfile.Removed: + indicatorColor.Add(color.FgHiYellow) + case dotfile.IsChanged(): + indicatorColor.Add(color.FgBlue) + default: + indicatorColor.Add(color.FgHiBlack) + indicator = "-" + } + + fmt.Printf(" %s ", indicatorColor.Sprint(indicator)) + + group := "" + if len(dotfile.Sources) == 1 { + group = dotfile.Sources[0].Group + } else { + groups := make([]string, 0, len(dotfile.Sources)) + + for _, source := range dotfile.Sources { + groups = append(groups, source.Group) + } + + group = strings.Join(groups, " ") + } + + group = fmt.Sprintf( + "%s %s %s", + color.HiBlackString("["), + color.HiWhiteString(group), + color.HiBlackString("]"), + ) + + output := fmt.Sprintf("%%-%ds %%s\n", l.maxDotfileLength+1) + fmt.Printf(output, dotfile.Path, group) + + if dotfile.PrepareError != nil { + fmt.Printf(" %s", color.RedString(dotfile.PrepareError.Error())) + } + + if !l.IsVerbose { + return + } + + // TODO: Implement all verbosity outputs here +}