Compare commits

...

6 Commits
v0.1.1 ... main

Author SHA1 Message Date
jolheiser b26f95ab49 Fix version (#11)
ci/woodpecker/push/goreleaser Pipeline was successful Details
ci/woodpecker/tag/goreleaser Pipeline was successful Details
Co-authored-by: jolheiser <john.olheiser@gmail.com>
Reviewed-on: #11
2022-07-25 04:43:32 +00:00
jolheiser 0d6109a4be Update for goreleaser (#10)
ci/woodpecker/push/goreleaser Pipeline was successful Details
ci/woodpecker/tag/goreleaser Pipeline was successful Details
Co-authored-by: jolheiser <john.olheiser@gmail.com>
Reviewed-on: #10
2022-07-25 03:49:44 +00:00
jolheiser 2da056a909 Refactor (#9)
continuous-integration/woodpecker the build was successful Details
Reviewed-on: #9
Co-authored-by: jolheiser <john.olheiser@gmail.com>
Co-committed-by: jolheiser <john.olheiser@gmail.com>
2021-11-03 04:37:57 +00:00
jolheiser a5d46fe7a2 Remove extra go mod (#8)
Reviewed-on: #8
Co-authored-by: jolheiser <john.olheiser@gmail.com>
Co-committed-by: jolheiser <john.olheiser@gmail.com>
2021-10-29 01:09:23 +00:00
John Olheiser ef0d29afa3 Rewrite (#7)
Closes #6

This is, more or less, a fundamental rewrite of gpm.

Co-authored-by: jolheiser <john.olheiser@gmail.com>
Reviewed-on: https://gitea.com/jolheiser/gpm/pulls/7
Co-authored-by: John Olheiser <john.olheiser@gmail.com>
Co-committed-by: John Olheiser <john.olheiser@gmail.com>
2021-02-28 13:04:05 +08:00
John Olheiser b23be07d5e Update dependencies and deprecate ioutil (#5)
Co-authored-by: jolheiser <john.olheiser@gmail.com>
Reviewed-on: https://gitea.com/jolheiser/gpm/pulls/5
Co-authored-by: John Olheiser <john.olheiser@gmail.com>
Co-committed-by: John Olheiser <john.olheiser@gmail.com>
2021-02-17 13:19:29 +08:00
41 changed files with 1455 additions and 868 deletions

View File

@ -1,23 +0,0 @@
linters:
enable:
- deadcode
- dogsled
- dupl
- errcheck
- funlen
- gocognit
- goconst
- gocritic
- gocyclo
- gofmt
- golint
- gosimple
- govet
- misspell
- prealloc
- staticcheck
- structcheck
- typecheck
- unparam
- unused
- varcheck

26
.goreleaser.yaml 100644
View File

@ -0,0 +1,26 @@
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
main: ./cmd/gpm
ldflags:
- "-s -w -X main.Version={{.Version}}"
archives:
- replacements:
386: i386
amd64: x86_64
format_overrides:
- goos: windows
format: zip
checksum:
name_template: 'checksums.txt'
release:
gitea:
owner: jolheiser
name: gpm
gitea_urls:
api: https://git.jojodev.com/api/v1/
download: https://git.jojodev.com

View File

@ -0,0 +1,39 @@
clone:
git:
image: woodpeckerci/plugin-git
settings:
tags: true
pipeline:
compliance:
image: golang:1.18
commands:
- go test -race ./...
- go vet ./...
- go run github.com/rs/zerolog/cmd/lint@latest go.jolheiser.com/gpm/cmd/gpm
when:
event: pull_request
build:
image: goreleaser/goreleaser
commands:
- goreleaser build --snapshot
when:
event: pull_request
release:
image: goreleaser/goreleaser
commands:
- goreleaser release
secrets: [ gitea_token ]
when:
event: tag
prune:
image: jolheiser/drone-gitea-prune
settings:
base: https://git.jojodev.com
token:
from_secret: gitea_token
when:
event: tag

66
DOCS.md 100644
View File

@ -0,0 +1,66 @@
# NAME
gpm - Go Package Manager
# SYNOPSIS
gpm
```
[--database|-d]=[value]
[--server|-s]=[value]
[--token|-t]=[value]
```
**Usage**:
```
gpm [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
```
# GLOBAL OPTIONS
**--database, -d**="": path to gpm database for server (default: /home/jolheiser/.config/gpm.db)
**--server, -s**="": gpm server to use (default: https://gpm.jolheiser.com)
**--token, -t**="": gpm auth token to use
# COMMANDS
## add, a
Add a package
**--force, -f**: Overwrite existing package without prompt
**--local, -l**: local mode
## get, g
Get package(s)
## list, ls, l
List local packages
## remove, rm
Remove package(s)
## search, s
Search packages
## server, web
Start the gpm server
**--port, -p**="": Port to run the gpm server on (default: 3333)
## update, u
Update a package
**--local, -l**: local mode

View File

@ -1,10 +0,0 @@
# To lint, install Earthly and run `earth +lint`
# This ensures the usage of the same version of golangci-lint
FROM golangci/golangci-lint:v1.31
WORKDIR /gpm
lint:
COPY . .
RUN golangci-lint run

19
LICENSE 100644
View File

@ -0,0 +1,19 @@
Copyright (c) 2021 John Olheiser
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,14 +0,0 @@
GO ?= go
VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')
.PHONY: build
build:
$(GO) build -ldflags '-s -w -X "go.jolheiser.com/gpm/config.Version=$(VERSION)"'
.PHONY: fmt
fmt:
$(GO) fmt ./...
.PHONY: test
test:
$(GO) test -race ./...

View File

@ -17,18 +17,21 @@ Using either a GPM server or local config, I can instead `gpm get cli` which fin
* `remove` - Remove a local package
* `list` - List local packages
* `config` - Change local configuration
* `export` - Export local packages to JSON
* `import` - Import JSON to local packages. Either give a path to a `.json` file, or a URL to a GPM server export endpoint
* e.g. `https://gpm.jolheiser.com/export`
* `get` - Get a list of packages
* e.g. `gpm get beaver survey toml homedir cli` to get all the modules needed for gpm itself (assuming the map resolves to the same packages)
* e.g. `gpm get zerolog survey bbolt cli chi` to get all the modules needed for gpm itself (assuming the map resolves to the same packages)
* `server` - Start a gpm server
### Server
If GPM doesn't find a package locally, it can call out to a configurable gpm server to find a package there instead.
gpm will call out to a gpm server to find a package.
This makes it much simpler to have a central library of packages rather than exporting and importing between environments.
Want to run your own server? It's very easy! This CLI comes packaged with the server inside, simply run `gpm server` to start up a GPM server.
Put it behind your favorite reverse proxy and it's ready to go!
Remember to set a `--token`!
Put it behind your favorite reverse proxy, and it's ready to go!
## License
[MIT](LICENSE)

59
client.go 100644
View File

@ -0,0 +1,59 @@
package gpm
import (
"context"
"io"
"net/http"
"strings"
)
const (
DefaultServer = "https://gpm.jolheiser.com"
TokenHeader = "X-GPM-Token"
)
// Client is a gpm client
type Client struct {
token string
server string
http *http.Client
}
// New returns a new Client
func New(token string, opts ...ClientOption) *Client {
c := &Client{
token: token,
server: DefaultServer,
http: http.DefaultClient,
}
for _, opt := range opts {
opt(c)
}
return c
}
// ClientOption is an option for a Client
type ClientOption func(*Client)
// WithHTTP sets the http.Client for a Client
func WithHTTP(client *http.Client) ClientOption {
return func(c *Client) {
c.http = client
}
}
// WithServer sets the gpm server for a Client
func WithServer(server string) ClientOption {
return func(c *Client) {
c.server = strings.TrimSuffix(server, "/")
}
}
func (c *Client) newRequest(ctx context.Context, method, url string, body io.Reader) (*http.Request, error) {
req, err := http.NewRequestWithContext(ctx, method, url, body)
if err != nil {
return nil, err
}
req.Header.Set(TokenHeader, c.token)
return req, nil
}

View File

@ -1,18 +0,0 @@
package cmd
import (
"go.jolheiser.com/gpm/config"
"github.com/urfave/cli/v2"
)
func NewFlags(cfg *config.Config) []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "url",
Aliases: []string{"u"},
Usage: "gpm server to use",
Value: cfg.GPMURL,
},
}
}

View File

@ -1,41 +0,0 @@
package cmd
import (
"go.jolheiser.com/gpm/config"
"github.com/AlecAivazis/survey/v2"
"github.com/urfave/cli/v2"
"go.jolheiser.com/beaver"
)
var Config = cli.Command{
Name: "config",
Aliases: []string{"cfg"},
Usage: "Configure local gpm",
Action: doConfig,
}
func doConfig(_ *cli.Context) error {
cfg, err := config.Load()
if err != nil {
return err
}
urlQuestion := &survey.Input{
Message: "gpm URL",
Default: cfg.GPMURL,
}
var urlAnswer string
if err := survey.AskOne(urlQuestion, &urlAnswer); err != nil {
return err
}
cfg.GPMURL = urlAnswer
if err := cfg.Save(); err != nil {
return err
}
beaver.Info("gpm URL saved!")
return nil
}

View File

@ -1,30 +0,0 @@
package cmd
import (
"fmt"
"go.jolheiser.com/gpm/config"
"github.com/urfave/cli/v2"
)
var Export = cli.Command{
Name: "export",
Usage: "Export JSON for local packages",
Action: doExport,
}
func doExport(_ *cli.Context) error {
cfg, err := config.Load()
if err != nil {
return err
}
export, err := cfg.Export()
if err != nil {
return err
}
fmt.Println(export)
return nil
}

View File

@ -1,113 +0,0 @@
package cmd
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/exec"
"strings"
"go.jolheiser.com/gpm/config"
"github.com/AlecAivazis/survey/v2"
"github.com/urfave/cli/v2"
"go.jolheiser.com/beaver"
)
var Get = cli.Command{
Name: "get",
Usage: "Get package(s)",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "ignore-local",
Usage: "Ignore local packages",
},
&cli.BoolFlag{
Name: "offline",
Usage: "Offline mode, return error instead of querying server",
},
},
Action: doGet,
}
func doGet(ctx *cli.Context) error {
cfg, err := config.Load()
if err != nil {
return err
}
pkgs := ctx.Args().Slice()
if len(pkgs) == 0 {
pkgsQuestion := &survey.Multiline{
Message: "Enter packages to get, one for each line",
}
var pkgsAnswer string
if err := survey.AskOne(pkgsQuestion, &pkgsAnswer); err != nil {
return err
}
pkgs = strings.Split(pkgsAnswer, "\n")
}
local := cfg.Packages.Map()
for _, pkg := range pkgs {
var url string
if u, ok := local[pkg]; ok && !ctx.Bool("ignore-local") {
url = u.Import
} else if !ctx.Bool("offline") {
u, err := queryServer(ctx.String("url"), pkg)
if err != nil {
beaver.Error(err)
continue
}
url = u
}
if url == "" {
beaver.Errorf("no package found for `%s`", pkg)
continue
}
beaver.Infof("getting `%s`...", pkg)
if err := goGet(url); err != nil {
beaver.Error(err)
}
}
return nil
}
func queryServer(server, name string) (string, error) {
endpoint := fmt.Sprintf("%s/package/%s", server, name)
resp, err := http.Get(endpoint)
if err != nil {
return "", fmt.Errorf("could not query server at `%s`", endpoint)
}
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("could not find server package for `%s`", name)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
defer resp.Body.Close()
var pkg config.Package
if err := json.Unmarshal(body, &pkg); err != nil {
return "", err
}
return pkg.Import, nil
}
func goGet(url string) error {
cmd := exec.Command("go", "get", url)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}

20
cmd/gpm/main.go 100644
View File

@ -0,0 +1,20 @@
package main
import (
"os"
"go.jolheiser.com/gpm/internal/cli"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
var Version = "develop"
func main() {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
if err := cli.New(Version).Run(os.Args); err != nil {
log.Err(err).Msg("")
}
}

View File

@ -1,78 +0,0 @@
package cmd
import (
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"strings"
"go.jolheiser.com/gpm/config"
"github.com/urfave/cli/v2"
"go.jolheiser.com/beaver"
)
var Import = cli.Command{
Name: "import",
Usage: "Import JSON for local packages",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "force",
Aliases: []string{"f"},
Usage: "Overwrite any existing packages without prompt",
},
},
Action: doImport,
}
func doImport(ctx *cli.Context) error {
cfg, err := config.Load()
if err != nil {
return err
}
if ctx.NArg() == 0 {
return errors.New("must point to either a JSON file or gpm server export endpoint")
}
arg := ctx.Args().First()
isJSON := strings.HasSuffix(arg, ".json")
isHTTP := strings.HasPrefix(arg, "http")
if !isJSON && !isHTTP {
return errors.New("must point to either a JSON file or gpm server export endpoint")
}
var data []byte
if isJSON {
data, err = ioutil.ReadFile(arg)
if err != nil {
return err
}
} else if isHTTP {
resp, err := http.Get(arg)
if err != nil {
return err
}
data, err = ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
defer resp.Body.Close()
}
var importPkgs []config.Package
if err := json.Unmarshal(data, &importPkgs); err != nil {
return err
}
cfg.AddPackages(ctx.Bool("force"), importPkgs...)
if err := cfg.Save(); err != nil {
return err
}
beaver.Info("Import complete")
return nil
}

View File

@ -1,27 +0,0 @@
package cmd
import (
"go.jolheiser.com/gpm/config"
"github.com/urfave/cli/v2"
"go.jolheiser.com/beaver"
)
var List = cli.Command{
Name: "list",
Aliases: []string{"l"},
Usage: "List local packages",
Action: doList,
}
func doList(_ *cli.Context) error {
cfg, err := config.Load()
if err != nil {
return err
}
for _, pkg := range cfg.Packages {
beaver.Infof("%s -> %s", pkg.Name, pkg.Import)
}
return nil
}

View File

@ -1,63 +0,0 @@
package cmd
import (
"fmt"
"strings"
"go.jolheiser.com/gpm/config"
"github.com/AlecAivazis/survey/v2"
"github.com/urfave/cli/v2"
"go.jolheiser.com/beaver"
)
var Remove = cli.Command{
Name: "remove",
Aliases: []string{"rm"},
Usage: "Remove package(s)",
Action: doRemove,
}
func doRemove(_ *cli.Context) error {
cfg, err := config.Load()
if err != nil {
return err
}
pkgQuestion := &survey.Input{
Message: "Package name",
}
var pkgAnswer string
if err := survey.AskOne(pkgQuestion, &pkgAnswer); err != nil {
return err
}
for idx, p := range cfg.Packages {
if strings.EqualFold(p.Name, pkgAnswer) {
confirm := &survey.Confirm{
Message: fmt.Sprintf("Are you sure you want to remove %s (%s) ?", p.Name, p.Import),
Default: false,
}
var answer bool
if err := survey.AskOne(confirm, &answer); err != nil {
return err
}
if answer {
cfg.Packages = append(cfg.Packages[:idx], cfg.Packages[idx+1:]...)
if err := cfg.Save(); err != nil {
return err
}
beaver.Infof("Removed `%s` from local gpm.", p.Name)
break
}
beaver.Infof("Did not remove `%s` from local gpm.", p.Name)
break
}
}
return nil
}

View File

@ -1,89 +0,0 @@
package cmd
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"go.jolheiser.com/gpm/config"
"github.com/AlecAivazis/survey/v2"
"github.com/urfave/cli/v2"
"go.jolheiser.com/beaver"
)
var Search = cli.Command{
Name: "search",
Aliases: []string{"s"},
Usage: "Search packages",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "local",
Usage: "Search locally",
},
},
Action: doSearch,
}
func doSearch(ctx *cli.Context) error {
cfg, err := config.Load()
if err != nil {
return err
}
packageMap := cfg.Packages.Map()
packageSlice := cfg.Packages.Slice()
if !ctx.Bool("local") {
export, err := queryExport(ctx.String("url"))
if err != nil {
return err
}
packageMap = export.Map()
packageSlice = export.Slice()
}
q := &survey.MultiSelect{
Message: "Select packages",
Options: packageSlice,
}
var a []string
if err := survey.AskOne(q, &a); err != nil {
return err
}
for _, name := range a {
pkg, ok := packageMap[name]
if !ok {
beaver.Errorf("could not find package for `%s`", name)
continue
}
beaver.Infof("getting `%s`...", name)
if err := goGet(pkg.Import); err != nil {
beaver.Error(err)
}
}
return nil
}
func queryExport(server string) (config.Packages, error) {
resp, err := http.Get(fmt.Sprintf("%s/export", server))
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var importPkgs config.Packages
if err := json.Unmarshal(data, &importPkgs); err != nil {
return nil, err
}
return importPkgs, nil
}

View File

@ -1,39 +0,0 @@
package cmd
import (
"fmt"
"net/http"
"go.jolheiser.com/gpm/config"
"go.jolheiser.com/gpm/router"
"github.com/urfave/cli/v2"
"go.jolheiser.com/beaver"
)
var Server = cli.Command{
Name: "server",
Usage: "Start the gpm server",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "port",
Aliases: []string{"p"},
Usage: "Port to run the gpm server on",
Value: "3333",
},
},
Action: doServer,
}
func doServer(ctx *cli.Context) error {
cfg, err := config.Load()
if err != nil {
return err
}
beaver.Infof("Running gpm server at http://localhost:%s", ctx.String("port"))
if err := http.ListenAndServe(fmt.Sprintf(":%s", ctx.String("port")), router.New(cfg)); err != nil {
return err
}
return nil
}

View File

@ -1,146 +0,0 @@
package config
import (
"encoding/json"
"fmt"
"os"
"path"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/pelletier/go-toml"
"go.jolheiser.com/beaver"
)
var Version = "develop"
type Config struct {
path string
GPMURL string `toml:"gpm-url" json:"gpm_url"`
Packages Packages `toml:"package" json:"packages"`
}
type Package struct {
Name string `toml:"name" json:"name"`
Import string `toml:"import" json:"import"`
}
type Packages []Package
func Load() (*Config, error) {
home, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("could not get user home dir: %v", err)
}
home = path.Join(home, ".gpm")
homeEnv := os.Getenv("GPM_HOME")
if homeEnv != "" {
home = homeEnv
}
configPath := path.Join(home, "gpm.toml")
configEnv := os.Getenv("GPM_CONFIG")
if configEnv != "" {
configPath = configEnv
}
if _, err := os.Stat(configPath); os.IsNotExist(err) {
if err := os.MkdirAll(path.Dir(configPath), os.ModePerm); err != nil {
return nil, fmt.Errorf("could not create gpm home: %v", err)
}
if _, err := os.Create(configPath); err != nil {
return nil, fmt.Errorf("could not create gpm config: %v", err)
}
}
var cfg Config
tree, err := toml.LoadFile(configPath)
if err != nil {
return nil, fmt.Errorf("could not decode gpm config: %v", err)
}
if err = tree.Unmarshal(&cfg); err != nil {
return nil, fmt.Errorf("could not unmarshal config: %v", err)
}
dupe := make(map[string]bool)
for _, pkg := range cfg.Packages {
name := strings.ToLower(pkg.Name)
if ok := dupe[name]; ok {
return nil, fmt.Errorf("duplicate package for %s", pkg.Name)
}
dupe[name] = true
}
cfg.path = configPath
return &cfg, nil
}
func (c *Config) Save() error {
fi, err := os.Create(c.path)
if err != nil {
return err
}
defer fi.Close()
if err := toml.NewEncoder(fi).Encode(c); err != nil {
return err
}
return nil
}
func (c *Config) Export() (string, error) {
data, err := json.Marshal(c.Packages)
return string(data), err
}
func (p Packages) Slice() []string {
pkgs := make([]string, len(p))
for idx, pkg := range p {
pkgs[idx] = fmt.Sprintf("%s (%s)", pkg.Name, pkg.Import)
}
return pkgs
}
func (p Packages) Map() map[string]Package {
pkgs := make(map[string]Package)
for _, pkg := range p {
pkgs[pkg.Name] = pkg
}
return pkgs
}
func (c *Config) AddPackages(force bool, pkgs ...Package) {
for _, pkg := range pkgs {
for idx, p := range c.Packages {
if strings.EqualFold(p.Name, pkg.Name) {
if force {
c.Packages[idx] = pkg
break
}
forceQuestion := &survey.Confirm{
Message: fmt.Sprintf("Package `%s` (%s) already exists. Overwrite with `%s`?", p.Name, p.Import, p.Import),
Default: false,
}
var forceAnswer bool
if err := survey.AskOne(forceQuestion, &forceAnswer); err != nil {
beaver.Error(err)
break
}
if !forceAnswer {
beaver.Errorf("leaving package `%s` as-is", pkg.Name)
break
}
c.Packages[idx] = pkg
break
}
}
c.Packages = append(c.Packages, pkg)
}
}

12
docker/Dockerfile 100644
View File

@ -0,0 +1,12 @@
FROM golang:1.16-alpine as builder
RUN apk --no-cache add build-base git
COPY . /app
WORKDIR /app
RUN go build ./cmd/gpm
FROM alpine:latest
LABEL maintainer="john.olheiser@gmail.com"
COPY --from=builder /app/gpm gpm
EXPOSE 3333
ENV GPM_TOKEN=""
ENTRYPOINT exec gpm --token $GPM_TOKEN server

View File

@ -0,0 +1,9 @@
version: "2"
services:
vanity:
image: jolheiser/gpm:latest
environment:
- GPM_TOKEN=<token>
restart: always
ports:
- "80:3333"

30
docs.go 100644
View File

@ -0,0 +1,30 @@
//go:build generate
// +build generate
package main
import (
"go.jolheiser.com/gpm/internal/cli"
"os"
)
//go:generate go run docs.go
func main() {
app := cli.New("docs")
md, err := app.ToMarkdown()
if err != nil {
panic(err)
}
fi, err := os.Create("DOCS.md")
if err != nil {
panic(err)
}
if _, err := fi.WriteString(md); err != nil {
panic(err)
}
if err := fi.Close(); err != nil {
panic(err)
}
}

24
go.mod
View File

@ -3,15 +3,19 @@ module go.jolheiser.com/gpm
go 1.15
require (
github.com/AlecAivazis/survey/v2 v2.1.1
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/go-chi/chi v4.1.2+incompatible
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/AlecAivazis/survey/v2 v2.3.5
github.com/go-chi/chi/v5 v5.0.7
github.com/go-chi/render v1.0.1
github.com/kr/pty v1.1.4 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/pelletier/go-toml v1.8.1
github.com/urfave/cli/v2 v2.2.0
go.jolheiser.com/beaver v1.0.2
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 // indirect
golang.org/x/sys v0.0.0-20201006155630-ac719f4daadf // indirect
golang.org/x/text v0.3.3 // indirect
github.com/rs/zerolog v1.27.0
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/urfave/cli/v2 v2.11.1
go.etcd.io/bbolt v1.3.6
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
golang.org/x/tools v0.1.7 // indirect
gopkg.in/yaml.v2 v2.2.3 // indirect
)

109
go.sum
View File

@ -1,70 +1,123 @@
github.com/AlecAivazis/survey/v2 v2.1.1 h1:LEMbHE0pLj75faaVEKClEX1TM4AJmmnOh9eimREzLWI=
github.com/AlecAivazis/survey/v2 v2.1.1/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/AlecAivazis/survey/v2 v2.2.7 h1:5NbxkF4RSKmpywYdcRgUmos1o+roJY8duCLZXbVjoig=
github.com/AlecAivazis/survey/v2 v2.2.7/go.mod h1:9DYvHgXtiXm6nCn+jXnOXLKbH+Yo9u8fAS/SduGdoPk=
github.com/AlecAivazis/survey/v2 v2.3.5 h1:A8cYupsAZkjaUmhtTYv3sSqc7LO5mp1XDfqe5E/9wRQ=
github.com/AlecAivazis/survey/v2 v2.3.5/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ=
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.26.0 h1:ORM4ibhEZeTeQlCojCK2kPz1ogAY4bGs4tD+SaAdGaE=
github.com/rs/zerolog v1.26.0/go.mod h1:yBiM87lvSqX8h0Ww4sdzNSkVYZ8dL2xjZJG1lAuGZEo=
github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs=
github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.1 h1:52QO5WkIUcHGIR7EnGagH88x1bUzqGXTC5/1bDTUQ7U=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4=
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
go.jolheiser.com/beaver v1.0.2 h1:KA2D6iO8MQhZi1nZYi/Chak/f1Cxfrs6b1XO623+Khk=
go.jolheiser.com/beaver v1.0.2/go.mod h1:7X4F5+XOGSC3LejTShoBdqtRCnPWcnRgmYGmG3EKW8g=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/urfave/cli/v2 v2.11.1 h1:UKK6SP7fV3eKOefbS87iT9YHefv7iB/53ih6e+GNAsE=
github.com/urfave/cli/v2 v2.11.1/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 h1:8dUaAV7K4uHsF56JQWkprecIQKdPHtR9jCHF5nB8uzc=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201006155630-ac719f4daadf h1:Bg47KQy0JhTHuf4sLiQwTMKwUMfSDwgSGatrxGR7nLM=
golang.org/x/sys v0.0.0-20201006155630-ac719f4daadf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,37 +1,43 @@
package cmd
package cli
import (
"context"
"regexp"
"strings"
"go.jolheiser.com/gpm/config"
"go.jolheiser.com/gpm"
"go.jolheiser.com/gpm/internal/database"
"github.com/AlecAivazis/survey/v2"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
"go.jolheiser.com/beaver"
)
var Add = cli.Command{
Name: "add",
Usage: "Add a package",
Name: "add",
Aliases: []string{"a"},
Usage: "Add a package",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "force",
Aliases: []string{"f"},
Usage: "Overwrite existing package without prompt",
Name: "force",
Aliases: []string{"f"},
Usage: "Overwrite existing package without prompt",
Destination: &forceFlag,
},
&cli.BoolFlag{
Name: "local",
Aliases: []string{"l"},
Usage: "local mode",
Destination: &localFlag,
},
},
Before: localOrToken,
Action: doAdd,
}
var vPattern = regexp.MustCompile(`v\d+$`)
func doAdd(ctx *cli.Context) error {
cfg, err := config.Load()
if err != nil {
return err
}
func doAdd(_ *cli.Context) error {
goGetQuestion := &survey.Input{
Message: "Package go-get import",
}
@ -58,16 +64,26 @@ func doAdd(ctx *cli.Context) error {
return err
}
pkg := config.Package{
pkg := gpm.Package{
Name: nameAnswer,
Import: goGetAnswer,
}
cfg.AddPackages(ctx.Bool("force"), pkg)
if err := cfg.Save(); err != nil {
return err
if localFlag {
db, err := database.Load(databaseFlag)
if err != nil {
return err
}
if err := db.PutPackage(pkg); err != nil {
return err
}
} else {
client := gpm.New(tokenFlag, gpm.WithServer(serverFlag))
if err := client.Add(context.Background(), pkg); err != nil {
return err
}
}
beaver.Infof("Added `%s` to local gpm.", nameAnswer)
log.Info().Msgf("Added %q", nameAnswer)
return nil
}

View File

@ -0,0 +1,99 @@
package cli
import (
"context"
"errors"
"os"
"path/filepath"
"go.jolheiser.com/gpm"
"go.jolheiser.com/gpm/internal/database"
"github.com/urfave/cli/v2"
)
func New(version string) *cli.App {
app := cli.NewApp()
app.Name = "gpm"
app.Usage = "Go Package Manager"
app.Version = version
app.Commands = []*cli.Command{
&Add,
&Get,
&List,
&Remove,
&Search,
&Server,
&Update,
}
app.Flags = []cli.Flag{
&cli.StringFlag{
Name: "server",
Aliases: []string{"s"},
Usage: "gpm server to use",
Value: gpm.DefaultServer,
EnvVars: []string{"GPM_SERVER"},
Destination: &serverFlag,
},
&cli.StringFlag{
Name: "token",
Aliases: []string{"t"},
Usage: "gpm auth token to use",
DefaultText: "${GPM_TOKEN}",
EnvVars: []string{"GPM_TOKEN"},
Destination: &tokenFlag,
},
&cli.StringFlag{
Name: "database",
Aliases: []string{"d"},
Usage: "path to gpm database for server",
Value: dbPath(),
DefaultText: "`${USERCONFIG}/gpm.db` or `${BINPATH}/gpm.db`",
EnvVars: []string{"GPM_DATABASE"},
Destination: &databaseFlag,
},
}
return app
}
func dbPath() string {
fn := "gpm.db"
home, err := os.UserConfigDir()
if err != nil {
bin, err := os.Executable()
if err != nil {
return fn
}
return filepath.Join(filepath.Dir(bin), fn)
}
return filepath.Join(home, fn)
}
func localOrToken(_ *cli.Context) error {
if localFlag && tokenFlag == "" {
return errors.New("server interaaction requires --token")
}
return nil
}
func listPackages() ([]gpm.Package, error) {
var pkgs []gpm.Package
if localFlag {
db, err := database.Load(databaseFlag)
if err != nil {
return pkgs, err
}
pkgs, err = db.Packages()
if err != nil {
return pkgs, err
}
} else {
client := gpm.New(tokenFlag, gpm.WithServer(serverFlag))
info, err := client.Info(context.Background())
if err != nil {
return pkgs, err
}
pkgs = info.Packages
}
return pkgs, nil
}

View File

@ -0,0 +1,11 @@
package cli
var (
serverFlag string
tokenFlag string
databaseFlag string
localFlag bool
forceFlag bool
portFlag int
)

View File

@ -0,0 +1,60 @@
package cli
import (
"context"
"os"
"os/exec"
"strings"
"go.jolheiser.com/gpm"
"github.com/AlecAivazis/survey/v2"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
)
var Get = cli.Command{
Name: "get",
Aliases: []string{"g"},
Usage: "Get package(s)",
Action: doGet,
}
func doGet(ctx *cli.Context) error {
pkgs := ctx.Args().Slice()
if len(pkgs) == 0 {
pkgsQuestion := &survey.Multiline{
Message: "Enter packages to get, one for each line",
}
var pkgsAnswer string
if err := survey.AskOne(pkgsQuestion, &pkgsAnswer); err != nil {
return err
}
pkgs = strings.Split(pkgsAnswer, "\n")
}
client := gpm.New(tokenFlag, gpm.WithServer(serverFlag))
for _, p := range pkgs {
pkg, err := client.Get(context.Background(), p)
if err != nil {
log.Err(err).Msg("")
continue
}
log.Info().Msgf("getting %q...", pkg.Import)
if err := goGet(pkg.Import); err != nil {
log.Err(err).Msg("")
}
}
return nil
}
func goGet(url string) error {
cmd := exec.Command("go", "get", url)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}

View File

@ -0,0 +1,29 @@
package cli
import (
"fmt"
"os"
"text/tabwriter"
"github.com/urfave/cli/v2"
)
var List = cli.Command{
Name: "list",
Aliases: []string{"ls", "l"},
Usage: "List local packages",
Action: doList,
}
func doList(_ *cli.Context) error {
pkgs, err := listPackages()
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
for _, pkg := range pkgs {
s := fmt.Sprintf("%s\t%s\n", pkg.Name, pkg.Import)
_, _ = w.Write([]byte(s))
}
return w.Flush()
}

View File

@ -0,0 +1,67 @@
package cli
import (
"context"
"go.jolheiser.com/gpm"
"go.jolheiser.com/gpm/internal/database"
"github.com/AlecAivazis/survey/v2"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
)
var Remove = cli.Command{
Name: "remove",
Aliases: []string{"rm"},
Usage: "Remove package(s)",
Before: localOrToken,
Action: doRemove,
}
func doRemove(_ *cli.Context) error {
pkgs, err := listPackages()
if err != nil {
return err
}
pkgSlice := make([]string, len(pkgs))
pkgMap := make(map[string]gpm.Package)
for idx, pkg := range pkgs {
pkgSlice[idx] = pkg.Name
pkgMap[pkg.Name] = pkg
}
pkgQuestion := &survey.Select{
Message: "Select package to remove",
Options: pkgSlice,
}
var pkgName string
if err := survey.AskOne(pkgQuestion, &pkgName); err != nil {
return err
}
pkg := gpm.Package{
Name: pkgName,
Import: pkgMap[pkgName].Import,
}
if localFlag {
db, err := database.Load(databaseFlag)
if err != nil {
return err
}
if err := db.RemovePackage(pkg.Name); err != nil {
return err
}
} else {
client := gpm.New(tokenFlag, gpm.WithServer(serverFlag))
if err := client.Remove(context.Background(), pkg); err != nil {
return err
}
}
log.Info().Msgf("Removed %q", pkgName)
return nil
}

View File

@ -0,0 +1,54 @@
package cli
import (
"go.jolheiser.com/gpm"
"github.com/AlecAivazis/survey/v2"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
)
var Search = cli.Command{
Name: "search",
Aliases: []string{"s"},
Usage: "Search packages",
Action: doSearch,
}
func doSearch(_ *cli.Context) error {
pkgs, err := listPackages()
if err != nil {
return err
}
pkgSlice := make([]string, len(pkgs))
pkgMap := make(map[string]gpm.Package)
for idx, pkg := range pkgs {
pkgSlice[idx] = pkg.Name
pkgMap[pkg.Name] = pkg
}
q := &survey.MultiSelect{
Message: "Select packages",
Options: pkgSlice,
}
var a []string
if err := survey.AskOne(q, &a); err != nil {
return err
}
for _, name := range a {
pkg, ok := pkgMap[name]
if !ok {
log.Error().Msgf("could not find package for %q", name)
continue
}
log.Info().Msgf("getting %q...", name)
if err := goGet(pkg.Import); err != nil {
log.Err(err).Msg("")
}
}
return nil
}

View File

@ -0,0 +1,47 @@
package cli
import (
"errors"
"fmt"
"net/http"
"go.jolheiser.com/gpm/internal/database"
"go.jolheiser.com/gpm/internal/router"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
)
var Server = cli.Command{
Name: "server",
Aliases: []string{"web"},
Usage: "Start the gpm server",
Flags: []cli.Flag{
&cli.IntFlag{
Name: "port",
Aliases: []string{"p"},
Usage: "Port to run the gpm server on",
Value: 3333,
EnvVars: []string{"GPM_PORT"},
Destination: &portFlag,
},
},
Action: doServer,
}
func doServer(ctx *cli.Context) error {
if tokenFlag == "" {
return errors.New("gpm server requires --token")
}
db, err := database.Load(databaseFlag)
if err != nil {
log.Fatal().Msgf("could not load database at %q: %v", databaseFlag, err)
}
log.Info().Msgf("Running gpm server at http://localhost:%d", portFlag)
if err := http.ListenAndServe(fmt.Sprintf(":%d", portFlag), router.New(tokenFlag, ctx.App.Version, db)); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,85 @@
package cli
import (
"context"
"go.jolheiser.com/gpm"
"go.jolheiser.com/gpm/internal/database"
"github.com/AlecAivazis/survey/v2"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
)
var Update = cli.Command{
Name: "update",
Aliases: []string{"u"},
Usage: "Update a package",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "local",
Aliases: []string{"l"},
Usage: "local mode",
Destination: &localFlag,
},
},
Before: localOrToken,
Action: doUpdate,
}
func doUpdate(_ *cli.Context) error {
pkgs, err := listPackages()
if err != nil {
return err
}
pkgSlice := make([]string, len(pkgs))
pkgMap := make(map[string]gpm.Package)
for idx, pkg := range pkgs {
pkgSlice[idx] = pkg.Name
pkgMap[pkg.Name] = pkg
}
pkgQuestion := &survey.Select{
Message: "Select package to update",
Options: pkgSlice,
}
var pkgName string
if err := survey.AskOne(pkgQuestion, &pkgName); err != nil {
return err
}
importQuestion := &survey.Input{
Message: "New import path",
Default: pkgMap[pkgName].Import,
}
var importPath string
if err := survey.AskOne(importQuestion, &importPath); err != nil {
return err
}
pkg := gpm.Package{
Name: pkgName,
Import: importPath,
}
if localFlag {
db, err := database.Load(databaseFlag)
if err != nil {
return err
}
if err := db.PutPackage(pkg); err != nil {
return err
}
} else {
client := gpm.New(tokenFlag, gpm.WithServer(serverFlag))
if err := client.Update(context.Background(), pkg); err != nil {
return err
}
}
log.Info().Msgf("Updated %q", pkgName)
return nil
}

View File

@ -0,0 +1,79 @@
package database
import (
"bytes"
"encoding/json"
"os"
"path/filepath"
"go.jolheiser.com/gpm"
"go.etcd.io/bbolt"
)
var packageBucket = []byte("packages")
type Database struct {
db *bbolt.DB
}
func Load(dbPath string) (*Database, error) {
if err := os.MkdirAll(filepath.Dir(dbPath), os.ModePerm); err != nil {
return nil, err
}
db, err := bbolt.Open(dbPath, os.ModePerm, nil)
if err != nil {
return nil, err
}
return &Database{
db: db,
}, db.Update(func(tx *bbolt.Tx) error {
_, err := tx.CreateBucketIfNotExists(packageBucket)
return err
})
}
func (d *Database) Package(name string) (gpm.Package, error) {
var pkg gpm.Package
data, err := d.PackageJSON(name)
if err != nil {
return pkg, err
}
return pkg, json.NewDecoder(bytes.NewReader(data)).Decode(&pkg)
}
func (d *Database) PackageJSON(name string) (pkg []byte, err error) {
return pkg, d.db.View(func(tx *bbolt.Tx) error {
pkg = tx.Bucket(packageBucket).Get([]byte(name))
return nil
})
}
func (d *Database) Packages() (pkgs []gpm.Package, err error) {
return pkgs, d.db.View(func(tx *bbolt.Tx) error {
return tx.Bucket(packageBucket).ForEach(func(key, val []byte) error {
var pkg gpm.Package
if err := json.NewDecoder(bytes.NewReader(val)).Decode(&pkg); err != nil {
return err
}
pkgs = append(pkgs, pkg)
return nil
})
})
}
func (d *Database) PutPackage(pkg gpm.Package) error {
return d.db.Update(func(tx *bbolt.Tx) error {
data, err := json.Marshal(pkg)
if err != nil {
return err
}
return tx.Bucket(packageBucket).Put([]byte(pkg.Name), data)
})
}
func (d *Database) RemovePackage(name string) error {
return d.db.Update(func(tx *bbolt.Tx) error {
return tx.Bucket(packageBucket).Delete([]byte(name))
})
}

View File

@ -0,0 +1,86 @@
package database
import (
"os"
"path/filepath"
"testing"
"go.jolheiser.com/gpm"
)
var db *Database
func TestMain(m *testing.M) {
tmp, err := os.MkdirTemp(os.TempDir(), "gpm")
if err != nil {
panic(err)
}
dbPath := filepath.Join(tmp, "gpm.db")
db, err = Load(dbPath)
if err != nil {
panic(err)
}
code := m.Run()
// Cleanup
if err := os.RemoveAll(tmp); err != nil {
panic(err)
}
os.Exit(code)
}
func TestPackage(t *testing.T) {
// Does not exist
_, err := db.Package("test")
if err == nil {
t.Log("test package should not exist")
t.FailNow()
}
// Add
pkg := gpm.Package{
Name: "test",
Import: "gitea.com/test/testing",
}
err = db.PutPackage(pkg)
if err != nil {
t.Logf("could not put test package: %v\n", err)
t.FailNow()
}
// Update
pkg.Import = "gitea.com/testing/test"
err = db.PutPackage(pkg)
if err != nil {
t.Logf("could not put test package: %v\n", err)
t.FailNow()
}
// Check
p, err := db.Package("test")
if err != nil {
t.Logf("should find test package: %v\n", err)
t.FailNow()
}
if p.Import != pkg.Import {
t.Logf("test package did not match update:\n\texpected: %s\n\t got: %s\n", pkg.Import, p.Import)
t.FailNow()
}
// Remove
err = db.RemovePackage("test")
if err != nil {
t.Log("could not remove test package")
t.FailNow()
}
// Check
_, err = db.Package("test")
if err == nil {
t.Log("test package should not exist after being removed")
t.FailNow()
}
}

View File

@ -0,0 +1,129 @@
package router
import (
"net/http"
"time"
"go.jolheiser.com/gpm"
"go.jolheiser.com/gpm/internal/database"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/render"
"github.com/rs/zerolog/log"
)
func New(token, version string, db *database.Database) *chi.Mux {
r := chi.NewRouter()
r.Use(middleware.RedirectSlashes)
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(30 * time.Second))
r.Get("/", handleHome(version, db))
r.Post("/", addUpdatePackage(db, token))
r.Patch("/", addUpdatePackage(db, token))
r.Delete("/", removePackage(db, token))
r.Get("/{name}", getPackage(db))
return r
}
func handleHome(version string, db *database.Database) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
pkgs, err := db.Packages()
if err != nil {
log.Err(err).Msg("")
return
}
render.JSON(w, r, gpm.Info{
Version: version,
NumPackages: len(pkgs),
Packages: pkgs,
})
}
}
func getPackage(db *database.Database) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
pkg, err := db.Package(name)
if err != nil {
w.WriteHeader(http.StatusNotFound)
render.JSON(w, r, struct{}{})
return
}
render.JSON(w, r, pkg)
}
}
func addUpdatePackage(db *database.Database, token string) func(http.ResponseWriter, *http.Request) {
return func(res http.ResponseWriter, req *http.Request) {
if req.Header.Get(gpm.TokenHeader) != token {
res.WriteHeader(http.StatusUnauthorized)
return
}
var pkg gpm.Package
if err := render.DecodeJSON(req.Body, &pkg); err != nil {
res.WriteHeader(http.StatusBadRequest)
return
}
defer req.Body.Close()
exists, err := db.PackageJSON(pkg.Name)
if err != nil {
res.WriteHeader(http.StatusInternalServerError)
return
}
switch req.Method {
case http.MethodPost:
if exists != nil {
res.WriteHeader(http.StatusConflict)
return
}
case http.MethodPatch:
if exists == nil {
res.WriteHeader(http.StatusNotFound)
return
}
}
if err := db.PutPackage(pkg); err != nil {
res.WriteHeader(http.StatusInternalServerError)
return
}
switch req.Method {
case http.MethodPost:
res.WriteHeader(http.StatusCreated)
case http.MethodPatch:
res.WriteHeader(http.StatusOK)
}
}
}
func removePackage(db *database.Database, token string) func(http.ResponseWriter, *http.Request) {
return func(res http.ResponseWriter, req *http.Request) {
if req.Header.Get(gpm.TokenHeader) != token {
res.WriteHeader(http.StatusUnauthorized)
return
}
var pkg gpm.Package
if err := render.DecodeJSON(req.Body, &pkg); err != nil {
res.WriteHeader(http.StatusBadRequest)
return
}
defer req.Body.Close()
if err := db.RemovePackage(pkg.Name); err != nil {
res.WriteHeader(http.StatusInternalServerError)
return
}
res.WriteHeader(http.StatusOK)
}
}

View File

@ -0,0 +1,166 @@
package router
import (
"context"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"go.jolheiser.com/gpm"
"go.jolheiser.com/gpm/internal/database"
"github.com/rs/zerolog/log"
)
var (
server *httptest.Server
token = "TestingRouter"
)
// NOTE: The router test is more or less a copy/paste from go-gpm
// However, this ensures that testing is the same with the "real" router and DB
func TestMain(m *testing.M) {
tmp, err := os.MkdirTemp(os.TempDir(), "gpm")
if err != nil {
panic(err)
}
dbPath := filepath.Join(tmp, "gpm.db")
db, err := database.Load(dbPath)
if err != nil {
log.Fatal().Msgf("could not load database at %q: %v", dbPath, err)
}
server = httptest.NewServer(New(token, "test", db))
code := m.Run()
// Cleanup
if err := os.RemoveAll(tmp); err != nil {
panic(err)
}
os.Exit(code)
}
func TestRouter(t *testing.T) {
ctx := context.Background()
client := gpm.New("", gpm.WithServer(server.URL))
// Info
checkInfo(t, client, 0)
pkg1 := gpm.Package{
Name: "test1",
Import: "gitea.com/test/testing",
}
pkg2 := gpm.Package{
Name: "test2",
Import: "gitea.com/testing/test",
}
// Add (without token)
if err := client.Add(ctx, pkg1); err == nil {
t.Log("adding without token should fail")
t.Fail()
}
// Add (with token)
client = gpm.New(token, gpm.WithServer(server.URL))
checkAdd(t, client, pkg1, pkg2)
// Info (after second package)
checkInfo(t, client, 2)
// Check package
checkGet(t, client, pkg2)
// Update package
checkUpdate(t, client, pkg1)
// Remove
checkRemove(t, client, pkg1)
// Info (final)
checkInfo(t, client, 1)
}
func checkInfo(t *testing.T, client *gpm.Client, numPackages int) {
info, err := client.Info(context.Background())
if err != nil {
t.Logf("info should not return error: %v\n", err)
t.Fail()
}
if info.Version != "test" || info.NumPackages != numPackages {
t.Log("info did not match expected")
t.Fail()
}
}
func checkGet(t *testing.T, client *gpm.Client, pkg gpm.Package) {
ctx := context.Background()
_, err := client.Get(ctx, "test3")
if err == nil {
t.Log("should not be able to get invalid package")
t.Fail()
}
// Check valid package
p, err := client.Get(ctx, "test2")
if err != nil {
t.Logf("should not be able to get invalid package: %v\n", err)
t.Fail()
}
if p != pkg {
t.Log("valid package should match pkg")
t.Fail()
}
}
func checkAdd(t *testing.T, client *gpm.Client, pkg1, pkg2 gpm.Package) {
ctx := context.Background()
if err := client.Add(ctx, pkg1); err != nil {
t.Logf("pkg1 should be added: %v\n", err)
t.Fail()
}
if err := client.Add(ctx, pkg2); err != nil {
t.Logf("pkg2 should be added: %v\n", err)
t.Fail()
}
// Duplicate package
if err := client.Add(ctx, pkg1); err == nil {
t.Log("pkg1 should already exist")
t.Fail()
}
}
func checkUpdate(t *testing.T, client *gpm.Client, pkg gpm.Package) {
ctx := context.Background()
// Update invalid package
if err := client.Update(ctx, gpm.Package{Name: "test4", Import: "gitea.com/invalid"}); err == nil {
t.Log("should not be able to update invalid package")
t.Fail()
}
// Update valid package
pkg.Import = "gitea.com/tester/testing"
if err := client.Update(ctx, pkg); err != nil {
t.Logf("should be able to update valid package: %v\n", err)
t.Fail()
}
}
func checkRemove(t *testing.T, client *gpm.Client, pkg gpm.Package) {
ctx := context.Background()
if err := client.Remove(ctx, pkg); err != nil {
t.Logf("should be able to remove package: %v\n", err)
t.Fail()
}
// Remove (idempotent)
if err := client.Remove(ctx, pkg); err != nil {
t.Logf("should be able to remove package idempotently: %v\n", err)
t.Fail()
}
}

38
main.go
View File

@ -1,38 +0,0 @@
package main
import (
"os"
"go.jolheiser.com/gpm/cmd"
"go.jolheiser.com/gpm/config"
"github.com/urfave/cli/v2"
"go.jolheiser.com/beaver"
)
func main() {
cfg, err := config.Load()
if err != nil {
beaver.Fatal(err)
}
app := cli.NewApp()
app.Name = "gpm"
app.Usage = "Go Package Manager"
app.Version = config.Version
app.Commands = []*cli.Command{
&cmd.Add,
&cmd.Remove,
&cmd.List,
&cmd.Get,
&cmd.Import,
&cmd.Export,
&cmd.Config,
&cmd.Server,
&cmd.Search,
}
app.Flags = cmd.NewFlags(cfg)
if err := app.Run(os.Args); err != nil {
beaver.Error(err)
}
}

124
package.go 100644
View File

@ -0,0 +1,124 @@
package gpm
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
)
// Package is a gpm package
type Package struct {
Name string `json:"name"`
Import string `json:"import"`
}
// Info is gpm information, such as version and list of packages
type Info struct {
Version string `json:"version"`
NumPackages int `json:"num_packages"`
Packages []Package `json:"packages"`
}
// Info gets Info from a gpm server
func (c *Client) Info(ctx context.Context) (Info, error) {
var info Info
resp, err := c.crud(ctx, Package{}, http.MethodGet)
if err != nil {
return info, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return info, fmt.Errorf("could not get info: %s", resp.Status)
}
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
return info, err
}
return info, nil
}
// Add adds a new Package to a gpm server
func (c *Client) Add(ctx context.Context, pkg Package) error {
resp, err := c.crud(ctx, pkg, http.MethodPost)
if err != nil {
return err
}
if resp.StatusCode != http.StatusCreated {
return fmt.Errorf("could not add package: %s", resp.Status)
}
return nil
}
// Update updates a Package on a gpm server
func (c *Client) Update(ctx context.Context, pkg Package) error {
resp, err := c.crud(ctx, pkg, http.MethodPatch)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("could not update package: %s", resp.Status)
}
return nil
}
// Remove removes a Package from a gpm server
func (c *Client) Remove(ctx context.Context, pkg Package) error {
resp, err := c.crud(ctx, pkg, http.MethodDelete)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("could not remove package: %s", resp.Status)
}
return nil
}
// Get gets a Package from a server
func (c *Client) Get(ctx context.Context, name string) (Package, error) {
var pkg Package
uri := fmt.Sprintf("%s/%s", c.server, name)
req, err := c.newRequest(ctx, http.MethodGet, uri, nil)
if err != nil {
return pkg, err
}
resp, err := c.http.Do(req)
if err != nil {
return pkg, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return pkg, fmt.Errorf("package not found for %s", name)
}
if err := json.NewDecoder(resp.Body).Decode(&pkg); err != nil {
return pkg, err
}
return pkg, nil
}
func (c *Client) crud(ctx context.Context, pkg Package, method string) (*http.Response, error) {
payload, err := json.Marshal(pkg)
if err != nil {
return nil, err
}
req, err := c.newRequest(ctx, method, c.server, bytes.NewReader(payload))
if err != nil {
return nil, err
}
return c.http.Do(req)
}

View File

@ -1,76 +0,0 @@
package router
import (
"encoding/json"
"net/http"
"time"
"go.jolheiser.com/gpm/config"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"go.jolheiser.com/beaver"
)
var cache map[string]config.Package
func New(cfg *config.Config) *chi.Mux {
r := chi.NewRouter()
r.Use(middleware.RedirectSlashes)
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(30 * time.Second))
r.Get("/", handleHome(cfg))
r.Get("/export", handleExport(cfg))
r.Get("/package/{name}", handlePackage)
cache = cfg.Packages.Map()
return r
}
func handleHome(cfg *config.Config) func(res http.ResponseWriter, _ *http.Request) {
return func(res http.ResponseWriter, _ *http.Request) {
status, err := json.Marshal(map[string]interface{}{
"version": config.Version,
"packages": len(cfg.Packages),
})
if err != nil {
res.WriteHeader(http.StatusInternalServerError)
_, _ = res.Write([]byte("{}"))
return
}
_, _ = res.Write(status)
}
}
func handleExport(cfg *config.Config) func(res http.ResponseWriter, _ *http.Request) {
return func(res http.ResponseWriter, _ *http.Request) {
export, err := cfg.Export()
if err != nil {
beaver.Error(err)
return
}
_, _ = res.Write([]byte(export))
}
}
func handlePackage(res http.ResponseWriter, req *http.Request) {
name := chi.URLParam(req, "name")
if pkg, ok := cache[name]; ok {
data, err := json.Marshal(pkg)
if err != nil {
res.WriteHeader(http.StatusInternalServerError)
_, _ = res.Write([]byte("{}"))
return
}
_, _ = res.Write(data)
return
}
res.WriteHeader(http.StatusNotFound)
_, _ = res.Write([]byte("{}"))
}