@ 261101.7

261101.4 · 5735ff673a95384397d923bbcec92a411a6328d1

main.go splits by purpose

main.go held argument parsing, endpoint and token resolution and opening a browser, beside the program's start. They move to args.go, endpoint.go and browser.go, and main_test.go's tests go with the code they cover. main.go keeps main, the reporter, detaching and error codes.

Declarations moved unchanged, as a sorted comparison of every line outside the import blocks confirms. git blame -C -C traces them.

reading declarations

Changes to args_test.go. All files

args_test.go

new file mode 100644@@ -0,0 +1,151 @@+package main++import (+	"errors"+	"os"+	"os/exec"+	"path/filepath"+	"strings"+	"testing"+)++// Options parse the GNU way: short ones combine, values sit joined or apart,+// and options may follow the directory.+func TestParseArgs(t *testing.T) {+	for _, tc := range parseOK {+		got, err := parseArgs(tc.args)+		if err != nil || got != tc.want {+			t.Errorf("%q: %+v, %v; want %+v", tc.args, got, err, tc.want)+		}+	}+	for _, args := range [][]string{+		{"-x"},+		{"-f"},+		{"-n"},+		{"-fn"},+		{"-fg"},+		{"-nolock"},+		{"--no-lock"},+		{"--no-breaks"},+		{"--fg"},+		{"--foreground=yes"},+		{"-a"},+		{"--addr"},+		{"--addr="},+		{"one", "two"},+		{"--restart", "--foreground"},+		{"-l", "--restart"},+		{"--share="},+		{"--share", "a", "b"},+		{"-a", "127.31.1.2", "--share"},+		{"--secret"},+		{"--share", "--secret=k3x9"},+		{"--share", "--secret="},+		{"--restart", "--share", "--secret"},+		{"--kill", "docs"},+		{"--commit"},+		{"--commit="},+		{"--commit=HEAD", "-l"},+		{"--commit=HEAD", "-a", "127.31.1.2"},+		{"--commit=HEAD", "--no-open"},+		{"--kill", "-l"},+		{"--kill", "--restart"},+		{"--kill", "--share", "--secret"},+		{"--cache=lots"},+		{"--cache=-1M"},+		{"--cache"},+		{"--public", "-a", "127.0.0.1"},+		{"--public", "--disk=6G"},+		{"--public", "--disk=6G", "-a", "127.0.0.1", "docs"},+		{"--public", "--disk=6G", "-a", "127.0.0.1", "--foreground"},+		{"--public", "--disk=6G", "-a", "127.0.0.1", "-p"},+		{"--public", "--disk=6G", "--share", "--secret"},+		{"--public", "--disk=6G", "--restart", "-a", "127.0.0.1"},+		{"--commit=HEAD", "--public"},+	} {+		if _, err := parseArgs(args); err == nil {+			t.Errorf("%q: no error", args)+		}+	}+}++// parseOK holds command lines www takes, and what each parses to.+var parseOK = []struct {+	args []string+	want config+}{+	{nil, config{dir: "."}},+	{[]string{"--foreground", "--debug"}, config{dir: ".", fg: true, debug: true}},+	{[]string{"--cache=64M"}, config{dir: ".", cache: "64M"}},+	{[]string{"--cache", "0"}, config{dir: ".", cache: "0"}},+	{[]string{"docs", "--foreground", "--no-open"}, config{dir: "docs", fg: true, noopen: true}},+	{[]string{"-a", "127.31.1.2"}, config{dir: ".", addr: "127.31.1.2"}},+	{[]string{"-a127.31.1.2:9000"}, config{dir: ".", addr: "127.31.1.2:9000"}},+	{[]string{"-pa", "127.31.1.2"}, config{dir: ".", persist: true, addr: "127.31.1.2"}},+	{[]string{"--addr", "127.31.1.2"}, config{dir: ".", addr: "127.31.1.2"}},+	{[]string{"--addr=127.31.1.2"}, config{dir: ".", addr: "127.31.1.2"}},+	{[]string{"--", "-f"}, config{dir: "-f"}},+	{[]string{"-h"}, config{dir: ".", help: true}},+	{[]string{"--help"}, config{dir: ".", help: true}},+	{[]string{"--version"}, config{dir: ".", version: true}},+	{[]string{"-V"}, config{dir: ".", version: true}},+	{[]string{"--restart", "-a", "127.31.1.2"}, config{addr: "127.31.1.2", restart: true}},+	{[]string{"--restart", "-a", "127.31.1.2", "."}, config{dir: ".", addr: "127.31.1.2", restart: true}},+	{[]string{"--restart", "--debug", "--no-bug"}, config{restart: true, debug: true, noBug: true}},+	{[]string{"--disk=4G", "--no-bug", "docs"}, config{dir: "docs", disk: "4G", noBug: true}},+	{[]string{"-l"}, config{list: true}},+	{[]string{"--list", "docs"}, config{dir: "docs", list: true}},+	{[]string{"--share=0.0.0.0"}, config{dir: ".", sharing: true, share: "0.0.0.0"}},+	{[]string{"--share"}, config{dir: ".", sharing: true}},+	{[]string{"--share", "docs"}, config{dir: "docs", sharing: true}},+	{[]string{"--share=10.0.0.1:9000", "--secret", "docs"}, config{dir: "docs", sharing: true, share: "10.0.0.1:9000", secret: true}},+	{[]string{"--restart", "--share=10.0.0.1"}, config{sharing: true, share: "10.0.0.1", restart: true}},+	{[]string{"--kill"}, config{kill: true}},+	{[]string{"--commit=261024.4"}, config{dir: ".", commit: "261024.4"}},+	{[]string{"--commit", "HEAD", "docs"}, config{dir: "docs", commit: "HEAD"}},+	{[]string{"--kill", "--share"}, config{sharing: true, kill: true}},+	{[]string{"--public", "--disk=6G", "-a", "127.0.0.1:8080"}, config{public: true, disk: "6G", addr: "127.0.0.1:8080"}},+	{[]string{"--public", "--disk=6G", "--share=0.0.0.0", "--debug"}, config{public: true, disk: "6G", sharing: true, share: "0.0.0.0", debug: true}},+}++// The usage text is docopt, and docopt takes every command line in+// parseOK. docopt-ng runs through uv, and the test skips without it.+// docopt has no option with an optional value, so --share[=HOST] is+// checked as --share.+func TestUsageIsDocopt(t *testing.T) {+	uv, err := exec.LookPath("uv")+	if err != nil {+		t.Skip("uv is not installed")+	}+	usage := filepath.Join(t.TempDir(), "usage")+	if err := os.WriteFile(usage, []byte(strings.ReplaceAll(usageText, "--share[=HOST]", "--share")), 0o600); err != nil {+		t.Fatal(err)+	}+	var lines strings.Builder+	for _, tc := range parseOK {+		args := make([]string, len(tc.args))+		for i, a := range tc.args {+			if strings.HasPrefix(a, "--share=") {+				a = "--share"+			}+			args[i] = shellQuote(a)+		}+		lines.WriteString(strings.Join(args, " ") + "\n")+	}+	cmd := exec.Command(uv, "run", "--quiet", "--script", "testdata/docopt-check.py", usage)+	cmd.Stdin = strings.NewReader(lines.String())+	out, err := cmd.CombinedOutput()+	var ee *exec.ExitError+	switch {+	case err == nil:+	case errors.As(err, &ee) && ee.ExitCode() == 22:+		t.Errorf("docopt-ng:\n%s", out)+	default:+		t.Skipf("uv could not run docopt-ng: %v\n%s", err, out)+	}+}++// shellQuote quotes s for shlex.split.+func shellQuote(s string) string {+	return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'"+}