diff --git a/README.md b/README.md index 732fb8d..2f28f8c 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,12 @@ To list all available cheatsheets: cheat -l ``` +To briefly list all cheatsheets (names and tags only): + +```sh +cheat -b +``` + To list all cheatsheets that are tagged with "networking": ```sh diff --git a/cmd/cheat/brief_integration_test.go b/cmd/cheat/brief_integration_test.go new file mode 100644 index 0000000..929646f --- /dev/null +++ b/cmd/cheat/brief_integration_test.go @@ -0,0 +1,128 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" +) + +// TestBriefFlagIntegration exercises the -b/--brief flag end-to-end. +func TestBriefFlagIntegration(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("integration test uses Unix-specific env vars") + } + + // Build the cheat binary once for all sub-tests. + binPath := filepath.Join(t.TempDir(), "cheat_test") + build := exec.Command("go", "build", "-o", binPath, ".") + if output, err := build.CombinedOutput(); err != nil { + t.Fatalf("failed to build cheat: %v\nOutput: %s", err, output) + } + + // Set up a temp environment with some cheatsheets. + root := t.TempDir() + sheetsDir := filepath.Join(root, "sheets") + os.MkdirAll(sheetsDir, 0755) + + os.WriteFile( + filepath.Join(sheetsDir, "tar"), + []byte("---\nsyntax: bash\ntags: [ compression ]\n---\ntar xf archive.tar\n"), + 0644, + ) + os.WriteFile( + filepath.Join(sheetsDir, "curl"), + []byte("---\nsyntax: bash\ntags: [ networking, http ]\n---\ncurl https://example.com\n"), + 0644, + ) + + confPath := filepath.Join(root, "conf.yml") + conf := fmt.Sprintf("---\neditor: vi\ncolorize: false\ncheatpaths:\n - name: test\n path: %s\n readonly: true\n", sheetsDir) + os.WriteFile(confPath, []byte(conf), 0644) + + env := []string{ + "CHEAT_CONFIG_PATH=" + confPath, + "HOME=" + root, + "PATH=" + os.Getenv("PATH"), + "EDITOR=vi", + } + + run := func(t *testing.T, args ...string) string { + t.Helper() + cmd := exec.Command(binPath, args...) + cmd.Dir = root + cmd.Env = env + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("cheat %v failed: %v\nOutput: %s", args, err, output) + } + return string(output) + } + + t.Run("brief output omits file path column", func(t *testing.T) { + output := run(t, "-b") + lines := strings.Split(strings.TrimSpace(output), "\n") + + // Header should have title and tags but not file + if !strings.Contains(lines[0], "title:") { + t.Errorf("expected title: in header, got: %s", lines[0]) + } + if !strings.Contains(lines[0], "tags:") { + t.Errorf("expected tags: in header, got: %s", lines[0]) + } + if strings.Contains(lines[0], "file:") { + t.Errorf("brief output should not contain file: column, got: %s", lines[0]) + } + + // Data lines should not contain the sheets directory path + for _, line := range lines[1:] { + if strings.Contains(line, sheetsDir) { + t.Errorf("brief output should not contain file paths, got: %s", line) + } + } + }) + + t.Run("list output still includes file path column", func(t *testing.T) { + output := run(t, "-l") + lines := strings.Split(strings.TrimSpace(output), "\n") + + if !strings.Contains(lines[0], "file:") { + t.Errorf("list output should contain file: column, got: %s", lines[0]) + } + }) + + t.Run("brief with filter works", func(t *testing.T) { + output := run(t, "-b", "tar") + if !strings.Contains(output, "tar") { + t.Errorf("expected tar in output, got: %s", output) + } + if strings.Contains(output, "curl") { + t.Errorf("filter should exclude curl, got: %s", output) + } + }) + + t.Run("combined -lb works identically to -b", func(t *testing.T) { + briefOnly := run(t, "-b", "tar") + combined := run(t, "-lb", "tar") + if briefOnly != combined { + t.Errorf("-b and -lb should produce identical output\n-b:\n%s\n-lb:\n%s", briefOnly, combined) + } + }) + + t.Run("brief with tag filter works", func(t *testing.T) { + output := run(t, "-b", "-t", "networking") + if !strings.Contains(output, "curl") { + t.Errorf("expected curl in tag-filtered output, got: %s", output) + } + if strings.Contains(output, "tar") { + // tar is tagged "compression", not "networking" + t.Errorf("tag filter should exclude tar, got: %s", output) + } + if strings.Contains(output, "file:") { + t.Errorf("brief output should not contain file: column, got: %s", output) + } + }) +} diff --git a/cmd/cheat/cmd_list.go b/cmd/cheat/cmd_list.go index 28bb442..4114333 100644 --- a/cmd/cheat/cmd_list.go +++ b/cmd/cheat/cmd_list.go @@ -87,12 +87,17 @@ func cmdList(opts map[string]interface{}, conf config.Config) { var out bytes.Buffer w := tabwriter.NewWriter(&out, 0, 0, 1, ' ', 0) - // write a header row - fmt.Fprintln(w, "title:\tfile:\ttags:") - // generate sorted, columnized output - for _, sheet := range flattened { - fmt.Fprintf(w, "%s\t%s\t%s\n", sheet.Title, sheet.Path, strings.Join(sheet.Tags, ",")) + if opts["--brief"].(bool) { + fmt.Fprintln(w, "title:\ttags:") + for _, sheet := range flattened { + fmt.Fprintf(w, "%s\t%s\n", sheet.Title, strings.Join(sheet.Tags, ",")) + } + } else { + fmt.Fprintln(w, "title:\tfile:\ttags:") + for _, sheet := range flattened { + fmt.Fprintf(w, "%s\t%s\t%s\n", sheet.Title, sheet.Path, strings.Join(sheet.Tags, ",")) + } } // write columnized output to stdout diff --git a/cmd/cheat/main.go b/cmd/cheat/main.go index 90531ee..68fd6bc 100755 --- a/cmd/cheat/main.go +++ b/cmd/cheat/main.go @@ -129,7 +129,7 @@ func main() { case opts["--edit"] != nil: cmd = cmdEdit - case opts["--list"].(bool): + case opts["--list"].(bool), opts["--brief"].(bool): cmd = cmdList case opts["--tags"].(bool): diff --git a/cmd/cheat/usage.go b/cmd/cheat/usage.go index 306e8a5..10674e3 100644 --- a/cmd/cheat/usage.go +++ b/cmd/cheat/usage.go @@ -8,6 +8,7 @@ func usage() string { Options: --init Write a default config file to stdout -a --all Search among all cheatpaths + -b --brief List cheatsheets without file paths -c --colorize Colorize output -d --directories List cheatsheet directories -e --edit= Edit @@ -41,8 +42,8 @@ Examples: To list all available cheatsheets: cheat -l - To list all cheatsheets whose titles match "apt": - cheat -l apt + To briefly list all cheatsheets whose titles match "apt": + cheat -b apt To list all tags in use: cheat -T diff --git a/doc/cheat.1 b/doc/cheat.1 index 0087d1b..99a9f59 100644 --- a/doc/cheat.1 +++ b/doc/cheat.1 @@ -23,6 +23,9 @@ Display the config file path. \-a, \[en]all Search among all cheatpaths. .TP +\-b, \[en]brief +List cheatsheets without file paths. +.TP \-c, \[en]colorize Colorize output. .TP @@ -72,8 +75,8 @@ cheat \-d To list all available cheatsheets: cheat \-l .TP -To list all cheatsheets whose titles match `apt': -cheat \-l \f[I]apt\f[R] +To briefly list all cheatsheets whose titles match `apt': +cheat \-b \f[I]apt\f[R] .TP To list all tags in use: cheat \-T diff --git a/doc/cheat.1.md b/doc/cheat.1.md index ec9e14b..1d02ffa 100644 --- a/doc/cheat.1.md +++ b/doc/cheat.1.md @@ -29,6 +29,9 @@ OPTIONS -a, --all : Search among all cheatpaths. +-b, --brief +: List cheatsheets without file paths. + -c, --colorize : Colorize output. @@ -81,8 +84,8 @@ To view all cheatsheet directories: To list all available cheatsheets: : cheat -l -To list all cheatsheets whose titles match 'apt': -: cheat -l _apt_ +To briefly list all cheatsheets whose titles match 'apt': +: cheat -b _apt_ To list all tags in use: : cheat -T