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 +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
|