mimi-core 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/.yardopts +1 -0
- data/README.md +199 -2
- data/lib/mimi/core/core_ext.rb +89 -2
- data/lib/mimi/core/inheritable_property.rb +173 -0
- data/lib/mimi/core/manifest.rb +538 -0
- data/lib/mimi/core/module.rb +80 -16
- data/lib/mimi/core/rake.rb +46 -0
- data/lib/mimi/core/version.rb +1 -1
- data/lib/mimi/core.rb +32 -2
- data/mimi-core.gemspec +1 -2
- metadata +8 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4386bcac3a69eeb1f5a0422c445ba509e735ea4a
|
4
|
+
data.tar.gz: 6c583235d2e0069d255c0598b3e2cce4f4631bcb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d234685fd746173702527047e71aaa0c0d237da588e84b1de2f0fa23ecd74cca832569b91729af0e5968e53807d06d6eafb6077c73ac73d454cdfaa9ea041e92
|
7
|
+
data.tar.gz: db3ece3e5d363f942ebd3d98ff92b2c286fc198f74261378dcee0d1b67e38d4dd3f88fbbb443e3f2c648623dc85c2eefd679f6fc59a94f16f65bd8cf4e35533a
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup markdown
|
data/README.md
CHANGED
@@ -1,8 +1,205 @@
|
|
1
1
|
# mimi-core
|
2
2
|
|
3
|
-
Core module
|
3
|
+
Core module of the [Mimi framework](Mimi).
|
4
4
|
|
5
|
-
|
5
|
+
`mimi-core` defines the top-level Mimi namespace, provides a base for Mimi modules
|
6
|
+
and offers some core extentions.
|
7
|
+
|
8
|
+
|
9
|
+
## Mimi namespace
|
10
|
+
|
11
|
+
At the top of hierarchy of the Mimi components there is a `Mimi` module that binds all components of the
|
12
|
+
framework together. It allows Mimi modules to be used (included in the stack and configured), started
|
13
|
+
and stopped.
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
require 'mimi/core'
|
17
|
+
|
18
|
+
Mimi # => Module
|
19
|
+
```
|
20
|
+
|
21
|
+
### Mimi.use(mod)
|
22
|
+
|
23
|
+
Configures the given module and appends it to the list of **used modules**.
|
24
|
+
|
25
|
+
### Mimi.start
|
26
|
+
|
27
|
+
Starts the used modules in the order of appending.
|
28
|
+
|
29
|
+
### Mimi.stop
|
30
|
+
|
31
|
+
Stops the used modules in reverse order.
|
32
|
+
|
33
|
+
### Example
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
require 'mimi/db'
|
37
|
+
|
38
|
+
Mimi.use Mimi::DB, db_adapter: 'sqlite', db_database: 'tmp/dev.db'
|
39
|
+
Mimi.start
|
40
|
+
|
41
|
+
Mimi.app_root_path # => <Pathname> -- guesses current application's root
|
42
|
+
```
|
43
|
+
|
44
|
+
## Mimi::Core::Module
|
45
|
+
|
46
|
+
`Mimi::Core::Module` is a base for any Mimi module.
|
47
|
+
|
48
|
+
Mimi modules are pluggable components that you can include in your applications.
|
49
|
+
They provide some useful functionality, e.g. database abstraction layer, logging etc.
|
50
|
+
|
51
|
+
Any Mimi module provides a standard list of methods to interact with it:
|
52
|
+
|
53
|
+
```
|
54
|
+
.configure(opts)
|
55
|
+
.start
|
56
|
+
.stop
|
57
|
+
```
|
58
|
+
|
59
|
+
A Mimi module is typically packaged as a gem and it can declare a `module_root_path`.
|
60
|
+
Under this path the module may export some files, related to the module's offered functionality.
|
61
|
+
For example `Mimi::DB` exposes some rake tasks to perform database clearing, migrations etc.
|
62
|
+
|
63
|
+
|
64
|
+
### Creating a new module
|
65
|
+
|
66
|
+
A Mimi component module can be created by including `Mimi::Core::Module` in a module or class
|
67
|
+
of yours:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
# my_module.rb
|
71
|
+
|
72
|
+
require 'mimi-core'
|
73
|
+
|
74
|
+
module MyModule
|
75
|
+
include Mimi::Core::Module
|
76
|
+
end
|
77
|
+
|
78
|
+
Mimi.use MyModule, param: 1 # invokes MyModule.configure(param: 1)
|
79
|
+
Mimi.start # invokes MyModule.start
|
80
|
+
```
|
81
|
+
|
82
|
+
|
83
|
+
### Configuring a module
|
84
|
+
|
85
|
+
A Mimi module implements a `.manifest`, `.configure` and `.options` class methods.
|
86
|
+
|
87
|
+
The `.configure` class method accepts a Hash of configurable parameter values and sets the options Hash
|
88
|
+
available via `.options` class method. Which parameters can be set and whether there are any default
|
89
|
+
values for the parameters is defined by the module *manifest*.
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
Mimi.configure(param: 1)
|
93
|
+
Mimi.options[:param] # => 1
|
94
|
+
```
|
95
|
+
|
96
|
+
### Module manifest
|
97
|
+
|
98
|
+
The original `.configure` implementation in `Mimi::Core::Module` takes into consideration the
|
99
|
+
module *manifest*.
|
100
|
+
|
101
|
+
Module manifest is a set of formal definitions of configurable parameters.
|
102
|
+
|
103
|
+
The manifest declares which configurable parameters are accepted by the module,
|
104
|
+
which types of values are accepted, which are default values and so on.
|
105
|
+
|
106
|
+
When designing your own module, you should redefine `.manifest` method to contain
|
107
|
+
your module's actual manifest. The `.manifest` class method should return a Hash representation of the module manifest,
|
108
|
+
where keys define the configurable parameter names, and values are Hash-es with the configurable parameter
|
109
|
+
properties:
|
110
|
+
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
# my_module.rb
|
114
|
+
|
115
|
+
require 'mimi-core'
|
116
|
+
|
117
|
+
module MyModule
|
118
|
+
include Mimi::Core::Module
|
119
|
+
|
120
|
+
def self.manifest
|
121
|
+
{
|
122
|
+
param1: {} # minimal definition of a configurable parameter
|
123
|
+
}
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
Mimi.configure(param: 1)
|
128
|
+
Mimi.options[:param] # => 1
|
129
|
+
```
|
130
|
+
|
131
|
+
Recognised configurable parameter properties are:
|
132
|
+
|
133
|
+
* `:desc` -- (default: `nil`) human readable description
|
134
|
+
* `:type` -- (default: `:string`) accepted value type
|
135
|
+
* `:default` -- (default: `nil`) default value for configurable parameter
|
136
|
+
* `:const` -- (default: `false`) makes the configurable parameter constant
|
137
|
+
* `:hidden` -- (default: `false`) omits the configurable parameter from a combined manifest
|
138
|
+
|
139
|
+
|
140
|
+
Example:
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
def self.manifest
|
144
|
+
{
|
145
|
+
param1: {
|
146
|
+
desc: 'My configurable param1',
|
147
|
+
type: :integer,
|
148
|
+
default: 1
|
149
|
+
},
|
150
|
+
}
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
154
|
+
@see Mimi::Core::Manifest
|
155
|
+
|
156
|
+
## Useful extensions
|
157
|
+
|
158
|
+
`mimi-core` gem contains some useful extensions which are included automatically, once the gem
|
159
|
+
is *require*-d:
|
160
|
+
|
161
|
+
* Mimi::Core::InheritableProperty
|
162
|
+
* Array and Hash extensions
|
163
|
+
|
164
|
+
### Mimi::Core::InheritableProperty
|
165
|
+
|
166
|
+
Makes `.inheritable_property` method available to the class.
|
167
|
+
|
168
|
+
Example:
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
class A
|
172
|
+
include Mimi::Core::InheritableProperty
|
173
|
+
|
174
|
+
inheritable_property :my_var
|
175
|
+
end
|
176
|
+
```
|
177
|
+
@see Mimi::Core::InheritableProperty::ClassMethods#inheritable_property
|
178
|
+
|
179
|
+
|
180
|
+
### Array and Hash extensions
|
181
|
+
|
182
|
+
`Array` extensions:
|
183
|
+
|
184
|
+
```
|
185
|
+
Array#only(*values) # select array elements only if listed in 'values'
|
186
|
+
Array#only!(*values) # as above, modify array in-place
|
187
|
+
Array#except(*values) # reject array elements listed in 'values'
|
188
|
+
Array#except!(*values) # as above, modify array in-place
|
189
|
+
```
|
190
|
+
|
191
|
+
`Hash` extensions:
|
192
|
+
|
193
|
+
```
|
194
|
+
Hash#only(*keys) # keep only those key-value pairs of the Hash which keys are listed in 'keys'
|
195
|
+
Hash#only!(*keys) # as above, modify Hash in-place
|
196
|
+
Hash#except(*keys) # reject key-value pairs of the Hash which keys are listed in 'keys'
|
197
|
+
Hash#except!(*keys) # as above, modify Hash in-place
|
198
|
+
Hash#deep_merge(other) # deep-merges another Hash
|
199
|
+
Hash#deep_dup # deep-duplicates a Hash, taking care of nested Hashes and Arrays
|
200
|
+
Hash#deep_symbolize_keys
|
201
|
+
Hash#deep_stringify_keys
|
202
|
+
```
|
6
203
|
|
7
204
|
## License
|
8
205
|
|
data/lib/mimi/core/core_ext.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
3
|
require 'hashie'
|
4
4
|
|
5
5
|
class Hash
|
@@ -9,6 +9,10 @@ class Hash
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def only!(*keys)
|
12
|
+
if keys.size == 1 && keys.first.is_a?(Array)
|
13
|
+
raise ArgumentError, 'Hash#only!() expects keys as list of arguments,' \
|
14
|
+
' not an Array as first argument'
|
15
|
+
end
|
12
16
|
select! { |k, _| keys.include?(k) }
|
13
17
|
self
|
14
18
|
end
|
@@ -20,6 +24,10 @@ class Hash
|
|
20
24
|
end
|
21
25
|
|
22
26
|
def except!(*keys)
|
27
|
+
if keys.size == 1 && keys.first.is_a?(Array)
|
28
|
+
raise ArgumentError, 'Hash#except!() expects keys as list of arguments,' \
|
29
|
+
' not an Array as first argument'
|
30
|
+
end
|
23
31
|
reject! { |k, _| keys.include?(k) }
|
24
32
|
self
|
25
33
|
end
|
@@ -28,6 +36,69 @@ class Hash
|
|
28
36
|
unless instance_methods(false).include?(:deep_merge)
|
29
37
|
include Hashie::Extensions::DeepMerge
|
30
38
|
end
|
39
|
+
|
40
|
+
unless instance_methods(false).include?(:deep_dup)
|
41
|
+
def deep_dup
|
42
|
+
map do |k, v|
|
43
|
+
v = v.respond_to?(:deep_dup) ? v.deep_dup : v.dup
|
44
|
+
[k, v]
|
45
|
+
end.to_h
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
unless instance_methods(false).include?(:symbolize_keys)
|
50
|
+
def symbolize_keys
|
51
|
+
map do |k, v|
|
52
|
+
k = k.respond_to?(:to_sym) ? k.to_sym : k
|
53
|
+
[k, v]
|
54
|
+
end.to_h
|
55
|
+
end
|
56
|
+
|
57
|
+
def symbolize_keys!
|
58
|
+
replace(symbolize_keys)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
unless instance_methods(false).include?(:deep_symbolize_keys)
|
63
|
+
def deep_symbolize_keys
|
64
|
+
map do |k, v|
|
65
|
+
k = k.respond_to?(:to_sym) ? k.to_sym : k
|
66
|
+
v = v.respond_to?(:deep_symbolize_keys) ? v.deep_symbolize_keys : v
|
67
|
+
[k, v]
|
68
|
+
end.to_h
|
69
|
+
end
|
70
|
+
|
71
|
+
def deep_symbolize_keys!
|
72
|
+
replace(deep_symbolize_keys)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
unless instance_methods(false).include?(:stringify_keys)
|
77
|
+
def stringify_keys
|
78
|
+
map do |k, v|
|
79
|
+
k = k.respond_to?(:to_s) ? k.to_s : k
|
80
|
+
[k, v]
|
81
|
+
end.to_h
|
82
|
+
end
|
83
|
+
|
84
|
+
def stringify_keys!
|
85
|
+
replace(stringify_keys)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
unless instance_methods(false).include?(:deep_stringify_keys)
|
90
|
+
def deep_stringify_keys
|
91
|
+
map do |k, v|
|
92
|
+
k = k.respond_to?(:to_s) ? k.to_s : k
|
93
|
+
v = v.respond_to?(:deep_stringify_keys) ? v.deep_stringify_keys : v
|
94
|
+
[k, v]
|
95
|
+
end.to_h
|
96
|
+
end
|
97
|
+
|
98
|
+
def deep_stringify_keys!
|
99
|
+
replace(deep_stringify_keys)
|
100
|
+
end
|
101
|
+
end
|
31
102
|
end
|
32
103
|
|
33
104
|
class Array
|
@@ -37,6 +108,10 @@ class Array
|
|
37
108
|
end
|
38
109
|
|
39
110
|
def only!(*keys)
|
111
|
+
if keys.size == 1 && keys.first.is_a?(Array)
|
112
|
+
raise ArgumentError, 'Array#only!() expects keys as list of arguments,' \
|
113
|
+
' not an Array as first argument'
|
114
|
+
end
|
40
115
|
select! { |k| keys.include?(k) }
|
41
116
|
self
|
42
117
|
end
|
@@ -48,8 +123,20 @@ class Array
|
|
48
123
|
end
|
49
124
|
|
50
125
|
def except!(*keys)
|
126
|
+
if keys.size == 1 && keys.first.is_a?(Array)
|
127
|
+
raise ArgumentError, 'Array#except!() expects keys as list of arguments,' \
|
128
|
+
' not an Array as first argument'
|
129
|
+
end
|
51
130
|
reject! { |k| keys.include?(k) }
|
52
131
|
self
|
53
132
|
end
|
54
133
|
end
|
134
|
+
|
135
|
+
unless instance_methods(false).include?(:deep_dup)
|
136
|
+
def deep_dup
|
137
|
+
map do |v|
|
138
|
+
v.respond_to?(:deep_dup) ? v.deep_dup : v.dup
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
55
142
|
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mimi
|
4
|
+
module Core
|
5
|
+
#
|
6
|
+
# Makes `.inheritable_property` method available to the class.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# ```
|
11
|
+
# class A
|
12
|
+
# include Mimi::Core::InheritableProperty
|
13
|
+
#
|
14
|
+
# inheritable_property :my_var
|
15
|
+
# end
|
16
|
+
# ```
|
17
|
+
# @see Mimi::Core::InheritableProperty::ClassMethods#inheritable_property
|
18
|
+
#
|
19
|
+
module InheritableProperty
|
20
|
+
def self.included(base)
|
21
|
+
base.send :extend, ClassMethods
|
22
|
+
end
|
23
|
+
|
24
|
+
module ClassMethods
|
25
|
+
#
|
26
|
+
# Declares an inheritable property.
|
27
|
+
#
|
28
|
+
# The inheritable property is a special class variable, that is accessible
|
29
|
+
# in descendant classes, but each of the descendant classes can alter only its local value.
|
30
|
+
#
|
31
|
+
# Once a property is declared, a class method with the name of the property
|
32
|
+
# becomes available.
|
33
|
+
#
|
34
|
+
# ```
|
35
|
+
# class A
|
36
|
+
# include Mimi::Core::InheritableProperty
|
37
|
+
#
|
38
|
+
# inheritable_property :var1, default: 1
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# class B < A
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# A.var1 # => 1
|
45
|
+
# B.var1 # => 1
|
46
|
+
# ```
|
47
|
+
#
|
48
|
+
# A class method with the name of the property accepts one optional argument -- a new value
|
49
|
+
# for the property. If the argument is omitted, current inherited value is returned.
|
50
|
+
#
|
51
|
+
# If the argument is present, it sets a new property value for this class
|
52
|
+
# and its subclasses, but not for the parent class.
|
53
|
+
#
|
54
|
+
# ```
|
55
|
+
# class A
|
56
|
+
# include Mimi::Core::InheritableProperty
|
57
|
+
#
|
58
|
+
# inheritable_property :var1, default: 1
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# class B < A
|
62
|
+
# var1 123 # sets new value for B.var1
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# class C < B
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# A.var1 # => 1, the default value, unchanged
|
69
|
+
# B.var1 # => 123
|
70
|
+
# C.var1 # => 123
|
71
|
+
# ```
|
72
|
+
#
|
73
|
+
# Working with Hash values
|
74
|
+
# ===
|
75
|
+
# An `inherited_property` can be declared having the type `:hash`, then the inherited
|
76
|
+
# values are deep-merged in subclasses.
|
77
|
+
#
|
78
|
+
# ```
|
79
|
+
# class A
|
80
|
+
# include Mimi::Core::InheritableProperty
|
81
|
+
#
|
82
|
+
# inheritable_property :var1, default: { a: 1 }
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
# class B < A
|
86
|
+
# var1 b: 2
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# class C < B
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# A.var1 # => { a: 1 }
|
93
|
+
# B.var1 # => { a: 1, b: 2 }
|
94
|
+
# C.var1 # => { a: 1, b: 2 }
|
95
|
+
# ```
|
96
|
+
#
|
97
|
+
# Proc as default value
|
98
|
+
# ===
|
99
|
+
# A Proc can be specified instead of literal default value, in which case it will
|
100
|
+
# be evaluated when the inherited property value is queried. The passed block will
|
101
|
+
# be evaluated in the context of the current class.
|
102
|
+
#
|
103
|
+
# ```
|
104
|
+
# class A
|
105
|
+
# include Mimi::Core::InheritableProperty
|
106
|
+
#
|
107
|
+
# inheritable_property :var1, default: -> { self.name }
|
108
|
+
# end
|
109
|
+
#
|
110
|
+
# class B < A
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# class C < B
|
114
|
+
# end
|
115
|
+
#
|
116
|
+
# A.var1 # => "A"
|
117
|
+
# B.var1 # => "B"
|
118
|
+
# ```
|
119
|
+
#
|
120
|
+
# @param name [Symbol] a name for the new inheritable property
|
121
|
+
# @param opts [Hash,nil] optional parameters for the inheritable property
|
122
|
+
# @option opts [Object,Proc] :default specifies the literal or Proc as the default value
|
123
|
+
# for the property
|
124
|
+
# @option opts [:hash,nil] :type instructs that the property is a Hash or a simple value
|
125
|
+
# (inherited values are deep-merged for Hash'es)
|
126
|
+
#
|
127
|
+
def inheritable_property(name, opts = {})
|
128
|
+
unless name.is_a?(Symbol)
|
129
|
+
raise ArgumentError, 'Symbol is expected as inheritable_property name'
|
130
|
+
end
|
131
|
+
var_name = :"@#{name}"
|
132
|
+
ip_get_name = :"inheritable_property_get_#{name}"
|
133
|
+
ip_set_name = :"inheritable_property_set_#{name}"
|
134
|
+
opts_default = opts[:default] && opts[:default].dup
|
135
|
+
|
136
|
+
define_singleton_method(ip_get_name) do |caller|
|
137
|
+
if opts[:type] == :hash
|
138
|
+
# combine this class' value with a superclass' value
|
139
|
+
self_value = instance_variable_get(var_name) || {}
|
140
|
+
super_value =
|
141
|
+
(superclass.respond_to?(ip_get_name) && superclass.send(ip_get_name, caller)) ||
|
142
|
+
(opts_default.is_a?(Proc) ? caller.instance_exec(&opts_default) : opts_default)
|
143
|
+
super_value.deep_merge(self_value)
|
144
|
+
else
|
145
|
+
instance_variable_get(var_name) ||
|
146
|
+
(superclass.respond_to?(ip_get_name) && superclass.send(ip_get_name, caller)) ||
|
147
|
+
(opts_default.is_a?(Proc) ? caller.instance_exec(&opts_default) : opts_default)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
define_singleton_method(ip_set_name) do |arg|
|
152
|
+
if opts[:type] == :hash && !arg.is_a?(Hash) && !arg.nil?
|
153
|
+
raise ArgumentError, "Hash or nil is expected as a value for property #{self}.#{name}"
|
154
|
+
end
|
155
|
+
instance_variable_set(var_name, arg)
|
156
|
+
end
|
157
|
+
|
158
|
+
define_singleton_method(name) do |*args|
|
159
|
+
if args.empty?
|
160
|
+
send(:"inheritable_property_get_#{name}", self)
|
161
|
+
else
|
162
|
+
send(:"inheritable_property_set_#{name}", args.first)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
define_singleton_method("#{name}!".to_sym) do
|
167
|
+
send(name) || (raise "Property #{self}.#{name} is not set")
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end # module ClassMethods
|
171
|
+
end # module InheritableProperty
|
172
|
+
end # module Core
|
173
|
+
end # module Mimi
|