na 1.2.80 → 1.2.81
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/.rubocop.yml +8 -2
- data/.rubocop_todo.yml +33 -538
- data/CHANGELOG.md +19 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +27 -10
- data/README.md +46 -5
- data/Rakefile +6 -0
- data/bin/commands/next.rb +4 -0
- data/bin/commands/scan.rb +84 -0
- data/bin/commands/update.rb +1 -1
- data/bin/na +7 -7
- data/lib/na/action.rb +101 -35
- data/lib/na/actions.rb +79 -77
- data/lib/na/array.rb +11 -7
- data/lib/na/benchmark.rb +21 -9
- data/lib/na/colors.rb +84 -86
- data/lib/na/editor.rb +22 -22
- data/lib/na/hash.rb +32 -9
- data/lib/na/help_monkey_patch.rb +9 -1
- data/lib/na/next_action.rb +314 -245
- data/lib/na/pager.rb +38 -14
- data/lib/na/project.rb +14 -1
- data/lib/na/prompt.rb +25 -3
- data/lib/na/string.rb +90 -130
- data/lib/na/theme.rb +37 -31
- data/lib/na/todo.rb +149 -131
- data/lib/na/version.rb +3 -1
- data/lib/na.rb +1 -0
- data/na.gemspec +4 -2
- data/scripts/generate-fish-completions.rb +18 -21
- data/src/_README.md +14 -4
- data/test_performance.rb +5 -5
- metadata +53 -24
data/lib/na/todo.rb
CHANGED
|
@@ -4,28 +4,28 @@ module NA
|
|
|
4
4
|
class Todo
|
|
5
5
|
attr_accessor :actions, :projects, :files
|
|
6
6
|
|
|
7
|
+
# Initialize a Todo object and parse actions/projects/files
|
|
8
|
+
#
|
|
9
|
+
# @param options [Hash] Options for parsing todo files
|
|
10
|
+
# @return [void]
|
|
7
11
|
def initialize(options = {})
|
|
8
12
|
@files, @actions, @projects = parse(options)
|
|
9
13
|
end
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
## @option project [String] The project
|
|
26
|
-
## @option require_na [Boolean] Require @na tag
|
|
27
|
-
## @option file_path [String] file path to parse
|
|
28
|
-
##
|
|
15
|
+
# Read a todo file and create a list of actions
|
|
16
|
+
#
|
|
17
|
+
# @param options [Hash] The options
|
|
18
|
+
# @option options [Number] :depth The directory depth to search for files
|
|
19
|
+
# @option options [Boolean] :done include @done actions
|
|
20
|
+
# @option options [Hash] :query The todo file query
|
|
21
|
+
# @option options [Array] :tag Tags to search for
|
|
22
|
+
# @option options [String] :search A search string
|
|
23
|
+
# @option options [Boolean] :negate Invert results
|
|
24
|
+
# @option options [Boolean] :regex Interpret as regular expression
|
|
25
|
+
# @option options [String] :project The project
|
|
26
|
+
# @option options [Boolean] :require_na Require @na tag
|
|
27
|
+
# @option options [String] :file_path file path to parse
|
|
28
|
+
# @return [Array] files, actions, projects
|
|
29
29
|
def parse(options)
|
|
30
30
|
NA::Benchmark.measure('Todo.parse') do
|
|
31
31
|
defaults = {
|
|
@@ -33,6 +33,7 @@ module NA
|
|
|
33
33
|
done: false,
|
|
34
34
|
file_path: nil,
|
|
35
35
|
negate: false,
|
|
36
|
+
hidden: false,
|
|
36
37
|
project: nil,
|
|
37
38
|
query: nil,
|
|
38
39
|
regex: false,
|
|
@@ -43,72 +44,86 @@ module NA
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
settings = defaults.merge(options)
|
|
47
|
+
# Ensure tag is always an Array
|
|
48
|
+
if settings[:tag].nil?
|
|
49
|
+
settings[:tag] = []
|
|
50
|
+
elsif !settings[:tag].is_a?(Array)
|
|
51
|
+
settings[:tag] = [settings[:tag]]
|
|
52
|
+
end
|
|
46
53
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
54
|
+
actions = NA::Actions.new
|
|
55
|
+
required = []
|
|
56
|
+
optional = []
|
|
57
|
+
negated = []
|
|
58
|
+
required_tag = []
|
|
59
|
+
optional_tag = []
|
|
60
|
+
negated_tag = []
|
|
61
|
+
projects = []
|
|
62
|
+
|
|
63
|
+
NA.notify("Tags: #{settings[:tag]}", debug: true)
|
|
64
|
+
NA.notify("Search: #{settings[:search]}", debug: true)
|
|
65
|
+
|
|
66
|
+
settings[:tag]&.each do |t|
|
|
67
|
+
# If t is a Hash, use its keys; if String, treat as a tag string
|
|
68
|
+
if t.is_a?(Hash)
|
|
69
|
+
unless t[:tag].nil?
|
|
70
|
+
if settings[:negate]
|
|
71
|
+
optional_tag.push(t) if t[:negate]
|
|
72
|
+
required_tag.push(t) if t[:required] && t[:negate]
|
|
73
|
+
negated_tag.push(t) unless t[:negate]
|
|
74
|
+
else
|
|
75
|
+
optional_tag.push(t) unless t[:negate] || t[:required]
|
|
76
|
+
required_tag.push(t) if t[:required] && !t[:negate]
|
|
77
|
+
negated_tag.push(t) if t[:negate]
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
elsif t.is_a?(String)
|
|
81
|
+
# Treat string as a simple tag
|
|
82
|
+
optional_tag.push({ tag: t })
|
|
69
83
|
end
|
|
70
84
|
end
|
|
71
|
-
end
|
|
72
85
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
86
|
+
unless settings[:search].nil? || settings[:search].empty?
|
|
87
|
+
if settings[:regex] || settings[:search].is_a?(String)
|
|
88
|
+
if settings[:negate]
|
|
89
|
+
negated.push(settings[:search])
|
|
90
|
+
else
|
|
91
|
+
optional.push(settings[:search])
|
|
92
|
+
required.push(settings[:search])
|
|
93
|
+
end
|
|
77
94
|
else
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
optional.concat(opt)
|
|
85
|
-
required.concat(req)
|
|
86
|
-
negated.concat(neg)
|
|
95
|
+
settings[:search].each do |t|
|
|
96
|
+
opt, req, neg = parse_search(t, settings[:negate])
|
|
97
|
+
optional.concat(opt)
|
|
98
|
+
required.concat(req)
|
|
99
|
+
negated.concat(neg)
|
|
100
|
+
end
|
|
87
101
|
end
|
|
88
102
|
end
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# Pre-compile regexes for better performance
|
|
92
|
-
optional = optional.map { |rx| rx.is_a?(Regexp) ? rx : Regexp.new(rx, Regexp::IGNORECASE) }
|
|
93
|
-
required = required.map { |rx| rx.is_a?(Regexp) ? rx : Regexp.new(rx, Regexp::IGNORECASE) }
|
|
94
|
-
negated = negated.map { |rx| rx.is_a?(Regexp) ? rx : Regexp.new(rx, Regexp::IGNORECASE) }
|
|
95
|
-
|
|
96
|
-
files = if !settings[:file_path].nil?
|
|
97
|
-
[settings[:file_path]]
|
|
98
|
-
elsif settings[:query].nil?
|
|
99
|
-
NA.find_files(depth: settings[:depth])
|
|
100
|
-
else
|
|
101
|
-
NA.match_working_dir(settings[:query])
|
|
102
|
-
end
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
# Pre-compile regexes for better performance
|
|
105
|
+
optional = optional.map { |rx| rx.is_a?(Regexp) ? rx : Regexp.new(rx, Regexp::IGNORECASE) }
|
|
106
|
+
required = required.map { |rx| rx.is_a?(Regexp) ? rx : Regexp.new(rx, Regexp::IGNORECASE) }
|
|
107
|
+
negated = negated.map { |rx| rx.is_a?(Regexp) ? rx : Regexp.new(rx, Regexp::IGNORECASE) }
|
|
108
|
+
|
|
109
|
+
files = if !settings[:file_path].nil?
|
|
110
|
+
[settings[:file_path]]
|
|
111
|
+
elsif settings[:query].nil?
|
|
112
|
+
NA.find_files(depth: settings[:depth], include_hidden: settings[:hidden])
|
|
113
|
+
else
|
|
114
|
+
NA.match_working_dir(settings[:query])
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
NA.notify("Files: #{files.join(', ')}", debug: true)
|
|
118
|
+
# Cache project regex compilation outside the line loop for better performance
|
|
119
|
+
project_regex = if settings[:project]
|
|
120
|
+
rx = settings[:project].split(%r{[/:]}).join('.*?/')
|
|
121
|
+
Regexp.new("#{rx}.*?", Regexp::IGNORECASE)
|
|
122
|
+
end
|
|
110
123
|
|
|
111
124
|
files.each do |file|
|
|
125
|
+
next if File.directory?(file)
|
|
126
|
+
|
|
112
127
|
NA::Benchmark.measure("Parse file: #{File.basename(file)}") do
|
|
113
128
|
NA.save_working_dir(File.expand_path(file))
|
|
114
129
|
content = file.read_file
|
|
@@ -116,60 +131,58 @@ module NA
|
|
|
116
131
|
parent = []
|
|
117
132
|
in_yaml = false
|
|
118
133
|
in_action = false
|
|
119
|
-
content.split(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
projects[-1].last_line = idx if projects.count.positive?
|
|
172
|
-
end
|
|
134
|
+
content.split("\n").each.with_index do |line, idx|
|
|
135
|
+
if in_yaml && line !~ /^(---|~~~)\s*$/
|
|
136
|
+
NA.notify("YAML: #{line}", debug: true)
|
|
137
|
+
elsif line =~ /^(---|~~~)\s*$/
|
|
138
|
+
in_yaml = !in_yaml
|
|
139
|
+
elsif line.project? && !in_yaml
|
|
140
|
+
in_action = false
|
|
141
|
+
proj = line.project
|
|
142
|
+
indent = line.indent_level
|
|
143
|
+
|
|
144
|
+
if indent.zero? # top level project
|
|
145
|
+
parent = [proj]
|
|
146
|
+
elsif indent <= indent_level # if indent level is same or less, split parent before indent level and append
|
|
147
|
+
parent.slice!(indent, parent.count - indent)
|
|
148
|
+
parent.push(proj)
|
|
149
|
+
else # if indent level is greater, append project to parent
|
|
150
|
+
parent.push(proj)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
projects.push(NA::Project.new(parent.join(':'), indent, idx, idx))
|
|
154
|
+
|
|
155
|
+
indent_level = indent
|
|
156
|
+
elsif line.blank?
|
|
157
|
+
in_action = false # Comment out to allow line breaks in of notes, which isn't TaskPaper-compatible
|
|
158
|
+
elsif line.action?
|
|
159
|
+
in_action = false
|
|
160
|
+
|
|
161
|
+
# Early exits before creating Action object
|
|
162
|
+
next if line.done? && !settings[:done]
|
|
163
|
+
|
|
164
|
+
next if settings[:require_na] && !line.na?
|
|
165
|
+
|
|
166
|
+
next if project_regex && parent.join('/') !~ project_regex
|
|
167
|
+
|
|
168
|
+
# Only create Action if we passed basic filters
|
|
169
|
+
action = line.action
|
|
170
|
+
new_action = NA::Action.new(file, File.basename(file, ".#{NA.extension}"), parent.dup, action, idx)
|
|
171
|
+
|
|
172
|
+
projects[-1].last_line = idx if projects.count.positive?
|
|
173
|
+
|
|
174
|
+
# Tag matching
|
|
175
|
+
has_tag = !optional_tag.empty? || !required_tag.empty? || !negated_tag.empty?
|
|
176
|
+
next if has_tag && !new_action.tags_match?(any: optional_tag,
|
|
177
|
+
all: required_tag,
|
|
178
|
+
none: negated_tag)
|
|
179
|
+
|
|
180
|
+
actions.push(new_action)
|
|
181
|
+
in_action = true
|
|
182
|
+
elsif in_action
|
|
183
|
+
actions[-1].note.push(line.strip) if actions.count.positive?
|
|
184
|
+
projects[-1].last_line = idx if projects.count.positive?
|
|
185
|
+
end
|
|
173
186
|
end
|
|
174
187
|
projects = projects.dup
|
|
175
188
|
end
|
|
@@ -179,9 +192,9 @@ module NA
|
|
|
179
192
|
actions.delete_if do |new_action|
|
|
180
193
|
has_search = !optional.empty? || !required.empty? || !negated.empty?
|
|
181
194
|
has_search && !new_action.search_match?(any: optional,
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
195
|
+
all: required,
|
|
196
|
+
none: negated,
|
|
197
|
+
include_note: settings[:search_note])
|
|
185
198
|
end
|
|
186
199
|
end
|
|
187
200
|
|
|
@@ -189,6 +202,11 @@ module NA
|
|
|
189
202
|
end
|
|
190
203
|
end
|
|
191
204
|
|
|
205
|
+
# Parse a search tag and categorize as optional, required, or negated
|
|
206
|
+
#
|
|
207
|
+
# @param tag [Hash] Search tag with :token, :negate, :required
|
|
208
|
+
# @param negate [Boolean] Invert results
|
|
209
|
+
# @return [Array<Array>] Arrays of optional, required, and negated regexes
|
|
192
210
|
def parse_search(tag, negate)
|
|
193
211
|
required = []
|
|
194
212
|
optional = []
|
data/lib/na/version.rb
CHANGED
data/lib/na.rb
CHANGED
data/na.gemspec
CHANGED
|
@@ -24,12 +24,14 @@ spec = Gem::Specification.new do |s|
|
|
|
24
24
|
s.add_development_dependency('minitest', '~> 5.14')
|
|
25
25
|
s.add_development_dependency('rdoc', '~> 4.3')
|
|
26
26
|
s.add_runtime_dependency('chronic', '~> 0.10', '>= 0.10.2')
|
|
27
|
+
s.add_runtime_dependency('git', '~> 3.0.0')
|
|
27
28
|
s.add_runtime_dependency('gli','~> 2.21.0')
|
|
28
29
|
s.add_runtime_dependency('mdless', '~> 1.0', '>= 1.0.32')
|
|
30
|
+
s.add_runtime_dependency('ostruct', '~> 0.6', '>= 0.6.1')
|
|
29
31
|
s.add_runtime_dependency('tty-reader', '~> 0.9', '>= 0.9.0')
|
|
30
32
|
s.add_runtime_dependency('tty-screen', '~> 0.8', '>= 0.8.1')
|
|
31
33
|
s.add_runtime_dependency('tty-which', '~> 0.5', '>= 0.5.0')
|
|
32
|
-
s.add_runtime_dependency('git', '~> 3.0.0')
|
|
33
|
-
s.add_runtime_dependency('ostruct', '~> 0.6', '>= 0.6.1')
|
|
34
34
|
s.add_development_dependency('tty-spinner', '~> 0.9', '>= 0.9.0')
|
|
35
|
+
s.add_development_dependency 'rspec', '~> 3.0'
|
|
36
|
+
s.add_development_dependency 'bump', '~> 0.6.0'
|
|
35
37
|
end
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
2
4
|
require 'tty-progressbar'
|
|
3
5
|
require 'shellwords'
|
|
4
6
|
|
|
@@ -21,7 +23,6 @@ class ::String
|
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
class FishCompletions
|
|
24
|
-
|
|
25
26
|
attr_accessor :commands, :global_options
|
|
26
27
|
|
|
27
28
|
def generate_helpers
|
|
@@ -68,7 +69,7 @@ class FishCompletions
|
|
|
68
69
|
sections = {}
|
|
69
70
|
scanned.each do |sect|
|
|
70
71
|
title = sect[0].downcase.strip.gsub(/ +/, '_').to_sym
|
|
71
|
-
content = sect[1].split(
|
|
72
|
+
content = sect[1].split("\n").map(&:strip).delete_if(&:empty?)
|
|
72
73
|
sections[title] = content
|
|
73
74
|
end
|
|
74
75
|
sections
|
|
@@ -77,6 +78,7 @@ class FishCompletions
|
|
|
77
78
|
def parse_option(option)
|
|
78
79
|
res = option.match(/(?:-(?<short>\w), )?(?:--(?:\[no-\])?(?<long>w+)(?:=(?<arg>\w+))?)\s+- (?<desc>.*?)$/)
|
|
79
80
|
return nil unless res
|
|
81
|
+
|
|
80
82
|
{
|
|
81
83
|
short: res['short'],
|
|
82
84
|
long: res['long'],
|
|
@@ -92,7 +94,7 @@ class FishCompletions
|
|
|
92
94
|
def parse_command(command)
|
|
93
95
|
res = command.match(/^(?<cmd>[^, \t]+)(?<alias>(?:, [^, \t]+)*)?\s+- (?<desc>.*?)$/)
|
|
94
96
|
commands = [res['cmd']]
|
|
95
|
-
commands.concat(res['alias'].split(
|
|
97
|
+
commands.concat(res['alias'].split(', ').delete_if(&:empty?)) if res['alias']
|
|
96
98
|
|
|
97
99
|
{
|
|
98
100
|
commands: commands,
|
|
@@ -106,7 +108,7 @@ class FishCompletions
|
|
|
106
108
|
|
|
107
109
|
def generate_subcommand_completions
|
|
108
110
|
out = []
|
|
109
|
-
@commands.each_with_index do |cmd,
|
|
111
|
+
@commands.each_with_index do |cmd, _i|
|
|
110
112
|
out << "complete -xc na -n '__fish_na_needs_command' -a '#{cmd[:commands].join(' ')}' -d #{Shellwords.escape(cmd[:description])}"
|
|
111
113
|
end
|
|
112
114
|
|
|
@@ -114,35 +116,30 @@ class FishCompletions
|
|
|
114
116
|
end
|
|
115
117
|
|
|
116
118
|
def generate_subcommand_option_completions
|
|
117
|
-
|
|
118
119
|
out = []
|
|
119
120
|
need_export = []
|
|
120
121
|
|
|
121
|
-
@commands.each_with_index do |cmd,
|
|
122
|
+
@commands.each_with_index do |cmd, _i|
|
|
122
123
|
@bar.advance
|
|
123
124
|
data = get_help_sections(cmd[:commands].first)
|
|
124
125
|
|
|
125
|
-
if data[:synopsis].join(' ').strip.split(/ /).last =~ /(path|file)/i
|
|
126
|
-
out << "complete -c na -F -n '__fish_na_using_command #{cmd[:commands].join(" ")}'"
|
|
127
|
-
end
|
|
126
|
+
out << "complete -c na -F -n '__fish_na_using_command #{cmd[:commands].join(' ')}'" if data[:synopsis].join(' ').strip.split(/ /).last =~ /(path|file)/i
|
|
128
127
|
|
|
129
|
-
|
|
130
|
-
parse_options(data[:command_options]).each do |option|
|
|
131
|
-
next if option.nil?
|
|
128
|
+
next unless data[:command_options]
|
|
132
129
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
long = option[:long] ? "-l #{option[:long]}" : ''
|
|
136
|
-
out << "complete -c na #{long} #{short} -f #{arg} -n '__fish_na_using_command #{cmd[:commands].join(' ')}' -d #{Shellwords.escape(option[:description])}"
|
|
130
|
+
parse_options(data[:command_options]).each do |option|
|
|
131
|
+
next if option.nil?
|
|
137
132
|
|
|
138
|
-
|
|
139
|
-
|
|
133
|
+
arg = option[:arg] ? '-r' : ''
|
|
134
|
+
short = option[:short] ? "-s #{option[:short]}" : ''
|
|
135
|
+
long = option[:long] ? "-l #{option[:long]}" : ''
|
|
136
|
+
out << "complete -c na #{long} #{short} -f #{arg} -n '__fish_na_using_command #{cmd[:commands].join(' ')}' -d #{Shellwords.escape(option[:description])}"
|
|
137
|
+
|
|
138
|
+
need_export.concat(cmd[:commands]) if option[:long] == 'output'
|
|
140
139
|
end
|
|
141
140
|
end
|
|
142
141
|
|
|
143
|
-
unless need_export.empty?
|
|
144
|
-
out << "complete -f -c na -s o -l output -x -n '__fish_na_using_command #{need_export.join(' ')}' -a '(__fish_na_export_plugins)'"
|
|
145
|
-
end
|
|
142
|
+
out << "complete -f -c na -s o -l output -x -n '__fish_na_using_command #{need_export.join(' ')}' -a '(__fish_na_export_plugins)'" unless need_export.empty?
|
|
146
143
|
|
|
147
144
|
# clear
|
|
148
145
|
out.join("\n")
|
data/src/_README.md
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
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 <!--VER-->1.2.
|
|
12
|
+
The current version of `na` is <!--VER-->1.2.80<!--END VER-->.
|
|
13
13
|
|
|
14
|
-
`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.
|
|
14
|
+
`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.
|
|
15
15
|
|
|
16
16
|
Used with Taskpaper files, it can add new action items quickly from the command line, automatically tagging them as next actions. It can also mark actions as completed, delete them, archive them, and move them between projects.
|
|
17
17
|
|
|
@@ -48,7 +48,7 @@ You can list next actions in files in the current directory by typing `na`. By d
|
|
|
48
48
|
|
|
49
49
|
#### Adding todos
|
|
50
50
|
|
|
51
|
-
You can also quickly add todo items from the command line with the `add` subcommand. The script will look for a file in the current directory with a `.taskpaper` extension (configurable).
|
|
51
|
+
You can also quickly add todo items from the command line with the `add` subcommand. The script will look for a file in the current directory with a `.taskpaper` extension (configurable).
|
|
52
52
|
|
|
53
53
|
If found, it will try to locate an `Inbox:` project, or create one if it doesn't exist. Any arguments after `add` will be combined to create a new task in TaskPaper format. They will automatically be assigned as next actions (tagged `@na`) and will show up when `na` lists the tasks for the project.
|
|
54
54
|
|
|
@@ -82,7 +82,7 @@ If you run the `add` command with no arguments, you'll be asked for input on the
|
|
|
82
82
|
|
|
83
83
|
###### Adding notes
|
|
84
84
|
|
|
85
|
-
Use the `--note` switch to add a note. If STDIN (piped) input is present when this switch is used, it will be included in the note. A prompt will be displayed for adding additional notes, which will be appended to any STDIN note passed. Press CTRL-d to end editing and save the note.
|
|
85
|
+
Use the `--note` switch to add a note. If STDIN (piped) input is present when this switch is used, it will be included in the note. A prompt will be displayed for adding additional notes, which will be appended to any STDIN note passed. Press CTRL-d to end editing and save the note.
|
|
86
86
|
|
|
87
87
|
Notes are not displayed by the `next/tagged/find` commands unless `--notes` is specified.
|
|
88
88
|
|
|
@@ -163,6 +163,16 @@ Run `na saved` without an argument to list your saved searches.
|
|
|
163
163
|
@cli(bundle exec bin/na help saved)
|
|
164
164
|
```
|
|
165
165
|
|
|
166
|
+
##### scan
|
|
167
|
+
|
|
168
|
+
Scan a directory tree for todo files and cache them in tdlist.txt. Avoids duplicates and can optionally prune non-existent entries.
|
|
169
|
+
|
|
170
|
+
Scan reports how many files were added and, if --prune is used, how many were pruned. With --dry-run, it lists the full file paths that would be added and/or pruned.
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
@cli(bundle exec bin/na help scan)
|
|
174
|
+
```
|
|
175
|
+
|
|
166
176
|
##### tagged
|
|
167
177
|
|
|
168
178
|
Example: `na tagged feature +maybe`.
|
data/test_performance.rb
CHANGED
|
@@ -8,7 +8,7 @@ require_relative 'lib/na/benchmark'
|
|
|
8
8
|
module NA
|
|
9
9
|
module Color
|
|
10
10
|
def self.template(input)
|
|
11
|
-
input.to_s
|
|
11
|
+
input.to_s # Simple mock
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
|
|
@@ -41,15 +41,15 @@ end
|
|
|
41
41
|
NA::Benchmark.init
|
|
42
42
|
|
|
43
43
|
# Test the optimizations
|
|
44
|
-
puts
|
|
44
|
+
puts 'Testing performance optimizations...'
|
|
45
45
|
|
|
46
46
|
# Test 1: Theme caching
|
|
47
47
|
NA::Benchmark.measure('Theme loading (first time)') do
|
|
48
|
-
|
|
48
|
+
NA::Theme.load_theme
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
NA::Benchmark.measure('Theme loading (cached)') do
|
|
52
|
-
|
|
52
|
+
NA.theme
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
# Test 2: Color template caching
|
|
@@ -70,7 +70,7 @@ end
|
|
|
70
70
|
|
|
71
71
|
NA::Benchmark.measure('Multiple color templates') do
|
|
72
72
|
100.times do
|
|
73
|
-
NA::Color.template(
|
|
73
|
+
NA::Color.template("{bg}Action {c}#{rand(1000)}{x}")
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
|