na 1.2.35 → 1.2.37
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/Gemfile.lock +1 -1
- data/README.md +79 -2
- data/bin/commands/complete.rb +4 -0
- data/bin/commands/edit.rb +6 -5
- data/bin/commands/find.rb +27 -19
- data/bin/commands/next.rb +22 -18
- data/bin/commands/prompt.rb +2 -0
- data/bin/commands/tagged.rb +32 -25
- data/bin/commands/todos.rb +3 -3
- data/bin/commands/undo.rb +22 -0
- data/bin/commands/update.rb +12 -2
- data/lib/na/action.rb +27 -1
- data/lib/na/actions.rb +87 -0
- data/lib/na/editor.rb +123 -0
- data/lib/na/next_action.rb +203 -471
- data/lib/na/todo.rb +183 -0
- data/lib/na/version.rb +1 -1
- data/lib/na.rb +3 -0
- data/src/_README.md +36 -12
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 35e6d784b55e397b8843663130837bcebb9041640a7833d650cedc9b783289e2
|
4
|
+
data.tar.gz: a1ba45d7ba0d77e122667bc5c93629832dc379ee5d2275076182849055c8d72e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1ee63a7b2bfd58496bf2047cd80a16f30bf255ca1edc8b0aa5a84a9abc8e4d1ccdd8082cd8f01fb8db60d5b73c7e2014f4f847369ac0d39274b5929ef8173f07
|
7
|
+
data.tar.gz: d0321cddedf590c96a18cbd1096c7982240cb8c172c9f3491e0569ecfdf8882955898056ff3fd56d9e529d91be2a9ca56f3003bdbf6f1517a9658d08a302ca30
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,31 @@
|
|
1
|
+
### 1.2.37
|
2
|
+
|
3
|
+
2023-09-01 12:42
|
4
|
+
|
5
|
+
#### NEW
|
6
|
+
|
7
|
+
- `na undo` command to undo last change or last change to file specified in arguments
|
8
|
+
|
9
|
+
#### IMPROVED
|
10
|
+
|
11
|
+
- Disable pagination when using --omnifocus
|
12
|
+
- Refactoring codebase
|
13
|
+
- `--in TODO` option for `na complete`
|
14
|
+
|
15
|
+
### 1.2.36
|
16
|
+
|
17
|
+
2023-09-01 12:41
|
18
|
+
|
19
|
+
#### NEW
|
20
|
+
|
21
|
+
- `na undo` command to undo last change or last change to file specified in arguments
|
22
|
+
|
23
|
+
#### IMPROVED
|
24
|
+
|
25
|
+
- Disable pagination when using --omnifocus
|
26
|
+
- Refactoring codebase
|
27
|
+
- `--in TODO` option for `na complete`
|
28
|
+
|
1
29
|
### 1.2.35
|
2
30
|
|
3
31
|
2023-08-30 11:59
|
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.37
|
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.37
|
81
81
|
|
82
82
|
GLOBAL OPTIONS
|
83
83
|
-a, --add - Add a next action (deprecated, for backwards compatibility)
|
@@ -116,6 +116,7 @@ COMMANDS
|
|
116
116
|
saved - Execute a saved search
|
117
117
|
tagged - Find actions matching a tag
|
118
118
|
todos - Show list of known todo files
|
119
|
+
undo - Undo the last change
|
119
120
|
update - Update an existing action
|
120
121
|
```
|
121
122
|
|
@@ -512,6 +513,82 @@ EXAMPLES
|
|
512
513
|
na update --archive My cool action
|
513
514
|
```
|
514
515
|
|
516
|
+
##### changelog
|
517
|
+
|
518
|
+
View recent changes with `na changelog` or `na changes`.
|
519
|
+
|
520
|
+
```
|
521
|
+
NAME
|
522
|
+
changes - Display the changelog
|
523
|
+
|
524
|
+
SYNOPSIS
|
525
|
+
|
526
|
+
na [global options] changes
|
527
|
+
```
|
528
|
+
|
529
|
+
##### complete
|
530
|
+
|
531
|
+
Mark an action as complete, shortcut for `na update --finish`.
|
532
|
+
|
533
|
+
```
|
534
|
+
NAME
|
535
|
+
complete - Find and mark an action as @done
|
536
|
+
|
537
|
+
SYNOPSIS
|
538
|
+
|
539
|
+
na [global options] complete [command options] ACTION
|
540
|
+
|
541
|
+
COMMAND OPTIONS
|
542
|
+
-a, --archive - Add a @done tag to action and move to Archive
|
543
|
+
--all - Act on all matches immediately (no menu)
|
544
|
+
-d, --depth=DEPTH - Search for files X directories deep (default: 1)
|
545
|
+
-e, --regex - Interpret search pattern as regular expression
|
546
|
+
--file=PATH - Specify the file to search for the task (default: none)
|
547
|
+
--in, --todo=TODO_FILE - Use a known todo file, partial matches allowed (default: none)
|
548
|
+
-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.
|
549
|
+
-o, --overwrite - Overwrite note instead of appending
|
550
|
+
--tagged=TAG - Match actions containing tag. Allows value comparisons (may be used more than once, default: none)
|
551
|
+
--to, --project, --proj=PROJECT - Move action to specific project (default: none)
|
552
|
+
-x, --exact - Match pattern exactly
|
553
|
+
|
554
|
+
EXAMPLES
|
555
|
+
|
556
|
+
# Find "An existing task" and mark @done
|
557
|
+
na complete "An existing task"
|
558
|
+
|
559
|
+
# Alias for complete
|
560
|
+
na finish "An existing task"
|
561
|
+
```
|
562
|
+
|
563
|
+
##### archive
|
564
|
+
|
565
|
+
Mark an action as complete and move to archive, shortcut for `na update --archive`.
|
566
|
+
|
567
|
+
```
|
568
|
+
NAME
|
569
|
+
archive - Mark an action as @done and archive
|
570
|
+
|
571
|
+
SYNOPSIS
|
572
|
+
|
573
|
+
na [global options] archive [command options] ACTION
|
574
|
+
|
575
|
+
COMMAND OPTIONS
|
576
|
+
--all - Act on all matches immediately (no menu)
|
577
|
+
-d, --depth=DEPTH - Search for files X directories deep (default: 1)
|
578
|
+
--done - Archive all done tasks
|
579
|
+
-e, --regex - Interpret search pattern as regular expression
|
580
|
+
--file=PATH - Specify the file to search for the task (default: none)
|
581
|
+
-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.
|
582
|
+
-o, --overwrite - Overwrite note instead of appending
|
583
|
+
--tagged=TAG - Match actions containing tag. Allows value comparisons (may be used more than once, default: none)
|
584
|
+
-x, --exact - Match pattern exactly
|
585
|
+
|
586
|
+
EXAMPLE
|
587
|
+
|
588
|
+
# Find "An existing task", mark @done if needed, and move to archive
|
589
|
+
na archive "An existing task"
|
590
|
+
```
|
591
|
+
|
515
592
|
### Configuration
|
516
593
|
|
517
594
|
Global options such as todo extension and default next action tag can be stored permanently by using the `na initconfig` command. Run na with the global options you'd like to set, and add `initconfig` at the end of the command. A file will be written to `~/.na.rc`. You can edit this manually, or just update it using the `initconfig --force` command to overwrite it with new settings.
|
data/bin/commands/complete.rb
CHANGED
@@ -28,6 +28,10 @@ class App
|
|
28
28
|
c.arg_name 'PATH'
|
29
29
|
c.flag %i[file]
|
30
30
|
|
31
|
+
c.desc 'Use a known todo file, partial matches allowed'
|
32
|
+
c.arg_name 'TODO_FILE'
|
33
|
+
c.flag %i[in todo]
|
34
|
+
|
31
35
|
c.desc 'Search for files X directories deep'
|
32
36
|
c.arg_name 'DEPTH'
|
33
37
|
c.flag %i[d depth], must_match: /^[1-9]$/, type: :integer, default_value: 1
|
data/bin/commands/edit.rb
CHANGED
@@ -140,11 +140,12 @@ class App
|
|
140
140
|
NA.notify('{r}No search terms provided', exit_code: 1) if tokens.nil? && options[:tagged].empty?
|
141
141
|
|
142
142
|
targets.each do |target|
|
143
|
-
NA.update_action(target,
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
143
|
+
NA.update_action(target,
|
144
|
+
tokens,
|
145
|
+
done: options[:done],
|
146
|
+
edit: options[:edit],
|
147
|
+
project: target_proj,
|
148
|
+
tagged: tags)
|
148
149
|
end
|
149
150
|
end
|
150
151
|
end
|
data/bin/commands/find.rb
CHANGED
@@ -112,47 +112,55 @@ class App
|
|
112
112
|
tokens = Regexp.new(search, Regexp::IGNORECASE)
|
113
113
|
else
|
114
114
|
tokens = []
|
115
|
-
all_req = search !~ /[
|
115
|
+
all_req = search !~ /[+!-]/ && !options[:or]
|
116
116
|
|
117
117
|
search.split(/ /).each do |arg|
|
118
118
|
m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
119
119
|
tokens.push({
|
120
120
|
token: Regexp.escape(m['tok']),
|
121
121
|
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
122
|
-
negate: !m['req'].nil? && m['req'] =~ /[
|
122
|
+
negate: !m['req'].nil? && m['req'] =~ /[!-]/
|
123
123
|
})
|
124
124
|
end
|
125
125
|
end
|
126
126
|
|
127
|
-
|
127
|
+
todos = nil
|
128
128
|
if options[:in]
|
129
|
-
|
129
|
+
todos = []
|
130
130
|
options[:in].split(/ *, */).each do |a|
|
131
131
|
m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
132
|
+
todos.push({
|
133
|
+
token: m['tok'],
|
134
|
+
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
135
|
+
negate: !m['req'].nil? && m['req'] =~ /[!-]/
|
136
|
+
})
|
137
137
|
end
|
138
138
|
end
|
139
139
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
140
|
+
todo = NA::Todo.new({
|
141
|
+
depth: depth,
|
142
|
+
done: options[:done],
|
143
|
+
query: todos,
|
144
|
+
search: tokens,
|
145
|
+
tag: tags,
|
146
|
+
negate: options[:invert],
|
147
|
+
regex: options[:regex],
|
148
|
+
project: options[:project],
|
149
|
+
require_na: false
|
150
|
+
})
|
151
|
+
|
149
152
|
regexes = if tokens.is_a?(Array)
|
150
153
|
tokens.delete_if { |token| token[:negate] }.map { |token| token[:token] }
|
151
154
|
else
|
152
155
|
[tokens]
|
153
156
|
end
|
154
157
|
|
155
|
-
|
158
|
+
todo.actions.output(depth,
|
159
|
+
files: todo.files,
|
160
|
+
regexes: regexes,
|
161
|
+
notes: options[:notes],
|
162
|
+
nest: options[:nest],
|
163
|
+
nest_projects: options[:omnifocus])
|
156
164
|
end
|
157
165
|
end
|
158
166
|
end
|
data/bin/commands/next.rb
CHANGED
@@ -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,23 +75,23 @@ class App
|
|
75
75
|
options[:depth].nil? ? global_options[:depth].to_i : options[:depth].to_i
|
76
76
|
end
|
77
77
|
|
78
|
-
all_req = options[:tagged].join(' ') !~ /[
|
78
|
+
all_req = options[:tagged].join(' ') !~ /[+!-]/ && !options[:or]
|
79
79
|
tags = []
|
80
80
|
options[:tagged].join(',').split(/ *, */).each do |arg|
|
81
|
-
m = arg.match(/^(?<req>[
|
81
|
+
m = arg.match(/^(?<req>[+!-])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
|
82
82
|
|
83
83
|
tags.push({
|
84
84
|
tag: m['tag'].wildcard_to_rx,
|
85
85
|
comp: m['op'],
|
86
86
|
value: m['val'],
|
87
87
|
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
88
|
-
negate: !m['req'].nil? && m['req'] =~ /[
|
88
|
+
negate: !m['req'].nil? && m['req'] =~ /[!-]/
|
89
89
|
})
|
90
90
|
end
|
91
91
|
|
92
92
|
args.concat(options[:in])
|
93
93
|
if args.count.positive?
|
94
|
-
all_req = args.join(' ') !~ /[
|
94
|
+
all_req = args.join(' ') !~ /[+!-]/
|
95
95
|
|
96
96
|
tokens = []
|
97
97
|
args.each do |arg|
|
@@ -100,7 +100,7 @@ class App
|
|
100
100
|
tokens.push({
|
101
101
|
token: m['tok'],
|
102
102
|
required: !m['req'].nil? && m['req'] == '+',
|
103
|
-
negate: !m['req'].nil? && m['req'] =~ /[
|
103
|
+
negate: !m['req'].nil? && m['req'] =~ /[!-]/
|
104
104
|
})
|
105
105
|
end
|
106
106
|
end
|
@@ -114,14 +114,14 @@ class App
|
|
114
114
|
search = Regexp.new(options[:search].join(' '), Regexp::IGNORECASE)
|
115
115
|
else
|
116
116
|
search = []
|
117
|
-
all_req = options[:search].join(' ') !~ /[
|
117
|
+
all_req = options[:search].join(' ') !~ /[+!-]/ && !options[:or]
|
118
118
|
|
119
119
|
options[:search].join(' ').split(/ /).each do |arg|
|
120
120
|
m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
121
121
|
search.push({
|
122
122
|
token: m['tok'],
|
123
123
|
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
124
|
-
negate: !m['req'].nil? && m['req'] =~ /[
|
124
|
+
negate: !m['req'].nil? && m['req'] =~ /[!-]/
|
125
125
|
})
|
126
126
|
end
|
127
127
|
end
|
@@ -133,15 +133,19 @@ class App
|
|
133
133
|
tag = [{ tag: NA.na_tag, value: nil }]
|
134
134
|
tag << { tag: 'done', value: nil, negate: true } unless options[:done]
|
135
135
|
tag.concat(tags)
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
136
|
+
todo = NA::Todo.new({ depth: depth,
|
137
|
+
done: options[:done],
|
138
|
+
query: tokens,
|
139
|
+
tag: tag,
|
140
|
+
search: search,
|
141
|
+
project: options[:project],
|
142
|
+
require_na: require_na })
|
143
|
+
NA::Pager.paginate = false if options[:omnifocus]
|
144
|
+
todo.actions.output(depth,
|
145
|
+
files: todo.files,
|
146
|
+
notes: options[:notes],
|
147
|
+
nest: options[:nest],
|
148
|
+
nest_projects: options[:omnifocus])
|
145
149
|
end
|
146
150
|
end
|
147
151
|
end
|
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/tagged.rb
CHANGED
@@ -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,7 +79,7 @@ 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
84
|
m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?) *(?:(?<op>[=<>]{1,2}|[*$\^]=) *(?<val>.*?))?$/)
|
84
85
|
next if m.nil?
|
@@ -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
136
|
NA.notify('{br}No actions matched search', exit_code: 1) if tags.empty? && tokens.empty?
|
135
137
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
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
@@ -10,16 +10,16 @@ class App
|
|
10
10
|
command %i[todos] do |c|
|
11
11
|
c.action do |_global_options, _options, args|
|
12
12
|
if args.count.positive?
|
13
|
-
all_req = args.join(' ') !~ /[
|
13
|
+
all_req = args.join(' ') !~ /[+!-]/
|
14
14
|
|
15
15
|
tokens = [{ token: '*', required: all_req, negate: false }]
|
16
16
|
args.each do |arg|
|
17
17
|
arg.split(/ *, */).each do |a|
|
18
|
-
m = a.match(/^(?<req>[
|
18
|
+
m = a.match(/^(?<req>[+!-])?(?<tok>.*?)$/)
|
19
19
|
tokens.push({
|
20
20
|
token: m['tok'],
|
21
21
|
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
22
|
-
negate: !m['req'].nil? && m['req'] =~ /[
|
22
|
+
negate: !m['req'].nil? && m['req'] =~ /[!-]/
|
23
23
|
})
|
24
24
|
end
|
25
25
|
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?
|
@@ -211,7 +213,15 @@ class App
|
|
211
213
|
|
212
214
|
end
|
213
215
|
else
|
214
|
-
files = NA.
|
216
|
+
files = NA.find_files_matching({
|
217
|
+
depth: options[:depth],
|
218
|
+
done: options[:done],
|
219
|
+
project: target_proj,
|
220
|
+
regex: options[:regex],
|
221
|
+
require_na: false,
|
222
|
+
search: tokens,
|
223
|
+
tag: tags
|
224
|
+
})
|
215
225
|
NA.notify('{r}No todo file found', exit_code: 1) if files.count.zero?
|
216
226
|
|
217
227
|
targets = files.count > 1 ? NA.select_file(files, multiple: true) : [files[0]]
|
data/lib/na/action.rb
CHANGED
@@ -18,6 +18,32 @@ module NA
|
|
18
18
|
@note = note
|
19
19
|
end
|
20
20
|
|
21
|
+
def process(priority: 0, finish: false, add_tag: [], remove_tag: [], note: [])
|
22
|
+
string = @action.dup
|
23
|
+
|
24
|
+
if priority&.positive?
|
25
|
+
string.gsub!(/(?<=\A| )@priority\(\d+\)/, '').strip!
|
26
|
+
string += " @priority(#{priority})"
|
27
|
+
end
|
28
|
+
|
29
|
+
add_tag.each do |tag|
|
30
|
+
string.gsub!(/(?<=\A| )@#{tag.gsub(/([()*?])/, '\\\\1')}(\(.*?\))?/, '')
|
31
|
+
string.strip!
|
32
|
+
string += " @#{tag}"
|
33
|
+
end
|
34
|
+
|
35
|
+
remove_tag.each do |tag|
|
36
|
+
string.gsub!(/(?<=\A| )@#{tag.gsub(/([()*?])/, '\\\\1')}(\(.*?\))?/, '')
|
37
|
+
string.strip!
|
38
|
+
end
|
39
|
+
|
40
|
+
string = "#{string.strip} @done(#{Time.now.strftime('%Y-%m-%d %H:%M')})" if finish && string !~ /(?<=\A| )@done/
|
41
|
+
|
42
|
+
@action = string
|
43
|
+
@action.expand_date_tags
|
44
|
+
@note = note unless note.empty?
|
45
|
+
end
|
46
|
+
|
21
47
|
def to_s
|
22
48
|
note = if @note.count.positive?
|
23
49
|
"\n#{@note.join("\n")}"
|
@@ -179,7 +205,7 @@ module NA
|
|
179
205
|
date = Time.parse(date.strftime('%Y-%m-%d 12:00'))
|
180
206
|
end
|
181
207
|
|
182
|
-
|
208
|
+
# NA.notify("{dw}Comparing #{tag_date} #{tag[:comp]} #{date}{x}", debug: true)
|
183
209
|
|
184
210
|
case tag[:comp]
|
185
211
|
when /^>$/
|