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