bunto 3.0.0 → 3.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +124 -76
  3. data/README.markdown +49 -12
  4. data/{bin → exe}/bunto +18 -14
  5. data/lib/bunto.rb +83 -78
  6. data/lib/bunto/cleaner.rb +10 -8
  7. data/lib/bunto/collection.rb +33 -17
  8. data/lib/bunto/command.rb +19 -13
  9. data/lib/bunto/commands/build.rb +22 -14
  10. data/lib/bunto/commands/clean.rb +9 -8
  11. data/lib/bunto/commands/doctor.rb +10 -8
  12. data/lib/bunto/commands/help.rb +4 -3
  13. data/lib/bunto/commands/new.rb +30 -21
  14. data/lib/bunto/commands/new_theme.rb +36 -0
  15. data/lib/bunto/commands/serve.rb +26 -20
  16. data/lib/bunto/commands/serve/servlet.rb +4 -5
  17. data/lib/bunto/configuration.rb +187 -125
  18. data/lib/bunto/converters/markdown.rb +19 -9
  19. data/lib/bunto/converters/markdown/kramdown_parser.rb +12 -5
  20. data/lib/bunto/converters/markdown/rdiscount_parser.rb +4 -4
  21. data/lib/bunto/converters/markdown/redcarpet_parser.rb +90 -84
  22. data/lib/bunto/convertible.rb +38 -25
  23. data/lib/bunto/deprecator.rb +11 -6
  24. data/lib/bunto/desktop.ini +2 -0
  25. data/lib/bunto/document.rb +53 -51
  26. data/lib/bunto/drops/bunto_drop.rb +12 -0
  27. data/lib/bunto/drops/document_drop.rb +40 -5
  28. data/lib/bunto/drops/drop.rb +49 -10
  29. data/lib/bunto/drops/excerpt_drop.rb +15 -0
  30. data/lib/bunto/drops/site_drop.rb +4 -2
  31. data/lib/bunto/drops/url_drop.rb +4 -4
  32. data/lib/bunto/entry_filter.rb +64 -19
  33. data/lib/bunto/errors.rb +6 -3
  34. data/lib/bunto/excerpt.rb +4 -6
  35. data/lib/bunto/external.rb +4 -4
  36. data/lib/bunto/filters.rb +72 -39
  37. data/lib/bunto/frontmatter_defaults.rb +45 -38
  38. data/lib/bunto/hooks.rb +21 -21
  39. data/lib/bunto/layout.rb +4 -8
  40. data/lib/bunto/liquid_renderer.rb +14 -3
  41. data/lib/bunto/liquid_renderer/file.rb +5 -1
  42. data/lib/bunto/liquid_renderer/table.rb +11 -11
  43. data/lib/bunto/log_adapter.rb +2 -2
  44. data/lib/bunto/page.rb +10 -10
  45. data/lib/bunto/plugin.rb +5 -5
  46. data/lib/bunto/plugin_manager.rb +12 -8
  47. data/lib/bunto/publisher.rb +1 -1
  48. data/lib/bunto/reader.rb +11 -7
  49. data/lib/bunto/readers/data_reader.rb +9 -9
  50. data/lib/bunto/readers/layout_reader.rb +7 -7
  51. data/lib/bunto/readers/page_reader.rb +3 -1
  52. data/lib/bunto/readers/post_reader.rb +9 -10
  53. data/lib/bunto/readers/static_file_reader.rb +3 -1
  54. data/lib/bunto/regenerator.rb +50 -28
  55. data/lib/bunto/related_posts.rb +1 -1
  56. data/lib/bunto/renderer.rb +33 -23
  57. data/lib/bunto/site.rb +94 -51
  58. data/lib/bunto/static_file.rb +33 -26
  59. data/lib/bunto/stevenson.rb +6 -5
  60. data/lib/bunto/tags/highlight.rb +50 -35
  61. data/lib/bunto/tags/include.rb +42 -31
  62. data/lib/bunto/tags/link.rb +11 -4
  63. data/lib/bunto/tags/post_url.rb +8 -7
  64. data/lib/bunto/theme.rb +10 -8
  65. data/lib/bunto/theme_builder.rb +117 -0
  66. data/lib/bunto/url.rb +21 -14
  67. data/lib/bunto/utils.rb +57 -28
  68. data/lib/bunto/utils/ansi.rb +9 -9
  69. data/lib/bunto/utils/platforms.rb +2 -2
  70. data/lib/bunto/version.rb +1 -1
  71. data/lib/site_template/_config.yml +3 -1
  72. data/lib/site_template/_posts/0000-00-00-welcome-to-bunto.markdown.erb +3 -3
  73. data/lib/site_template/about.md +3 -3
  74. data/lib/site_template/css/main.scss +3 -17
  75. data/lib/theme_template/CODE_OF_CONDUCT.md.erb +74 -0
  76. data/lib/theme_template/Gemfile +2 -0
  77. data/lib/theme_template/LICENSE.txt.erb +21 -0
  78. data/lib/theme_template/README.md.erb +48 -0
  79. data/lib/theme_template/_layouts/default.html +1 -0
  80. data/lib/theme_template/_layouts/page.html +5 -0
  81. data/lib/theme_template/_layouts/post.html +5 -0
  82. data/lib/theme_template/example/_config.yml.erb +1 -0
  83. data/lib/theme_template/example/_post.md +12 -0
  84. data/lib/theme_template/example/index.html +14 -0
  85. data/lib/theme_template/example/style.scss +7 -0
  86. data/lib/theme_template/gitignore.erb +4 -0
  87. data/lib/theme_template/theme.gemspec.erb +18 -0
  88. metadata +40 -19
  89. data/lib/site_template/_includes/footer.html +0 -38
  90. data/lib/site_template/_includes/head.html +0 -12
  91. data/lib/site_template/_includes/header.html +0 -27
  92. data/lib/site_template/_includes/icon-github.html +0 -1
  93. data/lib/site_template/_includes/icon-github.svg +0 -1
  94. data/lib/site_template/_includes/icon-twitter.html +0 -1
  95. data/lib/site_template/_includes/icon-twitter.svg +0 -1
  96. data/lib/site_template/_layouts/default.html +0 -20
  97. data/lib/site_template/_layouts/page.html +0 -14
  98. data/lib/site_template/_layouts/post.html +0 -15
  99. data/lib/site_template/_sass/_base.scss +0 -200
  100. data/lib/site_template/_sass/_layout.scss +0 -242
  101. data/lib/site_template/_sass/_syntax-highlighting.scss +0 -71
@@ -9,20 +9,24 @@ module Bunto
9
9
  'serve' subcommand."
10
10
  arg_is_present? args, "--no-server", "To build Bunto without launching a server, \
11
11
  use the 'build' subcommand."
12
- arg_is_present? args, "--auto", "The switch '--auto' has been replaced with '--watch'."
12
+ arg_is_present? args, "--auto", "The switch '--auto' has been replaced with \
13
+ '--watch'."
13
14
  arg_is_present? args, "--no-auto", "To disable auto-replication, simply leave off \
14
15
  the '--watch' switch."
15
16
  arg_is_present? args, "--pygments", "The 'pygments'settings has been removed in \
16
17
  favour of 'highlighter'."
17
- arg_is_present? args, "--paginate", "The 'paginate' setting can only be set in your \
18
+ arg_is_present? args, "--paginate", "The 'paginate' setting can only be set in \
19
+ your config files."
20
+ arg_is_present? args, "--url", "The 'url' setting can only be set in your \
18
21
  config files."
19
- arg_is_present? args, "--url", "The 'url' setting can only be set in your config files."
20
22
  no_subcommand(args)
21
23
  end
22
24
 
23
25
  def no_subcommand(args)
24
- if args.size > 0 && args.first =~ /^--/ && !%w(--help --version).include?(args.first)
25
- deprecation_message "Bunto now uses subcommands instead of just switches. Run `bunto help` to find out more."
26
+ unless args.empty? ||
27
+ args.first !~ %r(!/^--/!) || %w(--help --version).include?(args.first)
28
+ deprecation_message "Bunto now uses subcommands instead of just switches. \
29
+ Run `bunto help` to find out more."
26
30
  abort
27
31
  end
28
32
  end
@@ -39,7 +43,8 @@ module Bunto
39
43
 
40
44
  def defaults_deprecate_type(old, current)
41
45
  Bunto.logger.warn "Defaults:", "The '#{old}' type has become '#{current}'."
42
- Bunto.logger.warn "Defaults:", "Please update your front-matter defaults to use 'type: #{current}'."
46
+ Bunto.logger.warn "Defaults:", "Please update your front-matter defaults to use \
47
+ 'type: #{current}'."
43
48
  end
44
49
 
45
50
  end
@@ -0,0 +1,2 @@
1
+ [.ShellClassInfo]
2
+ LocalizedResourceName=bunto
@@ -7,9 +7,9 @@ module Bunto
7
7
  attr_reader :path, :site, :extname, :collection
8
8
  attr_accessor :content, :output
9
9
 
10
- YAML_FRONT_MATTER_REGEXP = /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m
11
- DATELESS_FILENAME_MATCHER = /^(.+\/)*(.*)(\.[^.]+)$/
12
- DATE_FILENAME_MATCHER = /^(.+\/)*(\d+-\d+-\d+)-(.*)(\.[^.]+)$/
10
+ YAML_FRONT_MATTER_REGEXP = %r!\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)!m
11
+ DATELESS_FILENAME_MATCHER = %r!^(?:.+/)*(.*)(\.[^.]+)$!
12
+ DATE_FILENAME_MATCHER = %r!^(?:.+/)*(\d+-\d+-\d+)-(.*)(\.[^.]+)$!
13
13
 
14
14
  # Create a new Document.
15
15
  #
@@ -51,16 +51,16 @@ module Bunto
51
51
  #
52
52
  # Returns the merged data.
53
53
  def merge_data!(other, source: "YAML front matter")
54
- if other.key?('categories') && !other['categories'].nil?
55
- if other['categories'].is_a?(String)
56
- other['categories'] = other['categories'].split(" ").map(&:strip)
54
+ if other.key?("categories") && !other["categories"].nil?
55
+ if other["categories"].is_a?(String)
56
+ other["categories"] = other["categories"].split(" ").map(&:strip)
57
57
  end
58
- other['categories'] = (data['categories'] || []) | other['categories']
58
+ other["categories"] = (data["categories"] || []) | other["categories"]
59
59
  end
60
60
  Utils.deep_merge_hashes!(data, other)
61
- if data.key?('date') && !data['date'].is_a?(Time)
62
- data['date'] = Utils.parse_date(
63
- data['date'].to_s,
61
+ if data.key?("date") && !data["date"].is_a?(Time)
62
+ data["date"] = Utils.parse_date(
63
+ data["date"].to_s,
64
64
  "Document '#{relative_path}' does not have a valid date in the #{source}."
65
65
  )
66
66
  end
@@ -68,7 +68,7 @@ module Bunto
68
68
  end
69
69
 
70
70
  def date
71
- data['date'] ||= (draft? ? source_file_mtime : site.time)
71
+ data["date"] ||= (draft? ? source_file_mtime : site.time)
72
72
  end
73
73
 
74
74
  def source_file_mtime
@@ -81,7 +81,8 @@ module Bunto
81
81
  #
82
82
  # Returns whether the document is a draft.
83
83
  def draft?
84
- data['draft'] ||= relative_path.index(collection.relative_directory).nil? && collection.label == "posts"
84
+ data["draft"] ||= relative_path.index(collection.relative_directory).nil? &&
85
+ collection.label == "posts"
85
86
  end
86
87
 
87
88
  # The path to the document, relative to the site source.
@@ -89,7 +90,8 @@ module Bunto
89
90
  # Returns a String path which represents the relative path
90
91
  # from the site source to this document
91
92
  def relative_path
92
- @relative_path ||= Pathname.new(path).relative_path_from(Pathname.new(site.source)).to_s
93
+ @relative_path ||= Pathname.new(path)
94
+ .relative_path_from(Pathname.new(site.source)).to_s
93
95
  end
94
96
 
95
97
  # The output extension of the document.
@@ -103,7 +105,7 @@ module Bunto
103
105
  #
104
106
  # Returns the basename without the file extname.
105
107
  def basename_without_ext
106
- @basename_without_ext ||= File.basename(path, '.*')
108
+ @basename_without_ext ||= File.basename(path, ".*")
107
109
  end
108
110
 
109
111
  # The base filename of the document.
@@ -156,7 +158,7 @@ module Bunto
156
158
  #
157
159
  # Returns true if extname == .coffee, false otherwise.
158
160
  def coffeescript_file?
159
- '.coffee'.eql?(extname)
161
+ ".coffee" == extname
160
162
  end
161
163
 
162
164
  # Determine whether the file should be rendered with Liquid.
@@ -195,7 +197,7 @@ module Bunto
195
197
  #
196
198
  # Returns the permalink or nil if no permalink was set in the data.
197
199
  def permalink
198
- data && data.is_a?(Hash) && data['permalink']
200
+ data && data.is_a?(Hash) && data["permalink"]
199
201
  end
200
202
 
201
203
  # The computed URL for the document. See `Bunto::URL#to_s` for more details.
@@ -203,9 +205,9 @@ module Bunto
203
205
  # Returns the computed URL for the document.
204
206
  def url
205
207
  @url = URL.new({
206
- :template => url_template,
208
+ :template => url_template,
207
209
  :placeholders => url_placeholders,
208
- :permalink => permalink
210
+ :permalink => permalink
209
211
  }).to_s
210
212
  end
211
213
 
@@ -237,18 +239,17 @@ module Bunto
237
239
  def write(dest)
238
240
  path = destination(dest)
239
241
  FileUtils.mkdir_p(File.dirname(path))
240
- File.open(path, 'wb') do |f|
241
- f.write(output)
242
- end
242
+ File.write(path, output, :mode => "wb")
243
243
 
244
244
  trigger_hooks(:post_write)
245
245
  end
246
246
 
247
247
  # Whether the file is published or not, as indicated in YAML front-matter
248
248
  #
249
- # Returns true if the 'published' key is specified in the YAML front-matter and not `false`.
249
+ # Returns 'false' if the 'published' key is specified in the
250
+ # YAML front-matter and is 'false'. Otherwise returns 'true'.
250
251
  def published?
251
- !(data.key?('published') && data['published'] == false)
252
+ !(data.key?("published") && data["published"] == false)
252
253
  end
253
254
 
254
255
  # Read in the file and assign the content and data based on the file contents.
@@ -263,23 +264,24 @@ module Bunto
263
264
  @data = SafeYAML.load_file(path)
264
265
  else
265
266
  begin
266
- defaults = @site.frontmatter_defaults.all(relative_path, collection.label.to_sym)
267
- merge_data!(defaults, source: "front matter defaults") unless defaults.empty?
267
+ defaults = @site.frontmatter_defaults.all(
268
+ relative_path,
269
+ collection.label.to_sym
270
+ )
271
+ merge_data!(defaults, :source => "front matter defaults") unless defaults.empty?
268
272
 
269
273
  self.content = File.read(path, Utils.merged_file_read_opts(site, opts))
270
274
  if content =~ YAML_FRONT_MATTER_REGEXP
271
275
  self.content = $POSTMATCH
272
276
  data_file = SafeYAML.load(Regexp.last_match(1))
273
- merge_data!(data_file, source: "YAML front matter") if data_file
277
+ merge_data!(data_file, :source => "YAML front matter") if data_file
274
278
  end
275
279
 
276
280
  post_read
277
281
  rescue SyntaxError => e
278
282
  Bunto.logger.error "Error:", "YAML Exception reading #{path}: #{e.message}"
279
- rescue Exception => e
280
- if e.is_a? Bunto::Errors::FatalException
281
- raise e
282
- end
283
+ rescue => e
284
+ raise e if e.is_a? Bunto::Errors::FatalException
283
285
  Bunto.logger.error "Error:", "could not read file #{path}: #{e.message}"
284
286
  end
285
287
  end
@@ -287,19 +289,19 @@ module Bunto
287
289
 
288
290
  def post_read
289
291
  if relative_path =~ DATE_FILENAME_MATCHER
290
- date, slug, ext = $2, $3, $4
291
- if !data['date'] || data['date'].to_i == site.time.to_i
292
- merge_data!({"date" => date}, source: "filename")
292
+ date, slug, ext = Regexp.last_match.captures
293
+ if !data["date"] || data["date"].to_i == site.time.to_i
294
+ merge_data!({ "date" => date }, :source => "filename")
293
295
  end
294
296
  elsif relative_path =~ DATELESS_FILENAME_MATCHER
295
- slug, ext = $2, $3
297
+ slug, ext = Regexp.last_match.captures
296
298
  end
297
299
 
298
300
  # Try to ensure the user gets a title.
299
301
  data["title"] ||= Utils.titleize_slug(slug)
300
302
  # Only overwrite slug & ext if they aren't specified.
301
- data['slug'] ||= slug
302
- data['ext'] ||= ext
303
+ data["slug"] ||= slug
304
+ data["ext"] ||= ext
303
305
 
304
306
  populate_categories
305
307
  populate_tags
@@ -312,16 +314,19 @@ module Bunto
312
314
  #
313
315
  # Returns nothing.
314
316
  def categories_from_path(special_dir)
315
- superdirs = relative_path.sub(/#{special_dir}(.*)/, '').split(File::SEPARATOR).reject do |c|
317
+ superdirs = relative_path.sub(%r!#{special_dir}(.*)!, "")
318
+ .split(File::SEPARATOR)
319
+ .reject do |c|
316
320
  c.empty? || c.eql?(special_dir) || c.eql?(basename)
317
321
  end
318
- merge_data!({ 'categories' => superdirs }, source: "file path")
322
+ merge_data!({ "categories" => superdirs }, :source => "file path")
319
323
  end
320
324
 
321
325
  def populate_categories
322
326
  merge_data!({
323
- 'categories' => (
324
- Array(data['categories']) + Utils.pluralized_array_from_hash(data, 'category', 'categories')
327
+ "categories" => (
328
+ Array(data["categories"]) +
329
+ Utils.pluralized_array_from_hash(data, "category", "categories")
325
330
  ).map(&:to_s).flatten.uniq
326
331
  })
327
332
  end
@@ -351,7 +356,7 @@ module Bunto
351
356
  #
352
357
  # Returns the content of the document
353
358
  def to_s
354
- output || content || 'NO CONTENT'
359
+ output || content || "NO CONTENT"
355
360
  end
356
361
 
357
362
  # Compare this document against another document.
@@ -361,8 +366,8 @@ module Bunto
361
366
  # equal or greater than the other doc's path. See String#<=> for more details.
362
367
  def <=>(other)
363
368
  return nil unless other.respond_to?(:data)
364
- cmp = data['date'] <=> other.data['date']
365
- cmp = path <=> other.path if cmp.nil? || cmp == 0
369
+ cmp = data["date"] <=> other.data["date"]
370
+ cmp = path <=> other.path if cmp.nil? || cmp.zero?
366
371
  cmp
367
372
  end
368
373
 
@@ -380,7 +385,7 @@ module Bunto
380
385
  #
381
386
  # Returns the document excerpt_separator
382
387
  def excerpt_separator
383
- (data['excerpt_separator'] || site.config['excerpt_separator']).to_s
388
+ (data["excerpt_separator"] || site.config["excerpt_separator"]).to_s
384
389
  end
385
390
 
386
391
  # Whether to generate an excerpt
@@ -394,8 +399,6 @@ module Bunto
394
399
  pos = collection.docs.index { |post| post.equal?(self) }
395
400
  if pos && pos < collection.docs.length - 1
396
401
  collection.docs[pos + 1]
397
- else
398
- nil
399
402
  end
400
403
  end
401
404
 
@@ -403,8 +406,6 @@ module Bunto
403
406
  pos = collection.docs.index { |post| post.equal?(self) }
404
407
  if pos && pos > 0
405
408
  collection.docs[pos - 1]
406
- else
407
- nil
408
409
  end
409
410
  end
410
411
 
@@ -414,7 +415,7 @@ module Bunto
414
415
  end
415
416
 
416
417
  def id
417
- @id ||= File.join(File.dirname(url), (data['slug'] || basename_without_ext).to_s)
418
+ @id ||= File.join(File.dirname(url), (data["slug"] || basename_without_ext).to_s)
418
419
  end
419
420
 
420
421
  # Calculate related posts.
@@ -433,8 +434,9 @@ module Bunto
433
434
  # Override of method_missing to check in @data for the key.
434
435
  def method_missing(method, *args, &blck)
435
436
  if data.key?(method.to_s)
436
- Bunto.logger.warn "Deprecation:", "Document##{method} is now a key in the #data hash."
437
- Bunto.logger.warn "", "Called by #{caller.first}."
437
+ Bunto::Deprecator.deprecation_message "Document##{method} is now a key "\
438
+ "in the #data hash."
439
+ Bunto::Deprecator.deprecation_message "Called by #{caller.first}."
438
440
  data[method.to_s]
439
441
  else
440
442
  super
@@ -16,6 +16,18 @@ module Bunto
16
16
  def environment
17
17
  Bunto.env
18
18
  end
19
+
20
+ def to_h
21
+ @to_h ||= {
22
+ "version" => version,
23
+ "environment" => environment
24
+ }
25
+ end
26
+
27
+ def to_json(state = nil)
28
+ require "json"
29
+ JSON.generate(to_h, state)
30
+ end
19
31
  end
20
32
  end
21
33
  end
@@ -5,10 +5,12 @@ module Bunto
5
5
  class DocumentDrop < Drop
6
6
  extend Forwardable
7
7
 
8
+ NESTED_OBJECT_FIELD_BLACKLIST = %w(
9
+ content output excerpt next previous
10
+ ).freeze
11
+
8
12
  mutable false
9
13
 
10
- def_delegator :@obj, :next_doc, :next
11
- def_delegator :@obj, :previous_doc, :previous
12
14
  def_delegator :@obj, :relative_path, :path
13
15
  def_delegators :@obj, :id, :output, :content, :to_s, :relative_path, :url
14
16
 
@@ -17,16 +19,49 @@ module Bunto
17
19
  end
18
20
 
19
21
  def excerpt
20
- fallback_data['excerpt'].to_s
22
+ fallback_data["excerpt"].to_s
21
23
  end
22
24
 
23
25
  def <=>(other)
24
26
  return nil unless other.is_a? DocumentDrop
25
- cmp = self['date'] <=> other['date']
26
- cmp = self['path'] <=> other['path'] if cmp.nil? || cmp == 0
27
+ cmp = self["date"] <=> other["date"]
28
+ cmp = self["path"] <=> other["path"] if cmp.nil? || cmp.zero?
27
29
  cmp
28
30
  end
29
31
 
32
+ def previous
33
+ @obj.previous_doc.to_liquid
34
+ end
35
+
36
+ def next
37
+ @obj.next_doc.to_liquid
38
+ end
39
+
40
+ # Generate a Hash for use in generating JSON.
41
+ # This is useful if fields need to be cleared before the JSON can generate.
42
+ #
43
+ # state - the JSON::State object which determines the state of current processing.
44
+ #
45
+ # Returns a Hash ready for JSON generation.
46
+ def hash_for_json(state = nil)
47
+ to_h.tap do |hash|
48
+ if state && state.depth >= 2
49
+ hash["previous"] = collapse_document(hash["previous"]) if hash["previous"]
50
+ hash["next"] = collapse_document(hash["next"]) if hash["next"]
51
+ end
52
+ end
53
+ end
54
+
55
+ # Generate a Hash which breaks the recursive chain.
56
+ # Certain fields which are normally available are omitted.
57
+ #
58
+ # Returns a Hash with only non-recursive fields present.
59
+ def collapse_document(doc)
60
+ doc.keys.each_with_object({}) do |(key, _), result|
61
+ result[key] = doc[key] unless NESTED_OBJECT_FIELD_BLACKLIST.include?(key)
62
+ end
63
+ end
64
+
30
65
  private
31
66
  def_delegator :@obj, :data, :fallback_data
32
67
  end
@@ -3,7 +3,9 @@
3
3
  module Bunto
4
4
  module Drops
5
5
  class Drop < Liquid::Drop
6
- NON_CONTENT_METHODS = [:[], :[]=, :inspect, :to_h, :fallback_data].freeze
6
+ include Enumerable
7
+
8
+ NON_CONTENT_METHODS = [:fallback_data, :collapse_document].freeze
7
9
 
8
10
  # Get or set whether the drop class is mutable.
9
11
  # Mutability determines whether or not pre-defined fields may be
@@ -13,11 +15,11 @@ module Bunto
13
15
  #
14
16
  # Returns the mutability of the class
15
17
  def self.mutable(is_mutable = nil)
16
- if is_mutable
17
- @is_mutable = is_mutable
18
- else
19
- @is_mutable = false
20
- end
18
+ @is_mutable = if is_mutable
19
+ is_mutable
20
+ else
21
+ false
22
+ end
21
23
  end
22
24
 
23
25
  def self.mutable?
@@ -86,7 +88,9 @@ module Bunto
86
88
  # Returns an Array of strings which represent method-specific keys.
87
89
  def content_methods
88
90
  @content_methods ||= (
89
- self.class.instance_methods(false) - NON_CONTENT_METHODS
91
+ self.class.instance_methods \
92
+ - Bunto::Drops::Drop.instance_methods \
93
+ - NON_CONTENT_METHODS
90
94
  ).map(&:to_s).reject do |method|
91
95
  method.end_with?("=")
92
96
  end
@@ -98,8 +102,8 @@ module Bunto
98
102
  #
99
103
  # Returns true if the given key is present
100
104
  def key?(key)
101
- if self.class.mutable && @mutations.key?(key)
102
- true
105
+ if self.class.mutable
106
+ @mutations.key?(key)
103
107
  else
104
108
  respond_to?(key) || fallback_data.key?(key)
105
109
  end
@@ -134,10 +138,28 @@ module Bunto
134
138
  #
135
139
  # Returns a pretty generation of the hash representation of the Drop.
136
140
  def inspect
137
- require 'json'
141
+ require "json"
138
142
  JSON.pretty_generate to_h
139
143
  end
140
144
 
145
+ # Generate a Hash for use in generating JSON.
146
+ # This is useful if fields need to be cleared before the JSON can generate.
147
+ #
148
+ # Returns a Hash ready for JSON generation.
149
+ def hash_for_json(*)
150
+ to_h
151
+ end
152
+
153
+ # Generate a JSON representation of the Drop.
154
+ #
155
+ # state - the JSON::State object which determines the state of current processing.
156
+ #
157
+ # Returns a JSON representation of the Drop in a String.
158
+ def to_json(state = nil)
159
+ require "json"
160
+ JSON.generate(hash_for_json(state), state)
161
+ end
162
+
141
163
  # Collects all the keys and passes each to the block in turn.
142
164
  #
143
165
  # block - a block which accepts one argument, the key
@@ -147,6 +169,12 @@ module Bunto
147
169
  keys.each(&block)
148
170
  end
149
171
 
172
+ def each
173
+ each_key.each do |key|
174
+ yield key, self[key]
175
+ end
176
+ end
177
+
150
178
  def merge(other, &block)
151
179
  self.dup.tap do |me|
152
180
  if block.nil?
@@ -171,6 +199,17 @@ module Bunto
171
199
  end
172
200
  end
173
201
  end
202
+
203
+ # Imitate Hash.fetch method in Drop
204
+ #
205
+ # Returns value if key is present in Drop, otherwise returns default value
206
+ # KeyError is raised if key is not present and no default value given
207
+ def fetch(key, default = nil, &block)
208
+ return self[key] if key?(key)
209
+ raise KeyError, %(key not found: "#{key}") if default.nil? && block.nil?
210
+ return yield(key) unless block.nil?
211
+ return default unless default.nil?
212
+ end
174
213
  end
175
214
  end
176
215
  end