-// "sync" sub-command: format changes
+// Frontend: Format changes
// Copyright (C) 2021 Simon Ruderich
//
// 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
+package frontend
import (
"fmt"
// the remote helper is untrusted and must be either escaped with %q or by
// calling EscapeControlCharacters().
-func (s *Sync) formatChanges(resp safcm.MsgSyncResp) string {
+type Changes struct {
+ DryRun bool
+ Quiet bool
+ IsTTY bool
+}
+
+func (c *Changes) FormatChanges(resp safcm.MsgSyncResp) string {
var changes []string
if len(resp.FileChanges) > 0 {
changes = append(changes,
- s.formatFileChanges(resp.FileChanges))
+ c.FormatFileChanges(resp.FileChanges))
}
if len(resp.PackageChanges) > 0 {
changes = append(changes,
- s.formatPackageChanges(resp.PackageChanges))
+ c.FormatPackageChanges(resp.PackageChanges))
}
if len(resp.ServiceChanges) > 0 {
changes = append(changes,
- s.formatServiceChanges(resp.ServiceChanges))
+ c.FormatServiceChanges(resp.ServiceChanges))
}
if len(resp.CommandChanges) > 0 {
changes = append(changes,
- s.formatCommandChanges(resp.CommandChanges))
+ c.FormatCommandChanges(resp.CommandChanges))
}
if len(changes) == 0 {
// Notify user that the host was synced successfully
return "\n" + x
}
-func (s *Sync) formatFileChanges(changes []safcm.FileChange) string {
+func (c *Changes) FormatFileChanges(changes []safcm.FileChange) string {
var buf strings.Builder
- if s.config.DryRun {
+ if c.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))
+ fmt.Fprintf(&buf, "%s:", c.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),
+ ColorString(c.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),
+ FormatFileType(x.Old),
+ FormatFileType(x.New),
))
}
if x.Old.User != x.New.User ||
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),
+ 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),
+ FormatFilePerm(x.Old),
+ FormatFilePerm(x.New),
))
}
}
}
if x.DataDiff != "" {
- fmt.Fprintf(&buf, "\n%s", s.formatDiff(x.DataDiff))
+ fmt.Fprintf(&buf, "\n%s", c.FormatDiff(x.DataDiff))
}
fmt.Fprintf(&buf, "\n")
}
return buf.String()
}
-func formatFileType(info safcm.FileChangeInfo) string {
+
+func FormatFileType(info safcm.FileChangeInfo) string {
switch info.Mode.Type() {
case 0: // regular file
return "file"
return fmt.Sprintf("invalid type %v", info.Mode.Type())
}
}
-func formatFileUserGroup(info safcm.FileChangeInfo) string {
+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 {
+func FormatFilePerm(info safcm.FileChangeInfo) string {
return fmt.Sprintf("%#o", config.FileModeToFullPerm(info.Mode))
}
-func (s *Sync) formatPackageChanges(changes []safcm.PackageChange) string {
+func (c *Changes) FormatPackageChanges(changes []safcm.PackageChange) string {
var buf strings.Builder
- if s.config.DryRun {
+ if c.DryRun {
fmt.Fprintf(&buf, "will install %d package(s): (dry-run)\n",
len(changes))
} else {
}
for _, x := range changes {
// TODO: indicate if installation failed
- fmt.Fprintf(&buf, "%s\n", s.formatTarget(x.Name))
+ fmt.Fprintf(&buf, "%s\n", c.FormatTarget(x.Name))
}
return buf.String()
}
-func (s *Sync) formatServiceChanges(changes []safcm.ServiceChange) string {
+func (c *Changes) FormatServiceChanges(changes []safcm.ServiceChange) string {
var buf strings.Builder
- if s.config.DryRun {
+ if c.DryRun {
fmt.Fprintf(&buf, "will modify %d service(s): (dry-run)\n",
len(changes))
} else {
info = append(info, "enabled")
}
fmt.Fprintf(&buf, "%s: %s\n",
- s.formatTarget(x.Name),
+ c.FormatTarget(x.Name),
strings.Join(info, ", "))
}
return buf.String()
}
-func (s *Sync) formatCommandChanges(changes []safcm.CommandChange) string {
+func (c *Changes) FormatCommandChanges(changes []safcm.CommandChange) string {
const indent = " > "
// Quiet hides all successful, non-trigger commands which produce no
// 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 {
+ if c.Quiet {
for _, x := range changes {
if x.Trigger == "" &&
x.Error == "" &&
}
var buf strings.Builder
- if s.config.DryRun {
+ if c.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 {
+ if noOutput > 0 && !c.DryRun {
fmt.Fprintf(&buf, ", %d with no output (hidden)", noOutput)
}
if noOutput != len(changes) {
fmt.Fprintf(&buf, ":")
}
- if s.config.DryRun {
+ if c.DryRun {
fmt.Fprintf(&buf, " (dry-run)")
}
fmt.Fprintf(&buf, "\n")
continue
}
- fmt.Fprintf(&buf, "%s", s.formatTarget(x.Command))
+ fmt.Fprintf(&buf, "%s", c.FormatTarget(x.Command))
if x.Trigger != "" {
fmt.Fprintf(&buf, ", trigger for %q", x.Trigger)
}
// TODO: truncate very large outputs?
x := indentBlock(x.Output, indent)
fmt.Fprintf(&buf, ":\n%s",
- EscapeControlCharacters(s.isTTY, x))
+ EscapeControlCharacters(c.IsTTY, x))
}
fmt.Fprintf(&buf, "\n")
}
return buf.String()
}
-func (s *Sync) formatTarget(x string) string {
+func (c *Changes) FormatTarget(x string) string {
x = fmt.Sprintf("%q", x) // escape!
- return ColorString(s.isTTY, ColorCyan, x)
+ return ColorString(c.IsTTY, ColorCyan, x)
}
-func (s *Sync) formatDiff(diff string) string {
+func (c *Changes) 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 {
+ if !c.IsTTY {
return diff
}
var res []string
for _, x := range strings.Split(diff, "\n") {
if strings.HasPrefix(x, indent+"+") {
- x = ColorString(s.isTTY, ColorGreen, x)
+ x = ColorString(c.IsTTY, ColorGreen, x)
} else if strings.HasPrefix(x, indent+"-") {
- x = ColorString(s.isTTY, ColorRed, x)
+ x = ColorString(c.IsTTY, ColorRed, x)
}
res = append(res, x)
}
// 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
+package frontend
import (
"io/fs"
"testing"
"ruderich.org/simon/safcm"
- "ruderich.org/simon/safcm/cmd/safcm/config"
"ruderich.org/simon/safcm/testutil"
)
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
- s := &Sync{
- config: &config.Config{
- DryRun: tc.dryRun,
- Quiet: tc.quiet,
- },
- isTTY: tc.isTTY,
+ c := Changes{
+ DryRun: tc.dryRun,
+ Quiet: tc.quiet,
+ IsTTY: tc.isTTY,
}
- res := s.formatChanges(tc.resp)
+ res := c.FormatChanges(tc.resp)
testutil.AssertEqual(t, "res", res, tc.exp)
})
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
- s := &Sync{
- config: &config.Config{
- DryRun: tc.dryRun,
- },
- isTTY: tc.isTTY,
+ c := Changes{
+ DryRun: tc.dryRun,
+ IsTTY: tc.isTTY,
}
- res := s.formatFileChanges(tc.changes)
+ res := c.FormatFileChanges(tc.changes)
testutil.AssertEqual(t, "res", res, tc.exp)
})
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
- s := &Sync{
- config: &config.Config{
- DryRun: tc.dryRun,
- },
- isTTY: tc.isTTY,
+ c := Changes{
+ DryRun: tc.dryRun,
+ IsTTY: tc.isTTY,
}
- res := s.formatPackageChanges(tc.changes)
+ res := c.FormatPackageChanges(tc.changes)
testutil.AssertEqual(t, "res", res, tc.exp)
})
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
- s := &Sync{
- config: &config.Config{
- DryRun: tc.dryRun,
- },
- isTTY: tc.isTTY,
+ c := Changes{
+ DryRun: tc.dryRun,
+ IsTTY: tc.isTTY,
}
- res := s.formatServiceChanges(tc.changes)
+ res := c.FormatServiceChanges(tc.changes)
testutil.AssertEqual(t, "res", res, tc.exp)
})
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
- s := &Sync{
- config: &config.Config{
- DryRun: tc.dryRun,
- Quiet: tc.quiet,
- },
- isTTY: tc.isTTY,
+ c := Changes{
+ DryRun: tc.dryRun,
+ Quiet: tc.quiet,
+ IsTTY: tc.isTTY,
}
- res := s.formatCommandChanges(tc.changes)
+ res := c.FormatCommandChanges(tc.changes)
testutil.AssertEqual(t, "res", res, tc.exp)
})
}