na 1.1.12 → 1.1.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/Gemfile.lock +1 -1
- data/README.md +7 -3
- data/bin/na +5 -3
- data/lib/na/action.rb +2 -2
- data/lib/na/next_action.rb +70 -57
- data/lib/na/string.rb +54 -19
- data/lib/na/version.rb +1 -1
- data/src/README.md +7 -3
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2324b729bcc5e20eab3e063427fa5c6480274ea844fe72117e7f25c7e9801764
|
4
|
+
data.tar.gz: 68d00ee26f320e14afd0e6a72a072b2cf30c82beaaf7818962b90e7fa0884914
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b6b7fd0066ba41b9fa184ab3231cb8d7b68e68526bc380f21f2b12048de1c4a4eae24d2da8e47abe95c2389536e90ee32fa35ebdf786570745b8153c72d65e4a
|
7
|
+
data.tar.gz: 320264ba4f636f8a68804f7767bd09a4c2105adf684b603514d933003f06f72e2cebb978476a659c2a7ee716fbdaadf7f9fd507c3985ec2dc870162da2751414
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
+
### 1.1.14
|
2
|
+
|
3
|
+
2022-10-06 12:30
|
4
|
+
|
5
|
+
#### IMPROVED
|
6
|
+
|
7
|
+
- Code cleanup
|
8
|
+
- Highlight search terms in results
|
9
|
+
|
10
|
+
#### FIXED
|
11
|
+
|
12
|
+
- Multiple search terms overriding each other
|
13
|
+
|
14
|
+
### 1.1.13
|
15
|
+
|
16
|
+
2022-10-06 06:28
|
17
|
+
|
18
|
+
#### IMPROVED
|
19
|
+
|
20
|
+
- When specifying arguments to `next`, allow paths separated by / to do more exact matching
|
21
|
+
|
1
22
|
### 1.1.12
|
2
23
|
|
3
24
|
2022-10-06 05:42
|
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.14
|
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.
|
@@ -37,7 +37,7 @@ You can list next actions in files in the current directory by typing `na`. By d
|
|
37
37
|
|
38
38
|
#### Easy matching
|
39
39
|
|
40
|
-
`na` features intelligent project matching. Every time it locates a todo file, it adds the project to the database. Once a project is recorded, you can list its actions by using any portion of the parent directories or file names. If your project is in `~/Sites/dev/markedapp`, you could quickly list its next actions by typing `na next dev
|
40
|
+
`na` features intelligent project matching. Every time it locates a todo file, it adds the project to the database. Once a project is recorded, you can list its actions by using any portion of the parent directories or file names. If your project is in `~/Sites/dev/markedapp`, you could quickly list its next actions by typing `na next dev/mark`. Creat paths by separating with / or :, separate multiple queries with spaces. na will always look for the shortest match for a path.
|
41
41
|
|
42
42
|
#### Recursion
|
43
43
|
|
@@ -152,6 +152,8 @@ EXAMPLES
|
|
152
152
|
|
153
153
|
Example: `na find cool feature idea`
|
154
154
|
|
155
|
+
Unless `--exact` is specified, search is tokenized and combined with OR, so `na find cool feature idea` translates to `cool OR feature OR idea`, matching any string that contains any of the words. To make a token required, 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).
|
156
|
+
|
155
157
|
```
|
156
158
|
NAME
|
157
159
|
find - Find actions matching a search pattern
|
@@ -229,7 +231,9 @@ EXAMPLES
|
|
229
231
|
|
230
232
|
##### tagged
|
231
233
|
|
232
|
-
Example: `na tagged feature +maybe
|
234
|
+
Example: `na tagged feature +maybe`.
|
235
|
+
|
236
|
+
Separate multiple tags with spaces or commas. By default tags are combined with OR, so actions matching any of the tags listed will be displayed. Use `+` to make a tag required and `!` to negate a tag (only display if the action does _not_ contain the tag). Use `-v` to invert the search and display all actions that _don't_ match.
|
233
237
|
|
234
238
|
```
|
235
239
|
NAME
|
data/bin/na
CHANGED
@@ -269,7 +269,7 @@ class App
|
|
269
269
|
tokens = Regexp.new(args.join(' '), Regexp::IGNORECASE)
|
270
270
|
else
|
271
271
|
tokens = []
|
272
|
-
args.each do |arg|
|
272
|
+
args.join(' ').split(/ /).each do |arg|
|
273
273
|
m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
274
274
|
tokens.push({
|
275
275
|
token: m['tok'],
|
@@ -285,7 +285,8 @@ class App
|
|
285
285
|
regex: options[:regex],
|
286
286
|
project: options[:project],
|
287
287
|
require_na: false)
|
288
|
-
|
288
|
+
regexes = tokens.delete_if { |token| token[:negate] }.map { |token| token[:token] }
|
289
|
+
NA.output_actions(actions, depth, files: files, regexes: regexes)
|
289
290
|
end
|
290
291
|
end
|
291
292
|
|
@@ -336,7 +337,8 @@ class App
|
|
336
337
|
negate: options[:invert],
|
337
338
|
project: options[:project],
|
338
339
|
require_na: false)
|
339
|
-
|
340
|
+
regexes = tags.delete_if { |token| token[:negate] }.map { |token| token[:token] }
|
341
|
+
NA.output_actions(actions, depth, files: files, regexes: regexes)
|
340
342
|
end
|
341
343
|
end
|
342
344
|
|
data/lib/na/action.rb
CHANGED
@@ -27,7 +27,7 @@ module NA
|
|
27
27
|
EOINSPECT
|
28
28
|
end
|
29
29
|
|
30
|
-
def pretty(extension: 'taskpaper', template: {})
|
30
|
+
def pretty(extension: 'taskpaper', template: {}, regexes: [])
|
31
31
|
default_template = {
|
32
32
|
file: '{xbk}',
|
33
33
|
parent: '{c}',
|
@@ -67,7 +67,7 @@ module NA
|
|
67
67
|
NA::Color.template(template[:output].gsub(/%filename/, filename)
|
68
68
|
.gsub(/%project/, project)
|
69
69
|
.gsub(/%parents?/, parents)
|
70
|
-
.gsub(/%action/, action))
|
70
|
+
.gsub(/%action/, action.highlight_search(regexes)))
|
71
71
|
end
|
72
72
|
|
73
73
|
def tags_match?(any: [], all: [], none: [])
|
data/lib/na/next_action.rb
CHANGED
@@ -80,8 +80,10 @@ module NA
|
|
80
80
|
notify("{by}Task added to {bw}#{file}")
|
81
81
|
end
|
82
82
|
|
83
|
-
def output_actions(actions, depth, files: nil)
|
84
|
-
|
83
|
+
def output_actions(actions, depth, files: nil, regexes: [])
|
84
|
+
return if files.nil?
|
85
|
+
|
86
|
+
template = if files.count.positive?
|
85
87
|
if files.count == 1
|
86
88
|
'%parent%action'
|
87
89
|
else
|
@@ -96,11 +98,10 @@ module NA
|
|
96
98
|
else
|
97
99
|
'%parent%action'
|
98
100
|
end
|
99
|
-
if files && @verbose
|
100
|
-
files.map { |f| notify("{dw}#{f}") }
|
101
|
-
end
|
102
101
|
|
103
|
-
|
102
|
+
files.map { |f| notify("{dw}#{f}") } if files && @verbose
|
103
|
+
|
104
|
+
puts(actions.map { |action| action.pretty(template: { output: template }, regexes: regexes) })
|
104
105
|
end
|
105
106
|
|
106
107
|
def parse_actions(depth: 1, query: nil, tag: nil, search: nil, negate: false, regex: false, project: nil, require_na: true)
|
@@ -136,28 +137,19 @@ module NA
|
|
136
137
|
end
|
137
138
|
else
|
138
139
|
search.each do |t|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
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
|
140
|
+
opt, req, neg = parse_search(t, negate)
|
141
|
+
optional.concat(opt)
|
142
|
+
required.concat(req)
|
143
|
+
negated.concat(neg)
|
150
144
|
end
|
151
145
|
end
|
152
146
|
end
|
153
147
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
files = match_working_dir(query)
|
160
|
-
end
|
148
|
+
files = if query.nil?
|
149
|
+
find_files(depth: depth)
|
150
|
+
else
|
151
|
+
match_working_dir(query)
|
152
|
+
end
|
161
153
|
|
162
154
|
files.each do |file|
|
163
155
|
save_working_dir(File.expand_path(file))
|
@@ -207,6 +199,40 @@ module NA
|
|
207
199
|
[files, actions]
|
208
200
|
end
|
209
201
|
|
202
|
+
def edit_file(file: nil, app: nil)
|
203
|
+
os_open(file, app: app) if file && File.exist?(file)
|
204
|
+
end
|
205
|
+
|
206
|
+
##
|
207
|
+
## Platform-agnostic open command
|
208
|
+
##
|
209
|
+
## @param file [String] The file to open
|
210
|
+
##
|
211
|
+
def os_open(file, app: nil)
|
212
|
+
os = RbConfig::CONFIG['target_os']
|
213
|
+
case os
|
214
|
+
when /darwin.*/i
|
215
|
+
darwin_open(file, app: app)
|
216
|
+
when /mingw|mswin/i
|
217
|
+
win_open(file)
|
218
|
+
else
|
219
|
+
linux_open(file)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def weed_cache_file
|
224
|
+
db_dir = File.expand_path('~/.local/share/na')
|
225
|
+
db_file = 'tdlist.txt'
|
226
|
+
file = File.join(db_dir, db_file)
|
227
|
+
if File.exist?(file)
|
228
|
+
dirs = IO.read(file).split("\n")
|
229
|
+
dirs.delete_if { |f| !File.exist?(f) }
|
230
|
+
File.open(file, 'w') { |f| f.puts dirs.join("\n") }
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
private
|
235
|
+
|
210
236
|
##
|
211
237
|
## Generate a menu of options and allow user selection
|
212
238
|
##
|
@@ -234,6 +260,25 @@ module NA
|
|
234
260
|
res
|
235
261
|
end
|
236
262
|
|
263
|
+
def parse_search(tag, negate)
|
264
|
+
required = []
|
265
|
+
optional = []
|
266
|
+
negated = []
|
267
|
+
new_rx = tag[:token].to_s.wildcard_to_rx
|
268
|
+
|
269
|
+
if negate
|
270
|
+
optional.push(new_rx) if tag[:negate]
|
271
|
+
required.push(new_rx) if tag[:required] && tag[:negate]
|
272
|
+
negated.push(new_rx) unless tag[:negate]
|
273
|
+
else
|
274
|
+
optional.push(new_rx) unless tag[:negate]
|
275
|
+
required.push(new_rx) if tag[:required] && !tag[:negate]
|
276
|
+
negated.push(new_rx) if tag[:negate]
|
277
|
+
end
|
278
|
+
|
279
|
+
[optional, required, negated]
|
280
|
+
end
|
281
|
+
|
237
282
|
##
|
238
283
|
## Get path to database of known todo files
|
239
284
|
##
|
@@ -276,7 +321,7 @@ module NA
|
|
276
321
|
notify('{r}No na database found', exit_code: 1) unless File.exist?(file)
|
277
322
|
|
278
323
|
dirs = IO.read(file).split("\n")
|
279
|
-
dirs.delete_if { |d| !d.
|
324
|
+
dirs.delete_if { |d| !d.dir_matches(any: optional, all: required) }
|
280
325
|
dirs.sort.uniq
|
281
326
|
end
|
282
327
|
|
@@ -289,21 +334,6 @@ module NA
|
|
289
334
|
File.open(file, 'w') { |f| f.puts dirs.join("\n") }
|
290
335
|
end
|
291
336
|
|
292
|
-
def weed_cache_file
|
293
|
-
db_dir = File.expand_path('~/.local/share/na')
|
294
|
-
db_file = 'tdlist.txt'
|
295
|
-
file = File.join(db_dir, db_file)
|
296
|
-
if File.exist?(file)
|
297
|
-
dirs = IO.read(file).split("\n")
|
298
|
-
dirs.delete_if { |f| !File.exist?(f) }
|
299
|
-
File.open(file, 'w') { |f| f.puts dirs.join("\n") }
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
|
-
def edit_file(file: nil, app: nil)
|
304
|
-
os_open(file, app: app) if file && File.exist?(file)
|
305
|
-
end
|
306
|
-
|
307
337
|
def darwin_open(file, app: nil)
|
308
338
|
if app
|
309
339
|
`open -a "#{app}" #{Shellwords.escape(file)}`
|
@@ -323,22 +353,5 @@ module NA
|
|
323
353
|
notify('{r}Unable to determine executable for `xdg-open`.')
|
324
354
|
end
|
325
355
|
end
|
326
|
-
|
327
|
-
##
|
328
|
-
## Platform-agnostic open command
|
329
|
-
##
|
330
|
-
## @param file [String] The file to open
|
331
|
-
##
|
332
|
-
def os_open(file, app: nil)
|
333
|
-
os = RbConfig::CONFIG['target_os']
|
334
|
-
case os
|
335
|
-
when /darwin.*/i
|
336
|
-
darwin_open(file, app: app)
|
337
|
-
when /mingw|mswin/i
|
338
|
-
win_open(file)
|
339
|
-
else
|
340
|
-
linux_open(file)
|
341
|
-
end
|
342
|
-
end
|
343
356
|
end
|
344
357
|
end
|
data/lib/na/string.rb
CHANGED
@@ -5,9 +5,7 @@ class ::String
|
|
5
5
|
prefix = match(/(^[ \t]+)/)
|
6
6
|
return 0 if prefix.nil?
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
tabs
|
8
|
+
prefix[1].gsub(/ /, "\t").scan(/\t/).count
|
11
9
|
end
|
12
10
|
|
13
11
|
##
|
@@ -21,13 +19,65 @@ class ::String
|
|
21
19
|
tag_color = NA::Color.template(color)
|
22
20
|
paren_color = NA::Color.template(parens)
|
23
21
|
value_color = NA::Color.template(value)
|
24
|
-
gsub(/(\s|m)(@[^ ("']+)(?:(\()(.*?)(\)))?/,
|
22
|
+
gsub(/(\s|m)(@[^ ("']+)(?:(\()(.*?)(\)))?/,
|
23
|
+
"\\1#{tag_color}\\2#{paren_color}\\3#{value_color}\\4#{paren_color}\\5#{last_color}")
|
24
|
+
end
|
25
|
+
|
26
|
+
def highlight_search(regexes, color: '{y}')
|
27
|
+
string = dup
|
28
|
+
color = NA::Color.template(color)
|
29
|
+
regexes.each do |rx|
|
30
|
+
next if rx.nil?
|
31
|
+
|
32
|
+
string.gsub!(/(#{rx.wildcard_to_rx})/i, "#{color}\\1#{NA::Color.template('{xg}')}")
|
33
|
+
end
|
34
|
+
string
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the last escape sequence from a string.
|
38
|
+
#
|
39
|
+
# Actually returns all escape codes, with the assumption
|
40
|
+
# that the result of inserting them will generate the
|
41
|
+
# same color as was set at the end of the string.
|
42
|
+
# Because you can send modifiers like dark and bold
|
43
|
+
# separate from color codes, only using the last code
|
44
|
+
# may not render the same style.
|
45
|
+
#
|
46
|
+
# @return [String] All escape codes in string
|
47
|
+
#
|
48
|
+
def last_color
|
49
|
+
scan(/\e\[[\d;]+m/).join('').gsub(/\e\[0m/, '')
|
50
|
+
end
|
51
|
+
|
52
|
+
def dir_to_rx
|
53
|
+
"#{split(%r{[/:]}).join('.*?/.*?')}[^/]+$"
|
54
|
+
end
|
55
|
+
|
56
|
+
def dir_matches(any: [], all: [])
|
57
|
+
matches_any(any.map(&:dir_to_rx)) && matches_all(all.map(&:dir_to_rx))
|
25
58
|
end
|
26
59
|
|
27
60
|
def matches(any: [], all: [], none: [])
|
28
61
|
matches_any(any) && matches_all(all) && matches_none(none)
|
29
62
|
end
|
30
63
|
|
64
|
+
def wildcard_to_rx
|
65
|
+
gsub(/\./, '\\.').gsub(/\*/, '.*?').gsub(/\?/, '.')
|
66
|
+
end
|
67
|
+
|
68
|
+
def cap_first!
|
69
|
+
replace cap_first
|
70
|
+
end
|
71
|
+
|
72
|
+
def cap_first
|
73
|
+
sub(/^([a-z])(.*)$/) do
|
74
|
+
m = Regexp.last_match
|
75
|
+
m[1].upcase << m[2]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
31
81
|
def matches_none(regexes)
|
32
82
|
regexes.each do |rx|
|
33
83
|
return false if match(Regexp.new(rx, Regexp::IGNORECASE))
|
@@ -48,19 +98,4 @@ class ::String
|
|
48
98
|
end
|
49
99
|
true
|
50
100
|
end
|
51
|
-
|
52
|
-
def wildcard_to_rx
|
53
|
-
gsub(/\./, '\\.').gsub(/\*/, '.*?').gsub(/\?/, '.')
|
54
|
-
end
|
55
|
-
|
56
|
-
def cap_first!
|
57
|
-
replace cap_first
|
58
|
-
end
|
59
|
-
|
60
|
-
def cap_first
|
61
|
-
sub(/^([a-z])(.*)$/) do
|
62
|
-
m = Regexp.last_match
|
63
|
-
m[1].upcase << m[2]
|
64
|
-
end
|
65
|
-
end
|
66
101
|
end
|
data/lib/na/version.rb
CHANGED
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.13<!--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
|
|
@@ -36,7 +36,7 @@ You can list next actions in files in the current directory by typing `na`. By d
|
|
36
36
|
|
37
37
|
#### Easy matching
|
38
38
|
|
39
|
-
`na` features intelligent project matching. Every time it locates a todo file, it adds the project to the database. Once a project is recorded, you can list its actions by using any portion of the parent directories or file names. If your project is in `~/Sites/dev/markedapp`, you could quickly list its next actions by typing `na next dev
|
39
|
+
`na` features intelligent project matching. Every time it locates a todo file, it adds the project to the database. Once a project is recorded, you can list its actions by using any portion of the parent directories or file names. If your project is in `~/Sites/dev/markedapp`, you could quickly list its next actions by typing `na next dev/mark`. Creat paths by separating with / or :, separate multiple queries with spaces. na will always look for the shortest match for a path.
|
40
40
|
|
41
41
|
#### Recursion
|
42
42
|
|
@@ -151,6 +151,8 @@ EXAMPLES
|
|
151
151
|
|
152
152
|
Example: `na find cool feature idea`
|
153
153
|
|
154
|
+
Unless `--exact` is specified, search is tokenized and combined with OR, so `na find cool feature idea` translates to `cool OR feature OR idea`, matching any string that contains any of the words. To make a token required, 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).
|
155
|
+
|
154
156
|
```
|
155
157
|
NAME
|
156
158
|
find - Find actions matching a search pattern
|
@@ -228,7 +230,9 @@ EXAMPLES
|
|
228
230
|
|
229
231
|
##### tagged
|
230
232
|
|
231
|
-
Example: `na tagged feature +maybe
|
233
|
+
Example: `na tagged feature +maybe`.
|
234
|
+
|
235
|
+
Separate multiple tags with spaces or commas. By default tags are combined with OR, so actions matching any of the tags listed will be displayed. Use `+` to make a tag required and `!` to negate a tag (only display if the action does _not_ contain the tag). Use `-v` to invert the search and display all actions that _don't_ match.
|
232
236
|
|
233
237
|
```
|
234
238
|
NAME
|