From cf5d04c83e4a6d3d4349afc4ec7fd64b8024cb2f Mon Sep 17 00:00:00 2001 From: Euan Kemp Date: Mon, 3 Sep 2018 12:33:58 -0700 Subject: [PATCH 01/11] Add go.mod file This allows building and testing outside of the go path if a developer has go1.11 installed. It's also nice to keep up with the ecosystem. --- go.mod | 3 +++ go.sum | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 go.mod create mode 100644 go.sum diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..29233b6 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/gsamokovarov/jump + +require github.com/gsamokovarov/assert v0.0.0-20180414063448-8cd8ab63a335 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3908c05 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/gsamokovarov/assert v0.0.0-20180414063448-8cd8ab63a335 h1:MFE3iUApg9Sl5MmZnosCEhYXRQCKz5coShpoAF86IiE= +github.com/gsamokovarov/assert v0.0.0-20180414063448-8cd8ab63a335/go.mod h1:ejyiK4+/RLW9C/QgBK+nlwDmNB9pIW9i2WVqMmAa7no= From 5057d0994e9c6c8ebb31487d6e03eefc79b4c2a4 Mon Sep 17 00:00:00 2001 From: Euan Kemp Date: Mon, 3 Sep 2018 13:36:52 -0700 Subject: [PATCH 02/11] Respect $HOME for config location As discussed in https://github.com/golang/go/issues/26463, Go's 'user.Current().HomeDir' does not do the right thing. The 'HOME' environment variable is meant to take precedent over whatever's in /etc/passwd. This updates the config directory initialization code to use the correct home directory. --- config/config.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/config/config.go b/config/config.go index cc7c5d5..7a4b4df 100644 --- a/config/config.go +++ b/config/config.go @@ -71,10 +71,24 @@ func normalizeDir(dir string) (string, error) { return dir, nil } - usr, err := user.Current() + home, err := homeDir() if err != nil { return dir, err } - return filepath.Join(usr.HomeDir, defaultDirName), nil + return filepath.Join(home, defaultDirName), nil +} + +// See https://github.com/golang/go/issues/26463 +func homeDir() (string, error) { + home := os.Getenv("HOME") + if home != "" { + return home, nil + } + usr, err := user.Current() + if err != nil { + return "", err + } + + return usr.HomeDir, nil } From 0ee89b27a1c8532c94377560a7d9954ebffeb08b Mon Sep 17 00:00:00 2001 From: Euan Kemp Date: Mon, 3 Sep 2018 14:14:47 -0700 Subject: [PATCH 03/11] Add comment clarifying 'dirty' field in atom/file --- config/atom/atom.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/config/atom/atom.go b/config/atom/atom.go index 021613d..fd76ee8 100644 --- a/config/atom/atom.go +++ b/config/atom/atom.go @@ -52,8 +52,10 @@ func Open(name string) (File, error) { } type file struct { - to string - tmp *os.File + to string + tmp *os.File + // dirty indicates whether this tmpfile has experienced errors which mean it + // should not be used to update the original. dirty bool } From 117ca9e21cfe6326128f24e17e5a06ff32f65d98 Mon Sep 17 00:00:00 2001 From: Euan Kemp Date: Mon, 3 Sep 2018 14:35:49 -0700 Subject: [PATCH 04/11] Minor simplification --- config/entries.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/config/entries.go b/config/entries.go index 5ee2c6f..b1b6406 100644 --- a/config/entries.go +++ b/config/entries.go @@ -16,10 +16,7 @@ func (c *fileConfig) ReadEntries() (entries scoring.Entries, err error) { } defer file.Close() - if err = jsonio.Decode(file, &entries); err != nil { - return - } - + err = jsonio.Decode(file, &entries) return } From 1cccfa89f47b59def778468c12d4304c58080733 Mon Sep 17 00:00:00 2001 From: Genadi Samokovarov Date: Thu, 6 Sep 2018 20:56:59 +0300 Subject: [PATCH 05/11] Introduce an autojump configuration importer --- .travis.yml | 6 +- cmd/help_test.go | 1 + cmd/import.go | 20 +++++++ importer/autojump.go | 106 +++++++++++++++++++++++++++++++++ importer/autojump_test.go | 39 ++++++++++++ importer/importer.go | 46 ++++++++++++++ importer/importer_test.go | 60 +++++++++++++++++++ importer/testdata/autojump.txt | 6 ++ scoring/entry.go | 8 +++ scoring/entry_test.go | 7 +++ 10 files changed, 298 insertions(+), 1 deletion(-) create mode 100644 cmd/import.go create mode 100644 importer/autojump.go create mode 100644 importer/autojump_test.go create mode 100644 importer/importer.go create mode 100644 importer/importer_test.go create mode 100644 importer/testdata/autojump.txt diff --git a/.travis.yml b/.travis.yml index 5d5f8d2..776f709 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: go go: - - 1.9 + - 1.11 - master script: @@ -12,3 +12,7 @@ matrix: fast_finish: true allow_failures: - go: master + +env: + global: + - GO111MODULE=on diff --git a/cmd/help_test.go b/cmd/help_test.go index 0027c60..60fd702 100644 --- a/cmd/help_test.go +++ b/cmd/help_test.go @@ -18,6 +18,7 @@ func Example_helpCmd() { // clean Cleans the database of inexisting entries. // forget Removes the current directory from the database. // hint Hints relevant paths for jumping. + // import Import autojump or z scores. // pin Pin a directory to a search term. // pins Lists all the pinned search terms. // shell Display a shell integration script. diff --git a/cmd/import.go b/cmd/import.go new file mode 100644 index 0000000..bfaad1d --- /dev/null +++ b/cmd/import.go @@ -0,0 +1,20 @@ +package cmd + +import ( + "github.com/gsamokovarov/jump/cli" + "github.com/gsamokovarov/jump/config" + "github.com/gsamokovarov/jump/importer" + "github.com/gsamokovarov/jump/scoring" +) + +func importCmd(args cli.Args, conf config.Config) error { + imp := importer.Autojump(conf) + + return imp.Import(func(entry *scoring.Entry) { + cli.Outf("Importing %s\n", entry.Path) + }) +} + +func init() { + cli.RegisterCommand("import", "Import autojump or z scores.", importCmd) +} diff --git a/importer/autojump.go b/importer/autojump.go new file mode 100644 index 0000000..5a36990 --- /dev/null +++ b/importer/autojump.go @@ -0,0 +1,106 @@ +package importer + +import ( + "fmt" + "io" + "math" + "strconv" + "strings" + + "github.com/gsamokovarov/jump/config" + "github.com/gsamokovarov/jump/scoring" +) + +var autojumpDefaultConfigPaths = []string{ + "$HOME/.local/share/autojump/autojump.txt", + "$HOME/Library/autojump/autojump.txt", +} + +// Autojump is an importer for the autojump tool. +func Autojump(conf config.Config, configPaths ...string) Importer { + if len(configPaths) == 0 { + configPaths = autojumpDefaultConfigPaths + } + + return &autojump{ + config: conf, + configPaths: configPaths, + } +} + +type autojump struct { + config config.Config + configPaths []string +} + +func (i *autojump) Import(fn Callback) error { + autojumpEntries, err := i.parseConfig() + if err != nil { + return err + } + + jumpEntries, err := i.config.ReadEntries() + if err != nil { + return err + } + + for _, entry := range autojumpEntries { + if _, found := jumpEntries.Find(entry.Path); found { + continue + } + + if fn != nil { + fn(entry) + } + + jumpEntries = append(jumpEntries, entry) + } + + return i.config.WriteEntries(jumpEntries) +} + +func (i *autojump) parseConfig() (scoring.Entries, error) { + content, err := readConfig(i.configPaths) + if err != nil { + return nil, err + } + + var entries scoring.Entries + + for _, line := range strings.Split(content, "\n") { + entry, err := i.newEntryFromLine(line) + if err == io.EOF { + continue + } + if err != nil { + return nil, err + } + + if _, found := entries.Find(entry.Path); found { + continue + } + + entries = append(entries, entry) + } + + return entries, nil +} + +func (i *autojump) newEntryFromLine(line string) (*scoring.Entry, error) { + if line == "" { + return nil, io.EOF + } + + parts := strings.Split(line, "\t") + if len(parts) != 2 { + return nil, fmt.Errorf("importer: cannot parse entry: %s", line) + } + + path := parts[1] + weight, err := strconv.ParseFloat(parts[0], 64) + if err != nil { + return nil, err + } + + return scoring.NewEntryWithWeight(path, int64(math.Round(weight))), nil +} diff --git a/importer/autojump_test.go b/importer/autojump_test.go new file mode 100644 index 0000000..652b793 --- /dev/null +++ b/importer/autojump_test.go @@ -0,0 +1,39 @@ +package importer + +import ( + p "path" + "testing" + + "github.com/gsamokovarov/assert" +) + +func TestAutojump(t *testing.T) { + conf := &testConfig{} + configPath := p.Join(td, "autojump.txt") + + imp := Autojump(conf, configPath) + + err := imp.Import(nil) + assert.Nil(t, err) + + assert. + Len(t, 6, conf.Entries). + // 0 + Equal(t, "/Users/genadi/Development/jump", conf.Entries[0].Path). + Equal(t, 39, conf.Entries[0].Score.Weight). + // 1 + Equal(t, "/Users/genadi/Development/mock_last_status", conf.Entries[1].Path). + Equal(t, 14, conf.Entries[1].Score.Weight). + // 2 + Equal(t, "/Users/genadi/Development", conf.Entries[2].Path). + Equal(t, 33, conf.Entries[2].Score.Weight). + // 3 + Equal(t, "/Users/genadi/.go/src/github.com/gsamokovarov/jump", conf.Entries[3].Path). + Equal(t, 14, conf.Entries[3].Score.Weight). + // 4 + Equal(t, "/usr/local/Cellar/autojump", conf.Entries[4].Path). + Equal(t, 44, conf.Entries[4].Score.Weight). + // 5 + Equal(t, "/Users/genadi/Development/gloat", conf.Entries[5].Path). + Equal(t, 20, conf.Entries[5].Score.Weight) +} diff --git a/importer/importer.go b/importer/importer.go new file mode 100644 index 0000000..324aff4 --- /dev/null +++ b/importer/importer.go @@ -0,0 +1,46 @@ +package importer + +import ( + "errors" + "io/ioutil" + "os" + + "github.com/gsamokovarov/jump/scoring" +) + +// UnsupportedErr is an error returned when the importing tool is not found. +var UnsupportedErr = errors.New("importer: unsupported") + +// Callback is called on every import. +type Callback func(*scoring.Entry) + +// Importer imports a configuration from an external tool into jump. +type Importer interface { + Import(fn Callback) error +} + +func readConfig(paths []string) (string, error) { + path, err := findPath(paths) + if err != nil { + return "", err + } + + bytes, err := ioutil.ReadFile(path) + if err != nil { + return "", err + } + + return string(bytes), nil +} + +func findPath(paths []string) (string, error) { + for _, path := range paths { + path = os.ExpandEnv(path) + + if _, err := os.Stat(path); !os.IsNotExist(err) { + return path, nil + } + } + + return "", UnsupportedErr +} diff --git a/importer/importer_test.go b/importer/importer_test.go new file mode 100644 index 0000000..58291ae --- /dev/null +++ b/importer/importer_test.go @@ -0,0 +1,60 @@ +package importer + +import ( + "os" + "path" + + "github.com/gsamokovarov/jump/config" + "github.com/gsamokovarov/jump/scoring" +) + +var td string + +type testConfig struct { + Entries scoring.Entries + Search config.Search + Pins map[string]string + Pin string +} + +func (c *testConfig) ReadEntries() (scoring.Entries, error) { + return c.Entries, nil +} + +func (c *testConfig) WriteEntries(entries scoring.Entries) error { + c.Entries = entries + return nil +} + +func (c *testConfig) ReadSearch() config.Search { + return c.Search +} + +func (c *testConfig) WriteSearch(term string, index int) error { + c.Search.Term = term + c.Search.Index = index + return nil +} + +func (c *testConfig) ReadPins() (map[string]string, error) { + return c.Pins, nil +} + +func (c *testConfig) FindPin(term string) (string, bool) { + return c.Pin, c.Pin != "" +} + +func (c *testConfig) WritePin(_, value string) error { + c.Pin = value + return nil +} + +func (c *testConfig) RemovePin(term string) error { + c.Pin = "" + return nil +} + +func init() { + cwd, _ := os.Getwd() + td = path.Join(cwd, "testdata") +} diff --git a/importer/testdata/autojump.txt b/importer/testdata/autojump.txt new file mode 100644 index 0000000..1d4e20c --- /dev/null +++ b/importer/testdata/autojump.txt @@ -0,0 +1,6 @@ +38.729833462 /Users/genadi/Development/jump +14.1421356237 /Users/genadi/Development/mock_last_status +33.1662479035 /Users/genadi/Development +14.1421356237 /Users/genadi/.go/src/github.com/gsamokovarov/jump +43.5889894353 /usr/local/Cellar/autojump +20.0 /Users/genadi/Development/gloat diff --git a/scoring/entry.go b/scoring/entry.go index 9f37a8f..fa99c49 100644 --- a/scoring/entry.go +++ b/scoring/entry.go @@ -27,3 +27,11 @@ func (e *Entry) String() string { func NewEntry(path string) *Entry { return &Entry{path, NewScore()} } + +// NewEntryWithWeight creates a new entry with the specified path and weight. +func NewEntryWithWeight(path string, weight int64) *Entry { + score := NewScore() + score.Weight = weight + + return &Entry{path, score} +} diff --git a/scoring/entry_test.go b/scoring/entry_test.go index b92a89b..c796a84 100644 --- a/scoring/entry_test.go +++ b/scoring/entry_test.go @@ -18,3 +18,10 @@ func TestEntriesUpdateScore(t *testing.T) { assert.Equal(t, 2, entry.Score.Weight) } + +func TestNewEntriesWithWeight(t *testing.T) { + entry := NewEntryWithWeight("/foo", 2) + + assert.Equal(t, "/foo", entry.Path) + assert.Equal(t, 2, entry.Score.Weight) +} From 9e344e4a9f84ce1b07b029048f1f094f6dca4a49 Mon Sep 17 00:00:00 2001 From: Genadi Samokovarov Date: Fri, 7 Sep 2018 02:08:07 +0300 Subject: [PATCH 06/11] Add z datafile importer --- cmd/import.go | 2 +- importer/autojump.go | 8 ++- importer/autojump_test.go | 26 +++++---- importer/importer.go | 18 ++++++ importer/importer_test.go | 24 ++++++++ importer/multi.go | 28 ++++++++++ importer/multi_test.go | 44 +++++++++++++++ importer/testdata/z | 4 ++ importer/z.go | 115 ++++++++++++++++++++++++++++++++++++++ importer/z_test.go | 42 ++++++++++++++ scoring/entry.go | 8 --- scoring/entry_test.go | 7 --- 12 files changed, 298 insertions(+), 28 deletions(-) create mode 100644 importer/multi.go create mode 100644 importer/multi_test.go create mode 100644 importer/testdata/z create mode 100644 importer/z.go create mode 100644 importer/z_test.go diff --git a/cmd/import.go b/cmd/import.go index bfaad1d..b1bcb39 100644 --- a/cmd/import.go +++ b/cmd/import.go @@ -8,7 +8,7 @@ import ( ) func importCmd(args cli.Args, conf config.Config) error { - imp := importer.Autojump(conf) + imp := importer.Guess(args.CommandName(), conf) return imp.Import(func(entry *scoring.Entry) { cli.Outf("Importing %s\n", entry.Path) diff --git a/importer/autojump.go b/importer/autojump.go index 5a36990..633c17c 100644 --- a/importer/autojump.go +++ b/importer/autojump.go @@ -102,5 +102,11 @@ func (i *autojump) newEntryFromLine(line string) (*scoring.Entry, error) { return nil, err } - return scoring.NewEntryWithWeight(path, int64(math.Round(weight))), nil + return &scoring.Entry{ + Path: path, + Score: &scoring.Score{ + Weight: int64(math.Round(weight)), + Age: scoring.Now, + }, + }, nil } diff --git a/importer/autojump_test.go b/importer/autojump_test.go index 652b793..780028d 100644 --- a/importer/autojump_test.go +++ b/importer/autojump_test.go @@ -19,21 +19,25 @@ func TestAutojump(t *testing.T) { assert. Len(t, 6, conf.Entries). // 0 - Equal(t, "/Users/genadi/Development/jump", conf.Entries[0].Path). - Equal(t, 39, conf.Entries[0].Score.Weight). + Equal(t, "/Users/genadi/Development/mock_last_status", conf.Entries[0].Path). + Equal(t, 14, conf.Entries[0].Score.Weight). // 1 - Equal(t, "/Users/genadi/Development/mock_last_status", conf.Entries[1].Path). + Equal(t, "/Users/genadi/.go/src/github.com/gsamokovarov/jump", conf.Entries[1].Path). Equal(t, 14, conf.Entries[1].Score.Weight). // 2 - Equal(t, "/Users/genadi/Development", conf.Entries[2].Path). - Equal(t, 33, conf.Entries[2].Score.Weight). + Equal(t, "/Users/genadi/Development/gloat", conf.Entries[2].Path). + Equal(t, 20, conf.Entries[2].Score.Weight). // 3 - Equal(t, "/Users/genadi/.go/src/github.com/gsamokovarov/jump", conf.Entries[3].Path). - Equal(t, 14, conf.Entries[3].Score.Weight). + Equal(t, "/Users/genadi/Development", conf.Entries[3].Path). + Equal(t, 33, conf.Entries[3].Score.Weight). // 4 - Equal(t, "/usr/local/Cellar/autojump", conf.Entries[4].Path). - Equal(t, 44, conf.Entries[4].Score.Weight). + Equal(t, "/Users/genadi/Development/jump", conf.Entries[4].Path). + Equal(t, 39, conf.Entries[4].Score.Weight). // 5 - Equal(t, "/Users/genadi/Development/gloat", conf.Entries[5].Path). - Equal(t, 20, conf.Entries[5].Score.Weight) + Equal(t, "/usr/local/Cellar/autojump", conf.Entries[5].Path). + Equal(t, 44, conf.Entries[5].Score.Weight) + + for i, j := 0, 1; i < len(conf.Entries)-1; i, j = i+1, j+1 { + assert.True(t, conf.Entries[i].CalculateScore() <= conf.Entries[j].CalculateScore()) + } } diff --git a/importer/importer.go b/importer/importer.go index 324aff4..82d279c 100644 --- a/importer/importer.go +++ b/importer/importer.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "os" + "github.com/gsamokovarov/jump/config" "github.com/gsamokovarov/jump/scoring" ) @@ -19,6 +20,23 @@ type Importer interface { Import(fn Callback) error } +// Guess tries to guess the importer to use based on a hint. +func Guess(hint string, conf config.Config) Importer { + var imp Importer + + switch hint { + case "autojump": + imp = Autojump(conf) + case "z": + imp = Z(conf) + default: + // First try z, then try autojump. + imp = multiImporter{Z(conf), Autojump(conf)} + } + + return imp +} + func readConfig(paths []string) (string, error) { path, err := findPath(paths) if err != nil { diff --git a/importer/importer_test.go b/importer/importer_test.go index 58291ae..1c3509e 100644 --- a/importer/importer_test.go +++ b/importer/importer_test.go @@ -3,7 +3,9 @@ package importer import ( "os" "path" + "testing" + "github.com/gsamokovarov/assert" "github.com/gsamokovarov/jump/config" "github.com/gsamokovarov/jump/scoring" ) @@ -23,6 +25,7 @@ func (c *testConfig) ReadEntries() (scoring.Entries, error) { func (c *testConfig) WriteEntries(entries scoring.Entries) error { c.Entries = entries + c.Entries.Sort() return nil } @@ -54,6 +57,27 @@ func (c *testConfig) RemovePin(term string) error { return nil } +func TestGuess_Autojump(t *testing.T) { + imp := Guess("autojump", &testConfig{}) + + _, ok := imp.(*autojump) + assert.True(t, ok) +} + +func TestGuess_Z(t *testing.T) { + imp := Guess("z", &testConfig{}) + + _, ok := imp.(*z) + assert.True(t, ok) +} + +func TestGuess_Both(t *testing.T) { + imp := Guess("", &testConfig{}) + + _, ok := imp.(multiImporter) + assert.True(t, ok) +} + func init() { cwd, _ := os.Getwd() td = path.Join(cwd, "testdata") diff --git a/importer/multi.go b/importer/multi.go new file mode 100644 index 0000000..263bd51 --- /dev/null +++ b/importer/multi.go @@ -0,0 +1,28 @@ +package importer + +// multiImporter tries to import configurations from multiple importers. If at +// least on of the importers succeed, no errors will be returned. +type multiImporter []Importer + +func (mi multiImporter) Import(fn Callback) error { + var lastErr error + atLeastOneSucceeded := false + + for _, i := range mi { + err := i.Import(fn) + if err == UnsupportedErr { + continue + } + if err != nil { + lastErr = err + } + + atLeastOneSucceeded = true + } + + if !atLeastOneSucceeded && lastErr != nil { + return lastErr + } + + return nil +} diff --git a/importer/multi_test.go b/importer/multi_test.go new file mode 100644 index 0000000..d6b06bf --- /dev/null +++ b/importer/multi_test.go @@ -0,0 +1,44 @@ +package importer + +import ( + "errors" + p "path" + "testing" + + "github.com/gsamokovarov/assert" +) + +type failingImporter struct{} + +func (failingImporter) Import(Callback) error { return errors.New("importer: failing") } + +func Test_multiImporter(t *testing.T) { + conf := &testConfig{} + autojumpPath := p.Join(td, "autojump.txt") + zPath := p.Join(td, "z") + + imp := multiImporter{ + Autojump(conf, autojumpPath), + Z(conf, zPath), + } + + err := imp.Import(nil) + assert.Nil(t, err) + + assert.Len(t, 8, conf.Entries) +} + +func Test_multiImporter_oneErrored(t *testing.T) { + conf := &testConfig{} + autojumpPath := p.Join(td, "autojump.txt") + + imp := multiImporter{ + failingImporter{}, + Autojump(conf, autojumpPath), + } + + err := imp.Import(nil) + assert.Nil(t, err) + + assert.Len(t, 6, conf.Entries) +} diff --git a/importer/testdata/z b/importer/testdata/z new file mode 100644 index 0000000..88ffd8f --- /dev/null +++ b/importer/testdata/z @@ -0,0 +1,4 @@ +/Users/genadi/Development/hack|11|1536272816 +/Users/genadi/Development/masse|1|1536272502 +/Users/genadi/Development|3|1536272506 +/Users/genadi/.go/src/github.com/gsamokovarov/jump|1|1536272492 diff --git a/importer/z.go b/importer/z.go new file mode 100644 index 0000000..b2e301d --- /dev/null +++ b/importer/z.go @@ -0,0 +1,115 @@ +package importer + +import ( + "fmt" + "io" + "strconv" + "strings" + "time" + + "github.com/gsamokovarov/jump/config" + "github.com/gsamokovarov/jump/scoring" +) + +var zDefaultConfigPaths = []string{ + "$HOME/.z", +} + +// Z is an importer for the z tool. +func Z(conf config.Config, configPaths ...string) Importer { + if len(configPaths) == 0 { + configPaths = zDefaultConfigPaths + } + + return &z{ + config: conf, + configPaths: configPaths, + } +} + +type z struct { + config config.Config + configPaths []string +} + +func (i *z) Import(fn Callback) error { + zEntries, err := i.parseConfig() + if err != nil { + return err + } + + jumpEntries, err := i.config.ReadEntries() + if err != nil { + return err + } + + for _, entry := range zEntries { + if _, found := jumpEntries.Find(entry.Path); found { + continue + } + + if fn != nil { + fn(entry) + } + + jumpEntries = append(jumpEntries, entry) + } + + return i.config.WriteEntries(jumpEntries) +} + +func (i *z) parseConfig() (scoring.Entries, error) { + content, err := readConfig(i.configPaths) + if err != nil { + return nil, err + } + + var entries scoring.Entries + + for _, line := range strings.Split(content, "\n") { + entry, err := i.newEntryFromLine(line) + if err == io.EOF { + continue + } + if err != nil { + return nil, err + } + + if _, found := entries.Find(entry.Path); found { + continue + } + + entries = append(entries, entry) + } + + return entries, nil +} + +func (i *z) newEntryFromLine(line string) (*scoring.Entry, error) { + if line == "" { + return nil, io.EOF + } + + parts := strings.Split(line, "|") + if len(parts) != 3 { + return nil, fmt.Errorf("importer: cannot parse entry: %s", line) + } + + path := parts[0] + weight, err := strconv.ParseInt(parts[1], 10, 64) + if err != nil { + return nil, err + } + epoch, err := strconv.ParseInt(parts[2], 10, 64) + if err != nil { + return nil, err + } + + return &scoring.Entry{ + Path: path, + Score: &scoring.Score{ + Weight: weight, + Age: time.Unix(epoch, 0), + }, + }, nil +} diff --git a/importer/z_test.go b/importer/z_test.go new file mode 100644 index 0000000..277bad8 --- /dev/null +++ b/importer/z_test.go @@ -0,0 +1,42 @@ +package importer + +import ( + p "path" + "testing" + "time" + + "github.com/gsamokovarov/assert" +) + +func TestZ(t *testing.T) { + conf := &testConfig{} + configPath := p.Join(td, "z") + + imp := Z(conf, configPath) + + err := imp.Import(nil) + assert.Nil(t, err) + + assert. + Len(t, 4, conf.Entries). + // 0 + Equal(t, "/Users/genadi/.go/src/github.com/gsamokovarov/jump", conf.Entries[0].Path). + Equal(t, 1, conf.Entries[0].Score.Weight). + Equal(t, time.Unix(1536272492, 0), conf.Entries[0].Score.Age). + // 1 + Equal(t, "/Users/genadi/Development/masse", conf.Entries[1].Path). + Equal(t, 1, conf.Entries[1].Score.Weight). + Equal(t, time.Unix(1536272502, 0), conf.Entries[1].Score.Age). + // 2 + Equal(t, "/Users/genadi/Development", conf.Entries[2].Path). + Equal(t, 3, conf.Entries[2].Score.Weight). + Equal(t, time.Unix(1536272506, 0), conf.Entries[2].Score.Age). + // 3 + Equal(t, "/Users/genadi/Development/hack", conf.Entries[3].Path). + Equal(t, 11, conf.Entries[3].Score.Weight). + Equal(t, time.Unix(1536272816, 0), conf.Entries[3].Score.Age) + + for i, j := 0, 1; i < len(conf.Entries)-1; i, j = i+1, j+1 { + assert.True(t, conf.Entries[i].CalculateScore() <= conf.Entries[j].CalculateScore()) + } +} diff --git a/scoring/entry.go b/scoring/entry.go index fa99c49..9f37a8f 100644 --- a/scoring/entry.go +++ b/scoring/entry.go @@ -27,11 +27,3 @@ func (e *Entry) String() string { func NewEntry(path string) *Entry { return &Entry{path, NewScore()} } - -// NewEntryWithWeight creates a new entry with the specified path and weight. -func NewEntryWithWeight(path string, weight int64) *Entry { - score := NewScore() - score.Weight = weight - - return &Entry{path, score} -} diff --git a/scoring/entry_test.go b/scoring/entry_test.go index c796a84..b92a89b 100644 --- a/scoring/entry_test.go +++ b/scoring/entry_test.go @@ -18,10 +18,3 @@ func TestEntriesUpdateScore(t *testing.T) { assert.Equal(t, 2, entry.Score.Weight) } - -func TestNewEntriesWithWeight(t *testing.T) { - entry := NewEntryWithWeight("/foo", 2) - - assert.Equal(t, "/foo", entry.Path) - assert.Equal(t, 2, entry.Score.Weight) -} From 3217ff7579e493d45538257e874e4626b7d76f6a Mon Sep 17 00:00:00 2001 From: Genadi Samokovarov Date: Fri, 7 Sep 2018 14:34:05 +0300 Subject: [PATCH 07/11] Move the nil check to the Callback --- importer/autojump.go | 4 +--- importer/importer.go | 7 +++++++ importer/importer_test.go | 7 +++++++ importer/z.go | 4 +--- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/importer/autojump.go b/importer/autojump.go index 633c17c..911b433 100644 --- a/importer/autojump.go +++ b/importer/autojump.go @@ -49,9 +49,7 @@ func (i *autojump) Import(fn Callback) error { continue } - if fn != nil { - fn(entry) - } + fn.Call(entry) jumpEntries = append(jumpEntries, entry) } diff --git a/importer/importer.go b/importer/importer.go index 82d279c..d9f33f0 100644 --- a/importer/importer.go +++ b/importer/importer.go @@ -15,6 +15,13 @@ var UnsupportedErr = errors.New("importer: unsupported") // Callback is called on every import. type Callback func(*scoring.Entry) +// Call calls the callback without the need of nil checks. +func (fn Callback) Call(entry *scoring.Entry) { + if fn != nil { + fn(entry) + } +} + // Importer imports a configuration from an external tool into jump. type Importer interface { Import(fn Callback) error diff --git a/importer/importer_test.go b/importer/importer_test.go index 1c3509e..c04b8a7 100644 --- a/importer/importer_test.go +++ b/importer/importer_test.go @@ -78,6 +78,13 @@ func TestGuess_Both(t *testing.T) { assert.True(t, ok) } +func TestCallback(t *testing.T) { + var fn Callback + + // Does not crash when fn is nil. + fn.Call(nil) +} + func init() { cwd, _ := os.Getwd() td = path.Join(cwd, "testdata") diff --git a/importer/z.go b/importer/z.go index b2e301d..609e367 100644 --- a/importer/z.go +++ b/importer/z.go @@ -48,9 +48,7 @@ func (i *z) Import(fn Callback) error { continue } - if fn != nil { - fn(entry) - } + fn.Call(entry) jumpEntries = append(jumpEntries, entry) } From d683ffba09ffbd5536ce7cdb4933b334683cc67b Mon Sep 17 00:00:00 2001 From: Genadi Samokovarov Date: Fri, 7 Sep 2018 15:28:38 +0300 Subject: [PATCH 08/11] Extract a common testing config implementation --- cmd/cd_test.go | 11 ++++---- cmd/chdir_test.go | 5 ++-- cmd/clean_test.go | 3 +- cmd/cmd_test.go | 47 ------------------------------- cmd/forget_test.go | 3 +- cmd/hint_test.go | 5 ++-- cmd/pin_test.go | 5 ++-- cmd/pins_test.go | 3 +- cmd/top_test.go | 3 +- cmd/unpin_test.go | 3 +- config/testing.go | 58 +++++++++++++++++++++++++++++++++++++++ importer/autojump_test.go | 3 +- importer/importer_test.go | 52 ++--------------------------------- importer/multi_test.go | 5 ++-- importer/z_test.go | 3 +- 15 files changed, 93 insertions(+), 116 deletions(-) create mode 100644 config/testing.go diff --git a/cmd/cd_test.go b/cmd/cd_test.go index 3442247..eee1139 100644 --- a/cmd/cd_test.go +++ b/cmd/cd_test.go @@ -8,11 +8,12 @@ import ( "github.com/gsamokovarov/assert" "github.com/gsamokovarov/jump/cli" + "github.com/gsamokovarov/jump/config" s "github.com/gsamokovarov/jump/scoring" ) func Test_cdCmd(t *testing.T) { - conf := &testConfig{ + conf := &config.Testing{ Entries: s.Entries{ &s.Entry{p.Join(td, "web-console"), &s.Score{Weight: 100, Age: s.Now}}, &s.Entry{p.Join(td, "/client/website"), &s.Score{Weight: 90, Age: s.Now}}, @@ -27,7 +28,7 @@ func Test_cdCmd(t *testing.T) { } func Test_cdCmd_noEntries(t *testing.T) { - conf := &testConfig{} + conf := &config.Testing{} output := capture(&os.Stderr, func() { assert.Nil(t, cdCmd(cli.Args{"wc"}, conf)) @@ -37,7 +38,7 @@ func Test_cdCmd_noEntries(t *testing.T) { } func Test_cdCmd_multipleArgumentsAsSeparators(t *testing.T) { - conf := &testConfig{ + conf := &config.Testing{ Entries: s.Entries{ &s.Entry{p.Join(td, "web-console"), &s.Score{Weight: 100, Age: s.Now}}, &s.Entry{p.Join(td, "/client/website"), &s.Score{Weight: 90, Age: s.Now}}, @@ -52,7 +53,7 @@ func Test_cdCmd_multipleArgumentsAsSeparators(t *testing.T) { } func Test_cdCmd_absolutePath(t *testing.T) { - conf := &testConfig{ + conf := &config.Testing{ Entries: s.Entries{ &s.Entry{p.Join(td, "web-console"), &s.Score{Weight: 100, Age: s.Now}}, &s.Entry{p.Join(td, "/client/website"), &s.Score{Weight: 90, Age: s.Now}}, @@ -71,7 +72,7 @@ func Test_cdCmd_exactMatch(t *testing.T) { p2 := p.Join(td, "/client/website") p3 := p.Join(td, "web") - conf := &testConfig{ + conf := &config.Testing{ Entries: s.Entries{ &s.Entry{p1, &s.Score{Weight: 100, Age: s.Now}}, &s.Entry{p2, &s.Score{Weight: 90, Age: s.Now}}, diff --git a/cmd/chdir_test.go b/cmd/chdir_test.go index aba2194..ecf0195 100644 --- a/cmd/chdir_test.go +++ b/cmd/chdir_test.go @@ -7,13 +7,14 @@ import ( "github.com/gsamokovarov/assert" "github.com/gsamokovarov/jump/cli" + "github.com/gsamokovarov/jump/config" ) func Test_chdirCmd(t *testing.T) { p1 := p.Join(td, "web-console") p2 := p.Join(td, "/client/website") - conf := &testConfig{} + conf := &config.Testing{} entries, err := conf.ReadEntries() assert.Nil(t, err) @@ -47,7 +48,7 @@ func Test_chdirCmd(t *testing.T) { } func Test_chdirCmd_cwd(t *testing.T) { - conf := &testConfig{} + conf := &config.Testing{} entries, err := conf.ReadEntries() assert.Nil(t, err) diff --git a/cmd/clean_test.go b/cmd/clean_test.go index 8aea87e..f80013f 100644 --- a/cmd/clean_test.go +++ b/cmd/clean_test.go @@ -7,10 +7,11 @@ import ( "github.com/gsamokovarov/assert" "github.com/gsamokovarov/jump/cli" + "github.com/gsamokovarov/jump/config" ) func Test_cleanCmd(t *testing.T) { - conf := &testConfig{} + conf := &config.Testing{} assert.Nil(t, chdirCmd(cli.Args{"/inexistent/dir/dh891n2kisdha"}, conf)) diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go index ae87d1f..2130d2d 100644 --- a/cmd/cmd_test.go +++ b/cmd/cmd_test.go @@ -4,57 +4,10 @@ import ( "io/ioutil" "os" "path" - - "github.com/gsamokovarov/jump/config" - "github.com/gsamokovarov/jump/scoring" ) var td string -type testConfig struct { - Entries scoring.Entries - Search config.Search - Pins map[string]string - Pin string -} - -func (c *testConfig) ReadEntries() (scoring.Entries, error) { - return c.Entries, nil -} - -func (c *testConfig) WriteEntries(entries scoring.Entries) error { - c.Entries = entries - return nil -} - -func (c *testConfig) ReadSearch() config.Search { - return c.Search -} - -func (c *testConfig) WriteSearch(term string, index int) error { - c.Search.Term = term - c.Search.Index = index - return nil -} - -func (c *testConfig) ReadPins() (map[string]string, error) { - return c.Pins, nil -} - -func (c *testConfig) FindPin(term string) (string, bool) { - return c.Pin, c.Pin != "" -} - -func (c *testConfig) WritePin(_, value string) error { - c.Pin = value - return nil -} - -func (c *testConfig) RemovePin(term string) error { - c.Pin = "" - return nil -} - func capture(stream **os.File, fn func()) string { rescue := *stream r, w, _ := os.Pipe() diff --git a/cmd/forget_test.go b/cmd/forget_test.go index a669e9f..99d3ce9 100644 --- a/cmd/forget_test.go +++ b/cmd/forget_test.go @@ -8,13 +8,14 @@ import ( "github.com/gsamokovarov/assert" "github.com/gsamokovarov/jump/cli" + "github.com/gsamokovarov/jump/config" "github.com/gsamokovarov/jump/scoring" ) func Test_forgetCmd(t *testing.T) { p := p.Join(td, "web-console") - conf := &testConfig{ + conf := &config.Testing{ Entries: scoring.Entries{scoring.NewEntry(p)}, } diff --git a/cmd/hint_test.go b/cmd/hint_test.go index 19578cb..04e8b25 100644 --- a/cmd/hint_test.go +++ b/cmd/hint_test.go @@ -8,6 +8,7 @@ import ( "github.com/gsamokovarov/assert" "github.com/gsamokovarov/jump/cli" + "github.com/gsamokovarov/jump/config" s "github.com/gsamokovarov/jump/scoring" ) @@ -15,7 +16,7 @@ func Test_hintCmd_short(t *testing.T) { p1 := p.Join(td, "web-console") p2 := p.Join(td, "/client/website") - conf := &testConfig{ + conf := &config.Testing{ Entries: s.Entries{ &s.Entry{p2, &s.Score{Weight: 90, Age: s.Now}}, &s.Entry{p1, &s.Score{Weight: 100, Age: s.Now}}, @@ -33,7 +34,7 @@ func Test_hintCmd_short(t *testing.T) { } func Test_hintCmd_noEntries(t *testing.T) { - conf := &testConfig{} + conf := &config.Testing{} output := capture(&os.Stdout, func() { assert.Nil(t, hintCmd(cli.Args{"webcons"}, conf)) diff --git a/cmd/pin_test.go b/cmd/pin_test.go index a44b68a..f664f70 100644 --- a/cmd/pin_test.go +++ b/cmd/pin_test.go @@ -7,6 +7,7 @@ import ( "github.com/gsamokovarov/assert" "github.com/gsamokovarov/jump/cli" + "github.com/gsamokovarov/jump/config" s "github.com/gsamokovarov/jump/scoring" ) @@ -14,7 +15,7 @@ func Test_pinCmd(t *testing.T) { p1 := p.Join(td, "web") p2 := p.Join(td, "web-console") - conf := &testConfig{ + conf := &config.Testing{ Entries: s.Entries{ &s.Entry{p2, &s.Score{Weight: 1, Age: s.Now}}, &s.Entry{p1, &s.Score{Weight: 100, Age: s.Now}}, @@ -34,7 +35,7 @@ func Test_pinCmd_normalizedTerms(t *testing.T) { p1 := p.Join(td, "web") p2 := p.Join(td, "web-console") - conf := &testConfig{ + conf := &config.Testing{ Entries: s.Entries{ &s.Entry{p2, &s.Score{Weight: 1, Age: s.Now}}, &s.Entry{p1, &s.Score{Weight: 100, Age: s.Now}}, diff --git a/cmd/pins_test.go b/cmd/pins_test.go index 489b128..abdce9f 100644 --- a/cmd/pins_test.go +++ b/cmd/pins_test.go @@ -5,10 +5,11 @@ import ( "testing" "github.com/gsamokovarov/assert" + "github.com/gsamokovarov/jump/config" ) func Test_pinsCmd(t *testing.T) { - conf := &testConfig{ + conf := &config.Testing{ Pins: map[string]string{ "r": "/home/user/projects/rails", }, diff --git a/cmd/top_test.go b/cmd/top_test.go index a232306..8418565 100644 --- a/cmd/top_test.go +++ b/cmd/top_test.go @@ -8,6 +8,7 @@ import ( "github.com/gsamokovarov/assert" "github.com/gsamokovarov/jump/cli" + "github.com/gsamokovarov/jump/config" s "github.com/gsamokovarov/jump/scoring" ) @@ -15,7 +16,7 @@ func Test_topCmd(t *testing.T) { wc := p.Join(td, "web-console") web := p.Join(td, "/client/website") - conf := &testConfig{ + conf := &config.Testing{ Entries: s.Entries{ &s.Entry{wc, &s.Score{Weight: 100, Age: s.Now}}, &s.Entry{web, &s.Score{Weight: 90, Age: s.Now}}, diff --git a/cmd/unpin_test.go b/cmd/unpin_test.go index 9923f1b..78937b5 100644 --- a/cmd/unpin_test.go +++ b/cmd/unpin_test.go @@ -7,6 +7,7 @@ import ( "github.com/gsamokovarov/assert" "github.com/gsamokovarov/jump/cli" + "github.com/gsamokovarov/jump/config" s "github.com/gsamokovarov/jump/scoring" ) @@ -14,7 +15,7 @@ func Test_unpinCmd(t *testing.T) { p1 := p.Join(td, "web") p2 := p.Join(td, "web-console") - conf := &testConfig{ + conf := &config.Testing{ Entries: s.Entries{ &s.Entry{p2, &s.Score{Weight: 1, Age: s.Now}}, &s.Entry{p1, &s.Score{Weight: 100, Age: s.Now}}, diff --git a/config/testing.go b/config/testing.go new file mode 100644 index 0000000..1fe5afe --- /dev/null +++ b/config/testing.go @@ -0,0 +1,58 @@ +package config + +import "github.com/gsamokovarov/jump/scoring" + +// Testing is an in-memory testing config which loosely follows the default +// file-based configuration behavior. +type Testing struct { + Entries scoring.Entries + Search Search + Pins map[string]string + Pin string +} + +// ReadEntries implements the Config interface. +func (c *Testing) ReadEntries() (scoring.Entries, error) { + return c.Entries, nil +} + +// WriteEntries implements the Config interface. +func (c *Testing) WriteEntries(entries scoring.Entries) error { + c.Entries = entries + c.Entries.Sort() + return nil +} + +// ReadSearch implements the Config interface. +func (c *Testing) ReadSearch() Search { + return c.Search +} + +// WriteSearch implements the Config interface. +func (c *Testing) WriteSearch(term string, index int) error { + c.Search.Term = term + c.Search.Index = index + return nil +} + +// ReadPins implements the Config interface. +func (c *Testing) ReadPins() (map[string]string, error) { + return c.Pins, nil +} + +// FindPin implements the Config interface. +func (c *Testing) FindPin(term string) (string, bool) { + return c.Pin, c.Pin != "" +} + +// WritePin implements the Config interface. +func (c *Testing) WritePin(_, value string) error { + c.Pin = value + return nil +} + +// RemovePin implements the Config interface. +func (c *Testing) RemovePin(term string) error { + c.Pin = "" + return nil +} diff --git a/importer/autojump_test.go b/importer/autojump_test.go index 780028d..17c5113 100644 --- a/importer/autojump_test.go +++ b/importer/autojump_test.go @@ -5,10 +5,11 @@ import ( "testing" "github.com/gsamokovarov/assert" + "github.com/gsamokovarov/jump/config" ) func TestAutojump(t *testing.T) { - conf := &testConfig{} + conf := &config.Testing{} configPath := p.Join(td, "autojump.txt") imp := Autojump(conf, configPath) diff --git a/importer/importer_test.go b/importer/importer_test.go index c04b8a7..8b38351 100644 --- a/importer/importer_test.go +++ b/importer/importer_test.go @@ -7,72 +7,26 @@ import ( "github.com/gsamokovarov/assert" "github.com/gsamokovarov/jump/config" - "github.com/gsamokovarov/jump/scoring" ) var td string -type testConfig struct { - Entries scoring.Entries - Search config.Search - Pins map[string]string - Pin string -} - -func (c *testConfig) ReadEntries() (scoring.Entries, error) { - return c.Entries, nil -} - -func (c *testConfig) WriteEntries(entries scoring.Entries) error { - c.Entries = entries - c.Entries.Sort() - return nil -} - -func (c *testConfig) ReadSearch() config.Search { - return c.Search -} - -func (c *testConfig) WriteSearch(term string, index int) error { - c.Search.Term = term - c.Search.Index = index - return nil -} - -func (c *testConfig) ReadPins() (map[string]string, error) { - return c.Pins, nil -} - -func (c *testConfig) FindPin(term string) (string, bool) { - return c.Pin, c.Pin != "" -} - -func (c *testConfig) WritePin(_, value string) error { - c.Pin = value - return nil -} - -func (c *testConfig) RemovePin(term string) error { - c.Pin = "" - return nil -} - func TestGuess_Autojump(t *testing.T) { - imp := Guess("autojump", &testConfig{}) + imp := Guess("autojump", &config.Testing{}) _, ok := imp.(*autojump) assert.True(t, ok) } func TestGuess_Z(t *testing.T) { - imp := Guess("z", &testConfig{}) + imp := Guess("z", &config.Testing{}) _, ok := imp.(*z) assert.True(t, ok) } func TestGuess_Both(t *testing.T) { - imp := Guess("", &testConfig{}) + imp := Guess("", &config.Testing{}) _, ok := imp.(multiImporter) assert.True(t, ok) diff --git a/importer/multi_test.go b/importer/multi_test.go index d6b06bf..f93b6dd 100644 --- a/importer/multi_test.go +++ b/importer/multi_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/gsamokovarov/assert" + "github.com/gsamokovarov/jump/config" ) type failingImporter struct{} @@ -13,7 +14,7 @@ type failingImporter struct{} func (failingImporter) Import(Callback) error { return errors.New("importer: failing") } func Test_multiImporter(t *testing.T) { - conf := &testConfig{} + conf := &config.Testing{} autojumpPath := p.Join(td, "autojump.txt") zPath := p.Join(td, "z") @@ -29,7 +30,7 @@ func Test_multiImporter(t *testing.T) { } func Test_multiImporter_oneErrored(t *testing.T) { - conf := &testConfig{} + conf := &config.Testing{} autojumpPath := p.Join(td, "autojump.txt") imp := multiImporter{ diff --git a/importer/z_test.go b/importer/z_test.go index 277bad8..11152a3 100644 --- a/importer/z_test.go +++ b/importer/z_test.go @@ -6,10 +6,11 @@ import ( "time" "github.com/gsamokovarov/assert" + "github.com/gsamokovarov/jump/config" ) func TestZ(t *testing.T) { - conf := &testConfig{} + conf := &config.Testing{} configPath := p.Join(td, "z") imp := Z(conf, configPath) From 06be925d97729de64ec673055fccf835046aa874 Mon Sep 17 00:00:00 2001 From: Genadi Samokovarov Date: Fri, 7 Sep 2018 16:13:45 +0300 Subject: [PATCH 09/11] Test the jump import command --- cmd/import_test.go | 79 +++++++++++++++++++ .../.local/share/autojump/autojump.txt | 6 ++ cmd/testdata/.z | 4 + 3 files changed, 89 insertions(+) create mode 100644 cmd/import_test.go create mode 100644 cmd/testdata/.local/share/autojump/autojump.txt create mode 100644 cmd/testdata/.z diff --git a/cmd/import_test.go b/cmd/import_test.go new file mode 100644 index 0000000..d7964a6 --- /dev/null +++ b/cmd/import_test.go @@ -0,0 +1,79 @@ +package cmd + +import ( + "os" + "testing" + + "github.com/gsamokovarov/assert" + "github.com/gsamokovarov/jump/cli" + "github.com/gsamokovarov/jump/config" +) + +func Test_importCmd_autojump(t *testing.T) { + oldHOME := os.Getenv("HOME") + defer os.Setenv("HOME", oldHOME) + + os.Setenv("HOME", td) + + conf := &config.Testing{} + + output := capture(&os.Stdout, func() { + assert.Nil(t, importCmd(cli.Args{"autojump"}, conf)) + }) + + assert.Equal(t, `Importing /Users/genadi/Development/jump +Importing /Users/genadi/Development/mock_last_status +Importing /Users/genadi/Development +Importing /Users/genadi/.go/src/github.com/gsamokovarov/jump +Importing /usr/local/Cellar/autojump +Importing /Users/genadi/Development/gloat +`, output) + + assert.Len(t, 6, conf.Entries) +} + +func Test_importCmd_z(t *testing.T) { + oldHOME := os.Getenv("HOME") + defer os.Setenv("HOME", oldHOME) + + os.Setenv("HOME", td) + + conf := &config.Testing{} + + output := capture(&os.Stdout, func() { + assert.Nil(t, importCmd(cli.Args{"z"}, conf)) + }) + + assert.Equal(t, `Importing /Users/genadi/Development/hack +Importing /Users/genadi/Development/masse +Importing /Users/genadi/Development +Importing /Users/genadi/.go/src/github.com/gsamokovarov/jump +`, output) + + assert.Len(t, 4, conf.Entries) +} + +func Test_importCmd_itALL(t *testing.T) { + oldHOME := os.Getenv("HOME") + defer os.Setenv("HOME", oldHOME) + + os.Setenv("HOME", td) + + conf := &config.Testing{} + + output := capture(&os.Stdout, func() { + assert.Nil(t, importCmd(cli.Args{""}, conf)) + }) + + assert.Equal(t, `Importing /Users/genadi/Development/hack +Importing /Users/genadi/Development/masse +Importing /Users/genadi/Development +Importing /Users/genadi/.go/src/github.com/gsamokovarov/jump +Importing /Users/genadi/Development/jump +Importing /Users/genadi/Development/mock_last_status +Importing /usr/local/Cellar/autojump +Importing /Users/genadi/Development/gloat +`, output) + + assert.Len(t, 8, conf.Entries) +} diff --git a/cmd/testdata/.local/share/autojump/autojump.txt b/cmd/testdata/.local/share/autojump/autojump.txt new file mode 100644 index 0000000..1d4e20c --- /dev/null +++ b/cmd/testdata/.local/share/autojump/autojump.txt @@ -0,0 +1,6 @@ +38.729833462 /Users/genadi/Development/jump +14.1421356237 /Users/genadi/Development/mock_last_status +33.1662479035 /Users/genadi/Development +14.1421356237 /Users/genadi/.go/src/github.com/gsamokovarov/jump +43.5889894353 /usr/local/Cellar/autojump +20.0 /Users/genadi/Development/gloat diff --git a/cmd/testdata/.z b/cmd/testdata/.z new file mode 100644 index 0000000..88ffd8f --- /dev/null +++ b/cmd/testdata/.z @@ -0,0 +1,4 @@ +/Users/genadi/Development/hack|11|1536272816 +/Users/genadi/Development/masse|1|1536272502 +/Users/genadi/Development|3|1536272506 +/Users/genadi/.go/src/github.com/gsamokovarov/jump|1|1536272492 From 1009f6d44781e056d94b3ae834dd6d374a2e694b Mon Sep 17 00:00:00 2001 From: Genadi Samokovarov Date: Fri, 7 Sep 2018 17:01:36 +0300 Subject: [PATCH 10/11] Give more explanatory errors when not import can happen --- importer/importer.go | 6 +++--- importer/multi.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/importer/importer.go b/importer/importer.go index d9f33f0..96ffbc5 100644 --- a/importer/importer.go +++ b/importer/importer.go @@ -9,8 +9,8 @@ import ( "github.com/gsamokovarov/jump/scoring" ) -// UnsupportedErr is an error returned when the importing tool is not found. -var UnsupportedErr = errors.New("importer: unsupported") +// NotFoundErr is an error returned when the importing tool is not found. +var NotFoundErr = errors.New("importer: cannot find autojump or z data files") // Callback is called on every import. type Callback func(*scoring.Entry) @@ -67,5 +67,5 @@ func findPath(paths []string) (string, error) { } } - return "", UnsupportedErr + return "", NotFoundErr } diff --git a/importer/multi.go b/importer/multi.go index 263bd51..a6d1fc7 100644 --- a/importer/multi.go +++ b/importer/multi.go @@ -10,7 +10,7 @@ func (mi multiImporter) Import(fn Callback) error { for _, i := range mi { err := i.Import(fn) - if err == UnsupportedErr { + if err == NotFoundErr { continue } if err != nil { From 35383a08518c858db98c4244053dbcea9b56f653 Mon Sep 17 00:00:00 2001 From: Genadi Samokovarov Date: Fri, 7 Sep 2018 23:20:59 +0300 Subject: [PATCH 11/11] Add a README entry for the import functionality --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index f998a82..9426e9d 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,26 @@ To trigger a case-sensitive search, use a term that has a capital letter. The jump will resolve to `/Users/genadi/Development` even if there is `/Users/genadi/Development/dev-tools` that scores better. +## Is it like autojump or z? + +Yes, it is! You can import your datafile from `autojump` or `z` with: + +```bash +$ jump import +``` + +This will try `z` first then `autojump`, so you can even combine all the +entries from both tools. + +The command is safe to run on pre-existing jump database, because if an entry +exist in jump already, it won't be imported and it's score will remain +unchanged. You can be explicit and choose to import `autojump` or `z` with: + +```bash +$ jump import autojump +$ jump import z +``` + ## Installation Jump comes in packages for macOS through homebrew and linux.