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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: acebb8f2a073f60c659eb7b994778f3717c140c9
4
+ data.tar.gz: c6bf4c35dbe97001bfed4b772a84e811b7200b93
5
+ SHA512:
6
+ metadata.gz: 623bacc9055973a92a710f2a7fe7235cdea0da93d5f434445b9b931ccc2c4ea51f261ba6715f19d95a5bd90ac5b496e05ce3f0e127877d1e45814863b0bd03d2
7
+ data.tar.gz: f33554183dc94171bb8191908745f218cf6bc5215e2125a9d9de4fc9cce1708a25799670e00833050fa83d54201fb02fb4f290f665881094f598870761c5476e
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ *.iml
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'minitest', '~> 5.4.2'
7
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Matt Edlefsen
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,4 @@
1
+ # base.alki
2
+
3
+ Basic library for creating applications.
4
+
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.name = 'test:unit'
6
+ t.pattern = "test/unit/*_test.rb"
7
+ end
8
+
9
+ Rake::TestTask.new do |t|
10
+ t.name = 'test:feature'
11
+ t.pattern = "test/feature/*_test.rb"
12
+ end
13
+
14
+ Rake::TestTask.new do |t|
15
+ t.name = 'test:integration'
16
+ t.pattern = "test/integration/*_test.rb"
17
+ end
18
+
19
+ Rake::TestTask.new do |t|
20
+ t.name = 'test:page'
21
+ t.pattern = "test/page/*_test.rb"
22
+ end
23
+
24
+ Rake::TestTask.new do |t|
25
+ t.name = 'test'
26
+ t.pattern = "test/*/*_test.rb"
27
+ end
28
+
29
+ task default: [:test]
data/alki.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'alki/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "alki"
8
+ spec.version = Alki::VERSION
9
+ spec.authors = ["Matt Edlefsen"]
10
+ spec.email = ["matt@xforty.com"]
11
+ spec.summary = %q{Base library for building applications.}
12
+ spec.description = %q{Base library for building applications. Provides tools for organizing and connection application units.}
13
+ spec.homepage = "https://gitlab.xforty.com/matt/base-alki"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.bindir = 'exe'
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.6"
23
+ spec.add_development_dependency "rake", '~> 10.0'
24
+ end
@@ -0,0 +1,41 @@
1
+ require 'alki/dsl_builder'
2
+
3
+ module Alki
4
+ class ClassBuilder
5
+ def initialize(*dsl_factories)
6
+ @dsl_factories = dsl_factories.flatten
7
+ end
8
+
9
+ def build(name=nil,&blk)
10
+ self.class.build(name,@dsl_factories,&blk)
11
+ end
12
+
13
+ def self.build(name=nil,dsl_factories,&blk)
14
+ Class.new.tap do |c|
15
+ builder = -> {
16
+ Module.new.tap do |m|
17
+ m.define_singleton_method :klass do
18
+ c
19
+ end
20
+ end
21
+ }
22
+ m = DslBuilder.build(c,dsl_factories,
23
+ builder: builder,exec: :class_exec,
24
+ &blk)
25
+ c.include m
26
+ if name
27
+ *ans, ln = name.to_s.split(/::/)
28
+ parent = Object
29
+ ans.each do |a|
30
+ unless parent.const_defined? a
31
+ parent.const_set a, Module.new
32
+ end
33
+ parent = parent.const_get a
34
+ end
35
+
36
+ parent.const_set ln, c
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,43 @@
1
+ module Alki
2
+ class DslBuilder
3
+ def initialize(*dsl_factories,builder: nil, exec: nil)
4
+ @builder = builder
5
+ @exec = exec
6
+ @dsl_factories = dsl_factories.flatten
7
+ end
8
+
9
+ def build(obj,&blk)
10
+ self.class.build(obj,@dsl_factories,
11
+ builder: @builder,
12
+ exec: @exec,
13
+ &blk)
14
+ end
15
+
16
+ def self.build(obj,dsl_factories,builder: nil, exec: nil, &blk)
17
+ builder ||= Object.method(:new)
18
+ exec ||= :instance_exec
19
+ unless dsl_factories.is_a?(Array)
20
+ dsl_factories = [dsl_factories]
21
+ end
22
+ builder = builder.call
23
+ dsls = []
24
+ dsl_factories.each do |dsl_factory|
25
+ if dsl_factory.respond_to? :new_dsl
26
+ dsl = dsl_factory.new_dsl obj
27
+ else
28
+ dsl = dsl_factory.new obj
29
+ end
30
+ dsl.dsl_methods.each do |method_name|
31
+ method = dsl.method(method_name)
32
+ builder.define_singleton_method method_name do |*args,&blk|
33
+ method.call *args, &blk
34
+ end
35
+ end
36
+ dsls << dsl
37
+ end
38
+ builder.send(exec,&blk)
39
+ dsls.each {|dsl| dsl.finalize if dsl.respond_to? :finalize }
40
+ builder
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,25 @@
1
+ require 'alki/group_dsl'
2
+ require 'alki/dsl_builder'
3
+
4
+ module Alki
5
+ class GroupBuilder
6
+ def self.build(loader,obj = {},&dsl)
7
+ Alki::GroupBuilder.new(loader).build(obj,&dsl)
8
+ obj
9
+ end
10
+
11
+ def initialize(loader)
12
+ @loader = loader
13
+ @builder = Alki::DslBuilder.new(self)
14
+ end
15
+
16
+ def new_dsl(obj)
17
+ GroupDsl.new(obj,loader: @loader, group_builder: @builder)
18
+ end
19
+
20
+ def build(group={},&dsl)
21
+ @builder.build(group,&dsl)
22
+ group
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,98 @@
1
+ require 'alki/util'
2
+
3
+ module Alki
4
+ class GroupDsl
5
+ def initialize(root,group_builder:,loader:)
6
+ @root = root
7
+ @group_builder = group_builder
8
+ @loader = loader
9
+ end
10
+
11
+ def dsl_methods
12
+ [:set,:service,:factory,:load,:import,:group,:overlay,:clear_overlays]
13
+ end
14
+
15
+ def set(name,value=nil,&blk)
16
+ service name, &(blk || -> { value })
17
+ end
18
+
19
+ def service(name,&blk)
20
+ name = name.to_sym
21
+ @root[name] = {
22
+ type: :service,
23
+ block: blk
24
+ }
25
+ end
26
+
27
+ def factory(name,&blk)
28
+ name = name.to_sym
29
+ @root[name] = {
30
+ type: :factory,
31
+ block: blk
32
+ }
33
+ end
34
+
35
+ def import(name)
36
+ instance_exec &@loader.load(name.to_s)
37
+ end
38
+
39
+ def load(name)
40
+ group(name.to_sym) do
41
+ import(name)
42
+ end
43
+ end
44
+
45
+ def package(name,pkg,klass=nil,&blk)
46
+ if pkg.is_a? String
47
+ require pkg
48
+ if klass == nil
49
+ pkg = Alki::Util.classify(pkg)
50
+ else
51
+ pkg = klass
52
+ end
53
+ end
54
+ if pkg.is_a? Class
55
+ pkg = pkg.new
56
+ end
57
+ if pkg.response_to? :package_definition
58
+ pkg = pkg.package_definition
59
+ end
60
+ unless pkg.is_a? Hash
61
+ raise "Invalid package: #{pkg.inspect}"
62
+ end
63
+ name = name.to_sym
64
+ overrides = {}
65
+ @group_builder.build(overrides,&blk) if blk
66
+ overrides[:original] = {
67
+ type: :package,
68
+ children: pkg,
69
+ overrides: {}
70
+ }
71
+ @root[name] = {
72
+ type: :package,
73
+ children: pkg,
74
+ overrides: overrides
75
+ }
76
+ end
77
+
78
+ def group(name,&blk)
79
+ name = name.to_sym
80
+ children = {}
81
+ @group_builder.build(children,&blk)
82
+ @root[name] = {
83
+ type: :group,
84
+ children: children
85
+ }
86
+ end
87
+
88
+ def overlay(&blk)
89
+ @root['overlays'] ||= []
90
+ @root['overlays'] << blk
91
+ end
92
+
93
+ def clear_overlays
94
+ @root['overlays'] ||= []
95
+ @root['overlays'] << :clear
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,32 @@
1
+ module Alki
2
+ class Loader
3
+ def self.load(config_file)
4
+ Fiber.new do
5
+ Kernel.load config_file
6
+ Thread.current[:alki_loader_current]
7
+ end.resume
8
+ end
9
+
10
+ def initialize(root_dir)
11
+ @root_dir = root_dir
12
+ end
13
+
14
+ def load_all
15
+ Dir[File.join(@root_dir,'**','*.rb')].inject({}) do |h,path|
16
+ file = path.gsub(File.join(@root_dir,''),'').gsub(/\.rb$/,'')
17
+ h.merge!(file => Loader.load(path))
18
+ end
19
+ end
20
+
21
+ def load(file)
22
+ Loader.load File.expand_path("#{file}.rb",@root_dir)
23
+ end
24
+ end
25
+ end
26
+
27
+ module Kernel
28
+ def Alki(&blk)
29
+ Thread.current[:alki_loader_current] = blk if blk
30
+ ::Alki
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ module Alki
2
+ class OverlayDelegator
3
+ def initialize(name,obj,overlay)
4
+ @name = name
5
+ @obj = obj
6
+ @overlay = overlay
7
+ @key = :"#{obj.object_id}:#{overlay.object_id}"
8
+ end
9
+
10
+ def respond_to_missing(method,include_private = false)
11
+ unless Thread.current[@key]
12
+ Thread.current[@key] = true
13
+ if @overlay.respond_to? :overlay_respond_to?
14
+ @overlay.overlay_respond_to? @obj, method, include_private
15
+ else
16
+ @obj.respond_to? method, include_private
17
+ end
18
+ Thread.current[@key] = false
19
+ end
20
+ end
21
+
22
+ def method_missing(method,*args,&blk)
23
+ if Thread.current[@key]
24
+ res = @obj.send method, *args, &blk
25
+ else
26
+ Thread.current[@key] = true
27
+ res = @overlay.call @name, @obj, method, *args, &blk
28
+ Thread.current[@key] = false
29
+ end
30
+ res
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ require 'alki/package_executor'
2
+ require 'alki/package_processor'
3
+
4
+ module Alki
5
+ class Package
6
+ def initialize(package_definition)
7
+ @def = package_definition
8
+ @cache = {}
9
+ end
10
+
11
+ def root
12
+ @root ||= __executor__.call @def, @cache, []
13
+ end
14
+
15
+ def package_definition
16
+ @def
17
+ end
18
+
19
+ def respond_to_missing?(name,include_all)
20
+ root.respond_to? name
21
+ end
22
+
23
+ def method_missing(name,*args,&blk)
24
+ root.send name, *args, &blk
25
+ end
26
+
27
+ private
28
+
29
+ def __executor__
30
+ @executor ||= Alki::PackageExecutor.new Alki::PackageProcessor.new
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,2 @@
1
+ module Alki
2
+ end
@@ -0,0 +1,120 @@
1
+ require 'alki/service_delegator'
2
+ require 'alki/overlay_delegator'
3
+
4
+ module Alki
5
+ class PackageExecutor
6
+ def initialize(processor)
7
+ @processor = processor
8
+ end
9
+
10
+ def call(pkg,cache,path,*args,&blk)
11
+ elem = @processor.lookup(pkg,path)
12
+ triple = [pkg,cache,elem]
13
+ case elem[:type]
14
+ when :service
15
+ service triple, path, *args
16
+ when :factory
17
+ factory triple, path, *args, &blk
18
+ when :group
19
+ group triple, *args
20
+ end
21
+ end
22
+
23
+ def service(triple,path)
24
+ pkg,cache,elem = triple
25
+ unless cache[path]
26
+ with_scope_context(triple) do |ctx,blk|
27
+ cache[path] = apply_overlays triple,path, ctx.instance_exec(&blk)
28
+ end
29
+ end
30
+ cache[path]
31
+ end
32
+
33
+ def apply_overlays((pkg,cache,elem),path,obj)
34
+ elem[:overlays].inject(obj) do |obj,overlay_elem|
35
+ unless cache[overlay_elem[:block]]
36
+ with_scope_context([pkg,cache,overlay_elem]) do |ctx,blk|
37
+ cache[overlay_elem[:block]] = ctx.instance_exec(&blk)
38
+ end
39
+ end
40
+ local_path = path[overlay_elem[:scope][:root].size..-1].join('.')
41
+ Alki::OverlayDelegator.new local_path,obj, cache[overlay_elem[:block]]
42
+ end
43
+ end
44
+
45
+ def factory(triple,path,*args,&blk)
46
+ pkg,cache,elem = triple
47
+ unless cache[path]
48
+ with_scope_context(triple) do |ctx,blk|
49
+ cache[path] = ctx.instance_exec(&blk)
50
+ end
51
+ end
52
+ cache[path].call *args, &blk
53
+ end
54
+
55
+ def group((pkg,cache,elem))
56
+ proc = -> (name,*args,&blk) {
57
+ call pkg, cache, elem[:children][name], *args, &blk
58
+ }
59
+
60
+ GroupContext.new(proc,elem[:children].keys)
61
+ end
62
+
63
+ def with_scope_context((pkg,cache,elem))
64
+ proc = -> (name,*args,&blk) {
65
+ call pkg, cache, elem[:scope][name], *args, &blk
66
+ }
67
+
68
+ yield ServiceContext.new(proc,elem[:scope].keys), elem[:block]
69
+ end
70
+
71
+ class Context
72
+ def initialize(executor,scope)
73
+ @executor = executor
74
+ @scope = scope
75
+ end
76
+
77
+ def respond_to_missing?(name,include_all)
78
+ @scope.include? name
79
+ end
80
+
81
+ def method_missing(name,*args,&blk)
82
+ if @scope.include? name
83
+ @executor.call name, *args, &blk
84
+ else
85
+ super
86
+ end
87
+ end
88
+ end
89
+
90
+ class ServiceContext < Context
91
+ def lookup(path)
92
+ unless path.is_a?(String) or path.is_a?(Symbol)
93
+ raise ArgumentError.new("lookup can only take Strings or Symbols")
94
+ end
95
+ path.to_s.split('.').inject(self) do |group,name|
96
+ raise "Invalid lookup path" unless group.is_a? Context
97
+ group.send name.to_sym
98
+ end
99
+ end
100
+
101
+ def lazy(path)
102
+ unless path.is_a?(String) or path.is_a?(Symbol)
103
+ raise ArgumentError.new("lazy can only take Strings or Symbols")
104
+ end
105
+ Alki::ServiceDelegator.new root, path
106
+ end
107
+ end
108
+
109
+ class GroupContext < Context
110
+ def lookup(path)
111
+ unless path.is_a?(String) or path.is_a?(Symbol)
112
+ raise ArgumentError.new("lookup can only take Strings or Symbols")
113
+ end
114
+ path.to_s.split('.').inject(self) do |group,name|
115
+ group.send name.to_sym
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end