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,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