mirror of
https://github.com/cheat/cheat.git
synced 2025-09-04 11:08:29 +02:00
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:
23
internal/sheets/consolidate.go
Normal file
23
internal/sheets/consolidate.go
Normal 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
|
||||
}
|
50
internal/sheets/consolidate_test.go
Normal file
50
internal/sheets/consolidate_test.go
Normal 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
53
internal/sheets/filter.go
Normal 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
|
||||
}
|
94
internal/sheets/filter_test.go
Normal file
94
internal/sheets/filter_test.go
Normal 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
77
internal/sheets/load.go
Normal 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
|
||||
}
|
3
internal/sheets/load_test.go
Normal file
3
internal/sheets/load_test.go
Normal file
@ -0,0 +1,3 @@
|
||||
package sheets
|
||||
|
||||
// TODO
|
32
internal/sheets/sort.go
Normal file
32
internal/sheets/sort.go
Normal 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
|
||||
}
|
34
internal/sheets/sort_test.go
Normal file
34
internal/sheets/sort_test.go
Normal 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user