Skip to content

Commit

Permalink
Add script support
Browse files Browse the repository at this point in the history
  • Loading branch information
twpayne committed May 12, 2019
1 parent 8bd7bc9 commit d75c32b
Show file tree
Hide file tree
Showing 8 changed files with 466 additions and 75 deletions.
108 changes: 108 additions & 0 deletions cmd/apply_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package cmd

import (
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/twpayne/chezmoi/lib/chezmoi"
"github.com/twpayne/go-vfs/vfst"
)

Expand Down Expand Up @@ -106,3 +109,108 @@ func TestApplyCommand(t *testing.T) {
})
}
}

func TestApplyScript(t *testing.T) {
tempDir, err := ioutil.TempDir("", "chezmoi")
require.NoError(t, err)
defer func() {
require.NoError(t, os.RemoveAll(tempDir))
}()
for _, tc := range []struct {
name string
root interface{}
data map[string]interface{}
evidence string
}{
{
name: "simple",
root: map[string]interface{}{
"/home/user/.local/share/chezmoi/run_true": "#!/bin/sh\ntouch " + filepath.Join(tempDir, "simple") + "\n",
},
evidence: "simple",
},
{
name: "simple_once",
root: map[string]interface{}{
"/home/user/.local/share/chezmoi/run_once_true": "#!/bin/sh\ntouch " + filepath.Join(tempDir, "simple_once") + "\n",
},
evidence: "simple_once",
},
{
name: "template",
root: map[string]interface{}{
"/home/user/.local/share/chezmoi/run_true.tmpl": "#!/bin/sh\ntouch {{ .Evidence }}\n",
},
data: map[string]interface{}{
"Evidence": filepath.Join(tempDir, "template"),
},
evidence: "template",
},
} {
t.Run(tc.name, func(t *testing.T) {
fs, cleanup, err := vfst.NewTestFS(tc.root)
require.NoError(t, err)
defer cleanup()
persistentState, err := chezmoi.NewBoltPersistentState(fs, "/home/user/.config/chezmoi/chezmoistate.boltdb")
require.NoError(t, err)
c := &Config{
SourceDir: "/home/user/.local/share/chezmoi",
DestDir: "/",
Umask: 022,
Data: tc.data,
persistentState: persistentState,
}
assert.NoError(t, c.runApplyCmd(fs, nil))
evidencePath := filepath.Join(tempDir, tc.evidence)
_, err = os.Stat(evidencePath)
assert.NoError(t, err)
assert.NoError(t, os.Remove(evidencePath))
})
}
}

func TestApplyRunOnce(t *testing.T) {
statePath := "/home/user/.config/chezmoi/chezmoistate.boltdb"

tempDir, err := ioutil.TempDir("", "chezmoi")
require.NoError(t, err)
defer func() {
require.NoError(t, os.RemoveAll(tempDir))
}()
tempFile := filepath.Join(tempDir, "foo")

fs, cleanup, err := vfst.NewTestFS(map[string]interface{}{
filepath.Dir(statePath): &vfst.Dir{Perm: 0755},
"/home/user/.local/share/chezmoi/run_once_foo.tmpl": "#!/bin/sh\necho bar >> {{ .TempFile }}\n",
})
require.NoError(t, err)
defer cleanup()

persistentState, err := chezmoi.NewBoltPersistentState(fs, statePath)
require.NoError(t, err)

c := &Config{
SourceDir: "/home/user/.local/share/chezmoi",
DestDir: "/",
Umask: 022,
Data: map[string]interface{}{
"TempFile": tempFile,
},
persistentState: persistentState,
}

require.NoError(t, c.runApplyCmd(fs, nil))
vfst.RunTests(t, fs, "",
vfst.TestPath(statePath,
vfst.TestModeIsRegular,
),
)
actualData, err := ioutil.ReadFile(tempFile)
require.NoError(t, err)
assert.Equal(t, []byte("bar\n"), actualData)

require.NoError(t, c.runApplyCmd(fs, nil))
actualData, err = ioutil.ReadFile(tempFile)
require.NoError(t, err)
assert.Equal(t, []byte("bar\n"), actualData)
}
95 changes: 55 additions & 40 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,39 +33,40 @@ type sourceVCSConfig struct {

// A Config represents a configuration.
type Config struct {
configFile string
err error
SourceDir string
DestDir string
Umask permValue
DryRun bool
Verbose bool
Color string
GPGRecipient string
SourceVCS sourceVCSConfig
Merge mergeConfig
Bitwarden bitwardenCmdConfig
GenericSecret genericSecretCmdConfig
Lastpass lastpassCmdConfig
Onepassword onepasswordCmdConfig
Vault vaultCmdConfig
Pass passCmdConfig
Data map[string]interface{}
colored bool
templateFuncs template.FuncMap
add addCmdConfig
data dataCmdConfig
dump dumpCmdConfig
edit editCmdConfig
init initCmdConfig
_import importCmdConfig
keyring keyringCmdConfig
update updateCmdConfig
upgrade upgradeCmdConfig
stdin io.Reader
stdout io.Writer
stderr io.Writer
bds *xdg.BaseDirectorySpecification
configFile string
err error
SourceDir string
DestDir string
Umask permValue
DryRun bool
Verbose bool
Color string
GPGRecipient string
SourceVCS sourceVCSConfig
Merge mergeConfig
Bitwarden bitwardenCmdConfig
GenericSecret genericSecretCmdConfig
Lastpass lastpassCmdConfig
Onepassword onepasswordCmdConfig
Vault vaultCmdConfig
Pass passCmdConfig
Data map[string]interface{}
colored bool
persistentState chezmoi.PersistentState
templateFuncs template.FuncMap
add addCmdConfig
data dataCmdConfig
dump dumpCmdConfig
edit editCmdConfig
init initCmdConfig
_import importCmdConfig
keyring keyringCmdConfig
update updateCmdConfig
upgrade upgradeCmdConfig
stdin io.Reader
stdout io.Writer
stderr io.Writer
bds *xdg.BaseDirectorySpecification
}

var (
Expand Down Expand Up @@ -130,20 +131,24 @@ func (c *Config) applyArgs(fs vfs.FS, args []string, mutator chezmoi.Mutator) er
if err != nil {
return err
}
applyOptions := &chezmoi.ApplyOptions{
DestDir: ts.DestDir,
DryRun: c.DryRun,
Ignore: ts.TargetIgnore.Match,
PersistentState: c.persistentState,
Stdout: c.Stdout(),
Umask: ts.Umask,
Verbose: c.Verbose,
}
if len(args) == 0 {
return ts.Apply(fs, mutator)
return ts.Apply(fs, mutator, applyOptions)
}
entries, err := c.getEntries(fs, ts, args)
if err != nil {
return err
}
applyOptions := chezmoi.ApplyOptions{
DestDir: ts.DestDir,
Ignore: ts.TargetIgnore.Match,
Umask: ts.Umask,
}
for _, entry := range entries {
if err := entry.Apply(fs, mutator, &applyOptions); err != nil {
if err := entry.Apply(fs, mutator, applyOptions); err != nil {
return err
}
}
Expand Down Expand Up @@ -391,6 +396,16 @@ func getDefaultSourceDir(bds *xdg.BaseDirectorySpecification) string {
return filepath.Join(bds.DataHome, "chezmoi")
}

func getPersistentStateFile(bds *xdg.BaseDirectorySpecification, configFile string) string {
for _, configDir := range bds.ConfigDirs {
persistentStateFile := filepath.Join(configDir, "chezmoi", "chezmoistate.boltdb")
if _, err := os.Stat(persistentStateFile); err == nil {
return persistentStateFile
}
}
return filepath.Join(filepath.Dir(configFile), "chezmoistate.boltdb")
}

// isWellKnownAbbreviation returns true if word is a well known abbreviation.
func isWellKnownAbbreviation(word string) bool {
_, ok := wellKnownAbbreviations[word]
Expand Down
1 change: 1 addition & 0 deletions cmd/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ func (c *Config) runEditCmd(fs vfs.FS, args []string) error {
DestDir: ts.DestDir,
DryRun: c.DryRun,
Ignore: ts.TargetIgnore.Match,
Stdout: c.Stdout(),
Umask: ts.Umask,
Verbose: c.Verbose,
}
Expand Down
7 changes: 7 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/mattn/go-isatty"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/twpayne/chezmoi/lib/chezmoi"
vfs "github.com/twpayne/go-vfs"
xdg "github.com/twpayne/go-xdg/v3"
)
Expand Down Expand Up @@ -94,6 +95,12 @@ func init() {
if config.err != nil {
config.warn(fmt.Sprintf("%s: %v", config.configFile, config.err))
}
persistentStateFile := getPersistentStateFile(config.bds, config.configFile)
persistentState, err := chezmoi.NewBoltPersistentState(vfs.OSFS, persistentStateFile)
if err != nil {
printErrorAndExit(err)
}
config.persistentState = persistentState
})
}

Expand Down
23 changes: 18 additions & 5 deletions lib/chezmoi/chezmoi.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package chezmoi
import (
"archive/tar"
"bytes"
"io"
"os"
"path/filepath"
"sort"
Expand All @@ -13,13 +14,15 @@ import (

// Suffixes and prefixes.
const (
symlinkPrefix = "symlink_"
privatePrefix = "private_"
dotPrefix = "dot_"
emptyPrefix = "empty_"
encryptedPrefix = "encrypted_"
exactPrefix = "exact_"
executablePrefix = "executable_"
dotPrefix = "dot_"
oncePrefix = "once_"
privatePrefix = "private_"
runPrefix = "run_"
symlinkPrefix = "symlink_"
TemplateSuffix = ".tmpl"
)

Expand All @@ -42,6 +45,7 @@ type ApplyOptions struct {
DryRun bool
Ignore func(string) bool
PersistentState PersistentState
Stdout io.Writer
Umask os.FileMode
Verbose bool
}
Expand All @@ -57,8 +61,9 @@ type Entry interface {
}

type parsedSourceFilePath struct {
FileAttributes
dirAttributes []DirAttributes
dirAttributes []DirAttributes
fileAttributes *FileAttributes
scriptAttributes *ScriptAttributes
}

// ReturnTemplateFuncError causes template execution to return an error.
Expand Down Expand Up @@ -96,6 +101,14 @@ func parseDirNameComponents(components []string) []DirAttributes {
func parseSourceFilePath(path string) parsedSourceFilePath {
components := splitPathList(path)
das := parseDirNameComponents(components[0 : len(components)-1])
sourceName := components[len(components)-1]
if strings.HasPrefix(sourceName, runPrefix) {
sa := ParseScriptAttributes(sourceName)
return parsedSourceFilePath{
dirAttributes: das,
scriptAttributes: &sa,
}
}
fa := ParseFileAttributes(components[len(components)-1])
return parsedSourceFilePath{
dirAttributes: das,
Expand Down
Loading

0 comments on commit d75c32b

Please sign in to comment.