rango 0.2.1 → 0.2.3

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/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: