na 1.2.28 → 1.2.30
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/Gemfile.lock +1 -1
- data/README.md +38 -30
- data/bin/commands/add.rb +167 -164
- data/bin/commands/archive.rb +54 -51
- data/bin/commands/changes.rb +18 -15
- data/bin/commands/complete.rb +50 -47
- data/bin/commands/completed.rb +99 -0
- data/bin/commands/edit.rb +37 -34
- data/bin/commands/find.rb +109 -106
- data/bin/commands/init.rb +22 -19
- data/bin/commands/next.rb +135 -131
- data/bin/commands/projects.rb +27 -24
- data/bin/commands/prompt.rb +41 -38
- data/bin/commands/saved.rb +42 -34
- data/bin/commands/tagged.rb +137 -134
- data/bin/commands/todos.rb +23 -20
- data/bin/commands/update.rb +215 -207
- data/bin/na +12 -12
- data/lib/na/array.rb +13 -0
- data/lib/na/help_monkey_patch.rb +32 -0
- data/lib/na/next_action.rb +3 -2
- data/lib/na/pager.rb +94 -0
- data/lib/na/string.rb +10 -0
- data/lib/na/version.rb +1 -1
- data/lib/na.rb +2 -0
- data/src/_README.md +1 -1
- metadata +7 -3
data/bin/commands/archive.rb
CHANGED
@@ -1,56 +1,59 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
options[:
|
3
|
+
class App
|
4
|
+
extend GLI::App
|
5
|
+
desc 'Mark an action as @done and archive'
|
6
|
+
arg_name 'ACTION'
|
7
|
+
command %i[archive] do |c|
|
8
|
+
c.example 'na archive "An existing task"',
|
9
|
+
desc: 'Find "An existing task", mark @done if needed, and move to archive'
|
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 'Archive all done tasks'
|
19
|
+
c.switch %i[done], 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
|
+
if options[:done]
|
44
|
+
options[:tagged] = ['done']
|
45
|
+
options[:all] = true
|
46
|
+
end
|
47
|
+
|
48
|
+
options[:done] = true
|
49
|
+
options[:finish] = true
|
50
|
+
options[:project] = 'Archive'
|
51
|
+
options[:archive] = true
|
52
|
+
options[:a] = true
|
53
|
+
|
54
|
+
cmd = commands[:update]
|
55
|
+
action = cmd.send(:get_action, nil)
|
56
|
+
action.call(global, options, args)
|
44
57
|
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
58
|
end
|
56
59
|
end
|
data/bin/commands/changes.rb
CHANGED
@@ -1,19 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
'
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
3
|
+
class App
|
4
|
+
extend GLI::App
|
5
|
+
desc 'Display the changelog'
|
6
|
+
command %i[changes changelog] do |c|
|
7
|
+
c.action do |_, _, _|
|
8
|
+
changelog = File.expand_path(File.join(File.dirname(__FILE__), '..', 'CHANGELOG.md'))
|
9
|
+
pagers = [
|
10
|
+
'mdless',
|
11
|
+
'mdcat',
|
12
|
+
'bat',
|
13
|
+
ENV['PAGER'],
|
14
|
+
'less -FXr',
|
15
|
+
ENV['GIT_PAGER'],
|
16
|
+
'more -r'
|
17
|
+
]
|
18
|
+
pager = pagers.find { |cmd| TTY::Which.exist?(cmd.split.first) }
|
19
|
+
system %(#{pager} "#{changelog}")
|
20
|
+
end
|
18
21
|
end
|
19
22
|
end
|
data/bin/commands/complete.rb
CHANGED
@@ -1,51 +1,54 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
options
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
3
|
+
class App
|
4
|
+
extend GLI::App
|
5
|
+
desc 'Find and mark an action as @done'
|
6
|
+
arg_name 'ACTION'
|
7
|
+
command %i[complete finish] do |c|
|
8
|
+
c.example 'na complete "An existing task"',
|
9
|
+
desc: 'Find "An existing task" and mark @done'
|
10
|
+
c.example 'na finish "An existing task"',
|
11
|
+
desc: 'Alias for complete'
|
12
|
+
|
13
|
+
c.desc 'Prompt for additional notes. Input will be appended to any existing note.
|
14
|
+
If STDIN input (piped) is detected, it will be used as a note.'
|
15
|
+
c.switch %i[n note], negatable: false
|
16
|
+
|
17
|
+
c.desc 'Overwrite note instead of appending'
|
18
|
+
c.switch %i[o overwrite], negatable: false
|
19
|
+
|
20
|
+
c.desc 'Add a @done tag to action and move to Archive'
|
21
|
+
c.switch %i[a archive], negatable: false
|
22
|
+
|
23
|
+
c.desc 'Specify the file to search for the task'
|
24
|
+
c.arg_name 'PATH'
|
25
|
+
c.flag %i[file]
|
26
|
+
|
27
|
+
c.desc 'Search for files X directories deep'
|
28
|
+
c.arg_name 'DEPTH'
|
29
|
+
c.flag %i[d depth], must_match: /^[1-9]$/, type: :integer, default_value: 1
|
30
|
+
|
31
|
+
c.desc 'Match actions containing tag. Allows value comparisons'
|
32
|
+
c.arg_name 'TAG'
|
33
|
+
c.flag %i[tagged], multiple: true
|
34
|
+
|
35
|
+
c.desc 'Act on all matches immediately (no menu)'
|
36
|
+
c.switch %i[all], negatable: false
|
37
|
+
|
38
|
+
c.desc 'Interpret search pattern as regular expression'
|
39
|
+
c.switch %i[e regex], negatable: false
|
40
|
+
|
41
|
+
c.desc 'Match pattern exactly'
|
42
|
+
c.switch %i[x exact], negatable: false
|
43
|
+
|
44
|
+
c.action do |global, options, args|
|
45
|
+
options[:finish] = true
|
46
|
+
options[:f] = true
|
47
|
+
options[:project] = 'Archive' if options[:archive]
|
48
|
+
|
49
|
+
cmd = commands[:update]
|
50
|
+
action = cmd.send(:get_action, nil)
|
51
|
+
action.call(global, options, args)
|
52
|
+
end
|
50
53
|
end
|
51
54
|
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class App
|
4
|
+
extend GLI::App
|
5
|
+
desc 'Display completed actions'
|
6
|
+
long_desc 'Search tokens are separated by spaces. Actions matching all tokens in the pattern will be shown
|
7
|
+
(partial matches allowed). Add a + before a token to make it required, e.g. `na completed +feature +maybe`,
|
8
|
+
add a - or ! to ignore matches containing that token.'
|
9
|
+
arg_name 'PATTERN', optional: true, multiple: true
|
10
|
+
command %i[completed finished] do |c|
|
11
|
+
c.example 'na completed', desc: 'display completed actions'
|
12
|
+
c.example 'na completed --before "2 days ago"',
|
13
|
+
desc: 'display actions completed more than two days ago'
|
14
|
+
c.example 'na completed --on yesterday',
|
15
|
+
desc: 'display actions completed yesterday'
|
16
|
+
c.example 'na completed --after "1 week ago"',
|
17
|
+
desc: 'display actions completed in the last week'
|
18
|
+
c.example 'na completed feature',
|
19
|
+
desc: 'display completed actions matcning "feature"'
|
20
|
+
|
21
|
+
c.desc 'Display actions completed before (natural language) date string'
|
22
|
+
c.arg_name 'DATE_STRING'
|
23
|
+
c.flag %i[b before]
|
24
|
+
|
25
|
+
c.desc 'Display actions completed on (natural language) date string'
|
26
|
+
c.arg_name 'DATE_STRING'
|
27
|
+
c.flag %i[on]
|
28
|
+
|
29
|
+
c.desc 'Display actions completed after (natural language) date string'
|
30
|
+
c.arg_name 'DATE_STRING'
|
31
|
+
c.flag %i[a after]
|
32
|
+
|
33
|
+
c.desc 'Combine before, on, and/or after with OR, displaying actions matching ANY of the ranges'
|
34
|
+
c.switch %i[o or], negatable: false
|
35
|
+
|
36
|
+
c.desc 'Recurse to depth'
|
37
|
+
c.arg_name 'DEPTH'
|
38
|
+
c.flag %i[d depth], type: :integer, must_match: /^\d+$/
|
39
|
+
|
40
|
+
c.desc 'Show actions from a specific todo file in history. May use wildcards (* and ?)'
|
41
|
+
c.arg_name 'TODO_PATH'
|
42
|
+
c.flag %i[in]
|
43
|
+
|
44
|
+
c.desc 'Include notes in output'
|
45
|
+
c.switch %i[notes], negatable: true, default_value: false
|
46
|
+
|
47
|
+
c.desc 'Show actions from a specific project'
|
48
|
+
c.arg_name 'PROJECT[/SUBPROJECT]'
|
49
|
+
c.flag %i[proj project]
|
50
|
+
|
51
|
+
c.desc 'Match actions containing tag. Allows value comparisons'
|
52
|
+
c.arg_name 'TAG'
|
53
|
+
c.flag %i[tagged], multiple: true
|
54
|
+
|
55
|
+
c.desc 'Output actions nested by file'
|
56
|
+
c.switch %[nest], negatable: false
|
57
|
+
|
58
|
+
c.desc 'Output actions nested by file and project'
|
59
|
+
c.switch %[omnifocus], negatable: false
|
60
|
+
|
61
|
+
c.desc 'Save this search for future use'
|
62
|
+
c.arg_name 'TITLE'
|
63
|
+
c.flag %i[save]
|
64
|
+
|
65
|
+
c.action do |_global_options, options, args|
|
66
|
+
tag_string = []
|
67
|
+
if options[:before] || options[:on] || options[:after]
|
68
|
+
tag_string << "done<#{options[:before]}" if options[:before]
|
69
|
+
tag_string << "done=#{options[:on]}" if options[:on]
|
70
|
+
tag_string << "done>#{options[:after]}" if options[:after]
|
71
|
+
else
|
72
|
+
tag_string << 'done'
|
73
|
+
end
|
74
|
+
|
75
|
+
tag_string.concat(options[:tagged]) if options[:tagged]
|
76
|
+
|
77
|
+
if args.empty?
|
78
|
+
cmd_string = %(tagged --done "#{tag_string.join(',')}")
|
79
|
+
else
|
80
|
+
cmd_string = %(find --tagged "#{tag_string.join(',')}" --done #{args.join(' ')})
|
81
|
+
end
|
82
|
+
|
83
|
+
cmd_string += ' --or' if options[:or]
|
84
|
+
cmd_string += %( --in "#{options[:in]}") if options[:in]
|
85
|
+
cmd_string += %( --project "#{options[:project]}") if options[:project]
|
86
|
+
cmd_string += %( --depth #{options[:depth]}) if options[:depth]
|
87
|
+
cmd_string += ' --nest' if options[:nest]
|
88
|
+
cmd_string += ' --omnifocus' if options[:omnifocus]
|
89
|
+
cmd_string += " --#{options[:notes] ? 'notes' : 'no-notes' }"
|
90
|
+
|
91
|
+
if options[:save]
|
92
|
+
title = options[:save].gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
|
93
|
+
NA.save_search(title, cmd_string)
|
94
|
+
end
|
95
|
+
|
96
|
+
exit run(Shellwords.shellsplit(cmd_string))
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/bin/commands/edit.rb
CHANGED
@@ -1,45 +1,48 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
command
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
class App
|
4
|
+
extend GLI::App
|
5
|
+
desc 'Open a todo file in the default editor'
|
6
|
+
long_desc 'Let the system choose the defualt, (e.g. TaskPaper), or specify a command line utility (e.g. vim).
|
7
|
+
If more than one todo file is found, a menu is displayed.'
|
8
|
+
command %i[edit] do |c|
|
9
|
+
c.example 'na edit', desc: 'Open the main todo file in the default editor'
|
10
|
+
c.example 'na edit -d 3 -a vim', desc: 'Display a menu of all todo files three levels deep from the
|
11
|
+
current directory, open selection in vim.'
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
c.desc 'Recurse to depth'
|
14
|
+
c.arg_name 'DEPTH'
|
15
|
+
c.default_value 1
|
16
|
+
c.flag %i[d depth], type: :integer, must_match: /^\d+$/
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
18
|
+
c.desc 'Specify an editor CLI'
|
19
|
+
c.arg_name 'EDITOR'
|
20
|
+
c.flag %i[e editor]
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
|
22
|
+
c.desc 'Specify a Mac app'
|
23
|
+
c.arg_name 'EDITOR'
|
24
|
+
c.flag %i[a app]
|
23
25
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
26
|
+
c.action do |global_options, options, args|
|
27
|
+
depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
|
28
|
+
3
|
29
|
+
else
|
30
|
+
options[:depth].nil? ? global_options[:depth].to_i : options[:depth].to_i
|
31
|
+
end
|
32
|
+
files = NA.find_files(depth: depth)
|
33
|
+
files.delete_if { |f| f !~ /.*?(#{args.join('|')}).*?.#{NA.extension}/ } if args.count.positive?
|
32
34
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
file = if files.count > 1
|
36
|
+
NA.select_file(files)
|
37
|
+
else
|
38
|
+
files[0]
|
39
|
+
end
|
38
40
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
41
|
+
if options[:editor]
|
42
|
+
system options[:editor], file
|
43
|
+
else
|
44
|
+
NA.edit_file(file: file, app: options[:app])
|
45
|
+
end
|
43
46
|
end
|
44
47
|
end
|
45
48
|
end
|