Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 078230217d | |||
| 90318f861b | |||
| 3bb1362c0e | |||
| 30ffbbdbc5 | |||
| b893e99864 | |||
| c7c51568d8 | |||
| 7793021260 | |||
| 692562818c | |||
| 9e19346fc0 | |||
| cb827169dc | |||
| 027d0173bc | |||
| 6f19b69bbd | |||
| 7e118bfdb0 | |||
|
|
e0868841bf | ||
|
|
c558405d11 | ||
|
|
a1eb035af7 | ||
| 5eedcff042 | |||
|
|
6ac8eb04b4 | ||
|
|
4a4e4cd3fd | ||
|
|
1207093a56 | ||
| 2b6ae03d1a |
@@ -1,8 +1,17 @@
|
||||
arch:
|
||||
- amd64
|
||||
- ppc64le
|
||||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- tip
|
||||
- 1.9
|
||||
jobs:
|
||||
exclude:
|
||||
- go: 1.9
|
||||
arch: amd64
|
||||
- go: 1.9
|
||||
arch: ppc64le
|
||||
script:
|
||||
- go get golang.org/x/lint/golint
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
|
||||
@@ -3,12 +3,6 @@ GOUTILS
|
||||
This is a collection of small utility code I've written in Go; the `cmd/`
|
||||
directory has a number of command-line utilities. Rather than keep all
|
||||
of these in superfluous repositories of their own, I'm putting them here.
|
||||
Note that for packaging purposes, the goutils-pkg repo should be used: it
|
||||
pins the library versions to working copies and vendors all depdencies. See
|
||||
https://github.com/kisom/goutils-pkg for more details.
|
||||
|
||||
The goutils-pkg repo [1] has stable versions of the command line
|
||||
utilities here, along with a vendored snapshot of any dependencies.
|
||||
|
||||
Contents:
|
||||
|
||||
@@ -65,5 +59,3 @@ Each program should have a small README in the directory with more
|
||||
information.
|
||||
|
||||
All code here is licensed under the MIT license.
|
||||
|
||||
[1] https://github.com/kisom/goutils-pkg/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Package ahash provides support for hashing data with a selectable
|
||||
// hash function.
|
||||
//
|
||||
// hash function.
|
||||
package ahash
|
||||
|
||||
import (
|
||||
@@ -16,7 +17,7 @@ import (
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"github.com/kisom/goutils/assert"
|
||||
"git.sr.ht/~kisom/goutils/assert"
|
||||
"golang.org/x/crypto/blake2b"
|
||||
"golang.org/x/crypto/blake2s"
|
||||
"golang.org/x/crypto/md4"
|
||||
@@ -213,6 +214,17 @@ func SumReader(algo string, r io.Reader) ([]byte, error) {
|
||||
return h.Sum(nil), nil
|
||||
}
|
||||
|
||||
// SumLimitedReader reads n bytes of data from the io.reader and returns the
|
||||
// digest (not the hex digest) from the specified algorithm.
|
||||
func SumLimitedReader(algo string, r io.Reader, n int64) ([]byte, error) {
|
||||
limit := &io.LimitedReader{
|
||||
R: r,
|
||||
N: n,
|
||||
}
|
||||
|
||||
return SumReader(algo, limit)
|
||||
}
|
||||
|
||||
var insecureHashList, secureHashList, hashList []string
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/kisom/goutils/assert"
|
||||
"git.sr.ht/~kisom/goutils/assert"
|
||||
)
|
||||
|
||||
func TestSecureHash(t *testing.T) {
|
||||
@@ -139,3 +139,19 @@ func TestListLengthSanity(t *testing.T) {
|
||||
|
||||
assert.BoolT(t, len(all) == len(secure)+len(insecure))
|
||||
}
|
||||
|
||||
func TestSumLimitedReader(t *testing.T) {
|
||||
data := bytes.NewBufferString("hello, world")
|
||||
dataLen := data.Len()
|
||||
extendedData := bytes.NewBufferString("hello, world! this is an extended message")
|
||||
expected := "09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b"
|
||||
|
||||
hash, err := SumReader("sha256", data)
|
||||
assert.NoErrorT(t, err)
|
||||
assert.BoolT(t, fmt.Sprintf("%x", hash) == expected, fmt.Sprintf("have hash %x, want %s", hash, expected))
|
||||
|
||||
extendedHash, err := SumLimitedReader("sha256", extendedData, int64(dataLen))
|
||||
assert.NoErrorT(t, err)
|
||||
|
||||
assert.BoolT(t, bytes.Equal(hash, extendedHash), fmt.Sprintf("have hash %x, want %x", extendedHash, hash))
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/kisom/goutils/die"
|
||||
"git.sr.ht/~kisom/goutils/die"
|
||||
)
|
||||
|
||||
var hasPort = regexp.MustCompile(`:\d+$`)
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cfssl/helpers"
|
||||
"github.com/kisom/goutils/die"
|
||||
"github.com/kisom/goutils/lib"
|
||||
"git.sr.ht/~kisom/goutils/die"
|
||||
"git.sr.ht/~kisom/goutils/lib"
|
||||
)
|
||||
|
||||
var warnOnly bool
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
|
||||
"github.com/cloudflare/cfssl/helpers"
|
||||
"github.com/cloudflare/cfssl/revoke"
|
||||
"github.com/kisom/goutils/die"
|
||||
"github.com/kisom/goutils/lib"
|
||||
"git.sr.ht/~kisom/goutils/die"
|
||||
"git.sr.ht/~kisom/goutils/lib"
|
||||
)
|
||||
|
||||
func printRevocation(cert *x509.Certificate) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/kisom/goutils/lib"
|
||||
"git.sr.ht/~kisom/goutils/lib"
|
||||
"github.com/pkg/sftp"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/kisom/goutils/die"
|
||||
"git.sr.ht/~kisom/goutils/die"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"github.com/kisom/goutils/die"
|
||||
"git.sr.ht/~kisom/goutils/die"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
52
cmd/eig/main.go
Normal file
52
cmd/eig/main.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
"git.sr.ht/~kisom/goutils/die"
|
||||
)
|
||||
|
||||
// size of a kilobit in bytes
|
||||
const kilobit = 128
|
||||
const pageSize = 4096
|
||||
|
||||
func main() {
|
||||
size := flag.Int("s", 256*kilobit, "size of EEPROM image in kilobits")
|
||||
fill := flag.Uint("f", 0, "byte to fill image with")
|
||||
flag.Parse()
|
||||
|
||||
if *fill > 256 {
|
||||
die.With("`fill` argument must be a byte value")
|
||||
}
|
||||
|
||||
path := "eeprom.img"
|
||||
|
||||
if flag.NArg() > 0 {
|
||||
path = flag.Arg(0)
|
||||
}
|
||||
|
||||
fillByte := uint8(*fill)
|
||||
|
||||
buf := make([]byte, pageSize)
|
||||
for i := 0; i < pageSize; i++ {
|
||||
buf[i] = fillByte
|
||||
}
|
||||
|
||||
pages := *size / pageSize
|
||||
last := *size % pageSize
|
||||
|
||||
file, err := os.Create(path)
|
||||
die.If(err)
|
||||
defer file.Close()
|
||||
|
||||
for i := 0; i < pages; i++ {
|
||||
_, err = file.Write(buf)
|
||||
die.If(err)
|
||||
}
|
||||
|
||||
if last != 0 {
|
||||
_, err = file.Write(buf[:last])
|
||||
die.If(err)
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/kisom/goutils/die"
|
||||
"git.sr.ht/~kisom/goutils/die"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/kisom/goutils/lib"
|
||||
"git.sr.ht/~kisom/goutils/lib"
|
||||
)
|
||||
|
||||
func prettify(file string, validateOnly bool) error {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/kisom/goutils/die"
|
||||
"git.sr.ht/~kisom/goutils/die"
|
||||
)
|
||||
|
||||
const dbVersion = "1"
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/kisom/goutils/lib"
|
||||
"git.sr.ht/~kisom/goutils/lib"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/kisom/goutils/assert"
|
||||
"github.com/kisom/goutils/die"
|
||||
"github.com/kisom/goutils/lib"
|
||||
"git.sr.ht/~kisom/goutils/assert"
|
||||
"git.sr.ht/~kisom/goutils/die"
|
||||
"git.sr.ht/~kisom/goutils/lib"
|
||||
)
|
||||
|
||||
func usage(w io.Writer) {
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/kisom/goutils/fileutil"
|
||||
"github.com/kisom/goutils/lib"
|
||||
"git.sr.ht/~kisom/goutils/fileutil"
|
||||
"git.sr.ht/~kisom/goutils/lib"
|
||||
)
|
||||
|
||||
func hashName(path, encodedHash string) string {
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/kisom/goutils/ahash"
|
||||
"github.com/kisom/goutils/die"
|
||||
"github.com/kisom/goutils/lib"
|
||||
"git.sr.ht/~kisom/goutils/ahash"
|
||||
"git.sr.ht/~kisom/goutils/die"
|
||||
"git.sr.ht/~kisom/goutils/lib"
|
||||
)
|
||||
|
||||
func usage(w io.Writer) {
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/kisom/goutils/die"
|
||||
"github.com/kisom/goutils/logging"
|
||||
"git.sr.ht/~kisom/goutils/die"
|
||||
"git.sr.ht/~kisom/goutils/logging"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/kisom/goutils/die"
|
||||
"github.com/kisom/goutils/lib"
|
||||
"git.sr.ht/~kisom/goutils/die"
|
||||
"git.sr.ht/~kisom/goutils/lib"
|
||||
)
|
||||
|
||||
func usage(w io.Writer) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/kisom/goutils/die"
|
||||
"git.sr.ht/~kisom/goutils/die"
|
||||
)
|
||||
|
||||
func proxy(conn net.Conn, inside string) error {
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/kisom/goutils/die"
|
||||
"git.sr.ht/~kisom/goutils/die"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/kisom/goutils/die"
|
||||
"git.sr.ht/~kisom/goutils/die"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/kisom/goutils/die"
|
||||
"github.com/kisom/goutils/lib"
|
||||
"git.sr.ht/~kisom/goutils/die"
|
||||
"git.sr.ht/~kisom/goutils/lib"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/kisom/goutils/die"
|
||||
"git.sr.ht/~kisom/goutils/die"
|
||||
)
|
||||
|
||||
var validPEMs = map[string]bool{
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
// Package config implements a simple global configuration system that
|
||||
// supports a file with key=value pairs and environment variables. Note
|
||||
// that the config system is global.
|
||||
//
|
||||
// This package is intended to be used for small daemons: some configuration
|
||||
// file is optionally populated at program start, then this is used to
|
||||
// transparently look up configuration values from either that file or the
|
||||
// environment.
|
||||
package config
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"git.sr.ht/~kisom/goutils/config/iniconf"
|
||||
)
|
||||
|
||||
// NB: Rather than define a singleton type, everything is defined at
|
||||
@@ -62,6 +70,34 @@ func LoadFile(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadFileFor scans the ini file at path, loading the default section
|
||||
// and overriding any keys found under section. If strict is true, the
|
||||
// named section must exist (i.e. to catch typos in the section name).
|
||||
func LoadFileFor(path, section string, strict bool) error {
|
||||
cmap, err := iniconf.ParseFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for key, value := range cmap[iniconf.DefaultSection] {
|
||||
vars[key] = value
|
||||
}
|
||||
|
||||
smap, ok := cmap[section]
|
||||
if !ok {
|
||||
if strict {
|
||||
return fmt.Errorf("config: section '%s' wasn't found in the config file", section)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for key, value := range smap {
|
||||
vars[key] = value
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get retrieves a value from either a configuration file or the
|
||||
// environment. Note that values from a file will override environment
|
||||
// variables.
|
||||
@@ -82,3 +118,24 @@ func GetDefault(key, def string) string {
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// Require retrieves a value from either a configuration file or the
|
||||
// environment. If the key isn't present, it will call log.Fatal, printing
|
||||
// the missing key.
|
||||
func Require(key string) string {
|
||||
if v, ok := vars[key]; ok {
|
||||
return v
|
||||
}
|
||||
|
||||
v, ok := os.LookupEnv(prefix + key)
|
||||
if !ok {
|
||||
var envMessage string
|
||||
if prefix != "" {
|
||||
envMessage = " (note: looked for the key " + prefix + key
|
||||
envMessage += " in the local env)"
|
||||
}
|
||||
log.Fatalf("missing required configuration value %s%s", key, envMessage)
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
223
config/iniconf/iniconf.go
Normal file
223
config/iniconf/iniconf.go
Normal file
@@ -0,0 +1,223 @@
|
||||
package iniconf
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// ConfigMap is shorthand for the type used as a config struct.
|
||||
type ConfigMap map[string]map[string]string
|
||||
|
||||
var (
|
||||
configSection = regexp.MustCompile(`^\s*\[\s*(\w+)\s*\]\s*$`)
|
||||
quotedConfigLine = regexp.MustCompile(`^\s*(\w+)\s*=\s*["'](.*)["']\s*$`)
|
||||
configLine = regexp.MustCompile(`^\s*(\w+)\s*=\s*(.*)\s*$`)
|
||||
commentLine = regexp.MustCompile(`^#.*$`)
|
||||
blankLine = regexp.MustCompile(`^\s*$`)
|
||||
)
|
||||
|
||||
// DefaultSection is the label for the default ini file section.
|
||||
var DefaultSection = "default"
|
||||
|
||||
// ParseFile attempts to load the named config file.
|
||||
func ParseFile(fileName string) (cfg ConfigMap, err error) {
|
||||
var file *os.File
|
||||
file, err = os.Open(fileName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
return ParseReader(file)
|
||||
}
|
||||
|
||||
// ParseReader reads a configuration from an io.Reader.
|
||||
func ParseReader(r io.Reader) (cfg ConfigMap, err error) {
|
||||
cfg = ConfigMap{}
|
||||
buf := bufio.NewReader(r)
|
||||
|
||||
var (
|
||||
line string
|
||||
longLine bool
|
||||
currentSection string
|
||||
lineBytes []byte
|
||||
isPrefix bool
|
||||
)
|
||||
|
||||
for {
|
||||
err = nil
|
||||
lineBytes, isPrefix, err = buf.ReadLine()
|
||||
if io.EOF == err {
|
||||
err = nil
|
||||
break
|
||||
} else if err != nil {
|
||||
break
|
||||
} else if isPrefix {
|
||||
line += string(lineBytes)
|
||||
|
||||
longLine = true
|
||||
continue
|
||||
} else if longLine {
|
||||
line += string(lineBytes)
|
||||
longLine = false
|
||||
} else {
|
||||
line = string(lineBytes)
|
||||
}
|
||||
|
||||
if commentLine.MatchString(line) {
|
||||
continue
|
||||
} else if blankLine.MatchString(line) {
|
||||
continue
|
||||
} else if configSection.MatchString(line) {
|
||||
section := configSection.ReplaceAllString(line,
|
||||
"$1")
|
||||
if section == "" {
|
||||
err = fmt.Errorf("invalid structure in file")
|
||||
break
|
||||
} else if !cfg.SectionInConfig(section) {
|
||||
cfg[section] = make(map[string]string, 0)
|
||||
}
|
||||
currentSection = section
|
||||
} else if configLine.MatchString(line) {
|
||||
regex := configLine
|
||||
if quotedConfigLine.MatchString(line) {
|
||||
regex = quotedConfigLine
|
||||
}
|
||||
if currentSection == "" {
|
||||
currentSection = DefaultSection
|
||||
if !cfg.SectionInConfig(currentSection) {
|
||||
cfg[currentSection] = map[string]string{}
|
||||
}
|
||||
}
|
||||
key := regex.ReplaceAllString(line, "$1")
|
||||
val := regex.ReplaceAllString(line, "$2")
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
cfg[currentSection][key] = val
|
||||
} else {
|
||||
err = fmt.Errorf("invalid config file")
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SectionInConfig determines whether a section is in the configuration.
|
||||
func (c ConfigMap) SectionInConfig(section string) bool {
|
||||
_, ok := c[section]
|
||||
return ok
|
||||
}
|
||||
|
||||
// ListSections returns the list of sections in the config map.
|
||||
func (c ConfigMap) ListSections() (sections []string) {
|
||||
for section := range c {
|
||||
sections = append(sections, section)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// WriteFile writes out the configuration to a file.
|
||||
func (c ConfigMap) WriteFile(filename string) (err error) {
|
||||
file, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
for _, section := range c.ListSections() {
|
||||
sName := fmt.Sprintf("[ %s ]\n", section)
|
||||
_, err = file.Write([]byte(sName))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range c[section] {
|
||||
line := fmt.Sprintf("%s = %s\n", k, v)
|
||||
_, err = file.Write([]byte(line))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
_, err = file.Write([]byte{0x0a})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AddSection creates a new section in the config map.
|
||||
func (c ConfigMap) AddSection(section string) {
|
||||
if nil != c[section] {
|
||||
c[section] = map[string]string{}
|
||||
}
|
||||
}
|
||||
|
||||
// AddKeyVal adds a key value pair to a config map.
|
||||
func (c ConfigMap) AddKeyVal(section, key, val string) {
|
||||
if section == "" {
|
||||
section = DefaultSection
|
||||
}
|
||||
|
||||
if nil == c[section] {
|
||||
c.AddSection(section)
|
||||
}
|
||||
|
||||
c[section][key] = val
|
||||
}
|
||||
|
||||
// GetValue retrieves the value from a key map.
|
||||
func (c ConfigMap) GetValue(section, key string) (val string, present bool) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if section == "" {
|
||||
section = DefaultSection
|
||||
}
|
||||
|
||||
_, ok := c[section]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
val, present = c[section][key]
|
||||
return
|
||||
}
|
||||
|
||||
// GetValueDefault retrieves the value from a key map if present,
|
||||
// otherwise the default value.
|
||||
func (c ConfigMap) GetValueDefault(section, key, value string) (val string) {
|
||||
kval, ok := c.GetValue(section, key)
|
||||
if !ok {
|
||||
return value
|
||||
}
|
||||
return kval
|
||||
}
|
||||
|
||||
// SectionKeys returns the sections in the config map.
|
||||
func (c ConfigMap) SectionKeys(section string) (keys []string, present bool) {
|
||||
if c == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if section == "" {
|
||||
section = DefaultSection
|
||||
}
|
||||
|
||||
cm := c
|
||||
s, ok := cm[section]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
keys = make([]string, 0, len(s))
|
||||
for key := range s {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
return keys, true
|
||||
}
|
||||
142
config/iniconf/iniconf_test.go
Normal file
142
config/iniconf/iniconf_test.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package iniconf
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// FailWithError is a utility for dumping errors and failing the test.
|
||||
func FailWithError(t *testing.T, err error) {
|
||||
fmt.Println("failed")
|
||||
if err != nil {
|
||||
fmt.Println("[!] ", err.Error())
|
||||
}
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// UnlinkIfExists removes a file if it exists.
|
||||
func UnlinkIfExists(file string) {
|
||||
_, err := os.Stat(file)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
panic("failed to remove " + file)
|
||||
}
|
||||
os.Remove(file)
|
||||
}
|
||||
|
||||
// stringSlicesEqual compares two string lists, checking that they
|
||||
// contain the same elements.
|
||||
func stringSlicesEqual(slice1, slice2 []string) bool {
|
||||
if len(slice1) != len(slice2) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range slice1 {
|
||||
if slice1[i] != slice2[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for i := range slice2 {
|
||||
if slice1[i] != slice2[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TestGoodConfig(t *testing.T) {
|
||||
testFile := "testdata/test.conf"
|
||||
fmt.Printf("[+] validating known-good config... ")
|
||||
cmap, err := ParseFile(testFile)
|
||||
if err != nil {
|
||||
FailWithError(t, err)
|
||||
} else if len(cmap) != 2 {
|
||||
FailWithError(t, err)
|
||||
}
|
||||
fmt.Println("ok")
|
||||
}
|
||||
|
||||
func TestGoodConfig2(t *testing.T) {
|
||||
testFile := "testdata/test2.conf"
|
||||
fmt.Printf("[+] validating second known-good config... ")
|
||||
cmap, err := ParseFile(testFile)
|
||||
if err != nil {
|
||||
FailWithError(t, err)
|
||||
} else if len(cmap) != 1 {
|
||||
FailWithError(t, err)
|
||||
} else if len(cmap["default"]) != 3 {
|
||||
FailWithError(t, err)
|
||||
}
|
||||
fmt.Println("ok")
|
||||
}
|
||||
|
||||
func TestBadConfig(t *testing.T) {
|
||||
testFile := "testdata/bad.conf"
|
||||
fmt.Printf("[+] ensure invalid config file fails... ")
|
||||
_, err := ParseFile(testFile)
|
||||
if err == nil {
|
||||
err = fmt.Errorf("invalid config file should fail")
|
||||
FailWithError(t, err)
|
||||
}
|
||||
fmt.Println("ok")
|
||||
}
|
||||
|
||||
func TestWriteConfigFile(t *testing.T) {
|
||||
fmt.Printf("[+] ensure config file is written properly... ")
|
||||
const testFile = "testdata/test.conf"
|
||||
const testOut = "testdata/test.out"
|
||||
|
||||
cmap, err := ParseFile(testFile)
|
||||
if err != nil {
|
||||
FailWithError(t, err)
|
||||
}
|
||||
|
||||
defer UnlinkIfExists(testOut)
|
||||
err = cmap.WriteFile(testOut)
|
||||
if err != nil {
|
||||
FailWithError(t, err)
|
||||
}
|
||||
|
||||
cmap2, err := ParseFile(testOut)
|
||||
if err != nil {
|
||||
FailWithError(t, err)
|
||||
}
|
||||
|
||||
sectionList1 := cmap.ListSections()
|
||||
sectionList2 := cmap2.ListSections()
|
||||
sort.Strings(sectionList1)
|
||||
sort.Strings(sectionList2)
|
||||
if !stringSlicesEqual(sectionList1, sectionList2) {
|
||||
err = fmt.Errorf("section lists don't match")
|
||||
FailWithError(t, err)
|
||||
}
|
||||
|
||||
for _, section := range sectionList1 {
|
||||
for _, k := range cmap[section] {
|
||||
if cmap[section][k] != cmap2[section][k] {
|
||||
err = fmt.Errorf("config key doesn't match")
|
||||
FailWithError(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println("ok")
|
||||
}
|
||||
|
||||
func TestQuotedValue(t *testing.T) {
|
||||
testFile := "testdata/test.conf"
|
||||
fmt.Printf("[+] validating quoted value... ")
|
||||
cmap, _ := ParseFile(testFile)
|
||||
val := cmap["sectionName"]["key4"]
|
||||
if val != " space at beginning and end " {
|
||||
FailWithError(t, errors.New("Wrong value in double quotes ["+val+"]"))
|
||||
}
|
||||
|
||||
val = cmap["sectionName"]["key5"]
|
||||
if val != " is quoted with single quotes " {
|
||||
FailWithError(t, errors.New("Wrong value in single quotes ["+val+"]"))
|
||||
}
|
||||
fmt.Println("ok")
|
||||
}
|
||||
5
config/iniconf/testdata/bad.conf
vendored
Normal file
5
config/iniconf/testdata/bad.conf
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
[]
|
||||
|
||||
key
|
||||
another key
|
||||
key = val
|
||||
13
config/iniconf/testdata/test.conf
vendored
Normal file
13
config/iniconf/testdata/test.conf
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
[ sectionName ]
|
||||
key1=some value
|
||||
key2 = some other value
|
||||
# we want to explain the importance and great forethought
|
||||
# in this next value.
|
||||
key3 = unintuitive value
|
||||
key4 = " space at beginning and end "
|
||||
key5 = ' is quoted with single quotes '
|
||||
|
||||
[ anotherSection ]
|
||||
key1 = a value
|
||||
key2 = yet another value
|
||||
key1 = overwrites previous value of a value
|
||||
3
config/iniconf/testdata/test2.conf
vendored
Normal file
3
config/iniconf/testdata/test2.conf
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
key1 = some value
|
||||
key2 = some other value
|
||||
key3 = unintuitive value
|
||||
19
config/path.go
Normal file
19
config/path.go
Normal file
@@ -0,0 +1,19 @@
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// DefaultConfigPath returns a sensible default configuration file path.
|
||||
func DefaultConfigPath(dir, base string) string {
|
||||
user, err := user.Current()
|
||||
if err != nil || user.HomeDir == "" {
|
||||
return filepath.Join(dir, base)
|
||||
}
|
||||
|
||||
return filepath.Join(user.HomeDir, dir, base)
|
||||
}
|
||||
43
config/path_linux.go
Normal file
43
config/path_linux.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// canUseXDGConfigDir checks whether the XDG config directory exists
|
||||
// and is accessible by the current user. If it is present, it will
|
||||
// be returned. Note that if the directory does not exist, it is
|
||||
// presumed unusable.
|
||||
func canUseXDGConfigDir() (string, bool) {
|
||||
xdgDir := os.Getenv("XDG_CONFIG_DIR")
|
||||
if xdgDir == "" {
|
||||
userDir := os.Getenv("HOME")
|
||||
if userDir == "" {
|
||||
return "", false
|
||||
}
|
||||
|
||||
xdgDir = filepath.Join(userDir, ".config")
|
||||
}
|
||||
|
||||
fi, err := os.Stat(xdgDir)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
if !fi.IsDir() {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return xdgDir, true
|
||||
}
|
||||
|
||||
// DefaultConfigPath returns a sensible default configuration file path.
|
||||
func DefaultConfigPath(dir, base string) string {
|
||||
dirPath, ok := canUseXDGConfigDir()
|
||||
if !ok {
|
||||
dirPath = "/etc"
|
||||
}
|
||||
|
||||
return filepath.Join(dirPath, dir, base)
|
||||
}
|
||||
7
config/path_test.go
Normal file
7
config/path_test.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package config
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDefaultPath(t *testing.T) {
|
||||
t.Log(DefaultConfigPath("demoapp", "app.conf"))
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/kisom/goutils/testio"
|
||||
"git.sr.ht/~kisom/goutils/testio"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
||||
14
go.mod
14
go.mod
@@ -1,3 +1,15 @@
|
||||
module github.com/kisom/goutils
|
||||
module git.sr.ht/~kisom/goutils
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/cloudflare/cfssl v1.5.0
|
||||
github.com/kisom/goutils v1.1.0
|
||||
github.com/kr/text v0.2.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pkg/sftp v1.12.0
|
||||
github.com/stretchr/testify v1.6.1
|
||||
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392
|
||||
golang.org/x/sys v0.0.0-20201126233918-771906719818
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
105
go.sum
Normal file
105
go.sum
Normal file
@@ -0,0 +1,105 @@
|
||||
bitbucket.org/liamstask/goose v0.0.0-20150115234039-8488cc47d90c/go.mod h1:hSVuE3qU7grINVSwrmzHfpg9k87ALBk+XaualNyUzI4=
|
||||
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
||||
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
|
||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
|
||||
github.com/cloudflare/backoff v0.0.0-20161212185259-647f3cdfc87a/go.mod h1:rzgs2ZOiguV6/NpiDgADjRLPNyZlApIWxKpkT+X8SdY=
|
||||
github.com/cloudflare/cfssl v1.5.0 h1:vFJDAvQgFSRbCn9zg8KpSrrEZrBAQ4KO5oNK7SXEyb0=
|
||||
github.com/cloudflare/cfssl v1.5.0/go.mod h1:sPPkBS5L8l8sRc/IOO1jG51Xb34u+TYhL6P//JdODMQ=
|
||||
github.com/cloudflare/go-metrics v0.0.0-20151117154305-6a9aea36fb41/go.mod h1:eaZPlJWD+G9wseg1BuRXlHnjntPMrywMsyxf+LTOdP4=
|
||||
github.com/cloudflare/redoctober v0.0.0-20171127175943-746a508df14c/go.mod h1:6Se34jNoqrd8bTxrmJB2Bg2aoZ2CdSXonils9NsiNgo=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
|
||||
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/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE=
|
||||
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVzF6no3QaDSMLGLEHtHSBSefs+MgcDWnmhmo=
|
||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||
github.com/kisielk/sqlstruct v0.0.0-20150923205031-648daed35d49/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
|
||||
github.com/kisom/goutils v1.1.0 h1:z4HEOgAnFq+e1+O4QdVsyDPatJDu5Ei/7w7DRbYjsIA=
|
||||
github.com/kisom/goutils v1.1.0/go.mod h1:+UBTfd78habUYWFbNWTJNG+jNG/i/lGURakr4A/yNRw=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/go-gypsy v0.0.0-20160905020020-08cad365cd28/go.mod h1:T/T7jsxVqf9k/zYOqbgNAsANsjxTd1Yq3htjDhQ1H0c=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
|
||||
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.12.0 h1:/f3b24xrDhkhddlaobPe2JgBqfdt+gC/NYl0QY9IOuI=
|
||||
github.com/pkg/sftp v1.12.0/go.mod h1:fUqqXB5vEgVCZ131L+9say31RAri6aF6KDViawhxKK8=
|
||||
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/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
|
||||
github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
|
||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||
github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
|
||||
github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=
|
||||
github.com/zmap/zcrypto v0.0.0-20200513165325-16679db567ff/go.mod h1:TxpejqcVKQjQaVVmMGfzx5HnmFMdIU+vLtaCyPBfGI4=
|
||||
github.com/zmap/zcrypto v0.0.0-20200911161511-43ff0ea04f21/go.mod h1:TxpejqcVKQjQaVVmMGfzx5HnmFMdIU+vLtaCyPBfGI4=
|
||||
github.com/zmap/zlint/v2 v2.2.1/go.mod h1:ixPWsdq8qLxYRpNUTbcKig3R7WgmspsHGLhCCs6rFAM=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200124225646-8b5121be2f68/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 h1:xYJJ3S178yv++9zXV/hnr29plCAGO9vAFG9dorqaFQc=
|
||||
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201126233918-771906719818 h1:f1CIuDlJhwANEC2MM87MBEVMr3jl5bifgsfj90XAF9c=
|
||||
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/kisom/goutils/assert"
|
||||
"git.sr.ht/~kisom/goutils/assert"
|
||||
)
|
||||
|
||||
// some CA certs I found on my computerbox.
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/kisom/goutils/logging"
|
||||
"git.sr.ht/~kisom/goutils/logging"
|
||||
)
|
||||
|
||||
var log = logging.NewConsole()
|
||||
|
||||
@@ -3,7 +3,7 @@ package logging_test
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kisom/goutils/logging"
|
||||
"git.sr.ht/~kisom/goutils/logging"
|
||||
)
|
||||
|
||||
var log = logging.NewConsole()
|
||||
|
||||
@@ -59,7 +59,7 @@ func NewSplitFile(outpath, errpath string, overwrite bool) (*File, error) {
|
||||
if overwrite {
|
||||
fl.fo, err = os.Create(outpath)
|
||||
} else {
|
||||
fl.fo, err = os.OpenFile(outpath, os.O_WRONLY|os.O_APPEND, 0644)
|
||||
fl.fo, err = os.OpenFile(outpath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -69,7 +69,7 @@ func NewSplitFile(outpath, errpath string, overwrite bool) (*File, error) {
|
||||
if overwrite {
|
||||
fl.fe, err = os.Create(errpath)
|
||||
} else {
|
||||
fl.fe, err = os.OpenFile(errpath, os.O_WRONLY|os.O_APPEND, 0644)
|
||||
fl.fe, err = os.OpenFile(errpath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -277,3 +277,93 @@ func (lw *LogWriter) SetLevel(l Level) {
|
||||
|
||||
// Close is a no-op that satisfies the Logger interface.
|
||||
func (lw *LogWriter) Close() error { return nil }
|
||||
|
||||
// Multi allows combining of loggers.
|
||||
type Multi struct {
|
||||
loggers []Logger
|
||||
}
|
||||
|
||||
func NewMulti(loggers ...Logger) *Multi {
|
||||
return &Multi{loggers: loggers}
|
||||
}
|
||||
|
||||
func (m *Multi) SetLevel(level Level) {
|
||||
for _, l := range m.loggers {
|
||||
l.SetLevel(level)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Multi) Good() bool {
|
||||
good := true
|
||||
for _, l := range m.loggers {
|
||||
good = good && l.Good()
|
||||
}
|
||||
|
||||
return good
|
||||
}
|
||||
|
||||
func (m *Multi) Status() error {
|
||||
for _, l := range m.loggers {
|
||||
if err := l.Status(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Multi) Close() error {
|
||||
for _, l := range m.loggers {
|
||||
l.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Multi) Debug(actor, event string, attrs map[string]string) {
|
||||
for _, l := range m.loggers {
|
||||
l.Debug(actor, event, attrs)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Multi) Info(actor, event string, attrs map[string]string) {
|
||||
for _, l := range m.loggers {
|
||||
l.Info(actor, event, attrs)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Multi) Warn(actor, event string, attrs map[string]string) {
|
||||
for _, l := range m.loggers {
|
||||
l.Warn(actor, event, attrs)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Multi) Error(actor, event string, attrs map[string]string) {
|
||||
for _, l := range m.loggers {
|
||||
l.Error(actor, event, attrs)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Multi) Critical(actor, event string, attrs map[string]string) {
|
||||
for _, l := range m.loggers {
|
||||
l.Critical(actor, event, attrs)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Multi) Fatal(actor, event string, attrs map[string]string) {
|
||||
for _, l := range m.loggers {
|
||||
l.Fatal(actor, event, attrs)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Multi) FatalCode(exitcode int, actor, event string, attrs map[string]string) {
|
||||
for _, l := range m.loggers {
|
||||
l.FatalCode(exitcode, actor, event, attrs)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Multi) FatalNoDie(actor, event string, attrs map[string]string) {
|
||||
for _, l := range m.loggers {
|
||||
l.FatalNoDie(actor, event, attrs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,3 +53,12 @@ func TestDestroyLogFiles(t *testing.T) {
|
||||
os.Remove("fw2.log")
|
||||
os.Remove("fw2.err")
|
||||
}
|
||||
|
||||
func TestMulti(t *testing.T) {
|
||||
c1 := NewConsole()
|
||||
c2 := NewConsole()
|
||||
m := NewMulti(c1, c2)
|
||||
if !m.Good() {
|
||||
t.Fatal("failed to set up multi logger")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/kisom/goutils/assert"
|
||||
"github.com/kisom/goutils/testio"
|
||||
"git.sr.ht/~kisom/goutils/assert"
|
||||
"git.sr.ht/~kisom/goutils/testio"
|
||||
)
|
||||
|
||||
func TestMWC(t *testing.T) {
|
||||
|
||||
49
rand/rand.go
Normal file
49
rand/rand.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Package rand contains utilities for interacting with math/rand, including
|
||||
// seeding from a random sed.
|
||||
package rand
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
mrand "math/rand"
|
||||
)
|
||||
|
||||
// CryptoUint64 generates a cryptographically-secure 64-bit integer.
|
||||
func CryptoUint64() (uint64, error) {
|
||||
bs := make([]byte, 8)
|
||||
_, err := rand.Read(bs)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return binary.BigEndian.Uint64(bs), nil
|
||||
}
|
||||
|
||||
// Seed initialises the non-cryptographic PRNG with a random,
|
||||
// cryptographically secure value. This is done just as a good
|
||||
// way to make this random. The returned 64-bit value is the seed.
|
||||
func Seed() (uint64, error) {
|
||||
seed, err := CryptoUint64()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// NB: this is permitted.
|
||||
mrand.Seed(int64(seed))
|
||||
return seed, nil
|
||||
}
|
||||
|
||||
// Int is a wrapper for math.Int so only one package needs to be imported.
|
||||
func Int() int {
|
||||
return mrand.Int()
|
||||
}
|
||||
|
||||
// Intn is a wrapper for math.Intn so only one package needs to be imported.
|
||||
func Intn(max int) int {
|
||||
return mrand.Intn(max)
|
||||
}
|
||||
|
||||
// Intn2 returns a random value between min and max, inclusive.
|
||||
func Intn2(min, max int) int {
|
||||
return Intn(max-min) + min
|
||||
}
|
||||
74
rand/rand_test.go
Normal file
74
rand/rand_test.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package rand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
mrand "math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCryptoUint64(t *testing.T) {
|
||||
n1, err := CryptoUint64()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
n2, err := CryptoUint64()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// This has such a low chance of occurring that it's likely to be
|
||||
// indicative of a bad CSPRNG.
|
||||
if n1 == n2 {
|
||||
t.Fatalf("repeated random uint64s: %d", n1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntn(t *testing.T) {
|
||||
expected := []int{3081, 4887, 4847, 1059, 3081}
|
||||
mrand.Seed(1)
|
||||
for i := 0; i < 5; i++ {
|
||||
n := Intn2(1000, 5000)
|
||||
|
||||
if n != expected[i] {
|
||||
fmt.Printf("invalid sequence at %d: expected %d, have %d", i, expected[i], n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeed(t *testing.T) {
|
||||
seed1, err := Seed()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var seed2 uint64
|
||||
n1 := Int()
|
||||
tries := 0
|
||||
|
||||
for {
|
||||
seed2, err = Seed()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if seed1 != seed2 {
|
||||
break
|
||||
}
|
||||
|
||||
tries++
|
||||
|
||||
if tries > 3 {
|
||||
t.Fatal("can't generate two unique seeds")
|
||||
}
|
||||
}
|
||||
|
||||
n2 := Int()
|
||||
|
||||
// Again, this not impossible, merely statistically improbably and a
|
||||
// potential canary for RNG issues.
|
||||
if n1 == n2 {
|
||||
t.Fatalf("repeated integers fresh from two unique seeds: %d/%d -> %d",
|
||||
seed1, seed2, n1)
|
||||
}
|
||||
}
|
||||
51
seekbuf/seekbuf.go
Normal file
51
seekbuf/seekbuf.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package seekbuf
|
||||
|
||||
import "io"
|
||||
|
||||
// Buffer is a ReadWriteCloser that supports seeking. It's intended to
|
||||
// replicate the functionality of bytes.Buffer that I use in my projects.
|
||||
//
|
||||
// Note that the seeking is limited to the read marker; all writes are
|
||||
// append-only.
|
||||
type Buffer struct {
|
||||
data []byte
|
||||
pos int
|
||||
}
|
||||
|
||||
func New(data []byte) *Buffer {
|
||||
return &Buffer{
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffer) Read(p []byte) (int, error) {
|
||||
if b.pos >= len(b.data) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
n := copy(p, b.data[b.pos:])
|
||||
b.pos += n
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (b *Buffer) Write(p []byte) (int, error) {
|
||||
b.data = append(b.data, p...)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// Seek sets the read pointer to pos.
|
||||
func (b *Buffer) Seek(pos int) {
|
||||
b.pos = pos
|
||||
}
|
||||
|
||||
// Rewind resets the read pointer to 0.
|
||||
func (b *Buffer) Rewind() {
|
||||
b.pos = 0
|
||||
}
|
||||
|
||||
// Close clears all the data out of the buffer and sets the read position to 0.
|
||||
func (b *Buffer) Close() error {
|
||||
b.data = nil
|
||||
b.pos = 0
|
||||
return nil
|
||||
}
|
||||
@@ -5,9 +5,15 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
type WriteStringCloser interface {
|
||||
Write([]byte) (int, error)
|
||||
WriteString(string) (int, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Tee emulates the Unix tee(1) command.
|
||||
type Tee struct {
|
||||
f *os.File
|
||||
f WriteStringCloser
|
||||
Verbose bool
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user