na 1.2.29 → 1.2.30

Sign up to get free protection for your applications and to get access to all the features.
@@ -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