bemer 0.0.0 → 0.1.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 (105) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +12 -0
  3. data/.overcommit.yml +59 -0
  4. data/.rspec +4 -0
  5. data/.rubocop.yml +12 -1
  6. data/.rubocop_todo.yml +3 -6
  7. data/Gemfile +6 -0
  8. data/LICENSE +21 -0
  9. data/LICENSE-RU +23 -0
  10. data/README.md +2 -10
  11. data/Rakefile +8 -1
  12. data/bemer.gemspec +19 -6
  13. data/lib/bemer.rb +93 -2
  14. data/lib/bemer/asset_matcher.rb +21 -0
  15. data/lib/bemer/builders.rb +23 -0
  16. data/lib/bemer/builders/tag/element.rb +22 -0
  17. data/lib/bemer/builders/template.rb +57 -0
  18. data/lib/bemer/builders/template_list.rb +56 -0
  19. data/lib/bemer/builders/tree.rb +35 -0
  20. data/lib/bemer/builders/tree/element.rb +22 -0
  21. data/lib/bemer/common_template.rb +29 -0
  22. data/lib/bemer/component.rb +19 -0
  23. data/lib/bemer/component_pack.rb +39 -0
  24. data/lib/bemer/configuration.rb +36 -0
  25. data/lib/bemer/context.rb +44 -0
  26. data/lib/bemer/context_extentions.rb +12 -0
  27. data/lib/bemer/context_extentions/structure.rb +32 -0
  28. data/lib/bemer/context_extentions/template.rb +15 -0
  29. data/lib/bemer/default_template_list.rb +35 -0
  30. data/lib/bemer/entity.rb +94 -0
  31. data/lib/bemer/entity_builder.rb +126 -0
  32. data/lib/bemer/helpers.rb +41 -0
  33. data/lib/bemer/mixin_list.rb +74 -0
  34. data/lib/bemer/modifier_list.rb +68 -0
  35. data/lib/bemer/pipeline.rb +85 -0
  36. data/lib/bemer/pipeline/handler.rb +132 -0
  37. data/lib/bemer/predicate.rb +63 -0
  38. data/lib/bemer/railtie.rb +36 -0
  39. data/lib/bemer/renderer.rb +15 -0
  40. data/lib/bemer/tag.rb +35 -0
  41. data/lib/bemer/tag_builder.rb +11 -0
  42. data/lib/bemer/template.rb +90 -0
  43. data/lib/bemer/template_catalog.rb +42 -0
  44. data/lib/bemer/template_catalog/drawer.rb +31 -0
  45. data/lib/bemer/template_list.rb +62 -0
  46. data/lib/bemer/tree.rb +143 -0
  47. data/lib/bemer/tree/base_node.rb +49 -0
  48. data/lib/bemer/tree/node.rb +129 -0
  49. data/lib/bemer/tree/text_node.rb +11 -0
  50. data/lib/bemer/version.rb +1 -1
  51. data/spec/bemer/entity_spec.rb +37 -0
  52. data/spec/bemer/mixin_list_spec.rb +99 -0
  53. data/spec/bemer/modifier_list_spec.rb +83 -0
  54. data/spec/bemer_spec.rb +36 -0
  55. data/spec/dummy/Rakefile +8 -0
  56. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  57. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  58. data/spec/dummy/app/helpers/application_helper.rb +4 -0
  59. data/spec/dummy/app/jobs/application_job.rb +4 -0
  60. data/spec/dummy/app/mailers/application_mailer.rb +6 -0
  61. data/spec/dummy/app/models/application_record.rb +5 -0
  62. data/spec/dummy/app/models/concerns/.keep +0 -0
  63. data/spec/dummy/app/views/layouts/application.html.erb +13 -0
  64. data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
  65. data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
  66. data/spec/dummy/bin/bundle +5 -0
  67. data/spec/dummy/bin/rails +6 -0
  68. data/spec/dummy/bin/rake +6 -0
  69. data/spec/dummy/bin/setup +39 -0
  70. data/spec/dummy/bin/update +31 -0
  71. data/spec/dummy/bin/yarn +13 -0
  72. data/spec/dummy/config.ru +7 -0
  73. data/spec/dummy/config/application.rb +27 -0
  74. data/spec/dummy/config/boot.rb +7 -0
  75. data/spec/dummy/config/cable.yml +10 -0
  76. data/spec/dummy/config/database.yml +25 -0
  77. data/spec/dummy/config/environment.rb +7 -0
  78. data/spec/dummy/config/environments/development.rb +51 -0
  79. data/spec/dummy/config/environments/production.rb +84 -0
  80. data/spec/dummy/config/environments/test.rb +44 -0
  81. data/spec/dummy/config/initializers/application_controller_renderer.rb +7 -0
  82. data/spec/dummy/config/initializers/backtrace_silencers.rb +8 -0
  83. data/spec/dummy/config/initializers/bemer.rb +7 -0
  84. data/spec/dummy/config/initializers/cookies_serializer.rb +7 -0
  85. data/spec/dummy/config/initializers/filter_parameter_logging.rb +6 -0
  86. data/spec/dummy/config/initializers/inflections.rb +17 -0
  87. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  88. data/spec/dummy/config/initializers/wrap_parameters.rb +16 -0
  89. data/spec/dummy/config/locales/en.yml +33 -0
  90. data/spec/dummy/config/puma.rb +58 -0
  91. data/spec/dummy/config/routes.rb +5 -0
  92. data/spec/dummy/config/secrets.yml +32 -0
  93. data/spec/dummy/config/spring.rb +8 -0
  94. data/spec/dummy/lib/assets/.keep +0 -0
  95. data/spec/dummy/log/.keep +0 -0
  96. data/spec/dummy/package.json +5 -0
  97. data/spec/dummy/public/404.html +67 -0
  98. data/spec/dummy/public/422.html +67 -0
  99. data/spec/dummy/public/500.html +66 -0
  100. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  101. data/spec/dummy/public/apple-touch-icon.png +0 -0
  102. data/spec/dummy/public/favicon.ico +0 -0
  103. data/spec/rails_helper.rb +37 -0
  104. data/spec/spec_helper.rb +41 -0
  105. metadata +301 -7
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/object/blank'
4
+
5
+ module Bemer
6
+ class TemplateList < DefaultTemplateList
7
+ def initialize(view, path, cached: false, prefix: true, **options)
8
+ super(view, cached)
9
+
10
+ @options = options
11
+ @path = build_full_path(prefix, path)
12
+ end
13
+
14
+ def compile
15
+ super
16
+
17
+ add_default_templates
18
+
19
+ output = view.render(template: template, locals: { **options })
20
+
21
+ remove_template_catalog!
22
+
23
+ output
24
+ end
25
+
26
+ protected
27
+
28
+ attr_reader :options, :path
29
+
30
+ def add_default_templates
31
+ default_template = template('index.bemhtml')
32
+
33
+ return unless view.lookup_context.exists?(default_template)
34
+
35
+ view.render(template: default_template)
36
+ end
37
+
38
+ def template(name = 'index')
39
+ [path, name].join('/')
40
+ end
41
+
42
+ def build_full_path(prefix, path)
43
+ return path if prefix.blank?
44
+
45
+ path_prefix = prefix.instance_of?(TrueClass) ? default_path_prefix(path) : prefix
46
+
47
+ [path_prefix, path].reject(&:blank?).join('/')
48
+ end
49
+
50
+ def default_path_prefix(path)
51
+ return Bemer.default_path_prefix.to_s unless Bemer.default_path_prefix.respond_to?(:call)
52
+
53
+ Bemer.default_path_prefix.call(path.to_s, view)
54
+ end
55
+
56
+ def remove_template_catalog!
57
+ return unless template_catalog.owner.eql?(object_id)
58
+
59
+ view.remove_instance_variable(:@bemer_template_catalog)
60
+ end
61
+ end
62
+ end
data/lib/bemer/tree.rb ADDED
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/string/output_safety'
4
+ require 'active_support/dependencies/autoload'
5
+
6
+ module Bemer
7
+ class Tree
8
+ extend ActiveSupport::Autoload
9
+
10
+ eager_autoload do
11
+ autoload :BaseNode
12
+ autoload :Node
13
+ autoload :TextNode
14
+ end
15
+
16
+ attr_accessor :parent_node
17
+ attr_reader :node_metadata, :pipeline
18
+
19
+ def initialize(template_catalog, **params)
20
+ @node_metadata = {}
21
+ @params = params
22
+ @parent_node = nil
23
+ @pipeline = Pipeline.new(template_catalog)
24
+ @root_nodes = []
25
+ end
26
+
27
+ def render(&block)
28
+ return unless block_given?
29
+
30
+ builder = Builders::Tree.new(self)
31
+ output = ActiveSupport::SafeBuffer.new
32
+
33
+ output << block.binding.receiver.capture(builder, &block)
34
+ output << render_root_nodes
35
+ end
36
+
37
+ def replace(node) # rubocop:disable Metrics/AbcSize
38
+ siblings = parent_node.nil? ? root_nodes : parent_node.children
39
+ metadata = node_metadata.delete(node.object_id)
40
+ position = metadata[:position]
41
+ offset = position + node.replacers.length
42
+
43
+ insert_metadata(position, metadata[:last], node.replacers)
44
+ update_metadata(offset, siblings[position..-1])
45
+
46
+ siblings[position - 1...position] = node.replacers
47
+ end
48
+
49
+ def print
50
+ root_nodes.each(&:print)
51
+ end
52
+
53
+ def add(node)
54
+ if need_replace_parent_node?
55
+ parent_node.replacers << node
56
+ else
57
+ add_metadata(node.object_id)
58
+
59
+ parent_node.nil? ? root_nodes << node : parent_node.children << node
60
+ end
61
+
62
+ nil
63
+ end
64
+
65
+ def add_node(block = '', element = nil, bem_cascade: nil, **options, &content)
66
+ bem_cascade = inherited_bem_cascade if bem_cascade.nil?
67
+ new_options = { **params, bem_cascade: bem_cascade, **options }
68
+
69
+ add Node.new(self, block, element, new_options, &content)
70
+ end
71
+
72
+ def add_text_node(content = nil, &callback)
73
+ add TextNode.new(self, content, &callback)
74
+ end
75
+
76
+ protected
77
+
78
+ attr_reader :root_nodes, :params
79
+
80
+ def inherited_bem_cascade
81
+ parent_node.nil? ? params[:bem_cascade] : parent_node.bem_cascade
82
+ end
83
+
84
+ def need_replace_parent_node?
85
+ !parent_node.nil? && parent_node.need_replace?
86
+ end
87
+
88
+ def render_root_nodes
89
+ output = ActiveSupport::SafeBuffer.new
90
+ position = 0
91
+
92
+ while position < root_nodes.length
93
+ root_node = root_nodes[position]
94
+
95
+ pipeline.run!(root_node)
96
+
97
+ next replace(root_node) if root_node.need_replace?
98
+
99
+ position += 1
100
+
101
+ output << root_node.render
102
+ end
103
+
104
+ output
105
+ end
106
+
107
+ def add_metadata(node_id)
108
+ siblings = parent_node.nil? ? root_nodes : parent_node.children
109
+ last_sibling = siblings.last
110
+
111
+ node_metadata[last_sibling.object_id][:last] = false if last_sibling
112
+
113
+ node_metadata[node_id] = { position: siblings.length + 1, last: true }
114
+ end
115
+
116
+ def insert_metadata(position, last, replacers)
117
+ index = 0
118
+ length = replacers.length
119
+ last_position = length - 1
120
+
121
+ while index < length
122
+ data = { position: position + index, last: last && last_position.eql?(index) }
123
+
124
+ node_metadata[replacers[index].object_id] = data
125
+
126
+ index += 1
127
+ end
128
+ end
129
+
130
+ def update_metadata(offset, siblings)
131
+ index = 0
132
+ length = siblings.length
133
+
134
+ while index < length
135
+ sibling = siblings[index]
136
+
137
+ node_metadata[sibling.object_id][:position] = offset + index
138
+
139
+ index += 1
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Bemer
6
+ class Tree
7
+ class BaseNode
8
+ extend Forwardable
9
+
10
+ attr_reader :entity, :entity_builder, :tree
11
+
12
+ def_delegators :entity, :attrs, :bem, :bem_cascade, :block, :block?, :cls,
13
+ :content, :elem, :elem?, :element?, :js, :mix, :mods, :name, :tag
14
+
15
+ def initialize(tree, block = '', element = nil, **options, &content)
16
+ @entity = Entity.new(block, element, options, &content)
17
+ @entity_builder = EntityBuilder.new(block, element, options, &content)
18
+ @renderer = Renderer.new
19
+ @tree = tree
20
+ end
21
+
22
+ def render
23
+ entity_builder.content = capture_content
24
+
25
+ renderer.render(entity_builder)
26
+ end
27
+
28
+ def print(level = 0)
29
+ prefix = ' ' * level
30
+
31
+ puts [prefix, name, "(#{object_id})"].join
32
+ end
33
+
34
+ def need_replace?
35
+ false
36
+ end
37
+
38
+ protected
39
+
40
+ attr_reader :renderer
41
+
42
+ def capture_content
43
+ return content unless content.respond_to?(:call)
44
+
45
+ content.binding.receiver.capture(&content)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/string/output_safety'
4
+
5
+ module Bemer
6
+ class Tree
7
+ class Node < BaseNode
8
+ attr_accessor :content_replaced, :need_replace, :params
9
+ attr_reader :applied_modes, :children, :replacers
10
+
11
+ alias content_replaced? content_replaced
12
+ alias need_replace? need_replace
13
+
14
+ def initialize(tree, block = '', element = nil, **options, &content)
15
+ super(tree, block, element, options, &content)
16
+
17
+ @applied_modes = Pipeline::MODES.map { |mode| [mode, false] }.to_h
18
+ @children = []
19
+ @content_replaced = false
20
+ @need_replace = false
21
+ @params = tree.parent_node.nil? ? {} : Hash[tree.parent_node.params]
22
+ @replacers = []
23
+ end
24
+
25
+ def last?
26
+ tree.node_metadata[object_id][:last]
27
+ end
28
+
29
+ def first?
30
+ position.eql?(1)
31
+ end
32
+
33
+ def position
34
+ tree.node_metadata[object_id][:position]
35
+ end
36
+
37
+ def add_child_nodes
38
+ return content unless content.respond_to?(:call)
39
+
40
+ builder = Builders::Tree::Element.new(tree, block) if block?
41
+
42
+ content.binding.receiver.capture(builder, &content)
43
+ end
44
+
45
+ def replace_parent_and_execute
46
+ return unless block_given?
47
+
48
+ old_parent_node = tree.parent_node
49
+ tree.parent_node = self
50
+
51
+ output = yield
52
+
53
+ tree.parent_node = old_parent_node
54
+
55
+ output
56
+ end
57
+
58
+ def print(level = 0)
59
+ super(level)
60
+
61
+ children.each do |node|
62
+ node.print(level + 1)
63
+ end
64
+ end
65
+
66
+ def apply_next(template, **params)
67
+ tree.pipeline.apply_next(template, self, params)
68
+ end
69
+
70
+ def apply(mode, template, **params)
71
+ tree.pipeline.apply(mode, template, self, params)
72
+ end
73
+
74
+ protected
75
+
76
+ def capture_content
77
+ output = ActiveSupport::SafeBuffer.new
78
+ plain_text = replace_parent_and_execute { add_child_nodes } if need_add_child_nodes?
79
+
80
+ output << entity_builder.content if need_include_builder_content?
81
+ output << plain_text
82
+ output << render_child_nodes
83
+ end
84
+
85
+ def need_include_builder_content?
86
+ !entity_builder.content.respond_to?(:call) && !need_replace?
87
+ end
88
+
89
+ def need_add_child_nodes?
90
+ content.respond_to?(:call) && children.empty? && !content_replaced
91
+ end
92
+
93
+ def render_child_nodes # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
94
+ return if children.empty?
95
+
96
+ position = 0
97
+ output = ActiveSupport::SafeBuffer.new
98
+
99
+ replace_parent_and_execute do
100
+ while position < children.length
101
+ node = children[position]
102
+
103
+ tree.pipeline.run!(node)
104
+
105
+ next tree.replace(node) if node.need_replace?
106
+
107
+ position += 1
108
+
109
+ output << node.render
110
+ end
111
+ end
112
+
113
+ output
114
+ end
115
+
116
+ def initialize_copy(original)
117
+ @applied_modes = Hash[original.applied_modes]
118
+ @children = []
119
+ @content_replaced = false
120
+ @entity = original.entity.dup
121
+ @entity_builder = original.entity_builder.dup
122
+ @entity_builder.content = @entity.content
123
+ @need_replace = false
124
+ @params = Hash[original.params]
125
+ @replacers = []
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bemer
4
+ class Tree
5
+ class TextNode < BaseNode
6
+ def initialize(tree, content = nil, &callback)
7
+ super(tree, tag: false, content: content, &callback)
8
+ end
9
+ end
10
+ end
11
+ end
data/lib/bemer/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bemer
4
- VERSION = '0.0.0'
4
+ VERSION = '0.1.0'.freeze
5
5
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Bemer::Entity do
4
+ subject(:entity) { described_class.new(:block, cls: 'class-1 class_2', class: [:class_3, 'class_4']) }
5
+
6
+ describe '#cls' do
7
+ it { expect(entity.cls).to match_array(%w[class-1 class_2 class-3 class_4]) }
8
+ end
9
+
10
+ context 'when a block' do
11
+ subject(:block) { described_class.new(:block) }
12
+
13
+ it { is_expected.to be_block }
14
+
15
+ describe '#name' do
16
+ it { expect(block.name).to eq 'block' }
17
+ end
18
+
19
+ describe '#bem_class' do
20
+ it { expect(block.bem_class).to eq 'block' }
21
+ end
22
+ end
23
+
24
+ context 'when an element' do
25
+ subject(:elem) { described_class.new('', :elem) }
26
+
27
+ it { is_expected.to be_element }
28
+
29
+ describe '#name' do
30
+ it { expect(elem.name).to eq '__elem' }
31
+ end
32
+
33
+ describe '#bem_class' do
34
+ it { expect(elem.bem_class).to eq '' }
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Bemer::MixinList, focus: true do
4
+ describe 'mixins from an empty params' do
5
+ subject(:mixin_list) { described_class.new([nil, '', {}]) }
6
+
7
+ it 'returns an empty string' do
8
+ expect(mixin_list.to_s).to be_empty
9
+ end
10
+
11
+ it 'returns an empty array' do
12
+ expect(mixin_list.to_a).to be_empty
13
+ end
14
+ end
15
+
16
+ describe 'mixins from an empty array' do
17
+ subject(:mixin_list) { described_class.new([]) }
18
+
19
+ it 'returns an empty string' do
20
+ expect(mixin_list.to_s).to be_empty
21
+ end
22
+
23
+ it 'returns an empty array' do
24
+ expect(mixin_list.to_a).to be_empty
25
+ end
26
+ end
27
+
28
+ describe 'mixins from an empty hash' do
29
+ subject(:mixin_list) { described_class.new({}) }
30
+
31
+ it 'returns an empty string' do
32
+ expect(mixin_list.to_s).to be_empty
33
+ end
34
+
35
+ it 'returns an empty array' do
36
+ expect(mixin_list.to_a).to be_empty
37
+ end
38
+ end
39
+
40
+ describe 'mixins from an array' do
41
+ subject(:mixin_list) { described_class.new([:block_name, { block_name: %i[elem_name1 elem_name2] }, block: :elem]) }
42
+
43
+ it 'returns mixins as a string' do
44
+ expect(mixin_list.to_s).to eq 'block-name block-name__elem-name1 block-name__elem-name2 block__elem'
45
+ end
46
+
47
+ it 'returns mixins as an array' do
48
+ expect(mixin_list.to_a).to match_array %w[block-name block-name__elem-name1 block-name__elem-name2 block__elem]
49
+ end
50
+ end
51
+
52
+ describe 'mixins from a hash' do
53
+ subject(:mixin_list) { described_class.new(block_name: [nil, :elem_name1, :elem_name2], block: :elem) }
54
+
55
+ it 'returns mixins as a string' do
56
+ expect(mixin_list.to_s).to eq 'block-name block-name__elem-name1 block-name__elem-name2 block__elem'
57
+ end
58
+
59
+ it 'returns mixins as an array' do
60
+ expect(mixin_list.to_a).to match_array %w[block-name block-name__elem-name1 block-name__elem-name2 block__elem]
61
+ end
62
+ end
63
+
64
+ describe 'mixins from a symbol' do
65
+ subject(:mixin_list) { described_class.new(:block_name) }
66
+
67
+ it 'returns mixins as a string' do
68
+ expect(mixin_list.to_s).to eq 'block-name'
69
+ end
70
+
71
+ it 'returns mixins as an array' do
72
+ expect(mixin_list.to_a).to match_array %w[block-name]
73
+ end
74
+ end
75
+
76
+ describe 'mixins from a string' do
77
+ subject(:mixin_list) { described_class.new('block_name block__elem') }
78
+
79
+ it 'returns mixins as a string' do
80
+ expect(mixin_list.to_s).to eq 'block_name block__elem'
81
+ end
82
+
83
+ it 'returns mixins as an array' do
84
+ expect(mixin_list.to_a).to match_array %w[block_name block__elem]
85
+ end
86
+ end
87
+
88
+ describe 'mixins from a hash with string values' do
89
+ subject(:mixin_list) { described_class.new('BlockName' => [nil, 'ElemName', :elem_name], 'Block' => 'ElemName') }
90
+
91
+ it 'returns mixins as a string' do
92
+ expect(mixin_list.to_s).to eq 'BlockName BlockName__ElemName BlockName__elem-name Block__ElemName'
93
+ end
94
+
95
+ it 'returns mixins as an array' do
96
+ expect(mixin_list.to_a).to match_array %w[BlockName BlockName__ElemName BlockName__elem-name Block__ElemName]
97
+ end
98
+ end
99
+ end