Skip to content

Commit

Permalink
feat: which command
Browse files Browse the repository at this point in the history
  • Loading branch information
nalgeon committed Jul 22, 2023
1 parent 24d1294 commit 0ac5dcf
Show file tree
Hide file tree
Showing 16 changed files with 256 additions and 10 deletions.
27 changes: 18 additions & 9 deletions cmd/help/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,51 @@ package help
import (
"errors"
"fmt"
"sort"
"text/tabwriter"

"sqlpkg.org/cli/logx"
)

const helpHelp = "usage: sqlpkg help"

var commands = []string{
"install", "uninstall", "update", "list", "init", "info", "help", "version",
}
const help = "usage: sqlpkg help"

var commandsHelp = map[string]string{
"help": "Display help",
"info": "Display package information",
"init": "Create a local repository",
"init": "Create local repository",
"install": "Install packages",
"list": "List installed packages",
"uninstall": "Uninstall a package",
"uninstall": "Uninstall package",
"update": "Update installed packages",
"version": "Display version",
"which": "Display path to extension file",
}

// Help prints available commands.
func Help(args []string) error {
if len(args) != 0 {
return errors.New(helpHelp)
return errors.New(help)
}

logx.Log(`sqlpkg is an SQLite package manager.
Use it to install or update SQLite extensions.`)
logx.Log("\nCommands:")

w := tabwriter.NewWriter(logx.Output(), 0, 4, 0, ' ', 0)
for _, cmd := range commands {
for _, cmd := range sortedCommands() {
fmt.Fprintln(w, cmd, "\t", commandsHelp[cmd])
}
w.Flush()

return nil
}

// sortedCommands returns a slice of all commands sorted alphabetically.
func sortedCommands() []string {
list := make([]string, 0, len(commandsHelp))
for cmd := range commandsHelp {
list = append(list, cmd)
}
sort.Strings(list)
return list
}
1 change: 1 addition & 0 deletions cmd/help/help_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func TestHelp(t *testing.T) {
mem.MustHave(t, "list")
mem.MustHave(t, "init")
mem.MustHave(t, "info")
mem.MustHave(t, "which")
mem.MustHave(t, "help")
mem.MustHave(t, "version")

Expand Down
2 changes: 1 addition & 1 deletion cmd/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func FindSpec(path string) (*spec.Package, error) {
return pkg, nil
}

logx.Debug("installed package not found")
logx.Debug("package is not installed")
pkg, err := ReadSpec(path)
return pkg, err
}
Expand Down
1 change: 1 addition & 0 deletions cmd/which/testdata/.sqlpkg/nalgeon/example/example.dll
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
text.dll
1 change: 1 addition & 0 deletions cmd/which/testdata/.sqlpkg/nalgeon/example/example.dylib
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
text.dylib
1 change: 1 addition & 0 deletions cmd/which/testdata/.sqlpkg/nalgeon/example/example.so
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
text.so
21 changes: 21 additions & 0 deletions cmd/which/testdata/.sqlpkg/nalgeon/example/sqlpkg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"owner": "nalgeon",
"name": "example",
"version": "0.1.0",
"homepage": "https://github.com/nalgeon/sqlite-example/blob/main/README.md",
"repository": "https://github.com/nalgeon/sqlite-example",
"specfile": "https://github.com/nalgeon/sqlite-example/raw/main/sqlpkg.json",
"authors": ["Anton Zhiyanov"],
"license": "MIT",
"description": "Example extension.",
"keywords": ["sqlite-example"],
"assets": {
"path": "https://github.com/nalgeon/sqlite-example/releases/download/0.1.0",
"files": {
"darwin-amd64": "example-macos-0.1.0-x86.zip",
"darwin-arm64": "example-macos-0.1.0-arm64.zip",
"linux-amd64": "example-linux-0.1.0-x86.zip",
"windows-amd64": "example-win-0.1.0-x64.zip"
}
}
}
13 changes: 13 additions & 0 deletions cmd/which/testdata/.sqlpkg/sqlite/broken/sqlpkg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"owner": "sqlite",
"name": "broken",
"assets": {
"path": "broken",
"files": {
"darwin-amd64": "broken.dylib",
"darwin-arm64": "broken.dylib",
"linux-amd64": "broken.so",
"windows-amd64": "broken.dll"
}
}
}
19 changes: 19 additions & 0 deletions cmd/which/testdata/.sqlpkg/sqlite/stmt/sqlpkg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"owner": "sqlite",
"name": "stmt",
"homepage": "https://www.sqlite.org/stmt.html",
"repository": "https://sqlite.org/src/file/ext/misc/",
"authors": ["Richard Hipp"],
"license": "Public Domain",
"description": "Natural string sorting and comparison.",
"symbols": ["sqlite_stmt"],
"assets": {
"path": "https://github.com/nalgeon/sqlean/releases/download/incubator",
"files": {
"darwin-amd64": "stmt.dylib",
"darwin-arm64": "stmt.dylib",
"linux-amd64": "stmt.so",
"windows-amd64": "stmt.dll"
}
}
}
1 change: 1 addition & 0 deletions cmd/which/testdata/.sqlpkg/sqlite/stmt/stmtvtab.dll
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
stmtvtab.dll
1 change: 1 addition & 0 deletions cmd/which/testdata/.sqlpkg/sqlite/stmt/stmtvtab.dylib
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
stmtvtab.dylib
1 change: 1 addition & 0 deletions cmd/which/testdata/.sqlpkg/sqlite/stmt/stmtvtab.so
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
stmtvtab.so
3 changes: 3 additions & 0 deletions cmd/which/testdata/sqlpkg.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"packages": {}
}
91 changes: 91 additions & 0 deletions cmd/which/which.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package which

import (
"errors"
"path/filepath"
"runtime"
"strings"

"sqlpkg.org/cli/cmd"
"sqlpkg.org/cli/fileio"
"sqlpkg.org/cli/logx"
"sqlpkg.org/cli/spec"
)

const help = "usage: sqlpkg which package"

// maps the OS name to the file extension
var fileExt = map[string]string{
"darwin": ".dylib",
"linux": ".so",
"windows": ".dll",
}

// Which prints a path to the extension file.
func Which(args []string) error {
if len(args) != 1 {
return errors.New(help)
}

parts := strings.Split(args[0], "/")
if len(parts) != 2 {
return errors.New("invalid package name")
}

owner, name := parts[0], parts[1]
pkgDir := spec.Dir(cmd.WorkDir, owner, name)
if !fileio.Exists(pkgDir) {
return errors.New("package is not installed")
}

path := findExact(pkgDir, name, runtime.GOOS)
if path != "" {
logx.Log(path)
return nil
}

paths := findByExt(pkgDir, name, runtime.GOOS)
if len(paths) == 0 {
return errors.New("extension file is not found")
}

logx.Log("exact match not found")
logx.Log("possible matches:")
for _, path := range paths {
logx.Log(path)
}

return nil
}

// findExact returns a path to the extension file
// if the extension file has the same name as the package itself.
func findExact(pkgDir, name, os string) string {
{
// e.g., text.dylib
pattern := filepath.Join(pkgDir, name+fileExt[os])
paths, _ := filepath.Glob(pattern)
if len(paths) != 0 {
return paths[0]
}
}
{
// e.g., text0.dylib
pattern := filepath.Join(pkgDir, name+"[0-9]"+fileExt[os])
paths, _ := filepath.Glob(pattern)
if len(paths) != 0 {
return paths[0]
}
}
// no exact match
return ""
}

// findByExt returns paths to files in the package dir
// that have an expected extension (e.g. textext.dylib)
func findByExt(pkgDir, name, os string) []string {
file := "*" + fileExt[os]
pattern := filepath.Join(pkgDir, file)
paths, _ := filepath.Glob(pattern)
return paths
}
80 changes: 80 additions & 0 deletions cmd/which/which_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package which

import (
"strings"
"testing"

"sqlpkg.org/cli/cmd"
)

func TestExact(t *testing.T) {
cmd.WorkDir = "."
repoDir, lockPath := cmd.SetupTestRepo(t)
cmd.CopyTestRepo(t, "")
mem := cmd.SetupTestLogger()

args := []string{"nalgeon/example"}
err := Which(args)
if err != nil {
t.Fatalf("which error: %v", err)
}

mem.Print()
mem.MustHave(t, ".sqlpkg/nalgeon/example/example.dylib")

cmd.TeardownTestRepo(t, repoDir, lockPath)
}

func TestPossible(t *testing.T) {
cmd.WorkDir = "."
repoDir, lockPath := cmd.SetupTestRepo(t)
cmd.CopyTestRepo(t, "")
mem := cmd.SetupTestLogger()

args := []string{"sqlite/stmt"}
err := Which(args)
if err != nil {
t.Fatalf("which error: %v", err)
}

mem.Print()
mem.MustHave(t, "exact match not found")
mem.MustHave(t, ".sqlpkg/sqlite/stmt/stmtvtab.dylib")

cmd.TeardownTestRepo(t, repoDir, lockPath)
}

func TestNotFound(t *testing.T) {
cmd.WorkDir = "."
repoDir, lockPath := cmd.SetupTestRepo(t)
cmd.CopyTestRepo(t, "")
cmd.SetupTestLogger()

args := []string{"sqlite/broken"}
err := Which(args)
if err == nil {
t.Fatalf("expected error, got nil")
}
if !strings.Contains(err.Error(), "extension file is not found") {
t.Fatalf("unexpected error: %v", err)
}

cmd.TeardownTestRepo(t, repoDir, lockPath)
}

func TestUnknown(t *testing.T) {
cmd.WorkDir = "."
repoDir, lockPath := cmd.SetupTestRepo(t)
cmd.SetupTestLogger()

args := []string{"sqlite/unknown"}
err := Which(args)
if err == nil {
t.Fatalf("expected error, got nil")
}
if !strings.Contains(err.Error(), "package is not installed") {
t.Fatalf("unexpected error: %v", err)
}

cmd.TeardownTestRepo(t, repoDir, lockPath)
}
3 changes: 3 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"sqlpkg.org/cli/cmd/list"
"sqlpkg.org/cli/cmd/uninstall"
"sqlpkg.org/cli/cmd/update"
"sqlpkg.org/cli/cmd/which"
"sqlpkg.org/cli/logx"
)

Expand Down Expand Up @@ -57,6 +58,8 @@ func execCommand(command string, args []string) error {
return list.List(args)
case "info":
return info.Info(args)
case "which":
return which.Which(args)
case "help":
return help.Help(args)
case "version":
Expand Down

0 comments on commit 0ac5dcf

Please sign in to comment.