na 1.2.35 → 1.2.37
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 +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 /^>$/
|