Skip to content

Commit

Permalink
Add inital undocumented mackup add command
Browse files Browse the repository at this point in the history
  • Loading branch information
twpayne committed Aug 4, 2021
1 parent 91d10b0 commit a4eeff4
Show file tree
Hide file tree
Showing 8 changed files with 832 additions and 510 deletions.
5 changes: 4 additions & 1 deletion internal/cmd/addcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,10 @@ func (c *Config) defaultPreAddFunc(targetRelPath chezmoi.RelPath, newSourceState
}

func (c *Config) runAddCmd(cmd *cobra.Command, args []string, sourceState *chezmoi.SourceState) error {
destAbsPathInfos, err := c.destAbsPathInfos(sourceState, args, c.Add.recursive, c.Add.follow)
destAbsPathInfos, err := c.destAbsPathInfos(sourceState, args, destAbsPathInfosOptions{
follow: c.Add.follow,
recursive: c.Add.recursive,
})
if err != nil {
return err
}
Expand Down
33 changes: 25 additions & 8 deletions internal/cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -735,7 +735,13 @@ func (c *Config) defaultTemplateData() map[string]interface{} {
}
}

func (c *Config) destAbsPathInfos(sourceState *chezmoi.SourceState, args []string, recursive, follow bool) (map[chezmoi.AbsPath]fs.FileInfo, error) {
type destAbsPathInfosOptions struct {
follow bool
ignoreNotExist bool
recursive bool
}

func (c *Config) destAbsPathInfos(sourceState *chezmoi.SourceState, args []string, options destAbsPathInfosOptions) (map[chezmoi.AbsPath]fs.FileInfo, error) {
destAbsPathInfos := make(map[chezmoi.AbsPath]fs.FileInfo)
for _, arg := range args {
destAbsPath, err := chezmoi.NewAbsPathFromExtPath(arg, c.homeDirAbsPath)
Expand All @@ -745,12 +751,15 @@ func (c *Config) destAbsPathInfos(sourceState *chezmoi.SourceState, args []strin
if _, err := destAbsPath.TrimDirPrefix(c.DestDirAbsPath); err != nil {
return nil, err
}
if recursive {
if options.recursive {
if err := chezmoi.Walk(c.destSystem, destAbsPath, func(destAbsPath chezmoi.AbsPath, info fs.FileInfo, err error) error {
if err != nil {
switch {
case options.ignoreNotExist && errors.Is(err, fs.ErrNotExist):
return nil
case err != nil:
return err
}
if follow && info.Mode().Type() == fs.ModeSymlink {
if options.follow && info.Mode().Type() == fs.ModeSymlink {
info, err = c.destSystem.Stat(destAbsPath)
if err != nil {
return err
Expand All @@ -762,12 +771,15 @@ func (c *Config) destAbsPathInfos(sourceState *chezmoi.SourceState, args []strin
}
} else {
var info fs.FileInfo
if follow {
if options.follow {
info, err = c.destSystem.Stat(destAbsPath)
} else {
info, err = c.destSystem.Lstat(destAbsPath)
}
if err != nil {
switch {
case options.ignoreNotExist && errors.Is(err, fs.ErrNotExist):
continue
case err != nil:
return nil, err
}
if err := sourceState.AddDestAbsPathInfos(destAbsPathInfos, c.destSystem, destAbsPath, info); err != nil {
Expand Down Expand Up @@ -1039,7 +1051,7 @@ func (c *Config) newRootCmd() (*cobra.Command, error) {
}

rootCmd.SetHelpCommand(c.newHelpCmd())
rootCmd.AddCommand(
for _, cmd := range []*cobra.Command{
c.newAddCmd(),
c.newApplyCmd(),
c.newArchiveCmd(),
Expand All @@ -1060,6 +1072,7 @@ func (c *Config) newRootCmd() (*cobra.Command, error) {
c.newImportCmd(),
c.newInitCmd(),
c.newInternalTestCmd(),
c.newMackupCmd(),
c.newManagedCmd(),
c.newMergeCmd(),
c.newPurgeCmd(),
Expand All @@ -1073,7 +1086,11 @@ func (c *Config) newRootCmd() (*cobra.Command, error) {
c.newUpdateCmd(),
c.newUpgradeCmd(),
c.newVerifyCmd(),
)
} {
if cmd != nil {
rootCmd.AddCommand(cmd)
}
}

return rootCmd, nil
}
Expand Down
163 changes: 163 additions & 0 deletions internal/cmd/mackupcmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package cmd

import (
"bufio"
"bytes"
"fmt"
"os/exec"
"regexp"
"runtime"
"strings"

"github.com/spf13/cobra"

"github.com/twpayne/chezmoi/v2/internal/chezmoi"
)

var (
mackupCommentRx = regexp.MustCompile(`\A#.*\z`)
mackupKeyValueRx = regexp.MustCompile(`\A(\w+)\s*=\s*(.*)\z`)
mackupSectionRx = regexp.MustCompile(`\A\[(.*)\]\z`)
mackupVersionRx = regexp.MustCompile(`\AMackup\s+(\d+\.\d+\.\d+)\s*\z`)
)

type mackupApplicationApplicationConfig struct {
Name string
}

type mackupApplicationConfig struct {
Application mackupApplicationApplicationConfig
ConfigurationFiles []chezmoi.RelPath
XDGConfigurationFiles []chezmoi.RelPath
}

func (c *Config) newMackupCmd() *cobra.Command {
if runtime.GOOS != "darwin" {
return nil
}

mackupCmd := &cobra.Command{
Use: "mackup",
Short: "Interact with Mackup",
Hidden: true,
}

mackupAddCmd := &cobra.Command{
Use: "add application...",
Short: "Add an application's configuration from its Mackup configuration",
Args: cobra.MinimumNArgs(1),
RunE: c.makeRunEWithSourceState(c.runMackupAddCmd),
Annotations: map[string]string{
modifiesSourceDirectory: "true",
persistentStateMode: persistentStateModeReadWrite,
requiresSourceDirectory: "true",
},
}
mackupCmd.AddCommand(mackupAddCmd)

// FIXME add other subcommands like
// mackup list
// mackup forget

return mackupCmd
}

func (c *Config) runMackupAddCmd(cmd *cobra.Command, args []string, sourceState *chezmoi.SourceState) error {
mackupApplicationsDir, err := c.mackupApplicationsDir()
if err != nil {
return err
}

var addArgs []string
for _, arg := range args {
data, err := c.baseSystem.ReadFile(mackupApplicationsDir.Join(chezmoi.RelPath(arg + ".cfg")))
if err != nil {
return err
}
config, err := parseMackupApplication(data)
if err != nil {
return err
}
for _, filename := range config.ConfigurationFiles {
addArg := c.DestDirAbsPath.Join(filename)
addArgs = append(addArgs, addArg.String())
}
configHomeAbsPath := chezmoi.AbsPath(c.bds.ConfigHome)
for _, filename := range config.XDGConfigurationFiles {
addArg := configHomeAbsPath.Join(filename)
addArgs = append(addArgs, addArg.String())
}
}

destAbsPathInfos, err := c.destAbsPathInfos(sourceState, addArgs, destAbsPathInfosOptions{
follow: c.Add.follow,
ignoreNotExist: true,
recursive: c.Add.recursive,
})
if err != nil {
return err
}

return sourceState.Add(c.sourceSystem, c.persistentState, c.destSystem, destAbsPathInfos, &chezmoi.AddOptions{
Include: chezmoi.NewEntryTypeSet(chezmoi.EntryTypesAll),
})
}

func (c *Config) mackupApplicationsDir() (chezmoi.AbsPath, error) {
brewPrefixCmd := exec.Command("brew", "--prefix")
brewPrefixData, err := c.baseSystem.IdempotentCmdOutput(brewPrefixCmd)
if err != nil {
return "", err
}
brewPrefix := chezmoi.AbsPath(strings.TrimRight(string(brewPrefixData), "\n"))

mackupVersionCmd := exec.Command("mackup", "--version")
mackupVersionData, err := c.baseSystem.IdempotentCmdCombinedOutput(mackupVersionCmd)
if err != nil {
return "", err
}
m := mackupVersionRx.FindSubmatch(mackupVersionData)
if m == nil {
return "", fmt.Errorf("%q: cannot determine Mackup version", mackupVersionData)
}
mackupVersion := string(m[1])

// FIXME the following line is not robust. It assumes that Python 3.9 is
// being used. Replace it with something more robust.
return brewPrefix.Join("Cellar", "mackup", chezmoi.RelPath(mackupVersion), "libexec", "lib", "python3.9", "site-packages", "mackup", "applications"), nil
}

func parseMackupApplication(data []byte) (mackupApplicationConfig, error) {
var config mackupApplicationConfig
var section string
s := bufio.NewScanner(bytes.NewReader(data))
for s.Scan() {
text := s.Text()
if mackupCommentRx.MatchString(text) {
continue
}
if m := mackupSectionRx.FindStringSubmatch(s.Text()); m != nil {
section = m[1]
continue
}
text = strings.TrimSpace(text)
if text == "" {
continue
}
//nolint:gocritic
switch section {
case "application":
if m := mackupKeyValueRx.FindStringSubmatch(text); m != nil {
switch m[1] {
case "name":
config.Application.Name = m[2]
}
}
case "configuration_files":
config.ConfigurationFiles = append(config.ConfigurationFiles, chezmoi.RelPath(text))
case "xdg_configuration_files":
config.XDGConfigurationFiles = append(config.XDGConfigurationFiles, chezmoi.RelPath(text))
}
}
return config, s.Err()
}
78 changes: 78 additions & 0 deletions internal/cmd/mackupcmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package cmd

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/twpayne/chezmoi/v2/internal/chezmoi"
"github.com/twpayne/chezmoi/v2/internal/chezmoitest"
)

func TestParseMackupApplication(t *testing.T) {
for _, tc := range []struct {
name string
lines []string
expected mackupApplicationConfig
}{
{
name: "curl.cfg",
lines: []string{
"[application]",
"name = Curl",
"",
"[configuration_files]",
".netrc",
".curlrc",
},
expected: mackupApplicationConfig{
Application: mackupApplicationApplicationConfig{
Name: "Curl",
},
ConfigurationFiles: []chezmoi.RelPath{
".netrc",
".curlrc",
},
},
},
{
name: "vscode.cfg",
lines: []string{
"[application]",
"name = Visual Studio Code",
"",
"[configuration_files]",
"Library/Application Support/Code/User/snippets",
"Library/Application Support/Code/User/keybindings.json",
"Library/Application Support/Code/User/settings.json",
"",
"[xdg_configuration_files]",
"Code/User/snippets",
"Code/User/keybindings.json",
"Code/User/settings.json",
},
expected: mackupApplicationConfig{
Application: mackupApplicationApplicationConfig{
Name: "Visual Studio Code",
},
ConfigurationFiles: []chezmoi.RelPath{
"Library/Application Support/Code/User/snippets",
"Library/Application Support/Code/User/keybindings.json",
"Library/Application Support/Code/User/settings.json",
},
XDGConfigurationFiles: []chezmoi.RelPath{
"Code/User/snippets",
"Code/User/keybindings.json",
"Code/User/settings.json",
},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
actual, err := parseMackupApplication([]byte(chezmoitest.JoinLines(tc.lines...)))
require.NoError(t, err)
assert.Equal(t, tc.expected, actual)
})
}
}
9 changes: 4 additions & 5 deletions internal/cmd/noupgradecmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
package cmd

import (
"fmt"
"runtime"

"github.com/spf13/cobra"
)

func (c *Config) runUpgradeCmd(cmd *cobra.Command, args []string) error {
return fmt.Errorf("%s: unsupported GOOS", runtime.GOOS)
type upgradeCmdConfig struct{}

func (c *Config) newUpgradeCmd() *cobra.Command {
return nil
}

0 comments on commit a4eeff4

Please sign in to comment.