alki 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
-
```
|