Closed arran4 closed 1 year ago
Two threads, maybe not solving this issue:
I am doing something similar to: https://github.com/twpayne/dotfiles/blob/f25b14407ec63b79b8c84a3a3159431fc4c02c24/home/dot_zshrc.tmpl#L9-L19
That's exactly what I imagined it to look like:
My issue is one step further; I would like to pick tools based on what's now available in paths. For instance installing on Solaris often puts tools in /opt/
which wouldn't be picked up with the current lookPath
once I have modified path..
So:
{{- $paths := list }}
{{- $homeDir := .chezmoi.homeDir }}
{{- range $_, $relPath := list "bin" "go/bin" ".cargo/bin" ".local/bin" }}
{{ $path := joinPath $homeDir $relPath }}
{{- if stat $path }}
{{- $paths = mustAppend $paths $path }}
{{- end }}
{{- end }}
{{- if $paths }}
export PATH={{ toStrings $paths | join ":" }}:$PATH
{{- end }}
{{ if lookPath "diff-so-fancy" $paths }}
# diff-so-fancy is in future $PATH
export DIFFTOOL=diff-so-fancy
{{ end }}
Is more to the goal.
So perhaps:
package chezmoi
import (
"filepath"
"os"
"os/exec"
"sync"
)
var (
lookPathCacheMutex sync.Mutex
lookPathCache = make(map[string]string)
foundExecutableCacheMutex sync.Mutex
foundExecutableCache = make(map[string]struct{})
)
// LookPath is like os/exec.LookPath except that the first positive result is
// cached.
func LookPath(file string) (string, error) {
lookPathCacheMutex.Lock()
defer lookPathCacheMutex.Unlock()
if path, ok := lookPathCache[file]; ok {
return path, nil
}
path, err := exec.LookPath(file)
if err == nil {
lookPathCache[file] = path
}
return path, err
}
func LookPathIn(file string, paths string) (string, error) {
foundExecutableCacheMutex.Lock()
defer foundExecutableCacheMutex.Unlock()
// stolen from: /usr/lib/go-1.20/src/os/exec/lp_unix.go:52
for _, dir := range filepath.SplitList(paths) {
if dir == "" {
continue
}
path := filepath.Join(dir, file)
if err := findExecutable(path); err == nil {
if !filepath.IsAbs(path) && execerrdot.Value() != "0" {
return path, &Error{file, os.ErrDot}
}
return path, nil
}
}
return path, err
}
// stolen from: /usr/lib/go-1.20/src/os/exec/lp_unix.go:52
func findExecutable(file string) error {
if path, ok := foundExecutableCache[file]; ok {
return file, nil
}
d, err := os.Stat(file)
if err != nil {
return err
}
m := d.Mode()
if m.IsDir() {
return syscall.EISDIR
}
err = unix.Eaccess(file, unix.X_OK)
// ENOSYS means Eaccess is not available or not implemented.
// EPERM can be returned by Linux containers employing seccomp.
// In both cases, fall back to checking the permission bits.
if err == nil || (err != syscall.ENOSYS && err != syscall.EPERM) {
return err
}
if m&0111 != 0 {
return nil
}
return fs.ErrPermission
}
Sorry I did this in the comments there are probably major issues with this example
I think that, as a separate function, this could be quite useful. Without this, I would need to do something like:
$ # run the commands to install bun, which installs in ~/.bun/bin/bun
$ chezmoi apply
# things apply without knowing that bun is present
$ exec $SHELL
# my shell config has bun file detection, and this is a fast way to replace the current shell with an updated shell
$ chezmoi apply
# things apply knowing that bun is present
With this, it would be more like:
$ # run the commands to install bun
$ chezmoi apply
# things apply knowing that bun will be present on next shell start
$ exec $SHELL
@arran4, thanks, this makes sense :)
Would you mind converting your comment in to a pull request?
https://github.com/twpayne/chezmoi/pull/2833/files should give you a complete picture of which files need to be modified when adding a new template function.
@twpayne Done: https://github.com/twpayne/chezmoi/pull/3148 It was a bit more extensive than I expected.
Fixed by #3157 (and also #3152, #3153, and #3156 on the way).
@twpayne Should I continue with this?
If you need the functionality, then please do (and re-open this issue). I think it's possible to do within the current templating functionality, something like (warning: untested):
{{ $haveEcho := false }}
{{ $paths := list "/my/path1" "/my/path2" }}
{{ range $_, $path := $paths }}
{{ if (joinPath $path "echo" | isExecutable) }}
{{ $haveEcho = true }}
{{ end }}
{{ end }}
{{ if $haveEcho }}
# echo already found in path
{{ end }}
It's more of a want, however it would also take time for me to adapt the changes into my disorganized dotfiles. I think the push back warrants a different name at least.
Is your feature request related to a problem? Please describe.
As part of my dotfiles script I'm building up a list of paths that will replace the existing paths as a result I want not just the "current state" of paths, but if something is found in the paths that I'm building up. One way to do this would be to provide an optional argument to
lookPath
where I could specify the value of$PATH
that I'm building upDescribe the solution you'd like
Would work along with:
Describe alternatives you've considered
I guess I could loop through it and use
stat
to build up a map of what's in there for a quick lookup later. But it seems slower.