mirror of
https://github.com/walles/moar.git
synced 2024-09-11 20:17:13 +03:00
Make a lot fewer functions public
This commit is contained in:
parent
f069409725
commit
2b18347022
19
README.md
19
README.md
@ -75,24 +75,33 @@ you can send questions to <johan.walles@gmail.com>.
|
||||
Embedding
|
||||
---------
|
||||
|
||||
Here's how to embed `moar` in your app:
|
||||
Here's one way to embed `moar` in your app:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/walles/moar/m"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := m.PageString("Months", "March, April\nMay")
|
||||
if err != nil {
|
||||
// Handle pager problems
|
||||
panic(err)
|
||||
buf := bytes.Buffer{}
|
||||
fmt.Fprintln(&buf, "March")
|
||||
fmt.Fprintln(&buf, "April")
|
||||
name := "Months"
|
||||
e := m.Page(m.NewReaderFromStream(&name, &buf))
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `m.Page()` parameter can also be initialized using `NewReaderFromText()` or
|
||||
`NewReaderFromFilename()`.
|
||||
|
||||
Developing
|
||||
----------
|
||||
|
||||
|
@ -47,13 +47,13 @@ func resetManPageFormatForTesting() {
|
||||
|
||||
func termcapToStyle(termcap string) tcell.Style {
|
||||
// Add a character to be sure we have one to take the format from
|
||||
tokens, _ := TokensFromString(termcap + "x")
|
||||
tokens, _ := tokensFromString(termcap + "x")
|
||||
return tokens[len(tokens)-1].Style
|
||||
}
|
||||
|
||||
// TokensFromString turns a (formatted) string into a series of tokens,
|
||||
// tokensFromString turns a (formatted) string into a series of tokens,
|
||||
// and an unformatted string
|
||||
func TokensFromString(s string) ([]Token, *string) {
|
||||
func tokensFromString(s string) ([]Token, *string) {
|
||||
var tokens []Token
|
||||
|
||||
styleBrokenUtf8 := tcell.StyleDefault.Background(tcell.ColorSilver).Foreground(tcell.ColorMaroon)
|
||||
|
@ -34,7 +34,7 @@ func TestTokenize(t *testing.T) {
|
||||
var loglines strings.Builder
|
||||
log.SetOutput(&loglines)
|
||||
|
||||
tokens, plainString := TokensFromString(line)
|
||||
tokens, plainString := tokensFromString(line)
|
||||
if len(tokens) != utf8.RuneCountInString(*plainString) {
|
||||
t.Errorf("%s:%d: len(tokens)=%d, len(plainString)=%d for: <%s>",
|
||||
fileName, lineNumber,
|
||||
@ -51,7 +51,7 @@ func TestTokenize(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnderline(t *testing.T) {
|
||||
tokens, _ := TokensFromString("a\x1b[4mb\x1b[24mc")
|
||||
tokens, _ := tokensFromString("a\x1b[4mb\x1b[24mc")
|
||||
assert.Equal(t, len(tokens), 3)
|
||||
assert.Equal(t, tokens[0], Token{Rune: 'a', Style: tcell.StyleDefault})
|
||||
assert.Equal(t, tokens[1], Token{Rune: 'b', Style: tcell.StyleDefault.Underline(true)})
|
||||
@ -60,14 +60,14 @@ func TestUnderline(t *testing.T) {
|
||||
|
||||
func TestManPages(t *testing.T) {
|
||||
// Bold
|
||||
tokens, _ := TokensFromString("ab\bbc")
|
||||
tokens, _ := tokensFromString("ab\bbc")
|
||||
assert.Equal(t, len(tokens), 3)
|
||||
assert.Equal(t, tokens[0], Token{Rune: 'a', Style: tcell.StyleDefault})
|
||||
assert.Equal(t, tokens[1], Token{Rune: 'b', Style: tcell.StyleDefault.Bold(true)})
|
||||
assert.Equal(t, tokens[2], Token{Rune: 'c', Style: tcell.StyleDefault})
|
||||
|
||||
// Underline
|
||||
tokens, _ = TokensFromString("a_\bbc")
|
||||
tokens, _ = tokensFromString("a_\bbc")
|
||||
assert.Equal(t, len(tokens), 3)
|
||||
assert.Equal(t, tokens[0], Token{Rune: 'a', Style: tcell.StyleDefault})
|
||||
assert.Equal(t, tokens[1], Token{Rune: 'b', Style: tcell.StyleDefault.Underline(true)})
|
||||
@ -75,7 +75,7 @@ func TestManPages(t *testing.T) {
|
||||
|
||||
// Bullet point 1, taken from doing this on my macOS system:
|
||||
// env PAGER="hexdump -C" man printf | moar
|
||||
tokens, _ = TokensFromString("a+\b+\bo\bob")
|
||||
tokens, _ = tokensFromString("a+\b+\bo\bob")
|
||||
assert.Equal(t, len(tokens), 3)
|
||||
assert.Equal(t, tokens[0], Token{Rune: 'a', Style: tcell.StyleDefault})
|
||||
assert.Equal(t, tokens[1], Token{Rune: '•', Style: tcell.StyleDefault})
|
||||
@ -83,7 +83,7 @@ func TestManPages(t *testing.T) {
|
||||
|
||||
// Bullet point 2, taken from doing this using the "fish" shell on my macOS system:
|
||||
// man printf | hexdump -C | moar
|
||||
tokens, _ = TokensFromString("a+\bob")
|
||||
tokens, _ = tokensFromString("a+\bob")
|
||||
assert.Equal(t, len(tokens), 3)
|
||||
assert.Equal(t, tokens[0], Token{Rune: 'a', Style: tcell.StyleDefault})
|
||||
assert.Equal(t, tokens[1], Token{Rune: '•', Style: tcell.StyleDefault})
|
||||
|
@ -7,8 +7,8 @@ type MatchRanges struct {
|
||||
Matches [][2]int
|
||||
}
|
||||
|
||||
// GetMatchRanges locates one or more regexp matches in a string
|
||||
func GetMatchRanges(String *string, Pattern *regexp.Regexp) *MatchRanges {
|
||||
// getMatchRanges locates one or more regexp matches in a string
|
||||
func getMatchRanges(String *string, Pattern *regexp.Regexp) *MatchRanges {
|
||||
if Pattern == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
var _TestString = "mamma"
|
||||
|
||||
func TestGetMatchRanges(t *testing.T) {
|
||||
matchRanges := GetMatchRanges(&_TestString, regexp.MustCompile("m+"))
|
||||
matchRanges := getMatchRanges(&_TestString, regexp.MustCompile("m+"))
|
||||
assert.Equal(t, len(matchRanges.Matches), 2) // Two matches
|
||||
|
||||
assert.DeepEqual(t, matchRanges.Matches[0][0], 0) // First match starts at 0
|
||||
@ -22,14 +22,14 @@ func TestGetMatchRanges(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetMatchRangesNilPattern(t *testing.T) {
|
||||
matchRanges := GetMatchRanges(&_TestString, nil)
|
||||
matchRanges := getMatchRanges(&_TestString, nil)
|
||||
assert.Assert(t, matchRanges == nil)
|
||||
assert.Assert(t, !matchRanges.InRange(0))
|
||||
}
|
||||
|
||||
func TestInRange(t *testing.T) {
|
||||
// Should match the one in TestGetMatchRanges()
|
||||
matchRanges := GetMatchRanges(&_TestString, regexp.MustCompile("m+"))
|
||||
matchRanges := getMatchRanges(&_TestString, regexp.MustCompile("m+"))
|
||||
|
||||
assert.Assert(t, !matchRanges.InRange(-1)) // Before start
|
||||
assert.Assert(t, matchRanges.InRange(0)) // m
|
||||
@ -43,7 +43,7 @@ func TestInRange(t *testing.T) {
|
||||
func TestUtf8(t *testing.T) {
|
||||
// This test verifies that the match ranges are by rune rather than by byte
|
||||
unicodes := "-ä-ä-"
|
||||
matchRanges := GetMatchRanges(&unicodes, regexp.MustCompile("ä"))
|
||||
matchRanges := getMatchRanges(&unicodes, regexp.MustCompile("ä"))
|
||||
|
||||
assert.Assert(t, !matchRanges.InRange(0)) // -
|
||||
assert.Assert(t, matchRanges.InRange(1)) // ä
|
||||
@ -55,7 +55,7 @@ func TestUtf8(t *testing.T) {
|
||||
func TestNoMatch(t *testing.T) {
|
||||
// This test verifies that the match ranges are by rune rather than by byte
|
||||
unicodes := "gris"
|
||||
matchRanges := GetMatchRanges(&unicodes, regexp.MustCompile("apa"))
|
||||
matchRanges := getMatchRanges(&unicodes, regexp.MustCompile("apa"))
|
||||
|
||||
assert.Assert(t, !matchRanges.InRange(0))
|
||||
assert.Assert(t, !matchRanges.InRange(1))
|
||||
@ -67,7 +67,7 @@ func TestNoMatch(t *testing.T) {
|
||||
func TestEndMatch(t *testing.T) {
|
||||
// This test verifies that the match ranges are by rune rather than by byte
|
||||
unicodes := "-ä"
|
||||
matchRanges := GetMatchRanges(&unicodes, regexp.MustCompile("ä"))
|
||||
matchRanges := getMatchRanges(&unicodes, regexp.MustCompile("ä"))
|
||||
|
||||
assert.Assert(t, !matchRanges.InRange(0)) // -
|
||||
assert.Assert(t, matchRanges.InRange(1)) // ä
|
||||
|
12
m/pager.go
12
m/pager.go
@ -152,13 +152,13 @@ func createScreenLine(
|
||||
searchHitDelta = -1
|
||||
}
|
||||
|
||||
tokens, plainString := TokensFromString(line)
|
||||
tokens, plainString := tokensFromString(line)
|
||||
if stringIndexAtColumnZero >= len(tokens) {
|
||||
// Nothing (more) to display, never mind
|
||||
return returnMe
|
||||
}
|
||||
|
||||
matchRanges := GetMatchRanges(plainString, search)
|
||||
matchRanges := getMatchRanges(plainString, search)
|
||||
for _, token := range tokens[stringIndexAtColumnZero:] {
|
||||
if len(returnMe) >= screenColumnsCount {
|
||||
// We are trying to add a character to the right of the screen.
|
||||
@ -329,7 +329,7 @@ func (p *Pager) _FindFirstHitLineOneBased(firstLineOneBased int, backwards bool)
|
||||
return nil
|
||||
}
|
||||
|
||||
_, lineText := TokensFromString(*line)
|
||||
_, lineText := tokensFromString(*line)
|
||||
if p.searchPattern.MatchString(*lineText) {
|
||||
return &lineNumber
|
||||
}
|
||||
@ -413,21 +413,21 @@ func (p *Pager) _ScrollToPreviousSearchHit() {
|
||||
}
|
||||
|
||||
func (p *Pager) _UpdateSearchPattern() {
|
||||
p.searchPattern = ToPattern(p.searchString)
|
||||
p.searchPattern = toPattern(p.searchString)
|
||||
|
||||
p._ScrollToSearchHits()
|
||||
|
||||
// FIXME: If the user is typing, indicate to user if we didn't find anything
|
||||
}
|
||||
|
||||
// ToPattern compiles a search string into a pattern.
|
||||
// toPattern compiles a search string into a pattern.
|
||||
//
|
||||
// If the string contains only lower-case letter the pattern will be case insensitive.
|
||||
//
|
||||
// If the string is empty the pattern will be nil.
|
||||
//
|
||||
// If the string does not compile into a regexp the pattern will match the string verbatim
|
||||
func ToPattern(compileMe string) *regexp.Regexp {
|
||||
func toPattern(compileMe string) *regexp.Regexp {
|
||||
if len(compileMe) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func TestUnicodeRendering(t *testing.T) {
|
||||
reader := NewReaderFromStream(strings.NewReader("åäö"))
|
||||
reader := NewReaderFromStream(nil, strings.NewReader("åäö"))
|
||||
if err := reader._Wait(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -47,7 +47,7 @@ func createExpectedCell(Rune rune, Style tcell.Style) Token {
|
||||
}
|
||||
|
||||
func TestFgColorRendering(t *testing.T) {
|
||||
reader := NewReaderFromStream(strings.NewReader(
|
||||
reader := NewReaderFromStream(nil, strings.NewReader(
|
||||
"\x1b[30ma\x1b[31mb\x1b[32mc\x1b[33md\x1b[34me\x1b[35mf\x1b[36mg\x1b[37mh\x1b[0mi"))
|
||||
if err := reader._Wait(); err != nil {
|
||||
panic(err)
|
||||
@ -73,7 +73,7 @@ func TestFgColorRendering(t *testing.T) {
|
||||
|
||||
func TestBrokenUtf8(t *testing.T) {
|
||||
// The broken UTF8 character in the middle is based on "©" = 0xc2a9
|
||||
reader := NewReaderFromStream(strings.NewReader("abc\xc2def"))
|
||||
reader := NewReaderFromStream(nil, strings.NewReader("abc\xc2def"))
|
||||
if err := reader._Wait(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -113,7 +113,7 @@ func startPaging(t *testing.T, reader *Reader) []tcell.SimCell {
|
||||
|
||||
// assertIndexOfFirstX verifies the (zero-based) index of the first 'x'
|
||||
func assertIndexOfFirstX(t *testing.T, s string, expectedIndex int) {
|
||||
reader := NewReaderFromStream(strings.NewReader(s))
|
||||
reader := NewReaderFromStream(nil, strings.NewReader(s))
|
||||
if err := reader._Wait(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -192,7 +192,7 @@ func TestCodeHighlighting(t *testing.T) {
|
||||
}
|
||||
|
||||
func testManPageFormatting(t *testing.T, input string, expected Token) {
|
||||
reader := NewReaderFromStream(strings.NewReader(input))
|
||||
reader := NewReaderFromStream(nil, strings.NewReader(input))
|
||||
if err := reader._Wait(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -221,23 +221,23 @@ func TestManPageFormatting(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestToPattern(t *testing.T) {
|
||||
assert.Assert(t, ToPattern("") == nil)
|
||||
assert.Assert(t, toPattern("") == nil)
|
||||
|
||||
// Test regexp matching
|
||||
assert.Assert(t, ToPattern("G.*S").MatchString("GRIIIS"))
|
||||
assert.Assert(t, !ToPattern("G.*S").MatchString("gRIIIS"))
|
||||
assert.Assert(t, toPattern("G.*S").MatchString("GRIIIS"))
|
||||
assert.Assert(t, !toPattern("G.*S").MatchString("gRIIIS"))
|
||||
|
||||
// Test case insensitive regexp matching
|
||||
assert.Assert(t, ToPattern("g.*s").MatchString("GRIIIS"))
|
||||
assert.Assert(t, ToPattern("g.*s").MatchString("gRIIIS"))
|
||||
assert.Assert(t, toPattern("g.*s").MatchString("GRIIIS"))
|
||||
assert.Assert(t, toPattern("g.*s").MatchString("gRIIIS"))
|
||||
|
||||
// Test non-regexp matching
|
||||
assert.Assert(t, ToPattern(")G").MatchString(")G"))
|
||||
assert.Assert(t, !ToPattern(")G").MatchString(")g"))
|
||||
assert.Assert(t, toPattern(")G").MatchString(")G"))
|
||||
assert.Assert(t, !toPattern(")G").MatchString(")g"))
|
||||
|
||||
// Test case insensitive non-regexp matching
|
||||
assert.Assert(t, ToPattern(")g").MatchString(")G"))
|
||||
assert.Assert(t, ToPattern(")g").MatchString(")g"))
|
||||
assert.Assert(t, toPattern(")g").MatchString(")G"))
|
||||
assert.Assert(t, toPattern(")g").MatchString(")g"))
|
||||
}
|
||||
|
||||
func assertTokenRangesEqual(t *testing.T, actual []Token, expected []Token) {
|
||||
@ -342,13 +342,13 @@ func TestCreateScreenLineScrolled2Utf8SearchHit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindFirstLineOneBasedSimple(t *testing.T) {
|
||||
reader := NewReaderFromStream(strings.NewReader("AB"))
|
||||
reader := NewReaderFromStream(nil, strings.NewReader("AB"))
|
||||
pager := NewPager(reader)
|
||||
|
||||
// Wait for reader to finish reading
|
||||
<-reader.done
|
||||
|
||||
pager.searchPattern = ToPattern("AB")
|
||||
pager.searchPattern = toPattern("AB")
|
||||
|
||||
hitLine := pager._FindFirstHitLineOneBased(1, false)
|
||||
assert.Check(t, hitLine != nil)
|
||||
@ -356,13 +356,13 @@ func TestFindFirstLineOneBasedSimple(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFindFirstLineOneBasedAnsi(t *testing.T) {
|
||||
reader := NewReaderFromStream(strings.NewReader("A\x1b[30mB"))
|
||||
reader := NewReaderFromStream(nil, strings.NewReader("A\x1b[30mB"))
|
||||
pager := NewPager(reader)
|
||||
|
||||
// Wait for reader to finish reading
|
||||
<-reader.done
|
||||
|
||||
pager.searchPattern = ToPattern("AB")
|
||||
pager.searchPattern = toPattern("AB")
|
||||
|
||||
hitLine := pager._FindFirstHitLineOneBased(1, false)
|
||||
assert.Check(t, hitLine != nil)
|
||||
|
25
m/reader.go
25
m/reader.go
@ -146,8 +146,13 @@ func readStream(stream io.Reader, reader *Reader, fromFilter *exec.Cmd) {
|
||||
}
|
||||
|
||||
// NewReaderFromStream creates a new stream reader
|
||||
func NewReaderFromStream(reader io.Reader) *Reader {
|
||||
return newReaderFromStream(reader, nil)
|
||||
func NewReaderFromStream(name *string, reader io.Reader) *Reader {
|
||||
mReader := newReaderFromStream(reader, nil)
|
||||
mReader.lock.Lock()
|
||||
mReader.name = name
|
||||
mReader.lock.Unlock()
|
||||
|
||||
return mReader
|
||||
}
|
||||
|
||||
// newReaderFromStream creates a new stream reader
|
||||
@ -178,7 +183,10 @@ func newReaderFromStream(reader io.Reader, fromFilter *exec.Cmd) *Reader {
|
||||
return &returnMe
|
||||
}
|
||||
|
||||
// NewReaderFromText creates a Reader from a block of text
|
||||
// NewReaderFromText creates a Reader from a block of text.
|
||||
//
|
||||
// First parameter is the name of this Reader. This name will be displayed by
|
||||
// Moar in the bottom left corner of the screen.
|
||||
func NewReaderFromText(name string, text string) *Reader {
|
||||
noExternalNewlines := strings.Trim(text, "\n")
|
||||
done := make(chan bool, 1)
|
||||
@ -299,7 +307,11 @@ func tryOpen(filename string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// NewReaderFromFilename creates a new file reader
|
||||
// NewReaderFromFilename creates a new file reader.
|
||||
//
|
||||
// The Reader will try to uncompress various compressed file format, and also
|
||||
// apply highlighting to the file using highlight:
|
||||
// http://www.andre-simon.de/doku/highlight/en/highlight.php
|
||||
func NewReaderFromFilename(filename string) (*Reader, error) {
|
||||
fileError := tryOpen(filename)
|
||||
if fileError != nil {
|
||||
@ -330,10 +342,7 @@ func NewReaderFromFilename(filename string) (*Reader, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reader := NewReaderFromStream(stream)
|
||||
reader.lock.Lock()
|
||||
reader.name = &filename
|
||||
reader.lock.Unlock()
|
||||
reader := NewReaderFromStream(&filename, stream)
|
||||
return reader, nil
|
||||
}
|
||||
|
||||
|
@ -172,7 +172,7 @@ func TestGetLongLine(t *testing.T) {
|
||||
}
|
||||
|
||||
func getReaderWithLineCount(totalLines int) *Reader {
|
||||
reader := NewReaderFromStream(strings.NewReader(strings.Repeat("x\n", totalLines)))
|
||||
reader := NewReaderFromStream(nil, strings.NewReader(strings.Repeat("x\n", totalLines)))
|
||||
if err := reader._Wait(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
10
moar.go
10
moar.go
@ -59,8 +59,8 @@ func printUsage(output io.Writer) {
|
||||
}
|
||||
}
|
||||
|
||||
// PrintProblemsHeader prints bug reporting information to stderr
|
||||
func PrintProblemsHeader() {
|
||||
// printProblemsHeader prints bug reporting information to stderr
|
||||
func printProblemsHeader() {
|
||||
fmt.Fprintln(os.Stderr, "Please post the following report at <https://github.com/walles/moar/issues>,")
|
||||
fmt.Fprintln(os.Stderr, "or e-mail it to johan.walles@gmail.com.")
|
||||
fmt.Fprintln(os.Stderr)
|
||||
@ -85,7 +85,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
PrintProblemsHeader()
|
||||
printProblemsHeader()
|
||||
panic(err)
|
||||
}()
|
||||
|
||||
@ -117,7 +117,7 @@ func main() {
|
||||
|
||||
if stdinIsRedirected && !stdoutIsRedirected {
|
||||
// Display input pipe contents
|
||||
reader := m.NewReaderFromStream(os.Stdin)
|
||||
reader := m.NewReaderFromStream(nil, os.Stdin)
|
||||
startPaging(reader)
|
||||
return
|
||||
}
|
||||
@ -171,7 +171,7 @@ func startPaging(reader *m.Reader) {
|
||||
}
|
||||
|
||||
if len(loglines.String()) > 0 {
|
||||
PrintProblemsHeader()
|
||||
printProblemsHeader()
|
||||
|
||||
// FIXME: Don't print duplicate log messages more than once,
|
||||
// maybe invent our own logger for this?
|
||||
|
Loading…
Reference in New Issue
Block a user