initial commit
Signed-off-by: jolheiser <john.olheiser@gmail.com>main v0.0.1
commit
0b81160c3c
|
@ -0,0 +1 @@
|
|||
/kv*
|
|
@ -0,0 +1,25 @@
|
|||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
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: kv
|
||||
gitea_urls:
|
||||
api: https://git.jojodev.com/api/v1/
|
||||
download: https://git.jojodev.com
|
|
@ -0,0 +1,38 @@
|
|||
clone:
|
||||
git:
|
||||
image: woodpeckerci/plugin-git
|
||||
settings:
|
||||
tags: true
|
||||
|
||||
pipeline:
|
||||
compliance:
|
||||
image: golang:1.18
|
||||
commands:
|
||||
- go test -race ./...
|
||||
- go vet ./...
|
||||
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
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2023 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.
|
|
@ -0,0 +1,13 @@
|
|||
module go.jolheiser.com/kv
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/adrg/xdg v0.4.0
|
||||
github.com/peterbourgon/ff/v3 v3.3.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/stretchr/testify v1.8.1 // indirect
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 // indirect
|
||||
)
|
|
@ -0,0 +1,23 @@
|
|||
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
|
||||
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
|
||||
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/peterbourgon/ff/v3 v3.3.1 h1:XSWvXxeNdgeppLNGGJEAOiXRdX2YMF/LuZfdnqQ1SNc=
|
||||
github.com/peterbourgon/ff/v3 v3.3.1/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ=
|
||||
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 h1:2B5p2L5IfGiD7+b9BOoRMC6DgObAVZV+Fsp050NqXik=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -0,0 +1,212 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/peterbourgon/ff/v3"
|
||||
"github.com/peterbourgon/ff/v3/ffcli"
|
||||
)
|
||||
|
||||
var Version = "develop"
|
||||
|
||||
func main() {
|
||||
defaultStore, err := xdg.ConfigFile("kv/store.json")
|
||||
if err != nil {
|
||||
defaultStore = "store.json"
|
||||
}
|
||||
|
||||
fs := flag.NewFlagSet("kv", flag.ContinueOnError)
|
||||
storeFlag := fs.String("store", defaultStore, "Location of the store on disk")
|
||||
fs.StringVar(storeFlag, "s", *storeFlag, "--store")
|
||||
|
||||
a := &app{
|
||||
storeLocation: storeFlag,
|
||||
}
|
||||
|
||||
var app *ffcli.Command
|
||||
app = &ffcli.Command{
|
||||
Name: "kv",
|
||||
ShortUsage: fmt.Sprintf("Key/Value store - version %s", Version),
|
||||
ShortHelp: "kv [get|set|del|list] ...",
|
||||
FlagSet: fs,
|
||||
Subcommands: []*ffcli.Command{
|
||||
a.get(),
|
||||
a.set(),
|
||||
a.del(),
|
||||
a.list(),
|
||||
},
|
||||
Options: []ff.Option{
|
||||
ff.WithEnvVarPrefix("KV"),
|
||||
},
|
||||
Exec: func(_ context.Context, _ []string) error {
|
||||
return errors.New(app.UsageFunc(app))
|
||||
},
|
||||
}
|
||||
|
||||
if err := app.ParseAndRun(context.Background(), os.Args[1:]); err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
return
|
||||
}
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
type app struct {
|
||||
storeLocation *string
|
||||
}
|
||||
|
||||
func (a *app) get() *ffcli.Command {
|
||||
fs := flag.NewFlagSet("get", flag.ContinueOnError)
|
||||
return &ffcli.Command{
|
||||
Name: "get",
|
||||
FlagSet: fs,
|
||||
Exec: func(ctx context.Context, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return errors.New("get requires at least one argument")
|
||||
}
|
||||
|
||||
data, err := a.load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key := strings.ToLower(strings.Join(args, " "))
|
||||
val, ok := data[key]
|
||||
if !ok {
|
||||
return fmt.Errorf("no value found for %q", key)
|
||||
}
|
||||
fmt.Print(val)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *app) set() *ffcli.Command {
|
||||
fs := flag.NewFlagSet("set", flag.ContinueOnError)
|
||||
return &ffcli.Command{
|
||||
Name: "set",
|
||||
FlagSet: fs,
|
||||
Exec: func(ctx context.Context, args []string) error {
|
||||
if len(args) < 2 {
|
||||
return errors.New("set requires at least two arguments")
|
||||
}
|
||||
|
||||
data, err := a.load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key := args[0]
|
||||
data[key] = strings.Join(args[1:], " ")
|
||||
if err := a.save(data); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("set %q\n", key)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *app) del() *ffcli.Command {
|
||||
fs := flag.NewFlagSet("del", flag.ContinueOnError)
|
||||
return &ffcli.Command{
|
||||
Name: "del",
|
||||
FlagSet: fs,
|
||||
Exec: func(ctx context.Context, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return errors.New("del requires at least 1 argument")
|
||||
}
|
||||
|
||||
data, err := a.load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key := strings.ToLower(strings.Join(args, " "))
|
||||
if _, ok := data[key]; !ok {
|
||||
return fmt.Errorf("no value found for %q", key)
|
||||
}
|
||||
delete(data, key)
|
||||
if err := a.save(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("deleted %q\n", key)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *app) list() *ffcli.Command {
|
||||
fs := flag.NewFlagSet("list", flag.ContinueOnError)
|
||||
return &ffcli.Command{
|
||||
Name: "list",
|
||||
FlagSet: fs,
|
||||
Exec: func(ctx context.Context, args []string) error {
|
||||
data, err := a.load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var keys []string
|
||||
prefix := strings.ToLower(strings.Join(args, " "))
|
||||
for key := range data {
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, key := range keys {
|
||||
fmt.Println(key)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *app) load() (map[string]string, error) {
|
||||
var m map[string]string
|
||||
fi, err := os.Open(*a.storeLocation)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
fi, err = os.Create(*a.storeLocation)
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
if _, err := fi.WriteString("{}"); err != nil {
|
||||
return m, err
|
||||
}
|
||||
if err := fi.Close(); err != nil {
|
||||
return m, err
|
||||
}
|
||||
return a.load()
|
||||
}
|
||||
return m, err
|
||||
}
|
||||
defer fi.Close()
|
||||
return m, json.NewDecoder(fi).Decode(&m)
|
||||
}
|
||||
|
||||
func (a *app) save(m map[string]string) error {
|
||||
fi, err := os.Create(*a.storeLocation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fi.Close()
|
||||
|
||||
enc := json.NewEncoder(fi)
|
||||
enc.SetIndent("", "\t")
|
||||
return enc.Encode(&m)
|
||||
}
|
Loading…
Reference in New Issue