obvious 0.0.0 → 0.0.1
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.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/bin/obvious +7 -0
- data/lib/obvious/files/Rakefile +4 -0
- data/lib/obvious/files/contract.rb +135 -0
- data/lib/obvious/version.rb +3 -0
- data/lib/obvious.rb +282 -3
- data/obvious.gemspec +20 -0
- metadata +20 -7
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Brian Knapp
|
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,29 @@
|
|
1
|
+
# Obvious
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'obvious'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install obvious
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/obvious
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
class Contract
|
2
|
+
@@disable_override = false
|
3
|
+
|
4
|
+
# This method needs to exist because the method_added bit looks for it.
|
5
|
+
# It intentionally returns an empty array
|
6
|
+
def self.contracts
|
7
|
+
[]
|
8
|
+
end
|
9
|
+
|
10
|
+
# This method will move methods defined in self.contracts into new methods.
|
11
|
+
# Each entry in self.contracts will cause the method with the same name to
|
12
|
+
# become method_name_alias and for the original method to point to
|
13
|
+
# method_name_contract.
|
14
|
+
def self.method_added name
|
15
|
+
unless @@disable_override
|
16
|
+
self.contracts.each do |method|
|
17
|
+
if name == method.to_sym
|
18
|
+
method_alias = "#{method}_alias".to_sym
|
19
|
+
method_contract = "#{method}_contract".to_sym
|
20
|
+
|
21
|
+
@@disable_override = true # to stop the new build method
|
22
|
+
self.send :alias_method, method_alias, name
|
23
|
+
self.send :remove_method, name
|
24
|
+
self.send :alias_method, name, method_contract
|
25
|
+
|
26
|
+
@@disable_override = false
|
27
|
+
else
|
28
|
+
# puts self.inspect
|
29
|
+
# puts "defining other method #{name}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# This method is used as a shorthand to mak the contract method calling pattern more DRY
|
36
|
+
# It starts by checking if you are sending in input and if so will check the input shape for
|
37
|
+
# errors. If no errors are found it calls the method via the passed in symbol(method).
|
38
|
+
#
|
39
|
+
# Output checking is more complicated because of the types of output we check for. Nil is
|
40
|
+
# never valid output. If we pass in the output shape of true, that means we are looking for
|
41
|
+
# result to be the object True. If the output shape is an array, that is actually a shorthand
|
42
|
+
# for telling our output check to look at the output as an array and compare it to the shape
|
43
|
+
# stored in output_shape[0]. If we pass in the symbol :true_false it means we are looking for
|
44
|
+
# the result to be either true or false. The default case will just check if result has the shape
|
45
|
+
# of the output_shape.
|
46
|
+
def call_method method, input, input_shape, output_shape
|
47
|
+
if input != nil && input_shape != nil
|
48
|
+
unless input.has_shape? input_shape
|
49
|
+
raise ContractInputError, 'incorrect input data format'
|
50
|
+
end
|
51
|
+
|
52
|
+
result = self.send method, input
|
53
|
+
else
|
54
|
+
result = self.send method
|
55
|
+
end
|
56
|
+
|
57
|
+
# check output
|
58
|
+
# output should never be nil
|
59
|
+
if result == nil
|
60
|
+
raise ContractOutputError, 'incorrect output data format'
|
61
|
+
end
|
62
|
+
|
63
|
+
# we are looking for result to be a True object
|
64
|
+
if output_shape === true
|
65
|
+
if output_shape == result
|
66
|
+
return result
|
67
|
+
else
|
68
|
+
raise ContractOutputError, 'incorrect output data format'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# we want to check the shape of each item in the result array
|
73
|
+
if output_shape.class == Array
|
74
|
+
if result.class == Array
|
75
|
+
inner_shape = output_shape[0]
|
76
|
+
result.each do |item|
|
77
|
+
unless item.has_shape? inner_shape
|
78
|
+
raise ContractOutputError, 'incorrect output data format'
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
return result
|
83
|
+
end
|
84
|
+
raise ContractOutputError, 'incorrect output data format'
|
85
|
+
end
|
86
|
+
|
87
|
+
# we want result to be true or false
|
88
|
+
if output_shape == :true_false
|
89
|
+
unless result == true || result == false
|
90
|
+
raise ContractOutputError, 'incorrect output data format'
|
91
|
+
end
|
92
|
+
|
93
|
+
return result
|
94
|
+
end
|
95
|
+
|
96
|
+
# we want result to be output_shape's shape
|
97
|
+
unless result.has_shape? output_shape
|
98
|
+
raise ContractOutputError, 'incorrect output data format'
|
99
|
+
end
|
100
|
+
|
101
|
+
result
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
# via https://github.com/citizen428/shenanigans/blob/master/lib/shenanigans/hash/has_shape_pred.rb
|
107
|
+
class Hash
|
108
|
+
# Checks if a hash has a certain structure.
|
109
|
+
# h = { k1: 1, k2: "1" }
|
110
|
+
# h.has_shape?(k1: Fixnum, k2: String)
|
111
|
+
# #=> true
|
112
|
+
# h.has_shape?(k1: Class, k2: String)
|
113
|
+
# #=> false
|
114
|
+
# It also works with compound data structures.
|
115
|
+
# h = { k1: [], k2: { k3: Struct.new("Foo") } }
|
116
|
+
# shape = { k1: Array, k2: { k3: Module } }
|
117
|
+
# h.has_shape?(shape)
|
118
|
+
# #=> true
|
119
|
+
def has_shape?(shape)
|
120
|
+
# I added an empty check
|
121
|
+
if self.empty?
|
122
|
+
return shape.empty?
|
123
|
+
end
|
124
|
+
|
125
|
+
all? do |k, v|
|
126
|
+
Hash === v ? v.has_shape?(shape[k]) : shape[k] === v
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class ContractInputError < StandardError
|
132
|
+
end
|
133
|
+
|
134
|
+
class ContractOutputError < StandardError
|
135
|
+
end
|
data/lib/obvious.rb
CHANGED
@@ -1,5 +1,284 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
require "obvious/version"
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Obvious
|
5
|
+
# Your code goes here...
|
6
|
+
def self.generate
|
7
|
+
puts 'generate the codes!a'
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
#`rm -rf app`
|
12
|
+
|
13
|
+
app_dir = 'app'
|
14
|
+
|
15
|
+
counter = 1
|
16
|
+
while File.directory? app_dir
|
17
|
+
app_dir = "app_#{counter}"
|
18
|
+
counter += 1
|
19
|
+
end
|
20
|
+
|
21
|
+
puts "Generating application at: #{app_dir}"
|
22
|
+
|
23
|
+
dirs = ['/', '/actions', '/contracts', '/entities',
|
24
|
+
'/spec', '/spec/actions', '/spec/contracts', '/spec/entities',
|
25
|
+
'/spec/doubles']
|
26
|
+
|
27
|
+
dirs.each do |dir|
|
28
|
+
Dir.mkdir app_dir + dir
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
target_path = File.realpath Dir.pwd
|
33
|
+
spec = Gem::Specification.find_by_name("obvious")
|
34
|
+
gem_root = spec.gem_dir
|
35
|
+
gem_lib = gem_root + "/lib"
|
36
|
+
|
37
|
+
`cp #{gem_lib}/obvious/files/contract.rb #{target_path}/app/contracts/contract.rb`
|
38
|
+
`cp #{gem_lib}/obvious/files/Rakefile #{target_path}/Rakefile`
|
39
|
+
entities = Hash.new
|
40
|
+
jacks = Hash.new
|
41
|
+
|
42
|
+
files = Dir['descriptors/*.yml']
|
43
|
+
|
44
|
+
files.each do |file|
|
45
|
+
action = YAML.load_file file
|
46
|
+
code = ''
|
47
|
+
#puts action.inspect
|
48
|
+
|
49
|
+
local_jacks = Hash.new
|
50
|
+
local_entities = Hash.new
|
51
|
+
|
52
|
+
action['Code'].each do |entry|
|
53
|
+
code << " \# #{entry['c']}\n"
|
54
|
+
code << " \# use: #{entry['requires']}\n" if entry['requires']
|
55
|
+
code << " \n"
|
56
|
+
|
57
|
+
if entry['requires']
|
58
|
+
requires = entry['requires'].split(',')
|
59
|
+
requires.each do |req|
|
60
|
+
req.strip!
|
61
|
+
info = req.split '.'
|
62
|
+
|
63
|
+
if info[0].index 'Jack'
|
64
|
+
unless jacks[info[0]]
|
65
|
+
jacks[info[0]] = []
|
66
|
+
end
|
67
|
+
|
68
|
+
unless local_jacks[info[0]]
|
69
|
+
local_jacks[info[0]] = []
|
70
|
+
end
|
71
|
+
|
72
|
+
jacks[info[0]] << info[1]
|
73
|
+
local_jacks[info[0]] << info[1]
|
74
|
+
else
|
75
|
+
unless entities[info[0]]
|
76
|
+
entities[info[0]] = []
|
77
|
+
end
|
78
|
+
|
79
|
+
unless local_entities[info[0]]
|
80
|
+
local_entities[info[0]] = []
|
81
|
+
end
|
82
|
+
|
83
|
+
entities[info[0]] << info[1]
|
84
|
+
local_entities[info[0]] << info[1]
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
jack_inputs = ''
|
94
|
+
jack_assignments = ''
|
95
|
+
|
96
|
+
local_jacks.each do |k, v|
|
97
|
+
name = k.chomp('Jack').downcase
|
98
|
+
jack_inputs << "#{name}_jack, "
|
99
|
+
jack_assignments << " @#{name}_jack = #{name}_jack\n"
|
100
|
+
end
|
101
|
+
|
102
|
+
jack_inputs.chomp! ', '
|
103
|
+
|
104
|
+
entity_requires = ''
|
105
|
+
|
106
|
+
local_entities.each do |k, v|
|
107
|
+
name = k.downcase
|
108
|
+
entity_requires << "require_relative '../entities/#{name}'\n"
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
output = <<FIN
|
113
|
+
#{entity_requires}
|
114
|
+
class #{action['Action']}
|
115
|
+
|
116
|
+
def initialize #{jack_inputs}
|
117
|
+
#{jack_assignments} end
|
118
|
+
|
119
|
+
def do input
|
120
|
+
#{code} end
|
121
|
+
|
122
|
+
end
|
123
|
+
FIN
|
124
|
+
snake_name = action['Action'].gsub(/(.)([A-Z])/,'\1_\2').downcase
|
125
|
+
|
126
|
+
filename = "app/actions/#{snake_name}.rb"
|
127
|
+
File.open(filename, 'w') {|f| f.write(output) }
|
128
|
+
|
129
|
+
#puts output
|
130
|
+
|
131
|
+
output = <<FIN
|
132
|
+
require_relative '../../actions/#{snake_name}'
|
133
|
+
|
134
|
+
describe #{action['Action']} do
|
135
|
+
|
136
|
+
it '#{action['Description']}'
|
137
|
+
|
138
|
+
it 'should raise an error with invalid input'
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
FIN
|
144
|
+
|
145
|
+
filename = "app/spec/actions/#{snake_name}_spec.rb"
|
146
|
+
File.open(filename, 'w') {|f| f.write(output) }
|
147
|
+
|
148
|
+
#puts output
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
#filter out duplicate methods
|
153
|
+
|
154
|
+
entities.each do |k, v|
|
155
|
+
v.uniq!
|
156
|
+
end
|
157
|
+
|
158
|
+
jacks.each do |k,v|
|
159
|
+
v.uniq!
|
160
|
+
end
|
161
|
+
|
162
|
+
#puts entities.inspect
|
163
|
+
#puts jacks.inspect
|
164
|
+
|
165
|
+
entities.each do |k, v|
|
166
|
+
name = k
|
167
|
+
method_specs = ''
|
168
|
+
method_definitions = ''
|
169
|
+
|
170
|
+
v.each do |method|
|
171
|
+
method_definitions << "
|
172
|
+
def #{method} input
|
173
|
+
nil
|
174
|
+
end
|
175
|
+
"
|
176
|
+
|
177
|
+
method_specs << "
|
178
|
+
describe '.#{method}' do
|
179
|
+
it 'should #{method} with valid input'
|
180
|
+
|
181
|
+
it 'should raise an error with invalid input'
|
182
|
+
|
183
|
+
end
|
184
|
+
"
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
output = <<FIN
|
189
|
+
class #{name}
|
190
|
+
#{method_definitions}
|
191
|
+
end
|
192
|
+
FIN
|
193
|
+
snake_name = name.gsub(/(.)([A-Z])/,'\1_\2').downcase
|
194
|
+
|
195
|
+
filename = "app/entities/#{snake_name}.rb"
|
196
|
+
File.open(filename, 'w') {|f| f.write(output) }
|
197
|
+
|
198
|
+
output = <<FIN
|
199
|
+
require_relative '../../entities/#{snake_name}'
|
200
|
+
|
201
|
+
describe #{name} do
|
202
|
+
#{method_specs}
|
203
|
+
end
|
204
|
+
|
205
|
+
|
206
|
+
FIN
|
207
|
+
filename = "app/spec/entities/#{snake_name}_spec.rb"
|
208
|
+
File.open(filename, 'w') {|f| f.write(output) }
|
209
|
+
|
210
|
+
|
211
|
+
#puts output
|
212
|
+
end
|
213
|
+
|
214
|
+
|
215
|
+
|
216
|
+
jacks.each do |k, v|
|
217
|
+
|
218
|
+
name = k.chomp('Jack').downcase
|
219
|
+
|
220
|
+
method_specs = ''
|
221
|
+
method_definitions = ''
|
222
|
+
|
223
|
+
v.each do |method|
|
224
|
+
|
225
|
+
method_definitions << "
|
226
|
+
def #{method}_contract input
|
227
|
+
input_shape = {}
|
228
|
+
output_shape = {}
|
229
|
+
call_method :#{method}_alias, input, input_shape, output_shape
|
230
|
+
end
|
231
|
+
"
|
232
|
+
|
233
|
+
method_specs << "
|
234
|
+
describe '.#{method}_contract' do
|
235
|
+
it 'should #{method} data with valid input'
|
236
|
+
|
237
|
+
it 'should raise an error with invalid input'
|
238
|
+
|
239
|
+
it 'should raise an error with invalid output'
|
240
|
+
|
241
|
+
end
|
242
|
+
"
|
243
|
+
|
244
|
+
end
|
245
|
+
|
246
|
+
|
247
|
+
output = <<FIN
|
248
|
+
require_relative 'contract'
|
249
|
+
|
250
|
+
class #{k}Contract < Contract
|
251
|
+
def self.contracts
|
252
|
+
#{v.to_s}
|
253
|
+
end
|
254
|
+
#{method_definitions}
|
255
|
+
end
|
256
|
+
FIN
|
257
|
+
|
258
|
+
snake_name = name.gsub(/(.)([A-Z])/,'\1_\2').downcase
|
259
|
+
|
260
|
+
filename = "app/contracts/#{snake_name}_contract.rb"
|
261
|
+
File.open(filename, 'w') {|f| f.write(output) }
|
262
|
+
|
263
|
+
#puts output
|
264
|
+
|
265
|
+
output = <<FIN
|
266
|
+
require_relative '../../contracts/#{snake_name}_contract'
|
267
|
+
|
268
|
+
describe #{k}Contract do
|
269
|
+
#{method_specs}
|
270
|
+
end
|
271
|
+
|
272
|
+
FIN
|
273
|
+
|
274
|
+
filename = "app/spec/contracts/#{snake_name}_spec.rb"
|
275
|
+
File.open(filename, 'w') {|f| f.write(output) }
|
276
|
+
|
277
|
+
#puts output
|
278
|
+
end
|
279
|
+
|
280
|
+
|
281
|
+
|
282
|
+
|
4
283
|
end
|
5
284
|
end
|
data/obvious.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'obvious/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "obvious"
|
8
|
+
gem.version = Obvious::VERSION
|
9
|
+
gem.authors = ["Brian Knapp"]
|
10
|
+
gem.email = ["brianknapp@gmail.com"]
|
11
|
+
gem.description = "A set of tools to build apps using the Obvious Architecture"
|
12
|
+
gem.summary = "Isn't it Obvious?"
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
#gem.executables << 'obvious'
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: obvious
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,16 +9,28 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-12-22 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: A set of tools to build apps using the Obvious Architecture
|
15
|
-
email:
|
16
|
-
|
15
|
+
email:
|
16
|
+
- brianknapp@gmail.com
|
17
|
+
executables:
|
18
|
+
- obvious
|
17
19
|
extensions: []
|
18
20
|
extra_rdoc_files: []
|
19
21
|
files:
|
22
|
+
- .gitignore
|
23
|
+
- Gemfile
|
24
|
+
- LICENSE.txt
|
25
|
+
- README.md
|
26
|
+
- Rakefile
|
27
|
+
- bin/obvious
|
20
28
|
- lib/obvious.rb
|
21
|
-
|
29
|
+
- lib/obvious/files/Rakefile
|
30
|
+
- lib/obvious/files/contract.rb
|
31
|
+
- lib/obvious/version.rb
|
32
|
+
- obvious.gemspec
|
33
|
+
homepage: ''
|
22
34
|
licenses: []
|
23
35
|
post_install_message:
|
24
36
|
rdoc_options: []
|
@@ -38,8 +50,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
38
50
|
version: '0'
|
39
51
|
requirements: []
|
40
52
|
rubyforge_project:
|
41
|
-
rubygems_version: 1.8.
|
53
|
+
rubygems_version: 1.8.17
|
42
54
|
signing_key:
|
43
55
|
specification_version: 3
|
44
|
-
summary:
|
56
|
+
summary: Isn't it Obvious?
|
45
57
|
test_files: []
|
58
|
+
has_rdoc:
|