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