na 1.1.21 → 1.1.23
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|