diff --git a/home/options.go b/home/options.go index ec789d4f..90d356e8 100644 --- a/home/options.go +++ b/home/options.go @@ -40,6 +40,33 @@ type arg struct { 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 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 @@ -50,18 +77,21 @@ var configArg = arg{ func(o options, v string) (options, error) { o.configFilename = v; return o, nil }, nil, nil, + func(o options) []string { return stringSliceOrNil(o.configFilename) }, } var workDirArg = arg{ "Path to the working directory", "work-dir", "w", 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{ "Host address to bind HTTP server on", "host", "h", 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{ @@ -80,6 +110,7 @@ var portArg = arg{ } return o, err }, nil, nil, + func(o options) []string { return intSliceOrNil(o.bindPort) }, } var serviceArg = arg{ @@ -89,42 +120,49 @@ var serviceArg = arg{ o.serviceControlAction = v return o, nil }, nil, nil, + func(o options) []string { return stringSliceOrNil(o.serviceControlAction) }, } var logfileArg = arg{ "Path to log file. If empty: write to stdout; if 'syslog': write to system log", "logfile", "l", 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{ "Path to a file where PID is stored", "pidfile", "", 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{ "Check configuration and exit", "check-config", "", nil, func(o options) (options, error) { o.checkConfig = true; return o, nil }, nil, + func(o options) []string { return boolSliceOrNil(o.checkConfig) }, } var noCheckUpdateArg = arg{ "Don't check for updates", "no-check-update", "", nil, func(o options) (options, error) { o.disableUpdate = true; return o, nil }, nil, + func(o options) []string { return boolSliceOrNil(o.disableUpdate) }, } var verboseArg = arg{ "Enable verbose output", "verbose", "v", nil, func(o options) (options, error) { o.verbose = true; return o, nil }, nil, + func(o options) []string { return boolSliceOrNil(o.verbose) }, } var glinetArg = arg{ "Run in GL-Inet compatibility mode", "glinet", "", nil, func(o options) (options, error) { o.glinetMode = true; return o, nil }, nil, + func(o options) []string { return boolSliceOrNil(o.glinetMode) }, } var versionArg = arg{ @@ -133,6 +171,7 @@ var versionArg = arg{ nil, nil, func(o options, exec string) (effect, error) { return func() error { fmt.Println(version()); os.Exit(0); return nil }, nil }, + func(o options) []string { return nil }, } var helpArg = arg{ @@ -141,6 +180,7 @@ var helpArg = arg{ nil, nil, func(o options, exec string) (effect, error) { return func() error { _ = printHelp(exec); os.Exit(64); return nil }, nil }, + func(o options) []string { return nil }, } func init() { @@ -253,3 +293,21 @@ func parse(exec string, ss []string) (o options, f effect, err error) { 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 +} diff --git a/home/options_test.go b/home/options_test.go index 750f3ce2..5a2b1155 100644 --- a/home/options_test.go +++ b/home/options_test.go @@ -169,3 +169,69 @@ func TestParseUnknown(t *testing.T) { testParseErr(t, "unknown plus", "+x") 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") +}