searchlink 2.3.87 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9ec0f6dcca720a1630213bc1addbad3dc28cc949756b5d43ca74394b9a3a1023
4
- data.tar.gz: 979260379965c9d0c69de75990fea6639f8cd628f4cf1c9fd7b9f9b226170761
3
+ metadata.gz: 1d1e0567872f0b80b0d51b37a4522c91cd70f3af268b07ee9d2ed35e56ee3288
4
+ data.tar.gz: a5327a1f2e2d355b3f1ba7c5818f9b51c640207e3e0e48374527038e4e2d049e
5
5
  SHA512:
6
- metadata.gz: '0934cdf6a1c087e2fe2dee24bb962ca699fcfafc9848260fd0c75500c2d1531e75cb5f6a33ce4a32ab586c7594ef630a325ccca644bbf0a3a68ca659997d9ab4'
7
- data.tar.gz: e4767b29327c7f7147efb584ea3e1dee68eafd982802b5b3375b6dddd8f3eb8e1aeb4ed444f9f8486422999eeb6f6b7f4bf94e8b051124d48c3d265867a05827
6
+ metadata.gz: 6bbf35b751bbdfc03e990ef90de99b62117183759640c0ffc102d606be0aed4e8982bec6d1bfaec7ffc67e0dd4670252174307ef2d8953752402f6789e388953
7
+ data.tar.gz: 1f2e05c2e4220b8f38a6bebe99c1ca019474d8dde50e2c61ecf57e557c265ad2536d317a5d7e47ad5583dc065ae983fa475f215650e1ceeb24c24b1e07440618
@@ -142,7 +142,7 @@ module Curl
142
142
  def images
143
143
  output = []
144
144
  %w[og:image twitter:image].each do |src|
145
- next unless @meta.key?(src)
145
+ next unless @meta&.key?(src)
146
146
 
147
147
  output << {
148
148
  type: "opengraph",
@@ -3,14 +3,19 @@
3
3
  module SL
4
4
  class << self
5
5
  attr_writer :titleize, :clipboard, :output, :footer, :line_num,
6
- :match_column, :match_length, :originput, :errors, :report, :printout,
7
- :shortener
6
+ :match_column, :match_length, :originput, :errors, :error_count,
7
+ :report, :printout, :shortener
8
8
 
9
9
  # Whether or not to add a title to the output
10
10
  def titleize
11
11
  @titleize ||= false
12
12
  end
13
13
 
14
+ # Count of errors
15
+ def error_count
16
+ @error_count ||= 0
17
+ end
18
+
14
19
  # Whether or not to copy results to clipbpard
15
20
  def clipboard
16
21
  @clipboard ||= false
@@ -106,11 +111,17 @@ module SL
106
111
  # @return [String] The link.
107
112
  #
108
113
  def make_link(type, text, url, title: false, force_title: false)
109
- title = title.gsub(/\P{Print}|\p{Cf}/, "") if title
110
- text = title || SL::URL.title(url) if SL.titleize && (!text || text.strip.empty?)
111
- text = text ? text.strip : title
114
+ url.sub!(%r{^//}, "https://")
115
+ is_image = url =~ /(gif|jpe?g|png|webp)(?:\?.*?)?$/ ? true : false
116
+ if is_image
117
+ title = text || File.basename(url).sub(/\?.*$/, "")
118
+ text = title if text.nil? || text.strip.empty?
119
+ else
120
+ title = title.gsub(/\P{Print}|\p{Cf}/, "") if title
121
+ text = title || SL::URL.title(url) if SL.titleize && (!text || text.strip.empty?)
122
+ text = text ? text.strip : title
123
+ end
112
124
  title = title && (SL.config["include_titles"] || force_title) ? %( "#{title.clean}") : ""
113
-
114
125
  title = title.gsub(/[ \t]+/, " ")
115
126
 
116
127
  url.add_query_string!
@@ -123,8 +134,7 @@ module SL
123
134
  when :ref_link
124
135
  %([#{text}][#{url}])
125
136
  when :inline
126
- image = url =~ /\.(gif|jpe?g|png|webp)$/ ? "!" : ""
127
- %(#{image}[#{text}](#{url}#{title}))
137
+ %(#{is_image ? '!' : ''}[#{text}](#{url}#{title}))
128
138
  end
129
139
  end
130
140
 
@@ -214,6 +224,8 @@ module SL
214
224
  end
215
225
  SL.errors[type] ||= []
216
226
  SL.errors[type].push("(#{position}): #{str}")
227
+ SL.error_count ||= 0
228
+ SL.error_count += 1
217
229
  end
218
230
 
219
231
  # Add to query string
@@ -28,25 +28,14 @@ module SL
28
28
  latest_version = SL.new_version?
29
29
  SL.add_output("<!-- v#{latest_version} available, run SearchLink on the word 'update' to install. -->") if latest_version
30
30
 
31
- @links = {}
32
31
  SL.footer = []
33
- counter_links = 0
34
- counter_errors = 0
35
32
 
36
33
  input.sub!(/\n?<!-- Report:.*?-->\n?/m, "")
37
34
  input.sub!(/\n?<!-- Errors:.*?-->\n?/m, "")
38
35
 
39
- input.scan(/\[(.*?)\]:\s+(.*?)\n/).each { |match| @links[match[1].strip] = match[0] }
36
+ @links = input.scan_links
40
37
 
41
- @prefix = if SL.config["prefix_random"]
42
- if input =~ /\[(\d{4}-)\d+\]: \S+/
43
- Regexp.last_match(1)
44
- else
45
- format("%04d-", rand(9999))
46
- end
47
- else
48
- ""
49
- end
38
+ @prefix = determine_prefix(input)
50
39
 
51
40
  @highest_marker = 0
52
41
  input.scan(/^\s{,3}\[(?:#{@prefix})?(\d+)\]: /).each do
@@ -61,16 +50,19 @@ module SL
61
50
  end
62
51
 
63
52
  if SL.config["complete_bare"]
53
+ # match all URLs not preceded by a ( or : or <, and are followed by a space or newline
64
54
  rx = %r{(?ix-m)(?<!\(|:\s|<)(?:
65
55
  (?:https?://)(?:[\da-z.-]+)\.(?:[a-z.]{2,6})
66
56
  (?:[/\w\d.\-()_+=?&%]*?(?=[\s\n]|$))
67
57
  )}
58
+ # replace with [%](url)
68
59
  input.gsub!(rx) do
69
60
  url_match = Regexp.last_match
70
61
  url_match.pre_match =~ /!\S+ +$/ ? url_match[0] : "[%](#{url_match[0]})"
71
62
  end
72
63
  end
73
64
 
65
+ # Handle multi-line links in the form of [\ntext\n](url)
74
66
  if input =~ /\[\n(.*?\n)+\]\((.*?)?\)/
75
67
  input.gsub!(/\[\n(((\s*(?:[-+*]|\d+\.)?\s+)*(!\S+ +)?(.*?))\n)+\]\((!\S+.*?)?\)/) do
76
68
  m = Regexp.last_match
@@ -95,431 +87,470 @@ module SL
95
87
 
96
88
  # Handle links in the form of [text](url) or [text](url "title")
97
89
  if input =~ /\[(.*?)\]\((.*?)\)/
98
- lines = input.split("\n")
99
- out = []
100
-
101
- total_links = input.scan(/\[(.*?)\]\((.*?)\)/).length
102
- in_code_block = false
103
- line_difference = 0
104
- lines.each_with_index do |line, num|
105
- SL.line_num = num - line_difference
106
- @cursor_difference = 0
107
- # ignore links in code blocks
108
- if line =~ /^(( {4,}|\t+)[^*+-])/
109
- out.push(line)
110
- next
111
- end
112
- if line =~ /^\s*[~`]{3,}/
113
- if in_code_block
114
- in_code_block = false
115
- out.push(line)
116
- next
117
- else
118
- in_code_block = true
119
- end
120
- end
121
- if in_code_block
122
- out.push(line)
123
- next
124
- end
90
+ parse_brackets(input)
91
+ else # Assume single line input
92
+ parse_single_line(input)
93
+ end
94
+ end
125
95
 
126
- @delete_line = false
96
+ private
127
97
 
128
- @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
129
110
 
130
- line.gsub!(/\[(.*?)\]\((.*?)\)/) do |match|
131
- this_match = Regexp.last_match
111
+ def parse_single_line(input)
112
+ link_only = false
113
+ SL.clipboard = false
132
114
 
133
- SL.match_column = this_match.begin(0) - @cursor_difference
134
- @match_string = this_match.to_s
135
- SL.match_length = @match_string.length
136
- 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({})
137
118
 
138
- invalid_search = false
139
- @ref_title = false
119
+ # if the end of input contain "^", copy to clipboard instead of STDOUT
120
+ SL.clipboard = true if input =~ /\^[!~:\s]*$/
140
121
 
141
- if match_before.scan(/(^|[^\\])`/).length.odd?
142
- SL.add_report("Match '#{@match_string}' within an inline code block")
143
- invalid_search = true
144
- end
122
+ # if the end of input contains "!!", only print the url
123
+ link_only = true if input =~ /!![\^~:\s]*$/
145
124
 
146
- counter_links += 1
147
- $stderr.print("\033[0K\rProcessed: #{counter_links} of #{total_links}, #{counter_errors} errors. ") unless SILENT
125
+ reference_link = input =~ /:([!\^~\s]*)$/
148
126
 
149
- @link_text = this_match[1] || ""
150
- link_info = parse_arguments(this_match[2].strip).strip || ""
151
- 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
152
139
 
153
- if @link_text.strip == "" && link_info =~ /".*?"/
154
- link_info.gsub!(/"(.*?)"/) do
155
- m = Regexp.last_match
156
- @link_text = m[1] if @link_text == ""
157
- m[0]
158
- end
159
- end
140
+ input.sub!(/[:!\^\s~]*$/, "")
160
141
 
161
- link_info.gsub!(/<(.*?)>/) do
162
- %(%22#{Regexp.last_match(1)}%22)
163
- 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
164
150
 
165
- if link_info.strip =~ /:$/ && line.strip == match
166
- @ref_title = true
167
- link_info.sub!(/\s*:\s*$/, "")
168
- 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
169
157
 
170
- if @link_text.empty? && link_info.sub(/^[!\^]\S+/, "").strip.empty?
171
- SL.add_error("No input", match)
172
- counter_errors += 1
173
- invalid_search = true
174
- 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
175
163
 
176
- if link_info =~ /^!(\S+)/
177
- search_type = Regexp.last_match(1).extract_shortener
178
- unless SL::Searches.valid_search?(search_type) || search_type =~ /^(\S+\.)+\S+$/
179
- SL.add_error("Invalid search#{SL::Searches.did_you_mean(search_type)}", match)
180
- invalid_search = true
181
- end
182
- end
164
+ @link_text = false
183
165
 
184
- if invalid_search
185
- match
186
- elsif link_info =~ /^\^(.+)/
187
- m = Regexp.last_match
188
- create_footnote(m)
189
- # Handle [](URL) and [%](URL), filling in title
190
- elsif (@link_text == "" || @link_text == "%") && SL::URL.url?(link_info)
191
- add_title(link_info)
192
- elsif (@link_text == "" && link_info == "") || SL::URL.url?(link_info)
193
- SL.add_error("Invalid search", match) unless SL::URL.url?(link_info)
194
- match
195
- else
196
- 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
197
170
 
198
- search_type = ""
199
- search_terms = ""
200
- link_only = false
201
- SL.clipboard = false
202
- 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")
203
173
 
204
- if link_info =~ /^(?:[!\^](\S+))\s*(.*)$/
205
- 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
206
178
 
207
- search_type = if m[1].nil?
208
- SL::GoogleSearch.api_key? ? "gg" : "g"
209
- else
210
- m[1]
211
- end
179
+ @link_text ||= link_info
180
+ terms = link_info + additional_terms
181
+ terms.strip!
212
182
 
213
- 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
214
188
 
215
- search_terms = m[2].gsub(/(^["']|["']$)/, "")
216
- 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
217
196
 
218
- # if the link text is just '%' replace with title regardless of config settings
219
- if @link_text == "%" && search_terms && !search_terms.empty?
220
- SL.titleize = true
221
- @link_text = ""
222
- 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
223
220
 
224
- 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
225
228
 
226
- # if the input starts with a +, append it to the link text as the search terms
227
- search_terms = "#{@link_text} #{search_terms.strip.sub(/^\+\s*/, '')}" if search_terms.strip =~ /^\+[^+]/
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
228
237
 
229
- # if the end of input contain "^", copy to clipboard instead of STDOUT
230
- 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
231
251
 
232
- # if the end of input contains "!!", only print the url
233
- link_only = true if search_terms =~ /!!\^?$/
252
+ return unless SL.clipboard
234
253
 
235
- 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
236
261
 
237
- if search_type =~ /^(\S+\.)+\S+$/
238
- search_type = "g"
239
- search_terms = "site:#{m[1]} #{search_terms}"
240
- end
241
- elsif link_info =~ /^!/
242
- search_word = link_info.match(/^!(\S+)/)
243
- st = search_word[1].extract_shortener
244
- if search_word && SL::Searches.valid_search?(st)
245
- search_type = st unless search_word.nil?
246
- search_terms = @link_text
247
- elsif search_word && st =~ /^(\S+\.)+\S+$/
248
- search_type = SL::GoogleSearch.api_key? ? "gg" : "g"
249
- search_terms = "site:#{search_word[1]} #{@link_text}"
250
- else
251
- SL.add_error("Invalid search#{SL::Searches.did_you_mean(st)}", match)
252
- search_type = false
253
- search_terms = false
254
- end
255
- elsif @link_text && !@link_text.empty? && (!link_info || link_info.empty?)
256
- search_type = SL::GoogleSearch.api_key? ? "gg" : "g"
257
- search_terms = @link_text
258
- elsif link_info && !link_info.empty?
259
- search_type = SL::GoogleSearch.api_key? ? "gg" : "g"
260
- search_terms = link_info
261
- else
262
- SL.add_error("Invalid search", match)
263
- search_type = false
264
- search_terms = false
265
- end
262
+ def parse_brackets(input)
263
+ lines = input.split("\n")
264
+ out = []
266
265
 
267
- search_type, search_terms = custom_search(search_type, search_terms) if search_type && !search_terms.empty?
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
268
275
 
269
- 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
270
310
 
271
- if (search_type && search_terms) || @url
272
- # warn "Searching #{search_type} for #{search_terms}"
311
+ total_links += line.count_links
273
312
 
274
- @search_count += 1
275
- @url, title, @link_text = do_search(search_type, search_terms, @link_text, @search_count)
313
+ @delete_line = false
276
314
 
277
- if (@link_text == "" || @link_text == "%") && @url
278
- if title
279
- @link_text = title
280
- else
281
- add_title(@url)
282
- end
283
- end
315
+ @search_count = 0
284
316
 
285
- if @url
286
- res = confirmed?(@url)
287
- return match unless res
288
-
289
- @url = res if res.is_a?(String) && SL::URL.url?(res)
290
-
291
- title = SL::URL.title(@url) if SL.titleize && (title.nil? || title.empty?)
292
-
293
- @link_text = title if @link_text == "" && title
294
- force_title = search_type =~ /def/ ? true : false
295
-
296
- if link_only || search_type =~ /sp(ell)?/ || @url == "embed"
297
- @url = title if @url == "embed"
298
- @cursor_difference += SL.match_length - @url.length
299
- SL.match_length = @url.length
300
- SL.add_report("#{@match_string} => #{@url}")
301
- @url
302
- elsif @ref_title
303
- unless @links.key? @url
304
- @links[@url] = @link_text
305
- SL.add_footer SL.make_link(:ref_title, @link_text, @url, title: title, force_title: force_title)
306
- end
307
- @delete_line = true
308
- elsif SL.config["inline"]
309
- res = SL.make_link(:inline, @link_text, @url, title: title, force_title: force_title)
310
- @cursor_difference += SL.match_length - res.length
311
- SL.match_length = res.length
312
- SL.add_report("#{@match_string} => #{@url}")
313
- res
314
- else
315
- unless @links.key? @url
316
- @highest_marker += 1
317
- @links[@url] = format("%<pre>s%<m>04d", pre: @prefix, m: @highest_marker)
318
- SL.add_footer SL.make_link(:ref_title, @links[@url], @url, title: title, force_title: force_title)
319
- end
320
-
321
- type = SL.config["inline"] ? :inline : :ref_link
322
- res = SL.make_link(type, @link_text, @links[@url], title: false, force_title: force_title)
323
- @cursor_difference += SL.match_length - res.length
324
- SL.match_length = res.length
325
- SL.add_report("#{@match_string} => #{@url}")
326
- res
327
- end
328
- else
329
- SL.add_error("No results", "#{search_terms} (#{@match_string})")
330
- counter_errors += 1
331
- match
332
- end
333
- else
334
- SL.add_error("Invalid search", match)
335
- counter_errors += 1
336
- match
337
- end
338
- end
339
- end
340
- line_difference += 1 if @delete_line
341
- out.push(line) unless @delete_line
342
- @delete_line = false
343
- end
344
- warn "\n" unless SILENT
317
+ line.gsub!(/\[(.*?)\]\((.*?)\)/) do |match|
318
+ this_match = Regexp.last_match
345
319
 
346
- input = out.delete_if { |l| l.strip =~ /^<!--DELETE-->$/ }.join("\n")
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
347
324
 
348
- if SL.config["inline"]
349
- SL.add_output "#{input}\n"
350
- SL.add_output "\n#{SL.print_footer}" unless SL.footer.empty?
351
- elsif SL.footer.empty?
352
- SL.add_output input
353
- else
354
- last_line = input.strip.split("\n")[-1]
355
- case last_line
356
- when /^\[.*?\]: http/
357
- SL.add_output "#{input.rstrip}\n"
358
- when /^\[\^.*?\]: /
359
- SL.add_output input.rstrip
360
- else
361
- SL.add_output "#{input}\n\n"
325
+ invalid_search = false
326
+ @ref_title = false
327
+
328
+ if match_before.scan(/(^|[^\\])`/).length.odd?
329
+ SL.add_report("Match '#{@match_string}' within an inline code block")
330
+ invalid_search = true
362
331
  end
363
- SL.add_output "#{SL.print_footer}\n\n"
364
- end
332
+ counter_links += 1
333
+ $stderr.print("\033[0K\rProcessed: #{counter_links} of #{total_links}, #{SL.error_count} errors. ") unless SILENT
365
334
 
366
- SL.line_num = nil
367
- SL.add_report("Processed: #{total_links} links, #{counter_errors} errors.")
368
- SL.print_report
369
- SL.print_errors
370
- else # Assume single line input
371
- link_only = false
372
- 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({})
373
338
 
374
- res = parse_arguments(input.strip!).strip
375
- input = res.nil? ? input.strip : res
376
- 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
377
346
 
378
- # if the end of input contain "^", copy to clipboard instead of STDOUT
379
- SL.clipboard = true if input =~ /\^[!~:\s]*$/
347
+ link_info.gsub!(/<(.*?)>/) do
348
+ %(%22#{Regexp.last_match(1)}%22)
349
+ end
380
350
 
381
- # if the end of input contains "!!", only print the url
382
- 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
383
355
 
384
- 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
385
360
 
386
- # if end of input contains ~, pull url from clipboard
387
- if input =~ /~[:\^!\s]*$/
388
- input.sub!(/[:!\^\s~]*$/, "")
389
- clipboard = `__CF_USER_TEXT_ENCODING=$UID:0x8000100:0x8000100 pbpaste`.strip
390
- if SL::URL.url?(clipboard)
391
- type = reference_link ? :ref_title : :inline
392
- print SL.make_link(type, input.strip, clipboard)
393
- else
394
- 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
395
367
  end
396
- Process.exit
397
- end
398
368
 
399
- 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 == ""
400
382
 
401
- ## Maybe if input is just a URL, convert it to a link
402
- ## using hostname as text without doing search
403
- if SL::URL.only_url?(input.strip)
404
- type = reference_link ? :ref_title : :inline
405
- @url, title = SL::URL.url_to_link(input.strip, type)
406
- print SL.make_link(type, title, @url, title: false, force_title: false)
407
- Process.exit
408
- end
383
+ search_type = ""
384
+ search_terms = ""
385
+ link_only = false
386
+ SL.clipboard = false
387
+ SL.titleize = SL.config["empty_uses_page_title"]
409
388
 
410
- # check for additional search terms in parenthesis
411
- additional_terms = ""
412
- if input =~ /\((.*?)\)/
413
- additional_terms = " #{Regexp.last_match(1).strip}"
414
- input.sub!(/\(.*?\)/, "")
415
- end
389
+ if link_info =~ /^(?:[!\^](\S+))\s*(.*)$/
390
+ m = Regexp.last_match
416
391
 
417
- # Maybe detect "search + addition terms" and remove additional terms from link text?
418
- # if input =~ /\+(.+?)$/
419
- # additional_terms = "#{additional_terms} #{Regexp.last_match(1).strip}"
420
- # input.sub!(/\+.*?$/, '').strip!
421
- # end
392
+ search_type = if m[1].nil?
393
+ SL::GoogleSearch.api_key? ? "gg" : "g"
394
+ else
395
+ m[1]
396
+ end
422
397
 
423
- @link_text = false
398
+ search_type.extract_shortener!
424
399
 
425
- if input =~ /"(.*?)"/
426
- @link_text = Regexp.last_match(1)
427
- input.gsub!(/"(.*?)"/, '\1')
428
- end
400
+ search_terms = m[2].gsub(/(^["']|["']$)/, "")
401
+ search_terms.strip!
429
402
 
430
- # remove quotes from terms, just in case
431
- # 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
432
408
 
433
- case input
434
- when /^!(\S+)\s+(.*)$/
435
- type = Regexp.last_match(1).extract_shortener
436
- link_info = Regexp.last_match(2).strip
409
+ search_terms = @link_text if search_terms == ""
437
410
 
438
- @link_text ||= link_info
439
- terms = link_info + additional_terms
440
- 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 =~ /^\+[^+]/
441
413
 
442
- if SL::Searches.valid_search?(type) || type =~ /^(\S+\.)+\S+$/
443
- if type && terms && !terms.empty?
444
- # Iterate through custom searches for a match, perform search if matched
445
- type, terms = custom_search(type, terms)
446
- end
414
+ # if the end of input contain "^", copy to clipboard instead of STDOUT
415
+ SL.clipboard = true if search_terms =~ /(!!)?\^(!!)?$/
416
+
417
+ # if the end of input contains "!!", only print the url
418
+ link_only = true if search_terms =~ /!!\^?$/
447
419
 
448
- # if contains TLD, use site-specific search
449
- if type =~ /^(\S+\.)+\S+$/
450
- terms = "site:#{type} #{terms}"
451
- type = SL::GoogleSearch.api_key? ? "gg" : "g"
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
446
+ else
447
+ SL.add_error("Invalid search", match)
448
+ search_type = false
449
+ search_terms = false
452
450
  end
453
- @search_count ||= 0
454
- @search_count += 1
451
+
452
+ search_type, search_terms = custom_search(search_type, search_terms) if search_type && !search_terms.empty?
455
453
 
456
454
  SL.add_query(query) if query
457
- @url, title, @link_text = do_search(type, terms, @link_text, @search_count)
458
- else
459
- SL.add_error("Invalid search#{SL::Searches.did_you_mean(type)}", input)
460
- counter_errors += 1
461
- end
462
- # Social handle expansion
463
- when /^([tfilm])?@(\S+)\s*$/
464
- type = Regexp.last_match(1)
465
- type ||= if Regexp.last_match(2) =~ /[a-z0-9_]@[a-z0-9_.]+/i
466
- "m"
467
- else
468
- "t"
469
- end
470
- @link_text = input.sub(/^[tfilm]/, "")
471
- SL.add_query(query) if query
472
- @url, title = SL::SocialSearch.social_handle(type, @link_text)
473
- @link_text = title
474
- else
475
- SL.add_query(query) if query
476
- @link_text ||= input
477
- @url, title, @link_text = SL.ddg(input, @link_text)
478
- end
479
455
 
480
- if @url
481
- res = confirmed?(@url)
482
- if res
483
- if res.is_a?(String) && SL::URL.url?(res)
484
- @url = res
485
- title = SL::URL.title(@url) if SL.titleize && title == ""
486
- end
456
+ if (search_type && search_terms) || @url
457
+ # warn "Searching #{search_type} for #{search_terms}"
487
458
 
488
- if type =~ /sp(ell)?/
489
- SL.add_output(@url)
490
- elsif link_only
491
- SL.add_output(@url)
492
- elsif @url == "embed"
493
- SL.add_output(title)
494
- else
495
- type = reference_link ? :ref_title : :inline
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
496
473
 
497
- SL.add_output SL.make_link(type, @link_text, @url, title: title, force_title: false)
498
- SL.print_errors
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
505
+
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
499
520
  end
500
- else
501
- SL.add_error("Canceled", "User canceled result #{@url}")
502
- SL.add_output SL.originput.chomp
503
- SL.print_errors
504
521
  end
505
- else
506
- SL.add_error("No results", title)
507
- SL.add_output SL.originput.chomp
508
- SL.print_errors
509
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
510
528
 
511
- if SL.clipboard
512
- if SL.output == SL.originput
513
- warn "No results found"
514
- else
515
- `echo #{Shellwords.escape(SL.output.join(""))}|tr -d "\n"|pbcopy`
516
- warn "Results in clipboard"
517
- 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"
518
545
  end
546
+ SL.add_output "#{SL.print_footer}\n\n"
519
547
  end
520
- end
521
548
 
522
- 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
523
554
 
524
555
  def add_title(link_info)
525
556
  @url = link_info
@@ -176,7 +176,8 @@ module SL
176
176
 
177
177
  def first_image(url)
178
178
  images = Curl::Html.new(url).images
179
- images.filter { |img| img[:type] == "img" }.first[:src]
179
+ images.filter! { |img| img[:type] == "img" }
180
+ images.first[:src] if images.any?
180
181
  end
181
182
  end
182
183
  end
@@ -67,7 +67,7 @@ module SL
67
67
  output_title = result["title"]
68
68
  output_title.remove_seo!(output_url) if SL.config["remove_seo"]
69
69
 
70
- output_url = SL.first_image if search_type =~ /img$/
70
+ output_url = SL.first_image(output_url) if search_type =~ /img$/
71
71
 
72
72
  [output_url, output_title, link_text]
73
73
  rescue StandardError
@@ -3,6 +3,32 @@
3
3
  module SL
4
4
  # String helpers
5
5
  class ::String
6
+ # Scan a string for links
7
+ # @return [Hash] Hash of links
8
+ def scan_links
9
+ links = {}
10
+ scan(/\[(.*?)\]:\s+(.*?)\n/).each { |match| links[match[1].strip] = match[0] }
11
+ links
12
+ end
13
+
14
+ # Count the indent level of a string
15
+ # @return [Integer] The indent level
16
+ def indent_level
17
+ return 0 if empty?
18
+
19
+ gsub!(/^ /, "\t") while self =~ /^ /
20
+ indent = match(/^\t+/)
21
+ return 0 unless indent
22
+
23
+ indent[0].length
24
+ end
25
+
26
+ # Count the links in a string
27
+ # @return [Integer] The number of links
28
+ def count_links
29
+ scan(/\[(.*?)\]\((.*?)\)/).length
30
+ end
31
+
6
32
  # Quote a YAML value if needed
7
33
  def yaml_val
8
34
  yaml = YAML.safe_load("key: '#{self}'")
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SL
4
- VERSION = "2.3.87"
4
+ VERSION = "2.3.88"
5
5
  end
6
6
 
7
7
  # Main module
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: searchlink
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.87
4
+ version: 2.3.88
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-04-01 00:00:00.000000000 Z
10
+ date: 2025-04-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: base64