mini-train 0.1.0

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/README.md ADDED
@@ -0,0 +1,108 @@
1
+ >There's a lady who sures
2
+ >all that glitters is gold
3
+ >and she's buying a stairway to heaven -- Led Zeppelin
4
+
5
+ mini-train
6
+ =====
7
+
8
+ mini-train is an attempt to fork from a rack project(rack-golem) into
9
+ a very simple web mini-framework (thats the name mini-train)
10
+
11
+ Install with: (there are no gems yet, so don't try to install right now)
12
+
13
+ sudo gem install mini-train
14
+
15
+ You can use config.ru as a start up file as for any rack app based
16
+
17
+ require 'models' # Loads your models and all ORM stuff
18
+ require 'app' # This is the main file
19
+ require 'json' # Some help from other gems
20
+ use Rack::ContentLength
21
+ use Rack::Session::Cookies
22
+ run App
23
+
24
+ Now save this into app.rb
25
+
26
+ require 'minitrain'
27
+
28
+ class App
29
+ include Minitrain # Still no classes to inherit but don't worry just await!
30
+
31
+ before do
32
+ # Here you can do many things
33
+ # In order to help you here are some variables you can read and override:
34
+ # @r => the Rack::Request object
35
+ # @res => the Rack::Response object
36
+ # @action => Name of the public method that will handle the request
37
+ # @action_arguments => Arguments for the action (really?)
38
+ end
39
+
40
+ helpers do
41
+ #altough you can write many things in before block
42
+ #you always need helpers for your main app
43
+ def help(with)
44
+ "Helping the World!"
45
+ end
46
+ end
47
+
48
+ def index(*args)
49
+ # When no public method is found
50
+ # Of course you don't have to declare one and it is gonna use Controller#not_found instead
51
+ # Still can have arguments
52
+ @articles = Post.all
53
+ erb :index
54
+ end
55
+
56
+ def post(id=nil)
57
+ @post = Post[id]
58
+ if @post.nil?
59
+ not_found
60
+ else
61
+ erb :post
62
+ end
63
+ end
64
+
65
+ def best_restaurants_json
66
+ # mini-train replaces dots and dashes with underscores
67
+ # So you can trigger this handler by visiting /best-restaurant.json
68
+ json_response({
69
+ 'title' => 'Best restaurants in town',
70
+ 'list' => Restaurant.full_list
71
+ })
72
+
73
+ def say(listener='me', *words)
74
+ "Hey #{listener} I don't need ERB to tell you that #{words.join(' ')}"
75
+ end
76
+
77
+ def not_found(*args)
78
+ # This one is defined by mini-train but here we decided to override it
79
+ # Like :index this method receives the arguments in order to make something with it
80
+ Email.alert('Too many people are looking for porn here') if args.includes?("porn")
81
+ super(args)
82
+ end
83
+
84
+ def error(err, *args)
85
+ # Again this one is defined by mini-train and only shows up when RACK_ENV is not `nil` or `dev` or `development`
86
+ # Default only prints "ERROR"
87
+ # Here we're going to send the error message
88
+ # One would rarely show that to the end user but this is just a demo
89
+ err.message
90
+ end
91
+
92
+ after do
93
+ Spy.analyse.send_info_to([:government, :facebook, :google, :james_bond])
94
+ #this was a joke
95
+ end
96
+
97
+ end
98
+
99
+ You need to provide the class Post in your models.
100
+ Class Spy doesn't exist so you need to create it (he he, just kidding).
101
+
102
+ WHAT mini-train DOES NOT
103
+ ===================
104
+
105
+ - Support templates other than ERB (Slim, Haml and other tilt based templates has been added)
106
+ - Session/Cookies administration (Like for many things, use a middleware instead ex: Rack::Session::Cookies)
107
+ - Prepare the coffee (Emacs does but i had never used it, just geany)
108
+ - So many things, but what is a fork for...
data/lib/minitrain.rb ADDED
@@ -0,0 +1,111 @@
1
+ require 'rack'
2
+ require 'tilt'
3
+
4
+ module Minitrain
5
+
6
+ def self.included(klass)
7
+ klass.class_eval do
8
+ extend ClassMethods
9
+ include InstanceMethods
10
+ end
11
+ end
12
+
13
+ module ClassMethods
14
+ attr_reader :before_block, :helpers_block, :after_block
15
+ def before(&block); @before_block = block; end
16
+ def helpers(&block); @helpers_block = block; end
17
+ def after(&block); @after_block = block; end
18
+ def dispatcher(&block); @dispatcher_block = block; end
19
+ def dispatcher_block
20
+ @dispatcher_block || proc{
21
+ @path_atoms = @r.path_info.split('/').find_all{|s| s!=''}
22
+ @action, *@action_arguments = @path_atoms
23
+ @action = @action.gsub(/[-.]/, '_') unless @action.nil?
24
+ unless public_methods.include?(@action)||(@action&&public_methods.include?(@action.to_sym))
25
+ if public_methods.include?('index')||public_methods.include?(:index) # For different RUBY_VERSION(s)
26
+ @action, @action_arguments = 'index', @path_atoms
27
+ else
28
+ @action, @action_arguments = 'not_found', @path_atoms
29
+ end
30
+ end
31
+ instance_eval(&self.class.before_block) unless self.class.before_block.nil?
32
+ instance_eval(&self.class.helpers_block) unless self.class.helpers_block.nil?
33
+ begin
34
+ @res.write(self.__send__(@action,*@action_arguments))
35
+ rescue ArgumentError => e
36
+ failed_method = e.backtrace[0][/`.*'$/][1..-2]
37
+ raise unless failed_method==@action
38
+ @res.write(self.__send__('not_found', @path_atoms))
39
+ end
40
+ instance_eval(&self.class.after_block) unless self.class.after_block.nil?
41
+ }
42
+ end
43
+ end
44
+
45
+ module InstanceMethods
46
+
47
+ DEV_ENV = [nil,'development','dev']
48
+
49
+ def initialize(app=nil); @app = app; end
50
+ def call(env); dup.call!(env); end
51
+
52
+ def call!(env)
53
+ catch(:response) {
54
+ @r = ::Rack::Request.new(env)
55
+ @res = ::Rack::Response.new
56
+ @session = env['rack.session'] || {}
57
+ begin
58
+ instance_eval(&self.class.dispatcher_block)
59
+ rescue => e
60
+ raise if DEV_ENV.include?(ENV['RACK_ENV'])
61
+ @res.write(self.__send__('error', e, @path_atoms))
62
+ end
63
+ @res.status==404&&!@app.nil? ? @app.call(env) : @res.finish
64
+ }
65
+ end
66
+
67
+ def not_found(*args)
68
+ @res.status = 404
69
+ @res.headers['X-Cascade']='pass'
70
+ "NOT FOUND: #{@r.path_info}"
71
+ end
72
+
73
+ def error(e, *args)
74
+ puts "\n", e.class, e.message, e.backtrace # Log the error anyway
75
+ @res.status = 500
76
+ "ERROR 500"
77
+ end
78
+
79
+ def tpl(template, extention)
80
+ key = (template.to_s + extention.gsub(/[.]/,"_")).to_sym
81
+ @@tilt_cache ||= {}
82
+ if @@tilt_cache.has_key?(key)
83
+ template_obj = @@tilt_cache[key]
84
+ else
85
+ views_location = @r.env['views'] || ::Dir.pwd+'/views/'
86
+ views_location << '/' unless views_location[-1]==?/
87
+ template_obj = Tilt.new("#{views_location}#{template}#{extention}")
88
+ @@tilt_cache.store(key,template_obj) if ENV['RACK_ENV']=='production'
89
+ end
90
+ @res['Content-Type'] = "text/html;charset=utf-8"
91
+ template_obj.render(self)
92
+ end
93
+
94
+ def erb(template)
95
+ tpl(template,'.erb')
96
+ end
97
+
98
+ def slim(template)
99
+ tpl(template,'.slim')
100
+ end
101
+
102
+ def haml(template)
103
+ tpl(template,'.haml')
104
+ end
105
+
106
+ def scss(template)
107
+ tpl(template,'.scss')
108
+ end
109
+ end
110
+
111
+ end
@@ -0,0 +1,14 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'mini-train'
3
+ s.version = "0.1.0"
4
+ s.platform = Gem::Platform::RUBY
5
+ s.summary = "An attempt to create a web mini-framework forked from an rack app (rack-golem)"
6
+ s.description = "This is a simplistic web framework"
7
+ s.files = `git ls-files`.split("\n").sort
8
+ s.require_path = './lib'
9
+ s.author = "Carlos Esquer"
10
+ s.email = "carlitos.esquer@gmail.com"
11
+ s.homepage = "http://rubymx.blogspot.com"
12
+ s.add_dependency(%q<tilt>, [">= 1.2.2"])
13
+ #s.add_development_dependency(%q<bacon>, "~> 1.1.0")
14
+ end
data/sample/app.rb ADDED
@@ -0,0 +1,104 @@
1
+ require 'minitrain'
2
+ require 'slim'
3
+ require 'yaml'
4
+ require 'json'
5
+ class App
6
+ include Minitrain # Still no classes to inherit but don't worry just await!
7
+
8
+ before do
9
+ # Here you can do many things
10
+ # In order to help you here are some variables you can read and override:
11
+ # @r => the Rack::Request object
12
+ # @res => the Rack::Response object
13
+ # @action => Name of the public method that will handle the request
14
+ # @action_arguments => Arguments for the action (really?)
15
+ end
16
+
17
+ helpers do
18
+ #altough you can write many things in before block
19
+ #we recommend you to write helpers here to give your app some structure
20
+ def simple
21
+ "<b>Siempre genera BOLD</b>"
22
+ end
23
+
24
+ def json_response(data=nil)
25
+ @res['Content-Type'] = "text/plain;charset=utf-8"
26
+ data.nil? ? "{}" : JSON.generate(data)
27
+ end
28
+
29
+ def yaml_response(data=nil)
30
+ @res['Content-Type'] = "text/plain;charset=utf-8"
31
+ data.to_yaml
32
+ end
33
+
34
+ end
35
+
36
+ def index(*args)
37
+ # When no public method is found
38
+ # Of course you don't have to declare one and it is gonna use Controller#not_found instead
39
+ # Still can have arguments
40
+ @articles = [{titulo: "hola mundo",contenido: "Este mundo material"},
41
+ {titulo: "bienvenidos", contenido: "Nunca habra paz..."},
42
+ {titulo: "el final", contenido: "como en los viejos tiempos"}
43
+ ]
44
+ simple + (slim :index)
45
+ #"<h1>Bienvenido</h1><br><h3>Hola mundo...</h3>"
46
+ end
47
+
48
+ def post(id=nil)
49
+ @post = Post[id]
50
+ if @post.nil?
51
+ not_found
52
+ else
53
+ erb :post
54
+ end
55
+ end
56
+
57
+ def best_restaurants_json
58
+ # mini-train replaces dots and dashes with underscores
59
+ # So you can trigger this handler by visiting /best-restaurants.json
60
+ json_response({
61
+ 'title' => 'Best restaurants in town',
62
+ 'list' => "{ nuevo: 'artículo', antiguo: 'años'}"
63
+ })
64
+ end
65
+
66
+ def best_restaurants_yaml
67
+ #you can generate YAML responses (next time we will use XML)
68
+ yaml_response({
69
+ 'title' => 'Best restaurants in town',
70
+ 'list' => "{ nuevo: 'artículo', antiguo: 'años'}"
71
+ })
72
+ end
73
+
74
+ def envy(*args)
75
+ @res['Content-Type'] = "text/html;charset=utf-8"
76
+ my_res = ""
77
+ my_res += "<p> Método: #{@r.request_method}</p>"
78
+ my_res += "<p> Metodo: #{@r.query_string}</p>"
79
+ end
80
+
81
+ def say(listener='me', *words)
82
+ "Hey #{listener} I don't need ERB to tell you that #{words.join(' ')}"
83
+ end
84
+
85
+ def not_found(*args)
86
+ # This one is defined by mini-train but here we decided to override it
87
+ # Like :index this method receives the arguments in order to make something with it
88
+ #Email.alert('Too many people are looking for porn here') if args.includes?("porn")
89
+ super(args)
90
+ end
91
+
92
+ def error(err, *args)
93
+ # Again this one is defined by mini-train and only shows up when RACK_ENV is not `nil` or `dev` or `development`
94
+ # Default only prints "ERROR"
95
+ # Here we're going to send the error message
96
+ # One would rarely show that to the end user but this is just a demo
97
+ err.message
98
+ end
99
+
100
+ after do
101
+ #Spy.analyse.send_info_to([:government, :facebook, :google, :james_bond])
102
+ end
103
+ end
104
+
data/sample/config.ru ADDED
@@ -0,0 +1,6 @@
1
+ #require 'models' # Loads your models and all ORM stuff
2
+ require './app' # This is the main file
3
+ # Some help from other gems
4
+ use Rack::ContentLength
5
+ #use Rack::Session::Cookies
6
+ run App.new
@@ -0,0 +1,8 @@
1
+ <head>
2
+ <link rel='stylesheet' type='text/css' href='http://fonts.googleapis.com/css?family=Play:400,700'/>
3
+ <link rel="stylesheet" type="text/css" href="/css/layout.css"/>
4
+ </head>
5
+ <h1>Algunos artículos (.erb)...<h1>
6
+ <% @articles.each do |art| %>
7
+ <h2> <%= art[:titulo] %> </h2>
8
+ <% end %>
@@ -0,0 +1,9 @@
1
+ %head
2
+ %link{:rel=>'stylesheet', :type=>'text/css', :href=>'http://fonts.googleapis.com/css?family=Play:400,700'}
3
+ %link{:rel=>"stylesheet", :type=>"text/css", :href=>"/css/layout.css"}
4
+ %body
5
+ %h1 Algunos artículos (.haml)...
6
+ %br
7
+ - @articles.each do |art|
8
+ %h2= art[:titulo]
9
+ %br
@@ -0,0 +1,11 @@
1
+ head
2
+ link rel='stylesheet' type='text/css' href='http://fonts.googleapis.com/css?family=Play:400,700'
3
+ link rel="stylesheet" type="text/css" href="/css/layout.css"
4
+ body
5
+ h1 Algunos artículos (.slim)...
6
+ br
7
+ - @articles.each do |art|
8
+ h2= art[:titulo]
9
+ br
10
+ .content=art[:contenido]
11
+
@@ -0,0 +1,185 @@
1
+ require 'minitest/autorun'
2
+ require 'rack/lobster'
3
+ require 'minitrain'
4
+
5
+ # =========
6
+ # = Basic =
7
+ # =========
8
+
9
+ class Basic
10
+ include Minitrain
11
+ def no_arg; 'nothing'; end
12
+ def with_args(a,b); '%s+%s' % [a,b]; end
13
+ def splat_arg(*a); a.join('+'); end
14
+ def test_throw
15
+ throw :response, [200,{'Content-Type'=>'text/html'},['Growl']]
16
+ 'Grrr'
17
+ end
18
+ def best_restaurants_rss; '<xml>test</xml>'; end
19
+ private
20
+ def no_way; 'This is private'; end
21
+ end
22
+ BasicR = ::Rack::MockRequest.new(::Rack::Lint.new(Basic.new))
23
+ BasicLobsterR = ::Rack::MockRequest.new(::Rack::Lint.new(Basic.new(::Rack::Lobster.new)))
24
+
25
+ # ==========
26
+ # = Filter =
27
+ # ==========
28
+
29
+ class Filter
30
+ include Minitrain
31
+ before{@res.write @action=='not_found' ? @action_arguments.join('+') : 'before+'}
32
+ after{@res.write '+after'}
33
+ def wrapped; 'wrapped'; end
34
+ end
35
+ FilterR = ::Rack::MockRequest.new(::Rack::Lint.new(Filter.new))
36
+
37
+ # ===========
38
+ # = Indexed =
39
+ # ===========
40
+
41
+ class Indexed
42
+ include Minitrain
43
+ before{ @res.write("action=#{@action} args=#{@action_arguments.join(',')} ") if @r['switch']=='true' }
44
+ def index(*a); a.join('+'); end
45
+ def exist(*a); a.join('+'); end
46
+ end
47
+ IndexedR = ::Rack::MockRequest.new(::Rack::Lint.new(Indexed.new))
48
+
49
+ # ==================
50
+ # = Simply indexed =
51
+ # ==================
52
+
53
+ class SimplyIndexed
54
+ include Minitrain
55
+ def index; 'index'; end
56
+ def will_fail; please_fail; end
57
+ private
58
+ def please_fail(num); 'ArgumentError baby'; end
59
+ end
60
+ SimplyIndexedR = ::Rack::MockRequest.new(::Rack::Lint.new(SimplyIndexed.new))
61
+ SimplyIndexedUsedR = ::Rack::MockRequest.new(::Rack::Lint.new(SimplyIndexed.new(lambda{|env| [200,{},"#{3+nil}"]})))
62
+
63
+ # =============
64
+ # = Sessioned =
65
+ # =============
66
+
67
+ class Sessioned
68
+ include Minitrain
69
+ def set_val(val); @session[:val] = val; end
70
+ def get_val; @session[:val]; end
71
+ end
72
+ SessionedR = ::Rack::MockRequest.new(::Rack::Session::Cookie.new(::Rack::Lint.new(Sessioned.new)))
73
+
74
+ # =============
75
+ # = Helpers =
76
+ # =============
77
+
78
+ class UsingHelpers
79
+ include Minitrain
80
+ helpers { def hola(w); "Hola " + w; end }
81
+ def index; hola("mundo"); end
82
+ end
83
+ UsinghelpersR = ::Rack::MockRequest.new(::Rack::Lint.new(UsingHelpers.new))
84
+
85
+ # =========
86
+ # = Specs =
87
+ # =========
88
+
89
+ describe "Minitrain" do
90
+
91
+ it "Should dispatch on a method with no arguments" do
92
+ assert_equal "nothing",BasicR.get('/no_arg').body
93
+ end
94
+
95
+ it "Should dispatch on a method with arguments" do
96
+ assert_equal "a+b",BasicR.get('/with_args/a/b').body
97
+ end
98
+
99
+ it "Should dispatch on a method with splat argument" do
100
+ assert_equal "a+b+c+d",BasicR.get('/splat_arg/a/b/c/d').body
101
+ end
102
+
103
+ it "Should not dispatch if the method is private or does not exist" do
104
+ r = BasicR.get('/no_way')
105
+ assert_equal 404,r.status
106
+ assert_equal "NOT FOUND: /no_way",r.body
107
+ r = BasicR.get('/no')
108
+ assert_equal 404,r.status
109
+ assert_equal "NOT FOUND: /no",r.body
110
+ end
111
+
112
+ it "Should dispatch to appropriate underscored action when name contains '-' or '.'" do
113
+ assert_equal "<xml>test</xml>",BasicR.get('/best-restaurants.rss').body
114
+ end
115
+
116
+ it "Should only apply '-' and '.' substitution on action names" do
117
+ assert_equal 'best-restaurants.rss',IndexedR.get('/best-restaurants.rss').body
118
+ end
119
+
120
+ it "Should follow the rack stack if response is 404 and there are middlewares below" do
121
+ r = BasicLobsterR.get("/no_way")
122
+ assert_equal 200,r.status
123
+ end
124
+
125
+ it "Should provide filters" do
126
+ assert_equal "before+wrapped+after",FilterR.get('/wrapped').body
127
+ end
128
+
129
+ it "Should provide arguments in filter when page is not_found" do
130
+ assert_equal "a+b+c+dNOT FOUND: /a/b/c/d+after",FilterR.get('/a/b/c/d').body
131
+ end
132
+
133
+ it "Should send everything to :index if it exists and there is no matching method for first arg" do
134
+ assert_equal 'a+b+c+d',IndexedR.get('/exist/a/b/c/d').body
135
+ assert_equal 'a+b+c+d',IndexedR.get('/a/b/c/d').body
136
+ assert_equal '',IndexedR.get('/').body
137
+ end
138
+
139
+ it "Should send not_found if there is an argument error on handlers" do
140
+ assert_equal 200,SimplyIndexedR.get('/').status
141
+ assert_equal 404,SimplyIndexedR.get('/unknown').status
142
+ assert_equal 404,SimplyIndexedR.get('/will_fail/useless').status
143
+ assert_raises(ArgumentError) { SimplyIndexedR.get('/will_fail') }
144
+ end
145
+
146
+ it "Should handle errors without raising an exception unless in dev mode" do
147
+ assert_raises(ArgumentError) { SimplyIndexedR.get('/will_fail') }
148
+ ENV['RACK_ENV'] = 'development'
149
+ assert_raises(ArgumentError) { SimplyIndexedR.get('/will_fail') }
150
+ ENV['RACK_ENV'] = 'production'
151
+ @old_stdout = $stdout
152
+ $stdout = StringIO.new
153
+ res = SimplyIndexedR.get('/will_fail')
154
+ logged = $stdout.dup
155
+ $stdout = @old_stdout
156
+ assert_equal res.status,500
157
+ assert_match logged.string, /ArgumentError/
158
+ ENV['RACK_ENV'] = nil
159
+ end
160
+
161
+ it "Should not use the error handler if the error occurs further down the rack stack" do
162
+ ENV['RACK_ENV'] = 'production'
163
+ assert_raises(TypeError) { SimplyIndexedUsedR.get('/not_found') }
164
+ ENV['RACK_ENV'] = nil
165
+ end
166
+
167
+ it "Should set dispatch-specific variables correctly when defaulting to :index" do
168
+ assert_equal "action=index args=a,b,c,d a+b+c+d",IndexedR.get('/a/b/c/d?switch=true').body
169
+ end
170
+
171
+ it "Should have a shortcut for session hash" do
172
+ res = SessionedR.get('/set_val/ichigo')
173
+ res_2 = SessionedR.get('/get_val', 'HTTP_COOKIE'=>res["Set-Cookie"])
174
+ assert_equal 'ichigo',res_2.body
175
+ end
176
+
177
+ it "Should catch :response if needed" do
178
+ assert_equal BasicR.get('/test_throw').body,'Growl'
179
+ end
180
+
181
+ it "Should response with the helpers content" do
182
+ assert_equal UsinghelpersR.get('/').body,'Hola mundo'
183
+ end
184
+
185
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mini-train
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Carlos Esquer
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-23 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: tilt
16
+ requirement: &71720560 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.2.2
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *71720560
25
+ description: This is a simplistic web framework
26
+ email: carlitos.esquer@gmail.com
27
+ executables: []
28
+ extensions: []
29
+ extra_rdoc_files: []
30
+ files:
31
+ - README.md
32
+ - lib/minitrain.rb
33
+ - mini-train-0.1.0.gem
34
+ - mini-train.gemspec
35
+ - sample/app.rb
36
+ - sample/config.ru
37
+ - sample/views/index.erb
38
+ - sample/views/index.haml
39
+ - sample/views/index.slim
40
+ - test/minitrain-test.rb
41
+ homepage: http://rubymx.blogspot.com
42
+ licenses: []
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - ./lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project:
61
+ rubygems_version: 1.8.11
62
+ signing_key:
63
+ specification_version: 3
64
+ summary: An attempt to create a web mini-framework forked from an rack app (rack-golem)
65
+ test_files: []