na 1.2.38 → 1.2.39
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 +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
|