alki 0.12.0 → 0.12.1
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 -0
- data/README.adoc +132 -20
- data/Rakefile +2 -27
- data/alki.gemspec +1 -3
- data/bin/bundler +17 -0
- data/bin/rake +17 -0
- data/doc/assemblies.adoc +2 -3
- data/doc/assembly_dsl.adoc +191 -30
- data/doc/{projects.adoc → executables.adoc} +40 -16
- data/doc/index.adoc +4 -3
- data/lib/alki.rb +3 -0
- data/lib/alki/assembly.rb +4 -34
- data/lib/alki/assembly/builder.rb +3 -3
- data/lib/alki/assembly/instance.rb +31 -5
- data/lib/alki/assembly/instance_builder.rb +48 -0
- data/lib/alki/assembly/meta/overlay.rb +7 -2
- data/lib/alki/assembly/meta/tags.rb +4 -4
- data/lib/alki/assembly/types.rb +9 -0
- data/lib/alki/assembly/types/assembly.rb +3 -3
- data/lib/alki/assembly/types/group.rb +16 -25
- data/lib/alki/assembly/types/original.rb +12 -0
- data/lib/alki/assembly/types/override.rb +0 -2
- data/lib/alki/assembly/types/service.rb +4 -4
- data/lib/alki/circular_reference_error.rb +25 -0
- data/lib/alki/dsls/assembly.rb +1 -1
- data/lib/alki/dsls/assembly_group.rb +28 -12
- data/lib/alki/execution/context_class_builder.rb +1 -1
- data/lib/alki/execution/helpers.rb +4 -4
- data/lib/alki/execution/overlay_map.rb +37 -0
- data/lib/alki/execution/tag_map.rb +42 -0
- data/lib/alki/executor.rb +140 -0
- data/lib/alki/override_builder.rb +30 -24
- data/lib/alki/overrides.rb +4 -0
- data/lib/alki/version.rb +1 -1
- data/test/feature/mounts_test.rb +15 -0
- data/test/feature/multithreading_test.rb +0 -3
- data/test/feature/overlays_test.rb +2 -2
- data/test/feature/overrides_test.rb +26 -1
- data/test/feature/references_test.rb +35 -0
- data/test/feature/try_mounts_test.rb +23 -0
- data/test/feature/values_test.rb +14 -0
- data/test/feature_test_helper.rb +1 -0
- data/test/fixtures/example/config/assembly.rb +17 -8
- data/test/fixtures/example/config/handlers.rb +10 -5
- data/test/fixtures/example/lib/dsls/num_handler.rb +2 -2
- data/test/fixtures/example/lib/example/array_output.rb +13 -0
- data/test/fixtures/example/lib/example/echo_handler.rb +11 -0
- data/test/fixtures/example/lib/example/log_overlay.rb +12 -0
- data/test/fixtures/example/lib/example/num_handler.rb +13 -0
- data/test/fixtures/example/lib/example/range_handler.rb +13 -0
- data/test/fixtures/example/lib/example/switch_handler.rb +11 -0
- metadata +39 -44
- data/lib/alki/assembly/executor.rb +0 -137
- data/test/fixtures/example/lib/array_output.rb +0 -11
- data/test/fixtures/example/lib/echo_handler.rb +0 -9
- data/test/fixtures/example/lib/log_overlay.rb +0 -10
- data/test/fixtures/example/lib/num_handler.rb +0 -11
- data/test/fixtures/example/lib/range_handler.rb +0 -11
- data/test/fixtures/example/lib/switch_handler.rb +0 -9
@@ -1,36 +1,48 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
Creating Executables
|
2
|
+
====================
|
3
3
|
:toc:
|
4
4
|
|
5
|
-
Generally, projects that use alki
|
6
|
-
|
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
7
|
|
8
|
-
Alki projects should always use Bundler and have a `Gemfile` in the project root. It is also optional
|
9
|
-
that the project itself be a gem, with a `<project name>.gemspec` file in the project root and a `gemspec`
|
10
|
-
line in the `Gemfile`.
|
11
8
|
|
12
|
-
|
13
|
-
|
14
|
-
----------------------------
|
9
|
+
Without a gemspec
|
10
|
+
-----------------
|
15
11
|
|
16
12
|
If your project isn't a gem, and doesn't have a gemspec, executables should go in `bin` and should include these two
|
17
13
|
lines before `require`-ing any other files.
|
18
14
|
```ruby
|
19
15
|
require 'bundler/setup'
|
20
|
-
require 'alki/bin
|
16
|
+
require 'alki/bin'
|
21
17
|
```
|
22
18
|
|
23
|
-
After that you can
|
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'
|
24
27
|
|
25
|
-
|
26
|
-
|
28
|
+
MyProject.new.cli.run
|
29
|
+
```
|
30
|
+
|
31
|
+
```
|
32
|
+
$ bin/my_exe
|
33
|
+
...
|
34
|
+
```
|
35
|
+
|
36
|
+
|
37
|
+
With a gemspec
|
38
|
+
--------------
|
27
39
|
|
28
40
|
If your project *is* a gem, you'll do things a bit differently.
|
29
41
|
|
30
42
|
First, in your gemspec, set the `bindir` option to `'exe'` (not `'bin'`). Also if automatically setting
|
31
43
|
the `executables` option, make sure it is looking for files in `exe`, not `bin`.
|
32
44
|
|
33
|
-
|
45
|
+
Executables should be placed in `exe`, not `bin`.
|
34
46
|
|
35
47
|
Finally, once your gemspec is setup, run `bundle binstubs <gem name>` where `<gem name>` is whatever
|
36
48
|
you set `spec.name` to in your gemspec.
|
@@ -40,5 +52,17 @@ should run for testing/development. If you add new executables to `exe`, just ru
|
|
40
52
|
generate new binstubs for them.
|
41
53
|
|
42
54
|
Second, executables in `exe` shouldn't require any extra files other then the project files they need to
|
43
|
-
run (
|
55
|
+
run (**do not require `bundle/setup` or `alki/bin`**).
|
56
|
+
|
57
|
+
.exe/my_exe
|
58
|
+
```ruby
|
59
|
+
require 'my_gem'
|
44
60
|
|
61
|
+
MyGem.new.cli.run
|
62
|
+
```
|
63
|
+
|
64
|
+
```
|
65
|
+
$ bundle binstubs my_gem
|
66
|
+
$ bin/my_exe
|
67
|
+
...
|
68
|
+
```
|
data/doc/index.adoc
CHANGED
@@ -6,11 +6,12 @@ Alki is a framework for creating projects that are modular, testable, and well o
|
|
6
6
|
|
7
7
|
It's goal is to remove uncertainty and friction when building Ruby projects, allowing developers to focus on implementing business logic.
|
8
8
|
|
9
|
-
:leveloffset: 1
|
10
|
-
include::projects.adoc[]
|
11
|
-
|
12
9
|
:leveloffset: 1
|
13
10
|
include::assemblies.adoc[]
|
14
11
|
|
15
12
|
:leveloffset: 1
|
16
13
|
include::assembly_dsl.adoc[]
|
14
|
+
|
15
|
+
:leveloffset: 1
|
16
|
+
include::executables.adoc[]
|
17
|
+
|
data/lib/alki.rb
CHANGED
data/lib/alki/assembly.rb
CHANGED
@@ -1,35 +1,11 @@
|
|
1
|
-
require 'alki/override_builder'
|
2
1
|
require 'alki/assembly/instance'
|
3
|
-
require 'alki/
|
4
|
-
require 'alki/assembly/meta/overlay'
|
5
|
-
require 'alki/overlay_info'
|
6
|
-
require 'ice_nine'
|
2
|
+
require 'alki/override_builder'
|
7
3
|
|
8
4
|
module Alki
|
9
5
|
module Assembly
|
10
|
-
def new(
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
def raw_instance(overrides,blk,&wrapper)
|
15
|
-
overrides_info = OverrideBuilder.build(overrides,&blk)
|
16
|
-
override_root = overrides_info[:root] || build(:group)
|
17
|
-
|
18
|
-
assembly = build :assembly, root, override_root
|
19
|
-
update_instance_overlay = [[],Meta::Overlay.new(
|
20
|
-
:value,
|
21
|
-
[:assembly_instance],
|
22
|
-
->obj{wrapper.call obj},
|
23
|
-
[]
|
24
|
-
)]
|
25
|
-
all_meta = meta+overrides_info[:meta]+[update_instance_overlay]
|
26
|
-
IceNine.deep_freeze all_meta
|
27
|
-
executor = Executor.new(assembly, all_meta)
|
28
|
-
|
29
|
-
override_root.children[:assembly_instance] = build(:service,->{ root })
|
30
|
-
override_root.children[:assembly_executor] = build(:value,executor)
|
31
|
-
|
32
|
-
executor.call [:assembly_instance]
|
6
|
+
def new(override_values={},&override_blk)
|
7
|
+
overrides = OverrideBuilder.build override_values, &override_blk
|
8
|
+
Instance.new load_class, overrides
|
33
9
|
end
|
34
10
|
|
35
11
|
def root
|
@@ -39,11 +15,5 @@ module Alki
|
|
39
15
|
def meta
|
40
16
|
self.definition.meta
|
41
17
|
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
def build(type,*args)
|
46
|
-
Alki.load("alki/assembly/types/#{type}").new *args
|
47
|
-
end
|
48
18
|
end
|
49
19
|
end
|
@@ -46,8 +46,8 @@ module Alki
|
|
46
46
|
|
47
47
|
def setup_project_assembly(path)
|
48
48
|
root = Alki::Support.find_root(path) do |dir|
|
49
|
-
File.
|
50
|
-
File.
|
49
|
+
File.exist?(File.join(dir,'config',"#{@primary_config}.rb")) ||
|
50
|
+
File.exist?(File.join(dir,'Gemfile')) ||
|
51
51
|
!Dir.glob(File.join(dir,'*.gemspec')).empty?
|
52
52
|
end
|
53
53
|
if root
|
@@ -62,7 +62,7 @@ module Alki
|
|
62
62
|
|
63
63
|
unless @config_dir
|
64
64
|
config_dir = File.join(root,'config')
|
65
|
-
@config_dir = config_dir if File.
|
65
|
+
@config_dir = config_dir if File.exist? config_dir
|
66
66
|
end
|
67
67
|
end
|
68
68
|
end
|
@@ -1,16 +1,19 @@
|
|
1
1
|
require 'delegate'
|
2
2
|
require 'concurrent'
|
3
3
|
require 'alki/support'
|
4
|
+
require 'alki/assembly/instance_builder'
|
4
5
|
|
5
6
|
module Alki
|
6
7
|
module Assembly
|
7
8
|
class Instance < Delegator
|
8
|
-
def initialize(assembly_module,
|
9
|
+
def initialize(assembly_module,overrides)
|
9
10
|
@assembly_module = assembly_module
|
10
|
-
@
|
11
|
+
@overrides = overrides
|
11
12
|
@version = 0
|
12
13
|
@needs_load = true
|
13
14
|
@lock = Concurrent::ReentrantReadWriteLock.new
|
15
|
+
@obj = nil
|
16
|
+
@executor = nil
|
14
17
|
end
|
15
18
|
|
16
19
|
def __reload__
|
@@ -30,6 +33,13 @@ module Alki
|
|
30
33
|
end
|
31
34
|
end
|
32
35
|
|
36
|
+
def __executor__
|
37
|
+
@lock.with_read_lock do
|
38
|
+
__load__ if @needs_load
|
39
|
+
@executor
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
33
43
|
def __getobj__
|
34
44
|
@lock.with_read_lock do
|
35
45
|
__load__ if @needs_load
|
@@ -37,15 +47,31 @@ module Alki
|
|
37
47
|
end
|
38
48
|
end
|
39
49
|
|
50
|
+
def to_s
|
51
|
+
inspect
|
52
|
+
end
|
53
|
+
|
54
|
+
def inspect
|
55
|
+
if @assembly_module.is_a?(Module)
|
56
|
+
name = @assembly_module.name || 'AnonymousAssembly'
|
57
|
+
else
|
58
|
+
name = Alki::Support.classify(@assembly_module.to_s)
|
59
|
+
end
|
60
|
+
"#<#{name}:#{object_id}>"
|
61
|
+
end
|
62
|
+
|
63
|
+
def pretty_print(q)
|
64
|
+
q.text(inspect)
|
65
|
+
end
|
40
66
|
private
|
41
67
|
|
42
68
|
def __load__
|
43
|
-
# Calls __setobj__
|
44
69
|
@lock.with_write_lock do
|
45
70
|
@needs_load = false
|
46
71
|
@obj.__unload__ if @obj.respond_to?(:__unload__)
|
47
|
-
|
48
|
-
@obj =
|
72
|
+
InstanceBuilder.build @assembly_module, @overrides do |instance,executor|
|
73
|
+
@obj = instance
|
74
|
+
@executor = executor
|
49
75
|
self
|
50
76
|
end
|
51
77
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'alki/assembly/types'
|
2
|
+
require 'alki/executor'
|
3
|
+
require 'alki/override_builder'
|
4
|
+
require 'alki/assembly/meta/overlay'
|
5
|
+
require 'alki/overrides'
|
6
|
+
require 'ice_nine'
|
7
|
+
|
8
|
+
module Alki
|
9
|
+
module Assembly
|
10
|
+
module InstanceBuilder
|
11
|
+
class << self
|
12
|
+
def build(assembly,overrides,&instance_wrapper)
|
13
|
+
assembly = Alki.load(assembly)
|
14
|
+
executor = Executor.new
|
15
|
+
|
16
|
+
overrides = inject_assembly_instance overrides, instance_wrapper, executor
|
17
|
+
|
18
|
+
executor.root = Types.build :assembly, assembly.root, overrides.root
|
19
|
+
executor.meta = IceNine.deep_freeze(assembly.meta+overrides.meta)
|
20
|
+
|
21
|
+
executor.call [:assembly_instance]
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def inject_assembly_instance(overrides,instance_wrapper,executor)
|
27
|
+
root = overrides.root.dup
|
28
|
+
root.children = root.children.merge(assembly_instance: assembly_instance)
|
29
|
+
meta = overrides.meta + [wrap_assembly_instance(instance_wrapper,executor)]
|
30
|
+
Overrides.new(root,meta)
|
31
|
+
end
|
32
|
+
|
33
|
+
def assembly_instance
|
34
|
+
Types.build(:service,-> { root })
|
35
|
+
end
|
36
|
+
|
37
|
+
def wrap_assembly_instance(wrapper,executor)
|
38
|
+
[[],Meta::Overlay.new(
|
39
|
+
:value,
|
40
|
+
[:assembly_instance],
|
41
|
+
-> obj { wrapper.call obj, executor },
|
42
|
+
[]
|
43
|
+
)]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'alki/invalid_path_error'
|
2
2
|
require 'alki/overlay_info'
|
3
|
+
require 'alki/execution/overlay_map'
|
3
4
|
|
4
5
|
module Alki
|
5
6
|
module Assembly
|
@@ -14,7 +15,8 @@ module Alki
|
|
14
15
|
|
15
16
|
def process(executor,from,data)
|
16
17
|
data[:total_overlays] ||= 0
|
17
|
-
data[:overlays]||=
|
18
|
+
data[:overlays] ||= Execution::OverlayMap.new
|
19
|
+
|
18
20
|
target_path = @target.dup
|
19
21
|
if target_path.last.to_s.start_with?('%')
|
20
22
|
tag = target_path.pop
|
@@ -22,8 +24,10 @@ module Alki
|
|
22
24
|
if target_path == []
|
23
25
|
target_path = [:root]
|
24
26
|
end
|
27
|
+
|
25
28
|
target = executor.canonical_path(from,target_path) or
|
26
29
|
raise InvalidPathError.new("Invalid overlay target #{@target.join('.')}")
|
30
|
+
|
27
31
|
target = target.dup.push tag if tag
|
28
32
|
overlay = @overlay
|
29
33
|
if overlay.is_a?(Array)
|
@@ -31,7 +35,8 @@ module Alki
|
|
31
35
|
raise InvalidPathError.new("Invalid overlay path #{@overlay.join('.')}")
|
32
36
|
end
|
33
37
|
order = data[:total_overlays]
|
34
|
-
|
38
|
+
|
39
|
+
data[:overlays].add target, OverlayInfo.new(order,@type, overlay, @args)
|
35
40
|
data[:total_overlays] += 1
|
36
41
|
end
|
37
42
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'alki/execution/tag_map'
|
2
|
+
|
1
3
|
module Alki
|
2
4
|
module Assembly
|
3
5
|
module Meta
|
@@ -7,10 +9,8 @@ module Alki
|
|
7
9
|
end
|
8
10
|
|
9
11
|
def process(_executor,from,data)
|
10
|
-
data[:tags]||=
|
11
|
-
|
12
|
-
(data[:tags][tag.to_sym]||={})[from] = value
|
13
|
-
end
|
12
|
+
data[:tags]||=Execution::TagMap.new
|
13
|
+
data[:tags].add from, @tags
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'alki/assembly/types/override'
|
2
|
+
require 'alki/assembly/types/original'
|
2
3
|
|
3
4
|
Alki do
|
4
5
|
attr :root
|
@@ -16,7 +17,7 @@ Alki do
|
|
16
17
|
data[:scope][k] = data[:prefix] + [k]
|
17
18
|
end
|
18
19
|
end
|
19
|
-
root
|
20
|
+
Alki::Assembly::Types::Original.new root
|
20
21
|
else
|
21
22
|
if overrides
|
22
23
|
data.replace(
|
@@ -40,9 +41,8 @@ Alki do
|
|
40
41
|
|
41
42
|
output = root.output(data)
|
42
43
|
add_parent_path output[:scope]
|
43
|
-
update_scope data, output[:
|
44
|
+
update_scope data, output[:scope]
|
44
45
|
output[:scope].merge! overrides.output(data)[:scope] if overrides
|
45
|
-
output[:full_scope].merge! overrides.output(data)[:full_scope] if overrides
|
46
46
|
output
|
47
47
|
end
|
48
48
|
|
@@ -1,5 +1,4 @@
|
|
1
1
|
Alki do
|
2
|
-
require 'ostruct'
|
3
2
|
require 'alki/execution/helpers'
|
4
3
|
|
5
4
|
attr(:children){ {} }
|
@@ -7,30 +6,12 @@ Alki do
|
|
7
6
|
index do
|
8
7
|
update_scope children, data[:prefix], data[:scope]
|
9
8
|
|
10
|
-
data[:tags]
|
11
|
-
|
12
|
-
tagged.each do |path,value|
|
13
|
-
if path.empty? || path[0] == key.to_sym
|
14
|
-
(tags[tag]||={})[(path[1..-1]||[])] = value
|
15
|
-
end
|
16
|
-
end
|
17
|
-
tags
|
9
|
+
if data[:tags]
|
10
|
+
data[:tags] = data[:tags].index key
|
18
11
|
end
|
19
12
|
|
20
|
-
data[:overlays]
|
21
|
-
|
22
|
-
target = target.dup
|
23
|
-
if target.size == 1 && target[0].to_s.start_with?('%')
|
24
|
-
tags = data[:tags][target[0].to_s[1..-1].to_sym]
|
25
|
-
if tags
|
26
|
-
tags.keys.each do |path|
|
27
|
-
(no[path]||=[]).push *overlays
|
28
|
-
end
|
29
|
-
end
|
30
|
-
elsif target.empty? || target.shift == key.to_sym
|
31
|
-
(no[target]||=[]).push *overlays
|
32
|
-
end
|
33
|
-
no
|
13
|
+
if data[:overlays]
|
14
|
+
data[:overlays] = data[:overlays].index key, data[:tags]
|
34
15
|
end
|
35
16
|
|
36
17
|
data[:prefix] << key
|
@@ -41,13 +22,23 @@ Alki do
|
|
41
22
|
output do
|
42
23
|
children_names = children.keys.map(&:to_sym)
|
43
24
|
{
|
44
|
-
|
45
|
-
scope: update_scope(children,data[:prefix]),
|
25
|
+
lookup_methods: update_scope(children,data[:prefix]),
|
26
|
+
scope: update_scope(children, data[:prefix], data[:scope]),
|
46
27
|
modules: [Alki::Execution::Helpers],
|
47
28
|
methods: {
|
48
29
|
children: -> {
|
49
30
|
children_names
|
50
31
|
},
|
32
|
+
elements: -> {
|
33
|
+
children.inject([]) do |elems, child_name|
|
34
|
+
child = send(child_name)
|
35
|
+
if child.respond_to?(:elements)
|
36
|
+
elems.push *child.elements
|
37
|
+
else
|
38
|
+
elems.push child
|
39
|
+
end
|
40
|
+
end
|
41
|
+
}
|
51
42
|
},
|
52
43
|
proc: ->{self}
|
53
44
|
}
|