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