na 1.2.29 → 1.2.31

Sign up to get free protection for your applications and to get access to all the features.
data/bin/commands/next.rb CHANGED
@@ -1,143 +1,147 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- desc 'Show next actions'
4
- long_desc 'Next actions are actions which contain the next action tag (default @na),
5
- do not contain @done, and are not in the Archive project.
6
-
7
- Arguments will target a todo file from history, whether it\'s in the current
8
- directory or not. Todo file queries can include path components separated by /
9
- or :, and may use wildcards (`*` to match any text, `?` to match a single character). Multiple queries allowed (separate arguments or separated by comma).'
10
- arg_name 'QUERY', optional: true
11
- command %i[next show] do |c|
12
- c.example 'na next', desc: 'display the next actions from any todo files in the current directory'
13
- c.example 'na next -d 3', desc: 'display the next actions from the current directory, traversing 3 levels deep'
14
- c.example 'na next marked', desc: 'display next actions for a project you visited in the past'
15
-
16
- c.desc 'Recurse to depth'
17
- c.arg_name 'DEPTH'
18
- c.flag %i[d depth], type: :integer, must_match: /^[1-9]$/
19
-
20
- c.desc 'Display matches from a known todo file'
21
- c.arg_name 'TODO_FILE'
22
- c.flag %i[in todo], multiple: true
23
-
24
- c.desc 'Alternate tag to search for'
25
- c.arg_name 'TAG'
26
- c.flag %i[t tag]
27
-
28
- c.desc 'Show actions from a specific project'
29
- c.arg_name 'PROJECT[/SUBPROJECT]'
30
- c.flag %i[proj project]
31
-
32
- c.desc 'Match actions containing tag. Allows value comparisons'
33
- c.arg_name 'TAG'
34
- c.flag %i[tagged], multiple: true
35
-
36
- c.desc 'Filter results using search terms'
37
- c.arg_name 'QUERY'
38
- c.flag %i[search], multiple: true
39
-
40
- c.desc 'Search query is regular expression'
41
- c.switch %i[regex], negatable: false
42
-
43
- c.desc 'Search query is exact text match (not tokens)'
44
- c.switch %i[exact], negatable: false
45
-
46
- c.desc 'Include notes in output'
47
- c.switch %i[notes], negatable: true, default_value: false
48
-
49
- c.desc 'Include @done actions'
50
- c.switch %i[done]
51
-
52
- c.desc 'Output actions nested by file'
53
- c.switch %[nest], negatable: false
54
-
55
- c.desc 'Output actions nested by file and project'
56
- c.switch %[omnifocus], negatable: false
57
-
58
- c.action do |global_options, options, args|
59
- if global_options[:add]
60
- cmd = ['add']
61
- cmd.push('--note') if global_options[:note]
62
- cmd.concat(['--priority', global_options[:priority]]) if global_options[:priority]
63
- cmd.push(NA.command_line) if NA.command_line.count > 1
64
- cmd.unshift(*NA.globals)
65
- exit run(cmd)
66
- end
3
+ class App
4
+ extend GLI::App
5
+ desc 'Show next actions'
6
+ long_desc 'Next actions are actions which contain the next action tag (default @na),
7
+ do not contain @done, and are not in the Archive project.
8
+
9
+ Arguments will target a todo file from history, whether it\'s in the current
10
+ directory or not. Todo file queries can include path components separated by /
11
+ or :, and may use wildcards (`*` to match any text, `?` to match a single character). Multiple queries allowed (separate arguments or separated by comma).'
12
+ arg_name 'QUERY', optional: true
13
+ command %i[next show] do |c|
14
+ c.example 'na next', desc: 'display the next actions from any todo files in the current directory'
15
+ c.example 'na next -d 3', desc: 'display the next actions from the current directory, traversing 3 levels deep'
16
+ c.example 'na next marked', desc: 'display next actions for a project you visited in the past'
17
+
18
+ c.desc 'Recurse to depth'
19
+ c.arg_name 'DEPTH'
20
+ c.flag %i[d depth], type: :integer, must_match: /^[1-9]$/
21
+
22
+ c.desc 'Display matches from a known todo file'
23
+ c.arg_name 'TODO_FILE'
24
+ c.flag %i[in todo], multiple: true
25
+
26
+ c.desc 'Alternate tag to search for'
27
+ c.arg_name 'TAG'
28
+ c.flag %i[t tag]
29
+
30
+ c.desc 'Show actions from a specific project'
31
+ c.arg_name 'PROJECT[/SUBPROJECT]'
32
+ c.flag %i[proj project]
33
+
34
+ c.desc 'Match actions containing tag. Allows value comparisons'
35
+ c.arg_name 'TAG'
36
+ c.flag %i[tagged], multiple: true
37
+
38
+ c.desc 'Filter results using search terms'
39
+ c.arg_name 'QUERY'
40
+ c.flag %i[search], multiple: true
41
+
42
+ c.desc 'Search query is regular expression'
43
+ c.switch %i[regex], negatable: false
44
+
45
+ c.desc 'Search query is exact text match (not tokens)'
46
+ c.switch %i[exact], negatable: false
47
+
48
+ c.desc 'Include notes in output'
49
+ c.switch %i[notes], negatable: true, default_value: false
50
+
51
+ c.desc 'Include @done actions'
52
+ c.switch %i[done]
53
+
54
+ c.desc 'Output actions nested by file'
55
+ c.switch %[nest], negatable: false
56
+
57
+ c.desc 'Output actions nested by file and project'
58
+ c.switch %[omnifocus], negatable: false
59
+
60
+ c.action do |global_options, options, args|
61
+ if global_options[:add]
62
+ cmd = ['add']
63
+ cmd.push('--note') if global_options[:note]
64
+ cmd.concat(['--priority', global_options[:priority]]) if global_options[:priority]
65
+ cmd.push(NA.command_line) if NA.command_line.count > 1
66
+ cmd.unshift(*NA.globals)
67
+ exit run(cmd)
68
+ end
67
69
 
68
- options[:nest] = true if options[:omnifocus]
69
-
70
- depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
71
- 3
72
- else
73
- options[:depth].nil? ? global_options[:depth].to_i : options[:depth].to_i
74
- end
75
-
76
- all_req = options[:tagged].join(' ') !~ /[+!\-]/ && !options[:or]
77
- tags = []
78
- options[:tagged].join(',').split(/ *, */).each do |arg|
79
- m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
80
-
81
- tags.push({
82
- tag: m['tag'].wildcard_to_rx,
83
- comp: m['op'],
84
- value: m['val'],
85
- required: all_req || (!m['req'].nil? && m['req'] == '+'),
86
- negate: !m['req'].nil? && m['req'] =~ /[!\-]/
87
- })
88
- end
70
+ options[:nest] = true if options[:omnifocus]
71
+
72
+ depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
73
+ 3
74
+ else
75
+ options[:depth].nil? ? global_options[:depth].to_i : options[:depth].to_i
76
+ end
77
+
78
+ all_req = options[:tagged].join(' ') !~ /[+!\-]/ && !options[:or]
79
+ tags = []
80
+ options[:tagged].join(',').split(/ *, */).each do |arg|
81
+ m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
82
+
83
+ tags.push({
84
+ tag: m['tag'].wildcard_to_rx,
85
+ comp: m['op'],
86
+ value: m['val'],
87
+ required: all_req || (!m['req'].nil? && m['req'] == '+'),
88
+ negate: !m['req'].nil? && m['req'] =~ /[!\-]/
89
+ })
90
+ end
89
91
 
90
- args.concat(options[:in])
91
- if args.count.positive?
92
- all_req = args.join(' ') !~ /[+!\-]/
93
-
94
- tokens = []
95
- args.each do |arg|
96
- arg.split(/ *, */).each do |a|
97
- m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
98
- tokens.push({
99
- token: m['tok'],
100
- required: !m['req'].nil? && m['req'] == '+',
101
- negate: !m['req'].nil? && m['req'] =~ /[!\-]/
102
- })
92
+ args.concat(options[:in])
93
+ if args.count.positive?
94
+ all_req = args.join(' ') !~ /[+!\-]/
95
+
96
+ tokens = []
97
+ args.each do |arg|
98
+ arg.split(/ *, */).each do |a|
99
+ m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
100
+ tokens.push({
101
+ token: m['tok'],
102
+ required: !m['req'].nil? && m['req'] == '+',
103
+ negate: !m['req'].nil? && m['req'] =~ /[!\-]/
104
+ })
105
+ end
103
106
  end
104
107
  end
105
- end
106
108
 
107
- search = nil
108
- if options[:search]
109
- if options[:exact]
110
- search = options[:search].join(' ')
111
- elsif options[:regex]
112
- search = Regexp.new(options[:search].join(' '), Regexp::IGNORECASE)
113
- else
114
- search = []
115
- all_req = options[:search].join(' ') !~ /[+!\-]/ && !options[:or]
116
-
117
- options[:search].join(' ').split(/ /).each do |arg|
118
- m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
119
- search.push({
120
- token: m['tok'],
121
- required: all_req || (!m['req'].nil? && m['req'] == '+'),
122
- negate: !m['req'].nil? && m['req'] =~ /[!\-]/
123
- })
109
+ search = nil
110
+ if options[:search]
111
+ if options[:exact]
112
+ search = options[:search].join(' ')
113
+ elsif options[:regex]
114
+ search = Regexp.new(options[:search].join(' '), Regexp::IGNORECASE)
115
+ else
116
+ search = []
117
+ all_req = options[:search].join(' ') !~ /[+!\-]/ && !options[:or]
118
+
119
+ options[:search].join(' ').split(/ /).each do |arg|
120
+ m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
121
+ search.push({
122
+ token: m['tok'],
123
+ required: all_req || (!m['req'].nil? && m['req'] == '+'),
124
+ negate: !m['req'].nil? && m['req'] =~ /[!\-]/
125
+ })
126
+ end
124
127
  end
125
128
  end
126
- end
127
-
128
- NA.na_tag = options[:tag] unless options[:tag].nil?
129
- require_na = true
130
129
 
131
- tag = [{ tag: NA.na_tag, value: nil }, { tag: 'done', value: nil, negate: true }]
132
- tag.concat(tags)
133
- files, actions, = NA.parse_actions(depth: depth,
134
- done: options[:done],
135
- query: tokens,
136
- tag: tag,
137
- search: search,
138
- project: options[:project],
139
- require_na: require_na)
140
-
141
- NA.output_actions(actions, depth, files: files, notes: options[:notes], nest: options[:nest], nest_projects: options[:omnifocus])
130
+ NA.na_tag = options[:tag] unless options[:tag].nil?
131
+ require_na = true
132
+
133
+ tag = [{ tag: NA.na_tag, value: nil }]
134
+ tag << { tag: 'done', value: nil, negate: true } unless options[:done]
135
+ tag.concat(tags)
136
+ files, actions, = NA.parse_actions(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
+
144
+ NA.output_actions(actions, depth, files: files, notes: options[:notes], nest: options[:nest], nest_projects: options[:omnifocus])
145
+ end
142
146
  end
143
147
  end
@@ -1,34 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- desc 'Show list of projects for a file'
4
- long_desc 'Arguments will be interpreted as a query for a known todo file,
5
- fuzzy matched. Separate directories with /, :, or a space, e.g. `na projects code/marked`'
6
- arg_name 'QUERY', optional: true
7
- command %i[projects] do |c|
8
- c.desc 'Search for files X directories deep'
9
- c.arg_name 'DEPTH'
10
- c.flag %i[d depth], must_match: /^[1-9]$/, type: :integer, default_value: 1
3
+ class App
4
+ extend GLI::App
5
+ desc 'Show list of projects for a file'
6
+ long_desc 'Arguments will be interpreted as a query for a known todo file,
7
+ fuzzy matched. Separate directories with /, :, or a space, e.g. `na projects code/marked`'
8
+ arg_name 'QUERY', optional: true
9
+ command %i[projects] do |c|
10
+ c.desc 'Search for files X directories deep'
11
+ c.arg_name 'DEPTH'
12
+ c.flag %i[d depth], must_match: /^[1-9]$/, type: :integer, default_value: 1
11
13
 
12
- c.desc 'Output projects as paths instead of hierarchy'
13
- c.switch %i[p paths], negatable: false
14
+ c.desc 'Output projects as paths instead of hierarchy'
15
+ c.switch %i[p paths], negatable: false
14
16
 
15
- c.action do |_global_options, options, args|
16
- if args.count.positive?
17
- all_req = args.join(' ') !~ /[+!-]/
17
+ c.action do |_global_options, options, args|
18
+ if args.count.positive?
19
+ all_req = args.join(' ') !~ /[+!-]/
18
20
 
19
- tokens = [{ token: '*', required: all_req, negate: false }]
20
- args.each do |arg|
21
- arg.split(/ *, */).each do |a|
22
- m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
23
- tokens.push({
24
- token: m['tok'],
25
- required: all_req || (!m['req'].nil? && m['req'] == '+'),
26
- negate: !m['req'].nil? && m['req'] =~ /[!-]/
27
- })
21
+ tokens = [{ token: '*', required: all_req, negate: false }]
22
+ args.each do |arg|
23
+ arg.split(/ *, */).each do |a|
24
+ m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
25
+ tokens.push({
26
+ token: m['tok'],
27
+ required: all_req || (!m['req'].nil? && m['req'] == '+'),
28
+ negate: !m['req'].nil? && m['req'] =~ /[!-]/
29
+ })
30
+ end
28
31
  end
29
32
  end
30
- end
31
33
 
32
- NA.list_projects(query: tokens, depth: options[:depth], paths: options[:paths])
34
+ NA.list_projects(query: tokens, depth: options[:depth], paths: options[:paths])
35
+ end
33
36
  end
34
37
  end
@@ -1,48 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- desc 'Show or install prompt hooks for the current shell'
4
- long_desc 'Installing the prompt hook allows you to automatically
5
- list next actions when you cd into a directory'
6
- command %i[prompt] do |c|
7
- c.desc 'Output the prompt hook for the current shell to STDOUT. Pass an argument to
8
- specify a shell (zsh, bash, fish)'
9
- c.arg_name 'SHELL', optional: true
10
- c.command %i[show] do |s|
11
- s.action do |_global_options, _options, args|
12
- shell = if args.count.positive?
13
- args[0]
14
- else
15
- File.basename(ENV['SHELL'])
16
- end
3
+ class App
4
+ extend GLI::App
5
+ desc 'Show or install prompt hooks for the current shell'
6
+ long_desc 'Installing the prompt hook allows you to automatically
7
+ list next actions when you cd into a directory'
8
+ command %i[prompt] do |c|
9
+ c.desc 'Output the prompt hook for the current shell to STDOUT. Pass an argument to
10
+ specify a shell (zsh, bash, fish)'
11
+ c.arg_name 'SHELL', optional: true
12
+ c.command %i[show] do |s|
13
+ s.action do |_global_options, _options, args|
14
+ shell = if args.count.positive?
15
+ args[0]
16
+ else
17
+ File.basename(ENV['SHELL'])
18
+ end
17
19
 
18
- case shell
19
- when /^f/i
20
- NA::Prompt.show_prompt_hook(:fish)
21
- when /^z/i
22
- NA::Prompt.show_prompt_hook(:zsh)
23
- when /^b/i
24
- NA::Prompt.show_prompt_hook(:bash)
20
+ case shell
21
+ when /^f/i
22
+ NA::Prompt.show_prompt_hook(:fish)
23
+ when /^z/i
24
+ NA::Prompt.show_prompt_hook(:zsh)
25
+ when /^b/i
26
+ NA::Prompt.show_prompt_hook(:bash)
27
+ end
25
28
  end
26
29
  end
27
- end
28
30
 
29
- c.desc 'Install the hook for the current shell to the appropriate startup file.'
30
- c.arg_name 'SHELL', optional: true
31
- c.command %i[install] do |s|
32
- s.action do |_global_options, _options, args|
33
- shell = if args.count.positive?
34
- args[0]
35
- else
36
- File.basename(ENV['SHELL'])
37
- end
31
+ c.desc 'Install the hook for the current shell to the appropriate startup file.'
32
+ c.arg_name 'SHELL', optional: true
33
+ c.command %i[install] do |s|
34
+ s.action do |_global_options, _options, args|
35
+ shell = if args.count.positive?
36
+ args[0]
37
+ else
38
+ File.basename(ENV['SHELL'])
39
+ end
38
40
 
39
- case shell
40
- when /^f/i
41
- NA::Prompt.install_prompt_hook(:fish)
42
- when /^z/i
43
- NA::Prompt.install_prompt_hook(:zsh)
44
- when /^b/i
45
- NA::Prompt.install_prompt_hook(:bash)
41
+ case shell
42
+ when /^f/i
43
+ NA::Prompt.install_prompt_hook(:fish)
44
+ when /^z/i
45
+ NA::Prompt.install_prompt_hook(:zsh)
46
+ when /^b/i
47
+ NA::Prompt.install_prompt_hook(:bash)
48
+ end
46
49
  end
47
50
  end
48
51
  end
@@ -1,39 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- desc 'Execute a saved search'
4
- long_desc 'Run without argument to list saved searches'
5
- arg_name 'SEARCH_TITLE', optional: true
6
- command %i[saved] do |c|
7
- c.example 'na tagged "+maybe,+priority<=3" --save maybelater', description: 'save a search called "maybelater"'
8
- c.example 'na saved maybelater', description: 'perform the search named "maybelater"'
9
- c.example 'na saved maybe',
10
- description: 'perform the search named "maybelater", assuming no other searches match "maybe"'
11
- c.example 'na maybe',
12
- description: 'na run with no command and a single argument automatically performs a matching saved search'
13
- c.example 'na saved', description: 'list available searches'
14
-
15
- c.desc 'Open the saved search file in $EDITOR'
16
- c.switch %i[e edit], negatable: false
17
-
18
- c.desc 'Delete the specified search definition'
19
- c.switch %i[d delete], negatable: false
20
-
21
- c.action do |_global_options, options, args|
22
- NA.edit_searches if options[:edit]
23
-
24
- searches = NA.load_searches
25
- if args.empty?
26
- NA.notify("{bg}Saved searches stored in {bw}#{NA.database_path(file: 'saved_searches.yml')}")
27
- NA.notify(searches.map { |k, v| "{y}#{k}: {w}#{v}" }.join("\n"), exit_code: 0)
28
- else
29
- NA.delete_search(args) if options[:delete]
30
-
31
- keys = searches.keys.delete_if { |k| k !~ /#{args[0]}/ }
32
- NA.notify("{r}Search #{args[0]} not found", exit_code: 1) if keys.empty?
33
-
34
- key = keys[0]
35
- cmd = Shellwords.shellsplit(searches[key])
36
- exit run(cmd)
3
+ class App
4
+ extend GLI::App
5
+ desc 'Execute a saved search'
6
+ long_desc 'Run without argument to list saved searches'
7
+ arg_name 'SEARCH_TITLE', optional: true, multiple: true
8
+ command %i[saved] do |c|
9
+ c.example 'na tagged "+maybe,+priority<=3" --save maybelater', desc: 'save a search called "maybelater"'
10
+ c.example 'na saved maybelater', desc: 'perform the search named "maybelater"'
11
+ c.example 'na saved maybe',
12
+ desc: 'perform the search named "maybelater", assuming no other searches match "maybe"'
13
+ c.example 'na maybe',
14
+ desc: 'na run with no command and a single argument automatically performs a matching saved search'
15
+ c.example 'na saved', desc: 'list available searches'
16
+
17
+ c.desc 'Open the saved search file in $EDITOR'
18
+ c.switch %i[e edit], negatable: false
19
+
20
+ c.desc 'Delete the specified search definition'
21
+ c.switch %i[d delete], negatable: false
22
+
23
+ c.action do |_global_options, options, args|
24
+ NA.edit_searches if options[:edit]
25
+
26
+ if args.empty?
27
+ searches = NA.load_searches
28
+ NA.notify("{bg}Saved searches stored in {bw}#{NA.database_path(file: 'saved_searches.yml')}")
29
+ NA.notify(searches.map { |k, v| "{y}#{k}: {w}#{v}" }.join("\n"), exit_code: 0)
30
+ else
31
+ args.each do |arg|
32
+ searches = NA.load_searches
33
+
34
+ NA.delete_search(arg) if options[:delete]
35
+
36
+ keys = searches.keys.delete_if { |k| k !~ /#{arg}/ }
37
+ NA.notify("{r}Search #{arg} not found", exit_code: 1) if keys.empty?
38
+
39
+ key = keys[0]
40
+ cmd = Shellwords.shellsplit(searches[key])
41
+ run(cmd)
42
+ end
43
+ exit
44
+ end
37
45
  end
38
46
  end
39
47
  end