mirror of
https://github.com/Threnklyn/jira.git
synced 2026-05-31 02:08:28 +02:00
rewrite checkpoint
This commit is contained in:
+1
@@ -0,0 +1 @@
|
||||
language: go
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
GENERATOR_SRC = \
|
||||
rawoption.go \
|
||||
$(NULL)
|
||||
|
||||
GENERATED_SRC = $(GENERATOR_SRC:%.go=gen-%.go)
|
||||
|
||||
test: $(GENERATED_SRC)
|
||||
go get -t -v
|
||||
go get github.com/kr/pretty
|
||||
go get gopkg.in/alecthomas/kingpin.v2
|
||||
go test
|
||||
|
||||
gen-%.go: %.go
|
||||
# use github.com/cheekybits/genny after https://github.com/cheekybits/genny/pull/42 is merged
|
||||
go get github.com/coryb/genny
|
||||
go generate
|
||||
|
||||
.PHONY: test
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
[](https://travis-ci.org/coryb/figtree)
|
||||
[](https://godoc.org/github.com/coryb/figtree)
|
||||
|
||||
Figtree is a go library to recusively parse and merge yaml based config files.
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package figtree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// dst must be a pointer type
|
||||
func convertString(src string, dst interface{}) (err error) {
|
||||
switch v := dst.(type) {
|
||||
case *bool:
|
||||
*v, err = strconv.ParseBool(src)
|
||||
case *string:
|
||||
*v = src
|
||||
case *int:
|
||||
var tmp int64
|
||||
// this is a cheat, we only know int is at least 32 bits
|
||||
// but we have to make a compromise here
|
||||
tmp, err = strconv.ParseInt(src, 10, 32)
|
||||
*v = int(tmp)
|
||||
case *int8:
|
||||
var tmp int64
|
||||
tmp, err = strconv.ParseInt(src, 10, 8)
|
||||
*v = int8(tmp)
|
||||
case *int16:
|
||||
var tmp int64
|
||||
tmp, err = strconv.ParseInt(src, 10, 16)
|
||||
*v = int16(tmp)
|
||||
case *int32:
|
||||
var tmp int64
|
||||
tmp, err = strconv.ParseInt(src, 10, 32)
|
||||
*v = int32(tmp)
|
||||
case *int64:
|
||||
var tmp int64
|
||||
tmp, err = strconv.ParseInt(src, 10, 64)
|
||||
*v = int64(tmp)
|
||||
case *uint:
|
||||
var tmp uint64
|
||||
// this is a cheat, we only know uint is at least 32 bits
|
||||
// but we have to make a compromise here
|
||||
tmp, err = strconv.ParseUint(src, 10, 32)
|
||||
*v = uint(tmp)
|
||||
case *uint8:
|
||||
var tmp uint64
|
||||
tmp, err = strconv.ParseUint(src, 10, 8)
|
||||
*v = uint8(tmp)
|
||||
case *uint16:
|
||||
var tmp uint64
|
||||
tmp, err = strconv.ParseUint(src, 10, 16)
|
||||
*v = uint16(tmp)
|
||||
case *uint32:
|
||||
var tmp uint64
|
||||
tmp, err = strconv.ParseUint(src, 10, 32)
|
||||
*v = uint32(tmp)
|
||||
case *uint64:
|
||||
var tmp uint64
|
||||
tmp, err = strconv.ParseUint(src, 10, 64)
|
||||
*v = uint64(tmp)
|
||||
// hmm, collides with uint8
|
||||
// case *byte:
|
||||
// tmp := []byte(src)
|
||||
// if len(tmp) == 1 {
|
||||
// *v = tmp[0]
|
||||
// } else {
|
||||
// err = fmt.Errorf("Cannot convert string %q to byte, length: %d", src, len(tmp))
|
||||
// }
|
||||
// hmm, collides with int32
|
||||
// case *rune:
|
||||
// tmp := []rune(src)
|
||||
// if len(tmp) == 1 {
|
||||
// *v = tmp[0]
|
||||
// } else {
|
||||
// err = fmt.Errorf("Cannot convert string %q to rune, lengt: %d", src, len(tmp))
|
||||
// }
|
||||
case *float32:
|
||||
var tmp float64
|
||||
tmp, err = strconv.ParseFloat(src, 32)
|
||||
*v = float32(tmp)
|
||||
case *float64:
|
||||
var tmp float64
|
||||
tmp, err = strconv.ParseFloat(src, 64)
|
||||
*v = float64(tmp)
|
||||
default:
|
||||
err = fmt.Errorf("Cannot convert string %q to type %T", src, dst)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
cat <<EOM
|
||||
str1: d3str1val1
|
||||
arr1:
|
||||
- d3arr1val1
|
||||
- d3arr1val2
|
||||
map1:
|
||||
key2: d3map1val2
|
||||
key3: d3map1val3
|
||||
int1: 333
|
||||
float1: 3.33
|
||||
bool1: true
|
||||
EOM
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
str1: d3str1val1
|
||||
arr1:
|
||||
- d3arr1val1
|
||||
- d3arr1val2
|
||||
map1:
|
||||
key2: d3map1val2
|
||||
key3: d3map1val3
|
||||
int1: 333
|
||||
float1: 3.33
|
||||
bool1: true
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
str1: d3str1val1
|
||||
arr1:
|
||||
- d3arr1val1
|
||||
- d3arr1val2
|
||||
map1:
|
||||
key2: d3map1val2
|
||||
key3: d3map1val3
|
||||
int1: 333
|
||||
float1: 3.33
|
||||
bool1: true
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
str1: d3str1val1
|
||||
arr1:
|
||||
- d3arr1val1
|
||||
- d3arr1val2
|
||||
map1:
|
||||
key2: d3map1val2
|
||||
key3: d3map1val3
|
||||
int1: 333
|
||||
float1: 3.33
|
||||
bool1: true
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
cat <<EOM
|
||||
str1: d2str1val1
|
||||
arr1:
|
||||
- d2arr1val1
|
||||
- d2arr1val2
|
||||
map1:
|
||||
key1: d2map1val1
|
||||
key2: d2map1val2
|
||||
int1: 222
|
||||
float1: 2.22
|
||||
bool1: false
|
||||
EOM
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
str1: d2str1val1
|
||||
arr1:
|
||||
- d2arr1val1
|
||||
- d2arr1val2
|
||||
map1:
|
||||
key1: d2map1val1
|
||||
key2: d2map1val2
|
||||
int1: 222
|
||||
float1: 2.22
|
||||
bool1: false
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
config:
|
||||
overwrite:
|
||||
- str1
|
||||
- arr1
|
||||
- bool1
|
||||
str1: d2str1val1
|
||||
arr1:
|
||||
- d2arr1val1
|
||||
- d2arr1val2
|
||||
map1:
|
||||
key1: d2map1val1
|
||||
key2: d2map1val2
|
||||
int1: 222
|
||||
float1: 2.22
|
||||
bool1: false
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
config:
|
||||
stop: true
|
||||
str1: d2str1val1
|
||||
arr1:
|
||||
- d2arr1val1
|
||||
- d2arr1val2
|
||||
map1:
|
||||
key1: d2map1val1
|
||||
key2: d2map1val2
|
||||
int1: 222
|
||||
float1: 2.22
|
||||
bool1: false
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
cat <<EOM
|
||||
str1: d1str1val1
|
||||
arr1:
|
||||
- d1arr1val1
|
||||
- d1arr1val2
|
||||
map1:
|
||||
key0: d1map1val0
|
||||
key1: d1map1val1
|
||||
int1: 111
|
||||
float1: 1.11
|
||||
bool1: true
|
||||
EOM
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
str1: d1str1val1
|
||||
arr1:
|
||||
- d1arr1val1
|
||||
- d1arr1val2
|
||||
map1:
|
||||
key0: d1map1val0
|
||||
key1: d1map1val1
|
||||
int1: 111
|
||||
float1: 1.11
|
||||
bool1: true
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
config:
|
||||
overwrite:
|
||||
- map1
|
||||
- int1
|
||||
- float1
|
||||
str1: d1str1val1
|
||||
arr1:
|
||||
- d1arr1val1
|
||||
- d1arr1val2
|
||||
map1:
|
||||
key0: d1map1val0
|
||||
key1: d1map1val1
|
||||
int1: 111
|
||||
float1: 1.11
|
||||
bool1: true
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
str1: d1str1val1
|
||||
arr1:
|
||||
- d1arr1val1
|
||||
- d1arr1val2
|
||||
map1:
|
||||
key0: d1map1val0
|
||||
key1: d1map1val1
|
||||
int1: 111
|
||||
float1: 1.11
|
||||
bool1: true
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package figtree
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestOptionsEnv(t *testing.T) {
|
||||
opts := TestOptions{}
|
||||
os.Chdir("d1")
|
||||
defer os.Chdir("..")
|
||||
|
||||
StringifyValue = true
|
||||
defer func() {
|
||||
StringifyValue = false
|
||||
}()
|
||||
|
||||
os.Clearenv()
|
||||
err := LoadAllConfigs("figtree.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
|
||||
got := []string{}
|
||||
for _, env := range os.Environ() {
|
||||
if strings.HasPrefix(env, "FIGTREE_") {
|
||||
got = append(got, env)
|
||||
}
|
||||
}
|
||||
|
||||
sort.StringSlice(got).Sort()
|
||||
|
||||
expected := []string{
|
||||
"FIGTREE_ARRAY_1=[\"d1arr1val1\",\"d1arr1val2\"]",
|
||||
"FIGTREE_BOOL_1=true",
|
||||
"FIGTREE_FLOAT_1=1.11",
|
||||
"FIGTREE_INT_1=111",
|
||||
"FIGTREE_MAP_1={\"key0\":\"d1map1val0\",\"key1\":\"d1map1val1\"}",
|
||||
"FIGTREE_STRING_1=d1str1val1",
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, got)
|
||||
}
|
||||
|
||||
func TestOptionsNamedEnv(t *testing.T) {
|
||||
opts := TestOptions{}
|
||||
os.Chdir("d1")
|
||||
defer os.Chdir("..")
|
||||
|
||||
StringifyValue = true
|
||||
defer func() {
|
||||
StringifyValue = false
|
||||
}()
|
||||
|
||||
os.Clearenv()
|
||||
fig := NewFigTree()
|
||||
fig.EnvPrefix = "TEST"
|
||||
err := fig.LoadAllConfigs("figtree.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
|
||||
got := []string{}
|
||||
for _, env := range os.Environ() {
|
||||
if strings.HasPrefix(env, "FIGTREE_") || strings.HasPrefix(env, "TEST_") {
|
||||
got = append(got, env)
|
||||
}
|
||||
}
|
||||
|
||||
sort.StringSlice(got).Sort()
|
||||
|
||||
expected := []string{
|
||||
"TEST_ARRAY_1=[\"d1arr1val1\",\"d1arr1val2\"]",
|
||||
"TEST_BOOL_1=true",
|
||||
"TEST_FLOAT_1=1.11",
|
||||
"TEST_INT_1=111",
|
||||
"TEST_MAP_1={\"key0\":\"d1map1val0\",\"key1\":\"d1map1val1\"}",
|
||||
"TEST_STRING_1=d1str1val1",
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, got)
|
||||
}
|
||||
|
||||
func TestBuiltinEnv(t *testing.T) {
|
||||
opts := TestBuiltin{}
|
||||
os.Chdir("d1")
|
||||
defer os.Chdir("..")
|
||||
|
||||
os.Clearenv()
|
||||
err := LoadAllConfigs("figtree.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
|
||||
got := []string{}
|
||||
for _, env := range os.Environ() {
|
||||
if strings.HasPrefix(env, "FIGTREE_") {
|
||||
got = append(got, env)
|
||||
}
|
||||
}
|
||||
|
||||
sort.StringSlice(got).Sort()
|
||||
|
||||
expected := []string{
|
||||
"FIGTREE_ARRAY_1=[\"d1arr1val1\",\"d1arr1val2\"]",
|
||||
"FIGTREE_BOOL_1=true",
|
||||
"FIGTREE_FLOAT_1=1.11",
|
||||
"FIGTREE_INT_1=111",
|
||||
"FIGTREE_LEAVE_EMPTY=",
|
||||
"FIGTREE_MAP_1={\"key0\":\"d1map1val0\",\"key1\":\"d1map1val1\"}",
|
||||
"FIGTREE_STRING_1=d1str1val1",
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, got)
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
package figtree
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestOptionsExecConfigD3(t *testing.T) {
|
||||
opts := TestOptions{}
|
||||
os.Chdir("d1/d2/d3")
|
||||
defer os.Chdir("../../..")
|
||||
|
||||
arr1 := []StringOption{}
|
||||
arr1 = append(arr1, StringOption{"exec.yml", true, "d3arr1val1"})
|
||||
arr1 = append(arr1, StringOption{"exec.yml", true, "d3arr1val2"})
|
||||
arr1 = append(arr1, StringOption{"../exec.yml", true, "d2arr1val1"})
|
||||
arr1 = append(arr1, StringOption{"../exec.yml", true, "d2arr1val2"})
|
||||
arr1 = append(arr1, StringOption{"../../exec.yml", true, "d1arr1val1"})
|
||||
arr1 = append(arr1, StringOption{"../../exec.yml", true, "d1arr1val2"})
|
||||
|
||||
expected := TestOptions{
|
||||
String1: StringOption{"exec.yml", true, "d3str1val1"},
|
||||
LeaveEmpty: StringOption{},
|
||||
Array1: arr1,
|
||||
Map1: map[string]StringOption{
|
||||
"key0": StringOption{"../../exec.yml", true, "d1map1val0"},
|
||||
"key1": StringOption{"../exec.yml", true, "d2map1val1"},
|
||||
"key2": StringOption{"exec.yml", true, "d3map1val2"},
|
||||
"key3": StringOption{"exec.yml", true, "d3map1val3"},
|
||||
},
|
||||
Int1: IntOption{"exec.yml", true, 333},
|
||||
Float1: Float32Option{"exec.yml", true, 3.33},
|
||||
Bool1: BoolOption{"exec.yml", true, true},
|
||||
}
|
||||
|
||||
err := LoadAllConfigs("exec.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
assert.Exactly(t, expected, opts)
|
||||
}
|
||||
|
||||
func TestOptionsExecConfigD2(t *testing.T) {
|
||||
opts := TestOptions{}
|
||||
os.Chdir("d1/d2")
|
||||
defer os.Chdir("../..")
|
||||
|
||||
arr1 := []StringOption{}
|
||||
arr1 = append(arr1, StringOption{"exec.yml", true, "d2arr1val1"})
|
||||
arr1 = append(arr1, StringOption{"exec.yml", true, "d2arr1val2"})
|
||||
arr1 = append(arr1, StringOption{"../exec.yml", true, "d1arr1val1"})
|
||||
arr1 = append(arr1, StringOption{"../exec.yml", true, "d1arr1val2"})
|
||||
|
||||
expected := TestOptions{
|
||||
String1: StringOption{"exec.yml", true, "d2str1val1"},
|
||||
LeaveEmpty: StringOption{},
|
||||
Array1: arr1,
|
||||
Map1: map[string]StringOption{
|
||||
"key0": StringOption{"../exec.yml", true, "d1map1val0"},
|
||||
"key1": StringOption{"exec.yml", true, "d2map1val1"},
|
||||
"key2": StringOption{"exec.yml", true, "d2map1val2"},
|
||||
},
|
||||
Int1: IntOption{"exec.yml", true, 222},
|
||||
Float1: Float32Option{"exec.yml", true, 2.22},
|
||||
Bool1: BoolOption{"exec.yml", true, false},
|
||||
}
|
||||
|
||||
err := LoadAllConfigs("exec.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
assert.Exactly(t, expected, opts)
|
||||
}
|
||||
|
||||
func TestOptionsExecConfigD1(t *testing.T) {
|
||||
opts := TestOptions{}
|
||||
os.Chdir("d1")
|
||||
defer os.Chdir("..")
|
||||
|
||||
arr1 := []StringOption{}
|
||||
arr1 = append(arr1, StringOption{"exec.yml", true, "d1arr1val1"})
|
||||
arr1 = append(arr1, StringOption{"exec.yml", true, "d1arr1val2"})
|
||||
|
||||
expected := TestOptions{
|
||||
String1: StringOption{"exec.yml", true, "d1str1val1"},
|
||||
LeaveEmpty: StringOption{},
|
||||
Array1: arr1,
|
||||
Map1: map[string]StringOption{
|
||||
"key0": StringOption{"exec.yml", true, "d1map1val0"},
|
||||
"key1": StringOption{"exec.yml", true, "d1map1val1"},
|
||||
},
|
||||
Int1: IntOption{"exec.yml", true, 111},
|
||||
Float1: Float32Option{"exec.yml", true, 1.11},
|
||||
Bool1: BoolOption{"exec.yml", true, true},
|
||||
}
|
||||
|
||||
err := LoadAllConfigs("exec.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
assert.Exactly(t, expected, opts)
|
||||
}
|
||||
|
||||
func TestBuiltinExecConfigD3(t *testing.T) {
|
||||
opts := TestBuiltin{}
|
||||
os.Chdir("d1/d2/d3")
|
||||
defer os.Chdir("../../..")
|
||||
|
||||
arr1 := []string{}
|
||||
arr1 = append(arr1, "d3arr1val1")
|
||||
arr1 = append(arr1, "d3arr1val2")
|
||||
arr1 = append(arr1, "d2arr1val1")
|
||||
arr1 = append(arr1, "d2arr1val2")
|
||||
arr1 = append(arr1, "d1arr1val1")
|
||||
arr1 = append(arr1, "d1arr1val2")
|
||||
|
||||
expected := TestBuiltin{
|
||||
String1: "d3str1val1",
|
||||
LeaveEmpty: "",
|
||||
Array1: arr1,
|
||||
Map1: map[string]string{
|
||||
"key0": "d1map1val0",
|
||||
"key1": "d2map1val1",
|
||||
"key2": "d3map1val2",
|
||||
"key3": "d3map1val3",
|
||||
},
|
||||
Int1: 333,
|
||||
Float1: 3.33,
|
||||
Bool1: true,
|
||||
}
|
||||
|
||||
err := LoadAllConfigs("exec.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
assert.Exactly(t, expected, opts)
|
||||
}
|
||||
|
||||
func TestBuiltinExecConfigD2(t *testing.T) {
|
||||
opts := TestBuiltin{}
|
||||
os.Chdir("d1/d2")
|
||||
defer os.Chdir("../..")
|
||||
|
||||
arr1 := []string{}
|
||||
arr1 = append(arr1, "d2arr1val1")
|
||||
arr1 = append(arr1, "d2arr1val2")
|
||||
arr1 = append(arr1, "d1arr1val1")
|
||||
arr1 = append(arr1, "d1arr1val2")
|
||||
|
||||
expected := TestBuiltin{
|
||||
String1: "d2str1val1",
|
||||
LeaveEmpty: "",
|
||||
Array1: arr1,
|
||||
Map1: map[string]string{
|
||||
"key0": "d1map1val0",
|
||||
"key1": "d2map1val1",
|
||||
"key2": "d2map1val2",
|
||||
},
|
||||
Int1: 222,
|
||||
Float1: 2.22,
|
||||
// note this will be true from d1/exec.yml since the
|
||||
// d1/d2/exec.yml set it to false which is a zero value
|
||||
Bool1: true,
|
||||
}
|
||||
|
||||
err := LoadAllConfigs("exec.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
assert.Exactly(t, expected, opts)
|
||||
}
|
||||
|
||||
func TestBuiltinExecConfigD1(t *testing.T) {
|
||||
opts := TestBuiltin{}
|
||||
os.Chdir("d1")
|
||||
defer os.Chdir("..")
|
||||
|
||||
arr1 := []string{}
|
||||
arr1 = append(arr1, "d1arr1val1")
|
||||
arr1 = append(arr1, "d1arr1val2")
|
||||
|
||||
expected := TestBuiltin{
|
||||
String1: "d1str1val1",
|
||||
LeaveEmpty: "",
|
||||
Array1: arr1,
|
||||
Map1: map[string]string{
|
||||
"key0": "d1map1val0",
|
||||
"key1": "d1map1val1",
|
||||
},
|
||||
Int1: 111,
|
||||
Float1: 1.11,
|
||||
Bool1: true,
|
||||
}
|
||||
|
||||
err := LoadAllConfigs("exec.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
assert.Exactly(t, expected, opts)
|
||||
}
|
||||
+394
@@ -0,0 +1,394 @@
|
||||
package figtree
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/fatih/camelcase"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
yaml "gopkg.in/coryb/yaml.v2"
|
||||
logging "gopkg.in/op/go-logging.v1"
|
||||
)
|
||||
|
||||
var log = logging.MustGetLogger("figtree")
|
||||
|
||||
type FigTree struct {
|
||||
Defaults interface{}
|
||||
EnvPrefix string
|
||||
stop bool
|
||||
}
|
||||
|
||||
func NewFigTree() *FigTree {
|
||||
return &FigTree{
|
||||
EnvPrefix: "FIGTREE",
|
||||
}
|
||||
}
|
||||
|
||||
func LoadAllConfigs(configFile string, options interface{}) error {
|
||||
return NewFigTree().LoadAllConfigs(configFile, options)
|
||||
}
|
||||
|
||||
func LoadConfig(configFile string, options interface{}) error {
|
||||
return NewFigTree().LoadConfig(configFile, options)
|
||||
}
|
||||
|
||||
func (f *FigTree) LoadAllConfigs(configFile string, options interface{}) error {
|
||||
// reset from any previous config parsing runs
|
||||
f.stop = false
|
||||
// assert options is a pointer
|
||||
|
||||
paths := FindParentPaths(configFile)
|
||||
paths = append([]string{fmt.Sprintf("/etc/%s", configFile)}, paths...)
|
||||
|
||||
// iterate paths in reverse
|
||||
for i := len(paths) - 1; i >= 0; i-- {
|
||||
file := paths[i]
|
||||
err := f.LoadConfig(file, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f.stop {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// apply defaults at the end to set any undefined fields
|
||||
if f.Defaults != nil {
|
||||
m := &merger{sourceFile: "default"}
|
||||
m.mergeStructs(
|
||||
reflect.ValueOf(options),
|
||||
reflect.ValueOf(f.Defaults),
|
||||
)
|
||||
f.populateEnv(options)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FigTree) LoadConfig(file string, options interface{}) (err error) {
|
||||
f.populateEnv(options)
|
||||
basePath, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rel, err := filepath.Rel(basePath, file)
|
||||
if err != nil {
|
||||
rel = file
|
||||
}
|
||||
m := &merger{sourceFile: rel}
|
||||
type tmpOpts struct {
|
||||
Config ConfigOptions
|
||||
}
|
||||
|
||||
if stat, err := os.Stat(file); err == nil {
|
||||
tmp := reflect.New(reflect.ValueOf(options).Elem().Type()).Interface()
|
||||
if stat.Mode()&0111 == 0 {
|
||||
log.Debugf("Loading config %s", file)
|
||||
// first parse out any config processing option
|
||||
if data, err := ioutil.ReadFile(file); err == nil {
|
||||
err := yaml.Unmarshal(data, m)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("Unable to parse %s", file))
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(data, tmp)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("Unable to parse %s", file))
|
||||
}
|
||||
// if reflect.ValueOf(tmp).Kind() == reflect.Map {
|
||||
// tmp, _ = util.YamlFixup(tmp)
|
||||
// }
|
||||
}
|
||||
} else {
|
||||
log.Debugf("Found Executable Config file: %s", file)
|
||||
// it is executable, so run it and try to parse the output
|
||||
cmd := exec.Command(file)
|
||||
stdout := bytes.NewBufferString("")
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = bytes.NewBufferString("")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("%s is exectuable, but it failed to execute:\n%s", file, cmd.Stderr))
|
||||
}
|
||||
// first parse out any config processing option
|
||||
err := yaml.Unmarshal(stdout.Bytes(), m)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("Unable to parse %s", file))
|
||||
}
|
||||
err = yaml.Unmarshal(stdout.Bytes(), tmp)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("Failed to parse STDOUT from executable config file %s", file))
|
||||
}
|
||||
}
|
||||
m.setSource(reflect.ValueOf(tmp))
|
||||
m.mergeStructs(
|
||||
reflect.ValueOf(options),
|
||||
reflect.ValueOf(tmp),
|
||||
)
|
||||
if m.Config.Stop {
|
||||
f.stop = true
|
||||
return nil
|
||||
}
|
||||
f.populateEnv(options)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ConfigOptions struct {
|
||||
Overwrite []string `json:"overwrite,omitempty" yaml:"overwrite,omitempty"`
|
||||
Stop bool `json:"stop,omitempty" yaml:"stop,omitempty"`
|
||||
// Merge bool `json:"merge,omitempty" yaml:"merge,omitempty"`
|
||||
}
|
||||
|
||||
type merger struct {
|
||||
sourceFile string
|
||||
Config ConfigOptions `json:"config,omitempty" yaml:"config,omitempty"`
|
||||
}
|
||||
|
||||
func yamlFieldName(sf reflect.StructField) string {
|
||||
if tag, ok := sf.Tag.Lookup("yaml"); ok {
|
||||
// with yaml:"foobar,omitempty"
|
||||
// we just want to the "foobar" part
|
||||
parts := strings.Split(tag, ",")
|
||||
return parts[0]
|
||||
}
|
||||
return sf.Name
|
||||
}
|
||||
|
||||
func (m *merger) mustOverwrite(name string) bool {
|
||||
for _, prop := range m.Config.Overwrite {
|
||||
if name == prop {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isEmpty(v reflect.Value) bool {
|
||||
return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())
|
||||
}
|
||||
|
||||
func isSame(v1, v2 reflect.Value) bool {
|
||||
return reflect.DeepEqual(v1.Interface(), v2.Interface())
|
||||
}
|
||||
|
||||
// recursively set the Source attribute of the Options
|
||||
func (m *merger) setSource(v reflect.Value) {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Map:
|
||||
for _, key := range v.MapKeys() {
|
||||
keyval := v.MapIndex(key)
|
||||
if keyval.Kind() == reflect.Struct && keyval.FieldByName("Source").IsValid() {
|
||||
// map values are immutable, so we need to copy the value
|
||||
// update the value, then re-insert the value to the map
|
||||
newval := reflect.New(keyval.Type())
|
||||
newval.Elem().Set(keyval)
|
||||
m.setSource(newval)
|
||||
v.SetMapIndex(key, newval.Elem())
|
||||
}
|
||||
}
|
||||
case reflect.Struct:
|
||||
if v.CanAddr() {
|
||||
if option, ok := v.Addr().Interface().(Option); ok {
|
||||
if option.IsDefined() {
|
||||
option.SetSource(m.sourceFile)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
structField := v.Type().Field(i)
|
||||
// PkgPath is empty for upper case (exported) field names.
|
||||
if structField.PkgPath != "" {
|
||||
// unexported field, skipping
|
||||
continue
|
||||
}
|
||||
m.setSource(v.Field(i))
|
||||
}
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
m.setSource(v.Index(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *merger) mergeStructs(ov, nv reflect.Value) {
|
||||
if ov.Kind() == reflect.Ptr {
|
||||
ov = ov.Elem()
|
||||
}
|
||||
if nv.Kind() == reflect.Ptr {
|
||||
nv = nv.Elem()
|
||||
}
|
||||
if ov.Kind() == reflect.Map && nv.Kind() == reflect.Map {
|
||||
m.mergeMaps(ov, nv)
|
||||
return
|
||||
}
|
||||
if !ov.IsValid() || !nv.IsValid() {
|
||||
return
|
||||
}
|
||||
for i := 0; i < nv.NumField(); i++ {
|
||||
ovStructField := ov.Type().Field(i)
|
||||
nvStructField := nv.Type().Field(i)
|
||||
// PkgPath is empty for upper case (exported) field names.
|
||||
if ovStructField.PkgPath != "" || nvStructField.PkgPath != "" {
|
||||
// unexported field, skipping
|
||||
continue
|
||||
}
|
||||
fieldName := yamlFieldName(ovStructField)
|
||||
|
||||
if (isEmpty(ov.Field(i)) || m.mustOverwrite(fieldName)) && !isSame(ov.Field(i), nv.Field(i)) {
|
||||
log.Debugf("Setting %s to %#v", nv.Type().Field(i).Name, nv.Field(i).Interface())
|
||||
ov.Field(i).Set(nv.Field(i))
|
||||
} else {
|
||||
switch ov.Field(i).Kind() {
|
||||
case reflect.Map:
|
||||
if nv.Field(i).Len() > 0 {
|
||||
log.Debugf("Merging: %v with %v", ov.Field(i), nv.Field(i))
|
||||
m.mergeMaps(ov.Field(i), nv.Field(i))
|
||||
}
|
||||
case reflect.Slice:
|
||||
if nv.Field(i).Len() > 0 {
|
||||
log.Debugf("Merging: %v with %v", ov.Field(i), nv.Field(i))
|
||||
if ov.Field(i).CanSet() {
|
||||
if ov.Field(i).Len() == 0 {
|
||||
ov.Field(i).Set(nv.Field(i))
|
||||
} else {
|
||||
log.Debugf("Merging: %v with %v", ov.Field(i), nv.Field(i))
|
||||
ov.Field(i).Set(m.mergeArrays(ov.Field(i), nv.Field(i)))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
case reflect.Array:
|
||||
if nv.Field(i).Len() > 0 {
|
||||
log.Debugf("Merging: %v with %v", ov.Field(i), nv.Field(i))
|
||||
ov.Field(i).Set(m.mergeArrays(ov.Field(i), nv.Field(i)))
|
||||
}
|
||||
case reflect.Struct:
|
||||
// only merge structs if they are not an Option type:
|
||||
if _, ok := ov.Field(i).Addr().Interface().(Option); !ok {
|
||||
log.Debugf("Merging: %v with %v", ov.Field(i), nv.Field(i))
|
||||
m.mergeStructs(ov.Field(i), nv.Field(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *merger) mergeMaps(ov, nv reflect.Value) {
|
||||
for _, key := range nv.MapKeys() {
|
||||
if !ov.MapIndex(key).IsValid() {
|
||||
log.Debugf("Setting %v to %#v", key.Interface(), nv.MapIndex(key).Interface())
|
||||
ov.SetMapIndex(key, nv.MapIndex(key))
|
||||
} else {
|
||||
ovi := reflect.ValueOf(ov.MapIndex(key).Interface())
|
||||
nvi := reflect.ValueOf(nv.MapIndex(key).Interface())
|
||||
switch ovi.Kind() {
|
||||
case reflect.Map:
|
||||
log.Debugf("Merging: %v with %v", ovi.Interface(), nvi.Interface())
|
||||
m.mergeMaps(ovi, nvi)
|
||||
case reflect.Slice:
|
||||
log.Debugf("Merging: %v with %v", ovi.Interface(), nvi.Interface())
|
||||
ov.SetMapIndex(key, m.mergeArrays(ovi, nvi))
|
||||
case reflect.Array:
|
||||
log.Debugf("Merging: %v with %v", ovi.Interface(), nvi.Interface())
|
||||
ov.SetMapIndex(key, m.mergeArrays(ovi, nvi))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *merger) mergeArrays(ov, nv reflect.Value) reflect.Value {
|
||||
Outer:
|
||||
for ni := 0; ni < nv.Len(); ni++ {
|
||||
niv := nv.Index(ni)
|
||||
for oi := 0; oi < ov.Len(); oi++ {
|
||||
oiv := ov.Index(oi)
|
||||
if reflect.DeepEqual(niv.Interface(), oiv.Interface()) {
|
||||
continue Outer
|
||||
}
|
||||
}
|
||||
log.Debugf("Appending %v to %v", niv.Interface(), ov)
|
||||
ov = reflect.Append(ov, niv)
|
||||
}
|
||||
return ov
|
||||
}
|
||||
|
||||
func (f *FigTree) populateEnv(data interface{}) {
|
||||
options := reflect.ValueOf(data)
|
||||
if options.Kind() == reflect.Ptr {
|
||||
options = reflect.ValueOf(options.Elem().Interface())
|
||||
}
|
||||
if options.Kind() == reflect.Struct {
|
||||
for i := 0; i < options.NumField(); i++ {
|
||||
structField := options.Type().Field(i)
|
||||
// PkgPath is empty for upper case (exported) field names.
|
||||
if structField.PkgPath != "" {
|
||||
// unexported field, skipping
|
||||
continue
|
||||
}
|
||||
name := strings.Join(camelcase.Split(structField.Name), "_")
|
||||
envName := fmt.Sprintf("%s_%s", f.EnvPrefix, strings.ToUpper(name))
|
||||
|
||||
envName = strings.Map(func(r rune) rune {
|
||||
if unicode.IsDigit(r) || unicode.IsLetter(r) {
|
||||
return r
|
||||
}
|
||||
return '_'
|
||||
}, envName)
|
||||
var val string
|
||||
switch t := options.Field(i).Interface().(type) {
|
||||
case string:
|
||||
val = t
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, bool:
|
||||
val = fmt.Sprintf("%v", t)
|
||||
default:
|
||||
switch options.Field(i).Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||
if options.Field(i).IsNil() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if t == nil {
|
||||
continue
|
||||
}
|
||||
type definable interface {
|
||||
IsDefined() bool
|
||||
}
|
||||
if def, ok := t.(definable); ok {
|
||||
// skip fields that are not defined
|
||||
if !def.IsDefined() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
type gettable interface {
|
||||
GetValue() interface{}
|
||||
}
|
||||
if get, ok := t.(gettable); ok {
|
||||
val = fmt.Sprintf("%v", get.GetValue())
|
||||
} else {
|
||||
if b, err := json.Marshal(t); err == nil {
|
||||
val = strings.TrimSpace(string(b))
|
||||
if val == "null" {
|
||||
val = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
os.Setenv(envName, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
package figtree
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func init() {
|
||||
StringifyValue = false
|
||||
}
|
||||
|
||||
type TestOptions struct {
|
||||
String1 StringOption `json:"str1,omitempty" yaml:"str1,omitempty"`
|
||||
LeaveEmpty StringOption `json:"leave-empty,omitempty" yaml:"leave-empty,omitempty"`
|
||||
Array1 ListStringOption `json:"arr1,omitempty" yaml:"arr1,omitempty"`
|
||||
Map1 MapStringOption `json:"map1,omitempty" yaml:"map1,omitempty"`
|
||||
Int1 IntOption `json:"int1,omitempty" yaml:"int1,omitempty"`
|
||||
Float1 Float32Option `json:"float1,omitempty" yaml:"float1,omitempty"`
|
||||
Bool1 BoolOption `json:"bool1,omitempty" yaml:"bool1,omitempty"`
|
||||
}
|
||||
|
||||
type TestBuiltin struct {
|
||||
String1 string `yaml:"str1,omitempty"`
|
||||
LeaveEmpty string `yaml:"leave-empty,omitempty"`
|
||||
Array1 []string `yaml:"arr1,omitempty"`
|
||||
Map1 map[string]string `yaml:"map1,omitempty"`
|
||||
Int1 int `yaml:"int1,omitempty"`
|
||||
Float1 float32 `yaml:"float1,omitempty"`
|
||||
Bool1 bool `yaml:"bool1,omitempty"`
|
||||
}
|
||||
|
||||
func TestOptionsLoadConfigD3(t *testing.T) {
|
||||
opts := TestOptions{}
|
||||
os.Chdir("d1/d2/d3")
|
||||
defer os.Chdir("../../..")
|
||||
|
||||
arr1 := []StringOption{}
|
||||
arr1 = append(arr1, StringOption{"figtree.yml", true, "d3arr1val1"})
|
||||
arr1 = append(arr1, StringOption{"figtree.yml", true, "d3arr1val2"})
|
||||
arr1 = append(arr1, StringOption{"../figtree.yml", true, "d2arr1val1"})
|
||||
arr1 = append(arr1, StringOption{"../figtree.yml", true, "d2arr1val2"})
|
||||
arr1 = append(arr1, StringOption{"../../figtree.yml", true, "d1arr1val1"})
|
||||
arr1 = append(arr1, StringOption{"../../figtree.yml", true, "d1arr1val2"})
|
||||
|
||||
expected := TestOptions{
|
||||
String1: StringOption{"figtree.yml", true, "d3str1val1"},
|
||||
LeaveEmpty: StringOption{},
|
||||
Array1: arr1,
|
||||
Map1: map[string]StringOption{
|
||||
"key0": StringOption{"../../figtree.yml", true, "d1map1val0"},
|
||||
"key1": StringOption{"../figtree.yml", true, "d2map1val1"},
|
||||
"key2": StringOption{"figtree.yml", true, "d3map1val2"},
|
||||
"key3": StringOption{"figtree.yml", true, "d3map1val3"},
|
||||
},
|
||||
Int1: IntOption{"figtree.yml", true, 333},
|
||||
Float1: Float32Option{"figtree.yml", true, 3.33},
|
||||
Bool1: BoolOption{"figtree.yml", true, true},
|
||||
}
|
||||
|
||||
err := LoadAllConfigs("figtree.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
assert.Exactly(t, expected, opts)
|
||||
}
|
||||
|
||||
func TestOptionsLoadConfigD2(t *testing.T) {
|
||||
opts := TestOptions{}
|
||||
os.Chdir("d1/d2")
|
||||
defer os.Chdir("../..")
|
||||
|
||||
arr1 := []StringOption{}
|
||||
arr1 = append(arr1, StringOption{"figtree.yml", true, "d2arr1val1"})
|
||||
arr1 = append(arr1, StringOption{"figtree.yml", true, "d2arr1val2"})
|
||||
arr1 = append(arr1, StringOption{"../figtree.yml", true, "d1arr1val1"})
|
||||
arr1 = append(arr1, StringOption{"../figtree.yml", true, "d1arr1val2"})
|
||||
|
||||
expected := TestOptions{
|
||||
String1: StringOption{"figtree.yml", true, "d2str1val1"},
|
||||
LeaveEmpty: StringOption{},
|
||||
Array1: arr1,
|
||||
Map1: map[string]StringOption{
|
||||
"key0": StringOption{"../figtree.yml", true, "d1map1val0"},
|
||||
"key1": StringOption{"figtree.yml", true, "d2map1val1"},
|
||||
"key2": StringOption{"figtree.yml", true, "d2map1val2"},
|
||||
},
|
||||
Int1: IntOption{"figtree.yml", true, 222},
|
||||
Float1: Float32Option{"figtree.yml", true, 2.22},
|
||||
Bool1: BoolOption{"figtree.yml", true, false},
|
||||
}
|
||||
|
||||
err := LoadAllConfigs("figtree.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
assert.Exactly(t, expected, opts)
|
||||
}
|
||||
|
||||
func TestOptionsLoadConfigD1(t *testing.T) {
|
||||
opts := TestOptions{}
|
||||
os.Chdir("d1")
|
||||
defer os.Chdir("..")
|
||||
|
||||
arr1 := []StringOption{}
|
||||
arr1 = append(arr1, StringOption{"figtree.yml", true, "d1arr1val1"})
|
||||
arr1 = append(arr1, StringOption{"figtree.yml", true, "d1arr1val2"})
|
||||
|
||||
expected := TestOptions{
|
||||
String1: StringOption{"figtree.yml", true, "d1str1val1"},
|
||||
LeaveEmpty: StringOption{},
|
||||
Array1: arr1,
|
||||
Map1: map[string]StringOption{
|
||||
"key0": StringOption{"figtree.yml", true, "d1map1val0"},
|
||||
"key1": StringOption{"figtree.yml", true, "d1map1val1"},
|
||||
},
|
||||
Int1: IntOption{"figtree.yml", true, 111},
|
||||
Float1: Float32Option{"figtree.yml", true, 1.11},
|
||||
Bool1: BoolOption{"figtree.yml", true, true},
|
||||
}
|
||||
|
||||
err := LoadAllConfigs("figtree.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
assert.Exactly(t, expected, opts)
|
||||
}
|
||||
|
||||
func TestBuiltinLoadConfigD3(t *testing.T) {
|
||||
opts := TestBuiltin{}
|
||||
os.Chdir("d1/d2/d3")
|
||||
defer os.Chdir("../../..")
|
||||
|
||||
arr1 := []string{}
|
||||
arr1 = append(arr1, "d3arr1val1")
|
||||
arr1 = append(arr1, "d3arr1val2")
|
||||
arr1 = append(arr1, "d2arr1val1")
|
||||
arr1 = append(arr1, "d2arr1val2")
|
||||
arr1 = append(arr1, "d1arr1val1")
|
||||
arr1 = append(arr1, "d1arr1val2")
|
||||
|
||||
expected := TestBuiltin{
|
||||
String1: "d3str1val1",
|
||||
LeaveEmpty: "",
|
||||
Array1: arr1,
|
||||
Map1: map[string]string{
|
||||
"key0": "d1map1val0",
|
||||
"key1": "d2map1val1",
|
||||
"key2": "d3map1val2",
|
||||
"key3": "d3map1val3",
|
||||
},
|
||||
Int1: 333,
|
||||
Float1: 3.33,
|
||||
Bool1: true,
|
||||
}
|
||||
|
||||
err := LoadAllConfigs("figtree.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
assert.Exactly(t, expected, opts)
|
||||
}
|
||||
|
||||
func TestBuiltinLoadConfigD2(t *testing.T) {
|
||||
opts := TestBuiltin{}
|
||||
os.Chdir("d1/d2")
|
||||
defer os.Chdir("../..")
|
||||
|
||||
arr1 := []string{}
|
||||
arr1 = append(arr1, "d2arr1val1")
|
||||
arr1 = append(arr1, "d2arr1val2")
|
||||
arr1 = append(arr1, "d1arr1val1")
|
||||
arr1 = append(arr1, "d1arr1val2")
|
||||
|
||||
expected := TestBuiltin{
|
||||
String1: "d2str1val1",
|
||||
LeaveEmpty: "",
|
||||
Array1: arr1,
|
||||
Map1: map[string]string{
|
||||
"key0": "d1map1val0",
|
||||
"key1": "d2map1val1",
|
||||
"key2": "d2map1val2",
|
||||
},
|
||||
Int1: 222,
|
||||
Float1: 2.22,
|
||||
// note this will be true from d1/figtree.yml since the
|
||||
// d1/d2/figtree.yml set it to false which is a zero value
|
||||
Bool1: true,
|
||||
}
|
||||
|
||||
err := LoadAllConfigs("figtree.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
assert.Exactly(t, expected, opts)
|
||||
}
|
||||
|
||||
func TestBuiltinLoadConfigD1(t *testing.T) {
|
||||
opts := TestBuiltin{}
|
||||
os.Chdir("d1")
|
||||
defer os.Chdir("..")
|
||||
|
||||
arr1 := []string{}
|
||||
arr1 = append(arr1, "d1arr1val1")
|
||||
arr1 = append(arr1, "d1arr1val2")
|
||||
|
||||
expected := TestBuiltin{
|
||||
String1: "d1str1val1",
|
||||
LeaveEmpty: "",
|
||||
Array1: arr1,
|
||||
Map1: map[string]string{
|
||||
"key0": "d1map1val0",
|
||||
"key1": "d1map1val1",
|
||||
},
|
||||
Int1: 111,
|
||||
Float1: 1.11,
|
||||
Bool1: true,
|
||||
}
|
||||
|
||||
err := LoadAllConfigs("figtree.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
assert.Exactly(t, expected, opts)
|
||||
}
|
||||
+4390
File diff suppressed because it is too large
Load Diff
+36
@@ -0,0 +1,36 @@
|
||||
hash: 4c141f4247c76717823951388fcd50678e63195fa40b0d5bc7097d0267003052
|
||||
updated: 2017-07-06T23:09:17.970073816-07:00
|
||||
imports:
|
||||
- name: github.com/cheekybits/genny
|
||||
version: 9127e812e1e9e501ce899a18121d316ecb52e4ba
|
||||
subpackages:
|
||||
- generic
|
||||
- name: github.com/fatih/camelcase
|
||||
version: f6a740d52f961c60348ebb109adde9f4635d7540
|
||||
- name: github.com/pkg/errors
|
||||
version: 645ef00459ed84a119197bfb8d8205042c6df63d
|
||||
- name: gopkg.in/coryb/yaml.v2
|
||||
version: f284bc8aa3c31dfdda9cc7917c610398c49b3acd
|
||||
- name: gopkg.in/op/go-logging.v1
|
||||
version: b2cb9fa56473e98db8caba80237377e83fe44db5
|
||||
testImports:
|
||||
- name: github.com/alecthomas/template
|
||||
version: a0175ee3bccc567396460bf5acd36800cb10c49c
|
||||
subpackages:
|
||||
- parse
|
||||
- name: github.com/alecthomas/units
|
||||
version: 2efee857e7cfd4f3d0138cc3cbb1b4966962b93a
|
||||
- name: github.com/davecgh/go-spew
|
||||
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
|
||||
subpackages:
|
||||
- spew
|
||||
- name: github.com/pmezard/go-difflib
|
||||
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
|
||||
subpackages:
|
||||
- difflib
|
||||
- name: github.com/stretchr/testify
|
||||
version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0
|
||||
subpackages:
|
||||
- assert
|
||||
- name: gopkg.in/alecthomas/kingpin.v2
|
||||
version: 7f0871f2e17818990e4eed73f9b5c2f429501228
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package: github.com/coryb/figtree
|
||||
import:
|
||||
- package: github.com/cheekybits/genny
|
||||
subpackages:
|
||||
- generic
|
||||
- package: github.com/pkg/errors
|
||||
version: ^0.8.0
|
||||
- package: gopkg.in/coryb/yaml.v2
|
||||
- package: gopkg.in/op/go-logging.v1
|
||||
version: ^1.0.0
|
||||
- package: github.com/fatih/camelcase
|
||||
testImport:
|
||||
- package: github.com/stretchr/testify
|
||||
version: ^1.1.4
|
||||
subpackages:
|
||||
- assert
|
||||
- package: gopkg.in/alecthomas/kingpin.v2
|
||||
version: ^2.2.4
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package figtree
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
func TestCommandLine(t *testing.T) {
|
||||
type CommandLineOptions struct {
|
||||
Str1 StringOption `yaml:"str1,omitempty"`
|
||||
Int1 IntOption `yaml:"int1,omitempty"`
|
||||
Map1 MapStringOption `yaml:"map1,omitempty"`
|
||||
Arr1 ListStringOption `yaml:"arr1,omitempty"`
|
||||
}
|
||||
|
||||
opts := CommandLineOptions{}
|
||||
os.Chdir("d1/d2/d3")
|
||||
defer os.Chdir("../../..")
|
||||
|
||||
err := LoadAllConfigs("figtree.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
|
||||
app := kingpin.New("test", "testing")
|
||||
app.Flag("str1", "Str1").SetValue(&opts.Str1)
|
||||
app.Flag("int1", "Int1").SetValue(&opts.Int1)
|
||||
app.Flag("map1", "Map1").SetValue(&opts.Map1)
|
||||
app.Flag("arr1", "Arr1").SetValue(&opts.Arr1)
|
||||
_, err = app.Parse([]string{"--int1", "999", "--map1", "k1=v1", "--map1", "k2=v2", "--arr1", "v1", "--arr1", "v2"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
arr1 := ListStringOption{}
|
||||
arr1 = append(arr1, StringOption{"figtree.yml", true, "d3arr1val1"})
|
||||
arr1 = append(arr1, StringOption{"figtree.yml", true, "d3arr1val2"})
|
||||
arr1 = append(arr1, StringOption{"../figtree.yml", true, "d2arr1val1"})
|
||||
arr1 = append(arr1, StringOption{"../figtree.yml", true, "d2arr1val2"})
|
||||
arr1 = append(arr1, StringOption{"../../figtree.yml", true, "d1arr1val1"})
|
||||
arr1 = append(arr1, StringOption{"../../figtree.yml", true, "d1arr1val2"})
|
||||
arr1 = append(arr1, StringOption{"override", true, "v1"})
|
||||
arr1 = append(arr1, StringOption{"override", true, "v2"})
|
||||
|
||||
expected := CommandLineOptions{
|
||||
Str1: StringOption{"figtree.yml", true, "d3str1val1"},
|
||||
Int1: IntOption{"override", true, 999},
|
||||
Map1: map[string]StringOption{
|
||||
"key0": StringOption{"../../figtree.yml", true, "d1map1val0"},
|
||||
"key1": StringOption{"../figtree.yml", true, "d2map1val1"},
|
||||
"key2": StringOption{"figtree.yml", true, "d3map1val2"},
|
||||
"key3": StringOption{"figtree.yml", true, "d3map1val3"},
|
||||
"k1": StringOption{"override", true, "v1"},
|
||||
"k2": StringOption{"override", true, "v2"},
|
||||
},
|
||||
Arr1: arr1,
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, opts)
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package figtree
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
yaml "gopkg.in/coryb/yaml.v2"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestOptionsMarshalYAML(t *testing.T) {
|
||||
opts := TestOptions{}
|
||||
os.Chdir("d1/d2/d3")
|
||||
defer os.Chdir("../../..")
|
||||
err := LoadAllConfigs("figtree.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
|
||||
StringifyValue = true
|
||||
defer func() {
|
||||
StringifyValue = false
|
||||
}()
|
||||
got, err := yaml.Marshal(&opts)
|
||||
assert.Nil(t, err)
|
||||
|
||||
expected := `str1: d3str1val1
|
||||
arr1:
|
||||
- d3arr1val1
|
||||
- d3arr1val2
|
||||
- d2arr1val1
|
||||
- d2arr1val2
|
||||
- d1arr1val1
|
||||
- d1arr1val2
|
||||
map1:
|
||||
key0: d1map1val0
|
||||
key1: d2map1val1
|
||||
key2: d3map1val2
|
||||
key3: d3map1val3
|
||||
int1: 333
|
||||
float1: 3.33
|
||||
bool1: true
|
||||
`
|
||||
assert.Equal(t, expected, string(got))
|
||||
}
|
||||
|
||||
func TestOptionsMarshalJSON(t *testing.T) {
|
||||
opts := TestOptions{}
|
||||
os.Chdir("d1/d2/d3")
|
||||
defer os.Chdir("../../..")
|
||||
err := LoadAllConfigs("figtree.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
|
||||
StringifyValue = true
|
||||
defer func() {
|
||||
StringifyValue = false
|
||||
}()
|
||||
got, err := json.Marshal(&opts)
|
||||
assert.Nil(t, err)
|
||||
// note that "leave-empty" is serialized even though "omitempty" tag is set
|
||||
// this is because json always assumes structs are not empty and there
|
||||
// is no interface to override this behavior
|
||||
expected := `{"str1":"d3str1val1","leave-empty":"","arr1":["d3arr1val1","d3arr1val2","d2arr1val1","d2arr1val2","d1arr1val1","d1arr1val2"],"map1":{"key0":"d1map1val0","key1":"d2map1val1","key2":"d3map1val2","key3":"d3map1val3"},"int1":333,"float1":3.33,"bool1":true}`
|
||||
assert.Equal(t, expected, string(got))
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package figtree
|
||||
|
||||
import "regexp"
|
||||
|
||||
type Option interface {
|
||||
IsDefined() bool
|
||||
GetValue() interface{}
|
||||
SetValue(interface{}) error
|
||||
SetSource(string)
|
||||
}
|
||||
|
||||
var StringifyValue = true
|
||||
|
||||
// used in option parsing for map types Set routines
|
||||
var stringMapRegex = regexp.MustCompile("[:=]")
|
||||
|
||||
// IsBoolFlag is required by kingpin interface to determine if
|
||||
// this variable requires a value
|
||||
func (b BoolOption) IsBoolFlag() bool {
|
||||
return true
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
package figtree
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func init() {
|
||||
StringifyValue = false
|
||||
}
|
||||
|
||||
func TestOptionsOverwriteConfigD3(t *testing.T) {
|
||||
opts := TestOptions{}
|
||||
os.Chdir("d1/d2/d3")
|
||||
defer os.Chdir("../../..")
|
||||
|
||||
arr1 := []StringOption{}
|
||||
arr1 = append(arr1, StringOption{"../overwrite.yml", true, "d2arr1val1"})
|
||||
arr1 = append(arr1, StringOption{"../overwrite.yml", true, "d2arr1val2"})
|
||||
arr1 = append(arr1, StringOption{"../../overwrite.yml", true, "d1arr1val1"})
|
||||
arr1 = append(arr1, StringOption{"../../overwrite.yml", true, "d1arr1val2"})
|
||||
|
||||
expected := TestOptions{
|
||||
String1: StringOption{"../overwrite.yml", true, "d2str1val1"},
|
||||
LeaveEmpty: StringOption{},
|
||||
Array1: arr1,
|
||||
Map1: map[string]StringOption{
|
||||
"key0": StringOption{"../../overwrite.yml", true, "d1map1val0"},
|
||||
"key1": StringOption{"../../overwrite.yml", true, "d1map1val1"},
|
||||
},
|
||||
Int1: IntOption{"../../overwrite.yml", true, 111},
|
||||
Float1: Float32Option{"../../overwrite.yml", true, 1.11},
|
||||
Bool1: BoolOption{"../overwrite.yml", true, false},
|
||||
}
|
||||
|
||||
err := LoadAllConfigs("overwrite.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
assert.Exactly(t, expected, opts)
|
||||
}
|
||||
|
||||
func TestOptionsOverwriteConfigD2(t *testing.T) {
|
||||
opts := TestOptions{}
|
||||
os.Chdir("d1/d2")
|
||||
defer os.Chdir("../..")
|
||||
|
||||
arr1 := []StringOption{}
|
||||
arr1 = append(arr1, StringOption{"overwrite.yml", true, "d2arr1val1"})
|
||||
arr1 = append(arr1, StringOption{"overwrite.yml", true, "d2arr1val2"})
|
||||
arr1 = append(arr1, StringOption{"../overwrite.yml", true, "d1arr1val1"})
|
||||
arr1 = append(arr1, StringOption{"../overwrite.yml", true, "d1arr1val2"})
|
||||
|
||||
expected := TestOptions{
|
||||
String1: StringOption{"overwrite.yml", true, "d2str1val1"},
|
||||
LeaveEmpty: StringOption{},
|
||||
Array1: arr1,
|
||||
Map1: map[string]StringOption{
|
||||
"key0": StringOption{"../overwrite.yml", true, "d1map1val0"},
|
||||
"key1": StringOption{"../overwrite.yml", true, "d1map1val1"},
|
||||
},
|
||||
Int1: IntOption{"../overwrite.yml", true, 111},
|
||||
Float1: Float32Option{"../overwrite.yml", true, 1.11},
|
||||
Bool1: BoolOption{"overwrite.yml", true, false},
|
||||
}
|
||||
|
||||
err := LoadAllConfigs("overwrite.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
assert.Exactly(t, expected, opts)
|
||||
}
|
||||
|
||||
func TestBuiltinOverwriteConfigD3(t *testing.T) {
|
||||
opts := TestBuiltin{}
|
||||
os.Chdir("d1/d2/d3")
|
||||
defer os.Chdir("../../..")
|
||||
|
||||
arr1 := []string{}
|
||||
arr1 = append(arr1, "d2arr1val1")
|
||||
arr1 = append(arr1, "d2arr1val2")
|
||||
arr1 = append(arr1, "d1arr1val1")
|
||||
arr1 = append(arr1, "d1arr1val2")
|
||||
|
||||
expected := TestBuiltin{
|
||||
String1: "d2str1val1",
|
||||
LeaveEmpty: "",
|
||||
Array1: arr1,
|
||||
Map1: map[string]string{
|
||||
"key0": "d1map1val0",
|
||||
"key1": "d1map1val1",
|
||||
},
|
||||
Int1: 111,
|
||||
Float1: 1.11,
|
||||
Bool1: true,
|
||||
}
|
||||
|
||||
err := LoadAllConfigs("overwrite.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
assert.Exactly(t, expected, opts)
|
||||
}
|
||||
|
||||
func TestBuiltinOverwriteConfigD2(t *testing.T) {
|
||||
opts := TestBuiltin{}
|
||||
os.Chdir("d1/d2")
|
||||
defer os.Chdir("../..")
|
||||
|
||||
arr1 := []string{}
|
||||
arr1 = append(arr1, "d2arr1val1")
|
||||
arr1 = append(arr1, "d2arr1val2")
|
||||
arr1 = append(arr1, "d1arr1val1")
|
||||
arr1 = append(arr1, "d1arr1val2")
|
||||
|
||||
expected := TestBuiltin{
|
||||
String1: "d2str1val1",
|
||||
LeaveEmpty: "",
|
||||
Array1: arr1,
|
||||
Map1: map[string]string{
|
||||
"key0": "d1map1val0",
|
||||
"key1": "d1map1val1",
|
||||
},
|
||||
Int1: 111,
|
||||
Float1: 1.11,
|
||||
// note this will be true from d1/overwrite.yml since the
|
||||
// d1/d2/overwrite.yml set it to false which is a zero value
|
||||
Bool1: true,
|
||||
}
|
||||
|
||||
err := LoadAllConfigs("overwrite.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
assert.Exactly(t, expected, opts)
|
||||
}
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
//go:generate genny -in=$GOFILE -out=gen-$GOFILE gen "RawType=BUILTINS"
|
||||
|
||||
package figtree
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/cheekybits/genny/generic"
|
||||
)
|
||||
|
||||
type RawType generic.Type
|
||||
|
||||
type RawTypeOption struct {
|
||||
Source string
|
||||
Defined bool
|
||||
Value RawType
|
||||
}
|
||||
|
||||
func NewRawTypeOption(dflt RawType) RawTypeOption {
|
||||
return RawTypeOption{
|
||||
Source: "default",
|
||||
Defined: true,
|
||||
Value: dflt,
|
||||
}
|
||||
}
|
||||
|
||||
func (o RawTypeOption) IsDefined() bool {
|
||||
return o.Defined
|
||||
}
|
||||
|
||||
func (o *RawTypeOption) SetSource(source string) {
|
||||
o.Source = source
|
||||
}
|
||||
|
||||
func (o RawTypeOption) GetValue() interface{} {
|
||||
return o.Value
|
||||
}
|
||||
|
||||
// This is useful with kingpin option parser
|
||||
func (o *RawTypeOption) Set(s string) error {
|
||||
err := convertString(s, &o.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Source = "override"
|
||||
o.Defined = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// This is useful with survey prompting library
|
||||
func (o *RawTypeOption) WriteAnswer(name string, value interface{}) error {
|
||||
if v, ok := value.(RawType); ok {
|
||||
o.Value = v
|
||||
o.Defined = true
|
||||
o.Source = "prompt"
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Got %T expected %T type: %v", value, o.Value, value)
|
||||
}
|
||||
|
||||
func (o *RawTypeOption) SetValue(v interface{}) error {
|
||||
if val, ok := v.(RawType); ok {
|
||||
o.Value = val
|
||||
o.Defined = true
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Got %T expected %T type: %v", v, o.Value, v)
|
||||
}
|
||||
|
||||
func (o *RawTypeOption) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
if err := unmarshal(&o.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
o.Defined = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *RawTypeOption) UnmarshalJSON(b []byte) error {
|
||||
if err := json.Unmarshal(b, &o.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
o.Defined = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o RawTypeOption) MarshalYAML() (interface{}, error) {
|
||||
if StringifyValue {
|
||||
return o.Value, nil
|
||||
}
|
||||
// need a copy of this struct without the MarshalYAML interface attached
|
||||
return struct {
|
||||
Value RawType
|
||||
Source string
|
||||
Defined bool
|
||||
}{
|
||||
Value: o.Value,
|
||||
Source: o.Source,
|
||||
Defined: o.Defined,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o RawTypeOption) MarshalJSON() ([]byte, error) {
|
||||
if StringifyValue {
|
||||
return json.Marshal(o.Value)
|
||||
}
|
||||
// need a copy of this struct without the MarshalJSON interface attached
|
||||
return json.Marshal(struct {
|
||||
Value RawType
|
||||
Source string
|
||||
Defined bool
|
||||
}{
|
||||
Value: o.Value,
|
||||
Source: o.Source,
|
||||
Defined: o.Defined,
|
||||
})
|
||||
}
|
||||
|
||||
// String is required for kingpin to generate usage with this datatype
|
||||
func (o RawTypeOption) String() string {
|
||||
if StringifyValue {
|
||||
return fmt.Sprintf("%v", o.Value)
|
||||
}
|
||||
return fmt.Sprintf("{Source:%s Defined:%t Value:%v}", o.Source, o.Defined, o.Value)
|
||||
}
|
||||
|
||||
type MapRawTypeOption map[string]RawTypeOption
|
||||
|
||||
// Set is required for kingpin interfaces to allow command line params
|
||||
// to be set to our map datatype
|
||||
func (o *MapRawTypeOption) Set(value string) error {
|
||||
parts := stringMapRegex.Split(value, 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("expected KEY=VALUE got '%s'", value)
|
||||
}
|
||||
val := RawTypeOption{}
|
||||
val.Set(parts[1])
|
||||
(*o)[parts[0]] = val
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsCumulative is required for kingpin interfaces to allow multiple values
|
||||
// to be set on the data structure.
|
||||
func (o MapRawTypeOption) IsCumulative() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// String is required for kingpin to generate usage with this datatype
|
||||
func (o MapRawTypeOption) String() string {
|
||||
return fmt.Sprintf("%v", map[string]RawTypeOption(o))
|
||||
}
|
||||
|
||||
func (o MapRawTypeOption) Map() map[string]RawType {
|
||||
tmp := map[string]RawType{}
|
||||
for k, v := range o {
|
||||
tmp[k] = v.Value
|
||||
}
|
||||
return tmp
|
||||
}
|
||||
|
||||
// This is useful with survey prompting library
|
||||
func (o *MapRawTypeOption) WriteAnswer(name string, value interface{}) error {
|
||||
tmp := RawTypeOption{}
|
||||
if v, ok := value.(RawType); ok {
|
||||
tmp.Value = v
|
||||
tmp.Defined = true
|
||||
tmp.Source = "prompt"
|
||||
(*o)[name] = tmp
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Got %T expected %T type: %v", value, tmp.Value, value)
|
||||
}
|
||||
|
||||
func (o MapRawTypeOption) IsDefined() bool {
|
||||
// true if the map has any keys
|
||||
if len(o) > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ListRawTypeOption []RawTypeOption
|
||||
|
||||
// Set is required for kingpin interfaces to allow command line params
|
||||
// to be set to our map datatype
|
||||
func (o *ListRawTypeOption) Set(value string) error {
|
||||
val := RawTypeOption{}
|
||||
val.Set(value)
|
||||
*o = append(*o, val)
|
||||
return nil
|
||||
}
|
||||
|
||||
// This is useful with survey prompting library
|
||||
func (o *ListRawTypeOption) WriteAnswer(name string, value interface{}) error {
|
||||
tmp := RawTypeOption{}
|
||||
if v, ok := value.(RawType); ok {
|
||||
tmp.Value = v
|
||||
tmp.Defined = true
|
||||
tmp.Source = "prompt"
|
||||
*o = append(*o, tmp)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Got %T expected %T type: %v", value, tmp.Value, value)
|
||||
}
|
||||
|
||||
// IsCumulative is required for kingpin interfaces to allow multiple values
|
||||
// to be set on the data structure.
|
||||
func (o ListRawTypeOption) IsCumulative() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// String is required for kingpin to generate usage with this datatype
|
||||
func (o ListRawTypeOption) String() string {
|
||||
return fmt.Sprintf("%v", []RawTypeOption(o))
|
||||
}
|
||||
|
||||
func (o ListRawTypeOption) Slice() []RawType {
|
||||
tmp := []RawType{}
|
||||
for _, elem := range o {
|
||||
tmp = append(tmp, elem.Value)
|
||||
}
|
||||
return tmp
|
||||
}
|
||||
|
||||
func (o ListRawTypeOption) IsDefined() bool {
|
||||
// true if the list is not empty
|
||||
if len(o) > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package figtree
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
yaml "gopkg.in/coryb/yaml.v2"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestOptionInterface(t *testing.T) {
|
||||
f := func(_ Option) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
assert.True(t, f(&BoolOption{}))
|
||||
assert.True(t, f(&ByteOption{}))
|
||||
assert.True(t, f(&Complex128Option{}))
|
||||
assert.True(t, f(&Complex64Option{}))
|
||||
assert.True(t, f(&ErrorOption{}))
|
||||
assert.True(t, f(&Float32Option{}))
|
||||
assert.True(t, f(&Float64Option{}))
|
||||
assert.True(t, f(&IntOption{}))
|
||||
assert.True(t, f(&Int16Option{}))
|
||||
assert.True(t, f(&Int32Option{}))
|
||||
assert.True(t, f(&Int64Option{}))
|
||||
assert.True(t, f(&Int8Option{}))
|
||||
assert.True(t, f(&RuneOption{}))
|
||||
assert.True(t, f(&StringOption{}))
|
||||
assert.True(t, f(&UintOption{}))
|
||||
assert.True(t, f(&Uint16Option{}))
|
||||
assert.True(t, f(&Uint32Option{}))
|
||||
assert.True(t, f(&Uint64Option{}))
|
||||
assert.True(t, f(&Uint8Option{}))
|
||||
assert.True(t, f(&UintptrOption{}))
|
||||
}
|
||||
|
||||
func TestStringOptionYAML(t *testing.T) {
|
||||
s := ""
|
||||
err := yaml.Unmarshal([]byte(`""`), &s)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, s, "")
|
||||
|
||||
type testType struct {
|
||||
String StringOption `yaml:"string,omitempty"`
|
||||
}
|
||||
tt := testType{}
|
||||
|
||||
err = yaml.Unmarshal([]byte(`string: ""`), &tt)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, tt.String, StringOption{Value: "", Defined: true})
|
||||
|
||||
tt = testType{}
|
||||
err = yaml.Unmarshal([]byte(`string: "value"`), &tt)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, tt.String, StringOption{Value: "value", Defined: true})
|
||||
}
|
||||
|
||||
func TestStringOptionJSON(t *testing.T) {
|
||||
type testType struct {
|
||||
String StringOption `json:"string,omitempty"`
|
||||
}
|
||||
tt := testType{}
|
||||
|
||||
err := json.Unmarshal([]byte(`{"string": ""}`), &tt)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, tt.String, StringOption{Value: "", Defined: true})
|
||||
|
||||
tt = testType{}
|
||||
err = json.Unmarshal([]byte(`{"string": "value"}`), &tt)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, tt.String, StringOption{Value: "value", Defined: true})
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
package figtree
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestOptionsStopConfigD3(t *testing.T) {
|
||||
opts := TestOptions{}
|
||||
os.Chdir("d1/d2/d3")
|
||||
defer os.Chdir("../../..")
|
||||
|
||||
arr1 := []StringOption{}
|
||||
arr1 = append(arr1, StringOption{"stop.yml", true, "d3arr1val1"})
|
||||
arr1 = append(arr1, StringOption{"stop.yml", true, "d3arr1val2"})
|
||||
arr1 = append(arr1, StringOption{"../stop.yml", true, "d2arr1val1"})
|
||||
arr1 = append(arr1, StringOption{"../stop.yml", true, "d2arr1val2"})
|
||||
|
||||
expected := TestOptions{
|
||||
String1: StringOption{"stop.yml", true, "d3str1val1"},
|
||||
LeaveEmpty: StringOption{},
|
||||
Array1: arr1,
|
||||
Map1: map[string]StringOption{
|
||||
"key1": StringOption{"../stop.yml", true, "d2map1val1"},
|
||||
"key2": StringOption{"stop.yml", true, "d3map1val2"},
|
||||
"key3": StringOption{"stop.yml", true, "d3map1val3"},
|
||||
},
|
||||
Int1: IntOption{"stop.yml", true, 333},
|
||||
Float1: Float32Option{"stop.yml", true, 3.33},
|
||||
Bool1: BoolOption{"stop.yml", true, true},
|
||||
}
|
||||
|
||||
err := LoadAllConfigs("stop.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
assert.Exactly(t, expected, opts)
|
||||
}
|
||||
|
||||
func TestOptionsStopConfigD2(t *testing.T) {
|
||||
opts := TestOptions{}
|
||||
os.Chdir("d1/d2")
|
||||
defer os.Chdir("../..")
|
||||
|
||||
arr1 := []StringOption{}
|
||||
arr1 = append(arr1, StringOption{"stop.yml", true, "d2arr1val1"})
|
||||
arr1 = append(arr1, StringOption{"stop.yml", true, "d2arr1val2"})
|
||||
|
||||
expected := TestOptions{
|
||||
String1: StringOption{"stop.yml", true, "d2str1val1"},
|
||||
LeaveEmpty: StringOption{},
|
||||
Array1: arr1,
|
||||
Map1: map[string]StringOption{
|
||||
"key1": StringOption{"stop.yml", true, "d2map1val1"},
|
||||
"key2": StringOption{"stop.yml", true, "d2map1val2"},
|
||||
},
|
||||
Int1: IntOption{"stop.yml", true, 222},
|
||||
Float1: Float32Option{"stop.yml", true, 2.22},
|
||||
Bool1: BoolOption{"stop.yml", true, false},
|
||||
}
|
||||
|
||||
err := LoadAllConfigs("stop.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
assert.Exactly(t, expected, opts)
|
||||
}
|
||||
|
||||
func TestBuiltinStopConfigD3(t *testing.T) {
|
||||
opts := TestBuiltin{}
|
||||
os.Chdir("d1/d2/d3")
|
||||
defer os.Chdir("../../..")
|
||||
|
||||
arr1 := []string{}
|
||||
arr1 = append(arr1, "d3arr1val1")
|
||||
arr1 = append(arr1, "d3arr1val2")
|
||||
arr1 = append(arr1, "d2arr1val1")
|
||||
arr1 = append(arr1, "d2arr1val2")
|
||||
|
||||
expected := TestBuiltin{
|
||||
String1: "d3str1val1",
|
||||
LeaveEmpty: "",
|
||||
Array1: arr1,
|
||||
Map1: map[string]string{
|
||||
"key1": "d2map1val1",
|
||||
"key2": "d3map1val2",
|
||||
"key3": "d3map1val3",
|
||||
},
|
||||
Int1: 333,
|
||||
Float1: 3.33,
|
||||
Bool1: true,
|
||||
}
|
||||
|
||||
err := LoadAllConfigs("stop.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
assert.Exactly(t, expected, opts)
|
||||
}
|
||||
|
||||
func TestBuiltinStopConfigD2(t *testing.T) {
|
||||
opts := TestBuiltin{}
|
||||
os.Chdir("d1/d2")
|
||||
defer os.Chdir("../..")
|
||||
|
||||
arr1 := []string{}
|
||||
arr1 = append(arr1, "d2arr1val1")
|
||||
arr1 = append(arr1, "d2arr1val2")
|
||||
|
||||
expected := TestBuiltin{
|
||||
String1: "d2str1val1",
|
||||
LeaveEmpty: "",
|
||||
Array1: arr1,
|
||||
Map1: map[string]string{
|
||||
"key1": "d2map1val1",
|
||||
"key2": "d2map1val2",
|
||||
},
|
||||
Int1: 222,
|
||||
Float1: 2.22,
|
||||
Bool1: false,
|
||||
}
|
||||
|
||||
err := LoadAllConfigs("stop.yml", &opts)
|
||||
assert.Nil(t, err)
|
||||
assert.Exactly(t, expected, opts)
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package figtree
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func homedir() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return os.Getenv("USERPROFILE")
|
||||
}
|
||||
return os.Getenv("HOME")
|
||||
}
|
||||
|
||||
func FindParentPaths(fileName string) []string {
|
||||
cwd, _ := os.Getwd()
|
||||
|
||||
paths := make([]string, 0)
|
||||
|
||||
// special case if homedir is not in current path then check there anyway
|
||||
homedir := homedir()
|
||||
if !strings.HasPrefix(cwd, homedir) {
|
||||
file := path.Join(homedir, fileName)
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
paths = append(paths, filepath.FromSlash(file))
|
||||
}
|
||||
}
|
||||
|
||||
var dir string
|
||||
for _, part := range strings.Split(cwd, string(os.PathSeparator)) {
|
||||
if part == "" && dir == "" {
|
||||
dir = "/"
|
||||
} else {
|
||||
dir = path.Join(dir, part)
|
||||
}
|
||||
file := path.Join(dir, fileName)
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
paths = append(paths, filepath.FromSlash(file))
|
||||
}
|
||||
}
|
||||
return paths
|
||||
}
|
||||
Reference in New Issue
Block a user