na 1.1.26 → 1.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 +4 -4
- data/CHANGELOG.md +41 -0
- data/Gemfile.lock +1 -1
- data/README.md +146 -12
- data/bin/na +332 -41
- data/lib/na/action.rb +11 -13
- data/lib/na/colors.rb +2 -1
- data/lib/na/next_action.rb +347 -49
- data/lib/na/project.rb +26 -0
- data/lib/na/string.rb +9 -5
- data/lib/na/version.rb +1 -1
- data/lib/na.rb +1 -0
- data/src/README.md +58 -4
- metadata +3 -2
data/bin/na
CHANGED
@@ -95,13 +95,16 @@ class App
|
|
95
95
|
end
|
96
96
|
|
97
97
|
if args.count.positive?
|
98
|
+
all_req = false
|
99
|
+
|
98
100
|
tokens = []
|
99
101
|
args.each do |arg|
|
100
102
|
arg.split(/ *, */).each do |a|
|
101
|
-
m = a.match(/^(?<req
|
103
|
+
m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
102
104
|
tokens.push({
|
103
105
|
token: m['tok'],
|
104
|
-
required: !m['req'].nil?
|
106
|
+
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
107
|
+
negate: !m['req'].nil? && m['req'] =~ /[!\-]/
|
105
108
|
})
|
106
109
|
end
|
107
110
|
end
|
@@ -110,12 +113,12 @@ class App
|
|
110
113
|
NA.na_tag = options[:tag] unless options[:tag].nil?
|
111
114
|
require_na = true
|
112
115
|
|
113
|
-
tag = [{ tag: tag, value: nil }]
|
114
|
-
files, actions = NA.parse_actions(depth: depth,
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
116
|
+
tag = [{ tag: tag, value: nil }, { tag: 'done', value: nil, negate: true}]
|
117
|
+
files, actions, = NA.parse_actions(depth: depth,
|
118
|
+
query: tokens,
|
119
|
+
tag: tag,
|
120
|
+
project: options[:project],
|
121
|
+
require_na: require_na)
|
119
122
|
|
120
123
|
NA.output_actions(actions, depth, files: files)
|
121
124
|
end
|
@@ -146,7 +149,11 @@ class App
|
|
146
149
|
c.desc 'Add action to specific project'
|
147
150
|
c.arg_name 'PROJECT'
|
148
151
|
c.default_value 'Inbox'
|
149
|
-
c.flag %i[to]
|
152
|
+
c.flag %i[to project proj]
|
153
|
+
|
154
|
+
c.desc 'Add to a known todo file, partial matches allowed'
|
155
|
+
c.arg_name 'TODO_FILE'
|
156
|
+
c.flag %i[in todo]
|
150
157
|
|
151
158
|
c.desc 'Use a tag other than the default next action tag'
|
152
159
|
c.arg_name 'TAG'
|
@@ -183,7 +190,7 @@ class App
|
|
183
190
|
action = "#{action.gsub(/@priority\(\d+\)/, '')} @priority(#{options[:priority]})"
|
184
191
|
end
|
185
192
|
|
186
|
-
note_rx = /^(.+)\((.*?)\)$/
|
193
|
+
note_rx = /^(.+) \((.*?)\)$/
|
187
194
|
split_note = if action =~ note_rx
|
188
195
|
n = Regexp.last_match(2)
|
189
196
|
action.sub!(note_rx, '\1').strip!
|
@@ -229,6 +236,34 @@ class App
|
|
229
236
|
Process.exit 1
|
230
237
|
end
|
231
238
|
end
|
239
|
+
elsif options[:todo]
|
240
|
+
todo = []
|
241
|
+
options[:todo].split(/ *, */).each do |a|
|
242
|
+
m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
|
243
|
+
todo.push({
|
244
|
+
token: m['tok'],
|
245
|
+
required: !m['req'].nil?
|
246
|
+
})
|
247
|
+
end
|
248
|
+
dirs = NA.match_working_dir(todo)
|
249
|
+
if dirs.count.positive?
|
250
|
+
target = dirs[0]
|
251
|
+
else
|
252
|
+
todo = "#{options[:todo].sub(/#{NA.extension}$/, '')}.#{NA.extension}"
|
253
|
+
target = File.expand_path(todo)
|
254
|
+
unless File.exist?(target)
|
255
|
+
|
256
|
+
res = NA.yn(NA::Color.template("{by}Specified file not found, create #{todo}"), default: true)
|
257
|
+
if res
|
258
|
+
basename = File.basename(target, ".#{NA.extension}")
|
259
|
+
NA.create_todo(target, basename)
|
260
|
+
else
|
261
|
+
NA.notify('{r}Cancelled{x}', exit_code: 1)
|
262
|
+
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
end
|
232
267
|
else
|
233
268
|
files = NA.find_files(depth: options[:depth])
|
234
269
|
if files.count.zero?
|
@@ -243,12 +278,209 @@ class App
|
|
243
278
|
end
|
244
279
|
target = files.count > 1 ? NA.select_file(files) : files[0]
|
245
280
|
unless files.count.positive? && File.exist?(target)
|
246
|
-
|
247
|
-
|
281
|
+
NA.notify('{r}Cancelled{x}', exit_code: 1)
|
282
|
+
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
NA.add_action(target, options[:project], action, note)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
desc 'Update an existing action'
|
291
|
+
long_desc 'Provides an easy way to complete, prioritize, and tag existing actions.
|
292
|
+
|
293
|
+
If multiple todo files are found in the current directory, a menu will
|
294
|
+
allow you to pick which file to act on.'
|
295
|
+
arg_name 'ACTION'
|
296
|
+
command %i[update] do |c|
|
297
|
+
c.example 'na update --remove na "An existing task"', desc: 'Find "An existing task" action and remove the @na tag from it'
|
298
|
+
c.example 'na update --tag waiting "A bug I need to fix" -p 4 -n',
|
299
|
+
desc: 'Find "A bug..." action, add @waiting, add/update @priority(4), and prompt for an additional note'
|
300
|
+
c.example 'na update --archive My cool action', desc: 'Add @done to "My cool action" and immediately move to Archive'
|
301
|
+
|
302
|
+
c.desc 'Prompt for additional notes. Input will be appended to any existing note.'
|
303
|
+
c.switch %i[n note], negatable: false
|
304
|
+
|
305
|
+
c.desc 'Overwrite note instead of appending'
|
306
|
+
c.switch %i[o overwrite], negatable: false
|
307
|
+
|
308
|
+
c.desc 'Add/change a priority level 1-5'
|
309
|
+
c.arg_name 'PRIO'
|
310
|
+
c.flag %i[p priority], must_match: /[1-5]/, type: :integer, default_value: 0
|
311
|
+
|
312
|
+
c.desc 'Move action to specific project'
|
313
|
+
c.arg_name 'PROJECT'
|
314
|
+
c.flag %i[to project proj]
|
315
|
+
|
316
|
+
c.desc 'Use a known todo file, partial matches allowed'
|
317
|
+
c.arg_name 'TODO_FILE'
|
318
|
+
c.flag %i[in todo]
|
319
|
+
|
320
|
+
c.desc 'Add a tag to the action, @tag(values) allowed'
|
321
|
+
c.arg_name 'TAG'
|
322
|
+
c.flag %i[t tag], multiple: true
|
323
|
+
|
324
|
+
c.desc 'Remove a tag to the action'
|
325
|
+
c.arg_name 'TAG'
|
326
|
+
c.flag %i[r remove], multiple: true
|
327
|
+
|
328
|
+
c.desc 'Add a @done tag to action'
|
329
|
+
c.switch %i[f finish done], negatable: false
|
330
|
+
|
331
|
+
c.desc 'Add a @done tag to action and move to Archive'
|
332
|
+
c.switch %i[a archive], negatable: false
|
333
|
+
|
334
|
+
c.desc 'Delete an action'
|
335
|
+
c.switch %i[delete], negatable: false
|
336
|
+
|
337
|
+
c.desc 'Specify the file to search for the task'
|
338
|
+
c.arg_name 'PATH'
|
339
|
+
c.flag %i[file]
|
340
|
+
|
341
|
+
c.desc 'Search for files X directories deep'
|
342
|
+
c.arg_name 'DEPTH'
|
343
|
+
c.flag %i[d depth], must_match: /^[1-9]$/, type: :integer, default_value: 1
|
344
|
+
|
345
|
+
c.desc 'Match actions containing tag. Allows value comparisons'
|
346
|
+
c.arg_name 'TAG'
|
347
|
+
c.flag %i[tagged], multiple: true
|
348
|
+
|
349
|
+
c.desc 'Act on all matches immediately (no menu)'
|
350
|
+
c.switch %i[all], negatable: false
|
351
|
+
|
352
|
+
c.desc 'Interpret search pattern as regular expression'
|
353
|
+
c.switch %i[e regex], negatable: false
|
354
|
+
|
355
|
+
c.desc 'Match pattern exactly'
|
356
|
+
c.switch %i[x exact], negatable: false
|
357
|
+
|
358
|
+
c.action do |_global_options, options, args|
|
359
|
+
reader = TTY::Reader.new
|
360
|
+
action = if args.count.positive?
|
361
|
+
args.join(' ').strip
|
362
|
+
elsif TTY::Which.exist?('gum') && options[:tagged].empty?
|
363
|
+
options = [
|
364
|
+
%(--placeholder "Enter a task to search for"),
|
365
|
+
'--char-limit=500',
|
366
|
+
"--width=#{TTY::Screen.columns}"
|
367
|
+
]
|
368
|
+
`gum input #{options.join(' ')}`.strip
|
369
|
+
elsif options[:tagged].empty?
|
370
|
+
puts NA::Color.template('{bm}Enter search string:{x}')
|
371
|
+
reader.read_line(NA::Color.template('{by}> {bw}')).strip
|
372
|
+
end
|
373
|
+
|
374
|
+
if action
|
375
|
+
tokens = nil
|
376
|
+
if options[:exact]
|
377
|
+
tokens = action
|
378
|
+
elsif options[:regex]
|
379
|
+
tokens = Regexp.new(action, Regexp::IGNORECASE)
|
380
|
+
else
|
381
|
+
tokens = []
|
382
|
+
all_req = action !~ /[+!\-]/ && !options[:or]
|
383
|
+
|
384
|
+
action.split(/ /).each do |arg|
|
385
|
+
m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
386
|
+
tokens.push({
|
387
|
+
token: m['tok'],
|
388
|
+
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
389
|
+
negate: !m['req'].nil? && m['req'] =~ /[!\-]/
|
390
|
+
})
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
if (action.nil? || action.empty?) && options[:tagged].empty?
|
396
|
+
puts 'Empty input, cancelled'
|
397
|
+
Process.exit 1
|
398
|
+
end
|
399
|
+
|
400
|
+
all_req = options[:tagged].join(' ') !~ /[+!\-]/ && !options[:or]
|
401
|
+
tags = []
|
402
|
+
options[:tagged].join(',').split(/ *, */).each do |arg|
|
403
|
+
m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
|
404
|
+
|
405
|
+
tags.push({
|
406
|
+
tag: m['tag'].wildcard_to_rx,
|
407
|
+
comp: m['op'],
|
408
|
+
value: m['val'],
|
409
|
+
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
410
|
+
negate: !m['req'].nil? && m['req'] =~ /[!\-]/
|
411
|
+
})
|
412
|
+
end
|
413
|
+
|
414
|
+
priority = options[:priority].to_i if options[:priority]&.to_i&.positive?
|
415
|
+
add_tags = options[:tag].map { |t| t.sub(/^@/, '').wildcard_to_rx }
|
416
|
+
remove_tags = options[:remove].map { |t| t.sub(/^@/, '').wildcard_to_rx }
|
417
|
+
|
418
|
+
line_note = if options[:note]
|
419
|
+
if TTY::Which.exist?('gum')
|
420
|
+
args = ['--placeholder "Enter a note, CTRL-d to save"']
|
421
|
+
args << '--char-limit 0'
|
422
|
+
args << '--width $(tput cols)'
|
423
|
+
`gum write #{args.join(' ')}`.strip.split("\n")
|
424
|
+
else
|
425
|
+
puts NA::Color.template('{bm}Enter a note, {bw}CTRL-d{bm} to end editing{bw}')
|
426
|
+
reader.read_multiline
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
note = line_note.nil? || line_note.empty? ? [] : line_note
|
431
|
+
|
432
|
+
if options[:file]
|
433
|
+
file = File.expand_path(options[:file])
|
434
|
+
NA.notify('{r}File not found', exit_code: 1) unless File.exist?(file)
|
435
|
+
|
436
|
+
targets = [file]
|
437
|
+
elsif options[:todo]
|
438
|
+
todo = []
|
439
|
+
options[:todo].split(/ *, */).each do |a|
|
440
|
+
m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
|
441
|
+
todo.push({
|
442
|
+
token: m['tok'],
|
443
|
+
required: !m['req'].nil?
|
444
|
+
})
|
445
|
+
end
|
446
|
+
dirs = NA.match_working_dir(todo)
|
447
|
+
|
448
|
+
if dirs.count == 1
|
449
|
+
targets = [dirs[0]]
|
450
|
+
elsif dirs.count.positive?
|
451
|
+
targets = NA.select_file(dirs, multiple: true)
|
452
|
+
NA.notify('{r}Cancelled', exit_code: 1) unless targets && targets.count.positive?
|
453
|
+
else
|
454
|
+
NA.notify('{r}Todo not found', exit_code: 1) unless targets && targets.count.positive?
|
455
|
+
|
248
456
|
end
|
457
|
+
else
|
458
|
+
files = NA.find_files(depth: options[:depth])
|
459
|
+
NA.notify('{r}No todo file found', exit_code: 1) if files.count.zero?
|
460
|
+
|
461
|
+
targets = files.count > 1 ? NA.select_file(files, multiple: true) : [files[0]]
|
462
|
+
NA.notify('{r}Cancelled{x}', exit_code: 1) unless files.count.positive?
|
463
|
+
|
249
464
|
end
|
250
465
|
|
251
|
-
|
466
|
+
options[:finish] = true if options[:archive]
|
467
|
+
options[:project] = 'Archive' if options[:archive]
|
468
|
+
|
469
|
+
NA.notify('{r}No search terms provided', exit_code: 1) if tokens.nil? && options[:tagged].empty?
|
470
|
+
|
471
|
+
targets.each do |target|
|
472
|
+
NA.update_action(target, tokens,
|
473
|
+
priority: priority,
|
474
|
+
add_tag: add_tags,
|
475
|
+
remove_tag: remove_tags,
|
476
|
+
finish: options[:finish],
|
477
|
+
project: options[:project],
|
478
|
+
delete: options[:delete],
|
479
|
+
note: note,
|
480
|
+
overwrite: options[:overwrite],
|
481
|
+
tagged: tags,
|
482
|
+
all: options[:all])
|
483
|
+
end
|
252
484
|
end
|
253
485
|
end
|
254
486
|
|
@@ -291,7 +523,7 @@ class App
|
|
291
523
|
c.action do |global_options, options, args|
|
292
524
|
if options[:save]
|
293
525
|
title = options[:save].gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
|
294
|
-
NA.save_search(title, "find #{NA.command_line.map { |
|
526
|
+
NA.save_search(title, "find #{NA.command_line.map { |cmd| "\"#{cmd}\"" }.join(' ')}")
|
295
527
|
end
|
296
528
|
|
297
529
|
depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
|
@@ -324,24 +556,24 @@ class App
|
|
324
556
|
options[:in].split(/ *, */).each do |a|
|
325
557
|
m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
|
326
558
|
todo.push({
|
327
|
-
|
328
|
-
|
329
|
-
|
559
|
+
token: m['tok'],
|
560
|
+
required: !m['req'].nil?
|
561
|
+
})
|
330
562
|
end
|
331
563
|
end
|
332
564
|
|
333
|
-
files, actions = NA.parse_actions(depth: depth,
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
if tokens.is_a?(Array)
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
565
|
+
files, actions, = NA.parse_actions(depth: depth,
|
566
|
+
query: todo,
|
567
|
+
search: tokens,
|
568
|
+
negate: options[:invert],
|
569
|
+
regex: options[:regex],
|
570
|
+
project: options[:project],
|
571
|
+
require_na: false)
|
572
|
+
regexes = if tokens.is_a?(Array)
|
573
|
+
tokens.delete_if { |token| token[:negate] }.map { |token| token[:token] }
|
574
|
+
else
|
575
|
+
[tokens]
|
576
|
+
end
|
345
577
|
|
346
578
|
NA.output_actions(actions, depth, files: files, regexes: regexes)
|
347
579
|
end
|
@@ -401,7 +633,6 @@ class App
|
|
401
633
|
|
402
634
|
all_req = args.join(' ') !~ /[+!\-]/ && !options[:or]
|
403
635
|
args.join(',').split(/ *, */).each do |arg|
|
404
|
-
# TODO: <> comparisons do nothing right now
|
405
636
|
m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
|
406
637
|
|
407
638
|
tags.push({
|
@@ -413,6 +644,10 @@ class App
|
|
413
644
|
})
|
414
645
|
end
|
415
646
|
|
647
|
+
search_for_done = false
|
648
|
+
tags.each { |tag| search_for_done = true if tag[:tag] =~ /done/ }
|
649
|
+
tags.push({ tag: 'done', value: nil, negate: true}) unless search_for_done
|
650
|
+
|
416
651
|
todo = nil
|
417
652
|
if options[:in]
|
418
653
|
todo = []
|
@@ -425,12 +660,12 @@ class App
|
|
425
660
|
end
|
426
661
|
end
|
427
662
|
|
428
|
-
files, actions = NA.parse_actions(depth: depth,
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
663
|
+
files, actions, = NA.parse_actions(depth: depth,
|
664
|
+
query: todo,
|
665
|
+
tag: tags,
|
666
|
+
negate: options[:invert],
|
667
|
+
project: options[:project],
|
668
|
+
require_na: false)
|
434
669
|
regexes = tags.delete_if { |token| token[:negate] }.map { |token| token[:token] }
|
435
670
|
NA.output_actions(actions, depth, files: files, regexes: regexes)
|
436
671
|
end
|
@@ -484,13 +719,15 @@ class App
|
|
484
719
|
c.arg_name 'EDITOR'
|
485
720
|
c.flag %i[a app]
|
486
721
|
|
487
|
-
c.action do |global_options, options,
|
722
|
+
c.action do |global_options, options, args|
|
488
723
|
depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
|
489
724
|
3
|
490
725
|
else
|
491
726
|
options[:depth].nil? ? global_options[:depth].to_i : options[:depth].to_i
|
492
727
|
end
|
493
728
|
files = NA.find_files(depth: depth)
|
729
|
+
files.delete_if { |f| f !~ /.*?(#{args.join('|')}).*?.#{NA.extension}/ } if args.count.positive?
|
730
|
+
|
494
731
|
file = if files.count > 1
|
495
732
|
NA.select_file(files)
|
496
733
|
else
|
@@ -513,13 +750,16 @@ class App
|
|
513
750
|
command %i[todos] do |c|
|
514
751
|
c.action do |_global_options, _options, args|
|
515
752
|
if args.count.positive?
|
516
|
-
|
753
|
+
all_req = args.join(' ') !~ /[+!\-]/
|
754
|
+
|
755
|
+
tokens = [{ token: '*', required: all_req, negate: false }]
|
517
756
|
args.each do |arg|
|
518
757
|
arg.split(/ *, */).each do |a|
|
519
|
-
m = a.match(/^(?<req
|
758
|
+
m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
520
759
|
tokens.push({
|
521
760
|
token: m['tok'],
|
522
|
-
required: !m['req'].nil?
|
761
|
+
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
762
|
+
negate: !m['req'].nil? && m['req'] =~ /[!\-]/
|
523
763
|
})
|
524
764
|
end
|
525
765
|
end
|
@@ -529,6 +769,39 @@ class App
|
|
529
769
|
end
|
530
770
|
end
|
531
771
|
|
772
|
+
desc 'Show list of projects for a file'
|
773
|
+
long_desc 'Arguments will be interpreted as a query for a known todo file,
|
774
|
+
fuzzy matched. Separate directories with /, :, or a space, e.g. `na projects code/marked`'
|
775
|
+
arg_name 'QUERY', optional: true
|
776
|
+
command %i[projects] do |c|
|
777
|
+
c.desc 'Search for files X directories deep'
|
778
|
+
c.arg_name 'DEPTH'
|
779
|
+
c.flag %i[d depth], must_match: /^[1-9]$/, type: :integer, default_value: 1
|
780
|
+
|
781
|
+
c.desc 'Output projects as paths instead of hierarchy'
|
782
|
+
c.switch %i[p paths], negatable: false
|
783
|
+
|
784
|
+
c.action do |_global_options, options, args|
|
785
|
+
if args.count.positive?
|
786
|
+
all_req = args.join(' ') !~ /[+!\-]/
|
787
|
+
|
788
|
+
tokens = [{ token: '*', required: all_req, negate: false }]
|
789
|
+
args.each do |arg|
|
790
|
+
arg.split(/ *, */).each do |a|
|
791
|
+
m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
792
|
+
tokens.push({
|
793
|
+
token: m['tok'],
|
794
|
+
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
795
|
+
negate: !m['req'].nil? && m['req'] =~ /[!\-]/
|
796
|
+
})
|
797
|
+
end
|
798
|
+
end
|
799
|
+
end
|
800
|
+
|
801
|
+
NA.list_projects(query: tokens, depth: options[:depth], paths: options[:paths])
|
802
|
+
end
|
803
|
+
end
|
804
|
+
|
532
805
|
desc 'Show or install prompt hooks for the current shell'
|
533
806
|
long_desc 'Installing the prompt hook allows you to automatically
|
534
807
|
list next actions when you cd into a directory'
|
@@ -581,12 +854,30 @@ class App
|
|
581
854
|
long_desc 'Run without argument to list saved searches'
|
582
855
|
arg_name 'SEARCH_TITLE', optional: true
|
583
856
|
command %i[saved] do |c|
|
584
|
-
c.
|
857
|
+
c.example 'na saved overdue', description: 'perform the search named "overdue"'
|
858
|
+
c.example 'na saved over', description: 'perform the search named "overdue", assuming no other searches match "over"'
|
859
|
+
c.example 'na saved', description: 'list available searches'
|
860
|
+
|
861
|
+
c.desc 'Open the saved search file in $EDITOR'
|
862
|
+
c.switch %i[e edit]
|
863
|
+
|
864
|
+
c.desc 'Delete the specified search definition'
|
865
|
+
c.switch %i[d delete]
|
866
|
+
|
867
|
+
c.action do |_global_options, options, args|
|
868
|
+
if options[:edit]
|
869
|
+
NA.edit_searches
|
870
|
+
end
|
871
|
+
|
585
872
|
searches = NA.load_searches
|
586
873
|
if args.empty?
|
587
874
|
NA.notify("{bg}Saved searches stored in {bw}#{NA.database_path(file: 'saved_searches.yml')}")
|
588
875
|
NA.notify(searches.map { |k, v| "{y}#{k}: {w}#{v}" }.join("\n"), exit_code: 0)
|
589
876
|
else
|
877
|
+
if options[:delete]
|
878
|
+
NA.delete_search(args)
|
879
|
+
end
|
880
|
+
|
590
881
|
keys = searches.keys.delete_if { |k| k !~ /#{args[0]}/ }
|
591
882
|
NA.notify("{r}Search #{args[0]} not found", exit_code: 1) if keys.empty?
|
592
883
|
|
data/lib/na/action.rb
CHANGED
@@ -2,20 +2,22 @@
|
|
2
2
|
|
3
3
|
module NA
|
4
4
|
class Action < Hash
|
5
|
-
attr_reader :file, :project, :parent, :action, :tags
|
5
|
+
attr_reader :file, :project, :parent, :action, :tags, :line, :note
|
6
6
|
|
7
|
-
def initialize(file, project, parent, action)
|
7
|
+
def initialize(file, project, parent, action, idx, note = [])
|
8
8
|
super()
|
9
9
|
|
10
10
|
@file = file
|
11
11
|
@project = project
|
12
12
|
@parent = parent
|
13
|
-
@action = action
|
13
|
+
@action = action.gsub(/\{/, '\\{')
|
14
14
|
@tags = scan_tags
|
15
|
+
@line = idx
|
16
|
+
@note = note
|
15
17
|
end
|
16
18
|
|
17
19
|
def to_s
|
18
|
-
"(#{@file}) #{@project}:#{@parent.join('>')} | #{@action}"
|
20
|
+
"(#{@file}:#{@line}) #{@project}:#{@parent.join('>')} | #{@action}"
|
19
21
|
end
|
20
22
|
|
21
23
|
def inspect
|
@@ -41,14 +43,10 @@ module NA
|
|
41
43
|
}
|
42
44
|
template = default_template.merge(template)
|
43
45
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
parents = "{dc}[{x}#{parents}{dc}]{x} "
|
49
|
-
else
|
50
|
-
parents = ''
|
51
|
-
end
|
46
|
+
parents = @parent.map do |par|
|
47
|
+
NA::Color.template("#{template[:parent]}#{par}")
|
48
|
+
end.join(NA::Color.template(template[:parent_divider]))
|
49
|
+
parents = "{dc}[{x}#{parents}{dc}]{x} "
|
52
50
|
|
53
51
|
project = NA::Color.template("#{template[:project]}#{@project}{x} ")
|
54
52
|
|
@@ -67,7 +65,7 @@ module NA
|
|
67
65
|
NA::Color.template(template[:output].gsub(/%filename/, filename)
|
68
66
|
.gsub(/%project/, project)
|
69
67
|
.gsub(/%parents?/, parents)
|
70
|
-
.gsub(/%action/, action.highlight_search(regexes)))
|
68
|
+
.gsub(/%action/, action.highlight_search(regexes))).gsub(/\\\{/, '{')
|
71
69
|
end
|
72
70
|
|
73
71
|
def tags_match?(any: [], all: [], none: [])
|
data/lib/na/colors.rb
CHANGED
@@ -226,8 +226,9 @@ module NA
|
|
226
226
|
##
|
227
227
|
def template(input)
|
228
228
|
input = input.join(' ') if input.is_a? Array
|
229
|
+
|
229
230
|
fmt = input.gsub(/%/, '%%')
|
230
|
-
fmt = fmt.gsub(/(?<!\\
|
231
|
+
fmt = fmt.gsub(/(?<!\\)\{(\w+)\}/i) do
|
231
232
|
Regexp.last_match(1).split('').map { |c| "%<#{c}>s" }.join('')
|
232
233
|
end
|
233
234
|
|