Compare commits

..

5 Commits
4.2.6 ... 4.2.7

Author SHA1 Message Date
73f80bde48 Merge pull request #688 from chrisallenlane/4.2.7
4.2.7
2022-08-05 07:01:51 -04:00
8130b2f3bd chore: bump version to 4.2.7 2022-08-05 06:41:16 -04:00
f4e6c76e58 fix: escape sequences in search output (#687)
Fix an issue whereby ANSI escape characters could appear in search
output when a pager was not configured.

The root cause of the problem was code that was overzealously applying
an underlying effect to search terms.

This commit simply rips out underlying entirely, both as means of
resolving this problem, and also simply for removing needless visual
noise from search output.
2022-08-05 06:41:16 -04:00
85f5ae8ec7 chore: various lint corrections
Make various lint corrections in order to appease `staticcheck`.
2022-08-04 20:43:50 -04:00
484b447391 perf(Sheets): do not walk hidden directories
Modify `Sheets.Load` to not walk hidden directories like `.git`. This
optimization can potentially prevent thousands of system calls from
being made, because `.git` directories can contain many files.
2022-08-04 20:43:42 -04:00
23 changed files with 55 additions and 90 deletions

View File

@ -9,13 +9,13 @@ On Unix-like systems, you may simply paste the following snippet into your termi
```sh ```sh
cd /tmp \ cd /tmp \
&& wget https://github.com/cheat/cheat/releases/download/4.2.5/cheat-linux-amd64.gz \ && wget https://github.com/cheat/cheat/releases/download/4.2.7/cheat-linux-amd64.gz \
&& gunzip cheat-linux-amd64.gz \ && gunzip cheat-linux-amd64.gz \
&& chmod +x cheat-linux-amd64 \ && chmod +x cheat-linux-amd64 \
&& sudo mv cheat-linux-amd64 /usr/local/bin/cheat && sudo mv cheat-linux-amd64 /usr/local/bin/cheat
``` ```
You may need to need to change the version number (`4.2.5`) and the archive You may need to need to change the version number (`4.2.7`) and the archive
(`cheat-linux-amd64.gz`) depending on your platform. (`cheat-linux-amd64.gz`) depending on your platform.
See the [releases page][releases] for a list of supported platforms. See the [releases page][releases] for a list of supported platforms.

View File

@ -18,11 +18,7 @@ func cmdDirectories(opts map[string]interface{}, conf config.Config) {
// generate sorted, columnized output // generate sorted, columnized output
for _, path := range conf.Cheatpaths { for _, path := range conf.Cheatpaths {
fmt.Fprintln(w, fmt.Sprintf( fmt.Fprintf(w, "%s:\t%s\n", path.Name, path.Path)
"%s:\t%s",
path.Name,
path.Path,
))
} }
// write columnized output to stdout // write columnized output to stdout

View File

@ -20,7 +20,7 @@ func cmdEdit(opts map[string]interface{}, conf config.Config) {
// load the cheatsheets // load the cheatsheets
cheatsheets, err := sheets.Load(conf.Cheatpaths) cheatsheets, err := sheets.Load(conf.Cheatpaths)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to list cheatsheets: %v", err)) fmt.Fprintf(os.Stderr, "failed to list cheatsheets: %v\n", err)
os.Exit(1) os.Exit(1)
} }

View File

@ -21,7 +21,7 @@ func cmdList(opts map[string]interface{}, conf config.Config) {
// load the cheatsheets // load the cheatsheets
cheatsheets, err := sheets.Load(conf.Cheatpaths) cheatsheets, err := sheets.Load(conf.Cheatpaths)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to list cheatsheets: %v", err)) fmt.Fprintf(os.Stderr, "failed to list cheatsheets: %v\n", err)
os.Exit(1) os.Exit(1)
} }
@ -63,10 +63,7 @@ func cmdList(opts map[string]interface{}, conf config.Config) {
// compile the regex // compile the regex
reg, err := regexp.Compile(pattern) reg, err := regexp.Compile(pattern)
if err != nil { if err != nil {
fmt.Fprintln( fmt.Fprintf(os.Stderr, "failed to compile regexp: %s, %v\n", pattern, err)
os.Stderr,
fmt.Sprintf("failed to compile regexp: %s, %v", pattern, err),
)
os.Exit(1) os.Exit(1)
} }
@ -95,12 +92,7 @@ func cmdList(opts map[string]interface{}, conf config.Config) {
// generate sorted, columnized output // generate sorted, columnized output
for _, sheet := range flattened { for _, sheet := range flattened {
fmt.Fprintln(w, fmt.Sprintf( fmt.Fprintf(w, "%s\t%s\t%s\n", sheet.Title, sheet.Path, strings.Join(sheet.Tags, ","))
"%s\t%s\t%s",
sheet.Title,
sheet.Path,
strings.Join(sheet.Tags, ","),
))
} }
// write columnized output to stdout // write columnized output to stdout

View File

@ -17,7 +17,7 @@ func cmdRemove(opts map[string]interface{}, conf config.Config) {
// load the cheatsheets // load the cheatsheets
cheatsheets, err := sheets.Load(conf.Cheatpaths) cheatsheets, err := sheets.Load(conf.Cheatpaths)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to list cheatsheets: %v", err)) fmt.Fprintf(os.Stderr, "failed to list cheatsheets: %v\n", err)
os.Exit(1) os.Exit(1)
} }
@ -37,19 +37,19 @@ func cmdRemove(opts map[string]interface{}, conf config.Config) {
// fail early if the requested cheatsheet does not exist // fail early if the requested cheatsheet does not exist
sheet, ok := consolidated[cheatsheet] sheet, ok := consolidated[cheatsheet]
if !ok { if !ok {
fmt.Fprintln(os.Stderr, fmt.Sprintf("No cheatsheet found for '%s'.\n", cheatsheet)) fmt.Fprintf(os.Stderr, "No cheatsheet found for '%s'.\n", cheatsheet)
os.Exit(2) os.Exit(2)
} }
// fail early if the sheet is read-only // fail early if the sheet is read-only
if sheet.ReadOnly { if sheet.ReadOnly {
fmt.Fprintln(os.Stderr, fmt.Sprintf("cheatsheet '%s' is read-only.", cheatsheet)) fmt.Fprintf(os.Stderr, "cheatsheet '%s' is read-only.\n", cheatsheet)
os.Exit(1) os.Exit(1)
} }
// otherwise, attempt to delete the sheet // otherwise, attempt to delete the sheet
if err := os.Remove(sheet.Path); err != nil { if err := os.Remove(sheet.Path); err != nil {
fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to delete sheet: %s, %v", sheet.Title, err)) fmt.Fprintf(os.Stderr, "failed to delete sheet: %s, %v\n", sheet.Title, err)
os.Exit(1) os.Exit(1)
} }
} }

View File

@ -19,7 +19,7 @@ func cmdSearch(opts map[string]interface{}, conf config.Config) {
// load the cheatsheets // load the cheatsheets
cheatsheets, err := sheets.Load(conf.Cheatpaths) cheatsheets, err := sheets.Load(conf.Cheatpaths)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to list cheatsheets: %v", err)) fmt.Fprintf(os.Stderr, "failed to list cheatsheets: %v\n", err)
os.Exit(1) os.Exit(1)
} }
@ -55,13 +55,13 @@ func cmdSearch(opts map[string]interface{}, conf config.Config) {
// compile the regex // compile the regex
reg, err := regexp.Compile(pattern) reg, err := regexp.Compile(pattern)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to compile regexp: %s, %v", pattern, err)) fmt.Fprintf(os.Stderr, "failed to compile regexp: %s, %v\n", pattern, err)
os.Exit(1) os.Exit(1)
} }
// `Search` will return text entries that match the search terms. We're // `Search` will return text entries that match the search terms.
// using it here to overwrite the prior cheatsheet Text, filtering it to // We're using it here to overwrite the prior cheatsheet Text,
// only what is relevant // filtering it to only what is relevant.
sheet.Text = sheet.Search(reg) sheet.Text = sheet.Search(reg)
// if the sheet did not match the search, ignore it and move on // if the sheet did not match the search, ignore it and move on
@ -74,14 +74,16 @@ func cmdSearch(opts map[string]interface{}, conf config.Config) {
sheet.Colorize(conf) sheet.Colorize(conf)
} }
// display the cheatsheet title and path // display the cheatsheet body
out += fmt.Sprintf("%s %s\n", out += fmt.Sprintf(
display.Underline(sheet.Title), "%s %s\n%s\n",
// append the cheatsheet title
sheet.Title,
// append the cheatsheet path
display.Faint(fmt.Sprintf("(%s)", sheet.CheatPath), conf), display.Faint(fmt.Sprintf("(%s)", sheet.CheatPath), conf),
// indent each line of content
display.Indent(sheet.Text),
) )
// indent each line of content
out += display.Indent(sheet.Text) + "\n"
} }
} }
@ -89,7 +91,7 @@ func cmdSearch(opts map[string]interface{}, conf config.Config) {
out = strings.TrimSpace(out) out = strings.TrimSpace(out)
// display the output // display the output
// NB: resist the temptation to call `display.Display` multiple times in // NB: resist the temptation to call `display.Write` multiple times in the
// the loop above. That will not play nicely with the paginator. // loop above. That will not play nicely with the paginator.
display.Write(out, conf) display.Write(out, conf)
} }

View File

@ -15,7 +15,7 @@ func cmdTags(opts map[string]interface{}, conf config.Config) {
// load the cheatsheets // load the cheatsheets
cheatsheets, err := sheets.Load(conf.Cheatpaths) cheatsheets, err := sheets.Load(conf.Cheatpaths)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to list cheatsheets: %v", err)) fmt.Fprintf(os.Stderr, "failed to list cheatsheets: %v\n", err)
os.Exit(1) os.Exit(1)
} }

View File

@ -18,7 +18,7 @@ func cmdView(opts map[string]interface{}, conf config.Config) {
// load the cheatsheets // load the cheatsheets
cheatsheets, err := sheets.Load(conf.Cheatpaths) cheatsheets, err := sheets.Load(conf.Cheatpaths)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to list cheatsheets: %v", err)) fmt.Fprintf(os.Stderr, "failed to list cheatsheets: %v\n", err)
os.Exit(1) os.Exit(1)
} }
@ -41,7 +41,7 @@ func cmdView(opts map[string]interface{}, conf config.Config) {
// identify the matching cheatsheet // identify the matching cheatsheet
out += fmt.Sprintf("%s %s\n", out += fmt.Sprintf("%s %s\n",
display.Underline(sheet.Title), sheet.Title,
display.Faint(fmt.Sprintf("(%s)", sheet.CheatPath), conf), display.Faint(fmt.Sprintf("(%s)", sheet.CheatPath), conf),
) )

View File

@ -16,7 +16,7 @@ import (
"github.com/cheat/cheat/internal/installer" "github.com/cheat/cheat/internal/installer"
) )
const version = "4.2.6" const version = "4.2.7"
func main() { func main() {

View File

@ -46,7 +46,7 @@ func TestFilterFailure(t *testing.T) {
} }
// filter the paths // filter the paths
paths, err := Filter(paths, "qux") _, err := Filter(paths, "qux")
if err == nil { if err == nil {
t.Errorf("failed to return an error on non-existent cheatpath") t.Errorf("failed to return an error on non-existent cheatpath")
} }

View File

@ -11,12 +11,10 @@ func Writeable(cheatpaths []Cheatpath) (Cheatpath, error) {
// NB: we're going backwards because we assume that the most "local" // NB: we're going backwards because we assume that the most "local"
// cheatpath will be specified last in the configs // cheatpath will be specified last in the configs
for i := len(cheatpaths) - 1; i >= 0; i-- { for i := len(cheatpaths) - 1; i >= 0; i-- {
// if the cheatpath is not read-only, it is writeable, and thus returned // if the cheatpath is not read-only, it is writeable, and thus returned
if cheatpaths[i].ReadOnly == false { if !cheatpaths[i].ReadOnly {
return cheatpaths[i], nil return cheatpaths[i], nil
} }
} }
// otherwise, return an error // otherwise, return an error

View File

@ -2,7 +2,6 @@ package config
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@ -29,7 +28,7 @@ type Config struct {
func New(opts map[string]interface{}, confPath string, resolve bool) (Config, error) { func New(opts map[string]interface{}, confPath string, resolve bool) (Config, error) {
// read the config file // read the config file
buf, err := ioutil.ReadFile(confPath) buf, err := os.ReadFile(confPath)
if err != nil { if err != nil {
return Config{}, fmt.Errorf("could not read config file: %v", err) return Config{}, fmt.Errorf("could not read config file: %v", err)
} }

View File

@ -2,7 +2,6 @@ package config
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
) )
@ -16,7 +15,7 @@ func Init(confpath string, configs string) error {
} }
// write the config file // write the config file
if err := ioutil.WriteFile(confpath, []byte(configs), 0644); err != nil { if err := os.WriteFile(confpath, []byte(configs), 0644); err != nil {
return fmt.Errorf("failed to create file: %v", err) return fmt.Errorf("failed to create file: %v", err)
} }

View File

@ -1,7 +1,6 @@
package config package config
import ( import (
"io/ioutil"
"os" "os"
"testing" "testing"
) )
@ -10,7 +9,7 @@ import (
func TestInit(t *testing.T) { func TestInit(t *testing.T) {
// initialize a temporary config file // initialize a temporary config file
confFile, err := ioutil.TempFile("", "cheat-test") confFile, err := os.CreateTemp("", "cheat-test")
if err != nil { if err != nil {
t.Errorf("failed to create temp file: %v", err) t.Errorf("failed to create temp file: %v", err)
} }
@ -25,7 +24,7 @@ func TestInit(t *testing.T) {
} }
// read back the config file contents // read back the config file contents
bytes, err := ioutil.ReadFile(confFile.Name()) bytes, err := os.ReadFile(confFile.Name())
if err != nil { if err != nil {
t.Errorf("failed to read config file: %v", err) t.Errorf("failed to read config file: %v", err)
} }

View File

@ -1,7 +1,6 @@
package config package config
import ( import (
"io/ioutil"
"os" "os"
"testing" "testing"
) )
@ -24,7 +23,7 @@ func TestPathConfigNotExists(t *testing.T) {
func TestPathConfigExists(t *testing.T) { func TestPathConfigExists(t *testing.T) {
// initialize a temporary config file // initialize a temporary config file
confFile, err := ioutil.TempFile("", "cheat-test") confFile, err := os.CreateTemp("", "cheat-test")
if err != nil { if err != nil {
t.Errorf("failed to create temp file: %v", err) t.Errorf("failed to create temp file: %v", err)
} }

View File

@ -10,7 +10,7 @@ import (
func Faint(str string, conf config.Config) string { func Faint(str string, conf config.Config) string {
// make `str` faint only if colorization has been requested // make `str` faint only if colorization has been requested
if conf.Colorize { if conf.Colorize {
return fmt.Sprintf(fmt.Sprintf("\033[2m%s\033[0m", str)) return fmt.Sprintf("\033[2m%s\033[0m", str)
} }
// otherwise, return the string unmodified // otherwise, return the string unmodified

View File

@ -1,8 +0,0 @@
package display
import "fmt"
// Underline returns an underlined string
func Underline(str string) string {
return fmt.Sprintf(fmt.Sprintf("\033[4m%s\033[0m", str))
}

View File

@ -1,14 +0,0 @@
package display
import (
"testing"
)
// TestUnderline asserts that Underline applies underline formatting
func TestUnderline(t *testing.T) {
want := "\033[4mfoo\033[0m"
got := Underline("foo")
if want != got {
t.Errorf("failed to underline: want: %s, got: %s", want, got)
}
}

View File

@ -23,15 +23,14 @@ func Write(out string, conf config.Config) {
pager := parts[0] pager := parts[0]
args := parts[1:] args := parts[1:]
// run the pager // configure the pager
cmd := exec.Command(pager, args...) cmd := exec.Command(pager, args...)
cmd.Stdin = strings.NewReader(out) cmd.Stdin = strings.NewReader(out)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
// handle errors // run the pager and handle errors
err := cmd.Run() if err := cmd.Run(); err != nil {
if err != nil { fmt.Fprintf(os.Stderr, "failed to write to pager: %v\n", err)
fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to write to pager: %v", err))
os.Exit(1) os.Exit(1)
} }
} }

View File

@ -14,7 +14,7 @@ func Prompt(prompt string, def bool) (bool, error) {
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
// display the prompt // display the prompt
fmt.Print(fmt.Sprintf("%s: ", prompt)) fmt.Printf("%s: ", prompt)
// read the answer // read the answer
ans, err := reader.ReadString('\n') ans, err := reader.ReadString('\n')

View File

@ -1,7 +1,6 @@
package sheet package sheet
import ( import (
"io/ioutil"
"os" "os"
"path" "path"
"testing" "testing"
@ -13,7 +12,7 @@ func TestCopyFlat(t *testing.T) {
// mock a cheatsheet file // mock a cheatsheet file
text := "this is the cheatsheet text" text := "this is the cheatsheet text"
src, err := ioutil.TempFile("", "foo-src") src, err := os.CreateTemp("", "foo-src")
if err != nil { if err != nil {
t.Errorf("failed to mock cheatsheet: %v", err) t.Errorf("failed to mock cheatsheet: %v", err)
} }
@ -41,7 +40,7 @@ func TestCopyFlat(t *testing.T) {
} }
// assert that the destination file contains the correct text // assert that the destination file contains the correct text
got, err := ioutil.ReadFile(outpath) got, err := os.ReadFile(outpath)
if err != nil { if err != nil {
t.Errorf("failed to read destination file: %v", err) t.Errorf("failed to read destination file: %v", err)
} }
@ -60,7 +59,7 @@ func TestCopyDeep(t *testing.T) {
// mock a cheatsheet file // mock a cheatsheet file
text := "this is the cheatsheet text" text := "this is the cheatsheet text"
src, err := ioutil.TempFile("", "foo-src") src, err := os.CreateTemp("", "foo-src")
if err != nil { if err != nil {
t.Errorf("failed to mock cheatsheet: %v", err) t.Errorf("failed to mock cheatsheet: %v", err)
} }
@ -94,7 +93,7 @@ func TestCopyDeep(t *testing.T) {
} }
// assert that the destination file contains the correct text // assert that the destination file contains the correct text
got, err := ioutil.ReadFile(outpath) got, err := os.ReadFile(outpath)
if err != nil { if err != nil {
t.Errorf("failed to read destination file: %v", err) t.Errorf("failed to read destination file: %v", err)
} }

View File

@ -2,7 +2,7 @@ package sheet
import ( import (
"fmt" "fmt"
"io/ioutil" "os"
"sort" "sort"
"github.com/cheat/cheat/internal/frontmatter" "github.com/cheat/cheat/internal/frontmatter"
@ -29,7 +29,7 @@ func New(
) (Sheet, error) { ) (Sheet, error) {
// read the cheatsheet file // read the cheatsheet file
markdown, err := ioutil.ReadFile(path) markdown, err := os.ReadFile(path)
if err != nil { if err != nil {
return Sheet{}, fmt.Errorf("failed to read file: %s, %v", path, err) return Sheet{}, fmt.Errorf("failed to read file: %s, %v", path, err)
} }

View File

@ -2,6 +2,7 @@ package sheets
import ( import (
"fmt" "fmt"
"io/fs"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -55,7 +56,11 @@ func Load(cheatpaths []cp.Cheatpath) ([]map[string]sheet.Sheet, error) {
// contained within hidden directories in the middle of a path, though // contained within hidden directories in the middle of a path, though
// that should not realistically occur. // that should not realistically occur.
if strings.HasPrefix(title, ".") || strings.HasPrefix(info.Name(), ".") { if strings.HasPrefix(title, ".") || strings.HasPrefix(info.Name(), ".") {
return nil // Do not walk hidden directories. This is important,
// because it's common for cheatsheets to be stored in
// version-control, and a `.git` directory can easily
// contain thousands of files.
return fs.SkipDir
} }
// parse the cheatsheet file into a `sheet` struct // parse the cheatsheet file into a `sheet` struct