howzit 2.0.8 → 2.0.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -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