rasti-web 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7421139482ecc1b7964db860e548a5805eb4fc66
4
+ data.tar.gz: 573b00b2c7da729862711044b6a62cad8bc3bad0
5
+ SHA512:
6
+ metadata.gz: 493938ffdf31f07406316a026096a00bf7ba3554c50dfbe4ec820d3676a663520343a40fd10521d3d968adda48f78b1a770375e8b5bb92098891a439938b0116
7
+ data.tar.gz: dc2e21807b7ceb4f6be33f21352f262444bf07f111c308b0c6de95876f61053b40200fe5c98fe77f168a68aa9c58ff34703a6bc82ba9058f8766bff6596f4de0
data/.coveralls.yml ADDED
@@ -0,0 +1,2 @@
1
+ service_name: travis-ci
2
+ repo_token: m9idBSHE2hpi7JYV14vDKnOsWV9xW00Yf
data/.gitignore ADDED
@@ -0,0 +1,22 @@
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
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ rasti-web
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.0
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0
5
+ - 2.1
6
+ - jruby
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rasti-web.gemspec
4
+ gemspec
5
+
6
+ gem 'coveralls', require: false
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Gabriel Naiman
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,35 @@
1
+ # Rasti::Web
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/rasti-web.png)](https://rubygems.org/gems/rasti-web)
4
+ [![Build Status](https://travis-ci.org/gabynaiman/rasti-web.png?branch=master)](https://travis-ci.org/gabynaiman/rasti-web)
5
+ [![Coverage Status](https://coveralls.io/repos/gabynaiman/rasti-web/badge.png?branch=master)](https://coveralls.io/r/gabynaiman/rasti-web?branch=master)
6
+ [![Code Climate](https://codeclimate.com/github/gabynaiman/rasti-web.png)](https://codeclimate.com/github/gabynaiman/rasti-web)
7
+ [![Dependency Status](https://gemnasium.com/gabynaiman/rasti-web.png)](https://gemnasium.com/gabynaiman/rasti-web)
8
+
9
+ Web blocks to build robust applications
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ gem 'rasti-web'
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install rasti-web
24
+
25
+ ## Usage
26
+
27
+ TODO: Write usage instructions here
28
+
29
+ ## Contributing
30
+
31
+ 1. Fork it ( https://github.com/gabynaiman/rasti-web/fork )
32
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
33
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
34
+ 4. Push to the branch (`git push origin my-new-feature`)
35
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:spec) do |t|
5
+ t.libs << 'spec'
6
+ t.pattern = 'spec/**/*_spec.rb'
7
+ t.verbose = false
8
+ end
9
+
10
+ task default: :spec
11
+
12
+ desc 'Pry console'
13
+ task :console do
14
+ require 'rasti-web'
15
+ require 'pry'
16
+ ARGV.clear
17
+ Pry.start
18
+ end
@@ -0,0 +1,52 @@
1
+ module Rasti
2
+ module Web
3
+ class Application
4
+ class << self
5
+
6
+ Router::VERBS.each do |verb|
7
+ define_method verb do |*args, &block|
8
+ router.public_send verb, *args, &block
9
+ end
10
+ end
11
+
12
+ def not_found(*args, &block)
13
+ router.not_found *args, &block
14
+ end
15
+
16
+ def use(*args, &block)
17
+ rack.use *args, &block
18
+ end
19
+
20
+ def map(path, endpoint=nil, &block)
21
+ rack.map path do
22
+ run endpoint || Endpoint.new(&block)
23
+ end
24
+ end
25
+
26
+ def call(env)
27
+ app.call env
28
+ end
29
+
30
+ private
31
+
32
+ def router
33
+ @router ||= Router.new
34
+ end
35
+
36
+ def rack
37
+ @rack ||= Rack::Builder.new
38
+ end
39
+
40
+ def app
41
+ @app ||= to_app
42
+ end
43
+
44
+ def to_app
45
+ rack.run router
46
+ rack.to_app
47
+ end
48
+
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,48 @@
1
+ module Rasti
2
+ module Web
3
+ class Controller
4
+
5
+ extend Forwardable
6
+
7
+ def_delegators :request, :params, :session
8
+ def_delegator :response, :redirect, :redirect_to
9
+
10
+ attr_reader :request, :response, :render
11
+
12
+ def initialize(request, response, render)
13
+ @request = request
14
+ @response = response
15
+ @render = render
16
+ end
17
+
18
+ def execute(action_name)
19
+ public_send action_name
20
+ rescue => ex
21
+ if respond_to? ex.class.name
22
+ public_send ex.class.name, ex
23
+ else
24
+ raise ex
25
+ end
26
+ end
27
+
28
+ class << self
29
+ def action(action_name)
30
+ raise "Undefined action '#{action_name}' in #{name}" unless instance_methods.include? action_name.to_sym
31
+
32
+ Endpoint.new do |req, res, render|
33
+ new(req, res, render).execute(action_name)
34
+ end
35
+ end
36
+
37
+ alias_method :>>, :action
38
+
39
+ def rescue_from(exception_class, &block)
40
+ define_method exception_class.name do |ex|
41
+ instance_exec ex, &block
42
+ end
43
+ end
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,18 @@
1
+ module Rasti
2
+ module Web
3
+ class Endpoint
4
+
5
+ def initialize(&block)
6
+ @block = block
7
+ end
8
+
9
+ def call(env)
10
+ request = Request.new env
11
+ response = Rack::Response.new
12
+ @block.call request, response, Render.new(request, response)
13
+ response.finish
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,82 @@
1
+ module Rasti
2
+ module Web
3
+ class Render
4
+
5
+ attr_reader :request, :response, :view_context
6
+
7
+ def initialize(request, response)
8
+ @request = request
9
+ @response = response
10
+ @view_context = ViewContext.new request, response
11
+ end
12
+
13
+ def status(status, *args)
14
+ respond_with status,
15
+ extract_headers(args),
16
+ extract_body(args)
17
+ end
18
+
19
+ def text(text, *args)
20
+ respond_with extract_status(args),
21
+ extract_headers(args).merge('Content-Type' => 'text/plain'),
22
+ text
23
+ end
24
+
25
+ def html(html, *args)
26
+ respond_with extract_status(args),
27
+ extract_headers(args).merge('Content-Type' => 'text/html'),
28
+ html
29
+ end
30
+
31
+ def json(object, *args)
32
+ respond_with extract_status(args),
33
+ extract_headers(args).merge('Content-Type' => 'application/json'),
34
+ object.is_a?(String) ? object : JSON.dump(object)
35
+ end
36
+
37
+ def js(script, *args)
38
+ respond_with extract_status(args),
39
+ extract_headers(args).merge('Content-Type' => 'application/javascript'),
40
+ script
41
+ end
42
+
43
+ def partial(template, locals={})
44
+ response['Content-Type'] = 'text/html'
45
+ response.write view_context.render(template, locals)
46
+ end
47
+
48
+ def layout(template=nil, &block)
49
+ content = block.call if block
50
+ layout = view_context.render(template || Web.default_layout) { content }
51
+
52
+ response['Content-Type'] = 'text/html'
53
+ response.write layout
54
+ end
55
+
56
+ def view(template, locals={}, layout_template=nil)
57
+ layout(layout_template) { view_context.render template, locals }
58
+ end
59
+
60
+ private
61
+
62
+ def respond_with(status, headers, body)
63
+ response.status = status if status
64
+ response.headers.merge! headers
65
+ response.write body if body
66
+ end
67
+
68
+ def extract_status(args)
69
+ args.detect { |a| a.is_a? Fixnum }
70
+ end
71
+
72
+ def extract_headers(args)
73
+ args.detect { |a| a.is_a? Hash } || {}
74
+ end
75
+
76
+ def extract_body(args)
77
+ args.detect { |a| a.is_a? String }
78
+ end
79
+
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,25 @@
1
+ module Rasti
2
+ module Web
3
+ class Request < Rack::Request
4
+
5
+ def initialize(env)
6
+ super
7
+ params.merge! JSON.parse(body_text) if json? && body_text
8
+ params.merge! env[ROUTE_PARAMS] if env[ROUTE_PARAMS]
9
+ end
10
+
11
+ def body_text
12
+ @body_text ||= begin
13
+ text = body.read
14
+ body.rewind
15
+ text
16
+ end
17
+ end
18
+
19
+ def json?
20
+ content_type == 'application/json'
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,41 @@
1
+ module Rasti
2
+ module Web
3
+ class Route
4
+
5
+ attr_reader :pattern, :endpoint, :regexp, :params
6
+
7
+ def initialize(pattern, endpoint=nil, &block)
8
+ @pattern = normalize pattern
9
+ @endpoint = endpoint || Endpoint.new(&block)
10
+ compile
11
+ end
12
+
13
+ def match?(path)
14
+ !regexp.match(normalize(path)).nil?
15
+ end
16
+
17
+ def extract_params(path)
18
+ result = regexp.match path
19
+ result ? Hash[params.zip(result.captures)] : {}
20
+ end
21
+
22
+ private
23
+
24
+ def compile
25
+ @params = []
26
+ regexp = pattern.gsub(/(:\w+)/) do |match|
27
+ @params << match[1..-1]
28
+ "([^/?#]+)"
29
+ end
30
+ @regexp = %r{^#{regexp}$}
31
+ end
32
+
33
+ def normalize(path)
34
+ return '/' if path.strip.empty? || path == '/'
35
+ return path[0..-2] if path[-1, 1] == '/'
36
+ path
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,43 @@
1
+ module Rasti
2
+ module Web
3
+ class Router
4
+
5
+ VERBS = %w(delete get head options patch post put).freeze
6
+
7
+ NOT_FOUND_PATTERN = '/404'
8
+
9
+ VERBS.each do |verb|
10
+ define_method verb do |pattern, endpoint=nil, &block|
11
+ routes[verb] << Route.new(pattern, endpoint, &block)
12
+ end
13
+ end
14
+
15
+ def not_found(endpoint=nil, &block)
16
+ @not_found_route = Route.new(NOT_FOUND_PATTERN, endpoint, &block)
17
+ end
18
+
19
+ def call(env)
20
+ route = route_for env
21
+ env[ROUTE_PARAMS] = route.extract_params env['PATH_INFO']
22
+ route.endpoint.call env
23
+ end
24
+
25
+ private
26
+
27
+ def routes
28
+ @routes ||= Hash.new { |h,k| h[k] = [] }
29
+ end
30
+
31
+ def route_for(env)
32
+ routes[env['REQUEST_METHOD'].downcase].detect { |r| r.match? env['PATH_INFO'] } || not_found_route
33
+ end
34
+
35
+ def not_found_route
36
+ @not_found_route ||= Route.new NOT_FOUND_PATTERN do |request, response, render|
37
+ render.status 404, "Not found: #{request.request_method} #{request.path_info}"
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ module Rasti
2
+ module Web
3
+ class Template
4
+
5
+ def self.render(template, context=nil, locals={}, &block)
6
+ files = Web.template_engines.map { |e| File.join Web.views_path, "#{template}.#{e}" }
7
+ template_file = files.detect { |f| File.exists? f }
8
+
9
+ raise "Missing template #{template} [#{files.join(', ')}]" unless template_file
10
+
11
+ tilt = cache.fetch(template_file) { Tilt.new template_file }
12
+ tilt.render(context, locals, &block)
13
+ end
14
+
15
+ private
16
+
17
+ def self.cache
18
+ Thread.current[:templates_cache] ||= Tilt::Cache.new
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ module Rasti
2
+ module Web
3
+ VERSION = '0.0.1'
4
+ end
5
+ end
@@ -0,0 +1,18 @@
1
+ module Rasti
2
+ module Web
3
+ class ViewContext
4
+
5
+ attr_reader :request, :response
6
+
7
+ def initialize(request, response)
8
+ @request = request
9
+ @response = response
10
+ end
11
+
12
+ def render(template, locals={}, &block)
13
+ Template.render template, self, locals, &block
14
+ end
15
+
16
+ end
17
+ end
18
+ end
data/lib/rasti/web.rb ADDED
@@ -0,0 +1,33 @@
1
+ require 'rack'
2
+ require 'tilt'
3
+ require 'json'
4
+ require 'class_config'
5
+ require 'forwardable'
6
+
7
+ require_relative 'web/route'
8
+ require_relative 'web/router'
9
+ require_relative 'web/endpoint'
10
+ require_relative 'web/application'
11
+ require_relative 'web/template'
12
+ require_relative 'web/view_context'
13
+ require_relative 'web/render'
14
+ require_relative 'web/request'
15
+ require_relative 'web/controller'
16
+ require_relative 'web/version'
17
+
18
+ module Rasti
19
+ module Web
20
+ ROUTE_PARAMS = 'rack.request.route_params'
21
+
22
+ extend ClassConfig
23
+
24
+ attr_config :views_path, File.join(Dir.pwd, 'views')
25
+ attr_config :template_engines, [:erb]
26
+ attr_config :default_layout, 'layout'
27
+ attr_config :helpers, []
28
+
29
+ after_config do |config|
30
+ config.helpers.each { |h| ViewContext.send :include, h }
31
+ end
32
+ end
33
+ end
data/lib/rasti-web.rb ADDED
@@ -0,0 +1 @@
1
+ require_relative 'rasti/web'
data/rasti-web.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rasti/web/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'rasti-web'
8
+ spec.version = Rasti::Web::VERSION
9
+ spec.authors = ['Gabriel Naiman']
10
+ spec.email = ['gabynaiman@gmail.com']
11
+ spec.summary = 'Web blocks to build robust applications'
12
+ spec.description = 'Web blocks to build robust applications'
13
+ spec.homepage = 'https://github.com/gabynaiman/rasti-web'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'rack'
22
+ spec.add_dependency 'tilt'
23
+ spec.add_dependency 'class_config', '~> 0.0.2'
24
+
25
+ spec.add_development_dependency 'bundler', '~> 1.6'
26
+ spec.add_development_dependency 'rake'
27
+ spec.add_development_dependency 'minitest', '~> 4.7'
28
+ spec.add_development_dependency 'turn', '~> 0.9'
29
+ spec.add_development_dependency 'simplecov'
30
+ spec.add_development_dependency 'pry'
31
+ spec.add_development_dependency 'rack-test'
32
+ end
@@ -0,0 +1,77 @@
1
+ require 'minitest_helper'
2
+
3
+ class TestMiddleware
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ if env['PATH_INFO'] == '/private'
10
+ [403, {}, ['Permission denied']]
11
+ else
12
+ @app.call env
13
+ end
14
+ end
15
+ end
16
+
17
+ class TestMap < Rasti::Web::Application
18
+ get '/resource/:id' do |request, response, render|
19
+ render.json id: request.params['id'].to_i
20
+ end
21
+ end
22
+
23
+ class TestApp < Rasti::Web::Application
24
+
25
+ use TestMiddleware
26
+
27
+ map '/api', TestMap
28
+
29
+ get '/' do |request, response, render|
30
+ render.html 'Page content'
31
+ end
32
+
33
+ not_found do |request, response, render|
34
+ render.status 404, 'Page not found'
35
+ end
36
+
37
+ end
38
+
39
+ describe Rasti::Web::Application do
40
+
41
+ include Rack::Test::Methods
42
+
43
+ def app
44
+ TestApp
45
+ end
46
+
47
+ it 'Defined route' do
48
+ get '/'
49
+
50
+ last_response.status.must_equal 200
51
+ last_response['Content-Type'].must_equal 'text/html'
52
+ last_response.body.must_equal 'Page content'
53
+ end
54
+
55
+ it 'Not found' do
56
+ get '/not_found'
57
+
58
+ last_response.status.must_equal 404
59
+ last_response.body.must_equal 'Page not found'
60
+ end
61
+
62
+ it 'Middleware' do
63
+ get '/private'
64
+
65
+ last_response.status.must_equal 403
66
+ last_response.body.must_equal 'Permission denied'
67
+ end
68
+
69
+ it 'Map' do
70
+ get '/api/resource/123'
71
+
72
+ last_response.status.must_equal 200
73
+ last_response['Content-Type'].must_equal 'application/json'
74
+ last_response.body.must_equal '{"id":123}'
75
+ end
76
+
77
+ end