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.
@@ -1,227 +1,235 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- desc 'Update an existing action'
4
- long_desc 'Provides an easy way to complete, prioritize, and tag existing actions.
5
-
6
- If multiple todo files are found in the current directory, a menu will
7
- allow you to pick which file to act on.'
8
- arg_name 'ACTION'
9
- command %i[update] do |c|
10
- c.example 'na update --remove na "An existing task"',
11
- desc: 'Find "An existing task" action and remove the @na tag from it'
12
- c.example 'na update --tag waiting "A bug I need to fix" -p 4 -n',
13
- desc: 'Find "A bug..." action, add @waiting, add/update @priority(4), and prompt for an additional note'
14
- c.example 'na update --archive My cool action',
15
- desc: 'Add @done to "My cool action" and immediately move to Archive'
16
-
17
- c.desc 'Prompt for additional notes. Input will be appended to any existing note.
18
- If STDIN input (piped) is detected, it will be used as a note.'
19
- c.switch %i[n note], negatable: false
20
-
21
- c.desc 'Overwrite note instead of appending'
22
- c.switch %i[o overwrite], negatable: false
23
-
24
- c.desc 'Add/change a priority level 1-5'
25
- c.arg_name 'PRIO'
26
- c.flag %i[p priority], must_match: /[1-5]/, type: :integer, default_value: 0
27
-
28
- c.desc 'When moving task, add at [s]tart or [e]nd of target project'
29
- c.arg_name 'POSITION'
30
- c.flag %i[at], must_match: /^[sbea].*?$/i
31
-
32
- c.desc 'Move action to specific project'
33
- c.arg_name 'PROJECT'
34
- c.flag %i[to project proj]
35
-
36
- c.desc 'Use a known todo file, partial matches allowed'
37
- c.arg_name 'TODO_FILE'
38
- c.flag %i[in todo]
39
-
40
- c.desc 'Include @done actions'
41
- c.switch %i[done]
42
-
43
- c.desc 'Add a tag to the action, @tag(values) allowed'
44
- c.arg_name 'TAG'
45
- c.flag %i[t tag], multiple: true
46
-
47
- c.desc 'Remove a tag to the action'
48
- c.arg_name 'TAG'
49
- c.flag %i[r remove], multiple: true
50
-
51
- c.desc 'Add a @done tag to action'
52
- c.switch %i[f finish], negatable: false
53
-
54
- c.desc 'Add a @done tag to action and move to Archive'
55
- c.switch %i[a archive], negatable: false
56
-
57
- c.desc 'Delete an action'
58
- c.switch %i[delete], negatable: false
59
-
60
- c.desc 'Specify the file to search for the task'
61
- c.arg_name 'PATH'
62
- c.flag %i[file]
63
-
64
- c.desc 'Search for files X directories deep'
65
- c.arg_name 'DEPTH'
66
- c.flag %i[d depth], must_match: /^[1-9]$/, type: :integer, default_value: 1
67
-
68
- c.desc 'Match actions containing tag. Allows value comparisons'
69
- c.arg_name 'TAG'
70
- c.flag %i[tagged], multiple: true
71
-
72
- c.desc 'Act on all matches immediately (no menu)'
73
- c.switch %i[all], negatable: false
74
-
75
- c.desc 'Interpret search pattern as regular expression'
76
- c.switch %i[e regex], negatable: false
77
-
78
- c.desc 'Match pattern exactly'
79
- c.switch %i[x exact], negatable: false
80
-
81
- c.action do |global_options, options, args|
82
- reader = TTY::Reader.new
83
- append = options[:at] ? options[:at] =~ /^[ae]/i : global_options[:add_at] =~ /^[ae]/i
84
-
85
- action = if args.count.positive?
86
- args.join(' ').strip
87
- elsif $stdin.isatty && TTY::Which.exist?('gum') && options[:tagged].empty?
88
- options = [
89
- %(--placeholder "Enter a task to search for"),
90
- '--char-limit=500',
91
- "--width=#{TTY::Screen.columns}"
92
- ]
93
- `gum input #{options.join(' ')}`.strip
94
- elsif $stdin.isatty && options[:tagged].empty?
95
- puts NA::Color.template('{bm}Enter search string:{x}')
96
- reader.read_line(NA::Color.template('{by}> {bw}')).strip
97
- end
98
-
99
- if action
100
- tokens = nil
101
- if options[:exact]
102
- tokens = action
103
- elsif options[:regex]
104
- tokens = Regexp.new(action, Regexp::IGNORECASE)
105
- else
106
- tokens = []
107
- all_req = action !~ /[+!\-]/ && !options[:or]
108
-
109
- action.split(/ /).each do |arg|
110
- m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
111
- tokens.push({
112
- token: m['tok'],
113
- required: all_req || (!m['req'].nil? && m['req'] == '+'),
114
- negate: !m['req'].nil? && m['req'] =~ /[!\-]/
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
- priority = options[:priority].to_i if options[:priority]&.to_i&.positive?
140
- add_tags = options[:tag] ? options[:tag].map { |t| t.sub(/^@/, '').wildcard_to_rx } : []
141
- remove_tags = options[:remove] ? options[:remove].map { |t| t.sub(/^@/, '').wildcard_to_rx } : []
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
- target_proj = if options[:project]
162
- options[:project]
163
- elsif NA.cwd_is == :project
164
- NA.cwd
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
- if options[:file]
170
- file = File.expand_path(options[:file])
171
- NA.notify('{r}File not found', exit_code: 1) unless File.exist?(file)
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
- if dirs.count == 1
187
- targets = [dirs[0]]
188
- elsif dirs.count.positive?
189
- targets = NA.select_file(dirs, multiple: true)
190
- NA.notify('{r}Cancelled', exit_code: 1) unless targets && targets.count.positive?
191
- else
192
- NA.notify('{r}Todo not found', exit_code: 1) unless targets && targets.count.positive?
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
- end
195
- else
196
- files = NA.find_files(depth: options[:depth])
197
- NA.notify('{r}No todo file found', exit_code: 1) if files.count.zero?
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
- targets = files.count > 1 ? NA.select_file(files, multiple: true) : [files[0]]
200
- NA.notify('{r}Cancelled{x}', exit_code: 1) unless files.count.positive?
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
- end
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
- if options[:archive]
205
- options[:finish] = true
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
- NA.notify('{r}No search terms provided', exit_code: 1) if tokens.nil? && options[:tagged].empty?
210
-
211
- targets.each do |target|
212
- NA.update_action(target, tokens,
213
- priority: priority,
214
- add_tag: add_tags,
215
- remove_tag: remove_tags,
216
- finish: options[:finish],
217
- project: target_proj,
218
- delete: options[:delete],
219
- note: note,
220
- overwrite: options[:overwrite],
221
- tagged: tags,
222
- all: options[:all],
223
- done: options[:done],
224
- append: append)
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