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/tagged.rb
CHANGED
|
@@ -1,146 +1,149 @@
|
|
|
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
|
-
|
|
3
|
+
class App
|
|
4
|
+
extend GLI::App
|
|
5
|
+
desc 'Find actions matching a tag'
|
|
6
|
+
long_desc 'Finds actions with tags matching the arguments. An action is shown if it
|
|
7
|
+
contains all of the tags listed. Add a + before a tag to make it required
|
|
8
|
+
and others optional. You can specify values using TAG=VALUE pairs.
|
|
9
|
+
Use <, >, and = for numeric comparisons, and *=, ^=, and $= for text comparisons.
|
|
10
|
+
Date comparisons use natural language (`na tagged "due<=today"`) and
|
|
11
|
+
are detected automatically.'
|
|
12
|
+
arg_name 'TAG[=VALUE]'
|
|
13
|
+
command %i[tagged] do |c|
|
|
14
|
+
c.example 'na tagged maybe', desc: 'Show all actions tagged @maybe'
|
|
15
|
+
c.example 'na tagged -d 3 "feature, idea"', desc: 'Show all actions tagged @feature AND @idea, recurse 3 levels'
|
|
16
|
+
c.example 'na tagged --or "feature, idea"', desc: 'Show all actions tagged @feature OR @idea'
|
|
17
|
+
c.example 'na tagged "priority>=4"', desc: 'Show actions with @priority(4) or @priority(5)'
|
|
18
|
+
c.example 'na tagged "due<in 2 days"', desc: 'Show actions with a due date coming up in the next 2 days'
|
|
19
|
+
|
|
20
|
+
c.desc 'Recurse to depth'
|
|
21
|
+
c.arg_name 'DEPTH'
|
|
22
|
+
c.default_value 1
|
|
23
|
+
c.flag %i[d depth], type: :integer, must_match: /^\d+$/
|
|
24
|
+
|
|
25
|
+
c.desc 'Show actions from a specific todo file in history. May use wildcards (* and ?)'
|
|
26
|
+
c.arg_name 'TODO_PATH'
|
|
27
|
+
c.flag %i[in]
|
|
28
|
+
|
|
29
|
+
c.desc 'Include notes in output'
|
|
30
|
+
c.switch %i[notes], negatable: true, default_value: false
|
|
31
|
+
|
|
32
|
+
c.desc 'Combine tags with OR, displaying actions matching ANY of the tags'
|
|
33
|
+
c.switch %i[o or], negatable: false
|
|
34
|
+
|
|
35
|
+
c.desc 'Show actions from a specific project'
|
|
36
|
+
c.arg_name 'PROJECT[/SUBPROJECT]'
|
|
37
|
+
c.flag %i[proj project]
|
|
38
|
+
|
|
39
|
+
c.desc 'Filter results using search terms'
|
|
40
|
+
c.arg_name 'QUERY'
|
|
41
|
+
c.flag %i[search], multiple: true
|
|
42
|
+
|
|
43
|
+
c.desc 'Search query is regular expression'
|
|
44
|
+
c.switch %i[regex], negatable: false
|
|
45
|
+
|
|
46
|
+
c.desc 'Search query is exact text match (not tokens)'
|
|
47
|
+
c.switch %i[exact], negatable: false
|
|
48
|
+
|
|
49
|
+
c.desc 'Include @done actions'
|
|
50
|
+
c.switch %i[done]
|
|
51
|
+
|
|
52
|
+
c.desc 'Show actions not matching tags'
|
|
53
|
+
c.switch %i[v invert], negatable: false
|
|
54
|
+
|
|
55
|
+
c.desc 'Save this search for future use'
|
|
56
|
+
c.arg_name 'TITLE'
|
|
57
|
+
c.flag %i[save]
|
|
58
|
+
|
|
59
|
+
c.desc 'Output actions nested by file'
|
|
60
|
+
c.switch %[nest], negatable: false
|
|
61
|
+
|
|
62
|
+
c.desc 'Output actions nested by file and project'
|
|
63
|
+
c.switch %[omnifocus], negatable: false
|
|
64
|
+
|
|
65
|
+
c.action do |global_options, options, args|
|
|
66
|
+
options[:nest] = true if options[:omnifocus]
|
|
67
|
+
|
|
68
|
+
if options[:save]
|
|
69
|
+
title = options[:save].gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
|
|
70
|
+
NA.save_search(title, "#{NA.command_line.join(' ').sub(/ --save[= ]*\S+/, '').split(' ').map { |t| %("#{t}") }.join(' ')}")
|
|
71
|
+
end
|
|
70
72
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
tags = []
|
|
78
|
-
|
|
79
|
-
all_req = args.join(' ') !~ /[+!\-]/ && !options[:or]
|
|
80
|
-
args.join(',').split(/ *, */).each do |arg|
|
|
81
|
-
m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
|
|
82
|
-
|
|
83
|
-
tags.push({
|
|
84
|
-
tag: m['tag'].wildcard_to_rx,
|
|
85
|
-
comp: m['op'],
|
|
86
|
-
value: m['val'],
|
|
87
|
-
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
|
88
|
-
negate: !m['req'].nil? && m['req'] =~ /[!\-]/
|
|
89
|
-
})
|
|
90
|
-
end
|
|
73
|
+
depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
|
|
74
|
+
3
|
|
75
|
+
else
|
|
76
|
+
options[:depth].nil? ? global_options[:depth].to_i : options[:depth].to_i
|
|
77
|
+
end
|
|
91
78
|
|
|
92
|
-
|
|
93
|
-
tags.each { |tag| search_for_done = true if tag[:tag] =~ /done/ }
|
|
94
|
-
tags.push({ tag: 'done', value: nil, negate: true}) unless search_for_done
|
|
95
|
-
|
|
96
|
-
tokens = nil
|
|
97
|
-
if options[:search]
|
|
98
|
-
if options[:exact]
|
|
99
|
-
tokens = options[:search].join(' ')
|
|
100
|
-
elsif options[:regex]
|
|
101
|
-
tokens = Regexp.new(options[:search].join(' '), Regexp::IGNORECASE)
|
|
102
|
-
else
|
|
103
|
-
tokens = []
|
|
104
|
-
all_req = options[:search].join(' ') !~ /[+!\-]/ && !options[:or]
|
|
105
|
-
|
|
106
|
-
options[:search].join(' ').split(/ /).each do |arg|
|
|
107
|
-
m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
|
108
|
-
tokens.push({
|
|
109
|
-
token: m['tok'],
|
|
110
|
-
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
|
111
|
-
negate: !m['req'].nil? && m['req'] =~ /[!\-]/
|
|
112
|
-
})
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
end
|
|
79
|
+
tags = []
|
|
116
80
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
81
|
+
all_req = args.join(' ') !~ /[+!\-]/ && !options[:or]
|
|
82
|
+
args.join(',').split(/ *, */).each do |arg|
|
|
83
|
+
m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
|
|
84
|
+
|
|
85
|
+
tags.push({
|
|
86
|
+
tag: m['tag'].wildcard_to_rx,
|
|
87
|
+
comp: m['op'],
|
|
88
|
+
value: m['val'],
|
|
124
89
|
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
|
125
90
|
negate: !m['req'].nil? && m['req'] =~ /[!\-]/
|
|
126
91
|
})
|
|
127
92
|
end
|
|
128
|
-
end
|
|
129
93
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
94
|
+
search_for_done = false
|
|
95
|
+
tags.each { |tag| search_for_done = true if tag[:tag] =~ /done/ }
|
|
96
|
+
tags.push({ tag: 'done', value: nil, negate: true}) unless search_for_done
|
|
97
|
+
|
|
98
|
+
tokens = nil
|
|
99
|
+
if options[:search]
|
|
100
|
+
if options[:exact]
|
|
101
|
+
tokens = options[:search].join(' ')
|
|
102
|
+
elsif options[:regex]
|
|
103
|
+
tokens = Regexp.new(options[:search].join(' '), Regexp::IGNORECASE)
|
|
104
|
+
else
|
|
105
|
+
tokens = []
|
|
106
|
+
all_req = options[:search].join(' ') !~ /[+!\-]/ && !options[:or]
|
|
107
|
+
|
|
108
|
+
options[:search].join(' ').split(/ /).each do |arg|
|
|
109
|
+
m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
|
110
|
+
tokens.push({
|
|
111
|
+
token: m['tok'],
|
|
112
|
+
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
|
113
|
+
negate: !m['req'].nil? && m['req'] =~ /[!\-]/
|
|
114
|
+
})
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
todo = nil
|
|
120
|
+
if options[:in]
|
|
121
|
+
todo = []
|
|
122
|
+
options[:in].split(/ *, */).each do |a|
|
|
123
|
+
m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
|
124
|
+
todo.push({
|
|
125
|
+
token: m['tok'],
|
|
126
|
+
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
|
127
|
+
negate: !m['req'].nil? && m['req'] =~ /[!\-]/
|
|
128
|
+
})
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
files, actions, = NA.parse_actions(depth: depth,
|
|
133
|
+
done: options[:done],
|
|
134
|
+
query: todo,
|
|
135
|
+
search: tokens,
|
|
136
|
+
tag: tags,
|
|
137
|
+
negate: options[:invert],
|
|
138
|
+
project: options[:project],
|
|
139
|
+
require_na: false)
|
|
140
|
+
# regexes = tags.delete_if { |token| token[:negate] }.map { |token| token[:token] }
|
|
141
|
+
regexes = if tokens.is_a?(Array)
|
|
142
|
+
tokens.delete_if { |token| token[:negate] }.map { |token| token[:token] }
|
|
143
|
+
else
|
|
144
|
+
[tokens]
|
|
145
|
+
end
|
|
146
|
+
NA.output_actions(actions, depth, files: files, regexes: regexes, notes: options[:notes], nest: options[:nest], nest_projects: options[:omnifocus])
|
|
147
|
+
end
|
|
145
148
|
end
|
|
146
149
|
end
|
data/bin/commands/todos.rb
CHANGED
|
@@ -1,28 +1,31 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
list of
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
class App
|
|
4
|
+
extend GLI::App
|
|
5
|
+
desc 'Show list of known todo files'
|
|
6
|
+
long_desc 'Arguments will be interpreted as a query against which the
|
|
7
|
+
list of todos will be fuzzy matched. Separate directories with
|
|
8
|
+
/, :, or a space, e.g. `na todos code/marked`'
|
|
9
|
+
arg_name 'QUERY', optional: true
|
|
10
|
+
command %i[todos] do |c|
|
|
11
|
+
c.action do |_global_options, _options, args|
|
|
12
|
+
if args.count.positive?
|
|
13
|
+
all_req = args.join(' ') !~ /[+!\-]/
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
tokens = [{ token: '*', required: all_req, negate: false }]
|
|
16
|
+
args.each do |arg|
|
|
17
|
+
arg.split(/ *, */).each do |a|
|
|
18
|
+
m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
|
19
|
+
tokens.push({
|
|
20
|
+
token: m['tok'],
|
|
21
|
+
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
|
22
|
+
negate: !m['req'].nil? && m['req'] =~ /[!\-]/
|
|
23
|
+
})
|
|
24
|
+
end
|
|
22
25
|
end
|
|
23
26
|
end
|
|
24
|
-
end
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
NA.list_todos(query: tokens)
|
|
29
|
+
end
|
|
27
30
|
end
|
|
28
31
|
end
|