mirror of
https://github.com/cheat/cheat.git
synced 2025-09-01 09:38:29 +02:00
Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
e08a4f3cec | |||
d6ebe0799d | |||
51aaaf3423 | |||
197ff58796 | |||
b8f512aae8 | |||
e7a1a296e3 | |||
f7c093bec0 | |||
c47b7f81aa | |||
52081b97ac | |||
1dda796e7c | |||
67469b0afa | |||
4f8431a600 | |||
5301442f7c | |||
cd45efcdec | |||
c31786fc5b | |||
934c36ad77 | |||
2c0099c28a | |||
749173f1f6 | |||
33e33dc7b7 | |||
201cd1d629 |
23
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
23
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Submit a bug report
|
||||
title: ''
|
||||
labels: 'bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Thanks for submitting a bug report. Please provide the following information:
|
||||
|
||||
**A description of the problem**
|
||||
Describe the problem here.
|
||||
|
||||
**cheat version info**
|
||||
Please paste the output of `cheat -v` here.
|
||||
|
||||
**cheat configuration info**
|
||||
If your bug pertains to how cheatsheets are loaded and/or displayed, please
|
||||
paste here the following information:
|
||||
|
||||
1. The output of `cheat -d`
|
||||
2. The contents of your `conf.yml` file
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: 'enhancement'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
@ -178,11 +178,11 @@ To search (by regex) for cheatsheets that contain an IP address:
|
||||
cheat -r -s '(?:[0-9]{1,3}\.){3}[0-9]{1,3}'
|
||||
```
|
||||
|
||||
Flags may be combined in inuitive ways. Example: to search sheets on the
|
||||
Flags may be combined in intuitive ways. Example: to search sheets on the
|
||||
"personal" cheatpath that are tagged with "networking" and match a regex:
|
||||
|
||||
```sh
|
||||
cheat -p personal -t networking -s --regex '(?:[0-9]{1,3}\.){3}[0-9]{1,3}'
|
||||
cheat -p personal -t networking --regex -s '(?:[0-9]{1,3}\.){3}[0-9]{1,3}'
|
||||
```
|
||||
|
||||
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
"github.com/cheat/cheat/internal/config"
|
||||
)
|
||||
|
||||
const version = "3.0.3"
|
||||
const version = "3.0.5"
|
||||
|
||||
func main() {
|
||||
|
||||
@ -39,7 +39,7 @@ func main() {
|
||||
}
|
||||
|
||||
// initialize the configs
|
||||
conf, err := config.New(opts, confpath)
|
||||
conf, err := config.New(opts, confpath, true)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to load config: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
@ -32,9 +32,9 @@ formatter: terminal16m
|
||||
# "upstream" cheatsheets.
|
||||
#
|
||||
# But what if you want to view the "upstream" cheatsheets instead of your own?
|
||||
# Cheatsheets may be filtered via 'cheat -f <tag>' in combination with other
|
||||
# Cheatsheets may be filtered via 'cheat -t <tag>' in combination with other
|
||||
# commands. So, if you want to view the 'tar' cheatsheet that is tagged as
|
||||
# 'community' rather than your own, you can use: cheat tar -f community
|
||||
# 'community' rather than your own, you can use: cheat tar -t community
|
||||
cheatpaths:
|
||||
|
||||
# Paths that come earlier are considered to be the most "global", and will
|
||||
|
@ -23,9 +23,9 @@ formatter: terminal16m
|
||||
# "upstream" cheatsheets.
|
||||
#
|
||||
# But what if you want to view the "upstream" cheatsheets instead of your own?
|
||||
# Cheatsheets may be filtered via 'cheat -f <tag>' in combination with other
|
||||
# Cheatsheets may be filtered via 'cheat -t <tag>' in combination with other
|
||||
# commands. So, if you want to view the 'tar' cheatsheet that is tagged as
|
||||
# 'community' rather than your own, you can use: cheat tar -f community
|
||||
# 'community' rather than your own, you can use: cheat tar -t community
|
||||
cheatpaths:
|
||||
|
||||
# Paths that come earlier are considered to be the most "global", and will
|
||||
|
3
go.mod
3
go.mod
@ -9,7 +9,6 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.10
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/tj/front v0.0.0-20170212063142-739be213b0a1
|
||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 // indirect
|
||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0
|
||||
gopkg.in/yaml.v2 v2.2.4
|
||||
)
|
||||
|
2
go.sum
2
go.sum
@ -50,8 +50,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/tj/front v0.0.0-20170212063142-739be213b0a1 h1:lA+aPRvltlx2fwv/BnxyYSDQo3pIeqzHgMO5GvK0T9E=
|
||||
github.com/tj/front v0.0.0-20170212063142-739be213b0a1/go.mod h1:deJrtusCTptAW4EUn5vBLpl3dhNqPqUwEjWJz5UNxpQ=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 h1:YAFjXN64LMvktoUZH9zgY4lGc/msGN7HQfoSuKCgaDU=
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
cp "github.com/cheat/cheat/internal/cheatpath"
|
||||
|
||||
@ -21,7 +22,7 @@ type Config struct {
|
||||
}
|
||||
|
||||
// New returns a new Config struct
|
||||
func New(opts map[string]interface{}, confPath string) (Config, error) {
|
||||
func New(opts map[string]interface{}, confPath string, resolve bool) (Config, error) {
|
||||
|
||||
// read the config file
|
||||
buf, err := ioutil.ReadFile(confPath)
|
||||
@ -38,14 +39,34 @@ func New(opts map[string]interface{}, confPath string) (Config, error) {
|
||||
return Config{}, fmt.Errorf("could not unmarshal yaml: %v", err)
|
||||
}
|
||||
|
||||
// expand ~ in config paths
|
||||
// process cheatpaths
|
||||
for i, cheatpath := range conf.Cheatpaths {
|
||||
|
||||
// expand ~ in config paths
|
||||
expanded, err := homedir.Expand(cheatpath.Path)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("failed to expand ~: %v", err)
|
||||
}
|
||||
|
||||
// follow symlinks
|
||||
//
|
||||
// NB: `resolve` is an ugly kludge that exists for the sake of unit-tests.
|
||||
// It's necessary because `EvalSymlinks` will error if the symlink points
|
||||
// to a non-existent location on the filesystem. When unit-testing,
|
||||
// however, we don't want to have dependencies on the filesystem. As such,
|
||||
// `resolve` is a switch that allows us to turn off symlink resolution when
|
||||
// running the config tests.
|
||||
if resolve {
|
||||
expanded, err = filepath.EvalSymlinks(expanded)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf(
|
||||
"failed to resolve symlink: %s, %v",
|
||||
expanded,
|
||||
err,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
conf.Cheatpaths[i].Path = expanded
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ import (
|
||||
func TestConfigSuccessful(t *testing.T) {
|
||||
|
||||
// initialize a config
|
||||
conf, err := New(map[string]interface{}{}, mock.Path("conf/conf.yml"))
|
||||
conf, err := New(map[string]interface{}{}, mock.Path("conf/conf.yml"), false)
|
||||
if err != nil {
|
||||
t.Errorf("failed to parse config file: %v", err)
|
||||
}
|
||||
@ -69,7 +69,7 @@ func TestConfigSuccessful(t *testing.T) {
|
||||
func TestConfigFailure(t *testing.T) {
|
||||
|
||||
// attempt to read a non-existent config file
|
||||
_, err := New(map[string]interface{}{}, "/does-not-exit")
|
||||
_, err := New(map[string]interface{}{}, "/does-not-exit", false)
|
||||
if err == nil {
|
||||
t.Errorf("failed to error on unreadable config")
|
||||
}
|
||||
@ -84,14 +84,14 @@ func TestEmptyEditor(t *testing.T) {
|
||||
os.Setenv("EDITOR", "")
|
||||
|
||||
// initialize a config
|
||||
conf, err := New(map[string]interface{}{}, mock.Path("conf/empty.yml"))
|
||||
conf, err := New(map[string]interface{}{}, mock.Path("conf/empty.yml"), false)
|
||||
if err == nil {
|
||||
t.Errorf("failed to return an error on empty editor")
|
||||
}
|
||||
|
||||
// set editor, and assert that it is respected
|
||||
os.Setenv("EDITOR", "foo")
|
||||
conf, err = New(map[string]interface{}{}, mock.Path("conf/empty.yml"))
|
||||
conf, err = New(map[string]interface{}{}, mock.Path("conf/empty.yml"), false)
|
||||
if err != nil {
|
||||
t.Errorf("failed to init configs: %v", err)
|
||||
}
|
||||
@ -101,7 +101,7 @@ func TestEmptyEditor(t *testing.T) {
|
||||
|
||||
// set visual, and assert that it overrides editor
|
||||
os.Setenv("VISUAL", "bar")
|
||||
conf, err = New(map[string]interface{}{}, mock.Path("conf/empty.yml"))
|
||||
conf, err = New(map[string]interface{}{}, mock.Path("conf/empty.yml"), false)
|
||||
if err != nil {
|
||||
t.Errorf("failed to init configs: %v", err)
|
||||
}
|
||||
|
34
internal/frontmatter/frontmatter.go
Normal file
34
internal/frontmatter/frontmatter.go
Normal file
@ -0,0 +1,34 @@
|
||||
package frontmatter
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v1"
|
||||
)
|
||||
|
||||
// Frontmatter encapsulates cheatsheet frontmatter data
|
||||
type Frontmatter struct {
|
||||
Tags []string
|
||||
Syntax string
|
||||
}
|
||||
|
||||
// Parse parses cheatsheet frontmatter
|
||||
func Parse(markdown string) (string, Frontmatter, error) {
|
||||
|
||||
// specify the frontmatter delimiter
|
||||
delim := "---"
|
||||
|
||||
// initialize a frontmatter struct
|
||||
var fm Frontmatter
|
||||
|
||||
// if the markdown does not contain frontmatter, pass it through unmodified
|
||||
if !strings.HasPrefix(markdown, delim) {
|
||||
return strings.TrimSpace(markdown), fm, nil
|
||||
}
|
||||
|
||||
// otherwise, split the frontmatter and cheatsheet text
|
||||
parts := strings.SplitN(markdown, delim, 3)
|
||||
err := yaml.Unmarshal([]byte(parts[1]), &fm)
|
||||
|
||||
return strings.TrimSpace(parts[2]), fm, err
|
||||
}
|
71
internal/frontmatter/frontmatter_test.go
Normal file
71
internal/frontmatter/frontmatter_test.go
Normal file
@ -0,0 +1,71 @@
|
||||
package frontmatter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestHasFrontmatter asserts that markdown is properly parsed when it contains
|
||||
// frontmatter
|
||||
func TestHasFrontmatter(t *testing.T) {
|
||||
|
||||
// stub our cheatsheet content
|
||||
markdown := `---
|
||||
syntax: go
|
||||
tags: [ test ]
|
||||
---
|
||||
To foo the bar: baz`
|
||||
|
||||
// parse the frontmatter
|
||||
text, fm, err := Parse(markdown)
|
||||
|
||||
// assert expectations
|
||||
if err != nil {
|
||||
t.Errorf("failed to parse markdown: %v", err)
|
||||
}
|
||||
|
||||
want := "To foo the bar: baz"
|
||||
if text != want {
|
||||
t.Errorf("failed to parse text: want: %s, got: %s", want, text)
|
||||
}
|
||||
|
||||
want = "go"
|
||||
if fm.Syntax != want {
|
||||
t.Errorf("failed to parse syntax: want: %s, got: %s", want, fm.Syntax)
|
||||
}
|
||||
|
||||
want = "test"
|
||||
if fm.Tags[0] != want {
|
||||
t.Errorf("failed to parse tags: want: %s, got: %s", want, fm.Tags[0])
|
||||
}
|
||||
if len(fm.Tags) != 1 {
|
||||
t.Errorf("failed to parse tags: want: len 0, got: len %d", len(fm.Tags))
|
||||
}
|
||||
}
|
||||
|
||||
// TestHasFrontmatter asserts that markdown is properly parsed when it does not
|
||||
// contain frontmatter
|
||||
func TestHasNoFrontmatter(t *testing.T) {
|
||||
|
||||
// stub our cheatsheet content
|
||||
markdown := "To foo the bar: baz"
|
||||
|
||||
// parse the frontmatter
|
||||
text, fm, err := Parse(markdown)
|
||||
|
||||
// assert expectations
|
||||
if err != nil {
|
||||
t.Errorf("failed to parse markdown: %v", err)
|
||||
}
|
||||
|
||||
if text != markdown {
|
||||
t.Errorf("failed to parse text: want: %s, got: %s", markdown, text)
|
||||
}
|
||||
|
||||
if fm.Syntax != "" {
|
||||
t.Errorf("failed to parse syntax: want: '', got: %s", fm.Syntax)
|
||||
}
|
||||
|
||||
if len(fm.Tags) != 0 {
|
||||
t.Errorf("failed to parse tags: want: len 0, got: len %d", len(fm.Tags))
|
||||
}
|
||||
}
|
@ -4,17 +4,10 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/tj/front"
|
||||
"github.com/cheat/cheat/internal/frontmatter"
|
||||
)
|
||||
|
||||
// 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
|
||||
@ -39,9 +32,8 @@ func New(
|
||||
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)
|
||||
// parse the cheatsheet frontmatter
|
||||
text, fm, err := frontmatter.Parse(string(markdown))
|
||||
if err != nil {
|
||||
return Sheet{}, fmt.Errorf("failed to parse front-matter: %v", err)
|
||||
}
|
||||
@ -56,7 +48,7 @@ func New(
|
||||
return Sheet{
|
||||
Title: title,
|
||||
Path: path,
|
||||
Text: strings.TrimSpace(string(text)) + "\n",
|
||||
Text: text + "\n",
|
||||
Tags: tags,
|
||||
Syntax: fm.Syntax,
|
||||
ReadOnly: readOnly,
|
||||
|
@ -48,8 +48,13 @@ func Load(cheatpaths []cp.Cheatpath) ([]map[string]sheet.Sheet, error) {
|
||||
"/",
|
||||
)
|
||||
|
||||
// ignore dotfiles. Otherwise, we'll likely load .git/*
|
||||
if strings.HasPrefix(title, ".") {
|
||||
// ignore hidden files and directories. Otherwise, we'll likely load
|
||||
// .git/* and .DS_Store.
|
||||
//
|
||||
// NB: this is still somewhat brittle in that it will miss files
|
||||
// contained within hidden directories in the middle of a path, though
|
||||
// that should not realistically occur.
|
||||
if strings.HasPrefix(title, ".") || strings.HasPrefix(info.Name(), ".") {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
9
scripts/cheat-autocompletion.bash
Executable file
9
scripts/cheat-autocompletion.bash
Executable file
@ -0,0 +1,9 @@
|
||||
function _cheat_autocomplete {
|
||||
sheets=$(cheat -l | sed -n '2,$p'|cut -d' ' -f1)
|
||||
COMPREPLY=()
|
||||
if [ $COMP_CWORD = 1 ]; then
|
||||
COMPREPLY=(`compgen -W "$sheets" -- $2`)
|
||||
fi
|
||||
}
|
||||
|
||||
complete -F _cheat_autocomplete cheat
|
11
scripts/cheat-autocompletion.fish
Executable file
11
scripts/cheat-autocompletion.fish
Executable file
@ -0,0 +1,11 @@
|
||||
complete -c cheat -f -a "(cheat -l | tail -n +2 | cut -d ' ' -f 1)"
|
||||
complete -c cheat -l init -d "Write a default config file to stdout"
|
||||
complete -c cheat -s c -l colorize -d "Colorize output"
|
||||
complete -c cheat -s d -l directories -d "List cheatsheet directories"
|
||||
complete -c cheat -s e -l edit -x -a "(cheat -l | tail -n +2 | cut -d ' ' -f 1)" -d "Edit cheatsheet"
|
||||
complete -c cheat -s l -l list -d "List cheatsheets"
|
||||
complete -c cheat -s p -l path -x -a "(cheat -d | cut -d ':' -f 1)" -d "Return only sheets found on given path"
|
||||
complete -c cheat -s r -l regex -d "Treat search phrase as a regex"
|
||||
complete -c cheat -s s -l search -x -d "Search cheatsheets for given phrase"
|
||||
complete -c cheat -s t -l tag -x -a "(cheat -l | tail -n +2 | rev | cut -d ' ' -f 1 | rev | sed 's/,/\n/g;/^\$/d' | sort -u)" -d "Return only sheets matching the given tag"
|
||||
complete -c cheat -s v -l version -d "Print the version number"
|
16
vendor/github.com/tj/front/Readme.md
generated
vendored
16
vendor/github.com/tj/front/Readme.md
generated
vendored
@ -1,16 +0,0 @@
|
||||
# Front
|
||||
|
||||
Frontmatter unmarshaller, couldn't find one without a weird API.
|
||||
|
||||
## Badges
|
||||
|
||||
[](https://godoc.org/github.com/tj/front)
|
||||

|
||||

|
||||
[](https://apex.sh/ping/)
|
||||
|
||||
---
|
||||
|
||||
> [tjholowaychuk.com](http://tjholowaychuk.com) ·
|
||||
> GitHub [@tj](https://github.com/tj) ·
|
||||
> Twitter [@tjholowaychuk](https://twitter.com/tjholowaychuk)
|
24
vendor/github.com/tj/front/front.go
generated
vendored
24
vendor/github.com/tj/front/front.go
generated
vendored
@ -1,24 +0,0 @@
|
||||
// Package front provides YAML frontmatter unmarshalling.
|
||||
package front
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"gopkg.in/yaml.v1"
|
||||
)
|
||||
|
||||
// Delimiter.
|
||||
var delim = []byte("---")
|
||||
|
||||
// Unmarshal parses YAML frontmatter and returns the content. When no
|
||||
// frontmatter delimiters are present the original content is returned.
|
||||
func Unmarshal(b []byte, v interface{}) (content []byte, err error) {
|
||||
if !bytes.HasPrefix(b, delim) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
parts := bytes.SplitN(b, delim, 3)
|
||||
content = parts[2]
|
||||
err = yaml.Unmarshal(parts[1], v)
|
||||
return
|
||||
}
|
Reference in New Issue
Block a user