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,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/object/blank'
4
+ require 'active_support/core_ext/hash/keys'
5
+
6
+ module Bemer
7
+ class Entity
8
+ attr_accessor :bem_cascade
9
+ attr_reader :bem, :bem_class, :block, :content, :elem, :js, :name, :tag
10
+
11
+ alias element elem
12
+
13
+ def initialize(block = '', element = nil, **options, &content)
14
+ @bem_class = Bemer.bem_class(block, element)
15
+ @block = block
16
+ @content = extract_content(options.delete(:content), &content)
17
+ @elem = element
18
+ @name = Bemer.entity_name(block, element)
19
+
20
+ extract_options!(options)
21
+ end
22
+
23
+ def attrs
24
+ @attrs ||= build_attrs(html_attrs)
25
+ end
26
+
27
+ def block?
28
+ !element?
29
+ end
30
+
31
+ def cls
32
+ @cls ||= build_css_classes(css_classes)
33
+ end
34
+
35
+ def elem?
36
+ !elem.nil?
37
+ end
38
+
39
+ alias element? elem?
40
+
41
+ def mix
42
+ @mix ||= mixins.to_a
43
+ end
44
+
45
+ def mods
46
+ @mods ||= modifiers.to_h
47
+ end
48
+
49
+ protected
50
+
51
+ attr_reader :css_classes, :html_attrs, :mixins, :modifiers
52
+
53
+ def extract_content(plain_text, &content)
54
+ block_given? ? content : plain_text
55
+ end
56
+
57
+ def extract_options!(options) # rubocop:disable Metrics/AbcSize
58
+ @bem = options.delete(:bem)
59
+ @bem_cascade = options.delete(:bem_cascade)
60
+ @css_classes = [options.delete(:class), options.delete(:cls)]
61
+ @js = options.delete(:js)
62
+ @mixins = MixinList.new(options.delete(:mix))
63
+ @modifiers = ModifierList.new(block, element, options.delete(:mods))
64
+ @tag = build_tag(options.delete(:tag))
65
+ @html_attrs = options
66
+ end
67
+
68
+ def build_css_classes(*classes)
69
+ classes.map do |css_class|
70
+ next [] if css_class.blank?
71
+
72
+ case css_class
73
+ when String then css_class.split
74
+ when Array then css_class.map { |css_cls| build_css_classes(css_cls) }
75
+ else Bemer.css_class(css_class)
76
+ end
77
+ end.flatten.uniq
78
+ end
79
+
80
+ def build_tag(new_tag)
81
+ return new_tag if new_tag.nil? || new_tag.instance_of?(Symbol)
82
+
83
+ new_tag.blank? ? '' : new_tag.to_sym
84
+ end
85
+
86
+ def build_attrs(new_attrs)
87
+ case new_attrs
88
+ when Array then new_attrs.to_h.symbolize_keys
89
+ when Hash then new_attrs.symbolize_keys
90
+ else {}
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/object/blank'
4
+ require 'active_support/core_ext/hash/deep_merge'
5
+
6
+ module Bemer
7
+ class EntityBuilder < Entity
8
+ def attrs
9
+ attributes = Hash[super]
10
+ attributes[:class] = cls
11
+
12
+ return attributes unless bem?
13
+
14
+ attributes.deep_merge!(js)
15
+ end
16
+
17
+ def attrs=(new_attrs, save = true)
18
+ new_attrs = build_attrs(new_attrs)
19
+
20
+ save ? @attrs = new_attrs : new_attrs
21
+ end
22
+
23
+ def bem
24
+ bem_via_option? ? super : true
25
+ end
26
+
27
+ def bem=(new_bem, save = true)
28
+ save ? @bem = new_bem : new_bem
29
+ end
30
+
31
+ def bem?
32
+ bem_enabled_via_option? || bem_cascade_enabled_via_option? || bem_enabled_fully?
33
+ end
34
+
35
+ def bem_cascade
36
+ bem_cascade_via_option? ? super : true
37
+ end
38
+
39
+ def bem_cascade=(new_bem_cascade, save = true)
40
+ save ? @bem_cascade = new_bem_cascade : new_bem_cascade
41
+ end
42
+
43
+ def cls
44
+ return super unless bem?
45
+
46
+ js_class = 'i-bem' if @js.present? && bem_class.present?
47
+
48
+ [bem_class, mods, mix, super, js_class].reject(&:blank?)
49
+ end
50
+
51
+ def cls=(new_cls, save = true)
52
+ new_cls = build_css_classes(new_cls)
53
+
54
+ save ? @cls = new_cls : new_cls
55
+ end
56
+
57
+ def content=(new_content, save = true)
58
+ save ? @content = new_content : new_content
59
+ end
60
+
61
+ def js
62
+ return {} if @js.blank? || bem_class.blank?
63
+
64
+ attrs = @js.instance_of?(TrueClass) ? {} : super
65
+
66
+ { data: { bem: { name => attrs } } }
67
+ end
68
+
69
+ def js=(new_js, save = true)
70
+ save ? @js = new_js : new_js
71
+ end
72
+
73
+ def mix=(new_mix, save = true)
74
+ new_mix = MixinList.new(new_mix).to_a
75
+
76
+ save ? @mix = new_mix : new_mix
77
+ end
78
+
79
+ def mods
80
+ modifiers.to_a
81
+ end
82
+
83
+ def mods=(new_mods, save = true)
84
+ modifiers = ModifierList.new(block, element, new_mods)
85
+ @modifiers = modifiers if save
86
+
87
+ modifiers.to_h
88
+ end
89
+
90
+ def tag
91
+ super.nil? ? default_tag : @tag
92
+ end
93
+
94
+ def tag=(new_tag, save = true)
95
+ new_tag = build_tag(new_tag)
96
+
97
+ save ? @tag = new_tag : new_tag
98
+ end
99
+
100
+ protected
101
+
102
+ def bem_via_option?
103
+ !@bem.nil?
104
+ end
105
+
106
+ def bem_cascade_via_option?
107
+ !@bem_cascade.nil?
108
+ end
109
+
110
+ def bem_enabled_via_option?
111
+ bem_via_option? && bem
112
+ end
113
+
114
+ def bem_cascade_enabled_via_option?
115
+ bem_cascade_via_option? && bem_cascade && bem
116
+ end
117
+
118
+ def bem_enabled_fully?
119
+ Bemer.bem && bem_cascade && bem
120
+ end
121
+
122
+ def default_tag
123
+ block? ? Bemer.default_block_tag : Bemer.default_element_tag
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bemer
4
+ module Helpers
5
+ def block_tag(name = '', **options, &content)
6
+ Bemer::Tag.new(name, options, &content).render
7
+ end
8
+
9
+ def elem_tag(block = '', element = '', **options, &content)
10
+ Bemer::Tag.new(block, element, options, &content).render
11
+ end
12
+
13
+ def bem_mix(*mix)
14
+ Bemer::MixinList.new(mix).to_s
15
+ end
16
+
17
+ def bem_mods(*block_and_element, mods)
18
+ block, element = *block_and_element
19
+
20
+ Bemer::ModifierList.new(block, element, mods).to_s
21
+ end
22
+
23
+ def render_component(path, **options, &block)
24
+ Bemer::TemplateList.new(self, path, options).compile(&block)
25
+ end
26
+
27
+ alias refine_component render_component
28
+
29
+ def define_templates(cached: true, &block)
30
+ Bemer::DefaultTemplateList.new(self, cached).compile(&block)
31
+ end
32
+
33
+ def define_component(**options, &block)
34
+ Bemer::Component.new(self).render(options, &block)
35
+ end
36
+
37
+ def component_pack(&block)
38
+ Bemer::ComponentPack.new(self).render(&block)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/object/blank'
4
+
5
+ module Bemer
6
+ class MixinList
7
+ def initialize(mix)
8
+ @mix = mix
9
+ @mixins = nil
10
+ end
11
+
12
+ def to_a
13
+ build_mixins
14
+ end
15
+
16
+ def to_s
17
+ @mix_as_string ||= to_a.join(' ')
18
+ end
19
+
20
+ protected
21
+
22
+ attr_reader :mixins
23
+
24
+ def build_mixins
25
+ return mixins unless mixins.nil?
26
+
27
+ mix = @mix.instance_of?(Hash) ? build_mixins_from_hash : build_mixins_from_array
28
+
29
+ @mixins = mix.flatten.reject(&:blank?).uniq
30
+ end
31
+
32
+ def build_mixin(mixin)
33
+ return mixin.split if mixin.instance_of?(String)
34
+
35
+ case mixin
36
+ when Symbol then Bemer.bem_class(mixin)
37
+ when Array then build_mixin_from_array(mixin)
38
+ when Hash then build_mixin_from_hash(mixin)
39
+ end
40
+ end
41
+
42
+ def build_mixin_from_hash(mixin)
43
+ mixin.map do |block, element|
44
+ next Bemer.bem_class(block, element) unless element.instance_of?(Array)
45
+
46
+ element.map { |elem| Bemer.bem_class(block, elem) }
47
+ end
48
+ end
49
+
50
+ def build_mixin_from_array(mixin)
51
+ block, element = *mixin
52
+
53
+ return Bemer.bem_class(block, element) unless element.instance_of?(Array)
54
+
55
+ element.map { |elem| Bemer.bem_class(block, elem) }
56
+ end
57
+
58
+ def build_mixins_from_hash
59
+ @mix.map do |mixin|
60
+ next if mixin.blank?
61
+
62
+ build_mixin(mixin)
63
+ end
64
+ end
65
+
66
+ def build_mixins_from_array
67
+ Array(@mix).map do |mixin|
68
+ next if mixin.blank?
69
+
70
+ mixin.instance_of?(Array) ? mixin.map { |mix| build_mixin(mix) } : build_mixin(mixin)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/object/blank'
4
+ require 'active_support/core_ext/hash/indifferent_access'
5
+
6
+ module Bemer
7
+ class ModifierList
8
+ def initialize(block, element, mods)
9
+ @bem_class = Bemer.bem_class(block, element)
10
+ @modifiers = nil
11
+ @mods = mods
12
+ end
13
+
14
+ def to_a
15
+ @mods_as_array ||= to_h.map { |name, value| build_css_class(name, value) }
16
+ end
17
+
18
+ def to_s
19
+ @mods_as_string ||= to_a.join(' ')
20
+ end
21
+
22
+ def to_h
23
+ modifiers.nil? ? build_modifiers : modifiers
24
+ end
25
+
26
+ protected
27
+
28
+ attr_reader :modifiers
29
+
30
+ def build_modifiers
31
+ @modifiers = ActiveSupport::HashWithIndifferentAccess.new
32
+
33
+ return modifiers if @mods.blank? || @bem_class.blank?
34
+
35
+ Array(@mods).each do |mods|
36
+ next if mods.blank?
37
+
38
+ mods.is_a?(Hash) ? mods.each { |attrs| add_modifier(attrs) } : add_modifier(mods)
39
+ end
40
+
41
+ modifiers
42
+ end
43
+
44
+ def add_modifier(attrs)
45
+ name, value = normalize(attrs)
46
+
47
+ return if name.blank? || value.blank?
48
+
49
+ modifiers[name] = value
50
+ end
51
+
52
+ def normalize(attrs)
53
+ name, value = *attrs, true
54
+
55
+ value = Bemer.css_class(value) unless [TrueClass, FalseClass, NilClass].include?(value.class)
56
+
57
+ [Bemer.css_class(name), value]
58
+ end
59
+
60
+ def build_css_class(name, value)
61
+ # rubocop:disable Metrics/LineLength
62
+ modifier = value.instance_of?(TrueClass) ? name : [name, value].join(Bemer.modifier_value_separator)
63
+ # rubocop:enable Metrics/LineLength
64
+
65
+ [@bem_class, modifier].join(Bemer.modifier_name_separator)
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/dependencies/autoload'
4
+
5
+ module Bemer
6
+ class Pipeline
7
+ extend ActiveSupport::Autoload
8
+
9
+ eager_autoload do
10
+ autoload :Handler
11
+ end
12
+
13
+ ADD_ATTRS_MODE = :add_attrs
14
+ ADD_CLS_MODE = :add_cls
15
+ ADD_MIX_MODE = :add_mix
16
+ ADD_MODS_MODE = :add_mods
17
+ ATTRS_MODE = :attrs
18
+ BEM_MODE = :bem
19
+ CLS_MODE = :cls
20
+ CONTENT_MODE = :content
21
+ JS_MODE = :js
22
+ MIX_MODE = :mix
23
+ MODS_MODE = :mods
24
+ REPLACE_MODE = :replace
25
+ TAG_MODE = :tag
26
+
27
+ ADD_MODES = [ADD_ATTRS_MODE, ADD_CLS_MODE, ADD_MIX_MODE, ADD_MODS_MODE].freeze
28
+ STRUCTURE_RELATED_MODES = [REPLACE_MODE, CONTENT_MODE].freeze
29
+ BEM_RELATED_MODES = [MODS_MODE, MIX_MODE, JS_MODE].freeze
30
+ TAG_RELATED_MODES = [CLS_MODE, ATTRS_MODE, BEM_MODE, *BEM_RELATED_MODES].freeze
31
+ MODES = [REPLACE_MODE, CONTENT_MODE, TAG_MODE, *TAG_RELATED_MODES].freeze
32
+
33
+ def initialize(template_catalog)
34
+ @handlers = {}
35
+ @template_catalog = template_catalog
36
+ end
37
+
38
+ def run!(node)
39
+ return node if node.instance_of?(Tree::TextNode)
40
+
41
+ MODES.each do |mode|
42
+ handler_by(node.name).apply!(mode, node)
43
+
44
+ break if processing_completed?(mode, node)
45
+ end
46
+
47
+ node
48
+ end
49
+
50
+ def apply_next(template, node, **params)
51
+ handler_by(node.name).apply_next(template, node, params)
52
+ end
53
+
54
+ def apply(mode, template, node, **params)
55
+ handler_by(node.name).apply(mode, template, node, params)
56
+ end
57
+
58
+ protected
59
+
60
+ attr_reader :handlers, :template_catalog
61
+
62
+ def processing_completed?(mode, node)
63
+ return true if JS_MODE.eql?(mode)
64
+
65
+ case mode
66
+ when REPLACE_MODE then node.need_replace?
67
+ when TAG_MODE then node.entity_builder.tag.blank?
68
+ when BEM_MODE then !node.entity_builder.bem?
69
+ else false
70
+ end
71
+ end
72
+
73
+ def handler_by(name)
74
+ return handlers[name] if handlers.key?(name)
75
+
76
+ templates = compiled_templates.select { |template| template.name_match?(name) }
77
+
78
+ handlers[name] = Handler.new(templates)
79
+ end
80
+
81
+ def compiled_templates
82
+ @compiled_templates ||= template_catalog.compiled_templates
83
+ end
84
+ end
85
+ end