From 200144dd009d33ff8334be24d0fb2cc91b3a87ab Mon Sep 17 00:00:00 2001 From: murphy Date: Wed, 14 Apr 2010 23:59:03 +0000 Subject: Moving scanner tests into separate repository. The repository can be reached at http://svn.rubychan.de/coderay-scanner-tests/trunk. --- test/scanners/ruby/rails.in.rb | 74259 --------------------------------------- 1 file changed, 74259 deletions(-) delete mode 100644 test/scanners/ruby/rails.in.rb (limited to 'test/scanners/ruby/rails.in.rb') diff --git a/test/scanners/ruby/rails.in.rb b/test/scanners/ruby/rails.in.rb deleted file mode 100644 index 587b56d..0000000 --- a/test/scanners/ruby/rails.in.rb +++ /dev/null @@ -1,74259 +0,0 @@ -require 'rbconfig' -require 'find' -require 'ftools' - -include Config - -# this was adapted from rdoc's install.rb by way of Log4r - -$sitedir = CONFIG["sitelibdir"] -unless $sitedir - version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"] - $libdir = File.join(CONFIG["libdir"], "ruby", version) - $sitedir = $:.find {|x| x =~ /site_ruby/ } - if !$sitedir - $sitedir = File.join($libdir, "site_ruby") - elsif $sitedir !~ Regexp.quote(version) - $sitedir = File.join($sitedir, version) - end -end - -# the acual gruntwork -Dir.chdir("lib") - -Find.find("action_mailer", "action_mailer.rb") { |f| - if f[-3..-1] == ".rb" - File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true) - else - File::makedirs(File.join($sitedir, *f.split(/\//))) - end -} -module ActionMailer - module AdvAttrAccessor #:nodoc: - def self.append_features(base) - super - base.extend(ClassMethods) - end - - module ClassMethods #:nodoc: - def adv_attr_accessor(*names) - names.each do |name| - ivar = "@#{name}" - - define_method("#{name}=") do |value| - instance_variable_set(ivar, value) - end - - define_method(name) do |*parameters| - raise ArgumentError, "expected 0 or 1 parameters" unless parameters.length <= 1 - if parameters.empty? - if instance_variables.include?(ivar) - instance_variable_get(ivar) - end - else - instance_variable_set(ivar, parameters.first) - end - end - end - end - end - end -end -require 'action_mailer/adv_attr_accessor' -require 'action_mailer/part' -require 'action_mailer/part_container' -require 'action_mailer/utils' -require 'tmail/net' - -module ActionMailer #:nodoc: - # ActionMailer allows you to send email from your application using a mailer model and views. - # - # = Mailer Models - # To use ActionMailer, you need to create a mailer model. - # - # $ script/generate mailer Notifier - # - # The generated model inherits from ActionMailer::Base. Emails are defined by creating methods within the model which are then - # used to set variables to be used in the mail template, to change options on the mail, or - # to add attachments. - # - # Examples: - # - # class Notifier < ActionMailer::Base - # def signup_notification(recipient) - # recipients recipient.email_address_with_name - # from "system@example.com" - # subject "New account information" - # body "account" => recipient - # end - # end - # - # Mailer methods have the following configuration methods available. - # - # * recipients - Takes one or more email addresses. These addresses are where your email will be delivered to. Sets the To: header. - # * subject - The subject of your email. Sets the Subject: header. - # * from - Who the email you are sending is from. Sets the From: header. - # * cc - Takes one or more email addresses. These addresses will receive a carbon copy of your email. Sets the Cc: header. - # * bcc - Takes one or more email address. These addresses will receive a blind carbon copy of your email. Sets the Bcc header. - # * sent_on - The date on which the message was sent. If not set, the header wil be set by the delivery agent. - # * content_type - Specify the content type of the message. Defaults to text/plain. - # * headers - Specify additional headers to be set for the message, e.g. headers 'X-Mail-Count' => 107370. - # - # The body method has special behavior. It takes a hash which generates an instance variable - # named after each key in the hash containing the value that that key points to. - # - # So, for example, body "account" => recipient would result - # in an instance variable @account with the value of recipient being accessible in the - # view. - # - # = Mailer Views - # Like ActionController, each mailer class has a corresponding view directory - # in which each method of the class looks for a template with its name. - # To define a template to be used with a mailing, create an .rhtml file with the same name as the method - # in your mailer model. For example, in the mailer defined above, the template at - # app/views/notifier/signup_notification.rhtml would be used to generate the email. - # - # Variables defined in the model are accessible as instance variables in the view. - # - # Emails by default are sent in plain text, so a sample view for our model example might look like this: - # - # Hi <%= @account.name %>, - # Thanks for joining our service! Please check back often. - # - # = Sending Mail - # Once a mailer action and template are defined, you can deliver your message or create it and save it - # for delivery later: - # - # Notifier.deliver_signup_notification(david) # sends the email - # mail = Notifier.create_signup_notification(david) # => a tmail object - # Notifier.deliver(mail) - # - # You never instantiate your mailer class. Rather, your delivery instance - # methods are automatically wrapped in class methods that start with the word - # deliver_ followed by the name of the mailer method that you would - # like to deliver. The signup_notification method defined above is - # delivered by invoking Notifier.deliver_signup_notification. - # - # = HTML Email - # To send mail as HTML, make sure your view (the .rhtml file) generates HTML and - # set the content type to html. - # - # class MyMailer < ActionMailer::Base - # def signup_notification(recipient) - # recipients recipient.email_address_with_name - # subject "New account information" - # body "account" => recipient - # from "system@example.com" - # content_type "text/html" # Here's where the magic happens - # end - # end - # - # = Multipart Email - # You can explicitly specify multipart messages: - # - # class ApplicationMailer < ActionMailer::Base - # def signup_notification(recipient) - # recipients recipient.email_address_with_name - # subject "New account information" - # from "system@example.com" - # - # part :content_type => "text/html", - # :body => render_message("signup-as-html", :account => recipient) - # - # part "text/plain" do |p| - # p.body = render_message("signup-as-plain", :account => recipient) - # p.transfer_encoding = "base64" - # end - # end - # end - # - # Multipart messages can also be used implicitly because ActionMailer will automatically - # detect and use multipart templates, where each template is named after the name of the action, followed - # by the content type. Each such detected template will be added as separate part to the message. - # - # For example, if the following templates existed: - # * signup_notification.text.plain.rhtml - # * signup_notification.text.html.rhtml - # * signup_notification.text.xml.rxml - # * signup_notification.text.x-yaml.rhtml - # - # Each would be rendered and added as a separate part to the message, - # with the corresponding content type. The same body hash is passed to - # each template. - # - # = Attachments - # Attachments can be added by using the +attachment+ method. - # - # Example: - # - # class ApplicationMailer < ActionMailer::Base - # # attachments - # def signup_notification(recipient) - # recipients recipient.email_address_with_name - # subject "New account information" - # from "system@example.com" - # - # attachment :content_type => "image/jpeg", - # :body => File.read("an-image.jpg") - # - # attachment "application/pdf" do |a| - # a.body = generate_your_pdf_here() - # end - # end - # end - # - # = Configuration options - # - # These options are specified on the class level, like ActionMailer::Base.template_root = "/my/templates" - # - # * template_root - template root determines the base from which template references will be made. - # - # * logger - the logger is used for generating information on the mailing run if available. - # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers. - # - # * server_settings - Allows detailed configuration of the server: - # * :address Allows you to use a remote mail server. Just change it from its default "localhost" setting. - # * :port On the off chance that your mail server doesn't run on port 25, you can change it. - # * :domain If you need to specify a HELO domain, you can do it here. - # * :user_name If your mail server requires authentication, set the username in this setting. - # * :password If your mail server requires authentication, set the password in this setting. - # * :authentication If your mail server requires authentication, you need to specify the authentication type here. - # This is a symbol and one of :plain, :login, :cram_md5 - # - # * raise_delivery_errors - whether or not errors should be raised if the email fails to be delivered. - # - # * delivery_method - Defines a delivery method. Possible values are :smtp (default), :sendmail, and :test. - # Sendmail is assumed to be present at "/usr/sbin/sendmail". - # - # * perform_deliveries - Determines whether deliver_* methods are actually carried out. By default they are, - # but this can be turned off to help functional testing. - # - # * deliveries - Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful - # for unit and functional testing. - # - # * default_charset - The default charset used for the body and to encode the subject. Defaults to UTF-8. You can also - # pick a different charset from inside a method with @charset. - # * default_content_type - The default content type used for the main part of the message. Defaults to "text/plain". You - # can also pick a different content type from inside a method with @content_type. - # * default_mime_version - The default mime version used for the message. Defaults to nil. You - # can also pick a different value from inside a method with @mime_version. When multipart messages are in - # use, @mime_version will be set to "1.0" if it is not set inside a method. - # * default_implicit_parts_order - When a message is built implicitly (i.e. multiple parts are assembled from templates - # which specify the content type in their filenames) this variable controls how the parts are ordered. Defaults to - # ["text/html", "text/enriched", "text/plain"]. Items that appear first in the array have higher priority in the mail client - # and appear last in the mime encoded message. You can also pick a different order from inside a method with - # @implicit_parts_order. - class Base - include AdvAttrAccessor, PartContainer - - # Action Mailer subclasses should be reloaded by the dispatcher in Rails - # when Dependencies.mechanism = :load. - include Reloadable::Subclasses - - private_class_method :new #:nodoc: - - class_inheritable_accessor :template_root - cattr_accessor :logger - - @@server_settings = { - :address => "localhost", - :port => 25, - :domain => 'localhost.localdomain', - :user_name => nil, - :password => nil, - :authentication => nil - } - cattr_accessor :server_settings - - @@raise_delivery_errors = true - cattr_accessor :raise_delivery_errors - - @@delivery_method = :smtp - cattr_accessor :delivery_method - - @@perform_deliveries = true - cattr_accessor :perform_deliveries - - @@deliveries = [] - cattr_accessor :deliveries - - @@default_charset = "utf-8" - cattr_accessor :default_charset - - @@default_content_type = "text/plain" - cattr_accessor :default_content_type - - @@default_mime_version = nil - cattr_accessor :default_mime_version - - @@default_implicit_parts_order = [ "text/html", "text/enriched", "text/plain" ] - cattr_accessor :default_implicit_parts_order - - # Specify the BCC addresses for the message - adv_attr_accessor :bcc - - # Define the body of the message. This is either a Hash (in which case it - # specifies the variables to pass to the template when it is rendered), - # or a string, in which case it specifies the actual text of the message. - adv_attr_accessor :body - - # Specify the CC addresses for the message. - adv_attr_accessor :cc - - # Specify the charset to use for the message. This defaults to the - # +default_charset+ specified for ActionMailer::Base. - adv_attr_accessor :charset - - # Specify the content type for the message. This defaults to text/plain - # in most cases, but can be automatically set in some situations. - adv_attr_accessor :content_type - - # Specify the from address for the message. - adv_attr_accessor :from - - # Specify additional headers to be added to the message. - adv_attr_accessor :headers - - # Specify the order in which parts should be sorted, based on content-type. - # This defaults to the value for the +default_implicit_parts_order+. - adv_attr_accessor :implicit_parts_order - - # Override the mailer name, which defaults to an inflected version of the - # mailer's class name. If you want to use a template in a non-standard - # location, you can use this to specify that location. - adv_attr_accessor :mailer_name - - # Defaults to "1.0", but may be explicitly given if needed. - adv_attr_accessor :mime_version - - # The recipient addresses for the message, either as a string (for a single - # address) or an array (for multiple addresses). - adv_attr_accessor :recipients - - # The date on which the message was sent. If not set (the default), the - # header will be set by the delivery agent. - adv_attr_accessor :sent_on - - # Specify the subject of the message. - adv_attr_accessor :subject - - # Specify the template name to use for current message. This is the "base" - # template name, without the extension or directory, and may be used to - # have multiple mailer methods share the same template. - adv_attr_accessor :template - - # The mail object instance referenced by this mailer. - attr_reader :mail - - class << self - def method_missing(method_symbol, *parameters)#:nodoc: - case method_symbol.id2name - when /^create_([_a-z]\w*)/ then new($1, *parameters).mail - when /^deliver_([_a-z]\w*)/ then new($1, *parameters).deliver! - when "new" then nil - else super - end - end - - # Receives a raw email, parses it into an email object, decodes it, - # instantiates a new mailer, and passes the email object to the mailer - # object's #receive method. If you want your mailer to be able to - # process incoming messages, you'll need to implement a #receive - # method that accepts the email object as a parameter: - # - # class MyMailer < ActionMailer::Base - # def receive(mail) - # ... - # end - # end - def receive(raw_email) - logger.info "Received mail:\n #{raw_email}" unless logger.nil? - mail = TMail::Mail.parse(raw_email) - mail.base64_decode - new.receive(mail) - end - - # Deliver the given mail object directly. This can be used to deliver - # a preconstructed mail object, like: - # - # email = MyMailer.create_some_mail(parameters) - # email.set_some_obscure_header "frobnicate" - # MyMailer.deliver(email) - def deliver(mail) - new.deliver!(mail) - end - end - - # Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer - # will be initialized according to the named method. If not, the mailer will - # remain uninitialized (useful when you only need to invoke the "receive" - # method, for instance). - def initialize(method_name=nil, *parameters) #:nodoc: - create!(method_name, *parameters) if method_name - end - - # Initialize the mailer via the given +method_name+. The body will be - # rendered and a new TMail::Mail object created. - def create!(method_name, *parameters) #:nodoc: - initialize_defaults(method_name) - send(method_name, *parameters) - - # If an explicit, textual body has not been set, we check assumptions. - unless String === @body - # First, we look to see if there are any likely templates that match, - # which include the content-type in their file name (i.e., - # "the_template_file.text.html.rhtml", etc.). Only do this if parts - # have not already been specified manually. - if @parts.empty? - templates = Dir.glob("#{template_path}/#{@template}.*") - templates.each do |path| - # TODO: don't hardcode rhtml|rxml - basename = File.basename(path) - next unless md = /^([^\.]+)\.([^\.]+\.[^\+]+)\.(rhtml|rxml)$/.match(basename) - template_name = basename - content_type = md.captures[1].gsub('.', '/') - @parts << Part.new(:content_type => content_type, - :disposition => "inline", :charset => charset, - :body => render_message(template_name, @body)) - end - unless @parts.empty? - @content_type = "multipart/alternative" - @parts = sort_parts(@parts, @implicit_parts_order) - end - end - - # Then, if there were such templates, we check to see if we ought to - # also render a "normal" template (without the content type). If a - # normal template exists (or if there were no implicit parts) we render - # it. - template_exists = @parts.empty? - template_exists ||= Dir.glob("#{template_path}/#{@template}.*").any? { |i| File.basename(i).split(".").length == 2 } - @body = render_message(@template, @body) if template_exists - - # Finally, if there are other message parts and a textual body exists, - # we shift it onto the front of the parts and set the body to nil (so - # that create_mail doesn't try to render it in addition to the parts). - if !@parts.empty? && String === @body - @parts.unshift Part.new(:charset => charset, :body => @body) - @body = nil - end - end - - # If this is a multipart e-mail add the mime_version if it is not - # already set. - @mime_version ||= "1.0" if !@parts.empty? - - # build the mail object itself - @mail = create_mail - end - - # Delivers a TMail::Mail object. By default, it delivers the cached mail - # object (from the #create! method). If no cached mail object exists, and - # no alternate has been given as the parameter, this will fail. - def deliver!(mail = @mail) - raise "no mail object available for delivery!" unless mail - logger.info "Sent mail:\n #{mail.encoded}" unless logger.nil? - - begin - send("perform_delivery_#{delivery_method}", mail) if perform_deliveries - rescue Object => e - raise e if raise_delivery_errors - end - - return mail - end - - private - # Set up the default values for the various instance variables of this - # mailer. Subclasses may override this method to provide different - # defaults. - def initialize_defaults(method_name) - @charset ||= @@default_charset.dup - @content_type ||= @@default_content_type.dup - @implicit_parts_order ||= @@default_implicit_parts_order.dup - @template ||= method_name - @mailer_name ||= Inflector.underscore(self.class.name) - @parts ||= [] - @headers ||= {} - @body ||= {} - @mime_version = @@default_mime_version.dup if @@default_mime_version - end - - def render_message(method_name, body) - render :file => method_name, :body => body - end - - def render(opts) - body = opts.delete(:body) - initialize_template_class(body).render(opts) - end - - def template_path - "#{template_root}/#{mailer_name}" - end - - def initialize_template_class(assigns) - ActionView::Base.new(template_path, assigns, self) - end - - def sort_parts(parts, order = []) - order = order.collect { |s| s.downcase } - - parts = parts.sort do |a, b| - a_ct = a.content_type.downcase - b_ct = b.content_type.downcase - - a_in = order.include? a_ct - b_in = order.include? b_ct - - s = case - when a_in && b_in - order.index(a_ct) <=> order.index(b_ct) - when a_in - -1 - when b_in - 1 - else - a_ct <=> b_ct - end - - # reverse the ordering because parts that come last are displayed - # first in mail clients - (s * -1) - end - - parts - end - - def create_mail - m = TMail::Mail.new - - m.subject, = quote_any_if_necessary(charset, subject) - m.to, m.from = quote_any_address_if_necessary(charset, recipients, from) - m.bcc = quote_address_if_necessary(bcc, charset) unless bcc.nil? - m.cc = quote_address_if_necessary(cc, charset) unless cc.nil? - - m.mime_version = mime_version unless mime_version.nil? - m.date = sent_on.to_time rescue sent_on if sent_on - headers.each { |k, v| m[k] = v } - - real_content_type, ctype_attrs = parse_content_type - - if @parts.empty? - m.set_content_type(real_content_type, nil, ctype_attrs) - m.body = Utils.normalize_new_lines(body) - else - if String === body - part = TMail::Mail.new - part.body = Utils.normalize_new_lines(body) - part.set_content_type(real_content_type, nil, ctype_attrs) - part.set_content_disposition "inline" - m.parts << part - end - - @parts.each do |p| - part = (TMail::Mail === p ? p : p.to_mail(self)) - m.parts << part - end - - if real_content_type =~ /multipart/ - ctype_attrs.delete "charset" - m.set_content_type(real_content_type, nil, ctype_attrs) - end - end - - @mail = m - end - - def perform_delivery_smtp(mail) - destinations = mail.destinations - mail.ready_to_send - - Net::SMTP.start(server_settings[:address], server_settings[:port], server_settings[:domain], - server_settings[:user_name], server_settings[:password], server_settings[:authentication]) do |smtp| - smtp.sendmail(mail.encoded, mail.from, destinations) - end - end - - def perform_delivery_sendmail(mail) - IO.popen("/usr/sbin/sendmail -i -t","w+") do |sm| - sm.print(mail.encoded.gsub(/\r/, '')) - sm.flush - end - end - - def perform_delivery_test(mail) - deliveries << mail - end - end -end -module ActionMailer - module Helpers #:nodoc: - def self.append_features(base) #:nodoc: - super - - # Initialize the base module to aggregate its helpers. - base.class_inheritable_accessor :master_helper_module - base.master_helper_module = Module.new - - # Extend base with class methods to declare helpers. - base.extend(ClassMethods) - - base.class_eval do - # Wrap inherited to create a new master helper module for subclasses. - class << self - alias_method :inherited_without_helper, :inherited - alias_method :inherited, :inherited_with_helper - end - - # Wrap initialize_template_class to extend new template class - # instances with the master helper module. - alias_method :initialize_template_class_without_helper, :initialize_template_class - alias_method :initialize_template_class, :initialize_template_class_with_helper - end - end - - module ClassMethods - # Makes all the (instance) methods in the helper module available to templates rendered through this controller. - # See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules - # available to the templates. - def add_template_helper(helper_module) #:nodoc: - master_helper_module.module_eval "include #{helper_module}" - end - - # Declare a helper: - # helper :foo - # requires 'foo_helper' and includes FooHelper in the template class. - # helper FooHelper - # includes FooHelper in the template class. - # helper { def foo() "#{bar} is the very best" end } - # evaluates the block in the template class, adding method #foo. - # helper(:three, BlindHelper) { def mice() 'mice' end } - # does all three. - def helper(*args, &block) - args.flatten.each do |arg| - case arg - when Module - add_template_helper(arg) - when String, Symbol - file_name = arg.to_s.underscore + '_helper' - class_name = file_name.camelize - - begin - require_dependency(file_name) - rescue LoadError => load_error - requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1] - msg = (requiree == file_name) ? "Missing helper file helpers/#{file_name}.rb" : "Can't load file: #{requiree}" - raise LoadError.new(msg).copy_blame!(load_error) - end - - add_template_helper(class_name.constantize) - else - raise ArgumentError, 'helper expects String, Symbol, or Module argument' - end - end - - # Evaluate block in template class if given. - master_helper_module.module_eval(&block) if block_given? - end - - # Declare a controller method as a helper. For example, - # helper_method :link_to - # def link_to(name, options) ... end - # makes the link_to controller method available in the view. - def helper_method(*methods) - methods.flatten.each do |method| - master_helper_module.module_eval <<-end_eval - def #{method}(*args, &block) - controller.send(%(#{method}), *args, &block) - end - end_eval - end - end - - # Declare a controller attribute as a helper. For example, - # helper_attr :name - # attr_accessor :name - # makes the name and name= controller methods available in the view. - # The is a convenience wrapper for helper_method. - def helper_attr(*attrs) - attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") } - end - - private - def inherited_with_helper(child) - inherited_without_helper(child) - begin - child.master_helper_module = Module.new - child.master_helper_module.send :include, master_helper_module - child.helper child.name.underscore - rescue MissingSourceFile => e - raise unless e.is_missing?("helpers/#{child.name.underscore}_helper") - end - end - end - - private - # Extend the template class instance with our controller's helper module. - def initialize_template_class_with_helper(assigns) - returning(template = initialize_template_class_without_helper(assigns)) do - template.extend self.class.master_helper_module - end - end - end -endrequire 'text/format' - -module MailHelper - # Uses Text::Format to take the text and format it, indented two spaces for - # each line, and wrapped at 72 columns. - def block_format(text) - formatted = text.split(/\n\r\n/).collect { |paragraph| - Text::Format.new( - :columns => 72, :first_indent => 2, :body_indent => 2, :text => paragraph - ).format - }.join("\n") - - # Make list points stand on their own line - formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { |s| " #{$1} #{$2.strip}\n" } - formatted.gsub!(/[ ]*([#]+) ([^#]*)/) { |s| " #{$1} #{$2.strip}\n" } - - formatted - end -end -require 'action_mailer/adv_attr_accessor' -require 'action_mailer/part_container' -require 'action_mailer/utils' - -module ActionMailer - # Represents a subpart of an email message. It shares many similar - # attributes of ActionMailer::Base. Although you can create parts manually - # and add them to the #parts list of the mailer, it is easier - # to use the helper methods in ActionMailer::PartContainer. - class Part - include ActionMailer::AdvAttrAccessor - include ActionMailer::PartContainer - - # Represents the body of the part, as a string. This should not be a - # Hash (like ActionMailer::Base), but if you want a template to be rendered - # into the body of a subpart you can do it with the mailer's #render method - # and assign the result here. - adv_attr_accessor :body - - # Specify the charset for this subpart. By default, it will be the charset - # of the containing part or mailer. - adv_attr_accessor :charset - - # The content disposition of this part, typically either "inline" or - # "attachment". - adv_attr_accessor :content_disposition - - # The content type of the part. - adv_attr_accessor :content_type - - # The filename to use for this subpart (usually for attachments). - adv_attr_accessor :filename - - # Accessor for specifying additional headers to include with this part. - adv_attr_accessor :headers - - # The transfer encoding to use for this subpart, like "base64" or - # "quoted-printable". - adv_attr_accessor :transfer_encoding - - # Create a new part from the given +params+ hash. The valid params keys - # correspond to the accessors. - def initialize(params) - @content_type = params[:content_type] - @content_disposition = params[:disposition] || "inline" - @charset = params[:charset] - @body = params[:body] - @filename = params[:filename] - @transfer_encoding = params[:transfer_encoding] || "quoted-printable" - @headers = params[:headers] || {} - @parts = [] - end - - # Convert the part to a mail object which can be included in the parts - # list of another mail object. - def to_mail(defaults) - part = TMail::Mail.new - - real_content_type, ctype_attrs = parse_content_type(defaults) - - if @parts.empty? - part.content_transfer_encoding = transfer_encoding || "quoted-printable" - case (transfer_encoding || "").downcase - when "base64" then - part.body = TMail::Base64.folding_encode(body) - when "quoted-printable" - part.body = [Utils.normalize_new_lines(body)].pack("M*") - else - part.body = body - end - - # Always set the content_type after setting the body and or parts! - # Also don't set filename and name when there is none (like in - # non-attachment parts) - if content_disposition == "attachment" - ctype_attrs.delete "charset" - part.set_content_type(real_content_type, nil, - squish("name" => filename).merge(ctype_attrs)) - part.set_content_disposition(content_disposition, - squish("filename" => filename).merge(ctype_attrs)) - else - part.set_content_type(real_content_type, nil, ctype_attrs) - part.set_content_disposition(content_disposition) - end - else - if String === body - part = TMail::Mail.new - part.body = body - part.set_content_type(real_content_type, nil, ctype_attrs) - part.set_content_disposition "inline" - m.parts << part - end - - @parts.each do |p| - prt = (TMail::Mail === p ? p : p.to_mail(defaults)) - part.parts << prt - end - - part.set_content_type(real_content_type, nil, ctype_attrs) if real_content_type =~ /multipart/ - end - - headers.each { |k,v| part[k] = v } - - part - end - - private - - def squish(values={}) - values.delete_if { |k,v| v.nil? } - end - end -end -module ActionMailer - # Accessors and helpers that ActionMailer::Base and ActionMailer::Part have - # in common. Using these helpers you can easily add subparts or attachments - # to your message: - # - # def my_mail_message(...) - # ... - # part "text/plain" do |p| - # p.body "hello, world" - # p.transfer_encoding "base64" - # end - # - # attachment "image/jpg" do |a| - # a.body = File.read("hello.jpg") - # a.filename = "hello.jpg" - # end - # end - module PartContainer - # The list of subparts of this container - attr_reader :parts - - # Add a part to a multipart message, with the given content-type. The - # part itself is yielded to the block so that other properties (charset, - # body, headers, etc.) can be set on it. - def part(params) - params = {:content_type => params} if String === params - part = Part.new(params) - yield part if block_given? - @parts << part - end - - # Add an attachment to a multipart message. This is simply a part with the - # content-disposition set to "attachment". - def attachment(params, &block) - params = { :content_type => params } if String === params - params = { :disposition => "attachment", - :transfer_encoding => "base64" }.merge(params) - part(params, &block) - end - - private - - def parse_content_type(defaults=nil) - return [defaults && defaults.content_type, {}] if content_type.blank? - ctype, *attrs = content_type.split(/;\s*/) - attrs = attrs.inject({}) { |h,s| k,v = s.split(/=/, 2); h[k] = v; h } - [ctype, {"charset" => charset || defaults && defaults.charset}.merge(attrs)] - end - - end -end -module ActionMailer - module Quoting #:nodoc: - # Convert the given text into quoted printable format, with an instruction - # that the text be eventually interpreted in the given charset. - def quoted_printable(text, charset) - text = text.gsub( /[^a-z ]/i ) { quoted_printable_encode($&) }. - gsub( / /, "_" ) - "=?#{charset}?Q?#{text}?=" - end - - # Convert the given character to quoted printable format, taking into - # account multi-byte characters (if executing with $KCODE="u", for instance) - def quoted_printable_encode(character) - result = "" - character.each_byte { |b| result << "=%02x" % b } - result - end - - # A quick-and-dirty regexp for determining whether a string contains any - # characters that need escaping. - if !defined?(CHARS_NEEDING_QUOTING) - CHARS_NEEDING_QUOTING = /[\000-\011\013\014\016-\037\177-\377]/ - end - - # Quote the given text if it contains any "illegal" characters - def quote_if_necessary(text, charset) - (text =~ CHARS_NEEDING_QUOTING) ? - quoted_printable(text, charset) : - text - end - - # Quote any of the given strings if they contain any "illegal" characters - def quote_any_if_necessary(charset, *args) - args.map { |v| quote_if_necessary(v, charset) } - end - - # Quote the given address if it needs to be. The address may be a - # regular email address, or it can be a phrase followed by an address in - # brackets. The phrase is the only part that will be quoted, and only if - # it needs to be. This allows extended characters to be used in the - # "to", "from", "cc", and "bcc" headers. - def quote_address_if_necessary(address, charset) - if Array === address - address.map { |a| quote_address_if_necessary(a, charset) } - elsif address =~ /^(\S.*)\s+(<.*>)$/ - address = $2 - phrase = quote_if_necessary($1.gsub(/^['"](.*)['"]$/, '\1'), charset) - "\"#{phrase}\" #{address}" - else - address - end - end - - # Quote any of the given addresses, if they need to be. - def quote_any_address_if_necessary(charset, *args) - args.map { |v| quote_address_if_necessary(v, charset) } - end - end -end -module ActionMailer - module Utils #:nodoc: - def normalize_new_lines(text) - text.to_s.gsub(/\r\n?/, "\n") - end - module_function :normalize_new_lines - end -end -#-- -# Text::Format for Ruby -# Version 0.63 -# -# Copyright (c) 2002 - 2003 Austin Ziegler -# -# $Id: format.rb,v 1.1.1.1 2004/10/14 11:59:57 webster132 Exp $ -# -# ========================================================================== -# Revision History :: -# YYYY.MM.DD Change ID Developer -# Description -# -------------------------------------------------------------------------- -# 2002.10.18 Austin Ziegler -# Fixed a minor problem with tabs not being counted. Changed -# abbreviations from Hash to Array to better suit Ruby's -# capabilities. Fixed problems with the way that Array arguments -# are handled in calls to the major object types, excepting in -# Text::Format#expand and Text::Format#unexpand (these will -# probably need to be fixed). -# 2002.10.30 Austin Ziegler -# Fixed the ordering of the <=> for binary tests. Fixed -# Text::Format#expand and Text::Format#unexpand to handle array -# arguments better. -# 2003.01.24 Austin Ziegler -# Fixed a problem with Text::Format::RIGHT_FILL handling where a -# single word is larger than #columns. Removed Comparable -# capabilities (<=> doesn't make sense; == does). Added Symbol -# equivalents for the Hash initialization. Hash initialization has -# been modified so that values are set as follows (Symbols are -# highest priority; strings are middle; defaults are lowest): -# @columns = arg[:columns] || arg['columns'] || @columns -# Added #hard_margins, #split_rules, #hyphenator, and #split_words. -# 2003.02.07 Austin Ziegler -# Fixed the installer for proper case-sensitive handling. -# 2003.03.28 Austin Ziegler -# Added the ability for a hyphenator to receive the formatter -# object. Fixed a bug for strings matching /\A\s*\Z/ failing -# entirely. Fixed a test case failing under 1.6.8. -# 2003.04.04 Austin Ziegler -# Handle the case of hyphenators returning nil for first/rest. -# 2003.09.17 Austin Ziegler -# Fixed a problem where #paragraphs(" ") was raising -# NoMethodError. -# -# ========================================================================== -#++ - -module Text #:nodoc: - # Text::Format for Ruby is copyright 2002 - 2005 by Austin Ziegler. It - # is available under Ruby's licence, the Perl Artistic licence, or the - # GNU GPL version 2 (or at your option, any later version). As a - # special exception, for use with official Rails (provided by the - # rubyonrails.org development team) and any project created with - # official Rails, the following alternative MIT-style licence may be - # used: - # - # == Text::Format Licence for Rails and Rails Applications - # Permission is hereby granted, free of charge, to any person - # obtaining a copy of this software and associated documentation files - # (the "Software"), to deal in the Software without restriction, - # including without limitation the rights to use, copy, modify, merge, - # publish, distribute, sublicense, and/or sell copies of the Software, - # and to permit persons to whom the Software is furnished to do so, - # subject to the following conditions: - # - # * The names of its contributors may not be used to endorse or - # promote products derived from this software without specific prior - # written permission. - # - # The above copyright notice and this permission notice shall be - # included in all copies or substantial portions of the Software. - # - # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - # SOFTWARE. - class Format - VERSION = '0.63' - - # Local abbreviations. More can be added with Text::Format.abbreviations - ABBREV = [ 'Mr', 'Mrs', 'Ms', 'Jr', 'Sr' ] - - # Formatting values - LEFT_ALIGN = 0 - RIGHT_ALIGN = 1 - RIGHT_FILL = 2 - JUSTIFY = 3 - - # Word split modes (only applies when #hard_margins is true). - SPLIT_FIXED = 1 - SPLIT_CONTINUATION = 2 - SPLIT_HYPHENATION = 4 - SPLIT_CONTINUATION_FIXED = SPLIT_CONTINUATION | SPLIT_FIXED - SPLIT_HYPHENATION_FIXED = SPLIT_HYPHENATION | SPLIT_FIXED - SPLIT_HYPHENATION_CONTINUATION = SPLIT_HYPHENATION | SPLIT_CONTINUATION - SPLIT_ALL = SPLIT_HYPHENATION | SPLIT_CONTINUATION | SPLIT_FIXED - - # Words forcibly split by Text::Format will be stored as split words. - # This class represents a word forcibly split. - class SplitWord - # The word that was split. - attr_reader :word - # The first part of the word that was split. - attr_reader :first - # The remainder of the word that was split. - attr_reader :rest - - def initialize(word, first, rest) #:nodoc: - @word = word - @first = first - @rest = rest - end - end - - private - LEQ_RE = /[.?!]['"]?$/ - - def brk_re(i) #:nodoc: - %r/((?:\S+\s+){#{i}})(.+)/ - end - - def posint(p) #:nodoc: - p.to_i.abs - end - - public - # Compares two Text::Format objects. All settings of the objects are - # compared *except* #hyphenator. Generated results (e.g., #split_words) - # are not compared, either. - def ==(o) - (@text == o.text) && - (@columns == o.columns) && - (@left_margin == o.left_margin) && - (@right_margin == o.right_margin) && - (@hard_margins == o.hard_margins) && - (@split_rules == o.split_rules) && - (@first_indent == o.first_indent) && - (@body_indent == o.body_indent) && - (@tag_text == o.tag_text) && - (@tabstop == o.tabstop) && - (@format_style == o.format_style) && - (@extra_space == o.extra_space) && - (@tag_paragraph == o.tag_paragraph) && - (@nobreak == o.nobreak) && - (@abbreviations == o.abbreviations) && - (@nobreak_regex == o.nobreak_regex) - end - - # The text to be manipulated. Note that value is optional, but if the - # formatting functions are called without values, this text is what will - # be formatted. - # - # *Default*:: [] - # Used in:: All methods - attr_accessor :text - - # The total width of the format area. The margins, indentation, and text - # are formatted into this space. - # - # COLUMNS - # <--------------------------------------------------------------> - # <-----------><------><---------------------------><------------> - # left margin indent text is formatted into here right margin - # - # *Default*:: 72 - # Used in:: #format, #paragraphs, - # #center - attr_reader :columns - - # The total width of the format area. The margins, indentation, and text - # are formatted into this space. The value provided is silently - # converted to a positive integer. - # - # COLUMNS - # <--------------------------------------------------------------> - # <-----------><------><---------------------------><------------> - # left margin indent text is formatted into here right margin - # - # *Default*:: 72 - # Used in:: #format, #paragraphs, - # #center - def columns=(c) - @columns = posint(c) - end - - # The number of spaces used for the left margin. - # - # columns - # <--------------------------------------------------------------> - # <-----------><------><---------------------------><------------> - # LEFT MARGIN indent text is formatted into here right margin - # - # *Default*:: 0 - # Used in:: #format, #paragraphs, - # #center - attr_reader :left_margin - - # The number of spaces used for the left margin. The value provided is - # silently converted to a positive integer value. - # - # columns - # <--------------------------------------------------------------> - # <-----------><------><---------------------------><------------> - # LEFT MARGIN indent text is formatted into here right margin - # - # *Default*:: 0 - # Used in:: #format, #paragraphs, - # #center - def left_margin=(left) - @left_margin = posint(left) - end - - # The number of spaces used for the right margin. - # - # columns - # <--------------------------------------------------------------> - # <-----------><------><---------------------------><------------> - # left margin indent text is formatted into here RIGHT MARGIN - # - # *Default*:: 0 - # Used in:: #format, #paragraphs, - # #center - attr_reader :right_margin - - # The number of spaces used for the right margin. The value provided is - # silently converted to a positive integer value. - # - # columns - # <--------------------------------------------------------------> - # <-----------><------><---------------------------><------------> - # left margin indent text is formatted into here RIGHT MARGIN - # - # *Default*:: 0 - # Used in:: #format, #paragraphs, - # #center - def right_margin=(r) - @right_margin = posint(r) - end - - # The number of spaces to indent the first line of a paragraph. - # - # columns - # <--------------------------------------------------------------> - # <-----------><------><---------------------------><------------> - # left margin INDENT text is formatted into here right margin - # - # *Default*:: 4 - # Used in:: #format, #paragraphs - attr_reader :first_indent - - # The number of spaces to indent the first line of a paragraph. The - # value provided is silently converted to a positive integer value. - # - # columns - # <--------------------------------------------------------------> - # <-----------><------><---------------------------><------------> - # left margin INDENT text is formatted into here right margin - # - # *Default*:: 4 - # Used in:: #format, #paragraphs - def first_indent=(f) - @first_indent = posint(f) - end - - # The number of spaces to indent all lines after the first line of a - # paragraph. - # - # columns - # <--------------------------------------------------------------> - # <-----------><------><---------------------------><------------> - # left margin INDENT text is formatted into here right margin - # - # *Default*:: 0 - # Used in:: #format, #paragraphs - attr_reader :body_indent - - # The number of spaces to indent all lines after the first line of - # a paragraph. The value provided is silently converted to a - # positive integer value. - # - # columns - # <--------------------------------------------------------------> - # <-----------><------><---------------------------><------------> - # left margin INDENT text is formatted into here right margin - # - # *Default*:: 0 - # Used in:: #format, #paragraphs - def body_indent=(b) - @body_indent = posint(b) - end - - # Normally, words larger than the format area will be placed on a line - # by themselves. Setting this to +true+ will force words larger than the - # format area to be split into one or more "words" each at most the size - # of the format area. The first line and the original word will be - # placed into #split_words. Note that this will cause the - # output to look *similar* to a #format_style of JUSTIFY. (Lines will be - # filled as much as possible.) - # - # *Default*:: +false+ - # Used in:: #format, #paragraphs - attr_accessor :hard_margins - - # An array of words split during formatting if #hard_margins is set to - # +true+. - # #split_words << Text::Format::SplitWord.new(word, first, rest) - attr_reader :split_words - - # The object responsible for hyphenating. It must respond to - # #hyphenate_to(word, size) or #hyphenate_to(word, size, formatter) and - # return an array of the word split into two parts; if there is a - # hyphenation mark to be applied, responsibility belongs to the - # hyphenator object. The size is the MAXIMUM size permitted, including - # any hyphenation marks. If the #hyphenate_to method has an arity of 3, - # the formatter will be provided to the method. This allows the - # hyphenator to make decisions about the hyphenation based on the - # formatting rules. - # - # *Default*:: +nil+ - # Used in:: #format, #paragraphs - attr_reader :hyphenator - - # The object responsible for hyphenating. It must respond to - # #hyphenate_to(word, size) and return an array of the word hyphenated - # into two parts. The size is the MAXIMUM size permitted, including any - # hyphenation marks. - # - # *Default*:: +nil+ - # Used in:: #format, #paragraphs - def hyphenator=(h) - raise ArgumentError, "#{h.inspect} is not a valid hyphenator." unless h.respond_to?(:hyphenate_to) - arity = h.method(:hyphenate_to).arity - raise ArgumentError, "#{h.inspect} must have exactly two or three arguments." unless [2, 3].include?(arity) - @hyphenator = h - @hyphenator_arity = arity - end - - # Specifies the split mode; used only when #hard_margins is set to - # +true+. Allowable values are: - # [+SPLIT_FIXED+] The word will be split at the number of - # characters needed, with no marking at all. - # repre - # senta - # ion - # [+SPLIT_CONTINUATION+] The word will be split at the number of - # characters needed, with a C-style continuation - # character. If a word is the only item on a - # line and it cannot be split into an - # appropriate size, SPLIT_FIXED will be used. - # repr\ - # esen\ - # tati\ - # on - # [+SPLIT_HYPHENATION+] The word will be split according to the - # hyphenator specified in #hyphenator. If there - # is no #hyphenator specified, works like - # SPLIT_CONTINUATION. The example is using - # TeX::Hyphen. If a word is the only item on a - # line and it cannot be split into an - # appropriate size, SPLIT_CONTINUATION mode will - # be used. - # rep- - # re- - # sen- - # ta- - # tion - # - # *Default*:: Text::Format::SPLIT_FIXED - # Used in:: #format, #paragraphs - attr_reader :split_rules - - # Specifies the split mode; used only when #hard_margins is set to - # +true+. Allowable values are: - # [+SPLIT_FIXED+] The word will be split at the number of - # characters needed, with no marking at all. - # repre - # senta - # ion - # [+SPLIT_CONTINUATION+] The word will be split at the number of - # characters needed, with a C-style continuation - # character. - # repr\ - # esen\ - # tati\ - # on - # [+SPLIT_HYPHENATION+] The word will be split according to the - # hyphenator specified in #hyphenator. If there - # is no #hyphenator specified, works like - # SPLIT_CONTINUATION. The example is using - # TeX::Hyphen as the #hyphenator. - # rep- - # re- - # sen- - # ta- - # tion - # - # These values can be bitwise ORed together (e.g., SPLIT_FIXED | - # SPLIT_CONTINUATION) to provide fallback split methods. In the - # example given, an attempt will be made to split the word using the - # rules of SPLIT_CONTINUATION; if there is not enough room, the word - # will be split with the rules of SPLIT_FIXED. These combinations are - # also available as the following values: - # * +SPLIT_CONTINUATION_FIXED+ - # * +SPLIT_HYPHENATION_FIXED+ - # * +SPLIT_HYPHENATION_CONTINUATION+ - # * +SPLIT_ALL+ - # - # *Default*:: Text::Format::SPLIT_FIXED - # Used in:: #format, #paragraphs - def split_rules=(s) - raise ArgumentError, "Invalid value provided for split_rules." if ((s < SPLIT_FIXED) || (s > SPLIT_ALL)) - @split_rules = s - end - - # Indicates whether sentence terminators should be followed by a single - # space (+false+), or two spaces (+true+). - # - # *Default*:: +false+ - # Used in:: #format, #paragraphs - attr_accessor :extra_space - - # Defines the current abbreviations as an array. This is only used if - # extra_space is turned on. - # - # If one is abbreviating "President" as "Pres." (abbreviations = - # ["Pres"]), then the results of formatting will be as illustrated in - # the table below: - # - # extra_space | include? | !include? - # true | Pres. Lincoln | Pres. Lincoln - # false | Pres. Lincoln | Pres. Lincoln - # - # *Default*:: {} - # Used in:: #format, #paragraphs - attr_accessor :abbreviations - - # Indicates whether the formatting of paragraphs should be done with - # tagged paragraphs. Useful only with #tag_text. - # - # *Default*:: +false+ - # Used in:: #format, #paragraphs - attr_accessor :tag_paragraph - - # The array of text to be placed before each paragraph when - # #tag_paragraph is +true+. When #format() is called, - # only the first element of the array is used. When #paragraphs - # is called, then each entry in the array will be used once, with - # corresponding paragraphs. If the tag elements are exhausted before the - # text is exhausted, then the remaining paragraphs will not be tagged. - # Regardless of indentation settings, a blank line will be inserted - # between all paragraphs when #tag_paragraph is +true+. - # - # *Default*:: [] - # Used in:: #format, #paragraphs - attr_accessor :tag_text - - # Indicates whether or not the non-breaking space feature should be - # used. - # - # *Default*:: +false+ - # Used in:: #format, #paragraphs - attr_accessor :nobreak - - # A hash which holds the regular expressions on which spaces should not - # be broken. The hash is set up such that the key is the first word and - # the value is the second word. - # - # For example, if +nobreak_regex+ contains the following hash: - # - # { '^Mrs?\.$' => '\S+$', '^\S+$' => '^(?:S|J)r\.$'} - # - # Then "Mr. Jones", "Mrs. Jones", and "Jones Jr." would not be broken. - # If this simple matching algorithm indicates that there should not be a - # break at the current end of line, then a backtrack is done until there - # are two words on which line breaking is permitted. If two such words - # are not found, then the end of the line will be broken *regardless*. - # If there is a single word on the current line, then no backtrack is - # done and the word is stuck on the end. - # - # *Default*:: {} - # Used in:: #format, #paragraphs - attr_accessor :nobreak_regex - - # Indicates the number of spaces that a single tab represents. - # - # *Default*:: 8 - # Used in:: #expand, #unexpand, - # #paragraphs - attr_reader :tabstop - - # Indicates the number of spaces that a single tab represents. - # - # *Default*:: 8 - # Used in:: #expand, #unexpand, - # #paragraphs - def tabstop=(t) - @tabstop = posint(t) - end - - # Specifies the format style. Allowable values are: - # [+LEFT_ALIGN+] Left justified, ragged right. - # |A paragraph that is| - # |left aligned.| - # [+RIGHT_ALIGN+] Right justified, ragged left. - # |A paragraph that is| - # | right aligned.| - # [+RIGHT_FILL+] Left justified, right ragged, filled to width by - # spaces. (Essentially the same as +LEFT_ALIGN+ except - # that lines are padded on the right.) - # |A paragraph that is| - # |left aligned. | - # [+JUSTIFY+] Fully justified, words filled to width by spaces, - # except the last line. - # |A paragraph that| - # |is justified.| - # - # *Default*:: Text::Format::LEFT_ALIGN - # Used in:: #format, #paragraphs - attr_reader :format_style - - # Specifies the format style. Allowable values are: - # [+LEFT_ALIGN+] Left justified, ragged right. - # |A paragraph that is| - # |left aligned.| - # [+RIGHT_ALIGN+] Right justified, ragged left. - # |A paragraph that is| - # | right aligned.| - # [+RIGHT_FILL+] Left justified, right ragged, filled to width by - # spaces. (Essentially the same as +LEFT_ALIGN+ except - # that lines are padded on the right.) - # |A paragraph that is| - # |left aligned. | - # [+JUSTIFY+] Fully justified, words filled to width by spaces. - # |A paragraph that| - # |is justified.| - # - # *Default*:: Text::Format::LEFT_ALIGN - # Used in:: #format, #paragraphs - def format_style=(fs) - raise ArgumentError, "Invalid value provided for format_style." if ((fs < LEFT_ALIGN) || (fs > JUSTIFY)) - @format_style = fs - end - - # Indicates that the format style is left alignment. - # - # *Default*:: +true+ - # Used in:: #format, #paragraphs - def left_align? - return @format_style == LEFT_ALIGN - end - - # Indicates that the format style is right alignment. - # - # *Default*:: +false+ - # Used in:: #format, #paragraphs - def right_align? - return @format_style == RIGHT_ALIGN - end - - # Indicates that the format style is right fill. - # - # *Default*:: +false+ - # Used in:: #format, #paragraphs - def right_fill? - return @format_style == RIGHT_FILL - end - - # Indicates that the format style is full justification. - # - # *Default*:: +false+ - # Used in:: #format, #paragraphs - def justify? - return @format_style == JUSTIFY - end - - # The default implementation of #hyphenate_to implements - # SPLIT_CONTINUATION. - def hyphenate_to(word, size) - [word[0 .. (size - 2)] + "\\", word[(size - 1) .. -1]] - end - - private - def __do_split_word(word, size) #:nodoc: - [word[0 .. (size - 1)], word[size .. -1]] - end - - def __format(to_wrap) #:nodoc: - words = to_wrap.split(/\s+/).compact - words.shift if words[0].nil? or words[0].empty? - to_wrap = [] - - abbrev = false - width = @columns - @first_indent - @left_margin - @right_margin - indent_str = ' ' * @first_indent - first_line = true - line = words.shift - abbrev = __is_abbrev(line) unless line.nil? || line.empty? - - while w = words.shift - if (w.size + line.size < (width - 1)) || - ((line !~ LEQ_RE || abbrev) && (w.size + line.size < width)) - line << " " if (line =~ LEQ_RE) && (not abbrev) - line << " #{w}" - else - line, w = __do_break(line, w) if @nobreak - line, w = __do_hyphenate(line, w, width) if @hard_margins - if w.index(/\s+/) - w, *w2 = w.split(/\s+/) - words.unshift(w2) - words.flatten! - end - to_wrap << __make_line(line, indent_str, width, w.nil?) unless line.nil? - if first_line - first_line = false - width = @columns - @body_indent - @left_margin - @right_margin - indent_str = ' ' * @body_indent - end - line = w - end - - abbrev = __is_abbrev(w) unless w.nil? - end - - loop do - break if line.nil? or line.empty? - line, w = __do_hyphenate(line, w, width) if @hard_margins - to_wrap << __make_line(line, indent_str, width, w.nil?) - line = w - end - - if (@tag_paragraph && (to_wrap.size > 0)) then - clr = %r{`(\w+)'}.match([caller(1)].flatten[0])[1] - clr = "" if clr.nil? - - if ((not @tag_text[0].nil?) && (@tag_cur.size < 1) && - (clr != "__paragraphs")) then - @tag_cur = @tag_text[0] - end - - fchar = /(\S)/.match(to_wrap[0])[1] - white = to_wrap[0].index(fchar) - if ((white - @left_margin - 1) > @tag_cur.size) then - white = @tag_cur.size + @left_margin - to_wrap[0].gsub!(/^ {#{white}}/, "#{' ' * @left_margin}#{@tag_cur}") - else - to_wrap.unshift("#{' ' * @left_margin}#{@tag_cur}\n") - end - end - to_wrap.join('') - end - - # format lines in text into paragraphs with each element of @wrap a - # paragraph; uses Text::Format.format for the formatting - def __paragraphs(to_wrap) #:nodoc: - if ((@first_indent == @body_indent) || @tag_paragraph) then - p_end = "\n" - else - p_end = '' - end - - cnt = 0 - ret = [] - to_wrap.each do |tw| - @tag_cur = @tag_text[cnt] if @tag_paragraph - @tag_cur = '' if @tag_cur.nil? - line = __format(tw) - ret << "#{line}#{p_end}" if (not line.nil?) && (line.size > 0) - cnt += 1 - end - - ret[-1].chomp! unless ret.empty? - ret.join('') - end - - # center text using spaces on left side to pad it out empty lines - # are preserved - def __center(to_center) #:nodoc: - tabs = 0 - width = @columns - @left_margin - @right_margin - centered = [] - to_center.each do |tc| - s = tc.strip - tabs = s.count("\t") - tabs = 0 if tabs.nil? - ct = ((width - s.size - (tabs * @tabstop) + tabs) / 2) - ct = (width - @left_margin - @right_margin) - ct - centered << "#{s.rjust(ct)}\n" - end - centered.join('') - end - - # expand tabs to spaces should be similar to Text::Tabs::expand - def __expand(to_expand) #:nodoc: - expanded = [] - to_expand.split("\n").each { |te| expanded << te.gsub(/\t/, ' ' * @tabstop) } - expanded.join('') - end - - def __unexpand(to_unexpand) #:nodoc: - unexpanded = [] - to_unexpand.split("\n").each { |tu| unexpanded << tu.gsub(/ {#{@tabstop}}/, "\t") } - unexpanded.join('') - end - - def __is_abbrev(word) #:nodoc: - # remove period if there is one. - w = word.gsub(/\.$/, '') unless word.nil? - return true if (!@extra_space || ABBREV.include?(w) || @abbreviations.include?(w)) - false - end - - def __make_line(line, indent, width, last = false) #:nodoc: - lmargin = " " * @left_margin - fill = " " * (width - line.size) if right_fill? && (line.size <= width) - - if (justify? && ((not line.nil?) && (not line.empty?)) && line =~ /\S+\s+\S+/ && !last) - spaces = width - line.size - words = line.split(/(\s+)/) - ws = spaces / (words.size / 2) - spaces = spaces % (words.size / 2) if ws > 0 - words.reverse.each do |rw| - next if (rw =~ /^\S/) - rw.sub!(/^/, " " * ws) - next unless (spaces > 0) - rw.sub!(/^/, " ") - spaces -= 1 - end - line = words.join('') - end - line = "#{lmargin}#{indent}#{line}#{fill}\n" unless line.nil? - if right_align? && (not line.nil?) - line.sub(/^/, " " * (@columns - @right_margin - (line.size - 1))) - else - line - end - end - - def __do_hyphenate(line, next_line, width) #:nodoc: - rline = line.dup rescue line - rnext = next_line.dup rescue next_line - loop do - if rline.size == width - break - elsif rline.size > width - words = rline.strip.split(/\s+/) - word = words[-1].dup - size = width - rline.size + word.size - if (size <= 0) - words[-1] = nil - rline = words.join(' ').strip - rnext = "#{word} #{rnext}".strip - next - end - - first = rest = nil - - if ((@split_rules & SPLIT_HYPHENATION) != 0) - if @hyphenator_arity == 2 - first, rest = @hyphenator.hyphenate_to(word, size) - else - first, rest = @hyphenator.hyphenate_to(word, size, self) - end - end - - if ((@split_rules & SPLIT_CONTINUATION) != 0) and first.nil? - first, rest = self.hyphenate_to(word, size) - end - - if ((@split_rules & SPLIT_FIXED) != 0) and first.nil? - first.nil? or @split_rules == SPLIT_FIXED - first, rest = __do_split_word(word, size) - end - - if first.nil? - words[-1] = nil - rest = word - else - words[-1] = first - @split_words << SplitWord.new(word, first, rest) - end - rline = words.join(' ').strip - rnext = "#{rest} #{rnext}".strip - break - else - break if rnext.nil? or rnext.empty? or rline.nil? or rline.empty? - words = rnext.split(/\s+/) - word = words.shift - size = width - rline.size - 1 - - if (size <= 0) - rnext = "#{word} #{words.join(' ')}".strip - break - end - - first = rest = nil - - if ((@split_rules & SPLIT_HYPHENATION) != 0) - if @hyphenator_arity == 2 - first, rest = @hyphenator.hyphenate_to(word, size) - else - first, rest = @hyphenator.hyphenate_to(word, size, self) - end - end - - first, rest = self.hyphenate_to(word, size) if ((@split_rules & SPLIT_CONTINUATION) != 0) and first.nil? - - first, rest = __do_split_word(word, size) if ((@split_rules & SPLIT_FIXED) != 0) and first.nil? - - if (rline.size + (first ? first.size : 0)) < width - @split_words << SplitWord.new(word, first, rest) - rline = "#{rline} #{first}".strip - rnext = "#{rest} #{words.join(' ')}".strip - end - break - end - end - [rline, rnext] - end - - def __do_break(line, next_line) #:nodoc: - no_brk = false - words = [] - words = line.split(/\s+/) unless line.nil? - last_word = words[-1] - - @nobreak_regex.each { |k, v| no_brk = ((last_word =~ /#{k}/) and (next_line =~ /#{v}/)) } - - if no_brk && words.size > 1 - i = words.size - while i > 0 - no_brk = false - @nobreak_regex.each { |k, v| no_brk = ((words[i + 1] =~ /#{k}/) && (words[i] =~ /#{v}/)) } - i -= 1 - break if not no_brk - end - if i > 0 - l = brk_re(i).match(line) - line.sub!(brk_re(i), l[1]) - next_line = "#{l[2]} #{next_line}" - line.sub!(/\s+$/, '') - end - end - [line, next_line] - end - - def __create(arg = nil, &block) #:nodoc: - # Format::Text.new(text-to-wrap) - @text = arg unless arg.nil? - # Defaults - @columns = 72 - @tabstop = 8 - @first_indent = 4 - @body_indent = 0 - @format_style = LEFT_ALIGN - @left_margin = 0 - @right_margin = 0 - @extra_space = false - @text = Array.new if @text.nil? - @tag_paragraph = false - @tag_text = Array.new - @tag_cur = "" - @abbreviations = Array.new - @nobreak = false - @nobreak_regex = Hash.new - @split_words = Array.new - @hard_margins = false - @split_rules = SPLIT_FIXED - @hyphenator = self - @hyphenator_arity = self.method(:hyphenate_to).arity - - instance_eval(&block) unless block.nil? - end - - public - # Formats text into a nice paragraph format. The text is separated - # into words and then reassembled a word at a time using the settings - # of this Format object. If a word is larger than the number of - # columns available for formatting, then that word will appear on the - # line by itself. - # - # If +to_wrap+ is +nil+, then the value of #text will be - # worked on. - def format(to_wrap = nil) - to_wrap = @text if to_wrap.nil? - if to_wrap.class == Array - __format(to_wrap[0]) - else - __format(to_wrap) - end - end - - # Considers each element of text (provided or internal) as a paragraph. - # If #first_indent is the same as #body_indent, then - # paragraphs will be separated by a single empty line in the result; - # otherwise, the paragraphs will follow immediately after each other. - # Uses #format to do the heavy lifting. - def paragraphs(to_wrap = nil) - to_wrap = @text if to_wrap.nil? - __paragraphs([to_wrap].flatten) - end - - # Centers the text, preserving empty lines and tabs. - def center(to_center = nil) - to_center = @text if to_center.nil? - __center([to_center].flatten) - end - - # Replaces all tab characters in the text with #tabstop spaces. - def expand(to_expand = nil) - to_expand = @text if to_expand.nil? - if to_expand.class == Array - to_expand.collect { |te| __expand(te) } - else - __expand(to_expand) - end - end - - # Replaces all occurrences of #tabstop consecutive spaces - # with a tab character. - def unexpand(to_unexpand = nil) - to_unexpand = @text if to_unexpand.nil? - if to_unexpand.class == Array - to_unexpand.collect { |te| v << __unexpand(te) } - else - __unexpand(to_unexpand) - end - end - - # This constructor takes advantage of a technique for Ruby object - # construction introduced by Andy Hunt and Dave Thomas (see reference), - # where optional values are set using commands in a block. - # - # Text::Format.new { - # columns = 72 - # left_margin = 0 - # right_margin = 0 - # first_indent = 4 - # body_indent = 0 - # format_style = Text::Format::LEFT_ALIGN - # extra_space = false - # abbreviations = {} - # tag_paragraph = false - # tag_text = [] - # nobreak = false - # nobreak_regex = {} - # tabstop = 8 - # text = nil - # } - # - # As shown above, +arg+ is optional. If +arg+ is specified and is a - # +String+, then arg is used as the default value of #text. - # Alternately, an existing Text::Format object can be used or a Hash can - # be used. With all forms, a block can be specified. - # - # *Reference*:: "Object Construction and Blocks" - # - # - def initialize(arg = nil, &block) - case arg - when Text::Format - __create(arg.text) do - @columns = arg.columns - @tabstop = arg.tabstop - @first_indent = arg.first_indent - @body_indent = arg.body_indent - @format_style = arg.format_style - @left_margin = arg.left_margin - @right_margin = arg.right_margin - @extra_space = arg.extra_space - @tag_paragraph = arg.tag_paragraph - @tag_text = arg.tag_text - @abbreviations = arg.abbreviations - @nobreak = arg.nobreak - @nobreak_regex = arg.nobreak_regex - @text = arg.text - @hard_margins = arg.hard_margins - @split_words = arg.split_words - @split_rules = arg.split_rules - @hyphenator = arg.hyphenator - end - instance_eval(&block) unless block.nil? - when Hash - __create do - @columns = arg[:columns] || arg['columns'] || @columns - @tabstop = arg[:tabstop] || arg['tabstop'] || @tabstop - @first_indent = arg[:first_indent] || arg['first_indent'] || @first_indent - @body_indent = arg[:body_indent] || arg['body_indent'] || @body_indent - @format_style = arg[:format_style] || arg['format_style'] || @format_style - @left_margin = arg[:left_margin] || arg['left_margin'] || @left_margin - @right_margin = arg[:right_margin] || arg['right_margin'] || @right_margin - @extra_space = arg[:extra_space] || arg['extra_space'] || @extra_space - @text = arg[:text] || arg['text'] || @text - @tag_paragraph = arg[:tag_paragraph] || arg['tag_paragraph'] || @tag_paragraph - @tag_text = arg[:tag_text] || arg['tag_text'] || @tag_text - @abbreviations = arg[:abbreviations] || arg['abbreviations'] || @abbreviations - @nobreak = arg[:nobreak] || arg['nobreak'] || @nobreak - @nobreak_regex = arg[:nobreak_regex] || arg['nobreak_regex'] || @nobreak_regex - @hard_margins = arg[:hard_margins] || arg['hard_margins'] || @hard_margins - @split_rules = arg[:split_rules] || arg['split_rules'] || @split_rules - @hyphenator = arg[:hyphenator] || arg['hyphenator'] || @hyphenator - end - instance_eval(&block) unless block.nil? - when String - __create(arg, &block) - when NilClass - __create(&block) - else - raise TypeError - end - end - end -end - -if __FILE__ == $0 - require 'test/unit' - - class TestText__Format < Test::Unit::TestCase #:nodoc: - attr_accessor :format_o - - GETTYSBURG = <<-'EOS' - Four score and seven years ago our fathers brought forth on this - continent a new nation, conceived in liberty and dedicated to the - proposition that all men are created equal. Now we are engaged in - a great civil war, testing whether that nation or any nation so - conceived and so dedicated can long endure. We are met on a great - battlefield of that war. We have come to dedicate a portion of - that field as a final resting-place for those who here gave their - lives that that nation might live. It is altogether fitting and - proper that we should do this. But in a larger sense, we cannot - dedicate, we cannot consecrate, we cannot hallow this ground. - The brave men, living and dead who struggled here have consecrated - it far above our poor power to add or detract. The world will - little note nor long remember what we say here, but it can never - forget what they did here. It is for us the living rather to be - dedicated here to the unfinished work which they who fought here - have thus far so nobly advanced. It is rather for us to be here - dedicated to the great task remaining before us--that from these - honored dead we take increased devotion to that cause for which - they gave the last full measure of devotion--that we here highly - resolve that these dead shall not have died in vain, that this - nation under God shall have a new birth of freedom, and that - government of the people, by the people, for the people shall - not perish from the earth. - - -- Pres. Abraham Lincoln, 19 November 1863 - EOS - - FIVE_COL = "Four \nscore\nand s\neven \nyears\nago o\nur fa\nthers\nbroug\nht fo\nrth o\nn thi\ns con\ntinen\nt a n\new na\ntion,\nconce\nived \nin li\nberty\nand d\nedica\nted t\no the\npropo\nsitio\nn tha\nt all\nmen a\nre cr\neated\nequal\n. Now\nwe ar\ne eng\naged \nin a \ngreat\ncivil\nwar, \ntesti\nng wh\nether\nthat \nnatio\nn or \nany n\nation\nso co\nnceiv\ned an\nd so \ndedic\nated \ncan l\nong e\nndure\n. We \nare m\net on\na gre\nat ba\nttlef\nield \nof th\nat wa\nr. We\nhave \ncome \nto de\ndicat\ne a p\nortio\nn of \nthat \nfield\nas a \nfinal\nresti\nng-pl\nace f\nor th\nose w\nho he\nre ga\nve th\neir l\nives \nthat \nthat \nnatio\nn mig\nht li\nve. I\nt is \naltog\nether\nfitti\nng an\nd pro\nper t\nhat w\ne sho\nuld d\no thi\ns. Bu\nt in \na lar\nger s\nense,\nwe ca\nnnot \ndedic\nate, \nwe ca\nnnot \nconse\ncrate\n, we \ncanno\nt hal\nlow t\nhis g\nround\n. The\nbrave\nmen, \nlivin\ng and\ndead \nwho s\ntrugg\nled h\nere h\nave c\nonsec\nrated\nit fa\nr abo\nve ou\nr poo\nr pow\ner to\nadd o\nr det\nract.\nThe w\norld \nwill \nlittl\ne not\ne nor\nlong \nremem\nber w\nhat w\ne say\nhere,\nbut i\nt can\nnever\nforge\nt wha\nt the\ny did\nhere.\nIt is\nfor u\ns the\nlivin\ng rat\nher t\no be \ndedic\nated \nhere \nto th\ne unf\ninish\ned wo\nrk wh\nich t\nhey w\nho fo\nught \nhere \nhave \nthus \nfar s\no nob\nly ad\nvance\nd. It\nis ra\nther \nfor u\ns to \nbe he\nre de\ndicat\ned to\nthe g\nreat \ntask \nremai\nning \nbefor\ne us-\n-that\nfrom \nthese\nhonor\ned de\nad we\ntake \nincre\nased \ndevot\nion t\no tha\nt cau\nse fo\nr whi\nch th\ney ga\nve th\ne las\nt ful\nl mea\nsure \nof de\nvotio\nn--th\nat we\nhere \nhighl\ny res\nolve \nthat \nthese\ndead \nshall\nnot h\nave d\nied i\nn vai\nn, th\nat th\nis na\ntion \nunder\nGod s\nhall \nhave \na new\nbirth\nof fr\needom\n, and\nthat \ngover\nnment\nof th\ne peo\nple, \nby th\ne peo\nple, \nfor t\nhe pe\nople \nshall\nnot p\nerish\nfrom \nthe e\narth.\n-- Pr\nes. A\nbraha\nm Lin\ncoln,\n19 No\nvembe\nr 186\n3 \n" - - FIVE_CNT = "Four \nscore\nand \nseven\nyears\nago \nour \nfath\\\ners \nbrou\\\nght \nforth\non t\\\nhis \ncont\\\ninent\na new\nnati\\\non, \nconc\\\neived\nin l\\\niber\\\nty a\\\nnd d\\\nedic\\\nated \nto t\\\nhe p\\\nropo\\\nsiti\\\non t\\\nhat \nall \nmen \nare \ncrea\\\nted \nequa\\\nl. N\\\now we\nare \nenga\\\nged \nin a \ngreat\ncivil\nwar, \ntest\\\ning \nwhet\\\nher \nthat \nnati\\\non or\nany \nnati\\\non so\nconc\\\neived\nand \nso d\\\nedic\\\nated \ncan \nlong \nendu\\\nre. \nWe a\\\nre m\\\net on\na gr\\\neat \nbatt\\\nlefi\\\neld \nof t\\\nhat \nwar. \nWe h\\\nave \ncome \nto d\\\nedic\\\nate a\nport\\\nion \nof t\\\nhat \nfield\nas a \nfinal\nrest\\\ning-\\\nplace\nfor \nthose\nwho \nhere \ngave \ntheir\nlives\nthat \nthat \nnati\\\non m\\\night \nlive.\nIt is\nalto\\\ngeth\\\ner f\\\nitti\\\nng a\\\nnd p\\\nroper\nthat \nwe s\\\nhould\ndo t\\\nhis. \nBut \nin a \nlarg\\\ner s\\\nense,\nwe c\\\nannot\ndedi\\\ncate,\nwe c\\\nannot\ncons\\\necra\\\nte, \nwe c\\\nannot\nhall\\\now t\\\nhis \ngrou\\\nnd. \nThe \nbrave\nmen, \nlivi\\\nng a\\\nnd d\\\nead \nwho \nstru\\\nggled\nhere \nhave \ncons\\\necra\\\nted \nit f\\\nar a\\\nbove \nour \npoor \npower\nto a\\\ndd or\ndetr\\\nact. \nThe \nworld\nwill \nlitt\\\nle n\\\note \nnor \nlong \nreme\\\nmber \nwhat \nwe s\\\nay h\\\nere, \nbut \nit c\\\nan n\\\never \nforg\\\net w\\\nhat \nthey \ndid \nhere.\nIt is\nfor \nus t\\\nhe l\\\niving\nrath\\\ner to\nbe d\\\nedic\\\nated \nhere \nto t\\\nhe u\\\nnfin\\\nished\nwork \nwhich\nthey \nwho \nfoug\\\nht h\\\nere \nhave \nthus \nfar \nso n\\\nobly \nadva\\\nnced.\nIt is\nrath\\\ner f\\\nor us\nto be\nhere \ndedi\\\ncated\nto t\\\nhe g\\\nreat \ntask \nrema\\\nining\nbefo\\\nre u\\\ns--t\\\nhat \nfrom \nthese\nhono\\\nred \ndead \nwe t\\\nake \nincr\\\neased\ndevo\\\ntion \nto t\\\nhat \ncause\nfor \nwhich\nthey \ngave \nthe \nlast \nfull \nmeas\\\nure \nof d\\\nevot\\\nion-\\\n-that\nwe h\\\nere \nhigh\\\nly r\\\nesol\\\nve t\\\nhat \nthese\ndead \nshall\nnot \nhave \ndied \nin v\\\nain, \nthat \nthis \nnati\\\non u\\\nnder \nGod \nshall\nhave \na new\nbirth\nof f\\\nreed\\\nom, \nand \nthat \ngove\\\nrnme\\\nnt of\nthe \npeop\\\nle, \nby t\\\nhe p\\\neopl\\\ne, f\\\nor t\\\nhe p\\\neople\nshall\nnot \nperi\\\nsh f\\\nrom \nthe \neart\\\nh. --\nPres.\nAbra\\\nham \nLinc\\\noln, \n19 N\\\novem\\\nber \n1863 \n" - - # Tests both abbreviations and abbreviations= - def test_abbreviations - abbr = [" Pres. Abraham Lincoln\n", " Pres. Abraham Lincoln\n"] - assert_nothing_raised { @format_o = Text::Format.new } - assert_equal([], @format_o.abbreviations) - assert_nothing_raised { @format_o.abbreviations = [ 'foo', 'bar' ] } - assert_equal([ 'foo', 'bar' ], @format_o.abbreviations) - assert_equal(abbr[0], @format_o.format(abbr[0])) - assert_nothing_raised { @format_o.extra_space = true } - assert_equal(abbr[1], @format_o.format(abbr[0])) - assert_nothing_raised { @format_o.abbreviations = [ "Pres" ] } - assert_equal([ "Pres" ], @format_o.abbreviations) - assert_equal(abbr[0], @format_o.format(abbr[0])) - assert_nothing_raised { @format_o.extra_space = false } - assert_equal(abbr[0], @format_o.format(abbr[0])) - end - - # Tests both body_indent and body_indent= - def test_body_indent - assert_nothing_raised { @format_o = Text::Format.new } - assert_equal(0, @format_o.body_indent) - assert_nothing_raised { @format_o.body_indent = 7 } - assert_equal(7, @format_o.body_indent) - assert_nothing_raised { @format_o.body_indent = -3 } - assert_equal(3, @format_o.body_indent) - assert_nothing_raised { @format_o.body_indent = "9" } - assert_equal(9, @format_o.body_indent) - assert_nothing_raised { @format_o.body_indent = "-2" } - assert_equal(2, @format_o.body_indent) - assert_match(/^ [^ ]/, @format_o.format(GETTYSBURG).split("\n")[1]) - end - - # Tests both columns and columns= - def test_columns - assert_nothing_raised { @format_o = Text::Format.new } - assert_equal(72, @format_o.columns) - assert_nothing_raised { @format_o.columns = 7 } - assert_equal(7, @format_o.columns) - assert_nothing_raised { @format_o.columns = -3 } - assert_equal(3, @format_o.columns) - assert_nothing_raised { @format_o.columns = "9" } - assert_equal(9, @format_o.columns) - assert_nothing_raised { @format_o.columns = "-2" } - assert_equal(2, @format_o.columns) - assert_nothing_raised { @format_o.columns = 40 } - assert_equal(40, @format_o.columns) - assert_match(/this continent$/, - @format_o.format(GETTYSBURG).split("\n")[1]) - end - - # Tests both extra_space and extra_space= - def test_extra_space - assert_nothing_raised { @format_o = Text::Format.new } - assert(!@format_o.extra_space) - assert_nothing_raised { @format_o.extra_space = true } - assert(@format_o.extra_space) - # The behaviour of extra_space is tested in test_abbreviations. There - # is no need to reproduce it here. - end - - # Tests both first_indent and first_indent= - def test_first_indent - assert_nothing_raised { @format_o = Text::Format.new } - assert_equal(4, @format_o.first_indent) - assert_nothing_raised { @format_o.first_indent = 7 } - assert_equal(7, @format_o.first_indent) - assert_nothing_raised { @format_o.first_indent = -3 } - assert_equal(3, @format_o.first_indent) - assert_nothing_raised { @format_o.first_indent = "9" } - assert_equal(9, @format_o.first_indent) - assert_nothing_raised { @format_o.first_indent = "-2" } - assert_equal(2, @format_o.first_indent) - assert_match(/^ [^ ]/, @format_o.format(GETTYSBURG).split("\n")[0]) - end - - def test_format_style - assert_nothing_raised { @format_o = Text::Format.new } - assert_equal(Text::Format::LEFT_ALIGN, @format_o.format_style) - assert_match(/^November 1863$/, - @format_o.format(GETTYSBURG).split("\n")[-1]) - assert_nothing_raised { - @format_o.format_style = Text::Format::RIGHT_ALIGN - } - assert_equal(Text::Format::RIGHT_ALIGN, @format_o.format_style) - assert_match(/^ +November 1863$/, - @format_o.format(GETTYSBURG).split("\n")[-1]) - assert_nothing_raised { - @format_o.format_style = Text::Format::RIGHT_FILL - } - assert_equal(Text::Format::RIGHT_FILL, @format_o.format_style) - assert_match(/^November 1863 +$/, - @format_o.format(GETTYSBURG).split("\n")[-1]) - assert_nothing_raised { @format_o.format_style = Text::Format::JUSTIFY } - assert_equal(Text::Format::JUSTIFY, @format_o.format_style) - assert_match(/^of freedom, and that government of the people, by the people, for the$/, - @format_o.format(GETTYSBURG).split("\n")[-3]) - assert_raises(ArgumentError) { @format_o.format_style = 33 } - end - - def test_tag_paragraph - assert_nothing_raised { @format_o = Text::Format.new } - assert(!@format_o.tag_paragraph) - assert_nothing_raised { @format_o.tag_paragraph = true } - assert(@format_o.tag_paragraph) - assert_not_equal(@format_o.paragraphs([GETTYSBURG, GETTYSBURG]), - Text::Format.new.paragraphs([GETTYSBURG, GETTYSBURG])) - end - - def test_tag_text - assert_nothing_raised { @format_o = Text::Format.new } - assert_equal([], @format_o.tag_text) - assert_equal(@format_o.format(GETTYSBURG), - Text::Format.new.format(GETTYSBURG)) - assert_nothing_raised { - @format_o.tag_paragraph = true - @format_o.tag_text = ["Gettysburg Address", "---"] - } - assert_not_equal(@format_o.format(GETTYSBURG), - Text::Format.new.format(GETTYSBURG)) - assert_not_equal(@format_o.paragraphs([GETTYSBURG, GETTYSBURG]), - Text::Format.new.paragraphs([GETTYSBURG, GETTYSBURG])) - assert_not_equal(@format_o.paragraphs([GETTYSBURG, GETTYSBURG, - GETTYSBURG]), - Text::Format.new.paragraphs([GETTYSBURG, GETTYSBURG, - GETTYSBURG])) - end - - def test_justify? - assert_nothing_raised { @format_o = Text::Format.new } - assert(!@format_o.justify?) - assert_nothing_raised { - @format_o.format_style = Text::Format::RIGHT_ALIGN - } - assert(!@format_o.justify?) - assert_nothing_raised { - @format_o.format_style = Text::Format::RIGHT_FILL - } - assert(!@format_o.justify?) - assert_nothing_raised { - @format_o.format_style = Text::Format::JUSTIFY - } - assert(@format_o.justify?) - # The format testing is done in test_format_style - end - - def test_left_align? - assert_nothing_raised { @format_o = Text::Format.new } - assert(@format_o.left_align?) - assert_nothing_raised { - @format_o.format_style = Text::Format::RIGHT_ALIGN - } - assert(!@format_o.left_align?) - assert_nothing_raised { - @format_o.format_style = Text::Format::RIGHT_FILL - } - assert(!@format_o.left_align?) - assert_nothing_raised { @format_o.format_style = Text::Format::JUSTIFY } - assert(!@format_o.left_align?) - # The format testing is done in test_format_style - end - - def test_left_margin - assert_nothing_raised { @format_o = Text::Format.new } - assert_equal(0, @format_o.left_margin) - assert_nothing_raised { @format_o.left_margin = -3 } - assert_equal(3, @format_o.left_margin) - assert_nothing_raised { @format_o.left_margin = "9" } - assert_equal(9, @format_o.left_margin) - assert_nothing_raised { @format_o.left_margin = "-2" } - assert_equal(2, @format_o.left_margin) - assert_nothing_raised { @format_o.left_margin = 7 } - assert_equal(7, @format_o.left_margin) - assert_nothing_raised { - ft = @format_o.format(GETTYSBURG).split("\n") - assert_match(/^ {11}Four score/, ft[0]) - assert_match(/^ {7}November/, ft[-1]) - } - end - - def test_hard_margins - assert_nothing_raised { @format_o = Text::Format.new } - assert(!@format_o.hard_margins) - assert_nothing_raised { - @format_o.hard_margins = true - @format_o.columns = 5 - @format_o.first_indent = 0 - @format_o.format_style = Text::Format::RIGHT_FILL - } - assert(@format_o.hard_margins) - assert_equal(FIVE_COL, @format_o.format(GETTYSBURG)) - assert_nothing_raised { - @format_o.split_rules |= Text::Format::SPLIT_CONTINUATION - assert_equal(Text::Format::SPLIT_CONTINUATION_FIXED, - @format_o.split_rules) - } - assert_equal(FIVE_CNT, @format_o.format(GETTYSBURG)) - end - - # Tests both nobreak and nobreak_regex, since one is only useful - # with the other. - def test_nobreak - assert_nothing_raised { @format_o = Text::Format.new } - assert(!@format_o.nobreak) - assert(@format_o.nobreak_regex.empty?) - assert_nothing_raised { - @format_o.nobreak = true - @format_o.nobreak_regex = { '^this$' => '^continent$' } - @format_o.columns = 77 - } - assert(@format_o.nobreak) - assert_equal({ '^this$' => '^continent$' }, @format_o.nobreak_regex) - assert_match(/^this continent/, - @format_o.format(GETTYSBURG).split("\n")[1]) - end - - def test_right_align? - assert_nothing_raised { @format_o = Text::Format.new } - assert(!@format_o.right_align?) - assert_nothing_raised { - @format_o.format_style = Text::Format::RIGHT_ALIGN - } - assert(@format_o.right_align?) - assert_nothing_raised { - @format_o.format_style = Text::Format::RIGHT_FILL - } - assert(!@format_o.right_align?) - assert_nothing_raised { @format_o.format_style = Text::Format::JUSTIFY } - assert(!@format_o.right_align?) - # The format testing is done in test_format_style - end - - def test_right_fill? - assert_nothing_raised { @format_o = Text::Format.new } - assert(!@format_o.right_fill?) - assert_nothing_raised { - @format_o.format_style = Text::Format::RIGHT_ALIGN - } - assert(!@format_o.right_fill?) - assert_nothing_raised { - @format_o.format_style = Text::Format::RIGHT_FILL - } - assert(@format_o.right_fill?) - assert_nothing_raised { - @format_o.format_style = Text::Format::JUSTIFY - } - assert(!@format_o.right_fill?) - # The format testing is done in test_format_style - end - - def test_right_margin - assert_nothing_raised { @format_o = Text::Format.new } - assert_equal(0, @format_o.right_margin) - assert_nothing_raised { @format_o.right_margin = -3 } - assert_equal(3, @format_o.right_margin) - assert_nothing_raised { @format_o.right_margin = "9" } - assert_equal(9, @format_o.right_margin) - assert_nothing_raised { @format_o.right_margin = "-2" } - assert_equal(2, @format_o.right_margin) - assert_nothing_raised { @format_o.right_margin = 7 } - assert_equal(7, @format_o.right_margin) - assert_nothing_raised { - ft = @format_o.format(GETTYSBURG).split("\n") - assert_match(/^ {4}Four score.*forth on$/, ft[0]) - assert_match(/^November/, ft[-1]) - } - end - - def test_tabstop - assert_nothing_raised { @format_o = Text::Format.new } - assert_equal(8, @format_o.tabstop) - assert_nothing_raised { @format_o.tabstop = 7 } - assert_equal(7, @format_o.tabstop) - assert_nothing_raised { @format_o.tabstop = -3 } - assert_equal(3, @format_o.tabstop) - assert_nothing_raised { @format_o.tabstop = "9" } - assert_equal(9, @format_o.tabstop) - assert_nothing_raised { @format_o.tabstop = "-2" } - assert_equal(2, @format_o.tabstop) - end - - def test_text - assert_nothing_raised { @format_o = Text::Format.new } - assert_equal([], @format_o.text) - assert_nothing_raised { @format_o.text = "Test Text" } - assert_equal("Test Text", @format_o.text) - assert_nothing_raised { @format_o.text = ["Line 1", "Line 2"] } - assert_equal(["Line 1", "Line 2"], @format_o.text) - end - - def test_s_new - # new(NilClass) { block } - assert_nothing_raised do - @format_o = Text::Format.new { - self.text = "Test 1, 2, 3" - } - end - assert_equal("Test 1, 2, 3", @format_o.text) - - # new(Hash Symbols) - assert_nothing_raised { @format_o = Text::Format.new(:columns => 72) } - assert_equal(72, @format_o.columns) - - # new(Hash String) - assert_nothing_raised { @format_o = Text::Format.new('columns' => 72) } - assert_equal(72, @format_o.columns) - - # new(Hash) { block } - assert_nothing_raised do - @format_o = Text::Format.new('columns' => 80) { - self.text = "Test 4, 5, 6" - } - end - assert_equal("Test 4, 5, 6", @format_o.text) - assert_equal(80, @format_o.columns) - - # new(Text::Format) - assert_nothing_raised do - fo = Text::Format.new(@format_o) - assert(fo == @format_o) - end - - # new(Text::Format) { block } - assert_nothing_raised do - fo = Text::Format.new(@format_o) { self.columns = 79 } - assert(fo != @format_o) - end - - # new(String) - assert_nothing_raised { @format_o = Text::Format.new("Test A, B, C") } - assert_equal("Test A, B, C", @format_o.text) - - # new(String) { block } - assert_nothing_raised do - @format_o = Text::Format.new("Test X, Y, Z") { self.columns = -5 } - end - assert_equal("Test X, Y, Z", @format_o.text) - assert_equal(5, @format_o.columns) - end - - def test_center - assert_nothing_raised { @format_o = Text::Format.new } - assert_nothing_raised do - ct = @format_o.center(GETTYSBURG.split("\n")).split("\n") - assert_match(/^ Four score and seven years ago our fathers brought forth on this/, ct[0]) - assert_match(/^ not perish from the earth./, ct[-3]) - end - end - - def test_expand - assert_nothing_raised { @format_o = Text::Format.new } - assert_equal(" ", @format_o.expand("\t ")) - assert_nothing_raised { @format_o.tabstop = 4 } - assert_equal(" ", @format_o.expand("\t ")) - end - - def test_unexpand - assert_nothing_raised { @format_o = Text::Format.new } - assert_equal("\t ", @format_o.unexpand(" ")) - assert_nothing_raised { @format_o.tabstop = 4 } - assert_equal("\t ", @format_o.unexpand(" ")) - end - - def test_space_only - assert_equal("", Text::Format.new.format(" ")) - assert_equal("", Text::Format.new.format("\n")) - assert_equal("", Text::Format.new.format(" ")) - assert_equal("", Text::Format.new.format(" \n")) - assert_equal("", Text::Format.new.paragraphs("\n")) - assert_equal("", Text::Format.new.paragraphs(" ")) - assert_equal("", Text::Format.new.paragraphs(" ")) - assert_equal("", Text::Format.new.paragraphs(" \n")) - assert_equal("", Text::Format.new.paragraphs(["\n"])) - assert_equal("", Text::Format.new.paragraphs([" "])) - assert_equal("", Text::Format.new.paragraphs([" "])) - assert_equal("", Text::Format.new.paragraphs([" \n"])) - end - - def test_splendiferous - h = nil - test = "This is a splendiferous test" - assert_nothing_raised { @format_o = Text::Format.new(:columns => 6, :left_margin => 0, :indent => 0, :first_indent => 0) } - assert_match(/^splendiferous$/, @format_o.format(test)) - assert_nothing_raised { @format_o.hard_margins = true } - assert_match(/^lendif$/, @format_o.format(test)) - assert_nothing_raised { h = Object.new } - assert_nothing_raised do - @format_o.split_rules = Text::Format::SPLIT_HYPHENATION - class << h #:nodoc: - def hyphenate_to(word, size) - return ["", word] if size < 2 - [word[0 ... size], word[size .. -1]] - end - end - @format_o.hyphenator = h - end - assert_match(/^iferou$/, @format_o.format(test)) - assert_nothing_raised { h = Object.new } - assert_nothing_raised do - class << h #:nodoc: - def hyphenate_to(word, size, formatter) - return ["", word] if word.size < formatter.columns - [word[0 ... size], word[size .. -1]] - end - end - @format_o.hyphenator = h - end - assert_match(/^ferous$/, @format_o.format(test)) - end - end -end -# -# address.rb -# -#-- -# Copyright (c) 1998-2003 Minero Aoki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Note: Originally licensed under LGPL v2+. Using MIT license for Rails -# with permission of Minero Aoki. -#++ - -require 'tmail/encode' -require 'tmail/parser' - - -module TMail - - class Address - - include TextUtils - - def Address.parse( str ) - Parser.parse :ADDRESS, str - end - - def address_group? - false - end - - def initialize( local, domain ) - if domain - domain.each do |s| - raise SyntaxError, 'empty word in domain' if s.empty? - end - end - @local = local - @domain = domain - @name = nil - @routes = [] - end - - attr_reader :name - - def name=( str ) - @name = str - @name = nil if str and str.empty? - end - - alias phrase name - alias phrase= name= - - attr_reader :routes - - def inspect - "#<#{self.class} #{address()}>" - end - - def local - return nil unless @local - return '""' if @local.size == 1 and @local[0].empty? - @local.map {|i| quote_atom(i) }.join('.') - end - - def domain - return nil unless @domain - join_domain(@domain) - end - - def spec - s = self.local - d = self.domain - if s and d - s + '@' + d - else - s - end - end - - alias address spec - - - def ==( other ) - other.respond_to? :spec and self.spec == other.spec - end - - alias eql? == - - def hash - @local.hash ^ @domain.hash - end - - def dup - obj = self.class.new(@local.dup, @domain.dup) - obj.name = @name.dup if @name - obj.routes.replace @routes - obj - end - - include StrategyInterface - - def accept( strategy, dummy1 = nil, dummy2 = nil ) - unless @local - strategy.meta '<>' # empty return-path - return - end - - spec_p = (not @name and @routes.empty?) - if @name - strategy.phrase @name - strategy.space - end - tmp = spec_p ? '' : '<' - unless @routes.empty? - tmp << @routes.map {|i| '@' + i }.join(',') << ':' - end - tmp << self.spec - tmp << '>' unless spec_p - strategy.meta tmp - strategy.lwsp '' - end - - end - - - class AddressGroup - - include Enumerable - - def address_group? - true - end - - def initialize( name, addrs ) - @name = name - @addresses = addrs - end - - attr_reader :name - - def ==( other ) - other.respond_to? :to_a and @addresses == other.to_a - end - - alias eql? == - - def hash - map {|i| i.hash }.hash - end - - def []( idx ) - @addresses[idx] - end - - def size - @addresses.size - end - - def empty? - @addresses.empty? - end - - def each( &block ) - @addresses.each(&block) - end - - def to_a - @addresses.dup - end - - alias to_ary to_a - - def include?( a ) - @addresses.include? a - end - - def flatten - set = [] - @addresses.each do |a| - if a.respond_to? :flatten - set.concat a.flatten - else - set.push a - end - end - set - end - - def each_address( &block ) - flatten.each(&block) - end - - def add( a ) - @addresses.push a - end - - alias push add - - def delete( a ) - @addresses.delete a - end - - include StrategyInterface - - def accept( strategy, dummy1 = nil, dummy2 = nil ) - strategy.phrase @name - strategy.meta ':' - strategy.space - first = true - each do |mbox| - if first - first = false - else - strategy.meta ',' - end - strategy.space - mbox.accept strategy - end - strategy.meta ';' - strategy.lwsp '' - end - - end - -end # module TMail -require 'stringio' - -module TMail - class Attachment < StringIO - attr_accessor :original_filename, :content_type - end - - class Mail - def has_attachments? - multipart? && parts.any? { |part| attachment?(part) } - end - - def attachment?(part) - (part['content-disposition'] && part['content-disposition'].disposition == "attachment") || - part.header['content-type'].main_type != "text" - end - - def attachments - if multipart? - parts.collect { |part| - if attachment?(part) - content = part.body # unquoted automatically by TMail#body - file_name = (part['content-location'] && - part['content-location'].body) || - part.sub_header("content-type", "name") || - part.sub_header("content-disposition", "filename") - - next if file_name.blank? || content.blank? - - attachment = Attachment.new(content) - attachment.original_filename = file_name.strip - attachment.content_type = part.content_type - attachment - end - }.compact - end - end - end -end -# -# base64.rb -# -#-- -# Copyright (c) 1998-2003 Minero Aoki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Note: Originally licensed under LGPL v2+. Using MIT license for Rails -# with permission of Minero Aoki. -#++ - -module TMail - - module Base64 - - module_function - - def rb_folding_encode( str, eol = "\n", limit = 60 ) - [str].pack('m') - end - - def rb_encode( str ) - [str].pack('m').tr( "\r\n", '' ) - end - - def rb_decode( str, strict = false ) - str.unpack('m') - end - - begin - require 'tmail/base64.so' - alias folding_encode c_folding_encode - alias encode c_encode - alias decode c_decode - class << self - alias folding_encode c_folding_encode - alias encode c_encode - alias decode c_decode - end - rescue LoadError - alias folding_encode rb_folding_encode - alias encode rb_encode - alias decode rb_decode - class << self - alias folding_encode rb_folding_encode - alias encode rb_encode - alias decode rb_decode - end - end - - end - -end -# -# config.rb -# -#-- -# Copyright (c) 1998-2003 Minero Aoki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Note: Originally licensed under LGPL v2+. Using MIT license for Rails -# with permission of Minero Aoki. -#++ - -module TMail - - class Config - - def initialize( strict ) - @strict_parse = strict - @strict_base64decode = strict - end - - def strict_parse? - @strict_parse - end - - attr_writer :strict_parse - - def strict_base64decode? - @strict_base64decode - end - - attr_writer :strict_base64decode - - def new_body_port( mail ) - StringPort.new - end - - alias new_preamble_port new_body_port - alias new_part_port new_body_port - - end - - DEFAULT_CONFIG = Config.new(false) - DEFAULT_STRICT_CONFIG = Config.new(true) - - def Config.to_config( arg ) - return DEFAULT_STRICT_CONFIG if arg == true - return DEFAULT_CONFIG if arg == false - arg or DEFAULT_CONFIG - end - -end -# -# encode.rb -# -#-- -# Copyright (c) 1998-2003 Minero Aoki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Note: Originally licensed under LGPL v2+. Using MIT license for Rails -# with permission of Minero Aoki. -#++ - -require 'nkf' -require 'tmail/base64.rb' -require 'tmail/stringio' -require 'tmail/utils' - - -module TMail - - module StrategyInterface - - def create_dest( obj ) - case obj - when nil - StringOutput.new - when String - StringOutput.new(obj) - when IO, StringOutput - obj - else - raise TypeError, 'cannot handle this type of object for dest' - end - end - module_function :create_dest - - def encoded( eol = "\r\n", charset = 'j', dest = nil ) - accept_strategy Encoder, eol, charset, dest - end - - def decoded( eol = "\n", charset = 'e', dest = nil ) - accept_strategy Decoder, eol, charset, dest - end - - alias to_s decoded - - def accept_strategy( klass, eol, charset, dest = nil ) - dest ||= '' - accept klass.new(create_dest(dest), charset, eol) - dest - end - - end - - - ### - ### MIME B encoding decoder - ### - - class Decoder - - include TextUtils - - encoded = '=\?(?:iso-2022-jp|euc-jp|shift_jis)\?[QB]\?[a-z0-9+/=]+\?=' - ENCODED_WORDS = /#{encoded}(?:\s+#{encoded})*/i - - OUTPUT_ENCODING = { - 'EUC' => 'e', - 'SJIS' => 's', - } - - def self.decode( str, encoding = nil ) - encoding ||= (OUTPUT_ENCODING[$KCODE] || 'j') - opt = '-m' + encoding - str.gsub(ENCODED_WORDS) {|s| NKF.nkf(opt, s) } - end - - def initialize( dest, encoding = nil, eol = "\n" ) - @f = StrategyInterface.create_dest(dest) - @encoding = (/\A[ejs]/ === encoding) ? encoding[0,1] : nil - @eol = eol - end - - def decode( str ) - self.class.decode(str, @encoding) - end - private :decode - - def terminate - end - - def header_line( str ) - @f << decode(str) - end - - def header_name( nm ) - @f << nm << ': ' - end - - def header_body( str ) - @f << decode(str) - end - - def space - @f << ' ' - end - - alias spc space - - def lwsp( str ) - @f << str - end - - def meta( str ) - @f << str - end - - def text( str ) - @f << decode(str) - end - - def phrase( str ) - @f << quote_phrase(decode(str)) - end - - def kv_pair( k, v ) - @f << k << '=' << v - end - - def puts( str = nil ) - @f << str if str - @f << @eol - end - - def write( str ) - @f << str - end - - end - - - ### - ### MIME B-encoding encoder - ### - - # - # FIXME: This class can handle only (euc-jp/shift_jis -> iso-2022-jp). - # - class Encoder - - include TextUtils - - BENCODE_DEBUG = false unless defined?(BENCODE_DEBUG) - - def Encoder.encode( str ) - e = new() - e.header_body str - e.terminate - e.dest.string - end - - SPACER = "\t" - MAX_LINE_LEN = 70 - - OPTIONS = { - 'EUC' => '-Ej -m0', - 'SJIS' => '-Sj -m0', - 'UTF8' => nil, # FIXME - 'NONE' => nil - } - - def initialize( dest = nil, encoding = nil, eol = "\r\n", limit = nil ) - @f = StrategyInterface.create_dest(dest) - @opt = OPTIONS[$KCODE] - @eol = eol - reset - end - - def normalize_encoding( str ) - if @opt - then NKF.nkf(@opt, str) - else str - end - end - - def reset - @text = '' - @lwsp = '' - @curlen = 0 - end - - def terminate - add_lwsp '' - reset - end - - def dest - @f - end - - def puts( str = nil ) - @f << str if str - @f << @eol - end - - def write( str ) - @f << str - end - - # - # add - # - - def header_line( line ) - scanadd line - end - - def header_name( name ) - add_text name.split(/-/).map {|i| i.capitalize }.join('-') - add_text ':' - add_lwsp ' ' - end - - def header_body( str ) - scanadd normalize_encoding(str) - end - - def space - add_lwsp ' ' - end - - alias spc space - - def lwsp( str ) - add_lwsp str.sub(/[\r\n]+[^\r\n]*\z/, '') - end - - def meta( str ) - add_text str - end - - def text( str ) - scanadd normalize_encoding(str) - end - - def phrase( str ) - str = normalize_encoding(str) - if CONTROL_CHAR === str - scanadd str - else - add_text quote_phrase(str) - end - end - - # FIXME: implement line folding - # - def kv_pair( k, v ) - return if v.nil? - v = normalize_encoding(v) - if token_safe?(v) - add_text k + '=' + v - elsif not CONTROL_CHAR === v - add_text k + '=' + quote_token(v) - else - # apply RFC2231 encoding - kv = k + '*=' + "iso-2022-jp'ja'" + encode_value(v) - add_text kv - end - end - - def encode_value( str ) - str.gsub(TOKEN_UNSAFE) {|s| '%%%02x' % s[0] } - end - - private - - def scanadd( str, force = false ) - types = '' - strs = [] - - until str.empty? - if m = /\A[^\e\t\r\n ]+/.match(str) - types << (force ? 'j' : 'a') - strs.push m[0] - - elsif m = /\A[\t\r\n ]+/.match(str) - types << 's' - strs.push m[0] - - elsif m = /\A\e../.match(str) - esc = m[0] - str = m.post_match - if esc != "\e(B" and m = /\A[^\e]+/.match(str) - types << 'j' - strs.push m[0] - end - - else - raise 'TMail FATAL: encoder scan fail' - end - (str = m.post_match) unless m.nil? - end - - do_encode types, strs - end - - def do_encode( types, strs ) - # - # result : (A|E)(S(A|E))* - # E : W(SW)* - # W : (J|A)+ but must contain J # (J|A)*J(J|A)* - # A : <> - # J : <> - # S : <> - # - # An encoding unit is `E'. - # Input (parameter `types') is (J|A)(J|A|S)*(J|A) - # - if BENCODE_DEBUG - puts - puts '-- do_encode ------------' - puts types.split(//).join(' ') - p strs - end - - e = /[ja]*j[ja]*(?:s[ja]*j[ja]*)*/ - - while m = e.match(types) - pre = m.pre_match - concat_A_S pre, strs[0, pre.size] unless pre.empty? - concat_E m[0], strs[m.begin(0) ... m.end(0)] - types = m.post_match - strs.slice! 0, m.end(0) - end - concat_A_S types, strs - end - - def concat_A_S( types, strs ) - i = 0 - types.each_byte do |t| - case t - when ?a then add_text strs[i] - when ?s then add_lwsp strs[i] - else - raise "TMail FATAL: unknown flag: #{t.chr}" - end - i += 1 - end - end - - METHOD_ID = { - ?j => :extract_J, - ?e => :extract_E, - ?a => :extract_A, - ?s => :extract_S - } - - def concat_E( types, strs ) - if BENCODE_DEBUG - puts '---- concat_E' - puts "types=#{types.split(//).join(' ')}" - puts "strs =#{strs.inspect}" - end - - flush() unless @text.empty? - - chunk = '' - strs.each_with_index do |s,i| - mid = METHOD_ID[types[i]] - until s.empty? - unless c = __send__(mid, chunk.size, s) - add_with_encode chunk unless chunk.empty? - flush - chunk = '' - fold - c = __send__(mid, 0, s) - raise 'TMail FATAL: extract fail' unless c - end - chunk << c - end - end - add_with_encode chunk unless chunk.empty? - end - - def extract_J( chunksize, str ) - size = max_bytes(chunksize, str.size) - 6 - size = (size % 2 == 0) ? (size) : (size - 1) - return nil if size <= 0 - "\e$B#{str.slice!(0, size)}\e(B" - end - - def extract_A( chunksize, str ) - size = max_bytes(chunksize, str.size) - return nil if size <= 0 - str.slice!(0, size) - end - - alias extract_S extract_A - - def max_bytes( chunksize, ssize ) - (restsize() - '=?iso-2022-jp?B??='.size) / 4 * 3 - chunksize - end - - # - # free length buffer - # - - def add_text( str ) - @text << str - # puts '---- text -------------------------------------' - # puts "+ #{str.inspect}" - # puts "txt >>>#{@text.inspect}<<<" - end - - def add_with_encode( str ) - @text << "=?iso-2022-jp?B?#{Base64.encode(str)}?=" - end - - def add_lwsp( lwsp ) - # puts '---- lwsp -------------------------------------' - # puts "+ #{lwsp.inspect}" - fold if restsize() <= 0 - flush - @lwsp = lwsp - end - - def flush - # puts '---- flush ----' - # puts "spc >>>#{@lwsp.inspect}<<<" - # puts "txt >>>#{@text.inspect}<<<" - @f << @lwsp << @text - @curlen += (@lwsp.size + @text.size) - @text = '' - @lwsp = '' - end - - def fold - # puts '---- fold ----' - @f << @eol - @curlen = 0 - @lwsp = SPACER - end - - def restsize - MAX_LINE_LEN - (@curlen + @lwsp.size + @text.size) - end - - end - -end # module TMail -# -# facade.rb -# -#-- -# Copyright (c) 1998-2003 Minero Aoki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Note: Originally licensed under LGPL v2+. Using MIT license for Rails -# with permission of Minero Aoki. -#++ - -require 'tmail/utils' - - -module TMail - - class Mail - - def header_string( name, default = nil ) - h = @header[name.downcase] or return default - h.to_s - end - - ### - ### attributes - ### - - include TextUtils - - def set_string_array_attr( key, strs ) - strs.flatten! - if strs.empty? - @header.delete key.downcase - else - store key, strs.join(', ') - end - strs - end - private :set_string_array_attr - - def set_string_attr( key, str ) - if str - store key, str - else - @header.delete key.downcase - end - str - end - private :set_string_attr - - def set_addrfield( name, arg ) - if arg - h = HeaderField.internal_new(name, @config) - h.addrs.replace [arg].flatten - @header[name] = h - else - @header.delete name - end - arg - end - private :set_addrfield - - def addrs2specs( addrs ) - return nil unless addrs - list = addrs.map {|addr| - if addr.address_group? - then addr.map {|a| a.spec } - else addr.spec - end - }.flatten - return nil if list.empty? - list - end - private :addrs2specs - - - # - # date time - # - - def date( default = nil ) - if h = @header['date'] - h.date - else - default - end - end - - def date=( time ) - if time - store 'Date', time2str(time) - else - @header.delete 'date' - end - time - end - - def strftime( fmt, default = nil ) - if t = date - t.strftime(fmt) - else - default - end - end - - - # - # destination - # - - def to_addrs( default = nil ) - if h = @header['to'] - h.addrs - else - default - end - end - - def cc_addrs( default = nil ) - if h = @header['cc'] - h.addrs - else - default - end - end - - def bcc_addrs( default = nil ) - if h = @header['bcc'] - h.addrs - else - default - end - end - - def to_addrs=( arg ) - set_addrfield 'to', arg - end - - def cc_addrs=( arg ) - set_addrfield 'cc', arg - end - - def bcc_addrs=( arg ) - set_addrfield 'bcc', arg - end - - def to( default = nil ) - addrs2specs(to_addrs(nil)) || default - end - - def cc( default = nil ) - addrs2specs(cc_addrs(nil)) || default - end - - def bcc( default = nil ) - addrs2specs(bcc_addrs(nil)) || default - end - - def to=( *strs ) - set_string_array_attr 'To', strs - end - - def cc=( *strs ) - set_string_array_attr 'Cc', strs - end - - def bcc=( *strs ) - set_string_array_attr 'Bcc', strs - end - - - # - # originator - # - - def from_addrs( default = nil ) - if h = @header['from'] - h.addrs - else - default - end - end - - def from_addrs=( arg ) - set_addrfield 'from', arg - end - - def from( default = nil ) - addrs2specs(from_addrs(nil)) || default - end - - def from=( *strs ) - set_string_array_attr 'From', strs - end - - def friendly_from( default = nil ) - h = @header['from'] - a, = h.addrs - return default unless a - return a.phrase if a.phrase - return h.comments.join(' ') unless h.comments.empty? - a.spec - end - - - def reply_to_addrs( default = nil ) - if h = @header['reply-to'] - h.addrs - else - default - end - end - - def reply_to_addrs=( arg ) - set_addrfield 'reply-to', arg - end - - def reply_to( default = nil ) - addrs2specs(reply_to_addrs(nil)) || default - end - - def reply_to=( *strs ) - set_string_array_attr 'Reply-To', strs - end - - - def sender_addr( default = nil ) - f = @header['sender'] or return default - f.addr or return default - end - - def sender_addr=( addr ) - if addr - h = HeaderField.internal_new('sender', @config) - h.addr = addr - @header['sender'] = h - else - @header.delete 'sender' - end - addr - end - - def sender( default ) - f = @header['sender'] or return default - a = f.addr or return default - a.spec - end - - def sender=( str ) - set_string_attr 'Sender', str - end - - - # - # subject - # - - def subject( default = nil ) - if h = @header['subject'] - h.body - else - default - end - end - alias quoted_subject subject - - def subject=( str ) - set_string_attr 'Subject', str - end - - - # - # identity & threading - # - - def message_id( default = nil ) - if h = @header['message-id'] - h.id || default - else - default - end - end - - def message_id=( str ) - set_string_attr 'Message-Id', str - end - - def in_reply_to( default = nil ) - if h = @header['in-reply-to'] - h.ids - else - default - end - end - - def in_reply_to=( *idstrs ) - set_string_array_attr 'In-Reply-To', idstrs - end - - def references( default = nil ) - if h = @header['references'] - h.refs - else - default - end - end - - def references=( *strs ) - set_string_array_attr 'References', strs - end - - - # - # MIME headers - # - - def mime_version( default = nil ) - if h = @header['mime-version'] - h.version || default - else - default - end - end - - def mime_version=( m, opt = nil ) - if opt - if h = @header['mime-version'] - h.major = m - h.minor = opt - else - store 'Mime-Version', "#{m}.#{opt}" - end - else - store 'Mime-Version', m - end - m - end - - - def content_type( default = nil ) - if h = @header['content-type'] - h.content_type || default - else - default - end - end - - def main_type( default = nil ) - if h = @header['content-type'] - h.main_type || default - else - default - end - end - - def sub_type( default = nil ) - if h = @header['content-type'] - h.sub_type || default - else - default - end - end - - def set_content_type( str, sub = nil, param = nil ) - if sub - main, sub = str, sub - else - main, sub = str.split(%r, 2) - raise ArgumentError, "sub type missing: #{str.inspect}" unless sub - end - if h = @header['content-type'] - h.main_type = main - h.sub_type = sub - h.params.clear - else - store 'Content-Type', "#{main}/#{sub}" - end - @header['content-type'].params.replace param if param - - str - end - - alias content_type= set_content_type - - def type_param( name, default = nil ) - if h = @header['content-type'] - h[name] || default - else - default - end - end - - def charset( default = nil ) - if h = @header['content-type'] - h['charset'] or default - else - default - end - end - - def charset=( str ) - if str - if h = @header[ 'content-type' ] - h['charset'] = str - else - store 'Content-Type', "text/plain; charset=#{str}" - end - end - str - end - - - def transfer_encoding( default = nil ) - if h = @header['content-transfer-encoding'] - h.encoding || default - else - default - end - end - - def transfer_encoding=( str ) - set_string_attr 'Content-Transfer-Encoding', str - end - - alias encoding transfer_encoding - alias encoding= transfer_encoding= - alias content_transfer_encoding transfer_encoding - alias content_transfer_encoding= transfer_encoding= - - - def disposition( default = nil ) - if h = @header['content-disposition'] - h.disposition || default - else - default - end - end - - alias content_disposition disposition - - def set_disposition( str, params = nil ) - if h = @header['content-disposition'] - h.disposition = str - h.params.clear - else - store('Content-Disposition', str) - h = @header['content-disposition'] - end - h.params.replace params if params - end - - alias disposition= set_disposition - alias set_content_disposition set_disposition - alias content_disposition= set_disposition - - def disposition_param( name, default = nil ) - if h = @header['content-disposition'] - h[name] || default - else - default - end - end - - ### - ### utils - ### - - def create_reply - mail = TMail::Mail.parse('') - mail.subject = 'Re: ' + subject('').sub(/\A(?:\[[^\]]+\])?(?:\s*Re:)*\s*/i, '') - mail.to_addrs = reply_addresses([]) - mail.in_reply_to = [message_id(nil)].compact - mail.references = references([]) + [message_id(nil)].compact - mail.mime_version = '1.0' - mail - end - - - def base64_encode - store 'Content-Transfer-Encoding', 'Base64' - self.body = Base64.folding_encode(self.body) - end - - def base64_decode - if /base64/i === self.transfer_encoding('') - store 'Content-Transfer-Encoding', '8bit' - self.body = Base64.decode(self.body, @config.strict_base64decode?) - end - end - - - def destinations( default = nil ) - ret = [] - %w( to cc bcc ).each do |nm| - if h = @header[nm] - h.addrs.each {|i| ret.push i.address } - end - end - ret.empty? ? default : ret - end - - def each_destination( &block ) - destinations([]).each do |i| - if Address === i - yield i - else - i.each(&block) - end - end - end - - alias each_dest each_destination - - - def reply_addresses( default = nil ) - reply_to_addrs(nil) or from_addrs(nil) or default - end - - def error_reply_addresses( default = nil ) - if s = sender(nil) - [s] - else - from_addrs(default) - end - end - - - def multipart? - main_type('').downcase == 'multipart' - end - - end # class Mail - -end # module TMail -# -# header.rb -# -#-- -# Copyright (c) 1998-2003 Minero Aoki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Note: Originally licensed under LGPL v2+. Using MIT license for Rails -# with permission of Minero Aoki. -#++ - -require 'tmail/encode' -require 'tmail/address' -require 'tmail/parser' -require 'tmail/config' -require 'tmail/utils' - - -module TMail - - class HeaderField - - include TextUtils - - class << self - - alias newobj new - - def new( name, body, conf = DEFAULT_CONFIG ) - klass = FNAME_TO_CLASS[name.downcase] || UnstructuredHeader - klass.newobj body, conf - end - - def new_from_port( port, name, conf = DEFAULT_CONFIG ) - re = Regep.new('\A(' + Regexp.quote(name) + '):', 'i') - str = nil - port.ropen {|f| - f.each do |line| - if m = re.match(line) then str = m.post_match.strip - elsif str and /\A[\t ]/ === line then str << ' ' << line.strip - elsif /\A-*\s*\z/ === line then break - elsif str then break - end - end - } - new(name, str, Config.to_config(conf)) - end - - def internal_new( name, conf ) - FNAME_TO_CLASS[name].newobj('', conf, true) - end - - end # class << self - - def initialize( body, conf, intern = false ) - @body = body - @config = conf - - @illegal = false - @parsed = false - if intern - @parsed = true - parse_init - end - end - - def inspect - "#<#{self.class} #{@body.inspect}>" - end - - def illegal? - @illegal - end - - def empty? - ensure_parsed - return true if @illegal - isempty? - end - - private - - def ensure_parsed - return if @parsed - @parsed = true - parse - end - - # defabstract parse - # end - - def clear_parse_status - @parsed = false - @illegal = false - end - - public - - def body - ensure_parsed - v = Decoder.new(s = '') - do_accept v - v.terminate - s - end - - def body=( str ) - @body = str - clear_parse_status - end - - include StrategyInterface - - def accept( strategy, dummy1 = nil, dummy2 = nil ) - ensure_parsed - do_accept strategy - strategy.terminate - end - - # abstract do_accept - - end - - - class UnstructuredHeader < HeaderField - - def body - ensure_parsed - @body - end - - def body=( arg ) - ensure_parsed - @body = arg - end - - private - - def parse_init - end - - def parse - @body = Decoder.decode(@body.gsub(/\n|\r\n|\r/, '')) - end - - def isempty? - not @body - end - - def do_accept( strategy ) - strategy.text @body - end - - end - - - class StructuredHeader < HeaderField - - def comments - ensure_parsed - @comments - end - - private - - def parse - save = nil - - begin - parse_init - do_parse - rescue SyntaxError - if not save and mime_encoded? @body - save = @body - @body = Decoder.decode(save) - retry - elsif save - @body = save - end - - @illegal = true - raise if @config.strict_parse? - end - end - - def parse_init - @comments = [] - init - end - - def do_parse - obj = Parser.parse(self.class::PARSE_TYPE, @body, @comments) - set obj if obj - end - - end - - - class DateTimeHeader < StructuredHeader - - PARSE_TYPE = :DATETIME - - def date - ensure_parsed - @date - end - - def date=( arg ) - ensure_parsed - @date = arg - end - - private - - def init - @date = nil - end - - def set( t ) - @date = t - end - - def isempty? - not @date - end - - def do_accept( strategy ) - strategy.meta time2str(@date) - end - - end - - - class AddressHeader < StructuredHeader - - PARSE_TYPE = :MADDRESS - - def addrs - ensure_parsed - @addrs - end - - private - - def init - @addrs = [] - end - - def set( a ) - @addrs = a - end - - def isempty? - @addrs.empty? - end - - def do_accept( strategy ) - first = true - @addrs.each do |a| - if first - first = false - else - strategy.meta ',' - strategy.space - end - a.accept strategy - end - - @comments.each do |c| - strategy.space - strategy.meta '(' - strategy.text c - strategy.meta ')' - end - end - - end - - - class ReturnPathHeader < AddressHeader - - PARSE_TYPE = :RETPATH - - def addr - addrs()[0] - end - - def spec - a = addr() or return nil - a.spec - end - - def routes - a = addr() or return nil - a.routes - end - - private - - def do_accept( strategy ) - a = addr() - - strategy.meta '<' - unless a.routes.empty? - strategy.meta a.routes.map {|i| '@' + i }.join(',') - strategy.meta ':' - end - spec = a.spec - strategy.meta spec if spec - strategy.meta '>' - end - - end - - - class SingleAddressHeader < AddressHeader - - def addr - addrs()[0] - end - - private - - def do_accept( strategy ) - a = addr() - a.accept strategy - @comments.each do |c| - strategy.space - strategy.meta '(' - strategy.text c - strategy.meta ')' - end - end - - end - - - class MessageIdHeader < StructuredHeader - - def id - ensure_parsed - @id - end - - def id=( arg ) - ensure_parsed - @id = arg - end - - private - - def init - @id = nil - end - - def isempty? - not @id - end - - def do_parse - @id = @body.slice(MESSAGE_ID) or - raise SyntaxError, "wrong Message-ID format: #{@body}" - end - - def do_accept( strategy ) - strategy.meta @id - end - - end - - - class ReferencesHeader < StructuredHeader - - def refs - ensure_parsed - @refs - end - - def each_id - self.refs.each do |i| - yield i if MESSAGE_ID === i - end - end - - def ids - ensure_parsed - @ids - end - - def each_phrase - self.refs.each do |i| - yield i unless MESSAGE_ID === i - end - end - - def phrases - ret = [] - each_phrase {|i| ret.push i } - ret - end - - private - - def init - @refs = [] - @ids = [] - end - - def isempty? - @ids.empty? - end - - def do_parse - str = @body - while m = MESSAGE_ID.match(str) - pre = m.pre_match.strip - @refs.push pre unless pre.empty? - @refs.push s = m[0] - @ids.push s - str = m.post_match - end - str = str.strip - @refs.push str unless str.empty? - end - - def do_accept( strategy ) - first = true - @ids.each do |i| - if first - first = false - else - strategy.space - end - strategy.meta i - end - end - - end - - - class ReceivedHeader < StructuredHeader - - PARSE_TYPE = :RECEIVED - - def from - ensure_parsed - @from - end - - def from=( arg ) - ensure_parsed - @from = arg - end - - def by - ensure_parsed - @by - end - - def by=( arg ) - ensure_parsed - @by = arg - end - - def via - ensure_parsed - @via - end - - def via=( arg ) - ensure_parsed - @via = arg - end - - def with - ensure_parsed - @with - end - - def id - ensure_parsed - @id - end - - def id=( arg ) - ensure_parsed - @id = arg - end - - def _for - ensure_parsed - @_for - end - - def _for=( arg ) - ensure_parsed - @_for = arg - end - - def date - ensure_parsed - @date - end - - def date=( arg ) - ensure_parsed - @date = arg - end - - private - - def init - @from = @by = @via = @with = @id = @_for = nil - @with = [] - @date = nil - end - - def set( args ) - @from, @by, @via, @with, @id, @_for, @date = *args - end - - def isempty? - @with.empty? and not (@from or @by or @via or @id or @_for or @date) - end - - def do_accept( strategy ) - list = [] - list.push 'from ' + @from if @from - list.push 'by ' + @by if @by - list.push 'via ' + @via if @via - @with.each do |i| - list.push 'with ' + i - end - list.push 'id ' + @id if @id - list.push 'for <' + @_for + '>' if @_for - - first = true - list.each do |i| - strategy.space unless first - strategy.meta i - first = false - end - if @date - strategy.meta ';' - strategy.space - strategy.meta time2str(@date) - end - end - - end - - - class KeywordsHeader < StructuredHeader - - PARSE_TYPE = :KEYWORDS - - def keys - ensure_parsed - @keys - end - - private - - def init - @keys = [] - end - - def set( a ) - @keys = a - end - - def isempty? - @keys.empty? - end - - def do_accept( strategy ) - first = true - @keys.each do |i| - if first - first = false - else - strategy.meta ',' - end - strategy.meta i - end - end - - end - - - class EncryptedHeader < StructuredHeader - - PARSE_TYPE = :ENCRYPTED - - def encrypter - ensure_parsed - @encrypter - end - - def encrypter=( arg ) - ensure_parsed - @encrypter = arg - end - - def keyword - ensure_parsed - @keyword - end - - def keyword=( arg ) - ensure_parsed - @keyword = arg - end - - private - - def init - @encrypter = nil - @keyword = nil - end - - def set( args ) - @encrypter, @keyword = args - end - - def isempty? - not (@encrypter or @keyword) - end - - def do_accept( strategy ) - if @key - strategy.meta @encrypter + ',' - strategy.space - strategy.meta @keyword - else - strategy.meta @encrypter - end - end - - end - - - class MimeVersionHeader < StructuredHeader - - PARSE_TYPE = :MIMEVERSION - - def major - ensure_parsed - @major - end - - def major=( arg ) - ensure_parsed - @major = arg - end - - def minor - ensure_parsed - @minor - end - - def minor=( arg ) - ensure_parsed - @minor = arg - end - - def version - sprintf('%d.%d', major, minor) - end - - private - - def init - @major = nil - @minor = nil - end - - def set( args ) - @major, @minor = *args - end - - def isempty? - not (@major or @minor) - end - - def do_accept( strategy ) - strategy.meta sprintf('%d.%d', @major, @minor) - end - - end - - - class ContentTypeHeader < StructuredHeader - - PARSE_TYPE = :CTYPE - - def main_type - ensure_parsed - @main - end - - def main_type=( arg ) - ensure_parsed - @main = arg.downcase - end - - def sub_type - ensure_parsed - @sub - end - - def sub_type=( arg ) - ensure_parsed - @sub = arg.downcase - end - - def content_type - ensure_parsed - @sub ? sprintf('%s/%s', @main, @sub) : @main - end - - def params - ensure_parsed - @params - end - - def []( key ) - ensure_parsed - @params and @params[key] - end - - def []=( key, val ) - ensure_parsed - (@params ||= {})[key] = val - end - - private - - def init - @main = @sub = @params = nil - end - - def set( args ) - @main, @sub, @params = *args - end - - def isempty? - not (@main or @sub) - end - - def do_accept( strategy ) - if @sub - strategy.meta sprintf('%s/%s', @main, @sub) - else - strategy.meta @main - end - @params.each do |k,v| - if v - strategy.meta ';' - strategy.space - strategy.kv_pair k, v - end - end - end - - end - - - class ContentTransferEncodingHeader < StructuredHeader - - PARSE_TYPE = :CENCODING - - def encoding - ensure_parsed - @encoding - end - - def encoding=( arg ) - ensure_parsed - @encoding = arg - end - - private - - def init - @encoding = nil - end - - def set( s ) - @encoding = s - end - - def isempty? - not @encoding - end - - def do_accept( strategy ) - strategy.meta @encoding.capitalize - end - - end - - - class ContentDispositionHeader < StructuredHeader - - PARSE_TYPE = :CDISPOSITION - - def disposition - ensure_parsed - @disposition - end - - def disposition=( str ) - ensure_parsed - @disposition = str.downcase - end - - def params - ensure_parsed - @params - end - - def []( key ) - ensure_parsed - @params and @params[key] - end - - def []=( key, val ) - ensure_parsed - (@params ||= {})[key] = val - end - - private - - def init - @disposition = @params = nil - end - - def set( args ) - @disposition, @params = *args - end - - def isempty? - not @disposition and (not @params or @params.empty?) - end - - def do_accept( strategy ) - strategy.meta @disposition - @params.each do |k,v| - strategy.meta ';' - strategy.space - strategy.kv_pair k, v - end - end - - end - - - class HeaderField # redefine - - FNAME_TO_CLASS = { - 'date' => DateTimeHeader, - 'resent-date' => DateTimeHeader, - 'to' => AddressHeader, - 'cc' => AddressHeader, - 'bcc' => AddressHeader, - 'from' => AddressHeader, - 'reply-to' => AddressHeader, - 'resent-to' => AddressHeader, - 'resent-cc' => AddressHeader, - 'resent-bcc' => AddressHeader, - 'resent-from' => AddressHeader, - 'resent-reply-to' => AddressHeader, - 'sender' => SingleAddressHeader, - 'resent-sender' => SingleAddressHeader, - 'return-path' => ReturnPathHeader, - 'message-id' => MessageIdHeader, - 'resent-message-id' => MessageIdHeader, - 'in-reply-to' => ReferencesHeader, - 'received' => ReceivedHeader, - 'references' => ReferencesHeader, - 'keywords' => KeywordsHeader, - 'encrypted' => EncryptedHeader, - 'mime-version' => MimeVersionHeader, - 'content-type' => ContentTypeHeader, - 'content-transfer-encoding' => ContentTransferEncodingHeader, - 'content-disposition' => ContentDispositionHeader, - 'content-id' => MessageIdHeader, - 'subject' => UnstructuredHeader, - 'comments' => UnstructuredHeader, - 'content-description' => UnstructuredHeader - } - - end - -end # module TMail -# -# info.rb -# -#-- -# Copyright (c) 1998-2003 Minero Aoki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Note: Originally licensed under LGPL v2+. Using MIT license for Rails -# with permission of Minero Aoki. -#++ - -module TMail - - Version = '0.10.7' - Copyright = 'Copyright (c) 1998-2002 Minero Aoki' - -end -require 'tmail/mailbox' -# -# mail.rb -# -#-- -# Copyright (c) 1998-2003 Minero Aoki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Note: Originally licensed under LGPL v2+. Using MIT license for Rails -# with permission of Minero Aoki. -#++ - -require 'tmail/facade' -require 'tmail/encode' -require 'tmail/header' -require 'tmail/port' -require 'tmail/config' -require 'tmail/utils' -require 'tmail/attachments' -require 'tmail/quoting' -require 'socket' - - -module TMail - - class Mail - - class << self - def load( fname ) - new(FilePort.new(fname)) - end - - alias load_from load - alias loadfrom load - - def parse( str ) - new(StringPort.new(str)) - end - end - - def initialize( port = nil, conf = DEFAULT_CONFIG ) - @port = port || StringPort.new - @config = Config.to_config(conf) - - @header = {} - @body_port = nil - @body_parsed = false - @epilogue = '' - @parts = [] - - @port.ropen {|f| - parse_header f - parse_body f unless @port.reproducible? - } - end - - attr_reader :port - - def inspect - "\#<#{self.class} port=#{@port.inspect} bodyport=#{@body_port.inspect}>" - end - - # - # to_s interfaces - # - - public - - include StrategyInterface - - def write_back( eol = "\n", charset = 'e' ) - parse_body - @port.wopen {|stream| encoded eol, charset, stream } - end - - def accept( strategy ) - with_multipart_encoding(strategy) { - ordered_each do |name, field| - next if field.empty? - strategy.header_name canonical(name) - field.accept strategy - strategy.puts - end - strategy.puts - body_port().ropen {|r| - strategy.write r.read - } - } - end - - private - - def canonical( name ) - name.split(/-/).map {|s| s.capitalize }.join('-') - end - - def with_multipart_encoding( strategy ) - if parts().empty? # DO NOT USE @parts - yield - - else - bound = ::TMail.new_boundary - if @header.key? 'content-type' - @header['content-type'].params['boundary'] = bound - else - store 'Content-Type', % - end - - yield - - parts().each do |tm| - strategy.puts - strategy.puts '--' + bound - tm.accept strategy - end - strategy.puts - strategy.puts '--' + bound + '--' - strategy.write epilogue() - end - end - - ### - ### header - ### - - public - - ALLOW_MULTIPLE = { - 'received' => true, - 'resent-date' => true, - 'resent-from' => true, - 'resent-sender' => true, - 'resent-to' => true, - 'resent-cc' => true, - 'resent-bcc' => true, - 'resent-message-id' => true, - 'comments' => true, - 'keywords' => true - } - USE_ARRAY = ALLOW_MULTIPLE - - def header - @header.dup - end - - def []( key ) - @header[key.downcase] - end - - def sub_header(key, param) - (hdr = self[key]) ? hdr[param] : nil - end - - alias fetch [] - - def []=( key, val ) - dkey = key.downcase - - if val.nil? - @header.delete dkey - return nil - end - - case val - when String - header = new_hf(key, val) - when HeaderField - ; - when Array - ALLOW_MULTIPLE.include? dkey or - raise ArgumentError, "#{key}: Header must not be multiple" - @header[dkey] = val - return val - else - header = new_hf(key, val.to_s) - end - if ALLOW_MULTIPLE.include? dkey - (@header[dkey] ||= []).push header - else - @header[dkey] = header - end - - val - end - - alias store []= - - def each_header - @header.each do |key, val| - [val].flatten.each {|v| yield key, v } - end - end - - alias each_pair each_header - - def each_header_name( &block ) - @header.each_key(&block) - end - - alias each_key each_header_name - - def each_field( &block ) - @header.values.flatten.each(&block) - end - - alias each_value each_field - - FIELD_ORDER = %w( - return-path received - resent-date resent-from resent-sender resent-to - resent-cc resent-bcc resent-message-id - date from sender reply-to to cc bcc - message-id in-reply-to references - subject comments keywords - mime-version content-type content-transfer-encoding - content-disposition content-description - ) - - def ordered_each - list = @header.keys - FIELD_ORDER.each do |name| - if list.delete(name) - [@header[name]].flatten.each {|v| yield name, v } - end - end - list.each do |name| - [@header[name]].flatten.each {|v| yield name, v } - end - end - - def clear - @header.clear - end - - def delete( key ) - @header.delete key.downcase - end - - def delete_if - @header.delete_if do |key,val| - if Array === val - val.delete_if {|v| yield key, v } - val.empty? - else - yield key, val - end - end - end - - def keys - @header.keys - end - - def key?( key ) - @header.key? key.downcase - end - - def values_at( *args ) - args.map {|k| @header[k.downcase] }.flatten - end - - alias indexes values_at - alias indices values_at - - private - - def parse_header( f ) - name = field = nil - unixfrom = nil - - while line = f.gets - case line - when /\A[ \t]/ # continue from prev line - raise SyntaxError, 'mail is began by space' unless field - field << ' ' << line.strip - - when /\A([^\: \t]+):\s*/ # new header line - add_hf name, field if field - name = $1 - field = $' #.strip - - when /\A\-*\s*\z/ # end of header - add_hf name, field if field - name = field = nil - break - - when /\AFrom (\S+)/ - unixfrom = $1 - - when /^charset=.*/ - - else - raise SyntaxError, "wrong mail header: '#{line.inspect}'" - end - end - add_hf name, field if name - - if unixfrom - add_hf 'Return-Path', "<#{unixfrom}>" unless @header['return-path'] - end - end - - def add_hf( name, field ) - key = name.downcase - field = new_hf(name, field) - - if ALLOW_MULTIPLE.include? key - (@header[key] ||= []).push field - else - @header[key] = field - end - end - - def new_hf( name, field ) - HeaderField.new(name, field, @config) - end - - ### - ### body - ### - - public - - def body_port - parse_body - @body_port - end - - def each( &block ) - body_port().ropen {|f| f.each(&block) } - end - - def quoted_body - parse_body - @body_port.ropen {|f| - return f.read - } - end - - def body=( str ) - parse_body - @body_port.wopen {|f| f.write str } - str - end - - alias preamble body - alias preamble= body= - - def epilogue - parse_body - @epilogue.dup - end - - def epilogue=( str ) - parse_body - @epilogue = str - str - end - - def parts - parse_body - @parts - end - - def each_part( &block ) - parts().each(&block) - end - - private - - def parse_body( f = nil ) - return if @body_parsed - if f - parse_body_0 f - else - @port.ropen {|f| - skip_header f - parse_body_0 f - } - end - @body_parsed = true - end - - def skip_header( f ) - while line = f.gets - return if /\A[\r\n]*\z/ === line - end - end - - def parse_body_0( f ) - if multipart? - read_multipart f - else - @body_port = @config.new_body_port(self) - @body_port.wopen {|w| - w.write f.read - } - end - end - - def read_multipart( src ) - bound = @header['content-type'].params['boundary'] - is_sep = /\A--#{Regexp.quote bound}(?:--)?[ \t]*(?:\n|\r\n|\r)/ - lastbound = "--#{bound}--" - - ports = [ @config.new_preamble_port(self) ] - begin - f = ports.last.wopen - while line = src.gets - if is_sep === line - f.close - break if line.strip == lastbound - ports.push @config.new_part_port(self) - f = ports.last.wopen - else - f << line - end - end - @epilogue = (src.read || '') - ensure - f.close if f and not f.closed? - end - - @body_port = ports.shift - @parts = ports.map {|p| self.class.new(p, @config) } - end - - end # class Mail - -end # module TMail -# -# mailbox.rb -# -#-- -# Copyright (c) 1998-2003 Minero Aoki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Note: Originally licensed under LGPL v2+. Using MIT license for Rails -# with permission of Minero Aoki. -#++ - -require 'tmail/port' -require 'socket' -require 'mutex_m' - - -unless [].respond_to?(:sort_by) -module Enumerable#:nodoc: - def sort_by - map {|i| [yield(i), i] }.sort {|a,b| a.first <=> b.first }.map {|i| i[1] } - end -end -end - - -module TMail - - class MhMailbox - - PORT_CLASS = MhPort - - def initialize( dir ) - edir = File.expand_path(dir) - raise ArgumentError, "not directory: #{dir}"\ - unless FileTest.directory? edir - @dirname = edir - @last_file = nil - @last_atime = nil - end - - def directory - @dirname - end - - alias dirname directory - - attr_accessor :last_atime - - def inspect - "#<#{self.class} #{@dirname}>" - end - - def close - end - - def new_port - PORT_CLASS.new(next_file_name()) - end - - def each_port - mail_files().each do |path| - yield PORT_CLASS.new(path) - end - @last_atime = Time.now - end - - alias each each_port - - def reverse_each_port - mail_files().reverse_each do |path| - yield PORT_CLASS.new(path) - end - @last_atime = Time.now - end - - alias reverse_each reverse_each_port - - # old #each_mail returns Port - #def each_mail - # each_port do |port| - # yield Mail.new(port) - # end - #end - - def each_new_port( mtime = nil, &block ) - mtime ||= @last_atime - return each_port(&block) unless mtime - return unless File.mtime(@dirname) >= mtime - - mail_files().each do |path| - yield PORT_CLASS.new(path) if File.mtime(path) > mtime - end - @last_atime = Time.now - end - - private - - def mail_files - Dir.entries(@dirname)\ - .select {|s| /\A\d+\z/ === s }\ - .map {|s| s.to_i }\ - .sort\ - .map {|i| "#{@dirname}/#{i}" }\ - .select {|path| FileTest.file? path } - end - - def next_file_name - unless n = @last_file - n = 0 - Dir.entries(@dirname)\ - .select {|s| /\A\d+\z/ === s }\ - .map {|s| s.to_i }.sort\ - .each do |i| - next unless FileTest.file? "#{@dirname}/#{i}" - n = i - end - end - begin - n += 1 - end while FileTest.exist? "#{@dirname}/#{n}" - @last_file = n - - "#{@dirname}/#{n}" - end - - end # MhMailbox - - MhLoader = MhMailbox - - - class UNIXMbox - - def UNIXMbox.lock( fname ) - begin - f = File.open(fname) - f.flock File::LOCK_EX - yield f - ensure - f.flock File::LOCK_UN - f.close if f and not f.closed? - end - end - - class << self - alias newobj new - end - - def UNIXMbox.new( fname, tmpdir = nil, readonly = false ) - tmpdir = ENV['TEMP'] || ENV['TMP'] || '/tmp' - newobj(fname, "#{tmpdir}/ruby_tmail_#{$$}_#{rand()}", readonly, false) - end - - def UNIXMbox.static_new( fname, dir, readonly = false ) - newobj(fname, dir, readonly, true) - end - - def initialize( fname, mhdir, readonly, static ) - @filename = fname - @readonly = readonly - @closed = false - - Dir.mkdir mhdir - @real = MhMailbox.new(mhdir) - @finalizer = UNIXMbox.mkfinal(@real, @filename, !@readonly, !static) - ObjectSpace.define_finalizer self, @finalizer - end - - def UNIXMbox.mkfinal( mh, mboxfile, writeback_p, cleanup_p ) - lambda { - if writeback_p - lock(mboxfile) {|f| - mh.each_port do |port| - f.puts create_from_line(port) - port.ropen {|r| - f.puts r.read - } - end - } - end - if cleanup_p - Dir.foreach(mh.dirname) do |fname| - next if /\A\.\.?\z/ === fname - File.unlink "#{mh.dirname}/#{fname}" - end - Dir.rmdir mh.dirname - end - } - end - - # make _From line - def UNIXMbox.create_from_line( port ) - sprintf 'From %s %s', - fromaddr(), TextUtils.time2str(File.mtime(port.filename)) - end - - def UNIXMbox.fromaddr - h = HeaderField.new_from_port(port, 'Return-Path') || - HeaderField.new_from_port(port, 'From') or return 'nobody' - a = h.addrs[0] or return 'nobody' - a.spec - end - private_class_method :fromaddr - - def close - return if @closed - - ObjectSpace.undefine_finalizer self - @finalizer.call - @finalizer = nil - @real = nil - @closed = true - @updated = nil - end - - def each_port( &block ) - close_check - update - @real.each_port(&block) - end - - alias each each_port - - def reverse_each_port( &block ) - close_check - update - @real.reverse_each_port(&block) - end - - alias reverse_each reverse_each_port - - # old #each_mail returns Port - #def each_mail( &block ) - # each_port do |port| - # yield Mail.new(port) - # end - #end - - def each_new_port( mtime = nil ) - close_check - update - @real.each_new_port(mtime) {|p| yield p } - end - - def new_port - close_check - @real.new_port - end - - private - - def close_check - @closed and raise ArgumentError, 'accessing already closed mbox' - end - - def update - return if FileTest.zero?(@filename) - return if @updated and File.mtime(@filename) < @updated - w = nil - port = nil - time = nil - UNIXMbox.lock(@filename) {|f| - begin - f.each do |line| - if /\AFrom / === line - w.close if w - File.utime time, time, port.filename if time - - port = @real.new_port - w = port.wopen - time = fromline2time(line) - else - w.print line if w - end - end - ensure - if w and not w.closed? - w.close - File.utime time, time, port.filename if time - end - end - f.truncate(0) unless @readonly - @updated = Time.now - } - end - - def fromline2time( line ) - m = /\AFrom \S+ \w+ (\w+) (\d+) (\d+):(\d+):(\d+) (\d+)/.match(line) \ - or return nil - Time.local(m[6].to_i, m[1], m[2].to_i, m[3].to_i, m[4].to_i, m[5].to_i) - end - - end # UNIXMbox - - MboxLoader = UNIXMbox - - - class Maildir - - extend Mutex_m - - PORT_CLASS = MaildirPort - - @seq = 0 - def Maildir.unique_number - synchronize { - @seq += 1 - return @seq - } - end - - def initialize( dir = nil ) - @dirname = dir || ENV['MAILDIR'] - raise ArgumentError, "not directory: #{@dirname}"\ - unless FileTest.directory? @dirname - @new = "#{@dirname}/new" - @tmp = "#{@dirname}/tmp" - @cur = "#{@dirname}/cur" - end - - def directory - @dirname - end - - def inspect - "#<#{self.class} #{@dirname}>" - end - - def close - end - - def each_port - mail_files(@cur).each do |path| - yield PORT_CLASS.new(path) - end - end - - alias each each_port - - def reverse_each_port - mail_files(@cur).reverse_each do |path| - yield PORT_CLASS.new(path) - end - end - - alias reverse_each reverse_each_port - - def new_port - fname = nil - tmpfname = nil - newfname = nil - - begin - fname = "#{Time.now.to_i}.#{$$}_#{Maildir.unique_number}.#{Socket.gethostname}" - - tmpfname = "#{@tmp}/#{fname}" - newfname = "#{@new}/#{fname}" - end while FileTest.exist? tmpfname - - if block_given? - File.open(tmpfname, 'w') {|f| yield f } - File.rename tmpfname, newfname - PORT_CLASS.new(newfname) - else - File.open(tmpfname, 'w') {|f| f.write "\n\n" } - PORT_CLASS.new(tmpfname) - end - end - - def each_new_port - mail_files(@new).each do |path| - dest = @cur + '/' + File.basename(path) - File.rename path, dest - yield PORT_CLASS.new(dest) - end - - check_tmp - end - - TOO_OLD = 60 * 60 * 36 # 36 hour - - def check_tmp - old = Time.now.to_i - TOO_OLD - - each_filename(@tmp) do |full, fname| - if FileTest.file? full and - File.stat(full).mtime.to_i < old - File.unlink full - end - end - end - - private - - def mail_files( dir ) - Dir.entries(dir)\ - .select {|s| s[0] != ?. }\ - .sort_by {|s| s.slice(/\A\d+/).to_i }\ - .map {|s| "#{dir}/#{s}" }\ - .select {|path| FileTest.file? path } - end - - def each_filename( dir ) - Dir.foreach(dir) do |fname| - path = "#{dir}/#{fname}" - if fname[0] != ?. and FileTest.file? path - yield path, fname - end - end - end - - end # Maildir - - MaildirLoader = Maildir - -end # module TMail -require 'tmail/mailbox' -# -# net.rb -# -#-- -# Copyright (c) 1998-2003 Minero Aoki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Note: Originally licensed under LGPL v2+. Using MIT license for Rails -# with permission of Minero Aoki. -#++ - -require 'nkf' - - -module TMail - - class Mail - - def send_to( smtp ) - do_send_to(smtp) do - ready_to_send - end - end - - def send_text_to( smtp ) - do_send_to(smtp) do - ready_to_send - mime_encode - end - end - - def do_send_to( smtp ) - from = from_address or raise ArgumentError, 'no from address' - (dests = destinations).empty? and raise ArgumentError, 'no receipient' - yield - send_to_0 smtp, from, dests - end - private :do_send_to - - def send_to_0( smtp, from, to ) - smtp.ready(from, to) do |f| - encoded "\r\n", 'j', f, '' - end - end - - def ready_to_send - delete_no_send_fields - add_message_id - add_date - end - - NOSEND_FIELDS = %w( - received - bcc - ) - - def delete_no_send_fields - NOSEND_FIELDS.each do |nm| - delete nm - end - delete_if {|n,v| v.empty? } - end - - def add_message_id( fqdn = nil ) - self.message_id = ::TMail::new_message_id(fqdn) - end - - def add_date - self.date = Time.now - end - - def mime_encode - if parts.empty? - mime_encode_singlepart - else - mime_encode_multipart true - end - end - - def mime_encode_singlepart - self.mime_version = '1.0' - b = body - if NKF.guess(b) != NKF::BINARY - mime_encode_text b - else - mime_encode_binary b - end - end - - def mime_encode_text( body ) - self.body = NKF.nkf('-j -m0', body) - self.set_content_type 'text', 'plain', {'charset' => 'iso-2022-jp'} - self.encoding = '7bit' - end - - def mime_encode_binary( body ) - self.body = [body].pack('m') - self.set_content_type 'application', 'octet-stream' - self.encoding = 'Base64' - end - - def mime_encode_multipart( top = true ) - self.mime_version = '1.0' if top - self.set_content_type 'multipart', 'mixed' - e = encoding(nil) - if e and not /\A(?:7bit|8bit|binary)\z/i === e - raise ArgumentError, - 'using C.T.Encoding with multipart mail is not permitted' - end - end - - def create_empty_mail - self.class.new(StringPort.new(''), @config) - end - - def create_reply - setup_reply create_empty_mail() - end - - def setup_reply( m ) - if tmp = reply_addresses(nil) - m.to_addrs = tmp - end - - mid = message_id(nil) - tmp = references(nil) || [] - tmp.push mid if mid - m.in_reply_to = [mid] if mid - m.references = tmp unless tmp.empty? - m.subject = 'Re: ' + subject('').sub(/\A(?:\s*re:)+/i, '') - - m - end - - def create_forward - setup_forward create_empty_mail() - end - - def setup_forward( mail ) - m = Mail.new(StringPort.new('')) - m.body = decoded - m.set_content_type 'message', 'rfc822' - m.encoding = encoding('7bit') - mail.parts.push m - end - - end - - - class DeleteFields - - NOSEND_FIELDS = %w( - received - bcc - ) - - def initialize( nosend = nil, delempty = true ) - @no_send_fields = nosend || NOSEND_FIELDS.dup - @delete_empty_fields = delempty - end - - attr :no_send_fields - attr :delete_empty_fields, true - - def exec( mail ) - @no_send_fields.each do |nm| - delete nm - end - delete_if {|n,v| v.empty? } if @delete_empty_fields - end - - end - - - class AddMessageId - - def initialize( fqdn = nil ) - @fqdn = fqdn - end - - attr :fqdn, true - - def exec( mail ) - mail.message_id = ::TMail::new_msgid(@fqdn) - end - - end - - - class AddDate - - def exec( mail ) - mail.date = Time.now - end - - end - - - class MimeEncodeAuto - - def initialize( s = nil, m = nil ) - @singlepart_composer = s || MimeEncodeSingle.new - @multipart_composer = m || MimeEncodeMulti.new - end - - attr :singlepart_composer - attr :multipart_composer - - def exec( mail ) - if mail._builtin_multipart? - then @multipart_composer - else @singlepart_composer end.exec mail - end - - end - - - class MimeEncodeSingle - - def exec( mail ) - mail.mime_version = '1.0' - b = mail.body - if NKF.guess(b) != NKF::BINARY - on_text b - else - on_binary b - end - end - - def on_text( body ) - mail.body = NKF.nkf('-j -m0', body) - mail.set_content_type 'text', 'plain', {'charset' => 'iso-2022-jp'} - mail.encoding = '7bit' - end - - def on_binary( body ) - mail.body = [body].pack('m') - mail.set_content_type 'application', 'octet-stream' - mail.encoding = 'Base64' - end - - end - - - class MimeEncodeMulti - - def exec( mail, top = true ) - mail.mime_version = '1.0' if top - mail.set_content_type 'multipart', 'mixed' - e = encoding(nil) - if e and not /\A(?:7bit|8bit|binary)\z/i === e - raise ArgumentError, - 'using C.T.Encoding with multipart mail is not permitted' - end - mail.parts.each do |m| - exec m, false if m._builtin_multipart? - end - end - - end - -end # module TMail -# -# obsolete.rb -# -#-- -# Copyright (c) 1998-2003 Minero Aoki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Note: Originally licensed under LGPL v2+. Using MIT license for Rails -# with permission of Minero Aoki. -#++ - -module TMail - - # mail.rb - class Mail - alias include? key? - alias has_key? key? - - def values - ret = [] - each_field {|v| ret.push v } - ret - end - - def value?( val ) - HeaderField === val or return false - - [ @header[val.name.downcase] ].flatten.include? val - end - - alias has_value? value? - end - - - # facade.rb - class Mail - def from_addr( default = nil ) - addr, = from_addrs(nil) - addr || default - end - - def from_address( default = nil ) - if a = from_addr(nil) - a.spec - else - default - end - end - - alias from_address= from_addrs= - - def from_phrase( default = nil ) - if a = from_addr(nil) - a.phrase - else - default - end - end - - alias msgid message_id - alias msgid= message_id= - - alias each_dest each_destination - end - - - # address.rb - class Address - alias route routes - alias addr spec - - def spec=( str ) - @local, @domain = str.split(/@/,2).map {|s| s.split(/\./) } - end - - alias addr= spec= - alias address= spec= - end - - - # mbox.rb - class MhMailbox - alias new_mail new_port - alias each_mail each_port - alias each_newmail each_new_port - end - class UNIXMbox - alias new_mail new_port - alias each_mail each_port - alias each_newmail each_new_port - end - class Maildir - alias new_mail new_port - alias each_mail each_port - alias each_newmail each_new_port - end - - - # utils.rb - extend TextUtils - - class << self - alias msgid? message_id? - alias boundary new_boundary - alias msgid new_message_id - alias new_msgid new_message_id - end - - def Mail.boundary - ::TMail.new_boundary - end - - def Mail.msgid - ::TMail.new_message_id - end - -end # module TMail -# -# DO NOT MODIFY!!!! -# This file is automatically generated by racc 1.4.3 -# from racc grammer file "parser.y". -# -# -# parser.rb: generated by racc (runtime embedded) -# - -###### racc/parser.rb - -unless $".index 'racc/parser.rb' -$".push 'racc/parser.rb' - -self.class.module_eval <<'..end /home/aamine/lib/ruby/racc/parser.rb modeval..idb76f2e220d', '/home/aamine/lib/ruby/racc/parser.rb', 1 -# -# parser.rb -# -# Copyright (c) 1999-2003 Minero Aoki -# -# This program is free software. -# You can distribute/modify this program under the same terms of ruby. -# -# As a special exception, when this code is copied by Racc -# into a Racc output file, you may use that output file -# without restriction. -# -# $Id: parser.rb,v 1.1.1.1 2004/10/14 11:59:58 webster132 Exp $ -# - -unless defined? NotImplementedError - NotImplementedError = NotImplementError -end - - -module Racc - class ParseError < StandardError; end -end -unless defined?(::ParseError) - ParseError = Racc::ParseError -end - - -module Racc - - unless defined? Racc_No_Extentions - Racc_No_Extentions = false - end - - class Parser - - Racc_Runtime_Version = '1.4.3' - Racc_Runtime_Revision = '$Revision: 1.1.1.1 $'.split(/\s+/)[1] - - Racc_Runtime_Core_Version_R = '1.4.3' - Racc_Runtime_Core_Revision_R = '$Revision: 1.1.1.1 $'.split(/\s+/)[1] - begin - require 'racc/cparse' - # Racc_Runtime_Core_Version_C = (defined in extention) - Racc_Runtime_Core_Revision_C = Racc_Runtime_Core_Id_C.split(/\s+/)[2] - unless new.respond_to?(:_racc_do_parse_c, true) - raise LoadError, 'old cparse.so' - end - if Racc_No_Extentions - raise LoadError, 'selecting ruby version of racc runtime core' - end - - Racc_Main_Parsing_Routine = :_racc_do_parse_c - Racc_YY_Parse_Method = :_racc_yyparse_c - Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_C - Racc_Runtime_Core_Revision = Racc_Runtime_Core_Revision_C - Racc_Runtime_Type = 'c' - rescue LoadError - Racc_Main_Parsing_Routine = :_racc_do_parse_rb - Racc_YY_Parse_Method = :_racc_yyparse_rb - Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_R - Racc_Runtime_Core_Revision = Racc_Runtime_Core_Revision_R - Racc_Runtime_Type = 'ruby' - end - - def self.racc_runtime_type - Racc_Runtime_Type - end - - private - - def _racc_setup - @yydebug = false unless self.class::Racc_debug_parser - @yydebug = false unless defined? @yydebug - if @yydebug - @racc_debug_out = $stderr unless defined? @racc_debug_out - @racc_debug_out ||= $stderr - end - arg = self.class::Racc_arg - arg[13] = true if arg.size < 14 - arg - end - - def _racc_init_sysvars - @racc_state = [0] - @racc_tstack = [] - @racc_vstack = [] - - @racc_t = nil - @racc_val = nil - - @racc_read_next = true - - @racc_user_yyerror = false - @racc_error_status = 0 - end - - - ### - ### do_parse - ### - - def do_parse - __send__ Racc_Main_Parsing_Routine, _racc_setup(), false - end - - def next_token - raise NotImplementedError, "#{self.class}\#next_token is not defined" - end - - def _racc_do_parse_rb( arg, in_debug ) - action_table, action_check, action_default, action_pointer, - goto_table, goto_check, goto_default, goto_pointer, - nt_base, reduce_table, token_table, shift_n, - reduce_n, use_result, * = arg - - _racc_init_sysvars - tok = act = i = nil - nerr = 0 - - catch(:racc_end_parse) { - while true - if i = action_pointer[@racc_state[-1]] - if @racc_read_next - if @racc_t != 0 # not EOF - tok, @racc_val = next_token() - unless tok # EOF - @racc_t = 0 - else - @racc_t = (token_table[tok] or 1) # error token - end - racc_read_token(@racc_t, tok, @racc_val) if @yydebug - @racc_read_next = false - end - end - i += @racc_t - if i >= 0 and - act = action_table[i] and - action_check[i] == @racc_state[-1] - ; - else - act = action_default[@racc_state[-1]] - end - else - act = action_default[@racc_state[-1]] - end - while act = _racc_evalact(act, arg) - end - end - } - end - - - ### - ### yyparse - ### - - def yyparse( recv, mid ) - __send__ Racc_YY_Parse_Method, recv, mid, _racc_setup(), true - end - - def _racc_yyparse_rb( recv, mid, arg, c_debug ) - action_table, action_check, action_default, action_pointer, - goto_table, goto_check, goto_default, goto_pointer, - nt_base, reduce_table, token_table, shift_n, - reduce_n, use_result, * = arg - - _racc_init_sysvars - tok = nil - act = nil - i = nil - nerr = 0 - - - catch(:racc_end_parse) { - until i = action_pointer[@racc_state[-1]] - while act = _racc_evalact(action_default[@racc_state[-1]], arg) - end - end - - recv.__send__(mid) do |tok, val| -# $stderr.puts "rd: tok=#{tok}, val=#{val}" - unless tok - @racc_t = 0 - else - @racc_t = (token_table[tok] or 1) # error token - end - @racc_val = val - @racc_read_next = false - - i += @racc_t - if i >= 0 and - act = action_table[i] and - action_check[i] == @racc_state[-1] - ; -# $stderr.puts "01: act=#{act}" - else - act = action_default[@racc_state[-1]] -# $stderr.puts "02: act=#{act}" -# $stderr.puts "curstate=#{@racc_state[-1]}" - end - - while act = _racc_evalact(act, arg) - end - - while not (i = action_pointer[@racc_state[-1]]) or - not @racc_read_next or - @racc_t == 0 # $ - if i and i += @racc_t and - i >= 0 and - act = action_table[i] and - action_check[i] == @racc_state[-1] - ; -# $stderr.puts "03: act=#{act}" - else -# $stderr.puts "04: act=#{act}" - act = action_default[@racc_state[-1]] - end - - while act = _racc_evalact(act, arg) - end - end - end - } - end - - - ### - ### common - ### - - def _racc_evalact( act, arg ) -# $stderr.puts "ea: act=#{act}" - action_table, action_check, action_default, action_pointer, - goto_table, goto_check, goto_default, goto_pointer, - nt_base, reduce_table, token_table, shift_n, - reduce_n, use_result, * = arg -nerr = 0 # tmp - - if act > 0 and act < shift_n - # - # shift - # - - if @racc_error_status > 0 - @racc_error_status -= 1 unless @racc_t == 1 # error token - end - - @racc_vstack.push @racc_val - @racc_state.push act - @racc_read_next = true - - if @yydebug - @racc_tstack.push @racc_t - racc_shift @racc_t, @racc_tstack, @racc_vstack - end - - elsif act < 0 and act > -reduce_n - # - # reduce - # - - code = catch(:racc_jump) { - @racc_state.push _racc_do_reduce(arg, act) - false - } - if code - case code - when 1 # yyerror - @racc_user_yyerror = true # user_yyerror - return -reduce_n - when 2 # yyaccept - return shift_n - else - raise RuntimeError, '[Racc Bug] unknown jump code' - end - end - - elsif act == shift_n - # - # accept - # - - racc_accept if @yydebug - throw :racc_end_parse, @racc_vstack[0] - - elsif act == -reduce_n - # - # error - # - - case @racc_error_status - when 0 - unless arg[21] # user_yyerror - nerr += 1 - on_error @racc_t, @racc_val, @racc_vstack - end - when 3 - if @racc_t == 0 # is $ - throw :racc_end_parse, nil - end - @racc_read_next = true - end - @racc_user_yyerror = false - @racc_error_status = 3 - - while true - if i = action_pointer[@racc_state[-1]] - i += 1 # error token - if i >= 0 and - (act = action_table[i]) and - action_check[i] == @racc_state[-1] - break - end - end - - throw :racc_end_parse, nil if @racc_state.size < 2 - @racc_state.pop - @racc_vstack.pop - if @yydebug - @racc_tstack.pop - racc_e_pop @racc_state, @racc_tstack, @racc_vstack - end - end - - return act - - else - raise RuntimeError, "[Racc Bug] unknown action #{act.inspect}" - end - - racc_next_state(@racc_state[-1], @racc_state) if @yydebug - - nil - end - - def _racc_do_reduce( arg, act ) - action_table, action_check, action_default, action_pointer, - goto_table, goto_check, goto_default, goto_pointer, - nt_base, reduce_table, token_table, shift_n, - reduce_n, use_result, * = arg - state = @racc_state - vstack = @racc_vstack - tstack = @racc_tstack - - i = act * -3 - len = reduce_table[i] - reduce_to = reduce_table[i+1] - method_id = reduce_table[i+2] - void_array = [] - - tmp_t = tstack[-len, len] if @yydebug - tmp_v = vstack[-len, len] - tstack[-len, len] = void_array if @yydebug - vstack[-len, len] = void_array - state[-len, len] = void_array - - # tstack must be updated AFTER method call - if use_result - vstack.push __send__(method_id, tmp_v, vstack, tmp_v[0]) - else - vstack.push __send__(method_id, tmp_v, vstack) - end - tstack.push reduce_to - - racc_reduce(tmp_t, reduce_to, tstack, vstack) if @yydebug - - k1 = reduce_to - nt_base - if i = goto_pointer[k1] - i += state[-1] - if i >= 0 and (curstate = goto_table[i]) and goto_check[i] == k1 - return curstate - end - end - goto_default[k1] - end - - def on_error( t, val, vstack ) - raise ParseError, sprintf("\nparse error on value %s (%s)", - val.inspect, token_to_str(t) || '?') - end - - def yyerror - throw :racc_jump, 1 - end - - def yyaccept - throw :racc_jump, 2 - end - - def yyerrok - @racc_error_status = 0 - end - - - # for debugging output - - def racc_read_token( t, tok, val ) - @racc_debug_out.print 'read ' - @racc_debug_out.print tok.inspect, '(', racc_token2str(t), ') ' - @racc_debug_out.puts val.inspect - @racc_debug_out.puts - end - - def racc_shift( tok, tstack, vstack ) - @racc_debug_out.puts "shift #{racc_token2str tok}" - racc_print_stacks tstack, vstack - @racc_debug_out.puts - end - - def racc_reduce( toks, sim, tstack, vstack ) - out = @racc_debug_out - out.print 'reduce ' - if toks.empty? - out.print ' ' - else - toks.each {|t| out.print ' ', racc_token2str(t) } - end - out.puts " --> #{racc_token2str(sim)}" - - racc_print_stacks tstack, vstack - @racc_debug_out.puts - end - - def racc_accept - @racc_debug_out.puts 'accept' - @racc_debug_out.puts - end - - def racc_e_pop( state, tstack, vstack ) - @racc_debug_out.puts 'error recovering mode: pop token' - racc_print_states state - racc_print_stacks tstack, vstack - @racc_debug_out.puts - end - - def racc_next_state( curstate, state ) - @racc_debug_out.puts "goto #{curstate}" - racc_print_states state - @racc_debug_out.puts - end - - def racc_print_stacks( t, v ) - out = @racc_debug_out - out.print ' [' - t.each_index do |i| - out.print ' (', racc_token2str(t[i]), ' ', v[i].inspect, ')' - end - out.puts ' ]' - end - - def racc_print_states( s ) - out = @racc_debug_out - out.print ' [' - s.each {|st| out.print ' ', st } - out.puts ' ]' - end - - def racc_token2str( tok ) - self.class::Racc_token_to_s_table[tok] or - raise RuntimeError, "[Racc Bug] can't convert token #{tok} to string" - end - - def token_to_str( t ) - self.class::Racc_token_to_s_table[t] - end - - end - -end -..end /home/aamine/lib/ruby/racc/parser.rb modeval..idb76f2e220d -end # end of racc/parser.rb - - -# -# parser.rb -# -#-- -# Copyright (c) 1998-2003 Minero Aoki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Note: Originally licensed under LGPL v2+. Using MIT license for Rails -# with permission of Minero Aoki. -#++ - -require 'tmail/scanner' -require 'tmail/utils' - - -module TMail - - class Parser < Racc::Parser - -module_eval <<'..end parser.y modeval..id43721faf1c', 'parser.y', 331 - - include TextUtils - - def self.parse( ident, str, cmt = nil ) - new.parse(ident, str, cmt) - end - - MAILP_DEBUG = false - - def initialize - self.debug = MAILP_DEBUG - end - - def debug=( flag ) - @yydebug = flag && Racc_debug_parser - @scanner_debug = flag - end - - def debug - @yydebug - end - - def parse( ident, str, comments = nil ) - @scanner = Scanner.new(str, ident, comments) - @scanner.debug = @scanner_debug - @first = [ident, ident] - result = yyparse(self, :parse_in) - comments.map! {|c| to_kcode(c) } if comments - result - end - - private - - def parse_in( &block ) - yield @first - @scanner.scan(&block) - end - - def on_error( t, val, vstack ) - raise SyntaxError, "parse error on token #{racc_token2str t}" - end - -..end parser.y modeval..id43721faf1c - -##### racc 1.4.3 generates ### - -racc_reduce_table = [ - 0, 0, :racc_error, - 2, 35, :_reduce_1, - 2, 35, :_reduce_2, - 2, 35, :_reduce_3, - 2, 35, :_reduce_4, - 2, 35, :_reduce_5, - 2, 35, :_reduce_6, - 2, 35, :_reduce_7, - 2, 35, :_reduce_8, - 2, 35, :_reduce_9, - 2, 35, :_reduce_10, - 2, 35, :_reduce_11, - 2, 35, :_reduce_12, - 6, 36, :_reduce_13, - 0, 48, :_reduce_none, - 2, 48, :_reduce_none, - 3, 49, :_reduce_16, - 5, 49, :_reduce_17, - 1, 50, :_reduce_18, - 7, 37, :_reduce_19, - 0, 51, :_reduce_none, - 2, 51, :_reduce_21, - 0, 52, :_reduce_none, - 2, 52, :_reduce_23, - 1, 58, :_reduce_24, - 3, 58, :_reduce_25, - 2, 58, :_reduce_26, - 0, 53, :_reduce_none, - 2, 53, :_reduce_28, - 0, 54, :_reduce_29, - 3, 54, :_reduce_30, - 0, 55, :_reduce_none, - 2, 55, :_reduce_32, - 2, 55, :_reduce_33, - 0, 56, :_reduce_none, - 2, 56, :_reduce_35, - 1, 61, :_reduce_36, - 1, 61, :_reduce_37, - 0, 57, :_reduce_none, - 2, 57, :_reduce_39, - 1, 38, :_reduce_none, - 1, 38, :_reduce_none, - 3, 38, :_reduce_none, - 1, 46, :_reduce_none, - 1, 46, :_reduce_none, - 1, 46, :_reduce_none, - 1, 39, :_reduce_none, - 2, 39, :_reduce_47, - 1, 64, :_reduce_48, - 3, 64, :_reduce_49, - 1, 68, :_reduce_none, - 1, 68, :_reduce_none, - 1, 69, :_reduce_52, - 3, 69, :_reduce_53, - 1, 47, :_reduce_none, - 1, 47, :_reduce_none, - 2, 47, :_reduce_56, - 2, 67, :_reduce_none, - 3, 65, :_reduce_58, - 2, 65, :_reduce_59, - 1, 70, :_reduce_60, - 2, 70, :_reduce_61, - 4, 62, :_reduce_62, - 3, 62, :_reduce_63, - 2, 72, :_reduce_none, - 2, 73, :_reduce_65, - 4, 73, :_reduce_66, - 3, 63, :_reduce_67, - 1, 63, :_reduce_68, - 1, 74, :_reduce_none, - 2, 74, :_reduce_70, - 1, 71, :_reduce_71, - 3, 71, :_reduce_72, - 1, 59, :_reduce_73, - 3, 59, :_reduce_74, - 1, 76, :_reduce_75, - 2, 76, :_reduce_76, - 1, 75, :_reduce_none, - 1, 75, :_reduce_none, - 1, 75, :_reduce_none, - 1, 77, :_reduce_none, - 1, 77, :_reduce_none, - 1, 77, :_reduce_none, - 1, 66, :_reduce_none, - 2, 66, :_reduce_none, - 3, 60, :_reduce_85, - 1, 40, :_reduce_86, - 3, 40, :_reduce_87, - 1, 79, :_reduce_none, - 2, 79, :_reduce_89, - 1, 41, :_reduce_90, - 2, 41, :_reduce_91, - 3, 42, :_reduce_92, - 5, 43, :_reduce_93, - 3, 43, :_reduce_94, - 0, 80, :_reduce_95, - 5, 80, :_reduce_96, - 1, 82, :_reduce_none, - 1, 82, :_reduce_none, - 1, 44, :_reduce_99, - 3, 45, :_reduce_100, - 0, 81, :_reduce_none, - 1, 81, :_reduce_none, - 1, 78, :_reduce_none, - 1, 78, :_reduce_none, - 1, 78, :_reduce_none, - 1, 78, :_reduce_none, - 1, 78, :_reduce_none, - 1, 78, :_reduce_none, - 1, 78, :_reduce_none ] - -racc_reduce_n = 110 - -racc_shift_n = 168 - -racc_action_table = [ - -70, -69, 23, 25, 146, 147, 29, 31, 105, 106, - 16, 17, 20, 22, 136, 27, -70, -69, 32, 101, - -70, -69, 154, 100, 113, 115, -70, -69, -70, 109, - 75, 23, 25, 101, 155, 29, 31, 142, 143, 16, - 17, 20, 22, 107, 27, 23, 25, 32, 98, 29, - 31, 96, 94, 16, 17, 20, 22, 78, 27, 23, - 25, 32, 112, 29, 31, 74, 91, 16, 17, 20, - 22, 88, 117, 92, 81, 32, 23, 25, 80, 123, - 29, 31, 100, 125, 16, 17, 20, 22, 126, 23, - 25, 109, 32, 29, 31, 91, 128, 16, 17, 20, - 22, 129, 27, 23, 25, 32, 101, 29, 31, 101, - 130, 16, 17, 20, 22, 79, 52, 23, 25, 32, - 78, 29, 31, 133, 78, 16, 17, 20, 22, 77, - 23, 25, 75, 32, 29, 31, 65, 62, 16, 17, - 20, 22, 139, 23, 25, 101, 32, 29, 31, 60, - 100, 16, 17, 20, 22, 44, 27, 101, 148, 32, - 23, 25, 120, 149, 29, 31, 152, 153, 16, 17, - 20, 22, 42, 27, 157, 159, 32, 23, 25, 120, - 40, 29, 31, 15, 164, 16, 17, 20, 22, 40, - 27, 23, 25, 32, 68, 29, 31, 166, 167, 16, - 17, 20, 22, nil, 27, 23, 25, 32, nil, 29, - 31, 74, nil, 16, 17, 20, 22, nil, 23, 25, - nil, 32, 29, 31, nil, nil, 16, 17, 20, 22, - nil, 23, 25, nil, 32, 29, 31, nil, nil, 16, - 17, 20, 22, nil, 23, 25, nil, 32, 29, 31, - nil, nil, 16, 17, 20, 22, nil, 23, 25, nil, - 32, 29, 31, nil, nil, 16, 17, 20, 22, nil, - 27, 23, 25, 32, nil, 29, 31, nil, nil, 16, - 17, 20, 22, nil, 23, 25, nil, 32, 29, 31, - nil, nil, 16, 17, 20, 22, nil, 23, 25, nil, - 32, 29, 31, nil, nil, 16, 17, 20, 22, nil, - 84, 25, nil, 32, 29, 31, nil, 87, 16, 17, - 20, 22, 4, 6, 7, 8, 9, 10, 11, 12, - 13, 1, 2, 3, 84, 25, nil, nil, 29, 31, - nil, 87, 16, 17, 20, 22, 84, 25, nil, nil, - 29, 31, nil, 87, 16, 17, 20, 22, 84, 25, - nil, nil, 29, 31, nil, 87, 16, 17, 20, 22, - 84, 25, nil, nil, 29, 31, nil, 87, 16, 17, - 20, 22, 84, 25, nil, nil, 29, 31, nil, 87, - 16, 17, 20, 22, 84, 25, nil, nil, 29, 31, - nil, 87, 16, 17, 20, 22 ] - -racc_action_check = [ - 75, 28, 68, 68, 136, 136, 68, 68, 72, 72, - 68, 68, 68, 68, 126, 68, 75, 28, 68, 67, - 75, 28, 143, 66, 86, 86, 75, 28, 75, 75, - 28, 3, 3, 86, 143, 3, 3, 134, 134, 3, - 3, 3, 3, 73, 3, 152, 152, 3, 62, 152, - 152, 60, 56, 152, 152, 152, 152, 51, 152, 52, - 52, 152, 80, 52, 52, 52, 50, 52, 52, 52, - 52, 45, 89, 52, 42, 52, 71, 71, 41, 96, - 71, 71, 97, 98, 71, 71, 71, 71, 100, 7, - 7, 101, 71, 7, 7, 102, 104, 7, 7, 7, - 7, 105, 7, 8, 8, 7, 108, 8, 8, 111, - 112, 8, 8, 8, 8, 40, 8, 9, 9, 8, - 36, 9, 9, 117, 121, 9, 9, 9, 9, 33, - 10, 10, 70, 9, 10, 10, 13, 12, 10, 10, - 10, 10, 130, 2, 2, 131, 10, 2, 2, 11, - 135, 2, 2, 2, 2, 6, 2, 138, 139, 2, - 90, 90, 90, 140, 90, 90, 141, 142, 90, 90, - 90, 90, 5, 90, 148, 151, 90, 127, 127, 127, - 4, 127, 127, 1, 157, 127, 127, 127, 127, 159, - 127, 26, 26, 127, 26, 26, 26, 163, 164, 26, - 26, 26, 26, nil, 26, 27, 27, 26, nil, 27, - 27, 27, nil, 27, 27, 27, 27, nil, 155, 155, - nil, 27, 155, 155, nil, nil, 155, 155, 155, 155, - nil, 122, 122, nil, 155, 122, 122, nil, nil, 122, - 122, 122, 122, nil, 76, 76, nil, 122, 76, 76, - nil, nil, 76, 76, 76, 76, nil, 38, 38, nil, - 76, 38, 38, nil, nil, 38, 38, 38, 38, nil, - 38, 55, 55, 38, nil, 55, 55, nil, nil, 55, - 55, 55, 55, nil, 94, 94, nil, 55, 94, 94, - nil, nil, 94, 94, 94, 94, nil, 59, 59, nil, - 94, 59, 59, nil, nil, 59, 59, 59, 59, nil, - 114, 114, nil, 59, 114, 114, nil, 114, 114, 114, - 114, 114, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 77, 77, nil, nil, 77, 77, - nil, 77, 77, 77, 77, 77, 44, 44, nil, nil, - 44, 44, nil, 44, 44, 44, 44, 44, 113, 113, - nil, nil, 113, 113, nil, 113, 113, 113, 113, 113, - 88, 88, nil, nil, 88, 88, nil, 88, 88, 88, - 88, 88, 74, 74, nil, nil, 74, 74, nil, 74, - 74, 74, 74, 74, 129, 129, nil, nil, 129, 129, - nil, 129, 129, 129, 129, 129 ] - -racc_action_pointer = [ - 320, 152, 129, 17, 165, 172, 137, 75, 89, 103, - 116, 135, 106, 105, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, 177, 191, 1, nil, - nil, nil, nil, 109, nil, nil, 94, nil, 243, nil, - 99, 64, 74, nil, 332, 52, nil, nil, nil, nil, - 50, 31, 45, nil, nil, 257, 36, nil, nil, 283, - 22, nil, 16, nil, nil, nil, -3, -10, -12, nil, - 103, 62, -8, 15, 368, 0, 230, 320, nil, nil, - 47, nil, nil, nil, nil, nil, 4, nil, 356, 50, - 146, nil, nil, nil, 270, nil, 65, 56, 52, nil, - 57, 62, 79, nil, 68, 81, nil, nil, 77, nil, - nil, 80, 96, 344, 296, nil, nil, 108, nil, nil, - nil, 98, 217, nil, nil, nil, -19, 163, nil, 380, - 128, 116, nil, nil, 14, 124, -26, nil, 128, 141, - 148, 141, 152, 7, nil, nil, nil, nil, 160, nil, - nil, 149, 31, nil, nil, 204, nil, 167, nil, 174, - nil, nil, nil, 169, 184, nil, nil, nil ] - -racc_action_default = [ - -110, -110, -110, -110, -14, -110, -20, -110, -110, -110, - -110, -110, -110, -110, -10, -95, -106, -107, -77, -44, - -108, -11, -109, -79, -43, -103, -110, -110, -60, -104, - -55, -105, -78, -68, -54, -71, -45, -12, -110, -1, - -110, -110, -110, -2, -110, -22, -51, -48, -50, -3, - -40, -41, -110, -46, -4, -86, -5, -88, -6, -90, - -110, -7, -95, -8, -9, -99, -101, -61, -59, -56, - -69, -110, -110, -110, -110, -75, -110, -110, -57, -15, - -110, 168, -73, -80, -82, -21, -24, -81, -110, -27, - -110, -83, -47, -89, -110, -91, -110, -101, -110, -100, - -102, -75, -58, -52, -110, -110, -64, -63, -65, -76, - -72, -67, -110, -110, -110, -26, -23, -110, -29, -49, - -84, -42, -87, -92, -94, -95, -110, -110, -62, -110, - -110, -25, -74, -28, -31, -101, -110, -53, -66, -110, - -110, -34, -110, -110, -93, -96, -98, -97, -110, -18, - -13, -38, -110, -30, -33, -110, -32, -16, -19, -14, - -35, -36, -37, -110, -110, -39, -85, -17 ] - -racc_goto_table = [ - 39, 67, 70, 73, 24, 37, 69, 66, 36, 38, - 57, 59, 55, 67, 108, 83, 90, 111, 69, 99, - 85, 49, 53, 76, 158, 134, 141, 70, 73, 151, - 118, 89, 45, 156, 160, 150, 140, 21, 14, 19, - 119, 102, 64, 63, 61, 83, 70, 104, 83, 58, - 124, 132, 56, 131, 97, 54, 93, 43, 5, 83, - 95, 145, 76, nil, 116, 76, nil, nil, 127, 138, - 103, nil, nil, nil, 38, nil, nil, 110, nil, nil, - nil, nil, nil, nil, 83, 83, nil, nil, 144, nil, - nil, nil, nil, nil, nil, 57, 121, 122, nil, nil, - 83, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, 135, nil, nil, - nil, nil, nil, 93, nil, nil, nil, 70, 162, 137, - 70, 163, 161, 38, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 165 ] - -racc_goto_check = [ - 2, 37, 37, 29, 13, 13, 28, 46, 31, 36, - 41, 41, 45, 37, 25, 44, 32, 25, 28, 47, - 24, 4, 4, 42, 23, 20, 21, 37, 29, 22, - 19, 18, 17, 26, 27, 16, 15, 12, 11, 33, - 34, 35, 10, 9, 8, 44, 37, 29, 44, 7, - 47, 43, 6, 25, 46, 5, 41, 3, 1, 44, - 41, 48, 42, nil, 24, 42, nil, nil, 32, 25, - 13, nil, nil, nil, 36, nil, nil, 41, nil, nil, - nil, nil, nil, nil, 44, 44, nil, nil, 47, nil, - nil, nil, nil, nil, nil, 41, 31, 45, nil, nil, - 44, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, 46, nil, nil, - nil, nil, nil, 41, nil, nil, nil, 37, 29, 13, - 37, 29, 28, 36, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 2 ] - -racc_goto_pointer = [ - nil, 58, -4, 51, 14, 47, 43, 39, 33, 31, - 29, 37, 35, 2, nil, -94, -105, 26, -14, -59, - -93, -108, -112, -127, -24, -60, -110, -118, -20, -24, - nil, 6, -34, 37, -50, -27, 6, -25, nil, nil, - nil, 1, -5, -63, -29, 3, -8, -47, -75 ] - -racc_goto_default = [ - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, 48, 41, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 86, nil, nil, 30, 34, - 50, 51, nil, 46, 47, nil, 26, 28, 71, 72, - 33, 35, 114, 82, 18, nil, nil, nil, nil ] - -racc_token_table = { - false => 0, - Object.new => 1, - :DATETIME => 2, - :RECEIVED => 3, - :MADDRESS => 4, - :RETPATH => 5, - :KEYWORDS => 6, - :ENCRYPTED => 7, - :MIMEVERSION => 8, - :CTYPE => 9, - :CENCODING => 10, - :CDISPOSITION => 11, - :ADDRESS => 12, - :MAILBOX => 13, - :DIGIT => 14, - :ATOM => 15, - "," => 16, - ":" => 17, - :FROM => 18, - :BY => 19, - "@" => 20, - :DOMLIT => 21, - :VIA => 22, - :WITH => 23, - :ID => 24, - :FOR => 25, - ";" => 26, - "<" => 27, - ">" => 28, - "." => 29, - :QUOTED => 30, - :TOKEN => 31, - "/" => 32, - "=" => 33 } - -racc_use_result_var = false - -racc_nt_base = 34 - -Racc_arg = [ - racc_action_table, - racc_action_check, - racc_action_default, - racc_action_pointer, - racc_goto_table, - racc_goto_check, - racc_goto_default, - racc_goto_pointer, - racc_nt_base, - racc_reduce_table, - racc_token_table, - racc_shift_n, - racc_reduce_n, - racc_use_result_var ] - -Racc_token_to_s_table = [ -'$end', -'error', -'DATETIME', -'RECEIVED', -'MADDRESS', -'RETPATH', -'KEYWORDS', -'ENCRYPTED', -'MIMEVERSION', -'CTYPE', -'CENCODING', -'CDISPOSITION', -'ADDRESS', -'MAILBOX', -'DIGIT', -'ATOM', -'","', -'":"', -'FROM', -'BY', -'"@"', -'DOMLIT', -'VIA', -'WITH', -'ID', -'FOR', -'";"', -'"<"', -'">"', -'"."', -'QUOTED', -'TOKEN', -'"/"', -'"="', -'$start', -'content', -'datetime', -'received', -'addrs_TOP', -'retpath', -'keys', -'enc', -'version', -'ctype', -'cencode', -'cdisp', -'addr_TOP', -'mbox', -'day', -'hour', -'zone', -'from', -'by', -'via', -'with', -'id', -'for', -'received_datetime', -'received_domain', -'domain', -'msgid', -'received_addrspec', -'routeaddr', -'spec', -'addrs', -'group_bare', -'commas', -'group', -'addr', -'mboxes', -'addr_phrase', -'local_head', -'routes', -'at_domains', -'local', -'word', -'dots', -'domword', -'atom', -'phrase', -'params', -'opt_semicolon', -'value'] - -Racc_debug_parser = false - -##### racc system variables end ##### - - # reduce 0 omitted - -module_eval <<'.,.,', 'parser.y', 16 - def _reduce_1( val, _values) - val[1] - end -.,., - -module_eval <<'.,.,', 'parser.y', 17 - def _reduce_2( val, _values) - val[1] - end -.,., - -module_eval <<'.,.,', 'parser.y', 18 - def _reduce_3( val, _values) - val[1] - end -.,., - -module_eval <<'.,.,', 'parser.y', 19 - def _reduce_4( val, _values) - val[1] - end -.,., - -module_eval <<'.,.,', 'parser.y', 20 - def _reduce_5( val, _values) - val[1] - end -.,., - -module_eval <<'.,.,', 'parser.y', 21 - def _reduce_6( val, _values) - val[1] - end -.,., - -module_eval <<'.,.,', 'parser.y', 22 - def _reduce_7( val, _values) - val[1] - end -.,., - -module_eval <<'.,.,', 'parser.y', 23 - def _reduce_8( val, _values) - val[1] - end -.,., - -module_eval <<'.,.,', 'parser.y', 24 - def _reduce_9( val, _values) - val[1] - end -.,., - -module_eval <<'.,.,', 'parser.y', 25 - def _reduce_10( val, _values) - val[1] - end -.,., - -module_eval <<'.,.,', 'parser.y', 26 - def _reduce_11( val, _values) - val[1] - end -.,., - -module_eval <<'.,.,', 'parser.y', 27 - def _reduce_12( val, _values) - val[1] - end -.,., - -module_eval <<'.,.,', 'parser.y', 33 - def _reduce_13( val, _values) - t = Time.gm(val[3].to_i, val[2], val[1].to_i, 0, 0, 0) - (t + val[4] - val[5]).localtime - end -.,., - - # reduce 14 omitted - - # reduce 15 omitted - -module_eval <<'.,.,', 'parser.y', 42 - def _reduce_16( val, _values) - (val[0].to_i * 60 * 60) + - (val[2].to_i * 60) - end -.,., - -module_eval <<'.,.,', 'parser.y', 47 - def _reduce_17( val, _values) - (val[0].to_i * 60 * 60) + - (val[2].to_i * 60) + - (val[4].to_i) - end -.,., - -module_eval <<'.,.,', 'parser.y', 54 - def _reduce_18( val, _values) - timezone_string_to_unixtime(val[0]) - end -.,., - -module_eval <<'.,.,', 'parser.y', 59 - def _reduce_19( val, _values) - val - end -.,., - - # reduce 20 omitted - -module_eval <<'.,.,', 'parser.y', 65 - def _reduce_21( val, _values) - val[1] - end -.,., - - # reduce 22 omitted - -module_eval <<'.,.,', 'parser.y', 71 - def _reduce_23( val, _values) - val[1] - end -.,., - -module_eval <<'.,.,', 'parser.y', 77 - def _reduce_24( val, _values) - join_domain(val[0]) - end -.,., - -module_eval <<'.,.,', 'parser.y', 81 - def _reduce_25( val, _values) - join_domain(val[2]) - end -.,., - -module_eval <<'.,.,', 'parser.y', 85 - def _reduce_26( val, _values) - join_domain(val[0]) - end -.,., - - # reduce 27 omitted - -module_eval <<'.,.,', 'parser.y', 91 - def _reduce_28( val, _values) - val[1] - end -.,., - -module_eval <<'.,.,', 'parser.y', 96 - def _reduce_29( val, _values) - [] - end -.,., - -module_eval <<'.,.,', 'parser.y', 100 - def _reduce_30( val, _values) - val[0].push val[2] - val[0] - end -.,., - - # reduce 31 omitted - -module_eval <<'.,.,', 'parser.y', 107 - def _reduce_32( val, _values) - val[1] - end -.,., - -module_eval <<'.,.,', 'parser.y', 111 - def _reduce_33( val, _values) - val[1] - end -.,., - - # reduce 34 omitted - -module_eval <<'.,.,', 'parser.y', 117 - def _reduce_35( val, _values) - val[1] - end -.,., - -module_eval <<'.,.,', 'parser.y', 123 - def _reduce_36( val, _values) - val[0].spec - end -.,., - -module_eval <<'.,.,', 'parser.y', 127 - def _reduce_37( val, _values) - val[0].spec - end -.,., - - # reduce 38 omitted - -module_eval <<'.,.,', 'parser.y', 134 - def _reduce_39( val, _values) - val[1] - end -.,., - - # reduce 40 omitted - - # reduce 41 omitted - - # reduce 42 omitted - - # reduce 43 omitted - - # reduce 44 omitted - - # reduce 45 omitted - - # reduce 46 omitted - -module_eval <<'.,.,', 'parser.y', 146 - def _reduce_47( val, _values) - [ Address.new(nil, nil) ] - end -.,., - -module_eval <<'.,.,', 'parser.y', 148 - def _reduce_48( val, _values) - val - end -.,., - -module_eval <<'.,.,', 'parser.y', 149 - def _reduce_49( val, _values) - val[0].push val[2]; val[0] - end -.,., - - # reduce 50 omitted - - # reduce 51 omitted - -module_eval <<'.,.,', 'parser.y', 156 - def _reduce_52( val, _values) - val - end -.,., - -module_eval <<'.,.,', 'parser.y', 160 - def _reduce_53( val, _values) - val[0].push val[2] - val[0] - end -.,., - - # reduce 54 omitted - - # reduce 55 omitted - -module_eval <<'.,.,', 'parser.y', 168 - def _reduce_56( val, _values) - val[1].phrase = Decoder.decode(val[0]) - val[1] - end -.,., - - # reduce 57 omitted - -module_eval <<'.,.,', 'parser.y', 176 - def _reduce_58( val, _values) - AddressGroup.new(val[0], val[2]) - end -.,., - -module_eval <<'.,.,', 'parser.y', 178 - def _reduce_59( val, _values) - AddressGroup.new(val[0], []) - end -.,., - -module_eval <<'.,.,', 'parser.y', 181 - def _reduce_60( val, _values) - val[0].join('.') - end -.,., - -module_eval <<'.,.,', 'parser.y', 182 - def _reduce_61( val, _values) - val[0] << ' ' << val[1].join('.') - end -.,., - -module_eval <<'.,.,', 'parser.y', 186 - def _reduce_62( val, _values) - val[2].routes.replace val[1] - val[2] - end -.,., - -module_eval <<'.,.,', 'parser.y', 191 - def _reduce_63( val, _values) - val[1] - end -.,., - - # reduce 64 omitted - -module_eval <<'.,.,', 'parser.y', 196 - def _reduce_65( val, _values) - [ val[1].join('.') ] - end -.,., - -module_eval <<'.,.,', 'parser.y', 197 - def _reduce_66( val, _values) - val[0].push val[3].join('.'); val[0] - end -.,., - -module_eval <<'.,.,', 'parser.y', 199 - def _reduce_67( val, _values) - Address.new( val[0], val[2] ) - end -.,., - -module_eval <<'.,.,', 'parser.y', 200 - def _reduce_68( val, _values) - Address.new( val[0], nil ) - end -.,., - - # reduce 69 omitted - -module_eval <<'.,.,', 'parser.y', 203 - def _reduce_70( val, _values) - val[0].push ''; val[0] - end -.,., - -module_eval <<'.,.,', 'parser.y', 206 - def _reduce_71( val, _values) - val - end -.,., - -module_eval <<'.,.,', 'parser.y', 209 - def _reduce_72( val, _values) - val[1].times do - val[0].push '' - end - val[0].push val[2] - val[0] - end -.,., - -module_eval <<'.,.,', 'parser.y', 217 - def _reduce_73( val, _values) - val - end -.,., - -module_eval <<'.,.,', 'parser.y', 220 - def _reduce_74( val, _values) - val[1].times do - val[0].push '' - end - val[0].push val[2] - val[0] - end -.,., - -module_eval <<'.,.,', 'parser.y', 227 - def _reduce_75( val, _values) - 0 - end -.,., - -module_eval <<'.,.,', 'parser.y', 228 - def _reduce_76( val, _values) - 1 - end -.,., - - # reduce 77 omitted - - # reduce 78 omitted - - # reduce 79 omitted - - # reduce 80 omitted - - # reduce 81 omitted - - # reduce 82 omitted - - # reduce 83 omitted - - # reduce 84 omitted - -module_eval <<'.,.,', 'parser.y', 243 - def _reduce_85( val, _values) - val[1] = val[1].spec - val.join('') - end -.,., - -module_eval <<'.,.,', 'parser.y', 247 - def _reduce_86( val, _values) - val - end -.,., - -module_eval <<'.,.,', 'parser.y', 248 - def _reduce_87( val, _values) - val[0].push val[2]; val[0] - end -.,., - - # reduce 88 omitted - -module_eval <<'.,.,', 'parser.y', 251 - def _reduce_89( val, _values) - val[0] << ' ' << val[1] - end -.,., - -module_eval <<'.,.,', 'parser.y', 255 - def _reduce_90( val, _values) - val.push nil - val - end -.,., - -module_eval <<'.,.,', 'parser.y', 260 - def _reduce_91( val, _values) - val - end -.,., - -module_eval <<'.,.,', 'parser.y', 265 - def _reduce_92( val, _values) - [ val[0].to_i, val[2].to_i ] - end -.,., - -module_eval <<'.,.,', 'parser.y', 270 - def _reduce_93( val, _values) - [ val[0].downcase, val[2].downcase, decode_params(val[3]) ] - end -.,., - -module_eval <<'.,.,', 'parser.y', 274 - def _reduce_94( val, _values) - [ val[0].downcase, nil, decode_params(val[1]) ] - end -.,., - -module_eval <<'.,.,', 'parser.y', 279 - def _reduce_95( val, _values) - {} - end -.,., - -module_eval <<'.,.,', 'parser.y', 283 - def _reduce_96( val, _values) - val[0][ val[2].downcase ] = val[4] - val[0] - end -.,., - - # reduce 97 omitted - - # reduce 98 omitted - -module_eval <<'.,.,', 'parser.y', 292 - def _reduce_99( val, _values) - val[0].downcase - end -.,., - -module_eval <<'.,.,', 'parser.y', 297 - def _reduce_100( val, _values) - [ val[0].downcase, decode_params(val[1]) ] - end -.,., - - # reduce 101 omitted - - # reduce 102 omitted - - # reduce 103 omitted - - # reduce 104 omitted - - # reduce 105 omitted - - # reduce 106 omitted - - # reduce 107 omitted - - # reduce 108 omitted - - # reduce 109 omitted - - def _reduce_none( val, _values) - val[0] - end - - end # class Parser - -end # module TMail -# -# port.rb -# -#-- -# Copyright (c) 1998-2003 Minero Aoki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Note: Originally licensed under LGPL v2+. Using MIT license for Rails -# with permission of Minero Aoki. -#++ - -require 'tmail/stringio' - - -module TMail - - class Port - def reproducible? - false - end - end - - - ### - ### FilePort - ### - - class FilePort < Port - - def initialize( fname ) - @filename = File.expand_path(fname) - super() - end - - attr_reader :filename - - alias ident filename - - def ==( other ) - other.respond_to?(:filename) and @filename == other.filename - end - - alias eql? == - - def hash - @filename.hash - end - - def inspect - "#<#{self.class}:#{@filename}>" - end - - def reproducible? - true - end - - def size - File.size @filename - end - - - def ropen( &block ) - File.open(@filename, &block) - end - - def wopen( &block ) - File.open(@filename, 'w', &block) - end - - def aopen( &block ) - File.open(@filename, 'a', &block) - end - - - def read_all - ropen {|f| - return f.read - } - end - - - def remove - File.unlink @filename - end - - def move_to( port ) - begin - File.link @filename, port.filename - rescue Errno::EXDEV - copy_to port - end - File.unlink @filename - end - - alias mv move_to - - def copy_to( port ) - if FilePort === port - copy_file @filename, port.filename - else - File.open(@filename) {|r| - port.wopen {|w| - while s = r.sysread(4096) - w.write << s - end - } } - end - end - - alias cp copy_to - - private - - # from fileutils.rb - def copy_file( src, dest ) - st = r = w = nil - - File.open(src, 'rb') {|r| - File.open(dest, 'wb') {|w| - st = r.stat - begin - while true - w.write r.sysread(st.blksize) - end - rescue EOFError - end - } } - end - - end - - - module MailFlags - - def seen=( b ) - set_status 'S', b - end - - def seen? - get_status 'S' - end - - def replied=( b ) - set_status 'R', b - end - - def replied? - get_status 'R' - end - - def flagged=( b ) - set_status 'F', b - end - - def flagged? - get_status 'F' - end - - private - - def procinfostr( str, tag, true_p ) - a = str.upcase.split(//) - a.push true_p ? tag : nil - a.delete tag unless true_p - a.compact.sort.join('').squeeze - end - - end - - - class MhPort < FilePort - - include MailFlags - - private - - def set_status( tag, flag ) - begin - tmpfile = @filename + '.tmailtmp.' + $$.to_s - File.open(tmpfile, 'w') {|f| - write_status f, tag, flag - } - File.unlink @filename - File.link tmpfile, @filename - ensure - File.unlink tmpfile - end - end - - def write_status( f, tag, flag ) - stat = '' - File.open(@filename) {|r| - while line = r.gets - if line.strip.empty? - break - elsif m = /\AX-TMail-Status:/i.match(line) - stat = m.post_match.strip - else - f.print line - end - end - - s = procinfostr(stat, tag, flag) - f.puts 'X-TMail-Status: ' + s unless s.empty? - f.puts - - while s = r.read(2048) - f.write s - end - } - end - - def get_status( tag ) - File.foreach(@filename) {|line| - return false if line.strip.empty? - if m = /\AX-TMail-Status:/i.match(line) - return m.post_match.strip.include?(tag[0]) - end - } - false - end - - end - - - class MaildirPort < FilePort - - def move_to_new - new = replace_dir(@filename, 'new') - File.rename @filename, new - @filename = new - end - - def move_to_cur - new = replace_dir(@filename, 'cur') - File.rename @filename, new - @filename = new - end - - def replace_dir( path, dir ) - "#{File.dirname File.dirname(path)}/#{dir}/#{File.basename path}" - end - private :replace_dir - - - include MailFlags - - private - - MAIL_FILE = /\A(\d+\.[\d_]+\.[^:]+)(?:\:(\d),(\w+)?)?\z/ - - def set_status( tag, flag ) - if m = MAIL_FILE.match(File.basename(@filename)) - s, uniq, type, info, = m.to_a - return if type and type != '2' # do not change anything - newname = File.dirname(@filename) + '/' + - uniq + ':2,' + procinfostr(info.to_s, tag, flag) - else - newname = @filename + ':2,' + tag - end - - File.link @filename, newname - File.unlink @filename - @filename = newname - end - - def get_status( tag ) - m = MAIL_FILE.match(File.basename(@filename)) or return false - m[2] == '2' and m[3].to_s.include?(tag[0]) - end - - end - - - ### - ### StringPort - ### - - class StringPort < Port - - def initialize( str = '' ) - @buffer = str - super() - end - - def string - @buffer - end - - def to_s - @buffer.dup - end - - alias read_all to_s - - def size - @buffer.size - end - - def ==( other ) - StringPort === other and @buffer.equal? other.string - end - - alias eql? == - - def hash - @buffer.object_id.hash - end - - def inspect - "#<#{self.class}:id=#{sprintf '0x%x', @buffer.object_id}>" - end - - def reproducible? - true - end - - def ropen( &block ) - @buffer or raise Errno::ENOENT, "#{inspect} is already removed" - StringInput.open(@buffer, &block) - end - - def wopen( &block ) - @buffer = '' - StringOutput.new(@buffer, &block) - end - - def aopen( &block ) - @buffer ||= '' - StringOutput.new(@buffer, &block) - end - - def remove - @buffer = nil - end - - alias rm remove - - def copy_to( port ) - port.wopen {|f| - f.write @buffer - } - end - - alias cp copy_to - - def move_to( port ) - if StringPort === port - str = @buffer - port.instance_eval { @buffer = str } - else - copy_to port - end - remove - end - - end - -end # module TMail -module TMail - class Mail - def subject(to_charset = 'utf-8') - Unquoter.unquote_and_convert_to(quoted_subject, to_charset) - end - - def unquoted_body(to_charset = 'utf-8') - from_charset = sub_header("content-type", "charset") - case (content_transfer_encoding || "7bit").downcase - when "quoted-printable" - Unquoter.unquote_quoted_printable_and_convert_to(quoted_body, - to_charset, from_charset, true) - when "base64" - Unquoter.unquote_base64_and_convert_to(quoted_body, to_charset, - from_charset) - when "7bit", "8bit" - Unquoter.convert_to(quoted_body, to_charset, from_charset) - when "binary" - quoted_body - else - quoted_body - end - end - - def body(to_charset = 'utf-8', &block) - attachment_presenter = block || Proc.new { |file_name| "Attachment: #{file_name}\n" } - - if multipart? - parts.collect { |part| - header = part["content-type"] - - if part.multipart? - part.body(to_charset, &attachment_presenter) - elsif header.nil? - "" - elsif !attachment?(part) - part.unquoted_body(to_charset) - else - attachment_presenter.call(header["name"] || "(unnamed)") - end - }.join - else - unquoted_body(to_charset) - end - end - end - - class Unquoter - class << self - def unquote_and_convert_to(text, to_charset, from_charset = "iso-8859-1", preserve_underscores=false) - return "" if text.nil? - if text =~ /^=\?(.*?)\?(.)\?(.*)\?=$/ - from_charset = $1 - quoting_method = $2 - text = $3 - case quoting_method.upcase - when "Q" then - unquote_quoted_printable_and_convert_to(text, to_charset, from_charset, preserve_underscores) - when "B" then - unquote_base64_and_convert_to(text, to_charset, from_charset) - else - raise "unknown quoting method #{quoting_method.inspect}" - end - else - convert_to(text, to_charset, from_charset) - end - end - - def unquote_quoted_printable_and_convert_to(text, to, from, preserve_underscores=false) - text = text.gsub(/_/, " ") unless preserve_underscores - convert_to(text.unpack("M*").first, to, from) - end - - def unquote_base64_and_convert_to(text, to, from) - convert_to(Base64.decode(text).first, to, from) - end - - begin - require 'iconv' - def convert_to(text, to, from) - return text unless to && from - text ? Iconv.iconv(to, from, text).first : "" - rescue Iconv::IllegalSequence, Errno::EINVAL - # the 'from' parameter specifies a charset other than what the text - # actually is...not much we can do in this case but just return the - # unconverted text. - # - # Ditto if either parameter represents an unknown charset, like - # X-UNKNOWN. - text - end - rescue LoadError - # Not providing quoting support - def convert_to(text, to, from) - warn "Action Mailer: iconv not loaded; ignoring conversion from #{from} to #{to} (#{__FILE__}:#{__LINE__})" - text - end - end - end - end -end - -if __FILE__ == $0 - require 'test/unit' - - class TC_Unquoter < Test::Unit::TestCase - def test_unquote_quoted_printable - a ="=?ISO-8859-1?Q?[166417]_Bekr=E6ftelse_fra_Rejsefeber?=" - b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8') - assert_equal "[166417] Bekr\303\246ftelse fra Rejsefeber", b - end - - def test_unquote_base64 - a ="=?ISO-8859-1?B?WzE2NjQxN10gQmVrcuZmdGVsc2UgZnJhIFJlanNlZmViZXI=?=" - b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8') - assert_equal "[166417] Bekr\303\246ftelse fra Rejsefeber", b - end - - def test_unquote_without_charset - a ="[166417]_Bekr=E6ftelse_fra_Rejsefeber" - b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8') - assert_equal "[166417]_Bekr=E6ftelse_fra_Rejsefeber", b - end - end -end -# -# scanner.rb -# -#-- -# Copyright (c) 1998-2003 Minero Aoki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Note: Originally licensed under LGPL v2+. Using MIT license for Rails -# with permission of Minero Aoki. -#++ - -require 'tmail/utils' - -module TMail - require 'tmail/scanner_r.rb' - begin - raise LoadError, 'Turn off Ruby extention by user choice' if ENV['NORUBYEXT'] - require 'tmail/scanner_c.so' - Scanner = Scanner_C - rescue LoadError - Scanner = Scanner_R - end -end -# -# scanner_r.rb -# -#-- -# Copyright (c) 1998-2003 Minero Aoki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Note: Originally licensed under LGPL v2+. Using MIT license for Rails -# with permission of Minero Aoki. -#++ - -require 'tmail/config' - - -module TMail - - class Scanner_R - - Version = '0.10.7' - Version.freeze - - MIME_HEADERS = { - :CTYPE => true, - :CENCODING => true, - :CDISPOSITION => true - } - - alnum = 'a-zA-Z0-9' - atomsyms = %q[ _#!$%&`'*+-{|}~^@/=? ].strip - tokensyms = %q[ _#!$%&`'*+-{|}~^@. ].strip - - atomchars = alnum + Regexp.quote(atomsyms) - tokenchars = alnum + Regexp.quote(tokensyms) - iso2022str = '\e(?!\(B)..(?:[^\e]+|\e(?!\(B)..)*\e\(B' - - eucstr = '(?:[\xa1-\xfe][\xa1-\xfe])+' - sjisstr = '(?:[\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc])+' - utf8str = '(?:[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf][\x80-\xbf])+' - - quoted_with_iso2022 = /\A(?:[^\\\e"]+|#{iso2022str})+/n - domlit_with_iso2022 = /\A(?:[^\\\e\]]+|#{iso2022str})+/n - comment_with_iso2022 = /\A(?:[^\\\e()]+|#{iso2022str})+/n - - quoted_without_iso2022 = /\A[^\\"]+/n - domlit_without_iso2022 = /\A[^\\\]]+/n - comment_without_iso2022 = /\A[^\\()]+/n - - PATTERN_TABLE = {} - PATTERN_TABLE['EUC'] = - [ - /\A(?:[#{atomchars}]+|#{iso2022str}|#{eucstr})+/n, - /\A(?:[#{tokenchars}]+|#{iso2022str}|#{eucstr})+/n, - quoted_with_iso2022, - domlit_with_iso2022, - comment_with_iso2022 - ] - PATTERN_TABLE['SJIS'] = - [ - /\A(?:[#{atomchars}]+|#{iso2022str}|#{sjisstr})+/n, - /\A(?:[#{tokenchars}]+|#{iso2022str}|#{sjisstr})+/n, - quoted_with_iso2022, - domlit_with_iso2022, - comment_with_iso2022 - ] - PATTERN_TABLE['UTF8'] = - [ - /\A(?:[#{atomchars}]+|#{utf8str})+/n, - /\A(?:[#{tokenchars}]+|#{utf8str})+/n, - quoted_without_iso2022, - domlit_without_iso2022, - comment_without_iso2022 - ] - PATTERN_TABLE['NONE'] = - [ - /\A[#{atomchars}]+/n, - /\A[#{tokenchars}]+/n, - quoted_without_iso2022, - domlit_without_iso2022, - comment_without_iso2022 - ] - - - def initialize( str, scantype, comments ) - init_scanner str - @comments = comments || [] - @debug = false - - # fix scanner mode - @received = (scantype == :RECEIVED) - @is_mime_header = MIME_HEADERS[scantype] - - atom, token, @quoted_re, @domlit_re, @comment_re = PATTERN_TABLE[$KCODE] - @word_re = (MIME_HEADERS[scantype] ? token : atom) - end - - attr_accessor :debug - - def scan( &block ) - if @debug - scan_main do |arr| - s, v = arr - printf "%7d %-10s %s\n", - rest_size(), - s.respond_to?(:id2name) ? s.id2name : s.inspect, - v.inspect - yield arr - end - else - scan_main(&block) - end - end - - private - - RECV_TOKEN = { - 'from' => :FROM, - 'by' => :BY, - 'via' => :VIA, - 'with' => :WITH, - 'id' => :ID, - 'for' => :FOR - } - - def scan_main - until eof? - if skip(/\A[\n\r\t ]+/n) # LWSP - break if eof? - end - - if s = readstr(@word_re) - if @is_mime_header - yield :TOKEN, s - else - # atom - if /\A\d+\z/ === s - yield :DIGIT, s - elsif @received - yield RECV_TOKEN[s.downcase] || :ATOM, s - else - yield :ATOM, s - end - end - - elsif skip(/\A"/) - yield :QUOTED, scan_quoted_word() - - elsif skip(/\A\[/) - yield :DOMLIT, scan_domain_literal() - - elsif skip(/\A\(/) - @comments.push scan_comment() - - else - c = readchar() - yield c, c - end - end - - yield false, '$' - end - - def scan_quoted_word - scan_qstr(@quoted_re, /\A"/, 'quoted-word') - end - - def scan_domain_literal - '[' + scan_qstr(@domlit_re, /\A\]/, 'domain-literal') + ']' - end - - def scan_qstr( pattern, terminal, type ) - result = '' - until eof? - if s = readstr(pattern) then result << s - elsif skip(terminal) then return result - elsif skip(/\A\\/) then result << readchar() - else - raise "TMail FATAL: not match in #{type}" - end - end - scan_error! "found unterminated #{type}" - end - - def scan_comment - result = '' - nest = 1 - content = @comment_re - - until eof? - if s = readstr(content) then result << s - elsif skip(/\A\)/) then nest -= 1 - return result if nest == 0 - result << ')' - elsif skip(/\A\(/) then nest += 1 - result << '(' - elsif skip(/\A\\/) then result << readchar() - else - raise 'TMail FATAL: not match in comment' - end - end - scan_error! 'found unterminated comment' - end - - # string scanner - - def init_scanner( str ) - @src = str - end - - def eof? - @src.empty? - end - - def rest_size - @src.size - end - - def readstr( re ) - if m = re.match(@src) - @src = m.post_match - m[0] - else - nil - end - end - - def readchar - readstr(/\A./) - end - - def skip( re ) - if m = re.match(@src) - @src = m.post_match - true - else - false - end - end - - def scan_error!( msg ) - raise SyntaxError, msg - end - - end - -end # module TMail -# -# stringio.rb -# -#-- -# Copyright (c) 1998-2003 Minero Aoki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Note: Originally licensed under LGPL v2+. Using MIT license for Rails -# with permission of Minero Aoki. -#++ - -class StringInput#:nodoc: - - include Enumerable - - class << self - - def new( str ) - if block_given? - begin - f = super - yield f - ensure - f.close if f - end - else - super - end - end - - alias open new - - end - - def initialize( str ) - @src = str - @pos = 0 - @closed = false - @lineno = 0 - end - - attr_reader :lineno - - def string - @src - end - - def inspect - "#<#{self.class}:#{@closed ? 'closed' : 'open'},src=#{@src[0,30].inspect}>" - end - - def close - stream_check! - @pos = nil - @closed = true - end - - def closed? - @closed - end - - def pos - stream_check! - [@pos, @src.size].min - end - - alias tell pos - - def seek( offset, whence = IO::SEEK_SET ) - stream_check! - case whence - when IO::SEEK_SET - @pos = offset - when IO::SEEK_CUR - @pos += offset - when IO::SEEK_END - @pos = @src.size - offset - else - raise ArgumentError, "unknown seek flag: #{whence}" - end - @pos = 0 if @pos < 0 - @pos = [@pos, @src.size + 1].min - offset - end - - def rewind - stream_check! - @pos = 0 - end - - def eof? - stream_check! - @pos > @src.size - end - - def each( &block ) - stream_check! - begin - @src.each(&block) - ensure - @pos = 0 - end - end - - def gets - stream_check! - if idx = @src.index(?\n, @pos) - idx += 1 # "\n".size - line = @src[ @pos ... idx ] - @pos = idx - @pos += 1 if @pos == @src.size - else - line = @src[ @pos .. -1 ] - @pos = @src.size + 1 - end - @lineno += 1 - - line - end - - def getc - stream_check! - ch = @src[@pos] - @pos += 1 - @pos += 1 if @pos == @src.size - ch - end - - def read( len = nil ) - stream_check! - return read_all unless len - str = @src[@pos, len] - @pos += len - @pos += 1 if @pos == @src.size - str - end - - alias sysread read - - def read_all - stream_check! - return nil if eof? - rest = @src[@pos ... @src.size] - @pos = @src.size + 1 - rest - end - - def stream_check! - @closed and raise IOError, 'closed stream' - end - -end - - -class StringOutput#:nodoc: - - class << self - - def new( str = '' ) - if block_given? - begin - f = super - yield f - ensure - f.close if f - end - else - super - end - end - - alias open new - - end - - def initialize( str = '' ) - @dest = str - @closed = false - end - - def close - @closed = true - end - - def closed? - @closed - end - - def string - @dest - end - - alias value string - alias to_str string - - def size - @dest.size - end - - alias pos size - - def inspect - "#<#{self.class}:#{@dest ? 'open' : 'closed'},#{id}>" - end - - def print( *args ) - stream_check! - raise ArgumentError, 'wrong # of argument (0 for >1)' if args.empty? - args.each do |s| - raise ArgumentError, 'nil not allowed' if s.nil? - @dest << s.to_s - end - nil - end - - def puts( *args ) - stream_check! - args.each do |str| - @dest << (s = str.to_s) - @dest << "\n" unless s[-1] == ?\n - end - @dest << "\n" if args.empty? - nil - end - - def putc( ch ) - stream_check! - @dest << ch.chr - nil - end - - def printf( *args ) - stream_check! - @dest << sprintf(*args) - nil - end - - def write( str ) - stream_check! - s = str.to_s - @dest << s - s.size - end - - alias syswrite write - - def <<( str ) - stream_check! - @dest << str.to_s - self - end - - private - - def stream_check! - @closed and raise IOError, 'closed stream' - end - -end -require 'tmail' -# -# utils.rb -# -#-- -# Copyright (c) 1998-2003 Minero Aoki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Note: Originally licensed under LGPL v2+. Using MIT license for Rails -# with permission of Minero Aoki. -#++ - -module TMail - - class SyntaxError < StandardError; end - - - def TMail.new_boundary - 'mimepart_' + random_tag - end - - def TMail.new_message_id( fqdn = nil ) - fqdn ||= ::Socket.gethostname - "<#{random_tag()}@#{fqdn}.tmail>" - end - - def TMail.random_tag - @uniq += 1 - t = Time.now - sprintf('%x%x_%x%x%d%x', - t.to_i, t.tv_usec, - $$, Thread.current.object_id, @uniq, rand(255)) - end - private_class_method :random_tag - - @uniq = 0 - - - module TextUtils - - aspecial = '()<>[]:;.\\,"' - tspecial = '()<>[];:\\,"/?=' - lwsp = " \t\r\n" - control = '\x00-\x1f\x7f-\xff' - - ATOM_UNSAFE = /[#{Regexp.quote aspecial}#{control}#{lwsp}]/n - PHRASE_UNSAFE = /[#{Regexp.quote aspecial}#{control}]/n - TOKEN_UNSAFE = /[#{Regexp.quote tspecial}#{control}#{lwsp}]/n - CONTROL_CHAR = /[#{control}]/n - - def atom_safe?( str ) - not ATOM_UNSAFE === str - end - - def quote_atom( str ) - (ATOM_UNSAFE === str) ? dquote(str) : str - end - - def quote_phrase( str ) - (PHRASE_UNSAFE === str) ? dquote(str) : str - end - - def token_safe?( str ) - not TOKEN_UNSAFE === str - end - - def quote_token( str ) - (TOKEN_UNSAFE === str) ? dquote(str) : str - end - - def dquote( str ) - '"' + str.gsub(/["\\]/n) {|s| '\\' + s } + '"' - end - private :dquote - - - def join_domain( arr ) - arr.map {|i| - if /\A\[.*\]\z/ === i - i - else - quote_atom(i) - end - }.join('.') - end - - - ZONESTR_TABLE = { - 'jst' => 9 * 60, - 'eet' => 2 * 60, - 'bst' => 1 * 60, - 'met' => 1 * 60, - 'gmt' => 0, - 'utc' => 0, - 'ut' => 0, - 'nst' => -(3 * 60 + 30), - 'ast' => -4 * 60, - 'edt' => -4 * 60, - 'est' => -5 * 60, - 'cdt' => -5 * 60, - 'cst' => -6 * 60, - 'mdt' => -6 * 60, - 'mst' => -7 * 60, - 'pdt' => -7 * 60, - 'pst' => -8 * 60, - 'a' => -1 * 60, - 'b' => -2 * 60, - 'c' => -3 * 60, - 'd' => -4 * 60, - 'e' => -5 * 60, - 'f' => -6 * 60, - 'g' => -7 * 60, - 'h' => -8 * 60, - 'i' => -9 * 60, - # j not use - 'k' => -10 * 60, - 'l' => -11 * 60, - 'm' => -12 * 60, - 'n' => 1 * 60, - 'o' => 2 * 60, - 'p' => 3 * 60, - 'q' => 4 * 60, - 'r' => 5 * 60, - 's' => 6 * 60, - 't' => 7 * 60, - 'u' => 8 * 60, - 'v' => 9 * 60, - 'w' => 10 * 60, - 'x' => 11 * 60, - 'y' => 12 * 60, - 'z' => 0 * 60 - } - - def timezone_string_to_unixtime( str ) - if m = /([\+\-])(\d\d?)(\d\d)/.match(str) - sec = (m[2].to_i * 60 + m[3].to_i) * 60 - m[1] == '-' ? -sec : sec - else - min = ZONESTR_TABLE[str.downcase] or - raise SyntaxError, "wrong timezone format '#{str}'" - min * 60 - end - end - - - WDAY = %w( Sun Mon Tue Wed Thu Fri Sat TMailBUG ) - MONTH = %w( TMailBUG Jan Feb Mar Apr May Jun - Jul Aug Sep Oct Nov Dec TMailBUG ) - - def time2str( tm ) - # [ruby-list:7928] - gmt = Time.at(tm.to_i) - gmt.gmtime - offset = tm.to_i - Time.local(*gmt.to_a[0,6].reverse).to_i - - # DO NOT USE strftime: setlocale() breaks it - sprintf '%s, %s %s %d %02d:%02d:%02d %+.2d%.2d', - WDAY[tm.wday], tm.mday, MONTH[tm.month], - tm.year, tm.hour, tm.min, tm.sec, - *(offset / 60).divmod(60) - end - - - MESSAGE_ID = /<[^\@>]+\@[^>\@]+>/ - - def message_id?( str ) - MESSAGE_ID === str - end - - - MIME_ENCODED = /=\?[^\s?=]+\?[QB]\?[^\s?=]+\?=/i - - def mime_encoded?( str ) - MIME_ENCODED === str - end - - - def decode_params( hash ) - new = Hash.new - encoded = nil - hash.each do |key, value| - if m = /\*(?:(\d+)\*)?\z/.match(key) - ((encoded ||= {})[m.pre_match] ||= [])[(m[1] || 0).to_i] = value - else - new[key] = to_kcode(value) - end - end - if encoded - encoded.each do |key, strings| - new[key] = decode_RFC2231(strings.join('')) - end - end - - new - end - - NKF_FLAGS = { - 'EUC' => '-e -m', - 'SJIS' => '-s -m' - } - - def to_kcode( str ) - flag = NKF_FLAGS[$KCODE] or return str - NKF.nkf(flag, str) - end - - RFC2231_ENCODED = /\A(?:iso-2022-jp|euc-jp|shift_jis|us-ascii)?'[a-z]*'/in - - def decode_RFC2231( str ) - m = RFC2231_ENCODED.match(str) or return str - begin - NKF.nkf(NKF_FLAGS[$KCODE], - m.post_match.gsub(/%[\da-f]{2}/in) {|s| s[1,2].hex.chr }) - rescue - m.post_match.gsub(/%[\da-f]{2}/in, "") - end - end - - end - -end -require 'tmail/info' -require 'tmail/mail' -require 'tmail/mailbox' -module ActionMailer - module VERSION #:nodoc: - MAJOR = 1 - MINOR = 2 - TINY = 5 - - STRING = [MAJOR, MINOR, TINY].join('.') - end -end -#-- -# Copyright (c) 2004 David Heinemeier Hansson -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -#++ - -begin - require 'action_controller' -rescue LoadError - begin - require File.dirname(__FILE__) + '/../../actionpack/lib/action_controller' - rescue LoadError - require 'rubygems' - require_gem 'actionpack', '>= 1.9.1' - end -end - -$:.unshift(File.dirname(__FILE__) + "/action_mailer/vendor/") - -require 'action_mailer/base' -require 'action_mailer/helpers' -require 'action_mailer/mail_helper' -require 'action_mailer/quoting' -require 'tmail' -require 'net/smtp' - -ActionMailer::Base.class_eval do - include ActionMailer::Quoting - include ActionMailer::Helpers - - helper MailHelper -end - -silence_warnings { TMail::Encoder.const_set("MAX_LINE_LEN", 200) }module TestHelper - def test_format(text) - "#{text}" - end -end -$:.unshift(File.dirname(__FILE__) + "/../lib/") -$:.unshift File.dirname(__FILE__) + "/fixtures/helpers" - -require 'test/unit' -require 'action_mailer' - -module MailerHelper - def person_name - "Mr. Joe Person" - end -end - -class HelperMailer < ActionMailer::Base - helper MailerHelper - helper :test - - def use_helper(recipient) - recipients recipient - subject "using helpers" - from "tester@example.com" - end - - def use_test_helper(recipient) - recipients recipient - subject "using helpers" - from "tester@example.com" - self.body = { :text => "emphasize me!" } - end - - def use_mail_helper(recipient) - recipients recipient - subject "using mailing helpers" - from "tester@example.com" - self.body = { :text => - "But soft! What light through yonder window breaks? It is the east, " + - "and Juliet is the sun. Arise, fair sun, and kill the envious moon, " + - "which is sick and pale with grief that thou, her maid, art far more " + - "fair than she. Be not her maid, for she is envious! Her vestal " + - "livery is but sick and green, and none but fools do wear it. Cast " + - "it off!" - } - end - - def use_helper_method(recipient) - recipients recipient - subject "using helpers" - from "tester@example.com" - self.body = { :text => "emphasize me!" } - end - - private - - def name_of_the_mailer_class - self.class.name - end - helper_method :name_of_the_mailer_class -end - -HelperMailer.template_root = File.dirname(__FILE__) + "/fixtures" - -class MailerHelperTest < Test::Unit::TestCase - def new_mail( charset="utf-8" ) - mail = TMail::Mail.new - mail.set_content_type "text", "plain", { "charset" => charset } if charset - mail - end - - def setup - ActionMailer::Base.delivery_method = :test - ActionMailer::Base.perform_deliveries = true - ActionMailer::Base.deliveries = [] - - @recipient = 'test@localhost' - end - - def test_use_helper - mail = HelperMailer.create_use_helper(@recipient) - assert_match %r{Mr. Joe Person}, mail.encoded - end - - def test_use_test_helper - mail = HelperMailer.create_use_test_helper(@recipient) - assert_match %r{emphasize me!}, mail.encoded - end - - def test_use_helper_method - mail = HelperMailer.create_use_helper_method(@recipient) - assert_match %r{HelperMailer}, mail.encoded - end - - def test_use_mail_helper - mail = HelperMailer.create_use_mail_helper(@recipient) - assert_match %r{ But soft!}, mail.encoded - assert_match %r{east, and\n Juliet}, mail.encoded - end -end - -$:.unshift(File.dirname(__FILE__) + "/../lib/") - -require 'test/unit' -require 'action_mailer' - -class RenderMailer < ActionMailer::Base - def inline_template(recipient) - recipients recipient - subject "using helpers" - from "tester@example.com" - body render(:inline => "Hello, <%= @world %>", :body => { :world => "Earth" }) - end - - def file_template(recipient) - recipients recipient - subject "using helpers" - from "tester@example.com" - body render(:file => "signed_up", :body => { :recipient => recipient }) - end - - def initialize_defaults(method_name) - super - mailer_name "test_mailer" - end -end - -RenderMailer.template_root = File.dirname(__FILE__) + "/fixtures" - -class RenderHelperTest < Test::Unit::TestCase - def setup - ActionMailer::Base.delivery_method = :test - ActionMailer::Base.perform_deliveries = true - ActionMailer::Base.deliveries = [] - - @recipient = 'test@localhost' - end - - def test_inline_template - mail = RenderMailer.create_inline_template(@recipient) - assert_equal "Hello, Earth", mail.body.strip - end - - def test_file_template - mail = RenderMailer.create_file_template(@recipient) - assert_equal "Hello there, \n\nMr. test@localhost", mail.body.strip - end -end - -$:.unshift(File.dirname(__FILE__) + "/../lib/") - -require 'test/unit' -require 'action_mailer' - -class MockSMTP - def self.deliveries - @@deliveries - end - - def initialize - @@deliveries = [] - end - - def sendmail(mail, from, to) - @@deliveries << [mail, from, to] - end -end - -class Net::SMTP - def self.start(*args) - yield MockSMTP.new - end -end - -class FunkyPathMailer < ActionMailer::Base - self.template_root = "#{File.dirname(__FILE__)}/fixtures/path.with.dots" - - def multipart_with_template_path_with_dots(recipient) - recipients recipient - subject "Have a lovely picture" - from "Chad Fowler " - attachment :content_type => "image/jpeg", - :body => "not really a jpeg, we're only testing, after all" - end - - def template_path - "#{File.dirname(__FILE__)}/fixtures/path.with.dots" - end -end - -class TestMailer < ActionMailer::Base - - def signed_up(recipient) - @recipients = recipient - @subject = "[Signed up] Welcome #{recipient}" - @from = "system@loudthinking.com" - @sent_on = Time.local(2004, 12, 12) - @body["recipient"] = recipient - end - - def cancelled_account(recipient) - self.recipients = recipient - self.subject = "[Cancelled] Goodbye #{recipient}" - self.from = "system@loudthinking.com" - self.sent_on = Time.local(2004, 12, 12) - self.body = "Goodbye, Mr. #{recipient}" - end - - def cc_bcc(recipient) - recipients recipient - subject "testing bcc/cc" - from "system@loudthinking.com" - sent_on Time.local(2004, 12, 12) - cc "nobody@loudthinking.com" - bcc "root@loudthinking.com" - body "Nothing to see here." - end - - def iso_charset(recipient) - @recipients = recipient - @subject = "testing isø charsets" - @from = "system@loudthinking.com" - @sent_on = Time.local 2004, 12, 12 - @cc = "nobody@loudthinking.com" - @bcc = "root@loudthinking.com" - @body = "Nothing to see here." - @charset = "iso-8859-1" - end - - def unencoded_subject(recipient) - @recipients = recipient - @subject = "testing unencoded subject" - @from = "system@loudthinking.com" - @sent_on = Time.local 2004, 12, 12 - @cc = "nobody@loudthinking.com" - @bcc = "root@loudthinking.com" - @body = "Nothing to see here." - end - - def extended_headers(recipient) - @recipients = recipient - @subject = "testing extended headers" - @from = "Grytøyr " - @sent_on = Time.local 2004, 12, 12 - @cc = "Grytøyr " - @bcc = "Grytøyr " - @body = "Nothing to see here." - @charset = "iso-8859-1" - end - - def utf8_body(recipient) - @recipients = recipient - @subject = "testing utf-8 body" - @from = "Foo áëô îü " - @sent_on = Time.local 2004, 12, 12 - @cc = "Foo áëô îü " - @bcc = "Foo áëô îü " - @body = "åœö blah" - @charset = "utf-8" - end - - def multipart_with_mime_version(recipient) - recipients recipient - subject "multipart with mime_version" - from "test@example.com" - sent_on Time.local(2004, 12, 12) - mime_version "1.1" - content_type "multipart/alternative" - - part "text/plain" do |p| - p.body = "blah" - end - - part "text/html" do |p| - p.body = "blah" - end - end - - def multipart_with_utf8_subject(recipient) - recipients recipient - subject "Foo áëô îü" - from "test@example.com" - charset "utf-8" - - part "text/plain" do |p| - p.body = "blah" - end - - part "text/html" do |p| - p.body = "blah" - end - end - - def explicitly_multipart_example(recipient, ct=nil) - recipients recipient - subject "multipart example" - from "test@example.com" - sent_on Time.local(2004, 12, 12) - body "plain text default" - content_type ct if ct - - part "text/html" do |p| - p.charset = "iso-8859-1" - p.body = "blah" - end - - attachment :content_type => "image/jpeg", :filename => "foo.jpg", - :body => "123456789" - end - - def implicitly_multipart_example(recipient, cs = nil, order = nil) - @recipients = recipient - @subject = "multipart example" - @from = "test@example.com" - @sent_on = Time.local 2004, 12, 12 - @body = { "recipient" => recipient } - @charset = cs if cs - @implicit_parts_order = order if order - end - - def implicitly_multipart_with_utf8 - recipients "no.one@nowhere.test" - subject "Foo áëô îü" - from "some.one@somewhere.test" - template "implicitly_multipart_example" - body ({ "recipient" => "no.one@nowhere.test" }) - end - - def html_mail(recipient) - recipients recipient - subject "html mail" - from "test@example.com" - body "Emphasize this" - content_type "text/html" - end - - def html_mail_with_underscores(recipient) - subject "html mail with underscores" - body %{_Google} - end - - def custom_template(recipient) - recipients recipient - subject "[Signed up] Welcome #{recipient}" - from "system@loudthinking.com" - sent_on Time.local(2004, 12, 12) - template "signed_up" - - body["recipient"] = recipient - end - - def various_newlines(recipient) - recipients recipient - subject "various newlines" - from "test@example.com" - body "line #1\nline #2\rline #3\r\nline #4\r\r" + - "line #5\n\nline#6\r\n\r\nline #7" - end - - def various_newlines_multipart(recipient) - recipients recipient - subject "various newlines multipart" - from "test@example.com" - content_type "multipart/alternative" - part :content_type => "text/plain", :body => "line #1\nline #2\rline #3\r\nline #4\r\r" - part :content_type => "text/html", :body => "

line #1

\n

line #2

\r

line #3

\r\n

line #4

\r\r" - end - - def nested_multipart(recipient) - recipients recipient - subject "nested multipart" - from "test@example.com" - content_type "multipart/mixed" - part :content_type => "multipart/alternative", :content_disposition => "inline" do |p| - p.part :content_type => "text/plain", :body => "test text\nline #2" - p.part :content_type => "text/html", :body => "test HTML
\nline #2" - end - attachment :content_type => "application/octet-stream",:filename => "test.txt", :body => "test abcdefghijklmnopqstuvwxyz" - end - - def attachment_with_custom_header(recipient) - recipients recipient - subject "custom header in attachment" - from "test@example.com" - content_type "multipart/related" - part :content_type => "text/html", :body => 'yo' - attachment :content_type => "image/jpeg",:filename => "test.jpeg", :body => "i am not a real picture", :headers => { 'Content-ID' => '' } - end - - def unnamed_attachment(recipient) - recipients recipient - subject "nested multipart" - from "test@example.com" - content_type "multipart/mixed" - part :content_type => "text/plain", :body => "hullo" - attachment :content_type => "application/octet-stream", :body => "test abcdefghijklmnopqstuvwxyz" - end - - def headers_with_nonalpha_chars(recipient) - recipients recipient - subject "nonalpha chars" - from "One: Two " - cc "Three: Four " - bcc "Five: Six " - body "testing" - end - - def custom_content_type_attributes - recipients "no.one@nowhere.test" - subject "custom content types" - from "some.one@somewhere.test" - content_type "text/plain; format=flowed" - body "testing" - end - - class < charset } - end - mail - end - - def setup - ActionMailer::Base.delivery_method = :test - ActionMailer::Base.perform_deliveries = true - ActionMailer::Base.deliveries = [] - - @recipient = 'test@localhost' - end - - def test_nested_parts - created = nil - assert_nothing_raised { created = TestMailer.create_nested_multipart(@recipient)} - assert_equal 2,created.parts.size - assert_equal 2,created.parts.first.parts.size - - assert_equal "multipart/mixed", created.content_type - assert_equal "multipart/alternative", created.parts.first.content_type - assert_equal "text/plain", created.parts.first.parts.first.content_type - assert_equal "text/html", created.parts.first.parts[1].content_type - assert_equal "application/octet-stream", created.parts[1].content_type - end - - def test_attachment_with_custom_header - created = nil - assert_nothing_raised { created = TestMailer.create_attachment_with_custom_header(@recipient)} - assert_equal "", created.parts[1].header['content-id'].to_s - end - - def test_signed_up - expected = new_mail - expected.to = @recipient - expected.subject = "[Signed up] Welcome #{@recipient}" - expected.body = "Hello there, \n\nMr. #{@recipient}" - expected.from = "system@loudthinking.com" - expected.date = Time.local(2004, 12, 12) - expected.mime_version = nil - - created = nil - assert_nothing_raised { created = TestMailer.create_signed_up(@recipient) } - assert_not_nil created - assert_equal expected.encoded, created.encoded - - assert_nothing_raised { TestMailer.deliver_signed_up(@recipient) } - assert_not_nil ActionMailer::Base.deliveries.first - assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded - end - - def test_custom_template - expected = new_mail - expected.to = @recipient - expected.subject = "[Signed up] Welcome #{@recipient}" - expected.body = "Hello there, \n\nMr. #{@recipient}" - expected.from = "system@loudthinking.com" - expected.date = Time.local(2004, 12, 12) - - created = nil - assert_nothing_raised { created = TestMailer.create_custom_template(@recipient) } - assert_not_nil created - assert_equal expected.encoded, created.encoded - end - - def test_cancelled_account - expected = new_mail - expected.to = @recipient - expected.subject = "[Cancelled] Goodbye #{@recipient}" - expected.body = "Goodbye, Mr. #{@recipient}" - expected.from = "system@loudthinking.com" - expected.date = Time.local(2004, 12, 12) - - created = nil - assert_nothing_raised { created = TestMailer.create_cancelled_account(@recipient) } - assert_not_nil created - assert_equal expected.encoded, created.encoded - - assert_nothing_raised { TestMailer.deliver_cancelled_account(@recipient) } - assert_not_nil ActionMailer::Base.deliveries.first - assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded - end - - def test_cc_bcc - expected = new_mail - expected.to = @recipient - expected.subject = "testing bcc/cc" - expected.body = "Nothing to see here." - expected.from = "system@loudthinking.com" - expected.cc = "nobody@loudthinking.com" - expected.bcc = "root@loudthinking.com" - expected.date = Time.local 2004, 12, 12 - - created = nil - assert_nothing_raised do - created = TestMailer.create_cc_bcc @recipient - end - assert_not_nil created - assert_equal expected.encoded, created.encoded - - assert_nothing_raised do - TestMailer.deliver_cc_bcc @recipient - end - - assert_not_nil ActionMailer::Base.deliveries.first - assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded - end - - def test_iso_charset - expected = new_mail( "iso-8859-1" ) - expected.to = @recipient - expected.subject = encode "testing isø charsets", "iso-8859-1" - expected.body = "Nothing to see here." - expected.from = "system@loudthinking.com" - expected.cc = "nobody@loudthinking.com" - expected.bcc = "root@loudthinking.com" - expected.date = Time.local 2004, 12, 12 - - created = nil - assert_nothing_raised do - created = TestMailer.create_iso_charset @recipient - end - assert_not_nil created - assert_equal expected.encoded, created.encoded - - assert_nothing_raised do - TestMailer.deliver_iso_charset @recipient - end - - assert_not_nil ActionMailer::Base.deliveries.first - assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded - end - - def test_unencoded_subject - expected = new_mail - expected.to = @recipient - expected.subject = "testing unencoded subject" - expected.body = "Nothing to see here." - expected.from = "system@loudthinking.com" - expected.cc = "nobody@loudthinking.com" - expected.bcc = "root@loudthinking.com" - expected.date = Time.local 2004, 12, 12 - - created = nil - assert_nothing_raised do - created = TestMailer.create_unencoded_subject @recipient - end - assert_not_nil created - assert_equal expected.encoded, created.encoded - - assert_nothing_raised do - TestMailer.deliver_unencoded_subject @recipient - end - - assert_not_nil ActionMailer::Base.deliveries.first - assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded - end - - def test_instances_are_nil - assert_nil ActionMailer::Base.new - assert_nil TestMailer.new - end - - def test_deliveries_array - assert_not_nil ActionMailer::Base.deliveries - assert_equal 0, ActionMailer::Base.deliveries.size - TestMailer.deliver_signed_up(@recipient) - assert_equal 1, ActionMailer::Base.deliveries.size - assert_not_nil ActionMailer::Base.deliveries.first - end - - def test_perform_deliveries_flag - ActionMailer::Base.perform_deliveries = false - TestMailer.deliver_signed_up(@recipient) - assert_equal 0, ActionMailer::Base.deliveries.size - ActionMailer::Base.perform_deliveries = true - TestMailer.deliver_signed_up(@recipient) - assert_equal 1, ActionMailer::Base.deliveries.size - end - - def test_unquote_quoted_printable_subject - msg = <" - - expected = new_mail "iso-8859-1" - expected.to = quote_address_if_necessary @recipient, "iso-8859-1" - expected.subject = "testing extended headers" - expected.body = "Nothing to see here." - expected.from = quote_address_if_necessary "Grytøyr ", "iso-8859-1" - expected.cc = quote_address_if_necessary "Grytøyr ", "iso-8859-1" - expected.bcc = quote_address_if_necessary "Grytøyr ", "iso-8859-1" - expected.date = Time.local 2004, 12, 12 - - created = nil - assert_nothing_raised do - created = TestMailer.create_extended_headers @recipient - end - - assert_not_nil created - assert_equal expected.encoded, created.encoded - - assert_nothing_raised do - TestMailer.deliver_extended_headers @recipient - end - - assert_not_nil ActionMailer::Base.deliveries.first - assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded - end - - def test_utf8_body_is_not_quoted - @recipient = "Foo áëô îü " - expected = new_mail "utf-8" - expected.to = quote_address_if_necessary @recipient, "utf-8" - expected.subject = "testing utf-8 body" - expected.body = "åœö blah" - expected.from = quote_address_if_necessary @recipient, "utf-8" - expected.cc = quote_address_if_necessary @recipient, "utf-8" - expected.bcc = quote_address_if_necessary @recipient, "utf-8" - expected.date = Time.local 2004, 12, 12 - - created = TestMailer.create_utf8_body @recipient - assert_match(/åœö blah/, created.encoded) - end - - def test_multiple_utf8_recipients - @recipient = ["\"Foo áëô îü\" ", "\"Example Recipient\" "] - expected = new_mail "utf-8" - expected.to = quote_address_if_necessary @recipient, "utf-8" - expected.subject = "testing utf-8 body" - expected.body = "åœö blah" - expected.from = quote_address_if_necessary @recipient.first, "utf-8" - expected.cc = quote_address_if_necessary @recipient, "utf-8" - expected.bcc = quote_address_if_necessary @recipient, "utf-8" - expected.date = Time.local 2004, 12, 12 - - created = TestMailer.create_utf8_body @recipient - assert_match(/\nFrom: =\?utf-8\?Q\?Foo_.*?\?= \r/, created.encoded) - assert_match(/\nTo: =\?utf-8\?Q\?Foo_.*?\?= , Example Recipient _Google}, mail.body - end - - def test_various_newlines - mail = TestMailer.create_various_newlines(@recipient) - assert_equal("line #1\nline #2\nline #3\nline #4\n\n" + - "line #5\n\nline#6\n\nline #7", mail.body) - end - - def test_various_newlines_multipart - mail = TestMailer.create_various_newlines_multipart(@recipient) - assert_equal "line #1\nline #2\nline #3\nline #4\n\n", mail.parts[0].body - assert_equal "

line #1

\n

line #2

\n

line #3

\n

line #4

\n\n", mail.parts[1].body - end - - def test_headers_removed_on_smtp_delivery - ActionMailer::Base.delivery_method = :smtp - TestMailer.deliver_cc_bcc(@recipient) - assert MockSMTP.deliveries[0][2].include?("root@loudthinking.com") - assert MockSMTP.deliveries[0][2].include?("nobody@loudthinking.com") - assert MockSMTP.deliveries[0][2].include?(@recipient) - assert_match %r{^Cc: nobody@loudthinking.com}, MockSMTP.deliveries[0][0] - assert_match %r{^To: #{@recipient}}, MockSMTP.deliveries[0][0] - assert_no_match %r{^Bcc: root@loudthinking.com}, MockSMTP.deliveries[0][0] - end - - def test_recursive_multipart_processing - fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email7") - mail = TMail::Mail.parse(fixture) - assert_equal "This is the first part.\n\nAttachment: test.rb\nAttachment: test.pdf\n\n\nAttachment: smime.p7s\n", mail.body - end - - def test_decode_encoded_attachment_filename - fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email8") - mail = TMail::Mail.parse(fixture) - attachment = mail.attachments.last - assert_equal "01QuienTeDijat.Pitbull.mp3", attachment.original_filename - end - - def test_wrong_mail_header - fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email9") - assert_raise(TMail::SyntaxError) { TMail::Mail.parse(fixture) } - end - - def test_decode_message_with_unknown_charset - fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email10") - mail = TMail::Mail.parse(fixture) - assert_nothing_raised { mail.body } - end - - def test_decode_message_with_unquoted_atchar_in_header - fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email11") - mail = TMail::Mail.parse(fixture) - assert_not_nil mail.from - end - - def test_empty_header_values_omitted - result = TestMailer.create_unnamed_attachment(@recipient).encoded - assert_match %r{Content-Type: application/octet-stream[^;]}, result - assert_match %r{Content-Disposition: attachment[^;]}, result - end - - def test_headers_with_nonalpha_chars - mail = TestMailer.create_headers_with_nonalpha_chars(@recipient) - assert !mail.from_addrs.empty? - assert !mail.cc_addrs.empty? - assert !mail.bcc_addrs.empty? - assert_match(/:/, mail.from_addrs.to_s) - assert_match(/:/, mail.cc_addrs.to_s) - assert_match(/:/, mail.bcc_addrs.to_s) - end - - def test_deliver_with_mail_object - mail = TestMailer.create_headers_with_nonalpha_chars(@recipient) - assert_nothing_raised { TestMailer.deliver(mail) } - assert_equal 1, TestMailer.deliveries.length - end - - def test_multipart_with_template_path_with_dots - mail = FunkyPathMailer.create_multipart_with_template_path_with_dots(@recipient) - assert_equal 2, mail.parts.length - end - - def test_custom_content_type_attributes - mail = TestMailer.create_custom_content_type_attributes - assert_match %r{format=flowed}, mail['content-type'].to_s - assert_match %r{charset=utf-8}, mail['content-type'].to_s - end -end - -class InheritableTemplateRootTest < Test::Unit::TestCase - def test_attr - expected = "#{File.dirname(__FILE__)}/fixtures/path.with.dots" - assert_equal expected, FunkyPathMailer.template_root - - sub = Class.new(FunkyPathMailer) - sub.template_root = 'test/path' - - assert_equal 'test/path', sub.template_root - assert_equal expected, FunkyPathMailer.template_root - end -end -$:.unshift(File.dirname(__FILE__) + "/../lib/") -$:.unshift(File.dirname(__FILE__) + "/../lib/action_mailer/vendor") - -require 'test/unit' -require 'tmail' -require 'tempfile' - -class QuotingTest < Test::Unit::TestCase - def test_quote_multibyte_chars - original = "\303\246 \303\270 and \303\245" - - result = execute_in_sandbox(<<-CODE) - $:.unshift(File.dirname(__FILE__) + "/../lib/") - $KCODE = 'u' - require 'jcode' - require 'action_mailer/quoting' - include ActionMailer::Quoting - quoted_printable(#{original.inspect}, "UTF-8") - CODE - - unquoted = TMail::Unquoter.unquote_and_convert_to(result, nil) - assert_equal unquoted, original - end - - private - - # This whole thing *could* be much simpler, but I don't think Tempfile, - # popen and others exist on all platforms (like Windows). - def execute_in_sandbox(code) - test_name = "#{File.dirname(__FILE__)}/am-quoting-test.#{$$}.rb" - res_name = "#{File.dirname(__FILE__)}/am-quoting-test.#{$$}.out" - - File.open(test_name, "w+") do |file| - file.write(<<-CODE) - block = Proc.new do - #{code} - end - puts block.call - CODE - end - - system("ruby #{test_name} > #{res_name}") or raise "could not run test in sandbox" - File.read(res_name) - ensure - File.delete(test_name) rescue nil - File.delete(res_name) rescue nil - end -end -$:.unshift(File.dirname(__FILE__) + "/../lib/") -$:.unshift File.dirname(__FILE__) + "/fixtures/helpers" - -require 'test/unit' -require 'action_mailer' - -class TMailMailTest < Test::Unit::TestCase - def test_body - m = TMail::Mail.new - expected = 'something_with_underscores' - m.encoding = 'quoted-printable' - quoted_body = [expected].pack('*M') - m.body = quoted_body - assert_equal "something_with_underscores=\n", m.quoted_body - assert_equal expected, m.body - end -end -$:.unshift(File.dirname(__FILE__) + "/../lib") - -require "action_controller" -require "action_controller/test_process" - -Person = Struct.new("Person", :id, :name, :email_address, :phone_number) - -class AddressBookService - attr_reader :people - - def initialize() @people = [] end - def create_person(data) people.unshift(Person.new(next_person_id, data["name"], data["email_address"], data["phone_number"])) end - def find_person(topic_id) people.select { |person| person.id == person.to_i }.first end - def next_person_id() people.first.id + 1 end -end - -class AddressBookController < ActionController::Base - layout "address_book/layout" - - before_filter :initialize_session_storage - - # Could also have used a proc - # before_filter proc { |c| c.instance_variable_set("@address_book", c.session["address_book"] ||= AddressBookService.new) } - - def index - @title = "Address Book" - @people = @address_book.people - end - - def person - @person = @address_book.find_person(@params["id"]) - end - - def create_person - @address_book.create_person(@params["person"]) - redirect_to :action => "index" - end - - private - def initialize_session_storage - @address_book = @session["address_book"] ||= AddressBookService.new - end -end - -ActionController::Base.template_root = File.dirname(__FILE__) -# ActionController::Base.logger = Logger.new("debug.log") # Remove first comment to turn on logging in current dir - -begin - AddressBookController.process_cgi(CGI.new) if $0 == __FILE__ -rescue => e - CGI.new.out { "#{e.class}: #{e.message}" } -end$:.unshift(File.dirname(__FILE__) + "/../lib") - -require "action_controller" -require 'action_controller/test_process' - -Person = Struct.new("Person", :name, :address, :age) - -class BenchmarkController < ActionController::Base - def message - render_text "hello world" - end - - def list - @people = [ Person.new("David"), Person.new("Mary") ] - render_template "hello: <% for person in @people %>Name: <%= person.name %><% end %>" - end - - def form_helper - @person = Person.new "david", "hyacintvej", 24 - render_template( - "<% person = Person.new 'Mary', 'hyacintvej', 22 %> " + - "change the name <%= text_field 'person', 'name' %> and <%= text_field 'person', 'address' %> and <%= text_field 'person', 'age' %>" - ) - end -end - -#ActionController::Base.template_root = File.dirname(__FILE__) - -require "benchmark" - -RUNS = ARGV[0] ? ARGV[0].to_i : 50 - -require "profile" if ARGV[1] - -runtime = Benchmark.measure { - RUNS.times { BenchmarkController.process_test(ActionController::TestRequest.new({ "action" => "list" })) } -} - -puts "List: #{RUNS / runtime.real}" - - -runtime = Benchmark.measure { - RUNS.times { BenchmarkController.process_test(ActionController::TestRequest.new({ "action" => "message" })) } -} - -puts "Message: #{RUNS / runtime.real}" - -runtime = Benchmark.measure { - RUNS.times { BenchmarkController.process_test(ActionController::TestRequest.new({ "action" => "form_helper" })) } -} - -puts "Form helper: #{RUNS / runtime.real}" -require 'rbconfig' -require 'find' -require 'ftools' - -include Config - -# this was adapted from rdoc's install.rb by ways of Log4r - -$sitedir = CONFIG["sitelibdir"] -unless $sitedir - version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"] - $libdir = File.join(CONFIG["libdir"], "ruby", version) - $sitedir = $:.find {|x| x =~ /site_ruby/ } - if !$sitedir - $sitedir = File.join($libdir, "site_ruby") - elsif $sitedir !~ Regexp.quote(version) - $sitedir = File.join($sitedir, version) - end -end - -# the acual gruntwork -Dir.chdir("lib") - -Find.find("action_controller", "action_controller.rb", "action_view", "action_view.rb") { |f| - if f[-3..-1] == ".rb" - File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true) - else - File::makedirs(File.join($sitedir, *f.split(/\//))) - end -}require 'test/unit' -require 'test/unit/assertions' -require 'rexml/document' -require File.dirname(__FILE__) + "/vendor/html-scanner/html/document" - -module Test #:nodoc: - module Unit #:nodoc: - # In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions - # can be used against. These collections are: - # - # * assigns: Instance variables assigned in the action that are available for the view. - # * session: Objects being saved in the session. - # * flash: The flash objects currently in the session. - # * cookies: Cookies being sent to the user on this request. - # - # These collections can be used just like any other hash: - # - # assert_not_nil assigns(:person) # makes sure that a @person instance variable was set - # assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave" - # assert flash.empty? # makes sure that there's nothing in the flash - # - # For historic reasons, the assigns hash uses string-based keys. So assigns[:person] won't work, but assigns["person"] will. To - # appease our yearning for symbols, though, an alternative accessor has been deviced using a method call instead of index referencing. - # So assigns(:person) will work just like assigns["person"], but again, assigns[:person] will not work. - # - # On top of the collections, you have the complete url that a given action redirected to available in redirect_to_url. - # - # For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another - # action call which can then be asserted against. - # - # == Manipulating the request collections - # - # The collections described above link to the response, so you can test if what the actions were expected to do happened. But - # sometimes you also want to manipulate these collections in the incoming request. This is really only relevant for sessions - # and cookies, though. For sessions, you just do: - # - # @request.session[:key] = "value" - # - # For cookies, you need to manually create the cookie, like this: - # - # @request.cookies["key"] = CGI::Cookie.new("key", "value") - # - # == Testing named routes - # - # If you're using named routes, they can be easily tested using the original named routes methods straight in the test case. - # Example: - # - # assert_redirected_to page_url(:title => 'foo') - module Assertions - # Asserts that the response is one of the following types: - # - # * :success: Status code was 200 - # * :redirect: Status code was in the 300-399 range - # * :missing: Status code was 404 - # * :error: Status code was in the 500-599 range - # - # You can also pass an explicit status code number as the type, like assert_response(501) - def assert_response(type, message = nil) - clean_backtrace do - if [ :success, :missing, :redirect, :error ].include?(type) && @response.send("#{type}?") - assert_block("") { true } # to count the assertion - elsif type.is_a?(Fixnum) && @response.response_code == type - assert_block("") { true } # to count the assertion - else - assert_block(build_message(message, "Expected response to be a , but was ", type, @response.response_code)) { false } - end - end - end - - # Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, - # such that assert_redirected_to(:controller => "weblog") will also match the redirection of - # redirect_to(:controller => "weblog", :action => "show") and so on. - def assert_redirected_to(options = {}, message=nil) - clean_backtrace do - assert_response(:redirect, message) - - if options.is_a?(String) - msg = build_message(message, "expected a redirect to , found one to ", options, @response.redirect_url) - url_regexp = %r{^(\w+://.*?(/|$|\?))(.*)$} - eurl, epath, url, path = [options, @response.redirect_url].collect do |url| - u, p = (url_regexp =~ url) ? [$1, $3] : [nil, url] - [u, (p[0..0] == '/') ? p : '/' + p] - end.flatten - - assert_equal(eurl, url, msg) if eurl && url - assert_equal(epath, path, msg) if epath && path - else - @response_diff = options.diff(@response.redirected_to) if options.is_a?(Hash) && @response.redirected_to.is_a?(Hash) - msg = build_message(message, "response is not a redirection to all of the options supplied (redirection is )#{', difference: ' if @response_diff}", - @response.redirected_to || @response.redirect_url, @response_diff) - - assert_block(msg) do - if options.is_a?(Symbol) - @response.redirected_to == options - else - options.keys.all? do |k| - if k == :controller then options[k] == ActionController::Routing.controller_relative_to(@response.redirected_to[k], @controller.class.controller_path) - else options[k] == (@response.redirected_to[k].respond_to?(:to_param) ? @response.redirected_to[k].to_param : @response.redirected_to[k] unless @response.redirected_to[k].nil?) - end - end - end - end - end - end - end - - # Asserts that the request was rendered with the appropriate template file. - def assert_template(expected = nil, message=nil) - clean_backtrace do - rendered = expected ? @response.rendered_file(!expected.include?('/')) : @response.rendered_file - msg = build_message(message, "expecting but rendering with ", expected, rendered) - assert_block(msg) do - if expected.nil? - !@response.rendered_with_file? - else - expected == rendered - end - end - end - end - - # Asserts that the routing of the given path was handled correctly and that the parsed options match. - def assert_recognizes(expected_options, path, extras={}, message=nil) - clean_backtrace do - path = "/#{path}" unless path[0..0] == '/' - # Load routes.rb if it hasn't been loaded. - ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty? - - # Assume given controller - request = ActionController::TestRequest.new({}, {}, nil) - request.path = path - ActionController::Routing::Routes.recognize!(request) - - expected_options = expected_options.clone - extras.each_key { |key| expected_options.delete key } unless extras.nil? - - expected_options.stringify_keys! - msg = build_message(message, "The recognized options did not match ", - request.path_parameters, expected_options) - assert_block(msg) { request.path_parameters == expected_options } - end - end - - # Asserts that the provided options can be used to generate the provided path. - def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil) - clean_backtrace do - expected_path = "/#{expected_path}" unless expected_path[0] == ?/ - # Load routes.rb if it hasn't been loaded. - ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty? - - generated_path, extra_keys = ActionController::Routing::Routes.generate(options, extras) - found_extras = options.reject {|k, v| ! extra_keys.include? k} - - msg = build_message(message, "found extras , not ", found_extras, extras) - assert_block(msg) { found_extras == extras } - - msg = build_message(message, "The generated path did not match ", generated_path, - expected_path) - assert_block(msg) { expected_path == generated_path } - end - end - - # Asserts that path and options match both ways; in other words, the URL generated from - # options is the same as path, and also that the options recognized from path are the same as options - def assert_routing(path, options, defaults={}, extras={}, message=nil) - assert_recognizes(options, path, extras, message) - - controller, default_controller = options[:controller], defaults[:controller] - if controller && controller.include?(?/) && default_controller && default_controller.include?(?/) - options[:controller] = "/#{controller}" - end - - assert_generates(path, options, defaults, extras, message) - end - - # Asserts that there is a tag/node/element in the body of the response - # that meets all of the given conditions. The +conditions+ parameter must - # be a hash of any of the following keys (all are optional): - # - # * :tag: the node type must match the corresponding value - # * :attributes: a hash. The node's attributes must match the - # corresponding values in the hash. - # * :parent: a hash. The node's parent must match the - # corresponding hash. - # * :child: a hash. At least one of the node's immediate children - # must meet the criteria described by the hash. - # * :ancestor: a hash. At least one of the node's ancestors must - # meet the criteria described by the hash. - # * :descendant: a hash. At least one of the node's descendants - # must meet the criteria described by the hash. - # * :sibling: a hash. At least one of the node's siblings must - # meet the criteria described by the hash. - # * :after: a hash. The node must be after any sibling meeting - # the criteria described by the hash, and at least one sibling must match. - # * :before: a hash. The node must be before any sibling meeting - # the criteria described by the hash, and at least one sibling must match. - # * :children: a hash, for counting children of a node. Accepts - # the keys: - # * :count: either a number or a range which must equal (or - # include) the number of children that match. - # * :less_than: the number of matching children must be less - # than this number. - # * :greater_than: the number of matching children must be - # greater than this number. - # * :only: another hash consisting of the keys to use - # to match on the children, and only matching children will be - # counted. - # * :content: the textual content of the node must match the - # given value. This will not match HTML tags in the body of a - # tag--only text. - # - # Conditions are matched using the following algorithm: - # - # * if the condition is a string, it must be a substring of the value. - # * if the condition is a regexp, it must match the value. - # * if the condition is a number, the value must match number.to_s. - # * if the condition is +true+, the value must not be +nil+. - # * if the condition is +false+ or +nil+, the value must be +nil+. - # - # Usage: - # - # # assert that there is a "span" tag - # assert_tag :tag => "span" - # - # # assert that there is a "span" tag with id="x" - # assert_tag :tag => "span", :attributes => { :id => "x" } - # - # # assert that there is a "span" tag using the short-hand - # assert_tag :span - # - # # assert that there is a "span" tag with id="x" using the short-hand - # assert_tag :span, :attributes => { :id => "x" } - # - # # assert that there is a "span" inside of a "div" - # assert_tag :tag => "span", :parent => { :tag => "div" } - # - # # assert that there is a "span" somewhere inside a table - # assert_tag :tag => "span", :ancestor => { :tag => "table" } - # - # # assert that there is a "span" with at least one "em" child - # assert_tag :tag => "span", :child => { :tag => "em" } - # - # # assert that there is a "span" containing a (possibly nested) - # # "strong" tag. - # assert_tag :tag => "span", :descendant => { :tag => "strong" } - # - # # assert that there is a "span" containing between 2 and 4 "em" tags - # # as immediate children - # assert_tag :tag => "span", - # :children => { :count => 2..4, :only => { :tag => "em" } } - # - # # get funky: assert that there is a "div", with an "ul" ancestor - # # and an "li" parent (with "class" = "enum"), and containing a - # # "span" descendant that contains text matching /hello world/ - # assert_tag :tag => "div", - # :ancestor => { :tag => "ul" }, - # :parent => { :tag => "li", - # :attributes => { :class => "enum" } }, - # :descendant => { :tag => "span", - # :child => /hello world/ } - # - # Please noteYou must explicitly - # close all of your tags to use these assertions.
- def assert_tag(*opts) - clean_backtrace do - opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first - tag = find_tag(opts) - assert tag, "expected tag, but no tag found matching #{opts.inspect} in:\n#{@response.body.inspect}" - end - end - - # Identical to #assert_tag, but asserts that a matching tag does _not_ - # exist. (See #assert_tag for a full discussion of the syntax.) - def assert_no_tag(*opts) - clean_backtrace do - opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first - tag = find_tag(opts) - assert !tag, "expected no tag, but found tag matching #{opts.inspect} in:\n#{@response.body.inspect}" - end - end - - # test 2 html strings to be equivalent, i.e. identical up to reordering of attributes - def assert_dom_equal(expected, actual, message="") - clean_backtrace do - expected_dom = HTML::Document.new(expected).root - actual_dom = HTML::Document.new(actual).root - full_message = build_message(message, " expected to be == to\n.", expected_dom.to_s, actual_dom.to_s) - assert_block(full_message) { expected_dom == actual_dom } - end - end - - # negated form of +assert_dom_equivalent+ - def assert_dom_not_equal(expected, actual, message="") - clean_backtrace do - expected_dom = HTML::Document.new(expected).root - actual_dom = HTML::Document.new(actual).root - full_message = build_message(message, " expected to be != to\n.", expected_dom.to_s, actual_dom.to_s) - assert_block(full_message) { expected_dom != actual_dom } - end - end - - # ensures that the passed record is valid by active record standards. returns the error messages if not - def assert_valid(record) - clean_backtrace do - assert record.valid?, record.errors.full_messages.join("\n") - end - end - - def clean_backtrace(&block) - yield - rescue AssertionFailedError => e - path = File.expand_path(__FILE__) - raise AssertionFailedError, e.message, e.backtrace.reject { |line| File.expand_path(line) =~ /#{path}/ } - end - end - end -end -require 'action_controller/mime_type' -require 'action_controller/request' -require 'action_controller/response' -require 'action_controller/routing' -require 'action_controller/code_generation' -require 'action_controller/url_rewriter' -require 'drb' -require 'set' - -module ActionController #:nodoc: - class ActionControllerError < StandardError #:nodoc: - end - class SessionRestoreError < ActionControllerError #:nodoc: - end - class MissingTemplate < ActionControllerError #:nodoc: - end - class RoutingError < ActionControllerError #:nodoc: - attr_reader :failures - def initialize(message, failures=[]) - super(message) - @failures = failures - end - end - class UnknownController < ActionControllerError #:nodoc: - end - class UnknownAction < ActionControllerError #:nodoc: - end - class MissingFile < ActionControllerError #:nodoc: - end - class SessionOverflowError < ActionControllerError #:nodoc: - DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.' - - def initialize(message = nil) - super(message || DEFAULT_MESSAGE) - end - end - class DoubleRenderError < ActionControllerError #:nodoc: - DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and only once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\". Finally, note that to cause a before filter to halt execution of the rest of the filter chain, the filter must return false, explicitly, so \"render(...) and return false\"." - - def initialize(message = nil) - super(message || DEFAULT_MESSAGE) - end - end - class RedirectBackError < ActionControllerError #:nodoc: - DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify @request.env["HTTP_REFERER"].' - - def initialize(message = nil) - super(message || DEFAULT_MESSAGE) - end - end - - # Action Controllers are the core of a web request in Rails. They are made up of one or more actions that are executed - # on request and then either render a template or redirect to another action. An action is defined as a public method - # on the controller, which will automatically be made accessible to the web-server through Rails Routes. - # - # A sample controller could look like this: - # - # class GuestBookController < ActionController::Base - # def index - # @entries = Entry.find(:all) - # end - # - # def sign - # Entry.create(params[:entry]) - # redirect_to :action => "index" - # end - # end - # - # Actions, by default, render a template in the app/views directory corresponding to the name of the controller and action - # after executing code in the action. For example, the +index+ action of the +GuestBookController+ would render the - # template app/views/guestbook/index.rhtml by default after populating the @entries instance variable. - # - # Unlike index, the sign action will not render a template. After performing its main purpose (creating a - # new entry in the guest book), it initiates a redirect instead. This redirect works by returning an external - # "302 Moved" HTTP response that takes the user to the index action. - # - # The index and sign represent the two basic action archetypes used in Action Controllers. Get-and-show and do-and-redirect. - # Most actions are variations of these themes. - # - # == Requests - # - # Requests are processed by the Action Controller framework by extracting the value of the "action" key in the request parameters. - # This value should hold the name of the action to be performed. Once the action has been identified, the remaining - # request parameters, the session (if one is available), and the full request with all the http headers are made available to - # the action through instance variables. Then the action is performed. - # - # The full request object is available with the request accessor and is primarily used to query for http headers. These queries - # are made by accessing the environment hash, like this: - # - # def server_ip - # location = request.env["SERVER_ADDR"] - # render :text => "This server hosted at #{location}" - # end - # - # == Parameters - # - # All request parameters, whether they come from a GET or POST request, or from the URL, are available through the params method - # which returns a hash. For example, an action that was performed through /weblog/list?category=All&limit=5 will include - # { "category" => "All", "limit" => 5 } in params. - # - # It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as: - # - # - # - # - # A request stemming from a form holding these inputs will include { "post" => { "name" => "david", "address" => "hyacintvej" } }. - # If the address input had been named "post[address][street]", the params would have included - # { "post" => { "address" => { "street" => "hyacintvej" } } }. There's no limit to the depth of the nesting. - # - # == Sessions - # - # Sessions allows you to store objects in between requests. This is useful for objects that are not yet ready to be persisted, - # such as a Signup object constructed in a multi-paged process, or objects that don't change much and are needed all the time, such - # as a User object for a system that requires login. The session should not be used, however, as a cache for objects where it's likely - # they could be changed unknowingly. It's usually too much work to keep it all synchronized -- something databases already excel at. - # - # You can place objects in the session by using the session method, which accesses a hash: - # - # session[:person] = Person.authenticate(user_name, password) - # - # And retrieved again through the same hash: - # - # Hello #{session[:person]} - # - # For removing objects from the session, you can either assign a single key to nil, like session[:person] = nil, or you can - # remove the entire session with reset_session. - # - # By default, sessions are stored on the file system in RAILS_ROOT/tmp/sessions. Any object can be placed in the session - # (as long as it can be Marshalled). But remember that 1000 active sessions each storing a 50kb object could lead to a 50MB store on the filesystem. - # In other words, think carefully about size and caching before resorting to the use of the session on the filesystem. - # - # An alternative to storing sessions on disk is to use ActiveRecordStore to store sessions in your database, which can solve problems - # caused by storing sessions in the file system and may speed up your application. To use ActiveRecordStore, uncomment the line: - # - # config.action_controller.session_store = :active_record_store - # - # in your environment.rb and run rake db:sessions:create. - # - # == Responses - # - # Each action results in a response, which holds the headers and document to be sent to the user's browser. The actual response - # object is generated automatically through the use of renders and redirects and requires no user intervention. - # - # == Renders - # - # Action Controller sends content to the user by using one of five rendering methods. The most versatile and common is the rendering - # of a template. Included in the Action Pack is the Action View, which enables rendering of ERb templates. It's automatically configured. - # The controller passes objects to the view by assigning instance variables: - # - # def show - # @post = Post.find(params[:id]) - # end - # - # Which are then automatically available to the view: - # - # Title: <%= @post.title %> - # - # You don't have to rely on the automated rendering. Especially actions that could result in the rendering of different templates will use - # the manual rendering methods: - # - # def search - # @results = Search.find(params[:query]) - # case @results - # when 0 then render :action => "no_results" - # when 1 then render :action => "show" - # when 2..10 then render :action => "show_many" - # end - # end - # - # Read more about writing ERb and Builder templates in link:classes/ActionView/Base.html. - # - # == Redirects - # - # Redirects are used to move from one action to another. For example, after a create action, which stores a blog entry to a database, - # we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're going to reuse (and redirect to) - # a show action that we'll assume has already been created. The code might look like this: - # - # def create - # @entry = Entry.new(params[:entry]) - # if @entry.save - # # The entry was saved correctly, redirect to show - # redirect_to :action => 'show', :id => @entry.id - # else - # # things didn't go so well, do something else - # end - # end - # - # In this case, after saving our new entry to the database, the user is redirected to the show method which is then executed. - # - # == Calling multiple redirects or renders - # - # An action should conclude with a single render or redirect. Attempting to try to do either again will result in a DoubleRenderError: - # - # def do_something - # redirect_to :action => "elsewhere" - # render :action => "overthere" # raises DoubleRenderError - # end - # - # If you need to redirect on the condition of something, then be sure to add "and return" to halt execution. - # - # def do_something - # redirect_to(:action => "elsewhere") and return if monkeys.nil? - # render :action => "overthere" # won't be called unless monkeys is nil - # end - # - class Base - DEFAULT_RENDER_STATUS_CODE = "200 OK" - - include Reloadable::Subclasses - - # Determines whether the view has access to controller internals @request, @response, @session, and @template. - # By default, it does. - @@view_controller_internals = true - cattr_accessor :view_controller_internals - - # Protected instance variable cache - @@protected_variables_cache = nil - cattr_accessor :protected_variables_cache - - # Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets, - # and images to a dedicated asset server away from the main web server. Example: - # ActionController::Base.asset_host = "http://assets.example.com" - @@asset_host = "" - cattr_accessor :asset_host - - # All requests are considered local by default, so everyone will be exposed to detailed debugging screens on errors. - # When the application is ready to go public, this should be set to false, and the protected method local_request? - # should instead be implemented in the controller to determine when debugging screens should be shown. - @@consider_all_requests_local = true - cattr_accessor :consider_all_requests_local - - # Enable or disable the collection of failure information for RoutingErrors. - # This information can be extremely useful when tweaking custom routes, but is - # pointless once routes have been tested and verified. - @@debug_routes = true - cattr_accessor :debug_routes - - # Controls whether the application is thread-safe, so multi-threaded servers like WEBrick know whether to apply a mutex - # around the performance of each action. Action Pack and Active Record are by default thread-safe, but many applications - # may not be. Turned off by default. - @@allow_concurrency = false - cattr_accessor :allow_concurrency - - # Modern REST web services often need to submit complex data to the web application. - # The param_parsers hash lets you register handlers wich will process the http body and add parameters to the - # params hash. These handlers are invoked for post and put requests. - # - # By default application/xml is enabled. A XmlSimple class with the same param name as the root will be instanciated - # in the params. This allows XML requests to mask themselves as regular form submissions, so you can have one - # action serve both regular forms and web service requests. - # - # Example of doing your own parser for a custom content type: - # - # ActionController::Base.param_parsers[Mime::Type.lookup('application/atom+xml')] = Proc.new do |data| - # node = REXML::Document.new(post) - # { node.root.name => node.root } - # end - # - # Note: Up until release 1.1 of Rails, Action Controller would default to using XmlSimple configured to discard the - # root node for such requests. The new default is to keep the root, such that "David" results - # in params[:r][:name] for "David" instead of params[:name]. To get the old behavior, you can - # re-register XmlSimple as application/xml handler ike this: - # - # ActionController::Base.param_parsers[Mime::XML] = - # Proc.new { |data| XmlSimple.xml_in(data, 'ForceArray' => false) } - # - # A YAML parser is also available and can be turned on with: - # - # ActionController::Base.param_parsers[Mime::YAML] = :yaml - @@param_parsers = { Mime::XML => :xml_simple } - cattr_accessor :param_parsers - - # Template root determines the base from which template references will be made. So a call to render("test/template") - # will be converted to "#{template_root}/test/template.rhtml". - class_inheritable_accessor :template_root - - # The logger is used for generating information on the action run-time (including benchmarking) if available. - # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers. - cattr_accessor :logger - - # Determines which template class should be used by ActionController. - cattr_accessor :template_class - - # Turn on +ignore_missing_templates+ if you want to unit test actions without making the associated templates. - cattr_accessor :ignore_missing_templates - - # Holds the request object that's primarily used to get environment variables through access like - # request.env["REQUEST_URI"]. - attr_accessor :request - - # Holds a hash of all the GET, POST, and Url parameters passed to the action. Accessed like params["post_id"] - # to get the post_id. No type casts are made, so all values are returned as strings. - attr_accessor :params - - # Holds the response object that's primarily used to set additional HTTP headers through access like - # response.headers["Cache-Control"] = "no-cache". Can also be used to access the final body HTML after a template - # has been rendered through response.body -- useful for after_filters that wants to manipulate the output, - # such as a OutputCompressionFilter. - attr_accessor :response - - # Holds a hash of objects in the session. Accessed like session[:person] to get the object tied to the "person" - # key. The session will hold any type of object as values, but the key should be a string or symbol. - attr_accessor :session - - # Holds a hash of header names and values. Accessed like headers["Cache-Control"] to get the value of the Cache-Control - # directive. Values should always be specified as strings. - attr_accessor :headers - - # Holds the hash of variables that are passed on to the template class to be made available to the view. This hash - # is generated by taking a snapshot of all the instance variables in the current scope just before a template is rendered. - attr_accessor :assigns - - # Returns the name of the action this controller is processing. - attr_accessor :action_name - - class << self - # Factory for the standard create, process loop where the controller is discarded after processing. - def process(request, response) #:nodoc: - new.process(request, response) - end - - # Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController". - def controller_class_name - @controller_class_name ||= name.demodulize - end - - # Converts the class name from something like "OneModule::TwoModule::NeatController" to "neat". - def controller_name - @controller_name ||= controller_class_name.sub(/Controller$/, '').underscore - end - - # Converts the class name from something like "OneModule::TwoModule::NeatController" to "one_module/two_module/neat". - def controller_path - @controller_path ||= name.gsub(/Controller$/, '').underscore - end - - # Return an array containing the names of public methods that have been marked hidden from the action processor. - # By default, all methods defined in ActionController::Base and included modules are hidden. - # More methods can be hidden using hide_actions. - def hidden_actions - write_inheritable_attribute(:hidden_actions, ActionController::Base.public_instance_methods) unless read_inheritable_attribute(:hidden_actions) - read_inheritable_attribute(:hidden_actions) - end - - # Hide each of the given methods from being callable as actions. - def hide_action(*names) - write_inheritable_attribute(:hidden_actions, hidden_actions | names.collect { |n| n.to_s }) - end - - # Replace sensitive paramater data from the request log. - # Filters paramaters that have any of the arguments as a substring. - # Looks in all subhashes of the param hash for keys to filter. - # If a block is given, each key and value of the paramater hash and all - # subhashes is passed to it, the value or key - # can be replaced using String#replace or similar method. - # - # Examples: - # filter_parameter_logging - # => Does nothing, just slows the logging process down - # - # filter_parameter_logging :password - # => replaces the value to all keys matching /password/i with "[FILTERED]" - # - # filter_parameter_logging :foo, "bar" - # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]" - # - # filter_parameter_logging { |k,v| v.reverse! if k =~ /secret/i } - # => reverses the value to all keys matching /secret/i - # - # filter_parameter_logging(:foo, "bar") { |k,v| v.reverse! if k =~ /secret/i } - # => reverses the value to all keys matching /secret/i, and - # replaces the value to all keys matching /foo|bar/i with "[FILTERED]" - def filter_parameter_logging(*filter_words, &block) - parameter_filter = Regexp.new(filter_words.collect{ |s| s.to_s }.join('|'), true) if filter_words.length > 0 - - define_method(:filter_parameters) do |unfiltered_parameters| - filtered_parameters = {} - - unfiltered_parameters.each do |key, value| - if key =~ parameter_filter - filtered_parameters[key] = '[FILTERED]' - elsif value.is_a?(Hash) - filtered_parameters[key] = filter_parameters(value) - elsif block_given? - key, value = key.dup, value.dup - yield key, value - filtered_parameters[key] = value - else - filtered_parameters[key] = value - end - end - - filtered_parameters - end - end - end - - public - # Extracts the action_name from the request parameters and performs that action. - def process(request, response, method = :perform_action, *arguments) #:nodoc: - initialize_template_class(response) - assign_shortcuts(request, response) - initialize_current_url - assign_names - forget_variables_added_to_assigns - - log_processing - send(method, *arguments) - - response - ensure - process_cleanup - end - - # Returns a URL that has been rewritten according to the options hash and the defined Routes. - # (For doing a complete redirect, use redirect_to). - #   - # url_for is used to: - #   - # All keys given to url_for are forwarded to the Route module, save for the following: - # * :anchor -- specifies the anchor name to be appended to the path. For example, - # url_for :controller => 'posts', :action => 'show', :id => 10, :anchor => 'comments' - # will produce "/posts/show/10#comments". - # * :only_path -- if true, returns the absolute URL (omitting the protocol, host name, and port) - # * :trailing_slash -- if true, adds a trailing slash, as in "/archive/2005/". Note that this - # is currently not recommended since it breaks caching. - # * :host -- overrides the default (current) host if provided - # * :protocol -- overrides the default (current) protocol if provided - # - # The URL is generated from the remaining keys in the hash. A URL contains two key parts: the and a query string. - # Routes composes a query string as the key/value pairs not included in the . - # - # The default Routes setup supports a typical Rails path of "controller/action/id" where action and id are optional, with - # action defaulting to 'index' when not given. Here are some typical url_for statements and their corresponding URLs: - #   - # url_for :controller => 'posts', :action => 'recent' # => 'proto://host.com/posts/recent' - # url_for :controller => 'posts', :action => 'index' # => 'proto://host.com/posts' - # url_for :controller => 'posts', :action => 'show', :id => 10 # => 'proto://host.com/posts/show/10' - # - # When generating a new URL, missing values may be filled in from the current request's parameters. For example, - # url_for :action => 'some_action' will retain the current controller, as expected. This behavior extends to - # other parameters, including :controller, :id, and any other parameters that are placed into a Route's - # path. - #   - # The URL helpers such as url_for have a limited form of memory: when generating a new URL, they can look for - # missing values in the current request's parameters. Routes attempts to guess when a value should and should not be - # taken from the defaults. There are a few simple rules on how this is performed: - # - # * If the controller name begins with a slash, no defaults are used: url_for :controller => '/home' - # * If the controller changes, the action will default to index unless provided - # - # The final rule is applied while the URL is being generated and is best illustrated by an example. Let us consider the - # route given by map.connect 'people/:last/:first/:action', :action => 'bio', :controller => 'people'. - # - # Suppose that the current URL is "people/hh/david/contacts". Let's consider a few different cases of URLs which are generated - # from this page. - # - # * url_for :action => 'bio' -- During the generation of this URL, default values will be used for the first and - # last components, and the action shall change. The generated URL will be, "people/hh/david/bio". - # * url_for :first => 'davids-little-brother' This generates the URL 'people/hh/davids-little-brother' -- note - # that this URL leaves out the assumed action of 'bio'. - # - # However, you might ask why the action from the current request, 'contacts', isn't carried over into the new URL. The - # answer has to do with the order in which the parameters appear in the generated path. In a nutshell, since the - # value that appears in the slot for :first is not equal to default value for :first we stop using - # defaults. On it's own, this rule can account for much of the typical Rails URL behavior. - #   - # Although a convienence, defaults can occasionaly get in your way. In some cases a default persists longer than desired. - # The default may be cleared by adding :name => nil to url_for's options. - # This is often required when writing form helpers, since the defaults in play may vary greatly depending upon where the - # helper is used from. The following line will redirect to PostController's default action, regardless of the page it is - # displayed on: - # - # url_for :controller => 'posts', :action => nil - # - # If you explicitly want to create a URL that's almost the same as the current URL, you can do so using the - # :overwrite_params options. Say for your posts you have different views for showing and printing them. - # Then, in the show view, you get the URL for the print view like this - # - # url_for :overwrite_params => { :action => 'print' } - # - # This takes the current URL as is and only exchanges the action. In contrast, url_for :action => 'print' - # would have slashed-off the path components after the changed action. - def url_for(options = {}, *parameters_for_method_reference) #:doc: - case options - when String then options - when Symbol then send(options, *parameters_for_method_reference) - when Hash then @url.rewrite(rewrite_options(options)) - end - end - - # Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController". - def controller_class_name - self.class.controller_class_name - end - - # Converts the class name from something like "OneModule::TwoModule::NeatController" to "neat". - def controller_name - self.class.controller_name - end - - def session_enabled? - request.session_options[:disabled] != false - end - - protected - # Renders the content that will be returned to the browser as the response body. - # - # === Rendering an action - # - # Action rendering is the most common form and the type used automatically by Action Controller when nothing else is - # specified. By default, actions are rendered within the current layout (if one exists). - # - # # Renders the template for the action "goal" within the current controller - # render :action => "goal" - # - # # Renders the template for the action "short_goal" within the current controller, - # # but without the current active layout - # render :action => "short_goal", :layout => false - # - # # Renders the template for the action "long_goal" within the current controller, - # # but with a custom layout - # render :action => "long_goal", :layout => "spectacular" - # - # _Deprecation_ _notice_: This used to have the signatures render_action("action", status = 200), - # render_without_layout("controller/action", status = 200), and - # render_with_layout("controller/action", status = 200, layout). - # - # === Rendering partials - # - # Partial rendering is most commonly used together with Ajax calls that only update one or a few elements on a page - # without reloading. Rendering of partials from the controller makes it possible to use the same partial template in - # both the full-page rendering (by calling it from within the template) and when sub-page updates happen (from the - # controller action responding to Ajax calls). By default, the current layout is not used. - # - # # Renders the partial located at app/views/controller/_win.r(html|xml) - # render :partial => "win" - # - # # Renders the partial with a status code of 500 (internal error) - # render :partial => "broken", :status => 500 - # - # # Renders the same partial but also makes a local variable available to it - # render :partial => "win", :locals => { :name => "david" } - # - # # Renders a collection of the same partial by making each element of @wins available through - # # the local variable "win" as it builds the complete response - # render :partial => "win", :collection => @wins - # - # # Renders the same collection of partials, but also renders the win_divider partial in between - # # each win partial. - # render :partial => "win", :collection => @wins, :spacer_template => "win_divider" - # - # _Deprecation_ _notice_: This used to have the signatures - # render_partial(partial_path = default_template_name, object = nil, local_assigns = {}) and - # render_partial_collection(partial_name, collection, partial_spacer_template = nil, local_assigns = {}). - # - # === Rendering a template - # - # Template rendering works just like action rendering except that it takes a path relative to the template root. - # The current layout is automatically applied. - # - # # Renders the template located in [TEMPLATE_ROOT]/weblog/show.r(html|xml) (in Rails, app/views/weblog/show.rhtml) - # render :template => "weblog/show" - # - # === Rendering a file - # - # File rendering works just like action rendering except that it takes a filesystem path. By default, the path - # is assumed to be absolute, and the current layout is not applied. - # - # # Renders the template located at the absolute filesystem path - # render :file => "/path/to/some/template.rhtml" - # render :file => "c:/path/to/some/template.rhtml" - # - # # Renders a template within the current layout, and with a 404 status code - # render :file => "/path/to/some/template.rhtml", :layout => true, :status => 404 - # render :file => "c:/path/to/some/template.rhtml", :layout => true, :status => 404 - # - # # Renders a template relative to the template root and chooses the proper file extension - # render :file => "some/template", :use_full_path => true - # - # _Deprecation_ _notice_: This used to have the signature render_file(path, status = 200) - # - # === Rendering text - # - # Rendering of text is usually used for tests or for rendering prepared content, such as a cache. By default, text - # rendering is not done within the active layout. - # - # # Renders the clear text "hello world" with status code 200 - # render :text => "hello world!" - # - # # Renders the clear text "Explosion!" with status code 500 - # render :text => "Explosion!", :status => 500 - # - # # Renders the clear text "Hi there!" within the current active layout (if one exists) - # render :text => "Explosion!", :layout => true - # - # # Renders the clear text "Hi there!" within the layout - # # placed in "app/views/layouts/special.r(html|xml)" - # render :text => "Explosion!", :layout => "special" - # - # _Deprecation_ _notice_: This used to have the signature render_text("text", status = 200) - # - # === Rendering an inline template - # - # Rendering of an inline template works as a cross between text and action rendering where the source for the template - # is supplied inline, like text, but its interpreted with ERb or Builder, like action. By default, ERb is used for rendering - # and the current layout is not used. - # - # # Renders "hello, hello, hello, again" - # render :inline => "<%= 'hello, ' * 3 + 'again' %>" - # - # # Renders "

Good seeing you!

" using Builder - # render :inline => "xml.p { 'Good seeing you!' }", :type => :rxml - # - # # Renders "hello david" - # render :inline => "<%= 'hello ' + name %>", :locals => { :name => "david" } - # - # _Deprecation_ _notice_: This used to have the signature render_template(template, status = 200, type = :rhtml) - # - # === Rendering inline JavaScriptGenerator page updates - # - # In addition to rendering JavaScriptGenerator page updates with Ajax in RJS templates (see ActionView::Base for details), - # you can also pass the :update parameter to +render+, along with a block, to render page updates inline. - # - # render :update do |page| - # page.replace_html 'user_list', :partial => 'user', :collection => @users - # page.visual_effect :highlight, 'user_list' - # end - # - # === Rendering nothing - # - # Rendering nothing is often convenient in combination with Ajax calls that perform their effect client-side or - # when you just want to communicate a status code. Due to a bug in Safari, nothing actually means a single space. - # - # # Renders an empty response with status code 200 - # render :nothing => true - # - # # Renders an empty response with status code 401 (access denied) - # render :nothing => true, :status => 401 - def render(options = nil, deprecated_status = nil, &block) #:doc: - raise DoubleRenderError, "Can only render or redirect once per action" if performed? - - # Backwards compatibility - unless options.is_a?(Hash) - if options == :update - options = {:update => true} - else - return render_file(options || default_template_name, deprecated_status, true) - end - end - - if content_type = options[:content_type] - headers["Content-Type"] = content_type - end - - if text = options[:text] - render_text(text, options[:status]) - - else - if file = options[:file] - render_file(file, options[:status], options[:use_full_path], options[:locals] || {}) - - elsif template = options[:template] - render_file(template, options[:status], true) - - elsif inline = options[:inline] - render_template(inline, options[:status], options[:type], options[:locals] || {}) - - elsif action_name = options[:action] - render_action(action_name, options[:status], options[:layout]) - - elsif xml = options[:xml] - render_xml(xml, options[:status]) - - elsif partial = options[:partial] - partial = default_template_name if partial == true - if collection = options[:collection] - render_partial_collection(partial, collection, options[:spacer_template], options[:locals], options[:status]) - else - render_partial(partial, ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals], options[:status]) - end - - elsif options[:update] - add_variables_to_assigns - @template.send :evaluate_assigns - - generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(@template, &block) - render_javascript(generator.to_s) - - elsif options[:nothing] - # Safari doesn't pass the headers of the return if the response is zero length - render_text(" ", options[:status]) - - else - render_file(default_template_name, options[:status], true) - - end - end - end - - # Renders according to the same rules as render, but returns the result in a string instead - # of sending it as the response body to the browser. - def render_to_string(options = nil, &block) #:doc: - result = render(options, &block) - - erase_render_results - forget_variables_added_to_assigns - reset_variables_added_to_assigns - - result - end - - def render_action(action_name, status = nil, with_layout = true) #:nodoc: - template = default_template_name(action_name.to_s) - if with_layout && !template_exempt_from_layout?(template) - render_with_layout(template, status) - else - render_without_layout(template, status) - end - end - - def render_file(template_path, status = nil, use_full_path = false, locals = {}) #:nodoc: - add_variables_to_assigns - assert_existence_of_template_file(template_path) if use_full_path - logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger - render_text(@template.render_file(template_path, use_full_path, locals), status) - end - - def render_template(template, status = nil, type = :rhtml, local_assigns = {}) #:nodoc: - add_variables_to_assigns - render_text(@template.render_template(type, template, nil, local_assigns), status) - end - - def render_text(text = nil, status = nil) #:nodoc: - @performed_render = true - @response.headers['Status'] = (status || DEFAULT_RENDER_STATUS_CODE).to_s - @response.body = text - end - - def render_javascript(javascript, status = nil) #:nodoc: - @response.headers['Content-Type'] = 'text/javascript; charset=UTF-8' - render_text(javascript, status) - end - - def render_xml(xml, status = nil) #:nodoc: - @response.headers['Content-Type'] = 'application/xml' - render_text(xml, status) - end - - def render_nothing(status = nil) #:nodoc: - render_text(' ', status) - end - - def render_partial(partial_path = default_template_name, object = nil, local_assigns = nil, status = nil) #:nodoc: - add_variables_to_assigns - render_text(@template.render_partial(partial_path, object, local_assigns), status) - end - - def render_partial_collection(partial_name, collection, partial_spacer_template = nil, local_assigns = nil, status = nil) #:nodoc: - add_variables_to_assigns - render_text(@template.render_partial_collection(partial_name, collection, partial_spacer_template, local_assigns), status) - end - - def render_with_layout(template_name = default_template_name, status = nil, layout = nil) #:nodoc: - render_with_a_layout(template_name, status, layout) - end - - def render_without_layout(template_name = default_template_name, status = nil) #:nodoc: - render_with_no_layout(template_name, status) - end - - - # Clears the rendered results, allowing for another render to be performed. - def erase_render_results #:nodoc: - @response.body = nil - @performed_render = false - end - - # Clears the redirected results from the headers, resets the status to 200 and returns - # the URL that was used to redirect or nil if there was no redirected URL - # Note that +redirect_to+ will change the body of the response to indicate a redirection. - # The response body is not reset here, see +erase_render_results+ - def erase_redirect_results #:nodoc: - @performed_redirect = false - response.redirected_to = nil - response.redirected_to_method_params = nil - response.headers['Status'] = DEFAULT_RENDER_STATUS_CODE - response.headers.delete('location') - end - - # Erase both render and redirect results - def erase_results #:nodoc: - erase_render_results - erase_redirect_results - end - - def rewrite_options(options) #:nodoc: - if defaults = default_url_options(options) - defaults.merge(options) - else - options - end - end - - # Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in - # the form of a hash, just like the one you would use for url_for directly. Example: - # - # def default_url_options(options) - # { :project => @project.active? ? @project.url_name : "unknown" } - # end - # - # As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the - # urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set - # by this method. - def default_url_options(options) #:doc: - end - - # Redirects the browser to the target specified in +options+. This parameter can take one of three forms: - # - # * Hash: The URL will be generated by calling url_for with the +options+. - # * String starting with protocol:// (like http://): Is passed straight through as the target for redirection. - # * String not containing a protocol: The current protocol and host is prepended to the string. - # * :back: Back to the page that issued the request. Useful for forms that are triggered from multiple places. - # Short-hand for redirect_to(request.env["HTTP_REFERER"]) - # - # Examples: - # redirect_to :action => "show", :id => 5 - # redirect_to "http://www.rubyonrails.org" - # redirect_to "/images/screenshot.jpg" - # redirect_to :back - # - # The redirection happens as a "302 Moved" header. - # - # When using redirect_to :back, if there is no referrer, - # RedirectBackError will be raised. You may specify some fallback - # behavior for this case by rescueing RedirectBackError. - def redirect_to(options = {}, *parameters_for_method_reference) #:doc: - case options - when %r{^\w+://.*} - raise DoubleRenderError if performed? - logger.info("Redirected to #{options}") if logger - response.redirect(options) - response.redirected_to = options - @performed_redirect = true - - when String - redirect_to(request.protocol + request.host_with_port + options) - - when :back - request.env["HTTP_REFERER"] ? redirect_to(request.env["HTTP_REFERER"]) : raise(RedirectBackError) - - else - if parameters_for_method_reference.empty? - redirect_to(url_for(options)) - response.redirected_to = options - else - redirect_to(url_for(options, *parameters_for_method_reference)) - response.redirected_to, response.redirected_to_method_params = options, parameters_for_method_reference - end - end - end - - # Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a "private" instruction, so that - # intermediate caches shouldn't cache the response. - # - # Examples: - # expires_in 20.minutes - # expires_in 3.hours, :private => false - # expires in 3.hours, 'max-stale' => 5.hours, :private => nil, :public => true - # - # This method will overwrite an existing Cache-Control header. - # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities. - def expires_in(seconds, options = {}) #:doc: - cache_options = { 'max-age' => seconds, 'private' => true }.symbolize_keys.merge!(options.symbolize_keys) - cache_options.delete_if { |k,v| v.nil? or v == false } - cache_control = cache_options.map{ |k,v| v == true ? k.to_s : "#{k.to_s}=#{v.to_s}"} - @response.headers["Cache-Control"] = cache_control.join(', ') - end - - # Sets a HTTP 1.1 Cache-Control header of "no-cache" so no caching should occur by the browser or - # intermediate caches (like caching proxy servers). - def expires_now #:doc: - @response.headers["Cache-Control"] = "no-cache" - end - - # Resets the session by clearing out all the objects stored within and initializing a new session object. - def reset_session #:doc: - @request.reset_session - @session = @request.session - @response.session = @session - end - - private - def self.view_class - @view_class ||= - # create a new class based on the default template class and include helper methods - returning Class.new(ActionView::Base) do |view_class| - view_class.send(:include, master_helper_module) - end - end - - def self.view_root - @view_root ||= template_root - end - - def initialize_template_class(response) - raise "You must assign a template class through ActionController.template_class= before processing a request" unless @@template_class - - response.template = self.class.view_class.new(self.class.view_root, {}, self) - response.redirected_to = nil - @performed_render = @performed_redirect = false - end - - def assign_shortcuts(request, response) - @request, @params, @cookies = request, request.parameters, request.cookies - - @response = response - @response.session = request.session - - @session = @response.session - @template = @response.template - @assigns = @response.template.assigns - - @headers = @response.headers - end - - def initialize_current_url - @url = UrlRewriter.new(@request, @params.clone()) - end - - def log_processing - if logger - logger.info "\n\nProcessing #{controller_class_name}\##{action_name} (for #{request_origin}) [#{request.method.to_s.upcase}]" - logger.info " Session ID: #{@session.session_id}" if @session and @session.respond_to?(:session_id) - logger.info " Parameters: #{respond_to?(:filter_parameters) ? filter_parameters(@params).inspect : @params.inspect}" - end - end - - def perform_action - if self.class.action_methods.include?(action_name) || self.class.action_methods.include?('method_missing') - send(action_name) - render unless performed? - elsif template_exists? && template_public? - render - else - raise UnknownAction, "No action responded to #{action_name}", caller - end - end - - def performed? - @performed_render || @performed_redirect - end - - def assign_names - @action_name = (params['action'] || 'index') - end - - def action_methods - self.class.action_methods - end - - def self.action_methods - @action_methods ||= Set.new(public_instance_methods - hidden_actions) - end - - def add_variables_to_assigns - unless @variables_added - add_instance_variables_to_assigns - add_class_variables_to_assigns if view_controller_internals - @variables_added = true - end - end - - def forget_variables_added_to_assigns - @variables_added = nil - end - - def reset_variables_added_to_assigns - @template.instance_variable_set("@assigns_added", nil) - end - - def add_instance_variables_to_assigns - @@protected_variables_cache ||= protected_instance_variables.inject({}) { |h, k| h[k] = true; h } - instance_variables.each do |var| - next if @@protected_variables_cache.include?(var) - @assigns[var[1..-1]] = instance_variable_get(var) - end - end - - def add_class_variables_to_assigns - %w( template_root logger template_class ignore_missing_templates ).each do |cvar| - @assigns[cvar] = self.send(cvar) - end - end - - def protected_instance_variables - if view_controller_internals - [ "@assigns", "@performed_redirect", "@performed_render" ] - else - [ "@assigns", "@performed_redirect", "@performed_render", "@request", "@response", "@session", "@cookies", "@template", "@request_origin", "@parent_controller" ] - end - end - - def request_origin - # this *needs* to be cached! - # otherwise you'd get different results if calling it more than once - @request_origin ||= "#{@request.remote_ip} at #{Time.now.to_s(:db)}" - end - - def complete_request_uri - "#{@request.protocol}#{@request.host}#{@request.request_uri}" - end - - def close_session - @session.close unless @session.nil? || Hash === @session - end - - def template_exists?(template_name = default_template_name) - @template.file_exists?(template_name) - end - - def template_public?(template_name = default_template_name) - @template.file_public?(template_name) - end - - def template_exempt_from_layout?(template_name = default_template_name) - template_name =~ /\.rjs$/ || (@template.pick_template_extension(template_name) == :rjs rescue false) - end - - def assert_existence_of_template_file(template_name) - unless template_exists?(template_name) || ignore_missing_templates - full_template_path = @template.send(:full_template_path, template_name, 'rhtml') - template_type = (template_name =~ /layouts/i) ? 'layout' : 'template' - raise(MissingTemplate, "Missing #{template_type} #{full_template_path}") - end - end - - def default_template_name(action_name = self.action_name) - if action_name - action_name = action_name.to_s - if action_name.include?('/') && template_path_includes_controller?(action_name) - action_name = strip_out_controller(action_name) - end - end - "#{self.class.controller_path}/#{action_name}" - end - - def strip_out_controller(path) - path.split('/', 2).last - end - - def template_path_includes_controller?(path) - self.class.controller_path.split('/')[-1] == path.split('/')[0] - end - - def process_cleanup - close_session - end - end -end -require 'benchmark' - -module ActionController #:nodoc: - # The benchmarking module times the performance of actions and reports to the logger. If the Active Record - # package has been included, a separate timing section for database calls will be added as well. - module Benchmarking #:nodoc: - def self.included(base) - base.extend(ClassMethods) - - base.class_eval do - alias_method :perform_action_without_benchmark, :perform_action - alias_method :perform_action, :perform_action_with_benchmark - - alias_method :render_without_benchmark, :render - alias_method :render, :render_with_benchmark - end - end - - module ClassMethods - # Log and benchmark the workings of a single block and silence whatever logging that may have happened inside it - # (unless use_silence is set to false). - # - # The benchmark is only recorded if the current level of the logger matches the log_level, which makes it - # easy to include benchmarking statements in production software that will remain inexpensive because the benchmark - # will only be conducted if the log level is low enough. - def benchmark(title, log_level = Logger::DEBUG, use_silence = true) - if logger && logger.level == log_level - result = nil - seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield } - logger.add(log_level, "#{title} (#{'%.5f' % seconds})") - result - else - yield - end - end - - # Silences the logger for the duration of the block. - def silence - old_logger_level, logger.level = logger.level, Logger::ERROR if logger - yield - ensure - logger.level = old_logger_level if logger - end - end - - def render_with_benchmark(options = nil, deprecated_status = nil, &block) - unless logger - render_without_benchmark(options, deprecated_status, &block) - else - db_runtime = ActiveRecord::Base.connection.reset_runtime if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected? - - render_output = nil - @rendering_runtime = Benchmark::measure{ render_output = render_without_benchmark(options, deprecated_status, &block) }.real - - if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected? - @db_rt_before_render = db_runtime - @db_rt_after_render = ActiveRecord::Base.connection.reset_runtime - @rendering_runtime -= @db_rt_after_render - end - - render_output - end - end - - def perform_action_with_benchmark - unless logger - perform_action_without_benchmark - else - runtime = [Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001].max - log_message = "Completed in #{sprintf("%.5f", runtime)} (#{(1 / runtime).floor} reqs/sec)" - log_message << rendering_runtime(runtime) if @rendering_runtime - log_message << active_record_runtime(runtime) if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected? - log_message << " | #{headers["Status"]}" - log_message << " [#{complete_request_uri rescue "unknown"}]" - logger.info(log_message) - end - end - - private - def rendering_runtime(runtime) - " | Rendering: #{sprintf("%.5f", @rendering_runtime)} (#{sprintf("%d", (@rendering_runtime * 100) / runtime)}%)" - end - - def active_record_runtime(runtime) - db_runtime = ActiveRecord::Base.connection.reset_runtime - db_runtime += @db_rt_before_render if @db_rt_before_render - db_runtime += @db_rt_after_render if @db_rt_after_render - db_percentage = (db_runtime * 100) / runtime - " | DB: #{sprintf("%.5f", db_runtime)} (#{sprintf("%d", db_percentage)}%)" - end - end -end -require 'fileutils' - -module ActionController #:nodoc: - # Caching is a cheap way of speeding up slow applications by keeping the result of calculations, renderings, and database calls - # around for subsequent requests. Action Controller affords you three approaches in varying levels of granularity: Page, Action, Fragment. - # - # You can read more about each approach and the sweeping assistance by clicking the modules below. - # - # Note: To turn off all caching and sweeping, set Base.perform_caching = false. - module Caching - def self.included(base) #:nodoc: - base.send(:include, Pages, Actions, Fragments, Sweeping) - - base.class_eval do - @@perform_caching = true - cattr_accessor :perform_caching - end - end - - # Page caching is an approach to caching where the entire action output of is stored as a HTML file that the web server - # can serve without going through the Action Pack. This can be as much as 100 times faster than going through the process of dynamically - # generating the content. Unfortunately, this incredible speed-up is only available to stateless pages where all visitors - # are treated the same. Content management systems -- including weblogs and wikis -- have many pages that are a great fit - # for this approach, but account-based systems where people log in and manipulate their own data are often less likely candidates. - # - # Specifying which actions to cache is done through the caches class method: - # - # class WeblogController < ActionController::Base - # caches_page :show, :new - # end - # - # This will generate cache files such as weblog/show/5 and weblog/new, which match the URLs used to trigger the dynamic - # generation. This is how the web server is able pick up a cache file when it exists and otherwise let the request pass on to - # the Action Pack to generate it. - # - # Expiration of the cache is handled by deleting the cached file, which results in a lazy regeneration approach where the cache - # is not restored before another hit is made against it. The API for doing so mimics the options from url_for and friends: - # - # class WeblogController < ActionController::Base - # def update - # List.update(params[:list][:id], params[:list]) - # expire_page :action => "show", :id => params[:list][:id] - # redirect_to :action => "show", :id => params[:list][:id] - # end - # end - # - # Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be - # expired. - # - # == Setting the cache directory - # - # The cache directory should be the document root for the web server and is set using Base.page_cache_directory = "/document/root". - # For Rails, this directory has already been set to RAILS_ROOT + "/public". - # - # == Setting the cache extension - # - # By default, the cache extension is .html, which makes it easy for the cached files to be picked up by the web server. If you want - # something else, like .php or .shtml, just set Base.page_cache_extension. - module Pages - def self.included(base) #:nodoc: - base.extend(ClassMethods) - base.class_eval do - @@page_cache_directory = defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/public" : "" - cattr_accessor :page_cache_directory - - @@page_cache_extension = '.html' - cattr_accessor :page_cache_extension - end - end - - module ClassMethods - # Expires the page that was cached with the +path+ as a key. Example: - # expire_page "/lists/show" - def expire_page(path) - return unless perform_caching - - benchmark "Expired page: #{page_cache_file(path)}" do - File.delete(page_cache_path(path)) if File.exists?(page_cache_path(path)) - end - end - - # Manually cache the +content+ in the key determined by +path+. Example: - # cache_page "I'm the cached content", "/lists/show" - def cache_page(content, path) - return unless perform_caching - - benchmark "Cached page: #{page_cache_file(path)}" do - FileUtils.makedirs(File.dirname(page_cache_path(path))) - File.open(page_cache_path(path), "wb+") { |f| f.write(content) } - end - end - - # Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that - # matches the triggering url. - def caches_page(*actions) - return unless perform_caching - actions.each do |action| - class_eval "after_filter { |c| c.cache_page if c.action_name == '#{action}' }" - end - end - - private - def page_cache_file(path) - name = ((path.empty? || path == "/") ? "/index" : URI.unescape(path)) - name << page_cache_extension unless (name.split('/').last || name).include? '.' - return name - end - - def page_cache_path(path) - page_cache_directory + page_cache_file(path) - end - end - - # Expires the page that was cached with the +options+ as a key. Example: - # expire_page :controller => "lists", :action => "show" - def expire_page(options = {}) - return unless perform_caching - if options[:action].is_a?(Array) - options[:action].dup.each do |action| - self.class.expire_page(url_for(options.merge({ :only_path => true, :skip_relative_url_root => true, :action => action }))) - end - else - self.class.expire_page(url_for(options.merge({ :only_path => true, :skip_relative_url_root => true }))) - end - end - - # Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of @response.body is used - # If no options are provided, the current +options+ for this action is used. Example: - # cache_page "I'm the cached content", :controller => "lists", :action => "show" - def cache_page(content = nil, options = {}) - return unless perform_caching && caching_allowed - self.class.cache_page(content || @response.body, url_for(options.merge({ :only_path => true, :skip_relative_url_root => true }))) - end - - private - def caching_allowed - !@request.post? && @response.headers['Status'] && @response.headers['Status'].to_i < 400 - end - end - - # Action caching is similar to page caching by the fact that the entire output of the response is cached, but unlike page caching, - # every request still goes through the Action Pack. The key benefit of this is that filters are run before the cache is served, which - # allows for authentication and other restrictions on whether someone is allowed to see the cache. Example: - # - # class ListsController < ApplicationController - # before_filter :authenticate, :except => :public - # caches_page :public - # caches_action :show, :feed - # end - # - # In this example, the public action doesn't require authentication, so it's possible to use the faster page caching method. But both the - # show and feed action are to be shielded behind the authenticate filter, so we need to implement those as action caches. - # - # Action caching internally uses the fragment caching and an around filter to do the job. The fragment cache is named according to both - # the current host and the path. So a page that is accessed at http://david.somewhere.com/lists/show/1 will result in a fragment named - # "david.somewhere.com/lists/show/1". This allows the cacher to differentiate between "david.somewhere.com/lists/" and - # "jamis.somewhere.com/lists/" -- which is a helpful way of assisting the subdomain-as-account-key pattern. - module Actions - def self.append_features(base) #:nodoc: - super - base.extend(ClassMethods) - base.send(:attr_accessor, :rendered_action_cache) - end - - module ClassMethods #:nodoc: - def caches_action(*actions) - return unless perform_caching - around_filter(ActionCacheFilter.new(*actions)) - end - end - - def expire_action(options = {}) - return unless perform_caching - if options[:action].is_a?(Array) - options[:action].dup.each do |action| - expire_fragment(url_for(options.merge({ :action => action })).split("://").last) - end - else - expire_fragment(url_for(options).split("://").last) - end - end - - class ActionCacheFilter #:nodoc: - def initialize(*actions) - @actions = actions - end - - def before(controller) - return unless @actions.include?(controller.action_name.intern) - if cache = controller.read_fragment(controller.url_for.split("://").last) - controller.rendered_action_cache = true - controller.send(:render_text, cache) - false - end - end - - def after(controller) - return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache - controller.write_fragment(controller.url_for.split("://").last, controller.response.body) - end - end - end - - # Fragment caching is used for caching various blocks within templates without caching the entire action as a whole. This is useful when - # certain elements of an action change frequently or depend on complicated state while other parts rarely change or can be shared amongst multiple - # parties. The caching is doing using the cache helper available in the Action View. A template with caching might look something like: - # - # Hello <%= @name %> - # <% cache do %> - # All the topics in the system: - # <%= render_collection_of_partials "topic", Topic.find_all %> - # <% end %> - # - # This cache will bind to the name of action that called it. So you would be able to invalidate it using - # expire_fragment(:controller => "topics", :action => "list") -- if that was the controller/action used. This is not too helpful - # if you need to cache multiple fragments per action or if the action itself is cached using caches_action. So instead we should - # qualify the name of the action used with something like: - # - # <% cache(:action => "list", :action_suffix => "all_topics") do %> - # - # That would result in a name such as "/topics/list/all_topics", which wouldn't conflict with any action cache and neither with another - # fragment using a different suffix. Note that the URL doesn't have to really exist or be callable. We're just using the url_for system - # to generate unique cache names that we can refer to later for expirations. The expiration call for this example would be - # expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics"). - # - # == Fragment stores - # - # In order to use the fragment caching, you need to designate where the caches should be stored. This is done by assigning a fragment store - # of which there are four different kinds: - # - # * FileStore: Keeps the fragments on disk in the +cache_path+, which works well for all types of environments and shares the fragments for - # all the web server processes running off the same application directory. - # * MemoryStore: Keeps the fragments in memory, which is fine for WEBrick and for FCGI (if you don't care that each FCGI process holds its - # own fragment store). It's not suitable for CGI as the process is thrown away at the end of each request. It can potentially also take - # up a lot of memory since each process keeps all the caches in memory. - # * DRbStore: Keeps the fragments in the memory of a separate, shared DRb process. This works for all environments and only keeps one cache - # around for all processes, but requires that you run and manage a separate DRb process. - # * MemCacheStore: Works like DRbStore, but uses Danga's MemCache instead. - # Requires the ruby-memcache library: gem install ruby-memcache. - # - # Configuration examples (MemoryStore is the default): - # - # ActionController::Base.fragment_cache_store = :memory_store - # ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory" - # ActionController::Base.fragment_cache_store = :drb_store, "druby://localhost:9192" - # ActionController::Base.fragment_cache_store = :mem_cache_store, "localhost" - # ActionController::Base.fragment_cache_store = MyOwnStore.new("parameter") - module Fragments - def self.append_features(base) #:nodoc: - super - base.class_eval do - @@fragment_cache_store = MemoryStore.new - cattr_reader :fragment_cache_store - - def self.fragment_cache_store=(store_option) - store, *parameters = *([ store_option ].flatten) - @@fragment_cache_store = if store.is_a?(Symbol) - store_class_name = (store == :drb_store ? "DRbStore" : store.to_s.camelize) - store_class = ActionController::Caching::Fragments.const_get(store_class_name) - store_class.new(*parameters) - else - store - end - end - end - end - - def fragment_cache_key(name) - name.is_a?(Hash) ? url_for(name).split("://").last : name - end - - # Called by CacheHelper#cache - def cache_erb_fragment(block, name = {}, options = nil) - unless perform_caching then block.call; return end - - buffer = eval("_erbout", block.binding) - - if cache = read_fragment(name, options) - buffer.concat(cache) - else - pos = buffer.length - block.call - write_fragment(name, buffer[pos..-1], options) - end - end - - def write_fragment(name, content, options = nil) - return unless perform_caching - - key = fragment_cache_key(name) - self.class.benchmark "Cached fragment: #{key}" do - fragment_cache_store.write(key, content, options) - end - content - end - - def read_fragment(name, options = nil) - return unless perform_caching - - key = fragment_cache_key(name) - self.class.benchmark "Fragment read: #{key}" do - fragment_cache_store.read(key, options) - end - end - - # Name can take one of three forms: - # * String: This would normally take the form of a path like "pages/45/notes" - # * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 } - # * Regexp: Will destroy all the matched fragments, example: %r{pages/\d*/notes} Ensure you do not specify start and finish in the regex (^$) because the actual filename matched looks like ./cache/filename/path.cache - def expire_fragment(name, options = nil) - return unless perform_caching - - key = fragment_cache_key(name) - - if key.is_a?(Regexp) - self.class.benchmark "Expired fragments matching: #{key.source}" do - fragment_cache_store.delete_matched(key, options) - end - else - self.class.benchmark "Expired fragment: #{key}" do - fragment_cache_store.delete(key, options) - end - end - end - - # Deprecated -- just call expire_fragment with a regular expression - def expire_matched_fragments(matcher = /.*/, options = nil) #:nodoc: - expire_fragment(matcher, options) - end - - - class UnthreadedMemoryStore #:nodoc: - def initialize #:nodoc: - @data = {} - end - - def read(name, options=nil) #:nodoc: - @data[name] - end - - def write(name, value, options=nil) #:nodoc: - @data[name] = value - end - - def delete(name, options=nil) #:nodoc: - @data.delete(name) - end - - def delete_matched(matcher, options=nil) #:nodoc: - @data.delete_if { |k,v| k =~ matcher } - end - end - - module ThreadSafety #:nodoc: - def read(name, options=nil) #:nodoc: - @mutex.synchronize { super } - end - - def write(name, value, options=nil) #:nodoc: - @mutex.synchronize { super } - end - - def delete(name, options=nil) #:nodoc: - @mutex.synchronize { super } - end - - def delete_matched(matcher, options=nil) #:nodoc: - @mutex.synchronize { super } - end - end - - class MemoryStore < UnthreadedMemoryStore #:nodoc: - def initialize #:nodoc: - super - if ActionController::Base.allow_concurrency - @mutex = Mutex.new - MemoryStore.send(:include, ThreadSafety) - end - end - end - - class DRbStore < MemoryStore #:nodoc: - attr_reader :address - - def initialize(address = 'druby://localhost:9192') - super() - @address = address - @data = DRbObject.new(nil, address) - end - end - - class MemCacheStore < MemoryStore #:nodoc: - attr_reader :addresses - - def initialize(*addresses) - super() - addresses = addresses.flatten - addresses = ["localhost"] if addresses.empty? - @addresses = addresses - @data = MemCache.new(*addresses) - end - end - - class UnthreadedFileStore #:nodoc: - attr_reader :cache_path - - def initialize(cache_path) - @cache_path = cache_path - end - - def write(name, value, options = nil) #:nodoc: - ensure_cache_path(File.dirname(real_file_path(name))) - File.open(real_file_path(name), "wb+") { |f| f.write(value) } - rescue => e - Base.logger.error "Couldn't create cache directory: #{name} (#{e.message})" if Base.logger - end - - def read(name, options = nil) #:nodoc: - File.open(real_file_path(name), 'rb') { |f| f.read } rescue nil - end - - def delete(name, options) #:nodoc: - File.delete(real_file_path(name)) - rescue SystemCallError => e - # If there's no cache, then there's nothing to complain about - end - - def delete_matched(matcher, options) #:nodoc: - search_dir(@cache_path) do |f| - if f =~ matcher - begin - File.delete(f) - rescue Object => e - # If there's no cache, then there's nothing to complain about - end - end - end - end - - private - def real_file_path(name) - '%s/%s.cache' % [@cache_path, name.gsub('?', '.').gsub(':', '.')] - end - - def ensure_cache_path(path) - FileUtils.makedirs(path) unless File.exists?(path) - end - - def search_dir(dir, &callback) - Dir.foreach(dir) do |d| - next if d == "." || d == ".." - name = File.join(dir, d) - if File.directory?(name) - search_dir(name, &callback) - else - callback.call name - end - end - end - end - - class FileStore < UnthreadedFileStore #:nodoc: - def initialize(cache_path) - super(cache_path) - if ActionController::Base.allow_concurrency - @mutex = Mutex.new - FileStore.send(:include, ThreadSafety) - end - end - end - end - - # Sweepers are the terminators of the caching world and responsible for expiring caches when model objects change. - # They do this by being half-observers, half-filters and implementing callbacks for both roles. A Sweeper example: - # - # class ListSweeper < ActionController::Caching::Sweeper - # observe List, Item - # - # def after_save(record) - # list = record.is_a?(List) ? record : record.list - # expire_page(:controller => "lists", :action => %w( show public feed ), :id => list.id) - # expire_action(:controller => "lists", :action => "all") - # list.shares.each { |share| expire_page(:controller => "lists", :action => "show", :id => share.url_key) } - # end - # end - # - # The sweeper is assigned in the controllers that wish to have its job performed using the cache_sweeper class method: - # - # class ListsController < ApplicationController - # caches_action :index, :show, :public, :feed - # cache_sweeper :list_sweeper, :only => [ :edit, :destroy, :share ] - # end - # - # In the example above, four actions are cached and three actions are responsible for expiring those caches. - module Sweeping - def self.append_features(base) #:nodoc: - super - base.extend(ClassMethods) - end - - module ClassMethods #:nodoc: - def cache_sweeper(*sweepers) - return unless perform_caching - configuration = sweepers.last.is_a?(Hash) ? sweepers.pop : {} - sweepers.each do |sweeper| - observer(sweeper) - - sweeper_instance = Object.const_get(Inflector.classify(sweeper)).instance - - if sweeper_instance.is_a?(Sweeper) - around_filter(sweeper_instance, :only => configuration[:only]) - else - after_filter(sweeper_instance, :only => configuration[:only]) - end - end - end - end - end - - if defined?(ActiveRecord) and defined?(ActiveRecord::Observer) - class Sweeper < ActiveRecord::Observer #:nodoc: - attr_accessor :controller - - # ActiveRecord::Observer will mark this class as reloadable even though it should not be. - # However, subclasses of ActionController::Caching::Sweeper should be Reloadable - include Reloadable::Subclasses - - def before(controller) - self.controller = controller - callback(:before) - end - - def after(controller) - callback(:after) - # Clean up, so that the controller can be collected after this request - self.controller = nil - end - - private - def callback(timing) - controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}" - action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}" - - send(controller_callback_method_name) if respond_to?(controller_callback_method_name) - send(action_callback_method_name) if respond_to?(action_callback_method_name) - end - - def method_missing(method, *arguments) - return if @controller.nil? - @controller.send(method, *arguments) - end - end - end - end -end -require 'cgi' -require 'cgi/session' -require 'cgi/session/pstore' -require 'action_controller/cgi_ext/cgi_methods' - -# Wrapper around the CGIMethods that have been secluded to allow testing without -# an instantiated CGI object -class CGI #:nodoc: - class << self - alias :escapeHTML_fail_on_nil :escapeHTML - - def escapeHTML(string) - escapeHTML_fail_on_nil(string) unless string.nil? - end - end - - # Returns a parameter hash including values from both the request (POST/GET) - # and the query string with the latter taking precedence. - def parameters - request_parameters.update(query_parameters) - end - - def query_parameters - CGIMethods.parse_query_parameters(query_string) - end - - def request_parameters - CGIMethods.parse_request_parameters(params, env_table) - end - - def redirect(where) - header({ - "Status" => "302 Moved", - "location" => "#{where}" - }) - end - - def session(parameters = nil) - parameters = {} if parameters.nil? - parameters['database_manager'] = CGI::Session::PStore - CGI::Session.new(self, parameters) - end -end -require 'cgi' -require 'action_controller/vendor/xml_simple' -require 'action_controller/vendor/xml_node' - -# Static methods for parsing the query and request parameters that can be used in -# a CGI extension class or testing in isolation. -class CGIMethods #:nodoc: - public - # Returns a hash with the pairs from the query string. The implicit hash construction that is done in - # parse_request_params is not done here. - def CGIMethods.parse_query_parameters(query_string) - parsed_params = {} - - query_string.split(/[&;]/).each { |p| - # Ignore repeated delimiters. - next if p.empty? - - k, v = p.split('=',2) - v = nil if (v && v.empty?) - - k = CGI.unescape(k) if k - v = CGI.unescape(v) if v - - unless k.include?(?[) - parsed_params[k] = v - else - keys = split_key(k) - last_key = keys.pop - last_key = keys.pop if (use_array = last_key.empty?) - parent = keys.inject(parsed_params) {|h, k| h[k] ||= {}} - - if use_array then (parent[last_key] ||= []) << v - else parent[last_key] = v - end - end - } - - parsed_params - end - - # Returns the request (POST/GET) parameters in a parsed form where pairs such as "customer[address][street]" / - # "Somewhere cool!" are translated into a full hash hierarchy, like - # { "customer" => { "address" => { "street" => "Somewhere cool!" } } } - def CGIMethods.parse_request_parameters(params) - parsed_params = {} - - for key, value in params - value = [value] if key =~ /.*\[\]$/ - unless key.include?('[') - # much faster to test for the most common case first (GET) - # and avoid the call to build_deep_hash - parsed_params[key] = get_typed_value(value[0]) - else - build_deep_hash(get_typed_value(value[0]), parsed_params, get_levels(key)) - end - end - - parsed_params - end - - def self.parse_formatted_request_parameters(mime_type, raw_post_data) - params = case strategy = ActionController::Base.param_parsers[mime_type] - when Proc - strategy.call(raw_post_data) - when :xml_simple - raw_post_data.blank? ? nil : - typecast_xml_value(XmlSimple.xml_in(raw_post_data, - 'forcearray' => false, - 'forcecontent' => true, - 'keeproot' => true, - 'contentkey' => '__content__')) - when :yaml - YAML.load(raw_post_data) - when :xml_node - node = XmlNode.from_xml(raw_post_data) - { node.node_name => node } - end - - dasherize_keys(params || {}) - rescue Object => e - { "exception" => "#{e.message} (#{e.class})", "backtrace" => e.backtrace, - "raw_post_data" => raw_post_data, "format" => mime_type } - end - - def self.typecast_xml_value(value) - case value - when Hash - if value.has_key?("__content__") - content = translate_xml_entities(value["__content__"]) - case value["type"] - when "integer" then content.to_i - when "boolean" then content == "true" - when "datetime" then Time.parse(content) - when "date" then Date.parse(content) - else content - end - else - value.empty? ? nil : value.inject({}) do |h,(k,v)| - h[k] = typecast_xml_value(v) - h - end - end - when Array - value.map! { |i| typecast_xml_value(i) } - case value.length - when 0 then nil - when 1 then value.first - else value - end - else - raise "can't typecast #{value.inspect}" - end - end - - private - - def self.translate_xml_entities(value) - value.gsub(/</, "<"). - gsub(/>/, ">"). - gsub(/"/, '"'). - gsub(/'/, "'"). - gsub(/&/, "&") - end - - def self.dasherize_keys(params) - case params.class.to_s - when "Hash" - params.inject({}) do |h,(k,v)| - h[k.to_s.tr("-", "_")] = dasherize_keys(v) - h - end - when "Array" - params.map { |v| dasherize_keys(v) } - else - params - end - end - - # Splits the given key into several pieces. Example keys are 'name', 'person[name]', - # 'person[name][first]', and 'people[]'. In each instance, an Array instance is returned. - # 'person[name][first]' produces ['person', 'name', 'first']; 'people[]' produces ['people', ''] - def CGIMethods.split_key(key) - if /^([^\[]+)((?:\[[^\]]*\])+)$/ =~ key - keys = [$1] - - keys.concat($2[1..-2].split('][')) - keys << '' if key[-2..-1] == '[]' # Have to add it since split will drop empty strings - - keys - else - [key] - end - end - - def CGIMethods.get_typed_value(value) - # test most frequent case first - if value.is_a?(String) - value - elsif value.respond_to?(:content_type) && ! value.content_type.blank? - # Uploaded file - unless value.respond_to?(:full_original_filename) - class << value - alias_method :full_original_filename, :original_filename - - # Take the basename of the upload's original filename. - # This handles the full Windows paths given by Internet Explorer - # (and perhaps other broken user agents) without affecting - # those which give the lone filename. - # The Windows regexp is adapted from Perl's File::Basename. - def original_filename - if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename) - md.captures.first - else - File.basename full_original_filename - end - end - end - end - - # Return the same value after overriding original_filename. - value - - elsif value.respond_to?(:read) - # Value as part of a multipart request - value.read - elsif value.class == Array - value.collect { |v| CGIMethods.get_typed_value(v) } - else - # other value (neither string nor a multipart request) - value.to_s - end - end - - PARAMS_HASH_RE = /^([^\[]+)(\[.*\])?(.)?.*$/ - def CGIMethods.get_levels(key) - all, main, bracketed, trailing = PARAMS_HASH_RE.match(key).to_a - if main.nil? - [] - elsif trailing - [key] - elsif bracketed - [main] + bracketed.slice(1...-1).split('][') - else - [main] - end - end - - def CGIMethods.build_deep_hash(value, hash, levels) - if levels.length == 0 - value - elsif hash.nil? - { levels.first => CGIMethods.build_deep_hash(value, nil, levels[1..-1]) } - else - hash.update({ levels.first => CGIMethods.build_deep_hash(value, hash[levels.first], levels[1..-1]) }) - end - end -end -CGI.module_eval { remove_const "Cookie" } - -class CGI #:nodoc: - # This is a cookie class that fixes the performance problems with the default one that ships with 1.8.1 and below. - # It replaces the inheritance on SimpleDelegator with DelegateClass(Array) following the suggestion from Matz on - # http://groups.google.com/groups?th=e3a4e68ba042f842&seekm=c3sioe%241qvm%241%40news.cybercity.dk#link14 - class Cookie < DelegateClass(Array) - # Create a new CGI::Cookie object. - # - # The contents of the cookie can be specified as a +name+ and one - # or more +value+ arguments. Alternatively, the contents can - # be specified as a single hash argument. The possible keywords of - # this hash are as follows: - # - # name:: the name of the cookie. Required. - # value:: the cookie's value or list of values. - # path:: the path for which this cookie applies. Defaults to the - # base directory of the CGI script. - # domain:: the domain for which this cookie applies. - # expires:: the time at which this cookie expires, as a +Time+ object. - # secure:: whether this cookie is a secure cookie or not (default to - # false). Secure cookies are only transmitted to HTTPS - # servers. - # - # These keywords correspond to attributes of the cookie object. - def initialize(name = '', *value) - if name.kind_of?(String) - @name = name - @value = Array(value) - @domain = nil - @expires = nil - @secure = false - @path = nil - else - @name = name['name'] - @value = Array(name['value']) - @domain = name['domain'] - @expires = name['expires'] - @secure = name['secure'] || false - @path = name['path'] - end - - unless @name - raise ArgumentError, "`name' required" - end - - # simple support for IE - unless @path - %r|^(.*/)|.match(ENV['SCRIPT_NAME']) - @path = ($1 or '') - end - - super(@value) - end - - def __setobj__(obj) - @_dc_obj = obj - end - - attr_accessor("name", "value", "path", "domain", "expires") - attr_reader("secure") - - # Set whether the Cookie is a secure cookie or not. - # - # +val+ must be a boolean. - def secure=(val) - @secure = val if val == true or val == false - @secure - end - - # Convert the Cookie to its string representation. - def to_s - buf = "" - buf << @name << '=' - - if @value.kind_of?(String) - buf << CGI::escape(@value) - else - buf << @value.collect{|v| CGI::escape(v) }.join("&") - end - - if @domain - buf << '; domain=' << @domain - end - - if @path - buf << '; path=' << @path - end - - if @expires - buf << '; expires=' << CGI::rfc1123_date(@expires) - end - - if @secure == true - buf << '; secure' - end - - buf - end - - # Parse a raw cookie string into a hash of cookie-name=>Cookie - # pairs. - # - # cookies = CGI::Cookie::parse("raw_cookie_string") - # # { "name1" => cookie1, "name2" => cookie2, ... } - # - def self.parse(raw_cookie) - cookies = Hash.new([]) - - if raw_cookie - raw_cookie.split(/; ?/).each do |pairs| - name, values = pairs.split('=',2) - next unless name and values - name = CGI::unescape(name) - values = values.split('&').collect!{|v| CGI::unescape(v) } - unless cookies.has_key?(name) - cookies[name] = new(name, *values) - end - end - end - - cookies - end - end # class Cookie -end -class CGI #:nodoc: - # Add @request.env['RAW_POST_DATA'] for the vegans. - module QueryExtension - # Initialize the data from the query. - # - # Handles multipart forms (in particular, forms that involve file uploads). - # Reads query parameters in the @params field, and cookies into @cookies. - def initialize_query() - @cookies = CGI::Cookie::parse(env_table['HTTP_COOKIE'] || env_table['COOKIE']) - - #fix some strange request environments - if method = env_table['REQUEST_METHOD'] - method = method.to_s.downcase.intern - else - method = :get - end - - if method == :post && (boundary = multipart_form_boundary) - @multipart = true - @params = read_multipart(boundary, Integer(env_table['CONTENT_LENGTH'])) - else - @multipart = false - @params = CGI::parse(read_query_params(method) || "") - end - end - - private - unless defined?(MULTIPART_FORM_BOUNDARY_RE) - MULTIPART_FORM_BOUNDARY_RE = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n #" - end - - def multipart_form_boundary - MULTIPART_FORM_BOUNDARY_RE.match(env_table['CONTENT_TYPE']).to_a.pop - end - - if defined? MOD_RUBY - def read_params_from_query - Apache::request.args || '' - end - else - def read_params_from_query - # fixes CGI querystring parsing for lighttpd - env_qs = env_table['QUERY_STRING'] - if env_qs.blank? && !(uri = env_table['REQUEST_URI']).blank? - uri.split('?', 2)[1] || '' - else - env_qs - end - end - end - - def read_params_from_post - stdinput.binmode if stdinput.respond_to?(:binmode) - content = stdinput.read(Integer(env_table['CONTENT_LENGTH'])) || '' - # fix for Safari Ajax postings that always append \000 - content.chop! if content[-1] == 0 - content.gsub! /&_=$/, '' - env_table['RAW_POST_DATA'] = content.freeze - end - - def read_query_params(method) - case method - when :get - read_params_from_query - when :post, :put - read_params_from_post - when :cmd - read_from_cmdline - else # when :head, :delete, :options - read_params_from_query - end - end - end # module QueryExtension -end -require 'action_controller/cgi_ext/cgi_ext' -require 'action_controller/cgi_ext/cookie_performance_fix' -require 'action_controller/cgi_ext/raw_post_data_fix' - -module ActionController #:nodoc: - class Base - # Process a request extracted from an CGI object and return a response. Pass false as session_options to disable - # sessions (large performance increase if sessions are not needed). The session_options are the same as for CGI::Session: - # - # * :database_manager - standard options are CGI::Session::FileStore, CGI::Session::MemoryStore, and CGI::Session::PStore - # (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in - # lib/action_controller/session. - # * :session_key - the parameter name used for the session id. Defaults to '_session_id'. - # * :session_id - the session id to use. If not provided, then it is retrieved from the +session_key+ parameter - # of the request, or automatically generated for a new session. - # * :new_session - if true, force creation of a new session. If not set, a new session is only created if none currently - # exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set, - # an ArgumentError is raised. - # * :session_expires - the time the current session expires, as a +Time+ object. If not set, the session will continue - # indefinitely. - # * :session_domain - the hostname domain for which this session is valid. If not set, defaults to the hostname of the - # server. - # * :session_secure - if +true+, this session will only work over HTTPS. - # * :session_path - the path for which this session applies. Defaults to the directory of the CGI script. - def self.process_cgi(cgi = CGI.new, session_options = {}) - new.process_cgi(cgi, session_options) - end - - def process_cgi(cgi, session_options = {}) #:nodoc: - process(CgiRequest.new(cgi, session_options), CgiResponse.new(cgi)).out - end - end - - class CgiRequest < AbstractRequest #:nodoc: - attr_accessor :cgi, :session_options - - DEFAULT_SESSION_OPTIONS = { - :database_manager => CGI::Session::PStore, - :prefix => "ruby_sess.", - :session_path => "/" - } unless const_defined?(:DEFAULT_SESSION_OPTIONS) - - def initialize(cgi, session_options = {}) - @cgi = cgi - @session_options = session_options - @env = @cgi.send(:env_table) - super() - end - - def query_string - if (qs = @cgi.query_string) && !qs.empty? - qs - elsif uri = @env['REQUEST_URI'] - parts = uri.split('?') - parts.shift - parts.join('?') - else - @env['QUERY_STRING'] || '' - end - end - - def query_parameters - (qs = self.query_string).empty? ? {} : CGIMethods.parse_query_parameters(qs) - end - - def request_parameters - @request_parameters ||= - if ActionController::Base.param_parsers.has_key?(content_type) - CGIMethods.parse_formatted_request_parameters(content_type, @env['RAW_POST_DATA']) - else - CGIMethods.parse_request_parameters(@cgi.params) - end - end - - def cookies - @cgi.cookies.freeze - end - - def host_with_port - if forwarded = env["HTTP_X_FORWARDED_HOST"] - forwarded.split(/,\s?/).last - elsif http_host = env['HTTP_HOST'] - http_host - elsif server_name = env['SERVER_NAME'] - server_name - else - "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}" - end - end - - def host - host_with_port[/^[^:]+/] - end - - def port - if host_with_port =~ /:(\d+)$/ - $1.to_i - else - standard_port - end - end - - def session - unless @session - if @session_options == false - @session = Hash.new - else - stale_session_check! do - if session_options_with_string_keys['new_session'] == true - @session = new_session - else - @session = CGI::Session.new(@cgi, session_options_with_string_keys) - end - @session['__valid_session'] - end - end - end - @session - end - - def reset_session - @session.delete if CGI::Session === @session - @session = new_session - end - - def method_missing(method_id, *arguments) - @cgi.send(method_id, *arguments) rescue super - end - - private - # Delete an old session if it exists then create a new one. - def new_session - if @session_options == false - Hash.new - else - CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => false)).delete rescue nil - CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => true)) - end - end - - def stale_session_check! - yield - rescue ArgumentError => argument_error - if argument_error.message =~ %r{undefined class/module (\w+)} - begin - Module.const_missing($1) - rescue LoadError, NameError => const_error - raise ActionController::SessionRestoreError, < e - # lost connection to the FCGI process -- ignore the output, then - end - end - - private - def convert_content_type!(headers) - if header = headers.delete("Content-Type") - headers["type"] = header - end - if header = headers.delete("Content-type") - headers["type"] = header - end - if header = headers.delete("content-type") - headers["type"] = header - end - end - end -end -module ActionController - module CodeGeneration #:nodoc: - class GenerationError < StandardError #:nodoc: - end - - class Source #:nodoc: - attr_reader :lines, :indentation_level - IndentationString = ' ' - def initialize - @lines, @indentation_level = [], 0 - end - def line(line) - @lines << (IndentationString * @indentation_level + line) - end - alias :<< :line - - def indent - @indentation_level += 1 - yield - ensure - @indentation_level -= 1 - end - - def to_s() lines.join("\n") end - end - - class CodeGenerator #:nodoc: - attr_accessor :source, :locals - def initialize(source = nil) - @locals = [] - @source = source || Source.new - end - - BeginKeywords = %w(if unless begin until while def).collect {|kw| kw.to_sym} - ResumeKeywords = %w(elsif else rescue).collect {|kw| kw.to_sym} - Keywords = BeginKeywords + ResumeKeywords - - def method_missing(keyword, *text) - if Keywords.include? keyword - if ResumeKeywords.include? keyword - raise GenerationError, "Can only resume with #{keyword} immediately after an end" unless source.lines.last =~ /^\s*end\s*$/ - source.lines.pop # Remove the 'end' - end - - line "#{keyword} #{text.join ' '}" - begin source.indent { yield(self.dup) } - ensure line 'end' - end - else - super(keyword, *text) - end - end - - def line(*args) self.source.line(*args) end - alias :<< :line - def indent(*args, &block) source(*args, &block) end - def to_s() source.to_s end - - def share_locals_with(other) - other.locals = self.locals = (other.locals | locals) - end - - FieldsToDuplicate = [:locals] - def dup - copy = self.class.new(source) - self.class::FieldsToDuplicate.each do |sym| - value = self.send(sym) - value = value.dup unless value.nil? || value.is_a?(Numeric) - copy.send("#{sym}=", value) - end - return copy - end - end - - class RecognitionGenerator < CodeGenerator #:nodoc: - Attributes = [:after, :before, :current, :results, :constants, :depth, :move_ahead, :finish_statement] - attr_accessor(*Attributes) - FieldsToDuplicate = CodeGenerator::FieldsToDuplicate + Attributes - - def initialize(*args) - super(*args) - @after, @before = [], [] - @current = nil - @results, @constants = {}, {} - @depth = 0 - @move_ahead = nil - @finish_statement = Proc.new {|hash_expr| hash_expr} - end - - def if_next_matches(string, &block) - test = Routing.test_condition(next_segment(true), string) - self.if(test, &block) - end - - def move_forward(places = 1) - dup = self.dup - dup.depth += 1 - dup.move_ahead = places - yield dup - end - - def next_segment(assign_inline = false, default = nil) - if locals.include?(segment_name) - code = segment_name - else - code = "#{segment_name} = #{path_name}[#{index_name}]" - if assign_inline - code = "(#{code})" - else - line(code) - code = segment_name - end - - locals << segment_name - end - code = "(#{code} || #{default.inspect})" if default - - return code.to_s - end - - def segment_name() "segment#{depth}".to_sym end - def path_name() :path end - def index_name - move_ahead, @move_ahead = @move_ahead, nil - move_ahead ? "index += #{move_ahead}" : 'index' - end - - def continue - dup = self.dup - dup.before << dup.current - dup.current = dup.after.shift - dup.go - end - - def go - if current then current.write_recognition(self) - else self.finish - end - end - - def result(key, expression, delay = false) - unless delay - line "#{key}_value = #{expression}" - expression = "#{key}_value" - end - results[key] = expression - end - def constant_result(key, object) - constants[key] = object - end - - def finish(ensure_traversal_finished = true) - pairs = [] - (results.keys + constants.keys).uniq.each do |key| - pairs << "#{key.to_s.inspect} => #{results[key] ? results[key] : constants[key].inspect}" - end - hash_expr = "{#{pairs.join(', ')}}" - - statement = finish_statement.call(hash_expr) - if ensure_traversal_finished then self.if("! #{next_segment(true)}") {|gp| gp << statement} - else self << statement - end - end - end - - class GenerationGenerator < CodeGenerator #:nodoc: - Attributes = [:after, :before, :current, :segments] - attr_accessor(*Attributes) - FieldsToDuplicate = CodeGenerator::FieldsToDuplicate + Attributes - - def initialize(*args) - super(*args) - @after, @before = [], [] - @current = nil - @segments = [] - end - - def hash_name() 'hash' end - def local_name(key) "#{key}_value" end - - def hash_value(key, assign = true, default = nil) - if locals.include?(local_name(key)) then code = local_name(key) - else - code = "hash[#{key.to_sym.inspect}]" - if assign - code = "(#{local_name(key)} = #{code})" - locals << local_name(key) - end - end - code = "(#{code} || (#{default.inspect}))" if default - return code - end - - def expire_for_keys(*keys) - return if keys.empty? - conds = keys.collect {|key| "expire_on[#{key.to_sym.inspect}]"} - line "not_expired, #{hash_name} = false, options if not_expired && #{conds.join(' && ')}" - end - - def add_segment(*segments) - d = dup - d.segments.concat segments - yield d - end - - def go - if current then current.write_generation(self) - else self.finish - end - end - - def continue - d = dup - d.before << d.current - d.current = d.after.shift - d.go - end - - def finish - line %("/#{segments.join('/')}") - end - - def check_conditions(conditions) - tests = [] - generator = nil - conditions.each do |key, condition| - tests << (generator || self).hash_value(key, true) if condition.is_a? Regexp - tests << Routing.test_condition((generator || self).hash_value(key, false), condition) - generator = self.dup unless generator - end - return tests.join(' && ') - end - end - end -end -module ActionController #:nodoc: - # Components allow you to call other actions for their rendered response while executing another action. You can either delegate - # the entire response rendering or you can mix a partial response in with your other content. - # - # class WeblogController < ActionController::Base - # # Performs a method and then lets hello_world output its render - # def delegate_action - # do_other_stuff_before_hello_world - # render_component :controller => "greeter", :action => "hello_world", :params => { :person => "david" } - # end - # end - # - # class GreeterController < ActionController::Base - # def hello_world - # render :text => "#{params[:person]} says, Hello World!" - # end - # end - # - # The same can be done in a view to do a partial rendering: - # - # Let's see a greeting: - # <%= render_component :controller => "greeter", :action => "hello_world" %> - # - # It is also possible to specify the controller as a class constant, bypassing the inflector - # code to compute the controller class at runtime: - # - # <%= render_component :controller => GreeterController, :action => "hello_world" %> - # - # == When to use components - # - # Components should be used with care. They're significantly slower than simply splitting reusable parts into partials and - # conceptually more complicated. Don't use components as a way of separating concerns inside a single application. Instead, - # reserve components to those rare cases where you truly have reusable view and controller elements that can be employed - # across many applications at once. - # - # So to repeat: Components are a special-purpose approach that can often be replaced with better use of partials and filters. - module Components - def self.included(base) #:nodoc: - base.send :include, InstanceMethods - base.extend(ClassMethods) - - base.helper do - def render_component(options) - @controller.send(:render_component_as_string, options) - end - end - - # If this controller was instantiated to process a component request, - # +parent_controller+ points to the instantiator of this controller. - base.send :attr_accessor, :parent_controller - - base.class_eval do - alias_method :process_cleanup_without_components, :process_cleanup - alias_method :process_cleanup, :process_cleanup_with_components - - alias_method :set_session_options_without_components, :set_session_options - alias_method :set_session_options, :set_session_options_with_components - - alias_method :flash_without_components, :flash - alias_method :flash, :flash_with_components - - alias_method :component_request?, :parent_controller - end - end - - module ClassMethods - # Track parent controller to identify component requests - def process_with_components(request, response, parent_controller = nil) #:nodoc: - controller = new - controller.parent_controller = parent_controller - controller.process(request, response) - end - - # Set the template root to be one directory behind the root dir of the controller. Examples: - # /code/weblog/components/admin/users_controller.rb with Admin::UsersController - # will use /code/weblog/components as template root - # and find templates in /code/weblog/components/admin/users/ - # - # /code/weblog/components/admin/parties/users_controller.rb with Admin::Parties::UsersController - # will also use /code/weblog/components as template root - # and find templates in /code/weblog/components/admin/parties/users/ - def uses_component_template_root - path_of_calling_controller = File.dirname(caller[0].split(/:\d+:/).first) - path_of_controller_root = path_of_calling_controller.sub(/#{controller_path.split("/")[0..-2]}$/, "") # " (for ruby-mode) - - self.template_root = path_of_controller_root - end - end - - module InstanceMethods - # Extracts the action_name from the request parameters and performs that action. - def process_with_components(request, response, method = :perform_action, *arguments) #:nodoc: - flash.discard if component_request? - process_without_components(request, response, method, *arguments) - end - - protected - # Renders the component specified as the response for the current method - def render_component(options) #:doc: - component_logging(options) do - render_text(component_response(options, true).body, response.headers["Status"]) - end - end - - # Returns the component response as a string - def render_component_as_string(options) #:doc: - component_logging(options) do - response = component_response(options, false) - - if redirected = response.redirected_to - render_component_as_string(redirected) - else - response.body - end - end - end - - def flash_with_components(refresh = false) #:nodoc: - if @flash.nil? || refresh - @flash = - if @parent_controller - @parent_controller.flash - else - flash_without_components - end - end - - @flash - end - - private - def component_response(options, reuse_response) - klass = component_class(options) - request = request_for_component(klass.controller_name, options) - response = reuse_response ? @response : @response.dup - - klass.process_with_components(request, response, self) - end - - # determine the controller class for the component request - def component_class(options) - if controller = options[:controller] - controller.is_a?(Class) ? controller : "#{controller.camelize}Controller".constantize - else - self.class - end - end - - # Create a new request object based on the current request. - # The new request inherits the session from the current request, - # bypassing any session options set for the component controller's class - def request_for_component(controller_name, options) - request = @request.dup - request.session = @request.session - - request.instance_variable_set( - :@parameters, - (options[:params] || {}).with_indifferent_access.update( - "controller" => controller_name, "action" => options[:action], "id" => options[:id] - ) - ) - - request - end - - def component_logging(options) - if logger - logger.info "Start rendering component (#{options.inspect}): " - result = yield - logger.info "\n\nEnd of component rendering" - result - else - yield - end - end - - def set_session_options_with_components(request) - set_session_options_without_components(request) unless component_request? - end - - def process_cleanup_with_components - process_cleanup_without_components unless component_request? - end - end - end -end -module ActionController #:nodoc: - # Cookies are read and written through ActionController#cookies. The cookies being read are what were received along with the request, - # the cookies being written are what will be sent out with the response. Cookies are read by value (so you won't get the cookie object - # itself back -- just the value it holds). Examples for writing: - # - # cookies[:user_name] = "david" # => Will set a simple session cookie - # cookies[:login] = { :value => "XJ-122", :expires => Time.now + 360} # => Will set a cookie that expires in 1 hour - # - # Examples for reading: - # - # cookies[:user_name] # => "david" - # cookies.size # => 2 - # - # Example for deleting: - # - # cookies.delete :user_name - # - # All the option symbols for setting cookies are: - # - # * value - the cookie's value or list of values (as an array). - # * path - the path for which this cookie applies. Defaults to the root of the application. - # * domain - the domain for which this cookie applies. - # * expires - the time at which this cookie expires, as a +Time+ object. - # * secure - whether this cookie is a secure cookie or not (default to false). - # Secure cookies are only transmitted to HTTPS servers. - module Cookies - protected - # Returns the cookie container, which operates as described above. - def cookies - CookieJar.new(self) - end - - # Deprecated cookie writer method - def cookie(*options) - @response.headers["cookie"] << CGI::Cookie.new(*options) - end - end - - class CookieJar < Hash #:nodoc: - def initialize(controller) - @controller, @cookies = controller, controller.instance_variable_get("@cookies") - super() - update(@cookies) - end - - # Returns the value of the cookie by +name+ -- or nil if no such cookie exists. You set new cookies using either the cookie method - # or cookies[]= (for simple name/value cookies without options). - def [](name) - @cookies[name.to_s].value.first if @cookies[name.to_s] && @cookies[name.to_s].respond_to?(:value) - end - - def []=(name, options) - if options.is_a?(Hash) - options = options.inject({}) { |options, pair| options[pair.first.to_s] = pair.last; options } - options["name"] = name.to_s - else - options = { "name" => name.to_s, "value" => options } - end - - set_cookie(options) - end - - # Removes the cookie on the client machine by setting the value to an empty string - # and setting its expiration date into the past - def delete(name) - set_cookie("name" => name.to_s, "value" => "", "expires" => Time.at(0)) - end - - private - def set_cookie(options) #:doc: - options["path"] = "/" unless options["path"] - cookie = CGI::Cookie.new(options) - @controller.logger.info "Cookie set: #{cookie}" unless @controller.logger.nil? - @controller.response.headers["cookie"] << cookie - end - end -end -module ActionController #:nodoc: - module Dependencies #:nodoc: - def self.append_features(base) - super - base.extend(ClassMethods) - end - - # Dependencies control what classes are needed for the controller to run its course. This is an alternative to doing explicit - # +require+ statements that bring a number of benefits. It's more succinct, communicates what type of dependency we're talking about, - # can trigger special behavior (as in the case of +observer+), and enables Rails to be clever about reloading in cached environments - # like FCGI. Example: - # - # class ApplicationController < ActionController::Base - # model :account, :company, :person, :project, :category - # helper :access_control - # service :notifications, :billings - # observer :project_change_observer - # end - # - # Please note that a controller like ApplicationController will automatically attempt to require_dependency on a model of its - # singuralized name and a helper of its name. If nothing is found, no error is raised. This is especially useful for concrete - # controllers like PostController: - # - # class PostController < ApplicationController - # # model :post (already required) - # # helper :post (already required) - # end - # - # Also note, that if the models follow the pattern of just 1 class per file in the form of MyClass => my_class.rb, then these - # classes don't have to be required as Active Support will auto-require them. - module ClassMethods #:nodoc: - # Specifies a variable number of models that this controller depends on. Models are normally Active Record classes or a similar - # backend for modelling entity classes. - def model(*models) - require_dependencies(:model, models) - depend_on(:model, models) - end - - # Specifies a variable number of services that this controller depends on. Services are normally singletons or factories, like - # Action Mailer service or a Payment Gateway service. - def service(*services) - require_dependencies(:service, services) - depend_on(:service, services) - end - - # Specifies a variable number of observers that are to govern when this controller is handling actions. The observers will - # automatically have .instance called on them to make them active on assignment. - def observer(*observers) - require_dependencies(:observer, observers) - depend_on(:observer, observers) - instantiate_observers(observers) - end - - # Returns an array of symbols that specify the dependencies on a given layer. For the example at the top, calling - # ApplicationController.dependencies_on(:model) would return [:account, :company, :person, :project, :category] - def dependencies_on(layer) - read_inheritable_attribute("#{layer}_dependencies") - end - - def depend_on(layer, dependencies) #:nodoc: - write_inheritable_array("#{layer}_dependencies", dependencies) - end - - private - def instantiate_observers(observers) - observers.flatten.each { |observer| Object.const_get(Inflector.classify(observer.to_s)).instance } - end - - def require_dependencies(layer, dependencies) - dependencies.flatten.each do |dependency| - begin - require_dependency(dependency.to_s) - rescue LoadError => e - raise LoadError.new("Missing #{layer} #{dependency}.rb").copy_blame!(e) - rescue Object => exception - exception.blame_file! "=> #{layer} #{dependency}.rb" - raise - end - end - end - end - end -end -require 'test/unit' -require 'test/unit/assertions' -require 'rexml/document' - -module Test #:nodoc: - module Unit #:nodoc: - module Assertions - def assert_success(message=nil) #:nodoc: - assert_response(:success, message) - end - - def assert_redirect(message=nil) #:nodoc: - assert_response(:redirect, message) - end - - def assert_rendered_file(expected=nil, message=nil) #:nodoc: - assert_template(expected, message) - end - - # ensure that the session has an object with the specified name - def assert_session_has(key=nil, message=nil) #:nodoc: - msg = build_message(message, " is not in the session ", key, @response.session) - assert_block(msg) { @response.has_session_object?(key) } - end - - # ensure that the session has no object with the specified name - def assert_session_has_no(key=nil, message=nil) #:nodoc: - msg = build_message(message, " is in the session ", key, @response.session) - assert_block(msg) { !@response.has_session_object?(key) } - end - - def assert_session_equal(expected = nil, key = nil, message = nil) #:nodoc: - msg = build_message(message, " expected in session['?'] but was ", expected, key, @response.session[key]) - assert_block(msg) { expected == @response.session[key] } - end - - # -- cookie assertions --------------------------------------------------- - - def assert_no_cookie(key = nil, message = nil) #:nodoc: - actual = @response.cookies[key] - msg = build_message(message, " not expected in cookies['?']", actual, key) - assert_block(msg) { actual.nil? or actual.empty? } - end - - def assert_cookie_equal(expected = nil, key = nil, message = nil) #:nodoc: - actual = @response.cookies[key] - actual = actual.first if actual - msg = build_message(message, " expected in cookies['?'] but was ", expected, key, actual) - assert_block(msg) { expected == actual } - end - - # -- flash assertions --------------------------------------------------- - - # ensure that the flash has an object with the specified name - def assert_flash_has(key=nil, message=nil) #:nodoc: - msg = build_message(message, " is not in the flash ", key, @response.flash) - assert_block(msg) { @response.has_flash_object?(key) } - end - - # ensure that the flash has no object with the specified name - def assert_flash_has_no(key=nil, message=nil) #:nodoc: - msg = build_message(message, " is in the flash ", key, @response.flash) - assert_block(msg) { !@response.has_flash_object?(key) } - end - - # ensure the flash exists - def assert_flash_exists(message=nil) #:nodoc: - msg = build_message(message, "the flash does not exist ", @response.session['flash'] ) - assert_block(msg) { @response.has_flash? } - end - - # ensure the flash does not exist - def assert_flash_not_exists(message=nil) #:nodoc: - msg = build_message(message, "the flash exists ", @response.flash) - assert_block(msg) { !@response.has_flash? } - end - - # ensure the flash is empty but existent - def assert_flash_empty(message=nil) #:nodoc: - msg = build_message(message, "the flash is not empty ", @response.flash) - assert_block(msg) { !@response.has_flash_with_contents? } - end - - # ensure the flash is not empty - def assert_flash_not_empty(message=nil) #:nodoc: - msg = build_message(message, "the flash is empty") - assert_block(msg) { @response.has_flash_with_contents? } - end - - def assert_flash_equal(expected = nil, key = nil, message = nil) #:nodoc: - msg = build_message(message, " expected in flash['?'] but was ", expected, key, @response.flash[key]) - assert_block(msg) { expected == @response.flash[key] } - end - - - # ensure our redirection url is an exact match - def assert_redirect_url(url=nil, message=nil) #:nodoc: - assert_redirect(message) - msg = build_message(message, " is not the redirected location ", url, @response.redirect_url) - assert_block(msg) { @response.redirect_url == url } - end - - # ensure our redirection url matches a pattern - def assert_redirect_url_match(pattern=nil, message=nil) #:nodoc: - assert_redirect(message) - msg = build_message(message, " was not found in the location: ", pattern, @response.redirect_url) - assert_block(msg) { @response.redirect_url_match?(pattern) } - end - - - # -- template assertions ------------------------------------------------ - - # ensure that a template object with the given name exists - def assert_template_has(key=nil, message=nil) #:nodoc: - msg = build_message(message, " is not a template object", key ) - assert_block(msg) { @response.has_template_object?(key) } - end - - # ensure that a template object with the given name does not exist - def assert_template_has_no(key=nil,message=nil) #:nodoc: - msg = build_message(message, " is a template object ", key, @response.template_objects[key]) - assert_block(msg) { !@response.has_template_object?(key) } - end - - # ensures that the object assigned to the template on +key+ is equal to +expected+ object. - def assert_template_equal(expected = nil, key = nil, message = nil) #:nodoc: - msg = build_message(message, " expected in assigns['?'] but was ", expected, key, @response.template.assigns[key.to_s]) - assert_block(msg) { expected == @response.template.assigns[key.to_s] } - end - alias_method :assert_assigned_equal, :assert_template_equal - - # Asserts that the template returns the +expected+ string or array based on the XPath +expression+. - # This will only work if the template rendered a valid XML document. - def assert_template_xpath_match(expression=nil, expected=nil, message=nil) #:nodoc: - xml, matches = REXML::Document.new(@response.body), [] - xml.elements.each(expression) { |e| matches << e.text } - if matches.empty? then - msg = build_message(message, " not found in document", expression) - flunk(msg) - return - elsif matches.length < 2 then - matches = matches.first - end - - msg = build_message(message, " found , not ", expression, matches, expected) - assert_block(msg) { matches == expected } - end - - # Assert the template object with the given name is an Active Record descendant and is valid. - def assert_valid_record(key = nil, message = nil) #:nodoc: - record = find_record_in_template(key) - msg = build_message(message, "Active Record is invalid )", record.errors.full_messages) - assert_block(msg) { record.valid? } - end - - # Assert the template object with the given name is an Active Record descendant and is invalid. - def assert_invalid_record(key = nil, message = nil) #:nodoc: - record = find_record_in_template(key) - msg = build_message(message, "Active Record is valid)") - assert_block(msg) { !record.valid? } - end - - # Assert the template object with the given name is an Active Record descendant and the specified column(s) are valid. - def assert_valid_column_on_record(key = nil, columns = "", message = nil) #:nodoc: - record = find_record_in_template(key) - record.send(:validate) - - cols = glue_columns(columns) - cols.delete_if { |col| !record.errors.invalid?(col) } - msg = build_message(message, "Active Record has invalid columns )", cols.join(",") ) - assert_block(msg) { cols.empty? } - end - - # Assert the template object with the given name is an Active Record descendant and the specified column(s) are invalid. - def assert_invalid_column_on_record(key = nil, columns = "", message = nil) #:nodoc: - record = find_record_in_template(key) - record.send(:validate) - - cols = glue_columns(columns) - cols.delete_if { |col| record.errors.invalid?(col) } - msg = build_message(message, "Active Record has valid columns )", cols.join(",") ) - assert_block(msg) { cols.empty? } - end - - private - def glue_columns(columns) - cols = [] - cols << columns if columns.class == String - cols += columns if columns.class == Array - cols - end - - def find_record_in_template(key = nil) - assert_template_has(key) - record = @response.template_objects[key] - - assert_not_nil(record) - assert_kind_of ActiveRecord::Base, record - - return record - end - end - end -endmodule ActionController - class Base - protected - # Deprecated in favor of calling redirect_to directly with the path. - def redirect_to_path(path) #:nodoc: - redirect_to(path) - end - - # Deprecated in favor of calling redirect_to directly with the url. If the resource has moved permanently, it's possible to pass - # true as the second parameter and the browser will get "301 Moved Permanently" instead of "302 Found". This can also be done through - # just setting the headers["Status"] to "301 Moved Permanently" before using the redirect_to. - def redirect_to_url(url, permanently = false) #:nodoc: - headers["Status"] = "301 Moved Permanently" if permanently - redirect_to(url) - end - end -end -module ActionController - class AbstractRequest - # Determine whether the body of a HTTP call is URL-encoded (default) - # or matches one of the registered param_parsers. - # - # For backward compatibility, the post format is extracted from the - # X-Post-Data-Format HTTP header if present. - def post_format - case content_type.to_s - when 'application/xml' - :xml - when 'application/x-yaml' - :yaml - else - :url_encoded - end - end - - # Is this a POST request formatted as XML or YAML? - def formatted_post? - post? && (post_format == :yaml || post_format == :xml) - end - - # Is this a POST request formatted as XML? - def xml_post? - post? && post_format == :xml - end - - # Is this a POST request formatted as YAML? - def yaml_post? - post? && post_format == :yaml - end - end -end -module ActionController #:nodoc: - module Filters #:nodoc: - def self.included(base) - base.extend(ClassMethods) - base.send(:include, ActionController::Filters::InstanceMethods) - end - - # Filters enable controllers to run shared pre and post processing code for its actions. These filters can be used to do - # authentication, caching, or auditing before the intended action is performed. Or to do localization or output - # compression after the action has been performed. - # - # Filters have access to the request, response, and all the instance variables set by other filters in the chain - # or by the action (in the case of after filters). Additionally, it's possible for a pre-processing before_filter - # to halt the processing before the intended action is processed by returning false or performing a redirect or render. - # This is especially useful for filters like authentication where you're not interested in allowing the action to be - # performed if the proper credentials are not in order. - # - # == Filter inheritance - # - # Controller inheritance hierarchies share filters downwards, but subclasses can also add new filters without - # affecting the superclass. For example: - # - # class BankController < ActionController::Base - # before_filter :audit - # - # private - # def audit - # # record the action and parameters in an audit log - # end - # end - # - # class VaultController < BankController - # before_filter :verify_credentials - # - # private - # def verify_credentials - # # make sure the user is allowed into the vault - # end - # end - # - # Now any actions performed on the BankController will have the audit method called before. On the VaultController, - # first the audit method is called, then the verify_credentials method. If the audit method returns false, then - # verify_credentials and the intended action are never called. - # - # == Filter types - # - # A filter can take one of three forms: method reference (symbol), external class, or inline method (proc). The first - # is the most common and works by referencing a protected or private method somewhere in the inheritance hierarchy of - # the controller by use of a symbol. In the bank example above, both BankController and VaultController use this form. - # - # Using an external class makes for more easily reused generic filters, such as output compression. External filter classes - # are implemented by having a static +filter+ method on any class and then passing this class to the filter method. Example: - # - # class OutputCompressionFilter - # def self.filter(controller) - # controller.response.body = compress(controller.response.body) - # end - # end - # - # class NewspaperController < ActionController::Base - # after_filter OutputCompressionFilter - # end - # - # The filter method is passed the controller instance and is hence granted access to all aspects of the controller and can - # manipulate them as it sees fit. - # - # The inline method (using a proc) can be used to quickly do something small that doesn't require a lot of explanation. - # Or just as a quick test. It works like this: - # - # class WeblogController < ActionController::Base - # before_filter { |controller| false if controller.params["stop_action"] } - # end - # - # As you can see, the block expects to be passed the controller after it has assigned the request to the internal variables. - # This means that the block has access to both the request and response objects complete with convenience methods for params, - # session, template, and assigns. Note: The inline method doesn't strictly have to be a block; any object that responds to call - # and returns 1 or -1 on arity will do (such as a Proc or an Method object). - # - # == Filter chain ordering - # - # Using before_filter and after_filter appends the specified filters to the existing chain. That's usually - # just fine, but some times you care more about the order in which the filters are executed. When that's the case, you - # can use prepend_before_filter and prepend_after_filter. Filters added by these methods will be put at the - # beginning of their respective chain and executed before the rest. For example: - # - # class ShoppingController - # before_filter :verify_open_shop - # - # class CheckoutController - # prepend_before_filter :ensure_items_in_cart, :ensure_items_in_stock - # - # The filter chain for the CheckoutController is now :ensure_items_in_cart, :ensure_items_in_stock, - # :verify_open_shop. So if either of the ensure filters return false, we'll never get around to see if the shop - # is open or not. - # - # You may pass multiple filter arguments of each type as well as a filter block. - # If a block is given, it is treated as the last argument. - # - # == Around filters - # - # In addition to the individual before and after filters, it's also possible to specify that a single object should handle - # both the before and after call. That's especially useful when you need to keep state active between the before and after, - # such as the example of a benchmark filter below: - # - # class WeblogController < ActionController::Base - # around_filter BenchmarkingFilter.new - # - # # Before this action is performed, BenchmarkingFilter#before(controller) is executed - # def index - # end - # # After this action has been performed, BenchmarkingFilter#after(controller) is executed - # end - # - # class BenchmarkingFilter - # def initialize - # @runtime - # end - # - # def before - # start_timer - # end - # - # def after - # stop_timer - # report_result - # end - # end - # - # == Filter chain skipping - # - # Some times its convenient to specify a filter chain in a superclass that'll hold true for the majority of the - # subclasses, but not necessarily all of them. The subclasses that behave in exception can then specify which filters - # they would like to be relieved of. Examples - # - # class ApplicationController < ActionController::Base - # before_filter :authenticate - # end - # - # class WeblogController < ApplicationController - # # will run the :authenticate filter - # end - # - # class SignupController < ApplicationController - # # will not run the :authenticate filter - # skip_before_filter :authenticate - # end - # - # == Filter conditions - # - # Filters can be limited to run for only specific actions. This can be expressed either by listing the actions to - # exclude or the actions to include when executing the filter. Available conditions are +:only+ or +:except+, both - # of which accept an arbitrary number of method references. For example: - # - # class Journal < ActionController::Base - # # only require authentication if the current action is edit or delete - # before_filter :authorize, :only => [ :edit, :delete ] - # - # private - # def authorize - # # redirect to login unless authenticated - # end - # end - # - # When setting conditions on inline method (proc) filters the condition must come first and be placed in parentheses. - # - # class UserPreferences < ActionController::Base - # before_filter(:except => :new) { # some proc ... } - # # ... - # end - # - module ClassMethods - # The passed filters will be appended to the array of filters that's run _before_ actions - # on this controller are performed. - def append_before_filter(*filters, &block) - conditions = extract_conditions!(filters) - filters << block if block_given? - add_action_conditions(filters, conditions) - append_filter_to_chain('before', filters) - end - - # The passed filters will be prepended to the array of filters that's run _before_ actions - # on this controller are performed. - def prepend_before_filter(*filters, &block) - conditions = extract_conditions!(filters) - filters << block if block_given? - add_action_conditions(filters, conditions) - prepend_filter_to_chain('before', filters) - end - - # Short-hand for append_before_filter since that's the most common of the two. - alias :before_filter :append_before_filter - - # The passed filters will be appended to the array of filters that's run _after_ actions - # on this controller are performed. - def append_after_filter(*filters, &block) - conditions = extract_conditions!(filters) - filters << block if block_given? - add_action_conditions(filters, conditions) - append_filter_to_chain('after', filters) - end - - # The passed filters will be prepended to the array of filters that's run _after_ actions - # on this controller are performed. - def prepend_after_filter(*filters, &block) - conditions = extract_conditions!(filters) - filters << block if block_given? - add_action_conditions(filters, conditions) - prepend_filter_to_chain("after", filters) - end - - # Short-hand for append_after_filter since that's the most common of the two. - alias :after_filter :append_after_filter - - # The passed filters will have their +before+ method appended to the array of filters that's run both before actions - # on this controller are performed and have their +after+ method prepended to the after actions. The filter objects must all - # respond to both +before+ and +after+. So if you do append_around_filter A.new, B.new, the callstack will look like: - # - # B#before - # A#before - # A#after - # B#after - def append_around_filter(*filters) - conditions = extract_conditions!(filters) - for filter in filters.flatten - ensure_filter_responds_to_before_and_after(filter) - append_before_filter(conditions || {}) { |c| filter.before(c) } - prepend_after_filter(conditions || {}) { |c| filter.after(c) } - end - end - - # The passed filters will have their +before+ method prepended to the array of filters that's run both before actions - # on this controller are performed and have their +after+ method appended to the after actions. The filter objects must all - # respond to both +before+ and +after+. So if you do prepend_around_filter A.new, B.new, the callstack will look like: - # - # A#before - # B#before - # B#after - # A#after - def prepend_around_filter(*filters) - for filter in filters.flatten - ensure_filter_responds_to_before_and_after(filter) - prepend_before_filter { |c| filter.before(c) } - append_after_filter { |c| filter.after(c) } - end - end - - # Short-hand for append_around_filter since that's the most common of the two. - alias :around_filter :append_around_filter - - # Removes the specified filters from the +before+ filter chain. Note that this only works for skipping method-reference - # filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out - # of many sub-controllers need a different hierarchy. - # - # You can control the actions to skip the filter for with the :only and :except options, - # just like when you apply the filters. - def skip_before_filter(*filters) - if conditions = extract_conditions!(filters) - remove_contradicting_conditions!(filters, conditions) - conditions[:only], conditions[:except] = conditions[:except], conditions[:only] - add_action_conditions(filters, conditions) - else - for filter in filters.flatten - write_inheritable_attribute("before_filters", read_inheritable_attribute("before_filters") - [ filter ]) - end - end - end - - # Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference - # filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out - # of many sub-controllers need a different hierarchy. - # - # You can control the actions to skip the filter for with the :only and :except options, - # just like when you apply the filters. - def skip_after_filter(*filters) - if conditions = extract_conditions!(filters) - remove_contradicting_conditions!(filters, conditions) - conditions[:only], conditions[:except] = conditions[:except], conditions[:only] - add_action_conditions(filters, conditions) - else - for filter in filters.flatten - write_inheritable_attribute("after_filters", read_inheritable_attribute("after_filters") - [ filter ]) - end - end - end - - # Returns all the before filters for this class and all its ancestors. - def before_filters #:nodoc: - @before_filters ||= read_inheritable_attribute("before_filters") || [] - end - - # Returns all the after filters for this class and all its ancestors. - def after_filters #:nodoc: - @after_filters ||= read_inheritable_attribute("after_filters") || [] - end - - # Returns a mapping between filters and the actions that may run them. - def included_actions #:nodoc: - @included_actions ||= read_inheritable_attribute("included_actions") || {} - end - - # Returns a mapping between filters and actions that may not run them. - def excluded_actions #:nodoc: - @excluded_actions ||= read_inheritable_attribute("excluded_actions") || {} - end - - private - def append_filter_to_chain(condition, filters) - write_inheritable_array("#{condition}_filters", filters) - end - - def prepend_filter_to_chain(condition, filters) - old_filters = read_inheritable_attribute("#{condition}_filters") || [] - write_inheritable_attribute("#{condition}_filters", filters + old_filters) - end - - def ensure_filter_responds_to_before_and_after(filter) - unless filter.respond_to?(:before) && filter.respond_to?(:after) - raise ActionControllerError, "Filter object must respond to both before and after" - end - end - - def extract_conditions!(filters) - return nil unless filters.last.is_a? Hash - filters.pop - end - - def add_action_conditions(filters, conditions) - return unless conditions - included, excluded = conditions[:only], conditions[:except] - write_inheritable_hash('included_actions', condition_hash(filters, included)) && return if included - write_inheritable_hash('excluded_actions', condition_hash(filters, excluded)) if excluded - end - - def condition_hash(filters, *actions) - filters.inject({}) {|hash, filter| hash.merge(filter => actions.flatten.map {|action| action.to_s})} - end - - def remove_contradicting_conditions!(filters, conditions) - return unless conditions[:only] - filters.each do |filter| - next unless included_actions_for_filter = (read_inheritable_attribute('included_actions') || {})[filter] - [*conditions[:only]].each do |conditional_action| - conditional_action = conditional_action.to_s - included_actions_for_filter.delete(conditional_action) if included_actions_for_filter.include?(conditional_action) - end - end - end - end - - module InstanceMethods # :nodoc: - def self.included(base) - base.class_eval do - alias_method :perform_action_without_filters, :perform_action - alias_method :perform_action, :perform_action_with_filters - - alias_method :process_without_filters, :process - alias_method :process, :process_with_filters - - alias_method :process_cleanup_without_filters, :process_cleanup - alias_method :process_cleanup, :process_cleanup_with_filters - end - end - - def perform_action_with_filters - before_action_result = before_action - - unless before_action_result == false || performed? - perform_action_without_filters - after_action - end - - @before_filter_chain_aborted = (before_action_result == false) - end - - def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc: - @before_filter_chain_aborted = false - process_without_filters(request, response, method, *arguments) - end - - # Calls all the defined before-filter filters, which are added by using "before_filter :method". - # If any of the filters return false, no more filters will be executed and the action is aborted. - def before_action #:doc: - call_filters(self.class.before_filters) - end - - # Calls all the defined after-filter filters, which are added by using "after_filter :method". - # If any of the filters return false, no more filters will be executed. - def after_action #:doc: - call_filters(self.class.after_filters) - end - - private - def call_filters(filters) - filters.each do |filter| - next if action_exempted?(filter) - - filter_result = case - when filter.is_a?(Symbol) - self.send(filter) - when filter_block?(filter) - filter.call(self) - when filter_class?(filter) - filter.filter(self) - else - raise( - ActionControllerError, - 'Filters need to be either a symbol, proc/method, or class implementing a static filter method' - ) - end - - if filter_result == false - logger.info "Filter chain halted as [#{filter}] returned false" if logger - return false - end - end - end - - def filter_block?(filter) - filter.respond_to?('call') && (filter.arity == 1 || filter.arity == -1) - end - - def filter_class?(filter) - filter.respond_to?('filter') - end - - def action_exempted?(filter) - case - when ia = self.class.included_actions[filter] - !ia.include?(action_name) - when ea = self.class.excluded_actions[filter] - ea.include?(action_name) - end - end - - def process_cleanup_with_filters - if @before_filter_chain_aborted - close_session - else - process_cleanup_without_filters - end - end - end - end -end -module ActionController #:nodoc: - # The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed - # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create action - # that sets flash[:notice] = "Successfully created" before redirecting to a display action that can then expose - # the flash to its template. Actually, that exposure is automatically done. Example: - # - # class WeblogController < ActionController::Base - # def create - # # save post - # flash[:notice] = "Successfully created post" - # redirect_to :action => "display", :params => { :id => post.id } - # end - # - # def display - # # doesn't need to assign the flash notice to the template, that's done automatically - # end - # end - # - # display.rhtml - # <% if @flash[:notice] %>
<%= @flash[:notice] %>
<% end %> - # - # This example just places a string in the flash, but you can put any object in there. And of course, you can put as many - # as you like at a time too. Just remember: They'll be gone by the time the next action has been performed. - # - # See docs on the FlashHash class for more details about the flash. - module Flash - def self.included(base) - base.send :include, InstanceMethods - - base.class_eval do - alias_method :assign_shortcuts_without_flash, :assign_shortcuts - alias_method :assign_shortcuts, :assign_shortcuts_with_flash - - alias_method :process_cleanup_without_flash, :process_cleanup - alias_method :process_cleanup, :process_cleanup_with_flash - end - end - - - class FlashNow #:nodoc: - def initialize(flash) - @flash = flash - end - - def []=(k, v) - @flash[k] = v - @flash.discard(k) - v - end - - def [](k) - @flash[k] - end - end - - class FlashHash < Hash - def initialize #:nodoc: - super - @used = {} - end - - def []=(k, v) #:nodoc: - keep(k) - super - end - - def update(h) #:nodoc: - h.keys.each{ |k| discard(k) } - super - end - - alias :merge! :update - - def replace(h) #:nodoc: - @used = {} - super - end - - # Sets a flash that will not be available to the next action, only to the current. - # - # flash.now[:message] = "Hello current action" - # - # This method enables you to use the flash as a central messaging system in your app. - # When you need to pass an object to the next action, you use the standard flash assign ([]=). - # When you need to pass an object to the current action, you use now, and your object will - # vanish when the current action is done. - # - # Entries set via now are accessed the same way as standard entries: flash['my-key']. - def now - FlashNow.new self - end - - # Keeps either the entire current flash or a specific flash entry available for the next action: - # - # flash.keep # keeps the entire flash - # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded - def keep(k=nil) - use(k, false) - end - - # Marks the entire flash or a single flash entry to be discarded by the end of the current action - # - # flash.keep # keep entire flash available for the next action - # flash.discard(:warning) # discard the "warning" entry (it'll still be available for the current action) - def discard(k=nil) - use(k) - end - - # Mark for removal entries that were kept, and delete unkept ones. - # - # This method is called automatically by filters, so you generally don't need to care about it. - def sweep #:nodoc: - keys.each do |k| - unless @used[k] - use(k) - else - delete(k) - @used.delete(k) - end - end - (@used.keys - keys).each{|k| @used.delete k } # clean up after keys that could have been left over by calling reject! or shift on the flash - end - - private - # Used internally by the keep and discard methods - # use() # marks the entire flash as used - # use('msg') # marks the "msg" entry as used - # use(nil, false) # marks the entire flash as unused (keeps it around for one more action) - # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action) - def use(k=nil, v=true) - unless k.nil? - @used[k] = v - else - keys.each{|key| use key, v } - end - end - end - - module InstanceMethods #:nodoc: - def assign_shortcuts_with_flash(request, response) #:nodoc: - assign_shortcuts_without_flash(request, response) - flash(:refresh) - end - - def process_cleanup_with_flash - flash.sweep if @session - process_cleanup_without_flash - end - - protected - # Access the contents of the flash. Use flash["notice"] to read a notice you put there or - # flash["notice"] = "hello" to put a new one. - # Note that if sessions are disabled only flash.now will work. - def flash(refresh = false) #:doc: - if @flash.nil? || refresh - @flash = - if @session.is_a?(Hash) - # @session is a Hash, if sessions are disabled - # we don't put the flash in the session in this case - FlashHash.new - else - # otherwise, @session is a CGI::Session or a TestSession - # so make sure it gets retrieved from/saved to session storage after request processing - @session["flash"] ||= FlashHash.new - end - end - - @flash - end - - # deprecated. use flash.keep instead - def keep_flash #:doc: - warn 'keep_flash is deprecated; use flash.keep instead.' - flash.keep - end - end - end -endmodule ActionController #:nodoc: - module Helpers #:nodoc: - def self.append_features(base) - super - - # Initialize the base module to aggregate its helpers. - base.class_inheritable_accessor :master_helper_module - base.master_helper_module = Module.new - - # Extend base with class methods to declare helpers. - base.extend(ClassMethods) - - base.class_eval do - # Wrap inherited to create a new master helper module for subclasses. - class << self - alias_method :inherited_without_helper, :inherited - alias_method :inherited, :inherited_with_helper - end - end - end - - # The template helpers serve to relieve the templates from including the same inline code again and again. It's a - # set of standardized methods for working with forms (FormHelper), dates (DateHelper), texts (TextHelper), and - # Active Records (ActiveRecordHelper) that's available to all templates by default. - # - # It's also really easy to make your own helpers and it's much encouraged to keep the template files free - # from complicated logic. It's even encouraged to bundle common compositions of methods from other helpers - # (often the common helpers) as they're used by the specific application. - # - # module MyHelper - # def hello_world() "hello world" end - # end - # - # MyHelper can now be included in a controller, like this: - # - # class MyController < ActionController::Base - # helper :my_helper - # end - # - # ...and, same as above, used in any template rendered from MyController, like this: - # - # Let's hear what the helper has to say: <%= hello_world %> - module ClassMethods - # Makes all the (instance) methods in the helper module available to templates rendered through this controller. - # See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules - # available to the templates. - def add_template_helper(helper_module) #:nodoc: - master_helper_module.send(:include, helper_module) - end - - # Declare a helper: - # helper :foo - # requires 'foo_helper' and includes FooHelper in the template class. - # helper FooHelper - # includes FooHelper in the template class. - # helper { def foo() "#{bar} is the very best" end } - # evaluates the block in the template class, adding method #foo. - # helper(:three, BlindHelper) { def mice() 'mice' end } - # does all three. - def helper(*args, &block) - args.flatten.each do |arg| - case arg - when Module - add_template_helper(arg) - when String, Symbol - file_name = arg.to_s.underscore + '_helper' - class_name = file_name.camelize - - begin - require_dependency(file_name) - rescue LoadError => load_error - requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1] - msg = (requiree == file_name) ? "Missing helper file helpers/#{file_name}.rb" : "Can't load file: #{requiree}" - raise LoadError.new(msg).copy_blame!(load_error) - end - - add_template_helper(class_name.constantize) - else - raise ArgumentError, 'helper expects String, Symbol, or Module argument' - end - end - - # Evaluate block in template class if given. - master_helper_module.module_eval(&block) if block_given? - end - - # Declare a controller method as a helper. For example, - # helper_method :link_to - # def link_to(name, options) ... end - # makes the link_to controller method available in the view. - def helper_method(*methods) - methods.flatten.each do |method| - master_helper_module.module_eval <<-end_eval - def #{method}(*args, &block) - controller.send(%(#{method}), *args, &block) - end - end_eval - end - end - - # Declare a controller attribute as a helper. For example, - # helper_attr :name - # attr_accessor :name - # makes the name and name= controller methods available in the view. - # The is a convenience wrapper for helper_method. - def helper_attr(*attrs) - attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") } - end - - private - def default_helper_module! - module_name = name.sub(/Controller$|$/, 'Helper') - module_path = module_name.split('::').map { |m| m.underscore }.join('/') - require_dependency module_path - helper module_name.constantize - rescue LoadError - logger.debug("#{name}: missing default helper path #{module_path}") if logger - rescue NameError - logger.debug("#{name}: missing default helper module #{module_name}") if logger - end - - def inherited_with_helper(child) - inherited_without_helper(child) - begin - child.master_helper_module = Module.new - child.master_helper_module.send :include, master_helper_module - child.send :default_helper_module! - rescue MissingSourceFile => e - raise unless e.is_missing?("helpers/#{child.controller_path}_helper") - end - end - end - end -end -require 'dispatcher' -require 'stringio' -require 'uri' - -module ActionController - module Integration #:nodoc: - # An integration Session instance represents a set of requests and responses - # performed sequentially by some virtual user. Becase you can instantiate - # multiple sessions and run them side-by-side, you can also mimic (to some - # limited extent) multiple simultaneous users interacting with your system. - # - # Typically, you will instantiate a new session using IntegrationTest#open_session, - # rather than instantiating Integration::Session directly. - class Session - include Test::Unit::Assertions - include ActionController::TestProcess - - # The integer HTTP status code of the last request. - attr_reader :status - - # The status message that accompanied the status code of the last request. - attr_reader :status_message - - # The URI of the last request. - attr_reader :path - - # The hostname used in the last request. - attr_accessor :host - - # The remote_addr used in the last request. - attr_accessor :remote_addr - - # The Accept header to send. - attr_accessor :accept - - # A map of the cookies returned by the last response, and which will be - # sent with the next request. - attr_reader :cookies - - # A map of the headers returned by the last response. - attr_reader :headers - - # A reference to the controller instance used by the last request. - attr_reader :controller - - # A reference to the request instance used by the last request. - attr_reader :request - - # A reference to the response instance used by the last request. - attr_reader :response - - # Create an initialize a new Session instance. - def initialize - reset! - end - - # Resets the instance. This can be used to reset the state information - # in an existing session instance, so it can be used from a clean-slate - # condition. - # - # session.reset! - def reset! - @status = @path = @headers = nil - @result = @status_message = nil - @https = false - @cookies = {} - @controller = @request = @response = nil - - self.host = "www.example.com" - self.remote_addr = "127.0.0.1" - self.accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5" - - unless @named_routes_configured - # install the named routes in this session instance. - klass = class< "XMLHttpRequest") - post(path, parameters, headers) - end - - # Returns the URL for the given options, according to the rules specified - # in the application's routes. - def url_for(options) - controller ? controller.url_for(options) : generic_url_rewriter.rewrite(options) - end - - private - - class MockCGI < CGI #:nodoc: - attr_accessor :stdinput, :stdoutput, :env_table - - def initialize(env, input=nil) - self.env_table = env - self.stdinput = StringIO.new(input || "") - self.stdoutput = StringIO.new - - super() - end - end - - # Tailors the session based on the given URI, setting the HTTPS value - # and the hostname. - def interpret_uri(path) - location = URI.parse(path) - https! URI::HTTPS === location if location.scheme - host! location.host if location.host - location.query ? "#{location.path}?#{location.query}" : location.path - end - - # Performs the actual request. - def process(method, path, parameters=nil, headers=nil) - data = requestify(parameters) - path = interpret_uri(path) if path =~ %r{://} - path = "/#{path}" unless path[0] == ?/ - @path = path - env = {} - - if method == :get - env["QUERY_STRING"] = data - data = nil - end - - env.update( - "REQUEST_METHOD" => method.to_s.upcase, - "REQUEST_URI" => path, - "HTTP_HOST" => host, - "REMOTE_ADDR" => remote_addr, - "SERVER_PORT" => (https? ? "443" : "80"), - "CONTENT_TYPE" => "application/x-www-form-urlencoded", - "CONTENT_LENGTH" => data ? data.length.to_s : nil, - "HTTP_COOKIE" => encode_cookies, - "HTTPS" => https? ? "on" : "off", - "HTTP_ACCEPT" => accept - ) - - (headers || {}).each do |key, value| - key = key.to_s.upcase.gsub(/-/, "_") - key = "HTTP_#{key}" unless env.has_key?(key) || env =~ /^X|HTTP/ - env[key] = value - end - - unless ActionController::Base.respond_to?(:clear_last_instantiation!) - ActionController::Base.send(:include, ControllerCapture) - end - - ActionController::Base.clear_last_instantiation! - - cgi = MockCGI.new(env, data) - Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput) - @result = cgi.stdoutput.string - - @controller = ActionController::Base.last_instantiation - @request = @controller.request - @response = @controller.response - - # Decorate the response with the standard behavior of the TestResponse - # so that things like assert_response can be used in integration - # tests. - @response.extend(TestResponseBehavior) - - parse_result - return status - end - - # Parses the result of the response and extracts the various values, - # like cookies, status, headers, etc. - def parse_result - headers, result_body = @result.split(/\r\n\r\n/, 2) - - @headers = Hash.new { |h,k| h[k] = [] } - headers.each_line do |line| - key, value = line.strip.split(/:\s*/, 2) - @headers[key.downcase] << value - end - - (@headers['set-cookie'] || [] ).each do |string| - name, value = string.match(/^(.*?)=(.*?);/)[1,2] - @cookies[name] = value - end - - @status, @status_message = @headers["status"].first.split(/ /) - @status = @status.to_i - end - - # Encode the cookies hash in a format suitable for passing to a - # request. - def encode_cookies - cookies.inject("") do |string, (name, value)| - string << "#{name}=#{value}; " - end - end - - # Get a temporarly URL writer object - def generic_url_rewriter - cgi = MockCGI.new('REQUEST_METHOD' => "GET", - 'QUERY_STRING' => "", - "REQUEST_URI" => "/", - "HTTP_HOST" => host, - "SERVER_PORT" => https? ? "443" : "80", - "HTTPS" => https? ? "on" : "off") - ActionController::UrlRewriter.new(ActionController::CgiRequest.new(cgi), {}) - end - - def name_with_prefix(prefix, name) - prefix ? "#{prefix}[#{name}]" : name.to_s - end - - # Convert the given parameters to a request string. The parameters may - # be a string, +nil+, or a Hash. - def requestify(parameters, prefix=nil) - if Hash === parameters - return nil if parameters.empty? - parameters.map { |k,v| requestify(v, name_with_prefix(prefix, k)) }.join("&") - elsif Array === parameters - parameters.map { |v| requestify(v, name_with_prefix(prefix, "")) }.join("&") - elsif prefix.nil? - parameters - else - "#{CGI.escape(prefix)}=#{CGI.escape(parameters.to_s)}" - end - end - - end - - # A module used to extend ActionController::Base, so that integration tests - # can capture the controller used to satisfy a request. - module ControllerCapture #:nodoc: - def self.included(base) - base.extend(ClassMethods) - base.class_eval do - class < people(:jamis).username, - # :password => people(:jamis).password - # follow_redirect! - # assert_equal 200, status - # assert_equal "/home", path - # end - # end - # - # However, you can also have multiple session instances open per test, and - # even extend those instances with assertions and methods to create a very - # powerful testing DSL that is specific for your application. You can even - # reference any named routes you happen to have defined! - # - # require "#{File.dirname(__FILE__)}/test_helper" - # - # class AdvancedTest < ActionController::IntegrationTest - # fixtures :people, :rooms - # - # def test_login_and_speak - # jamis, david = login(:jamis), login(:david) - # room = rooms(:office) - # - # jamis.enter(room) - # jamis.speak(room, "anybody home?") - # - # david.enter(room) - # david.speak(room, "hello!") - # end - # - # private - # - # module CustomAssertions - # def enter(room) - # # reference a named route, for maximum internal consistency! - # get(room_url(:id => room.id)) - # assert(...) - # ... - # end - # - # def speak(room, message) - # xml_http_request "/say/#{room.id}", :message => message - # assert(...) - # ... - # end - # end - # - # def login(who) - # open_session do |sess| - # sess.extend(CustomAssertions) - # who = people(who) - # sess.post "/login", :username => who.username, - # :password => who.password - # assert(...) - # end - # end - # end - class IntegrationTest < Test::Unit::TestCase - # Work around a bug in test/unit caused by the default test being named - # as a symbol (:default_test), which causes regex test filters - # (like "ruby test.rb -n /foo/") to fail because =~ doesn't work on - # symbols. - def initialize(name) #:nodoc: - super(name.to_s) - end - - # Work around test/unit's requirement that every subclass of TestCase have - # at least one test method. Note that this implementation extends to all - # subclasses, as well, so subclasses of IntegrationTest may also exist - # without any test methods. - def run(*args) #:nodoc: - return if @method_name == "default_test" - super - end - - # Because of how use_instantiated_fixtures and use_transactional_fixtures - # are defined, we need to treat them as special cases. Otherwise, users - # would potentially have to set their values for both Test::Unit::TestCase - # ActionController::IntegrationTest, since by the time the value is set on - # TestCase, IntegrationTest has already been defined and cannot inherit - # changes to those variables. So, we make those two attributes copy-on-write. - - class << self - def use_transactional_fixtures=(flag) #:nodoc: - @_use_transactional_fixtures = true - @use_transactional_fixtures = flag - end - - def use_instantiated_fixtures=(flag) #:nodoc: - @_use_instantiated_fixtures = true - @use_instantiated_fixtures = flag - end - - def use_transactional_fixtures #:nodoc: - @_use_transactional_fixtures ? - @use_transactional_fixtures : - superclass.use_transactional_fixtures - end - - def use_instantiated_fixtures #:nodoc: - @_use_instantiated_fixtures ? - @use_instantiated_fixtures : - superclass.use_instantiated_fixtures - end - end - - # Reset the current session. This is useful for testing multiple sessions - # in a single test case. - def reset! - @integration_session = open_session - end - - %w(get post cookies assigns xml_http_request).each do |method| - define_method(method) do |*args| - reset! unless @integration_session - returning @integration_session.send(method, *args) do - copy_session_variables! - end - end - end - - # Open a new session instance. If a block is given, the new session is - # yielded to the block before being returned. - # - # session = open_session do |sess| - # sess.extend(CustomAssertions) - # end - # - # By default, a single session is automatically created for you, but you - # can use this method to open multiple sessions that ought to be tested - # simultaneously. - def open_session - session = Integration::Session.new - - # delegate the fixture accessors back to the test instance - extras = Module.new { attr_accessor :delegate, :test_result } - self.class.fixture_table_names.each do |table_name| - name = table_name.tr(".", "_") - next unless respond_to?(name) - extras.send(:define_method, name) { |*args| delegate.send(name, *args) } - end - - # delegate add_assertion to the test case - extras.send(:define_method, :add_assertion) { test_result.add_assertion } - session.extend(extras) - session.delegate = self - session.test_result = @_result - - yield session if block_given? - session - end - - # Copy the instance variables from the current session instance into the - # test instance. - def copy_session_variables! #:nodoc: - return unless @integration_session - %w(controller response request).each do |var| - instance_variable_set("@#{var}", @integration_session.send(var)) - end - end - - # Delegate unhandled messages to the current session instance. - def method_missing(sym, *args, &block) - reset! unless @integration_session - returning @integration_session.send(sym, *args, &block) do - copy_session_variables! - end - end - end -end -module ActionController #:nodoc: - module Layout #:nodoc: - def self.included(base) - base.extend(ClassMethods) - base.class_eval do - alias_method :render_with_no_layout, :render - alias_method :render, :render_with_a_layout - - class << self - alias_method :inherited_without_layout, :inherited - alias_method :inherited, :inherited_with_layout - end - end - end - - # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in - # repeated setups. The inclusion pattern has pages that look like this: - # - # <%= render "shared/header" %> - # Hello World - # <%= render "shared/footer" %> - # - # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose - # and if you ever want to change the structure of these two includes, you'll have to change all the templates. - # - # With layouts, you can flip it around and have the common structure know where to insert changing content. This means - # that the header and footer are only mentioned in one place, like this: - # - # - # <%= yield %> - # - # - # And then you have content pages that look like this: - # - # hello world - # - # Not a word about common structures. At rendering time, the content page is computed and then inserted in the layout, - # like this: - # - # - # hello world - # - # - # == Accessing shared variables - # - # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with - # references that won't materialize before rendering time: - # - #

<%= @page_title %>

- # <%= yield %> - # - # ...and content pages that fulfill these references _at_ rendering time: - # - # <% @page_title = "Welcome" %> - # Off-world colonies offers you a chance to start a new life - # - # The result after rendering is: - # - #

Welcome

- # Off-world colonies offers you a chance to start a new life - # - # == Automatic layout assignment - # - # If there is a template in app/views/layouts/ with the same name as the current controller then it will be automatically - # set as that controller's layout unless explicitly told otherwise. Say you have a WeblogController, for example. If a template named - # app/views/layouts/weblog.rhtml or app/views/layouts/weblog.rxml exists then it will be automatically set as - # the layout for your WeblogController. You can create a layout with the name application.rhtml or application.rxml - # and this will be set as the default controller if there is no layout with the same name as the current controller and there is - # no layout explicitly assigned with the +layout+ method. Nested controllers use the same folder structure for automatic layout. - # assignment. So an Admin::WeblogController will look for a template named app/views/layouts/admin/weblog.rhtml. - # Setting a layout explicitly will always override the automatic behaviour for the controller where the layout is set. - # Explicitly setting the layout in a parent class, though, will not override the child class's layout assignement if the child - # class has a layout with the same name. - # - # == Inheritance for layouts - # - # Layouts are shared downwards in the inheritance hierarchy, but not upwards. Examples: - # - # class BankController < ActionController::Base - # layout "bank_standard" - # - # class InformationController < BankController - # - # class VaultController < BankController - # layout :access_level_layout - # - # class EmployeeController < BankController - # layout nil - # - # The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites - # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all. - # - # == Types of layouts - # - # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes - # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can - # be done either by specifying a method reference as a symbol or using an inline method (as a proc). - # - # The method reference is the preferred approach to variable layouts and is used like this: - # - # class WeblogController < ActionController::Base - # layout :writers_and_readers - # - # def index - # # fetching posts - # end - # - # private - # def writers_and_readers - # logged_in? ? "writer_layout" : "reader_layout" - # end - # - # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing - # is logged in or not. - # - # If you want to use an inline method, such as a proc, do something like this: - # - # class WeblogController < ActionController::Base - # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" } - # - # Of course, the most common way of specifying a layout is still just as a plain template name: - # - # class WeblogController < ActionController::Base - # layout "weblog_standard" - # - # If no directory is specified for the template name, the template will by default by looked for in +app/views/layouts/+. - # - # == Conditional layouts - # - # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering - # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The - # :only and :except options can be passed to the layout call. For example: - # - # class WeblogController < ActionController::Base - # layout "weblog_standard", :except => :rss - # - # # ... - # - # end - # - # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout - # around the rendered view. - # - # Both the :only and :except condition can accept an arbitrary number of method references, so - # #:except => [ :rss, :text_only ] is valid, as is :except => :rss. - # - # == Using a different layout in the action render call - # - # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above. - # Some times you'll have exceptions, though, where one action wants to use a different layout than the rest of the controller. - # This is possible using the render method. It's just a bit more manual work as you'll have to supply fully - # qualified template and layout names as this example shows: - # - # class WeblogController < ActionController::Base - # def help - # render :action => "help/index", :layout => "help" - # end - # end - # - # As you can see, you pass the template as the first parameter, the status code as the second ("200" is OK), and the layout - # as the third. - # - # NOTE: The old notation for rendering the view from a layout was to expose the magic @content_for_layout instance - # variable. The preferred notation now is to use yield, as documented above. - module ClassMethods - # If a layout is specified, all rendered actions will have their result rendered - # when the layoutyield's. This layout can itself depend on instance variables assigned during action - # performance and have access to them as any normal template would. - def layout(template_name, conditions = {}) - add_layout_conditions(conditions) - write_inheritable_attribute "layout", template_name - end - - def layout_conditions #:nodoc: - @layout_conditions ||= read_inheritable_attribute("layout_conditions") - end - - def default_layout #:nodoc: - @default_layout ||= read_inheritable_attribute("layout") - end - - private - def inherited_with_layout(child) - inherited_without_layout(child) - child.send :include, Reloadable - layout_match = child.name.underscore.sub(/_controller$/, '').sub(/^controllers\//, '') - child.layout(layout_match) unless layout_list.grep(%r{layouts/#{layout_match}\.[a-z][0-9a-z]*$}).empty? - end - - def layout_list - Dir.glob("#{template_root}/layouts/**/*") - end - - def add_layout_conditions(conditions) - write_inheritable_hash "layout_conditions", normalize_conditions(conditions) - end - - def normalize_conditions(conditions) - conditions.inject({}) {|hash, (key, value)| hash.merge(key => [value].flatten.map {|action| action.to_s})} - end - - def layout_directory_exists_cache - @@layout_directory_exists_cache ||= Hash.new do |h, dirname| - h[dirname] = File.directory? dirname - end - end - end - - # Returns the name of the active layout. If the layout was specified as a method reference (through a symbol), this method - # is called and the return value is used. Likewise if the layout was specified as an inline method (through a proc or method - # object). If the layout was defined without a directory, layouts is assumed. So layout "weblog/standard" will return - # weblog/standard, but layout "standard" will return layouts/standard. - def active_layout(passed_layout = nil) - layout = passed_layout || self.class.default_layout - - active_layout = case layout - when String then layout - when Symbol then send(layout) - when Proc then layout.call(self) - end - - # Explicitly passed layout names with slashes are looked up relative to the template root, - # but auto-discovered layouts derived from a nested controller will contain a slash, though be relative - # to the 'layouts' directory so we have to check the file system to infer which case the layout name came from. - if active_layout - if active_layout.include?('/') && ! layout_directory?(active_layout) - active_layout - else - "layouts/#{active_layout}" - end - end - end - - def render_with_a_layout(options = nil, deprecated_status = nil, deprecated_layout = nil, &block) #:nodoc: - template_with_options = options.is_a?(Hash) - - if apply_layout?(template_with_options, options) && (layout = pick_layout(template_with_options, options, deprecated_layout)) - options = options.merge :layout => false if template_with_options - logger.info("Rendering #{options} within #{layout}") if logger - - if template_with_options - content_for_layout = render_with_no_layout(options, &block) - deprecated_status = options[:status] || deprecated_status - else - content_for_layout = render_with_no_layout(options, deprecated_status, &block) - end - - erase_render_results - add_variables_to_assigns - @template.instance_variable_set("@content_for_layout", content_for_layout) - render_text(@template.render_file(layout, true), deprecated_status) - else - render_with_no_layout(options, deprecated_status, &block) - end - end - - private - - def apply_layout?(template_with_options, options) - return false if options == :update - template_with_options ? candidate_for_layout?(options) : !template_exempt_from_layout? - end - - def candidate_for_layout?(options) - (options.has_key?(:layout) && options[:layout] != false) || - options.values_at(:text, :xml, :file, :inline, :partial, :nothing).compact.empty? && - !template_exempt_from_layout?(default_template_name(options[:action] || options[:template])) - end - - def pick_layout(template_with_options, options, deprecated_layout) - if deprecated_layout - deprecated_layout - elsif template_with_options - case layout = options[:layout] - when FalseClass - nil - when NilClass, TrueClass - active_layout if action_has_layout? - else - active_layout(layout) - end - else - active_layout if action_has_layout? - end - end - - def action_has_layout? - if conditions = self.class.layout_conditions - case - when only = conditions[:only] - only.include?(action_name) - when except = conditions[:except] - !except.include?(action_name) - else - true - end - else - true - end - end - - # Does a layout directory for this class exist? - # we cache this info in a class level hash - def layout_directory?(layout_name) - template_path = File.join(self.class.view_root, 'layouts', layout_name) - dirname = File.dirname(template_path) - self.class.send(:layout_directory_exists_cache)[dirname] - end - end -end -module ActionController - # Macros are class-level calls that add pre-defined actions to the controller based on the parameters passed in. - # Currently, they're used to bridge the JavaScript macros, like autocompletion and in-place editing, with the controller - # backing. - module Macros - module AutoComplete #:nodoc: - def self.append_features(base) #:nodoc: - super - base.extend(ClassMethods) - end - - # Example: - # - # # Controller - # class BlogController < ApplicationController - # auto_complete_for :post, :title - # end - # - # # View - # <%= text_field_with_auto_complete :post, title %> - # - # By default, auto_complete_for limits the results to 10 entries, - # and sorts by the given field. - # - # auto_complete_for takes a third parameter, an options hash to - # the find method used to search for the records: - # - # auto_complete_for :post, :title, :limit => 15, :order => 'created_at DESC' - # - # For help on defining text input fields with autocompletion, - # see ActionView::Helpers::JavaScriptHelper. - # - # For more examples, see script.aculo.us: - # * http://script.aculo.us/demos/ajax/autocompleter - # * http://script.aculo.us/demos/ajax/autocompleter_customized - module ClassMethods - def auto_complete_for(object, method, options = {}) - define_method("auto_complete_for_#{object}_#{method}") do - find_options = { - :conditions => [ "LOWER(#{method}) LIKE ?", '%' + params[object][method].downcase + '%' ], - :order => "#{method} ASC", - :limit => 10 }.merge!(options) - - @items = object.to_s.camelize.constantize.find(:all, find_options) - - render :inline => "<%= auto_complete_result @items, '#{method}' %>" - end - end - end - end - end -endmodule ActionController - module Macros - module InPlaceEditing #:nodoc: - def self.append_features(base) #:nodoc: - super - base.extend(ClassMethods) - end - - # Example: - # - # # Controller - # class BlogController < ApplicationController - # in_place_edit_for :post, :title - # end - # - # # View - # <%= in_place_editor_field :post, 'title' %> - # - # For help on defining an in place editor in the browser, - # see ActionView::Helpers::JavaScriptHelper. - module ClassMethods - def in_place_edit_for(object, attribute, options = {}) - define_method("set_#{object}_#{attribute}") do - @item = object.to_s.camelize.constantize.find(params[:id]) - @item.update_attribute(attribute, params[:value]) - render :text => @item.send(attribute) - end - end - end - end - end -end -module ActionController #:nodoc: - module MimeResponds #:nodoc: - def self.included(base) - base.send(:include, ActionController::MimeResponds::InstanceMethods) - end - - module InstanceMethods - # Without web-service support, an action which collects the data for displaying a list of people - # might look something like this: - # - # def list - # @people = Person.find(:all) - # end - # - # Here's the same action, with web-service support baked in: - # - # def list - # @people = Person.find(:all) - # - # respond_to do |wants| - # wants.html - # wants.xml { render :xml => @people.to_xml } - # end - # end - # - # What that says is, "if the client wants HTML in response to this action, just respond as we - # would have before, but if the client wants XML, return them the list of people in XML format." - # (Rails determines the desired response format from the HTTP Accept header submitted by the client.) - # - # Supposing you have an action that adds a new person, optionally creating their company - # (by name) if it does not already exist, without web-services, it might look like this: - # - # def add - # @company = Company.find_or_create_by_name(params[:company][:name]) - # @person = @company.people.create(params[:person]) - # - # redirect_to(person_list_url) - # end - # - # Here's the same action, with web-service support baked in: - # - # def add - # company = params[:person].delete(:company) - # @company = Company.find_or_create_by_name(company[:name]) - # @person = @company.people.create(params[:person]) - # - # respond_to do |wants| - # wants.html { redirect_to(person_list_url) } - # wants.js - # wants.xml { render :xml => @person.to_xml(:include => @company) } - # end - # end - # - # If the client wants HTML, we just redirect them back to the person list. If they want Javascript - # (wants.js), then it is an RJS request and we render the RJS template associated with this action. - # Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also - # include the person’s company in the rendered XML, so you get something like this: - # - # - # ... - # ... - # - # ... - # ... - # ... - # - # - # - # Note, however, the extra bit at the top of that action: - # - # company = params[:person].delete(:company) - # @company = Company.find_or_create_by_name(company[:name]) - # - # This is because the incoming XML document (if a web-service request is in process) can only contain a - # single root-node. So, we have to rearrange things so that the request looks like this (url-encoded): - # - # person[name]=...&person[company][name]=...&... - # - # And, like this (xml-encoded): - # - # - # ... - # - # ... - # - # - # - # In other words, we make the request so that it operates on a single entity—a person. Then, in the action, - # we extract the company data from the request, find or create the company, and then create the new person - # with the remaining data. - # - # Note that you can define your own XML parameter parser which would allow you to describe multiple entities - # in a single request (i.e., by wrapping them all in a single root note), but if you just go with the flow - # and accept Rails' defaults, life will be much easier. - # - # If you need to use a MIME type which isn't supported by default, you can register your own handlers in - # environment.rb as follows. - # - # Mime::Type.register "image/jpg", :jpg - # - def respond_to(*types, &block) - raise ArgumentError, "respond_to takes either types or a block, never bot" unless types.any? ^ block - block ||= lambda { |responder| types.each { |type| responder.send(type) } } - responder = Responder.new(block.binding) - block.call(responder) - responder.respond - end - end - - class Responder #:nodoc: - DEFAULT_BLOCKS = { - :html => 'Proc.new { render }', - :js => 'Proc.new { render :action => "#{action_name}.rjs" }', - :xml => 'Proc.new { render :action => "#{action_name}.rxml" }' - } - - def initialize(block_binding) - @block_binding = block_binding - @mime_type_priority = eval("request.accepts", block_binding) - @order = [] - @responses = {} - end - - def custom(mime_type, &block) - mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s) - - @order << mime_type - - if block_given? - @responses[mime_type] = block - else - @responses[mime_type] = eval(DEFAULT_BLOCKS[mime_type.to_sym], @block_binding) - end - end - - for mime_type in %w( all html js xml rss atom yaml ) - eval <<-EOT - def #{mime_type}(&block) - custom(Mime::#{mime_type.upcase}, &block) - end - EOT - end - - def any(*args, &block) - args.each { |type| send(type, &block) } - end - - def respond - for priority in @mime_type_priority - if priority == Mime::ALL - @responses[@order.first].call - return - else - if priority === @order - @responses[priority].call - return # mime type match found, be happy and return - end - end - end - - if @order.include?(Mime::ALL) - @responses[Mime::ALL].call - else - eval 'render(:nothing => true, :status => "406 Not Acceptable")', @block_binding - end - end - end - end -end -module Mime - class Type #:nodoc: - # A simple helper class used in parsing the accept header - class AcceptItem #:nodoc: - attr_accessor :order, :name, :q - - def initialize(order, name, q=nil) - @order = order - @name = name.strip - q ||= 0.0 if @name == "*/*" # default "*/*" to end of list - @q = ((q || 1.0).to_f * 100).to_i - end - - def to_s - @name - end - - def <=>(item) - result = item.q <=> q - result = order <=> item.order if result == 0 - result - end - - def ==(item) - name == (item.respond_to?(:name) ? item.name : item) - end - end - - class << self - def lookup(string) - LOOKUP[string] - end - - def parse(accept_header) - # keep track of creation order to keep the subsequent sort stable - index = 0 - list = accept_header.split(/,/). - map! { |i| AcceptItem.new(index += 1, *i.split(/;\s*q=/)) }.sort! - - # Take care of the broken text/xml entry by renaming or deleting it - - text_xml = list.index("text/xml") - app_xml = list.index("application/xml") - - if text_xml && app_xml - # set the q value to the max of the two - list[app_xml].q = [list[text_xml].q, list[app_xml].q].max - - # make sure app_xml is ahead of text_xml in the list - if app_xml > text_xml - list[app_xml], list[text_xml] = list[text_xml], list[app_xml] - app_xml, text_xml = text_xml, app_xml - end - - # delete text_xml from the list - list.delete_at(text_xml) - - elsif text_xml - list[text_xml].name = "application/xml" - end - - # Look for more specific xml-based types and sort them ahead of app/xml - - if app_xml - idx = app_xml - app_xml_type = list[app_xml] - - while(idx < list.length) - type = list[idx] - break if type.q < app_xml_type.q - if type.name =~ /\+xml$/ - list[app_xml], list[idx] = list[idx], list[app_xml] - app_xml = idx - end - idx += 1 - end - end - - list.map! { |i| Mime::Type.lookup(i.name) }.uniq! - list - end - end - - def initialize(string, symbol = nil, synonyms = []) - @symbol, @synonyms = symbol, synonyms - @string = string - end - - def to_s - @string - end - - def to_str - to_s - end - - def to_sym - @symbol || @string.to_sym - end - - def ===(list) - if list.is_a?(Array) - (@synonyms + [ self ]).any? { |synonym| list.include?(synonym) } - else - super - end - end - - def ==(mime_type) - (@synonyms + [ self ]).any? { |synonym| synonym.to_s == mime_type.to_s } if mime_type - end - end - - ALL = Type.new "*/*", :all - HTML = Type.new "text/html", :html, %w( application/xhtml+xml ) - JS = Type.new "text/javascript", :js, %w( application/javascript application/x-javascript ) - XML = Type.new "application/xml", :xml, %w( text/xml application/x-xml ) - RSS = Type.new "application/rss+xml", :rss - ATOM = Type.new "application/atom+xml", :atom - YAML = Type.new "application/x-yaml", :yaml, %w( text/yaml ) - - LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) } - - LOOKUP["*/*"] = ALL - - LOOKUP["text/html"] = HTML - LOOKUP["application/xhtml+xml"] = HTML - - LOOKUP["application/xml"] = XML - LOOKUP["text/xml"] = XML - LOOKUP["application/x-xml"] = XML - - LOOKUP["text/javascript"] = JS - LOOKUP["application/javascript"] = JS - LOOKUP["application/x-javascript"] = JS - - LOOKUP["text/yaml"] = YAML - LOOKUP["application/x-yaml"] = YAML - - LOOKUP["application/rss+xml"] = RSS - LOOKUP["application/atom+xml"] = ATOM -endmodule ActionController - # === Action Pack pagination for Active Record collections - # - # The Pagination module aids in the process of paging large collections of - # Active Record objects. It offers macro-style automatic fetching of your - # model for multiple views, or explicit fetching for single actions. And if - # the magic isn't flexible enough for your needs, you can create your own - # paginators with a minimal amount of code. - # - # The Pagination module can handle as much or as little as you wish. In the - # controller, have it automatically query your model for pagination; or, - # if you prefer, create Paginator objects yourself. - # - # Pagination is included automatically for all controllers. - # - # For help rendering pagination links, see - # ActionView::Helpers::PaginationHelper. - # - # ==== Automatic pagination for every action in a controller - # - # class PersonController < ApplicationController - # model :person - # - # paginate :people, :order => 'last_name, first_name', - # :per_page => 20 - # - # # ... - # end - # - # Each action in this controller now has access to a @people - # instance variable, which is an ordered collection of model objects for the - # current page (at most 20, sorted by last name and first name), and a - # @person_pages Paginator instance. The current page is determined - # by the params[:page] variable. - # - # ==== Pagination for a single action - # - # def list - # @person_pages, @people = - # paginate :people, :order => 'last_name, first_name' - # end - # - # Like the previous example, but explicitly creates @person_pages - # and @people for a single action, and uses the default of 10 items - # per page. - # - # ==== Custom/"classic" pagination - # - # def list - # @person_pages = Paginator.new self, Person.count, 10, params[:page] - # @people = Person.find :all, :order => 'last_name, first_name', - # :limit => @person_pages.items_per_page, - # :offset => @person_pages.current.offset - # end - # - # Explicitly creates the paginator from the previous example and uses - # Paginator#to_sql to retrieve @people from the model. - # - module Pagination - unless const_defined?(:OPTIONS) - # A hash holding options for controllers using macro-style pagination - OPTIONS = Hash.new - - # The default options for pagination - DEFAULT_OPTIONS = { - :class_name => nil, - :singular_name => nil, - :per_page => 10, - :conditions => nil, - :order_by => nil, - :order => nil, - :join => nil, - :joins => nil, - :count => nil, - :include => nil, - :select => nil, - :parameter => 'page' - } - end - - def self.included(base) #:nodoc: - super - base.extend(ClassMethods) - end - - def self.validate_options!(collection_id, options, in_action) #:nodoc: - options.merge!(DEFAULT_OPTIONS) {|key, old, new| old} - - valid_options = DEFAULT_OPTIONS.keys - valid_options << :actions unless in_action - - unknown_option_keys = options.keys - valid_options - raise ActionController::ActionControllerError, - "Unknown options: #{unknown_option_keys.join(', ')}" unless - unknown_option_keys.empty? - - options[:singular_name] ||= Inflector.singularize(collection_id.to_s) - options[:class_name] ||= Inflector.camelize(options[:singular_name]) - end - - # Returns a paginator and a collection of Active Record model instances - # for the paginator's current page. This is designed to be used in a - # single action; to automatically paginate multiple actions, consider - # ClassMethods#paginate. - # - # +options+ are: - # :singular_name:: the singular name to use, if it can't be inferred by - # singularizing the collection name - # :class_name:: the class name to use, if it can't be inferred by - # camelizing the singular name - # :per_page:: the maximum number of items to include in a - # single page. Defaults to 10 - # :conditions:: optional conditions passed to Model.find(:all, *params) and - # Model.count - # :order:: optional order parameter passed to Model.find(:all, *params) - # :order_by:: (deprecated, used :order) optional order parameter passed to Model.find(:all, *params) - # :joins:: optional joins parameter passed to Model.find(:all, *params) - # and Model.count - # :join:: (deprecated, used :joins or :include) optional join parameter passed to Model.find(:all, *params) - # and Model.count - # :include:: optional eager loading parameter passed to Model.find(:all, *params) - # and Model.count - # :select:: :select parameter passed to Model.find(:all, *params) - # - # :count:: parameter passed as :select option to Model.count(*params) - # - def paginate(collection_id, options={}) - Pagination.validate_options!(collection_id, options, true) - paginator_and_collection_for(collection_id, options) - end - - # These methods become class methods on any controller - module ClassMethods - # Creates a +before_filter+ which automatically paginates an Active - # Record model for all actions in a controller (or certain actions if - # specified with the :actions option). - # - # +options+ are the same as PaginationHelper#paginate, with the addition - # of: - # :actions:: an array of actions for which the pagination is - # active. Defaults to +nil+ (i.e., every action) - def paginate(collection_id, options={}) - Pagination.validate_options!(collection_id, options, false) - module_eval do - before_filter :create_paginators_and_retrieve_collections - OPTIONS[self] ||= Hash.new - OPTIONS[self][collection_id] = options - end - end - end - - def create_paginators_and_retrieve_collections #:nodoc: - Pagination::OPTIONS[self.class].each do |collection_id, options| - next unless options[:actions].include? action_name if - options[:actions] - - paginator, collection = - paginator_and_collection_for(collection_id, options) - - paginator_name = "@#{options[:singular_name]}_pages" - self.instance_variable_set(paginator_name, paginator) - - collection_name = "@#{collection_id.to_s}" - self.instance_variable_set(collection_name, collection) - end - end - - # Returns the total number of items in the collection to be paginated for - # the +model+ and given +conditions+. Override this method to implement a - # custom counter. - def count_collection_for_pagination(model, options) - model.count(:conditions => options[:conditions], - :joins => options[:join] || options[:joins], - :include => options[:include], - :select => options[:count]) - end - - # Returns a collection of items for the given +model+ and +options[conditions]+, - # ordered by +options[order]+, for the current page in the given +paginator+. - # Override this method to implement a custom finder. - def find_collection_for_pagination(model, options, paginator) - model.find(:all, :conditions => options[:conditions], - :order => options[:order_by] || options[:order], - :joins => options[:join] || options[:joins], :include => options[:include], - :select => options[:select], :limit => options[:per_page], - :offset => paginator.current.offset) - end - - protected :create_paginators_and_retrieve_collections, - :count_collection_for_pagination, - :find_collection_for_pagination - - def paginator_and_collection_for(collection_id, options) #:nodoc: - klass = options[:class_name].constantize - page = @params[options[:parameter]] - count = count_collection_for_pagination(klass, options) - paginator = Paginator.new(self, count, options[:per_page], page) - collection = find_collection_for_pagination(klass, options, paginator) - - return paginator, collection - end - - private :paginator_and_collection_for - - # A class representing a paginator for an Active Record collection. - class Paginator - include Enumerable - - # Creates a new Paginator on the given +controller+ for a set of items - # of size +item_count+ and having +items_per_page+ items per page. - # Raises ArgumentError if items_per_page is out of bounds (i.e., less - # than or equal to zero). The page CGI parameter for links defaults to - # "page" and can be overridden with +page_parameter+. - def initialize(controller, item_count, items_per_page, current_page=1) - raise ArgumentError, 'must have at least one item per page' if - items_per_page <= 0 - - @controller = controller - @item_count = item_count || 0 - @items_per_page = items_per_page - @pages = {} - - self.current_page = current_page - end - attr_reader :controller, :item_count, :items_per_page - - # Sets the current page number of this paginator. If +page+ is a Page - # object, its +number+ attribute is used as the value; if the page does - # not belong to this Paginator, an ArgumentError is raised. - def current_page=(page) - if page.is_a? Page - raise ArgumentError, 'Page/Paginator mismatch' unless - page.paginator == self - end - page = page.to_i - @current_page_number = has_page_number?(page) ? page : 1 - end - - # Returns a Page object representing this paginator's current page. - def current_page - @current_page ||= self[@current_page_number] - end - alias current :current_page - - # Returns a new Page representing the first page in this paginator. - def first_page - @first_page ||= self[1] - end - alias first :first_page - - # Returns a new Page representing the last page in this paginator. - def last_page - @last_page ||= self[page_count] - end - alias last :last_page - - # Returns the number of pages in this paginator. - def page_count - @page_count ||= @item_count.zero? ? 1 : - (q,r=@item_count.divmod(@items_per_page); r==0? q : q+1) - end - - alias length :page_count - - # Returns true if this paginator contains the page of index +number+. - def has_page_number?(number) - number >= 1 and number <= page_count - end - - # Returns a new Page representing the page with the given index - # +number+. - def [](number) - @pages[number] ||= Page.new(self, number) - end - - # Successively yields all the paginator's pages to the given block. - def each(&block) - page_count.times do |n| - yield self[n+1] - end - end - - # A class representing a single page in a paginator. - class Page - include Comparable - - # Creates a new Page for the given +paginator+ with the index - # +number+. If +number+ is not in the range of valid page numbers or - # is not a number at all, it defaults to 1. - def initialize(paginator, number) - @paginator = paginator - @number = number.to_i - @number = 1 unless @paginator.has_page_number? @number - end - attr_reader :paginator, :number - alias to_i :number - - # Compares two Page objects and returns true when they represent the - # same page (i.e., their paginators are the same and they have the - # same page number). - def ==(page) - return false if page.nil? - @paginator == page.paginator and - @number == page.number - end - - # Compares two Page objects and returns -1 if the left-hand page comes - # before the right-hand page, 0 if the pages are equal, and 1 if the - # left-hand page comes after the right-hand page. Raises ArgumentError - # if the pages do not belong to the same Paginator object. - def <=>(page) - raise ArgumentError unless @paginator == page.paginator - @number <=> page.number - end - - # Returns the item offset for the first item in this page. - def offset - @paginator.items_per_page * (@number - 1) - end - - # Returns the number of the first item displayed. - def first_item - offset + 1 - end - - # Returns the number of the last item displayed. - def last_item - [@paginator.items_per_page * @number, @paginator.item_count].min - end - - # Returns true if this page is the first page in the paginator. - def first? - self == @paginator.first - end - - # Returns true if this page is the last page in the paginator. - def last? - self == @paginator.last - end - - # Returns a new Page object representing the page just before this - # page, or nil if this is the first page. - def previous - if first? then nil else @paginator[@number - 1] end - end - - # Returns a new Page object representing the page just after this - # page, or nil if this is the last page. - def next - if last? then nil else @paginator[@number + 1] end - end - - # Returns a new Window object for this page with the specified - # +padding+. - def window(padding=2) - Window.new(self, padding) - end - - # Returns the limit/offset array for this page. - def to_sql - [@paginator.items_per_page, offset] - end - - def to_param #:nodoc: - @number.to_s - end - end - - # A class for representing ranges around a given page. - class Window - # Creates a new Window object for the given +page+ with the specified - # +padding+. - def initialize(page, padding=2) - @paginator = page.paginator - @page = page - self.padding = padding - end - attr_reader :paginator, :page - - # Sets the window's padding (the number of pages on either side of the - # window page). - def padding=(padding) - @padding = padding < 0 ? 0 : padding - # Find the beginning and end pages of the window - @first = @paginator.has_page_number?(@page.number - @padding) ? - @paginator[@page.number - @padding] : @paginator.first - @last = @paginator.has_page_number?(@page.number + @padding) ? - @paginator[@page.number + @padding] : @paginator.last - end - attr_reader :padding, :first, :last - - # Returns an array of Page objects in the current window. - def pages - (@first.number..@last.number).to_a.collect! {|n| @paginator[n]} - end - alias to_a :pages - end - end - - end -end -module ActionController - # Subclassing AbstractRequest makes these methods available to the request objects used in production and testing, - # CgiRequest and TestRequest - class AbstractRequest - cattr_accessor :relative_url_root - - # Returns the hash of environment variables for this request, - # such as { 'RAILS_ENV' => 'production' }. - attr_reader :env - - # Returns both GET and POST parameters in a single hash. - def parameters - @parameters ||= request_parameters.update(query_parameters).update(path_parameters).with_indifferent_access - end - - # Returns the HTTP request method as a lowercase symbol (:get, for example) - def method - @request_method ||= @env['REQUEST_METHOD'].downcase.to_sym - end - - # Is this a GET request? Equivalent to request.method == :get - def get? - method == :get - end - - # Is this a POST request? Equivalent to request.method == :post - def post? - method == :post - end - - # Is this a PUT request? Equivalent to request.method == :put - def put? - method == :put - end - - # Is this a DELETE request? Equivalent to request.method == :delete - def delete? - method == :delete - end - - # Is this a HEAD request? Equivalent to request.method == :head - def head? - method == :head - end - - # Determine whether the body of a HTTP call is URL-encoded (default) - # or matches one of the registered param_parsers. - # - # For backward compatibility, the post format is extracted from the - # X-Post-Data-Format HTTP header if present. - def content_type - @content_type ||= - begin - content_type = @env['CONTENT_TYPE'].to_s.downcase - - if x_post_format = @env['HTTP_X_POST_DATA_FORMAT'] - case x_post_format.to_s.downcase - when 'yaml' - content_type = 'application/x-yaml' - when 'xml' - content_type = 'application/xml' - end - end - - Mime::Type.lookup(content_type) - end - end - - # Returns the accepted MIME type for the request - def accepts - @accepts ||= - if @env['HTTP_ACCEPT'].to_s.strip.empty? - [ content_type, Mime::ALL ] - else - Mime::Type.parse(@env['HTTP_ACCEPT']) - end - end - - # Returns true if the request's "X-Requested-With" header contains - # "XMLHttpRequest". (The Prototype Javascript library sends this header with - # every Ajax request.) - def xml_http_request? - not /XMLHttpRequest/i.match(@env['HTTP_X_REQUESTED_WITH']).nil? - end - alias xhr? :xml_http_request? - - # Determine originating IP address. REMOTE_ADDR is the standard - # but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or - # HTTP_X_FORWARDED_FOR are set by proxies so check for these before - # falling back to REMOTE_ADDR. HTTP_X_FORWARDED_FOR may be a comma- - # delimited list in the case of multiple chained proxies; the first is - # the originating IP. - def remote_ip - return @env['HTTP_CLIENT_IP'] if @env.include? 'HTTP_CLIENT_IP' - - if @env.include? 'HTTP_X_FORWARDED_FOR' then - remote_ips = @env['HTTP_X_FORWARDED_FOR'].split(',').reject do |ip| - ip =~ /^unknown$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\./i - end - - return remote_ips.first.strip unless remote_ips.empty? - end - - @env['REMOTE_ADDR'] - end - - # Returns the domain part of a host, such as rubyonrails.org in "www.rubyonrails.org". You can specify - # a different tld_length, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk". - def domain(tld_length = 1) - return nil if !/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/.match(host).nil? or host.nil? - - host.split('.').last(1 + tld_length).join('.') - end - - # Returns all the subdomains as an array, so ["dev", "www"] would be returned for "dev.www.rubyonrails.org". - # You can specify a different tld_length, such as 2 to catch ["www"] instead of ["www", "rubyonrails"] - # in "www.rubyonrails.co.uk". - def subdomains(tld_length = 1) - return [] unless host - parts = host.split('.') - parts[0..-(tld_length+2)] - end - - # Receive the raw post data. - # This is useful for services such as REST, XMLRPC and SOAP - # which communicate over HTTP POST but don't use the traditional parameter format. - def raw_post - @env['RAW_POST_DATA'] - end - - # Returns the request URI correctly, taking into account the idiosyncracies - # of the various servers. - def request_uri - if uri = @env['REQUEST_URI'] - (%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri # Remove domain, which webrick puts into the request_uri. - else # REQUEST_URI is blank under IIS - get this from PATH_INFO and SCRIPT_NAME - script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$}) - uri = @env['PATH_INFO'] - uri = uri.sub(/#{script_filename}\//, '') unless script_filename.nil? - unless (env_qs = @env['QUERY_STRING']).nil? || env_qs.empty? - uri << '?' << env_qs - end - uri - end - end - - # Return 'https://' if this is an SSL request and 'http://' otherwise. - def protocol - ssl? ? 'https://' : 'http://' - end - - # Is this an SSL request? - def ssl? - @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https' - end - - # Returns the interpreted path to requested resource after all the installation directory of this application was taken into account - def path - path = (uri = request_uri) ? uri.split('?').first : '' - - # Cut off the path to the installation directory if given - root = relative_url_root - path[0, root.length] = '' if root - path || '' - end - - # Returns the path minus the web server relative installation directory. - # This can be set with the environment variable RAILS_RELATIVE_URL_ROOT. - # It can be automatically extracted for Apache setups. If the server is not - # Apache, this method returns an empty string. - def relative_url_root - @@relative_url_root ||= case - when @env["RAILS_RELATIVE_URL_ROOT"] - @env["RAILS_RELATIVE_URL_ROOT"] - when server_software == 'apache' - @env["SCRIPT_NAME"].to_s.sub(/\/dispatch\.(fcgi|rb|cgi)$/, '') - else - '' - end - end - - # Returns the port number of this request as an integer. - def port - @port_as_int ||= @env['SERVER_PORT'].to_i - end - - # Returns the standard port number for this request's protocol - def standard_port - case protocol - when 'https://' then 443 - else 80 - end - end - - # Returns a port suffix like ":8080" if the port number of this request - # is not the default HTTP port 80 or HTTPS port 443. - def port_string - (port == standard_port) ? '' : ":#{port}" - end - - # Returns a host:port string for this request, such as example.com or - # example.com:8080. - def host_with_port - host + port_string - end - - def path_parameters=(parameters) #:nodoc: - @path_parameters = parameters - @symbolized_path_parameters = @parameters = nil - end - - # The same as path_parameters with explicitly symbolized keys - def symbolized_path_parameters - @symbolized_path_parameters ||= path_parameters.symbolize_keys - end - - # Returns a hash with the parameters used to form the path of the request - # - # Example: - # - # {:action => 'my_action', :controller => 'my_controller'} - def path_parameters - @path_parameters ||= {} - end - - # Returns the lowercase name of the HTTP server software. - def server_software - (@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil - end - - #-- - # Must be implemented in the concrete request - #++ - def query_parameters #:nodoc: - end - - def request_parameters #:nodoc: - end - - # Returns the host for this request, such as example.com. - def host - end - - def cookies #:nodoc: - end - - def session #:nodoc: - end - - def session=(session) #:nodoc: - @session = session - end - - def reset_session #:nodoc: - end - end -end -module ActionController #:nodoc: - # Actions that fail to perform as expected throw exceptions. These exceptions can either be rescued for the public view - # (with a nice user-friendly explanation) or for the developers view (with tons of debugging information). The developers view - # is already implemented by the Action Controller, but the public view should be tailored to your specific application. So too - # could the decision on whether something is a public or a developer request. - # - # You can tailor the rescuing behavior and appearance by overwriting the following two stub methods. - module Rescue - def self.append_features(base) #:nodoc: - super - base.extend(ClassMethods) - base.class_eval do - alias_method :perform_action_without_rescue, :perform_action - alias_method :perform_action, :perform_action_with_rescue - end - end - - module ClassMethods #:nodoc: - def process_with_exception(request, response, exception) - new.process(request, response, :rescue_action, exception) - end - end - - protected - # Exception handler called when the performance of an action raises an exception. - def rescue_action(exception) - log_error(exception) if logger - erase_results if performed? - - if consider_all_requests_local || local_request? - rescue_action_locally(exception) - else - rescue_action_in_public(exception) - end - end - - # Overwrite to implement custom logging of errors. By default logs as fatal. - def log_error(exception) #:doc: - if ActionView::TemplateError === exception - logger.fatal(exception.to_s) - else - logger.fatal( - "\n\n#{exception.class} (#{exception.message}):\n " + - clean_backtrace(exception).join("\n ") + - "\n\n" - ) - end - end - - # Overwrite to implement public exception handling (for requests answering false to local_request?). - def rescue_action_in_public(exception) #:doc: - case exception - when RoutingError, UnknownAction then - render_text(IO.read(File.join(RAILS_ROOT, 'public', '404.html')), "404 Not Found") - else render_text "

Application error (Rails)

" - end - end - - # Overwrite to expand the meaning of a local request in order to show local rescues on other occurrences than - # the remote IP being 127.0.0.1. For example, this could include the IP of the developer machine when debugging - # remotely. - def local_request? #:doc: - [@request.remote_addr, @request.remote_ip] == ["127.0.0.1"] * 2 - end - - # Renders a detailed diagnostics screen on action exceptions. - def rescue_action_locally(exception) - add_variables_to_assigns - @template.instance_variable_set("@exception", exception) - @template.instance_variable_set("@rescues_path", File.dirname(__FILE__) + "/templates/rescues/") - @template.send(:assign_variables_from_controller) - - @template.instance_variable_set("@contents", @template.render_file(template_path_for_local_rescue(exception), false)) - - @headers["Content-Type"] = "text/html" - render_file(rescues_path("layout"), response_code_for_rescue(exception)) - end - - private - def perform_action_with_rescue #:nodoc: - begin - perform_action_without_rescue - rescue Object => exception - if defined?(Breakpoint) && @params["BP-RETRY"] - msg = exception.backtrace.first - if md = /^(.+?):(\d+)(?::in `(.+)')?$/.match(msg) then - origin_file, origin_line = md[1], md[2].to_i - - set_trace_func(lambda do |type, file, line, method, context, klass| - if file == origin_file and line == origin_line then - set_trace_func(nil) - @params["BP-RETRY"] = false - - callstack = caller - callstack.slice!(0) if callstack.first["rescue.rb"] - file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures - - message = "Exception at #{file}:#{line}#{" in `#{method}'" if method}." # `´ ( for ruby-mode) - - Breakpoint.handle_breakpoint(context, message, file, line) - end - end) - - retry - end - end - - rescue_action(exception) - end - end - - def rescues_path(template_name) - File.dirname(__FILE__) + "/templates/rescues/#{template_name}.rhtml" - end - - def template_path_for_local_rescue(exception) - rescues_path( - case exception - when MissingTemplate then "missing_template" - when RoutingError then "routing_error" - when UnknownAction then "unknown_action" - when ActionView::TemplateError then "template_error" - else "diagnostics" - end - ) - end - - def response_code_for_rescue(exception) - case exception - when UnknownAction, RoutingError then "404 Page Not Found" - else "500 Internal Error" - end - end - - def clean_backtrace(exception) - exception.backtrace.collect { |line| Object.const_defined?(:RAILS_ROOT) ? line.gsub(RAILS_ROOT, "") : line } - end - end -end -module ActionController - class AbstractResponse #:nodoc: - DEFAULT_HEADERS = { "Cache-Control" => "no-cache" } - attr_accessor :body, :headers, :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params - - def initialize - @body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], [] - end - - def redirect(to_url, permanently = false) - @headers["Status"] = "302 Found" unless @headers["Status"] == "301 Moved Permanently" - @headers["location"] = to_url - - @body = "You are being redirected." - end - end -endmodule ActionController - module Routing #:nodoc: - class << self - def expiry_hash(options, recall) - k = v = nil - expire_on = {} - options.each {|k, v| expire_on[k] = ((rcv = recall[k]) && (rcv != v))} - expire_on - end - - def extract_parameter_value(parameter) #:nodoc: - CGI.escape((parameter.respond_to?(:to_param) ? parameter.to_param : parameter).to_s) - end - def controller_relative_to(controller, previous) - if controller.nil? then previous - elsif controller[0] == ?/ then controller[1..-1] - elsif %r{^(.*)/} =~ previous then "#{$1}/#{controller}" - else controller - end - end - - def treat_hash(hash, keys_to_delete = []) - k = v = nil - hash.each do |k, v| - if v then hash[k] = (v.respond_to? :to_param) ? v.to_param.to_s : v.to_s - else - hash.delete k - keys_to_delete << k - end - end - hash - end - - def test_condition(expression, condition) - case condition - when String then "(#{expression} == #{condition.inspect})" - when Regexp then - condition = Regexp.new("^#{condition.source}$") unless /^\^.*\$$/ =~ condition.source - "(#{condition.inspect} =~ #{expression})" - when Array then - conds = condition.collect do |condition| - cond = test_condition(expression, condition) - (cond[0, 1] == '(' && cond[-1, 1] == ')') ? cond : "(#{cond})" - end - "(#{conds.join(' || ')})" - when true then expression - when nil then "! #{expression}" - else - raise ArgumentError, "Valid criteria are strings, regular expressions, true, or nil" - end - end - end - - class Component #:nodoc: - def dynamic?() false end - def optional?() false end - - def key() nil end - - def self.new(string, *args) - return super(string, *args) unless self == Component - case string - when ':controller' then ControllerComponent.new(:controller, *args) - when /^:(\w+)$/ then DynamicComponent.new($1, *args) - when /^\*(\w+)$/ then PathComponent.new($1, *args) - else StaticComponent.new(string, *args) - end - end - end - - class StaticComponent < Component #:nodoc: - attr_reader :value - - def initialize(value) - @value = value - end - - def write_recognition(g) - g.if_next_matches(value) do |gp| - gp.move_forward {|gpp| gpp.continue} - end - end - - def write_generation(g) - g.add_segment(value) {|gp| gp.continue } - end - end - - class DynamicComponent < Component #:nodoc: - attr_reader :key, :default - attr_accessor :condition - - def dynamic?() true end - def optional?() @optional end - - def default=(default) - @optional = true - @default = default - end - - def initialize(key, options = {}) - @key = key.to_sym - @optional = false - default, @condition = options[:default], options[:condition] - self.default = default if options.key?(:default) - end - - def default_check(g) - presence = "#{g.hash_value(key, !! default)}" - if default - "!(#{presence} && #{g.hash_value(key, false)} != #{default.to_s.inspect})" - else - "! #{presence}" - end - end - - def write_generation(g) - wrote_dropout = write_dropout_generation(g) - write_continue_generation(g, wrote_dropout) - end - - def write_dropout_generation(g) - return false unless optional? && g.after.all? {|c| c.optional?} - - check = [default_check(g)] - gp = g.dup # Use another generator to write the conditions after the first && - # We do this to ensure that the generator will not assume x_value is set. It will - # not be set if it follows a false condition -- for example, false && (x = 2) - - check += gp.after.map {|c| c.default_check gp} - gp.if(check.join(' && ')) { gp.finish } # If this condition is met, we stop here - true - end - - def write_continue_generation(g, use_else) - test = Routing.test_condition(g.hash_value(key, true, default), condition || true) - check = (use_else && condition.nil? && default) ? [:else] : [use_else ? :elsif : :if, test] - - g.send(*check) do |gp| - gp.expire_for_keys(key) unless gp.after.empty? - add_segments_to(gp) {|gpp| gpp.continue} - end - end - - def add_segments_to(g) - g.add_segment(%(\#{CGI.escape(#{g.hash_value(key, true, default)})})) {|gp| yield gp} - end - - def recognition_check(g) - test_type = [true, nil].include?(condition) ? :presence : :constraint - - prefix = condition.is_a?(Regexp) ? "#{g.next_segment(true)} && " : '' - check = prefix + Routing.test_condition(g.next_segment(true), condition || true) - - g.if(check) {|gp| yield gp, test_type} - end - - def write_recognition(g) - test_type = nil - recognition_check(g) do |gp, test_type| - assign_result(gp) {|gpp| gpp.continue} - end - - if optional? && g.after.all? {|c| c.optional?} - call = (test_type == :presence) ? [:else] : [:elsif, "! #{g.next_segment(true)}"] - - g.send(*call) do |gp| - assign_default(gp) - gp.after.each {|c| c.assign_default(gp)} - gp.finish(false) - end - end - end - - def assign_result(g, with_default = false) - g.result key, "CGI.unescape(#{g.next_segment(true, with_default ? default : nil)})" - g.move_forward {|gp| yield gp} - end - - def assign_default(g) - g.constant_result key, default unless default.nil? - end - end - - class ControllerComponent < DynamicComponent #:nodoc: - def key() :controller end - - def add_segments_to(g) - g.add_segment(%(\#{#{g.hash_value(key, true, default)}})) {|gp| yield gp} - end - - def recognition_check(g) - g << "controller_result = ::ActionController::Routing::ControllerComponent.traverse_to_controller(#{g.path_name}, #{g.index_name})" - g.if('controller_result') do |gp| - gp << 'controller_value, segments_to_controller = controller_result' - if condition - gp << "controller_path = #{gp.path_name}[#{gp.index_name},segments_to_controller].join('/')" - gp.if(Routing.test_condition("controller_path", condition)) do |gpp| - gpp.move_forward('segments_to_controller') {|gppp| yield gppp, :constraint} - end - else - gp.move_forward('segments_to_controller') {|gpp| yield gpp, :constraint} - end - end - end - - def assign_result(g) - g.result key, 'controller_value' - yield g - end - - def assign_default(g) - ControllerComponent.assign_controller(g, default) - end - - class << self - def assign_controller(g, controller) - expr = "::#{controller.split('/').collect {|c| c.camelize}.join('::')}Controller" - g.result :controller, expr, true - end - - def traverse_to_controller(segments, start_at = 0) - mod = ::Object - length = segments.length - index = start_at - mod_name = controller_name = segment = nil - while index < length - return nil unless /\A[A-Za-z][A-Za-z\d_]*\Z/ =~ (segment = segments[index]) - index += 1 - - mod_name = segment.camelize - controller_name = "#{mod_name}Controller" - path_suffix = File.join(segments[start_at..(index - 1)]) - next_mod = nil - - # If the controller is already present, or if we load it, return it. - if mod.const_defined?(controller_name) || attempt_load(mod, controller_name, path_suffix + "_controller") == :defined - controller = mod.const_get(controller_name) - return nil unless controller.is_a?(Class) && controller.ancestors.include?(ActionController::Base) # it's not really a controller? - return [controller, (index - start_at)] - end - - # No controller? Look for the module - if mod.const_defined? mod_name - next_mod = mod.send(:const_get, mod_name) - next_mod = nil unless next_mod.is_a?(Module) - else - # Try to load a file that defines the module we want. - case attempt_load(mod, mod_name, path_suffix) - when :defined then next_mod = mod.const_get mod_name - when :dir then # We didn't find a file, but there's a dir. - next_mod = Module.new # So create a module for the directory - mod.send :const_set, mod_name, next_mod - else - return nil - end - end - mod = next_mod - - return nil unless mod && mod.is_a?(Module) - end - nil - end - - protected - def safe_load_paths #:nodoc: - if defined?(RAILS_ROOT) - $LOAD_PATH.select do |base| - base = File.expand_path(base) - extended_root = File.expand_path(RAILS_ROOT) - # Exclude all paths that are not nested within app, lib, or components. - base.match(/\A#{Regexp.escape(extended_root)}\/*(app|lib|components)\/[a-z]/) || base =~ %r{rails-[\d.]+/builtin} - end - else - $LOAD_PATH - end - end - - def attempt_load(mod, const_name, path) - has_dir = false - safe_load_paths.each do |load_path| - full_path = File.join(load_path, path) - file_path = full_path + '.rb' - if File.file?(file_path) # Found a .rb file? Load it up - require_dependency(file_path) - return :defined if mod.const_defined? const_name - else - has_dir ||= File.directory?(full_path) - end - end - return (has_dir ? :dir : nil) - end - end - end - - class PathComponent < DynamicComponent #:nodoc: - def optional?() true end - def default() [] end - def condition() nil end - - def default=(value) - raise RoutingError, "All path components have an implicit default of []" unless value == [] - end - - def write_generation(g) - raise RoutingError, 'Path components must occur last' unless g.after.empty? - g.if("#{g.hash_value(key, true)} && ! #{g.hash_value(key, true)}.empty?") do - g << "#{g.hash_value(key, true)} = #{g.hash_value(key, true)}.join('/') unless #{g.hash_value(key, true)}.is_a?(String)" - g.add_segment("\#{CGI.escape_skipping_slashes(#{g.hash_value(key, true)})}") {|gp| gp.finish } - end - g.else { g.finish } - end - - def write_recognition(g) - raise RoutingError, "Path components must occur last" unless g.after.empty? - - start = g.index_name - start = "(#{start})" unless /^\w+$/ =~ start - - value_expr = "#{g.path_name}[#{start}..-1] || []" - g.result key, "ActionController::Routing::PathComponent::Result.new_escaped(#{value_expr})" - g.finish(false) - end - - class Result < ::Array #:nodoc: - def to_s() join '/' end - def self.new_escaped(strings) - new strings.collect {|str| CGI.unescape str} - end - end - end - - class Route #:nodoc: - attr_accessor :components, :known - attr_reader :path, :options, :keys, :defaults - - def initialize(path, options = {}) - @path, @options = path, options - - initialize_components path - defaults, conditions = initialize_hashes options.dup - @defaults = defaults.dup - configure_components(defaults, conditions) - add_default_requirements - initialize_keys - end - - def inspect - "<#{self.class} #{path.inspect}, #{options.inspect[1..-1]}>" - end - - def write_generation(generator = CodeGeneration::GenerationGenerator.new) - generator.before, generator.current, generator.after = [], components.first, (components[1..-1] || []) - - if known.empty? then generator.go - else - # Alter the conditions to allow :action => 'index' to also catch :action => nil - altered_known = known.collect do |k, v| - if k == :action && v== 'index' then [k, [nil, 'index']] - else [k, v] - end - end - generator.if(generator.check_conditions(altered_known)) {|gp| gp.go } - end - - generator - end - - def write_recognition(generator = CodeGeneration::RecognitionGenerator.new) - g = generator.dup - g.share_locals_with generator - g.before, g.current, g.after = [], components.first, (components[1..-1] || []) - - known.each do |key, value| - if key == :controller then ControllerComponent.assign_controller(g, value) - else g.constant_result(key, value) - end - end - - g.go - - generator - end - - def initialize_keys - @keys = (components.collect {|c| c.key} + known.keys).compact - @keys.freeze - end - - def extra_keys(options) - options.keys - @keys - end - - def matches_controller?(controller) - if known[:controller] then known[:controller] == controller - else - c = components.find {|c| c.key == :controller} - return false unless c - return c.condition.nil? || eval(Routing.test_condition('controller', c.condition)) - end - end - - protected - def initialize_components(path) - path = path.split('/') if path.is_a? String - path.shift if path.first.blank? - self.components = path.collect {|str| Component.new str} - end - - def initialize_hashes(options) - path_keys = components.collect {|c| c.key }.compact - self.known = {} - defaults = options.delete(:defaults) || {} - conditions = options.delete(:require) || {} - conditions.update(options.delete(:requirements) || {}) - - options.each do |k, v| - if path_keys.include?(k) then (v.is_a?(Regexp) ? conditions : defaults)[k] = v - else known[k] = v - end - end - [defaults, conditions] - end - - def configure_components(defaults, conditions) - components.each do |component| - if defaults.key?(component.key) then component.default = defaults[component.key] - elsif component.key == :action then component.default = 'index' - elsif component.key == :id then component.default = nil - end - - component.condition = conditions[component.key] if conditions.key?(component.key) - end - end - - def add_default_requirements - component_keys = components.collect {|c| c.key} - known[:action] ||= 'index' unless component_keys.include? :action - end - end - - class RouteSet #:nodoc: - attr_reader :routes, :categories, :controller_to_selector - def initialize - @routes = [] - @generation_methods = Hash.new(:generate_default_path) - end - - def generate(options, request_or_recall_hash = {}) - recall = request_or_recall_hash.is_a?(Hash) ? request_or_recall_hash : request_or_recall_hash.symbolized_path_parameters - use_recall = true - - controller = options[:controller] - options[:action] ||= 'index' if controller - recall_controller = recall[:controller] - if (recall_controller && recall_controller.include?(?/)) || (controller && controller.include?(?/)) - recall = {} if controller && controller[0] == ?/ - options[:controller] = Routing.controller_relative_to(controller, recall_controller) - end - options = recall.dup if options.empty? # XXX move to url_rewriter? - - keys_to_delete = [] - Routing.treat_hash(options, keys_to_delete) - - merged = recall.merge(options) - keys_to_delete.each {|key| merged.delete key} - expire_on = Routing.expiry_hash(options, recall) - - generate_path(merged, options, expire_on) - end - - def generate_path(merged, options, expire_on) - send @generation_methods[merged[:controller]], merged, options, expire_on - end - def generate_default_path(*args) - write_generation - generate_default_path(*args) - end - - def write_generation - method_sources = [] - @generation_methods = Hash.new(:generate_default_path) - categorize_routes.each do |controller, routes| - next unless routes.length < @routes.length - - ivar = controller.gsub('/', '__') - method_name = "generate_path_for_#{ivar}".to_sym - instance_variable_set "@#{ivar}", routes - code = generation_code_for(ivar, method_name).to_s - method_sources << code - - filename = "generated_code/routing/generation_for_controller_#{controller}.rb" - eval(code, nil, filename) - - @generation_methods[controller.to_s] = method_name - @generation_methods[controller.to_sym] = method_name - end - - code = generation_code_for('routes', 'generate_default_path').to_s - eval(code, nil, 'generated_code/routing/generation.rb') - - return (method_sources << code) - end - - def recognize(request) - string_path = request.path - string_path.chomp! if string_path[0] == ?/ - path = string_path.split '/' - path.shift - - hash = recognize_path(path) - return recognition_failed(request) unless hash && hash['controller'] - - controller = hash['controller'] - hash['controller'] = controller.controller_path - request.path_parameters = hash - controller.new - end - alias :recognize! :recognize - - def recognition_failed(request) - raise ActionController::RoutingError, "Recognition failed for #{request.path.inspect}" - end - - def write_recognition - g = generator = CodeGeneration::RecognitionGenerator.new - g.finish_statement = Proc.new {|hash_expr| "return #{hash_expr}"} - - g.def "self.recognize_path(path)" do - each do |route| - g << 'index = 0' - route.write_recognition(g) - end - end - - eval g.to_s, nil, 'generated/routing/recognition.rb' - return g.to_s - end - - def generation_code_for(ivar = 'routes', method_name = nil) - routes = instance_variable_get('@' + ivar) - key_ivar = "@keys_for_#{ivar}" - instance_variable_set(key_ivar, routes.collect {|route| route.keys}) - - g = generator = CodeGeneration::GenerationGenerator.new - g.def "self.#{method_name}(merged, options, expire_on)" do - g << 'unused_count = options.length + 1' - g << "unused_keys = keys = options.keys" - g << 'path = nil' - - routes.each_with_index do |route, index| - g << "new_unused_keys = keys - #{key_ivar}[#{index}]" - g << 'new_path = (' - g.source.indent do - if index.zero? - g << "new_unused_count = new_unused_keys.length" - g << "hash = merged; not_expired = true" - route.write_generation(g.dup) - else - g.if "(new_unused_count = new_unused_keys.length) < unused_count" do |gp| - gp << "hash = merged; not_expired = true" - route.write_generation(gp) - end - end - end - g.source.lines.last << ' )' # Add the closing brace to the end line - g.if 'new_path' do - g << 'return new_path, [] if new_unused_count.zero?' - g << 'path = new_path; unused_keys = new_unused_keys; unused_count = new_unused_count' - end - end - - g << "raise RoutingError, \"No url can be generated for the hash \#{options.inspect}\" unless path" - g << "return path, unused_keys" - end - - return g - end - - def categorize_routes - @categorized_routes = by_controller = Hash.new(self) - - known_controllers.each do |name| - set = by_controller[name] = [] - each do |route| - set << route if route.matches_controller? name - end - end - - @categorized_routes - end - - def known_controllers - @routes.inject([]) do |known, route| - if (controller = route.known[:controller]) - if controller.is_a?(Regexp) - known << controller.source.scan(%r{[\w\d/]+}).select {|word| controller =~ word} - else known << controller - end - end - known - end.uniq - end - - def reload - NamedRoutes.clear - - if defined?(RAILS_ROOT) then load(File.join(RAILS_ROOT, 'config', 'routes.rb')) - else connect(':controller/:action/:id', :action => 'index', :id => nil) - end - - NamedRoutes.install - end - - def connect(*args) - new_route = Route.new(*args) - @routes << new_route - return new_route - end - - def draw - old_routes = @routes - @routes = [] - - begin yield self - rescue - @routes = old_routes - raise - end - write_generation - write_recognition - end - - def empty?() @routes.empty? end - - def each(&block) @routes.each(&block) end - - # Defines a new named route with the provided name and arguments. - # This method need only be used when you wish to use a name that a RouteSet instance - # method exists for, such as categories. - # - # For example, map.categories '/categories', :controller => 'categories' will not work - # due to RouteSet#categories. - def named_route(name, path, hash = {}) - route = connect(path, hash) - NamedRoutes.name_route(route, name) - route - end - - def method_missing(name, *args) - (1..2).include?(args.length) ? named_route(name, *args) : super(name, *args) - end - - def extra_keys(options, recall = {}) - generate(options.dup, recall).last - end - end - - module NamedRoutes #:nodoc: - Helpers = [] - class << self - def clear() Helpers.clear end - - def hash_access_name(name) - "hash_for_#{name}_url" - end - - def url_helper_name(name) - "#{name}_url" - end - - def known_hash_for_route(route) - hash = route.known.symbolize_keys - route.defaults.each do |key, value| - hash[key.to_sym] ||= value if value - end - hash[:controller] = "/#{hash[:controller]}" - - hash - end - - def define_hash_access_method(route, name) - hash = known_hash_for_route(route) - define_method(hash_access_name(name)) do |*args| - args.first ? hash.merge(args.first) : hash - end - end - - def name_route(route, name) - define_hash_access_method(route, name) - - module_eval(%{def #{url_helper_name name}(options = {}) - url_for(#{hash_access_name(name)}.merge(options)) - end}, "generated/routing/named_routes/#{name}.rb") - - protected url_helper_name(name), hash_access_name(name) - - Helpers << url_helper_name(name).to_sym - Helpers << hash_access_name(name).to_sym - Helpers.uniq! - end - - def install(cls = ActionController::Base) - cls.send :include, self - if cls.respond_to? :helper_method - Helpers.each do |helper_name| - cls.send :helper_method, helper_name - end - end - end - end - end - - Routes = RouteSet.new - end -end -module ActionController - module Scaffolding # :nodoc: - def self.append_features(base) - super - base.extend(ClassMethods) - end - - # Scaffolding is a way to quickly put an Active Record class online by providing a series of standardized actions - # for listing, showing, creating, updating, and destroying objects of the class. These standardized actions come - # with both controller logic and default templates that through introspection already know which fields to display - # and which input types to use. Example: - # - # class WeblogController < ActionController::Base - # scaffold :entry - # end - # - # This tiny piece of code will add all of the following methods to the controller: - # - # class WeblogController < ActionController::Base - # verify :method => :post, :only => [ :destroy, :create, :update ], - # :redirect_to => { :action => :list } - # - # def index - # list - # end - # - # def list - # @entries = Entry.find_all - # render_scaffold "list" - # end - # - # def show - # @entry = Entry.find(params[:id]) - # render_scaffold - # end - # - # def destroy - # Entry.find(params[:id]).destroy - # redirect_to :action => "list" - # end - # - # def new - # @entry = Entry.new - # render_scaffold - # end - # - # def create - # @entry = Entry.new(params[:entry]) - # if @entry.save - # flash[:notice] = "Entry was successfully created" - # redirect_to :action => "list" - # else - # render_scaffold('new') - # end - # end - # - # def edit - # @entry = Entry.find(params[:id]) - # render_scaffold - # end - # - # def update - # @entry = Entry.find(params[:id]) - # @entry.attributes = params[:entry] - # - # if @entry.save - # flash[:notice] = "Entry was successfully updated" - # redirect_to :action => "show", :id => @entry - # else - # render_scaffold('edit') - # end - # end - # end - # - # The render_scaffold method will first check to see if you've made your own template (like "weblog/show.rhtml" for - # the show action) and if not, then render the generic template for that action. This gives you the possibility of using the - # scaffold while you're building your specific application. Start out with a totally generic setup, then replace one template - # and one action at a time while relying on the rest of the scaffolded templates and actions. - module ClassMethods - # Adds a swath of generic CRUD actions to the controller. The +model_id+ is automatically converted into a class name unless - # one is specifically provide through options[:class_name]. So scaffold :post would use Post as the class - # and @post/@posts for the instance variables. - # - # It's possible to use more than one scaffold in a single controller by specifying options[:suffix] = true. This will - # make scaffold :post, :suffix => true use method names like list_post, show_post, and create_post - # instead of just list, show, and post. If suffix is used, then no index method is added. - def scaffold(model_id, options = {}) - options.assert_valid_keys(:class_name, :suffix) - - singular_name = model_id.to_s - class_name = options[:class_name] || singular_name.camelize - plural_name = singular_name.pluralize - suffix = options[:suffix] ? "_#{singular_name}" : "" - - unless options[:suffix] - module_eval <<-"end_eval", __FILE__, __LINE__ - def index - list - end - end_eval - end - - module_eval <<-"end_eval", __FILE__, __LINE__ - - verify :method => :post, :only => [ :destroy#{suffix}, :create#{suffix}, :update#{suffix} ], - :redirect_to => { :action => :list#{suffix} } - - - def list#{suffix} - @#{singular_name}_pages, @#{plural_name} = paginate :#{plural_name}, :per_page => 10 - render#{suffix}_scaffold "list#{suffix}" - end - - def show#{suffix} - @#{singular_name} = #{class_name}.find(params[:id]) - render#{suffix}_scaffold - end - - def destroy#{suffix} - #{class_name}.find(params[:id]).destroy - redirect_to :action => "list#{suffix}" - end - - def new#{suffix} - @#{singular_name} = #{class_name}.new - render#{suffix}_scaffold - end - - def create#{suffix} - @#{singular_name} = #{class_name}.new(params[:#{singular_name}]) - if @#{singular_name}.save - flash[:notice] = "#{class_name} was successfully created" - redirect_to :action => "list#{suffix}" - else - render#{suffix}_scaffold('new') - end - end - - def edit#{suffix} - @#{singular_name} = #{class_name}.find(params[:id]) - render#{suffix}_scaffold - end - - def update#{suffix} - @#{singular_name} = #{class_name}.find(params[:id]) - @#{singular_name}.attributes = params[:#{singular_name}] - - if @#{singular_name}.save - flash[:notice] = "#{class_name} was successfully updated" - redirect_to :action => "show#{suffix}", :id => @#{singular_name} - else - render#{suffix}_scaffold('edit') - end - end - - private - def render#{suffix}_scaffold(action=nil) - action ||= caller_method_name(caller) - # logger.info ("testing template:" + "\#{self.class.controller_path}/\#{action}") if logger - - if template_exists?("\#{self.class.controller_path}/\#{action}") - render_action(action) - else - @scaffold_class = #{class_name} - @scaffold_singular_name, @scaffold_plural_name = "#{singular_name}", "#{plural_name}" - @scaffold_suffix = "#{suffix}" - add_instance_variables_to_assigns - - @template.instance_variable_set("@content_for_layout", @template.render_file(scaffold_path(action.sub(/#{suffix}$/, "")), false)) - - if !active_layout.nil? - render_file(active_layout, nil, true) - else - render_file(scaffold_path("layout")) - end - end - end - - def scaffold_path(template_name) - File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml" - end - - def caller_method_name(caller) - caller.first.scan(/`(.*)'/).first.first # ' ruby-mode - end - end_eval - end - end - end -end -require 'cgi' -require 'cgi/session' -require 'digest/md5' -require 'base64' - -class CGI - class Session - # Return this session's underlying Session instance. Useful for the DB-backed session stores. - def model - @dbman.model if @dbman - end - - - # A session store backed by an Active Record class. A default class is - # provided, but any object duck-typing to an Active Record +Session+ class - # with text +session_id+ and +data+ attributes is sufficient. - # - # The default assumes a +sessions+ tables with columns: - # +id+ (numeric primary key), - # +session_id+ (text, or longtext if your session data exceeds 65K), and - # +data+ (text or longtext; careful if your session data exceeds 65KB). - # The +session_id+ column should always be indexed for speedy lookups. - # Session data is marshaled to the +data+ column in Base64 format. - # If the data you write is larger than the column's size limit, - # ActionController::SessionOverflowError will be raised. - # - # You may configure the table name, primary key, and data column. - # For example, at the end of config/environment.rb: - # CGI::Session::ActiveRecordStore::Session.table_name = 'legacy_session_table' - # CGI::Session::ActiveRecordStore::Session.primary_key = 'session_id' - # CGI::Session::ActiveRecordStore::Session.data_column_name = 'legacy_session_data' - # Note that setting the primary key to the session_id frees you from - # having a separate id column if you don't want it. However, you must - # set session.model.id = session.session_id by hand! A before_filter - # on ApplicationController is a good place. - # - # Since the default class is a simple Active Record, you get timestamps - # for free if you add +created_at+ and +updated_at+ datetime columns to - # the +sessions+ table, making periodic session expiration a snap. - # - # You may provide your own session class implementation, whether a - # feature-packed Active Record or a bare-metal high-performance SQL - # store, by setting - # +CGI::Session::ActiveRecordStore.session_class = MySessionClass+ - # You must implement these methods: - # self.find_by_session_id(session_id) - # initialize(hash_of_session_id_and_data) - # attr_reader :session_id - # attr_accessor :data - # save - # destroy - # - # The example SqlBypass class is a generic SQL session store. You may - # use it as a basis for high-performance database-specific stores. - class ActiveRecordStore - # The default Active Record class. - class Session < ActiveRecord::Base - # Customizable data column name. Defaults to 'data'. - cattr_accessor :data_column_name - self.data_column_name = 'data' - - before_save :marshal_data! - before_save :raise_on_session_data_overflow! - - class << self - # Don't try to reload ARStore::Session in dev mode. - def reloadable? #:nodoc: - false - end - - def data_column_size_limit - @data_column_size_limit ||= columns_hash[@@data_column_name].limit - end - - # Hook to set up sessid compatibility. - def find_by_session_id(session_id) - setup_sessid_compatibility! - find_by_session_id(session_id) - end - - def marshal(data) Base64.encode64(Marshal.dump(data)) if data end - def unmarshal(data) Marshal.load(Base64.decode64(data)) if data end - - def create_table! - connection.execute <<-end_sql - CREATE TABLE #{table_name} ( - id INTEGER PRIMARY KEY, - #{connection.quote_column_name('session_id')} TEXT UNIQUE, - #{connection.quote_column_name(@@data_column_name)} TEXT(255) - ) - end_sql - end - - def drop_table! - connection.execute "DROP TABLE #{table_name}" - end - - private - # Compatibility with tables using sessid instead of session_id. - def setup_sessid_compatibility! - # Reset column info since it may be stale. - reset_column_information - if columns_hash['sessid'] - def self.find_by_session_id(*args) - find_by_sessid(*args) - end - - define_method(:session_id) { sessid } - define_method(:session_id=) { |session_id| self.sessid = session_id } - else - def self.find_by_session_id(session_id) - find :first, :conditions => ["session_id #{attribute_condition(session_id)}", session_id] - end - end - end - end - - # Lazy-unmarshal session state. - def data - @data ||= self.class.unmarshal(read_attribute(@@data_column_name)) || {} - end - - # Has the session been loaded yet? - def loaded? - !! @data - end - - private - attr_writer :data - - def marshal_data! - return false if !loaded? - write_attribute(@@data_column_name, self.class.marshal(self.data)) - end - - # Ensures that the data about to be stored in the database is not - # larger than the data storage column. Raises - # ActionController::SessionOverflowError. - def raise_on_session_data_overflow! - return false if !loaded? - limit = self.class.data_column_size_limit - if loaded? and limit and read_attribute(@@data_column_name).size > limit - raise ActionController::SessionOverflowError - end - end - end - - # A barebones session store which duck-types with the default session - # store but bypasses Active Record and issues SQL directly. This is - # an example session model class meant as a basis for your own classes. - # - # The database connection, table name, and session id and data columns - # are configurable class attributes. Marshaling and unmarshaling - # are implemented as class methods that you may override. By default, - # marshaling data is +Base64.encode64(Marshal.dump(data))+ and - # unmarshaling data is +Marshal.load(Base64.decode64(data))+. - # - # This marshaling behavior is intended to store the widest range of - # binary session data in a +text+ column. For higher performance, - # store in a +blob+ column instead and forgo the Base64 encoding. - class SqlBypass - # Use the ActiveRecord::Base.connection by default. - cattr_accessor :connection - - # The table name defaults to 'sessions'. - cattr_accessor :table_name - @@table_name = 'sessions' - - # The session id field defaults to 'session_id'. - cattr_accessor :session_id_column - @@session_id_column = 'session_id' - - # The data field defaults to 'data'. - cattr_accessor :data_column - @@data_column = 'data' - - class << self - - def connection - @@connection ||= ActiveRecord::Base.connection - end - - # Look up a session by id and unmarshal its data if found. - def find_by_session_id(session_id) - if record = @@connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{@@connection.quote(session_id)}") - new(:session_id => session_id, :marshaled_data => record['data']) - end - end - - def marshal(data) Base64.encode64(Marshal.dump(data)) if data end - def unmarshal(data) Marshal.load(Base64.decode64(data)) if data end - - def create_table! - @@connection.execute <<-end_sql - CREATE TABLE #{table_name} ( - id INTEGER PRIMARY KEY, - #{@@connection.quote_column_name(session_id_column)} TEXT UNIQUE, - #{@@connection.quote_column_name(data_column)} TEXT - ) - end_sql - end - - def drop_table! - @@connection.execute "DROP TABLE #{table_name}" - end - end - - attr_reader :session_id - attr_writer :data - - # Look for normal and marshaled data, self.find_by_session_id's way of - # telling us to postpone unmarshaling until the data is requested. - # We need to handle a normal data attribute in case of a new record. - def initialize(attributes) - @session_id, @data, @marshaled_data = attributes[:session_id], attributes[:data], attributes[:marshaled_data] - @new_record = @marshaled_data.nil? - end - - def new_record? - @new_record - end - - # Lazy-unmarshal session state. - def data - unless @data - if @marshaled_data - @data, @marshaled_data = self.class.unmarshal(@marshaled_data) || {}, nil - else - @data = {} - end - end - @data - end - - def loaded? - !! @data - end - - def save - return false if !loaded? - marshaled_data = self.class.marshal(data) - - if @new_record - @new_record = false - @@connection.update <<-end_sql, 'Create session' - INSERT INTO #{@@table_name} ( - #{@@connection.quote_column_name(@@session_id_column)}, - #{@@connection.quote_column_name(@@data_column)} ) - VALUES ( - #{@@connection.quote(session_id)}, - #{@@connection.quote(marshaled_data)} ) - end_sql - else - @@connection.update <<-end_sql, 'Update session' - UPDATE #{@@table_name} - SET #{@@connection.quote_column_name(@@data_column)}=#{@@connection.quote(marshaled_data)} - WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)} - end_sql - end - end - - def destroy - unless @new_record - @@connection.delete <<-end_sql, 'Destroy session' - DELETE FROM #{@@table_name} - WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)} - end_sql - end - end - end - - - # The class used for session storage. Defaults to - # CGI::Session::ActiveRecordStore::Session. - cattr_accessor :session_class - self.session_class = Session - - # Find or instantiate a session given a CGI::Session. - def initialize(session, option = nil) - session_id = session.session_id - unless @session = ActiveRecord::Base.silence { @@session_class.find_by_session_id(session_id) } - unless session.new_session - raise CGI::Session::NoSession, 'uninitialized session' - end - @session = @@session_class.new(:session_id => session_id, :data => {}) - # session saving can be lazy again, because of improved component implementation - # therefore next line gets commented out: - # @session.save - end - end - - # Access the underlying session model. - def model - @session - end - - # Restore session state. The session model handles unmarshaling. - def restore - if @session - @session.data - end - end - - # Save session store. - def update - if @session - ActiveRecord::Base.silence { @session.save } - end - end - - # Save and close the session store. - def close - if @session - update - @session = nil - end - end - - # Delete and close the session store. - def delete - if @session - ActiveRecord::Base.silence { @session.destroy } - @session = nil - end - end - - protected - def logger - ActionController::Base.logger rescue nil - end - end - end -end -#!/usr/local/bin/ruby -w - -# This is a really simple session storage daemon, basically just a hash, -# which is enabled for DRb access. - -require 'drb' - -session_hash = Hash.new -session_hash.instance_eval { @mutex = Mutex.new } - -class <:only and - # :except clauses to restrict the subset, otherwise options - # apply to all actions on this controller. - # - # The session options are inheritable, as well, so if you specify them in - # a parent controller, they apply to controllers that extend the parent. - # - # Usage: - # - # # turn off session management for all actions. - # session :off - # - # # turn off session management for all actions _except_ foo and bar. - # session :off, :except => %w(foo bar) - # - # # turn off session management for only the foo and bar actions. - # session :off, :only => %w(foo bar) - # - # # the session will only work over HTTPS, but only for the foo action - # session :only => :foo, :session_secure => true - # - # # the session will only be disabled for 'foo', and only if it is - # # requested as a web service - # session :off, :only => :foo, - # :if => Proc.new { |req| req.parameters[:ws] } - # - # All session options described for ActionController::Base.process_cgi - # are valid arguments. - def session(*args) - options = Hash === args.last ? args.pop : {} - - options[:disabled] = true if !args.empty? - options[:only] = [*options[:only]].map { |o| o.to_s } if options[:only] - options[:except] = [*options[:except]].map { |o| o.to_s } if options[:except] - if options[:only] && options[:except] - raise ArgumentError, "only one of either :only or :except are allowed" - end - - write_inheritable_array("session_options", [options]) - end - - def cached_session_options #:nodoc: - @session_options ||= read_inheritable_attribute("session_options") || [] - end - - def session_options_for(request, action) #:nodoc: - if (session_options = cached_session_options).empty? - {} - else - options = {} - - action = action.to_s - session_options.each do |opts| - next if opts[:if] && !opts[:if].call(request) - if opts[:only] && opts[:only].include?(action) - options.merge!(opts) - elsif opts[:except] && !opts[:except].include?(action) - options.merge!(opts) - elsif !opts[:only] && !opts[:except] - options.merge!(opts) - end - end - - if options.empty? then options - else - options.delete :only - options.delete :except - options.delete :if - options[:disabled] ? false : options - end - end - end - end - - def process_with_session_management_support(request, response, method = :perform_action, *arguments) #:nodoc: - set_session_options(request) - process_without_session_management_support(request, response, method, *arguments) - end - - private - def set_session_options(request) - request.session_options = self.class.session_options_for(request, request.parameters["action"] || "index") - end - - def process_cleanup_with_session_management_support - process_cleanup_without_session_management_support - clear_persistent_model_associations - end - - # Clear cached associations in session data so they don't overflow - # the database field. Only applies to ActiveRecordStore since there - # is not a standard way to iterate over session data. - def clear_persistent_model_associations #:doc: - if defined?(@session) && @session.instance_variables.include?('@data') - session_data = @session.instance_variable_get('@data') - - if session_data && session_data.respond_to?(:each_value) - session_data.each_value do |obj| - obj.clear_association_cache if obj.respond_to?(:clear_association_cache) - end - end - end - end - end -end -module ActionController #:nodoc: - # Methods for sending files and streams to the browser instead of rendering. - module Streaming - DEFAULT_SEND_FILE_OPTIONS = { - :type => 'application/octet-stream'.freeze, - :disposition => 'attachment'.freeze, - :stream => true, - :buffer_size => 4096 - }.freeze - - protected - # Sends the file by streaming it 4096 bytes at a time. This way the - # whole file doesn't need to be read into memory at once. This makes - # it feasible to send even large files. - # - # Be careful to sanitize the path parameter if it coming from a web - # page. send_file(params[:path]) allows a malicious user to - # download any file on your server. - # - # Options: - # * :filename - suggests a filename for the browser to use. - # Defaults to File.basename(path). - # * :type - specifies an HTTP content type. - # Defaults to 'application/octet-stream'. - # * :disposition - specifies whether the file will be shown inline or downloaded. - # Valid values are 'inline' and 'attachment' (default). - # * :stream - whether to send the file to the user agent as it is read (true) - # or to read the entire file before sending (false). Defaults to true. - # * :buffer_size - specifies size (in bytes) of the buffer used to stream the file. - # Defaults to 4096. - # * :status - specifies the status code to send with the response. Defaults to '200 OK'. - # - # The default Content-Type and Content-Disposition headers are - # set to download arbitrary binary files in as many browsers as - # possible. IE versions 4, 5, 5.5, and 6 are all known to have - # a variety of quirks (especially when downloading over SSL). - # - # Simple download: - # send_file '/path/to.zip' - # - # Show a JPEG in the browser: - # send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline' - # - # Show a 404 page in the browser: - # send_file '/path/to/404.html, :type => 'text/html; charset=utf-8', :status => 404 - # - # Read about the other Content-* HTTP headers if you'd like to - # provide the user with more information (such as Content-Description). - # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 - # - # Also be aware that the document may be cached by proxies and browsers. - # The Pragma and Cache-Control headers declare how the file may be cached - # by intermediaries. They default to require clients to validate with - # the server before releasing cached responses. See - # http://www.mnot.net/cache_docs/ for an overview of web caching and - # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 - # for the Cache-Control header spec. - def send_file(path, options = {}) #:doc: - raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path) - - options[:length] ||= File.size(path) - options[:filename] ||= File.basename(path) - send_file_headers! options - - @performed_render = false - - if options[:stream] - render :status => options[:status], :text => Proc.new { |response, output| - logger.info "Streaming file #{path}" unless logger.nil? - len = options[:buffer_size] || 4096 - File.open(path, 'rb') do |file| - if output.respond_to?(:syswrite) - begin - while true - output.syswrite(file.sysread(len)) - end - rescue EOFError - end - else - while buf = file.read(len) - output.write(buf) - end - end - end - } - else - logger.info "Sending file #{path}" unless logger.nil? - File.open(path, 'rb') { |file| render :status => options[:status], :text => file.read } - end - end - - # Send binary data to the user as a file download. May set content type, apparent file name, - # and specify whether to show data inline or download as an attachment. - # - # Options: - # * :filename - Suggests a filename for the browser to use. - # * :type - specifies an HTTP content type. - # Defaults to 'application/octet-stream'. - # * :disposition - specifies whether the file will be shown inline or downloaded. - # * :status - specifies the status code to send with the response. Defaults to '200 OK'. - # Valid values are 'inline' and 'attachment' (default). - # - # Generic data download: - # send_data buffer - # - # Download a dynamically-generated tarball: - # send_data generate_tgz('dir'), :filename => 'dir.tgz' - # - # Display an image Active Record in the browser: - # send_data image.data, :type => image.content_type, :disposition => 'inline' - # - # See +send_file+ for more information on HTTP Content-* headers and caching. - def send_data(data, options = {}) #:doc: - logger.info "Sending data #{options[:filename]}" unless logger.nil? - send_file_headers! options.merge(:length => data.size) - @performed_render = false - render :status => options[:status], :text => data - end - - private - def send_file_headers!(options) - options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options)) - [:length, :type, :disposition].each do |arg| - raise ArgumentError, ":#{arg} option required" if options[arg].nil? - end - - disposition = options[:disposition].dup || 'attachment' - - disposition <<= %(; filename="#{options[:filename]}") if options[:filename] - - @headers.update( - 'Content-Length' => options[:length], - 'Content-Type' => options[:type].strip, # fixes a problem with extra '\r' with some browsers - 'Content-Disposition' => disposition, - 'Content-Transfer-Encoding' => 'binary' - ) - - # Fix a problem with IE 6.0 on opening downloaded files: - # If Cache-Control: no-cache is set (which Rails does by default), - # IE removes the file it just downloaded from its cache immediately - # after it displays the "open/save" dialog, which means that if you - # hit "open" the file isn't there anymore when the application that - # is called for handling the download is run, so let's workaround that - @headers['Cache-Control'] = 'private' if @headers['Cache-Control'] == 'no-cache' - end - end -end -require File.dirname(__FILE__) + '/assertions' -require File.dirname(__FILE__) + '/deprecated_assertions' - -module ActionController #:nodoc: - class Base - # Process a test request called with a +TestRequest+ object. - def self.process_test(request) - new.process_test(request) - end - - def process_test(request) #:nodoc: - process(request, TestResponse.new) - end - - def process_with_test(*args) - returning process_without_test(*args) do - add_variables_to_assigns - end - end - - alias_method :process_without_test, :process - alias_method :process, :process_with_test - end - - class TestRequest < AbstractRequest #:nodoc: - attr_accessor :cookies, :session_options - attr_accessor :query_parameters, :request_parameters, :path, :session, :env - attr_accessor :host - - def initialize(query_parameters = nil, request_parameters = nil, session = nil) - @query_parameters = query_parameters || {} - @request_parameters = request_parameters || {} - @session = session || TestSession.new - - initialize_containers - initialize_default_values - - super() - end - - def reset_session - @session = {} - end - - def raw_post - if raw_post = env['RAW_POST_DATA'] - raw_post - else - params = self.request_parameters.dup - %w(controller action only_path).each do |k| - params.delete(k) - params.delete(k.to_sym) - end - - params.map { |k,v| [ CGI.escape(k.to_s), CGI.escape(v.to_s) ].join('=') }.sort.join('&') - end - end - - def port=(number) - @env["SERVER_PORT"] = number.to_i - @port_as_int = nil - end - - def action=(action_name) - @query_parameters.update({ "action" => action_name }) - @parameters = nil - end - - # Used to check AbstractRequest's request_uri functionality. - # Disables the use of @path and @request_uri so superclass can handle those. - def set_REQUEST_URI(value) - @env["REQUEST_URI"] = value - @request_uri = nil - @path = nil - end - - def request_uri=(uri) - @request_uri = uri - @path = uri.split("?").first - end - - def remote_addr=(addr) - @env['REMOTE_ADDR'] = addr - end - - def remote_addr - @env['REMOTE_ADDR'] - end - - def request_uri - @request_uri || super() - end - - def path - @path || super() - end - - def assign_parameters(controller_path, action, parameters) - parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action) - extra_keys = ActionController::Routing::Routes.extra_keys(parameters) - non_path_parameters = get? ? query_parameters : request_parameters - parameters.each do |key, value| - if value.is_a? Fixnum - value = value.to_s - elsif value.is_a? Array - value = ActionController::Routing::PathComponent::Result.new(value) - end - - if extra_keys.include?(key.to_sym) - non_path_parameters[key] = value - else - path_parameters[key.to_s] = value - end - end - end - - def recycle! - self.request_parameters = {} - self.query_parameters = {} - self.path_parameters = {} - @request_method, @accepts, @content_type = nil, nil, nil - end - - private - def initialize_containers - @env, @cookies = {}, {} - end - - def initialize_default_values - @host = "test.host" - @request_uri = "/" - self.remote_addr = "0.0.0.0" - @env["SERVER_PORT"] = 80 - @env['REQUEST_METHOD'] = "GET" - end - end - - # A refactoring of TestResponse to allow the same behavior to be applied - # to the "real" CgiResponse class in integration tests. - module TestResponseBehavior #:nodoc: - # the response code of the request - def response_code - headers['Status'][0,3].to_i rescue 0 - end - - # returns a String to ensure compatibility with Net::HTTPResponse - def code - headers['Status'].to_s.split(' ')[0] - end - - def message - headers['Status'].to_s.split(' ',2)[1] - end - - # was the response successful? - def success? - response_code == 200 - end - - # was the URL not found? - def missing? - response_code == 404 - end - - # were we redirected? - def redirect? - (300..399).include?(response_code) - end - - # was there a server-side error? - def error? - (500..599).include?(response_code) - end - - alias_method :server_error?, :error? - - # returns the redirection location or nil - def redirect_url - redirect? ? headers['location'] : nil - end - - # does the redirect location match this regexp pattern? - def redirect_url_match?( pattern ) - return false if redirect_url.nil? - p = Regexp.new(pattern) if pattern.class == String - p = pattern if pattern.class == Regexp - return false if p.nil? - p.match(redirect_url) != nil - end - - # returns the template path of the file which was used to - # render this response (or nil) - def rendered_file(with_controller=false) - unless template.first_render.nil? - unless with_controller - template.first_render - else - template.first_render.split('/').last || template.first_render - end - end - end - - # was this template rendered by a file? - def rendered_with_file? - !rendered_file.nil? - end - - # a shortcut to the flash (or an empty hash if no flash.. hey! that rhymes!) - def flash - session['flash'] || {} - end - - # do we have a flash? - def has_flash? - !session['flash'].empty? - end - - # do we have a flash that has contents? - def has_flash_with_contents? - !flash.empty? - end - - # does the specified flash object exist? - def has_flash_object?(name=nil) - !flash[name].nil? - end - - # does the specified object exist in the session? - def has_session_object?(name=nil) - !session[name].nil? - end - - # a shortcut to the template.assigns - def template_objects - template.assigns || {} - end - - # does the specified template object exist? - def has_template_object?(name=nil) - !template_objects[name].nil? - end - - # Returns the response cookies, converted to a Hash of (name => CGI::Cookie) pairs - # Example: - # - # assert_equal ['AuthorOfNewPage'], r.cookies['author'].value - def cookies - headers['cookie'].inject({}) { |hash, cookie| hash[cookie.name] = cookie; hash } - end - - # Returns binary content (downloadable file), converted to a String - def binary_content - raise "Response body is not a Proc: #{body.inspect}" unless body.kind_of?(Proc) - require 'stringio' - - sio = StringIO.new - - begin - $stdout = sio - body.call - ensure - $stdout = STDOUT - end - - sio.rewind - sio.read - end - end - - class TestResponse < AbstractResponse #:nodoc: - include TestResponseBehavior - end - - class TestSession #:nodoc: - def initialize(attributes = {}) - @attributes = attributes - end - - def [](key) - @attributes[key] - end - - def []=(key, value) - @attributes[key] = value - end - - def session_id - "" - end - - def update() end - def close() end - def delete() @attributes = {} end - end - - # Essentially generates a modified Tempfile object similar to the object - # you'd get from the standard library CGI module in a multipart - # request. This means you can use an ActionController::TestUploadedFile - # object in the params of a test request in order to simulate - # a file upload. - # - # Usage example, within a functional test: - # post :change_avatar, :avatar => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/spongebob.png', 'image/png') - class TestUploadedFile - # The filename, *not* including the path, of the "uploaded" file - attr_reader :original_filename - - # The content type of the "uploaded" file - attr_reader :content_type - - def initialize(path, content_type = 'text/plain') - raise "file does not exist" unless File.exist?(path) - @content_type = content_type - @original_filename = path.sub(/^.*#{File::SEPARATOR}([^#{File::SEPARATOR}]+)$/) { $1 } - @tempfile = Tempfile.new(@original_filename) - FileUtils.copy_file(path, @tempfile.path) - end - - def path #:nodoc: - @tempfile.path - end - - alias local_path path - - def method_missing(method_name, *args, &block) #:nodoc: - @tempfile.send(method_name, *args, &block) - end - end - - module TestProcess - def self.included(base) - # execute the request simulating a specific http method and set/volley the response - %w( get post put delete head ).each do |method| - base.class_eval <<-EOV, __FILE__, __LINE__ - def #{method}(action, parameters = nil, session = nil, flash = nil) - @request.env['REQUEST_METHOD'] = "#{method.upcase}" if @request - process(action, parameters, session, flash) - end - EOV - end - end - - # execute the request and set/volley the response - def process(action, parameters = nil, session = nil, flash = nil) - # Sanity check for required instance variables so we can give an - # understandable error message. - %w(controller request response).each do |iv_name| - raise "@#{iv_name} is nil: make sure you set it in your test's setup method." if instance_variable_get("@#{iv_name}").nil? - end - - @request.recycle! - - @html_document = nil - @request.env['REQUEST_METHOD'] ||= "GET" - @request.action = action.to_s - - parameters ||= {} - @request.assign_parameters(@controller.class.controller_path, action.to_s, parameters) - - @request.session = ActionController::TestSession.new(session) unless session.nil? - @request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash - build_request_uri(action, parameters) - @controller.process(@request, @response) - end - - def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil) - @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' - @request.env['HTTP_ACCEPT'] = 'text/javascript, text/html, application/xml, text/xml, */*' - returning self.send(request_method, action, parameters, session, flash) do - @request.env.delete 'HTTP_X_REQUESTED_WITH' - @request.env.delete 'HTTP_ACCEPT' - end - end - alias xhr :xml_http_request - - def follow_redirect - if @response.redirected_to[:controller] - raise "Can't follow redirects outside of current controller (#{@response.redirected_to[:controller]})" - end - - get(@response.redirected_to.delete(:action), @response.redirected_to.stringify_keys) - end - - def assigns(key = nil) - if key.nil? - @response.template.assigns - else - @response.template.assigns[key.to_s] - end - end - - def session - @response.session - end - - def flash - @response.flash - end - - def cookies - @response.cookies - end - - def redirect_to_url - @response.redirect_url - end - - def build_request_uri(action, parameters) - unless @request.env['REQUEST_URI'] - options = @controller.send(:rewrite_options, parameters) - options.update(:only_path => true, :action => action) - - url = ActionController::UrlRewriter.new(@request, parameters) - @request.set_REQUEST_URI(url.rewrite(options)) - end - end - - def html_document - @html_document ||= HTML::Document.new(@response.body) - end - - def find_tag(conditions) - html_document.find(conditions) - end - - def find_all_tag(conditions) - html_document.find_all(conditions) - end - - def method_missing(selector, *args) - return @controller.send(selector, *args) if ActionController::Routing::NamedRoutes::Helpers.include?(selector) - return super - end - - # Shortcut for ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + path, type). Example: - # post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png') - def fixture_file_upload(path, mime_type = nil) - ActionController::TestUploadedFile.new( - Test::Unit::TestCase.respond_to?(:fixture_path) ? Test::Unit::TestCase.fixture_path + path : path, - mime_type - ) - end - - # A helper to make it easier to test different route configurations. - # This method temporarily replaces ActionController::Routing::Routes - # with a new RouteSet instance. - # - # The new instance is yielded to the passed block. Typically the block - # will create some routes using map.draw { map.connect ... }: - # - # with_routing do |set| - # set.draw { set.connect ':controller/:id/:action' } - # assert_equal( - # ['/content/10/show', {}], - # set.generate(:controller => 'content', :id => 10, :action => 'show') - # ) - # end - # - def with_routing - real_routes = ActionController::Routing::Routes - ActionController::Routing.send :remove_const, :Routes - - temporary_routes = ActionController::Routing::RouteSet.new - ActionController::Routing.send :const_set, :Routes, temporary_routes - - yield temporary_routes - ensure - if ActionController::Routing.const_defined? :Routes - ActionController::Routing.send(:remove_const, :Routes) - end - ActionController::Routing.const_set(:Routes, real_routes) if real_routes - end - end -end - -module Test - module Unit - class TestCase #:nodoc: - include ActionController::TestProcess - end - end -end -module ActionController - # Rewrites URLs for Base.redirect_to and Base.url_for in the controller. - - class UrlRewriter #:nodoc: - RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :trailing_slash, :skip_relative_url_root] - def initialize(request, parameters) - @request, @parameters = request, parameters - end - - def rewrite(options = {}) - rewrite_url(rewrite_path(options), options) - end - - def to_str - "#{@request.protocol}, #{@request.host_with_port}, #{@request.path}, #{@parameters[:controller]}, #{@parameters[:action]}, #{@request.parameters.inspect}" - end - - alias_method :to_s, :to_str - - private - def rewrite_url(path, options) - rewritten_url = "" - unless options[:only_path] - rewritten_url << (options[:protocol] || @request.protocol) - rewritten_url << (options[:host] || @request.host_with_port) - end - - rewritten_url << @request.relative_url_root.to_s unless options[:skip_relative_url_root] - rewritten_url << path - rewritten_url << '/' if options[:trailing_slash] - rewritten_url << "##{options[:anchor]}" if options[:anchor] - - rewritten_url - end - - def rewrite_path(options) - options = options.symbolize_keys - options.update(options[:params].symbolize_keys) if options[:params] - if (overwrite = options.delete(:overwrite_params)) - options.update(@parameters.symbolize_keys) - options.update(overwrite) - end - RESERVED_OPTIONS.each {|k| options.delete k} - path, extra_keys = Routing::Routes.generate(options.dup, @request) # Warning: Routes will mutate and violate the options hash - - path << build_query_string(options, extra_keys) unless extra_keys.empty? - - path - end - - # Returns a query string with escaped keys and values from the passed hash. If the passed hash contains an "id" it'll - # be added as a path element instead of a regular parameter pair. - def build_query_string(hash, only_keys = nil) - elements = [] - query_string = "" - - only_keys ||= hash.keys - - only_keys.each do |key| - value = hash[key] - key = CGI.escape key.to_s - if value.class == Array - key << '[]' - else - value = [ value ] - end - value.each { |val| elements << "#{key}=#{Routing.extract_parameter_value(val)}" } - end - - query_string << ("?" + elements.join("&")) unless elements.empty? - query_string - end - end -end -require File.dirname(__FILE__) + '/tokenizer' -require File.dirname(__FILE__) + '/node' - -module HTML #:nodoc: - - # A top-level HTMl document. You give it a body of text, and it will parse that - # text into a tree of nodes. - class Document #:nodoc: - - # The root of the parsed document. - attr_reader :root - - # Create a new Document from the given text. - def initialize(text, strict=false, xml=false) - tokenizer = Tokenizer.new(text) - @root = Node.new(nil) - node_stack = [ @root ] - while token = tokenizer.next - node = Node.parse(node_stack.last, tokenizer.line, tokenizer.position, token) - - node_stack.last.children << node unless node.tag? && node.closing == :close - if node.tag? - if node_stack.length > 1 && node.closing == :close - if node_stack.last.name == node.name - node_stack.pop - else - open_start = node_stack.last.position - 20 - open_start = 0 if open_start < 0 - close_start = node.position - 20 - close_start = 0 if close_start < 0 - msg = < hash } unless Hash === hash - hash = keys_to_symbols(hash) - hash.each do |k,v| - case k - when :tag, :content then - # keys are valid, and require no further processing - when :attributes then - hash[k] = keys_to_strings(v) - when :parent, :child, :ancestor, :descendant, :sibling, :before, - :after - hash[k] = Conditions.new(v) - when :children - hash[k] = v = keys_to_symbols(v) - v.each do |k,v2| - case k - when :count, :greater_than, :less_than - # keys are valid, and require no further processing - when :only - v[k] = Conditions.new(v2) - else - raise "illegal key #{k.inspect} => #{v2.inspect}" - end - end - else - raise "illegal key #{k.inspect} => #{v.inspect}" - end - end - update hash - end - - private - - def keys_to_strings(hash) - hash.keys.inject({}) do |h,k| - h[k.to_s] = hash[k] - h - end - end - - def keys_to_symbols(hash) - hash.keys.inject({}) do |h,k| - raise "illegal key #{k.inspect}" unless k.respond_to?(:to_sym) - h[k.to_sym] = hash[k] - h - end - end - end - - # The base class of all nodes, textual and otherwise, in an HTML document. - class Node #:nodoc: - # The array of children of this node. Not all nodes have children. - attr_reader :children - - # The parent node of this node. All nodes have a parent, except for the - # root node. - attr_reader :parent - - # The line number of the input where this node was begun - attr_reader :line - - # The byte position in the input where this node was begun - attr_reader :position - - # Create a new node as a child of the given parent. - def initialize(parent, line=0, pos=0) - @parent = parent - @children = [] - @line, @position = line, pos - end - - # Return a textual representation of the node. - def to_s - s = "" - @children.each { |child| s << child.to_s } - s - end - - # Return false (subclasses must override this to provide specific matching - # behavior.) +conditions+ may be of any type. - def match(conditions) - false - end - - # Search the children of this node for the first node for which #find - # returns non +nil+. Returns the result of the #find call that succeeded. - def find(conditions) - conditions = validate_conditions(conditions) - - @children.each do |child| - node = child.find(conditions) - return node if node - end - nil - end - - # Search for all nodes that match the given conditions, and return them - # as an array. - def find_all(conditions) - conditions = validate_conditions(conditions) - - matches = [] - matches << self if match(conditions) - @children.each do |child| - matches.concat child.find_all(conditions) - end - matches - end - - # Returns +false+. Subclasses may override this if they define a kind of - # tag. - def tag? - false - end - - def validate_conditions(conditions) - Conditions === conditions ? conditions : Conditions.new(conditions) - end - - def ==(node) - return false unless self.class == node.class && children.size == node.children.size - - equivalent = true - - children.size.times do |i| - equivalent &&= children[i] == node.children[i] - end - - equivalent - end - - class </) - return CDATA.new(parent, line, pos, scanner.pre_match) - end - - closing = ( scanner.scan(/\//) ? :close : nil ) - return Text.new(parent, line, pos, content) unless name = scanner.scan(/[\w:]+/) - name.downcase! - - unless closing - scanner.skip(/\s*/) - attributes = {} - while attr = scanner.scan(/[-\w:]+/) - value = true - if scanner.scan(/\s*=\s*/) - if delim = scanner.scan(/['"]/) - value = "" - while text = scanner.scan(/[^#{delim}\\]+|./) - case text - when "\\" then - value << text - value << scanner.getch - when delim - break - else value << text - end - end - else - value = scanner.scan(/[^\s>\/]+/) - end - end - attributes[attr.downcase] = value - scanner.skip(/\s*/) - end - - closing = ( scanner.scan(/\//) ? :self : nil ) - end - - unless scanner.scan(/\s*>/) - if strict - raise "expected > (got #{scanner.rest.inspect} for #{content}, #{attributes.inspect})" - else - # throw away all text until we find what we're looking for - scanner.skip_until(/>/) or scanner.terminate - end - end - - Tag.new(parent, line, pos, name, attributes, closing) - end - end - end - end - - # A node that represents text, rather than markup. - class Text < Node #:nodoc: - - attr_reader :content - - # Creates a new text node as a child of the given parent, with the given - # content. - def initialize(parent, line, pos, content) - super(parent, line, pos) - @content = content - end - - # Returns the content of this node. - def to_s - @content - end - - # Returns +self+ if this node meets the given conditions. Text nodes support - # conditions of the following kinds: - # - # * if +conditions+ is a string, it must be a substring of the node's - # content - # * if +conditions+ is a regular expression, it must match the node's - # content - # * if +conditions+ is a hash, it must contain a :content key that - # is either a string or a regexp, and which is interpreted as described - # above. - def find(conditions) - match(conditions) && self - end - - # Returns non-+nil+ if this node meets the given conditions, or +nil+ - # otherwise. See the discussion of #find for the valid conditions. - def match(conditions) - case conditions - when String - @content.index(conditions) - when Regexp - @content =~ conditions - when Hash - conditions = validate_conditions(conditions) - - # Text nodes only have :content, :parent, :ancestor - unless (conditions.keys - [:content, :parent, :ancestor]).empty? - return false - end - - match(conditions[:content]) - else - nil - end - end - - def ==(node) - return false unless super - content == node.content - end - end - - # A CDATA node is simply a text node with a specialized way of displaying - # itself. - class CDATA < Text #:nodoc: - def to_s - "" - end - end - - # A Tag is any node that represents markup. It may be an opening tag, a - # closing tag, or a self-closing tag. It has a name, and may have a hash of - # attributes. - class Tag < Node #:nodoc: - - # Either +nil+, :close, or :self - attr_reader :closing - - # Either +nil+, or a hash of attributes for this node. - attr_reader :attributes - - # The name of this tag. - attr_reader :name - - # Create a new node as a child of the given parent, using the given content - # to describe the node. It will be parsed and the node name, attributes and - # closing status extracted. - def initialize(parent, line, pos, name, attributes, closing) - super(parent, line, pos) - @name = name - @attributes = attributes - @closing = closing - end - - # A convenience for obtaining an attribute of the node. Returns +nil+ if - # the node has no attributes. - def [](attr) - @attributes ? @attributes[attr] : nil - end - - # Returns non-+nil+ if this tag can contain child nodes. - def childless?(xml = false) - return false if xml && @closing.nil? - !@closing.nil? || - @name =~ /^(img|br|hr|link|meta|area|base|basefont| - col|frame|input|isindex|param)$/ox - end - - # Returns a textual representation of the node - def to_s - if @closing == :close - "" - else - s = "<#{@name}" - @attributes.each do |k,v| - s << " #{k}" - s << "='#{v.gsub(/'/,"\\\\'")}'" if String === v - end - s << " /" if @closing == :self - s << ">" - @children.each { |child| s << child.to_s } - s << "" if @closing != :self && !@children.empty? - s - end - end - - # If either the node or any of its children meet the given conditions, the - # matching node is returned. Otherwise, +nil+ is returned. (See the - # description of the valid conditions in the +match+ method.) - def find(conditions) - match(conditions) && self || super - end - - # Returns +true+, indicating that this node represents an HTML tag. - def tag? - true - end - - # Returns +true+ if the node meets any of the given conditions. The - # +conditions+ parameter must be a hash of any of the following keys - # (all are optional): - # - # * :tag: the node name must match the corresponding value - # * :attributes: a hash. The node's values must match the - # corresponding values in the hash. - # * :parent: a hash. The node's parent must match the - # corresponding hash. - # * :child: a hash. At least one of the node's immediate children - # must meet the criteria described by the hash. - # * :ancestor: a hash. At least one of the node's ancestors must - # meet the criteria described by the hash. - # * :descendant: a hash. At least one of the node's descendants - # must meet the criteria described by the hash. - # * :sibling: a hash. At least one of the node's siblings must - # meet the criteria described by the hash. - # * :after: a hash. The node must be after any sibling meeting - # the criteria described by the hash, and at least one sibling must match. - # * :before: a hash. The node must be before any sibling meeting - # the criteria described by the hash, and at least one sibling must match. - # * :children: a hash, for counting children of a node. Accepts the - # keys: - # ** :count: either a number or a range which must equal (or - # include) the number of children that match. - # ** :less_than: the number of matching children must be less than - # this number. - # ** :greater_than: the number of matching children must be - # greater than this number. - # ** :only: another hash consisting of the keys to use - # to match on the children, and only matching children will be - # counted. - # - # Conditions are matched using the following algorithm: - # - # * if the condition is a string, it must be a substring of the value. - # * if the condition is a regexp, it must match the value. - # * if the condition is a number, the value must match number.to_s. - # * if the condition is +true+, the value must not be +nil+. - # * if the condition is +false+ or +nil+, the value must be +nil+. - # - # Usage: - # - # # test if the node is a "span" tag - # node.match :tag => "span" - # - # # test if the node's parent is a "div" - # node.match :parent => { :tag => "div" } - # - # # test if any of the node's ancestors are "table" tags - # node.match :ancestor => { :tag => "table" } - # - # # test if any of the node's immediate children are "em" tags - # node.match :child => { :tag => "em" } - # - # # test if any of the node's descendants are "strong" tags - # node.match :descendant => { :tag => "strong" } - # - # # test if the node has between 2 and 4 span tags as immediate children - # node.match :children => { :count => 2..4, :only => { :tag => "span" } } - # - # # get funky: test to see if the node is a "div", has a "ul" ancestor - # # and an "li" parent (with "class" = "enum"), and whether or not it has - # # a "span" descendant that contains # text matching /hello world/: - # node.match :tag => "div", - # :ancestor => { :tag => "ul" }, - # :parent => { :tag => "li", - # :attributes => { :class => "enum" } }, - # :descendant => { :tag => "span", - # :child => /hello world/ } - def match(conditions) - conditions = validate_conditions(conditions) - - # check content of child nodes - if conditions[:content] - if children.empty? - return false unless match_condition("", conditions[:content]) - else - return false unless children.find { |child| child.match(conditions[:content]) } - end - end - - # test the name - return false unless match_condition(@name, conditions[:tag]) if conditions[:tag] - - # test attributes - (conditions[:attributes] || {}).each do |key, value| - return false unless match_condition(self[key], value) - end - - # test parent - return false unless parent.match(conditions[:parent]) if conditions[:parent] - - # test children - return false unless children.find { |child| child.match(conditions[:child]) } if conditions[:child] - - # test ancestors - if conditions[:ancestor] - return false unless catch :found do - p = self - throw :found, true if p.match(conditions[:ancestor]) while p = p.parent - end - end - - # test descendants - if conditions[:descendant] - return false unless children.find do |child| - # test the child - child.match(conditions[:descendant]) || - # test the child's descendants - child.match(:descendant => conditions[:descendant]) - end - end - - # count children - if opts = conditions[:children] - matches = children.select do |c| - c.match(/./) or - (c.kind_of?(HTML::Tag) and (c.closing == :self or ! c.childless?)) - end - - matches = matches.select { |c| c.match(opts[:only]) } if opts[:only] - opts.each do |key, value| - next if key == :only - case key - when :count - if Integer === value - return false if matches.length != value - else - return false unless value.include?(matches.length) - end - when :less_than - return false unless matches.length < value - when :greater_than - return false unless matches.length > value - else raise "unknown count condition #{key}" - end - end - end - - # test siblings - if conditions[:sibling] || conditions[:before] || conditions[:after] - siblings = parent ? parent.children : [] - self_index = siblings.index(self) - - if conditions[:sibling] - return false unless siblings.detect do |s| - s != self && s.match(conditions[:sibling]) - end - end - - if conditions[:before] - return false unless siblings[self_index+1..-1].detect do |s| - s != self && s.match(conditions[:before]) - end - end - - if conditions[:after] - return false unless siblings[0,self_index].detect do |s| - s != self && s.match(conditions[:after]) - end - end - end - - true - end - - def ==(node) - return false unless super - return false unless closing == node.closing && self.name == node.name - attributes == node.attributes - end - - private - # Match the given value to the given condition. - def match_condition(value, condition) - case condition - when String - value && value == condition - when Regexp - value && value.match(condition) - when Numeric - value == condition.to_s - when true - !value.nil? - when false, nil - value.nil? - else - false - end - end - end -end -require 'strscan' - -module HTML #:nodoc: - - # A simple HTML tokenizer. It simply breaks a stream of text into tokens, where each - # token is a string. Each string represents either "text", or an HTML element. - # - # This currently assumes valid XHTML, which means no free < or > characters. - # - # Usage: - # - # tokenizer = HTML::Tokenizer.new(text) - # while token = tokenizer.next - # p token - # end - class Tokenizer #:nodoc: - - # The current (byte) position in the text - attr_reader :position - - # The current line number - attr_reader :line - - # Create a new Tokenizer for the given text. - def initialize(text) - @scanner = StringScanner.new(text) - @position = 0 - @line = 0 - @current_line = 1 - end - - # Return the next token in the sequence, or +nil+ if there are no more tokens in - # the stream. - def next - return nil if @scanner.eos? - @position = @scanner.pos - @line = @current_line - if @scanner.check(/<\S/) - update_current_line(scan_tag) - else - update_current_line(scan_text) - end - end - - private - - # Treat the text at the current position as a tag, and scan it. Supports - # comments, doctype tags, and regular tags, and ignores less-than and - # greater-than characters within quoted strings. - def scan_tag - tag = @scanner.getch - if @scanner.scan(/!--/) # comment - tag << @scanner.matched - tag << (@scanner.scan_until(/--\s*>/) || @scanner.scan_until(/\Z/)) - elsif @scanner.scan(/!\[CDATA\[/) - tag << @scanner.matched - tag << @scanner.scan_until(/\]\]>/) - elsif @scanner.scan(/!/) # doctype - tag << @scanner.matched - tag << consume_quoted_regions - else - tag << consume_quoted_regions - end - tag - end - - # Scan all text up to the next < character and return it. - def scan_text - "#{@scanner.getch}#{@scanner.scan(/[^<]*/)}" - end - - # Counts the number of newlines in the text and updates the current line - # accordingly. - def update_current_line(text) - text.scan(/\r?\n/) { @current_line += 1 } - end - - # Skips over quoted strings, so that less-than and greater-than characters - # within the strings are ignored. - def consume_quoted_regions - text = "" - loop do - match = @scanner.scan_until(/['"<>]/) or break - - delim = @scanner.matched - if delim == "<" - match = match.chop - @scanner.pos -= 1 - end - - text << match - break if delim == "<" || delim == ">" - - # consume the quoted region - while match = @scanner.scan_until(/[\\#{delim}]/) - text << match - break if @scanner.matched == delim - text << @scanner.getch # skip the escaped character - end - end - text - end - end - -end -module HTML #:nodoc: - module Version #:nodoc: - - MAJOR = 0 - MINOR = 5 - TINY = 3 - - STRING = [ MAJOR, MINOR, TINY ].join(".") - - end -end -require 'rexml/document' - -# SimpleXML like xml parser. Written by leon breet from the ruby on rails Mailing list -class XmlNode #:nodoc: - attr :node - - def initialize(node, options = {}) - @node = node - @children = {} - @raise_errors = options[:raise_errors] - end - - def self.from_xml(xml_or_io) - document = REXML::Document.new(xml_or_io) - if document.root - XmlNode.new(document.root) - else - XmlNode.new(document) - end - end - - def node_encoding - @node.encoding - end - - def node_name - @node.name - end - - def node_value - @node.text - end - - def node_value=(value) - @node.text = value - end - - def xpath(expr) - matches = nil - REXML::XPath.each(@node, expr) do |element| - matches ||= XmlNodeList.new - matches << (@children[element] ||= XmlNode.new(element)) - end - matches - end - - def method_missing(name, *args) - name = name.to_s - nodes = nil - @node.each_element(name) do |element| - nodes ||= XmlNodeList.new - nodes << (@children[element] ||= XmlNode.new(element)) - end - nodes - end - - def <<(node) - if node.is_a? REXML::Node - child = node - elsif node.respond_to? :node - child = node.node - end - @node.add_element child - @children[child] ||= XmlNode.new(child) - end - - def [](name) - @node.attributes[name.to_s] - end - - def []=(name, value) - @node.attributes[name.to_s] = value - end - - def to_s - @node.to_s - end - - def to_i - to_s.to_i - end -end - -class XmlNodeList < Array #:nodoc: - def [](i) - i.is_a?(String) ? super(0)[i] : super(i) - end - - def []=(i, value) - i.is_a?(String) ? self[0][i] = value : super(i, value) - end - - def method_missing(name, *args) - name = name.to_s - self[0].__send__(name, *args) - end -end# = XmlSimple -# -# Author:: Maik Schmidt -# Copyright:: Copyright (c) 2003 Maik Schmidt -# License:: Distributes under the same terms as Ruby. -# -require 'rexml/document' - -# Easy API to maintain XML (especially configuration files). -class XmlSimple #:nodoc: - include REXML - - @@VERSION = '1.0.2' - - # A simple cache for XML documents that were already transformed - # by xml_in. - class Cache #:nodoc: - # Creates and initializes a new Cache object. - def initialize - @mem_share_cache = {} - @mem_copy_cache = {} - end - - # Saves a data structure into a file. - # - # data:: - # Data structure to be saved. - # filename:: - # Name of the file belonging to the data structure. - def save_storable(data, filename) - cache_file = get_cache_filename(filename) - File.open(cache_file, "w+") { |f| Marshal.dump(data, f) } - end - - # Restores a data structure from a file. If restoring the data - # structure failed for any reason, nil will be returned. - # - # filename:: - # Name of the file belonging to the data structure. - def restore_storable(filename) - cache_file = get_cache_filename(filename) - return nil unless File::exist?(cache_file) - return nil unless File::mtime(cache_file).to_i > File::mtime(filename).to_i - data = nil - File.open(cache_file) { |f| data = Marshal.load(f) } - data - end - - # Saves a data structure in a shared memory cache. - # - # data:: - # Data structure to be saved. - # filename:: - # Name of the file belonging to the data structure. - def save_mem_share(data, filename) - @mem_share_cache[filename] = [Time::now.to_i, data] - end - - # Restores a data structure from a shared memory cache. You - # should consider these elements as "read only". If restoring - # the data structure failed for any reason, nil will be - # returned. - # - # filename:: - # Name of the file belonging to the data structure. - def restore_mem_share(filename) - get_from_memory_cache(filename, @mem_share_cache) - end - - # Copies a data structure to a memory cache. - # - # data:: - # Data structure to be copied. - # filename:: - # Name of the file belonging to the data structure. - def save_mem_copy(data, filename) - @mem_share_cache[filename] = [Time::now.to_i, Marshal.dump(data)] - end - - # Restores a data structure from a memory cache. If restoring - # the data structure failed for any reason, nil will be - # returned. - # - # filename:: - # Name of the file belonging to the data structure. - def restore_mem_copy(filename) - data = get_from_memory_cache(filename, @mem_share_cache) - data = Marshal.load(data) unless data.nil? - data - end - - private - - # Returns the "cache filename" belonging to a filename, i.e. - # the extension '.xml' in the original filename will be replaced - # by '.stor'. If filename does not have this extension, '.stor' - # will be appended. - # - # filename:: - # Filename to get "cache filename" for. - def get_cache_filename(filename) - filename.sub(/(\.xml)?$/, '.stor') - end - - # Returns a cache entry from a memory cache belonging to a - # certain filename. If no entry could be found for any reason, - # nil will be returned. - # - # filename:: - # Name of the file the cache entry belongs to. - # cache:: - # Memory cache to get entry from. - def get_from_memory_cache(filename, cache) - return nil unless cache[filename] - return nil unless cache[filename][0] > File::mtime(filename).to_i - return cache[filename][1] - end - end - - # Create a "global" cache. - @@cache = Cache.new - - # Creates and intializes a new XmlSimple object. - # - # defaults:: - # Default values for options. - def initialize(defaults = nil) - unless defaults.nil? || defaults.instance_of?(Hash) - raise ArgumentError, "Options have to be a Hash." - end - @default_options = normalize_option_names(defaults, KNOWN_OPTIONS['in'] & KNOWN_OPTIONS['out']) - @options = Hash.new - @_var_values = nil - end - - # Converts an XML document in the same way as the Perl module XML::Simple. - # - # string:: - # XML source. Could be one of the following: - # - # - nil: Tries to load and parse '.xml'. - # - filename: Tries to load and parse filename. - # - IO object: Reads from object until EOF is detected and parses result. - # - XML string: Parses string. - # - # options:: - # Options to be used. - def xml_in(string = nil, options = nil) - handle_options('in', options) - - # If no XML string or filename was supplied look for scriptname.xml. - if string.nil? - string = File::basename($0) - string.sub!(/\.[^.]+$/, '') - string += '.xml' - - directory = File::dirname($0) - @options['searchpath'].unshift(directory) unless directory.nil? - end - - if string.instance_of?(String) - if string =~ /<.*?>/m - @doc = parse(string) - elsif string == '-' - @doc = parse($stdin.readlines.to_s) - else - filename = find_xml_file(string, @options['searchpath']) - - if @options.has_key?('cache') - @options['cache'].each { |scheme| - case(scheme) - when 'storable' - content = @@cache.restore_storable(filename) - when 'mem_share' - content = @@cache.restore_mem_share(filename) - when 'mem_copy' - content = @@cache.restore_mem_copy(filename) - else - raise ArgumentError, "Unsupported caching scheme: <#{scheme}>." - end - return content if content - } - end - - @doc = load_xml_file(filename) - end - elsif string.kind_of?(IO) - @doc = parse(string.readlines.to_s) - else - raise ArgumentError, "Could not parse object of type: <#{string.type}>." - end - - result = collapse(@doc.root) - result = @options['keeproot'] ? merge({}, @doc.root.name, result) : result - put_into_cache(result, filename) - result - end - - # This is the functional version of the instance method xml_in. - def XmlSimple.xml_in(string = nil, options = nil) - xml_simple = XmlSimple.new - xml_simple.xml_in(string, options) - end - - # Converts a data structure into an XML document. - # - # ref:: - # Reference to data structure to be converted into XML. - # options:: - # Options to be used. - def xml_out(ref, options = nil) - handle_options('out', options) - if ref.instance_of?(Array) - ref = { @options['anonymoustag'] => ref } - end - - if @options['keeproot'] - keys = ref.keys - if keys.size == 1 - ref = ref[keys[0]] - @options['rootname'] = keys[0] - end - elsif @options['rootname'] == '' - if ref.instance_of?(Hash) - refsave = ref - ref = {} - refsave.each { |key, value| - if !scalar(value) - ref[key] = value - else - ref[key] = [ value.to_s ] - end - } - end - end - - @ancestors = [] - xml = value_to_xml(ref, @options['rootname'], '') - @ancestors = nil - - if @options['xmldeclaration'] - xml = @options['xmldeclaration'] + "\n" + xml - end - - if @options.has_key?('outputfile') - if @options['outputfile'].kind_of?(IO) - return @options['outputfile'].write(xml) - else - File.open(@options['outputfile'], "w") { |file| file.write(xml) } - end - end - xml - end - - # This is the functional version of the instance method xml_out. - def XmlSimple.xml_out(hash, options = nil) - xml_simple = XmlSimple.new - xml_simple.xml_out(hash, options) - end - - private - - # Declare options that are valid for xml_in and xml_out. - KNOWN_OPTIONS = { - 'in' => %w( - keyattr keeproot forcecontent contentkey noattr - searchpath forcearray suppressempty anonymoustag - cache grouptags normalisespace normalizespace - variables varattr - ), - 'out' => %w( - keyattr keeproot contentkey noattr rootname - xmldeclaration outputfile noescape suppressempty - anonymoustag indent grouptags noindent - ) - } - - # Define some reasonable defaults. - DEF_KEY_ATTRIBUTES = [] - DEF_ROOT_NAME = 'opt' - DEF_CONTENT_KEY = 'content' - DEF_XML_DECLARATION = "" - DEF_ANONYMOUS_TAG = 'anon' - DEF_FORCE_ARRAY = true - DEF_INDENTATION = ' ' - - # Normalizes option names in a hash, i.e., turns all - # characters to lower case and removes all underscores. - # Additionally, this method checks, if an unknown option - # was used and raises an according exception. - # - # options:: - # Hash to be normalized. - # known_options:: - # List of known options. - def normalize_option_names(options, known_options) - return nil if options.nil? - result = Hash.new - options.each { |key, value| - lkey = key.downcase - lkey.gsub!(/_/, '') - if !known_options.member?(lkey) - raise ArgumentError, "Unrecognised option: #{lkey}." - end - result[lkey] = value - } - result - end - - # Merges a set of options with the default options. - # - # direction:: - # 'in': If options should be handled for xml_in. - # 'out': If options should be handled for xml_out. - # options:: - # Options to be merged with the default options. - def handle_options(direction, options) - @options = options || Hash.new - - raise ArgumentError, "Options must be a Hash!" unless @options.instance_of?(Hash) - - unless KNOWN_OPTIONS.has_key?(direction) - raise ArgumentError, "Unknown direction: <#{direction}>." - end - - known_options = KNOWN_OPTIONS[direction] - @options = normalize_option_names(@options, known_options) - - unless @default_options.nil? - known_options.each { |option| - unless @options.has_key?(option) - if @default_options.has_key?(option) - @options[option] = @default_options[option] - end - end - } - end - - unless @options.has_key?('noattr') - @options['noattr'] = false - end - - if @options.has_key?('rootname') - @options['rootname'] = '' if @options['rootname'].nil? - else - @options['rootname'] = DEF_ROOT_NAME - end - - if @options.has_key?('xmldeclaration') && @options['xmldeclaration'] == true - @options['xmldeclaration'] = DEF_XML_DECLARATION - end - - if @options.has_key?('contentkey') - if @options['contentkey'] =~ /^-(.*)$/ - @options['contentkey'] = $1 - @options['collapseagain'] = true - end - else - @options['contentkey'] = DEF_CONTENT_KEY - end - - unless @options.has_key?('normalisespace') - @options['normalisespace'] = @options['normalizespace'] - end - @options['normalisespace'] = 0 if @options['normalisespace'].nil? - - if @options.has_key?('searchpath') - unless @options['searchpath'].instance_of?(Array) - @options['searchpath'] = [ @options['searchpath'] ] - end - else - @options['searchpath'] = [] - end - - if @options.has_key?('cache') && scalar(@options['cache']) - @options['cache'] = [ @options['cache'] ] - end - - @options['anonymoustag'] = DEF_ANONYMOUS_TAG unless @options.has_key?('anonymoustag') - - if !@options.has_key?('indent') || @options['indent'].nil? - @options['indent'] = DEF_INDENTATION - end - - @options['indent'] = '' if @options.has_key?('noindent') - - # Special cleanup for 'keyattr' which could be an array or - # a hash or left to default to array. - if @options.has_key?('keyattr') - if !scalar(@options['keyattr']) - # Convert keyattr => { elem => '+attr' } - # to keyattr => { elem => ['attr', '+'] } - if @options['keyattr'].instance_of?(Hash) - @options['keyattr'].each { |key, value| - if value =~ /^([-+])?(.*)$/ - @options['keyattr'][key] = [$2, $1 ? $1 : ''] - end - } - elsif !@options['keyattr'].instance_of?(Array) - raise ArgumentError, "'keyattr' must be String, Hash, or Array!" - end - else - @options['keyattr'] = [ @options['keyattr'] ] - end - else - @options['keyattr'] = DEF_KEY_ATTRIBUTES - end - - if @options.has_key?('forcearray') - if @options['forcearray'].instance_of?(Regexp) - @options['forcearray'] = [ @options['forcearray'] ] - end - - if @options['forcearray'].instance_of?(Array) - force_list = @options['forcearray'] - unless force_list.empty? - @options['forcearray'] = {} - force_list.each { |tag| - if tag.instance_of?(Regexp) - unless @options['forcearray']['_regex'].instance_of?(Array) - @options['forcearray']['_regex'] = [] - end - @options['forcearray']['_regex'] << tag - else - @options['forcearray'][tag] = true - end - } - else - @options['forcearray'] = false - end - else - @options['forcearray'] = @options['forcearray'] ? true : false - end - else - @options['forcearray'] = DEF_FORCE_ARRAY - end - - if @options.has_key?('grouptags') && !@options['grouptags'].instance_of?(Hash) - raise ArgumentError, "Illegal value for 'GroupTags' option - expected a Hash." - end - - if @options.has_key?('variables') && !@options['variables'].instance_of?(Hash) - raise ArgumentError, "Illegal value for 'Variables' option - expected a Hash." - end - - if @options.has_key?('variables') - @_var_values = @options['variables'] - elsif @options.has_key?('varattr') - @_var_values = {} - end - end - - # Actually converts an XML document element into a data structure. - # - # element:: - # The document element to be collapsed. - def collapse(element) - result = @options['noattr'] ? {} : get_attributes(element) - - if @options['normalisespace'] == 2 - result.each { |k, v| result[k] = normalise_space(v) } - end - - if element.has_elements? - element.each_element { |child| - value = collapse(child) - if empty(value) && (element.attributes.empty? || @options['noattr']) - next if @options.has_key?('suppressempty') && @options['suppressempty'] == true - end - result = merge(result, child.name, value) - } - if has_mixed_content?(element) - # normalisespace? - content = element.texts.map { |x| x.to_s } - content = content[0] if content.size == 1 - result[@options['contentkey']] = content - end - elsif element.has_text? # i.e. it has only text. - return collapse_text_node(result, element) - end - - # Turn Arrays into Hashes if key fields present. - count = fold_arrays(result) - - # Disintermediate grouped tags. - if @options.has_key?('grouptags') - result.each { |key, value| - next unless (value.instance_of?(Hash) && (value.size == 1)) - child_key, child_value = value.to_a[0] - if @options['grouptags'][key] == child_key - result[key] = child_value - end - } - end - - # Fold Hases containing a single anonymous Array up into just the Array. - if count == 1 - anonymoustag = @options['anonymoustag'] - if result.has_key?(anonymoustag) && result[anonymoustag].instance_of?(Array) - return result[anonymoustag] - end - end - - if result.empty? && @options.has_key?('suppressempty') - return @options['suppressempty'] == '' ? '' : nil - end - - result - end - - # Collapses a text node and merges it with an existing Hash, if - # possible. - # Thanks to Curtis Schofield for reporting a subtle bug. - # - # hash:: - # Hash to merge text node value with, if possible. - # element:: - # Text node to be collapsed. - def collapse_text_node(hash, element) - value = node_to_text(element) - if empty(value) && !element.has_attributes? - return {} - end - - if element.has_attributes? && !@options['noattr'] - return merge(hash, @options['contentkey'], value) - else - if @options['forcecontent'] - return merge(hash, @options['contentkey'], value) - else - return value - end - end - end - - # Folds all arrays in a Hash. - # - # hash:: - # Hash to be folded. - def fold_arrays(hash) - fold_amount = 0 - keyattr = @options['keyattr'] - if (keyattr.instance_of?(Array) || keyattr.instance_of?(Hash)) - hash.each { |key, value| - if value.instance_of?(Array) - if keyattr.instance_of?(Array) - hash[key] = fold_array(value) - else - hash[key] = fold_array_by_name(key, value) - end - fold_amount += 1 - end - } - end - fold_amount - end - - # Folds an Array to a Hash, if possible. Folding happens - # according to the content of keyattr, which has to be - # an array. - # - # array:: - # Array to be folded. - def fold_array(array) - hash = Hash.new - array.each { |x| - return array unless x.instance_of?(Hash) - key_matched = false - @options['keyattr'].each { |key| - if x.has_key?(key) - key_matched = true - value = x[key] - return array if value.instance_of?(Hash) || value.instance_of?(Array) - value = normalise_space(value) if @options['normalisespace'] == 1 - x.delete(key) - hash[value] = x - break - end - } - return array unless key_matched - } - hash = collapse_content(hash) if @options['collapseagain'] - hash - end - - # Folds an Array to a Hash, if possible. Folding happens - # according to the content of keyattr, which has to be - # a Hash. - # - # name:: - # Name of the attribute to be folded upon. - # array:: - # Array to be folded. - def fold_array_by_name(name, array) - return array unless @options['keyattr'].has_key?(name) - key, flag = @options['keyattr'][name] - - hash = Hash.new - array.each { |x| - if x.instance_of?(Hash) && x.has_key?(key) - value = x[key] - return array if value.instance_of?(Hash) || value.instance_of?(Array) - value = normalise_space(value) if @options['normalisespace'] == 1 - hash[value] = x - hash[value]["-#{key}"] = hash[value][key] if flag == '-' - hash[value].delete(key) unless flag == '+' - else - $stderr.puts("Warning: <#{name}> element has no '#{key}' attribute.") - return array - end - } - hash = collapse_content(hash) if @options['collapseagain'] - hash - end - - # Tries to collapse a Hash even more ;-) - # - # hash:: - # Hash to be collapsed again. - def collapse_content(hash) - content_key = @options['contentkey'] - hash.each_value { |value| - return hash unless value.instance_of?(Hash) && value.size == 1 && value.has_key?(content_key) - hash.each_key { |key| hash[key] = hash[key][content_key] } - } - hash - end - - # Adds a new key/value pair to an existing Hash. If the key to be added - # does already exist and the existing value associated with key is not - # an Array, it will be converted into an Array. Then the new value is - # appended to that Array. - # - # hash:: - # Hash to add key/value pair to. - # key:: - # Key to be added. - # value:: - # Value to be associated with key. - def merge(hash, key, value) - if value.instance_of?(String) - value = normalise_space(value) if @options['normalisespace'] == 2 - - # do variable substitutions - unless @_var_values.nil? || @_var_values.empty? - value.gsub!(/\$\{(\w+)\}/) { |x| get_var($1) } - end - - # look for variable definitions - if @options.has_key?('varattr') - varattr = @options['varattr'] - if hash.has_key?(varattr) - set_var(hash[varattr], value) - end - end - end - if hash.has_key?(key) - if hash[key].instance_of?(Array) - hash[key] << value - else - hash[key] = [ hash[key], value ] - end - elsif value.instance_of?(Array) # Handle anonymous arrays. - hash[key] = [ value ] - else - if force_array?(key) - hash[key] = [ value ] - else - hash[key] = value - end - end - hash - end - - # Checks, if the 'forcearray' option has to be used for - # a certain key. - def force_array?(key) - return false if key == @options['contentkey'] - return true if @options['forcearray'] == true - forcearray = @options['forcearray'] - if forcearray.instance_of?(Hash) - return true if forcearray.has_key?(key) - return false unless forcearray.has_key?('_regex') - forcearray['_regex'].each { |x| return true if key =~ x } - end - return false - end - - # Converts the attributes array of a document node into a Hash. - # Returns an empty Hash, if node has no attributes. - # - # node:: - # Document node to extract attributes from. - def get_attributes(node) - attributes = {} - node.attributes.each { |n,v| attributes[n] = v } - attributes - end - - # Determines, if a document element has mixed content. - # - # element:: - # Document element to be checked. - def has_mixed_content?(element) - if element.has_text? && element.has_elements? - return true if element.texts.join('') !~ /^\s*$/s - end - false - end - - # Called when a variable definition is encountered in the XML. - # A variable definition looks like - # value - # where attrname matches the varattr setting. - def set_var(name, value) - @_var_values[name] = value - end - - # Called during variable substitution to get the value for the - # named variable. - def get_var(name) - if @_var_values.has_key?(name) - return @_var_values[name] - else - return "${#{name}}" - end - end - - # Recurses through a data structure building up and returning an - # XML representation of that structure as a string. - # - # ref:: - # Reference to the data structure to be encoded. - # name:: - # The XML tag name to be used for this item. - # indent:: - # A string of spaces for use as the current indent level. - def value_to_xml(ref, name, indent) - named = !name.nil? && name != '' - nl = @options.has_key?('noindent') ? '' : "\n" - - if !scalar(ref) - if @ancestors.member?(ref) - raise ArgumentError, "Circular data structures not supported!" - end - @ancestors << ref - else - if named - return [indent, '<', name, '>', @options['noescape'] ? ref.to_s : escape_value(ref.to_s), '', nl].join('') - else - return ref.to_s + nl - end - end - - # Unfold hash to array if possible. - if ref.instance_of?(Hash) && !ref.empty? && !@options['keyattr'].empty? && indent != '' - ref = hash_to_array(name, ref) - end - - result = [] - if ref.instance_of?(Hash) - # Reintermediate grouped values if applicable. - if @options.has_key?('grouptags') - ref.each { |key, value| - if @options['grouptags'].has_key?(key) - ref[key] = { @options['grouptags'][key] => value } - end - } - end - - nested = [] - text_content = nil - if named - result << indent << '<' << name - end - - if !ref.empty? - ref.each { |key, value| - next if !key.nil? && key[0, 1] == '-' - if value.nil? - unless @options.has_key?('suppressempty') && @options['suppressempty'].nil? - raise ArgumentError, "Use of uninitialized value!" - end - value = {} - end - - if !scalar(value) || @options['noattr'] - nested << value_to_xml(value, key, indent + @options['indent']) - else - value = value.to_s - value = escape_value(value) unless @options['noescape'] - if key == @options['contentkey'] - text_content = value - else - result << ' ' << key << '="' << value << '"' - end - end - } - else - text_content = '' - end - - if !nested.empty? || !text_content.nil? - if named - result << '>' - if !text_content.nil? - result << text_content - nested[0].sub!(/^\s+/, '') if !nested.empty? - else - result << nl - end - if !nested.empty? - result << nested << indent - end - result << '' << nl - else - result << nested - end - else - result << ' />' << nl - end - elsif ref.instance_of?(Array) - ref.each { |value| - if scalar(value) - result << indent << '<' << name << '>' - result << (@options['noescape'] ? value.to_s : escape_value(value.to_s)) - result << '' << nl - elsif value.instance_of?(Hash) - result << value_to_xml(value, name, indent) - else - result << indent << '<' << name << '>' << nl - result << value_to_xml(value, @options['anonymoustag'], indent + @options['indent']) - result << indent << '' << nl - end - } - else - # Probably, this is obsolete. - raise ArgumentError, "Can't encode a value of type: #{ref.type}." - end - @ancestors.pop if !scalar(ref) - result.join('') - end - - # Checks, if a certain value is a "scalar" value. Whatever - # that will be in Ruby ... ;-) - # - # value:: - # Value to be checked. - def scalar(value) - return false if value.instance_of?(Hash) || value.instance_of?(Array) - return true - end - - # Attempts to unfold a hash of hashes into an array of hashes. Returns - # a reference to th array on success or the original hash, if unfolding - # is not possible. - # - # parent:: - # - # hashref:: - # Reference to the hash to be unfolded. - def hash_to_array(parent, hashref) - arrayref = [] - hashref.each { |key, value| - return hashref unless value.instance_of?(Hash) - - if @options['keyattr'].instance_of?(Hash) - return hashref unless @options['keyattr'].has_key?(parent) - arrayref << { @options['keyattr'][parent][0] => key }.update(value) - else - arrayref << { @options['keyattr'][0] => key }.update(value) - end - } - arrayref - end - - # Replaces XML markup characters by their external entities. - # - # data:: - # The string to be escaped. - def escape_value(data) - return data if data.nil? || data == '' - result = data.dup - result.gsub!('&', '&') - result.gsub!('<', '<') - result.gsub!('>', '>') - result.gsub!('"', '"') - result.gsub!("'", ''') - result - end - - # Removes leading and trailing whitespace and sequences of - # whitespaces from a string. - # - # text:: - # String to be normalised. - def normalise_space(text) - text.sub!(/^\s+/, '') - text.sub!(/\s+$/, '') - text.gsub!(/\s\s+/, ' ') - text - end - - # Checks, if an object is nil, an empty String or an empty Hash. - # Thanks to Norbert Gawor for a bugfix. - # - # value:: - # Value to be checked for emptyness. - def empty(value) - case value - when Hash - return value.empty? - when String - return value !~ /\S/m - else - return value.nil? - end - end - - # Converts a document node into a String. - # If the node could not be converted into a String - # for any reason, default will be returned. - # - # node:: - # Document node to be converted. - # default:: - # Value to be returned, if node could not be converted. - def node_to_text(node, default = nil) - if node.instance_of?(Element) - return node.texts.join('') - elsif node.instance_of?(Attribute) - return node.value.nil? ? default : node.value.strip - elsif node.instance_of?(Text) - return node.to_s.strip - else - return default - end - end - - # Parses an XML string and returns the according document. - # - # xml_string:: - # XML string to be parsed. - # - # The following exception may be raised: - # - # REXML::ParseException:: - # If the specified file is not wellformed. - def parse(xml_string) - Document.new(xml_string) - end - - # Searches in a list of paths for a certain file. Returns - # the full path to the file, if it could be found. Otherwise, - # an exception will be raised. - # - # filename:: - # Name of the file to search for. - # searchpath:: - # List of paths to search in. - def find_xml_file(file, searchpath) - filename = File::basename(file) - - if filename != file - return file if File::file?(file) - else - searchpath.each { |path| - full_path = File::join(path, filename) - return full_path if File::file?(full_path) - } - end - - if searchpath.empty? - return file if File::file?(file) - raise ArgumentError, "File does not exist: #{file}." - end - raise ArgumentError, "Could not find <#{filename}> in <#{searchpath.join(':')}>" - end - - # Loads and parses an XML configuration file. - # - # filename:: - # Name of the configuration file to be loaded. - # - # The following exceptions may be raised: - # - # Errno::ENOENT:: - # If the specified file does not exist. - # REXML::ParseException:: - # If the specified file is not wellformed. - def load_xml_file(filename) - parse(File.readlines(filename).to_s) - end - - # Caches the data belonging to a certain file. - # - # data:: - # Data to be cached. - # filename:: - # Name of file the data was read from. - def put_into_cache(data, filename) - if @options.has_key?('cache') - @options['cache'].each { |scheme| - case(scheme) - when 'storable' - @@cache.save_storable(data, filename) - when 'mem_share' - @@cache.save_mem_share(data, filename) - when 'mem_copy' - @@cache.save_mem_copy(data, filename) - else - raise ArgumentError, "Unsupported caching scheme: <#{scheme}>." - end - } - end - end -end - -# vim:sw=2 -module ActionController #:nodoc: - module Verification #:nodoc: - def self.append_features(base) #:nodoc: - super - base.extend(ClassMethods) - end - - # This module provides a class-level method for specifying that certain - # actions are guarded against being called without certain prerequisites - # being met. This is essentially a special kind of before_filter. - # - # An action may be guarded against being invoked without certain request - # parameters being set, or without certain session values existing. - # - # When a verification is violated, values may be inserted into the flash, and - # a specified redirection is triggered. - # - # Usage: - # - # class GlobalController < ActionController::Base - # # prevent the #update_settings action from being invoked unless - # # the 'admin_privileges' request parameter exists. - # verify :params => "admin_privileges", :only => :update_post, - # :redirect_to => { :action => "settings" } - # - # # disallow a post from being updated if there was no information - # # submitted with the post, and if there is no active post in the - # # session, and if there is no "note" key in the flash. - # verify :params => "post", :session => "post", "flash" => "note", - # :only => :update_post, - # :add_flash => { "alert" => "Failed to create your message" }, - # :redirect_to => :category_url - # - module ClassMethods - # Verify the given actions so that if certain prerequisites are not met, - # the user is redirected to a different action. The +options+ parameter - # is a hash consisting of the following key/value pairs: - # - # * :params: a single key or an array of keys that must - # be in the params hash in order for the action(s) to be safely - # called. - # * :session: a single key or an array of keys that must - # be in the @session in order for the action(s) to be safely called. - # * :flash: a single key or an array of keys that must - # be in the flash in order for the action(s) to be safely called. - # * :method: a single key or an array of keys--any one of which - # must match the current request method in order for the action(s) to - # be safely called. (The key should be a symbol: :get or - # :post, for example.) - # * :xhr: true/false option to ensure that the request is coming - # from an Ajax call or not. - # * :add_flash: a hash of name/value pairs that should be merged - # into the session's flash if the prerequisites cannot be satisfied. - # * :redirect_to: the redirection parameters to be used when - # redirecting if the prerequisites cannot be satisfied. - # * :render: the render parameters to be used when - # the prerequisites cannot be satisfied. - # * :only: only apply this verification to the actions specified - # in the associated array (may also be a single value). - # * :except: do not apply this verification to the actions - # specified in the associated array (may also be a single value). - def verify(options={}) - filter_opts = { :only => options[:only], :except => options[:except] } - before_filter(filter_opts) do |c| - c.send :verify_action, options - end - end - end - - def verify_action(options) #:nodoc: - prereqs_invalid = - [*options[:params] ].find { |v| @params[v].nil? } || - [*options[:session]].find { |v| @session[v].nil? } || - [*options[:flash] ].find { |v| flash[v].nil? } - - if !prereqs_invalid && options[:method] - prereqs_invalid ||= - [*options[:method]].all? { |v| @request.method != v.to_sym } - end - - prereqs_invalid ||= (request.xhr? != options[:xhr]) unless options[:xhr].nil? - - if prereqs_invalid - flash.update(options[:add_flash]) if options[:add_flash] - unless performed? - render(options[:render]) if options[:render] - redirect_to(options[:redirect_to]) if options[:redirect_to] - end - return false - end - - true - end - private :verify_action - end -end -#-- -# Copyright (c) 2004 David Heinemeier Hansson -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -#++ - -$:.unshift(File.dirname(__FILE__)) unless - $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__))) - -unless defined?(ActiveSupport) - begin - $:.unshift(File.dirname(__FILE__) + "/../../activesupport/lib") - require 'active_support' - rescue LoadError - require 'rubygems' - require_gem 'activesupport' - end -end - -require 'action_controller/base' -require 'action_controller/deprecated_redirects' -require 'action_controller/request' -require 'action_controller/deprecated_request_methods' -require 'action_controller/rescue' -require 'action_controller/benchmarking' -require 'action_controller/flash' -require 'action_controller/filters' -require 'action_controller/layout' -require 'action_controller/dependencies' -require 'action_controller/mime_responds' -require 'action_controller/pagination' -require 'action_controller/scaffolding' -require 'action_controller/helpers' -require 'action_controller/cookies' -require 'action_controller/cgi_process' -require 'action_controller/caching' -require 'action_controller/verification' -require 'action_controller/streaming' -require 'action_controller/session_management' -require 'action_controller/components' -require 'action_controller/macros/auto_complete' -require 'action_controller/macros/in_place_editing' - -require 'action_view' -ActionController::Base.template_class = ActionView::Base - -ActionController::Base.class_eval do - include ActionController::Flash - include ActionController::Filters - include ActionController::Layout - include ActionController::Benchmarking - include ActionController::Rescue - include ActionController::Dependencies - include ActionController::MimeResponds - include ActionController::Pagination - include ActionController::Scaffolding - include ActionController::Helpers - include ActionController::Cookies - include ActionController::Caching - include ActionController::Verification - include ActionController::Streaming - include ActionController::SessionManagement - include ActionController::Components - include ActionController::Macros::AutoComplete - include ActionController::Macros::InPlaceEditing -end -module ActionPack #:nodoc: - module VERSION #:nodoc: - MAJOR = 1 - MINOR = 12 - TINY = 5 - - STRING = [MAJOR, MINOR, TINY].join('.') - end -end -#-- -# Copyright (c) 2004 David Heinemeier Hansson -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -#++ - -require 'action_pack/version' -require 'erb' - -module ActionView #:nodoc: - - class ActionViewError < StandardError #:nodoc: - end - - # Action View templates can be written in three ways. If the template file has a +.rhtml+ extension then it uses a mixture of ERb - # (included in Ruby) and HTML. If the template file has a +.rxml+ extension then Jim Weirich's Builder::XmlMarkup library is used. - # If the template file has a +.rjs+ extension then it will use ActionView::Helpers::PrototypeHelper::JavaScriptGenerator. - # - # = ERb - # - # You trigger ERb by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the - # following loop for names: - # - # Names of all the people - # <% for person in @people %> - # Name: <%= person.name %>
- # <% end %> - # - # The loop is setup in regular embedding tags <% %> and the name is written using the output embedding tag <%= %>. Note that this - # is not just a usage suggestion. Regular output functions like print or puts won't work with ERb templates. So this would be wrong: - # - # Hi, Mr. <% puts "Frodo" %> - # - # If you absolutely must write from within a function, you can use the TextHelper#concat - # - # <%- and -%> suppress leading and trailing whitespace, including the trailing newline, and can be used interchangeably with <% and %>. - # - # == Using sub templates - # - # Using sub templates allows you to sidestep tedious replication and extract common display structures in shared templates. The - # classic example is the use of a header and footer (even though the Action Pack-way would be to use Layouts): - # - # <%= render "shared/header" %> - # Something really specific and terrific - # <%= render "shared/footer" %> - # - # As you see, we use the output embeddings for the render methods. The render call itself will just return a string holding the - # result of the rendering. The output embedding writes it to the current template. - # - # But you don't have to restrict yourself to static includes. Templates can share variables amongst themselves by using instance - # variables defined using the regular embedding tags. Like this: - # - # <% @page_title = "A Wonderful Hello" %> - # <%= render "shared/header" %> - # - # Now the header can pick up on the @page_title variable and use it for outputting a title tag: - # - # <%= @page_title %> - # - # == Passing local variables to sub templates - # - # You can pass local variables to sub templates by using a hash with the variable names as keys and the objects as values: - # - # <%= render "shared/header", { "headline" => "Welcome", "person" => person } %> - # - # These can now be accessed in shared/header with: - # - # Headline: <%= headline %> - # First name: <%= person.first_name %> - # - # == Template caching - # - # By default, Rails will compile each template to a method in order to render it. When you alter a template, Rails will - # check the file's modification time and recompile it. - # - # == Builder - # - # Builder templates are a more programmatic alternative to ERb. They are especially useful for generating XML content. An +XmlMarkup+ object - # named +xml+ is automatically made available to templates with a +.rxml+ extension. - # - # Here are some basic examples: - # - # xml.em("emphasized") # => emphasized - # xml.em { xml.b("emp & bold") } # => emph & bold - # xml.a("A Link", "href"=>"http://onestepback.org") # => A Link - # xml.target("name"=>"compile", "option"=>"fast") # => - # # NOTE: order of attributes is not specified. - # - # Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following: - # - # xml.div { - # xml.h1(@person.name) - # xml.p(@person.bio) - # } - # - # would produce something like: - # - #
- #

David Heinemeier Hansson

- #

A product of Danish Design during the Winter of '79...

- #
- # - # A full-length RSS example actually used on Basecamp: - # - # xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do - # xml.channel do - # xml.title(@feed_title) - # xml.link(@url) - # xml.description "Basecamp: Recent items" - # xml.language "en-us" - # xml.ttl "40" - # - # for item in @recent_items - # xml.item do - # xml.title(item_title(item)) - # xml.description(item_description(item)) if item_description(item) - # xml.pubDate(item_pubDate(item)) - # xml.guid(@person.firm.account.url + @recent_items.url(item)) - # xml.link(@person.firm.account.url + @recent_items.url(item)) - # - # xml.tag!("dc:creator", item.author_name) if item_has_creator?(item) - # end - # end - # end - # end - # - # More builder documentation can be found at http://builder.rubyforge.org. - # - # == JavaScriptGenerator - # - # JavaScriptGenerator templates end in +.rjs+. Unlike conventional templates which are used to - # render the results of an action, these templates generate instructions on how to modify an already rendered page. This makes it easy to - # modify multiple elements on your page in one declarative Ajax response. Actions with these templates are called in the background with Ajax - # and make updates to the page where the request originated from. - # - # An instance of the JavaScriptGenerator object named +page+ is automatically made available to your template, which is implicitly wrapped in an ActionView::Helpers::PrototypeHelper#update_page block. - # - # When an .rjs action is called with +link_to_remote+, the generated JavaScript is automatically evaluated. Example: - # - # link_to_remote :url => {:action => 'delete'} - # - # The subsequently rendered +delete.rjs+ might look like: - # - # page.replace_html 'sidebar', :partial => 'sidebar' - # page.remove "person-#{@person.id}" - # page.visual_effect :highlight, 'user-list' - # - # This refreshes the sidebar, removes a person element and highlights the user list. - # - # See the ActionView::Helpers::PrototypeHelper::JavaScriptGenerator documentation for more details. - class Base - include ERB::Util - - attr_reader :first_render - attr_accessor :base_path, :assigns, :template_extension - attr_accessor :controller - - attr_reader :logger, :params, :request, :response, :session, :headers, :flash - - # Specify trim mode for the ERB compiler. Defaults to '-'. - # See ERB documentation for suitable values. - @@erb_trim_mode = '-' - cattr_accessor :erb_trim_mode - - # Specify whether file modification times should be checked to see if a template needs recompilation - @@cache_template_loading = false - cattr_accessor :cache_template_loading - - # Specify whether file extension lookup should be cached. - # Should be +false+ for development environments. Defaults to +true+. - @@cache_template_extensions = true - cattr_accessor :cache_template_extensions - - # Specify whether local_assigns should be able to use string keys. - # Defaults to +true+. String keys are deprecated and will be removed - # shortly. - @@local_assigns_support_string_keys = true - cattr_accessor :local_assigns_support_string_keys - - # Specify whether RJS responses should be wrapped in a try/catch block - # that alert()s the caught exception (and then re-raises it). - @@debug_rjs = false - cattr_accessor :debug_rjs - - @@template_handlers = HashWithIndifferentAccess.new - - module CompiledTemplates #:nodoc: - # holds compiled template code - end - include CompiledTemplates - - # maps inline templates to their method names - @@method_names = {} - # map method names to their compile time - @@compile_time = {} - # map method names to the names passed in local assigns so far - @@template_args = {} - # count the number of inline templates - @@inline_template_count = 0 - # maps template paths without extension to their file extension returned by pick_template_extension. - # if for a given path, path.ext1 and path.ext2 exist on the file system, the order of extensions - # used by pick_template_extension determines whether ext1 or ext2 will be stored - @@cached_template_extension = {} - - class ObjectWrapper < Struct.new(:value) #:nodoc: - end - - def self.load_helpers(helper_dir)#:nodoc: - Dir.foreach(helper_dir) do |helper_file| - next unless helper_file =~ /^([a-z][a-z_]*_helper).rb$/ - require File.join(helper_dir, $1) - helper_module_name = $1.camelize - class_eval("include ActionView::Helpers::#{helper_module_name}") if Helpers.const_defined?(helper_module_name) - end - end - - # Register a class that knows how to handle template files with the given - # extension. This can be used to implement new template types. - # The constructor for the class must take the ActiveView::Base instance - # as a parameter, and the class must implement a #render method that - # takes the contents of the template to render as well as the Hash of - # local assigns available to the template. The #render method ought to - # return the rendered template as a string. - def self.register_template_handler(extension, klass) - @@template_handlers[extension] = klass - end - - def initialize(base_path = nil, assigns_for_first_render = {}, controller = nil)#:nodoc: - @base_path, @assigns = base_path, assigns_for_first_render - @assigns_added = nil - @controller = controller - @logger = controller && controller.logger - end - - # Renders the template present at template_path. If use_full_path is set to true, - # it's relative to the template_root, otherwise it's absolute. The hash in local_assigns - # is made available as local variables. - def render_file(template_path, use_full_path = true, local_assigns = {}) #:nodoc: - @first_render ||= template_path - - if use_full_path - template_path_without_extension, template_extension = path_and_extension(template_path) - - if template_extension - template_file_name = full_template_path(template_path_without_extension, template_extension) - else - template_extension = pick_template_extension(template_path).to_s - template_file_name = full_template_path(template_path, template_extension) - end - else - template_file_name = template_path - template_extension = template_path.split('.').last - end - - template_source = nil # Don't read the source until we know that it is required - - begin - render_template(template_extension, template_source, template_file_name, local_assigns) - rescue Exception => e - if TemplateError === e - e.sub_template_of(template_file_name) - raise e - else - raise TemplateError.new(@base_path, template_file_name, @assigns, template_source, e) - end - end - end - - # Renders the template present at template_path (relative to the template_root). - # The hash in local_assigns is made available as local variables. - def render(options = {}, old_local_assigns = {}, &block) #:nodoc: - if options.is_a?(String) - render_file(options, true, old_local_assigns) - elsif options == :update - update_page(&block) - elsif options.is_a?(Hash) - options[:locals] ||= {} - options[:use_full_path] = options[:use_full_path].nil? ? true : options[:use_full_path] - - if options[:file] - render_file(options[:file], options[:use_full_path], options[:locals]) - elsif options[:partial] && options[:collection] - render_partial_collection(options[:partial], options[:collection], options[:spacer_template], options[:locals]) - elsif options[:partial] - render_partial(options[:partial], ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals]) - elsif options[:inline] - render_template(options[:type] || :rhtml, options[:inline], nil, options[:locals] || {}) - end - end - end - - # Renders the +template+ which is given as a string as either rhtml or rxml depending on template_extension. - # The hash in local_assigns is made available as local variables. - def render_template(template_extension, template, file_path = nil, local_assigns = {}) #:nodoc: - if handler = @@template_handlers[template_extension] - template ||= read_template_file(file_path, template_extension) # Make sure that a lazyily-read template is loaded. - delegate_render(handler, template, local_assigns) - else - compile_and_render_template(template_extension, template, file_path, local_assigns) - end - end - - # Render the provided template with the given local assigns. If the template has not been rendered with the provided - # local assigns yet, or if the template has been updated on disk, then the template will be compiled to a method. - # - - # Either, but not both, of template and file_path may be nil. If file_path is given, the template - # will only be read if it has to be compiled. - # - def compile_and_render_template(extension, template = nil, file_path = nil, local_assigns = {}) #:nodoc: - # compile the given template, if necessary - if compile_template?(template, file_path, local_assigns) - template ||= read_template_file(file_path, extension) - compile_template(extension, template, file_path, local_assigns) - end - - # Get the method name for this template and run it - method_name = @@method_names[file_path || template] - evaluate_assigns - - local_assigns = local_assigns.symbolize_keys if @@local_assigns_support_string_keys - - send(method_name, local_assigns) do |*name| - instance_variable_get "@content_for_#{name.first || 'layout'}" - end - end - - def pick_template_extension(template_path)#:nodoc: - if @@cache_template_extensions - @@cached_template_extension[template_path] ||= find_template_extension_for(template_path) - else - find_template_extension_for(template_path) - end - end - - def delegate_template_exists?(template_path)#:nodoc: - @@template_handlers.find { |k,| template_exists?(template_path, k) } - end - - def erb_template_exists?(template_path)#:nodoc: - template_exists?(template_path, :rhtml) - end - - def builder_template_exists?(template_path)#:nodoc: - template_exists?(template_path, :rxml) - end - - def javascript_template_exists?(template_path)#:nodoc: - template_exists?(template_path, :rjs) - end - - def file_exists?(template_path)#:nodoc: - template_file_name, template_file_extension = path_and_extension(template_path) - - if template_file_extension - template_exists?(template_file_name, template_file_extension) - else - cached_template_extension(template_path) || - %w(erb builder javascript delegate).any? do |template_type| - send("#{template_type}_template_exists?", template_path) - end - end - end - - # Returns true is the file may be rendered implicitly. - def file_public?(template_path)#:nodoc: - template_path.split('/').last[0,1] != '_' - end - - private - def full_template_path(template_path, extension) - "#{@base_path}/#{template_path}.#{extension}" - end - - def template_exists?(template_path, extension) - file_path = full_template_path(template_path, extension) - @@method_names.has_key?(file_path) || FileTest.exists?(file_path) - end - - def path_and_extension(template_path) - template_path_without_extension = template_path.sub(/\.(\w+)$/, '') - [ template_path_without_extension, $1 ] - end - - def cached_template_extension(template_path) - @@cache_template_extensions && @@cached_template_extension[template_path] - end - - def find_template_extension_for(template_path) - if match = delegate_template_exists?(template_path) - match.first.to_sym - elsif erb_template_exists?(template_path): :rhtml - elsif builder_template_exists?(template_path): :rxml - elsif javascript_template_exists?(template_path): :rjs - else - raise ActionViewError, "No rhtml, rxml, rjs or delegate template found for #{template_path}" - end - end - - # This method reads a template file. - def read_template_file(template_path, extension) - File.read(template_path) - end - - def evaluate_assigns - unless @assigns_added - assign_variables_from_controller - @assigns_added = true - end - end - - def delegate_render(handler, template, local_assigns) - handler.new(self).render(template, local_assigns) - end - - def assign_variables_from_controller - @assigns.each { |key, value| instance_variable_set("@#{key}", value) } - end - - - # Return true if the given template was compiled for a superset of the keys in local_assigns - def supports_local_assigns?(render_symbol, local_assigns) - local_assigns.empty? || - ((args = @@template_args[render_symbol]) && local_assigns.all? { |k,_| args.has_key?(k) }) - end - - # Check whether compilation is necessary. - # Compile if the inline template or file has not been compiled yet. - # Or if local_assigns has a new key, which isn't supported by the compiled code yet. - # Or if the file has changed on disk and checking file mods hasn't been disabled. - def compile_template?(template, file_name, local_assigns) - method_key = file_name || template - render_symbol = @@method_names[method_key] - - if @@compile_time[render_symbol] && supports_local_assigns?(render_symbol, local_assigns) - if file_name && !@@cache_template_loading - @@compile_time[render_symbol] < File.mtime(file_name) || (File.symlink?(file_name) ? - @@compile_time[render_symbol] < File.lstat(file_name).mtime : false) - end - else - true - end - end - - # Create source code for given template - def create_template_source(extension, template, render_symbol, locals) - if template_requires_setup?(extension) - body = case extension.to_sym - when :rxml - "xml = Builder::XmlMarkup.new(:indent => 2)\n" + - "@controller.headers['Content-Type'] ||= 'application/xml'\n" + - template - when :rjs - "@controller.headers['Content-Type'] ||= 'text/javascript'\n" + - "update_page do |page|\n#{template}\nend" - end - else - body = ERB.new(template, nil, @@erb_trim_mode).src - end - - @@template_args[render_symbol] ||= {} - locals_keys = @@template_args[render_symbol].keys | locals - @@template_args[render_symbol] = locals_keys.inject({}) { |h, k| h[k] = true; h } - - locals_code = "" - locals_keys.each do |key| - locals_code << "#{key} = local_assigns[:#{key}] if local_assigns.has_key?(:#{key})\n" - end - - "def #{render_symbol}(local_assigns)\n#{locals_code}#{body}\nend" - end - - def template_requires_setup?(extension) - templates_requiring_setup.include? extension.to_s - end - - def templates_requiring_setup - %w(rxml rjs) - end - - def assign_method_name(extension, template, file_name) - method_name = '_run_' - method_name << "#{extension}_" if extension - - if file_name - file_path = File.expand_path(file_name) - base_path = File.expand_path(@base_path) - - i = file_path.index(base_path) - l = base_path.length - - method_name_file_part = i ? file_path[i+l+1,file_path.length-l-1] : file_path.clone - method_name_file_part.sub!(/\.r(html|xml|js)$/,'') - method_name_file_part.tr!('/:-', '_') - method_name_file_part.gsub!(/[^a-zA-Z0-9_]/){|s| s[0].to_s} - - method_name += method_name_file_part - else - @@inline_template_count += 1 - method_name << @@inline_template_count.to_s - end - - @@method_names[file_name || template] = method_name.intern - end - - def compile_template(extension, template, file_name, local_assigns) - method_key = file_name || template - - render_symbol = @@method_names[method_key] || assign_method_name(extension, template, file_name) - render_source = create_template_source(extension, template, render_symbol, local_assigns.keys) - - line_offset = @@template_args[render_symbol].size - if extension - case extension.to_sym - when :rxml, :rjs - line_offset += 2 - end - end - - begin - unless file_name.blank? - CompiledTemplates.module_eval(render_source, file_name, -line_offset) - else - CompiledTemplates.module_eval(render_source, 'compiled-template', -line_offset) - end - rescue Object => e - if logger - logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}" - logger.debug "Function body: #{render_source}" - logger.debug "Backtrace: #{e.backtrace.join("\n")}" - end - - raise TemplateError.new(@base_path, method_key, @assigns, template, e) - end - - @@compile_time[render_symbol] = Time.now - # logger.debug "Compiled template #{method_key}\n ==> #{render_symbol}" if logger - end - end -end - -require 'action_view/template_error' - -module ActionView - - # CompiledTemplates modules hold methods that have been compiled. - # Templates are compiled into these methods so that they do not need to be - # re-read and re-parsed each request. - # - # Each template may be compiled into one or more methods. Each method accepts a given - # set of parameters which is used to implement local assigns passing. - # - # To use a compiled template module, create a new instance and include it into the class - # in which you want the template to be rendered. - class CompiledTemplates < Module #:nodoc: - attr_reader :method_names - - def initialize - @method_names = Hash.new do |hash, key| - hash[key] = "__compiled_method_#{(hash.length + 1)}" - end - @mtimes = {} - end - - # Return the full key for the given identifier and argument names - def full_key(identifier, arg_names) - [identifier, arg_names] - end - - # Return the selector for this method or nil if it has not been compiled - def selector(identifier, arg_names) - key = full_key(identifier, arg_names) - method_names.key?(key) ? method_names[key] : nil - end - alias :compiled? :selector - - # Return the time at which the method for the given identifier and argument names was compiled. - def mtime(identifier, arg_names) - @mtimes[full_key(identifier, arg_names)] - end - - # Compile the provided source code for the given argument names and with the given initial line number. - # The identifier should be unique to this source. - # - # The file_name, if provided will appear in backtraces. If not provded, the file_name defaults - # to the identifier. - # - # This method will return the selector for the compiled version of this method. - def compile_source(identifier, arg_names, source, initial_line_number = 0, file_name = nil) - file_name ||= identifier - name = method_names[full_key(identifier, arg_names)] - arg_desc = arg_names.empty? ? '' : "(#{arg_names * ', '})" - fake_file_name = "#{file_name}#{arg_desc}" # Include the arguments for this version (for now) - - method_def = wrap_source(name, arg_names, source) - - begin - module_eval(method_def, fake_file_name, initial_line_number) - @mtimes[full_key(identifier, arg_names)] = Time.now - rescue Object => e - e.blame_file! identifier - raise - end - name - end - - # Wrap the provided source in a def ... end block. - def wrap_source(name, arg_names, source) - "def #{name}(#{arg_names * ', '})\n#{source}\nend" - end - end -end -require 'cgi' -require File.dirname(__FILE__) + '/form_helper' - -module ActionView - class Base - @@field_error_proc = Proc.new{ |html_tag, instance| "
#{html_tag}
" } - cattr_accessor :field_error_proc - end - - module Helpers - # The Active Record Helper makes it easier to create forms for records kept in instance variables. The most far-reaching is the form - # method that creates a complete form for all the basic content types of the record (not associations or aggregations, though). This - # is a great of making the record quickly available for editing, but likely to prove lackluster for a complicated real-world form. - # In that case, it's better to use the input method and the specialized form methods in link:classes/ActionView/Helpers/FormHelper.html - module ActiveRecordHelper - # Returns a default input tag for the type of object returned by the method. Example - # (title is a VARCHAR column and holds "Hello World"): - # input("post", "title") => - # - def input(record_name, method, options = {}) - InstanceTag.new(record_name, method, self).to_tag(options) - end - - # Returns an entire form with input tags and everything for a specified Active Record object. Example - # (post is a new record that has a title using VARCHAR and a body using TEXT): - # form("post") => - #
- #

- #
- # - #

- #

- #
- # - #

- # - #
- # - # It's possible to specialize the form builder by using a different action name and by supplying another - # block renderer. Example (entry is a new record that has a message attribute using VARCHAR): - # - # form("entry", :action => "sign", :input_block => - # Proc.new { |record, column| "#{column.human_name}: #{input(record, column.name)}
" }) => - # - #
- # Message: - #
- # - #
- # - # It's also possible to add additional content to the form by giving it a block, such as: - # - # form("entry", :action => "sign") do |form| - # form << content_tag("b", "Department") - # form << collection_select("department", "id", @departments, "id", "name") - # end - def form(record_name, options = {}) - record = instance_variable_get("@#{record_name}") - - options = options.symbolize_keys - options[:action] ||= record.new_record? ? "create" : "update" - action = url_for(:action => options[:action], :id => record) - - submit_value = options[:submit_value] || options[:action].gsub(/[^\w]/, '').capitalize - - contents = '' - contents << hidden_field(record_name, :id) unless record.new_record? - contents << all_input_tags(record, record_name, options) - yield contents if block_given? - contents << submit_tag(submit_value) - - content_tag('form', contents, :action => action, :method => 'post', :enctype => options[:multipart] ? 'multipart/form-data': nil) - end - - # Returns a string containing the error message attached to the +method+ on the +object+, if one exists. - # This error message is wrapped in a DIV tag, which can be specialized to include both a +prepend_text+ and +append_text+ - # to properly introduce the error and a +css_class+ to style it accordingly. Examples (post has an error message - # "can't be empty" on the title attribute): - # - # <%= error_message_on "post", "title" %> => - #
can't be empty
- # - # <%= error_message_on "post", "title", "Title simply ", " (or it won't work)", "inputError" %> => - #
Title simply can't be empty (or it won't work)
- def error_message_on(object, method, prepend_text = "", append_text = "", css_class = "formError") - if errors = instance_variable_get("@#{object}").errors.on(method) - content_tag("div", "#{prepend_text}#{errors.is_a?(Array) ? errors.first : errors}#{append_text}", :class => css_class) - end - end - - # Returns a string with a div containing all the error messages for the object located as an instance variable by the name - # of object_name. This div can be tailored by the following options: - # - # * header_tag - Used for the header of the error div (default: h2) - # * id - The id of the error div (default: errorExplanation) - # * class - The class of the error div (default: errorExplanation) - # - # NOTE: This is a pre-packaged presentation of the errors with embedded strings and a certain HTML structure. If what - # you need is significantly different from the default presentation, it makes plenty of sense to access the object.errors - # instance yourself and set it up. View the source of this method to see how easy it is. - def error_messages_for(object_name, options = {}) - options = options.symbolize_keys - object = instance_variable_get("@#{object_name}") - if object && !object.errors.empty? - content_tag("div", - content_tag( - options[:header_tag] || "h2", - "#{pluralize(object.errors.count, "error")} prohibited this #{object_name.to_s.gsub("_", " ")} from being saved" - ) + - content_tag("p", "There were problems with the following fields:") + - content_tag("ul", object.errors.full_messages.collect { |msg| content_tag("li", msg) }), - "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation" - ) - else - "" - end - end - - private - def all_input_tags(record, record_name, options) - input_block = options[:input_block] || default_input_block - record.class.content_columns.collect{ |column| input_block.call(record_name, column) }.join("\n") - end - - def default_input_block - Proc.new { |record, column| %(


#{input(record, column.name)}

) } - end - end - - class InstanceTag #:nodoc: - def to_tag(options = {}) - case column_type - when :string - field_type = @method_name.include?("password") ? "password" : "text" - to_input_field_tag(field_type, options) - when :text - to_text_area_tag(options) - when :integer, :float - to_input_field_tag("text", options) - when :date - to_date_select_tag(options) - when :datetime, :timestamp - to_datetime_select_tag(options) - when :boolean - to_boolean_select_tag(options) - end - end - - alias_method :tag_without_error_wrapping, :tag - def tag(name, options) - if object.respond_to?("errors") && object.errors.respond_to?("on") - error_wrapping(tag_without_error_wrapping(name, options), object.errors.on(@method_name)) - else - tag_without_error_wrapping(name, options) - end - end - - alias_method :content_tag_without_error_wrapping, :content_tag - def content_tag(name, value, options) - if object.respond_to?("errors") && object.errors.respond_to?("on") - error_wrapping(content_tag_without_error_wrapping(name, value, options), object.errors.on(@method_name)) - else - content_tag_without_error_wrapping(name, value, options) - end - end - - alias_method :to_date_select_tag_without_error_wrapping, :to_date_select_tag - def to_date_select_tag(options = {}) - if object.respond_to?("errors") && object.errors.respond_to?("on") - error_wrapping(to_date_select_tag_without_error_wrapping(options), object.errors.on(@method_name)) - else - to_date_select_tag_without_error_wrapping(options) - end - end - - alias_method :to_datetime_select_tag_without_error_wrapping, :to_datetime_select_tag - def to_datetime_select_tag(options = {}) - if object.respond_to?("errors") && object.errors.respond_to?("on") - error_wrapping(to_datetime_select_tag_without_error_wrapping(options), object.errors.on(@method_name)) - else - to_datetime_select_tag_without_error_wrapping(options) - end - end - - def error_wrapping(html_tag, has_error) - has_error ? Base.field_error_proc.call(html_tag, self) : html_tag - end - - def error_message - object.errors.on(@method_name) - end - - def column_type - object.send("column_for_attribute", @method_name).type - end - end - end -end -require 'cgi' -require File.dirname(__FILE__) + '/url_helper' -require File.dirname(__FILE__) + '/tag_helper' - -module ActionView - module Helpers - # Provides methods for linking a HTML page together with other assets, such as javascripts, stylesheets, and feeds. - module AssetTagHelper - # Returns a link tag that browsers and news readers can use to auto-detect a RSS or ATOM feed for this page. The +type+ can - # either be :rss (default) or :atom and the +options+ follow the url_for style of declaring a link target. - # - # Examples: - # auto_discovery_link_tag # => - # - # auto_discovery_link_tag(:atom) # => - # - # auto_discovery_link_tag(:rss, {:action => "feed"}) # => - # - # auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"}) # => - # - def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {}) - tag( - "link", - "rel" => tag_options[:rel] || "alternate", - "type" => tag_options[:type] || "application/#{type}+xml", - "title" => tag_options[:title] || type.to_s.upcase, - "href" => url_options.is_a?(Hash) ? url_for(url_options.merge(:only_path => false)) : url_options - ) - end - - # Returns path to a javascript asset. Example: - # - # javascript_path "xmlhr" # => /javascripts/xmlhr.js - def javascript_path(source) - compute_public_path(source, 'javascripts', 'js') - end - - JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'] unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES) - @@javascript_default_sources = JAVASCRIPT_DEFAULT_SOURCES.dup - - # Returns a script include tag per source given as argument. Examples: - # - # javascript_include_tag "xmlhr" # => - # - # - # javascript_include_tag "common.javascript", "/elsewhere/cools" # => - # - # - # - # javascript_include_tag :defaults # => - # - # - # ... - # *see below - # - # If there's an application.js file in your public/javascripts directory, - # javascript_include_tag :defaults will automatically include it. This file - # facilitates the inclusion of small snippets of JavaScript code, along the lines of - # controllers/application.rb and helpers/application_helper.rb. - def javascript_include_tag(*sources) - options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { } - - if sources.include?(:defaults) - sources = sources[0..(sources.index(:defaults))] + - @@javascript_default_sources.dup + - sources[(sources.index(:defaults) + 1)..sources.length] - - sources.delete(:defaults) - sources << "application" if defined?(RAILS_ROOT) && File.exists?("#{RAILS_ROOT}/public/javascripts/application.js") - end - - sources.collect { |source| - source = javascript_path(source) - content_tag("script", "", { "type" => "text/javascript", "src" => source }.merge(options)) - }.join("\n") - end - - # Register one or more additional JavaScript files to be included when - # - # javascript_include_tag :defaults - # - # is called. This method is intended to be called only from plugin initialization - # to register extra .js files the plugin installed in public/javascripts. - def self.register_javascript_include_default(*sources) - @@javascript_default_sources.concat(sources) - end - - def self.reset_javascript_include_default #:nodoc: - @@javascript_default_sources = JAVASCRIPT_DEFAULT_SOURCES.dup - end - - # Returns path to a stylesheet asset. Example: - # - # stylesheet_path "style" # => /stylesheets/style.css - def stylesheet_path(source) - compute_public_path(source, 'stylesheets', 'css') - end - - # Returns a css link tag per source given as argument. Examples: - # - # stylesheet_link_tag "style" # => - # - # - # stylesheet_link_tag "style", :media => "all" # => - # - # - # stylesheet_link_tag "random.styles", "/css/stylish" # => - # - # - def stylesheet_link_tag(*sources) - options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { } - sources.collect { |source| - source = stylesheet_path(source) - tag("link", { "rel" => "Stylesheet", "type" => "text/css", "media" => "screen", "href" => source }.merge(options)) - }.join("\n") - end - - # Returns path to an image asset. Example: - # - # The +src+ can be supplied as a... - # * full path, like "/my_images/image.gif" - # * file name, like "rss.gif", that gets expanded to "/images/rss.gif" - # * file name without extension, like "logo", that gets expanded to "/images/logo.png" - def image_path(source) - compute_public_path(source, 'images', 'png') - end - - # Returns an image tag converting the +options+ into html options on the tag, but with these special cases: - # - # * :alt - If no alt text is given, the file name part of the +src+ is used (capitalized and without the extension) - # * :size - Supplied as "XxY", so "30x45" becomes width="30" and height="45" - # - # The +src+ can be supplied as a... - # * full path, like "/my_images/image.gif" - # * file name, like "rss.gif", that gets expanded to "/images/rss.gif" - # * file name without extension, like "logo", that gets expanded to "/images/logo.png" - def image_tag(source, options = {}) - options.symbolize_keys! - - options[:src] = image_path(source) - options[:alt] ||= File.basename(options[:src], '.*').split('.').first.capitalize - - if options[:size] - options[:width], options[:height] = options[:size].split("x") - options.delete :size - end - - tag("img", options) - end - - private - def compute_public_path(source, dir, ext) - source = "/#{dir}/#{source}" unless source.first == "/" || source.include?(":") - source << ".#{ext}" unless source.split("/").last.include?(".") - source << '?' + rails_asset_id(source) if defined?(RAILS_ROOT) && %r{^[-a-z]+://} !~ source - source = "#{@controller.request.relative_url_root}#{source}" unless %r{^[-a-z]+://} =~ source - source = ActionController::Base.asset_host + source unless source.include?(":") - source - end - - def rails_asset_id(source) - ENV["RAILS_ASSET_ID"] || - File.mtime("#{RAILS_ROOT}/public/#{source}").to_i.to_s rescue "" - end - end - end -end -require 'benchmark' - -module ActionView - module Helpers - module BenchmarkHelper - # Measures the execution time of a block in a template and reports the result to the log. Example: - # - # <% benchmark "Notes section" do %> - # <%= expensive_notes_operation %> - # <% end %> - # - # Will add something like "Notes section (0.34523)" to the log. - # - # You may give an optional logger level as the second argument - # (:debug, :info, :warn, :error). The default is :info. - def benchmark(message = "Benchmarking", level = :info) - if @logger - real = Benchmark.realtime { yield } - @logger.send level, "#{message} (#{'%.5f' % real})" - end - end - end - end -end -module ActionView - module Helpers - # See ActionController::Caching::Fragments for usage instructions. - module CacheHelper - def cache(name = {}, &block) - @controller.cache_erb_fragment(block, name) - end - end - end -end -module ActionView - module Helpers - # Capture lets you extract parts of code which - # can be used in other points of the template or even layout file. - # - # == Capturing a block into an instance variable - # - # <% @script = capture do %> - # [some html...] - # <% end %> - # - # == Add javascript to header using content_for - # - # content_for("name") is a wrapper for capture which will - # make the fragment available by name to a yielding layout or template. - # - # layout.rhtml: - # - # - # - # layout with js - # - # - # - # <%= yield %> - # - # - # - # view.rhtml - # - # This page shows an alert box! - # - # <% content_for("script") do %> - # alert('hello world') - # <% end %> - # - # Normal view text - module CaptureHelper - # Capture allows you to extract a part of the template into an - # instance variable. You can use this instance variable anywhere - # in your templates and even in your layout. - # - # Example of capture being used in a .rhtml page: - # - # <% @greeting = capture do %> - # Welcome To my shiny new web page! - # <% end %> - # - # Example of capture being used in a .rxml page: - # - # @greeting = capture do - # 'Welcome To my shiny new web page!' - # end - def capture(*args, &block) - # execute the block - begin - buffer = eval("_erbout", block.binding) - rescue - buffer = nil - end - - if buffer.nil? - capture_block(*args, &block) - else - capture_erb_with_buffer(buffer, *args, &block) - end - end - - # Calling content_for stores the block of markup for later use. - # Subsequently, you can make calls to it by name with yield - # in another template or in the layout. - # - # Example: - # - # <% content_for("header") do %> - # alert('hello world') - # <% end %> - # - # You can use yield :header anywhere in your templates. - # - # <%= yield :header %> - # - # NOTE: Beware that content_for is ignored in caches. So you shouldn't use it - # for elements that are going to be fragment cached. - # - # The deprecated way of accessing a content_for block was to use a instance variable - # named @@content_for_#{name_of_the_content_block}@. So <%= content_for('footer') %> - # would be avaiable as <%= @content_for_footer %>. The preferred notation now is - # <%= yield :footer %>. - def content_for(name, &block) - eval "@content_for_#{name} = (@content_for_#{name} || '') + capture(&block)" - end - - private - def capture_block(*args, &block) - block.call(*args) - end - - def capture_erb(*args, &block) - buffer = eval("_erbout", block.binding) - capture_erb_with_buffer(buffer, *args, &block) - end - - def capture_erb_with_buffer(buffer, *args, &block) - pos = buffer.length - block.call(*args) - - # extract the block - data = buffer[pos..-1] - - # replace it in the original with empty string - buffer[pos..-1] = '' - - data - end - - def erb_content_for(name, &block) - eval "@content_for_#{name} = (@content_for_#{name} || '') + capture_erb(&block)" - end - - def block_content_for(name, &block) - eval "@content_for_#{name} = (@content_for_#{name} || '') + capture_block(&block)" - end - end - end -end -require "date" - -module ActionView - module Helpers - # The Date Helper primarily creates select/option tags for different kinds of dates and date elements. All of the select-type methods - # share a number of common options that are as follows: - # - # * :prefix - overwrites the default prefix of "date" used for the select names. So specifying "birthday" would give - # birthday[month] instead of date[month] if passed to the select_month method. - # * :include_blank - set to true if it should be possible to set an empty date. - # * :discard_type - set to true if you want to discard the type part of the select name. If set to true, the select_month - # method would use simply "date" (which can be overwritten using :prefix) instead of "date[month]". - module DateHelper - DEFAULT_PREFIX = 'date' unless const_defined?('DEFAULT_PREFIX') - - # Reports the approximate distance in time between two Time objects or integers. - # For example, if the distance is 47 minutes, it'll return - # "about 1 hour". See the source for the complete wording list. - # - # Integers are interpreted as seconds. So, - # distance_of_time_in_words(50) returns "less than a minute". - # - # Set include_seconds to true if you want more detailed approximations if distance < 1 minute - def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false) - from_time = from_time.to_time if from_time.respond_to?(:to_time) - to_time = to_time.to_time if to_time.respond_to?(:to_time) - distance_in_minutes = (((to_time - from_time).abs)/60).round - distance_in_seconds = ((to_time - from_time).abs).round - - case distance_in_minutes - when 0..1 - return (distance_in_minutes==0) ? 'less than a minute' : '1 minute' unless include_seconds - case distance_in_seconds - when 0..5 then 'less than 5 seconds' - when 6..10 then 'less than 10 seconds' - when 11..20 then 'less than 20 seconds' - when 21..40 then 'half a minute' - when 41..59 then 'less than a minute' - else '1 minute' - end - - when 2..45 then "#{distance_in_minutes} minutes" - when 46..90 then 'about 1 hour' - when 90..1440 then "about #{(distance_in_minutes.to_f / 60.0).round} hours" - when 1441..2880 then '1 day' - else "#{(distance_in_minutes / 1440).round} days" - end - end - - # Like distance_of_time_in_words, but where to_time is fixed to Time.now. - def time_ago_in_words(from_time, include_seconds = false) - distance_of_time_in_words(from_time, Time.now, include_seconds) - end - - alias_method :distance_of_time_in_words_to_now, :time_ago_in_words - - # Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based attribute (identified by - # +method+) on an object assigned to the template (identified by +object+). It's possible to tailor the selects through the +options+ hash, - # which accepts all the keys that each of the individual select builders do (like :use_month_numbers for select_month) as well as a range of - # discard options. The discard options are :discard_year, :discard_month and :discard_day. Set to true, they'll - # drop the respective select. Discarding the month select will also automatically discard the day select. It's also possible to explicitly - # set the order of the tags using the :order option with an array of symbols :year, :month and :day in - # the desired order. Symbols may be omitted and the respective select is not included. - # - # Passing :disabled => true as part of the +options+ will make elements inaccessible for change. - # - # NOTE: Discarded selects will default to 1. So if no month select is available, January will be assumed. - # - # Examples: - # - # date_select("post", "written_on") - # date_select("post", "written_on", :start_year => 1995) - # date_select("post", "written_on", :start_year => 1995, :use_month_numbers => true, - # :discard_day => true, :include_blank => true) - # date_select("post", "written_on", :order => [:day, :month, :year]) - # date_select("user", "birthday", :order => [:month, :day]) - # - # The selects are prepared for multi-parameter assignment to an Active Record object. - def date_select(object_name, method, options = {}) - InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_date_select_tag(options) - end - - # Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based - # attribute (identified by +method+) on an object assigned to the template (identified by +object+). Examples: - # - # datetime_select("post", "written_on") - # datetime_select("post", "written_on", :start_year => 1995) - # - # The selects are prepared for multi-parameter assignment to an Active Record object. - def datetime_select(object_name, method, options = {}) - InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_datetime_select_tag(options) - end - - # Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+. - def select_date(date = Date.today, options = {}) - select_year(date, options) + select_month(date, options) + select_day(date, options) - end - - # Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+. - def select_datetime(datetime = Time.now, options = {}) - select_year(datetime, options) + select_month(datetime, options) + select_day(datetime, options) + - select_hour(datetime, options) + select_minute(datetime, options) - end - - # Returns a set of html select-tags (one for hour and minute) - def select_time(datetime = Time.now, options = {}) - h = select_hour(datetime, options) + select_minute(datetime, options) + (options[:include_seconds] ? select_second(datetime, options) : '') - end - - # Returns a select tag with options for each of the seconds 0 through 59 with the current second selected. - # The second can also be substituted for a second number. - # Override the field name using the :field_name option, 'second' by default. - def select_second(datetime, options = {}) - second_options = [] - - 0.upto(59) do |second| - second_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.sec) == second) ? - %(\n) : - %(\n) - ) - end - - select_html(options[:field_name] || 'second', second_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled]) - end - - # Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected. - # Also can return a select tag with options by minute_step from 0 through 59 with the 00 minute selected - # The minute can also be substituted for a minute number. - # Override the field name using the :field_name option, 'minute' by default. - def select_minute(datetime, options = {}) - minute_options = [] - - 0.step(59, options[:minute_step] || 1) do |minute| - minute_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.min) == minute) ? - %(\n) : - %(\n) - ) - end - - select_html(options[:field_name] || 'minute', minute_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled]) - end - - # Returns a select tag with options for each of the hours 0 through 23 with the current hour selected. - # The hour can also be substituted for a hour number. - # Override the field name using the :field_name option, 'hour' by default. - def select_hour(datetime, options = {}) - hour_options = [] - - 0.upto(23) do |hour| - hour_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.hour) == hour) ? - %(\n) : - %(\n) - ) - end - - select_html(options[:field_name] || 'hour', hour_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled]) - end - - # Returns a select tag with options for each of the days 1 through 31 with the current day selected. - # The date can also be substituted for a hour number. - # Override the field name using the :field_name option, 'day' by default. - def select_day(date, options = {}) - day_options = [] - - 1.upto(31) do |day| - day_options << ((date && (date.kind_of?(Fixnum) ? date : date.day) == day) ? - %(\n) : - %(\n) - ) - end - - select_html(options[:field_name] || 'day', day_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled]) - end - - # Returns a select tag with options for each of the months January through December with the current month selected. - # The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are used as values - # (what's submitted to the server). It's also possible to use month numbers for the presentation instead of names -- - # set the :use_month_numbers key in +options+ to true for this to happen. If you want both numbers and names, - # set the :add_month_numbers key in +options+ to true. Examples: - # - # select_month(Date.today) # Will use keys like "January", "March" - # select_month(Date.today, :use_month_numbers => true) # Will use keys like "1", "3" - # select_month(Date.today, :add_month_numbers => true) # Will use keys like "1 - January", "3 - March" - # - # Override the field name using the :field_name option, 'month' by default. - # - # If you would prefer to show month names as abbreviations, set the - # :use_short_month key in +options+ to true. - def select_month(date, options = {}) - month_options = [] - month_names = options[:use_short_month] ? Date::ABBR_MONTHNAMES : Date::MONTHNAMES - - 1.upto(12) do |month_number| - month_name = if options[:use_month_numbers] - month_number - elsif options[:add_month_numbers] - month_number.to_s + ' - ' + month_names[month_number] - else - month_names[month_number] - end - - month_options << ((date && (date.kind_of?(Fixnum) ? date : date.month) == month_number) ? - %(\n) : - %(\n) - ) - end - - select_html(options[:field_name] || 'month', month_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled]) - end - - # Returns a select tag with options for each of the five years on each side of the current, which is selected. The five year radius - # can be changed using the :start_year and :end_year keys in the +options+. Both ascending and descending year - # lists are supported by making :start_year less than or greater than :end_year. The date can also be - # substituted for a year given as a number. Example: - # - # select_year(Date.today, :start_year => 1992, :end_year => 2007) # ascending year values - # select_year(Date.today, :start_year => 2005, :end_year => 1900) # descending year values - # - # Override the field name using the :field_name option, 'year' by default. - def select_year(date, options = {}) - year_options = [] - y = date ? (date.kind_of?(Fixnum) ? (y = (date == 0) ? Date.today.year : date) : date.year) : Date.today.year - - start_year, end_year = (options[:start_year] || y-5), (options[:end_year] || y+5) - step_val = start_year < end_year ? 1 : -1 - - start_year.step(end_year, step_val) do |year| - year_options << ((date && (date.kind_of?(Fixnum) ? date : date.year) == year) ? - %(\n) : - %(\n) - ) - end - - select_html(options[:field_name] || 'year', year_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled]) - end - - private - def select_html(type, options, prefix = nil, include_blank = false, discard_type = false, disabled = false) - select_html = %(\n" - end - - def leading_zero_on_single_digits(number) - number > 9 ? number : "0#{number}" - end - end - - class InstanceTag #:nodoc: - include DateHelper - - def to_date_select_tag(options = {}) - defaults = { :discard_type => true } - options = defaults.merge(options) - options_with_prefix = Proc.new { |position| options.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") } - date = options[:include_blank] ? (value || 0) : (value || Date.today) - - date_select = '' - options[:order] = [:month, :year, :day] if options[:month_before_year] # For backwards compatibility - options[:order] ||= [:year, :month, :day] - - position = {:year => 1, :month => 2, :day => 3} - - discard = {} - discard[:year] = true if options[:discard_year] - discard[:month] = true if options[:discard_month] - discard[:day] = true if options[:discard_day] or options[:discard_month] - - options[:order].each do |param| - date_select << self.send("select_#{param}", date, options_with_prefix.call(position[param])) unless discard[param] - end - - date_select - end - - def to_datetime_select_tag(options = {}) - defaults = { :discard_type => true } - options = defaults.merge(options) - options_with_prefix = Proc.new { |position| options.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") } - datetime = options[:include_blank] ? (value || nil) : (value || Time.now) - - datetime_select = select_year(datetime, options_with_prefix.call(1)) - datetime_select << select_month(datetime, options_with_prefix.call(2)) unless options[:discard_month] - datetime_select << select_day(datetime, options_with_prefix.call(3)) unless options[:discard_day] || options[:discard_month] - datetime_select << ' — ' + select_hour(datetime, options_with_prefix.call(4)) unless options[:discard_hour] - datetime_select << ' : ' + select_minute(datetime, options_with_prefix.call(5)) unless options[:discard_minute] || options[:discard_hour] - - datetime_select - end - end - - class FormBuilder - def date_select(method, options = {}) - @template.date_select(@object_name, method, options.merge(:object => @object)) - end - - def datetime_select(method, options = {}) - @template.datetime_select(@object_name, method, options.merge(:object => @object)) - end - end - end -end -module ActionView - module Helpers - # Provides a set of methods for making it easier to locate problems. - module DebugHelper - # Returns a
-tag set with the +object+ dumped by YAML. Very readable way to inspect an object.
-      def debug(object)
-        begin
-          Marshal::dump(object)
-          "
#{h(object.to_yaml).gsub("  ", "  ")}
" - rescue Object => e - # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback - "#{h(object.inspect)}" - end - end - end - end -endrequire 'cgi' -require File.dirname(__FILE__) + '/date_helper' -require File.dirname(__FILE__) + '/tag_helper' - -module ActionView - module Helpers - # Provides a set of methods for working with forms and especially forms related to objects assigned to the template. - # The following is an example of a complete form for a person object that works for both creates and updates built - # with all the form helpers. The @person object was assigned by an action on the controller: - #
- # Name: - # <%= text_field "person", "name", "size" => 20 %> - # - # Password: - # <%= password_field "person", "password", "maxsize" => 20 %> - # - # Single?: - # <%= check_box "person", "single" %> - # - # Description: - # <%= text_area "person", "description", "cols" => 20 %> - # - # - #
- # - # ...is compiled to: - # - #
- # Name: - # - # - # Password: - # - # - # Single?: - # - # - # Description: - # - # - # - #
- # - # If the object name contains square brackets the id for the object will be inserted. Example: - # - # <%= text_field "person[]", "name" %> - # - # ...becomes: - # - # - # - # If the helper is being used to generate a repetitive sequence of similar form elements, for example in a partial - # used by render_collection_of_partials, the "index" option may come in handy. Example: - # - # <%= text_field "person", "name", "index" => 1 %> - # - # becomes - # - # - # - # There's also methods for helping to build form tags in link:classes/ActionView/Helpers/FormOptionsHelper.html, - # link:classes/ActionView/Helpers/DateHelper.html, and link:classes/ActionView/Helpers/ActiveRecordHelper.html - module FormHelper - # Creates a form and a scope around a specific model object, which is then used as a base for questioning about - # values for the fields. Examples: - # - # <% form_for :person, @person, :url => { :action => "update" } do |f| %> - # First name: <%= f.text_field :first_name %> - # Last name : <%= f.text_field :last_name %> - # Biography : <%= f.text_area :biography %> - # Admin? : <%= f.check_box :admin %> - # <% end %> - # - # Worth noting is that the form_for tag is called in a ERb evaluation block, not a ERb output block. So that's <% %>, - # not <%= %>. Also worth noting is that the form_for yields a form_builder object, in this example as f, which emulates - # the API for the stand-alone FormHelper methods, but without the object name. So instead of text_field :person, :name, - # you get away with f.text_field :name. - # - # That in itself is a modest increase in comfort. The big news is that form_for allows us to more easily escape the instance - # variable convention, so while the stand-alone approach would require text_field :person, :name, :object => person - # to work with local variables instead of instance ones, the form_for calls remain the same. You simply declare once with - # :person, person and all subsequent field calls save :person and :object => person. - # - # Also note that form_for doesn't create an exclusive scope. It's still possible to use both the stand-alone FormHelper methods - # and methods from FormTagHelper. Example: - # - # <% form_for :person, @person, :url => { :action => "update" } do |f| %> - # First name: <%= f.text_field :first_name %> - # Last name : <%= f.text_field :last_name %> - # Biography : <%= text_area :person, :biography %> - # Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %> - # <% end %> - # - # Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base. - # Like collection_select and datetime_select. - # - # Html attributes for the form tag can be given as :html => {...}. Example: - # - # <% form_for :person, @person, :html => {:id => 'person_form'} do |f| %> - # ... - # <% end %> - # - # You can also build forms using a customized FormBuilder class. Subclass FormBuilder and override or define some more helpers, - # then use your custom builder like so: - # - # <% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %> - # <%= f.text_field :first_name %> - # <%= f.text_field :last_name %> - # <%= text_area :person, :biography %> - # <%= check_box_tag "person[admin]", @person.company.admin? %> - # <% end %> - # - # In many cases you will want to wrap the above in another helper, such as: - # - # def labelled_form_for(name, object, options, &proc) - # form_for(name, object, options.merge(:builder => LabellingFormBuiler), &proc) - # end - # - def form_for(object_name, *args, &proc) - raise ArgumentError, "Missing block" unless block_given? - options = args.last.is_a?(Hash) ? args.pop : {} - concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}), proc.binding) - fields_for(object_name, *(args << options), &proc) - concat('', proc.binding) - end - - # Creates a scope around a specific model object like form_for, but doesn't create the form tags themselves. This makes - # fields_for suitable for specifying additional model objects in the same form. Example: - # - # <% form_for :person, @person, :url => { :action => "update" } do |person_form| %> - # First name: <%= person_form.text_field :first_name %> - # Last name : <%= person_form.text_field :last_name %> - # - # <% fields_for :permission, @person.permission do |permission_fields| %> - # Admin? : <%= permission_fields.check_box :admin %> - # <% end %> - # <% end %> - # - # Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base. - # Like collection_select and datetime_select. - def fields_for(object_name, *args, &proc) - raise ArgumentError, "Missing block" unless block_given? - options = args.last.is_a?(Hash) ? args.pop : {} - object = args.first - yield((options[:builder] || FormBuilder).new(object_name, object, self, options, proc)) - end - - # Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object - # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a - # hash with +options+. - # - # Examples (call, result): - # text_field("post", "title", "size" => 20) - # - def text_field(object_name, method, options = {}) - InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("text", options) - end - - # Works just like text_field, but returns an input tag of the "password" type instead. - def password_field(object_name, method, options = {}) - InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("password", options) - end - - # Works just like text_field, but returns an input tag of the "hidden" type instead. - def hidden_field(object_name, method, options = {}) - InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("hidden", options) - end - - # Works just like text_field, but returns an input tag of the "file" type instead, which won't have a default value. - def file_field(object_name, method, options = {}) - InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("file", options) - end - - # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+) - # on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a - # hash with +options+. - # - # Example (call, result): - # text_area("post", "body", "cols" => 20, "rows" => 40) - # - def text_area(object_name, method, options = {}) - InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_text_area_tag(options) - end - - # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object - # assigned to the template (identified by +object+). It's intended that +method+ returns an integer and if that - # integer is above zero, then the checkbox is checked. Additional options on the input tag can be passed as a - # hash with +options+. The +checked_value+ defaults to 1 while the default +unchecked_value+ - # is set to 0 which is convenient for boolean values. Usually unchecked checkboxes don't post anything. - # We work around this problem by adding a hidden value with the same name as the checkbox. - # - # Example (call, result). Imagine that @post.validated? returns 1: - # check_box("post", "validated") - # - # - # - # Example (call, result). Imagine that @puppy.gooddog returns no: - # check_box("puppy", "gooddog", {}, "yes", "no") - # - # - def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0") - InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value) - end - - # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object - # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the - # radio button will be checked. Additional options on the input tag can be passed as a - # hash with +options+. - # Example (call, result). Imagine that @post.category returns "rails": - # radio_button("post", "category", "rails") - # radio_button("post", "category", "java") - # - # - # - def radio_button(object_name, method, tag_value, options = {}) - InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_radio_button_tag(tag_value, options) - end - end - - class InstanceTag #:nodoc: - include Helpers::TagHelper - - attr_reader :method_name, :object_name - - DEFAULT_FIELD_OPTIONS = { "size" => 30 }.freeze unless const_defined?(:DEFAULT_FIELD_OPTIONS) - DEFAULT_RADIO_OPTIONS = { }.freeze unless const_defined?(:DEFAULT_RADIO_OPTIONS) - DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }.freeze unless const_defined?(:DEFAULT_TEXT_AREA_OPTIONS) - DEFAULT_DATE_OPTIONS = { :discard_type => true }.freeze unless const_defined?(:DEFAULT_DATE_OPTIONS) - - def initialize(object_name, method_name, template_object, local_binding = nil, object = nil) - @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup - @template_object, @local_binding = template_object, local_binding - @object = object - if @object_name.sub!(/\[\]$/,"") - @auto_index = @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}").id_before_type_cast - end - end - - def to_input_field_tag(field_type, options = {}) - options = options.stringify_keys - options["size"] ||= options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] - options = DEFAULT_FIELD_OPTIONS.merge(options) - if field_type == "hidden" - options.delete("size") - end - options["type"] = field_type - options["value"] ||= value_before_type_cast unless field_type == "file" - add_default_name_and_id(options) - tag("input", options) - end - - def to_radio_button_tag(tag_value, options = {}) - options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys) - options["type"] = "radio" - options["value"] = tag_value - options["checked"] = "checked" if value.to_s == tag_value.to_s - pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase - options["id"] = @auto_index ? - "#{@object_name}_#{@auto_index}_#{@method_name}_#{pretty_tag_value}" : - "#{@object_name}_#{@method_name}_#{pretty_tag_value}" - add_default_name_and_id(options) - tag("input", options) - end - - def to_text_area_tag(options = {}) - options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys) - add_default_name_and_id(options) - content_tag("textarea", html_escape(options.delete('value') || value_before_type_cast), options) - end - - def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0") - options = options.stringify_keys - options["type"] = "checkbox" - options["value"] = checked_value - checked = case value - when TrueClass, FalseClass - value - when NilClass - false - when Integer - value != 0 - when String - value == checked_value - else - value.to_i != 0 - end - if checked || options["checked"] == "checked" - options["checked"] = "checked" - else - options.delete("checked") - end - add_default_name_and_id(options) - tag("input", options) << tag("input", "name" => options["name"], "type" => "hidden", "value" => unchecked_value) - end - - def to_date_tag() - defaults = DEFAULT_DATE_OPTIONS.dup - date = value || Date.today - options = Proc.new { |position| defaults.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") } - html_day_select(date, options.call(3)) + - html_month_select(date, options.call(2)) + - html_year_select(date, options.call(1)) - end - - def to_boolean_select_tag(options = {}) - options = options.stringify_keys - add_default_name_and_id(options) - tag_text = "" - end - - def to_content_tag(tag_name, options = {}) - content_tag(tag_name, value, options) - end - - def object - @object || @template_object.instance_variable_get("@#{@object_name}") - end - - def value - unless object.nil? - object.send(@method_name) - end - end - - def value_before_type_cast - unless object.nil? - object.respond_to?(@method_name + "_before_type_cast") ? - object.send(@method_name + "_before_type_cast") : - object.send(@method_name) - end - end - - private - def add_default_name_and_id(options) - if options.has_key?("index") - options["name"] ||= tag_name_with_index(options["index"]) - options["id"] ||= tag_id_with_index(options["index"]) - options.delete("index") - elsif @auto_index - options["name"] ||= tag_name_with_index(@auto_index) - options["id"] ||= tag_id_with_index(@auto_index) - else - options["name"] ||= tag_name - options["id"] ||= tag_id - end - end - - def tag_name - "#{@object_name}[#{@method_name}]" - end - - def tag_name_with_index(index) - "#{@object_name}[#{index}][#{@method_name}]" - end - - def tag_id - "#{@object_name}_#{@method_name}" - end - - def tag_id_with_index(index) - "#{@object_name}_#{index}_#{@method_name}" - end - end - - class FormBuilder #:nodoc: - # The methods which wrap a form helper call. - class_inheritable_accessor :field_helpers - self.field_helpers = (FormHelper.instance_methods - ['form_for']) - - attr_accessor :object_name, :object - - def initialize(object_name, object, template, options, proc) - @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc - end - - (field_helpers - %w(check_box radio_button)).each do |selector| - src = <<-end_src - def #{selector}(method, options = {}) - @template.send(#{selector.inspect}, @object_name, method, options.merge(:object => @object)) - end - end_src - class_eval src, __FILE__, __LINE__ - end - - def check_box(method, options = {}, checked_value = "1", unchecked_value = "0") - @template.check_box(@object_name, method, options.merge(:object => @object), checked_value, unchecked_value) - end - - def radio_button(method, tag_value, options = {}) - @template.radio_button(@object_name, method, tag_value, options.merge(:object => @object)) - end - end - end -end -require 'cgi' -require 'erb' -require File.dirname(__FILE__) + '/form_helper' - -module ActionView - module Helpers - # Provides a number of methods for turning different kinds of containers into a set of option tags. - # == Options - # The collection_select, country_select, select, - # and time_zone_select methods take an options parameter, - # a hash. - # - # * :include_blank - set to true if the first option element of the select element is a blank. Useful if there is not a default value required for the select element. For example, - # - # select("post", "category", Post::CATEGORIES, {:include_blank => true}) - # - # could become: - # - # - # - # * :prompt - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string. - # - # Another common case is a select tag for an belongs_to-associated object. For example, - # - # select("post", "person_id", Person.find_all.collect {|p| [ p.name, p.id ] }) - # - # could become: - # - # - module FormOptionsHelper - include ERB::Util - - # Create a select tag and a series of contained option tags for the provided object and method. - # The option currently held by the object will be selected, provided that the object is available. - # See options_for_select for the required format of the choices parameter. - # - # Example with @post.person_id => 1: - # select("post", "person_id", Person.find_all.collect {|p| [ p.name, p.id ] }, { :include_blank => true }) - # - # could become: - # - # - # - # This can be used to provide a default set of options in the standard way: before rendering the create form, a - # new model instance is assigned the default options and bound to @model_name. Usually this model is not saved - # to the database. Instead, a second model object is created when the create request is received. - # This allows the user to submit a form page more than once with the expected results of creating multiple records. - # In addition, this allows a single partial to be used to generate form inputs for both edit and create forms. - # - # By default, post.person_id is the selected option. Specify :selected => value to use a different selection - # or :selected => nil to leave all options unselected. - def select(object, method, choices, options = {}, html_options = {}) - InstanceTag.new(object, method, self, nil, options.delete(:object)).to_select_tag(choices, options, html_options) - end - - # Return select and option tags for the given object and method using options_from_collection_for_select to generate the list of option tags. - def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {}) - InstanceTag.new(object, method, self, nil, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options) - end - - # Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags. - def country_select(object, method, priority_countries = nil, options = {}, html_options = {}) - InstanceTag.new(object, method, self, nil, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options) - end - - # Return select and option tags for the given object and method, using - # #time_zone_options_for_select to generate the list of option tags. - # - # In addition to the :include_blank option documented above, - # this method also supports a :model option, which defaults - # to TimeZone. This may be used by users to specify a different time - # zone model object. (See #time_zone_options_for_select for more - # information.) - def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {}) - InstanceTag.new(object, method, self, nil, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options) - end - - # Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container - # where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and - # the "firsts" as option text. Hashes are turned into this form automatically, so the keys become "firsts" and values - # become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +Selected+ - # may also be an array of values to be selected when using a multiple select. - # - # Examples (call, result): - # options_for_select([["Dollar", "$"], ["Kroner", "DKK"]]) - # \n - # - # options_for_select([ "VISA", "MasterCard" ], "MasterCard") - # \n - # - # options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40") - # \n - # - # options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"]) - # \n\n - # - # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag. - def options_for_select(container, selected = nil) - container = container.to_a if Hash === container - - options_for_select = container.inject([]) do |options, element| - if !element.is_a?(String) and element.respond_to?(:first) and element.respond_to?(:last) - is_selected = ( (selected.respond_to?(:include?) ? selected.include?(element.last) : element.last == selected) ) - is_selected = ( (selected.respond_to?(:include?) && !selected.is_a?(String) ? selected.include?(element.last) : element.last == selected) ) - if is_selected - options << "" - else - options << "" - end - else - is_selected = ( (selected.respond_to?(:include?) ? selected.include?(element) : element == selected) ) - is_selected = ( (selected.respond_to?(:include?) && !selected.is_a?(String) ? selected.include?(element) : element == selected) ) - options << ((is_selected) ? "" : "") - end - end - - options_for_select.join("\n") - end - - # Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning the - # the result of a call to the +value_method+ as the option value and the +text_method+ as the option text. - # If +selected_value+ is specified, the element returning a match on +value_method+ will get the selected option tag. - # - # Example (call, result). Imagine a loop iterating over each +person+ in @project.people to generate an input tag: - # options_from_collection_for_select(@project.people, "id", "name") - # - # - # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag. - def options_from_collection_for_select(collection, value_method, text_method, selected_value = nil) - options_for_select( - collection.inject([]) { |options, object| options << [ object.send(text_method), object.send(value_method) ] }, - selected_value - ) - end - - # Returns a string of option tags, like options_from_collection_for_select, but surrounds them with tags. - # - # An array of group objects are passed. Each group should return an array of options when calling group_method - # Each group should return its name when calling group_label_method. - # - # html_option_groups_from_collection(@continents, "countries", "continent_name", "country_id", "country_name", @selected_country.id) - # - # Could become: - # - # - # - # ... - # - # - # - # - # - # ... - # - # - # with objects of the following classes: - # class Continent - # def initialize(p_name, p_countries) @continent_name = p_name; @countries = p_countries; end - # def continent_name() @continent_name; end - # def countries() @countries; end - # end - # class Country - # def initialize(id, name) @id = id; @name = name end - # def country_id() @id; end - # def country_name() @name; end - # end - # - # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag. - def option_groups_from_collection_for_select(collection, group_method, group_label_method, - option_key_method, option_value_method, selected_key = nil) - collection.inject("") do |options_for_select, group| - group_label_string = eval("group.#{group_label_method}") - options_for_select += "" - options_for_select += options_from_collection_for_select(eval("group.#{group_method}"), option_key_method, option_value_method, selected_key) - options_for_select += '' - end - end - - # Returns a string of option tags for pretty much any country in the world. Supply a country name as +selected+ to - # have it marked as the selected option tag. You can also supply an array of countries as +priority_countries+, so - # that they will be listed above the rest of the (long) list. - # - # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag. - def country_options_for_select(selected = nil, priority_countries = nil) - country_options = "" - - if priority_countries - country_options += options_for_select(priority_countries, selected) - country_options += "\n" - end - - if priority_countries && priority_countries.include?(selected) - country_options += options_for_select(COUNTRIES - priority_countries, selected) - else - country_options += options_for_select(COUNTRIES, selected) - end - - return country_options - end - - # Returns a string of option tags for pretty much any time zone in the - # world. Supply a TimeZone name as +selected+ to have it marked as the - # selected option tag. You can also supply an array of TimeZone objects - # as +priority_zones+, so that they will be listed above the rest of the - # (long) list. (You can use TimeZone.us_zones as a convenience for - # obtaining a list of the US time zones.) - # - # The +selected+ parameter must be either +nil+, or a string that names - # a TimeZone. - # - # By default, +model+ is the TimeZone constant (which can be obtained - # in ActiveRecord as a value object). The only requirement is that the - # +model+ parameter be an object that responds to #all, and returns - # an array of objects that represent time zones. - # - # NOTE: Only the option tags are returned, you have to wrap this call in - # a regular HTML select tag. - def time_zone_options_for_select(selected = nil, priority_zones = nil, model = TimeZone) - zone_options = "" - - zones = model.all - convert_zones = lambda { |list| list.map { |z| [ z.to_s, z.name ] } } - - if priority_zones - zone_options += options_for_select(convert_zones[priority_zones], selected) - zone_options += "\n" - - zones = zones.reject { |z| priority_zones.include?( z ) } - end - - zone_options += options_for_select(convert_zones[zones], selected) - zone_options - end - - private - # All the countries included in the country_options output. - COUNTRIES = [ "Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", "Anguilla", - "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia", - "Austria", "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", - "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina", - "Botswana", "Bouvet Island", "Brazil", "British Indian Ocean Territory", - "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burma", "Burundi", "Cambodia", - "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", - "Chad", "Chile", "China", "Christmas Island", "Cocos (Keeling) Islands", "Colombia", - "Comoros", "Congo", "Congo, the Democratic Republic of the", "Cook Islands", - "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba", "Cyprus", "Czech Republic", "Denmark", - "Djibouti", "Dominica", "Dominican Republic", "East Timor", "Ecuador", "Egypt", - "El Salvador", "England", "Equatorial Guinea", "Eritrea", "Espana", "Estonia", - "Ethiopia", "Falkland Islands", "Faroe Islands", "Fiji", "Finland", "France", - "French Guiana", "French Polynesia", "French Southern Territories", "Gabon", "Gambia", - "Georgia", "Germany", "Ghana", "Gibraltar", "Great Britain", "Greece", "Greenland", - "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guinea", "Guinea-Bissau", "Guyana", - "Haiti", "Heard and Mc Donald Islands", "Honduras", "Hong Kong", "Hungary", "Iceland", - "India", "Indonesia", "Ireland", "Israel", "Italy", "Iran", "Iraq", "Jamaica", "Japan", "Jordan", - "Kazakhstan", "Kenya", "Kiribati", "Korea, Republic of", "Korea (South)", "Kuwait", - "Kyrgyzstan", "Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", - "Liberia", "Liechtenstein", "Lithuania", "Luxembourg", "Macau", "Macedonia", - "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", - "Martinique", "Mauritania", "Mauritius", "Mayotte", "Mexico", - "Micronesia, Federated States of", "Moldova, Republic of", "Monaco", "Mongolia", - "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", "Nepal", - "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", - "Niger", "Nigeria", "Niue", "Norfolk Island", "Northern Ireland", - "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau", "Panama", - "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Pitcairn", "Poland", - "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russia", "Rwanda", - "Saint Kitts and Nevis", "Saint Lucia", "Saint Vincent and the Grenadines", - "Samoa (Independent)", "San Marino", "Sao Tome and Principe", "Saudi Arabia", - "Scotland", "Senegal", "Serbia and Montenegro", "Seychelles", "Sierra Leone", "Singapore", - "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa", - "South Georgia and the South Sandwich Islands", "South Korea", "Spain", "Sri Lanka", - "St. Helena", "St. Pierre and Miquelon", "Suriname", "Svalbard and Jan Mayen Islands", - "Swaziland", "Sweden", "Switzerland", "Taiwan", "Tajikistan", "Tanzania", "Thailand", - "Togo", "Tokelau", "Tonga", "Trinidad", "Trinidad and Tobago", "Tunisia", "Turkey", - "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", - "United Arab Emirates", "United Kingdom", "United States", - "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", - "Vatican City State (Holy See)", "Venezuela", "Viet Nam", "Virgin Islands (British)", - "Virgin Islands (U.S.)", "Wales", "Wallis and Futuna Islands", "Western Sahara", - "Yemen", "Zambia", "Zimbabwe" ] unless const_defined?("COUNTRIES") - end - - class InstanceTag #:nodoc: - include FormOptionsHelper - - def to_select_tag(choices, options, html_options) - html_options = html_options.stringify_keys - add_default_name_and_id(html_options) - selected_value = options.has_key?(:selected) ? options[:selected] : value - content_tag("select", add_options(options_for_select(choices, selected_value), options, value), html_options) - end - - def to_collection_select_tag(collection, value_method, text_method, options, html_options) - html_options = html_options.stringify_keys - add_default_name_and_id(html_options) - content_tag( - "select", add_options(options_from_collection_for_select(collection, value_method, text_method, value), options, value), html_options - ) - end - - def to_country_select_tag(priority_countries, options, html_options) - html_options = html_options.stringify_keys - add_default_name_and_id(html_options) - content_tag("select", add_options(country_options_for_select(value, priority_countries), options, value), html_options) - end - - def to_time_zone_select_tag(priority_zones, options, html_options) - html_options = html_options.stringify_keys - add_default_name_and_id(html_options) - content_tag("select", - add_options( - time_zone_options_for_select(value, priority_zones, options[:model] || TimeZone), - options, value - ), html_options - ) - end - - private - def add_options(option_tags, options, value = nil) - option_tags = "\n" + option_tags if options[:include_blank] - - if value.blank? && options[:prompt] - ("\n") + option_tags - else - option_tags - end - end - end - - class FormBuilder - def select(method, choices, options = {}, html_options = {}) - @template.select(@object_name, method, choices, options.merge(:object => @object), html_options) - end - - def collection_select(method, collection, value_method, text_method, options = {}, html_options = {}) - @template.collection_select(@object_name, method, collection, value_method, text_method, options.merge(:object => @object), html_options) - end - - def country_select(method, priority_countries = nil, options = {}, html_options = {}) - @template.country_select(@object_name, method, priority_countries, options.merge(:object => @object), html_options) - end - - def time_zone_select(method, priority_zones = nil, options = {}, html_options = {}) - @template.time_zone_select(@object_name, method, priority_zones, options.merge(:object => @object), html_options) - end - end - end -end -require 'cgi' -require File.dirname(__FILE__) + '/tag_helper' - -module ActionView - module Helpers - # Provides a number of methods for creating form tags that doesn't rely on conventions with an object assigned to the template like - # FormHelper does. With the FormTagHelper, you provide the names and values yourself. - # - # NOTE: The html options disabled, readonly, and multiple can all be treated as booleans. So specifying :disabled => true - # will give disabled="disabled". - module FormTagHelper - # Starts a form tag that points the action to an url configured with url_for_options just like - # ActionController::Base#url_for. The method for the form defaults to POST. - # - # Options: - # * :multipart - If set to true, the enctype is set to "multipart/form-data". - # * :method - The method to use when submitting the form, usually either "get" or "post". - def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &proc) - html_options = { "method" => "post" }.merge(options.stringify_keys) - html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart") - html_options["action"] = url_for(url_for_options, *parameters_for_url) - tag :form, html_options, true - end - - alias_method :start_form_tag, :form_tag - - # Outputs "" - def end_form_tag - "" - end - - # Creates a dropdown selection box, or if the :multiple option is set to true, a multiple - # choice selection box. - # - # Helpers::FormOptions can be used to create common select boxes such as countries, time zones, or - # associated records. - # - # option_tags is a string containing the option tags for the select box: - # # Outputs - # select_tag "people", "" - # - # Options: - # * :multiple - If set to true the selection will allow multiple choices. - def select_tag(name, option_tags = nil, options = {}) - content_tag :select, option_tags, { "name" => name, "id" => name }.update(options.stringify_keys) - end - - # Creates a standard text field. - # - # Options: - # * :disabled - If set to true, the user will not be able to use this input. - # * :size - The number of visible characters that will fit in the input. - # * :maxlength - The maximum number of characters that the browser will allow the user to enter. - # - # A hash of standard HTML options for the tag. - def text_field_tag(name, value = nil, options = {}) - tag :input, { "type" => "text", "name" => name, "id" => name, "value" => value }.update(options.stringify_keys) - end - - # Creates a hidden field. - # - # Takes the same options as text_field_tag - def hidden_field_tag(name, value = nil, options = {}) - text_field_tag(name, value, options.stringify_keys.update("type" => "hidden")) - end - - # Creates a file upload field. - # - # If you are using file uploads then you will also need to set the multipart option for the form: - # <%= form_tag { :action => "post" }, { :multipart => true } %> - # <%= file_field_tag "file" %> - # <%= submit_tag %> - # <%= end_form_tag %> - # - # The specified URL will then be passed a File object containing the selected file, or if the field - # was left blank, a StringIO object. - def file_field_tag(name, options = {}) - text_field_tag(name, nil, options.update("type" => "file")) - end - - # Creates a password field. - # - # Takes the same options as text_field_tag - def password_field_tag(name = "password", value = nil, options = {}) - text_field_tag(name, value, options.update("type" => "password")) - end - - # Creates a text input area. - # - # Options: - # * :size - A string specifying the dimensions of the textarea. - # # Outputs - # <%= text_area_tag "body", nil, :size => "25x10" %> - def text_area_tag(name, content = nil, options = {}) - options.stringify_keys! - - if size = options.delete("size") - options["cols"], options["rows"] = size.split("x") - end - - content_tag :textarea, content, { "name" => name, "id" => name }.update(options.stringify_keys) - end - - # Creates a check box. - def check_box_tag(name, value = "1", checked = false, options = {}) - html_options = { "type" => "checkbox", "name" => name, "id" => name, "value" => value }.update(options.stringify_keys) - html_options["checked"] = "checked" if checked - tag :input, html_options - end - - # Creates a radio button. - def radio_button_tag(name, value, checked = false, options = {}) - html_options = { "type" => "radio", "name" => name, "id" => name, "value" => value }.update(options.stringify_keys) - html_options["checked"] = "checked" if checked - tag :input, html_options - end - - # Creates a submit button with the text value as the caption. If options contains a pair with the key of "disable_with", - # then the value will be used to rename a disabled version of the submit button. - def submit_tag(value = "Save changes", options = {}) - options.stringify_keys! - - if disable_with = options.delete("disable_with") - options["onclick"] = "this.disabled=true;this.value='#{disable_with}';this.form.submit();#{options["onclick"]}" - end - - tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys) - end - - # Displays an image which when clicked will submit the form. - # - # source is passed to AssetTagHelper#image_path - def image_submit_tag(source, options = {}) - tag :input, { "type" => "image", "src" => image_path(source) }.update(options.stringify_keys) - end - end - end -end -require File.dirname(__FILE__) + '/tag_helper' - -module ActionView - module Helpers - # Provides a set of helpers for creating JavaScript macros that rely on and often bundle methods from JavaScriptHelper into - # larger units. These macros also rely on counterparts in the controller that provide them with their backing. The in-place - # editing relies on ActionController::Base.in_place_edit_for and the autocompletion relies on - # ActionController::Base.auto_complete_for. - module JavaScriptMacrosHelper - # Makes an HTML element specified by the DOM ID +field_id+ become an in-place - # editor of a property. - # - # A form is automatically created and displayed when the user clicks the element, - # something like this: - #
- # - # - # cancel - #
- # - # The form is serialized and sent to the server using an AJAX call, the action on - # the server should process the value and return the updated value in the body of - # the reponse. The element will automatically be updated with the changed value - # (as returned from the server). - # - # Required +options+ are: - # :url:: Specifies the url where the updated value should - # be sent after the user presses "ok". - # - # - # Addtional +options+ are: - # :rows:: Number of rows (more than 1 will use a TEXTAREA) - # :cols:: Number of characters the text input should span (works for both INPUT and TEXTAREA) - # :size:: Synonym for :cols when using a single line text input. - # :cancel_text:: The text on the cancel link. (default: "cancel") - # :save_text:: The text on the save link. (default: "ok") - # :loading_text:: The text to display when submitting to the server (default: "Saving...") - # :external_control:: The id of an external control used to enter edit mode. - # :load_text_url:: URL where initial value of editor (content) is retrieved. - # :options:: Pass through options to the AJAX call (see prototype's Ajax.Updater) - # :with:: JavaScript snippet that should return what is to be sent - # in the AJAX call, +form+ is an implicit parameter - # :script:: Instructs the in-place editor to evaluate the remote JavaScript response (default: false) - def in_place_editor(field_id, options = {}) - function = "new Ajax.InPlaceEditor(" - function << "'#{field_id}', " - function << "'#{url_for(options[:url])}'" - - js_options = {} - js_options['cancelText'] = %('#{options[:cancel_text]}') if options[:cancel_text] - js_options['okText'] = %('#{options[:save_text]}') if options[:save_text] - js_options['loadingText'] = %('#{options[:loading_text]}') if options[:loading_text] - js_options['rows'] = options[:rows] if options[:rows] - js_options['cols'] = options[:cols] if options[:cols] - js_options['size'] = options[:size] if options[:size] - js_options['externalControl'] = "'#{options[:external_control]}'" if options[:external_control] - js_options['loadTextURL'] = "'#{url_for(options[:load_text_url])}'" if options[:load_text_url] - js_options['ajaxOptions'] = options[:options] if options[:options] - js_options['evalScripts'] = options[:script] if options[:script] - js_options['callback'] = "function(form) { return #{options[:with]} }" if options[:with] - function << (', ' + options_for_javascript(js_options)) unless js_options.empty? - - function << ')' - - javascript_tag(function) - end - - # Renders the value of the specified object and method with in-place editing capabilities. - # - # See the RDoc on ActionController::InPlaceEditing to learn more about this. - def in_place_editor_field(object, method, tag_options = {}, in_place_editor_options = {}) - tag = ::ActionView::Helpers::InstanceTag.new(object, method, self) - tag_options = {:tag => "span", :id => "#{object}_#{method}_#{tag.object.id}_in_place_editor", :class => "in_place_editor_field"}.merge!(tag_options) - in_place_editor_options[:url] = in_place_editor_options[:url] || url_for({ :action => "set_#{object}_#{method}", :id => tag.object.id }) - tag.to_content_tag(tag_options.delete(:tag), tag_options) + - in_place_editor(tag_options[:id], in_place_editor_options) - end - - # Adds AJAX autocomplete functionality to the text input field with the - # DOM ID specified by +field_id+. - # - # This function expects that the called action returns a HTML
    list, - # or nothing if no entries should be displayed for autocompletion. - # - # You'll probably want to turn the browser's built-in autocompletion off, - # so be sure to include a autocomplete="off" attribute with your text - # input field. - # - # The autocompleter object is assigned to a Javascript variable named field_id_auto_completer. - # This object is useful if you for example want to trigger the auto-complete suggestions through - # other means than user input (for that specific case, call the activate method on that object). - # - # Required +options+ are: - # :url:: URL to call for autocompletion results - # in url_for format. - # - # Addtional +options+ are: - # :update:: Specifies the DOM ID of the element whose - # innerHTML should be updated with the autocomplete - # entries returned by the AJAX request. - # Defaults to field_id + '_auto_complete' - # :with:: A JavaScript expression specifying the - # parameters for the XMLHttpRequest. This defaults - # to 'fieldname=value'. - # :frequency:: Determines the time to wait after the last keystroke - # for the AJAX request to be initiated. - # :indicator:: Specifies the DOM ID of an element which will be - # displayed while autocomplete is running. - # :tokens:: A string or an array of strings containing - # separator tokens for tokenized incremental - # autocompletion. Example: :tokens => ',' would - # allow multiple autocompletion entries, separated - # by commas. - # :min_chars:: The minimum number of characters that should be - # in the input field before an Ajax call is made - # to the server. - # :on_hide:: A Javascript expression that is called when the - # autocompletion div is hidden. The expression - # should take two variables: element and update. - # Element is a DOM element for the field, update - # is a DOM element for the div from which the - # innerHTML is replaced. - # :on_show:: Like on_hide, only now the expression is called - # then the div is shown. - # :after_update_element:: A Javascript expression that is called when the - # user has selected one of the proposed values. - # The expression should take two variables: element and value. - # Element is a DOM element for the field, value - # is the value selected by the user. - # :select:: Pick the class of the element from which the value for - # insertion should be extracted. If this is not specified, - # the entire element is used. - def auto_complete_field(field_id, options = {}) - function = "var #{field_id}_auto_completer = new Ajax.Autocompleter(" - function << "'#{field_id}', " - function << "'" + (options[:update] || "#{field_id}_auto_complete") + "', " - function << "'#{url_for(options[:url])}'" - - js_options = {} - js_options[:tokens] = array_or_string_for_javascript(options[:tokens]) if options[:tokens] - js_options[:callback] = "function(element, value) { return #{options[:with]} }" if options[:with] - js_options[:indicator] = "'#{options[:indicator]}'" if options[:indicator] - js_options[:select] = "'#{options[:select]}'" if options[:select] - js_options[:frequency] = "#{options[:frequency]}" if options[:frequency] - - { :after_update_element => :afterUpdateElement, - :on_show => :onShow, :on_hide => :onHide, :min_chars => :minChars }.each do |k,v| - js_options[v] = options[k] if options[k] - end - - function << (', ' + options_for_javascript(js_options) + ')') - - javascript_tag(function) - end - - # Use this method in your view to generate a return for the AJAX autocomplete requests. - # - # Example action: - # - # def auto_complete_for_item_title - # @items = Item.find(:all, - # :conditions => [ 'LOWER(description) LIKE ?', - # '%' + request.raw_post.downcase + '%' ]) - # render :inline => '<%= auto_complete_result(@items, 'description') %>' - # end - # - # The auto_complete_result can of course also be called from a view belonging to the - # auto_complete action if you need to decorate it further. - def auto_complete_result(entries, field, phrase = nil) - return unless entries - items = entries.map { |entry| content_tag("li", phrase ? highlight(entry[field], phrase) : h(entry[field])) } - content_tag("ul", items.uniq) - end - - # Wrapper for text_field with added AJAX autocompletion functionality. - # - # In your controller, you'll need to define an action called - # auto_complete_for_object_method to respond the AJAX calls, - # - # See the RDoc on ActionController::AutoComplete to learn more about this. - def text_field_with_auto_complete(object, method, tag_options = {}, completion_options = {}) - (completion_options[:skip_style] ? "" : auto_complete_stylesheet) + - text_field(object, method, tag_options) + - content_tag("div", "", :id => "#{object}_#{method}_auto_complete", :class => "auto_complete") + - auto_complete_field("#{object}_#{method}", { :url => { :action => "auto_complete_for_#{object}_#{method}" } }.update(completion_options)) - end - - private - def auto_complete_stylesheet - content_tag("style", <<-EOT - div.auto_complete { - width: 350px; - background: #fff; - } - div.auto_complete ul { - border:1px solid #888; - margin:0; - padding:0; - width:100%; - list-style-type:none; - } - div.auto_complete ul li { - margin:0; - padding:3px; - } - div.auto_complete ul li.selected { - background-color: #ffb; - } - div.auto_complete ul strong.highlight { - color: #800; - margin:0; - padding:0; - } - EOT - ) - end - - end - end -end -require File.dirname(__FILE__) + '/tag_helper' - -module ActionView - module Helpers - # Provides functionality for working with JavaScript in your views. - # - # == Ajax, controls and visual effects - # - # * For information on using Ajax, see - # ActionView::Helpers::PrototypeHelper. - # * For information on using controls and visual effects, see - # ActionView::Helpers::ScriptaculousHelper. - # - # == Including the JavaScript libraries into your pages - # - # Rails includes the Prototype JavaScript framework and the Scriptaculous - # JavaScript controls and visual effects library. If you wish to use - # these libraries and their helpers (ActionView::Helpers::PrototypeHelper - # and ActionView::Helpers::ScriptaculousHelper), you must do one of the - # following: - # - # * Use <%= javascript_include_tag :defaults %> in the HEAD - # section of your page (recommended): This function will return - # references to the JavaScript files created by the +rails+ command in - # your public/javascripts directory. Using it is recommended as - # the browser can then cache the libraries instead of fetching all the - # functions anew on every request. - # * Use <%= javascript_include_tag 'prototype' %>: As above, but - # will only include the Prototype core library, which means you are able - # to use all basic AJAX functionality. For the Scriptaculous-based - # JavaScript helpers, like visual effects, autocompletion, drag and drop - # and so on, you should use the method described above. - # * Use <%= define_javascript_functions %>: this will copy all the - # JavaScript support functions within a single script block. Not - # recommended. - # - # For documentation on +javascript_include_tag+ see - # ActionView::Helpers::AssetTagHelper. - module JavaScriptHelper - unless const_defined? :JAVASCRIPT_PATH - JAVASCRIPT_PATH = File.join(File.dirname(__FILE__), 'javascripts') - end - - # Returns a link that'll trigger a JavaScript +function+ using the - # onclick handler and return false after the fact. - # - # Examples: - # link_to_function "Greeting", "alert('Hello world!')" - # link_to_function(image_tag("delete"), "if confirm('Really?'){ do_delete(); }") - def link_to_function(name, function, html_options = {}) - html_options.symbolize_keys! - content_tag( - "a", name, - html_options.merge({ - :href => html_options[:href] || "#", - :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function}; return false;" - }) - ) - end - - # Returns a link that'll trigger a JavaScript +function+ using the - # onclick handler. - # - # Examples: - # button_to_function "Greeting", "alert('Hello world!')" - # button_to_function "Delete", "if confirm('Really?'){ do_delete(); }") - def button_to_function(name, function, html_options = {}) - html_options.symbolize_keys! - tag(:input, html_options.merge({ - :type => "button", :value => name, - :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" - })) - end - - # Includes the Action Pack JavaScript libraries inside a single ' - end - - # Escape carrier returns and single and double quotes for JavaScript segments. - def escape_javascript(javascript) - (javascript || '').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" } - end - - # Returns a JavaScript tag with the +content+ inside. Example: - # javascript_tag "alert('All is good')" # => - def javascript_tag(content) - content_tag("script", javascript_cdata_section(content), :type => "text/javascript") - end - - def javascript_cdata_section(content) #:nodoc: - "\n//#{cdata_section("\n#{content}\n//")}\n" - end - - protected - def options_for_javascript(options) - '{' + options.map {|k, v| "#{k}:#{v}"}.sort.join(', ') + '}' - end - - def array_or_string_for_javascript(option) - js_option = if option.kind_of?(Array) - "['#{option.join('\',\'')}']" - elsif !option.nil? - "'#{option}'" - end - js_option - end - end - - JavascriptHelper = JavaScriptHelper unless const_defined? :JavascriptHelper - end -end -module ActionView - module Helpers - # Provides methods for converting a number into a formatted string that currently represents - # one of the following forms: phone number, percentage, money, or precision level. - module NumberHelper - - # Formats a +number+ into a US phone number string. The +options+ can be a hash used to customize the format of the output. - # The area code can be surrounded by parentheses by setting +:area_code+ to true; default is false - # The delimiter can be set using +:delimiter+; default is "-" - # Examples: - # number_to_phone(1235551234) => 123-555-1234 - # number_to_phone(1235551234, {:area_code => true}) => (123) 555-1234 - # number_to_phone(1235551234, {:delimiter => " "}) => 123 555 1234 - # number_to_phone(1235551234, {:area_code => true, :extension => 555}) => (123) 555-1234 x 555 - def number_to_phone(number, options = {}) - options = options.stringify_keys - area_code = options.delete("area_code") { false } - delimiter = options.delete("delimiter") { "-" } - extension = options.delete("extension") { "" } - begin - str = area_code == true ? number.to_s.gsub(/([0-9]{3})([0-9]{3})([0-9]{4})/,"(\\1) \\2#{delimiter}\\3") : number.to_s.gsub(/([0-9]{3})([0-9]{3})([0-9]{4})/,"\\1#{delimiter}\\2#{delimiter}\\3") - extension.to_s.strip.empty? ? str : "#{str} x #{extension.to_s.strip}" - rescue - number - end - end - - # Formats a +number+ into a currency string. The +options+ hash can be used to customize the format of the output. - # The +number+ can contain a level of precision using the +precision+ key; default is 2 - # The currency type can be set using the +unit+ key; default is "$" - # The unit separator can be set using the +separator+ key; default is "." - # The delimiter can be set using the +delimiter+ key; default is "," - # Examples: - # number_to_currency(1234567890.50) => $1,234,567,890.50 - # number_to_currency(1234567890.506) => $1,234,567,890.51 - # number_to_currency(1234567890.50, {:unit => "£", :separator => ",", :delimiter => ""}) => £1234567890,50 - def number_to_currency(number, options = {}) - options = options.stringify_keys - precision, unit, separator, delimiter = options.delete("precision") { 2 }, options.delete("unit") { "$" }, options.delete("separator") { "." }, options.delete("delimiter") { "," } - separator = "" unless precision > 0 - begin - parts = number_with_precision(number, precision).split('.') - unit + number_with_delimiter(parts[0], delimiter) + separator + parts[1].to_s - rescue - number - end - end - - # Formats a +number+ as into a percentage string. The +options+ hash can be used to customize the format of the output. - # The +number+ can contain a level of precision using the +precision+ key; default is 3 - # The unit separator can be set using the +separator+ key; default is "." - # Examples: - # number_to_percentage(100) => 100.000% - # number_to_percentage(100, {:precision => 0}) => 100% - # number_to_percentage(302.0574, {:precision => 2}) => 302.06% - def number_to_percentage(number, options = {}) - options = options.stringify_keys - precision, separator = options.delete("precision") { 3 }, options.delete("separator") { "." } - begin - number = number_with_precision(number, precision) - parts = number.split('.') - if parts.at(1).nil? - parts[0] + "%" - else - parts[0] + separator + parts[1].to_s + "%" - end - rescue - number - end - end - - # Formats a +number+ with a +delimiter+. - # Example: - # number_with_delimiter(12345678) => 12,345,678 - def number_with_delimiter(number, delimiter=",") - number.to_s.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}") - end - - # Returns a formatted-for-humans file size. - # - # Examples: - # human_size(123) => 123 Bytes - # human_size(1234) => 1.2 KB - # human_size(12345) => 12.1 KB - # human_size(1234567) => 1.2 MB - # human_size(1234567890) => 1.1 GB - def number_to_human_size(size) - case - when size < 1.kilobyte: '%d Bytes' % size - when size < 1.megabyte: '%.1f KB' % (size / 1.0.kilobyte) - when size < 1.gigabyte: '%.1f MB' % (size / 1.0.megabyte) - when size < 1.terabyte: '%.1f GB' % (size / 1.0.gigabyte) - else '%.1f TB' % (size / 1.0.terabyte) - end.sub('.0', '') - rescue - nil - end - - alias_method :human_size, :number_to_human_size # deprecated alias - - # Formats a +number+ with a level of +precision+. - # Example: - # number_with_precision(111.2345) => 111.235 - def number_with_precision(number, precision=3) - sprintf("%01.#{precision}f", number) - end - end - end -end -module ActionView - module Helpers - # Provides methods for linking to ActionController::Pagination objects. - # - # You can also build your links manually, like in this example: - # - # <%= link_to "Previous page", { :page => paginator.current.previous } if paginator.current.previous %> - # - # <%= link_to "Next page", { :page => paginator.current.next } if paginator.current.next %> - module PaginationHelper - unless const_defined?(:DEFAULT_OPTIONS) - DEFAULT_OPTIONS = { - :name => :page, - :window_size => 2, - :always_show_anchors => true, - :link_to_current_page => false, - :params => {} - } - end - - # Creates a basic HTML link bar for the given +paginator+. - # +html_options+ are passed to +link_to+. - # - # +options+ are: - # :name:: the routing name for this paginator - # (defaults to +page+) - # :window_size:: the number of pages to show around - # the current page (defaults to +2+) - # :always_show_anchors:: whether or not the first and last - # pages should always be shown - # (defaults to +true+) - # :link_to_current_page:: whether or not the current page - # should be linked to (defaults to - # +false+) - # :params:: any additional routing parameters - # for page URLs - def pagination_links(paginator, options={}, html_options={}) - name = options[:name] || DEFAULT_OPTIONS[:name] - params = (options[:params] || DEFAULT_OPTIONS[:params]).clone - - pagination_links_each(paginator, options) do |n| - params[name] = n - link_to(n.to_s, params, html_options) - end - end - - # Iterate through the pages of a given +paginator+, invoking a - # block for each page number that needs to be rendered as a link. - def pagination_links_each(paginator, options) - options = DEFAULT_OPTIONS.merge(options) - link_to_current_page = options[:link_to_current_page] - always_show_anchors = options[:always_show_anchors] - - current_page = paginator.current_page - window_pages = current_page.window(options[:window_size]).pages - return if window_pages.length <= 1 unless link_to_current_page - - first, last = paginator.first, paginator.last - - html = '' - if always_show_anchors and not (wp_first = window_pages[0]).first? - html << yield(first.number) - html << ' ... ' if wp_first.number - first.number > 1 - html << ' ' - end - - window_pages.each do |page| - if current_page == page && !link_to_current_page - html << page.number.to_s - else - html << yield(page.number) - end - html << ' ' - end - - if always_show_anchors and not (wp_last = window_pages[-1]).last? - html << ' ... ' if last.number - wp_last.number > 1 - html << yield(last.number) - end - - html - end - - end # PaginationHelper - end # Helpers -end # ActionView -require File.dirname(__FILE__) + '/javascript_helper' -require 'set' - -module ActionView - module Helpers - # Provides a set of helpers for calling Prototype JavaScript functions, - # including functionality to call remote methods using - # Ajax[http://www.adaptivepath.com/publications/essays/archives/000385.php]. - # This means that you can call actions in your controllers without - # reloading the page, but still update certain parts of it using - # injections into the DOM. The common use case is having a form that adds - # a new element to a list without reloading the page. - # - # To be able to use these helpers, you must include the Prototype - # JavaScript framework in your pages. See the documentation for - # ActionView::Helpers::JavaScriptHelper for more information on including - # the necessary JavaScript. - # - # See link_to_remote for documentation of options common to all Ajax - # helpers. - # - # See also ActionView::Helpers::ScriptaculousHelper for helpers which work - # with the Scriptaculous controls and visual effects library. - # - # See JavaScriptGenerator for information on updating multiple elements - # on the page in an Ajax response. - module PrototypeHelper - unless const_defined? :CALLBACKS - CALLBACKS = Set.new([ :uninitialized, :loading, :loaded, - :interactive, :complete, :failure, :success ] + - (100..599).to_a) - AJAX_OPTIONS = Set.new([ :before, :after, :condition, :url, - :asynchronous, :method, :insertion, :position, - :form, :with, :update, :script ]).merge(CALLBACKS) - end - - # Returns a link to a remote action defined by options[:url] - # (using the url_for format) that's called in the background using - # XMLHttpRequest. The result of that request can then be inserted into a - # DOM object whose id can be specified with options[:update]. - # Usually, the result would be a partial prepared by the controller with - # either render_partial or render_partial_collection. - # - # Examples: - # link_to_remote "Delete this post", :update => "posts", - # :url => { :action => "destroy", :id => post.id } - # link_to_remote(image_tag("refresh"), :update => "emails", - # :url => { :action => "list_emails" }) - # - # You can also specify a hash for options[:update] to allow for - # easy redirection of output to an other DOM element if a server-side - # error occurs: - # - # Example: - # link_to_remote "Delete this post", - # :url => { :action => "destroy", :id => post.id }, - # :update => { :success => "posts", :failure => "error" } - # - # Optionally, you can use the options[:position] parameter to - # influence how the target DOM element is updated. It must be one of - # :before, :top, :bottom, or :after. - # - # By default, these remote requests are processed asynchronous during - # which various JavaScript callbacks can be triggered (for progress - # indicators and the likes). All callbacks get access to the - # request object, which holds the underlying XMLHttpRequest. - # - # To access the server response, use request.responseText, to - # find out the HTTP status, use request.status. - # - # Example: - # link_to_remote word, - # :url => { :action => "undo", :n => word_counter }, - # :complete => "undoRequestCompleted(request)" - # - # The callbacks that may be specified are (in order): - # - # :loading:: Called when the remote document is being - # loaded with data by the browser. - # :loaded:: Called when the browser has finished loading - # the remote document. - # :interactive:: Called when the user can interact with the - # remote document, even though it has not - # finished loading. - # :success:: Called when the XMLHttpRequest is completed, - # and the HTTP status code is in the 2XX range. - # :failure:: Called when the XMLHttpRequest is completed, - # and the HTTP status code is not in the 2XX - # range. - # :complete:: Called when the XMLHttpRequest is complete - # (fires after success/failure if they are - # present). - # - # You can further refine :success and :failure by - # adding additional callbacks for specific status codes. - # - # Example: - # link_to_remote word, - # :url => { :action => "action" }, - # 404 => "alert('Not found...? Wrong URL...?')", - # :failure => "alert('HTTP Error ' + request.status + '!')" - # - # A status code callback overrides the success/failure handlers if - # present. - # - # If you for some reason or another need synchronous processing (that'll - # block the browser while the request is happening), you can specify - # options[:type] = :synchronous. - # - # You can customize further browser side call logic by passing in - # JavaScript code snippets via some optional parameters. In their order - # of use these are: - # - # :confirm:: Adds confirmation dialog. - # :condition:: Perform remote request conditionally - # by this expression. Use this to - # describe browser-side conditions when - # request should not be initiated. - # :before:: Called before request is initiated. - # :after:: Called immediately after request was - # initiated and before :loading. - # :submit:: Specifies the DOM element ID that's used - # as the parent of the form elements. By - # default this is the current form, but - # it could just as well be the ID of a - # table row or any other DOM element. - def link_to_remote(name, options = {}, html_options = {}) - link_to_function(name, remote_function(options), html_options) - end - - # Periodically calls the specified url (options[:url]) every - # options[:frequency] seconds (default is 10). Usually used to - # update a specified div (options[:update]) with the results - # of the remote call. The options for specifying the target with :url - # and defining callbacks is the same as link_to_remote. - def periodically_call_remote(options = {}) - frequency = options[:frequency] || 10 # every ten seconds by default - code = "new PeriodicalExecuter(function() {#{remote_function(options)}}, #{frequency})" - javascript_tag(code) - end - - # Returns a form tag that will submit using XMLHttpRequest in the - # background instead of the regular reloading POST arrangement. Even - # though it's using JavaScript to serialize the form elements, the form - # submission will work just like a regular submission as viewed by the - # receiving side (all elements available in params). The options for - # specifying the target with :url and defining callbacks is the same as - # link_to_remote. - # - # A "fall-through" target for browsers that doesn't do JavaScript can be - # specified with the :action/:method options on :html. - # - # Example: - # form_remote_tag :html => { :action => - # url_for(:controller => "some", :action => "place") } - # - # The Hash passed to the :html key is equivalent to the options (2nd) - # argument in the FormTagHelper.form_tag method. - # - # By default the fall-through action is the same as the one specified in - # the :url (and the default method is :post). - def form_remote_tag(options = {}) - options[:form] = true - - options[:html] ||= {} - options[:html][:onsubmit] = "#{remote_function(options)}; return false;" - options[:html][:action] = options[:html][:action] || url_for(options[:url]) - options[:html][:method] = options[:html][:method] || "post" - - tag("form", options[:html], true) - end - - # Works like form_remote_tag, but uses form_for semantics. - def remote_form_for(object_name, *args, &proc) - options = args.last.is_a?(Hash) ? args.pop : {} - concat(form_remote_tag(options), proc.binding) - fields_for(object_name, *(args << options), &proc) - concat('', proc.binding) - end - alias_method :form_remote_for, :remote_form_for - - # Returns a button input tag that will submit form using XMLHttpRequest - # in the background instead of regular reloading POST arrangement. - # options argument is the same as in form_remote_tag. - def submit_to_remote(name, value, options = {}) - options[:with] ||= 'Form.serialize(this.form)' - - options[:html] ||= {} - options[:html][:type] = 'button' - options[:html][:onclick] = "#{remote_function(options)}; return false;" - options[:html][:name] = name - options[:html][:value] = value - - tag("input", options[:html], false) - end - - # Returns a JavaScript function (or expression) that'll update a DOM - # element according to the options passed. - # - # * :content: The content to use for updating. Can be left out - # if using block, see example. - # * :action: Valid options are :update (assumed by default), - # :empty, :remove - # * :position If the :action is :update, you can optionally - # specify one of the following positions: :before, :top, :bottom, - # :after. - # - # Examples: - # <%= javascript_tag(update_element_function("products", - # :position => :bottom, :content => "

    New product!

    ")) %> - # - # <% replacement_function = update_element_function("products") do %> - #

    Product 1

    - #

    Product 2

    - # <% end %> - # <%= javascript_tag(replacement_function) %> - # - # This method can also be used in combination with remote method call - # where the result is evaluated afterwards to cause multiple updates on - # a page. Example: - # - # # Calling view - # <%= form_remote_tag :url => { :action => "buy" }, - # :complete => evaluate_remote_response %> - # all the inputs here... - # - # # Controller action - # def buy - # @product = Product.find(1) - # end - # - # # Returning view - # <%= update_element_function( - # "cart", :action => :update, :position => :bottom, - # :content => "

    New Product: #{@product.name}

    ")) %> - # <% update_element_function("status", :binding => binding) do %> - # You've bought a new product! - # <% end %> - # - # Notice how the second call doesn't need to be in an ERb output block - # since it uses a block and passes in the binding to render directly. - # This trick will however only work in ERb (not Builder or other - # template forms). - # - # See also JavaScriptGenerator and update_page. - def update_element_function(element_id, options = {}, &block) - content = escape_javascript(options[:content] || '') - content = escape_javascript(capture(&block)) if block - - javascript_function = case (options[:action] || :update) - when :update - if options[:position] - "new Insertion.#{options[:position].to_s.camelize}('#{element_id}','#{content}')" - else - "$('#{element_id}').innerHTML = '#{content}'" - end - - when :empty - "$('#{element_id}').innerHTML = ''" - - when :remove - "Element.remove('#{element_id}')" - - else - raise ArgumentError, "Invalid action, choose one of :update, :remove, :empty" - end - - javascript_function << ";\n" - options[:binding] ? concat(javascript_function, options[:binding]) : javascript_function - end - - # Returns 'eval(request.responseText)' which is the JavaScript function - # that form_remote_tag can call in :complete to evaluate a multiple - # update return document using update_element_function calls. - def evaluate_remote_response - "eval(request.responseText)" - end - - # Returns the JavaScript needed for a remote function. - # Takes the same arguments as link_to_remote. - # - # Example: - # - def remote_function(options) - javascript_options = options_for_ajax(options) - - update = '' - if options[:update] and options[:update].is_a?Hash - update = [] - update << "success:'#{options[:update][:success]}'" if options[:update][:success] - update << "failure:'#{options[:update][:failure]}'" if options[:update][:failure] - update = '{' + update.join(',') + '}' - elsif options[:update] - update << "'#{options[:update]}'" - end - - function = update.empty? ? - "new Ajax.Request(" : - "new Ajax.Updater(#{update}, " - - url_options = options[:url] - url_options = url_options.merge(:escape => false) if url_options.is_a? Hash - function << "'#{url_for(url_options)}'" - function << ", #{javascript_options})" - - function = "#{options[:before]}; #{function}" if options[:before] - function = "#{function}; #{options[:after]}" if options[:after] - function = "if (#{options[:condition]}) { #{function}; }" if options[:condition] - function = "if (confirm('#{escape_javascript(options[:confirm])}')) { #{function}; }" if options[:confirm] - - return function - end - - # Observes the field with the DOM ID specified by +field_id+ and makes - # an Ajax call when its contents have changed. - # - # Required +options+ are either of: - # :url:: +url_for+-style options for the action to call - # when the field has changed. - # :function:: Instead of making a remote call to a URL, you - # can specify a function to be called instead. - # - # Additional options are: - # :frequency:: The frequency (in seconds) at which changes to - # this field will be detected. Not setting this - # option at all or to a value equal to or less than - # zero will use event based observation instead of - # time based observation. - # :update:: Specifies the DOM ID of the element whose - # innerHTML should be updated with the - # XMLHttpRequest response text. - # :with:: A JavaScript expression specifying the - # parameters for the XMLHttpRequest. This defaults - # to 'value', which in the evaluated context - # refers to the new field value. If you specify a - # string without a "=", it'll be extended to mean - # the form key that the value should be assigned to. - # So :with => "term" gives "'term'=value". If a "=" is - # present, no extension will happen. - # :on:: Specifies which event handler to observe. By default, - # it's set to "changed" for text fields and areas and - # "click" for radio buttons and checkboxes. With this, - # you can specify it instead to be "blur" or "focus" or - # any other event. - # - # Additionally, you may specify any of the options documented in - # link_to_remote. - def observe_field(field_id, options = {}) - if options[:frequency] && options[:frequency] > 0 - build_observer('Form.Element.Observer', field_id, options) - else - build_observer('Form.Element.EventObserver', field_id, options) - end - end - - # Like +observe_field+, but operates on an entire form identified by the - # DOM ID +form_id+. +options+ are the same as +observe_field+, except - # the default value of the :with option evaluates to the - # serialized (request string) value of the form. - def observe_form(form_id, options = {}) - if options[:frequency] - build_observer('Form.Observer', form_id, options) - else - build_observer('Form.EventObserver', form_id, options) - end - end - - # All the methods were moved to GeneratorMethods so that - # #include_helpers_from_context has nothing to overwrite. - class JavaScriptGenerator #:nodoc: - def initialize(context, &block) #:nodoc: - @context, @lines = context, [] - include_helpers_from_context - @context.instance_exec(self, &block) - end - - private - def include_helpers_from_context - @context.extended_by.each do |mod| - extend mod unless mod.name =~ /^ActionView::Helpers/ - end - extend GeneratorMethods - end - - # JavaScriptGenerator generates blocks of JavaScript code that allow you - # to change the content and presentation of multiple DOM elements. Use - # this in your Ajax response bodies, either in a - # - # mail_to "me@domain.com", "My email", :encode => "hex" # => - # My email - # - # You can also specify the cc address, bcc address, subject, and body parts of the message header to create a complex e-mail using the - # corresponding +cc+, +bcc+, +subject+, and +body+ html_options keys. Each of these options are URI escaped and then appended to - # the email_address before being output. Be aware that javascript keywords will not be escaped and may break this feature - # when encoding with javascript. - # Examples: - # mail_to "me@domain.com", "My email", :cc => "ccaddress@domain.com", :bcc => "bccaddress@domain.com", :subject => "This is an example email", :body => "This is the body of the message." # => - # My email - def mail_to(email_address, name = nil, html_options = {}) - html_options = html_options.stringify_keys - encode = html_options.delete("encode") - cc, bcc, subject, body = html_options.delete("cc"), html_options.delete("bcc"), html_options.delete("subject"), html_options.delete("body") - - string = '' - extras = '' - extras << "cc=#{CGI.escape(cc).gsub("+", "%20")}&" unless cc.nil? - extras << "bcc=#{CGI.escape(bcc).gsub("+", "%20")}&" unless bcc.nil? - extras << "body=#{CGI.escape(body).gsub("+", "%20")}&" unless body.nil? - extras << "subject=#{CGI.escape(subject).gsub("+", "%20")}&" unless subject.nil? - extras = "?" << extras.gsub!(/&?$/,"") unless extras.empty? - - email_address_obfuscated = email_address.dup - email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.has_key?("replace_at") - email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.has_key?("replace_dot") - - if encode == 'javascript' - tmp = "document.write('#{content_tag("a", name || email_address, html_options.merge({ "href" => "mailto:"+email_address.to_s+extras }))}');" - for i in 0...tmp.length - string << sprintf("%%%x",tmp[i]) - end - "" - elsif encode == 'hex' - for i in 0...email_address.length - if email_address[i,1] =~ /\w/ - string << sprintf("%%%x",email_address[i]) - else - string << email_address[i,1] - end - end - content_tag "a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:#{string}#{extras}" }) - else - content_tag "a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:#{email_address}#{extras}" }) - end - end - - # Returns true if the current page uri is generated by the options passed (in url_for format). - def current_page?(options) - CGI.escapeHTML(url_for(options)) == @controller.request.request_uri - end - - private - def convert_options_to_javascript!(html_options) - confirm, popup, post = html_options.delete("confirm"), html_options.delete("popup"), html_options.delete("post") - - html_options["onclick"] = case - when popup && post - raise ActionView::ActionViewError, "You can't use :popup and :post in the same link" - when confirm && popup - "if (#{confirm_javascript_function(confirm)}) { #{popup_javascript_function(popup)} };return false;" - when confirm && post - "if (#{confirm_javascript_function(confirm)}) { #{post_javascript_function} };return false;" - when confirm - "return #{confirm_javascript_function(confirm)};" - when post - "#{post_javascript_function}return false;" - when popup - popup_javascript_function(popup) + 'return false;' - else - html_options["onclick"] - end - end - - def confirm_javascript_function(confirm) - "confirm('#{escape_javascript(confirm)}')" - end - - def popup_javascript_function(popup) - popup.is_a?(Array) ? "window.open(this.href,'#{popup.first}','#{popup.last}');" : "window.open(this.href);" - end - - def post_javascript_function - "var f = document.createElement('form'); this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href; f.submit();" - end - - # Processes the _html_options_ hash, converting the boolean - # attributes from true/false form into the form required by - # HTML/XHTML. (An attribute is considered to be boolean if - # its name is listed in the given _bool_attrs_ array.) - # - # More specifically, for each boolean attribute in _html_options_ - # given as: - # - # "attr" => bool_value - # - # if the associated _bool_value_ evaluates to true, it is - # replaced with the attribute's name; otherwise the attribute is - # removed from the _html_options_ hash. (See the XHTML 1.0 spec, - # section 4.5 "Attribute Minimization" for more: - # http://www.w3.org/TR/xhtml1/#h-4.5) - # - # Returns the updated _html_options_ hash, which is also modified - # in place. - # - # Example: - # - # convert_boolean_attributes!( html_options, - # %w( checked disabled readonly ) ) - def convert_boolean_attributes!(html_options, bool_attrs) - bool_attrs.each { |x| html_options[x] = x if html_options.delete(x) } - html_options - end - end - end -end -module ActionView - # There's also a convenience method for rendering sub templates within the current controller that depends on a single object - # (we call this kind of sub templates for partials). It relies on the fact that partials should follow the naming convention of being - # prefixed with an underscore -- as to separate them from regular templates that could be rendered on their own. - # - # In a template for Advertiser#account: - # - # <%= render :partial => "account" %> - # - # This would render "advertiser/_account.rhtml" and pass the instance variable @account in as a local variable +account+ to - # the template for display. - # - # In another template for Advertiser#buy, we could have: - # - # <%= render :partial => "account", :locals => { :account => @buyer } %> - # - # <% for ad in @advertisements %> - # <%= render :partial => "ad", :locals => { :ad => ad } %> - # <% end %> - # - # This would first render "advertiser/_account.rhtml" with @buyer passed in as the local variable +account+, then render - # "advertiser/_ad.rhtml" and pass the local variable +ad+ to the template for display. - # - # == Rendering a collection of partials - # - # The example of partial use describes a familiar pattern where a template needs to iterate over an array and render a sub - # template for each of the elements. This pattern has been implemented as a single method that accepts an array and renders - # a partial by the same name as the elements contained within. So the three-lined example in "Using partials" can be rewritten - # with a single line: - # - # <%= render :partial => "ad", :collection => @advertisements %> - # - # This will render "advertiser/_ad.rhtml" and pass the local variable +ad+ to the template for display. An iteration counter - # will automatically be made available to the template with a name of the form +partial_name_counter+. In the case of the - # example above, the template would be fed +ad_counter+. - # - # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also just keep domain objects, - # like Active Records, in there. - # - # == Rendering shared partials - # - # Two controllers can share a set of partials and render them like this: - # - # <%= render :partial => "advertisement/ad", :locals => { :ad => @advertisement } %> - # - # This will render the partial "advertisement/_ad.rhtml" regardless of which controller this is being called from. - module Partials - # Deprecated, use render :partial - def render_partial(partial_path, local_assigns = nil, deprecated_local_assigns = nil) #:nodoc: - path, partial_name = partial_pieces(partial_path) - object = extracting_object(partial_name, local_assigns, deprecated_local_assigns) - local_assigns = extract_local_assigns(local_assigns, deprecated_local_assigns) - local_assigns = local_assigns ? local_assigns.clone : {} - add_counter_to_local_assigns!(partial_name, local_assigns) - add_object_to_local_assigns!(partial_name, local_assigns, object) - - if logger - ActionController::Base.benchmark("Rendered #{path}/_#{partial_name}", Logger::DEBUG, false) do - render("#{path}/_#{partial_name}", local_assigns) - end - else - render("#{path}/_#{partial_name}", local_assigns) - end - end - - # Deprecated, use render :partial, :collection - def render_partial_collection(partial_name, collection, partial_spacer_template = nil, local_assigns = nil) #:nodoc: - collection_of_partials = Array.new - counter_name = partial_counter_name(partial_name) - local_assigns = local_assigns ? local_assigns.clone : {} - collection.each_with_index do |element, counter| - local_assigns[counter_name] = counter - collection_of_partials.push(render_partial(partial_name, element, local_assigns)) - end - - return " " if collection_of_partials.empty? - - if partial_spacer_template - spacer_path, spacer_name = partial_pieces(partial_spacer_template) - collection_of_partials.join(render("#{spacer_path}/_#{spacer_name}")) - else - collection_of_partials.join - end - end - - alias_method :render_collection_of_partials, :render_partial_collection - - private - def partial_pieces(partial_path) - if partial_path.include?('/') - return File.dirname(partial_path), File.basename(partial_path) - else - return controller.class.controller_path, partial_path - end - end - - def partial_counter_name(partial_name) - "#{partial_name.split('/').last}_counter".intern - end - - def extracting_object(partial_name, local_assigns, deprecated_local_assigns) - if local_assigns.is_a?(Hash) || local_assigns.nil? - controller.instance_variable_get("@#{partial_name}") - else - # deprecated form where object could be passed in as second parameter - local_assigns - end - end - - def extract_local_assigns(local_assigns, deprecated_local_assigns) - local_assigns.is_a?(Hash) ? local_assigns : deprecated_local_assigns - end - - def add_counter_to_local_assigns!(partial_name, local_assigns) - counter_name = partial_counter_name(partial_name) - local_assigns[counter_name] = 1 unless local_assigns.has_key?(counter_name) - end - - def add_object_to_local_assigns!(partial_name, local_assigns, object) - local_assigns[partial_name.intern] ||= - if object.is_a?(ActionView::Base::ObjectWrapper) - object.value - else - object - end || controller.instance_variable_get("@#{partial_name}") - end - end -end -module ActionView - # The TemplateError exception is raised when the compilation of the template fails. This exception then gathers a - # bunch of intimate details and uses it to report a very precise exception message. - class TemplateError < ActionViewError #:nodoc: - SOURCE_CODE_RADIUS = 3 - - attr_reader :original_exception - - def initialize(base_path, file_name, assigns, source, original_exception) - @base_path, @assigns, @source, @original_exception = - base_path, assigns, source, original_exception - @file_name = file_name - end - - def message - original_exception.message - end - - def sub_template_message - if @sub_templates - "Trace of template inclusion: " + - @sub_templates.collect { |template| strip_base_path(template) }.join(", ") - else - "" - end - end - - def source_extract(indention = 0) - source_code = IO.readlines(@file_name) - - start_on_line = [ line_number - SOURCE_CODE_RADIUS - 1, 0 ].max - end_on_line = [ line_number + SOURCE_CODE_RADIUS - 1, source_code.length].min - - line_counter = start_on_line - extract = source_code[start_on_line..end_on_line].collect do |line| - line_counter += 1 - "#{' ' * indention}#{line_counter}: " + line - end - - extract.join - end - - def sub_template_of(file_name) - @sub_templates ||= [] - @sub_templates << file_name - end - - def line_number - if file_name - regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/ - [@original_exception.message, @original_exception.clean_backtrace].flatten.each do |line| - return $1.to_i if regexp =~ line - end - end - 0 - end - - def file_name - stripped = strip_base_path(@file_name) - stripped[0] == ?/ ? stripped[1..-1] : stripped - end - - def to_s - "\n\n#{self.class} (#{message}) on line ##{line_number} of #{file_name}:\n" + - source_extract + "\n " + - original_exception.clean_backtrace.join("\n ") + - "\n\n" - end - - def backtrace - [ - "On line ##{line_number} of #{file_name}\n\n#{source_extract(4)}\n " + - original_exception.clean_backtrace.join("\n ") - ] - end - - private - def strip_base_path(file_name) - file_name = File.expand_path(file_name).gsub(/^#{Regexp.escape File.expand_path(RAILS_ROOT)}/, '') - file_name.gsub(@base_path, "") - end - end -end - -Exception::TraceSubstitutions << [/:in\s+`_run_(html|xml).*'\s*$/, ''] if defined?(Exception::TraceSubstitutions) -Exception::TraceSubstitutions << [%r{^\s*#{Regexp.escape RAILS_ROOT}}, '#{RAILS_ROOT}'] if defined?(RAILS_ROOT) -#-- -# Copyright (c) 2004 David Heinemeier Hansson -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -#++ - -$:.unshift(File.dirname(__FILE__) + "/action_view/vendor") -require 'action_view/base' -require 'action_view/partials' - -ActionView::Base.class_eval do - include ActionView::Partials -end - -ActionView::Base.load_helpers(File.dirname(__FILE__) + "/action_view/helpers/") -$:.unshift(File.dirname(__FILE__) + '/../lib') -$:.unshift(File.dirname(__FILE__) + '/../../activesupport/lib/active_support') -$:.unshift(File.dirname(__FILE__) + '/fixtures/helpers') - -require 'yaml' -require 'test/unit' -require 'action_controller' -require 'breakpoint' - -require 'action_controller/test_process' - -ActionController::Base.logger = nil -ActionController::Base.ignore_missing_templates = false -ActionController::Routing::Routes.reload rescue nilrequire File.dirname(__FILE__) + '/abstract_unit' - -# Define the essentials -class ActiveRecordTestConnector - cattr_accessor :able_to_connect - cattr_accessor :connected - - # Set our defaults - self.connected = false - self.able_to_connect = true -end - -# Try to grab AR -begin - PATH_TO_AR = File.dirname(__FILE__) + '/../../activerecord' - require "#{PATH_TO_AR}/lib/active_record" unless Object.const_defined?(:ActiveRecord) - require "#{PATH_TO_AR}/lib/active_record/fixtures" unless Object.const_defined?(:Fixtures) -rescue Object => e - $stderr.puts "\nSkipping ActiveRecord assertion tests: #{e}" - ActiveRecordTestConnector.able_to_connect = false -end - -# Define the rest of the connector -class ActiveRecordTestConnector - def self.setup - unless self.connected || !self.able_to_connect - setup_connection - load_schema - self.connected = true - end - rescue Object => e - $stderr.puts "\nSkipping ActiveRecord assertion tests: #{e}" - #$stderr.puts " #{e.backtrace.join("\n ")}\n" - self.able_to_connect = false - end - - private - - def self.setup_connection - if Object.const_defined?(:ActiveRecord) - - begin - ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :dbfile => ':memory:') - ActiveRecord::Base.connection - rescue Object - $stderr.puts 'SQLite 3 unavailable; falling to SQLite 2.' - ActiveRecord::Base.establish_connection(:adapter => 'sqlite', :dbfile => ':memory:') - ActiveRecord::Base.connection - end - - Object.send(:const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name('type')) unless Object.const_defined?(:QUOTED_TYPE) - else - raise "Couldn't locate ActiveRecord." - end - end - - # Load actionpack sqlite tables - def self.load_schema - File.read(File.dirname(__FILE__) + "/fixtures/db_definitions/sqlite.sql").split(';').each do |sql| - ActiveRecord::Base.connection.execute(sql) unless sql.blank? - end - end -end - -# Test case for inheiritance -class ActiveRecordTestCase < Test::Unit::TestCase - # Set our fixture path - self.fixture_path = "#{File.dirname(__FILE__)}/fixtures/" - - def setup - abort_tests unless ActiveRecordTestConnector.connected = true - end - - # Default so Test::Unit::TestCase doesn't complain - def test_truth - end - - private - - # If things go wrong, we don't want to run our test cases. We'll just define them to test nothing. - def abort_tests - self.class.public_instance_methods.grep(/^test./).each do |method| - self.class.class_eval { define_method(method.to_sym){} } - end - end -end - -ActiveRecordTestConnector.setuprequire "#{File.dirname(__FILE__)}/../active_record_unit" -require 'fixtures/company' - -class ActiveRecordAssertionsController < ActionController::Base - self.template_root = "#{File.dirname(__FILE__)}/../fixtures/" - - # fail with 1 bad column - def nasty_columns_1 - @company = Company.new - @company.name = "B" - @company.rating = 2 - render :inline => "snicker...." - end - - # fail with 2 bad columns - def nasty_columns_2 - @company = Company.new - @company.name = "" - @company.rating = 2 - render :inline => "double snicker...." - end - - # this will pass validation - def good_company - @company = Company.new - @company.name = "A" - @company.rating = 69 - render :inline => "Goodness Gracious!" - end - - # this will fail validation - def bad_company - @company = Company.new - render :inline => "Who's Bad?" - end - - # the safety dance...... - def rescue_action(e) raise; end -end - -class ActiveRecordAssertionsControllerTest < ActiveRecordTestCase - fixtures :companies - - def setup - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - @controller = ActiveRecordAssertionsController.new - super - end - - # test for 1 bad apple column - def test_some_invalid_columns - process :nasty_columns_1 - assert_success - assert_invalid_record 'company' - assert_invalid_column_on_record 'company', 'rating' - assert_valid_column_on_record 'company', 'name' - assert_valid_column_on_record 'company', %w(name id) - end - - # test for 2 bad apples columns - def test_all_invalid_columns - process :nasty_columns_2 - assert_success - assert_invalid_record 'company' - assert_invalid_column_on_record 'company', 'rating' - assert_invalid_column_on_record 'company', 'name' - assert_invalid_column_on_record 'company', %w(name rating) - end - - # ensure we have no problems with an ActiveRecord - def test_valid_record - process :good_company - assert_success - assert_valid_record 'company' - end - - # ensure we have problems with an ActiveRecord - def test_invalid_record - process :bad_company - assert_success - assert_invalid_record 'company' - end -end# Unfurl the safety net. -path_to_ar = File.dirname(__FILE__) + '/../../../activerecord' -if Object.const_defined?(:ActiveRecord) or File.exist?(path_to_ar) - begin - -# These tests exercise CGI::Session::ActiveRecordStore, so you're going to -# need AR in a sibling directory to AP and have SQLite installed. - -unless Object.const_defined?(:ActiveRecord) - require File.join(path_to_ar, 'lib', 'active_record') -end - -require File.dirname(__FILE__) + '/../abstract_unit' -require 'action_controller/session/active_record_store' - -#ActiveRecord::Base.logger = Logger.new($stdout) -begin - CGI::Session::ActiveRecordStore::Session.establish_connection(:adapter => 'sqlite3', :database => ':memory:') - CGI::Session::ActiveRecordStore::Session.connection -rescue Object - $stderr.puts 'SQLite 3 unavailable; falling back to SQLite 2.' - begin - CGI::Session::ActiveRecordStore::Session.establish_connection(:adapter => 'sqlite', :database => ':memory:') - CGI::Session::ActiveRecordStore::Session.connection - rescue Object - $stderr.puts 'SQLite 2 unavailable; skipping ActiveRecordStore test suite.' - raise SystemExit - end -end - - -module CommonActiveRecordStoreTests - def test_basics - s = session_class.new(:session_id => '1234', :data => { 'foo' => 'bar' }) - assert_equal 'bar', s.data['foo'] - assert s.save - assert_equal 'bar', s.data['foo'] - - assert_not_nil t = session_class.find_by_session_id('1234') - assert_not_nil t.data - assert_equal 'bar', t.data['foo'] - end - - def test_reload_same_session - @new_session.update - reloaded = CGI::Session.new(CGI.new, 'session_id' => @new_session.session_id, 'database_manager' => CGI::Session::ActiveRecordStore) - assert_equal 'bar', reloaded['foo'] - end - - def test_tolerates_close_close - assert_nothing_raised do - @new_session.close - @new_session.close - end - end -end - -class ActiveRecordStoreTest < Test::Unit::TestCase - include CommonActiveRecordStoreTests - - def session_class - CGI::Session::ActiveRecordStore::Session - end - - def session_id_column - "session_id" - end - - def setup - session_class.create_table! - - ENV['REQUEST_METHOD'] = 'GET' - CGI::Session::ActiveRecordStore.session_class = session_class - - @cgi = CGI.new - @new_session = CGI::Session.new(@cgi, 'database_manager' => CGI::Session::ActiveRecordStore, 'new_session' => true) - @new_session['foo'] = 'bar' - end - -# this test only applies for eager sesssion saving -# def test_another_instance -# @another = CGI::Session.new(@cgi, 'session_id' => @new_session.session_id, 'database_manager' => CGI::Session::ActiveRecordStore) -# assert_equal @new_session.session_id, @another.session_id -# end - - def test_model_attribute - assert_kind_of CGI::Session::ActiveRecordStore::Session, @new_session.model - assert_equal({ 'foo' => 'bar' }, @new_session.model.data) - end - - def test_save_unloaded_session - c = session_class.connection - bogus_class = c.quote(Base64.encode64("\004\010o:\vBlammo\000")) - c.insert("INSERT INTO #{session_class.table_name} ('#{session_id_column}', 'data') VALUES ('abcdefghijklmnop', #{bogus_class})") - - sess = session_class.find_by_session_id('abcdefghijklmnop') - assert_not_nil sess - assert !sess.loaded? - - # because the session is not loaded, the save should be a no-op. If it - # isn't, this'll try and unmarshall the bogus class, and should get an error. - assert_nothing_raised { sess.save } - end - - def teardown - session_class.drop_table! - end -end - -class ColumnLimitTest < Test::Unit::TestCase - def setup - @session_class = CGI::Session::ActiveRecordStore::Session - @session_class.create_table! - end - - def teardown - @session_class.drop_table! - end - - def test_protection_from_data_larger_than_column - # Can't test this unless there is a limit - return unless limit = @session_class.data_column_size_limit - too_big = ':(' * limit - s = @session_class.new(:session_id => '666', :data => {'foo' => too_big}) - s.data - assert_raise(ActionController::SessionOverflowError) { s.save } - end -end - -class DeprecatedActiveRecordStoreTest < ActiveRecordStoreTest - def session_id_column - "sessid" - end - - def setup - session_class.connection.execute 'create table old_sessions (id integer primary key, sessid text unique, data text)' - session_class.table_name = 'old_sessions' - session_class.send :setup_sessid_compatibility! - - ENV['REQUEST_METHOD'] = 'GET' - CGI::Session::ActiveRecordStore.session_class = session_class - - @new_session = CGI::Session.new(CGI.new, 'database_manager' => CGI::Session::ActiveRecordStore, 'new_session' => true) - @new_session['foo'] = 'bar' - end - - def teardown - session_class.connection.execute 'drop table old_sessions' - session_class.table_name = 'sessions' - end -end - -class SqlBypassActiveRecordStoreTest < ActiveRecordStoreTest - def session_class - unless @session_class - @session_class = CGI::Session::ActiveRecordStore::SqlBypass - @session_class.connection = CGI::Session::ActiveRecordStore::Session.connection - end - @session_class - end - - def test_model_attribute - assert_kind_of CGI::Session::ActiveRecordStore::SqlBypass, @new_session.model - assert_equal({ 'foo' => 'bar' }, @new_session.model.data) - end -end - - -# End of safety net. - rescue Object => e - $stderr.puts "Skipping CGI::Session::ActiveRecordStore tests: #{e}" - #$stderr.puts " #{e.backtrace.join("\n ")}" - end -end -require File.dirname(__FILE__) + '/../active_record_unit' - -require 'fixtures/topic' -require 'fixtures/reply' -require 'fixtures/developer' -require 'fixtures/project' - -class PaginationTest < ActiveRecordTestCase - fixtures :topics, :replies, :developers, :projects, :developers_projects - - class PaginationController < ActionController::Base - self.template_root = "#{File.dirname(__FILE__)}/../fixtures/" - - def simple_paginate - @topic_pages, @topics = paginate(:topics) - render :nothing => true - end - - def paginate_with_per_page - @topic_pages, @topics = paginate(:topics, :per_page => 1) - render :nothing => true - end - - def paginate_with_order - @topic_pages, @topics = paginate(:topics, :order => 'created_at asc') - render :nothing => true - end - - def paginate_with_order_by - @topic_pages, @topics = paginate(:topics, :order_by => 'created_at asc') - render :nothing => true - end - - def paginate_with_include_and_order - @topic_pages, @topics = paginate(:topics, :include => :replies, :order => 'replies.created_at asc, topics.created_at asc') - render :nothing => true - end - - def paginate_with_conditions - @topic_pages, @topics = paginate(:topics, :conditions => ["created_at > ?", 30.minutes.ago]) - render :nothing => true - end - - def paginate_with_class_name - @developer_pages, @developers = paginate(:developers, :class_name => "DeVeLoPeR") - render :nothing => true - end - - def paginate_with_singular_name - @developer_pages, @developers = paginate() - render :nothing => true - end - - def paginate_with_joins - @developer_pages, @developers = paginate(:developers, - :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id', - :conditions => 'project_id=1') - render :nothing => true - end - - def paginate_with_join - @developer_pages, @developers = paginate(:developers, - :join => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id', - :conditions => 'project_id=1') - render :nothing => true - end - - def paginate_with_join_and_count - @developer_pages, @developers = paginate(:developers, - :join => 'd LEFT JOIN developers_projects ON d.id = developers_projects.developer_id', - :conditions => 'project_id=1', - :count => "d.id") - render :nothing => true - end - - def rescue_errors(e) raise e end - - def rescue_action(e) raise end - - end - - def setup - @controller = PaginationController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - super - end - - # Single Action Pagination Tests - - def test_simple_paginate - get :simple_paginate - assert_equal 1, assigns(:topic_pages).page_count - assert_equal 3, assigns(:topics).size - end - - def test_paginate_with_per_page - get :paginate_with_per_page - assert_equal 1, assigns(:topics).size - assert_equal 3, assigns(:topic_pages).page_count - end - - def test_paginate_with_order - get :paginate_with_order - expected = [topics(:futurama), - topics(:harvey_birdman), - topics(:rails)] - assert_equal expected, assigns(:topics) - assert_equal 1, assigns(:topic_pages).page_count - end - - def test_paginate_with_order_by - get :paginate_with_order - expected = assigns(:topics) - get :paginate_with_order_by - assert_equal expected, assigns(:topics) - assert_equal 1, assigns(:topic_pages).page_count - end - - def test_paginate_with_conditions - get :paginate_with_conditions - expected = [topics(:rails)] - assert_equal expected, assigns(:topics) - assert_equal 1, assigns(:topic_pages).page_count - end - - def test_paginate_with_class_name - get :paginate_with_class_name - - assert assigns(:developers).size > 0 - assert_equal DeVeLoPeR, assigns(:developers).first.class - end - - def test_paginate_with_joins - get :paginate_with_joins - assert_equal 2, assigns(:developers).size - developer_names = assigns(:developers).map { |d| d.name } - assert developer_names.include?('David') - assert developer_names.include?('Jamis') - end - - def test_paginate_with_join_and_conditions - get :paginate_with_joins - expected = assigns(:developers) - get :paginate_with_join - assert_equal expected, assigns(:developers) - end - - def test_paginate_with_join_and_count - get :paginate_with_joins - expected = assigns(:developers) - get :paginate_with_join_and_count - assert_equal expected, assigns(:developers) - end - - def test_paginate_with_include_and_order - get :paginate_with_include_and_order - expected = Topic.find(:all, :include => 'replies', :order => 'replies.created_at asc, topics.created_at asc', :limit => 10) - assert_equal expected, assigns(:topics) - end -end -require File.dirname(__FILE__) + '/../abstract_unit' - -# a controller class to facilitate the tests -class ActionPackAssertionsController < ActionController::Base - - # this does absolutely nothing - def nothing() render_text ""; end - - # a standard template - def hello_world() render "test/hello_world"; end - - # a standard template - def hello_xml_world() render "test/hello_xml_world"; end - - # a redirect to an internal location - def redirect_internal() redirect_to "/nothing"; end - - def redirect_to_action() redirect_to :action => "flash_me", :id => 1, :params => { "panda" => "fun" }; end - - def redirect_to_controller() redirect_to :controller => "elsewhere", :action => "flash_me"; end - - def redirect_to_path() redirect_to '/some/path' end - - def redirect_to_named_route() redirect_to route_one_url end - - # a redirect to an external location - def redirect_external() redirect_to_url "http://www.rubyonrails.org"; end - - # a 404 - def response404() render_text "", "404 AWOL"; end - - # a 500 - def response500() render_text "", "500 Sorry"; end - - # a fictional 599 - def response599() render_text "", "599 Whoah!"; end - - # putting stuff in the flash - def flash_me - flash['hello'] = 'my name is inigo montoya...' - render_text "Inconceivable!" - end - - # we have a flash, but nothing is in it - def flash_me_naked - flash.clear - render_text "wow!" - end - - # assign some template instance variables - def assign_this - @howdy = "ho" - render :inline => "Mr. Henke" - end - - def render_based_on_parameters - render_text "Mr. #{@params["name"]}" - end - - def render_url - render_text "
    #{url_for(:action => 'flash_me', :only_path => true)}
    " - end - - # puts something in the session - def session_stuffing - session['xmas'] = 'turkey' - render_text "ho ho ho" - end - - # raises exception on get requests - def raise_on_get - raise "get" if @request.get? - render_text "request method: #{@request.env['REQUEST_METHOD']}" - end - - # raises exception on post requests - def raise_on_post - raise "post" if @request.post? - render_text "request method: #{@request.env['REQUEST_METHOD']}" - end - - def get_valid_record - @record = Class.new do - def valid? - true - end - - def errors - Class.new do - def full_messages; []; end - end.new - end - - end.new - - render :nothing => true - end - - - def get_invalid_record - @record = Class.new do - - def valid? - false - end - - def errors - Class.new do - def full_messages; ['...stuff...']; end - end.new - end - end.new - - render :nothing => true - end - - # 911 - def rescue_action(e) raise; end -end - -module Admin - class InnerModuleController < ActionController::Base - def redirect_to_absolute_controller - redirect_to :controller => '/content' - end - def redirect_to_fellow_controller - redirect_to :controller => 'user' - end - end -end - -# --------------------------------------------------------------------------- - - -# tell the controller where to find its templates but start from parent -# directory of test_request_response to simulate the behaviour of a -# production environment -ActionPackAssertionsController.template_root = File.dirname(__FILE__) + "/../fixtures/" - - -# a test case to exercise the new capabilities TestRequest & TestResponse -class ActionPackAssertionsControllerTest < Test::Unit::TestCase - # let's get this party started - def setup - @controller = ActionPackAssertionsController.new - @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new - end - - # -- assertion-based testing ------------------------------------------------ - - def test_assert_tag_and_url_for - get :render_url - assert_tag :content => "/action_pack_assertions/flash_me" - end - - # test the session assertion to make sure something is there. - def test_assert_session_has - process :session_stuffing - assert_session_has 'xmas' - assert_session_has_no 'halloween' - end - - # test the get method, make sure the request really was a get - def test_get - assert_raise(RuntimeError) { get :raise_on_get } - get :raise_on_post - assert_equal @response.body, 'request method: GET' - end - - # test the get method, make sure the request really was a get - def test_post - assert_raise(RuntimeError) { post :raise_on_post } - post :raise_on_get - assert_equal @response.body, 'request method: POST' - end - -# the following test fails because the request_method is now cached on the request instance -# test the get/post switch within one test action -# def test_get_post_switch -# post :raise_on_get -# assert_equal @response.body, 'request method: POST' -# get :raise_on_post -# assert_equal @response.body, 'request method: GET' -# post :raise_on_get -# assert_equal @response.body, 'request method: POST' -# get :raise_on_post -# assert_equal @response.body, 'request method: GET' -# end - - # test the assertion of goodies in the template - def test_assert_template_has - process :assign_this - assert_template_has 'howdy' - end - - # test the assertion for goodies that shouldn't exist in the template - def test_assert_template_has_no - process :nothing - assert_template_has_no 'maple syrup' - assert_template_has_no 'howdy' - end - - # test the redirection assertions - def test_assert_redirect - process :redirect_internal - assert_redirect - end - - # test the redirect url string - def test_assert_redirect_url - process :redirect_external - assert_redirect_url 'http://www.rubyonrails.org' - end - - # test the redirection pattern matching on a string - def test_assert_redirect_url_match_string - process :redirect_external - assert_redirect_url_match 'rails.org' - end - - # test the redirection pattern matching on a pattern - def test_assert_redirect_url_match_pattern - process :redirect_external - assert_redirect_url_match /ruby/ - end - - # test the redirection to a named route - def test_assert_redirect_to_named_route - process :redirect_to_named_route - assert_raise(Test::Unit::AssertionFailedError) do - assert_redirected_to 'http://test.host/route_two' - end - end - - # test the flash-based assertions with something is in the flash - def test_flash_assertions_full - process :flash_me - assert @response.has_flash_with_contents? - assert_flash_exists - assert_flash_not_empty - assert_flash_has 'hello' - assert_flash_has_no 'stds' - end - - # test the flash-based assertions with no flash at all - def test_flash_assertions_negative - process :nothing - assert_flash_empty - assert_flash_has_no 'hello' - assert_flash_has_no 'qwerty' - end - - # test the assert_rendered_file - def test_assert_rendered_file - process :hello_world - assert_rendered_file 'test/hello_world' - assert_rendered_file 'hello_world' - end - - # test the assert_success assertion - def test_assert_success - process :nothing - assert_success - assert_rendered_file - end - - # -- standard request/response object testing -------------------------------- - - # ensure our session is working properly - def test_session_objects - process :session_stuffing - assert @response.has_session_object?('xmas') - assert_session_equal 'turkey', 'xmas' - assert !@response.has_session_object?('easter') - end - - # make sure that the template objects exist - def test_template_objects_alive - process :assign_this - assert !@response.has_template_object?('hi') - assert @response.has_template_object?('howdy') - end - - # make sure we don't have template objects when we shouldn't - def test_template_object_missing - process :nothing - assert_nil @response.template_objects['howdy'] - end - - def test_assigned_equal - process :assign_this - assert_assigned_equal "ho", :howdy - end - - # check the empty flashing - def test_flash_me_naked - process :flash_me_naked - assert !@response.has_flash? - assert !@response.has_flash_with_contents? - end - - # check if we have flash objects - def test_flash_haves - process :flash_me - assert @response.has_flash? - assert @response.has_flash_with_contents? - assert @response.has_flash_object?('hello') - end - - # ensure we don't have flash objects - def test_flash_have_nots - process :nothing - assert !@response.has_flash? - assert !@response.has_flash_with_contents? - assert_nil @response.flash['hello'] - end - - # examine that the flash objects are what we expect - def test_flash_equals - process :flash_me - assert_flash_equal 'my name is inigo montoya...', 'hello' - end - - - # check if we were rendered by a file-based template? - def test_rendered_action - process :nothing - assert !@response.rendered_with_file? - - process :hello_world - assert @response.rendered_with_file? - assert 'hello_world', @response.rendered_file - end - - # check the redirection location - def test_redirection_location - process :redirect_internal - assert_equal 'http://test.host/nothing', @response.redirect_url - - process :redirect_external - assert_equal 'http://www.rubyonrails.org', @response.redirect_url - - process :nothing - assert_nil @response.redirect_url - end - - - # check server errors - def test_server_error_response_code - process :response500 - assert @response.server_error? - - process :response599 - assert @response.server_error? - - process :response404 - assert !@response.server_error? - end - - # check a 404 response code - def test_missing_response_code - process :response404 - assert @response.missing? - end - - # check to see if our redirection matches a pattern - def test_redirect_url_match - process :redirect_external - assert @response.redirect? - assert @response.redirect_url_match?("rubyonrails") - assert @response.redirect_url_match?(/rubyonrails/) - assert !@response.redirect_url_match?("phpoffrails") - assert !@response.redirect_url_match?(/perloffrails/) - end - - # check for a redirection - def test_redirection - process :redirect_internal - assert @response.redirect? - - process :redirect_external - assert @response.redirect? - - process :nothing - assert !@response.redirect? - end - - # check a successful response code - def test_successful_response_code - process :nothing - assert @response.success? - end - - # a basic check to make sure we have a TestResponse object - def test_has_response - process :nothing - assert_kind_of ActionController::TestResponse, @response - end - - def test_render_based_on_parameters - process :render_based_on_parameters, "name" => "David" - assert_equal "Mr. David", @response.body - end - - def test_assert_template_xpath_match_no_matches - process :hello_xml_world - assert_raises Test::Unit::AssertionFailedError do - assert_template_xpath_match('/no/such/node/in/document') - end - end - - def test_simple_one_element_xpath_match - process :hello_xml_world - assert_template_xpath_match('//title', "Hello World") - end - - def test_array_of_elements_in_xpath_match - process :hello_xml_world - assert_template_xpath_match('//p', %w( abes monks wiseguys )) - end - - def test_follow_redirect - process :redirect_to_action - assert_redirected_to :action => "flash_me" - - follow_redirect - assert_equal 1, @request.parameters["id"].to_i - - assert "Inconceivable!", @response.body - end - - def test_follow_redirect_outside_current_action - process :redirect_to_controller - assert_redirected_to :controller => "elsewhere", :action => "flash_me" - - assert_raises(RuntimeError, "Can't follow redirects outside of current controller (elsewhere)") { follow_redirect } - end - - def test_redirected_to_url_leadling_slash - process :redirect_to_path - assert_redirected_to '/some/path' - end - def test_redirected_to_url_no_leadling_slash - process :redirect_to_path - assert_redirected_to 'some/path' - end - def test_redirected_to_url_full_url - process :redirect_to_path - assert_redirected_to 'http://test.host/some/path' - end - - def test_redirected_to_with_nested_controller - @controller = Admin::InnerModuleController.new - get :redirect_to_absolute_controller - assert_redirected_to :controller => 'content' - - get :redirect_to_fellow_controller - assert_redirected_to :controller => 'admin/user' - end - - def test_assert_valid - get :get_valid_record - assert_valid assigns('record') - end - - def test_assert_valid_failing - get :get_invalid_record - - begin - assert_valid assigns('record') - assert false - rescue Test::Unit::AssertionFailedError => e - end - end -end - -class ActionPackHeaderTest < Test::Unit::TestCase - def setup - @controller = ActionPackAssertionsController.new - @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new - end - - def test_rendering_xml_sets_content_type - process :hello_xml_world - assert_equal('application/xml', @controller.headers['Content-Type']) - end - - def test_rendering_xml_respects_content_type - @response.headers['Content-Type'] = 'application/pdf' - process :hello_xml_world - assert_equal('application/pdf', @controller.headers['Content-Type']) - end -end -require File.dirname(__FILE__) + '/../abstract_unit' - -class Address - - def Address.count(conditions = nil, join = nil) - nil - end - - def Address.find_all(arg1, arg2, arg3, arg4) - [] - end - - def self.find(*args) - [] - end -end - -class AddressesTestController < ActionController::Base - - scaffold :address - - def self.controller_name; "addresses"; end - def self.controller_path; "addresses"; end - -end - -AddressesTestController.template_root = File.dirname(__FILE__) + "/../fixtures/" - -class AddressesTest < Test::Unit::TestCase - def setup - @controller = AddressesTestController.new - - # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get - # a more accurate simulation of what happens in "real life". - @controller.logger = Logger.new(nil) - - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - - @request.host = "www.nextangle.com" - end - - def test_list - get :list - assert_equal "We only need to get this far!", @response.body.chomp - end - - -end -require File.dirname(__FILE__) + '/../abstract_unit' -require 'test/unit' -require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late - -# Provide some controller to run the tests on. -module Submodule - class ContainedEmptyController < ActionController::Base - end - class ContainedNonEmptyController < ActionController::Base - def public_action - end - - hide_action :hidden_action - def hidden_action - end - - def another_hidden_action - end - hide_action :another_hidden_action - end - class SubclassedController < ContainedNonEmptyController - hide_action :public_action # Hiding it here should not affect the superclass. - end -end -class EmptyController < ActionController::Base - include ActionController::Caching -end -class NonEmptyController < ActionController::Base - def public_action - end - - hide_action :hidden_action - def hidden_action - end -end - -class ControllerClassTests < Test::Unit::TestCase - def test_controller_path - assert_equal 'empty', EmptyController.controller_path - assert_equal 'submodule/contained_empty', Submodule::ContainedEmptyController.controller_path - end - def test_controller_name - assert_equal 'empty', EmptyController.controller_name - assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name - end -end - -class ControllerInstanceTests < Test::Unit::TestCase - def setup - @empty = EmptyController.new - @contained = Submodule::ContainedEmptyController.new - @empty_controllers = [@empty, @contained, Submodule::SubclassedController.new] - - @non_empty_controllers = [NonEmptyController.new, - Submodule::ContainedNonEmptyController.new] - end - - def test_action_methods - @empty_controllers.each do |c| - assert_equal Set.new, c.send(:action_methods), "#{c.class.controller_path} should be empty!" - end - @non_empty_controllers.each do |c| - assert_equal Set.new('public_action'), c.send(:action_methods), "#{c.class.controller_path} should not be empty!" - end - end -end -require File.dirname(__FILE__) + '/../abstract_unit' -require 'test/unit' - -# Provide some static controllers. -class BenchmarkedController < ActionController::Base - def public_action - render :nothing => true - end - - def rescue_action(e) - raise e - end -end - -class BenchmarkTest < Test::Unit::TestCase - class MockLogger - def method_missing(*args) - end - end - - def setup - @controller = BenchmarkedController.new - # benchmark doesn't do anything unless a logger is set - @controller.logger = MockLogger.new - @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new - @request.host = "test.actioncontroller.i" - end - - def test_with_http_1_0_request - @request.host = nil - assert_nothing_raised { get :public_action } - end -end -require 'fileutils' -require File.dirname(__FILE__) + '/../abstract_unit' - -class TestLogDevice < Logger::LogDevice - attr :last_message, true - - def initialize - @last_message=String.new - end - - def write(message) - @last_message << message - end - - def clear - @last_message = String.new - end -end - -#setup our really sophisticated logger -TestLog = TestLogDevice.new -RAILS_DEFAULT_LOGGER = Logger.new(TestLog) -ActionController::Base.logger = RAILS_DEFAULT_LOGGER - -def use_store - #generate a random key to ensure the cache is always in a different location - RANDOM_KEY = rand(99999999).to_s - FILE_STORE_PATH = File.dirname(__FILE__) + '/../temp/' + RANDOM_KEY - ActionController::Base.perform_caching = true - ActionController::Base.fragment_cache_store = :file_store, FILE_STORE_PATH -end - -class TestController < ActionController::Base - caches_action :render_to_cache, :index - - def render_to_cache - render_text "Render Cached" - end - alias :index :render_to_cache -end - -class FileStoreTest < Test::Unit::TestCase - def setup - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - @controller = TestController.new - @request.host = "hostname.com" - end - - def teardown - FileUtils.rm_rf(FILE_STORE_PATH) - end - - def test_render_cached - assert_fragment_cached { get :render_to_cache } - assert_fragment_hit { get :render_to_cache } - end - - - private - def assert_fragment_cached - yield - assert(TestLog.last_message.include?("Cached fragment:"), "--ERROR-- FileStore write failed ----") - assert(!TestLog.last_message.include?("Couldn't create cache directory:"), "--ERROR-- FileStore create directory failed ----") - TestLog.clear - end - - def assert_fragment_hit - yield - assert(TestLog.last_message.include?("Fragment read:"), "--ERROR-- Fragment not found in FileStore ----") - assert(!TestLog.last_message.include?("Cached fragment:"), "--ERROR-- Did cache ----") - TestLog.clear - end -endrequire File.dirname(__FILE__) + '/../abstract_unit' - -class CaptureController < ActionController::Base - def self.controller_name; "test"; end - def self.controller_path; "test"; end - - def content_for - render :layout => "talk_from_action" - end - - def erb_content_for - render :layout => "talk_from_action" - end - - def block_content_for - render :layout => "talk_from_action" - end - - def non_erb_block_content_for - render :layout => "talk_from_action" - end - - def rescue_action(e) raise end -end - -CaptureController.template_root = File.dirname(__FILE__) + "/../fixtures/" - -class CaptureTest < Test::Unit::TestCase - def setup - @controller = CaptureController.new - - # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get - # a more accurate simulation of what happens in "real life". - @controller.logger = Logger.new(nil) - - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - - @request.host = "www.nextangle.com" - end - - def test_simple_capture - get :capturing - assert_equal "Dreamy days", @response.body.strip - end - - def test_content_for - get :content_for - assert_equal expected_content_for_output, @response.body - end - - def test_erb_content_for - get :content_for - assert_equal expected_content_for_output, @response.body - end - - def test_block_content_for - get :block_content_for - assert_equal expected_content_for_output, @response.body - end - - def test_non_erb_block_content_for - get :non_erb_block_content_for - assert_equal expected_content_for_output, @response.body - end - - def test_update_element_with_capture - get :update_element_with_capture - assert_equal( - "" + - "\n\n$('status').innerHTML = '\\n You bought something!\\n';", - @response.body.strip - ) - end - - private - def expected_content_for_output - "Putting stuff in the title!\n\nGreat stuff!" - end -end -require File.dirname(__FILE__) + '/../abstract_unit' -require 'action_controller/cgi_process' -require 'action_controller/cgi_ext/cgi_ext' - - -require 'stringio' - -class CGITest < Test::Unit::TestCase - def setup - @query_string = "action=create_customer&full_name=David%20Heinemeier%20Hansson&customerId=1" - @query_string_with_nil = "action=create_customer&full_name=" - @query_string_with_array = "action=create_customer&selected[]=1&selected[]=2&selected[]=3" - @query_string_with_amps = "action=create_customer&name=Don%27t+%26+Does" - @query_string_with_multiple_of_same_name = - "action=update_order&full_name=Lau%20Taarnskov&products=4&products=2&products=3" - @query_string_with_many_equal = "action=create_customer&full_name=abc=def=ghi" - @query_string_without_equal = "action" - @query_string_with_many_ampersands = - "&action=create_customer&&&full_name=David%20Heinemeier%20Hansson" - end - - def test_query_string - assert_equal( - { "action" => "create_customer", "full_name" => "David Heinemeier Hansson", "customerId" => "1"}, - CGIMethods.parse_query_parameters(@query_string) - ) - end - - def test_deep_query_string - assert_equal({'x' => {'y' => {'z' => '10'}}}, CGIMethods.parse_query_parameters('x[y][z]=10')) - end - - def test_deep_query_string_with_array - assert_equal({'x' => {'y' => {'z' => ['10']}}}, CGIMethods.parse_query_parameters('x[y][z][]=10')) - assert_equal({'x' => {'y' => {'z' => ['10', '5']}}}, CGIMethods.parse_query_parameters('x[y][z][]=10&x[y][z][]=5')) - end - - def test_query_string_with_nil - assert_equal( - { "action" => "create_customer", "full_name" => nil}, - CGIMethods.parse_query_parameters(@query_string_with_nil) - ) - end - - def test_query_string_with_array - assert_equal( - { "action" => "create_customer", "selected" => ["1", "2", "3"]}, - CGIMethods.parse_query_parameters(@query_string_with_array) - ) - end - - def test_query_string_with_amps - assert_equal( - { "action" => "create_customer", "name" => "Don't & Does"}, - CGIMethods.parse_query_parameters(@query_string_with_amps) - ) - end - - def test_query_string_with_many_equal - assert_equal( - { "action" => "create_customer", "full_name" => "abc=def=ghi"}, - CGIMethods.parse_query_parameters(@query_string_with_many_equal) - ) - end - - def test_query_string_without_equal - assert_equal( - { "action" => nil }, - CGIMethods.parse_query_parameters(@query_string_without_equal) - ) - end - - def test_query_string_with_many_ampersands - assert_equal( - { "action" => "create_customer", "full_name" => "David Heinemeier Hansson"}, - CGIMethods.parse_query_parameters(@query_string_with_many_ampersands) - ) - end - - def test_parse_params - input = { - "customers[boston][first][name]" => [ "David" ], - "customers[boston][first][url]" => [ "http://David" ], - "customers[boston][second][name]" => [ "Allan" ], - "customers[boston][second][url]" => [ "http://Allan" ], - "something_else" => [ "blah" ], - "something_nil" => [ nil ], - "something_empty" => [ "" ], - "products[first]" => [ "Apple Computer" ], - "products[second]" => [ "Pc" ] - } - - expected_output = { - "customers" => { - "boston" => { - "first" => { - "name" => "David", - "url" => "http://David" - }, - "second" => { - "name" => "Allan", - "url" => "http://Allan" - } - } - }, - "something_else" => "blah", - "something_empty" => "", - "something_nil" => "", - "products" => { - "first" => "Apple Computer", - "second" => "Pc" - } - } - - assert_equal expected_output, CGIMethods.parse_request_parameters(input) - end - - def test_parse_params_from_multipart_upload - mockup = Struct.new(:content_type, :original_filename) - file = mockup.new('img/jpeg', 'foo.jpg') - ie_file = mockup.new('img/jpeg', 'c:\\Documents and Settings\\foo\\Desktop\\bar.jpg') - - input = { - "something" => [ StringIO.new("") ], - "array_of_stringios" => [[ StringIO.new("One"), StringIO.new("Two") ]], - "mixed_types_array" => [[ StringIO.new("Three"), "NotStringIO" ]], - "mixed_types_as_checkboxes[strings][nested]" => [[ file, "String", StringIO.new("StringIO")]], - "ie_mixed_types_as_checkboxes[strings][nested]" => [[ ie_file, "String", StringIO.new("StringIO")]], - "products[string]" => [ StringIO.new("Apple Computer") ], - "products[file]" => [ file ], - "ie_products[string]" => [ StringIO.new("Microsoft") ], - "ie_products[file]" => [ ie_file ] - } - - expected_output = { - "something" => "", - "array_of_stringios" => ["One", "Two"], - "mixed_types_array" => [ "Three", "NotStringIO" ], - "mixed_types_as_checkboxes" => { - "strings" => { - "nested" => [ file, "String", "StringIO" ] - }, - }, - "ie_mixed_types_as_checkboxes" => { - "strings" => { - "nested" => [ ie_file, "String", "StringIO" ] - }, - }, - "products" => { - "string" => "Apple Computer", - "file" => file - }, - "ie_products" => { - "string" => "Microsoft", - "file" => ie_file - } - } - - params = CGIMethods.parse_request_parameters(input) - assert_equal expected_output, params - - # Lone filenames are preserved. - assert_equal 'foo.jpg', params['mixed_types_as_checkboxes']['strings']['nested'].first.original_filename - assert_equal 'foo.jpg', params['products']['file'].original_filename - - # But full Windows paths are reduced to their basename. - assert_equal 'bar.jpg', params['ie_mixed_types_as_checkboxes']['strings']['nested'].first.original_filename - assert_equal 'bar.jpg', params['ie_products']['file'].original_filename - end - - def test_parse_params_with_file - input = { - "customers[boston][first][name]" => [ "David" ], - "something_else" => [ "blah" ], - "logo" => [ File.new(File.dirname(__FILE__) + "/cgi_test.rb").path ] - } - - expected_output = { - "customers" => { - "boston" => { - "first" => { - "name" => "David" - } - } - }, - "something_else" => "blah", - "logo" => File.new(File.dirname(__FILE__) + "/cgi_test.rb").path, - } - - assert_equal expected_output, CGIMethods.parse_request_parameters(input) - end - - def test_parse_params_with_array - input = { "selected[]" => [ "1", "2", "3" ] } - - expected_output = { "selected" => [ "1", "2", "3" ] } - - assert_equal expected_output, CGIMethods.parse_request_parameters(input) - end - - def test_parse_params_with_non_alphanumeric_name - input = { "a/b[c]" => %w(d) } - expected = { "a/b" => { "c" => "d" }} - assert_equal expected, CGIMethods.parse_request_parameters(input) - end - - def test_parse_params_with_single_brackets_in_middle - input = { "a/b[c]d" => %w(e) } - expected = { "a/b[c]d" => "e" } - assert_equal expected, CGIMethods.parse_request_parameters(input) - end - - def test_parse_params_with_separated_brackets - input = { "a/b@[c]d[e]" => %w(f) } - expected = { "a/b@" => { "c]d[e" => "f" }} - assert_equal expected, CGIMethods.parse_request_parameters(input) - end - - def test_parse_params_with_separated_brackets_and_array - input = { "a/b@[c]d[e][]" => %w(f) } - expected = { "a/b@" => { "c]d[e" => ["f"] }} - assert_equal expected , CGIMethods.parse_request_parameters(input) - end - - def test_parse_params_with_unmatched_brackets_and_array - input = { "a/b@[c][d[e][]" => %w(f) } - expected = { "a/b@" => { "c" => { "d[e" => ["f"] }}} - assert_equal expected, CGIMethods.parse_request_parameters(input) - end -end - -class MultipartCGITest < Test::Unit::TestCase - FIXTURE_PATH = File.dirname(__FILE__) + '/../fixtures/multipart' - - def setup - ENV['REQUEST_METHOD'] = 'POST' - ENV['CONTENT_LENGTH'] = '0' - ENV['CONTENT_TYPE'] = 'multipart/form-data, boundary=AaB03x' - end - - def test_single_parameter - params = process('single_parameter') - assert_equal({ 'foo' => 'bar' }, params) - end - - def test_text_file - params = process('text_file') - assert_equal %w(file foo), params.keys.sort - assert_equal 'bar', params['foo'] - - file = params['file'] - assert_kind_of StringIO, file - assert_equal 'file.txt', file.original_filename - assert_equal "text/plain\r", file.content_type - assert_equal 'contents', file.read - end - - def test_large_text_file - params = process('large_text_file') - assert_equal %w(file foo), params.keys.sort - assert_equal 'bar', params['foo'] - - file = params['file'] - assert_kind_of Tempfile, file - assert_equal 'file.txt', file.original_filename - assert_equal "text/plain\r", file.content_type - assert ('a' * 20480) == file.read - end - - def test_binary_file - params = process('binary_file') - assert_equal %w(file flowers foo), params.keys.sort - assert_equal 'bar', params['foo'] - - file = params['file'] - assert_kind_of StringIO, file - assert_equal 'file.txt', file.original_filename - assert_equal "text/plain\r", file.content_type - assert_equal 'contents', file.read - - file = params['flowers'] - assert_kind_of StringIO, file - assert_equal 'flowers.jpg', file.original_filename - assert_equal "image/jpeg\r", file.content_type - assert_equal 19512, file.size - #assert_equal File.read(File.dirname(__FILE__) + '/../../../activerecord/test/fixtures/flowers.jpg'), file.read - end - - def test_mixed_files - params = process('mixed_files') - assert_equal %w(files foo), params.keys.sort - assert_equal 'bar', params['foo'] - - # Ruby CGI doesn't handle multipart/mixed for us. - assert_kind_of StringIO, params['files'] - assert_equal 19756, params['files'].size - end - - private - def process(name) - old_stdin = $stdin - File.open(File.join(FIXTURE_PATH, name), 'rb') do |file| - ENV['CONTENT_LENGTH'] = file.stat.size.to_s - $stdin = file - CGIMethods.parse_request_parameters CGI.new.params - end - ensure - $stdin = old_stdin - end -end - - -class CGIRequestTest < Test::Unit::TestCase - def setup - @request_hash = {"HTTP_MAX_FORWARDS"=>"10", "SERVER_NAME"=>"glu.ttono.us:8007", "FCGI_ROLE"=>"RESPONDER", "HTTP_X_FORWARDED_HOST"=>"glu.ttono.us", "HTTP_ACCEPT_ENCODING"=>"gzip, deflate", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", "PATH_INFO"=>"", "HTTP_ACCEPT_LANGUAGE"=>"en", "HTTP_HOST"=>"glu.ttono.us:8007", "SERVER_PROTOCOL"=>"HTTP/1.1", "REDIRECT_URI"=>"/dispatch.fcgi", "SCRIPT_NAME"=>"/dispatch.fcgi", "SERVER_ADDR"=>"207.7.108.53", "REMOTE_ADDR"=>"207.7.108.53", "SERVER_SOFTWARE"=>"lighttpd/1.4.5", "HTTP_COOKIE"=>"_session_id=c84ace84796670c052c6ceb2451fb0f2; is_admin=yes", "HTTP_X_FORWARDED_SERVER"=>"glu.ttono.us", "REQUEST_URI"=>"/admin", "DOCUMENT_ROOT"=>"/home/kevinc/sites/typo/public", "SERVER_PORT"=>"8007", "QUERY_STRING"=>"", "REMOTE_PORT"=>"63137", "GATEWAY_INTERFACE"=>"CGI/1.1", "HTTP_X_FORWARDED_FOR"=>"65.88.180.234", "HTTP_ACCEPT"=>"*/*", "SCRIPT_FILENAME"=>"/home/kevinc/sites/typo/public/dispatch.fcgi", "REDIRECT_STATUS"=>"200", "REQUEST_METHOD"=>"GET"} - # cookie as returned by some Nokia phone browsers (no space after semicolon separator) - @alt_cookie_fmt_request_hash = {"HTTP_COOKIE"=>"_session_id=c84ace84796670c052c6ceb2451fb0f2;is_admin=yes"} - @fake_cgi = Struct.new(:env_table).new(@request_hash) - @request = ActionController::CgiRequest.new(@fake_cgi) - end - - def test_proxy_request - assert_equal 'glu.ttono.us', @request.host_with_port - end - - def test_http_host - @request_hash.delete "HTTP_X_FORWARDED_HOST" - @request_hash['HTTP_HOST'] = "rubyonrails.org:8080" - assert_equal "rubyonrails.org:8080", @request.host_with_port - - @request_hash['HTTP_X_FORWARDED_HOST'] = "www.firsthost.org, www.secondhost.org" - assert_equal "www.secondhost.org", @request.host - end - - def test_http_host_with_default_port_overrides_server_port - @request_hash.delete "HTTP_X_FORWARDED_HOST" - @request_hash['HTTP_HOST'] = "rubyonrails.org" - assert_equal "rubyonrails.org", @request.host_with_port - end - - def test_host_with_port_defaults_to_server_name_if_no_host_headers - @request_hash.delete "HTTP_X_FORWARDED_HOST" - @request_hash.delete "HTTP_HOST" - assert_equal "glu.ttono.us:8007", @request.host_with_port - end - - def test_host_with_port_falls_back_to_server_addr_if_necessary - @request_hash.delete "HTTP_X_FORWARDED_HOST" - @request_hash.delete "HTTP_HOST" - @request_hash.delete "SERVER_NAME" - assert_equal "207.7.108.53:8007", @request.host_with_port - end - - def test_cookie_syntax_resilience - cookies = CGI::Cookie::parse(@request_hash["HTTP_COOKIE"]); - assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], cookies["_session_id"] - assert_equal ["yes"], cookies["is_admin"] - - alt_cookies = CGI::Cookie::parse(@alt_cookie_fmt_request_hash["HTTP_COOKIE"]); - assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], alt_cookies["_session_id"] - assert_equal ["yes"], alt_cookies["is_admin"] - end -end -require File.dirname(__FILE__) + '/../abstract_unit' - -class CallerController < ActionController::Base - def calling_from_controller - render_component(:controller => "callee", :action => "being_called") - end - - def calling_from_controller_with_params - render_component(:controller => "callee", :action => "being_called", :params => { "name" => "David" }) - end - - def calling_from_controller_with_different_status_code - render_component(:controller => "callee", :action => "blowing_up") - end - - def calling_from_template - render_template "Ring, ring: <%= render_component(:controller => 'callee', :action => 'being_called') %>" - end - - def internal_caller - render_template "Are you there? <%= render_component(:action => 'internal_callee') %>" - end - - def internal_callee - render_text "Yes, ma'am" - end - - def set_flash - render_component(:controller => "callee", :action => "set_flash") - end - - def use_flash - render_component(:controller => "callee", :action => "use_flash") - end - - def calling_redirected - render_component(:controller => "callee", :action => "redirected") - end - - def calling_redirected_as_string - render_template "<%= render_component(:controller => 'callee', :action => 'redirected') %>" - end - - def rescue_action(e) raise end -end - -class CalleeController < ActionController::Base - def being_called - render_text "#{@params["name"] || "Lady"} of the House, speaking" - end - - def blowing_up - render_text "It's game over, man, just game over, man!", "500 Internal Server Error" - end - - def set_flash - flash[:notice] = 'My stoney baby' - render :text => 'flash is set' - end - - def use_flash - render :text => flash[:notice] || 'no flash' - end - - def redirected - redirect_to :controller => "callee", :action => "being_called" - end - - def rescue_action(e) raise end -end - -class ComponentsTest < Test::Unit::TestCase - def setup - @controller = CallerController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end - - def test_calling_from_controller - get :calling_from_controller - assert_equal "Lady of the House, speaking", @response.body - end - - def test_calling_from_controller_with_params - get :calling_from_controller_with_params - assert_equal "David of the House, speaking", @response.body - end - - def test_calling_from_controller_with_different_status_code - get :calling_from_controller_with_different_status_code - assert_equal 500, @response.response_code - end - - def test_calling_from_template - get :calling_from_template - assert_equal "Ring, ring: Lady of the House, speaking", @response.body - end - - def test_internal_calling - get :internal_caller - assert_equal "Are you there? Yes, ma'am", @response.body - end - - def test_flash - get :set_flash - assert_equal 'My stoney baby', flash[:notice] - get :use_flash - assert_equal 'My stoney baby', @response.body - get :use_flash - assert_equal 'no flash', @response.body - end - - def test_component_redirect_redirects - get :calling_redirected - - assert_redirected_to :action => "being_called" - end - - def test_component_multiple_redirect_redirects - test_component_redirect_redirects - test_internal_calling - end - - def test_component_as_string_redirect_renders_redirecte_action - get :calling_redirected_as_string - - assert_equal "Lady of the House, speaking", @response.body - end -endrequire File.dirname(__FILE__) + '/../abstract_unit' - -class CookieTest < Test::Unit::TestCase - class TestController < ActionController::Base - def authenticate_with_deprecated_writer - cookie "name" => "user_name", "value" => "david" - render_text "hello world" - end - - def authenticate - cookies["user_name"] = "david" - render_text "hello world" - end - - def authenticate_for_fourten_days - cookies["user_name"] = { "value" => "david", "expires" => Time.local(2005, 10, 10) } - render_text "hello world" - end - - def authenticate_for_fourten_days_with_symbols - cookies[:user_name] = { :value => "david", :expires => Time.local(2005, 10, 10) } - render_text "hello world" - end - - def set_multiple_cookies - cookies["user_name"] = { "value" => "david", "expires" => Time.local(2005, 10, 10) } - cookies["login"] = "XJ-122" - render_text "hello world" - end - - def access_frozen_cookies - @cookies["will"] = "work" - render_text "hello world" - end - - def rescue_action(e) raise end - end - - def setup - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - - @request.host = "www.nextangle.com" - end - - def test_setting_cookie_with_deprecated_writer - @request.action = "authenticate_with_deprecated_writer" - assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david") ], process_request.headers["cookie"] - end - - def test_setting_cookie - @request.action = "authenticate" - assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david") ], process_request.headers["cookie"] - end - - def test_setting_cookie_for_fourteen_days - @request.action = "authenticate_for_fourten_days" - assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david", "expires" => Time.local(2005, 10, 10)) ], process_request.headers["cookie"] - end - - def test_setting_cookie_for_fourteen_days_with_symbols - @request.action = "authenticate_for_fourten_days" - assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david", "expires" => Time.local(2005, 10, 10)) ], process_request.headers["cookie"] - end - - def test_multiple_cookies - @request.action = "set_multiple_cookies" - assert_equal 2, process_request.headers["cookie"].size - end - - def test_setting_test_cookie - @request.action = "access_frozen_cookies" - assert_nothing_raised { process_request } - end - - private - def process_request - TestController.process(@request, @response) - end -end -require File.dirname(__FILE__) + '/../abstract_unit' - -class CustomHandler - def initialize( view ) - @view = view - end - - def render( template, local_assigns ) - [ template, - local_assigns, - @view ] - end -end - -class CustomHandlerTest < Test::Unit::TestCase - def setup - ActionView::Base.register_template_handler "foo", CustomHandler - ActionView::Base.register_template_handler :foo2, CustomHandler - @view = ActionView::Base.new - end - - def test_custom_render - result = @view.render_template( "foo", "hello <%= one %>", nil, :one => "two" ) - assert_equal( - [ "hello <%= one %>", { :one => "two" }, @view ], - result ) - end - - def test_custom_render2 - result = @view.render_template( "foo2", "hello <%= one %>", nil, :one => "two" ) - assert_equal( - [ "hello <%= one %>", { :one => "two" }, @view ], - result ) - end - - def test_unhandled_extension - # uses the ERb handler by default if the extension isn't recognized - result = @view.render_template( "bar", "hello <%= one %>", nil, :one => "two" ) - assert_equal "hello two", result - end -end -class << Object; alias_method :const_available?, :const_defined?; end - -class ContentController < Class.new(ActionController::Base) -end -class NotAController -end -module Admin - class << self; alias_method :const_available?, :const_defined?; end - SomeConstant = 10 - class UserController < Class.new(ActionController::Base); end - class NewsFeedController < Class.new(ActionController::Base); end -end - -ActionController::Routing::Routes.draw do |map| - map.route_one 'route_one', :controller => 'elsewhere', :action => 'flash_me' - map.connect ':controller/:action/:id' -end -require File.dirname(__FILE__) + '/../abstract_unit' - -class FilterParamController < ActionController::Base -end - -class FilterParamTest < Test::Unit::TestCase - def setup - @controller = FilterParamController.new - end - - def test_filter_parameters - assert FilterParamController.respond_to?(:filter_parameter_logging) - assert !@controller.respond_to?(:filter_parameters) - - FilterParamController.filter_parameter_logging - assert @controller.respond_to?(:filter_parameters) - - test_hashes = [[{},{},[]], - [{'foo'=>'bar'},{'foo'=>'bar'},[]], - [{'foo'=>'bar'},{'foo'=>'bar'},%w'food'], - [{'foo'=>'bar'},{'foo'=>'[FILTERED]'},%w'foo'], - [{'foo'=>'bar', 'bar'=>'foo'},{'foo'=>'[FILTERED]', 'bar'=>'foo'},%w'foo baz'], - [{'foo'=>'bar', 'baz'=>'foo'},{'foo'=>'[FILTERED]', 'baz'=>'[FILTERED]'},%w'foo baz'], - [{'bar'=>{'foo'=>'bar','bar'=>'foo'}},{'bar'=>{'foo'=>'[FILTERED]','bar'=>'foo'}},%w'fo'], - [{'foo'=>{'foo'=>'bar','bar'=>'foo'}},{'foo'=>'[FILTERED]'},%w'f banana']] - - test_hashes.each do |before_filter, after_filter, filter_words| - FilterParamController.filter_parameter_logging(*filter_words) - assert_equal after_filter, @controller.filter_parameters(before_filter) - - filter_words.push('blah') - FilterParamController.filter_parameter_logging(*filter_words) do |key, value| - value.reverse! if key =~ /bargain/ - end - - before_filter['barg'] = {'bargain'=>'gain', 'blah'=>'bar', 'bar'=>{'bargain'=>{'blah'=>'foo'}}} - after_filter['barg'] = {'bargain'=>'niag', 'blah'=>'[FILTERED]', 'bar'=>{'bargain'=>{'blah'=>'[FILTERED]'}}} - - assert_equal after_filter, @controller.filter_parameters(before_filter) - end - end -end -require File.dirname(__FILE__) + '/../abstract_unit' - -class FilterTest < Test::Unit::TestCase - class TestController < ActionController::Base - before_filter :ensure_login - after_filter :clean_up - - def show - render :inline => "ran action" - end - - private - def ensure_login - @ran_filter ||= [] - @ran_filter << "ensure_login" - end - - def clean_up - @ran_after_filter ||= [] - @ran_after_filter << "clean_up" - end - end - - class RenderingController < ActionController::Base - before_filter :render_something_else - - def show - @ran_action = true - render :inline => "ran action" - end - - private - def render_something_else - render :inline => "something else" - end - end - - class ConditionalFilterController < ActionController::Base - def show - render :inline => "ran action" - end - - def another_action - render :inline => "ran action" - end - - def show_without_filter - render :inline => "ran action without filter" - end - - private - def ensure_login - @ran_filter ||= [] - @ran_filter << "ensure_login" - end - - def clean_up_tmp - @ran_filter ||= [] - @ran_filter << "clean_up_tmp" - end - - def rescue_action(e) raise(e) end - end - - class ConditionalCollectionFilterController < ConditionalFilterController - before_filter :ensure_login, :except => [ :show_without_filter, :another_action ] - end - - class OnlyConditionSymController < ConditionalFilterController - before_filter :ensure_login, :only => :show - end - - class ExceptConditionSymController < ConditionalFilterController - before_filter :ensure_login, :except => :show_without_filter - end - - class BeforeAndAfterConditionController < ConditionalFilterController - before_filter :ensure_login, :only => :show - after_filter :clean_up_tmp, :only => :show - end - - class OnlyConditionProcController < ConditionalFilterController - before_filter(:only => :show) {|c| c.assigns["ran_proc_filter"] = true } - end - - class ExceptConditionProcController < ConditionalFilterController - before_filter(:except => :show_without_filter) {|c| c.assigns["ran_proc_filter"] = true } - end - - class ConditionalClassFilter - def self.filter(controller) controller.assigns["ran_class_filter"] = true end - end - - class OnlyConditionClassController < ConditionalFilterController - before_filter ConditionalClassFilter, :only => :show - end - - class ExceptConditionClassController < ConditionalFilterController - before_filter ConditionalClassFilter, :except => :show_without_filter - end - - class AnomolousYetValidConditionController < ConditionalFilterController - before_filter(ConditionalClassFilter, :ensure_login, Proc.new {|c| c.assigns["ran_proc_filter1"] = true }, :except => :show_without_filter) { |c| c.assigns["ran_proc_filter2"] = true} - end - - class PrependingController < TestController - prepend_before_filter :wonderful_life - # skip_before_filter :fire_flash - - private - def wonderful_life - @ran_filter ||= [] - @ran_filter << "wonderful_life" - end - end - - class ConditionalSkippingController < TestController - skip_before_filter :ensure_login, :only => [ :login ] - skip_after_filter :clean_up, :only => [ :login ] - - before_filter :find_user, :only => [ :change_password ] - - def login - render :inline => "ran action" - end - - def change_password - render :inline => "ran action" - end - - protected - def find_user - @ran_filter ||= [] - @ran_filter << "find_user" - end - end - - class ConditionalParentOfConditionalSkippingController < ConditionalFilterController - before_filter :conditional_in_parent, :only => [:show, :another_action] - after_filter :conditional_in_parent, :only => [:show, :another_action] - - private - - def conditional_in_parent - @ran_filter ||= [] - @ran_filter << 'conditional_in_parent' - end - end - - class ChildOfConditionalParentController < ConditionalParentOfConditionalSkippingController - skip_before_filter :conditional_in_parent, :only => :another_action - skip_after_filter :conditional_in_parent, :only => :another_action - end - - class ProcController < PrependingController - before_filter(proc { |c| c.assigns["ran_proc_filter"] = true }) - end - - class ImplicitProcController < PrependingController - before_filter { |c| c.assigns["ran_proc_filter"] = true } - end - - class AuditFilter - def self.filter(controller) - controller.assigns["was_audited"] = true - end - end - - class AroundFilter - def before(controller) - @execution_log = "before" - controller.class.execution_log << " before aroundfilter " if controller.respond_to? :execution_log - controller.assigns["before_ran"] = true - end - - def after(controller) - controller.assigns["execution_log"] = @execution_log + " and after" - controller.assigns["after_ran"] = true - controller.class.execution_log << " after aroundfilter " if controller.respond_to? :execution_log - end - end - - class AppendedAroundFilter - def before(controller) - controller.class.execution_log << " before appended aroundfilter " - end - - def after(controller) - controller.class.execution_log << " after appended aroundfilter " - end - end - - class AuditController < ActionController::Base - before_filter(AuditFilter) - - def show - render_text "hello" - end - end - - class BadFilterController < ActionController::Base - before_filter 2 - - def show() "show" end - - protected - def rescue_action(e) raise(e) end - end - - class AroundFilterController < PrependingController - around_filter AroundFilter.new - end - - class MixedFilterController < PrependingController - cattr_accessor :execution_log - - def initialize - @@execution_log = "" - end - - before_filter { |c| c.class.execution_log << " before procfilter " } - prepend_around_filter AroundFilter.new - - after_filter { |c| c.class.execution_log << " after procfilter " } - append_around_filter AppendedAroundFilter.new - end - - class MixedSpecializationController < ActionController::Base - class OutOfOrder < StandardError; end - - before_filter :first - before_filter :second, :only => :foo - - def foo - render_text 'foo' - end - - def bar - render_text 'bar' - end - - protected - def first - @first = true - end - - def second - raise OutOfOrder unless @first - end - end - - class DynamicDispatchController < ActionController::Base - before_filter :choose - - %w(foo bar baz).each do |action| - define_method(action) { render :text => action } - end - - private - def choose - self.action_name = params[:choose] - end - end - - def test_added_filter_to_inheritance_graph - assert_equal [ :ensure_login ], TestController.before_filters - end - - def test_base_class_in_isolation - assert_equal [ ], ActionController::Base.before_filters - end - - def test_prepending_filter - assert_equal [ :wonderful_life, :ensure_login ], PrependingController.before_filters - end - - def test_running_filters - assert_equal %w( wonderful_life ensure_login ), test_process(PrependingController).template.assigns["ran_filter"] - end - - def test_running_filters_with_proc - assert test_process(ProcController).template.assigns["ran_proc_filter"] - end - - def test_running_filters_with_implicit_proc - assert test_process(ImplicitProcController).template.assigns["ran_proc_filter"] - end - - def test_running_filters_with_class - assert test_process(AuditController).template.assigns["was_audited"] - end - - def test_running_anomolous_yet_valid_condition_filters - response = test_process(AnomolousYetValidConditionController) - assert_equal %w( ensure_login ), response.template.assigns["ran_filter"] - assert response.template.assigns["ran_class_filter"] - assert response.template.assigns["ran_proc_filter1"] - assert response.template.assigns["ran_proc_filter2"] - - response = test_process(AnomolousYetValidConditionController, "show_without_filter") - assert_equal nil, response.template.assigns["ran_filter"] - assert !response.template.assigns["ran_class_filter"] - assert !response.template.assigns["ran_proc_filter1"] - assert !response.template.assigns["ran_proc_filter2"] - end - - def test_running_collection_condition_filters - assert_equal %w( ensure_login ), test_process(ConditionalCollectionFilterController).template.assigns["ran_filter"] - assert_equal nil, test_process(ConditionalCollectionFilterController, "show_without_filter").template.assigns["ran_filter"] - assert_equal nil, test_process(ConditionalCollectionFilterController, "another_action").template.assigns["ran_filter"] - end - - def test_running_only_condition_filters - assert_equal %w( ensure_login ), test_process(OnlyConditionSymController).template.assigns["ran_filter"] - assert_equal nil, test_process(OnlyConditionSymController, "show_without_filter").template.assigns["ran_filter"] - - assert test_process(OnlyConditionProcController).template.assigns["ran_proc_filter"] - assert !test_process(OnlyConditionProcController, "show_without_filter").template.assigns["ran_proc_filter"] - - assert test_process(OnlyConditionClassController).template.assigns["ran_class_filter"] - assert !test_process(OnlyConditionClassController, "show_without_filter").template.assigns["ran_class_filter"] - end - - def test_running_except_condition_filters - assert_equal %w( ensure_login ), test_process(ExceptConditionSymController).template.assigns["ran_filter"] - assert_equal nil, test_process(ExceptConditionSymController, "show_without_filter").template.assigns["ran_filter"] - - assert test_process(ExceptConditionProcController).template.assigns["ran_proc_filter"] - assert !test_process(ExceptConditionProcController, "show_without_filter").template.assigns["ran_proc_filter"] - - assert test_process(ExceptConditionClassController).template.assigns["ran_class_filter"] - assert !test_process(ExceptConditionClassController, "show_without_filter").template.assigns["ran_class_filter"] - end - - def test_running_before_and_after_condition_filters - assert_equal %w( ensure_login clean_up_tmp), test_process(BeforeAndAfterConditionController).template.assigns["ran_filter"] - assert_equal nil, test_process(BeforeAndAfterConditionController, "show_without_filter").template.assigns["ran_filter"] - end - - def test_bad_filter - assert_raises(ActionController::ActionControllerError) { - test_process(BadFilterController) - } - end - - def test_around_filter - controller = test_process(AroundFilterController) - assert controller.template.assigns["before_ran"] - assert controller.template.assigns["after_ran"] - end - - def test_having_properties_in_around_filter - controller = test_process(AroundFilterController) - assert_equal "before and after", controller.template.assigns["execution_log"] - end - - def test_prepending_and_appending_around_filter - controller = test_process(MixedFilterController) - assert_equal " before aroundfilter before procfilter before appended aroundfilter " + - " after appended aroundfilter after aroundfilter after procfilter ", - MixedFilterController.execution_log - end - - def test_rendering_breaks_filtering_chain - response = test_process(RenderingController) - assert_equal "something else", response.body - assert !response.template.assigns["ran_action"] - end - - def test_filters_with_mixed_specialization_run_in_order - assert_nothing_raised do - response = test_process(MixedSpecializationController, 'bar') - assert_equal 'bar', response.body - end - - assert_nothing_raised do - response = test_process(MixedSpecializationController, 'foo') - assert_equal 'foo', response.body - end - end - - def test_dynamic_dispatch - %w(foo bar baz).each do |action| - request = ActionController::TestRequest.new - request.query_parameters[:choose] = action - response = DynamicDispatchController.process(request, ActionController::TestResponse.new) - assert_equal action, response.body - end - end - - def test_conditional_skipping_of_filters - assert_nil test_process(ConditionalSkippingController, "login").template.assigns["ran_filter"] - assert_equal %w( ensure_login find_user ), test_process(ConditionalSkippingController, "change_password").template.assigns["ran_filter"] - - assert_nil test_process(ConditionalSkippingController, "login").template.controller.instance_variable_get("@ran_after_filter") - assert_equal %w( clean_up ), test_process(ConditionalSkippingController, "change_password").template.controller.instance_variable_get("@ran_after_filter") - end - - def test_conditional_skipping_of_filters_when_parent_filter_is_also_conditional - assert_equal %w( conditional_in_parent conditional_in_parent ), test_process(ChildOfConditionalParentController).template.assigns['ran_filter'] - assert_nil test_process(ChildOfConditionalParentController, 'another_action').template.assigns['ran_filter'] - end - - private - def test_process(controller, action = "show") - request = ActionController::TestRequest.new - request.action = action - controller.process(request, ActionController::TestResponse.new) - end -end -require File.dirname(__FILE__) + '/../abstract_unit' - -class FlashTest < Test::Unit::TestCase - class TestController < ActionController::Base - def set_flash - flash["that"] = "hello" - render :inline => "hello" - end - - def set_flash_now - flash.now["that"] = "hello" - flash.now["foo"] ||= "bar" - flash.now["foo"] ||= "err" - @flashy = flash.now["that"] - @flash_copy = {}.update flash - render :inline => "hello" - end - - def attempt_to_use_flash_now - @flash_copy = {}.update flash - @flashy = flash["that"] - render :inline => "hello" - end - - def use_flash - @flash_copy = {}.update flash - @flashy = flash["that"] - render :inline => "hello" - end - - def use_flash_and_keep_it - @flash_copy = {}.update flash - @flashy = flash["that"] - silence_warnings { keep_flash } - render :inline => "hello" - end - - def rescue_action(e) - raise unless ActionController::MissingTemplate === e - end - end - - def setup - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - @controller = TestController.new - end - - def test_flash - get :set_flash - - get :use_flash - assert_equal "hello", @response.template.assigns["flash_copy"]["that"] - assert_equal "hello", @response.template.assigns["flashy"] - - get :use_flash - assert_nil @response.template.assigns["flash_copy"]["that"], "On second flash" - end - - def test_keep_flash - get :set_flash - - get :use_flash_and_keep_it - assert_equal "hello", @response.template.assigns["flash_copy"]["that"] - assert_equal "hello", @response.template.assigns["flashy"] - - get :use_flash - assert_equal "hello", @response.template.assigns["flash_copy"]["that"], "On second flash" - - get :use_flash - assert_nil @response.template.assigns["flash_copy"]["that"], "On third flash" - end - - def test_flash_now - get :set_flash_now - assert_equal "hello", @response.template.assigns["flash_copy"]["that"] - assert_equal "bar" , @response.template.assigns["flash_copy"]["foo"] - assert_equal "hello", @response.template.assigns["flashy"] - - get :attempt_to_use_flash_now - assert_nil @response.template.assigns["flash_copy"]["that"] - assert_nil @response.template.assigns["flash_copy"]["foo"] - assert_nil @response.template.assigns["flashy"] - end -endrequire File.dirname(__FILE__) + '/../abstract_unit' - -MemCache = Struct.new(:MemCache, :address) unless Object.const_defined?(:MemCache) - -class FragmentCacheStoreSettingTest < Test::Unit::TestCase - def teardown - ActionController::Base.fragment_cache_store = ActionController::Caching::Fragments::MemoryStore.new - end - - def test_file_fragment_cache_store - ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory" - assert_kind_of( - ActionController::Caching::Fragments::FileStore, - ActionController::Base.fragment_cache_store - ) - assert_equal "/path/to/cache/directory", ActionController::Base.fragment_cache_store.cache_path - end - - def test_drb_fragment_cache_store - ActionController::Base.fragment_cache_store = :drb_store, "druby://localhost:9192" - assert_kind_of( - ActionController::Caching::Fragments::DRbStore, - ActionController::Base.fragment_cache_store - ) - assert_equal "druby://localhost:9192", ActionController::Base.fragment_cache_store.address - end - - def test_mem_cache_fragment_cache_store - ActionController::Base.fragment_cache_store = :mem_cache_store, "localhost" - assert_kind_of( - ActionController::Caching::Fragments::MemCacheStore, - ActionController::Base.fragment_cache_store - ) - assert_equal %w(localhost), ActionController::Base.fragment_cache_store.addresses - end - - def test_object_assigned_fragment_cache_store - ActionController::Base.fragment_cache_store = ActionController::Caching::Fragments::FileStore.new("/path/to/cache/directory") - assert_kind_of( - ActionController::Caching::Fragments::FileStore, - ActionController::Base.fragment_cache_store - ) - assert_equal "/path/to/cache/directory", ActionController::Base.fragment_cache_store.cache_path - end -end -require File.dirname(__FILE__) + '/../abstract_unit' - -class TestController < ActionController::Base - attr_accessor :delegate_attr - def delegate_method() end - def rescue_action(e) raise end -end - -module Fun - class GamesController < ActionController::Base - def render_hello_world - render :inline => "hello: <%= stratego %>" - end - - def rescue_action(e) raise end - end - - class PDFController < ActionController::Base - def test - render :inline => "test: <%= foobar %>" - end - - def rescue_action(e) raise end - end -end - -module LocalAbcHelper - def a() end - def b() end - def c() end -end - -class HelperTest < Test::Unit::TestCase - def setup - # Increment symbol counter. - @symbol = (@@counter ||= 'A0').succ!.dup - - # Generate new controller class. - controller_class_name = "Helper#{@symbol}Controller" - eval("class #{controller_class_name} < TestController; end") - @controller_class = self.class.const_get(controller_class_name) - - # Generate new template class and assign to controller. - template_class_name = "Test#{@symbol}View" - eval("class #{template_class_name} < ActionView::Base; end") - @template_class = self.class.const_get(template_class_name) - @controller_class.template_class = @template_class - - # Set default test helper. - self.test_helper = LocalAbcHelper - end - - def teardown - # Reset template class. - #ActionController::Base.template_class = ActionView::Base - end - - - def test_deprecated_helper - assert_equal expected_helper_methods, missing_methods - assert_nothing_raised { @controller_class.helper TestHelper } - assert_equal [], missing_methods - end - - def test_declare_helper - require 'abc_helper' - self.test_helper = AbcHelper - assert_equal expected_helper_methods, missing_methods - assert_nothing_raised { @controller_class.helper :abc } - assert_equal [], missing_methods - end - - def test_declare_missing_helper - assert_equal expected_helper_methods, missing_methods - assert_raise(MissingSourceFile) { @controller_class.helper :missing } - end - - def test_declare_missing_file_from_helper - require 'broken_helper' - rescue LoadError => e - assert_nil /\bbroken_helper\b/.match(e.to_s)[1] - end - - def test_helper_block - assert_nothing_raised { - @controller_class.helper { def block_helper_method; end } - } - assert master_helper_methods.include?('block_helper_method') - end - - def test_helper_block_include - assert_equal expected_helper_methods, missing_methods - assert_nothing_raised { - @controller_class.helper { include TestHelper } - } - assert [], missing_methods - end - - def test_helper_method - assert_nothing_raised { @controller_class.helper_method :delegate_method } - assert master_helper_methods.include?('delegate_method') - end - - def test_helper_attr - assert_nothing_raised { @controller_class.helper_attr :delegate_attr } - assert master_helper_methods.include?('delegate_attr') - assert master_helper_methods.include?('delegate_attr=') - end - - def test_helper_for_nested_controller - request = ActionController::TestRequest.new - response = ActionController::TestResponse.new - request.action = 'render_hello_world' - - assert_equal 'hello: Iz guuut!', Fun::GamesController.process(request, response).body - end - - def test_helper_for_acronym_controller - request = ActionController::TestRequest.new - response = ActionController::TestResponse.new - request.action = 'test' - - assert_equal 'test: baz', Fun::PDFController.process(request, response).body - end - - private - def expected_helper_methods - TestHelper.instance_methods - end - - def master_helper_methods - @controller_class.master_helper_module.instance_methods - end - - def missing_methods - expected_helper_methods - master_helper_methods - end - - def test_helper=(helper_module) - silence_warnings { self.class.const_set('TestHelper', helper_module) } - end -end - - -class IsolatedHelpersTest < Test::Unit::TestCase - class A < ActionController::Base - def index - render :inline => '<%= shout %>' - end - - def rescue_action(e) raise end - end - - class B < A - helper { def shout; 'B' end } - - def index - render :inline => '<%= shout %>' - end - end - - class C < A - helper { def shout; 'C' end } - - def index - render :inline => '<%= shout %>' - end - end - - def setup - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - @request.action = 'index' - end - - def test_helper_in_a - assert_raise(NameError) { A.process(@request, @response) } - end - - def test_helper_in_b - assert_equal 'B', B.process(@request, @response).body - end - - def test_helper_in_c - assert_equal 'C', C.process(@request, @response).body - end -end -require File.dirname(__FILE__) + '/../abstract_unit' - -# The template_root must be set on Base and not LayoutTest so that LayoutTest's inherited method has access to -# the template_root when looking for a layout -ActionController::Base.template_root = File.dirname(__FILE__) + '/../fixtures/layout_tests/' - -class LayoutTest < ActionController::Base - def self.controller_path; 'views' end -end - -# Restore template root to be unset -ActionController::Base.template_root = nil - -class ProductController < LayoutTest -end - -class ItemController < LayoutTest -end - -class ThirdPartyTemplateLibraryController < LayoutTest -end - -module ControllerNameSpace -end - -class ControllerNameSpace::NestedController < LayoutTest -end - -class MabView - def initialize(view) - end - - def render(text, locals = {}) - text - end -end - -ActionView::Base::register_template_handler :mab, MabView - -class LayoutAutoDiscoveryTest < Test::Unit::TestCase - def setup - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - - @request.host = "www.nextangle.com" - end - - def test_application_layout_is_default_when_no_controller_match - @controller = ProductController.new - get :hello - assert_equal 'layout_test.rhtml hello.rhtml', @response.body - end - - def test_controller_name_layout_name_match - @controller = ItemController.new - get :hello - assert_equal 'item.rhtml hello.rhtml', @response.body - end - - def test_third_party_template_library_auto_discovers_layout - @controller = ThirdPartyTemplateLibraryController.new - get :hello - assert_equal 'layouts/third_party_template_library', @controller.active_layout - assert_equal 'Mab', @response.body - end - - def test_namespaced_controllers_auto_detect_layouts - @controller = ControllerNameSpace::NestedController.new - get :hello - assert_equal 'layouts/controller_name_space/nested', @controller.active_layout - assert_equal 'controller_name_space/nested.rhtml hello.rhtml', @response.body - end -endrequire File.dirname(__FILE__) + '/../abstract_unit' - -class RespondToController < ActionController::Base - layout :set_layout - - def html_xml_or_rss - respond_to do |type| - type.html { render :text => "HTML" } - type.xml { render :text => "XML" } - type.rss { render :text => "RSS" } - type.all { render :text => "Nothing" } - end - end - - def js_or_html - respond_to do |type| - type.html { render :text => "HTML" } - type.js { render :text => "JS" } - type.all { render :text => "Nothing" } - end - end - - def html_or_xml - respond_to do |type| - type.html { render :text => "HTML" } - type.xml { render :text => "XML" } - type.all { render :text => "Nothing" } - end - end - - def just_xml - respond_to do |type| - type.xml { render :text => "XML" } - end - end - - def using_defaults - respond_to do |type| - type.html - type.js - type.xml - end - end - - def using_defaults_with_type_list - respond_to(:html, :js, :xml) - end - - def made_for_content_type - respond_to do |type| - type.rss { render :text => "RSS" } - type.atom { render :text => "ATOM" } - type.all { render :text => "Nothing" } - end - end - - def custom_type_handling - respond_to do |type| - type.html { render :text => "HTML" } - type.custom("application/crazy-xml") { render :text => "Crazy XML" } - type.all { render :text => "Nothing" } - end - end - - def handle_any - respond_to do |type| - type.html { render :text => "HTML" } - type.any(:js, :xml) { render :text => "Either JS or XML" } - end - end - - def all_types_with_layout - respond_to do |type| - type.html - type.js - end - end - - def rescue_action(e) - raise - end - - protected - def set_layout - if action_name == "all_types_with_layout" - "standard" - end - end -end - -RespondToController.template_root = File.dirname(__FILE__) + "/../fixtures/" - -class MimeControllerTest < Test::Unit::TestCase - def setup - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - - @controller = RespondToController.new - @request.host = "www.example.com" - end - - def test_html - @request.env["HTTP_ACCEPT"] = "text/html" - get :js_or_html - assert_equal 'HTML', @response.body - - get :html_or_xml - assert_equal 'HTML', @response.body - - get :just_xml - assert_response 406 - end - - def test_all - @request.env["HTTP_ACCEPT"] = "*/*" - get :js_or_html - assert_equal 'HTML', @response.body # js is not part of all - - get :html_or_xml - assert_equal 'HTML', @response.body - - get :just_xml - assert_equal 'XML', @response.body - end - - def test_xml - @request.env["HTTP_ACCEPT"] = "application/xml" - get :html_xml_or_rss - assert_equal 'XML', @response.body - end - - def test_js_or_html - @request.env["HTTP_ACCEPT"] = "text/javascript, text/html" - get :js_or_html - assert_equal 'JS', @response.body - - get :html_or_xml - assert_equal 'HTML', @response.body - - get :just_xml - assert_response 406 - end - - def test_js_or_anything - @request.env["HTTP_ACCEPT"] = "text/javascript, */*" - get :js_or_html - assert_equal 'JS', @response.body - - get :html_or_xml - assert_equal 'HTML', @response.body - - get :just_xml - assert_equal 'XML', @response.body - end - - def test_using_defaults - @request.env["HTTP_ACCEPT"] = "*/*" - get :using_defaults - assert_equal 'Hello world!', @response.body - - @request.env["HTTP_ACCEPT"] = "text/javascript" - get :using_defaults - assert_equal '$("body").visualEffect("highlight");', @response.body - - @request.env["HTTP_ACCEPT"] = "application/xml" - get :using_defaults - assert_equal "

    Hello world!

    \n", @response.body - end - - def test_using_defaults_with_type_list - @request.env["HTTP_ACCEPT"] = "*/*" - get :using_defaults_with_type_list - assert_equal 'Hello world!', @response.body - - @request.env["HTTP_ACCEPT"] = "text/javascript" - get :using_defaults_with_type_list - assert_equal '$("body").visualEffect("highlight");', @response.body - - @request.env["HTTP_ACCEPT"] = "application/xml" - get :using_defaults_with_type_list - assert_equal "

    Hello world!

    \n", @response.body - end - - def test_with_content_type - @request.env["CONTENT_TYPE"] = "application/atom+xml" - get :made_for_content_type - assert_equal "ATOM", @response.body - - @request.env["CONTENT_TYPE"] = "application/rss+xml" - get :made_for_content_type - assert_equal "RSS", @response.body - end - - def test_synonyms - @request.env["HTTP_ACCEPT"] = "application/javascript" - get :js_or_html - assert_equal 'JS', @response.body - - @request.env["HTTP_ACCEPT"] = "application/x-xml" - get :html_xml_or_rss - assert_equal "XML", @response.body - end - - def test_custom_types - @request.env["HTTP_ACCEPT"] = "application/crazy-xml" - get :custom_type_handling - assert_equal 'Crazy XML', @response.body - - @request.env["HTTP_ACCEPT"] = "text/html" - get :custom_type_handling - assert_equal 'HTML', @response.body - end - - def test_xhtml_alias - @request.env["HTTP_ACCEPT"] = "application/xhtml+xml,application/xml" - get :html_or_xml - assert_equal 'HTML', @response.body - end - - def test_firefox_simulation - @request.env["HTTP_ACCEPT"] = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5" - get :html_or_xml - assert_equal 'HTML', @response.body - end - - def test_handle_any - @request.env["HTTP_ACCEPT"] = "*/*" - get :handle_any - assert_equal 'HTML', @response.body - - @request.env["HTTP_ACCEPT"] = "text/javascript" - get :handle_any - assert_equal 'Either JS or XML', @response.body - - @request.env["HTTP_ACCEPT"] = "text/xml" - get :handle_any - assert_equal 'Either JS or XML', @response.body - end - - def test_all_types_with_layout - @request.env["HTTP_ACCEPT"] = "text/javascript" - get :all_types_with_layout - assert_equal 'RJS for all_types_with_layout', @response.body - - @request.env["HTTP_ACCEPT"] = "text/html" - get :all_types_with_layout - assert_equal 'HTML for all_types_with_layout', @response.body - end - - def test_xhr - xhr :get, :js_or_html - assert_equal 'JS', @response.body - - xhr :get, :using_defaults - assert_equal '$("body").visualEffect("highlight");', @response.body - end -end -require File.dirname(__FILE__) + '/../abstract_unit' - -class MimeTypeTest < Test::Unit::TestCase - Mime::PNG = Mime::Type.new("image/png") - Mime::PLAIN = Mime::Type.new("text/plain") - - def test_parse_single - Mime::LOOKUP.keys.each do |mime_type| - assert_equal [Mime::Type.lookup(mime_type)], Mime::Type.parse(mime_type) - end - end - - def test_parse_without_q - accept = "text/xml,application/xhtml+xml,text/yaml,application/xml,text/html,image/png,text/plain,*/*" - expect = [Mime::HTML, Mime::XML, Mime::YAML, Mime::PNG, Mime::PLAIN, Mime::ALL] - assert_equal expect, Mime::Type.parse(accept) - end - - def test_parse_with_q - accept = "text/xml,application/xhtml+xml,text/yaml; q=0.3,application/xml,text/html; q=0.8,image/png,text/plain; q=0.5,*/*; q=0.2" - expect = [Mime::HTML, Mime::XML, Mime::PNG, Mime::PLAIN, Mime::YAML, Mime::ALL] - assert_equal expect, Mime::Type.parse(accept) - end -endrequire File.dirname(__FILE__) + '/../abstract_unit' - -silence_warnings { Customer = Struct.new("Customer", :name) } - -module Fun - class GamesController < ActionController::Base - def hello_world - end - end -end - -module NewRenderTestHelper - def rjs_helper_method_from_module - page.visual_effect :highlight - end -end - -class NewRenderTestController < ActionController::Base - layout :determine_layout - - def self.controller_name; "test"; end - def self.controller_path; "test"; end - - def hello_world - end - - def render_hello_world - render :template => "test/hello_world" - end - - def render_hello_world_from_variable - @person = "david" - render :text => "hello #{@person}" - end - - def render_action_hello_world - render :action => "hello_world" - end - - def render_action_hello_world_as_symbol - render :action => :hello_world - end - - def render_text_hello_world - render :text => "hello world" - end - - def render_text_hello_world_with_layout - @variable_for_layout = ", I'm here!" - render :text => "hello world", :layout => true - end - - def hello_world_with_layout_false - render :layout => false - end - - def render_custom_code - render :text => "hello world", :status => "404 Moved" - end - - def render_file_with_instance_variables - @secret = 'in the sauce' - path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.rhtml') - render :file => path - end - - def render_file_with_locals - path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_locals.rhtml') - render :file => path, :locals => {:secret => 'in the sauce'} - end - - def render_file_not_using_full_path - @secret = 'in the sauce' - render :file => 'test/render_file_with_ivar', :use_full_path => true - end - - def render_file_not_using_full_path_with_relative_path - @secret = 'in the sauce' - render :file => 'test/../test/render_file_with_ivar', :use_full_path => true - end - - def render_file_not_using_full_path_with_dot_in_path - @secret = 'in the sauce' - render :file => 'test/dot.directory/render_file_with_ivar', :use_full_path => true - end - - def render_xml_hello - @name = "David" - render :template => "test/hello" - end - - def greeting - # let's just rely on the template - end - - def layout_test - render :action => "hello_world" - end - - def layout_test_with_different_layout - render :action => "hello_world", :layout => "standard" - end - - def rendering_without_layout - render :action => "hello_world", :layout => false - end - - def layout_overriding_layout - render :action => "hello_world", :layout => "standard" - end - - def rendering_nothing_on_layout - render :nothing => true - end - - def builder_layout_test - render :action => "hello" - end - - def partials_list - @test_unchanged = 'hello' - @customers = [ Customer.new("david"), Customer.new("mary") ] - render :action => "list" - end - - def partial_only - render :partial => true - end - - def partial_only_with_layout - render :partial => "partial_only", :layout => true - end - - def partial_with_locals - render :partial => "customer", :locals => { :customer => Customer.new("david") } - end - - def partial_collection - render :partial => "customer", :collection => [ Customer.new("david"), Customer.new("mary") ] - end - - def partial_collection_with_locals - render :partial => "customer_greeting", :collection => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" } - end - - def empty_partial_collection - render :partial => "customer", :collection => [] - end - - def partial_with_hash_object - render :partial => "hash_object", :object => {:first_name => "Sam"} - end - - def partial_with_implicit_local_assignment - @customer = Customer.new("Marcel") - render :partial => "customer" - end - - def hello_in_a_string - @customers = [ Customer.new("david"), Customer.new("mary") ] - render :text => "How's there? #{render_to_string("test/list")}" - end - - def accessing_params_in_template - render :inline => "Hello: <%= params[:name] %>" - end - - def accessing_params_in_template_with_layout - render :layout => nil, :inline => "Hello: <%= params[:name] %>" - end - - def render_with_explicit_template - render "test/hello_world" - end - - def double_render - render :text => "hello" - render :text => "world" - end - - def double_redirect - redirect_to :action => "double_render" - redirect_to :action => "double_render" - end - - def render_and_redirect - render :text => "hello" - redirect_to :action => "double_render" - end - - def rendering_with_conflicting_local_vars - @name = "David" - def @template.name() nil end - render :action => "potential_conflicts" - end - - def hello_world_from_rxml_using_action - render :action => "hello_world.rxml" - end - - def hello_world_from_rxml_using_template - render :template => "test/hello_world.rxml" - end - - helper NewRenderTestHelper - helper do - def rjs_helper_method(value) - page.visual_effect :highlight, value - end - end - - def enum_rjs_test - render :update do |page| - page.select('.product').each do |value| - page.rjs_helper_method_from_module - page.rjs_helper_method(value) - page.sortable(value, :url => { :action => "order" }) - page.draggable(value) - end - end - end - - def delete_with_js - @project_id = 4 - end - - def render_js_with_explicit_template - @project_id = 4 - render :template => 'test/delete_with_js' - end - - def render_js_with_explicit_action_template - @project_id = 4 - render :action => 'delete_with_js' - end - - def update_page - render :update do |page| - page.replace_html 'balance', '$37,000,000.00' - page.visual_effect :highlight, 'balance' - end - end - - def update_page_with_instance_variables - @money = '$37,000,000.00' - @div_id = 'balance' - render :update do |page| - page.replace_html @div_id, @money - page.visual_effect :highlight, @div_id - end - end - - def action_talk_to_layout - # Action template sets variable that's picked up by layout - end - - def render_text_with_assigns - @hello = "world" - render :text => "foo" - end - - def yield_content_for - render :action => "content_for", :layout => "yield" - end - - def rescue_action(e) raise end - - private - def determine_layout - case action_name - when "hello_world", "layout_test", "rendering_without_layout", - "rendering_nothing_on_layout", "render_text_hello_world", - "render_text_hello_world_with_layout", - "hello_world_with_layout_false", - "partial_only", "partial_only_with_layout", - "accessing_params_in_template", - "accessing_params_in_template_with_layout", - "render_with_explicit_template", - "render_js_with_explicit_template", - "render_js_with_explicit_action_template", - "delete_with_js", "update_page", "update_page_with_instance_variables" - - "layouts/standard" - when "builder_layout_test" - "layouts/builder" - when "action_talk_to_layout", "layout_overriding_layout" - "layouts/talk_from_action" - end - end -end - -NewRenderTestController.template_root = File.dirname(__FILE__) + "/../fixtures/" -Fun::GamesController.template_root = File.dirname(__FILE__) + "/../fixtures/" - -class NewRenderTest < Test::Unit::TestCase - def setup - @controller = NewRenderTestController.new - - # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get - # a more accurate simulation of what happens in "real life". - @controller.logger = Logger.new(nil) - - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - - @request.host = "www.nextangle.com" - end - - def test_simple_show - get :hello_world - assert_response :success - assert_template "test/hello_world" - assert_equal "Hello world!", @response.body - end - - def test_do_with_render - get :render_hello_world - assert_template "test/hello_world" - end - - def test_do_with_render_from_variable - get :render_hello_world_from_variable - assert_equal "hello david", @response.body - end - - def test_do_with_render_action - get :render_action_hello_world - assert_template "test/hello_world" - end - - def test_do_with_render_action_as_symbol - get :render_action_hello_world_as_symbol - assert_template "test/hello_world" - end - - def test_do_with_render_text - get :render_text_hello_world - assert_equal "hello world", @response.body - end - - def test_do_with_render_text_and_layout - get :render_text_hello_world_with_layout - assert_equal "hello world, I'm here!", @response.body - end - - def test_do_with_render_action_and_layout_false - get :hello_world_with_layout_false - assert_equal 'Hello world!', @response.body - end - - def test_do_with_render_custom_code - get :render_custom_code - assert_response :missing - end - - def test_render_file_with_instance_variables - get :render_file_with_instance_variables - assert_equal "The secret is in the sauce\n", @response.body - end - - def test_render_file_not_using_full_path - get :render_file_not_using_full_path - assert_equal "The secret is in the sauce\n", @response.body - end - - def test_render_file_not_using_full_path_with_relative_path - get :render_file_not_using_full_path_with_relative_path - assert_equal "The secret is in the sauce\n", @response.body - end - - def test_render_file_not_using_full_path_with_dot_in_path - get :render_file_not_using_full_path_with_dot_in_path - assert_equal "The secret is in the sauce\n", @response.body - end - - def test_render_file_with_locals - get :render_file_with_locals - assert_equal "The secret is in the sauce\n", @response.body - end - - def test_attempt_to_access_object_method - assert_raises(ActionController::UnknownAction, "No action responded to [clone]") { get :clone } - end - - def test_private_methods - assert_raises(ActionController::UnknownAction, "No action responded to [determine_layout]") { get :determine_layout } - end - - def test_access_to_request_in_view - view_internals_old_value = ActionController::Base.view_controller_internals - - ActionController::Base.view_controller_internals = false - ActionController::Base.protected_variables_cache = nil - - get :hello_world - assert_nil(assigns["request"]) - - ActionController::Base.view_controller_internals = true - ActionController::Base.protected_variables_cache = nil - - get :hello_world - assert_kind_of ActionController::AbstractRequest, assigns["request"] - - ActionController::Base.view_controller_internals = view_internals_old_value - ActionController::Base.protected_variables_cache = nil - end - - def test_render_xml - get :render_xml_hello - assert_equal "\n

    Hello David

    \n

    This is grand!

    \n\n", @response.body - end - - def test_enum_rjs_test - get :enum_rjs_test - assert_equal <<-EOS.strip, @response.body -$$(".product").each(function(value, index) { -new Effect.Highlight(element,{}); -new Effect.Highlight(value,{}); -Sortable.create(value, {onUpdate:function(){new Ajax.Request('/test/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(value)})}}); -new Draggable(value, {}); -}); -EOS - end - - def test_render_xml_with_default - get :greeting - assert_equal "

    This is grand!

    \n", @response.body - end - - def test_render_rjs_with_default - get :delete_with_js - assert_equal %!["person"].each(Element.remove);\nnew Effect.Highlight(\"project-4\",{});!, @response.body - end - - def test_render_rjs_template_explicitly - get :render_js_with_explicit_template - assert_equal %!["person"].each(Element.remove);\nnew Effect.Highlight(\"project-4\",{});!, @response.body - end - - def test_rendering_rjs_action_explicitly - get :render_js_with_explicit_action_template - assert_equal %!["person"].each(Element.remove);\nnew Effect.Highlight(\"project-4\",{});!, @response.body - end - - def test_layout_rendering - get :layout_test - assert_equal "Hello world!", @response.body - end - - def test_layout_test_with_different_layout - get :layout_test_with_different_layout - assert_equal "Hello world!", @response.body - end - - def test_rendering_without_layout - get :rendering_without_layout - assert_equal "Hello world!", @response.body - end - - def test_layout_overriding_layout - get :layout_overriding_layout - assert_no_match %r{}, @response.body - end - - def test_rendering_nothing_on_layout - get :rendering_nothing_on_layout - assert_equal " ", @response.body - end - - def test_render_xml_with_layouts - get :builder_layout_test - assert_equal "<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", @response.body - end - - def test_partial_only - get :partial_only - assert_equal "only partial", @response.body - end - - def test_partial_only_with_layout - get :partial_only_with_layout - assert_equal "<html>only partial</html>", @response.body - end - - def test_render_to_string - get :hello_in_a_string - assert_equal "How's there? goodbyeHello: davidHello: marygoodbye\n", @response.body - end - - def test_nested_rendering - get :hello_world - assert_equal "Living in a nested world", Fun::GamesController.process(@request, @response).body - end - - def test_accessing_params_in_template - get :accessing_params_in_template, :name => "David" - assert_equal "Hello: David", @response.body - end - - def test_accessing_params_in_template_with_layout - get :accessing_params_in_template_with_layout, :name => "David" - assert_equal "<html>Hello: David</html>", @response.body - end - - def test_render_with_explicit_template - get :render_with_explicit_template - assert_response :success - end - - def test_double_render - assert_raises(ActionController::DoubleRenderError) { get :double_render } - end - - def test_double_redirect - assert_raises(ActionController::DoubleRenderError) { get :double_redirect } - end - - def test_render_and_redirect - assert_raises(ActionController::DoubleRenderError) { get :render_and_redirect } - end - - def test_rendering_with_conflicting_local_vars - get :rendering_with_conflicting_local_vars - assert_equal("First: David\nSecond: Stephan\nThird: David\nFourth: David\nFifth: ", @response.body) - end - - def test_action_talk_to_layout - get :action_talk_to_layout - assert_equal "<title>Talking to the layout\nAction was here!", @response.body - end - - def test_partials_list - get :partials_list - assert_equal "goodbyeHello: davidHello: marygoodbye\n", @response.body - end - - def test_partial_with_locals - get :partial_with_locals - assert_equal "Hello: david", @response.body - end - - def test_partial_collection - get :partial_collection - assert_equal "Hello: davidHello: mary", @response.body - end - - def test_partial_collection_with_locals - get :partial_collection_with_locals - assert_equal "Bonjour: davidBonjour: mary", @response.body - end - - def test_empty_partial_collection - get :empty_partial_collection - assert_equal " ", @response.body - end - - def test_partial_with_hash_object - get :partial_with_hash_object - assert_equal "Sam", @response.body - end - - def test_partial_with_implicit_local_assignment - get :partial_with_implicit_local_assignment - assert_equal "Hello: Marcel", @response.body - end - - def test_render_text_with_assigns - get :render_text_with_assigns - assert_equal "world", assigns["hello"] - end - - def test_update_page - get :update_page - assert_template nil - assert_equal 'text/javascript; charset=UTF-8', @response.headers['Content-Type'] - assert_equal 2, @response.body.split($/).length - end - - def test_update_page_with_instance_variables - get :update_page_with_instance_variables - assert_template nil - assert_equal 'text/javascript; charset=UTF-8', @response.headers['Content-Type'] - assert_match /balance/, @response.body - assert_match /\$37/, @response.body - end - - def test_yield_content_for - get :yield_content_for - assert_equal "Putting stuff in the title!\n\nGreat stuff!\n", @response.body - end - - - def test_overwritting_rendering_relative_file_with_extension - get :hello_world_from_rxml_using_template - assert_equal "\n

    Hello

    \n\n", @response.body - - get :hello_world_from_rxml_using_action - assert_equal "\n

    Hello

    \n\n", @response.body - end -end -require 'test/unit' -require 'cgi' -require 'stringio' -require File.dirname(__FILE__) + '/../../lib/action_controller/cgi_ext/raw_post_data_fix' - -class RawPostDataTest < Test::Unit::TestCase - def setup - ENV['REQUEST_METHOD'] = 'POST' - ENV['CONTENT_TYPE'] = '' - ENV['CONTENT_LENGTH'] = '0' - end - - def test_raw_post_data - process_raw "action=create_customer&full_name=David%20Heinemeier%20Hansson&customerId=1" - end - - private - def process_raw(query_string) - old_stdin = $stdin - begin - $stdin = StringIO.new(query_string.dup) - ENV['CONTENT_LENGTH'] = $stdin.size.to_s - CGI.new - assert_not_nil ENV['RAW_POST_DATA'] - assert ENV['RAW_POST_DATA'].frozen? - assert_equal query_string, ENV['RAW_POST_DATA'] - ensure - $stdin = old_stdin - end - end -end -require File.dirname(__FILE__) + '/../abstract_unit' - -class RedirectController < ActionController::Base - def simple_redirect - redirect_to :action => "hello_world" - end - - def method_redirect - redirect_to :dashbord_url, 1, "hello" - end - - def host_redirect - redirect_to :action => "other_host", :only_path => false, :host => 'other.test.host' - end - - def module_redirect - redirect_to :controller => 'module_test/module_redirect', :action => "hello_world" - end - - def redirect_with_assigns - @hello = "world" - redirect_to :action => "hello_world" - end - - def redirect_to_back - redirect_to :back - end - - def rescue_errors(e) raise e end - - def rescue_action(e) raise end - - protected - def dashbord_url(id, message) - url_for :action => "dashboard", :params => { "id" => id, "message" => message } - end -end - -class RedirectTest < Test::Unit::TestCase - def setup - @controller = RedirectController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end - - def test_simple_redirect - get :simple_redirect - assert_redirect_url "http://test.host/redirect/hello_world" - end - - def test_redirect_with_method_reference_and_parameters - get :method_redirect - assert_redirect_url "http://test.host/redirect/dashboard/1?message=hello" - end - - def test_simple_redirect_using_options - get :host_redirect - assert_redirected_to :action => "other_host", :only_path => false, :host => 'other.test.host' - end - - def test_redirect_error_with_pretty_diff - get :host_redirect - begin - assert_redirected_to :action => "other_host", :only_path => true - rescue Test::Unit::AssertionFailedError => err - redirection_msg, diff_msg = err.message.scan(/<\{[^\}]+\}>/).collect { |s| s[2..-3] } - assert_match %r(:only_path=>false), redirection_msg - assert_match %r(:host=>"other.test.host"), redirection_msg - assert_match %r(:action=>"other_host"), redirection_msg - assert_match %r(:only_path=>true), diff_msg - assert_match %r(:host=>"other.test.host"), diff_msg - end - end - - def test_module_redirect - get :module_redirect - assert_redirect_url "http://test.host/module_test/module_redirect/hello_world" - end - - def test_module_redirect_using_options - get :module_redirect - assert_redirected_to :controller => 'module_test/module_redirect', :action => 'hello_world' - end - - def test_redirect_with_assigns - get :redirect_with_assigns - assert_equal "world", assigns["hello"] - end - - def test_redirect_to_back - @request.env["HTTP_REFERER"] = "http://www.example.com/coming/from" - get :redirect_to_back - assert_redirect_url "http://www.example.com/coming/from" - end - - def test_redirect_to_back_with_no_referer - assert_raises(ActionController::RedirectBackError) { - @request.env["HTTP_REFERER"] = nil - get :redirect_to_back - } - end -end - -module ModuleTest - class ModuleRedirectController < ::RedirectController - def module_redirect - redirect_to :controller => '/redirect', :action => "hello_world" - end - end - - class ModuleRedirectTest < Test::Unit::TestCase - def setup - @controller = ModuleRedirectController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end - - def test_simple_redirect - get :simple_redirect - assert_redirect_url "http://test.host/module_test/module_redirect/hello_world" - end - - def test_redirect_with_method_reference_and_parameters - get :method_redirect - assert_redirect_url "http://test.host/module_test/module_redirect/dashboard/1?message=hello" - end - - def test_simple_redirect_using_options - get :host_redirect - assert_redirected_to :action => "other_host", :only_path => false, :host => 'other.test.host' - end - - def test_module_redirect - get :module_redirect - assert_redirect_url "http://test.host/redirect/hello_world" - end - - def test_module_redirect_using_options - get :module_redirect - assert_redirected_to :controller => 'redirect', :action => "hello_world" - end - end -end -require File.dirname(__FILE__) + '/../abstract_unit' - -unless defined?(Customer) - Customer = Struct.new("Customer", :name) -end - -module Fun - class GamesController < ActionController::Base - def hello_world - end - end -end - - -class TestController < ActionController::Base - layout :determine_layout - - def hello_world - end - - def render_hello_world - render "test/hello_world" - end - - def render_hello_world_from_variable - @person = "david" - render_text "hello #{@person}" - end - - def render_action_hello_world - render_action "hello_world" - end - - def render_action_hello_world_with_symbol - render_action :hello_world - end - - def render_text_hello_world - render_text "hello world" - end - - def render_custom_code - render_text "hello world", "404 Moved" - end - - def render_xml_hello - @name = "David" - render "test/hello" - end - - def greeting - # let's just rely on the template - end - - def layout_test - render_action "hello_world" - end - - def builder_layout_test - render_action "hello" - end - - def partials_list - @test_unchanged = 'hello' - @customers = [ Customer.new("david"), Customer.new("mary") ] - render_action "list" - end - - def partial_only - render_partial - end - - def hello_in_a_string - @customers = [ Customer.new("david"), Customer.new("mary") ] - render_text "How's there? #{render_to_string("test/list")}" - end - - def accessing_params_in_template - render_template "Hello: <%= params[:name] %>" - end - - def accessing_local_assigns_in_inline_template - name = params[:local_name] - render :inline => "<%= 'Goodbye, ' + local_name %>", - :locals => { :local_name => name } - end - - def accessing_local_assigns_in_inline_template_with_string_keys - name = params[:local_name] - ActionView::Base.local_assigns_support_string_keys = true - render :inline => "<%= 'Goodbye, ' + local_name %>", - :locals => { "local_name" => name } - ActionView::Base.local_assigns_support_string_keys = false - end - - def render_to_string_test - @foo = render_to_string :inline => "this is a test" - end - - def rescue_action(e) raise end - - private - def determine_layout - case action_name - when "layout_test": "layouts/standard" - when "builder_layout_test": "layouts/builder" - end - end -end - -TestController.template_root = File.dirname(__FILE__) + "/../fixtures/" -Fun::GamesController.template_root = File.dirname(__FILE__) + "/../fixtures/" - -class RenderTest < Test::Unit::TestCase - def setup - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - @controller = TestController.new - - @request.host = "www.nextangle.com" - end - - def test_simple_show - get :hello_world - assert_response 200 - assert_template "test/hello_world" - end - - def test_do_with_render - get :render_hello_world - assert_template "test/hello_world" - end - - def test_do_with_render_from_variable - get :render_hello_world_from_variable - assert_equal "hello david", @response.body - end - - def test_do_with_render_action - get :render_action_hello_world - assert_template "test/hello_world" - end - - def test_do_with_render_action_with_symbol - get :render_action_hello_world_with_symbol - assert_template "test/hello_world" - end - - def test_do_with_render_text - get :render_text_hello_world - assert_equal "hello world", @response.body - end - - def test_do_with_render_custom_code - get :render_custom_code - assert_response 404 - end - - def test_attempt_to_access_object_method - assert_raises(ActionController::UnknownAction, "No action responded to [clone]") { get :clone } - end - - def test_private_methods - assert_raises(ActionController::UnknownAction, "No action responded to [determine_layout]") { get :determine_layout } - end - - def test_access_to_request_in_view - view_internals_old_value = ActionController::Base.view_controller_internals - - ActionController::Base.view_controller_internals = false - ActionController::Base.protected_variables_cache = nil - - get :hello_world - assert_nil assigns["request"] - - ActionController::Base.view_controller_internals = true - ActionController::Base.protected_variables_cache = nil - - get :hello_world - assert_kind_of ActionController::AbstractRequest, assigns["request"] - - ActionController::Base.view_controller_internals = view_internals_old_value - ActionController::Base.protected_variables_cache = nil - end - - def test_render_xml - get :render_xml_hello - assert_equal "\n

    Hello David

    \n

    This is grand!

    \n\n", @response.body - end - - def test_render_xml_with_default - get :greeting - assert_equal "

    This is grand!

    \n", @response.body - end - - def test_layout_rendering - get :layout_test - assert_equal "Hello world!", @response.body - end - - def test_render_xml_with_layouts - get :builder_layout_test - assert_equal "\n\n

    Hello

    \n

    This is grand!

    \n\n
    \n", @response.body - end - - # def test_partials_list - # get :partials_list - # assert_equal "goodbyeHello: davidHello: marygoodbye\n", process_request.body - # end - - def test_partial_only - get :partial_only - assert_equal "only partial", @response.body - end - - def test_render_to_string - get :hello_in_a_string - assert_equal "How's there? goodbyeHello: davidHello: marygoodbye\n", @response.body - end - - def test_render_to_string_resets_assigns - get :render_to_string_test - assert_equal "The value of foo is: ::this is a test::\n", @response.body - end - - def test_nested_rendering - @controller = Fun::GamesController.new - get :hello_world - assert_equal "Living in a nested world", @response.body - end - - def test_accessing_params_in_template - get :accessing_params_in_template, :name => "David" - assert_equal "Hello: David", @response.body - end - - def test_accessing_local_assigns_in_inline_template - get :accessing_local_assigns_in_inline_template, :local_name => "Local David" - assert_equal "Goodbye, Local David", @response.body - end - - def test_accessing_local_assigns_in_inline_template_with_string_keys - get :accessing_local_assigns_in_inline_template_with_string_keys, :local_name => "Local David" - assert_equal "Goodbye, Local David", @response.body - end -end -require File.dirname(__FILE__) + '/../abstract_unit' - -class RequestTest < Test::Unit::TestCase - def setup - @request = ActionController::TestRequest.new - end - - def test_remote_ip - assert_equal '0.0.0.0', @request.remote_ip - - @request.remote_addr = '1.2.3.4' - assert_equal '1.2.3.4', @request.remote_ip - - @request.env['HTTP_CLIENT_IP'] = '2.3.4.5' - assert_equal '2.3.4.5', @request.remote_ip - @request.env.delete 'HTTP_CLIENT_IP' - - @request.env['HTTP_X_FORWARDED_FOR'] = '3.4.5.6' - assert_equal '3.4.5.6', @request.remote_ip - - @request.env['HTTP_X_FORWARDED_FOR'] = 'unknown,3.4.5.6' - assert_equal '3.4.5.6', @request.remote_ip - - @request.env['HTTP_X_FORWARDED_FOR'] = '172.16.0.1,3.4.5.6' - assert_equal '3.4.5.6', @request.remote_ip - - @request.env['HTTP_X_FORWARDED_FOR'] = '192.168.0.1,3.4.5.6' - assert_equal '3.4.5.6', @request.remote_ip - - @request.env['HTTP_X_FORWARDED_FOR'] = '10.0.0.1,3.4.5.6' - assert_equal '3.4.5.6', @request.remote_ip - - @request.env['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,3.4.5.6' - assert_equal '127.0.0.1', @request.remote_ip - - @request.env['HTTP_X_FORWARDED_FOR'] = 'unknown,192.168.0.1' - assert_equal '1.2.3.4', @request.remote_ip - @request.env.delete 'HTTP_X_FORWARDED_FOR' - end - - def test_domains - @request.host = "www.rubyonrails.org" - assert_equal "rubyonrails.org", @request.domain - - @request.host = "www.rubyonrails.co.uk" - assert_equal "rubyonrails.co.uk", @request.domain(2) - - @request.host = "192.168.1.200" - assert_nil @request.domain - - @request.host = nil - assert_nil @request.domain - end - - def test_subdomains - @request.host = "www.rubyonrails.org" - assert_equal %w( www ), @request.subdomains - - @request.host = "www.rubyonrails.co.uk" - assert_equal %w( www ), @request.subdomains(2) - - @request.host = "dev.www.rubyonrails.co.uk" - assert_equal %w( dev www ), @request.subdomains(2) - - @request.host = "foobar.foobar.com" - assert_equal %w( foobar ), @request.subdomains - - @request.host = nil - assert_equal [], @request.subdomains - end - - def test_port_string - @request.port = 80 - assert_equal "", @request.port_string - - @request.port = 8080 - assert_equal ":8080", @request.port_string - end - - def test_relative_url_root - @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi" - @request.env['SERVER_SOFTWARE'] = 'lighttpd/1.2.3' - assert_equal '', @request.relative_url_root, "relative_url_root should be disabled on lighttpd" - - @request.env['SERVER_SOFTWARE'] = 'apache/1.2.3 some random text' - - @request.env['SCRIPT_NAME'] = nil - assert_equal "", @request.relative_url_root - - @request.env['SCRIPT_NAME'] = "/dispatch.cgi" - assert_equal "", @request.relative_url_root - - @request.env['SCRIPT_NAME'] = "/myapp.rb" - assert_equal "", @request.relative_url_root - - @request.relative_url_root = nil - @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi" - assert_equal "/hieraki", @request.relative_url_root - - @request.relative_url_root = nil - @request.env['SCRIPT_NAME'] = "/collaboration/hieraki/dispatch.cgi" - assert_equal "/collaboration/hieraki", @request.relative_url_root - - # apache/scgi case - @request.relative_url_root = nil - @request.env['SCRIPT_NAME'] = "/collaboration/hieraki" - assert_equal "/collaboration/hieraki", @request.relative_url_root - - @request.relative_url_root = nil - @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi" - @request.env['SERVER_SOFTWARE'] = 'lighttpd/1.2.3' - @request.env['RAILS_RELATIVE_URL_ROOT'] = "/hieraki" - assert_equal "/hieraki", @request.relative_url_root - - # @env overrides path guess - @request.relative_url_root = nil - @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi" - @request.env['SERVER_SOFTWARE'] = 'apache/1.2.3 some random text' - @request.env['RAILS_RELATIVE_URL_ROOT'] = "/real_url" - assert_equal "/real_url", @request.relative_url_root - end - - def test_request_uri - @request.env['SERVER_SOFTWARE'] = 'Apache 42.342.3432' - - @request.relative_url_root = nil - @request.set_REQUEST_URI "http://www.rubyonrails.org/path/of/some/uri?mapped=1" - assert_equal "/path/of/some/uri?mapped=1", @request.request_uri - assert_equal "/path/of/some/uri", @request.path - - @request.relative_url_root = nil - @request.set_REQUEST_URI "http://www.rubyonrails.org/path/of/some/uri" - assert_equal "/path/of/some/uri", @request.request_uri - assert_equal "/path/of/some/uri", @request.path - - @request.relative_url_root = nil - @request.set_REQUEST_URI "/path/of/some/uri" - assert_equal "/path/of/some/uri", @request.request_uri - assert_equal "/path/of/some/uri", @request.path - - @request.relative_url_root = nil - @request.set_REQUEST_URI "/" - assert_equal "/", @request.request_uri - assert_equal "/", @request.path - - @request.relative_url_root = nil - @request.set_REQUEST_URI "/?m=b" - assert_equal "/?m=b", @request.request_uri - assert_equal "/", @request.path - - @request.relative_url_root = nil - @request.set_REQUEST_URI "/" - @request.env['SCRIPT_NAME'] = "/dispatch.cgi" - assert_equal "/", @request.request_uri - assert_equal "/", @request.path - - @request.relative_url_root = nil - @request.set_REQUEST_URI "/hieraki/" - @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi" - assert_equal "/hieraki/", @request.request_uri - assert_equal "/", @request.path - - @request.relative_url_root = nil - @request.set_REQUEST_URI "/collaboration/hieraki/books/edit/2" - @request.env['SCRIPT_NAME'] = "/collaboration/hieraki/dispatch.cgi" - assert_equal "/collaboration/hieraki/books/edit/2", @request.request_uri - assert_equal "/books/edit/2", @request.path - - # The following tests are for when REQUEST_URI is not supplied (as in IIS) - @request.relative_url_root = nil - @request.set_REQUEST_URI nil - @request.env['PATH_INFO'] = "/path/of/some/uri?mapped=1" - @request.env['SCRIPT_NAME'] = nil #"/path/dispatch.rb" - assert_equal "/path/of/some/uri?mapped=1", @request.request_uri - assert_equal "/path/of/some/uri", @request.path - - @request.relative_url_root = nil - @request.env['PATH_INFO'] = "/path/of/some/uri?mapped=1" - @request.env['SCRIPT_NAME'] = "/path/dispatch.rb" - assert_equal "/path/of/some/uri?mapped=1", @request.request_uri - assert_equal "/of/some/uri", @request.path - - @request.relative_url_root = nil - @request.env['PATH_INFO'] = "/path/of/some/uri" - @request.env['SCRIPT_NAME'] = nil - assert_equal "/path/of/some/uri", @request.request_uri - assert_equal "/path/of/some/uri", @request.path - - @request.relative_url_root = nil - @request.env['PATH_INFO'] = "/" - assert_equal "/", @request.request_uri - assert_equal "/", @request.path - - @request.relative_url_root = nil - @request.env['PATH_INFO'] = "/?m=b" - assert_equal "/?m=b", @request.request_uri - assert_equal "/", @request.path - - @request.relative_url_root = nil - @request.env['PATH_INFO'] = "/" - @request.env['SCRIPT_NAME'] = "/dispatch.cgi" - assert_equal "/", @request.request_uri - assert_equal "/", @request.path - - @request.relative_url_root = nil - @request.env['PATH_INFO'] = "/hieraki/" - @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi" - assert_equal "/hieraki/", @request.request_uri - assert_equal "/", @request.path - - # This test ensures that Rails uses REQUEST_URI over PATH_INFO - @request.relative_url_root = nil - @request.env['REQUEST_URI'] = "/some/path" - @request.env['PATH_INFO'] = "/another/path" - @request.env['SCRIPT_NAME'] = "/dispatch.cgi" - assert_equal "/some/path", @request.request_uri - assert_equal "/some/path", @request.path - end - - - def test_host_with_port - @request.host = "rubyonrails.org" - @request.port = 80 - assert_equal "rubyonrails.org", @request.host_with_port - - @request.host = "rubyonrails.org" - @request.port = 81 - assert_equal "rubyonrails.org:81", @request.host_with_port - end - - def test_server_software - assert_equal nil, @request.server_software - - @request.env['SERVER_SOFTWARE'] = 'Apache3.422' - assert_equal 'apache', @request.server_software - - @request.env['SERVER_SOFTWARE'] = 'lighttpd(1.1.4)' - assert_equal 'lighttpd', @request.server_software - end - - def test_xml_http_request - assert !@request.xml_http_request? - assert !@request.xhr? - - @request.env['HTTP_X_REQUESTED_WITH'] = "DefinitelyNotAjax1.0" - assert !@request.xml_http_request? - assert !@request.xhr? - - @request.env['HTTP_X_REQUESTED_WITH'] = "XMLHttpRequest" - assert @request.xml_http_request? - assert @request.xhr? - end - - def test_reports_ssl - assert !@request.ssl? - @request.env['HTTPS'] = 'on' - assert @request.ssl? - end - - def test_reports_ssl_when_proxied_via_lighttpd - assert !@request.ssl? - @request.env['HTTP_X_FORWARDED_PROTO'] = 'https' - assert @request.ssl? - end - -end -require File.dirname(__FILE__) + '/../abstract_unit' -require 'test/unit' -require File.dirname(__FILE__) + '/fake_controllers' -require 'stringio' - -RunTimeTests = ARGV.include? 'time' - -module ActionController::CodeGeneration - -class SourceTests < Test::Unit::TestCase - attr_accessor :source - def setup - @source = Source.new - end - - def test_initial_state - assert_equal [], source.lines - assert_equal 0, source.indentation_level - end - - def test_trivial_operations - source << "puts 'Hello World'" - assert_equal ["puts 'Hello World'"], source.lines - assert_equal "puts 'Hello World'", source.to_s - - source.line "puts 'Goodbye World'" - assert_equal ["puts 'Hello World'", "puts 'Goodbye World'"], source.lines - assert_equal "puts 'Hello World'\nputs 'Goodbye World'", source.to_s - end - - def test_indentation - source << "x = gets.to_i" - source << 'if x.odd?' - source.indent { source << "puts 'x is odd!'" } - source << 'else' - source.indent { source << "puts 'x is even!'" } - source << 'end' - - assert_equal ["x = gets.to_i", "if x.odd?", " puts 'x is odd!'", 'else', " puts 'x is even!'", 'end'], source.lines - - text = "x = gets.to_i -if x.odd? - puts 'x is odd!' -else - puts 'x is even!' -end" - - assert_equal text, source.to_s - end -end - -class CodeGeneratorTests < Test::Unit::TestCase - attr_accessor :generator - def setup - @generator = CodeGenerator.new - end - - def test_initial_state - assert_equal [], generator.source.lines - assert_equal [], generator.locals - end - - def test_trivial_operations - ["puts 'Hello World'", "puts 'Goodbye World'"].each {|l| generator << l} - assert_equal ["puts 'Hello World'", "puts 'Goodbye World'"], generator.source.lines - assert_equal "puts 'Hello World'\nputs 'Goodbye World'", generator.to_s - end - - def test_if - generator << "x = gets.to_i" - generator.if("x.odd?") { generator << "puts 'x is odd!'" } - - assert_equal "x = gets.to_i\nif x.odd?\n puts 'x is odd!'\nend", generator.to_s - end - - def test_else - test_if - generator.else { generator << "puts 'x is even!'" } - - assert_equal "x = gets.to_i\nif x.odd?\n puts 'x is odd!'\nelse \n puts 'x is even!'\nend", generator.to_s - end - - def test_dup - generator << 'x = 2' - generator.locals << :x - - g = generator.dup - assert_equal generator.source, g.source - assert_equal generator.locals, g.locals - - g << 'y = 3' - g.locals << :y - assert_equal [:x, :y], g.locals # Make sure they don't share the same array. - assert_equal [:x], generator.locals - end -end - -class RecognitionTests < Test::Unit::TestCase - attr_accessor :generator - alias :g :generator - def setup - @generator = RecognitionGenerator.new - end - - def go(components) - g.current = components.first - g.after = components[1..-1] || [] - g.go - end - - def execute(path, show = false) - path = path.split('/') if path.is_a? String - source = "index, path = 0, #{path.inspect}\n#{g.to_s}" - puts source if show - r = eval source - r ? r.symbolize_keys : nil - end - - Static = ::ActionController::Routing::StaticComponent - Dynamic = ::ActionController::Routing::DynamicComponent - Path = ::ActionController::Routing::PathComponent - Controller = ::ActionController::Routing::ControllerComponent - - def test_all_static - c = %w(hello world how are you).collect {|str| Static.new(str)} - - g.result :controller, "::ContentController", true - g.constant_result :action, 'index' - - go c - - assert_nil execute('x') - assert_nil execute('hello/world/how') - assert_nil execute('hello/world/how/are') - assert_nil execute('hello/world/how/are/you/today') - assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hello/world/how/are/you')) - end - - def test_basic_dynamic - c = [Static.new("hi"), Dynamic.new(:action)] - g.result :controller, "::ContentController", true - go c - - assert_nil execute('boo') - assert_nil execute('boo/blah') - assert_nil execute('hi') - assert_nil execute('hi/dude/what') - assert_equal({:controller => ::ContentController, :action => 'dude'}, execute('hi/dude')) - end - - def test_basic_dynamic_backwards - c = [Dynamic.new(:action), Static.new("hi")] - go c - - assert_nil execute('') - assert_nil execute('boo') - assert_nil execute('boo/blah') - assert_nil execute('hi') - assert_equal({:action => 'index'}, execute('index/hi')) - assert_equal({:action => 'show'}, execute('show/hi')) - assert_nil execute('hi/dude') - end - - def test_dynamic_with_default - c = [Static.new("hi"), Dynamic.new(:action, :default => 'index')] - g.result :controller, "::ContentController", true - go c - - assert_nil execute('boo') - assert_nil execute('boo/blah') - assert_nil execute('hi/dude/what') - assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi')) - assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi/index')) - assert_equal({:controller => ::ContentController, :action => 'dude'}, execute('hi/dude')) - end - - def test_dynamic_with_string_condition - c = [Static.new("hi"), Dynamic.new(:action, :condition => 'index')] - g.result :controller, "::ContentController", true - go c - - assert_nil execute('boo') - assert_nil execute('boo/blah') - assert_nil execute('hi') - assert_nil execute('hi/dude/what') - assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi/index')) - assert_nil execute('hi/dude') - end - - def test_dynamic_with_string_condition_backwards - c = [Dynamic.new(:action, :condition => 'index'), Static.new("hi")] - g.result :controller, "::ContentController", true - go c - - assert_nil execute('boo') - assert_nil execute('boo/blah') - assert_nil execute('hi') - assert_nil execute('dude/what/hi') - assert_nil execute('index/what') - assert_equal({:controller => ::ContentController, :action => 'index'}, execute('index/hi')) - assert_nil execute('dude/hi') - end - - def test_dynamic_with_regexp_condition - c = [Static.new("hi"), Dynamic.new(:action, :condition => /^[a-z]+$/)] - g.result :controller, "::ContentController", true - go c - - assert_nil execute('boo') - assert_nil execute('boo/blah') - assert_nil execute('hi') - assert_nil execute('hi/FOXY') - assert_nil execute('hi/138708jkhdf') - assert_nil execute('hi/dkjfl8792343dfsf') - assert_nil execute('hi/dude/what') - assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi/index')) - assert_equal({:controller => ::ContentController, :action => 'dude'}, execute('hi/dude')) - end - - def test_dynamic_with_regexp_and_default - c = [Static.new("hi"), Dynamic.new(:action, :condition => /^[a-z]+$/, :default => 'index')] - g.result :controller, "::ContentController", true - go c - - assert_nil execute('boo') - assert_nil execute('boo/blah') - assert_nil execute('hi/FOXY') - assert_nil execute('hi/138708jkhdf') - assert_nil execute('hi/dkjfl8792343dfsf') - assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi')) - assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi/index')) - assert_equal({:controller => ::ContentController, :action => 'dude'}, execute('hi/dude')) - assert_nil execute('hi/dude/what') - end - - def test_path - c = [Static.new("hi"), Path.new(:file)] - g.result :controller, "::ContentController", true - g.constant_result :action, "download" - - go c - - assert_nil execute('boo') - assert_nil execute('boo/blah') - assert_equal({:controller => ::ContentController, :action => 'download', :file => []}, execute('hi')) - assert_equal({:controller => ::ContentController, :action => 'download', :file => %w(books agile_rails_dev.pdf)}, - execute('hi/books/agile_rails_dev.pdf')) - assert_equal({:controller => ::ContentController, :action => 'download', :file => ['dude']}, execute('hi/dude')) - assert_equal 'dude/what', execute('hi/dude/what')[:file].to_s - end - - def test_path_with_dynamic - c = [Dynamic.new(:action), Path.new(:file)] - g.result :controller, "::ContentController", true - - go c - - assert_nil execute('') - assert_equal({:controller => ::ContentController, :action => 'download', :file => []}, execute('download')) - assert_equal({:controller => ::ContentController, :action => 'download', :file => %w(books agile_rails_dev.pdf)}, - execute('download/books/agile_rails_dev.pdf')) - assert_equal({:controller => ::ContentController, :action => 'download', :file => ['dude']}, execute('download/dude')) - assert_equal 'dude/what', execute('hi/dude/what')[:file].to_s - end - - def test_path_with_dynamic_and_default - c = [Dynamic.new(:action, :default => 'index'), Path.new(:file)] - - go c - - assert_equal({:action => 'index', :file => []}, execute('')) - assert_equal({:action => 'index', :file => []}, execute('index')) - assert_equal({:action => 'blarg', :file => []}, execute('blarg')) - assert_equal({:action => 'index', :file => ['content']}, execute('index/content')) - assert_equal({:action => 'show', :file => ['rails_dev.pdf']}, execute('show/rails_dev.pdf')) - end - - def test_controller - c = [Static.new("hi"), Controller.new(:controller)] - g.constant_result :action, "hi" - - go c - - assert_nil execute('boo') - assert_nil execute('boo/blah') - assert_nil execute('hi/x') - assert_nil execute('hi/13870948') - assert_nil execute('hi/content/dog') - assert_nil execute('hi/admin/user/foo') - assert_equal({:controller => ::ContentController, :action => 'hi'}, execute('hi/content')) - assert_equal({:controller => ::Admin::UserController, :action => 'hi'}, execute('hi/admin/user')) - end - - def test_controller_with_regexp - c = [Static.new("hi"), Controller.new(:controller, :condition => /^admin\/.+$/)] - g.constant_result :action, "hi" - - go c - - assert_nil execute('hi') - assert_nil execute('hi/x') - assert_nil execute('hi/content') - assert_equal({:controller => ::Admin::UserController, :action => 'hi'}, execute('hi/admin/user')) - assert_equal({:controller => ::Admin::NewsFeedController, :action => 'hi'}, execute('hi/admin/news_feed')) - assert_nil execute('hi/admin/user/foo') - end - - def test_standard_route(time = ::RunTimeTests) - c = [Controller.new(:controller), Dynamic.new(:action, :default => 'index'), Dynamic.new(:id, :default => nil)] - go c - - # Make sure we get the right answers - assert_equal({:controller => ::ContentController, :action => 'index'}, execute('content')) - assert_equal({:controller => ::ContentController, :action => 'list'}, execute('content/list')) - assert_equal({:controller => ::ContentController, :action => 'show', :id => '10'}, execute('content/show/10')) - - assert_equal({:controller => ::Admin::UserController, :action => 'index'}, execute('admin/user')) - assert_equal({:controller => ::Admin::UserController, :action => 'list'}, execute('admin/user/list')) - assert_equal({:controller => ::Admin::UserController, :action => 'show', :id => 'nseckar'}, execute('admin/user/show/nseckar')) - - assert_nil execute('content/show/10/20') - assert_nil execute('food') - - if time - source = "def self.execute(path) - path = path.split('/') if path.is_a? String - index = 0 - r = #{g.to_s} - end" - eval(source) - - GC.start - n = 1000 - time = Benchmark.realtime do n.times { - execute('content') - execute('content/list') - execute('content/show/10') - - execute('admin/user') - execute('admin/user/list') - execute('admin/user/show/nseckar') - - execute('admin/user/show/nseckar/dude') - execute('admin/why/show/nseckar') - execute('content/show/10/20') - execute('food') - } end - time -= Benchmark.realtime do n.times { } end - - - puts "\n\nRecognition:" - per_url = time / (n * 10) - - puts "#{per_url * 1000} ms/url" - puts "#{1 / per_url} urls/s\n\n" - end - end - - def test_default_route - g.result :controller, "::ContentController", true - g.constant_result :action, 'index' - - go [] - - assert_nil execute('x') - assert_nil execute('hello/world/how') - assert_nil execute('hello/world/how/are') - assert_nil execute('hello/world/how/are/you/today') - assert_equal({:controller => ::ContentController, :action => 'index'}, execute([])) - end -end - -class GenerationTests < Test::Unit::TestCase - attr_accessor :generator - alias :g :generator - def setup - @generator = GenerationGenerator.new # ha! - end - - def go(components) - g.current = components.first - g.after = components[1..-1] || [] - g.go - end - - def execute(options, recall, show = false) - source = "\n -expire_on = ::ActionController::Routing.expiry_hash(options, recall) -hash = merged = recall.merge(options) -not_expired = true - -#{g.to_s}\n\n" - puts source if show - eval(source) - end - - Static = ::ActionController::Routing::StaticComponent - Dynamic = ::ActionController::Routing::DynamicComponent - Path = ::ActionController::Routing::PathComponent - Controller = ::ActionController::Routing::ControllerComponent - - def test_all_static_no_requirements - c = [Static.new("hello"), Static.new("world")] - go c - - assert_equal "/hello/world", execute({}, {}) - end - - def test_basic_dynamic - c = [Static.new("hi"), Dynamic.new(:action)] - go c - - assert_equal '/hi/index', execute({:action => 'index'}, {:action => 'index'}) - assert_equal '/hi/show', execute({:action => 'show'}, {:action => 'index'}) - assert_equal '/hi/list+people', execute({}, {:action => 'list people'}) - assert_nil execute({},{}) - end - - def test_dynamic_with_default - c = [Static.new("hi"), Dynamic.new(:action, :default => 'index')] - go c - - assert_equal '/hi', execute({:action => 'index'}, {:action => 'index'}) - assert_equal '/hi/show', execute({:action => 'show'}, {:action => 'index'}) - assert_equal '/hi/list+people', execute({}, {:action => 'list people'}) - assert_equal '/hi', execute({}, {}) - end - - def test_dynamic_with_regexp_condition - c = [Static.new("hi"), Dynamic.new(:action, :condition => /^[a-z]+$/)] - go c - - assert_equal '/hi/index', execute({:action => 'index'}, {:action => 'index'}) - assert_nil execute({:action => 'fox5'}, {:action => 'index'}) - assert_nil execute({:action => 'something_is_up'}, {:action => 'index'}) - assert_nil execute({}, {:action => 'list people'}) - assert_equal '/hi/abunchofcharacter', execute({:action => 'abunchofcharacter'}, {}) - assert_nil execute({}, {}) - end - - def test_dynamic_with_default_and_regexp_condition - c = [Static.new("hi"), Dynamic.new(:action, :default => 'index', :condition => /^[a-z]+$/)] - go c - - assert_equal '/hi', execute({:action => 'index'}, {:action => 'index'}) - assert_nil execute({:action => 'fox5'}, {:action => 'index'}) - assert_nil execute({:action => 'something_is_up'}, {:action => 'index'}) - assert_nil execute({}, {:action => 'list people'}) - assert_equal '/hi/abunchofcharacter', execute({:action => 'abunchofcharacter'}, {}) - assert_equal '/hi', execute({}, {}) - end - - def test_path - c = [Static.new("hi"), Path.new(:file)] - go c - - assert_equal '/hi', execute({:file => []}, {}) - assert_equal '/hi/books/agile_rails_dev.pdf', execute({:file => %w(books agile_rails_dev.pdf)}, {}) - assert_equal '/hi/books/development%26whatever/agile_rails_dev.pdf', execute({:file => %w(books development&whatever agile_rails_dev.pdf)}, {}) - - assert_equal '/hi', execute({:file => ''}, {}) - assert_equal '/hi/books/agile_rails_dev.pdf', execute({:file => 'books/agile_rails_dev.pdf'}, {}) - assert_equal '/hi/books/development%26whatever/agile_rails_dev.pdf', execute({:file => 'books/development&whatever/agile_rails_dev.pdf'}, {}) - end - - def test_controller - c = [Static.new("hi"), Controller.new(:controller)] - go c - - assert_nil execute({}, {}) - assert_equal '/hi/content', execute({:controller => 'content'}, {}) - assert_equal '/hi/admin/user', execute({:controller => 'admin/user'}, {}) - assert_equal '/hi/content', execute({}, {:controller => 'content'}) - assert_equal '/hi/admin/user', execute({}, {:controller => 'admin/user'}) - end - - def test_controller_with_regexp - c = [Static.new("hi"), Controller.new(:controller, :condition => /^admin\/.+$/)] - go c - - assert_nil execute({}, {}) - assert_nil execute({:controller => 'content'}, {}) - assert_equal '/hi/admin/user', execute({:controller => 'admin/user'}, {}) - assert_nil execute({}, {:controller => 'content'}) - assert_equal '/hi/admin/user', execute({}, {:controller => 'admin/user'}) - end - - def test_standard_route(time = ::RunTimeTests) - c = [Controller.new(:controller), Dynamic.new(:action, :default => 'index'), Dynamic.new(:id, :default => nil)] - go c - - # Make sure we get the right answers - assert_equal('/content', execute({:action => 'index'}, {:controller => 'content', :action => 'list'})) - assert_equal('/content/list', execute({:action => 'list'}, {:controller => 'content', :action => 'index'})) - assert_equal('/content/show/10', execute({:action => 'show', :id => '10'}, {:controller => 'content', :action => 'list'})) - - assert_equal('/admin/user', execute({:action => 'index'}, {:controller => 'admin/user', :action => 'list'})) - assert_equal('/admin/user/list', execute({:action => 'list'}, {:controller => 'admin/user', :action => 'index'})) - assert_equal('/admin/user/show/10', execute({:action => 'show', :id => '10'}, {:controller => 'admin/user', :action => 'list'})) - - if time - GC.start - n = 1000 - time = Benchmark.realtime do n.times { - execute({:action => 'index'}, {:controller => 'content', :action => 'list'}) - execute({:action => 'list'}, {:controller => 'content', :action => 'index'}) - execute({:action => 'show', :id => '10'}, {:controller => 'content', :action => 'list'}) - - execute({:action => 'index'}, {:controller => 'admin/user', :action => 'list'}) - execute({:action => 'list'}, {:controller => 'admin/user', :action => 'index'}) - execute({:action => 'show', :id => '10'}, {:controller => 'admin/user', :action => 'list'}) - } end - time -= Benchmark.realtime do n.times { } end - - puts "\n\nGeneration:" - per_url = time / (n * 6) - - puts "#{per_url * 1000} ms/url" - puts "#{1 / per_url} urls/s\n\n" - end - end - - def test_default_route - g.if(g.check_conditions(:controller => 'content', :action => 'welcome')) { go [] } - - assert_nil execute({:controller => 'foo', :action => 'welcome'}, {}) - assert_nil execute({:controller => 'content', :action => 'elcome'}, {}) - assert_nil execute({:action => 'elcome'}, {:controller => 'content'}) - - assert_equal '/', execute({:controller => 'content', :action => 'welcome'}, {}) - assert_equal '/', execute({:action => 'welcome'}, {:controller => 'content'}) - assert_equal '/', execute({:action => 'welcome', :id => '10'}, {:controller => 'content'}) - end -end - -class RouteTests < Test::Unit::TestCase - - def route(*args) - @route = ::ActionController::Routing::Route.new(*args) unless args.empty? - return @route - end - - def rec(path, show = false) - path = path.split('/') if path.is_a? String - index = 0 - source = route.write_recognition.to_s - puts "\n\n#{source}\n\n" if show - r = eval(source) - r ? r.symbolize_keys : r - end - def gen(options, recall = nil, show = false) - recall ||= options.dup - - expire_on = ::ActionController::Routing.expiry_hash(options, recall) - hash = merged = recall.merge(options) - not_expired = true - - source = route.write_generation.to_s - puts "\n\n#{source}\n\n" if show - eval(source) - - end - - def test_static - route 'hello/world', :known => 'known_value', :controller => 'content', :action => 'index' - - assert_nil rec('hello/turn') - assert_nil rec('turn/world') - assert_equal( - {:known => 'known_value', :controller => ::ContentController, :action => 'index'}, - rec('hello/world') - ) - - assert_nil gen(:known => 'foo') - assert_nil gen({}) - assert_equal '/hello/world', gen(:known => 'known_value', :controller => 'content', :action => 'index') - assert_equal '/hello/world', gen(:known => 'known_value', :extra => 'hi', :controller => 'content', :action => 'index') - assert_equal [:extra], route.extra_keys(:known => 'known_value', :extra => 'hi') - end - - def test_dynamic - route 'hello/:name', :controller => 'content', :action => 'show_person' - - assert_nil rec('hello') - assert_nil rec('foo/bar') - assert_equal({:controller => ::ContentController, :action => 'show_person', :name => 'rails'}, rec('hello/rails')) - assert_equal({:controller => ::ContentController, :action => 'show_person', :name => 'Nicholas Seckar'}, rec('hello/Nicholas+Seckar')) - - assert_nil gen(:controller => 'content', :action => 'show_dude', :name => 'rails') - assert_nil gen(:controller => 'content', :action => 'show_person') - assert_nil gen(:controller => 'admin/user', :action => 'show_person', :name => 'rails') - assert_equal '/hello/rails', gen(:controller => 'content', :action => 'show_person', :name => 'rails') - assert_equal '/hello/Nicholas+Seckar', gen(:controller => 'content', :action => 'show_person', :name => 'Nicholas Seckar') - end - - def test_typical - route ':controller/:action/:id', :action => 'index', :id => nil - assert_nil rec('hello') - assert_nil rec('foo bar') - assert_equal({:controller => ::ContentController, :action => 'index'}, rec('content')) - assert_equal({:controller => ::Admin::UserController, :action => 'index'}, rec('admin/user')) - - assert_equal({:controller => ::Admin::UserController, :action => 'index'}, rec('admin/user/index')) - assert_equal({:controller => ::Admin::UserController, :action => 'list'}, rec('admin/user/list')) - assert_equal({:controller => ::Admin::UserController, :action => 'show', :id => '10'}, rec('admin/user/show/10')) - - assert_equal({:controller => ::ContentController, :action => 'list'}, rec('content/list')) - assert_equal({:controller => ::ContentController, :action => 'show', :id => '10'}, rec('content/show/10')) - - - assert_equal '/content', gen(:controller => 'content', :action => 'index') - assert_equal '/content/list', gen(:controller => 'content', :action => 'list') - assert_equal '/content/show/10', gen(:controller => 'content', :action => 'show', :id => '10') - - assert_equal '/admin/user', gen(:controller => 'admin/user', :action => 'index') - assert_equal '/admin/user', gen(:controller => 'admin/user') - assert_equal '/admin/user', gen({:controller => 'admin/user'}, {:controller => 'content', :action => 'list', :id => '10'}) - assert_equal '/admin/user/show/10', gen(:controller => 'admin/user', :action => 'show', :id => '10') - end -end - -class RouteSetTests < Test::Unit::TestCase - attr_reader :rs - def setup - @rs = ::ActionController::Routing::RouteSet.new - @rs.draw {|m| m.connect ':controller/:action/:id' } - ::ActionController::Routing::NamedRoutes.clear - end - - def test_default_setup - assert_equal({:controller => ::ContentController, :action => 'index'}.stringify_keys, rs.recognize_path(%w(content))) - assert_equal({:controller => ::ContentController, :action => 'list'}.stringify_keys, rs.recognize_path(%w(content list))) - assert_equal({:controller => ::ContentController, :action => 'show', :id => '10'}.stringify_keys, rs.recognize_path(%w(content show 10))) - - assert_equal({:controller => ::Admin::UserController, :action => 'show', :id => '10'}.stringify_keys, rs.recognize_path(%w(admin user show 10))) - - assert_equal ['/admin/user/show/10', []], rs.generate({:controller => 'admin/user', :action => 'show', :id => 10}) - - assert_equal ['/admin/user/show', []], rs.generate({:action => 'show'}, {:controller => 'admin/user', :action => 'list', :id => '10'}) - assert_equal ['/admin/user/list/10', []], rs.generate({}, {:controller => 'admin/user', :action => 'list', :id => '10'}) - - assert_equal ['/admin/stuff', []], rs.generate({:controller => 'stuff'}, {:controller => 'admin/user', :action => 'list', :id => '10'}) - assert_equal ['/stuff', []], rs.generate({:controller => '/stuff'}, {:controller => 'admin/user', :action => 'list', :id => '10'}) - end - - def test_ignores_leading_slash - @rs.draw {|m| m.connect '/:controller/:action/:id'} - test_default_setup - end - - def test_time_recognition - n = 10000 - if RunTimeTests - GC.start - rectime = Benchmark.realtime do - n.times do - rs.recognize_path(%w(content)) - rs.recognize_path(%w(content list)) - rs.recognize_path(%w(content show 10)) - rs.recognize_path(%w(admin user)) - rs.recognize_path(%w(admin user list)) - rs.recognize_path(%w(admin user show 10)) - end - end - puts "\n\nRecognition (RouteSet):" - per_url = rectime / (n * 6) - puts "#{per_url * 1000} ms/url" - puts "#{1 / per_url} url/s\n\n" - end - end - def test_time_generation - n = 5000 - if RunTimeTests - GC.start - pairs = [ - [{:controller => 'content', :action => 'index'}, {:controller => 'content', :action => 'show'}], - [{:controller => 'content'}, {:controller => 'content', :action => 'index'}], - [{:controller => 'content', :action => 'list'}, {:controller => 'content', :action => 'index'}], - [{:controller => 'content', :action => 'show', :id => '10'}, {:controller => 'content', :action => 'list'}], - [{:controller => 'admin/user', :action => 'index'}, {:controller => 'admin/user', :action => 'show'}], - [{:controller => 'admin/user'}, {:controller => 'admin/user', :action => 'index'}], - [{:controller => 'admin/user', :action => 'list'}, {:controller => 'admin/user', :action => 'index'}], - [{:controller => 'admin/user', :action => 'show', :id => '10'}, {:controller => 'admin/user', :action => 'list'}], - ] - p = nil - gentime = Benchmark.realtime do - n.times do - pairs.each {|(a, b)| rs.generate(a, b)} - end - end - - puts "\n\nGeneration (RouteSet): (#{(n * 8)} urls)" - per_url = gentime / (n * 8) - puts "#{per_url * 1000} ms/url" - puts "#{1 / per_url} url/s\n\n" - end - end - - def test_route_with_colon_first - rs.draw do |map| - map.connect '/:controller/:action/:id', :action => 'index', :id => nil - map.connect ':url', :controller => 'tiny_url', :action => 'translate' - end - end - - def test_route_generating_string_literal_in_comparison_warning - old_stderr = $stderr - $stderr = StringIO.new - rs.draw do |map| - map.connect 'subscriptions/:action/:subscription_type', :controller => "subscriptions" - end - assert_equal "", $stderr.string - ensure - $stderr = old_stderr - end - - def test_route_with_regexp_for_controller - rs.draw do |map| - map.connect ':controller/:admintoken/:action/:id', :controller => /admin\/.+/ - map.connect ':controller/:action/:id' - end - assert_equal({:controller => ::Admin::UserController, :admintoken => "foo", :action => "index"}.stringify_keys, - rs.recognize_path(%w(admin user foo))) - assert_equal({:controller => ::ContentController, :action => "foo"}.stringify_keys, - rs.recognize_path(%w(content foo))) - assert_equal ['/admin/user/foo', []], rs.generate(:controller => "admin/user", :admintoken => "foo", :action => "index") - assert_equal ['/content/foo',[]], rs.generate(:controller => "content", :action => "foo") - end - - def test_basic_named_route - rs.home '', :controller => 'content', :action => 'list' - x = setup_for_named_route - assert_equal({:controller => '/content', :action => 'list'}, - x.new.send(:home_url)) - end - - def test_named_route_with_option - rs.page 'page/:title', :controller => 'content', :action => 'show_page' - x = setup_for_named_route - assert_equal({:controller => '/content', :action => 'show_page', :title => 'new stuff'}, - x.new.send(:page_url, :title => 'new stuff')) - end - - def test_named_route_with_default - rs.page 'page/:title', :controller => 'content', :action => 'show_page', :title => 'AboutPage' - x = setup_for_named_route - assert_equal({:controller => '/content', :action => 'show_page', :title => 'AboutPage'}, - x.new.send(:page_url)) - assert_equal({:controller => '/content', :action => 'show_page', :title => 'AboutRails'}, - x.new.send(:page_url, :title => "AboutRails")) - - end - - def setup_for_named_route - x = Class.new - x.send(:define_method, :url_for) {|x| x} - x.send :include, ::ActionController::Routing::NamedRoutes - x - end - - def test_named_route_without_hash - rs.draw do |map| - rs.normal ':controller/:action/:id' - end - end - - def test_named_route_with_regexps - rs.draw do |map| - rs.article 'page/:year/:month/:day/:title', :controller => 'page', :action => 'show', - :year => /^\d+$/, :month => /^\d+$/, :day => /^\d+$/ - rs.connect ':controller/:action/:id' - end - x = setup_for_named_route - assert_equal( - {:controller => '/page', :action => 'show', :title => 'hi'}, - x.new.send(:article_url, :title => 'hi') - ) - assert_equal( - {:controller => '/page', :action => 'show', :title => 'hi', :day => 10, :year => 2005, :month => 6}, - x.new.send(:article_url, :title => 'hi', :day => 10, :year => 2005, :month => 6) - ) - end - - def test_changing_controller - assert_equal ['/admin/stuff/show/10', []], rs.generate( - {:controller => 'stuff', :action => 'show', :id => 10}, - {:controller => 'admin/user', :action => 'index'} - ) - end - - def test_paths_escaped - rs.draw do |map| - rs.path 'file/*path', :controller => 'content', :action => 'show_file' - rs.connect ':controller/:action/:id' - end - results = rs.recognize_path %w(file hello+world how+are+you%3F) - assert results, "Recognition should have succeeded" - assert_equal ['hello world', 'how are you?'], results['path'] - - results = rs.recognize_path %w(file) - assert results, "Recognition should have succeeded" - assert_equal [], results['path'] - end - - def test_non_controllers_cannot_be_matched - rs.draw do - rs.connect ':controller/:action/:id' - end - assert_nil rs.recognize_path(%w(not_a show 10)), "Shouldn't recognize non-controllers as controllers!" - end - - def test_paths_do_not_accept_defaults - assert_raises(ActionController::RoutingError) do - rs.draw do |map| - rs.path 'file/*path', :controller => 'content', :action => 'show_file', :path => %w(fake default) - rs.connect ':controller/:action/:id' - end - end - - rs.draw do |map| - rs.path 'file/*path', :controller => 'content', :action => 'show_file', :path => [] - rs.connect ':controller/:action/:id' - end - end - - def test_backwards - rs.draw do |map| - rs.connect 'page/:id/:action', :controller => 'pages', :action => 'show' - rs.connect ':controller/:action/:id' - end - - assert_equal ['/page/20', []], rs.generate({:id => 20}, {:controller => 'pages'}) - assert_equal ['/page/20', []], rs.generate(:controller => 'pages', :id => 20, :action => 'show') - assert_equal ['/pages/boo', []], rs.generate(:controller => 'pages', :action => 'boo') - end - - def test_route_with_fixnum_default - rs.draw do |map| - rs.connect 'page/:id', :controller => 'content', :action => 'show_page', :id => 1 - rs.connect ':controller/:action/:id' - end - - assert_equal ['/page', []], rs.generate(:controller => 'content', :action => 'show_page') - assert_equal ['/page', []], rs.generate(:controller => 'content', :action => 'show_page', :id => 1) - assert_equal ['/page', []], rs.generate(:controller => 'content', :action => 'show_page', :id => '1') - assert_equal ['/page/10', []], rs.generate(:controller => 'content', :action => 'show_page', :id => 10) - - ctrl = ::ContentController - - assert_equal({'controller' => ctrl, 'action' => 'show_page', 'id' => 1}, rs.recognize_path(%w(page))) - assert_equal({'controller' => ctrl, 'action' => 'show_page', 'id' => '1'}, rs.recognize_path(%w(page 1))) - assert_equal({'controller' => ctrl, 'action' => 'show_page', 'id' => '10'}, rs.recognize_path(%w(page 10))) - end - - def test_action_expiry - assert_equal ['/content', []], rs.generate({:controller => 'content'}, {:controller => 'content', :action => 'show'}) - end - - def test_recognition_with_uppercase_controller_name - assert_equal({'controller' => ::ContentController, 'action' => 'index'}, rs.recognize_path(%w(Content))) - assert_equal({'controller' => ::ContentController, 'action' => 'list'}, rs.recognize_path(%w(Content list))) - assert_equal({'controller' => ::ContentController, 'action' => 'show', 'id' => '10'}, rs.recognize_path(%w(Content show 10))) - - assert_equal({'controller' => ::Admin::NewsFeedController, 'action' => 'index'}, rs.recognize_path(%w(Admin NewsFeed))) - assert_equal({'controller' => ::Admin::NewsFeedController, 'action' => 'index'}, rs.recognize_path(%w(Admin News_Feed))) - end - - def test_both_requirement_and_optional - rs.draw do - rs.blog('test/:year', :controller => 'post', :action => 'show', - :defaults => { :year => nil }, - :requirements => { :year => /\d{4}/ } - ) - rs.connect ':controller/:action/:id' - end - - assert_equal ['/test', []], rs.generate(:controller => 'post', :action => 'show') - assert_equal ['/test', []], rs.generate(:controller => 'post', :action => 'show', :year => nil) - - x = setup_for_named_route - assert_equal({:controller => '/post', :action => 'show'}, - x.new.send(:blog_url)) - end - - def test_set_to_nil_forgets - rs.draw do - rs.connect 'pages/:year/:month/:day', :controller => 'content', :action => 'list_pages', :month => nil, :day => nil - rs.connect ':controller/:action/:id' - end - - assert_equal ['/pages/2005', []], - rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005) - assert_equal ['/pages/2005/6', []], - rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005, :month => 6) - assert_equal ['/pages/2005/6/12', []], - rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005, :month => 6, :day => 12) - - assert_equal ['/pages/2005/6/4', []], - rs.generate({:day => 4}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'}) - - assert_equal ['/pages/2005/6', []], - rs.generate({:day => nil}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'}) - - assert_equal ['/pages/2005', []], - rs.generate({:day => nil, :month => nil}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'}) - end - - def test_url_with_no_action_specified - rs.draw do - rs.connect '', :controller => 'content' - rs.connect ':controller/:action/:id' - end - - assert_equal ['/', []], rs.generate(:controller => 'content', :action => 'index') - assert_equal ['/', []], rs.generate(:controller => 'content') - end - - def test_named_url_with_no_action_specified - rs.draw do - rs.root '', :controller => 'content' - rs.connect ':controller/:action/:id' - end - - assert_equal ['/', []], rs.generate(:controller => 'content', :action => 'index') - assert_equal ['/', []], rs.generate(:controller => 'content') - - x = setup_for_named_route - assert_equal({:controller => '/content', :action => 'index'}, - x.new.send(:root_url)) - end - - def test_url_generated_when_forgetting_action - [{:controller => 'content', :action => 'index'}, {:controller => 'content'}].each do |hash| - rs.draw do - rs.root '', hash - rs.connect ':controller/:action/:id' - end - assert_equal ['/', []], rs.generate({:action => nil}, {:controller => 'content', :action => 'hello'}) - assert_equal ['/', []], rs.generate({:controller => 'content'}) - assert_equal ['/content/hi', []], rs.generate({:controller => 'content', :action => 'hi'}) - end - end - - def test_named_route_method - rs.draw do - assert_raises(ArgumentError) { rs.categories 'categories', :controller => 'content', :action => 'categories' } - - rs.named_route :categories, 'categories', :controller => 'content', :action => 'categories' - rs.connect ':controller/:action/:id' - end - - assert_equal ['/categories', []], rs.generate(:controller => 'content', :action => 'categories') - assert_equal ['/content/hi', []], rs.generate({:controller => 'content', :action => 'hi'}) - end - - def test_named_route_helper_array - test_named_route_method - assert_equal [:categories_url, :hash_for_categories_url], ::ActionController::Routing::NamedRoutes::Helpers - end - - def test_nil_defaults - rs.draw do - rs.connect 'journal', - :controller => 'content', - :action => 'list_journal', - :date => nil, :user_id => nil - rs.connect ':controller/:action/:id' - end - - assert_equal ['/journal', []], rs.generate(:controller => 'content', :action => 'list_journal', :date => nil, :user_id => nil) - end -end - -class ControllerComponentTest < Test::Unit::TestCase - - def test_traverse_to_controller_should_not_load_arbitrary_files - load_path = $:.dup - base = File.dirname(File.dirname(File.expand_path(__FILE__))) - $: << File.join(base, 'fixtures') - Object.send :const_set, :RAILS_ROOT, File.join(base, 'fixtures/application_root') - assert_equal nil, ActionController::Routing::ControllerComponent.traverse_to_controller(%w(dont_load pretty please)) - ensure - $:[0..-1] = load_path - Object.send :remove_const, :RAILS_ROOT - end - - def test_traverse_should_not_trip_on_non_module_constants - assert_equal nil, ActionController::Routing::ControllerComponent.traverse_to_controller(%w(admin some_constant a)) - end - - # This is evil, but people do it. - def test_traverse_to_controller_should_pass_thru_classes - load_path = $:.dup - base = File.dirname(File.dirname(File.expand_path(__FILE__))) - $: << File.join(base, 'fixtures') - $: << File.join(base, 'fixtures/application_root/app/controllers') - $: << File.join(base, 'fixtures/application_root/app/models') - Object.send :const_set, :RAILS_ROOT, File.join(base, 'fixtures/application_root') - pair = ActionController::Routing::ControllerComponent.traverse_to_controller(%w(a_class_that_contains_a_controller poorly_placed)) - - # Make sure the container class was loaded properly - assert defined?(AClassThatContainsAController) - assert_kind_of Class, AClassThatContainsAController - assert_equal :you_know_it, AClassThatContainsAController.is_special? - - # Make sure the controller was too - assert_kind_of Array, pair - assert_equal 2, pair[1] - klass = pair.first - assert_kind_of Class, klass - assert_equal :decidedly_so, klass.is_evil? - assert klass.ancestors.include?(ActionController::Base) - assert defined?(AClassThatContainsAController::PoorlyPlacedController) - assert_equal klass, AClassThatContainsAController::PoorlyPlacedController - ensure - $:[0..-1] = load_path - Object.send :remove_const, :RAILS_ROOT - end - - def test_traverse_to_nested_controller - load_path = $:.dup - base = File.dirname(File.dirname(File.expand_path(__FILE__))) - $: << File.join(base, 'fixtures') - $: << File.join(base, 'fixtures/application_root/app/controllers') - Object.send :const_set, :RAILS_ROOT, File.join(base, 'fixtures/application_root') - pair = ActionController::Routing::ControllerComponent.traverse_to_controller(%w(module_that_holds_controllers nested)) - - assert_not_equal nil, pair - - # Make sure that we created a module for the dir - assert defined?(ModuleThatHoldsControllers) - assert_kind_of Module, ModuleThatHoldsControllers - - # Make sure the controller is ok - assert_kind_of Array, pair - assert_equal 2, pair[1] - klass = pair.first - assert_kind_of Class, klass - assert klass.ancestors.include?(ActionController::Base) - assert defined?(ModuleThatHoldsControllers::NestedController) - assert_equal klass, ModuleThatHoldsControllers::NestedController - ensure - $:[0..-1] = load_path - Object.send :remove_const, :RAILS_ROOT - end - -end - -end -require File.join(File.dirname(__FILE__), '..', 'abstract_unit') - - -module TestFileUtils - def file_name() File.basename(__FILE__) end - def file_path() File.expand_path(__FILE__) end - def file_data() File.open(file_path, 'rb') { |f| f.read } end -end - - -class SendFileController < ActionController::Base - include TestFileUtils - layout "layouts/standard" # to make sure layouts don't interfere - - attr_writer :options - def options() @options ||= {} end - - def file() send_file(file_path, options) end - def data() send_data(file_data, options) end - - def rescue_action(e) raise end -end - -SendFileController.template_root = File.dirname(__FILE__) + "/../fixtures/" - -class SendFileTest < Test::Unit::TestCase - include TestFileUtils - - def setup - @controller = SendFileController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end - - def test_file_nostream - @controller.options = { :stream => false } - response = nil - assert_nothing_raised { response = process('file') } - assert_not_nil response - assert_kind_of String, response.body - assert_equal file_data, response.body - end - - def test_file_stream - response = nil - assert_nothing_raised { response = process('file') } - assert_not_nil response - assert_kind_of Proc, response.body - - require 'stringio' - output = StringIO.new - output.binmode - assert_nothing_raised { response.body.call(response, output) } - assert_equal file_data, output.string - end - - def test_data - response = nil - assert_nothing_raised { response = process('data') } - assert_not_nil response - - assert_kind_of String, response.body - assert_equal file_data, response.body - end - - # Test that send_file_headers! is setting the correct HTTP headers. - def test_send_file_headers! - options = { - :length => 1, - :type => 'type', - :disposition => 'disposition', - :filename => 'filename' - } - - # Do it a few times: the resulting headers should be identical - # no matter how many times you send with the same options. - # Test resolving Ticket #458. - @controller.headers = {} - @controller.send(:send_file_headers!, options) - @controller.send(:send_file_headers!, options) - @controller.send(:send_file_headers!, options) - - h = @controller.headers - assert_equal 1, h['Content-Length'] - assert_equal 'type', h['Content-Type'] - assert_equal 'disposition; filename="filename"', h['Content-Disposition'] - assert_equal 'binary', h['Content-Transfer-Encoding'] - - # test overriding Cache-Control: no-cache header to fix IE open/save dialog - @controller.headers = { 'Cache-Control' => 'no-cache' } - @controller.send(:send_file_headers!, options) - h = @controller.headers - assert_equal 'private', h['Cache-Control'] - end - - %w(file data).each do |method| - define_method "test_send_#{method}_status" do - @controller.options = { :stream => false, :status => 500 } - assert_nothing_raised { assert_not_nil process(method) } - assert_equal '500', @controller.headers['Status'] - end - - define_method "test_default_send_#{method}_status" do - @controller.options = { :stream => false } - assert_nothing_raised { assert_not_nil process(method) } - assert_equal ActionController::Base::DEFAULT_RENDER_STATUS_CODE, @controller.headers['Status'] - end - end -end -require File.dirname(__FILE__) + '/../abstract_unit' - -class SessionManagementTest < Test::Unit::TestCase - class SessionOffController < ActionController::Base - session :off - - def show - render_text "done" - end - - def tell - render_text "done" - end - end - - class TestController < ActionController::Base - session :off, :only => :show - session :session_secure => true, :except => :show - session :off, :only => :conditional, - :if => Proc.new { |r| r.parameters[:ws] } - - def show - render_text "done" - end - - def tell - render_text "done" - end - - def conditional - render_text ">>>#{params[:ws]}<<<" - end - end - - class SpecializedController < SessionOffController - session :disabled => false, :only => :something - - def something - render_text "done" - end - - def another - render_text "done" - end - end - - def setup - @request, @response = ActionController::TestRequest.new, - ActionController::TestResponse.new - end - - def test_session_off_globally - @controller = SessionOffController.new - get :show - assert_equal false, @request.session_options - get :tell - assert_equal false, @request.session_options - end - - def test_session_off_conditionally - @controller = TestController.new - get :show - assert_equal false, @request.session_options - get :tell - assert_instance_of Hash, @request.session_options - assert @request.session_options[:session_secure] - end - - def test_controller_specialization_overrides_settings - @controller = SpecializedController.new - get :something - assert_instance_of Hash, @request.session_options - get :another - assert_equal false, @request.session_options - end - - def test_session_off_with_if - @controller = TestController.new - get :conditional - assert_instance_of Hash, @request.session_options - get :conditional, :ws => "ws" - assert_equal false, @request.session_options - end - - def test_session_store_setting - ActionController::Base.session_store = :drb_store - assert_equal CGI::Session::DRbStore, ActionController::Base.session_store - - if Object.const_defined?(:ActiveRecord) - ActionController::Base.session_store = :active_record_store - assert_equal CGI::Session::ActiveRecordStore, ActionController::Base.session_store - end - end -end -require File.dirname(__FILE__) + '/../abstract_unit' -require File.dirname(__FILE__) + '/fake_controllers' - -class TestTest < Test::Unit::TestCase - class TestController < ActionController::Base - def set_flash - flash["test"] = ">#{flash["test"]}<" - render :text => 'ignore me' - end - - def render_raw_post - raise Test::Unit::AssertionFailedError, "#raw_post is blank" if request.raw_post.blank? - render :text => request.raw_post - end - - def test_params - render :text => params.inspect - end - - def test_uri - render :text => request.request_uri - end - - def test_html_output - render :text => < - - -
    -
      -
    • hello
    • -
    • goodbye
    • -
    -
    -
    -
    - Name: -
    -
    - - -HTML - end - - def test_only_one_param - render :text => (params[:left] && params[:right]) ? "EEP, Both here!" : "OK" - end - - def test_remote_addr - render :text => (request.remote_addr || "not specified") - end - - def test_file_upload - render :text => params[:file].size - end - - def redirect_to_symbol - redirect_to :generate_url, :id => 5 - end - - private - - def rescue_action(e) - raise e - end - - def generate_url(opts) - url_for(opts.merge(:action => "test_uri")) - end - end - - def setup - @controller = TestController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - ActionController::Routing::Routes.reload - end - - def teardown - ActionController::Routing::Routes.reload - end - - def test_raw_post_handling - params = {:page => {:name => 'page name'}, 'some key' => 123} - get :render_raw_post, params.dup - - raw_post = params.map {|k,v| [CGI::escape(k.to_s), CGI::escape(v.to_s)].join('=')}.sort.join('&') - assert_equal raw_post, @response.body - end - - def test_process_without_flash - process :set_flash - assert_equal '><', flash['test'] - end - - def test_process_with_flash - process :set_flash, nil, nil, { "test" => "value" } - assert_equal '>value<', flash['test'] - end - - def test_process_with_request_uri_with_no_params - process :test_uri - assert_equal "/test_test/test/test_uri", @response.body - end - - def test_process_with_request_uri_with_params - process :test_uri, :id => 7 - assert_equal "/test_test/test/test_uri/7", @response.body - end - - def test_process_with_request_uri_with_params_with_explicit_uri - @request.set_REQUEST_URI "/explicit/uri" - process :test_uri, :id => 7 - assert_equal "/explicit/uri", @response.body - end - - def test_multiple_calls - process :test_only_one_param, :left => true - assert_equal "OK", @response.body - process :test_only_one_param, :right => true - assert_equal "OK", @response.body - end - - def test_assert_tag_tag - process :test_html_output - - # there is a 'form' tag - assert_tag :tag => 'form' - # there is not an 'hr' tag - assert_no_tag :tag => 'hr' - end - - def test_assert_tag_attributes - process :test_html_output - - # there is a tag with an 'id' of 'bar' - assert_tag :attributes => { :id => "bar" } - # there is no tag with a 'name' of 'baz' - assert_no_tag :attributes => { :name => "baz" } - end - - def test_assert_tag_parent - process :test_html_output - - # there is a tag with a parent 'form' tag - assert_tag :parent => { :tag => "form" } - # there is no tag with a parent of 'input' - assert_no_tag :parent => { :tag => "input" } - end - - def test_assert_tag_child - process :test_html_output - - # there is a tag with a child 'input' tag - assert_tag :child => { :tag => "input" } - # there is no tag with a child 'strong' tag - assert_no_tag :child => { :tag => "strong" } - end - - def test_assert_tag_ancestor - process :test_html_output - - # there is a 'li' tag with an ancestor having an id of 'foo' - assert_tag :ancestor => { :attributes => { :id => "foo" } }, :tag => "li" - # there is no tag of any kind with an ancestor having an href matching 'foo' - assert_no_tag :ancestor => { :attributes => { :href => /foo/ } } - end - - def test_assert_tag_descendant - process :test_html_output - - # there is a tag with a decendant 'li' tag - assert_tag :descendant => { :tag => "li" } - # there is no tag with a descendant 'html' tag - assert_no_tag :descendant => { :tag => "html" } - end - - def test_assert_tag_sibling - process :test_html_output - - # there is a tag with a sibling of class 'item' - assert_tag :sibling => { :attributes => { :class => "item" } } - # there is no tag with a sibling 'ul' tag - assert_no_tag :sibling => { :tag => "ul" } - end - - def test_assert_tag_after - process :test_html_output - - # there is a tag following a sibling 'div' tag - assert_tag :after => { :tag => "div" } - # there is no tag following a sibling tag with id 'bar' - assert_no_tag :after => { :attributes => { :id => "bar" } } - end - - def test_assert_tag_before - process :test_html_output - - # there is a tag preceeding a tag with id 'bar' - assert_tag :before => { :attributes => { :id => "bar" } } - # there is no tag preceeding a 'form' tag - assert_no_tag :before => { :tag => "form" } - end - - def test_assert_tag_children_count - process :test_html_output - - # there is a tag with 2 children - assert_tag :children => { :count => 2 } - # there is no tag with 4 children - assert_no_tag :children => { :count => 4 } - end - - def test_assert_tag_children_less_than - process :test_html_output - - # there is a tag with less than 5 children - assert_tag :children => { :less_than => 5 } - # there is no 'ul' tag with less than 2 children - assert_no_tag :children => { :less_than => 2 }, :tag => "ul" - end - - def test_assert_tag_children_greater_than - process :test_html_output - - # there is a 'body' tag with more than 1 children - assert_tag :children => { :greater_than => 1 }, :tag => "body" - # there is no tag with more than 10 children - assert_no_tag :children => { :greater_than => 10 } - end - - def test_assert_tag_children_only - process :test_html_output - - # there is a tag containing only one child with an id of 'foo' - assert_tag :children => { :count => 1, - :only => { :attributes => { :id => "foo" } } } - # there is no tag containing only one 'li' child - assert_no_tag :children => { :count => 1, :only => { :tag => "li" } } - end - - def test_assert_tag_content - process :test_html_output - - # the output contains the string "Name" - assert_tag :content => "Name" - # the output does not contain the string "test" - assert_no_tag :content => "test" - end - - def test_assert_tag_multiple - process :test_html_output - - # there is a 'div', id='bar', with an immediate child whose 'action' - # attribute matches the regexp /somewhere/. - assert_tag :tag => "div", :attributes => { :id => "bar" }, - :child => { :attributes => { :action => /somewhere/ } } - - # there is no 'div', id='foo', with a 'ul' child with more than - # 2 "li" children. - assert_no_tag :tag => "div", :attributes => { :id => "foo" }, - :child => { - :tag => "ul", - :children => { :greater_than => 2, - :only => { :tag => "li" } } } - end - - def test_assert_tag_children_without_content - process :test_html_output - - # there is a form tag with an 'input' child which is a self closing tag - assert_tag :tag => "form", - :children => { :count => 1, - :only => { :tag => "input" } } - - # the body tag has an 'a' child which in turn has an 'img' child - assert_tag :tag => "body", - :children => { :count => 1, - :only => { :tag => "a", - :children => { :count => 1, - :only => { :tag => "img" } } } } - end - - def test_assert_generates - assert_generates 'controller/action/5', :controller => 'controller', :action => 'action', :id => '5' - end - - def test_assert_routing - assert_routing 'content', :controller => 'content', :action => 'index' - end - - def test_assert_routing_in_module - assert_routing 'admin/user', :controller => 'admin/user', :action => 'index' - end - - def test_params_passing - get :test_params, :page => {:name => "Page name", :month => '4', :year => '2004', :day => '6'} - parsed_params = eval(@response.body) - assert_equal( - {'controller' => 'test_test/test', 'action' => 'test_params', - 'page' => {'name' => "Page name", 'month' => '4', 'year' => '2004', 'day' => '6'}}, - parsed_params - ) - end - - def test_id_converted_to_string - get :test_params, :id => 20, :foo => Object.new - assert_kind_of String, @request.path_parameters['id'] - end - - def test_array_path_parameter_handled_properly - with_routing do |set| - set.draw do - set.connect 'file/*path', :controller => 'test_test/test', :action => 'test_params' - set.connect ':controller/:action/:id' - end - - get :test_params, :path => ['hello', 'world'] - assert_equal ['hello', 'world'], @request.path_parameters['path'] - assert_equal 'hello/world', @request.path_parameters['path'].to_s - end - end - - def test_assert_realistic_path_parameters - get :test_params, :id => 20, :foo => Object.new - - # All elements of path_parameters should use string keys - @request.path_parameters.keys.each do |key| - assert_kind_of String, key - end - end - - def test_with_routing_places_routes_back - assert ActionController::Routing::Routes - routes_id = ActionController::Routing::Routes.object_id - - begin - with_routing { raise 'fail' } - fail 'Should not be here.' - rescue RuntimeError - end - - assert ActionController::Routing::Routes - assert_equal routes_id, ActionController::Routing::Routes.object_id - end - - def test_remote_addr - get :test_remote_addr - assert_equal "0.0.0.0", @response.body - - @request.remote_addr = "192.0.0.1" - get :test_remote_addr - assert_equal "192.0.0.1", @response.body - end - - def test_header_properly_reset_after_remote_http_request - xhr :get, :test_params - assert_nil @request.env['HTTP_X_REQUESTED_WITH'] - end - - def test_header_properly_reset_after_get_request - get :test_params - @request.recycle! - assert_nil @request.instance_variable_get("@request_method") - end - - %w(controller response request).each do |variable| - %w(get post put delete head process).each do |method| - define_method("test_#{variable}_missing_for_#{method}_raises_error") do - remove_instance_variable "@#{variable}" - begin - send(method, :test_remote_addr) - assert false, "expected RuntimeError, got nothing" - rescue RuntimeError => error - assert true - assert_match %r{@#{variable} is nil}, error.message - rescue => error - assert false, "expected RuntimeError, got #{error.class}" - end - end - end - end - - FILES_DIR = File.dirname(__FILE__) + '/../fixtures/multipart' - - def test_test_uploaded_file - filename = 'mona_lisa.jpg' - path = "#{FILES_DIR}/#{filename}" - content_type = 'image/png' - - file = ActionController::TestUploadedFile.new(path, content_type) - assert_equal filename, file.original_filename - assert_equal content_type, file.content_type - assert_equal file.path, file.local_path - assert_equal File.read(path), file.read - end - - def test_fixture_file_upload - post :test_file_upload, :file => fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg") - assert_equal 159528, @response.body - end - - def test_test_uploaded_file_exception_when_file_doesnt_exist - assert_raise(RuntimeError) { ActionController::TestUploadedFile.new('non_existent_file') } - end - - def test_assert_redirected_to_symbol - get :redirect_to_symbol - assert_redirected_to :generate_url - end -end -require File.dirname(__FILE__) + '/../abstract_unit' - -class UrlRewriterTests < Test::Unit::TestCase - def setup - @request = ActionController::TestRequest.new - @params = {} - @rewriter = ActionController::UrlRewriter.new(@request, @params) - end - - def test_simple_build_query_string - assert_query_equal '?x=1&y=2', @rewriter.send(:build_query_string, :x => '1', :y => '2') - end - def test_convert_ints_build_query_string - assert_query_equal '?x=1&y=2', @rewriter.send(:build_query_string, :x => 1, :y => 2) - end - def test_escape_spaces_build_query_string - assert_query_equal '?x=hello+world&y=goodbye+world', @rewriter.send(:build_query_string, :x => 'hello world', :y => 'goodbye world') - end - def test_expand_array_build_query_string - assert_query_equal '?x[]=1&x[]=2', @rewriter.send(:build_query_string, :x => [1, 2]) - end - - def test_escape_spaces_build_query_string_selected_keys - assert_query_equal '?x=hello+world', @rewriter.send(:build_query_string, {:x => 'hello world', :y => 'goodbye world'}, [:x]) - end - - def test_overwrite_params - @params[:controller] = 'hi' - @params[:action] = 'bye' - @params[:id] = '2' - - assert_equal '/hi/hi/2', @rewriter.rewrite(:only_path => true, :overwrite_params => {:action => 'hi'}) - u = @rewriter.rewrite(:only_path => false, :overwrite_params => {:action => 'hi'}) - assert_match %r(/hi/hi/2$), u - end - - - private - def split_query_string(str) - [str[0].chr] + str[1..-1].split(/&/).sort - end - - def assert_query_equal(q1, q2) - assert_equal(split_query_string(q1), split_query_string(q2)) - end -end -require File.dirname(__FILE__) + '/../abstract_unit' - -class VerificationTest < Test::Unit::TestCase - class TestController < ActionController::Base - verify :only => :guarded_one, :params => "one", - :redirect_to => { :action => "unguarded" } - - verify :only => :guarded_two, :params => %w( one two ), - :redirect_to => { :action => "unguarded" } - - verify :only => :guarded_with_flash, :params => "one", - :add_flash => { "notice" => "prereqs failed" }, - :redirect_to => { :action => "unguarded" } - - verify :only => :guarded_in_session, :session => "one", - :redirect_to => { :action => "unguarded" } - - verify :only => [:multi_one, :multi_two], :session => %w( one two ), - :redirect_to => { :action => "unguarded" } - - verify :only => :guarded_by_method, :method => :post, - :redirect_to => { :action => "unguarded" } - - verify :only => :guarded_by_xhr, :xhr => true, - :redirect_to => { :action => "unguarded" } - - verify :only => :guarded_by_not_xhr, :xhr => false, - :redirect_to => { :action => "unguarded" } - - before_filter :unconditional_redirect, :only => :two_redirects - verify :only => :two_redirects, :method => :post, - :redirect_to => { :action => "unguarded" } - - verify :only => :must_be_post, :method => :post, :render => { :status => 500, :text => "Must be post"} - - def guarded_one - render :text => "#{@params["one"]}" - end - - def guarded_with_flash - render :text => "#{@params["one"]}" - end - - def guarded_two - render :text => "#{@params["one"]}:#{@params["two"]}" - end - - def guarded_in_session - render :text => "#{@session["one"]}" - end - - def multi_one - render :text => "#{@session["one"]}:#{@session["two"]}" - end - - def multi_two - render :text => "#{@session["two"]}:#{@session["one"]}" - end - - def guarded_by_method - render :text => "#{@request.method}" - end - - def guarded_by_xhr - render :text => "#{@request.xhr?}" - end - - def guarded_by_not_xhr - render :text => "#{@request.xhr?}" - end - - def unguarded - render :text => "#{@params["one"]}" - end - - def two_redirects - render :nothing => true - end - - def must_be_post - render :text => "Was a post!" - end - - protected - def rescue_action(e) raise end - - def unconditional_redirect - redirect_to :action => "unguarded" - end - end - - def setup - @controller = TestController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end - - def test_guarded_one_with_prereqs - get :guarded_one, :one => "here" - assert_equal "here", @response.body - end - - def test_guarded_one_without_prereqs - get :guarded_one - assert_redirected_to :action => "unguarded" - end - - def test_guarded_with_flash_with_prereqs - get :guarded_with_flash, :one => "here" - assert_equal "here", @response.body - assert_flash_empty - end - - def test_guarded_with_flash_without_prereqs - get :guarded_with_flash - assert_redirected_to :action => "unguarded" - assert_flash_equal "prereqs failed", "notice" - end - - def test_guarded_two_with_prereqs - get :guarded_two, :one => "here", :two => "there" - assert_equal "here:there", @response.body - end - - def test_guarded_two_without_prereqs_one - get :guarded_two, :two => "there" - assert_redirected_to :action => "unguarded" - end - - def test_guarded_two_without_prereqs_two - get :guarded_two, :one => "here" - assert_redirected_to :action => "unguarded" - end - - def test_guarded_two_without_prereqs_both - get :guarded_two - assert_redirected_to :action => "unguarded" - end - - def test_unguarded_with_params - get :unguarded, :one => "here" - assert_equal "here", @response.body - end - - def test_unguarded_without_params - get :unguarded - assert_equal "", @response.body - end - - def test_guarded_in_session_with_prereqs - get :guarded_in_session, {}, "one" => "here" - assert_equal "here", @response.body - end - - def test_guarded_in_session_without_prereqs - get :guarded_in_session - assert_redirected_to :action => "unguarded" - end - - def test_multi_one_with_prereqs - get :multi_one, {}, "one" => "here", "two" => "there" - assert_equal "here:there", @response.body - end - - def test_multi_one_without_prereqs - get :multi_one - assert_redirected_to :action => "unguarded" - end - - def test_multi_two_with_prereqs - get :multi_two, {}, "one" => "here", "two" => "there" - assert_equal "there:here", @response.body - end - - def test_multi_two_without_prereqs - get :multi_two - assert_redirected_to :action => "unguarded" - end - - def test_guarded_by_method_with_prereqs - post :guarded_by_method - assert_equal "post", @response.body - end - - def test_guarded_by_method_without_prereqs - get :guarded_by_method - assert_redirected_to :action => "unguarded" - end - - def test_guarded_by_xhr_with_prereqs - xhr :post, :guarded_by_xhr - assert_equal "true", @response.body - end - - def test_guarded_by_xhr_without_prereqs - get :guarded_by_xhr - assert_redirected_to :action => "unguarded" - end - - def test_guarded_by_not_xhr_with_prereqs - get :guarded_by_not_xhr - assert_equal "false", @response.body - end - - def test_guarded_by_not_xhr_without_prereqs - xhr :post, :guarded_by_not_xhr - assert_redirected_to :action => "unguarded" - end - - def test_guarded_post_and_calls_render_succeeds - post :must_be_post - assert_equal "Was a post!", @response.body - end - - def test_guarded_post_and_calls_render_fails - get :must_be_post - assert_response 500 - assert_equal "Must be post", @response.body - end - - - def test_second_redirect - assert_nothing_raised { get :two_redirects } - end -end -require File.dirname(__FILE__) + '/../abstract_unit' -require 'stringio' - -class WebServiceTest < Test::Unit::TestCase - - class MockCGI < CGI #:nodoc: - attr_accessor :stdinput, :stdoutput, :env_table - - def initialize(env, data = '') - self.env_table = env - self.stdinput = StringIO.new(data) - self.stdoutput = StringIO.new - super() - end - end - - - class TestController < ActionController::Base - session :off - - def assign_parameters - if params[:full] - render :text => dump_params_keys - else - render :text => (params.keys - ['controller', 'action']).sort.join(", ") - end - end - - def dump_params_keys(hash=params) - hash.keys.sort.inject("") do |s, k| - value = hash[k] - value = Hash === value ? "(#{dump_params_keys(value)})" : "" - s << ", " unless s.empty? - s << "#{k}#{value}" - end - end - - def rescue_action(e) raise end - end - - def setup - @controller = TestController.new - ActionController::Base.param_parsers.clear - ActionController::Base.param_parsers[Mime::XML] = :xml_node - end - - def test_check_parameters - process('GET') - assert_equal '', @controller.response.body - end - - def test_post_xml - process('POST', 'application/xml', 'content...') - - assert_equal 'entry', @controller.response.body - assert @controller.params.has_key?(:entry) - assert_equal 'content...', @controller.params["entry"].summary.node_value - assert_equal 'true', @controller.params["entry"]['attributed'] - end - - def test_put_xml - process('PUT', 'application/xml', 'content...') - - assert_equal 'entry', @controller.response.body - assert @controller.params.has_key?(:entry) - assert_equal 'content...', @controller.params["entry"].summary.node_value - assert_equal 'true', @controller.params["entry"]['attributed'] - end - - def test_register_and_use_yaml - ActionController::Base.param_parsers[Mime::YAML] = Proc.new { |d| YAML.load(d) } - process('POST', 'application/x-yaml', {"entry" => "loaded from yaml"}.to_yaml) - assert_equal 'entry', @controller.response.body - assert @controller.params.has_key?(:entry) - assert_equal 'loaded from yaml', @controller.params["entry"] - end - - def test_register_and_use_yaml_as_symbol - ActionController::Base.param_parsers[Mime::YAML] = :yaml - process('POST', 'application/x-yaml', {"entry" => "loaded from yaml"}.to_yaml) - assert_equal 'entry', @controller.response.body - assert @controller.params.has_key?(:entry) - assert_equal 'loaded from yaml', @controller.params["entry"] - end - - def test_register_and_use_xml_simple - ActionController::Base.param_parsers[Mime::XML] = Proc.new { |data| XmlSimple.xml_in(data, 'ForceArray' => false) } - process('POST', 'application/xml', 'content...SimpleXml' ) - assert_equal 'summary, title', @controller.response.body - assert @controller.params.has_key?(:summary) - assert @controller.params.has_key?(:title) - assert_equal 'content...', @controller.params["summary"] - assert_equal 'SimpleXml', @controller.params["title"] - end - - def test_use_xml_ximple_with_empty_request - ActionController::Base.param_parsers[Mime::XML] = :xml_simple - assert_nothing_raised { process('POST', 'application/xml', "") } - assert_equal "", @controller.response.body - end - - def test_deprecated_request_methods - process('POST', 'application/x-yaml') - assert_equal Mime::YAML, @controller.request.content_type - assert_equal true, @controller.request.post? - assert_equal :yaml, @controller.request.post_format - assert_equal true, @controller.request.yaml_post? - assert_equal false, @controller.request.xml_post? - end - - def test_dasherized_keys_as_xml - ActionController::Base.param_parsers[Mime::XML] = :xml_simple - process('POST', 'application/xml', "\n...\n", true) - assert_equal 'action, controller, first_key(sub_key), full', @controller.response.body - assert_equal "...", @controller.params[:first_key][:sub_key] - end - - def test_typecast_as_xml - ActionController::Base.param_parsers[Mime::XML] = :xml_simple - process('POST', 'application/xml', <<-XML) - - 15 - false - true - 2005-03-17 - 2005-03-17T21:41:07Z - unparsed - 1 - hello - 1974-07-25 - - XML - params = @controller.params - assert_equal 15, params[:data][:a] - assert_equal false, params[:data][:b] - assert_equal true, params[:data][:c] - assert_equal Date.new(2005,3,17), params[:data][:d] - assert_equal Time.utc(2005,3,17,21,41,7), params[:data][:e] - assert_equal "unparsed", params[:data][:f] - assert_equal [1, "hello", Date.new(1974,7,25)], params[:data][:g] - end - - def test_entities_unescaped_as_xml_simple - ActionController::Base.param_parsers[Mime::XML] = :xml_simple - process('POST', 'application/xml', <<-XML) - <foo "bar's" & friends> - XML - assert_equal %(), @controller.params[:data] - end - - def test_dasherized_keys_as_yaml - ActionController::Base.param_parsers[Mime::YAML] = :yaml - process('POST', 'application/x-yaml', "---\nfirst-key:\n sub-key: ...\n", true) - assert_equal 'action, controller, first_key(sub_key), full', @controller.response.body - assert_equal "...", @controller.params[:first_key][:sub_key] - end - - def test_typecast_as_yaml - ActionController::Base.param_parsers[Mime::YAML] = :yaml - process('POST', 'application/x-yaml', <<-YAML) - --- - data: - a: 15 - b: false - c: true - d: 2005-03-17 - e: 2005-03-17T21:41:07Z - f: unparsed - g: - - 1 - - hello - - 1974-07-25 - YAML - params = @controller.params - assert_equal 15, params[:data][:a] - assert_equal false, params[:data][:b] - assert_equal true, params[:data][:c] - assert_equal Date.new(2005,3,17), params[:data][:d] - assert_equal Time.utc(2005,3,17,21,41,7), params[:data][:e] - assert_equal "unparsed", params[:data][:f] - assert_equal [1, "hello", Date.new(1974,7,25)], params[:data][:g] - end - - private - - def process(verb, content_type = 'application/x-www-form-urlencoded', data = '', full=false) - - cgi = MockCGI.new({ - 'REQUEST_METHOD' => verb, - 'CONTENT_TYPE' => content_type, - 'QUERY_STRING' => "action=assign_parameters&controller=webservicetest/test#{"&full=1" if full}", - "REQUEST_URI" => "/", - "HTTP_HOST" => 'testdomain.com', - "CONTENT_LENGTH" => data.size, - "SERVER_PORT" => "80", - "HTTPS" => "off"}, data) - - @controller.send(:process, ActionController::CgiRequest.new(cgi, {}), ActionController::CgiResponse.new(cgi)) - end - -end - - -class XmlNodeTest < Test::Unit::TestCase - def test_all - xn = XmlNode.from_xml(%{ - - - With O'Reilly and Adaptive Path - - - Staying at the Savoy - - - - - - - - - } - ) - assert_equal 'UTF-8', xn.node.document.encoding - assert_equal '1.0', xn.node.document.version - assert_equal 'true', xn['success'] - assert_equal 'response', xn.node_name - assert_equal 'Ajax Summit', xn.page['title'] - assert_equal '1133', xn.page['id'] - assert_equal "With O'Reilly and Adaptive Path", xn.page.description.node_value - assert_equal nil, xn.nonexistent - assert_equal "Staying at the Savoy", xn.page.notes.note.node_value.strip - assert_equal 'Technology', xn.page.tags.tag[0]['name'] - assert_equal 'Travel', xn.page.tags.tag[1][:name] - matches = xn.xpath('//@id').map{ |id| id.to_i } - assert_equal [4, 5, 1020, 1133], matches.sort - matches = xn.xpath('//tag').map{ |tag| tag['name'] } - assert_equal ['Technology', 'Travel'], matches.sort - assert_equal "Ajax Summit", xn.page['title'] - xn.page['title'] = 'Ajax Summit V2' - assert_equal "Ajax Summit V2", xn.page['title'] - assert_equal "Staying at the Savoy", xn.page.notes.note.node_value.strip - xn.page.notes.note.node_value = "Staying at the Ritz" - assert_equal "Staying at the Ritz", xn.page.notes.note.node_value.strip - assert_equal '5', xn.page.tags.tag[1][:id] - xn.page.tags.tag[1]['id'] = '7' - assert_equal '7', xn.page.tags.tag[1]['id'] - end - - - def test_small_entry - node = XmlNode.from_xml('hi') - assert_equal 'hi', node.node_value - end - -end -class AClassThatContainsAController::PoorlyPlacedController < ActionController::Base - - def self.is_evil? - :decidedly_so - end - -endclass ModuleThatHoldsControllers::NestedController < ActionController::Base - -endclass AClassThatContainsAController #often < ActiveRecord::Base - - def self.is_special? - :you_know_it - end - -endclass Company < ActiveRecord::Base - attr_protected :rating - set_sequence_name :companies_nonstd_seq - - validates_presence_of :name - def validate - errors.add('rating', 'rating should not be 2') if rating == 2 - end -endclass Developer < ActiveRecord::Base - has_and_belongs_to_many :projects -end - -class DeVeLoPeR < ActiveRecord::Base - set_table_name "developers" -end -# see routing/controller component tests - -raise Exception, "I should never be loaded"module AbcHelper - def bare_a() end - def bare_b() end - def bare_c() end -end -module Fun::GamesHelper - def stratego() "Iz guuut!" end -endmodule Fun::PDFHelper - def foobar() 'baz' end -end -class Project < ActiveRecord::Base - has_and_belongs_to_many :developers, :uniq => true -end -class Reply < ActiveRecord::Base - belongs_to :topic, :include => [:replies] - - validates_presence_of :content -end -class Topic < ActiveRecord::Base - has_many :replies, :include => [:user], :dependent => true -endrequire 'test/unit' -require File.dirname(__FILE__) + '/../../lib/action_view/helpers/date_helper' -require File.dirname(__FILE__) + '/../../lib/action_view/helpers/form_helper' -require File.dirname(__FILE__) + '/../../lib/action_view/helpers/text_helper' -require File.dirname(__FILE__) + '/../../lib/action_view/helpers/tag_helper' -require File.dirname(__FILE__) + '/../../lib/action_view/helpers/url_helper' -require File.dirname(__FILE__) + '/../../lib/action_view/helpers/form_tag_helper' -# require File.dirname(__FILE__) + '/../../lib/action_view/helpers/active_record_helper' - -class ActiveRecordHelperTest < Test::Unit::TestCase - include ActionView::Helpers::FormHelper - include ActionView::Helpers::ActiveRecordHelper - include ActionView::Helpers::TextHelper - include ActionView::Helpers::TagHelper - include ActionView::Helpers::UrlHelper - include ActionView::Helpers::FormTagHelper - - silence_warnings do - Post = Struct.new("Post", :title, :author_name, :body, :secret, :written_on) - Post.class_eval do - alias_method :title_before_type_cast, :title unless respond_to?(:title_before_type_cast) - alias_method :body_before_type_cast, :body unless respond_to?(:body_before_type_cast) - alias_method :author_name_before_type_cast, :author_name unless respond_to?(:author_name_before_type_cast) - end - Column = Struct.new("Column", :type, :name, :human_name) - end - - def setup - @post = Post.new - def @post.errors - Class.new { - def on(field) field == "author_name" || field == "body" end - def empty?() false end - def count() 1 end - def full_messages() [ "Author name can't be empty" ] end - }.new - end - - def @post.new_record?() true end - def @post.to_param() nil end - - def @post.column_for_attribute(attr_name) - Post.content_columns.select { |column| column.name == attr_name }.first - end - - def Post.content_columns() [ Column.new(:string, "title", "Title"), Column.new(:text, "body", "Body") ] end - - @post.title = "Hello World" - @post.author_name = "" - @post.body = "Back to the hill and over it again!" - @post.secret = 1 - @post.written_on = Date.new(2004, 6, 15) - - @controller = Object.new - def @controller.url_for(options, *parameters_for_method_reference) - options = options.symbolize_keys - - [options[:action], options[:id].to_param].compact.join('/') - end - end - - def test_generic_input_tag - assert_dom_equal( - %(), input("post", "title") - ) - end - - def test_text_area_with_errors - assert_dom_equal( - %(
    ), - text_area("post", "body") - ) - end - - def test_text_field_with_errors - assert_dom_equal( - %(
    ), - text_field("post", "author_name") - ) - end - - def test_form_with_string - assert_dom_equal( - %(


    \n


    ), - form("post") - ) - - class << @post - def new_record?() false end - def to_param() id end - def id() 1 end - end - assert_dom_equal( - %(


    \n


    ), - form("post") - ) - end - - def test_form_with_date - def Post.content_columns() [ Column.new(:date, "written_on", "Written on") ] end - - assert_dom_equal( - %(


    \n\n\n

    ), - form("post") - ) - end - - def test_form_with_datetime - def Post.content_columns() [ Column.new(:datetime, "written_on", "Written on") ] end - @post.written_on = Time.gm(2004, 6, 15, 16, 30) - - assert_dom_equal( - %(


    \n\n\n — \n : \n

    ), - form("post") - ) - end - - def test_error_for_block - assert_dom_equal %(

    1 error prohibited this post from being saved

    There were problems with the following fields:

    • Author name can't be empty
    ), error_messages_for("post") - assert_equal %(

    1 error prohibited this post from being saved

    There were problems with the following fields:

    • Author name can't be empty
    ), error_messages_for("post", :class => "errorDeathByClass", :id => "errorDeathById", :header_tag => "h1") - end - - def test_error_messages_for_handles_nil - assert_equal "", error_messages_for("notthere") - end - - def test_form_with_string_multipart - assert_dom_equal( - %(


    \n


    ), - form("post", :multipart => true) - ) - end -end -require File.dirname(__FILE__) + '/../abstract_unit' - -class AssetTagHelperTest < Test::Unit::TestCase - include ActionView::Helpers::TagHelper - include ActionView::Helpers::UrlHelper - include ActionView::Helpers::AssetTagHelper - - def setup - @controller = Class.new do - - attr_accessor :request - - def url_for(options, *parameters_for_method_reference) - "http://www.example.com" - end - - end.new - - @request = Class.new do - def relative_url_root - "" - end - end.new - - @controller.request = @request - - ActionView::Helpers::AssetTagHelper::reset_javascript_include_default - end - - def teardown - Object.send(:remove_const, :RAILS_ROOT) if defined?(RAILS_ROOT) - ENV["RAILS_ASSET_ID"] = nil - end - - AutoDiscoveryToTag = { - %(auto_discovery_link_tag) => %(), - %(auto_discovery_link_tag(:atom)) => %(), - %(auto_discovery_link_tag(:rss, :action => "feed")) => %(), - %(auto_discovery_link_tag(:rss, "http://localhost/feed")) => %(), - %(auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"})) => %(), - %(auto_discovery_link_tag(:rss, {}, {:title => "My RSS"})) => %(), - %(auto_discovery_link_tag(nil, {}, {:type => "text/html"})) => %(), - %(auto_discovery_link_tag(nil, {}, {:title => "No stream.. really", :type => "text/html"})) => %(), - %(auto_discovery_link_tag(:rss, {}, {:title => "My RSS", :type => "text/html"})) => %(), - %(auto_discovery_link_tag(:atom, {}, {:rel => "Not so alternate"})) => %(), - } - - JavascriptPathToTag = { - %(javascript_path("xmlhr")) => %(/javascripts/xmlhr.js), - %(javascript_path("super/xmlhr")) => %(/javascripts/super/xmlhr.js) - } - - JavascriptIncludeToTag = { - %(javascript_include_tag("xmlhr")) => %(), - %(javascript_include_tag("xmlhr", :lang => "vbscript")) => %(), - %(javascript_include_tag("common.javascript", "/elsewhere/cools")) => %(\n), - %(javascript_include_tag(:defaults)) => %(\n\n\n), - %(javascript_include_tag(:defaults, "test")) => %(\n\n\n\n), - %(javascript_include_tag("test", :defaults)) => %(\n\n\n\n) - } - - StylePathToTag = { - %(stylesheet_path("style")) => %(/stylesheets/style.css), - %(stylesheet_path('dir/file')) => %(/stylesheets/dir/file.css), - %(stylesheet_path('/dir/file')) => %(/dir/file.css) - } - - StyleLinkToTag = { - %(stylesheet_link_tag("style")) => %(), - %(stylesheet_link_tag("/dir/file")) => %(), - %(stylesheet_link_tag("dir/file")) => %(), - %(stylesheet_link_tag("style", :media => "all")) => %(), - %(stylesheet_link_tag("random.styles", "/css/stylish")) => %(\n) - } - - ImagePathToTag = { - %(image_path("xml")) => %(/images/xml.png), - } - - ImageLinkToTag = { - %(image_tag("xml")) => %(Xml), - %(image_tag("rss", :alt => "rss syndication")) => %(rss syndication), - %(image_tag("gold", :size => "45x70")) => %(Gold), - %(image_tag("symbolize", "size" => "45x70")) => %(Symbolize), - %(image_tag("http://www.rubyonrails.com/images/rails")) => %(Rails) - } - - def test_auto_discovery - AutoDiscoveryToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } - end - - def test_javascript_path - JavascriptPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } - end - - def test_javascript_include - JavascriptIncludeToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } - end - - def test_register_javascript_include_default - ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'slider' - assert_dom_equal %(\n\n\n\n), javascript_include_tag(:defaults) - ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'lib1', '/elsewhere/blub/lib2' - assert_dom_equal %(\n\n\n\n\n\n), javascript_include_tag(:defaults) - end - - def test_style_path - StylePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } - end - - def test_style_link - StyleLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } - end - - def test_image_path - ImagePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } - end - - def test_image_tag - ImageLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } - end - - def test_timebased_asset_id - Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + "/../fixtures/") - expected_time = File.stat(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).mtime.to_i.to_s - assert_equal %(Rails), image_tag("rails.png") - end - - def test_skipping_asset_id_on_complete_url - Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + "/../fixtures/") - assert_equal %(Rails), image_tag("http://www.example.com/rails.png") - end - - def test_preset_asset_id - Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + "/../fixtures/") - ENV["RAILS_ASSET_ID"] = "4500" - assert_equal %(Rails), image_tag("rails.png") - end -end - -class AssetTagHelperNonVhostTest < Test::Unit::TestCase - include ActionView::Helpers::TagHelper - include ActionView::Helpers::UrlHelper - include ActionView::Helpers::AssetTagHelper - - def setup - @controller = Class.new do - - attr_accessor :request - - def url_for(options, *parameters_for_method_reference) - "http://www.example.com/calloboration/hieraki" - end - - end.new - - @request = Class.new do - def relative_url_root - "/calloboration/hieraki" - end - end.new - - @controller.request = @request - - ActionView::Helpers::AssetTagHelper::reset_javascript_include_default - end - - AutoDiscoveryToTag = { - %(auto_discovery_link_tag(:rss, :action => "feed")) => %(), - %(auto_discovery_link_tag(:atom)) => %(), - %(auto_discovery_link_tag) => %(), - } - - JavascriptPathToTag = { - %(javascript_path("xmlhr")) => %(/calloboration/hieraki/javascripts/xmlhr.js), - } - - JavascriptIncludeToTag = { - %(javascript_include_tag("xmlhr")) => %(), - %(javascript_include_tag("common.javascript", "/elsewhere/cools")) => %(\n), - %(javascript_include_tag(:defaults)) => %(\n\n\n) - } - - StylePathToTag = { - %(stylesheet_path("style")) => %(/calloboration/hieraki/stylesheets/style.css), - } - - StyleLinkToTag = { - %(stylesheet_link_tag("style")) => %(), - %(stylesheet_link_tag("random.styles", "/css/stylish")) => %(\n) - } - - ImagePathToTag = { - %(image_path("xml")) => %(/calloboration/hieraki/images/xml.png), - } - - ImageLinkToTag = { - %(image_tag("xml")) => %(Xml), - %(image_tag("rss", :alt => "rss syndication")) => %(rss syndication), - %(image_tag("gold", :size => "45x70")) => %(Gold), - %(image_tag("http://www.example.com/images/icon.gif")) => %(Icon), - %(image_tag("symbolize", "size" => "45x70")) => %(Symbolize) - } - - def test_auto_discovery - AutoDiscoveryToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } - end - - def test_javascript_path - JavascriptPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } - end - - def test_javascript_include - JavascriptIncludeToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } - end - - def test_register_javascript_include_default - ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'slider' - assert_dom_equal %(\n\n\n\n), javascript_include_tag(:defaults) - ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'lib1', '/elsewhere/blub/lib2' - assert_dom_equal %(\n\n\n\n\n\n), javascript_include_tag(:defaults) - end - - def test_style_path - StylePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } - end - - def test_style_link - StyleLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } - end - - def test_image_path - ImagePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } - end - - def test_image_tag - ImageLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } - # Assigning a default alt tag should not cause an exception to be raised - assert_nothing_raised { image_tag('') } - end - - def test_stylesheet_with_asset_host_already_encoded - ActionController::Base.asset_host = "http://foo.example.com" - result = stylesheet_link_tag("http://bar.example.com/stylesheets/style.css") - assert_dom_equal( - %(), - result) - ensure - ActionController::Base.asset_host = "" - end - -end -require 'test/unit' -require File.dirname(__FILE__) + '/../../lib/action_view/helpers/benchmark_helper' - -class BenchmarkHelperTest < Test::Unit::TestCase - include ActionView::Helpers::BenchmarkHelper - - class MockLogger - attr_reader :logged - - def initialize - @logged = [] - end - - def method_missing(method, *args) - @logged << [method, args] - end - end - - def setup - @logger = MockLogger.new - end - - def test_without_logger_or_block - @logger = nil - assert_nothing_raised { benchmark } - end - - def test_without_block - assert_raise(LocalJumpError) { benchmark } - assert @logger.logged.empty? - end - - def test_without_logger - @logger = nil - i_was_run = false - benchmark { i_was_run = true } - assert !i_was_run - end - - def test_defaults - i_was_run = false - benchmark { i_was_run = true } - assert i_was_run - assert 1, @logger.logged.size - assert_last_logged - end - - def test_with_message - i_was_run = false - benchmark('test_run') { i_was_run = true } - assert i_was_run - assert 1, @logger.logged.size - assert_last_logged 'test_run' - end - - def test_with_message_and_level - i_was_run = false - benchmark('debug_run', :debug) { i_was_run = true } - assert i_was_run - assert 1, @logger.logged.size - assert_last_logged 'debug_run', :debug - end - - private - def assert_last_logged(message = 'Benchmarking', level = :info) - last = @logger.logged.last - assert 2, last.size - assert_equal level, last.first - assert 1, last[1].size - assert last[1][0] =~ /^#{message} \(.*\)$/ - end -end -require 'test/unit' -require File.dirname(__FILE__) + '/../../lib/action_view/helpers/date_helper' -require File.dirname(__FILE__) + "/../abstract_unit" - -class CompiledTemplateTests < Test::Unit::TestCase - - def setup - @ct = ActionView::CompiledTemplates.new - @v = Class.new - @v.send :include, @ct - @a = './test_compile_template_a.rhtml' - @b = './test_compile_template_b.rhtml' - @s = './test_compile_template_link.rhtml' - end - def teardown - [@a, @b, @s].each do |f| - `rm #{f}` if File.exist?(f) || File.symlink?(f) - end - end - attr_reader :ct, :v - - def test_name_allocation - hi_world = ct.method_names['hi world'] - hi_sexy = ct.method_names['hi sexy'] - wish_upon_a_star = ct.method_names['I love seeing decent error messages'] - - assert_equal hi_world, ct.method_names['hi world'] - assert_equal hi_sexy, ct.method_names['hi sexy'] - assert_equal wish_upon_a_star, ct.method_names['I love seeing decent error messages'] - assert_equal 3, [hi_world, hi_sexy, wish_upon_a_star].uniq.length - end - - def test_wrap_source - assert_equal( - "def aliased_assignment(value)\nself.value = value\nend", - @ct.wrap_source(:aliased_assignment, [:value], 'self.value = value') - ) - - assert_equal( - "def simple()\nnil\nend", - @ct.wrap_source(:simple, [], 'nil') - ) - end - - def test_compile_source_single_method - selector = ct.compile_source('doubling method', [:a], 'a + a') - assert_equal 2, @v.new.send(selector, 1) - assert_equal 4, @v.new.send(selector, 2) - assert_equal -4, @v.new.send(selector, -2) - assert_equal 0, @v.new.send(selector, 0) - selector - end - - def test_compile_source_two_method - sel1 = test_compile_source_single_method # compile the method in the other test - sel2 = ct.compile_source('doubling method', [:a, :b], 'a + b + a + b') - assert_not_equal sel1, sel2 - - assert_equal 2, @v.new.send(sel1, 1) - assert_equal 4, @v.new.send(sel1, 2) - - assert_equal 6, @v.new.send(sel2, 1, 2) - assert_equal 32, @v.new.send(sel2, 15, 1) - end - - def test_mtime - t1 = Time.now - test_compile_source_single_method - assert (t1..Time.now).include?(ct.mtime('doubling method', [:a])) - end - - def test_compile_time - `echo '#{@a}' > #{@a}; echo '#{@b}' > #{@b}; ln -s #{@a} #{@s}` - - v = ActionView::Base.new - v.base_path = '.' - v.cache_template_loading = false; - - sleep 1 - t = Time.now - v.compile_and_render_template(:rhtml, '', @a) - v.compile_and_render_template(:rhtml, '', @b) - v.compile_and_render_template(:rhtml, '', @s) - a_n = v.method_names[@a] - b_n = v.method_names[@b] - s_n = v.method_names[@s] - # all of the files have changed since last compile - assert v.compile_time[a_n] > t - assert v.compile_time[b_n] > t - assert v.compile_time[s_n] > t - - sleep 1 - t = Time.now - v.compile_and_render_template(:rhtml, '', @a) - v.compile_and_render_template(:rhtml, '', @b) - v.compile_and_render_template(:rhtml, '', @s) - # none of the files have changed since last compile - assert v.compile_time[a_n] < t - assert v.compile_time[b_n] < t - assert v.compile_time[s_n] < t - - `rm #{@s}; ln -s #{@b} #{@s}` - v.compile_and_render_template(:rhtml, '', @a) - v.compile_and_render_template(:rhtml, '', @b) - v.compile_and_render_template(:rhtml, '', @s) - # the symlink has changed since last compile - assert v.compile_time[a_n] < t - assert v.compile_time[b_n] < t - assert v.compile_time[s_n] > t - - sleep 1 - `touch #{@b}` - t = Time.now - v.compile_and_render_template(:rhtml, '', @a) - v.compile_and_render_template(:rhtml, '', @b) - v.compile_and_render_template(:rhtml, '', @s) - # the file at the end of the symlink has changed since last compile - # both the symlink and the file at the end of it should be recompiled - assert v.compile_time[a_n] < t - assert v.compile_time[b_n] > t - assert v.compile_time[s_n] > t - end -end - -module ActionView - class Base - def compile_time - @@compile_time - end - def method_names - @@method_names - end - end -end -require 'test/unit' -require File.dirname(__FILE__) + "/../abstract_unit" - -class DateHelperTest < Test::Unit::TestCase - include ActionView::Helpers::DateHelper - include ActionView::Helpers::FormHelper - - silence_warnings do - Post = Struct.new("Post", :written_on, :updated_at) - end - - def test_distance_in_words - from = Time.mktime(2004, 3, 6, 21, 41, 18) - - assert_equal "less than a minute", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 41, 25)) - assert_equal "5 minutes", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 46, 25)) - assert_equal "about 1 hour", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 22, 47, 25)) - assert_equal "about 3 hours", distance_of_time_in_words(from, Time.mktime(2004, 3, 7, 0, 41)) - assert_equal "about 4 hours", distance_of_time_in_words(from, Time.mktime(2004, 3, 7, 1, 20)) - assert_equal "2 days", distance_of_time_in_words(from, Time.mktime(2004, 3, 9, 15, 40)) - - # include seconds - assert_equal "less than a minute", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 41, 19), false) - assert_equal "less than 5 seconds", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 41, 19), true) - assert_equal "less than 10 seconds", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 41, 28), true) - assert_equal "less than 20 seconds", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 41, 38), true) - assert_equal "half a minute", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 41, 48), true) - assert_equal "less than a minute", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 42, 17), true) - - assert_equal "1 minute", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 42, 18), true) - assert_equal "1 minute", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 42, 28), true) - assert_equal "2 minutes", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 42, 48), true) - - # test to < from - assert_equal "about 4 hours", distance_of_time_in_words(Time.mktime(2004, 3, 7, 1, 20), from) - assert_equal "less than 20 seconds", distance_of_time_in_words(Time.mktime(2004, 3, 6, 21, 41, 38), from, true) - - # test with integers - assert_equal "less than a minute", distance_of_time_in_words(50) - assert_equal "about 1 hour", distance_of_time_in_words(60*60) - - # more cumbersome test with integers - assert_equal "less than a minute", distance_of_time_in_words(0, 50) - assert_equal "about 1 hour", distance_of_time_in_words(60*60, 0) - - end - - def test_distance_in_words_date - start_date = Date.new 1975, 1, 31 - end_date = Date.new 1977, 4, 17 - assert_not_equal("13 minutes", - distance_of_time_in_words(start_date, end_date)) - end - - def test_select_day - expected = %(\n" - - assert_equal expected, select_day(Time.mktime(2003, 8, 16)) - assert_equal expected, select_day(16) - end - - def test_select_day_with_blank - expected = %(\n" - - assert_equal expected, select_day(Time.mktime(2003, 8, 16), :include_blank => true) - assert_equal expected, select_day(16, :include_blank => true) - end - - def test_select_day_nil_with_blank - expected = %(\n" - - assert_equal expected, select_day(nil, :include_blank => true) - end - - def test_select_month - expected = %(\n" - - assert_equal expected, select_month(Time.mktime(2003, 8, 16)) - assert_equal expected, select_month(8) - end - - def test_select_month_with_disabled - expected = %(\n" - - assert_equal expected, select_month(Time.mktime(2003, 8, 16), :disabled => true) - assert_equal expected, select_month(8, :disabled => true) - end - - def test_select_month_with_field_name_override - expected = %(\n" - - assert_equal expected, select_month(Time.mktime(2003, 8, 16), :field_name => 'mois') - assert_equal expected, select_month(8, :field_name => 'mois') - end - - def test_select_month_with_blank - expected = %(\n" - - assert_equal expected, select_month(Time.mktime(2003, 8, 16), :include_blank => true) - assert_equal expected, select_month(8, :include_blank => true) - end - - def test_select_month_nil_with_blank - expected = %(\n" - - assert_equal expected, select_month(nil, :include_blank => true) - end - - def test_select_month_with_numbers - expected = %(\n" - - assert_equal expected, select_month(Time.mktime(2003, 8, 16), :use_month_numbers => true) - assert_equal expected, select_month(8, :use_month_numbers => true) - end - - def test_select_month_with_numbers_and_names - expected = %(\n" - - assert_equal expected, select_month(Time.mktime(2003, 8, 16), :add_month_numbers => true) - assert_equal expected, select_month(8, :add_month_numbers => true) - end - - def test_select_month_with_numbers_and_names_with_abbv - expected = %(\n" - - assert_equal expected, select_month(Time.mktime(2003, 8, 16), :add_month_numbers => true, :use_short_month => true) - assert_equal expected, select_month(8, :add_month_numbers => true, :use_short_month => true) - end - - def test_select_month_with_abbv - expected = %(\n" - - assert_equal expected, select_month(Time.mktime(2003, 8, 16), :use_short_month => true) - assert_equal expected, select_month(8, :use_short_month => true) - end - - def test_select_year - expected = %(\n" - - assert_equal expected, select_year(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005) - assert_equal expected, select_year(2003, :start_year => 2003, :end_year => 2005) - end - - def test_select_year_with_disabled - expected = %(\n" - - assert_equal expected, select_year(Time.mktime(2003, 8, 16), :disabled => true, :start_year => 2003, :end_year => 2005) - assert_equal expected, select_year(2003, :disabled => true, :start_year => 2003, :end_year => 2005) - end - - def test_select_year_with_field_name_override - expected = %(\n" - - assert_equal expected, select_year(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :field_name => 'annee') - assert_equal expected, select_year(2003, :start_year => 2003, :end_year => 2005, :field_name => 'annee') - end - - def test_select_year_with_type_discarding - expected = %(\n" - - assert_equal expected, select_year( - Time.mktime(2003, 8, 16), :prefix => "date_year", :discard_type => true, :start_year => 2003, :end_year => 2005) - assert_equal expected, select_year( - 2003, :prefix => "date_year", :discard_type => true, :start_year => 2003, :end_year => 2005) - end - - def test_select_year_descending - expected = %(\n" - - assert_equal expected, select_year(Time.mktime(2005, 8, 16), :start_year => 2005, :end_year => 2003) - assert_equal expected, select_year(2005, :start_year => 2005, :end_year => 2003) - end - - def test_select_hour - expected = %(\n" - - assert_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18)) - end - - def test_select_hour_with_disabled - expected = %(\n" - - assert_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :disabled => true) - end - - def test_select_hour_with_field_name_override - expected = %(\n" - - assert_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :field_name => 'heure') - end - - def test_select_hour_with_blank - expected = %(\n" - - assert_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :include_blank => true) - end - - def test_select_hour_nil_with_blank - expected = %(\n" - - assert_equal expected, select_hour(nil, :include_blank => true) - end - - def test_select_minute - expected = %(\n" - - assert_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18)) - end - - def test_select_minute_with_disabled - expected = %(\n" - - assert_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), :disabled => true) - end - - def test_select_minute_with_field_name_override - expected = %(\n" - - assert_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), :field_name => 'minuto') - end - - def test_select_minute_with_blank - expected = %(\n" - - assert_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), :include_blank => true) - end - - def test_select_minute_with_blank_and_step - expected = %(\n" - - assert_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), { :include_blank => true , :minute_step => 15 }) - end - - def test_select_minute_nil_with_blank - expected = %(\n" - - assert_equal expected, select_minute(nil, :include_blank => true) - end - - def test_select_minute_nil_with_blank_and_step - expected = %(\n" - - assert_equal expected, select_minute(nil, { :include_blank => true , :minute_step => 15 }) - end - - def test_select_second - expected = %(\n" - - assert_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18)) - end - - def test_select_second_with_disabled - expected = %(\n" - - assert_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), :disabled => true) - end - - def test_select_second_with_field_name_override - expected = %(\n" - - assert_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), :field_name => 'segundo') - end - - def test_select_second_with_blank - expected = %(\n" - - assert_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), :include_blank => true) - end - - def test_select_second_nil_with_blank - expected = %(\n" - - assert_equal expected, select_second(nil, :include_blank => true) - end - - def test_select_date - expected = %(\n" - - expected << %(\n" - - expected << %(\n" - - assert_equal expected, select_date( - Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :prefix => "date[first]" - ) - end - - def test_select_date_with_disabled - expected = %(\n" - - expected << %(\n" - - expected << %(\n" - - assert_equal expected, select_date(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :prefix => "date[first]", :disabled => true) - end - - - def test_select_date_with_no_start_year - expected = %(\n" - - expected << %(\n" - - expected << %(\n" - - assert_equal expected, select_date( - Time.mktime(Date.today.year, 8, 16), :end_year => Date.today.year+1, :prefix => "date[first]" - ) - end - - def test_select_date_with_no_end_year - expected = %(\n" - - expected << %(\n" - - expected << %(\n" - - assert_equal expected, select_date( - Time.mktime(2003, 8, 16), :start_year => 2003, :prefix => "date[first]" - ) - end - - def test_select_date_with_no_start_or_end_year - expected = %(\n" - - expected << %(\n" - - expected << %(\n" - - assert_equal expected, select_date( - Time.mktime(Date.today.year, 8, 16), :prefix => "date[first]" - ) - end - - def test_select_time_with_seconds - expected = %(\n" - - expected << %(\n" - - expected << %(\n" - - assert_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => true) - end - - def test_select_time_without_seconds - expected = %(\n" - - expected << %(\n" - - assert_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18)) - assert_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => false) - end - - def test_date_select_with_zero_value - expected = %(\n" - - expected << %(\n" - - expected << %(\n" - - assert_equal expected, select_date(0, :start_year => 2003, :end_year => 2005, :prefix => "date[first]") - end - - def test_date_select_within_fields_for - @post = Post.new - @post.written_on = Date.new(2004, 6, 15) - - _erbout = '' - - fields_for :post, @post do |f| - _erbout.concat f.date_select(:written_on) - end - - expected = "\n" + - "\n" + - "\n" - - assert_dom_equal(expected, _erbout) - end - - def test_datetime_select_within_fields_for - @post = Post.new - @post.updated_at = Time.local(2004, 6, 15, 16, 35) - - _erbout = '' - - fields_for :post, @post do |f| - _erbout.concat f.datetime_select(:updated_at) - end - - expected = "\n\n\n — \n : \n" - - assert_dom_equal(expected, _erbout) - end - - def test_date_select_with_zero_value_and_no_start_year - expected = %(\n" - - expected << %(\n" - - expected << %(\n" - - assert_equal expected, select_date(0, :end_year => Date.today.year+1, :prefix => "date[first]") - end - - def test_date_select_with_zero_value_and_no_end_year - expected = %(\n" - - expected << %(\n" - - expected << %(\n" - - assert_equal expected, select_date(0, :start_year => 2003, :prefix => "date[first]") - end - - def test_date_select_with_zero_value_and_no_start_and_end_year - expected = %(\n" - - expected << %(\n" - - expected << %(\n" - - assert_equal expected, select_date(0, :prefix => "date[first]") - end - - def test_date_select_with_nil_value_and_no_start_and_end_year - expected = %(\n" - - expected << %(\n" - - expected << %(\n" - - assert_equal expected, select_date(nil, :prefix => "date[first]") - end - - def test_datetime_select_with_nil_value_and_no_start_and_end_year - expected = %(\n" - - expected << %(\n" - - expected << %(\n" - - expected << %(\n" - - expected << %(\n" - - assert_equal expected, select_datetime(nil, :prefix => "date[first]") - end - -end -require File.dirname(__FILE__) + '/../abstract_unit' - -class FormHelperTest < Test::Unit::TestCase - include ActionView::Helpers::FormHelper - include ActionView::Helpers::FormTagHelper - include ActionView::Helpers::UrlHelper - include ActionView::Helpers::TagHelper - include ActionView::Helpers::TextHelper - - silence_warnings do - Post = Struct.new("Post", :title, :author_name, :body, :secret, :written_on, :cost) - Post.class_eval do - alias_method :title_before_type_cast, :title unless respond_to?(:title_before_type_cast) - alias_method :body_before_type_cast, :body unless respond_to?(:body_before_type_cast) - alias_method :author_name_before_type_cast, :author_name unless respond_to?(:author_name_before_type_cast) - end - end - - def setup - @post = Post.new - def @post.errors() Class.new{ def on(field) field == "author_name" end }.new end - - def @post.id; 123; end - def @post.id_before_type_cast; 123; end - - @post.title = "Hello World" - @post.author_name = "" - @post.body = "Back to the hill and over it again!" - @post.secret = 1 - @post.written_on = Date.new(2004, 6, 15) - - @controller = Class.new do - attr_reader :url_for_options - def url_for(options, *parameters_for_method_reference) - @url_for_options = options - "http://www.example.com" - end - end - @controller = @controller.new - end - - def test_text_field - assert_dom_equal( - '', text_field("post", "title") - ) - assert_dom_equal( - '', password_field("post", "title") - ) - assert_dom_equal( - '', password_field("person", "name") - ) - end - - def test_text_field_with_escapes - @post.title = "Hello World" - assert_dom_equal( - '', text_field("post", "title") - ) - end - - def test_text_field_with_options - expected = '' - assert_dom_equal expected, text_field("post", "title", "size" => 35) - assert_dom_equal expected, text_field("post", "title", :size => 35) - end - - def test_text_field_assuming_size - expected = '' - assert_dom_equal expected, text_field("post", "title", "maxlength" => 35) - assert_dom_equal expected, text_field("post", "title", :maxlength => 35) - end - - def test_text_field_doesnt_change_param_values - object_name = 'post[]' - expected = '' - assert_equal expected, text_field(object_name, "title") - assert_equal object_name, "post[]" - end - - def test_check_box - assert_dom_equal( - '', - check_box("post", "secret") - ) - @post.secret = 0 - assert_dom_equal( - '', - check_box("post", "secret") - ) - assert_dom_equal( - '', - check_box("post", "secret" ,{"checked"=>"checked"}) - ) - @post.secret = true - assert_dom_equal( - '', - check_box("post", "secret") - ) - end - - def test_check_box_with_explicit_checked_and_unchecked_values - @post.secret = "on" - assert_dom_equal( - '', - check_box("post", "secret", {}, "on", "off") - ) - end - - def test_radio_button - assert_dom_equal('', - radio_button("post", "title", "Hello World") - ) - assert_dom_equal('', - radio_button("post", "title", "Goodbye World") - ) - end - - def test_radio_button_is_checked_with_integers - assert_dom_equal('', - radio_button("post", "secret", "1") - ) - end - - def test_text_area - assert_dom_equal( - '', - text_area("post", "body") - ) - end - - def test_text_area_with_escapes - @post.body = "Back to the hill and over it again!" - assert_dom_equal( - '', - text_area("post", "body") - ) - end - - def test_text_area_with_alternate_value - assert_dom_equal( - '', - text_area("post", "body", :value => 'Testing alternate values.') - ) - end - - def test_date_selects - assert_dom_equal( - '', - text_area("post", "body") - ) - end - - def test_explicit_name - assert_dom_equal( - '', text_field("post", "title", "name" => "dont guess") - ) - assert_dom_equal( - '', - text_area("post", "body", "name" => "really!") - ) - assert_dom_equal( - '', - check_box("post", "secret", "name" => "i mean it") - ) - assert_dom_equal text_field("post", "title", "name" => "dont guess"), - text_field("post", "title", :name => "dont guess") - assert_dom_equal text_area("post", "body", "name" => "really!"), - text_area("post", "body", :name => "really!") - assert_dom_equal check_box("post", "secret", "name" => "i mean it"), - check_box("post", "secret", :name => "i mean it") - end - - def test_explicit_id - assert_dom_equal( - '', text_field("post", "title", "id" => "dont guess") - ) - assert_dom_equal( - '', - text_area("post", "body", "id" => "really!") - ) - assert_dom_equal( - '', - check_box("post", "secret", "id" => "i mean it") - ) - assert_dom_equal text_field("post", "title", "id" => "dont guess"), - text_field("post", "title", :id => "dont guess") - assert_dom_equal text_area("post", "body", "id" => "really!"), - text_area("post", "body", :id => "really!") - assert_dom_equal check_box("post", "secret", "id" => "i mean it"), - check_box("post", "secret", :id => "i mean it") - end - - def test_auto_index - pid = @post.id - assert_dom_equal( - "", text_field("post[]","title") - ) - assert_dom_equal( - "", - text_area("post[]", "body") - ) - assert_dom_equal( - "", - check_box("post[]", "secret") - ) - assert_dom_equal( -"", - radio_button("post[]", "title", "Hello World") - ) - assert_dom_equal("", - radio_button("post[]", "title", "Goodbye World") - ) - end - - def test_form_for - _erbout = '' - - form_for(:post, @post, :html => { :id => 'create-post' }) do |f| - _erbout.concat f.text_field(:title) - _erbout.concat f.text_area(:body) - _erbout.concat f.check_box(:secret) - end - - expected = - "
    " + - "" + - "" + - "" + - "" + - "
    " - - assert_dom_equal expected, _erbout - end - - def test_form_for_without_object - _erbout = '' - - form_for(:post, :html => { :id => 'create-post' }) do |f| - _erbout.concat f.text_field(:title) - _erbout.concat f.text_area(:body) - _erbout.concat f.check_box(:secret) - end - - expected = - "
    " + - "" + - "" + - "" + - "" + - "
    " - - assert_dom_equal expected, _erbout - end - - def test_fields_for - _erbout = '' - - fields_for(:post, @post) do |f| - _erbout.concat f.text_field(:title) - _erbout.concat f.text_area(:body) - _erbout.concat f.check_box(:secret) - end - - expected = - "" + - "" + - "" + - "" - - assert_dom_equal expected, _erbout - end - - def test_fields_for_without_object - _erbout = '' - fields_for(:post) do |f| - _erbout.concat f.text_field(:title) - _erbout.concat f.text_area(:body) - _erbout.concat f.check_box(:secret) - end - - expected = - "" + - "" + - "" + - "" - - assert_dom_equal expected, _erbout - end - - def test_form_builder_does_not_have_form_for_method - assert ! ActionView::Helpers::FormBuilder.instance_methods.include?('form_for') - end - - def test_form_for_and_fields_for - _erbout = '' - - form_for(:post, @post, :html => { :id => 'create-post' }) do |post_form| - _erbout.concat post_form.text_field(:title) - _erbout.concat post_form.text_area(:body) - - fields_for(:parent_post, @post) do |parent_fields| - _erbout.concat parent_fields.check_box(:secret) - end - end - - expected = - "
    " + - "" + - "" + - "" + - "" + - "
    " - - assert_dom_equal expected, _erbout - end - - class LabelledFormBuilder < ActionView::Helpers::FormBuilder - (field_helpers - %w(hidden_field)).each do |selector| - src = <<-END_SRC - def #{selector}(field, *args, &proc) - " " + super + "
    " - end - END_SRC - class_eval src, __FILE__, __LINE__ - end - end - - def test_form_for_with_labelled_builder - _erbout = '' - - form_for(:post, @post, :builder => LabelledFormBuilder) do |f| - _erbout.concat f.text_field(:title) - _erbout.concat f.text_area(:body) - _erbout.concat f.check_box(:secret) - end - - expected = - "
    " + - "
    " + - "
    " + - " " + - "
    " + - "
    " - - assert_dom_equal expected, _erbout - end - - # Perhaps this test should be moved to prototype helper tests. - def test_remote_form_for_with_labelled_builder - self.extend ActionView::Helpers::PrototypeHelper - _erbout = '' - - remote_form_for(:post, @post, :builder => LabelledFormBuilder) do |f| - _erbout.concat f.text_field(:title) - _erbout.concat f.text_area(:body) - _erbout.concat f.check_box(:secret) - end - - expected = - %(
    ) + - "
    " + - "
    " + - " " + - "
    " + - "
    " - - assert_dom_equal expected, _erbout - end - - def test_fields_for_with_labelled_builder - _erbout = '' - - fields_for(:post, @post, :builder => LabelledFormBuilder) do |f| - _erbout.concat f.text_field(:title) - _erbout.concat f.text_area(:body) - _erbout.concat f.check_box(:secret) - end - - expected = - "
    " + - "
    " + - " " + - "
    " - - assert_dom_equal expected, _erbout - end - - def test_form_for_with_html_options_adds_options_to_form_tag - _erbout = '' - - form_for(:post, @post, :html => {:id => 'some_form', :class => 'some_class'}) do |f| end - expected = "
    " - - assert_dom_equal expected, _erbout - end - - def test_form_for_with_string_url_option - _erbout = '' - - form_for(:post, @post, :url => 'http://www.otherdomain.com') do |f| end - - assert_equal 'http://www.otherdomain.com', @controller.url_for_options - end - - def test_form_for_with_hash_url_option - _erbout = '' - - form_for(:post, @post, :url => {:controller => 'controller', :action => 'action'}) do |f| end - - assert_equal 'controller', @controller.url_for_options[:controller] - assert_equal 'action', @controller.url_for_options[:action] - end - - def test_remote_form_for_with_html_options_adds_options_to_form_tag - self.extend ActionView::Helpers::PrototypeHelper - _erbout = '' - - remote_form_for(:post, @post, :html => {:id => 'some_form', :class => 'some_class'}) do |f| end - expected = "
    " - - assert_dom_equal expected, _erbout - end -end -require File.dirname(__FILE__) + '/../abstract_unit' - -class MockTimeZone - attr_reader :name - - def initialize( name ) - @name = name - end - - def self.all - [ "A", "B", "C", "D", "E" ].map { |s| new s } - end - - def ==( z ) - z && @name == z.name - end - - def to_s - @name - end -end - -ActionView::Helpers::FormOptionsHelper::TimeZone = MockTimeZone - -class FormOptionsHelperTest < Test::Unit::TestCase - include ActionView::Helpers::FormHelper - include ActionView::Helpers::FormOptionsHelper - - silence_warnings do - Post = Struct.new('Post', :title, :author_name, :body, :secret, :written_on, :category, :origin) - Continent = Struct.new('Continent', :continent_name, :countries) - Country = Struct.new('Country', :country_id, :country_name) - Firm = Struct.new('Firm', :time_zone) - end - - def test_collection_options - @posts = [ - Post.new(" went home", "", "To a little house", "shh!"), - Post.new("Babe went home", "Babe", "To a little house", "shh!"), - Post.new("Cabe went home", "Cabe", "To a little house", "shh!") - ] - - assert_dom_equal( - "\n\n", - options_from_collection_for_select(@posts, "author_name", "title") - ) - end - - - def test_collection_options_with_preselected_value - @posts = [ - Post.new(" went home", "", "To a little house", "shh!"), - Post.new("Babe went home", "Babe", "To a little house", "shh!"), - Post.new("Cabe went home", "Cabe", "To a little house", "shh!") - ] - - assert_dom_equal( - "\n\n", - options_from_collection_for_select(@posts, "author_name", "title", "Babe") - ) - end - - def test_collection_options_with_preselected_value_array - @posts = [ - Post.new(" went home", "", "To a little house", "shh!"), - Post.new("Babe went home", "Babe", "To a little house", "shh!"), - Post.new("Cabe went home", "Cabe", "To a little house", "shh!") - ] - - assert_dom_equal( - "\n\n", - options_from_collection_for_select(@posts, "author_name", "title", [ "Babe", "Cabe" ]) - ) - end - - def test_array_options_for_select - assert_dom_equal( - "\n\n", - options_for_select([ "", "USA", "Sweden" ]) - ) - end - - def test_array_options_for_select_with_selection - assert_dom_equal( - "\n\n", - options_for_select([ "Denmark", "", "Sweden" ], "") - ) - end - - def test_array_options_for_select_with_selection_array - assert_dom_equal( - "\n\n", - options_for_select([ "Denmark", "", "Sweden" ], [ "", "Sweden" ]) - ) - end - - def test_array_options_for_string_include_in_other_string_bug_fix - assert_dom_equal( - "\n", - options_for_select([ "ruby", "rubyonrails" ], "rubyonrails") - ) - assert_dom_equal( - "\n", - options_for_select([ "ruby", "rubyonrails" ], "ruby") - ) - end - - def test_hash_options_for_select - assert_dom_equal( - "\n", - options_for_select({ "$" => "Dollar", "" => "" }) - ) - assert_dom_equal( - "\n", - options_for_select({ "$" => "Dollar", "" => "" }, "Dollar") - ) - assert_dom_equal( - "\n", - options_for_select({ "$" => "Dollar", "" => "" }, [ "Dollar", "" ]) - ) - end - - def test_ducktyped_options_for_select - quack = Struct.new(:first, :last) - assert_dom_equal( - "\n", - options_for_select([quack.new("", ""), quack.new("$", "Dollar")]) - ) - assert_dom_equal( - "\n", - options_for_select([quack.new("", ""), quack.new("$", "Dollar")], "Dollar") - ) - assert_dom_equal( - "\n", - options_for_select([quack.new("", ""), quack.new("$", "Dollar")], ["Dollar", ""]) - ) - end - - def test_html_option_groups_from_collection - @continents = [ - Continent.new("", [Country.new("", ""), Country.new("so", "Somalia")] ), - Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] ) - ] - - assert_dom_equal( - "\n\n", - option_groups_from_collection_for_select(@continents, "countries", "continent_name", "country_id", "country_name", "dk") - ) - end - - def test_time_zone_options_no_parms - opts = time_zone_options_for_select - assert_dom_equal "\n" + - "\n" + - "\n" + - "\n" + - "", - opts - end - - def test_time_zone_options_with_selected - opts = time_zone_options_for_select( "D" ) - assert_dom_equal "\n" + - "\n" + - "\n" + - "\n" + - "", - opts - end - - def test_time_zone_options_with_unknown_selected - opts = time_zone_options_for_select( "K" ) - assert_dom_equal "\n" + - "\n" + - "\n" + - "\n" + - "", - opts - end - - def test_time_zone_options_with_priority_zones - zones = [ TimeZone.new( "B" ), TimeZone.new( "E" ) ] - opts = time_zone_options_for_select( nil, zones ) - assert_dom_equal "\n" + - "" + - "\n" + - "\n" + - "\n" + - "", - opts - end - - def test_time_zone_options_with_selected_priority_zones - zones = [ TimeZone.new( "B" ), TimeZone.new( "E" ) ] - opts = time_zone_options_for_select( "E", zones ) - assert_dom_equal "\n" + - "" + - "\n" + - "\n" + - "\n" + - "", - opts - end - - def test_time_zone_options_with_unselected_priority_zones - zones = [ TimeZone.new( "B" ), TimeZone.new( "E" ) ] - opts = time_zone_options_for_select( "C", zones ) - assert_dom_equal "\n" + - "" + - "\n" + - "\n" + - "\n" + - "", - opts - end - - def test_select - @post = Post.new - @post.category = "" - assert_dom_equal( - "", - select("post", "category", %w( abe hest)) - ) - end - - def test_select_under_fields_for - @post = Post.new - @post.category = "" - - _erbout = '' - - fields_for :post, @post do |f| - _erbout.concat f.select(:category, %w( abe hest)) - end - - assert_dom_equal( - "", - _erbout - ) - end - - def test_select_with_blank - @post = Post.new - @post.category = "" - assert_dom_equal( - "", - select("post", "category", %w( abe hest), :include_blank => true) - ) - end - - def test_select_with_default_prompt - @post = Post.new - @post.category = "" - assert_dom_equal( - "", - select("post", "category", %w( abe hest), :prompt => true) - ) - end - - def test_select_no_prompt_when_select_has_value - @post = Post.new - @post.category = "" - assert_dom_equal( - "", - select("post", "category", %w( abe hest), :prompt => true) - ) - end - - def test_select_with_given_prompt - @post = Post.new - @post.category = "" - assert_dom_equal( - "", - select("post", "category", %w( abe hest), :prompt => 'The prompt') - ) - end - - def test_select_with_prompt_and_blank - @post = Post.new - @post.category = "" - assert_dom_equal( - "", - select("post", "category", %w( abe hest), :prompt => true, :include_blank => true) - ) - end - - def test_select_with_selected_value - @post = Post.new - @post.category = "" - assert_dom_equal( - "", - select("post", "category", %w( abe hest ), :selected => 'abe') - ) - end - - def test_select_with_selected_nil - @post = Post.new - @post.category = "" - assert_dom_equal( - "", - select("post", "category", %w( abe hest ), :selected => nil) - ) - end - - def test_collection_select - @posts = [ - Post.new(" went home", "", "To a little house", "shh!"), - Post.new("Babe went home", "Babe", "To a little house", "shh!"), - Post.new("Cabe went home", "Cabe", "To a little house", "shh!") - ] - - @post = Post.new - @post.author_name = "Babe" - - assert_dom_equal( - "", - collection_select("post", "author_name", @posts, "author_name", "author_name") - ) - end - - def test_collection_select_under_fields_for - @posts = [ - Post.new(" went home", "", "To a little house", "shh!"), - Post.new("Babe went home", "Babe", "To a little house", "shh!"), - Post.new("Cabe went home", "Cabe", "To a little house", "shh!") - ] - - @post = Post.new - @post.author_name = "Babe" - - _erbout = '' - - fields_for :post, @post do |f| - _erbout.concat f.collection_select(:author_name, @posts, :author_name, :author_name) - end - - assert_dom_equal( - "", - _erbout - ) - end - - def test_collection_select_with_blank_and_style - @posts = [ - Post.new(" went home", "", "To a little house", "shh!"), - Post.new("Babe went home", "Babe", "To a little house", "shh!"), - Post.new("Cabe went home", "Cabe", "To a little house", "shh!") - ] - - @post = Post.new - @post.author_name = "Babe" - - assert_dom_equal( - "", - collection_select("post", "author_name", @posts, "author_name", "author_name", { :include_blank => true }, "style" => "width: 200px") - ) - end - - def test_country_select - @post = Post.new - @post.origin = "Denmark" - assert_dom_equal( - "", - country_select("post", "origin") - ) - end - - def test_time_zone_select - @firm = Firm.new("D") - html = time_zone_select( "firm", "time_zone" ) - assert_dom_equal "", - html - end - - def test_time_zone_select_under_fields_for - @firm = Firm.new("D") - - _erbout = '' - - fields_for :firm, @firm do |f| - _erbout.concat f.time_zone_select(:time_zone) - end - - assert_dom_equal( - "", - _erbout - ) - end - - def test_time_zone_select_with_blank - @firm = Firm.new("D") - html = time_zone_select("firm", "time_zone", nil, :include_blank => true) - assert_dom_equal "", - html - end - - def test_time_zone_select_with_style - @firm = Firm.new("D") - html = time_zone_select("firm", "time_zone", nil, {}, - "style" => "color: red") - assert_dom_equal "", - html - assert_dom_equal html, time_zone_select("firm", "time_zone", nil, {}, - :style => "color: red") - end - - def test_time_zone_select_with_blank_and_style - @firm = Firm.new("D") - html = time_zone_select("firm", "time_zone", nil, - { :include_blank => true }, "style" => "color: red") - assert_dom_equal "", - html - assert_dom_equal html, time_zone_select("firm", "time_zone", nil, - { :include_blank => true }, :style => "color: red") - end - - def test_time_zone_select_with_priority_zones - @firm = Firm.new("D") - zones = [ TimeZone.new("A"), TimeZone.new("D") ] - html = time_zone_select("firm", "time_zone", zones ) - assert_dom_equal "", - html - end -end -require File.dirname(__FILE__) + '/../abstract_unit' - -class FormTagHelperTest < Test::Unit::TestCase - - include ActionView::Helpers::UrlHelper - include ActionView::Helpers::TagHelper - include ActionView::Helpers::FormTagHelper - - def setup - @controller = Class.new do - def url_for(options, *parameters_for_method_reference) - "http://www.example.com" - end - end - @controller = @controller.new - end - - def test_check_box_tag - actual = check_box_tag "admin" - expected = %() - assert_dom_equal expected, actual - end - - def test_form_tag - actual = form_tag - expected = %(
    ) - assert_dom_equal expected, actual - end - - def test_form_tag_multipart - actual = form_tag({}, { 'multipart' => true }) - expected = %() - assert_dom_equal expected, actual - end - - def test_hidden_field_tag - actual = hidden_field_tag "id", 3 - expected = %() - assert_dom_equal expected, actual - end - - def test_password_field_tag - actual = password_field_tag - expected = %() - assert_dom_equal expected, actual - end - - def test_radio_button_tag - actual = radio_button_tag "people", "david" - expected = %() - assert_dom_equal expected, actual - end - - def test_select_tag - actual = select_tag "people", "" - expected = %() - assert_dom_equal expected, actual - end - - def test_text_area_tag_size_string - actual = text_area_tag "body", "hello world", "size" => "20x40" - expected = %() - assert_dom_equal expected, actual - end - - def test_text_area_tag_size_symbol - actual = text_area_tag "body", "hello world", :size => "20x40" - expected = %() - assert_dom_equal expected, actual - end - - def test_text_field_tag - actual = text_field_tag "title", "Hello!" - expected = %() - assert_dom_equal expected, actual - end - - def test_text_field_tag_class_string - actual = text_field_tag "title", "Hello!", "class" => "admin" - expected = %() - assert_dom_equal expected, actual - end - - def test_boolean_optios - assert_dom_equal %(), check_box_tag("admin", 1, true, 'disabled' => true, :readonly => "yes") - assert_dom_equal %(), check_box_tag("admin", 1, true, :disabled => false, :readonly => nil) - assert_dom_equal %(), select_tag("people", "", :multiple => true) - assert_dom_equal %(), select_tag("people", "", :multiple => nil) - end - - def test_stringify_symbol_keys - actual = text_field_tag "title", "Hello!", :id => "admin" - expected = %() - assert_dom_equal expected, actual - end - - def test_submit_tag - assert_dom_equal( - %(), - submit_tag("Save", :disable_with => "Saving...", :onclick => "alert('hello!')") - ) - end - - def test_pass - assert_equal 1, 1 - end -end - -require File.dirname(__FILE__) + '/../abstract_unit' - -class JavaScriptMacrosHelperTest < Test::Unit::TestCase - include ActionView::Helpers::JavaScriptHelper - include ActionView::Helpers::JavaScriptMacrosHelper - - include ActionView::Helpers::UrlHelper - include ActionView::Helpers::TagHelper - include ActionView::Helpers::TextHelper - include ActionView::Helpers::FormHelper - include ActionView::Helpers::CaptureHelper - - def setup - @controller = Class.new do - def url_for(options, *parameters_for_method_reference) - url = "http://www.example.com/" - url << options[:action].to_s if options and options[:action] - url - end - end - @controller = @controller.new - end - - - def test_auto_complete_field - assert_dom_equal %(), - auto_complete_field("some_input", :url => { :action => "autocomplete" }); - assert_dom_equal %(), - auto_complete_field("some_input", :url => { :action => "autocomplete" }, :tokens => ','); - assert_dom_equal %(), - auto_complete_field("some_input", :url => { :action => "autocomplete" }, :tokens => [',']); - assert_dom_equal %(), - auto_complete_field("some_input", :url => { :action => "autocomplete" }, :min_chars => 3); - assert_dom_equal %(), - auto_complete_field("some_input", :url => { :action => "autocomplete" }, :on_hide => "function(element, update){alert('me');}"); - assert_dom_equal %(), - auto_complete_field("some_input", :url => { :action => "autocomplete" }, :frequency => 2); - assert_dom_equal %(), - auto_complete_field("some_input", :url => { :action => "autocomplete" }, - :after_update_element => "function(element,value){alert('You have chosen: '+value)}"); - end - - def test_auto_complete_result - result = [ { :title => 'test1' }, { :title => 'test2' } ] - assert_equal %(
    • test1
    • test2
    ), - auto_complete_result(result, :title) - assert_equal %(
    • test1
    • test2
    ), - auto_complete_result(result, :title, "est") - - resultuniq = [ { :title => 'test1' }, { :title => 'test1' } ] - assert_equal %(
    • test1
    ), - auto_complete_result(resultuniq, :title, "est") - end - - def test_text_field_with_auto_complete - assert_match "