searchlink 2.3.69 → 2.3.70

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9615984b2fcd6435ae483198dd59b311218ab9df21e6b5b41df5dcfbe80e293
4
- data.tar.gz: c0ef544491bfeb402c60b0f3843233515291dc706cac1b9cf448e0a5535aaffa
3
+ metadata.gz: 94434b4c63f4188ca00e93ad4c2be7765b61a2fdbccd3b7e706482bf1e09b618
4
+ data.tar.gz: 4af48f283d1a693bed323cdbbb91121ee5b4616225d4ecef46f80302016227ff
5
5
  SHA512:
6
- metadata.gz: 4da2625e08d78d367917246412a11dec1e4c33888201ea6444bc19ee10764e243e20651e9ca883ba1c54cb6d7ba3a6131fba64a1f6a968fe43a0dfd69daa3380
7
- data.tar.gz: c01d67b2ec225e12bd8bd25fa18833936f73f2e24b05ce28189873bd2dcc627513d00c68a23862ee7753519cc90a65d3512a5a51b12b9b3954e0a85569ed1323
6
+ metadata.gz: 3fa2094dbbf341680baefae73f98aba890ace10c6f6d44198b7902c28810b9e3cc8121199b9ba26a1da0243dcf779c3f018ebaaa6f1e794456d72106fafe5ad6
7
+ data.tar.gz: 7a39a40fbc8e8466c3652e236eee03091b027dd83aebd481b31dcb3acaa3a53124d362887d4cbe33bc22699fadd4e7adfe40f4f40db48cb8f69754584d84ae70
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SL
4
+ # Custom Semantic Versioning error
5
+ class VersionError < StandardError
6
+ def initialize(msg)
7
+ msg = msg ? ": #{msg}" : ''
8
+ puts "Versioning error#{msg}"
9
+
10
+ super()
11
+
12
+ Process.exit 1
13
+ end
14
+ end
15
+
16
+ # Custom plugin error
17
+ class PluginError < StandardError
18
+ def initialize(msg = nil, plugin: nil)
19
+ plugin = %("#{plugin}") if plugin
20
+ plugin ||= 'plugin'
21
+ msg = ": #{msg}" if msg
22
+ puts "Error in #{plugin}#{msg}"
23
+
24
+ super()
25
+
26
+ Process.exit 1
27
+ end
28
+ end
29
+ end
@@ -62,13 +62,16 @@ module SL
62
62
 
63
63
  # Posts macOS notifications
64
64
  #
65
- # @param str The title of the notification
66
- # @param sub The text of the notification
65
+ # @param title [String] The title of the notification
66
+ # @param subtitle [String] The text of the notification
67
67
  #
68
- def notify(str, sub)
68
+ def notify(title, subtitle)
69
69
  return unless SL.config['notifications']
70
70
 
71
- `osascript -e 'display notification "SearchLink" with title "#{str}" subtitle "#{sub}"'`
71
+ title = title.gsub(/"/, '\\"')
72
+ subtitle = subtitle.gsub(/"/, '\\"')
73
+
74
+ `osascript -e 'display notification "SearchLink" with title "#{title}" subtitle "#{subtitle}"'`
72
75
  end
73
76
  end
74
77
  end
@@ -20,14 +20,14 @@ module SL
20
20
 
21
21
  input.parse_flags! unless skip_flags
22
22
 
23
- options = %w[debug country_code inline prefix_random include_titles remove_seo validate_links]
23
+ options = %w[debug country_code inline prefix_random include_titles remove_seo validate_links complete_bare]
24
24
  options.each do |o|
25
25
  if input =~ /^ *#{o}:\s+(\S+)$/
26
26
  val = Regexp.last_match(1).strip
27
27
  val = true if val =~ /true/i
28
28
  val = false if val =~ /false/i
29
29
  SL.config[o] = val
30
- $stderr.print "\r\033[0KGlobal config: #{o} = #{SL.config[o]}\n" unless SILENT
30
+ warn "\r\033[0KGlobal config: #{o} = #{SL.config[o]}\n" unless SILENT
31
31
  end
32
32
 
33
33
  next if skip_flags
@@ -57,7 +57,7 @@ module SL
57
57
  case input.strip
58
58
  when /^!?help$/i
59
59
  if SILENT
60
- help_dialog # %x{open http://brettterpstra.com/projects/searchlink/}
60
+ help_dialog
61
61
  else
62
62
  $stdout.puts SL.version_check.to_s
63
63
  $stdout.puts 'See https://github.com/ttscoff/searchlink/wiki for help'
@@ -75,6 +75,117 @@ module SL
75
75
  Process.exit 0
76
76
  end
77
77
 
78
+ def create_footnote(mtch)
79
+ if mtch[1].nil? || mtch[1] == ''
80
+ match
81
+ else
82
+ note = mtch[1].strip
83
+ @footnote_counter += 1
84
+ ref = if !@link_text.empty? && @link_text.scan(/\s/).empty?
85
+ @link_text
86
+ else
87
+ format('%<p>sfn%<c>04d', p: @prefix, c: @footnote_counter)
88
+ end
89
+ SL.add_footer "[^#{ref}]: #{note}"
90
+ res = "[^#{ref}]"
91
+ @cursor_difference += (SL.match_length - res.length)
92
+ SL.match_length = res.length
93
+ SL.add_report("#{@match_string} => Footnote #{ref}")
94
+ res
95
+ end
96
+ end
97
+
98
+ def add_title(link_info)
99
+ @url = link_info
100
+ title = SL::URL.title(@url)
101
+ @link_text = title
102
+
103
+ if @ref_title
104
+ unless @links.key? @url
105
+ @links[@url] = @link_text
106
+ SL.add_footer SL.make_link(:ref_title, @link_text, @url, title: title, force_title: false)
107
+ end
108
+ @delete_line = true
109
+ elsif SL.config['inline']
110
+ res = SL.make_link(:inline, @link_text, @url, title: title, force_title: false)
111
+ @cursor_difference += SL.match_length - res.length
112
+ SL.match_length = res.length
113
+ SL.add_report("#{@match_string} => #{@url}")
114
+ res
115
+ else
116
+ unless @links.key? @url
117
+ @highest_marker += 1
118
+ @links[@url] = format('%<pre>s%<m>04d', pre: @prefix, m: @highest_marker)
119
+ SL.add_footer SL.make_link(:ref_title, @links[@url], @url, title: title, force_title: false)
120
+ end
121
+
122
+ type = SL.config['inline'] ? :inline : :ref_link
123
+ res = SL.make_link(type, @link_text, @links[@url], title: false, force_title: false)
124
+ @cursor_difference += SL.match_length - res.length
125
+ SL.match_length = res.length
126
+ SL.add_report("#{@match_string} => #{@url}")
127
+ res
128
+ end
129
+ end
130
+
131
+ def custom_search(search_type, search_terms)
132
+ SL.config['custom_site_searches'].each do |k, v|
133
+ next unless search_type == k
134
+
135
+ @link_text = search_terms if !SL.titleize && @link_text == ''
136
+ v = parse_arguments(v, { no_restore: true })
137
+ if v =~ %r{^(/|http)}i
138
+ search_type = 'r'
139
+ tokens = v.scan(/\$term\d+[ds]?/).sort.uniq
140
+
141
+ if !tokens.empty?
142
+ highest_token = 0
143
+ tokens.each do |token|
144
+ if token =~ /(\d+)[ds]?$/ && Regexp.last_match(1).to_i > highest_token
145
+ highest_token = Regexp.last_match(1).to_i
146
+ end
147
+ end
148
+ terms_p = search_terms.split(/ +/)
149
+ if terms_p.length > highest_token
150
+ remainder = terms_p[highest_token - 1..-1].join(' ')
151
+ terms_p = terms_p[0..highest_token - 2]
152
+ terms_p.push(remainder)
153
+ end
154
+ tokens.each do |t|
155
+ next unless t =~ /(\d+)[ds]?$/
156
+
157
+ int = Regexp.last_match(1).to_i - 1
158
+ replacement = terms_p[int]
159
+ case t
160
+ when /d$/
161
+ replacement.downcase!
162
+ re_down = ''
163
+ when /s$/
164
+ replacement.slugify!
165
+ re_down = ''
166
+ else
167
+ re_down = '(?!d|s)'
168
+ end
169
+ v.gsub!(/#{Regexp.escape(t) + re_down}/, replacement.url_encode)
170
+ end
171
+ search_terms = v
172
+ else
173
+ search_terms = v.gsub(/\$term[ds]?/i) do |mtch|
174
+ search_terms.downcase! if mtch =~ /d$/i
175
+ search_terms.slugify! if mtch =~ /s$/i
176
+ search_terms.url_encode
177
+ end
178
+ end
179
+ else
180
+ search_type = SL::GoogleSearch.api_key? ? 'gg' : 'g'
181
+ search_terms = "site:#{v} #{search_terms}"
182
+ end
183
+
184
+ break
185
+ end
186
+ [search_type, search_terms]
187
+ end
188
+
78
189
  def parse(input)
79
190
  SL.output = []
80
191
  return false if input.empty?
@@ -94,7 +205,7 @@ module SL
94
205
  SL.add_output("<!-- v#{latest_version} available, run SearchLink on the word 'update' to install. -->")
95
206
  end
96
207
 
97
- links = {}
208
+ @links = {}
98
209
  SL.footer = []
99
210
  counter_links = 0
100
211
  counter_errors = 0
@@ -102,9 +213,9 @@ module SL
102
213
  input.sub!(/\n?<!-- Report:.*?-->\n?/m, '')
103
214
  input.sub!(/\n?<!-- Errors:.*?-->\n?/m, '')
104
215
 
105
- input.scan(/\[(.*?)\]:\s+(.*?)\n/).each { |match| links[match[1].strip] = match[0] }
216
+ input.scan(/\[(.*?)\]:\s+(.*?)\n/).each { |match| @links[match[1].strip] = match[0] }
106
217
 
107
- prefix = if SL.config['prefix_random']
218
+ @prefix = if SL.config['prefix_random']
108
219
  if input =~ /\[(\d{4}-)\d+\]: \S+/
109
220
  Regexp.last_match(1)
110
221
  else
@@ -114,16 +225,27 @@ module SL
114
225
  ''
115
226
  end
116
227
 
117
- highest_marker = 0
118
- input.scan(/^\s{,3}\[(?:#{prefix})?(\d+)\]: /).each do
228
+ @highest_marker = 0
229
+ input.scan(/^\s{,3}\[(?:#{@prefix})?(\d+)\]: /).each do
119
230
  m = Regexp.last_match
120
- highest_marker = m[1].to_i if m[1].to_i > highest_marker
231
+ @highest_marker = m[1].to_i if m[1].to_i > @highest_marker
121
232
  end
122
233
 
123
- footnote_counter = 0
124
- input.scan(/^\s{,3}\[\^(?:#{prefix})?fn(\d+)\]: /).each do
234
+ @footnote_counter = 0
235
+ input.scan(/^\s{,3}\[\^(?:#{@prefix})?fn(\d+)\]: /).each do
125
236
  m = Regexp.last_match
126
- footnote_counter = m[1].to_i if m[1].to_i > footnote_counter
237
+ @footnote_counter = m[1].to_i if m[1].to_i > @footnote_counter
238
+ end
239
+
240
+ if SL.config['complete_bare']
241
+ rx = %r{(?ix-m)(?<!\(|:\s|<)(?:
242
+ (?:https?://)(?:[\da-z.-]+)\.(?:[a-z.]{2,6})
243
+ (?:[/\w\d.\-()_/+=?&%]*?(?=[\s\n]|$))
244
+ )}
245
+ input.gsub!(rx) do
246
+ url_match = Regexp.last_match
247
+ url_match.pre_match =~ /!\S+ +$/ ? url_match[0] : "[%](#{url_match[0]})"
248
+ end
127
249
  end
128
250
 
129
251
  if input =~ /\[(.*?)\]\((.*?)\)/
@@ -135,7 +257,7 @@ module SL
135
257
  line_difference = 0
136
258
  lines.each_with_index do |line, num|
137
259
  SL.line_num = num - line_difference
138
- cursor_difference = 0
260
+ @cursor_difference = 0
139
261
  # ignore links in code blocks
140
262
  if line =~ /^( {4,}|\t+)[^*+\-]/
141
263
  out.push(line)
@@ -155,22 +277,22 @@ module SL
155
277
  next
156
278
  end
157
279
 
158
- delete_line = false
280
+ @delete_line = false
159
281
 
160
- search_count = 0
282
+ @search_count = 0
161
283
 
162
284
  line.gsub!(/\[(.*?)\]\((.*?)\)/) do |match|
163
285
  this_match = Regexp.last_match
164
- SL.match_column = this_match.begin(0) - cursor_difference
165
- match_string = this_match.to_s
166
- SL.match_length = match_string.length
286
+ SL.match_column = this_match.begin(0) - @cursor_difference
287
+ @match_string = this_match.to_s
288
+ SL.match_length = @match_string.length
167
289
  match_before = this_match.pre_match
168
290
 
169
291
  invalid_search = false
170
- ref_title = false
292
+ @ref_title = false
171
293
 
172
294
  if match_before.scan(/(^|[^\\])`/).length.odd?
173
- SL.add_report("Match '#{match_string}' within an inline code block")
295
+ SL.add_report("Match '#{@match_string}' within an inline code block")
174
296
  invalid_search = true
175
297
  end
176
298
 
@@ -179,14 +301,14 @@ module SL
179
301
  $stderr.print("\033[0K\rProcessed: #{counter_links} of #{total_links}, #{counter_errors} errors. ")
180
302
  end
181
303
 
182
- link_text = this_match[1] || ''
304
+ @link_text = this_match[1] || ''
183
305
  link_info = parse_arguments(this_match[2].strip).strip || ''
184
306
 
185
- if link_text.strip == '' && link_info =~ /".*?"/
307
+ if @link_text.strip == '' && link_info =~ /".*?"/
186
308
  link_info.gsub!(/"(.*?)"/) do
187
- m = Regexp.last_match(1)
188
- link_text = m if link_text == ''
189
- %("#")
309
+ m = Regexp.last_match
310
+ @link_text = m[1] if @link_text == ''
311
+ m[0]
190
312
  end
191
313
  end
192
314
 
@@ -195,11 +317,11 @@ module SL
195
317
  end
196
318
 
197
319
  if link_info.strip =~ /:$/ && line.strip == match
198
- ref_title = true
320
+ @ref_title = true
199
321
  link_info.sub!(/\s*:\s*$/, '')
200
322
  end
201
323
 
202
- unless !link_text.empty? || !link_info.sub(/^[!\^]\S+/, '').strip.empty?
324
+ unless !@link_text.empty? || !link_info.sub(/^[!\^]\S+/, '').strip.empty?
203
325
  SL.add_error('No input', match)
204
326
  counter_errors += 1
205
327
  invalid_search = true
@@ -217,60 +339,15 @@ module SL
217
339
  match
218
340
  elsif link_info =~ /^\^(.+)/
219
341
  m = Regexp.last_match
220
- if m[1].nil? || m[1] == ''
221
- match
222
- else
223
- note = m[1].strip
224
- footnote_counter += 1
225
- ref = if !link_text.empty? && link_text.scan(/\s/).empty?
226
- link_text
227
- else
228
- format('%<p>sfn%<c>04d', p: prefix, c: footnote_counter)
229
- end
230
- SL.add_footer "[^#{ref}]: #{note}"
231
- res = "[^#{ref}]"
232
- cursor_difference += (SL.match_length - res.length)
233
- SL.match_length = res.length
234
- SL.add_report("#{match_string} => Footnote #{ref}")
235
- res
236
- end
342
+ create_footnote(m)
237
343
  # Handle [](URL) and [%](URL), filling in title
238
- elsif (link_text == '' || link_text == '%') && SL::URL.url?(link_info)
239
- url = link_info
240
- title = SL::URL.title(link_info)
241
- link_text = title
242
-
243
- if ref_title
244
- unless links.key? url
245
- links[url] = link_text
246
- SL.add_footer SL.make_link(:ref_title, link_text, url, title: title, force_title: false)
247
- end
248
- delete_line = true
249
- elsif SL.config['inline']
250
- res = SL.make_link(:inline, link_text, url, title: title, force_title: false)
251
- cursor_difference += SL.match_length - res.length
252
- SL.match_length = res.length
253
- SL.add_report("#{match_string} => #{url}")
254
- res
255
- else
256
- unless links.key? url
257
- highest_marker += 1
258
- links[url] = format('%<pre>s%<m>04d', pre: prefix, m: highest_marker)
259
- SL.add_footer SL.make_link(:ref_title, links[url], url, title: title, force_title: false)
260
- end
261
-
262
- type = SL.config['inline'] ? :inline : :ref_link
263
- res = SL.make_link(type, link_text, links[url], title: false, force_title: false)
264
- cursor_difference += SL.match_length - res.length
265
- SL.match_length = res.length
266
- SL.add_report("#{match_string} => #{url}")
267
- res
268
- end
269
- elsif (link_text == '' && link_info == '') || SL::URL.url?(link_info)
344
+ elsif (@link_text == '' || @link_text == '%') && SL::URL.url?(link_info)
345
+ add_title(link_info)
346
+ elsif (@link_text == '' && link_info == '') || SL::URL.url?(link_info)
270
347
  SL.add_error('Invalid search', match) unless SL::URL.url?(link_info)
271
348
  match
272
349
  else
273
- link_info = link_text if !link_text.empty? && link_info == ''
350
+ link_info = @link_text if !@link_text.empty? && link_info == ''
274
351
 
275
352
  search_type = ''
276
353
  search_terms = ''
@@ -291,15 +368,15 @@ module SL
291
368
  search_terms.strip!
292
369
 
293
370
  # if the link text is just '%' replace with title regardless of config settings
294
- if link_text == '%' && search_terms && !search_terms.empty?
371
+ if @link_text == '%' && search_terms && !search_terms.empty?
295
372
  SL.titleize = true
296
- link_text = ''
373
+ @link_text = ''
297
374
  end
298
375
 
299
- search_terms = link_text if search_terms == ''
376
+ search_terms = @link_text if search_terms == ''
300
377
 
301
378
  # if the input starts with a +, append it to the link text as the search terms
302
- search_terms = "#{link_text} #{search_terms.strip.sub(/^\+\s*/, '')}" if search_terms.strip =~ /^\+[^+]/
379
+ search_terms = "#{@link_text} #{search_terms.strip.sub(/^\+\s*/, '')}" if search_terms.strip =~ /^\+[^+]/
303
380
 
304
381
  # if the end of input contain "^", copy to clipboard instead of STDOUT
305
382
  SL.clipboard = true if search_terms =~ /(!!)?\^(!!)?$/
@@ -314,19 +391,19 @@ module SL
314
391
 
315
392
  if search_word && SL::Searches.valid_search?(search_word[1])
316
393
  search_type = search_word[1] unless search_word.nil?
317
- search_terms = link_text
394
+ search_terms = @link_text
318
395
  elsif search_word && search_word[1] =~ /^(\S+\.)+\S+$/
319
396
  search_type = SL::GoogleSearch.api_key? ? 'gg' : 'g'
320
397
  puts SL::GoogleSearch.api_key?
321
- search_terms = "site:#{search_word[1]} #{link_text}"
398
+ search_terms = "site:#{search_word[1]} #{@link_text}"
322
399
  else
323
400
  SL.add_error("Invalid search#{SL::Searches.did_you_mean(search_word[1])}", match)
324
401
  search_type = false
325
402
  search_terms = false
326
403
  end
327
- elsif link_text && !link_text.empty? && (!link_info || link_info.empty?)
404
+ elsif @link_text && !@link_text.empty? && (!link_info || link_info.empty?)
328
405
  search_type = SL::GoogleSearch.api_key? ? 'gg' : 'g'
329
- search_terms = link_text
406
+ search_terms = @link_text
330
407
  elsif link_info && !link_info.empty?
331
408
  search_type = SL::GoogleSearch.api_key? ? 'gg' : 'g'
332
409
  search_terms = link_info
@@ -337,109 +414,56 @@ module SL
337
414
  end
338
415
 
339
416
  if search_type && !search_terms.empty?
340
- SL.config['custom_site_searches'].each do |k, v|
341
- next unless search_type == k
342
-
343
- link_text = search_terms if !SL.titleize && link_text == ''
344
- v = parse_arguments(v, { no_restore: true })
345
- if v =~ %r{^(/|http)}i
346
- search_type = 'r'
347
- tokens = v.scan(/\$term\d+[ds]?/).sort.uniq
348
-
349
- if !tokens.empty?
350
- highest_token = 0
351
- tokens.each do |token|
352
- if token =~ /(\d+)[ds]?$/ && Regexp.last_match(1).to_i > highest_token
353
- highest_token = Regexp.last_match(1).to_i
354
- end
355
- end
356
- terms_p = search_terms.split(/ +/)
357
- if terms_p.length > highest_token
358
- remainder = terms_p[highest_token - 1..-1].join(' ')
359
- terms_p = terms_p[0..highest_token - 2]
360
- terms_p.push(remainder)
361
- end
362
- tokens.each do |t|
363
- next unless t =~ /(\d+)[ds]?$/
364
-
365
- int = Regexp.last_match(1).to_i - 1
366
- replacement = terms_p[int]
367
- case t
368
- when /d$/
369
- replacement.downcase!
370
- re_down = ''
371
- when /s$/
372
- replacement.slugify!
373
- re_down = ''
374
- else
375
- re_down = '(?!d|s)'
376
- end
377
- v.gsub!(/#{Regexp.escape(t) + re_down}/, replacement.url_encode)
378
- end
379
- search_terms = v
380
- else
381
- search_terms = v.gsub(/\$term[ds]?/i) do |mtch|
382
- search_terms.downcase! if mtch =~ /d$/i
383
- search_terms.slugify! if mtch =~ /s$/i
384
- search_terms.url_encode
385
- end
386
- end
387
- else
388
- search_type = SL::GoogleSearch.api_key? ? 'gg' : 'g'
389
- search_terms = "site:#{v} #{search_terms}"
390
- end
391
-
392
- break
393
- end
417
+ search_type, search_terms = custom_search(search_type, search_terms)
394
418
  end
395
419
 
396
- if (search_type && search_terms) || url
420
+ if (search_type && search_terms) || @url
397
421
  # warn "Searching #{search_type} for #{search_terms}"
398
- unless url
399
- search_count += 1
400
- url, title, link_text = do_search(search_type, search_terms, link_text, search_count)
422
+ unless @url
423
+ @search_count += 1
424
+ @url, title, @link_text = do_search(search_type, search_terms, @link_text, @search_count)
401
425
  end
402
426
 
403
- if url
404
- title = SL::URL.title(url) if SL.titleize && title == ''
427
+ if @url
428
+ title = SL::URL.title(@url) if SL.titleize && title == ''
405
429
 
406
- link_text = title if link_text == '' && title
430
+ @link_text = title if @link_text == '' && title
407
431
  force_title = search_type =~ /def/ ? true : false
408
432
 
409
- if link_only || search_type =~ /sp(ell)?/ || url == 'embed'
410
- url = title if url == 'embed'
411
- cursor_difference += SL.match_length - url.length
412
- SL.match_length = url.length
413
- SL.add_report("#{match_string} => #{url}")
414
- url
415
- elsif ref_title
416
- unless links.key? url
417
- links[url] = link_text
418
- SL.add_footer SL.make_link(:ref_title, link_text, url, title: title, force_title: force_title)
433
+ if link_only || search_type =~ /sp(ell)?/ || @url == 'embed'
434
+ @url = title if @url == 'embed'
435
+ @cursor_difference += SL.match_length - @url.length
436
+ SL.match_length = @url.length
437
+ SL.add_report("#{@match_string} => #{@url}")
438
+ @url
439
+ elsif @ref_title
440
+ unless @links.key? @url
441
+ @links[@url] = @link_text
442
+ SL.add_footer SL.make_link(:ref_title, @link_text, @url, title: title, force_title: force_title)
419
443
  end
420
- delete_line = true
444
+ @delete_line = true
421
445
  elsif SL.config['inline']
422
- res = SL.make_link(:inline, link_text, url, title: title, force_title: force_title)
423
- cursor_difference += SL.match_length - res.length
446
+ res = SL.make_link(:inline, @link_text, @url, title: title, force_title: force_title)
447
+ @cursor_difference += SL.match_length - res.length
424
448
  SL.match_length = res.length
425
- SL.add_report("#{match_string} => #{url}")
449
+ SL.add_report("#{@match_string} => #{@url}")
426
450
  res
427
451
  else
428
- unless links.key? url
429
- highest_marker += 1
430
- links[url] = format('%<pre>s%<m>04d', pre: prefix, m: highest_marker)
431
- SL.add_footer SL.make_link(:ref_title, links[url], url, title: title, force_title: force_title)
452
+ unless @links.key? @url
453
+ @highest_marker += 1
454
+ @links[@url] = format('%<pre>s%<m>04d', pre: @prefix, m: @highest_marker)
455
+ SL.add_footer SL.make_link(:ref_title, @links[@url], @url, title: title, force_title: force_title)
432
456
  end
433
457
 
434
458
  type = SL.config['inline'] ? :inline : :ref_link
435
- res = SL.make_link(type, link_text, links[url], title: false, force_title: force_title)
436
- cursor_difference += SL.match_length - res.length
459
+ res = SL.make_link(type, @link_text, @links[@url], title: false, force_title: force_title)
460
+ @cursor_difference += SL.match_length - res.length
437
461
  SL.match_length = res.length
438
- SL.add_report("#{match_string} => #{url}")
462
+ SL.add_report("#{@match_string} => #{@url}")
439
463
  res
440
464
  end
441
465
  else
442
- SL.add_error('No results', "#{search_terms} (#{match_string})")
466
+ SL.add_error('No results', "#{search_terms} (#{@match_string})")
443
467
  counter_errors += 1
444
468
  match
445
469
  end
@@ -450,9 +474,9 @@ module SL
450
474
  end
451
475
  end
452
476
  end
453
- line_difference += 1 if delete_line
454
- out.push(line) unless delete_line
455
- delete_line = false
477
+ line_difference += 1 if @delete_line
478
+ out.push(line) unless @delete_line
479
+ @delete_line = false
456
480
  end
457
481
  warn "\n" unless SILENT
458
482
 
@@ -514,8 +538,8 @@ module SL
514
538
  ## using hostname as text without doing search
515
539
  if SL::URL.only_url?(input.strip)
516
540
  type = reference_link ? :ref_title : :inline
517
- url, title = SL::URL.url_to_link(input.strip, type)
518
- print SL.make_link(type, title, url, title: false, force_title: false)
541
+ @url, title = SL::URL.url_to_link(input.strip, type)
542
+ print SL.make_link(type, title, @url, title: false, force_title: false)
519
543
  Process.exit
520
544
  end
521
545
 
@@ -532,10 +556,10 @@ module SL
532
556
  # input.sub!(/\+.*?$/, '').strip!
533
557
  # end
534
558
 
535
- link_text = false
559
+ @link_text = false
536
560
 
537
561
  if input =~ /"(.*?)"/
538
- link_text = Regexp.last_match(1)
562
+ @link_text = Regexp.last_match(1)
539
563
  input.gsub!(/"(.*?)"/, '\1')
540
564
  end
541
565
 
@@ -546,7 +570,7 @@ module SL
546
570
  when /^!(\S+)\s+(.*)$/
547
571
  type = Regexp.last_match(1)
548
572
  link_info = Regexp.last_match(2).strip
549
- link_text ||= link_info
573
+ @link_text ||= link_info
550
574
  terms = link_info + additional_terms
551
575
  terms.strip!
552
576
 
@@ -556,7 +580,7 @@ module SL
556
580
  SL.config['custom_site_searches'].each do |k, v|
557
581
  next unless type == k
558
582
 
559
- link_text = terms if link_text == ''
583
+ @link_text = terms if @link_text == ''
560
584
  v = parse_arguments(v, { no_restore: true })
561
585
  if v =~ %r{^(/|http)}i
562
586
  type = 'r'
@@ -614,10 +638,10 @@ module SL
614
638
  terms = "site:#{type} #{terms}"
615
639
  type = SL::GoogleSearch.api_key? ? 'gg' : 'g'
616
640
  end
617
- search_count ||= 0
618
- search_count += 1
641
+ @search_count ||= 0
642
+ @search_count += 1
619
643
 
620
- url, title, link_text = do_search(type, terms, link_text, search_count)
644
+ @url, title, @link_text = do_search(type, terms, @link_text, @search_count)
621
645
  else
622
646
  SL.add_error("Invalid search#{SL::Searches.did_you_mean(type)}", input)
623
647
  counter_errors += 1
@@ -630,25 +654,25 @@ module SL
630
654
  else
631
655
  't'
632
656
  end
633
- link_text = input.sub(/^[tfilm]/, '')
634
- url, title = SL::SocialSearch.social_handle(type, link_text)
635
- link_text = title
657
+ @link_text = input.sub(/^[tfilm]/, '')
658
+ @url, title = SL::SocialSearch.social_handle(type, @link_text)
659
+ @link_text = title
636
660
  else
637
- link_text ||= input
638
- url, title, link_text = SL.ddg(input, link_text)
661
+ @link_text ||= input
662
+ @url, title, @link_text = SL.ddg(input, @link_text)
639
663
  end
640
664
 
641
- if url
665
+ if @url
642
666
  if type =~ /sp(ell)?/
643
- SL.add_output(url)
667
+ SL.add_output(@url)
644
668
  elsif link_only
645
- SL.add_output(url)
646
- elsif url == 'embed'
669
+ SL.add_output(@url)
670
+ elsif @url == 'embed'
647
671
  SL.add_output(title)
648
672
  else
649
673
  type = reference_link ? :ref_title : :inline
650
674
 
651
- SL.add_output SL.make_link(type, link_text, url, title: title, force_title: false)
675
+ SL.add_output SL.make_link(type, @link_text, @url, title: title, force_title: false)
652
676
  SL.print_errors
653
677
  end
654
678
  else
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ # title: Script plugin
4
+ # description: Load custom searches from non-ruby scripts
5
+ module SL
6
+ # Script Search
7
+ class ScriptSearch
8
+ def initialize(config)
9
+ @filename = config['filename']
10
+ @path = config['path']
11
+
12
+ %w[trigger searches name script].each do |key|
13
+ raise PluginError.new(%(configuration missing key "#{key}"), plugin: @filename) unless config.key?(key)
14
+ end
15
+
16
+ @trigger = config['trigger']
17
+ @searches = config['searches']
18
+ @name = config['name']
19
+ @script = find_script(config['script'])
20
+
21
+ unless File.executable?(@script)
22
+ raise PluginError.new(%(script "#{File.basename(@script)}" not executable\nrun `chmod a+x #{@script.shorten_path}` to correct),
23
+ plugin: @filename)
24
+ end
25
+
26
+ class << self
27
+ def settings
28
+ {
29
+ trigger: @trigger,
30
+ searches: @searches
31
+ }
32
+ end
33
+
34
+ def search(search_type, search_terms, link_text)
35
+ type = Shellwords.escape(search_type)
36
+ terms = Shellwords.escape(search_terms)
37
+ text = Shellwords.escape(link_text)
38
+
39
+ stdout = `#{[@script, type, terms, text].join(' ')} 2>&1`
40
+
41
+ unless $CHILD_STATUS.success?
42
+ raise PluginError.new(%("#{File.basename(@script)}" returned error #{$CHILD_STATUS.exitstatus}\n#{stdout}),
43
+ plugin: @filename)
44
+ end
45
+
46
+ begin
47
+ res = JSON.parse(stdout)
48
+ rescue JSON::ParserError
49
+ res = YAML.safe_load(stdout)
50
+ end
51
+
52
+ unless res.is_a?(Hash)
53
+ raise PluginError.new(%(invalid results from "#{File.basename(@script)}", must be YAML or JSON string),
54
+ plugin: @filename)
55
+ end
56
+
57
+ %w[url title link_text].each do |key|
58
+ unless res.key?(key)
59
+ raise PluginError.new(%("#{File.basename(@script)}" output missing key "#{key}"), plugin: @filename)
60
+ end
61
+ end
62
+
63
+ [res['url'], res['title'], res['link_text']]
64
+ end
65
+ end
66
+
67
+ SL::Searches.register @name, :search, self
68
+ end
69
+
70
+ def find_script(script)
71
+ return script if File.exist?(File.expand_path(script))
72
+
73
+ base = File.expand_path('~/.config/searchlink/plugins')
74
+ first = File.join(base, script)
75
+ return first if File.exist?(first)
76
+
77
+ base = File.expand_path('~/.config/searchlink')
78
+ second = File.join(base, script)
79
+ return second if File.exist?(second)
80
+
81
+ raise PluginError.new(%(Script plugin script "#{script}" not found), plugin: @filename)
82
+ end
83
+ end
84
+ end
@@ -31,14 +31,25 @@ module SL
31
31
  ## @return [Array] Single bookmark, [url, title, date]
32
32
  ##
33
33
  def search_brave_history(term)
34
- # Google history
35
- history_file = File.expand_path('~/Library/Application Support/BraveSoftware/Brave-Browser/Default/History')
36
- if File.exist?(history_file)
37
- SL.notify('Searching Brave History', term)
38
- search_chromium_history(history_file, term)
39
- else
40
- false
34
+ base = File.expand_path('~/Library/Application Support/BraveSoftware/Brave-Browser/')
35
+ profiles = Dir.glob('**/History', base: base)
36
+ profiles.delete_if { |p| p =~ /^Snapshots/ }
37
+ profiles.map! { |f| File.join(base, f) }
38
+
39
+ res = false
40
+
41
+ profiles.each do |bookmarks|
42
+ next unless File.exist?(bookmarks)
43
+
44
+ profile = bookmarks.match(%r{Browser/([^/]+)/})[1]
45
+
46
+ SL.notify("Searching Brave History for profile #{profile}", term)
47
+ res = search_chromium_history(bookmarks, term)
48
+
49
+ break if res
41
50
  end
51
+
52
+ res
42
53
  end
43
54
 
44
55
  ## Search Edge history
@@ -66,9 +77,7 @@ module SL
66
77
  break if res
67
78
  end
68
79
 
69
- return res if res
70
-
71
- false
80
+ res
72
81
  end
73
82
 
74
83
  ## Search Chrome history
@@ -99,9 +108,7 @@ module SL
99
108
  break if res
100
109
  end
101
110
 
102
- return res if res
103
-
104
- false
111
+ res
105
112
  end
106
113
 
107
114
  ##
@@ -172,7 +179,7 @@ module SL
172
179
 
173
180
  if File.exist?(bookmarks_file)
174
181
  SL.notify('Searching Arc Bookmarks', term)
175
- return search_json_bookmarks(bookmarks_file, term)
182
+ return search_arc_json(bookmarks_file, term)
176
183
  end
177
184
 
178
185
  false
@@ -204,9 +211,7 @@ module SL
204
211
  break if res
205
212
  end
206
213
 
207
- return res if res
208
-
209
- false
214
+ res
210
215
  end
211
216
 
212
217
  ##
@@ -225,18 +230,16 @@ module SL
225
230
  res = false
226
231
 
227
232
  profiles.each do |bookmarks|
228
- if File.exist?(bookmarks)
229
- profile = bookmarks.match(%r{Edge/([^/]+)/})[1]
233
+ next unless File.exist?(bookmarks)
230
234
 
231
- SL.notify("Searching Edge Bookmarks for profile #{profile}", term)
232
- res = search_chromium_bookmarks(bookmarks, term)
233
- break if res
234
- end
235
- end
235
+ profile = bookmarks.match(%r{Edge/([^/]+)/})[1]
236
236
 
237
- return res if res
237
+ SL.notify("Searching Edge Bookmarks for profile #{profile}", term)
238
+ res = search_chromium_bookmarks(bookmarks, term)
239
+ break if res
240
+ end
238
241
 
239
- false
242
+ res
240
243
  end
241
244
 
242
245
  ##
@@ -264,9 +267,7 @@ module SL
264
267
  end
265
268
  end
266
269
 
267
- return res if res
268
-
269
- false
270
+ res
270
271
  end
271
272
 
272
273
  ##
@@ -277,7 +278,7 @@ module SL
277
278
  ##
278
279
  ## @return [Array] single bookmark [url, title, date]
279
280
  ##
280
- def search_json_bookmarks(bookmarks_file, term)
281
+ def search_arc_json(bookmarks_file, term)
281
282
  arc_bookmarks = JSON.parse(IO.read(bookmarks_file))
282
283
 
283
284
  exact_match = false
@@ -341,7 +342,7 @@ module SL
341
342
 
342
343
  return false if bookmarks.empty?
343
344
 
344
- lastest_bookmark = bookmarks.max_by { |u| [u[:created], u[:active]] }
345
+ lastest_bookmark = bookmarks.min_by { |u| u[:created] }
345
346
 
346
347
  return [lastest_bookmark[:url], lastest_bookmark[:title], lastest_bookmark[:date]]
347
348
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SL
4
+ # Searches and plugin registration
4
5
  module Searches
5
6
  class << self
6
7
  def plugins
@@ -25,7 +26,7 @@ module SL
25
26
 
26
27
  def description_for_search(search_type)
27
28
  description = "#{search_type} search"
28
- plugins[:search].each do |_, plugin|
29
+ plugins[:search].each_value do |plugin|
29
30
  search = plugin[:searches].select { |s| s[0].is_a?(Array) ? s[0].include?(search_type) : s[0] == search_type }
30
31
  unless search.empty?
31
32
  description = search[0][1]
@@ -50,7 +51,11 @@ module SL
50
51
  '<tbody>']
51
52
 
52
53
  searches.each do |s|
53
- out << "<tr><td><code>!#{s[0].is_a?(Array) ? "#{s[0][0]} (#{s[0][1..-1].join(',')})" : s[0]}</code></td><td>#{s[1]}</td></tr>"
54
+ out << "<tr>
55
+ <td>
56
+ <code>!#{s[0].is_a?(Array) ? "#{s[0][0]} (#{s[0][1..-1].join(',')})" : s[0]}
57
+ </code>
58
+ </td><td>#{s[1]}</td></tr>"
54
59
  end
55
60
  out.concat(['</tbody>', '</table>']).join("\n")
56
61
  end
@@ -62,7 +67,7 @@ module SL
62
67
  #
63
68
  def available_searches
64
69
  searches = []
65
- plugins[:search].each { |_, plugin| searches.concat(plugin[:searches].delete_if { |s| s[1].nil? }) }
70
+ plugins[:search].each_value { |plugin| searches.concat(plugin[:searches].delete_if { |s| s[1].nil? }) }
66
71
  out = []
67
72
  searches.each do |s|
68
73
  out += "!#{s[0].is_a?(Array) ? "#{s[0][0]} (#{s[0][1..-1].join(',')})" : s[0]}#{s[0].spacer}#{s[1]}"
@@ -77,8 +82,8 @@ module SL
77
82
 
78
83
  def all_possible_searches
79
84
  searches = []
80
- plugins[:search].each { |_, plugin| plugin[:searches].each { |s| searches.push(s[0]) } }
81
- searches.concat(SL.config['custom_site_searches'].keys)
85
+ plugins[:search].each_value { |plugin| plugin[:searches].each { |s| searches.push(s[0]) } }
86
+ searches.concat(SL.config['custom_site_searches'].keys.sort)
82
87
  end
83
88
 
84
89
  def did_you_mean(term)
@@ -101,11 +106,11 @@ module SL
101
106
  end
102
107
 
103
108
  def register_plugin(title, type, klass)
104
- raise StandardError, "Plugin #{title} has no settings method" unless klass.respond_to? :settings
109
+ raise PluginError.new("Plugin has no settings method", plugin: title) unless klass.respond_to? :settings
105
110
 
106
111
  settings = klass.settings
107
112
 
108
- raise StandardError, "Plugin #{title} has no search method" unless klass.respond_to? :search
113
+ raise PluginError.new("Plugin has no search method", plugin: title) unless klass.respond_to? :search
109
114
 
110
115
  plugins[type] ||= {}
111
116
  plugins[type][title] = {
@@ -123,6 +128,8 @@ module SL
123
128
  Dir.glob(File.join(plugins_folder, '**/*.rb')).sort.each do |plugin|
124
129
  require plugin
125
130
  end
131
+
132
+ load_custom_scripts(plugins_folder)
126
133
  end
127
134
 
128
135
  return unless File.directory?(new_plugins_folder)
@@ -130,6 +137,24 @@ module SL
130
137
  Dir.glob(File.join(new_plugins_folder, '**/*.rb')).sort.each do |plugin|
131
138
  require plugin
132
139
  end
140
+
141
+ load_custom_scripts(new_plugins_folder)
142
+ end
143
+
144
+ def load_custom_scripts(plugins_folder)
145
+ Dir.glob(File.join(plugins_folder, '**/*.{json,yml,yaml}')).each do |file|
146
+ ext = File.extname(file).sub(/^\./, '')
147
+ config = IO.read(file)
148
+ cfg = case ext
149
+ when /^y/i
150
+ YAML.safe_load(config)
151
+ else
152
+ JSON.parse(config)
153
+ end
154
+ cfg['filename'] = File.basename(file)
155
+ cfg['path'] = file.shorten_path
156
+ SL::ScriptSearch.new(cfg)
157
+ end
133
158
  end
134
159
 
135
160
  def do_search(search_type, search_terms, link_text, timeout: SL.config['timeout'])
@@ -5,14 +5,14 @@ module SL
5
5
  class SemVer
6
6
  attr_accessor :maj, :min, :patch, :pre
7
7
 
8
- # Initialize a Semantic Version object
9
- #
10
- # @param version_string [String] a semantic version number
11
- #
12
- # @return [SemVer] SemVer object
13
- #
8
+ ## Initialize a Semantic Version object
9
+ ##
10
+ ## @param version_string [String] a semantic version number
11
+ ##
12
+ ## @return [SemVer] SemVer object
13
+ ##
14
14
  def initialize(version_string)
15
- raise "Invalid semantic version number: #{version_string}" unless version_string.valid_version?
15
+ raise VersionError.new("Invalid semantic version number: #{version_string}") unless version_string.valid_version?
16
16
 
17
17
  @maj, @min, @patch = version_string.split(/\./)
18
18
  @pre = nil
@@ -488,5 +488,13 @@ module SL
488
488
  def code_indent
489
489
  split(/\n/).map { |l| " #{l}" }.join("\n")
490
490
  end
491
+
492
+ ##
493
+ ## Shorten path by adding ~ for home directory
494
+ ##
495
+ def shorten_path
496
+ home_directory = ENV['HOME']
497
+ sub(home_directory, '~')
498
+ end
491
499
  end
492
500
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SL
4
- VERSION = '2.3.69'
4
+ VERSION = '2.3.70'
5
5
  end
6
6
 
7
7
  # Main module
data/lib/searchlink.rb CHANGED
@@ -13,10 +13,14 @@ require 'zlib'
13
13
  require 'time'
14
14
  require 'json'
15
15
  require 'erb'
16
+ require 'english'
16
17
 
17
18
  # import
18
19
  require 'tokens' if File.exist?('lib/tokens.rb')
19
20
 
21
+ # import
22
+ require 'searchlink/exceptions'
23
+
20
24
  # import
21
25
  require 'searchlink/number'
22
26
 
@@ -65,4 +69,7 @@ require 'searchlink/output'
65
69
  # import
66
70
  require 'searchlink/which'
67
71
 
72
+ # import
73
+ require 'searchlink/script_plugin'
74
+
68
75
  module Secrets; end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: searchlink
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.69
4
+ version: 2.3.70
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-08-09 00:00:00.000000000 Z
11
+ date: 2024-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -235,11 +235,13 @@ files:
235
235
  - lib/searchlink/curl.rb
236
236
  - lib/searchlink/curl/html.rb
237
237
  - lib/searchlink/curl/json.rb
238
+ - lib/searchlink/exceptions.rb
238
239
  - lib/searchlink/help.rb
239
240
  - lib/searchlink/number.rb
240
241
  - lib/searchlink/output.rb
241
242
  - lib/searchlink/parse.rb
242
243
  - lib/searchlink/plist.rb
244
+ - lib/searchlink/script_plugin.rb
243
245
  - lib/searchlink/search.rb
244
246
  - lib/searchlink/searches.rb
245
247
  - lib/searchlink/searches/amazon.rb