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
@@ -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
  }