searchlink 2.3.69 → 2.3.70

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 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