]> ruderich.org/simon Gitweb - safcm/safcm.git/blobdiff - cmd/safcm/sync_changes.go
safcm: move sync_changes.go and term.go to frontend package
[safcm/safcm.git] / cmd / safcm / sync_changes.go
diff --git a/cmd/safcm/sync_changes.go b/cmd/safcm/sync_changes.go
deleted file mode 100644 (file)
index 2650bb4..0000000
+++ /dev/null
@@ -1,277 +0,0 @@
-// "sync" sub-command: format changes
-
-// Copyright (C) 2021  Simon Ruderich
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package main
-
-import (
-       "fmt"
-       "io/fs"
-       "strings"
-
-       "ruderich.org/simon/safcm"
-       "ruderich.org/simon/safcm/cmd/safcm/config"
-)
-
-// NOTE: Be careful when implementing new format* functions. All input from
-// the remote helper is untrusted and must be either escaped with %q or by
-// calling EscapeControlCharacters().
-
-func (s *Sync) formatChanges(resp safcm.MsgSyncResp) string {
-       var changes []string
-       if len(resp.FileChanges) > 0 {
-               changes = append(changes,
-                       s.formatFileChanges(resp.FileChanges))
-       }
-       if len(resp.PackageChanges) > 0 {
-               changes = append(changes,
-                       s.formatPackageChanges(resp.PackageChanges))
-       }
-       if len(resp.ServiceChanges) > 0 {
-               changes = append(changes,
-                       s.formatServiceChanges(resp.ServiceChanges))
-       }
-       if len(resp.CommandChanges) > 0 {
-               changes = append(changes,
-                       s.formatCommandChanges(resp.CommandChanges))
-       }
-       if len(changes) == 0 {
-               // Notify user that the host was synced successfully
-               return "no changes"
-       }
-
-       x := strings.Join(changes, "\n")
-       // If quiet is used and only commands without output were executed
-       // then don't prepend a newline so that the whole change output of a
-       // host fits in a single line. This makes the output much more
-       // readable with multiple hosts.
-       if strings.Count(x, "\n") == 1 {
-               return x
-       }
-       return "\n" + x
-}
-
-func (s *Sync) formatFileChanges(changes []safcm.FileChange) string {
-       var buf strings.Builder
-       if s.config.DryRun {
-               fmt.Fprintf(&buf, "will change %d file(s): (dry-run)\n",
-                       len(changes))
-       } else {
-               fmt.Fprintf(&buf, "changed %d file(s):\n", len(changes))
-       }
-       for _, x := range changes {
-               fmt.Fprintf(&buf, "%s:", s.formatTarget(x.Path))
-
-               var info []string
-               if x.Created {
-                       info = append(info,
-                               ColorString(s.isTTY, ColorGreen, "created"),
-                               formatFileType(x.New),
-                               formatFileUserGroup(x.New),
-                               formatFilePerm(x.New),
-                       )
-               } else {
-                       if x.Old.Mode.Type() != x.New.Mode.Type() {
-                               info = append(info, fmt.Sprintf("%s -> %s",
-                                       formatFileType(x.Old),
-                                       formatFileType(x.New),
-                               ))
-                       }
-                       if x.Old.User != x.New.User ||
-                               x.Old.Uid != x.New.Uid ||
-                               x.Old.Group != x.New.Group ||
-                               x.Old.Gid != x.New.Gid {
-                               info = append(info, fmt.Sprintf("%s -> %s",
-                                       formatFileUserGroup(x.Old),
-                                       formatFileUserGroup(x.New),
-                               ))
-                       }
-                       if config.FileModeToFullPerm(x.Old.Mode) !=
-                               config.FileModeToFullPerm(x.New.Mode) {
-                               info = append(info, fmt.Sprintf("%s -> %s",
-                                       formatFilePerm(x.Old),
-                                       formatFilePerm(x.New),
-                               ))
-                       }
-               }
-               if len(info) > 0 {
-                       fmt.Fprint(&buf, " ")
-                       fmt.Fprint(&buf, strings.Join(info, ", "))
-               }
-
-               if x.DataDiff != "" {
-                       fmt.Fprintf(&buf, "\n%s", s.formatDiff(x.DataDiff))
-               }
-               fmt.Fprintf(&buf, "\n")
-       }
-       return buf.String()
-}
-func formatFileType(info safcm.FileChangeInfo) string {
-       switch info.Mode.Type() {
-       case 0: // regular file
-               return "file"
-       case fs.ModeSymlink:
-               return "symlink"
-       case fs.ModeDir:
-               return "dir"
-       default:
-               return fmt.Sprintf("invalid type %v", info.Mode.Type())
-       }
-}
-func formatFileUserGroup(info safcm.FileChangeInfo) string {
-       return fmt.Sprintf("%s(%d) %s(%d)",
-               EscapeControlCharacters(false, info.User), info.Uid,
-               EscapeControlCharacters(false, info.Group), info.Gid)
-}
-func formatFilePerm(info safcm.FileChangeInfo) string {
-       return fmt.Sprintf("%#o", config.FileModeToFullPerm(info.Mode))
-}
-
-func (s *Sync) formatPackageChanges(changes []safcm.PackageChange) string {
-       var buf strings.Builder
-       if s.config.DryRun {
-               fmt.Fprintf(&buf, "will install %d package(s): (dry-run)\n",
-                       len(changes))
-       } else {
-               fmt.Fprintf(&buf, "installed %d package(s):\n", len(changes))
-       }
-       for _, x := range changes {
-               // TODO: indicate if installation failed
-               fmt.Fprintf(&buf, "%s\n", s.formatTarget(x.Name))
-       }
-       return buf.String()
-}
-
-func (s *Sync) formatServiceChanges(changes []safcm.ServiceChange) string {
-       var buf strings.Builder
-       if s.config.DryRun {
-               fmt.Fprintf(&buf, "will modify %d service(s): (dry-run)\n",
-                       len(changes))
-       } else {
-               fmt.Fprintf(&buf, "modified %d service(s):\n", len(changes))
-       }
-       for _, x := range changes {
-               var info []string
-               if x.Started {
-                       info = append(info, "started")
-               }
-               if x.Enabled {
-                       info = append(info, "enabled")
-               }
-               fmt.Fprintf(&buf, "%s: %s\n",
-                       s.formatTarget(x.Name),
-                       strings.Join(info, ", "))
-       }
-       return buf.String()
-}
-
-func (s *Sync) formatCommandChanges(changes []safcm.CommandChange) string {
-       const indent = "   > "
-
-       // Quiet hides all successful, non-trigger commands which produce no
-       // output. This is useful as many commands will be used to enforce a
-       // certain state (e.g. file not-present, `ainsl`, etc.) and are run on
-       // each sync. Displaying them provides not much useful information.
-       // Instead, quiet shows them only when they produce output (e.g.
-       // `ainsl`, `rm -v`) and thus modify the host's state.
-       var noOutput int
-       if s.config.Quiet {
-               for _, x := range changes {
-                       if x.Trigger == "" &&
-                               x.Error == "" &&
-                               x.Output == "" {
-                               noOutput++
-                       }
-               }
-       }
-
-       var buf strings.Builder
-       if s.config.DryRun {
-               fmt.Fprintf(&buf, "will execute %d command(s)", len(changes))
-       } else {
-               fmt.Fprintf(&buf, "executed %d command(s)", len(changes))
-       }
-       if noOutput > 0 && !s.config.DryRun {
-               fmt.Fprintf(&buf, ", %d with no output (hidden)", noOutput)
-       }
-       if noOutput != len(changes) {
-               fmt.Fprintf(&buf, ":")
-       }
-       if s.config.DryRun {
-               fmt.Fprintf(&buf, " (dry-run)")
-       }
-       fmt.Fprintf(&buf, "\n")
-       for _, x := range changes {
-               if noOutput > 0 &&
-                       x.Trigger == "" && x.Error == "" && x.Output == "" {
-                       continue
-               }
-
-               fmt.Fprintf(&buf, "%s", s.formatTarget(x.Command))
-               if x.Trigger != "" {
-                       fmt.Fprintf(&buf, ", trigger for %q", x.Trigger)
-               }
-               if x.Error != "" {
-                       fmt.Fprintf(&buf, ", failed: %q", x.Error)
-               }
-               if x.Output != "" {
-                       // TODO: truncate very large outputs?
-                       x := indentBlock(x.Output, indent)
-                       fmt.Fprintf(&buf, ":\n%s",
-                               EscapeControlCharacters(s.isTTY, x))
-               }
-               fmt.Fprintf(&buf, "\n")
-       }
-       return buf.String()
-}
-
-func (s *Sync) formatTarget(x string) string {
-       x = fmt.Sprintf("%q", x) // escape!
-       return ColorString(s.isTTY, ColorCyan, x)
-}
-
-func (s *Sync) formatDiff(diff string) string {
-       const indent = "   "
-
-       diff = indentBlock(diff, indent)
-       // Never color diff content as we want to color the whole diff
-       diff = EscapeControlCharacters(false, diff)
-       if !s.isTTY {
-               return diff
-       }
-
-       var res []string
-       for _, x := range strings.Split(diff, "\n") {
-               if strings.HasPrefix(x, indent+"+") {
-                       x = ColorString(s.isTTY, ColorGreen, x)
-               } else if strings.HasPrefix(x, indent+"-") {
-                       x = ColorString(s.isTTY, ColorRed, x)
-               }
-               res = append(res, x)
-       }
-       return strings.Join(res, "\n")
-}
-
-func indentBlock(x string, sep string) string {
-       lines := strings.Split(x, "\n")
-       if lines[len(lines)-1] == "" {
-               lines = lines[:len(lines)-1]
-       } else {
-               lines = append(lines, "\\ No newline at end of file")
-       }
-
-       return sep + strings.Join(lines, "\n"+sep)
-}