Skip to content

Commit

Permalink
path: allow users to override the config home fs
Browse files Browse the repository at this point in the history
This is particularly useful to support use-cases where the user config
file should be overriden via an environment variable or flag.
  • Loading branch information
Snaipe committed Jul 25, 2023
1 parent cba9edd commit da9a979
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 7 deletions.
32 changes: 32 additions & 0 deletions fileset.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,35 @@ func (cfg *FileSet) Skip() {
cfg.nameIndex++
}
}

// NewSingleFileFS returns an io/fs.FS containing the file at path, under the
// specified name.
//
// For instance, NewSingleFileFS("program.toml", os.Discard) will return an
// FS object such that opening the program.toml file opens the os.Discard file
// instead.
//
// This is particularly useful to pass single file config overrides to Open
// and OpenMultiple.
//
// If path is the empty string, NewSingleFileFS returns nil.
func NewSingleFileFS(name, path string) fs.FS {
if path == "" {
return nil
}
return singleFileFS{Name: name, Path: path}
}

type singleFileFS struct {
Name, Path string
}

func (f singleFileFS) Open(name string) (fs.File, error) {
if !fs.ValidPath(name) {
return nil, fs.ErrInvalid
}
if name != f.Name {
return nil, fs.ErrNotExist
}
return os.Open(f.Path)
}
30 changes: 30 additions & 0 deletions fileset_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
// Copyright 2022 Franklin "Snaipe" Mathieu.
//
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

package boa

import (
Expand Down Expand Up @@ -103,4 +108,29 @@ func TestFileset(t *testing.T) {
assertUsedEqual(t, tc.expectUsed, cfg.Used())
})
}

func ExampleSetConfigHomeFS() {

Check failure on line 112 in fileset_test.go

View workflow job for this annotation

GitHub Actions / test (1.16.x, ubuntu-latest)

expected '(', found ExampleSetConfigHomeFS

var config struct {
Greeting string `help:"A nice hello."`
}

// Use NewSingleFileFS to expose example_defaults.toml as program.toml.
//
// This could be used to override the default config paths via the
// environment or a flag
SetConfigHomeFS(
NewSingleFileFS("program.toml", "example_defaults.toml"),
)

cfg := Open("program.toml")
defer cfg.Close()

// Load the defaults into the config variable
if err := NewDecoder(cfg).Decode(&config); err != nil {
log.Fatalln(err)
}

fmt.Println(config.Greeting)
// Output: Hello from TOML!
}
42 changes: 40 additions & 2 deletions path.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ func ConfigHome() (string, error) {
return configHome()
}

// SystemConfigDirs returns the filesystem paths to the system configuration
// directories.
//
// The returned paths are OS-specific. Typical valies per OS are:
//
// - Linux & UNIX derivatives: /etc, /etc/xdg ($XDG_CONFIG_DIRS)
// - macOS: /Library/Preferences
// - Windows: C:\ProgramData
func SystemConfigDirs() []string {
return configDirs()
}

// ConfigPaths returns, in order of least important to most important, the
// paths that may hold configuration files for the current user.
//
Expand All @@ -44,14 +56,17 @@ func ConfigHome() (string, error) {
// - macOS: /Library/Preferences, ~/Library/Preferences
// - Windows: C:\ProgramData, C:\Users\<user>\AppData\Roaming
func ConfigPaths() []fs.FS {
paths := configPaths()
fs := make([]fs.FS, 0, len(paths)+1)
paths := configDirs()
fs := make([]fs.FS, 0, len(paths)+2)
if defaultPath != nil {
fs = append(fs, defaultPath)
}
for _, path := range paths {
fs = append(fs, os.DirFS(path))
}
if cfs := ConfigHomeFS(); cfs != nil {
fs = append(fs, cfs)
}
return fs
}

Expand All @@ -63,3 +78,26 @@ var defaultPath fs.FS
func SetDefaults(defaults fs.FS) {
defaultPath = defaults
}

var configHomeFS fs.FS

// SetConfigHomeFS overrides the user configuration home, which gets added as
// the most important path in the slice returned by ConfigPaths.
func SetConfigHomeFS(f fs.FS) {
configHomeFS = f
}

// ConfigHomeFS returns the fs.FS for the user's configuration home.
//
// By default, it returns os.DirFS(ConfigHome()) (or nil if unsuccessful),
// unless SetConfigHomeFS has been called, in which case the FS that was set
// by the function is returned.
func ConfigHomeFS() fs.FS {
if configHomeFS == nil {
path, err := configHome()
if err == nil {
configHomeFS = os.DirFS(path)
}
}
return configHomeFS
}
7 changes: 5 additions & 2 deletions path_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ func configHome() (string, error) {
return filepath.Join(home, "Library", "Preferences"), nil
}

func configDirs() []string {
return []string{"/Library/Preferences"}
}

func configPaths() []string {
paths := make([]string, 0, 2)
paths = append(paths, "/Library/Preferences")
paths := configDirs()
configHome, err := ConfigHome()
if err == nil {
paths = append(paths, configHome)
Expand Down
7 changes: 6 additions & 1 deletion path_generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@ func configHome() (string, error) {
return xdg.ConfigHome()
}

func configPaths() []string {
func configDirs() []string {
var paths []string
paths = append(paths, "/etc")
paths = append(paths, xdg.ConfigDirs()...)
return paths
}

func configPaths() []string {
paths := configDirs()
configHome, err := xdg.ConfigHome()
if err == nil {
paths = append(paths, configHome)
Expand Down
7 changes: 5 additions & 2 deletions path_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ func configHome() (string, error) {
return appdata, nil
}

func configDirs() []string {
return []string{`C:\ProgramData`}
}

func configPaths() []string {
paths := make([]string, 0, 2)
paths = append(paths, `C:\ProgramData`)
paths := configDirs()
configHome, err := ConfigHome()
if err == nil {
paths = append(paths, configHome)
Expand Down

0 comments on commit da9a979

Please sign in to comment.