na 1.2.37 → 1.2.38

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,6 +7,10 @@ module NA
7
7
 
8
8
  attr_accessor :verbose, :extension, :na_tag, :command_line, :command, :globals, :global_file, :cwd_is, :cwd, :stdin
9
9
 
10
+ def theme
11
+ @theme ||= NA::Theme.load_theme
12
+ end
13
+
10
14
  ##
11
15
  ## Output to STDERR
12
16
  ##
@@ -19,7 +23,7 @@ module NA
19
23
  return if debug && !NA.verbose
20
24
 
21
25
  if debug
22
- $stderr.puts NA::Color.template("{xdw}#{msg}{x}")
26
+ $stderr.puts NA::Color.template("{x}#{NA.theme[:debug]}#{msg}{x}")
23
27
  else
24
28
  $stderr.puts NA::Color.template("{x}#{msg}{x}")
25
29
  end
@@ -100,7 +104,7 @@ module NA
100
104
  f.puts(content)
101
105
  end
102
106
  save_working_dir(target)
103
- notify("{y}Created {bw}#{target}")
107
+ notify("#{NA.theme[:warning]}Created #{NA.theme[:file]}#{target}")
104
108
  end
105
109
 
106
110
  ##
@@ -116,7 +120,7 @@ module NA
116
120
  def select_file(files, multiple: false)
117
121
  res = choose_from(files, prompt: multiple ? 'Select files' : 'Select a file', multiple: multiple)
118
122
 
119
- notify('{r}No file selected, cancelled', exit_code: 1) unless res && res.length.positive?
123
+ notify("#{NA.theme[:error]}No file selected, cancelled", exit_code: 1) unless res && res.length.positive?
120
124
 
121
125
  res
122
126
  end
@@ -143,7 +147,7 @@ module NA
143
147
  done: done })
144
148
 
145
149
  unless todo.actions.count.positive?
146
- NA.notify("{r}No matching actions found in {bw}#{File.basename(target, ".#{NA.extension}")}")
150
+ NA.notify("#{NA.theme[:error]}No matching actions found in #{File.basename(target, ".#{NA.extension}").highlight_filename}")
147
151
  return
148
152
  end
149
153
 
@@ -152,7 +156,7 @@ module NA
152
156
  options = todo.actions.map { |action| "#{action.line} % #{action.parent.join('/')} : #{action.action}" }
153
157
  res = choose_from(options, prompt: 'Make a selection: ', multiple: true, sorted: true)
154
158
 
155
- NA.notify('{r}Cancelled', exit_code: 1) unless res && res.length.positive?
159
+ NA.notify("#{NA.theme[:error]}Cancelled", exit_code: 1) unless res && res.length.positive?
156
160
 
157
161
  selected = NA::Actions.new
158
162
  res.each do |result|
@@ -247,11 +251,11 @@ module NA
247
251
  project = project.sub(/:$/, '')
248
252
  target_proj = projects.select { |pr| pr.project =~ /#{project.gsub(/:/, '.*?:.*?')}/i }.first
249
253
  if target_proj.nil?
250
- res = NA.yn(NA::Color.template("{y}Project {bw}#{project}{xy} doesn't exist, add it"), default: true)
254
+ res = NA.yn(NA::Color.template("#{NA.theme[:warning]}Project #{NA.theme[:file]}#{project}#{NA.theme[:warning]} doesn't exist, add it"), default: true)
251
255
  if res
252
256
  target_proj = insert_project(target, project, projects)
253
257
  else
254
- NA.notify('{x}Cancelled', exit_code: 1)
258
+ NA.notify("#{NA.theme[:error]}Cancelled", exit_code: 1)
255
259
  end
256
260
  end
257
261
  end
@@ -270,7 +274,7 @@ module NA
270
274
  projects.select { |proj| proj.project =~ /^#{add.parent.join(':')}$/ }.first
271
275
  end
272
276
 
273
- NA.notify("{r}Error parsing project #{target}", exit_code: 1) if target_proj.nil?
277
+ NA.notify("#{NA.theme[:error]}Error parsing project #{NA.theme[:filename]}#{target}", exit_code: 1) if target_proj.nil?
274
278
 
275
279
  indent = "\t" * target_proj.indent
276
280
  note = note.split("\n") unless note.is_a?(Array)
@@ -364,7 +368,11 @@ module NA
364
368
  backup_file(target)
365
369
  File.open(target, 'w') { |f| f.puts contents.join("\n") }
366
370
 
367
- add ? notify("{by}Task added to {bw}#{target}") : notify("{by}Task updated in {bw}#{target}")
371
+ if add
372
+ notify("#{NA.theme[:success]}Task added to #{NA.theme[:filename]}#{target}")
373
+ else
374
+ notify("#{NA.theme[:success]}Task updated in #{NA.theme[:filename]}#{target}")
375
+ end
368
376
  end
369
377
 
370
378
  ##
@@ -385,7 +393,6 @@ module NA
385
393
  else
386
394
  project = NA.cwd
387
395
  end
388
- puts [add_tag, project]
389
396
  end
390
397
 
391
398
  action = Action.new(file, project, parent, action, nil, note)
@@ -519,12 +526,12 @@ module NA
519
526
  ##
520
527
  def match_working_dir(search, distance: 1, require_last: true)
521
528
  file = database_path
522
- NA.notify('{r}No na database found', exit_code: 1) unless File.exist?(file)
529
+ NA.notify("#{NA.theme[:error]}No na database found", exit_code: 1) unless File.exist?(file)
523
530
 
524
531
  dirs = file.read_file.split("\n")
525
532
 
526
533
  optional = search.filter { |s| !s[:negate] }.map { |t| t[:token] }
527
- required = search.filter { |s| s[:required] }.map { |t| t[:token] }
534
+ required = search.filter { |s| s[:required] && !s[:negate] }.map { |t| t[:token] }
528
535
  negated = search.filter { |s| s[:negate] }.map { |t| t[:token] }
529
536
 
530
537
  optional.push('*') if optional.count.zero? && required.count.zero? && negated.count.positive?
@@ -533,9 +540,9 @@ module NA
533
540
  optional = ['*']
534
541
  end
535
542
 
536
- NA.notify("{dw}Optional directory regex: {x}#{optional.map { |t| t.dir_to_rx(distance: distance) }}", debug: true)
537
- NA.notify("{dw}Required directory regex: {x}#{required.map { |t| t.dir_to_rx(distance: distance) }}", debug: true)
538
- NA.notify("{dw}Negated directory regex: {x}#{negated.map { |t| t.dir_to_rx(distance: distance, require_last: false) }}", debug: true)
543
+ NA.notify("Optional directory regex: {x}#{optional.map { |t| t.dir_to_rx(distance: distance) }}", debug: true)
544
+ NA.notify("Required directory regex: {x}#{required.map { |t| t.dir_to_rx(distance: distance) }}", debug: true)
545
+ NA.notify("Negated directory regex: {x}#{negated.map { |t| t.dir_to_rx(distance: distance, require_last: false) }}", debug: true)
539
546
 
540
547
  if require_last
541
548
  dirs.delete_if { |d| !d.sub(/\.#{NA.extension}$/, '').dir_matches(any: optional, all: required, none: negated) }
@@ -546,9 +553,9 @@ module NA
546
553
  end
547
554
  end
548
555
 
549
- dirs = dirs.sort.uniq
556
+ dirs = dirs.sort_by { |d| File.basename(d) }.uniq
550
557
  if dirs.empty? && require_last
551
- NA.notify('{y}No matches, loosening search', debug: true)
558
+ NA.notify("#{NA.theme[:warning]}No matches, loosening search", debug: true)
552
559
  match_working_dir(search, distance: 2, require_last: false)
553
560
  else
554
561
  dirs
@@ -605,7 +612,7 @@ module NA
605
612
  if file
606
613
  restore_modified_file(file)
607
614
  else
608
- NA.notify('{br}No matching file found')
615
+ NA.notify("#{NA.theme[:error]}No matching file found")
609
616
  end
610
617
  end
611
618
 
@@ -618,9 +625,9 @@ module NA
618
625
  bak_file = File.join(File.dirname(file), ".#{File.basename(file)}.bak")
619
626
  if File.exist?(bak_file)
620
627
  FileUtils.mv(bak_file, file)
621
- NA.notify("{bg}Backup restored for #{file}")
628
+ NA.notify("#{NA.theme[:success]}Backup restored for #{file.highlight_filename}")
622
629
  else
623
- NA.notify("{br}Backup file for #{file} not found")
630
+ NA.notify("#{NA.theme[:error]}Backup file for #{file.highlight_filename} not found")
624
631
  end
625
632
  end
626
633
 
@@ -698,13 +705,13 @@ module NA
698
705
  else
699
706
  file = database_path
700
707
  content = File.exist?(file) ? file.read_file.strip : ''
701
- notify('{br}Database empty', exit_code: 1) if content.empty?
708
+ notify("#{NA.theme[:error]}Database empty", exit_code: 1) if content.empty?
702
709
 
703
710
  content.split(/\n/)
704
711
  end
705
712
 
706
713
  dirs.map! do |dir|
707
- "{xdg}#{dir.sub(/^#{ENV['HOME']}/, '~').sub(%r{/([^/]+)\.#{NA.extension}$}, '/{xby}\1{x}')}"
714
+ dir.highlight_filename
708
715
  end
709
716
 
710
717
  puts NA::Color.template(dirs.join("\n"))
@@ -717,13 +724,13 @@ module NA
717
724
 
718
725
  if searches.key?(title)
719
726
  res = yn('Overwrite existing definition?', default: true)
720
- notify('{r}Cancelled', exit_code: 0) unless res
727
+ notify("#{NA.theme[:error]}Cancelled", exit_code: 0) unless res
721
728
 
722
729
  end
723
730
 
724
731
  searches[title] = search
725
732
  File.open(file, 'w') { |f| f.puts(YAML.dump(searches)) }
726
- NA.notify("{y}Search #{title} saved", exit_code: 0)
733
+ NA.notify("#{NA.theme[:success]}Search #{NA.theme[:filename]}#{title}#{NA.theme[:success]} saved", exit_code: 0)
727
734
  end
728
735
 
729
736
  def load_searches
@@ -743,37 +750,37 @@ module NA
743
750
  end
744
751
 
745
752
  def delete_search(strings = nil)
746
- NA.notify('{r}Name search required', exit_code: 1) if strings.nil? || strings.empty?
753
+ NA.notify("#{NA.theme[:error]}Name of search required", exit_code: 1) if strings.nil? || strings.empty?
747
754
 
748
755
  file = database_path(file: 'saved_searches.yml')
749
- NA.notify('{r}No search definitions file found', exit_code: 1) unless File.exist?(file)
756
+ NA.notify("#{NA.theme[:error]}No search definitions file found", exit_code: 1) unless File.exist?(file)
750
757
 
751
758
  searches = YAML.safe_load(file.read_file)
752
759
  keys = searches.keys.delete_if { |k| k !~ /(#{strings.join('|')})/ }
753
760
 
754
- res = yn(NA::Color.template(%({y}Remove #{keys.count > 1 ? 'searches' : 'search'} {bw}"#{keys.join(', ')}"{x})),
761
+ res = yn(NA::Color.template(%(#{NA.theme[:warning]}Remove #{keys.count > 1 ? 'searches' : 'search'} #{NA.theme[:file]}"#{keys.join(', ')}"{x})),
755
762
  default: false)
756
763
 
757
- NA.notify('{r}Cancelled', exit_code: 1) unless res
764
+ NA.notify("#{NA.theme[:error]}Cancelled", exit_code: 1) unless res
758
765
 
759
766
  searches.delete_if { |k| keys.include?(k) }
760
767
 
761
768
  File.open(file, 'w') { |f| f.puts(YAML.dump(searches)) }
762
769
 
763
- NA.notify("{y}Deleted {bw}#{keys.count}{xy} #{keys.count > 1 ? 'searches' : 'search'}", exit_code: 0)
770
+ NA.notify("#{NA.theme[:warning]}Deleted {bw}#{keys.count}{x}#{NA.theme[:warning]} #{keys.count > 1 ? 'searches' : 'search'}", exit_code: 0)
764
771
  end
765
772
 
766
773
  def edit_searches
767
774
  file = database_path(file: 'saved_searches.yml')
768
775
  searches = load_searches
769
776
 
770
- NA.notify('{r}No search definitions found', exit_code: 1) unless searches.count.positive?
777
+ NA.notify("#{NA.theme[:error]}No search definitions found", exit_code: 1) unless searches.count.positive?
771
778
 
772
- editor = ENV['EDITOR']
773
- NA.notify('{r}No $EDITOR defined', exit_code: 1) unless editor && TTY::Which.exist?(editor)
779
+ editor = NA.default_editor
780
+ NA.notify("#{NA.theme[:error]}No $EDITOR defined", exit_code: 1) unless editor && TTY::Which.exist?(editor)
774
781
 
775
782
  system %(#{editor} "#{file}")
776
- NA.notify("Opened #{file} in #{editor}", exit_code: 0)
783
+ NA.notify("#{NA.theme[:success]}Opened #{file} in #{editor}", exit_code: 0)
777
784
  end
778
785
 
779
786
  ##
@@ -786,7 +793,7 @@ module NA
786
793
  backup = File.join(File.dirname(target), file)
787
794
  FileUtils.cp(target, backup)
788
795
  save_modified_file(target)
789
- NA.notify("{dw}Backup file created at #{backup}", debug: true)
796
+ NA.notify("#{NA.theme[:warning]}Backup file created at #{backup.highlight_filename}", debug: true)
790
797
  end
791
798
 
792
799
  private
@@ -822,15 +829,15 @@ module NA
822
829
  '--item.foreground=""'
823
830
  ]
824
831
  args.push '--no-limit' if multiple
825
- puts NS::Color.template("{bw}#{prompt}{x}")
832
+ puts NS::Color.template("#{NA.theme[:prompt]}#{prompt}{x}")
826
833
  `echo #{Shellwords.escape(options.join("\n"))}|#{TTY::Which.which('gum')} choose #{args.join(' ')}`.strip
827
834
  else
828
835
  reader = TTY::Reader.new
829
836
  puts
830
837
  options.each.with_index do |f, i|
831
- puts NA::Color.template(format("{bw}%<idx> 2d{xw}) {y}%<action>s{x}\n", idx: i + 1, action: f))
838
+ puts NA::Color.template(format("#{NA.theme[:prompt]}%<idx> 2d{xw}) #{NA.theme[:file]}%<action>s{x}\n", idx: i + 1, action: f))
832
839
  end
833
- result = reader.read_line(NA::Color.template("{bw}#{prompt}{x}")).strip
840
+ result = reader.read_line(NA::Color.template("#{NA.theme[:prompt]}#{prompt}{x}")).strip
834
841
  result.to_i&.positive? ? options[result.to_i - 1] : nil
835
842
  end
836
843
 
@@ -871,7 +878,7 @@ module NA
871
878
  if TTY::Which.exist?('xdg-open')
872
879
  `xdg-open #{Shellwords.escape(file)}`
873
880
  else
874
- notify('{r}Unable to determine executable for `xdg-open`.')
881
+ notify("#{NA.theme[:error]}Unable to determine executable for `xdg-open`.")
875
882
  end
876
883
  end
877
884
  end
data/lib/na/pager.rb CHANGED
@@ -44,7 +44,7 @@ module NA
44
44
  IO.select [input]
45
45
 
46
46
  begin
47
- NA.notify("{dw}Pager #{pager}", debug: true)
47
+ NA.notify("#{NA.theme[:debug]}Pager #{pager}", debug: true)
48
48
  exec(pager)
49
49
  rescue SystemCallError => e
50
50
  raise Errors::DoingStandardError, "Pager error, #{e}"
data/lib/na/prompt.rb CHANGED
@@ -14,7 +14,7 @@ module NA
14
14
  when :tag
15
15
  'na tagged $(basename "$PWD")'
16
16
  else
17
- NA.notify('When using a global file, a prompt hook requires `--cwd_as [tag|project]`', exit_code: 1)
17
+ NA.notify("#{NA.theme[:error]}When using a global file, a prompt hook requires `--cwd_as [tag|project]`", exit_code: 1)
18
18
  end
19
19
  else
20
20
  'na next'
@@ -31,7 +31,7 @@ module NA
31
31
  when :tag
32
32
  'na tagged (basename "$PWD")'
33
33
  else
34
- NA.notify('When using a global file, a prompt hook requires `--cwd_as [tag|project]`', exit_code: 1)
34
+ NA.notify("#{NA.theme[:error]}When using a global file, a prompt hook requires `--cwd_as [tag|project]`", exit_code: 1)
35
35
  end
36
36
  else
37
37
  'na next'
@@ -50,7 +50,7 @@ module NA
50
50
  when :tag
51
51
  'na tagged $(basename "$PWD")'
52
52
  else
53
- NA.notify('When using a global file, a prompt hook requires `--cwd_as [tag|project]`', exit_code: 1)
53
+ NA.notify("#{NA.theme[:error]}When using a global file, a prompt hook requires `--cwd_as [tag|project]`", exit_code: 1)
54
54
  end
55
55
  else
56
56
  'na next'
@@ -83,7 +83,7 @@ module NA
83
83
  def show_prompt_hook(shell)
84
84
  file = prompt_file(shell)
85
85
 
86
- NA.notify("{bw}# Add this to {y}#{file}{x}")
86
+ NA.notify("#{NA.theme[:warning]}# Add this to #{NA.theme[:filename]}#{file}")
87
87
  puts prompt_hook(shell)
88
88
  end
89
89
 
@@ -91,8 +91,8 @@ module NA
91
91
  file = prompt_file(shell)
92
92
 
93
93
  File.open(File.expand_path(file), 'a') { |f| f.puts prompt_hook(shell) }
94
- NA.notify("{y}Added {bw}#{shell}{xy} prompt hook to {bw}#{file}{xy}.{x}")
95
- NA.notify("{y}You may need to close the current terminal and open a new one to enable the script.{x}")
94
+ NA.notify("#{NA.theme[:success]}Added #{NA.theme[:filename]}#{shell}{x}#{NA.theme[:success]} prompt hook to #{NA.theme[:filename]}#{file}#{NA.theme[:success]}.")
95
+ NA.notify("#{NA.theme[:warning]}You may need to close the current terminal and open a new one to enable the script.")
96
96
  end
97
97
  end
98
98
  end
data/lib/na/string.rb CHANGED
@@ -6,6 +6,15 @@ REGEX_TIME = /^#{REGEX_CLOCK}$/i.freeze
6
6
 
7
7
  # String helpers
8
8
  class ::String
9
+ ##
10
+ ## Insert a comment character at the start of every line
11
+ ##
12
+ ## @param char [String] The character to insert (default #)
13
+ ##
14
+ def comment(char = "#")
15
+ split(/\n/).map { |l| "# #{l}" }.join("\n")
16
+ end
17
+
9
18
  ##
10
19
  ## Tests if object is nil or empty
11
20
  ##
@@ -75,6 +84,17 @@ class ::String
75
84
  self =~ /@#{NA.na_tag}\b/
76
85
  end
77
86
 
87
+ ##
88
+ ## Colorize the dirname and filename of a path
89
+ ##
90
+ ## @return Colorized string
91
+ ##
92
+ def highlight_filename
93
+ dir = File.dirname(self).shorten_path
94
+ file = File.basename(self, ".#{NA.extension}")
95
+ "#{NA.theme[:dirname]}#{dir}/#{NA.theme[:filename]}#{file}{x}"
96
+ end
97
+
78
98
  ##
79
99
  ## Colorize @tags with ANSI escapes
80
100
  ##
@@ -88,7 +108,7 @@ class ::String
88
108
  ##
89
109
  ## @return [String] string with @tags highlighted
90
110
  ##
91
- def highlight_tags(color: '{m}', value: '{y}', parens: '{m}', last_color: '{xg}')
111
+ def highlight_tags(color: NA.theme[:tags], value: NA.theme[:value], parens: NA.theme[:value_parens], last_color: NA.theme[:action])
92
112
  tag_color = NA::Color.template(color)
93
113
  paren_color = NA::Color.template(parens)
94
114
  value_color = NA::Color.template(value)
@@ -106,9 +126,9 @@ class ::String
106
126
  ## @param last_color [String] Color to restore after
107
127
  ## highlight
108
128
  ##
109
- def highlight_search(regexes, color: '{y}', last_color: '{xg}')
129
+ def highlight_search(regexes, color: NA.theme[:search_highlight], last_color: NA.theme[:action])
110
130
  string = dup
111
- color = NA::Color.template(color)
131
+ color = NA::Color.template(color.dup)
112
132
  regexes.each do |rx|
113
133
  next if rx.nil?
114
134
 
data/lib/na/theme.rb ADDED
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NA
4
+ module Theme
5
+ class << self
6
+ def template_help
7
+ <<~EOHELP
8
+ Use {X} placeholders to apply colors. Available colors are:
9
+
10
+ w: white, k: black, g: green, l: blue,
11
+ y: yellow, c: cyan, m: magenta, r: red,
12
+ W: bgwhite, K: bgblack, G: bggreen, L: bgblue,
13
+ Y: bgyellow, C: bgcyan, M: bgmagenta, R: bgred,
14
+ d: dark, b: bold, u: underline, i: italic, x: reset
15
+
16
+ Multiple placeholders can be combined in a single {} pair.
17
+
18
+ You can also use {#RGB} and {#RRGGBB} to specify hex colors.
19
+ Add a b before the # to make the hex a background color ({b#fa0}).
20
+
21
+
22
+ EOHELP
23
+ end
24
+
25
+ def load_theme(template: {})
26
+ # Default colorization, can be overridden with full or partial template variable
27
+ default_template = {
28
+ parent: '{c}',
29
+ bracket: '{dc}',
30
+ parent_divider: '{xw}/',
31
+ action: '{bg}',
32
+ project: '{xbk}',
33
+ tags: '{m}',
34
+ value_parens: '{m}',
35
+ values: '{c}',
36
+ search_highlight: '{y}',
37
+ note: '{dw}',
38
+ dirname: '{dw}',
39
+ filename: '{xb}{#eccc87}',
40
+ prompt: '{m}',
41
+ success: '{bg}',
42
+ error: '{b}{#b61d2a}',
43
+ warning: '{by}',
44
+ debug: '{dw}',
45
+ templates: {
46
+ output: '%filename%parents| %action',
47
+ default: '%parent%action',
48
+ single_file: '%parent%action',
49
+ multi_file: '%filename%parent%action'
50
+ }
51
+ }
52
+
53
+ # Load custom theme
54
+ theme_file = NA.database_path(file: 'theme.yaml')
55
+ theme = if File.exist?(theme_file)
56
+ YAML.load(IO.read(theme_file)) || {}
57
+ else
58
+ {}
59
+ end
60
+ theme = default_template.merge(theme)
61
+
62
+ File.open(theme_file, 'w') do |f|
63
+ f.puts template_help.comment
64
+ f.puts YAML.dump(theme)
65
+ end
66
+
67
+ theme.merge(template)
68
+ end
69
+ end
70
+ end
71
+ end
data/lib/na/todo.rb CHANGED
@@ -51,8 +51,8 @@ module NA
51
51
  negated_tag = []
52
52
  projects = []
53
53
 
54
- NA.notify("{dw}Tags: #{settings[:tag]}", debug:true)
55
- NA.notify("{dw}Search: #{settings[:search]}", debug:true)
54
+ NA.notify("Tags: #{settings[:tag]}", debug:true)
55
+ NA.notify("Search: #{settings[:search]}", debug:true)
56
56
 
57
57
  settings[:tag]&.each do |t|
58
58
  unless t[:tag].nil?
data/lib/na/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Na
2
- VERSION = '1.2.37'
2
+ VERSION = '1.2.38'
3
3
  end
data/lib/na.rb CHANGED
@@ -13,6 +13,7 @@ require 'na/hash'
13
13
  require 'na/colors'
14
14
  require 'na/string'
15
15
  require 'na/array'
16
+ require 'na/theme'
16
17
  require 'na/todo'
17
18
  require 'na/actions'
18
19
  require 'na/project'
data/src/_README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  _If you're one of the rare people like me who find this useful, feel free to
10
10
  [buy me some coffee][donate]._
11
11
 
12
- The current version of `na` is <!--VER-->1.2.35<!--END VER-->.
12
+ The current version of `na` is <!--VER-->1.2.37<!--END VER-->.
13
13
 
14
14
  `na` ("next action") is a command line tool designed to make it easy to see what your next actions are for any project, right from the command line. It works with TaskPaper-formatted files (but any plain text format will do), looking for `@na` tags (or whatever you specify) in todo files in your current folder.
15
15
 
@@ -69,7 +69,7 @@ You can mark todos as complete, delete them, add and remove tags, change priorit
69
69
  ### Usage
70
70
 
71
71
  ```
72
- @cli(na help)
72
+ @cli(bundle exec bin/na help)
73
73
  ```
74
74
 
75
75
  #### Commands
@@ -87,13 +87,13 @@ Use the `--note` switch to add a note. If STDIN (piped) input is present when th
87
87
  Notes are not displayed by the `next/tagged/find` commands unless `--notes` is specified.
88
88
 
89
89
  ```
90
- @cli(na help add)
90
+ @cli(bundle exec bin/na help add)
91
91
  ```
92
92
 
93
93
  ##### edit
94
94
 
95
95
  ```
96
- @cli(na help edit)
96
+ @cli(bundle exec bin/na help edit)
97
97
  ```
98
98
 
99
99
  ##### find
@@ -103,13 +103,13 @@ Example: `na find cool feature idea`
103
103
  Unless `--exact` is specified, search is tokenized and combined with AND, so `na find cool feature idea` translates to `cool AND feature AND idea`, matching any string that contains all of the words. To make a token required and others optional, add a `+` before it (e.g. `cool +feature idea` is `(cool OR idea) AND feature`). Wildcards allowed (`*` and `?`), use `--regex` to interpret the search as a regular expression. Use `-v` to invert the results (display non-matching actions only).
104
104
 
105
105
  ```
106
- @cli(na help find)
106
+ @cli(bundle exec bin/na help find)
107
107
  ```
108
108
 
109
109
  ##### init, create
110
110
 
111
111
  ```
112
- @cli(na help init)
112
+ @cli(bundle exec bin/na help init)
113
113
  ```
114
114
 
115
115
  ##### next, show
@@ -123,7 +123,7 @@ Examples:
123
123
  To see all next actions across all known todos, use `na next "*"`. You can combine multiple arguments to see actions across multiple todos, e.g. `na next marked nvultra`.
124
124
 
125
125
  ```
126
- @cli(na help next)
126
+ @cli(bundle exec bin/na help next)
127
127
  ```
128
128
 
129
129
  ##### projects
@@ -131,7 +131,7 @@ To see all next actions across all known todos, use `na next "*"`. You can combi
131
131
  List all projects in a file. If arguments are provided, they're used to match a todo file from history, otherwise the todo file(s) in the current directory will be used.
132
132
 
133
133
  ```
134
- @cli(na help projects)
134
+ @cli(bundle exec bin/na help projects)
135
135
  ```
136
136
 
137
137
  ##### saved
@@ -146,7 +146,7 @@ Run `na saved` without an argument to list your saved searches.
146
146
  <!--JEKYLL{:.tip}-->
147
147
 
148
148
  ```
149
- @cli(na help saved)
149
+ @cli(bundle exec bin/na help saved)
150
150
  ```
151
151
 
152
152
  ##### tagged
@@ -160,7 +160,7 @@ You can also perform value comparisons on tags. A value in a TaskPaper tag is ad
160
160
  To perform a string comparison, you can use `*=` (contains), `^=` (starts with), `$=` (ends with), or `=` (matches). E.g. `na tagged "note*=video"`.
161
161
 
162
162
  ```
163
- @cli(na help show)
163
+ @cli(bundle exec bin/na help show)
164
164
  ```
165
165
 
166
166
  ##### todos
@@ -168,7 +168,7 @@ To perform a string comparison, you can use `*=` (contains), `^=` (starts with),
168
168
  List all known todo files from history.
169
169
 
170
170
  ```
171
- @cli(na help todos)
171
+ @cli(bundle exec bin/na help todos)
172
172
  ```
173
173
 
174
174
  ##### update
@@ -206,7 +206,7 @@ Notes are not displayed by the `next/tagged/find` commands unless `--notes` is s
206
206
  See the help output for a list of all available actions.
207
207
 
208
208
  ```
209
- @cli(na help update)
209
+ @cli(bundle exec bin/na help update)
210
210
  ```
211
211
 
212
212
  ##### changelog
@@ -214,7 +214,7 @@ See the help output for a list of all available actions.
214
214
  View recent changes with `na changelog` or `na changes`.
215
215
 
216
216
  ```
217
- @cli(na help changelog)
217
+ @cli(bundle exec bin/na help changelog)
218
218
  ```
219
219
 
220
220
  ##### complete
@@ -222,7 +222,7 @@ View recent changes with `na changelog` or `na changes`.
222
222
  Mark an action as complete, shortcut for `na update --finish`.
223
223
 
224
224
  ```
225
- @cli(na help complete)
225
+ @cli(bundle exec bin/na help complete)
226
226
  ```
227
227
 
228
228
  ##### archive
@@ -230,7 +230,27 @@ Mark an action as complete, shortcut for `na update --finish`.
230
230
  Mark an action as complete and move to archive, shortcut for `na update --archive`.
231
231
 
232
232
  ```
233
- @cli(na help archive)
233
+ @cli(bundle exec bin/na help archive)
234
+ ```
235
+
236
+ ##### tag
237
+
238
+ Add, remove, or modify tags.
239
+
240
+ Use `na tag TAGNAME --[search|tagged] SEARCH_STRING` to add a tag to matching action (use `--all` to apply to all matching actions). If you use `!TAGNAME` it will remove that tag (regardless of value). To change the value of an existing tag (or add it if it doesn't exist), use `~TAGNAME(NEW VALUE)`.
241
+
242
+ ```
243
+ @cli(bundle exec bin/na help tag)
244
+ ```
245
+
246
+ ##### undo
247
+
248
+ Undoes the last file change resulting from an add or update command. If no argument is given, it undoes whatever the last change in history was. If an argument is provided, it's used to match against the change history, finding a specific file to restore from backup.
249
+
250
+ Only the most recent change can be undone.
251
+
252
+ ```
253
+ @cli(bundle exec bin/na help undo)
234
254
  ```
235
255
 
236
256
  ### Configuration
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: na
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.37
4
+ version: 1.2.38
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-09-01 00:00:00.000000000 Z
11
+ date: 2023-09-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -238,6 +238,7 @@ files:
238
238
  - lib/na/project.rb
239
239
  - lib/na/prompt.rb
240
240
  - lib/na/string.rb
241
+ - lib/na/theme.rb
241
242
  - lib/na/todo.rb
242
243
  - lib/na/version.rb
243
244
  - na.gemspec