<|start_filename|>match.go<|end_filename|>
package the_platinum_searcher

type match struct {
	path  string
	lines []line
}

type line struct {
	num     int
	column  int
	text    string
	matched bool
}

func (m *match) add(num int, column int, text string, matched bool) {
	m.lines = append(m.lines, line{
		num:     num,
		column:  column,
		text:    text,
		matched: matched,
	})
}

func (m match) size() int {
	return len(m.lines)
}


<|start_filename|>encoding_test.go<|end_filename|>
package the_platinum_searcher

import (
	"io/ioutil"
	"path/filepath"
	"testing"
)

type Assert struct {
	path     string
	fileType int
}

var Asserts = []Assert{
	Assert{"ascii.txt", ASCII},
	Assert{"binary/binary.bin", BINARY},
	Assert{"ja/euc-jp.txt", EUCJP},
	Assert{"ja/shift_jis.txt", SHIFTJIS},
	Assert{"ja/utf8.txt", UTF8},
	Assert{"ja/broken_euc-jp.txt", EUCJP},
	Assert{"ja/broken_shift_jis.txt", SHIFTJIS},
	Assert{"ja/broken_utf8.txt", UTF8},
}

func TestIdentifyType(t *testing.T) {
	for _, f := range Asserts {
		b, _ := ioutil.ReadFile(filepath.Join("files", f.path))
		fileType := detectEncoding(b)
		if fileType != f.fileType {
			t.Errorf("%s should be %d.", f.path, f.fileType)
		}
	}
}


<|start_filename|>ignore.go<|end_filename|>
package the_platinum_searcher

import (
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/monochromegane/go-gitignore"
	"github.com/monochromegane/go-home"
)

type ignoreMatchers []gitignore.IgnoreMatcher

func (im ignoreMatchers) Match(path string, isDir bool) bool {
	for _, ig := range im {
		if ig == nil {
			return false
		}
		if ig.Match(path, isDir) {
			return true
		}
	}
	return false
}

func newIgnoreMatchers(path string, ignores []string) ignoreMatchers {
	var matchers ignoreMatchers
	for _, i := range ignores {
		if matcher, err := gitignore.NewGitIgnore(filepath.Join(path, i), path); err == nil {
			matchers = append(matchers, matcher)
		}
	}
	return matchers
}

func globalGitIgnore(base string) gitignore.IgnoreMatcher {
	if homeDir := home.Dir(); homeDir != "" {
		globalIgnore := globalGitIgnoreName()
		if globalIgnore != "" {
			if matcher, err := gitignore.NewGitIgnore(filepath.Join(homeDir, globalIgnore), base); err == nil {
				return matcher
			}
		}
	}
	return nil
}

func globalGitIgnoreName() string {
	gitCmd, err := exec.LookPath("git")
	if err != nil {
		return ""
	}

	file, err := exec.Command(gitCmd, "config", "--get", "core.excludesfile").Output()
	if err != nil {
		return ""
	}
	return strings.TrimSpace(filepath.Base(string(file)))
}

func homePtIgnore(base string) gitignore.IgnoreMatcher {
	if homeDir := home.Dir(); homeDir != "" {
		if matcher, err := gitignore.NewGitIgnore(filepath.Join(homeDir, ".ptignore"), base); err == nil {
			return matcher
		}
	}
	return nil
}


<|start_filename|>formatter_test.go<|end_filename|>
package the_platinum_searcher

import "os"

func ExampleFormatPrinterFileWithMatch() {
	opts := defaultOption()
	opts.OutputOption.EnableColor = false
	opts.OutputOption.FilesWithMatches = true

	match := match{path: "filename"}
	match.add(1, 0, "go test", true)

	pattern, _ := newPattern("go", opts)
	p := newFormatPrinter(pattern, os.Stdout, opts)
	p.print(match)

	// Output:
	// filename
}

func ExampleFormatPrinterCount() {
	opts := defaultOption()
	opts.OutputOption.EnableColor = false
	opts.OutputOption.Count = true

	match := match{path: "filename"}
	match.add(1, 0, "go test", true)

	pattern, _ := newPattern("go", opts)
	p := newFormatPrinter(pattern, os.Stdout, opts)
	p.print(match)

	// Output:
	// filename:1
}

func ExampleFormatEnableGroup() {
	opts := defaultOption()
	opts.OutputOption.EnableColor = false
	opts.OutputOption.EnableGroup = true

	match := match{path: "filename"}
	match.add(1, 0, "before", false) // before
	match.add(2, 0, "go test", true) // no column
	match.add(3, 0, "after", false)  // after

	pattern, _ := newPattern("go", opts)
	p := newFormatPrinter(pattern, os.Stdout, opts)
	p.print(match)

	// Output:
	// filename
	// 1-before
	// 2:go test
	// 3-after
}

func ExampleFormatEnableGroupWithColumn() {
	opts := defaultOption()
	opts.OutputOption.EnableColor = false
	opts.OutputOption.EnableGroup = true

	match := match{path: "filename"}
	match.add(1, 0, "before", false) // before
	match.add(2, 1, "go test", true) // no column
	match.add(3, 0, "after", false)  // after

	pattern, _ := newPattern("go", opts)
	p := newFormatPrinter(pattern, os.Stdout, opts)
	p.print(match)

	// Output:
	// filename
	// 1-before
	// 2:1:go test
	// 3-after
}

func ExampleFormatNoGroup() {
	opts := defaultOption()
	opts.OutputOption.EnableColor = false
	opts.OutputOption.EnableGroup = false

	match := match{path: "filename"}
	match.add(1, 0, "before", false) // before
	match.add(2, 0, "go test", true) // no column
	match.add(3, 0, "after", false)  // after

	pattern, _ := newPattern("go", opts)
	p := newFormatPrinter(pattern, os.Stdout, opts)
	p.print(match)

	// Output:
	// filename:1-before
	// filename:2:go test
	// filename:3-after
}

func ExampleFormatNoGroupWithColumn() {
	opts := defaultOption()
	opts.OutputOption.EnableColor = false
	opts.OutputOption.EnableGroup = false

	match := match{path: "filename"}
	match.add(1, 0, "before", false) // before
	match.add(2, 1, "go test", true) // no column
	match.add(3, 0, "after", false)  // after

	pattern, _ := newPattern("go", opts)
	p := newFormatPrinter(pattern, os.Stdout, opts)
	p.print(match)

	// Output:
	// filename:1-before
	// filename:2:1:go test
	// filename:3-after
}

func ExampleFormatMatchLine() {
	opts := defaultOption()
	opts.OutputOption.EnableColor = false
	opts.SearchOption.SearchStream = true

	match := match{path: "/dev/stdin"}
	match.add(1, 0, "before", false) // before
	match.add(2, 0, "go test", true) // no column
	match.add(3, 0, "after", false)  // after

	pattern, _ := newPattern("go", opts)
	p := newFormatPrinter(pattern, os.Stdout, opts)
	p.print(match)

	// Output:
	// before
	// go test
	// after
}

func ExampleFormatMatchLineWithColumn() {
	opts := defaultOption()
	opts.OutputOption.EnableColor = false
	opts.SearchOption.SearchStream = true

	match := match{path: "/dev/stdin"}
	match.add(1, 0, "before", false) // before
	match.add(2, 1, "go test", true) // no column
	match.add(3, 0, "after", false)  // after

	pattern, _ := newPattern("go", opts)
	p := newFormatPrinter(pattern, os.Stdout, opts)
	p.print(match)

	// Output:
	// before
	// 1:go test
	// after
}


<|start_filename|>file_handler.go<|end_filename|>
package the_platinum_searcher

import "os"

func getFileHandler(path string) (*os.File, error) {
	if path == "" {
		return os.Stdin, nil
	} else {
		return os.Open(path)
	}
}


<|start_filename|>match_state.go<|end_filename|>
package the_platinum_searcher

type matchState interface {
	transition(matched bool) matchState
	reset() matchState
	isBefore() bool
	isMatching() bool
	isAfter() bool
}

func newMatchState() matchState {
	return stateBeforeMatch{}
}

type stateBeforeMatch struct{}

func (s stateBeforeMatch) transition(matched bool) matchState {
	if matched {
		return stateMatching{}
	} else {
		return s
	}
}

func (s stateBeforeMatch) reset() matchState {
	return s
}

func (s stateBeforeMatch) isBefore() bool {
	return true
}

func (s stateBeforeMatch) isMatching() bool {
	return false
}

func (s stateBeforeMatch) isAfter() bool {
	return false
}

type stateMatching struct{}

func (s stateMatching) transition(matched bool) matchState {
	if matched {
		return s
	} else {
		return stateAfterMatch{}
	}
}

func (s stateMatching) reset() matchState {
	return stateBeforeMatch{}
}

func (s stateMatching) isBefore() bool {
	return false
}

func (s stateMatching) isMatching() bool {
	return true
}

func (s stateMatching) isAfter() bool {
	return false
}

type stateAfterMatch struct{}

func (s stateAfterMatch) transition(matched bool) matchState {
	if matched {
		return stateMatching{}
	} else {
		return s
	}
}

func (s stateAfterMatch) reset() matchState {
	return stateBeforeMatch{}
}

func (s stateAfterMatch) isBefore() bool {
	return false
}

func (s stateAfterMatch) isMatching() bool {
	return false
}

func (s stateAfterMatch) isAfter() bool {
	return true
}


<|start_filename|>pattern.go<|end_filename|>
package the_platinum_searcher

import "regexp"

// pattern should be interface, but match(large []byte) method called through interface is too slow.
type pattern struct {
	pattern []byte
	regexp  *regexp.Regexp
	opts    Option
}

func newPattern(p string, opts Option) (pattern, error) {
	pattern := pattern{pattern: []byte(p), opts: opts}

	if opts.SearchOption.Regexp {
		var reg *regexp.Regexp
		var err error
		if opts.SearchOption.IgnoreCase {
			reg, err = regexp.Compile(`(?i)(` + p + `)`)
		} else {
			reg, err = regexp.Compile(`(` + p + `)`)
		}
		if err != nil {
			return pattern, err
		}
		pattern.regexp = reg
	}
	return pattern, nil
}


<|start_filename|>grep_test.go<|end_filename|>
package the_platinum_searcher

import (
	"bytes"
	"os"
	"strings"
	"testing"
)

func TestFixedGrep(t *testing.T) {
	opts := defaultOption()
	opts.OutputOption.EnableColor = false
	opts.OutputOption.EnableGroup = false

	pattern, _ := newPattern("go", opts)

	paths := []string{
		"ascii.txt",
		"ja/utf8.txt",
		"ja/euc-jp.txt",
		"ja/shift_jis.txt",
		"ja/broken_utf8.txt",
		"ja/broken_euc-jp.txt",
		"ja/broken_shift_jis.txt",
	}

	asserts := []string{
		"ascii.txt:2:go test",
		"ja/utf8.txt:2:go テスト",
		"ja/euc-jp.txt:2:go テスト",
		"ja/shift_jis.txt:2:go テスト",
		"ja/broken_utf8.txt:2:go テスト",
		"ja/broken_euc-jp.txt:2:go テスト",
		"ja/broken_shift_jis.txt:2:go テスト",
	}

	if !assertGrep(pattern, opts, paths, asserts) {
		t.Errorf("Grep result should contain assserts.")
	}

}

func TestFixedGrepLargeFile(t *testing.T) {
	opts := defaultOption()
	opts.OutputOption.EnableColor = false
	opts.OutputOption.EnableGroup = false

	pattern, _ := newPattern("This is a large file.", opts)

	paths := []string{"large/large.txt"}

	asserts := []string{
		"large/large.txt:10:This is a large file.",
	}

	if !assertGrep(pattern, opts, paths, asserts) {
		t.Errorf("Grep result should contain assserts.")
	}

}

func TestExtendedGrep(t *testing.T) {
	opts := defaultOption()
	opts.OutputOption.EnableColor = false
	opts.OutputOption.EnableGroup = false
	opts.SearchOption.Regexp = true

	pattern, _ := newPattern("g.*", opts)

	paths := []string{
		"ascii.txt",
		"ja/utf8.txt",
		"ja/euc-jp.txt",
		"ja/shift_jis.txt",
		"ja/broken_utf8.txt",
		"ja/broken_euc-jp.txt",
		"ja/broken_shift_jis.txt",
	}

	asserts := []string{
		"ascii.txt:2:go test",
		"ja/utf8.txt:2:go テスト",
		"ja/euc-jp.txt:2:go テスト",
		"ja/shift_jis.txt:2:go テスト",
		"ja/broken_utf8.txt:2:go テスト",
		"ja/broken_euc-jp.txt:2:go テスト",
		"ja/broken_shift_jis.txt:2:go テスト",
	}

	if !assertGrep(pattern, opts, paths, asserts) {
		t.Errorf("Grep result should contain assserts.")
	}

}

func TestStdinGrep(t *testing.T) {
	// emulate stdin
	stashStdin := os.Stdin
	fh, _ := os.Open("files/ascii.txt")
	os.Stdin = fh
	defer func() { os.Stdin = stashStdin }()

	opts := defaultOption()
	opts.OutputOption.EnableColor = false
	opts.SearchOption.SearchStream = true

	pattern, _ := newPattern("go", opts)

	paths := []string{""} // from stdin

	asserts := []string{
		"go test",
	}

	if !assertGrep(pattern, opts, paths, asserts) {
		t.Errorf("Grep result should contain assserts.")
	}
}

func assertGrep(pattern pattern, opts Option, paths, asserts []string) bool {
	buf := new(bytes.Buffer)
	printer := newPrinter(pattern, buf, opts)

	in := make(chan string)
	done := make(chan struct{})
	grep := newGrep(pattern, in, done, opts, printer)
	go grep.start()

	for _, path := range paths {
		if path == "" {
			in <- path
		} else {
			in <- "files/" + path
		}
	}
	close(in)
	<-done

	result := buf.String()
	for _, assert := range asserts {
		if !strings.Contains(result, assert) {
			return false
		}
	}
	return true
}
