commit 87da1a20d339586a37b6b1fe952d3ed27ae48876
parent 7d9e7a76cf679d71242ff2ae4c5f0ef94794b4c8
Author: Sean Enck <sean@ttypty.com>
Date: Sat, 7 Dec 2024 14:54:29 -0500
migrate core back out into proper subcomponents
Diffstat:
23 files changed, 323 insertions(+), 320 deletions(-)
diff --git a/internal/app/core.go b/internal/app/core.go
@@ -13,8 +13,8 @@ import (
"text/template"
"github.com/seanenck/lockbox/internal/backend"
- "github.com/seanenck/lockbox/internal/core"
"github.com/seanenck/lockbox/internal/platform"
+ "github.com/seanenck/lockbox/internal/util"
)
const (
@@ -299,7 +299,7 @@ func processDoc(header, file string, doc Documentation) (string, error) {
if err := t.Execute(&buf, doc); err != nil {
return "", err
}
- return fmt.Sprintf("%s\n%s", header, core.TextWrap(0, buf.String())), nil
+ return fmt.Sprintf("%s\n%s", header, util.TextWrap(0, buf.String())), nil
}
func setDocFlag(f string) string {
diff --git a/internal/app/totp.go b/internal/app/totp.go
@@ -12,8 +12,8 @@ import (
"github.com/seanenck/lockbox/internal/backend"
"github.com/seanenck/lockbox/internal/config"
- "github.com/seanenck/lockbox/internal/core"
"github.com/seanenck/lockbox/internal/platform/clip"
+ "github.com/seanenck/lockbox/internal/util"
)
var (
@@ -76,12 +76,12 @@ func clearFunc() {
fmt.Print("\033[H\033[2J")
}
-func colorWhenRules() ([]core.ColorWindow, error) {
+func colorWhenRules() ([]util.TimeWindow, error) {
envTime := config.EnvTOTPColorBetween.Get()
if envTime == config.TOTPDefaultBetween {
return config.TOTPDefaultColorWindow, nil
}
- return core.ParseColorWindow(envTime)
+ return util.ParseTimeWindow(envTime)
}
func (w totpWrapper) generateCode() (string, error) {
diff --git a/internal/backend/query.go b/internal/backend/query.go
@@ -10,7 +10,7 @@ import (
"strings"
"github.com/seanenck/lockbox/internal/config"
- "github.com/seanenck/lockbox/internal/core"
+ "github.com/seanenck/lockbox/internal/output"
"github.com/tobischo/gokeepasslib/v3"
)
@@ -175,16 +175,16 @@ func (t *Transaction) QueryCallback(args QueryOptions) (QuerySeq2, error) {
if err != nil {
return nil, err
}
- jsonMode := core.JSONOutputs.Blank
+ jsonMode := output.JSONModes.Blank
if args.Values == JSONValue {
- m, err := core.ParseJSONOutput(config.EnvJSONMode.Get())
+ m, err := output.ParseJSONMode(config.EnvJSONMode.Get())
if err != nil {
return nil, err
}
jsonMode = m
}
var hashLength int
- if jsonMode == core.JSONOutputs.Hash {
+ if jsonMode == output.JSONModes.Hash {
hashLength, err = config.EnvJSONHashLength.Get()
if err != nil {
return nil, err
@@ -203,9 +203,9 @@ func (t *Transaction) QueryCallback(args QueryOptions) (QuerySeq2, error) {
case JSONValue:
data := ""
switch jsonMode {
- case core.JSONOutputs.Raw:
+ case output.JSONModes.Raw:
data = val
- case core.JSONOutputs.Hash:
+ case output.JSONModes.Hash:
data = fmt.Sprintf("%x", sha512.Sum512([]byte(val)))
if hashLength > 0 && len(data) > hashLength {
data = data[0:hashLength]
diff --git a/internal/config/core.go b/internal/config/core.go
@@ -11,7 +11,7 @@ import (
"strings"
"time"
- "github.com/seanenck/lockbox/internal/core"
+ "github.com/seanenck/lockbox/internal/util"
"mvdan.cc/sh/v3/shell"
)
@@ -44,24 +44,24 @@ const (
requiredKeyOrKeyFile = "a key, a key file, or both must be set"
// ModTimeFormat is the expected modtime format
ModTimeFormat = time.RFC3339
- exampleColorWindow = "start" + core.ColorWindowSpan + "end"
+ exampleColorWindow = "start" + util.TimeWindowSpan + "end"
)
var (
- exampleColorWindows = []string{fmt.Sprintf("[%s]", strings.Join([]string{exampleColorWindow, exampleColorWindow, exampleColorWindow + "..."}, core.ColorWindowDelimiter))}
+ exampleColorWindows = []string{fmt.Sprintf("[%s]", strings.Join([]string{exampleColorWindow, exampleColorWindow, exampleColorWindow + "..."}, util.TimeWindowDelimiter))}
configDirOffsetFile = filepath.Join(configDirName, tomlFile)
xdgPaths = []string{configDirOffsetFile, tomlFile}
homePaths = []string{filepath.Join(configDir, configDirOffsetFile), filepath.Join(configDir, tomlFile)}
registry = map[string]printer{}
// TOTPDefaultColorWindow is the default coloring rules for totp
- TOTPDefaultColorWindow = []core.ColorWindow{{Start: 0, End: 5}, {Start: 30, End: 35}}
+ TOTPDefaultColorWindow = []util.TimeWindow{{Start: 0, End: 5}, {Start: 30, End: 35}}
// TOTPDefaultBetween is the default color window as a string
TOTPDefaultBetween = func() string {
var results []string
for _, w := range TOTPDefaultColorWindow {
- results = append(results, fmt.Sprintf("%d%s%d", w.Start, core.ColorWindowSpan, w.End))
+ results = append(results, fmt.Sprintf("%d%s%d", w.Start, util.TimeWindowSpan, w.End))
}
- return strings.Join(results, core.ColorWindowDelimiter)
+ return strings.Join(results, util.TimeWindowDelimiter)
}()
)
diff --git a/internal/config/toml.go b/internal/config/toml.go
@@ -10,7 +10,7 @@ import (
"strings"
"github.com/BurntSushi/toml"
- "github.com/seanenck/lockbox/internal/core"
+ "github.com/seanenck/lockbox/internal/util"
)
const (
@@ -119,7 +119,7 @@ func generateDetailText(data printer) (string, error) {
value = "(unset)"
}
key := env.Key()
- description := strings.TrimSpace(core.TextWrap(2, env.desc))
+ description := strings.TrimSpace(util.TextWrap(2, env.desc))
requirement := "optional/default"
r := strings.TrimSpace(env.requirement)
if r != "" {
diff --git a/internal/config/vars.go b/internal/config/vars.go
@@ -5,8 +5,9 @@ import (
"fmt"
"strings"
- "github.com/seanenck/lockbox/internal/core"
+ "github.com/seanenck/lockbox/internal/output"
"github.com/seanenck/lockbox/internal/platform"
+ "github.com/seanenck/lockbox/internal/util"
)
var (
@@ -29,7 +30,7 @@ var (
environmentBase{
cat: jsonCategory,
subKey: "HASH_LENGTH",
- desc: fmt.Sprintf("Maximum string length of the JSON value when '%s' mode is set for JSON output.", core.JSONOutputs.Hash),
+ desc: fmt.Sprintf("Maximum string length of the JSON value when '%s' mode is set for JSON output.", output.JSONModes.Hash),
}),
shortDesc: "hash length",
allowZero: true,
@@ -181,7 +182,7 @@ var (
cat: totpCategory,
desc: fmt.Sprintf(`Override when to set totp generated outputs to different colors,
must be a list of one (or more) rules where a '%s' delimits the start and end second (0-60 for each),
-and '%s' allows for multiple windows.`, core.ColorWindowSpan, core.ColorWindowDelimiter),
+and '%s' allows for multiple windows.`, util.TimeWindowSpan, util.TimeWindowDelimiter),
}),
isArray: true,
canDefault: true,
@@ -215,14 +216,14 @@ and '%s' allows for multiple windows.`, core.ColorWindowSpan, core.ColorWindowDe
// EnvJSONMode controls how JSON is output in the 'data' field
EnvJSONMode = environmentRegister(
EnvironmentString{
- environmentDefault: newDefaultedEnvironment(string(core.JSONOutputs.Hash),
+ environmentDefault: newDefaultedEnvironment(string(output.JSONModes.Hash),
environmentBase{
cat: jsonCategory,
subKey: "MODE",
- desc: fmt.Sprintf("Changes what the data field in JSON outputs will contain.\n\nUse '%s' with CAUTION.", core.JSONOutputs.Raw),
+ desc: fmt.Sprintf("Changes what the data field in JSON outputs will contain.\n\nUse '%s' with CAUTION.", output.JSONModes.Raw),
}),
canDefault: true,
- allowed: core.JSONOutputs.List(),
+ allowed: output.JSONModes.List(),
})
// EnvTOTPFormat supports formatting the TOTP tokens for generation of tokens
EnvTOTPFormat = environmentRegister(EnvironmentFormatter{environmentBase: environmentBase{
diff --git a/internal/core/colors.go b/internal/core/colors.go
@@ -1,53 +0,0 @@
-// Package core has to assist with some color components
-package core
-
-import (
- "errors"
- "fmt"
- "strconv"
- "strings"
-)
-
-const (
- // ColorWindowDelimiter indicates how windows are split in env/config keys
- ColorWindowDelimiter = " "
- // ColorWindowSpan indicates the delineation betwee start -> end (start:end)
- ColorWindowSpan = ":"
-)
-
-// ColorWindow for handling terminal colors based on timing
-type ColorWindow struct {
- Start int
- End int
-}
-
-// ParseColorWindow will handle parsing a window of colors for TOTP operations
-func ParseColorWindow(windowString string) ([]ColorWindow, error) {
- var rules []ColorWindow
- for _, item := range strings.Split(windowString, ColorWindowDelimiter) {
- line := strings.TrimSpace(item)
- if line == "" {
- continue
- }
- parts := strings.Split(line, ColorWindowSpan)
- if len(parts) != 2 {
- return nil, fmt.Errorf("invalid colorization rule found: %s", line)
- }
- s, err := strconv.Atoi(parts[0])
- if err != nil {
- return nil, err
- }
- e, err := strconv.Atoi(parts[1])
- if err != nil {
- return nil, err
- }
- if s < 0 || e < 0 || e < s || s > 59 || e > 59 {
- return nil, fmt.Errorf("invalid time found for colorization rule: %s", line)
- }
- rules = append(rules, ColorWindow{Start: s, End: e})
- }
- if len(rules) == 0 {
- return nil, errors.New("invalid colorization rules for totp, none found")
- }
- return rules, nil
-}
diff --git a/internal/core/colors_test.go b/internal/core/colors_test.go
@@ -1,40 +0,0 @@
-package core_test
-
-import (
- "testing"
-
- "github.com/seanenck/lockbox/internal/core"
-)
-
-func TestParseWindows(t *testing.T) {
- if _, err := core.ParseColorWindow(""); err.Error() != "invalid colorization rules for totp, none found" {
- t.Errorf("invalid error: %v", err)
- }
- if _, err := core.ParseColorWindow(" 2"); err.Error() != "invalid colorization rule found: 2" {
- t.Errorf("invalid error: %v", err)
- }
- if _, err := core.ParseColorWindow(" 1:200"); err.Error() != "invalid time found for colorization rule: 1:200" {
- t.Errorf("invalid error: %v", err)
- }
- if _, err := core.ParseColorWindow(" 1:-1"); err.Error() != "invalid time found for colorization rule: 1:-1" {
- t.Errorf("invalid error: %v", err)
- }
- if _, err := core.ParseColorWindow(" 200:1"); err.Error() != "invalid time found for colorization rule: 200:1" {
- t.Errorf("invalid error: %v", err)
- }
- if _, err := core.ParseColorWindow(" -1:1"); err.Error() != "invalid time found for colorization rule: -1:1" {
- t.Errorf("invalid error: %v", err)
- }
- if _, err := core.ParseColorWindow(" 2:1"); err.Error() != "invalid time found for colorization rule: 2:1" {
- t.Errorf("invalid error: %v", err)
- }
- if _, err := core.ParseColorWindow("xxx:1"); err.Error() != "strconv.Atoi: parsing \"xxx\": invalid syntax" {
- t.Errorf("invalid error: %v", err)
- }
- if _, err := core.ParseColorWindow(" 1:xxx"); err.Error() != "strconv.Atoi: parsing \"xxx\": invalid syntax" {
- t.Errorf("invalid error: %v", err)
- }
- if _, err := core.ParseColorWindow("1:2 11:22"); err != nil {
- t.Errorf("invalid error: %v", err)
- }
-}
diff --git a/internal/core/core.go b/internal/core/core.go
@@ -1,19 +0,0 @@
-// Package core has helpers
-package core
-
-import (
- "fmt"
- "reflect"
- "sort"
-)
-
-// ListFields will get the values of strings on an "all string" struct
-func ListFields(p any) []string {
- v := reflect.ValueOf(p)
- var vals []string
- for i := 0; i < v.NumField(); i++ {
- vals = append(vals, fmt.Sprintf("%v", v.Field(i).Interface()))
- }
- sort.Strings(vals)
- return vals
-}
diff --git a/internal/core/core_test.go b/internal/core/core_test.go
@@ -1,20 +0,0 @@
-package core_test
-
-import (
- "fmt"
- "testing"
-
- "github.com/seanenck/lockbox/internal/core"
-)
-
-type mock struct {
- Name string
- Field string
-}
-
-func TestListFields(t *testing.T) {
- fields := core.ListFields(mock{"abc", "xyz"})
- if len(fields) != 2 || fmt.Sprintf("%v", fields) != "[abc xyz]" {
- t.Errorf("invalid fields: %v", fields)
- }
-}
diff --git a/internal/core/json.go b/internal/core/json.go
@@ -1,41 +0,0 @@
-// Package core defines JSON outputs
-package core
-
-import (
- "fmt"
- "strings"
-)
-
-// JSONOutputs are the JSON data output types for exporting/output of values
-var JSONOutputs = JSONOutputTypes{
- Hash: "hash",
- Blank: "empty",
- Raw: "plaintext",
-}
-
-type (
- // JSONOutputMode is the output mode definition
- JSONOutputMode string
-
- // JSONOutputTypes indicate how JSON data can be exported for values
- JSONOutputTypes struct {
- Hash JSONOutputMode
- Blank JSONOutputMode
- Raw JSONOutputMode
- }
-)
-
-// List will list the output modes on the struct
-func (p JSONOutputTypes) List() []string {
- return ListFields(p)
-}
-
-// ParseJSONOutput handles detecting the JSON output mode
-func ParseJSONOutput(value string) (JSONOutputMode, error) {
- val := JSONOutputMode(strings.ToLower(strings.TrimSpace(value)))
- switch val {
- case JSONOutputs.Hash, JSONOutputs.Blank, JSONOutputs.Raw:
- return val, nil
- }
- return JSONOutputs.Blank, fmt.Errorf("invalid JSON output mode: %s", val)
-}
diff --git a/internal/core/json_test.go b/internal/core/json_test.go
@@ -1,33 +0,0 @@
-package core_test
-
-import (
- "fmt"
- "testing"
-
- "github.com/seanenck/lockbox/internal/core"
-)
-
-func TestJSONList(t *testing.T) {
- list := core.JSONOutputs.List()
- if len(list) != 3 || fmt.Sprintf("%v", list) != "[empty hash plaintext]" {
- t.Errorf("invalid list result: %v", list)
- }
-}
-
-func TestParseJSONMode(t *testing.T) {
- m, err := core.ParseJSONOutput("hAsH ")
- if m != core.JSONOutputs.Hash || err != nil {
- t.Error("invalid mode read")
- }
- m, err = core.ParseJSONOutput("EMPTY")
- if m != core.JSONOutputs.Blank || err != nil {
- t.Error("invalid mode read")
- }
- m, err = core.ParseJSONOutput(" PLAINtext ")
- if m != core.JSONOutputs.Raw || err != nil {
- t.Error("invalid mode read")
- }
- if _, err = core.ParseJSONOutput("a"); err == nil || err.Error() != "invalid JSON output mode: a" {
- t.Errorf("invalid error: %v", err)
- }
-}
diff --git a/internal/core/text.go b/internal/core/text.go
@@ -1,61 +0,0 @@
-package core
-
-import (
- "bytes"
- "fmt"
- "strings"
-)
-
-// TextWrap performs simple block text word wrapping
-func TextWrap(indent uint, in string) string {
- var sections []string
- var cur []string
- for _, line := range strings.Split(strings.TrimSpace(in), "\n") {
- trimmed := strings.TrimSpace(line)
- if trimmed == "" {
- if len(cur) > 0 {
- sections = append(sections, strings.Join(cur, " "))
- cur = []string{}
- }
- continue
- }
- cur = append(cur, line)
- }
- if len(cur) > 0 {
- sections = append(sections, strings.Join(cur, " "))
- }
- var out bytes.Buffer
- indenting := ""
- var cnt uint
- for cnt < indent {
- indenting = fmt.Sprintf("%s ", indenting)
- cnt++
- }
- indenture := int(80 - indent)
- for _, s := range sections {
- for _, line := range strings.Split(wrap(s, indenture), "\n") {
- fmt.Fprintf(&out, "%s%s\n", indenting, line)
- }
- fmt.Fprint(&out, "\n")
- }
- return out.String()
-}
-
-func wrap(in string, maxLength int) string {
- var lines []string
- var cur []string
- for _, p := range strings.Split(in, " ") {
- state := strings.Join(cur, " ")
- l := len(p)
- if len(state)+l >= maxLength {
- lines = append(lines, strings.Join(cur, " "))
- cur = []string{p}
- } else {
- cur = append(cur, p)
- }
- }
- if len(cur) > 0 {
- lines = append(lines, strings.Join(cur, " "))
- }
- return strings.Join(lines, "\n")
-}
diff --git a/internal/core/text_test.go b/internal/core/text_test.go
@@ -1,26 +0,0 @@
-package core_test
-
-import (
- "testing"
-
- "github.com/seanenck/lockbox/internal/core"
-)
-
-func TestWrap(t *testing.T) {
- w := core.TextWrap(0, "")
- if w != "" {
- t.Errorf("invalid wrap: %s", w)
- }
- w = core.TextWrap(0, "abc\n\nabc\nxyz\n")
- if w != "abc\n\nabc xyz\n\n" {
- t.Errorf("invalid wrap: %s", w)
- }
- w = core.TextWrap(0, "abc\n\nabc\nxyz\n\nx")
- if w != "abc\n\nabc xyz\n\nx\n\n" {
- t.Errorf("invalid wrap: %s", w)
- }
- w = core.TextWrap(5, "abc\n\nabc\nxyz\n\nx")
- if w != " abc\n\n abc xyz\n\n x\n\n" {
- t.Errorf("invalid wrap: %s", w)
- }
-}
diff --git a/internal/output/json.go b/internal/output/json.go
@@ -0,0 +1,43 @@
+// Package output defines JSON settings/modes
+package output
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/seanenck/lockbox/internal/util"
+)
+
+// JSONModes are the JSON data output types for exporting/output of values
+var JSONModes = JSONTypes{
+ Hash: "hash",
+ Blank: "empty",
+ Raw: "plaintext",
+}
+
+type (
+ // JSONMode is the output mode definition
+ JSONMode string
+
+ // JSONTypes indicate how JSON data can be exported for values
+ JSONTypes struct {
+ Hash JSONMode
+ Blank JSONMode
+ Raw JSONMode
+ }
+)
+
+// List will list the output modes on the struct
+func (p JSONTypes) List() []string {
+ return util.ListFields(p)
+}
+
+// ParseJSONMode handles detecting the JSON output mode
+func ParseJSONMode(value string) (JSONMode, error) {
+ val := JSONMode(strings.ToLower(strings.TrimSpace(value)))
+ switch val {
+ case JSONModes.Hash, JSONModes.Blank, JSONModes.Raw:
+ return val, nil
+ }
+ return JSONModes.Blank, fmt.Errorf("invalid JSON output mode: %s", val)
+}
diff --git a/internal/output/json_test.go b/internal/output/json_test.go
@@ -0,0 +1,33 @@
+package output_test
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/seanenck/lockbox/internal/output"
+)
+
+func TestJSONList(t *testing.T) {
+ list := output.JSONModes.List()
+ if len(list) != 3 || fmt.Sprintf("%v", list) != "[empty hash plaintext]" {
+ t.Errorf("invalid list result: %v", list)
+ }
+}
+
+func TestParseJSONMode(t *testing.T) {
+ m, err := output.ParseJSONMode("hAsH ")
+ if m != output.JSONModes.Hash || err != nil {
+ t.Error("invalid mode read")
+ }
+ m, err = output.ParseJSONMode("EMPTY")
+ if m != output.JSONModes.Blank || err != nil {
+ t.Error("invalid mode read")
+ }
+ m, err = output.ParseJSONMode(" PLAINtext ")
+ if m != output.JSONModes.Raw || err != nil {
+ t.Error("invalid mode read")
+ }
+ if _, err = output.ParseJSONMode("a"); err == nil || err.Error() != "invalid JSON output mode: a" {
+ t.Errorf("invalid error: %v", err)
+ }
+}
diff --git a/internal/platform/core.go b/internal/platform/core.go
@@ -7,7 +7,7 @@ import (
"os/exec"
"strings"
- "github.com/seanenck/lockbox/internal/core"
+ "github.com/seanenck/lockbox/internal/util"
)
// Systems are the known platforms for lockbox
@@ -35,7 +35,7 @@ type (
// List will list the platform types on the struct
func (p SystemTypes) List() []string {
- return core.ListFields(p)
+ return util.ListFields(p)
}
// NewSystem gets a new system platform.
diff --git a/internal/util/reflect.go b/internal/util/reflect.go
@@ -0,0 +1,19 @@
+// Package util has reflection helpers
+package util
+
+import (
+ "fmt"
+ "reflect"
+ "sort"
+)
+
+// ListFields will get the values of strings on an "all string" struct
+func ListFields(p any) []string {
+ v := reflect.ValueOf(p)
+ var vals []string
+ for i := 0; i < v.NumField(); i++ {
+ vals = append(vals, fmt.Sprintf("%v", v.Field(i).Interface()))
+ }
+ sort.Strings(vals)
+ return vals
+}
diff --git a/internal/util/reflect_test.go b/internal/util/reflect_test.go
@@ -0,0 +1,20 @@
+package util_test
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/seanenck/lockbox/internal/util"
+)
+
+type mock struct {
+ Name string
+ Field string
+}
+
+func TestListFields(t *testing.T) {
+ fields := util.ListFields(mock{"abc", "xyz"})
+ if len(fields) != 2 || fmt.Sprintf("%v", fields) != "[abc xyz]" {
+ t.Errorf("invalid fields: %v", fields)
+ }
+}
diff --git a/internal/util/text.go b/internal/util/text.go
@@ -0,0 +1,61 @@
+package util
+
+import (
+ "bytes"
+ "fmt"
+ "strings"
+)
+
+// TextWrap performs simple block text word wrapping
+func TextWrap(indent uint, in string) string {
+ var sections []string
+ var cur []string
+ for _, line := range strings.Split(strings.TrimSpace(in), "\n") {
+ trimmed := strings.TrimSpace(line)
+ if trimmed == "" {
+ if len(cur) > 0 {
+ sections = append(sections, strings.Join(cur, " "))
+ cur = []string{}
+ }
+ continue
+ }
+ cur = append(cur, line)
+ }
+ if len(cur) > 0 {
+ sections = append(sections, strings.Join(cur, " "))
+ }
+ var out bytes.Buffer
+ indenting := ""
+ var cnt uint
+ for cnt < indent {
+ indenting = fmt.Sprintf("%s ", indenting)
+ cnt++
+ }
+ indenture := int(80 - indent)
+ for _, s := range sections {
+ for _, line := range strings.Split(wrap(s, indenture), "\n") {
+ fmt.Fprintf(&out, "%s%s\n", indenting, line)
+ }
+ fmt.Fprint(&out, "\n")
+ }
+ return out.String()
+}
+
+func wrap(in string, maxLength int) string {
+ var lines []string
+ var cur []string
+ for _, p := range strings.Split(in, " ") {
+ state := strings.Join(cur, " ")
+ l := len(p)
+ if len(state)+l >= maxLength {
+ lines = append(lines, strings.Join(cur, " "))
+ cur = []string{p}
+ } else {
+ cur = append(cur, p)
+ }
+ }
+ if len(cur) > 0 {
+ lines = append(lines, strings.Join(cur, " "))
+ }
+ return strings.Join(lines, "\n")
+}
diff --git a/internal/util/text_test.go b/internal/util/text_test.go
@@ -0,0 +1,26 @@
+package util_test
+
+import (
+ "testing"
+
+ "github.com/seanenck/lockbox/internal/util"
+)
+
+func TestWrap(t *testing.T) {
+ w := util.TextWrap(0, "")
+ if w != "" {
+ t.Errorf("invalid wrap: %s", w)
+ }
+ w = util.TextWrap(0, "abc\n\nabc\nxyz\n")
+ if w != "abc\n\nabc xyz\n\n" {
+ t.Errorf("invalid wrap: %s", w)
+ }
+ w = util.TextWrap(0, "abc\n\nabc\nxyz\n\nx")
+ if w != "abc\n\nabc xyz\n\nx\n\n" {
+ t.Errorf("invalid wrap: %s", w)
+ }
+ w = util.TextWrap(5, "abc\n\nabc\nxyz\n\nx")
+ if w != " abc\n\n abc xyz\n\n x\n\n" {
+ t.Errorf("invalid wrap: %s", w)
+ }
+}
diff --git a/internal/util/time.go b/internal/util/time.go
@@ -0,0 +1,53 @@
+// Package util has to assist with some time windowing
+package util
+
+import (
+ "errors"
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+const (
+ // TimeWindowDelimiter indicates how windows are split in env/config keys
+ TimeWindowDelimiter = " "
+ // TimeWindowSpan indicates the delineation between start -> end (start:end)
+ TimeWindowSpan = ":"
+)
+
+// TimeWindow for handling terminal colors based on timing
+type TimeWindow struct {
+ Start int
+ End int
+}
+
+// ParseTimeWindow will handle parsing a window of colors for TOTP operations
+func ParseTimeWindow(windowString string) ([]TimeWindow, error) {
+ var rules []TimeWindow
+ for _, item := range strings.Split(windowString, TimeWindowDelimiter) {
+ line := strings.TrimSpace(item)
+ if line == "" {
+ continue
+ }
+ parts := strings.Split(line, TimeWindowSpan)
+ if len(parts) != 2 {
+ return nil, fmt.Errorf("invalid colorization rule found: %s", line)
+ }
+ s, err := strconv.Atoi(parts[0])
+ if err != nil {
+ return nil, err
+ }
+ e, err := strconv.Atoi(parts[1])
+ if err != nil {
+ return nil, err
+ }
+ if s < 0 || e < 0 || e < s || s > 59 || e > 59 {
+ return nil, fmt.Errorf("invalid time found for colorization rule: %s", line)
+ }
+ rules = append(rules, TimeWindow{Start: s, End: e})
+ }
+ if len(rules) == 0 {
+ return nil, errors.New("invalid colorization rules for totp, none found")
+ }
+ return rules, nil
+}
diff --git a/internal/util/time_test.go b/internal/util/time_test.go
@@ -0,0 +1,40 @@
+package util_test
+
+import (
+ "testing"
+
+ "github.com/seanenck/lockbox/internal/util"
+)
+
+func TestParseWindows(t *testing.T) {
+ if _, err := util.ParseTimeWindow(""); err.Error() != "invalid colorization rules for totp, none found" {
+ t.Errorf("invalid error: %v", err)
+ }
+ if _, err := util.ParseTimeWindow(" 2"); err.Error() != "invalid colorization rule found: 2" {
+ t.Errorf("invalid error: %v", err)
+ }
+ if _, err := util.ParseTimeWindow(" 1:200"); err.Error() != "invalid time found for colorization rule: 1:200" {
+ t.Errorf("invalid error: %v", err)
+ }
+ if _, err := util.ParseTimeWindow(" 1:-1"); err.Error() != "invalid time found for colorization rule: 1:-1" {
+ t.Errorf("invalid error: %v", err)
+ }
+ if _, err := util.ParseTimeWindow(" 200:1"); err.Error() != "invalid time found for colorization rule: 200:1" {
+ t.Errorf("invalid error: %v", err)
+ }
+ if _, err := util.ParseTimeWindow(" -1:1"); err.Error() != "invalid time found for colorization rule: -1:1" {
+ t.Errorf("invalid error: %v", err)
+ }
+ if _, err := util.ParseTimeWindow(" 2:1"); err.Error() != "invalid time found for colorization rule: 2:1" {
+ t.Errorf("invalid error: %v", err)
+ }
+ if _, err := util.ParseTimeWindow("xxx:1"); err.Error() != "strconv.Atoi: parsing \"xxx\": invalid syntax" {
+ t.Errorf("invalid error: %v", err)
+ }
+ if _, err := util.ParseTimeWindow(" 1:xxx"); err.Error() != "strconv.Atoi: parsing \"xxx\": invalid syntax" {
+ t.Errorf("invalid error: %v", err)
+ }
+ if _, err := util.ParseTimeWindow("1:2 11:22"); err != nil {
+ t.Errorf("invalid error: %v", err)
+ }
+}