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
@@ -1,36 +1,48 @@
1
- Setting up Alki Projects
2
- ========================
1
+ Creating Executables
2
+ ====================
3
3
  :toc:
4
4
 
5
- Generally, projects that use alki work the same as normal Ruby projects but there are a couple of
6
- guidelines to make life easier.
5
+ Generally, projects that use alki are setup the same as normal Ruby projects but there are a couple of
6
+ few differences plus some common conventions.
7
7
 
8
- Alki projects should always use Bundler and have a `Gemfile` in the project root. It is also optional
9
- that the project itself be a gem, with a `<project name>.gemspec` file in the project root and a `gemspec`
10
- line in the `Gemfile`.
11
8
 
12
-
13
- Using Alki without a gemspec
14
- ----------------------------
9
+ Without a gemspec
10
+ -----------------
15
11
 
16
12
  If your project isn't a gem, and doesn't have a gemspec, executables should go in `bin` and should include these two
17
13
  lines before `require`-ing any other files.
18
14
  ```ruby
19
15
  require 'bundler/setup'
20
- require 'alki/bin`
16
+ require 'alki/bin'
21
17
  ```
22
18
 
23
- After that you can require your library files and run them.
19
+ After that you can use your library.
20
+
21
+ .bin/my_exe
22
+ ```ruby
23
+ require 'bundler/setup'
24
+ require 'alki/bin'
25
+
26
+ require 'my_project'
24
27
 
25
- Using Alki with a gemspec
26
- -------------------------
28
+ MyProject.new.cli.run
29
+ ```
30
+
31
+ ```
32
+ $ bin/my_exe
33
+ ...
34
+ ```
35
+
36
+
37
+ With a gemspec
38
+ --------------
27
39
 
28
40
  If your project *is* a gem, you'll do things a bit differently.
29
41
 
30
42
  First, in your gemspec, set the `bindir` option to `'exe'` (not `'bin'`). Also if automatically setting
31
43
  the `executables` option, make sure it is looking for files in `exe`, not `bin`.
32
44
 
33
- As noted, executables should be placed in `exe`, not `bin`.
45
+ Executables should be placed in `exe`, not `bin`.
34
46
 
35
47
  Finally, once your gemspec is setup, run `bundle binstubs <gem name>` where `<gem name>` is whatever
36
48
  you set `spec.name` to in your gemspec.
@@ -40,5 +52,17 @@ should run for testing/development. If you add new executables to `exe`, just ru
40
52
  generate new binstubs for them.
41
53
 
42
54
  Second, executables in `exe` shouldn't require any extra files other then the project files they need to
43
- run (no `bundle/setup` or `alki/bin`).
55
+ run (**do not require `bundle/setup` or `alki/bin`**).
56
+
57
+ .exe/my_exe
58
+ ```ruby
59
+ require 'my_gem'
44
60
 
61
+ MyGem.new.cli.run
62
+ ```
63
+
64
+ ```
65
+ $ bundle binstubs my_gem
66
+ $ bin/my_exe
67
+ ...
68
+ ```
@@ -6,11 +6,12 @@ Alki is a framework for creating projects that are modular, testable, and well o
6
6
 
7
7
  It's goal is to remove uncertainty and friction when building Ruby projects, allowing developers to focus on implementing business logic.
8
8
 
9
- :leveloffset: 1
10
- include::projects.adoc[]
11
-
12
9
  :leveloffset: 1
13
10
  include::assemblies.adoc[]
14
11
 
15
12
  :leveloffset: 1
16
13
  include::assembly_dsl.adoc[]
14
+
15
+ :leveloffset: 1
16
+ include::executables.adoc[]
17
+
@@ -13,3 +13,6 @@ def Alki.create_assembly(opts={},&blk)
13
13
  Alki::Assembly::Builder.build(opts,&blk)
14
14
  end
15
15
 
16
+ def Alki.singleton_assembly(opts={},&blk)
17
+ Alki.create_assembly(opts,&blk).new
18
+ end
@@ -1,35 +1,11 @@
1
- require 'alki/override_builder'
2
1
  require 'alki/assembly/instance'
3
- require 'alki/assembly/executor'
4
- require 'alki/assembly/meta/overlay'
5
- require 'alki/overlay_info'
6
- require 'ice_nine'
2
+ require 'alki/override_builder'
7
3
 
8
4
  module Alki
9
5
  module Assembly
10
- def new(overrides={},&blk)
11
- Instance.new load_class, [overrides, blk]
12
- end
13
-
14
- def raw_instance(overrides,blk,&wrapper)
15
- overrides_info = OverrideBuilder.build(overrides,&blk)
16
- override_root = overrides_info[:root] || build(:group)
17
-
18
- assembly = build :assembly, root, override_root
19
- update_instance_overlay = [[],Meta::Overlay.new(
20
- :value,
21
- [:assembly_instance],
22
- ->obj{wrapper.call obj},
23
- []
24
- )]
25
- all_meta = meta+overrides_info[:meta]+[update_instance_overlay]
26
- IceNine.deep_freeze all_meta
27
- executor = Executor.new(assembly, all_meta)
28
-
29
- override_root.children[:assembly_instance] = build(:service,->{ root })
30
- override_root.children[:assembly_executor] = build(:value,executor)
31
-
32
- executor.call [:assembly_instance]
6
+ def new(override_values={},&override_blk)
7
+ overrides = OverrideBuilder.build override_values, &override_blk
8
+ Instance.new load_class, overrides
33
9
  end
34
10
 
35
11
  def root
@@ -39,11 +15,5 @@ module Alki
39
15
  def meta
40
16
  self.definition.meta
41
17
  end
42
-
43
- private
44
-
45
- def build(type,*args)
46
- Alki.load("alki/assembly/types/#{type}").new *args
47
- end
48
18
  end
49
19
  end
@@ -46,8 +46,8 @@ module Alki
46
46
 
47
47
  def setup_project_assembly(path)
48
48
  root = Alki::Support.find_root(path) do |dir|
49
- File.exists?(File.join(dir,'config',"#{@primary_config}.rb")) ||
50
- File.exists?(File.join(dir,'Gemfile')) ||
49
+ File.exist?(File.join(dir,'config',"#{@primary_config}.rb")) ||
50
+ File.exist?(File.join(dir,'Gemfile')) ||
51
51
  !Dir.glob(File.join(dir,'*.gemspec')).empty?
52
52
  end
53
53
  if root
@@ -62,7 +62,7 @@ module Alki
62
62
 
63
63
  unless @config_dir
64
64
  config_dir = File.join(root,'config')
65
- @config_dir = config_dir if File.exists? config_dir
65
+ @config_dir = config_dir if File.exist? config_dir
66
66
  end
67
67
  end
68
68
  end
@@ -1,16 +1,19 @@
1
1
  require 'delegate'
2
2
  require 'concurrent'
3
3
  require 'alki/support'
4
+ require 'alki/assembly/instance_builder'
4
5
 
5
6
  module Alki
6
7
  module Assembly
7
8
  class Instance < Delegator
8
- def initialize(assembly_module,args)
9
+ def initialize(assembly_module,overrides)
9
10
  @assembly_module = assembly_module
10
- @args = args
11
+ @overrides = overrides
11
12
  @version = 0
12
13
  @needs_load = true
13
14
  @lock = Concurrent::ReentrantReadWriteLock.new
15
+ @obj = nil
16
+ @executor = nil
14
17
  end
15
18
 
16
19
  def __reload__
@@ -30,6 +33,13 @@ module Alki
30
33
  end
31
34
  end
32
35
 
36
+ def __executor__
37
+ @lock.with_read_lock do
38
+ __load__ if @needs_load
39
+ @executor
40
+ end
41
+ end
42
+
33
43
  def __getobj__
34
44
  @lock.with_read_lock do
35
45
  __load__ if @needs_load
@@ -37,15 +47,31 @@ module Alki
37
47
  end
38
48
  end
39
49
 
50
+ def to_s
51
+ inspect
52
+ end
53
+
54
+ def inspect
55
+ if @assembly_module.is_a?(Module)
56
+ name = @assembly_module.name || 'AnonymousAssembly'
57
+ else
58
+ name = Alki::Support.classify(@assembly_module.to_s)
59
+ end
60
+ "#<#{name}:#{object_id}>"
61
+ end
62
+
63
+ def pretty_print(q)
64
+ q.text(inspect)
65
+ end
40
66
  private
41
67
 
42
68
  def __load__
43
- # Calls __setobj__
44
69
  @lock.with_write_lock do
45
70
  @needs_load = false
46
71
  @obj.__unload__ if @obj.respond_to?(:__unload__)
47
- Alki.load(@assembly_module).raw_instance *@args do |obj|
48
- @obj = obj
72
+ InstanceBuilder.build @assembly_module, @overrides do |instance,executor|
73
+ @obj = instance
74
+ @executor = executor
49
75
  self
50
76
  end
51
77
  end
@@ -0,0 +1,48 @@
1
+ require 'alki/assembly/types'
2
+ require 'alki/executor'
3
+ require 'alki/override_builder'
4
+ require 'alki/assembly/meta/overlay'
5
+ require 'alki/overrides'
6
+ require 'ice_nine'
7
+
8
+ module Alki
9
+ module Assembly
10
+ module InstanceBuilder
11
+ class << self
12
+ def build(assembly,overrides,&instance_wrapper)
13
+ assembly = Alki.load(assembly)
14
+ executor = Executor.new
15
+
16
+ overrides = inject_assembly_instance overrides, instance_wrapper, executor
17
+
18
+ executor.root = Types.build :assembly, assembly.root, overrides.root
19
+ executor.meta = IceNine.deep_freeze(assembly.meta+overrides.meta)
20
+
21
+ executor.call [:assembly_instance]
22
+ end
23
+
24
+ private
25
+
26
+ def inject_assembly_instance(overrides,instance_wrapper,executor)
27
+ root = overrides.root.dup
28
+ root.children = root.children.merge(assembly_instance: assembly_instance)
29
+ meta = overrides.meta + [wrap_assembly_instance(instance_wrapper,executor)]
30
+ Overrides.new(root,meta)
31
+ end
32
+
33
+ def assembly_instance
34
+ Types.build(:service,-> { root })
35
+ end
36
+
37
+ def wrap_assembly_instance(wrapper,executor)
38
+ [[],Meta::Overlay.new(
39
+ :value,
40
+ [:assembly_instance],
41
+ -> obj { wrapper.call obj, executor },
42
+ []
43
+ )]
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,5 +1,6 @@
1
1
  require 'alki/invalid_path_error'
2
2
  require 'alki/overlay_info'
3
+ require 'alki/execution/overlay_map'
3
4
 
4
5
  module Alki
5
6
  module Assembly
@@ -14,7 +15,8 @@ module Alki
14
15
 
15
16
  def process(executor,from,data)
16
17
  data[:total_overlays] ||= 0
17
- data[:overlays]||={}
18
+ data[:overlays] ||= Execution::OverlayMap.new
19
+
18
20
  target_path = @target.dup
19
21
  if target_path.last.to_s.start_with?('%')
20
22
  tag = target_path.pop
@@ -22,8 +24,10 @@ module Alki
22
24
  if target_path == []
23
25
  target_path = [:root]
24
26
  end
27
+
25
28
  target = executor.canonical_path(from,target_path) or
26
29
  raise InvalidPathError.new("Invalid overlay target #{@target.join('.')}")
30
+
27
31
  target = target.dup.push tag if tag
28
32
  overlay = @overlay
29
33
  if overlay.is_a?(Array)
@@ -31,7 +35,8 @@ module Alki
31
35
  raise InvalidPathError.new("Invalid overlay path #{@overlay.join('.')}")
32
36
  end
33
37
  order = data[:total_overlays]
34
- (data[:overlays][target]||=[]) << OverlayInfo.new(order,@type, overlay, @args)
38
+
39
+ data[:overlays].add target, OverlayInfo.new(order,@type, overlay, @args)
35
40
  data[:total_overlays] += 1
36
41
  end
37
42
  end
@@ -1,3 +1,5 @@
1
+ require 'alki/execution/tag_map'
2
+
1
3
  module Alki
2
4
  module Assembly
3
5
  module Meta
@@ -7,10 +9,8 @@ module Alki
7
9
  end
8
10
 
9
11
  def process(_executor,from,data)
10
- data[:tags]||={}
11
- @tags.each do |tag,value|
12
- (data[:tags][tag.to_sym]||={})[from] = value
13
- end
12
+ data[:tags]||=Execution::TagMap.new
13
+ data[:tags].add from, @tags
14
14
  end
15
15
  end
16
16
  end
@@ -0,0 +1,9 @@
1
+ module Alki
2
+ module Assembly
3
+ module Types
4
+ def self.build(type,*args)
5
+ Alki.load("alki/assembly/types/#{type}").new *args
6
+ end
7
+ end
8
+ end
9
+ end
@@ -1,4 +1,5 @@
1
1
  require 'alki/assembly/types/override'
2
+ require 'alki/assembly/types/original'
2
3
 
3
4
  Alki do
4
5
  attr :root
@@ -16,7 +17,7 @@ Alki do
16
17
  data[:scope][k] = data[:prefix] + [k]
17
18
  end
18
19
  end
19
- root
20
+ Alki::Assembly::Types::Original.new root
20
21
  else
21
22
  if overrides
22
23
  data.replace(
@@ -40,9 +41,8 @@ Alki do
40
41
 
41
42
  output = root.output(data)
42
43
  add_parent_path output[:scope]
43
- update_scope data, output[:full_scope]
44
+ update_scope data, output[:scope]
44
45
  output[:scope].merge! overrides.output(data)[:scope] if overrides
45
- output[:full_scope].merge! overrides.output(data)[:full_scope] if overrides
46
46
  output
47
47
  end
48
48
 
@@ -1,5 +1,4 @@
1
1
  Alki do
2
- require 'ostruct'
3
2
  require 'alki/execution/helpers'
4
3
 
5
4
  attr(:children){ {} }
@@ -7,30 +6,12 @@ Alki do
7
6
  index do
8
7
  update_scope children, data[:prefix], data[:scope]
9
8
 
10
- data[:tags] ||= {}
11
- data[:tags] = data[:tags].inject({}) do |tags,(tag,tagged)|
12
- tagged.each do |path,value|
13
- if path.empty? || path[0] == key.to_sym
14
- (tags[tag]||={})[(path[1..-1]||[])] = value
15
- end
16
- end
17
- tags
9
+ if data[:tags]
10
+ data[:tags] = data[:tags].index key
18
11
  end
19
12
 
20
- data[:overlays] ||= {}
21
- data[:overlays] = data[:overlays].inject({}) do |no,(target,overlays)|
22
- target = target.dup
23
- if target.size == 1 && target[0].to_s.start_with?('%')
24
- tags = data[:tags][target[0].to_s[1..-1].to_sym]
25
- if tags
26
- tags.keys.each do |path|
27
- (no[path]||=[]).push *overlays
28
- end
29
- end
30
- elsif target.empty? || target.shift == key.to_sym
31
- (no[target]||=[]).push *overlays
32
- end
33
- no
13
+ if data[:overlays]
14
+ data[:overlays] = data[:overlays].index key, data[:tags]
34
15
  end
35
16
 
36
17
  data[:prefix] << key
@@ -41,13 +22,23 @@ Alki do
41
22
  output do
42
23
  children_names = children.keys.map(&:to_sym)
43
24
  {
44
- full_scope: update_scope(children, data[:prefix], data[:scope]),
45
- scope: update_scope(children,data[:prefix]),
25
+ lookup_methods: update_scope(children,data[:prefix]),
26
+ scope: update_scope(children, data[:prefix], data[:scope]),
46
27
  modules: [Alki::Execution::Helpers],
47
28
  methods: {
48
29
  children: -> {
49
30
  children_names
50
31
  },
32
+ elements: -> {
33
+ children.inject([]) do |elems, child_name|
34
+ child = send(child_name)
35
+ if child.respond_to?(:elements)
36
+ elems.push *child.elements
37
+ else
38
+ elems.push child
39
+ end
40
+ end
41
+ }
51
42
  },
52
43
  proc: ->{self}
53
44
  }