179 lines
6.2 KiB
Go
179 lines
6.2 KiB
Go
|
package userdirs
|
||
|
|
||
|
import (
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
)
|
||
|
|
||
|
// Dirs represents a set of directory paths with different purposes.
|
||
|
type Dirs struct {
|
||
|
// ConfigDirs is a list, in preference order, of directory paths to search
|
||
|
// for configuration files.
|
||
|
//
|
||
|
// The list must always contain at least one element, and its first element
|
||
|
// is the directory where any new configuration files should be written.
|
||
|
//
|
||
|
// On some systems, ConfigDirs and DataDirs may overlap, so applications
|
||
|
// which scan the contents of the configuration directories should impose
|
||
|
// some additional filtering to distinguish configuration files from data
|
||
|
// files.
|
||
|
//
|
||
|
// Files placed in ConfigDirs should ideally be things that it would be
|
||
|
// reasonable to share among multiple systems (possibly on different
|
||
|
// platforms, possibly to check into a version control system, etc.
|
||
|
ConfigDirs []string
|
||
|
|
||
|
// DataDirs is a list, in preference order, of directory paths to search for
|
||
|
// data files.
|
||
|
//
|
||
|
// The list must always contain at least one element, and its first element
|
||
|
// is the directory where any new data files should be written.
|
||
|
//
|
||
|
// On some systems, ConfigDirs and DataDirs may overlap, so applications
|
||
|
// which scan the contents of the data directories should impose some
|
||
|
// additional filtering to distinguish data files from configuration files.
|
||
|
DataDirs []string
|
||
|
|
||
|
// CacheDir is the path of a single directory that can be used for temporary
|
||
|
// cache data.
|
||
|
//
|
||
|
// The cache is suitable only for data that the calling application could
|
||
|
// recreate if lost. Any file or directory under this prefix may be deleted
|
||
|
// at any time by other software.
|
||
|
//
|
||
|
// This directory may, on some systems, match one of the directories
|
||
|
// returned in ConfigDirs and/or DataDirs. For this reason applications
|
||
|
// must ensure that they do not misinterpret config and data files as
|
||
|
// cache files, and in particular should not naively purge a cache by
|
||
|
// emptying this directory.
|
||
|
CacheDir string
|
||
|
}
|
||
|
|
||
|
// ConfigHome returns the path for the directory where any new configuration
|
||
|
// files should be written.
|
||
|
func (d Dirs) ConfigHome() string {
|
||
|
return d.ConfigDirs[0]
|
||
|
}
|
||
|
|
||
|
// DataHome returns the path for the directory where any new configuration
|
||
|
// files should be written.
|
||
|
func (d Dirs) DataHome() string {
|
||
|
return d.DataDirs[0]
|
||
|
}
|
||
|
|
||
|
// NewConfigPath joins the given path segments to the ConfigHome to produce a
|
||
|
// path where a new configuration file might be written.
|
||
|
func (d Dirs) NewConfigPath(parts ...string) string {
|
||
|
return filepath.Join(d.ConfigHome(), filepath.Join(parts...))
|
||
|
}
|
||
|
|
||
|
// NewDataPath joins the given path segments to the DataHome to produce a
|
||
|
// path where a new data file might be written.
|
||
|
func (d Dirs) NewDataPath(parts ...string) string {
|
||
|
return filepath.Join(d.DataHome(), filepath.Join(parts...))
|
||
|
}
|
||
|
|
||
|
// CachePath joins the given path segments to the CacheHome to produce a
|
||
|
// path for a cache file or directory.
|
||
|
func (d Dirs) CachePath(parts ...string) string {
|
||
|
return filepath.Join(d.CacheDir, filepath.Join(parts...))
|
||
|
}
|
||
|
|
||
|
// ConfigSearchPaths joins the given path segments to each of the directories
|
||
|
// in in ConfigDirs to produce a more specific set of paths to be searched
|
||
|
// in preference order.
|
||
|
func (d Dirs) ConfigSearchPaths(parts ...string) []string {
|
||
|
return searchPaths(d.ConfigDirs, parts...)
|
||
|
}
|
||
|
|
||
|
// DataSearchPaths joins the given path segments to each of the directories
|
||
|
// in in ConfigDirs to produce a more specific set of paths to be searched
|
||
|
// in preference order.
|
||
|
func (d Dirs) DataSearchPaths(parts ...string) []string {
|
||
|
return searchPaths(d.DataDirs, parts...)
|
||
|
}
|
||
|
|
||
|
// FindConfigFiles scans over all of the paths in ConfigDirs and tests whether
|
||
|
// a file of the given name is present in each, returning a slice of full
|
||
|
// paths that matched.
|
||
|
func (d Dirs) FindConfigFiles(parts ...string) []string {
|
||
|
return findFiles(d.ConfigDirs, parts...)
|
||
|
}
|
||
|
|
||
|
// FindDataFiles scans over all of the paths in ConfigDirs and tests whether
|
||
|
// a file of the given name is present in each, returning a slice of full
|
||
|
// paths that matched.
|
||
|
func (d Dirs) FindDataFiles(parts ...string) []string {
|
||
|
return findFiles(d.DataDirs, parts...)
|
||
|
}
|
||
|
|
||
|
// GlobConfigFiles joins the given parts to create a glob pattern and then
|
||
|
// applies it relative to each of the paths in ConfigDirs, returning all
|
||
|
// of the matches in a single slice.
|
||
|
//
|
||
|
// The order of the result preserves the directory preference order and
|
||
|
// sorts multiple files within the same directory lexicographically.
|
||
|
//
|
||
|
// Remember that on some platforms the config dirs and data dirs overlap,
|
||
|
// so to be robust you should use distinct naming patterns for configuration
|
||
|
// and data files to avoid accidentally matching data files with this method.
|
||
|
func (d Dirs) GlobConfigFiles(parts ...string) []string {
|
||
|
return globFiles(d.ConfigDirs, parts...)
|
||
|
}
|
||
|
|
||
|
// GlobDataFiles joins the given parts to create a glob pattern and then
|
||
|
// applies it relative to each of the paths in DataDirs, returning all
|
||
|
// of the matches in a single slice.
|
||
|
//
|
||
|
// The order of the result preserves the directory preference order and
|
||
|
// sorts multiple files within the same directory lexicographically.
|
||
|
//
|
||
|
// Remember that on some platforms the config dirs and data dirs overlap,
|
||
|
// so to be robust you should use distinct naming patterns for configuration
|
||
|
// and data files to avoid accidentally matching configuration files with this
|
||
|
// method.
|
||
|
func (d Dirs) GlobDataFiles(parts ...string) []string {
|
||
|
return globFiles(d.DataDirs, parts...)
|
||
|
}
|
||
|
|
||
|
func searchPaths(bases []string, parts ...string) []string {
|
||
|
extra := filepath.Join(parts...)
|
||
|
ret := make([]string, len(bases))
|
||
|
for i, base := range bases {
|
||
|
ret[i] = filepath.Join(base, extra)
|
||
|
}
|
||
|
return ret
|
||
|
}
|
||
|
|
||
|
func findFiles(bases []string, parts ...string) []string {
|
||
|
extra := filepath.Join(parts...)
|
||
|
ret := make([]string, 0, len(bases))
|
||
|
for _, base := range bases {
|
||
|
candidate := filepath.Join(base, extra)
|
||
|
if info, err := os.Stat(candidate); err == nil && !info.IsDir() {
|
||
|
ret = append(ret, candidate)
|
||
|
}
|
||
|
}
|
||
|
if len(ret) == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
return ret
|
||
|
}
|
||
|
|
||
|
func globFiles(bases []string, parts ...string) []string {
|
||
|
extra := filepath.Join(parts...)
|
||
|
var ret []string
|
||
|
for _, base := range bases {
|
||
|
pattern := filepath.Join(base, extra)
|
||
|
found, err := filepath.Glob(pattern)
|
||
|
if err != nil {
|
||
|
continue
|
||
|
}
|
||
|
ret = append(ret, found...)
|
||
|
}
|
||
|
if len(ret) == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
return ret
|
||
|
}
|