na 1.2.26 → 1.2.28
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/Gemfile.lock +1 -1
- data/README.md +12 -12
- data/bin/commands/add.rb +183 -0
- data/bin/commands/archive.rb +56 -0
- data/bin/commands/changes.rb +19 -0
- data/bin/commands/complete.rb +51 -0
- data/bin/commands/edit.rb +45 -0
- data/bin/commands/find.rb +135 -0
- data/bin/commands/init.rb +28 -0
- data/bin/commands/next.rb +143 -0
- data/bin/commands/projects.rb +34 -0
- data/bin/commands/prompt.rb +49 -0
- data/bin/commands/saved.rb +39 -0
- data/bin/commands/tagged.rb +146 -0
- data/bin/commands/todos.rb +28 -0
- data/bin/commands/update.rb +227 -0
- data/bin/na +11 -1162
- data/lib/na/version.rb +1 -1
- data/src/_README.md +1 -1
- metadata +15 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a331d16e9572f0a63e251c2de46348880b73dbce47838968ac9ac987e6a053f5
|
4
|
+
data.tar.gz: dec5319c89b04f65eb039867a5e01f6e1906c7287af2542bc7feab40f93dbf97
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4ac0842dbf415b87ffa200662eea10ac9fd382406d2e953342f7c81777504e05710cf6bf622fc831309d58178c8d1622103713b82f81bfe5df07001366fb5058
|
7
|
+
data.tar.gz: 975091dac71d91659df9c1c9b69722aad42cb74a69d6a030b7a2a335d0273925e72e87cce0afb9cc4507d7c5f376bf304a6f8a806dbeb25b7d03ff259e8031a1
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
### 1.2.28
|
2
|
+
|
3
|
+
2023-08-21 11:01
|
4
|
+
|
5
|
+
### 1.2.27
|
6
|
+
|
7
|
+
2023-08-21 10:58
|
8
|
+
|
9
|
+
#### NEW
|
10
|
+
|
11
|
+
- `na archive --done` to archive all @done actions
|
12
|
+
|
13
|
+
#### IMPROVED
|
14
|
+
|
15
|
+
- Split commands into separate files for easier maintenance
|
16
|
+
|
1
17
|
### 1.2.26
|
2
18
|
|
3
19
|
2023-08-21 10:26
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -9,7 +9,7 @@
|
|
9
9
|
_If you're one of the rare people like me who find this useful, feel free to
|
10
10
|
[buy me some coffee][donate]._
|
11
11
|
|
12
|
-
The current version of `na` is 1.2.
|
12
|
+
The current version of `na` is 1.2.28
|
13
13
|
.
|
14
14
|
|
15
15
|
`na` ("next action") is a command line tool designed to make it easy to see what your next actions are for any project, right from the command line. It works with TaskPaper-formatted files (but any plain text format will do), looking for `@na` tags (or whatever you specify) in todo files in your current folder.
|
@@ -77,7 +77,7 @@ SYNOPSIS
|
|
77
77
|
na [global options] command [command options] [arguments...]
|
78
78
|
|
79
79
|
VERSION
|
80
|
-
1.2.
|
80
|
+
1.2.28
|
81
81
|
|
82
82
|
GLOBAL OPTIONS
|
83
83
|
-a, --add - Add a next action (deprecated, for backwards compatibility)
|
@@ -138,7 +138,7 @@ SYNOPSIS
|
|
138
138
|
na [global options] add [command options] ACTION
|
139
139
|
|
140
140
|
DESCRIPTION
|
141
|
-
Provides an easy way to store todos while you work. Add quick
|
141
|
+
Provides an easy way to store todos while you work. Add quick reminders and (if you set up Prompt Hooks) they'll automatically display next time you enter the directory. If multiple todo files are found in the current directory, a menu will allow you to pick to which file the action gets added.
|
142
142
|
|
143
143
|
COMMAND OPTIONS
|
144
144
|
--at=POSITION - Add task at [s]tart or [e]nd of target project (default: none)
|
@@ -175,7 +175,7 @@ SYNOPSIS
|
|
175
175
|
na [global options] edit [command options]
|
176
176
|
|
177
177
|
DESCRIPTION
|
178
|
-
Let the system choose the defualt, (e.g. TaskPaper), or specify a command line utility (e.g. vim).
|
178
|
+
Let the system choose the defualt, (e.g. TaskPaper), or specify a command line utility (e.g. vim). If more than one todo file is found, a menu is displayed.
|
179
179
|
|
180
180
|
COMMAND OPTIONS
|
181
181
|
-a, --app=EDITOR - Specify a Mac app (default: none)
|
@@ -188,7 +188,7 @@ EXAMPLES
|
|
188
188
|
na edit
|
189
189
|
|
190
190
|
# Display a menu of all todo files three levels deep from the
|
191
|
-
|
191
|
+
current directory, open selection in vim.
|
192
192
|
na edit -d 3 -a vim
|
193
193
|
```
|
194
194
|
|
@@ -207,7 +207,7 @@ SYNOPSIS
|
|
207
207
|
na [global options] find [command options] PATTERN
|
208
208
|
|
209
209
|
DESCRIPTION
|
210
|
-
Search tokens are separated by spaces. Actions matching all tokens in the pattern will be shown
|
210
|
+
Search tokens are separated by spaces. Actions matching all tokens in the pattern will be shown (partial matches allowed). Add a + before a token to make it required, e.g. `na find +feature +maybe`, add a - or ! to ignore matches containing that token.
|
211
211
|
|
212
212
|
COMMAND OPTIONS
|
213
213
|
-d, --depth=DEPTH - Recurse to depth (default: none)
|
@@ -274,7 +274,7 @@ SYNOPSIS
|
|
274
274
|
na [global options] next [command options] [QUERY]
|
275
275
|
|
276
276
|
DESCRIPTION
|
277
|
-
Next actions are actions which contain the next action tag (default @na),
|
277
|
+
Next actions are actions which contain the next action tag (default @na), do not contain @done, and are not in the Archive project. Arguments will target a todo file from history, whether it's in the current directory or not. Todo file queries can include path components separated by / or :, and may use wildcards (`*` to match any text, `?` to match a single character). Multiple queries allowed (separate arguments or separated by comma).
|
278
278
|
|
279
279
|
COMMAND OPTIONS
|
280
280
|
-d, --depth=DEPTH - Recurse to depth (default: none)
|
@@ -315,7 +315,7 @@ SYNOPSIS
|
|
315
315
|
na [global options] projects [command options] [QUERY]
|
316
316
|
|
317
317
|
DESCRIPTION
|
318
|
-
Arguments will be interpreted as a query for a known todo file,
|
318
|
+
Arguments will be interpreted as a query for a known todo file, fuzzy matched. Separate directories with /, :, or a space, e.g. `na projects code/marked`
|
319
319
|
|
320
320
|
COMMAND OPTIONS
|
321
321
|
-d, --depth=DEPTH - Search for files X directories deep (default: 1)
|
@@ -380,7 +380,7 @@ SYNOPSIS
|
|
380
380
|
na [global options] next [command options] [QUERY]
|
381
381
|
|
382
382
|
DESCRIPTION
|
383
|
-
Next actions are actions which contain the next action tag (default @na),
|
383
|
+
Next actions are actions which contain the next action tag (default @na), do not contain @done, and are not in the Archive project. Arguments will target a todo file from history, whether it's in the current directory or not. Todo file queries can include path components separated by / or :, and may use wildcards (`*` to match any text, `?` to match a single character). Multiple queries allowed (separate arguments or separated by comma).
|
384
384
|
|
385
385
|
COMMAND OPTIONS
|
386
386
|
-d, --depth=DEPTH - Recurse to depth (default: none)
|
@@ -421,7 +421,7 @@ SYNOPSIS
|
|
421
421
|
na [global options] todos [QUERY]
|
422
422
|
|
423
423
|
DESCRIPTION
|
424
|
-
Arguments will be interpreted as a query against which the
|
424
|
+
Arguments will be interpreted as a query against which the list of todos will be fuzzy matched. Separate directories with /, :, or a space, e.g. `na todos code/marked`
|
425
425
|
```
|
426
426
|
|
427
427
|
##### update
|
@@ -467,7 +467,7 @@ SYNOPSIS
|
|
467
467
|
na [global options] update [command options] ACTION
|
468
468
|
|
469
469
|
DESCRIPTION
|
470
|
-
Provides an easy way to complete, prioritize, and tag existing actions.
|
470
|
+
Provides an easy way to complete, prioritize, and tag existing actions. If multiple todo files are found in the current directory, a menu will allow you to pick which file to act on.
|
471
471
|
|
472
472
|
COMMAND OPTIONS
|
473
473
|
-a, --archive - Add a @done tag to action and move to Archive
|
@@ -480,7 +480,7 @@ COMMAND OPTIONS
|
|
480
480
|
-f, --finish - Add a @done tag to action
|
481
481
|
--file=PATH - Specify the file to search for the task (default: none)
|
482
482
|
--in, --todo=TODO_FILE - Use a known todo file, partial matches allowed (default: none)
|
483
|
-
-n, --note - Prompt for additional notes. Input will be appended to any existing note.
|
483
|
+
-n, --note - Prompt for additional notes. Input will be appended to any existing note. If STDIN input (piped) is detected, it will be used as a note.
|
484
484
|
-o, --overwrite - Overwrite note instead of appending
|
485
485
|
-p, --priority=PRIO - Add/change a priority level 1-5 (default: 0)
|
486
486
|
-r, --remove=TAG - Remove a tag to the action (may be used more than once, default: none)
|
data/bin/commands/add.rb
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
desc 'Add a new next action'
|
4
|
+
long_desc 'Provides an easy way to store todos while you work. Add quick
|
5
|
+
reminders and (if you set up Prompt Hooks) they\'ll automatically display
|
6
|
+
next time you enter the directory.
|
7
|
+
|
8
|
+
If multiple todo files are found in the current directory, a menu will
|
9
|
+
allow you to pick to which file the action gets added.'
|
10
|
+
arg_name 'ACTION'
|
11
|
+
command :add do |c|
|
12
|
+
c.example 'na add "A cool feature I thought of @idea"', desc: 'Add a new action to the Inbox, including a tag'
|
13
|
+
c.example 'na add "A bug I need to fix" -p 4 -n',
|
14
|
+
desc: 'Add a new action to the Inbox, set its @priority to 4, and prompt for an additional note.'
|
15
|
+
c.example 'na add "An action item (with a note)"',
|
16
|
+
desc: 'A parenthetical at the end of an action is interpreted as a note'
|
17
|
+
|
18
|
+
c.desc 'Prompt for additional notes. STDIN input (piped) will be treated as a note if present.'
|
19
|
+
c.switch %i[n note], negatable: false
|
20
|
+
|
21
|
+
c.desc 'Add a priority level 1-5'
|
22
|
+
c.arg_name 'PRIO'
|
23
|
+
c.flag %i[p priority], must_match: /[1-5]/, type: :integer, default_value: 0
|
24
|
+
|
25
|
+
c.desc 'Add action to specific project'
|
26
|
+
c.arg_name 'PROJECT'
|
27
|
+
c.default_value 'Inbox'
|
28
|
+
c.flag %i[to project proj]
|
29
|
+
|
30
|
+
c.desc 'Add task 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 'Add to a known todo file, partial matches allowed'
|
35
|
+
c.arg_name 'TODO_FILE'
|
36
|
+
c.flag %i[in todo]
|
37
|
+
|
38
|
+
c.desc 'Use a tag other than the default next action tag'
|
39
|
+
c.arg_name 'TAG'
|
40
|
+
c.flag %i[t tag]
|
41
|
+
|
42
|
+
c.desc 'Don\'t add next action tag to new entry'
|
43
|
+
c.switch %i[x], negatable: false
|
44
|
+
|
45
|
+
c.desc 'Specify the file to which the task should be added'
|
46
|
+
c.arg_name 'PATH'
|
47
|
+
c.flag %i[f file]
|
48
|
+
|
49
|
+
c.desc 'Mark task as @done with date'
|
50
|
+
c.switch %i[finish done], negatable: false
|
51
|
+
|
52
|
+
c.desc 'Search for files X directories deep'
|
53
|
+
c.arg_name 'DEPTH'
|
54
|
+
c.flag %i[d depth], must_match: /^[1-9]$/, type: :integer, default_value: 1
|
55
|
+
|
56
|
+
c.action do |global_options, options, args|
|
57
|
+
reader = TTY::Reader.new
|
58
|
+
append = options[:at] ? options[:at] =~ /^[ae]/i : global_options[:add_at] =~ /^[ae]/
|
59
|
+
|
60
|
+
if NA.global_file
|
61
|
+
target = File.expand_path(NA.global_file)
|
62
|
+
unless File.exist?(target)
|
63
|
+
res = NA.yn(NA::Color.template('{by}Specified file not found, create it'), default: true)
|
64
|
+
if res
|
65
|
+
basename = File.basename(target, ".#{NA.extension}")
|
66
|
+
NA.create_todo(target, basename, template: global_options[:template])
|
67
|
+
else
|
68
|
+
puts NA::Color.template('{r}Cancelled{x}')
|
69
|
+
Process.exit 1
|
70
|
+
end
|
71
|
+
end
|
72
|
+
elsif options[:file]
|
73
|
+
target = File.expand_path(options[:file])
|
74
|
+
unless File.exist?(target)
|
75
|
+
res = NA.yn(NA::Color.template('{by}Specified file not found, create it'), default: true)
|
76
|
+
if res
|
77
|
+
basename = File.basename(target, ".#{NA.extension}")
|
78
|
+
NA.create_todo(target, basename, template: global_options[:template])
|
79
|
+
else
|
80
|
+
puts NA::Color.template('{r}Cancelled{x}')
|
81
|
+
Process.exit 1
|
82
|
+
end
|
83
|
+
end
|
84
|
+
elsif options[:todo]
|
85
|
+
todo = []
|
86
|
+
all_req = options[:todo] !~ /[+!\-]/
|
87
|
+
options[:todo].split(/ *, */).each do |a|
|
88
|
+
m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
89
|
+
todo.push({
|
90
|
+
token: m['tok'],
|
91
|
+
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
92
|
+
negate: !m['req'].nil? && m['req'] =~ /[!\-]/
|
93
|
+
})
|
94
|
+
end
|
95
|
+
dirs = NA.match_working_dir(todo)
|
96
|
+
if dirs.count.positive?
|
97
|
+
target = dirs[0]
|
98
|
+
else
|
99
|
+
todo = "#{options[:todo].sub(/#{NA.extension}$/, '')}.#{NA.extension}"
|
100
|
+
target = File.expand_path(todo)
|
101
|
+
unless File.exist?(target)
|
102
|
+
|
103
|
+
res = NA.yn(NA::Color.template("{by}Specified file not found, create #{todo}"), default: true)
|
104
|
+
NA.notify('{r}Cancelled{x}', exit_code: 1) unless res
|
105
|
+
|
106
|
+
basename = File.basename(target, ".#{NA.extension}")
|
107
|
+
NA.create_todo(target, basename, template: global_options[:template])
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
else
|
112
|
+
files = NA.find_files(depth: options[:depth])
|
113
|
+
if files.count.zero?
|
114
|
+
res = NA.yn(NA::Color.template('{by}No todo file found, create one'), default: true)
|
115
|
+
if res
|
116
|
+
basename = File.expand_path('.').split('/').last
|
117
|
+
target = "#{basename}.#{NA.extension}"
|
118
|
+
NA.create_todo(target, basename, template: global_options[:template])
|
119
|
+
files = NA.find_files(depth: 1)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
target = files.count > 1 ? NA.select_file(files) : files[0]
|
123
|
+
NA.notify('{r}Cancelled{x}', exit_code: 1) unless files.count.positive? && File.exist?(target)
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
action = if args.count.positive?
|
128
|
+
args.join(' ').strip
|
129
|
+
elsif $stdin.isatty && TTY::Which.exist?('gum')
|
130
|
+
`gum input --placeholder "Enter a task" --char-limit=500 --width=#{TTY::Screen.columns}`.strip
|
131
|
+
elsif $stdin.isatty
|
132
|
+
puts NA::Color.template('{bm}Enter task:{x}')
|
133
|
+
reader.read_line(NA::Color.template('{by}> {bw}')).strip
|
134
|
+
end
|
135
|
+
|
136
|
+
if action.nil? || action.empty?
|
137
|
+
puts 'Empty input, cancelled'
|
138
|
+
Process.exit 1
|
139
|
+
end
|
140
|
+
|
141
|
+
if options[:priority]&.to_i&.positive?
|
142
|
+
action = "#{action.gsub(/@priority\(\d+\)/, '')} @priority(#{options[:priority]})"
|
143
|
+
end
|
144
|
+
|
145
|
+
note_rx = /^(.+) \((.*?)\)$/
|
146
|
+
split_note = if action =~ note_rx
|
147
|
+
n = Regexp.last_match(2)
|
148
|
+
action.sub!(note_rx, '\1').strip!
|
149
|
+
n
|
150
|
+
end
|
151
|
+
|
152
|
+
na_tag = NA.na_tag
|
153
|
+
if options[:x]
|
154
|
+
na_tag = ''
|
155
|
+
else
|
156
|
+
na_tag = options[:tag] unless options[:tag].nil?
|
157
|
+
na_tag = " @#{na_tag}"
|
158
|
+
end
|
159
|
+
|
160
|
+
action = "#{action.gsub(/#{na_tag}\b/, '')}#{na_tag}"
|
161
|
+
|
162
|
+
stdin_note = NA.stdin ? NA.stdin.split("\n") : []
|
163
|
+
|
164
|
+
line_note = if options[:note] && $stdin.isatty
|
165
|
+
puts stdin_note unless stdin_note.nil?
|
166
|
+
if TTY::Which.exist?('gum')
|
167
|
+
args = ['--placeholder "Enter additional note, CTRL-d to save"']
|
168
|
+
args << '--char-limit 0'
|
169
|
+
args << '--width $(tput cols)'
|
170
|
+
`gum write #{args.join(' ')}`.strip.split("\n")
|
171
|
+
else
|
172
|
+
puts NA::Color.template('{bm}Enter a note, {bw}CTRL-d{bm} to end editing{bw}')
|
173
|
+
reader.read_multiline
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
note = stdin_note.empty? ? [] : stdin_note
|
178
|
+
note.concat(split_note) unless split_note.nil?
|
179
|
+
note.concat(line_note) unless line_note.nil?
|
180
|
+
|
181
|
+
NA.add_action(target, options[:project], action, note, finish: options[:finish], append: append)
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
desc 'Mark an action as @done and archive'
|
4
|
+
arg_name 'ACTION'
|
5
|
+
command %i[archive] do |c|
|
6
|
+
c.example 'na archive "An existing task"',
|
7
|
+
desc: 'Find "An existing task", mark @done if needed, and move to archive'
|
8
|
+
|
9
|
+
c.desc 'Prompt for additional notes. Input will be appended to any existing note.
|
10
|
+
If STDIN input (piped) is detected, it will be used as a note.'
|
11
|
+
c.switch %i[n note], negatable: false
|
12
|
+
|
13
|
+
c.desc 'Overwrite note instead of appending'
|
14
|
+
c.switch %i[o overwrite], negatable: false
|
15
|
+
|
16
|
+
c.desc 'Archive all done tasks'
|
17
|
+
c.switch %i[done], negatable: false
|
18
|
+
|
19
|
+
c.desc 'Specify the file to search for the task'
|
20
|
+
c.arg_name 'PATH'
|
21
|
+
c.flag %i[file]
|
22
|
+
|
23
|
+
c.desc 'Search for files X directories deep'
|
24
|
+
c.arg_name 'DEPTH'
|
25
|
+
c.flag %i[d depth], must_match: /^[1-9]$/, type: :integer, default_value: 1
|
26
|
+
|
27
|
+
c.desc 'Match actions containing tag. Allows value comparisons'
|
28
|
+
c.arg_name 'TAG'
|
29
|
+
c.flag %i[tagged], multiple: true
|
30
|
+
|
31
|
+
c.desc 'Act on all matches immediately (no menu)'
|
32
|
+
c.switch %i[all], negatable: false
|
33
|
+
|
34
|
+
c.desc 'Interpret search pattern as regular expression'
|
35
|
+
c.switch %i[e regex], negatable: false
|
36
|
+
|
37
|
+
c.desc 'Match pattern exactly'
|
38
|
+
c.switch %i[x exact], negatable: false
|
39
|
+
|
40
|
+
c.action do |global, options, args|
|
41
|
+
if options[:done]
|
42
|
+
options[:tagged] = ['done']
|
43
|
+
options[:all] = true
|
44
|
+
end
|
45
|
+
|
46
|
+
options[:done] = true
|
47
|
+
options[:finish] = true
|
48
|
+
options[:project] = 'Archive'
|
49
|
+
options[:archive] = true
|
50
|
+
options[:a] = true
|
51
|
+
|
52
|
+
cmd = commands[:update]
|
53
|
+
action = cmd.send(:get_action, nil)
|
54
|
+
action.call(global, options, args)
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
desc 'Display the changelog'
|
4
|
+
command %i[changes changelog] do |c|
|
5
|
+
c.action do |_, _, _|
|
6
|
+
changelog = File.expand_path(File.join(File.dirname(__FILE__), '..', 'CHANGELOG.md'))
|
7
|
+
pagers = [
|
8
|
+
'mdless',
|
9
|
+
'mdcat',
|
10
|
+
'bat',
|
11
|
+
ENV['PAGER'],
|
12
|
+
'less -FXr',
|
13
|
+
ENV['GIT_PAGER'],
|
14
|
+
'more -r'
|
15
|
+
]
|
16
|
+
pager = pagers.find { |cmd| TTY::Which.exist?(cmd.split.first) }
|
17
|
+
system %(#{pager} "#{changelog}")
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
desc 'Find and mark an action as @done'
|
4
|
+
arg_name 'ACTION'
|
5
|
+
command %i[complete finish] do |c|
|
6
|
+
c.example 'na complete "An existing task"',
|
7
|
+
desc: 'Find "An existing task" and mark @done'
|
8
|
+
c.example 'na finish "An existing task"',
|
9
|
+
desc: 'Alias for complete'
|
10
|
+
|
11
|
+
c.desc 'Prompt for additional notes. Input will be appended to any existing note.
|
12
|
+
If STDIN input (piped) is detected, it will be used as a note.'
|
13
|
+
c.switch %i[n note], negatable: false
|
14
|
+
|
15
|
+
c.desc 'Overwrite note instead of appending'
|
16
|
+
c.switch %i[o overwrite], negatable: false
|
17
|
+
|
18
|
+
c.desc 'Add a @done tag to action and move to Archive'
|
19
|
+
c.switch %i[a archive], negatable: false
|
20
|
+
|
21
|
+
c.desc 'Specify the file to search for the task'
|
22
|
+
c.arg_name 'PATH'
|
23
|
+
c.flag %i[file]
|
24
|
+
|
25
|
+
c.desc 'Search for files X directories deep'
|
26
|
+
c.arg_name 'DEPTH'
|
27
|
+
c.flag %i[d depth], must_match: /^[1-9]$/, type: :integer, default_value: 1
|
28
|
+
|
29
|
+
c.desc 'Match actions containing tag. Allows value comparisons'
|
30
|
+
c.arg_name 'TAG'
|
31
|
+
c.flag %i[tagged], multiple: true
|
32
|
+
|
33
|
+
c.desc 'Act on all matches immediately (no menu)'
|
34
|
+
c.switch %i[all], negatable: false
|
35
|
+
|
36
|
+
c.desc 'Interpret search pattern as regular expression'
|
37
|
+
c.switch %i[e regex], negatable: false
|
38
|
+
|
39
|
+
c.desc 'Match pattern exactly'
|
40
|
+
c.switch %i[x exact], negatable: false
|
41
|
+
|
42
|
+
c.action do |global, options, args|
|
43
|
+
options[:finish] = true
|
44
|
+
options[:f] = true
|
45
|
+
options[:project] = 'Archive' if options[:archive]
|
46
|
+
|
47
|
+
cmd = commands[:update]
|
48
|
+
action = cmd.send(:get_action, nil)
|
49
|
+
action.call(global, options, args)
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
desc 'Open a todo file in the default editor'
|
4
|
+
long_desc 'Let the system choose the defualt, (e.g. TaskPaper), or specify a command line utility (e.g. vim).
|
5
|
+
If more than one todo file is found, a menu is displayed.'
|
6
|
+
command %i[edit] do |c|
|
7
|
+
c.example 'na edit', desc: 'Open the main todo file in the default editor'
|
8
|
+
c.example 'na edit -d 3 -a vim', desc: 'Display a menu of all todo files three levels deep from the
|
9
|
+
current directory, open selection in vim.'
|
10
|
+
|
11
|
+
c.desc 'Recurse to depth'
|
12
|
+
c.arg_name 'DEPTH'
|
13
|
+
c.default_value 1
|
14
|
+
c.flag %i[d depth], type: :integer, must_match: /^\d+$/
|
15
|
+
|
16
|
+
c.desc 'Specify an editor CLI'
|
17
|
+
c.arg_name 'EDITOR'
|
18
|
+
c.flag %i[e editor]
|
19
|
+
|
20
|
+
c.desc 'Specify a Mac app'
|
21
|
+
c.arg_name 'EDITOR'
|
22
|
+
c.flag %i[a app]
|
23
|
+
|
24
|
+
c.action do |global_options, options, args|
|
25
|
+
depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
|
26
|
+
3
|
27
|
+
else
|
28
|
+
options[:depth].nil? ? global_options[:depth].to_i : options[:depth].to_i
|
29
|
+
end
|
30
|
+
files = NA.find_files(depth: depth)
|
31
|
+
files.delete_if { |f| f !~ /.*?(#{args.join('|')}).*?.#{NA.extension}/ } if args.count.positive?
|
32
|
+
|
33
|
+
file = if files.count > 1
|
34
|
+
NA.select_file(files)
|
35
|
+
else
|
36
|
+
files[0]
|
37
|
+
end
|
38
|
+
|
39
|
+
if options[:editor]
|
40
|
+
system options[:editor], file
|
41
|
+
else
|
42
|
+
NA.edit_file(file: file, app: options[:app])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
desc 'Find actions matching a search pattern'
|
4
|
+
long_desc 'Search tokens are separated by spaces. Actions matching all tokens in the pattern will be shown
|
5
|
+
(partial matches allowed). Add a + before a token to make it required, e.g. `na find +feature +maybe`,
|
6
|
+
add a - or ! to ignore matches containing that token.'
|
7
|
+
arg_name 'PATTERN'
|
8
|
+
command %i[find grep] do |c|
|
9
|
+
c.example 'na find feature idea swift', desc: 'Find all actions containing feature, idea, and swift'
|
10
|
+
c.example 'na find feature idea -swift', desc: 'Find all actions containing feature and idea but NOT swift'
|
11
|
+
c.example 'na find -x feature idea', desc: 'Find all actions containing the exact text "feature idea"'
|
12
|
+
|
13
|
+
c.desc 'Interpret search pattern as regular expression'
|
14
|
+
c.switch %i[e regex], negatable: false
|
15
|
+
|
16
|
+
c.desc 'Match pattern exactly'
|
17
|
+
c.switch %i[x exact], negatable: false
|
18
|
+
|
19
|
+
c.desc 'Recurse to depth'
|
20
|
+
c.arg_name 'DEPTH'
|
21
|
+
c.flag %i[d depth], type: :integer, must_match: /^\d+$/
|
22
|
+
|
23
|
+
c.desc 'Show actions from a specific todo file in history. May use wildcards (* and ?)'
|
24
|
+
c.arg_name 'TODO_PATH'
|
25
|
+
c.flag %i[in]
|
26
|
+
|
27
|
+
c.desc 'Include notes in output'
|
28
|
+
c.switch %i[notes], negatable: true, default_value: false
|
29
|
+
|
30
|
+
c.desc 'Combine search tokens with OR, displaying actions matching ANY of the terms'
|
31
|
+
c.switch %i[o or], negatable: false
|
32
|
+
|
33
|
+
c.desc 'Show actions from a specific project'
|
34
|
+
c.arg_name 'PROJECT[/SUBPROJECT]'
|
35
|
+
c.flag %i[proj project]
|
36
|
+
|
37
|
+
c.desc 'Match actions containing tag. Allows value comparisons'
|
38
|
+
c.arg_name 'TAG'
|
39
|
+
c.flag %i[tagged], multiple: true
|
40
|
+
|
41
|
+
c.desc 'Include @done actions'
|
42
|
+
c.switch %i[done]
|
43
|
+
|
44
|
+
c.desc 'Show actions not matching search pattern'
|
45
|
+
c.switch %i[v invert], negatable: false
|
46
|
+
|
47
|
+
c.desc 'Save this search for future use'
|
48
|
+
c.arg_name 'TITLE'
|
49
|
+
c.flag %i[save]
|
50
|
+
|
51
|
+
c.desc 'Output actions nested by file'
|
52
|
+
c.switch %[nest], negatable: false
|
53
|
+
|
54
|
+
c.desc 'Output actions nested by file and project'
|
55
|
+
c.switch %[omnifocus], negatable: false
|
56
|
+
|
57
|
+
c.action do |global_options, options, args|
|
58
|
+
options[:nest] = true if options[:omnifocus]
|
59
|
+
|
60
|
+
if options[:save]
|
61
|
+
title = options[:save].gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
|
62
|
+
NA.save_search(title, "#{NA.command_line.join(' ').sub(/ --save[= ]*\S+/, '').split(' ').map { |t| %("#{t}") }.join(' ')}")
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
|
67
|
+
3
|
68
|
+
else
|
69
|
+
options[:depth].nil? ? global_options[:depth].to_i : options[:depth].to_i
|
70
|
+
end
|
71
|
+
|
72
|
+
all_req = options[:tagged].join(' ') !~ /[+!\-]/ && !options[:or]
|
73
|
+
tags = []
|
74
|
+
options[:tagged].join(',').split(/ *, */).each do |arg|
|
75
|
+
m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
|
76
|
+
|
77
|
+
tags.push({
|
78
|
+
tag: m['tag'].wildcard_to_rx,
|
79
|
+
comp: m['op'],
|
80
|
+
value: m['val'],
|
81
|
+
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
82
|
+
negate: !m['req'].nil? && m['req'] =~ /[!\-]/
|
83
|
+
})
|
84
|
+
end
|
85
|
+
|
86
|
+
tokens = nil
|
87
|
+
if options[:exact]
|
88
|
+
tokens = args.join(' ')
|
89
|
+
elsif options[:regex]
|
90
|
+
tokens = Regexp.new(args.join(' '), Regexp::IGNORECASE)
|
91
|
+
else
|
92
|
+
tokens = []
|
93
|
+
all_req = args.join(' ') !~ /[+!\-]/ && !options[:or]
|
94
|
+
|
95
|
+
args.join(' ').split(/ /).each do |arg|
|
96
|
+
m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
97
|
+
tokens.push({
|
98
|
+
token: m['tok'],
|
99
|
+
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
100
|
+
negate: !m['req'].nil? && m['req'] =~ /[!\-]/
|
101
|
+
})
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
todo = nil
|
106
|
+
if options[:in]
|
107
|
+
todo = []
|
108
|
+
options[:in].split(/ *, */).each do |a|
|
109
|
+
m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
110
|
+
todo.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
|
+
|
118
|
+
files, actions, = NA.parse_actions(depth: depth,
|
119
|
+
done: options[:done],
|
120
|
+
query: todo,
|
121
|
+
search: tokens,
|
122
|
+
tag: tags,
|
123
|
+
negate: options[:invert],
|
124
|
+
regex: options[:regex],
|
125
|
+
project: options[:project],
|
126
|
+
require_na: false)
|
127
|
+
regexes = if tokens.is_a?(Array)
|
128
|
+
tokens.delete_if { |token| token[:negate] }.map { |token| token[:token] }
|
129
|
+
else
|
130
|
+
[tokens]
|
131
|
+
end
|
132
|
+
|
133
|
+
NA.output_actions(actions, depth, files: files, regexes: regexes, notes: options[:notes], nest: options[:nest], nest_projects: options[:omnifocus])
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
desc 'Create a new todo file in the current directory'
|
4
|
+
arg_name 'PROJECT', optional: true
|
5
|
+
command %i[init create] do |c|
|
6
|
+
c.example 'na init', desc: 'Generate a new todo file, prompting for project name'
|
7
|
+
c.example 'na init warpspeed', desc: 'Generate a new todo for a project called warpspeed'
|
8
|
+
|
9
|
+
c.action do |global_options, _options, args|
|
10
|
+
reader = TTY::Reader.new
|
11
|
+
if args.count.positive?
|
12
|
+
project = args.join(' ')
|
13
|
+
elsif
|
14
|
+
project = File.expand_path('.').split('/').last
|
15
|
+
project = reader.read_line(NA::Color.template('{y}Project name {bw}> {x}'), value: project).strip if $stdin.isatty
|
16
|
+
end
|
17
|
+
|
18
|
+
target = "#{project}.#{NA.extension}"
|
19
|
+
|
20
|
+
if File.exist?(target)
|
21
|
+
res = NA.yn(NA::Color.template("{r}File {bw}#{target}{r} already exists, overwrite it"), default: false)
|
22
|
+
Process.exit 1 unless res
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
NA.create_todo(target, project, template: global_options[:template])
|
27
|
+
end
|
28
|
+
end
|