Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 61 additions & 11 deletions browser.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,50 @@ var Stdout io.Writer = os.Stdout
// Stderr is the io.Writer to which executed commands write standard error.
var Stderr io.Writer = os.Stderr

// Opener allows customizing browser opening behavior.
type Opener struct {
// Stdout is the io.Writer to which executed commands write standard output.
// If nil, os.Stdout is used.
Stdout io.Writer

// Stderr is the io.Writer to which executed commands write standard error.
// If nil, os.Stderr is used.
Stderr io.Writer
}

func (o *Opener) stdout() io.Writer {
if o.Stdout != nil {
return o.Stdout
}
return Stdout
}

func (o *Opener) stderr() io.Writer {
if o.Stderr != nil {
return o.Stderr
}
return Stderr
}

func (o *Opener) runCmd(prog string, args ...string) error {
cmd := exec.Command(prog, args...)
cmd.Stdout = o.stdout()
cmd.Stderr = o.stderr()
return cmd.Run()
}

// OpenFile opens new browser window for the file path.
func OpenFile(path string) error {
func (o *Opener) OpenFile(path string) error {
path, err := filepath.Abs(path)
if err != nil {
return err
}
return OpenURL("file://" + path)
return o.OpenURL("file://" + path)
}

// OpenReader consumes the contents of r and presents the
// results in a new browser window.
func OpenReader(r io.Reader) error {
func (o *Opener) OpenReader(r io.Reader) error {
f, err := ioutil.TempFile("", "browser.*.html")
if err != nil {
return fmt.Errorf("browser: could not create temporary file: %v", err)
Expand All @@ -41,17 +73,35 @@ func OpenReader(r io.Reader) error {
if err := f.Close(); err != nil {
return fmt.Errorf("browser: caching temporary file failed: %v", err)
}
return OpenFile(f.Name())
return o.OpenFile(f.Name())
}

// OpenURL opens a new browser window pointing to url.
func OpenURL(url string) error {
return openBrowser(url)
func (o *Opener) OpenURL(url string) error {
return o.openBrowser(url)
}

func runCmd(prog string, args ...string) error {
cmd := exec.Command(prog, args...)
cmd.Stdout = Stdout
cmd.Stderr = Stderr
return cmd.Run()
// defaultOpener returns an Opener configured with the package-level Stdout/Stderr.
// This is done as a function to always grab the latest values of Stdout/Stderr.
func defaultOpener() *Opener {
return &Opener{
Stdout: Stdout,
Stderr: Stderr,
}
}

// OpenFile opens new browser window for the file path.
func OpenFile(path string) error {
return defaultOpener().OpenFile(path)
}

// OpenReader consumes the contents of r and presents the
// results in a new browser window.
func OpenReader(r io.Reader) error {
return defaultOpener().OpenReader(r)
}

// OpenURL opens a new browser window pointing to url.
func OpenURL(url string) error {
return defaultOpener().OpenURL(url)
}
4 changes: 2 additions & 2 deletions browser_darwin.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package browser

func openBrowser(url string) error {
return runCmd("open", url)
func (o *Opener) openBrowser(url string) error {
return o.runCmd("open", url)
}
4 changes: 2 additions & 2 deletions browser_freebsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import (
"os/exec"
)

func openBrowser(url string) error {
err := runCmd("xdg-open", url)
func (o *Opener) openBrowser(url string) error {
err := o.runCmd("xdg-open", url)
if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound {
return errors.New("xdg-open: command not found - install xdg-utils from ports(8)")
}
Expand Down
4 changes: 2 additions & 2 deletions browser_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import (
"strings"
)

func openBrowser(url string) error {
func (o *Opener) openBrowser(url string) error {
providers := []string{"xdg-open", "x-www-browser", "www-browser"}

// There are multiple possible providers to open a browser on linux
// One of them is xdg-open, another is x-www-browser, then there's www-browser, etc.
// Look for one that exists and run it
for _, provider := range providers {
if _, err := exec.LookPath(provider); err == nil {
return runCmd(provider, url)
return o.runCmd(provider, url)
}
}

Expand Down
4 changes: 2 additions & 2 deletions browser_netbsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import (
"os/exec"
)

func openBrowser(url string) error {
err := runCmd("xdg-open", url)
func (o *Opener) openBrowser(url string) error {
err := o.runCmd("xdg-open", url)
if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound {
return errors.New("xdg-open: command not found - install xdg-utils from pkgsrc(7)")
}
Expand Down
4 changes: 2 additions & 2 deletions browser_openbsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import (
"os/exec"
)

func openBrowser(url string) error {
err := runCmd("xdg-open", url)
func (o *Opener) openBrowser(url string) error {
err := o.runCmd("xdg-open", url)
if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound {
return errors.New("xdg-open: command not found - install xdg-utils from ports(8)")
}
Expand Down
19 changes: 19 additions & 0 deletions browser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package browser

import (
"bytes"
"path/filepath"
"testing"
)

func TestErrorRedirect(t *testing.T) {
stderr := new(bytes.Buffer)
o := &Opener{
Stderr: stderr,
}

_ = o.OpenFile(filepath.Join(t.TempDir(), "nonexistentfile.html"))
if stderr.Len() == 0 {
t.Errorf("expected stderr to contain error message, got empty")
}
}
3 changes: 2 additions & 1 deletion browser_unsupported.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build !linux && !windows && !darwin && !openbsd && !freebsd && !netbsd
// +build !linux,!windows,!darwin,!openbsd,!freebsd,!netbsd

package browser
Expand All @@ -7,6 +8,6 @@ import (
"runtime"
)

func openBrowser(url string) error {
func (o *Opener) openBrowser(url string) error {
return fmt.Errorf("openBrowser: unsupported operating system: %v", runtime.GOOS)
}
2 changes: 1 addition & 1 deletion browser_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ package browser

import "golang.org/x/sys/windows"

func openBrowser(url string) error {
func (o *Opener) openBrowser(url string) error {
return windows.ShellExecute(0, nil, windows.StringToUTF16Ptr(url), nil, nil, windows.SW_SHOWNORMAL)
}