home/options: add options -> args serialization

This commit is contained in:
David Sheets 2020-09-07 09:53:16 +01:00
parent 03506df25d
commit d3428ca46c
2 changed files with 124 additions and 0 deletions

View File

@ -40,6 +40,33 @@ type arg struct {
updateWithValue func(o options, v string) (options, error) // the mutator for arguments with parameters updateWithValue func(o options, v string) (options, error) // the mutator for arguments with parameters
updateNoValue func(o options) (options, error) // the mutator for arguments without parameters updateNoValue func(o options) (options, error) // the mutator for arguments without parameters
effect func(o options, exec string) (f effect, err error) // the side-effect closure generator effect func(o options, exec string) (f effect, err error) // the side-effect closure generator
serialize func(o options) []string // the re-serialization function back to arguments (return nil for omit)
}
// {type}SliceOrNil functions check their parameter of type {type}
// against its zero value and return nil if the parameter value is
// zero otherwise they return a string slice of the parameter
func stringSliceOrNil(s string) []string {
if s == "" {
return nil
}
return []string{s}
}
func intSliceOrNil(i int) []string {
if i == 0 {
return nil
}
return []string{strconv.Itoa(i)}
}
func boolSliceOrNil(b bool) []string {
if b {
return []string{}
}
return nil
} }
var args []arg var args []arg
@ -50,18 +77,21 @@ var configArg = arg{
func(o options, v string) (options, error) { o.configFilename = v; return o, nil }, func(o options, v string) (options, error) { o.configFilename = v; return o, nil },
nil, nil,
nil, nil,
func(o options) []string { return stringSliceOrNil(o.configFilename) },
} }
var workDirArg = arg{ var workDirArg = arg{
"Path to the working directory", "Path to the working directory",
"work-dir", "w", "work-dir", "w",
func(o options, v string) (options, error) { o.workDir = v; return o, nil }, nil, nil, func(o options, v string) (options, error) { o.workDir = v; return o, nil }, nil, nil,
func(o options) []string { return stringSliceOrNil(o.workDir) },
} }
var hostArg = arg{ var hostArg = arg{
"Host address to bind HTTP server on", "Host address to bind HTTP server on",
"host", "h", "host", "h",
func(o options, v string) (options, error) { o.bindHost = v; return o, nil }, nil, nil, func(o options, v string) (options, error) { o.bindHost = v; return o, nil }, nil, nil,
func(o options) []string { return stringSliceOrNil(o.bindHost) },
} }
var portArg = arg{ var portArg = arg{
@ -80,6 +110,7 @@ var portArg = arg{
} }
return o, err return o, err
}, nil, nil, }, nil, nil,
func(o options) []string { return intSliceOrNil(o.bindPort) },
} }
var serviceArg = arg{ var serviceArg = arg{
@ -89,42 +120,49 @@ var serviceArg = arg{
o.serviceControlAction = v o.serviceControlAction = v
return o, nil return o, nil
}, nil, nil, }, nil, nil,
func(o options) []string { return stringSliceOrNil(o.serviceControlAction) },
} }
var logfileArg = arg{ var logfileArg = arg{
"Path to log file. If empty: write to stdout; if 'syslog': write to system log", "Path to log file. If empty: write to stdout; if 'syslog': write to system log",
"logfile", "l", "logfile", "l",
func(o options, v string) (options, error) { o.logFile = v; return o, nil }, nil, nil, func(o options, v string) (options, error) { o.logFile = v; return o, nil }, nil, nil,
func(o options) []string { return stringSliceOrNil(o.logFile) },
} }
var pidfileArg = arg{ var pidfileArg = arg{
"Path to a file where PID is stored", "Path to a file where PID is stored",
"pidfile", "", "pidfile", "",
func(o options, v string) (options, error) { o.pidFile = v; return o, nil }, nil, nil, func(o options, v string) (options, error) { o.pidFile = v; return o, nil }, nil, nil,
func(o options) []string { return stringSliceOrNil(o.pidFile) },
} }
var checkConfigArg = arg{ var checkConfigArg = arg{
"Check configuration and exit", "Check configuration and exit",
"check-config", "", "check-config", "",
nil, func(o options) (options, error) { o.checkConfig = true; return o, nil }, nil, nil, func(o options) (options, error) { o.checkConfig = true; return o, nil }, nil,
func(o options) []string { return boolSliceOrNil(o.checkConfig) },
} }
var noCheckUpdateArg = arg{ var noCheckUpdateArg = arg{
"Don't check for updates", "Don't check for updates",
"no-check-update", "", "no-check-update", "",
nil, func(o options) (options, error) { o.disableUpdate = true; return o, nil }, nil, nil, func(o options) (options, error) { o.disableUpdate = true; return o, nil }, nil,
func(o options) []string { return boolSliceOrNil(o.disableUpdate) },
} }
var verboseArg = arg{ var verboseArg = arg{
"Enable verbose output", "Enable verbose output",
"verbose", "v", "verbose", "v",
nil, func(o options) (options, error) { o.verbose = true; return o, nil }, nil, nil, func(o options) (options, error) { o.verbose = true; return o, nil }, nil,
func(o options) []string { return boolSliceOrNil(o.verbose) },
} }
var glinetArg = arg{ var glinetArg = arg{
"Run in GL-Inet compatibility mode", "Run in GL-Inet compatibility mode",
"glinet", "", "glinet", "",
nil, func(o options) (options, error) { o.glinetMode = true; return o, nil }, nil, nil, func(o options) (options, error) { o.glinetMode = true; return o, nil }, nil,
func(o options) []string { return boolSliceOrNil(o.glinetMode) },
} }
var versionArg = arg{ var versionArg = arg{
@ -133,6 +171,7 @@ var versionArg = arg{
nil, nil, func(o options, exec string) (effect, error) { nil, nil, func(o options, exec string) (effect, error) {
return func() error { fmt.Println(version()); os.Exit(0); return nil }, nil return func() error { fmt.Println(version()); os.Exit(0); return nil }, nil
}, },
func(o options) []string { return nil },
} }
var helpArg = arg{ var helpArg = arg{
@ -141,6 +180,7 @@ var helpArg = arg{
nil, nil, func(o options, exec string) (effect, error) { nil, nil, func(o options, exec string) (effect, error) {
return func() error { _ = printHelp(exec); os.Exit(64); return nil }, nil return func() error { _ = printHelp(exec); os.Exit(64); return nil }, nil
}, },
func(o options) []string { return nil },
} }
func init() { func init() {
@ -253,3 +293,21 @@ func parse(exec string, ss []string) (o options, f effect, err error) {
return return
} }
func shortestFlag(a arg) string {
if a.shortName != "" {
return "-" + a.shortName
}
return "--" + a.longName
}
func serialize(o options) []string {
ss := []string{}
for _, arg := range args {
s := arg.serialize(o)
if s != nil {
ss = append(ss, append([]string{shortestFlag(arg)}, s...)...)
}
}
return ss
}

View File

@ -169,3 +169,69 @@ func TestParseUnknown(t *testing.T) {
testParseErr(t, "unknown plus", "+x") testParseErr(t, "unknown plus", "+x")
testParseErr(t, "unknown dash", "-") testParseErr(t, "unknown dash", "-")
} }
func testSerialize(t *testing.T, o options, ss ...string) {
result := serialize(o)
if len(result) != len(ss) {
t.Fatalf("expected %s but got %s", ss, result)
}
for i, r := range result {
if r != ss[i] {
t.Fatalf("expected %s but got %s", ss, result)
}
}
}
func TestSerializeEmpty(t *testing.T) {
testSerialize(t, options{})
}
func TestSerializeConfigFilename(t *testing.T) {
testSerialize(t, options{configFilename: "path"}, "-c", "path")
}
func TestSerializeWorkDir(t *testing.T) {
testSerialize(t, options{workDir: "path"}, "-w", "path")
}
func TestSerializeBindHost(t *testing.T) {
testSerialize(t, options{bindHost: "addr"}, "-h", "addr")
}
func TestSerializeBindPort(t *testing.T) {
testSerialize(t, options{bindPort: 666}, "-p", "666")
}
func TestSerializeLogfile(t *testing.T) {
testSerialize(t, options{logFile: "path"}, "-l", "path")
}
func TestSerializePidfile(t *testing.T) {
testSerialize(t, options{pidFile: "path"}, "--pidfile", "path")
}
func TestSerializeCheckConfig(t *testing.T) {
testSerialize(t, options{checkConfig: true}, "--check-config")
}
func TestSerializeDisableUpdate(t *testing.T) {
testSerialize(t, options{disableUpdate: true}, "--no-check-update")
}
func TestSerializeService(t *testing.T) {
testSerialize(t, options{serviceControlAction: "run"}, "-s", "run")
}
func TestSerializeGLInet(t *testing.T) {
testSerialize(t, options{glinetMode: true}, "--glinet")
}
func TestSerializeMultiple(t *testing.T) {
testSerialize(t, options{
serviceControlAction: "run",
configFilename: "config",
workDir: "work",
pidFile: "pid",
disableUpdate: true,
}, "-c", "config", "-w", "work", "-s", "run", "--pidfile", "pid", "--no-check-update")
}