wrap_it 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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