howzit 2.0.8 → 2.0.11
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/.travis.yml +1 -1
- data/CHANGELOG.md +32 -0
- data/README.md +4 -343
- data/bin/howzit +101 -83
- data/fish/completions/howzit.fish +34 -2
- data/howzit.gemspec +2 -0
- data/lib/howzit/buildnote.rb +161 -33
- data/lib/howzit/colors.rb +3 -0
- data/lib/howzit/config.rb +39 -5
- data/lib/howzit/console_logger.rb +38 -0
- data/lib/howzit/hash.rb +25 -4
- data/lib/howzit/prompt.rb +42 -6
- data/lib/howzit/stringutils.rb +131 -4
- data/lib/howzit/task.rb +10 -6
- data/lib/howzit/topic.rb +72 -10
- data/lib/howzit/util.rb +35 -5
- data/lib/howzit/version.rb +1 -1
- data/lib/howzit.rb +13 -0
- data/spec/cli_spec.rb +27 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/task_spec.rb +6 -1
- metadata +32 -5
- data/lib/howzit/buildnotes.rb +0 -1252
- data/spec/buildnotes.md.bak +0 -22
@@ -1,3 +1,35 @@
|
|
1
1
|
complete -xc howzit -a "(howzit -L)"
|
2
|
-
complete -
|
3
|
-
|
2
|
+
complete -c howzit -l default -d "Answer all prompts with default response"
|
3
|
+
complete -c howzit -x -s m -l matching -a "partial exact fuzzy beginswith" -d "Topics matching type"
|
4
|
+
complete -c howzit -x -l multiple -a "first best all choose" -d "Multiple result handling"
|
5
|
+
complete -c howzit -s u -l no-upstream -d "Don't traverse up parent directories for additional build notes"
|
6
|
+
complete -c howzit -s u -l upstream -d "Traverse up parent directories for additional build notes"
|
7
|
+
complete -c howzit -s L -l list-completions -d "List topics for completion"
|
8
|
+
complete -c howzit -s l -l list -d "List available topics"
|
9
|
+
complete -c howzit -s R -l list-runnable -d "List topics containing @ directives (verbose)"
|
10
|
+
complete -c howzit -s T -l task-list -d "List topics containing @ directives (completion-compatible)"
|
11
|
+
complete -c howzit -l templates -d "List available templates"
|
12
|
+
complete -c howzit -l title-only -d "Output title only"
|
13
|
+
complete -c howzit -s c -l create -d "Create a skeleton build note in the current working directory"
|
14
|
+
complete -c howzit -f -l config-get -d "Display the configuration settings or setting for a specific key"
|
15
|
+
complete -c howzit -f -l config-set -d "Set a config value (must be a valid key)"
|
16
|
+
complete -c howzit -l edit-config -d "Edit configuration file using editor (subl)"
|
17
|
+
complete -c howzit -s e -l edit -d "Edit buildnotes file in current working directory using editor (subl)"
|
18
|
+
complete -c howzit -x -l grep -d "Display sections matching a search pattern"
|
19
|
+
complete -c howzit -s r -l run -a "(howzit -T)" -d "Execute @run, @open, and/or @copy commands for given topic"
|
20
|
+
complete -c howzit -s s -l select -d "Select topic from menu"
|
21
|
+
complete -c howzit -l color -d "Colorize output (default on)"
|
22
|
+
complete -c howzit -l no-color -d "Don't colorize output (default on)"
|
23
|
+
complete -c howzit -x -l header-format -d "Formatting style for topic titles (border, block)"
|
24
|
+
complete -c howzit -l md-highlight -d "Highlight Markdown syntax (default on), requires mdless or mdcat"
|
25
|
+
complete -c howzit -l no-md-highlight -d "Don't highlight Markdown syntax (default on), requires mdless or mdcat"
|
26
|
+
complete -c howzit -l pager -d "Paginate output (default on)"
|
27
|
+
complete -c howzit -l no-pager -d "Don't paginate output (default on)"
|
28
|
+
complete -c howzit -l show-code -d "Display the content of fenced run blocks"
|
29
|
+
complete -c howzit -s t -l title -d "Output title with build notes"
|
30
|
+
complete -c howzit -x -s w -l wrap -d "Wrap to specified width (default 80, 0 to disable)"
|
31
|
+
complete -c howzit -s d -l debug -d "Show debug messages (and all messages)"
|
32
|
+
complete -c howzit -s q -l quiet -d "Silence info message"
|
33
|
+
complete -c howzit -l verbose -d "Show all messages"
|
34
|
+
complete -c howzit -s h -l help -d "Display this screen"
|
35
|
+
complete -c howzit -s v -l version -d "Display version number"
|
data/howzit.gemspec
CHANGED
@@ -30,6 +30,7 @@ Gem::Specification.new do |spec|
|
|
30
30
|
|
31
31
|
spec.add_development_dependency 'rubocop', '~> 0.28'
|
32
32
|
spec.add_development_dependency 'rspec', '~> 3.1'
|
33
|
+
spec.add_development_dependency 'cli-test', '~> 1.0'
|
33
34
|
spec.add_development_dependency 'simplecov', '~> 0.9'
|
34
35
|
# spec.add_development_dependency 'codecov', '~> 0.1'
|
35
36
|
spec.add_development_dependency 'fuubar', '~> 2.0'
|
@@ -40,5 +41,6 @@ Gem::Specification.new do |spec|
|
|
40
41
|
|
41
42
|
spec.add_runtime_dependency 'mdless', '~> 1.0', '>= 1.0.28'
|
42
43
|
spec.add_runtime_dependency 'tty-screen', '~> 0.8'
|
44
|
+
spec.add_runtime_dependency 'tty-box', '~> 0.7'
|
43
45
|
# spec.add_runtime_dependency 'tty-prompt', '~> 0.23'
|
44
46
|
end
|
data/lib/howzit/buildnote.rb
CHANGED
@@ -7,10 +7,27 @@ module Howzit
|
|
7
7
|
|
8
8
|
attr_reader :metadata, :title
|
9
9
|
|
10
|
+
##
|
11
|
+
## Initialize a build note
|
12
|
+
##
|
13
|
+
## @param file [String] The path to the build note file
|
14
|
+
## @param args [Array] additional args
|
15
|
+
##
|
10
16
|
def initialize(file: nil, args: [])
|
11
17
|
@topics = []
|
12
|
-
|
13
|
-
|
18
|
+
if note_file.nil?
|
19
|
+
res = Prompt.yn('No build notes file found, create one?', default: true)
|
20
|
+
|
21
|
+
create_note if res
|
22
|
+
Process.exit 0
|
23
|
+
end
|
24
|
+
content = Util.read_file(note_file)
|
25
|
+
if content.nil? || content.empty?
|
26
|
+
Howzit.console.error("{br}No content found in build note (#{note_file}){x}".c)
|
27
|
+
Process.exit 1
|
28
|
+
else
|
29
|
+
@metadata = content.split(/^#/)[0].strip.get_metadata
|
30
|
+
end
|
14
31
|
|
15
32
|
read_help(file)
|
16
33
|
end
|
@@ -19,14 +36,25 @@ module Howzit
|
|
19
36
|
puts "#<Howzit::BuildNote @topics=[#{@topics.count}]>"
|
20
37
|
end
|
21
38
|
|
39
|
+
##
|
40
|
+
## Public method to begin processing the build note based on command line options
|
41
|
+
##
|
22
42
|
def run
|
23
43
|
process
|
24
44
|
end
|
25
45
|
|
46
|
+
##
|
47
|
+
## Public method to open build note in editor
|
48
|
+
##
|
26
49
|
def edit
|
27
50
|
edit_note
|
28
51
|
end
|
29
52
|
|
53
|
+
##
|
54
|
+
## Find a topic based on a fuzzy match
|
55
|
+
##
|
56
|
+
## @param term [String] The search term
|
57
|
+
##
|
30
58
|
def find_topic(term)
|
31
59
|
@topics.filter do |topic|
|
32
60
|
rx = term.to_rx
|
@@ -34,11 +62,19 @@ module Howzit
|
|
34
62
|
end
|
35
63
|
end
|
36
64
|
|
65
|
+
##
|
66
|
+
## Call grep on all topics, filtering out those that don't match
|
67
|
+
##
|
68
|
+
## @param term [String] The search pattern
|
69
|
+
##
|
37
70
|
def grep(term)
|
38
71
|
@topics.filter { |topic| topic.grep(term) }
|
39
72
|
end
|
40
73
|
|
41
74
|
# Output a list of topic titles
|
75
|
+
#
|
76
|
+
# @return [String] formatted list of topics in build note
|
77
|
+
#
|
42
78
|
def list
|
43
79
|
output = []
|
44
80
|
output.push("{bg}Topics:{x}\n".c)
|
@@ -48,14 +84,32 @@ module Howzit
|
|
48
84
|
output.join("\n")
|
49
85
|
end
|
50
86
|
|
87
|
+
|
88
|
+
##
|
89
|
+
## Return an array of topic titles
|
90
|
+
##
|
91
|
+
## @return [Array] array of topic titles
|
92
|
+
##
|
51
93
|
def list_topics
|
52
94
|
@topics.map { |topic| topic.title }
|
53
95
|
end
|
54
96
|
|
97
|
+
##
|
98
|
+
## Return a list of topic titles suitable for shell completion
|
99
|
+
##
|
100
|
+
## @return [String] newline-separated list of topic titles
|
101
|
+
##
|
55
102
|
def list_completions
|
56
103
|
list_topics.join("\n")
|
57
104
|
end
|
58
105
|
|
106
|
+
##
|
107
|
+
## Return a list of topics containing @directives,
|
108
|
+
## suitable for shell completion
|
109
|
+
##
|
110
|
+
## @return [String] newline-separated list of topic
|
111
|
+
## titles
|
112
|
+
##
|
59
113
|
def list_runnable_completions
|
60
114
|
output = []
|
61
115
|
@topics.each do |topic|
|
@@ -64,6 +118,12 @@ module Howzit
|
|
64
118
|
output.join("\n")
|
65
119
|
end
|
66
120
|
|
121
|
+
##
|
122
|
+
## Return a formatted list of topics containing
|
123
|
+
## directives suitable for console output
|
124
|
+
##
|
125
|
+
## @return [String] formatted list
|
126
|
+
##
|
67
127
|
def list_runnable
|
68
128
|
output = []
|
69
129
|
output.push(%({bg}"Runnable" Topics:{x}\n).c)
|
@@ -82,6 +142,11 @@ module Howzit
|
|
82
142
|
output.join("\n")
|
83
143
|
end
|
84
144
|
|
145
|
+
##
|
146
|
+
## Read the help file contents
|
147
|
+
##
|
148
|
+
## @param file [String] The filepath
|
149
|
+
##
|
85
150
|
def read_file(file)
|
86
151
|
read_help_file(file)
|
87
152
|
end
|
@@ -89,7 +154,7 @@ module Howzit
|
|
89
154
|
# Create a buildnotes skeleton
|
90
155
|
def create_note
|
91
156
|
trap('SIGINT') do
|
92
|
-
Howzit.console.info "\
|
157
|
+
Howzit.console.info "\nCancelled"
|
93
158
|
exit!
|
94
159
|
end
|
95
160
|
default = !$stdout.isatty || Howzit.options[:default]
|
@@ -210,26 +275,24 @@ module Howzit
|
|
210
275
|
buildnotes.reverse
|
211
276
|
end
|
212
277
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
278
|
+
##
|
279
|
+
## Glob current directory for valid build note filenames
|
280
|
+
## (must start with "build" or "howzit" and have
|
281
|
+
## extension of "txt", "md", or "markdown")
|
282
|
+
##
|
283
|
+
## @return [String] file path
|
284
|
+
##
|
219
285
|
def glob_note
|
220
|
-
|
221
|
-
# Check for a build note file in the current folder. Filename must start
|
222
|
-
# with "build" and have an extension of txt, md, or markdown.
|
223
|
-
|
224
|
-
Dir.glob('*.{txt,md,markdown}').each do |f|
|
225
|
-
if is_build_notes(f)
|
226
|
-
filename = f
|
227
|
-
break
|
228
|
-
end
|
229
|
-
end
|
230
|
-
filename
|
286
|
+
Dir.glob('*.{txt,md,markdown}').select(&:build_note?)[0]
|
231
287
|
end
|
232
288
|
|
289
|
+
##
|
290
|
+
## Search for a valid build note, checking current
|
291
|
+
## directory, git top level directory, and parent
|
292
|
+
## directories
|
293
|
+
##
|
294
|
+
## @return [String] filepath
|
295
|
+
##
|
233
296
|
def find_note_file
|
234
297
|
filename = glob_note
|
235
298
|
|
@@ -251,6 +314,11 @@ module Howzit
|
|
251
314
|
File.expand_path(filename)
|
252
315
|
end
|
253
316
|
|
317
|
+
##
|
318
|
+
## Search upstream directories for build notes
|
319
|
+
##
|
320
|
+
## @return [Array] array of build note paths
|
321
|
+
##
|
254
322
|
def read_upstream
|
255
323
|
buildnotes = glob_upstream
|
256
324
|
|
@@ -261,16 +329,23 @@ module Howzit
|
|
261
329
|
topics_dict
|
262
330
|
end
|
263
331
|
|
332
|
+
##
|
333
|
+
## Test to ensure that any `required` metadata in a
|
334
|
+
## template is fulfilled by the build note
|
335
|
+
##
|
336
|
+
## @param template [String] The template to read
|
337
|
+
## from
|
338
|
+
##
|
264
339
|
def ensure_requirements(template)
|
265
|
-
t_leader =
|
340
|
+
t_leader = Util.read_file(template).split(/^#/)[0].strip
|
266
341
|
if t_leader.length > 0
|
267
342
|
t_meta = t_leader.get_metadata
|
268
343
|
if t_meta.key?('required')
|
269
344
|
required = t_meta['required'].strip.split(/\s*,\s*/)
|
270
345
|
required.each do |req|
|
271
346
|
unless @metadata.keys.include?(req.downcase)
|
272
|
-
Howzit.console.error %({
|
273
|
-
Howzit.console.error %({
|
347
|
+
Howzit.console.error %({bRw}ERROR:{xbr} Missing required metadata key from template '{bw}#{File.basename(template, '.md')}{xr}'{x}).c
|
348
|
+
Howzit.console.error %({br}Please define {by}#{req.downcase}{xr} in build notes{x}).c
|
274
349
|
Process.exit 1
|
275
350
|
end
|
276
351
|
end
|
@@ -278,6 +353,11 @@ module Howzit
|
|
278
353
|
end
|
279
354
|
end
|
280
355
|
|
356
|
+
##
|
357
|
+
## Read a list of topics from an included template
|
358
|
+
##
|
359
|
+
## @param content [String] The template contents
|
360
|
+
##
|
281
361
|
def get_template_topics(content)
|
282
362
|
leader = content.split(/^#/)[0].strip
|
283
363
|
|
@@ -325,12 +405,18 @@ module Howzit
|
|
325
405
|
template_topics
|
326
406
|
end
|
327
407
|
|
328
|
-
|
329
|
-
|
408
|
+
##
|
409
|
+
## Import the contents of a filename as new topics
|
410
|
+
##
|
411
|
+
## @param mtch [MatchData] the filename match from
|
412
|
+
## the include directive
|
413
|
+
##
|
414
|
+
def include_file(mtch)
|
415
|
+
file = File.expand_path(mtch[1])
|
330
416
|
|
331
|
-
return
|
417
|
+
return mtch[0] unless File.exist?(file)
|
332
418
|
|
333
|
-
content =
|
419
|
+
content = Util.read_file(file)
|
334
420
|
home = ENV['HOME']
|
335
421
|
short_path = File.dirname(file.sub(/^#{home}/, '~'))
|
336
422
|
prefix = "#{short_path}/#{File.basename(file)}:"
|
@@ -343,8 +429,13 @@ module Howzit
|
|
343
429
|
end
|
344
430
|
end
|
345
431
|
|
432
|
+
##
|
433
|
+
## Get the title of the build note (top level header)
|
434
|
+
##
|
435
|
+
## @param truncate [Integer] Truncate to width
|
436
|
+
##
|
346
437
|
def note_title(truncate = 0)
|
347
|
-
help =
|
438
|
+
help = Util.read_file(note_file)
|
348
439
|
title = help.match(/(?:^(\S.*?)(?=\n==)|^# ?(.*?)$)/)
|
349
440
|
title = if title
|
350
441
|
title[1].nil? ? title[2] : title[1]
|
@@ -355,13 +446,24 @@ module Howzit
|
|
355
446
|
title && truncate.positive? ? title.trunc(truncate) : title
|
356
447
|
end
|
357
448
|
|
358
|
-
# Read in the build notes file and output a hash of
|
449
|
+
# Read in the build notes file and output a hash of
|
450
|
+
# "Title" => contents
|
451
|
+
#
|
452
|
+
# @param path [String] The build note path
|
453
|
+
#
|
454
|
+
# @return [Array] array of Topics
|
455
|
+
#
|
359
456
|
def read_help_file(path = nil)
|
360
457
|
topics = []
|
361
458
|
|
362
459
|
filename = path.nil? ? note_file : path
|
363
460
|
|
364
|
-
help =
|
461
|
+
help = Util.read_file(filename)
|
462
|
+
|
463
|
+
if help.nil? || help.empty?
|
464
|
+
Howzit.console.error("{br}No content found in #{filename}{x}".c)
|
465
|
+
Process.exit 1
|
466
|
+
end
|
365
467
|
|
366
468
|
@title = note_title
|
367
469
|
|
@@ -401,6 +503,11 @@ module Howzit
|
|
401
503
|
topics
|
402
504
|
end
|
403
505
|
|
506
|
+
##
|
507
|
+
## Read build note and include upstream topics
|
508
|
+
##
|
509
|
+
## @param path [String] The build note path
|
510
|
+
##
|
404
511
|
def read_help(path = nil)
|
405
512
|
@topics = read_help_file(path)
|
406
513
|
return unless path.nil? && Howzit.options[:include_upstream]
|
@@ -410,8 +517,17 @@ module Howzit
|
|
410
517
|
upstream_topics.each do |topic|
|
411
518
|
@topics.push(topic) unless find_topic(title.sub(/^.+:/, '')).count.positive?
|
412
519
|
end
|
520
|
+
|
521
|
+
if note_file && @topics.empty?
|
522
|
+
Howzit.console.error("{br}Note file found but no topics detected in #{note_file}{x}".c)
|
523
|
+
Process.exit 1
|
524
|
+
end
|
525
|
+
|
413
526
|
end
|
414
527
|
|
528
|
+
##
|
529
|
+
## Open build note in editor
|
530
|
+
##
|
415
531
|
def edit_note
|
416
532
|
editor = Howzit.options.fetch(:editor, ENV['EDITOR'])
|
417
533
|
|
@@ -429,7 +545,16 @@ module Howzit
|
|
429
545
|
end
|
430
546
|
end
|
431
547
|
|
432
|
-
|
548
|
+
##
|
549
|
+
## Run or print a topic
|
550
|
+
##
|
551
|
+
## @param topic [Topic] The topic
|
552
|
+
## @param run [Boolean] execute directives if
|
553
|
+
## true
|
554
|
+
## @param single [Boolean] is being output as a
|
555
|
+
## single topic
|
556
|
+
##
|
557
|
+
def process_topic(topic, run, single: false)
|
433
558
|
new_topic = topic.dup
|
434
559
|
|
435
560
|
# Handle variable replacement
|
@@ -443,6 +568,9 @@ module Howzit
|
|
443
568
|
output.nil? ? '' : output.join("\n")
|
444
569
|
end
|
445
570
|
|
571
|
+
##
|
572
|
+
## Search and process the build note
|
573
|
+
##
|
446
574
|
def process
|
447
575
|
output = []
|
448
576
|
|
@@ -535,10 +663,10 @@ module Howzit
|
|
535
663
|
|
536
664
|
if !topic_matches.empty?
|
537
665
|
# If we found a match
|
538
|
-
topic_matches.each { |topic_match| output.push(process_topic(topic_match, Howzit.options[:run], true)) }
|
666
|
+
topic_matches.each { |topic_match| output.push(process_topic(topic_match, Howzit.options[:run], single: true)) }
|
539
667
|
else
|
540
668
|
# If there's no argument or no match found, output all
|
541
|
-
topics.each { |k| output.push(process_topic(k, false, false)) }
|
669
|
+
topics.each { |k| output.push(process_topic(k, false, single: false)) }
|
542
670
|
end
|
543
671
|
Howzit.options[:paginate] = false if Howzit.options[:run]
|
544
672
|
Util.show(output.join("\n").strip, Howzit.options)
|
data/lib/howzit/colors.rb
CHANGED
@@ -4,7 +4,9 @@
|
|
4
4
|
module Howzit
|
5
5
|
# Terminal output color functions.
|
6
6
|
module Color
|
7
|
+
# Regexp to match excape sequences
|
7
8
|
ESCAPE_REGEX = /(?<=\[)(?:(?:(?:[349]|10)[0-9]|[0-9])?;?)+(?=m)/.freeze
|
9
|
+
|
8
10
|
# All available color names. Available as methods and string extensions.
|
9
11
|
#
|
10
12
|
# @example Use a color as a method. Color reset will be added to end of string.
|
@@ -79,6 +81,7 @@ module Howzit
|
|
79
81
|
[:default, '0;39']
|
80
82
|
].map(&:freeze).freeze
|
81
83
|
|
84
|
+
# Array of attribute keys only
|
82
85
|
ATTRIBUTE_NAMES = ATTRIBUTES.transpose.first
|
83
86
|
|
84
87
|
# Returns true if Howzit::Color supports the +feature+.
|
data/lib/howzit/config.rb
CHANGED
@@ -3,6 +3,7 @@ module Howzit
|
|
3
3
|
class Config
|
4
4
|
attr_reader :options
|
5
5
|
|
6
|
+
# Configuration defaults
|
6
7
|
DEFAULTS = {
|
7
8
|
color: true,
|
8
9
|
config_editor: ENV['EDITOR'] || nil,
|
@@ -46,7 +47,7 @@ module Howzit
|
|
46
47
|
def should_ignore(filename)
|
47
48
|
return false unless File.exist?(ignore_file)
|
48
49
|
|
49
|
-
@ignore_patterns ||= YAML.safe_load(
|
50
|
+
@ignore_patterns ||= YAML.safe_load(Util.read_file(ignore_file))
|
50
51
|
|
51
52
|
ignore = false
|
52
53
|
|
@@ -78,6 +79,11 @@ module Howzit
|
|
78
79
|
|
79
80
|
private
|
80
81
|
|
82
|
+
##
|
83
|
+
## Load command line options
|
84
|
+
##
|
85
|
+
## @return [Hash] options with command line flags merged in
|
86
|
+
##
|
81
87
|
def load_options
|
82
88
|
Color.coloring = $stdout.isatty
|
83
89
|
flags = {
|
@@ -98,19 +104,39 @@ module Howzit
|
|
98
104
|
@options = flags.merge(config)
|
99
105
|
end
|
100
106
|
|
107
|
+
##
|
108
|
+
## Get the config directory
|
109
|
+
##
|
110
|
+
## @return [String] path to config directory
|
111
|
+
##
|
101
112
|
def config_dir
|
102
113
|
File.expand_path(CONFIG_DIR)
|
103
114
|
end
|
104
115
|
|
116
|
+
##
|
117
|
+
## Get the config file
|
118
|
+
##
|
119
|
+
## @return [String] path to config file
|
120
|
+
##
|
105
121
|
def config_file
|
106
122
|
File.join(config_dir, CONFIG_FILE)
|
107
123
|
end
|
108
124
|
|
125
|
+
##
|
126
|
+
## Get the ignore config file
|
127
|
+
##
|
128
|
+
## @return [String] path to ignore config file
|
129
|
+
##
|
109
130
|
def ignore_file
|
110
131
|
File.join(config_dir, IGNORE_FILE)
|
111
132
|
end
|
112
133
|
|
113
|
-
|
134
|
+
##
|
135
|
+
## Create a new config file (and directory if needed)
|
136
|
+
##
|
137
|
+
## @param default [Hash] default configuration to write
|
138
|
+
##
|
139
|
+
def create_config(default)
|
114
140
|
unless File.directory?(config_dir)
|
115
141
|
Howzit::ConsoleLogger.new(1).info "Creating config directory at #{config_dir}"
|
116
142
|
FileUtils.mkdir_p(config_dir)
|
@@ -118,20 +144,28 @@ module Howzit
|
|
118
144
|
|
119
145
|
unless File.exist?(config_file)
|
120
146
|
Howzit::ConsoleLogger.new(1).info "Writing fresh config file to #{config_file}"
|
121
|
-
write_config(
|
147
|
+
write_config(default)
|
122
148
|
end
|
123
149
|
config_file
|
124
150
|
end
|
125
151
|
|
152
|
+
##
|
153
|
+
## Load the config file
|
154
|
+
##
|
155
|
+
## @return [Hash] configuration object
|
156
|
+
##
|
126
157
|
def load_config
|
127
158
|
file = create_config(DEFAULTS)
|
128
|
-
config = YAML.load(
|
159
|
+
config = YAML.load(Util.read_file(file))
|
129
160
|
newconfig = config ? DEFAULTS.merge(config) : DEFAULTS
|
130
161
|
write_config(newconfig)
|
131
162
|
newconfig.dup
|
132
163
|
end
|
133
164
|
|
134
|
-
|
165
|
+
##
|
166
|
+
## Open the config in an editor
|
167
|
+
##
|
168
|
+
def edit_config
|
135
169
|
editor = Howzit.options.fetch(:config_editor, ENV['EDITOR'])
|
136
170
|
|
137
171
|
raise 'No config_editor defined' if editor.nil?
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Available log levels
|
3
4
|
LOG_LEVELS = {
|
4
5
|
debug: 0,
|
5
6
|
info: 1,
|
@@ -12,30 +13,67 @@ module Howzit
|
|
12
13
|
class ConsoleLogger
|
13
14
|
attr_accessor :log_level
|
14
15
|
|
16
|
+
##
|
17
|
+
## Init the console logging object
|
18
|
+
##
|
19
|
+
## @param level [Integer] log level
|
20
|
+
##
|
15
21
|
def initialize(level = nil)
|
16
22
|
@log_level = level.to_i || Howzit.options[:log_level]
|
17
23
|
end
|
18
24
|
|
25
|
+
##
|
26
|
+
## Get the log level from options
|
27
|
+
##
|
28
|
+
## @return [Integer] log level
|
29
|
+
##
|
19
30
|
def reset_level
|
20
31
|
@log_level = Howzit.options[:log_level]
|
21
32
|
end
|
22
33
|
|
34
|
+
##
|
35
|
+
## Write a message to the console based on the urgency
|
36
|
+
## level and user's log level setting
|
37
|
+
##
|
38
|
+
## @param msg [String] The message
|
39
|
+
## @param level [Symbol] The level
|
40
|
+
##
|
23
41
|
def write(msg, level = :info)
|
24
42
|
$stderr.puts msg if LOG_LEVELS[level] >= @log_level
|
25
43
|
end
|
26
44
|
|
45
|
+
##
|
46
|
+
## Write a message at debug level
|
47
|
+
##
|
48
|
+
## @param msg The message
|
49
|
+
##
|
27
50
|
def debug(msg)
|
28
51
|
write msg, :debug
|
29
52
|
end
|
30
53
|
|
54
|
+
##
|
55
|
+
## Write a message at info level
|
56
|
+
##
|
57
|
+
## @param msg The message
|
58
|
+
##
|
31
59
|
def info(msg)
|
32
60
|
write msg, :info
|
33
61
|
end
|
34
62
|
|
63
|
+
##
|
64
|
+
## Write a message at warn level
|
65
|
+
##
|
66
|
+
## @param msg The message
|
67
|
+
##
|
35
68
|
def warn(msg)
|
36
69
|
write msg, :warn
|
37
70
|
end
|
38
71
|
|
72
|
+
##
|
73
|
+
## Write a message at error level
|
74
|
+
##
|
75
|
+
## @param msg The message
|
76
|
+
##
|
39
77
|
def error(msg)
|
40
78
|
write msg, :error
|
41
79
|
end
|
data/lib/howzit/hash.rb
CHANGED
@@ -5,7 +5,7 @@ class ::Hash
|
|
5
5
|
##
|
6
6
|
## Freeze all values in a hash
|
7
7
|
##
|
8
|
-
## @return Hash with all values frozen
|
8
|
+
## @return [Hash] Hash with all values frozen
|
9
9
|
##
|
10
10
|
def deep_freeze
|
11
11
|
chilled = {}
|
@@ -16,10 +16,18 @@ class ::Hash
|
|
16
16
|
chilled.freeze
|
17
17
|
end
|
18
18
|
|
19
|
+
##
|
20
|
+
## Deep freeze a hash in place (destructive)
|
21
|
+
##
|
19
22
|
def deep_freeze!
|
20
23
|
replace deep_thaw.deep_freeze
|
21
24
|
end
|
22
25
|
|
26
|
+
##
|
27
|
+
## Unfreeze nested hash values
|
28
|
+
##
|
29
|
+
## @return [Hash] Hash with all values unfrozen
|
30
|
+
##
|
23
31
|
def deep_thaw
|
24
32
|
chilled = {}
|
25
33
|
each do |k, v|
|
@@ -29,27 +37,40 @@ class ::Hash
|
|
29
37
|
chilled.dup
|
30
38
|
end
|
31
39
|
|
40
|
+
##
|
41
|
+
## Unfreeze nested hash values in place (destructive)
|
42
|
+
##
|
32
43
|
def deep_thaw!
|
33
44
|
replace deep_thaw
|
34
45
|
end
|
35
46
|
|
36
47
|
# Turn all keys into string
|
37
48
|
#
|
38
|
-
#
|
49
|
+
# @return [Hash] hash with all keys as strings
|
50
|
+
#
|
39
51
|
def stringify_keys
|
40
52
|
each_with_object({}) { |(k, v), hsh| hsh[k.to_s] = v.is_a?(Hash) ? v.stringify_keys : v }
|
41
53
|
end
|
42
54
|
|
55
|
+
##
|
56
|
+
## Turn all keys into strings in place (destructive)
|
57
|
+
##
|
43
58
|
def stringify_keys!
|
44
|
-
|
59
|
+
replace stringify_keys
|
45
60
|
end
|
46
61
|
|
47
62
|
# Turn all keys into symbols
|
63
|
+
#
|
64
|
+
# @return [Hash] hash with all keys as symbols
|
65
|
+
#
|
48
66
|
def symbolize_keys
|
49
67
|
each_with_object({}) { |(k, v), hsh| hsh[k.to_sym] = v.is_a?(Hash) ? v.symbolize_keys : v }
|
50
68
|
end
|
51
69
|
|
70
|
+
##
|
71
|
+
## Turn all keys into symbols in place (destructive)
|
72
|
+
##
|
52
73
|
def symbolize_keys!
|
53
|
-
|
74
|
+
replace symbolize_keys
|
54
75
|
end
|
55
76
|
end
|