From b9dcd4820093085a1f84e170af94433e7717a2e4 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Wed, 21 Apr 2021 01:20:16 +0200 Subject: [PATCH] Tidy up path handling --- cmd/applycmd.go | 2 +- cmd/catcmd.go | 2 +- cmd/cdcmd.go | 2 +- cmd/chattrcmd.go | 8 +- cmd/config.go | 222 +++++++++++++++++++----------- cmd/config_test.go | 18 ++- cmd/diffcmd.go | 4 +- cmd/doctorcmd.go | 6 +- cmd/editcmd.go | 10 +- cmd/gitcmd.go | 2 +- cmd/importcmd.go | 12 +- cmd/initcmd.go | 8 +- cmd/managedcmd.go | 2 +- cmd/mergecmd.go | 8 +- cmd/removecmd.go | 4 +- cmd/sourcepathcmd.go | 2 +- cmd/statecmd.go | 15 +- cmd/statuscmd.go | 2 +- cmd/templatefuncs.go | 2 +- cmd/unmanagedcmd.go | 6 +- cmd/updatecmd.go | 6 +- cmd/util.go | 37 ----- cmd/verifycmd.go | 2 +- internal/chezmoi/ageencryption.go | 8 +- internal/chezmoi/path.go | 59 ++++++++ testdata/scripts/options.txt | 27 ++++ 26 files changed, 297 insertions(+), 179 deletions(-) create mode 100644 testdata/scripts/options.txt diff --git a/cmd/applycmd.go b/cmd/applycmd.go index d158609675c..391bf471b0d 100644 --- a/cmd/applycmd.go +++ b/cmd/applycmd.go @@ -34,7 +34,7 @@ func (c *Config) newApplyCmd() *cobra.Command { } func (c *Config) runApplyCmd(cmd *cobra.Command, args []string) error { - return c.applyArgs(c.destSystem, c.destDirAbsPath, args, applyArgsOptions{ + return c.applyArgs(c.destSystem, c.DestDirAbsPath, args, applyArgsOptions{ include: c.apply.include.Sub(c.apply.exclude), recursive: c.apply.recursive, umask: c.Umask, diff --git a/cmd/catcmd.go b/cmd/catcmd.go index efa04811565..075777fb56e 100644 --- a/cmd/catcmd.go +++ b/cmd/catcmd.go @@ -32,7 +32,7 @@ func (c *Config) runCatCmd(cmd *cobra.Command, args []string, sourceState *chezm sb := strings.Builder{} for _, targetRelPath := range targetRelPaths { - targetStateEntry, err := sourceState.MustEntry(targetRelPath).TargetStateEntry(c.destSystem, c.destDirAbsPath.Join(targetRelPath)) + targetStateEntry, err := sourceState.MustEntry(targetRelPath).TargetStateEntry(c.destSystem, c.DestDirAbsPath.Join(targetRelPath)) if err != nil { return fmt.Errorf("%s: %w", targetRelPath, err) } diff --git a/cmd/cdcmd.go b/cmd/cdcmd.go index d2ce5349c90..0452d1b70ef 100644 --- a/cmd/cdcmd.go +++ b/cmd/cdcmd.go @@ -33,5 +33,5 @@ func (c *Config) runCDCmd(cmd *cobra.Command, args []string) error { if shellCommand == "" { shellCommand, _ = shell.CurrentUserShell() } - return c.run(c.sourceDirAbsPath, shellCommand, c.CD.Args) + return c.run(c.SourceDirAbsPath, shellCommand, c.CD.Args) } diff --git a/cmd/chattrcmd.go b/cmd/chattrcmd.go index e54db2c0de6..616fe7a8a04 100644 --- a/cmd/chattrcmd.go +++ b/cmd/chattrcmd.go @@ -101,8 +101,8 @@ func (c *Config) runChattrCmd(cmd *cobra.Command, args []string, sourceState *ch switch sourceStateEntry := sourceStateEntry.(type) { case *chezmoi.SourceStateDir: if newBaseNameRelPath := chezmoi.RelPath(am.modifyDirAttr(sourceStateEntry.Attr).SourceName()); newBaseNameRelPath != fileRelPath { - oldSourceAbsPath := c.sourceDirAbsPath.Join(parentRelPath, fileRelPath) - newSourceAbsPath := c.sourceDirAbsPath.Join(parentRelPath, newBaseNameRelPath) + oldSourceAbsPath := c.SourceDirAbsPath.Join(parentRelPath, fileRelPath) + newSourceAbsPath := c.SourceDirAbsPath.Join(parentRelPath, newBaseNameRelPath) if err := c.sourceSystem.Rename(oldSourceAbsPath, newSourceAbsPath); err != nil { return err } @@ -111,8 +111,8 @@ func (c *Config) runChattrCmd(cmd *cobra.Command, args []string, sourceState *ch // FIXME encrypted attribute changes // FIXME when changing encrypted attribute add new file before removing old one if newBaseNameRelPath := chezmoi.RelPath(am.modifyFileAttr(sourceStateEntry.Attr).SourceName(encryptedSuffix)); newBaseNameRelPath != fileRelPath { - oldSourceAbsPath := c.sourceDirAbsPath.Join(parentRelPath, fileRelPath) - newSourceAbsPath := c.sourceDirAbsPath.Join(parentRelPath, newBaseNameRelPath) + oldSourceAbsPath := c.SourceDirAbsPath.Join(parentRelPath, fileRelPath) + newSourceAbsPath := c.SourceDirAbsPath.Join(parentRelPath, newBaseNameRelPath) if err := c.sourceSystem.Rename(oldSourceAbsPath, newSourceAbsPath); err != nil { return err } diff --git a/cmd/config.go b/cmd/config.go index 638216ff009..d34faabf89b 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -56,23 +56,23 @@ type Config struct { bds *xdg.BaseDirectorySpecification - fs vfs.FS - configFile string - baseSystem chezmoi.System - sourceSystem chezmoi.System - destSystem chezmoi.System - persistentState chezmoi.PersistentState - color bool + fs vfs.FS + configFileAbsPath chezmoi.AbsPath + baseSystem chezmoi.System + sourceSystem chezmoi.System + destSystem chezmoi.System + persistentState chezmoi.PersistentState + color bool // Global configuration, settable in the config file. - SourceDir string `mapstructure:"sourceDir"` - DestDir string `mapstructure:"destDir"` - Umask os.FileMode `mapstructure:"umask"` - Remove bool `mapstructure:"remove"` - Color string `mapstructure:"color"` - Data map[string]interface{} `mapstructure:"data"` - Template templateConfig `mapstructure:"template"` - UseBuiltinGit string `mapstructure:"useBuiltinGit"` + SourceDirAbsPath chezmoi.AbsPath `mapstructure:"sourceDir"` + DestDirAbsPath chezmoi.AbsPath `mapstructure:"destDir"` + Umask os.FileMode `mapstructure:"umask"` + Remove bool `mapstructure:"remove"` + Color string `mapstructure:"color"` + Data map[string]interface{} `mapstructure:"data"` + Template templateConfig `mapstructure:"template"` + UseBuiltinGit string `mapstructure:"useBuiltinGit"` // Global configuration, not settable in the config file. cpuProfile string @@ -83,7 +83,7 @@ type Config struct { keepGoing bool noPager bool noTTY bool - outputStr string + outputAbsPath chezmoi.AbsPath sourcePath bool verbose bool templateFuncs template.FuncMap @@ -133,11 +133,8 @@ type Config struct { verify verifyCmdConfig // Computed configuration. - configFileAbsPath chezmoi.AbsPath - homeDirAbsPath chezmoi.AbsPath - sourceDirAbsPath chezmoi.AbsPath - destDirAbsPath chezmoi.AbsPath - encryption chezmoi.Encryption + homeDirAbsPath chezmoi.AbsPath + encryption chezmoi.Encryption stdin io.Reader stdout io.Writer @@ -175,6 +172,7 @@ var ( mapstructure.StringToTimeDurationHookFunc(), mapstructure.StringToSliceHookFunc(","), chezmoi.StringSliceToEntryTypeSetHookFunc(), + chezmoi.StringToAbsPathHookFunc(), ), ), } @@ -182,11 +180,11 @@ var ( // newConfig creates a new Config with the given options. func newConfig(options ...configOption) (*Config, error) { - homeDir, err := os.UserHomeDir() + userHomeDir, err := os.UserHomeDir() if err != nil { return nil, err } - normalizedHomeDir, err := chezmoi.NormalizePath(homeDir) + homeDirAbsPath, err := chezmoi.NormalizePath(userHomeDir) if err != nil { return nil, err } @@ -199,8 +197,7 @@ func newConfig(options ...configOption) (*Config, error) { c := &Config{ bds: bds, fs: vfs.OSFS, - homeDir: homeDir, - DestDir: homeDir, + homeDir: userHomeDir, Umask: chezmoi.Umask, Color: "auto", Diff: diffCmdConfig{ @@ -273,8 +270,9 @@ func newConfig(options ...configOption) (*Config, error) { stdinIsATTY: true, }, _import: importCmdConfig{ - exclude: chezmoi.NewEntryTypeSet(chezmoi.EntryTypesNone), - include: chezmoi.NewEntryTypeSet(chezmoi.EntryTypesAll), + destination: homeDirAbsPath, + exclude: chezmoi.NewEntryTypeSet(chezmoi.EntryTypesNone), + include: chezmoi.NewEntryTypeSet(chezmoi.EntryTypesAll), }, init: initCmdConfig{ data: true, @@ -313,7 +311,7 @@ func newConfig(options ...configOption) (*Config, error) { stdout: os.Stdout, stderr: os.Stderr, - homeDirAbsPath: normalizedHomeDir, + homeDirAbsPath: homeDirAbsPath, } for key, value := range map[string]interface{}{ @@ -350,14 +348,20 @@ func newConfig(options ...configOption) (*Config, error) { } } - c.configFile = string(defaultConfigFile(c.fs, c.bds)) - c.SourceDir = string(defaultSourceDir(c.fs, c.bds)) - c.homeDirAbsPath, err = chezmoi.NormalizePath(c.homeDir) if err != nil { return nil, err } - c._import.destination = string(c.homeDirAbsPath) + c.configFileAbsPath, err = c.defaultConfigFile(c.fs, c.bds) + if err != nil { + return nil, err + } + c.SourceDirAbsPath, err = c.defaultSourceDir(c.fs, c.bds) + if err != nil { + return nil, err + } + c.DestDirAbsPath = c.homeDirAbsPath + c._import.destination = c.homeDirAbsPath return c, nil } @@ -481,6 +485,30 @@ func (c *Config) cmdOutput(dirAbsPath chezmoi.AbsPath, name string, args []strin return c.baseSystem.IdempotentCmdOutput(cmd) } +// defaultConfigFile returns the default config file according to the XDG Base +// Directory Specification. +func (c *Config) defaultConfigFile(fs vfs.Stater, bds *xdg.BaseDirectorySpecification) (chezmoi.AbsPath, error) { + // Search XDG Base Directory Specification config directories first. + for _, configDir := range bds.ConfigDirs { + configDirAbsPath, err := chezmoi.NewAbsPathFromExtPath(configDir, c.homeDirAbsPath) + if err != nil { + return "", err + } + for _, extension := range viper.SupportedExts { + configFileAbsPath := configDirAbsPath.Join(chezmoi.RelPath("chezmoi"), chezmoi.RelPath("chezmoi."+extension)) + if _, err := fs.Stat(string(configFileAbsPath)); err == nil { + return configFileAbsPath, nil + } + } + } + // Fallback to XDG Base Directory Specification default. + configHomeAbsPath, err := chezmoi.NewAbsPathFromExtPath(bds.ConfigHome, c.homeDirAbsPath) + if err != nil { + return "", err + } + return configHomeAbsPath.Join(chezmoi.RelPath("chezmoi"), chezmoi.RelPath("chezmoi.toml")), nil +} + func (c *Config) defaultPreApplyFunc(targetRelPath chezmoi.RelPath, targetEntryState, lastWrittenEntryState, actualEntryState *chezmoi.EntryState) error { switch { case targetEntryState.Type == chezmoi.EntryStateTypeScript: @@ -524,13 +552,35 @@ func (c *Config) defaultPreApplyFunc(targetRelPath chezmoi.RelPath, targetEntryS } } +// defaultSourceDir returns the default source directory according to the XDG +// Base Directory Specification. +func (c *Config) defaultSourceDir(fs vfs.Stater, bds *xdg.BaseDirectorySpecification) (chezmoi.AbsPath, error) { + // Check for XDG Base Directory Specification data directories first. + for _, dataDir := range bds.DataDirs { + dataDirAbsPath, err := chezmoi.NewAbsPathFromExtPath(dataDir, c.homeDirAbsPath) + if err != nil { + return "", err + } + sourceDirAbsPath := dataDirAbsPath.Join(chezmoi.RelPath("chezmoi")) + if _, err := fs.Stat(string(sourceDirAbsPath)); err == nil { + return sourceDirAbsPath, nil + } + } + // Fallback to XDG Base Directory Specification default. + dataHomeAbsPath, err := chezmoi.NewAbsPathFromExtPath(bds.DataHome, c.homeDirAbsPath) + if err != nil { + return "", err + } + return dataHomeAbsPath.Join(chezmoi.RelPath("chezmoi")), nil +} + func (c *Config) defaultTemplateData() map[string]interface{} { data := map[string]interface{}{ "arch": runtime.GOARCH, "homeDir": c.homeDir, "homedir": c.homeDir, // TODO Remove in version 2.1. "os": runtime.GOOS, - "sourceDir": string(c.sourceDirAbsPath), + "sourceDir": string(c.SourceDirAbsPath), "version": map[string]interface{}{ "builtBy": c.versionInfo.BuiltBy, "commit": c.versionInfo.Commit, @@ -635,7 +685,7 @@ func (c *Config) destAbsPathInfos(sourceState *chezmoi.SourceState, args []strin if err != nil { return nil, err } - if _, err := destAbsPath.TrimDirPrefix(c.destDirAbsPath); err != nil { + if _, err := destAbsPath.TrimDirPrefix(c.DestDirAbsPath); err != nil { return nil, err } if recursive { @@ -716,12 +766,15 @@ func (c *Config) doPurge(purgeOptions *purgeOptions) error { } } - absSlashPersistentStateFile := c.persistentStateFile() + persistentStateFileAbsPath, err := c.persistentStateFile() + if err != nil { + return err + } absPaths := chezmoi.AbsPaths{ c.configFileAbsPath.Dir(), c.configFileAbsPath, - absSlashPersistentStateFile, - c.sourceDirAbsPath, + persistentStateFileAbsPath, + c.SourceDirAbsPath, } if purgeOptions != nil && purgeOptions.binary { executable, err := os.Executable() @@ -814,7 +867,7 @@ func (c *Config) execute(args []string) error { func (c *Config) findConfigTemplate() (chezmoi.RelPath, string, []byte, error) { for _, ext := range viper.SupportedExts { filename := chezmoi.RelPath(chezmoi.Prefix + "." + ext + chezmoi.TemplateSuffix) - contents, err := c.baseSystem.ReadFile(c.sourceDirAbsPath.Join(filename)) + contents, err := c.baseSystem.ReadFile(c.SourceDirAbsPath.Join(filename)) switch { case os.IsNotExist(err): continue @@ -827,10 +880,10 @@ func (c *Config) findConfigTemplate() (chezmoi.RelPath, string, []byte, error) { } func (c *Config) gitAutoAdd() (*git.Status, error) { - if err := c.run(c.sourceDirAbsPath, c.Git.Command, []string{"add", "."}); err != nil { + if err := c.run(c.SourceDirAbsPath, c.Git.Command, []string{"add", "."}); err != nil { return nil, err } - output, err := c.cmdOutput(c.sourceDirAbsPath, c.Git.Command, []string{"status", "--porcelain=v2"}) + output, err := c.cmdOutput(c.SourceDirAbsPath, c.Git.Command, []string{"status", "--porcelain=v2"}) if err != nil { return nil, err } @@ -853,14 +906,14 @@ func (c *Config) gitAutoCommit(status *git.Status) error { if err := commitMessageTmpl.Execute(&commitMessage, status); err != nil { return err } - return c.run(c.sourceDirAbsPath, c.Git.Command, []string{"commit", "--message", commitMessage.String()}) + return c.run(c.SourceDirAbsPath, c.Git.Command, []string{"commit", "--message", commitMessage.String()}) } func (c *Config) gitAutoPush(status *git.Status) error { if status.Empty() { return nil } - return c.run(c.sourceDirAbsPath, c.Git.Command, []string{"push"}) + return c.run(c.SourceDirAbsPath, c.Git.Command, []string{"push"}) } func (c *Config) makeRunEWithSourceState(runE func(*cobra.Command, []string, *chezmoi.SourceState) error) func(*cobra.Command, []string) error { @@ -904,9 +957,9 @@ func (c *Config) newRootCmd() (*cobra.Command, error) { persistentFlags := rootCmd.PersistentFlags() persistentFlags.StringVar(&c.Color, "color", c.Color, "colorize diffs") - persistentFlags.StringVarP(&c.DestDir, "destination", "D", c.DestDir, "destination directory") + persistentFlags.VarP(&c.DestDirAbsPath, "destination", "D", "destination directory") persistentFlags.BoolVar(&c.Remove, "remove", c.Remove, "remove targets") - persistentFlags.StringVarP(&c.SourceDir, "source", "S", c.SourceDir, "source directory") + persistentFlags.VarP(&c.SourceDirAbsPath, "source", "S", "source directory") persistentFlags.StringVar(&c.UseBuiltinGit, "use-builtin-git", c.UseBuiltinGit, "use builtin git") for _, key := range []string{ "color", @@ -919,7 +972,7 @@ func (c *Config) newRootCmd() (*cobra.Command, error) { } } - persistentFlags.StringVarP(&c.configFile, "config", "c", c.configFile, "config file") + persistentFlags.VarP(&c.configFileAbsPath, "config", "c", "config file") persistentFlags.StringVar(&c.cpuProfile, "cpu-profile", c.cpuProfile, "write CPU profile to file") persistentFlags.BoolVar(&c.debug, "debug", c.debug, "write debug logs") persistentFlags.BoolVarP(&c.dryRun, "dry-run", "n", c.dryRun, "dry run") @@ -927,9 +980,9 @@ func (c *Config) newRootCmd() (*cobra.Command, error) { persistentFlags.BoolVarP(&c.keepGoing, "keep-going", "k", c.keepGoing, "keep going as far as possible after an error") persistentFlags.BoolVar(&c.noPager, "no-pager", c.noPager, "do not use the pager") persistentFlags.BoolVar(&c.noTTY, "no-tty", c.noTTY, "don't attempt to get a TTY for reading passwords") + persistentFlags.VarP(&c.outputAbsPath, "output", "o", "output file") persistentFlags.BoolVar(&c.sourcePath, "source-path", c.sourcePath, "specify targets by source path") persistentFlags.BoolVarP(&c.verbose, "verbose", "v", c.verbose, "verbose") - persistentFlags.StringVarP(&c.outputStr, "output", "o", c.outputStr, "output file") for _, err := range []error{ rootCmd.MarkPersistentFlagFilename("config"), @@ -1037,17 +1090,11 @@ func (c *Config) persistentPreRunRootE(cmd *cobra.Command, args []string) error } } - var err error - c.configFileAbsPath, err = chezmoi.NewAbsPathFromExtPath(c.configFile, c.homeDirAbsPath) - if err != nil { - return err - } - if err := c.readConfig(); err != nil { if !boolAnnotation(cmd, doesNotRequireValidConfig) { - return fmt.Errorf("invalid config: %s: %w", c.configFile, err) + return fmt.Errorf("invalid config: %s: %w", c.configFileAbsPath, err) } - cmd.Printf("warning: %s: %v\n", c.configFile, err) + cmd.Printf("warning: %s: %v\n", c.configFileAbsPath, err) } if c.Color == "" || strings.ToLower(c.Color) == "auto" { @@ -1070,13 +1117,6 @@ func (c *Config) persistentPreRunRootE(cmd *cobra.Command, args []string) error } } - if c.sourceDirAbsPath, err = chezmoi.NewAbsPathFromExtPath(c.SourceDir, c.homeDirAbsPath); err != nil { - return err - } - if c.destDirAbsPath, err = chezmoi.NewAbsPathFromExtPath(c.DestDir, c.homeDirAbsPath); err != nil { - return err - } - log.Logger = log.Output(zerolog.NewConsoleWriter( func(w *zerolog.ConsoleWriter) { w.Out = c.stderr @@ -1099,16 +1139,22 @@ func (c *Config) persistentPreRunRootE(cmd *cobra.Command, args []string) error case cmd.Annotations[persistentStateMode] == persistentStateModeEmpty: c.persistentState = chezmoi.NewMockPersistentState() case cmd.Annotations[persistentStateMode] == persistentStateModeReadOnly: - persistentStateFile := c.persistentStateFile() - c.persistentState, err = chezmoi.NewBoltPersistentState(c.baseSystem, persistentStateFile, chezmoi.BoltPersistentStateReadOnly) + persistentStateFileAbsPath, err := c.persistentStateFile() + if err != nil { + return err + } + c.persistentState, err = chezmoi.NewBoltPersistentState(c.baseSystem, persistentStateFileAbsPath, chezmoi.BoltPersistentStateReadOnly) if err != nil { return err } case cmd.Annotations[persistentStateMode] == persistentStateModeReadMockWrite: fallthrough case cmd.Annotations[persistentStateMode] == persistentStateModeReadWrite && c.dryRun: - persistentStateFile := c.persistentStateFile() - persistentState, err := chezmoi.NewBoltPersistentState(c.baseSystem, persistentStateFile, chezmoi.BoltPersistentStateReadOnly) + persistentStateFileAbsPath, err := c.persistentStateFile() + if err != nil { + return err + } + persistentState, err := chezmoi.NewBoltPersistentState(c.baseSystem, persistentStateFileAbsPath, chezmoi.BoltPersistentStateReadOnly) if err != nil { return err } @@ -1121,8 +1167,11 @@ func (c *Config) persistentPreRunRootE(cmd *cobra.Command, args []string) error } c.persistentState = dryRunPeristentState case cmd.Annotations[persistentStateMode] == persistentStateModeReadWrite: - persistentStateFile := c.persistentStateFile() - c.persistentState, err = chezmoi.NewBoltPersistentState(c.baseSystem, persistentStateFile, chezmoi.BoltPersistentStateReadWrite) + persistentStateFileAbsPath, err := c.persistentStateFile() + if err != nil { + return err + } + c.persistentState, err = chezmoi.NewBoltPersistentState(c.baseSystem, persistentStateFileAbsPath, chezmoi.BoltPersistentStateReadWrite) if err != nil { return err } @@ -1146,8 +1195,8 @@ func (c *Config) persistentPreRunRootE(cmd *cobra.Command, args []string) error c.destSystem = chezmoi.NewDryRunSystem(c.destSystem) } if c.verbose { - c.sourceSystem = chezmoi.NewGitDiffSystem(c.sourceSystem, c.stdout, c.sourceDirAbsPath, c.color) - c.destSystem = chezmoi.NewGitDiffSystem(c.destSystem, c.stdout, c.destDirAbsPath, c.color) + c.sourceSystem = chezmoi.NewGitDiffSystem(c.sourceSystem, c.stdout, c.SourceDirAbsPath, c.color) + c.destSystem = chezmoi.NewGitDiffSystem(c.destSystem, c.stdout, c.DestDirAbsPath, c.color) } switch c.Encryption { @@ -1180,7 +1229,7 @@ func (c *Config) persistentPreRunRootE(cmd *cobra.Command, args []string) error } if boolAnnotation(cmd, requiresSourceDirectory) { - if err := chezmoi.MkdirAll(c.baseSystem, c.sourceDirAbsPath, 0o777); err != nil { + if err := chezmoi.MkdirAll(c.baseSystem, c.SourceDirAbsPath, 0o777); err != nil { return err } } @@ -1202,18 +1251,25 @@ func (c *Config) persistentPreRunRootE(cmd *cobra.Command, args []string) error return nil } -func (c *Config) persistentStateFile() chezmoi.AbsPath { - if c.configFile != "" { - return chezmoi.AbsPath(c.configFile).Dir().Join(persistentStateFilename) +func (c *Config) persistentStateFile() (chezmoi.AbsPath, error) { + if c.configFileAbsPath != "" { + return c.configFileAbsPath.Dir().Join(persistentStateFilename), nil } for _, configDir := range c.bds.ConfigDirs { - configDirAbsPath := chezmoi.AbsPath(configDir) + configDirAbsPath, err := chezmoi.NewAbsPathFromExtPath(configDir, c.homeDirAbsPath) + if err != nil { + return "", err + } persistentStateFile := configDirAbsPath.Join(chezmoi.RelPath("chezmoi"), persistentStateFilename) if _, err := os.Stat(string(persistentStateFile)); err == nil { - return persistentStateFile + return persistentStateFile, nil } } - return defaultConfigFile(c.fs, c.bds).Dir().Join(persistentStateFilename) + defaultConfigFileAbsPath, err := c.defaultConfigFile(c.fs, c.bds) + if err != nil { + return "", err + } + return defaultConfigFileAbsPath.Dir().Join(persistentStateFilename), nil } func (c *Config) promptChoice(prompt string, choices []string) (string, error) { @@ -1302,7 +1358,7 @@ func (c *Config) sourceAbsPaths(sourceState *chezmoi.SourceState, args []string) } sourceAbsPaths := make(chezmoi.AbsPaths, 0, len(targetRelPaths)) for _, targetRelPath := range targetRelPaths { - sourceAbsPath := c.sourceDirAbsPath.Join(sourceState.MustEntry(targetRelPath).SourceRelPath().RelPath()) + sourceAbsPath := c.SourceDirAbsPath.Join(sourceState.MustEntry(targetRelPath).SourceRelPath().RelPath()) sourceAbsPaths = append(sourceAbsPaths, sourceAbsPath) } return sourceAbsPaths, nil @@ -1311,10 +1367,10 @@ func (c *Config) sourceAbsPaths(sourceState *chezmoi.SourceState, args []string) func (c *Config) sourceState() (*chezmoi.SourceState, error) { s := chezmoi.NewSourceState( chezmoi.WithDefaultTemplateDataFunc(c.defaultTemplateData), - chezmoi.WithDestDir(c.destDirAbsPath), + chezmoi.WithDestDir(c.DestDirAbsPath), chezmoi.WithEncryption(c.encryption), chezmoi.WithPriorityTemplateData(c.Data), - chezmoi.WithSourceDir(c.sourceDirAbsPath), + chezmoi.WithSourceDir(c.SourceDirAbsPath), chezmoi.WithSystem(c.sourceSystem), chezmoi.WithTemplateFuncs(c.templateFuncs), chezmoi.WithTemplateOptions(c.Template.Options), @@ -1343,7 +1399,7 @@ func (c *Config) targetRelPaths(sourceState *chezmoi.SourceState, args []string, if err != nil { return nil, err } - targetRelPath, err := argAbsPath.TrimDirPrefix(c.destDirAbsPath) + targetRelPath, err := argAbsPath.TrimDirPrefix(c.DestDirAbsPath) if err != nil { return nil, err } @@ -1395,7 +1451,7 @@ func (c *Config) targetRelPathsBySourcePath(sourceState *chezmoi.SourceState, ar if err != nil { return nil, err } - sourceRelPath, err := argAbsPath.TrimDirPrefix(c.sourceDirAbsPath) + sourceRelPath, err := argAbsPath.TrimDirPrefix(c.SourceDirAbsPath) if err != nil { return nil, err } @@ -1482,11 +1538,11 @@ func (c *Config) withTerminal(prompt string, f func(terminal) error) error { } func (c *Config) writeOutput(data []byte) error { - if c.outputStr == "" || c.outputStr == "-" { + if c.outputAbsPath == "" || c.outputAbsPath == "-" { _, err := c.stdout.Write(data) return err } - return c.baseSystem.WriteFile(chezmoi.AbsPath(c.outputStr), data, 0o666) + return c.baseSystem.WriteFile(c.outputAbsPath, data, 0o666) } func (c *Config) writeOutputString(data string) error { diff --git a/cmd/config_test.go b/cmd/config_test.go index 2e444f7e07c..a14eb8595e0 100644 --- a/cmd/config_test.go +++ b/cmd/config_test.go @@ -226,14 +226,28 @@ func withTestFS(fs vfs.FS) configOption { func withTestUser(username string) configOption { return func(c *Config) error { + var env string switch runtime.GOOS { + case "plan9": + c.homeDir = "/home/user" + env = "home" case "windows": c.homeDir = `c:\home\user` + env = "USERPROFILE" default: c.homeDir = "/home/user" + env = "HOME" + } + if err := os.Setenv(env, c.homeDir); err != nil { + panic(err) + } + var err error + c.homeDirAbsPath, err = chezmoi.NormalizePath(c.homeDir) + if err != nil { + panic(err) } - c.SourceDir = filepath.Join(c.homeDir, ".local", "share", "chezmoi") - c.DestDir = c.homeDir + c.SourceDirAbsPath = c.homeDirAbsPath.Join(".local", "share", "chezmoi") + c.DestDirAbsPath = c.homeDirAbsPath c.Umask = 0o22 configHome := filepath.Join(c.homeDir, ".config") dataHome := filepath.Join(c.homeDir, ".local", "share") diff --git a/cmd/diffcmd.go b/cmd/diffcmd.go index 251968a61b2..8d28951edd3 100644 --- a/cmd/diffcmd.go +++ b/cmd/diffcmd.go @@ -38,8 +38,8 @@ func (c *Config) newDiffCmd() *cobra.Command { func (c *Config) runDiffCmd(cmd *cobra.Command, args []string) error { sb := strings.Builder{} dryRunSystem := chezmoi.NewDryRunSystem(c.destSystem) - gitDiffSystem := chezmoi.NewGitDiffSystem(dryRunSystem, &sb, c.destDirAbsPath, c.color) - if err := c.applyArgs(gitDiffSystem, c.destDirAbsPath, args, applyArgsOptions{ + gitDiffSystem := chezmoi.NewGitDiffSystem(dryRunSystem, &sb, c.DestDirAbsPath, c.color) + if err := c.applyArgs(gitDiffSystem, c.DestDirAbsPath, args, applyArgsOptions{ include: c.Diff.include.Sub(c.Diff.Exclude), recursive: c.Diff.recursive, umask: c.Umask, diff --git a/cmd/doctorcmd.go b/cmd/doctorcmd.go index 867af10d85f..0b6049a1f1d 100644 --- a/cmd/doctorcmd.go +++ b/cmd/doctorcmd.go @@ -121,14 +121,14 @@ func (c *Config) runDoctorCmd(cmd *cobra.Command, args []string) error { }, &dirCheck{ name: "source-dir", - dirname: string(c.sourceDirAbsPath), + dirname: string(c.SourceDirAbsPath), }, &suspiciousEntriesCheck{ - dirname: string(c.sourceDirAbsPath), + dirname: string(c.SourceDirAbsPath), }, &dirCheck{ name: "dest-dir", - dirname: string(c.destDirAbsPath), + dirname: string(c.DestDirAbsPath), }, &binaryCheck{ name: "shell", diff --git a/cmd/editcmd.go b/cmd/editcmd.go index 617230f2624..a38975195ec 100644 --- a/cmd/editcmd.go +++ b/cmd/editcmd.go @@ -43,11 +43,11 @@ func (c *Config) newEditCmd() *cobra.Command { func (c *Config) runEditCmd(cmd *cobra.Command, args []string, sourceState *chezmoi.SourceState) error { if len(args) == 0 { - if err := c.runEditor([]string{string(c.sourceDirAbsPath)}); err != nil { + if err := c.runEditor([]string{string(c.SourceDirAbsPath)}); err != nil { return err } if c.Edit.apply { - if err := c.applyArgs(c.destSystem, c.destDirAbsPath, noArgs, applyArgsOptions{ + if err := c.applyArgs(c.destSystem, c.DestDirAbsPath, noArgs, applyArgsOptions{ include: c.Edit.include.Sub(c.Edit.exclude), recursive: true, umask: c.Umask, @@ -106,13 +106,13 @@ func (c *Config) runEditCmd(cmd *cobra.Command, args []string, sourceState *chez return err } transparentlyDecryptedFile := transparentlyDecryptedFile{ - sourceAbsPath: c.sourceDirAbsPath.Join(sourceRelPath), + sourceAbsPath: c.SourceDirAbsPath.Join(sourceRelPath), decryptedAbsPath: decryptedAbsPath, } transparentlyDecryptedFiles = append(transparentlyDecryptedFiles, transparentlyDecryptedFile) editorArg = string(decryptedAbsPath) } else { - sourceAbsPath := c.sourceDirAbsPath.Join(sourceRelPath) + sourceAbsPath := c.SourceDirAbsPath.Join(sourceRelPath) editorArg = string(sourceAbsPath) } editorArgs = append(editorArgs, editorArg) @@ -133,7 +133,7 @@ func (c *Config) runEditCmd(cmd *cobra.Command, args []string, sourceState *chez } if c.Edit.apply { - if err := c.applyArgs(c.destSystem, c.destDirAbsPath, args, applyArgsOptions{ + if err := c.applyArgs(c.destSystem, c.DestDirAbsPath, args, applyArgsOptions{ include: c.Edit.include, recursive: false, umask: c.Umask, diff --git a/cmd/gitcmd.go b/cmd/gitcmd.go index e4fd9f8df96..39e9d470f78 100644 --- a/cmd/gitcmd.go +++ b/cmd/gitcmd.go @@ -28,5 +28,5 @@ func (c *Config) newGitCmd() *cobra.Command { } func (c *Config) runGitCmd(cmd *cobra.Command, args []string) error { - return c.run(c.sourceDirAbsPath, c.Git.Command, args) + return c.run(c.SourceDirAbsPath, c.Git.Command, args) } diff --git a/cmd/importcmd.go b/cmd/importcmd.go index 767cfd65f8e..db04dd2d7f0 100644 --- a/cmd/importcmd.go +++ b/cmd/importcmd.go @@ -18,7 +18,7 @@ import ( type importCmdConfig struct { exclude *chezmoi.EntryTypeSet - destination string + destination chezmoi.AbsPath exact bool include *chezmoi.EntryTypeSet removeDestination bool @@ -41,7 +41,7 @@ func (c *Config) newImportCmd() *cobra.Command { } flags := importCmd.Flags() - flags.StringVarP(&c._import.destination, "destination", "d", c._import.destination, "destination prefix") + flags.VarP(&c._import.destination, "destination", "d", "destination prefix") flags.BoolVar(&c._import.exact, "exact", c._import.exact, "import directories exactly") flags.VarP(c._import.exclude, "exclude", "x", "exclude entry types") flags.VarP(c._import.include, "include", "i", "include entry types") @@ -78,12 +78,8 @@ func (c *Config) runImportCmd(cmd *cobra.Command, args []string, sourceState *ch return fmt.Errorf("unknown format: %s", base) } } - rootAbsPath, err := chezmoi.NewAbsPathFromExtPath(c._import.destination, c.homeDirAbsPath) - if err != nil { - return err - } tarReaderSystem, err := chezmoi.NewTARReaderSystem(tar.NewReader(r), chezmoi.TARReaderSystemOptions{ - RootAbsPath: rootAbsPath, + RootAbsPath: c._import.destination, StripComponents: c._import.stripComponents, }) if err != nil { @@ -91,7 +87,7 @@ func (c *Config) runImportCmd(cmd *cobra.Command, args []string, sourceState *ch } var removeDir chezmoi.RelPath if c._import.removeDestination { - removeDir, err = rootAbsPath.TrimDirPrefix(c.destDirAbsPath) + removeDir, err = c._import.destination.TrimDirPrefix(c.DestDirAbsPath) if err != nil { return err } diff --git a/cmd/initcmd.go b/cmd/initcmd.go index deb55ee412f..e8be1aab8e7 100644 --- a/cmd/initcmd.go +++ b/cmd/initcmd.go @@ -105,9 +105,9 @@ func (c *Config) runInitCmd(cmd *cobra.Command, args []string) error { } // If the source repo does not exist then init or clone it. - switch _, err := c.baseSystem.Stat(c.sourceDirAbsPath.Join(chezmoi.RelPath(".git"))); { + switch _, err := c.baseSystem.Stat(c.SourceDirAbsPath.Join(chezmoi.RelPath(".git"))); { case os.IsNotExist(err): - rawSourceDir, err := c.baseSystem.RawPath(c.sourceDirAbsPath) + rawSourceDir, err := c.baseSystem.RawPath(c.SourceDirAbsPath) if err != nil { return err } @@ -123,7 +123,7 @@ func (c *Config) runInitCmd(cmd *cobra.Command, args []string) error { if _, err = git.PlainInit(string(rawSourceDir), isBare); err != nil { return err } - } else if err := c.run(c.sourceDirAbsPath, c.Git.Command, []string{"init"}); err != nil { + } else if err := c.run(c.SourceDirAbsPath, c.Git.Command, []string{"init"}); err != nil { return err } } else { @@ -200,7 +200,7 @@ func (c *Config) runInitCmd(cmd *cobra.Command, args []string) error { // Apply. if c.init.apply { - if err := c.applyArgs(c.destSystem, c.destDirAbsPath, noArgs, applyArgsOptions{ + if err := c.applyArgs(c.destSystem, c.DestDirAbsPath, noArgs, applyArgsOptions{ include: chezmoi.NewEntryTypeSet(chezmoi.EntryTypesAll).Sub(c.init.exclude), recursive: false, umask: c.Umask, diff --git a/cmd/managedcmd.go b/cmd/managedcmd.go index c9b200d6d25..ca10caca441 100644 --- a/cmd/managedcmd.go +++ b/cmd/managedcmd.go @@ -37,7 +37,7 @@ func (c *Config) runManagedCmd(cmd *cobra.Command, args []string, sourceState *c entries := sourceState.Entries() targetRelPaths := make(chezmoi.RelPaths, 0, len(entries)) for targetRelPath, sourceStateEntry := range entries { - targetStateEntry, err := sourceStateEntry.TargetStateEntry(c.destSystem, c.destDirAbsPath.Join(targetRelPath)) + targetStateEntry, err := sourceStateEntry.TargetStateEntry(c.destSystem, c.DestDirAbsPath.Join(targetRelPath)) if err != nil { return err } diff --git a/cmd/mergecmd.go b/cmd/mergecmd.go index 37c9f5fb5e8..3d60caf5fb6 100644 --- a/cmd/mergecmd.go +++ b/cmd/mergecmd.go @@ -56,7 +56,7 @@ func (c *Config) runMergeCmd(cmd *cobra.Command, args []string, sourceState *che // targetStateEntry's contents, which means that we cannot fallback to a // two-way merge if the source state's contents cannot be decrypted or // are an invalid template - targetStateEntry, err := sourceStateEntry.TargetStateEntry(c.destSystem, c.destDirAbsPath.Join(targetRelPath)) + targetStateEntry, err := sourceStateEntry.TargetStateEntry(c.destSystem, c.DestDirAbsPath.Join(targetRelPath)) if err != nil { return fmt.Errorf("%s: %w", targetRelPath, err) } @@ -75,14 +75,14 @@ func (c *Config) runMergeCmd(cmd *cobra.Command, args []string, sourceState *che } args := append( append([]string{}, c.Merge.Args...), - string(c.destDirAbsPath.Join(targetRelPath)), - string(c.sourceDirAbsPath.Join(sourceStateEntry.SourceRelPath().RelPath())), + string(c.DestDirAbsPath.Join(targetRelPath)), + string(c.SourceDirAbsPath.Join(sourceStateEntry.SourceRelPath().RelPath())), string(targetStatePath), ) if err := c.persistentState.Close(); err != nil { return err } - if err := c.run(c.destDirAbsPath, c.Merge.Command, args); err != nil { + if err := c.run(c.DestDirAbsPath, c.Merge.Command, args); err != nil { return fmt.Errorf("%s: %w", targetRelPath, err) } } diff --git a/cmd/removecmd.go b/cmd/removecmd.go index f411b91978f..727ae849aad 100644 --- a/cmd/removecmd.go +++ b/cmd/removecmd.go @@ -36,8 +36,8 @@ func (c *Config) runRemoveCmd(cmd *cobra.Command, args []string, sourceState *ch } for _, targetRelPath := range targetRelPaths { - destAbsPath := c.destDirAbsPath.Join(targetRelPath) - sourceAbsPath := c.sourceDirAbsPath.Join(sourceState.MustEntry(targetRelPath).SourceRelPath().RelPath()) + destAbsPath := c.DestDirAbsPath.Join(targetRelPath) + sourceAbsPath := c.SourceDirAbsPath.Join(sourceState.MustEntry(targetRelPath).SourceRelPath().RelPath()) if !c.force { choice, err := c.promptChoice(fmt.Sprintf("Remove %s and %s", destAbsPath, sourceAbsPath), yesNoAllQuit) if err != nil { diff --git a/cmd/sourcepathcmd.go b/cmd/sourcepathcmd.go index bd5a4caf487..795d6ace7a5 100644 --- a/cmd/sourcepathcmd.go +++ b/cmd/sourcepathcmd.go @@ -23,7 +23,7 @@ func (c *Config) newSourcePathCmd() *cobra.Command { func (c *Config) runSourcePathCmd(cmd *cobra.Command, args []string, sourceState *chezmoi.SourceState) error { if len(args) == 0 { - return c.writeOutputString(string(c.sourceDirAbsPath) + "\n") + return c.writeOutputString(string(c.SourceDirAbsPath) + "\n") } sourceAbsPaths, err := c.sourceAbsPaths(sourceState, args) diff --git a/cmd/statecmd.go b/cmd/statecmd.go index 24f182745b6..0ca4710555c 100644 --- a/cmd/statecmd.go +++ b/cmd/statecmd.go @@ -161,15 +161,18 @@ func (c *Config) runStateGetCmd(cmd *cobra.Command, args []string) error { } func (c *Config) runStateResetCmd(cmd *cobra.Command, args []string) error { - path := c.persistentStateFile() - _, err := c.destSystem.Stat(path) - if os.IsNotExist(err) { + persistentStateFileAbsPath, err := c.persistentStateFile() + if err != nil { + return err + } + switch _, err := c.destSystem.Stat(persistentStateFileAbsPath); { + case os.IsNotExist(err): return nil - } else if err != nil { + case err != nil: return err } if !c.force { - switch choice, err := c.promptChoice(fmt.Sprintf("Remove %s", path), []string{"yes", "no"}); { + switch choice, err := c.promptChoice(fmt.Sprintf("Remove %s", persistentStateFileAbsPath), []string{"yes", "no"}); { case err != nil: return err case choice == "yes": @@ -177,7 +180,7 @@ func (c *Config) runStateResetCmd(cmd *cobra.Command, args []string) error { return nil } } - return c.destSystem.RemoveAll(path) + return c.destSystem.RemoveAll(persistentStateFileAbsPath) } func (c *Config) runStateSetCmd(cmd *cobra.Command, args []string) error { diff --git a/cmd/statuscmd.go b/cmd/statuscmd.go index 2695a968a61..c9faf777ee1 100644 --- a/cmd/statuscmd.go +++ b/cmd/statuscmd.go @@ -56,7 +56,7 @@ func (c *Config) runStatusCmd(cmd *cobra.Command, args []string, sourceState *ch } return chezmoi.Skip } - if err := c.applyArgs(dryRunSystem, c.destDirAbsPath, args, applyArgsOptions{ + if err := c.applyArgs(dryRunSystem, c.DestDirAbsPath, args, applyArgsOptions{ include: c.status.include.Sub(c.status.exclude), recursive: c.status.recursive, umask: c.Umask, diff --git a/cmd/templatefuncs.go b/cmd/templatefuncs.go index 69461da1db0..72bcb53c095 100644 --- a/cmd/templatefuncs.go +++ b/cmd/templatefuncs.go @@ -27,7 +27,7 @@ func (c *Config) includeTemplateFunc(filename string) string { panic(err) } } else { - absPath = c.sourceDirAbsPath.Join(chezmoi.RelPath(filename)) + absPath = c.SourceDirAbsPath.Join(chezmoi.RelPath(filename)) } contents, err := c.fs.ReadFile(string(absPath)) if err != nil { diff --git a/cmd/unmanagedcmd.go b/cmd/unmanagedcmd.go index a91c2c70e32..64b15b9ff12 100644 --- a/cmd/unmanagedcmd.go +++ b/cmd/unmanagedcmd.go @@ -25,14 +25,14 @@ func (c *Config) newUnmanagedCmd() *cobra.Command { func (c *Config) runUnmanagedCmd(cmd *cobra.Command, args []string, sourceState *chezmoi.SourceState) error { sb := strings.Builder{} - if err := chezmoi.Walk(c.destSystem, c.destDirAbsPath, func(destAbsPath chezmoi.AbsPath, info os.FileInfo, err error) error { + if err := chezmoi.Walk(c.destSystem, c.DestDirAbsPath, func(destAbsPath chezmoi.AbsPath, info os.FileInfo, err error) error { if err != nil { return err } - if destAbsPath == c.destDirAbsPath { + if destAbsPath == c.DestDirAbsPath { return nil } - targeRelPath := destAbsPath.MustTrimDirPrefix(c.destDirAbsPath) + targeRelPath := destAbsPath.MustTrimDirPrefix(c.DestDirAbsPath) _, managed := sourceState.Entry(targeRelPath) ignored := sourceState.Ignored(targeRelPath) if !managed && !ignored { diff --git a/cmd/updatecmd.go b/cmd/updatecmd.go index cef1c8eb356..024f124dcc9 100644 --- a/cmd/updatecmd.go +++ b/cmd/updatecmd.go @@ -46,7 +46,7 @@ func (c *Config) runUpdateCmd(cmd *cobra.Command, args []string) error { case err != nil: return err case useBuiltinGit: - rawSourceAbsPath, err := c.baseSystem.RawPath(c.sourceDirAbsPath) + rawSourceAbsPath, err := c.baseSystem.RawPath(c.SourceDirAbsPath) if err != nil { return err } @@ -69,7 +69,7 @@ func (c *Config) runUpdateCmd(cmd *cobra.Command, args []string) error { "--rebase", "--recurse-submodules", } - if err := c.run(c.sourceDirAbsPath, c.Git.Command, args); err != nil { + if err := c.run(c.SourceDirAbsPath, c.Git.Command, args); err != nil { return err } } @@ -78,7 +78,7 @@ func (c *Config) runUpdateCmd(cmd *cobra.Command, args []string) error { return nil } - return c.applyArgs(c.destSystem, c.destDirAbsPath, args, applyArgsOptions{ + return c.applyArgs(c.destSystem, c.DestDirAbsPath, args, applyArgsOptions{ include: c.update.include.Sub(c.update.exclude), recursive: c.update.recursive, umask: c.Umask, diff --git a/cmd/util.go b/cmd/util.go index f25ad52744b..fbe42b5ed0b 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -11,12 +11,7 @@ import ( "unicode" "github.com/google/go-github/v34/github" - "github.com/spf13/viper" - "github.com/twpayne/go-vfs/v2" - "github.com/twpayne/go-xdg/v4" "golang.org/x/oauth2" - - "github.com/twpayne/chezmoi/v2/internal/chezmoi" ) var ( @@ -35,38 +30,6 @@ var ( } ) -// defaultConfigFile returns the default config file according to the XDG Base -// Directory Specification. -func defaultConfigFile(fs vfs.Stater, bds *xdg.BaseDirectorySpecification) chezmoi.AbsPath { - // Search XDG Base Directory Specification config directories first. - for _, configDir := range bds.ConfigDirs { - configDirAbsPath := chezmoi.AbsPath(configDir) - for _, extension := range viper.SupportedExts { - configFileAbsPath := configDirAbsPath.Join(chezmoi.RelPath("chezmoi"), chezmoi.RelPath("chezmoi."+extension)) - if _, err := fs.Stat(string(configFileAbsPath)); err == nil { - return configFileAbsPath - } - } - } - // Fallback to XDG Base Directory Specification default. - return chezmoi.AbsPath(bds.ConfigHome).Join(chezmoi.RelPath("chezmoi"), chezmoi.RelPath("chezmoi.toml")) -} - -// defaultSourceDir returns the default source directory according to the XDG -// Base Directory Specification. -func defaultSourceDir(fs vfs.Stater, bds *xdg.BaseDirectorySpecification) chezmoi.AbsPath { - // Check for XDG Base Directory Specification data directories first. - for _, dataDir := range bds.DataDirs { - dataDirAbsPath := chezmoi.AbsPath(dataDir) - sourceDirAbsPath := dataDirAbsPath.Join(chezmoi.RelPath("chezmoi")) - if _, err := fs.Stat(string(sourceDirAbsPath)); err == nil { - return sourceDirAbsPath - } - } - // Fallback to XDG Base Directory Specification default. - return chezmoi.AbsPath(bds.DataHome).Join(chezmoi.RelPath("chezmoi")) -} - // firstNonEmptyString returns its first non-empty argument, or "" if all // arguments are empty. func firstNonEmptyString(ss ...string) string { diff --git a/cmd/verifycmd.go b/cmd/verifycmd.go index 2eadbdeed48..1cf3de9a229 100644 --- a/cmd/verifycmd.go +++ b/cmd/verifycmd.go @@ -34,7 +34,7 @@ func (c *Config) newVerifyCmd() *cobra.Command { func (c *Config) runVerifyCmd(cmd *cobra.Command, args []string) error { dryRunSystem := chezmoi.NewDryRunSystem(c.destSystem) - if err := c.applyArgs(dryRunSystem, c.destDirAbsPath, args, applyArgsOptions{ + if err := c.applyArgs(dryRunSystem, c.DestDirAbsPath, args, applyArgsOptions{ include: c.verify.include.Sub(c.verify.exclude), recursive: c.verify.recursive, umask: c.Umask, diff --git a/internal/chezmoi/ageencryption.go b/internal/chezmoi/ageencryption.go index f84096739c9..29d7e600df7 100644 --- a/internal/chezmoi/ageencryption.go +++ b/internal/chezmoi/ageencryption.go @@ -19,8 +19,8 @@ type AGEEncryption struct { Identities []string Recipient string Recipients []string - RecipientsFile string - RecipientsFiles []string + RecipientsFile AbsPath + RecipientsFiles []AbsPath Suffix string } @@ -94,10 +94,10 @@ func (e *AGEEncryption) encryptArgs() []string { args = append(args, "--recipient", recipient) } if e.RecipientsFile != "" { - args = append(args, "--recipients-file", e.RecipientsFile) + args = append(args, "--recipients-file", string(e.RecipientsFile)) } for _, recipientsFile := range e.RecipientsFiles { - args = append(args, "--recipients-file", recipientsFile) + args = append(args, "--recipients-file", string(recipientsFile)) } return args } diff --git a/internal/chezmoi/path.go b/internal/chezmoi/path.go index 51a4db94d2c..7caf08e70d0 100644 --- a/internal/chezmoi/path.go +++ b/internal/chezmoi/path.go @@ -2,9 +2,13 @@ package chezmoi import ( "fmt" + "os" "path" "path/filepath" + "reflect" "strings" + + "github.com/mitchellh/mapstructure" ) // An AbsPath is an absolute path. @@ -47,12 +51,30 @@ func (p AbsPath) MustTrimDirPrefix(dirPrefix AbsPath) RelPath { return relPath } +// Set implements github.com/spf13/pflag.Value.Set. +func (p *AbsPath) Set(s string) error { + homeDirAbsPath, err := homeDirAbsPath() + if err != nil { + return err + } + absPath, err := NewAbsPathFromExtPath(s, homeDirAbsPath) + if err != nil { + return err + } + *p = absPath + return nil +} + // Split returns p's directory and file. func (p AbsPath) Split() (AbsPath, RelPath) { dir, file := path.Split(string(p)) return AbsPath(dir), RelPath(file) } +func (p AbsPath) String() string { + return string(p) +} + // TrimDirPrefix trims prefix from p. func (p AbsPath) TrimDirPrefix(dirPrefixAbsPath AbsPath) (RelPath, error) { if !strings.HasPrefix(string(p), string(dirPrefixAbsPath+"/")) { @@ -64,6 +86,11 @@ func (p AbsPath) TrimDirPrefix(dirPrefixAbsPath AbsPath) (RelPath, error) { return RelPath(p[len(dirPrefixAbsPath)+1:]), nil } +// Type implements github.com/spf13/pflag.Value.Type. +func (p AbsPath) Type() string { + return "absolute path" +} + // AbsPaths is a slice of AbsPaths that implements sort.Interface. type AbsPaths []AbsPath @@ -122,3 +149,35 @@ type RelPaths []RelPath func (ps RelPaths) Len() int { return len(ps) } func (ps RelPaths) Less(i, j int) bool { return string(ps[i]) < string(ps[j]) } func (ps RelPaths) Swap(i, j int) { ps[i], ps[j] = ps[j], ps[i] } + +// StringSliceToAbsPathHookFunc is a +// github.com/mitchellh/mapstructure.DecodeHookFunc that parses an AbsPath from +// a string. +func StringToAbsPathHookFunc() mapstructure.DecodeHookFunc { + return func(from, to reflect.Type, data interface{}) (interface{}, error) { + if to != reflect.TypeOf(AbsPath("")) { + return data, nil + } + s, ok := data.(string) + if !ok { + return nil, fmt.Errorf("expected a string, got a %T", data) + } + var absPath AbsPath + if err := absPath.Set(s); err != nil { + return nil, err + } + return absPath, nil + } +} + +func homeDirAbsPath() (AbsPath, error) { + userHomeDir, err := os.UserHomeDir() + if err != nil { + return AbsPath(""), err + } + absPath, err := NormalizePath(userHomeDir) + if err != nil { + return AbsPath(""), err + } + return absPath, nil +} diff --git a/testdata/scripts/options.txt b/testdata/scripts/options.txt new file mode 100644 index 00000000000..d6563dfba8b --- /dev/null +++ b/testdata/scripts/options.txt @@ -0,0 +1,27 @@ +# test that --source flag is respected +chezmoi apply --source=~/.dotfiles +cmp $HOME/.file golden/.file + +# test that --destination flag is respected +chhome home2/user +mkdir tmp +chezmoi apply --destination=$WORK/tmp +cmp tmp/.file golden/.file + +# test that --config flag is respected +chhome home3/user +chezmoi apply --config=$HOME/.chezmoi.toml --debug +cmp $HOME/tmp/.file golden/.file + +-- golden/.file -- +# contents of .file +-- home/user/.dotfiles/dot_file -- +# contents of .file +-- home2/user/.local/share/chezmoi/dot_file -- +# contents of .file +-- home3/user/.chezmoi.toml -- +sourceDir = "~/.dotfiles" +destDir = "~/tmp" +-- home3/user/.dotfiles/dot_file -- +# contents of .file +-- home3/user/tmp/.keep --