alki 0.13.2 → 0.13.3
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/Gemfile +1 -1
- data/README.adoc +64 -13
- data/lib/alki/assembly/instance.rb +16 -5
- data/lib/alki/version.rb +1 -1
- data/test/feature/reload_test.rb +24 -0
- metadata +3 -10
- data/doc/assemblies.adoc +0 -152
- data/doc/assembly_dsl.adoc +0 -511
- data/doc/executables.adoc +0 -68
- data/doc/index.adoc +0 -17
- data/exe/alki +0 -109
- data/lib/alki/generator.rb +0 -177
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 477bb49ab83abf569bcc2b415b077cf15428c016
|
4
|
+
data.tar.gz: 32261729b502b2a85e66f5fa6b00f4c6ecd20adf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 250fe69e4e13e33b5e66886fba89685511fafe043c45ecbf69c419075f249f069f80a791bcee2e1d569d2a8ab533321a6625c868c65ab30d5db7809958367c57
|
7
|
+
data.tar.gz: 229abd1df5d0b641eb2ace2d7ebf7788a0b750daed24db20d648cf52b5cee416313a9e2332fcce65fa567ec5885b676f4a1b9394b8d867fdb44cdb17c7f569bb
|
data/Gemfile
CHANGED
data/README.adoc
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
# What is Alki?
|
2
2
|
|
3
|
-
Alki is a
|
4
|
-
|
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
|
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
|
-
|
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
|
-
|
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,'.
|
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.
|
113
|
-
|
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
|
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
|
-
|
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
|
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
|
-
|
73
|
-
|
74
|
-
|
75
|
-
@
|
76
|
-
|
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
|
data/lib/alki/version.rb
CHANGED
data/test/feature/reload_test.rb
CHANGED
@@ -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.
|
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:
|
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
|
data/doc/assemblies.adoc
DELETED
@@ -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.
|
data/doc/assembly_dsl.adoc
DELETED
@@ -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
|
-
```
|
data/doc/executables.adoc
DELETED
@@ -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
|
-
```
|
data/doc/index.adoc
DELETED
@@ -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
|
data/lib/alki/generator.rb
DELETED
@@ -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
|