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/link.rb CHANGED
@@ -1,6 +1,22 @@
1
1
  module WrapIt
2
2
  #
3
- # Link
3
+ # HTML link element
4
+ #
5
+ # You can specify link by `link`, `href` or `url` option or by first String
6
+ # argument. Also includes {TextContainer} module, so you can specify link
7
+ # body with `text` or `body` option or by second String argument or inside
8
+ # block.
9
+ #
10
+ # @example usage
11
+ # link = WrapIt::Link.new(template, 'http://some.url', 'text')
12
+ # link.render # => '<a href="http://some.url">test</a>'
13
+ # link = WrapIt::Link.new(template, link: 'http://some.url', text: 'text')
14
+ # link.render # => '<a href="http://some.url">test</a>'
15
+ # link = WrapIt::Link.new(template, 'text', link: http://some.url')
16
+ # link.render # => '<a href="http://some.url">test</a>'
17
+ #
18
+ # @example in template
19
+ # <%= link 'http://some.url' do %>text<% end %>
4
20
  #
5
21
  # @author Alexey Ovchinnikov <alexiss@cybernetlab.ru>
6
22
  #
@@ -9,33 +25,40 @@ module WrapIt
9
25
 
10
26
  default_tag 'a'
11
27
 
28
+ option :link, if: %i(link href url)
29
+
30
+ # extract first string argument as link only if it not specified in options
31
+ argument(:link, first_only: true, after_options: true,
32
+ if: String, and: ->{ !option_provided?(:link, :href, :url) }
33
+ ) do |_, v|
34
+ self.href = v
35
+ end
36
+
37
+ #
38
+ # Retrieves current link
39
+ #
40
+ # @return [String] link
12
41
  def href
13
- @options[:href]
42
+ html_attr[:href]
14
43
  end
15
44
 
45
+ #
46
+ # Sets link
47
+ # @param value [String] link
48
+ #
49
+ # @return [String] setted link
16
50
  def href=(value)
17
51
  if value.is_a?(Hash)
18
- defined?(Rails) || fail(
52
+ WrapIt.rails? || fail(
19
53
  ArgumentError,
20
54
  'Hash links supported only in Rails env'
21
55
  )
22
56
  value = @template.url_for(value)
23
57
  end
24
58
  value.is_a?(String) || fail(ArgumentError, 'Wrong link type')
25
- @options[:href] = value
26
- end
27
-
28
- before_initialize do
29
- link = @options[:link] || @options[:href] || @options[:url]
30
- @options.delete(:link)
31
- @options.delete(:href)
32
- @options.delete(:url)
33
- unless link.is_a?(String) || link.is_a?(Hash)
34
- @block.nil? && tmp = @arguments.extract_first!(String)
35
- link = @arguments.extract_first!(String)
36
- tmp.nil? || @arguments.unshift(tmp)
37
- end
38
- link.nil? || self.href = link
59
+ html_attr[:href] = value
39
60
  end
61
+ alias_method :link=, :href=
62
+ alias_method :url=, :href=
40
63
  end
41
64
  end
@@ -1,12 +1,60 @@
1
1
  module WrapIt
2
2
  #
3
- # Adds sections functionality
3
+ # Sections is a smart way to make complex components with inheritance.
4
+ #
5
+ # Sections is just array of named HTML markup pieces. You can place any
6
+ # section before or after another at class level and change their content
7
+ # at instance level.
8
+ #
9
+ # Each component have three stages. First is initialization, then sections
10
+ # capturing and rendering. You can change any sections content until
11
+ # rendering stage begins. Finally, renderer joins all sections in order,
12
+ # that they have at render time.
13
+ #
14
+ # {WrapIt::Base} provides following sections: main section is `:content`.
15
+ # All text from block captured there. `:render_arguments` and `:render_block`
16
+ # also provided, so arguments and block passed to render method captured
17
+ # here.
18
+ #
19
+ # Access to sections at instance level performed throw hash-like getter and
20
+ # setter ([] and []=) of self.
21
+ #
22
+ # With this functionality you can easy organize you inheritance, so any
23
+ # descendant can change sections order or any section content without
24
+ # changes to unrelated sections.
25
+ #
26
+ # @example sections usage
27
+ # class IconedButton < WrapIt::Base
28
+ # include TextContainer
29
+ # html_class 'btn'
30
+ # section :icon
31
+ # place :icon, before: :content
32
+ #
33
+ # after_capture do
34
+ # self[:icon] = html_safe('<i class="my-icon"></i>')
35
+ # end
36
+ # end
37
+ #
38
+ # class RightIconedButton < IconedButton
39
+ # place :icon, after: :content
40
+ # end
41
+ #
42
+ # b1 = IconedButton.new(template, 'text')
43
+ # b2 = RightIconedButton.new(template, 'text')
44
+ # b1.render # => '<div class="btn"><i class="my-icon"></i>text</div>'
45
+ # b2.render # => '<div class="btn">text<i class="my-icon"></i></div>'
4
46
  #
5
47
  # @author Alexey Ovchinnikov <alexiss@cybernetlab.ru>
6
48
  #
7
49
  module Sections
50
+ # Documentation includes
51
+ # @!parse extend Sections::ClassMethods
52
+
53
+ # module implementation
54
+
8
55
  extend DerivedAttributes
9
56
 
57
+ #
10
58
  def self.included(base)
11
59
  base == Base || fail(
12
60
  TypeError,
@@ -15,6 +63,11 @@ module WrapIt
15
63
  base.extend ClassMethods
16
64
  end
17
65
 
66
+ #
67
+ # Retrieves specified section content
68
+ # @param name [Symbol] section name
69
+ #
70
+ # @return [String] section content
18
71
  def [](name)
19
72
  @section_names ||= self.class.sections
20
73
  return nil unless @section_names.include?(name)
@@ -22,6 +75,12 @@ module WrapIt
22
75
  @sections[name] ||= empty_html
23
76
  end
24
77
 
78
+ #
79
+ # Sets specified section content
80
+ # @param name [Symbol] section name
81
+ # @param value [String] content
82
+ #
83
+ # @return [String] section content
25
84
  def []=(name, value)
26
85
  @section_names ||= self.class.sections
27
86
  return unless @section_names.include?(name)
@@ -30,13 +89,24 @@ module WrapIt
30
89
  end
31
90
 
32
91
  #
33
- # Class methods to include
92
+ # {Sections} class methods
34
93
  #
35
94
  module ClassMethods
95
+ #
96
+ # Retrieves all sections, including ancestors
97
+ #
98
+ # @return [Array<Symbol>] array of sections
36
99
  def sections
37
100
  collect_derived(:@sections)
38
101
  end
39
102
 
103
+ #
104
+ # Defines new section or sections. Places its to end of section list
105
+ #
106
+ # @overload section([name, ...])
107
+ # @param name [Symbol, String] section name
108
+ #
109
+ # @return [void]
40
110
  def section(*args)
41
111
  @sections ||= []
42
112
  args.flatten.each do |name|
@@ -49,6 +119,10 @@ module WrapIt
49
119
  end
50
120
  end
51
121
 
122
+ #
123
+ # Retrieves section names in current order
124
+ #
125
+ # @return [Array<Symbol>] ordered sections array
52
126
  def placement
53
127
  @placement ||=
54
128
  if self == Base
@@ -59,6 +133,20 @@ module WrapIt
59
133
  end
60
134
  end
61
135
 
136
+ #
137
+ # Places specific section in specified place
138
+ #
139
+ # @overload place(src, to)
140
+ # @param src [Symbol] section name to place
141
+ # @param to [Hash] single key-value hash. Key can be `:before` or
142
+ # `after`, value can be `:begin`, `:end` or section name
143
+ #
144
+ # @overload place(src, at, dst)
145
+ # @param src [Symbol] section name to place
146
+ # @param at [Symbol] can be `:before` or `:after`
147
+ # @param dst [Symbol] can be `:begin`, `:end` or section name
148
+ #
149
+ # @return [void]
62
150
  def place(src, at, dst = nil)
63
151
  if dst == nil && at.is_a?(Hash) && at.keys.size == 1
64
152
  dst = at.values[0]
@@ -5,40 +5,28 @@ module WrapIt
5
5
  # @author Alexey Ovchinnikov <alexiss@cybernetlab.ru>
6
6
  #
7
7
  module Switches
8
+ # Documentation includes
9
+ # @!parse extend Switches::ClassMethods
10
+
11
+ # module implementation
12
+
8
13
  extend DerivedAttributes
9
14
 
15
+ #
10
16
  def self.included(base)
11
17
  base == Base || fail(
12
18
  TypeError,
13
19
  "#{self.class.name} can be included only into WrapIt::Base"
14
20
  )
15
21
  base.extend ClassMethods
16
- base.after_initialize :switches_init
17
- end
18
-
19
- private
20
-
21
- def switches_init
22
- @switches = {}
23
- keys = switches.keys
24
- @options.keys.select { |o| keys.include?(o) }.each do |switch|
25
- send("#{switches[switch][:name]}=", @options.delete(switch) == true)
26
- end
27
- @arguments.extract!(Symbol, and: [keys]).each do |switch|
28
- send("#{switches[switch][:name]}=", true)
29
- end
30
- end
31
-
32
- def switches
33
- @switches_hash ||= self.class.collect_derived(:@switches, {}, :merge)
22
+ base.before_initialize { @switches = {} }
34
23
  end
35
24
 
36
25
  #
37
- # Class methods to include
26
+ # {Switches} class methods
38
27
  #
39
28
  module ClassMethods
40
29
  #
41
- # @dsl
42
30
  # Adds `switch`. Switch is a boolean flag. When element created, creation
43
31
  # arguments will be scanned for `Symbol`, that equals to `name`. If
44
32
  # it founded, switch turned on. Also creation options inspected. If
@@ -57,11 +45,11 @@ module WrapIt
57
45
  # on or removed in other case.
58
46
  #
59
47
  # @param name [String, Symbol] Switch name. Converted to `Symbol`.
60
- # @param opts [Hash] Switch options
61
- # @options opts [true, String, Symbol, Array<String, Symbol>] :html_class
48
+ # @param options [Hash] Switch options
49
+ # @option options [true, String, Symbol, Array<String, Symbol>] :html_class
62
50
  # HTML classes list that will automatically added to element if switch
63
51
  # is on or removed from element if switch id off.
64
- # @options opts [Symbol, Array<Symbol>] :aliases list of aliases.
52
+ # @option options [Symbol, Array<Symbol>] :aliases list of aliases.
65
53
  # Warning! Values are not converted - pass only `Symbols` here.
66
54
  # @yield [state] Runs block when switch state changed, gives it to block.
67
55
  # @yieldparam state [Boolean] Whether switch is on or off.
@@ -83,11 +71,28 @@ module WrapIt
83
71
  end
84
72
  end
85
73
  end
86
- names = [name] + [[options[:aliases]] || []].flatten.compact
74
+
87
75
  define_method("#{name}?") { @switches[name] == true }
88
76
  define_method("#{name}=", &Switches.setter(name, &block))
89
77
  @switches ||= {}
90
- names.each { |n| @switches[n] = options }
78
+
79
+ @switches[name] = options
80
+
81
+ o_params = {}
82
+ a_params = { if: Symbol, and: name }
83
+ if options.key?(:aliases)
84
+ aliases = [options[:aliases]].flatten.compact
85
+ o_params[:if] = [name] + aliases
86
+ a_params[:and] = [name] + aliases
87
+ end
88
+
89
+ option(name, **o_params) do |_, v|
90
+ send("#{options[:name]}=", v == true)
91
+ end
92
+
93
+ argument(name, **a_params) do |_, _|
94
+ send("#{options[:name]}=", true)
95
+ end
91
96
  end
92
97
  end
93
98
 
@@ -96,19 +101,18 @@ module WrapIt
96
101
  #
97
102
  # Makes switch setter block
98
103
  # @param name [String] switch name
99
- # @param &block [Proc] switch block
100
104
  #
101
105
  # @return [Proc] switch setter block
102
106
  def self.setter(name, &block)
103
107
  proc do |value|
104
- opts = switches[name]
108
+ opts = self.class.collect_derived(:@switches, {}, :merge)[name]
105
109
  cb_return = block.nil? || instance_exec(value == true, &block)
106
110
  unless cb_return == false
107
111
  @switches[name] = value == true
108
112
  if value == true
109
- opts.key?(:html_class) && add_html_class(*opts[:html_class])
113
+ opts.key?(:html_class) && html_class << opts[:html_class]
110
114
  else
111
- opts.key?(:html_class) && remove_html_class(*opts[:html_class])
115
+ opts.key?(:html_class) && html_class.delete(*opts[:html_class])
112
116
  end
113
117
  end
114
118
  end
@@ -1,30 +1,103 @@
1
1
  module WrapIt
2
2
  #
3
- # TextContainer
3
+ # Provides functionality for text-contained components.
4
+ #
5
+ # Text can be captured from `:text` or `:body` option, or as first unparsed
6
+ # String argument, or in block, provided to constructor.
7
+ #
8
+ # If block given, text will be captured from it in priority, so String
9
+ # arguments and options will not parsed. You can cancel this manner by
10
+ # calling {ClassMethods#text_in_block text_in_block(false)} method.
11
+ #
12
+ # This module adds `body` section before base `content` section.
4
13
  #
5
14
  # @author Alexey Ovchinnikov <alexiss@cybernetlab.ru>
6
15
  #
7
16
  module TextContainer
17
+ #
8
18
  def self.included(base)
9
19
  base.class_eval do
20
+ extend ClassMethods
21
+
10
22
  default_tag 'p', false
11
23
 
24
+ option(:body, if: %i(body text)) { |_, v| body << v }
25
+ argument(:body, first_only: true, after_options: true,
26
+ if: String,
27
+ and: ->{ !block_provided? || !text_in_block? }) do |_, v|
28
+ self.class.html_safe? && v = html_safe(v)
29
+ body << v
30
+ end
31
+
12
32
  section :body
13
33
  place :body, :before, :content
14
34
 
15
- after_initialize do
16
- @body = @arguments.extract_first!(String) || empty_html
17
- @body += @options[:body] || @options[:text] || empty_html
18
- @options.delete(:body)
19
- @options.delete(:text)
20
- end
21
-
22
35
  after_capture do
23
- self[:body] = html_safe(@body) unless @body.nil?
36
+ self[:body] = html_safe(@body) unless @body.nil? || @body.empty?
24
37
  end
25
38
  end
26
39
  end
27
40
 
28
- attr_accessor :body
41
+ #
42
+ # Retrieves body text
43
+ #
44
+ # @return [String] text
45
+ def body
46
+ @body ||= empty_html
47
+ end
48
+
49
+ module ClassMethods
50
+ #
51
+ # Sets priotiy of text source
52
+ #
53
+ # @param value [Boolean] `true` means if block present - text will
54
+ # be captured from there. `false` means first to inspect arguments and
55
+ # options and if it ommited retirieve text from block.
56
+ #
57
+ # @return [Boolean] current value
58
+ def text_in_block(value = nil)
59
+ if value.nil?
60
+ @text_in_block.nil? && @text_in_block = true
61
+ @text_in_block
62
+ else
63
+ @text_in_block = value == true
64
+ end
65
+ end
66
+
67
+ #
68
+ # Retrieves block priority
69
+ #
70
+ # @return [Boolean] current value
71
+ def text_in_block?
72
+ @text_in_block.nil? && @text_in_block = true
73
+ @text_in_block
74
+ end
75
+
76
+ #
77
+ # Sets whether text from arguments are html-safe
78
+ # @param value [Boolean] `true` means that text from arguments have
79
+ # proper markup and component will mark it as save via html_safe
80
+ # method. `flase` means, that this values can contain unsafe content,
81
+ # so user should make html-safe string by itself.
82
+ #
83
+ # @return [Boolean] current value
84
+ def html_safe(value = nil)
85
+ if value.nil?
86
+ @html_safe.nil? && @html_safe = true
87
+ @html_safe
88
+ else
89
+ @html_safe = value == true
90
+ end
91
+ end
92
+
93
+ #
94
+ # Retrieves whether text from attributes are html-safe
95
+ #
96
+ # @return [Boolean] current value
97
+ def html_safe?
98
+ @html_safe.nil? && @html_safe = true
99
+ @html_safe
100
+ end
101
+ end
29
102
  end
30
103
  end