fuzz 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +19 -1
- data/README.md +76 -20
- data/fuzz.gemspec +2 -1
- data/lib/fuzz.rb +3 -0
- data/lib/fuzz/cache.rb +39 -37
- data/lib/fuzz/dmenu_picker.rb +9 -0
- data/lib/fuzz/entry.rb +17 -13
- data/lib/fuzz/executable.rb +28 -0
- data/lib/fuzz/null_cache.rb +11 -9
- data/lib/fuzz/pick_picker.rb +9 -0
- data/lib/fuzz/rofi_picker.rb +9 -20
- data/lib/fuzz/selector.rb +39 -29
- data/lib/fuzz/version.rb +1 -1
- metadata +20 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9d42afd0f155728e3950089a89cf53ea30567cd151f40726a3e554e0a734daa
|
4
|
+
data.tar.gz: 0f384622fe5d6c66e6ac7c09b2221a972a4c7155ed51513c2fde9fab9266cf50
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2e25ce9074901620fdeeb50a4aaaa90ec0b39d7c7ba0af27797d186e6fe9f63f8a52772ff81a59d13b9c87bfd8cc16912308ae1888d27a8936d354e1ae610039
|
7
|
+
data.tar.gz: 23c5622897c83d6dbb517adaa6dae68004b29761403b9339a7633cffdee55ff7addb2af0ee31099f7e7c322f7a807fa9e563a05be3c0f3536311b1c3e9f77bd9
|
data/Gemfile.lock
CHANGED
@@ -1,12 +1,20 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
fuzz (0.1.
|
4
|
+
fuzz (0.1.2)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
+
coveralls (0.8.21)
|
10
|
+
json (>= 1.8, < 3)
|
11
|
+
simplecov (~> 0.14.1)
|
12
|
+
term-ansicolor (~> 1.3)
|
13
|
+
thor (~> 0.19.4)
|
14
|
+
tins (~> 1.6)
|
9
15
|
diff-lcs (1.3)
|
16
|
+
docile (1.1.5)
|
17
|
+
json (2.1.0)
|
10
18
|
rake (10.5.0)
|
11
19
|
rspec (3.7.0)
|
12
20
|
rspec-core (~> 3.7.0)
|
@@ -21,12 +29,22 @@ GEM
|
|
21
29
|
diff-lcs (>= 1.2.0, < 2.0)
|
22
30
|
rspec-support (~> 3.7.0)
|
23
31
|
rspec-support (3.7.1)
|
32
|
+
simplecov (0.14.1)
|
33
|
+
docile (~> 1.1.0)
|
34
|
+
json (>= 1.8, < 3)
|
35
|
+
simplecov-html (~> 0.10.0)
|
36
|
+
simplecov-html (0.10.2)
|
37
|
+
term-ansicolor (1.6.0)
|
38
|
+
tins (~> 1.0)
|
39
|
+
thor (0.19.4)
|
40
|
+
tins (1.16.3)
|
24
41
|
|
25
42
|
PLATFORMS
|
26
43
|
ruby
|
27
44
|
|
28
45
|
DEPENDENCIES
|
29
46
|
bundler (~> 1.16)
|
47
|
+
coveralls
|
30
48
|
fuzz!
|
31
49
|
rake (~> 10.0)
|
32
50
|
rspec (~> 3.0)
|
data/README.md
CHANGED
@@ -1,19 +1,28 @@
|
|
1
|
-
## Fuzz: interactively select Ruby objects with `rofi`
|
1
|
+
## Fuzz: interactively select Ruby objects with `rofi`, `dmenu`, or `pick`
|
2
|
+
|
3
|
+
[](https://travis-ci.org/hrs/fuzz)
|
4
|
+
[](https://codeclimate.com/github/hrs/fuzz/maintainability)
|
5
|
+
[](https://coveralls.io/github/hrs/fuzz?branch=master)
|
6
|
+
[](http://www.gnu.org/licenses/gpl-3.0)
|
2
7
|
|
3
8
|
I often write scripts in which a user needs to choose one of a number of
|
4
9
|
possibilities: maybe they need to select a directory, or a file, or an action,
|
5
10
|
or just an arbitrary object.
|
6
11
|
|
7
|
-
[rofi][]
|
8
|
-
use fuzzy searching to choose among a selection of strings.
|
12
|
+
[rofi][] and [dmenu][] are really cool tools for that! They provide a visual way
|
13
|
+
for a user to use fuzzy searching to choose among a selection of strings.
|
14
|
+
[pick][] provides a similar service on the command line with ncurses.
|
9
15
|
|
10
|
-
Unfortunately, though,
|
11
|
-
would often be a lot simpler if the user were able to select arbitrary
|
12
|
-
objects. Fuzz manages the translation between lists of strings that can be
|
13
|
-
selected through
|
14
|
-
makes it a bit easier to write interactive and OOP-friendly Ruby
|
16
|
+
Unfortunately, though, these tools do *just* choose between strings. But my
|
17
|
+
scripts would often be a lot simpler if the user were able to select arbitrary
|
18
|
+
Ruby objects. Fuzz manages the translation between lists of strings that can be
|
19
|
+
selected through these visual pickers and the associated collection of Ruby
|
20
|
+
objects, which makes it a bit easier to write interactive and OOP-friendly Ruby
|
21
|
+
scripts.
|
15
22
|
|
16
23
|
[rofi]: https://github.com/DaveDavenport/rofi
|
24
|
+
[dmenu]: https://tools.suckless.org/dmenu
|
25
|
+
[pick]: https://github.com/calleerlandsson/pick
|
17
26
|
|
18
27
|
### For example
|
19
28
|
|
@@ -35,14 +44,14 @@ system("vlc \"#{ choice.path }\"")
|
|
35
44
|
```
|
36
45
|
|
37
46
|
The call to `#pick` will call `#to_s` on every episode, display the results
|
38
|
-
through `rofi
|
39
|
-
object.
|
47
|
+
through `rofi` (the default picker), get the user's choice, and use that to
|
48
|
+
return the corresponding object.
|
40
49
|
|
41
50
|
[RubyTapas screencasts]: https://www.rubytapas.com/
|
42
51
|
|
43
52
|
### Caching selections
|
44
53
|
|
45
|
-
If you run your script frequently
|
54
|
+
If you run your script frequently you may find that you often make the same
|
46
55
|
selections. It's convenient to have those selections appear near the top of the
|
47
56
|
list.
|
48
57
|
|
@@ -66,11 +75,62 @@ and it complies with the [XDG Base Directory Specification][], but you can keep
|
|
66
75
|
|
67
76
|
[XDG Base Directory Specification]: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
68
77
|
|
69
|
-
###
|
78
|
+
### Supplying a default value
|
79
|
+
|
80
|
+
If the user enters a string that doesn't correspond to an object,
|
81
|
+
`Fuzz::Selector#pick` will normally return `nil`.
|
82
|
+
|
83
|
+
However, you can manually specify a default return value with the `default:`
|
84
|
+
option:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
Fuzz::Selector.new(
|
88
|
+
[1, 2, 3],
|
89
|
+
default: 42,
|
90
|
+
).pick # => returns 42 if the user picks a value other than 1, 2, or 3
|
91
|
+
```
|
92
|
+
|
93
|
+
### Choosing a different picker
|
70
94
|
|
71
|
-
|
72
|
-
takes an optional
|
73
|
-
|
95
|
+
Fuzz ships with support for `rofi` and `dmenu`. The `Fuzz::Selector` constructor
|
96
|
+
takes an optional `picker:` argument to pick a picker.
|
97
|
+
|
98
|
+
For example, to use `dmenu` as your picker:
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
Fuzz::Selector.new(
|
102
|
+
some_objects,
|
103
|
+
picker: Fuzz::DmenuPicker.new,
|
104
|
+
)
|
105
|
+
```
|
106
|
+
|
107
|
+
Or to search in a terminal with the `pick` tool:
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
|
111
|
+
Fuzz::Selector.new(
|
112
|
+
some_objects,
|
113
|
+
picker: Fuzz::PickPicker.new,
|
114
|
+
)
|
115
|
+
```
|
116
|
+
|
117
|
+
The `rofi` picker is the default, but you can also explicitly specify it:
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
Fuzz::Selector.new(
|
121
|
+
some_objects,
|
122
|
+
picker: Fuzz::RofiPicker.new,
|
123
|
+
)
|
124
|
+
```
|
125
|
+
|
126
|
+
If your chosen picker can't be found (perhaps it's not installed, or not in your
|
127
|
+
`$PATH`), `Fuzz::Selector#pick` will raise a `Fuzz::MissingExecutableError`.
|
128
|
+
|
129
|
+
### Extending `fuzz` with new pickers
|
130
|
+
|
131
|
+
It's possible to extend `fuzz` to use a picker of your choice. The object
|
132
|
+
supplied to `picker:` must implement a `#pick` method, which should take an
|
133
|
+
array of strings and return a string.
|
74
134
|
|
75
135
|
Here's a simple example with a silly picker that always chooses the first
|
76
136
|
option:
|
@@ -90,11 +150,7 @@ selector = Fuzz::Selector.new(
|
|
90
150
|
selector.pick # => 1
|
91
151
|
```
|
92
152
|
|
93
|
-
|
94
|
-
to [dmenu][], [pick][], or whatever else you'd like.
|
95
|
-
|
96
|
-
[dmenu]: https://wiki.archlinux.org/index.php/Dmenu
|
97
|
-
[pick]: https://github.com/calleerlandsson/pick
|
153
|
+
Your custom pickers will probably be more interactive than this! =)
|
98
154
|
|
99
155
|
## Installation
|
100
156
|
|
data/fuzz.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |spec|
|
|
7
7
|
spec.version = Fuzz::VERSION
|
8
8
|
spec.authors = ["Harry Schwartz"]
|
9
9
|
spec.email = ["hello@harryrschwartz.com"]
|
10
|
-
spec.summary = "
|
10
|
+
spec.summary = "Use command-line tools to choose from a list of Ruby objects!"
|
11
11
|
spec.homepage = "https://github.com/hrs/fuzz"
|
12
12
|
spec.license = "GPL-3.0"
|
13
13
|
|
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
22
|
spec.add_development_dependency "bundler", "~> 1.16"
|
23
|
+
spec.add_development_dependency "coveralls"
|
23
24
|
spec.add_development_dependency "rake", "~> 10.0"
|
24
25
|
spec.add_development_dependency "rspec", "~> 3.0"
|
25
26
|
end
|
data/lib/fuzz.rb
CHANGED
data/lib/fuzz/cache.rb
CHANGED
@@ -1,53 +1,55 @@
|
|
1
1
|
require "fileutils"
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
module Fuzz
|
4
|
+
class Cache
|
5
|
+
def initialize(cache_file)
|
6
|
+
@cache_file = File.expand_path(cache_file)
|
7
|
+
@entries = cache_entries(@cache_file)
|
8
|
+
end
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
def weight(title)
|
11
|
+
entries.fetch(title, 0)
|
12
|
+
end
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
def increment(title)
|
15
|
+
entries[title] = weight(title) + 1
|
16
|
+
write
|
17
|
+
end
|
17
18
|
|
18
|
-
|
19
|
+
private
|
19
20
|
|
20
|
-
|
21
|
+
attr_reader :cache_file, :entries
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
def write
|
24
|
+
File.open(cache_file, "w") do |file|
|
25
|
+
entries.each do |title, count|
|
26
|
+
file.puts("#{ count } #{ title }")
|
27
|
+
end
|
26
28
|
end
|
27
29
|
end
|
28
|
-
end
|
29
30
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
def cache_entries(cache_file)
|
32
|
+
if File.exist?(cache_file)
|
33
|
+
hash_from_file(File.new(cache_file))
|
34
|
+
else
|
35
|
+
{}
|
36
|
+
end
|
35
37
|
end
|
36
|
-
end
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
39
|
+
def hash_from_file(cache_file)
|
40
|
+
cache_file.readlines.inject({}) { |hash, line|
|
41
|
+
count, title = line.split(" ", 2)
|
42
|
+
hash[title.strip] = count.to_i
|
43
|
+
hash
|
44
|
+
}
|
45
|
+
end
|
45
46
|
|
46
|
-
|
47
|
-
|
48
|
-
|
47
|
+
def directory
|
48
|
+
File.dirname(cache_file)
|
49
|
+
end
|
49
50
|
|
50
|
-
|
51
|
-
|
51
|
+
def ensure_directory_exists
|
52
|
+
FileUtils.mkdir_p(directory)
|
53
|
+
end
|
52
54
|
end
|
53
55
|
end
|
data/lib/fuzz/entry.rb
CHANGED
@@ -1,19 +1,23 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
module Fuzz
|
2
|
+
class Entry
|
3
|
+
include Comparable
|
3
4
|
|
4
|
-
|
5
|
+
attr_reader :title, :object, :weight
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
def initialize(title:, object:, weight:)
|
8
|
+
@title = title
|
9
|
+
@object = object
|
10
|
+
@weight = weight
|
11
|
+
end
|
12
|
+
|
13
|
+
def <=>(other)
|
14
|
+
other_weight = other.weight
|
11
15
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
16
|
+
if weight != other_weight
|
17
|
+
other_weight <=> weight
|
18
|
+
else
|
19
|
+
title <=> other.title
|
20
|
+
end
|
17
21
|
end
|
18
22
|
end
|
19
23
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Fuzz
|
2
|
+
class MissingExecutableError < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
class Executable
|
6
|
+
def initialize(command)
|
7
|
+
@command = command
|
8
|
+
end
|
9
|
+
|
10
|
+
def error_if_missing
|
11
|
+
if !installed?
|
12
|
+
raise(
|
13
|
+
MissingExecutableError,
|
14
|
+
"Can't find the `#{ command }` executable!",
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :command
|
22
|
+
|
23
|
+
def installed?
|
24
|
+
system("which #{ command }")
|
25
|
+
$?.success?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/fuzz/null_cache.rb
CHANGED
@@ -1,14 +1,16 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
module Fuzz
|
2
|
+
class NullCache
|
3
|
+
def initialize(*)
|
4
|
+
end
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
6
|
+
def weight(*)
|
7
|
+
0
|
8
|
+
end
|
8
9
|
|
9
|
-
|
10
|
-
|
10
|
+
def increment(*)
|
11
|
+
end
|
11
12
|
|
12
|
-
|
13
|
+
def write
|
14
|
+
end
|
13
15
|
end
|
14
16
|
end
|
data/lib/fuzz/rofi_picker.rb
CHANGED
@@ -1,26 +1,15 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
module Fuzz
|
2
|
+
class RofiPicker
|
3
|
+
def pick(keys)
|
4
|
+
Fuzz::Executable.new("rofi").error_if_missing
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
end
|
6
|
+
`echo "#{ keys.join("\n") }" | #{ command }`.strip
|
7
|
+
end
|
9
8
|
|
10
|
-
|
9
|
+
private
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
end
|
15
|
-
|
16
|
-
def assert_executable_available
|
17
|
-
if !installed?
|
18
|
-
raise "Can't find the `rofi` executable!"
|
11
|
+
def command
|
12
|
+
"rofi -show run -matching fuzzy -dmenu -i"
|
19
13
|
end
|
20
14
|
end
|
21
|
-
|
22
|
-
def installed?
|
23
|
-
`which rofi`
|
24
|
-
$?.success?
|
25
|
-
end
|
26
15
|
end
|
data/lib/fuzz/selector.rb
CHANGED
@@ -3,42 +3,52 @@ require_relative "entry"
|
|
3
3
|
require_relative "rofi_picker"
|
4
4
|
require_relative "null_cache"
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
6
|
+
module Fuzz
|
7
|
+
class Selector
|
8
|
+
def initialize(items, options = {})
|
9
|
+
@cache = options.fetch(:cache, Fuzz::NullCache.new)
|
10
|
+
@default = options.fetch(:default, nil)
|
11
|
+
@picker = options.fetch(:picker, Fuzz::RofiPicker.new)
|
12
|
+
@entries = items.map { |item| make_entry(item, @cache) }
|
13
|
+
end
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
def pick
|
16
|
+
title = picker.pick(titles)
|
17
|
+
chosen_entry = find_entry_by_title(title)
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
if chosen_entry.nil?
|
20
|
+
default
|
21
|
+
else
|
22
|
+
cache.increment(chosen_entry.title)
|
23
|
+
chosen_entry.object
|
24
|
+
end
|
22
25
|
end
|
23
|
-
end
|
24
26
|
|
25
|
-
|
27
|
+
private
|
26
28
|
|
27
|
-
|
29
|
+
attr_reader(
|
30
|
+
:cache,
|
31
|
+
:default,
|
32
|
+
:entries,
|
33
|
+
:picker,
|
34
|
+
)
|
28
35
|
|
29
|
-
|
30
|
-
|
31
|
-
|
36
|
+
def titles
|
37
|
+
entries.sort.map(&:title)
|
38
|
+
end
|
32
39
|
|
33
|
-
|
34
|
-
|
35
|
-
|
40
|
+
def find_entry_by_title(title)
|
41
|
+
entries.detect { |entry| entry.title == title }
|
42
|
+
end
|
36
43
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
44
|
+
def make_entry(item, cache)
|
45
|
+
title = item.to_s
|
46
|
+
|
47
|
+
Fuzz::Entry.new(
|
48
|
+
title: title,
|
49
|
+
object: item,
|
50
|
+
weight: cache.weight(title) || 0,
|
51
|
+
)
|
52
|
+
end
|
43
53
|
end
|
44
54
|
end
|
data/lib/fuzz/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fuzz
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Harry Schwartz
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-03-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.16'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: coveralls
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rake
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -73,8 +87,11 @@ files:
|
|
73
87
|
- fuzz.gemspec
|
74
88
|
- lib/fuzz.rb
|
75
89
|
- lib/fuzz/cache.rb
|
90
|
+
- lib/fuzz/dmenu_picker.rb
|
76
91
|
- lib/fuzz/entry.rb
|
92
|
+
- lib/fuzz/executable.rb
|
77
93
|
- lib/fuzz/null_cache.rb
|
94
|
+
- lib/fuzz/pick_picker.rb
|
78
95
|
- lib/fuzz/rofi_picker.rb
|
79
96
|
- lib/fuzz/selector.rb
|
80
97
|
- lib/fuzz/version.rb
|
@@ -101,5 +118,5 @@ rubyforge_project:
|
|
101
118
|
rubygems_version: 2.7.3
|
102
119
|
signing_key:
|
103
120
|
specification_version: 4
|
104
|
-
summary:
|
121
|
+
summary: Use command-line tools to choose from a list of Ruby objects!
|
105
122
|
test_files: []
|