From b3d5be4d93004469762d7978a0e48e53f8b02968 Mon Sep 17 00:00:00 2001 From: Jingwen Owen Ou Date: Sun, 14 Sep 2014 17:46:13 -0700 Subject: [PATCH] Refactor to `configService` to read & write `Config` `configService` can save and load `Config` using a strategy of `configEncoder` and `configDecoder`. This pave the path for swapping out `tomlConfigEncoder` and `tomlConfigDecoder` with `yamlConfigEncoder` and `yamlConfigDecoder`. `Configs` also has been renamed to `Config`. --- commands/clone.go | 4 +- commands/create.go | 4 +- commands/fork.go | 7 +- commands/pull_request.go | 2 +- commands/remote.go | 2 +- github/client.go | 2 +- github/{configs.go => config.go} | 114 +++++++----------- github/config_decoder.go | 19 +++ github/config_encoder.go | 19 +++ github/config_service.go | 43 +++++++ ...configs_test.go => config_service_test.go} | 16 +-- github/project.go | 4 +- 12 files changed, 147 insertions(+), 89 deletions(-) rename github/{configs.go => config.go} (70%) create mode 100644 github/config_decoder.go create mode 100644 github/config_encoder.go create mode 100644 github/config_service.go rename github/{configs_test.go => config_service_test.go} (72%) diff --git a/commands/clone.go b/commands/clone.go index f3c2a4d6..7ae34e6c 100644 --- a/commands/clone.go +++ b/commands/clone.go @@ -59,8 +59,8 @@ func transformCloneArgs(args *Args) { name, owner := parseCloneNameAndOwner(a) var host *github.Host if owner == "" { - configs := github.CurrentConfigs() - h, err := configs.DefaultHost() + config := github.CurrentConfig() + h, err := config.DefaultHost() if err != nil { utils.Check(github.FormatError("cloning repository", err)) } diff --git a/commands/create.go b/commands/create.go index 8f3e4e3c..1caa60cf 100644 --- a/commands/create.go +++ b/commands/create.go @@ -73,8 +73,8 @@ func create(command *Command, args *Args) { newRepoName = args.FirstParam() } - configs := github.CurrentConfigs() - host, err := configs.DefaultHost() + config := github.CurrentConfig() + host, err := config.DefaultHost() if err != nil { utils.Check(github.FormatError("creating repository", err)) } diff --git a/commands/fork.go b/commands/fork.go index 85088d7b..8c20bc95 100644 --- a/commands/fork.go +++ b/commands/fork.go @@ -2,9 +2,10 @@ package commands import ( "fmt" + "os" + "github.com/github/hub/github" "github.com/github/hub/utils" - "os" ) var cmdFork = &Command{ @@ -41,8 +42,8 @@ func fork(cmd *Command, args *Args) { utils.Check(fmt.Errorf("Error: repository under 'origin' remote is not a GitHub project")) } - configs := github.CurrentConfigs() - host, err := configs.PromptForHost(project.Host) + config := github.CurrentConfig() + host, err := config.PromptForHost(project.Host) if err != nil { utils.Check(github.FormatError("forking repository", err)) } diff --git a/commands/pull_request.go b/commands/pull_request.go index e0454f2e..ba1139b2 100644 --- a/commands/pull_request.go +++ b/commands/pull_request.go @@ -86,7 +86,7 @@ func pullRequest(cmd *Command, args *Args) { baseProject, err := localRepo.MainProject() utils.Check(err) - host, err := github.CurrentConfigs().PromptForHost(baseProject.Host) + host, err := github.CurrentConfig().PromptForHost(baseProject.Host) if err != nil { utils.Check(github.FormatError("creating pull request", err)) } diff --git a/commands/remote.go b/commands/remote.go index d0af420b..2acd1f23 100644 --- a/commands/remote.go +++ b/commands/remote.go @@ -69,7 +69,7 @@ func transformRemoteArgs(args *Args) { isPriavte := parseRemotePrivateFlag(args) if len(words) == 2 && words[1] == "origin" { // Origin special case triggers default user/repo - host, err := github.CurrentConfigs().DefaultHost() + host, err := github.CurrentConfig().DefaultHost() if err != nil { utils.Check(github.FormatError("adding remote", err)) } diff --git a/github/client.go b/github/client.go index 51fd7bcc..c0740c61 100644 --- a/github/client.go +++ b/github/client.go @@ -443,7 +443,7 @@ func (client *Client) FindOrCreateToken(user, password, twoFactorCode string) (t func (client *Client) api() (c *octokit.Client, err error) { if client.Host.AccessToken == "" { - host, e := CurrentConfigs().PromptForHost(client.Host.Host) + host, e := CurrentConfig().PromptForHost(client.Host.Host) if e != nil { err = e return diff --git a/github/configs.go b/github/config.go similarity index 70% rename from github/configs.go rename to github/config.go index 2eed4328..9c96db1e 100644 --- a/github/configs.go +++ b/github/config.go @@ -8,7 +8,6 @@ import ( "path/filepath" "strconv" - "github.com/BurntSushi/toml" "github.com/github/hub/utils" "github.com/howeyc/gopass" ) @@ -24,11 +23,11 @@ type Host struct { Protocol string `toml:"protocol"` } -type Configs struct { +type Config struct { Hosts []Host `toml:"hosts"` } -func (c *Configs) PromptForHost(host string) (h *Host, err error) { +func (c *Config) PromptForHost(host string) (h *Host, err error) { h = c.Find(host) if h != nil { return @@ -65,12 +64,12 @@ func (c *Configs) PromptForHost(host string) (h *Host, err error) { Protocol: "https", } c.Hosts = append(c.Hosts, *h) - err = saveTo(configsFile(), c) + err = newConfigService().Save(configsFile(), c) return } -func (c *Configs) PromptForUser() (user string) { +func (c *Config) PromptForUser() (user string) { user = os.Getenv("GITHUB_USER") if user != "" { return @@ -82,7 +81,7 @@ func (c *Configs) PromptForUser() (user string) { return } -func (c *Configs) PromptForPassword(host, user string) (pass string) { +func (c *Config) PromptForPassword(host, user string) (pass string) { pass = os.Getenv("GITHUB_PASSWORD") if pass != "" { return @@ -98,12 +97,12 @@ func (c *Configs) PromptForPassword(host, user string) (pass string) { return } -func (c *Configs) PromptForOTP() string { +func (c *Config) PromptForOTP() string { fmt.Print("two-factor authentication code: ") return c.scanLine() } -func (c *Configs) scanLine() string { +func (c *Config) scanLine() string { var line string scanner := bufio.NewScanner(os.Stdin) if scanner.Scan() { @@ -114,7 +113,7 @@ func (c *Configs) scanLine() string { return line } -func (c *Configs) Find(host string) *Host { +func (c *Config) Find(host string) *Host { for _, h := range c.Hosts { if h.Host == host { return &h @@ -124,61 +123,7 @@ func (c *Configs) Find(host string) *Host { return nil } -func saveTo(filename string, v interface{}) error { - err := os.MkdirAll(filepath.Dir(filename), 0771) - if err != nil { - return err - } - - f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) - if err != nil { - return err - } - defer f.Close() - - enc := toml.NewEncoder(f) - return enc.Encode(v) -} - -func loadFrom(filename string, c *Configs) (err error) { - _, err = toml.DecodeFile(filename, c) - return -} - -func configsFile() string { - configsFile := os.Getenv("GH_CONFIG") - if configsFile == "" { - configsFile = defaultConfigsFile - } - - return configsFile -} - -func CurrentConfigs() *Configs { - c := &Configs{} - - configFile := configsFile() - err := loadFrom(configFile, c) - if err != nil { - // load from YAML - } - - return c -} - -func (c *Configs) DefaultHost() (host *Host, err error) { - if GitHubHostEnv != "" { - host, err = c.PromptForHost(GitHubHostEnv) - } else if len(c.Hosts) > 0 { - host = c.selectHost() - } else { - host, err = c.PromptForHost(DefaultGitHubHost()) - } - - return -} - -func (c *Configs) selectHost() *Host { +func (c *Config) selectHost() *Host { options := len(c.Hosts) if options == 1 { @@ -201,12 +146,40 @@ func (c *Configs) selectHost() *Host { return &c.Hosts[i-1] } -func (c *Configs) Save() error { - return saveTo(configsFile(), c) +func configsFile() string { + configsFile := os.Getenv("GH_CONFIG") + if configsFile == "" { + configsFile = defaultConfigsFile + } + + return configsFile +} + +func CurrentConfig() *Config { + c := &Config{} + + err := newConfigService().Load(configsFile(), c) + if err != nil { + // load from YAML + } + + return c +} + +func (c *Config) DefaultHost() (host *Host, err error) { + if GitHubHostEnv != "" { + host, err = c.PromptForHost(GitHubHostEnv) + } else if len(c.Hosts) > 0 { + host = c.selectHost() + } else { + host, err = c.PromptForHost(DefaultGitHubHost()) + } + + return } // Public for testing purpose -func CreateTestConfigs(user, token string) *Configs { +func CreateTestConfigs(user, token string) *Config { f, _ := ioutil.TempFile("", "test-config") defaultConfigsFile = f.Name() @@ -216,8 +189,11 @@ func CreateTestConfigs(user, token string) *Configs { Host: GitHubHost, } - c := &Configs{Hosts: []Host{host}} - saveTo(f.Name(), c) + c := &Config{Hosts: []Host{host}} + err := newConfigService().Save(f.Name(), c) + if err != nil { + panic(err) + } return c } diff --git a/github/config_decoder.go b/github/config_decoder.go new file mode 100644 index 00000000..b5861aad --- /dev/null +++ b/github/config_decoder.go @@ -0,0 +1,19 @@ +package github + +import ( + "io" + + "github.com/BurntSushi/toml" +) + +type configDecoder interface { + Decode(r io.Reader, v interface{}) error +} + +type tomlConfigDecoder struct { +} + +func (t *tomlConfigDecoder) Decode(r io.Reader, v interface{}) error { + _, err := toml.DecodeReader(r, v) + return err +} diff --git a/github/config_encoder.go b/github/config_encoder.go new file mode 100644 index 00000000..8abfba13 --- /dev/null +++ b/github/config_encoder.go @@ -0,0 +1,19 @@ +package github + +import ( + "io" + + "github.com/BurntSushi/toml" +) + +type configEncoder interface { + Encode(w io.Writer, v interface{}) error +} + +type tomlConfigEncoder struct { +} + +func (t *tomlConfigEncoder) Encode(w io.Writer, v interface{}) error { + enc := toml.NewEncoder(w) + return enc.Encode(v) +} diff --git a/github/config_service.go b/github/config_service.go new file mode 100644 index 00000000..1b54d525 --- /dev/null +++ b/github/config_service.go @@ -0,0 +1,43 @@ +package github + +import ( + "os" + "path/filepath" +) + +func newConfigService() *configService { + return &configService{ + Encoder: &tomlConfigEncoder{}, + Decoder: &tomlConfigDecoder{}, + } +} + +type configService struct { + Encoder configEncoder + Decoder configDecoder +} + +func (s *configService) Save(filename string, c *Config) error { + err := os.MkdirAll(filepath.Dir(filename), 0771) + if err != nil { + return err + } + + w, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return err + } + defer w.Close() + + return s.Encoder.Encode(w, c) +} + +func (s *configService) Load(filename string, c *Config) error { + r, err := os.Open(filename) + if err != nil { + return err + } + defer r.Close() + + return s.Decoder.Decode(r, c) +} diff --git a/github/configs_test.go b/github/config_service_test.go similarity index 72% rename from github/configs_test.go rename to github/config_service_test.go index 4e792c92..61000993 100644 --- a/github/configs_test.go +++ b/github/config_service_test.go @@ -10,12 +10,12 @@ import ( "github.com/github/hub/fixtures" ) -func TestConfigs_loadFrom(t *testing.T) { - testConfigs := fixtures.SetupTestConfigs() - defer testConfigs.TearDown() +func TestConfigService_Load(t *testing.T) { + testConfig := fixtures.SetupTestConfigs() + defer testConfig.TearDown() - cc := &Configs{} - err := loadFrom(testConfigs.Path, cc) + cc := &Config{} + err := newConfigService().Load(testConfig.Path, cc) assert.Equal(t, nil, err) assert.Equal(t, 1, len(cc.Hosts)) @@ -26,7 +26,7 @@ func TestConfigs_loadFrom(t *testing.T) { assert.Equal(t, "http", host.Protocol) } -func TestConfigs_saveTo(t *testing.T) { +func TestConfigService_Save(t *testing.T) { file, _ := ioutil.TempFile("", "test-gh-config-") defer os.RemoveAll(file.Name()) @@ -36,9 +36,9 @@ func TestConfigs_saveTo(t *testing.T) { AccessToken: "123", Protocol: "https", } - c := Configs{Hosts: []Host{host}} + c := Config{Hosts: []Host{host}} - err := saveTo(file.Name(), &c) + err := newConfigService().Save(file.Name(), &c) assert.Equal(t, nil, err) b, _ := ioutil.ReadFile(file.Name()) diff --git a/github/project.go b/github/project.go index c5c27c25..a17f6e62 100644 --- a/github/project.go +++ b/github/project.go @@ -151,7 +151,7 @@ func newProject(owner, name, host, protocol string) *Project { protocol = "" } if protocol == "" { - h := CurrentConfigs().Find(host) + h := CurrentConfig().Find(host) if h != nil { protocol = h.Protocol } @@ -161,7 +161,7 @@ func newProject(owner, name, host, protocol string) *Project { } if owner == "" { - h := CurrentConfigs().Find(host) + h := CurrentConfig().Find(host) if h != nil { owner = h.User }