Skip to content

Commit

Permalink
added -N flag, added -m template support to get and list commands
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas von Dein committed Dec 20, 2024
1 parent ba39e3f commit d948681
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 70 deletions.
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ reasons:
often, which is not good for a tool intended to be used for many
years.
- more features:
- output table in list mode uses <tab> separator
- better STDIN + pipe support
- supports JSON output
- supports more verbose tabular output
- filtering using regular expressions
- tagging
- filtering using tags
- encryption of entries
- templates for custom output for maximum flexibility

**anydb** can do all the things you can do with skate:

Expand Down Expand Up @@ -74,8 +76,8 @@ anydb list '[a-z]+\d'
anydb list -o wide
KEY TAGS SIZE AGE VALUE
blah important 4 B 7 seconds ago haha
foo 3 B 15 seconds ago bar
猫咪 3 B 3 seconds ago 喵
foo 3 B 15 seconds ago bar
猫咪 3 B 3 seconds ago 喵

# there are shortcuts as well
anydb ls -l
Expand All @@ -95,6 +97,19 @@ anydb import -r backup.json
# get command.
anydb set mypassword -e

# using template output mode you can freely design how to print stuff
# here, we print the values in CSV format ONLY if they have some tag
anydb ls -m template -T "{{ if .Tags }}{{ .Key }},{{ .Value }},{{ .Created}}{{ end }}"

# or, to simulate skate's -k or -v
anydb ls -m template -T "{{ .Key }}"
anydb ls -m template -T "{{ .Value }}"

# maybe you want to digest the item in a shell script? also
# note, that both the list and get commands support templates
eval $(anydb get foo -m template -T "key='{{ .Key }}' value='{{ .Value }}' ts='{{ .Created}}'")
echo "$key: $value"

# it comes with a manpage builtin
anydb man
```
Expand Down
22 changes: 22 additions & 0 deletions app/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
bolt "go.etcd.io/bbolt"
)

const MaxValueWidth int = 60

type DB struct {
Debug bool
Dbfile string
Expand All @@ -27,6 +29,26 @@ type DbEntry struct {
Bin []byte `json:"bin"`
Tags []string `json:"tags"`
Created time.Time `json:"created"`
Size int
}

// Post process an entry for list output.
// Do NOT call it during write processing!
func (entry *DbEntry) Normalize() {
entry.Size = len(entry.Value)

if entry.Encrypted {
entry.Value = "<encrypted-content>"
}

if len(entry.Bin) > 0 {
entry.Value = "<binary-content>"
entry.Size = len(entry.Bin)
}

if len(entry.Value) > MaxValueWidth {
entry.Value = entry.Value[0:MaxValueWidth] + "..."
}
}

type DbEntries []DbEntry
Expand Down
20 changes: 11 additions & 9 deletions cfg/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ package cfg

import "github.com/tlinden/anydb/app"

var Version string = "v0.0.2"
var Version string = "v0.0.3"

type Config struct {
Debug bool
Dbfile string
Mode string // wide, table, yaml, json
NoHeaders bool
Encrypt bool
DB *app.DB
File string
Tags []string
Debug bool
Dbfile string
Template string
Mode string // wide, table, yaml, json
NoHeaders bool
NoHumanize bool
Encrypt bool
DB *app.DB
File string
Tags []string
}
12 changes: 8 additions & 4 deletions cmd/maincommands.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func Get(conf *cfg.Config) *cobra.Command {
)

var cmd = &cobra.Command{
Use: "get <key> [-o <file>] [-m <mode>] [-n]",
Use: "get <key> [-o <file>] [-m <mode>] [-n -N] [-T <tpl>]",
Short: "Retrieve value for a key",
Long: `Retrieve value for a key`,
RunE: func(cmd *cobra.Command, args []string) error {
Expand Down Expand Up @@ -124,9 +124,11 @@ func Get(conf *cfg.Config) *cobra.Command {
},
}

cmd.PersistentFlags().StringVarP(&attr.File, "output", "o", "", "output to file (ignores -m)")
cmd.PersistentFlags().StringVarP(&attr.File, "output", "o", "", "output value to file (ignores -m)")
cmd.PersistentFlags().StringVarP(&conf.Mode, "mode", "m", "", "output format (simple|wide|json) (default 'simple')")
cmd.PersistentFlags().BoolVarP(&conf.NoHeaders, "no-headers", "n", false, "omit headers in tables")
cmd.PersistentFlags().BoolVarP(&conf.NoHumanize, "no-human", "N", false, "do not translate to human readable values")
cmd.PersistentFlags().StringVarP(&conf.Template, "template", "T", "", "go template for '-m template'")

cmd.Aliases = append(cmd.Aliases, "show")
cmd.Aliases = append(cmd.Aliases, "g")
Expand Down Expand Up @@ -186,7 +188,7 @@ func Export(conf *cfg.Config) *cobra.Command {
return err
}

return output.WriteFile(&attr, conf, entries)
return output.WriteJSON(&attr, conf, entries)
},
}

Expand All @@ -205,7 +207,7 @@ func List(conf *cfg.Config) *cobra.Command {
)

var cmd = &cobra.Command{
Use: "list [-t <tag>] [-o <mode>] [<filter-regex>]",
Use: "list [<filter-regex>] [-t <tag>] [-m <mode>] [-n -N] [-T <tpl>]",
Short: "List database contents",
Long: `List database contents`,
RunE: func(cmd *cobra.Command, args []string) error {
Expand Down Expand Up @@ -235,8 +237,10 @@ func List(conf *cfg.Config) *cobra.Command {
}

cmd.PersistentFlags().StringVarP(&conf.Mode, "mode", "m", "", "output format (table|wide|json), wide is a verbose table. (default 'table')")
cmd.PersistentFlags().StringVarP(&conf.Template, "template", "T", "", "go template for '-m template'")
cmd.PersistentFlags().BoolVarP(&wide, "wide-output", "l", false, "output mode: wide")
cmd.PersistentFlags().BoolVarP(&conf.NoHeaders, "no-headers", "n", false, "omit headers in tables")
cmd.PersistentFlags().BoolVarP(&conf.NoHumanize, "no-human", "N", false, "do not translate to human readable values")
cmd.PersistentFlags().StringArrayVarP(&attr.Tags, "tags", "t", nil, "tags, multiple allowed")

cmd.Aliases = append(cmd.Aliases, "/")
Expand Down
2 changes: 1 addition & 1 deletion output/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/tlinden/anydb/cfg"
)

func WriteFile(attr *app.DbAttr, conf *cfg.Config, entries app.DbEntries) error {
func WriteJSON(attr *app.DbAttr, conf *cfg.Config, entries app.DbEntries) error {
jsonentries, err := json.Marshal(entries)
if err != nil {
return fmt.Errorf("failed to marshall json: %w", err)
Expand Down
81 changes: 53 additions & 28 deletions output/list.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package output

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"strconv"
"strings"
tpl "text/template"

"github.com/dustin/go-humanize"
"github.com/olekukonko/tablewriter"
Expand All @@ -16,14 +19,12 @@ import (
func List(writer io.Writer, conf *cfg.Config, entries app.DbEntries) error {
// FIXME: call sort here
switch conf.Mode {
case "wide":
fallthrough
case "":
fallthrough
case "table":
case "wide", "", "table":
return ListTable(writer, conf, entries)
case "json":
return ListJson(writer, conf, entries)
case "template":
return ListTemplate(writer, conf, entries)
default:
return errors.New("unsupported mode")
}
Expand All @@ -39,43 +40,67 @@ func ListJson(writer io.Writer, conf *cfg.Config, entries app.DbEntries) error {
return nil
}

func ListTemplate(writer io.Writer, conf *cfg.Config, entries app.DbEntries) error {
tmpl, err := tpl.New("list").Parse(conf.Template)
if err != nil {
return fmt.Errorf("failed to parse output template: %w", err)
}

buf := bytes.Buffer{}

for _, row := range entries {
row.Normalize()

buf.Reset()
err = tmpl.Execute(&buf, row)
if err != nil {
return fmt.Errorf("failed to execute output template: %w", err)
}

if buf.Len() > 0 {
fmt.Fprintln(writer, buf.String())
}
}

return nil
}

func ListTable(writer io.Writer, conf *cfg.Config, entries app.DbEntries) error {
tableString := &strings.Builder{}
table := tablewriter.NewWriter(tableString)

if !conf.NoHeaders {
if conf.Mode == "wide" {
table.SetHeader([]string{"KEY", "TAGS", "SIZE", "AGE", "VALUE"})
table.SetHeader([]string{"KEY", "TAGS", "SIZE", "UPDATED", "VALUE"})
} else {
table.SetHeader([]string{"KEY", "VALUE"})
}
}

for _, row := range entries {
size := len(row.Value)

if row.Encrypted {
row.Value = "<encrypted-content>"
}

if len(row.Bin) > 0 {
row.Value = "<binary-content>"
size = len(row.Bin)
}

if len(row.Value) > 60 {
row.Value = row.Value[0:60] + "..."
}
row.Normalize()

if conf.Mode == "wide" {
table.Append([]string{
row.Key,
strings.Join(row.Tags, ","),
humanize.Bytes(uint64(size)),
//row.Created.Format("02.01.2006T03:04.05"),
humanize.Time(row.Created),
row.Value,
})
switch conf.NoHumanize {
case true:
table.Append([]string{
row.Key,
strings.Join(row.Tags, ","),
strconv.Itoa(row.Size),
row.Created.Format("02.01.2006T03:04.05"),
row.Value,
})
default:
table.Append([]string{
row.Key,
strings.Join(row.Tags, ","),
humanize.Bytes(uint64(row.Size)),
//row.Created.Format("02.01.2006T03:04.05"),
humanize.Time(row.Created),
row.Value,
})
}

} else {
table.Append([]string{row.Key, row.Value})
}
Expand Down
57 changes: 31 additions & 26 deletions output/single.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,14 @@ import (

func Print(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app.DbEntry) error {
if attr.File != "" {
fd, err := os.OpenFile(attr.File, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
if err != nil {
return fmt.Errorf("failed to open file %s for writing: %w", attr.File, err)
}
defer fd.Close()

if len(entry.Bin) > 0 {
// binary file content
_, err = fd.Write(entry.Bin)
} else {
val := entry.Value
if !strings.HasSuffix(val, "\n") {
// always add a terminal newline
val += "\n"
}

_, err = fd.Write([]byte(val))
}

if err != nil {
return fmt.Errorf("failed to write to file %s: %w", attr.File, err)
}
WriteFile(writer, conf, attr, entry)

Check failure on line 17 in output/single.go

View workflow job for this annotation

GitHub Actions / lint

Error return value is not checked (errcheck)

return nil
}

isatty := term.IsTerminal(int(os.Stdout.Fd()))

switch conf.Mode {
case "simple":
fallthrough
case "":
case "simple", "":
if len(entry.Bin) > 0 {
if isatty {
fmt.Println("binary data omitted")
Expand All @@ -69,6 +45,35 @@ func Print(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app.DbEn
fmt.Println(string(jsonentry))
case "wide":
return ListTable(writer, conf, app.DbEntries{*entry})
case "template":
return ListTemplate(writer, conf, app.DbEntries{*entry})
}

return nil
}

func WriteFile(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app.DbEntry) error {
fd, err := os.OpenFile(attr.File, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
if err != nil {
return fmt.Errorf("failed to open file %s for writing: %w", attr.File, err)
}
defer fd.Close()

if len(entry.Bin) > 0 {
// binary file content
_, err = fd.Write(entry.Bin)
} else {
val := entry.Value
if !strings.HasSuffix(val, "\n") {
// always add a terminal newline
val += "\n"
}

_, err = fd.Write([]byte(val))
}

if err != nil {
return fmt.Errorf("failed to write to file %s: %w", attr.File, err)
}

return nil
Expand Down

0 comments on commit d948681

Please sign in to comment.