Re-wrote from scratch in Golang

- Re-implemented the project in Golang, and deprecated Python entirely
- Implemented several new, long-requested features
- Refactored cheatsheets into a separate repository
This commit is contained in:
Chris Lane
2019-10-20 10:02:28 -04:00
parent 307c4e6ad6
commit e5114a3e76
271 changed files with 2630 additions and 7834 deletions

View File

@ -0,0 +1,23 @@
package sheets
import (
"github.com/cheat/cheat/internal/sheet"
)
// Consolidate applies cheatsheet "overrides", resolving title conflicts that
// exist among cheatpaths by preferring more local cheatsheets over less local
// cheatsheets.
func Consolidate(
cheatpaths []map[string]sheet.Sheet,
) map[string]sheet.Sheet {
consolidated := make(map[string]sheet.Sheet)
for _, cheatpath := range cheatpaths {
for title, sheet := range cheatpath {
consolidated[title] = sheet
}
}
return consolidated
}

View File

@ -0,0 +1,50 @@
package sheets
import (
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/cheat/cheat/internal/sheet"
)
// TestConsolidate asserts that cheatsheets are properly consolidated
func TestConsolidate(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", Path: "community/foo"},
"bar": sheet.Sheet{Title: "bar", Path: "community/bar"},
},
// mock local cheatsheets
map[string]sheet.Sheet{
"bar": sheet.Sheet{Title: "bar", Path: "local/bar"},
"baz": sheet.Sheet{Title: "baz", Path: "local/baz"},
},
}
// consolidate the cheatsheets
consolidated := Consolidate(cheatpaths)
// specify the expected output
want := map[string]sheet.Sheet{
"foo": sheet.Sheet{Title: "foo", Path: "community/foo"},
"bar": sheet.Sheet{Title: "bar", Path: "local/bar"},
"baz": sheet.Sheet{Title: "baz", Path: "local/baz"},
}
// assert that the cheatsheets properly consolidated
if !reflect.DeepEqual(consolidated, want) {
t.Errorf(
"failed to consolidate cheatpaths: want:\n%s, got:\n%s",
spew.Sdump(want),
spew.Sdump(consolidated),
)
}
}

53
internal/sheets/filter.go Normal file
View File

@ -0,0 +1,53 @@
package sheets
import (
"strings"
"github.com/cheat/cheat/internal/sheet"
)
// Filter filters cheatsheets that do not match `tag(s)`
func Filter(
cheatpaths []map[string]sheet.Sheet,
tags []string,
) []map[string]sheet.Sheet {
// buffer a map of filtered cheatsheets
filtered := make([]map[string]sheet.Sheet, 0, len(cheatpaths))
// iterate over each cheatpath
for _, cheatsheets := range cheatpaths {
// create a map of cheatsheets for each cheatpath. The filtering will be
// applied to each cheatpath individually.
pathFiltered := make(map[string]sheet.Sheet)
// iterate over each cheatsheet that exists on each cheatpath
for title, sheet := range cheatsheets {
// assume that the sheet should be kept (ie, should not be filtered)
keep := true
// iterate over each tag. If the sheet does not match *all* tags, filter
// it out.
for _, tag := range tags {
if !sheet.Tagged(strings.TrimSpace(tag)) {
keep = false
}
}
// if the sheet does match all tags, it passes the filter
if keep {
pathFiltered[title] = sheet
}
}
// the sheets on this individual cheatpath have now been filtered. Now,
// store those alongside the sheets on the other cheatpaths that also made
// it passed the filter.
filtered = append(filtered, pathFiltered)
}
// return the filtered cheatsheets on all paths
return filtered
}

View File

@ -0,0 +1,94 @@
package sheets
import (
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/cheat/cheat/internal/sheet"
)
// TestFilterSingleTag asserts that Filter properly filters results when passed
// a single tag
func TestFilterSingleTag(t *testing.T) {
// mock cheatsheets available on multiple cheatpaths
cheatpaths := []map[string]sheet.Sheet{
map[string]sheet.Sheet{
"foo": sheet.Sheet{Title: "foo", Tags: []string{"alpha", "bravo"}},
"bar": sheet.Sheet{Title: "bar", Tags: []string{"bravo", "charlie"}},
},
map[string]sheet.Sheet{
"baz": sheet.Sheet{Title: "baz", Tags: []string{"alpha", "bravo"}},
"bat": sheet.Sheet{Title: "bat", Tags: []string{"bravo", "charlie"}},
},
}
// filter the cheatsheets
filtered := Filter(cheatpaths, []string{"bravo"})
// assert that the expect results were returned
want := []map[string]sheet.Sheet{
map[string]sheet.Sheet{
"foo": sheet.Sheet{Title: "foo", Tags: []string{"alpha", "bravo"}},
"bar": sheet.Sheet{Title: "bar", Tags: []string{"bravo", "charlie"}},
},
map[string]sheet.Sheet{
"baz": sheet.Sheet{Title: "baz", Tags: []string{"alpha", "bravo"}},
"bat": sheet.Sheet{Title: "bat", Tags: []string{"bravo", "charlie"}},
},
}
if !reflect.DeepEqual(filtered, want) {
t.Errorf(
"failed to return expected results: want:\n%s, got:\n%s",
spew.Sdump(want),
spew.Sdump(filtered),
)
}
}
// TestFilterSingleTag asserts that Filter properly filters results when passed
// multiple tags
func TestFilterMultiTag(t *testing.T) {
// mock cheatsheets available on multiple cheatpaths
cheatpaths := []map[string]sheet.Sheet{
map[string]sheet.Sheet{
"foo": sheet.Sheet{Title: "foo", Tags: []string{"alpha", "bravo"}},
"bar": sheet.Sheet{Title: "bar", Tags: []string{"bravo", "charlie"}},
},
map[string]sheet.Sheet{
"baz": sheet.Sheet{Title: "baz", Tags: []string{"alpha", "bravo"}},
"bat": sheet.Sheet{Title: "bat", Tags: []string{"bravo", "charlie"}},
},
}
// filter the cheatsheets
filtered := Filter(cheatpaths, []string{"alpha", "bravo"})
// assert that the expect results were returned
want := []map[string]sheet.Sheet{
map[string]sheet.Sheet{
"foo": sheet.Sheet{Title: "foo", Tags: []string{"alpha", "bravo"}},
},
map[string]sheet.Sheet{
"baz": sheet.Sheet{Title: "baz", Tags: []string{"alpha", "bravo"}},
},
}
if !reflect.DeepEqual(filtered, want) {
t.Errorf(
"failed to return expected results: want:\n%s, got:\n%s",
spew.Sdump(want),
spew.Sdump(filtered),
)
}
}

77
internal/sheets/load.go Normal file
View File

@ -0,0 +1,77 @@
package sheets
import (
"fmt"
"os"
"path/filepath"
"strings"
cp "github.com/cheat/cheat/internal/cheatpath"
"github.com/cheat/cheat/internal/sheet"
)
// Load produces a map of cheatsheet titles to filesystem paths
func Load(cheatpaths []cp.Cheatpath) ([]map[string]sheet.Sheet, error) {
// create a slice of maps of sheets. This structure will store all sheets
// that are associated with each cheatpath.
sheets := make([]map[string]sheet.Sheet, len(cheatpaths))
// iterate over each cheatpath
for _, cheatpath := range cheatpaths {
// vivify the map of cheatsheets on this specific cheatpath
pathsheets := make(map[string]sheet.Sheet)
// recursively iterate over the cheatpath, and load each cheatsheet
// encountered along the way
err := filepath.Walk(
cheatpath.Path, func(
path string,
info os.FileInfo,
err error) error {
// fail if an error occurred while walking the directory
if err != nil {
return fmt.Errorf("error walking path: %v", err)
}
// don't register directories as cheatsheets
if info.IsDir() {
return nil
}
// calculate the cheatsheet's "title" (the phrase with which it may be
// accessed. Eg: `cheat tar` - `tar` is the title)
title := strings.TrimPrefix(
strings.TrimPrefix(path, cheatpath.Path),
"/",
)
// ignore dotfiles. Otherwise, we'll likely load .git/*
if strings.HasPrefix(title, ".") {
return nil
}
// parse the cheatsheet file into a `sheet` struct
s, err := sheet.New(title, path, cheatpath.Tags, cheatpath.ReadOnly)
if err != nil {
return fmt.Errorf("could not create sheet: %v", err)
}
// register the cheatsheet on its cheatpath, keyed by its title
pathsheets[title] = s
return nil
})
if err != nil {
return sheets, fmt.Errorf("failed to load cheatsheets: %v", err)
}
// store the sheets on this cheatpath alongside the other cheatsheets on
// other cheatpaths
sheets = append(sheets, pathsheets)
}
// return the cheatsheets, grouped by cheatpath
return sheets, nil
}

View File

@ -0,0 +1,3 @@
package sheets
// TODO

32
internal/sheets/sort.go Normal file
View File

@ -0,0 +1,32 @@
package sheets
import (
"sort"
"github.com/cheat/cheat/internal/sheet"
)
// Sort organizes the cheatsheets into an alphabetically-sorted slice
func Sort(cheatsheets map[string]sheet.Sheet) []sheet.Sheet {
// create a slice that contains the cheatsheet titles
var titles []string
for title := range cheatsheets {
titles = append(titles, title)
}
// sort the slice of titles
sort.Strings(titles)
// create a slice of sorted cheatsheets
sorted := []sheet.Sheet{}
// iterate over the sorted slice of titles, and append cheatsheets to
// `sorted` in an identical (alabetically sequential) order
for _, title := range titles {
sorted = append(sorted, cheatsheets[title])
}
// return the sorted slice of cheatsheets
return sorted
}

View File

@ -0,0 +1,34 @@
package sheets
import (
"testing"
"github.com/cheat/cheat/internal/sheet"
)
// TestSort asserts that Sort properly sorts sheets
func TestSort(t *testing.T) {
// mock a map of cheatsheets
sheets := map[string]sheet.Sheet{
"foo": sheet.Sheet{Title: "foo"},
"bar": sheet.Sheet{Title: "bar"},
"baz": sheet.Sheet{Title: "baz"},
}
// sort the sheets
sorted := Sort(sheets)
// assert that the sheets sorted properly
want := []string{"bar", "baz", "foo"}
for i, got := range sorted {
if got.Title != want[i] {
t.Errorf(
"sort returned incorrect value: want: %s, got: %s",
want[i],
got.Title,
)
}
}
}