blocks 3.0.2 → 4.0.0

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.
Files changed (58) hide show
  1. checksums.yaml +5 -5
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +61 -17
  4. data/CHANGELOG.rdoc +27 -0
  5. data/Gemfile +11 -10
  6. data/Guardfile +1 -0
  7. data/README.md +2 -0
  8. data/bin/deploy_docs +1 -1
  9. data/blocks.gemspec +3 -1
  10. data/docs/_includes/configuration.md +3 -6
  11. data/docs/_includes/defining.md +7 -7
  12. data/docs/_includes/{introduction.md → features.md} +2 -2
  13. data/docs/_includes/helper-blocks.md +5 -0
  14. data/docs/_includes/helper-blocks/content_tag.md +44 -0
  15. data/docs/_includes/installation.md +16 -2
  16. data/docs/_includes/overview.md +21 -0
  17. data/docs/_includes/templating.md +1 -1
  18. data/docs/_includes/templating/bootstrap_4_cards.md +9 -9
  19. data/docs/_layouts/slate.html +11 -9
  20. data/docs/index.md +3 -1
  21. data/gemfiles/Gemfile.rails-3-0-stable +1 -0
  22. data/gemfiles/Gemfile.rails-3-1-stable +1 -0
  23. data/gemfiles/Gemfile.rails-3-2-stable +1 -0
  24. data/gemfiles/Gemfile.rails-4-0-stable +2 -1
  25. data/gemfiles/Gemfile.rails-4-1-stable +2 -1
  26. data/gemfiles/Gemfile.rails-4-2-stable +2 -1
  27. data/gemfiles/Gemfile.rails-5-0-stable +4 -3
  28. data/gemfiles/Gemfile.rails-5-1-stable +4 -3
  29. data/gemfiles/Gemfile.rails-5-2-stable +13 -0
  30. data/lib/blocks.rb +37 -29
  31. data/lib/blocks/builders/block_definition.rb +45 -43
  32. data/lib/blocks/builders/builder.rb +96 -60
  33. data/lib/blocks/builders/hook_definition.rb +19 -4
  34. data/lib/blocks/engine.rb +14 -0
  35. data/lib/blocks/helpers/controller_extensions.rb +13 -0
  36. data/lib/blocks/helpers/haml_capture.rb +44 -0
  37. data/lib/blocks/{action_view_extensions → helpers}/view_extensions.rb +10 -4
  38. data/lib/blocks/renderers/adjacent_blocks_renderer.rb +9 -7
  39. data/lib/blocks/renderers/block_placeholder.rb +2 -0
  40. data/lib/blocks/renderers/block_renderer.rb +26 -5
  41. data/lib/blocks/renderers/block_with_hooks_renderer.rb +29 -19
  42. data/lib/blocks/renderers/collection_renderer.rb +18 -6
  43. data/lib/blocks/renderers/nesting_blocks_renderer.rb +9 -11
  44. data/lib/blocks/renderers/partial_renderer.rb +16 -14
  45. data/lib/blocks/renderers/renderer.rb +9 -24
  46. data/lib/blocks/renderers/runtime_context.rb +175 -147
  47. data/lib/blocks/renderers/wrapper_renderer.rb +21 -10
  48. data/lib/blocks/utilities/configurator.rb +30 -6
  49. data/lib/blocks/utilities/hash_with_caller.rb +36 -32
  50. data/lib/blocks/utilities/hash_with_render_strategy.rb +67 -19
  51. data/lib/blocks/utilities/options_set.rb +38 -63
  52. data/lib/blocks/version.rb +3 -1
  53. metadata +23 -22
  54. data/docs/_includes/wip.md +0 -34
  55. data/lib/blocks/experimental/builder_permissions.rb +0 -52
  56. data/lib/blocks/experimental/invalid_permissions_handler.rb +0 -27
  57. data/lib/blocks/renderers/abstract_renderer.rb +0 -69
  58. data/lib/blocks/utilities/dynamic_configuration.rb +0 -71
@@ -1,18 +1,29 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Blocks
2
- class WrapperRenderer < AbstractRenderer
3
- def render(wrapper, runtime_context)
4
- content_block = Proc.new { with_output_buffer { yield } }
4
+ class WrapperRenderer
5
+ def self.render(wrapper, wrapper_type, runtime_context)
6
+ output_buffer = runtime_context.output_buffer
7
+
5
8
  if wrapper.nil?
6
9
  yield
10
+
7
11
  elsif wrapper.is_a?(Proc)
8
- output_buffer << capture(content_block, *(runtime_context.runtime_args), runtime_context, &wrapper)
9
- elsif block_definition = block_for(wrapper)
10
- runtime_context = runtime_context.extend_to_block_definition(block_definition)
11
- block_renderer.render(content_block, runtime_context)
12
- elsif builder.respond_to?(wrapper)
13
- output_buffer << builder.send(wrapper, runtime_context, &content_block)
12
+ output_buffer << runtime_context.capture(
13
+ Proc.new { runtime_context.with_output_buffer { yield } },
14
+ *(runtime_context.runtime_args),
15
+ runtime_context.to_hash.merge(wrapper_type: wrapper_type),
16
+ &wrapper
17
+ )
18
+
14
19
  else
15
- yield
20
+ wrapper_runtime_context = runtime_context.extend_from_definition(
21
+ wrapper,
22
+ wrapper_type: wrapper_type
23
+ ) do
24
+ runtime_context.with_output_buffer { yield }
25
+ end
26
+ output_buffer << BlockWithHooksRenderer.render(wrapper_runtime_context)
16
27
  end
17
28
  end
18
29
  end
@@ -1,14 +1,27 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Blocks
2
4
  module Configurator
3
5
  extend ActiveSupport::Concern
4
6
 
5
7
  included do
6
- include DynamicConfiguration
8
+ mattr_accessor :builder_class
9
+ mattr_accessor :renderer_class
10
+ mattr_accessor :global_options
11
+ mattr_accessor :lookup_caller_location
12
+ mattr_accessor :track_caller
13
+ # TODO: add default_defintions option
14
+ # TODO: add submodules / extensions option
15
+ # TODO: add option to specify render order for nesting hooks
16
+
17
+ # Specifies whether the default options specified for a render call will take priority over ones specified as block default options
18
+ # Legacy versions of Blocks had default render options always take precedence over block-level default options
19
+ mattr_accessor :default_render_options_take_precedence_over_block_defaults
20
+
21
+ # Specifies whether a block defined with a proc will receive a collection item as its first argument when rendering collections
22
+ # Legacy versions of Blocks assumed that the collection item would be the first argument
23
+ mattr_accessor :collection_item_passed_to_block_as_first_arg
7
24
 
8
- add_config :builder_class
9
- add_config :renderer_class
10
- add_config :global_options_set
11
- add_config :lookup_caller_location, instance_predicate: true
12
25
  reset_config
13
26
  end
14
27
 
@@ -18,7 +31,18 @@ module Blocks
18
31
  config.builder_class = Builder
19
32
  config.renderer_class = Renderer
20
33
  config.lookup_caller_location = false
21
- config.global_options_set = OptionsSet.new("Global Options")
34
+ config.track_caller = false
35
+ config.global_options = nil
36
+ config.default_render_options_take_precedence_over_block_defaults = false
37
+ config.collection_item_passed_to_block_as_first_arg = false
38
+ end
39
+ end
40
+
41
+ def configure
42
+ yield self
43
+ self.track_caller = true if lookup_caller_location
44
+ if track_caller
45
+ HashWithRenderStrategy.send(:prepend, HashWithCaller)
22
46
  end
23
47
  end
24
48
  end
@@ -1,41 +1,49 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Blocks
2
- class HashWithCaller < HashWithIndifferentAccess
4
+ module HashWithCaller
3
5
  attr_accessor :callers
4
6
 
5
7
  def initialize(*args)
6
- self.callers = HashWithIndifferentAccess.new
7
- options = args.extract_options!
8
- add_options(args.first, options)
9
- super &nil
8
+ self.callers = {}
9
+ super
10
10
  end
11
11
 
12
- def to_s
13
- description = []
14
-
15
- description << "{"
16
- description << map do |key, value|
17
- value_display = case value
18
- when Symbol
19
- ":#{value}"
20
- when String
21
- "\"#{value}\""
22
- when Proc
23
- "Proc"
24
- else
25
- value
26
- end
27
- "\"#{key}\" => #{value_display}, # [#{callers[key]}]"
28
- end.join(",\n")
29
- description << "}"
30
- description.join("\n")
12
+ def initialize_copy(original)
13
+ super
14
+ self.callers = original.callers.clone
31
15
  end
32
16
 
33
- def add_options(*args)
17
+ # TODO: implement inspect
18
+
19
+ # TODO: fix and test this implementation
20
+ # def to_s
21
+ # description = []
22
+ #
23
+ # description << "{"
24
+ # description << map do |key, value|
25
+ # value_display = case value
26
+ # when Symbol
27
+ # ":#{value}"
28
+ # when String
29
+ # "\"#{value}\""
30
+ # when Proc
31
+ # "Proc"
32
+ # else
33
+ # value
34
+ # end
35
+ # "\"#{key}\" => #{value_display}, # [#{callers[key]}]"
36
+ # end.join(",\n")
37
+ # description << "}"
38
+ # description.join("\n")
39
+ # end
40
+
41
+ def reverse_merge!(*args)
34
42
  options = args.extract_options!
35
43
 
36
44
  caller_id = args.first.to_s.presence || ""
37
45
 
38
- if !options.is_a?(HashWithCaller) && Blocks.lookup_caller_location?
46
+ if !options.is_a?(HashWithCaller) && Blocks.lookup_caller_location
39
47
  caller_location = caller.detect do |c|
40
48
  !c.include?("/lib/blocks") &&
41
49
  !c.include?("/lib/ruby") &&
@@ -55,19 +63,15 @@ module Blocks
55
63
  end
56
64
 
57
65
  if !self.key?(key)
58
- self[key] = value
59
66
  callers[key] = setter
60
67
 
61
68
  elsif current_value.is_a?(Hash) && value.is_a?(Hash)
62
- self[key] = value.deep_merge(current_value)
69
+ # self[key] = value.deep_merge(current_value)
63
70
  callers[key] = "#{callers[key]}, #{setter}"
64
- # TODO: handle attribute merges here
65
71
  end
66
72
  end
67
- end
68
73
 
69
- def nested_under_indifferent_access
70
- self
74
+ super options
71
75
  end
72
76
  end
73
77
  end
@@ -1,36 +1,35 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Blocks
2
- class HashWithRenderStrategy < HashWithCaller
4
+ class HashWithRenderStrategy < Hash
3
5
  attr_accessor :render_strategy
4
6
 
5
7
  RENDER_WITH_PROXY = :with
6
8
  RENDER_WITH_BLOCK = :block
7
9
  RENDER_WITH_PARTIAL = :partial
8
10
 
9
- RENDERING_STRATEGIES = [RENDER_WITH_PROXY, RENDER_WITH_BLOCK, RENDER_WITH_PARTIAL]
11
+ RENDERING_STRATEGIES = [RENDER_WITH_PROXY, RENDER_WITH_PARTIAL, RENDER_WITH_BLOCK]
10
12
 
11
- def initialize_copy(original)
12
- super
13
- self.callers = original.callers.clone
14
- self.render_strategy = nil
15
- RENDERING_STRATEGIES.each do |rs|
16
- self.delete(rs)
17
- end
13
+ def initialize(*args, &block)
14
+ super &nil
15
+ options = args.extract_options!
16
+ reverse_merge!(args.first, options, &block)
18
17
  end
19
18
 
20
- alias_method :dup, :clone
19
+ def to_hash
20
+ {}.update(self)
21
+ end
21
22
 
22
- def reverse_merge(options)
23
- self.clone.tap {|cloned| cloned.add_options(options) }
23
+ def slice(*keys)
24
+ self.class.new(super)
24
25
  end
25
26
 
26
- # TODO: need to implement either merge or update to update
27
- # both the hash and the callers hash
27
+ def except(*keys)
28
+ slice(*self.keys - keys)
29
+ end
28
30
 
29
- def add_options(*args, &block)
31
+ def reverse_merge!(*args, &block)
30
32
  options = args.extract_options!
31
- if !options.is_a?(HashWithIndifferentAccess)
32
- options = options.with_indifferent_access
33
- end
34
33
  options[:block] = block if block
35
34
  if render_strategy.nil?
36
35
  self.render_strategy = if options.is_a?(HashWithRenderStrategy)
@@ -40,11 +39,60 @@ module Blocks
40
39
  end
41
40
  end
42
41
 
43
- super(*args, options)
42
+ options.each do |key, value|
43
+ current_value = self[key]
44
+
45
+ if !self.key?(key)
46
+ self[key] = value
47
+ elsif current_value.is_a?(Hash) && value.is_a?(Hash)
48
+ # TODO: handle attribute merges here
49
+ self[key] = value.deep_merge(current_value)
50
+ end
51
+ end
44
52
  end
45
53
 
46
54
  def render_strategy_and_item
47
55
  [render_strategy, self[render_strategy]] if render_strategy
48
56
  end
57
+
58
+ def render_strategy_item
59
+ self[render_strategy] if render_strategy
60
+ end
61
+
62
+ def renders_with_proxy?
63
+ render_strategy == RENDER_WITH_PROXY
64
+ end
65
+
66
+ def nested_under_indifferent_access
67
+ self
68
+ end
69
+
70
+ # Returns +true+ so that <tt>Array#extract_options!</tt> finds members of
71
+ # this class.
72
+ def extractable_options?
73
+ true
74
+ end
75
+
76
+ def to_s
77
+ description = []
78
+
79
+ description << "{"
80
+ description << map do |key, value|
81
+ value_display = case value
82
+ when Symbol
83
+ ":#{value}"
84
+ when String
85
+ "\"#{value}\""
86
+ when Proc
87
+ "Proc"
88
+ else
89
+ value
90
+ end
91
+ # "\"#{key}\" => #{value_display}, # [#{callers[key]}]"
92
+ "\"#{key}\" => #{value_display}"
93
+ end.join(",\n")
94
+ description << "}"
95
+ description.join("\n")
96
+ end
49
97
  end
50
98
  end
@@ -1,95 +1,70 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Blocks
2
- class OptionsSet < HashWithIndifferentAccess
4
+ class OptionsSet < HashWithRenderStrategy
3
5
  attr_accessor :name
4
-
5
- attr_accessor :runtime_options
6
- attr_accessor :standard_options
7
6
  attr_accessor :default_options
8
7
 
9
8
  def initialize(*args, &block)
10
- options = args.extract_options!
11
- self.name = args.first
12
- reset
13
- add_options options, &block
14
- super(&nil)
15
- end
16
-
17
- def initialize_copy(original)
18
9
  super
19
- control_fields = (
20
- RuntimeContext::CONTROL_VARIABLES.keys +
21
- RuntimeContext::CONTROL_VARIABLES.values
22
- ).flatten.compact
23
- self.runtime_options = original.runtime_options.clone.except(*control_fields)
24
- self.default_options = original.default_options.clone.except(*control_fields)
25
- self.standard_options = original.standard_options.clone.except(*control_fields)
26
- end
27
-
28
- def to_s
29
- description = []
30
- description << "Block Name: #{name}"
31
- description << "------------------------------"
32
- description << "Runtime Options:"
33
- description << runtime_options.to_s
34
- description << "------------------------------"
35
- description << "Standard Options:"
36
- description << standard_options.to_s
37
- description << "------------------------------"
38
- description << "Default Options:"
39
- description << default_options.to_s
40
- description.join("\n")
10
+ self.name = args.first
41
11
  end
42
12
 
13
+ # def to_s
14
+ # description = []
15
+ # description << "Block Name: #{name}"
16
+ # description << "------------------------------"
17
+ # description << "Standard Options:"
18
+ # description << standard_options.to_s
19
+ # description << "------------------------------"
20
+ # description << "Default Options:"
21
+ # description << default_options.to_s
22
+ # description.join("\n")
23
+ # end
24
+ #
43
25
  def inspect
44
- hash = standard_options.to_hash
45
- hash[:defaults] = default_options if default_options.present?
46
- hash[:runtime] = runtime_options if runtime_options.present?
26
+ hash = to_hash
27
+ hash[:defaults] = default_options.to_hash if default_options.present?
47
28
  hash
48
29
  end
49
30
 
50
- def add_options(*args, &block)
31
+ def reverse_merge!(*args, &block)
51
32
  options = args.extract_options!
52
33
  caller_id = args.first
53
34
 
54
- runtime, defaults, standard = if options.is_a?(OptionsSet)
35
+ defaults, standard = if options.is_a?(OptionsSet)
55
36
  caller_id ||= options.name
56
- [options.runtime_options, options.default_options, options.standard_options]
37
+ [options.default_options, options]
57
38
  else
58
- if !options.is_a?(HashWithIndifferentAccess)
59
- options = options.with_indifferent_access
60
- end
61
- [options.delete(:runtime), options.delete(:defaults), options]
39
+ [options.delete(:defaults), options]
62
40
  end
63
41
 
64
42
  caller_id ||= self.name
65
43
 
66
- runtime_options.add_options caller_id, runtime
67
- standard_options.add_options caller_id, standard, &block
68
- default_options.add_options caller_id, defaults
44
+ if standard.present? || block
45
+ super caller_id, standard, &block
46
+ end
69
47
 
70
- self
71
- end
48
+ if defaults.present?
49
+ if !default_options
50
+ self.default_options = HashWithRenderStrategy.new "#{name} Default Options"
51
+ end
52
+ default_options.reverse_merge! caller_id, defaults
53
+ end
72
54
 
73
- def reset
74
- self.runtime_options = HashWithRenderStrategy.new "#{name} Runtime Options"
75
- self.standard_options = HashWithRenderStrategy.new "#{name} Standard Options"
76
- self.default_options = HashWithRenderStrategy.new "#{name} Default Options"
55
+ self
77
56
  end
78
57
 
79
- def current_render_strategy_and_item
80
- render_strategies_and_items.compact.first
58
+ def renders_with_proxy?
59
+ render_strategy == HashWithRenderStrategy::RENDER_WITH_PROXY
81
60
  end
82
61
 
83
- def render_strategies_and_items
84
- [
85
- runtime_options.render_strategy_and_item,
86
- standard_options.render_strategy_and_item,
87
- default_options.render_strategy_and_item
88
- ]
62
+ def render_strategy
63
+ super || default_options.try(:render_strategy)
89
64
  end
90
65
 
91
- def nested_under_indifferent_access
92
- self
66
+ def render_strategy_item
67
+ super || default_options.try(:render_strategy_item)
93
68
  end
94
69
  end
95
70
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Blocks
2
- VERSION = "3.0.2"
4
+ VERSION = "4.0.0"
3
5
  end
metadata CHANGED
@@ -1,45 +1,45 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blocks
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.2
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Hunter
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-12-11 00:00:00.000000000 Z
11
+ date: 2020-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: call_with_params
14
+ name: rails
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: 3.0.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: 3.0.0
27
27
  - !ruby/object:Gem::Dependency
28
- name: rails
28
+ name: simplecov
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 3.0.0
34
- type: :runtime
33
+ version: '0'
34
+ type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 3.0.0
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: simplecov
42
+ name: memory_profiler
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
@@ -82,10 +82,13 @@ files:
82
82
  - docs/_includes/custom-builders.md
83
83
  - docs/_includes/defining.md
84
84
  - docs/_includes/demos/hooks-and-wrappers-output.html
85
+ - docs/_includes/features.md
86
+ - docs/_includes/helper-blocks.md
87
+ - docs/_includes/helper-blocks/content_tag.md
85
88
  - docs/_includes/hooks.md
86
89
  - docs/_includes/installation.md
87
- - docs/_includes/introduction.md
88
90
  - docs/_includes/option-merging.md
91
+ - docs/_includes/overview.md
89
92
  - docs/_includes/rendering.md
90
93
  - docs/_includes/reserved-keywords.md
91
94
  - docs/_includes/skipping.md
@@ -94,7 +97,6 @@ files:
94
97
  - docs/_includes/templating.md
95
98
  - docs/_includes/templating/bootstrap_4_cards.md
96
99
  - docs/_includes/use-cases.md
97
- - docs/_includes/wip.md
98
100
  - docs/_includes/wrappers.md
99
101
  - docs/_layouts/slate.html
100
102
  - docs/_plugins/gem_version.rb
@@ -147,14 +149,15 @@ files:
147
149
  - gemfiles/Gemfile.rails-4-2-stable
148
150
  - gemfiles/Gemfile.rails-5-0-stable
149
151
  - gemfiles/Gemfile.rails-5-1-stable
152
+ - gemfiles/Gemfile.rails-5-2-stable
150
153
  - lib/blocks.rb
151
- - lib/blocks/action_view_extensions/view_extensions.rb
152
154
  - lib/blocks/builders/block_definition.rb
153
155
  - lib/blocks/builders/builder.rb
154
156
  - lib/blocks/builders/hook_definition.rb
155
- - lib/blocks/experimental/builder_permissions.rb
156
- - lib/blocks/experimental/invalid_permissions_handler.rb
157
- - lib/blocks/renderers/abstract_renderer.rb
157
+ - lib/blocks/engine.rb
158
+ - lib/blocks/helpers/controller_extensions.rb
159
+ - lib/blocks/helpers/haml_capture.rb
160
+ - lib/blocks/helpers/view_extensions.rb
158
161
  - lib/blocks/renderers/adjacent_blocks_renderer.rb
159
162
  - lib/blocks/renderers/block_placeholder.rb
160
163
  - lib/blocks/renderers/block_renderer.rb
@@ -166,7 +169,6 @@ files:
166
169
  - lib/blocks/renderers/runtime_context.rb
167
170
  - lib/blocks/renderers/wrapper_renderer.rb
168
171
  - lib/blocks/utilities/configurator.rb
169
- - lib/blocks/utilities/dynamic_configuration.rb
170
172
  - lib/blocks/utilities/hash_with_caller.rb
171
173
  - lib/blocks/utilities/hash_with_render_strategy.rb
172
174
  - lib/blocks/utilities/options_set.rb
@@ -176,7 +178,7 @@ licenses:
176
178
  - MIT
177
179
  metadata:
178
180
  allowed_push_host: https://rubygems.org
179
- post_install_message:
181
+ post_install_message:
180
182
  rdoc_options: []
181
183
  require_paths:
182
184
  - lib
@@ -191,9 +193,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
191
193
  - !ruby/object:Gem::Version
192
194
  version: '0'
193
195
  requirements: []
194
- rubyforge_project:
195
- rubygems_version: 2.6.13
196
- signing_key:
196
+ rubygems_version: 3.1.2
197
+ signing_key:
197
198
  specification_version: 4
198
199
  summary: Blocks gives you total control over how your blocks of code render.
199
200
  test_files: []