bemer 0.0.0 → 0.1.0

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