na 1.1.11 → 1.1.12
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 +19 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/bin/na +44 -8
- data/lib/na/action.rb +103 -1
- data/lib/na/hash.rb +7 -0
- data/lib/na/next_action.rb +46 -19
- data/lib/na/string.rb +4 -0
- data/lib/na/version.rb +1 -1
- data/lib/na.rb +8 -8
- data/src/README.md +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: da1e1113960a99932feda46ee18cd7a8b9e8d309f479c9e3a6ec323d97db686a
|
4
|
+
data.tar.gz: 871fcae47831f04efcff94b7034abb70d32cb47b2f504247b94705ad015c191f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c8e8c92c9d3a3ffa4a00fe8fcdd8ace94681e19f9ac53a3d8b988bcf4bd69d3b032765b4a870e3d9f660cd58b18783e97e8333cdb252164cb60f0062495210b5
|
7
|
+
data.tar.gz: de5596e85883b893afbe2ca43833fe60f82fd96ab12e1f2506d84d13a90644d5d403b748b6598c9fb4c4d4db3998bdd0e560a1c071f4f7c2946b5f75dd45c728
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,22 @@
|
|
1
|
+
### 1.1.12
|
2
|
+
|
3
|
+
2022-10-06 05:42
|
4
|
+
|
5
|
+
#### NEW
|
6
|
+
|
7
|
+
- `na add -d X` to allow adding new actions to todo files in subdirectories
|
8
|
+
- You can now perform <>= queries on tag values (`na tagged "priority>=3"`)
|
9
|
+
- You can now perform string matches on tag values (`na tagged "note*=markdown"`)
|
10
|
+
- You can use `--project X` to display only actions within a specific project. Specify subprojects with a path, e.g. `na/bugs`. Partial matches allowed, works with `next`, `find`, and `tagged`
|
11
|
+
- Find and tagged recognize * and ? as wildcards
|
12
|
+
- --regex flag for find command
|
13
|
+
- --invert command (like grep -v) for find
|
14
|
+
- -v/--invert for tagged command
|
15
|
+
|
16
|
+
#### IMPROVED
|
17
|
+
|
18
|
+
- Require value 1-9 for --depth option
|
19
|
+
|
1
20
|
### 1.1.11
|
2
21
|
|
3
22
|
2022-10-05 08:56
|
data/Gemfile.lock
CHANGED
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.1.
|
12
|
+
The current version of `na` is 1.1.12
|
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.
|
data/bin/na
CHANGED
@@ -20,7 +20,7 @@ class App
|
|
20
20
|
|
21
21
|
desc 'File extension to consider a todo file'
|
22
22
|
default_value 'taskpaper'
|
23
|
-
arg_name '
|
23
|
+
arg_name 'EXT'
|
24
24
|
flag :ext
|
25
25
|
|
26
26
|
desc 'Tag to consider a next action'
|
@@ -48,7 +48,7 @@ class App
|
|
48
48
|
desc 'Recurse to depth'
|
49
49
|
arg_name 'DEPTH'
|
50
50
|
default_value 1
|
51
|
-
flag %i[d depth], type: :integer, must_match:
|
51
|
+
flag %i[d depth], type: :integer, must_match: /^[1-9]$/
|
52
52
|
|
53
53
|
desc 'Display verbose output'
|
54
54
|
switch %i[debug]
|
@@ -59,13 +59,19 @@ class App
|
|
59
59
|
c.example 'na next', desc: 'display the next actions from any todo files in the current directory'
|
60
60
|
c.example 'na next -d 3', desc: 'display the next actions from the current directory, traversing 3 levels deep'
|
61
61
|
c.example 'na next marked', desc: 'display next actions for a project you visited in the past'
|
62
|
+
|
62
63
|
c.desc 'Recurse to depth'
|
63
64
|
c.arg_name 'DEPTH'
|
64
|
-
c.flag %i[d depth], type: :integer, must_match:
|
65
|
+
c.flag %i[d depth], type: :integer, must_match: /^[1-9]$/
|
65
66
|
|
66
67
|
c.desc 'Alternate tag to search for'
|
68
|
+
c.arg_name 'TAG'
|
67
69
|
c.flag %i[t tag]
|
68
70
|
|
71
|
+
c.desc 'Show actions from a specific project'
|
72
|
+
c.arg_name 'PROJECT[/SUBPROJECT]'
|
73
|
+
c.flag %i[proj project]
|
74
|
+
|
69
75
|
c.action do |global_options, options, args|
|
70
76
|
if global_options[:add]
|
71
77
|
cmd = ['add']
|
@@ -100,6 +106,7 @@ class App
|
|
100
106
|
files, actions = NA.parse_actions(depth: depth,
|
101
107
|
query: tokens,
|
102
108
|
tag: tag,
|
109
|
+
project: options[:project],
|
103
110
|
require_na: require_na)
|
104
111
|
|
105
112
|
NA.output_actions(actions, depth, files: files)
|
@@ -122,9 +129,11 @@ class App
|
|
122
129
|
c.switch %i[n note], negatable: false
|
123
130
|
|
124
131
|
c.desc 'Add a priority level 1-5'
|
132
|
+
c.arg_name 'PRIO'
|
125
133
|
c.flag %i[p priority], must_match: /[1-5]/, type: :integer, default_value: 0
|
126
134
|
|
127
135
|
c.desc 'Add action to specific project'
|
136
|
+
c.arg_name 'PROJECT'
|
128
137
|
c.default_value 'Inbox'
|
129
138
|
c.flag %i[to]
|
130
139
|
|
@@ -139,6 +148,10 @@ class App
|
|
139
148
|
c.arg_name 'PATH'
|
140
149
|
c.flag %i[f file]
|
141
150
|
|
151
|
+
c.desc 'Search for files X directories deep'
|
152
|
+
c.arg_name 'DEPTH'
|
153
|
+
c.flag %i[d depth], must_match: /^[1-9]$/, type: :integer, default_value: 1
|
154
|
+
|
142
155
|
c.action do |_global_options, options, args|
|
143
156
|
reader = TTY::Reader.new
|
144
157
|
action = if args.count.positive?
|
@@ -195,7 +208,7 @@ class App
|
|
195
208
|
end
|
196
209
|
end
|
197
210
|
else
|
198
|
-
files = NA.find_files(depth:
|
211
|
+
files = NA.find_files(depth: options[:depth])
|
199
212
|
if files.count.zero?
|
200
213
|
print NA::Color.template('{by}No todo file found, create one? {w}(y/{g}N{w}){x} ')
|
201
214
|
res = reader.read_char
|
@@ -221,19 +234,28 @@ class App
|
|
221
234
|
long_desc 'Search tokens are separated by spaces. Actions matching any token in the pattern will be shown
|
222
235
|
(partial matches allowed). Add a + before a token to make it required, e.g. `na find +feature +maybe`'
|
223
236
|
arg_name 'PATTERN'
|
224
|
-
command %i[find] do |c|
|
237
|
+
command %i[find grep] do |c|
|
225
238
|
c.example 'na find feature +idea +swift', desc: 'Find all actions containing feature, idea, and swift'
|
226
239
|
c.example 'na find -x feature idea', desc: 'Find all actions containing the exact text "feature idea"'
|
227
240
|
c.example 'na find -d 3 swift obj-c', desc: 'Find all actions 3 directories deep containing either swift or obj-c'
|
228
241
|
|
242
|
+
c.desc 'Interpret search pattern as regular expression'
|
243
|
+
c.switch %i[e regex], negatable: false
|
244
|
+
|
229
245
|
c.desc 'Match pattern exactly'
|
230
246
|
c.switch %i[x exact], negatable: false
|
231
247
|
|
232
248
|
c.desc 'Recurse to depth'
|
233
249
|
c.arg_name 'DEPTH'
|
234
|
-
c.default_value 1
|
235
250
|
c.flag %i[d depth], type: :integer, must_match: /^\d+$/
|
236
251
|
|
252
|
+
c.desc 'Show actions from a specific project'
|
253
|
+
c.arg_name 'PROJECT[/SUBPROJECT]'
|
254
|
+
c.flag %i[proj project]
|
255
|
+
|
256
|
+
c.desc 'Show actions not matching search pattern'
|
257
|
+
c.switch %i[v invert], negatable: false
|
258
|
+
|
237
259
|
c.action do |global_options, options, args|
|
238
260
|
depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
|
239
261
|
3
|
@@ -243,6 +265,8 @@ class App
|
|
243
265
|
tokens = nil
|
244
266
|
if options[:exact]
|
245
267
|
tokens = args.join(' ')
|
268
|
+
elsif options[:regex]
|
269
|
+
tokens = Regexp.new(args.join(' '), Regexp::IGNORECASE)
|
246
270
|
else
|
247
271
|
tokens = []
|
248
272
|
args.each do |arg|
|
@@ -257,6 +281,9 @@ class App
|
|
257
281
|
|
258
282
|
files, actions = NA.parse_actions(depth: depth,
|
259
283
|
search: tokens,
|
284
|
+
negate: options[:invert],
|
285
|
+
regex: options[:regex],
|
286
|
+
project: options[:project],
|
260
287
|
require_na: false)
|
261
288
|
NA.output_actions(actions, depth, files: files)
|
262
289
|
end
|
@@ -277,6 +304,13 @@ class App
|
|
277
304
|
c.default_value 1
|
278
305
|
c.flag %i[d depth], type: :integer, must_match: /^\d+$/
|
279
306
|
|
307
|
+
c.desc 'Show actions from a specific project'
|
308
|
+
c.arg_name 'PROJECT[/SUBPROJECT]'
|
309
|
+
c.flag %i[proj project]
|
310
|
+
|
311
|
+
c.desc 'Show actions not matching tags'
|
312
|
+
c.switch %i[v invert], negatable: false
|
313
|
+
|
280
314
|
c.action do |global_options, options, args|
|
281
315
|
depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
|
282
316
|
3
|
@@ -285,9 +319,9 @@ class App
|
|
285
319
|
end
|
286
320
|
|
287
321
|
tags = []
|
288
|
-
args.each do |arg|
|
322
|
+
args.join(',').split(/ *, */).each do |arg|
|
289
323
|
# TODO: <> comparisons do nothing right now
|
290
|
-
m = arg.match(/^(?<req>[+\-!])?(?<tag>[^
|
324
|
+
m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>*$\^]+)(?:(?<op>[=<>*$\^]+)(?<val>\S+))?$/)
|
291
325
|
tags.push({
|
292
326
|
tag: m['tag'],
|
293
327
|
comp: m['op'],
|
@@ -299,6 +333,8 @@ class App
|
|
299
333
|
|
300
334
|
files, actions = NA.parse_actions(depth: depth,
|
301
335
|
tag: tags,
|
336
|
+
negate: options[:invert],
|
337
|
+
project: options[:project],
|
302
338
|
require_na: false)
|
303
339
|
NA.output_actions(actions, depth, files: files)
|
304
340
|
end
|
data/lib/na/action.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module NA
|
4
4
|
class Action < Hash
|
5
|
-
attr_reader :file, :project, :parent, :action
|
5
|
+
attr_reader :file, :project, :parent, :action, :tags
|
6
6
|
|
7
7
|
def initialize(file, project, parent, action)
|
8
8
|
super()
|
@@ -11,6 +11,7 @@ module NA
|
|
11
11
|
@project = project
|
12
12
|
@parent = parent
|
13
13
|
@action = action
|
14
|
+
@tags = scan_tags
|
14
15
|
end
|
15
16
|
|
16
17
|
def to_s
|
@@ -68,5 +69,106 @@ module NA
|
|
68
69
|
.gsub(/%parents?/, parents)
|
69
70
|
.gsub(/%action/, action))
|
70
71
|
end
|
72
|
+
|
73
|
+
def tags_match?(any: [], all: [], none: [])
|
74
|
+
tag_matches_any(any) && tag_matches_all(all) && tag_matches_none(none)
|
75
|
+
end
|
76
|
+
|
77
|
+
def search_match?(any: [], all: [], none: [])
|
78
|
+
search_matches_any(any) && search_matches_all(all) && search_matches_none(none)
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def search_matches_none(regexes)
|
84
|
+
regexes.each do |rx|
|
85
|
+
return false if @action.match(Regexp.new(rx, Regexp::IGNORECASE))
|
86
|
+
end
|
87
|
+
true
|
88
|
+
end
|
89
|
+
|
90
|
+
def search_matches_any(regexes)
|
91
|
+
return true if regexes.empty?
|
92
|
+
|
93
|
+
regexes.each do |rx|
|
94
|
+
return true if @action.match(Regexp.new(rx, Regexp::IGNORECASE))
|
95
|
+
end
|
96
|
+
false
|
97
|
+
end
|
98
|
+
|
99
|
+
def search_matches_all(regexes)
|
100
|
+
regexes.each do |rx|
|
101
|
+
return false unless @action.match(Regexp.new(rx, Regexp::IGNORECASE))
|
102
|
+
end
|
103
|
+
true
|
104
|
+
end
|
105
|
+
|
106
|
+
def tag_matches_none(tags)
|
107
|
+
tags.each do |tag|
|
108
|
+
return false if compare_tag(tag)
|
109
|
+
end
|
110
|
+
true
|
111
|
+
end
|
112
|
+
|
113
|
+
def tag_matches_any(tags)
|
114
|
+
return true if tags.empty?
|
115
|
+
|
116
|
+
tags.each do |tag|
|
117
|
+
return true if compare_tag(tag)
|
118
|
+
end
|
119
|
+
false
|
120
|
+
end
|
121
|
+
|
122
|
+
def tag_matches_all(tags)
|
123
|
+
tags.each do |tag|
|
124
|
+
return false unless compare_tag(tag)
|
125
|
+
end
|
126
|
+
true
|
127
|
+
end
|
128
|
+
|
129
|
+
def compare_tag(tag)
|
130
|
+
return false unless @tags.key?(tag[:tag])
|
131
|
+
|
132
|
+
return true if tag[:comp].nil?
|
133
|
+
|
134
|
+
tag_val = @tags[tag[:tag]]
|
135
|
+
val = tag[:value]
|
136
|
+
|
137
|
+
return false if tag_val.nil?
|
138
|
+
|
139
|
+
return case tag[:comp]
|
140
|
+
when /^>$/
|
141
|
+
tag_val.to_f > val.to_f
|
142
|
+
when /^<$/
|
143
|
+
tag_val.to_f < val.to_f
|
144
|
+
when /^<=$/
|
145
|
+
tag_val.to_f <= val.to_f
|
146
|
+
when /^>=$/
|
147
|
+
tag_val.to_f >= val.to_f
|
148
|
+
when /^==?$/
|
149
|
+
tag_val =~ /^#{val.wildcard_to_rx}$/
|
150
|
+
when /^\$=$/
|
151
|
+
tag_val =~ /#{val.wildcard_to_rx}$/i
|
152
|
+
when /^\*=$/
|
153
|
+
tag_val =~ /#{val.wildcard_to_rx}/i
|
154
|
+
when /^\^=$/
|
155
|
+
tag_val =~ /^#{val.wildcard_to_rx}/
|
156
|
+
else
|
157
|
+
false
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def scan_tags
|
162
|
+
tags = {}
|
163
|
+
rx = /(?<= |^)@(?<tag>\S+?)(?:\((?<value>.*?)\))?(?= |$)/
|
164
|
+
all_tags = []
|
165
|
+
@action.scan(rx) { all_tags << Regexp.last_match }
|
166
|
+
all_tags.each do |m|
|
167
|
+
tag = m.named_captures.symbolize_keys
|
168
|
+
tags[tag[:tag]] = tag[:value]
|
169
|
+
end
|
170
|
+
|
171
|
+
tags
|
172
|
+
end
|
71
173
|
end
|
72
174
|
end
|
data/lib/na/hash.rb
ADDED
data/lib/na/next_action.rb
CHANGED
@@ -103,34 +103,50 @@ module NA
|
|
103
103
|
puts actions.map { |action| action.pretty(template: { output: template }) }
|
104
104
|
end
|
105
105
|
|
106
|
-
def parse_actions(depth: 1, query: nil, tag: nil, search: nil, require_na: true)
|
106
|
+
def parse_actions(depth: 1, query: nil, tag: nil, search: nil, negate: false, regex: false, project: nil, require_na: true)
|
107
107
|
actions = []
|
108
108
|
required = []
|
109
109
|
optional = []
|
110
110
|
negated = []
|
111
|
+
required_tag = []
|
112
|
+
optional_tag = []
|
113
|
+
negated_tag = []
|
111
114
|
|
112
115
|
tag&.each do |t|
|
113
116
|
unless t[:tag].nil?
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
117
|
+
if negate
|
118
|
+
optional_tag.push(t) if t[:negate]
|
119
|
+
required_tag.push(t) if t[:required] && t[:negate]
|
120
|
+
negated_tag.push(t) unless t[:negate]
|
121
|
+
else
|
122
|
+
optional_tag.push(t) unless t[:negate]
|
123
|
+
required_tag.push(t) if t[:required] && !t[:negate]
|
124
|
+
negated_tag.push(t) if t[:negate]
|
125
|
+
end
|
120
126
|
end
|
121
127
|
end
|
122
128
|
|
123
129
|
unless search.nil?
|
124
|
-
if search.is_a?(String)
|
125
|
-
|
126
|
-
|
130
|
+
if regex || search.is_a?(String)
|
131
|
+
if negate
|
132
|
+
negated.push(search)
|
133
|
+
else
|
134
|
+
optional.push(search)
|
135
|
+
required.push(search)
|
136
|
+
end
|
127
137
|
else
|
128
138
|
search.each do |t|
|
129
|
-
new_rx = t[:token].to_s
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
139
|
+
new_rx = t[:token].to_s.wildcard_to_rx
|
140
|
+
|
141
|
+
if negate
|
142
|
+
optional.push(new_rx) if t[:negate]
|
143
|
+
required.push(new_rx) if t[:required] && t[:negate]
|
144
|
+
negated.push(new_rx) unless t[:negate]
|
145
|
+
else
|
146
|
+
optional.push(new_rx) unless t[:negate]
|
147
|
+
required.push(new_rx) if t[:required] && !t[:negate]
|
148
|
+
negated.push(new_rx) if t[:negate]
|
149
|
+
end
|
134
150
|
end
|
135
151
|
end
|
136
152
|
end
|
@@ -166,13 +182,24 @@ module NA
|
|
166
182
|
elsif line =~ /^[ \t]*- / && line !~ / @done/
|
167
183
|
next if require_na && line !~ /@#{NA.na_tag}\b/
|
168
184
|
|
169
|
-
|
170
|
-
|
185
|
+
action = line.sub(/^[ \t]*- /, '').sub(/ @#{NA.na_tag}\b/, '')
|
186
|
+
new_action = NA::Action.new(file, File.basename(file, ".#{NA.extension}"), parent.dup, action)
|
187
|
+
|
188
|
+
has_search = !optional.empty? || !required.empty? || !negated.empty?
|
189
|
+
next if has_search && !new_action.search_match?(any: optional,
|
190
|
+
all: required,
|
191
|
+
none: negated)
|
171
192
|
|
193
|
+
if project
|
194
|
+
rx = project.split(%r{[/:]}).join('.*?/.*?')
|
195
|
+
next unless parent.join('/') =~ Regexp.new(rx, Regexp::IGNORECASE)
|
172
196
|
end
|
173
197
|
|
174
|
-
|
175
|
-
|
198
|
+
has_tag = !optional_tag.empty? || !required_tag.empty? || !negated_tag.empty?
|
199
|
+
next if has_tag && !new_action.tags_match?(any: optional_tag,
|
200
|
+
all: required_tag,
|
201
|
+
none: negated_tag)
|
202
|
+
|
176
203
|
actions.push(new_action)
|
177
204
|
end
|
178
205
|
end
|
data/lib/na/string.rb
CHANGED
data/lib/na/version.rb
CHANGED
data/lib/na.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
# you just need to require this one file in your bin file
|
3
|
+
require 'na/version'
|
5
4
|
require 'fileutils'
|
6
5
|
require 'shellwords'
|
7
6
|
require 'tty-screen'
|
8
7
|
require 'tty-reader'
|
9
8
|
require 'tty-which'
|
10
|
-
require 'na/
|
11
|
-
require 'na/
|
12
|
-
require 'na/
|
13
|
-
require 'na/
|
14
|
-
require 'na/
|
9
|
+
require 'na/hash'
|
10
|
+
require 'na/colors'
|
11
|
+
require 'na/string'
|
12
|
+
require 'na/action'
|
13
|
+
require 'na/next_action'
|
14
|
+
require 'na/prompt'
|
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.1.
|
12
|
+
The current version of `na` is <!--VER-->1.1.11<!--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.1.
|
4
|
+
version: 1.1.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brett Terpstra
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-10-
|
11
|
+
date: 2022-10-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -169,6 +169,7 @@ files:
|
|
169
169
|
- lib/na.rb
|
170
170
|
- lib/na/action.rb
|
171
171
|
- lib/na/colors.rb
|
172
|
+
- lib/na/hash.rb
|
172
173
|
- lib/na/next_action.rb
|
173
174
|
- lib/na/prompt.rb
|
174
175
|
- lib/na/string.rb
|