alki-dsl 0.3.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.
- checksums.yaml +7 -0
- data/.gitignore +53 -0
- data/Gemfile +3 -0
- data/alki-dsl.gemspec +22 -0
- data/lib/alki/class_builder.rb +132 -0
- data/lib/alki/dsl/base.rb +45 -0
- data/lib/alki/dsl/builder.rb +42 -0
- data/lib/alki/dsl/class_helpers.rb +86 -0
- data/lib/alki/dsl/evaluator.rb +67 -0
- data/lib/alki/dsl/loader.rb +28 -0
- data/lib/alki/dsl/registry.rb +66 -0
- data/lib/alki/dsl/version.rb +5 -0
- data/lib/alki/dsl.rb +48 -0
- data/lib/alki/dsls/class.rb +31 -0
- data/lib/alki/dsls/dsl.rb +78 -0
- data/lib/alki/dsls/dsl_config.rb +28 -0
- data/test/feature/config_test.rb +19 -0
- data/test/fixtures/example/config/dsls.rb +4 -0
- data/test/fixtures/example/lib/alki_test/dsls/number.rb +11 -0
- data/test/fixtures/example/lib/alki_test/dsls/value.rb +19 -0
- data/test/fixtures/example/numbers/three.rb +5 -0
- data/test/integration/class_builder_test.rb +148 -0
- data/test/test_helper.rb +13 -0
- data/test/unit/evaluator_test.rb +6 -0
- metadata +109 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 509cf74d2d757a7d980460a7bd63199e0cfabe39
|
4
|
+
data.tar.gz: 33c3f48bac58f19f73f7469ec42f4357cd2321b6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2ad60f1b2f6c8f012e41c61ff18171ff1fafb65af694c04a96dc15c20ba664c88b60e25d9fc23cf430f7c41396509b7443169ef9f447af2af46e27114255894c
|
7
|
+
data.tar.gz: 1245f5d5471641f77603f23aac033fd273f9a622f327fb19ce2268616a9a1c6fb968d589e602f1294a28f69596d0a786242e035d8d5cdbe8bfdc9decca40ec40
|
data/.gitignore
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# Created by .ignore support plugin (hsz.mobi)
|
2
|
+
### Ruby template
|
3
|
+
*.gem
|
4
|
+
*.rbc
|
5
|
+
/.config
|
6
|
+
/coverage/
|
7
|
+
/InstalledFiles
|
8
|
+
/pkg/
|
9
|
+
/spec/reports/
|
10
|
+
/spec/examples.txt
|
11
|
+
/test/tmp/
|
12
|
+
/test/version_tmp/
|
13
|
+
/tmp/
|
14
|
+
|
15
|
+
# Used by dotenv library to load environment variables.
|
16
|
+
# .env
|
17
|
+
|
18
|
+
## Specific to RubyMotion:
|
19
|
+
.dat*
|
20
|
+
.repl_history
|
21
|
+
build/
|
22
|
+
*.bridgesupport
|
23
|
+
build-iPhoneOS/
|
24
|
+
build-iPhoneSimulator/
|
25
|
+
|
26
|
+
## Specific to RubyMotion (use of CocoaPods):
|
27
|
+
#
|
28
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
29
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
30
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
31
|
+
#
|
32
|
+
# vendor/Pods/
|
33
|
+
|
34
|
+
## Documentation cache and generated files:
|
35
|
+
/.yardoc/
|
36
|
+
/_yardoc/
|
37
|
+
/doc/
|
38
|
+
/rdoc/
|
39
|
+
|
40
|
+
## Environment normalization:
|
41
|
+
/.bundle/
|
42
|
+
/vendor/bundle
|
43
|
+
/lib/bundler/man/
|
44
|
+
|
45
|
+
# for a library or gem, you might want to ignore these files since the code is
|
46
|
+
# intended to run in multiple environments; otherwise, check them in:
|
47
|
+
Gemfile.lock
|
48
|
+
# .ruby-version
|
49
|
+
# .ruby-gemset
|
50
|
+
|
51
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
52
|
+
.rvmrc
|
53
|
+
|
data/Gemfile
ADDED
data/alki-dsl.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require_relative 'lib/alki/dsl/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.name = "alki-dsl"
|
6
|
+
spec.version = Alki::Dsl::VERSION
|
7
|
+
spec.authors = ["Matt Edlefsen"]
|
8
|
+
spec.email = ["matt.edlefsen@gmail.com"]
|
9
|
+
spec.summary = %q{Alki dsl library}
|
10
|
+
spec.description = %q{Library for defining and using DSLs}
|
11
|
+
spec.homepage = "https://github.com/medlefsen/alki-dsl"
|
12
|
+
spec.license = "MIT"
|
13
|
+
|
14
|
+
spec.files = `git ls-files -z`.split("\x0")
|
15
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
16
|
+
spec.bindir = 'exe'
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_dependency 'alki-support', '~> 0.3'
|
21
|
+
spec.add_development_dependency 'minitest', '~> 5.9', '>= 5.9.1'
|
22
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'alki/support'
|
3
|
+
|
4
|
+
module Alki
|
5
|
+
class ClassBuilder
|
6
|
+
def self.create_constant(name,value = Class.new, parent=nil)
|
7
|
+
parent ||= Object
|
8
|
+
*ans, ln = name.to_s.split('::')
|
9
|
+
ans.each do |a|
|
10
|
+
unless parent.const_defined? a
|
11
|
+
parent.const_set a, Module.new
|
12
|
+
end
|
13
|
+
parent = parent.const_get a
|
14
|
+
end
|
15
|
+
|
16
|
+
parent.const_set ln, value
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.build(data)
|
20
|
+
class_name = data[:class_name]
|
21
|
+
if !class_name && data[:name] && data[:prefix]
|
22
|
+
class_name = Alki::Support.classify(
|
23
|
+
data[:prefix].empty? ? data[:name] : "#{data[:prefix]}/#{data[:name]}"
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
if data[:type] == :module
|
28
|
+
klass = Module.new
|
29
|
+
else
|
30
|
+
super_class = if data[:super_class]
|
31
|
+
Alki::Support.load_class data[:super_class]
|
32
|
+
else
|
33
|
+
Object
|
34
|
+
end
|
35
|
+
klass = Class.new super_class
|
36
|
+
end
|
37
|
+
build_class data, klass
|
38
|
+
if class_name
|
39
|
+
create_constant class_name, klass, data[:parent_class]
|
40
|
+
end
|
41
|
+
if data[:secondary_classes]
|
42
|
+
data[:secondary_classes].each do |data|
|
43
|
+
if data[:subclass]
|
44
|
+
data = data.merge(parent_class: klass,class_name: data[:subclass])
|
45
|
+
elsif !data[:class_name] && !data[:name]
|
46
|
+
raise ArgumentError.new("Secondary classes must have names")
|
47
|
+
end
|
48
|
+
build data
|
49
|
+
end
|
50
|
+
end
|
51
|
+
klass
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.module_not_empty?(mod)
|
55
|
+
not mod.instance_methods.empty? &&
|
56
|
+
mod.private_instance_methods.empty?
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.build_class(data,klass)
|
60
|
+
if data[:module]
|
61
|
+
if module_not_empty? data[:module]
|
62
|
+
klass.include data[:module]
|
63
|
+
end
|
64
|
+
if data[:module].const_defined?(:ClassMethods) &&
|
65
|
+
module_not_empty?(data[:module]::ClassMethods)
|
66
|
+
klass.extend data[:module]::ClassMethods
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
if data[:modules]
|
71
|
+
data[:modules].each do |mod|
|
72
|
+
klass.include mod
|
73
|
+
end
|
74
|
+
end
|
75
|
+
if data[:class_modules]
|
76
|
+
data[:class_modules].each do |mod|
|
77
|
+
klass.extend mod
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
add_methods klass, data
|
82
|
+
add_initialize klass, data[:initialize_params] if data[:initialize_params]
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.add_methods(klass, data)
|
86
|
+
if data[:class_methods]
|
87
|
+
data[:class_methods].each do |name, method|
|
88
|
+
klass.define_singleton_method name, &method[:body]
|
89
|
+
klass.singleton_class.send :private, name if method[:private]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
if data[:instance_methods]
|
94
|
+
data[:instance_methods].each do |name, method|
|
95
|
+
klass.send :define_method, name, &method[:body]
|
96
|
+
klass.send :private, name if method[:private]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
if data[:delegators]
|
101
|
+
klass.extend Forwardable
|
102
|
+
data[:delegators].each do |name,delegator|
|
103
|
+
klass.def_delegator delegator[:accessor], delegator[:method], name
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
klass.send :attr_reader, *data[:attr_readers] if data[:attr_readers]
|
108
|
+
klass.send :attr_writer, *data[:attr_writers] if data[:attr_writers]
|
109
|
+
klass.send :attr_accessor, *data[:attr_accessors] if data[:attr_accessors]
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.add_initialize(klass,params)
|
113
|
+
at_setters = ''
|
114
|
+
params.each do |(p,default)|
|
115
|
+
if default
|
116
|
+
default_method = "_default_#{p}".to_sym
|
117
|
+
klass.send :define_method, default_method do
|
118
|
+
default
|
119
|
+
end
|
120
|
+
klass.send :private, default_method
|
121
|
+
at_setters << "@#{p} = #{p} || #{default_method}\n"
|
122
|
+
else
|
123
|
+
at_setters << "@#{p} = #{p}\n"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
klass.class_eval "
|
128
|
+
def initialize(#{params.map{|p| p[1] ? "#{p[0]}=nil" : p[0]}.join(', ')})
|
129
|
+
#{at_setters}end"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'alki/support'
|
2
|
+
require 'alki/dsl/evaluator'
|
3
|
+
|
4
|
+
module Alki
|
5
|
+
module Dsl
|
6
|
+
class Base
|
7
|
+
def self.build(data={},&blk)
|
8
|
+
Alki::Dsl::Evaluator.evaluate self, data, &blk
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.generate(ctx)
|
12
|
+
obj = new(ctx)
|
13
|
+
result = {methods: {}}
|
14
|
+
info = self.dsl_info
|
15
|
+
|
16
|
+
result[:init] = obj.method(info[:init]) if info[:init]
|
17
|
+
result[:finish] = obj.method(info[:finish]) if info[:finish]
|
18
|
+
result[:requires] = info[:requires] if info[:requires]
|
19
|
+
result[:helpers] = info[:helpers] if info[:helpers]
|
20
|
+
|
21
|
+
if info[:methods]
|
22
|
+
info[:methods].each do |method|
|
23
|
+
if method.is_a?(Array)
|
24
|
+
name, method = method
|
25
|
+
else
|
26
|
+
name = method
|
27
|
+
end
|
28
|
+
result[:methods][name] = obj.method method
|
29
|
+
end
|
30
|
+
end
|
31
|
+
result
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.dsl_info
|
35
|
+
{}
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(ctx)
|
39
|
+
@ctx = ctx
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_reader :ctx
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'alki/support'
|
2
|
+
require 'alki/dsl/evaluator'
|
3
|
+
|
4
|
+
module Alki
|
5
|
+
module Dsl
|
6
|
+
class Builder
|
7
|
+
def self.build(data,&blk)
|
8
|
+
result = Alki::Dsl::Evaluator.evaluate _dsls,data,&blk
|
9
|
+
if _processor
|
10
|
+
_processor.build result
|
11
|
+
else
|
12
|
+
result
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def self.dsl(name)
|
19
|
+
klass = Alki::Support.load_class name
|
20
|
+
unless klass
|
21
|
+
raise "Unable to load class #{name.inspect}"
|
22
|
+
end
|
23
|
+
dsls = _dsls
|
24
|
+
dsls += [klass]
|
25
|
+
define_singleton_method(:_dsls) { dsls }
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.processor(name)
|
29
|
+
klass = Alki::Support.load_class name
|
30
|
+
define_singleton_method(:_processor) { klass }
|
31
|
+
end
|
32
|
+
|
33
|
+
def self._dsls
|
34
|
+
[]
|
35
|
+
end
|
36
|
+
|
37
|
+
def self._processor
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Alki
|
2
|
+
module Dsl
|
3
|
+
module ClassHelpers
|
4
|
+
def class_builder(subclass = nil)
|
5
|
+
unless @ctx[:class_builder]
|
6
|
+
@ctx[:class_builder] = {}
|
7
|
+
%i(module name prefix).each do |attr|
|
8
|
+
@ctx[:class_builder][attr] = @ctx[attr] if @ctx[attr]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
if subclass
|
12
|
+
scs = @ctx[:class_builder][:secondary_classes] ||= []
|
13
|
+
cb = scs.find { |sc| sc[:subclass] == subclass }
|
14
|
+
unless cb
|
15
|
+
cb = { subclass: subclass }
|
16
|
+
scs << cb
|
17
|
+
end
|
18
|
+
cb
|
19
|
+
else
|
20
|
+
@ctx[:class_builder]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_as_module(subclass: nil)
|
25
|
+
class_builder(subclass)[:type] = :module
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_super_class(klass,subclass: nil)
|
29
|
+
class_builder(subclass)[:super_class] = klass
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_method(name,context:nil,private: false,subclass: nil, &blk)
|
33
|
+
class_builder(subclass)[:instance_methods] ||= {}
|
34
|
+
class_builder(subclass)[:instance_methods][name.to_sym] = {
|
35
|
+
body: blk,
|
36
|
+
context: context,
|
37
|
+
private: private
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_class_method (name,context: nil,private: false,subclass: nil,&blk)
|
42
|
+
class_builder(subclass)[:class_methods] ||= {}
|
43
|
+
class_builder(subclass)[:class_methods][name.to_sym] = {
|
44
|
+
body: blk,
|
45
|
+
context: context,
|
46
|
+
private: private
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_helper(name,&blk)
|
51
|
+
class_builder('Helpers')[:type] = :module
|
52
|
+
add_method name, &blk
|
53
|
+
add_method name, subclass: 'Helpers', &blk
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_helper_module(mod)
|
57
|
+
class_builder('Helpers')[:type] = :module
|
58
|
+
add_module mod
|
59
|
+
add_module mod, subclass: 'Helpers'
|
60
|
+
end
|
61
|
+
|
62
|
+
def add_initialize_param(name,default=nil,subclass: nil)
|
63
|
+
(class_builder(subclass)[:initialize_params]||=[]) << [name.to_sym,default]
|
64
|
+
end
|
65
|
+
|
66
|
+
def add_instance_class_proxy(type, name,subclass: nil)
|
67
|
+
(class_builder(subclass)[:instance_class]||={})[name.to_sym] = {type: type}
|
68
|
+
end
|
69
|
+
|
70
|
+
def add_module(mod,subclass: nil)
|
71
|
+
(class_builder(subclass)[:modules]||=[]) << mod
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_accessor(name,type: :accessor,subclass: nil)
|
75
|
+
(class_builder(subclass)["attr_#{type}s".to_sym]||=[]) << name
|
76
|
+
end
|
77
|
+
|
78
|
+
def add_delegator(name,accessor,method=name,subclass: nil)
|
79
|
+
(class_builder(subclass)[:delegators]||={})[name] = {
|
80
|
+
accessor: accessor,
|
81
|
+
method: method
|
82
|
+
}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'alki/support'
|
3
|
+
|
4
|
+
module Alki
|
5
|
+
module Dsl
|
6
|
+
class Evaluator
|
7
|
+
def initialize
|
8
|
+
@inits = []
|
9
|
+
@finishers = []
|
10
|
+
@processors = []
|
11
|
+
@dsls_seen = Set.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def evaluate(dsl,data={},&blk)
|
15
|
+
mod = (data[:module] ||= Module.new)
|
16
|
+
process_dsl dsl, data
|
17
|
+
|
18
|
+
@inits.each(&:call)
|
19
|
+
dsl_exec mod, &blk
|
20
|
+
@finishers.reverse_each(&:call)
|
21
|
+
clear_dsl_methods mod
|
22
|
+
|
23
|
+
@processors.each do |processor|
|
24
|
+
processor.build data
|
25
|
+
end
|
26
|
+
|
27
|
+
data
|
28
|
+
end
|
29
|
+
|
30
|
+
def process_dsl(dsl,data)
|
31
|
+
return unless @dsls_seen.add? dsl
|
32
|
+
cbs = dsl.generate(data)
|
33
|
+
if cbs[:requires]
|
34
|
+
cbs[:requires].each do |required_dsl|
|
35
|
+
process_dsl Alki::Support.load_class(required_dsl), data
|
36
|
+
end
|
37
|
+
end
|
38
|
+
@inits << cbs[:init] if cbs[:init]
|
39
|
+
@finishers << cbs[:finish] if cbs[:finish]
|
40
|
+
@processors << cbs[:processors] if cbs[:processors]
|
41
|
+
if cbs[:methods]
|
42
|
+
cbs[:methods].each do |name, proc|
|
43
|
+
define_dsl_method data[:module], name, &proc
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def define_dsl_method(mod,name,&blk)
|
49
|
+
mod.define_singleton_method name, &blk
|
50
|
+
end
|
51
|
+
|
52
|
+
def clear_dsl_methods(mod)
|
53
|
+
mod.singleton_methods do |m|
|
54
|
+
mod.singleton_class.send :remove_method, m
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def dsl_exec(mod,&blk)
|
59
|
+
mod.class_exec &blk
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.evaluate(dsl, data={}, &blk)
|
63
|
+
new.evaluate dsl, data, &blk
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'alki/support'
|
2
|
+
require 'alki/dsl'
|
3
|
+
|
4
|
+
module Alki
|
5
|
+
module Dsl
|
6
|
+
class Loader
|
7
|
+
def initialize(root_dir)
|
8
|
+
@root_dir = root_dir
|
9
|
+
end
|
10
|
+
|
11
|
+
def all_paths
|
12
|
+
Dir[File.join(@root_dir,'**','*.rb')].map do |path|
|
13
|
+
path.gsub(File.join(@root_dir,''),'').gsub(/\.rb$/,'')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def load_all
|
18
|
+
all_paths.inject({}) do |h,path|
|
19
|
+
h.merge!(path => Alki::Dsl.load(path))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def load(file)
|
24
|
+
Alki::Dsl.load File.expand_path("#{file}.rb",@root_dir)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'alki/dsl/loader'
|
2
|
+
require 'alki/support'
|
3
|
+
|
4
|
+
module Alki
|
5
|
+
module Dsl
|
6
|
+
module Registry
|
7
|
+
@registered_paths = {}
|
8
|
+
@registered_dirs = {}
|
9
|
+
|
10
|
+
def self.register(path,builder,**data)
|
11
|
+
@registered_paths[File.absolute_path(path)] = Entry.new(builder,data)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.register_dir(dir_path,builder,**data)
|
15
|
+
@registered_dirs[File.join(File.absolute_path(dir_path),'')] = [builder,data]
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.lookup(path, load_configs: true)
|
19
|
+
path = File.absolute_path path
|
20
|
+
entry = @registered_paths[path]
|
21
|
+
return entry if entry
|
22
|
+
|
23
|
+
@registered_dirs.each do |dir,(builder,data)|
|
24
|
+
if path.start_with? dir
|
25
|
+
data = {name: Alki::Support.path_name(path, dir)}.merge data
|
26
|
+
return Entry.new(builder,data)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
if load_configs
|
31
|
+
root = Alki::Support.find_root(path) do |dir|
|
32
|
+
File.exists?(File.join(dir,'config','dsls.rb'))
|
33
|
+
end
|
34
|
+
if root
|
35
|
+
config_file = File.join(root,'config','dsls.rb')
|
36
|
+
register config_file, 'alki/dsls/dsl_config', root: root
|
37
|
+
require config_file
|
38
|
+
return lookup path, load_configs: false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.build(path,&blk)
|
46
|
+
entry = lookup path
|
47
|
+
if entry
|
48
|
+
entry.build blk
|
49
|
+
else
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Entry
|
55
|
+
def initialize(builder,data)
|
56
|
+
@builder = builder
|
57
|
+
@data = data
|
58
|
+
end
|
59
|
+
|
60
|
+
def build(blk)
|
61
|
+
Alki::Support.load_class(@builder).build @data, &blk
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/alki/dsl.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'alki/dsl/evaluator'
|
2
|
+
require 'alki/dsl/registry'
|
3
|
+
|
4
|
+
module Alki
|
5
|
+
module Dsl
|
6
|
+
@loaded = {}
|
7
|
+
def self.[]=(path,value)
|
8
|
+
@loaded[path] =value
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.[](path)
|
12
|
+
@loaded[path]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.register(*args)
|
16
|
+
Alki::Dsl::Registry.register *args
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.register_dir(*args)
|
20
|
+
Alki::Dsl::Registry.register_dir *args
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.load(path)
|
24
|
+
path = File.absolute_path(path)
|
25
|
+
require path
|
26
|
+
self[path]
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.build(name,data={},&blk)
|
30
|
+
Alki::Support.load_class(name).build data, &blk
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module Kernel
|
36
|
+
def Alki(builder=nil,&blk)
|
37
|
+
if blk
|
38
|
+
path = caller_locations(1,1)[0].absolute_path
|
39
|
+
result = if builder
|
40
|
+
builder.build({}, &blk)
|
41
|
+
else
|
42
|
+
Alki::Dsl::Registry.build path, &blk
|
43
|
+
end
|
44
|
+
Alki::Dsl[path] = result
|
45
|
+
end
|
46
|
+
::Alki
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'alki/dsl/base'
|
2
|
+
require 'alki/dsl/class_helpers'
|
3
|
+
require 'alki/class_builder'
|
4
|
+
|
5
|
+
module Alki
|
6
|
+
module Dsls
|
7
|
+
class Class < Alki::Dsl::Base
|
8
|
+
include Alki::Dsl::ClassHelpers
|
9
|
+
|
10
|
+
self::Helpers = Alki::Dsl::ClassHelpers
|
11
|
+
|
12
|
+
def self.dsl_info
|
13
|
+
{
|
14
|
+
methods: %i(class_methods),
|
15
|
+
finish: :finish
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def class_methods(&blk)
|
20
|
+
unless ctx[:module].const_defined? :ClassMethods
|
21
|
+
ctx[:module].const_set :ClassMethods, Module.new
|
22
|
+
end
|
23
|
+
ctx[:module]::ClassMethods.class_exec &blk
|
24
|
+
end
|
25
|
+
|
26
|
+
def finish
|
27
|
+
ctx[:class] = Alki::ClassBuilder.build class_builder
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'alki/dsl/base'
|
2
|
+
require 'alki/support'
|
3
|
+
require 'alki/dsl/class_helpers'
|
4
|
+
|
5
|
+
module Alki
|
6
|
+
module Dsls
|
7
|
+
class Dsl < Alki::Dsl::Base
|
8
|
+
include Alki::Dsl::ClassHelpers
|
9
|
+
Helpers = Alki::Dsl::ClassHelpers
|
10
|
+
|
11
|
+
def self.dsl_info
|
12
|
+
{
|
13
|
+
requires: ['alki/dsls/class'],
|
14
|
+
methods: [
|
15
|
+
:dsl_method,
|
16
|
+
[:init,:dsl_init],
|
17
|
+
[:finish,:dsl_finish],
|
18
|
+
:require_dsl,
|
19
|
+
:helper,
|
20
|
+
:helper_module
|
21
|
+
],
|
22
|
+
init: :init,
|
23
|
+
finish: :finish
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def init
|
28
|
+
@info = {
|
29
|
+
methods: [],
|
30
|
+
requires: []
|
31
|
+
}
|
32
|
+
@helper_modules = []
|
33
|
+
@helpers = {}
|
34
|
+
end
|
35
|
+
|
36
|
+
def dsl_method(name, &blk)
|
37
|
+
method_name = name.to_sym
|
38
|
+
add_method method_name, private: true, &blk
|
39
|
+
@info[:methods] << [name.to_sym,method_name]
|
40
|
+
end
|
41
|
+
|
42
|
+
def dsl_init(&blk)
|
43
|
+
add_method :_dsl_init, private: true, &blk
|
44
|
+
@info[:init] = :_dsl_init
|
45
|
+
end
|
46
|
+
|
47
|
+
def dsl_finish(&blk)
|
48
|
+
add_method :_dsl_finish, private: true, &blk
|
49
|
+
@info[:finish] = :_dsl_finish
|
50
|
+
end
|
51
|
+
|
52
|
+
def require_dsl(dsl)
|
53
|
+
dsl_class = Alki::Support.load_class(dsl)
|
54
|
+
@info[:requires] << dsl_class
|
55
|
+
if defined? dsl_class::Helpers
|
56
|
+
add_module dsl_class::Helpers
|
57
|
+
add_helper_module dsl_class::Helpers
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def helper(name,&blk)
|
62
|
+
add_helper name, &blk
|
63
|
+
end
|
64
|
+
|
65
|
+
def helper_module(mod)
|
66
|
+
add_helper_module mod
|
67
|
+
end
|
68
|
+
|
69
|
+
def finish
|
70
|
+
set_super_class 'alki/dsl/base'
|
71
|
+
info = @info.freeze
|
72
|
+
add_class_method :dsl_info do
|
73
|
+
info
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'alki/dsl/base'
|
2
|
+
|
3
|
+
module Alki
|
4
|
+
module Dsls
|
5
|
+
class DslConfig < Alki::Dsl::Base
|
6
|
+
def self.dsl_info
|
7
|
+
{
|
8
|
+
methods: %i(register register_dir register_lib_dir)
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
def register(path,*args)
|
13
|
+
path = File.expand_path(path,@ctx[:root])
|
14
|
+
Alki::Dsl::Registry.register path, *args
|
15
|
+
end
|
16
|
+
|
17
|
+
def register_dir(path,*args)
|
18
|
+
path = File.expand_path(path,@ctx[:root])
|
19
|
+
Alki::Dsl::Registry.register_dir path, *args
|
20
|
+
end
|
21
|
+
|
22
|
+
def register_lib_dir(prefix,dsl,**data)
|
23
|
+
path = File.join(@ctx[:root],'lib', prefix)
|
24
|
+
Alki::Dsl::Registry.register_dir path, dsl, data.merge(prefix: prefix)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
require 'alki/dsl'
|
3
|
+
|
4
|
+
describe 'dsl configuration' do
|
5
|
+
before do
|
6
|
+
$LOAD_PATH.unshift fixture_path('example','lib')
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should be automatically loaded when requiring a dsl' do
|
10
|
+
require 'alki_test/dsls/number'
|
11
|
+
AlkiTest::Dsls::Number.must_be_instance_of Class
|
12
|
+
AlkiTest::Dsls::Number.singleton_methods.must_include :generate
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should allow using dsls specified in same dsl config' do
|
16
|
+
Kernel.load(fixture_path('example','numbers','three.rb'))
|
17
|
+
AlkiTest::Numbers::Three.new.must_equal 3
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
Alki do
|
2
|
+
require_dsl 'alki/dsls/class'
|
3
|
+
|
4
|
+
helper :value= do |v|
|
5
|
+
ctx[:value] = v
|
6
|
+
end
|
7
|
+
|
8
|
+
helper :value do
|
9
|
+
ctx[:value]
|
10
|
+
end
|
11
|
+
|
12
|
+
finish do
|
13
|
+
create_as_module
|
14
|
+
value = ctx[:value]
|
15
|
+
add_class_method :new do
|
16
|
+
value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
require 'alki/class_builder'
|
3
|
+
|
4
|
+
describe Alki::ClassBuilder do
|
5
|
+
describe :build do
|
6
|
+
def build(data={})
|
7
|
+
Alki::ClassBuilder.build data
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should create and return a new class' do
|
11
|
+
build.must_be_instance_of Class
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should allow creation of modules' do
|
15
|
+
build(type: :module).must_be_instance_of Module
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should allow setting instance methods' do
|
19
|
+
obj = build(
|
20
|
+
instance_methods: {
|
21
|
+
test1: {
|
22
|
+
body: -> { :test1 }
|
23
|
+
},
|
24
|
+
test2: {
|
25
|
+
body: -> { :test2 },
|
26
|
+
private: true
|
27
|
+
}
|
28
|
+
}
|
29
|
+
).new
|
30
|
+
obj.test1.must_equal :test1
|
31
|
+
assert_raises NoMethodError do
|
32
|
+
obj.test2
|
33
|
+
end
|
34
|
+
obj.send(:test2).must_equal :test2
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should allow setting class methods' do
|
38
|
+
klass = build(
|
39
|
+
class_methods: {
|
40
|
+
test1: {
|
41
|
+
body: -> { :test1 }
|
42
|
+
},
|
43
|
+
test2: {
|
44
|
+
body: -> { :test2 },
|
45
|
+
private: true
|
46
|
+
}
|
47
|
+
}
|
48
|
+
)
|
49
|
+
klass.test1.must_equal :test1
|
50
|
+
assert_raises NoMethodError do
|
51
|
+
klass.test2
|
52
|
+
end
|
53
|
+
klass.send(:test2).must_equal :test2
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should allow setting super class' do
|
57
|
+
super_class = Class.new
|
58
|
+
build(super_class: super_class).superclass.must_equal super_class
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should allow setting super class by name' do
|
62
|
+
class AlkiTestClass; end
|
63
|
+
build(super_class: 'alki_test_class').superclass.must_equal AlkiTestClass
|
64
|
+
Object.send :remove_const, :AlkiTestClass
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should include module and extend module::ClassMethods if provided' do
|
68
|
+
m = Module.new do
|
69
|
+
module self::ClassMethods
|
70
|
+
def test1
|
71
|
+
:test1
|
72
|
+
end
|
73
|
+
end
|
74
|
+
def test2
|
75
|
+
:test2
|
76
|
+
end
|
77
|
+
end
|
78
|
+
klass = build(module: m)
|
79
|
+
klass.included_modules.must_include m
|
80
|
+
klass.test1.must_equal :test1
|
81
|
+
klass.new.test2.must_equal :test2
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'should include modules' do
|
85
|
+
ms = [Module.new,Module.new]
|
86
|
+
klass = build(modules: ms)
|
87
|
+
ms.each do |m|
|
88
|
+
klass.included_modules.must_include m
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'should extend class_modules' do
|
93
|
+
ms = [
|
94
|
+
Module.new { def test1; :test1; end },
|
95
|
+
Module.new { def test2; :test2; end },
|
96
|
+
]
|
97
|
+
klass = build(class_modules: ms)
|
98
|
+
klass.test1.must_equal :test1
|
99
|
+
klass.test2.must_equal :test2
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'should create basic #initialize using initialize_params' do
|
103
|
+
obj = build(initialize_params: [:a,:b]).new 1, 2
|
104
|
+
obj.instance_variable_get(:@a).must_equal 1
|
105
|
+
obj.instance_variable_get(:@b).must_equal 2
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'should allow providing a class name' do
|
109
|
+
if defined?(AlkiTestClass)
|
110
|
+
Object.send :remove_const, :AlkiTestClass
|
111
|
+
end
|
112
|
+
build(class_name: "AlkiTestClass")
|
113
|
+
assert(defined?(AlkiTestClass),'Expected AlkiTestClass to be defined')
|
114
|
+
Object.send :remove_const, :AlkiTestClass
|
115
|
+
assert(!defined?(AlkiTestClass))
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'should use name to create class name' do
|
119
|
+
if defined?(AlkiTest::TestClass)
|
120
|
+
Object.send :remove_const, :AlkiTest
|
121
|
+
end
|
122
|
+
build(prefix: '', name: "alki_test/test_class")
|
123
|
+
assert(defined?(AlkiTest::TestClass),'Expected AlkiTest::TestClass to be defined')
|
124
|
+
Object.send :remove_const, :AlkiTest
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'should use prefix and name to create class name' do
|
128
|
+
if defined?(AlkiTest::TestClass)
|
129
|
+
Object.send :remove_const, :AlkiTest
|
130
|
+
end
|
131
|
+
build(prefix: 'alki_test', name: "test_class")
|
132
|
+
assert(defined?(AlkiTest::TestClass),'Expected AlkiTest::TestClass to be defined')
|
133
|
+
Object.send :remove_const, :AlkiTest
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'should allow creating secondary classes' do
|
137
|
+
build(
|
138
|
+
secondary_classes: [
|
139
|
+
{
|
140
|
+
class_name: 'AlkiTestClass'
|
141
|
+
}
|
142
|
+
]
|
143
|
+
)
|
144
|
+
assert(defined?(AlkiTestClass),'Expected AlkiTestClass to be defined')
|
145
|
+
Object.send :remove_const, :AlkiTestClass
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: alki-dsl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matt Edlefsen
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-12-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: alki-support
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.9'
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 5.9.1
|
37
|
+
type: :development
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '5.9'
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 5.9.1
|
47
|
+
description: Library for defining and using DSLs
|
48
|
+
email:
|
49
|
+
- matt.edlefsen@gmail.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- ".gitignore"
|
55
|
+
- Gemfile
|
56
|
+
- alki-dsl.gemspec
|
57
|
+
- lib/alki/class_builder.rb
|
58
|
+
- lib/alki/dsl.rb
|
59
|
+
- lib/alki/dsl/base.rb
|
60
|
+
- lib/alki/dsl/builder.rb
|
61
|
+
- lib/alki/dsl/class_helpers.rb
|
62
|
+
- lib/alki/dsl/evaluator.rb
|
63
|
+
- lib/alki/dsl/loader.rb
|
64
|
+
- lib/alki/dsl/registry.rb
|
65
|
+
- lib/alki/dsl/version.rb
|
66
|
+
- lib/alki/dsls/class.rb
|
67
|
+
- lib/alki/dsls/dsl.rb
|
68
|
+
- lib/alki/dsls/dsl_config.rb
|
69
|
+
- test/feature/config_test.rb
|
70
|
+
- test/fixtures/example/config/dsls.rb
|
71
|
+
- test/fixtures/example/lib/alki_test/dsls/number.rb
|
72
|
+
- test/fixtures/example/lib/alki_test/dsls/value.rb
|
73
|
+
- test/fixtures/example/numbers/three.rb
|
74
|
+
- test/integration/class_builder_test.rb
|
75
|
+
- test/test_helper.rb
|
76
|
+
- test/unit/evaluator_test.rb
|
77
|
+
homepage: https://github.com/medlefsen/alki-dsl
|
78
|
+
licenses:
|
79
|
+
- MIT
|
80
|
+
metadata: {}
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
requirements: []
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 2.5.1
|
98
|
+
signing_key:
|
99
|
+
specification_version: 4
|
100
|
+
summary: Alki dsl library
|
101
|
+
test_files:
|
102
|
+
- test/feature/config_test.rb
|
103
|
+
- test/fixtures/example/config/dsls.rb
|
104
|
+
- test/fixtures/example/lib/alki_test/dsls/number.rb
|
105
|
+
- test/fixtures/example/lib/alki_test/dsls/value.rb
|
106
|
+
- test/fixtures/example/numbers/three.rb
|
107
|
+
- test/integration/class_builder_test.rb
|
108
|
+
- test/test_helper.rb
|
109
|
+
- test/unit/evaluator_test.rb
|