na 1.2.35 → 1.2.38
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +50 -0
- data/Gemfile.lock +1 -1
- data/README.md +156 -6
- data/bin/commands/add.rb +11 -13
- data/bin/commands/complete.rb +4 -0
- data/bin/commands/edit.rb +21 -24
- data/bin/commands/find.rb +36 -23
- data/bin/commands/init.rb +1 -1
- data/bin/commands/next.rb +70 -37
- data/bin/commands/projects.rb +1 -1
- data/bin/commands/prompt.rb +2 -0
- data/bin/commands/saved.rb +3 -4
- data/bin/commands/tagged.rb +37 -30
- data/bin/commands/todos.rb +24 -14
- data/bin/commands/undo.rb +22 -0
- data/bin/commands/update.rb +28 -21
- data/bin/na +2 -1
- data/lib/na/action.rb +39 -22
- data/lib/na/actions.rb +87 -0
- data/lib/na/colors.rb +23 -1
- data/lib/na/editor.rb +125 -0
- data/lib/na/hash.rb +31 -0
- data/lib/na/next_action.rb +237 -498
- data/lib/na/pager.rb +1 -1
- data/lib/na/prompt.rb +6 -6
- data/lib/na/string.rb +23 -3
- data/lib/na/theme.rb +71 -0
- data/lib/na/todo.rb +183 -0
- data/lib/na/version.rb +1 -1
- data/lib/na.rb +4 -0
- data/src/_README.md +45 -1
- metadata +7 -2
data/bin/commands/next.rb
CHANGED
@@ -37,7 +37,7 @@ class App
|
|
37
37
|
|
38
38
|
c.desc 'Filter results using search terms'
|
39
39
|
c.arg_name 'QUERY'
|
40
|
-
c.flag %i[search], multiple: true
|
40
|
+
c.flag %i[search find grep], multiple: true
|
41
41
|
|
42
42
|
c.desc 'Search query is regular expression'
|
43
43
|
c.switch %i[regex], negatable: false
|
@@ -52,10 +52,10 @@ class App
|
|
52
52
|
c.switch %i[done]
|
53
53
|
|
54
54
|
c.desc 'Output actions nested by file'
|
55
|
-
c.switch %[nest], negatable: false
|
55
|
+
c.switch %i[nest], negatable: false
|
56
56
|
|
57
57
|
c.desc 'Output actions nested by file and project'
|
58
|
-
c.switch %[omnifocus], negatable: false
|
58
|
+
c.switch %i[omnifocus], negatable: false
|
59
59
|
|
60
60
|
c.action do |global_options, options, args|
|
61
61
|
if global_options[:add]
|
@@ -75,73 +75,106 @@ class App
|
|
75
75
|
options[:depth].nil? ? global_options[:depth].to_i : options[:depth].to_i
|
76
76
|
end
|
77
77
|
|
78
|
-
|
78
|
+
if options[:exact] || options[:regex]
|
79
|
+
search = options[:search].join(' ')
|
80
|
+
else
|
81
|
+
rx = [
|
82
|
+
'(?<=\A|[ ,])(?<req>[+!-])?@(?<tag>[^ *=<>$~\^,@(]+)',
|
83
|
+
'(?:\((?<value>.*?)\)| *(?<op>=~|[=<>~]{1,2}|[*$\^]=) *',
|
84
|
+
'(?<val>.*?(?=\Z|[,@])))?'
|
85
|
+
].join('')
|
86
|
+
search = options[:search].join(' ').gsub(Regexp.new(rx)) do
|
87
|
+
m = Regexp.last_match
|
88
|
+
string = if m['value']
|
89
|
+
"#{m['req']}#{m['tag']}=#{m['value']}"
|
90
|
+
else
|
91
|
+
m[0]
|
92
|
+
end
|
93
|
+
options[:tagged] << string.sub(/@/, '')
|
94
|
+
''
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
search = search.gsub(/,/, '').gsub(/ +/, ' ') unless search.nil?
|
99
|
+
|
100
|
+
all_req = options[:tagged].join(' ') !~ /(?<=[, ])[+!-]/ && !options[:or]
|
79
101
|
tags = []
|
80
102
|
options[:tagged].join(',').split(/ *, */).each do |arg|
|
81
|
-
m = arg.match(/^(?<req>[
|
103
|
+
m = arg.match(/^(?<req>[+!-])?(?<tag>[^ =<>$*~\^]+?) *(?:(?<op>[=<>~]{1,2}|[*$\^]=) *(?<val>.*?))?$/)
|
82
104
|
|
83
105
|
tags.push({
|
84
106
|
tag: m['tag'].wildcard_to_rx,
|
85
107
|
comp: m['op'],
|
86
108
|
value: m['val'],
|
87
109
|
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
88
|
-
negate: !m['req'].nil? && m['req'] =~ /[
|
110
|
+
negate: !m['req'].nil? && m['req'] =~ /[!-]/ ? true : false
|
89
111
|
})
|
90
112
|
end
|
91
113
|
|
92
114
|
args.concat(options[:in])
|
93
115
|
if args.count.positive?
|
94
|
-
all_req = args.join(' ') !~ /[
|
116
|
+
all_req = args.join(' ') !~ /(?<=[, ])[+!-]/
|
95
117
|
|
96
118
|
tokens = []
|
97
119
|
args.each do |arg|
|
98
120
|
arg.split(/ *, */).each do |a|
|
99
|
-
m = a.match(/^(?<req>[
|
121
|
+
m = a.match(/^(?<req>[+!-])?(?<tok>.*?)$/)
|
100
122
|
tokens.push({
|
101
123
|
token: m['tok'],
|
102
124
|
required: !m['req'].nil? && m['req'] == '+',
|
103
|
-
negate: !m['req'].nil? && m['req'] =~ /[
|
125
|
+
negate: !m['req'].nil? && m['req'] =~ /[!-]/ ? true : false
|
104
126
|
})
|
105
127
|
end
|
106
128
|
end
|
107
129
|
end
|
108
130
|
|
109
|
-
|
110
|
-
if
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
131
|
+
search_for_done = false
|
132
|
+
tags.each { |tag| search_for_done = true if tag[:tag] =~ /done/ }
|
133
|
+
options[:done] = true if search_for_done
|
134
|
+
|
135
|
+
search_tokens = nil
|
136
|
+
if options[:exact]
|
137
|
+
search_tokens = search
|
138
|
+
elsif options[:regex]
|
139
|
+
search_tokens = Regexp.new(search, Regexp::IGNORECASE)
|
140
|
+
else
|
141
|
+
search_tokens = []
|
142
|
+
all_req = search !~ /(?<=[, ])[+!-]/ && !options[:or]
|
143
|
+
|
144
|
+
search.split(/ /).each do |arg|
|
145
|
+
m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
146
|
+
search_tokens.push({
|
147
|
+
token: m['tok'],
|
148
|
+
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
149
|
+
negate: !m['req'].nil? && m['req'] =~ /[!-]/ ? true : false
|
150
|
+
})
|
127
151
|
end
|
128
152
|
end
|
129
153
|
|
130
154
|
NA.na_tag = options[:tag] unless options[:tag].nil?
|
131
155
|
require_na = true
|
132
156
|
|
133
|
-
tag = [{ tag: NA.na_tag, value: nil }]
|
157
|
+
tag = [{ tag: NA.na_tag, value: nil, required: true, negate: false }]
|
134
158
|
tag << { tag: 'done', value: nil, negate: true } unless options[:done]
|
135
159
|
tag.concat(tags)
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
160
|
+
|
161
|
+
todo = NA::Todo.new({ depth: depth,
|
162
|
+
done: options[:done],
|
163
|
+
query: tokens,
|
164
|
+
tag: tag,
|
165
|
+
search: search_tokens,
|
166
|
+
project: options[:project],
|
167
|
+
require_na: require_na })
|
168
|
+
if todo.files.empty?
|
169
|
+
NA.notify("#{NA.theme[:error]}No matches found for #{tokens[0][:token]}.
|
170
|
+
Run `na todos` to see available todo files.")
|
171
|
+
end
|
172
|
+
NA::Pager.paginate = false if options[:omnifocus]
|
173
|
+
todo.actions.output(depth,
|
174
|
+
files: todo.files,
|
175
|
+
notes: options[:notes],
|
176
|
+
nest: options[:nest],
|
177
|
+
nest_projects: options[:omnifocus])
|
145
178
|
end
|
146
179
|
end
|
147
180
|
end
|
data/bin/commands/projects.rb
CHANGED
@@ -16,7 +16,7 @@ class App
|
|
16
16
|
|
17
17
|
c.action do |_global_options, options, args|
|
18
18
|
if args.count.positive?
|
19
|
-
all_req = args.join(' ') !~ /[+!-]/
|
19
|
+
all_req = args.join(' ') !~ /(?<=[, ])[+!-]/
|
20
20
|
|
21
21
|
tokens = [{ token: '*', required: all_req, negate: false }]
|
22
22
|
args.each do |arg|
|
data/bin/commands/prompt.rb
CHANGED
@@ -9,6 +9,8 @@ class App
|
|
9
9
|
c.desc 'Output the prompt hook for the current shell to STDOUT. Pass an argument to
|
10
10
|
specify a shell (zsh, bash, fish)'
|
11
11
|
c.arg_name 'SHELL', optional: true
|
12
|
+
c.default_command :show
|
13
|
+
|
12
14
|
c.command %i[show] do |s|
|
13
15
|
s.action do |_global_options, _options, args|
|
14
16
|
shell = if args.count.positive?
|
data/bin/commands/saved.rb
CHANGED
@@ -25,8 +25,8 @@ class App
|
|
25
25
|
|
26
26
|
if args.empty?
|
27
27
|
searches = NA.load_searches
|
28
|
-
NA.notify("{
|
29
|
-
NA.notify(searches.map { |k, v| "{
|
28
|
+
NA.notify("#{NA.theme[:success]}Saved searches stored in #{NA.database_path(file: 'saved_searches.yml').highlight_filename}")
|
29
|
+
NA.notify(searches.map { |k, v| "#{NA.theme[:filename]}#{k}: #{NA.theme[:values]}#{v}" }.join("\n"))
|
30
30
|
else
|
31
31
|
args.each do |arg|
|
32
32
|
searches = NA.load_searches
|
@@ -34,13 +34,12 @@ class App
|
|
34
34
|
NA.delete_search(arg) if options[:delete]
|
35
35
|
|
36
36
|
keys = searches.keys.delete_if { |k| k !~ /#{arg}/ }
|
37
|
-
NA.notify("{
|
37
|
+
NA.notify("#{NA.theme[:error]}Search #{arg} not found", exit_code: 1) if keys.empty?
|
38
38
|
|
39
39
|
key = keys[0]
|
40
40
|
cmd = Shellwords.shellsplit(searches[key])
|
41
41
|
run(cmd)
|
42
42
|
end
|
43
|
-
exit
|
44
43
|
end
|
45
44
|
end
|
46
45
|
end
|
data/bin/commands/tagged.rb
CHANGED
@@ -6,7 +6,7 @@ class App
|
|
6
6
|
long_desc 'Finds actions with tags matching the arguments. An action is shown if it
|
7
7
|
contains all of the tags listed. Add a + before a tag to make it required
|
8
8
|
and others optional. You can specify values using TAG=VALUE pairs.
|
9
|
-
Use <, >, and = for numeric comparisons, and *=, ^=,
|
9
|
+
Use <, >, and = for numeric comparisons, and *=, ^=, $=, or =~ (regex) for text comparisons.
|
10
10
|
Date comparisons use natural language (`na tagged "due<=today"`) and
|
11
11
|
are detected automatically.'
|
12
12
|
arg_name 'TAG[=VALUE]'
|
@@ -38,7 +38,7 @@ class App
|
|
38
38
|
|
39
39
|
c.desc 'Filter results using search terms'
|
40
40
|
c.arg_name 'QUERY'
|
41
|
-
c.flag %i[search], multiple: true
|
41
|
+
c.flag %i[search find grep], multiple: true
|
42
42
|
|
43
43
|
c.desc 'Search query is regular expression'
|
44
44
|
c.switch %i[regex], negatable: false
|
@@ -57,17 +57,18 @@ class App
|
|
57
57
|
c.flag %i[save]
|
58
58
|
|
59
59
|
c.desc 'Output actions nested by file'
|
60
|
-
c.switch %[nest], negatable: false
|
60
|
+
c.switch %i[nest], negatable: false
|
61
61
|
|
62
62
|
c.desc 'Output actions nested by file and project'
|
63
|
-
c.switch %[omnifocus], negatable: false
|
63
|
+
c.switch %i[omnifocus], negatable: false
|
64
64
|
|
65
65
|
c.action do |global_options, options, args|
|
66
66
|
options[:nest] = true if options[:omnifocus]
|
67
67
|
|
68
68
|
if options[:save]
|
69
69
|
title = options[:save].gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
|
70
|
-
|
70
|
+
cmd = NA.command_line.join(' ').sub(/ --save[= ]*\S+/, '').split(' ').map { |t| %("#{t}") }.join(' ')
|
71
|
+
NA.save_search(title, cmd)
|
71
72
|
end
|
72
73
|
|
73
74
|
depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
|
@@ -78,9 +79,9 @@ class App
|
|
78
79
|
|
79
80
|
tags = []
|
80
81
|
|
81
|
-
all_req = args.join(' ') !~ /[
|
82
|
+
all_req = args.join(' ') !~ /(?<=[, ])[+!-]/ && !options[:or]
|
82
83
|
args.join(',').split(/ *, */).each do |arg|
|
83
|
-
m = arg.match(/^(?<req>[
|
84
|
+
m = arg.match(/^(?<req>[+!-])?(?<tag>[^ =<>$*~\^]+?) *(?:(?<op>[=<>~]{1,2}|[*$\^]=) *(?<val>.*?))?$/)
|
84
85
|
next if m.nil?
|
85
86
|
|
86
87
|
tags.push({
|
@@ -88,13 +89,13 @@ class App
|
|
88
89
|
comp: m['op'],
|
89
90
|
value: m['val'],
|
90
91
|
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
91
|
-
negate: !m['req'].nil? && m['req'] =~ /[
|
92
|
+
negate: !m['req'].nil? && m['req'] =~ /[!-]/
|
92
93
|
})
|
93
94
|
end
|
94
95
|
|
95
96
|
search_for_done = false
|
96
97
|
tags.each { |tag| search_for_done = true if tag[:tag] =~ /done/ }
|
97
|
-
tags.push({ tag: 'done', value: nil, negate: true}) unless search_for_done || options[:done]
|
98
|
+
tags.push({ tag: 'done', value: nil, negate: true }) unless search_for_done || options[:done]
|
98
99
|
options[:done] = true if search_for_done
|
99
100
|
|
100
101
|
tokens = nil
|
@@ -105,49 +106,55 @@ class App
|
|
105
106
|
tokens = Regexp.new(options[:search].join(' '), Regexp::IGNORECASE)
|
106
107
|
else
|
107
108
|
tokens = []
|
108
|
-
all_req = options[:search].join(' ') !~ /[
|
109
|
+
all_req = options[:search].join(' ') !~ /(?<=[, ])[+!-]/ && !options[:or]
|
109
110
|
|
110
111
|
options[:search].join(' ').split(/ /).each do |arg|
|
111
112
|
m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
112
113
|
tokens.push({
|
113
114
|
token: m['tok'],
|
114
115
|
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
115
|
-
negate: !m['req'].nil? && m['req'] =~ /[
|
116
|
+
negate: !m['req'].nil? && m['req'] =~ /[!-]/
|
116
117
|
})
|
117
118
|
end
|
118
119
|
end
|
119
120
|
end
|
120
121
|
|
121
|
-
|
122
|
+
todos = nil
|
122
123
|
if options[:in]
|
123
|
-
|
124
|
+
todos = []
|
125
|
+
all_req = options[:in] !~ /(?<=[, ])[+!-]/ && !options[:or]
|
124
126
|
options[:in].split(/ *, */).each do |a|
|
125
127
|
m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
128
|
+
todos.push({
|
129
|
+
token: m['tok'],
|
130
|
+
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
131
|
+
negate: !m['req'].nil? && m['req'] =~ /[!-]/
|
132
|
+
})
|
131
133
|
end
|
132
134
|
end
|
133
135
|
|
134
|
-
NA.notify(
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
136
|
+
NA.notify("#{NA.theme[:error]}No actions matched search", exit_code: 1) if tags.empty? && tokens.empty?
|
137
|
+
|
138
|
+
todo = NA::Todo.new({ depth: depth,
|
139
|
+
done: options[:done],
|
140
|
+
query: todos,
|
141
|
+
search: tokens,
|
142
|
+
tag: tags,
|
143
|
+
negate: options[:invert],
|
144
|
+
project: options[:project],
|
145
|
+
require_na: false })
|
146
|
+
|
145
147
|
regexes = if tokens.is_a?(Array)
|
146
148
|
tokens.delete_if { |token| token[:negate] }.map { |token| token[:token] }
|
147
149
|
else
|
148
150
|
[tokens]
|
149
151
|
end
|
150
|
-
|
152
|
+
todo.actions.output(depth,
|
153
|
+
files: todo.files,
|
154
|
+
regexes: regexes,
|
155
|
+
notes: options[:notes],
|
156
|
+
nest: options[:nest],
|
157
|
+
nest_projects: options[:omnifocus])
|
151
158
|
end
|
152
159
|
end
|
153
160
|
end
|
data/bin/commands/todos.rb
CHANGED
@@ -8,24 +8,34 @@ class App
|
|
8
8
|
/, :, or a space, e.g. `na todos code/marked`'
|
9
9
|
arg_name 'QUERY', optional: true
|
10
10
|
command %i[todos] do |c|
|
11
|
-
c.
|
12
|
-
|
13
|
-
all_req = args.join(' ') !~ /[+!\-]/
|
11
|
+
c.desc 'Open the todo database in an editor for manual modification'
|
12
|
+
c.switch %i[e edit]
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
14
|
+
c.action do |_global_options, options, args|
|
15
|
+
if options[:edit]
|
16
|
+
system("#{NA::Editor.default_editor(prefer_git_editor: false)} #{NA.database_path}")
|
17
|
+
editor = NA::Editor.default_editor(prefer_git_editor: false).highlight_filename
|
18
|
+
database = NA.database_path.highlight_filename
|
19
|
+
NA.notify("{b}#{NA.theme[:success]}Opened #{database}#{NA.theme[:success]} in #{editor}")
|
20
|
+
else
|
21
|
+
if args.count.positive?
|
22
|
+
all_req = args.join(' ') !~ /(?<=[, ])[+!-]/
|
23
|
+
|
24
|
+
tokens = [{ token: '*', required: all_req, negate: false }]
|
25
|
+
args.each do |arg|
|
26
|
+
arg.split(/ *, */).each do |a|
|
27
|
+
m = a.match(/^(?<req>[+!-])?(?<tok>.*?)$/)
|
28
|
+
tokens.push({
|
29
|
+
token: m['tok'],
|
30
|
+
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
31
|
+
negate: (!m['req'].nil? && m['req'] =~ /[!-]/) ? true : false
|
32
|
+
})
|
33
|
+
end
|
24
34
|
end
|
25
35
|
end
|
26
|
-
end
|
27
36
|
|
28
|
-
|
37
|
+
NA.list_todos(query: tokens)
|
38
|
+
end
|
29
39
|
end
|
30
40
|
end
|
31
41
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class App
|
4
|
+
extend GLI::App
|
5
|
+
desc 'Undo the last change'
|
6
|
+
long_desc 'Run without argument to undo most recent change'
|
7
|
+
arg_name 'FILE', optional: true, multiple: true
|
8
|
+
command %i[undo] do |c|
|
9
|
+
c.example 'na undo', desc: 'Undo the last change'
|
10
|
+
c.example 'na undo myproject', desc: 'Undo the last change to a file matching "myproject"'
|
11
|
+
|
12
|
+
c.action do |_global_options, options, args|
|
13
|
+
if args.empty?
|
14
|
+
NA.restore_last_modified_file
|
15
|
+
else
|
16
|
+
args.each do |arg|
|
17
|
+
NA.restore_last_modified_file(search: arg)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/bin/commands/update.rb
CHANGED
@@ -63,7 +63,7 @@ class App
|
|
63
63
|
c.desc 'Delete an action'
|
64
64
|
c.switch %i[delete], negatable: false
|
65
65
|
|
66
|
-
c.desc "Open action in editor (#{NA.default_editor}).
|
66
|
+
c.desc "Open action in editor (#{NA::Editor.default_editor}).
|
67
67
|
Natural language dates will be parsed and converted in date-based tags."
|
68
68
|
c.switch %i[edit], negatable: false
|
69
69
|
|
@@ -97,6 +97,8 @@ class App
|
|
97
97
|
options[:tagged] << '+done'
|
98
98
|
elsif !options[:remove].nil? && !options[:remove].empty?
|
99
99
|
options[:tagged].concat(options[:remove])
|
100
|
+
elsif options[:finish]
|
101
|
+
options[:tagged] << '-done'
|
100
102
|
end
|
101
103
|
|
102
104
|
action = if args.count.positive?
|
@@ -109,8 +111,8 @@ class App
|
|
109
111
|
]
|
110
112
|
`gum input #{opts.join(' ')}`.strip
|
111
113
|
elsif $stdin.isatty && options[:tagged].empty?
|
112
|
-
|
113
|
-
reader.read_line(NA::Color.template(
|
114
|
+
NA.notify("#{NA.theme[:prompt]}Enter search string:")
|
115
|
+
reader.read_line(NA::Color.template("#{NA.theme[:filename]}> #{NA.theme[:action]}")).strip
|
114
116
|
end
|
115
117
|
|
116
118
|
if action
|
@@ -121,35 +123,34 @@ class App
|
|
121
123
|
tokens = Regexp.new(action, Regexp::IGNORECASE)
|
122
124
|
else
|
123
125
|
tokens = []
|
124
|
-
all_req = action !~ /[
|
126
|
+
all_req = action !~ /[+!-]/ && !options[:or]
|
125
127
|
|
126
128
|
action.split(/ /).each do |arg|
|
127
129
|
m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
128
130
|
tokens.push({
|
129
131
|
token: m['tok'],
|
130
132
|
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
131
|
-
negate: !m['req'].nil? && m['req'] =~ /[
|
133
|
+
negate: !m['req'].nil? && m['req'] =~ /[!-]/ ? true : false
|
132
134
|
})
|
133
135
|
end
|
134
136
|
end
|
135
137
|
end
|
136
138
|
|
137
139
|
if (action.nil? || action.empty?) && options[:tagged].empty?
|
138
|
-
|
139
|
-
Process.exit 1
|
140
|
+
NA.notify("#{NA.theme[:error]}Empty input, cancelled", exit_code: 1)
|
140
141
|
end
|
141
142
|
|
142
|
-
all_req = options[:tagged].join(' ') !~ /[
|
143
|
+
all_req = options[:tagged].join(' ') !~ /[+!-]/ && !options[:or]
|
143
144
|
tags = []
|
144
145
|
options[:tagged].join(',').split(/ *, */).each do |arg|
|
145
|
-
m = arg.match(/^(?<req>[
|
146
|
+
m = arg.match(/^(?<req>[+!-])?(?<tag>[^ =<>$*~\^]+?) *(?:(?<op>[=<>~]{1,2}|[*$\^]=) *(?<val>.*?))?$/)
|
146
147
|
|
147
148
|
tags.push({
|
148
149
|
tag: m['tag'].wildcard_to_rx,
|
149
150
|
comp: m['op'],
|
150
151
|
value: m['val'],
|
151
152
|
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
152
|
-
negate: !m['req'].nil? && m['req'] =~ /[
|
153
|
+
negate: !m['req'].nil? && m['req'] =~ /[!-]/ ? true : false
|
153
154
|
})
|
154
155
|
end
|
155
156
|
|
@@ -168,7 +169,7 @@ class App
|
|
168
169
|
args << '--width $(tput cols)'
|
169
170
|
`gum write #{args.join(' ')}`.strip.split("\n")
|
170
171
|
else
|
171
|
-
|
172
|
+
NA.notify("#{NA.theme[:prompt]}Enter a note, {bw}CTRL-d#{NA.theme[:prompt]} to end editing:#{NA.theme[:action]}")
|
172
173
|
reader.read_multiline
|
173
174
|
end
|
174
175
|
end
|
@@ -180,13 +181,11 @@ class App
|
|
180
181
|
options[:project]
|
181
182
|
elsif NA.cwd_is == :project
|
182
183
|
NA.cwd
|
183
|
-
else
|
184
|
-
nil
|
185
184
|
end
|
186
185
|
|
187
186
|
if options[:file]
|
188
187
|
file = File.expand_path(options[:file])
|
189
|
-
NA.notify(
|
188
|
+
NA.notify("#{NA.theme[:error]}File not found", exit_code: 1) unless File.exist?(file)
|
190
189
|
|
191
190
|
targets = [file]
|
192
191
|
elsif options[:todo]
|
@@ -196,7 +195,7 @@ class App
|
|
196
195
|
todo.push({
|
197
196
|
token: m['tok'],
|
198
197
|
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
199
|
-
negate: !m['req'].nil? && m['req'] =~ /[
|
198
|
+
negate: !m['req'].nil? && m['req'] =~ /[!-]/ ? true : false
|
200
199
|
})
|
201
200
|
end
|
202
201
|
dirs = NA.match_working_dir(todo)
|
@@ -205,17 +204,25 @@ class App
|
|
205
204
|
targets = [dirs[0]]
|
206
205
|
elsif dirs.count.positive?
|
207
206
|
targets = NA.select_file(dirs, multiple: true)
|
208
|
-
NA.notify(
|
207
|
+
NA.notify("#{NA.theme[:error]}Cancelled", exit_code: 1) unless targets && targets.count.positive?
|
209
208
|
else
|
210
|
-
NA.notify(
|
209
|
+
NA.notify("#{NA.theme[:error]}Todo not found", exit_code: 1) unless targets && targets.count.positive?
|
211
210
|
|
212
211
|
end
|
213
212
|
else
|
214
|
-
files = NA.
|
215
|
-
|
213
|
+
files = NA.find_files_matching({
|
214
|
+
depth: options[:depth],
|
215
|
+
done: options[:done],
|
216
|
+
project: target_proj,
|
217
|
+
regex: options[:regex],
|
218
|
+
require_na: false,
|
219
|
+
search: tokens,
|
220
|
+
tag: tags
|
221
|
+
})
|
222
|
+
NA.notify("#{NA.theme[:error]}No todo file found", exit_code: 1) if files.count.zero?
|
216
223
|
|
217
224
|
targets = files.count > 1 ? NA.select_file(files, multiple: true) : [files[0]]
|
218
|
-
NA.notify(
|
225
|
+
NA.notify("#{NA.theme[:error]}Cancelled", exit_code: 1) unless files.count.positive?
|
219
226
|
|
220
227
|
end
|
221
228
|
|
@@ -224,7 +231,7 @@ class App
|
|
224
231
|
options[:project] = 'Archive'
|
225
232
|
end
|
226
233
|
|
227
|
-
NA.notify(
|
234
|
+
NA.notify("#{NA.theme[:error]}No search terms provided", exit_code: 1) if tokens.nil? && options[:tagged].empty?
|
228
235
|
|
229
236
|
targets.each do |target|
|
230
237
|
NA.update_action(target, tokens,
|
data/bin/na
CHANGED
@@ -37,6 +37,7 @@ class App
|
|
37
37
|
default_command :next
|
38
38
|
|
39
39
|
NA::Color.coloring = $stdin.isatty
|
40
|
+
NA::Pager.paginate = $stdin.isatty
|
40
41
|
|
41
42
|
desc 'Add a next action (deprecated, for backwards compatibility)'
|
42
43
|
switch %i[a add], negatable: false
|
@@ -84,7 +85,7 @@ class App
|
|
84
85
|
|
85
86
|
pre do |global, _command, _options, _args|
|
86
87
|
NA.verbose = global[:debug]
|
87
|
-
NA::Pager.paginate = global[:pager]
|
88
|
+
NA::Pager.paginate = global[:pager] && $stdout.isatty
|
88
89
|
NA::Color.coloring = global[:color]
|
89
90
|
NA.extension = global[:ext]
|
90
91
|
NA.na_tag = global[:na_tag]
|