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

51
internal/sheet/copy.go Normal file
View File

@ -0,0 +1,51 @@
package sheet
import (
"fmt"
"io"
"os"
"path"
)
// Copy copies a cheatsheet to a new location
func (s *Sheet) Copy(dest string) error {
// NB: while the `infile` has already been loaded and parsed into a `sheet`
// struct, we're going to read it again here. This is a bit wasteful, but
// necessary if we want the "raw" file contents (including the front-matter).
// This is because the frontmatter is parsed and then discarded when the file
// is loaded via `sheets.Load`.
infile, err := os.Open(s.Path)
if err != nil {
return fmt.Errorf("failed to open cheatsheet: %s, %v", s.Path, err)
}
defer infile.Close()
// create any necessary subdirectories
dirs := path.Dir(dest)
if dirs != "." {
if err := os.MkdirAll(dirs, 0755); err != nil {
return fmt.Errorf("failed to create directory: %s, %v", dirs, err)
}
}
// create the outfile
outfile, err := os.Create(dest)
if err != nil {
return fmt.Errorf("failed to create outfile: %s, %v", dest, err)
}
defer outfile.Close()
// copy file contents
_, err = io.Copy(outfile, infile)
if err != nil {
return fmt.Errorf(
"failed to copy file: infile: %s, outfile: %s, err: %v",
s.Path,
dest,
err,
)
}
return nil
}

102
internal/sheet/copy_test.go Normal file
View File

@ -0,0 +1,102 @@
package sheet
import (
"io/ioutil"
"os"
"path"
"testing"
)
// TestCopyFlat asserts that Copy correctly copies files at a single level of
// depth
func TestCopyFlat(t *testing.T) {
// mock a cheatsheet file
text := "this is the cheatsheet text"
src, err := ioutil.TempFile("", "foo-src")
if err != nil {
t.Errorf("failed to mock cheatsheet: %v", err)
}
defer src.Close()
defer os.Remove(src.Name())
if _, err := src.WriteString(text); err != nil {
t.Errorf("failed to write to mock cheatsheet: %v", err)
}
// mock a cheatsheet struct
sheet, err := New("foo", src.Name(), []string{}, false)
if err != nil {
t.Errorf("failed to init cheatsheet: %v", err)
}
// compute the outfile's path
outpath := path.Join(os.TempDir(), sheet.Title)
defer os.Remove(outpath)
// attempt to copy the cheatsheet
err = sheet.Copy(outpath)
if err != nil {
t.Errorf("failed to copy cheatsheet: %v", err)
}
// assert that the destination file contains the correct text
got, err := ioutil.ReadFile(outpath)
if err != nil {
t.Errorf("failed to read destination file: %v", err)
}
if string(got) != text {
t.Errorf(
"destination file contained wrong text: want: '%s', got: '%s'",
text,
got,
)
}
}
// TestCopyDeep asserts that Copy correctly copies files at several levels of
// depth
func TestCopyDeep(t *testing.T) {
// mock a cheatsheet file
text := "this is the cheatsheet text"
src, err := ioutil.TempFile("", "foo-src")
if err != nil {
t.Errorf("failed to mock cheatsheet: %v", err)
}
defer src.Close()
defer os.Remove(src.Name())
if _, err := src.WriteString(text); err != nil {
t.Errorf("failed to write to mock cheatsheet: %v", err)
}
// mock a cheatsheet struct
sheet, err := New("/cheat-tests/alpha/bravo/foo", src.Name(), []string{}, false)
if err != nil {
t.Errorf("failed to init cheatsheet: %v", err)
}
// compute the outfile's path
outpath := path.Join(os.TempDir(), sheet.Title)
defer os.RemoveAll(path.Join(os.TempDir(), "cheat-tests"))
// attempt to copy the cheatsheet
err = sheet.Copy(outpath)
if err != nil {
t.Errorf("failed to copy cheatsheet: %v", err)
}
// assert that the destination file contains the correct text
got, err := ioutil.ReadFile(outpath)
if err != nil {
t.Errorf("failed to read destination file: %v", err)
}
if string(got) != text {
t.Errorf(
"destination file contained wrong text: want: '%s', got: '%s'",
text,
got,
)
}
}

7
internal/sheet/match.go Normal file
View File

@ -0,0 +1,7 @@
package sheet
// Match encapsulates search matches within cheatsheets
type Match struct {
Line int
Text string
}

45
internal/sheet/search.go Normal file
View File

@ -0,0 +1,45 @@
package sheet
import (
"regexp"
"strings"
"github.com/mgutz/ansi"
)
// Search searches for regexp matches in a cheatsheet's text, and optionally
// colorizes matching strings.
func (s *Sheet) Search(reg *regexp.Regexp, colorize bool) []Match {
// record matches
matches := []Match{}
// search through the cheatsheet's text line by line
// TODO: searching line-by-line is surely the "naive" approach. Revisit this
// later with an eye for performance improvements.
for linenum, line := range strings.Split(s.Text, "\n") {
// exit early if the line doesn't match the regex
if !reg.MatchString(line) {
continue
}
// init the match
m := Match{
Line: linenum + 1,
Text: strings.TrimSpace(line),
}
// colorize the matching text if so configured
if colorize {
m.Text = reg.ReplaceAllStringFunc(m.Text, func(matched string) string {
return ansi.Color(matched, "red+b")
})
}
// record the match
matches = append(matches, m)
}
return matches
}

View File

@ -0,0 +1,185 @@
package sheet
import (
"reflect"
"regexp"
"testing"
"github.com/davecgh/go-spew/spew"
)
// TestSearchNoMatch ensures that the expected output is returned when no
// matches are found
func TestSearchNoMatch(t *testing.T) {
// mock a cheatsheet
sheet := Sheet{
Text: "The quick brown fox\njumped over\nthe lazy dog.",
}
// compile the search regex
reg, err := regexp.Compile("(?i)foo")
if err != nil {
t.Errorf("failed to compile regex: %v", err)
}
// search the sheet
matches := sheet.Search(reg, false)
// assert that no matches were found
if len(matches) != 0 {
t.Errorf("failure: expected no matches: got: %s", spew.Sdump(matches))
}
}
// TestSearchSingleMatchNoColor asserts that the expected output is returned
// when a single match is returned, and no colorization is applied.
func TestSearchSingleMatchNoColor(t *testing.T) {
// mock a cheatsheet
sheet := Sheet{
Text: "The quick brown fox\njumped over\nthe lazy dog.",
}
// compile the search regex
reg, err := regexp.Compile("(?i)fox")
if err != nil {
t.Errorf("failed to compile regex: %v", err)
}
// search the sheet
matches := sheet.Search(reg, false)
// specify the expected results
want := []Match{
Match{
Line: 1,
Text: "The quick brown fox",
},
}
// assert that the correct matches were returned
if !reflect.DeepEqual(matches, want) {
t.Errorf(
"failed to return expected matches: want:\n%s, got:\n%s",
spew.Sdump(want),
spew.Sdump(matches),
)
}
}
// TestSearchSingleMatchColorized asserts that the expected output is returned
// when a single match is returned, and colorization is applied
func TestSearchSingleMatchColorized(t *testing.T) {
// mock a cheatsheet
sheet := Sheet{
Text: "The quick brown fox\njumped over\nthe lazy dog.",
}
// compile the search regex
reg, err := regexp.Compile("(?i)fox")
if err != nil {
t.Errorf("failed to compile regex: %v", err)
}
// search the sheet
matches := sheet.Search(reg, true)
// specify the expected results
want := []Match{
Match{
Line: 1,
Text: "The quick brown \x1b[1;31mfox\x1b[0m",
},
}
// assert that the correct matches were returned
if !reflect.DeepEqual(matches, want) {
t.Errorf(
"failed to return expected matches: want:\n%s, got:\n%s",
spew.Sdump(want),
spew.Sdump(matches),
)
}
}
// TestSearchMultiMatchNoColor asserts that the expected output is returned
// when a multiple matches are returned, and no colorization is applied
func TestSearchMultiMatchNoColor(t *testing.T) {
// mock a cheatsheet
sheet := Sheet{
Text: "The quick brown fox\njumped over\nthe lazy dog.",
}
// compile the search regex
reg, err := regexp.Compile("(?i)the")
if err != nil {
t.Errorf("failed to compile regex: %v", err)
}
// search the sheet
matches := sheet.Search(reg, false)
// specify the expected results
want := []Match{
Match{
Line: 1,
Text: "The quick brown fox",
},
Match{
Line: 3,
Text: "the lazy dog.",
},
}
// assert that the correct matches were returned
if !reflect.DeepEqual(matches, want) {
t.Errorf(
"failed to return expected matches: want:\n%s, got:\n%s",
spew.Sdump(want),
spew.Sdump(matches),
)
}
}
// TestSearchMultiMatchColorized asserts that the expected output is returned
// when a multiple matches are returned, and colorization is applied
func TestSearchMultiMatchColorized(t *testing.T) {
// mock a cheatsheet
sheet := Sheet{
Text: "The quick brown fox\njumped over\nthe lazy dog.",
}
// compile the search regex
reg, err := regexp.Compile("(?i)the")
if err != nil {
t.Errorf("failed to compile regex: %v", err)
}
// search the sheet
matches := sheet.Search(reg, true)
// specify the expected results
want := []Match{
Match{
Line: 1,
Text: "\x1b[1;31mThe\x1b[0m quick brown fox",
},
Match{
Line: 3,
Text: "\x1b[1;31mthe\x1b[0m lazy dog.",
},
}
// assert that the correct matches were returned
if !reflect.DeepEqual(matches, want) {
t.Errorf(
"failed to return expected matches: want:\n%s, got:\n%s",
spew.Sdump(want),
spew.Sdump(matches),
)
}
}

64
internal/sheet/sheet.go Normal file
View File

@ -0,0 +1,64 @@
package sheet
import (
"fmt"
"io/ioutil"
"sort"
"strings"
"github.com/tj/front"
)
// frontmatter is an un-exported helper struct used in parsing cheatsheets
type frontmatter struct {
Tags []string
Syntax string
}
// Sheet encapsulates sheet information
type Sheet struct {
Title string
Path string
Text string
Tags []string
Syntax string
ReadOnly bool
}
// New initializes a new Sheet
func New(
title string,
path string,
tags []string,
readOnly bool,
) (Sheet, error) {
// read the cheatsheet file
markdown, err := ioutil.ReadFile(path)
if err != nil {
return Sheet{}, fmt.Errorf("failed to read file: %s, %v", path, err)
}
// parse the front-matter
var fm frontmatter
text, err := front.Unmarshal(markdown, &fm)
if err != nil {
return Sheet{}, fmt.Errorf("failed to parse front-matter: %v", err)
}
// merge the sheet-specific tags into the cheatpath tags
tags = append(tags, fm.Tags...)
// sort strings so they pretty-print nicely
sort.Strings(tags)
// initialize and return a sheet
return Sheet{
Title: title,
Path: path,
Text: strings.TrimSpace(string(text)) + "\n",
Tags: tags,
Syntax: fm.Syntax,
ReadOnly: readOnly,
}, nil
}

View File

@ -0,0 +1,71 @@
package sheet
import (
"reflect"
"testing"
"github.com/cheat/cheat/internal/mock"
)
// TestSheetSuccess asserts that sheets initialize properly
func TestSheetSuccess(t *testing.T) {
// initialize a sheet
sheet, err := New(
"foo",
mock.Path("sheet/foo"),
[]string{"alpha", "bravo"},
false,
)
if err != nil {
t.Errorf("failed to load sheet: %v", err)
}
// assert that the sheet loaded correctly
if sheet.Title != "foo" {
t.Errorf("failed to init title: want: foo, got: %s", sheet.Title)
}
if sheet.Path != mock.Path("sheet/foo") {
t.Errorf(
"failed to init path: want: %s, got: %s",
mock.Path("sheet/foo"),
sheet.Path,
)
}
wantText := "# To foo the bar:\n foo bar\n"
if sheet.Text != wantText {
t.Errorf("failed to init text: want: %s, got: %s", wantText, sheet.Text)
}
// NB: tags should sort alphabetically
wantTags := []string{"alpha", "bar", "baz", "bravo", "foo"}
if !reflect.DeepEqual(sheet.Tags, wantTags) {
t.Errorf("failed to init tags: want: %v, got: %v", wantTags, sheet.Tags)
}
if sheet.Syntax != "sh" {
t.Errorf("failed to init syntax: want: sh, got: %s", sheet.Syntax)
}
if sheet.ReadOnly != false {
t.Errorf("failed to init readonly")
}
}
// TestSheetFailure asserts that an error is returned if the sheet cannot be
// read
func TestSheetFailure(t *testing.T) {
// initialize a sheet
_, err := New(
"foo",
mock.Path("/does-not-exist"),
[]string{"alpha", "bravo"},
false,
)
if err == nil {
t.Errorf("failed to return an error on unreadable sheet")
}
}

15
internal/sheet/tagged.go Normal file
View File

@ -0,0 +1,15 @@
package sheet
// Tagged returns true if a sheet was tagged with `needle`
func (s *Sheet) Tagged(needle string) bool {
// if any of the tags match `needle`, return `true`
for _, tag := range s.Tags {
if tag == needle {
return true
}
}
// otherwise, return `false`
return false
}

View File

@ -0,0 +1,26 @@
package sheet
import (
"testing"
)
// TestTagged ensures that tags are properly recognized as being absent or
// present
func TestTagged(t *testing.T) {
// initialize a cheatsheet
tags := []string{"foo", "bar", "baz"}
sheet := Sheet{Tags: tags}
// assert that set tags are recognized as set
for _, tag := range tags {
if sheet.Tagged(tag) == false {
t.Errorf("failed to recognize tag: %s", tag)
}
}
// assert that unset tags are recognized as unset
if sheet.Tagged("qux") {
t.Errorf("failed to recognize absent tag")
}
}