From 1d45b2c6fe790b5f880d0545e223aae8634695f7 Mon Sep 17 00:00:00 2001 From: "T. von Dein" Date: Mon, 31 Aug 2026 22:06:25 +0200 Subject: [PATCH] update linter config, satisfy linter (#28) --- .golangci.bck.yaml | 6 -- .golangci.yaml | 7 -- .golangci.yml | 97 ++++++++++++++++++++++ .woodpecker/build.yaml | 5 +- Makefile | 8 +- anydb.1 | 12 +-- anydb.pod | 6 +- app/attr.go | 16 +++- app/crypto.go | 17 ++-- app/db.go | 182 ++++++++++++++++++++--------------------- app/io.go | 16 +++- cfg/config.go | 1 + cmd/anydb.go | 9 +- cmd/crud.go | 12 +-- cmd/extra.go | 20 +++-- cmd/root.go | 5 +- common/io.go | 1 - common/logger.go | 24 +++--- main.go | 24 +----- output/list.go | 29 +++---- output/single.go | 27 +++--- rest/handlers.go | 18 ++-- rest/log.go | 9 +- rest/serve.go | 1 - 24 files changed, 331 insertions(+), 221 deletions(-) delete mode 100644 .golangci.bck.yaml delete mode 100644 .golangci.yaml create mode 100644 .golangci.yml diff --git a/.golangci.bck.yaml b/.golangci.bck.yaml deleted file mode 100644 index 58aeea5..0000000 --- a/.golangci.bck.yaml +++ /dev/null @@ -1,6 +0,0 @@ -linters: - exclusions: - rules: - - linters: - - staticcheck - text: "QF1008:" diff --git a/.golangci.yaml b/.golangci.yaml deleted file mode 100644 index 4cdd836..0000000 --- a/.golangci.yaml +++ /dev/null @@ -1,7 +0,0 @@ -version: "2" -linters: - exclusions: - rules: - - linters: - - staticcheck - text: "QF1008:" diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..360b517 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,97 @@ +version: "2" + +linters: + enable: + - errcheck + - govet + - ineffassign + - staticcheck + - unused + - arangolint + - asasalint + - asciicheck + - bidichk + - bodyclose + - canonicalheader + - clickhouselint + - containedctx + - copyloopvar + - decorder + - dogsled + - durationcheck + - embeddedstructfieldcheck + - errchkjson + - errname + - exhaustive + - exptostd + - fatcontext + - funcorder + - ginkgolinter + - gocheckcompilerdirectives + - gochecknoinits + - gochecksumtype + - gocritic + - goheader + - gomoddirectives + - gomodguard_v2 + - goprintffuncname + - gosmopolitan + - grouper + - iface + - importas + - inamedparam + - interfacebloat + - intrange + - iotamixing + - lll + - loggercheck + - makezero + - misspell + - modernize + - nakedret + - nilerr + - nilnesserr + - nlreturn + - nonamedreturns + - nosprintfhostport + - perfsprint + - prealloc + - promlinter + - protogetter + - reassign + - recvcheck + - rowserrcheck + - sloglint + - spancheck + - sqlclosecheck + - tagalign + - testableexamples + - testifylint + - testpackage + - thelper + - tparallel + - unconvert + - unparam + - unqueryvet + - usestdlibvars + - usetesting + - varnamelen + - wastedassign + - whitespace + - wsl_v5 + - zerologlint + + settings: + varnamelen: + ignore-names: + - err + - wg + - mu + - ts + - to + - es + - op + - fd + - id + - fn + diff --git a/.woodpecker/build.yaml b/.woodpecker/build.yaml index e20cc6f..a2b147a 100644 --- a/.woodpecker/build.yaml +++ b/.woodpecker/build.yaml @@ -3,6 +3,9 @@ matrix: - linux/amd64 goversion: - 1.24 + lintversion: + - v2.12.2 + labels: platform: ${platform} @@ -21,7 +24,7 @@ steps: event: [push] image: golang:${goversion} commands: - - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.5.0 + - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin ${lintversion} - golangci-lint --version - golangci-lint run ./... depends_on: [build] diff --git a/Makefile b/Makefile index 3c7b7bf..13eaba7 100644 --- a/Makefile +++ b/Makefile @@ -97,12 +97,8 @@ goupdate: go get -t -u=patch ./... lint: - golangci-lint run - -# keep til ireturn -lint-full: - golangci-lint run --enable-all --exclude-use-default --disable exhaustivestruct,exhaustruct,depguard,interfacer,deadcode,golint,structcheck,scopelint,varcheck,ifshort,maligned,nosnakecase,godot,funlen,gofumpt,cyclop,noctx,gochecknoglobals,paralleltest,forbidigo,gci,godox,goimports,ireturn,stylecheck,testpackage,mirror,nestif,revive,goerr113,gomnd - gocritic check -enableAll *.go + golangci-lint run --show-stats=false + @ # gocritic check -enableAll *.go demo: make -C demo demo diff --git a/anydb.1 b/anydb.1 index 5ba505c..64d37f8 100644 --- a/anydb.1 +++ b/anydb.1 @@ -133,13 +133,12 @@ .\" ======================================================================== .\" .IX Title "ANYDB 1" -.TH ANYDB 1 "2026-04-29" "1" "User Commands" +.TH ANYDB 1 "2026-08-31" "1" "User Commands" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh -.SH "anydb" -.IX Header "anydb" +.SH "NAME" anydb \- a personal key value store .SH "SYNOPSIS" .IX Header "SYNOPSIS" @@ -346,7 +345,7 @@ subcommand, see there for more details. The options \f(CW\*(C`\-n\*(C'\fR and have the same meaning as in the list command. The \*(L"json\*(R" mode prints the raw \s-1JSON\s0 representation of the whole database entry. Decryption will only take place in \*(L"simple\*(R" and \*(L"json\*(R" mode. The \*(L"template\*(R" mode -provides the most flexibily, it is detailed in the section +provides the most flexibility, it is detailed in the section \&\s-1TEMPLATES\s0. .SS "list" .IX Subsection "list" @@ -605,7 +604,7 @@ being used is the \s-1GO\s0 template language, refer to .PP Each template operates on one or more entries, no loop construct is required, the template provided applies to every matching entry -separatley. +separateley. .PP The following template variables can be used: .IP "\fB.Key\fR \- string" 4 @@ -617,6 +616,9 @@ The following template variables can be used: To be able to use the value in a template, you'll need to add the \&\f(CW\*(C`\-s\*(C'\fR flag so that values are included in output data stream. Otherwise you can only use \f(CW\*(C`.Preview\*(C'\fR. +.Sp +Be careful if you use \f(CW\*(C`\-s\*(C'\fR and \f(CW\*(C`.Value\*(C'\fR though: if a value is a +multiline string, it may clutter your output. .IP "\fB.Preview\fR string" 4 .IX Item ".Preview string" First line of entry value (if it is a string). diff --git a/anydb.pod b/anydb.pod index 4cee91d..d1830d6 100644 --- a/anydb.pod +++ b/anydb.pod @@ -1,4 +1,4 @@ -=head1 anydb +=head1 NAME anydb - a personal key value store @@ -196,7 +196,7 @@ subcommand, see there for more details. The options C<-n> and C<-N> have the same meaning as in the list command. The "json" mode prints the raw JSON representation of the whole database entry. Decryption will only take place in "simple" and "json" mode. The "template" mode -provides the most flexibily, it is detailed in the section +provides the most flexibility, it is detailed in the section L. =head2 list @@ -442,7 +442,7 @@ L for details. Each template operates on one or more entries, no loop construct is required, the template provided applies to every matching entry -separatley. +separateley. The following template variables can be used: diff --git a/app/attr.go b/app/attr.go index 5866d21..372f7eb 100644 --- a/app/attr.go +++ b/app/attr.go @@ -1,3 +1,5 @@ +package app + /* Copyright © 2024-2025 Thomas von Dein @@ -14,7 +16,6 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package app import ( "fmt" @@ -125,17 +126,24 @@ func (attr *DbAttr) GetFileValue() error { } } else { // read from console stdin - var input string - var data string + var ( + input string + data string + buf strings.Builder + ) for { _, err := fmt.Scanln(&input) if err != nil { break } - data += input + "\n" + + buf.WriteString(input) + buf.WriteString("\n") } + data += buf.String() + attr.Val = []byte(data) } diff --git a/app/crypto.go b/app/crypto.go index 75f0587..901ebc8 100644 --- a/app/crypto.go +++ b/app/crypto.go @@ -1,3 +1,5 @@ +package app + /* Copyright © 2024 Thomas von Dein @@ -14,7 +16,6 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package app import ( "crypto/rand" @@ -35,7 +36,7 @@ const ( ArgonParallel uint8 = 2 ArgonSaltLen int = 16 ArgonKeyLen uint32 = 32 - B64SaltLen int = 16 //22 + B64SaltLen int = 16 // 22 ) type Key struct { @@ -47,7 +48,8 @@ type Key struct { // password func AskForPassword() ([]byte, error) { fmt.Fprint(os.Stderr, "Password: ") - pass, err := term.ReadPassword(int(syscall.Stdin)) + + pass, err := term.ReadPassword(syscall.Stdin) if err != nil { return nil, fmt.Errorf("failed to read password: %w", err) } @@ -57,6 +59,8 @@ func AskForPassword() ([]byte, error) { return pass, nil } +// DeriveKey derives a key from password. +// // We're using the Argon2id key derivation algorithm to derive a // secure key from the given password. This is important, because // users might use unsecure passwords. The resulting encrypted data @@ -78,7 +82,7 @@ func DeriveKey(password []byte, salt []byte) (*Key, error) { } hash := argon2.IDKey( - []byte(password), salt, + password, salt, ArgonIter, ArgonMem, ArgonParallel, @@ -95,6 +99,7 @@ func DeriveKey(password []byte, salt []byte) (*Key, error) { // Retrieve a random chunk of given size func GetRandom(size int, capacity int) ([]byte, error) { buf := make([]byte, size, capacity) + _, err := rand.Read(buf) if err != nil { return nil, fmt.Errorf("failed to retrieve random bytes: %w", err) @@ -103,7 +108,7 @@ func GetRandom(size int, capacity int) ([]byte, error) { return buf, nil } -// Encrypt clear text given in attr using ChaCha20 and auhtenticate +// Encrypt clear text given in attr using ChaCha20 and authenticate // using the mac Poly1305. The cipher text will be put into attr, thus // modifying it. // @@ -143,7 +148,7 @@ func Encrypt(pass []byte, attr *DbAttr) error { // Do the reverse func Decrypt(pass []byte, cipherb []byte) ([]byte, error) { if len(cipherb) < B64SaltLen { - return nil, fmt.Errorf("encrypted cipher block too small") + return nil, errors.New("encrypted cipher block too small") } key, err := DeriveKey(pass, cipherb[0:B64SaltLen]) diff --git a/app/db.go b/app/db.go index 65cc093..f3ad16d 100644 --- a/app/db.go +++ b/app/db.go @@ -25,6 +25,7 @@ import ( "os" "path/filepath" "regexp" + "slices" "strings" "time" @@ -64,7 +65,7 @@ type DbTag struct { } func (entry *DbEntry) Taglist() string { - return strings.Join(entry.Tags, ",") + return strings.Join(entry.GetTags(), ",") } const BucketData string = "data" @@ -121,6 +122,7 @@ func (db *DB) Open() error { } db.DB = b + return nil } @@ -136,8 +138,10 @@ func (db *DB) List(attr *DbAttr, fulltext bool) (DbEntries, error) { } defer db.Close() - var entries DbEntries - var filter *regexp.Regexp + var ( + entries DbEntries + filter *regexp.Regexp + ) if len(attr.Args) > 0 { // via cli @@ -166,7 +170,7 @@ func (db *DB) List(attr *DbAttr, fulltext bool) (DbEntries, error) { databucket := root.Bucket([]byte("data")) if databucket == nil { - return fmt.Errorf("failed to retrieve data sub bucket") + return errors.New("failed to retrieve data sub bucket") } err := bucket.ForEach(func(key, pbentry []byte) error { @@ -177,7 +181,7 @@ func (db *DB) List(attr *DbAttr, fulltext bool) (DbEntries, error) { if fulltext { // avoid crash due to access fault - value := databucket.Get([]byte(entry.Key)) // empty is ok + value := databucket.Get([]byte(entry.GetKey())) // empty is ok vc := make([]byte, len(value)) copy(vc, value) entry.Value = string(vc) @@ -187,26 +191,21 @@ func (db *DB) List(attr *DbAttr, fulltext bool) (DbEntries, error) { switch { case filter != nil: - if filter.MatchString(entry.Key) || - filter.MatchString(strings.Join(entry.Tags, " ")) { + if filter.MatchString(entry.GetKey()) || + filter.MatchString(strings.Join(entry.GetTags(), " ")) { include = true } - if !entry.Binary && !include && fulltext { - if filter.MatchString(string(entry.Value)) { + if !entry.GetBinary() && !include && fulltext { + if filter.MatchString(entry.GetValue()) { include = true } } case len(attr.Tags) > 0: for _, search := range attr.Tags { - for _, tag := range entry.Tags { - if tag == search { - include = true - break - } - } + if slices.Contains(entry.GetTags(), search) { + include = true - if include { break } } @@ -247,6 +246,7 @@ func (db *DB) Set(attr *DbAttr) error { // any tags. if so, we initialize our update struct with these // tags unless it has new tags configured. slog.Debug("+++ GET") + oldentry, err := db.txGet(attr) if err != nil { if !strings.Contains(err.Error(), "no such key") { @@ -255,9 +255,9 @@ func (db *DB) Set(attr *DbAttr) error { } if oldentry != nil { - if len(oldentry.Tags) > 0 && len(entry.Tags) == 0 { + if len(oldentry.GetTags()) > 0 && len(entry.GetTags()) == 0 { // initialize update entry with tags from old entry - entry.Tags = oldentry.Tags + entry.Tags = oldentry.GetTags() } } @@ -269,6 +269,7 @@ func (db *DB) Set(attr *DbAttr) error { } slog.Debug("+++ UPDATE") + err = db.DB.Update(func(tx *bolt.Tx) error { // create root bucket root, err := tx.CreateBucketIfNotExists([]byte(db.Bucket)) @@ -285,7 +286,7 @@ func (db *DB) Set(attr *DbAttr) error { slog.Debug("opened/created buckets", "root", root, "data", bucket) // write meta data - err = bucket.Put([]byte(entry.Key), []byte(pbentry)) + err = bucket.Put([]byte(entry.GetKey()), pbentry) if err != nil { return fmt.Errorf("failed to insert data: %w", err) } @@ -297,14 +298,13 @@ func (db *DB) Set(attr *DbAttr) error { } // write value - err = databucket.Put([]byte(entry.Key), attr.Val) + err = databucket.Put([]byte(entry.GetKey()), attr.Val) if err != nil { return fmt.Errorf("failed to insert data: %w", err) } return nil }) - if err != nil { return err } @@ -312,67 +312,6 @@ func (db *DB) Set(attr *DbAttr) error { return nil } -// internal DB getter, assumes db.DB has already been -// opened successfully. Do NOT call this w/o valid -// DB handle! -func (db *DB) txGet(attr *DbAttr) (*DbEntry, error) { - entry := DbEntry{} - - err := db.DB.View(func(tx *bolt.Tx) error { - // root bucket - root := tx.Bucket([]byte(db.Bucket)) - if root == nil { - return nil - } - - // get meta sub bucket - bucket := root.Bucket([]byte("meta")) - if bucket == nil { - return nil - } - - slog.Debug("opened buckets", "root", root, "data", bucket) - - // retrieve meta data - pbentry := bucket.Get([]byte(attr.Key)) - if pbentry == nil { - return fmt.Errorf("no such key: %s", attr.Key) - } - - // put into struct - if err := proto.Unmarshal(pbentry, &entry); err != nil { - return fmt.Errorf("failed to unmarshal from protobuf: %w", err) - } - - // get data sub bucket - databucket := root.Bucket([]byte("data")) - if databucket == nil { - return fmt.Errorf("failed to retrieve data sub bucket") - } - - // retrieve actual data value - value := databucket.Get([]byte(attr.Key)) - if len(value) == 0 { - return fmt.Errorf("no such key: %s", attr.Key) - } - - // we need to make a copy of it, otherwise we'll get an - // "unexpected fault address" error - vc := make([]byte, len(value)) - copy(vc, value) - - entry.Value = string(vc) - - return nil - }) - - if err != nil { - return nil, err - } - - return &entry, nil -} - func (db *DB) Get(attr *DbAttr) (*DbEntry, error) { if err := db.Open(); err != nil { return nil, err @@ -380,7 +319,6 @@ func (db *DB) Get(attr *DbAttr) (*DbEntry, error) { defer db.Close() entry, err := db.txGet(attr) - if err != nil { return nil, fmt.Errorf("failed to read from DB: %w", err) } @@ -426,10 +364,11 @@ func (db *DB) Import(attr *DbAttr) (string, error) { } var entries DbEntries + now := time.Now() newfile := db.Dbfile + now.Format("-02.01.2006T03:04.05") - if err := json.Unmarshal([]byte(attr.Val), &entries); err != nil { + if err := json.Unmarshal(attr.Val, &entries); err != nil { return "", cleanError(newfile, fmt.Errorf("failed to unmarshal json: %w", err)) } @@ -439,7 +378,6 @@ func (db *DB) Import(attr *DbAttr) (string, error) { if err != nil { return "", fmt.Errorf("failed to rename file %s to %s: %w", db.Dbfile, newfile, err) } - } // should now be a new db file @@ -470,7 +408,7 @@ func (db *DB) Import(attr *DbAttr) (string, error) { } // write meta data - err = bucket.Put([]byte(entry.Key), []byte(pbentry)) + err = bucket.Put([]byte(entry.GetKey()), pbentry) if err != nil { return fmt.Errorf("failed to insert data into DB: %w", err) } @@ -482,7 +420,7 @@ func (db *DB) Import(attr *DbAttr) (string, error) { } // write value - err = databucket.Put([]byte(entry.Key), []byte(entry.Value)) + err = databucket.Put([]byte(entry.GetKey()), []byte(entry.GetValue())) if err != nil { return fmt.Errorf("failed to insert data: %w", err) } @@ -490,7 +428,6 @@ func (db *DB) Import(attr *DbAttr) (string, error) { return nil }) - if err != nil { return "", cleanError(newfile, err) } @@ -528,16 +465,14 @@ func (db *DB) Info() (*DbInfo, error) { } info.Buckets = append(info.Buckets, binfo) + return nil - }) - if err != nil { return fmt.Errorf("failed to read from DB: %w", err) } return nil - }) return info, err @@ -567,7 +502,7 @@ func (db *DB) Getall(attr *DbAttr) (DbEntries, error) { // get data sub bucket databucket := root.Bucket([]byte("data")) if databucket == nil { - return fmt.Errorf("failed to retrieve data sub bucket") + return errors.New("failed to retrieve data sub bucket") } slog.Debug("opened buckets", "root", root, "data", bucket) @@ -580,7 +515,7 @@ func (db *DB) Getall(attr *DbAttr) (DbEntries, error) { } // retrieve the value from the data sub bucket - value := databucket.Get([]byte(entry.Key)) + value := databucket.Get([]byte(entry.GetKey())) // we need to make a copy of it, otherwise we'll get an // "unexpected fault address" error @@ -595,5 +530,66 @@ func (db *DB) Getall(attr *DbAttr) (DbEntries, error) { return err }) + return entries, err } + +// internal DB getter, assumes db.DB has already been +// opened successfully. Do NOT call this w/o valid +// DB handle! +func (db *DB) txGet(attr *DbAttr) (*DbEntry, error) { + entry := DbEntry{} + + err := db.DB.View(func(tx *bolt.Tx) error { + // root bucket + root := tx.Bucket([]byte(db.Bucket)) + if root == nil { + return nil + } + + // get meta sub bucket + bucket := root.Bucket([]byte("meta")) + if bucket == nil { + return nil + } + + slog.Debug("opened buckets", "root", root, "data", bucket) + + // retrieve meta data + pbentry := bucket.Get([]byte(attr.Key)) + if pbentry == nil { + return fmt.Errorf("no such key: %s", attr.Key) + } + + // put into struct + if err := proto.Unmarshal(pbentry, &entry); err != nil { + return fmt.Errorf("failed to unmarshal from protobuf: %w", err) + } + + // get data sub bucket + databucket := root.Bucket([]byte("data")) + if databucket == nil { + return errors.New("failed to retrieve data sub bucket") + } + + // retrieve actual data value + value := databucket.Get([]byte(attr.Key)) + if len(value) == 0 { + return fmt.Errorf("no such key: %s", attr.Key) + } + + // we need to make a copy of it, otherwise we'll get an + // "unexpected fault address" error + vc := make([]byte, len(value)) + copy(vc, value) + + entry.Value = string(vc) + + return nil + }) + if err != nil { + return nil, err + } + + return &entry, nil +} diff --git a/app/io.go b/app/io.go index 7754bf1..e8c49f7 100644 --- a/app/io.go +++ b/app/io.go @@ -1,3 +1,5 @@ +package app + /* Copyright © 2024 Thomas von Dein @@ -14,18 +16,24 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package app -import "os" +import ( + "errors" + "os" +) func cleanError(file string, err error) error { // remove given [backup] file and forward the given error - return os.Remove(file) + oserr := os.Remove(file) + if oserr != nil { + return errors.Join(err, oserr) + } + + return err } func fileExists(filename string) bool { info, err := os.Stat(filename) - if err != nil { // return false on any error return false diff --git a/cfg/config.go b/cfg/config.go index bd4b220..2d55868 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -83,6 +83,7 @@ func (conf *Config) ParseConfigFile(file string) error { } add := Config{} + err = toml.Unmarshal(data, &add) if err != nil { return fmt.Errorf("failed to unmarshall toml: %w", err) diff --git a/cmd/anydb.go b/cmd/anydb.go index 28414a8..314cdff 100644 --- a/cmd/anydb.go +++ b/cmd/anydb.go @@ -1,7 +1,7 @@ package cmd var manpage = ` -anydb +NAME anydb - a personal key value store SYNOPSIS @@ -179,7 +179,7 @@ SUBCOMMANDS the same meaning as in the list command. The "json" mode prints the raw JSON representation of the whole database entry. Decryption will only take place in "simple" and "json" mode. The "template" mode provides the - most flexibily, it is detailed in the section TEMPLATES. + most flexibility, it is detailed in the section TEMPLATES. list The list subcommand displays a list of all database entries. @@ -407,7 +407,7 @@ TEMPLATES Each template operates on one or more entries, no loop construct is required, the template provided applies to every matching entry - separatley. + separateley. The following template variables can be used: @@ -417,6 +417,9 @@ TEMPLATES "-s" flag so that values are included in output data stream. Otherwise you can only use ".Preview". + Be careful if you use "-s" and ".Value" though: if a value is a + multiline string, it may clutter your output. + .Preview string First line of entry value (if it is a string). diff --git a/cmd/crud.go b/cmd/crud.go index 7f9c6b4..3597e6a 100644 --- a/cmd/crud.go +++ b/cmd/crud.go @@ -116,19 +116,19 @@ func Get(conf *cfg.Config) *cobra.Command { return err } - if entry.Encrypted { + if entry.GetEncrypted() { pass, err := getPassword() if err != nil { return err } - clear, err := app.Decrypt(pass, []byte(entry.Value)) + clear, err := app.Decrypt(pass, []byte(entry.GetValue())) if err != nil { return err } entry.Value = string(clear) - entry.Size = uint64(len(entry.Value)) + entry.Size = uint64(len(entry.GetValue())) entry.Encrypted = false } @@ -137,7 +137,8 @@ func Get(conf *cfg.Config) *cobra.Command { } 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|template) (default 'simple')") + cmd.PersistentFlags().StringVarP(&conf.Mode, "mode", "m", "", + "output format (simple|wide|json|template) (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'") @@ -224,7 +225,8 @@ func List(conf *cfg.Config) *cobra.Command { }, } - cmd.PersistentFlags().StringVarP(&conf.Mode, "mode", "m", "", "output format (table|wide|json|template), wide is a verbose table. (default 'table')") + cmd.PersistentFlags().StringVarP(&conf.Mode, "mode", "m", "", + "output format (table|wide|json|template), 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") diff --git a/cmd/extra.go b/cmd/extra.go index 741ede0..a982b65 100644 --- a/cmd/extra.go +++ b/cmd/extra.go @@ -1,3 +1,5 @@ +package cmd + /* Copyright © 2024 Thomas von Dein @@ -14,7 +16,6 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package cmd import ( "bytes" @@ -25,11 +26,11 @@ import ( "os" "os/exec" - "github.com/spf13/cobra" "codeberg.org/scip/anydb/app" "codeberg.org/scip/anydb/cfg" "codeberg.org/scip/anydb/output" "codeberg.org/scip/anydb/rest" + "github.com/spf13/cobra" ) func Export(conf *cfg.Config) *cobra.Command { @@ -57,6 +58,7 @@ func Export(conf *cfg.Config) *cobra.Command { } cmd.PersistentFlags().StringVarP(&attr.File, "output-file", "o", "", "filename or - for STDIN") + if err := cmd.MarkPersistentFlagRequired("output-file"); err != nil { panic(err) } @@ -86,12 +88,14 @@ func Import(conf *cfg.Config) *cobra.Command { } fmt.Print(out) + return nil }, } cmd.PersistentFlags().StringVarP(&attr.File, "import-file", "i", "", "filename or - for STDIN") cmd.PersistentFlags().StringArrayVarP(&attr.Tags, "tags", "t", nil, "tags, multiple allowed") + if err := cmd.MarkPersistentFlagRequired("import-file"); err != nil { panic(err) } @@ -124,7 +128,6 @@ func Man(conf *cfg.Config) *cobra.Command { man.Stderr = os.Stderr err := man.Run() - if err != nil { return fmt.Errorf("failed to execute 'less': %w", err) } @@ -205,19 +208,20 @@ func Edit(conf *cfg.Config) *cobra.Command { return err } - if len(entry.Value) == 0 && entry.Binary { + if len(entry.GetValue()) == 0 && entry.GetBinary() { return errors.New("key contains binary uneditable content") } // decrypt if needed - if entry.Encrypted { + if entry.GetEncrypted() { pass, err := getPassword() if err != nil { return err } + password = pass - clear, err := app.Decrypt(pass, []byte(entry.Value)) + clear, err := app.Decrypt(pass, []byte(entry.GetValue())) if err != nil { return err } @@ -232,7 +236,7 @@ func Edit(conf *cfg.Config) *cobra.Command { // save file to a temp file, call the editor with it, read // it back in and compare the content with the original // one - newcontent, err := editContent(editor, string(entry.Value)) + newcontent, err := editContent(editor, entry.GetValue()) if err != nil { return err } @@ -328,7 +332,7 @@ func editContent(editor string, content string) (string, error) { newcontentstr := string(newcontent) if content == newcontentstr { - return "", fmt.Errorf("content not modified, aborting") + return "", errors.New("content not modified, aborting") } return newcontentstr, nil diff --git a/cmd/root.go b/cmd/root.go index 61925eb..609aa19 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -24,9 +24,9 @@ import ( "path/filepath" "runtime/debug" - "github.com/spf13/cobra" "codeberg.org/scip/anydb/app" "codeberg.org/scip/anydb/cfg" + "github.com/spf13/cobra" "github.com/tlinden/yadu" ) @@ -69,7 +69,6 @@ func Execute() { Short: "anydb", Long: `A personal key value store`, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - var configs []string if configfile != "" { configs = []string{configfile} @@ -110,12 +109,14 @@ func Execute() { } conf.DB = db + return nil }, RunE: func(cmd *cobra.Command, args []string) error { if ShowVersion { fmt.Printf("This is anydb version %s\n", cfg.Version) + return nil } diff --git a/common/io.go b/common/io.go index 47933fb..7a4e273 100644 --- a/common/io.go +++ b/common/io.go @@ -25,7 +25,6 @@ func CleanError(file string, err error) error { func FileExists(filename string) bool { info, err := os.Stat(filename) - if err != nil { // return false on any error return false diff --git a/common/logger.go b/common/logger.go index b11fef6..fa14e26 100644 --- a/common/logger.go +++ b/common/logger.go @@ -9,15 +9,15 @@ type Slogger struct { *slog.Logger } -func (l Slogger) Debug(v ...interface{}) {} -func (l Slogger) Debugf(format string, v ...interface{}) { l.Logger.Debug(fmt.Sprintf(format, v...)) } -func (l Slogger) Error(v ...interface{}) {} -func (l Slogger) Errorf(format string, v ...interface{}) { l.Logger.Error(fmt.Sprintf(format, v...)) } -func (l Slogger) Info(v ...interface{}) {} -func (l Slogger) Infof(format string, v ...interface{}) { l.Logger.Info(fmt.Sprintf(format, v...)) } -func (l Slogger) Warning(v ...interface{}) {} -func (l Slogger) Warningf(format string, v ...interface{}) { l.Logger.Warn(fmt.Sprintf(format, v...)) } -func (l Slogger) Fatal(v ...interface{}) {} -func (l Slogger) Fatalf(format string, v ...interface{}) { l.Logger.Error(fmt.Sprintf(format, v...)) } -func (l Slogger) Panic(v ...interface{}) {} -func (l Slogger) Panicf(format string, v ...interface{}) { l.Logger.Error(fmt.Sprintf(format, v...)) } +func (l Slogger) Debug(v ...any) {} +func (l Slogger) Debugf(format string, v ...any) { l.Logger.Debug(fmt.Sprintf(format, v...)) } +func (l Slogger) Error(v ...any) {} +func (l Slogger) Errorf(format string, v ...any) { l.Logger.Error(fmt.Sprintf(format, v...)) } +func (l Slogger) Info(v ...any) {} +func (l Slogger) Infof(format string, v ...any) { l.Logger.Info(fmt.Sprintf(format, v...)) } +func (l Slogger) Warning(v ...any) {} +func (l Slogger) Warningf(format string, v ...any) { l.Warn(fmt.Sprintf(format, v...)) } +func (l Slogger) Fatal(v ...any) {} +func (l Slogger) Fatalf(format string, v ...any) { l.Logger.Error(fmt.Sprintf(format, v...)) } +func (l Slogger) Panic(v ...any) {} +func (l Slogger) Panicf(format string, v ...any) { l.Logger.Error(fmt.Sprintf(format, v...)) } diff --git a/main.go b/main.go index dc4c7da..52c4d48 100644 --- a/main.go +++ b/main.go @@ -23,8 +23,8 @@ import ( "os" "runtime" - "github.com/inconshreveable/mousetrap" "codeberg.org/scip/anydb/cmd" + "github.com/inconshreveable/mousetrap" ) func main() { @@ -34,30 +34,13 @@ func main() { Main() } -func init() { - // if we're running on Windows AND if the user double clicked the - // exe file from explorer, we tell them and then wait until any - // key has been hit, which will make the cmd window disappear and - // thus give the user time to read it. - if runtime.GOOS == "windows" { - if mousetrap.StartedByExplorer() { - fmt.Println("Do no double click anydb.exe!") - fmt.Println("Please open a command shell and run it from there.") - fmt.Println() - fmt.Print("Press any key to quit: ") - _, err := bufio.NewReader(os.Stdin).ReadString('\n') - if err != nil { - panic(err) - } - } - } -} - func Main() int { cmd.Execute() + return 0 } +// nolint gochecknoinits func init() { // if we're running on Windows AND if the user double clicked the // exe file from explorer, we tell them and then wait until any @@ -69,6 +52,7 @@ func init() { fmt.Println("Please open a command shell and run it from there.") fmt.Println() fmt.Print("Press any key to quit: ") + _, err := bufio.NewReader(os.Stdin).ReadString('\n') if err != nil { panic(err) diff --git a/output/list.go b/output/list.go index cd08f4c..781eef3 100644 --- a/output/list.go +++ b/output/list.go @@ -26,12 +26,12 @@ import ( "strings" tpl "text/template" + "codeberg.org/scip/anydb/app" + "codeberg.org/scip/anydb/cfg" "github.com/dustin/go-humanize" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/renderer" "github.com/olekukonko/tablewriter/tw" - "codeberg.org/scip/anydb/app" - "codeberg.org/scip/anydb/cfg" ) func List(writer io.Writer, conf *cfg.Config, entries app.DbEntries) error { @@ -54,6 +54,7 @@ func ListJson(writer io.Writer, conf *cfg.Config, entries app.DbEntries) error { } fmt.Println(string(jsonentries)) + return nil } @@ -67,6 +68,7 @@ func ListTemplate(writer io.Writer, conf *cfg.Config, entries app.DbEntries) err for _, row := range entries { buf.Reset() + err = tmpl.Execute(&buf, row) if err != nil { return fmt.Errorf("failed to execute output template: %w", err) @@ -133,28 +135,27 @@ func ListTable(writer io.Writer, conf *cfg.Config, entries app.DbEntries) error case true: if err := table.Append([]string{ - row.Key, - strings.Join(row.Tags, ","), - strconv.FormatUint(row.Size, 10), - row.Created.AsTime().Format("02.01.2006T03:04.05"), - row.Preview, + row.GetKey(), + strings.Join(row.GetTags(), ","), + strconv.FormatUint(row.GetSize(), 10), + row.GetCreated().AsTime().Format("02.01.2006T03:04.05"), + row.GetPreview(), }); err != nil { return fmt.Errorf("failed to add data to table: %w", err) } default: if err := table.Append([]string{ - row.Key, - strings.Join(row.Tags, ","), - humanize.Bytes(uint64(row.Size)), - humanize.Time(row.Created.AsTime()), - row.Preview, + row.GetKey(), + strings.Join(row.GetTags(), ","), + humanize.Bytes(row.GetSize()), + humanize.Time(row.GetCreated().AsTime()), + row.GetPreview(), }); err != nil { return fmt.Errorf("failed to add data to table: %w", err) } } - } else { - if err := table.Append([]string{row.Key, row.Preview}); err != nil { + if err := table.Append([]string{row.GetKey(), row.GetPreview()}); err != nil { return fmt.Errorf("failed to add data to table: %w", err) } } diff --git a/output/single.go b/output/single.go index 2a5f777..badd5cd 100644 --- a/output/single.go +++ b/output/single.go @@ -24,11 +24,11 @@ import ( "os" "reflect" - "github.com/dustin/go-humanize" "codeberg.org/scip/anydb/app" "codeberg.org/scip/anydb/cfg" + "github.com/dustin/go-humanize" "golang.org/x/term" - //"github.com/alecthomas/repr" + // "github.com/alecthomas/repr" ) func Print(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app.DbEntry) error { @@ -40,17 +40,18 @@ func Print(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app.DbEn switch conf.Mode { case "simple", "": - if entry.Binary { + if entry.GetBinary() { if isatty { fmt.Println("binary data omitted") } else { - if _, err := os.Stdout.WriteString(entry.Value); err != nil { + if _, err := os.Stdout.WriteString(entry.GetValue()); err != nil { return err } } } else { - fmt.Print(string(entry.Value)) - if entry.Value[entry.Size-1] != '\n' { + fmt.Print(entry.GetValue()) + + if entry.GetValue()[entry.GetSize()-1] != '\n' { // always add a terminal newline fmt.Println() } @@ -72,8 +73,10 @@ func Print(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app.DbEn } func WriteFile(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app.DbEntry) error { - var fileHandle *os.File - var err error + var ( + fileHandle *os.File + err error + ) if attr.File == "-" { fileHandle = os.Stdout @@ -92,10 +95,10 @@ func WriteFile(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app. } // actually write file content - _, err = fileHandle.WriteString(entry.Value) + _, err = fileHandle.WriteString(entry.GetValue()) - if !entry.Binary { - if entry.Value[entry.Size-1] != '\n' { + if !entry.GetBinary() { + if entry.GetValue()[entry.GetSize()-1] != '\n' { // always add a terminal newline _, err = fileHandle.Write([]byte{'\n'}) } @@ -137,7 +140,7 @@ func Info(writer io.Writer, conf *cfg.Config, info *app.DbInfo) error { if conf.Debug { val := reflect.ValueOf(&bucket.Stats).Elem() - for i := 0; i < val.NumField(); i++ { + for i := range val.NumField() { if _, err := fmt.Fprintf(writer, "%19s: %v\n", val.Type().Field(i).Name, val.Field(i)); err != nil { return fmt.Errorf("failed to write output: %w", err) } diff --git a/rest/handlers.go b/rest/handlers.go index 6ade7d4..f6d2d80 100644 --- a/rest/handlers.go +++ b/rest/handlers.go @@ -17,7 +17,7 @@ along with this program. If not, see . package rest import ( - //"github.com/alecthomas/repr" + // "github.com/alecthomas/repr" "encoding/json" "log" @@ -28,7 +28,7 @@ import ( ) type SetContext struct { - Query string `json:"query" form:"query"` + Query string `form:"query" json:"query"` } type ListResponse struct { @@ -50,6 +50,7 @@ func RestList(resp http.ResponseWriter, req *http.Request, conf *cfg.Config) { if err != nil { if err.Error() != `EOF` { http.Error(resp, err.Error(), http.StatusBadRequest) + return } } @@ -58,6 +59,7 @@ func RestList(resp http.ResponseWriter, req *http.Request, conf *cfg.Config) { entries, err := conf.DB.List(attr, attr.Fulltext) if err != nil { JsonStatus(resp, http.StatusForbidden, "Unable to list keys: "+err.Error()) + return } @@ -69,7 +71,6 @@ func RestList(resp http.ResponseWriter, req *http.Request, conf *cfg.Config) { Success: true, Entries: entries, }) - if err != nil { log.Fatal(err) } @@ -78,6 +79,7 @@ func RestList(resp http.ResponseWriter, req *http.Request, conf *cfg.Config) { func RestGet(resp http.ResponseWriter, req *http.Request, key string, conf *cfg.Config) { if key == "" { JsonStatus(resp, http.StatusForbidden, "key not provided") + return } @@ -85,10 +87,13 @@ func RestGet(resp http.ResponseWriter, req *http.Request, key string, conf *cfg. entry, err := conf.DB.Get(&app.DbAttr{Key: key}) if err != nil { JsonStatus(resp, http.StatusForbidden, "Unable to get key: "+err.Error()) + return } - if entry.Key == "" { + + if entry.GetKey() == "" { JsonStatus(resp, http.StatusForbidden, "Key does not exist") + return } @@ -100,7 +105,6 @@ func RestGet(resp http.ResponseWriter, req *http.Request, key string, conf *cfg. Success: true, Entry: entry, }) - if err != nil { log.Fatal(err) } @@ -109,6 +113,7 @@ func RestGet(resp http.ResponseWriter, req *http.Request, key string, conf *cfg. func RestDelete(resp http.ResponseWriter, req *http.Request, key string, conf *cfg.Config) { if key == "" { JsonStatus(resp, http.StatusForbidden, "key not provided") + return } @@ -116,6 +121,7 @@ func RestDelete(resp http.ResponseWriter, req *http.Request, key string, conf *c err := conf.DB.Del(&app.DbAttr{Key: key}) if err != nil { JsonStatus(resp, http.StatusForbidden, "Unable to delete key: "+err.Error()) + return } @@ -128,6 +134,7 @@ func RestSet(resp http.ResponseWriter, req *http.Request, conf *cfg.Config) { err := json.NewDecoder(req.Body).Decode(&attr) if err != nil { http.Error(resp, err.Error(), http.StatusBadRequest) + return } @@ -141,6 +148,7 @@ func RestSet(resp http.ResponseWriter, req *http.Request, conf *cfg.Config) { err = conf.DB.Set(attr) if err != nil { JsonStatus(resp, http.StatusForbidden, "Unable to set key: "+err.Error()) + return } diff --git a/rest/log.go b/rest/log.go index 577cc6a..70aa51a 100644 --- a/rest/log.go +++ b/rest/log.go @@ -1,3 +1,5 @@ +package rest + /* This logging middleware is based on @@ -7,7 +9,6 @@ https://github.com/elithrar/admission-control/blob/v0.6.3/request_logger.go I am using slog and added a couple of small modifications. */ -package rest import ( "log/slog" @@ -20,6 +21,7 @@ import ( // written HTTP status code to be captured for logging. type responseWriter struct { http.ResponseWriter + status int size int wroteHeader bool @@ -48,7 +50,6 @@ func (rw *responseWriter) WriteHeader(code int) { } func (rw *responseWriter) Write(data []byte) (int, error) { - written, err := rw.ResponseWriter.Write(data) rw.size += written @@ -75,7 +76,9 @@ func LogHandler() func(http.Handler) http.Handler { next.ServeHTTP(wrapped, req) header := wrapped.Header()["Content-Type"] - contenttype := "" + + var contenttype string + if header == nil { contenttype = "text/plain" } else { diff --git a/rest/serve.go b/rest/serve.go index fb8f0ab..0c23093 100644 --- a/rest/serve.go +++ b/rest/serve.go @@ -85,7 +85,6 @@ func JsonStatus(resp http.ResponseWriter, code int, msg string) { Message: msg, Success: success, }) - if err != nil { log.Fatal(err) }