na 1.2.27 → 1.2.29

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e1ae4b30284b89cff9f9e5469ec923668c2b314e907b9f8c398b6b6cc46dee37
4
- data.tar.gz: 4b4b84c3b995e38a1d02e487129324316a3949d36003418b09602a214a2d560e
3
+ metadata.gz: fb14c4a36c28fce7e726c0a4c5b50680e05b9efa87c2f41d02a1af3040027401
4
+ data.tar.gz: 461c9f651ec9838548288efd9b972d7d21ffeca11089fe01b92a10676ce018bc
5
5
  SHA512:
6
- metadata.gz: f1d145af86c822a521d991ff1dcc2e759a96a88d86b317249ed48eba1e60d1a5425f91c09d5a452418e75162b249fc1c55a74f3dc9ba2d6736dbe2dd3489d23a
7
- data.tar.gz: 0aae383f45f4f73e96d15dd24d1e0002b2dcaca77ebf5e6dd7a0a6d956f3233c01e959497b8f60cd328c6d0a690f2575583f7760cb433a9ea5a60d33268425f6
6
+ metadata.gz: e664e139c0927e205b8f16c3d7036d8912ed5e75aa2da772d8ac5a11b0a3fc72cec49cbb4f07fb45cb533130ed49f10efa4b348d5b708f7840b04b9492347252
7
+ data.tar.gz: f1b9bb77e3d73d73dd6d3b4ac4db52979cf6345d7cb5231106fec6670e914116375762619bfe67c9697caa5622b65053935edee91bf21116d07be69b9d16ee4c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ### 1.2.29
2
+
3
+ 2023-08-21 11:08
4
+
5
+ #### FIXED
6
+
7
+ - Reverting subcommand breakup
8
+
9
+ ### 1.2.28
10
+
11
+ 2023-08-21 11:01
12
+
1
13
  ### 1.2.27
2
14
 
3
15
  2023-08-21 10:58
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- na (1.2.27)
4
+ na (1.2.29)
5
5
  chronic (~> 0.10, >= 0.10.2)
6
6
  gli (~> 2.21.0)
7
7
  mdless (~> 1.0, >= 1.0.32)
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.27
12
+ The current version of `na` is 1.2.29
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.27
80
+ 1.2.29
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 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.
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). If more than one todo file is found, a menu is displayed.
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
- current directory, open selection in vim.
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 (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.
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), 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).
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, fuzzy matched. Separate directories with /, :, or a space, e.g. `na projects code/marked`
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), 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).
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 list of todos will be fuzzy matched. Separate directories with /, :, or a space, e.g. `na todos code/marked`
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. If multiple todo files are found in the current directory, a menu will allow you to pick which file to act on.
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. If STDIN input (piped) is detected, it will be used as a 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)
@@ -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