usefuldb 0.1.0 → 0.2.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 +5 -5
- data/CHANGELOG +5 -5
- data/COPYING +338 -3
- data/Gemfile +5 -0
- data/Makefile +22 -0
- data/README.md +20 -7
- data/Rakefile +11 -18
- data/bin/usefuldb +6 -117
- data/lib/usefuldb/cli.rb +563 -0
- data/lib/usefuldb/exceptions.rb +7 -13
- data/lib/usefuldb/import_export.rb +163 -0
- data/lib/usefuldb/json_encoder.rb +38 -0
- data/lib/usefuldb/utilities.rb +136 -96
- data/lib/usefuldb/version.rb +6 -13
- data/lib/usefuldb.rb +32 -24
- data/resources/db.yaml +495 -21
- data/usefuldb.gemspec +48 -0
- metadata +55 -36
- data/lib/usefuldb/gui.rb +0 -109
- data/test/main_test.rb +0 -104
data/lib/usefuldb/cli.rb
ADDED
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
require "usefuldb/json_encoder"
|
|
5
|
+
require "usefuldb/utilities"
|
|
6
|
+
|
|
7
|
+
module UsefulDB
|
|
8
|
+
class CLI
|
|
9
|
+
COMMANDS = %w[search list add remove rm show count export import help].freeze
|
|
10
|
+
ImportExport = UsefulDB::ImportExport
|
|
11
|
+
|
|
12
|
+
def self.run(argv, log:)
|
|
13
|
+
global, remaining = parse_global_options(argv)
|
|
14
|
+
configure_logger(log, global)
|
|
15
|
+
|
|
16
|
+
if remaining.empty?
|
|
17
|
+
print_help
|
|
18
|
+
return 0
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
if command?(remaining.first)
|
|
22
|
+
command = remaining.shift
|
|
23
|
+
|
|
24
|
+
case command
|
|
25
|
+
when "help"
|
|
26
|
+
print_help(remaining.first)
|
|
27
|
+
when "search"
|
|
28
|
+
run_search(remaining, global, log)
|
|
29
|
+
when "list"
|
|
30
|
+
run_list(remaining, global, log)
|
|
31
|
+
when "add"
|
|
32
|
+
run_add(remaining, global, log)
|
|
33
|
+
when "remove", "rm"
|
|
34
|
+
run_remove(remaining, global, log)
|
|
35
|
+
when "show"
|
|
36
|
+
run_show(remaining, global, log)
|
|
37
|
+
when "count"
|
|
38
|
+
run_count(global, log)
|
|
39
|
+
when "export"
|
|
40
|
+
run_export(remaining, global, log)
|
|
41
|
+
when "import"
|
|
42
|
+
run_import(remaining, global, log)
|
|
43
|
+
end
|
|
44
|
+
else
|
|
45
|
+
unknown = remaining.first
|
|
46
|
+
raise OptionParser::ParseError, "Unknown command: #{unknown}. Use `usefuldb search` to find entries."
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
0
|
|
50
|
+
rescue EntryInDB, EmptyDB, KeyOutOfBounds, EntryNotFound, ImportError => e
|
|
51
|
+
warn e.message unless global[:quiet]
|
|
52
|
+
1
|
|
53
|
+
rescue OptionParser::ParseError => e
|
|
54
|
+
warn e.message unless global[:quiet]
|
|
55
|
+
warn "Run `usefuldb help` for usage." unless global[:quiet]
|
|
56
|
+
1
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.command?(name)
|
|
60
|
+
COMMANDS.include?(name)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.parse_global_options(argv)
|
|
64
|
+
global = {
|
|
65
|
+
db: nil,
|
|
66
|
+
quiet: false,
|
|
67
|
+
verbose: false
|
|
68
|
+
}
|
|
69
|
+
remaining = argv.dup
|
|
70
|
+
|
|
71
|
+
while (arg = remaining.first)
|
|
72
|
+
case arg
|
|
73
|
+
when "--db"
|
|
74
|
+
remaining.shift
|
|
75
|
+
global[:db] = remaining.shift or raise OptionParser::ParseError, "missing argument: --db"
|
|
76
|
+
when "-q", "--quiet"
|
|
77
|
+
remaining.shift
|
|
78
|
+
global[:quiet] = true
|
|
79
|
+
when "-v", "--verbose"
|
|
80
|
+
remaining.shift
|
|
81
|
+
global[:verbose] = true
|
|
82
|
+
when "--version"
|
|
83
|
+
remaining.shift
|
|
84
|
+
puts UsefulDB::Version.to_s
|
|
85
|
+
exit 0
|
|
86
|
+
when "-h", "--help"
|
|
87
|
+
remaining.shift
|
|
88
|
+
print_help
|
|
89
|
+
exit 0
|
|
90
|
+
when "--"
|
|
91
|
+
remaining.shift
|
|
92
|
+
break
|
|
93
|
+
else
|
|
94
|
+
break
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
[global, remaining]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def self.configure_logger(log, global)
|
|
102
|
+
if global[:quiet]
|
|
103
|
+
log.level = Logger::ERROR
|
|
104
|
+
elsif global[:verbose]
|
|
105
|
+
log.level = Logger::DEBUG
|
|
106
|
+
else
|
|
107
|
+
log.level = Logger::ERROR
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def self.partition_argv(argv, value_flags: [])
|
|
112
|
+
options_argv = []
|
|
113
|
+
positional = []
|
|
114
|
+
index = 0
|
|
115
|
+
|
|
116
|
+
while index < argv.length
|
|
117
|
+
token = argv[index]
|
|
118
|
+
|
|
119
|
+
if token == "--"
|
|
120
|
+
positional.concat(argv[(index + 1)..])
|
|
121
|
+
break
|
|
122
|
+
elsif token == "-"
|
|
123
|
+
positional << token
|
|
124
|
+
index += 1
|
|
125
|
+
elsif token.start_with?("-")
|
|
126
|
+
options_argv << token
|
|
127
|
+
index += 1
|
|
128
|
+
|
|
129
|
+
if value_flags.include?(token) && index < argv.length && !argv[index].start_with?("-")
|
|
130
|
+
options_argv << argv[index]
|
|
131
|
+
index += 1
|
|
132
|
+
end
|
|
133
|
+
else
|
|
134
|
+
positional << token
|
|
135
|
+
index += 1
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
[options_argv, positional]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def self.load_options(global)
|
|
143
|
+
options = {}
|
|
144
|
+
options[:db] = global[:db] if global[:db]
|
|
145
|
+
options
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def self.load_db!(log, global)
|
|
149
|
+
UsefulDB.setup(log) unless global[:db]
|
|
150
|
+
UsefulDB.dbLoad(log, load_options(global))
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def self.run_search(argv, global, log)
|
|
154
|
+
options = {
|
|
155
|
+
match: :all,
|
|
156
|
+
format: :human,
|
|
157
|
+
ids: false
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
parser = OptionParser.new do |opts|
|
|
161
|
+
opts.banner = "Usage: usefuldb search [options] [tags...]"
|
|
162
|
+
opts.on("--any", "Match entries with any tag (OR)") { options[:match] = :any }
|
|
163
|
+
opts.on("--json", "Print results as JSON") { options[:format] = :json }
|
|
164
|
+
opts.on("--value-only", "Print only entry values") { options[:format] = :value_only }
|
|
165
|
+
opts.on("--ids", "Include entry ids in human output") { options[:ids] = true }
|
|
166
|
+
opts.on("-h", "--help", "Show this help") do
|
|
167
|
+
puts opts
|
|
168
|
+
exit 0
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
option_argv, tags = partition_argv(argv)
|
|
173
|
+
parser.parse!(option_argv)
|
|
174
|
+
|
|
175
|
+
load_db!(log, global)
|
|
176
|
+
results = UsefulDB::Utils.search_entries(tags, match: options[:match])
|
|
177
|
+
print_entries(results, format: options[:format], ids: options[:ids])
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def self.run_list(argv, global, log)
|
|
181
|
+
options = {
|
|
182
|
+
format: :human,
|
|
183
|
+
tags_only: false
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
parser = OptionParser.new do |opts|
|
|
187
|
+
opts.banner = "Usage: usefuldb list [options]"
|
|
188
|
+
opts.on("--json", "Print results as JSON") { options[:format] = :json }
|
|
189
|
+
opts.on("--tags-only", "Print unique tags") { options[:tags_only] = true }
|
|
190
|
+
opts.on("-h", "--help", "Show this help") do
|
|
191
|
+
puts opts
|
|
192
|
+
exit 0
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
parser.order!(argv)
|
|
197
|
+
|
|
198
|
+
load_db!(log, global)
|
|
199
|
+
|
|
200
|
+
if options[:tags_only]
|
|
201
|
+
tags = UsefulDB::Utils.all_tags
|
|
202
|
+
if options[:format] == :json
|
|
203
|
+
puts UsefulDB::JSONEncoder.generate(tags)
|
|
204
|
+
else
|
|
205
|
+
tags.each { |tag| puts tag }
|
|
206
|
+
end
|
|
207
|
+
return
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
print_entries(UsefulDB::Utils.entries, format: options[:format], ids: true)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def self.run_add(argv, global, log)
|
|
214
|
+
options = {
|
|
215
|
+
tags: nil,
|
|
216
|
+
value: nil,
|
|
217
|
+
description: nil
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
parser = OptionParser.new do |opts|
|
|
221
|
+
opts.banner = "Usage: usefuldb add [options]"
|
|
222
|
+
opts.on("--tags TAGS", "Comma-separated search tags") { |value| options[:tags] = value }
|
|
223
|
+
opts.on("--value VALUE", "Stored command, URL, or text") { |value| options[:value] = value }
|
|
224
|
+
opts.on("--description TEXT", "Entry description") { |value| options[:description] = value }
|
|
225
|
+
opts.on("-h", "--help", "Show this help") do
|
|
226
|
+
puts opts
|
|
227
|
+
exit 0
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
parser.order!(argv)
|
|
232
|
+
|
|
233
|
+
load_db!(log, global)
|
|
234
|
+
|
|
235
|
+
tags = options[:tags]
|
|
236
|
+
value = options[:value]
|
|
237
|
+
description = options[:description]
|
|
238
|
+
|
|
239
|
+
if tags.nil?
|
|
240
|
+
$stdout.print "Tags (comma-separated): "
|
|
241
|
+
tags = $stdin.gets.to_s.strip
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
if value.nil?
|
|
245
|
+
$stdout.print "Value: "
|
|
246
|
+
value = $stdin.gets.to_s.strip
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
if description.nil? && $stdin.tty?
|
|
250
|
+
$stdout.print "Description (optional): "
|
|
251
|
+
description = $stdin.gets.to_s.strip
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
normalized_tags = UsefulDB::Utils.normalize_tags(tags)
|
|
255
|
+
raise OptionParser::ParseError, "At least one tag is required" if normalized_tags.empty?
|
|
256
|
+
raise OptionParser::ParseError, "Value is required" if value.to_s.strip.empty?
|
|
257
|
+
|
|
258
|
+
entry = {
|
|
259
|
+
"tag" => normalized_tags,
|
|
260
|
+
"value" => value.strip,
|
|
261
|
+
"description" => description.to_s
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
UsefulDB.add(entry, log)
|
|
265
|
+
UsefulDB.dbSave(log, load_options(global))
|
|
266
|
+
|
|
267
|
+
new_id = UsefulDB::Utils.count(log) - 1
|
|
268
|
+
puts "Added entry [#{new_id}]." unless global[:quiet]
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def self.run_remove(argv, global, log)
|
|
272
|
+
options = {
|
|
273
|
+
tags: nil,
|
|
274
|
+
value: nil
|
|
275
|
+
}
|
|
276
|
+
id = nil
|
|
277
|
+
|
|
278
|
+
parser = OptionParser.new do |opts|
|
|
279
|
+
opts.banner = "Usage: usefuldb remove <id> [options]"
|
|
280
|
+
opts.on("--tags TAGS", "Match entry tags when removing by value") { |value| options[:tags] = value }
|
|
281
|
+
opts.on("--value VALUE", "Match entry value when removing by attributes") { |value| options[:value] = value }
|
|
282
|
+
opts.on("-h", "--help", "Show this help") do
|
|
283
|
+
puts opts
|
|
284
|
+
exit 0
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
option_argv, positional = partition_argv(argv, value_flags: ["--tags", "--value"])
|
|
289
|
+
parser.parse!(option_argv)
|
|
290
|
+
id = positional.first.to_i if positional.first&.match?(/\A\d+\z/)
|
|
291
|
+
|
|
292
|
+
load_db!(log, global)
|
|
293
|
+
|
|
294
|
+
if id.nil?
|
|
295
|
+
raise OptionParser::ParseError, "Entry id is required" if options[:value].nil?
|
|
296
|
+
|
|
297
|
+
normalized_tags = UsefulDB::Utils.normalize_tags(options[:tags] || [])
|
|
298
|
+
id = UsefulDB::Utils.find_entry_id(tags: normalized_tags, value: options[:value])
|
|
299
|
+
raise EntryNotFound, "No entry matched the given tags and value" if id.nil?
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
removed = UsefulDB::Utils.get_entry(id)
|
|
303
|
+
UsefulDB.remove(id, log)
|
|
304
|
+
UsefulDB.dbSave(log, load_options(global))
|
|
305
|
+
|
|
306
|
+
puts "Removed entry [#{id}]: #{removed['value']}" unless global[:quiet]
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def self.run_show(argv, global, log)
|
|
310
|
+
json = false
|
|
311
|
+
|
|
312
|
+
parser = OptionParser.new do |opts|
|
|
313
|
+
opts.banner = "Usage: usefuldb show <id> [options]"
|
|
314
|
+
opts.on("--json", "Print entry as JSON") { json = true }
|
|
315
|
+
opts.on("-h", "--help", "Show this help") do
|
|
316
|
+
puts opts
|
|
317
|
+
exit 0
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
option_argv, positional = partition_argv(argv)
|
|
322
|
+
parser.parse!(option_argv)
|
|
323
|
+
|
|
324
|
+
id = positional.first
|
|
325
|
+
raise OptionParser::ParseError, "Entry id is required" if id.nil?
|
|
326
|
+
|
|
327
|
+
load_db!(log, global)
|
|
328
|
+
entry = UsefulDB::Utils.get_entry(id.to_i)
|
|
329
|
+
|
|
330
|
+
if json
|
|
331
|
+
puts UsefulDB::JSONEncoder.generate(entry)
|
|
332
|
+
else
|
|
333
|
+
print_entries([entry], format: :human, ids: true)
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def self.run_count(global, log)
|
|
338
|
+
load_db!(log, global)
|
|
339
|
+
puts UsefulDB::Utils.count(log)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def self.run_export(argv, global, log)
|
|
343
|
+
options = {
|
|
344
|
+
output: nil,
|
|
345
|
+
format: nil
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
parser = OptionParser.new do |opts|
|
|
349
|
+
opts.banner = "Usage: usefuldb export [options] [file]"
|
|
350
|
+
opts.on("-o", "--output FILE", "Write export to FILE (- for stdout)") { |value| options[:output] = value }
|
|
351
|
+
opts.on("--format FORMAT", ImportExport::FORMATS.map(&:to_s), "Export format (yaml or json)") do |value|
|
|
352
|
+
options[:format] = value.to_sym
|
|
353
|
+
end
|
|
354
|
+
opts.on("-h", "--help", "Show this help") do
|
|
355
|
+
puts opts
|
|
356
|
+
exit 0
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
option_argv, positional = partition_argv(argv, value_flags: ["-o", "--output", "--format"])
|
|
361
|
+
parser.parse!(option_argv)
|
|
362
|
+
|
|
363
|
+
output = options[:output] || positional.first
|
|
364
|
+
raise OptionParser::ParseError, "Output file is required (use - for stdout)" if output.nil?
|
|
365
|
+
|
|
366
|
+
format = options[:format]
|
|
367
|
+
format ||= UsefulDB::ImportExport.detect_format(output) if output != "-"
|
|
368
|
+
format ||= :yaml
|
|
369
|
+
|
|
370
|
+
load_db!(log, global)
|
|
371
|
+
content = UsefulDB::ImportExport.export_content(UsefulDB::Utils.export_data, format: format)
|
|
372
|
+
UsefulDB::ImportExport.write_export(output, content)
|
|
373
|
+
|
|
374
|
+
unless global[:quiet] || output == "-"
|
|
375
|
+
puts "Exported #{UsefulDB::Utils.count(log)} entries to #{output}."
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def self.run_import(argv, global, log)
|
|
380
|
+
options = {
|
|
381
|
+
input: nil,
|
|
382
|
+
format: nil,
|
|
383
|
+
mode: :merge
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
parser = OptionParser.new do |opts|
|
|
387
|
+
opts.banner = "Usage: usefuldb import [options] [file]"
|
|
388
|
+
opts.on("-i", "--input FILE", "Read import from FILE (- for stdin)") { |value| options[:input] = value }
|
|
389
|
+
opts.on("--format FORMAT", ImportExport::FORMATS.map(&:to_s), "Import format (yaml or json)") do |value|
|
|
390
|
+
options[:format] = value.to_sym
|
|
391
|
+
end
|
|
392
|
+
opts.on("--merge", "Merge entries into the current database (default)") { options[:mode] = :merge }
|
|
393
|
+
opts.on("--replace", "Replace the current database with the import") { options[:mode] = :replace }
|
|
394
|
+
opts.on("-h", "--help", "Show this help") do
|
|
395
|
+
puts opts
|
|
396
|
+
exit 0
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
option_argv, positional = partition_argv(argv, value_flags: ["-i", "--input", "--format"])
|
|
401
|
+
parser.parse!(option_argv)
|
|
402
|
+
|
|
403
|
+
input = options[:input] || positional.first
|
|
404
|
+
raise OptionParser::ParseError, "Input file is required (use - for stdin)" if input.nil?
|
|
405
|
+
|
|
406
|
+
prepare_db_for_import!(log, global, replace: options[:mode] == :replace)
|
|
407
|
+
|
|
408
|
+
imported = UsefulDB::ImportExport.parse_file(input, format: options[:format])
|
|
409
|
+
result = UsefulDB::ImportExport.import!(imported, mode: options[:mode], log: log)
|
|
410
|
+
UsefulDB.dbSave(log, load_options(global))
|
|
411
|
+
|
|
412
|
+
return if global[:quiet]
|
|
413
|
+
|
|
414
|
+
if result[:mode] == :replace
|
|
415
|
+
puts "Replaced database with #{result[:total]} entries."
|
|
416
|
+
else
|
|
417
|
+
puts "Imported #{result[:added]} entries (#{result[:skipped]} skipped as duplicates). Database now has #{result[:total]} entries."
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def self.prepare_db_for_import!(log, global, replace:)
|
|
422
|
+
db_options = load_options(global)
|
|
423
|
+
|
|
424
|
+
if global[:db]
|
|
425
|
+
UsefulDB::Utils.ensure_db!(log, db_options)
|
|
426
|
+
elsif replace && !File.exist?(UsefulDB::Utils.db_path(db_options))
|
|
427
|
+
UsefulDB::Utils.ensure_db!(log, db_options)
|
|
428
|
+
else
|
|
429
|
+
UsefulDB.setup(log) unless global[:db]
|
|
430
|
+
UsefulDB.dbLoad(log, db_options)
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def self.print_entries(entries, format:, ids: false)
|
|
435
|
+
case format
|
|
436
|
+
when :json
|
|
437
|
+
puts UsefulDB::JSONEncoder.generate(entries)
|
|
438
|
+
when :value_only
|
|
439
|
+
entries.each { |entry| puts entry["value"] }
|
|
440
|
+
else
|
|
441
|
+
if entries.empty?
|
|
442
|
+
puts "No matching entries."
|
|
443
|
+
return
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
entries.each do |entry|
|
|
447
|
+
prefix = ids ? "[#{entry['id']}] " : ""
|
|
448
|
+
puts "#{prefix}#{entry['value']}"
|
|
449
|
+
puts " tags: #{entry['tag'].join(', ')}"
|
|
450
|
+
puts " #{entry['description']}" unless entry['description'].to_s.empty?
|
|
451
|
+
puts
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def self.print_help(command = nil)
|
|
457
|
+
case command
|
|
458
|
+
when "search"
|
|
459
|
+
puts <<~HELP
|
|
460
|
+
Usage: usefuldb search [options] [tags...]
|
|
461
|
+
|
|
462
|
+
Options:
|
|
463
|
+
--any Match entries with any tag (OR)
|
|
464
|
+
--json Print results as JSON
|
|
465
|
+
--value-only Print only entry values
|
|
466
|
+
--ids Include entry ids in human output
|
|
467
|
+
-h, --help Show this help
|
|
468
|
+
HELP
|
|
469
|
+
when "list"
|
|
470
|
+
puts <<~HELP
|
|
471
|
+
Usage: usefuldb list [options]
|
|
472
|
+
|
|
473
|
+
Options:
|
|
474
|
+
--json Print results as JSON
|
|
475
|
+
--tags-only Print unique tags
|
|
476
|
+
-h, --help Show this help
|
|
477
|
+
HELP
|
|
478
|
+
when "add"
|
|
479
|
+
puts <<~HELP
|
|
480
|
+
Usage: usefuldb add [options]
|
|
481
|
+
|
|
482
|
+
Options:
|
|
483
|
+
--tags TAGS Comma-separated search tags
|
|
484
|
+
--value VALUE Stored command, URL, or text
|
|
485
|
+
--description TEXT Entry description
|
|
486
|
+
-h, --help Show this help
|
|
487
|
+
HELP
|
|
488
|
+
when "remove", "rm"
|
|
489
|
+
puts <<~HELP
|
|
490
|
+
Usage: usefuldb remove <id> [options]
|
|
491
|
+
|
|
492
|
+
Options:
|
|
493
|
+
--tags TAGS Match entry tags when removing by value
|
|
494
|
+
--value VALUE Match entry value when removing by attributes
|
|
495
|
+
-h, --help Show this help
|
|
496
|
+
HELP
|
|
497
|
+
when "show"
|
|
498
|
+
puts <<~HELP
|
|
499
|
+
Usage: usefuldb show <id> [options]
|
|
500
|
+
|
|
501
|
+
Options:
|
|
502
|
+
--json Print entry as JSON
|
|
503
|
+
-h, --help Show this help
|
|
504
|
+
HELP
|
|
505
|
+
when "export"
|
|
506
|
+
puts <<~HELP
|
|
507
|
+
Usage: usefuldb export [options] [file]
|
|
508
|
+
|
|
509
|
+
Options:
|
|
510
|
+
-o, --output FILE Write export to FILE (- for stdout)
|
|
511
|
+
--format FORMAT Export format: yaml or json
|
|
512
|
+
-h, --help Show this help
|
|
513
|
+
HELP
|
|
514
|
+
when "import"
|
|
515
|
+
puts <<~HELP
|
|
516
|
+
Usage: usefuldb import [options] [file]
|
|
517
|
+
|
|
518
|
+
Options:
|
|
519
|
+
-i, --input FILE Read import from FILE (- for stdin)
|
|
520
|
+
--format FORMAT Import format: yaml or json
|
|
521
|
+
--merge Merge entries into the current database (default)
|
|
522
|
+
--replace Replace the current database with the import
|
|
523
|
+
-h, --help Show this help
|
|
524
|
+
HELP
|
|
525
|
+
else
|
|
526
|
+
puts <<~HELP
|
|
527
|
+
Usage: usefuldb [global options] <command> [arguments]
|
|
528
|
+
|
|
529
|
+
A simple command and URL database searchable by tag.
|
|
530
|
+
|
|
531
|
+
Commands:
|
|
532
|
+
search [tags...] Find entries by tag
|
|
533
|
+
list List all entries
|
|
534
|
+
add Add an entry
|
|
535
|
+
remove <id> Remove an entry by id
|
|
536
|
+
show <id> Show a single entry
|
|
537
|
+
count Print entry count
|
|
538
|
+
export [file] Export the database to YAML or JSON
|
|
539
|
+
import [file] Import a database export
|
|
540
|
+
help [command] Show help
|
|
541
|
+
|
|
542
|
+
Global options:
|
|
543
|
+
--db PATH Database file (default: ~/.usefuldb/db.yaml)
|
|
544
|
+
-q, --quiet Suppress non-essential output
|
|
545
|
+
-v, --verbose Enable debug logging
|
|
546
|
+
--version Print version
|
|
547
|
+
-h, --help Show this help
|
|
548
|
+
|
|
549
|
+
Examples:
|
|
550
|
+
usefuldb search git push
|
|
551
|
+
usefuldb search git commit --value-only
|
|
552
|
+
usefuldb add --tags git,commit --value "git commit -m 'msg'" --description "Commit changes"
|
|
553
|
+
usefuldb list --json
|
|
554
|
+
usefuldb show 42
|
|
555
|
+
usefuldb remove 42
|
|
556
|
+
usefuldb export backup.yaml
|
|
557
|
+
usefuldb import backup.yaml --merge
|
|
558
|
+
usefuldb export -o - --format json | jq '.db | length'
|
|
559
|
+
HELP
|
|
560
|
+
end
|
|
561
|
+
end
|
|
562
|
+
end
|
|
563
|
+
end
|
data/lib/usefuldb/exceptions.rb
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
require 'usefuldb'
|
|
1
|
+
# frozen_string_literal: true
|
|
3
2
|
|
|
4
3
|
module UsefulDB
|
|
5
|
-
|
|
6
|
-
class
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
class
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class KeyOutOfBounds < Exception
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
end
|
|
4
|
+
class EntryInDB < StandardError; end
|
|
5
|
+
class EmptyDB < StandardError; end
|
|
6
|
+
class KeyOutOfBounds < StandardError; end
|
|
7
|
+
class EntryNotFound < StandardError; end
|
|
8
|
+
class ImportError < StandardError; end
|
|
9
|
+
end
|