From 4ff4a241508b5213c3ecd04eef81291f948c54e9 Mon Sep 17 00:00:00 2001 From: Sam Coe Date: Thu, 4 Aug 2022 14:36:56 +0200 Subject: [PATCH] Migrate browser package from gh --- go.mod | 2 + go.sum | 5 ++ pkg/browser/browser.go | 80 ++++++++++++++++++++++++++++ pkg/browser/browser_test.go | 103 ++++++++++++++++++++++++++++++++++++ 4 files changed, 190 insertions(+) create mode 100644 pkg/browser/browser.go create mode 100644 pkg/browser/browser_test.go diff --git a/go.mod b/go.mod index 419acc6..fd08fd0 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,10 @@ go 1.16 require ( github.com/MakeNowJust/heredoc v1.0.0 + github.com/cli/browser v1.1.0 github.com/cli/safeexec v1.0.0 github.com/cli/shurcooL-graphql v0.0.1 + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/henvic/httpretty v0.0.6 github.com/kr/pretty v0.1.0 // indirect github.com/stretchr/testify v1.7.0 diff --git a/go.sum b/go.sum index 83b2380..c8fa149 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,15 @@ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/cli/browser v1.1.0 h1:xOZBfkfY9L9vMBgqb1YwRirGu6QFaQ5dP/vXt5ENSOY= +github.com/cli/browser v1.1.0/go.mod h1:HKMQAt9t12kov91Mn7RfZxyJQQgWgyS/3SZswlZ5iTI= github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI= github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= github.com/cli/shurcooL-graphql v0.0.1 h1:/9J3t9O6p1B8zdBBtQighq5g7DQRItBwuwGh3SocsKM= github.com/cli/shurcooL-graphql v0.0.1/go.mod h1:U7gCSuMZP/Qy7kbqkk5PrqXEeDgtfG5K+W+u8weorps= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/henvic/httpretty v0.0.6 h1:JdzGzKZBajBfnvlMALXXMVQWxWMF/ofTy8C3/OSUTxs= @@ -27,6 +31,7 @@ github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e/go.mod h1: golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e h1:XMgFehsDnnLGtjvjOfqWSUzt0alpTR1RSEuznObga2c= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/pkg/browser/browser.go b/pkg/browser/browser.go new file mode 100644 index 0000000..aeb4745 --- /dev/null +++ b/pkg/browser/browser.go @@ -0,0 +1,80 @@ +// Package browser facilitates opening of URLs in a web browser. +package browser + +import ( + "io" + "os" + "os/exec" + + cliBrowser "github.com/cli/browser" + "github.com/cli/go-gh/pkg/config" + "github.com/cli/safeexec" + "github.com/google/shlex" +) + +// Browser represents a web browser that can be used to open up URLs. +type Browser struct { + launcher string + stderr io.Writer + stdout io.Writer +} + +// NewBrowser initializes a Browser. If a launcher is not specified +// one is determined based on environment variables or from the +// configuration file. +// The order of precedence for determining a launcher is: +// - Specified launcher; +// - GH_BROWSER environment variable; +// - browser option from configuration file; +// - BROWSER environment variable. +func NewBrowser(launcher string, stdout, stderr io.Writer) Browser { + if launcher == "" { + launcher = resolveLauncher() + } + b := Browser{ + launcher: launcher, + stderr: stderr, + stdout: stdout, + } + return b +} + +// Browse opens the launcher and navigates to the specified URL. +func (b *Browser) Browse(url string) error { + return b.browse(url, nil) +} + +func (b *Browser) browse(url string, env []string) error { + if b.launcher != "" { + launcherArgs, err := shlex.Split(b.launcher) + if err != nil { + return err + } + launcherExe, err := safeexec.LookPath(launcherArgs[0]) + if err != nil { + return err + } + args := append(launcherArgs[1:], url) + cmd := exec.Command(launcherExe, args...) + cmd.Stdout = b.stdout + cmd.Stderr = b.stderr + if env != nil { + cmd.Env = env + } + return cmd.Run() + } + return cliBrowser.OpenURL(url) +} + +func resolveLauncher() string { + if ghBrowser := os.Getenv("GH_BROWSER"); ghBrowser != "" { + return ghBrowser + } + cfg, err := config.Read() + if err == nil { + if cfgBrowser, _ := cfg.Get([]string{"browser"}); cfgBrowser != "" { + return cfgBrowser + } + } + return os.Getenv("BROWSER") +} diff --git a/pkg/browser/browser_test.go b/pkg/browser/browser_test.go new file mode 100644 index 0000000..7d946d9 --- /dev/null +++ b/pkg/browser/browser_test.go @@ -0,0 +1,103 @@ +package browser + +import ( + "bytes" + "fmt" + "os" + "testing" + + "github.com/cli/go-gh/pkg/config" + "github.com/stretchr/testify/assert" +) + +func TestHelperProcess(t *testing.T) { + if os.Getenv("GH_WANT_HELPER_PROCESS") != "1" { + return + } + fmt.Fprintf(os.Stdout, "%v", os.Args[3:]) + os.Exit(0) +} + +func TestBrowse(t *testing.T) { + launcher := fmt.Sprintf("%q -test.run=TestHelperProcess -- chrome", os.Args[0]) + stdout := &bytes.Buffer{} + stderr := &bytes.Buffer{} + b := Browser{launcher: launcher, stdout: stdout, stderr: stderr} + err := b.browse("github.com", []string{"GH_WANT_HELPER_PROCESS=1"}) + assert.NoError(t, err) + assert.Equal(t, "[chrome github.com]", stdout.String()) + assert.Equal(t, "", stderr.String()) +} + +func TestResolveLauncher(t *testing.T) { + tests := []struct { + name string + env map[string]string + config *config.Config + wantLauncher string + }{ + { + name: "GH_BROWSER set", + env: map[string]string{ + "GH_BROWSER": "GH_BROWSER", + }, + wantLauncher: "GH_BROWSER", + }, + { + name: "config browser set", + config: config.ReadFromString("browser: CONFIG_BROWSER"), + wantLauncher: "CONFIG_BROWSER", + }, + { + name: "BROWSER set", + env: map[string]string{ + "BROWSER": "BROWSER", + }, + wantLauncher: "BROWSER", + }, + { + name: "GH_BROWSER and config browser set", + env: map[string]string{ + "GH_BROWSER": "GH_BROWSER", + }, + config: config.ReadFromString("browser: CONFIG_BROWSER"), + wantLauncher: "GH_BROWSER", + }, + { + name: "config browser and BROWSER set", + env: map[string]string{ + "BROWSER": "BROWSER", + }, + config: config.ReadFromString("browser: CONFIG_BROWSER"), + wantLauncher: "CONFIG_BROWSER", + }, + { + name: "GH_BROWSER and BROWSER set", + env: map[string]string{ + "BROWSER": "BROWSER", + "GH_BROWSER": "GH_BROWSER", + }, + wantLauncher: "GH_BROWSER", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.env != nil { + for k, v := range tt.env { + old := os.Getenv(k) + os.Setenv(k, v) + defer os.Setenv(k, old) + } + } + if tt.config != nil { + old := config.Read + config.Read = func() (*config.Config, error) { + return tt.config, nil + } + defer func() { config.Read = old }() + } + launcher := resolveLauncher() + assert.Equal(t, tt.wantLauncher, launcher) + }) + } +}