honeybadger 5.0.2 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +713 -701
  3. data/LICENSE +19 -19
  4. data/README.md +57 -57
  5. data/TROUBLESHOOTING.md +3 -3
  6. data/bin/honeybadger +5 -5
  7. data/lib/honeybadger/agent.rb +488 -488
  8. data/lib/honeybadger/backend/base.rb +116 -116
  9. data/lib/honeybadger/backend/debug.rb +22 -22
  10. data/lib/honeybadger/backend/null.rb +29 -29
  11. data/lib/honeybadger/backend/server.rb +62 -62
  12. data/lib/honeybadger/backend/test.rb +46 -46
  13. data/lib/honeybadger/backend.rb +27 -27
  14. data/lib/honeybadger/backtrace.rb +181 -181
  15. data/lib/honeybadger/breadcrumbs/active_support.rb +119 -119
  16. data/lib/honeybadger/breadcrumbs/breadcrumb.rb +53 -53
  17. data/lib/honeybadger/breadcrumbs/collector.rb +82 -82
  18. data/lib/honeybadger/breadcrumbs/logging.rb +51 -51
  19. data/lib/honeybadger/breadcrumbs/ring_buffer.rb +44 -44
  20. data/lib/honeybadger/breadcrumbs.rb +8 -8
  21. data/lib/honeybadger/cli/deploy.rb +43 -43
  22. data/lib/honeybadger/cli/exec.rb +143 -143
  23. data/lib/honeybadger/cli/helpers.rb +28 -28
  24. data/lib/honeybadger/cli/heroku.rb +129 -129
  25. data/lib/honeybadger/cli/install.rb +101 -101
  26. data/lib/honeybadger/cli/main.rb +237 -237
  27. data/lib/honeybadger/cli/notify.rb +67 -67
  28. data/lib/honeybadger/cli/test.rb +267 -267
  29. data/lib/honeybadger/cli.rb +14 -14
  30. data/lib/honeybadger/config/defaults.rb +336 -333
  31. data/lib/honeybadger/config/env.rb +42 -42
  32. data/lib/honeybadger/config/ruby.rb +146 -146
  33. data/lib/honeybadger/config/yaml.rb +76 -76
  34. data/lib/honeybadger/config.rb +413 -413
  35. data/lib/honeybadger/const.rb +20 -20
  36. data/lib/honeybadger/context_manager.rb +55 -55
  37. data/lib/honeybadger/conversions.rb +16 -16
  38. data/lib/honeybadger/init/rails.rb +38 -38
  39. data/lib/honeybadger/init/rake.rb +66 -66
  40. data/lib/honeybadger/init/ruby.rb +11 -11
  41. data/lib/honeybadger/init/sinatra.rb +51 -51
  42. data/lib/honeybadger/logging.rb +177 -177
  43. data/lib/honeybadger/notice.rb +579 -568
  44. data/lib/honeybadger/plugin.rb +210 -210
  45. data/lib/honeybadger/plugins/breadcrumbs.rb +111 -111
  46. data/lib/honeybadger/plugins/delayed_job/plugin.rb +56 -56
  47. data/lib/honeybadger/plugins/delayed_job.rb +22 -22
  48. data/lib/honeybadger/plugins/faktory.rb +52 -52
  49. data/lib/honeybadger/plugins/lambda.rb +71 -71
  50. data/lib/honeybadger/plugins/local_variables.rb +44 -44
  51. data/lib/honeybadger/plugins/passenger.rb +23 -23
  52. data/lib/honeybadger/plugins/rails.rb +72 -63
  53. data/lib/honeybadger/plugins/resque.rb +72 -72
  54. data/lib/honeybadger/plugins/shoryuken.rb +52 -52
  55. data/lib/honeybadger/plugins/sidekiq.rb +71 -62
  56. data/lib/honeybadger/plugins/sucker_punch.rb +18 -18
  57. data/lib/honeybadger/plugins/thor.rb +32 -32
  58. data/lib/honeybadger/plugins/warden.rb +19 -19
  59. data/lib/honeybadger/rack/error_notifier.rb +92 -92
  60. data/lib/honeybadger/rack/user_feedback.rb +88 -88
  61. data/lib/honeybadger/rack/user_informer.rb +45 -45
  62. data/lib/honeybadger/ruby.rb +2 -2
  63. data/lib/honeybadger/singleton.rb +103 -103
  64. data/lib/honeybadger/tasks.rb +22 -22
  65. data/lib/honeybadger/templates/feedback_form.erb +84 -84
  66. data/lib/honeybadger/util/http.rb +92 -92
  67. data/lib/honeybadger/util/lambda.rb +32 -32
  68. data/lib/honeybadger/util/request_hash.rb +73 -73
  69. data/lib/honeybadger/util/request_payload.rb +41 -41
  70. data/lib/honeybadger/util/revision.rb +39 -39
  71. data/lib/honeybadger/util/sanitizer.rb +214 -214
  72. data/lib/honeybadger/util/sql.rb +34 -34
  73. data/lib/honeybadger/util/stats.rb +50 -50
  74. data/lib/honeybadger/version.rb +4 -4
  75. data/lib/honeybadger/worker.rb +253 -253
  76. data/lib/honeybadger.rb +11 -11
  77. data/resources/ca-bundle.crt +3376 -3376
  78. data/vendor/capistrano-honeybadger/lib/capistrano/honeybadger.rb +5 -5
  79. data/vendor/capistrano-honeybadger/lib/capistrano/tasks/deploy.cap +89 -89
  80. data/vendor/capistrano-honeybadger/lib/honeybadger/capistrano/legacy.rb +47 -47
  81. data/vendor/capistrano-honeybadger/lib/honeybadger/capistrano.rb +2 -2
  82. data/vendor/cli/inifile.rb +628 -628
  83. data/vendor/cli/thor/actions/create_file.rb +103 -103
  84. data/vendor/cli/thor/actions/create_link.rb +59 -59
  85. data/vendor/cli/thor/actions/directory.rb +118 -118
  86. data/vendor/cli/thor/actions/empty_directory.rb +135 -135
  87. data/vendor/cli/thor/actions/file_manipulation.rb +316 -316
  88. data/vendor/cli/thor/actions/inject_into_file.rb +107 -107
  89. data/vendor/cli/thor/actions.rb +319 -319
  90. data/vendor/cli/thor/base.rb +656 -656
  91. data/vendor/cli/thor/command.rb +133 -133
  92. data/vendor/cli/thor/core_ext/hash_with_indifferent_access.rb +77 -77
  93. data/vendor/cli/thor/core_ext/io_binary_read.rb +10 -10
  94. data/vendor/cli/thor/core_ext/ordered_hash.rb +98 -98
  95. data/vendor/cli/thor/error.rb +32 -32
  96. data/vendor/cli/thor/group.rb +281 -281
  97. data/vendor/cli/thor/invocation.rb +178 -178
  98. data/vendor/cli/thor/line_editor/basic.rb +35 -35
  99. data/vendor/cli/thor/line_editor/readline.rb +88 -88
  100. data/vendor/cli/thor/line_editor.rb +17 -17
  101. data/vendor/cli/thor/parser/argument.rb +73 -73
  102. data/vendor/cli/thor/parser/arguments.rb +175 -175
  103. data/vendor/cli/thor/parser/option.rb +125 -125
  104. data/vendor/cli/thor/parser/options.rb +218 -218
  105. data/vendor/cli/thor/parser.rb +4 -4
  106. data/vendor/cli/thor/rake_compat.rb +71 -71
  107. data/vendor/cli/thor/runner.rb +322 -322
  108. data/vendor/cli/thor/shell/basic.rb +421 -421
  109. data/vendor/cli/thor/shell/color.rb +149 -149
  110. data/vendor/cli/thor/shell/html.rb +126 -126
  111. data/vendor/cli/thor/shell.rb +81 -81
  112. data/vendor/cli/thor/util.rb +267 -267
  113. data/vendor/cli/thor/version.rb +3 -3
  114. data/vendor/cli/thor.rb +484 -484
  115. metadata +10 -5
@@ -1,628 +1,628 @@
1
- #encoding: UTF-8
2
-
3
- # This class represents the INI file and can be used to parse, modify,
4
- # and write INI files.
5
- class IniFile
6
- include Enumerable
7
-
8
- class Error < StandardError; end
9
- VERSION = '3.0.0'
10
-
11
- # Public: Open an INI file and load the contents.
12
- #
13
- # filename - The name of the file as a String
14
- # opts - The Hash of options (default: {})
15
- # :comment - String containing the comment character(s)
16
- # :parameter - String used to separate parameter and value
17
- # :encoding - Encoding String for reading / writing
18
- # :default - The String name of the default global section
19
- #
20
- # Examples
21
- #
22
- # IniFile.load('file.ini')
23
- # #=> IniFile instance
24
- #
25
- # IniFile.load('does/not/exist.ini')
26
- # #=> nil
27
- #
28
- # Returns an IniFile instance or nil if the file could not be opened.
29
- def self.load( filename, opts = {} )
30
- return unless File.file? filename
31
- new(opts.merge(:filename => filename))
32
- end
33
-
34
- # Get and set the filename
35
- attr_accessor :filename
36
-
37
- # Get and set the encoding
38
- attr_accessor :encoding
39
-
40
- # Public: Create a new INI file from the given set of options. If :content
41
- # is provided then it will be used to populate the INI file. If a :filename
42
- # is provided then the contents of the file will be parsed and stored in the
43
- # INI file. If neither the :content or :filename is provided then an empty
44
- # INI file is created.
45
- #
46
- # opts - The Hash of options (default: {})
47
- # :content - The String/Hash containing the INI contents
48
- # :comment - String containing the comment character(s)
49
- # :parameter - String used to separate parameter and value
50
- # :encoding - Encoding String for reading / writing
51
- # :default - The String name of the default global section
52
- # :filename - The filename as a String
53
- #
54
- # Examples
55
- #
56
- # IniFile.new
57
- # #=> an empty IniFile instance
58
- #
59
- # IniFile.new( :content => "[global]\nfoo=bar" )
60
- # #=> an IniFile instance
61
- #
62
- # IniFile.new( :filename => 'file.ini', :encoding => 'UTF-8' )
63
- # #=> an IniFile instance
64
- #
65
- # IniFile.new( :content => "[global]\nfoo=bar", :comment => '#' )
66
- # #=> an IniFile instance
67
- #
68
- def initialize( opts = {} )
69
- @comment = opts.fetch(:comment, ';#')
70
- @param = opts.fetch(:parameter, '=')
71
- @encoding = opts.fetch(:encoding, nil)
72
- @default = opts.fetch(:default, 'global')
73
- @filename = opts.fetch(:filename, nil)
74
- content = opts.fetch(:content, nil)
75
-
76
- @ini = Hash.new {|h,k| h[k] = Hash.new}
77
-
78
- if content.is_a?(Hash) then merge!(content)
79
- elsif content then parse(content)
80
- elsif @filename then read
81
- end
82
- end
83
-
84
- # Public: Write the contents of this IniFile to the file system. If left
85
- # unspecified, the currently configured filename and encoding will be used.
86
- # Otherwise the filename and encoding can be specified in the options hash.
87
- #
88
- # opts - The default options Hash
89
- # :filename - The filename as a String
90
- # :encoding - The encoding as a String
91
- #
92
- # Returns this IniFile instance.
93
- def write( opts = {} )
94
- filename = opts.fetch(:filename, @filename)
95
- encoding = opts.fetch(:encoding, @encoding)
96
- mode = encoding ? "w:#{encoding}" : "w"
97
-
98
- File.open(filename, mode) do |f|
99
- @ini.each do |section,hash|
100
- f.puts "[#{section}]"
101
- hash.each {|param,val| f.puts "#{param} #{@param} #{escape_value val}"}
102
- f.puts
103
- end
104
- end
105
-
106
- self
107
- end
108
- alias :save :write
109
-
110
- # Public: Read the contents of the INI file from the file system and replace
111
- # and set the state of this IniFile instance. If left unspecified the
112
- # currently configured filename and encoding will be used when reading from
113
- # the file system. Otherwise the filename and encoding can be specified in
114
- # the options hash.
115
- #
116
- # opts - The default options Hash
117
- # :filename - The filename as a String
118
- # :encoding - The encoding as a String
119
- #
120
- # Returns this IniFile instance if the read was successful; nil is returned
121
- # if the file could not be read.
122
- def read( opts = {} )
123
- filename = opts.fetch(:filename, @filename)
124
- encoding = opts.fetch(:encoding, @encoding)
125
- return unless File.file? filename
126
-
127
- mode = encoding ? "r:#{encoding}" : "r"
128
- File.open(filename, mode) { |fd| parse fd }
129
- self
130
- end
131
- alias :restore :read
132
-
133
- # Returns this IniFile converted to a String.
134
- def to_s
135
- s = []
136
- @ini.each do |section,hash|
137
- s << "[#{section}]"
138
- hash.each {|param,val| s << "#{param} #{@param} #{escape_value val}"}
139
- s << ""
140
- end
141
- s.join("\n")
142
- end
143
-
144
- # Returns this IniFile converted to a Hash.
145
- def to_h
146
- @ini.dup
147
- end
148
-
149
- # Public: Creates a copy of this inifile with the entries from the
150
- # other_inifile merged into the copy.
151
- #
152
- # other - The other IniFile.
153
- #
154
- # Returns a new IniFile.
155
- def merge( other )
156
- self.dup.merge!(other)
157
- end
158
-
159
- # Public: Merges other_inifile into this inifile, overwriting existing
160
- # entries. Useful for having a system inifile with user overridable settings
161
- # elsewhere.
162
- #
163
- # other - The other IniFile.
164
- #
165
- # Returns this IniFile.
166
- def merge!( other )
167
- return self if other.nil?
168
-
169
- my_keys = @ini.keys
170
- other_keys = case other
171
- when IniFile
172
- other.instance_variable_get(:@ini).keys
173
- when Hash
174
- other.keys
175
- else
176
- raise Error, "cannot merge contents from '#{other.class.name}'"
177
- end
178
-
179
- (my_keys & other_keys).each do |key|
180
- case other[key]
181
- when Hash
182
- @ini[key].merge!(other[key])
183
- when nil
184
- nil
185
- else
186
- raise Error, "cannot merge section #{key.inspect} - unsupported type: #{other[key].class.name}"
187
- end
188
- end
189
-
190
- (other_keys - my_keys).each do |key|
191
- @ini[key] = case other[key]
192
- when Hash
193
- other[key].dup
194
- when nil
195
- {}
196
- else
197
- raise Error, "cannot merge section #{key.inspect} - unsupported type: #{other[key].class.name}"
198
- end
199
- end
200
-
201
- self
202
- end
203
-
204
- # Public: Yield each INI file section, parameter, and value in turn to the
205
- # given block.
206
- #
207
- # block - The block that will be iterated by the each method. The block will
208
- # be passed the current section and the parameter/value pair.
209
- #
210
- # Examples
211
- #
212
- # inifile.each do |section, parameter, value|
213
- # puts "#{parameter} = #{value} [in section - #{section}]"
214
- # end
215
- #
216
- # Returns this IniFile.
217
- def each
218
- return unless block_given?
219
- @ini.each do |section,hash|
220
- hash.each do |param,val|
221
- yield section, param, val
222
- end
223
- end
224
- self
225
- end
226
-
227
- # Public: Yield each section in turn to the given block.
228
- #
229
- # block - The block that will be iterated by the each method. The block will
230
- # be passed the current section as a Hash.
231
- #
232
- # Examples
233
- #
234
- # inifile.each_section do |section|
235
- # puts section.inspect
236
- # end
237
- #
238
- # Returns this IniFile.
239
- def each_section
240
- return unless block_given?
241
- @ini.each_key {|section| yield section}
242
- self
243
- end
244
-
245
- # Public: Remove a section identified by name from the IniFile.
246
- #
247
- # section - The section name as a String.
248
- #
249
- # Returns the deleted section Hash.
250
- def delete_section( section )
251
- @ini.delete section.to_s
252
- end
253
-
254
- # Public: Get the section Hash by name. If the section does not exist, then
255
- # it will be created.
256
- #
257
- # section - The section name as a String.
258
- #
259
- # Examples
260
- #
261
- # inifile['global']
262
- # #=> global section Hash
263
- #
264
- # Returns the Hash of parameter/value pairs for this section.
265
- def []( section )
266
- return nil if section.nil?
267
- @ini[section.to_s]
268
- end
269
-
270
- # Public: Set the section to a hash of parameter/value pairs.
271
- #
272
- # section - The section name as a String.
273
- # value - The Hash of parameter/value pairs.
274
- #
275
- # Examples
276
- #
277
- # inifile['tenderloin'] = { 'gritty' => 'yes' }
278
- # #=> { 'gritty' => 'yes' }
279
- #
280
- # Returns the value Hash.
281
- def []=( section, value )
282
- @ini[section.to_s] = value
283
- end
284
-
285
- # Public: Create a Hash containing only those INI file sections whose names
286
- # match the given regular expression.
287
- #
288
- # regex - The Regexp used to match section names.
289
- #
290
- # Examples
291
- #
292
- # inifile.match(/^tree_/)
293
- # #=> Hash of matching sections
294
- #
295
- # Return a Hash containing only those sections that match the given regular
296
- # expression.
297
- def match( regex )
298
- @ini.dup.delete_if { |section, _| section !~ regex }
299
- end
300
-
301
- # Public: Check to see if the IniFile contains the section.
302
- #
303
- # section - The section name as a String.
304
- #
305
- # Returns true if the section exists in the IniFile.
306
- def has_section?( section )
307
- @ini.has_key? section.to_s
308
- end
309
-
310
- # Returns an Array of section names contained in this IniFile.
311
- def sections
312
- @ini.keys
313
- end
314
-
315
- # Public: Freeze the state of this IniFile object. Any attempts to change
316
- # the object will raise an error.
317
- #
318
- # Returns this IniFile.
319
- def freeze
320
- super
321
- @ini.each_value {|h| h.freeze}
322
- @ini.freeze
323
- self
324
- end
325
-
326
- # Public: Mark this IniFile as tainted -- this will traverse each section
327
- # marking each as tainted.
328
- #
329
- # Returns this IniFile.
330
- def taint
331
- super
332
- @ini.each_value {|h| h.taint}
333
- @ini.taint
334
- self
335
- end
336
-
337
- # Public: Produces a duplicate of this IniFile. The duplicate is independent
338
- # of the original -- i.e. the duplicate can be modified without changing the
339
- # original. The tainted state of the original is copied to the duplicate.
340
- #
341
- # Returns a new IniFile.
342
- def dup
343
- other = super
344
- other.instance_variable_set(:@ini, Hash.new {|h,k| h[k] = Hash.new})
345
- @ini.each_pair {|s,h| other[s].merge! h}
346
- other.taint if self.tainted?
347
- other
348
- end
349
-
350
- # Public: Produces a duplicate of this IniFile. The duplicate is independent
351
- # of the original -- i.e. the duplicate can be modified without changing the
352
- # original. The tainted state and the frozen state of the original is copied
353
- # to the duplicate.
354
- #
355
- # Returns a new IniFile.
356
- def clone
357
- other = dup
358
- other.freeze if self.frozen?
359
- other
360
- end
361
-
362
- # Public: Compare this IniFile to some other IniFile. For two INI files to
363
- # be equivalent, they must have the same sections with the same parameter /
364
- # value pairs in each section.
365
- #
366
- # other - The other IniFile.
367
- #
368
- # Returns true if the INI files are equivalent and false if they differ.
369
- def eql?( other )
370
- return true if equal? other
371
- return false unless other.instance_of? self.class
372
- @ini == other.instance_variable_get(:@ini)
373
- end
374
- alias :== :eql?
375
-
376
- # Escape special characters.
377
- #
378
- # value - The String value to escape.
379
- #
380
- # Returns the escaped value.
381
- def escape_value( value )
382
- value = value.to_s.dup
383
- value.gsub!(%r/\\([0nrt])/, '\\\\\1')
384
- value.gsub!(%r/\n/, '\n')
385
- value.gsub!(%r/\r/, '\r')
386
- value.gsub!(%r/\t/, '\t')
387
- value.gsub!(%r/\0/, '\0')
388
- value
389
- end
390
-
391
- # Parse the given content and store the information in this IniFile
392
- # instance. All data will be cleared out and replaced with the information
393
- # read from the content.
394
- #
395
- # content - A String or a file descriptor (must respond to `each_line`)
396
- #
397
- # Returns this IniFile.
398
- def parse( content )
399
- parser = Parser.new(@ini, @param, @comment, @default)
400
- parser.parse(content)
401
- self
402
- end
403
-
404
- # The IniFile::Parser has the responsibility of reading the contents of an
405
- # .ini file and storing that information into a ruby Hash. The object being
406
- # parsed must respond to `each_line` - this includes Strings and any IO
407
- # object.
408
- class Parser
409
-
410
- attr_writer :section
411
- attr_accessor :property
412
- attr_accessor :value
413
-
414
- # Create a new IniFile::Parser that can be used to parse the contents of
415
- # an .ini file.
416
- #
417
- # hash - The Hash where parsed information will be stored
418
- # param - String used to separate parameter and value
419
- # comment - String containing the comment character(s)
420
- # default - The String name of the default global section
421
- #
422
- def initialize( hash, param, comment, default )
423
- @hash = hash
424
- @default = default
425
-
426
- comment = comment.to_s.empty? ? "\\z" : "\\s*(?:[#{comment}].*)?\\z"
427
-
428
- @section_regexp = %r/\A\s*\[([^\]]+)\]#{comment}/
429
- @ignore_regexp = %r/\A#{comment}/
430
- @property_regexp = %r/\A(.*?)(?<!\\)#{param}(.*)\z/
431
-
432
- @open_quote = %r/\A\s*(".*)\z/
433
- @close_quote = %r/\A(.*(?<!\\)")#{comment}/
434
- @full_quote = %r/\A\s*(".*(?<!\\)")#{comment}/
435
- @trailing_slash = %r/\A(.*)(?<!\\)\\#{comment}/
436
- @normal_value = %r/\A(.*?)#{comment}/
437
- end
438
-
439
- # Returns `true` if the current value starts with a leading double quote.
440
- # Otherwise returns false.
441
- def leading_quote?
442
- value && value =~ %r/\A"/
443
- end
444
-
445
- # Given a string, attempt to parse out a value from that string. This
446
- # value might be continued on the following line. So this method returns
447
- # `true` if it is expecting more data.
448
- #
449
- # string - String to parse
450
- #
451
- # Returns `true` if the next line is also part of the current value.
452
- # Returns `fase` if the string contained a complete value.
453
- def parse_value( string )
454
- continuation = false
455
-
456
- # if our value starts with a double quote, then we are in a
457
- # line continuation situation
458
- if leading_quote?
459
- # check for a closing quote at the end of the string
460
- if string =~ @close_quote
461
- value << $1
462
-
463
- # otherwise just append the string to the value
464
- else
465
- value << string
466
- continuation = true
467
- end
468
-
469
- # not currently processing a continuation line
470
- else
471
- case string
472
- when @full_quote
473
- self.value = $1
474
-
475
- when @open_quote
476
- self.value = $1
477
- continuation = true
478
-
479
- when @trailing_slash
480
- self.value ? self.value << $1 : self.value = $1
481
- continuation = true
482
-
483
- when @normal_value
484
- self.value ? self.value << $1 : self.value = $1
485
-
486
- else
487
- error
488
- end
489
- end
490
-
491
- if continuation
492
- self.value << $/ if leading_quote?
493
- else
494
- process_property
495
- end
496
-
497
- continuation
498
- end
499
-
500
- # Parse the ini file contents. This will clear any values currently stored
501
- # in the ini hash.
502
- #
503
- # content - Any object that responds to `each_line`
504
- #
505
- # Returns nil.
506
- def parse( content )
507
- return unless content
508
-
509
- continuation = false
510
-
511
- @hash.clear
512
- @line = nil
513
- self.section = nil
514
-
515
- content.each_line do |line|
516
- @line = line.chomp
517
-
518
- if continuation
519
- continuation = parse_value @line
520
- else
521
- case @line
522
- when @ignore_regexp
523
- nil
524
- when @section_regexp
525
- self.section = @hash[$1]
526
- when @property_regexp
527
- self.property = $1.strip
528
- error if property.empty?
529
-
530
- continuation = parse_value $2
531
- else
532
- error
533
- end
534
- end
535
- end
536
-
537
- # check here if we have a dangling value ... usually means we have an
538
- # unmatched open quote
539
- if leading_quote?
540
- error "Unmatched open quote"
541
- elsif property && value
542
- process_property
543
- elsif value
544
- error
545
- end
546
-
547
- nil
548
- end
549
-
550
- # Store the property/value pair in the currently active section. This
551
- # method checks for continuation of the value to the next line.
552
- #
553
- # Returns nil.
554
- def process_property
555
- property.strip!
556
- value.strip!
557
-
558
- self.value = $1 if value =~ %r/\A"(.*)(?<!\\)"\z/m
559
-
560
- section[property] = typecast(value)
561
-
562
- self.property = nil
563
- self.value = nil
564
- end
565
-
566
- # Returns the current section Hash.
567
- def section
568
- @section ||= @hash[@default]
569
- end
570
-
571
- # Raise a parse error using the given message and appending the current line
572
- # being parsed.
573
- #
574
- # msg - The message String to use.
575
- #
576
- # Raises IniFile::Error
577
- def error( msg = 'Could not parse line' )
578
- raise Error, "#{msg}: #{@line.inspect}"
579
- end
580
-
581
- # Attempt to typecast the value string. We are looking for boolean values,
582
- # integers, floats, and empty strings. Below is how each gets cast, but it
583
- # is pretty logical and straightforward.
584
- #
585
- # "true" --> true
586
- # "false" --> false
587
- # "" --> nil
588
- # "42" --> 42
589
- # "3.14" --> 3.14
590
- # "foo" --> "foo"
591
- #
592
- # Returns the typecast value.
593
- def typecast( value )
594
- case value
595
- when %r/\Atrue\z/i; true
596
- when %r/\Afalse\z/i; false
597
- when %r/\A\s*\z/i; nil
598
- else
599
- Integer(value) rescue \
600
- Float(value) rescue \
601
- unescape_value(value)
602
- end
603
- end
604
-
605
- # Unescape special characters found in the value string. This will convert
606
- # escaped null, tab, carriage return, newline, and backslash into their
607
- # literal equivalents.
608
- #
609
- # value - The String value to unescape.
610
- #
611
- # Returns the unescaped value.
612
- def unescape_value( value )
613
- value = value.to_s
614
- value.gsub!(%r/\\[0nrt\\]/) { |char|
615
- case char
616
- when '\0'; "\0"
617
- when '\n'; "\n"
618
- when '\r'; "\r"
619
- when '\t'; "\t"
620
- when '\\\\'; "\\"
621
- end
622
- }
623
- value
624
- end
625
- end
626
-
627
- end # IniFile
628
-
1
+ #encoding: UTF-8
2
+
3
+ # This class represents the INI file and can be used to parse, modify,
4
+ # and write INI files.
5
+ class IniFile
6
+ include Enumerable
7
+
8
+ class Error < StandardError; end
9
+ VERSION = '3.0.0'
10
+
11
+ # Public: Open an INI file and load the contents.
12
+ #
13
+ # filename - The name of the file as a String
14
+ # opts - The Hash of options (default: {})
15
+ # :comment - String containing the comment character(s)
16
+ # :parameter - String used to separate parameter and value
17
+ # :encoding - Encoding String for reading / writing
18
+ # :default - The String name of the default global section
19
+ #
20
+ # Examples
21
+ #
22
+ # IniFile.load('file.ini')
23
+ # #=> IniFile instance
24
+ #
25
+ # IniFile.load('does/not/exist.ini')
26
+ # #=> nil
27
+ #
28
+ # Returns an IniFile instance or nil if the file could not be opened.
29
+ def self.load( filename, opts = {} )
30
+ return unless File.file? filename
31
+ new(opts.merge(:filename => filename))
32
+ end
33
+
34
+ # Get and set the filename
35
+ attr_accessor :filename
36
+
37
+ # Get and set the encoding
38
+ attr_accessor :encoding
39
+
40
+ # Public: Create a new INI file from the given set of options. If :content
41
+ # is provided then it will be used to populate the INI file. If a :filename
42
+ # is provided then the contents of the file will be parsed and stored in the
43
+ # INI file. If neither the :content or :filename is provided then an empty
44
+ # INI file is created.
45
+ #
46
+ # opts - The Hash of options (default: {})
47
+ # :content - The String/Hash containing the INI contents
48
+ # :comment - String containing the comment character(s)
49
+ # :parameter - String used to separate parameter and value
50
+ # :encoding - Encoding String for reading / writing
51
+ # :default - The String name of the default global section
52
+ # :filename - The filename as a String
53
+ #
54
+ # Examples
55
+ #
56
+ # IniFile.new
57
+ # #=> an empty IniFile instance
58
+ #
59
+ # IniFile.new( :content => "[global]\nfoo=bar" )
60
+ # #=> an IniFile instance
61
+ #
62
+ # IniFile.new( :filename => 'file.ini', :encoding => 'UTF-8' )
63
+ # #=> an IniFile instance
64
+ #
65
+ # IniFile.new( :content => "[global]\nfoo=bar", :comment => '#' )
66
+ # #=> an IniFile instance
67
+ #
68
+ def initialize( opts = {} )
69
+ @comment = opts.fetch(:comment, ';#')
70
+ @param = opts.fetch(:parameter, '=')
71
+ @encoding = opts.fetch(:encoding, nil)
72
+ @default = opts.fetch(:default, 'global')
73
+ @filename = opts.fetch(:filename, nil)
74
+ content = opts.fetch(:content, nil)
75
+
76
+ @ini = Hash.new {|h,k| h[k] = Hash.new}
77
+
78
+ if content.is_a?(Hash) then merge!(content)
79
+ elsif content then parse(content)
80
+ elsif @filename then read
81
+ end
82
+ end
83
+
84
+ # Public: Write the contents of this IniFile to the file system. If left
85
+ # unspecified, the currently configured filename and encoding will be used.
86
+ # Otherwise the filename and encoding can be specified in the options hash.
87
+ #
88
+ # opts - The default options Hash
89
+ # :filename - The filename as a String
90
+ # :encoding - The encoding as a String
91
+ #
92
+ # Returns this IniFile instance.
93
+ def write( opts = {} )
94
+ filename = opts.fetch(:filename, @filename)
95
+ encoding = opts.fetch(:encoding, @encoding)
96
+ mode = encoding ? "w:#{encoding}" : "w"
97
+
98
+ File.open(filename, mode) do |f|
99
+ @ini.each do |section,hash|
100
+ f.puts "[#{section}]"
101
+ hash.each {|param,val| f.puts "#{param} #{@param} #{escape_value val}"}
102
+ f.puts
103
+ end
104
+ end
105
+
106
+ self
107
+ end
108
+ alias :save :write
109
+
110
+ # Public: Read the contents of the INI file from the file system and replace
111
+ # and set the state of this IniFile instance. If left unspecified the
112
+ # currently configured filename and encoding will be used when reading from
113
+ # the file system. Otherwise the filename and encoding can be specified in
114
+ # the options hash.
115
+ #
116
+ # opts - The default options Hash
117
+ # :filename - The filename as a String
118
+ # :encoding - The encoding as a String
119
+ #
120
+ # Returns this IniFile instance if the read was successful; nil is returned
121
+ # if the file could not be read.
122
+ def read( opts = {} )
123
+ filename = opts.fetch(:filename, @filename)
124
+ encoding = opts.fetch(:encoding, @encoding)
125
+ return unless File.file? filename
126
+
127
+ mode = encoding ? "r:#{encoding}" : "r"
128
+ File.open(filename, mode) { |fd| parse fd }
129
+ self
130
+ end
131
+ alias :restore :read
132
+
133
+ # Returns this IniFile converted to a String.
134
+ def to_s
135
+ s = []
136
+ @ini.each do |section,hash|
137
+ s << "[#{section}]"
138
+ hash.each {|param,val| s << "#{param} #{@param} #{escape_value val}"}
139
+ s << ""
140
+ end
141
+ s.join("\n")
142
+ end
143
+
144
+ # Returns this IniFile converted to a Hash.
145
+ def to_h
146
+ @ini.dup
147
+ end
148
+
149
+ # Public: Creates a copy of this inifile with the entries from the
150
+ # other_inifile merged into the copy.
151
+ #
152
+ # other - The other IniFile.
153
+ #
154
+ # Returns a new IniFile.
155
+ def merge( other )
156
+ self.dup.merge!(other)
157
+ end
158
+
159
+ # Public: Merges other_inifile into this inifile, overwriting existing
160
+ # entries. Useful for having a system inifile with user overridable settings
161
+ # elsewhere.
162
+ #
163
+ # other - The other IniFile.
164
+ #
165
+ # Returns this IniFile.
166
+ def merge!( other )
167
+ return self if other.nil?
168
+
169
+ my_keys = @ini.keys
170
+ other_keys = case other
171
+ when IniFile
172
+ other.instance_variable_get(:@ini).keys
173
+ when Hash
174
+ other.keys
175
+ else
176
+ raise Error, "cannot merge contents from '#{other.class.name}'"
177
+ end
178
+
179
+ (my_keys & other_keys).each do |key|
180
+ case other[key]
181
+ when Hash
182
+ @ini[key].merge!(other[key])
183
+ when nil
184
+ nil
185
+ else
186
+ raise Error, "cannot merge section #{key.inspect} - unsupported type: #{other[key].class.name}"
187
+ end
188
+ end
189
+
190
+ (other_keys - my_keys).each do |key|
191
+ @ini[key] = case other[key]
192
+ when Hash
193
+ other[key].dup
194
+ when nil
195
+ {}
196
+ else
197
+ raise Error, "cannot merge section #{key.inspect} - unsupported type: #{other[key].class.name}"
198
+ end
199
+ end
200
+
201
+ self
202
+ end
203
+
204
+ # Public: Yield each INI file section, parameter, and value in turn to the
205
+ # given block.
206
+ #
207
+ # block - The block that will be iterated by the each method. The block will
208
+ # be passed the current section and the parameter/value pair.
209
+ #
210
+ # Examples
211
+ #
212
+ # inifile.each do |section, parameter, value|
213
+ # puts "#{parameter} = #{value} [in section - #{section}]"
214
+ # end
215
+ #
216
+ # Returns this IniFile.
217
+ def each
218
+ return unless block_given?
219
+ @ini.each do |section,hash|
220
+ hash.each do |param,val|
221
+ yield section, param, val
222
+ end
223
+ end
224
+ self
225
+ end
226
+
227
+ # Public: Yield each section in turn to the given block.
228
+ #
229
+ # block - The block that will be iterated by the each method. The block will
230
+ # be passed the current section as a Hash.
231
+ #
232
+ # Examples
233
+ #
234
+ # inifile.each_section do |section|
235
+ # puts section.inspect
236
+ # end
237
+ #
238
+ # Returns this IniFile.
239
+ def each_section
240
+ return unless block_given?
241
+ @ini.each_key {|section| yield section}
242
+ self
243
+ end
244
+
245
+ # Public: Remove a section identified by name from the IniFile.
246
+ #
247
+ # section - The section name as a String.
248
+ #
249
+ # Returns the deleted section Hash.
250
+ def delete_section( section )
251
+ @ini.delete section.to_s
252
+ end
253
+
254
+ # Public: Get the section Hash by name. If the section does not exist, then
255
+ # it will be created.
256
+ #
257
+ # section - The section name as a String.
258
+ #
259
+ # Examples
260
+ #
261
+ # inifile['global']
262
+ # #=> global section Hash
263
+ #
264
+ # Returns the Hash of parameter/value pairs for this section.
265
+ def []( section )
266
+ return nil if section.nil?
267
+ @ini[section.to_s]
268
+ end
269
+
270
+ # Public: Set the section to a hash of parameter/value pairs.
271
+ #
272
+ # section - The section name as a String.
273
+ # value - The Hash of parameter/value pairs.
274
+ #
275
+ # Examples
276
+ #
277
+ # inifile['tenderloin'] = { 'gritty' => 'yes' }
278
+ # #=> { 'gritty' => 'yes' }
279
+ #
280
+ # Returns the value Hash.
281
+ def []=( section, value )
282
+ @ini[section.to_s] = value
283
+ end
284
+
285
+ # Public: Create a Hash containing only those INI file sections whose names
286
+ # match the given regular expression.
287
+ #
288
+ # regex - The Regexp used to match section names.
289
+ #
290
+ # Examples
291
+ #
292
+ # inifile.match(/^tree_/)
293
+ # #=> Hash of matching sections
294
+ #
295
+ # Return a Hash containing only those sections that match the given regular
296
+ # expression.
297
+ def match( regex )
298
+ @ini.dup.delete_if { |section, _| section !~ regex }
299
+ end
300
+
301
+ # Public: Check to see if the IniFile contains the section.
302
+ #
303
+ # section - The section name as a String.
304
+ #
305
+ # Returns true if the section exists in the IniFile.
306
+ def has_section?( section )
307
+ @ini.has_key? section.to_s
308
+ end
309
+
310
+ # Returns an Array of section names contained in this IniFile.
311
+ def sections
312
+ @ini.keys
313
+ end
314
+
315
+ # Public: Freeze the state of this IniFile object. Any attempts to change
316
+ # the object will raise an error.
317
+ #
318
+ # Returns this IniFile.
319
+ def freeze
320
+ super
321
+ @ini.each_value {|h| h.freeze}
322
+ @ini.freeze
323
+ self
324
+ end
325
+
326
+ # Public: Mark this IniFile as tainted -- this will traverse each section
327
+ # marking each as tainted.
328
+ #
329
+ # Returns this IniFile.
330
+ def taint
331
+ super
332
+ @ini.each_value {|h| h.taint}
333
+ @ini.taint
334
+ self
335
+ end
336
+
337
+ # Public: Produces a duplicate of this IniFile. The duplicate is independent
338
+ # of the original -- i.e. the duplicate can be modified without changing the
339
+ # original. The tainted state of the original is copied to the duplicate.
340
+ #
341
+ # Returns a new IniFile.
342
+ def dup
343
+ other = super
344
+ other.instance_variable_set(:@ini, Hash.new {|h,k| h[k] = Hash.new})
345
+ @ini.each_pair {|s,h| other[s].merge! h}
346
+ other.taint if self.tainted?
347
+ other
348
+ end
349
+
350
+ # Public: Produces a duplicate of this IniFile. The duplicate is independent
351
+ # of the original -- i.e. the duplicate can be modified without changing the
352
+ # original. The tainted state and the frozen state of the original is copied
353
+ # to the duplicate.
354
+ #
355
+ # Returns a new IniFile.
356
+ def clone
357
+ other = dup
358
+ other.freeze if self.frozen?
359
+ other
360
+ end
361
+
362
+ # Public: Compare this IniFile to some other IniFile. For two INI files to
363
+ # be equivalent, they must have the same sections with the same parameter /
364
+ # value pairs in each section.
365
+ #
366
+ # other - The other IniFile.
367
+ #
368
+ # Returns true if the INI files are equivalent and false if they differ.
369
+ def eql?( other )
370
+ return true if equal? other
371
+ return false unless other.instance_of? self.class
372
+ @ini == other.instance_variable_get(:@ini)
373
+ end
374
+ alias :== :eql?
375
+
376
+ # Escape special characters.
377
+ #
378
+ # value - The String value to escape.
379
+ #
380
+ # Returns the escaped value.
381
+ def escape_value( value )
382
+ value = value.to_s.dup
383
+ value.gsub!(%r/\\([0nrt])/, '\\\\\1')
384
+ value.gsub!(%r/\n/, '\n')
385
+ value.gsub!(%r/\r/, '\r')
386
+ value.gsub!(%r/\t/, '\t')
387
+ value.gsub!(%r/\0/, '\0')
388
+ value
389
+ end
390
+
391
+ # Parse the given content and store the information in this IniFile
392
+ # instance. All data will be cleared out and replaced with the information
393
+ # read from the content.
394
+ #
395
+ # content - A String or a file descriptor (must respond to `each_line`)
396
+ #
397
+ # Returns this IniFile.
398
+ def parse( content )
399
+ parser = Parser.new(@ini, @param, @comment, @default)
400
+ parser.parse(content)
401
+ self
402
+ end
403
+
404
+ # The IniFile::Parser has the responsibility of reading the contents of an
405
+ # .ini file and storing that information into a ruby Hash. The object being
406
+ # parsed must respond to `each_line` - this includes Strings and any IO
407
+ # object.
408
+ class Parser
409
+
410
+ attr_writer :section
411
+ attr_accessor :property
412
+ attr_accessor :value
413
+
414
+ # Create a new IniFile::Parser that can be used to parse the contents of
415
+ # an .ini file.
416
+ #
417
+ # hash - The Hash where parsed information will be stored
418
+ # param - String used to separate parameter and value
419
+ # comment - String containing the comment character(s)
420
+ # default - The String name of the default global section
421
+ #
422
+ def initialize( hash, param, comment, default )
423
+ @hash = hash
424
+ @default = default
425
+
426
+ comment = comment.to_s.empty? ? "\\z" : "\\s*(?:[#{comment}].*)?\\z"
427
+
428
+ @section_regexp = %r/\A\s*\[([^\]]+)\]#{comment}/
429
+ @ignore_regexp = %r/\A#{comment}/
430
+ @property_regexp = %r/\A(.*?)(?<!\\)#{param}(.*)\z/
431
+
432
+ @open_quote = %r/\A\s*(".*)\z/
433
+ @close_quote = %r/\A(.*(?<!\\)")#{comment}/
434
+ @full_quote = %r/\A\s*(".*(?<!\\)")#{comment}/
435
+ @trailing_slash = %r/\A(.*)(?<!\\)\\#{comment}/
436
+ @normal_value = %r/\A(.*?)#{comment}/
437
+ end
438
+
439
+ # Returns `true` if the current value starts with a leading double quote.
440
+ # Otherwise returns false.
441
+ def leading_quote?
442
+ value && value =~ %r/\A"/
443
+ end
444
+
445
+ # Given a string, attempt to parse out a value from that string. This
446
+ # value might be continued on the following line. So this method returns
447
+ # `true` if it is expecting more data.
448
+ #
449
+ # string - String to parse
450
+ #
451
+ # Returns `true` if the next line is also part of the current value.
452
+ # Returns `fase` if the string contained a complete value.
453
+ def parse_value( string )
454
+ continuation = false
455
+
456
+ # if our value starts with a double quote, then we are in a
457
+ # line continuation situation
458
+ if leading_quote?
459
+ # check for a closing quote at the end of the string
460
+ if string =~ @close_quote
461
+ value << $1
462
+
463
+ # otherwise just append the string to the value
464
+ else
465
+ value << string
466
+ continuation = true
467
+ end
468
+
469
+ # not currently processing a continuation line
470
+ else
471
+ case string
472
+ when @full_quote
473
+ self.value = $1
474
+
475
+ when @open_quote
476
+ self.value = $1
477
+ continuation = true
478
+
479
+ when @trailing_slash
480
+ self.value ? self.value << $1 : self.value = $1
481
+ continuation = true
482
+
483
+ when @normal_value
484
+ self.value ? self.value << $1 : self.value = $1
485
+
486
+ else
487
+ error
488
+ end
489
+ end
490
+
491
+ if continuation
492
+ self.value << $/ if leading_quote?
493
+ else
494
+ process_property
495
+ end
496
+
497
+ continuation
498
+ end
499
+
500
+ # Parse the ini file contents. This will clear any values currently stored
501
+ # in the ini hash.
502
+ #
503
+ # content - Any object that responds to `each_line`
504
+ #
505
+ # Returns nil.
506
+ def parse( content )
507
+ return unless content
508
+
509
+ continuation = false
510
+
511
+ @hash.clear
512
+ @line = nil
513
+ self.section = nil
514
+
515
+ content.each_line do |line|
516
+ @line = line.chomp
517
+
518
+ if continuation
519
+ continuation = parse_value @line
520
+ else
521
+ case @line
522
+ when @ignore_regexp
523
+ nil
524
+ when @section_regexp
525
+ self.section = @hash[$1]
526
+ when @property_regexp
527
+ self.property = $1.strip
528
+ error if property.empty?
529
+
530
+ continuation = parse_value $2
531
+ else
532
+ error
533
+ end
534
+ end
535
+ end
536
+
537
+ # check here if we have a dangling value ... usually means we have an
538
+ # unmatched open quote
539
+ if leading_quote?
540
+ error "Unmatched open quote"
541
+ elsif property && value
542
+ process_property
543
+ elsif value
544
+ error
545
+ end
546
+
547
+ nil
548
+ end
549
+
550
+ # Store the property/value pair in the currently active section. This
551
+ # method checks for continuation of the value to the next line.
552
+ #
553
+ # Returns nil.
554
+ def process_property
555
+ property.strip!
556
+ value.strip!
557
+
558
+ self.value = $1 if value =~ %r/\A"(.*)(?<!\\)"\z/m
559
+
560
+ section[property] = typecast(value)
561
+
562
+ self.property = nil
563
+ self.value = nil
564
+ end
565
+
566
+ # Returns the current section Hash.
567
+ def section
568
+ @section ||= @hash[@default]
569
+ end
570
+
571
+ # Raise a parse error using the given message and appending the current line
572
+ # being parsed.
573
+ #
574
+ # msg - The message String to use.
575
+ #
576
+ # Raises IniFile::Error
577
+ def error( msg = 'Could not parse line' )
578
+ raise Error, "#{msg}: #{@line.inspect}"
579
+ end
580
+
581
+ # Attempt to typecast the value string. We are looking for boolean values,
582
+ # integers, floats, and empty strings. Below is how each gets cast, but it
583
+ # is pretty logical and straightforward.
584
+ #
585
+ # "true" --> true
586
+ # "false" --> false
587
+ # "" --> nil
588
+ # "42" --> 42
589
+ # "3.14" --> 3.14
590
+ # "foo" --> "foo"
591
+ #
592
+ # Returns the typecast value.
593
+ def typecast( value )
594
+ case value
595
+ when %r/\Atrue\z/i; true
596
+ when %r/\Afalse\z/i; false
597
+ when %r/\A\s*\z/i; nil
598
+ else
599
+ Integer(value) rescue \
600
+ Float(value) rescue \
601
+ unescape_value(value)
602
+ end
603
+ end
604
+
605
+ # Unescape special characters found in the value string. This will convert
606
+ # escaped null, tab, carriage return, newline, and backslash into their
607
+ # literal equivalents.
608
+ #
609
+ # value - The String value to unescape.
610
+ #
611
+ # Returns the unescaped value.
612
+ def unescape_value( value )
613
+ value = value.to_s
614
+ value.gsub!(%r/\\[0nrt\\]/) { |char|
615
+ case char
616
+ when '\0'; "\0"
617
+ when '\n'; "\n"
618
+ when '\r'; "\r"
619
+ when '\t'; "\t"
620
+ when '\\\\'; "\\"
621
+ end
622
+ }
623
+ value
624
+ end
625
+ end
626
+
627
+ end # IniFile
628
+