wrap_it 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/.yardopts +3 -0
  4. data/README.md +67 -66
  5. data/lib/wrap_it.rb +16 -16
  6. data/lib/wrap_it/arguments.rb +368 -0
  7. data/lib/wrap_it/base.rb +56 -47
  8. data/lib/wrap_it/callbacks.rb +24 -5
  9. data/lib/wrap_it/capture_array.rb +140 -0
  10. data/lib/wrap_it/container.rb +69 -25
  11. data/lib/wrap_it/derived_attributes.rb +22 -6
  12. data/lib/wrap_it/enums.rb +44 -34
  13. data/lib/wrap_it/frameworks.rb +7 -3
  14. data/lib/wrap_it/helpers.rb +66 -1
  15. data/lib/wrap_it/html.rb +149 -0
  16. data/lib/wrap_it/html_class.rb +164 -183
  17. data/lib/wrap_it/html_data.rb +28 -15
  18. data/lib/wrap_it/link.rb +40 -17
  19. data/lib/wrap_it/sections.rb +90 -2
  20. data/lib/wrap_it/switches.rb +33 -29
  21. data/lib/wrap_it/text_container.rb +83 -10
  22. data/lib/wrap_it/version.rb +2 -1
  23. data/spec/frameworks/log/development.log +2108 -0
  24. data/spec/integration/base_spec.rb +2 -2
  25. data/spec/integration/container_spec.rb +3 -3
  26. data/spec/integration/examples_spec.rb +16 -15
  27. data/spec/integration/text_container_spec.rb +3 -3
  28. data/spec/lib/arguments_array_spec.rb +37 -27
  29. data/spec/lib/arguments_spec.rb +153 -0
  30. data/spec/lib/base_spec.rb +2 -25
  31. data/spec/lib/callbacks_spec.rb +1 -1
  32. data/spec/lib/container_spec.rb +1 -1
  33. data/spec/lib/derived_attributes_spec.rb +1 -1
  34. data/spec/lib/enums_spec.rb +2 -3
  35. data/spec/lib/html_class_spec.rb +269 -80
  36. data/spec/lib/html_data_spec.rb +18 -12
  37. data/spec/lib/html_spec.rb +124 -0
  38. data/spec/lib/link_spec.rb +2 -2
  39. data/spec/lib/sections_spec.rb +1 -1
  40. data/spec/lib/switches_spec.rb +3 -3
  41. data/spec/lib/text_container_spec.rb +2 -2
  42. data/spec/support/example_groups/{wrap_it_example_group.rb → wrapped_example_group.rb} +5 -5
  43. data/wrap_it.gemspec +2 -0
  44. metadata +15 -8
  45. data/lib/wrap_it/arguments_array.rb +0 -128
  46. data/lib/wrap_it/module_helpers.rb +0 -23
data/lib/wrap_it/base.rb CHANGED
@@ -19,59 +19,79 @@ module WrapIt
19
19
  # @author Alexey Ovchinnikov <alexiss@cybernetlab.ru>
20
20
  #
21
21
  class Base
22
+ # Documentation includes
23
+ #
24
+ # @!parse extend Arguments::ClassMethods
25
+ # @!parse extend Enums::ClassMethods
26
+ # @!parse extend HTML::ClassMethods
27
+ # @!parse extend Sections::ClassMethods
28
+ # @!parse extend Switches::ClassMethods
29
+
30
+ #
31
+ # include appropriate functionality from modules
22
32
  include DerivedAttributes
23
33
  include Callbacks
24
34
 
25
35
  callback :initialize, :capture, :render
26
36
 
37
+ include Arguments
27
38
  include Sections
28
- include HTMLClass
29
- include HTMLData
39
+ include HTML
30
40
  include Switches
31
41
  include Enums
32
42
  include Renderer
33
43
 
34
44
  @omit_content = false
35
45
 
36
- attr_reader :tag
37
- attr_reader :options
38
-
46
+ attr_reader :helper_name
47
+ option :helper_name
48
+ option :tag
39
49
  section :content, :render_arguments, :render_block
40
50
  place :content, :after, :begin
41
51
  place :render_block, :after, :begin
42
52
  place :render_arguments, :after, :begin
43
53
 
44
54
  def initialize(template, *args, &block)
45
- @template, @arguments, @block = template, args, block
46
- self.options = @arguments.extract_options!
47
-
48
- @helper_name = @options.delete(:helper_name)
49
- @helper_name.is_a?(String) && @helper_name = @helper_name.to_sym
50
-
51
- @arguments.extend ArgumentsArray
55
+ @template, @block = template, block
52
56
  add_default_classes
53
-
54
57
  run_callbacks :initialize do
55
- @tag = @options.delete(:tag) ||
56
- self.class.get_derived(:@default_tag) || 'div'
57
- @tag = @tag.to_s
58
+ capture_arguments!(args, &block)
59
+ # TODO: uncomment following after html_attr implementation finished
60
+ #html_attr.merge!(args.extract_options!)
61
+ self.html_attr = args.extract_options!
62
+ # TODO: find convenient way to save unprocessed arguments
63
+ @arguments = args
58
64
  end
59
65
  end
60
66
 
67
+ def tag
68
+ @tag ||= (self.class.get_derived(:@default_tag) || 'div').to_s
69
+ end
70
+
71
+ def tag=(value)
72
+ value.is_a?(Symbol) && value = value.to_s
73
+ value.is_a?(String) && @tag = value
74
+ end
75
+
76
+ def helper_name=(value)
77
+ value.is_a?(String) && value = value.to_sym
78
+ value.is_a?(Symbol) && @helper_name = value
79
+ end
80
+
61
81
  def omit_content?
62
- self.class.get_derived(:@omit_content)
82
+ self.class.get_derived(:@omit_content) == true
63
83
  end
64
84
 
65
85
  #
66
86
  # Renders element to template
67
87
  #
68
- # @override render([content, ...])
69
- # @param content [String] additional content that will be appended
70
- # to element content
71
- # @yield [element] Runs block after capturing element content and before
72
- # rendering it. Returned value appended to content.
73
- # @yieldparam element [Base] rendering element.
74
- # @yieldreturn [String, nil] content to append to HTML
88
+ # @overload render([content, ...])
89
+ # @param content [String] additional content that will be appended
90
+ # to element content
91
+ # @yield [element] Runs block after capturing element content and before
92
+ # rendering it. Returned value appended to content.
93
+ # @yieldparam element [Base] rendering element.
94
+ # @yieldreturn [String, nil] content to append to HTML
75
95
  #
76
96
  # @return [String] rendered HTML for element
77
97
  def render(*args, &render_block)
@@ -110,17 +130,17 @@ module WrapIt
110
130
  #
111
131
  # If block present, it will be called when wrapper will rendered.
112
132
  #
113
- # @override wrap(wrapper)
114
- # @param wrapper [Base] wrapper instance.
133
+ # @overload wrap(wrapper)
134
+ # @param wrapper [Base] wrapper instance.
115
135
  #
116
- # @override wrap(wrapper_class, [arg, ...], options = {})
117
- # @param wrapper_class [Class] WrapIt::Base subclass for wrapper.
118
- # @param arg [String, Symbol] wrapper creation arguments.
119
- # @param options [Hash] wrapper creation options.
136
+ # @overload wrap(wrapper_class, [arg, ...], options = {})
137
+ # @param wrapper_class [Class] WrapIt::Base subclass for wrapper.
138
+ # @param arg [String, Symbol] wrapper creation arguments.
139
+ # @param options [Hash] wrapper creation options.
120
140
  #
121
- # @override wrap([arg, ...], options = {})
122
- # @param arg [String, Symbol] wrapper creation arguments.
123
- # @param options [Hash] wrapper creation options.
141
+ # @overload wrap([arg, ...], options = {})
142
+ # @param arg [String, Symbol] wrapper creation arguments.
143
+ # @param options [Hash] wrapper creation options.
124
144
  #
125
145
  # @return [void]
126
146
  def wrap(*args, &block)
@@ -139,7 +159,6 @@ module WrapIt
139
159
  protected
140
160
 
141
161
  #
142
- # @dsl
143
162
  # Defines or gets default tag name for element. This tag can be changed
144
163
  # soon. Without parameters returns current default_tag value.
145
164
  # @param name [<Symbol, String>] Tag name. Converted to `String`.
@@ -158,17 +177,6 @@ module WrapIt
158
177
  @omit_content = true
159
178
  end
160
179
 
161
- def options=(hash)
162
- hash.is_a?(Hash) || return
163
- hash.symbolize_keys!
164
-
165
- # sanitize class
166
- hash[:class] ||= []
167
- hash[:class] = [hash[:class]] unless hash[:class].is_a?(Array)
168
- hash[:class] = hash[:class].map { |c| c.to_s }.uniq
169
- @options = hash
170
- end
171
-
172
180
  def capture_sections
173
181
  run_callbacks :capture do
174
182
  unless @block.nil?
@@ -199,12 +207,13 @@ module WrapIt
199
207
 
200
208
  def do_render
201
209
  # cleanup options from empty values
202
- @options.select! do |k, v|
210
+ html_attr.select! do |k, v|
203
211
  !v.nil? && (!v.respond_to?(:empty?) || !v.empty?)
204
212
  end
205
213
  @rendered = render_sections
206
214
  run_callbacks :render do
207
- @rendered = content_tag(@tag, @rendered, @options)
215
+ options = html_attr.merge(class: html_class.to_html, data: html_data)
216
+ @rendered = content_tag(tag, @rendered, options)
208
217
  end
209
218
  end
210
219
 
@@ -5,38 +5,57 @@ module WrapIt
5
5
  # @author Alexey Ovchinnikov <alexiss@cybernetlab.ru>
6
6
  #
7
7
  module Callbacks
8
+ # Documentation includes
9
+ # @!parse extend Callbacks::ClassMethods
10
+
11
+ # module implementation
12
+
8
13
  extend DerivedAttributes
9
14
 
15
+ #
10
16
  def self.included(base)
11
17
  base.extend ClassMethods
12
18
  end
13
19
 
20
+ #
21
+ # Runs specified callbacks with block
22
+ #
23
+ # Runs first `before` callbacks in inheritance order, then yields block if
24
+ # it given and then `after` callbacks in reverse order.
25
+ #
26
+ # @param name [Symbol] callback name, that should be defined by
27
+ # {ClassMethods#callback callback} method.
28
+ #
29
+ # @return [void]
14
30
  def run_callbacks(name)
15
31
  self.class.collect_derived("@before_#{name}").each do |cb|
16
32
  if cb.is_a?(Symbol)
17
- # break if send(cb) == false # if respond_to?(cb)
18
33
  send(cb) # if respond_to?(cb)
19
34
  else
20
- # break if instance_eval(&cb) == false
21
35
  instance_eval(&cb)
22
36
  end
23
37
  end
24
38
  yield if block_given?
25
39
  self.class.collect_derived("@after_#{name}").reverse.each do |cb|
26
40
  if cb.is_a?(Symbol)
27
- # break if send(cb) == false # if respond_to?(cb)
28
41
  send(cb) # if respond_to?(cb)
29
42
  else
30
- # break if instance_eval(&cb) == false
31
43
  instance_eval(&cb)
32
44
  end
33
45
  end
34
46
  end
35
47
 
36
48
  #
37
- # Class methods to include
49
+ # {Callbacks} class methods
38
50
  #
39
51
  module ClassMethods
52
+ #
53
+ # Defines callback
54
+ #
55
+ # @overload callback([name, ...])
56
+ # @param name [Symbol, String] callback name
57
+ #
58
+ # @return [void]
40
59
  def callback(*args)
41
60
  args.each do |name|
42
61
  instance_eval(&Callbacks.define_callback(:before, name))
@@ -0,0 +1,140 @@
1
+ module WrapIt
2
+ #
3
+ # Adds #capture! and #capture_first! methods to array. Theese methods are
4
+ # extracts items from array by some conditions and returns its as separate
5
+ # array for #capture! and as first item for #capture_first!.
6
+ #
7
+ # @author Alexey Ovchinnikov <alexiss@cybernetlab.ru>
8
+ #
9
+ module CaptureArray
10
+ REQUIRED_METHODS = %i(reject! find_index delete_at)
11
+
12
+ #
13
+ def self.included(base)
14
+ methods = base.methods
15
+ # avoid including in classes thats doen't have methods, used in
16
+ # inplementation
17
+ REQUIRED_METHODS.all? { |m| methods.include?(m) } || fail(
18
+ TypeError,
19
+ "#{self.class.name} can't be included into #{base.class.name}"
20
+ )
21
+ end
22
+
23
+ #
24
+ # Extracts elements from array by conditions, passed in arguments and
25
+ # returns theese elements as new array.
26
+ #
27
+ # Condition can be Regexp, Class, Array, lambdas and any other value.
28
+ # if condition contains labdas, all off them will be called before
29
+ # tests and results of theese calls will be used as conditions.
30
+ #
31
+ # If condition is `Regexp`, all elements of array are tested for matching
32
+ # to this regexp, previously converted to String by their `to_s` method. If
33
+ # condition is an `Array`, all elements tested if it included in these
34
+ # array. If the condition is a class, then elements are tested via `is_a?`
35
+ # method for this class. `true` and `false` conditions do exactly what it
36
+ # mean - `true` will satisfy condition, `false` will not. For any other
37
+ # value, elements are tested with equality operator `==`.
38
+ #
39
+ # You can provide a block. In this case, all arguments are ignored, and
40
+ # block yielded for each element of array. If block returns `true`,
41
+ # element extracted from array.
42
+ #
43
+ # All conditions, passed as arguments are `or`-ed so `String, Symbol` means
44
+ # select Symbol or String elements.
45
+ #
46
+ # You can also specify `and` option, so all tests will be and'ed with its
47
+ # conditions.
48
+ #
49
+ # @overload capture!([condition, ...], opts = {})
50
+ # @param condition [Object] one of `or`-ed conditions for comparing
51
+ # @param opts [Hash] options for extracting
52
+ # @option opts [Object, Array] :and one or array of `and`-ed conditions
53
+ #
54
+ # @overload capture!(&block)
55
+ # @yield [element] Gives each element of array to block. You should return
56
+ # `true` to capture this element or `false` to keep it in array.
57
+ # @yieldparam [Object] element element of array to inspect
58
+ # @yieldreturn [Boolean] whether exclude this element or not
59
+ #
60
+ # @return [Array] array of captured elements
61
+ #
62
+ # @example capture by class
63
+ # arr = [1, 2, 3, 'and', 'string']
64
+ # arr.extend WrapIt::CaptureArray
65
+ # arr.capture(String) #=> ['and', 'string']
66
+ # arr #=> [1, 2, 3]
67
+ #
68
+ # @example capture by value
69
+ # arr = [1, 2, 3, 'and', 'string']
70
+ # arr.extend WrapIt::CaptureArray
71
+ # arr.capture(1, 2) #=> [1, 2]
72
+ # arr #=> [3, 'and', 'string']
73
+ #
74
+ # @example capture by Regexp
75
+ # arr = [1, 2, 3, 'and', 'string', :str]
76
+ # arr.extend WrapIt::CaptureArray
77
+ # arr.capture(/^str/) #=> ['string', :str]
78
+ # arr #=> [1, 2, 3, 'and']
79
+ #
80
+ # @example capture by Array
81
+ # arr = [1, 2, 3, 'and', 'string']
82
+ # arr.extend WrapIt::CaptureArray
83
+ # arr.capture([1, 10, 'and']) #=> [1, 'and']
84
+ # arr #=> [2, 3, 'string']
85
+ #
86
+ # @example capture by block
87
+ # arr = [1, 2, 3, 'and', 'string']
88
+ # arr.extend WrapIt::CaptureArray
89
+ # arr.capture {|x| x < 3} #=> [1, 2]
90
+ # arr #=> [3, 'and', 'string']
91
+ #
92
+ # @example capture with `and` condition
93
+ # arr = [1, 2, 3, 'and', 'string', :str]
94
+ # arr.extend WrapIt::CaptureArray
95
+ # arr.capture(String, and: [/^str/]) #=> ['string']
96
+ # arr #=> [1, 2, 3, 'and', :str]
97
+ def capture!(*args, &block)
98
+ captureed = []
99
+ reject! do |arg|
100
+ do_compare(arg, *args, &block) && (captureed << arg) && true
101
+ end
102
+ captureed
103
+ end
104
+
105
+ #
106
+ # Extracts first element from array that is satisfy conditions, passed in
107
+ # arguments and returns these element.
108
+ #
109
+ # @see #capture!
110
+ def capture_first!(*args, &block)
111
+ index = find_index { |arg| do_compare(arg, *args, &block) }
112
+ index.nil? ? nil : delete_at(index)
113
+ end
114
+
115
+ private
116
+
117
+ def do_compare(target, *compare_args, &block)
118
+ if block_given?
119
+ yield target
120
+ else
121
+ options = compare_args.extract_options!
122
+ compare_args.map! { |x| x.is_a?(Proc) && x.lambda? ? x.call : x }
123
+ result = compare_args.any? do |dest|
124
+ case
125
+ when dest == true || dest == false then dest
126
+ when dest.is_a?(Array) then dest.include?(target)
127
+ when dest.is_a?(Regexp) then dest.match(target.to_s)
128
+ when dest.is_a?(Class) then target.is_a?(dest)
129
+ when dest.is_a?(Proc) then dest.call(dest) == true
130
+ else dest == target
131
+ end
132
+ end
133
+ if options[:and].is_a?(Array)
134
+ result &&= do_compare(target, *options[:and])
135
+ end
136
+ result
137
+ end
138
+ end
139
+ end
140
+ end
@@ -4,7 +4,8 @@ module WrapIt
4
4
  #
5
5
  # @author Alexey Ovchinnikov <alexiss@cybernetlab.ru>
6
6
  #
7
- # TODO: single_child
7
+ # @todo single_child realization
8
+ # @todo refactor code for more clearness
8
9
  class Container < Base
9
10
  switch :deffered_render do |_|
10
11
  # avoid changing deffered_render after any child added
@@ -15,27 +16,64 @@ module WrapIt
15
16
  end
16
17
  end
17
18
 
19
+ # list of children elements
18
20
  attr_reader :children
21
+
22
+ # children can be extracted from normal template flow and rendered in
23
+ # separate section.
19
24
  attr_writer :extract_children
25
+
20
26
  section :children
21
27
 
22
28
  def extract_children?
23
29
  @extract_children == true
24
30
  end
25
31
 
26
- after_initialize do
32
+ before_initialize do
27
33
  @children = []
28
- self.class.extract_from_options.each do |option, name|
29
- args = options.delete(option)
30
- next if args.nil?
31
- args = [args] unless args.is_a?(Array)
32
- self.deffered_render = true
33
- send(name, *args)
34
- end
35
34
  end
36
35
 
37
36
  #
38
- # Defines child elements helper for creation of child items.
37
+ # Defines helper for child elements creation.
38
+ #
39
+ # @example simple usage
40
+ # class Item < WrapIt::Base
41
+ # include TextContainer
42
+ # end
43
+ #
44
+ # class List < WrapIt::Container
45
+ # default_tag 'ul'
46
+ # child :item, tag: 'li'
47
+ # end
48
+ #
49
+ # list = List.new(template)
50
+ # list.item 'list item 1'
51
+ # list.item 'list item 2'
52
+ # list.render # => '<ul><li>list item 1'</li><li>list item 2</li></ul>'
53
+ #
54
+ # @example with option
55
+ # class Button < WrapIt::Container
56
+ # include TextContainer
57
+ # html_class 'btn'
58
+ # child :icon, tag: 'i', option: true
59
+ # end
60
+ #
61
+ # btn = Button.new(template, 'Home', icon: { class: 'i-home' })
62
+ # btn.render # => '<div class="btn">Home<i class="i-home"></i></div>'
63
+ #
64
+ # @overload child(name, class_name = nil, [args, ...], opts = {}, &block)
65
+ # @param name [Symbol, String] helper method name
66
+ # @param class_name [String, Base] class for child elements. If ommited
67
+ # WrapIt::Base will be used
68
+ # @param args [Object] any arguments that will be passed to child
69
+ # element constructor
70
+ # @param opts [Hash] options
71
+ # @option opts [true, Symbol] :option if specified, child can be created
72
+ # via option with same name (if :option is true) or with specified
73
+ # name
74
+ # @option opts [Symbol] :section section to that this children will be
75
+ # rendered. By default children rendered to `children`. Refer to
76
+ # {Sections} module for details.
39
77
  #
40
78
  # @return [String]
41
79
  def self.child(name, *args, &block)
@@ -48,8 +86,11 @@ module WrapIt
48
86
  'WrapIt::Base'
49
87
  end
50
88
  child_class = child_class.name if child_class.is_a?(Class)
51
- @helpers ||= []
52
- @helpers << name
89
+
90
+ opts = args.extract_options!
91
+ extract = opts.delete(:option)
92
+ args << opts
93
+
53
94
  define_method name do |*helper_args, &helper_block|
54
95
  # We should clone arguments becouse if we have loop in template,
55
96
  # `extract_options!` below works only for first iterration
@@ -60,20 +101,21 @@ module WrapIt
60
101
  helper_args += default_args + [options]
61
102
  add_children(name, child_class, block, *helper_args, &helper_block)
62
103
  end
63
- end
64
104
 
65
- def self.extract_from_options(*args)
66
- return @extract_from_options || [] if args.size == 0
67
- hash = args.extract_options!
68
- args.size.odd? && fail(ArgumentError, 'odd arguments number')
69
- args.each_with_index { |arg, i| i.even? && hash[arg] = args[i + 1] }
70
- @helpers ||= []
71
- hash.symbolize_keys!
72
- @extract_from_options = Hash[
73
- hash.select do |k, v|
74
- (v.is_a?(String) || v.is_a?(Symbol)) && @helpers.include?(k)
75
- end.map { |k, v| [k, v.to_sym] }
76
- ]
105
+ unless extract.nil?
106
+ extract.is_a?(Array) || extract = [extract]
107
+ extract.each do |opt_name|
108
+ opt_name = name if opt_name == true
109
+ option(opt_name) do |_, arguments|
110
+ self.deffered_render = true
111
+ arguments.is_a?(Array) || arguments = [arguments]
112
+ o = arguments.extract_options!
113
+ o.merge!(extracted: true)
114
+ arguments << o
115
+ send name, *arguments
116
+ end
117
+ end
118
+ end
77
119
  end
78
120
 
79
121
  after_capture do
@@ -106,6 +148,7 @@ module WrapIt
106
148
  def add_children(name, helper_class, class_block, *args, &helper_block)
107
149
  options = args.extract_options!
108
150
  section = options.delete(:section) || :children
151
+ extracted = options.delete(:extracted) == true
109
152
  args << options
110
153
  item = Object
111
154
  .const_get(helper_class)
@@ -120,6 +163,7 @@ module WrapIt
120
163
  class_block.nil? || instance_exec(item, &class_block)
121
164
 
122
165
  deffered_render? && @children << item
166
+ return if extracted
123
167
  if !deffered_render? && (omit_content? || extract_children?)
124
168
  self[section] << capture { item.render }
125
169
  end