snibbets 2.0.7 → 2.0.9
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/CHANGELOG.md +26 -2
- data/Gemfile.lock +7 -1
- data/README.md +3 -0
- data/bin/snibbets +0 -284
- data/lib/snibbets/array.rb +39 -0
- data/lib/snibbets/config.rb +9 -0
- data/lib/snibbets/menu.rb +13 -1
- data/lib/snibbets/string.rb +8 -2
- data/lib/snibbets/version.rb +1 -1
- data/lib/snibbets.rb +285 -0
- data/snibbets.gemspec +1 -0
- metadata +16 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e721debb0d3122f59722537839a728cc81e532c8039edeca696b46f19c91d920
|
|
4
|
+
data.tar.gz: ab7261e014473773db55aa1e56f392ba11dc2b423c2d281216d18b57bf51378e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9f991baaab2bbe857bb1f2f0edb924ebc241faa58ffb20dec1084283a54d5e7c0163e1aac0368cb051b6b8bc3fe90d7db18517ff48176956e98b6889d7c918bb
|
|
7
|
+
data.tar.gz: 01175aaaa465ae16bafff11a8a1d891b188c288042073f667f34a597e28bce105fd3e73e2a56683ed3780c12f7520d525694a1075af728f609e8a28803aad614
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
|
-
### 2.0.
|
|
1
|
+
### 2.0.9
|
|
2
2
|
|
|
3
|
-
2023-04-15
|
|
3
|
+
2023-04-15 15:44
|
|
4
|
+
|
|
5
|
+
#### IMPROVED
|
|
6
|
+
|
|
7
|
+
- Allow setting `menus` config key to force Snibbets to use fzf, gum, or console menus
|
|
8
|
+
- Allow setting `menus` config key to force Snibbets to use fzf, gum, or console menus
|
|
9
|
+
|
|
10
|
+
#### FIXED
|
|
11
|
+
|
|
12
|
+
- If a header section contains no code blocks, don't display it in menu
|
|
13
|
+
- Remove leading and trailing newlines without affecting indentation
|
|
14
|
+
|
|
15
|
+
### 2.0.8
|
|
16
|
+
|
|
17
|
+
2023-04-15 15:41
|
|
18
|
+
|
|
19
|
+
#### IMPROVED
|
|
20
|
+
|
|
21
|
+
- Allow setting `menus` config key to force Snibbets to use fzf, gum, or console menus
|
|
22
|
+
- Allow setting `menus` config key to force Snibbets to use fzf, gum, or console menus
|
|
23
|
+
|
|
24
|
+
#### FIXED
|
|
25
|
+
|
|
26
|
+
- If a header section contains no code blocks, don't display it in menu
|
|
27
|
+
- Remove leading and trailing newlines without affecting indentation
|
|
4
28
|
|
|
5
29
|
### 2.0.6
|
|
6
30
|
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
snibbets (2.0.
|
|
4
|
+
snibbets (2.0.7)
|
|
5
5
|
tty-which (~> 0.5, >= 0.5.0)
|
|
6
6
|
|
|
7
7
|
GEM
|
|
@@ -9,6 +9,7 @@ GEM
|
|
|
9
9
|
specs:
|
|
10
10
|
ansi (1.5.0)
|
|
11
11
|
ast (2.4.2)
|
|
12
|
+
awesome_print (1.9.2)
|
|
12
13
|
diff-lcs (1.5.0)
|
|
13
14
|
docile (1.4.0)
|
|
14
15
|
gem-release (2.2.2)
|
|
@@ -76,11 +77,15 @@ GEM
|
|
|
76
77
|
thor (1.2.1)
|
|
77
78
|
tty-which (0.5.0)
|
|
78
79
|
unicode-display_width (2.4.2)
|
|
80
|
+
webrick (1.7.0)
|
|
81
|
+
yard (0.9.28)
|
|
82
|
+
webrick (~> 1.7.0)
|
|
79
83
|
|
|
80
84
|
PLATFORMS
|
|
81
85
|
arm64-darwin-20
|
|
82
86
|
|
|
83
87
|
DEPENDENCIES
|
|
88
|
+
awesome_print (~> 1.9)
|
|
84
89
|
bundler (~> 2.0)
|
|
85
90
|
gem-release (~> 2.2)
|
|
86
91
|
parse_gemspec-cli (~> 1.0)
|
|
@@ -91,6 +96,7 @@ DEPENDENCIES
|
|
|
91
96
|
simplecov-console (~> 0.9)
|
|
92
97
|
snibbets!
|
|
93
98
|
standard (~> 1.3)
|
|
99
|
+
yard (~> 0.9, >= 0.9.26)
|
|
94
100
|
|
|
95
101
|
BUNDLED WITH
|
|
96
102
|
2.2.29
|
data/README.md
CHANGED
|
@@ -88,6 +88,7 @@ extension: md
|
|
|
88
88
|
highlight: false
|
|
89
89
|
interactive: true
|
|
90
90
|
launchbar: false
|
|
91
|
+
menus:
|
|
91
92
|
name_only: false
|
|
92
93
|
output: raw
|
|
93
94
|
source: "~/Dropbox/Snippets"
|
|
@@ -111,6 +112,8 @@ The `highlight` key turns on syntax highlighting. This requires that either `pyg
|
|
|
111
112
|
|
|
112
113
|
The `interactive` setting determines whether menus will be displayed. This should generally be true, but if you want silent operation that just displays the best match automatically, set it to false.
|
|
113
114
|
|
|
115
|
+
The `menus` setting will determine what method is used for displaying interactive menus. If this is not set, it will be automatically determined in the order of `fzf`, `gum`, and `console`. You can manually choose to use one of these options over another by making it the `menus` setting.
|
|
116
|
+
|
|
114
117
|
The `name_only` key will permanently set Snibbets to only search for snippets by their filename rather than examining their contents. You can enable this at runtime using `--name-only` in the command.
|
|
115
118
|
|
|
116
119
|
### Usage
|
data/bin/snibbets
CHANGED
|
@@ -4,290 +4,6 @@
|
|
|
4
4
|
$LOAD_PATH.unshift File.join(__dir__, '..', 'lib')
|
|
5
5
|
require 'snibbets'
|
|
6
6
|
|
|
7
|
-
module Snibbets
|
|
8
|
-
class << self
|
|
9
|
-
# Search the snippets directory for query using find and grep
|
|
10
|
-
def search(try: 0)
|
|
11
|
-
folder = Snibbets.options[:source]
|
|
12
|
-
# start by doing a spotlight search, if that fails, start trying:
|
|
13
|
-
# First try only search by filenames
|
|
14
|
-
# Second try search with grep
|
|
15
|
-
ext = Snibbets.options[:extension] || 'md'
|
|
16
|
-
cmd = case try
|
|
17
|
-
when 1
|
|
18
|
-
%(find "#{folder}" -iregex '#{@query.rx}' -name '*.#{ext}')
|
|
19
|
-
when 2
|
|
20
|
-
rg = TTY::Which.which('rg')
|
|
21
|
-
ag = TTY::Which.which('ag')
|
|
22
|
-
ack = TTY::Which.which('ack')
|
|
23
|
-
grep = TTY::Which.which('grep')
|
|
24
|
-
if Snibbets.options[:name_only]
|
|
25
|
-
nil
|
|
26
|
-
elsif !rg.empty?
|
|
27
|
-
%(#{rg} -li --color=never --glob='*.#{ext}' '#{@query.rx}' "#{folder}")
|
|
28
|
-
elsif !ag.empty?
|
|
29
|
-
%(#{ag} -li --nocolor -G '.*.#{ext}' '#{@query.rx}' "#{folder}")
|
|
30
|
-
elsif !ack.empty?
|
|
31
|
-
%(#{ack} -li --nocolor --markdown '#{@query.rx}' "#{folder}")
|
|
32
|
-
elsif !grep.empty?
|
|
33
|
-
%(#{grep} -iEl '#{@query.rx}' "#{folder}"/**/*.#{ext})
|
|
34
|
-
else
|
|
35
|
-
nil
|
|
36
|
-
end
|
|
37
|
-
else
|
|
38
|
-
mdfind = TTY::Which.which('mdfind')
|
|
39
|
-
if mdfind.empty?
|
|
40
|
-
nil
|
|
41
|
-
else
|
|
42
|
-
name_only = Snibbets.options[:name_only] ? '-name ' : ''
|
|
43
|
-
%(mdfind -onlyin #{folder} #{name_only}'#{@query} filename:.#{ext}' 2>/dev/null)
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
if try == 2 && cmd.nil?
|
|
48
|
-
puts "No search method available on this system. Please install ripgrep, silver surfer, ack, or grep."
|
|
49
|
-
Process.exit 1
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
res = cmd.nil? ? '' : `#{cmd}`.strip
|
|
53
|
-
|
|
54
|
-
matches = []
|
|
55
|
-
|
|
56
|
-
unless res.empty?
|
|
57
|
-
lines = res.split(/\n/)
|
|
58
|
-
lines.each do |l|
|
|
59
|
-
matches << {
|
|
60
|
-
'title' => File.basename(l, '.*'),
|
|
61
|
-
'path' => l
|
|
62
|
-
}
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
matches.sort_by! { |a| a['title'] }.uniq!
|
|
66
|
-
|
|
67
|
-
return matches unless matches.empty?
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
return matches if try == 2
|
|
71
|
-
|
|
72
|
-
# if no results on the first try, try again searching all text
|
|
73
|
-
search(try: try + 1) if matches.empty?
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def open_snippet_in_editor(filepath)
|
|
77
|
-
editor = Snibbets.options[:editor] || Snibbets::Config.best_editor
|
|
78
|
-
|
|
79
|
-
os = RbConfig::CONFIG['target_os']
|
|
80
|
-
|
|
81
|
-
if editor.nil?
|
|
82
|
-
OS.open(filepath)
|
|
83
|
-
else
|
|
84
|
-
if os =~ /darwin.*/i
|
|
85
|
-
if editor =~ /^TextEdit/
|
|
86
|
-
`open -a TextEdit "#{filepath}"`
|
|
87
|
-
elsif TTY::Which.bundle_id?(editor)
|
|
88
|
-
`open -b "#{editor}" "#{filepath}"`
|
|
89
|
-
elsif TTY::Which.app?(editor)
|
|
90
|
-
`open -a "#{editor}" "#{filepath}"`
|
|
91
|
-
elsif TTY::Which.exist?(editor)
|
|
92
|
-
editor = TTY::Which.which(editor)
|
|
93
|
-
system %(#{editor} "#{filepath}") if editor
|
|
94
|
-
else
|
|
95
|
-
puts "No editor configured, or editor is missing"
|
|
96
|
-
Process.exit 1
|
|
97
|
-
end
|
|
98
|
-
elsif TTY::Which.exist?(editor)
|
|
99
|
-
editor = TTY::Which.which(editor)
|
|
100
|
-
system %(#{editor} "#{filepath}") if editor
|
|
101
|
-
else
|
|
102
|
-
puts "No editor configured, or editor is missing"
|
|
103
|
-
Process.exit 1
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def new_snippet_from_clipboard
|
|
109
|
-
trap('SIGINT') do
|
|
110
|
-
Howzit.console.info "\nCancelled"
|
|
111
|
-
exit!
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
build_lexers
|
|
115
|
-
|
|
116
|
-
pb = OS.paste.outdent
|
|
117
|
-
|
|
118
|
-
printf 'What does this snippet do? '
|
|
119
|
-
input = $stdin.gets.chomp
|
|
120
|
-
title = input unless input.empty?
|
|
121
|
-
|
|
122
|
-
printf 'What language(s) does it use (separate with spaces, full names or file extensions will work)? '
|
|
123
|
-
input = $stdin.gets.chomp
|
|
124
|
-
langs = input.split(/ +/).map(&:strip) unless input.empty?
|
|
125
|
-
exts = langs.map { |lang| Snibbets::Lexers.lang_to_ext(lang) }
|
|
126
|
-
tags = langs.map { |lang| Snibbets::Lexers.ext_to_lang(lang) }.concat(langs).sort.uniq
|
|
127
|
-
|
|
128
|
-
filename ="#{title}.#{exts.join('.')}.#{Snibbets.options[:extension]}"
|
|
129
|
-
|
|
130
|
-
File.open(File.join(Snibbets.options[:source], filename), 'w') do |f|
|
|
131
|
-
f.puts "tags: #{tags.join(', ')}
|
|
132
|
-
|
|
133
|
-
```
|
|
134
|
-
#{pb}
|
|
135
|
-
```"
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
puts "New snippet written to #{filename}."
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def handle_launchbar(results)
|
|
142
|
-
output = []
|
|
143
|
-
|
|
144
|
-
if results.empty?
|
|
145
|
-
out = {
|
|
146
|
-
'title' => 'No matching snippets found'
|
|
147
|
-
}.to_json
|
|
148
|
-
puts out
|
|
149
|
-
Process.exit
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
results.each do |result|
|
|
153
|
-
input = IO.read(result['path'])
|
|
154
|
-
snippets = input.snippets
|
|
155
|
-
next if snippets.empty?
|
|
156
|
-
|
|
157
|
-
children = []
|
|
158
|
-
|
|
159
|
-
if snippets.length == 1
|
|
160
|
-
output << {
|
|
161
|
-
'title' => result['title'],
|
|
162
|
-
'path' => result['path'],
|
|
163
|
-
'action' => 'copyIt',
|
|
164
|
-
'actionArgument' => snippets[0]['code'],
|
|
165
|
-
'label' => 'Copy'
|
|
166
|
-
}
|
|
167
|
-
next
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
snippets.each { |s|
|
|
171
|
-
children << {
|
|
172
|
-
'title' => s['title'],
|
|
173
|
-
'path' => result['path'],
|
|
174
|
-
'action' => 'copyIt',
|
|
175
|
-
'actionArgument' => s['code'],
|
|
176
|
-
'label' => 'Copy'
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
output << {
|
|
181
|
-
'title' => result['title'],
|
|
182
|
-
'path' => result['path'],
|
|
183
|
-
'children' => children
|
|
184
|
-
}
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
puts output.to_json
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
def handle_results(results)
|
|
191
|
-
if Snibbets.options[:launchbar]
|
|
192
|
-
handle_launchbar(results)
|
|
193
|
-
else
|
|
194
|
-
filepath = nil
|
|
195
|
-
if results.empty?
|
|
196
|
-
warn 'No results'
|
|
197
|
-
Process.exit 0
|
|
198
|
-
elsif results.length == 1 || !Snibbets.options[:interactive]
|
|
199
|
-
filepath = results[0]['path']
|
|
200
|
-
input = IO.read(filepath)
|
|
201
|
-
else
|
|
202
|
-
answer = Snibbets::Menu.menu(results, title: 'Select a file')
|
|
203
|
-
filepath = answer['path']
|
|
204
|
-
input = IO.read(filepath)
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
if @arguments[:edit_snippet]
|
|
208
|
-
open_snippet_in_editor(filepath)
|
|
209
|
-
Process.exit 0
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
snippets = input.snippets
|
|
213
|
-
|
|
214
|
-
if snippets.empty?
|
|
215
|
-
warn 'No snippets found'
|
|
216
|
-
Process.exit 0
|
|
217
|
-
elsif snippets.length == 1 || !Snibbets.options[:interactive]
|
|
218
|
-
if Snibbets.options[:output] == 'json'
|
|
219
|
-
print(snippets.to_json)
|
|
220
|
-
else
|
|
221
|
-
snippets.each do |snip|
|
|
222
|
-
header = File.basename(filepath, '.md')
|
|
223
|
-
warn header
|
|
224
|
-
warn '-' * header.length
|
|
225
|
-
code = snip['code']
|
|
226
|
-
code = highlight(code, filepath) if Snibbets.options[:highlight]
|
|
227
|
-
print(code)
|
|
228
|
-
end
|
|
229
|
-
end
|
|
230
|
-
elsif snippets.length > 1
|
|
231
|
-
if Snibbets.options[:all]
|
|
232
|
-
if Snibbets.options[:output] == 'json'
|
|
233
|
-
print(snippets.to_json)
|
|
234
|
-
else
|
|
235
|
-
output = []
|
|
236
|
-
snippets.each do |snippet|
|
|
237
|
-
output << snippet['title']
|
|
238
|
-
output << '-' * snippet['title'].length
|
|
239
|
-
output << snippet['code']
|
|
240
|
-
output << "\n"
|
|
241
|
-
end
|
|
242
|
-
print(output.join("\n"))
|
|
243
|
-
end
|
|
244
|
-
else
|
|
245
|
-
snippets.push({ 'title' => 'All snippets', 'code' => '' })
|
|
246
|
-
|
|
247
|
-
answer = Snibbets::Menu.menu(snippets, filename: File.basename(filepath, '.md'), title: 'Select snippet', query: @query)
|
|
248
|
-
|
|
249
|
-
if answer['title'] == 'All snippets'
|
|
250
|
-
snippets.delete_if { |s| s['title'] == 'All snippets'}
|
|
251
|
-
if Snibbets.options[:output] == 'json'
|
|
252
|
-
print(snippets.to_json)
|
|
253
|
-
else
|
|
254
|
-
header = File.basename(filepath, '.md')
|
|
255
|
-
warn header
|
|
256
|
-
warn '=' * header.length
|
|
257
|
-
output = []
|
|
258
|
-
snippets.each do |snippet|
|
|
259
|
-
output << snippet['title']
|
|
260
|
-
output << '-' * snippet['title'].length
|
|
261
|
-
output << snippet['code']
|
|
262
|
-
output << "\n"
|
|
263
|
-
end
|
|
264
|
-
print(output.join("\n"))
|
|
265
|
-
end
|
|
266
|
-
elsif Snibbets.options[:output] == 'json'
|
|
267
|
-
print(answer.to_json)
|
|
268
|
-
else
|
|
269
|
-
header = "#{File.basename(filepath, '.md')}: #{answer['title']}"
|
|
270
|
-
warn header
|
|
271
|
-
warn '-' * header.length
|
|
272
|
-
code = answer['code']
|
|
273
|
-
code = highlight(code, filepath) if Snibbets.options[:highlight]
|
|
274
|
-
print(code)
|
|
275
|
-
end
|
|
276
|
-
end
|
|
277
|
-
end
|
|
278
|
-
end
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
def print(output)
|
|
282
|
-
$stdout.puts(output)
|
|
283
|
-
if Snibbets.options[:copy]
|
|
284
|
-
OS.copy(output)
|
|
285
|
-
$stderr.puts "Copied to clipboard"
|
|
286
|
-
end
|
|
287
|
-
end
|
|
288
|
-
end
|
|
289
|
-
end
|
|
290
|
-
|
|
291
7
|
module Snibbets
|
|
292
8
|
class << self
|
|
293
9
|
attr_reader :arguments, :query
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Snibbets
|
|
2
|
+
class ::Array
|
|
3
|
+
def blocks
|
|
4
|
+
select { |el| el =~ /^<block\d+>$/ }.count
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def strip_empty
|
|
8
|
+
remove_leading_empty_elements.remove_trailing_empty_elements
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def strip_empty!
|
|
12
|
+
replace strip_empty
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def remove_leading_empty_elements
|
|
16
|
+
output = []
|
|
17
|
+
|
|
18
|
+
each do |line|
|
|
19
|
+
next if line =~ /^\s*$/ || line.empty?
|
|
20
|
+
|
|
21
|
+
output << line
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
output
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def remove_trailing_empty_elements
|
|
28
|
+
output = []
|
|
29
|
+
|
|
30
|
+
reverse.each do |line|
|
|
31
|
+
next if line =~ /^\s*$/ || line.empty?
|
|
32
|
+
|
|
33
|
+
output << line
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
output.reverse
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/snibbets/config.rb
CHANGED
|
@@ -12,6 +12,7 @@ module Snibbets
|
|
|
12
12
|
highlight: false,
|
|
13
13
|
interactive: true,
|
|
14
14
|
launchbar: false,
|
|
15
|
+
menus: nil,
|
|
15
16
|
name_only: false,
|
|
16
17
|
output: 'raw',
|
|
17
18
|
source: File.expand_path('~/Dropbox/Snippets')
|
|
@@ -21,9 +22,17 @@ module Snibbets
|
|
|
21
22
|
custom_config = read_config
|
|
22
23
|
@options = DEFAULT_OPTIONS.merge(custom_config)
|
|
23
24
|
@options[:editor] ||= best_editor
|
|
25
|
+
@options[:menus] ||= best_menu
|
|
26
|
+
|
|
24
27
|
write_config unless @options.equal?(custom_config)
|
|
25
28
|
end
|
|
26
29
|
|
|
30
|
+
def best_menu
|
|
31
|
+
return 'fzf' if Which.exist?('fzf')
|
|
32
|
+
return 'gum' if Which.exist?('gum')
|
|
33
|
+
'console'
|
|
34
|
+
end
|
|
35
|
+
|
|
27
36
|
def best_editor
|
|
28
37
|
if ENV['EDITOR']
|
|
29
38
|
ENV['EDITOR']
|
data/lib/snibbets/menu.rb
CHANGED
|
@@ -122,11 +122,23 @@ module Snibbets
|
|
|
122
122
|
end
|
|
123
123
|
|
|
124
124
|
def menu(res, filename: nil, title: 'Select one', query: nil)
|
|
125
|
+
menu_opt = Snibbets.options[:menus]
|
|
125
126
|
query&.remove_spotlight_tags!
|
|
127
|
+
|
|
126
128
|
fzf = TTY::Which.which('fzf')
|
|
129
|
+
gum = TTY::Which.which('gum')
|
|
130
|
+
|
|
131
|
+
case menu_opt
|
|
132
|
+
when /fzf/
|
|
133
|
+
return fzf_menu(fzf, res, title, query, filename) unless fzf.empty?
|
|
134
|
+
when /gum/
|
|
135
|
+
return gum_menu(gum, res, title, query, filename) unless gum.empty?
|
|
136
|
+
when /console/
|
|
137
|
+
return console_menu(res, title, filename, query: query)
|
|
138
|
+
end
|
|
139
|
+
|
|
127
140
|
return fzf_menu(fzf, res, title, query, filename) unless fzf.empty?
|
|
128
141
|
|
|
129
|
-
gum = TTY::Which.which('gum')
|
|
130
142
|
return gum_menu(gum, res, title, query, filename) unless gum.empty?
|
|
131
143
|
|
|
132
144
|
console_menu(res, title, filename, query: query)
|
data/lib/snibbets/string.rb
CHANGED
|
@@ -28,6 +28,10 @@ module Snibbets
|
|
|
28
28
|
lines.join("\n")
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
def strip_newlines
|
|
32
|
+
split(/\n/).strip_empty.join("\n")
|
|
33
|
+
end
|
|
34
|
+
|
|
31
35
|
# Are there multiple snippets (indicated by ATX headers)
|
|
32
36
|
def multiple?
|
|
33
37
|
gsub(/(`{3,}).*?\n\1/m, '').scan(/^#+/).length > 1
|
|
@@ -131,8 +135,10 @@ module Snibbets
|
|
|
131
135
|
|
|
132
136
|
parts.shift if parts.count > 1
|
|
133
137
|
|
|
134
|
-
parts.each do |
|
|
135
|
-
lines =
|
|
138
|
+
parts.each do |part|
|
|
139
|
+
lines = part.split(/\n/).strip_empty
|
|
140
|
+
|
|
141
|
+
next if lines.blocks == 0
|
|
136
142
|
|
|
137
143
|
title = lines.count > 1 ? lines.shift.strip.sub(/[.:]$/, '') : 'Default snippet'
|
|
138
144
|
block = lines.join("\n").gsub(/<(block\d+)>/) { code_blocks[Regexp.last_match(1)] }
|
data/lib/snibbets/version.rb
CHANGED
data/lib/snibbets.rb
CHANGED
|
@@ -13,6 +13,7 @@ require_relative 'snibbets/config'
|
|
|
13
13
|
require_relative 'snibbets/which'
|
|
14
14
|
require_relative 'snibbets/string'
|
|
15
15
|
require_relative 'snibbets/hash'
|
|
16
|
+
require_relative 'snibbets/array'
|
|
16
17
|
require_relative 'snibbets/menu'
|
|
17
18
|
require_relative 'snibbets/os'
|
|
18
19
|
require_relative 'snibbets/highlight'
|
|
@@ -30,3 +31,287 @@ module Snibbets
|
|
|
30
31
|
end
|
|
31
32
|
end
|
|
32
33
|
end
|
|
34
|
+
|
|
35
|
+
module Snibbets
|
|
36
|
+
class << self
|
|
37
|
+
# Search the snippets directory for query using find and grep
|
|
38
|
+
def search(try: 0)
|
|
39
|
+
folder = Snibbets.options[:source]
|
|
40
|
+
# start by doing a spotlight search, if that fails, start trying:
|
|
41
|
+
# First try only search by filenames
|
|
42
|
+
# Second try search with grep
|
|
43
|
+
ext = Snibbets.options[:extension] || 'md'
|
|
44
|
+
cmd = case try
|
|
45
|
+
when 1
|
|
46
|
+
%(find "#{folder}" -iregex '#{@query.rx}' -name '*.#{ext}')
|
|
47
|
+
when 2
|
|
48
|
+
rg = TTY::Which.which('rg')
|
|
49
|
+
ag = TTY::Which.which('ag')
|
|
50
|
+
ack = TTY::Which.which('ack')
|
|
51
|
+
grep = TTY::Which.which('grep')
|
|
52
|
+
if Snibbets.options[:name_only]
|
|
53
|
+
nil
|
|
54
|
+
elsif !rg.empty?
|
|
55
|
+
%(#{rg} -li --color=never --glob='*.#{ext}' '#{@query.rx}' "#{folder}")
|
|
56
|
+
elsif !ag.empty?
|
|
57
|
+
%(#{ag} -li --nocolor -G '.*.#{ext}' '#{@query.rx}' "#{folder}")
|
|
58
|
+
elsif !ack.empty?
|
|
59
|
+
%(#{ack} -li --nocolor --markdown '#{@query.rx}' "#{folder}")
|
|
60
|
+
elsif !grep.empty?
|
|
61
|
+
%(#{grep} -iEl '#{@query.rx}' "#{folder}"/**/*.#{ext})
|
|
62
|
+
else
|
|
63
|
+
nil
|
|
64
|
+
end
|
|
65
|
+
else
|
|
66
|
+
mdfind = TTY::Which.which('mdfind')
|
|
67
|
+
if mdfind.empty?
|
|
68
|
+
nil
|
|
69
|
+
else
|
|
70
|
+
name_only = Snibbets.options[:name_only] ? '-name ' : ''
|
|
71
|
+
%(mdfind -onlyin #{folder} #{name_only}'#{@query} filename:.#{ext}' 2>/dev/null)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
if try == 2 && cmd.nil?
|
|
76
|
+
puts "No search method available on this system. Please install ripgrep, silver surfer, ack, or grep."
|
|
77
|
+
Process.exit 1
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
res = cmd.nil? ? '' : `#{cmd}`.strip
|
|
81
|
+
|
|
82
|
+
matches = []
|
|
83
|
+
|
|
84
|
+
unless res.empty?
|
|
85
|
+
lines = res.split(/\n/)
|
|
86
|
+
lines.each do |l|
|
|
87
|
+
matches << {
|
|
88
|
+
'title' => File.basename(l, '.*'),
|
|
89
|
+
'path' => l
|
|
90
|
+
}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
matches.sort_by! { |a| a['title'] }.uniq!
|
|
94
|
+
|
|
95
|
+
return matches unless matches.empty?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
return matches if try == 2
|
|
99
|
+
|
|
100
|
+
# if no results on the first try, try again searching all text
|
|
101
|
+
search(try: try + 1) if matches.empty?
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def open_snippet_in_editor(filepath)
|
|
105
|
+
editor = Snibbets.options[:editor] || Snibbets::Config.best_editor
|
|
106
|
+
|
|
107
|
+
os = RbConfig::CONFIG['target_os']
|
|
108
|
+
|
|
109
|
+
if editor.nil?
|
|
110
|
+
OS.open(filepath)
|
|
111
|
+
else
|
|
112
|
+
if os =~ /darwin.*/i
|
|
113
|
+
if editor =~ /^TextEdit/
|
|
114
|
+
`open -a TextEdit "#{filepath}"`
|
|
115
|
+
elsif TTY::Which.bundle_id?(editor)
|
|
116
|
+
`open -b "#{editor}" "#{filepath}"`
|
|
117
|
+
elsif TTY::Which.app?(editor)
|
|
118
|
+
`open -a "#{editor}" "#{filepath}"`
|
|
119
|
+
elsif TTY::Which.exist?(editor)
|
|
120
|
+
editor = TTY::Which.which(editor)
|
|
121
|
+
system %(#{editor} "#{filepath}") if editor
|
|
122
|
+
else
|
|
123
|
+
puts "No editor configured, or editor is missing"
|
|
124
|
+
Process.exit 1
|
|
125
|
+
end
|
|
126
|
+
elsif TTY::Which.exist?(editor)
|
|
127
|
+
editor = TTY::Which.which(editor)
|
|
128
|
+
system %(#{editor} "#{filepath}") if editor
|
|
129
|
+
else
|
|
130
|
+
puts "No editor configured, or editor is missing"
|
|
131
|
+
Process.exit 1
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def new_snippet_from_clipboard
|
|
137
|
+
trap('SIGINT') do
|
|
138
|
+
Howzit.console.info "\nCancelled"
|
|
139
|
+
exit!
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
build_lexers
|
|
143
|
+
|
|
144
|
+
pb = OS.paste.outdent
|
|
145
|
+
|
|
146
|
+
printf 'What does this snippet do? '
|
|
147
|
+
input = $stdin.gets.chomp
|
|
148
|
+
title = input unless input.empty?
|
|
149
|
+
|
|
150
|
+
printf 'What language(s) does it use (separate with spaces, full names or file extensions will work)? '
|
|
151
|
+
input = $stdin.gets.chomp
|
|
152
|
+
langs = input.split(/ +/).map(&:strip) unless input.empty?
|
|
153
|
+
exts = langs.map { |lang| Snibbets::Lexers.lang_to_ext(lang) }
|
|
154
|
+
tags = langs.map { |lang| Snibbets::Lexers.ext_to_lang(lang) }.concat(langs).sort.uniq
|
|
155
|
+
|
|
156
|
+
filename ="#{title}.#{exts.join('.')}.#{Snibbets.options[:extension]}"
|
|
157
|
+
|
|
158
|
+
File.open(File.join(Snibbets.options[:source], filename), 'w') do |f|
|
|
159
|
+
f.puts "tags: #{tags.join(', ')}
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
#{pb}
|
|
163
|
+
```"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
puts "New snippet written to #{filename}."
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def handle_launchbar(results)
|
|
170
|
+
output = []
|
|
171
|
+
|
|
172
|
+
if results.empty?
|
|
173
|
+
out = {
|
|
174
|
+
'title' => 'No matching snippets found'
|
|
175
|
+
}.to_json
|
|
176
|
+
puts out
|
|
177
|
+
Process.exit
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
results.each do |result|
|
|
181
|
+
input = IO.read(result['path'])
|
|
182
|
+
snippets = input.snippets
|
|
183
|
+
next if snippets.empty?
|
|
184
|
+
|
|
185
|
+
children = []
|
|
186
|
+
|
|
187
|
+
if snippets.length == 1
|
|
188
|
+
output << {
|
|
189
|
+
'title' => result['title'],
|
|
190
|
+
'path' => result['path'],
|
|
191
|
+
'action' => 'copyIt',
|
|
192
|
+
'actionArgument' => snippets[0]['code'],
|
|
193
|
+
'label' => 'Copy'
|
|
194
|
+
}
|
|
195
|
+
next
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
snippets.each { |s|
|
|
199
|
+
children << {
|
|
200
|
+
'title' => s['title'],
|
|
201
|
+
'path' => result['path'],
|
|
202
|
+
'action' => 'copyIt',
|
|
203
|
+
'actionArgument' => s['code'],
|
|
204
|
+
'label' => 'Copy'
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
output << {
|
|
209
|
+
'title' => result['title'],
|
|
210
|
+
'path' => result['path'],
|
|
211
|
+
'children' => children
|
|
212
|
+
}
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
puts output.to_json
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def handle_results(results)
|
|
219
|
+
if Snibbets.options[:launchbar]
|
|
220
|
+
handle_launchbar(results)
|
|
221
|
+
else
|
|
222
|
+
filepath = nil
|
|
223
|
+
if results.empty?
|
|
224
|
+
warn 'No results'
|
|
225
|
+
Process.exit 0
|
|
226
|
+
elsif results.length == 1 || !Snibbets.options[:interactive]
|
|
227
|
+
filepath = results[0]['path']
|
|
228
|
+
input = IO.read(filepath)
|
|
229
|
+
else
|
|
230
|
+
answer = Snibbets::Menu.menu(results, title: 'Select a file')
|
|
231
|
+
filepath = answer['path']
|
|
232
|
+
input = IO.read(filepath)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
if @arguments[:edit_snippet]
|
|
236
|
+
open_snippet_in_editor(filepath)
|
|
237
|
+
Process.exit 0
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
snippets = input.snippets
|
|
241
|
+
|
|
242
|
+
if snippets.empty?
|
|
243
|
+
warn 'No snippets found'
|
|
244
|
+
Process.exit 0
|
|
245
|
+
elsif snippets.length == 1 || !Snibbets.options[:interactive]
|
|
246
|
+
if Snibbets.options[:output] == 'json'
|
|
247
|
+
print(snippets.to_json)
|
|
248
|
+
else
|
|
249
|
+
snippets.each do |snip|
|
|
250
|
+
header = File.basename(filepath, '.md')
|
|
251
|
+
warn header
|
|
252
|
+
warn '-' * header.length
|
|
253
|
+
code = snip['code']
|
|
254
|
+
code = highlight(code, filepath) if Snibbets.options[:highlight]
|
|
255
|
+
print(code)
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
elsif snippets.length > 1
|
|
259
|
+
if Snibbets.options[:all]
|
|
260
|
+
if Snibbets.options[:output] == 'json'
|
|
261
|
+
print(snippets.to_json)
|
|
262
|
+
else
|
|
263
|
+
output = []
|
|
264
|
+
snippets.each do |snippet|
|
|
265
|
+
output << snippet['title']
|
|
266
|
+
output << '-' * snippet['title'].length
|
|
267
|
+
output << snippet['code']
|
|
268
|
+
output << "\n"
|
|
269
|
+
end
|
|
270
|
+
print(output.join("\n"))
|
|
271
|
+
end
|
|
272
|
+
else
|
|
273
|
+
snippets.push({ 'title' => 'All snippets', 'code' => '' })
|
|
274
|
+
|
|
275
|
+
answer = Snibbets::Menu.menu(snippets, filename: File.basename(filepath, '.md'), title: 'Select snippet', query: @query)
|
|
276
|
+
|
|
277
|
+
if answer['title'] == 'All snippets'
|
|
278
|
+
snippets.delete_if { |s| s['title'] == 'All snippets'}
|
|
279
|
+
if Snibbets.options[:output] == 'json'
|
|
280
|
+
print(snippets.to_json)
|
|
281
|
+
else
|
|
282
|
+
header = File.basename(filepath, '.md')
|
|
283
|
+
warn header
|
|
284
|
+
warn '=' * header.length
|
|
285
|
+
output = []
|
|
286
|
+
snippets.each do |snippet|
|
|
287
|
+
output << snippet['title']
|
|
288
|
+
output << '-' * snippet['title'].length
|
|
289
|
+
output << snippet['code']
|
|
290
|
+
output << "\n"
|
|
291
|
+
end
|
|
292
|
+
print(output.join("\n"))
|
|
293
|
+
end
|
|
294
|
+
elsif Snibbets.options[:output] == 'json'
|
|
295
|
+
print(answer.to_json)
|
|
296
|
+
else
|
|
297
|
+
header = "#{File.basename(filepath, '.md')}: #{answer['title']}"
|
|
298
|
+
warn header
|
|
299
|
+
warn '-' * header.length
|
|
300
|
+
code = answer['code']
|
|
301
|
+
code = highlight(code, filepath) if Snibbets.options[:highlight]
|
|
302
|
+
print(code)
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def print(output)
|
|
310
|
+
$stdout.puts(output)
|
|
311
|
+
if Snibbets.options[:copy]
|
|
312
|
+
OS.copy(output)
|
|
313
|
+
$stderr.puts "Copied to clipboard"
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end
|
data/snibbets.gemspec
CHANGED
|
@@ -31,6 +31,7 @@ Gem::Specification.new do |spec|
|
|
|
31
31
|
spec.files += Dir["[A-Z]*"]
|
|
32
32
|
|
|
33
33
|
spec.add_development_dependency "bundler", "~> 2.0"
|
|
34
|
+
spec.add_development_dependency "awesome_print", "~> 1.9"
|
|
34
35
|
spec.add_development_dependency "gem-release", "~> 2.2"
|
|
35
36
|
spec.add_development_dependency "parse_gemspec-cli", "~> 1.0"
|
|
36
37
|
spec.add_development_dependency "rake", "~> 13.0"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: snibbets
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.
|
|
4
|
+
version: 2.0.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brett Terpstra
|
|
@@ -24,6 +24,20 @@ dependencies:
|
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '2.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: awesome_print
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '1.9'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '1.9'
|
|
27
41
|
- !ruby/object:Gem::Dependency
|
|
28
42
|
name: gem-release
|
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -194,6 +208,7 @@ files:
|
|
|
194
208
|
- bin/snibbets
|
|
195
209
|
- buildnotes.md
|
|
196
210
|
- lib/snibbets.rb
|
|
211
|
+
- lib/snibbets/array.rb
|
|
197
212
|
- lib/snibbets/config.rb
|
|
198
213
|
- lib/snibbets/hash.rb
|
|
199
214
|
- lib/snibbets/highlight.rb
|