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.
@@ -1,3 +1,35 @@
1
1
  complete -xc howzit -a "(howzit -L)"
2
- complete -xc howzit -s r -a "(howzit -T)"
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
@@ -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
- create_note if note_file.nil?
13
- @metadata = IO.read(note_file).split(/^#/)[0].strip.get_metadata
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 "\nCanceled"
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
- def is_build_notes(filename)
214
- return false if filename.downcase !~ /(^howzit[^.]*|build[^.]+)/
215
- return false if Howzit.config.should_ignore(filename)
216
- true
217
- end
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
- filename = nil
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 = IO.read(template).split(/^#/)[0].strip
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 %({xr}ERROR: Missing required metadata key from template '{bw}#{File.basename(template, '.md')}{xr}'{x}).c
273
- Howzit.console.error %({xr}Please define {by}#{req.downcase}{xr} in build notes{x}).c
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
- def include_file(m)
329
- file = File.expand_path(m[1])
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 m[0] unless File.exist?(file)
417
+ return mtch[0] unless File.exist?(file)
332
418
 
333
- content = IO.read(file)
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 = IO.read(note_file).strip
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 "Title" => contents
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 = IO.read(filename)
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
- def process_topic(topic, run, single = false)
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(IO.read(ignore_file))
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
- def create_config(d)
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(d)
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(IO.read(file))
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
- def edit_config(d)
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
- # Return a copy of the hash where all its keys are strings
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
- replace stringify_keys
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
- replace symbolize_keys
74
+ replace symbolize_keys
54
75
  end
55
76
  end