na 1.2.29 → 1.2.30
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 +22 -0
- data/Gemfile.lock +1 -1
- data/README.md +28 -20
- data/bin/commands/add.rb +167 -164
- data/bin/commands/archive.rb +54 -51
- data/bin/commands/changes.rb +18 -15
- data/bin/commands/complete.rb +50 -47
- data/bin/commands/completed.rb +99 -0
- data/bin/commands/edit.rb +37 -34
- data/bin/commands/find.rb +109 -106
- data/bin/commands/init.rb +22 -19
- data/bin/commands/next.rb +135 -131
- data/bin/commands/projects.rb +27 -24
- data/bin/commands/prompt.rb +41 -38
- data/bin/commands/saved.rb +42 -34
- data/bin/commands/tagged.rb +137 -134
- data/bin/commands/todos.rb +23 -20
- data/bin/commands/update.rb +215 -207
- data/bin/na +7 -1166
- data/lib/na/array.rb +13 -0
- data/lib/na/help_monkey_patch.rb +32 -0
- data/lib/na/next_action.rb +3 -2
- data/lib/na/pager.rb +94 -0
- data/lib/na/string.rb +10 -0
- data/lib/na/version.rb +1 -1
- data/lib/na.rb +2 -0
- metadata +7 -3
data/bin/commands/update.rb
CHANGED
|
@@ -1,227 +1,235 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
tokens.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
3
|
+
class App
|
|
4
|
+
extend GLI::App
|
|
5
|
+
desc 'Update an existing action'
|
|
6
|
+
long_desc 'Provides an easy way to complete, prioritize, and tag existing actions.
|
|
7
|
+
|
|
8
|
+
If multiple todo files are found in the current directory, a menu will
|
|
9
|
+
allow you to pick which file to act on.'
|
|
10
|
+
arg_name 'ACTION'
|
|
11
|
+
command %i[update] do |c|
|
|
12
|
+
c.example 'na update --remove na "An existing task"',
|
|
13
|
+
desc: 'Find "An existing task" action and remove the @na tag from it'
|
|
14
|
+
c.example 'na update --tag waiting "A bug I need to fix" -p 4 -n',
|
|
15
|
+
desc: 'Find "A bug..." action, add @waiting, add/update @priority(4), and prompt for an additional note'
|
|
16
|
+
c.example 'na update --archive My cool action',
|
|
17
|
+
desc: 'Add @done to "My cool action" and immediately move to Archive'
|
|
18
|
+
|
|
19
|
+
c.desc 'Prompt for additional notes. Input will be appended to any existing note.
|
|
20
|
+
If STDIN input (piped) is detected, it will be used as a note.'
|
|
21
|
+
c.switch %i[n note], negatable: false
|
|
22
|
+
|
|
23
|
+
c.desc 'Overwrite note instead of appending'
|
|
24
|
+
c.switch %i[o overwrite], negatable: false
|
|
25
|
+
|
|
26
|
+
c.desc 'Add/change a priority level 1-5'
|
|
27
|
+
c.arg_name 'PRIO'
|
|
28
|
+
c.flag %i[p priority], must_match: /[1-5]/, type: :integer, default_value: 0
|
|
29
|
+
|
|
30
|
+
c.desc 'When moving task, add at [s]tart or [e]nd of target project'
|
|
31
|
+
c.arg_name 'POSITION'
|
|
32
|
+
c.flag %i[at], must_match: /^[sbea].*?$/i
|
|
33
|
+
|
|
34
|
+
c.desc 'Move action to specific project'
|
|
35
|
+
c.arg_name 'PROJECT'
|
|
36
|
+
c.flag %i[to project proj]
|
|
37
|
+
|
|
38
|
+
c.desc 'Use a known todo file, partial matches allowed'
|
|
39
|
+
c.arg_name 'TODO_FILE'
|
|
40
|
+
c.flag %i[in todo]
|
|
41
|
+
|
|
42
|
+
c.desc 'Include @done actions'
|
|
43
|
+
c.switch %i[done]
|
|
44
|
+
|
|
45
|
+
c.desc 'Add a tag to the action, @tag(values) allowed'
|
|
46
|
+
c.arg_name 'TAG'
|
|
47
|
+
c.flag %i[t tag], multiple: true
|
|
48
|
+
|
|
49
|
+
c.desc 'Remove a tag to the action'
|
|
50
|
+
c.arg_name 'TAG'
|
|
51
|
+
c.flag %i[r remove], multiple: true
|
|
52
|
+
|
|
53
|
+
c.desc 'Add a @done tag to action'
|
|
54
|
+
c.switch %i[f finish], negatable: false
|
|
55
|
+
|
|
56
|
+
c.desc 'Add a @done tag to action and move to Archive'
|
|
57
|
+
c.switch %i[a archive], negatable: false
|
|
58
|
+
|
|
59
|
+
c.desc 'Remove @done tag from action'
|
|
60
|
+
c.switch %i[restore], negatable: false
|
|
61
|
+
|
|
62
|
+
c.desc 'Delete an action'
|
|
63
|
+
c.switch %i[delete], negatable: false
|
|
64
|
+
|
|
65
|
+
c.desc 'Specify the file to search for the task'
|
|
66
|
+
c.arg_name 'PATH'
|
|
67
|
+
c.flag %i[file]
|
|
68
|
+
|
|
69
|
+
c.desc 'Search for files X directories deep'
|
|
70
|
+
c.arg_name 'DEPTH'
|
|
71
|
+
c.flag %i[d depth], must_match: /^[1-9]$/, type: :integer, default_value: 1
|
|
72
|
+
|
|
73
|
+
c.desc 'Match actions containing tag. Allows value comparisons'
|
|
74
|
+
c.arg_name 'TAG'
|
|
75
|
+
c.flag %i[tagged], multiple: true
|
|
76
|
+
|
|
77
|
+
c.desc 'Act on all matches immediately (no menu)'
|
|
78
|
+
c.switch %i[all], negatable: false
|
|
79
|
+
|
|
80
|
+
c.desc 'Interpret search pattern as regular expression'
|
|
81
|
+
c.switch %i[e regex], negatable: false
|
|
82
|
+
|
|
83
|
+
c.desc 'Match pattern exactly'
|
|
84
|
+
c.switch %i[x exact], negatable: false
|
|
85
|
+
|
|
86
|
+
c.action do |global_options, options, args|
|
|
87
|
+
reader = TTY::Reader.new
|
|
88
|
+
append = options[:at] ? options[:at] =~ /^[ae]/i : global_options[:add_at] =~ /^[ae]/i
|
|
89
|
+
|
|
90
|
+
options[:done] = true if options[:restore] || options[:remove] =~ /^done/
|
|
91
|
+
|
|
92
|
+
action = if args.count.positive?
|
|
93
|
+
args.join(' ').strip
|
|
94
|
+
elsif $stdin.isatty && TTY::Which.exist?('gum') && options[:tagged].empty?
|
|
95
|
+
options = [
|
|
96
|
+
%(--placeholder "Enter a task to search for"),
|
|
97
|
+
'--char-limit=500',
|
|
98
|
+
"--width=#{TTY::Screen.columns}"
|
|
99
|
+
]
|
|
100
|
+
`gum input #{options.join(' ')}`.strip
|
|
101
|
+
elsif $stdin.isatty && options[:tagged].empty?
|
|
102
|
+
puts NA::Color.template('{bm}Enter search string:{x}')
|
|
103
|
+
reader.read_line(NA::Color.template('{by}> {bw}')).strip
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
if action
|
|
107
|
+
tokens = nil
|
|
108
|
+
if options[:exact]
|
|
109
|
+
tokens = action
|
|
110
|
+
elsif options[:regex]
|
|
111
|
+
tokens = Regexp.new(action, Regexp::IGNORECASE)
|
|
112
|
+
else
|
|
113
|
+
tokens = []
|
|
114
|
+
all_req = action !~ /[+!\-]/ && !options[:or]
|
|
115
|
+
|
|
116
|
+
action.split(/ /).each do |arg|
|
|
117
|
+
m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
|
118
|
+
tokens.push({
|
|
119
|
+
token: m['tok'],
|
|
120
|
+
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
|
121
|
+
negate: !m['req'].nil? && m['req'] =~ /[!\-]/
|
|
122
|
+
})
|
|
123
|
+
end
|
|
116
124
|
end
|
|
117
125
|
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
if (action.nil? || action.empty?) && options[:tagged].empty?
|
|
121
|
-
puts 'Empty input, cancelled'
|
|
122
|
-
Process.exit 1
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
all_req = options[:tagged].join(' ') !~ /[+!\-]/ && !options[:or]
|
|
126
|
-
tags = []
|
|
127
|
-
options[:tagged].join(',').split(/ *, */).each do |arg|
|
|
128
|
-
m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
|
|
129
|
-
|
|
130
|
-
tags.push({
|
|
131
|
-
tag: m['tag'].wildcard_to_rx,
|
|
132
|
-
comp: m['op'],
|
|
133
|
-
value: m['val'],
|
|
134
|
-
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
|
135
|
-
negate: !m['req'].nil? && m['req'] =~ /[!\-]/
|
|
136
|
-
})
|
|
137
|
-
end
|
|
138
126
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
stdin_note = NA.stdin ? NA.stdin.split("\n") : []
|
|
144
|
-
|
|
145
|
-
line_note = if options[:note] && $stdin.isatty
|
|
146
|
-
puts stdin_note unless stdin_note.nil?
|
|
147
|
-
if TTY::Which.exist?('gum')
|
|
148
|
-
args = ['--placeholder "Enter a note, CTRL-d to save"']
|
|
149
|
-
args << '--char-limit 0'
|
|
150
|
-
args << '--width $(tput cols)'
|
|
151
|
-
`gum write #{args.join(' ')}`.strip.split("\n")
|
|
152
|
-
else
|
|
153
|
-
puts NA::Color.template('{bm}Enter a note, {bw}CTRL-d{bm} to end editing{bw}')
|
|
154
|
-
reader.read_multiline
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
note = stdin_note.empty? ? [] : stdin_note
|
|
159
|
-
note.concat(line_note) unless line_note.nil? || line_note.empty?
|
|
127
|
+
if (action.nil? || action.empty?) && options[:tagged].empty?
|
|
128
|
+
puts 'Empty input, cancelled'
|
|
129
|
+
Process.exit 1
|
|
130
|
+
end
|
|
160
131
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
else
|
|
166
|
-
nil
|
|
167
|
-
end
|
|
132
|
+
all_req = options[:tagged].join(' ') !~ /[+!\-]/ && !options[:or]
|
|
133
|
+
tags = []
|
|
134
|
+
options[:tagged].join(',').split(/ *, */).each do |arg|
|
|
135
|
+
m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
|
|
168
136
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
targets = [file]
|
|
174
|
-
elsif options[:todo]
|
|
175
|
-
todo = []
|
|
176
|
-
options[:todo].split(/ *, */).each do |a|
|
|
177
|
-
m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
|
178
|
-
todo.push({
|
|
179
|
-
token: m['tok'],
|
|
137
|
+
tags.push({
|
|
138
|
+
tag: m['tag'].wildcard_to_rx,
|
|
139
|
+
comp: m['op'],
|
|
140
|
+
value: m['val'],
|
|
180
141
|
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
|
181
142
|
negate: !m['req'].nil? && m['req'] =~ /[!\-]/
|
|
182
143
|
})
|
|
183
144
|
end
|
|
184
|
-
dirs = NA.match_working_dir(todo)
|
|
185
145
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
146
|
+
priority = options[:priority].to_i if options[:priority]&.to_i&.positive?
|
|
147
|
+
add_tags = options[:tag] ? options[:tag].map { |t| t.sub(/^@/, '').wildcard_to_rx } : []
|
|
148
|
+
remove_tags = options[:remove] ? options[:remove].map { |t| t.sub(/^@/, '').wildcard_to_rx } : []
|
|
149
|
+
|
|
150
|
+
stdin_note = NA.stdin ? NA.stdin.split("\n") : []
|
|
151
|
+
|
|
152
|
+
line_note = if options[:note] && $stdin.isatty
|
|
153
|
+
puts stdin_note unless stdin_note.nil?
|
|
154
|
+
if TTY::Which.exist?('gum')
|
|
155
|
+
args = ['--placeholder "Enter a note, CTRL-d to save"']
|
|
156
|
+
args << '--char-limit 0'
|
|
157
|
+
args << '--width $(tput cols)'
|
|
158
|
+
`gum write #{args.join(' ')}`.strip.split("\n")
|
|
159
|
+
else
|
|
160
|
+
puts NA::Color.template('{bm}Enter a note, {bw}CTRL-d{bm} to end editing{bw}')
|
|
161
|
+
reader.read_multiline
|
|
162
|
+
end
|
|
163
|
+
end
|
|
193
164
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
165
|
+
note = stdin_note.empty? ? [] : stdin_note
|
|
166
|
+
note.concat(line_note) unless line_note.nil? || line_note.empty?
|
|
167
|
+
|
|
168
|
+
target_proj = if options[:project]
|
|
169
|
+
options[:project]
|
|
170
|
+
elsif NA.cwd_is == :project
|
|
171
|
+
NA.cwd
|
|
172
|
+
else
|
|
173
|
+
nil
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
if options[:file]
|
|
177
|
+
file = File.expand_path(options[:file])
|
|
178
|
+
NA.notify('{r}File not found', exit_code: 1) unless File.exist?(file)
|
|
179
|
+
|
|
180
|
+
targets = [file]
|
|
181
|
+
elsif options[:todo]
|
|
182
|
+
todo = []
|
|
183
|
+
options[:todo].split(/ *, */).each do |a|
|
|
184
|
+
m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
|
185
|
+
todo.push({
|
|
186
|
+
token: m['tok'],
|
|
187
|
+
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
|
188
|
+
negate: !m['req'].nil? && m['req'] =~ /[!\-]/
|
|
189
|
+
})
|
|
190
|
+
end
|
|
191
|
+
dirs = NA.match_working_dir(todo)
|
|
198
192
|
|
|
199
|
-
|
|
200
|
-
|
|
193
|
+
if dirs.count == 1
|
|
194
|
+
targets = [dirs[0]]
|
|
195
|
+
elsif dirs.count.positive?
|
|
196
|
+
targets = NA.select_file(dirs, multiple: true)
|
|
197
|
+
NA.notify('{r}Cancelled', exit_code: 1) unless targets && targets.count.positive?
|
|
198
|
+
else
|
|
199
|
+
NA.notify('{r}Todo not found', exit_code: 1) unless targets && targets.count.positive?
|
|
201
200
|
|
|
202
|
-
|
|
201
|
+
end
|
|
202
|
+
else
|
|
203
|
+
files = NA.find_files(depth: options[:depth])
|
|
204
|
+
NA.notify('{r}No todo file found', exit_code: 1) if files.count.zero?
|
|
203
205
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
options[:project] = 'Archive'
|
|
207
|
-
end
|
|
206
|
+
targets = files.count > 1 ? NA.select_file(files, multiple: true) : [files[0]]
|
|
207
|
+
NA.notify('{r}Cancelled{x}', exit_code: 1) unless files.count.positive?
|
|
208
208
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
if options[:archive]
|
|
212
|
+
options[:finish] = true
|
|
213
|
+
options[:project] = 'Archive'
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
NA.notify('{r}No search terms provided', exit_code: 1) if tokens.nil? && options[:tagged].empty?
|
|
217
|
+
|
|
218
|
+
targets.each do |target|
|
|
219
|
+
NA.update_action(target, tokens,
|
|
220
|
+
priority: priority,
|
|
221
|
+
add_tag: add_tags,
|
|
222
|
+
remove_tag: remove_tags,
|
|
223
|
+
finish: options[:finish],
|
|
224
|
+
project: target_proj,
|
|
225
|
+
delete: options[:delete],
|
|
226
|
+
note: note,
|
|
227
|
+
overwrite: options[:overwrite],
|
|
228
|
+
tagged: tags,
|
|
229
|
+
all: options[:all],
|
|
230
|
+
done: options[:done],
|
|
231
|
+
append: append)
|
|
232
|
+
end
|
|
225
233
|
end
|
|
226
234
|
end
|
|
227
235
|
end
|