Skip to content
Snippets Groups Projects
Verified Commit 16566494 authored by Volker Schukai's avatar Volker Schukai :alien:
Browse files

initial version

parent 35fab608
No related branches found
No related tags found
No related merge requests found
Showing with 1937 additions and 0 deletions
package configuration
import (
"io"
"strings"
"testing"
)
func TestWrite(t *testing.T) {
config := ConfigStruct2{
A: "AXXX",
F: 99,
}
s := New(config)
// not implemented , Toml, Properties
for _, f := range []Format{Yaml, Json} {
w := strings.Builder{}
x := io.Writer(&w)
s.Write(x, f)
if s.HasError() {
t.Error(s.Errors())
s.ResetErrors()
continue
}
if w.String() == "" {
t.Error("Expected not empty")
}
content := w.String()
if f == Yaml {
if strings.Contains(content, "AXXX") == false {
t.Error("Expected AXXX")
}
if strings.Contains(content, "B: false") == false {
t.Error("Expected not B: false got ", w.String())
}
if strings.Contains(content, "F: 99") == false {
t.Error("Expected not F: 99 got ", w.String())
}
} else if f == Json {
}
}
}
file.go 0 → 100644
package configuration
import (
"golang.org/x/exp/slices"
"io/fs"
"os"
"path"
)
type files struct {
path string
format Format
}
type fileBackend struct {
directories []string
files []files
format Format
name string
fs fs.FS
}
// AddFiles adds a file to the list of files to import
func (s *setting[C]) AddFile(file string, format Format) *setting[C] {
s.files.files = append(s.files.files, files{file, format})
return s
}
func initFileBackend(files *fileBackend) {
files.format = Yaml
files.name = fileName
files.fs = fs.FS(internalFS{})
}
// Path returns the configuration directory
func (s *setting[C]) Directories() []string {
return s.files.directories
}
// AddPath adds a directory to the configuration directory
func (s *setting[C]) AddDirectory(d string) *setting[C] {
s.Lock()
defer s.Unlock()
s.files.directories = append(s.files.directories, d)
s.sanitizeDirectories()
return s
}
func (s *setting[C]) sanitizeDirectories() {
wd, err := os.Getwd()
if err != nil {
s.errors = append(s.errors, err)
}
visited := make(map[string]bool, 0)
for i := 0; i < len(s.files.directories); i++ {
d := s.files.directories[i]
if !path.IsAbs(d) {
d = path.Join(wd, d)
}
if visited[d] == true {
s.errors = append(s.errors, newPathAlreadyExistsError(d))
} else {
visited[d] = true
}
if _, err := os.Stat(d); os.IsNotExist(err) {
s.errors = append(s.errors, newPathDoesNotExistError(d))
}
}
}
// Set all configuration directories
func (s *setting[C]) SetDirectories(d []string) *setting[C] {
s.Lock()
defer s.Unlock()
s.files.directories = d
s.sanitizeDirectories()
return s
}
// Add the current working directory to the configuration directory
func (s *setting[C]) AddWorkingDirectory() *setting[C] {
s.Lock()
defer s.Unlock()
current, err := os.Getwd()
if err != nil {
s.errors = append(s.errors, err)
return s
}
s.files.directories = append(s.files.directories, current)
s.sanitizeDirectories()
return s
}
// Add the Unix etc directory to the configuration directory
func (s *setting[C]) AddEtcDirectory() *setting[C] {
s.Lock()
defer s.Unlock()
if s.mnemonic == "" {
s.errors = append(s.errors, MnemonicEmptyError)
return s
}
p := "/etc"
if _, err := os.Stat(p); os.IsNotExist(err) {
s.errors = append(s.errors, newPathDoesNotExistError(p))
return s
}
p = path.Join(p, s.mnemonic)
s.files.directories = append(s.files.directories, p)
s.sanitizeDirectories()
return s
}
// Add the user configuration directory to the configuration directory
// The mnemonic must be set for this function
func (s *setting[C]) AddUserConfigDirectory() *setting[C] {
if s.mnemonic == "" {
s.errors = append(s.errors, MnemonicEmptyError)
return s
}
s.Lock()
defer s.Unlock()
current, err := os.UserConfigDir()
if err != nil {
s.errors = append(s.errors, err)
return s
}
current = path.Join(current, s.mnemonic)
s.files.directories = append(s.files.directories, current)
s.sanitizeDirectories()
return s
}
// Add the current working directory, the user configuration directory
// and the Unix etc directory to the configuration directory
func (s *setting[C]) SetDefaultDirectories() *setting[C] {
s.AddWorkingDirectory().
AddUserConfigDirectory().
AddEtcDirectory()
return s
}
func (s *setting[C]) SetFileFormat(format Format) *setting[C] {
if slices.Contains(availableFormats, format) {
s.files.format = format
} else {
s.errors = append(s.errors, newFormatNotSupportedError(format))
}
return s
}
// Set the file name without extension
func (s *setting[C]) SetFileName(name string) *setting[C] {
if name == "" {
s.errors = append(s.errors, FileNameEmptyError)
} else {
s.files.name = name
}
return s
}
func (s *setting[C]) SetFilesystem(f fs.FS) *setting[C] {
s.files.fs = f
return s
}
package configuration
import (
"bytes"
"github.com/magiconair/properties/assert"
"io"
"io/fs"
"os"
"testing"
"time"
)
type mockFS struct{}
type mockFile struct {
fs.FileInfo
buffer io.Reader
}
type mockFileInfo struct {
name string
size int64
mode fs.FileMode
mod time.Time
dir bool
sys interface{}
}
func (m mockFileInfo) Name() string {
return m.name
}
func (m mockFileInfo) Size() int64 {
return m.size
}
func (m mockFileInfo) Mode() fs.FileMode {
return m.mode
}
func (m mockFileInfo) ModTime() time.Time {
return m.mod
}
func (m mockFileInfo) IsDir() bool {
return m.dir
}
func (m mockFileInfo) Sys() any {
return m.sys
}
func (f mockFile) Stat() (fs.FileInfo, error) {
fi := mockFileInfo{}
return fi, nil
}
func (f mockFile) Read(b []byte) (int, error) {
return f.buffer.Read(b)
}
func (f mockFile) Close() error {
return nil
}
type fileSample struct {
file *mockFile
err error
}
var fileSamples []fileSample
func (mockFS) Open(name string) (fs.File, error) {
if len(fileSamples) > 0 {
s := fileSamples[0]
fileSamples = fileSamples[1:]
if s.err != nil {
return nil, s.err
}
return s.file, nil
}
return os.Open(name)
}
func TestDirectories(t *testing.T) {
cfg := ConfigStruct2{}
c := New(cfg)
c.AddDirectory("/a")
if c.Directories() == nil {
t.Error("Expected not nil")
}
if len(c.Directories()) != 1 {
t.Error("Expected 1")
}
}
func TestSetDirectories(t *testing.T) {
cfg := ConfigStruct2{}
c := New(&cfg)
c.SetDirectories([]string{"/a", "/b"})
if c.Directories() == nil {
t.Error("Expected not nil")
}
if len(c.Directories()) != 2 {
t.Error("Expected 2")
}
}
func TestAddWorkingDirectory(t *testing.T) {
cfg := ConfigStruct2{}
c := New(&cfg)
c.AddWorkingDirectory()
if c.Directories() == nil {
t.Error("Expected not nil")
}
if len(c.Directories()) != 1 {
t.Error("Expected 1")
}
}
func TestAddWorkingDirectoryTwice(t *testing.T) {
cfg := ConfigStruct2{}
c := New(&cfg)
c.AddWorkingDirectory()
c.AddWorkingDirectory()
if c.Directories() == nil {
t.Error("Expected not nil")
}
if len(c.Directories()) != 2 {
t.Error("Expected 2 got ", len(c.Directories()))
}
if c.Errors() == nil {
t.Error("Expected not nil")
}
}
func TestAddDirectoryMissingMnemonic(t *testing.T) {
cfg := ConfigStruct2{}
c := New(cfg)
c.AddEtcDirectory()
if !c.HasError() {
t.Error("Expected error")
}
if c.Errors()[0] != MnemonicEmptyError {
t.Error("Expected error")
}
}
func TestAddDirectory(t *testing.T) {
cfg := ConfigStruct2{}
c := New(&cfg)
c.SetMnemonic("test")
c.AddEtcDirectory()
if c.Directories() == nil {
t.Error("Expected not nil")
}
if len(c.Directories()) != 1 {
t.Error("Expected 1")
}
}
func TestAddDirectoryTwice(t *testing.T) {
cfg := ConfigStruct2{}
c := New(&cfg)
c.SetMnemonic("test")
c.AddEtcDirectory()
c.AddEtcDirectory()
if c.Directories() == nil {
t.Error("Expected not nil")
}
if len(c.Directories()) != 2 {
t.Error("Expected 1 got ", len(c.Directories()))
}
if c.Errors() == nil {
t.Error("Expected not nil")
}
}
func TestAddUserDirectory(t *testing.T) {
cfg := ConfigStruct2{}
c := New(&cfg)
c.SetMnemonic("test")
c.AddUserConfigDirectory()
if c.Directories() == nil {
t.Error("Expected not nil")
}
if len(c.Directories()) != 1 {
t.Error("Expected 1")
}
}
func TestSetDefaultPath(t *testing.T) {
cfg := ConfigStruct2{}
c := New(&cfg)
c.SetMnemonic("test")
c.SetDefaultDirectories()
if c.Directories() == nil {
t.Error("Expected not nil")
}
if len(c.Directories()) != 3 {
t.Error("Expected 3 got ", len(c.Directories()))
}
}
func TestSetFileFormat(t *testing.T) {
cfg := ConfigStruct2{}
c := New(cfg)
c.SetFileFormat(Yaml)
if c.HasError() {
t.Error("Expected not error")
}
}
func TestSetFileFormatError(t *testing.T) {
cfg := ConfigStruct2{}
c := New(cfg)
c.SetFileFormat(Format(9999))
if !c.HasError() {
t.Error("Expected error")
}
}
func TestImport(t *testing.T) {
fileSamples = []fileSample{}
defer func() { fileSamples = nil }()
addSample1()
defaults := ConfigStruct2{
A: "Hello!",
D: map[string]string{"A": "1", "B": "2"},
F: 99,
}
mockFs := mockFS{}
c := New(defaults)
c.SetMnemonic("test")
c.SetFileFormat(Yaml)
c.SetFilesystem(mockFs)
// setDefaultdirectories can be returned errors
c.SetDefaultDirectories().ResetErrors()
c.Import()
if c.HasError() {
t.Error("Expected not error", c.Errors())
}
if c.Config().A != "from file" {
t.Error("Expected \"from file\" got ", c.Config().A)
}
m := c.Config().D.(map[string]interface{})
if m["A"] != "x" {
t.Error("Expected x got ", m["A"])
}
if m["B"] != "2" {
t.Error("Expected 2 got ", m["B"])
}
}
func addSample1() {
e := &mockFile{}
content := []byte(
`---
A: "from file"
F: 3
D:
A: x
C:
- CA:
CAA: 1
- CB: 2
...
`)
e.buffer = bytes.NewBuffer(content)
e.FileInfo = mockFileInfo{
name: "test",
size: int64(len(content)),
mode: 0,
mod: time.Now(),
dir: false,
sys: nil,
}
fileSamples = append(fileSamples, fileSample{
file: e,
})
}
func addSample2() {
e := &mockFile{}
content := []byte(
`---
F: 5
C:
- CA:
CAA: 1
- CB: 2
...
`)
e.buffer = bytes.NewBuffer(content)
e.FileInfo = mockFileInfo{
name: "test",
size: int64(len(content)),
mode: 0,
mod: time.Now(),
dir: false,
sys: nil,
}
fileSamples = append(fileSamples, fileSample{
file: e,
})
}
// Import two files. The second file should overwrite the first file.
func TestImport2(t *testing.T) {
fileSamples = []fileSample{}
defer func() { fileSamples = nil }()
addSample2()
addSample1()
defaults := ConfigStruct2{
A: "Hello!",
D: map[string]string{"A": "1", "B": "2"},
F: 99,
}
mockFs := mockFS{}
c := New(defaults)
c.SetMnemonic("test")
c.SetFileFormat(Yaml)
c.SetFilesystem(mockFs)
c.SetDefaultDirectories().ResetErrors() // errors not important here
c.Import()
if c.HasError() {
t.Error("Expected not error", c.Errors())
}
if c.Config().A != "from file" {
t.Error("Expected \"from file\" got ", c.Config().A)
}
m := c.Config().D.(map[string]interface{})
if m["A"] != "x" {
t.Error("Expected x got ", m["A"])
}
if m["B"] != "2" {
t.Error("Expected 2 got ", m["B"])
}
if c.Config().F != 5 {
t.Error("Expected 5 got ", c.Config().F)
}
}
// Import two files. The second file should overwrite the first file.
func TestImport3(t *testing.T) {
fileSamples = []fileSample{}
defer func() { fileSamples = nil }()
addSample1()
addSample2()
defaults := ConfigStruct2{
A: "Hello!",
D: map[string]string{"A": "1", "B": "2"},
F: 99,
}
mockFs := mockFS{}
c := New(defaults)
c.SetMnemonic("test")
c.SetFileFormat(Yaml)
c.SetFilesystem(mockFs)
c.SetDefaultDirectories().ResetErrors() // errors not important here
c.Import()
if c.HasError() {
t.Error("Expected not error", c.Errors())
}
if c.Config().A != "from file" {
t.Error("Expected \"from file\" got ", c.Config().A)
}
m := c.Config().D.(map[string]interface{})
if m["A"] != "x" {
t.Error("Expected x got ", m["A"])
}
if m["B"] != "2" {
t.Error("Expected 2 got ", m["B"])
}
if c.Config().F != 3 {
t.Error("Expected 3 got ", c.Config().F)
}
}
func TestSettingAddFile(t *testing.T) {
cfg := ConfigStruct2{}
f, err := os.CreateTemp("", "watch_test")
if err != nil {
t.Error(err)
return
}
defer os.Remove(f.Name())
f.WriteString("A: \"from file\"")
c := New(cfg)
c.AddFile(f.Name(), Yaml)
assert.Equal(t, c.Config().A, "")
c.Import()
assert.Equal(t, c.Config().A, "from file")
}
func TestSetConfigName(t *testing.T) {
cfg := ConfigStruct2{}
c := New(cfg)
c.SetFileName("test")
if c.HasError() {
t.Error("Expected not error")
}
}
func TestConfigSetFileName(t *testing.T) {
cfg := ConfigStruct2{}
c := New(cfg)
c.SetFileName("test")
if c.HasError() {
t.Error("Expected not error")
}
c.SetFileName("")
if !c.HasError() {
t.Error("Expected error")
}
}
func TestConfigSetFileFormat(t *testing.T) {
cfg := ConfigStruct2{}
c := New(cfg)
c.SetFileFormat(99)
if !c.HasError() {
t.Error("Expected not error")
}
}
func TestConfigSetDirectories(t *testing.T) {
cfg := ConfigStruct2{}
c := New(cfg)
c.SetDirectories([]string{"a", "b"})
if !c.HasError() {
t.Error("Expected not error")
}
}
func TestConfigDirectories(t *testing.T) {
cfg := ConfigStruct2{}
c := New(cfg)
c.SetDirectories([]string{"a", "b"})
if !c.HasError() {
t.Error("Expected not error")
}
dirs := c.Directories()
if len(dirs) != 2 {
t.Error("Expected 2 got ", len(dirs))
}
}
func TestConfigAddDirectory(t *testing.T) {
cfg := ConfigStruct2{}
c := New(cfg)
c.AddDirectory("a")
if !c.HasError() {
t.Error("Expected not error")
}
dirs := c.Directories()
if len(dirs) != 1 {
t.Error("Expected 1 got ", len(dirs))
}
}
package configuration
import (
"io/fs"
"os"
)
type internalFS struct{}
func (internalFS) Open(name string) (fs.File, error) {
return os.Open(name)
}
package configuration
import "testing"
func TestFilesystemOpen(t *testing.T) {
f := internalFS{}
h, _ := f.Open("test-not-exists-unknown-file")
if h == nil {
t.Error("Expected nil")
}
}
flags.go 0 → 100644
package configuration
import (
"flag"
"reflect"
"strconv"
)
// AddFileFromFlags adds a file to the configuration
// The file is read from the flag specified by the name
func (s *setting[C]) AddFileFromFlagSet(flagset *flag.FlagSet, name string, format Format) *setting[C] {
flag := flagset.Lookup(name)
if flag == nil {
s.errors = append(s.errors, newFlagDoesNotExistError(name))
return s
}
path := flag.Value.String()
if path == "" {
s.errors = append(s.errors, FileNameEmptyError)
return s
}
return s.AddFile(path, format)
}
// InitFromFlags initializes the configuration from the command line flags.
func (s *setting[C]) InitFromFlagSet(flagset *flag.FlagSet) *setting[C] {
s.Lock()
defer s.Unlock()
err := runOnTags(&s.config, []string{flagTagKey}, func(k string, field reflect.Value) {
flag := flagset.Lookup(k)
if flag == nil {
s.errors = append(s.errors, newFlagNotFoundError(k))
return // flag not found
}
v := flag.Value
t := reflect.TypeOf(v)
if !field.CanSet() {
return
}
switch field.Kind() {
case reflect.String:
field.SetString(v.String())
case reflect.Int:
intVar, err := strconv.Atoi(v.String())
if err != nil {
s.errors = append(s.errors, err)
return
}
field.SetInt(int64(intVar))
case reflect.Bool:
field.SetBool(v.String() == "true")
default:
s.errors = append(s.errors, newMismatchedTypeError(t, field.Type()))
}
})
if err != nil {
s.errors = append(s.errors, err)
}
return s
}
package configuration
import (
"flag"
"os"
"testing"
"github.com/magiconair/properties/assert"
)
// Example for README
// Print are commented out because they are only for demonstration
func TestReadmeExample3(t *testing.T) {
config := struct {
Host string `flag:"host"`
}{
Host: "localhost",
}
// Set value
flag.String("host", "www.example.com", "help message for flagname")
flag.Parse()
s := New(config)
s.InitFromFlagSet(flag.CommandLine)
//fmt.Println(s.Config().Host) // www.example.com
assert.Equal(t, s.Config().Host, "www.example.com")
}
func TestSettingFromFlagWithDefault(t *testing.T) {
config := ConfigStruct6{}
s := New(config)
assert.Equal(t, s.Config().IB, "yes")
cmd := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
cmd.String("config", "", "help message for flagname")
tmp, err := os.CreateTemp("", "config")
if err != nil {
t.Error(err)
return
}
defer os.Remove(tmp.Name())
tmp.WriteString("IB: i-am-b")
cmd.Parse([]string{"--config", tmp.Name()})
s.AddFileFromFlagSet(cmd, "config", Yaml)
s.Import()
assert.Equal(t, s.Config().IB, "i-am-b")
}
func TestSettingFromFlag2(t *testing.T) {
config := ConfigStruct6{
IA: 1234,
IB: "no",
IC: false,
ID: true,
}
s := New(config)
cmd := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
cmd.Int("value-ia", 1234, "help message for flagname")
cmd.String("value-ib", "no", "help message for flagname")
cmd.Bool("value-ic", false, "help message for flagname")
cmd.Bool("value-id", true, "help message for flagname")
cmd.Parse([]string{"--value-ia=1423"})
s.InitFromFlagSet(cmd)
if s.Config().IA != 1423 {
t.Errorf("Expected 1423, got %d", s.Config().IA)
}
}
func TestSettingFromFlag(t *testing.T) {
config := ConfigStruct6{
IA: 34567,
ID: true,
}
s := New(config)
cmd := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
cmd.Int("value-ia", 1234, "help message for flagname")
cmd.String("value-ib", "no", "help message for flagname")
cmd.Bool("value-ic", false, "help message for flagname")
cmd.Bool("value-id", true, "help message for flagname")
cmd.Parse([]string{"--value-ia=8900",
"--value-ib=i-am-b",
"--value-ic=true",
"--value-id=false"})
s.InitFromFlagSet(cmd)
if s.Config().IA != 8900 {
t.Errorf("Expected 8900, got %d", s.Config().IA)
}
if s.Config().IB != "i-am-b" {
t.Errorf("Expected i-am-b, got %s", s.Config().IB)
}
if s.Config().IC != true {
t.Errorf("Expected true, got %t", s.Config().IC)
}
if s.Config().ID != false {
t.Errorf("Expected false, got %t", s.Config().ID)
}
}
package configuration
type Format int
const (
Yaml Format = iota
Json
Toml
Properties
)
var availableFormats = []Format{Yaml, Json, Toml, Properties}
func (f Format) String() string {
switch f {
case Yaml:
return "yaml"
case Json:
return "json"
case Toml:
return "toml"
case Properties:
return "properties"
default:
return "unknown"
}
}
func (f Format) Extension() string {
return "." + f.String()
}
package configuration
import "testing"
func TestFileFormat(t *testing.T) {
var f Format
for _, v := range availableFormats {
f = v
if f.String() == "unknown" {
t.Error("Expected not unknown")
}
if f.Extension() == ".unknown" {
t.Error("Expected not .unknown")
}
}
}
go.mod 0 → 100644
module gitlab.schukai.com/oss/libraries/go/application/configuration
go 1.19
require (
github.com/fsnotify/fsnotify v1.5.4
github.com/imdario/mergo v0.3.13
github.com/kinbiko/jsonassert v1.1.1
github.com/magiconair/properties v1.8.6
github.com/pelletier/go-toml/v2 v2.0.5
github.com/r3labs/diff/v3 v3.0.0
github.com/stretchr/testify v1.8.0
gitlab.schukai.com/oss/libraries/go/network/http-negotiation v1.1.0
golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
)
go.sum 0 → 100644
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/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/kinbiko/jsonassert v1.1.1 h1:DB12divY+YB+cVpHULLuKePSi6+ui4M/shHSzJISkSE=
github.com/kinbiko/jsonassert v1.1.1/go.mod h1:NO4lzrogohtIdNUNzx8sdzB55M4R4Q1bsrWVdqQ7C+A=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
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/r3labs/diff/v3 v3.0.0 h1:ZhPwNxn9gW5WLPBV9GCYaVbMdLOSmJ0DeKdCiSbOLUI=
github.com/r3labs/diff/v3 v3.0.0/go.mod h1:wCkTySAiDnZao1sZrVTDIzuzgLZ+cNPGn3LC8DlIg5g=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
gitlab.schukai.com/oss/libraries/go/network/http-negotiation v1.1.0 h1:4sZ1sQ9JU2KfLWwhpM3rgOa4zG7CNJc+ntWKmya2vl4=
gitlab.schukai.com/oss/libraries/go/network/http-negotiation v1.1.0/go.mod h1:RS2rKf5O+rmSBshHLOgjG7dxg5N2MhNYokZOBcuXdX8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b h1:SCE/18RnFsLrjydh/R/s5EVvHoZprqEQUuoxK8q2Pc4=
golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI=
golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 h1:ohgcoMbSofXygzo6AD2I1kz3BFmW1QArPYTtwEM3UXc=
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
package configuration
import (
"bytes"
"context"
negotiation "gitlab.schukai.com/oss/libraries/go/network/http-negotiation"
"net/http"
)
// ContextKey is the key used to store the configuration in the request context
// This is used by the middleware
func (s *setting[C]) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = context.WithValue(ctx, contextKey, s.config)
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
// serveGet handles GET requests
func (s *setting[C]) serveGet(w http.ResponseWriter, r *http.Request) {
n := negotiation.New(r.Header)
m := n.Type("application/json", "text/json", "application/yaml", "text/yaml", "application/toml", "text/toml", "application/properties", "text/properties", "text/x-java-properties", "text/x-properties")
if m == "" {
w.WriteHeader(http.StatusNotAcceptable)
return
}
buf := new(bytes.Buffer)
switch m {
case "application/json", "text/json":
w.Header().Set("Content-Type", "application/json")
s.Write(buf, Json)
case "application/yaml", "text/yaml":
w.Header().Set("Content-Type", "application/yaml")
s.Write(buf, Yaml)
case "application/toml", "text/toml":
w.Header().Set("Content-Type", "application/toml")
s.Write(buf, Toml)
case "application/properties", "text/properties", "text/x-java-properties", "text/x-properties":
w.Header().Set("Content-Type", "application/properties")
s.Write(buf, Properties)
}
if s.HasError() {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write(buf.Bytes())
}
func (s *setting[C]) servePost(w http.ResponseWriter, r *http.Request) {
n := negotiation.New(r.Header)
m := n.ContentType("application/json", "text/json", "application/yaml", "text/yaml", "application/toml", "text/toml", "application/properties", "text/properties", "text/x-java-properties", "text/x-properties")
rs := reader{
reader: r.Body,
}
switch m {
case "application/json", "text/json":
rs.format = Json
case "application/yaml", "text/yaml":
rs.format = Yaml
case "application/toml", "text/toml":
rs.format = Toml
case "application/properties", "text/properties", "text/x-java-properties", "text/x-properties":
rs.format = Properties
default:
w.WriteHeader(http.StatusUnsupportedMediaType)
return
}
s.Lock()
defer s.Unlock()
b := s.config
s.importStream(rs)
x := s.config
s.config = b
s.setConfigInternal(x, false)
}
// ServeHTTP implements the http.Handler interface
func (s *setting[C]) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
s.serveGet(w, r)
case http.MethodPost:
s.servePost(w, r)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
package configuration
import (
"github.com/kinbiko/jsonassert"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
type ConfigHttpStruct1 struct {
A string `yaml:"A" json:"A" toml:"A" properties:"A"`
B bool `yaml:"B" json:"B" toml:"B" properties:"B"`
C int `yaml:"C" json:"C" toml:"C" properties:"C"`
}
func TestReadMeHttp(t *testing.T) {
var config ConfigHttpStruct1
s := New(config)
req, err := http.NewRequest("GET", "/config", nil)
if err != nil {
t.Fatal(err)
}
req.Header.Set("Accept", "application/json")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(s.ServeHTTP)
handler.ServeHTTP(rr, req)
jsonassert.New(t).Assertf(rr.Body.String(),
`{
"A": "",
"B": false,
"C": 0
}
`)
}
func TestConfigurationMiddleware(t *testing.T) {
req, err := http.NewRequest("GET", "/config", nil)
if err != nil {
t.Fatal(err)
}
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
val := r.Context().Value(contextKey)
if val == nil {
t.Error("config not present")
return
}
config, ok := val.(ConfigHttpStruct1)
if !ok {
t.Error("config not of correct type")
}
assert.Equal(t, config.A, "a")
})
var config ConfigHttpStruct1
config.A = "a"
s := New(config)
s.Middleware(nextHandler).ServeHTTP(httptest.NewRecorder(), req)
}
func TestSettingServeHTTPJson(t *testing.T) {
// Create a request to pass to our handler. We don't have any query parameters for now, so we'll
// pass 'nil' as the third parameter.
req, err := http.NewRequest("GET", "/config", nil)
if err != nil {
t.Fatal(err)
}
var config ConfigHttpStruct1
s := New(config)
req.Header.Set("Accept", "application/json")
// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
rr := httptest.NewRecorder()
handler := http.HandlerFunc(s.ServeHTTP)
// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
// directly and pass in our Request and ResponseRecorder.
handler.ServeHTTP(rr, req)
// Check the status code is what we expect.
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
// Check the response body is what we expect.
jsonassert.New(t).Assertf(rr.Body.String(),
`{
"A": "",
"B": false,
"C": 0
}
`)
}
func TestSettingServeHTTPYaml(t *testing.T) {
// Create a request to pass to our handler. We don't have any query parameters for now, so we'll
// pass 'nil' as the third parameter.
req, err := http.NewRequest("GET", "/config", nil)
if err != nil {
t.Fatal(err)
}
req.Header.Set("Accept", "application/yaml")
var config ConfigHttpStruct1
s := New(config)
// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
rr := httptest.NewRecorder()
handler := http.HandlerFunc(s.ServeHTTP)
// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
// directly and pass in our Request and ResponseRecorder.
handler.ServeHTTP(rr, req)
// Check the status code is what we expect.
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
// Check the response body is what we expect.
yaml := `A: ""
B: false
C: 0
`
if rr.Body.String() != yaml {
t.Errorf("handler returned unexpected body: got %v want %v",
rr.Body.String(), yaml)
}
}
func TestSettingServeHTTPToml(t *testing.T) {
// Create a request to pass to our handler. We don't have any query parameters for now, so we'll
// pass 'nil' as the third parameter.
req, err := http.NewRequest("GET", "/config", nil)
if err != nil {
t.Fatal(err)
}
req.Header.Set("Accept", "application/toml")
var config ConfigHttpStruct1
s := New(config)
// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
rr := httptest.NewRecorder()
handler := http.HandlerFunc(s.ServeHTTP)
// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
// directly and pass in our Request and ResponseRecorder.
handler.ServeHTTP(rr, req)
// Check the status code is what we expect.
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
// Check the response body is what we expect.
toml := `A = ''
B = false
C = 0
`
if rr.Body.String() != toml {
t.Errorf("handler returned unexpected body: got %v want %v",
rr.Body.String(), toml)
}
}
func TestSettingsHttpStatusNotAcceptable(t *testing.T) {
req, err := http.NewRequest("GET", "/config", nil)
if err != nil {
t.Fatal(err)
}
req.Header.Set("Accept", "text/html")
var config ConfigHttpStruct1
s := New(config)
// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
rr := httptest.NewRecorder()
handler := http.HandlerFunc(s.ServeHTTP)
// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
// directly and pass in our Request and ResponseRecorder.
handler.ServeHTTP(rr, req)
// Check the status code is what we expect.
if status := rr.Code; status != http.StatusNotAcceptable {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusNotAcceptable)
}
}
func TestSettingServeHTTPProperties(t *testing.T) {
// Create a request to pass to our handler. We don't have any query parameters for now, so we'll
// pass 'nil' as the third parameter.
req, err := http.NewRequest("GET", "/config", nil)
if err != nil {
t.Fatal(err)
}
req.Header.Set("Accept", "application/properties")
var config ConfigHttpStruct1
s := New(config)
// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
rr := httptest.NewRecorder()
handler := http.HandlerFunc(s.ServeHTTP)
// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
// directly and pass in our Request and ResponseRecorder.
handler.ServeHTTP(rr, req)
// Check the status code is what we expect.
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
// Check the response body is what we expect.
// https://en.wikipedia.org/wiki/.properties
// Each line in a .properties file normally stores a single property.
// Several formats are possible for each line, including key=value,
// key = value, key:value, and key value. Single-quotes or double-quotes
// are considered part of the string. Trailing space is significant and
// presumed to be trimmed as required by the consumer.
r := rr.Body.String()
// the order of the properties is not guaranteed
assert.Contains(t, r, "A = ")
assert.Contains(t, r, "B = false")
assert.Contains(t, r, "C = 0")
}
func TestConfigurationServePostJson(t *testing.T) {
var config ConfigHttpStruct1
s := New(config)
closeChan := make(chan bool)
counter := 0
s.OnChange(func(event ChangeEvent) {
counter++
closeChan <- true
})
// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
rr := httptest.NewRecorder()
handler := http.HandlerFunc(s.ServeHTTP)
// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
// directly and pass in our Request and ResponseRecorder.
req, err := http.NewRequest("POST", "/config", strings.NewReader(`{
"A": "hello",
"B": true,
"C": 1
}`))
if err != nil {
t.Fatal(err)
}
req.Header.Set("Content-Type", "application/json")
handler.ServeHTTP(rr, req)
// Check the status code is what we expect.
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
config = s.Config()
assert.Equal(t, "hello", config.A)
assert.Equal(t, true, config.B)
assert.Equal(t, 1, config.C)
select {
case <-closeChan:
case <-time.After(time.Second * 5):
t.Fatalf("Timeout")
}
if counter != 1 {
t.Error("Expected 1 got ", counter)
}
}
import.go 0 → 100644
package configuration
import (
"bytes"
"encoding/json"
"github.com/imdario/mergo"
"github.com/magiconair/properties"
"github.com/pelletier/go-toml/v2"
"gopkg.in/yaml.v3"
"io"
"os"
"path"
)
func importJson[C any](config *C, reader io.Reader) error {
decoder := json.NewDecoder(reader)
return decoder.Decode(config)
}
func importYaml[C any](config *C, reader io.Reader) error {
decoder := yaml.NewDecoder(reader)
return decoder.Decode(config)
}
func importToml[C any](config *C, reader io.Reader) error {
decoder := toml.NewDecoder(reader)
return decoder.Decode(config)
}
func importProperties[C any](config *C, reader io.Reader) error {
buf := &bytes.Buffer{}
buf.ReadFrom(reader)
b := buf.Bytes()
p, err := properties.Load(b, properties.UTF8)
if err != nil {
return err
}
return p.Decode(config)
}
func (s *setting[C]) importStream(r reader) {
var c C
var err error
err = nil // reset error
x := r.reader
switch r.format {
case Json:
err = importJson(&c, x)
case Yaml:
err = importYaml(&c, x)
case Toml:
err = importToml(&c, x)
case Properties:
err = importProperties(&c, x)
default:
err = newFormatNotSupportedError(r.format)
}
if err == nil {
s.importCounter++
} else {
s.errors = append(s.errors, err)
}
if err := mergo.Merge(&s.config, c); err != nil {
s.errors = append(s.errors, err)
}
}
func (s *setting[C]) importStreams() {
for _, r := range s.stream.readers {
s.importStream(r)
}
}
func (s *setting[C]) importFiles() {
s.fileWatch.Lock()
defer s.fileWatch.Unlock()
// new files may have been added
s.fileWatch.watchList = make(map[string]string)
for _, d := range s.files.directories {
fn := path.Join(d, s.files.name+s.files.format.Extension())
f, err := s.files.fs.Open(fn)
if os.IsNotExist(err) {
f.Close()
continue
}
r := (io.Reader)(f)
s.importStream(reader{s.files.format, r})
f.Close()
s.fileWatch.watchList[fn] = fn
}
for _, f := range s.files.files {
r, err := s.files.fs.Open(f.path)
if err != nil {
s.errors = append(s.errors, err)
r.Close()
continue
}
s.importStream(reader{f.format, r})
r.Close()
s.fileWatch.watchList[f.path] = f.path
}
}
func (s *setting[C]) Import() *setting[C] {
s.Lock()
defer s.Unlock()
defaults := s.config
var n C
s.config = n
s.importCounter = 0
s.importFiles()
s.importStreams()
if err := mergo.Merge(&s.config, defaults); err != nil {
s.errors = append(s.errors, err)
}
if s.importCounter == 0 {
s.errors = append(s.errors, NoFileImportedError)
}
x := s.config
s.config = defaults
s.setConfigInternal(x, false)
return s
}
package configuration
import (
"github.com/magiconair/properties/assert"
"testing"
)
// Example for README
// Print are commented out because they are only for demonstration
func TestReadExample3(t *testing.T) {
config := struct {
Host string
}{
Host: "localhost",
}
c := New(config)
c.SetMnemonic("my-app")
c.SetDefaultDirectories()
c.Import()
//fmt.Println(c.Config().Host)
assert.Equal(t, c.Config().Host, "localhost")
}
package configuration
import (
"fmt"
"os"
"testing"
)
func FuzzTest(f *testing.F) {
f.Fuzz(func(t *testing.T, a string, b bool, f int) {
config := ConfigStruct2{
A: a,
B: b,
F: f,
}
s := New(config)
fmt.Println("A:", a, "B:", b, "F:", f)
if s == nil {
t.Error("Expected not nil")
}
if s.Config().A != a {
t.Error("Expected %i got %i", a, s.Config().A)
}
if s.Config().B != b {
t.Error("Expected %V got %V", b, s.Config().B)
}
if s.Config().F != f {
t.Error("Expected %i got %i", f, s.Config().F)
}
s.Write(os.Stdout, Yaml)
if s.HasError() {
t.Error(s.Errors())
s.ResetErrors()
}
})
}
Copyright (c) 2013 Dario Castañé. All rights reserved.
Copyright (c) 2012 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Copyright (c) 2013-2020, Frank Schroeder
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The MIT License (MIT)
Copyright (c) 2013 - 2022 Thomas Pelletier, Eric Anderton
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
.idea/
patchflags_string.go
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment