Compare commits

..

31 Commits

Author SHA1 Message Date
3bb1362c0e tee: allow writing strings. 2022-02-20 17:43:11 -08:00
30ffbbdbc5 config: add test data for iniconf. 2022-02-20 17:43:11 -08:00
b893e99864 config: add default path, customized configs.
A customised config is an ini file with a [default] section and some
other name sections; a config file is loaded from the default section
with any keys in the named section being added in, overriding keys in
the host. This allows for, e.g. setting different paths based on the
host name or operating system.
2022-02-20 17:43:11 -08:00
c7c51568d8 seekbuf lost the Clear function. 2022-02-05 15:40:13 -08:00
7793021260 New package: seekbuf (a seekable buffer). 2022-02-05 15:00:39 -08:00
692562818c clean README 2020-11-26 20:32:46 -08:00
9e19346fc0 add sum file 2020-11-26 20:09:46 -08:00
cb827169dc switching hosting providers 2020-11-26 20:09:37 -08:00
027d0173bc logging: finish multi implementation 2020-11-11 21:15:02 -08:00
6f19b69bbd logging: add CREATE flag to file-based loggers. 2020-11-11 10:06:13 -08:00
7e118bfdb0 logging: add Multi 2020-11-11 09:52:07 -08:00
Kyle Isom
e0868841bf Merge pull request #7 from santosh653/master 2020-11-04 07:24:04 -08:00
santosh653
c558405d11 Update .travis.yml
Excluding go version 1.9 as only go version1.13 onwards are supported.
2020-11-04 05:22:10 -05:00
santosh653
a1eb035af7 Update .travis.yml
Adding Power support
2020-10-20 04:56:21 -04:00
5eedcff042 add rand package, utilities for math/rand. 2020-06-02 17:26:17 -07:00
Kyle Isom
6ac8eb04b4 Updated by OWASP Threat Dragon 2020-03-05 15:29:57 -08:00
Kyle Isom
4a4e4cd3fd Updated by OWASP Threat Dragon 2020-03-05 12:20:49 -08:00
Kyle Isom
1207093a56 Created by OWASP Threat Dragon 2020-03-05 12:19:57 -08:00
2b6ae03d1a config: add a Require message.
Also update explanation of the intended use-case for this package.
2020-03-02 17:36:29 -08:00
ef0f14a512 Add global config package.
This is a simple config system that I find myself wanting a lot.
2020-03-02 17:11:55 -08:00
6ae393ebf2 Have to explicitly allow darwin/amd64 in the build tag. 2020-01-16 06:19:21 -08:00
76d88c220d Add go mod, update ftime.
+ Stat_t on Darwin amd64 now uses the same struct fields
  for file times as Lunix, not the BSD ones. I need to
  follow up on this.
2020-01-16 06:13:31 -08:00
Kyle Isom
40e015373f Point to goutils-pkg. 2020-01-16 06:13:18 -08:00
50c226b726 Add parts. 2019-01-08 12:46:53 -08:00
070ffb9dff Cleanup testio. 2018-12-17 18:16:52 -08:00
5ac05bd298 Add debug printer. 2018-12-17 18:12:51 -08:00
cf1edf2d31 Add simple proxy. 2018-12-12 14:57:08 -08:00
Kyle Isom
03e8958dd7 Merge pull request #6 from golint-fixer/master
Fix golint import path
2018-10-31 18:31:23 -07:00
golint fixer
6cef585071 Fix golint import path 2018-10-25 09:37:10 -05:00
06678499d4 Add missing format arg. 2018-09-15 16:49:46 -07:00
fad17065fe Update README. 2018-09-15 15:26:42 -07:00
56 changed files with 1517 additions and 43 deletions

View File

@@ -1,10 +1,19 @@
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 github.com/golang/lint/golint
- go get golang.org/x/lint/golint
- go get golang.org/x/tools/cmd/cover
- go get github.com/kisom/goutils/...
- go test -cover github.com/kisom/goutils/...

View File

@@ -1,3 +1,7 @@
Release 1.2.1 - 2018-09-15
+ Add missing format argument to Errorf call in kgz.
Release 1.2.0 - 2018-09-15
+ Adds the kgz command line utility.

View File

@@ -16,7 +16,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"

View File

@@ -5,7 +5,7 @@ import (
"fmt"
"testing"
"github.com/kisom/goutils/assert"
"git.sr.ht/~kisom/goutils/assert"
)
func TestSecureHash(t *testing.T) {

View File

@@ -7,7 +7,7 @@ import (
"fmt"
"regexp"
"github.com/kisom/goutils/die"
"git.sr.ht/~kisom/goutils/die"
)
var hasPort = regexp.MustCompile(`:\d+$`)

View File

@@ -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

View File

@@ -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) {

View File

@@ -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"

View File

@@ -11,7 +11,7 @@ import (
"os"
"path/filepath"
"github.com/kisom/goutils/die"
"git.sr.ht/~kisom/goutils/die"
)
var (

View File

@@ -10,7 +10,7 @@ import (
"io/ioutil"
"log"
"github.com/kisom/goutils/die"
"git.sr.ht/~kisom/goutils/die"
)
func main() {

View File

@@ -9,7 +9,7 @@ import (
"path/filepath"
"strconv"
"github.com/kisom/goutils/die"
"git.sr.ht/~kisom/goutils/die"
)
func init() {

View File

@@ -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 {

View File

@@ -113,7 +113,7 @@ func pathForUncompressing(source, dest string) (string, error) {
source = filepath.Base(source)
if !strings.HasSuffix(source, gzipExt) {
return "", errors.Errorf("%s is a not gzip-compressed file")
return "", errors.Errorf("%s is a not gzip-compressed file", source)
}
outFile := source[:len(source)-len(gzipExt)]
outFile = filepath.Join(dest, outFile)
@@ -127,7 +127,7 @@ func pathForCompressing(source, dest string) (string, error) {
source = filepath.Base(source)
if strings.HasSuffix(source, gzipExt) {
return "", errors.Errorf("%s is a gzip-compressed file")
return "", errors.Errorf("%s is a gzip-compressed file", source)
}
dest = filepath.Join(dest, source+gzipExt)

9
cmd/parts/README Normal file
View File

@@ -0,0 +1,9 @@
parts: simple parts database for electronic components
Usage: parts [id] -- query the database for a part
parts [-c class] [id] [description] -- store a part in the database
Options:
-f path Path to parts database (default is
/home/kyle/.parts.json).

142
cmd/parts/main.go Normal file
View File

@@ -0,0 +1,142 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"git.sr.ht/~kisom/goutils/die"
)
const dbVersion = "1"
var dbFile = filepath.Join(os.Getenv("HOME"), ".parts.json")
var partsDB = &database{Version: dbVersion}
type part struct {
Name string `json:"name"`
Description string `json:"description"`
Class string `json:"class,omitempty"`
}
func (p part) String() string {
return fmt.Sprintf("%s: %s", p.Name, p.Description)
}
type database struct {
Version string `json:"version"`
LastUpdate int64 `json:"json"`
Parts map[string]part `json:"parts"`
}
func help(w io.Writer) {
fmt.Fprintf(w, `Usage: parts [id] -- query the database for a part
parts [-c class] [id] [description] -- store a part in the database
Options:
-f path Path to parts database (default is
%s).
`, dbFile)
}
func loadDatabase() {
data, err := ioutil.ReadFile(dbFile)
if err != nil && os.IsNotExist(err) {
partsDB = &database{
Version: dbVersion,
Parts: map[string]part{},
}
return
}
die.If(err)
err = json.Unmarshal(data, partsDB)
die.If(err)
}
func findPart(partName string) {
partName = strings.ToLower(partName)
for name, part := range partsDB.Parts {
if strings.Contains(strings.ToLower(name), partName) {
fmt.Println(part.String())
}
}
}
func writeDB() {
data, err := json.Marshal(partsDB)
die.If(err)
err = ioutil.WriteFile(dbFile, data, 0644)
die.If(err)
}
func storePart(name, class, description string) {
p, exists := partsDB.Parts[name]
if exists {
fmt.Printf("warning: replacing part %s\n", name)
fmt.Printf("\t%s\n", p.String())
}
partsDB.Parts[name] = part{
Name: name,
Class: class,
Description: description,
}
writeDB()
}
func listParts() {
parts := make([]string, 0, len(partsDB.Parts))
for partName := range partsDB.Parts {
parts = append(parts, partName)
}
sort.Strings(parts)
for _, partName := range parts {
fmt.Println(partsDB.Parts[partName].String())
}
}
func main() {
var class string
var helpFlag bool
flag.StringVar(&class, "c", "", "device class")
flag.StringVar(&dbFile, "f", dbFile, "`path` to database")
flag.BoolVar(&helpFlag, "h", false, "Print a help message.")
flag.Parse()
if helpFlag {
help(os.Stdout)
return
}
loadDatabase()
switch flag.NArg() {
case 0:
help(os.Stdout)
return
case 1:
partName := flag.Arg(0)
if partName == "list" {
listParts()
} else {
findPart(flag.Arg(0))
}
return
default:
description := strings.Join(flag.Args()[1:], " ")
storePart(flag.Arg(0), class, description)
return
}
}

View File

@@ -7,7 +7,7 @@ import (
"io/ioutil"
"os"
"github.com/kisom/goutils/lib"
"git.sr.ht/~kisom/goutils/lib"
)
func main() {

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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 (

View File

@@ -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) {

46
cmd/sprox/main.go Normal file
View File

@@ -0,0 +1,46 @@
package main
import (
"flag"
"io"
"log"
"net"
"git.sr.ht/~kisom/goutils/die"
)
func proxy(conn net.Conn, inside string) error {
proxyConn, err := net.Dial("tcp", inside)
if err != nil {
return err
}
defer proxyConn.Close()
defer conn.Close()
go func() {
io.Copy(conn, proxyConn)
}()
_, err = io.Copy(proxyConn, conn)
return err
}
func main() {
var outside, inside string
flag.StringVar(&outside, "f", "8080", "outside port")
flag.StringVar(&inside, "p", "4000", "inside port")
flag.Parse()
l, err := net.Listen("tcp", "0.0.0.0:"+outside)
die.If(err)
for {
conn, err := l.Accept()
if err != nil {
log.Println(err)
continue
}
go proxy(conn, "127.0.0.1:"+inside)
}
}

View File

@@ -12,7 +12,7 @@ import (
"net"
"os"
"github.com/kisom/goutils/die"
"git.sr.ht/~kisom/goutils/die"
)
func main() {

View File

@@ -10,7 +10,7 @@ import (
"net"
"os"
"github.com/kisom/goutils/die"
"git.sr.ht/~kisom/goutils/die"
)
func main() {

View File

@@ -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() {

View File

@@ -15,7 +15,7 @@ import (
"log"
"os"
"github.com/kisom/goutils/die"
"git.sr.ht/~kisom/goutils/die"
)
var validPEMs = map[string]bool{

141
config/config.go Normal file
View File

@@ -0,0 +1,141 @@
// 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
// the top-level
var (
vars = map[string]string{}
prefix = ""
)
// SetEnvPrefix sets the prefix for all environment variables; it's
// assumed to not be needed for files.
func SetEnvPrefix(pfx string) {
prefix = pfx
}
func addLine(line string) {
if strings.HasPrefix(line, "#") || line == "" {
return
}
lineParts := strings.SplitN(line, "=", 2)
if len(lineParts) != 2 {
log.Print("skipping line: ", line)
return // silently ignore empty keys
}
lineParts[0] = strings.TrimSpace(lineParts[0])
lineParts[1] = strings.TrimSpace(lineParts[1])
vars[lineParts[0]] = lineParts[1]
}
// LoadFile scans the file at path for key=value pairs and adds them
// to the configuration.
func LoadFile(path string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
addLine(line)
}
if err = scanner.Err(); err != nil {
return err
}
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.
func Get(key string) string {
if v, ok := vars[key]; ok {
return v
}
return os.Getenv(prefix + key)
}
// GetDefault retrieves a value from either a configuration file or
// the environment. Note that value from a file will override
// environment variables. If a value isn't found (e.g. Get returns an
// empty string), the default value will be used.
func GetDefault(key, def string) string {
if v := Get(key); v != "" {
return v
}
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
}

66
config/config_test.go Normal file
View File

@@ -0,0 +1,66 @@
package config
import (
"os"
"testing"
)
const (
testFilePath = "testdata/test.env"
// Keys
kOrder = "ORDER"
kSpecies = "SPECIES"
kName = "COMMON_NAME"
// Env
eOrder = "corvus"
eSpecies = "corvus corax"
eName = "northern raven"
// File
fOrder = "stringiformes"
fSpecies = "strix aluco"
// Name isn't set in the file to test fall through.
)
func init() {
os.Setenv(kOrder, eOrder)
os.Setenv(kSpecies, eSpecies)
os.Setenv(kName, eName)
}
func TestLoadEnvOnly(t *testing.T) {
order := Get(kOrder)
species := Get(kSpecies)
if order != eOrder {
t.Errorf("want %s, have %s", eOrder, order)
}
if species != eSpecies {
t.Errorf("want %s, have %s", eSpecies, species)
}
}
func TestLoadFile(t *testing.T) {
err := LoadFile(testFilePath)
if err != nil {
t.Fatal(err)
}
order := Get(kOrder)
species := Get(kSpecies)
name := Get(kName)
if order != fOrder {
t.Errorf("want %s, have %s", fOrder, order)
}
if species != fSpecies {
t.Errorf("want %s, have %s", fSpecies, species)
}
if name != eName {
t.Errorf("want %s, have %s", eName, name)
}
}

223
config/iniconf/iniconf.go Normal file
View 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
}

View 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
View File

@@ -0,0 +1,5 @@
[]
key
another key
key = val

13
config/iniconf/testdata/test.conf vendored Normal file
View 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
View File

@@ -0,0 +1,3 @@
key1 = some value
key2 = some other value
key3 = unintuitive value

19
config/path.go Normal file
View 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
View 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
View File

@@ -0,0 +1,7 @@
package config
import "testing"
func TestDefaultPath(t *testing.T) {
t.Log(DefaultConfigPath("demoapp", "app.conf"))
}

2
config/testdata/test.env vendored Normal file
View File

@@ -0,0 +1,2 @@
ORDER=stringiformes
SPECIES=strix aluco

76
dbg/dbg.go Normal file
View File

@@ -0,0 +1,76 @@
// Package dbg implements a debug printer.
package dbg
import (
"fmt"
"io"
"os"
)
// A DebugPrinter is a drop-in replacement for fmt.Print*, and also acts as
// an io.WriteCloser when enabled.
type DebugPrinter struct {
// If Enabled is false, the print statements won't do anything.
Enabled bool
out io.WriteCloser
}
// Close satisfies the Closer interface.
func (dbg *DebugPrinter) Close() error {
return dbg.out.Close()
}
// Write satisfies the Writer interface.
func (dbg *DebugPrinter) Write(p []byte) (int, error) {
if dbg.Enabled {
return dbg.out.Write(p)
}
return 0, nil
}
// New returns a new DebugPrinter on os.Stdout.
func New() *DebugPrinter {
return &DebugPrinter{
out: os.Stdout,
}
}
// ToFile sets up a new DebugPrinter to a file, truncating it if it exists.
func ToFile(path string) (*DebugPrinter, error) {
file, err := os.Create(path)
if err != nil {
return nil, err
}
return &DebugPrinter{
out: file,
}, nil
}
// To sets up a new DebugPrint to an io.WriteCloser.
func To(w io.WriteCloser) *DebugPrinter {
return &DebugPrinter{
out: w,
}
}
// Print calls fmt.Print if Enabled is true.
func (dbg DebugPrinter) Print(v ...interface{}) {
if dbg.Enabled {
fmt.Fprint(dbg.out, v...)
}
}
// Println calls fmt.Println if Enabled is true.
func (dbg DebugPrinter) Println(v ...interface{}) {
if dbg.Enabled {
fmt.Fprintln(dbg.out, v...)
}
}
// Printf calls fmt.Printf if Enabled is true.
func (dbg DebugPrinter) Printf(format string, v ...interface{}) {
if dbg.Enabled {
fmt.Fprintf(dbg.out, format, v...)
}
}

120
dbg/dbg_test.go Normal file
View File

@@ -0,0 +1,120 @@
package dbg
import (
"io/ioutil"
"os"
"testing"
"git.sr.ht/~kisom/goutils/testio"
"github.com/stretchr/testify/require"
)
func TestNew(t *testing.T) {
buf := testio.NewBufCloser(nil)
dbg := New()
dbg.out = buf
dbg.Print("hello")
dbg.Println("hello")
dbg.Printf("hello %s", "world")
require.Equal(t, 0, buf.Len())
dbg.Enabled = true
dbg.Print("hello") // +5
dbg.Println("hello") // +6
dbg.Printf("hello %s", "world") // +11
require.Equal(t, 22, buf.Len())
err := dbg.Close()
require.NoError(t, err)
}
func TestTo(t *testing.T) {
buf := testio.NewBufCloser(nil)
dbg := To(buf)
dbg.Print("hello")
dbg.Println("hello")
dbg.Printf("hello %s", "world")
require.Equal(t, 0, buf.Len())
dbg.Enabled = true
dbg.Print("hello") // +5
dbg.Println("hello") // +6
dbg.Printf("hello %s", "world") // +11
require.Equal(t, 22, buf.Len())
err := dbg.Close()
require.NoError(t, err)
}
func TestToFile(t *testing.T) {
testFile, err := ioutil.TempFile("", "dbg")
require.NoError(t, err)
err = testFile.Close()
require.NoError(t, err)
testFileName := testFile.Name()
defer os.Remove(testFileName)
dbg, err := ToFile(testFileName)
require.NoError(t, err)
dbg.Print("hello")
dbg.Println("hello")
dbg.Printf("hello %s", "world")
stat, err := os.Stat(testFileName)
require.NoError(t, err)
require.EqualValues(t, 0, stat.Size())
dbg.Enabled = true
dbg.Print("hello") // +5
dbg.Println("hello") // +6
dbg.Printf("hello %s", "world") // +11
stat, err = os.Stat(testFileName)
require.NoError(t, err)
require.EqualValues(t, 22, stat.Size())
err = dbg.Close()
require.NoError(t, err)
}
func TestWriting(t *testing.T) {
data := []byte("hello, world")
buf := testio.NewBufCloser(nil)
dbg := To(buf)
n, err := dbg.Write(data)
require.NoError(t, err)
require.EqualValues(t, 0, n)
dbg.Enabled = true
n, err = dbg.Write(data)
require.NoError(t, err)
require.EqualValues(t, 12, n)
err = dbg.Close()
require.NoError(t, err)
}
func TestToFileError(t *testing.T) {
testFile, err := ioutil.TempFile("", "dbg")
require.NoError(t, err)
err = testFile.Chmod(0400)
require.NoError(t, err)
err = testFile.Close()
require.NoError(t, err)
testFileName := testFile.Name()
_, err = ToFile(testFileName)
require.Error(t, err)
err = os.Remove(testFileName)
require.NoError(t, err)
}

15
go.mod Normal file
View File

@@ -0,0 +1,15 @@
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
View 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=

View File

@@ -1,4 +1,4 @@
// +build freebsd darwin netbsd
// +build freebsd darwin,386 netbsd
package lib

View File

@@ -1,4 +1,4 @@
// +build unix linux openbsd
// +build unix linux openbsd darwin,amd64
package lib

View File

@@ -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.

View File

@@ -4,7 +4,7 @@ import (
"os"
"time"
"github.com/kisom/goutils/logging"
"git.sr.ht/~kisom/goutils/logging"
)
var log = logging.NewConsole()

View File

@@ -3,7 +3,7 @@ package logging_test
import (
"time"
"github.com/kisom/goutils/logging"
"git.sr.ht/~kisom/goutils/logging"
)
var log = logging.NewConsole()

View File

@@ -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 {

View File

@@ -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)
}
}

View File

@@ -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")
}
}

View File

@@ -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
View 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
View 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
View 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
}

View File

@@ -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
}

Binary file not shown.

View File

@@ -194,6 +194,11 @@ func (buf *BufCloser) Bytes() []byte {
return buf.buf.Bytes()
}
// Len returns the length of the buffer.
func (buf *BufCloser) Len() int {
return buf.buf.Len()
}
// NewBufCloser creates and initializes a new BufCloser using buf as
// its initial contents. It is intended to prepare a BufCloser to read
// existing data. It can also be used to size the internal buffer for