howzit 1.2.15 → 1.2.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/bin/howzit +193 -2
- data/lib/howzit/buildnote.rb +543 -0
- data/lib/howzit/buildnotes.rb +0 -1
- data/lib/howzit/colors.rb +2 -2
- data/lib/howzit/config.rb +128 -0
- data/lib/howzit/hash.rb +35 -0
- data/lib/howzit/prompt.rb +84 -11
- data/lib/howzit/stringutils.rb +54 -8
- data/lib/howzit/task.rb +22 -0
- data/lib/howzit/topic.rb +233 -0
- data/lib/howzit/util.rb +149 -0
- data/lib/howzit/version.rb +1 -1
- data/lib/howzit.rb +40 -5
- data/spec/ruby_gem_spec.rb +87 -17
- metadata +8 -2
@@ -0,0 +1,543 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Howzit
|
4
|
+
# BuildNote Class
|
5
|
+
class BuildNote
|
6
|
+
attr_accessor :topics
|
7
|
+
|
8
|
+
attr_reader :metadata, :title
|
9
|
+
|
10
|
+
def initialize(file: nil, args: [])
|
11
|
+
@topics = []
|
12
|
+
@metadata = {}
|
13
|
+
|
14
|
+
read_help(file)
|
15
|
+
end
|
16
|
+
|
17
|
+
def inspect
|
18
|
+
puts "#<Howzit::BuildNote @topics=[#{@topics.count}]>"
|
19
|
+
end
|
20
|
+
|
21
|
+
def run
|
22
|
+
process
|
23
|
+
end
|
24
|
+
|
25
|
+
def edit
|
26
|
+
edit_note
|
27
|
+
end
|
28
|
+
|
29
|
+
def find_topic(term)
|
30
|
+
@topics.filter do |topic|
|
31
|
+
rx = term.to_rx
|
32
|
+
topic.title.downcase =~ rx
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def grep(term)
|
37
|
+
@topics.filter { |topic| topic.grep(term) }
|
38
|
+
end
|
39
|
+
|
40
|
+
# Output a list of topic titles
|
41
|
+
def list
|
42
|
+
output = []
|
43
|
+
output.push(Color.template("{bg}Topics:{x}\n"))
|
44
|
+
@topics.each do |topic|
|
45
|
+
output.push(Color.template("- {bw}#{topic.title}{x}"))
|
46
|
+
end
|
47
|
+
output.join("\n")
|
48
|
+
end
|
49
|
+
|
50
|
+
def list_topics
|
51
|
+
@topics.map { |topic| topic.title }
|
52
|
+
end
|
53
|
+
|
54
|
+
def list_completions
|
55
|
+
list_topics.join("\n")
|
56
|
+
end
|
57
|
+
|
58
|
+
def list_runnable_completions
|
59
|
+
output = []
|
60
|
+
@topics.each do |topic|
|
61
|
+
output.push(topic.title) if topic.tasks.count.positive?
|
62
|
+
end
|
63
|
+
output.join("\n")
|
64
|
+
end
|
65
|
+
|
66
|
+
def list_runnable
|
67
|
+
output = []
|
68
|
+
output.push(Color.template(%({bg}"Runnable" Topics:{x}\n)))
|
69
|
+
@topics.each do |topic|
|
70
|
+
s_out = []
|
71
|
+
|
72
|
+
topic.tasks.each do |task|
|
73
|
+
s_out.push(task.to_list)
|
74
|
+
end
|
75
|
+
|
76
|
+
unless s_out.empty?
|
77
|
+
output.push(Color.template("- {bw}#{topic.title}{x}"))
|
78
|
+
output.push(s_out.join("\n"))
|
79
|
+
end
|
80
|
+
end
|
81
|
+
output.join("\n")
|
82
|
+
end
|
83
|
+
|
84
|
+
def read_file(file)
|
85
|
+
read_help_file(file)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Create a buildnotes skeleton
|
89
|
+
def create_note
|
90
|
+
trap('SIGINT') do
|
91
|
+
warn "\nCanceled"
|
92
|
+
exit!
|
93
|
+
end
|
94
|
+
default = !$stdout.isatty || Howzit.options[:default]
|
95
|
+
# First make sure there isn't already a buildnotes file
|
96
|
+
if note_file
|
97
|
+
fname = Color.template("{by}#{note_file}{bw}")
|
98
|
+
unless default
|
99
|
+
res = Prompt.yn("#{fname} exists and appears to be a build note, continue anyway?", false)
|
100
|
+
unless res
|
101
|
+
puts 'Canceled'
|
102
|
+
Process.exit 0
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
title = File.basename(Dir.pwd)
|
108
|
+
if default
|
109
|
+
input = title
|
110
|
+
else
|
111
|
+
printf Color.template("{bw}Project name {xg}[#{title}]{bw}: {x}")
|
112
|
+
input = $stdin.gets.chomp
|
113
|
+
title = input unless input.empty?
|
114
|
+
end
|
115
|
+
summary = ''
|
116
|
+
unless default
|
117
|
+
printf Color.template('{bw}Project summary: {x}')
|
118
|
+
input = $stdin.gets.chomp
|
119
|
+
summary = input unless input.empty?
|
120
|
+
end
|
121
|
+
|
122
|
+
fname = 'buildnotes.md'
|
123
|
+
unless default
|
124
|
+
printf Color.template("{bw}Build notes filename (must begin with 'howzit' or 'build')\n{xg}[#{fname}]{bw}: {x}")
|
125
|
+
input = $stdin.gets.chomp
|
126
|
+
fname = input unless input.empty?
|
127
|
+
end
|
128
|
+
|
129
|
+
note = <<~EOBUILDNOTES
|
130
|
+
# #{title}
|
131
|
+
|
132
|
+
#{summary}
|
133
|
+
|
134
|
+
## File Structure
|
135
|
+
|
136
|
+
Where are the main editable files? Is there a dist/build folder that should be ignored?
|
137
|
+
|
138
|
+
## Build
|
139
|
+
|
140
|
+
What build system/parameters does this use?
|
141
|
+
|
142
|
+
@run(./build command)
|
143
|
+
|
144
|
+
## Deploy
|
145
|
+
|
146
|
+
What are the procedures/commands to deploy this project?
|
147
|
+
|
148
|
+
## Other
|
149
|
+
|
150
|
+
Version control notes, additional gulp/rake/make/etc tasks...
|
151
|
+
|
152
|
+
EOBUILDNOTES
|
153
|
+
|
154
|
+
if File.exist?(fname) && !default
|
155
|
+
file = Color.template("{by}#{fname}")
|
156
|
+
res = Prompt.yn("Are you absolutely sure you want to overwrite #{file}", false)
|
157
|
+
|
158
|
+
unless res
|
159
|
+
puts 'Canceled'
|
160
|
+
Process.exit 0
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
File.open(fname, 'w') do |f|
|
165
|
+
f.puts note
|
166
|
+
puts Color.template("{by}Build notes for #{title} written to #{fname}")
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def note_file
|
171
|
+
@note_file ||= find_note_file
|
172
|
+
end
|
173
|
+
|
174
|
+
private
|
175
|
+
|
176
|
+
##
|
177
|
+
## Traverse up directory tree looking for build notes
|
178
|
+
##
|
179
|
+
## @return topics dictionary
|
180
|
+
##
|
181
|
+
def glob_upstream
|
182
|
+
home = Dir.pwd
|
183
|
+
dir = File.dirname(home)
|
184
|
+
buildnotes = []
|
185
|
+
filename = nil
|
186
|
+
|
187
|
+
while dir != '/' && (dir =~ %r{[A-Z]:/}).nil?
|
188
|
+
Dir.chdir(dir)
|
189
|
+
filename = glob_note
|
190
|
+
unless filename.nil?
|
191
|
+
note = File.join(dir, filename)
|
192
|
+
buildnotes.push(note) unless note == note_file
|
193
|
+
end
|
194
|
+
dir = File.dirname(dir)
|
195
|
+
end
|
196
|
+
|
197
|
+
Dir.chdir(home)
|
198
|
+
|
199
|
+
buildnotes.reverse
|
200
|
+
end
|
201
|
+
|
202
|
+
def is_build_notes(filename)
|
203
|
+
return false if filename.downcase !~ /(^howzit[^.]*|build[^.]+)/
|
204
|
+
return false if Howzit.config.should_ignore(filename)
|
205
|
+
true
|
206
|
+
end
|
207
|
+
|
208
|
+
def glob_note
|
209
|
+
filename = nil
|
210
|
+
# Check for a build note file in the current folder. Filename must start
|
211
|
+
# with "build" and have an extension of txt, md, or markdown.
|
212
|
+
|
213
|
+
Dir.glob('*.{txt,md,markdown}').each do |f|
|
214
|
+
if is_build_notes(f)
|
215
|
+
filename = f
|
216
|
+
break
|
217
|
+
end
|
218
|
+
end
|
219
|
+
filename
|
220
|
+
end
|
221
|
+
|
222
|
+
def find_note_file
|
223
|
+
filename = glob_note
|
224
|
+
|
225
|
+
if filename.nil? && 'git'.available?
|
226
|
+
proj_dir = `git rev-parse --show-toplevel 2>/dev/null`.strip
|
227
|
+
unless proj_dir == ''
|
228
|
+
Dir.chdir(proj_dir)
|
229
|
+
filename = glob_note
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
if filename.nil? && Howzit.options[:include_upstream]
|
234
|
+
upstream_notes = glob_upstream
|
235
|
+
filename = upstream_notes[-1] unless upstream_notes.empty?
|
236
|
+
end
|
237
|
+
|
238
|
+
return nil if filename.nil?
|
239
|
+
|
240
|
+
File.expand_path(filename)
|
241
|
+
end
|
242
|
+
|
243
|
+
def read_upstream
|
244
|
+
buildnotes = glob_upstream
|
245
|
+
|
246
|
+
topics_dict = []
|
247
|
+
buildnotes.each do |path|
|
248
|
+
topics_dict.concat(read_help_file(path))
|
249
|
+
end
|
250
|
+
topics_dict
|
251
|
+
end
|
252
|
+
|
253
|
+
def ensure_requirements(template)
|
254
|
+
t_leader = IO.read(template).split(/^#/)[0].strip
|
255
|
+
if t_leader.length > 0
|
256
|
+
t_meta = t_leader.get_metadata
|
257
|
+
if t_meta.key?('required')
|
258
|
+
required = t_meta['required'].strip.split(/\s*,\s*/)
|
259
|
+
required.each do |req|
|
260
|
+
unless @metadata.keys.include?(req.downcase)
|
261
|
+
warn Color.template(%({xr}ERROR: Missing required metadata key from template '{bw}#{File.basename(template, '.md')}{xr}'{x}))
|
262
|
+
warn Color.template(%({xr}Please define {by}#{req.downcase}{xr} in build notes{x}))
|
263
|
+
Process.exit 1
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def get_template_topics(content)
|
271
|
+
leader = content.split(/^#/)[0].strip
|
272
|
+
|
273
|
+
template_topics = []
|
274
|
+
|
275
|
+
if leader.length > 0
|
276
|
+
data = leader.get_metadata
|
277
|
+
@metadata = @metadata.merge(data)
|
278
|
+
|
279
|
+
if data.key?('template')
|
280
|
+
templates = data['template'].strip.split(/\s*,\s*/)
|
281
|
+
templates.each do |t|
|
282
|
+
tasks = nil
|
283
|
+
if t =~ /\[(.*?)\]$/
|
284
|
+
tasks = Regexp.last_match[1].split(/\s*,\s*/).map {|t| t.gsub(/\*/, '.*?')}
|
285
|
+
t = t.sub(/\[.*?\]$/, '').strip
|
286
|
+
end
|
287
|
+
|
288
|
+
t_file = t.sub(/(\.md)?$/, '.md')
|
289
|
+
template = File.join(Howzit.config.template_folder, t_file)
|
290
|
+
if File.exist?(template)
|
291
|
+
ensure_requirements(template)
|
292
|
+
|
293
|
+
t_topics = BuildNote.new(file: template)
|
294
|
+
if tasks
|
295
|
+
tasks.each do |task|
|
296
|
+
t_topics.topics.each do |topic|
|
297
|
+
if topic.title =~ /^(.*?:)?#{task}$/i
|
298
|
+
topic.parent = t
|
299
|
+
template_topics.push(topic)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
else
|
304
|
+
t_topics.topics.map! do |topic|
|
305
|
+
topic.parent = t
|
306
|
+
topic
|
307
|
+
end
|
308
|
+
|
309
|
+
template_topics.concat(t_topics.topics)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
template_topics
|
316
|
+
end
|
317
|
+
|
318
|
+
def include_file(m)
|
319
|
+
file = File.expand_path(m[1])
|
320
|
+
|
321
|
+
return m[0] unless File.exist?(file)
|
322
|
+
|
323
|
+
content = IO.read(file)
|
324
|
+
home = ENV['HOME']
|
325
|
+
short_path = File.dirname(file.sub(/^#{home}/, '~'))
|
326
|
+
prefix = "#{short_path}/#{File.basename(file)}:"
|
327
|
+
parts = content.split(/^##+/)
|
328
|
+
parts.shift
|
329
|
+
if parts.empty?
|
330
|
+
content
|
331
|
+
else
|
332
|
+
"## #{parts.join('## ')}".gsub(/^(##+ *)(?=\S)/, "\\1#{prefix}")
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
def note_title(truncate = 0)
|
337
|
+
help = IO.read(note_file).strip
|
338
|
+
title = help.match(/(?:^(\S.*?)(?=\n==)|^# ?(.*?)$)/)
|
339
|
+
title = if title
|
340
|
+
title[1].nil? ? title[2] : title[1]
|
341
|
+
else
|
342
|
+
note_file.sub(/(\.\w+)?$/, '')
|
343
|
+
end
|
344
|
+
|
345
|
+
title && truncate.positive? ? title.trunc(truncate) : title
|
346
|
+
end
|
347
|
+
|
348
|
+
# Read in the build notes file and output a hash of "Title" => contents
|
349
|
+
def read_help_file(path = nil)
|
350
|
+
topics = []
|
351
|
+
|
352
|
+
filename = path.nil? ? note_file : path
|
353
|
+
|
354
|
+
help = IO.read(filename)
|
355
|
+
|
356
|
+
@title = note_title
|
357
|
+
|
358
|
+
help.gsub!(/@include\((.*?)\)/) do
|
359
|
+
include_file(Regexp.last_match)
|
360
|
+
end
|
361
|
+
|
362
|
+
template_topics = get_template_topics(help)
|
363
|
+
|
364
|
+
split = help.split(/^##+/)
|
365
|
+
split.slice!(0)
|
366
|
+
split.each do |sect|
|
367
|
+
next if sect.strip.empty?
|
368
|
+
|
369
|
+
lines = sect.split(/\n/)
|
370
|
+
title = lines.slice!(0).strip
|
371
|
+
prefix = ''
|
372
|
+
if path
|
373
|
+
if path =~ /#{Howzit.config.template_folder}/
|
374
|
+
short_path = File.basename(path, '.md')
|
375
|
+
else
|
376
|
+
home = ENV['HOME']
|
377
|
+
short_path = File.dirname(path.sub(/^#{home}/, '~'))
|
378
|
+
prefix = "_from #{short_path}_\n\n"
|
379
|
+
end
|
380
|
+
title = "#{short_path}:#{title}"
|
381
|
+
end
|
382
|
+
topic = Topic.new(title, prefix + lines.join("\n").strip.render_template(@metadata))
|
383
|
+
topics.push(topic)
|
384
|
+
end
|
385
|
+
|
386
|
+
template_topics.each do |topic|
|
387
|
+
topics.push(topic) unless find_topic(topic.title.sub(/^.+:/, '')).count.positive?
|
388
|
+
end
|
389
|
+
|
390
|
+
topics
|
391
|
+
end
|
392
|
+
|
393
|
+
def read_help(path = nil)
|
394
|
+
@topics = read_help_file(path)
|
395
|
+
return unless path.nil? && Howzit.options[:include_upstream]
|
396
|
+
|
397
|
+
upstream_topics = read_upstream
|
398
|
+
|
399
|
+
upstream_topics.each do |topic|
|
400
|
+
@topics.push(topic) unless find_topic(title.sub(/^.+:/, '')).count.positive?
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
def edit_note
|
405
|
+
editor = Howzit.options.fetch(:editor, ENV['EDITOR'])
|
406
|
+
|
407
|
+
raise 'No editor defined' if editor.nil?
|
408
|
+
|
409
|
+
raise "Invalid editor (#{editor})" unless Util.valid_command?(editor)
|
410
|
+
|
411
|
+
if note_file.nil?
|
412
|
+
res = Prompt.yn('No build notes file found, create one?', true)
|
413
|
+
|
414
|
+
create_note if res
|
415
|
+
edit_note
|
416
|
+
else
|
417
|
+
`#{editor} "#{note_file}"`
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
def process_topic(topic, run, single = false)
|
422
|
+
new_topic = topic.dup
|
423
|
+
|
424
|
+
# Handle variable replacement
|
425
|
+
|
426
|
+
unless Howzit.arguments.empty?
|
427
|
+
new_topic.content = new_topic.content.gsub(/\$(\d+)/) do |m|
|
428
|
+
idx = m[1].to_i - 1
|
429
|
+
Howzit.arguments.length > idx ? Howzit.arguments[idx] : m
|
430
|
+
end
|
431
|
+
new_topic.content = new_topic.content.gsub(/\$[@*]/, Shellwords.join(Howzit.arguments))
|
432
|
+
end
|
433
|
+
|
434
|
+
output = if run
|
435
|
+
new_topic.run
|
436
|
+
else
|
437
|
+
new_topic.print_out({ single: single })
|
438
|
+
end
|
439
|
+
output.nil? ? '' : output.join("\n")
|
440
|
+
end
|
441
|
+
|
442
|
+
def process
|
443
|
+
output = []
|
444
|
+
|
445
|
+
unless note_file
|
446
|
+
Process.exit 0 if Howzit.options[:list_runnable_titles] || Howzit.options[:list_topic_titles]
|
447
|
+
|
448
|
+
# clear the buffer
|
449
|
+
ARGV.length.times do
|
450
|
+
ARGV.shift
|
451
|
+
end
|
452
|
+
res = yn("No build notes file found, create one?", true)
|
453
|
+
create_note if res
|
454
|
+
Process.exit 1
|
455
|
+
end
|
456
|
+
|
457
|
+
if Howzit.options[:title_only]
|
458
|
+
out = note_title(20)
|
459
|
+
$stdout.print(out.strip)
|
460
|
+
Process.exit(0)
|
461
|
+
elsif Howzit.options[:output_title] && !Howzit.options[:run]
|
462
|
+
if @title && !@title.empty?
|
463
|
+
header = @title.format_header({ hr: "\u{2550}", color: '{bwK}' })
|
464
|
+
output.push("#{header}\n")
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
if Howzit.options[:list_runnable]
|
469
|
+
if Howzit.options[:list_runnable_titles]
|
470
|
+
out = list_runnable_completions
|
471
|
+
$stdout.print(out.strip)
|
472
|
+
else
|
473
|
+
out = list_runnable
|
474
|
+
Util.show(out, { color: Howzit.options[:color], paginate: false, highlight: false })
|
475
|
+
end
|
476
|
+
Process.exit(0)
|
477
|
+
end
|
478
|
+
|
479
|
+
if Howzit.options[:list_topics]
|
480
|
+
if Howzit.options[:list_topic_titles]
|
481
|
+
$stdout.print(list_completions)
|
482
|
+
else
|
483
|
+
out = list
|
484
|
+
Util.show(out, { color: Howzit.options[:color], paginate: false, highlight: false })
|
485
|
+
end
|
486
|
+
Process.exit(0)
|
487
|
+
end
|
488
|
+
|
489
|
+
topic_matches = []
|
490
|
+
if Howzit.options[:grep]
|
491
|
+
matches = grep_topics(Howzit.options[:grep])
|
492
|
+
case Howzit.options[:multiple_matches]
|
493
|
+
when :all
|
494
|
+
topic_matches.concat(matches.sort)
|
495
|
+
else
|
496
|
+
topic_matches.concat(Prompt.choose(matches))
|
497
|
+
end
|
498
|
+
elsif Howzit.options[:choose]
|
499
|
+
titles = Prompt.choose(list_topics)
|
500
|
+
titles.each { |title| topic_matches.push(find_topic(title)[0]) }
|
501
|
+
# If there are arguments use those to search for a matching topic
|
502
|
+
elsif !Howzit.cli_args.empty?
|
503
|
+
search = Howzit.cli_args.join(' ').strip.downcase.split(/ *, */).map(&:strip)
|
504
|
+
|
505
|
+
search.each do |s|
|
506
|
+
matches = find_topic(s)
|
507
|
+
|
508
|
+
if matches.empty?
|
509
|
+
output.push(Color.template(%({bR}ERROR:{xr} No topic match found for {bw}#{s}{x}\n)))
|
510
|
+
else
|
511
|
+
case Howzit.options[:multiple_matches]
|
512
|
+
when :first
|
513
|
+
topic_matches.push(matches[0])
|
514
|
+
when :best
|
515
|
+
topic_matches.push(matches.sort.min_by { |t| t.title.length })
|
516
|
+
when :all
|
517
|
+
topic_matches.concat(matches)
|
518
|
+
else
|
519
|
+
titles = matches.map { |topic| topic.title }
|
520
|
+
res = Prompt.choose(titles)
|
521
|
+
res.each { |title| topic_matches.concat(find_topic(title)) }
|
522
|
+
end
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
if topic_matches.empty? && !Howzit.options[:show_all_on_error]
|
527
|
+
Util.show(output.join("\n"), { color: true, highlight: false, paginate: false, wrap: 0 })
|
528
|
+
Process.exit 1
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
if !topic_matches.empty?
|
533
|
+
# If we found a match
|
534
|
+
topic_matches.each { |topic_match| output.push(process_topic(topic_match, Howzit.options[:run], true)) }
|
535
|
+
else
|
536
|
+
# If there's no argument or no match found, output all
|
537
|
+
topics.each { |k| output.push(process_topic(k, false, false)) }
|
538
|
+
end
|
539
|
+
Howzit.options[:paginate] = false if Howzit.options[:run]
|
540
|
+
Util.show(output.join("\n").strip, Howzit.options)
|
541
|
+
end
|
542
|
+
end
|
543
|
+
end
|
data/lib/howzit/buildnotes.rb
CHANGED
data/lib/howzit/colors.rb
CHANGED
@@ -223,8 +223,8 @@ module Howzit
|
|
223
223
|
##
|
224
224
|
def template(input)
|
225
225
|
input = input.join(' ') if input.is_a? Array
|
226
|
-
input.gsub
|
227
|
-
fmt =
|
226
|
+
fmt = input.gsub(/%/, '%%')
|
227
|
+
fmt = fmt.gsub(/\{(\w+)\}/) do
|
228
228
|
Regexp.last_match(1).split('').map { |c| "%<#{c}>s" }.join('')
|
229
229
|
end
|
230
230
|
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module Howzit
|
2
|
+
# Config Class
|
3
|
+
class Config
|
4
|
+
attr_reader :options
|
5
|
+
|
6
|
+
DEFAULTS = {
|
7
|
+
color: true,
|
8
|
+
config_editor: ENV['EDITOR'] || nil,
|
9
|
+
editor: ENV['EDITOR'] || nil,
|
10
|
+
header_format: 'border',
|
11
|
+
highlight: true,
|
12
|
+
highlighter: 'auto',
|
13
|
+
include_upstream: false,
|
14
|
+
log_level: 1, # 0: debug, 1: info, 2: warn, 3: error
|
15
|
+
matching: 'partial', # exact, partial, fuzzy, beginswith
|
16
|
+
multiple_matches: 'choose',
|
17
|
+
output_title: false,
|
18
|
+
pager: 'auto',
|
19
|
+
paginate: true,
|
20
|
+
show_all_code: false,
|
21
|
+
show_all_on_error: false,
|
22
|
+
wrap: 0
|
23
|
+
}.deep_freeze
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
load_options
|
27
|
+
end
|
28
|
+
|
29
|
+
def write_config(config)
|
30
|
+
File.open(config_file, 'w') { |f| f.puts config.to_yaml }
|
31
|
+
end
|
32
|
+
|
33
|
+
def should_ignore(filename)
|
34
|
+
return false unless File.exist?(ignore_file)
|
35
|
+
|
36
|
+
@ignore_patterns ||= YAML.safe_load(IO.read(ignore_file))
|
37
|
+
|
38
|
+
ignore = false
|
39
|
+
|
40
|
+
@ignore_patterns.each do |pat|
|
41
|
+
if filename =~ /#{pat}/
|
42
|
+
ignore = true
|
43
|
+
break
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
ignore
|
48
|
+
end
|
49
|
+
|
50
|
+
def template_folder
|
51
|
+
File.join(config_dir, 'templates')
|
52
|
+
end
|
53
|
+
|
54
|
+
def editor
|
55
|
+
edit_config(DEFAULTS)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def load_options
|
61
|
+
Color.coloring = $stdout.isatty
|
62
|
+
flags = {
|
63
|
+
choose: false,
|
64
|
+
default: false,
|
65
|
+
grep: nil,
|
66
|
+
list_runnable: false,
|
67
|
+
list_runnable_titles: false,
|
68
|
+
list_topic_titles: false,
|
69
|
+
list_topics: false,
|
70
|
+
quiet: false,
|
71
|
+
run: false,
|
72
|
+
title_only: false,
|
73
|
+
verbose: false
|
74
|
+
}
|
75
|
+
|
76
|
+
config = load_config
|
77
|
+
@options = flags.merge(config)
|
78
|
+
end
|
79
|
+
|
80
|
+
def config_dir
|
81
|
+
File.expand_path(CONFIG_DIR)
|
82
|
+
end
|
83
|
+
|
84
|
+
def config_file
|
85
|
+
File.join(config_dir, CONFIG_FILE)
|
86
|
+
end
|
87
|
+
|
88
|
+
def ignore_file
|
89
|
+
File.join(config_dir, IGNORE_FILE)
|
90
|
+
end
|
91
|
+
|
92
|
+
def create_config(d)
|
93
|
+
unless File.directory?(config_dir)
|
94
|
+
warn "Creating config directory at #{config_dir}"
|
95
|
+
FileUtils.mkdir_p(config_dir)
|
96
|
+
end
|
97
|
+
|
98
|
+
unless File.exist?(config_file)
|
99
|
+
warn "Writing fresh config file to #{config_file}"
|
100
|
+
write_config(d)
|
101
|
+
end
|
102
|
+
config_file
|
103
|
+
end
|
104
|
+
|
105
|
+
def load_config
|
106
|
+
file = create_config(DEFAULTS)
|
107
|
+
config = YAML.load(IO.read(file))
|
108
|
+
newconfig = config ? DEFAULTS.merge(config) : DEFAULTS
|
109
|
+
write_config(newconfig)
|
110
|
+
newconfig.dup
|
111
|
+
end
|
112
|
+
|
113
|
+
def edit_config(d)
|
114
|
+
editor = Howzit.options.fetch(:config_editor, ENV['EDITOR'])
|
115
|
+
|
116
|
+
raise 'No config_editor defined' if editor.nil?
|
117
|
+
|
118
|
+
# raise "Invalid editor (#{editor})" unless Util.valid_command?(editor)
|
119
|
+
|
120
|
+
load_config
|
121
|
+
if Util.valid_command?(editor.split(/ /).first)
|
122
|
+
system %(#{editor} "#{config_file}")
|
123
|
+
else
|
124
|
+
`open -a "#{editor}" "#{config_file}"`
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|