alki 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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