Skip to content

Commit 9eba431

Browse files
Fix missing fields in show command (#315)
* show empty strings on selected fields * add maybe.Optional[T] && maybe.Bool * add Stringer to maybe.Bool * update all references to *bool with maybe.Bool * add test for BoolDecoder function * add test on JSON marshaller/unmarshaller * set maybe.Bool type to bool in jsonschema * add a unit test for generating maybe.Bool json schema * try running ajv tests on github action * make schema test running under windows * use nodejs 18
1 parent 15004f9 commit 9eba431

23 files changed

+405
-77
lines changed

.github/workflows/build.yml

+5
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ jobs:
3636
check-latest: true
3737
cache: true
3838

39+
- name: Set up Node.js LTS for running JSON schema tests (using ajv)
40+
uses: actions/setup-node@v4
41+
with:
42+
node-version: 18
43+
3944
- name: Build
4045
run: make build
4146
env:

battery_test.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,9 @@ import (
1010

1111
var errTest = errors.New("test error")
1212

13-
func TestNotRunningOnBattery(t *testing.T) {
14-
battery, charge, err := IsRunningOnBattery()
13+
func TestNoErrorIsRunningOnBattery(t *testing.T) {
14+
_, _, err := IsRunningOnBattery()
1515
assert.NoError(t, err)
16-
assert.False(t, battery)
17-
assert.Zero(t, charge)
1816
}
1917

2018
func TestIsFatalError(t *testing.T) {

config/config.go

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/creativeprojects/clog"
1616
"github.com/creativeprojects/resticprofile/constants"
1717
"github.com/creativeprojects/resticprofile/filesearch"
18+
"github.com/creativeprojects/resticprofile/util/maybe"
1819
"github.com/creativeprojects/resticprofile/util/templates"
1920
"github.com/mitchellh/mapstructure"
2021
"github.com/spf13/viper"
@@ -42,6 +43,7 @@ type Config struct {
4243
var (
4344
configOption = viper.DecodeHook(mapstructure.ComposeDecodeHookFunc(
4445
mapstructure.StringToTimeDurationHookFunc(),
46+
maybe.BoolDecoder(),
4547
confidentialValueDecoder(),
4648
))
4749

config/config_test.go

+11-15
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"testing"
1111
"time"
1212

13+
"github.com/creativeprojects/resticprofile/util/maybe"
1314
"github.com/stretchr/testify/assert"
1415
"github.com/stretchr/testify/require"
1516
)
@@ -198,14 +199,9 @@ profile:
198199
}
199200

200201
func TestBoolPointer(t *testing.T) {
201-
boolPointer := func(value bool) *bool {
202-
output := &value
203-
return output
204-
}
205-
206202
fixtures := []struct {
207203
testTemplate
208-
continueOnError *bool
204+
continueOnError maybe.Bool
209205
}{
210206
{
211207
testTemplate: testTemplate{
@@ -217,7 +213,7 @@ version = 2
217213
profiles = []
218214
`,
219215
},
220-
continueOnError: nil,
216+
continueOnError: maybe.Bool{},
221217
},
222218
{
223219
testTemplate: testTemplate{
@@ -229,7 +225,7 @@ groups:
229225
profiles: []
230226
`,
231227
},
232-
continueOnError: nil,
228+
continueOnError: maybe.Bool{},
233229
},
234230
{
235231
testTemplate: testTemplate{
@@ -245,7 +241,7 @@ groups:
245241
}
246242
`,
247243
},
248-
continueOnError: nil,
244+
continueOnError: maybe.Bool{},
249245
},
250246
{
251247
testTemplate: testTemplate{
@@ -258,7 +254,7 @@ profiles = []
258254
continue-on-error = true
259255
`,
260256
},
261-
continueOnError: boolPointer(true),
257+
continueOnError: maybe.True(),
262258
},
263259
{
264260
testTemplate: testTemplate{
@@ -271,7 +267,7 @@ groups:
271267
continue-on-error: true
272268
`,
273269
},
274-
continueOnError: boolPointer(true),
270+
continueOnError: maybe.True(),
275271
},
276272
{
277273
testTemplate: testTemplate{
@@ -288,7 +284,7 @@ groups:
288284
}
289285
`,
290286
},
291-
continueOnError: boolPointer(true),
287+
continueOnError: maybe.True(),
292288
},
293289
{
294290
testTemplate: testTemplate{
@@ -301,7 +297,7 @@ profiles = []
301297
continue-on-error = false
302298
`,
303299
},
304-
continueOnError: boolPointer(false),
300+
continueOnError: maybe.False(),
305301
},
306302
{
307303
testTemplate: testTemplate{
@@ -314,7 +310,7 @@ groups:
314310
continue-on-error: false
315311
`,
316312
},
317-
continueOnError: boolPointer(false),
313+
continueOnError: maybe.False(),
318314
},
319315
{
320316
testTemplate: testTemplate{
@@ -331,7 +327,7 @@ groups:
331327
}
332328
`,
333329
},
334-
continueOnError: boolPointer(false),
330+
continueOnError: maybe.False(),
335331
},
336332
}
337333

config/config_v1.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"reflect"
77

88
"github.com/creativeprojects/resticprofile/constants"
9+
"github.com/creativeprojects/resticprofile/util/maybe"
910
"github.com/mitchellh/mapstructure"
1011
"github.com/spf13/viper"
1112
)
@@ -28,11 +29,13 @@ import (
2829
var (
2930
configOptionV1 = viper.DecodeHook(mapstructure.ComposeDecodeHookFunc(
3031
mapstructure.StringToTimeDurationHookFunc(),
32+
maybe.BoolDecoder(),
3133
confidentialValueDecoder(),
3234
))
3335

3436
configOptionV1HCL = viper.DecodeHook(mapstructure.ComposeDecodeHookFunc(
3537
mapstructure.StringToTimeDurationHookFunc(),
38+
maybe.BoolDecoder(),
3639
confidentialValueDecoder(),
3740
sliceOfMapsToMapHookFunc(),
3841
))
@@ -70,7 +73,7 @@ func (c *Config) loadGroupsV1() (err error) {
7073
c.groups[groupName] = Group{
7174
Description: "",
7275
Profiles: group,
73-
ContinueOnError: nil,
76+
ContinueOnError: maybe.Bool{},
7477
}
7578
}
7679
}

config/display.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,18 @@ import (
55
"io"
66
"strings"
77
"text/tabwriter"
8+
9+
"github.com/fatih/color"
810
)
911

1012
const (
1113
indent = " " // 4 spaces
1214
)
1315

16+
var (
17+
ansiBold = color.New(color.Bold).SprintFunc()
18+
)
19+
1420
// Display is a temporary struct to display a config object to the console
1521
type Display struct {
1622
topLevel string
@@ -30,6 +36,7 @@ func (d *Display) addEntry(stack []string, key string, values []string) {
3036
entry := Entry{
3137
section: strings.Join(stack, "."), // in theory we should only have zero or one level, but don't fail if we get more
3238
key: key,
39+
keyOnly: false,
3340
values: values,
3441
}
3542
d.entries = append(d.entries, entry)
@@ -41,9 +48,10 @@ func (d *Display) addKeyOnlyEntry(stack []string, key string) {
4148
}
4249

4350
func (d *Display) Flush() {
44-
tabWriter := tabwriter.NewWriter(d.writer, 0, 2, 2, ' ', 0)
51+
const minWidth, tabWidth, padding = 0, 2, 2
52+
tabWriter := tabwriter.NewWriter(d.writer, minWidth, tabWidth, padding, ' ', 0)
4553
// title
46-
fmt.Fprintf(tabWriter, "%s:\n", d.topLevel)
54+
fmt.Fprintf(tabWriter, "%s:\n", ansiBold(d.topLevel))
4755
section := ""
4856
prefix := indent
4957
for _, entry := range d.entries {

config/flag.go

+3-7
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,8 @@ import (
1414
)
1515

1616
var (
17-
emptyStringArray []string
18-
)
19-
20-
func init() {
2117
emptyStringArray = make([]string, 0)
22-
}
18+
)
2319

2420
var allowedEmptyValueArgs = []string{
2521
constants.ParameterKeepTag, // allows --keep-tag="" - means keep all with any assigned tag
@@ -68,8 +64,8 @@ func addArgsFromStruct(args *shell.Args, section any) {
6864
}
6965
}
7066

71-
func argAliasesFromStruct(section any) (aliases map[string]string) {
72-
aliases = make(map[string]string)
67+
func argAliasesFromStruct(section any) map[string]string {
68+
aliases := make(map[string]string)
7369
if t := util.ElementType(reflect.TypeOf(section)); t.Kind() == reflect.Struct {
7470
for i := 0; i < t.NumField(); i++ {
7571
field := t.Field(i)

config/group.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package config
22

3+
import "github.com/creativeprojects/resticprofile/util/maybe"
4+
35
// Group of profiles
46
type Group struct {
5-
Description string `mapstructure:"description" description:"Describe the group"`
6-
Profiles []string `mapstructure:"profiles" description:"Names of the profiles belonging to this group"`
7-
ContinueOnError *bool `mapstructure:"continue-on-error" default:"auto" description:"Continue with the next profile on a failure, overrides \"global.group-continue-on-error\""`
7+
Description string `mapstructure:"description" description:"Describe the group"`
8+
Profiles []string `mapstructure:"profiles" description:"Names of the profiles belonging to this group"`
9+
ContinueOnError maybe.Bool `mapstructure:"continue-on-error" default:"auto" description:"Continue with the next profile on a failure, overrides \"global.group-continue-on-error\""`
810
}

config/info_customizer.go

+12
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/creativeprojects/resticprofile/constants"
1010
"github.com/creativeprojects/resticprofile/restic"
1111
"github.com/creativeprojects/resticprofile/util"
12+
"github.com/creativeprojects/resticprofile/util/maybe"
1213
)
1314

1415
// resticDescriptionReplacements removes or replaces misleading documentation fragments from restic man pages
@@ -123,6 +124,17 @@ func init() {
123124
}
124125
})
125126

127+
// Profile: special handling for maybe.Bool
128+
maybeBoolType := reflect.TypeOf(maybe.Bool{})
129+
registerPropertyInfoCustomizer(func(sectionName, propertyName string, property accessibleProperty) {
130+
if field := property.field(); field != nil {
131+
if util.ElementType(field.Type).AssignableTo(maybeBoolType) {
132+
basic := property.basic().resetTypeInfo()
133+
basic.mayBool = true
134+
}
135+
}
136+
})
137+
126138
// Profile: deprecated sections (squash with deprecated, e.g. schedule in retention)
127139
registerPropertyInfoCustomizer(func(sectionName, propertyName string, property accessibleProperty) {
128140
if field := property.sectionField(nil); field != nil {

config/info_customizer_test.go

+33-1
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ package config
22

33
import (
44
"fmt"
5-
"github.com/stretchr/testify/require"
65
"reflect"
76
"strings"
87
"testing"
98

9+
"github.com/stretchr/testify/require"
10+
1011
"github.com/creativeprojects/resticprofile/constants"
1112
"github.com/creativeprojects/resticprofile/restic"
1213
"github.com/creativeprojects/resticprofile/util/collect"
14+
"github.com/creativeprojects/resticprofile/util/maybe"
1315
"github.com/stretchr/testify/assert"
1416
)
1517

@@ -171,6 +173,36 @@ func TestConfidentialProperty(t *testing.T) {
171173
}
172174
}
173175

176+
func TestMaybeBoolProperty(t *testing.T) {
177+
var testType = struct {
178+
Simple maybe.Bool `mapstructure:"simple"`
179+
}{}
180+
181+
set := propertySetFromType(reflect.TypeOf(testType))
182+
183+
assert.ElementsMatch(t, []string{"simple"}, set.Properties())
184+
for _, name := range set.Properties() {
185+
t.Run(name+"/before", func(t *testing.T) {
186+
info := set.PropertyInfo(name)
187+
require.True(t, info.CanBePropertySet())
188+
assert.Equal(t, "Bool", info.PropertySet().TypeName())
189+
assert.False(t, info.CanBeBool())
190+
assert.False(t, info.IsMultiType())
191+
})
192+
}
193+
194+
customizeProperties("any", set.properties)
195+
196+
for _, name := range set.Properties() {
197+
t.Run(name, func(t *testing.T) {
198+
info := set.PropertyInfo(name)
199+
assert.True(t, info.CanBeBool())
200+
assert.False(t, info.CanBePropertySet())
201+
assert.False(t, info.IsMultiType())
202+
})
203+
}
204+
}
205+
174206
func TestDeprecatedSection(t *testing.T) {
175207
var testType = struct {
176208
ScheduleBaseSection `mapstructure:",squash" deprecated:"true"`

config/jsonschema/schema_test.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//go:build !ajv_test
2+
13
package jsonschema
24

35
import (
@@ -67,7 +69,7 @@ func npmRunner(t *testing.T) npmRunnerFunc {
6769
var npm npmRunnerFunc
6870

6971
func initNpmEnv(t *testing.T) {
70-
if !testing.Short() && npm == nil {
72+
if npm == nil {
7173
npm = npmRunner(&testing.T{})
7274
if npm(t, "list", "ajv") != nil {
7375
t.Log("Installing AJV JSONSchema validator")
@@ -131,7 +133,7 @@ func TestJsonSchemaValidation(t *testing.T) {
131133
require.NoError(t, v.ReadInConfig())
132134

133135
v.SetConfigType("json")
134-
filename = path.Join(t.TempDir(), fmt.Sprintf(path.Base(filename)+".json"))
136+
filename = filepath.Join(t.TempDir(), fmt.Sprintf(filepath.Base(filename)+".json"))
135137
require.NoError(t, v.WriteConfigAs(filename))
136138
return filename
137139
}

0 commit comments

Comments
 (0)