na 1.2.38 → 1.2.39
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/Gemfile.lock +1 -1
- data/README.md +5 -2
- data/bin/commands/add.rb +2 -5
- data/bin/commands/edit.rb +3 -11
- data/bin/commands/find.rb +6 -4
- data/bin/commands/next.rb +10 -1
- data/bin/commands/saved.rb +19 -7
- data/bin/commands/tagged.rb +1 -1
- data/bin/commands/update.rb +3 -12
- data/lib/na/action.rb +8 -1
- data/lib/na/colors.rb +1 -1
- data/lib/na/next_action.rb +48 -14
- data/lib/na/string.rb +30 -4
- data/lib/na/version.rb +1 -1
- data/src/_README.md +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e55b998c33447bd876e7c26c1fcf7f136a5f3de847715f8565882b0df4d078f2
|
4
|
+
data.tar.gz: 8ce2a06c64b3e45291c185ffe9a38f9a6cb0fa3dc4b0eaf9f2de9a38bc295701
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cca3d6c0f98d5c30fdc5c22f6a10958b6888e08f2c22b92d13d698152465f756e491258fd4290f42f85062b7718068deabc43a07e4ea92891370f0ba35024a00
|
7
|
+
data.tar.gz: e20114aef91f0c9aa2edb15b95d335d86afa5aee03b6a4e214b2fdca7fa6acbc7b178e6f00d4348dd3329d6f88122f044591271f2886b732f8d046673e628403
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
### 1.2.39
|
2
|
+
|
3
|
+
2023-09-06 04:25
|
4
|
+
|
5
|
+
#### NEW
|
6
|
+
|
7
|
+
- Add `--save NAME` to `na next` to save more complex queries and run with `na saved NAME` (or just `na NAME`)
|
8
|
+
- `na saved --select` flag to allow interactive selection of search(es)
|
9
|
+
|
10
|
+
#### IMPROVED
|
11
|
+
|
12
|
+
- Allow `na saved --delete` to handle multiple arguments
|
13
|
+
- Allow wildcards when deleting saved searches
|
14
|
+
- Refactor request for input, no change to user experience
|
15
|
+
- Refined wildcard (?*) handling
|
16
|
+
- When displaying actions wider than the screen, wrap at words and indent 2 spaces from start of action (after prefix)
|
17
|
+
|
1
18
|
### 1.2.38
|
2
19
|
|
3
20
|
2023-09-03 11:25
|
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.2.
|
12
|
+
The current version of `na` is 1.2.39
|
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.
|
@@ -77,7 +77,7 @@ SYNOPSIS
|
|
77
77
|
na [global options] command [command options] [arguments...]
|
78
78
|
|
79
79
|
VERSION
|
80
|
-
1.2.
|
80
|
+
1.2.39
|
81
81
|
|
82
82
|
GLOBAL OPTIONS
|
83
83
|
-a, --add - Add a next action (deprecated, for backwards compatibility)
|
@@ -292,6 +292,7 @@ COMMAND OPTIONS
|
|
292
292
|
--omnifocus - Output actions nested by file and project
|
293
293
|
--proj, --project=PROJECT[/SUBPROJECT] - Show actions from a specific project (default: none)
|
294
294
|
--regex - Search query is regular expression
|
295
|
+
--save=TITLE - Save this search for future use (default: none)
|
295
296
|
--search, --find, --grep=QUERY - Filter results using search terms (may be used more than once, default: none)
|
296
297
|
-t, --tag=TAG - Alternate tag to search for (default: none)
|
297
298
|
--tagged=TAG - Match actions containing tag. Allows value comparisons (may be used more than once, default: none)
|
@@ -353,6 +354,7 @@ DESCRIPTION
|
|
353
354
|
COMMAND OPTIONS
|
354
355
|
-d, --delete - Delete the specified search definition
|
355
356
|
-e, --edit - Open the saved search file in $EDITOR
|
357
|
+
-s, --select - Interactively select a saved search to run
|
356
358
|
|
357
359
|
EXAMPLES
|
358
360
|
|
@@ -403,6 +405,7 @@ COMMAND OPTIONS
|
|
403
405
|
--omnifocus - Output actions nested by file and project
|
404
406
|
--proj, --project=PROJECT[/SUBPROJECT] - Show actions from a specific project (default: none)
|
405
407
|
--regex - Search query is regular expression
|
408
|
+
--save=TITLE - Save this search for future use (default: none)
|
406
409
|
--search, --find, --grep=QUERY - Filter results using search terms (may be used more than once, default: none)
|
407
410
|
-t, --tag=TAG - Alternate tag to search for (default: none)
|
408
411
|
--tagged=TAG - Match actions containing tag. Allows value comparisons (may be used more than once, default: none)
|
data/bin/commands/add.rb
CHANGED
@@ -126,11 +126,8 @@ class App
|
|
126
126
|
|
127
127
|
action = if args.count.positive?
|
128
128
|
args.join(' ').strip
|
129
|
-
|
130
|
-
|
131
|
-
elsif $stdin.isatty
|
132
|
-
NA.notify("#{NA.theme[:prompt]}Enter task:")
|
133
|
-
reader.read_line(NA::Color.template("#{NA.theme[:warning]}> #{NA.theme[:action]}")).strip
|
129
|
+
else
|
130
|
+
NA.request_input(options, prompt: 'Enter a task')
|
134
131
|
end
|
135
132
|
|
136
133
|
if action.nil? || action.empty?
|
data/bin/commands/edit.rb
CHANGED
@@ -43,16 +43,8 @@ class App
|
|
43
43
|
options[:edit] = true
|
44
44
|
action = if args.count.positive?
|
45
45
|
args.join(' ').strip
|
46
|
-
|
47
|
-
|
48
|
-
%(--placeholder "Enter a task to search for"),
|
49
|
-
'--char-limit=500',
|
50
|
-
"--width=#{TTY::Screen.columns}"
|
51
|
-
]
|
52
|
-
`gum input #{opts.join(' ')}`.strip
|
53
|
-
elsif $stdin.isatty && options[:tagged].empty?
|
54
|
-
NA.notify("#{NA.theme[:prompt]}Enter search string:")
|
55
|
-
reader.read_line(NA::Color.template("#{NA.theme[:warning]}> #{NA.theme[:action]}")).strip
|
46
|
+
else
|
47
|
+
NA.request_input(options, prompt: 'Enter a task to search for')
|
56
48
|
end
|
57
49
|
|
58
50
|
NA.notify("#{NA.theme[:error]}Empty input", exit_code: 1) if (action.nil? || action.empty?) && options[:tagged].empty?
|
@@ -85,7 +77,7 @@ class App
|
|
85
77
|
all_req = options[:tagged].join(' ') !~ /[+!-]/ && !options[:or]
|
86
78
|
tags = []
|
87
79
|
options[:tagged].join(',').split(/ *, */).each do |arg|
|
88
|
-
m = arg.match(/^(?<req>[+\-!])?(?<tag>[^
|
80
|
+
m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^~]+?)(?:(?<op>[=<>~]{1,2}|[*$\^]=)(?<val>.*?))?$/)
|
89
81
|
|
90
82
|
tags.push({
|
91
83
|
tag: m['tag'].wildcard_to_rx,
|
data/bin/commands/find.rb
CHANGED
@@ -61,7 +61,8 @@ class App
|
|
61
61
|
|
62
62
|
if options[:save]
|
63
63
|
title = options[:save].gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
|
64
|
-
|
64
|
+
cmd = NA.command_line.join(' ').sub(/ --save[= ]*\S+/, '').split(' ').map { |t| %("#{t}") }.join(' ')
|
65
|
+
NA.save_search(title, cmd)
|
65
66
|
end
|
66
67
|
|
67
68
|
depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
|
@@ -95,7 +96,7 @@ class App
|
|
95
96
|
all_req = options[:tagged].join(' ') !~ /[+!-]/ && !options[:or]
|
96
97
|
tags = []
|
97
98
|
options[:tagged].join(',').split(/ *, */).each do |arg|
|
98
|
-
m = arg.match(/^(?<req>[+!-])?(?<tag>[^
|
99
|
+
m = arg.match(/^(?<req>[+!-])?(?<tag>[^ =<>$~\^]+?) *(?:(?<op>[=<>~]{1,2}|[*$\^]=) *(?<val>.*?))?$/)
|
99
100
|
|
100
101
|
tags.push({
|
101
102
|
tag: m['tag'].wildcard_to_rx,
|
@@ -121,8 +122,9 @@ class App
|
|
121
122
|
|
122
123
|
search.split(/ /).each do |arg|
|
123
124
|
m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
125
|
+
|
124
126
|
tokens.push({
|
125
|
-
token:
|
127
|
+
token: m['tok'],
|
126
128
|
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
127
129
|
negate: !m['req'].nil? && m['req'] =~ /[!-]/ ? true : false
|
128
130
|
})
|
@@ -155,7 +157,7 @@ class App
|
|
155
157
|
})
|
156
158
|
|
157
159
|
regexes = if tokens.is_a?(Array)
|
158
|
-
tokens.delete_if { |token| token[:negate] }.map { |token| token[:token] }
|
160
|
+
tokens.delete_if { |token| token[:negate] }.map { |token| token[:token].wildcard_to_rx }
|
159
161
|
else
|
160
162
|
[tokens]
|
161
163
|
end
|
data/bin/commands/next.rb
CHANGED
@@ -57,6 +57,10 @@ class App
|
|
57
57
|
c.desc 'Output actions nested by file and project'
|
58
58
|
c.switch %i[omnifocus], negatable: false
|
59
59
|
|
60
|
+
c.desc 'Save this search for future use'
|
61
|
+
c.arg_name 'TITLE'
|
62
|
+
c.flag %i[save]
|
63
|
+
|
60
64
|
c.action do |global_options, options, args|
|
61
65
|
if global_options[:add]
|
62
66
|
cmd = ['add']
|
@@ -67,6 +71,11 @@ class App
|
|
67
71
|
exit run(cmd)
|
68
72
|
end
|
69
73
|
|
74
|
+
if options[:save]
|
75
|
+
title = options[:save].gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
|
76
|
+
NA.save_search(title, "#{NA.command_line.join(' ').sub(/ --save[= ]*\S+/, '').split(' ').map { |t| %("#{t}") }.join(' ')}")
|
77
|
+
end
|
78
|
+
|
70
79
|
options[:nest] = true if options[:omnifocus]
|
71
80
|
|
72
81
|
depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
|
@@ -100,7 +109,7 @@ class App
|
|
100
109
|
all_req = options[:tagged].join(' ') !~ /(?<=[, ])[+!-]/ && !options[:or]
|
101
110
|
tags = []
|
102
111
|
options[:tagged].join(',').split(/ *, */).each do |arg|
|
103
|
-
m = arg.match(/^(?<req>[+!-])?(?<tag>[^
|
112
|
+
m = arg.match(/^(?<req>[+!-])?(?<tag>[^ =<>$~\^]+?) *(?:(?<op>[=<>~]{1,2}|[*$\^]=) *(?<val>.*?))?$/)
|
104
113
|
|
105
114
|
tags.push({
|
106
115
|
tag: m['tag'].wildcard_to_rx,
|
data/bin/commands/saved.rb
CHANGED
@@ -20,25 +20,37 @@ class App
|
|
20
20
|
c.desc 'Delete the specified search definition'
|
21
21
|
c.switch %i[d delete], negatable: false
|
22
22
|
|
23
|
+
c.desc 'Interactively select a saved search to run'
|
24
|
+
c.switch %i[s select], negatable: false
|
25
|
+
|
23
26
|
c.action do |_global_options, options, args|
|
24
27
|
NA.edit_searches if options[:edit]
|
25
28
|
|
26
|
-
if args.empty?
|
29
|
+
if args.empty? && !options[:select]
|
27
30
|
searches = NA.load_searches
|
28
31
|
NA.notify("#{NA.theme[:success]}Saved searches stored in #{NA.database_path(file: 'saved_searches.yml').highlight_filename}")
|
29
32
|
NA.notify(searches.map { |k, v| "#{NA.theme[:filename]}#{k}: #{NA.theme[:values]}#{v}" }.join("\n"))
|
30
33
|
else
|
31
|
-
args.
|
34
|
+
NA.delete_search(args.join(',').split(/[ ,]/)) if options[:delete]
|
35
|
+
|
36
|
+
if options[:select]
|
32
37
|
searches = NA.load_searches
|
38
|
+
res = NA.choose_from(searches.map { |k, v| "#{NA.theme[:filename]}#{k} #{NA.theme[:value]}(#{v})" }, multiple: true)
|
39
|
+
NA.notify("#{NA.theme[:error]}Nothing selected", exit_code: 0) if res&.empty?
|
40
|
+
args = res.map { |r| r.match(/(\S+)(?= \()/)[1] }
|
41
|
+
end
|
33
42
|
|
34
|
-
|
43
|
+
args.each do |arg|
|
44
|
+
searches = NA.load_searches
|
35
45
|
|
36
|
-
keys = searches.keys.delete_if { |k| k !~ /#{arg}/ }
|
46
|
+
keys = searches.keys.delete_if { |k| k !~ /#{arg.wildcard_to_rx}/ }
|
37
47
|
NA.notify("#{NA.theme[:error]}Search #{arg} not found", exit_code: 1) if keys.empty?
|
38
48
|
|
39
|
-
|
40
|
-
|
41
|
-
|
49
|
+
keys.each do |key|
|
50
|
+
NA.notify("#{NA.theme[:prompt]}Saved search #{NA.theme[:filename]}#{key}#{NA.theme[:warning]}:")
|
51
|
+
cmd = Shellwords.shellsplit(searches[key])
|
52
|
+
run(cmd)
|
53
|
+
end
|
42
54
|
end
|
43
55
|
end
|
44
56
|
end
|
data/bin/commands/tagged.rb
CHANGED
@@ -81,7 +81,7 @@ class App
|
|
81
81
|
|
82
82
|
all_req = args.join(' ') !~ /(?<=[, ])[+!-]/ && !options[:or]
|
83
83
|
args.join(',').split(/ *, */).each do |arg|
|
84
|
-
m = arg.match(/^(?<req>[+!-])?(?<tag>[^
|
84
|
+
m = arg.match(/^(?<req>[+!-])?(?<tag>[^ =<>$~\^]+?) *(?:(?<op>[=<>~]{1,2}|[*$\^]=) *(?<val>.*?))?$/)
|
85
85
|
next if m.nil?
|
86
86
|
|
87
87
|
tags.push({
|
data/bin/commands/update.rb
CHANGED
@@ -103,18 +103,9 @@ class App
|
|
103
103
|
|
104
104
|
action = if args.count.positive?
|
105
105
|
args.join(' ').strip
|
106
|
-
|
107
|
-
|
108
|
-
%(--placeholder "Enter a task to search for"),
|
109
|
-
'--char-limit=500',
|
110
|
-
"--width=#{TTY::Screen.columns}"
|
111
|
-
]
|
112
|
-
`gum input #{opts.join(' ')}`.strip
|
113
|
-
elsif $stdin.isatty && options[:tagged].empty?
|
114
|
-
NA.notify("#{NA.theme[:prompt]}Enter search string:")
|
115
|
-
reader.read_line(NA::Color.template("#{NA.theme[:filename]}> #{NA.theme[:action]}")).strip
|
106
|
+
else
|
107
|
+
NA.request_input(options, prompt: 'Enter a task to search for')
|
116
108
|
end
|
117
|
-
|
118
109
|
if action
|
119
110
|
tokens = nil
|
120
111
|
if options[:exact]
|
@@ -143,7 +134,7 @@ class App
|
|
143
134
|
all_req = options[:tagged].join(' ') !~ /[+!-]/ && !options[:or]
|
144
135
|
tags = []
|
145
136
|
options[:tagged].join(',').split(/ *, */).each do |arg|
|
146
|
-
m = arg.match(/^(?<req>[+!-])?(?<tag>[^
|
137
|
+
m = arg.match(/^(?<req>[+!-])?(?<tag>[^ =<>$~\^]+?) *(?:(?<op>[=<>~]{1,2}|[*$\^]=) *(?<val>.*?))?$/)
|
147
138
|
|
148
139
|
tags.push({
|
149
140
|
tag: m['tag'].wildcard_to_rx,
|
data/lib/na/action.rb
CHANGED
@@ -73,7 +73,7 @@ module NA
|
|
73
73
|
## highlight (searches)
|
74
74
|
## @param notes [Boolean] Include notes
|
75
75
|
##
|
76
|
-
def pretty(extension: 'taskpaper', template: {}, regexes: [], notes: false)
|
76
|
+
def pretty(extension: 'taskpaper', template: {}, regexes: [], notes: false, detect_width: true)
|
77
77
|
theme = NA::Theme.load_theme
|
78
78
|
template = theme.merge(template)
|
79
79
|
|
@@ -108,6 +108,13 @@ module NA
|
|
108
108
|
value: template[:values],
|
109
109
|
last_color: template[:action])
|
110
110
|
|
111
|
+
if detect_width
|
112
|
+
width = TTY::Screen.columns
|
113
|
+
prefix = NA::Color.uncolor(pretty(template: { output: template[:output].sub(/%action/, '') }, detect_width: false))
|
114
|
+
indent = prefix.length
|
115
|
+
action = action.wrap(width, indent)
|
116
|
+
end
|
117
|
+
|
111
118
|
# Replace variables in template string and output colorized
|
112
119
|
NA::Color.template(template[:output].gsub(/%filename/, filename)
|
113
120
|
.gsub(/%project/, project)
|
data/lib/na/colors.rb
CHANGED
@@ -334,7 +334,7 @@ module NA
|
|
334
334
|
|
335
335
|
# Regular expression that is used to scan for ANSI-sequences while
|
336
336
|
# uncoloring strings.
|
337
|
-
COLORED_REGEXP = /\e\[(?:(?:[349]|10)[0-
|
337
|
+
COLORED_REGEXP = /\e\[(?:(?:(?:[349]|10)[0-9]|[0-9])?;?)+m/
|
338
338
|
|
339
339
|
# Returns an uncolored version of the string, that is all
|
340
340
|
# ANSI-sequences are stripped from the string.
|
data/lib/na/next_action.rb
CHANGED
@@ -197,12 +197,13 @@ module NA
|
|
197
197
|
end
|
198
198
|
|
199
199
|
if new_path.join('') =~ /Archive/i
|
200
|
-
line = todo.projects.last
|
200
|
+
line = todo.projects.last&.last_line || 0
|
201
201
|
content = content.split(/\n/).insert(line, input.join("\n")).join("\n")
|
202
202
|
else
|
203
203
|
split = content.split(/\n/)
|
204
|
-
|
205
|
-
|
204
|
+
line = todo.projects.first&.line || 0
|
205
|
+
before = split.slice(0, line).join("\n")
|
206
|
+
after = split.slice(line, split.count - 0).join("\n")
|
206
207
|
content = "#{before}\n#{input.join("\n")}\n#{after}"
|
207
208
|
end
|
208
209
|
|
@@ -755,10 +756,14 @@ module NA
|
|
755
756
|
file = database_path(file: 'saved_searches.yml')
|
756
757
|
NA.notify("#{NA.theme[:error]}No search definitions file found", exit_code: 1) unless File.exist?(file)
|
757
758
|
|
759
|
+
strings = [strings] unless strings.is_a? Array
|
760
|
+
|
758
761
|
searches = YAML.safe_load(file.read_file)
|
759
|
-
keys = searches.keys.delete_if { |k| k !~ /(#{strings.join('|')})/ }
|
762
|
+
keys = searches.keys.delete_if { |k| k !~ /(#{strings.map(&:wildcard_to_rx).join('|')})/ }
|
763
|
+
|
764
|
+
NA.notify("#{NA.theme[:error]}No search named #{strings.join(', ')} found", exit_code: 1) if keys.empty?
|
760
765
|
|
761
|
-
res = yn(NA::Color.template(%(#{NA.theme[:warning]}Remove #{keys.count > 1 ? 'searches' : 'search'} #{NA.theme[:
|
766
|
+
res = yn(NA::Color.template(%(#{NA.theme[:warning]}Remove #{keys.count > 1 ? 'searches' : 'search'} #{NA.theme[:filename]}"#{keys.join(', ')}"{x})),
|
762
767
|
default: false)
|
763
768
|
|
764
769
|
NA.notify("#{NA.theme[:error]}Cancelled", exit_code: 1) unless res
|
@@ -796,7 +801,23 @@ module NA
|
|
796
801
|
NA.notify("#{NA.theme[:warning]}Backup file created at #{backup.highlight_filename}", debug: true)
|
797
802
|
end
|
798
803
|
|
799
|
-
|
804
|
+
##
|
805
|
+
## Request terminal input from user, readline style
|
806
|
+
##
|
807
|
+
## @param options [Hash] The options
|
808
|
+
## @param prompt [String] The prompt
|
809
|
+
##
|
810
|
+
def request_input(options, prompt: 'Enter text')
|
811
|
+
if $stdin.isatty && TTY::Which.exist?('gum') && options[:tagged].empty?
|
812
|
+
opts = [%(--placeholder "#{prompt}"),
|
813
|
+
'--char-limit=500',
|
814
|
+
"--width=#{TTY::Screen.columns}"]
|
815
|
+
`gum input #{opts.join(' ')}`.strip
|
816
|
+
elsif $stdin.isatty && options[:tagged].empty?
|
817
|
+
NA.notify("#{NA.theme[:prompt]}#{prompt}:")
|
818
|
+
reader.read_line(NA::Color.template("#{NA.theme[:filename]}> #{NA.theme[:action]}")).strip
|
819
|
+
end
|
820
|
+
end
|
800
821
|
|
801
822
|
##
|
802
823
|
## Generate a menu of options and allow user selection
|
@@ -822,30 +843,43 @@ module NA
|
|
822
843
|
header = "esc: cancel,#{multiple ? ' tab: multi-select, ctrl-a: select all,' : ''} return: confirm"
|
823
844
|
default_args << %(--header="#{header}")
|
824
845
|
default_args.concat(fzf_args)
|
825
|
-
|
846
|
+
options = NA::Color.uncolor(NA::Color.template(options.join("\n")))
|
847
|
+
`echo #{Shellwords.escape(options)}|#{TTY::Which.which('fzf')} #{default_args.join(' ')}`.strip
|
826
848
|
elsif TTY::Which.exist?('gum')
|
827
849
|
args = [
|
828
850
|
'--cursor.foreground="151"',
|
829
851
|
'--item.foreground=""'
|
830
852
|
]
|
831
853
|
args.push '--no-limit' if multiple
|
832
|
-
puts
|
833
|
-
|
854
|
+
puts NA::Color.template("#{NA.theme[:prompt]}#{prompt}{x}")
|
855
|
+
options = NA::Color.uncolor(NA::Color.template(options.join("\n")))
|
856
|
+
`echo #{Shellwords.escape(options)}|#{TTY::Which.which('gum')} choose #{args.join(' ')}`.strip
|
834
857
|
else
|
835
858
|
reader = TTY::Reader.new
|
836
859
|
puts
|
837
860
|
options.each.with_index do |f, i|
|
838
|
-
puts NA::Color.template(format("#{NA.theme[:prompt]}%<idx> 2d{xw}) #{NA.theme[:
|
861
|
+
puts NA::Color.template(format("#{NA.theme[:prompt]}%<idx> 2d{xw}) #{NA.theme[:filename]}%<action>s{x}\n", idx: i + 1, action: f))
|
839
862
|
end
|
840
863
|
result = reader.read_line(NA::Color.template("#{NA.theme[:prompt]}#{prompt}{x}")).strip
|
841
|
-
|
864
|
+
if multiple
|
865
|
+
mult_res = []
|
866
|
+
result = result.gsub(/,/, ' ').gsub(/ +/, ' ').split(/ /)
|
867
|
+
result.each do |r|
|
868
|
+
mult_res << options[r.to_i - 1] if r.to_i&.positive?
|
869
|
+
end
|
870
|
+
mult_res.join("\n")
|
871
|
+
else
|
872
|
+
result.to_i&.positive? ? options[result.to_i - 1] : nil
|
873
|
+
end
|
842
874
|
end
|
843
875
|
|
844
|
-
return false if res
|
845
|
-
|
846
|
-
multiple ? res.split(/\n/) : res
|
876
|
+
return false if res&.strip&.size&.zero?
|
877
|
+
pp NA::Color.uncolor(NA::Color.template(res))
|
878
|
+
multiple ? NA::Color.uncolor(NA::Color.template(res)).split(/\n/) : NA::Color.uncolor(NA::Color.template(res))
|
847
879
|
end
|
848
880
|
|
881
|
+
private
|
882
|
+
|
849
883
|
##
|
850
884
|
## macOS open command
|
851
885
|
##
|
data/lib/na/string.rb
CHANGED
@@ -112,8 +112,14 @@ class ::String
|
|
112
112
|
tag_color = NA::Color.template(color)
|
113
113
|
paren_color = NA::Color.template(parens)
|
114
114
|
value_color = NA::Color.template(value)
|
115
|
-
gsub(/(
|
116
|
-
|
115
|
+
gsub(/(?<pre>\s|m)(?<tag>@[^ ("']+)(?:(?<lparen>\()(?<val>.*?)(?<rparen>\)))?/) do
|
116
|
+
m = Regexp.last_match
|
117
|
+
if m['val']
|
118
|
+
"#{m['pre']}#{tag_color}#{m['tag']}#{paren_color}(#{value_color}#{m['val']}#{paren_color})#{last_color}"
|
119
|
+
else
|
120
|
+
"#{m['pre']}#{tag_color}#{m['tag']}#{last_color}"
|
121
|
+
end
|
122
|
+
end
|
117
123
|
end
|
118
124
|
|
119
125
|
##
|
@@ -131,8 +137,7 @@ class ::String
|
|
131
137
|
color = NA::Color.template(color.dup)
|
132
138
|
regexes.each do |rx|
|
133
139
|
next if rx.nil?
|
134
|
-
|
135
|
-
rx = Regexp.new(rx.wildcard_to_rx, Regexp::IGNORECASE) if rx.is_a?(String)
|
140
|
+
rx = Regexp.new(rx, Regexp::IGNORECASE) if rx.is_a?(String)
|
136
141
|
|
137
142
|
string.gsub!(rx) do
|
138
143
|
m = Regexp.last_match
|
@@ -143,6 +148,27 @@ class ::String
|
|
143
148
|
string
|
144
149
|
end
|
145
150
|
|
151
|
+
def wrap(width, indent)
|
152
|
+
output = []
|
153
|
+
line = []
|
154
|
+
length = indent
|
155
|
+
gsub!(/(@\S+)\((.*?)\)/) { "#{Regexp.last_match(1)}(#{Regexp.last_match(2).gsub(/ /, '†')})" }
|
156
|
+
|
157
|
+
split(' ').each do |word|
|
158
|
+
uncolored = NA::Color.uncolor(word)
|
159
|
+
if (length + uncolored.length + 1) < width
|
160
|
+
line << word
|
161
|
+
length += uncolored.length + 1
|
162
|
+
else
|
163
|
+
output << line.join(' ')
|
164
|
+
line = [word]
|
165
|
+
length = indent + uncolored.length + 1
|
166
|
+
end
|
167
|
+
end
|
168
|
+
output << line.join(' ')
|
169
|
+
output.join("\n" + ' ' * (indent + 2)).gsub(/†/, ' ')
|
170
|
+
end
|
171
|
+
|
146
172
|
# Returns the last escape sequence from a string.
|
147
173
|
#
|
148
174
|
# @note Actually returns all escape codes, with the
|
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.2.
|
12
|
+
The current version of `na` is <!--VER-->1.2.38<!--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.2.
|
4
|
+
version: 1.2.39
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brett Terpstra
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-09-
|
11
|
+
date: 2023-09-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|