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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -0
- data/.yardopts +3 -0
- data/README.md +67 -66
- data/lib/wrap_it.rb +16 -16
- data/lib/wrap_it/arguments.rb +368 -0
- data/lib/wrap_it/base.rb +56 -47
- data/lib/wrap_it/callbacks.rb +24 -5
- data/lib/wrap_it/capture_array.rb +140 -0
- data/lib/wrap_it/container.rb +69 -25
- data/lib/wrap_it/derived_attributes.rb +22 -6
- data/lib/wrap_it/enums.rb +44 -34
- data/lib/wrap_it/frameworks.rb +7 -3
- data/lib/wrap_it/helpers.rb +66 -1
- data/lib/wrap_it/html.rb +149 -0
- data/lib/wrap_it/html_class.rb +164 -183
- data/lib/wrap_it/html_data.rb +28 -15
- data/lib/wrap_it/link.rb +40 -17
- data/lib/wrap_it/sections.rb +90 -2
- data/lib/wrap_it/switches.rb +33 -29
- data/lib/wrap_it/text_container.rb +83 -10
- data/lib/wrap_it/version.rb +2 -1
- data/spec/frameworks/log/development.log +2108 -0
- data/spec/integration/base_spec.rb +2 -2
- data/spec/integration/container_spec.rb +3 -3
- data/spec/integration/examples_spec.rb +16 -15
- data/spec/integration/text_container_spec.rb +3 -3
- data/spec/lib/arguments_array_spec.rb +37 -27
- data/spec/lib/arguments_spec.rb +153 -0
- data/spec/lib/base_spec.rb +2 -25
- data/spec/lib/callbacks_spec.rb +1 -1
- data/spec/lib/container_spec.rb +1 -1
- data/spec/lib/derived_attributes_spec.rb +1 -1
- data/spec/lib/enums_spec.rb +2 -3
- data/spec/lib/html_class_spec.rb +269 -80
- data/spec/lib/html_data_spec.rb +18 -12
- data/spec/lib/html_spec.rb +124 -0
- data/spec/lib/link_spec.rb +2 -2
- data/spec/lib/sections_spec.rb +1 -1
- data/spec/lib/switches_spec.rb +3 -3
- data/spec/lib/text_container_spec.rb +2 -2
- data/spec/support/example_groups/{wrap_it_example_group.rb → wrapped_example_group.rb} +5 -5
- data/wrap_it.gemspec +2 -0
- metadata +15 -8
- data/lib/wrap_it/arguments_array.rb +0 -128
- data/lib/wrap_it/module_helpers.rb +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 995784a5858c83bfefb7ec041d0019e87535b279
|
4
|
+
data.tar.gz: 0e68c3329c7e692bbc685c09caf13981b7dcaa2a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7fb4f2c24ce43bcdbb875dbc303aaa5562a0eb25d91aec437b6b30d254a1e2a3c6a9a22d8de9c00704048317b5aaf18ce19cfe00bb69765fd9735ddfb6801801
|
7
|
+
data.tar.gz: 0b026802d29c1c858e92d6de642b823a113b75fffef8a2c5420cd2b449137289c455d74eb87a7d2380ab86123dcf6a48ee7a16de6e9a6bbdd490638c55cd3f57
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.0.0-p353
|
data/.yardopts
ADDED
data/README.md
CHANGED
@@ -1,19 +1,28 @@
|
|
1
|
+
[![Gem Version](https://badge.fury.io/rb/wrap_it.png)](http://badge.fury.io/rb/wrap_it)
|
2
|
+
[![Code Climate](https://codeclimate.com/github/cybernetlab/wrap_it.png)](https://codeclimate.com/github/cybernetlab/wrap_it)
|
3
|
+
|
1
4
|
# WrapIt
|
2
5
|
|
3
6
|
This library provides set of classes and modules with simple DSL for quick and easy creating html helpers with your own DSL. It's usefull for implementing CSS frameworks, or making your own.
|
4
7
|
|
8
|
+
> Required ruby version is 2.0.0
|
9
|
+
|
10
|
+
> **Warning** A lot of code refactored. API changed. Review you code if you using previous versions of library.
|
11
|
+
|
5
12
|
For example, your designer makes perfect button style for some site. This element will appears in many places of site in some variations. The button have `danger`, `success` and `default` look, and can have `active` state. Button can have some icon. So, you make some CSS styles, and now you should place HTML markup of this element in many places of site. With `wrap_it` library you can do it with following code:
|
6
13
|
|
7
14
|
```ruby
|
15
|
+
module Helpers; end
|
16
|
+
|
8
17
|
WrapIt.register_module Helpers
|
9
18
|
|
10
19
|
module Helpers
|
11
20
|
class PerfectButton < WrapIt::Container
|
12
21
|
include TextContainer
|
13
22
|
html_class 'button'
|
14
|
-
enum :look,
|
23
|
+
enum :look, %i(default success danger), html_class_prefix: 'button-'
|
15
24
|
switch :active, html_class: 'button-active'
|
16
|
-
child :icon,
|
25
|
+
child :icon, tag: 'img', class: 'button-icon'
|
17
26
|
end
|
18
27
|
|
19
28
|
register :p_button, 'PerfectButton'
|
@@ -55,11 +64,9 @@ This will produce following code:
|
|
55
64
|
|
56
65
|
Note, that lines 2 and 3 produces same html markup.
|
57
66
|
|
58
|
-
> **Wraning!** Module registration process changed since 0.2.0. So, if you migrate from 0.1.5, look above examples again and fix your startup code.
|
59
|
-
|
60
67
|
# Status
|
61
68
|
|
62
|
-
|
69
|
+
This is a first release version - `1.0.0`.
|
63
70
|
|
64
71
|
# Installation
|
65
72
|
|
@@ -87,25 +94,29 @@ Library have no specific configuration.
|
|
87
94
|
|
88
95
|
# Usage
|
89
96
|
|
97
|
+
Now, package is well documented, so make sure to inspect [Reference documentation](http://rubydoc.info/github/cybernetlab/wrap_it/frames)
|
98
|
+
|
90
99
|
> This library actively used in [BootstrapIt](https://github.com/cybernetlab/bootstrap_it) package, so explore this project, especially it's [lib/bootstrap_it/view_helpers](https://github.com/cybernetlab/bootstrap_it/tree/master/lib/bootstrap_it/view_helpers) folder for usage examples.
|
91
100
|
|
92
|
-
All helpers classes derived from `WrapIt::Base` class, that provides allmost all functionality. For helpers, thats includes other helpers, use `WrapIt::Container` class.
|
101
|
+
All helpers classes derived from `WrapIt::Base` class, that provides allmost all functionality. For helpers, thats includes other helpers, use `WrapIt::Container` class.
|
93
102
|
|
94
103
|
Simple example explained above. More complex usage is to provide some logic to initalization, capturing and rendering process. To do this, use `after` or `before` `initialize`, `capture` and `reder` callbacks respectively. Usually `after` callbacks used. `initialize` callbacks runs around arguments and optioins parsing, `capture` callbacks runs around capturing element sections and `render` callbacks runs around wrapping content into element tag.
|
95
104
|
|
105
|
+
Also, please inspect arguments [module documentation](http://rubydoc.info/github/cybernetlab/wrap_it/WrapIt/Arguments) for details about creation arguments and options.
|
106
|
+
|
96
107
|
Inside callbacks some usefull instance variables available.
|
97
108
|
|
98
|
-
|
109
|
+
`tag` contains tag name for element.
|
99
110
|
|
100
|
-
|
111
|
+
`html_attr` contains HTML attributes hash.
|
101
112
|
|
102
|
-
|
113
|
+
`html_data` contains HTML data hash.
|
103
114
|
|
104
|
-
|
115
|
+
`html_class` contains array of HTML classes and provides array-like acces to its. See [class documentation](http://rubydoc.info/github/cybernetlab/wrap_it/WrapIt/HTMLClass) for details.
|
105
116
|
|
106
|
-
|
117
|
+
Inside `capture` callback you deals with sections. This mechanism explained in [module documentation](http://rubydoc.info/github/cybernetlab/wrap_it/WrapIt/Sections).
|
107
118
|
|
108
|
-
|
119
|
+
`template` contains rendering template. Use this variable carefully, so if you call `template.link_to` or something else Rails-related, your library will not be portable to other frameworks. So, if you use this gem in user-end application, or Rails-only library, you are free to use all of `template` methods.
|
109
120
|
|
110
121
|
*Examples*
|
111
122
|
|
@@ -113,7 +124,7 @@ Prevent user from changing element tag:
|
|
113
124
|
|
114
125
|
```ruby
|
115
126
|
class Helper < WrapIt::Base
|
116
|
-
after_initialize {
|
127
|
+
after_initialize { self.tag = 'table' }
|
117
128
|
end
|
118
129
|
```
|
119
130
|
|
@@ -121,9 +132,8 @@ Including some simple HTML into content
|
|
121
132
|
|
122
133
|
```ruby
|
123
134
|
class IconHelper < WrapIt::Base
|
124
|
-
|
125
|
-
|
126
|
-
end
|
135
|
+
option :icon
|
136
|
+
attr_accessor :icon
|
127
137
|
|
128
138
|
after_capture do
|
129
139
|
unless @icon.nil?
|
@@ -214,6 +224,24 @@ Sets html class prefix. It can be `Symbol` or `String` and converted to `String`
|
|
214
224
|
|
215
225
|
Once this method called from class, this class will ommit any text content, captured from template. For example, `<%= element do %><p>Any content</p><% end %>` normally will produce `<div><p>Any content</p></div>`. In some cases you whant to drop `<p>Any content</p>`, for exmaple, inside tables.
|
216
226
|
|
227
|
+
#### argument(name, first_only: false, after_options: false, **opts, &block)
|
228
|
+
|
229
|
+
Desclares argument for capturing on initialization process.
|
230
|
+
|
231
|
+
Inside initialization process, all arguments (except options hash), passed to constructor will be inspected to satisfy conditions, specified in `:if` and `:and` options. If this happens, and block given, it evaluated in context of component instance. If no block given, setter with `name` will be attempted to set value. In any way if conditions satisfied, argument removed from future processing.
|
232
|
+
|
233
|
+
If no conditions specified, the `name` of attribute taked as only condition.
|
234
|
+
|
235
|
+
#### option(name, after: nil, **opts, &block)
|
236
|
+
|
237
|
+
Desclares option for capturing on initialization process.
|
238
|
+
|
239
|
+
Provides same manner as `argument` but for hash of options, passed to constructor. Specified conditions are applied to options keys, not to values.
|
240
|
+
|
241
|
+
> Hint: you can specify argument and options with same name to call
|
242
|
+
> same setter.
|
243
|
+
|
244
|
+
|
217
245
|
#### switch(name, options = {}, &block)
|
218
246
|
|
219
247
|
Adds `switch`. Switch is a boolean flag. When element created, creation arguments will be scanned for `Symbol`, that equals to `name`. If it founded, switch turned on. Also creation options inspected. If its contains `name: true` key-value pair, this pair removed from options and switch also turned on. `name` can be `Symbol` or `String` and it converted to `Symbol`.
|
@@ -246,7 +274,7 @@ Adds one ore more sections to element. Refer to [Sections explained](https://git
|
|
246
274
|
|
247
275
|
#### place(src, dst)
|
248
276
|
|
249
|
-
Places section `src` to destination, specified in `dst` hash. `dst` is a single key-value Hash. Key can be `:before` and `:after`. Value can be `:begin`, `:end` or any section name.
|
277
|
+
Places section `src` to destination, specified in `dst` hash. `dst` is a single key-value Hash. Key can be `:before` and `:after`. Value can be `:begin`, `:end` or any section name.
|
250
278
|
|
251
279
|
#### sections
|
252
280
|
|
@@ -278,15 +306,11 @@ Returns array of html classes
|
|
278
306
|
|
279
307
|
Sets html class(es) for element. Arguments can be `String`, `Symbol` or `Array` of it. All converted to plain array of `Symbols`. Duplicated classes removed.
|
280
308
|
|
281
|
-
####
|
282
|
-
|
283
|
-
Adds html class. For args see `#html_class=`
|
309
|
+
#### html_class << *args
|
284
310
|
|
285
|
-
|
311
|
+
You can add html classes as into array
|
286
312
|
|
287
|
-
|
288
|
-
|
289
|
-
#### html_class?(*args, &block)
|
313
|
+
#### html_class.include?(*args, &block)
|
290
314
|
|
291
315
|
Determines whether element contains class, satisfied by conditions, specified in method arguments.
|
292
316
|
|
@@ -301,68 +325,45 @@ In second form all arguments are ignored and for each comparison given block cal
|
|
301
325
|
```ruby
|
302
326
|
# with `Symbol` or `String` conditions
|
303
327
|
element.html_class = [:a, :b, :c]
|
304
|
-
element.html_class?(:a) #=> true
|
305
|
-
element.html_class?(:d) #=> false
|
306
|
-
element.html_class?(:a, 'b') #=> true
|
307
|
-
element.html_class?(:a, :d) #=> false
|
328
|
+
element.html_class.include?(:a) #=> true
|
329
|
+
element.html_class.include?(:d) #=> false
|
330
|
+
element.html_class.include?(:a, 'b') #=> true
|
331
|
+
element.html_class.include?(:a, :d) #=> false
|
308
332
|
|
309
333
|
# with `Regexp` conditions
|
310
334
|
element.html_class = [:some, :test]
|
311
|
-
element.html_class?(/some/) #=> true
|
312
|
-
element.html_class?(/some/, /bad/) #=> false
|
313
|
-
element.html_class?(/some/, :test) #=> true
|
335
|
+
element.html_class.include?(/some/) #=> true
|
336
|
+
element.html_class.include?(/some/, /bad/) #=> false
|
337
|
+
element.html_class.include?(/some/, :test) #=> true
|
314
338
|
|
315
339
|
# with `Array` conditions
|
316
340
|
element.html_class = [:a, :b, :c]
|
317
|
-
element.html_class?(%w(a d)) #=> true
|
318
|
-
element.html_class?(%w(e d)) #=> false
|
341
|
+
element.html_class.include?(%w(a d)) #=> true
|
342
|
+
element.html_class.include?(%w(e d)) #=> false
|
319
343
|
|
320
344
|
# with block
|
321
345
|
element.html_class = [:a, :b, :c]
|
322
|
-
element.html_class? { |x| x == 'a' } #=> true
|
346
|
+
element.html_class.include? { |x| x == 'a' } #=> true
|
323
347
|
```
|
324
348
|
|
325
|
-
#### no_html_class?(*args, &block)
|
326
|
-
|
327
|
-
Determines whether element doesn't contains class, satisfied by conditions, specified in method arguments. See `html_class?`.
|
328
|
-
|
329
|
-
#### set_html_data(name, value)
|
330
|
-
|
331
|
-
Sets HTML data attribute named `name` to `value`.
|
332
|
-
|
333
|
-
#### remove_html_data(name)
|
334
|
-
|
335
|
-
Removes HTML data attribute named `name`.
|
336
|
-
|
337
|
-
## WrapIt::Container
|
338
|
-
|
339
|
-
This class used for elements, that can hold other elements.
|
340
|
-
|
341
|
-
At first, note, that children can be created by two ways. First - inside template and second - from code. If child created from template, you have two choises again: first to keep child in place, where it defined in template, second - to cut it from there and place together with other childs. Two variables affects on this process: `ommit_content`, defined in `WrapIt::Base` and `extarct_children`. If `ommit_content` is `true`, all content will be dropped and children placed inside `children` section. If `extract_children` is true, children also placed into `children` section, but `content` is keeped.
|
342
349
|
|
343
|
-
|
344
|
-
|
345
|
-
All children, added to container injected with `render_to` and `parent` methods, that are gives you rendering section name and container itself.
|
346
|
-
|
347
|
-
### DSL methods
|
348
|
-
|
349
|
-
#### child(name, *args, &block)
|
350
|
-
|
351
|
-
Creates your own DSL method to create child items. In arguments, you should specify name of method. Then you can specify class name or class itself for child. If ommited, `WrapIt::Base` will be used. All other arguments will be mixed with arguments, specified by user and passed to child constructor. **Warning!** Make shure, that your child arguments don't begins with `String` if you ommit class argument. As workaround, don't ommit class argument and specify it as 'WrapIt::Base'. At last, you can define block, that will be called after creating child, but before its rendering. This child passed as argument to block.
|
352
|
-
|
353
|
-
You can render children to section, other than `:children`. To do this, specify `section` option with section name.
|
354
|
-
|
355
|
-
Look into [lib/bootstrap_it/view_helpers](https://github.com/cybernetlab/bootstrap_it/tree/master/lib/bootstrap_it/view_helpers) folder for usage examples.
|
350
|
+
Look to [Reference documentation](http://rubydoc.info/github/cybernetlab/wrap_it/frames) for other classes description.
|
356
351
|
|
357
352
|
# Todo
|
358
353
|
|
359
354
|
* Enlarge library functionality
|
360
|
-
* Strong testing
|
361
355
|
* Finish Sinatra integration
|
362
|
-
* Rubydoc documentation
|
363
356
|
|
364
357
|
# Changes
|
365
358
|
|
359
|
+
`1.0.0`
|
360
|
+
* first release version
|
361
|
+
* a lot of code refactored
|
362
|
+
* documentation allmost finished
|
363
|
+
* well test coverage
|
364
|
+
* API changed
|
365
|
+
* added: arguments and options processing
|
366
|
+
|
366
367
|
`0.2.0`
|
367
368
|
* added: sections mechanism
|
368
369
|
* many fixes
|
data/lib/wrap_it.rb
CHANGED
@@ -1,23 +1,23 @@
|
|
1
|
-
require
|
1
|
+
require File.join %w(wrap_it frameworks)
|
2
2
|
|
3
3
|
if WrapIt.rails?
|
4
4
|
require 'rails'
|
5
|
-
require
|
5
|
+
require File.join %w(wrap_it rails)
|
6
6
|
else
|
7
|
-
require
|
7
|
+
require File.join %w(wrap_it no_rails)
|
8
8
|
end
|
9
9
|
|
10
|
-
require
|
10
|
+
require File.join %w(wrap_it helpers)
|
11
11
|
|
12
|
-
require
|
13
|
-
require
|
14
|
-
require
|
15
|
-
require
|
16
|
-
require
|
17
|
-
require
|
18
|
-
require
|
19
|
-
require
|
20
|
-
require
|
21
|
-
require
|
22
|
-
require
|
23
|
-
require
|
12
|
+
require File.join %w(wrap_it derived_attributes)
|
13
|
+
require File.join %w(wrap_it callbacks)
|
14
|
+
require File.join %w(wrap_it capture_array)
|
15
|
+
require File.join %w(wrap_it arguments)
|
16
|
+
require File.join %w(wrap_it sections)
|
17
|
+
require File.join %w(wrap_it html)
|
18
|
+
require File.join %w(wrap_it switches)
|
19
|
+
require File.join %w(wrap_it enums)
|
20
|
+
require File.join %w(wrap_it base)
|
21
|
+
require File.join %w(wrap_it container)
|
22
|
+
require File.join %w(wrap_it text_container)
|
23
|
+
require File.join %w(wrap_it link)
|
@@ -0,0 +1,368 @@
|
|
1
|
+
module WrapIt
|
2
|
+
#
|
3
|
+
# This module responisble to parse creation arguments in component
|
4
|
+
# initialization process.
|
5
|
+
#
|
6
|
+
# Respect to ruby language, any method can take variable number of
|
7
|
+
# arguments and a hash of options. Also you can pass a block to it. So,
|
8
|
+
# when your component subclassed from WrapIt::Base, user can create its
|
9
|
+
# instances via helpers. And when such component initialized you should
|
10
|
+
# be able to process all arguments, passed to helper or constructor. Finally
|
11
|
+
# all unprocessed options setted as component html attributes.
|
12
|
+
#
|
13
|
+
# Two API methods provided for this purposes - `argument` and `option`.
|
14
|
+
# Each of them declares conditions for capturing some arguments and options.
|
15
|
+
# Conditions applies to arguments itself or to options keys. CapturedArray
|
16
|
+
# Array extension is used to capture arguments, so refer to its documentation
|
17
|
+
# for conditions details.
|
18
|
+
#
|
19
|
+
# @author Alexey Ovchinnikov <alexiss@cybernetlab.ru>
|
20
|
+
#
|
21
|
+
module Arguments
|
22
|
+
# Documentation includes
|
23
|
+
# @!parse extend Arguments::ClassMethods
|
24
|
+
|
25
|
+
# module implementation
|
26
|
+
|
27
|
+
extend DerivedAttributes
|
28
|
+
|
29
|
+
#
|
30
|
+
def self.included(base)
|
31
|
+
@base = base
|
32
|
+
base.extend ClassMethods
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# {Arguments} Class methods to include
|
37
|
+
#
|
38
|
+
module ClassMethods
|
39
|
+
#
|
40
|
+
# Desclares argument for capturing on initialization process.
|
41
|
+
#
|
42
|
+
# Inside initialization process, all arguments (except options hash),
|
43
|
+
# passed to constructor will be inspected to satisfy conditions,
|
44
|
+
# specified in `:if` and `:and` options. If this happens, and block
|
45
|
+
# given, it evaluated in context of component instance. If no block
|
46
|
+
# given, setter with `name` will be attempted to set value. In any way
|
47
|
+
# if conditions satisfied, argument removed from future processing.
|
48
|
+
#
|
49
|
+
# If no conditions specified, the `name` of attribute taked as only
|
50
|
+
# condition.
|
51
|
+
#
|
52
|
+
# @example without conditions - name is a condition
|
53
|
+
# class Button < WrapIt::Base
|
54
|
+
# argument(:disabled) { |name, value| puts 'DISABLED' }
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# Button.new(template, :disabled) # => 'DISABLED'
|
58
|
+
# Button.new(template, 'disabled') # => nothing
|
59
|
+
#
|
60
|
+
# @example with conditions and setter
|
61
|
+
# class Button < WrapIt::Base
|
62
|
+
# argument :disabled, if: /^disable(?:d)?$/
|
63
|
+
#
|
64
|
+
# def disabled=(value)
|
65
|
+
# puts 'DISABLED'
|
66
|
+
# end
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# Button.new(template, :disabled) # => 'DISABLED'
|
70
|
+
# Button.new(template, 'disabled') # => 'DISABLED'
|
71
|
+
# Button.new(template, :disable) # => 'DISABLED'
|
72
|
+
# Button.new(template, 'some_text') # => nothing
|
73
|
+
#
|
74
|
+
# @overload argument(name, opts = {}, &block)
|
75
|
+
# @param name [Symbol] unique name, used to refer to this declaration
|
76
|
+
# @param opts [Hash] options
|
77
|
+
# @option opts [Object] :if one or array of conditions that should be
|
78
|
+
# satisfied to capture argument. See {CaptureArray} for details. If
|
79
|
+
# array given, conditions will be or'ed.
|
80
|
+
# @option opts [Object] :and additional one or array of conditions,
|
81
|
+
# that will be and'ed with :if conditions.
|
82
|
+
# @option opts [Boolean] :first_only (false) stop processing on first
|
83
|
+
# match
|
84
|
+
# @option opts [Boolean] :after_options (false) process this argument
|
85
|
+
# after options
|
86
|
+
# @yield [name, value] yields every time argument captured. Evaluated
|
87
|
+
# in instance context
|
88
|
+
# @yieldparam name [Symbol] name of argument, specified in name param
|
89
|
+
# above
|
90
|
+
# @yieldparam value [Object] real argument value
|
91
|
+
#
|
92
|
+
# @return [void]
|
93
|
+
# @since 1.0.0
|
94
|
+
def argument(name, first_only: false, after_options: false,
|
95
|
+
**opts, &block)
|
96
|
+
name.is_a?(String) && name = name.to_sym
|
97
|
+
fail ArgumentError, 'Wrong name' unless name.is_a?(Symbol)
|
98
|
+
arguments[name] = {
|
99
|
+
name: name,
|
100
|
+
conditions: Arguments.make_conditions(name, **opts),
|
101
|
+
block: block,
|
102
|
+
first_only: first_only == true,
|
103
|
+
after_options: after_options == true
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# Desclares option for capturing on initialization process.
|
109
|
+
#
|
110
|
+
# Provides same manner as {#argument} but for hash of options, passed
|
111
|
+
# to constructor. Specified conditions are applied to options keys, not
|
112
|
+
# to values.
|
113
|
+
#
|
114
|
+
# > Hint: you can specify argument and options with same name to call
|
115
|
+
# > same setter.
|
116
|
+
#
|
117
|
+
# @example shared setter
|
118
|
+
# class Button < WrapIt::Base
|
119
|
+
# REGEXP = /^disable(?:d)?$/
|
120
|
+
#
|
121
|
+
# argument :disabled, if: REGEXP
|
122
|
+
# option :disabled, if: %i(disable disabled)
|
123
|
+
#
|
124
|
+
# def disabled=(value)
|
125
|
+
# if value == true || REGEXP =~ value.to_s
|
126
|
+
# puts 'DISABLED'
|
127
|
+
# end
|
128
|
+
# end
|
129
|
+
# end
|
130
|
+
#
|
131
|
+
# Button.new(template, :disabled) # => 'DISABLED'
|
132
|
+
# Button.new(template, 'disabled') # => 'DISABLED'
|
133
|
+
# Button.new(template, :disable) # => 'DISABLED'
|
134
|
+
# Button.new(template, disabled: true) # => 'DISABLED'
|
135
|
+
# Button.new(template, disable: true) # => 'DISABLED'
|
136
|
+
# Button.new(template, disable: false) # => nothing
|
137
|
+
# Button.new(template, 'some_text') # => nothing
|
138
|
+
#
|
139
|
+
# @overload option(name, opts = {}, &block)
|
140
|
+
# @param name [Symbol] unique name, used to refer to this declaration
|
141
|
+
# @param opts [Hash] options
|
142
|
+
# @option opts [Object] :if see
|
143
|
+
# {WrapIt::Arguments::ClassMethods#argument}
|
144
|
+
# @option opts [Object] :and see
|
145
|
+
# {WrapIt::Arguments::ClassMethods#argument}
|
146
|
+
# @yield [name, value] yields every time option captured. Evaluated
|
147
|
+
# in instance context
|
148
|
+
# @yieldparam name [Symbol] name of option, specified in name param
|
149
|
+
# above
|
150
|
+
# @yieldparam value [Object] real option value
|
151
|
+
#
|
152
|
+
# @return [void]
|
153
|
+
# @since 1.0.0
|
154
|
+
def option(name, after: nil, **opts, &block)
|
155
|
+
name.is_a?(String) && name = name.to_sym
|
156
|
+
fail ArgumentError, 'Wrong name' unless name.is_a?(Symbol)
|
157
|
+
@dependencies = !after.nil?
|
158
|
+
options[name] = {
|
159
|
+
name: name,
|
160
|
+
conditions: Arguments.make_conditions(name, **opts),
|
161
|
+
block: block
|
162
|
+
}
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
#
|
167
|
+
# Capture arguments for class and it's ancestors. All captured arguments
|
168
|
+
# and options will be extracted from original `args` argument.
|
169
|
+
#
|
170
|
+
# Actually you rare needs to call this method directly. For example
|
171
|
+
# you can call it in instance
|
172
|
+
# {Arguments#capture_arguments! capture_arguments!}
|
173
|
+
# override to capture arguments for some child components.
|
174
|
+
#
|
175
|
+
# @example capturing arguments for child component
|
176
|
+
# class Button < WrapIt::Base
|
177
|
+
# option(:color) { |name, value| puts "BUTTON COLOR IS: #{value}" }
|
178
|
+
# end
|
179
|
+
#
|
180
|
+
# class Toolbar < WrapIt::Base
|
181
|
+
# protected
|
182
|
+
# def capture_arguments!(args, &block)
|
183
|
+
# @button = Button.new(Button.capture_arguments!(args))
|
184
|
+
# super(args, &block) # ! don't forget to call parent method
|
185
|
+
# end
|
186
|
+
# end
|
187
|
+
#
|
188
|
+
# Toolbar.new(template, color: :red) # => 'BUTTON COLOR IS red'
|
189
|
+
#
|
190
|
+
# @overload capture_arguments!(args, opts = {}, &block)
|
191
|
+
# @param args [Array<Object>] arguments to process (include options)
|
192
|
+
# @param opts [Hash] options
|
193
|
+
# @option opts [Boolean] :inherited (true) process ancestors
|
194
|
+
# @option opts [Base] :instance (nil) if specified valid instance,
|
195
|
+
# all {ClassMethods#argument} and {ClassMethods#option} blocks will
|
196
|
+
# and setters will be called.
|
197
|
+
# @param &block [Proc] block, passed to constructor if present
|
198
|
+
#
|
199
|
+
# @return [Array<Object>] captured arguments
|
200
|
+
def capture_arguments!(args, inherited = true, instance = nil, &block)
|
201
|
+
opts = args.extract_options!
|
202
|
+
if inherited
|
203
|
+
ancestors.take_while { |a| a != Arguments.base }
|
204
|
+
.reverse
|
205
|
+
.unshift(Arguments.base)
|
206
|
+
.map do |a|
|
207
|
+
next unless a.methods.include?(:extract_for_class)
|
208
|
+
a.extract_for_class(args, opts, instance, &block)
|
209
|
+
end
|
210
|
+
result_args = collect_derived(:@provided_arguments, {}, :merge)
|
211
|
+
.values
|
212
|
+
.flatten
|
213
|
+
result_opts = collect_derived(:@provided_options, {}, :merge)
|
214
|
+
.values
|
215
|
+
.reduce({}) { |a, e| a.merge!(e) }
|
216
|
+
else
|
217
|
+
extract_for_class(args, opts, instance, &block)
|
218
|
+
result_args = @provided_arguments.values.flatten
|
219
|
+
result_opts = @provided_options
|
220
|
+
.values
|
221
|
+
.reduce({}) { |a, e| a.merge!(e) }
|
222
|
+
end
|
223
|
+
opts.empty? || args << opts
|
224
|
+
result_opts.empty? || result_args << result_opts
|
225
|
+
result_args
|
226
|
+
end
|
227
|
+
|
228
|
+
protected
|
229
|
+
|
230
|
+
attr_reader :provided_options, :provided_arguments, :provided_block
|
231
|
+
|
232
|
+
def option_provided?(*list)
|
233
|
+
return false if provided_options.nil?
|
234
|
+
if list.empty?
|
235
|
+
return provided_options.empty?
|
236
|
+
else
|
237
|
+
return list.all? do |option|
|
238
|
+
provided_options.key?(option)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def argument_provided?(*list)
|
244
|
+
return false if provided_arguments.nil?
|
245
|
+
if list.empty?
|
246
|
+
return provided_arguments.empty?
|
247
|
+
else
|
248
|
+
return list.all? do |arg|
|
249
|
+
provided_arguments.key?(arg)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def block_provided?
|
255
|
+
provided_block.is_a?(Proc)
|
256
|
+
end
|
257
|
+
|
258
|
+
def options
|
259
|
+
@options ||= {}
|
260
|
+
end
|
261
|
+
|
262
|
+
def arguments
|
263
|
+
@arguments ||= {}
|
264
|
+
end
|
265
|
+
|
266
|
+
def extract_args(args, list, instance = nil)
|
267
|
+
args.respond_to?(:extract!) || args.extend(WrapIt::CaptureArray)
|
268
|
+
list.each do |arg|
|
269
|
+
processed =
|
270
|
+
if arg[:first_only]
|
271
|
+
[args.capture_first!(*arg[:conditions])].compact
|
272
|
+
else
|
273
|
+
args.capture!(*arg[:conditions])
|
274
|
+
end
|
275
|
+
(provided_arguments[arg[:name]] ||= []).concat(processed)
|
276
|
+
next if instance.nil?
|
277
|
+
processed.each do |v|
|
278
|
+
instance.instance_exec(arg[:name], v, arg[:block], &SETTER)
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def extract_opts(opts, instance = nil)
|
284
|
+
keys = opts.keys.extend(WrapIt::CaptureArray)
|
285
|
+
options.each do |name, opt|
|
286
|
+
(provided_options[name] ||= {}).merge!(Hash[
|
287
|
+
keys.capture!(*opt[:conditions])
|
288
|
+
.map do |key|
|
289
|
+
value = opts.delete(key)
|
290
|
+
unless instance.nil?
|
291
|
+
instance.instance_exec(key, value, opt[:block], &SETTER)
|
292
|
+
end
|
293
|
+
[key, value]
|
294
|
+
end
|
295
|
+
])
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def extract_for_class(args, opts, instance = nil, &block)
|
300
|
+
@provided_options = {}
|
301
|
+
@provided_arguments = {}
|
302
|
+
@provided_block = block
|
303
|
+
|
304
|
+
after, before = arguments.values.partition { |x| x[:after_options] }
|
305
|
+
|
306
|
+
extract_args(args, before, instance)
|
307
|
+
extract_opts(opts, instance)
|
308
|
+
extract_args(args, after, instance)
|
309
|
+
|
310
|
+
@provided_block = nil
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
protected
|
315
|
+
|
316
|
+
# @!visibility public
|
317
|
+
#
|
318
|
+
# Captures arguments
|
319
|
+
#
|
320
|
+
# In rare cases you can override this method to control directly arguments
|
321
|
+
# capturing process. Refer to
|
322
|
+
# {ClassMethods#capture_arguments! capture_arguments!} for examples.
|
323
|
+
#
|
324
|
+
# > Note that this method is `protected`, so override should be `protected`
|
325
|
+
# > too.
|
326
|
+
#
|
327
|
+
# @param args [Array<Object>] arguments, passed to constructor
|
328
|
+
# @param block [Proc] block, passed to constructor
|
329
|
+
#
|
330
|
+
# @return [Array<Object>] captured arguments
|
331
|
+
def capture_arguments!(args, &block)
|
332
|
+
self.class.capture_arguments!(args, true, self, &block)
|
333
|
+
end
|
334
|
+
|
335
|
+
private
|
336
|
+
|
337
|
+
#
|
338
|
+
# Evaluated in instance context by .extract_for_class
|
339
|
+
#
|
340
|
+
SETTER = ->(name, value, block) do
|
341
|
+
if block.nil?
|
342
|
+
setter = "#{name}=".to_sym
|
343
|
+
respond_to?(setter) && send(setter, value)
|
344
|
+
else
|
345
|
+
instance_exec(name, value, &block)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
def self.normalize_conditions(cond)
|
350
|
+
if cond.is_a?(Array) &&
|
351
|
+
cond.any? { |x| !x.is_a?(Symbol) && !x.is_a?(String) }
|
352
|
+
cond
|
353
|
+
else
|
354
|
+
[cond]
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
def self.make_conditions(name, **opts)
|
359
|
+
cond = normalize_conditions(opts.key?(:if) ? opts[:if] : name)
|
360
|
+
opts.key?(:and) && cond << {and: normalize_conditions(opts[:and])}
|
361
|
+
cond
|
362
|
+
end
|
363
|
+
|
364
|
+
def self.base
|
365
|
+
@base
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|