dsl_maker 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,47 @@
1
+ # This verifies the various error-handling situations.
2
+
3
+ describe "DSL::Maker validation" do
4
+ it "requires a block for :add_entrypoint" do
5
+ expect {
6
+ Class.new(DSL::Maker) do
7
+ add_entrypoint(:pizza)
8
+ end
9
+ }.to raise_error('Block required for add_entrypoint')
10
+ end
11
+
12
+ it "requires a block for :generate_dsl" do
13
+ expect {
14
+ Class.new(DSL::Maker) do
15
+ add_entrypoint(:first, {
16
+ :second => generate_dsl({})
17
+ }) {}
18
+ end
19
+ }.to raise_error('Block required for generate_dsl')
20
+ end
21
+
22
+ it "requires a recognized type for attributes" do
23
+ expect {
24
+ Class.new(DSL::Maker) do
25
+ add_entrypoint(:pizza, {
26
+ :cheese => true,
27
+ }) do
28
+ Pizza.new(cheese, nil, nil, nil)
29
+ end
30
+ end
31
+ }.to raise_error("Unrecognized element type 'true'")
32
+ end
33
+
34
+ it "rejects attributes which block Boolean helper methods" do
35
+ %w(yes no on off __apply).each do |name|
36
+ expect {
37
+ Class.new(DSL::Maker) do
38
+ add_entrypoint(:pizza, {
39
+ name => String,
40
+ }) do
41
+ Pizza.new(cheese, nil, nil, nil)
42
+ end
43
+ end
44
+ }.to raise_error("Illegal attribute name '#{name}'")
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,158 @@
1
+ # This will use a DSL that parses a family tree.
2
+ #
3
+ # Notes:
4
+ # 1. Because we're creating classes on the fly, we must fully-qualify the Boolean
5
+ # class name. If we created real classes, the context would be provided for us.
6
+ describe 'A multi-level DSL making family-trees' do
7
+ Person = Struct.new(:name, :child)
8
+
9
+ it "can handle a simple single-level parse of a two-level DSL" do
10
+ dsl_class = Class.new(DSL::Maker) do
11
+ add_entrypoint(:person, {
12
+ :name => String,
13
+ :child => generate_dsl({
14
+ :name => String,
15
+ }) {
16
+ Person.new(name)
17
+ },
18
+ }) do
19
+ Person.new(name, child)
20
+ end
21
+ end
22
+
23
+ person = dsl_class.parse_dsl('person { name "Tom" }')
24
+ expect(person).to be_instance_of(Person)
25
+ expect(person.name).to eq('Tom')
26
+ expect(person.child).to be_nil
27
+ end
28
+
29
+ it "can handle a two-level parse of a two-level DSL" do
30
+ dsl_class = Class.new(DSL::Maker) do
31
+ add_entrypoint(:person, {
32
+ :name => String,
33
+ :child => generate_dsl({
34
+ :name => String,
35
+ }) {
36
+ Person.new(name, nil)
37
+ },
38
+ }) do
39
+ Person.new(name, child)
40
+ end
41
+ end
42
+
43
+ person = dsl_class.parse_dsl("
44
+ person {
45
+ name 'Tom'
46
+ child {
47
+ name 'Bill'
48
+ }
49
+ }
50
+ ")
51
+ expect(person).to be_instance_of(Person)
52
+ expect(person.name).to eq('Tom')
53
+ expect(person.child).to be_instance_of(Person)
54
+ expect(person.child.name).to eq('Bill')
55
+ end
56
+
57
+ it "can handle a three-level parse of a three-level DSL" do
58
+ dsl_class = Class.new(DSL::Maker) do
59
+ add_entrypoint(:person, {
60
+ :name => String,
61
+ :child => generate_dsl({
62
+ :name => String,
63
+ :child => generate_dsl({
64
+ :name => String,
65
+ }) {
66
+ Person.new(name, nil)
67
+ },
68
+ }) {
69
+ Person.new(name, child)
70
+ },
71
+ }) do
72
+ Person.new(name, child)
73
+ end
74
+ end
75
+
76
+ person = dsl_class.parse_dsl("
77
+ person {
78
+ name 'Tom'
79
+ child {
80
+ name 'Bill'
81
+ child {
82
+ name 'Judith'
83
+ }
84
+ }
85
+ }
86
+ ")
87
+ expect(person).to be_instance_of(Person)
88
+ expect(person.name).to eq('Tom')
89
+ expect(person.child).to be_instance_of(Person)
90
+ expect(person.child.name).to eq('Bill')
91
+ expect(person.child.child).to be_instance_of(Person)
92
+ expect(person.child.child.name).to eq('Judith')
93
+ end
94
+
95
+ it "can define things recursively" do
96
+ dsl_class = Class.new(DSL::Maker) do
97
+ person = generate_dsl({
98
+ :name => String,
99
+ }) {
100
+ Person.new(name, child)
101
+ }
102
+ build_dsl_element(person, :child, person)
103
+
104
+ add_entrypoint(:person, {
105
+ :name => String,
106
+ :child => person,
107
+ }) do
108
+ Person.new(name, child)
109
+ end
110
+ end
111
+
112
+ # This is taken from https://en.wikipedia.org/wiki/Family_tree_of_the_Bible
113
+ person = dsl_class.parse_dsl("
114
+ person {
115
+ name 'Adam'
116
+ child {
117
+ name 'Seth'
118
+ child {
119
+ name 'Enos'
120
+ child {
121
+ name 'Cainan'
122
+ child {
123
+ name 'Mahalaleel'
124
+ child {
125
+ name 'Jared'
126
+ child {
127
+ name 'Enoch'
128
+ child {
129
+ name 'Methuselah'
130
+ child {
131
+ name 'Lamech'
132
+ child {
133
+ name 'Noah'
134
+ child {
135
+ name 'Shem'
136
+ }
137
+ }
138
+ }
139
+ }
140
+ }
141
+ }
142
+ }
143
+ }
144
+ }
145
+ }
146
+ }
147
+ ")
148
+
149
+ [
150
+ 'Adam', 'Seth', 'Enos', 'Cainan', 'Mahalaleel', 'Jared',
151
+ 'Enoch', 'Methuselah', 'Lamech', 'Noah', 'Shem',
152
+ ].each do |name|
153
+ expect(person).to be_instance_of(Person)
154
+ expect(person.name).to eq(name)
155
+ person = person.child
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,56 @@
1
+ # This will use a DSL that defines Cars
2
+
3
+ describe "A DSL describing cars used with multiple invocations" do
4
+ Car = Struct.new(:maker)
5
+ Truck = Struct.new(:maker)
6
+
7
+ it "returns two items in the right order" do
8
+ dsl_class = Class.new(DSL::Maker) do
9
+ add_entrypoint(:car, {
10
+ :maker => String,
11
+ }) do
12
+ Car.new(maker)
13
+ end
14
+ end
15
+
16
+ cars = dsl_class.parse_dsl("
17
+ car { maker 'Honda' }
18
+ car { maker 'Acura' }
19
+ ")
20
+ expect(cars).to be_instance_of(Array)
21
+ expect(cars.length).to eq(2)
22
+ expect(cars[0]).to be_instance_of(Car)
23
+ expect(cars[0].maker).to eq('Honda')
24
+ expect(cars[1]).to be_instance_of(Car)
25
+ expect(cars[1].maker).to eq('Acura')
26
+ end
27
+
28
+ it "returns items from different entrypoints in the right order" do
29
+ dsl_class = Class.new(DSL::Maker) do
30
+ add_entrypoint(:car, {
31
+ :maker => String,
32
+ }) do
33
+ Car.new(maker)
34
+ end
35
+ add_entrypoint(:truck, {
36
+ :maker => String,
37
+ }) do
38
+ Truck.new(maker)
39
+ end
40
+ end
41
+
42
+ vehicles = dsl_class.parse_dsl("
43
+ truck { maker 'Ford' }
44
+ car { maker 'Honda' }
45
+ truck { maker 'Toyota' }
46
+ ")
47
+ expect(vehicles).to be_instance_of(Array)
48
+ expect(vehicles.length).to eq(3)
49
+ expect(vehicles[0]).to be_instance_of(Truck)
50
+ expect(vehicles[0].maker).to eq('Ford')
51
+ expect(vehicles[1]).to be_instance_of(Car)
52
+ expect(vehicles[1].maker).to eq('Honda')
53
+ expect(vehicles[2]).to be_instance_of(Truck)
54
+ expect(vehicles[2].maker).to eq('Toyota')
55
+ end
56
+ end
@@ -0,0 +1,131 @@
1
+ # This will use the pizza-maker DSL from Docile's documentation.
2
+ # @sauce_level = :extra
3
+ # pizza do
4
+ # cheese yes
5
+ # pepperoni yes
6
+ # sauce @sauce_level
7
+ # end
8
+ #=> #<Pizza:0x00001009dc398 @cheese=true, @pepperoni=true, @bacon=false, @sauce=:extra>
9
+
10
+ # Notes:
11
+ # 1. Because we're creating classes on the fly, we must fully-qualify the Boolean
12
+ # class name. If we created real classes, the context would be provided for us.
13
+ describe 'A single-level DSL for pizza' do
14
+ $toppings = [:cheese, :pepperoni, :bacon, :sauce]
15
+ Pizza = Struct.new(*$toppings)
16
+
17
+ def verify_pizza(pizza, values={})
18
+ expect(pizza).to be_instance_of(Pizza)
19
+ $toppings.each do |topping|
20
+ expect(pizza.send(topping)).to eq(values[topping])
21
+ end
22
+ end
23
+
24
+ it 'makes a blank pizza' do
25
+ dsl_class = Class.new(DSL::Maker) do
26
+ add_entrypoint(:pizza) { Pizza.new }
27
+ end
28
+
29
+ pizza = dsl_class.parse_dsl('pizza')
30
+ verify_pizza(pizza)
31
+ end
32
+
33
+ # This tests all the possible Boolean invocations
34
+ it 'makes a cheese(-less) pizza' do
35
+ dsl_class = Class.new(DSL::Maker) do
36
+ add_entrypoint(:pizza, {
37
+ :cheese => DSL::Maker::Boolean,
38
+ }) do
39
+ Pizza.new(cheese, nil, nil, nil)
40
+ end
41
+ end
42
+
43
+ # There is no way to tell if this is invocation is to set the value or to
44
+ # retrieve the value from within the DSL. Therefore, we assume it's a getter
45
+ # and don't default the value to true. Otherwise, false values wouldn't work.
46
+ pizza = dsl_class.parse_dsl('pizza { cheese }')
47
+ verify_pizza(pizza, :cheese => false)
48
+
49
+ # Test the Ruby booleans and falsey's.
50
+ [ true, false, nil ].each do |cheese|
51
+ pizza = dsl_class.parse_dsl("pizza { cheese #{cheese} }")
52
+ verify_pizza(pizza, :cheese => !!cheese)
53
+ end
54
+
55
+ # Test the true values we provide
56
+ %w(Yes On True yes on).each do |cheese|
57
+ pizza = dsl_class.parse_dsl("pizza { cheese #{cheese} }")
58
+ verify_pizza(pizza, :cheese => true)
59
+ end
60
+
61
+ # Test the false values we provide
62
+ %w(No Off False no off).each do |cheese|
63
+ pizza = dsl_class.parse_dsl("pizza { cheese #{cheese} }")
64
+ verify_pizza(pizza, :cheese => false)
65
+ end
66
+
67
+ # Test the boolean-ized strings we provide
68
+ %w(Yes On True yes on true).each do |cheese|
69
+ pizza = dsl_class.parse_dsl("pizza { cheese '#{cheese}' }")
70
+ verify_pizza(pizza, :cheese => true)
71
+ end
72
+ %w(No Off False no off false nil).each do |cheese|
73
+ pizza = dsl_class.parse_dsl("pizza { cheese '#{cheese}' }")
74
+ verify_pizza(pizza, :cheese => false)
75
+ end
76
+
77
+ # Test some other things which should all be true
78
+ pizza = dsl_class.parse_dsl("pizza { cheese 5 }")
79
+ verify_pizza(pizza, :cheese => true)
80
+ end
81
+
82
+ it 'makes a saucy pizza' do
83
+ dsl_class = Class.new(DSL::Maker) do
84
+ add_entrypoint(:pizza, {
85
+ :sauce => String,
86
+ }) do
87
+ Pizza.new(nil, nil, nil, sauce)
88
+ end
89
+ end
90
+
91
+ [ :extra, 'extra', :none ].each do |level|
92
+ pizza = case level
93
+ when String
94
+ dsl_class.parse_dsl("pizza { sauce '#{level}' }")
95
+ when Symbol
96
+ dsl_class.parse_dsl("pizza { sauce :#{level} }")
97
+ else
98
+ raise "Unexpected class #{level.class}"
99
+ end
100
+ verify_pizza(pizza, :sauce => level.to_s)
101
+ end
102
+ end
103
+
104
+ it 'makes a pizza with everything' do
105
+ dsl_class = Class.new(DSL::Maker) do
106
+ add_entrypoint(:pizza, {
107
+ :cheese => DSL::Maker::Boolean,
108
+ :bacon => DSL::Maker::Boolean,
109
+ :pepperoni => DSL::Maker::Boolean,
110
+ :sauce => String,
111
+ }) do
112
+ Pizza.new(cheese, pepperoni, bacon, sauce)
113
+ end
114
+ end
115
+
116
+ pizza = dsl_class.parse_dsl("
117
+ pizza {
118
+ cheese yes
119
+ pepperoni yes
120
+ bacon no
121
+ sauce :extra
122
+ }
123
+ ")
124
+ verify_pizza(pizza,
125
+ :sauce => 'extra',
126
+ :pepperoni => true,
127
+ :bacon => false,
128
+ :cheese => true,
129
+ )
130
+ end
131
+ end
@@ -0,0 +1,31 @@
1
+ require File.expand_path('on_what', File.dirname(File.dirname(__FILE__)))
2
+
3
+ RSpec.configure do |config|
4
+ config.expect_with :rspec do |c|
5
+ c.syntax = :expect
6
+ end
7
+ end
8
+
9
+ unless on_1_8?
10
+ begin
11
+ require 'simplecov'
12
+
13
+ SimpleCov.configure do
14
+ add_filter '/spec/'
15
+ add_filter '/vendor/'
16
+ minimum_coverage 100
17
+ refuse_coverage_drop
18
+ end
19
+
20
+ if on_travis?
21
+ require 'codecov'
22
+ SimpleCov.formatter = SimpleCov::Formatter::Codecov
23
+ end
24
+
25
+ SimpleCov.start
26
+ rescue LoadError
27
+ puts "Coverage is disabled - install simplecov to enable."
28
+ end
29
+ end
30
+
31
+ require 'dsl/maker'
metadata ADDED
@@ -0,0 +1,172 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dsl_maker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Rob Kinyon
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: docile
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.1'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.1.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.1'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.1.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: rake
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '10'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '10'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: 3.0.0
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 3.0.0
57
+ type: :development
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: 3.0.0
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 3.0.0
67
+ - !ruby/object:Gem::Dependency
68
+ name: simplecov
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ type: :development
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ - !ruby/object:Gem::Dependency
82
+ name: yard
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '0.8'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: '0.8'
95
+ - !ruby/object:Gem::Dependency
96
+ name: redcarpet
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: '3'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: '3'
109
+ - !ruby/object:Gem::Dependency
110
+ name: github-markup
111
+ requirement: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
114
+ - !ruby/object:Gem::Version
115
+ version: '1.3'
116
+ type: :development
117
+ prerelease: false
118
+ version_requirements: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - "~>"
121
+ - !ruby/object:Gem::Version
122
+ version: '1.3'
123
+ description: Easy multi-level DSLs, built on top of Docile
124
+ email: rob.kinyon@gmail.com
125
+ executables: []
126
+ extensions: []
127
+ extra_rdoc_files: []
128
+ files:
129
+ - ".gitattributes"
130
+ - ".gitignore"
131
+ - ".rspec"
132
+ - ".travis.yml"
133
+ - ".yardopts"
134
+ - Gemfile
135
+ - LICENSE
136
+ - README.md
137
+ - Rakefile
138
+ - dsl_maker.gemspec
139
+ - lib/dsl/maker.rb
140
+ - lib/dsl/maker/version.rb
141
+ - on_what.rb
142
+ - spec/args_spec.rb
143
+ - spec/error_spec.rb
144
+ - spec/multi_level_spec.rb
145
+ - spec/multiple_invocation_spec.rb
146
+ - spec/single_level_spec.rb
147
+ - spec/spec_helper.rb
148
+ homepage: https://github.com/robkinyon/ruby-dsl-maker
149
+ licenses:
150
+ - GPL2
151
+ metadata: {}
152
+ post_install_message:
153
+ rdoc_options: []
154
+ require_paths:
155
+ - lib
156
+ required_ruby_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: 1.9.3
161
+ required_rubygems_version: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ requirements: []
167
+ rubyforge_project:
168
+ rubygems_version: 2.4.6
169
+ signing_key:
170
+ specification_version: 4
171
+ summary: Easy multi-level DSLs
172
+ test_files: []