wrap_it 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.
@@ -0,0 +1,84 @@
1
+ module WrapIt
2
+ #
3
+ # Describes elements that can contain other elements
4
+ #
5
+ # @author Alexey Ovchinnikov <alexiss@cybernetlab.ru>
6
+ #
7
+ class Container < Base
8
+ switch :deffered_render
9
+
10
+ after_initialize do
11
+ @children = deffered_render? ? [] : empty_html
12
+ end
13
+
14
+ def self.child(*args, &block)
15
+ create_args = args.last.is_a?(Array) ? args.pop : []
16
+ klass = args.pop
17
+ klass.is_a?(Class) && klass = klass.name
18
+ unless klass.is_a?(String)
19
+ args.push(klass)
20
+ klass = 'WrapIt::Base'
21
+ end
22
+ args.select! { |n| n.is_a?(Symbol) }
23
+ args.size > 0 || fail(ArgumentError, 'No valid method names given')
24
+ args.each do |method|
25
+ define_method method do |*helper_args, &helper_block|
26
+ # We should clone arguments becouse if we have loop in template,
27
+ # `extract_options!` below works only for first iterration
28
+ default_args = create_args.clone
29
+ options = helper_args.extract_options!
30
+ options[:helper_name] = method
31
+ options.merge!(default_args.extract_options!)
32
+ helper_args += default_args + [options]
33
+ add_children(klass, block, *helper_args, &helper_block)
34
+ end
35
+ end
36
+ end
37
+
38
+ # protected
39
+
40
+ after_capture do
41
+ if deffered_render?
42
+ html = Hash[@children.map { |c| [c.object_id, capture { c.render }] }]
43
+ if omit_content?
44
+ @content = html.values.reduce(empty_html) { |a, e| a << e }
45
+ else
46
+ safe = html_safe?(@content)
47
+ @content = @content
48
+ .split(CONTENT_SPLIT_REGEXP)
49
+ .reduce(empty_html) do |a, e|
50
+ match = CONTENT_REPLACE_REGEXP.match(e)
51
+ safe || e = html_safe(e)
52
+ a << match.nil? ? e : html[match[:obj_id].to_i(16)]
53
+ end
54
+ end
55
+ else
56
+ omit_content? && @content = @children
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ CONTENT_SPLIT_REGEXP = /(<!-- WrapIt::Container\(\h+\) -->)/
63
+ CONTENT_REPLACE_REGEXP = /\A<!-- WrapIt::Container\((?<obj_id>\h+)\) -->\z/
64
+
65
+ def add_children(helper_class, class_block, *args, &helper_block)
66
+ item = Object
67
+ .const_get(helper_class)
68
+ .new(@template, *args, &helper_block)
69
+ class_block.nil? || instance_exec(item, &class_block)
70
+
71
+ item = item.render unless deffered_render?
72
+ @children << item if deffered_render? || omit_content?
73
+ if omit_content?
74
+ empty_html
75
+ else
76
+ if deffered_render?
77
+ html_safe("<!-- WrapIt::Container(#{item.object_id.to_s(16)}) -->")
78
+ else
79
+ item
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,37 @@
1
+ module WrapIt
2
+ #
3
+ # Adds minimal support to retrieve derived class variables
4
+ #
5
+ # @author Alexey Ovchinnikov <alexiss@cybernetlab.ru>
6
+ #
7
+ module DerivedAttributes
8
+ def self.included(base)
9
+ base.extend ClassMethods
10
+ end
11
+
12
+ #
13
+ # Class methods to include
14
+ #
15
+ module ClassMethods
16
+ def get_derived(name)
17
+ return instance_variable_get(name) if instance_variable_defined?(name)
18
+ ancestors.each do |ancestor|
19
+ break if ancestor == Base
20
+ next unless ancestor.instance_variable_defined?(name)
21
+ return ancestor.instance_variable_get(name)
22
+ end
23
+ nil
24
+ end
25
+
26
+ def collect_derived(name, initial = [], method = :concat)
27
+ result = initial
28
+ ancestors.each do |ancestor|
29
+ break if ancestor == Base
30
+ next unless ancestor.instance_variable_defined?(name)
31
+ result = result.send(method, ancestor.instance_variable_get(name))
32
+ end
33
+ result
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,89 @@
1
+ module WrapIt
2
+ #
3
+ # Adds enums functionality
4
+ #
5
+ # @author Alexey Ovchinnikov <alexiss@cybernetlab.ru>
6
+ #
7
+ module Enums
8
+ def self.included(base)
9
+ base <= Base || fail(
10
+ TypeError,
11
+ "#{self.class.name} can be included only into WrapIt::Base subclasses"
12
+ )
13
+ extend DerivedAttributes
14
+ base.extend ClassMethods
15
+ base.after_initialize :enums_init
16
+ end
17
+
18
+ private
19
+
20
+ def enums_init
21
+ opt_keys = @options.keys
22
+ self.class.collect_derived(:@enums, {}, :merge).each do |name, opts|
23
+ value = nil
24
+ names = [name] + [opts[:aliases] || []].flatten
25
+ opt_keys.select { |o| names.include? o }.each do |key|
26
+ tmp = @options.delete(key)
27
+ value ||= tmp
28
+ !value.nil? && !opts[:values].include?(value) && value = nil
29
+ end
30
+ @arguments.extract!(Symbol, and: [opts[:values]]).each do |key|
31
+ value ||= key
32
+ end
33
+ send("#{name}=", value)
34
+ end
35
+ end
36
+
37
+ #
38
+ # Class methods to include
39
+ #
40
+ module ClassMethods
41
+ #
42
+ # @dsl
43
+ # Adds `enum`. When element created, creation arguments will be scanned
44
+ # for `Symbol`, that included contains in `values`. If it founded, enum
45
+ # takes this value. Also creation options inspected. If its contains
46
+ # `name: value` key-value pair with valid value, this pair removed from
47
+ # options and enum takes this value.
48
+ #
49
+ # This method also adds getter and setter for this enum.
50
+ #
51
+ # @param name [String, Symbol] Enum name. Converted to `Symbol`.
52
+ # @param options = {} [Hash] Enum options
53
+ # @options options [String, Symbol] :html_class_prefix prefix of HTML
54
+ # class that will automatically added to element if enum changes its
55
+ # value.
56
+ # @options options [Symbol, Array<Symbol>] :aliases list of enum aliases.
57
+ # Warning! Values are not converted - pass only `Symbols` here.
58
+ # @options options [String, Symbol] :default default value for enum,
59
+ # if nil or wrong value given. Converted to `Symbol`.
60
+ # @yield [value] Runs block when enum value changed, gives it to block.
61
+ # @yieldparam value [Symbol] New enum value.
62
+ # @yieldreturn [void]
63
+ #
64
+ # @return [void]
65
+ def enum(name, values, options = {}, &block)
66
+ options.symbolize_keys!
67
+ name = name.to_sym
68
+ options.merge!(block: block, name: name, values: values)
69
+ options.key?(:default) && options[:default] = options[:default].to_sym
70
+ options.key?(:html_class_prefix) && options[:regexp] =
71
+ /\A#{options[:html_class_prefix]}(?:#{values.join('|')})\z/
72
+ var = "@#{name}".to_sym
73
+ define_method("#{name}") { instance_variable_get(var) }
74
+ define_method("#{name}=") do |value|
75
+ v = value if values.include?(value)
76
+ v ||= options[:default] if options.key?(:default)
77
+ instance_variable_set(var, v)
78
+ block.nil? || instance_exec(v, &block)
79
+ if options.key?(:regexp)
80
+ remove_html_class(options[:regexp])
81
+ v.nil? || add_html_class("#{options[:html_class_prefix]}#{v}")
82
+ end
83
+ end
84
+ @enums ||= {}
85
+ @enums[name] = options
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,193 @@
1
+ module WrapIt
2
+ #
3
+ # Methods for manipulationg with HTML class. For internal usage.
4
+ # You should not include this class directly - subclass from
5
+ # `WrapIt::Base` instead.
6
+ #
7
+ # @author Alexey Ovchinnikov <alexiss@cybernetlab.ru>
8
+ #
9
+ module HTMLClass
10
+ extend DerivedAttributes
11
+
12
+ def self.included(base)
13
+ base <= Base || fail(
14
+ TypeError,
15
+ "#{self.class.name} can be included only into WrapIt::Base subclasses"
16
+ )
17
+ base.extend ClassMethods
18
+ end
19
+
20
+ #
21
+ # html class getter
22
+ #
23
+ # @return [Array<String>] array of html classes of element
24
+ def html_class
25
+ @options[:class]
26
+ end
27
+
28
+ #
29
+ # Sets html class(es) for element
30
+ # @param value [Symbol, String, Array<Symbol, String>] HTML class or list
31
+ # of classes. All classes will be converted to Strings, duplicates are
32
+ # removed.
33
+ # @return [void]
34
+ #
35
+ # @example
36
+ # element.html_class = [:a, 'b', ['c', :d, 'a']]
37
+ # element.html_class #=> ['a', 'b', 'c', 'd']
38
+ def html_class=(value)
39
+ @options[:class] = []
40
+ add_html_class(value)
41
+ end
42
+
43
+ #
44
+ # Adds html class(es) to element. Chaining allowed. All classes will be
45
+ # converted to Strings, duplicates are removed.
46
+ # @override add_html_class([[html_class], ...])
47
+ # @param html_class [Symbol, String, Array<Symbol, String>]
48
+ # HTML class or list of HTML classes.
49
+ # @return [self]
50
+ #
51
+ # @example
52
+ # element.html_class = 'a'
53
+ # element.add_html_class :b, :c, ['d', :c, :e, 'a']
54
+ # element.html_class #=> ['a', 'b', 'c', 'd', 'e']
55
+ def add_html_class(*args)
56
+ @options[:class] += args.flatten.map { |c| c.to_s }
57
+ @options[:class].uniq!
58
+ self # allow chaining
59
+ end
60
+
61
+ #
62
+ # Removes html class(es) from element. Chaining allowed
63
+ # @override add_html_class([[html_class], ...])
64
+ # @param html_class [Symbol, String, Regexp, Array<Symbol, String, Regexp>]
65
+ # HTML class or list of HTML classes.
66
+ # @return [self]
67
+ #
68
+ # @example
69
+ # element.add_html_class %w(a b c d e)
70
+ # element.remove_html_class :b, ['c', :e]
71
+ # element.html_class #=> ['a', 'd']
72
+ def remove_html_class(*args)
73
+ args.flatten!
74
+ re = []
75
+ args.reject! { |c| c.is_a?(Regexp) && re << c && true }
76
+ args = args.uniq.map { |c| c.to_s }
77
+ args.size > 0 && @options[:class].reject! { |c| args.include?(c) }
78
+ re.is_a?(Array) && re.each do |r|
79
+ @options[:class].reject! { |c| r.match(c) }
80
+ end
81
+ self # allow chaining
82
+ end
83
+
84
+ #
85
+ # Determines whether element contains class, satisfied by conditions,
86
+ # specified in method arguments.
87
+ #
88
+ # There are two forms of method call: with list of conditions as arguments
89
+ # and with block for comparing. Method makes comparison with html class
90
+ # untill first `true` return value or end of list. All conditions should
91
+ # be satisfied for `true` return of this method.
92
+ #
93
+ # In first form, each argument treated as condition. Condition can be a
94
+ # `Regexp`, so html classes of element tested for matching to that
95
+ # regular expression. If condition is an `Array` then every class will be
96
+ # tested for presence in this array. If condition is `Symbol` or `String`
97
+ # classes will be compared with it via equality operator `==`.
98
+ #
99
+ # In second form all arguments are ignored and for each comparison given
100
+ # block called with html class as argument. Block return value then used.
101
+ #
102
+ # @overload html_class([condition, ...])
103
+ # @param condition [<Regexp, Symbol, String, Array<String>]
104
+ # condition for comparison.
105
+ #
106
+ # @overload html_class(&block)
107
+ # @yield [html_class] Gives each html class to block. You should return
108
+ # `true` if element contains this html class.
109
+ # @yieldparam html_class [String] html class to inspect.
110
+ # @yieldreturn [Boolean] whether element has html class.
111
+ #
112
+ # @return [Boolean] whether element has class with specified conditions.
113
+ #
114
+ # @example with `Symbol` or `String` conditions
115
+ # element.html_class = [:a, :b, :c]
116
+ # element.html_class?(:a) #=> true
117
+ # element.html_class?(:d) #=> false
118
+ # element.html_class?(:a, 'b') #=> true
119
+ # element.html_class?(:a, :d) #=> false
120
+ #
121
+ # @example with `Regexp` conditions
122
+ # element.html_class = [:some, :test]
123
+ # element.html_class?(/some/) #=> true
124
+ # element.html_class?(/some/, /bad/) #=> false
125
+ # element.html_class?(/some/, :test) #=> true
126
+ #
127
+ # @example with `Array` conditions
128
+ # element.html_class = [:a, :b, :c]
129
+ # element.html_class?(%w(a d)) #=> true
130
+ # element.html_class?(%w(e d)) #=> false
131
+ #
132
+ # @example with block
133
+ # element.html_class = [:a, :b, :c]
134
+ # element.html_class? { |x| x == 'a' } #=> true
135
+ def html_class?(*args, &block)
136
+ args.all? { |c| inspect_class(:any?, c, &block) }
137
+ end
138
+
139
+ #
140
+ # Determines whether element doesn't contains class, satisfied by
141
+ # conditions, specified in method arguments.
142
+ #
143
+ # @see html_class?
144
+ def no_html_class?(*args, &block)
145
+ args.all? { |c| inspect_class(:none?, c, &block) }
146
+ end
147
+
148
+ protected
149
+
150
+ def add_default_classes
151
+ add_html_class self.class.collect_derived(:@html_class)
152
+ end
153
+
154
+ private
155
+
156
+ def inspect_class(with, value = nil, &block)
157
+ if block_given?
158
+ @options[:class].send(with, &block)
159
+ else
160
+ case
161
+ when value.is_a?(Regexp)
162
+ @options[:class].send(with) { |c| value.match(c) }
163
+ when value.is_a?(String) || value.is_a?(Symbol)
164
+ @options[:class].send(with) { |c| value.to_s == c }
165
+ when value.is_a?(Array)
166
+ @options[:class].send(with) { |c| value.include?(c) }
167
+ else
168
+ false
169
+ end
170
+ end
171
+ end
172
+
173
+ #
174
+ # Class methods to include
175
+ #
176
+ module ClassMethods
177
+ #
178
+ # @dsl
179
+ # Adds default html classes, thats are automatically added when element
180
+ # created.
181
+ # @override html_class([html_class, ...])
182
+ # @param html_class [String, Symbol, Array<String, Symbol>] HTML class.
183
+ # Converted to `String`
184
+ #
185
+ # @return [void]
186
+ def html_class(*args)
187
+ @html_class ||= []
188
+ @html_class += args.flatten.map { |c| c.to_s }
189
+ @html_class.uniq!
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,66 @@
1
+
2
+ methods = Hash.public_instance_methods(true)
3
+ unless methods.include?(:extractable_options?)
4
+ Hash.send(:define_method, :extractable_options?, proc do
5
+ instance_of?(Hash)
6
+ end)
7
+ end
8
+
9
+ unless methods.include?(:symbolize_keys!)
10
+ Hash.send(:define_method, :symbolize_keys!, proc do
11
+ keys.each do |key|
12
+ next unless key.respond_to?(:to_sym)
13
+ self[key.to_sym] = delete(key)
14
+ end
15
+ self
16
+ end)
17
+ end
18
+
19
+ methods = Array.public_instance_methods(true)
20
+ unless methods.include?(:extract_options!)
21
+ Array.send(:define_method, :extract_options!, proc do
22
+ if last.is_a?(Hash) && last.extractable_options?
23
+ pop
24
+ else
25
+ {}
26
+ end
27
+ end)
28
+ end
29
+
30
+ module WrapIt
31
+ #
32
+ # Non rails render implementation
33
+ #
34
+ module Renderer
35
+ def empty_html
36
+ ''
37
+ end
38
+
39
+ def capture(text = nil)
40
+ block_given? ? yield : text
41
+ end
42
+
43
+ def concat(text)
44
+ @buffer ||= empty_html
45
+ @buffer << text
46
+ end
47
+
48
+ def output_buffer
49
+ @buffer
50
+ end
51
+
52
+ def content_tag(tag, body, options = {})
53
+ arr = [tag]
54
+ options.each { |o, v| arr << "#{o}=\"#{v.to_s}\"" }
55
+ "<#{arr.join(' ')}>#{body}</#{tag}>"
56
+ end
57
+
58
+ def html_safe(text)
59
+ text
60
+ end
61
+
62
+ def html_safe?(text)
63
+ true
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,51 @@
1
+ module WrapIt
2
+ #
3
+ # Provides render function for Rails
4
+ #
5
+ # @author Alexey Ovchinnikov <alexiss@cybernetlab.ru>
6
+ #
7
+ module Renderer
8
+ def empty_html
9
+ ''.html_safe
10
+ end
11
+
12
+ def html_safe(text)
13
+ text.html_safe
14
+ end
15
+
16
+ def html_safe?(text)
17
+ text.html_safe?
18
+ end
19
+
20
+ def superhtml(text)
21
+ text.to_s
22
+ end
23
+
24
+ def capture(*args, &block)
25
+ @template.capture(*args, &block)
26
+ end
27
+
28
+ def concat(*args, &block)
29
+ @template.concat(*args, &block)
30
+ end
31
+
32
+ def content_tag(*args, &block)
33
+ @template.content_tag(*args, &block)
34
+ end
35
+
36
+ def output_buffer(*args, &block)
37
+ @template.output_buffer(*args, &block)
38
+ end
39
+
40
+
41
+ # def self.included(base)
42
+ # puts "LOADED"
43
+ # base.class_eval do
44
+ # delegate :capture, :concat, :content_tag,
45
+ # :output_buffer, to: :@template
46
+ # protected :capture, :concat, :content_tag,
47
+ # :output_buffer
48
+ # end
49
+ # end
50
+ end
51
+ end
@@ -0,0 +1,82 @@
1
+ module WrapIt
2
+ #
3
+ # Adds switches functionality
4
+ #
5
+ # @author Alexey Ovchinnikov <alexiss@cybernetlab.ru>
6
+ #
7
+ module Switches
8
+ def self.included(base)
9
+ base <= Base || fail(
10
+ TypeError,
11
+ "#{self.class.name} can be included only into WrapIt::Base subclasses"
12
+ )
13
+ extend DerivedAttributes
14
+ base.extend ClassMethods
15
+ base.after_initialize :switches_init
16
+ end
17
+
18
+ private
19
+
20
+ def switches_init
21
+ switches = self.class.collect_derived(:@switches, {}, :merge)
22
+ keys = switches.keys
23
+ keys.each { |switch| instance_variable_set("@#{switch}", false) }
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
+ #
33
+ # Class methods to include
34
+ #
35
+ module ClassMethods
36
+ #
37
+ # @dsl
38
+ # Adds `switch`. Switch is a boolean flag. When element created, creation
39
+ # arguments will be scanned for `Symbol`, that equals to `name`. If
40
+ # it founded, switch turned on. Also creation options inspected. If
41
+ # its contains `name: true` key-value pair, this pair removed from
42
+ # options and switch also turned on.
43
+ #
44
+ # This method also adds getter and setter for this switch in form `name?`
45
+ # and `name=` respectively.
46
+ #
47
+ # @param name [String, Symbol] Switch name. Converted to `Symbol`.
48
+ # @param options = {} [Hash] Switch options
49
+ # @options options [String, Symbol, Array<String, Symbol>] :html_class
50
+ # HTML class that will automatically added to element if switch is on
51
+ # or removed from element if switch id off.
52
+ # @options options [Symbol, Array<Symbol>] :aliases list of aliases.
53
+ # Warning! Values are not converted - pass only `Symbols` here.
54
+ # @yield [state] Runs block when switch state changed, gives it to block.
55
+ # @yieldparam state [Boolean] Whether switch is on or off.
56
+ # @yieldreturn [void]
57
+ #
58
+ # @return [void]
59
+ def switch(name, options = {}, &block)
60
+ options.symbolize_keys!
61
+ name = name.to_sym
62
+ options.merge!(block: block, name: name)
63
+ names = [name] + [[options[:aliases]] || []].flatten
64
+ var = "@#{name}".to_sym
65
+ define_method("#{name}?") { instance_variable_get(var) == true }
66
+ define_method("#{name}=") do |value|
67
+ instance_variable_set(var, value == true)
68
+ if value == true
69
+ options.key?(:html_class) && add_html_class(options[:html_class])
70
+ block.nil? || instance_exec(true, &block)
71
+ else
72
+ options.key?(:html_class) &&
73
+ remove_html_class(options[:html_class])
74
+ block.nil? || instance_exec(false, &block)
75
+ end
76
+ end
77
+ @switches ||= {}
78
+ names.each { |n| @switches[n] = options }
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,25 @@
1
+ module WrapIt
2
+ #
3
+ # TextContainer
4
+ #
5
+ # @author Alexey Ovchinnikov <alexiss@cybernetlab.ru>
6
+ #
7
+ module TextContainer
8
+ def self.included(base)
9
+ base.class_eval do
10
+ default_tag 'p'
11
+
12
+ after_initialize do
13
+ @body = @arguments.extract_first!(String) || empty_html
14
+ @body += @options[:body] || @options[:text] || empty_html
15
+ @options.delete(:body)
16
+ @options.delete(:text)
17
+ end
18
+
19
+ after_capture do
20
+ @content = html_safe(@body) + @content unless @body.nil?
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module WrapIt
2
+ VERSION = '0.1.0'
3
+ end