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
@@ -4,10 +4,10 @@ Alki do
|
|
4
4
|
attr :block
|
5
5
|
|
6
6
|
output do
|
7
|
-
|
8
|
-
value_overlays =
|
9
|
-
reference_overlays =
|
10
|
-
tags = data[:tags]
|
7
|
+
all_overlays = data[:overlays]&.overlays || []
|
8
|
+
value_overlays = all_overlays[:value] || []
|
9
|
+
reference_overlays = all_overlays[:reference] || []
|
10
|
+
tags = data[:tags]&.tags || {}
|
11
11
|
methods = {
|
12
12
|
__build__: block,
|
13
13
|
__apply_overlays__: -> obj, overlays {
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Alki
|
2
|
+
class CircularReferenceError < RuntimeError
|
3
|
+
attr_reader :chain
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@chain = []
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
"Circular Alki element reference:\n#{formatted_chain}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def formatted_chain
|
15
|
+
chain.reverse.map do |path|
|
16
|
+
p = path.join('.')
|
17
|
+
if path == chain[0]
|
18
|
+
"> #{p}"
|
19
|
+
else
|
20
|
+
" #{p}"
|
21
|
+
end
|
22
|
+
end.join("\n")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/alki/dsls/assembly.rb
CHANGED
@@ -6,14 +6,14 @@ Alki do
|
|
6
6
|
init do
|
7
7
|
ctx[:root] = build(:group,{})
|
8
8
|
ctx[:meta] = []
|
9
|
-
|
10
|
-
|
9
|
+
ctx[:addons] ||= []
|
10
|
+
ctx[:addons].each do |addon|
|
11
11
|
require_dsl addon
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
15
|
helper :add do |name,elem|
|
16
|
-
if @tags
|
16
|
+
if defined?(@tags) && @tags
|
17
17
|
ctx[:meta] << [[name.to_sym],build_meta(:tags,@tags)]
|
18
18
|
@tags = nil
|
19
19
|
end
|
@@ -58,7 +58,7 @@ Alki do
|
|
58
58
|
addon = addon.alki_addon
|
59
59
|
end
|
60
60
|
require_dsl addon
|
61
|
-
|
61
|
+
ctx[:addons] << addon
|
62
62
|
end
|
63
63
|
|
64
64
|
dsl_method :tag do |*tags,**value_tags|
|
@@ -94,7 +94,7 @@ Alki do
|
|
94
94
|
end
|
95
95
|
|
96
96
|
dsl_method :group do |name,&blk|
|
97
|
-
grp = Alki::Dsls::AssemblyGroup.build(&blk)
|
97
|
+
grp = Alki::Dsls::AssemblyGroup.build(addons: ctx[:addons], &blk)
|
98
98
|
add name, grp[:root]
|
99
99
|
update_meta name, grp[:meta]
|
100
100
|
end
|
@@ -107,10 +107,10 @@ Alki do
|
|
107
107
|
if require_path
|
108
108
|
elems = path[dir.size..-1].chomp('.rb').split('/')
|
109
109
|
*parents,basename = elems
|
110
|
-
parent_group = parents.inject(grp) do |
|
111
|
-
|
110
|
+
parent_group = parents.inject(grp) do |group,parent|
|
111
|
+
group.children[parent.to_sym] ||= build(:group)
|
112
112
|
end
|
113
|
-
parent_group.children[basename] = build :service,-> {
|
113
|
+
parent_group.children[basename.to_sym] = build :service,-> {
|
114
114
|
lookup(callable).call require_path, *args
|
115
115
|
}
|
116
116
|
end
|
@@ -123,8 +123,8 @@ Alki do
|
|
123
123
|
raise "Load command is not available without a config directory"
|
124
124
|
end
|
125
125
|
grp = Alki.load(File.join(ctx[:prefix],name))
|
126
|
-
add
|
127
|
-
update_meta
|
126
|
+
add group_name, grp.root
|
127
|
+
update_meta group_name, grp.meta
|
128
128
|
end
|
129
129
|
|
130
130
|
dsl_method :mount do |name,pkg=name.to_s,**overrides,&blk|
|
@@ -135,16 +135,32 @@ Alki do
|
|
135
135
|
update_meta name, mounted_meta
|
136
136
|
|
137
137
|
overrides = Alki::OverrideBuilder.build overrides, &blk
|
138
|
-
update_meta name, overrides
|
138
|
+
update_meta name, overrides.meta
|
139
139
|
|
140
|
-
add name, build(:assembly, klass.root, overrides
|
140
|
+
add name, build(:assembly, klass.root, overrides.root)
|
141
|
+
end
|
142
|
+
|
143
|
+
dsl_method :try_mount do |name,pkg=name.to_s,**overrides,&blk|
|
144
|
+
begin
|
145
|
+
mount name,pkg,overrides,&blk
|
146
|
+
rescue LoadError
|
147
|
+
nil
|
148
|
+
end
|
141
149
|
end
|
142
150
|
|
143
151
|
dsl_method :reference_overlay do |target,overlay,*args|
|
144
152
|
add_overlay :reference, target, overlay, args
|
145
153
|
end
|
154
|
+
|
155
|
+
dsl_method :reference_overlay_tag do |target,overlay,*args|
|
156
|
+
add_overlay :reference, "%#{target}", overlay, args
|
157
|
+
end
|
146
158
|
|
147
159
|
dsl_method :overlay do |target,overlay,*args|
|
148
160
|
add_overlay :value, target, overlay, args
|
149
161
|
end
|
162
|
+
|
163
|
+
dsl_method :overlay_tag do |target,overlay,*args|
|
164
|
+
add_overlay :value, "%#{target}", overlay, args
|
165
|
+
end
|
150
166
|
end
|
@@ -4,11 +4,11 @@ module Alki
|
|
4
4
|
module Execution
|
5
5
|
module Helpers
|
6
6
|
def lookup(*path)
|
7
|
-
path.flatten.inject(self) do |
|
7
|
+
path.flatten.inject(self) do |from_group,elem|
|
8
8
|
unless elem.is_a?(String) or elem.is_a?(Symbol)
|
9
9
|
raise ArgumentError.new("lookup can only take Strings or Symbols")
|
10
10
|
end
|
11
|
-
elem.to_s.split('.').inject(
|
11
|
+
elem.to_s.split('.').inject(from_group) do |group,name|
|
12
12
|
raise "Invalid lookup elem" unless group.is_a? Helpers
|
13
13
|
if name =~ /^\d/
|
14
14
|
group[name.to_i]
|
@@ -20,11 +20,11 @@ module Alki
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def lazy(*path)
|
23
|
-
path = path.flatten.inject('') do |
|
23
|
+
path = path.flatten.inject('') do |new_path,elem|
|
24
24
|
unless elem.is_a?(String) or elem.is_a?(Symbol)
|
25
25
|
raise ArgumentError.new("lookup can only take Strings or Symbols")
|
26
26
|
end
|
27
|
-
|
27
|
+
new_path << elem.to_s
|
28
28
|
end
|
29
29
|
Alki::ServiceDelegator.new self, path
|
30
30
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Alki
|
2
|
+
module Execution
|
3
|
+
class OverlayMap
|
4
|
+
def initialize(overlays = {})
|
5
|
+
@overlays = overlays
|
6
|
+
end
|
7
|
+
|
8
|
+
def index(key,tags)
|
9
|
+
self.class.new.tap do |new_overlays|
|
10
|
+
@overlays.each do |target,overlays|
|
11
|
+
target = target.dup
|
12
|
+
if target.size == 1 && target[0].to_s.start_with?('%')
|
13
|
+
if tags
|
14
|
+
tag = target[0].to_s[1..-1].to_sym
|
15
|
+
tags.elements_in(tag).each do |path|
|
16
|
+
new_overlays.add path, *overlays
|
17
|
+
end
|
18
|
+
end
|
19
|
+
elsif target.empty? || target.shift == key.to_sym
|
20
|
+
new_overlays.add target, *overlays
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def add(path,*overlays)
|
27
|
+
@overlays[path] ||= []
|
28
|
+
@overlays[path].push *overlays
|
29
|
+
end
|
30
|
+
|
31
|
+
def overlays
|
32
|
+
overlays = @overlays[[]] || []
|
33
|
+
overlays.sort_by(&:order).group_by(&:type)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Alki
|
2
|
+
module Execution
|
3
|
+
class TagMap
|
4
|
+
def initialize(tag_map = {})
|
5
|
+
@tag_map = tag_map
|
6
|
+
end
|
7
|
+
|
8
|
+
def add(path,tags)
|
9
|
+
tags.each do |tag,value|
|
10
|
+
@tag_map[tag] ||= {}
|
11
|
+
@tag_map[tag][path] = value
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def elements_in(tag)
|
16
|
+
@tag_map[tag]&.keys || []
|
17
|
+
end
|
18
|
+
|
19
|
+
def index(key)
|
20
|
+
new_tag_map = {}
|
21
|
+
@tag_map.each do |tag,tagged|
|
22
|
+
tagged.each do |path,value|
|
23
|
+
if path.empty? || path[0] == key.to_sym
|
24
|
+
new_tag_map[tag] ||= {}
|
25
|
+
new_path = path[1..-1] || []
|
26
|
+
new_tag_map[tag][new_path] = value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
self.class.new new_tag_map
|
31
|
+
end
|
32
|
+
|
33
|
+
def tags
|
34
|
+
Hash.new.tap do |tags|
|
35
|
+
@tag_map.each do |tag,tagged|
|
36
|
+
tags[tag] = tagged[[]]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'alki/execution/context_class_builder'
|
2
|
+
require 'alki/execution/cache_entry'
|
3
|
+
require 'concurrent'
|
4
|
+
require 'alki/invalid_path_error'
|
5
|
+
require 'alki/circular_reference_error'
|
6
|
+
|
7
|
+
module Alki
|
8
|
+
class Executor
|
9
|
+
attr_accessor :root, :meta
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@semaphore = Concurrent::ReentrantReadWriteLock.new
|
13
|
+
@lookup_cache = {}
|
14
|
+
@call_cache = {}
|
15
|
+
@context_cache = {}
|
16
|
+
@data = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def lock
|
20
|
+
@semaphore.with_write_lock do
|
21
|
+
yield
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def call(path,*args,&blk)
|
26
|
+
execute({},path,args,blk)
|
27
|
+
end
|
28
|
+
|
29
|
+
def lookup(path)
|
30
|
+
@semaphore.with_read_lock do
|
31
|
+
unless @lookup_cache[path]
|
32
|
+
@semaphore.with_write_lock do
|
33
|
+
@lookup_cache[path] = lookup_elem(path).tap do |elem|
|
34
|
+
unless elem
|
35
|
+
raise InvalidPathError.new("Invalid path #{path.inspect}")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
@lookup_cache[path]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def canonical_path(from,path)
|
45
|
+
from_elem = lookup(from)
|
46
|
+
scope = from_elem[:scope]
|
47
|
+
path.inject(nil) do |p,elem|
|
48
|
+
scope = lookup(p)[:scope] if p
|
49
|
+
scope[elem]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def execute(meta,path,args,blk)
|
54
|
+
type,value = nil,nil
|
55
|
+
@semaphore.with_read_lock do
|
56
|
+
cache_entry = @call_cache[path]
|
57
|
+
if cache_entry
|
58
|
+
if cache_entry == :building
|
59
|
+
raise Alki::CircularReferenceError.new
|
60
|
+
end
|
61
|
+
type,value = cache_entry.type,cache_entry.value
|
62
|
+
else
|
63
|
+
@semaphore.with_write_lock do
|
64
|
+
@call_cache[path] = :building
|
65
|
+
type, value = build(path)
|
66
|
+
@call_cache[path] = Alki::Execution::CacheEntry.finished type, value
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
call_value(type, value, meta, args, blk)
|
71
|
+
rescue Alki::CircularReferenceError => e
|
72
|
+
e.chain << path
|
73
|
+
raise
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def build(path)
|
79
|
+
action = lookup(path)
|
80
|
+
if action[:build]
|
81
|
+
build_meta = {building: path.join('.')}
|
82
|
+
build_meta.merge!(action[:meta]) if action[:meta]
|
83
|
+
build_action = action[:build].merge(scope: action[:scope], modules: action[:modules])
|
84
|
+
call_value(*process_action(build_action), build_meta, [action])
|
85
|
+
end
|
86
|
+
process_action action
|
87
|
+
end
|
88
|
+
|
89
|
+
def data_copy
|
90
|
+
unless @data
|
91
|
+
@data = {}
|
92
|
+
@meta.each do |(from,meta)|
|
93
|
+
meta.process self, from, @data
|
94
|
+
end
|
95
|
+
IceNine.deep_freeze @data
|
96
|
+
end
|
97
|
+
@data.dup
|
98
|
+
end
|
99
|
+
|
100
|
+
def lookup_elem(path)
|
101
|
+
data = data_copy
|
102
|
+
elem = @root
|
103
|
+
path.each do |key|
|
104
|
+
elem = elem.index data, key
|
105
|
+
return nil unless elem
|
106
|
+
end
|
107
|
+
elem.output data
|
108
|
+
end
|
109
|
+
|
110
|
+
def process_action(action)
|
111
|
+
if action.key?(:value)
|
112
|
+
[:value,action[:value]]
|
113
|
+
elsif action[:proc]
|
114
|
+
if action[:scope]
|
115
|
+
[:class,context_class(action)]
|
116
|
+
else
|
117
|
+
[:proc,action[:proc]]
|
118
|
+
end
|
119
|
+
end or raise "Invalid action"
|
120
|
+
end
|
121
|
+
|
122
|
+
def call_value(type,value,meta,args=[],blk=nil)
|
123
|
+
case type
|
124
|
+
when :value then value
|
125
|
+
when :proc then proc.call *args, &blk
|
126
|
+
when :class then value.new(self,meta).__call__ *args, &blk
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def context_class(action)
|
131
|
+
desc = {
|
132
|
+
scope: action[:scope],
|
133
|
+
body: action[:proc],
|
134
|
+
modules: action[:modules],
|
135
|
+
methods: action[:methods]
|
136
|
+
}
|
137
|
+
@context_cache[desc] ||= Alki::Execution::ContextClassBuilder.build(desc)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -1,37 +1,43 @@
|
|
1
|
-
require 'alki/
|
1
|
+
require 'alki/dsl'
|
2
|
+
require 'alki/overrides'
|
3
|
+
require 'alki/assembly/types'
|
2
4
|
|
3
5
|
module Alki
|
4
6
|
module OverrideBuilder
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
class << self
|
8
|
+
def build(override_hash=nil,&blk)
|
9
|
+
if blk
|
10
|
+
data = Alki::Dsl.build('alki/dsls/assembly_group',&blk)
|
11
|
+
Overrides.new data[:root], data[:meta]
|
12
|
+
elsif override_hash && !override_hash.empty?
|
13
|
+
Overrides.new create_override_group(override_hash), []
|
14
|
+
else
|
15
|
+
Overrides.new build_type(:group), []
|
16
|
+
end
|
12
17
|
end
|
13
|
-
end
|
14
18
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
overrides.
|
19
|
-
|
19
|
+
private
|
20
|
+
|
21
|
+
def create_override_group(overrides)
|
22
|
+
unless overrides.empty?
|
23
|
+
root = build_type(:group)
|
24
|
+
overrides.each do |path,value|
|
25
|
+
set_override root, *path.to_s.split('.'), value
|
26
|
+
end
|
27
|
+
root
|
20
28
|
end
|
21
|
-
root
|
22
29
|
end
|
23
|
-
end
|
24
30
|
|
25
|
-
|
26
|
-
|
27
|
-
|
31
|
+
def set_override(root,*parent_keys,key,value)
|
32
|
+
parent = parent_keys.inject(root) do |group,parent_key|
|
33
|
+
group.children[parent_key.to_sym] ||= build_type(:group)
|
34
|
+
end
|
35
|
+
parent.children[key.to_sym] = build_type(:value, value)
|
28
36
|
end
|
29
|
-
parent.children[key.to_sym] = build_type(:value, value)
|
30
|
-
end
|
31
37
|
|
32
|
-
|
33
|
-
|
38
|
+
def build_type(type,*args)
|
39
|
+
Assembly::Types.build(type,*args)
|
40
|
+
end
|
34
41
|
end
|
35
|
-
|
36
42
|
end
|
37
43
|
end
|