mirror of
https://codeberg.org/scip/anydb.git
synced 2025-12-17 12:31:02 +01:00
added -N flag, added -m template support to get and list commands
This commit is contained in:
15
README.md
15
README.md
@@ -16,6 +16,7 @@ reasons:
|
|||||||
often, which is not good for a tool intended to be used for many
|
often, which is not good for a tool intended to be used for many
|
||||||
years.
|
years.
|
||||||
- more features:
|
- more features:
|
||||||
|
- output table in list mode uses <tab> separator
|
||||||
- better STDIN + pipe support
|
- better STDIN + pipe support
|
||||||
- supports JSON output
|
- supports JSON output
|
||||||
- supports more verbose tabular output
|
- supports more verbose tabular output
|
||||||
@@ -23,6 +24,7 @@ reasons:
|
|||||||
- tagging
|
- tagging
|
||||||
- filtering using tags
|
- filtering using tags
|
||||||
- encryption of entries
|
- encryption of entries
|
||||||
|
- templates for custom output for maximum flexibility
|
||||||
|
|
||||||
**anydb** can do all the things you can do with skate:
|
**anydb** can do all the things you can do with skate:
|
||||||
|
|
||||||
@@ -95,6 +97,19 @@ anydb import -r backup.json
|
|||||||
# get command.
|
# get command.
|
||||||
anydb set mypassword -e
|
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
|
# it comes with a manpage builtin
|
||||||
anydb man
|
anydb man
|
||||||
```
|
```
|
||||||
|
|||||||
22
app/db.go
22
app/db.go
@@ -13,6 +13,8 @@ import (
|
|||||||
bolt "go.etcd.io/bbolt"
|
bolt "go.etcd.io/bbolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const MaxValueWidth int = 60
|
||||||
|
|
||||||
type DB struct {
|
type DB struct {
|
||||||
Debug bool
|
Debug bool
|
||||||
Dbfile string
|
Dbfile string
|
||||||
@@ -27,6 +29,26 @@ type DbEntry struct {
|
|||||||
Bin []byte `json:"bin"`
|
Bin []byte `json:"bin"`
|
||||||
Tags []string `json:"tags"`
|
Tags []string `json:"tags"`
|
||||||
Created time.Time `json:"created"`
|
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
|
type DbEntries []DbEntry
|
||||||
|
|||||||
@@ -2,13 +2,15 @@ package cfg
|
|||||||
|
|
||||||
import "github.com/tlinden/anydb/app"
|
import "github.com/tlinden/anydb/app"
|
||||||
|
|
||||||
var Version string = "v0.0.2"
|
var Version string = "v0.0.3"
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Debug bool
|
Debug bool
|
||||||
Dbfile string
|
Dbfile string
|
||||||
|
Template string
|
||||||
Mode string // wide, table, yaml, json
|
Mode string // wide, table, yaml, json
|
||||||
NoHeaders bool
|
NoHeaders bool
|
||||||
|
NoHumanize bool
|
||||||
Encrypt bool
|
Encrypt bool
|
||||||
DB *app.DB
|
DB *app.DB
|
||||||
File string
|
File string
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ func Get(conf *cfg.Config) *cobra.Command {
|
|||||||
)
|
)
|
||||||
|
|
||||||
var cmd = &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",
|
Short: "Retrieve value for a key",
|
||||||
Long: `Retrieve value for a key`,
|
Long: `Retrieve value for a key`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
@@ -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().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.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, "show")
|
||||||
cmd.Aliases = append(cmd.Aliases, "g")
|
cmd.Aliases = append(cmd.Aliases, "g")
|
||||||
@@ -186,7 +188,7 @@ func Export(conf *cfg.Config) *cobra.Command {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return output.WriteFile(&attr, conf, entries)
|
return output.WriteJSON(&attr, conf, entries)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,7 +207,7 @@ func List(conf *cfg.Config) *cobra.Command {
|
|||||||
)
|
)
|
||||||
|
|
||||||
var cmd = &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",
|
Short: "List database contents",
|
||||||
Long: `List database contents`,
|
Long: `List database contents`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
@@ -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.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(&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.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.PersistentFlags().StringArrayVarP(&attr.Tags, "tags", "t", nil, "tags, multiple allowed")
|
||||||
|
|
||||||
cmd.Aliases = append(cmd.Aliases, "/")
|
cmd.Aliases = append(cmd.Aliases, "/")
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/tlinden/anydb/cfg"
|
"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)
|
jsonentries, err := json.Marshal(entries)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshall json: %w", err)
|
return fmt.Errorf("failed to marshall json: %w", err)
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package output
|
package output
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
tpl "text/template"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/olekukonko/tablewriter"
|
"github.com/olekukonko/tablewriter"
|
||||||
@@ -16,14 +19,12 @@ import (
|
|||||||
func List(writer io.Writer, conf *cfg.Config, entries app.DbEntries) error {
|
func List(writer io.Writer, conf *cfg.Config, entries app.DbEntries) error {
|
||||||
// FIXME: call sort here
|
// FIXME: call sort here
|
||||||
switch conf.Mode {
|
switch conf.Mode {
|
||||||
case "wide":
|
case "wide", "", "table":
|
||||||
fallthrough
|
|
||||||
case "":
|
|
||||||
fallthrough
|
|
||||||
case "table":
|
|
||||||
return ListTable(writer, conf, entries)
|
return ListTable(writer, conf, entries)
|
||||||
case "json":
|
case "json":
|
||||||
return ListJson(writer, conf, entries)
|
return ListJson(writer, conf, entries)
|
||||||
|
case "template":
|
||||||
|
return ListTemplate(writer, conf, entries)
|
||||||
default:
|
default:
|
||||||
return errors.New("unsupported mode")
|
return errors.New("unsupported mode")
|
||||||
}
|
}
|
||||||
@@ -39,43 +40,67 @@ func ListJson(writer io.Writer, conf *cfg.Config, entries app.DbEntries) error {
|
|||||||
return nil
|
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 {
|
func ListTable(writer io.Writer, conf *cfg.Config, entries app.DbEntries) error {
|
||||||
tableString := &strings.Builder{}
|
tableString := &strings.Builder{}
|
||||||
table := tablewriter.NewWriter(tableString)
|
table := tablewriter.NewWriter(tableString)
|
||||||
|
|
||||||
if !conf.NoHeaders {
|
if !conf.NoHeaders {
|
||||||
if conf.Mode == "wide" {
|
if conf.Mode == "wide" {
|
||||||
table.SetHeader([]string{"KEY", "TAGS", "SIZE", "AGE", "VALUE"})
|
table.SetHeader([]string{"KEY", "TAGS", "SIZE", "UPDATED", "VALUE"})
|
||||||
} else {
|
} else {
|
||||||
table.SetHeader([]string{"KEY", "VALUE"})
|
table.SetHeader([]string{"KEY", "VALUE"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, row := range entries {
|
for _, row := range entries {
|
||||||
size := len(row.Value)
|
row.Normalize()
|
||||||
|
|
||||||
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] + "..."
|
|
||||||
}
|
|
||||||
|
|
||||||
if conf.Mode == "wide" {
|
if conf.Mode == "wide" {
|
||||||
|
switch conf.NoHumanize {
|
||||||
|
case true:
|
||||||
table.Append([]string{
|
table.Append([]string{
|
||||||
row.Key,
|
row.Key,
|
||||||
strings.Join(row.Tags, ","),
|
strings.Join(row.Tags, ","),
|
||||||
humanize.Bytes(uint64(size)),
|
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"),
|
//row.Created.Format("02.01.2006T03:04.05"),
|
||||||
humanize.Time(row.Created),
|
humanize.Time(row.Created),
|
||||||
row.Value,
|
row.Value,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
table.Append([]string{row.Key, row.Value})
|
table.Append([]string{row.Key, row.Value})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,45 @@ import (
|
|||||||
|
|
||||||
func Print(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app.DbEntry) error {
|
func Print(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app.DbEntry) error {
|
||||||
if attr.File != "" {
|
if attr.File != "" {
|
||||||
|
WriteFile(writer, conf, attr, entry)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
isatty := term.IsTerminal(int(os.Stdout.Fd()))
|
||||||
|
|
||||||
|
switch conf.Mode {
|
||||||
|
case "simple", "":
|
||||||
|
if len(entry.Bin) > 0 {
|
||||||
|
if isatty {
|
||||||
|
fmt.Println("binary data omitted")
|
||||||
|
} else {
|
||||||
|
os.Stdout.Write(entry.Bin)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Print(entry.Value)
|
||||||
|
|
||||||
|
if !strings.HasSuffix(entry.Value, "\n") {
|
||||||
|
// always add a terminal newline
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "json":
|
||||||
|
jsonentry, err := json.Marshal(entry)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshall json: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
fd, err := os.OpenFile(attr.File, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to open file %s for writing: %w", attr.File, err)
|
return fmt.Errorf("failed to open file %s for writing: %w", attr.File, err)
|
||||||
@@ -39,37 +78,3 @@ func Print(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app.DbEn
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
isatty := term.IsTerminal(int(os.Stdout.Fd()))
|
|
||||||
|
|
||||||
switch conf.Mode {
|
|
||||||
case "simple":
|
|
||||||
fallthrough
|
|
||||||
case "":
|
|
||||||
if len(entry.Bin) > 0 {
|
|
||||||
if isatty {
|
|
||||||
fmt.Println("binary data omitted")
|
|
||||||
} else {
|
|
||||||
os.Stdout.Write(entry.Bin)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Print(entry.Value)
|
|
||||||
|
|
||||||
if !strings.HasSuffix(entry.Value, "\n") {
|
|
||||||
// always add a terminal newline
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "json":
|
|
||||||
jsonentry, err := json.Marshal(entry)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to marshall json: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(string(jsonentry))
|
|
||||||
case "wide":
|
|
||||||
return ListTable(writer, conf, app.DbEntries{*entry})
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user