na 1.1.21 → 1.1.23
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 +10 -8
- data/bin/na +80 -24
- data/lib/na/action.rb +5 -2
- data/lib/na/next_action.rb +98 -21
- data/lib/na/string.rb +53 -9
- data/lib/na/version.rb +1 -1
- data/src/README.md +4 -4
- 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: c8bee2cada1590cdc80fe9943947c719649d441ed15a6c51228fa0bebe4e94eb
|
4
|
+
data.tar.gz: 3dfc5a53bb88952243388b86e853af744b76938cbc69ef3b6f6ac8e58c03a9fc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0603c3187363e3c5895f9e49af6deab11779cbf47442f4f76b7b5ad2608e4676d0cb337368e2a0d6a5f9bad7f5aff957d63e986cefe1997c671a6b6b5193e3e4
|
7
|
+
data.tar.gz: e570fe4a32d7e45b4bd2171c73bee68adb6017ae22901c92486ccbe1bdb2461a50dac988d8ed862aa85237c070ebb305d3b6c2c61afc920826568c628d630faf
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,30 @@
|
|
1
|
+
### 1.1.23
|
2
|
+
|
3
|
+
2022-10-07 10:02
|
4
|
+
|
5
|
+
#### NEW
|
6
|
+
|
7
|
+
- Saved searches. Add `--save TITLE` to `tagged` or `find` commands to save the parameters for use with `na saved TITLE`
|
8
|
+
|
9
|
+
#### FIXED
|
10
|
+
|
11
|
+
- Restore wildcard capability of tag searches
|
12
|
+
|
13
|
+
### 1.1.22
|
14
|
+
|
15
|
+
2022-10-07 05:58
|
16
|
+
|
17
|
+
#### IMPROVED
|
18
|
+
|
19
|
+
- Help output and code documentation
|
20
|
+
- Allow wildcards (* and ?) when matching todo history
|
21
|
+
- Allow multiple todo queries separated by comma
|
22
|
+
|
23
|
+
#### FIXED
|
24
|
+
|
25
|
+
- Remove file extension when matching todo history
|
26
|
+
- Todo history query failed on exact match
|
27
|
+
|
1
28
|
### 1.1.21
|
2
29
|
|
3
30
|
2022-10-07 04:26
|
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.23
|
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.
|
@@ -59,7 +59,7 @@ SYNOPSIS
|
|
59
59
|
na [global options] command [command options] [arguments...]
|
60
60
|
|
61
61
|
VERSION
|
62
|
-
1.1.
|
62
|
+
1.1.23
|
63
63
|
|
64
64
|
GLOBAL OPTIONS
|
65
65
|
-a, --[no-]add - Add a next action (deprecated, for backwards compatibility)
|
@@ -82,6 +82,7 @@ COMMANDS
|
|
82
82
|
initconfig - Initialize the config file using current global options
|
83
83
|
next, show - Show next actions
|
84
84
|
prompt - Show or install prompt hooks for the current shell
|
85
|
+
saved - Execute a saved search
|
85
86
|
tagged - Find actions matching a tag
|
86
87
|
todos - Show list of known todo files
|
87
88
|
```
|
@@ -171,9 +172,10 @@ DESCRIPTION
|
|
171
172
|
COMMAND OPTIONS
|
172
173
|
-d, --depth=DEPTH - Recurse to depth (default: none)
|
173
174
|
-e, --regex - Interpret search pattern as regular expression
|
174
|
-
--in=TODO_PATH - Show actions from a specific todo file in history (default: none)
|
175
|
+
--in=TODO_PATH - Show actions from a specific todo file in history. May use wildcards (* and ?) (default: none)
|
175
176
|
-o, --or - Combine search tokens with OR, displaying actions matching ANY of the terms
|
176
177
|
--proj, --project=PROJECT[/SUBPROJECT] - Show actions from a specific project (default: none)
|
178
|
+
--save=TITLE - Save this search for future use (default: none)
|
177
179
|
-v, --invert - Show actions not matching search pattern
|
178
180
|
-x, --exact - Match pattern exactly
|
179
181
|
|
@@ -209,9 +211,9 @@ EXAMPLES
|
|
209
211
|
|
210
212
|
Examples:
|
211
213
|
|
212
|
-
- `na
|
213
|
-
- `na
|
214
|
-
- `na
|
214
|
+
- `na next` (list all next actions in the current directory)
|
215
|
+
- `na next -d 3` (list all next actions in the current directory and look for additional files 3 levels deep from there)
|
216
|
+
- `na next marked2` (show next actions from another directory you've previously used na on)
|
215
217
|
|
216
218
|
```
|
217
219
|
NAME
|
@@ -222,7 +224,7 @@ SYNOPSIS
|
|
222
224
|
na [global options] next [command options] [QUERY]
|
223
225
|
|
224
226
|
DESCRIPTION
|
225
|
-
Next actions are actions which contain the next action tag (default @na), do not contain @done, and are not in the Archive project.
|
227
|
+
Next actions are actions which contain the next action tag (default @na), do not contain @done, and are not in the Archive project. Arguments will target a todo file from history, whether it's in the current directory or not. Todo file queries can include path components separated by / or :, and may use wildcards (`*` to match any text, `?` to match a single character). Multiple queries allowed (separate arguments or separated by comma).
|
226
228
|
|
227
229
|
COMMAND OPTIONS
|
228
230
|
-d, --depth=DEPTH - Recurse to depth (default: 2)
|
@@ -256,7 +258,7 @@ SYNOPSIS
|
|
256
258
|
na [global options] next [command options] [QUERY]
|
257
259
|
|
258
260
|
DESCRIPTION
|
259
|
-
Next actions are actions which contain the next action tag (default @na), do not contain @done, and are not in the Archive project.
|
261
|
+
Next actions are actions which contain the next action tag (default @na), do not contain @done, and are not in the Archive project. Arguments will target a todo file from history, whether it's in the current directory or not. Todo file queries can include path components separated by / or :, and may use wildcards (`*` to match any text, `?` to match a single character). Multiple queries allowed (separate arguments or separated by comma).
|
260
262
|
|
261
263
|
COMMAND OPTIONS
|
262
264
|
-d, --depth=DEPTH - Recurse to depth (default: 2)
|
data/bin/na
CHANGED
@@ -54,8 +54,12 @@ class App
|
|
54
54
|
switch %i[debug]
|
55
55
|
|
56
56
|
desc 'Show next actions'
|
57
|
-
long_desc
|
58
|
-
do not contain @done, and are not in the Archive project.
|
57
|
+
long_desc 'Next actions are actions which contain the next action tag (default @na),
|
58
|
+
do not contain @done, and are not in the Archive project.
|
59
|
+
|
60
|
+
Arguments will target a todo file from history, whether it\'s in the current
|
61
|
+
directory or not. Todo file queries can include path components separated by /
|
62
|
+
or :, and may use wildcards (`*` to match any text, `?` to match a single character). Multiple queries allowed (separate arguments or separated by comma).'
|
59
63
|
arg_name 'QUERY', optional: true
|
60
64
|
command %i[next show] do |c|
|
61
65
|
c.example 'na next', desc: 'display the next actions from any todo files in the current directory'
|
@@ -93,11 +97,13 @@ class App
|
|
93
97
|
if args.count.positive?
|
94
98
|
tokens = []
|
95
99
|
args.each do |arg|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
100
|
+
arg.split(/ *, */).each do |a|
|
101
|
+
m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
|
102
|
+
tokens.push({
|
103
|
+
token: m['tok'],
|
104
|
+
required: !m['req'].nil?
|
105
|
+
})
|
106
|
+
end
|
101
107
|
end
|
102
108
|
end
|
103
109
|
|
@@ -251,7 +257,7 @@ class App
|
|
251
257
|
c.arg_name 'DEPTH'
|
252
258
|
c.flag %i[d depth], type: :integer, must_match: /^\d+$/
|
253
259
|
|
254
|
-
c.desc 'Show actions from a specific todo file in history'
|
260
|
+
c.desc 'Show actions from a specific todo file in history. May use wildcards (* and ?)'
|
255
261
|
c.arg_name 'TODO_PATH'
|
256
262
|
c.flag %i[in]
|
257
263
|
|
@@ -265,7 +271,16 @@ class App
|
|
265
271
|
c.desc 'Show actions not matching search pattern'
|
266
272
|
c.switch %i[v invert], negatable: false
|
267
273
|
|
274
|
+
c.desc 'Save this search for future use'
|
275
|
+
c.arg_name 'TITLE'
|
276
|
+
c.flag %i[save]
|
277
|
+
|
268
278
|
c.action do |global_options, options, args|
|
279
|
+
if options[:save]
|
280
|
+
title = options[:save].gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
|
281
|
+
NA.save_search(title, "find #{NA.command_line.map { |c| "\"#{c}\"" }.join(' ')}")
|
282
|
+
end
|
283
|
+
|
269
284
|
depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
|
270
285
|
3
|
271
286
|
else
|
@@ -292,10 +307,14 @@ class App
|
|
292
307
|
|
293
308
|
todo = nil
|
294
309
|
if options[:in]
|
295
|
-
todo = [
|
296
|
-
|
297
|
-
|
298
|
-
|
310
|
+
todo = []
|
311
|
+
options[:in].split(/ *, */).each do |a|
|
312
|
+
m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
|
313
|
+
todo.push({
|
314
|
+
token: m['tok'],
|
315
|
+
required: !m['req'].nil?
|
316
|
+
})
|
317
|
+
end
|
299
318
|
end
|
300
319
|
|
301
320
|
files, actions = NA.parse_actions(depth: depth,
|
@@ -335,7 +354,7 @@ class App
|
|
335
354
|
c.default_value 1
|
336
355
|
c.flag %i[d depth], type: :integer, must_match: /^\d+$/
|
337
356
|
|
338
|
-
c.desc 'Show actions from a specific todo file in history'
|
357
|
+
c.desc 'Show actions from a specific todo file in history. May use wildcards (* and ?)'
|
339
358
|
c.arg_name 'TODO_PATH'
|
340
359
|
c.flag %i[in]
|
341
360
|
|
@@ -349,7 +368,16 @@ class App
|
|
349
368
|
c.desc 'Show actions not matching tags'
|
350
369
|
c.switch %i[v invert], negatable: false
|
351
370
|
|
371
|
+
c.desc 'Save this search for future use'
|
372
|
+
c.arg_name 'TITLE'
|
373
|
+
c.flag %i[save]
|
374
|
+
|
352
375
|
c.action do |global_options, options, args|
|
376
|
+
if options[:save]
|
377
|
+
title = options[:save].gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
|
378
|
+
NA.save_search(title, "tagged #{NA.command_line.map { |cmd| "\"#{cmd}\"" }.join(' ')}")
|
379
|
+
end
|
380
|
+
|
353
381
|
depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
|
354
382
|
3
|
355
383
|
else
|
@@ -361,9 +389,10 @@ class App
|
|
361
389
|
all_req = args.join(' ') !~ /[+!\-]/ && !options[:or]
|
362
390
|
args.join(',').split(/ *, */).each do |arg|
|
363
391
|
# TODO: <> comparisons do nothing right now
|
364
|
-
m = arg.match(/^(?<req>[+\-!])?(?<tag>[^
|
392
|
+
m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
|
393
|
+
|
365
394
|
tags.push({
|
366
|
-
tag: m['tag'],
|
395
|
+
tag: m['tag'].wildcard_to_rx,
|
367
396
|
comp: m['op'],
|
368
397
|
value: m['val'],
|
369
398
|
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
@@ -373,10 +402,14 @@ class App
|
|
373
402
|
|
374
403
|
todo = nil
|
375
404
|
if options[:in]
|
376
|
-
todo = [
|
377
|
-
|
378
|
-
|
379
|
-
|
405
|
+
todo = []
|
406
|
+
options[:in].split(/ *, */).each do |a|
|
407
|
+
m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
|
408
|
+
todo.push({
|
409
|
+
token: m['tok'],
|
410
|
+
required: !m['req'].nil?
|
411
|
+
})
|
412
|
+
end
|
380
413
|
end
|
381
414
|
|
382
415
|
files, actions = NA.parse_actions(depth: depth,
|
@@ -469,11 +502,13 @@ class App
|
|
469
502
|
if args.count.positive?
|
470
503
|
tokens = []
|
471
504
|
args.each do |arg|
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
505
|
+
arg.split(/ *, */).each do |a|
|
506
|
+
m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
|
507
|
+
tokens.push({
|
508
|
+
token: m['tok'],
|
509
|
+
required: !m['req'].nil?
|
510
|
+
})
|
511
|
+
end
|
477
512
|
end
|
478
513
|
end
|
479
514
|
|
@@ -529,6 +564,26 @@ class App
|
|
529
564
|
end
|
530
565
|
end
|
531
566
|
|
567
|
+
desc 'Execute a saved search'
|
568
|
+
long_desc 'Run without argument to list saved searches'
|
569
|
+
arg_name 'SEARCH_TITLE', optional: true
|
570
|
+
command %i[saved] do |c|
|
571
|
+
c.action do |_global_options, _options, args|
|
572
|
+
searches = NA.load_searches
|
573
|
+
if args.empty?
|
574
|
+
NA.notify("{bg}Saved searches stored in {bw}#{NA.database_path(file: 'saved_searches.yml')}")
|
575
|
+
NA.notify(searches.map { |k, v| "{y}#{k}: {w}#{v}" }.join("\n"), exit_code: 0)
|
576
|
+
else
|
577
|
+
keys = searches.keys.delete_if { |k| k !~ /#{args[0]}/ }
|
578
|
+
NA.notify("{r}Search #{args[0]} not found", exit_code: 1) if keys.empty?
|
579
|
+
|
580
|
+
key = keys[0]
|
581
|
+
cmd = Shellwords.shellsplit(searches[key])
|
582
|
+
exit run(cmd)
|
583
|
+
end
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
532
587
|
pre do |global, _command, _options, _args|
|
533
588
|
NA.verbose = global[:debug]
|
534
589
|
NA.extension = global[:ext]
|
@@ -558,6 +613,7 @@ class App
|
|
558
613
|
end
|
559
614
|
end
|
560
615
|
|
616
|
+
NA.command_line = ARGV
|
561
617
|
@command = ARGV[0]
|
562
618
|
$first_arg = ARGV[1]
|
563
619
|
|
data/lib/na/action.rb
CHANGED
@@ -127,11 +127,14 @@ module NA
|
|
127
127
|
end
|
128
128
|
|
129
129
|
def compare_tag(tag)
|
130
|
-
|
130
|
+
keys = @tags.keys.delete_if { |k| k !~ Regexp.new(tag[:tag], Regexp::IGNORECASE) }
|
131
|
+
return false if keys.empty?
|
132
|
+
|
133
|
+
key = keys[0]
|
131
134
|
|
132
135
|
return true if tag[:comp].nil?
|
133
136
|
|
134
|
-
tag_val = @tags[
|
137
|
+
tag_val = @tags[key]
|
135
138
|
val = tag[:value]
|
136
139
|
|
137
140
|
return false if tag_val.nil?
|
data/lib/na/next_action.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
# Next Action methods
|
4
4
|
module NA
|
5
5
|
class << self
|
6
|
-
attr_accessor :verbose, :extension, :na_tag
|
6
|
+
attr_accessor :verbose, :extension, :na_tag, :command_line
|
7
7
|
|
8
8
|
##
|
9
9
|
## Output to STDERR
|
@@ -15,11 +15,57 @@ module NA
|
|
15
15
|
return if debug && !@verbose
|
16
16
|
|
17
17
|
$stderr.puts NA::Color.template("{x}#{msg}{x}")
|
18
|
-
if exit_code
|
18
|
+
if exit_code
|
19
19
|
Process.exit exit_code
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
+
##
|
24
|
+
## Display and read a Yes/No prompt
|
25
|
+
##
|
26
|
+
## @param prompt [String] The prompt string
|
27
|
+
## @param default [Boolean] default value if
|
28
|
+
## return is pressed or prompt is
|
29
|
+
## skipped
|
30
|
+
##
|
31
|
+
## @return [Boolean] result
|
32
|
+
##
|
33
|
+
def yn(prompt, default: true)
|
34
|
+
return default unless $stdout.isatty
|
35
|
+
|
36
|
+
tty_state = `stty -g`
|
37
|
+
system 'stty raw -echo cbreak isig'
|
38
|
+
yn = color_single_options(default ? %w[Y n] : %w[y N])
|
39
|
+
$stdout.syswrite "\e[1;37m#{prompt} #{yn}\e[1;37m? \e[0m"
|
40
|
+
res = $stdin.sysread 1
|
41
|
+
res.chomp!
|
42
|
+
puts
|
43
|
+
system 'stty cooked'
|
44
|
+
system "stty #{tty_state}"
|
45
|
+
res.empty? ? default : res =~ /y/i
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
## Helper function to colorize the Y/N prompt
|
50
|
+
##
|
51
|
+
## @param choices [Array] The choices with
|
52
|
+
## default capitalized
|
53
|
+
##
|
54
|
+
## @return [String] colorized string
|
55
|
+
##
|
56
|
+
def color_single_options(choices = %w[y n])
|
57
|
+
out = []
|
58
|
+
choices.each do |choice|
|
59
|
+
case choice
|
60
|
+
when /[A-Z]/
|
61
|
+
out.push(NA::Color.template("{bw}#{choice}{x}"))
|
62
|
+
else
|
63
|
+
out.push(NA::Color.template("{dw}#{choice}{xg}"))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
NA::Color.template("{xg}[#{out.join('/')}{xg}]{x}")
|
67
|
+
end
|
68
|
+
|
23
69
|
##
|
24
70
|
## Create a new todo file
|
25
71
|
##
|
@@ -308,6 +354,50 @@ module NA
|
|
308
354
|
puts NA::Color.template(dirs.join("\n"))
|
309
355
|
end
|
310
356
|
|
357
|
+
def save_search(title, search)
|
358
|
+
file = database_path(file: 'saved_searches.yml')
|
359
|
+
searches = load_searches
|
360
|
+
title = title.gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
|
361
|
+
|
362
|
+
if searches.key?(title)
|
363
|
+
res = yn('Overwrite existing definition?', default: true)
|
364
|
+
notify('{r}Cancelled', exit_code: 0) unless res
|
365
|
+
|
366
|
+
end
|
367
|
+
|
368
|
+
searches[title] = search
|
369
|
+
File.open(file, 'w') { |f| f.puts(YAML.dump(searches)) }
|
370
|
+
NA.notify("{y}Search #{title} saved", exit_code: 0)
|
371
|
+
end
|
372
|
+
|
373
|
+
def load_searches
|
374
|
+
file = database_path(file: 'saved_searches.yml')
|
375
|
+
if File.exist?(file)
|
376
|
+
searches = YAML.safe_load(IO.read(file))
|
377
|
+
else
|
378
|
+
searches = {
|
379
|
+
'soon' => 'tagged "due<in 2 days,due>yesterday"',
|
380
|
+
'overdue' => 'tagged "due<now"',
|
381
|
+
'high' => 'tagged "prio>3"',
|
382
|
+
'maybe' => 'tagged "maybe"'
|
383
|
+
}
|
384
|
+
File.open(file, 'w') { |f| f.puts(YAML.dump(searches)) }
|
385
|
+
end
|
386
|
+
searches
|
387
|
+
end
|
388
|
+
|
389
|
+
##
|
390
|
+
## Get path to database of known todo files
|
391
|
+
##
|
392
|
+
## @return [String] File path
|
393
|
+
##
|
394
|
+
def database_path(file: 'tdlist.txt')
|
395
|
+
db_dir = File.expand_path('~/.local/share/na')
|
396
|
+
# Create directory if needed
|
397
|
+
FileUtils.mkdir_p(db_dir) unless File.directory?(db_dir)
|
398
|
+
File.join(db_dir, file)
|
399
|
+
end
|
400
|
+
|
311
401
|
private
|
312
402
|
|
313
403
|
##
|
@@ -356,19 +446,6 @@ module NA
|
|
356
446
|
[optional, required, negated]
|
357
447
|
end
|
358
448
|
|
359
|
-
##
|
360
|
-
## Get path to database of known todo files
|
361
|
-
##
|
362
|
-
## @return [String] File path
|
363
|
-
##
|
364
|
-
def database_path
|
365
|
-
db_dir = File.expand_path('~/.local/share/na')
|
366
|
-
# Create directory if needed
|
367
|
-
FileUtils.mkdir_p(db_dir) unless File.directory?(db_dir)
|
368
|
-
db_file = 'tdlist.txt'
|
369
|
-
File.join(db_dir, db_file)
|
370
|
-
end
|
371
|
-
|
372
449
|
##
|
373
450
|
## Find a matching path using semi-fuzzy matching.
|
374
451
|
## Search tokens can include ! and + to negate or make
|
@@ -379,18 +456,18 @@ module NA
|
|
379
456
|
## between characters
|
380
457
|
##
|
381
458
|
def match_working_dir(search, distance: 1)
|
382
|
-
search = search.map { |t| t[:token] }.join('/')
|
383
|
-
optional = [search]
|
384
|
-
required = [search]
|
385
|
-
|
386
459
|
file = database_path
|
387
460
|
notify('{r}No na database found', exit_code: 1) unless File.exist?(file)
|
388
461
|
|
389
462
|
dirs = IO.read(file).split("\n")
|
390
463
|
|
391
|
-
|
464
|
+
optional = search.map { |t| t[:token] }
|
465
|
+
required = search.filter { |s| s[:required] }.map { |t| t[:token] }
|
466
|
+
|
467
|
+
NA.notify("{bw}Optional directory regex: {x}#{optional.map(&:dir_to_rx)}", debug: true)
|
468
|
+
NA.notify("{bw}Required directory regex: {x}#{required.map(&:dir_to_rx)}", debug: true)
|
392
469
|
|
393
|
-
dirs.delete_if { |d| !d.dir_matches(any: optional, all: required) }
|
470
|
+
dirs.delete_if { |d| !d.sub(/\.#{NA.extension}$/, '').dir_matches(any: optional, all: required) }
|
394
471
|
dirs.sort.uniq
|
395
472
|
end
|
396
473
|
|
data/lib/na/string.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# String helpers
|
3
4
|
class ::String
|
5
|
+
##
|
6
|
+
## Determine indentation level of line
|
7
|
+
##
|
8
|
+
## @return [Number] number of indents detected
|
9
|
+
##
|
4
10
|
def indent_level
|
5
11
|
prefix = match(/(^[ \t]+)/)
|
6
12
|
return 0 if prefix.nil?
|
@@ -11,7 +17,13 @@ class ::String
|
|
11
17
|
##
|
12
18
|
## Colorize @tags with ANSI escapes
|
13
19
|
##
|
14
|
-
## @param color
|
20
|
+
## @param color [String] color (see #Color)
|
21
|
+
## @param value [String] The value color
|
22
|
+
## template
|
23
|
+
## @param parens [String] The parens color
|
24
|
+
## template
|
25
|
+
## @param last_color [String] Color to restore after
|
26
|
+
## tag highlight
|
15
27
|
##
|
16
28
|
## @return [String] string with @tags highlighted
|
17
29
|
##
|
@@ -23,6 +35,16 @@ class ::String
|
|
23
35
|
"\\1#{tag_color}\\2#{paren_color}\\3#{value_color}\\4#{paren_color}\\5#{last_color}")
|
24
36
|
end
|
25
37
|
|
38
|
+
##
|
39
|
+
## Highlight search results
|
40
|
+
##
|
41
|
+
## @param regexes [Array] The regexes for the
|
42
|
+
## search
|
43
|
+
## @param color [String] The highlight color
|
44
|
+
## template
|
45
|
+
## @param last_color [String] Color to restore after
|
46
|
+
## highlight
|
47
|
+
##
|
26
48
|
def highlight_search(regexes, color: '{y}', last_color: '{xg}')
|
27
49
|
string = dup
|
28
50
|
color = NA::Color.template(color)
|
@@ -42,12 +64,13 @@ class ::String
|
|
42
64
|
|
43
65
|
# Returns the last escape sequence from a string.
|
44
66
|
#
|
45
|
-
# Actually returns all escape codes, with the
|
46
|
-
# that the result of inserting them
|
47
|
-
# same color as was set at
|
48
|
-
# Because you can send
|
49
|
-
#
|
50
|
-
#
|
67
|
+
# @note Actually returns all escape codes, with the
|
68
|
+
# assumption that the result of inserting them
|
69
|
+
# will generate the same color as was set at
|
70
|
+
# the end of the string. Because you can send
|
71
|
+
# modifiers like dark and bold separate from
|
72
|
+
# color codes, only using the last code may
|
73
|
+
# not render the same style.
|
51
74
|
#
|
52
75
|
# @return [String] All escape codes in string
|
53
76
|
#
|
@@ -55,8 +78,18 @@ class ::String
|
|
55
78
|
scan(/\e\[[\d;]+m/).join('').gsub(/\e\[0m/, '')
|
56
79
|
end
|
57
80
|
|
81
|
+
##
|
82
|
+
## Convert a directory path to a regular expression
|
83
|
+
##
|
84
|
+
## @note Splits at / or :, adds variable distance
|
85
|
+
## between characters, joins segments with
|
86
|
+
## slashes and requires that last segment
|
87
|
+
## match last segment of target path
|
88
|
+
##
|
89
|
+
## @param distance The distance
|
90
|
+
##
|
58
91
|
def dir_to_rx(distance: 2)
|
59
|
-
"#{split(%r{[/:]}).map { |comp| comp.split('').join(".{0,#{distance}}") }.join('.*?/.*?')}[^/]
|
92
|
+
"#{split(%r{[/:]}).map { |comp| comp.split('').join(".{0,#{distance}}").gsub(/\*/, '[^ ]*?') }.join('.*?/.*?')}[^/]*?$"
|
60
93
|
end
|
61
94
|
|
62
95
|
def dir_matches(any: [], all: [])
|
@@ -67,14 +100,25 @@ class ::String
|
|
67
100
|
matches_any(any) && matches_all(all) && matches_none(none)
|
68
101
|
end
|
69
102
|
|
103
|
+
##
|
104
|
+
## Convert wildcard characters to regular expressions
|
105
|
+
##
|
106
|
+
## @return [String] Regex string
|
107
|
+
##
|
70
108
|
def wildcard_to_rx
|
71
|
-
gsub(/\./, '\\.').gsub(
|
109
|
+
gsub(/\./, '\\.').gsub(/\?/, '.').gsub(/\*/, '[^ ]*?')
|
72
110
|
end
|
73
111
|
|
74
112
|
def cap_first!
|
75
113
|
replace cap_first
|
76
114
|
end
|
77
115
|
|
116
|
+
##
|
117
|
+
## Capitalize first character, leaving other
|
118
|
+
## capitalization in place
|
119
|
+
##
|
120
|
+
## @return [String] capitalized string
|
121
|
+
##
|
78
122
|
def cap_first
|
79
123
|
sub(/^([a-z])(.*)$/) do
|
80
124
|
m = Regexp.last_match
|
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.22<!--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
|
|
@@ -92,9 +92,9 @@ Unless `--exact` is specified, search is tokenized and combined with AND, so `na
|
|
92
92
|
|
93
93
|
Examples:
|
94
94
|
|
95
|
-
- `na
|
96
|
-
- `na
|
97
|
-
- `na
|
95
|
+
- `na next` (list all next actions in the current directory)
|
96
|
+
- `na next -d 3` (list all next actions in the current directory and look for additional files 3 levels deep from there)
|
97
|
+
- `na next marked2` (show next actions from another directory you've previously used na on)
|
98
98
|
|
99
99
|
```
|
100
100
|
@cli(bundle exec bin/na help next)
|