na 1.0.0
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 +7 -0
- data/CHANGELOG.md +8 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +35 -0
- data/README.rdoc +6 -0
- data/Rakefile +31 -0
- data/bin/na +285 -0
- data/html/App.html +105 -0
- data/html/NA/Action.html +299 -0
- data/html/NA/Color.html +373 -0
- data/html/NA.html +100 -0
- data/html/README_rdoc.html +328 -0
- data/html/String.html +368 -0
- data/html/created.rid +9 -0
- data/html/css/fonts.css +167 -0
- data/html/css/rdoc.css +639 -0
- data/html/fonts/Lato-Light.ttf +0 -0
- data/html/fonts/Lato-LightItalic.ttf +0 -0
- data/html/fonts/Lato-Regular.ttf +0 -0
- data/html/fonts/Lato-RegularItalic.ttf +0 -0
- data/html/fonts/SourceCodePro-Bold.ttf +0 -0
- data/html/fonts/SourceCodePro-Regular.ttf +0 -0
- data/html/images/add.png +0 -0
- data/html/images/arrow_up.png +0 -0
- data/html/images/brick.png +0 -0
- data/html/images/brick_link.png +0 -0
- data/html/images/bug.png +0 -0
- data/html/images/bullet_black.png +0 -0
- data/html/images/bullet_toggle_minus.png +0 -0
- data/html/images/bullet_toggle_plus.png +0 -0
- data/html/images/date.png +0 -0
- data/html/images/delete.png +0 -0
- data/html/images/find.png +0 -0
- data/html/images/loadingAnimation.gif +0 -0
- data/html/images/macFFBgHack.png +0 -0
- data/html/images/package.png +0 -0
- data/html/images/page_green.png +0 -0
- data/html/images/page_white_text.png +0 -0
- data/html/images/page_white_width.png +0 -0
- data/html/images/plugin.png +0 -0
- data/html/images/ruby.png +0 -0
- data/html/images/tag_blue.png +0 -0
- data/html/images/tag_green.png +0 -0
- data/html/images/transparent.png +0 -0
- data/html/images/wrench.png +0 -0
- data/html/images/wrench_orange.png +0 -0
- data/html/images/zoom.png +0 -0
- data/html/index.html +297 -0
- data/html/js/darkfish.js +84 -0
- data/html/js/navigation.js +105 -0
- data/html/js/navigation.js.gz +0 -0
- data/html/js/search.js +110 -0
- data/html/js/search_index.js +1 -0
- data/html/js/search_index.js.gz +0 -0
- data/html/js/searcher.js +229 -0
- data/html/js/searcher.js.gz +0 -0
- data/html/table_of_contents.html +237 -0
- data/lib/na/action.rb +65 -0
- data/lib/na/colors.rb +335 -0
- data/lib/na/next_action.rb +181 -0
- data/lib/na/string.rb +42 -0
- data/lib/na/version.rb +3 -0
- data/lib/na.rb +11 -0
- data/na.gemspec +26 -0
- data/na.rdoc +167 -0
- metadata +211 -0
data/lib/na/colors.rb
ADDED
@@ -0,0 +1,335 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Cribbed from <https://github.com/flori/term-ansicolor>
|
4
|
+
module NA
|
5
|
+
# Terminal output color functions.
|
6
|
+
module Color
|
7
|
+
# Regexp to match excape sequences
|
8
|
+
ESCAPE_REGEX = /(?<=\[)(?:(?:(?:[349]|10)[0-9]|[0-9])?;?)+(?=m)/.freeze
|
9
|
+
|
10
|
+
# All available color names. Available as methods and string extensions.
|
11
|
+
#
|
12
|
+
# @example Use a color as a method. Color reset will be added to end of string.
|
13
|
+
# Color.yellow('This text is yellow') => "\e[33mThis text is yellow\e[0m"
|
14
|
+
#
|
15
|
+
# @example Use a color as a string extension. Color reset added automatically.
|
16
|
+
# 'This text is green'.green => "\e[1;32mThis text is green\e[0m"
|
17
|
+
#
|
18
|
+
# @example Send a text string as a color
|
19
|
+
# Color.send('red') => "\e[31m"
|
20
|
+
ATTRIBUTES = [
|
21
|
+
[:clear, 0], # String#clear is already used to empty string in Ruby 1.9
|
22
|
+
[:reset, 0], # synonym for :clear
|
23
|
+
[:bold, 1],
|
24
|
+
[:dark, 2],
|
25
|
+
[:italic, 3], # not widely implemented
|
26
|
+
[:underline, 4],
|
27
|
+
[:underscore, 4], # synonym for :underline
|
28
|
+
[:blink, 5],
|
29
|
+
[:rapid_blink, 6], # not widely implemented
|
30
|
+
[:negative, 7], # no reverse because of String#reverse
|
31
|
+
[:concealed, 8],
|
32
|
+
[:strikethrough, 9], # not widely implemented
|
33
|
+
[:strike, 9], # not widely implemented
|
34
|
+
[:black, 30],
|
35
|
+
[:red, 31],
|
36
|
+
[:green, 32],
|
37
|
+
[:yellow, 33],
|
38
|
+
[:blue, 34],
|
39
|
+
[:magenta, 35],
|
40
|
+
[:purple, 35],
|
41
|
+
[:cyan, 36],
|
42
|
+
[:white, 37],
|
43
|
+
[:bgblack, 40],
|
44
|
+
[:bgred, 41],
|
45
|
+
[:bggreen, 42],
|
46
|
+
[:bgyellow, 43],
|
47
|
+
[:bgblue, 44],
|
48
|
+
[:bgmagenta, 45],
|
49
|
+
[:bgpurple, 45],
|
50
|
+
[:bgcyan, 46],
|
51
|
+
[:bgwhite, 47],
|
52
|
+
[:boldblack, 90],
|
53
|
+
[:boldred, 91],
|
54
|
+
[:boldgreen, 92],
|
55
|
+
[:boldyellow, 93],
|
56
|
+
[:boldblue, 94],
|
57
|
+
[:boldmagenta, 95],
|
58
|
+
[:boldpurple, 95],
|
59
|
+
[:boldcyan, 96],
|
60
|
+
[:boldwhite, 97],
|
61
|
+
[:boldbgblack, 100],
|
62
|
+
[:boldbgred, 101],
|
63
|
+
[:boldbggreen, 102],
|
64
|
+
[:boldbgyellow, 103],
|
65
|
+
[:boldbgblue, 104],
|
66
|
+
[:boldbgmagenta, 105],
|
67
|
+
[:boldbgpurple, 105],
|
68
|
+
[:boldbgcyan, 106],
|
69
|
+
[:boldbgwhite, 107],
|
70
|
+
[:softpurple, '0;35;40'],
|
71
|
+
[:hotpants, '7;34;40'],
|
72
|
+
[:knightrider, '7;30;40'],
|
73
|
+
[:flamingo, '7;31;47'],
|
74
|
+
[:yeller, '1;37;43'],
|
75
|
+
[:whiteboard, '1;30;47'],
|
76
|
+
[:chalkboard, '1;37;40'],
|
77
|
+
[:led, '0;32;40'],
|
78
|
+
[:redacted, '0;30;40'],
|
79
|
+
[:alert, '1;31;43'],
|
80
|
+
[:error, '1;37;41'],
|
81
|
+
[:default, '0;39']
|
82
|
+
].map(&:freeze).freeze
|
83
|
+
|
84
|
+
# Array of attribute keys only
|
85
|
+
ATTRIBUTE_NAMES = ATTRIBUTES.transpose.first
|
86
|
+
|
87
|
+
# Returns true if NA::Color supports the +feature+.
|
88
|
+
#
|
89
|
+
# The feature :clear, that is mixing the clear color attribute into String,
|
90
|
+
# is only supported on ruby implementations, that do *not* already
|
91
|
+
# implement the String#clear method. It's better to use the reset color
|
92
|
+
# attribute instead.
|
93
|
+
def support?(feature)
|
94
|
+
case feature
|
95
|
+
when :clear
|
96
|
+
!String.instance_methods(false).map(&:to_sym).include?(:clear)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Template coloring
|
101
|
+
class ::String
|
102
|
+
##
|
103
|
+
## Extract the longest valid %color name from a string.
|
104
|
+
##
|
105
|
+
## Allows %colors to bleed into other text and still
|
106
|
+
## be recognized, e.g. %greensomething still finds
|
107
|
+
## %green.
|
108
|
+
##
|
109
|
+
## @return [String] a valid color name
|
110
|
+
##
|
111
|
+
def validate_color
|
112
|
+
valid_color = nil
|
113
|
+
compiled = ''
|
114
|
+
normalize_color.split('').each do |char|
|
115
|
+
compiled += char
|
116
|
+
valid_color = compiled if Color.attributes.include?(compiled.to_sym) || compiled =~ /^([fb]g?)?#([a-f0-9]{6})$/i
|
117
|
+
end
|
118
|
+
|
119
|
+
valid_color
|
120
|
+
end
|
121
|
+
|
122
|
+
##
|
123
|
+
## Normalize a color name, removing underscores,
|
124
|
+
## replacing "bright" with "bold", and converting
|
125
|
+
## bgbold to boldbg
|
126
|
+
##
|
127
|
+
## @return [String] Normalized color name
|
128
|
+
##
|
129
|
+
def normalize_color
|
130
|
+
gsub(/_/, '').sub(/bright/i, 'bold').sub(/bgbold/, 'boldbg')
|
131
|
+
end
|
132
|
+
|
133
|
+
# Get the calculated ANSI color at the end of the
|
134
|
+
# string
|
135
|
+
#
|
136
|
+
# @return ANSI escape sequence to match color
|
137
|
+
#
|
138
|
+
def last_color_code
|
139
|
+
m = scan(ESCAPE_REGEX)
|
140
|
+
|
141
|
+
em = ['0']
|
142
|
+
fg = nil
|
143
|
+
bg = nil
|
144
|
+
rgbf = nil
|
145
|
+
rgbb = nil
|
146
|
+
|
147
|
+
m.each do |c|
|
148
|
+
case c
|
149
|
+
when '0'
|
150
|
+
em = ['0']
|
151
|
+
fg, bg, rgbf, rgbb = nil
|
152
|
+
when /^[34]8/
|
153
|
+
case c
|
154
|
+
when /^3/
|
155
|
+
fg = nil
|
156
|
+
rgbf = c
|
157
|
+
when /^4/
|
158
|
+
bg = nil
|
159
|
+
rgbb = c
|
160
|
+
end
|
161
|
+
else
|
162
|
+
c.split(/;/).each do |i|
|
163
|
+
x = i.to_i
|
164
|
+
if x <= 9
|
165
|
+
em << x
|
166
|
+
elsif x >= 30 && x <= 39
|
167
|
+
rgbf = nil
|
168
|
+
fg = x
|
169
|
+
elsif x >= 40 && x <= 49
|
170
|
+
rgbb = nil
|
171
|
+
bg = x
|
172
|
+
elsif x >= 90 && x <= 97
|
173
|
+
rgbf = nil
|
174
|
+
fg = x
|
175
|
+
elsif x >= 100 && x <= 107
|
176
|
+
rgbb = nil
|
177
|
+
bg = x
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
escape = "\e[#{em.join(';')}m"
|
184
|
+
escape += "\e[#{rgbb}m" if rgbb
|
185
|
+
escape += "\e[#{rgbf}m" if rgbf
|
186
|
+
escape + "\e[#{[fg, bg].delete_if(&:nil?).join(';')}m"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class << self
|
191
|
+
# Returns true if the coloring function of this module
|
192
|
+
# is switched on, false otherwise.
|
193
|
+
def coloring?
|
194
|
+
@coloring
|
195
|
+
end
|
196
|
+
|
197
|
+
attr_writer :coloring
|
198
|
+
|
199
|
+
##
|
200
|
+
## Enables colored output
|
201
|
+
##
|
202
|
+
## @example Turn color on or off based on TTY
|
203
|
+
## NA::Color.coloring = STDOUT.isatty
|
204
|
+
def coloring
|
205
|
+
@coloring ||= true
|
206
|
+
end
|
207
|
+
|
208
|
+
##
|
209
|
+
## Convert a template string to a colored string.
|
210
|
+
## Colors are specified with single letters inside
|
211
|
+
## curly braces. Uppercase changes background color.
|
212
|
+
##
|
213
|
+
## w: white, k: black, g: green, l: blue, y: yellow, c: cyan,
|
214
|
+
## m: magenta, r: red, b: bold, u: underline, i: italic,
|
215
|
+
## x: reset (remove background, color, emphasis)
|
216
|
+
##
|
217
|
+
## @example Convert a templated string
|
218
|
+
## Color.template('{Rwb}Warning:{x} {w}you look a little {g}ill{x}')
|
219
|
+
##
|
220
|
+
## @param input [String, Array] The template
|
221
|
+
## string. If this is an array, the
|
222
|
+
## elements will be joined with a
|
223
|
+
## space.
|
224
|
+
##
|
225
|
+
## @return [String] Colorized string
|
226
|
+
##
|
227
|
+
def template(input)
|
228
|
+
input = input.join(' ') if input.is_a? Array
|
229
|
+
fmt = input.gsub(/%/, '%%')
|
230
|
+
fmt = fmt.gsub(/(?<!\\u)\{(\w+)\}/i) do
|
231
|
+
Regexp.last_match(1).split('').map { |c| "%<#{c}>s" }.join('')
|
232
|
+
end
|
233
|
+
|
234
|
+
colors = { w: white, k: black, g: green, l: blue,
|
235
|
+
y: yellow, c: cyan, m: magenta, r: red,
|
236
|
+
W: bgwhite, K: bgblack, G: bggreen, L: bgblue,
|
237
|
+
Y: bgyellow, C: bgcyan, M: bgmagenta, R: bgred,
|
238
|
+
d: dark, b: bold, u: underline, i: italic, x: reset }
|
239
|
+
|
240
|
+
format(fmt, colors)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
ATTRIBUTES.each do |c, v|
|
245
|
+
new_method = <<-EOSCRIPT
|
246
|
+
# Color string as #{c}
|
247
|
+
def #{c}(string = nil)
|
248
|
+
result = ''
|
249
|
+
result << "\e[#{v}m" if NA::Color.coloring?
|
250
|
+
if block_given?
|
251
|
+
result << yield
|
252
|
+
elsif string.respond_to?(:to_str)
|
253
|
+
result << string.to_str
|
254
|
+
elsif respond_to?(:to_str)
|
255
|
+
result << to_str
|
256
|
+
else
|
257
|
+
return result #only switch on
|
258
|
+
end
|
259
|
+
result << "\e[0m" if NA::Color.coloring?
|
260
|
+
result
|
261
|
+
end
|
262
|
+
EOSCRIPT
|
263
|
+
|
264
|
+
module_eval(new_method)
|
265
|
+
|
266
|
+
next unless c =~ /bold/
|
267
|
+
|
268
|
+
# Accept brightwhite in addition to boldwhite
|
269
|
+
new_method = <<-EOSCRIPT
|
270
|
+
# color string as #{c}
|
271
|
+
def #{c.to_s.sub(/bold/, 'bright')}(string = nil)
|
272
|
+
result = ''
|
273
|
+
result << "\e[#{v}m" if NA::Color.coloring?
|
274
|
+
if block_given?
|
275
|
+
result << yield
|
276
|
+
elsif string.respond_to?(:to_str)
|
277
|
+
result << string.to_str
|
278
|
+
elsif respond_to?(:to_str)
|
279
|
+
result << to_str
|
280
|
+
else
|
281
|
+
return result #only switch on
|
282
|
+
end
|
283
|
+
result << "\e[0m" if NA::Color.coloring?
|
284
|
+
result
|
285
|
+
end
|
286
|
+
EOSCRIPT
|
287
|
+
|
288
|
+
module_eval(new_method)
|
289
|
+
end
|
290
|
+
|
291
|
+
##
|
292
|
+
## Generate escape codes for hex colors
|
293
|
+
##
|
294
|
+
## @param hex [String] The hexadecimal color code
|
295
|
+
##
|
296
|
+
## @return [String] ANSI escape string
|
297
|
+
##
|
298
|
+
def rgb(hex)
|
299
|
+
is_bg = hex.match(/^bg?#/) ? true : false
|
300
|
+
hex_string = hex.sub(/^([fb]g?)?#/, '')
|
301
|
+
|
302
|
+
parts = hex_string.match(/(?<r>..)(?<g>..)(?<b>..)/)
|
303
|
+
t = []
|
304
|
+
%w[r g b].each do |e|
|
305
|
+
t << parts[e].hex
|
306
|
+
end
|
307
|
+
color =
|
308
|
+
"\e[#{is_bg ? '48' : '38'};2;#{t.join(';')}m"
|
309
|
+
end
|
310
|
+
|
311
|
+
# Regular expression that is used to scan for ANSI-sequences while
|
312
|
+
# uncoloring strings.
|
313
|
+
COLORED_REGEXP = /\e\[(?:(?:[349]|10)[0-7]|[0-9])?m/.freeze
|
314
|
+
|
315
|
+
# Returns an uncolored version of the string, that is all
|
316
|
+
# ANSI-sequences are stripped from the string.
|
317
|
+
def uncolor(string = nil) # :yields:
|
318
|
+
if block_given?
|
319
|
+
yield.to_str.gsub(COLORED_REGEXP, '')
|
320
|
+
elsif string.respond_to?(:to_str)
|
321
|
+
string.to_str.gsub(COLORED_REGEXP, '')
|
322
|
+
elsif respond_to?(:to_str)
|
323
|
+
to_str.gsub(COLORED_REGEXP, '')
|
324
|
+
else
|
325
|
+
''
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
# Returns an array of all NA::Color attributes as symbols.
|
330
|
+
def attributes
|
331
|
+
ATTRIBUTE_NAMES
|
332
|
+
end
|
333
|
+
extend self
|
334
|
+
end
|
335
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Next Action methods
|
4
|
+
module NA
|
5
|
+
class << self
|
6
|
+
def create_todo(target, basename)
|
7
|
+
File.open(target, 'w') do |f|
|
8
|
+
content = <<~ENDCONTENT
|
9
|
+
Inbox: @inbox
|
10
|
+
#{basename}:
|
11
|
+
\tNew Features:
|
12
|
+
\tIdeas:
|
13
|
+
\tBugs:
|
14
|
+
Archive:
|
15
|
+
Search Definitions:
|
16
|
+
\tTop Priority @search(@priority = 5 and not @done)
|
17
|
+
\tHigh Priority @search(@priority > 3 and not @done)
|
18
|
+
\tMaybe @search(@maybe)
|
19
|
+
\tNext @search(@na and not @done and not project = \"Archive\")
|
20
|
+
ENDCONTENT
|
21
|
+
f.puts(content)
|
22
|
+
end
|
23
|
+
puts NA::Color.template("{y}Created {bw}#{target}{x}")
|
24
|
+
end
|
25
|
+
|
26
|
+
def find_files(depth: 1, extension: 'taskpaper')
|
27
|
+
`find . -name "*.#{extension}" -maxdepth #{depth}`.strip.split("\n")
|
28
|
+
end
|
29
|
+
|
30
|
+
def select_file(files)
|
31
|
+
if TTY::Which.exist?('gum')
|
32
|
+
args = [
|
33
|
+
'--cursor.foreground="151"',
|
34
|
+
'--item.foreground=""'
|
35
|
+
]
|
36
|
+
`echo #{Shellwords.escape(files.join("\n"))}|#{TTY::Which.which('gum')} choose #{args.join(' ')}`.strip
|
37
|
+
elsif TTY::Which.exist?('fzf')
|
38
|
+
res = choose_from(files, prompt: 'Use which file?')
|
39
|
+
unless res
|
40
|
+
puts 'No file selected, cancelled'
|
41
|
+
Process.exit 1
|
42
|
+
end
|
43
|
+
|
44
|
+
res.strip
|
45
|
+
else
|
46
|
+
reader = TTY::Reader.new
|
47
|
+
puts
|
48
|
+
files.each.with_index do |f, i|
|
49
|
+
puts NA::Color.template(format("{bw}%<idx> 2d{xw}) {y}%<file>s{x}\n", idx: i + 1, file: f))
|
50
|
+
end
|
51
|
+
res = reader.read_line(NA::Color.template('{bw}Use which file? {x}')).strip.to_i
|
52
|
+
files[res - 1]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_action(file, action, note = nil)
|
57
|
+
content = IO.read(file)
|
58
|
+
unless content =~ /^[ \t]*Inbox:/i
|
59
|
+
content = "Inbox: @inbox\n#{content}"
|
60
|
+
end
|
61
|
+
|
62
|
+
content.sub!(/^([ \t]*)Inbox:(.*?)$/) do
|
63
|
+
m = Regexp.last_match
|
64
|
+
note = note.nil? ? '' : "\n#{m[1]}\t\t#{note.join('').strip}"
|
65
|
+
"#{m[1]}Inbox:#{m[2]}\n#{m[1]}\t- #{action}#{note}"
|
66
|
+
end
|
67
|
+
|
68
|
+
File.open(file, 'w') { |f| f.puts content }
|
69
|
+
|
70
|
+
puts NA::Color.template("{by}Task added to {bw}#{file}{x}")
|
71
|
+
end
|
72
|
+
|
73
|
+
def output_actions(actions, depth, extension)
|
74
|
+
template = if NA.find_files(depth: depth, extension: extension).count > 1
|
75
|
+
if depth > 1
|
76
|
+
'%filename%parent%action'
|
77
|
+
else
|
78
|
+
'%project%parent%action'
|
79
|
+
end
|
80
|
+
else
|
81
|
+
'%parent%action'
|
82
|
+
end
|
83
|
+
puts actions.map { |action| action.pretty(template: { output: template }) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def parse_actions(depth: 1, extension: 'taskpaper', na_tag: 'na', tag: nil, search: nil)
|
87
|
+
actions = []
|
88
|
+
required = []
|
89
|
+
optional = []
|
90
|
+
|
91
|
+
tag&.each do |t|
|
92
|
+
new_rx = " @#{t[:tag]}"
|
93
|
+
new_rx = "#{new_rx}\\(#{t[:value]}\\)" if t[:value]
|
94
|
+
|
95
|
+
optional.push(new_rx)
|
96
|
+
required.push(new_rx) if t[:required]
|
97
|
+
end
|
98
|
+
|
99
|
+
unless search.nil?
|
100
|
+
if search.is_a?(String)
|
101
|
+
optional.push(search)
|
102
|
+
required.push(search)
|
103
|
+
else
|
104
|
+
search.each do |t|
|
105
|
+
new_rx = t[:token].to_s
|
106
|
+
|
107
|
+
optional.push(new_rx)
|
108
|
+
required.push(new_rx) if t[:required]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
na_tag = "@#{na_tag.sub(/^@/, '')}"
|
114
|
+
|
115
|
+
files = find_files(depth: depth, extension: extension)
|
116
|
+
files.each do |file|
|
117
|
+
content = IO.read(file)
|
118
|
+
indent_level = 0
|
119
|
+
parent = []
|
120
|
+
content.split("\n").each do |line|
|
121
|
+
new_action = nil
|
122
|
+
if line =~ /([ \t]*)([^\-]+.*?):/
|
123
|
+
proj = Regexp.last_match(2)
|
124
|
+
indent = line.indent_level
|
125
|
+
|
126
|
+
if indent.zero?
|
127
|
+
parent = [proj]
|
128
|
+
elsif indent <= indent_level
|
129
|
+
parent.slice!(indent_level, parent.count - indent_level)
|
130
|
+
parent.push(proj)
|
131
|
+
elsif indent > indent_level
|
132
|
+
parent.push(proj)
|
133
|
+
indent_level = indent
|
134
|
+
end
|
135
|
+
elsif line =~ /^[ \t]*- / && line !~ / @done/
|
136
|
+
unless optional.empty? && required.empty?
|
137
|
+
next unless line.matches(any: optional, all: required)
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
action = line.sub(/^[ \t]*- /, '').sub(/ #{na_tag}/, '')
|
142
|
+
new_action = NA::Action.new(file, File.basename(file, ".#{extension}"), parent.dup, action)
|
143
|
+
actions.push(new_action)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
actions
|
148
|
+
end
|
149
|
+
|
150
|
+
##
|
151
|
+
## Generate a menu of options and allow user selection
|
152
|
+
##
|
153
|
+
## @return [String] The selected option
|
154
|
+
##
|
155
|
+
## @param options [Array] The options from which to choose
|
156
|
+
## @param prompt [String] The prompt
|
157
|
+
## @param multiple [Boolean] If true, allow multiple selections
|
158
|
+
## @param sorted [Boolean] If true, sort selections alphanumerically
|
159
|
+
## @param fzf_args [Array] Additional fzf arguments
|
160
|
+
##
|
161
|
+
def choose_from(options, prompt: 'Make a selection: ', multiple: false, sorted: true, fzf_args: [])
|
162
|
+
return nil unless $stdout.isatty
|
163
|
+
|
164
|
+
# fzf_args << '-1' # User is expecting a menu, and even if only one it seves as confirmation
|
165
|
+
default_args = []
|
166
|
+
default_args << %(--prompt="#{prompt}")
|
167
|
+
default_args << "--height=#{options.count + 2}"
|
168
|
+
default_args << '--info=inline'
|
169
|
+
default_args << '--multi' if multiple
|
170
|
+
header = "esc: cancel,#{multiple ? ' tab: multi-select, ctrl-a: select all,' : ''} return: confirm"
|
171
|
+
default_args << %(--header="#{header}")
|
172
|
+
default_args.concat(fzf_args)
|
173
|
+
options.sort! if sorted
|
174
|
+
|
175
|
+
res = `echo #{Shellwords.escape(options.join("\n"))}|#{TTY::Which.which('fzf')} #{default_args.join(' ')}`
|
176
|
+
return false if res.strip.size.zero?
|
177
|
+
|
178
|
+
res
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
data/lib/na/string.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ::String
|
4
|
+
def indent_level
|
5
|
+
prefix = match(/(^[ \t]+)/)
|
6
|
+
return 0 if prefix.nil?
|
7
|
+
|
8
|
+
prefix[1].gsub(/ /, "\t").scan(/\t/).count
|
9
|
+
end
|
10
|
+
|
11
|
+
##
|
12
|
+
## Colorize @tags with ANSI escapes
|
13
|
+
##
|
14
|
+
## @param color [String] color (see #Color)
|
15
|
+
##
|
16
|
+
## @return [String] string with @tags highlighted
|
17
|
+
##
|
18
|
+
def highlight_tags(color: '{m}', value: '{y}', parens: '{m}', last_color: '{g}')
|
19
|
+
tag_color = NA::Color.template(color)
|
20
|
+
paren_color = NA::Color.template(parens)
|
21
|
+
value_color = NA::Color.template(value)
|
22
|
+
gsub(/(\s|m)(@[^ ("']+)(?:(\()(.*?)(\)))?/, "\\1#{tag_color}\\2#{paren_color}\\3#{value_color}\\4#{paren_color}\\5#{last_color}")
|
23
|
+
end
|
24
|
+
|
25
|
+
def matches(any: [], all: [])
|
26
|
+
matches_any(any) && matches_all(all)
|
27
|
+
end
|
28
|
+
|
29
|
+
def matches_any(regexes)
|
30
|
+
regexes.each do |rx|
|
31
|
+
return true if match(Regexp.new(rx, Regexp::IGNORECASE))
|
32
|
+
end
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
def matches_all(regexes)
|
37
|
+
regexes.each do |rx|
|
38
|
+
return false unless match(Regexp.new(rx, Regexp::IGNORECASE))
|
39
|
+
end
|
40
|
+
true
|
41
|
+
end
|
42
|
+
end
|
data/lib/na/version.rb
ADDED
data/lib/na.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'na/version.rb'
|
2
|
+
|
3
|
+
# Add requires for other files you add to your project here, so
|
4
|
+
# you just need to require this one file in your bin file
|
5
|
+
require 'shellwords'
|
6
|
+
require 'tty-reader'
|
7
|
+
require 'tty-which'
|
8
|
+
require 'na/colors.rb'
|
9
|
+
require 'na/string.rb'
|
10
|
+
require 'na/action.rb'
|
11
|
+
require 'na/next_action.rb'
|
data/na.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# Ensure we require the local version and not one we might have installed already
|
2
|
+
require File.join([File.dirname(__FILE__),'lib','na','version.rb'])
|
3
|
+
spec = Gem::Specification.new do |s|
|
4
|
+
s.name = 'na'
|
5
|
+
s.version = Na::VERSION
|
6
|
+
s.author = 'Brett Terpstra'
|
7
|
+
s.email = 'me@brettterpstra.com'
|
8
|
+
s.homepage = 'https://brettterpstra.com'
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.summary = 'A command line tool for adding and listing project todos'
|
11
|
+
s.license = 'MIT'
|
12
|
+
s.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
13
|
+
`git ls-files -z`.split("\x0").reject { |f| f.strip =~ %r{^((test|spec|features)/|\.git|buildnotes|.*\.taskpaper)} }
|
14
|
+
end
|
15
|
+
s.require_paths << 'lib'
|
16
|
+
s.extra_rdoc_files = ['README.rdoc','na.rdoc']
|
17
|
+
s.rdoc_options << '--title' << 'na' << '--main' << 'README.rdoc' << '-ri'
|
18
|
+
s.bindir = 'bin'
|
19
|
+
s.executables << 'na'
|
20
|
+
s.add_development_dependency('rake','~> 0.9.2')
|
21
|
+
s.add_development_dependency('rdoc', '~> 4.3')
|
22
|
+
s.add_development_dependency('minitest', '~> 5.14')
|
23
|
+
s.add_runtime_dependency('gli','~> 2.21.0')
|
24
|
+
s.add_runtime_dependency('tty-reader', '~> 0.9', '>= 0.9.0')
|
25
|
+
s.add_runtime_dependency('tty-which', '~> 0.5', '>= 0.5.0')
|
26
|
+
end
|