clearest 0.0.1a

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in clearest.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,23 @@
1
+ clearest Copyright (c) 2012-2013 Ultragreen Software, Romain GEORGES
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions
6
+ are met:
7
+ 1. Redistributions of source code must retain the above copyright
8
+ notice, this list of conditions and the following disclaimer.
9
+ 2. Redistributions in binary form must reproduce the above copyright
10
+ notice, this list of conditions and the following disclaimer in the
11
+ documentation and/or other materials provided with the distribution.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23
+ SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Clearest
2
+
3
+ Clearest : Ruby Rack RESTfull services generator
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'clearest'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install clearest
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( http://github.com/<my-github-username>/clearest/fork )
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,62 @@
1
+ #require 'bundler_geminabox/gem_tasks'
2
+ require "bundler/gem_tasks"
3
+ require 'rubygems'
4
+ require 'rspec'
5
+ require 'rake'
6
+ require "rake/clean"
7
+ require "rubygems/package_task"
8
+ require "rdoc/task"
9
+ require 'code_statistics'
10
+ require 'rspec/core/rake_task'
11
+ require 'yard'
12
+ require 'yard/rake/yardoc_task.rb'
13
+ require "rake/tasklib"
14
+ require "roodi"
15
+ require "roodi_task"
16
+ require 'rake/version_task'
17
+
18
+ RoodiTask.new() do | t |
19
+ t.patterns = %w(lib/**/*.rb)
20
+ t.config = "ultragreen_roodi_coding_convention.yml"
21
+ end
22
+
23
+
24
+ CLEAN.include('*.tmp','*.old')
25
+ CLOBBER.include('*.tmp', 'build/*','#*#')
26
+
27
+
28
+ content = File::readlines(File.join(File.dirname(__FILE__), 'clearest.gemspec')).join
29
+ spec = eval(content)
30
+
31
+ RSpec::Core::RakeTask.new('spec')
32
+
33
+ YARD::Rake::YardocTask.new do |t|
34
+ t.files = [ 'lib/**/*.rb', '-', 'doc/**/*','spec/**/*_spec.rb']
35
+ t.options += ['--title', "Gem Documentation"]
36
+ t.options += ['-o', "yardoc"]
37
+ end
38
+ YARD::Config.load_plugin('yard-rspec')
39
+
40
+ namespace :yardoc do
41
+ task :clobber do
42
+ rm_r "yardoc" rescue nil
43
+ rm_r ".yardoc" rescue nil
44
+ end
45
+ end
46
+ task :clobber => "yardoc:clobber"
47
+
48
+
49
+ Gem::PackageTask.new(spec) do |pkg|
50
+ pkg.need_tar = true
51
+ pkg.need_zip = true
52
+ end
53
+
54
+ Rake::RDocTask.new('rdoc') do |d|
55
+ d.rdoc_files.include('doc/**/*','bin/*')
56
+ d.title = 'Dorsal : Yard'
57
+ d.options << '--line-numbers' << '--diagram' << '-SHN'
58
+ end
59
+
60
+ task :default => [:gem]
61
+
62
+ Rake::VersionTask.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.01a
data/bin/clearest ADDED
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+ require 'clearest'
3
+ require 'thor'
4
+
5
+ module CleaRESTCLI
6
+ class Application < Thor
7
+ desc "new <name> <dsn>", "Adds a new CleaREST application"
8
+ def new(name,dsn)
9
+ Clearest::ApplicationBuilder::new :project_name => name, :target_path => '.', :dsn => dsn
10
+ end
11
+
12
+ desc "show", "Display information on the current application"
13
+ def show
14
+ end
15
+ end
16
+
17
+
18
+ class Services < Thor
19
+ desc "add <table>", "Adds the new service named <table> to the current application"
20
+ def add(table)
21
+ app = Clearest::Validators::Applications::check :path => '.'
22
+ Clearest::ServiceBuilder::new :table_name => table, :dsn => app[:dsn], :project_name => app[:project_name], :target_path => '.'
23
+ end
24
+ desc "remove <name>", "remove service <name> of the current application"
25
+ def remove(name)
26
+
27
+ end
28
+
29
+ end
30
+
31
+ class CleaREST < Thor
32
+ desc "application SUBCOMMAND ...ARGS", "manage CleaREST application"
33
+ subcommand "application", Application
34
+ desc "services SUBCOMMAND ...ARGS", "manage services in application"
35
+ subcommand "services", Services
36
+ end
37
+ end
38
+
39
+ CleaRESTCLI::CleaREST.start(ARGV)
40
+
41
+
data/clearest.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'clearest/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "clearest"
8
+ spec.version = Clearest::VERSION
9
+ spec.authors = ["Romain GEORGES"]
10
+ spec.email = ["romain@ultragreen.net"]
11
+ spec.summary = %q{Clearest: REST Api/crud generator }
12
+ spec.description = %q{Clearest: command line tools for REST (Rack/Sinatra) API generation}
13
+ spec.homepage = "http://github.com/Ultragreen/clearest"
14
+ spec.license = "BSD-2"
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.5"
21
+ spec.add_development_dependency "rake"
22
+ spec.add_development_dependency 'rspec', '~> 2.14', '>= 2.14.1'
23
+ spec.add_development_dependency 'yard', '~> 0.8', '>= 0.8.7.2'
24
+ spec.add_development_dependency 'rdoc', '~> 4.0', '>= 4.0.1'
25
+ spec.add_development_dependency 'roodi', '~> 3.1', '>= 3.1.1'
26
+ spec.add_development_dependency 'code_statistics', '~> 0.2', '>= 0.2.13'
27
+ spec.add_development_dependency 'yard-rspec', '~> 0.1'
28
+
29
+
30
+ spec.add_dependency 'dm-is-reflective', '= 1.2.0'
31
+ spec.add_dependency 'colorize', '~> 0.7', '>= 0.7.7'
32
+ spec.add_dependency 'dm-sqlite-adapter', '~> 1.2', '>= 1.2.0'
33
+ # spec.add_development_dependency "bundler_geminabox", "~> 0.2",'>= 0.2.0'
34
+ spec.add_development_dependency "version", "~> 1.0",'>= 1.0.0'
35
+
36
+ end
data/lib/clearest.rb ADDED
@@ -0,0 +1,20 @@
1
+ require "clearest/version"
2
+ require 'template'
3
+ require 'pp'
4
+ require 'date'
5
+ require 'data_mapper'
6
+ require 'dm-is-reflective'
7
+ require 'dm-core'
8
+ require 'colorize'
9
+ require 'dm-sqlite-adapter'
10
+
11
+
12
+
13
+
14
+ Dir[File.dirname(__FILE__) + '/clearest/patches/*_patch.rb'].sort.each {|file| require file }
15
+ Dir[File.dirname(__FILE__) + '/clearest/generators/*.rb'].sort.each {|file| require file }
16
+ Dir[File.dirname(__FILE__) + '/clearest/builders/*.rb'].sort.each {|file| require file }
17
+ Dir[File.dirname(__FILE__) + '/clearest/validators/*.rb'].sort.each {|file| require file }
18
+
19
+
20
+
@@ -0,0 +1,13 @@
1
+ module Clearest
2
+ class ApplicationBuilder
3
+ def initialize(_options = {})
4
+ _options[:template_map] = {
5
+ 'main.rb' => [:dsn]
6
+ }
7
+ Clearest::FoldersGenerator::new _options
8
+ Clearest::FilesCopyGenerator::new _options
9
+ Clearest::TemplatesGenerator::new _options
10
+ File.open("#{_options[:target_path]}/#{_options[:project_name]}/application.yml", 'w') {|f| f.write _options.to_yaml }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ module Clearest
2
+ class ServiceBuilder
3
+ def initialize(_options = {})
4
+ _options[:template_map] = {
5
+ 'models/TABLE_NAME.rb' => [:model_definition],
6
+ 'routes/TABLE_NAME.rb' => [:prefix,:table_name,:model_name],
7
+ 'spec/TABLE_NAME_spec.rb' => [:prefix,:table_name,:model_name,:test_db_filename,:first_obj,:second_obj,:updated_field,:new_value]
8
+ }
9
+ Clearest::TemplatesGenerator::new _options
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Clearest
2
+ class AbstractGenerator
3
+ def initialize(_options = {})
4
+ @options = _options
5
+ @template_root = File.expand_path("../../../template", __FILE__)
6
+ @@dm ||= DataMapper.setup(:default, _options[:dsn])
7
+ @options[:list_folder] = Dir.glob("#{@template_root}/*").reject {|folder| (File::file? folder) }.map {|folder| folder.split('/').last }
8
+ @options[:list_folder] << '.'
9
+ @options[:prefix] ||= '/api'
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ module Clearest
2
+ class FilesCopyGenerator < AbstractGenerator
3
+ def initialize(_options = {})
4
+ super _options
5
+ @options[:list_folder].each do |folder|
6
+ files = Dir.glob("#{@template_root}/#{folder}/*").reject {|file| (file =~ /.tpl$/ or File::directory? file) }
7
+ files.each do |file|
8
+ fullpath = "#{@options[:target_path]}/#{@options[:project_name]}/#{folder}"
9
+ puts " #{'copying'.colorize(:light_green)} (file) #{fullpath}/#{File.basename(file)}"
10
+ FileUtils::copy file, fullpath
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,92 @@
1
+ module Clearest
2
+ class AutoFixtures < AbstractGenerator
3
+
4
+ FLOAT_MAX = 256
5
+ INT_MAX = 256
6
+ STRING_MAX_LENGTH = 100
7
+
8
+ attr_reader :fixtures
9
+
10
+ def initialize(_options = {})
11
+ super(_options)
12
+ generate
13
+ end
14
+
15
+
16
+ private
17
+ def random_string(length=10)
18
+ chars = 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ0123456789'
19
+ password = ''
20
+ length.times { password << chars[rand(chars.size)] }
21
+ password
22
+ end
23
+
24
+
25
+ def fp_rand(limit)
26
+ fl = limit.floor
27
+ rm = limit.remainder(fl)
28
+ rand(fl) + rand(rm)
29
+ end
30
+
31
+ def ignore
32
+ end
33
+
34
+ def mapping(_options = { :field => 'default',:type => 'String', :length => STRING_MAX_LENGTH})
35
+ type_map = { 'DateTime' => DateTime.now,
36
+ 'String' => "#{random_string(_options[:length]-(_options[:field].size + 1))}_#{_options[:field]}",
37
+ 'Text' => "#{random_string(STRING_MAX_LENGTH- (_options[:field].size + 1))}_#{_options[:field]}",
38
+ 'Serial' => ignore,
39
+ 'Boolean' => true,
40
+ 'Float' => fp_rand(FLOAT_MAX),
41
+ 'Decimal' => fp_rand(FLOAT_MAX),
42
+ 'Integer' => rand(INT_MAX)
43
+ }
44
+ return type_map[_options[:type]]
45
+ end
46
+
47
+ def generate
48
+ @fixtures = Hash::new
49
+ @@dm.fields(@options[:table_name]).each do |content|
50
+ field,type,spec = content
51
+ type = type.to_s.split('::').last
52
+ length = (spec[:length])? spec[:length] : STRING_MAX_LENGTH
53
+ fixture = mapping :type => type, :length => length, :field => field
54
+ @fixtures[field] = {:type => type, :length => length, :value => fixture } unless fixture.nil?
55
+ end
56
+ @fixtures
57
+ end
58
+
59
+ public
60
+
61
+
62
+ def get_value_for(_options = {})
63
+ field = _options[:field]
64
+ field_spec = @fixtures[field]
65
+ return mapping :type => field_spec[:type], :length => field_spec[:length], :field => field
66
+ end
67
+
68
+ def get_field(_options = { :type => ['String','Integer','Text','Float'] })
69
+ @test_map = Hash::new
70
+ @fixtures.each {|item,content| @test_map[item] = content[:type] }
71
+ _options[:type].each do |type|
72
+ res = @test_map.rassoc type
73
+ return res unless res.nil?
74
+ end
75
+ return false
76
+ end
77
+
78
+ def to_s
79
+ res = "{\n"
80
+ records = Array::new
81
+ @fixtures.each do |field,content|
82
+ if ['Float','Decimal','Integer'].include? content[:type] then
83
+ records << " :#{field} => #{content[:value]}"
84
+ else
85
+ records << " :#{field} => \"#{content[:value]}\""
86
+ end
87
+ end
88
+ res << records.join(",\n")
89
+ res << "\n }\n"
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,13 @@
1
+ module Clearest
2
+ class FoldersGenerator < AbstractGenerator
3
+ def initialize(_options = {})
4
+ super _options
5
+ @options[:list_folder].each do |folder|
6
+ next if folder == '.'
7
+ fullpath = "#{@options[:target_path]}/#{@options[:project_name]}/#{folder}"
8
+ FileUtils.mkdir_p fullpath
9
+ puts " #{'creating'.colorize(:light_green)} (folder) #{fullpath}"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,37 @@
1
+ module Clearest
2
+ class TemplatesGenerator < AbstractGenerator
3
+ def initialize(_options = {})
4
+ super _options
5
+ fullpath = "#{@options[:target_path]}"
6
+ @options[:test_db_filename] ||= '/tmp/clearest.db'
7
+ @options[:template_map].each do |template,list|
8
+ file_target = (@options[:table_name])? template.sub('TABLE_NAME', @options[:table_name]) : template
9
+ generator = Template::new :list_token => list, :template_file => "#{@template_root}/#{template}.tpl"
10
+ @options[:model_name] = DataMapper::Inflector.singularize _options[:table_name].capitalize if _options[:table_name]
11
+ _data = @options.dup
12
+ _data.each {|item,value| _data.delete item unless list.include? item.to_sym}
13
+ if template == 'models/TABLE_NAME.rb' then
14
+ models = @@dm.auto_genclass! :storages => @options[:table_name], :scope => Object
15
+ _data[:model_definition] = models.first.to_source
16
+ end
17
+ fixtures = AutoFixtures::new(_options) if _options[:table_name]
18
+ if template == 'spec/TABLE_NAME_spec.rb' then
19
+ [:first_obj,:second_obj].each do |obj|
20
+ _data[obj] = fixtures.to_s
21
+ end
22
+ field,type = fixtures.get_field
23
+ new_fixture = fixtures.get_value_for :field => field
24
+ _data[:updated_field] = ":#{field.to_s}"
25
+ if ['Float','Decimal','Integer'].include? type then
26
+ _data[:new_value] = "#{new_fixture}"
27
+ else
28
+ _data[:new_value] = "'#{new_fixture}'"
29
+ end
30
+ end
31
+ generator.map _data
32
+ puts " #{'staging'.colorize(:light_green)} (template) #{fullpath}/#{file_target}"
33
+ File.open("#{fullpath}/#{file_target}", 'w') { |file| file.write(generator.output) }
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,36 @@
1
+ module DmIsReflective::SqliteAdapterPatch
2
+ def reflective_lookup_primitive primitive
3
+ case primitive.upcase
4
+ when 'INTEGER' ; Integer
5
+ when 'REAL', 'NUMERIC', 'FLOAT'; Float
6
+ when 'VARCHAR' ; String
7
+ when 'TIMESTAMP' ; DateTime
8
+ when 'BOOLEAN' ; Property::Boolean
9
+ when 'TEXT' ; Property::Text
10
+ else Property::Text
11
+ end || super(primitive)
12
+ end
13
+ end
14
+
15
+
16
+ module DmIsReflective::ClassMethod
17
+ def to_source scope=nil
18
+
19
+ <<-RUBY
20
+ class #{scope}::#{name} < #{superclass}
21
+ include DataMapper::Resource
22
+ #{
23
+ properties.map do |prop|
24
+ hash = prop.options.dup
25
+ hash.delete :scale if hash.include? :scale and hash[:scale].nil?
26
+ "property :#{prop.name}, #{prop.class.name}, #{hash}"
27
+ end.join("\n")
28
+ }
29
+ end
30
+ RUBY
31
+ end
32
+ end
33
+
34
+ DataMapper::Adapters.const_get("SqliteAdapter").__send__(:include,
35
+ DmIsReflective::SqliteAdapterPatch)
36
+ DataMapper::Model.append_extensions(DmIsReflective)
@@ -0,0 +1,10 @@
1
+ require 'yaml'
2
+ module Clearest
3
+ module Validators
4
+ module Applications
5
+ def Applications::check(_options = {})
6
+ conf = YAML.load_file('application.yml')
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,4 @@
1
+ require 'version'
2
+ module Clearest
3
+ is_versioned
4
+ end
data/lib/template.rb ADDED
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+
4
+ require 'rubygems'
5
+ require 'methodic'
6
+
7
+
8
+ class Template
9
+
10
+ attr_reader :list_token
11
+ attr_reader :template_file
12
+ attr_reader :content
13
+
14
+ def initialize(_options)
15
+ options = Methodic.get_options(_options)
16
+ options.specify_classes_of({:list_token => Array, :template_file => String})
17
+ options.specify_presences_of([:list_token,:template_file])
18
+ options.validate!
19
+
20
+
21
+ @template_file = _options[:template_file]
22
+ raise NoTemplateFile::new('No template file found') unless File::exist?(@template_file)
23
+ begin
24
+ @content = IO::readlines(@template_file).join.chomp
25
+ rescue
26
+ raise NoTemplateFile::new('No template file found')
27
+ end
28
+ token_from_template = @content.scan(/%%(\w+)%%/).flatten.uniq.map{ |item| item.downcase.to_sym}
29
+ begin
30
+ @list_token = _options[:list_token].map!{|_token| _token.downcase.to_sym }
31
+ @hash_token = Hash::new; @list_token.each{|_item| @hash_token[_item.to_s] = String::new('')}
32
+ rescue
33
+ raise InvalidTokenList::new("Token list malformation")
34
+ end
35
+ raise InvalidTokenList::new("Token list doesn't match the template") unless token_from_template.sort == @list_token.sort
36
+ @list_token.each{|_token| eval("def #{_token}=(_value); raise ArgumentError::new('Not a String') unless _value.class == String; @hash_token['#{_token}'] = _value ;end")}
37
+ @list_token.each{|_token| eval("def #{_token}; @hash_token['#{_token}'] ;end")}
38
+ end
39
+
40
+ def token(_token,_value)
41
+ raise ArgumentError::new('Not a String') unless _value.class == String
42
+ @hash_token[_token.to_s] = _value
43
+ end
44
+
45
+
46
+ def map(_hash)
47
+ _data = {}
48
+ _hash.each { |item,val|
49
+ raise ArgumentError::new("#{item} : Not a String") unless val.class == String
50
+ _data[item.to_s.downcase] = val
51
+ }
52
+ raise InvalidTokenList::new("Token list malformation") unless _data.keys.sort == @list_token.map{|_token| _token.to_s }.sort
53
+ @hash_token = _data
54
+ end
55
+
56
+ def method_missing(_name,*_args)
57
+ raise NotAToken
58
+ end
59
+
60
+
61
+ def output
62
+ _my_res = String::new('')
63
+ _my_res = @content
64
+ @list_token.each{|_token|
65
+ _my_res.gsub!(/%%#{_token.to_s.upcase}%%/,@hash_token[_token.to_s])
66
+ }
67
+ return _my_res
68
+ end
69
+
70
+ end
71
+
72
+ class InvalidTokenList < Exception; end
73
+ class NotAToken < Exception; end
74
+ class NoTemplateFile < Exception; end
@@ -0,0 +1,13 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'sinatra', '1.4.4'
4
+ gem 'json', '1.7.4'
5
+ gem 'data_mapper', '1.2.0'
6
+ gem 'dm-sqlite-adapter','1.2.0'
7
+ gem 'dm-postgres-adapter', '1.2.0'
8
+ gem "rack-rest-rspec"
9
+ gem 'rake'
10
+ gem 'rspec'
11
+ gem 'rack-test'
12
+ gem 'roodi'
13
+ gem 'code_statistics'
@@ -0,0 +1,2 @@
1
+ # Rest service with Sinatra + Datamapper
2
+ # Generated by CleaREST (c) romain GEORGES
@@ -0,0 +1,37 @@
1
+ require 'dm-migrations'
2
+ require 'rspec'
3
+ require 'rspec/core/rake_task'
4
+ require "roodi"
5
+ require "roodi_task"
6
+ require 'rake'
7
+ require "rake/clean"
8
+
9
+ ENV['DIRECTORIES_TO_CALCULATE'] = 'routes/,helpers/,models/'
10
+ require 'code_statistics'
11
+
12
+ RoodiTask.new() do | t |
13
+ t.patterns = %w(**/*.rb)
14
+ t.config = "roodi.yml"
15
+ end
16
+
17
+ CLEAN.include('*.tmp','*.old')
18
+ CLOBBER.include('*.tmp', 'build/*','#*#')
19
+
20
+ RSpec::Core::RakeTask.new(:spec)
21
+
22
+ desc "List all routes for this application"
23
+ task :routes do
24
+ puts `grep '^[get|post|put|delete].*do$' routes/*.rb | sed 's/ do$//'`
25
+ end
26
+
27
+ desc "auto migrates the database"
28
+ task :migrate do
29
+ require './main'
30
+ DataMapper.auto_migrate!
31
+ end
32
+
33
+ desc "auto upgrades the database"
34
+ task :upgrade do
35
+ require './main'
36
+ DataMapper.auto_upgrade!
37
+ end
@@ -0,0 +1,2 @@
1
+ require './main'
2
+ run Sinatra::Application
@@ -0,0 +1,9 @@
1
+ class Hash
2
+ def symbolize!
3
+ self.keys.each do |key|
4
+ self[key.to_sym] = self.delete(key)
5
+ end
6
+ return ahash
7
+ end
8
+
9
+ end
@@ -0,0 +1,3 @@
1
+ # encoding: UTF-8
2
+ Dir[File.dirname(__FILE__) + '/*.rb'].each {|file| require file unless File.basename(file) == 'init.rb'}
3
+
@@ -0,0 +1,27 @@
1
+ # encoding: UTF-8
2
+ require 'sinatra/base'
3
+
4
+ module Sinatra
5
+ module ResponseFormat
6
+ def format_response(data, format)
7
+ response = case format
8
+ when 'text/xml' then data.to_xml
9
+ when 'application/json' then data.to_json
10
+ when 'text/x-yaml' then data.to_yaml
11
+ when 'text/csv' then data.to_csv
12
+ else data.to_json
13
+ end
14
+ return response
15
+ end
16
+
17
+ def format_by_extensions(extension)
18
+ result = {
19
+ 'xml' => 'text/xml',
20
+ 'json' => 'application/json',
21
+ 'yaml' => 'text/x-yaml',
22
+ 'csv' => 'text/csv'}
23
+ return result[extension]
24
+ end
25
+ end
26
+ helpers ResponseFormat
27
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: UTF-8
2
+ require 'json'
3
+ require 'sinatra'
4
+ require 'data_mapper'
5
+ require 'dm-migrations'
6
+
7
+
8
+ config = {
9
+ :development => {
10
+ :debug => true,
11
+ :dsn => '%%DSN%%'
12
+ },
13
+ :test => {
14
+ :debug => false,
15
+ :dsn => '%%DSN%%'
16
+ },
17
+ :production => {
18
+ :debug => false,
19
+ :dsn => '%%DSN%%'
20
+ }
21
+ }
22
+
23
+ config.each do |env,params|
24
+
25
+ configure env do
26
+ DataMapper::Logger.new($stdout, :debug) if params[:debug]
27
+ DataMapper.setup( :default, params[:dsn])
28
+ end
29
+ end
30
+
31
+ require './models/init'
32
+ require './helpers/init'
33
+ require './routes/init'
34
+
35
+ DataMapper.finalize
@@ -0,0 +1,2 @@
1
+ # encoding: UTF-8
2
+ %%MODEL_DEFINITION%%
@@ -0,0 +1,2 @@
1
+ # encoding: UTF-8
2
+ Dir[File.dirname(__FILE__) + '/*.rb'].each {|file| require file unless File.basename(file) == 'init.rb'}
@@ -0,0 +1,25 @@
1
+ AssignmentInConditionalCheck:
2
+ CaseMissingElseCheck:
3
+ ClassLineCountCheck:
4
+ line_count: 300
5
+ ClassNameCheck:
6
+ pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/
7
+ #ClassVariableCheck:
8
+ CyclomaticComplexityBlockCheck:
9
+ complexity: 5
10
+ CyclomaticComplexityMethodCheck:
11
+ complexity: 10
12
+ EmptyRescueBodyCheck:
13
+ ForLoopCheck:
14
+ MethodLineCountCheck:
15
+ line_count: 30
16
+ MethodNameCheck:
17
+ pattern: !ruby/regexp /^[_a-z<>=\[|+-\/\*`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/
18
+ # MissingForeignKeyIndexCheck:
19
+ ModuleLineCountCheck:
20
+ line_count: 500
21
+ ModuleNameCheck:
22
+ pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/
23
+ ParameterNumberCheck:
24
+ parameter_count: 5
25
+
@@ -0,0 +1,33 @@
1
+ # encoding: UTF-8
2
+
3
+ get '%%PREFIX%%/%%TABLE_NAME%%.?:format?' do
4
+ format_response(%%MODEL_NAME%%.all, (params[:format])? format_by_extensions(params[:format]): request.accept.first)
5
+ end
6
+
7
+ get '%%PREFIX%%/%%TABLE_NAME%%/:id.?:format?' do
8
+ obj ||= %%MODEL_NAME%%.get(params[:id]) || halt(404)
9
+ format_response(obj, (params[:format])? format_by_extensions(params[:format]): request.accept.first)
10
+ end
11
+
12
+ post '%%PREFIX%%/%%TABLE_NAME%%.?:format?' do
13
+ is_raw = request.content_type.to_s.downcase.eql?('application/x-www-form-urlencoded')
14
+ body = (is_raw)? request.POST() : JSON.parse(request.body.read)
15
+ obj = %%MODEL_NAME%%.create(body)
16
+ status 201
17
+ format_response(obj, (params[:format])? format_by_extensions(params[:format]): request.accept.first)
18
+ end
19
+
20
+ put '%%PREFIX%%/%%TABLE_NAME%%/:id.?:format?' do
21
+ is_raw = request.content_type.to_s.downcase.eql?('application/x-www-form-urlencoded')
22
+ body = (is_raw)? request.POST() : JSON.parse(request.body.read)
23
+ obj ||= %%MODEL_NAME%%.get(params[:id]) || halt(404)
24
+ body.symbolize!
25
+ obj.attributes = obj.attributes.merge(body)
26
+ halt 500 unless obj.save!
27
+ format_response(obj, (params[:format])? format_by_extensions(params[:format]): request.accept.first)
28
+ end
29
+
30
+ delete '%%PREFIX%%/%%TABLE_NAME%%/:id' do
31
+ obj ||= %%MODEL_NAME%%.get(params[:id]) || halt(404)
32
+ halt 500 unless obj.destroy
33
+ end
@@ -0,0 +1,2 @@
1
+ # encoding: UTF-8
2
+ Dir[File.dirname(__FILE__) + '/*.rb'].each {|file| require file unless File.basename(file) == 'init.rb'}
@@ -0,0 +1,82 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+
3
+ require './main'
4
+ require './spec/spec_helper.rb'
5
+
6
+
7
+ describe '%%MODEL_NAME%% REST CRUD API' do
8
+ before :all do
9
+ base_file = '%%TEST_DB_FILENAME%%'
10
+ File::unlink(base_file) if File::exist?(base_file)
11
+ DataMapper.auto_migrate!
12
+ $service = RestCRUDService::new :object => '%%TABLE_NAME%%'
13
+ $first_obj = %%FIRST_OBJ%%
14
+ $second_obj = %%SECOND_OBJ%%
15
+ end
16
+
17
+ subject { $service }
18
+ context "POST %%PREFIX%%/%%TABLE_NAME%% : create a new record" do
19
+ it { expect(subject.create_record($first_obj)).to be_correctly_sent }
20
+ it { expect(subject).to respond_with_status 201 }
21
+ end
22
+
23
+ context "POST %%PREFIX%%/%%TABLE_NAME%% : create an other record" do
24
+ it { expect(subject.create_record($second_obj)).to be_correctly_sent }
25
+ it { expect(subject).to respond_with_status 201 }
26
+ end
27
+
28
+ context "GET %%PREFIX%%/%%TABLE_NAME%% : retrieve all records" do
29
+ before do
30
+ $first_obj[:id] = 1
31
+ $second_obj[:id] = 2
32
+ end
33
+ it { expect(subject.retrieve_all_records).to be_correctly_sent }
34
+ it { expect(subject).to respond_with_status 200 }
35
+ it { expect(subject).to respond_a_collection_of_record }
36
+ it { expect(subject).to respond_with_data [$first_obj,$second_obj] }
37
+ it { expect(subject).to respond_with_collection_size 2 }
38
+ end
39
+
40
+ context 'GET %%PREFIX%%/%%TABLE_NAME%%/2 : retrieve the second record' do
41
+ it { expect(subject.retrieve_record(2)).to be_correctly_sent }
42
+ it { expect(subject).to respond_with_status 200 }
43
+ it { expect(subject).to respond_a_record }
44
+ it { expect(subject).to respond_with_data $second_obj }
45
+ end
46
+
47
+ context 'PUT %%PREFIX%%/%%TABLE_NAME%%/2 : update the second record ' do
48
+ it { expect(subject.update_record(2,{ %%UPDATED_FIELD%% => %%NEW_VALUE%%})).to be_correctly_sent }
49
+ it { expect(subject).to respond_with_status 200 }
50
+ end
51
+
52
+ context 'DELETE %%PREFIX%%/%%TABLE_NAME%%/1 : delete the first record ' do
53
+ it { expect(subject.destroy_record(1)).to be_correctly_sent }
54
+ it { expect(subject).to respond_with_status 200 }
55
+ end
56
+
57
+ context 'GET %%PREFIX%%/%%TABLE_NAME%% : retrieve all records ' do
58
+ before do
59
+ # todo mod record for first not default string field
60
+ $second_obj[%%UPDATED_FIELD%%] = %%NEW_VALUE%%
61
+ end
62
+ it { expect(subject.retrieve_all_records).to be_correctly_sent }
63
+ it { expect(subject).to respond_with_status 200 }
64
+ it { expect(subject).to respond_a_collection_of_record }
65
+ it { expect(subject).to respond_with_collection_size 1 }
66
+ it { expect(subject).to respond_with_data $second_obj }
67
+ end
68
+
69
+ context 'GET %%PREFIX%%/%%TABLE_NAME%%/1 : failed to retrieve the first record' do
70
+ it { expect(subject.retrieve_record(1)).to be_correctly_sent }
71
+ it { expect(subject).to respond_with_status 404 }
72
+ it { expect(subject).to_not respond_with_data $first_obj }
73
+ end
74
+
75
+ context 'GET %%PREFIX%%/%%TABLE_NAME%%/2 : retrieve the second record' do
76
+ it { expect(subject.retrieve_record(2)).to be_correctly_sent }
77
+ it { expect(subject).to respond_with_status 200 }
78
+ it { expect(subject).to respond_a_record }
79
+ it { expect(subject).to respond_with_data $second_obj}
80
+ end
81
+
82
+ end
@@ -0,0 +1,3 @@
1
+ require 'rack-rest-rspec/prepare'
2
+
3
+
data/main.rb ADDED
File without changes
data/spec/table.txt ADDED
@@ -0,0 +1,11 @@
1
+ CREATE TABLE COMPANY(
2
+ ID INT PRIMARY KEY NOT NULL,
3
+ NAME TEXT NOT NULL,
4
+ AGE INT NOT NULL,
5
+ ADDRESS CHAR(50),
6
+ SALARY REAL,
7
+ DATE DATETIME
8
+ );
9
+
10
+
11
+ CREATE TABLE "othermovies" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "title" VARCHAR(50), "director" VARCHAR(50), "synopsis" TEXT, "year" VARCHAR(50), "date" DATETIME, "indice" INTEGER, "float" FLOAT);
@@ -0,0 +1,25 @@
1
+ AssignmentInConditionalCheck:
2
+ CaseMissingElseCheck:
3
+ ClassLineCountCheck:
4
+ line_count: 300
5
+ ClassNameCheck:
6
+ pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/
7
+ #ClassVariableCheck:
8
+ CyclomaticComplexityBlockCheck:
9
+ complexity: 5
10
+ CyclomaticComplexityMethodCheck:
11
+ complexity: 10
12
+ EmptyRescueBodyCheck:
13
+ ForLoopCheck:
14
+ MethodLineCountCheck:
15
+ line_count: 30
16
+ MethodNameCheck:
17
+ pattern: !ruby/regexp /^[_a-z<>=\[|+-\/\*`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/
18
+ # MissingForeignKeyIndexCheck:
19
+ ModuleLineCountCheck:
20
+ line_count: 500
21
+ ModuleNameCheck:
22
+ pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/
23
+ ParameterNumberCheck:
24
+ parameter_count: 5
25
+
metadata ADDED
@@ -0,0 +1,330 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: clearest
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1a
5
+ prerelease: 5
6
+ platform: ruby
7
+ authors:
8
+ - Romain GEORGES
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-11-02 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.5'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.5'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '2.14'
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
56
+ version: 2.14.1
57
+ type: :development
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ~>
63
+ - !ruby/object:Gem::Version
64
+ version: '2.14'
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: 2.14.1
68
+ - !ruby/object:Gem::Dependency
69
+ name: yard
70
+ requirement: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '0.8'
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: 0.8.7.2
79
+ type: :development
80
+ prerelease: false
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ~>
85
+ - !ruby/object:Gem::Version
86
+ version: '0.8'
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: 0.8.7.2
90
+ - !ruby/object:Gem::Dependency
91
+ name: rdoc
92
+ requirement: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ~>
96
+ - !ruby/object:Gem::Version
97
+ version: '4.0'
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: 4.0.1
101
+ type: :development
102
+ prerelease: false
103
+ version_requirements: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ~>
107
+ - !ruby/object:Gem::Version
108
+ version: '4.0'
109
+ - - ! '>='
110
+ - !ruby/object:Gem::Version
111
+ version: 4.0.1
112
+ - !ruby/object:Gem::Dependency
113
+ name: roodi
114
+ requirement: !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ~>
118
+ - !ruby/object:Gem::Version
119
+ version: '3.1'
120
+ - - ! '>='
121
+ - !ruby/object:Gem::Version
122
+ version: 3.1.1
123
+ type: :development
124
+ prerelease: false
125
+ version_requirements: !ruby/object:Gem::Requirement
126
+ none: false
127
+ requirements:
128
+ - - ~>
129
+ - !ruby/object:Gem::Version
130
+ version: '3.1'
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: 3.1.1
134
+ - !ruby/object:Gem::Dependency
135
+ name: code_statistics
136
+ requirement: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ~>
140
+ - !ruby/object:Gem::Version
141
+ version: '0.2'
142
+ - - ! '>='
143
+ - !ruby/object:Gem::Version
144
+ version: 0.2.13
145
+ type: :development
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ none: false
149
+ requirements:
150
+ - - ~>
151
+ - !ruby/object:Gem::Version
152
+ version: '0.2'
153
+ - - ! '>='
154
+ - !ruby/object:Gem::Version
155
+ version: 0.2.13
156
+ - !ruby/object:Gem::Dependency
157
+ name: yard-rspec
158
+ requirement: !ruby/object:Gem::Requirement
159
+ none: false
160
+ requirements:
161
+ - - ~>
162
+ - !ruby/object:Gem::Version
163
+ version: '0.1'
164
+ type: :development
165
+ prerelease: false
166
+ version_requirements: !ruby/object:Gem::Requirement
167
+ none: false
168
+ requirements:
169
+ - - ~>
170
+ - !ruby/object:Gem::Version
171
+ version: '0.1'
172
+ - !ruby/object:Gem::Dependency
173
+ name: dm-is-reflective
174
+ requirement: !ruby/object:Gem::Requirement
175
+ none: false
176
+ requirements:
177
+ - - '='
178
+ - !ruby/object:Gem::Version
179
+ version: 1.2.0
180
+ type: :runtime
181
+ prerelease: false
182
+ version_requirements: !ruby/object:Gem::Requirement
183
+ none: false
184
+ requirements:
185
+ - - '='
186
+ - !ruby/object:Gem::Version
187
+ version: 1.2.0
188
+ - !ruby/object:Gem::Dependency
189
+ name: colorize
190
+ requirement: !ruby/object:Gem::Requirement
191
+ none: false
192
+ requirements:
193
+ - - ~>
194
+ - !ruby/object:Gem::Version
195
+ version: '0.7'
196
+ - - ! '>='
197
+ - !ruby/object:Gem::Version
198
+ version: 0.7.7
199
+ type: :runtime
200
+ prerelease: false
201
+ version_requirements: !ruby/object:Gem::Requirement
202
+ none: false
203
+ requirements:
204
+ - - ~>
205
+ - !ruby/object:Gem::Version
206
+ version: '0.7'
207
+ - - ! '>='
208
+ - !ruby/object:Gem::Version
209
+ version: 0.7.7
210
+ - !ruby/object:Gem::Dependency
211
+ name: dm-sqlite-adapter
212
+ requirement: !ruby/object:Gem::Requirement
213
+ none: false
214
+ requirements:
215
+ - - ~>
216
+ - !ruby/object:Gem::Version
217
+ version: '1.2'
218
+ - - ! '>='
219
+ - !ruby/object:Gem::Version
220
+ version: 1.2.0
221
+ type: :runtime
222
+ prerelease: false
223
+ version_requirements: !ruby/object:Gem::Requirement
224
+ none: false
225
+ requirements:
226
+ - - ~>
227
+ - !ruby/object:Gem::Version
228
+ version: '1.2'
229
+ - - ! '>='
230
+ - !ruby/object:Gem::Version
231
+ version: 1.2.0
232
+ - !ruby/object:Gem::Dependency
233
+ name: version
234
+ requirement: !ruby/object:Gem::Requirement
235
+ none: false
236
+ requirements:
237
+ - - ~>
238
+ - !ruby/object:Gem::Version
239
+ version: '1.0'
240
+ - - ! '>='
241
+ - !ruby/object:Gem::Version
242
+ version: 1.0.0
243
+ type: :development
244
+ prerelease: false
245
+ version_requirements: !ruby/object:Gem::Requirement
246
+ none: false
247
+ requirements:
248
+ - - ~>
249
+ - !ruby/object:Gem::Version
250
+ version: '1.0'
251
+ - - ! '>='
252
+ - !ruby/object:Gem::Version
253
+ version: 1.0.0
254
+ description: ! 'Clearest: command line tools for REST (Rack/Sinatra) API generation'
255
+ email:
256
+ - romain@ultragreen.net
257
+ executables:
258
+ - clearest
259
+ extensions: []
260
+ extra_rdoc_files: []
261
+ files:
262
+ - .gitignore
263
+ - Gemfile
264
+ - LICENSE.txt
265
+ - README.md
266
+ - Rakefile
267
+ - VERSION
268
+ - bin/clearest
269
+ - clearest.gemspec
270
+ - lib/clearest.rb
271
+ - lib/clearest/builders/1_application.rb
272
+ - lib/clearest/builders/2_services.rb
273
+ - lib/clearest/generators/1_abstract.rb
274
+ - lib/clearest/generators/2_files_copy.rb
275
+ - lib/clearest/generators/3_fixtures.rb
276
+ - lib/clearest/generators/4_folders.rb
277
+ - lib/clearest/generators/5_templates.rb
278
+ - lib/clearest/patches/1_sqlite_adapter_patch.rb
279
+ - lib/clearest/validators/applications.rb
280
+ - lib/clearest/version.rb
281
+ - lib/template.rb
282
+ - lib/template/Gemfile
283
+ - lib/template/README.md
284
+ - lib/template/Rakefile
285
+ - lib/template/config.ru
286
+ - lib/template/helpers/hash.rb
287
+ - lib/template/helpers/init.rb
288
+ - lib/template/helpers/response_format.rb
289
+ - lib/template/main.rb.tpl
290
+ - lib/template/models/TABLE_NAME.rb.tpl
291
+ - lib/template/models/init.rb
292
+ - lib/template/roodi.yml
293
+ - lib/template/routes/TABLE_NAME.rb.tpl
294
+ - lib/template/routes/init.rb
295
+ - lib/template/spec/TABLE_NAME_spec.rb.tpl
296
+ - lib/template/spec/spec_helper.rb
297
+ - main.rb
298
+ - spec/table.txt
299
+ - ultragreen_roodi_coding_convention.yml
300
+ homepage: http://github.com/Ultragreen/clearest
301
+ licenses:
302
+ - BSD-2
303
+ post_install_message:
304
+ rdoc_options: []
305
+ require_paths:
306
+ - lib
307
+ required_ruby_version: !ruby/object:Gem::Requirement
308
+ none: false
309
+ requirements:
310
+ - - ! '>='
311
+ - !ruby/object:Gem::Version
312
+ version: '0'
313
+ segments:
314
+ - 0
315
+ hash: 1768095623213614804
316
+ required_rubygems_version: !ruby/object:Gem::Requirement
317
+ none: false
318
+ requirements:
319
+ - - ! '>'
320
+ - !ruby/object:Gem::Version
321
+ version: 1.3.1
322
+ requirements: []
323
+ rubyforge_project:
324
+ rubygems_version: 1.8.23
325
+ signing_key:
326
+ specification_version: 3
327
+ summary: ! 'Clearest: REST Api/crud generator'
328
+ test_files:
329
+ - spec/table.txt
330
+ has_rdoc: