Permalink
Browse files

Version 0.0.1 - first public version with generation

  • Loading branch information...
0 parents commit 8d3d17016117638a029cb0a7d30c23dd6d020bf8 @brianknapp brianknapp committed Dec 22, 2012
Showing with 526 additions and 0 deletions.
  1. +17 −0 .gitignore
  2. +4 −0 Gemfile
  3. +22 −0 LICENSE.txt
  4. +29 −0 README.md
  5. +1 −0 Rakefile
  6. +7 −0 bin/obvious
  7. +284 −0 lib/obvious.rb
  8. +4 −0 lib/obvious/files/Rakefile
  9. +135 −0 lib/obvious/files/contract.rb
  10. +3 −0 lib/obvious/version.rb
  11. +20 −0 obvious.gemspec
17 .gitignore
@@ -0,0 +1,17 @@
+*.gem
+*.rbc
+.bundle
+.config
+.yardoc
+Gemfile.lock
+InstalledFiles
+_yardoc
+coverage
+doc/
+lib/bundler/man
+pkg
+rdoc
+spec/reports
+test/tmp
+test/version_tmp
+tmp
4 Gemfile
@@ -0,0 +1,4 @@
+source 'https://rubygems.org'
+
+# Specify your gem's dependencies in obvious.gemspec
+gemspec
22 LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright (c) 2012 Brian Knapp
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 README.md
@@ -0,0 +1,29 @@
+# Obvious
+
+TODO: Write a gem description
+
+## Installation
+
+Add this line to your application's Gemfile:
+
+ gem 'obvious'
+
+And then execute:
+
+ $ bundle
+
+Or install it yourself as:
+
+ $ gem install obvious
+
+## Usage
+
+TODO: Write usage instructions here
+
+## Contributing
+
+1. Fork it
+2. Create your feature branch (`git checkout -b my-new-feature`)
+3. Commit your changes (`git commit -am 'Add some feature'`)
+4. Push to the branch (`git push origin my-new-feature`)
+5. Create new Pull Request
1 Rakefile
@@ -0,0 +1 @@
+require "bundler/gem_tasks"
7 bin/obvious
@@ -0,0 +1,7 @@
+#!/usr/bin/env ruby
+
+require 'obvious'
+
+if ARGV[0] == 'generate'
+ Obvious::generate
+end
284 lib/obvious.rb
@@ -0,0 +1,284 @@
+require "obvious/version"
+require 'yaml'
+
+module Obvious
+ # Your code goes here...
+ def self.generate
+ puts 'generate the codes!a'
+
+
+
+#`rm -rf app`
+
+app_dir = 'app'
+
+counter = 1
+while File.directory? app_dir
+ app_dir = "app_#{counter}"
+ counter += 1
+end
+
+puts "Generating application at: #{app_dir}"
+
+dirs = ['/', '/actions', '/contracts', '/entities',
+ '/spec', '/spec/actions', '/spec/contracts', '/spec/entities',
+ '/spec/doubles']
+
+dirs.each do |dir|
+ Dir.mkdir app_dir + dir
+end
+
+
+target_path = File.realpath Dir.pwd
+spec = Gem::Specification.find_by_name("obvious")
+gem_root = spec.gem_dir
+gem_lib = gem_root + "/lib"
+
+`cp #{gem_lib}/obvious/files/contract.rb #{target_path}/app/contracts/contract.rb`
+`cp #{gem_lib}/obvious/files/Rakefile #{target_path}/Rakefile`
+entities = Hash.new
+jacks = Hash.new
+
+files = Dir['descriptors/*.yml']
+
+files.each do |file|
+ action = YAML.load_file file
+ code = ''
+ #puts action.inspect
+
+ local_jacks = Hash.new
+ local_entities = Hash.new
+
+ action['Code'].each do |entry|
+ code << " \# #{entry['c']}\n"
+ code << " \# use: #{entry['requires']}\n" if entry['requires']
+ code << " \n"
+
+ if entry['requires']
+ requires = entry['requires'].split(',')
+ requires.each do |req|
+ req.strip!
+ info = req.split '.'
+
+ if info[0].index 'Jack'
+ unless jacks[info[0]]
+ jacks[info[0]] = []
+ end
+
+ unless local_jacks[info[0]]
+ local_jacks[info[0]] = []
+ end
+
+ jacks[info[0]] << info[1]
+ local_jacks[info[0]] << info[1]
+ else
+ unless entities[info[0]]
+ entities[info[0]] = []
+ end
+
+ unless local_entities[info[0]]
+ local_entities[info[0]] = []
+ end
+
+ entities[info[0]] << info[1]
+ local_entities[info[0]] << info[1]
+ end
+
+ end
+ end
+
+ end
+
+
+ jack_inputs = ''
+ jack_assignments = ''
+
+ local_jacks.each do |k, v|
+ name = k.chomp('Jack').downcase
+ jack_inputs << "#{name}_jack, "
+ jack_assignments << " @#{name}_jack = #{name}_jack\n"
+ end
+
+ jack_inputs.chomp! ', '
+
+ entity_requires = ''
+
+ local_entities.each do |k, v|
+ name = k.downcase
+ entity_requires << "require_relative '../entities/#{name}'\n"
+ end
+
+
+ output = <<FIN
+#{entity_requires}
+class #{action['Action']}
+
+ def initialize #{jack_inputs}
+#{jack_assignments} end
+
+ def do input
+#{code} end
+
+end
+FIN
+ snake_name = action['Action'].gsub(/(.)([A-Z])/,'\1_\2').downcase
+
+ filename = "app/actions/#{snake_name}.rb"
+ File.open(filename, 'w') {|f| f.write(output) }
+
+#puts output
+
+ output = <<FIN
+require_relative '../../actions/#{snake_name}'
+
+describe #{action['Action']} do
+
+ it '#{action['Description']}'
+
+ it 'should raise an error with invalid input'
+
+end
+
+
+FIN
+
+ filename = "app/spec/actions/#{snake_name}_spec.rb"
+ File.open(filename, 'w') {|f| f.write(output) }
+
+ #puts output
+end
+
+
+#filter out duplicate methods
+
+entities.each do |k, v|
+ v.uniq!
+end
+
+jacks.each do |k,v|
+ v.uniq!
+end
+
+#puts entities.inspect
+#puts jacks.inspect
+
+entities.each do |k, v|
+ name = k
+ method_specs = ''
+ method_definitions = ''
+
+ v.each do |method|
+ method_definitions << "
+ def #{method} input
+ nil
+ end
+ "
+
+ method_specs << "
+ describe '.#{method}' do
+ it 'should #{method} with valid input'
+
+ it 'should raise an error with invalid input'
+
+ end
+ "
+
+ end
+
+ output = <<FIN
+class #{name}
+#{method_definitions}
+end
+FIN
+ snake_name = name.gsub(/(.)([A-Z])/,'\1_\2').downcase
+
+ filename = "app/entities/#{snake_name}.rb"
+ File.open(filename, 'w') {|f| f.write(output) }
+
+ output = <<FIN
+require_relative '../../entities/#{snake_name}'
+
+describe #{name} do
+#{method_specs}
+end
+
+
+FIN
+ filename = "app/spec/entities/#{snake_name}_spec.rb"
+ File.open(filename, 'w') {|f| f.write(output) }
+
+
+ #puts output
+end
+
+
+
+jacks.each do |k, v|
+
+ name = k.chomp('Jack').downcase
+
+ method_specs = ''
+ method_definitions = ''
+
+ v.each do |method|
+
+ method_definitions << "
+ def #{method}_contract input
+ input_shape = {}
+ output_shape = {}
+ call_method :#{method}_alias, input, input_shape, output_shape
+ end
+ "
+
+ method_specs << "
+ describe '.#{method}_contract' do
+ it 'should #{method} data with valid input'
+
+ it 'should raise an error with invalid input'
+
+ it 'should raise an error with invalid output'
+
+ end
+ "
+
+ end
+
+
+ output = <<FIN
+require_relative 'contract'
+
+class #{k}Contract < Contract
+ def self.contracts
+ #{v.to_s}
+ end
+#{method_definitions}
+end
+FIN
+
+ snake_name = name.gsub(/(.)([A-Z])/,'\1_\2').downcase
+
+ filename = "app/contracts/#{snake_name}_contract.rb"
+ File.open(filename, 'w') {|f| f.write(output) }
+
+ #puts output
+
+ output = <<FIN
+require_relative '../../contracts/#{snake_name}_contract'
+
+describe #{k}Contract do
+#{method_specs}
+end
+
+FIN
+
+ filename = "app/spec/contracts/#{snake_name}_spec.rb"
+ File.open(filename, 'w') {|f| f.write(output) }
+
+ #puts output
+end
+
+
+
+
+ end
+end
4 lib/obvious/files/Rakefile
@@ -0,0 +1,4 @@
+task :rspec do
+ sh 'rspec ./app -c'
+end
+
135 lib/obvious/files/contract.rb
@@ -0,0 +1,135 @@
+class Contract
+ @@disable_override = false
+
+ # This method needs to exist because the method_added bit looks for it.
+ # It intentionally returns an empty array
+ def self.contracts
+ []
+ end
+
+ # This method will move methods defined in self.contracts into new methods.
+ # Each entry in self.contracts will cause the method with the same name to
+ # become method_name_alias and for the original method to point to
+ # method_name_contract.
+ def self.method_added name
+ unless @@disable_override
+ self.contracts.each do |method|
+ if name == method.to_sym
+ method_alias = "#{method}_alias".to_sym
+ method_contract = "#{method}_contract".to_sym
+
+ @@disable_override = true # to stop the new build method
+ self.send :alias_method, method_alias, name
+ self.send :remove_method, name
+ self.send :alias_method, name, method_contract
+
+ @@disable_override = false
+ else
+ # puts self.inspect
+ # puts "defining other method #{name}"
+ end
+ end
+ end
+ end
+
+ # This method is used as a shorthand to mak the contract method calling pattern more DRY
+ # It starts by checking if you are sending in input and if so will check the input shape for
+ # errors. If no errors are found it calls the method via the passed in symbol(method).
+ #
+ # Output checking is more complicated because of the types of output we check for. Nil is
+ # never valid output. If we pass in the output shape of true, that means we are looking for
+ # result to be the object True. If the output shape is an array, that is actually a shorthand
+ # for telling our output check to look at the output as an array and compare it to the shape
+ # stored in output_shape[0]. If we pass in the symbol :true_false it means we are looking for
+ # the result to be either true or false. The default case will just check if result has the shape
+ # of the output_shape.
+ def call_method method, input, input_shape, output_shape
+ if input != nil && input_shape != nil
+ unless input.has_shape? input_shape
+ raise ContractInputError, 'incorrect input data format'
+ end
+
+ result = self.send method, input
+ else
+ result = self.send method
+ end
+
+ # check output
+ # output should never be nil
+ if result == nil
+ raise ContractOutputError, 'incorrect output data format'
+ end
+
+ # we are looking for result to be a True object
+ if output_shape === true
+ if output_shape == result
+ return result
+ else
+ raise ContractOutputError, 'incorrect output data format'
+ end
+ end
+
+ # we want to check the shape of each item in the result array
+ if output_shape.class == Array
+ if result.class == Array
+ inner_shape = output_shape[0]
+ result.each do |item|
+ unless item.has_shape? inner_shape
+ raise ContractOutputError, 'incorrect output data format'
+ end
+ end
+
+ return result
+ end
+ raise ContractOutputError, 'incorrect output data format'
+ end
+
+ # we want result to be true or false
+ if output_shape == :true_false
+ unless result == true || result == false
+ raise ContractOutputError, 'incorrect output data format'
+ end
+
+ return result
+ end
+
+ # we want result to be output_shape's shape
+ unless result.has_shape? output_shape
+ raise ContractOutputError, 'incorrect output data format'
+ end
+
+ result
+ end
+
+end
+
+# via https://github.com/citizen428/shenanigans/blob/master/lib/shenanigans/hash/has_shape_pred.rb
+class Hash
+ # Checks if a hash has a certain structure.
+ # h = { k1: 1, k2: "1" }
+ # h.has_shape?(k1: Fixnum, k2: String)
+ # #=> true
+ # h.has_shape?(k1: Class, k2: String)
+ # #=> false
+ # It also works with compound data structures.
+ # h = { k1: [], k2: { k3: Struct.new("Foo") } }
+ # shape = { k1: Array, k2: { k3: Module } }
+ # h.has_shape?(shape)
+ # #=> true
+ def has_shape?(shape)
+ # I added an empty check
+ if self.empty?
+ return shape.empty?
+ end
+
+ all? do |k, v|
@hqm42
hqm42 Jan 7, 2013

Is this the desired behavour?

{a: 1}.has_shape?(a: Fixnum, b: Fixnum)
=> true
@brianknapp
brianknapp Jan 8, 2013

No it isn't. I pulled that code from elsewhere. I need to run some tests on it and to fix it.

The desired behavior is that it would force both a and b to be fixnums in that case and both to be in the hash.

+ Hash === v ? v.has_shape?(shape[k]) : shape[k] === v
+ end
+ end
+end
+
+class ContractInputError < StandardError
+end
+
+class ContractOutputError < StandardError
+end
3 lib/obvious/version.rb
@@ -0,0 +1,3 @@
+module Obvious
+ VERSION = "0.0.1"
+end
20 obvious.gemspec
@@ -0,0 +1,20 @@
+# -*- encoding: utf-8 -*-
+lib = File.expand_path('../lib', __FILE__)
+$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
+require 'obvious/version'
+
+Gem::Specification.new do |gem|
+ gem.name = "obvious"
+ gem.version = Obvious::VERSION
+ gem.authors = ["Brian Knapp"]
+ gem.email = ["brianknapp@gmail.com"]
+ gem.description = "A set of tools to build apps using the Obvious Architecture"
+ gem.summary = "Isn't it Obvious?"
+ gem.homepage = ""
+
+ gem.files = `git ls-files`.split($/)
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
+ #gem.executables << 'obvious'
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
+ gem.require_paths = ["lib"]
+end

0 comments on commit 8d3d170

Please sign in to comment.