rango 0.2.1 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -65,3 +65,13 @@
65
65
  * More inteligent environments handling, just use Rango.environment and it will detect the proper one from ENV["RACK_ENV"], RACK_ENV constant or it will defaults to development. Rango.environment=(environment) will set all these variables so everything will stay consistent.
66
66
  * MongoMapper supported in stack generator (rango create stack blog --orm=mongomapper)
67
67
  * Added Rango::StackController which is more complete than just basic Rango::Controller
68
+
69
+ = Version 0.2.2
70
+ * Sequel support in contrib/pagination
71
+
72
+ = Version 0.2.3
73
+ * Experimental support for bundler 0.9
74
+ * Controller#redirect takes a block where you get the exception, so you can set headers etc
75
+ * [FIX] Cookies are passed into the redirect request
76
+ * [FIX] All non-http exceptions which occur in controller are captured and passed to rescue_http_error as a new instance of InternalServerError, except development environment where we want to get the Rack error screen.
77
+ * [FIX] Fixed encoding problem in messages. It's actually Rack fault, but hey.
@@ -2,3 +2,4 @@ Jakub Šťastný aka Botanicus (main author)
2
2
  Grant Michael Levkoff aka grantmichaels (wrote awesome http://www.rubyinside.com/rango-ruby-web-app-framework-2858.html)
3
3
  Martin Hrdlička
4
4
  Tomasz Werbicki aka neaf
5
+ Simon Hafner aka Tass
@@ -2,10 +2,11 @@
2
2
 
3
3
  module Rango
4
4
  def self.parse(args = ARGV)
5
+ return if args.empty?
5
6
  # if you will run this script with -i argument, interactive session will begin
6
7
  Rango.interactive if ARGV.delete("-i")
7
8
  # so it can work as a runner
8
- load ARGV.shift if File.exist?(ARGV.first)
9
+ load ARGV.shift if ARGV.first && File.exist?(ARGV.first)
9
10
  end
10
11
 
11
12
  # Start IRB interactive session
@@ -2,24 +2,25 @@
2
2
 
3
3
  require "dm-aggregates"
4
4
 
5
- module DataMapper
6
- module Model
7
- # @since 0.0.2
8
- # @example Post.paginate(page, order: [:updated_at.desc])
9
- def paginate(pagenum = 1, options = Hash.new)
10
- pagenum = 1 if pagenum.nil?
11
- page = self.page(pagenum.to_i, options)
12
- Page.current = page
13
- offset = page.number(:db) * page.per_page
14
- self.all(options.merge!(offset: offset, limit: page.per_page))
15
- end
5
+ module Rango
6
+ module Pagination
7
+ module DataMapper
8
+ module Model
9
+ # @since 0.2.2
10
+ attr_accessor :page
16
11
 
17
- # @since 0.0.2
18
- def page(current, options = Hash.new)
19
- per_page = defined?(PER_PAGE) ? PER_PAGE : 10
20
- # the count options are very important
21
- # Product.count vs. Product.count(online: true)
22
- Page.new(current: current, count: self.count(options), per_page: per_page)
12
+ # @since 0.0.2
13
+ # @example Post.paginate(page, order: [:updated_at.desc])
14
+ # the count options are very important
15
+ # Product.count vs. Product.count(online: true)
16
+ def paginate(current = 1, per_page = Page.per_page, options = Hash.new)
17
+ current = current ? current.to_i : 1 # current can be nil or "1"
18
+ page = Page.new(current: current, count: self.count(options), per_page: per_page)
19
+ offset = page.number(:db) * page.per_page
20
+ dataset = self.all(options.merge!(offset: offset, limit: page.per_page))
21
+ return page, dataset
22
+ end
23
+ end
23
24
  end
24
25
  end
25
26
  end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+ module Rango
4
+ module Pagination
5
+ module Sequel
6
+ module Dataset
7
+ # @since 0.2.2
8
+ def paginate(current = 1, per_page = Page.per_page, options = Hash.new)
9
+ current = current ? current.to_i : 1 # current can be nil or "1"
10
+ page = Page.new(current: current, count: self.count, per_page: per_page)
11
+ return page, limit(per_page, (current - 1) * per_page)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ Sequel::Dataset.send(:include, Rango::Pagination::Sequel::Dataset)
@@ -4,19 +4,19 @@ module Rango
4
4
  module Pagination
5
5
  module PaginationMixin
6
6
  # @since 0.0.2
7
- def paginate(page = Page.current)
7
+ def paginate(page)
8
8
  root = File.dirname(__FILE__)
9
9
  template = File.join(root, "pagination")
10
10
  partial template, page: page
11
11
  end
12
12
 
13
13
  # @since 0.0.7
14
- def previous_page(text, page = Page.current)
14
+ def previous_page(text, page)
15
15
  link_to text, Page.route(request, page, page.previous.number)
16
16
  end
17
17
 
18
18
  # @since 0.0.7
19
- def next_page(text, page = Page.current)
19
+ def next_page(text, page)
20
20
  link_to text, Page.route(request, page, page.next.number)
21
21
  end
22
22
  end
@@ -2,21 +2,15 @@
2
2
 
3
3
  class Page
4
4
  class << self
5
- # @since 0.0.2
6
- attr_writer :current
7
-
8
5
  # @since 0.0.2
9
6
  attr_accessor :route_hook
10
7
 
11
- # @since 0.0.2
12
- def current
13
- Rango.logger.debug("Page initialized: #{@current.inspect}")
14
- return @current
8
+ # @since 0.2.2
9
+ attr_writer :per_page
10
+ def per_page
11
+ @per_page ||= 10
15
12
  end
16
13
 
17
- # Page.current = page # implicitly in datamapper.rb
18
- # paginate Page.current
19
-
20
14
  # register_route_hook do ... end
21
15
  # OR
22
16
  # register_route_hook method(:foo)
@@ -6,6 +6,7 @@ require "forwardable"
6
6
  require "rango/router"
7
7
  require "rango/exceptions"
8
8
  require "rango/rack/request"
9
+ require "rango/environments"
9
10
 
10
11
  module Rango
11
12
  class Controller
@@ -53,8 +54,19 @@ module Rango
53
54
  self.run_action
54
55
  #self.response.finish # do we need this?
55
56
  [response.status, response.headers, [response.body]] # this way we got real body rather than response object
57
+ rescue Redirection => redirection
58
+ redirection.to_response
56
59
  rescue HttpError => exception
57
60
  self.rescue_http_error(exception)
61
+ rescue Exception => exception # so we can be sure that all the exceptions which occures in controller can be captured by rescue_http_error method
62
+ if Rango.development?
63
+ raise exception
64
+ else
65
+ message = "#{exception.class}: #{exception.message}"
66
+ server_error = InternalServerError.new(message)
67
+ server_error.backtrace = caller
68
+ self.rescue_http_error(server_error)
69
+ end
58
70
  end
59
71
 
60
72
  # @since 0.0.1
@@ -66,13 +78,25 @@ module Rango
66
78
  def_delegators :response, :status, :status=
67
79
  def_delegators :response, :headers, :headers=
68
80
 
81
+ # absolute_uri "http://google.com" => "http://google.com"
82
+ # absolute_uri "/products" => "http://localhost:4000/products"
83
+ def absolute_uri(path)
84
+ if path.match(/^https?:\/{2}/)
85
+ path
86
+ else
87
+ (request.base_url.chomp("/") + path).chomp("/")
88
+ end
89
+ end
90
+
69
91
  # @since 0.0.2
70
92
  # @version 0.2.1
71
93
  # @return [String] Escaped URL (which is RFC recommendation)
72
- def redirect(location, status = 301)
94
+ def redirect(location, status = 301, &block)
73
95
  if (300..399).include?(status)
74
- exception = Redirection.new(location)
96
+ exception = Redirection.new(absolute_uri(location))
75
97
  exception.status = status
98
+ exception.headers["Set-Cookie"] = response["Set-Cookie"] # otherwise it don't save cookies
99
+ block.call(exception) unless block.nil?
76
100
  raise exception
77
101
  else
78
102
  raise ArgumentError, "Status has to be between 300 and 399"
@@ -24,6 +24,12 @@ module Rango
24
24
  @message = message
25
25
  end
26
26
 
27
+ # backtrace isn't writable, but we want to set it time to time, see Controller.call
28
+ attr_writer :backtrace
29
+ def backtrace
30
+ super || @backtrace
31
+ end
32
+
27
33
  def self.message
28
34
  @message ||= begin
29
35
  self.superclass.message
@@ -107,6 +113,16 @@ module Rango
107
113
  self.name ||= "Redirection"
108
114
  alias_method :location, :message
109
115
 
116
+ def body
117
+ %{Please follow <a href="#{URI.escape(self.location)}">#{self.location}</a>}
118
+ end
119
+
120
+ def to_response
121
+ super.tap do |response|
122
+ response[2] = [self.body]
123
+ end
124
+ end
125
+
110
126
  # use raise MovedPermanently, "http://example.com"
111
127
  # Yes, you can use just redirect method from the controller, but
112
128
  # this will work even in filters or in environments without controllers
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+
3
+ require "net/smtp"
4
+
5
+ # Mailer.new("noreply@rangoproject.org", "RangoProject.org")
6
+ # self.to = "tony@example.com"
7
+ # self.subject = "Just hey"
8
+ # self.body = "Hey Tony, what's up?"
9
+ # end
10
+ module Rango
11
+ class Mailer
12
+ @@config = {smtp: ["localhost", 25]}
13
+
14
+ def self.mail(options = Hash.new)
15
+ self.new(options[:from]).tap do |mailer|
16
+ mailer.body = options[:body]
17
+ end
18
+ end
19
+
20
+ attr_accessor :to, :to_alias
21
+ attr_accessor :from, :from_alias
22
+ attr_accessor :body, :subject
23
+
24
+ def initialize(from, from_alias = from, &block)
25
+ @from, @from_alias = from, from_alias
26
+ unless block.nil?
27
+ block.instance_eval(&block)
28
+ block.send
29
+ end
30
+ end
31
+
32
+ def raw
33
+ <<-EOF
34
+ From: #{from_alias} <#{from}>
35
+ To: #{to_alias} <#{to}>
36
+ Subject: #{subject}
37
+
38
+ #{body}
39
+ EOF
40
+ end
41
+
42
+ def send(to)
43
+ Net::SMTP.start(*@@config[:smtp]) do |smtp|
44
+ smtp.send_message(self.raw, @from, to)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -19,6 +19,23 @@ module Rango
19
19
  def context
20
20
  super.merge!(message: self.message)
21
21
  end
22
+
23
+ def redirect(uri, status = 301, options = Hash.new, &block)
24
+ status, options = 301, status if status.is_a?(Hash)
25
+ if options.respond_to?(:inject)
26
+ # redirect "/post", error: "Try again"
27
+ # ?msg[error]="Try again"
28
+ uri = options.inject(uri) do |uri, pair|
29
+ type, message = pair
30
+ uri + "?msg[#{type}]=#{message}"
31
+ end
32
+ else
33
+ # redirect "/post", "Try again"
34
+ # ?msg="Try again"
35
+ uri.concat("?msg=#{options}")
36
+ end
37
+ super(uri, status)
38
+ end
22
39
  end
23
40
  end
24
41
  else
@@ -27,29 +44,12 @@ module Rango
27
44
  end
28
45
 
29
46
  def message
30
- @message ||= (request.GET[:msg] || Hash.new)
31
- end
32
-
33
- # @since 0.0.2
34
- def redirect(url, options = Hash.new)
35
- url = [self.request.base_url.chomp("/"), url].join("/").chomp("/") unless url.match(/^http/)
36
-
37
- if options.respond_to?(:inject)
38
- # redirect "/post", error: "Try again"
39
- # ?msg[error]="Try again"
40
- url = options.inject(url) do |url, pair|
41
- type, message = pair
42
- url + "?msg[#{type}]=#{message}"
47
+ @message ||= begin
48
+ messages = request.GET[:msg] || Hash.new
49
+ messages.inject(Hash.new) do |result, pair|
50
+ result.merge(pair[0] => pair[1].force_encoding(Encoding.default_external))
43
51
  end
44
- else
45
- # redirect "/post", "Try again"
46
- # ?msg="Try again"
47
- url.concat("?msg=#{options}")
48
52
  end
49
-
50
- self.status = 302
51
- self.headers["Location"] = URI.escape(url)
52
- return String.new
53
53
  end
54
54
  end
55
55
  end
@@ -0,0 +1,15 @@
1
+ require "crudtree/generator"
2
+ require_relative "usher"
3
+
4
+ module Rango
5
+ module UrlHelper
6
+ # resource(@post, :edit), resource(:posts)
7
+ def resource(*args)
8
+ crudtree_generator.generate(*args)
9
+ end
10
+
11
+ def crudtree_generator
12
+ @@generator ||= CRUDtree::Generator.new(Rango::Router.app.master)
13
+ end
14
+ end
15
+ end
@@ -4,10 +4,13 @@ require "rango"
4
4
  require "rango/templates/template"
5
5
 
6
6
  module Rango
7
+ SubtemplateNotFound = Class.new(StandardError)
8
+
7
9
  module TemplateHelpers
8
10
  def self.extended(scope)
9
11
  class << scope
10
12
  attr_accessor :template
13
+ attr_accessor :context
11
14
  # @example Capture being used in a .html.erb page:
12
15
  # <% @foo = capture do %>
13
16
  # <p>Some Foo content!</p>
@@ -48,7 +51,7 @@ module Rango
48
51
  end
49
52
 
50
53
  def self.included(scope_class)
51
- scope_class.class_eval { attr_accessor :template}
54
+ scope_class.class_eval { attr_accessor :template }
52
55
  end
53
56
 
54
57
  # post/show.html: it's block is the block we like to see in output
@@ -77,7 +80,7 @@ module Rango
77
80
  raise ArgumentError, "Block has to have a name!" if name.nil?
78
81
  raise ArgumentError, "You have to provide value or block, not both of them!" if value && block
79
82
  value = self.template.scope.capture(&block) if value.nil? && block
80
- self.template.blocks[name] = "#{self.template.blocks[name]}\n#{value}" if value
83
+ self.template.blocks[name] = value if value
81
84
  return self.template.blocks[name]
82
85
  end
83
86
 
@@ -88,12 +91,14 @@ module Rango
88
91
  # render "base.html"
89
92
  # render "./base.html"
90
93
  # render "../base.html"
91
- def render(template, context = Hash.new)
92
- normalize_template_path(template)
94
+ def render(path, context = Hash.new)
95
+ full_path = normalize_template_path(path)
93
96
  original_template = self.template
94
- template = Rango::Template.new(template, self) # self is scope
97
+ template = Rango::Template.new(full_path, self) # self is scope
95
98
  self.template = original_template
96
99
  return template.render(context)
100
+ rescue Exceptions::TemplateNotFound # FIXME: this doesn't work
101
+ raise SubtemplateNotFound, "Template #{path} doesn't exist in #{full_path}"
97
102
  end
98
103
 
99
104
  # partial "products/list"
@@ -59,7 +59,7 @@ module Rango
59
59
  def render(context = Hash.new)
60
60
  raise Exceptions::TemplateNotFound.new("Template #{self.path} wasn't found in these template_paths: #{self.class.template_paths.inspect}") if self.fullpath.nil?
61
61
  Rango.logger.info("Rendering template #{self.path} with context keys #{context.keys.inspect}")
62
- self.context = context
62
+ self.scope.context = self.context = context # so we can access context in the scope object as well
63
63
  value = self.template.render(self.scope, context)
64
64
  Rango.logger.debug("Available blocks: #{self.blocks.keys.inspect}")
65
65
  if self.supertemplate
@@ -5,7 +5,7 @@ require "base64"
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "rango"
8
- s.version = "0.2.1"
8
+ s.version = "0.2.3"
9
9
  s.authors = ["Jakub Šťastný aka Botanicus"]
10
10
  s.homepage = "http://github.com/botanicus/rango"
11
11
  s.summary = "Rango is ultralightweight, ultracustomizable, ultracool web framework deeply inspired by Django."
@@ -27,7 +27,7 @@ Gem::Specification.new do |s|
27
27
  # Ruby version to 1.9, even if it actually is 1.9.1
28
28
  s.required_ruby_version = ::Gem::Requirement.new("~> 1.9")
29
29
 
30
- # === Dependencies ===
30
+ # Dependencies
31
31
  # RubyGems has runtime dependencies (add_dependency) and
32
32
  # development dependencies (add_development_dependency)
33
33
  # Rango isn't a monolithic framework, so you might want
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  SimpleTemplater.scope(:rango) do
2
4
  root = File.join(File.dirname(__FILE__))
3
5
  Dir["#{root}/stubs/*"].each do |path|
@@ -29,7 +29,7 @@ Rango.boot
29
29
 
30
30
  $LOAD_PATH.unshift(File.expand_path("../lib", __FILE__))
31
31
 
32
- require "<%= name %>"
32
+ require "<%= @name %>"
33
33
 
34
34
  # Available arguments:
35
35
  # ./init.rb -i
@@ -1,7 +1,14 @@
1
1
  #!/usr/bin/env nake
2
2
  # encoding: utf-8
3
3
 
4
- require_relative "gems/environment.rb"
4
+ begin
5
+ require File.expand_path("../.bundle/environment", __FILE__)
6
+ rescue LoadError
7
+ require "bundler"
8
+ Bundler.setup
9
+ end
10
+
11
+ $LOAD_PATH.unshift(File.expand_path("../lib", __FILE__))
5
12
 
6
13
  # Boot environment
7
14
  # This task isn't useful as is, but a lot of Rango tasks expect this
@@ -17,5 +17,5 @@ hook do |generator, context|
17
17
  context[:template_engine] = "haml" unless context[:template_engine]
18
18
  context[:git_deployer] = true unless context.has_key?(:git_deployer)
19
19
  context[:code_cleaner] = true unless context.has_key?(:git_deployer)
20
- context[:email_hash] = Base64.encode64(context[:email])
20
+ context[:email_hash] = Base64.encode64(context[:email]) if context[:email]
21
21
  end
data/tasks.rb CHANGED
@@ -1,13 +1,14 @@
1
- #!./script/nake
1
+ #!/usr/bin/env nake
2
2
  # encoding: utf-8
3
3
 
4
4
  begin
5
- require_relative "gems/environment.rb"
5
+ require File.expand_path("../.bundle/environment", __FILE__)
6
6
  rescue LoadError
7
- abort "You have to install bundler and run gem bundle first!"
7
+ require "bundler"
8
+ Bundler.setup
8
9
  end
9
10
 
10
- ENV["PATH"] = "script:#{ENV["PATH"]}"
11
+ $LOAD_PATH.unshift(File.expand_path("../lib", __FILE__))
11
12
 
12
13
  require "nake/tasks/gem"
13
14
  require "nake/tasks/spec"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rango
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - "Jakub \xC5\xA0\xC5\xA5astn\xC3\xBD aka Botanicus"
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain:
11
- date: 2010-02-09 00:00:00 +00:00
11
+ date: 2010-03-05 00:00:00 +00:00
12
12
  default_executable: rango
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
@@ -253,6 +253,7 @@ files:
253
253
  - lib/rango/contrib/pagination/TODO
254
254
  - lib/rango/contrib/pagination/_pagination.html.haml
255
255
  - lib/rango/contrib/pagination/adapters/datamapper.rb
256
+ - lib/rango/contrib/pagination/adapters/sequel.rb
256
257
  - lib/rango/contrib/pagination/helpers.rb
257
258
  - lib/rango/contrib/pagination/page.rb
258
259
  - lib/rango/contrib/pagination/strategies.rb
@@ -270,6 +271,7 @@ files:
270
271
  - lib/rango/helpers/assets.rb
271
272
  - lib/rango/helpers/general.rb
272
273
  - lib/rango/helpers/syntax.rb
274
+ - lib/rango/mailer.rb
273
275
  - lib/rango/mini.rb
274
276
  - lib/rango/mini_render.rb
275
277
  - lib/rango/mixins/action_args.rb
@@ -292,6 +294,7 @@ files:
292
294
  - lib/rango/rack/middlewares/static.rb
293
295
  - lib/rango/rack/request.rb
294
296
  - lib/rango/router.rb
297
+ - lib/rango/router/adapters/crudtree.rb
295
298
  - lib/rango/router/adapters/rack_mount.rb
296
299
  - lib/rango/router/adapters/rack_router.rb
297
300
  - lib/rango/router/adapters/urlmap.rb
@@ -454,9 +457,11 @@ has_rdoc: true
454
457
  homepage: http://github.com/botanicus/rango
455
458
  licenses: []
456
459
 
457
- post_install_message: "[\e[32mVersion 0.2.1\e[0m] More inteligent environments handling, just use Rango.environment and it will detect the proper one from ENV[\"RACK_ENV\"], RACK_ENV constant or it will defaults to development. Rango.environment=(environment) will set all these variables so everything will stay consistent.\n\
458
- [\e[32mVersion 0.2.1\e[0m] MongoMapper supported in stack generator (rango create stack blog --orm=mongomapper)\n\
459
- [\e[32mVersion 0.2.1\e[0m] Added Rango::StackController which is more complete than just basic Rango::Controller\n"
460
+ post_install_message: "[\e[32mVersion 0.2.3\e[0m] Experimental support for bundler 0.9\n\
461
+ [\e[32mVersion 0.2.3\e[0m] Controller#redirect takes a block where you get the exception, so you can set headers etc\n\
462
+ [\e[32mVersion 0.2.3\e[0m] [FIX] Cookies are passed into the redirect request\n\
463
+ [\e[32mVersion 0.2.3\e[0m] [FIX] All non-http exceptions which occur in controller are captured and passed to rescue_http_error as a new instance of InternalServerError, except development environment where we want to get the Rack error screen.\n\
464
+ [\e[32mVersion 0.2.3\e[0m] [FIX] Fixed encoding problem in messages. It's actually Rack fault, but hey.\n"
460
465
  rdoc_options: []
461
466
 
462
467
  require_paths: