alki 0.13.2 → 0.13.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c45abd42f73d63b3416055e3d9b7083a987a933d
4
- data.tar.gz: 9eb429b7597cb856e731f0eb46a6e3094db4464a
3
+ metadata.gz: 477bb49ab83abf569bcc2b415b077cf15428c016
4
+ data.tar.gz: 32261729b502b2a85e66f5fa6b00f4c6ecd20adf
5
5
  SHA512:
6
- metadata.gz: d15a85dc3d44267dafe127f76dd7c54faa594b163926ff2d8198afe3a2493297904d0d778b82bbd649ee0c47dea2f0aa18961c7ef3ad7be00f8ba66c846af854
7
- data.tar.gz: 6bd49d1cd77f45018e376e059555f17b066d0c5be9c0cf40f6be653bb8518b737df5ef69811a673c0943aba3b1603bea42ae3ba84fce1d5127ade5c40ca8f3fd
6
+ metadata.gz: 250fe69e4e13e33b5e66886fba89685511fafe043c45ecbf69c419075f249f069f80a791bcee2e1d569d2a8ab533321a6625c868c65ab30d5db7809958367c57
7
+ data.tar.gz: 229abd1df5d0b641eb2ace2d7ebf7788a0b750daed24db20d648cf52b5cee416313a9e2332fcce65fa567ec5885b676f4a1b9394b8d867fdb44cdb17c7f569bb
data/Gemfile CHANGED
@@ -1,6 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'alki-testing', '= 0.3.0'
3
+ gem 'alki-testing'
4
4
  gem 'rake'
5
5
 
6
6
  gemspec
@@ -1,12 +1,14 @@
1
1
  # What is Alki?
2
2
 
3
- Alki is a small library to help organize and scale your Ruby project, so you can focus on the
4
- important stuff. It can be used alongside frameworks such as Ruby on Rails.
3
+ Alki (AL-kai) is a Dependency Injection framework for Ruby. Alki is designed to help organize and scale your project,
4
+ so you can focus on the important stuff.
5
+ It can be used alongside frameworks such as Ruby on Rails.
5
6
 
6
7
  Some high level features:
7
8
 
8
9
  * Easily manage objects and dependencies
9
10
  * Enables writing reusable and testable code
11
+ * Requires no annotations, mixins, or other changes to your code.
10
12
  * https://github.com/alki-project/alki-console[Developer console] (built on pry)
11
13
  * Automatic https://github.com/alki-project/alki-reload[code reloading]
12
14
  * Powerful https://github.com/alki-project/alki-dsl[DSL toolkit]
@@ -39,7 +41,7 @@ that connects our classes and modules together, and puts them into a special obj
39
41
  called an Assembly.
40
42
 
41
43
  There are many ways to use Assemblies, but the most common is to have a single
42
- Assembly for you project. For example, if you hade a "todo" command line utility
44
+ Assembly for a project. For example, if you hade a "todo" command line utility
43
45
  project that you wanted to use Alki with, all you would need to do to create
44
46
  an Assembly is add this file.
45
47
 
@@ -61,18 +63,19 @@ $ bundle exec irb -Ilib
61
63
 
62
64
  ### Defining Elements
63
65
 
64
- Add things to the assembly requires an assembly definition file. By convention this is
66
+ Adding things to the assembly requires an assembly definition file. By convention this is
65
67
  named `config/assembly.rb` and is built using a simple DSL. There are
66
68
  a handful of different element types in Assemblies. Below are a few of the
67
- most common. Full documentation of the DSL can be found
68
- https://github.com/alki-project/alki/blob/master/doc/assembly_dsl.adoc[here]
69
+ most common. Full documentation of the DSL can be found at http://alki.io[alki.io].
70
+
71
+ Elements can refer to other elements, and can be defined in any order.
69
72
 
70
73
  .config/assembly.rb
71
74
  ```ruby
72
75
  Alki do
73
76
  group :settings do <1>
74
77
  set(:home) { ENV['HOME'] } <2>
75
- set(:db_path) { ENV['TODO_DB_PATH'] || File.join(home,'.todo_db2') }
78
+ set(:db_path) { ENV['TODO_DB_PATH'] || File.join(home,'.todo_db') }
76
79
  set :prompt, 'todo> '
77
80
  end
78
81
 
@@ -97,7 +100,7 @@ Alki do
97
100
  end
98
101
  end
99
102
  ```
100
- <1> `group` allows bundling together subelements and can be moved to their own files
103
+ <1> `group` allows bundling together subelements (and which can be moved to their own files)
101
104
  <2> `set` defines simple values
102
105
  <3> `service` defines our main application objects
103
106
 
@@ -109,12 +112,22 @@ $ bundle exec irb -Ilib
109
112
  => true
110
113
  2.4.0 :002 > todo = Todo.new
111
114
  => #<Todo:21964520>
112
- 2.4.0 :003 > todo.settings.prompt
113
- => 'todo> '
115
+ 2.4.0 :003 > todo.interface.run
116
+ > ?
117
+ All commands can be shortened to their first letters
118
+ print
119
+ add <description>
120
+ edit <id> <description>
121
+ complete <id>
122
+ uncomplete <id>
123
+ remove <id>
124
+ move <from> <to>
125
+ quit
126
+
114
127
  ```
115
128
 
116
129
  The 'alki-console' tool can also be used to quickly work with assemblies.
117
- Add `gem 'alki-console'` to your Gemspec and run `bundle --binstubs`.
130
+ Add `gem 'alki-console'` to your Gemfile and run `bundle --binstubs`.
118
131
 
119
132
  ```
120
133
  $ bin/alki-console
@@ -125,9 +138,12 @@ todo> settings.prompt
125
138
  ### Creating an executable
126
139
 
127
140
  Read more about creating executables with Alki
128
- https://github.com/alki-project/alki/blob/master/doc/executables.adoc[here]
141
+ http://alki.io/executables.html[here].
129
142
 
130
143
  In the todo example, it's a CLI utility so it requires an executable.
144
+ The executable just needs to require the main project file,
145
+ create a new instance of the assembly,
146
+ and call a method on a service.
131
147
 
132
148
  .exe/todo
133
149
  ```ruby
@@ -135,9 +151,44 @@ require 'todo'
135
151
  Todo.new.interface.run
136
152
  ```
137
153
 
154
+ ### Splitting up the Assembly configuration
155
+
156
+ As a project grows, it's helpful to be able to split out
157
+ parts of the Assembly configuration into multiple files.
158
+
159
+ This can be accomplished with the `load` method in the DSL,
160
+ which will load the named file and add it the elements
161
+ defined in it as a group in the assembly.
162
+
163
+ For example, it's common to split out application settings into
164
+ a separate config file.
165
+
166
+ .config/settings.rb
167
+ ```ruby
168
+ Alki do
169
+ set(:home) { ENV['HOME'] }
170
+ set(:db_path) { ENV['TODO_DB_PATH'] || File.join(home,'.todo_db') }
171
+ set :prompt, 'todo> '
172
+ end
173
+ ```
174
+
175
+ .config/assembly.rb
176
+ ```ruby
177
+ Alki do
178
+ load :settings
179
+
180
+ service :interface do
181
+ require 'todo/readline_interface'
182
+ Todo::ReadlineInterface.new settings.prompt, handler
183
+ end
184
+
185
+ ...
186
+ end
187
+ ```
188
+
138
189
  ## Further Documentation
139
190
 
140
- Further documentation can be found https://github.com/alki-project/alki/blob/master/doc/index.adoc[here]
191
+ Further documentation can be found at http://alki.io[alki.io].
141
192
 
142
193
  Example projects can be found https://github.com/alki-project/alki-examples[here]
143
194
 
@@ -28,6 +28,12 @@ module Alki
28
28
  end
29
29
  end
30
30
 
31
+ def __reloading__
32
+ @lock.with_read_lock do
33
+ @needs_load
34
+ end
35
+ end
36
+
31
37
  def __version__
32
38
  @lock.with_read_lock do
33
39
  @version
@@ -69,11 +75,16 @@ module Alki
69
75
  def __load__
70
76
  @lock.with_write_lock do
71
77
  @needs_load = false
72
- @obj.__unload__ if @obj.respond_to?(:__unload__)
73
- @executor = Executor.new(self)
74
- InstanceBuilder.build @executor,@assembly_module, @overrides do |instance|
75
- @obj = instance
76
- self
78
+ begin
79
+ @obj.__unload__ if @obj.respond_to?(:__unload__)
80
+ @executor = Executor.new(self)
81
+ InstanceBuilder.build @executor,@assembly_module, @overrides do |instance|
82
+ @obj = instance
83
+ self
84
+ end
85
+ rescue Exception
86
+ @needs_load = true
87
+ __raise__
77
88
  end
78
89
  end
79
90
  end
@@ -1,3 +1,3 @@
1
1
  module Alki
2
- VERSION = "0.13.2"
2
+ VERSION = "0.13.3"
3
3
  end
@@ -29,4 +29,28 @@ describe 'Reload' do
29
29
  @obj.__reload__
30
30
  @obj.__version__.must_equal 0
31
31
  end
32
+
33
+ it 'should work if reloading after previous loading raises an error' do
34
+ assembly = @assembly
35
+ raise_error = []
36
+ proxy = Class.new do
37
+ define_method :root do
38
+ raise SyntaxError, 'error' unless raise_error.empty?
39
+ assembly.root
40
+ end
41
+ define_method :meta do
42
+ assembly.meta
43
+ end
44
+ end.new
45
+ obj = Alki::Assembly::Instance.new proxy, Alki::OverrideBuilder.build
46
+ obj.svc.must_equal 1
47
+ ref = obj.__reference_svc__
48
+ raise_error.push true
49
+ obj.__reload__
50
+ assert_raises(SyntaxError) do
51
+ obj.svc
52
+ end
53
+ raise_error.pop
54
+ obj.svc.must_equal 2
55
+ end
32
56
  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.13.2
4
+ version: 0.13.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Edlefsen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-12-10 00:00:00.000000000 Z
11
+ date: 2018-03-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: alki-dsl
@@ -76,8 +76,7 @@ description: Base library for building applications. Provides tools for organizi
76
76
  and connecting application units.
77
77
  email:
78
78
  - matt.edlefsen@gmail.com
79
- executables:
80
- - alki
79
+ executables: []
81
80
  extensions: []
82
81
  extra_rdoc_files: []
83
82
  files:
@@ -91,11 +90,6 @@ files:
91
90
  - bin/bundler
92
91
  - bin/rake
93
92
  - config/assembly.rb
94
- - doc/assemblies.adoc
95
- - doc/assembly_dsl.adoc
96
- - doc/executables.adoc
97
- - doc/index.adoc
98
- - exe/alki
99
93
  - lib/alki.rb
100
94
  - lib/alki/assembly.rb
101
95
  - lib/alki/assembly/builder.rb
@@ -132,7 +126,6 @@ files:
132
126
  - lib/alki/execution/tag_map.rb
133
127
  - lib/alki/execution/value_helpers.rb
134
128
  - lib/alki/executor.rb
135
- - lib/alki/generator.rb
136
129
  - lib/alki/invalid_path_error.rb
137
130
  - lib/alki/overlay_delegator.rb
138
131
  - lib/alki/overlay_info.rb
@@ -1,152 +0,0 @@
1
- Assemblies
2
- ==========
3
- :toc:
4
-
5
- If a set of classes are your raw materials, an Assembly is the finished product,
6
- ready to be used.
7
-
8
- To get there, you provide Alki with your assembly definition, which acts as the instructions for
9
- how to piece together your classes and objects. Assemblies are made up of elements, which are groups
10
- of other elements, or various value types like services and factories.
11
-
12
- Assembly definitions are written in a simple DSL.
13
-
14
- Project Assemblies
15
- ------------------
16
-
17
- Most of the time, a project will have a single assembly, so Alki makes having a single project wide
18
- assembly especially easy.
19
-
20
- First, in your project's `lib` directory create a ruby file for your assembly. If your assembly is
21
- to be called `MyAssembly`, create `lib/my_assembly.rb`. If it's namespaced put it in a subdirectory
22
- as usual (i.e. `MyModule::MyAssembly` would go in `lib/my_module/my_assembly.rb`).
23
-
24
- Your assembly file just needs two lines:
25
-
26
- ```ruby
27
- require 'alki'
28
- Alki.project_assembly!
29
- ```
30
-
31
- It will detect the project root and what class name it should create automatically.
32
-
33
- Second, a `config` directory must be created in the project root, and in that directory an `assembly.rb`
34
- file should be created. It should contain an `Alki do ... end` block which contains the top level
35
- definition for your Assembly.
36
-
37
- ```ruby
38
- Alki do
39
- # Assembly DSL ...
40
- end
41
- ```
42
-
43
- `Alki.project_assembly!` defaults can be overridden by passing in the following keyword arguments:
44
-
45
- [horizontal]
46
- name:: Set the name of the assembly. Should be formatted how you would put it in a `require` call
47
- (e.g. if `MyModule::MyAssembly` is desired, use `'my_module/my_assembly'`). Default is
48
- determined from the filename of the caller.
49
-
50
- config_dir:: Set directory where assembly config files are found. Default is `<project root>/config`.
51
-
52
- primary_config:: Sets the name of the main config file to load. Defaults to `assembly.rb`.
53
-
54
- Manually Creating Assemblies
55
- ----------------------------
56
-
57
- In addition to Project Assemblies, you can also create assemblies directly using `Alki.create_assembly`.
58
- It should be called with a block that contains the assembly DSL. It will return a Module object that can
59
- be used directly or assigned to a constant.
60
-
61
- ```ruby
62
- require 'alki'
63
- assembly = Alki.create_assembly do
64
- set :msg, "hello world"
65
- func :run do
66
- puts msg
67
- end
68
- end
69
-
70
- assembly.new.run
71
-
72
- # output: hello world
73
- ```
74
-
75
- Using Assemblies
76
- ----------------
77
-
78
- Assemblies have a `new` method used to create new assembly instances (like a normal class). Once an
79
- instance is created, anything in the assembly is accessible by drilling down into groups.
80
-
81
- Given an assembly like this:
82
-
83
- ```ruby
84
- require 'alki'
85
- assembly = Alki.create_assembly do
86
- set :log_io, STDERR
87
- group :util do
88
- service :logger do
89
- require 'logger'
90
- Logger.new log_io
91
- end
92
- end
93
- end
94
- ```
95
-
96
- One can use the logger service like so:
97
-
98
- ```ruby
99
- instance = assembly.new
100
- instance.util.logger.info "test"
101
-
102
- # output: I, [<timestamp>] INFO -- : test
103
- ```
104
-
105
- ### Overrides
106
-
107
- Assembly overrides provide a way to configure or customize an Assembly when
108
- constructing an instance.
109
-
110
- For example, using the assembly created above, one might want to change the IO object logged to.
111
-
112
- The simplist way to do this is to provide a hash which will override values in the assembly (as if the `set`
113
- command was called in the DSL):
114
-
115
- ```ruby
116
- require 'stringio'
117
- io = StringIO.new
118
- instance = assembly.new log_io: io
119
- instance.util.logger.info "test"
120
- puts(io.string.match(/INFO -- : test/) != nil)
121
-
122
- # output: true
123
- ```
124
-
125
- The limitiation of this is that it can only override basic values. To override more complex elements
126
- a block can be given to `new` allowing the full assembly DSL.
127
-
128
- ```ruby
129
- class MyLogger
130
- def initialize(io)
131
- @io = io
132
- end
133
- def info(msg)
134
- @io.puts "INFO #{msg}"
135
- end
136
- end
137
-
138
- instance = assembly.new do
139
- group :util do
140
- service :logger do
141
- MyLogger.new original.log_io
142
- end
143
- end
144
- end
145
- instance.util.logger.info "test"
146
-
147
- # output: INFO test
148
- ```
149
-
150
- One thing of note is that elements from the assembly are accessible in overrides via the `original`
151
- method, as seen above. This can also be used to access the original versions of elements that have
152
- been overriden.
@@ -1,511 +0,0 @@
1
- Assembly DSL
2
- ============
3
- :toc:
4
-
5
- Building Assemblies is done via a small DSL
6
-
7
- Groups (group)
8
- --------------
9
-
10
- Groups are the basic way of organizing the elements of your Assembly. Creating elements at the top
11
- level of an Assembly will place them in a root group, but subgroups are easily created via the `group`
12
- command.
13
-
14
- ```ruby
15
- require 'alki'
16
- assembly = Alki.create_assembly do
17
- group :sub_group do
18
- set :val, "hello world"
19
- end
20
- end
21
- puts assembly.new.sub_group.val
22
-
23
- #output: hello world
24
- ```
25
-
26
- Scoping is also done by group, so that an element will be found by searching through parent groups.
27
-
28
- ```ruby
29
- require 'alki'
30
- assembly = Alki.create_assembly do
31
- set :val, "one"
32
- func :print do
33
- puts val
34
- end
35
-
36
- group :g1 do
37
- set :val, "two"
38
-
39
- group :g2 do
40
- func :print do
41
- puts val
42
- end
43
- end
44
-
45
- end
46
- end
47
-
48
- assembly.new.print
49
-
50
- #output: one
51
-
52
- assembly.new.g1.g2.print
53
-
54
- #output: two
55
- ```
56
-
57
- === Loading groups (load)
58
-
59
- Groups can also be loaded from other config files via the `load` command.
60
-
61
- .config/settings.rb
62
- ```ruby
63
- Alki do
64
- set :val, "hello"
65
- end
66
- ```
67
-
68
- .main code
69
- ```ruby
70
- require 'alki'
71
- assembly = Alki.create_assembly config_dir: 'config' do
72
- load :settings
73
- func :print do
74
- puts settings.val
75
- end
76
- end
77
- assembly.new.print
78
-
79
- # output: hello
80
- ```
81
-
82
-
83
- Values
84
- ------
85
-
86
- Values are how you add functionality to your Assembly. There are different types of values, but they
87
- all share a number of common features.
88
-
89
- ### On-Demand
90
-
91
- Values are only built when referenced, on demand. This means even if you have a very large assembly
92
- only the things you actually use in a given run will be created, or often even `require`-d. It also
93
- means that all values will automatically be built in the correct order to satisfy dependencies.
94
-
95
- ### Scope
96
-
97
- When providing a block for a value, the block will be executed in a special context with some
98
- additional helper methods.
99
-
100
- The most important methods available are other elements that are "in scope". For example, a value can
101
- reference a sibling element in the same group, or any value in any ancestor group. The order that
102
- elements are defined does not effect what is in scope. All other elements are out of scope, but can
103
- usually still be referenced by finding an ancestor group that 'is' in scope, and then drilling down
104
- from there.
105
-
106
- ```ruby
107
- require 'alki'
108
- assembly = Alki.create_assembly config_dir: 'config' do
109
- set :val1, "1"
110
- group :main do
111
- group :sub_group do
112
- set :val2, "2"
113
- end
114
-
115
- set :values do
116
- [
117
- val1, # OK
118
- # val2, # ERROR
119
- sub_group.val2, # OK
120
- val3, # OK
121
- # val4, # ERROR
122
- other_group.val4, #OK
123
- ].join('')
124
- end
125
-
126
- set :val3, "3"
127
- end
128
-
129
- group :other_group do
130
- set :val4, "4"
131
- end
132
-
133
- end
134
- puts assembly.new.main.values
135
-
136
- #output: 1234
137
- ```
138
-
139
- ### Helpers
140
-
141
- In addition to elements in scope, there are some helper methods that are always available in value
142
- blocks.
143
-
144
- [horizontal]
145
-
146
- assembly:: This will return the root group of the assembly the element is defined in.
147
-
148
- root:: This will return the root group of the 'top most' assembly being run. If only a single
149
- assembly is being run, this will be the same as `assembly` but if the element being run is in
150
- an assembly that has been mounted into another assembly, they will differ.
151
-
152
- lookup(path):: This can be used to reference an element by a string path (using periods (`.`) to
153
- drill down into groups). If called directly it will lookup using the local scope. It is also available
154
- as a method on all groups, so `assembly.lookup(path)` would lookup an element starting from the root
155
- of the assembly.
156
-
157
- lazy(path):: This works the same as `lookup`, but with an important difference: Instead of doing the
158
- lookup immediately, it will instead return a "proxy" object, which will do the lookup the first time
159
- a method is called on the proxy object, and then delegate all method calls to the actual element. This
160
- can be used to handle circular references in services.
161
-
162
- Value Types
163
- -----------
164
-
165
- There are four types of elements loosely categorized as "values".
166
-
167
- ### Basic Values (set)
168
-
169
- The simplest type of values are ones created via the `set` command. There are two forms of `set`.
170
-
171
- ```ruby
172
- require 'alki'
173
- assembly = Alki.create_assembly do
174
- # This form takes the value as the second argument
175
- set :val1, "hello"
176
-
177
- # INVALID! Value may not be a reference to another element
178
- # set :val2, val1
179
-
180
- # This form takes the value as a block.
181
- # Block is run once and result cached.
182
- # Allows referencing other elements
183
- set :val2 do
184
- val1
185
- end
186
- end
187
- puts assembly.new.val2
188
-
189
- #output: hello
190
- ```
191
-
192
- ### Functions (func)
193
-
194
- Simple callable values can be created with the `func` command. These can take arguments, are run
195
- each time they are referenced, and can access other elements.
196
-
197
- ```ruby
198
- require 'alki'
199
- assembly = Alki.create_assembly do
200
- set :greeting, "Hello %s!"
201
-
202
- func :greet do |name|
203
- puts(greeting % [name])
204
- end
205
- end
206
- puts assembly.new.greet "Matt"
207
-
208
- #output: Hello Matt!
209
- ```
210
-
211
- ### Services (service)
212
-
213
- Services are the key element Assemblies are typically made up of. Like the block form of `set`,
214
- `service` takes a name and block, which will be evaluated once on-demand and the result cached.
215
- Whereas `set` is a lightweight element for simple values, `service` provides more functionality.
216
-
217
- Commonly a service will require the file that defines a class, and then constructs an instance of
218
- that class.
219
-
220
- ```ruby
221
- require 'alki'
222
- assembly = Alki.create_assembly do
223
- service :logger do
224
- require 'logger'
225
- Logger.new STDOUT
226
- end
227
- end
228
- assembly.new.logger << "hello\n"
229
-
230
- #output: hello
231
- ```
232
-
233
- ### Factories (factory)
234
-
235
- Factories are a mix between services and funcs. Like services, they take a block which is evaluated
236
- once. Unlike services though, that block must return a callable "builder" (like a Proc).
237
-
238
- If a factory is referenced as a service (i.e. no arguments) it returns a factory object
239
- that responds to either `#call` or `#new` and will call the builder in turn.
240
-
241
- If a factory is instead referenced like a method (i.e. with arguments) it will
242
- call the builder directly.
243
-
244
- ```ruby
245
- require 'alki'
246
- assembly = Alki.create_assembly do
247
- factory :logger do
248
- require 'logger'
249
- -> (io) { Logger.new io }
250
- end
251
-
252
- service :main_logger do
253
- logger STDOUT
254
- # -or-
255
- logger.call STDOUT
256
- end
257
- end
258
- assembly.new.main_logger << "hello\n"
259
-
260
- #output: hello
261
- ```
262
-
263
-
264
- ## Mounting Assemblies (mount)
265
-
266
- Other assemblies can be mounted into your Assembly using the `mount` command.
267
-
268
- The first argument is what the element should be named in the parent assembly.
269
- The optional second argument is the assembly to be mounted.
270
- This can either be the assembly module,
271
- or be a "require" string (relative path but no `.rb`).
272
- It defaults to the element name.
273
- If a string, Alki will attempt to `require` it, and then look for a matching constant.
274
-
275
-
276
- ```ruby
277
- require 'alki'
278
-
279
- other_assembly = Alki.create_assembly do
280
- set :val, "one"
281
-
282
- # This is invalid as there is no such element as 'val2'
283
- set :invalid_val2 do
284
- val2
285
- end
286
-
287
- # Normally, this would also be invalid, but if mounted
288
- # in an assembly that has a 'val2' element, this works.
289
- set :root_val2 do
290
- root.val2
291
- end
292
- end
293
-
294
- assembly = Alki.create_assembly do
295
- set :val2, "two"
296
- mount :other, other_assembly
297
- end
298
- instance = assembly.new
299
- puts instance.other.val
300
- #output: one
301
-
302
- # Even though val2 exists in MainAssembly, it is not directly accessibly to elements
303
- # within OtherAssembly
304
- begin
305
- puts instance.other.invalid_val2
306
- rescue => e
307
- puts e
308
- end
309
- # output: undefined local variable or method 'val2'
310
-
311
- # This works, because root returns the root assembly, which has a 'val2' element
312
- puts instance.other.root_val2
313
- #output: two
314
- ```
315
-
316
- In addition, `assembly` takes an optional third hash argument or a block which can be used to set
317
- overrides in the same way `::new` does for assemblies. Elements from the parent assembly are
318
- automatically in scope for overrides.
319
-
320
- ```ruby
321
- require 'alki'
322
- other_assembly = Alki.create_assembly do
323
- set :msg, nil
324
- func :print do
325
- puts msg
326
- end
327
- end
328
-
329
- assembly = Alki.create_assembly do
330
- set :val, "hello"
331
- mount :other, other_assembly do
332
- set :msg do
333
- val
334
- end
335
- end
336
- end
337
- assembly.new.other.print
338
-
339
- #output: hello
340
- ```
341
-
342
- ### Optional mounts (try_mount)
343
-
344
- Sometimes a library might not always be present. For example, using Bundler
345
- for development only gems. In that case `try_mount` can be used instead of
346
- `mount`. If the assembly library can't be found, the mount will simply be
347
- ignore.
348
-
349
- ## Overlays and Tags
350
-
351
- Overlays are a way to transparently wrap services. They work by taking the
352
- name of a service or a group, in which case they are applied to all services in that group,
353
- along with the name of an element to be used as the overlay, plus some optional arguments.
354
-
355
- When the named service is built, the overlay element will be called (with `.call`), with
356
- the built service object and the optional arguments, and it's result will be what's
357
- returned.
358
-
359
- Factories work well as overlay elements.
360
-
361
- ```ruby
362
- require 'alki'
363
-
364
- assembly = Alki.create_assembly do
365
- overlay :greeting, :exclaim, 3
366
-
367
- service :greeting do
368
- 'Hello World'
369
- end
370
-
371
- factory :exclaim do
372
- -> (string,count) do
373
- string + ('!' * count)
374
- end
375
- end
376
- end
377
-
378
- puts assembly.new.greeting
379
-
380
- #output: Hello World!!!
381
- ```
382
-
383
- ### Tags (tag)
384
-
385
- Tags are way of adding metadata to your elements. They can either be just a name, or
386
- optionally carry a value
387
-
388
- ```ruby
389
- require 'alki'
390
-
391
- assembly = Alki.create_assembly do
392
- tag :tag1, with_value: 123
393
- service :tagged do
394
- meta[:tags]
395
- end
396
- end
397
-
398
- puts assembly.new.tagged
399
-
400
- #output: {:with_value=>123, :tag1=>true}
401
- ```
402
-
403
- Tags can be applied to groups to tag everything within that group
404
-
405
- ```ruby
406
- require 'alki'
407
-
408
- assembly = Alki.create_assembly do
409
- tag :tag1
410
- group :grp do
411
- tag :tag2
412
- service :tagged do
413
- meta[:tags]
414
- end
415
- end
416
- end
417
-
418
- puts assembly.new.grp.tagged
419
-
420
- #output: {:tag1=>true, :tag2=>true}
421
- ```
422
-
423
- #### Overlaying tags (overlay_tag)
424
-
425
- Instead of overlaying services directly, it's often useful to overlay all services
426
- with a given tag.
427
-
428
- ```ruby
429
- require 'alki'
430
-
431
- assembly = Alki.create_assembly do
432
- overlay_tag :exclaimed, :exclaim, 3
433
-
434
- tag :exclaimed
435
- service :greeting do
436
- 'Hello World'
437
- end
438
-
439
- factory :exclaim do
440
- -> (string,count) do
441
- string + ('!' * count)
442
- end
443
- end
444
- end
445
-
446
- puts assembly.new.greeting
447
-
448
- #output: Hello World!!!
449
- ```
450
-
451
- Factories can access the tags of the services their being called from, allowing you
452
- to customize the build based on what tags are present
453
-
454
- ```ruby
455
- require 'alki'
456
-
457
- assembly = Alki.create_assembly do
458
- overlay_tag :process, :process_string
459
-
460
- tag :process, exclaim: 3
461
- service :greeting do
462
- 'Hello World'
463
- end
464
-
465
- factory :process_string do
466
- -> (string) do
467
- if exclaim = meta[:tags][:exclaim]
468
- string = string + ('!' * exclaim)
469
- end
470
- string
471
- end
472
- end
473
- end
474
-
475
- puts assembly.new.greeting
476
-
477
- #output: Hello World!!!
478
- ```
479
-
480
- Finally, tag overlays work even across assembly mounts, allowing overlays to
481
- be defined in a library, and then applied by tagging services.
482
-
483
- ```ruby
484
- require 'alki'
485
-
486
- string_processor = Alki.create_assembly do
487
- overlay_tag :process, :process_string
488
-
489
- factory :process_string do
490
- -> (string) do
491
- if exclaim = meta[:tags][:exclaim]
492
- string = string + ('!' * exclaim)
493
- end
494
- string
495
- end
496
- end
497
- end
498
-
499
- assembly = Alki.create_assembly do
500
- mount :string_processor, string_processor
501
-
502
- tag :process, exclaim: 3
503
- service :greeting do
504
- 'Hello World'
505
- end
506
- end
507
-
508
- puts assembly.new.greeting
509
-
510
- #output: Hello World!!!
511
- ```
@@ -1,68 +0,0 @@
1
- Creating Executables
2
- ====================
3
- :toc:
4
-
5
- Generally, projects that use alki are setup the same as normal Ruby projects but there are a couple of
6
- few differences plus some common conventions.
7
-
8
-
9
- Without a gemspec
10
- -----------------
11
-
12
- If your project isn't a gem, and doesn't have a gemspec, executables should go in `bin` and should include these two
13
- lines before `require`-ing any other files.
14
- ```ruby
15
- require 'bundler/setup'
16
- require 'alki/bin'
17
- ```
18
-
19
- After that you can use your library.
20
-
21
- .bin/my_exe
22
- ```ruby
23
- require 'bundler/setup'
24
- require 'alki/bin'
25
-
26
- require 'my_project'
27
-
28
- MyProject.new.cli.run
29
- ```
30
-
31
- ```
32
- $ bin/my_exe
33
- ...
34
- ```
35
-
36
-
37
- With a gemspec
38
- --------------
39
-
40
- If your project *is* a gem, you'll do things a bit differently.
41
-
42
- First, in your gemspec, set the `bindir` option to `'exe'` (not `'bin'`). Also if automatically setting
43
- the `executables` option, make sure it is looking for files in `exe`, not `bin`.
44
-
45
- Executables should be placed in `exe`, not `bin`.
46
-
47
- Finally, once your gemspec is setup, run `bundle binstubs <gem name>` where `<gem name>` is whatever
48
- you set `spec.name` to in your gemspec.
49
-
50
- Once done, you should have binstubs in `bin` for each executable you have in `exe`. These are what you
51
- should run for testing/development. If you add new executables to `exe`, just run `bundle install` to
52
- generate new binstubs for them.
53
-
54
- Second, executables in `exe` shouldn't require any extra files other then the project files they need to
55
- run (**do not require `bundle/setup` or `alki/bin`**).
56
-
57
- .exe/my_exe
58
- ```ruby
59
- require 'my_gem'
60
-
61
- MyGem.new.cli.run
62
- ```
63
-
64
- ```
65
- $ bundle binstubs my_gem
66
- $ bin/my_exe
67
- ...
68
- ```
@@ -1,17 +0,0 @@
1
- Alki
2
- ====
3
- :toc:
4
-
5
- Alki is a framework for creating projects that are modular, testable, and well organized.
6
-
7
- It's goal is to remove uncertainty and friction when building Ruby projects, allowing developers to focus on implementing business logic.
8
-
9
- :leveloffset: 1
10
- include::assemblies.adoc[]
11
-
12
- :leveloffset: 1
13
- include::assembly_dsl.adoc[]
14
-
15
- :leveloffset: 1
16
- include::executables.adoc[]
17
-
data/exe/alki DELETED
@@ -1,109 +0,0 @@
1
- #!/usr/bin/env ruby
2
- require 'fileutils'
3
- require 'optparse'
4
- require 'alki/support'
5
- require 'alki/generator'
6
-
7
- valid_addons = ["console","reload"]
8
-
9
- options = {
10
- config_dir: nil,
11
- primary_config: nil,
12
- addons: [],
13
- project_dir: Dir.pwd,
14
- }
15
- parser = OptionParser.new do |opts|
16
- opts.banner = "Usage: alki init PROJECT_NAME [options]"
17
-
18
- opts.on("-a", "--addons=ADDONS", Array, "Add addon to project. Valid addons: #{valid_addons.join(', ')}") do |vs|
19
- vs.each do |v|
20
- unless valid_addons.include? v
21
- puts "Invalid addon: #{v}"
22
- exit 1
23
- end
24
- options[:addons] |= [v]
25
- end
26
- end
27
-
28
- opts.on('-d','--directory=DIRECTORY', 'Project root, defaults to the current working directory') do |v|
29
- options[:project_dir] = v
30
- end
31
-
32
- opts.on("-c", "--config=DIECTORY", "Override config dir (default \"config\")") do |v|
33
- options[:config_dir] = v
34
- end
35
-
36
- opts.on("-p", "--primary=PRIMARY_CONFIG", "Override primary config (default \"assembly\")") do |v|
37
- options[:primary_config] = v
38
- end
39
- end
40
-
41
- parser.parse!
42
-
43
- if ARGV.size != 2 || ARGV[0] != "init"
44
- puts parser.banner
45
- exit 1
46
- end
47
-
48
- unless ARGV[1] =~ %r{^[a-z0-9_]+(/[a-z0-9_]+)*$}
49
- puts "Invalid project name. May only consist of lowercase letters, numbers, underscores, and forward slashes"
50
- exit 1
51
- end
52
-
53
- project_name = ARGV[1]
54
-
55
- fw = Alki::Generator.new options[:project_dir]
56
-
57
- config_dir = options[:config_dir] || 'config'
58
- primary_config = options[:primary_config] || 'assembly'
59
- primary_config_path = File.join(config_dir,primary_config+'.rb')
60
- fw.opt_create primary_config_path, <<END
61
- Alki do
62
- # Assembly config goes here
63
- end
64
- END
65
-
66
- opts = []
67
- opts << "config_dir: '#{options[:config_dir]}'" if options[:config_dir]
68
- opts << "primary_config: '#{options[:primary_config]}'" if options[:primary_config]
69
- unless opts.empty?
70
- opt_str = " #{opts.join(', ')}"
71
- end
72
- fw.create "lib/#{project_name}.rb", <<END
73
- require 'alki'
74
- Alki.project_assembly!#{opt_str}
75
- END
76
-
77
- fw.opt_create "Gemfile", <<END
78
- source "https://rubygems.org"
79
- END
80
-
81
- fw.trigger 'Gemfile', 'bundle install'
82
-
83
- fw.add_line "Gemfile", "gem 'alki'", match: /^gem ['"]alki['"]/
84
-
85
- if options[:addons].include? "console"
86
- fw.add_line "Gemfile", "gem 'alki-console'", match: /^gem ['"]alki-console['"]/
87
-
88
- name = project_name.tr('/','-')
89
- fw.add_line primary_config_path, " mount :console, 'alki/console', name: '#{name}'", match: /^\s*mount\( *:console/, after: 'Alki do'
90
-
91
- fw.create_exec 'bin/console', <<END
92
- #!/usr/bin/env ruby
93
- require 'bundler/setup'
94
- require 'alki/bin'
95
- require '#{project_name}'
96
- #{Alki::Support.classify(project_name)}.new.console.run
97
- END
98
- end
99
-
100
- if options[:addons].include? "reload"
101
- fw.add_line "Gemfile", "gem 'alki-reload'", match: /^gem ['"]alki-reload['"]/
102
-
103
- env_name = project_name.tr('a-z/','A-Z_')+"_ENV"
104
- fw.add_line primary_config_path, " mount(:reloader, 'alki/reload'){ set(:watch) { development? } }", match: /^\s*mount\(? *:reloader/, after: 'Alki do'
105
- fw.add_line primary_config_path, " set(:development?){ environment == 'development' }", match: /^\s*set\(? *:development\?/, after: 'Alki do'
106
- fw.add_line primary_config_path, " set(:environment){ ENV['#{env_name}'] || 'development' }", match: /^\s*set\(? *:environment/, after: 'Alki do'
107
- end
108
-
109
- fw.write
@@ -1,177 +0,0 @@
1
- module Alki
2
- class Generator
3
- def initialize(root_dir)
4
- @root_dir = root_dir
5
- @changes = []
6
- @triggers = []
7
- end
8
-
9
- def create(file,contents)
10
- @changes << [:create,file: file,contents: contents,opts: {}]
11
- end
12
-
13
- def create_exec(file,contents)
14
- @changes << [:create,file: file,contents: contents,opts: {exec: true}]
15
- end
16
-
17
- def check_create(file:, contents:, opts:)
18
- path = abs_path( file)
19
- if File.exists? path
20
- if File.read(path) == contents
21
- :skip
22
- end
23
- end
24
- end
25
-
26
- def desc_create(file:, contents:, opts: {})
27
- path = abs_path( file)
28
- if opts[:exec]
29
- adj = "executable "
30
- end
31
- if File.exists? path
32
- "Overwriting #{adj}#{file}!"
33
- else
34
- "Create #{adj}#{file}"
35
- end
36
- end
37
-
38
- def do_create(file:, contents:, opts: {})
39
- path = abs_path( file)
40
- FileUtils.mkdir_p File.dirname(path)
41
- File.write path, contents
42
- FileUtils.chmod '+x', path if opts[:exec]
43
- end
44
-
45
- def opt_create(file,contents)
46
- @changes << [:opt_create,file: file,contents: contents]
47
- end
48
-
49
- def check_opt_create(file:, contents:)
50
- if File.exists? abs_path( file)
51
- :skip
52
- end
53
- end
54
-
55
- def desc_opt_create(opts)
56
- desc_create opts
57
- end
58
-
59
- def do_opt_create(opts)
60
- do_create opts
61
- end
62
-
63
- def add_line(file,line,opts={})
64
- @changes << [:add_line,file: file,line: line,opts: opts]
65
- end
66
-
67
- def check_add_line(file:,line:,opts:)
68
- if File.exists? abs_path(file)
69
- File.open(file) do |f|
70
- if opts[:after]
71
- found = until f.eof?
72
- break true if f.readline.chomp == opts[:after]
73
- end
74
- unless found
75
- puts "File \"#{file}\" doesn't contain required line #{opts[:after]}"
76
- return :abort
77
- end
78
- end
79
- until f.eof?
80
- l = f.readline
81
- if opts[:match] ? l.chomp.match(opts[:match]) : (l.chomp == line)
82
- return :skip
83
- end
84
- end
85
- end
86
- else
87
- unless @changes.find {|c| [:create,:opt_create].include?(c[0]) && c[1][:file] == file}
88
- puts "File \"#{file}\" doesn't exist!"
89
- return :abort
90
- end
91
- end
92
- end
93
-
94
- def desc_add_line(file:,line:,opts:)
95
- "Add line \"#{line}\" to #{file}"
96
- end
97
-
98
- def do_add_line(file:,line:,opts:)
99
- if opts[:after]
100
- File.write file, File.read(file).sub(/^#{Regexp.quote(opts[:after])}\n/){|m| m + line + "\n"}
101
- else
102
- File.open(file,'a') do |f|
103
- f.puts line
104
- end
105
- end
106
- end
107
-
108
- def trigger(file,cmd)
109
- @triggers << [file, cmd]
110
- end
111
-
112
- def check_trigger(changes,file)
113
- changes.find {|c| c[1][:file] == file}
114
- end
115
-
116
- def desc_trigger(cmd:)
117
- "Run \"#{cmd}\""
118
- end
119
-
120
- def do_trigger(cmd:)
121
- system cmd
122
- end
123
-
124
- def check_changes
125
- puts "Checking preconditions..."
126
- abort = false
127
- unless Dir.exists?(@root_dir)
128
- puts "Root dir doesn't exist"
129
- exit 1
130
- end
131
- do_changes = []
132
- @changes.each do |(type,args)|
133
- res = send "check_#{type}", args
134
- abort = true if res == :abort
135
- do_changes << [type,args] unless res == :skip
136
- end
137
- @triggers.each do |(file,cmd)|
138
- if check_trigger do_changes, file
139
- do_changes << [:trigger,cmd: cmd]
140
- end
141
- end
142
- exit 1 if abort
143
- do_changes
144
- end
145
-
146
- def print_overview(changes)
147
- puts "Overview of changes to be made:"
148
-
149
- changes.each do |(type,args)|
150
- desc = send "desc_#{type}", args
151
- puts " #{desc}" if desc
152
- end
153
- print "Proceed? "
154
- resp = STDIN.gets.chomp
155
- unless resp =~ /^y(es?)?/i
156
- puts "Aborting"
157
- exit
158
- end
159
- end
160
-
161
- def write
162
- puts "Using project root: #{@root_dir}\n"
163
- do_changes = check_changes
164
- print_overview do_changes
165
-
166
- puts "Writing changes..."
167
- do_changes.each do |(type,args)|
168
- send "do_#{type}", args
169
- end
170
- puts "Done"
171
- end
172
-
173
- def abs_path(p)
174
- File.expand_path(p,@root_dir)
175
- end
176
- end
177
- end