alki 0.8.0 → 0.9.0

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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +108 -6
  3. data/alki.gemspec +2 -2
  4. data/bin/alki +17 -0
  5. data/config/dsls.rb +2 -2
  6. data/doc/assembly_dsl.adoc +6 -6
  7. data/exe/alki +275 -0
  8. data/lib/alki/assembly/builder.rb +113 -0
  9. data/lib/alki/assembly/executor.rb +127 -0
  10. data/lib/alki/assembly/handler_base.rb +21 -0
  11. data/lib/alki/assembly/instance.rb +21 -0
  12. data/lib/alki/assembly/types/assembly.rb +92 -0
  13. data/lib/alki/assembly/types/factory.rb +29 -0
  14. data/lib/alki/assembly/types/func.rb +12 -0
  15. data/lib/alki/assembly/types/group.rb +39 -0
  16. data/lib/alki/assembly/types/override.rb +40 -0
  17. data/lib/alki/assembly/types/proc_value.rb +18 -0
  18. data/lib/alki/assembly/types/service.rb +27 -0
  19. data/lib/alki/assembly/types/value.rb +9 -0
  20. data/lib/alki/assembly.rb +21 -40
  21. data/lib/alki/dsls/assembly.rb +10 -12
  22. data/lib/alki/dsls/assembly_group.rb +92 -0
  23. data/lib/alki/dsls/assembly_type.rb +5 -15
  24. data/lib/alki/execution/cache_entry.rb +16 -0
  25. data/lib/alki/execution/context.rb +29 -0
  26. data/lib/alki/execution/context_class_builder.rb +36 -0
  27. data/lib/alki/execution/value_context.rb +14 -0
  28. data/lib/alki/overlay_delegator.rb +8 -20
  29. data/lib/alki/overlay_info.rb +3 -0
  30. data/lib/alki/override_builder.rb +6 -4
  31. data/lib/alki/service_delegator.rb +3 -3
  32. data/lib/alki/version.rb +1 -1
  33. data/lib/alki.rb +4 -4
  34. data/test/feature/alki_test.rb +1 -2
  35. data/test/feature/example_test.rb +2 -3
  36. data/test/feature/factories_test.rb +48 -0
  37. data/test/feature/overlays_test.rb +225 -0
  38. data/test/feature/overrides_test.rb +1 -2
  39. data/test/feature/pseudo_elements_test.rb +67 -0
  40. data/test/feature_test_helper.rb +1 -0
  41. data/test/fixtures/example/config/assembly.rb +11 -2
  42. data/test/fixtures/example/config/handlers.rb +2 -7
  43. data/test/fixtures/example/lib/log_overlay.rb +3 -3
  44. data/test/integration/dsls/assembly_test.rb +3 -8
  45. data/test/integration/dsls/assembly_type_test.rb +2 -2
  46. data/test/integration/dsls/service_dsl_test.rb +2 -2
  47. metadata +36 -18
  48. data/lib/alki/assembly_builder.rb +0 -109
  49. data/lib/alki/assembly_executor.rb +0 -129
  50. data/lib/alki/assembly_handler_base.rb +0 -19
  51. data/lib/alki/dsls/assembly_type_dsl.rb +0 -21
  52. data/lib/alki/dsls/assembly_types/assembly.rb +0 -101
  53. data/lib/alki/dsls/assembly_types/group.rb +0 -41
  54. data/lib/alki/dsls/assembly_types/load.rb +0 -31
  55. data/lib/alki/dsls/assembly_types/overlay.rb +0 -9
  56. data/lib/alki/dsls/assembly_types/value.rb +0 -100
  57. data/test/test_helper.rb +0 -1
@@ -0,0 +1,127 @@
1
+ require 'alki/execution/context_class_builder'
2
+ require 'alki/execution/cache_entry'
3
+ require 'thread'
4
+
5
+ module Alki
6
+ InvalidPathError = Class.new(StandardError)
7
+ module Assembly
8
+ class Executor
9
+ def initialize(assembly,overlays)
10
+ @assembly = assembly
11
+ @overlays = overlays
12
+ @data = {}
13
+ @semaphore = Monitor.new
14
+ clear
15
+ end
16
+
17
+ def clear
18
+ @lookup_cache = {}
19
+ @call_cache = {}
20
+ @context_cache = {}
21
+ @processed_overlays = false
22
+ end
23
+
24
+ def call(path,*args,&blk)
25
+ execute({},path,args,blk)
26
+ end
27
+
28
+ def execute(meta,path,args,blk)
29
+ cache_entry = @call_cache[path]
30
+ if cache_entry
31
+ if cache_entry.status == :building
32
+ raise "Circular element reference found"
33
+ end
34
+ else
35
+ @semaphore.synchronize do
36
+ cache_entry = @call_cache[path]
37
+ unless cache_entry
38
+ cache_entry = @call_cache[path] = Alki::Execution::CacheEntry.new
39
+ action = lookup(path)
40
+ if action[:build]
41
+ build_meta = meta.merge(building: path.join('.'))
42
+ build_action = action[:build].merge(scope: action[:scope],modules: action[:modules])
43
+ call_value(*process_action(build_action),build_meta,[action])
44
+ end
45
+ cache_entry.finish *process_action(action)
46
+ end
47
+ end
48
+ end
49
+ call_value(cache_entry.type,cache_entry.value,meta,args,blk)
50
+ end
51
+
52
+ private
53
+
54
+ def process_overlays
55
+ unless @processed_overlays
56
+ @processed_overlays = true
57
+ @data[:overlays] = {}
58
+ @overlays.each do |(from,info)|
59
+ target = canonical_path(from,info.target) or
60
+ raise InvalidPathError.new("Invalid overlay target #{info.target.join('.')}")
61
+ overlay = canonical_path(from,info.overlay) or
62
+ raise InvalidPathError.new("Invalid overlay path #{info.overlay.join('.')}")
63
+ (@data[:overlays][target]||=[]) << [overlay,info.args]
64
+ end
65
+ end
66
+ end
67
+
68
+ def lookup(path)
69
+ process_overlays
70
+ @lookup_cache[path] ||= lookup_elem(path).tap do |elem|
71
+ unless elem
72
+ raise InvalidPathError.new("Invalid path #{path.inspect}")
73
+ end
74
+ end
75
+ end
76
+
77
+ def lookup_elem(path)
78
+ data = @data.dup
79
+ elem = @assembly
80
+ path.each do |key|
81
+ elem = elem.index data, key
82
+ return nil unless elem
83
+ end
84
+ elem.output data
85
+ end
86
+
87
+
88
+ def canonical_path(from,path)
89
+ scope = lookup(from)[:full_scope]
90
+ path.inject(nil) do |p,elem|
91
+ scope = lookup(p)[:scope] if p
92
+ scope[elem]
93
+ end
94
+ end
95
+
96
+ def process_action(action)
97
+ if action.key?(:value)
98
+ [:value,action[:value]]
99
+ elsif action[:proc]
100
+ if action[:scope]
101
+ [:class,context_class(action)]
102
+ else
103
+ [:proc,action[:proc]]
104
+ end
105
+ end or raise "Invalid action"
106
+ end
107
+
108
+ def call_value(type,value,meta,args=[],blk=nil)
109
+ case type
110
+ when :value then value
111
+ when :proc then proc.call *args, &blk
112
+ when :class then value.new(self,meta).__call__ *args, &blk
113
+ end
114
+ end
115
+
116
+ def context_class(action)
117
+ desc = {
118
+ scope: action[:scope],
119
+ body: action[:proc],
120
+ modules: action[:modules],
121
+ methods: action[:methods]
122
+ }
123
+ @context_cache[desc] ||= Alki::Execution::ContextClassBuilder.build(desc)
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,21 @@
1
+ module Alki
2
+ module Assembly
3
+ class HandlerBase
4
+ def initialize(elem,data,key=nil)
5
+ @elem = elem
6
+ @data = data
7
+ @key = key
8
+ end
9
+
10
+ attr_reader :elem, :data, :key
11
+
12
+ def index
13
+ raise NotImplementedError.new("Can't index into this element")
14
+ end
15
+
16
+ def output
17
+ raise NotImplementedError.new("Can't output this element")
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module Alki
2
+ module Assembly
3
+ class Instance
4
+ def initialize(executor)
5
+ @executor = executor
6
+ end
7
+
8
+ def root
9
+ @root ||= @executor.call []
10
+ end
11
+
12
+ def respond_to_missing?(name,include_all)
13
+ root.respond_to? name
14
+ end
15
+
16
+ def method_missing(name,*args,&blk)
17
+ root.send name, *args, &blk
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,92 @@
1
+ require 'alki/assembly/types/override'
2
+
3
+ Alki do
4
+ attr :root
5
+ attr(:overrides) { nil }
6
+
7
+ index do
8
+ data[:scope] ||= {}
9
+ data[:prefix] ||= []
10
+ data[:overlays] ||= {}
11
+
12
+ if key == :original
13
+ update_main data
14
+ if overrides
15
+ overrides.children.keys.each do |k|
16
+ data[:scope][k] = data[:prefix] + [k]
17
+ end
18
+ end
19
+ root
20
+ else
21
+ if overrides
22
+ data.replace(
23
+ main: deep_copy(data),
24
+ override: data.dup,
25
+ )
26
+ update_main data[:main]
27
+ update_override data[:override]
28
+ override.index data, key
29
+ else
30
+ update_main data
31
+ root.index data, key
32
+ end
33
+ end
34
+ end
35
+
36
+ output do
37
+ data[:scope] ||= {}
38
+ data[:prefix] ||= []
39
+ data[:overlays] ||= {}
40
+
41
+ output = root.output(data)
42
+ add_parent_path output[:scope]
43
+ update_scope data, output[:full_scope]
44
+ output[:scope].merge! overrides.output(data)[:scope] if overrides
45
+ output[:full_scope].merge! overrides.output(data)[:full_scope] if overrides
46
+ output
47
+ end
48
+
49
+ def deep_copy(val)
50
+ if val.is_a?(Hash)
51
+ val.inject({}) do |h,(k,v)|
52
+ h[k] = deep_copy v
53
+ h
54
+ end
55
+ elsif val.is_a?(Array)
56
+ val.inject([]) do |a,v|
57
+ a.push deep_copy v
58
+ end
59
+ else
60
+ val
61
+ end
62
+ end
63
+
64
+ def update_main(data)
65
+ data[:scope] = {assembly: data[:scope][:assembly], root: []}
66
+ update_scope data
67
+ data[:scope][:assembly] = data[:prefix].dup
68
+ end
69
+
70
+ def update_override(data)
71
+ data[:scope][:root] ||= []
72
+ add_original data[:scope], data
73
+ end
74
+
75
+ def update_scope(data,scope=data[:scope])
76
+ add_parent_path scope, data
77
+ add_original scope, data
78
+ end
79
+
80
+ def add_parent_path(scope,data=self.data)
81
+ parent_path = data[:scope][:assembly]||nil
82
+ scope[:parent] = parent_path if parent_path
83
+ end
84
+
85
+ def add_original(scope,data=self.data)
86
+ scope[:original] = data[:prefix] + [:original]
87
+ end
88
+
89
+ def override
90
+ Alki::Assembly::Types::Override.new root, overrides
91
+ end
92
+ end
@@ -0,0 +1,29 @@
1
+ Alki do
2
+ require 'alki/execution/value_context'
3
+
4
+ attr :block
5
+
6
+ output do
7
+ {
8
+ modules: [Alki::Execution::ValueContext],
9
+ scope: data[:scope],
10
+ build: {
11
+ methods: {
12
+ __build__: block
13
+ },
14
+ proc: ->(desc) {
15
+ desc[:methods] = {
16
+ __create__: __build__
17
+ }
18
+ desc[:proc] = ->(*args,&blk) {
19
+ if !args.empty? || blk
20
+ __create__ *args, &blk
21
+ else
22
+ method(:__create__)
23
+ end
24
+ }
25
+ }
26
+ }
27
+ }
28
+ end
29
+ end
@@ -0,0 +1,12 @@
1
+ Alki do
2
+ require 'alki/execution/value_context'
3
+
4
+ attr :block
5
+ output do
6
+ {
7
+ modules: [Alki::Execution::ValueContext],
8
+ scope: data[:scope],
9
+ proc: block
10
+ }
11
+ end
12
+ end
@@ -0,0 +1,39 @@
1
+ Alki do
2
+ require 'alki/execution/context'
3
+
4
+ attr(:children){ {} }
5
+
6
+ index do
7
+ update_scope children, data[:prefix], data[:scope]
8
+
9
+ data[:overlays] = data[:overlays].inject({}) do |no,(target,overlays)|
10
+ target = target.dup
11
+ if target.empty? || target.shift == key.to_sym
12
+ (no[target]||=[]).push *overlays
13
+ end
14
+ no
15
+ end
16
+
17
+ data[:prefix] << key
18
+
19
+ children[key]
20
+ end
21
+
22
+ output do
23
+ {
24
+ full_scope: update_scope(children, data[:prefix], data[:scope]),
25
+ scope: update_scope(children,data[:prefix]),
26
+ modules: [Alki::Execution::Context],
27
+ proc: ->{self}
28
+ }
29
+ end
30
+
31
+ def update_scope(children, prefix, scope=nil)
32
+ scope ||= {}
33
+ prefix ||= []
34
+ children.keys.inject(scope) do |h,k|
35
+ h.merge! k => (prefix+[k])
36
+ end
37
+ scope
38
+ end
39
+ end
@@ -0,0 +1,40 @@
1
+ require 'alki/assembly/types/override'
2
+
3
+ Alki do
4
+ attr :main
5
+ attr :override
6
+
7
+ index do
8
+ main_child = main.index data[:main], key
9
+ override_child = override.index data[:override], key
10
+
11
+ if main_child && override_child
12
+ (data[:main][:scope]||={}).merge! (data[:override][:scope]||{})
13
+ data[:main][:overlays]||={}
14
+ if data[:override][:overlays]
15
+ data[:override][:overlays].each do |target,overlays|
16
+ (data[:main][:overlays][target]||=[]).push *overlays
17
+ end
18
+ end
19
+ data[:override][:overlays]=data[:main][:overlays].dup
20
+ Alki::Assembly::Types::Override.new main_child, override_child
21
+ elsif main_child
22
+ data.replace data[:main]
23
+ main_child
24
+ elsif override_child
25
+ data.replace data[:override]
26
+ override_child
27
+ end
28
+ end
29
+
30
+ output do
31
+ result = override.output(data[:override])
32
+ if result[:type] == :group
33
+ main_result = main.output(data[:main])
34
+ if main_result[:type] == :group
35
+ result[:scope] = main_result[:scope].merge result[:scope]
36
+ end
37
+ end
38
+ result
39
+ end
40
+ end
@@ -0,0 +1,18 @@
1
+ Alki do
2
+ require 'alki/execution/value_context'
3
+
4
+ attr :proc
5
+
6
+ output do
7
+ {
8
+ build: {
9
+ methods: {
10
+ __build__: proc
11
+ },
12
+ proc: ->(desc) {desc[:value] = __build__}
13
+ },
14
+ modules: [Alki::Execution::ValueContext],
15
+ scope: data[:scope]
16
+ }
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ Alki do
2
+ require 'alki/execution/value_context'
3
+
4
+ attr :block
5
+
6
+ output do
7
+ overlays = data[:overlays][[]]||[]
8
+ {
9
+ build: {
10
+ methods: {
11
+ __build__: block
12
+ },
13
+ proc: -> (elem) {
14
+ elem[:value] = overlays.inject(__build__) do |val,(overlay,args)|
15
+ overlay = root.lookup(overlay)
16
+ if !overlay.respond_to?(:call) && overlay.respond_to?(:new)
17
+ overlay = overlay.method(:new)
18
+ end
19
+ overlay.call val, *args
20
+ end
21
+ },
22
+ },
23
+ modules: [Alki::Execution::ValueContext],
24
+ scope: data[:scope],
25
+ }
26
+ end
27
+ end
@@ -0,0 +1,9 @@
1
+ Alki do
2
+ attr :value
3
+
4
+ output do
5
+ {
6
+ value: value
7
+ }
8
+ end
9
+ end
data/lib/alki/assembly.rb CHANGED
@@ -1,53 +1,34 @@
1
- require 'alki/assembly_executor'
2
1
  require 'alki/override_builder'
3
- require 'alki/dsls/assembly'
2
+ require 'alki/assembly/types/assembly'
3
+ require 'alki/assembly/types/group'
4
+ require 'alki/assembly/instance'
5
+ require 'alki/assembly/executor'
4
6
 
5
7
  module Alki
6
8
  module Assembly
7
9
  def new(overrides={},&blk)
8
- Alki::Assembly::Instance.new create_assembly(overrides,&blk), self.assembly_options
9
- end
10
+ overrides_info = OverrideBuilder.build(overrides,&blk)
11
+ override_root = overrides_info[:root] || build(:group)
10
12
 
11
- def root
12
- self.definition.root
13
- end
13
+ assembly = build :assembly, root, override_root
14
+ executor = Executor.new(assembly, overlays+overrides_info[:overlays])
14
15
 
15
- private
16
-
17
- def create_assembly(overrides={},&blk)
18
- config_dir = if assembly_options[:load_path]
19
- Alki::Support.load_class("alki/assembly_types/value").new assembly_options[:load_path]
20
- else
21
- nil
22
- end
23
-
24
- Alki::Support.load_class("alki/assembly_types/assembly").new root, config_dir, OverrideBuilder.build(overrides,&blk)
16
+ override_root.children[:assembly_instance] = build(:service,->{
17
+ Instance.new(executor)
18
+ })
19
+ executor.call [:assembly_instance]
25
20
  end
26
21
 
27
- class Instance
28
- def initialize(assembly,opts)
29
- @assembly = assembly
30
- @cache = {}
31
- @opts = opts
32
- end
33
-
34
- def root
35
- @root ||= __executor__.call @assembly, @cache, []
36
- end
37
-
38
- def respond_to_missing?(name,include_all)
39
- root.respond_to? name
40
- end
41
-
42
- def method_missing(name,*args,&blk)
43
- root.send name, *args, &blk
44
- end
22
+ def build(type,*args)
23
+ Alki::Support.load_class("alki/assembly/types/#{type}").new *args
24
+ end
45
25
 
46
- private
26
+ def root
27
+ self.definition.root
28
+ end
47
29
 
48
- def __executor__
49
- @executor ||= Alki::AssemblyExecutor.new @opts
50
- end
30
+ def overlays
31
+ self.definition.overlays
51
32
  end
52
33
  end
53
- end
34
+ end
@@ -1,20 +1,18 @@
1
1
  Alki do
2
2
  require_dsl 'alki/dsls/class'
3
- require_dsl 'alki/dsls/assembly_types/group'
4
- require_dsl 'alki/dsls/assembly_types/value'
5
- require_dsl 'alki/dsls/assembly_types/assembly'
6
- require_dsl 'alki/dsls/assembly_types/load'
7
- require_dsl 'alki/dsls/assembly_types/overlay'
8
-
9
- init do
10
- ctx[:elems] = {}
11
- ctx[:overlays] = []
12
- end
3
+ require_dsl 'alki/dsls/assembly_group'
13
4
 
14
5
  finish do
15
- ctx[:root] = root = build_group(ctx.delete(:elems), ctx.delete(:overlays))
6
+ add :config_dir, build(:value, ctx[:config_dir])
7
+ prefix_overlays :original, ctx[:overlays]
8
+
9
+ root = ctx[:root]
10
+ overlays = ctx[:overlays]
16
11
  add_class_method :root do
17
12
  root
18
13
  end
14
+ add_class_method :overlays do
15
+ overlays
16
+ end
19
17
  end
20
- end
18
+ end
@@ -0,0 +1,92 @@
1
+ require 'alki/override_builder'
2
+ require 'alki/support'
3
+ require 'alki/overlay_info'
4
+
5
+ Alki do
6
+ require_dsl 'alki/dsls/dsl'
7
+
8
+ init do
9
+ ctx[:root] = build(:group,{})
10
+ ctx[:overlays] = []
11
+ end
12
+
13
+ helper :add do |name,elem|
14
+ ctx[:root].children[name.to_sym] = elem
15
+ nil
16
+ end
17
+
18
+ helper :build do |type,*args|
19
+ Alki::Support.load_class("alki/assembly/types/#{type}").new *args
20
+ end
21
+
22
+ helper :prefix_overlays do |*prefix,overlays|
23
+ overlays.each do |overlay|
24
+ overlay[0].unshift *prefix
25
+ end
26
+ overlays
27
+ end
28
+
29
+ helper :update_overlays do |*prefix,overlays|
30
+ ctx[:overlays].push *prefix_overlays(*prefix,overlays)
31
+ end
32
+
33
+ dsl_method :config_dir do
34
+ ctx[:config_dir]
35
+ end
36
+
37
+ dsl_method :set do |name,value=nil,&blk|
38
+ if blk
39
+ add name, build(:proc_value, blk)
40
+ else
41
+ add name, build(:value, value)
42
+ end
43
+ end
44
+
45
+ dsl_method :service do |name,&blk|
46
+ add name, build(:service, blk)
47
+ end
48
+
49
+ dsl_method :factory do |name,&blk|
50
+ add name, build(:factory, blk)
51
+ end
52
+
53
+ dsl_method :func do |name,&blk|
54
+ add name, build(:func, blk)
55
+ end
56
+
57
+ dsl_method :group do |name,&blk|
58
+ grp = Alki::Dsls::AssemblyGroup.build(&blk)
59
+ add name, grp[:root]
60
+ update_overlays name, grp[:overlays]
61
+ end
62
+
63
+ dsl_method :load do |group_name,name=group_name.to_s|
64
+ grp = Alki::Dsl.load(File.expand_path(name+'.rb',ctx[:config_dir]))[:class]
65
+ add name, grp.root
66
+ update_overlays name, grp.overlays
67
+ end
68
+
69
+ dsl_method :mount do |name,pkg=name.to_s,**overrides,&blk|
70
+ klass = Alki::Support.load_class pkg
71
+ mounted_assemblies = klass.overlays.map do |(path,info)|
72
+ [path.dup,info]
73
+ end
74
+ update_overlays name, mounted_assemblies
75
+
76
+ overrides = Alki::OverrideBuilder.build overrides, &blk
77
+ update_overlays name, overrides[:overlays]
78
+
79
+ add name, build(:assembly, klass.root, overrides[:root])
80
+ end
81
+
82
+ dsl_method :overlay do |target,overlay,*args|
83
+ (ctx[:overlays]||=[]) << [
84
+ [],
85
+ Alki::OverlayInfo.new(
86
+ target.to_s.split('.').map(&:to_sym),
87
+ overlay.to_s.split('.').map(&:to_sym),
88
+ args
89
+ )
90
+ ]
91
+ end
92
+ end
@@ -1,10 +1,10 @@
1
- require 'alki/assembly_handler_base'
1
+ require 'alki/assembly/handler_base'
2
2
 
3
3
  Alki do
4
4
  require_dsl 'alki/dsls/class'
5
5
 
6
6
  init do
7
- set_super_class Alki::AssemblyHandlerBase, subclass: 'Handler'
7
+ set_super_class Alki::Assembly::HandlerBase, subclass: 'Handler'
8
8
 
9
9
  add_method :handler, private: true do |*args|
10
10
  self.class::Handler.new(self,*args)
@@ -18,23 +18,13 @@ Alki do
18
18
  handler(*args).output
19
19
  end
20
20
 
21
- add_method :lookup do |path,data={}|
22
- data = data.dup
23
- elem = self
24
- path.each do |key|
25
- elem = elem.index data, key
26
- return nil unless elem
27
- end
28
- elem.output data
29
- end
30
-
31
21
  # Add defined methods to handler class
32
22
  class_builder('Handler')[:module] = class_builder[:module]
33
23
  end
34
24
 
35
- dsl_method :attr do |name,default=nil|
25
+ dsl_method :attr do |name,&default|
36
26
  add_delegator name, :@elem, subclass: 'Handler'
37
- add_initialize_param name, default
27
+ add_initialize_param name, &default
38
28
  add_accessor name
39
29
  end
40
30
 
@@ -45,4 +35,4 @@ Alki do
45
35
  dsl_method :output do |&blk|
46
36
  add_method :output, subclass: 'Handler', &blk
47
37
  end
48
- end
38
+ end