alki 0.1.0

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 (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/Gemfile +7 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +4 -0
  6. data/Rakefile +29 -0
  7. data/alki.gemspec +24 -0
  8. data/lib/alki/class_builder.rb +41 -0
  9. data/lib/alki/dsl_builder.rb +43 -0
  10. data/lib/alki/group_builder.rb +25 -0
  11. data/lib/alki/group_dsl.rb +98 -0
  12. data/lib/alki/loader.rb +32 -0
  13. data/lib/alki/overlay_delegator.rb +33 -0
  14. data/lib/alki/package.rb +33 -0
  15. data/lib/alki/package_builder.rb +2 -0
  16. data/lib/alki/package_executor.rb +120 -0
  17. data/lib/alki/package_processor.rb +167 -0
  18. data/lib/alki/service_delegator.rb +14 -0
  19. data/lib/alki/standard_package.rb +19 -0
  20. data/lib/alki/test.rb +28 -0
  21. data/lib/alki/util.rb +35 -0
  22. data/lib/alki/version.rb +3 -0
  23. data/lib/alki.rb +27 -0
  24. data/test/feature/create_package_test.rb +17 -0
  25. data/test/fixtures/config.rb +6 -0
  26. data/test/fixtures/example/config/handlers.rb +29 -0
  27. data/test/fixtures/example/config/package.rb +35 -0
  28. data/test/fixtures/example/config/settings.rb +11 -0
  29. data/test/fixtures/example/lib/array_output.rb +11 -0
  30. data/test/fixtures/example/lib/echo_handler.rb +9 -0
  31. data/test/fixtures/example/lib/example.rb +3 -0
  32. data/test/fixtures/example/lib/num_handler.rb +11 -0
  33. data/test/fixtures/example/lib/range_handler.rb +11 -0
  34. data/test/fixtures/example/lib/switch_handler.rb +9 -0
  35. data/test/integration/class_builder_test.rb +130 -0
  36. data/test/integration/loader_test.rb +36 -0
  37. data/test/test_helper.rb +1 -0
  38. data/test/unit/application_settings_test.rb +56 -0
  39. data/test/unit/application_test.rb +159 -0
  40. data/test/unit/package_processor_test.rb +319 -0
  41. metadata +129 -0
@@ -0,0 +1,167 @@
1
+ module Alki
2
+ class PackageProcessor
3
+ def lookup(package,path)
4
+ lookup_path(package,path)
5
+ end
6
+
7
+ private
8
+
9
+ def lookup_path(desc, path, data = {})
10
+ data_defaults data
11
+ path = path.dup
12
+ update_data data, desc
13
+ until path.empty?
14
+ key = path.shift
15
+ data[:prefix] += [key]
16
+ elem = desc[key]
17
+ if !elem
18
+ return nil
19
+ else
20
+ unless path.empty?
21
+ if elem[:type] == :group
22
+ desc = elem[:children]
23
+ update_data data, desc
24
+ elsif elem[:type] == :package
25
+ return package_lookup_path elem, path, data
26
+ else
27
+ raise "Invalid path"
28
+ end
29
+ end
30
+ end
31
+ end
32
+ if elem
33
+ case elem[:type]
34
+ when :group
35
+ {type: :group, children: prefix_keys(elem[:children], data[:prefix])}
36
+ when :package
37
+ children = prefix_keys([elem[:children],elem[:overrides]], data[:prefix])
38
+ {type: :group, children: children}
39
+ when :service, :factory
40
+ last_clear = data[:overlays].rindex(:clear)
41
+ overlays = last_clear ? data[:overlays][(last_clear + 1)..-1] : data[:overlays]
42
+ {type: elem[:type], block: elem[:block], overlays: overlays, scope: data[:scope]}
43
+ else
44
+ raise "Invalid elem type: #{elem[:type]}"
45
+ end
46
+ else
47
+ {type: :group, children: prefix_keys(desc,data[:prefix])}
48
+ end
49
+ end
50
+
51
+ def package_lookup_path(elem,path,data)
52
+ if path[0] == :orig
53
+ if path.size == 1
54
+ {type: :group, children: prefix_keys(elem[:children],data[:prefix]+[:orig])}
55
+ else
56
+ lookup_path(elem[:children],path[1..-1],data.merge(scope:nil))
57
+ end
58
+ else
59
+ override_scope = data[:scope].merge(pkg: data[:prefix]+[:orig])
60
+ override_data = data.merge(overlays: data[:overlays].dup, scope: override_scope)
61
+ lookup_path(merge_overlays(elem[:overrides],elem[:children]), path, override_data) or
62
+ lookup_path(
63
+ merge_overrides(elem[:children], elem[:overrides]),
64
+ path, data.merge(scope: nil)
65
+ )
66
+ end
67
+ end
68
+
69
+ def data_defaults(data)
70
+ data[:overlays] ||= []
71
+ data[:prefix] ||= []
72
+ data[:scope] ||= {root: data[:prefix]}
73
+ end
74
+
75
+ def prefix_keys(source, prefix, result={})
76
+ source = [source] unless source.is_a? Array
77
+ source.inject([]){|a,h| a|=h.keys}.inject(result) do |h,k|
78
+ h.merge! k => (prefix+[k])
79
+ end
80
+ result
81
+ end
82
+
83
+ def update_data(data,desc)
84
+ prefix_keys desc, data[:prefix], data[:scope]
85
+ if desc['overlays']
86
+ data[:overlays].push *desc['overlays'].map { |o|
87
+ o == :clear ? o : {block: o, scope: data[:scope]}
88
+ }
89
+ end
90
+ end
91
+
92
+ def merge_overlays(a,b)
93
+ updates = {}
94
+ b.keys.each do |k|
95
+ if k == 'overlays'
96
+ updates[k] = (a[k] || []) + b[k]
97
+ elsif a.key? k
98
+ val = merge_item_overlays(a[k],b[k])
99
+ updates[k] = val unless a[k].equal? val
100
+ end
101
+ end
102
+ if updates.empty?
103
+ a
104
+ else
105
+ a.merge updates
106
+ end
107
+ end
108
+
109
+ def merge_item_overlays(a,b)
110
+ if a[:children] && b[:children]
111
+ if b[:type] == :package
112
+ b_children = merge_overlays(b[:children],b[:overrides])
113
+ elsif b[:type] == :group
114
+ b_children = b[:children]
115
+ end
116
+ if a[:type] == :package
117
+ a.merge(overrides: merge_overlays(a[:overrides],b_children))
118
+ elsif b[:type] == :group
119
+ a.merge(children: merge_overlays(a[:children],b_children))
120
+ end
121
+ else
122
+ a
123
+ end
124
+ end
125
+
126
+ def merge_overrides(a,b)
127
+ updates = {}
128
+ b.keys.each do |k|
129
+ if a.key? k
130
+ if k == 'overlays'
131
+ updates[k] = b[k] + a[k]
132
+ else
133
+ val = merge_item(a[k],b[k])
134
+ updates[k] = val unless a[k].equal? val
135
+ end
136
+ else
137
+ updates[k] = b[k]
138
+ end
139
+ end
140
+ if updates.empty?
141
+ a
142
+ else
143
+ a.merge updates
144
+ end
145
+
146
+ end
147
+
148
+ def merge_item(a,b)
149
+ if a[:children] && b[:children]
150
+ if b[:type] == :package
151
+ b_children = merge_overrides(b[:children],b[:overrides])
152
+ elsif b[:type] == :group
153
+ b_children = b[:children]
154
+ end
155
+ if a[:type] == :package
156
+ a.merge(overrides: merge_overrides(a[:overrides],b_children))
157
+ elsif b[:type] == :group
158
+ a.merge(children: merge_overrides(a[:children],b_children))
159
+ end
160
+ elsif a != b
161
+ b
162
+ else
163
+ a
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,14 @@
1
+ require 'delegate'
2
+
3
+ module Alki
4
+ class ServiceDelegator < Delegator
5
+ def initialize(app,path)
6
+ @app = app
7
+ @path = path
8
+ end
9
+
10
+ def __getobj__
11
+ @obj ||= @app.lookup(@path)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ require 'alki/package'
2
+ require 'alki/loader'
3
+ require 'alki/group_builder'
4
+
5
+ module Alki
6
+ class StandardPackage < Package
7
+ def initialize(root_dir)
8
+ @root_dir = root_dir
9
+ loader = Alki::Loader.new(File.join(root_dir,'config'))
10
+ builder = Alki::GroupBuilder.new loader
11
+ pkg = builder.build &loader.load('package')
12
+ loader_proc = ->() { loader }
13
+ builder.build pkg do
14
+ service :loader, &loader_proc
15
+ end
16
+ super pkg
17
+ end
18
+ end
19
+ end
data/lib/alki/test.rb ADDED
@@ -0,0 +1,28 @@
1
+ require 'minitest/autorun'
2
+ Bundler.setup(:default,:test)
3
+
4
+ class Minitest::Spec
5
+ def app_root
6
+ Bundler.root
7
+ end
8
+
9
+ def lib_dir
10
+ File.join app_root, 'lib'
11
+ end
12
+
13
+ def tests_root
14
+ File.join app_root, 'test'
15
+ end
16
+
17
+ def fixtures_path
18
+ File.join(tests_root, 'fixtures')
19
+ end
20
+
21
+ def fixture_path(fixture)
22
+ File.join(fixtures_path, fixture)
23
+ end
24
+
25
+ def load_fixture(fixture)
26
+ require fixture_path(fixture)
27
+ end
28
+ end
data/lib/alki/util.rb ADDED
@@ -0,0 +1,35 @@
1
+ module Alki
2
+ module Util
3
+ def self.classify(str)
4
+ str.split('/').map do |c|
5
+ c.split('_').map{|n| n.capitalize }.join('')
6
+ end.join('::')
7
+ end
8
+
9
+ def self.create_class(name,klass = Class.new)
10
+ *ans, ln = name.to_s.split(/::/)
11
+ parent = Object
12
+ ans.each do |a|
13
+ unless parent.const_defined? a
14
+ parent.const_set a, Module.new
15
+ end
16
+ parent = parent.const_get a
17
+ end
18
+
19
+ parent.const_set ln, klass
20
+ end
21
+
22
+ def self.find_pkg_root(path)
23
+ old_dir = File.absolute_path(path)
24
+ dir = File.dirname(old_dir)
25
+ until dir == old_dir || File.exists?(File.join(dir,'config','package.rb'))
26
+ old_dir = dir
27
+ dir = File.dirname(old_dir)
28
+ end
29
+ if dir == old_dir
30
+ raise "Couldn't find app root"
31
+ end
32
+ dir
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ module Alki
2
+ VERSION = "0.1.0"
3
+ end
data/lib/alki.rb ADDED
@@ -0,0 +1,27 @@
1
+ module Alki
2
+ def self.create_package!(name=nil,root=nil)
3
+ require 'alki/util'
4
+ require 'alki/standard_package'
5
+
6
+ if !root
7
+ root = Alki::Util.find_pkg_root(caller_locations(1, 1)[0].absolute_path)
8
+ end
9
+ if !name
10
+ path = caller_locations(1,1)[0].absolute_path
11
+ lib_dir = File.join(root,'lib','')
12
+ unless path.start_with?(lib_dir) && path.end_with?('.rb')
13
+ raise "Can't auto-detect name of package"
14
+ end
15
+ name = path[lib_dir.size..-4]
16
+ end
17
+ unless name =~ /[A-Z]/
18
+ name = Alki::Util.classify(name)
19
+ end
20
+ klass = Class.new(Alki::StandardPackage)
21
+ klass.send :define_method, :initialize do
22
+ super root
23
+ end
24
+
25
+ Alki::Util.create_class(name,klass)
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ require_relative '../test_helper'
2
+
3
+ describe 'Alki.create_package!' do
4
+ describe 'example' do
5
+ it 'should do fizzbuzz' do
6
+ $LOAD_PATH << File.join(fixtures_path,'example','lib')
7
+ require 'example'
8
+ app = Example.new
9
+ app.range_handler.handle 1..20
10
+ app.output.to_a.must_equal [
11
+ "1","2","Fizz!","4","Buzz!","Fizz!","7", "8", "Fizz!",
12
+ "Buzz!", "11", "Fizz!", "13", "14", "Fizzbuzz!", "16",
13
+ "17", "Fizz!", "19", "Buzz!"
14
+ ]
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,6 @@
1
+ wait = $wait || 0
2
+ $wait =nil
3
+ Alki do
4
+ wait
5
+ end
6
+ sleep wait if wait > 0
@@ -0,0 +1,29 @@
1
+ Alki do
2
+ factory :num_handler do
3
+ require 'num_handler'
4
+ -> (num,str) {
5
+ NumHandler.new num, str, output
6
+ }
7
+ end
8
+ service :fizz do
9
+ num_handler 3, settings.fizz
10
+ end
11
+ service :buzz do
12
+ num_handler 5, settings.buzz
13
+ end
14
+ service :fizzbuzz do
15
+ num_handler 15, settings.fizzbuzz
16
+ end
17
+ service :echo do
18
+ require 'echo_handler'
19
+ EchoHandler.new output
20
+ end
21
+
22
+ overlay do
23
+ log_obj = log
24
+ -> (name,obj,method,*args,&blk) {
25
+ log_obj << "Calling #{name}##{method} #{args.join(", ")}\n"
26
+ obj.public_send method, *args, &blk
27
+ }
28
+ end
29
+ end
@@ -0,0 +1,35 @@
1
+ Alki do
2
+ load :settings
3
+ load :handlers
4
+
5
+ service :handler do
6
+ require 'switch_handler'
7
+ SwitchHandler.new [
8
+ handlers.fizzbuzz,
9
+ handlers.fizz,
10
+ handlers.buzz,
11
+ handlers.echo
12
+ ]
13
+ end
14
+
15
+ service :range_handler do
16
+ require 'range_handler'
17
+ RangeHandler.new lazy('handler')
18
+ end
19
+
20
+ service :output do
21
+ require 'array_output'
22
+ ArrayOutput.new
23
+ end
24
+
25
+ service :log do
26
+ require 'logger'
27
+ Logger.new STDERR
28
+ end
29
+
30
+ service :message_proc do
31
+ -> (msg) {
32
+ msg[0].upcase+msg[1..-1].downcase+"!"
33
+ }
34
+ end
35
+ end
@@ -0,0 +1,11 @@
1
+ Alki do
2
+ set :fizz do
3
+ message_proc.call("fizz")
4
+ end
5
+ set :buzz do
6
+ message_proc.call("buzz")
7
+ end
8
+ set :fizzbuzz do
9
+ message_proc.call("fizzbuzz")
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ class ArrayOutput
2
+ def initialize
3
+ @output = []
4
+ end
5
+ def <<(val)
6
+ @output << val
7
+ end
8
+ def to_a
9
+ @output.dup
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ class EchoHandler
2
+ def initialize(output)
3
+ @output = output
4
+ end
5
+
6
+ def handle(val)
7
+ @output << val.to_s
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ require 'alki'
2
+
3
+ Alki.create_package!
@@ -0,0 +1,11 @@
1
+ class NumHandler
2
+ def initialize(num,message,output)
3
+ @num = num
4
+ @msg = message
5
+ @output = output
6
+ end
7
+
8
+ def handle(val)
9
+ @output << @msg if val % @num == 0
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ class RangeHandler
2
+ def initialize(subhandler)
3
+ @subhandler = subhandler
4
+ end
5
+
6
+ def handle(range)
7
+ range.each do |i|
8
+ @subhandler.handle i
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ class SwitchHandler
2
+ def initialize(handlers)
3
+ @handlers = handlers
4
+ end
5
+
6
+ def handle(val)
7
+ @handlers.find {|h| h.handle val }
8
+ end
9
+ end
@@ -0,0 +1,130 @@
1
+ require 'alki/test'
2
+ require 'alki/class_builder'
3
+
4
+ describe Alki::ClassBuilder do
5
+ class DoitBuilder
6
+ def initialize(c)
7
+ @c = c
8
+ @num = 0
9
+ end
10
+
11
+ def dsl_methods
12
+ [:doit]
13
+ end
14
+
15
+ def doit(&blk)
16
+ @c.send(:define_method,"doit#{@num}",&blk)
17
+ @num += 1
18
+ end
19
+
20
+ def finalize
21
+ num = @num
22
+ @c.define_singleton_method :doits do
23
+ num
24
+ end
25
+
26
+ @c.send(:define_method,:doit_all) do
27
+ num.times.map {|i| send "doit#{i}"}
28
+ end
29
+ end
30
+ end
31
+
32
+ class JobTypeBuilder
33
+ def initialize(c)
34
+ @c = c
35
+ end
36
+
37
+ def dsl_methods
38
+ [:job]
39
+ end
40
+
41
+ def job(job)
42
+ @job = job
43
+ end
44
+
45
+ def finalize
46
+ job = @job
47
+ @c.send :define_method, :job do
48
+ job
49
+ end
50
+ end
51
+ end
52
+
53
+ describe :build do
54
+ it 'should allow calling methods in dsl' do
55
+ c = Alki::ClassBuilder.build(DoitBuilder) do
56
+ doit { "hello" }
57
+ doit { "world" }
58
+ end
59
+ c.new.doit_all.must_equal ["hello","world"]
60
+ end
61
+
62
+ it 'should allow defining normal methods' do
63
+ c = Alki::ClassBuilder.build(DoitBuilder) do
64
+ def initialize(msg)
65
+ @msg = msg
66
+ end
67
+ def msg
68
+ @msg
69
+ end
70
+ doit { msg }
71
+ doit { "world" }
72
+ end
73
+ obj = c.new "hello"
74
+ obj.msg.must_equal "hello"
75
+ obj.doit_all.must_equal ["hello","world"]
76
+ end
77
+
78
+ it 'should allow setting class methods' do
79
+ c = Alki::ClassBuilder.build(DoitBuilder) do
80
+ def klass.doit_count
81
+ doits
82
+ end
83
+ end
84
+ c.doit_count.must_equal 0
85
+ end
86
+
87
+ it 'should not make dsl methods available on class' do
88
+ c = Alki::ClassBuilder.build(DoitBuilder) do
89
+ end
90
+ c.methods.wont_include :doit
91
+ assert_raises NoMethodError do
92
+ c.doit { puts "test" }
93
+ end
94
+ assert_raises NoMethodError do
95
+ c.send(:doit) { puts "test" }
96
+ end
97
+ end
98
+
99
+ it 'should only allow calling dsl methods returned from #dsl_methods' do
100
+ assert_raises NoMethodError do
101
+ Alki::ClassBuilder.build(DoitBuilder) do
102
+ finalize()
103
+ end
104
+ end
105
+ end
106
+
107
+ it 'should allow creating classes with names' do
108
+ Alki::ClassBuilder.build(:TestClass,DoitBuilder) do
109
+ end
110
+ TestClass.new.doit_all.must_equal []
111
+ end
112
+
113
+ it 'should allow creating classes in modules' do
114
+ module TestModule; end
115
+ Alki::ClassBuilder.build('TestModule::TestClass',DoitBuilder) do
116
+ end
117
+ TestModule::TestClass.new.doit_all.must_equal []
118
+ end
119
+
120
+ it 'should allow passing in multiple dsl builders' do
121
+ c = Alki::ClassBuilder.build([DoitBuilder,JobTypeBuilder]) do
122
+ job "greeter"
123
+ doit { "Welcome!" }
124
+ end
125
+ obj = c.new
126
+ obj.doit_all.must_equal ["Welcome!"]
127
+ obj.job.must_equal "greeter"
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,36 @@
1
+ require_relative '../test_helper'
2
+
3
+ require 'alki/loader'
4
+
5
+ describe Alki::Loader do
6
+ describe 'load' do
7
+ before do
8
+ @loader = Alki::Loader.new fixtures_path
9
+ end
10
+
11
+ it 'should load config file from root directory' do
12
+ @loader.load(:config).call.must_equal 0
13
+ end
14
+ end
15
+
16
+ describe 'self.load' do
17
+ before do
18
+ @config_path = fixture_path('config.rb')
19
+ end
20
+
21
+ it 'should load a config file and call block with proc' do
22
+ Alki::Loader::load(@config_path).call.must_equal 0
23
+ end
24
+
25
+ it 'should be threadsafe' do
26
+ t1 = Thread.new do
27
+ $wait = 1
28
+ Alki::Loader::load(@config_path).call
29
+ end
30
+ sleep 0.1
31
+ $wait = 0
32
+ Alki::Loader::load(@config_path).call.must_equal 0
33
+ t1.value.must_equal 1
34
+ end
35
+ end
36
+ end
@@ -0,0 +1 @@
1
+ require_relative('../lib/alki/test')
@@ -0,0 +1,56 @@
1
+ require_relative '../test_helper'
2
+
3
+ require 'alki/application_settings'
4
+
5
+ describe Alki::ApplicationSettings do
6
+ before do
7
+ @settings = Alki::ApplicationSettings.new
8
+ end
9
+
10
+ describe :initialize do
11
+ it 'should set environment to development by default' do
12
+ @settings.environment.must_equal :development
13
+ end
14
+ end
15
+
16
+ describe :set do
17
+ it 'should add setting as method and make it available as an index' do
18
+ @settings.respond_to?(:test).must_equal false
19
+ @settings.set :test, :test_value
20
+ @settings.test.must_equal :test_value
21
+ end
22
+
23
+ it 'should make setting available as an index' do
24
+ @settings[:test].must_equal nil
25
+ @settings.set :test, :test_value
26
+ @settings[:test].must_equal :test_value
27
+ end
28
+ end
29
+
30
+ describe :environment? do
31
+ it 'should return whether environment is one of the arguments' do
32
+ @settings.environment?(:development).must_equal true
33
+ @settings.environment?(:production,:development).must_equal true
34
+ @settings.environment?(:production,:test).must_equal false
35
+ end
36
+
37
+ it 'should run provided block if true' do
38
+ test = false
39
+ @settings.environment?(:test) { test = true }
40
+ test.must_equal false
41
+ @settings.environment?(:development) { test = true }
42
+ test.must_equal true
43
+ end
44
+ end
45
+
46
+ describe :configure do
47
+ it 'should run provided block in object context' do
48
+ @settings.respond_to?(:test).must_equal false
49
+ @settings.configure do
50
+ set :test, :test_value
51
+ test.must_equal :test_value
52
+ end
53
+ @settings.test.must_equal :test_value
54
+ end
55
+ end
56
+ end