na 1.1.11 → 1.1.13
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 +27 -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 +95 -59
- data/lib/na/string.rb +13 -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: 03c240ef72b49cbb692ba1f55da9a36036d72bd51bb29549ea87262cceedfd51
|
4
|
+
data.tar.gz: e61a921f6af384ae5615f54116cb4e4e0c9472ab76d4a347fc50ea5633fc80a3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e577876361f3022e29d4c56e06d7c507fe05ac81da752debe476b481f92f671ee85ab25cb06e32a0608baf1b926454ea4e68aa6391e32ee7c5e7e02a5751dd11
|
7
|
+
data.tar.gz: 660039f2b13e23365ffa88fa8a59266f50941275830175458f867a26bfcbcd3553c8e5dc6ef70da1d1ab5946fc9d7a3361bdf932f8d63eda23c3c692657a5074
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,30 @@
|
|
1
|
+
### 1.1.13
|
2
|
+
|
3
|
+
2022-10-06 06:28
|
4
|
+
|
5
|
+
#### IMPROVED
|
6
|
+
|
7
|
+
- When specifying arguments to `next`, allow paths separated by / to do more exact matching
|
8
|
+
|
9
|
+
### 1.1.12
|
10
|
+
|
11
|
+
2022-10-06 05:42
|
12
|
+
|
13
|
+
#### NEW
|
14
|
+
|
15
|
+
- `na add -d X` to allow adding new actions to todo files in subdirectories
|
16
|
+
- You can now perform <>= queries on tag values (`na tagged "priority>=3"`)
|
17
|
+
- You can now perform string matches on tag values (`na tagged "note*=markdown"`)
|
18
|
+
- 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`
|
19
|
+
- Find and tagged recognize * and ? as wildcards
|
20
|
+
- --regex flag for find command
|
21
|
+
- --invert command (like grep -v) for find
|
22
|
+
- -v/--invert for tagged command
|
23
|
+
|
24
|
+
#### IMPROVED
|
25
|
+
|
26
|
+
- Require value 1-9 for --depth option
|
27
|
+
|
1
28
|
### 1.1.11
|
2
29
|
|
3
30
|
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.13
|
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,45 +103,49 @@ 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
|
-
|
130
|
-
|
131
|
-
optional.push(new_rx) unless t[:negate]
|
132
|
-
required.push(new_rx) if t[:required] && !t[:negate]
|
133
|
-
negated.push(new_rx) if t[:negate]
|
139
|
+
optional, required, negated = parse_search(t, negate)
|
134
140
|
end
|
135
141
|
end
|
136
142
|
end
|
137
143
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
files = match_working_dir(query)
|
144
|
-
end
|
144
|
+
files = if query.nil?
|
145
|
+
find_files(depth: depth)
|
146
|
+
else
|
147
|
+
match_working_dir(query)
|
148
|
+
end
|
145
149
|
|
146
150
|
files.each do |file|
|
147
151
|
save_working_dir(File.expand_path(file))
|
@@ -166,13 +170,24 @@ module NA
|
|
166
170
|
elsif line =~ /^[ \t]*- / && line !~ / @done/
|
167
171
|
next if require_na && line !~ /@#{NA.na_tag}\b/
|
168
172
|
|
169
|
-
|
170
|
-
|
173
|
+
action = line.sub(/^[ \t]*- /, '').sub(/ @#{NA.na_tag}\b/, '')
|
174
|
+
new_action = NA::Action.new(file, File.basename(file, ".#{NA.extension}"), parent.dup, action)
|
171
175
|
|
176
|
+
has_search = !optional.empty? || !required.empty? || !negated.empty?
|
177
|
+
next if has_search && !new_action.search_match?(any: optional,
|
178
|
+
all: required,
|
179
|
+
none: negated)
|
180
|
+
|
181
|
+
if project
|
182
|
+
rx = project.split(%r{[/:]}).join('.*?/.*?')
|
183
|
+
next unless parent.join('/') =~ Regexp.new(rx, Regexp::IGNORECASE)
|
172
184
|
end
|
173
185
|
|
174
|
-
|
175
|
-
|
186
|
+
has_tag = !optional_tag.empty? || !required_tag.empty? || !negated_tag.empty?
|
187
|
+
next if has_tag && !new_action.tags_match?(any: optional_tag,
|
188
|
+
all: required_tag,
|
189
|
+
none: negated_tag)
|
190
|
+
|
176
191
|
actions.push(new_action)
|
177
192
|
end
|
178
193
|
end
|
@@ -180,6 +195,40 @@ module NA
|
|
180
195
|
[files, actions]
|
181
196
|
end
|
182
197
|
|
198
|
+
def edit_file(file: nil, app: nil)
|
199
|
+
os_open(file, app: app) if file && File.exist?(file)
|
200
|
+
end
|
201
|
+
|
202
|
+
##
|
203
|
+
## Platform-agnostic open command
|
204
|
+
##
|
205
|
+
## @param file [String] The file to open
|
206
|
+
##
|
207
|
+
def os_open(file, app: nil)
|
208
|
+
os = RbConfig::CONFIG['target_os']
|
209
|
+
case os
|
210
|
+
when /darwin.*/i
|
211
|
+
darwin_open(file, app: app)
|
212
|
+
when /mingw|mswin/i
|
213
|
+
win_open(file)
|
214
|
+
else
|
215
|
+
linux_open(file)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def weed_cache_file
|
220
|
+
db_dir = File.expand_path('~/.local/share/na')
|
221
|
+
db_file = 'tdlist.txt'
|
222
|
+
file = File.join(db_dir, db_file)
|
223
|
+
if File.exist?(file)
|
224
|
+
dirs = IO.read(file).split("\n")
|
225
|
+
dirs.delete_if { |f| !File.exist?(f) }
|
226
|
+
File.open(file, 'w') { |f| f.puts dirs.join("\n") }
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
private
|
231
|
+
|
183
232
|
##
|
184
233
|
## Generate a menu of options and allow user selection
|
185
234
|
##
|
@@ -207,6 +256,25 @@ module NA
|
|
207
256
|
res
|
208
257
|
end
|
209
258
|
|
259
|
+
def parse_search(tag, negate)
|
260
|
+
required = []
|
261
|
+
optional = []
|
262
|
+
negated = []
|
263
|
+
new_rx = tag[:token].to_s.wildcard_to_rx
|
264
|
+
|
265
|
+
if negate
|
266
|
+
optional.push(new_rx) if tag[:negate]
|
267
|
+
required.push(new_rx) if tag[:required] && tag[:negate]
|
268
|
+
negated.push(new_rx) unless tag[:negate]
|
269
|
+
else
|
270
|
+
optional.push(new_rx) unless tag[:negate]
|
271
|
+
required.push(new_rx) if tag[:required] && !tag[:negate]
|
272
|
+
negated.push(new_rx) if tag[:negate]
|
273
|
+
end
|
274
|
+
|
275
|
+
[optional, required, negated]
|
276
|
+
end
|
277
|
+
|
210
278
|
##
|
211
279
|
## Get path to database of known todo files
|
212
280
|
##
|
@@ -249,7 +317,7 @@ module NA
|
|
249
317
|
notify('{r}No na database found', exit_code: 1) unless File.exist?(file)
|
250
318
|
|
251
319
|
dirs = IO.read(file).split("\n")
|
252
|
-
dirs.delete_if { |d| !d.
|
320
|
+
dirs.delete_if { |d| !d.dir_matches(any: optional, all: required) }
|
253
321
|
dirs.sort.uniq
|
254
322
|
end
|
255
323
|
|
@@ -262,21 +330,6 @@ module NA
|
|
262
330
|
File.open(file, 'w') { |f| f.puts dirs.join("\n") }
|
263
331
|
end
|
264
332
|
|
265
|
-
def weed_cache_file
|
266
|
-
db_dir = File.expand_path('~/.local/share/na')
|
267
|
-
db_file = 'tdlist.txt'
|
268
|
-
file = File.join(db_dir, db_file)
|
269
|
-
if File.exist?(file)
|
270
|
-
dirs = IO.read(file).split("\n")
|
271
|
-
dirs.delete_if { |f| !File.exist?(f) }
|
272
|
-
File.open(file, 'w') { |f| f.puts dirs.join("\n") }
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
|
-
def edit_file(file: nil, app: nil)
|
277
|
-
os_open(file, app: app) if file && File.exist?(file)
|
278
|
-
end
|
279
|
-
|
280
333
|
def darwin_open(file, app: nil)
|
281
334
|
if app
|
282
335
|
`open -a "#{app}" #{Shellwords.escape(file)}`
|
@@ -296,22 +349,5 @@ module NA
|
|
296
349
|
notify('{r}Unable to determine executable for `xdg-open`.')
|
297
350
|
end
|
298
351
|
end
|
299
|
-
|
300
|
-
##
|
301
|
-
## Platform-agnostic open command
|
302
|
-
##
|
303
|
-
## @param file [String] The file to open
|
304
|
-
##
|
305
|
-
def os_open(file, app: nil)
|
306
|
-
os = RbConfig::CONFIG['target_os']
|
307
|
-
case os
|
308
|
-
when /darwin.*/i
|
309
|
-
darwin_open(file, app: app)
|
310
|
-
when /mingw|mswin/i
|
311
|
-
win_open(file)
|
312
|
-
else
|
313
|
-
linux_open(file)
|
314
|
-
end
|
315
|
-
end
|
316
352
|
end
|
317
353
|
end
|
data/lib/na/string.rb
CHANGED
@@ -24,6 +24,14 @@ class ::String
|
|
24
24
|
gsub(/(\s|m)(@[^ ("']+)(?:(\()(.*?)(\)))?/, "\\1#{tag_color}\\2#{paren_color}\\3#{value_color}\\4#{paren_color}\\5#{last_color}")
|
25
25
|
end
|
26
26
|
|
27
|
+
def dir_to_rx
|
28
|
+
split(%r{[/:]}).join('.*?/.*?') + '[^/]+$'
|
29
|
+
end
|
30
|
+
|
31
|
+
def dir_matches(any: [], all: [])
|
32
|
+
matches_any(any.map { |token| token.dir_to_rx }) && matches_all(all.map { |token| token.dir_to_rx })
|
33
|
+
end
|
34
|
+
|
27
35
|
def matches(any: [], all: [], none: [])
|
28
36
|
matches_any(any) && matches_all(all) && matches_none(none)
|
29
37
|
end
|
@@ -36,6 +44,7 @@ class ::String
|
|
36
44
|
end
|
37
45
|
|
38
46
|
def matches_any(regexes)
|
47
|
+
string = dup
|
39
48
|
regexes.each do |rx|
|
40
49
|
return true if match(Regexp.new(rx, Regexp::IGNORECASE))
|
41
50
|
end
|
@@ -49,6 +58,10 @@ class ::String
|
|
49
58
|
true
|
50
59
|
end
|
51
60
|
|
61
|
+
def wildcard_to_rx
|
62
|
+
gsub(/\./, '\\.').gsub(/\*/, '.*?').gsub(/\?/, '.')
|
63
|
+
end
|
64
|
+
|
52
65
|
def cap_first!
|
53
66
|
replace cap_first
|
54
67
|
end
|
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.12<!--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.13
|
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
|