alki 0.12.0 → 0.12.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -0
  3. data/README.adoc +132 -20
  4. data/Rakefile +2 -27
  5. data/alki.gemspec +1 -3
  6. data/bin/bundler +17 -0
  7. data/bin/rake +17 -0
  8. data/doc/assemblies.adoc +2 -3
  9. data/doc/assembly_dsl.adoc +191 -30
  10. data/doc/{projects.adoc → executables.adoc} +40 -16
  11. data/doc/index.adoc +4 -3
  12. data/lib/alki.rb +3 -0
  13. data/lib/alki/assembly.rb +4 -34
  14. data/lib/alki/assembly/builder.rb +3 -3
  15. data/lib/alki/assembly/instance.rb +31 -5
  16. data/lib/alki/assembly/instance_builder.rb +48 -0
  17. data/lib/alki/assembly/meta/overlay.rb +7 -2
  18. data/lib/alki/assembly/meta/tags.rb +4 -4
  19. data/lib/alki/assembly/types.rb +9 -0
  20. data/lib/alki/assembly/types/assembly.rb +3 -3
  21. data/lib/alki/assembly/types/group.rb +16 -25
  22. data/lib/alki/assembly/types/original.rb +12 -0
  23. data/lib/alki/assembly/types/override.rb +0 -2
  24. data/lib/alki/assembly/types/service.rb +4 -4
  25. data/lib/alki/circular_reference_error.rb +25 -0
  26. data/lib/alki/dsls/assembly.rb +1 -1
  27. data/lib/alki/dsls/assembly_group.rb +28 -12
  28. data/lib/alki/execution/context_class_builder.rb +1 -1
  29. data/lib/alki/execution/helpers.rb +4 -4
  30. data/lib/alki/execution/overlay_map.rb +37 -0
  31. data/lib/alki/execution/tag_map.rb +42 -0
  32. data/lib/alki/executor.rb +140 -0
  33. data/lib/alki/override_builder.rb +30 -24
  34. data/lib/alki/overrides.rb +4 -0
  35. data/lib/alki/version.rb +1 -1
  36. data/test/feature/mounts_test.rb +15 -0
  37. data/test/feature/multithreading_test.rb +0 -3
  38. data/test/feature/overlays_test.rb +2 -2
  39. data/test/feature/overrides_test.rb +26 -1
  40. data/test/feature/references_test.rb +35 -0
  41. data/test/feature/try_mounts_test.rb +23 -0
  42. data/test/feature/values_test.rb +14 -0
  43. data/test/feature_test_helper.rb +1 -0
  44. data/test/fixtures/example/config/assembly.rb +17 -8
  45. data/test/fixtures/example/config/handlers.rb +10 -5
  46. data/test/fixtures/example/lib/dsls/num_handler.rb +2 -2
  47. data/test/fixtures/example/lib/example/array_output.rb +13 -0
  48. data/test/fixtures/example/lib/example/echo_handler.rb +11 -0
  49. data/test/fixtures/example/lib/example/log_overlay.rb +12 -0
  50. data/test/fixtures/example/lib/example/num_handler.rb +13 -0
  51. data/test/fixtures/example/lib/example/range_handler.rb +13 -0
  52. data/test/fixtures/example/lib/example/switch_handler.rb +11 -0
  53. metadata +39 -44
  54. data/lib/alki/assembly/executor.rb +0 -137
  55. data/test/fixtures/example/lib/array_output.rb +0 -11
  56. data/test/fixtures/example/lib/echo_handler.rb +0 -9
  57. data/test/fixtures/example/lib/log_overlay.rb +0 -10
  58. data/test/fixtures/example/lib/num_handler.rb +0 -11
  59. data/test/fixtures/example/lib/range_handler.rb +0 -11
  60. data/test/fixtures/example/lib/switch_handler.rb +0 -9
@@ -0,0 +1,12 @@
1
+ Alki do
2
+ attr :group
3
+
4
+ index do
5
+ group.index data, key
6
+ end
7
+
8
+ output do
9
+ data[:prefix] << :original
10
+ group.output(data)
11
+ end
12
+ end
@@ -1,5 +1,3 @@
1
- require 'alki/assembly/types/override'
2
-
3
1
  Alki do
4
2
  attr :main
5
3
  attr :override
@@ -4,10 +4,10 @@ Alki do
4
4
  attr :block
5
5
 
6
6
  output do
7
- overlays = (data[:overlays][[]]||[]).sort_by(&:order).group_by(&:type)
8
- value_overlays = overlays[:value]||[]
9
- reference_overlays = overlays[:reference]||[]
10
- tags = data[:tags].inject({}){|tags,(tag,tagged)| tags[tag] = tagged[[]]; 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
@@ -7,7 +7,7 @@ Alki do
7
7
  add :config_dir, build(:value, ctx[:config_dir])
8
8
  add :assembly_name, build(:value, ctx[:assembly_name])
9
9
 
10
- root = IceNine.deep_freeze ctx[:root]
10
+ root = ctx[:root]
11
11
  meta = IceNine.deep_freeze ctx[:meta]
12
12
  add_class_method :root do
13
13
  root
@@ -6,14 +6,14 @@ Alki do
6
6
  init do
7
7
  ctx[:root] = build(:group,{})
8
8
  ctx[:meta] = []
9
- @addons = Array ctx[:addons]
10
- @addons.each do |addon|
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
- @addons << addon
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 |grp,parent|
111
- grp.children[parent] ||= build(:group)
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 name, grp.root
127
- update_meta name, grp.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[:meta]
138
+ update_meta name, overrides.meta
139
139
 
140
- add name, build(:assembly, klass.root, overrides[:root])
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
@@ -13,7 +13,7 @@ module Alki
13
13
  else
14
14
  methods = {}
15
15
  end
16
- (config[:scope]||{}).each do |name,path|
16
+ (config[:lookup_methods]||config[:scope]||{}).each do |name,path|
17
17
  methods[name] = {
18
18
  body:->(*args,&blk) {
19
19
  __execute__ path, args, blk
@@ -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 |group,elem|
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(group) do |group,name|
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 |path,elem|
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
- path << elem.to_s
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/dsls/assembly'
1
+ require 'alki/dsl'
2
+ require 'alki/overrides'
3
+ require 'alki/assembly/types'
2
4
 
3
5
  module Alki
4
6
  module OverrideBuilder
5
- def self.build(override_hash=nil,&blk)
6
- if blk
7
- Alki::Dsls::AssemblyGroup.build(&blk)
8
- elsif override_hash && !override_hash.empty?
9
- { root: create_override_group(override_hash), meta: [] }
10
- else
11
- { root: nil, meta: [] }
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
- def self.create_override_group(overrides)
16
- unless overrides.empty?
17
- root = build_type(:group)
18
- overrides.each do |path,value|
19
- set_override root, *path.to_s.split('.'), value
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
- def self.set_override(root,*parent_keys,key,value)
26
- parent = parent_keys.inject(root) do |parent,key|
27
- parent.children[key.to_sym] ||= build_type(:group)
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
- def self.build_type(type,*args)
33
- Alki.load("alki/assembly/types/#{type}").new *args
38
+ def build_type(type,*args)
39
+ Assembly::Types.build(type,*args)
40
+ end
34
41
  end
35
-
36
42
  end
37
43
  end