Skip to content

Commit 8696a5f

Browse files
committed
Simplify config package
We only ever do three things from the outside: 1. Load an existing config, falling back to defaults if it doesn't exist. 2. Update values on a config. 3. Write the config to disc. Everything else is details. In order to be able to set a fake homeDir on this, the New function delegates to a separate load function. This also allows us to avoid potential race conditions in the tests by not setting ENV variables.
1 parent 8a68293 commit 8696a5f

15 files changed

Lines changed: 177 additions & 214 deletions

cmd/configure.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
// If a setting is not passed as an argument, default
1414
// values are used.
1515
func Configure(ctx *cli.Context) {
16-
c, err := config.Read(ctx.GlobalString("config"))
16+
c, err := config.New(ctx.GlobalString("config"))
1717
if err != nil {
1818
log.Fatal(err)
1919
}

cmd/debug.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func Debug(ctx *cli.Context) {
2424
}
2525
fmt.Printf("Home Dir: %s\n", dir)
2626

27-
c, err := config.Read(ctx.GlobalString("config"))
27+
c, err := config.New(ctx.GlobalString("config"))
2828
if err != nil {
2929
log.Fatal(err)
3030
}

cmd/demo.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212

1313
// Demo returns one problem for each active track.
1414
func Demo(ctx *cli.Context) {
15-
c, err := config.Read(ctx.GlobalString("config"))
15+
c, err := config.New(ctx.GlobalString("config"))
1616
if err != nil {
1717
log.Fatal(err)
1818
}

cmd/download.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414

1515
// Download returns specified submissions and related problem.
1616
func Download(ctx *cli.Context) {
17-
c, err := config.Read(ctx.GlobalString("config"))
17+
c, err := config.New(ctx.GlobalString("config"))
1818
if err != nil {
1919
log.Fatal(err)
2020
}

cmd/fetch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212

1313
// Fetch returns exercism problems.
1414
func Fetch(ctx *cli.Context) {
15-
c, err := config.Read(ctx.GlobalString("config"))
15+
c, err := config.New(ctx.GlobalString("config"))
1616
if err != nil {
1717
log.Fatal(err)
1818
}

cmd/restore.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212

1313
// Restore returns a user's solved problems.
1414
func Restore(ctx *cli.Context) {
15-
c, err := config.Read(ctx.GlobalString("config"))
15+
c, err := config.New(ctx.GlobalString("config"))
1616
if err != nil {
1717
log.Fatal(err)
1818
}

cmd/submit.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func Submit(ctx *cli.Context) {
1818
log.Fatal("Please enter a file name")
1919
}
2020

21-
c, err := config.Read(ctx.GlobalString("config"))
21+
c, err := config.New(ctx.GlobalString("config"))
2222
if err != nil {
2323
log.Fatal(err)
2424
}

cmd/tracks.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212

1313
// Tracks lists available tracks.
1414
func Tracks(ctx *cli.Context) {
15-
c, err := config.Read(ctx.GlobalString("config"))
15+
c, err := config.New(ctx.GlobalString("config"))
1616
if err != nil {
1717
log.Fatal(err)
1818
}

cmd/unsubmit.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
// If no iteration is specified, the most recent iteration
1414
// is deleted.
1515
func Unsubmit(ctx *cli.Context) {
16-
c, err := config.Read(ctx.GlobalString("config"))
16+
c, err := config.New(ctx.GlobalString("config"))
1717
if err != nil {
1818
log.Fatal(err)
1919
}

config/config.go

Lines changed: 63 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package config
33
import (
44
"encoding/json"
55
"errors"
6-
"fmt"
76
"os"
87
"path/filepath"
98
"runtime"
@@ -65,99 +64,116 @@ func Home() (string, error) {
6564
return dir, nil
6665
}
6766

68-
// Read loads the config from the stored JSON file.
69-
func Read(file string) (*Config, error) {
67+
func New(path string) (*Config, error) {
7068
c := &Config{}
71-
err := c.Read(file)
69+
err := c.load(path, os.Getenv(fileEnvKey))
7270
return c, err
7371
}
7472

7573
// Update sets new values where given.
7674
func (c *Config) Update(key, host, dir, xapi string) {
75+
key = strings.TrimSpace(key)
7776
if key != "" {
7877
c.APIKey = key
7978
}
8079

80+
host = strings.TrimSpace(host)
8181
if host != "" {
8282
c.API = host
8383
}
8484

85+
dir = strings.TrimSpace(dir)
8586
if dir != "" {
8687
c.Dir = dir
8788
}
8889

90+
xapi = strings.TrimSpace(xapi)
8991
if xapi != "" {
9092
c.XAPI = xapi
9193
}
92-
93-
c.configure()
9494
}
9595

96-
// Expand takes inputs for a config file location and builds an absolute path.
97-
func Expand(path, env, home string) string {
98-
if path == "" {
99-
path = env
100-
}
101-
102-
if path != "" && path[0] == '~' {
103-
path = strings.Replace(path, "~/", fmt.Sprintf("%s/", home), 1)
104-
}
105-
106-
if path == "" {
107-
path = filepath.Join(home, File)
96+
// Write saves the config as JSON.
97+
func (c *Config) Write() error {
98+
// truncates existing file if it exists
99+
f, err := os.Create(c.File)
100+
if err != nil {
101+
return err
108102
}
103+
defer f.Close()
109104

110-
return path
105+
e := json.NewEncoder(f)
106+
return e.Encode(c)
111107
}
112108

113-
// Read loads the config from the stored JSON file.
114-
func (c *Config) Read(file string) error {
115-
home, err := c.homeDir()
109+
func (c *Config) load(argPath, envPath string) error {
110+
path, err := c.resolvePath(argPath, envPath)
116111
if err != nil {
117112
return err
118113
}
114+
c.File = path
119115

120-
c.File = Expand(file, os.Getenv(fileEnvKey), home)
116+
if err := c.read(); err != nil {
117+
return err
118+
}
121119

120+
// in case people manually update the config file
121+
// with weird formatting
122+
c.APIKey = strings.TrimSpace(c.APIKey)
123+
c.Dir = strings.TrimSpace(c.Dir)
124+
c.API = strings.TrimSpace(c.API)
125+
c.XAPI = strings.TrimSpace(c.XAPI)
126+
127+
return c.setDefaults()
128+
}
129+
130+
func (c *Config) read() error {
122131
if _, err := os.Stat(c.File); err != nil {
123132
if os.IsNotExist(err) {
124-
c.configure()
125133
return nil
126134
}
127135
return err
128136
}
129-
130137
f, err := os.Open(c.File)
131138
if err != nil {
132139
return err
133140
}
134141
defer f.Close()
135142

136143
d := json.NewDecoder(f)
137-
err = d.Decode(&c)
138-
if err != nil {
139-
return err
144+
return d.Decode(&c)
145+
}
146+
147+
// IsAuthenticated returns true if the config contains an API key.
148+
// This does not check whether or not that key is valid.
149+
func (c *Config) IsAuthenticated() bool {
150+
return c.APIKey != ""
151+
}
152+
153+
// homeDir caches the lookup of the user's home directory.
154+
func (c *Config) homeDir() (string, error) {
155+
if c.home != "" {
156+
return c.home, nil
140157
}
141-
c.configure()
142-
return nil
158+
return Home()
143159
}
144160

145-
// Write() saves the config as JSON.
146-
func (c *Config) Write() error {
147-
// truncates existing file if it exists
148-
f, err := os.Create(c.File)
161+
func (c *Config) resolvePath(argPath, envPath string) (string, error) {
162+
path := argPath
163+
if path == "" {
164+
path = envPath
165+
}
166+
if path == "" {
167+
path = filepath.Join("~", File)
168+
}
169+
h, err := c.homeDir()
149170
if err != nil {
150-
return err
171+
return "", err
151172
}
152-
defer f.Close()
153-
154-
e := json.NewEncoder(f)
155-
return e.Encode(c)
173+
return strings.Replace(path, "~", h, 1), nil
156174
}
157175

158-
func (c *Config) configure() (*Config, error) {
159-
c.sanitize()
160-
176+
func (c *Config) setDefaults() error {
161177
if c.API == "" {
162178
c.API = hostAPI
163179
}
@@ -166,44 +182,15 @@ func (c *Config) configure() (*Config, error) {
166182
c.XAPI = hostXAPI
167183
}
168184

169-
homeDir, err := c.homeDir()
185+
h, err := c.homeDir()
170186
if err != nil {
171-
return c, err
187+
return err
172188
}
173189

174190
if c.Dir == "" {
175-
// fall back to default value
176-
c.Dir = filepath.Join(homeDir, DirExercises)
177-
} else {
178-
// replace '~' with user's home
179-
c.Dir = strings.Replace(c.Dir, "~/", fmt.Sprintf("%s/", homeDir), 1)
180-
}
181-
182-
if c.File == "" {
183-
c.File = filepath.Join(homeDir, File)
184-
}
185-
186-
return c, nil
187-
}
188-
189-
// IsAuthenticated returns true if the config contains an API key.
190-
// This does not check whether or not that key is valid.
191-
func (c *Config) IsAuthenticated() bool {
192-
return c.APIKey != ""
193-
}
194-
195-
// See: http://stackoverflow.com/questions/7922270/obtain-users-home-directory
196-
// we can't cross compile using cgo and use user.Current()
197-
func (c *Config) homeDir() (string, error) {
198-
if c.home != "" {
199-
return c.home, nil
191+
c.Dir = filepath.Join(h, DirExercises)
200192
}
201-
return Home()
202-
}
193+
c.Dir = strings.Replace(c.Dir, "~", h, 1)
203194

204-
func (c *Config) sanitize() {
205-
c.APIKey = strings.TrimSpace(c.APIKey)
206-
c.Dir = strings.TrimSpace(c.Dir)
207-
c.API = strings.TrimSpace(c.API)
208-
c.XAPI = strings.TrimSpace(c.XAPI)
195+
return nil
209196
}

0 commit comments

Comments
 (0)