alki 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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