Class: SL::SearchLink

Inherits:
Object
  • Object
show all
Defined in:
lib/searchlink/config.rb,
lib/searchlink/help.rb,
lib/searchlink/parse.rb,
lib/searchlink/search.rb

Overview

Main SearchLink class

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opt = {}) ⇒ SearchLink

Returns a new instance of SearchLink.



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/searchlink/config.rb', line 33

def initialize(opt = {})
  SL.printout = opt[:echo] || false
  if File.exist? config_file
    write_new_plugin_config
  else
    default_config = <<~ENDCONFIG
      # set to true to have an HTML comment included detailing any errors
      # Can be disabled per search with `--d`, or enabled with `++d`.
      debug: true
      # set to true to have an HTML comment included reporting results
      report: true

      # use Notification Center to display progress
      notifications: false

      # when running on a file, back up original to *.bak
      backup: true

      # Time limit for searches. Increase if your searches are regularly
      # timing out
      timeout: 15

      # change this to set a specific country for search (default US)
      country_code: US

      # set to true to force inline Markdown links. Can be disabled
      # per search with `--i`, or enabled with `++i`
      inline: false

      # set to true to include a random string in reference titles.
      # Avoids conflicts if you're only running on part of a document
      # or using SearchLink multiple times within a document
      prefix_random: true

      # set to true to add titles to links based on the page title
      # of the search result. Can be disabled per search with `--t`,
      # or enabled with `++t`.
      include_titles: false

      # set to true to attempt to remove SEO elements from page titles,
      # such that "Regular expressions for beginners | Brett Terpstra.com"
      # becomes "Regular expressions for beginners"
      remove_seo: false

      # confirm existence (200) of generated links. Can be disabled
      # per search with `--v`, or enabled with `++v`.
      validate_links: false

      # If the link text is left empty, always insert the page title
      # E.g. [](!g Search Text)
      empty_uses_page_title: false

      # If confirm is true, then a popup dialog will be displayed
      # showing the destination of each found link. Hitting cancel
      # will leave the link unchanged.
      confirm: false

      # To create custom abbreviations for Google Site Searches,
      # add to (or replace) the hash below.
      # "abbreviation" => "site.url",
      # This allows you, for example to use [search term](!bt)
      # as a shortcut to search brettterpstra.com (using a site-specific
      # Google search). Keys in this list can override existing
      # search trigger abbreviations.
      #
      # If a custom search starts with "http" or "/", it becomes
      # a simple replacement. Any instance of "$term" is replaced
      # with a URL-escaped version of your search terms.
      # Use $term1, $term2, etc. to replace in sequence from
      # multiple search terms. No instances of "$term" functions
      # as a simple shortcut. "$term" followed by a "d" lowercases
      # the replacement. Use "$term1d," "$term2d" to downcase
      # sequential replacements (affected individually).
      # Long flags (e.g. --no-validate_links) can be used after
      # any url in the custom searches.
      #
      # Use $terms to slugify all search terms, turning
      # "Markdown Service Tools" into "markdown-service-tools"
      custom_site_searches:
        bt: brettterpstra.com
        btt: https://brettterpstra.com/topic/$term1d
        bts: /search/$term --no-validate_links
        md: www.macdrifter.com
        ms: macstories.net
        dd: www.leancrew.com
        spark: macsparky.com
        man: http://man.cx/$term
        dev: developer.apple.com
        nq: http://nerdquery.com/?media_only=0&query=$term&search=1&category=-1&catid=&type=and&results=50&db=0&prefix=0
        gs: http://scholar.google.com/scholar?btnI&hl=en&q=$term&btnG=&as_sdt=80006

    ENDCONFIG

    default_config = get_plugin_configs(default_config)

    File.open(config_file, "w") do |f|
      f.puts default_config
    end
  end

  config = YAML.load_file(config_file)

  # set to true to have an HTML comment inserted showing any errors
  config["debug"] ||= false

  # set to true to get a verbose report at the end of multi-line processing
  config["report"] ||= false

  config["backup"] = true unless config.key? "backup"

  config["timeout"] ||= 15

  # set to true to force inline links
  config["inline"] ||= false

  # set to true to add titles to links based on site title
  config["include_titles"] ||= false

  # set to true to remove SEO elements from page titles
  config["remove_seo"] ||= false

  # set to true to use page title as link text when empty
  config["empty_uses_page_title"] ||= false

  # change this to set a specific country for search (default US)
  config["country_code"] ||= "US"

  # set to true to include a random string in ref titles
  # allows running SearchLink multiple times w/out conflicts
  config["prefix_random"] = false unless config["prefix_random"]

  config["social_template"] ||= "%service%/%user%"

  # append affiliate link info to iTunes urls, empty quotes for none
  # example:
  # $itunes_affiliate = "&at=10l4tL&ct=searchlink"
  config["itunes_affiliate"] ||= "&at=10l4tL&ct=searchlink"

  # to create Amazon affiliate links, set amazon_partner to your amazon
  # affiliate tag
  #    amazon_partner: "bretttercom-20"
  config["amazon_partner"] ||= ""

  # display a popup dialog confirmation
  config["confirm"] ||= false

  # To create custom abbreviations for Google Site Searches,
  # add to (or replace) the hash below.
  # "abbreviation" => "site.url",
  # This allows you, for example to use [search term](!bt)
  # as a shortcut to search brettterpstra.com. Keys in this
  # hash can override existing search triggers.
  config["custom_site_searches"] ||= {
    "bt" => "brettterpstra.com",
    "imdb" => "imdb.com"
  }

  # confirm existence of links generated from custom search replacements
  config["validate_links"] ||= false

  # use notification center to show progress
  config["notifications"] ||= false

  SL.line_num = nil
  SL.match_column = nil
  SL.match_length = nil
  SL.config = config

  add_plugin_configs(config)
end

Instance Attribute Details

#clipboardObject (readonly)

Returns the value of attribute clipboard.



8
9
10
# File 'lib/searchlink/search.rb', line 8

def clipboard
  @clipboard
end

#originputObject (readonly)

Returns the value of attribute originput.



8
9
10
# File 'lib/searchlink/search.rb', line 8

def originput
  @originput
end

#outputObject (readonly)

Returns the value of attribute output.



8
9
10
# File 'lib/searchlink/search.rb', line 8

def output
  @output
end

Instance Method Details

#add_plugin_configs(config) ⇒ Object

Note:

applies configurations to SL.config

Add plugin configurations to config object

Parameters:

  • config (Hash)

    Hash of plugin configurations



210
211
212
213
214
215
216
217
218
# File 'lib/searchlink/config.rb', line 210

def add_plugin_configs(config)
  SL::Searches.plugins[:search].each_value do |plugin|
    next unless plugin.key?(:config) && !plugin[:config].nil? && !plugin[:config].empty?

    plugin[:config].each do |cfg|
      SL.config[cfg[:key]] = config[cfg[:key]] if config.key?(cfg[:key])
    end
  end
end

#config_fileObject

Values found in ~/.searchlink will override defaults in this script



22
23
24
25
26
27
28
29
30
31
# File 'lib/searchlink/config.rb', line 22

def config_file
  old_style = File.expand_path("~/.searchlink")
  new_style = File.expand_path("~/.config/searchlink/config.yaml")
  if File.exist?(old_style) && !File.exist?(new_style)
    old_style
  else
    FileUtils.mkdir_p(File.dirname(new_style))
    new_style
  end
end

#confirmed?(url) ⇒ Boolean

Confirm a URL with a popup if requested

Returns:

  • (Boolean)


6
7
8
9
10
# File 'lib/searchlink/parse.rb', line 6

def confirmed?(url)
  return true if !SL.config["confirm"] || NO_CONFIRM

  SL::Shortener.confirm?(url)
end

#get_plugin_config(cfg) ⇒ String

Get a single plugin configuration

Parameters:

  • cfg (Hash)

    Hash of single plugin config

Returns:

  • (String)

    String representation of config



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# File 'lib/searchlink/config.rb', line 266

def get_plugin_config(cfg)
  key = cfg[:key]
  value = cfg[:value]
  required = cfg[:required]
  description = cfg[:description]
  description = "\n#{description}" if description
  description = description.word_wrap(60, "# ") if description
  key = required ? key : "# #{key}"
  if value.is_a?(Array)
    array_value = "\n"
    value.each do |v|
      array_value += required ? "- #{v.yaml_val}" : "# - #{v.yaml_val}\n"
    end
    value = array_value
  elsif value.is_a?(Hash)
    hash_value = "\n"
    value.each do |k, v|
      hash_value += required ? "  #{k}: #{v.yaml_val}" : "#  #{k}: #{v.yaml_val}"
    end
    value = hash_value
  else
    value = value.yaml_val
  end
  new_config = ""
  new_config += description if description

  new_config + "#{key}: #{value}"
end

#get_plugin_configs(default_config) ⇒ String

Get plugin configs

Parameters:

  • default_config (String)

    Existing configuration

Returns:

  • (String)

    default_config with plugin configurations added



246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/searchlink/config.rb', line 246

def get_plugin_configs(default_config)
  SL::Searches.plugins[:search].each_value do |plugin|
    next unless plugin.key?(:config) && !plugin[:config].nil? && !plugin[:config].empty?

    plugin[:config].each do |cfg|
      new_config = get_plugin_config(cfg)

      default_config += new_config
    end
  end
  default_config
end

#help_cliObject



105
106
107
# File 'lib/searchlink/help.rb', line 105

def help_cli
  $stdout.puts help_text
end

#help_cssObject



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/searchlink/help.rb', line 5

def help_css
  <<~ENDCSS
    body{-webkit-font-smoothing:antialiased;font-family:"Avenir Next",Avenir,"Helvetica Neue",Helvetica,Arial,Verdana,sans-serif;
    margin:30px 0 0;padding:0;background:#fff;color:#303030;font-size:16px;line-height:1.5;text-align:center}h1{color:#000}
    h2{color:#111}p,td,div{color:#111;font-family:"Avenir Next",Avenir,"Helvetica Neue",Helvetica,Arial,Verdana,sans-serif;
    word-wrap:break-word}a{color:#de5456;text-decoration:none;-webkit-transition:color .2s ease-in-out;
    -moz-transition:color .2s ease-in-out;-o-transition:color .2s ease-in-out;-ms-transition:color .2s ease-in-out;
    transition:color .2s ease-in-out}a:hover{color:#3593d9}h1,h2,h3,h4,h5{margin:2.75rem 0 2rem;font-weight:500;line-height:1.15}
    h1{margin-top:0;font-size:2em}h2{font-size:1.7em}ul,ol,pre,table,blockquote{margin-top:2em;margin-bottom:2em}
    caption,col,colgroup,table,tbody,td,tfoot,th,thead,tr{border-spacing:0}table{border:1px solid rgba(0,0,0,0.25);
    border-collapse:collapse;display:table;empty-cells:hide;margin:-1px 0 1.3125em;padding:0;table-layout:fixed;margin:0 auto}
    caption{display:table-caption;font-weight:700}col{display:table-column}colgroup{display:table-column-group}
    tbody{display:table-row-group}tfoot{display:table-footer-group}thead{display:table-header-group}
    td,th{display:table-cell}tr{display:table-row}table th,table td{font-size:1.2em;line-height:1.3;padding:.5em 1em 0}
    table thead{background:rgba(0,0,0,0.15);border:1px solid rgba(0,0,0,0.15);border-bottom:1px solid rgba(0,0,0,0.2)}
    table tbody{background:rgba(0,0,0,0.05)}table tfoot{background:rgba(0,0,0,0.15);border:1px solid rgba(0,0,0,0.15);
    border-top:1px solid rgba(0,0,0,0.2)}p{font-size:1.1429em;line-height:1.72em;margin:1.3125em 0}dt,th{font-weight:700}
    table tr:nth-child(odd),table th:nth-child(odd),table td:nth-child(odd){background:rgba(255,255,255,0.06)}
    table tr:nth-child(even),table td:nth-child(even){background:rgba(200,200,200,0.25)}
    input[type=text] {padding: 5px;border-radius: 5px;border: solid 1px #ccc;font-size: 20px;}
  ENDCSS
end

#help_dialogObject



93
94
95
96
97
98
99
100
101
102
103
# File 'lib/searchlink/help.rb', line 93

def help_dialog
  text = ["<html><head><style>#{help_css}</style><script>#{help_js}</script></head><body>"]
  text << "<h1>SearchLink Help</h1>"
  text << "<p>[#{SL.version_check}] [<a href='https://github.com/ttscoff/searchlink/wiki'>Wiki</a>]</p>"
  text << help_html
  text << '<p><a href="https://github.com/ttscoff/searchlink/wiki">Visit the wiki</a> for additional information</p>'
  text << "</body>"
  html_file = File.expand_path("~/.searchlink_searches.html")
  File.open(html_file, "w") { |f| f.puts text.join("\n") }
  `open #{html_file}`
end

#help_htmlObject



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/searchlink/help.rb', line 77

def help_html
  out = ['<input type="text" id="filter" onkeyup="filterTable()" placeholder="Filter searches">']
  out << "<h2>Available Searches</h2>"
  out << SL::Searches.available_searches_html
  out << "<h2>Custom Searches</h2>"
  out << '<table id="custom">'
  out << "<thead><td>Shortcut</td><td>Search Type</td></thead>"
  out << "<tbody>"
  SL.config["custom_site_searches"].each do |label, site|
    out << "<tr><td><code>!#{label}</code></td><td>#{site}</td></tr>"
  end
  out << "</tbody>"
  out << "</table>"
  out.join("\n")
end

#help_jsObject



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/searchlink/help.rb', line 28

def help_js
  <<~EOJS
    function filterTable() {
      let input, filter, table, tr, i, txtValue;
      input = document.getElementById("filter");
      filter = input.value.toUpperCase();
      table = document.getElementById("searches");
      table2 = document.getElementById("custom");

      tr = table.getElementsByTagName("tr");

      for (i = 0; i < tr.length; i++) {
          txtValue = tr[i].textContent || tr[i].innerText;
          if (txtValue.toUpperCase().indexOf(filter) > -1) {
            tr[i].style.display = "";
          } else {
            tr[i].style.display = "none";
          }
      }

      tr = table2.getElementsByTagName("tr");

      for (i = 0; i < tr.length; i++) {
          txtValue = tr[i].textContent || tr[i].innerText;
          if (txtValue.toUpperCase().indexOf(filter) > -1) {
            tr[i].style.display = "";
          } else {
            tr[i].style.display = "none";
          }
      }
    }
  EOJS
end

#help_textObject



62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/searchlink/help.rb', line 62

def help_text
  text = <<~EOHELP
    -- [Available searches] -------------------
    #{SL::Searches.available_searches}
  EOHELP

  if SL.config["custom_site_searches"]
    text += "\n-- [Custom Searches] ----------------------\n"
    SL.config["custom_site_searches"].sort_by do |l, _s|
      l
    end.each { |label, site| text += "!#{label}#{label.spacer} #{site}\n" }
  end
  text
end

#parse(input) ⇒ Object

Parse the input string and perform searches



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/searchlink/parse.rb', line 13

def parse(input)
  SL.output = []
  return false if input.empty?

  parse_arguments(input, { only_meta: true })
  SL.originput = input.dup

  parse_commands(input)

  SL.config["inline"] = true if input.scan("](").length == 1 && input.split("\n").length == 1
  SL.errors = {}
  SL.report = []
  SL.shortener = :none

  # Check for new version
  latest_version = SL.new_version?
  SL.add_output("<!-- v#{latest_version} available, run SearchLink on the word 'update' to install. -->") if latest_version

  SL.footer = []

  input.sub!(/\n?<!-- Report:.*?-->\n?/m, "")
  input.sub!(/\n?<!-- Errors:.*?-->\n?/m, "")

  @links = input.scan_links

  @prefix = determine_prefix(input)

  @highest_marker = 0
  input.scan(/^\s{,3}\[(?:#{@prefix})?(\d+)\]: /).each do
    m = Regexp.last_match
    @highest_marker = m[1].to_i if m[1].to_i > @highest_marker
  end

  @footnote_counter = 0
  input.scan(/^\s{,3}\[\^(?:#{@prefix})?fn(\d+)\]: /).each do
    m = Regexp.last_match
    @footnote_counter = m[1].to_i if m[1].to_i > @footnote_counter
  end

  if SL.config["complete_bare"]
    # match all URLs not preceded by a ( or : or <, and are followed by a space or newline
    rx = %r{(?ix-m)(?<!\(|:\s|<)(?:
            (?:https?://)(?:[\da-z.-]+)\.(?:[a-z.]{2,6})
            (?:[/\w\d.\-()_+=?&%]*?(?=[\s\n]|$))
          )}
    # replace with [%](url)
    input.gsub!(rx) do
      url_match = Regexp.last_match
      url_match.pre_match =~ /!\S+ +$/ ? url_match[0] : "[%](#{url_match[0]})"
    end
  end

  # Handle multi-line links in the form of [\ntext\n](url)
  if input =~ /\[\n(.*?\n)+\]\((.*?)?\)/
    input.gsub!(/\[\n(((\s*(?:[-+*]|\d+\.)?\s+)*(!\S+ +)?(.*?))\n)+\]\((!\S+.*?)?\)/) do
      m = Regexp.last_match
      lines = m[0].split("\n")
      lines = lines[1..-2]
      lines.map do |l|
        el_rx = /(\s*(?:[-+*]|\d+\.)?\s+)?(!\S+ )?(\w.*?)$/
        if l =~ el_rx
          els = l.match(el_rx)
          search = if els[2]
                     els[2].strip
                   else
                     m[6] || "!g"
                   end
          "#{els[1]}[#{els[3].strip}](#{search})"
        else
          l
        end
      end.join("\n")
    end
  end

  # Handle links in the form of [text](url) or [text](url "title")
  if input =~ /\[(.*?)\]\((.*?)\)/
    parse_brackets(input)
  else # Assume single line input
    parse_single_line(input)
  end
end

#restore_prev_configObject

Reset configuration



296
297
298
299
300
301
302
# File 'lib/searchlink/config.rb', line 296

def restore_prev_config
  @prev_config&.each do |k, v|
    SL.config[k] = v
    $stderr.print "\r\033[0KReset config: #{k} = #{SL.config[k]}\n" unless SILENT
  end
  @prev_config = {}
end

#write_new_plugin_configObject

Add new keys to config if don’t exist



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/searchlink/config.rb', line 221

def write_new_plugin_config
  default_config = IO.read(config_file)
  new_config = ""
  SL::Searches.plugins[:search].each_value do |plugin|
    next unless plugin.key?(:config) && !plugin[:config].nil? && !plugin[:config].empty?

    plugin[:config].each do |cfg|
      next if default_config =~ /^(# *)?#{cfg[:key]}:/

      new_config += get_plugin_config(cfg)
    end
  end

  return if new_config.empty?

  File.open(config_file, "w") { |f| f.puts default_config + new_config }
end