booker 1.2.1 → 2.0.0
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 +4 -4
- data/bin/booker +3 -3
- data/completions/_booker +115 -0
- data/completions/booker.bash +52 -0
- data/completions/booker.fish +49 -0
- data/lib/booker/bookmarks.rb +164 -0
- data/lib/booker/cli.rb +226 -0
- data/lib/booker/config.rb +219 -0
- data/lib/booker/installer.rb +396 -0
- data/lib/booker/output.rb +84 -0
- data/lib/booker/parsers.rb +294 -0
- data/lib/booker/picker.rb +139 -0
- data/lib/booker/version.rb +5 -0
- data/lib/booker.rb +14 -423
- metadata +41 -24
- data/lib/bookmarks.rb +0 -324
- data/lib/config.rb +0 -187
- data/lib/consts.rb +0 -114
data/lib/bookmarks.rb
DELETED
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
# grab/parse bookmarks from json file on computer
|
|
2
|
-
require "fileutils"
|
|
3
|
-
|
|
4
|
-
# get int number of columns in half of screen
|
|
5
|
-
def terminal_width
|
|
6
|
-
guess = `tput cols`.to_i
|
|
7
|
-
(guess == 0) ? 100 : guess
|
|
8
|
-
end
|
|
9
|
-
TERMWIDTH = terminal_width
|
|
10
|
-
|
|
11
|
-
# compl. color codes space
|
|
12
|
-
CODEWIDTH = 16
|
|
13
|
-
|
|
14
|
-
# add some colors, windowing methods
|
|
15
|
-
class String
|
|
16
|
-
def window(width)
|
|
17
|
-
if length >= width
|
|
18
|
-
self[0..width - 1]
|
|
19
|
-
else
|
|
20
|
-
ljust(width)
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def colorize(color, mod)
|
|
25
|
-
"\033[#{mod};#{color};49m#{self}\033[0;0m"
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def reset
|
|
29
|
-
colorize(0, 0)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def blu
|
|
33
|
-
colorize(34, 0)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def cyan
|
|
37
|
-
colorize(36, 0)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def yel
|
|
41
|
-
colorize(33, 0)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def grn
|
|
45
|
-
colorize(32, 0)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def red
|
|
49
|
-
colorize(31, 0)
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Abstract base class for bookmark parsers
|
|
54
|
-
class BookmarkParser
|
|
55
|
-
attr_reader :allurls
|
|
56
|
-
|
|
57
|
-
def initialize(file_path, search_term)
|
|
58
|
-
@file_path = file_path
|
|
59
|
-
@searching = /#{search_term}/i
|
|
60
|
-
@allurls = []
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Abstract method - must be implemented by subclasses
|
|
64
|
-
def parse
|
|
65
|
-
raise NotImplementedError, "Subclasses must implement parse method"
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def results
|
|
69
|
-
@allurls
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
protected
|
|
73
|
-
|
|
74
|
-
def matches_search?(values)
|
|
75
|
-
values.any? { |v| v && @searching.match(v.to_s) }
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Chrome/Chromium bookmark parser (JSON format)
|
|
80
|
-
class ChromeBookmarkParser < BookmarkParser
|
|
81
|
-
def parse
|
|
82
|
-
begin
|
|
83
|
-
local_bookmarks = JSON.parse(File.read(@file_path))
|
|
84
|
-
@chrome_bookmarks = local_bookmarks["roots"]["bookmark_bar"]["children"]
|
|
85
|
-
rescue
|
|
86
|
-
puts "Warning: ".yel + "Bookmarks file not found or invalid."
|
|
87
|
-
puts "Suggest: ".grn + "booker --install bookmarks"
|
|
88
|
-
@chrome_bookmarks = []
|
|
89
|
-
return
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
parse_recursive
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
private
|
|
96
|
-
|
|
97
|
-
def parse_recursive(root = nil)
|
|
98
|
-
root = Folder.new(@chrome_bookmarks, "|") if root.nil?
|
|
99
|
-
|
|
100
|
-
root.json.each { |x| parse_link(root.title, x) }
|
|
101
|
-
root.json.each { |x| parse_folder(root, x) }
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def parse_link(base, link)
|
|
105
|
-
checking = [base, link["name"], link["url"], link["id"]]
|
|
106
|
-
if matches_search?(checking)
|
|
107
|
-
if link["type"] == "url"
|
|
108
|
-
@allurls.push(Bookmark.new(base, link["name"], link["url"], link["id"]))
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def parse_folder(base, link)
|
|
114
|
-
if link["type"] == "folder"
|
|
115
|
-
title = base.title + link["name"] + "/"
|
|
116
|
-
subdir = Folder.new(link["children"], title)
|
|
117
|
-
parse_recursive(subdir)
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
# Firefox bookmark parser (SQLite format)
|
|
123
|
-
class FirefoxBookmarkParser < BookmarkParser
|
|
124
|
-
def parse
|
|
125
|
-
begin
|
|
126
|
-
require "sqlite3"
|
|
127
|
-
rescue LoadError
|
|
128
|
-
puts "Error: ".red + "sqlite3 gem not installed"
|
|
129
|
-
puts "Run: ".grn + "gem install sqlite3"
|
|
130
|
-
return
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
db_path = @file_path
|
|
134
|
-
|
|
135
|
-
# Copy database to temp file if Firefox is running (to avoid lock)
|
|
136
|
-
if firefox_running?
|
|
137
|
-
require "tempfile"
|
|
138
|
-
temp = Tempfile.new(["places", ".sqlite"])
|
|
139
|
-
temp.close
|
|
140
|
-
FileUtils.cp(@file_path, temp.path)
|
|
141
|
-
db_path = temp.path
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
begin
|
|
145
|
-
db = SQLite3::Database.new(db_path)
|
|
146
|
-
db.results_as_hash = true
|
|
147
|
-
|
|
148
|
-
# Recursive CTE query to build folder paths
|
|
149
|
-
query = <<-SQL
|
|
150
|
-
WITH RECURSIVE bookmark_tree(id, parent, title, url, path) AS (
|
|
151
|
-
-- Start with bookmarks toolbar root
|
|
152
|
-
SELECT b.id, b.parent, b.title, p.url,
|
|
153
|
-
CAST(b.title as TEXT) as path
|
|
154
|
-
FROM moz_bookmarks b
|
|
155
|
-
LEFT JOIN moz_places p ON b.fk = p.id
|
|
156
|
-
WHERE b.parent = (
|
|
157
|
-
SELECT id FROM moz_bookmarks
|
|
158
|
-
WHERE guid = 'toolbar_____'
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
UNION ALL
|
|
162
|
-
|
|
163
|
-
-- Recursively get children
|
|
164
|
-
SELECT b.id, b.parent, b.title, p.url,
|
|
165
|
-
CAST(bt.path || '/' || b.title as TEXT) as path
|
|
166
|
-
FROM moz_bookmarks b
|
|
167
|
-
INNER JOIN bookmark_tree bt ON b.parent = bt.id
|
|
168
|
-
LEFT JOIN moz_places p ON b.fk = p.id
|
|
169
|
-
)
|
|
170
|
-
SELECT id, title, path, url
|
|
171
|
-
FROM bookmark_tree
|
|
172
|
-
WHERE url IS NOT NULL
|
|
173
|
-
ORDER BY path, title
|
|
174
|
-
SQL
|
|
175
|
-
|
|
176
|
-
db.execute(query).each do |row|
|
|
177
|
-
folder_path = row["path"].to_s.split("/")[0..-2].join("/") + "/"
|
|
178
|
-
folder_path = "|" + folder_path.gsub(/[:,'"]/, "-").downcase
|
|
179
|
-
|
|
180
|
-
values = [folder_path, row["title"], row["url"], row["id"].to_s]
|
|
181
|
-
if matches_search?(values)
|
|
182
|
-
@allurls << Bookmark.new(
|
|
183
|
-
folder_path,
|
|
184
|
-
row["title"] || "",
|
|
185
|
-
row["url"] || "",
|
|
186
|
-
row["id"].to_s
|
|
187
|
-
)
|
|
188
|
-
end
|
|
189
|
-
end
|
|
190
|
-
rescue SQLite3::Exception => e
|
|
191
|
-
puts "Error: ".red + "Could not read Firefox bookmarks database"
|
|
192
|
-
puts e.message
|
|
193
|
-
ensure
|
|
194
|
-
db&.close
|
|
195
|
-
temp&.unlink
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
private
|
|
200
|
-
|
|
201
|
-
def firefox_running?
|
|
202
|
-
# Simple check - try to get a shared lock
|
|
203
|
-
# If Firefox is running, it has an exclusive lock
|
|
204
|
-
return false unless File.exist?(@file_path)
|
|
205
|
-
|
|
206
|
-
begin
|
|
207
|
-
File.open(@file_path, "r") do |f|
|
|
208
|
-
# If we can open for reading, Firefox might still be running
|
|
209
|
-
# but we'll handle it by copying to temp
|
|
210
|
-
end
|
|
211
|
-
# Check for Firefox process
|
|
212
|
-
`pgrep -x firefox 2>/dev/null`.strip != ""
|
|
213
|
-
rescue
|
|
214
|
-
false
|
|
215
|
-
end
|
|
216
|
-
end
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
# Main Bookmarks facade class
|
|
220
|
-
class Bookmarks
|
|
221
|
-
def initialize(search_term = "")
|
|
222
|
-
@conf = BConfig.new
|
|
223
|
-
file_paths = @conf.bookmarks # Now returns an array
|
|
224
|
-
@allurls = []
|
|
225
|
-
|
|
226
|
-
# Parse bookmarks from all sources
|
|
227
|
-
file_paths.each_with_index do |file_path, source_index|
|
|
228
|
-
next unless file_path && File.exist?(file_path)
|
|
229
|
-
|
|
230
|
-
parser_class = detect_parser(file_path)
|
|
231
|
-
parser = parser_class.new(file_path, search_term)
|
|
232
|
-
parser.parse
|
|
233
|
-
|
|
234
|
-
# Merge results, prefixing IDs to ensure uniqueness across sources
|
|
235
|
-
parser.results.each do |bookmark|
|
|
236
|
-
# Create unique ID: source_index + original_id
|
|
237
|
-
unique_bookmark = Bookmark.new(
|
|
238
|
-
bookmark.folder,
|
|
239
|
-
bookmark.title,
|
|
240
|
-
bookmark.url,
|
|
241
|
-
"#{source_index}_#{bookmark.id}"
|
|
242
|
-
)
|
|
243
|
-
@allurls << unique_bookmark
|
|
244
|
-
end
|
|
245
|
-
end
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
# output for zsh autocompetion, print out id, title and cleaned url
|
|
249
|
-
def autocomplete
|
|
250
|
-
@allurls.each do |url|
|
|
251
|
-
name = clean_name(url)
|
|
252
|
-
link = clean_link(url)
|
|
253
|
-
puts url.id + ":" + name + ":" + link
|
|
254
|
-
end
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
# clean title for completion, delete anything not allowed in linktitle
|
|
258
|
-
def clean_name(url)
|
|
259
|
-
# Clean up folder display (remove leading |, show [root] for top-level)
|
|
260
|
-
folder = url.folder.gsub(/^\|/, "")
|
|
261
|
-
folder = (folder == "/") ? "[root]" : folder.chomp("/")
|
|
262
|
-
|
|
263
|
-
# Format: folder | title
|
|
264
|
-
name = folder + " |" + url.title.gsub(/[^a-z0-9\-\/_ ]/i, "")
|
|
265
|
-
name.squeeze!("-")
|
|
266
|
-
name.squeeze!(" ")
|
|
267
|
-
# Use half terminal width for name to leave room for URL
|
|
268
|
-
name.window([TERMWIDTH / 2, 50].min)
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
# clean link for completion, remove strange things from any linkurls
|
|
272
|
-
def clean_link(url)
|
|
273
|
-
link = url.url.gsub(/[,'"&?].*/, "")
|
|
274
|
-
link.gsub!(/.*:\/+/, "")
|
|
275
|
-
link.delete!(" ")
|
|
276
|
-
# Use half terminal width for URL
|
|
277
|
-
max_width = [TERMWIDTH / 2 - CODEWIDTH, 50].min
|
|
278
|
-
link[0..max_width]
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
# get link (from id number)
|
|
282
|
-
def bookmark_url(id)
|
|
283
|
-
@allurls.each do |url|
|
|
284
|
-
return url.url if id == url.id
|
|
285
|
-
end
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
private
|
|
289
|
-
|
|
290
|
-
def detect_parser(file_path)
|
|
291
|
-
if file_path&.end_with?(".sqlite")
|
|
292
|
-
FirefoxBookmarkParser
|
|
293
|
-
else
|
|
294
|
-
ChromeBookmarkParser
|
|
295
|
-
end
|
|
296
|
-
end
|
|
297
|
-
end # close bookmarks class
|
|
298
|
-
|
|
299
|
-
# for recursively parsing bookmarks
|
|
300
|
-
class Folder
|
|
301
|
-
include Enumerable
|
|
302
|
-
|
|
303
|
-
attr_reader :json, :title
|
|
304
|
-
def initialize(json, title = "|")
|
|
305
|
-
@title = title.gsub(/[:,'"]/, "-").downcase
|
|
306
|
-
@json = json
|
|
307
|
-
end
|
|
308
|
-
|
|
309
|
-
# needed for Enumerable
|
|
310
|
-
def each
|
|
311
|
-
@json.each
|
|
312
|
-
end
|
|
313
|
-
end
|
|
314
|
-
|
|
315
|
-
# clean bookmark title, set attrs
|
|
316
|
-
class Bookmark
|
|
317
|
-
attr_reader :title, :folder, :url, :id
|
|
318
|
-
def initialize(f, t, u, id)
|
|
319
|
-
@title = t.gsub(/[:'"+]/, " ").downcase
|
|
320
|
-
@folder = f
|
|
321
|
-
@url = u
|
|
322
|
-
@id = id
|
|
323
|
-
end
|
|
324
|
-
end
|
data/lib/config.rb
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
# configuation - get where bookmarks are
|
|
2
|
-
|
|
3
|
-
# detect operating system
|
|
4
|
-
module OS
|
|
5
|
-
def self.windows?
|
|
6
|
-
(/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
def self.mac?
|
|
10
|
-
(/darwin/ =~ RUBY_PLATFORM) != nil
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def self.linux?
|
|
14
|
-
!(OS.windows? or OS.mac?)
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# return browser (chrome) opening command
|
|
19
|
-
module Browser
|
|
20
|
-
extend OS
|
|
21
|
-
|
|
22
|
-
def browse
|
|
23
|
-
if OS.windows?
|
|
24
|
-
# alternatively, start seems to work - probably check if powershell v cygwin?
|
|
25
|
-
'/cygdrive/c/Program\ Files\ \(x86\)/Google/Chrome/Application/chrome.exe '
|
|
26
|
-
elsif OS.mac?
|
|
27
|
-
"open "
|
|
28
|
-
elsif OS.linux?
|
|
29
|
-
"xdg-open "
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def domain
|
|
34
|
-
/.*(io|com|web|net|org|gov|edu|xyz)(\/.*)?$/i
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# helper methods
|
|
38
|
-
def prep(url)
|
|
39
|
-
if /^http/.match?(url)
|
|
40
|
-
url
|
|
41
|
-
else
|
|
42
|
-
"http://" + url
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def wrap(url)
|
|
47
|
-
"\"#{url}\""
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# configuration
|
|
52
|
-
class BConfig
|
|
53
|
-
VALID = [:searcher, :bookmarks, :browser]
|
|
54
|
-
HOME = ENV["HOME"].nil? ? "/usr/local/" : ENV["HOME"]
|
|
55
|
-
YAMLCONF = HOME + "/.booker.yml"
|
|
56
|
-
|
|
57
|
-
def initialize
|
|
58
|
-
# config defaults (for osx, default chrome profile)
|
|
59
|
-
readyaml = read(YAMLCONF)
|
|
60
|
-
default_bookmarks = detect_default_bookmarks
|
|
61
|
-
default_config = {
|
|
62
|
-
browser: "open ",
|
|
63
|
-
searcher: "https://google.com/search?q=",
|
|
64
|
-
bookmarks: default_bookmarks
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
# configure w/ yaml config file, if it exists
|
|
68
|
-
@config = readyaml || default_config
|
|
69
|
-
|
|
70
|
-
# prune bad config keys
|
|
71
|
-
@config.each do |k, v|
|
|
72
|
-
if !VALID.include? k.to_sym
|
|
73
|
-
puts "Failure:".red + " Bad key found in config file: #{k}"
|
|
74
|
-
exit 1
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def detect_default_bookmarks
|
|
80
|
-
# Return ALL available bookmark sources (both Chrome and Firefox)
|
|
81
|
-
all_sources = discover_all_bookmark_sources
|
|
82
|
-
|
|
83
|
-
# If we found sources, return them all; otherwise return a default single path
|
|
84
|
-
if all_sources.empty?
|
|
85
|
-
# Fallback to macOS Chrome default for backward compatibility
|
|
86
|
-
HOME + "/Library/Application Support/Google/Chrome/Profile 1/Bookmarks"
|
|
87
|
-
else
|
|
88
|
-
all_sources
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def discover_all_bookmark_sources
|
|
93
|
-
sources = []
|
|
94
|
-
|
|
95
|
-
# Discover all Chrome/Chromium bookmarks
|
|
96
|
-
chrome_base_paths = [
|
|
97
|
-
HOME + "/Library/Application Support/Google/Chrome", # macOS
|
|
98
|
-
HOME + "/Library/Application Support/Chromium", # macOS Chromium
|
|
99
|
-
HOME + "/.config/google-chrome", # Linux
|
|
100
|
-
HOME + "/.config/chromium", # Linux Chromium
|
|
101
|
-
HOME + "/snap/chromium/common/chromium", # Linux (snap)
|
|
102
|
-
HOME + "/snap/chromium/current/.config/chromium", # Linux (snap alt)
|
|
103
|
-
HOME + "/AppData/Local/Google/Chrome/User Data" # Windows
|
|
104
|
-
]
|
|
105
|
-
|
|
106
|
-
chrome_base_paths.each do |base|
|
|
107
|
-
next unless Dir.exist?(base)
|
|
108
|
-
|
|
109
|
-
# Find all profile directories and their Bookmarks files
|
|
110
|
-
begin
|
|
111
|
-
Dir.glob(File.join(base, "**/Bookmarks")).each do |bookmark_file|
|
|
112
|
-
sources << bookmark_file if File.file?(bookmark_file)
|
|
113
|
-
end
|
|
114
|
-
rescue
|
|
115
|
-
# Skip if we can't read this directory
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Discover all Firefox profiles
|
|
120
|
-
firefox_base_paths = [
|
|
121
|
-
HOME + "/.mozilla/firefox", # Linux
|
|
122
|
-
HOME + "/snap/firefox/common/.mozilla/firefox", # Linux (snap)
|
|
123
|
-
HOME + "/Library/Application Support/Firefox", # macOS
|
|
124
|
-
HOME + "/AppData/Roaming/Mozilla/Firefox" # Windows
|
|
125
|
-
]
|
|
126
|
-
|
|
127
|
-
firefox_base_paths.each do |firefox_base|
|
|
128
|
-
next unless Dir.exist?(firefox_base)
|
|
129
|
-
|
|
130
|
-
profiles_ini = File.join(firefox_base, "profiles.ini")
|
|
131
|
-
next unless File.exist?(profiles_ini)
|
|
132
|
-
|
|
133
|
-
# Parse all Firefox profiles
|
|
134
|
-
File.readlines(profiles_ini).each do |line|
|
|
135
|
-
if line.include?("Path=")
|
|
136
|
-
profile_path = line.split("=", 2)[1].strip
|
|
137
|
-
db_path = File.join(firefox_base, profile_path, "places.sqlite")
|
|
138
|
-
sources << db_path if File.exist?(db_path)
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
sources.uniq
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def read(file)
|
|
147
|
-
begin
|
|
148
|
-
config = YAML.load(IO.read(file))
|
|
149
|
-
rescue Errno::ENOENT
|
|
150
|
-
warn "Warning: ".yel +
|
|
151
|
-
"YAML configuration file couldn't be found. Using defaults."
|
|
152
|
-
warn "Suggest: ".grn + "booker --install config"
|
|
153
|
-
return false
|
|
154
|
-
rescue Psych::SyntaxError
|
|
155
|
-
warn "Warning: ".red +
|
|
156
|
-
"YAML configuration file contains invalid syntax. Using defaults."
|
|
157
|
-
return false
|
|
158
|
-
end
|
|
159
|
-
config
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
# used for creating and updating the default configuration
|
|
163
|
-
def write(k = nil, v = nil)
|
|
164
|
-
if k.nil? && v.nil?
|
|
165
|
-
else
|
|
166
|
-
@config[k] = v # update users yaml config file
|
|
167
|
-
end
|
|
168
|
-
File.write(YAMLCONF, @config.to_yaml)
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
def bookmarks
|
|
172
|
-
# Return array of bookmark sources
|
|
173
|
-
# Handles both old single-path configs and new multi-source configs
|
|
174
|
-
result = @config[:bookmarks]
|
|
175
|
-
|
|
176
|
-
if result.is_a?(Array)
|
|
177
|
-
result
|
|
178
|
-
else
|
|
179
|
-
# Single path - wrap in array for consistent interface
|
|
180
|
-
[result]
|
|
181
|
-
end
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
def searcher
|
|
185
|
-
@config[:searcher]
|
|
186
|
-
end
|
|
187
|
-
end
|
data/lib/consts.rb
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
# file for large constant strings
|
|
2
|
-
|
|
3
|
-
# todo - wrap in module
|
|
4
|
-
|
|
5
|
-
HELP_BANNER = <<~EOS
|
|
6
|
-
Open browser:
|
|
7
|
-
$ booker [option] [arguments]
|
|
8
|
-
|
|
9
|
-
Main options:
|
|
10
|
-
--bookmark, -b: explicity open bookmark
|
|
11
|
-
--install, -i: install [bookmarks, completion, config]
|
|
12
|
-
--search, -s: explicity search arguments
|
|
13
|
-
|
|
14
|
-
Others:
|
|
15
|
-
--complete, -c: show tab completions
|
|
16
|
-
--version, -v: print version
|
|
17
|
-
--help, -h: show help
|
|
18
|
-
EOS
|
|
19
|
-
|
|
20
|
-
DEF_CONFIG = <<~EOS
|
|
21
|
-
---
|
|
22
|
-
:searcher: https://google.com/search?q=
|
|
23
|
-
:bookmarks: null
|
|
24
|
-
EOS
|
|
25
|
-
|
|
26
|
-
COMPLETION = <<~EOS
|
|
27
|
-
#compdef booker
|
|
28
|
-
#autoload
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
_booker_bookmarks() {
|
|
32
|
-
#http://zsh.sourceforge.net/Guide/zshguide06.html#l147
|
|
33
|
-
setopt glob bareglobqual nullglob rcexpandparam extendedglob automenu
|
|
34
|
-
unsetopt allexport aliases errexit octalzeroes ksharrays cshnullglob
|
|
35
|
-
|
|
36
|
-
#get bookmarks
|
|
37
|
-
local -a bookmarks sites search_terms
|
|
38
|
-
search_terms=()
|
|
39
|
-
|
|
40
|
-
#filter out 'booker' and already-selected bookmark IDs (numeric with underscores)
|
|
41
|
-
for arg in "$@"; do
|
|
42
|
-
if [[ "$arg" != "booker" && ! "$arg" =~ ^[0-9_]+$ ]]; then
|
|
43
|
-
search_terms+=("$arg")
|
|
44
|
-
fi
|
|
45
|
-
done
|
|
46
|
-
|
|
47
|
-
#join search terms and get matching bookmarks
|
|
48
|
-
local search="${search_terms[*]}"
|
|
49
|
-
sites=`booker --complete $search`
|
|
50
|
-
bookmarks=("${(f)${sites}}")
|
|
51
|
-
|
|
52
|
-
#terminal colors
|
|
53
|
-
BLUE=`echo -e "\033[4;34m"`
|
|
54
|
-
RESET=`echo -e "\033[0;0m"`
|
|
55
|
-
|
|
56
|
-
#parse parts, color
|
|
57
|
-
local -a ids links
|
|
58
|
-
for bookmark in ${bookmarks[@]}; do
|
|
59
|
-
local -a split
|
|
60
|
-
split=("${(@s/:/)bookmark}")
|
|
61
|
-
ids+=( $split[1] )
|
|
62
|
-
links+=("$split[2] $BLUE$split[3]$RESET")
|
|
63
|
-
done
|
|
64
|
-
|
|
65
|
-
#finally, add completions
|
|
66
|
-
compstate[insert]=menu
|
|
67
|
-
compadd -U -l -X 'found bookmarks:' -d links -a ids
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
_booker() {
|
|
72
|
-
compstate[insert]=menu
|
|
73
|
-
local curcontext state line
|
|
74
|
-
typeset -A opt_args
|
|
75
|
-
|
|
76
|
-
_arguments -C \
|
|
77
|
-
'(-b)-b[do bookmark completion]'\
|
|
78
|
-
'(--bookmark)--bookmark[do bookmark completion]'\
|
|
79
|
-
'(-c)-c[show bookmark completions]'\
|
|
80
|
-
'(--complete)--complete[show bookmark completions]'\
|
|
81
|
-
'(-i)-i[perform installations (bookmarks, completion, config)]'\
|
|
82
|
-
'(--install)--install[perform installations (bookmarks, completion, config)]'\
|
|
83
|
-
'(-s)-s[search google for...]'\
|
|
84
|
-
'(--search)--search[search google for...]'\
|
|
85
|
-
'(-h)-h[show booker help]'\
|
|
86
|
-
'(--help)--help[show booker help]'\
|
|
87
|
-
'(-v)-v[show version]'\
|
|
88
|
-
'(--version)--version[show version]'\
|
|
89
|
-
'*::bookmarks:->bookmarks'
|
|
90
|
-
|
|
91
|
-
# Handle the bookmarks state for repeated completions
|
|
92
|
-
case $state in
|
|
93
|
-
bookmarks)
|
|
94
|
-
_booker_bookmarks $words
|
|
95
|
-
;;
|
|
96
|
-
esac
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
_booker
|
|
101
|
-
EOS
|
|
102
|
-
|
|
103
|
-
ZSH_SCRIPT = <<~EOS
|
|
104
|
-
# load zsh booker completions
|
|
105
|
-
install_loc=~/.oh-my-zsh/completions
|
|
106
|
-
script=_booker
|
|
107
|
-
|
|
108
|
-
install:
|
|
109
|
-
mkdir -vp $(install_loc)
|
|
110
|
-
cp -v $(script) $(install_loc)/
|
|
111
|
-
|
|
112
|
-
uninstall:
|
|
113
|
-
rm -v $(install_loc)/$(script)
|
|
114
|
-
EOS
|