From aeaf01e1de67e00f722d5cd6ea0f79de487b2d55 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Thu, 14 Nov 2019 21:50:49 -0500 Subject: [PATCH 1/4] feat: implements --tags Implements `--tags`, which lists all tags in use. --- cmd/cheat/cmd_tags.go | 25 ++++++++++++++++++ cmd/cheat/docopt.txt | 4 +++ cmd/cheat/main.go | 3 +++ cmd/cheat/str_usage.go | 1 + internal/sheets/tags.go | 36 +++++++++++++++++++++++++ internal/sheets/tags_test.go | 51 ++++++++++++++++++++++++++++++++++++ 6 files changed, 120 insertions(+) create mode 100644 cmd/cheat/cmd_tags.go create mode 100644 internal/sheets/tags.go create mode 100644 internal/sheets/tags_test.go diff --git a/cmd/cheat/cmd_tags.go b/cmd/cheat/cmd_tags.go new file mode 100644 index 0000000..a17c87c --- /dev/null +++ b/cmd/cheat/cmd_tags.go @@ -0,0 +1,25 @@ +package main + +import ( + "fmt" + "os" + + "github.com/cheat/cheat/internal/config" + "github.com/cheat/cheat/internal/sheets" +) + +// cmdTags lists all tags in use. +func cmdTags(opts map[string]interface{}, conf config.Config) { + + // load the cheatsheets + cheatsheets, err := sheets.Load(conf.Cheatpaths) + if err != nil { + fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to list cheatsheets: %v", err)) + os.Exit(1) + } + + // write sheet tags to stdout + for _, tag := range sheets.Tags(cheatsheets) { + fmt.Println(tag) + } +} diff --git a/cmd/cheat/docopt.txt b/cmd/cheat/docopt.txt index effe2cc..4b4d87b 100644 --- a/cmd/cheat/docopt.txt +++ b/cmd/cheat/docopt.txt @@ -11,6 +11,7 @@ Options: -r --regex Treat search as a regex -s --search= Search cheatsheets for -t --tag= Return only sheets matching + -T --tags List all tags in use -v --version Print the version number Examples: @@ -33,6 +34,9 @@ Examples: To list all available cheatsheets: cheat -l + To list all tags in use: + cheat -T + To list available cheatsheets that are tagged as "personal": cheat -l -t personal diff --git a/cmd/cheat/main.go b/cmd/cheat/main.go index a4608ab..c36b569 100755 --- a/cmd/cheat/main.go +++ b/cmd/cheat/main.go @@ -76,6 +76,9 @@ func main() { case opts["--list"].(bool): cmd = cmdList + case opts["--tags"].(bool): + cmd = cmdTags + case opts["--search"] != nil: cmd = cmdSearch diff --git a/cmd/cheat/str_usage.go b/cmd/cheat/str_usage.go index 49baa38..482df0f 100644 --- a/cmd/cheat/str_usage.go +++ b/cmd/cheat/str_usage.go @@ -20,6 +20,7 @@ Options: -r --regex Treat search as a regex -s --search= Search cheatsheets for -t --tag= Return only sheets matching + -T --tags List all tags in use -v --version Print the version number Examples: diff --git a/internal/sheets/tags.go b/internal/sheets/tags.go new file mode 100644 index 0000000..1d7b0e7 --- /dev/null +++ b/internal/sheets/tags.go @@ -0,0 +1,36 @@ +package sheets + +import ( + "sort" + + "github.com/cheat/cheat/internal/sheet" +) + +// Tags returns a slice of all tags in use in any sheet +func Tags(cheatpaths []map[string]sheet.Sheet) []string { + + // create a map of all tags in use in any sheet + tags := make(map[string]bool) + + // iterate over all tags on all sheets on all cheatpaths + for _, path := range cheatpaths { + for _, sheet := range path { + for _, tag := range sheet.Tags { + tags[tag] = true + } + } + } + + // restructure the map into a slice + sorted := []string{} + for tag := range tags { + sorted = append(sorted, tag) + } + + // sort the slice + sort.Slice(sorted, func(i, j int) bool { + return sorted[i] < sorted[j] + }) + + return sorted +} diff --git a/internal/sheets/tags_test.go b/internal/sheets/tags_test.go new file mode 100644 index 0000000..54d156b --- /dev/null +++ b/internal/sheets/tags_test.go @@ -0,0 +1,51 @@ +package sheets + +import ( + "reflect" + "testing" + + "github.com/davecgh/go-spew/spew" + + "github.com/cheat/cheat/internal/sheet" +) + +// TestTags asserts that cheetsheet tags are properly returned +func TestTags(t *testing.T) { + + // mock cheatsheets available on multiple cheatpaths + cheatpaths := []map[string]sheet.Sheet{ + + // mock community cheatsheets + map[string]sheet.Sheet{ + "foo": sheet.Sheet{Title: "foo", Tags: []string{"alpha"}}, + "bar": sheet.Sheet{Title: "bar", Tags: []string{"alpha", "bravo"}}, + }, + + // mock local cheatsheets + map[string]sheet.Sheet{ + "bar": sheet.Sheet{Title: "bar", Tags: []string{"bravo", "charlie"}}, + "baz": sheet.Sheet{Title: "baz", Tags: []string{"delta"}}, + }, + } + + // consolidate the cheatsheets + tags := Tags(cheatpaths) + + // specify the expected output + want := []string{ + "alpha", + "bravo", + "charlie", + "delta", + } + + // assert that the cheatsheets properly consolidated + if !reflect.DeepEqual(tags, want) { + t.Errorf( + "failed to return tags: want:\n%s, got:\n%s", + spew.Sdump(want), + spew.Sdump(tags), + ) + } + +} From 9a6130b6b7d47652d37e080b13c4f207a3a08a2b Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Sat, 16 Nov 2019 09:03:03 -0500 Subject: [PATCH 2/4] feat: --rm and --tags - Implements the `--rm` command (#483) - Implements the `--tags` command (#484) - Bumps version to `3.1.0` --- cmd/cheat/cmd_remove.go | 55 +++++++++++++++++++++++++++++++++++++++++ cmd/cheat/docopt.txt | 4 +++ cmd/cheat/main.go | 5 +++- cmd/cheat/str_usage.go | 7 ++++++ 4 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 cmd/cheat/cmd_remove.go diff --git a/cmd/cheat/cmd_remove.go b/cmd/cheat/cmd_remove.go new file mode 100644 index 0000000..caaa09a --- /dev/null +++ b/cmd/cheat/cmd_remove.go @@ -0,0 +1,55 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/cheat/cheat/internal/config" + "github.com/cheat/cheat/internal/sheets" +) + +// cmdRemove opens a cheatsheet for editing (or creates it if it doesn't exist). +func cmdRemove(opts map[string]interface{}, conf config.Config) { + + cheatsheet := opts["--rm"].(string) + + // load the cheatsheets + cheatsheets, err := sheets.Load(conf.Cheatpaths) + if err != nil { + fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to list cheatsheets: %v", err)) + os.Exit(1) + } + + // filter cheatcheats by tag if --tag was provided + if opts["--tag"] != nil { + cheatsheets = sheets.Filter( + cheatsheets, + strings.Split(opts["--tag"].(string), ","), + ) + } + + // consolidate the cheatsheets found on all paths into a single map of + // `title` => `sheet` (ie, allow more local cheatsheets to override less + // local cheatsheets) + consolidated := sheets.Consolidate(cheatsheets) + + // fail early if the requested cheatsheet does not exist + sheet, ok := consolidated[cheatsheet] + if !ok { + fmt.Fprintln(os.Stderr, fmt.Sprintf("no cheatsheet found for '%s'.\n", cheatsheet)) + os.Exit(1) + } + + // fail early if the sheet is read-only + if sheet.ReadOnly { + fmt.Fprintln(os.Stderr, fmt.Sprintf("cheatsheet '%s' is read-only.", cheatsheet)) + os.Exit(1) + } + + // otherwise, attempt to delete the sheet + if err := os.Remove(sheet.Path); err != nil { + fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to delete sheet: %s, %v", sheet.Title, err)) + os.Exit(1) + } +} diff --git a/cmd/cheat/docopt.txt b/cmd/cheat/docopt.txt index 4b4d87b..ccf4fae 100644 --- a/cmd/cheat/docopt.txt +++ b/cmd/cheat/docopt.txt @@ -13,6 +13,7 @@ Options: -t --tag= Return only sheets matching -T --tags List all tags in use -v --version Print the version number + --rm= Remove (delete) Examples: @@ -45,3 +46,6 @@ Examples: To search (by regex) for cheatsheets that contain an IP address: cheat -c -r -s '(?:[0-9]{1,3}\.){3}[0-9]{1,3}' + + To remove (delete) the foo/bar cheatsheet: + cheat --rm foo/bar diff --git a/cmd/cheat/main.go b/cmd/cheat/main.go index c36b569..036e1c3 100755 --- a/cmd/cheat/main.go +++ b/cmd/cheat/main.go @@ -13,7 +13,7 @@ import ( "github.com/cheat/cheat/internal/config" ) -const version = "3.0.7" +const version = "3.1.0" func main() { @@ -82,6 +82,9 @@ func main() { case opts["--search"] != nil: cmd = cmdSearch + case opts["--rm"] != nil: + cmd = cmdRemove + case opts[""] != nil: cmd = cmdView diff --git a/cmd/cheat/str_usage.go b/cmd/cheat/str_usage.go index 482df0f..c794c8b 100644 --- a/cmd/cheat/str_usage.go +++ b/cmd/cheat/str_usage.go @@ -22,6 +22,7 @@ Options: -t --tag= Return only sheets matching -T --tags List all tags in use -v --version Print the version number + --rm= Remove (delete) Examples: @@ -43,6 +44,9 @@ Examples: To list all available cheatsheets: cheat -l + To list all tags in use: + cheat -T + To list available cheatsheets that are tagged as "personal": cheat -l -t personal @@ -51,5 +55,8 @@ Examples: To search (by regex) for cheatsheets that contain an IP address: cheat -c -r -s '(?:[0-9]{1,3}\.){3}[0-9]{1,3}' + + To remove (delete) the foo/bar cheatsheet: + cheat --rm foo/bar `) } From eab3c14f1fcac27b1c06dc47527c9224fad0869e Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Sat, 16 Nov 2019 09:18:45 -0500 Subject: [PATCH 3/4] fix: broken logline in --search Resolves a malformed logging statement in `cmd_search` which would never actually write to stderr. --- cmd/cheat/cmd_search.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/cheat/cmd_search.go b/cmd/cheat/cmd_search.go index 89652b1..ebc53e1 100644 --- a/cmd/cheat/cmd_search.go +++ b/cmd/cheat/cmd_search.go @@ -55,7 +55,7 @@ func cmdSearch(opts map[string]interface{}, conf config.Config) { // compile the regex reg, err := regexp.Compile(pattern) if err != nil { - fmt.Errorf("failed to compile regexp: %s, %v", pattern, err) + fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to compile regexp: %s, %v", pattern, err)) os.Exit(1) } From 879e8f2be4b2b09f8c496c0101d4df7d6619c1d2 Mon Sep 17 00:00:00 2001 From: Chris Lane Date: Sat, 16 Nov 2019 09:23:45 -0500 Subject: [PATCH 4/4] chore: trivial copy change in docopt.txt --- cmd/cheat/docopt.txt | 2 +- cmd/cheat/str_usage.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/cheat/docopt.txt b/cmd/cheat/docopt.txt index ccf4fae..609f209 100644 --- a/cmd/cheat/docopt.txt +++ b/cmd/cheat/docopt.txt @@ -5,7 +5,7 @@ Options: --init Write a default config file to stdout -c --colorize Colorize output -d --directories List cheatsheet directories - -e --edit= Edit cheatsheet + -e --edit= Edit -l --list List cheatsheets -p --path= Return only sheets found on path -r --regex Treat search as a regex diff --git a/cmd/cheat/str_usage.go b/cmd/cheat/str_usage.go index c794c8b..dfcc96c 100644 --- a/cmd/cheat/str_usage.go +++ b/cmd/cheat/str_usage.go @@ -14,7 +14,7 @@ Options: --init Write a default config file to stdout -c --colorize Colorize output -d --directories List cheatsheet directories - -e --edit= Edit cheatsheet + -e --edit= Edit -l --list List cheatsheets -p --path= Return only sheets found on path -r --regex Treat search as a regex