na 1.1.26 → 1.2.1

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.
data/bin/na CHANGED
@@ -78,6 +78,9 @@ class App
78
78
  c.arg_name 'PROJECT[/SUBPROJECT]'
79
79
  c.flag %i[proj project]
80
80
 
81
+ c.desc 'Include @done actions'
82
+ c.switch %i[done]
83
+
81
84
  c.action do |global_options, options, args|
82
85
  if global_options[:add]
83
86
  cmd = ['add']
@@ -95,13 +98,16 @@ class App
95
98
  end
96
99
 
97
100
  if args.count.positive?
101
+ all_req = false
102
+
98
103
  tokens = []
99
104
  args.each do |arg|
100
105
  arg.split(/ *, */).each do |a|
101
- m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
106
+ m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
102
107
  tokens.push({
103
108
  token: m['tok'],
104
- required: !m['req'].nil?
109
+ required: all_req || (!m['req'].nil? && m['req'] == '+'),
110
+ negate: !m['req'].nil? && m['req'] =~ /[!\-]/
105
111
  })
106
112
  end
107
113
  end
@@ -110,12 +116,13 @@ class App
110
116
  NA.na_tag = options[:tag] unless options[:tag].nil?
111
117
  require_na = true
112
118
 
113
- tag = [{ tag: tag, value: nil }]
114
- files, actions = NA.parse_actions(depth: depth,
115
- query: tokens,
116
- tag: tag,
117
- project: options[:project],
118
- require_na: require_na)
119
+ tag = [{ tag: tag, value: nil }, { tag: 'done', value: nil, negate: true}]
120
+ files, actions, = NA.parse_actions(depth: depth,
121
+ done: options[:done],
122
+ query: tokens,
123
+ tag: tag,
124
+ project: options[:project],
125
+ require_na: require_na)
119
126
 
120
127
  NA.output_actions(actions, depth, files: files)
121
128
  end
@@ -146,7 +153,11 @@ class App
146
153
  c.desc 'Add action to specific project'
147
154
  c.arg_name 'PROJECT'
148
155
  c.default_value 'Inbox'
149
- c.flag %i[to]
156
+ c.flag %i[to project proj]
157
+
158
+ c.desc 'Add to a known todo file, partial matches allowed'
159
+ c.arg_name 'TODO_FILE'
160
+ c.flag %i[in todo]
150
161
 
151
162
  c.desc 'Use a tag other than the default next action tag'
152
163
  c.arg_name 'TAG'
@@ -183,7 +194,7 @@ class App
183
194
  action = "#{action.gsub(/@priority\(\d+\)/, '')} @priority(#{options[:priority]})"
184
195
  end
185
196
 
186
- note_rx = /^(.+)\((.*?)\)$/
197
+ note_rx = /^(.+) \((.*?)\)$/
187
198
  split_note = if action =~ note_rx
188
199
  n = Regexp.last_match(2)
189
200
  action.sub!(note_rx, '\1').strip!
@@ -229,6 +240,34 @@ class App
229
240
  Process.exit 1
230
241
  end
231
242
  end
243
+ elsif options[:todo]
244
+ todo = []
245
+ options[:todo].split(/ *, */).each do |a|
246
+ m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
247
+ todo.push({
248
+ token: m['tok'],
249
+ required: !m['req'].nil?
250
+ })
251
+ end
252
+ dirs = NA.match_working_dir(todo)
253
+ if dirs.count.positive?
254
+ target = dirs[0]
255
+ else
256
+ todo = "#{options[:todo].sub(/#{NA.extension}$/, '')}.#{NA.extension}"
257
+ target = File.expand_path(todo)
258
+ unless File.exist?(target)
259
+
260
+ res = NA.yn(NA::Color.template("{by}Specified file not found, create #{todo}"), default: true)
261
+ if res
262
+ basename = File.basename(target, ".#{NA.extension}")
263
+ NA.create_todo(target, basename)
264
+ else
265
+ NA.notify('{r}Cancelled{x}', exit_code: 1)
266
+
267
+ end
268
+ end
269
+
270
+ end
232
271
  else
233
272
  files = NA.find_files(depth: options[:depth])
234
273
  if files.count.zero?
@@ -243,12 +282,209 @@ class App
243
282
  end
244
283
  target = files.count > 1 ? NA.select_file(files) : files[0]
245
284
  unless files.count.positive? && File.exist?(target)
246
- puts NA::Color.template('{r}Cancelled{x}')
247
- Process.exit 1
285
+ NA.notify('{r}Cancelled{x}', exit_code: 1)
286
+
287
+ end
288
+ end
289
+
290
+ NA.add_action(target, options[:project], action, note)
291
+ end
292
+ end
293
+
294
+ desc 'Update an existing action'
295
+ long_desc 'Provides an easy way to complete, prioritize, and tag existing actions.
296
+
297
+ If multiple todo files are found in the current directory, a menu will
298
+ allow you to pick which file to act on.'
299
+ arg_name 'ACTION'
300
+ command %i[update] do |c|
301
+ c.example 'na update --remove na "An existing task"', desc: 'Find "An existing task" action and remove the @na tag from it'
302
+ c.example 'na update --tag waiting "A bug I need to fix" -p 4 -n',
303
+ desc: 'Find "A bug..." action, add @waiting, add/update @priority(4), and prompt for an additional note'
304
+ c.example 'na update --archive My cool action', desc: 'Add @done to "My cool action" and immediately move to Archive'
305
+
306
+ c.desc 'Prompt for additional notes. Input will be appended to any existing note.'
307
+ c.switch %i[n note], negatable: false
308
+
309
+ c.desc 'Overwrite note instead of appending'
310
+ c.switch %i[o overwrite], negatable: false
311
+
312
+ c.desc 'Add/change a priority level 1-5'
313
+ c.arg_name 'PRIO'
314
+ c.flag %i[p priority], must_match: /[1-5]/, type: :integer, default_value: 0
315
+
316
+ c.desc 'Move action to specific project'
317
+ c.arg_name 'PROJECT'
318
+ c.flag %i[to project proj]
319
+
320
+ c.desc 'Use a known todo file, partial matches allowed'
321
+ c.arg_name 'TODO_FILE'
322
+ c.flag %i[in todo]
323
+
324
+ c.desc 'Add a tag to the action, @tag(values) allowed'
325
+ c.arg_name 'TAG'
326
+ c.flag %i[t tag], multiple: true
327
+
328
+ c.desc 'Remove a tag to the action'
329
+ c.arg_name 'TAG'
330
+ c.flag %i[r remove], multiple: true
331
+
332
+ c.desc 'Add a @done tag to action'
333
+ c.switch %i[f finish done], negatable: false
334
+
335
+ c.desc 'Add a @done tag to action and move to Archive'
336
+ c.switch %i[a archive], negatable: false
337
+
338
+ c.desc 'Delete an action'
339
+ c.switch %i[delete], negatable: false
340
+
341
+ c.desc 'Specify the file to search for the task'
342
+ c.arg_name 'PATH'
343
+ c.flag %i[file]
344
+
345
+ c.desc 'Search for files X directories deep'
346
+ c.arg_name 'DEPTH'
347
+ c.flag %i[d depth], must_match: /^[1-9]$/, type: :integer, default_value: 1
348
+
349
+ c.desc 'Match actions containing tag. Allows value comparisons'
350
+ c.arg_name 'TAG'
351
+ c.flag %i[tagged], multiple: true
352
+
353
+ c.desc 'Act on all matches immediately (no menu)'
354
+ c.switch %i[all], negatable: false
355
+
356
+ c.desc 'Interpret search pattern as regular expression'
357
+ c.switch %i[e regex], negatable: false
358
+
359
+ c.desc 'Match pattern exactly'
360
+ c.switch %i[x exact], negatable: false
361
+
362
+ c.action do |_global_options, options, args|
363
+ reader = TTY::Reader.new
364
+ action = if args.count.positive?
365
+ args.join(' ').strip
366
+ elsif TTY::Which.exist?('gum') && options[:tagged].empty?
367
+ options = [
368
+ %(--placeholder "Enter a task to search for"),
369
+ '--char-limit=500',
370
+ "--width=#{TTY::Screen.columns}"
371
+ ]
372
+ `gum input #{options.join(' ')}`.strip
373
+ elsif options[:tagged].empty?
374
+ puts NA::Color.template('{bm}Enter search string:{x}')
375
+ reader.read_line(NA::Color.template('{by}> {bw}')).strip
376
+ end
377
+
378
+ if action
379
+ tokens = nil
380
+ if options[:exact]
381
+ tokens = action
382
+ elsif options[:regex]
383
+ tokens = Regexp.new(action, Regexp::IGNORECASE)
384
+ else
385
+ tokens = []
386
+ all_req = action !~ /[+!\-]/ && !options[:or]
387
+
388
+ action.split(/ /).each do |arg|
389
+ m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
390
+ tokens.push({
391
+ token: m['tok'],
392
+ required: all_req || (!m['req'].nil? && m['req'] == '+'),
393
+ negate: !m['req'].nil? && m['req'] =~ /[!\-]/
394
+ })
395
+ end
396
+ end
397
+ end
398
+
399
+ if (action.nil? || action.empty?) && options[:tagged].empty?
400
+ puts 'Empty input, cancelled'
401
+ Process.exit 1
402
+ end
403
+
404
+ all_req = options[:tagged].join(' ') !~ /[+!\-]/ && !options[:or]
405
+ tags = []
406
+ options[:tagged].join(',').split(/ *, */).each do |arg|
407
+ m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
408
+
409
+ tags.push({
410
+ tag: m['tag'].wildcard_to_rx,
411
+ comp: m['op'],
412
+ value: m['val'],
413
+ required: all_req || (!m['req'].nil? && m['req'] == '+'),
414
+ negate: !m['req'].nil? && m['req'] =~ /[!\-]/
415
+ })
416
+ end
417
+
418
+ priority = options[:priority].to_i if options[:priority]&.to_i&.positive?
419
+ add_tags = options[:tag].map { |t| t.sub(/^@/, '').wildcard_to_rx }
420
+ remove_tags = options[:remove].map { |t| t.sub(/^@/, '').wildcard_to_rx }
421
+
422
+ line_note = if options[:note]
423
+ if TTY::Which.exist?('gum')
424
+ args = ['--placeholder "Enter a note, CTRL-d to save"']
425
+ args << '--char-limit 0'
426
+ args << '--width $(tput cols)'
427
+ `gum write #{args.join(' ')}`.strip.split("\n")
428
+ else
429
+ puts NA::Color.template('{bm}Enter a note, {bw}CTRL-d{bm} to end editing{bw}')
430
+ reader.read_multiline
431
+ end
432
+ end
433
+
434
+ note = line_note.nil? || line_note.empty? ? [] : line_note
435
+
436
+ if options[:file]
437
+ file = File.expand_path(options[:file])
438
+ NA.notify('{r}File not found', exit_code: 1) unless File.exist?(file)
439
+
440
+ targets = [file]
441
+ elsif options[:todo]
442
+ todo = []
443
+ options[:todo].split(/ *, */).each do |a|
444
+ m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
445
+ todo.push({
446
+ token: m['tok'],
447
+ required: !m['req'].nil?
448
+ })
449
+ end
450
+ dirs = NA.match_working_dir(todo)
451
+
452
+ if dirs.count == 1
453
+ targets = [dirs[0]]
454
+ elsif dirs.count.positive?
455
+ targets = NA.select_file(dirs, multiple: true)
456
+ NA.notify('{r}Cancelled', exit_code: 1) unless targets && targets.count.positive?
457
+ else
458
+ NA.notify('{r}Todo not found', exit_code: 1) unless targets && targets.count.positive?
459
+
248
460
  end
461
+ else
462
+ files = NA.find_files(depth: options[:depth])
463
+ NA.notify('{r}No todo file found', exit_code: 1) if files.count.zero?
464
+
465
+ targets = files.count > 1 ? NA.select_file(files, multiple: true) : [files[0]]
466
+ NA.notify('{r}Cancelled{x}', exit_code: 1) unless files.count.positive?
467
+
249
468
  end
250
469
 
251
- NA.add_action(target, options[:to], action, note)
470
+ options[:finish] = true if options[:archive]
471
+ options[:project] = 'Archive' if options[:archive]
472
+
473
+ NA.notify('{r}No search terms provided', exit_code: 1) if tokens.nil? && options[:tagged].empty?
474
+
475
+ targets.each do |target|
476
+ NA.update_action(target, tokens,
477
+ priority: priority,
478
+ add_tag: add_tags,
479
+ remove_tag: remove_tags,
480
+ finish: options[:finish],
481
+ project: options[:project],
482
+ delete: options[:delete],
483
+ note: note,
484
+ overwrite: options[:overwrite],
485
+ tagged: tags,
486
+ all: options[:all])
487
+ end
252
488
  end
253
489
  end
254
490
 
@@ -281,6 +517,9 @@ class App
281
517
  c.arg_name 'PROJECT[/SUBPROJECT]'
282
518
  c.flag %i[proj project]
283
519
 
520
+ c.desc 'Include @done actions'
521
+ c.switch %i[done]
522
+
284
523
  c.desc 'Show actions not matching search pattern'
285
524
  c.switch %i[v invert], negatable: false
286
525
 
@@ -291,7 +530,7 @@ class App
291
530
  c.action do |global_options, options, args|
292
531
  if options[:save]
293
532
  title = options[:save].gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
294
- NA.save_search(title, "find #{NA.command_line.map { |c| "\"#{c}\"" }.join(' ')}")
533
+ NA.save_search(title, "find #{NA.command_line.map { |cmd| "\"#{cmd}\"" }.join(' ')}")
295
534
  end
296
535
 
297
536
  depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
@@ -324,24 +563,25 @@ class App
324
563
  options[:in].split(/ *, */).each do |a|
325
564
  m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
326
565
  todo.push({
327
- token: m['tok'],
328
- required: !m['req'].nil?
329
- })
566
+ token: m['tok'],
567
+ required: !m['req'].nil?
568
+ })
330
569
  end
331
570
  end
332
571
 
333
- files, actions = NA.parse_actions(depth: depth,
334
- query: todo,
335
- search: tokens,
336
- negate: options[:invert],
337
- regex: options[:regex],
338
- project: options[:project],
339
- require_na: false)
340
- if tokens.is_a?(Array)
341
- regexes = tokens.delete_if { |token| token[:negate] }.map { |token| token[:token] }
342
- else
343
- regexes = [tokens]
344
- end
572
+ files, actions, = NA.parse_actions(depth: depth,
573
+ done: options[:done],
574
+ query: todo,
575
+ search: tokens,
576
+ negate: options[:invert],
577
+ regex: options[:regex],
578
+ project: options[:project],
579
+ require_na: false)
580
+ regexes = if tokens.is_a?(Array)
581
+ tokens.delete_if { |token| token[:negate] }.map { |token| token[:token] }
582
+ else
583
+ [tokens]
584
+ end
345
585
 
346
586
  NA.output_actions(actions, depth, files: files, regexes: regexes)
347
587
  end
@@ -378,6 +618,9 @@ class App
378
618
  c.arg_name 'PROJECT[/SUBPROJECT]'
379
619
  c.flag %i[proj project]
380
620
 
621
+ c.desc 'Include @done actions'
622
+ c.switch %i[done]
623
+
381
624
  c.desc 'Show actions not matching tags'
382
625
  c.switch %i[v invert], negatable: false
383
626
 
@@ -401,7 +644,6 @@ class App
401
644
 
402
645
  all_req = args.join(' ') !~ /[+!\-]/ && !options[:or]
403
646
  args.join(',').split(/ *, */).each do |arg|
404
- # TODO: <> comparisons do nothing right now
405
647
  m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
406
648
 
407
649
  tags.push({
@@ -413,6 +655,10 @@ class App
413
655
  })
414
656
  end
415
657
 
658
+ search_for_done = false
659
+ tags.each { |tag| search_for_done = true if tag[:tag] =~ /done/ }
660
+ tags.push({ tag: 'done', value: nil, negate: true}) unless search_for_done
661
+
416
662
  todo = nil
417
663
  if options[:in]
418
664
  todo = []
@@ -425,12 +671,13 @@ class App
425
671
  end
426
672
  end
427
673
 
428
- files, actions = NA.parse_actions(depth: depth,
429
- query: todo,
430
- tag: tags,
431
- negate: options[:invert],
432
- project: options[:project],
433
- require_na: false)
674
+ files, actions, = NA.parse_actions(depth: depth,
675
+ done: options[:done],
676
+ query: todo,
677
+ tag: tags,
678
+ negate: options[:invert],
679
+ project: options[:project],
680
+ require_na: false)
434
681
  regexes = tags.delete_if { |token| token[:negate] }.map { |token| token[:token] }
435
682
  NA.output_actions(actions, depth, files: files, regexes: regexes)
436
683
  end
@@ -484,13 +731,15 @@ class App
484
731
  c.arg_name 'EDITOR'
485
732
  c.flag %i[a app]
486
733
 
487
- c.action do |global_options, options, _args|
734
+ c.action do |global_options, options, args|
488
735
  depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
489
736
  3
490
737
  else
491
738
  options[:depth].nil? ? global_options[:depth].to_i : options[:depth].to_i
492
739
  end
493
740
  files = NA.find_files(depth: depth)
741
+ files.delete_if { |f| f !~ /.*?(#{args.join('|')}).*?.#{NA.extension}/ } if args.count.positive?
742
+
494
743
  file = if files.count > 1
495
744
  NA.select_file(files)
496
745
  else
@@ -513,13 +762,16 @@ class App
513
762
  command %i[todos] do |c|
514
763
  c.action do |_global_options, _options, args|
515
764
  if args.count.positive?
516
- tokens = []
765
+ all_req = args.join(' ') !~ /[+!\-]/
766
+
767
+ tokens = [{ token: '*', required: all_req, negate: false }]
517
768
  args.each do |arg|
518
769
  arg.split(/ *, */).each do |a|
519
- m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
770
+ m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
520
771
  tokens.push({
521
772
  token: m['tok'],
522
- required: !m['req'].nil?
773
+ required: all_req || (!m['req'].nil? && m['req'] == '+'),
774
+ negate: !m['req'].nil? && m['req'] =~ /[!\-]/
523
775
  })
524
776
  end
525
777
  end
@@ -529,6 +781,39 @@ class App
529
781
  end
530
782
  end
531
783
 
784
+ desc 'Show list of projects for a file'
785
+ long_desc 'Arguments will be interpreted as a query for a known todo file,
786
+ fuzzy matched. Separate directories with /, :, or a space, e.g. `na projects code/marked`'
787
+ arg_name 'QUERY', optional: true
788
+ command %i[projects] do |c|
789
+ c.desc 'Search for files X directories deep'
790
+ c.arg_name 'DEPTH'
791
+ c.flag %i[d depth], must_match: /^[1-9]$/, type: :integer, default_value: 1
792
+
793
+ c.desc 'Output projects as paths instead of hierarchy'
794
+ c.switch %i[p paths], negatable: false
795
+
796
+ c.action do |_global_options, options, args|
797
+ if args.count.positive?
798
+ all_req = args.join(' ') !~ /[+!\-]/
799
+
800
+ tokens = [{ token: '*', required: all_req, negate: false }]
801
+ args.each do |arg|
802
+ arg.split(/ *, */).each do |a|
803
+ m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
804
+ tokens.push({
805
+ token: m['tok'],
806
+ required: all_req || (!m['req'].nil? && m['req'] == '+'),
807
+ negate: !m['req'].nil? && m['req'] =~ /[!\-]/
808
+ })
809
+ end
810
+ end
811
+ end
812
+
813
+ NA.list_projects(query: tokens, depth: options[:depth], paths: options[:paths])
814
+ end
815
+ end
816
+
532
817
  desc 'Show or install prompt hooks for the current shell'
533
818
  long_desc 'Installing the prompt hook allows you to automatically
534
819
  list next actions when you cd into a directory'
@@ -577,16 +862,52 @@ class App
577
862
  end
578
863
  end
579
864
 
865
+ desc 'Display the changelog'
866
+ command %i[changes changelog] do |c|
867
+ c.action do |_, _, _|
868
+ changelog = File.expand_path(File.join(File.dirname(__FILE__), '..', 'CHANGELOG.md'))
869
+ pagers = [
870
+ 'mdless',
871
+ 'mdcat',
872
+ 'bat',
873
+ ENV['PAGER'],
874
+ 'less -FXr',
875
+ ENV['GIT_PAGER'],
876
+ 'more -r'
877
+ ]
878
+ pager = pagers.find { |cmd| TTY::Which.exist?(cmd.split.first) }
879
+ system %(#{pager} "#{changelog}")
880
+ end
881
+ end
882
+
580
883
  desc 'Execute a saved search'
581
884
  long_desc 'Run without argument to list saved searches'
582
885
  arg_name 'SEARCH_TITLE', optional: true
583
886
  command %i[saved] do |c|
584
- c.action do |_global_options, _options, args|
887
+ c.example 'na saved overdue', description: 'perform the search named "overdue"'
888
+ c.example 'na saved over', description: 'perform the search named "overdue", assuming no other searches match "over"'
889
+ c.example 'na saved', description: 'list available searches'
890
+
891
+ c.desc 'Open the saved search file in $EDITOR'
892
+ c.switch %i[e edit]
893
+
894
+ c.desc 'Delete the specified search definition'
895
+ c.switch %i[d delete]
896
+
897
+ c.action do |_global_options, options, args|
898
+ if options[:edit]
899
+ NA.edit_searches
900
+ end
901
+
585
902
  searches = NA.load_searches
586
903
  if args.empty?
587
904
  NA.notify("{bg}Saved searches stored in {bw}#{NA.database_path(file: 'saved_searches.yml')}")
588
905
  NA.notify(searches.map { |k, v| "{y}#{k}: {w}#{v}" }.join("\n"), exit_code: 0)
589
906
  else
907
+ if options[:delete]
908
+ NA.delete_search(args)
909
+ end
910
+
590
911
  keys = searches.keys.delete_if { |k| k !~ /#{args[0]}/ }
591
912
  NA.notify("{r}Search #{args[0]} not found", exit_code: 1) if keys.empty?
592
913
 
@@ -614,8 +935,8 @@ class App
614
935
  on_error do |exception|
615
936
  case exception
616
937
  when GLI::UnknownCommand
617
- cmd = ['add']
618
- cmd.concat(ARGV.unshift($first_arg)) if ARGV.count.positive?
938
+ cmd = ['saved']
939
+ cmd.concat(ARGV.unshift($first_arg))
619
940
 
620
941
  exit run(cmd)
621
942
  when SystemExit
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
- if @parent != ['Inbox']
45
- parents = @parent.map do |par|
46
- NA::Color.template("#{template[:parent]}#{par}")
47
- end.join(NA::Color.template(template[:parent_divider]))
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(/(?<!\\u)\{(\w+)\}/i) do
231
+ fmt = fmt.gsub(/(?<!\\)\{(\w+)\}/i) do
231
232
  Regexp.last_match(1).split('').map { |c| "%<#{c}>s" }.join('')
232
233
  end
233
234