na 1.2.33 → 1.2.35

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d77588ce09a5a3cfc680b1866081dba5f56e75c8b51301cf0e1efee60c32f8f7
4
- data.tar.gz: 06d73082ad5371cda4760df90c929c74963d76843b7b979dde59e46fdb933389
3
+ metadata.gz: 1f8982f5720115576a78ab96a430d1d83f501bb1b477b2cff1a694648a186afc
4
+ data.tar.gz: 86dd520a3265473471b6d09fc05136dd07b53ded24ab2c82936074fa50595b67
5
5
  SHA512:
6
- metadata.gz: 07ee85d5f2f95ba2542ff15ca3b6c3736bd85d4a5b1bd9c76a7099296b3976d9b0e2478d6e2b8b84a824787c9fc05050b89153c316b7878c9743e3cd4cad3a8a
7
- data.tar.gz: 0af252e545f390a539279747f7d89e87b5efaceb82db3adfa57ce037cd6e4b139e18aa2d90340dec14d9eb90f78c3b8963c5f7e92d59258f1711ea1dbb76462c
6
+ metadata.gz: 7ea9bb861c82251c1ec52925d6527f0e4efa1eef0175d268b8a8bb307fafcc407dc888e5e5cd6d588e5e4a500a96ae64765106f23d9c4acda6e0667c71247d62
7
+ data.tar.gz: e5f603160851411f66dd50ae47a65369aaa1e51352cf2eb6313674d95d64db8914d3a870f816dcc4303fbd5b4e675f213020fa6a7fd93805af4692f04eb99aed
data/CHANGELOG.md CHANGED
@@ -1,3 +1,28 @@
1
+ ### 1.2.35
2
+
3
+ 2023-08-30 11:59
4
+
5
+ #### IMPROVED
6
+
7
+ - If a search string contains @tags and --exact or --regex isn't specified, the @tags will be extracted and passed as a --tagged search.
8
+ - Tag handling (including values and comparisons) in tags extracted from search strings
9
+ - `na complete --project PROJ` flag to move to a specific project
10
+ - `na restore --project PROJ` flag to move restored action to
11
+ - Exit gracefully if tagged command is run with invalid
12
+ - Display action affected when using update command
13
+
14
+ #### FIXED
15
+
16
+ - Escape search for tokens to allow parenthesis and other
17
+
18
+ ### 1.2.34
19
+
20
+ 2023-08-30 09:22
21
+
22
+ #### NEW
23
+
24
+ - `na restore SEARCH` to remove @done tag from result(s) (aliased as `unfinish`)
25
+
1
26
  ### 1.2.33
2
27
 
3
28
  2023-08-29 13:45
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- na (1.2.33)
4
+ na (1.2.35)
5
5
  chronic (~> 0.10, >= 0.10.2)
6
6
  gli (~> 2.21.0)
7
7
  mdless (~> 1.0, >= 1.0.32)
data/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 1.2.33
12
+ The current version of `na` is 1.2.35
13
13
  .
14
14
 
15
15
  `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.
@@ -77,7 +77,7 @@ SYNOPSIS
77
77
  na [global options] command [command options] [arguments...]
78
78
 
79
79
  VERSION
80
- 1.2.33
80
+ 1.2.35
81
81
 
82
82
  GLOBAL OPTIONS
83
83
  -a, --add - Add a next action (deprecated, for backwards compatibility)
@@ -104,7 +104,7 @@ COMMANDS
104
104
  complete, finish - Find and mark an action as @done
105
105
  completed, finished - Display completed actions
106
106
  edit - Edit an existing action
107
- find, grep - Find actions matching a search pattern
107
+ find, grep, search - Find actions matching a search pattern
108
108
  help - Shows a list of commands or help for one command
109
109
  init, create - Create a new todo file in the current directory
110
110
  initconfig - Initialize the config file using current global options
@@ -112,6 +112,7 @@ COMMANDS
112
112
  open - Open a todo file in the default editor
113
113
  projects - Show list of projects for a file
114
114
  prompt - Show or install prompt hooks for the current shell
115
+ restore, unfinish - Find and remove @done tag from an action
115
116
  saved - Execute a saved search
116
117
  tagged - Find actions matching a tag
117
118
  todos - Show list of known todo files
@@ -20,6 +20,10 @@ class App
20
20
  c.desc 'Add a @done tag to action and move to Archive'
21
21
  c.switch %i[a archive], negatable: false
22
22
 
23
+ c.desc 'Move action to specific project'
24
+ c.arg_name 'PROJECT'
25
+ c.flag %i[to project proj]
26
+
23
27
  c.desc 'Specify the file to search for the task'
24
28
  c.arg_name 'PATH'
25
29
  c.flag %i[file]
@@ -44,7 +48,7 @@ class App
44
48
  c.action do |global, options, args|
45
49
  options[:finish] = true
46
50
  options[:f] = true
47
- options[:project] = 'Archive' if options[:archive]
51
+ options[:project] = 'Archive' if options[:archive] && !options[:project]
48
52
 
49
53
  cmd = commands[:update]
50
54
  action = cmd.send(:get_action, nil)
data/bin/commands/find.rb CHANGED
@@ -7,7 +7,7 @@ class App
7
7
  (partial matches allowed). Add a + before a token to make it required, e.g. `na find +feature +maybe`,
8
8
  add a - or ! to ignore matches containing that token.'
9
9
  arg_name 'PATTERN'
10
- command %i[find grep] do |c|
10
+ command %i[find grep search] do |c|
11
11
  c.example 'na find feature idea swift', desc: 'Find all actions containing feature, idea, and swift'
12
12
  c.example 'na find feature idea -swift', desc: 'Find all actions containing feature and idea but NOT swift'
13
13
  c.example 'na find -x feature idea', desc: 'Find all actions containing the exact text "feature idea"'
@@ -64,17 +64,33 @@ class App
64
64
  NA.save_search(title, "#{NA.command_line.join(' ').sub(/ --save[= ]*\S+/, '').split(' ').map { |t| %("#{t}") }.join(' ')}")
65
65
  end
66
66
 
67
-
68
67
  depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
69
68
  3
70
69
  else
71
70
  options[:depth].nil? ? global_options[:depth].to_i : options[:depth].to_i
72
71
  end
73
72
 
73
+ if options[:exact] || options[:regex]
74
+ search = args.join(' ')
75
+ else
76
+ search = args.join(' ').gsub(/(?<=\A|[ ,])(?<req>[+\-!])?@(?<tag>[^ *=<>$\^,@(]+)(?:\((?<value>.*?)\)| *(?<op>[=<>]{1,2}|[*$\^]=) *(?<val>.*?(?=\Z|[,@])))?/) do |arg|
77
+ m = Regexp.last_match
78
+ string = if m['value']
79
+ "#{m['req']}#{m['tag']}=#{m['value']}"
80
+ else
81
+ m[0]
82
+ end
83
+ options[:tagged] << string.sub(/@/, '')
84
+ ''
85
+ end
86
+ end
87
+
88
+ search = search.gsub(/ +/, ' ').strip
89
+
74
90
  all_req = options[:tagged].join(' ') !~ /[+!\-]/ && !options[:or]
75
91
  tags = []
76
92
  options[:tagged].join(',').split(/ *, */).each do |arg|
77
- m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
93
+ m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?) *(?:(?<op>[=<>]{1,2}|[*$\^]=) *(?<val>.*?))?$/)
78
94
 
79
95
  tags.push({
80
96
  tag: m['tag'].wildcard_to_rx,
@@ -85,19 +101,23 @@ class App
85
101
  })
86
102
  end
87
103
 
104
+ search_for_done = false
105
+ tags.each { |tag| search_for_done = true if tag[:tag] =~ /done/ }
106
+ options[:done] = true if search_for_done
107
+
88
108
  tokens = nil
89
109
  if options[:exact]
90
- tokens = args.join(' ')
110
+ tokens = search
91
111
  elsif options[:regex]
92
- tokens = Regexp.new(args.join(' '), Regexp::IGNORECASE)
112
+ tokens = Regexp.new(search, Regexp::IGNORECASE)
93
113
  else
94
114
  tokens = []
95
- all_req = args.join(' ') !~ /[+!\-]/ && !options[:or]
115
+ all_req = search !~ /[+!\-]/ && !options[:or]
96
116
 
97
- args.join(' ').split(/ /).each do |arg|
117
+ search.split(/ /).each do |arg|
98
118
  m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
99
119
  tokens.push({
100
- token: m['tok'],
120
+ token: Regexp.escape(m['tok']),
101
121
  required: all_req || (!m['req'].nil? && m['req'] == '+'),
102
122
  negate: !m['req'].nil? && m['req'] =~ /[!\-]/
103
123
  })
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ class App
4
+ extend GLI::App
5
+ desc 'Find and remove @done tag from an action'
6
+ arg_name 'PATTERN'
7
+ command %i[restore unfinish] do |c|
8
+ c.example 'na restore "An existing task"',
9
+ desc: 'Find "An existing task" and remove @done'
10
+ c.example 'na unfinish "An existing task"',
11
+ desc: 'Alias for restore'
12
+
13
+ c.desc 'Prompt for additional notes. Input will be appended to any existing note.
14
+ If STDIN input (piped) is detected, it will be used as a note.'
15
+ c.switch %i[n note], negatable: false
16
+
17
+ c.desc 'Overwrite note instead of appending'
18
+ c.switch %i[o overwrite], negatable: false
19
+
20
+ c.desc 'Move action to specific project'
21
+ c.arg_name 'PROJECT'
22
+ c.flag %i[to project proj]
23
+
24
+ c.desc 'Specify the file to search for the task'
25
+ c.arg_name 'PATH'
26
+ c.flag %i[file]
27
+
28
+ c.desc 'Search for files X directories deep'
29
+ c.arg_name 'DEPTH'
30
+ c.flag %i[d depth], must_match: /^[1-9]$/, type: :integer, default_value: 1
31
+
32
+ c.desc 'Match actions containing tag. Allows value comparisons'
33
+ c.arg_name 'TAG'
34
+ c.flag %i[tagged], multiple: true
35
+
36
+ c.desc 'Act on all matches immediately (no menu)'
37
+ c.switch %i[all], negatable: false
38
+
39
+ c.desc 'Interpret search pattern as regular expression'
40
+ c.switch %i[e regex], negatable: false
41
+
42
+ c.desc 'Match pattern exactly'
43
+ c.switch %i[x exact], negatable: false
44
+
45
+ c.action do |global, options, args|
46
+ options[:remove] = ['done']
47
+ options[:done] = true
48
+ options[:finish] = false
49
+ options[:f] = false
50
+
51
+ cmd = commands[:update]
52
+ action = cmd.send(:get_action, nil)
53
+ action.call(global, options, args)
54
+ end
55
+ end
56
+ end
@@ -80,10 +80,11 @@ class App
80
80
 
81
81
  all_req = args.join(' ') !~ /[+!\-]/ && !options[:or]
82
82
  args.join(',').split(/ *, */).each do |arg|
83
- m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
83
+ m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?) *(?:(?<op>[=<>]{1,2}|[*$\^]=) *(?<val>.*?))?$/)
84
+ next if m.nil?
84
85
 
85
86
  tags.push({
86
- tag: m['tag'].wildcard_to_rx,
87
+ tag: m['tag'].sub(/^@/, '').wildcard_to_rx,
87
88
  comp: m['op'],
88
89
  value: m['val'],
89
90
  required: all_req || (!m['req'].nil? && m['req'] == '+'),
@@ -93,7 +94,8 @@ class App
93
94
 
94
95
  search_for_done = false
95
96
  tags.each { |tag| search_for_done = true if tag[:tag] =~ /done/ }
96
- tags.push({ tag: 'done', value: nil, negate: true}) unless search_for_done
97
+ tags.push({ tag: 'done', value: nil, negate: true}) unless search_for_done || options[:done]
98
+ options[:done] = true if search_for_done
97
99
 
98
100
  tokens = nil
99
101
  if options[:search]
@@ -129,6 +131,8 @@ class App
129
131
  end
130
132
  end
131
133
 
134
+ NA.notify('{br}No actions matched search', exit_code: 1) if tags.empty? && tokens.empty?
135
+
132
136
  files, actions, = NA.parse_actions(depth: depth,
133
137
  done: options[:done],
134
138
  query: todo,
data/lib/na/action.rb CHANGED
@@ -37,7 +37,18 @@ module NA
37
37
  EOINSPECT
38
38
  end
39
39
 
40
+ ##
41
+ ## Pretty print an action
42
+ ##
43
+ ## @param extension [String] The file extension
44
+ ## @param template [Hash] The template to use for
45
+ ## colorization
46
+ ## @param regexes [Array] The regexes to
47
+ ## highlight (searches)
48
+ ## @param notes [Boolean] Include notes
49
+ ##
40
50
  def pretty(extension: 'taskpaper', template: {}, regexes: [], notes: false)
51
+ # Default colorization, can be overridden with full or partial template variable
41
52
  default_template = {
42
53
  file: '{xbk}',
43
54
  parent: '{c}',
@@ -51,32 +62,38 @@ module NA
51
62
  note: '{dw}'
52
63
  }
53
64
  template = default_template.merge(template)
54
-
65
+ # Create the hierarchical parent string
55
66
  parents = @parent.map do |par|
56
67
  NA::Color.template("#{template[:parent]}#{par}")
57
68
  end.join(NA::Color.template(template[:parent_divider]))
58
69
  parents = "{dc}[{x}#{parents}{dc}]{x} "
59
70
 
71
+ # Create the project string
60
72
  project = NA::Color.template("#{template[:project]}#{@project}{x} ")
61
73
 
74
+ # Create the source filename string, substituting ~ for HOME and removing extension
62
75
  file = @file.sub(%r{^\./}, '').sub(/#{ENV['HOME']}/, '~')
63
76
  file = file.sub(/\.#{extension}$/, '')
77
+ # colorize the basename
64
78
  file = file.sub(/#{File.basename(@file, ".#{extension}")}$/, "{dw}#{File.basename(@file, ".#{extension}")}{x}")
65
79
  file_tpl = "#{template[:file]}#{file} {x}"
66
80
  filename = NA::Color.template(file_tpl)
67
81
 
82
+ # Add notes if needed
68
83
  note = if notes && @note.count.positive?
69
84
  NA::Color.template("\n#{@note.map { |l| " #{template[:note]}• #{l}{x}" }.join("\n")}")
70
85
  else
71
86
  ''
72
87
  end
73
88
 
89
+ # colorize the action and highlight tags
74
90
  action = NA::Color.template("#{template[:action]}#{@action.sub(/ @#{NA.na_tag}\b/, '')}{x}")
75
91
  action = action.highlight_tags(color: template[:tags],
76
92
  parens: template[:value_parens],
77
93
  value: template[:values],
78
94
  last_color: template[:action])
79
95
 
96
+ # Replace variables in template string and output colorized
80
97
  NA::Color.template(template[:output].gsub(/%filename/, filename)
81
98
  .gsub(/%project/, project)
82
99
  .gsub(/%parents?/, parents)
@@ -420,7 +420,7 @@ module NA
420
420
  projects.select { |proj| proj.project =~ /^#{action.parent.join(':')}$/ }.first
421
421
  end
422
422
 
423
- NA.notify("{r}Error parsing project #{target_proj}", exit_code: 1) if target_proj.nil?
423
+ NA.notify("{r}Error parsing project #{target}", exit_code: 1) if target_proj.nil?
424
424
 
425
425
  indent = "\t" * target_proj.indent
426
426
  note = note.split("\n") unless note.is_a?(Array)
@@ -450,6 +450,8 @@ module NA
450
450
  end
451
451
 
452
452
  contents.insert(target_line, "#{indent}\t- #{action.action}#{note}")
453
+
454
+ notify(action.pretty)
453
455
  else
454
456
  _, actions = find_actions(target, search, tagged, done: done, all: all)
455
457
 
@@ -502,7 +504,10 @@ module NA
502
504
  target_line = target_proj.line + 1
503
505
  end
504
506
 
507
+
505
508
  contents.insert(target_line, "#{indent}\t- #{action.action}#{note}")
509
+
510
+ notify(action.pretty)
506
511
  end
507
512
  end
508
513
 
@@ -692,6 +697,9 @@ module NA
692
697
  negated_tag = []
693
698
  projects = []
694
699
 
700
+ notify("{dw}Tags: #{tag}", debug:true)
701
+ notify("{dw}Search: #{search}", debug:true)
702
+
695
703
  tag&.each do |t|
696
704
  unless t[:tag].nil?
697
705
  if negate
@@ -706,7 +714,7 @@ module NA
706
714
  end
707
715
  end
708
716
 
709
- unless search.nil?
717
+ unless search.nil? || search.empty?
710
718
  if regex || search.is_a?(String)
711
719
  if negate
712
720
  negated.push(search)
data/lib/na/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Na
2
- VERSION = '1.2.33'
2
+ VERSION = '1.2.35'
3
3
  end
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.32<!--END VER-->.
12
+ The current version of `na` is <!--VER-->1.2.34<!--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
 
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.33
4
+ version: 1.2.35
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-08-29 00:00:00.000000000 Z
11
+ date: 2023-08-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -218,6 +218,7 @@ files:
218
218
  - bin/commands/open.rb
219
219
  - bin/commands/projects.rb
220
220
  - bin/commands/prompt.rb
221
+ - bin/commands/restore.rb
221
222
  - bin/commands/saved.rb
222
223
  - bin/commands/tagged.rb
223
224
  - bin/commands/todos.rb