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.
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