searchlink 2.3.86 → 2.3.88

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.
@@ -4,7 +4,7 @@ module SL
4
4
  class SearchLink
5
5
  # Confirm a URL with a popup if requested
6
6
  def confirmed?(url)
7
- return true unless SL.config["confirm"]
7
+ return true if !SL.config["confirm"] || NO_CONFIRM
8
8
 
9
9
  SL::Shortener.confirm?(url)
10
10
  end
@@ -19,36 +19,23 @@ module SL
19
19
 
20
20
  parse_commands(input)
21
21
 
22
- SL.config["inline"] = true if input.scan(/\]\(/).length == 1 && input.split(/\n/).length == 1
22
+ SL.config["inline"] = true if input.scan("](").length == 1 && input.split("\n").length == 1
23
23
  SL.errors = {}
24
24
  SL.report = []
25
25
  SL.shortener = :none
26
26
 
27
27
  # Check for new version
28
28
  latest_version = SL.new_version?
29
- if latest_version
30
- SL.add_output("<!-- v#{latest_version} available, run SearchLink on the word 'update' to install. -->")
31
- end
29
+ SL.add_output("<!-- v#{latest_version} available, run SearchLink on the word 'update' to install. -->") if latest_version
32
30
 
33
- @links = {}
34
31
  SL.footer = []
35
- counter_links = 0
36
- counter_errors = 0
37
32
 
38
33
  input.sub!(/\n?<!-- Report:.*?-->\n?/m, "")
39
34
  input.sub!(/\n?<!-- Errors:.*?-->\n?/m, "")
40
35
 
41
- input.scan(/\[(.*?)\]:\s+(.*?)\n/).each { |match| @links[match[1].strip] = match[0] }
36
+ @links = input.scan_links
42
37
 
43
- @prefix = if SL.config["prefix_random"]
44
- if input =~ /\[(\d{4}-)\d+\]: \S+/
45
- Regexp.last_match(1)
46
- else
47
- format("%04d-", rand(9999))
48
- end
49
- else
50
- ""
51
- end
38
+ @prefix = determine_prefix(input)
52
39
 
53
40
  @highest_marker = 0
54
41
  input.scan(/^\s{,3}\[(?:#{@prefix})?(\d+)\]: /).each do
@@ -63,30 +50,33 @@ module SL
63
50
  end
64
51
 
65
52
  if SL.config["complete_bare"]
53
+ # match all URLs not preceded by a ( or : or <, and are followed by a space or newline
66
54
  rx = %r{(?ix-m)(?<!\(|:\s|<)(?:
67
55
  (?:https?://)(?:[\da-z.-]+)\.(?:[a-z.]{2,6})
68
- (?:[/\w\d.\-()_/+=?&%]*?(?=[\s\n]|$))
56
+ (?:[/\w\d.\-()_+=?&%]*?(?=[\s\n]|$))
69
57
  )}
58
+ # replace with [%](url)
70
59
  input.gsub!(rx) do
71
60
  url_match = Regexp.last_match
72
61
  url_match.pre_match =~ /!\S+ +$/ ? url_match[0] : "[%](#{url_match[0]})"
73
62
  end
74
63
  end
75
64
 
65
+ # Handle multi-line links in the form of [\ntext\n](url)
76
66
  if input =~ /\[\n(.*?\n)+\]\((.*?)?\)/
77
67
  input.gsub!(/\[\n(((\s*(?:[-+*]|\d+\.)?\s+)*(!\S+ +)?(.*?))\n)+\]\((!\S+.*?)?\)/) do
78
68
  m = Regexp.last_match
79
- lines = m[0].split(/\n/)
69
+ lines = m[0].split("\n")
80
70
  lines = lines[1..-2]
81
71
  lines.map do |l|
82
72
  el_rx = /(\s*(?:[-+*]|\d+\.)?\s+)?(!\S+ )?(\w.*?)$/
83
73
  if l =~ el_rx
84
74
  els = l.match(el_rx)
85
75
  search = if els[2]
86
- els[2].strip
87
- else
88
- m[6] || "!g"
89
- end
76
+ els[2].strip
77
+ else
78
+ m[6] || "!g"
79
+ end
90
80
  "#{els[1]}[#{els[3].strip}](#{search})"
91
81
  else
92
82
  l
@@ -97,439 +87,470 @@ module SL
97
87
 
98
88
  # Handle links in the form of [text](url) or [text](url "title")
99
89
  if input =~ /\[(.*?)\]\((.*?)\)/
100
- lines = input.split(/\n/)
101
- out = []
102
-
103
- total_links = input.scan(/\[(.*?)\]\((.*?)\)/).length
104
- in_code_block = false
105
- line_difference = 0
106
- lines.each_with_index do |line, num|
107
- SL.line_num = num - line_difference
108
- @cursor_difference = 0
109
- # ignore links in code blocks
110
- if line =~ /^(( {4,}|\t+)[^*+-])/
111
- out.push(line)
112
- next
113
- end
114
- if line =~ /^\s*[~`]{3,}/
115
- if in_code_block
116
- in_code_block = false
117
- out.push(line)
118
- next
119
- else
120
- in_code_block = true
121
- end
122
- end
123
- if in_code_block
124
- out.push(line)
125
- next
126
- end
90
+ parse_brackets(input)
91
+ else # Assume single line input
92
+ parse_single_line(input)
93
+ end
94
+ end
127
95
 
128
- @delete_line = false
96
+ private
129
97
 
130
- @search_count = 0
98
+ # Determine the prefix for footnotes
99
+ def determine_prefix(input)
100
+ if SL.config["prefix_random"]
101
+ if input =~ /\[(\d{4}-)\d+\]: \S+/
102
+ Regexp.last_match(1)
103
+ else
104
+ format("%04d-", rand(9999))
105
+ end
106
+ else
107
+ ""
108
+ end
109
+ end
131
110
 
132
- line.gsub!(/\[(.*?)\]\((.*?)\)/) do |match|
133
- this_match = Regexp.last_match
111
+ def parse_single_line(input)
112
+ link_only = false
113
+ SL.clipboard = false
134
114
 
135
- SL.match_column = this_match.begin(0) - @cursor_difference
136
- @match_string = this_match.to_s
137
- SL.match_length = @match_string.length
138
- match_before = this_match.pre_match
115
+ res = parse_arguments(input.strip!).strip
116
+ input = res.nil? ? input.strip : res
117
+ query, input = input.extract_query({})
139
118
 
140
- invalid_search = false
141
- @ref_title = false
119
+ # if the end of input contain "^", copy to clipboard instead of STDOUT
120
+ SL.clipboard = true if input =~ /\^[!~:\s]*$/
142
121
 
143
- if match_before.scan(/(^|[^\\])`/).length.odd?
144
- SL.add_report("Match '#{@match_string}' within an inline code block")
145
- invalid_search = true
146
- end
122
+ # if the end of input contains "!!", only print the url
123
+ link_only = true if input =~ /!![\^~:\s]*$/
147
124
 
148
- counter_links += 1
149
- unless SILENT
150
- $stderr.print("\033[0K\rProcessed: #{counter_links} of #{total_links}, #{counter_errors} errors. ")
151
- end
125
+ reference_link = input =~ /:([!\^~\s]*)$/
152
126
 
153
- @link_text = this_match[1] || ""
154
- link_info = parse_arguments(this_match[2].strip).strip || ""
155
- query, link_info = link_info.extract_query({})
127
+ # if end of input contains ~, pull url from clipboard
128
+ if input =~ /~[:\^!\s]*$/
129
+ input.sub!(/[:!\^\s~]*$/, "")
130
+ clipboard = `__CF_USER_TEXT_ENCODING=$UID:0x8000100:0x8000100 pbpaste`.strip
131
+ if SL::URL.url?(clipboard)
132
+ type = reference_link ? :ref_title : :inline
133
+ print SL.make_link(type, input.strip, clipboard)
134
+ else
135
+ print SL.originput
136
+ end
137
+ Process.exit
138
+ end
156
139
 
157
- if @link_text.strip == "" && link_info =~ /".*?"/
158
- link_info.gsub!(/"(.*?)"/) do
159
- m = Regexp.last_match
160
- @link_text = m[1] if @link_text == ""
161
- m[0]
162
- end
163
- end
140
+ input.sub!(/[:!\^\s~]*$/, "")
164
141
 
165
- link_info.gsub!(/<(.*?)>/) do
166
- %(%22#{Regexp.last_match(1)}%22)
167
- end
142
+ ## Maybe if input is just a URL, convert it to a link
143
+ ## using hostname as text without doing search
144
+ if SL::URL.only_url?(input.strip)
145
+ type = reference_link ? :ref_title : :inline
146
+ @url, title = SL::URL.url_to_link(input.strip, type)
147
+ print SL.make_link(type, title, @url, title: false, force_title: false)
148
+ Process.exit
149
+ end
168
150
 
169
- if link_info.strip =~ /:$/ && line.strip == match
170
- @ref_title = true
171
- link_info.sub!(/\s*:\s*$/, "")
172
- end
151
+ # check for additional search terms in parenthesis
152
+ additional_terms = ""
153
+ if input =~ /\((.*?)\)/
154
+ additional_terms = " #{Regexp.last_match(1).strip}"
155
+ input.sub!(/\(.*?\)/, "")
156
+ end
173
157
 
174
- if @link_text.empty? && link_info.sub(/^[!\^]\S+/, "").strip.empty?
175
- SL.add_error("No input", match)
176
- counter_errors += 1
177
- invalid_search = true
178
- end
158
+ # Maybe detect "search + addition terms" and remove additional terms from link text?
159
+ # if input =~ /\+(.+?)$/
160
+ # additional_terms = "#{additional_terms} #{Regexp.last_match(1).strip}"
161
+ # input.sub!(/\+.*?$/, '').strip!
162
+ # end
179
163
 
180
- if link_info =~ /^!(\S+)/
181
- search_type = Regexp.last_match(1).extract_shortener
182
- unless SL::Searches.valid_search?(search_type) || search_type =~ /^(\S+\.)+\S+$/
183
- SL.add_error("Invalid search#{SL::Searches.did_you_mean(search_type)}", match)
184
- invalid_search = true
185
- end
186
- end
164
+ @link_text = false
187
165
 
188
- if invalid_search
189
- match
190
- elsif link_info =~ /^\^(.+)/
191
- m = Regexp.last_match
192
- create_footnote(m)
193
- # Handle [](URL) and [%](URL), filling in title
194
- elsif (@link_text == "" || @link_text == "%") && SL::URL.url?(link_info)
195
- add_title(link_info)
196
- elsif (@link_text == "" && link_info == "") || SL::URL.url?(link_info)
197
- SL.add_error("Invalid search", match) unless SL::URL.url?(link_info)
198
- match
199
- else
200
- link_info = @link_text if !@link_text.empty? && link_info == ""
166
+ if input =~ /"(.*?)"/
167
+ @link_text = Regexp.last_match(1)
168
+ input.gsub!(/"(.*?)"/, '\1')
169
+ end
201
170
 
202
- search_type = ""
203
- search_terms = ""
204
- link_only = false
205
- SL.clipboard = false
206
- SL.titleize = SL.config["empty_uses_page_title"]
171
+ # remove quotes from terms, just in case
172
+ # input.sub!(/^(!\S+)?\s*(["'])(.*?)\2([\!\^]+)?$/, "\\1 \\3\\4")
207
173
 
208
- if link_info =~ /^(?:[!\^](\S+))\s*(.*)$/
209
- m = Regexp.last_match
174
+ case input
175
+ when /^!(\S+)\s+(.*)$/
176
+ type = Regexp.last_match(1).extract_shortener
177
+ link_info = Regexp.last_match(2).strip
210
178
 
211
- search_type = if m[1].nil?
212
- SL::GoogleSearch.api_key? ? "gg" : "g"
213
- else
214
- m[1]
215
- end
179
+ @link_text ||= link_info
180
+ terms = link_info + additional_terms
181
+ terms.strip!
216
182
 
217
- search_type.extract_shortener!
183
+ if SL::Searches.valid_search?(type) || type =~ /^(\S+\.)+\S+$/
184
+ if type && terms && !terms.empty?
185
+ # Iterate through custom searches for a match, perform search if matched
186
+ type, terms = custom_search(type, terms)
187
+ end
218
188
 
219
- search_terms = m[2].gsub(/(^["']|["']$)/, "")
220
- search_terms.strip!
189
+ # if contains TLD, use site-specific search
190
+ if type =~ /^(\S+\.)+\S+$/
191
+ terms = "site:#{type} #{terms}"
192
+ type = SL::GoogleSearch.api_key? ? "gg" : "g"
193
+ end
194
+ @search_count ||= 0
195
+ @search_count += 1
221
196
 
222
- # if the link text is just '%' replace with title regardless of config settings
223
- if @link_text == "%" && search_terms && !search_terms.empty?
224
- SL.titleize = true
225
- @link_text = ""
226
- end
197
+ SL.add_query(query) if query
198
+ @url, title, @link_text = do_search(type, terms, @link_text, @search_count)
199
+ else
200
+ SL.add_error("Invalid search#{SL::Searches.did_you_mean(type)}", input)
201
+ SL.error_count
202
+ end
203
+ # Social handle expansion
204
+ when /^([tfilm])?@(\S+)\s*$/
205
+ type = Regexp.last_match(1)
206
+ type ||= if Regexp.last_match(2) =~ /[a-z0-9_]@[a-z0-9_.]+/i
207
+ "m"
208
+ else
209
+ "t"
210
+ end
211
+ @link_text = input.sub(/^[tfilm]/, "")
212
+ SL.add_query(query) if query
213
+ @url, title = SL::SocialSearch.social_handle(type, @link_text)
214
+ @link_text = title
215
+ else
216
+ SL.add_query(query) if query
217
+ @link_text ||= input
218
+ @url, title, @link_text = SL.ddg(input, @link_text)
219
+ end
227
220
 
228
- search_terms = @link_text if search_terms == ""
221
+ if @url
222
+ res = confirmed?(@url)
223
+ if res
224
+ if res.is_a?(String) && SL::URL.url?(res)
225
+ @url = res
226
+ title = SL::URL.title(@url) if SL.titleize && title == ""
227
+ end
229
228
 
230
- # if the input starts with a +, append it to the link text as the search terms
231
- if search_terms.strip =~ /^\+[^+]/
232
- search_terms = "#{@link_text} #{search_terms.strip.sub(/^\+\s*/, "")}"
233
- end
229
+ if type =~ /sp(ell)?/
230
+ SL.add_output(@url)
231
+ elsif link_only
232
+ SL.add_output(@url)
233
+ elsif @url == "embed"
234
+ SL.add_output(title)
235
+ else
236
+ type = reference_link ? :ref_title : :inline
234
237
 
235
- # if the end of input contain "^", copy to clipboard instead of STDOUT
236
- SL.clipboard = true if search_terms =~ /(!!)?\^(!!)?$/
238
+ SL.add_output SL.make_link(type, @link_text, @url, title: title, force_title: false)
239
+ SL.print_errors
240
+ end
241
+ else
242
+ SL.add_error("Canceled", "User canceled result #{@url}")
243
+ SL.add_output SL.originput.chomp
244
+ SL.print_errors
245
+ end
246
+ else
247
+ SL.add_error("No results", title)
248
+ SL.add_output SL.originput.chomp
249
+ SL.print_errors
250
+ end
237
251
 
238
- # if the end of input contains "!!", only print the url
239
- link_only = true if search_terms =~ /!!\^?$/
252
+ return unless SL.clipboard
240
253
 
241
- search_terms = search_terms.sub(/(!!)?\^?(!!)?$/, "")
254
+ if SL.output == SL.originput
255
+ warn "No results found"
256
+ else
257
+ `echo #{Shellwords.escape(SL.output.join(""))}|tr -d "\n"|pbcopy`
258
+ warn "Results in clipboard"
259
+ end
260
+ end
242
261
 
243
- if search_type =~ /^(\S+\.)+\S+$/
244
- search_type = "g"
245
- search_terms = "site:#{m[1]} #{search_terms}"
246
- end
247
- elsif link_info =~ /^!/
248
- search_word = link_info.match(/^!(\S+)/)
249
- st = search_word[1].extract_shortener
250
- if search_word && SL::Searches.valid_search?(st)
251
- search_type = st unless search_word.nil?
252
- search_terms = @link_text
253
- elsif search_word && st =~ /^(\S+\.)+\S+$/
254
- search_type = SL::GoogleSearch.api_key? ? "gg" : "g"
255
- search_terms = "site:#{search_word[1]} #{@link_text}"
256
- else
257
- SL.add_error("Invalid search#{SL::Searches.did_you_mean(st)}", match)
258
- search_type = false
259
- search_terms = false
260
- end
261
- elsif @link_text && !@link_text.empty? && (!link_info || link_info.empty?)
262
- search_type = SL::GoogleSearch.api_key? ? "gg" : "g"
263
- search_terms = @link_text
264
- elsif link_info && !link_info.empty?
265
- search_type = SL::GoogleSearch.api_key? ? "gg" : "g"
266
- search_terms = link_info
267
- else
268
- SL.add_error("Invalid search", match)
269
- search_type = false
270
- search_terms = false
271
- end
262
+ def parse_brackets(input)
263
+ lines = input.split("\n")
264
+ out = []
272
265
 
273
- if search_type && !search_terms.empty?
274
- search_type, search_terms = custom_search(search_type, search_terms)
275
- end
266
+ total_links = 0
267
+ in_code_block = false
268
+ in_list = false
269
+ last_indent = 0
270
+ line_difference = 0
271
+ counter_links = 0
272
+ lines.each_with_index do |line, num|
273
+ SL.line_num = num - line_difference
274
+ @cursor_difference = 0
276
275
 
277
- SL.add_query(query) if query
276
+ if line =~ /^\s*[~`]{3,}/
277
+ if in_code_block
278
+ in_code_block = false
279
+ out.push(line)
280
+ next
281
+ else
282
+ in_code_block = true
283
+ end
284
+ elsif in_code_block
285
+ out.push(line)
286
+ next
287
+ elsif line.strip.empty?
288
+ out.push(line)
289
+ next
290
+ elsif line =~ /^\s*([-+*]|\d+\.)\s/
291
+ in_list = true
292
+ last_indent = line.indent_level
293
+ elsif in_list && line.indent_level > last_indent + 2
294
+ out.push(line)
295
+ next
296
+ elsif in_list && line.indent_level < last_indent && line !~ /^\s*([-+*]|\d+\.)\s/
297
+ in_list = false
298
+ elsif in_list && line.indent_level == last_indent + 1
299
+ # noop
300
+ else
301
+ in_list = false
302
+ # ignore links in code blocks
303
+ if line =~ /^(( {4,}|\t+)[^*+-])/
304
+ last_indent = line.indent_level
305
+ out.push(line)
306
+ next
307
+ end
308
+ last_indent = line.indent_level
309
+ end
278
310
 
279
- if (search_type && search_terms) || @url
280
- # warn "Searching #{search_type} for #{search_terms}"
311
+ total_links += line.count_links
281
312
 
282
- @search_count += 1
283
- @url, title, @link_text = do_search(search_type, search_terms, @link_text, @search_count)
313
+ @delete_line = false
284
314
 
285
- if (@link_text == "" || @link_text == "%") && @url
286
- if title
287
- @link_text = title
288
- else
289
- add_title(@url)
290
- end
291
- end
315
+ @search_count = 0
292
316
 
293
- if @url
294
- res = confirmed?(@url)
295
- if !res
296
- return match
297
- else
298
- @url = res if res.is_a?(String) && SL::URL.url?(res)
299
- end
317
+ line.gsub!(/\[(.*?)\]\((.*?)\)/) do |match|
318
+ this_match = Regexp.last_match
300
319
 
301
- title = SL::URL.title(@url) if SL.titleize && title == ""
302
-
303
- @link_text = title if @link_text == "" && title
304
- force_title = search_type =~ /def/ ? true : false
305
-
306
- if link_only || search_type =~ /sp(ell)?/ || @url == "embed"
307
- @url = title if @url == "embed"
308
- @cursor_difference += SL.match_length - @url.length
309
- SL.match_length = @url.length
310
- SL.add_report("#{@match_string} => #{@url}")
311
- @url
312
- elsif @ref_title
313
- unless @links.key? @url
314
- @links[@url] = @link_text
315
- SL.add_footer SL.make_link(:ref_title, @link_text, @url, title: title, force_title: force_title)
316
- end
317
- @delete_line = true
318
- elsif SL.config["inline"]
319
- res = SL.make_link(:inline, @link_text, @url, title: title, force_title: force_title)
320
- @cursor_difference += SL.match_length - res.length
321
- SL.match_length = res.length
322
- SL.add_report("#{@match_string} => #{@url}")
323
- res
324
- else
325
- unless @links.key? @url
326
- @highest_marker += 1
327
- @links[@url] = format("%<pre>s%<m>04d", pre: @prefix, m: @highest_marker)
328
- SL.add_footer SL.make_link(:ref_title, @links[@url], @url, title: title, force_title: force_title)
329
- end
330
-
331
- type = SL.config["inline"] ? :inline : :ref_link
332
- res = SL.make_link(type, @link_text, @links[@url], title: false, force_title: force_title)
333
- @cursor_difference += SL.match_length - res.length
334
- SL.match_length = res.length
335
- SL.add_report("#{@match_string} => #{@url}")
336
- res
337
- end
338
- else
339
- SL.add_error("No results", "#{search_terms} (#{@match_string})")
340
- counter_errors += 1
341
- match
342
- end
343
- else
344
- SL.add_error("Invalid search", match)
345
- counter_errors += 1
346
- match
347
- end
348
- end
349
- end
350
- line_difference += 1 if @delete_line
351
- out.push(line) unless @delete_line
352
- @delete_line = false
353
- end
354
- warn "\n" unless SILENT
320
+ SL.match_column = this_match.begin(0) - @cursor_difference
321
+ @match_string = this_match.to_s
322
+ SL.match_length = @match_string.length
323
+ match_before = this_match.pre_match
355
324
 
356
- input = out.delete_if { |l| l.strip =~ /^<!--DELETE-->$/ }.join("\n")
325
+ invalid_search = false
326
+ @ref_title = false
357
327
 
358
- if SL.config["inline"]
359
- SL.add_output "#{input}\n"
360
- SL.add_output "\n#{SL.print_footer}" unless SL.footer.empty?
361
- elsif SL.footer.empty?
362
- SL.add_output input
363
- else
364
- last_line = input.strip.split(/\n/)[-1]
365
- case last_line
366
- when /^\[.*?\]: http/
367
- SL.add_output "#{input.rstrip}\n"
368
- when /^\[\^.*?\]: /
369
- SL.add_output input.rstrip
370
- else
371
- SL.add_output "#{input}\n\n"
328
+ if match_before.scan(/(^|[^\\])`/).length.odd?
329
+ SL.add_report("Match '#{@match_string}' within an inline code block")
330
+ invalid_search = true
372
331
  end
373
- SL.add_output "#{SL.print_footer}\n\n"
374
- end
332
+ counter_links += 1
333
+ $stderr.print("\033[0K\rProcessed: #{counter_links} of #{total_links}, #{SL.error_count} errors. ") unless SILENT
375
334
 
376
- SL.line_num = nil
377
- SL.add_report("Processed: #{total_links} links, #{counter_errors} errors.")
378
- SL.print_report
379
- SL.print_errors
380
- else # Assume single line input
381
- link_only = false
382
- SL.clipboard = false
335
+ @link_text = this_match[1] || ""
336
+ link_info = parse_arguments(this_match[2].strip).strip || ""
337
+ query, link_info = link_info.extract_query({})
383
338
 
384
- res = parse_arguments(input.strip!).strip
385
- input = res.nil? ? input.strip : res
386
- query, input = input.extract_query({})
339
+ if @link_text.strip == "" && link_info =~ /".*?"/
340
+ link_info.gsub!(/"(.*?)"/) do
341
+ m = Regexp.last_match
342
+ @link_text = m[1] if @link_text == ""
343
+ m[0]
344
+ end
345
+ end
387
346
 
388
- # if the end of input contain "^", copy to clipboard instead of STDOUT
389
- SL.clipboard = true if input =~ /\^[!~:\s]*$/
347
+ link_info.gsub!(/<(.*?)>/) do
348
+ %(%22#{Regexp.last_match(1)}%22)
349
+ end
390
350
 
391
- # if the end of input contains "!!", only print the url
392
- link_only = true if input =~ /!![\^~:\s]*$/
351
+ if link_info.strip =~ /:$/ && line.strip == match
352
+ @ref_title = true
353
+ link_info.sub!(/\s*:\s*$/, "")
354
+ end
393
355
 
394
- reference_link = input =~ /:([!\^~\s]*)$/
356
+ if @link_text.empty? && link_info.sub(/^[!\^]\S+/, "").strip.empty?
357
+ SL.add_error("No input", match)
358
+ invalid_search = true
359
+ end
395
360
 
396
- # if end of input contains ~, pull url from clipboard
397
- if input =~ /~[:\^!\s]*$/
398
- input.sub!(/[:!\^\s~]*$/, "")
399
- clipboard = `__CF_USER_TEXT_ENCODING=$UID:0x8000100:0x8000100 pbpaste`.strip
400
- if SL::URL.url?(clipboard)
401
- type = reference_link ? :ref_title : :inline
402
- print SL.make_link(type, input.strip, clipboard)
403
- else
404
- print SL.originput
361
+ if link_info =~ /^!(\S+)/
362
+ search_type = Regexp.last_match(1).extract_shortener
363
+ unless SL::Searches.valid_search?(search_type) || search_type =~ /^(\S+\.)+\S+$/
364
+ SL.add_error("Invalid search#{SL::Searches.did_you_mean(search_type)}", match)
365
+ invalid_search = true
366
+ end
405
367
  end
406
- Process.exit
407
- end
408
368
 
409
- input.sub!(/[:!\^\s~]*$/, "")
369
+ if invalid_search
370
+ match
371
+ elsif link_info =~ /^\^(.+)/
372
+ m = Regexp.last_match
373
+ create_footnote(m)
374
+ # Handle [](URL) and [%](URL), filling in title
375
+ elsif (@link_text == "" || @link_text == "%") && SL::URL.url?(link_info)
376
+ add_title(link_info)
377
+ elsif (@link_text == "" && link_info == "") || SL::URL.url?(link_info)
378
+ SL.add_error("Invalid search", match) unless SL::URL.url?(link_info)
379
+ match
380
+ else
381
+ link_info = @link_text if !@link_text.empty? && link_info == ""
410
382
 
411
- ## Maybe if input is just a URL, convert it to a link
412
- ## using hostname as text without doing search
413
- if SL::URL.only_url?(input.strip)
414
- type = reference_link ? :ref_title : :inline
415
- @url, title = SL::URL.url_to_link(input.strip, type)
416
- print SL.make_link(type, title, @url, title: false, force_title: false)
417
- Process.exit
418
- end
383
+ search_type = ""
384
+ search_terms = ""
385
+ link_only = false
386
+ SL.clipboard = false
387
+ SL.titleize = SL.config["empty_uses_page_title"]
419
388
 
420
- # check for additional search terms in parenthesis
421
- additional_terms = ""
422
- if input =~ /\((.*?)\)/
423
- additional_terms = " #{Regexp.last_match(1).strip}"
424
- input.sub!(/\(.*?\)/, "")
425
- end
389
+ if link_info =~ /^(?:[!\^](\S+))\s*(.*)$/
390
+ m = Regexp.last_match
426
391
 
427
- # Maybe detect "search + addition terms" and remove additional terms from link text?
428
- # if input =~ /\+(.+?)$/
429
- # additional_terms = "#{additional_terms} #{Regexp.last_match(1).strip}"
430
- # input.sub!(/\+.*?$/, '').strip!
431
- # end
392
+ search_type = if m[1].nil?
393
+ SL::GoogleSearch.api_key? ? "gg" : "g"
394
+ else
395
+ m[1]
396
+ end
432
397
 
433
- @link_text = false
398
+ search_type.extract_shortener!
434
399
 
435
- if input =~ /"(.*?)"/
436
- @link_text = Regexp.last_match(1)
437
- input.gsub!(/"(.*?)"/, '\1')
438
- end
400
+ search_terms = m[2].gsub(/(^["']|["']$)/, "")
401
+ search_terms.strip!
439
402
 
440
- # remove quotes from terms, just in case
441
- # input.sub!(/^(!\S+)?\s*(["'])(.*?)\2([\!\^]+)?$/, "\\1 \\3\\4")
403
+ # if the link text is just '%' replace with title regardless of config settings
404
+ if @link_text == "%" && search_terms && !search_terms.empty?
405
+ SL.titleize = true
406
+ @link_text = ""
407
+ end
442
408
 
443
- case input
444
- when /^!(\S+)\s+(.*)$/
445
- type = Regexp.last_match(1).extract_shortener
446
- link_info = Regexp.last_match(2).strip
409
+ search_terms = @link_text if search_terms == ""
447
410
 
448
- @link_text ||= link_info
449
- terms = link_info + additional_terms
450
- terms.strip!
411
+ # if the input starts with a +, append it to the link text as the search terms
412
+ search_terms = "#{@link_text} #{search_terms.strip.sub(/^\+\s*/, '')}" if search_terms.strip =~ /^\+[^+]/
451
413
 
452
- if SL::Searches.valid_search?(type) || type =~ /^(\S+\.)+\S+$/
453
- if type && terms && !terms.empty?
454
- # Iterate through custom searches for a match, perform search if matched
455
- type, terms = custom_search(type, terms)
456
- end
414
+ # if the end of input contain "^", copy to clipboard instead of STDOUT
415
+ SL.clipboard = true if search_terms =~ /(!!)?\^(!!)?$/
457
416
 
458
- # if contains TLD, use site-specific search
459
- if type =~ /^(\S+\.)+\S+$/
460
- terms = "site:#{type} #{terms}"
461
- type = SL::GoogleSearch.api_key? ? "gg" : "g"
462
- end
463
- @search_count ||= 0
464
- @search_count += 1
417
+ # if the end of input contains "!!", only print the url
418
+ link_only = true if search_terms =~ /!!\^?$/
465
419
 
466
- SL.add_query(query) if query
467
- @url, title, @link_text = do_search(type, terms, @link_text, @search_count)
468
- else
469
- SL.add_error("Invalid search#{SL::Searches.did_you_mean(type)}", input)
470
- counter_errors += 1
471
- end
472
- # Social handle expansion
473
- when /^([tfilm])?@(\S+)\s*$/
474
- type = Regexp.last_match(1)
475
- type ||= if Regexp.last_match(2) =~ /[a-z0-9_]@[a-z0-9_.]+/i
476
- "m"
420
+ search_terms = search_terms.sub(/(!!)?\^?(!!)?$/, "")
421
+
422
+ if search_type =~ /^(\S+\.)+\S+$/
423
+ search_type = "g"
424
+ search_terms = "site:#{m[1]} #{search_terms}"
425
+ end
426
+ elsif link_info =~ /^!/
427
+ search_word = link_info.match(/^!(\S+)/)
428
+ st = search_word[1].extract_shortener
429
+ if search_word && SL::Searches.valid_search?(st)
430
+ search_type = st unless search_word.nil?
431
+ search_terms = @link_text
432
+ elsif search_word && st =~ /^(\S+\.)+\S+$/
433
+ search_type = SL::GoogleSearch.api_key? ? "gg" : "g"
434
+ search_terms = "site:#{search_word[1]} #{@link_text}"
435
+ else
436
+ SL.add_error("Invalid search#{SL::Searches.did_you_mean(st)}", match)
437
+ search_type = false
438
+ search_terms = false
439
+ end
440
+ elsif @link_text && !@link_text.empty? && (!link_info || link_info.empty?)
441
+ search_type = SL::GoogleSearch.api_key? ? "gg" : "g"
442
+ search_terms = @link_text
443
+ elsif link_info && !link_info.empty?
444
+ search_type = SL::GoogleSearch.api_key? ? "gg" : "g"
445
+ search_terms = link_info
477
446
  else
478
- "t"
447
+ SL.add_error("Invalid search", match)
448
+ search_type = false
449
+ search_terms = false
479
450
  end
480
- @link_text = input.sub(/^[tfilm]/, "")
481
- SL.add_query(query) if query
482
- @url, title = SL::SocialSearch.social_handle(type, @link_text)
483
- @link_text = title
484
- else
485
- SL.add_query(query) if query
486
- @link_text ||= input
487
- @url, title, @link_text = SL.ddg(input, @link_text)
488
- end
489
451
 
490
- if @url
491
- res = confirmed?(@url)
492
- if res
493
- if res.is_a?(String) && SL::URL.url?(res)
494
- @url = res
495
- title = SL::URL.title(@url) unless title == ""
496
- end
452
+ search_type, search_terms = custom_search(search_type, search_terms) if search_type && !search_terms.empty?
497
453
 
498
- if type =~ /sp(ell)?/
499
- SL.add_output(@url)
500
- elsif link_only
501
- SL.add_output(@url)
502
- elsif @url == "embed"
503
- SL.add_output(title)
504
- else
505
- type = reference_link ? :ref_title : :inline
454
+ SL.add_query(query) if query
455
+
456
+ if (search_type && search_terms) || @url
457
+ # warn "Searching #{search_type} for #{search_terms}"
458
+
459
+ @search_count += 1
460
+ @url, title, @link_text = do_search(search_type, search_terms, @link_text, @search_count)
461
+
462
+ if (@link_text == "" || @link_text == "%") && @url
463
+ if title
464
+ @link_text = title
465
+ else
466
+ add_title(@url)
467
+ end
468
+ end
469
+
470
+ if @url
471
+ res = confirmed?(@url)
472
+ return match unless res
473
+
474
+ @url = res if res.is_a?(String) && SL::URL.url?(res)
475
+
476
+ title = SL::URL.title(@url) if SL.titleize && (title.nil? || title.empty?)
477
+
478
+ @link_text = title if @link_text == "" && title
479
+ force_title = search_type =~ /def/ ? true : false
480
+
481
+ if link_only || search_type =~ /sp(ell)?/ || @url == "embed"
482
+ @url = title if @url == "embed"
483
+ @cursor_difference += SL.match_length - @url.length
484
+ SL.match_length = @url.length
485
+ SL.add_report("#{@match_string} => #{@url}")
486
+ @url
487
+ elsif @ref_title
488
+ unless @links.key? @url
489
+ @links[@url] = @link_text
490
+ SL.add_footer SL.make_link(:ref_title, @link_text, @url, title: title, force_title: force_title)
491
+ end
492
+ @delete_line = true
493
+ elsif SL.config["inline"]
494
+ res = SL.make_link(:inline, @link_text, @url, title: title, force_title: force_title)
495
+ @cursor_difference += SL.match_length - res.length
496
+ SL.match_length = res.length
497
+ SL.add_report("#{@match_string} => #{@url}")
498
+ res
499
+ else
500
+ unless @links.key? @url
501
+ @highest_marker += 1
502
+ @links[@url] = format("%<pre>s%<m>04d", pre: @prefix, m: @highest_marker)
503
+ SL.add_footer SL.make_link(:ref_title, @links[@url], @url, title: title, force_title: force_title)
504
+ end
506
505
 
507
- SL.add_output SL.make_link(type, @link_text, @url, title: title, force_title: false)
508
- SL.print_errors
506
+ type = SL.config["inline"] ? :inline : :ref_link
507
+ res = SL.make_link(type, @link_text, @links[@url], title: false, force_title: force_title)
508
+ @cursor_difference += SL.match_length - res.length
509
+ SL.match_length = res.length
510
+ SL.add_report("#{@match_string} => #{@url}")
511
+ res
512
+ end
513
+ else
514
+ SL.add_error("No results", "#{search_terms} (#{@match_string})")
515
+ match
516
+ end
517
+ else
518
+ SL.add_error("Invalid search", match)
519
+ match
509
520
  end
510
- else
511
- SL.add_error("Canceled", "User canceled result #{@url}")
512
- SL.add_output SL.originput.chomp
513
- SL.print_errors
514
521
  end
515
- else
516
- SL.add_error("No results", title)
517
- SL.add_output SL.originput.chomp
518
- SL.print_errors
519
522
  end
523
+ line_difference += 1 if @delete_line
524
+ out.push(line) unless @delete_line
525
+ @delete_line = false
526
+ end
527
+ warn "\n" unless SILENT
520
528
 
521
- if SL.clipboard
522
- if SL.output == SL.originput
523
- warn "No results found"
524
- else
525
- `echo #{Shellwords.escape(SL.output.join(""))}|tr -d "\n"|pbcopy`
526
- warn "Results in clipboard"
527
- end
529
+ input = out.delete_if { |l| l.strip =~ /^<!--DELETE-->$/ }.join("\n")
530
+
531
+ if SL.config["inline"]
532
+ SL.add_output "#{input}\n"
533
+ SL.add_output "\n#{SL.print_footer}" unless SL.footer.empty?
534
+ elsif SL.footer.empty?
535
+ SL.add_output input
536
+ else
537
+ last_line = input.strip.split("\n")[-1]
538
+ case last_line
539
+ when /^\[.*?\]: http/
540
+ SL.add_output "#{input.rstrip}\n"
541
+ when /^\[\^.*?\]: /
542
+ SL.add_output input.rstrip
543
+ else
544
+ SL.add_output "#{input}\n\n"
528
545
  end
546
+ SL.add_output "#{SL.print_footer}\n\n"
529
547
  end
530
- end
531
548
 
532
- private
549
+ SL.line_num = nil
550
+ SL.add_report("Processed: #{total_links} links, #{SL.error_count} errors.")
551
+ SL.print_report
552
+ SL.print_errors
553
+ end
533
554
 
534
555
  def add_title(link_info)
535
556
  @url = link_info
@@ -582,7 +603,8 @@ module SL
582
603
 
583
604
  input.parse_flags! unless skip_flags
584
605
 
585
- options = %w[debug country_code inline prefix_random include_titles remove_seo validate_links complete_bare confirm]
606
+ options = %w[debug country_code inline prefix_random include_titles remove_seo validate_links complete_bare
607
+ confirm]
586
608
  options.each do |o|
587
609
  if input =~ /^ *#{o}:\s+(\S+)$/
588
610
  val = Regexp.last_match(1).strip
@@ -648,10 +670,10 @@ module SL
648
670
  note = mtch[1].strip
649
671
  @footnote_counter += 1
650
672
  ref = if !@link_text.empty? && @link_text.scan(/\s/).empty?
651
- @link_text
652
- else
653
- format("%<p>sfn%<c>04d", p: @prefix, c: @footnote_counter)
654
- end
673
+ @link_text
674
+ else
675
+ format("%<p>sfn%<c>04d", p: @prefix, c: @footnote_counter)
676
+ end
655
677
  SL.add_footer "[^#{ref}]: #{note}"
656
678
  res = "[^#{ref}]"
657
679
  @cursor_difference += (SL.match_length - res.length)
@@ -675,12 +697,16 @@ module SL
675
697
  search_type = "r"
676
698
  tokens = v.scan(/\$term\d+[ds]?/).sort.uniq
677
699
 
678
- if !tokens.empty?
700
+ if tokens.empty?
701
+ search_terms = v.gsub(/\$term[ds]?/i) do |mtch|
702
+ search_terms.downcase! if mtch =~ /d$/i
703
+ search_terms.slugify! if mtch =~ /s$/i
704
+ search_terms.url_encode
705
+ end
706
+ else
679
707
  highest_token = 0
680
708
  tokens.each do |token|
681
- if token =~ /(\d+)[ds]?$/ && Regexp.last_match(1).to_i > highest_token
682
- highest_token = Regexp.last_match(1).to_i
683
- end
709
+ highest_token = Regexp.last_match(1).to_i if token =~ /(\d+)[ds]?$/ && Regexp.last_match(1).to_i > highest_token
684
710
  end
685
711
  terms_p = search_terms.split(/ +/)
686
712
  if terms_p.length > highest_token
@@ -706,12 +732,6 @@ module SL
706
732
  v.gsub!(/#{Regexp.escape(t) + re_down}/, replacement.url_encode)
707
733
  end
708
734
  search_terms = v
709
- else
710
- search_terms = v.gsub(/\$term[ds]?/i) do |mtch|
711
- search_terms.downcase! if mtch =~ /d$/i
712
- search_terms.slugify! if mtch =~ /s$/i
713
- search_terms.url_encode
714
- end
715
735
  end
716
736
  else
717
737
  search_type = SL::GoogleSearch.api_key? ? "gg" : "g"