alki 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +0 -1
- data/README.ad +12 -0
- data/doc/assemblies.ad +138 -0
- data/doc/assembly_dsl.ad +242 -0
- data/doc/index.ad +15 -0
- data/doc/projects.ad +43 -0
- data/lib/alki/assembly.rb +6 -27
- data/lib/alki/assembly_builder.rb +8 -1
- data/lib/alki/assembly_executor.rb +19 -26
- data/lib/alki/bin.rb +5 -0
- data/lib/alki/dsls/assembly.rb +1 -1
- data/lib/alki/dsls/assembly_types/assembly.rb +16 -7
- data/lib/alki/dsls/assembly_types/group.rb +1 -6
- data/lib/alki/override_builder.rb +35 -0
- data/lib/alki/version.rb +1 -1
- data/test/feature/overrides_test.rb +39 -7
- metadata +9 -3
- data/README.md +0 -55
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 31a32ce28977b35ce72bab1992d4512c7ea33f44
|
4
|
+
data.tar.gz: c6ab121484e99b437aaae6390db983d83a011cc7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd1da1b284b8caa1d75a32ac5e199f5f64e504213de1abeedb694e6f6fca517633992b8f2572129b71d329ed0069de2d698b89c0dd2f395a72d49243dbe2032c
|
7
|
+
data.tar.gz: fd7e999ffc561b91e6452bd859f8f8783fd0a90f097a703febd7746d837227717fa2bfafdaac310903eecef08090d1b2fd834444682b49a38ccdb37dac404a10
|
data/.gitignore
CHANGED
data/README.ad
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# What is Alki?
|
2
|
+
|
3
|
+
Alki is a framework for creating projects that are modular, testable, and well organized.
|
4
|
+
|
5
|
+
It's goal is to remove uncertainty and friction when building Ruby projects, allowing developers to focus on implementing business logic.
|
6
|
+
|
7
|
+
# Synopsis
|
8
|
+
|
9
|
+
Best place to start would be to check out some examples:
|
10
|
+
|
11
|
+
* https://github.com/alki-project/alki-example
|
12
|
+
* https://github.com/alki-project/alki/tree/master/test/fixtures/example
|
data/doc/assemblies.ad
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
Assemblies
|
2
|
+
==========
|
3
|
+
|
4
|
+
Project Assemblies
|
5
|
+
------------------
|
6
|
+
|
7
|
+
Most of the time, a project will have a single assembly. Alki makes doing this easy.
|
8
|
+
|
9
|
+
First. in your project's `lib` directory create a ruby file for your assembly. If your assembly is
|
10
|
+
to be called `MyAssembly`, create `lib/my_assembly.rb`. If it's namespaced but it in a subdirectory
|
11
|
+
as usual, `MyModule::MyAssembly` would go in `lib/my_module/my_assembly.rb`
|
12
|
+
|
13
|
+
Your assembly file just needs two lines:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
require 'alki'
|
17
|
+
Alki.project_assembly!
|
18
|
+
```
|
19
|
+
|
20
|
+
It will detect the project root and what class name it should create automatically.
|
21
|
+
|
22
|
+
Second, a `config` directory must be created in the project root, and in that directory an `assembly.rb`
|
23
|
+
file should be created. It should be a normal Alki loader block.
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
Alki do
|
27
|
+
# Assembly DSL ...
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
`Alki.project_assembly!` defaults can be overridden by passing in the following keyword arguments:
|
32
|
+
|
33
|
+
[horizontal]
|
34
|
+
name:: Set the name of the assembly. Should be formatted how you would put it in a `require` call
|
35
|
+
(e.g. if `MyModule::MyAssembly` is desired, use `'my_module/my_assembly'`). Default is
|
36
|
+
determined from the filename of the caller.
|
37
|
+
|
38
|
+
config_dir:: Set directory where assembly config files are found. Default is `<project root>/config`.
|
39
|
+
|
40
|
+
primary_config:: Sets the name of the main config file to load. Defaults to `assembly.rb`.
|
41
|
+
|
42
|
+
Manually Creating Assemblies
|
43
|
+
----------------------------
|
44
|
+
|
45
|
+
In addition to Project Assemblies, you can also create assemblies directly using `Alki.create_assembly`.
|
46
|
+
It should be called with a block that contains the assembly DSL. It will return a Module object that can
|
47
|
+
be used directly or assigned to a constant.
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
assembly = Alki.create_assembly do
|
51
|
+
set :msg, "hello world"
|
52
|
+
func :run do
|
53
|
+
puts msg
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
assembly.new.run
|
58
|
+
|
59
|
+
# output: hello world
|
60
|
+
```
|
61
|
+
|
62
|
+
Using Assemblies
|
63
|
+
----------------
|
64
|
+
|
65
|
+
Assemblies have a `new` method used to create new assembly instances (like a normal class). Once an
|
66
|
+
instance is created, anything in the assembly is accessible by drilling down into groups.
|
67
|
+
|
68
|
+
Given an assembly like this:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
require 'alki'
|
72
|
+
assembly = Alki.create_assembly do
|
73
|
+
set :log_io, STDERR
|
74
|
+
group :util do
|
75
|
+
service :logger do
|
76
|
+
require 'logger'
|
77
|
+
Logger.new log_io
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
Once can use the logger service like so:
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
instance = assembly.new
|
87
|
+
instance.util.logger.info "test"
|
88
|
+
|
89
|
+
# output: I, [<timestamp>] INFO -- : test
|
90
|
+
```
|
91
|
+
|
92
|
+
### Overrides
|
93
|
+
|
94
|
+
Assembly overrides provide a way to do configure or customize an Assembly when
|
95
|
+
constructing an instance.
|
96
|
+
|
97
|
+
For example, using the assembly created above, one might want to change the IO object logged to.
|
98
|
+
|
99
|
+
The simplist way to do this is to provide a hash of values which will set values (as if the `set`
|
100
|
+
command was called in the DSL):
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
require 'stringio'
|
104
|
+
io = StringIO.new
|
105
|
+
instance = assembly.new log_io: io
|
106
|
+
instance.util.logger.info "test"
|
107
|
+
puts(io.string.match(/INFO -- : test/) != nil)
|
108
|
+
|
109
|
+
# output: true
|
110
|
+
```
|
111
|
+
|
112
|
+
The limitiation of this is that it can only override basic values. To override more complex elements
|
113
|
+
a block can be given to `new` allowing the full assembly DSL.
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
class MyLogger
|
117
|
+
def initialize(io)
|
118
|
+
@io = io
|
119
|
+
end
|
120
|
+
def info(msg)
|
121
|
+
@io.puts "INFO #{msg}"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
instance = assembly.new do
|
126
|
+
group :util do
|
127
|
+
service :logger do
|
128
|
+
MyLogger.new original.log_io
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
instance.util.logger.info "test"
|
133
|
+
|
134
|
+
# output: INFO test
|
135
|
+
```
|
136
|
+
|
137
|
+
One thing of note is that elements from the assembly are accessibly via the `original` method.
|
138
|
+
This can also be used to access the original versions of elements that have been overriden.
|
data/doc/assembly_dsl.ad
ADDED
@@ -0,0 +1,242 @@
|
|
1
|
+
Assembly DSL
|
2
|
+
===========
|
3
|
+
|
4
|
+
Building Assemblies is done via a DSL that makes putting together the pieces of your project easy.
|
5
|
+
|
6
|
+
Groups
|
7
|
+
------
|
8
|
+
|
9
|
+
Groups are the basic way of organizing the elements of your Assembly. Creating elements at the top
|
10
|
+
level of an Assembly will place them in a root group, but subgroups are easily created via the `group`
|
11
|
+
command.
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
require 'alki'
|
15
|
+
assembly = Alki.create_assembly do
|
16
|
+
group :sub_group do
|
17
|
+
set :val, "hello world"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
puts assembly.new.sub_group.val
|
21
|
+
```
|
22
|
+
|
23
|
+
Scoping is also done by group, so that an element will be found by moving up parent groups until
|
24
|
+
it is found.
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
require 'alki'
|
28
|
+
assembly = Alki.create_assembly do
|
29
|
+
set :val, "one"
|
30
|
+
group :g1 do
|
31
|
+
set :val, "two"
|
32
|
+
group :g2 do
|
33
|
+
func :print do
|
34
|
+
puts val
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
assembly.new.g1.g2.print
|
40
|
+
|
41
|
+
#output: two
|
42
|
+
```
|
43
|
+
|
44
|
+
=== Loading groups
|
45
|
+
|
46
|
+
Groups can also be loaded from other config files via the `load` command.
|
47
|
+
|
48
|
+
.config/settings.rb
|
49
|
+
```ruby
|
50
|
+
Alki do
|
51
|
+
set :val, "hello"
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
require 'alki'
|
57
|
+
assembly = Alki.create_assembly config_dir: 'config' do
|
58
|
+
load :settings
|
59
|
+
func :print do
|
60
|
+
puts settings.val
|
61
|
+
end
|
62
|
+
end
|
63
|
+
assembly.new.print
|
64
|
+
|
65
|
+
# output: hello
|
66
|
+
```
|
67
|
+
|
68
|
+
|
69
|
+
Values
|
70
|
+
------
|
71
|
+
|
72
|
+
There are four types of elements loosely categorized as "values".
|
73
|
+
|
74
|
+
### Basic Values (set)
|
75
|
+
|
76
|
+
The simplest type of values are ones created via the `set` command. There are two forms of `set`.
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
require 'alki'
|
80
|
+
assembly = Alki.create_assembly do
|
81
|
+
# This form takes the value as the second argument
|
82
|
+
set :val1, "hello"
|
83
|
+
|
84
|
+
# INVALID! Value may not be a reference to another element
|
85
|
+
# set :val2, val1
|
86
|
+
|
87
|
+
# This form takes the value as a block.
|
88
|
+
# Block is run once and result cached.
|
89
|
+
# Allows referencing other elements
|
90
|
+
set :val2 do
|
91
|
+
val1
|
92
|
+
end
|
93
|
+
end
|
94
|
+
puts assembly.new.val2
|
95
|
+
|
96
|
+
#output: hello
|
97
|
+
```
|
98
|
+
|
99
|
+
### Functions (func)
|
100
|
+
|
101
|
+
Simple callable values can be created with the `func` command. These can take arguments, are run
|
102
|
+
each time they are referenced, and can access other elements.
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
require 'alki'
|
106
|
+
assembly = Alki.create_assembly do
|
107
|
+
set :greeting, "Hello %s!"
|
108
|
+
|
109
|
+
func :greet do |name|
|
110
|
+
puts(greeting % [name])
|
111
|
+
end
|
112
|
+
end
|
113
|
+
puts assembly.new.greet "Matt"
|
114
|
+
|
115
|
+
#output: Hello Matt!
|
116
|
+
```
|
117
|
+
|
118
|
+
### Services (service)
|
119
|
+
|
120
|
+
Services are the key element Assemblies are typically made up of. Like the block form of `set`,
|
121
|
+
`service` takes a name and block, which will be evaluated once on-demand and the result cached.
|
122
|
+
One difference between the block form of `set` and `service` is that services are affected
|
123
|
+
by overlays, whereas basic values are not.
|
124
|
+
|
125
|
+
Commonly a service will require the file that defines a class, and then constructs an instance of
|
126
|
+
that class.
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
require 'alki'
|
130
|
+
assembly = Alki.create_assembly do
|
131
|
+
service :logger do
|
132
|
+
require 'logger'
|
133
|
+
Logger.new STDOUT
|
134
|
+
end
|
135
|
+
end
|
136
|
+
assembly.new.logger << "hello"
|
137
|
+
|
138
|
+
#output: hello
|
139
|
+
```
|
140
|
+
|
141
|
+
### Factories (factory)
|
142
|
+
|
143
|
+
Factories are a mix between services and funcs. Like services, they take a block which is evaluated
|
144
|
+
once. Unlike services though, that block must return a callable object (like a Proc). This object
|
145
|
+
is then called directly when the factory is referenced. This allows you to require files or construct
|
146
|
+
a factory object once but still run code on every reference.
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
require 'alki'
|
150
|
+
assembly = Alki.create_assembly do
|
151
|
+
factory :logger do
|
152
|
+
require 'logger'
|
153
|
+
-> (io) { Logger.new io }
|
154
|
+
end
|
155
|
+
|
156
|
+
service :main_logger do
|
157
|
+
logger STDOUT
|
158
|
+
end
|
159
|
+
end
|
160
|
+
assembly.new.main_logger << "hello"
|
161
|
+
|
162
|
+
#output: hello
|
163
|
+
```
|
164
|
+
|
165
|
+
### Overlays (overlay)
|
166
|
+
|
167
|
+
Overlays are a way to intercept and transform calls made to all services in a given group or it's
|
168
|
+
sub-groups.
|
169
|
+
|
170
|
+
Overlays are often most useful in groups where all services adhere to a common interface, and overlays
|
171
|
+
can be used to perform aspect oriented programming like logging, validation, or access controls.
|
172
|
+
|
173
|
+
|
174
|
+
### Assemblies (assembly)
|
175
|
+
|
176
|
+
Other assemblies can be mounted into your Assembly using the `assembly` command.
|
177
|
+
|
178
|
+
The first argument is what it should be named in your assembly. The optional second argument
|
179
|
+
is the name of the assembly. This should be formatted like a require string (relative path but
|
180
|
+
no `.rb`) and will default to the value of the first argument. If a classified version of that name
|
181
|
+
can't be found, it will attempt to `require` it, and then try again.
|
182
|
+
|
183
|
+
```ruby
|
184
|
+
require 'alki'
|
185
|
+
Alki.create_assembly name: 'other_assembly' do
|
186
|
+
set :val, "one"
|
187
|
+
|
188
|
+
set :invalid_val2 do
|
189
|
+
val2
|
190
|
+
end
|
191
|
+
|
192
|
+
set :root_val2 do
|
193
|
+
root.val2
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
Alki.create_assembly name: 'main_assembly' do
|
198
|
+
set :val2, "two"
|
199
|
+
assembly :other, 'other_assembly'
|
200
|
+
end
|
201
|
+
instance = MainAssembly.new
|
202
|
+
puts instance.other.val
|
203
|
+
#output: one
|
204
|
+
|
205
|
+
# Can't access val2 in main assembly
|
206
|
+
begin
|
207
|
+
puts instance.other.invalid_val2
|
208
|
+
rescue => e
|
209
|
+
puts e
|
210
|
+
end
|
211
|
+
# output: undefined local variable or method 'val2'
|
212
|
+
|
213
|
+
# This works, because root returns the root assembly
|
214
|
+
puts instance.other.root_val2
|
215
|
+
#output: two
|
216
|
+
```
|
217
|
+
|
218
|
+
In addition, `assembly` takes an optional third hash argument or a block which can be used to set
|
219
|
+
overrides in the same way `::new` does for assemblies. Elements from the parent assembly are
|
220
|
+
automatically in scope for overrides.
|
221
|
+
|
222
|
+
```ruby
|
223
|
+
require 'alki'
|
224
|
+
Alki.create_assembly name: 'other_assembly' do
|
225
|
+
set :msg, nil
|
226
|
+
func :print do
|
227
|
+
puts msg
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
Alki.create_assembly name: 'main_assembly' do
|
232
|
+
set :val, "hello"
|
233
|
+
assembly :other, 'other_assembly' do
|
234
|
+
set :msg do
|
235
|
+
val
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
MainAssembly.new.other.print
|
240
|
+
|
241
|
+
#output: hello
|
242
|
+
```
|
data/doc/index.ad
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Alki
|
2
|
+
====
|
3
|
+
|
4
|
+
Alki is a framework for creating projects that are modular, testable, and well organized.
|
5
|
+
|
6
|
+
It's goal is to remove uncertainty and friction when building Ruby projects, allowing developers to focus on implementing business logic.
|
7
|
+
|
8
|
+
:leveloffset: 1
|
9
|
+
include::projects.ad[]
|
10
|
+
|
11
|
+
:leveloffset: 1
|
12
|
+
include::assemblies.ad[]
|
13
|
+
|
14
|
+
:leveloffset: 1
|
15
|
+
include::assembly_dsl.ad[]
|
data/doc/projects.ad
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
Setting up Alki Projects
|
2
|
+
========================
|
3
|
+
|
4
|
+
Generally, projects that use alki work the same as normal Ruby projects but there are a couple of
|
5
|
+
guidelines to make life easier.
|
6
|
+
|
7
|
+
Alki projects should always use Bundler and have a `Gemfile` in the project root. It is also optional
|
8
|
+
that the project itself be a gem, with a `<project name>.gemspec` file in the project root and a `gemspec`
|
9
|
+
line in the `Gemfile`.
|
10
|
+
|
11
|
+
|
12
|
+
Using Alki without a gemspec
|
13
|
+
----------------------------
|
14
|
+
|
15
|
+
If your project isn't a gem, and doesn't have a gemspec, executables should go in `bin` and should include these two
|
16
|
+
lines before `require`-ing any other files.
|
17
|
+
```ruby
|
18
|
+
require 'bundler/setup'
|
19
|
+
require 'alki/bin`
|
20
|
+
```
|
21
|
+
|
22
|
+
After that you can require your library files and run them.
|
23
|
+
|
24
|
+
Using Alki with a gemspec
|
25
|
+
-------------------------
|
26
|
+
|
27
|
+
If your project *is* a gem, you'll do things a bit differently.
|
28
|
+
|
29
|
+
First, in your gemspec, set the `bindir` option to `'exe'` (not `'bin'`). Also if automatically setting
|
30
|
+
the `executables` option, make sure it is looking for files in `exe`, not `bin`.
|
31
|
+
|
32
|
+
As noted, executables should be placed in `exe`, not `bin`.
|
33
|
+
|
34
|
+
Finally, once your gemspec is setup, run `bundle binstubs <gem name>` where `<gem name>` is whatever
|
35
|
+
you set `spec.name` to in your gemspec.
|
36
|
+
|
37
|
+
Once done, you should have binstubs in `bin` for each executable you have in `exe`. These are what you
|
38
|
+
should run for testing/development. If you add new executables to `exe`, just run `bundle install` to
|
39
|
+
generate new binstubs for them.
|
40
|
+
|
41
|
+
Second, executables in `exe` shouldn't require any extra files other then the project files they need to
|
42
|
+
run (no `bundle/setup` or `alki/bin`).
|
43
|
+
|
data/lib/alki/assembly.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
require 'alki/assembly_executor'
|
2
|
+
require 'alki/override_builder'
|
2
3
|
require 'alki/dsls/assembly'
|
3
4
|
|
4
5
|
module Alki
|
5
6
|
module Assembly
|
6
|
-
def new(overrides={})
|
7
|
-
Alki::Assembly::Instance.new create_assembly(overrides), self.assembly_options
|
7
|
+
def new(overrides={},&blk)
|
8
|
+
Alki::Assembly::Instance.new create_assembly(overrides,&blk), self.assembly_options
|
8
9
|
end
|
9
10
|
|
10
11
|
def root
|
@@ -13,36 +14,14 @@ module Alki
|
|
13
14
|
|
14
15
|
private
|
15
16
|
|
16
|
-
def create_assembly(overrides={})
|
17
|
+
def create_assembly(overrides={},&blk)
|
17
18
|
config_dir = if assembly_options[:load_path]
|
18
|
-
|
19
|
+
Alki::Support.load_class("alki/assembly_types/value").new assembly_options[:load_path]
|
19
20
|
else
|
20
21
|
nil
|
21
22
|
end
|
22
23
|
|
23
|
-
|
24
|
-
end
|
25
|
-
|
26
|
-
|
27
|
-
def create_override_group(overrides)
|
28
|
-
unless overrides.empty?
|
29
|
-
root = build_type(:group)
|
30
|
-
overrides.each do |path,value|
|
31
|
-
set_override root, *path.to_s.split('.'), value
|
32
|
-
end
|
33
|
-
root
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def set_override(root,*parent_keys,key,value)
|
38
|
-
parent = parent_keys.inject(root) do |parent,key|
|
39
|
-
parent.children[key.to_sym] ||= build_type(:group)
|
40
|
-
end
|
41
|
-
parent.children[key.to_sym] = build_type(:value, value)
|
42
|
-
end
|
43
|
-
|
44
|
-
def build_type(type,*args)
|
45
|
-
Alki::Support.load_class("alki/assembly_types/#{type}").new *args
|
24
|
+
Alki::Support.load_class("alki/assembly_types/assembly").new root, config_dir, OverrideBuilder.build(overrides,&blk)
|
46
25
|
end
|
47
26
|
|
48
27
|
class Instance
|
@@ -19,7 +19,14 @@ module Alki
|
|
19
19
|
|
20
20
|
def build(opts={},&blk)
|
21
21
|
build_assembly blk if blk
|
22
|
-
|
22
|
+
if opts[:config_dir]
|
23
|
+
context = if opts[:project_assembly]
|
24
|
+
File.dirname opts[:project_assembly]
|
25
|
+
else
|
26
|
+
Dir.pwd
|
27
|
+
end
|
28
|
+
set_config_directory File.expand_path opts[:config_dir], context
|
29
|
+
end
|
23
30
|
set_assembly_name opts[:name] if opts[:name]
|
24
31
|
setup_project_assembly opts[:project_assembly] if opts[:project_assembly]
|
25
32
|
load_assembly_file opts[:primary_config] unless definition
|
@@ -65,42 +65,35 @@ module Alki
|
|
65
65
|
proc = -> (name,*args,&blk) {
|
66
66
|
call res.pkg, res.cache, res.elem[:scope][name], *args, &blk
|
67
67
|
}
|
68
|
-
group = GroupContext
|
68
|
+
group = create_context(GroupContext,res)
|
69
69
|
-> { group }
|
70
70
|
end
|
71
71
|
|
72
72
|
def with_scope_context(res,blk = nil)
|
73
|
-
|
74
|
-
|
73
|
+
methods = {
|
74
|
+
__call__: { body: (blk || res.elem[:block])}
|
75
75
|
}
|
76
|
+
yield create_context(ValueContext,res,methods)
|
77
|
+
end
|
76
78
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
79
|
+
def create_context(super_class,res,methods={})
|
80
|
+
executor = self
|
81
|
+
|
82
|
+
res.elem[:scope].keys.each do |meth|
|
83
|
+
methods[meth] = {
|
84
|
+
body: ->(*args,&blk) {
|
85
|
+
executor.call res.pkg, res.cache, res.elem[:scope][meth], *args, &blk
|
86
|
+
}
|
81
87
|
}
|
88
|
+
end
|
89
|
+
context_class = Alki::ClassBuilder.build(
|
90
|
+
super_class: super_class,
|
91
|
+
instance_methods: methods
|
82
92
|
)
|
83
|
-
|
84
|
-
yield context_class.new(proc, res.elem[:scope].keys)
|
93
|
+
context_class.new
|
85
94
|
end
|
86
95
|
|
87
96
|
class Context
|
88
|
-
def initialize(executor,scope)
|
89
|
-
@executor = executor
|
90
|
-
@scope = scope
|
91
|
-
end
|
92
|
-
|
93
|
-
def respond_to_missing?(name,include_all)
|
94
|
-
@scope.include? name
|
95
|
-
end
|
96
|
-
|
97
|
-
def method_missing(name,*args,&blk)
|
98
|
-
if @scope.include? name
|
99
|
-
@executor.call name, *args, &blk
|
100
|
-
else
|
101
|
-
super
|
102
|
-
end
|
103
|
-
end
|
104
97
|
end
|
105
98
|
|
106
99
|
class ValueContext < Context
|
@@ -118,7 +111,7 @@ module Alki
|
|
118
111
|
unless path.is_a?(String) or path.is_a?(Symbol)
|
119
112
|
raise ArgumentError.new("lazy can only take Strings or Symbols")
|
120
113
|
end
|
121
|
-
Alki::ServiceDelegator.new
|
114
|
+
Alki::ServiceDelegator.new assembly, path
|
122
115
|
end
|
123
116
|
end
|
124
117
|
|
data/lib/alki/bin.rb
ADDED
data/lib/alki/dsls/assembly.rb
CHANGED
@@ -1,16 +1,15 @@
|
|
1
|
+
require 'alki/override_builder'
|
1
2
|
require 'alki/support'
|
2
3
|
|
3
4
|
Alki do
|
4
5
|
require_dsl 'alki/dsls/assembly_types/group'
|
5
6
|
require_dsl 'alki/dsls/assembly_types/value'
|
6
7
|
|
7
|
-
dsl_method :assembly do |name,pkg=name.to_s,&blk|
|
8
|
+
dsl_method :assembly do |name,pkg=name.to_s,**overrides,&blk|
|
8
9
|
klass = Alki::Support.load_class pkg
|
9
10
|
config_dir = klass.assembly_options[:load_path]
|
10
11
|
config_dir = build_value config_dir if config_dir
|
11
|
-
overrides =
|
12
|
-
build_group_dsl(blk)
|
13
|
-
end
|
12
|
+
overrides = Alki::OverrideBuilder.build overrides, &blk
|
14
13
|
|
15
14
|
add_assembly name, klass.root, config_dir, overrides
|
16
15
|
end
|
@@ -24,11 +23,13 @@ Alki do
|
|
24
23
|
if key == :config_dir
|
25
24
|
data.merge! main_data
|
26
25
|
config_dir
|
26
|
+
elsif key == :original
|
27
|
+
root
|
27
28
|
else
|
28
29
|
if overrides
|
29
30
|
data.replace(
|
30
31
|
main: data.merge(main_data),
|
31
|
-
override:
|
32
|
+
override: override_data,
|
32
33
|
)
|
33
34
|
override.index data, key
|
34
35
|
else
|
@@ -40,6 +41,7 @@ Alki do
|
|
40
41
|
output do
|
41
42
|
scope = root.output(data)[:scope]
|
42
43
|
scope[:config_dir] = (data[:prefix]||[]) + [:config_dir]
|
44
|
+
scope[:original] = (data[:prefix]||[]) + [:original]
|
43
45
|
scope.merge! overrides.output(data)[:scope] if overrides
|
44
46
|
{
|
45
47
|
type: :group,
|
@@ -47,9 +49,16 @@ Alki do
|
|
47
49
|
}
|
48
50
|
end
|
49
51
|
|
52
|
+
def override_data
|
53
|
+
od = data.dup
|
54
|
+
od[:scope] ||= {}
|
55
|
+
od[:scope].merge! original: ((data[:prefix]||[]) + [:original])
|
56
|
+
od
|
57
|
+
end
|
58
|
+
|
50
59
|
def main_data
|
51
|
-
|
52
|
-
{scope: {
|
60
|
+
assembly_path = data[:prefix] ? data[:prefix].dup : []
|
61
|
+
{scope: {assembly: assembly_path, root: []}, overlays: []}
|
53
62
|
end
|
54
63
|
|
55
64
|
def override
|
@@ -2,12 +2,7 @@ require 'alki/dsls/assembly'
|
|
2
2
|
|
3
3
|
Alki do
|
4
4
|
dsl_method :group do |name,&blk|
|
5
|
-
add name,
|
6
|
-
end
|
7
|
-
|
8
|
-
helper :build_group_dsl do |blk|
|
9
|
-
data = Alki::Dsls::Assembly.build &blk
|
10
|
-
build_group data[:elems], data[:overlays]
|
5
|
+
add name, Alki::Dsls::Assembly.build(&blk)[:root]
|
11
6
|
end
|
12
7
|
|
13
8
|
element_type :group do
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'alki/dsls/assembly'
|
2
|
+
|
3
|
+
module Alki
|
4
|
+
module OverrideBuilder
|
5
|
+
def self.build(override_hash=nil,&blk)
|
6
|
+
if blk
|
7
|
+
Alki::Dsls::Assembly.build(&blk)[:root]
|
8
|
+
elsif override_hash && !override_hash.empty?
|
9
|
+
create_override_group override_hash
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.create_override_group(overrides)
|
14
|
+
unless overrides.empty?
|
15
|
+
root = build_type(:group)
|
16
|
+
overrides.each do |path,value|
|
17
|
+
set_override root, *path.to_s.split('.'), value
|
18
|
+
end
|
19
|
+
root
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.set_override(root,*parent_keys,key,value)
|
24
|
+
parent = parent_keys.inject(root) do |parent,key|
|
25
|
+
parent.children[key.to_sym] ||= build_type(:group)
|
26
|
+
end
|
27
|
+
parent.children[key.to_sym] = build_type(:value, value)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.build_type(type,*args)
|
31
|
+
Alki::Support.load_class("alki/assembly_types/#{type}").new *args
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
data/lib/alki/version.rb
CHANGED
@@ -1,18 +1,50 @@
|
|
1
1
|
require_relative '../test_helper'
|
2
|
-
|
3
|
-
|
4
|
-
require 'tlogger'
|
2
|
+
require 'logger'
|
3
|
+
require 'alki'
|
5
4
|
require 'stringio'
|
6
5
|
|
7
6
|
describe 'Overrides' do
|
7
|
+
before do
|
8
|
+
@assembly = Alki.create_assembly do
|
9
|
+
set :log_io do
|
10
|
+
raise "Must set log_io"
|
11
|
+
end
|
12
|
+
group :util do
|
13
|
+
service :logger do
|
14
|
+
require 'logger'
|
15
|
+
Logger.new log_io
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
8
21
|
it 'should be possibly to override assembly values on initialize' do
|
9
22
|
assert_raises RuntimeError do
|
10
|
-
|
23
|
+
@assembly.new.util.logger << "test"
|
11
24
|
end
|
12
25
|
io = StringIO.new
|
13
|
-
logger =
|
14
|
-
logger.
|
15
|
-
logger.
|
26
|
+
logger = @assembly.new(log_io: io)
|
27
|
+
logger.util.logger << "test"
|
28
|
+
logger.util.logger << "test"
|
16
29
|
io.string.must_equal "testtest"
|
17
30
|
end
|
31
|
+
|
32
|
+
it 'should allow overriding via block' do
|
33
|
+
logger_class = Class.new(Logger) do
|
34
|
+
def info(msg)
|
35
|
+
self << "INFO #{msg}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
io = StringIO.new
|
39
|
+
instance = @assembly.new do
|
40
|
+
set :log_io, io
|
41
|
+
group :util do
|
42
|
+
service :logger do
|
43
|
+
logger_class.new original.log_io
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
instance.util.logger.info "test"
|
48
|
+
io.string.must_equal "INFO test"
|
49
|
+
end
|
18
50
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: alki
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Edlefsen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-12-
|
11
|
+
date: 2016-12-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -103,15 +103,20 @@ files:
|
|
103
103
|
- ".gitignore"
|
104
104
|
- Gemfile
|
105
105
|
- LICENSE.txt
|
106
|
-
- README.
|
106
|
+
- README.ad
|
107
107
|
- Rakefile
|
108
108
|
- alki.gemspec
|
109
109
|
- config/dsls.rb
|
110
|
+
- doc/assemblies.ad
|
111
|
+
- doc/assembly_dsl.ad
|
112
|
+
- doc/index.ad
|
113
|
+
- doc/projects.ad
|
110
114
|
- lib/alki.rb
|
111
115
|
- lib/alki/assembly.rb
|
112
116
|
- lib/alki/assembly_builder.rb
|
113
117
|
- lib/alki/assembly_executor.rb
|
114
118
|
- lib/alki/assembly_handler_base.rb
|
119
|
+
- lib/alki/bin.rb
|
115
120
|
- lib/alki/dsls/assembly.rb
|
116
121
|
- lib/alki/dsls/assembly_type.rb
|
117
122
|
- lib/alki/dsls/assembly_type_dsl.rb
|
@@ -122,6 +127,7 @@ files:
|
|
122
127
|
- lib/alki/dsls/assembly_types/value.rb
|
123
128
|
- lib/alki/dsls/service.rb
|
124
129
|
- lib/alki/overlay_delegator.rb
|
130
|
+
- lib/alki/override_builder.rb
|
125
131
|
- lib/alki/service_delegator.rb
|
126
132
|
- lib/alki/test.rb
|
127
133
|
- lib/alki/version.rb
|
data/README.md
DELETED
@@ -1,55 +0,0 @@
|
|
1
|
-
# What is Alki?
|
2
|
-
|
3
|
-
Alki is a framework for creating projects that are modular, testable, and well organized.
|
4
|
-
|
5
|
-
It's goal is to remove uncertainty and friction when building Ruby projects, allowing developers to focus on implementing business logic.
|
6
|
-
|
7
|
-
# Synopsis
|
8
|
-
|
9
|
-
Best place to start would be to check out some examples:
|
10
|
-
|
11
|
-
* https://github.com/alki-project/alki-example
|
12
|
-
* https://github.com/alki-project/alki/tree/master/test/fixtures/example
|
13
|
-
|
14
|
-
# The Alki Assembly
|
15
|
-
|
16
|
-
If a set of classes are the raw materials of your product, an Assembly is the finished product, ready to ship.
|
17
|
-
|
18
|
-
To get there, you provide Alki with your assembly definition, which acts as the instructions for how to piece together your classes and objects.
|
19
|
-
|
20
|
-
Assembly definitions are written in a simple DSL and are transformed into classes.
|
21
|
-
|
22
|
-
```ruby
|
23
|
-
require 'alki'
|
24
|
-
|
25
|
-
class Printer
|
26
|
-
def print(msg)
|
27
|
-
puts msg
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
MyAssembly = Alki.create_assembly do
|
32
|
-
service :printer do
|
33
|
-
Printer.new
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
MyAssembly.new.printer.print "hello world"
|
38
|
-
```
|
39
|
-
|
40
|
-
## Project Assemblies
|
41
|
-
|
42
|
-
While Assemblies can be created directly as in the previous example, most
|
43
|
-
of the time an entire project will contain instructions for a single Assembly.
|
44
|
-
|
45
|
-
To ease this use case, Alki supports a simple standard project layout.
|
46
|
-
|
47
|
-
* `config` and `lib` directories in your project root.
|
48
|
-
* A file called `config/assembly.rb` with your assembly definition inside an `Alki do ... end` block.
|
49
|
-
* A file under lib that has the name of your project. For example if your project was called `MyProject`, create a file called `lib/my_project.rb`.
|
50
|
-
Inside the file put the following two lines:
|
51
|
-
|
52
|
-
```ruby
|
53
|
-
require 'alki'
|
54
|
-
Alki.project_assembly!
|
55
|
-
```
|