aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Fischer <martin@push-f.com>2026-01-17 11:07:37 +0100
committerMartin Fischer <martin@push-f.com>2026-01-17 11:54:35 +0100
commitdb778877e6273c3d878eb9f3858531ddfb68242c (patch)
tree8bbeabd541748685dce8f96ac52840ba453e4fdc
parent222b4dc884892d5927dabbab2b2b6e64d3244002 (diff)
refactor(open): extract findCommand
-rw-r--r--go.mod2
-rw-r--r--go.sum2
-rw-r--r--hashes.nix2
-rw-r--r--programs/open/main.go55
-rw-r--r--programs/open/main_test.go150
5 files changed, 181 insertions, 30 deletions
diff --git a/go.mod b/go.mod
index 1bbc9da..43de4d8 100644
--- a/go.mod
+++ b/go.mod
@@ -3,3 +3,5 @@ module push-f.com/cmd50
go 1.25.5
require github.com/BurntSushi/toml v1.6.0
+
+require github.com/google/go-cmp v0.7.0 // indirect
diff --git a/go.sum b/go.sum
index f74b269..7d2396d 100644
--- a/go.sum
+++ b/go.sum
@@ -1,2 +1,4 @@
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
diff --git a/hashes.nix b/hashes.nix
index 3196db1..9326d68 100644
--- a/hashes.nix
+++ b/hashes.nix
@@ -1,3 +1,3 @@
{
- go = "sha256-pbA/AlBz3cQYRTMnQ/qBPcinYOKokrBLNhkbRTq54gE=";
+ go = "sha256-XXgXzv6MARTUse1lf4RAaMp9xg8FfysaPMM7wq5zdlw=";
}
diff --git a/programs/open/main.go b/programs/open/main.go
index 415f62a..ad3c3ca 100644
--- a/programs/open/main.go
+++ b/programs/open/main.go
@@ -40,23 +40,35 @@ func main() {
fatal("unknown config keys: %v", meta.Undecoded())
}
- for scheme, cmd := range cfg.UriSchemes {
+ args, err := cfg.findCommand(arg)
+ if err != nil {
+ fatal(err.Error())
+ }
+ args = append(args, arg)
+ cmd := exec.Command(args[0], args[1:]...)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+
+ err = cmd.Run()
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+func (c Config) findCommand(arg string) ([]string, error) {
+ for scheme, cmd := range c.UriSchemes {
if strings.HasPrefix(arg, scheme+":") {
- cmd = append(cmd, arg)
- err := run(cmd)
- if err != nil {
- log.Fatal(err)
- }
- return
+ return cmd, nil
}
}
stat, err := os.Stat(arg)
if err != nil {
if os.IsNotExist(err) && strings.Contains(arg, ":") {
- fatal("file not found or URI scheme not registered")
+ return nil, fmt.Errorf("file not found or URI scheme not registered")
}
- fatal("stat failed: %v", err)
+ return nil, fmt.Errorf("stat failed: %v", err)
}
var mimeType string
@@ -67,28 +79,23 @@ func main() {
ext := path.Ext(arg)
mimeType = mime.TypeByExtension(ext)
if mimeType == "" {
- fatal("no associated mime type for file extension %s", ext)
+ return nil, fmt.Errorf("no associated mime type for file extension %s", ext)
}
// strip e.g. `; charset=utf-8`
mimeType, _, _ = strings.Cut(mimeType, ";")
}
- for mimeTypePat, cmd := range cfg.MimeTypes {
+ for mimeTypePat, cmd := range c.MimeTypes {
matched, err := path.Match(mimeTypePat, mimeType)
if err != nil {
- log.Fatal(err)
+ return nil, err
}
if matched {
- cmd = append(cmd, arg)
- err := run(cmd)
- if err != nil {
- log.Fatal(err)
- }
- return
+ return cmd, nil
}
}
- fatal("no opener configured for mime %s", mimeType)
+ return nil, fmt.Errorf("no opener configured for mime %s", mimeType)
}
func fatal(msg string, v ...any) {
@@ -96,16 +103,6 @@ func fatal(msg string, v ...any) {
os.Exit(1)
}
-func run(args []string) error {
- cmd := exec.Command(args[0], args[1:]...)
-
- cmd.Stdin = os.Stdin
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
-
- return cmd.Run()
-}
-
func xdgConfigPath(appName string) (string, error) {
xdgHome := os.Getenv("XDG_CONFIG_HOME")
diff --git a/programs/open/main_test.go b/programs/open/main_test.go
new file mode 100644
index 0000000..213c2e4
--- /dev/null
+++ b/programs/open/main_test.go
@@ -0,0 +1,150 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "path"
+ "strings"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+)
+
+func TestFindCommand_Uri(t *testing.T) {
+ cfg := Config{
+ MimeTypes: map[string][]string{},
+ UriSchemes: map[string][]string{
+ "http": {"my-browser"},
+ },
+ }
+
+ cases := []string{
+ "http://example.com",
+ "http:example.com",
+ "http:",
+ }
+
+ for _, arg := range cases {
+ t.Run(arg, func(t *testing.T) {
+ cmd, err := cfg.findCommand(arg)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if diff := cmp.Diff(cmd, cfg.UriSchemes["http"]); diff != "" {
+ t.Error("unexpected command", diff)
+ }
+ })
+ }
+}
+
+func TestFindCommand_MimeFile(t *testing.T) {
+ err := os.Chdir(t.TempDir())
+ if err != nil {
+ t.Fatal(err)
+ }
+ filename := "test.html"
+ _, err = os.Create(filename)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ cases := []string{
+ "text/html",
+ "text/*",
+ }
+
+ for _, pattern := range cases {
+ t.Run(pattern, func(t *testing.T) {
+ cfg := Config{
+ MimeTypes: map[string][]string{
+ pattern: {"the-program"},
+ },
+ UriSchemes: map[string][]string{},
+ }
+ cmd, err := cfg.findCommand(filename)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if diff := cmp.Diff(cmd, cfg.MimeTypes[pattern]); diff != "" {
+ t.Error("unexpected command", diff)
+ }
+ })
+ }
+}
+
+func TestFindCommand_MimeDir(t *testing.T) {
+ path := t.TempDir()
+
+ cfg := Config{
+ MimeTypes: map[string][]string{
+ "inode/directory": {"the-program"},
+ },
+ UriSchemes: map[string][]string{},
+ }
+ cmd, err := cfg.findCommand(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if diff := cmp.Diff(cmd, cfg.MimeTypes["inode/directory"]); diff != "" {
+ t.Error("unexpected command", diff)
+ }
+}
+
+func TestFindCommand_FileMissing(t *testing.T) {
+ filePath := path.Join(t.TempDir(), "test.html")
+
+ cfg := Config{
+ MimeTypes: map[string][]string{},
+ UriSchemes: map[string][]string{},
+ }
+ _, err := cfg.findCommand(filePath)
+ expectedPrefix := "stat failed:"
+ if !strings.HasPrefix(err.Error(), expectedPrefix) {
+ t.Fatalf("expected error to start with %q but got %q", expectedPrefix, err.Error())
+ }
+}
+
+func TestFindCommand_UriCheckedFirst(t *testing.T) {
+ cases := []struct {
+ uriHandlerExists bool
+ expectedCommand []string
+ }{
+ {true, []string{"uri-program"}},
+ {false, []string{"mime-program"}},
+ }
+
+ for _, c := range cases {
+ t.Run(fmt.Sprintf("uriHandlerExists=%v", c.uriHandlerExists), func(t *testing.T) {
+ scheme := "uri-or"
+ arg := scheme + ":file.txt"
+
+ cfg := Config{
+ MimeTypes: map[string][]string{
+ "text/plain": {"mime-program"},
+ },
+ UriSchemes: map[string][]string{},
+ }
+
+ if c.uriHandlerExists {
+ cfg.UriSchemes[scheme] = []string{"uri-program"}
+ }
+
+ err := os.Chdir(t.TempDir())
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, err = os.Create(arg)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ cmd, err := cfg.findCommand(arg)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if diff := cmp.Diff(cmd, c.expectedCommand); diff != "" {
+ t.Error("unexpected command", diff)
+ }
+ })
+ }
+}