diff --git a/.gitignore b/.gitignore index b81217d..1a07229 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .bundle -*.gem \ No newline at end of file +*.gem +.DS_Store \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..93ed21b --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,55 @@ +GEM + remote: http://rubygems.org/ + specs: + actionpack (3.1.3) + activemodel (= 3.1.3) + activesupport (= 3.1.3) + builder (~> 3.0.0) + erubis (~> 2.7.0) + i18n (~> 0.6) + rack (~> 1.3.5) + rack-cache (~> 1.1) + rack-mount (~> 0.8.2) + rack-test (~> 0.6.1) + sprockets (~> 2.0.3) + activemodel (3.1.3) + activesupport (= 3.1.3) + builder (~> 3.0.0) + i18n (~> 0.6) + activesupport (3.1.3) + multi_json (~> 1.0) + builder (3.0.0) + diff-lcs (1.1.3) + erubis (2.7.0) + hike (1.2.1) + i18n (0.6.0) + multi_json (1.0.4) + rack (1.3.6) + rack-cache (1.1) + rack (>= 0.4) + rack-mount (0.8.3) + rack (>= 1.0.0) + rack-test (0.6.1) + rack (>= 1.0) + rake (0.9.2.2) + rspec (2.8.0) + rspec-core (~> 2.8.0) + rspec-expectations (~> 2.8.0) + rspec-mocks (~> 2.8.0) + rspec-core (2.8.0) + rspec-expectations (2.8.0) + diff-lcs (~> 1.1.2) + rspec-mocks (2.8.0) + sprockets (2.0.3) + hike (~> 1.2) + rack (~> 1.0) + tilt (~> 1.1, != 1.3.0) + tilt (1.3.3) + +PLATFORMS + ruby + +DEPENDENCIES + actionpack + rake + rspec diff --git a/README.md b/README.md index 5047594..343f2dd 100644 --- a/README.md +++ b/README.md @@ -83,4 +83,4 @@ Astaire supports inline templates in the same style as Sinatra. For example: ### TODO I guess, at this point I'm just putting what I have into the wild. Next -steps will be determined by feedback that I get. \ No newline at end of file +steps will be determined by feedback that I get. diff --git a/astaire.gemspec b/astaire.gemspec index 77db954..dd0a32e 100644 --- a/astaire.gemspec +++ b/astaire.gemspec @@ -14,5 +14,5 @@ Gem::Specification.new do |s| s.files = Dir['README.md', 'LICENSE', 'lib/**/*'] s.require_path = 'lib' - s.add_dependency 'actionpack', '~> 3.0.0.beta2' -end \ No newline at end of file +# s.add_dependency 'actionpack', '> 3.0.0.beta2' +end diff --git a/lib/astaire.rb b/lib/astaire.rb index f71ce41..3750dbf 100644 --- a/lib/astaire.rb +++ b/lib/astaire.rb @@ -14,166 +14,9 @@ module Astaire # add rubinius (and hopefully other VM impls) ignore patterns ... CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS) if defined?(RUBY_IGNORE_CALLERS) - - class InlineTemplates < ActionView::PathResolver - def initialize - super - @templates = {} - end - - def add_controller(controller) - file = caller_files.first - - begin - app, data = - ::IO.read(file).gsub("\r\n", "\n").split(/^__END__$/, 2) - rescue Errno::ENOENT - app, data = nil - end - - if data - lines = app.count("\n") + 1 - template = nil - data.each_line do |line| - lines += 1 - if line =~ /^@@\s*(.*)/ - template = '' - @templates["#{controller.controller_path}/#{$1}"] = - [template, file, lines] - elsif template - template << line - end - end - end - end - - def query(path, exts, formats) - query = Regexp.escape(path) - exts.each do |ext| - query << '(' << ext.map {|e| e && Regexp.escape(".#{e}") }.join('|') << '|)' - end - - templates = [] - @templates.select { |k,v| k =~ /^#{query}$/ }.each do |path, (source, file, lines)| - handler, format = extract_handler_and_format(path, formats) - templates << ActionView::Template.new(source, path, handler, - :virtual_path => path, :format => format) - end - - templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size } - end - - # Like Kernel#caller but excluding certain magic entries and without - # line / method information; the resulting array contains filenames only. - def caller_files - caller_locations. - map { |file,line| file } - end - - def caller_locations - caller(1). - map { |line| line.split(/:(?=\d|in )/)[0,2] }. - reject { |file,line| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } } - end - end - - module DSL - extend ActiveSupport::Concern - - include AbstractController::Helpers - - included do - class_attribute :_astaire_router - self._astaire_router = ActionDispatch::Routing::RouteSet.new - - class_attribute :_astaire_helpers - self._astaire_helpers = url_helper_module - - class_attribute :_inline_resolver - self._inline_resolver = InlineTemplates.new - - _inline_resolver.add_controller(self) - - include _astaire_helpers - helper _astaire_helpers - - append_view_path _inline_resolver - end - - module ClassMethods - def call(env) - _astaire_router.call(env) - end - - def mapper - @mapper ||= ActionDispatch::Routing::Mapper.new(_astaire_router) - end - - %w(get post put delete).each do |method| - class_eval <<-R, __FILE__, __LINE__+1 - def #{method}(path, opts = {}, &blk) - map_astaire_action "#{method}", path, opts, blk - end - R - end - - def inherited(klass) - super - _inline_resolver.add_controller(klass) - end - - private - - def map_astaire_action(method, path, opts, blk) - action_name = "[#{method}] #{path}" - define_method action_name, &blk - opts.merge! :via => method, :to => action(action_name) - - mapper.match(path, opts) - make_url_helper(opts[:as]) if opts[:as] - end - - def url_helper_module - Module.new do - def _astaire_url_opts_from_args(name, route, args, only_path) - opts = args.extract_options! - - if args.any? - opts[:_positional_args] = args - opts[:_positional_keys] = route.segment_keys - end - - opts = url_options.merge(opts) - opts.merge!(:use_route => name, :only_path => only_path) - - if path_segments = opts[:_path_segments] - path_segments.delete(:controller) - path_segments.delete(:action) - end - - opts - end - end - end - - def make_url_helper(name) - name = name.to_sym - router = _astaire_router - - _astaire_helpers.module_eval do - define_method "#{name}_path" do |*args| - route = router.named_routes[name] - opts = _astaire_url_opts_from_args(name, route, args, true) - router.url_for(opts) - end - - define_method "#{name}_url" do |*args| - route = router.named_routes[name] - opts = _astaire_url_opts_from_args(name, route, args, false) - router.url_for(opts) - end - end - end - end - end + + # autoload the sub modules + autoload :Cascade, 'astaire/cascade' + autoload :DSL, 'astaire/dsl' + autoload :InlineTemplates, 'astaire/inline_templates' end \ No newline at end of file diff --git a/lib/astaire/cascade.rb b/lib/astaire/cascade.rb new file mode 100644 index 0000000..9000671 --- /dev/null +++ b/lib/astaire/cascade.rb @@ -0,0 +1,30 @@ +module Astaire + class Cascade + def self.new(*apps) + apps = apps.flatten + + case apps.length + when 0 + raise ArgumentError, "app is required" + when 1 + apps.first + else + super(apps) + end + end + + def initialize(apps) + @apps = apps + end + + def call(env) + result = nil + @apps.each do |app| + result = app.call(env) + break unless result[1]["X-Cascade"] == "pass" + end + + result + end + end +end \ No newline at end of file diff --git a/lib/astaire/dsl.rb b/lib/astaire/dsl.rb new file mode 100644 index 0000000..5309ab3 --- /dev/null +++ b/lib/astaire/dsl.rb @@ -0,0 +1,100 @@ +module Astaire + module DSL + extend ActiveSupport::Concern + include AbstractController::Helpers + + included do + class_attribute :_astaire_router + self._astaire_router = ActionDispatch::Routing::RouteSet.new + + class_attribute :_astaire_helpers + self._astaire_helpers = url_helper_module + + class_attribute :_inline_resolver + self._inline_resolver = InlineTemplates.new + + _inline_resolver.add_controller(self) + + include _astaire_helpers + helper _astaire_helpers + + append_view_path _inline_resolver + end + + module ClassMethods + def call(env) + _astaire_router.call(env) + end + + def mapper + @mapper ||= ActionDispatch::Routing::Mapper.new(_astaire_router) + end + + %w(get post put delete).each do |method| + class_eval <<-R, __FILE__, __LINE__+1 + def #{method}(path, opts = {}, &blk) + map_astaire_action "#{method}", path, opts, blk + end + R + end + + def inherited(klass) + super + _inline_resolver.add_controller(klass) + end + + private + + def map_astaire_action(method, path, opts, blk) + action_name = "[#{method}] #{path}" + define_method action_name, &blk + opts.merge! :via => method, :to => action(action_name) + + mapper.match(path, opts) + make_url_helper(opts[:as]) if opts[:as] + end + + def url_helper_module + Module.new do + def _astaire_url_opts_from_args(name, route, args, only_path) + opts = args.extract_options! + + if args.any? + opts[:_positional_args] = args + opts[:_positional_keys] = route.segment_keys + end + + opts = url_options.merge(opts) + opts.merge!(:use_route => name, :only_path => only_path) + + if path_segments = opts[:_path_segments] + path_segments.delete(:controller) + path_segments.delete(:action) + end + + opts + end + end + end + + def make_url_helper(name) + name = name.to_sym + router = _astaire_router + + _astaire_helpers.module_eval do + define_method "#{name}_path" do |*args| + route = router.named_routes[name] + opts = _astaire_url_opts_from_args(name, route, args, true) + router.url_for(opts) + end + + define_method "#{name}_url" do |*args| + route = router.named_routes[name] + opts = _astaire_url_opts_from_args(name, route, args, false) + router.url_for(opts) + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/astaire/inline_templates.rb b/lib/astaire/inline_templates.rb new file mode 100644 index 0000000..ebd3ba4 --- /dev/null +++ b/lib/astaire/inline_templates.rb @@ -0,0 +1,63 @@ +module Astaire + class InlineTemplates < ActionView::PathResolver + def initialize + super + @templates = {} + end + + def add_controller(controller) + file = caller_files.first + + begin + app, data = + ::IO.read(file).gsub("\r\n", "\n").split(/^__END__$/, 2) + rescue Errno::ENOENT + app, data = nil + end + + if data + lines = app.count("\n") + 1 + template = nil + data.each_line do |line| + lines += 1 + if line =~ /^@@\s*(.*)/ + template = '' + @templates["#{controller.controller_path}/#{$1}"] = + [template, file, lines] + elsif template + template << line + end + end + end + end + + def query(path, exts, formats) + query = Regexp.escape(path) + exts.each do |ext| + query << '(' << ext.map {|e| e && Regexp.escape(".#{e}") }.join('|') << '|)' + end + + templates = [] + @templates.select { |k,v| k =~ /^#{query}$/ }.each do |path, (source, file, lines)| + handler, format = extract_handler_and_format(path, formats) + templates << ActionView::Template.new(source, path, handler, + :virtual_path => path, :format => format) + end + + templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size } + end + + # Like Kernel#caller but excluding certain magic entries and without + # line / method information; the resulting array contains filenames only. + def caller_files + caller_locations. + map { |file,line| file } + end + + def caller_locations + caller(1). + map { |line| line.split(/:(?=\d|in )/)[0,2] }. + reject { |file,line| Astaire::CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } } + end + end +end \ No newline at end of file diff --git a/lib/astaire/railtie.rb b/lib/astaire/railtie.rb index bc73669..a19a9e3 100644 --- a/lib/astaire/railtie.rb +++ b/lib/astaire/railtie.rb @@ -5,17 +5,15 @@ class Rails < Rails::Railtie end initializer "astaire.cascade_routing" do |app| - # A lambda is needed here to ensure that the constant is reloaded - # after each request (in development mode) astaire_app = proc { |env| ApplicationController.call(env) } - app.middleware.use ActionDispatch::Cascade, lambda { astaire_app } + app.middleware.use Astaire::Cascade, astaire_app end # Controllers must be preloaded in order for Astaire's routing # to be hooked up initializer "astaire.preload_controllers" do |app| config.to_prepare do - app.config.paths.app.controllers.each do |load_path| + app.config.paths["app/controllers"].each do |load_path| matcher = /\A#{Regexp.escape(load_path)}\/(.*)\.rb\Z/ Dir["#{load_path}/**/*_controller.rb"].each do |file| require_dependency file.sub(matcher, '\1')