searchlink 2.3.59
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 +7 -0
- data/bin/searchlink +84 -0
- data/lib/searchlink/array.rb +7 -0
- data/lib/searchlink/config.rb +230 -0
- data/lib/searchlink/curl/html.rb +482 -0
- data/lib/searchlink/curl/json.rb +90 -0
- data/lib/searchlink/curl.rb +7 -0
- data/lib/searchlink/help.rb +103 -0
- data/lib/searchlink/output.rb +270 -0
- data/lib/searchlink/parse.rb +668 -0
- data/lib/searchlink/plist.rb +213 -0
- data/lib/searchlink/search.rb +70 -0
- data/lib/searchlink/searches/amazon.rb +25 -0
- data/lib/searchlink/searches/applemusic.rb +123 -0
- data/lib/searchlink/searches/bitly.rb +50 -0
- data/lib/searchlink/searches/definition.rb +67 -0
- data/lib/searchlink/searches/duckduckgo.rb +167 -0
- data/lib/searchlink/searches/github.rb +245 -0
- data/lib/searchlink/searches/google.rb +67 -0
- data/lib/searchlink/searches/helpers/chromium.rb +318 -0
- data/lib/searchlink/searches/helpers/firefox.rb +135 -0
- data/lib/searchlink/searches/helpers/safari.rb +133 -0
- data/lib/searchlink/searches/history.rb +166 -0
- data/lib/searchlink/searches/hook.rb +77 -0
- data/lib/searchlink/searches/itunes.rb +97 -0
- data/lib/searchlink/searches/lastfm.rb +41 -0
- data/lib/searchlink/searches/lyrics.rb +91 -0
- data/lib/searchlink/searches/pinboard.rb +183 -0
- data/lib/searchlink/searches/social.rb +105 -0
- data/lib/searchlink/searches/software.rb +27 -0
- data/lib/searchlink/searches/spelling.rb +59 -0
- data/lib/searchlink/searches/spotlight.rb +28 -0
- data/lib/searchlink/searches/stackoverflow.rb +31 -0
- data/lib/searchlink/searches/tmdb.rb +52 -0
- data/lib/searchlink/searches/twitter.rb +46 -0
- data/lib/searchlink/searches/wikipedia.rb +33 -0
- data/lib/searchlink/searches/youtube.rb +48 -0
- data/lib/searchlink/searches.rb +194 -0
- data/lib/searchlink/semver.rb +140 -0
- data/lib/searchlink/string.rb +469 -0
- data/lib/searchlink/url.rb +153 -0
- data/lib/searchlink/util.rb +87 -0
- data/lib/searchlink/version.rb +93 -0
- data/lib/searchlink/which.rb +175 -0
- data/lib/searchlink.rb +66 -0
- data/lib/tokens.rb +3 -0
- metadata +299 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
module SL
|
|
2
|
+
module URL
|
|
3
|
+
class << self
|
|
4
|
+
# Validates that a link exists and returns 200
|
|
5
|
+
def valid_link?(uri_str, limit = 5)
|
|
6
|
+
return false unless uri_str
|
|
7
|
+
|
|
8
|
+
SL.notify('Validating', uri_str)
|
|
9
|
+
return false if limit.zero?
|
|
10
|
+
|
|
11
|
+
url = URI(uri_str)
|
|
12
|
+
return true unless url.scheme
|
|
13
|
+
|
|
14
|
+
url.path = '/' if url.path == ''
|
|
15
|
+
# response = Net::HTTP.get_response(URI(uri_str))
|
|
16
|
+
response = false
|
|
17
|
+
|
|
18
|
+
Net::HTTP.start(url.host, url.port, use_ssl: url.scheme == 'https') do |http|
|
|
19
|
+
response = http.request_head(url.path)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
case response
|
|
23
|
+
when Net::HTTPMethodNotAllowed, Net::HTTPServiceUnavailable
|
|
24
|
+
unless /amazon\.com/ =~ url.host
|
|
25
|
+
SL.add_error('link validation', "Validation blocked: #{uri_str} (#{e})")
|
|
26
|
+
end
|
|
27
|
+
SL.notify('Error validating', uri_str)
|
|
28
|
+
true
|
|
29
|
+
when Net::HTTPSuccess
|
|
30
|
+
true
|
|
31
|
+
when Net::HTTPRedirection
|
|
32
|
+
location = response['location']
|
|
33
|
+
valid_link?(location, limit - 1)
|
|
34
|
+
else
|
|
35
|
+
SL.notify('Error validating', uri_str)
|
|
36
|
+
false
|
|
37
|
+
end
|
|
38
|
+
rescue StandardError => e
|
|
39
|
+
SL.notify('Error validating', uri_str)
|
|
40
|
+
SL.add_error('link validation', "Possibly invalid => #{uri_str} (#{e})")
|
|
41
|
+
true
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def url?(input)
|
|
45
|
+
input =~ %r{^(#.*|https?://\S+|/\S+|\S+/|[^!]\S+\.\S+)(\s+".*?")?$}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def only_url?(input)
|
|
49
|
+
input =~ %r{(?i)^((http|https)://)?([\w\-_]+(\.[\w\-_]+)+)([\w\-.,@?^=%&:/~+#]*[\w\-@^=%&/~+#])?$}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def ref_title_for_url(url)
|
|
53
|
+
url = URI.parse(url) if url.is_a?(String)
|
|
54
|
+
|
|
55
|
+
parts = url.hostname.split(/\./)
|
|
56
|
+
domain = if parts.count > 1
|
|
57
|
+
parts.slice(-2, 1).join('')
|
|
58
|
+
else
|
|
59
|
+
parts.join('')
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
path = url.path.split(%r{/}).last
|
|
63
|
+
if path
|
|
64
|
+
path.gsub!(/-/, ' ').gsub!(/\.\w{2-4}$/, '')
|
|
65
|
+
else
|
|
66
|
+
path = domain
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
path.length > domain.length ? path : domain
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def url_to_link(url, type)
|
|
73
|
+
input = url.dup
|
|
74
|
+
|
|
75
|
+
if only_url?(input)
|
|
76
|
+
input.sub!(%r{(?mi)^(?!https?://)(.*?)$}, 'https://\1')
|
|
77
|
+
url = URI.parse(input.downcase)
|
|
78
|
+
|
|
79
|
+
title = if type == :ref_title
|
|
80
|
+
ref_title_for_url(url)
|
|
81
|
+
else
|
|
82
|
+
title(url.to_s) || input.sub(%r{^https?://}, '')
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
return [url.to_s, title] if url.hostname
|
|
86
|
+
end
|
|
87
|
+
false
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def amazon_affiliatize(url, amazon_partner)
|
|
91
|
+
return url if amazon_partner.nil? || amazon_partner.empty?
|
|
92
|
+
|
|
93
|
+
unless url =~ %r{https?://(?<subdomain>.*?)amazon.com/(?:(?<title>.*?)/)?(?<type>[dg])p/(?<id>[^?]+)}
|
|
94
|
+
return [url, '']
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
m = Regexp.last_match
|
|
98
|
+
sd = m['subdomain']
|
|
99
|
+
title = m['title'].gsub(/-/, ' ')
|
|
100
|
+
t = m['type']
|
|
101
|
+
id = m['id']
|
|
102
|
+
["https://#{sd}amazon.com/#{t}p/#{id}/?ref=as_li_ss_tl&ie=UTF8&linkCode=sl1&tag=#{amazon_partner}", title]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def title(url)
|
|
106
|
+
title = nil
|
|
107
|
+
|
|
108
|
+
## Gather proving too inexact
|
|
109
|
+
# gather = false
|
|
110
|
+
# ['/usr/local/bin', '/opt/homebrew/bin'].each do |root|
|
|
111
|
+
# if File.exist?(File.join(root, 'gather')) && File.executable?(File.join(root, 'gather'))
|
|
112
|
+
# gather = File.join(root, 'gather')
|
|
113
|
+
# break
|
|
114
|
+
# end
|
|
115
|
+
# end
|
|
116
|
+
|
|
117
|
+
# if gather
|
|
118
|
+
# cmd = %(#{gather} --title-only '#{url.strip}' --fallback-title 'Unknown')
|
|
119
|
+
# title = SL::Util.exec_with_timeout(cmd, 15)
|
|
120
|
+
# if title
|
|
121
|
+
# title = title.strip.gsub(/\n+/, ' ').gsub(/ +/, ' ')
|
|
122
|
+
# title.remove_seo!(url) if SL.config['remove_seo']
|
|
123
|
+
# return title.remove_protocol
|
|
124
|
+
# else
|
|
125
|
+
# SL.add_error('Error retrieving title', "Gather timed out on #{url}")
|
|
126
|
+
# SL.notify('Error retrieving title', 'Gather timed out')
|
|
127
|
+
# end
|
|
128
|
+
# end
|
|
129
|
+
|
|
130
|
+
begin
|
|
131
|
+
page = Curl::Html.new(url)
|
|
132
|
+
|
|
133
|
+
title = page.title || nil
|
|
134
|
+
|
|
135
|
+
if title.nil? || title =~ /^\s*$/
|
|
136
|
+
SL.add_error('Title not found', "Warning: missing title for #{url.strip}")
|
|
137
|
+
title = url.gsub(%r{(^https?://|/.*$)}, '').gsub(/-/, ' ').strip
|
|
138
|
+
else
|
|
139
|
+
title = title.gsub(/\n/, ' ').gsub(/\s+/, ' ').strip # .sub(/[^a-z]*$/i,'')
|
|
140
|
+
title.remove_seo!(url) if SL.config['remove_seo']
|
|
141
|
+
end
|
|
142
|
+
title.gsub!(/\|/, '—')
|
|
143
|
+
title.remove_seo!(url.strip) if SL.config['remove_seo']
|
|
144
|
+
title.remove_protocol
|
|
145
|
+
rescue StandardError
|
|
146
|
+
SL.add_error('Error retrieving title', "Error determining title for #{url.strip}")
|
|
147
|
+
warn "Error retrieving title for #{url.strip}"
|
|
148
|
+
url.remove_protocol
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module SL
|
|
2
|
+
module Util
|
|
3
|
+
class << self
|
|
4
|
+
## Execute system command with deadman's switch
|
|
5
|
+
##
|
|
6
|
+
## <https://stackoverflow.com/questions/8292031/ruby-timeouts-and-system-commands>
|
|
7
|
+
## <https://stackoverflow.com/questions/12189904/fork-child-process-with-timeout-and-capture-output>
|
|
8
|
+
##
|
|
9
|
+
## @param cmd The command to execute
|
|
10
|
+
## @param timeout The timeout
|
|
11
|
+
##
|
|
12
|
+
## @return [String] STDOUT output
|
|
13
|
+
##
|
|
14
|
+
def exec_with_timeout(cmd, timeout)
|
|
15
|
+
begin
|
|
16
|
+
# stdout, stderr pipes
|
|
17
|
+
rout, wout = IO.pipe
|
|
18
|
+
rerr, werr = IO.pipe
|
|
19
|
+
stdout, stderr = nil
|
|
20
|
+
|
|
21
|
+
pid = Process.spawn(cmd, pgroup: true, out: wout, err: werr)
|
|
22
|
+
|
|
23
|
+
Timeout.timeout(timeout) do
|
|
24
|
+
Process.waitpid(pid)
|
|
25
|
+
|
|
26
|
+
# close write ends so we can read from them
|
|
27
|
+
wout.close
|
|
28
|
+
werr.close
|
|
29
|
+
|
|
30
|
+
stdout = rout.readlines.join
|
|
31
|
+
stderr = rerr.readlines.join
|
|
32
|
+
end
|
|
33
|
+
rescue Timeout::Error
|
|
34
|
+
Process.kill(-9, pid)
|
|
35
|
+
Process.detach(pid)
|
|
36
|
+
ensure
|
|
37
|
+
wout.close unless wout.closed?
|
|
38
|
+
werr.close unless werr.closed?
|
|
39
|
+
# dispose the read ends of the pipes
|
|
40
|
+
rout.close
|
|
41
|
+
rerr.close
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
stdout&.strip
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
##
|
|
48
|
+
## Execute a search with deadman's switch
|
|
49
|
+
##
|
|
50
|
+
## @param search [Proc] The search command
|
|
51
|
+
## @param timeout [Number] The timeout
|
|
52
|
+
##
|
|
53
|
+
## @return [Array] url, title, link_text
|
|
54
|
+
##
|
|
55
|
+
def search_with_timeout(search, timeout)
|
|
56
|
+
url = nil
|
|
57
|
+
title = nil
|
|
58
|
+
link_text = nil
|
|
59
|
+
|
|
60
|
+
begin
|
|
61
|
+
Timeout.timeout(timeout) do
|
|
62
|
+
url, title, link_text = search.call
|
|
63
|
+
end
|
|
64
|
+
rescue Timeout::Error
|
|
65
|
+
SL.add_error('Timeout', 'Search timed out')
|
|
66
|
+
url, title, link_text = false
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
[url, title, link_text]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
##
|
|
73
|
+
## Get the path for a cache file
|
|
74
|
+
##
|
|
75
|
+
## @param filename [String] The filename to
|
|
76
|
+
## generate the cache for
|
|
77
|
+
##
|
|
78
|
+
## @return [String] path to new cache file
|
|
79
|
+
##
|
|
80
|
+
def cache_file_for(filename)
|
|
81
|
+
cache_folder = File.expand_path('~/.local/share/searchlink/cache')
|
|
82
|
+
FileUtils.mkdir_p(cache_folder) unless File.directory?(cache_folder)
|
|
83
|
+
File.join(cache_folder, filename.sub(/(\.cache)?$/, '.cache'))
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
module SL
|
|
2
|
+
VERSION = '2.3.59'
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
module SL
|
|
6
|
+
class << self
|
|
7
|
+
def version_check
|
|
8
|
+
cachefile = File.expand_path('~/.searchlink_update_check')
|
|
9
|
+
if File.exist?(cachefile)
|
|
10
|
+
last_check, latest_tag = IO.read(cachefile).strip.split(/\|/)
|
|
11
|
+
last_time = Time.parse(last_check)
|
|
12
|
+
else
|
|
13
|
+
latest_tag = new_version?
|
|
14
|
+
last_time = Time.now
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
if last_time + (24 * 60 * 60) < Time.now
|
|
18
|
+
latest_tag = new_version?
|
|
19
|
+
last_time = Time.now
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
latest_tag ||= SL::VERSION
|
|
23
|
+
latest = SemVer.new(latest_tag)
|
|
24
|
+
current = SemVer.new(SL::VERSION)
|
|
25
|
+
|
|
26
|
+
File.open(cachefile, 'w') { |f| f.puts("#{last_time.strftime('%c')}|#{latest.to_s}") }
|
|
27
|
+
|
|
28
|
+
return "SearchLink v#{current.to_s}, #{latest.to_s} available. Run 'update' to download." if latest_tag && current.older_than(latest)
|
|
29
|
+
|
|
30
|
+
"SearchLink v#{current.to_s}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Check for a newer version than local copy using GitHub release tag
|
|
34
|
+
#
|
|
35
|
+
# @return false if no new version, or semantic version of latest release
|
|
36
|
+
def new_version?
|
|
37
|
+
headers = {
|
|
38
|
+
'Accept' => 'application/vnd.github+json',
|
|
39
|
+
'X-GitHub-Api-Version' => '2022-11-28'
|
|
40
|
+
}
|
|
41
|
+
headers['Authorization'] = "Bearer #{Secrets::GH_AUTH_TOKEN}" if defined? Secrets::GH_AUTH_TOKEN
|
|
42
|
+
|
|
43
|
+
url = 'https://api.github.com/repos/ttscoff/searchlink/releases/latest'
|
|
44
|
+
page = Curl::Json.new(url, headers: headers)
|
|
45
|
+
result = page.json
|
|
46
|
+
|
|
47
|
+
if result
|
|
48
|
+
latest_tag = result['tag_name']
|
|
49
|
+
|
|
50
|
+
return false unless latest_tag
|
|
51
|
+
|
|
52
|
+
return false if latest_tag =~ /^#{Regexp.escape(SL::VERSION)}$/
|
|
53
|
+
|
|
54
|
+
latest = SemVer.new(latest_tag)
|
|
55
|
+
current = SemVer.new(SL::VERSION)
|
|
56
|
+
|
|
57
|
+
return latest_tag if current.older_than(latest)
|
|
58
|
+
else
|
|
59
|
+
warn 'Check for new version failed.'
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
false
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def update_searchlink
|
|
66
|
+
new_version = SL.new_version?
|
|
67
|
+
if new_version
|
|
68
|
+
folder = File.expand_path('~/Downloads')
|
|
69
|
+
services = File.expand_path('~/Library/Services')
|
|
70
|
+
dl = File.join(folder, 'SearchLink.zip')
|
|
71
|
+
curl = TTY::Which.which('curl')
|
|
72
|
+
`#{curl} -SsL -o "#{dl}" https://github.com/ttscoff/searchlink/releases/latest/download/SearchLink.zip`
|
|
73
|
+
Dir.chdir(folder)
|
|
74
|
+
`unzip -qo #{dl} -d #{folder}`
|
|
75
|
+
FileUtils.rm(dl)
|
|
76
|
+
|
|
77
|
+
['SearchLink', 'SearchLink File', 'Jump to SearchLink Error'].each do |workflow|
|
|
78
|
+
wflow = "#{workflow}.workflow"
|
|
79
|
+
src = File.join(folder, 'SearchLink Services', wflow)
|
|
80
|
+
dest = File.join(services, wflow)
|
|
81
|
+
if File.exist?(src) && File.exist?(dest)
|
|
82
|
+
FileUtils.rm_rf(dest)
|
|
83
|
+
FileUtils.mv(src, dest, force: true)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
add_output("Installed SearchLink #{new_version}")
|
|
87
|
+
FileUtils.rm_rf('SearchLink Services')
|
|
88
|
+
else
|
|
89
|
+
add_output('Already up to date.')
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# From <https://github.com/piotrmurach/tty-which/tree/master>
|
|
4
|
+
# [MIT License](https://github.com/piotrmurach/tty-which/blob/master/LICENSE.txt)
|
|
5
|
+
|
|
6
|
+
module TTY
|
|
7
|
+
module Which
|
|
8
|
+
VERSION = "0.5.0"
|
|
9
|
+
end # Which
|
|
10
|
+
end # TTY
|
|
11
|
+
|
|
12
|
+
module TTY
|
|
13
|
+
# A class responsible for finding an executable in the PATH
|
|
14
|
+
module Which
|
|
15
|
+
# Find an executable in a platform independent way
|
|
16
|
+
#
|
|
17
|
+
# @param [String] cmd
|
|
18
|
+
# the command to search for
|
|
19
|
+
# @param [Array<String>] paths
|
|
20
|
+
# the paths to look through
|
|
21
|
+
#
|
|
22
|
+
# @example
|
|
23
|
+
# which("ruby") # => "/usr/local/bin/ruby"
|
|
24
|
+
# which("/usr/local/bin/ruby") # => "/usr/local/bin/ruby"
|
|
25
|
+
# which("foo") # => nil
|
|
26
|
+
#
|
|
27
|
+
# @example
|
|
28
|
+
# which("ruby", paths: ["/usr/locale/bin", "/usr/bin", "/bin"])
|
|
29
|
+
#
|
|
30
|
+
# @return [String, nil]
|
|
31
|
+
# the absolute path to executable if found, `nil` otherwise
|
|
32
|
+
#
|
|
33
|
+
# @api public
|
|
34
|
+
def which(cmd, paths: search_paths)
|
|
35
|
+
if file_with_path?(cmd)
|
|
36
|
+
return cmd if executable_file?(cmd)
|
|
37
|
+
|
|
38
|
+
extensions.each do |ext|
|
|
39
|
+
exe = "#{cmd}#{ext}"
|
|
40
|
+
return ::File.absolute_path(exe) if executable_file?(exe)
|
|
41
|
+
end
|
|
42
|
+
return nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
paths.each do |path|
|
|
46
|
+
if file_with_exec_ext?(cmd)
|
|
47
|
+
exe = ::File.join(path, cmd)
|
|
48
|
+
return ::File.absolute_path(exe) if executable_file?(exe)
|
|
49
|
+
end
|
|
50
|
+
extensions.each do |ext|
|
|
51
|
+
exe = ::File.join(path, "#{cmd}#{ext}")
|
|
52
|
+
return ::File.absolute_path(exe) if executable_file?(exe)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
module_function :which
|
|
58
|
+
|
|
59
|
+
# Check if executable exists in the path
|
|
60
|
+
#
|
|
61
|
+
# @param [String] cmd
|
|
62
|
+
# the executable to check
|
|
63
|
+
#
|
|
64
|
+
# @param [Array<String>] paths
|
|
65
|
+
# paths to check
|
|
66
|
+
#
|
|
67
|
+
# @return [Boolean]
|
|
68
|
+
#
|
|
69
|
+
# @api public
|
|
70
|
+
def exist?(cmd, paths: search_paths)
|
|
71
|
+
!which(cmd, paths: paths).nil?
|
|
72
|
+
end
|
|
73
|
+
module_function :exist?
|
|
74
|
+
|
|
75
|
+
# Find default system paths
|
|
76
|
+
#
|
|
77
|
+
# @param [String] path
|
|
78
|
+
# the path to search through
|
|
79
|
+
#
|
|
80
|
+
# @example
|
|
81
|
+
# search_paths("/usr/local/bin:/bin")
|
|
82
|
+
# # => ["/bin"]
|
|
83
|
+
#
|
|
84
|
+
# @return [Array<String>]
|
|
85
|
+
# the array of paths to search
|
|
86
|
+
#
|
|
87
|
+
# @api private
|
|
88
|
+
def search_paths(path = ENV["PATH"])
|
|
89
|
+
paths = if path && !path.empty?
|
|
90
|
+
path.split(::File::PATH_SEPARATOR)
|
|
91
|
+
else
|
|
92
|
+
%w[/usr/local/bin /usr/ucb /usr/bin /bin /opt/homebrew/bin]
|
|
93
|
+
end
|
|
94
|
+
paths.select(&Dir.method(:exist?))
|
|
95
|
+
end
|
|
96
|
+
module_function :search_paths
|
|
97
|
+
|
|
98
|
+
# All possible file extensions
|
|
99
|
+
#
|
|
100
|
+
# @example
|
|
101
|
+
# extensions(".exe;cmd;.bat")
|
|
102
|
+
# # => [".exe", ".bat"]
|
|
103
|
+
#
|
|
104
|
+
# @param [String] path_ext
|
|
105
|
+
# a string of semicolon separated filename extensions
|
|
106
|
+
#
|
|
107
|
+
# @return [Array<String>]
|
|
108
|
+
# an array with valid file extensions
|
|
109
|
+
#
|
|
110
|
+
# @api private
|
|
111
|
+
def extensions(path_ext = ENV["PATHEXT"])
|
|
112
|
+
return [""] unless path_ext
|
|
113
|
+
|
|
114
|
+
path_ext.split(::File::PATH_SEPARATOR).select { |part| part.include?(".") }
|
|
115
|
+
end
|
|
116
|
+
module_function :extensions
|
|
117
|
+
|
|
118
|
+
# Determines if filename is an executable file
|
|
119
|
+
#
|
|
120
|
+
# @example Basic usage
|
|
121
|
+
# executable_file?("/usr/bin/less") # => true
|
|
122
|
+
#
|
|
123
|
+
# @example Executable in directory
|
|
124
|
+
# executable_file?("less", "/usr/bin") # => true
|
|
125
|
+
# executable_file?("less", "/usr") # => false
|
|
126
|
+
#
|
|
127
|
+
# @param [String] filename
|
|
128
|
+
# the path to file
|
|
129
|
+
# @param [String] dir
|
|
130
|
+
# the directory within which to search for filename
|
|
131
|
+
#
|
|
132
|
+
# @return [Boolean]
|
|
133
|
+
#
|
|
134
|
+
# @api private
|
|
135
|
+
def executable_file?(filename, dir = nil)
|
|
136
|
+
path = ::File.join(dir, filename) if dir
|
|
137
|
+
path ||= filename
|
|
138
|
+
::File.file?(path) && ::File.executable?(path)
|
|
139
|
+
end
|
|
140
|
+
module_function :executable_file?
|
|
141
|
+
|
|
142
|
+
# Check if command itself has executable extension
|
|
143
|
+
#
|
|
144
|
+
# @param [String] filename
|
|
145
|
+
# the path to executable file
|
|
146
|
+
#
|
|
147
|
+
# @example
|
|
148
|
+
# file_with_exec_ext?("file.bat")
|
|
149
|
+
# # => true
|
|
150
|
+
#
|
|
151
|
+
# @return [Boolean]
|
|
152
|
+
#
|
|
153
|
+
# @api private
|
|
154
|
+
def file_with_exec_ext?(filename)
|
|
155
|
+
extension = ::File.extname(filename)
|
|
156
|
+
return false if extension.empty?
|
|
157
|
+
|
|
158
|
+
extensions.any? { |ext| extension.casecmp(ext).zero? }
|
|
159
|
+
end
|
|
160
|
+
module_function :file_with_exec_ext?
|
|
161
|
+
|
|
162
|
+
# Check if executable file is part of absolute/relative path
|
|
163
|
+
#
|
|
164
|
+
# @param [String] cmd
|
|
165
|
+
# the executable to check
|
|
166
|
+
#
|
|
167
|
+
# @return [Boolean]
|
|
168
|
+
#
|
|
169
|
+
# @api private
|
|
170
|
+
def file_with_path?(cmd)
|
|
171
|
+
::File.expand_path(cmd) == cmd
|
|
172
|
+
end
|
|
173
|
+
module_function :file_with_path?
|
|
174
|
+
end # Which
|
|
175
|
+
end # TTY
|
data/lib/searchlink.rb
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SearchLink by Brett Terpstra 2015 <http://brettterpstra.com/projects/searchlink/>
|
|
4
|
+
# MIT License, please maintain attribution
|
|
5
|
+
require 'net/https'
|
|
6
|
+
require 'uri'
|
|
7
|
+
require 'rexml/document'
|
|
8
|
+
require 'shellwords'
|
|
9
|
+
require 'yaml'
|
|
10
|
+
require 'cgi'
|
|
11
|
+
require 'fileutils'
|
|
12
|
+
require 'tempfile'
|
|
13
|
+
require 'zlib'
|
|
14
|
+
require 'time'
|
|
15
|
+
require 'json'
|
|
16
|
+
require 'erb'
|
|
17
|
+
|
|
18
|
+
# import
|
|
19
|
+
require 'tokens' if File.exist?('lib/tokens.rb')
|
|
20
|
+
|
|
21
|
+
# import
|
|
22
|
+
require 'searchlink/util'
|
|
23
|
+
|
|
24
|
+
# import
|
|
25
|
+
require 'searchlink/curl'
|
|
26
|
+
|
|
27
|
+
# import
|
|
28
|
+
require 'searchlink/semver'
|
|
29
|
+
|
|
30
|
+
# import
|
|
31
|
+
require 'searchlink/version'
|
|
32
|
+
|
|
33
|
+
# import
|
|
34
|
+
require 'searchlink/array'
|
|
35
|
+
|
|
36
|
+
# import
|
|
37
|
+
require 'searchlink/string'
|
|
38
|
+
|
|
39
|
+
# import
|
|
40
|
+
require 'searchlink/plist'
|
|
41
|
+
|
|
42
|
+
# import
|
|
43
|
+
require 'searchlink/config'
|
|
44
|
+
|
|
45
|
+
# import
|
|
46
|
+
require 'searchlink/searches'
|
|
47
|
+
|
|
48
|
+
# import
|
|
49
|
+
require 'searchlink/url'
|
|
50
|
+
|
|
51
|
+
# import
|
|
52
|
+
require 'searchlink/search'
|
|
53
|
+
|
|
54
|
+
# import
|
|
55
|
+
require 'searchlink/help'
|
|
56
|
+
|
|
57
|
+
# import
|
|
58
|
+
require 'searchlink/parse'
|
|
59
|
+
|
|
60
|
+
# import
|
|
61
|
+
require 'searchlink/output'
|
|
62
|
+
|
|
63
|
+
# import
|
|
64
|
+
require 'searchlink/which'
|
|
65
|
+
|
|
66
|
+
module Secrets; end
|
data/lib/tokens.rb
ADDED