na 1.2.28 → 1.2.30

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -1,146 +1,149 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- desc 'Find actions matching a tag'
4
- long_desc 'Finds actions with tags matching the arguments. An action is shown if it
5
- contains all of the tags listed. Add a + before a tag to make it required
6
- and others optional. You can specify values using TAG=VALUE pairs.
7
- Use <, >, and = for numeric comparisons, and *=, ^=, and $= for text comparisons.
8
- Date comparisons use natural language (`na tagged "due<=today"`) and
9
- are detected automatically.'
10
- arg_name 'TAG[=VALUE]'
11
- command %i[tagged] do |c|
12
- c.example 'na tagged maybe', desc: 'Show all actions tagged @maybe'
13
- c.example 'na tagged -d 3 "feature, idea"', desc: 'Show all actions tagged @feature AND @idea, recurse 3 levels'
14
- c.example 'na tagged --or "feature, idea"', desc: 'Show all actions tagged @feature OR @idea'
15
- c.example 'na tagged "priority>=4"', desc: 'Show actions with @priority(4) or @priority(5)'
16
- c.example 'na tagged "due<in 2 days"', desc: 'Show actions with a due date coming up in the next 2 days'
17
-
18
- c.desc 'Recurse to depth'
19
- c.arg_name 'DEPTH'
20
- c.default_value 1
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 tags with OR, displaying actions matching ANY of the tags'
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 'Filter results using search terms'
38
- c.arg_name 'QUERY'
39
- c.flag %i[search], multiple: true
40
-
41
- c.desc 'Search query is regular expression'
42
- c.switch %i[regex], negatable: false
43
-
44
- c.desc 'Search query is exact text match (not tokens)'
45
- c.switch %i[exact], negatable: false
46
-
47
- c.desc 'Include @done actions'
48
- c.switch %i[done]
49
-
50
- c.desc 'Show actions not matching tags'
51
- c.switch %i[v invert], negatable: false
52
-
53
- c.desc 'Save this search for future use'
54
- c.arg_name 'TITLE'
55
- c.flag %i[save]
56
-
57
- c.desc 'Output actions nested by file'
58
- c.switch %[nest], negatable: false
59
-
60
- c.desc 'Output actions nested by file and project'
61
- c.switch %[omnifocus], negatable: false
62
-
63
- c.action do |global_options, options, args|
64
- options[:nest] = true if options[:omnifocus]
65
-
66
- if options[:save]
67
- title = options[:save].gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
68
- NA.save_search(title, "#{NA.command_line.join(' ').sub(/ --save[= ]*\S+/, '').split(' ').map { |t| %("#{t}") }.join(' ')}")
69
- end
3
+ class App
4
+ extend GLI::App
5
+ desc 'Find actions matching a tag'
6
+ long_desc 'Finds actions with tags matching the arguments. An action is shown if it
7
+ contains all of the tags listed. Add a + before a tag to make it required
8
+ and others optional. You can specify values using TAG=VALUE pairs.
9
+ Use <, >, and = for numeric comparisons, and *=, ^=, and $= for text comparisons.
10
+ Date comparisons use natural language (`na tagged "due<=today"`) and
11
+ are detected automatically.'
12
+ arg_name 'TAG[=VALUE]'
13
+ command %i[tagged] do |c|
14
+ c.example 'na tagged maybe', desc: 'Show all actions tagged @maybe'
15
+ c.example 'na tagged -d 3 "feature, idea"', desc: 'Show all actions tagged @feature AND @idea, recurse 3 levels'
16
+ c.example 'na tagged --or "feature, idea"', desc: 'Show all actions tagged @feature OR @idea'
17
+ c.example 'na tagged "priority>=4"', desc: 'Show actions with @priority(4) or @priority(5)'
18
+ c.example 'na tagged "due<in 2 days"', desc: 'Show actions with a due date coming up in the next 2 days'
19
+
20
+ c.desc 'Recurse to depth'
21
+ c.arg_name 'DEPTH'
22
+ c.default_value 1
23
+ c.flag %i[d depth], type: :integer, must_match: /^\d+$/
24
+
25
+ c.desc 'Show actions from a specific todo file in history. May use wildcards (* and ?)'
26
+ c.arg_name 'TODO_PATH'
27
+ c.flag %i[in]
28
+
29
+ c.desc 'Include notes in output'
30
+ c.switch %i[notes], negatable: true, default_value: false
31
+
32
+ c.desc 'Combine tags with OR, displaying actions matching ANY of the tags'
33
+ c.switch %i[o or], negatable: false
34
+
35
+ c.desc 'Show actions from a specific project'
36
+ c.arg_name 'PROJECT[/SUBPROJECT]'
37
+ c.flag %i[proj project]
38
+
39
+ c.desc 'Filter results using search terms'
40
+ c.arg_name 'QUERY'
41
+ c.flag %i[search], multiple: true
42
+
43
+ c.desc 'Search query is regular expression'
44
+ c.switch %i[regex], negatable: false
45
+
46
+ c.desc 'Search query is exact text match (not tokens)'
47
+ c.switch %i[exact], negatable: false
48
+
49
+ c.desc 'Include @done actions'
50
+ c.switch %i[done]
51
+
52
+ c.desc 'Show actions not matching tags'
53
+ c.switch %i[v invert], negatable: false
54
+
55
+ c.desc 'Save this search for future use'
56
+ c.arg_name 'TITLE'
57
+ c.flag %i[save]
58
+
59
+ c.desc 'Output actions nested by file'
60
+ c.switch %[nest], negatable: false
61
+
62
+ c.desc 'Output actions nested by file and project'
63
+ c.switch %[omnifocus], negatable: false
64
+
65
+ c.action do |global_options, options, args|
66
+ options[:nest] = true if options[:omnifocus]
67
+
68
+ if options[:save]
69
+ title = options[:save].gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
70
+ NA.save_search(title, "#{NA.command_line.join(' ').sub(/ --save[= ]*\S+/, '').split(' ').map { |t| %("#{t}") }.join(' ')}")
71
+ end
70
72
 
71
- depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
72
- 3
73
- else
74
- options[:depth].nil? ? global_options[:depth].to_i : options[:depth].to_i
75
- end
76
-
77
- tags = []
78
-
79
- all_req = args.join(' ') !~ /[+!\-]/ && !options[:or]
80
- args.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
73
+ depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
74
+ 3
75
+ else
76
+ options[:depth].nil? ? global_options[:depth].to_i : options[:depth].to_i
77
+ end
91
78
 
92
- search_for_done = false
93
- tags.each { |tag| search_for_done = true if tag[:tag] =~ /done/ }
94
- tags.push({ tag: 'done', value: nil, negate: true}) unless search_for_done
95
-
96
- tokens = nil
97
- if options[:search]
98
- if options[:exact]
99
- tokens = options[:search].join(' ')
100
- elsif options[:regex]
101
- tokens = Regexp.new(options[:search].join(' '), Regexp::IGNORECASE)
102
- else
103
- tokens = []
104
- all_req = options[:search].join(' ') !~ /[+!\-]/ && !options[:or]
105
-
106
- options[:search].join(' ').split(/ /).each do |arg|
107
- m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
108
- tokens.push({
109
- token: m['tok'],
110
- required: all_req || (!m['req'].nil? && m['req'] == '+'),
111
- negate: !m['req'].nil? && m['req'] =~ /[!\-]/
112
- })
113
- end
114
- end
115
- end
79
+ tags = []
116
80
 
117
- todo = nil
118
- if options[:in]
119
- todo = []
120
- options[:in].split(/ *, */).each do |a|
121
- m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
122
- todo.push({
123
- token: m['tok'],
81
+ all_req = args.join(' ') !~ /[+!\-]/ && !options[:or]
82
+ args.join(',').split(/ *, */).each do |arg|
83
+ m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
84
+
85
+ tags.push({
86
+ tag: m['tag'].wildcard_to_rx,
87
+ comp: m['op'],
88
+ value: m['val'],
124
89
  required: all_req || (!m['req'].nil? && m['req'] == '+'),
125
90
  negate: !m['req'].nil? && m['req'] =~ /[!\-]/
126
91
  })
127
92
  end
128
- end
129
93
 
130
- files, actions, = NA.parse_actions(depth: depth,
131
- done: options[:done],
132
- query: todo,
133
- search: tokens,
134
- tag: tags,
135
- negate: options[:invert],
136
- project: options[:project],
137
- require_na: false)
138
- # regexes = tags.delete_if { |token| token[:negate] }.map { |token| token[:token] }
139
- regexes = if tokens.is_a?(Array)
140
- tokens.delete_if { |token| token[:negate] }.map { |token| token[:token] }
141
- else
142
- [tokens]
143
- end
144
- NA.output_actions(actions, depth, files: files, regexes: regexes, notes: options[:notes], nest: options[:nest], nest_projects: options[:omnifocus])
94
+ search_for_done = false
95
+ tags.each { |tag| search_for_done = true if tag[:tag] =~ /done/ }
96
+ tags.push({ tag: 'done', value: nil, negate: true}) unless search_for_done
97
+
98
+ tokens = nil
99
+ if options[:search]
100
+ if options[:exact]
101
+ tokens = options[:search].join(' ')
102
+ elsif options[:regex]
103
+ tokens = Regexp.new(options[:search].join(' '), Regexp::IGNORECASE)
104
+ else
105
+ tokens = []
106
+ all_req = options[:search].join(' ') !~ /[+!\-]/ && !options[:or]
107
+
108
+ options[:search].join(' ').split(/ /).each do |arg|
109
+ m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
110
+ tokens.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
+ end
118
+
119
+ todo = nil
120
+ if options[:in]
121
+ todo = []
122
+ options[:in].split(/ *, */).each do |a|
123
+ m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
124
+ todo.push({
125
+ token: m['tok'],
126
+ required: all_req || (!m['req'].nil? && m['req'] == '+'),
127
+ negate: !m['req'].nil? && m['req'] =~ /[!\-]/
128
+ })
129
+ end
130
+ end
131
+
132
+ files, actions, = NA.parse_actions(depth: depth,
133
+ done: options[:done],
134
+ query: todo,
135
+ search: tokens,
136
+ tag: tags,
137
+ negate: options[:invert],
138
+ project: options[:project],
139
+ require_na: false)
140
+ # regexes = tags.delete_if { |token| token[:negate] }.map { |token| token[:token] }
141
+ regexes = if tokens.is_a?(Array)
142
+ tokens.delete_if { |token| token[:negate] }.map { |token| token[:token] }
143
+ else
144
+ [tokens]
145
+ end
146
+ NA.output_actions(actions, depth, files: files, regexes: regexes, notes: options[:notes], nest: options[:nest], nest_projects: options[:omnifocus])
147
+ end
145
148
  end
146
149
  end
@@ -1,28 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- desc 'Show list of known todo files'
4
- long_desc 'Arguments will be interpreted as a query against which the
5
- list of todos will be fuzzy matched. Separate directories with
6
- /, :, or a space, e.g. `na todos code/marked`'
7
- arg_name 'QUERY', optional: true
8
- command %i[todos] do |c|
9
- c.action do |_global_options, _options, args|
10
- if args.count.positive?
11
- all_req = args.join(' ') !~ /[+!\-]/
3
+ class App
4
+ extend GLI::App
5
+ desc 'Show list of known todo files'
6
+ long_desc 'Arguments will be interpreted as a query against which the
7
+ list of todos will be fuzzy matched. Separate directories with
8
+ /, :, or a space, e.g. `na todos code/marked`'
9
+ arg_name 'QUERY', optional: true
10
+ command %i[todos] do |c|
11
+ c.action do |_global_options, _options, args|
12
+ if args.count.positive?
13
+ all_req = args.join(' ') !~ /[+!\-]/
12
14
 
13
- tokens = [{ token: '*', required: all_req, negate: false }]
14
- args.each do |arg|
15
- arg.split(/ *, */).each do |a|
16
- m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
17
- tokens.push({
18
- token: m['tok'],
19
- required: all_req || (!m['req'].nil? && m['req'] == '+'),
20
- negate: !m['req'].nil? && m['req'] =~ /[!\-]/
21
- })
15
+ tokens = [{ token: '*', required: all_req, negate: false }]
16
+ args.each do |arg|
17
+ arg.split(/ *, */).each do |a|
18
+ m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
19
+ tokens.push({
20
+ token: m['tok'],
21
+ required: all_req || (!m['req'].nil? && m['req'] == '+'),
22
+ negate: !m['req'].nil? && m['req'] =~ /[!\-]/
23
+ })
24
+ end
22
25
  end
23
26
  end
24
- end
25
27
 
26
- NA.list_todos(query: tokens)
28
+ NA.list_todos(query: tokens)
29
+ end
27
30
  end
28
31
  end