diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 000000000..753409b5e --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,21 @@ +.DS_Store +log/* +tmp/* +TAGS +*~ +.#* +schema/schema.rb +schema/*_structure.sql +schema/*.sqlite3 +schema/*.sqlite +schema/*.db +*.sqlite +*.sqlite3 +*.db +src/* +.hgignore +.hg/* +.svn/* +gems/gems/* +gems/specifications/* +merb_profile_results \ No newline at end of file diff --git a/web/Rakefile b/web/Rakefile new file mode 100644 index 000000000..a760c99c6 --- /dev/null +++ b/web/Rakefile @@ -0,0 +1,35 @@ +require 'rubygems' +require 'rake/rdoctask' + +require 'merb-core' +require 'merb-core/tasks/merb' + +include FileUtils + +# Load the basic runtime dependencies; this will include +# any plugins and therefore plugin rake tasks. +init_env = ENV['MERB_ENV'] || 'rake' +Merb.load_dependencies(:environment => init_env) + +# Get Merb plugins and dependencies +Merb::Plugins.rakefiles.each { |r| require r } + +# Load any app level custom rakefile extensions from lib/tasks +tasks_path = File.join(File.dirname(__FILE__), "lib", "tasks") +rake_files = Dir["#{tasks_path}/*.rake"] +rake_files.each{|rake_file| load rake_file } + +desc "Start runner environment" +task :merb_env do + Merb.start_environment(:environment => init_env, :adapter => 'runner') +end + +require 'spec/rake/spectask' +require 'merb-core/test/tasks/spectasks' +desc 'Default: run spec examples' +task :default => 'spec' + +############################################################################## +# ADD YOUR CUSTOM TASKS IN /lib/tasks +# NAME YOUR RAKE FILES file_name.rake +############################################################################## diff --git a/web/app/controllers/application.rb b/web/app/controllers/application.rb new file mode 100644 index 000000000..5ce39a010 --- /dev/null +++ b/web/app/controllers/application.rb @@ -0,0 +1,2 @@ +class Application < Merb::Controller +end \ No newline at end of file diff --git a/web/app/controllers/exceptions.rb b/web/app/controllers/exceptions.rb new file mode 100644 index 000000000..4fdb5665a --- /dev/null +++ b/web/app/controllers/exceptions.rb @@ -0,0 +1,13 @@ +class Exceptions < Merb::Controller + + # handle NotFound exceptions (404) + def not_found + render :format => :html + end + + # handle NotAcceptable exceptions (406) + def not_acceptable + render :format => :html + end + +end \ No newline at end of file diff --git a/web/app/controllers/search.rb b/web/app/controllers/search.rb new file mode 100644 index 000000000..f5fbbf512 --- /dev/null +++ b/web/app/controllers/search.rb @@ -0,0 +1,57 @@ + +$: << ".." +require "lib/net/client" +require "lib/net/messages/search" +require "lib/net/messages/searchhits" +require "lib/net/messages/ping" +require "timeout" + +class SearchClient < LogStash::Net::MessageClient + attr_reader :results + attr_reader :hits + + def SearchHitsResponseHandler(msg) + @hits = msg.hits + end + + def SearchResponseHandler(msg) + if @results == nil + @results = [] + end + + msg.results.each do |result| + @results << result + end + + if msg.finished + close + end + end # def SearchResponseHandler +end # class SearchClient + +class Search < Application + + def index + render + end + + def query + @searchclient = SearchClient.new(host="localhost", port=61613) + msg = LogStash::Net::Messages::SearchHitsRequest.new + msg.log_type = (params[:log_type] or "linux-syslog") + msg.query = params[:q] + @searchclient.sendmsg("/queue/logstash", msg) + + msg = LogStash::Net::Messages::SearchRequest.new + msg.log_type = (params[:log_type] or "linux-syslog") + msg.query = params[:q] + msg.limit = 20 + @searchclient.sendmsg("/queue/logstash", msg) + + Timeout.timeout(10) do + @searchclient.run + render + end + end + +end diff --git a/web/app/helpers/global_helpers.rb b/web/app/helpers/global_helpers.rb new file mode 100644 index 000000000..9c9e5aa19 --- /dev/null +++ b/web/app/helpers/global_helpers.rb @@ -0,0 +1,5 @@ +module Merb + module GlobalHelpers + # helpers defined here available to all views. + end +end diff --git a/web/app/helpers/search_helper.rb b/web/app/helpers/search_helper.rb new file mode 100644 index 000000000..2562ab65f --- /dev/null +++ b/web/app/helpers/search_helper.rb @@ -0,0 +1,5 @@ +module Merb + module SearchHelper + + end +end # Merb \ No newline at end of file diff --git a/web/app/models/user.rb b/web/app/models/user.rb new file mode 100644 index 000000000..ae84896cd --- /dev/null +++ b/web/app/models/user.rb @@ -0,0 +1,17 @@ +# This is a default user class used to activate merb-auth. Feel free to change from a User to +# Some other class, or to remove it altogether. If removed, merb-auth may not work by default. +# +# Don't forget that by default the salted_user mixin is used from merb-more +# You'll need to setup your db as per the salted_user mixin, and you'll need +# To use :password, and :password_confirmation when creating a user +# +# see merb/merb-auth/setup.rb to see how to disable the salted_user mixin +# +# You will need to setup your database and create a user. +class User + include DataMapper::Resource + + property :id, Serial + property :login, String + +end diff --git a/web/app/views/exceptions/not_acceptable.html.erb b/web/app/views/exceptions/not_acceptable.html.erb new file mode 100644 index 000000000..a7b7752ab --- /dev/null +++ b/web/app/views/exceptions/not_acceptable.html.erb @@ -0,0 +1,63 @@ +
+
+ + +

pocket rocket web framework

+
+
+ +
+

Exception:

+

<%= request.exceptions.first.message %>

+
+ +
+

Why am I seeing this page?

+

Merb couldn't find an appropriate content_type to return, + based on what you said was available via provides() and + what the client requested.

+ +

How to add a mime-type

+

+      Merb.add_mime_type :pdf, :to_pdf, %w[application/pdf], "Content-Encoding" => "gzip"
+    
+

What this means is:

+ + +

You can then do:

+

+      class Foo < Application
+        provides :pdf
+      end
+    
+ +

Where can I find help?

+

If you have any questions or if you can't figure something out, please take a + look at our project page, + feel free to come chat at irc.freenode.net, channel #merb, + or post to merb mailing list + on Google Groups.

+ +

What if I've found a bug?

+

If you want to file a bug or make your own contribution to Merb, + feel free to register and create a ticket at our + project development page + on Lighthouse.

+ +

How do I edit this page?

+

You can change what people see when this happens by editing app/views/exceptions/not_acceptable.html.erb.

+ +
+ + +
diff --git a/web/app/views/exceptions/not_found.html.erb b/web/app/views/exceptions/not_found.html.erb new file mode 100644 index 000000000..42b41a89d --- /dev/null +++ b/web/app/views/exceptions/not_found.html.erb @@ -0,0 +1,47 @@ +
+
+ + +

pocket rocket web framework

+
+
+ +
+

Exception:

+

<%= request.exceptions.first.message %>

+
+ +
+

Welcome to Merb!

+

Merb is a light-weight MVC framework written in Ruby. We hope you enjoy it.

+ +

Where can I find help?

+

If you have any questions or if you can't figure something out, please take a + look at our project page, + feel free to come chat at irc.freenode.net, channel #merb, + or post to merb mailing list + on Google Groups.

+ +

What if I've found a bug?

+

If you want to file a bug or make your own contribution to Merb, + feel free to register and create a ticket at our + project development page + on Lighthouse.

+ +

How do I edit this page?

+

You're seeing this page because you need to edit the following files: +

+

+
+ + +
diff --git a/web/app/views/layout/application.html.erb b/web/app/views/layout/application.html.erb new file mode 100644 index 000000000..4637ef2de --- /dev/null +++ b/web/app/views/layout/application.html.erb @@ -0,0 +1,12 @@ + + + + Fresh Merb App + + + + + <%#= message[:notice] %> + <%= catch_content :for_layout %> + + \ No newline at end of file diff --git a/web/app/views/search/index.html.erb b/web/app/views/search/index.html.erb new file mode 100644 index 000000000..a5626a112 --- /dev/null +++ b/web/app/views/search/index.html.erb @@ -0,0 +1,4 @@ +<%= form :action => url(:controller => "search", :action => "query") do %> + <%= text_field :name => "q", :label => "Query" %> + <%= submit "Search" %> +<% end =%> diff --git a/web/app/views/search/query.html.erb b/web/app/views/search/query.html.erb new file mode 100644 index 000000000..3456f9450 --- /dev/null +++ b/web/app/views/search/query.html.erb @@ -0,0 +1,12 @@ +<%= form :action => url(:controller => "search", :action => "query") do %> + <%= text_field :name => "q", :label => "Query", :value => escape_html(params[:q]) %> + <%= submit "Search" %> +<% end =%> + +
+ +

Hits: <%= @searchclient.hits %>

+
+
+<%=h @searchclient.results.join("\n") %>
+
diff --git a/web/autotest/discover.rb b/web/autotest/discover.rb new file mode 100644 index 000000000..8df3b0f34 --- /dev/null +++ b/web/autotest/discover.rb @@ -0,0 +1,2 @@ +Autotest.add_discovery { "merb" } +Autotest.add_discovery { "rspec" } \ No newline at end of file diff --git a/web/autotest/merb.rb b/web/autotest/merb.rb new file mode 100644 index 000000000..f78df1570 --- /dev/null +++ b/web/autotest/merb.rb @@ -0,0 +1,152 @@ +# Adapted from Autotest::Rails +require 'autotest' + +class Autotest::Merb < Autotest + + # +model_tests_dir+:: the directory to find model-centric tests + # +controller_tests_dir+:: the directory to find controller-centric tests + # +view_tests_dir+:: the directory to find view-centric tests + # +fixtures_dir+:: the directory to find fixtures in + attr_accessor :model_tests_dir, :controller_tests_dir, :view_tests_dir, :fixtures_dir + + def initialize + super + + initialize_test_layout + + # Ignore any happenings in these directories + add_exception %r%^\./(?:doc|log|public|tmp|\.git|\.hg|\.svn|framework|gems|schema|\.DS_Store|autotest|bin|.*\.sqlite3)% + # Ignore SCM directories and custom Autotest mappings + %w[.svn .hg .git .autotest].each { |exception| add_exception(exception) } + + + # Ignore any mappings that Autotest may have already set up + clear_mappings + + # Any changes to a file in the root of the 'lib' directory will run any + # model test with a corresponding name. + add_mapping %r%^lib\/.*\.rb% do |filename, _| + files_matching Regexp.new(["^#{model_test_for(filename)}$"]) + end + + # Any changes to a fixture will run corresponding view, controller and + # model tests + add_mapping %r%^#{fixtures_dir}/(.*)s.yml% do |_, m| + [ + model_test_for(m[1]), + controller_test_for(m[1]), + view_test_for(m[1]) + ] + end + + # Any change to a test will cause it to be run + add_mapping %r%^test/(unit|models|integration|controllers|views|functional)/.*rb$% do |filename, _| + filename + end + + # Any change to a model will cause it's corresponding test to be run + add_mapping %r%^app/models/(.*)\.rb$% do |_, m| + model_test_for(m[1]) + end + + # Any change to the global helper will result in all view and controller + # tests being run + add_mapping %r%^app/helpers/global_helpers.rb% do + files_matching %r%^test/(views|functional|controllers)/.*_test\.rb$% + end + + # Any change to a helper will run it's corresponding view and controller + # tests, unless the helper is the global helper. Changes to the global + # helper run all view and controller tests. + add_mapping %r%^app/helpers/(.*)_helper(s)?.rb% do |_, m| + if m[1] == "global" then + files_matching %r%^test/(views|functional|controllers)/.*_test\.rb$% + else + [ + view_test_for(m[1]), + controller_test_for(m[1]) + ] + end + end + + # Changes to views result in their corresponding view and controller test + # being run + add_mapping %r%^app/views/(.*)/% do |_, m| + [ + view_test_for(m[1]), + controller_test_for(m[1]) + ] + end + + # Changes to a controller result in its corresponding test being run. If + # the controller is the exception or application controller, all + # controller tests are run. + add_mapping %r%^app/controllers/(.*)\.rb$% do |_, m| + if ["application", "exception"].include?(m[1]) + files_matching %r%^test/(controllers|views|functional)/.*_test\.rb$% + else + controller_test_for(m[1]) + end + end + + # If a change is made to the router, run all controller and view tests + add_mapping %r%^config/router.rb$% do # FIX + files_matching %r%^test/(controllers|views|functional)/.*_test\.rb$% + end + + # If any of the major files governing the environment are altered, run + # everything + add_mapping %r%^test/test_helper.rb|config/(init|rack|environments/test.rb|database.yml)% do # FIX + files_matching %r%^test/(unit|models|controllers|views|functional)/.*_test\.rb$% + end + end + +private + + # Determines the paths we can expect tests or specs to reside, as well as + # corresponding fixtures. + def initialize_test_layout + self.model_tests_dir = "test/unit" + self.controller_tests_dir = "test/functional" + self.view_tests_dir = "test/views" + self.fixtures_dir = "test/fixtures" + end + + # Given a filename and the test type, this method will return the + # corresponding test's or spec's name. + # + # ==== Arguments + # +filename+:: the file name of the model, view, or controller + # +kind_of_test+:: the type of test we that we should run + # + # ==== Returns + # String:: the name of the corresponding test or spec + # + # ==== Example + # + # > test_for("user", :model) + # => "user_test.rb" + # > test_for("login", :controller) + # => "login_controller_test.rb" + # > test_for("form", :view) + # => "form_view_spec.rb" # If you're running a RSpec-like suite + def test_for(filename, kind_of_test) + name = [filename] + name << kind_of_test.to_s if kind_of_test == :view + name << "test" + return name.join("_") + ".rb" + end + + def model_test_for(filename) + [model_tests_dir, test_for(filename, :model)].join("/") + end + + def controller_test_for(filename) + [controller_tests_dir, test_for(filename, :controller)].join("/") + end + + def view_test_for(filename) + [view_tests_dir, test_for(filename, :view)].join("/") + end + +end \ No newline at end of file diff --git a/web/autotest/merb_rspec.rb b/web/autotest/merb_rspec.rb new file mode 100644 index 000000000..536665d89 --- /dev/null +++ b/web/autotest/merb_rspec.rb @@ -0,0 +1,165 @@ +# Adapted from Autotest::Rails, RSpec's autotest class, as well as merb-core's. +require 'autotest' + +class RspecCommandError < StandardError; end + +# This class maps your application's structure so Autotest can understand what +# specs to run when files change. +# +# Fixtures are _not_ covered by this class. If you change a fixture file, you +# will have to run your spec suite manually, or, better yet, provide your own +# Autotest map explaining how your fixtures are set up. +class Autotest::MerbRspec < Autotest + def initialize + super + + # Ignore any happenings in these directories + add_exception %r%^\./(?:doc|log|public|tmp|\.git|\.hg|\.svn|framework|gems|schema|\.DS_Store|autotest|bin|.*\.sqlite3|.*\.thor)% + # Ignore SCM directories and custom Autotest mappings + %w[.svn .hg .git .autotest].each { |exception| add_exception(exception) } + + # Ignore any mappings that Autotest may have already set up + clear_mappings + + # Anything in /lib could have a spec anywhere, if at all. So, look for + # files with roughly the same name as the file in /lib + add_mapping %r%^lib\/(.*)\.rb% do |_, m| + files_matching %r%^spec\/#{m[1]}% + end + + add_mapping %r%^spec/(spec_helper|shared/.*)\.rb$% do + all_specs + end + + # Changing a spec will cause it to run itself + add_mapping %r%^spec/.*\.rb$% do |filename, _| + filename + end + + # Any change to a model will cause it's corresponding test to be run + add_mapping %r%^app/models/(.*)\.rb$% do |_, m| + spec_for(m[1], 'model') + end + + # Any change to global_helpers will result in all view and controller + # tests being run + add_mapping %r%^app/helpers/global_helpers\.rb% do + files_matching %r%^spec/(views|controllers|helpers|requests)/.*_spec\.rb$% + end + + # Any change to a helper will cause its spec to be run + add_mapping %r%^app/helpers/((.*)_helper(s)?)\.rb% do |_, m| + spec_for(m[1], 'helper') + end + + # Changes to a view cause its spec to be run + add_mapping %r%^app/views/(.*)/% do |_, m| + spec_for(m[1], 'view') + end + + # Changes to a controller result in its corresponding spec being run. If + # the controller is the exception or application controller, all + # controller specs are run. + add_mapping %r%^app/controllers/(.*)\.rb$% do |_, m| + if ["application", "exception"].include?(m[1]) + files_matching %r%^spec/controllers/.*_spec\.rb$% + else + spec_for(m[1], 'controller') + end + end + + # If a change is made to the router, run controller, view and helper specs + add_mapping %r%^config/router.rb$% do + files_matching %r%^spec/(controllers|views|helpers)/.*_spec\.rb$% + end + + # If any of the major files governing the environment are altered, run + # everything + add_mapping %r%^config/(init|rack|environments/test).*\.rb|database\.yml% do + all_specs + end + end + + def failed_results(results) + results.scan(/^\d+\)\n(?:\e\[\d*m)?(?:.*?Error in )?'([^\n]*)'(?: FAILED)?(?:\e\[\d*m)?\n(.*?)\n\n/m) + end + + def handle_results(results) + @failures = failed_results(results) + @files_to_test = consolidate_failures(@failures) + @files_to_test.empty? && !$TESTING ? hook(:green) : hook(:red) + @tainted = !@files_to_test.empty? + end + + def consolidate_failures(failed) + filters = Hash.new { |h,k| h[k] = [] } + failed.each do |spec, failed_trace| + if f = test_files_for(failed).find { |f| f =~ /spec\// } + filters[f] << spec + break + end + end + filters + end + + def make_test_cmd(specs_to_runs) + [ + ruby, + "-S", + spec_command, + add_options_if_present, + files_to_test.keys.flatten.join(' ') + ].join(' ') + end + + def add_options_if_present + File.exist?("spec/spec.opts") ? "-O spec/spec.opts " : "" + end + + # Finds the proper spec command to use. Precendence is set in the + # lazily-evaluated method spec_commands. Alias + Override that in + # ~/.autotest to provide a different spec command then the default + # paths provided. + def spec_command(separator=File::ALT_SEPARATOR) + unless defined?(@spec_command) + @spec_command = spec_commands.find { |cmd| File.exists?(cmd) } + + raise RspecCommandError, "No spec command could be found" unless @spec_command + + @spec_command.gsub!(File::SEPARATOR, separator) if separator + end + @spec_command + end + + # Autotest will look for spec commands in the following + # locations, in this order: + # + # * default spec bin/loader installed in Rubygems + # * any spec command found in PATH + def spec_commands + [File.join(Config::CONFIG['bindir'], 'spec'), 'spec'] + end + +private + + # Runs +files_matching+ for all specs + def all_specs + files_matching %r%^spec/.*_spec\.rb$% + end + + # Generates a path to some spec given its kind and the match from a mapping + # + # ==== Arguments + # match:: the match from a mapping + # kind:: the kind of spec that the match represents + # + # ==== Returns + # String + # + # ==== Example + # > spec_for('post', :view') + # => "spec/views/post_spec.rb" + def spec_for(match, kind) + File.join("spec", kind + 's', "#{match}_spec.rb") + end +end diff --git a/web/config/database.yml b/web/config/database.yml new file mode 100644 index 000000000..ba3a4f824 --- /dev/null +++ b/web/config/database.yml @@ -0,0 +1,33 @@ +--- +# This is a sample database file for the DataMapper ORM +development: &defaults + # These are the settings for repository :default + adapter: sqlite3 + database: sample_development.db + + # Add more repositories + # repositories: + # repo1: + # adapter: sqlite3 + # database: sample_1_development.db + # repo2: + # ... + +test: + <<: *defaults + database: sample_test.db + + # repositories: + # repo1: + # database: sample_1_test.db + +production: + <<: *defaults + database: production.db + + # repositories: + # repo1: + # database: sample_production.db + +rake: + <<: *defaults \ No newline at end of file diff --git a/web/config/dependencies.rb b/web/config/dependencies.rb new file mode 100644 index 000000000..5c207bb23 --- /dev/null +++ b/web/config/dependencies.rb @@ -0,0 +1,34 @@ +# dependencies are generated using a strict version, don't forget to edit the dependency versions when upgrading. +merb_gems_version = "1.0.12" +dm_gems_version = "0.9.11" +do_gems_version = "0.9.11" + +# For more information about each component, please read http://wiki.merbivore.com/faqs/merb_components +dependency "merb-core", merb_gems_version +dependency "merb-action-args", merb_gems_version +dependency "merb-assets", merb_gems_version +dependency("merb-cache", merb_gems_version) do + Merb::Cache.setup do + register(Merb::Cache::FileStore) unless Merb.cache + end +end +dependency "merb-helpers", merb_gems_version +dependency "merb-mailer", merb_gems_version +dependency "merb-slices", merb_gems_version +dependency "merb-auth-core", merb_gems_version +dependency "merb-auth-more", merb_gems_version +dependency "merb-auth-slice-password", merb_gems_version +dependency "merb-param-protection", merb_gems_version +dependency "merb-exceptions", merb_gems_version + +dependency "data_objects", do_gems_version +dependency "do_sqlite3", do_gems_version # If using another database, replace this +dependency "dm-core", dm_gems_version +dependency "dm-aggregates", dm_gems_version +dependency "dm-migrations", dm_gems_version +dependency "dm-timestamps", dm_gems_version +dependency "dm-types", dm_gems_version +dependency "dm-validations", dm_gems_version +dependency "dm-serializer", dm_gems_version + +dependency "merb_datamapper", merb_gems_version \ No newline at end of file diff --git a/web/config/environments/development.rb b/web/config/environments/development.rb new file mode 100644 index 000000000..d6d4ab383 --- /dev/null +++ b/web/config/environments/development.rb @@ -0,0 +1,15 @@ +Merb.logger.info("Loaded DEVELOPMENT Environment...") +Merb::Config.use { |c| + c[:exception_details] = true + c[:reload_templates] = true + c[:reload_classes] = true + c[:reload_time] = 0.5 + c[:ignore_tampered_cookies] = true + c[:log_auto_flush ] = true + c[:log_level] = :debug + + c[:log_stream] = STDOUT + c[:log_file] = nil + # Or redirect logging into a file: + # c[:log_file] = Merb.root / "log" / "development.log" +} diff --git a/web/config/environments/production.rb b/web/config/environments/production.rb new file mode 100644 index 000000000..17a2d4b14 --- /dev/null +++ b/web/config/environments/production.rb @@ -0,0 +1,10 @@ +Merb.logger.info("Loaded PRODUCTION Environment...") +Merb::Config.use { |c| + c[:exception_details] = false + c[:reload_classes] = false + c[:log_level] = :error + + c[:log_file] = Merb.root / "log" / "production.log" + # or redirect logger using IO handle + # c[:log_stream] = STDOUT +} diff --git a/web/config/environments/rake.rb b/web/config/environments/rake.rb new file mode 100644 index 000000000..5e4b9a77f --- /dev/null +++ b/web/config/environments/rake.rb @@ -0,0 +1,11 @@ +Merb.logger.info("Loaded RAKE Environment...") +Merb::Config.use { |c| + c[:exception_details] = true + c[:reload_classes] = false + c[:log_auto_flush ] = true + + c[:log_stream] = STDOUT + c[:log_file] = nil + # Or redirect logging into a file: + # c[:log_file] = Merb.root / "log" / "development.log" +} diff --git a/web/config/environments/staging.rb b/web/config/environments/staging.rb new file mode 100644 index 000000000..f5b505893 --- /dev/null +++ b/web/config/environments/staging.rb @@ -0,0 +1,10 @@ +Merb.logger.info("Loaded STAGING Environment...") +Merb::Config.use { |c| + c[:exception_details] = false + c[:reload_classes] = false + c[:log_level] = :error + + c[:log_file] = Merb.root / "log" / "staging.log" + # or redirect logger using IO handle + # c[:log_stream] = STDOUT +} diff --git a/web/config/environments/test.rb b/web/config/environments/test.rb new file mode 100644 index 000000000..671ec76e7 --- /dev/null +++ b/web/config/environments/test.rb @@ -0,0 +1,12 @@ +Merb.logger.info("Loaded TEST Environment...") +Merb::Config.use { |c| + c[:testing] = true + c[:exception_details] = true + c[:log_auto_flush ] = true + # log less in testing environment + c[:log_level] = :error + + #c[:log_file] = Merb.root / "log" / "test.log" + # or redirect logger using IO handle + c[:log_stream] = STDOUT +} diff --git a/web/config/init.rb b/web/config/init.rb new file mode 100644 index 000000000..1efa5395b --- /dev/null +++ b/web/config/init.rb @@ -0,0 +1,24 @@ +# Go to http://wiki.merbivore.com/pages/init-rb + +require 'config/dependencies.rb' + +use_orm :datamapper +use_test :rspec +use_template_engine :erb + +Merb::Config.use do |c| + c[:use_mutex] = false + c[:session_store] = 'cookie' # can also be 'memory', 'memcache', 'container', 'datamapper + + # cookie session store configuration + c[:session_secret_key] = 'bfa5ae857a67de5ef5bd123f3f0c41ffac540deb' # required for cookie session store + c[:session_id_key] = '_web_session_id' # cookie session id key, defaults to "_session_id" +end + +Merb::BootLoader.before_app_loads do + # This will get executed after dependencies have been loaded but before your app's classes have loaded. +end + +Merb::BootLoader.after_app_loads do + # This will get executed after your app's classes have been loaded. +end diff --git a/web/config/rack.rb b/web/config/rack.rb new file mode 100644 index 000000000..494c68734 --- /dev/null +++ b/web/config/rack.rb @@ -0,0 +1,11 @@ +# use PathPrefix Middleware if :path_prefix is set in Merb::Config +if prefix = ::Merb::Config[:path_prefix] + use Merb::Rack::PathPrefix, prefix +end + +# comment this out if you are running merb behind a load balancer +# that serves static files +use Merb::Rack::Static, Merb.dir_for(:public) + +# this is our main merb application +run Merb::Rack::Application.new \ No newline at end of file diff --git a/web/config/router.rb b/web/config/router.rb new file mode 100644 index 000000000..b83c9afa6 --- /dev/null +++ b/web/config/router.rb @@ -0,0 +1,44 @@ +# Merb::Router is the request routing mapper for the merb framework. +# +# You can route a specific URL to a controller / action pair: +# +# match("/contact"). +# to(:controller => "info", :action => "contact") +# +# You can define placeholder parts of the url with the :symbol notation. These +# placeholders will be available in the params hash of your controllers. For example: +# +# match("/books/:book_id/:action"). +# to(:controller => "books") +# +# Or, use placeholders in the "to" results for more complicated routing, e.g.: +# +# match("/admin/:module/:controller/:action/:id"). +# to(:controller => ":module/:controller") +# +# You can specify conditions on the placeholder by passing a hash as the second +# argument of "match" +# +# match("/registration/:course_name", :course_name => /^[a-z]{3,5}-\d{5}$/). +# to(:controller => "registration") +# +# You can also use regular expressions, deferred routes, and many other options. +# See merb/specs/merb/router.rb for a fairly complete usage sample. + +Merb.logger.info("Compiling routes...") +Merb::Router.prepare do + # RESTful routes + # resources :posts + + # Adds the required routes for merb-auth using the password slice + slice(:merb_auth_slice_password, :name_prefix => nil, :path_prefix => "") + + # This is the default route for /:controller/:action/:id + # This is fine for most cases. If you're heavily using resource-based + # routes, you may want to comment/remove this line to prevent + # clients from calling your create or destroy actions with a GET + default_routes + + # Change this for your home page to be available at / + # match('/').to(:controller => 'whatever', :action =>'index') +end \ No newline at end of file diff --git a/web/doc/rdoc/generators/merb_generator.rb b/web/doc/rdoc/generators/merb_generator.rb new file mode 100644 index 000000000..544c273ad --- /dev/null +++ b/web/doc/rdoc/generators/merb_generator.rb @@ -0,0 +1,1362 @@ + +# # We're responsible for generating all the HTML files +# from the object tree defined in code_objects.rb. We +# generate: +# +# [files] an html file for each input file given. These +# input files appear as objects of class +# TopLevel +# +# [classes] an html file for each class or module encountered. +# These classes are not grouped by file: if a file +# contains four classes, we'll generate an html +# file for the file itself, and four html files +# for the individual classes. +# +# Method descriptions appear in whatever entity (file, class, +# or module) that contains them. +# +# We generate files in a structure below a specified subdirectory, +# normally +doc+. +# +# opdir +# | +# |___ files +# | |__ per file summaries +# | +# |___ classes +# |__ per class/module descriptions +# +# HTML is generated using the Template class. +# + +require 'ftools' + +require 'rdoc/options' +require 'rdoc/template' +require 'rdoc/markup/simple_markup' +require 'rdoc/markup/simple_markup/to_html' +require 'cgi' + +module Generators + + # Name of sub-direcories that hold file and class/module descriptions + + FILE_DIR = "files" + CLASS_DIR = "classes" + CSS_NAME = "stylesheet.css" + + + ## + # Build a hash of all items that can be cross-referenced. + # This is used when we output required and included names: + # if the names appear in this hash, we can generate + # an html cross reference to the appropriate description. + # We also use this when parsing comment blocks: any decorated + # words matching an entry in this list are hyperlinked. + + class AllReferences + @@refs = {} + + def AllReferences::reset + @@refs = {} + end + + def AllReferences.add(name, html_class) + @@refs[name] = html_class + end + + def AllReferences.[](name) + @@refs[name] + end + + def AllReferences.keys + @@refs.keys + end + end + + + ## + # Subclass of the SM::ToHtml class that supports looking + # up words in the AllReferences list. Those that are + # found (like AllReferences in this comment) will + # be hyperlinked + + class HyperlinkHtml < SM::ToHtml + # We need to record the html path of our caller so we can generate + # correct relative paths for any hyperlinks that we find + def initialize(from_path, context) + super() + @from_path = from_path + + @parent_name = context.parent_name + @parent_name += "::" if @parent_name + @context = context + end + + # We're invoked when any text matches the CROSSREF pattern + # (defined in MarkUp). If we fine the corresponding reference, + # generate a hyperlink. If the name we're looking for contains + # no punctuation, we look for it up the module/class chain. For + # example, HyperlinkHtml is found, even without the Generators:: + # prefix, because we look for it in module Generators first. + + def handle_special_CROSSREF(special) + name = special.text + if name[0,1] == '#' + lookup = name[1..-1] + name = lookup unless Options.instance.show_hash + else + lookup = name + end + + if /([A-Z].*)[.\#](.*)/ =~ lookup + container = $1 + method = $2 + ref = @context.find_symbol(container, method) + else + ref = @context.find_symbol(lookup) + end + + if ref and ref.document_self + "#{name}" + else + name #it does not need to be a link + end + end + + + # Generate a hyperlink for url, labeled with text. Handle the + # special cases for img: and link: described under handle_special_HYPEDLINK + def gen_url(url, text) + if url =~ /([A-Za-z]+):(.*)/ + type = $1 + path = $2 + else + type = "http" + path = url + url = "http://#{url}" + end + + if type == "link" + url = path + end + + if (type == "http" || type == "link") && url =~ /\.(gif|png|jpg|jpeg|bmp)$/ + "" + elsif (type == "http" || type == "link") + "#{text}" + else + "#{text.sub(%r{^#{type}:/*}, '')}" + + end + end + + # And we're invoked with a potential external hyperlink mailto: + # just gets inserted. http: links are checked to see if they + # reference an image. If so, that image gets inserted using an + # tag. Otherwise a conventional is used. We also + # support a special type of hyperlink, link:, which is a reference + # to a local file whose path is relative to the --op directory. + + def handle_special_HYPERLINK(special) + url = special.text + gen_url(url, url) + end + + # HEre's a hypedlink where the label is different to the URL + #

/, '') + res.sub!(/<\/p>$/, '') + end + res + end + + + def style_url(path, css_name=nil) + css_name ||= CSS_NAME + end + + # Build a webcvs URL with the given 'url' argument. URLs with a '%s' in them + # get the file's path sprintfed into them; otherwise they're just catenated + # together. + + def cvs_url(url, full_path) + if /%s/ =~ url + return sprintf( url, full_path ) + else + return url + full_path + end + end + end + + + ##################################################################### + # + # A Context is built by the parser to represent a container: contexts + # hold classes, modules, methods, require lists and include lists. + # ClassModule and TopLevel are the context objects we process here + # + class ContextUser + + include MarkUp + + attr_reader :context + + def initialize(context, options) + @context = context + @options = options + + end + + # convenience method to build a hyperlink # Where's the DRY in this?? Put this in the template where it belongs + def href(link, cls, name) + %{"#{name}"} + end + + # Create a list of HtmlMethod objects for each method + # in the corresponding context object. If the @options.show_all + # variable is set (corresponding to the --all option, + # we include all methods, otherwise just the public ones. + + def collect_methods + list = @context.method_list + unless @options.show_all + list = list.find_all {|m| m.visibility == :public || m.visibility == :protected || m.force_documentation } + end + @methods = list.collect {|m| HtmlMethod.new(m, self, @options) } + end + + # Build a summary list of all the methods in this context + def build_method_summary_list(path_prefix="") + collect_methods unless @methods + meths = @methods.sort + res = [] + meths.each do |meth| + res << { + "name" => CGI.escapeHTML(meth.name), + "aref" => meth.aref, + "href" => meth.path + } + end + res + end + + + # Build a list of aliases for which we couldn't find a + # corresponding method + def build_alias_summary_list(section) + values = [] + @context.aliases.each do |al| + next unless al.section == section + res = { + 'old_name' => al.old_name, + 'new_name' => al.new_name, + } + if al.comment && !al.comment.empty? + res['desc'] = markup(al.comment, true) + end + values << res + end + values + end + + # Build a list of constants + def build_constants_summary_list(section) + values = [] + @context.constants.each do |co| + next unless co.section == section + res = { + 'name' => co.name, + 'value' => CGI.escapeHTML(co.value) + } + res['desc'] = markup(co.comment, true) if co.comment && !co.comment.empty? + values << res + end + values + end + + def build_requires_list(context) + potentially_referenced_list(context.requires) {|fn| [fn + ".rb"] } + end + + def build_include_list(context) + potentially_referenced_list(context.includes) + end + + # Build a list from an array of Htmlxxx items. Look up each + # in the AllReferences hash: if we find a corresponding entry, + # we generate a hyperlink to it, otherwise just output the name. + # However, some names potentially need massaging. For example, + # you may require a Ruby file without the .rb extension, + # but the file names we know about may have it. To deal with + # this, we pass in a block which performs the massaging, + # returning an array of alternative names to match + + def potentially_referenced_list(array) + res = [] + array.each do |i| + ref = AllReferences[i.name] + # if !ref + # container = @context.parent + # while !ref && container + # name = container.name + "::" + i.name + # ref = AllReferences[name] + # container = container.parent + # end + # end + + ref = @context.find_symbol(i.name) + ref = ref.viewer if ref + + if !ref && block_given? + possibles = yield(i.name) + while !ref and !possibles.empty? + ref = AllReferences[possibles.shift] + end + end + h_name = CGI.escapeHTML(i.name) + if ref and ref.document_self + path = ref.path + res << { "name" => h_name, "href" => path } + else + res << { "name" => h_name, "href" => "" } + end + end + res + end + + # Build an array of arrays of method details. The outer array has up + # to six entries, public, private, and protected for both class + # methods, the other for instance methods. The inner arrays contain + # a hash for each method + + def build_method_detail_list(section) + outer = [] + + methods = @methods.sort + for singleton in [true, false] + for vis in [ :public, :protected, :private ] + res = [] + methods.each do |m| + if m.section == section and + m.document_self and + m.visibility == vis and + m.singleton == singleton + row = {} + if m.call_seq + row["callseq"] = m.call_seq.gsub(/->/, '→') + else + row["name"] = CGI.escapeHTML(m.name) + row["params"] = m.params + end + desc = m.description.strip + row["m_desc"] = desc unless desc.empty? + row["aref"] = m.aref + row["href"] = m.path + row["m_seq"] = m.seq + row["visibility"] = m.visibility.to_s + + alias_names = [] + m.aliases.each do |other| + if other.viewer # won't be if the alias is private + alias_names << { + 'name' => other.name, + 'href' => other.viewer.path, + 'aref' => other.viewer.aref + } + end + end + unless alias_names.empty? + row["aka"] = alias_names + end + + #if @options.inline_source + code = m.source_code + row["sourcecode"] = code if code + #else + # code = m.src_url + #if code + # row["codeurl"] = code + # row["imgurl"] = m.img_url + #end + #end + res << row + end + end + if res.size > 0 + outer << { + "type" => vis.to_s.capitalize, + "category" => singleton ? "Class" : "Instance", + "methods" => res + } + end + end + end + outer + end + + # Build the structured list of classes and modules contained + # in this context. + + def build_class_list(level, from, section, infile=nil) + res = "" + prefix = "  ::" * level; + + from.modules.sort.each do |mod| + next unless mod.section == section + next if infile && !mod.defined_in?(infile) + if mod.document_self + res << + prefix << + "Module " << + href(mod.viewer.path, "link", mod.full_name) << + "
\n" << + build_class_list(level + 1, mod, section, infile) + end + end + + from.classes.sort.each do |cls| + next unless cls.section == section + next if infile && !cls.defined_in?(infile) + if cls.document_self + res << + prefix << + "Class " << + href(cls.viewer.path, "link", cls.full_name) << + "
\n" << + build_class_list(level + 1, cls, section, infile) + end + end + + res + end + + def document_self + @context.document_self + end + + def diagram_reference(diagram) + res = diagram.gsub(/((?:src|href)=")(.*?)"/) { + $1 + $2 + '"' + } + res + end + + + # Find a symbol in ourselves or our parent + def find_symbol(symbol, method=nil) + res = @context.find_symbol(symbol, method) + if res + res = res.viewer + end + res + end + + # create table of contents if we contain sections + + def add_table_of_sections + toc = [] + @context.sections.each do |section| + if section.title + toc << { + 'secname' => section.title, + 'href' => section.sequence + } + end + end + + @values['toc'] = toc unless toc.empty? + end + + + end + + ##################################################################### + # + # Wrap a ClassModule context + + class HtmlClass < ContextUser + + @@c_seq = "C00000000" + + attr_reader :path + + def initialize(context, html_file, prefix, options) + super(context, options) + @@c_seq = @@c_seq.succ + @c_seq = @@c_seq + @html_file = html_file + @is_module = context.is_module? + @values = {} + + context.viewer = self + + @path = http_url(context.full_name, prefix) + + collect_methods + + AllReferences.add(name, self) + end + + # return the relative file name to store this class in, + # which is also its url + def http_url(full_name, prefix) + path = full_name.dup + if path['<<'] + path.gsub!(/<<\s*(\w*)/) { "from-#$1" } + end + File.join(prefix, path.split("::")) + ".html" + end + + def seq + @c_seq + end + + def aref + @c_seq + end + + def scope + a = @context.full_name.split("::") + if a.length > 1 + a.pop + a.join("::") + else + "" + end + end + + def name + @context.full_name.gsub("#{scope}::", '') + end + + def full_name + @context.full_name + end + + def parent_name + @context.parent.full_name + end + + def write_on(f) + value_hash + template = TemplatePage.new(RDoc::Page::BODY, + RDoc::Page::CLASS_PAGE, + RDoc::Page::METHOD_LIST) + template.write_html_on(f, @values) + end + + def value_hash + class_attribute_values + add_table_of_sections + + @values["charset"] = @options.charset + @values["style_url"] = style_url(path, @options.css) + + # Convert README to html + unless File.exist?('files/README.html') + File.open('files/README.html', 'w') do |file| + file << markup(File.read(File.expand_path(@options.main_page))) + end + end + + d = markup(@context.comment) + @values["description"] = d unless d.empty? + + ml = build_method_summary_list + @values["methods"] = ml unless ml.empty? + + il = build_include_list(@context) + @values["includes"] = il unless il.empty? + + @values["sections"] = @context.sections.map do |section| + + secdata = { + "sectitle" => section.title, + "secsequence" => section.sequence, + "seccomment" => markup(section.comment) + } + + al = build_alias_summary_list(section) + secdata["aliases"] = al unless al.empty? + + co = build_constants_summary_list(section) + secdata["constants"] = co unless co.empty? + + al = build_attribute_list(section) + secdata["attributes"] = al unless al.empty? + + cl = build_class_list(0, @context, section) + secdata["classlist"] = cl unless cl.empty? + + mdl = build_method_detail_list(section) + secdata["method_list"] = mdl unless mdl.empty? + + secdata + end + + @values + end + + def build_attribute_list(section) + atts = @context.attributes.sort + res = [] + atts.each do |att| + next unless att.section == section + if att.visibility == :public || att.visibility == :protected || @options.show_all + entry = { + "name" => CGI.escapeHTML(att.name), + "rw" => att.rw, + "a_desc" => markup(att.comment, true) + } + unless att.visibility == :public || att.visibility == :protected + entry["rw"] << "-" + end + res << entry + end + end + res + end + + def class_attribute_values + h_name = CGI.escapeHTML(name) + + @values["classmod"] = @is_module ? "Module" : "Class" + @values["title"] = "#{@values['classmod']}: #{h_name}" + + c = @context + c = c.parent while c and !c.diagram + if c && c.diagram + @values["diagram"] = diagram_reference(c.diagram) + end + + @values["full_name"] = h_name + @values["class_seq"] = seq + parent_class = @context.superclass + + if parent_class + @values["parent"] = CGI.escapeHTML(parent_class) + + if parent_name + lookup = parent_name + "::" + parent_class + else + lookup = parent_class + end + + parent_url = AllReferences[lookup] || AllReferences[parent_class] + + if parent_url and parent_url.document_self + @values["par_url"] = parent_url.path + end + end + + files = [] + @context.in_files.each do |f| + res = {} + full_path = CGI.escapeHTML(f.file_absolute_name) + + res["full_path"] = full_path + res["full_path_url"] = f.viewer.path if f.document_self + + if @options.webcvs + res["cvsurl"] = cvs_url( @options.webcvs, full_path ) + end + + files << res + end + + @values['infiles'] = files + end + + def <=>(other) + self.name <=> other.name + end + + end + + ##################################################################### + # + # Handles the mapping of a file's information to HTML. In reality, + # a file corresponds to a +TopLevel+ object, containing modules, + # classes, and top-level methods. In theory it _could_ contain + # attributes and aliases, but we ignore these for now. + + class HtmlFile < ContextUser + + @@f_seq = "F00000000" + + attr_reader :path + attr_reader :name + + def initialize(context, options, file_dir) + super(context, options) + @@f_seq = @@f_seq.succ + @f_seq = @@f_seq + @values = {} + + @path = http_url(file_dir) + @source_file_path = File.expand_path(@context.file_relative_name).gsub("\/doc\/", "/") + @name = @context.file_relative_name + + collect_methods + AllReferences.add(name, self) + context.viewer = self + end + + def http_url(file_dir) + File.join(file_dir, @context.file_relative_name.tr('.', '_')) + ".html" + end + + def filename_to_label + @context.file_relative_name.gsub(/%|\/|\?|\#/) {|s| '%' + ("%x" % s[0]) } + end + + def seq + @f_seq + end + + def aref + @f_seq + end + + def name + full_path = @context.file_absolute_name + short_name = File.basename(full_path) + end + + def full_name + @context.file_absolute_name + end + + def scope + @context.file_relative_name.gsub(/\/#{name}$/, '') + end + + def parent_name + nil + end + + def full_file_source + ret_str = "" + File.open(@source_file_path, 'r') do |f| + while(!f.eof?) do + ret_str += f.readline() + end + end + ret_str + rescue + "file not found -#{@source_file_path}-" + #@source_file_path + end + + def value_hash + file_attribute_values + add_table_of_sections + + @values["charset"] = @options.charset + @values["href"] = path + @values["style_url"] = style_url(path, @options.css) + @values["file_seq"] = seq + + #pulling in the source for this file + #@values["source_code"] = @context.token_stream + + @values["file_source_code"] = CGI.escapeHTML(full_file_source) + + if @context.comment + d = markup(@context.comment) + @values["description"] = d if d.size > 0 + end + + ml = build_method_summary_list + @values["methods"] = ml unless ml.empty? + + il = build_include_list(@context) + @values["includes"] = il unless il.empty? + + rl = build_requires_list(@context) + @values["requires"] = rl unless rl.empty? + + + file_context = @context + + @values["sections"] = @context.sections.map do |section| + + secdata = { + "sectitle" => section.title, + "secsequence" => section.sequence, + "seccomment" => markup(section.comment) + } + + cl = build_class_list(0, @context, section, file_context) + @values["classlist"] = cl unless cl.empty? + + mdl = build_method_detail_list(section) + secdata["method_list"] = mdl unless mdl.empty? + + al = build_alias_summary_list(section) + secdata["aliases"] = al unless al.empty? + + co = build_constants_summary_list(section) + @values["constants"] = co unless co.empty? + + secdata + end + + @values + end + + def write_on(f) + value_hash + template = TemplatePage.new(RDoc::Page::SRC_BODY,RDoc::Page::FILE_PAGE, RDoc::Page::METHOD_LIST) + template.write_html_on(f, @values) + end + + def file_attribute_values + full_path = @context.file_absolute_name + short_name = File.basename(full_path) + + @values["title"] = CGI.escapeHTML("File: #{short_name}") + + if @context.diagram + @values["diagram"] = diagram_reference(@context.diagram) + end + + @values["short_name"] = CGI.escapeHTML(short_name) + @values["full_path"] = CGI.escapeHTML(full_path) + @values["dtm_modified"] = @context.file_stat.mtime.to_s + + if @options.webcvs + @values["cvsurl"] = cvs_url( @options.webcvs, @values["full_path"] ) + end + end + + def <=>(other) + self.name <=> other.name + end + end + + ##################################################################### + + class HtmlMethod + include MarkUp + + attr_reader :context + attr_reader :src_url + attr_reader :img_url + attr_reader :source_code + + @@m_seq = "M000000" + + @@all_methods = [] + + def HtmlMethod::reset + @@all_methods = [] + end + + def initialize(context, html_class, options) + @context = context + @html_class = html_class + @options = options + @@m_seq = @@m_seq.succ + @m_seq = @@m_seq + @@all_methods << self + + context.viewer = self + + if (ts = @context.token_stream) + @source_code = markup_code(ts) + #unless @options.inline_source + # @src_url = create_source_code_file(@source_code) + # @img_url = MERBGenerator.gen_url(path, 'source.png') + #end + end + AllReferences.add(name, self) + end + + def seq + @m_seq + end + + def aref + @m_seq + end + + def scope + @html_class.full_name + end + + # return a reference to outselves to be used as an href= + # the form depends on whether we're all in one file + # or in multiple files + + def name + @context.name + end + + def section + @context.section + end + + def parent_name + if @context.parent.parent + @context.parent.parent.full_name + else + nil + end + end + + def path + @html_class.path + end + + def description + markup(@context.comment) + end + + def visibility + @context.visibility + end + + def singleton + @context.singleton + end + + def call_seq + cs = @context.call_seq + if cs + cs.gsub(/\n/, "
\n") + else + nil + end + end + + def params + # params coming from a call-seq in 'C' will start with the + # method name + p = @context.params + if p !~ /^\w/ + p = @context.params.gsub(/\s*\#.*/, '') + p = p.tr("\n", " ").squeeze(" ") + p = "(" + p + ")" unless p[0] == ?( + + if (block = @context.block_params) + # If this method has explicit block parameters, remove any + # explicit &block + + p.sub!(/,?\s*&\w+/, '') + + block.gsub!(/\s*\#.*/, '') + block = block.tr("\n", " ").squeeze(" ") + if block[0] == ?( + block.sub!(/^\(/, '').sub!(/\)/, '') + end + p << " {|#{block.strip}| ...}" + end + end + CGI.escapeHTML(p) + end + + def create_source_code_file(code_body) + meth_path = @html_class.path.sub(/\.html$/, '.src') + File.makedirs(meth_path) + file_path = File.join(meth_path, seq) + ".html" + + template = TemplatePage.new(RDoc::Page::SRC_PAGE) + File.open(file_path, "w") do |f| + values = { + 'title' => CGI.escapeHTML(name), + 'code' => code_body, + 'style_url' => style_url(file_path, @options.css), + 'charset' => @options.charset + } + template.write_html_on(f, values) + end + file_path + end + + def HtmlMethod.all_methods + @@all_methods + end + + def <=>(other) + @context <=> other.context + end + + ## + # Given a sequence of source tokens, mark up the source code + # to make it look purty. + + + def markup_code(tokens) + src = "" + tokens.each do |t| + next unless t + # p t.class + # style = STYLE_MAP[t.class] + style = case t + when RubyToken::TkCONSTANT then "ruby-constant" + when RubyToken::TkKW then "ruby-keyword kw" + when RubyToken::TkIVAR then "ruby-ivar" + when RubyToken::TkOp then "ruby-operator" + when RubyToken::TkId then "ruby-identifier" + when RubyToken::TkNode then "ruby-node" + when RubyToken::TkCOMMENT then "ruby-comment cmt" + when RubyToken::TkREGEXP then "ruby-regexp re" + when RubyToken::TkSTRING then "ruby-value str" + when RubyToken::TkVal then "ruby-value" + else + nil + end + + text = CGI.escapeHTML(t.text) + + if style + src << "#{text}" + else + src << text + end + end + + add_line_numbers(src) + src + end + + # we rely on the fact that the first line of a source code + # listing has + # # File xxxxx, line dddd + + def add_line_numbers(src) + if src =~ /\A.*, line (\d+)/ + first = $1.to_i - 1 + last = first + src.count("\n") + size = last.to_s.length + real_fmt = "%#{size}d: " + fmt = " " * (size+2) + src.gsub!(/^/) do + res = sprintf(fmt, first) + first += 1 + fmt = real_fmt + res + end + end + end + + def document_self + @context.document_self + end + + def aliases + @context.aliases + end + + def find_symbol(symbol, method=nil) + res = @context.parent.find_symbol(symbol, method) + if res + res = res.viewer + end + res + end + end + + ##################################################################### + + class MERBGenerator + + include MarkUp + + # Generators may need to return specific subclasses depending + # on the options they are passed. Because of this + # we create them using a factory + + def MERBGenerator.for(options) + AllReferences::reset + HtmlMethod::reset + + MERBGenerator.new(options) + + end + + class < "helvetica"} #this is not used anywhere but the template function demands a hash of values + template.write_html_on(f, values) + end + end + + def write_javascript + #Argh... I couldn't figure out how to copy these from the template dir so they were copied into + # the template file "ajax.rb" and processed similarlly to the style sheets. Not exactly a good thing to do with + # external library code. Not very DRY. + + File.open("api_grease.js", "w") do |f| + f << RDoc::Page::API_GREASE_JS + end + + File.open("prototype.js", "w") do |f| + f << RDoc::Page::PROTOTYPE_JS + end + + rescue LoadError + $stderr.puts "Could not find AJAX template" + exit 99 + end + + ## + # See the comments at the top for a description of the + # directory structure + + def gen_sub_directories + File.makedirs(FILE_DIR, CLASS_DIR) + rescue + $stderr.puts $!.message + exit 1 + end + + ## + # Generate: + # + # * a list of HtmlFile objects for each TopLevel object. + # * a list of HtmlClass objects for each first level + # class or module in the TopLevel objects + # * a complete list of all hyperlinkable terms (file, + # class, module, and method names) + + def build_indices + + @toplevels.each do |toplevel| + @files << HtmlFile.new(toplevel, @options, FILE_DIR) + end + + RDoc::TopLevel.all_classes_and_modules.each do |cls| + build_class_list(cls, @files[0], CLASS_DIR) + end + end + + def build_class_list(from, html_file, class_dir) + @classes << HtmlClass.new(from, html_file, class_dir, @options) + from.each_classmodule do |mod| + build_class_list(mod, html_file, class_dir) + end + end + + ## + # Generate all the HTML + # + def generate_html + # the individual descriptions for files and classes + gen_into(@files) + gen_into(@classes) + # and the index files + gen_file_index + gen_class_index + gen_method_index + gen_main_index + + # this method is defined in the template file + write_extra_pages if defined? write_extra_pages + end + + def gen_into(list) + list.each do |item| + if item.document_self + op_file = item.path + File.makedirs(File.dirname(op_file)) + File.open(op_file, "w") { |file| item.write_on(file) } + end + end + + end + + def gen_file_index + gen_an_index(@files, 'Files', + RDoc::Page::FILE_INDEX, + "fr_file_index.html") + end + + def gen_class_index + gen_an_index(@classes, 'Classes', + RDoc::Page::CLASS_INDEX, + "fr_class_index.html") + end + + def gen_method_index + gen_an_index(HtmlMethod.all_methods, 'Methods', + RDoc::Page::METHOD_INDEX, + "fr_method_index.html") + end + + + def gen_an_index(collection, title, template, filename) + template = TemplatePage.new(RDoc::Page::FR_INDEX_BODY, template) + res = [] + collection.sort.each do |f| + if f.document_self + res << { "href" => f.path, "name" => f.name, "scope" => f.scope, "seq_id" => f.seq } + end + end + + values = { + "entries" => res, + 'list_title' => CGI.escapeHTML(title), + 'index_url' => main_url, + 'charset' => @options.charset, + 'style_url' => style_url('', @options.css), + } + + File.open(filename, "w") do |f| + template.write_html_on(f, values) + end + end + + # The main index page is mostly a template frameset, but includes + # the initial page. If the --main option was given, + # we use this as our main page, otherwise we use the + # first file specified on the command line. + + def gen_main_index + template = TemplatePage.new(RDoc::Page::INDEX) + File.open("index.html", "w") do |f| + tStr = "" + #File.open(main_url, 'r') do |g| + # tStr = markup(g) + #end + values = { + "initial_page" => tStr, + 'title' => CGI.escapeHTML(@options.title), + 'charset' => @options.charset, + 'content' => File.read('files/README.html') + } + + values['inline_source'] = true + template.write_html_on(f, values) + end + end + + # return the url of the main page + def main_url + "files/README.html" + end + + + end + +end diff --git a/web/doc/rdoc/generators/template/merb/api_grease.js b/web/doc/rdoc/generators/template/merb/api_grease.js new file mode 100644 index 000000000..3df710e8b --- /dev/null +++ b/web/doc/rdoc/generators/template/merb/api_grease.js @@ -0,0 +1,640 @@ + +function setupPage(){ + hookUpActiveSearch(); + hookUpTabs(); + suppressPostbacks(); + var url_params = getUrlParams(); + if (url_params != null){ + loadUrlParams(url_params); + }else{ + loadDefaults(); + } + resizeDivs(); + window.onresize = function(){ resizeDivs(); }; +} + +function getUrlParams(){ + var window_location = window.location.href + var param_pos = window_location.search(/\?/) + if (param_pos > 0){ + return(window_location.slice(param_pos, window_location.length)); + }else{ + return(null); + } +} + +function loadUrlParams(url_param){ + //get the tabs + var t = getTabs(); + // now find our variables + var s_params = /(\?)(a=.+?)(&)(name=.*)/; + var results = url_param.match(s_params); + url_anchor = results[2].replace(/a=/,''); + + if (url_anchor.match(/M.+/)){//load the methods tab and scroller content + setActiveTabAndLoadContent(t[0]); + }else{ + if(url_anchor.match(/C.+/)){ //load the classes tab and scroller content + setActiveTabAndLoadContent(t[1]); + }else{ + if (url_anchor.match(/F.+/)){//load the files tab + setActiveTabAndLoadContent(t[2]); + }else{ + // default to loading the methods + setActiveTabAndLoadContent(t[0]); + } + } + } + paramLoadOfContentAnchor(url_anchor + "_link"); +} + +function updateUrlParams(anchor_id, name){ + //Also setting the page title + //window.document.title = name + " method - MerbBrain.com "; + + //updating the window location + var current_href = window.location.href; + //var m_name = name.replace("?","?"); + var rep_str = ".html?a=" + anchor_id + "&name=" + name; + var new_href = current_href.replace(/\.html.*/, rep_str); + if (new_href != current_href){ + window.location.href = new_href; + } +} + +//does as it says... +function hookUpActiveSearch(){ + + var s_field = $('searchForm').getInputs('text')[0]; + //var s_field = document.forms[0].searchText; + Event.observe(s_field, 'keydown', function(event) { + var el = Event.element(event); + var key = event.which || event.keyCode; + + switch (key) { + case Event.KEY_RETURN: + forceLoadOfContentAnchor(getCurrentAnchor()); + Event.stop(event); + break; + + case Event.KEY_UP: + scrollListToElementOffset(getCurrentAnchor(),-1); + break; + + case Event.KEY_DOWN: + scrollListToElementOffset(getCurrentAnchor(),1); + break; + + default: + break; + } + + }); + + Event.observe(s_field, 'keyup', function(event) { + var el = Event.element(event); + var key = event.which || event.keyCode; + switch (key) { + case Event.KEY_RETURN: + Event.stop(event); + break; + + case Event.KEY_UP: + break; + + case Event.KEY_DOWN: + break; + + default: + scrollToName(el.value); + setSavedSearch(getCurrentTab(), el.value); + break; + } + + }); + + Event.observe(s_field, 'keypress', function(event){ + var el = Event.element(event); + var key = event.which || event.keyCode; + switch (key) { + case Event.KEY_RETURN: + Event.stop(event); + break; + + default: + break; + } + + }); + + //Event.observe(document, 'keypress', function(event){ + // var key = event.which || event.keyCode; + // if (key == Event.KEY_TAB){ + // cycleNextTab(); + // Event.stop(event); + // } + //}); +} + +function hookUpTabs(){ + + var tabs = getTabs(); + for(x=0; x < tabs.length; x++) + { + Event.observe(tabs[x], 'click', function(event){ + var el = Event.element(event); + setActiveTabAndLoadContent(el); + }); + //tabs[x].onclick = function (){ return setActiveTabAndLoadContent(this);}; //the prototype guys say this is bad.. + } + +} + +function suppressPostbacks(){ + Event.observe('searchForm', 'submit', function(event){ + Event.stop(event); + }); +} + +function loadDefaults(){ + var t = getTabs(); + setActiveTabAndLoadContent(t[0]); //default loading of the first tab +} + +function resizeDivs(){ + var inner_height = 700; + if (window.innerHeight){ + inner_height = window.innerHeight; //all browsers except IE use this to determine the space available inside a window. Thank you Microsoft!! + }else{ + if(document.documentElement.clientHeight > 0){ //IE uses this in 'strict' mode + inner_height = document.documentElement.clientHeight; + }else{ + inner_height = document.body.clientHeight; //IE uses this in 'quirks' mode + } + } + $('rdocContent').style.height = (inner_height - 92) + "px";//Thankfully all browsers can agree on how to set the height of a div + $('listScroller').style.height = (inner_height - 88) + "px"; +} + +//The main function for handling clicks on the tabs +function setActiveTabAndLoadContent(current_tab){ + changeLoadingStatus("on"); + var tab_string = String(current_tab.innerHTML).strip(); //thank you ProtoType! + switch (tab_string){ + case "classes": + setCurrentTab("classes"); + loadScrollerContent('fr_class_index.html'); + setSearchFieldValue(getSavedSearch("classes")); + scrollToName(getSavedSearch("classes")); + setSearchFocus(); + break; + + case "files": + setCurrentTab("files"); + loadScrollerContent('fr_file_index.html'); + setSearchFieldValue(getSavedSearch("files")); + scrollToName(getSavedSearch("files")); + setSearchFocus(); + break; + + case "methods": + setCurrentTab("methods"); + loadScrollerContent('fr_method_index.html'); + setSearchFieldValue(getSavedSearch("methods")); + scrollToName(getSavedSearch("methods")); + setSearchFocus(); + break; + + default: + break; + } + changeLoadingStatus("off"); +} + +function cycleNextTab(){ + var currentT = getCurrentTab(); + var tabs = getTabs(); + if (currentT == "methods"){ + setActiveTabAndLoadContent(tabs[1]); + setSearchFocus(); + }else{ + if (currentT == "classes"){ + setActiveTabAndLoadContent(tabs[2]); + setSearchFocus(); + }else{ + if (currentT == "files"){ + setActiveTabAndLoadContent(tabs[0]); + setSearchFocus(); + } + } + } +} + +function getTabs(){ + return($('groupType').getElementsByTagName('li')); +} + +var Active_Tab = ""; +function getCurrentTab(){ + return Active_Tab; +} + +function setCurrentTab(tab_name){ + var tabs = getTabs(); + for(x=0; x < tabs.length; x++) + { + if(tabs[x].innerHTML.strip() == tab_name) //W00t!!! String.prototype.strip! + { + tabs[x].className = "activeLi"; + Active_Tab = tab_name; + } + else + { + tabs[x].className = ""; + } + } +} + +//These globals should not be used globally (hence the getters and setters) +var File_Search = ""; +var Method_Search = ""; +var Class_Search = ""; +function setSavedSearch(tab_name, s_val){ + switch(tab_name){ + case "methods": + Method_Search = s_val; + break; + case "files": + File_Search = s_val; + break; + case "classes": + Class_Search = s_val; + break; + } +} + +function getSavedSearch(tab_name){ + switch(tab_name){ + case "methods": + return (Method_Search); + break; + case "files": + return (File_Search); + break; + case "classes": + return (Class_Search); + break; + } +} + +//These globals handle the history stack + + +function setListScrollerContent(s){ + + $('listScroller').innerHTML = s; +} + +function setMainContent(s){ + + $('rdocContent').innerHTML = s; +} + +function setSearchFieldValue(s){ + + document.forms[0].searchText.value = s; +} + +function getSearchFieldValue(){ + + return Form.Element.getValue('searchText'); +} + +function setSearchFocus(){ + + document.forms[0].searchText.focus(); +} + +var Anchor_ID_Of_Current = null; // holds the last highlighted anchor tag in the scroll lsit +function getCurrentAnchor(){ + return(Anchor_ID_Of_Current); +} + +function setCurrentAnchor(a_id){ + Anchor_ID_Of_Current = a_id; +} + +//var Index_Of_Current = 0; //holds the last highlighted index +//function getCurrentIndex(){ +// return (Index_Of_Current); +//} + +//function setCurrentIndex(new_i){ +// Index_Of_Current = new_i; +//} + +function loadScrollerContent(url){ + + var scrollerHtml = new Ajax.Request(url, { + asynchronous: false, + method: 'get', + onComplete: function(method_data) { + setListScrollerContent(method_data.responseText); + } + }); + +} + +//called primarily from the links inside the scroller list +//loads the main page div then jumps to the anchor/element with id +function loadContent(url, anchor_id){ + + var mainHtml = new Ajax.Request(url, { + method: 'get', + onLoading: changeLoadingStatus("on"), + onSuccess: function(method_data) { + setMainContent(method_data.responseText);}, + onComplete: function(request) { + changeLoadingStatus("off"); + new jumpToAnchor(anchor_id); + } + }); +} + +//An alternative function that also will stuff the index history for methods, files, classes +function loadIndexContent(url, anchor_id, name, scope) +{ + if (From_URL_Param == true){ + var mainHtml = new Ajax.Request(url, { + method: 'get', + onLoading: changeLoadingStatus("on"), + onSuccess: function(method_data) { + setMainContent(method_data.responseText);}, + onComplete: function(request) { + changeLoadingStatus("off"); + updateBrowserBar(name, anchor_id, scope); + new jumpToAnchor(anchor_id);} + }); + From_URL_Param = false; + }else{ + updateUrlParams(anchor_id, name); + } + +} + +function updateBrowserBar(name, anchor_id, scope){ + if (getCurrentTab() == "methods"){ + $('browserBarInfo').update("class/module: " + scope + "  method: " + name + " "); + }else{ if(getCurrentTab() == "classes"){ + $('browserBarInfo').update("class/module: " + scope + "::" + name + " "); + }else{ + $('browserBarInfo').update("file: " + scope + "/" + name + " "); + } + } +} + + +// Force loads the contents of the index of the current scroller list. It does this by +// pulling the onclick method out and executing it manually. +function forceLoadOfContent(index_to_load){ + var scroller = $('listScroller'); + var a_array = scroller.getElementsByTagName('a'); + if ((index_to_load >= 0) && (index_to_load < a_array.length)){ + var load_element = a_array[index_to_load]; + var el_text = load_element.innerHTML.strip(); + setSearchFieldValue(el_text); + setSavedSearch(getCurrentTab(), el_text); + eval("new " + load_element.onclick); + } +} + +function forceLoadOfContentAnchor(anchor_id){ + + var load_element = $(anchor_id); + if (load_element != null){ + var el_text = load_element.innerHTML.strip(); + setSearchFieldValue(el_text); + scrollToAnchor(anchor_id); + setSavedSearch(getCurrentTab(), el_text); + eval("new " + load_element.onclick); + } +} + +var From_URL_Param = false; +function paramLoadOfContentAnchor(anchor_id){ + From_URL_Param = true; + forceLoadOfContentAnchor(anchor_id); +} + +//this handles the up/down keystrokes to move the selection of items in the list +function scrollListToElementOffset(anchor_id, offset){ + var scroller = $('listScroller'); + var a_array = scroller.getElementsByTagName('a'); + var current_index = findIndexOfAnchor(a_array, anchor_id); + if ((current_index >= 0) && (current_index < a_array.length)){ + scrollListToAnchor(a_array[current_index + offset].id); + setListActiveAnchor(a_array[current_index + offset].id); + } +} + +function findIndexOfAnchor(a_array, anchor_id){ + var found=false; + var counter = 0; + while(!found && counter < a_array.length){ + if (a_array[counter].id == anchor_id){ + found = true; + }else{ + counter +=1; + } + } + return(counter); +} + +function scrollToName(searcher_name){ + + var scroller = $('listScroller'); + var a_array = scroller.getElementsByTagName('a'); + + if (!searcher_name.match(new RegExp(/\s+/))){ //if searcher name is blank + + var searcher_pattern = new RegExp("^"+searcher_name, "i"); //the "i" is for case INsensitive + var found_index = -1; + + var found = false; + var x = 0; + while(!found && x < a_array.length){ + if(a_array[x].innerHTML.match(searcher_pattern)){ + found = true; + found_index = x; + } + else{ + x++; + } + } + + // // an attempt at binary searching... have not given up on this yet... + //found_index = binSearcher(searcher_pattern, a_array, 0, a_array.length); + + if ((found_index >= 0) && (found_index < a_array.length)){ + + scrollListToAnchor(a_array[found_index].id);//scroll to the item + setListActiveAnchor(a_array[found_index].id);//highlight the item + } + }else{ //since searcher name is blank + //scrollListToIndex(a_array, 0);//scroll to the item + //setListActiveItem(a_array, 0);//highlight the item + } +} + +function scrollToAnchor(anchor_id){ + var scroller = $('listScroller'); + if ($(anchor_id) != null){ + scrollListToAnchor(anchor_id); + setListActiveAnchor(anchor_id); + } +} + +function getY(element){ + + var y = 0; + for( var e = element; e; e = e.offsetParent)//iterate the offset Parents + { + y += e.offsetTop; //add up the offsetTop values + } + //for( e = element.parentNode; e && e != document.body; e = e.parentNode) + // if (e.scrollTop) y -= e.scrollTop; //subtract scrollbar values + return y; +} + +//function setListActiveItem(item_array, active_index){ +// +// item_array[getCurrentIndex()].className = ""; +// setCurrentIndex(active_index); +// item_array[getCurrentIndex()].className = "activeA"; //setting the active class name +//} + +function setListActiveAnchor(active_anchor){ + if ((getCurrentAnchor() != null) && ($(getCurrentAnchor()) != null)){ + $(getCurrentAnchor()).className = ""; + } + setCurrentAnchor(active_anchor); + $(getCurrentAnchor()).className = "activeA"; + +} + +//handles the scrolling of the list and setting of the current index +//function scrollListToIndex(a_array, scroll_index){ +// if (scroll_index > 0){ +// var scroller = $('listScroller'); +// scroller.scrollTop = getY(a_array[scroll_index]) - 120; //the -120 is what keeps it from going to the top... +// } +//} + +function scrollListToAnchor(scroll2_anchor){ + var scroller = $('listScroller'); + scroller.scrollTop = getY($(scroll2_anchor)) - 120; +} + +function jumpToAnchor(anchor_id){ + + var contentScroller = $('rdocContent'); + var a_div = $(anchor_id); + contentScroller.scrollTop = getY(a_div) - 80; //80 is the offset to adjust scroll point + var a_title = $(anchor_id + "_title"); + a_title.style.backgroundColor = "#222"; + a_title.style.color = "#FFF"; + a_title.style.padding = "3px"; + // a_title.style.borderBottom = "2px solid #ccc"; + + //other attempts + //a_div.className = "activeMethod"; //setting the active class name + //a_div.style.backgroundColor = "#ffc"; + //var titles = a_div.getElementsByClassName("title"); + //titles[0].className = "activeTitle"; + +} + +function jumpToTop(){ + $('rdocContent').scrollTop = 0; +} + +function changeLoadingStatus(status){ + if (status == "on"){ + $('loadingStatus').show(); + } + else{ + $('loadingStatus').hide(); + } +} + +//************* Misc functions (mostly from the old rdocs) *********************** +//snagged code from the old templating system +function toggleSource( id ){ + + var elem + var link + + if( document.getElementById ) + { + elem = document.getElementById( id ) + link = document.getElementById( "l_" + id ) + } + else if ( document.all ) + { + elem = eval( "document.all." + id ) + link = eval( "document.all.l_" + id ) + } + else + return false; + + if( elem.style.display == "block" ) + { + elem.style.display = "none" + link.innerHTML = "show source" + } + else + { + elem.style.display = "block" + link.innerHTML = "hide source" + } +} + +function openCode( url ){ + window.open( url, "SOURCE_CODE", "width=400,height=400,scrollbars=yes" ) +} + +//this function handles the ajax calling and afterits loaded the jumping to the anchor... +function jsHref(url){ + //alert(url); + var mainHtml = new Ajax.Request(url, { + method: 'get', + onSuccess: function(method_data) { + setMainContent(method_data.responseText);} + }); +} + +//function comparePatterns(string, regexp){ +// var direction = 0; +// +// +// return (direction) +//} + +////returns the index of the element +//function binSearcher(regexp_pattern, list, start_index, stop_index){ +// //divide the list in half +// var split_point = 0; +// split_point = parseInt((stop_index - start_index)/2); +// direction = comparePatterns(list[split_point].innerHTML, regexp_pattern); +// if(direction < 0) +// return (binSearcher(regexp_pattern, list, start_index, split_point)); +// else +// if(direction > 0) +// return (binSearcher(regexp_pattern, list, split_point, stop_index)); +// else +// return(split_point); +// +//} + + + diff --git a/web/doc/rdoc/generators/template/merb/index.html.erb b/web/doc/rdoc/generators/template/merb/index.html.erb new file mode 100644 index 000000000..5c8e79867 --- /dev/null +++ b/web/doc/rdoc/generators/template/merb/index.html.erb @@ -0,0 +1,37 @@ + + + + Documentation + + + + + + + + +

+ + +
+ + diff --git a/web/doc/rdoc/generators/template/merb/merb.css b/web/doc/rdoc/generators/template/merb/merb.css new file mode 100644 index 000000000..e4f5531b8 --- /dev/null +++ b/web/doc/rdoc/generators/template/merb/merb.css @@ -0,0 +1,252 @@ +html, body, div, span, applet, object, iframe,h1, h2, h3, h4, h5, h6, p, blockquote, pre,a, abbr, acronym, address, big, cite, code,del, dfn, em, font, img, ins, kbd, q, s, samp,small, strike, strong, sub, sup, tt, var,dl, dt, dd, ol, ul, li,fieldset, form, label, legend,table, caption, tbody, tfoot, thead, tr, th, td { + margin: 0; + padding: 0; + border: 0; + font-weight: inherit; + font-style: inherit; + font-size: 100%; + font-family: inherit; + vertical-align: baseline; } + +/* GENERAL RULES */ + +body { + background: #000 url(../img/body.gif) repeat-x bottom center; + color: #000; + font: normal 12px "Lucida Grande", "Arial", sans-serif; + line-height: 1; +} +ul {list-style-type: none;} +#content_full ul.revisions{list-style-type: disc;} +#content_full ul.revisions li{margin-left: 15px;padding: 3px 0;} +li a {display: block;} +#content_full ul.revisions li a{display: inline;} +strong {font-weight: bold;} +table {border-collapse: separate;border-spacing: 0; } +caption, th, td {text-align: left;font-weight: normal; } +.invisible {display: none;} +.full_width {width:100%;} + +/* LAYOUT */ + +.wrap_to_center, #foot { + margin: 0 auto; + display: block; + width: 800px; +} + +#content {width: 100%;} + +#content_top { + background: #fff url(../img/content_top.gif) no-repeat top center; + float:left; + margin:25px 0px; + width:100%; +} +#content_bottom { + background: url(../img/content_bottom.gif) no-repeat bottom center; + width:100%; + float:left; +} +#content_main { + float:left; + margin: 10px 20px 20px 20px; + width:506px; +} +#content p { + line-height:17px; +} +#content_full {margin: 10px 20px 20px 20px;} + +/* HEADER & NAVIGATION */ + +#header { + background: #1db900 url(../img/header_waves.gif) repeat-x top center; + height:74px; + width: 100%; +} +#waves { + background: url(../img/header_waves.gif)no-repeat top left; + height:74px; + width:980px; +} +#header img {margin-top:8px; float:left;} +#header a {color:#fff; text-decoration:none;} +#header a:hover {color:#000;} +ul#nav {float:right;display:block;width:43.3em;margin-top:25px;} +ul#nav li {display:block;float:left;} +ul#nav li a {display:block;float:left;margin:0px 5px;padding:6px 9px 31px 9px;} +ul#nav li a:hover {background:url(../img/header_hover.gif) repeat-x bottom center;} +ul#nav li a#active {background:url(../img/header_arrow.gif)no-repeat bottom center;} +ul#nav li.last a {margin-right:0;} + +/* TEXT FORMATTING */ + +h1 { + border-bottom:2px solid #ccc; + color:#000; + font:bold 28px "Arial" sans-serif; + letter-spacing:1px; + margin:20px 0px; + text-align:left; + width:100%; +} +h1.home { + border:0; + color:#fff; + font-size:36px; + margin:20px 0px; + text-align:center; +} +h2 { + color:#7aad00; + font:bold 22px "Lucida Grande" sans-serif; + margin:10px 0px; +} +h3 { + font:bold 16px "Lucida Grande"; + margin:10px 0px; +} +#content a {color:#d7ff00;} +#content a:hover {background:#d7ff00;color:#000;} +#content_main ul {margin:10px 0px;} +#content_main ul li { + background: url(../img/li.gif) no-repeat left center; + padding: 4px 4px 4px 16px; + font-weight:bold; +} +p {margin-bottom:12px;} +#content_main a,#content_full a {color:#11b716;font-weight:bold;} +#content_main a:hover,#content_full a:hover {background:#22d716;} +pre { + background:#222; + color:#fff; + font:12px "Courier" serif; + line-height:18px; + padding: 12px; + margin-bottom: 10px; +} +code { + font:bold 12px "Courier" serif; +} +pre code {font-weight:normal;} + +/* SIDEBAR FOR CONTENT */ + +#content_sidebar { + float: left; + margin: 20px 20px 15px 10px; + width: 224px; +} +.sidebar_top { + background:#868686 url(../img/sidebar_top.gif) no-repeat top center; + margin-bottom:12px; + width:224px; +} +dl.sidebar_bottom { + background: url(../img/sidebar_bottom.gif) no-repeat bottom center; + padding:12px; +} +dl.sidebar_bottom dt { + color:#fff; + font:bold 14px "Lucida Grande" sans-serif; + margin-bottom:6px; +} +dl.sidebar_bottom dd {padding:3px 0px;} +#content_sidebar p {padding:10px 0px;} +p#rss a { + background: url(../img/rss.gif) no-repeat left center; + color:#000; + font:bold 14px "Lucida Grande"; + padding: 8px 6px 8px 34px; + text-decoration:none; +} +p#rss a:hover { + background: url(../img/rss.gif) no-repeat left center; + background-color:#fff; + text-decoration:underline; +} + +/* FOOTER */ + +#footer {background:#444; clear:both;} +#footer p {padding:12px; color:#999; margin:0; text-align:center;} + +/* FEATURES PAGE */ +.feature { + background-repeat:no-repeat; + background-position:top left; + border-bottom:2px solid #ccc; + padding-left:150px; +} +div#speed {background-image: url(../img/feature_speed.gif);} +div#light {background-image: url(../img/feature_light.gif);} +div#power {background-image: url(../img/feature_power.gif);} + +.quicklinks_top { + background:#868686 url(../img/quicklinks_top.gif) no-repeat top center; + float:right; + margin-bottom:12px; + width:169px; +} +ul.quicklinks_bottom { + background: url(../img/quicklinks_bottom.gif) no-repeat bottom center; + padding:12px; +} +ul.quicklinks_bottom li { + display:block; + padding:3px 0px; +} +#content_full ul.quicklinks_bottom li a{ + color:#d7ff00; + display:inline; +} +#content_full ul.quicklinks_bottom li a:hover { + background:#d7ff00; + color:#000; +} + +/* DOCUMENTATION PAGE */ +.sub-framework { + border-bottom:2px solid #ccc; + margin-bottom: 20px; + padding-bottom: 10px; +} + + +/* ICONS FOR HOMEPAGE */ + +#icons_top { + background: url(../img/icons_top.gif) no-repeat top center; + float:left; + width:800px; +} +#icons_bottom { + background: url(../img/icons_bottom.gif) no-repeat bottom center; + float:left; + width:800px; +} +#icons_top dl { + color:#fff; + float:left; + width: 224px; + padding: 15px 20px; +} +#icons_top dt { + background-repeat:no-repeat; + background-position:center 2.5em; + color:#35d726; + font:bold 18px 'Lucida Grande' sans-serif; + padding: 6px 6px 150px 6px; + text-align:center; +} +#icons_top dd { + font: 11px "Lucida Grande"; + line-height:18px; + text-align:center; +} +dl#speed, dl#light {border-right:1px solid #444;} +dl#light, dl#power {border-left:1px solid #000;} +dl#speed dt {background-image: url(../img/icon_speed.gif);} +dl#light dt {background-image: url(../img/icon_light.gif);} +dl#power dt {background-image: url(../img/icon_power.gif);} \ No newline at end of file diff --git a/web/doc/rdoc/generators/template/merb/merb.rb b/web/doc/rdoc/generators/template/merb/merb.rb new file mode 100644 index 000000000..36a77ed31 --- /dev/null +++ b/web/doc/rdoc/generators/template/merb/merb.rb @@ -0,0 +1,351 @@ +module RDoc +module Page + +STYLE = File.read(File.join(File.dirname(__FILE__), 'merb_doc_styles.css')) +FONTS = "" + +################################################################### + +CLASS_PAGE = < + +HTML + +################################################################### + +METHOD_LIST = < +IF:diagram +
+ %diagram% +
+ENDIF:diagram + +IF:description +
%description%
+ENDIF:description + +IF:requires +
Required Files
+
    +START:requires +
  • %name%
  • +END:requires +
+ENDIF:requires + +IF:toc +
Contents
+ +ENDIF:toc + +IF:methods +
Methods
+
    +START:methods +
  • %name%
  • +END:methods +
+ENDIF:methods + +IF:includes +
Included Modules
+
    +START:includes +
  • %name%
  • +END:includes +
+ENDIF:includes + +START:sections +IF:sectitle + +IF:seccomment +
+%seccomment% +
+ENDIF:seccomment +ENDIF:sectitle + +IF:classlist +
Classes and Modules
+ %classlist% +ENDIF:classlist + +IF:constants +
Constants
+ +START:constants + + + + + +IF:desc + + + + +ENDIF:desc +END:constants +
%name%=%value%
 %desc%
+ENDIF:constants + +IF:attributes +
Attributes
+ +START:attributes + + + + + +END:attributes +
+IF:rw +[%rw%] +ENDIF:rw + %name%%a_desc%
+ENDIF:attributes + +IF:method_list +START:method_list +IF:methods +
%type% %category% methods
+START:methods +
+
+IF:callseq + %callseq% +ENDIF:callseq +IFNOT:callseq + %name%%params% +ENDIF:callseq +IF:codeurl +[ source ] +ENDIF:codeurl +
+IF:m_desc +
+ %m_desc% +
+ENDIF:m_desc +IF:aka +
+ This method is also aliased as +START:aka + %name% +END:aka +
+ENDIF:aka +IF:sourcecode +
+ +
+
+%sourcecode%
+
+
+
+ENDIF:sourcecode +
+END:methods +ENDIF:methods +END:method_list +ENDIF:method_list +END:sections + +HTML + + + + +BODY = < + +
+ #{METHOD_LIST} +
+ +ENDBODY + + + +SRC_BODY = < + +
+

Source Code

+
%file_source_code%
+
+ENDSRCBODY + + +###################### File Page ########################## +FILE_PAGE = < +

%short_name%

+ + + + + + + + + +
Path:%full_path% +IF:cvsurl +  (CVS) +ENDIF:cvsurl +
Last Update:%dtm_modified%
+ +HTML + + +#### This is not used but kept for historical purposes +########################## Source code ########################## +# Separate page onlye + +SRC_PAGE = < +%title% + + + + +
%code%
+ + +HTML + +########################### source page body ################### + +SCR_CODE_BODY = < + %source_code% + + +HTML + +########################## Index ################################ + +FR_INDEX_BODY = < +START:entries +
  • %name%%scope%
  • +END:entries + +HTML + +CLASS_INDEX = FILE_INDEX +METHOD_INDEX = FILE_INDEX + +INDEX = < + + + + + + + + Merb | %title% API Documentation + + + + + +
      +
    • methods
    • +
    • classes
    • +
    • files
    • + +
    +
    +
    +
    + +
    +
    +
    + Loading via ajax... this could take a sec. +
    +
    +
    +    %title% README +
    +
    + %content% +
    +
    +Documentation for %title% usage tips + + + + + + +HTML + +API_GREASE_JS = File.read(File.join(File.dirname(__FILE__), 'api_grease.js')) + +PROTOTYPE_JS = File.read(File.join(File.dirname(__FILE__), 'prototype.js')) +end +end + diff --git a/web/doc/rdoc/generators/template/merb/merb_doc_styles.css b/web/doc/rdoc/generators/template/merb/merb_doc_styles.css new file mode 100644 index 000000000..cb01bb732 --- /dev/null +++ b/web/doc/rdoc/generators/template/merb/merb_doc_styles.css @@ -0,0 +1,492 @@ +html, body { + padding:10px 0px 0px 5px; + margin: 0px; + font-family: "Lucida Grande", "Lucida Sans Unicode", sans-serif; + font-size: 14px; +} + +body { + background: #000000 url(http://merbivore.com/img/header_waves.gif) repeat-x scroll center top; +} + +td, p { + background: #FFF; + color: #000; + margin: 0px; + font-size: small; + line-height: 17px; + margin-bottom 12px; +} + +#floater { + position: absolute; + top: 5px; + right: 5px; +} + +#floater strong { + color: white; +} + +#floater a { + color: black; +} + +#floater a:hover { + background-color: transparent; +} + + +/*holds the whole searching/drill down stuff */ +#listFrame{ + float:left; + padding: 2px; + width: 350px; + background-color: #868686; + border: 1px solid #999; + border-right: none; +} + +#browserBar{ + height: 25px; + padding:11px 0px 0px 0px; + margin:0px; + background-color: #868686; + border-top: 1px solid #999; + color: white; +} + +#browserBar a{ + text-decoration: none; +} + +.button{ + text-decoration: none; + padding:3px 8px 3px 8px; + border: 1px solid #66a; + background-color: #ccf; + color: #66a; +} + +.buttonInactive{ + text-decoration: none; + padding:3px 8px 3px 8px; + border: 1px solid #999; + background-color: #ccc; + color: #999; +} + +.miniButton{ + text-decoration: none; + padding:3px 2px 3px 2px; + border: 1px solid #66a; + background-color: #ccf; + color: #66a; +} + +.miniButtonInactive{ + text-decoration: none; + padding:3px 2px 3px 2px; + border: 1px solid #999; + background-color: #ccc; + color: #999; +} + +#blowOutListBox{ + position: absolute; + top: 63px; + left: 399px; + border: 1px solid #999; + padding: 0px; + margin: 0px; + z-index: 1000; + background-color: #ccf; + color: #66a; +} + +#blowOutListBox ul{ + list-style-type:none; + padding: 0px; + margin: 0px; +} + +#blowOutListBox ul li{ + padding: 3px; + margin: 0px; + line-height: 1.1em; + +} + +#blowOutListBox ul li a{ + text-decoration: none; + padding: 3px; +} +#blowOutListBox ul li a:hover{ + background-color: #ddf; +} + +/*holds the content for browsing etc... also is the target of method/class/file name clicks */ +#rdocContent{ + height: 600px; + background-color: #fff; + border: 1px solid #999; + border-left: 0px; + padding:5px; + overflow: auto; +} + +/*the grouping for methods,files,class,all... i.e. the tabs */ +ul#groupType{ + list-style-type: none; + padding: 0px; + padding-left: 5px; + margin: 0px; +} + +ul#groupType li{ + color: white; + display:inline; + padding: 5px 5px 0px 5px; + cursor: pointer; +} + +ul#groupType li#loadingStatus{ + margin: 3px; + border: 0px; + padding: 3px 3px 6px 3px; + color: #666; +} + +ul#groupType li.activeLi{ + border: 1px solid #999; + border-bottom: 0px; + background-color: #868686; + font-weight: bold; + padding-bottom: 1px; +} + +#listSearch{ + height: 25px; + padding: 3px; +} + +#listSearch input[type=text]{ + width: 340px; + font-size: 1.2em; +} + +#listScroller{ + width: 342px; + height: 700px; + margin: 3px; + background-color: #fcfcfc; + border: 1px solid #999; + overflow: auto; +} + +#listScroller ul{ + width: 500px; + padding:0px; + margin:0px; + list-style: none; +} + +#listScroller li{ + padding: 0px; + margin: 0px; + display: block; + line-height: 1.1em; +} + +a, h1 a, h2 a, .sectiontitle a, #listScroller a{ + color: #11B716; + font-weight: bold; + text-decoration: none; + padding: 0px 1px 1px 1px; + margin: 3px; + font-weight: bold; +} + +a:hover, h1 a:hover, h2 a:hover, .sectiontitle a:hover, #listScroller a:hover{ + background-color: #22D716; + color: black; +} + +#browserBar a, .banner a { + color: #D7FF00; +} + +#browserBar a:hover, .banner a:hover { + background-color: #D7FF00; + color: #000; +} + +#listScroller a.activeA { + background-color: #22D716; + color: black ; + border: 1px solid #ccc; + padding: 0px 1px 1px 1px; +} + +#listScroller small{ + color: #999; +} + +.activeTitle{ + font-family: monospace; + font-size: large; + border-bottom: 1px dashed black; + margin-bottom: 0.3em; + padding-bottom: 0.1em; + background-color: #ffc; +} + +.activeMethod{ + margin-left: 1em; + margin-right: 1em; + margin-bottom: 1em; +} + + +.activeMethod .title { + font-family: monospace; + font-size: large; + border-bottom: 1px dashed black; + margin-bottom: 0.3em; + padding-bottom: 0.1em; + background-color: #ffa; +} + +.activeMethod .description, .activeMethod .sourcecode { + margin-left: 1em; +} + +.activeMethod .sourcecode p.source-link { + text-indent: 0em; + margin-top: 0.5em; +} + +.activeMethod .aka { + margin-top: 0.3em; + margin-left: 1em; + font-style: italic; + text-indent: 2em; +} + +#content { + margin: 0.5em; +} + +#description p { + margin-bottom: 0.5em; +} + +.sectiontitle { + color: black; + font-size: 28px; + margin: 20px 0px; + border-bottom: 2px solid #CCCCCC; + +/* margin-top: 1em; + margin-bottom: 1em; + padding: 0.5em; + padding-left: 2em; + background: #005; + color: #FFF; + font-weight: bold; + border: 1px dotted black;*/ +} + +.attr-rw { + padding-left: 1em; + padding-right: 1em; + text-align: center; + color: #7AAD00; +} + +.attr-name { + font-weight: bold; +} + +.attr-desc { +} + +.attr-value { + font-family: monospace; +} + +.file-title-prefix { + font-size: large; +} + +.file-title { + font-size: large; + font-weight: bold; + background: #005; + color: #FFF; +} + +.banner { + background: #888; + color: #FFF; +/* border: 1px solid black;*/ + padding: 1em; +} + +.banner td { + background: transparent; + color: #FFF; +} + +.dyn-source { + display: none; + color: #000; + border: 0px; + border-left: 1px dotted #CCC; + border-top: 1px dotted #CCC; + margin: 0em; + padding: 0em; +} + +.dyn-source .cmt { + color: #7AAD00; + font-style: italic; +} + +.dyn-source .kw { + color: #11B716; + font-weight: bold; +} + +.method { + margin-left: 1em; + margin-right: 1em; + margin-bottom: 2em; +} + +.description pre, .description td { + font-family:"Courier",serif; +} +pre, .description pre { + font-size: 12px; + line-height: 18px; + color: white; + padding: 12px; + background: #222; + overflow: auto; +} + +h2.title, .method .title { + color: #7AAD00; + font-size: 22px; + margin: 10px 0px; + +/* font-size: large; + border-bottom: 1px dashed black; + margin: 0.3em; + padding: 0.2em; +*/} + +.method .description, .method .sourcecode { + margin-left: 1em; +} + +.description p, .sourcecode p { + margin-bottom: 0.5em; +} + +.method .sourcecode p.source-link { + text-indent: 0em; + margin-top: 0.5em; +} + +.method .aka { + margin-top: 0.3em; + margin-left: 1em; + font-style: italic; + text-indent: 2em; +} + +h1 { + padding: 1em; + font-size: x-large; +} + +h2 { + padding: 0.5em 1em 0.5em 1em; + font-size: large; +} + + +h1, h2, h3, h4, h5, h6 { + color: white; + background-color: #868686; +} + +h3, h4, h5, h6 { + padding: 0.2em 1em 0.2em 1em; + font-weight: bold; +} + +h4 { + margin-bottom: 2px; +} + +.sourcecode > pre { + padding: 0px; + margin: 0px; + border: 1px dotted black; + background: #FFE; +} + +/* ============= */ +/* = home page = */ +/* ============= */ + +body#home { + margin: 0; + padding: 0; +} + +#content { + margin: 0 auto; + width: 800px; +} + +#content h1.home { + background-color: transparent; + border:0pt none; + color:#FFFFFF; + font-size:36px; + margin:20px 0px; + padding: 0; + text-align:center; +} + +#content #documentation-links h2.title { + background-color: transparent; +} + +#documentation-links { + background-color : white; + margin-bottom: 20px; + padding-bottom: 10px; +} + +#documentation-links p { + margin: 22px; +} + +body#home #footer { + background:#444444 none repeat scroll 0%; + clear:both; +} + +#footer p { + background-color: transparent; + color:#999999; + margin:0 auto; + padding:12px; + text-align:center; + width: 800px; +} \ No newline at end of file diff --git a/web/doc/rdoc/generators/template/merb/prototype.js b/web/doc/rdoc/generators/template/merb/prototype.js new file mode 100644 index 000000000..505822177 --- /dev/null +++ b/web/doc/rdoc/generators/template/merb/prototype.js @@ -0,0 +1,2515 @@ +/* Prototype JavaScript framework, version 1.5.0 + * (c) 2005-2007 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://prototype.conio.net/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.5.0', + BrowserFeatures: { + XPath: !!document.evaluate + }, + + ScriptFragment: '(?:)((\n|\r|.)*?)(?:<\/script>)', + emptyFunction: function() {}, + K: function(x) { return x } +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.extend = function(destination, source) { + for (var property in source) { + destination[property] = source[property]; + } + return destination; +} + +Object.extend(Object, { + inspect: function(object) { + try { + if (object === undefined) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + }, + + keys: function(object) { + var keys = []; + for (var property in object) + keys.push(property); + return keys; + }, + + values: function(object) { + var values = []; + for (var property in object) + values.push(object[property]); + return values; + }, + + clone: function(object) { + return Object.extend({}, object); + } +}); + +Function.prototype.bind = function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var __method = this, args = $A(arguments), object = args.shift(); + return function(event) { + return __method.apply(object, [( event || window.event)].concat(args).concat($A(arguments))); + } +} + +Object.extend(Number.prototype, { + toColorPart: function() { + var digits = this.toString(16); + if (this < 16) return '0' + digits; + return digits; + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + } +}); + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) {} + } + + return returnValue; + } +} + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create(); +PeriodicalExecuter.prototype = { + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(this); + } finally { + this.currentlyExecuting = false; + } + } + } +} +String.interpret = function(value){ + return value == null ? '' : String(value); +} + +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + + sub: function(pattern, replacement, count) { + replacement = this.gsub.prepareReplacement(replacement); + count = count === undefined ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + }, + + scan: function(pattern, iterator) { + this.gsub(pattern, iterator); + return this; + }, + + truncate: function(length, truncation) { + length = length || 30; + truncation = truncation === undefined ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : this; + }, + + strip: function() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(function(script) { return eval(script) }); + }, + + escapeHTML: function() { + var div = document.createElement('div'); + var text = document.createTextNode(this); + div.appendChild(text); + return div.innerHTML; + }, + + unescapeHTML: function() { + var div = document.createElement('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0] ? (div.childNodes.length > 1 ? + $A(div.childNodes).inject('',function(memo,node){ return memo+node.nodeValue }) : + div.childNodes[0].nodeValue) : ''; + }, + + toQueryParams: function(separator) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return {}; + + return match[1].split(separator || '&').inject({}, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var name = decodeURIComponent(pair[0]); + var value = pair[1] ? decodeURIComponent(pair[1]) : undefined; + + if (hash[name] !== undefined) { + if (hash[name].constructor != Array) + hash[name] = [hash[name]]; + if (value) hash[name].push(value); + } + else hash[name] = value; + } + return hash; + }); + }, + + toArray: function() { + return this.split(''); + }, + + succ: function() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + }, + + camelize: function() { + var parts = this.split('-'), len = parts.length; + if (len == 1) return parts[0]; + + var camelized = this.charAt(0) == '-' + ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) + : parts[0]; + + for (var i = 1; i < len; i++) + camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); + + return camelized; + }, + + capitalize: function(){ + return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); + }, + + underscore: function() { + return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); + }, + + dasherize: function() { + return this.gsub(/_/,'-'); + }, + + inspect: function(useDoubleQuotes) { + var escapedString = this.replace(/\\/g, '\\\\'); + if (useDoubleQuotes) + return '"' + escapedString.replace(/"/g, '\\"') + '"'; + else + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + } +}); + +String.prototype.gsub.prepareReplacement = function(replacement) { + if (typeof replacement == 'function') return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +} + +String.prototype.parseQuery = String.prototype.toQueryParams; + +var Template = Class.create(); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; +Template.prototype = { + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + return this.template.gsub(this.pattern, function(match) { + var before = match[1]; + if (before == '\\') return match[2]; + return before + String.interpret(object[match[3]]); + }); + } +} + +var $break = new Object(); +var $continue = new Object(); + +var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { + try { + iterator(value, index++); + } catch (e) { + if (e != $continue) throw e; + } + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + }, + + eachSlice: function(number, iterator) { + var index = -number, slices = [], array = this.toArray(); + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.map(iterator); + }, + + all: function(iterator) { + var result = true; + this.each(function(value, index) { + result = result && !!(iterator || Prototype.K)(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator) { + var result = false; + this.each(function(value, index) { + if (result = !!(iterator || Prototype.K)(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator) { + var results = []; + this.each(function(value, index) { + results.push((iterator || Prototype.K)(value, index)); + }); + return results; + }, + + detect: function(iterator) { + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator) { + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(pattern, iterator) { + var results = []; + this.each(function(value, index) { + var stringValue = value.toString(); + if (stringValue.match(pattern)) + results.push((iterator || Prototype.K)(value, index)); + }) + return results; + }, + + include: function(object) { + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inGroupsOf: function(number, fillWith) { + fillWith = fillWith === undefined ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + }, + + inject: function(memo, iterator) { + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value >= result) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value < result) + result = value; + }); + return result; + }, + + partition: function(iterator) { + var trues = [], falses = []; + this.each(function(value, index) { + ((iterator || Prototype.K)(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value, index) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator) { + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator) { + return this.map(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.map(); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (typeof args.last() == 'function') + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + }, + + size: function() { + return this.toArray().length; + }, + + inspect: function() { + return '#'; + } +} + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray +}); +var $A = Array.from = function(iterable) { + if (!iterable) return []; + if (iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0, length = iterable.length; i < length; i++) + results.push(iterable[i]); + return results; + } +} + +Object.extend(Array.prototype, Enumerable); + +if (!Array.prototype._reverse) + Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(value && value.constructor == Array ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + indexOf: function(object) { + for (var i = 0, length = this.length; i < length; i++) + if (this[i] == object) return i; + return -1; + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + reduce: function() { + return this.length > 1 ? this : this[0]; + }, + + uniq: function() { + return this.inject([], function(array, value) { + return array.include(value) ? array : array.concat([value]); + }); + }, + + clone: function() { + return [].concat(this); + }, + + size: function() { + return this.length; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } +}); + +Array.prototype.toArray = Array.prototype.clone; + +function $w(string){ + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +if(window.opera){ + Array.prototype.concat = function(){ + var array = []; + for(var i = 0, length = this.length; i < length; i++) array.push(this[i]); + for(var i = 0, length = arguments.length; i < length; i++) { + if(arguments[i].constructor == Array) { + for(var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) + array.push(arguments[i][j]); + } else { + array.push(arguments[i]); + } + } + return array; + } +} +var Hash = function(obj) { + Object.extend(this, obj || {}); +}; + +Object.extend(Hash, { + toQueryString: function(obj) { + var parts = []; + + this.prototype._each.call(obj, function(pair) { + if (!pair.key) return; + + if (pair.value && pair.value.constructor == Array) { + var values = pair.value.compact(); + if (values.length < 2) pair.value = values.reduce(); + else { + key = encodeURIComponent(pair.key); + values.each(function(value) { + value = value != undefined ? encodeURIComponent(value) : ''; + parts.push(key + '=' + encodeURIComponent(value)); + }); + return; + } + } + if (pair.value == undefined) pair[1] = ''; + parts.push(pair.map(encodeURIComponent).join('=')); + }); + + return parts.join('&'); + } +}); + +Object.extend(Hash.prototype, Enumerable); +Object.extend(Hash.prototype, { + _each: function(iterator) { + for (var key in this) { + var value = this[key]; + if (value && value == Hash.prototype[key]) continue; + + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + merge: function(hash) { + return $H(hash).inject(this, function(mergedHash, pair) { + mergedHash[pair.key] = pair.value; + return mergedHash; + }); + }, + + remove: function() { + var result; + for(var i = 0, length = arguments.length; i < length; i++) { + var value = this[arguments[i]]; + if (value !== undefined){ + if (result === undefined) result = value; + else { + if (result.constructor != Array) result = [result]; + result.push(value) + } + } + delete this[arguments[i]]; + } + return result; + }, + + toQueryString: function() { + return Hash.toQueryString(this); + }, + + inspect: function() { + return '#'; + } +}); + +function $H(object) { + if (object && object.constructor == Hash) return object; + return new Hash(object); +}; +ObjectRange = Class.create(); +Object.extend(ObjectRange.prototype, Enumerable); +Object.extend(ObjectRange.prototype, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +} + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (typeof responder[callback] == 'function') { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) {} + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { + Ajax.activeRequestCount++; + }, + onComplete: function() { + Ajax.activeRequestCount--; + } +}); + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '' + } + Object.extend(this.options, options || {}); + + this.options.method = this.options.method.toLowerCase(); + if (typeof this.options.parameters == 'string') + this.options.parameters = this.options.parameters.toQueryParams(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { + _complete: false, + + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + var params = this.options.parameters; + + if (!['get', 'post'].include(this.method)) { + // simulate other verbs over post + params['_method'] = this.method; + this.method = 'post'; + } + + params = Hash.toQueryString(params); + if (params && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_=' + + // when GET, append parameters to URL + if (this.method == 'get' && params) + this.url += (this.url.indexOf('?') > -1 ? '&' : '?') + params; + + try { + Ajax.Responders.dispatch('onCreate', this, this.transport); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) + setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + var body = this.method == 'post' ? (this.options.postBody || params) : null; + + this.transport.send(body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + // user-defined headers + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (typeof extras.push == 'function') + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + return !this.transport.status + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + this.transport.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + } catch (e) { + this.dispatchException(e); + } + + if ((this.getHeader('Content-type') || 'text/javascript').strip(). + match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i)) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + state, this, transport, json); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + // avoid memory leak in MSIE: clean up + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) { return null } + }, + + evalJSON: function() { + try { + var json = this.getHeader('X-JSON'); + return json ? eval('(' + json + ')') : null; + } catch (e) { return null } + }, + + evalResponse: function() { + try { + return eval(this.transport.responseText); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Updater = Class.create(); + +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { + initialize: function(container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + } + + this.transport = Ajax.getTransport(); + this.setOptions(options); + + var onComplete = this.options.onComplete || Prototype.emptyFunction; + this.options.onComplete = (function(transport, param) { + this.updateContent(); + onComplete(transport, param); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.container[this.success() ? 'success' : 'failure']; + var response = this.transport.responseText; + + if (!this.options.evalScripts) response = response.stripScripts(); + + if (receiver = $(receiver)) { + if (this.options.insertion) + new this.options.insertion(receiver, response); + else + receiver.update(response); + } + + if (this.success()) { + if (this.onComplete) + setTimeout(this.onComplete.bind(this), 10); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = {}; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(request) { + if (this.options.decay) { + this.decay = (request.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = request.responseText; + } + this.timer = setTimeout(this.onTimerEvent.bind(this), + this.decay * this.frequency * 1000); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); +function $(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push($(arguments[i])); + return elements; + } + if (typeof element == 'string') + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(query.snapshotItem(i)); + return results; + }; +} + +document.getElementsByClassName = function(className, parentElement) { + if (Prototype.BrowserFeatures.XPath) { + var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]"; + return document._getElementsByXPath(q, parentElement); + } else { + var children = ($(parentElement) || document.body).getElementsByTagName('*'); + var elements = [], child; + for (var i = 0, length = children.length; i < length; i++) { + child = children[i]; + if (Element.hasClassName(child, className)) + elements.push(Element.extend(child)); + } + return elements; + } +}; + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) + var Element = new Object(); + +Element.extend = function(element) { + if (!element || _nativeExtensions || element.nodeType == 3) return element; + + if (!element._extended && element.tagName && element != window) { + var methods = Object.clone(Element.Methods), cache = Element.extend.cache; + + if (element.tagName == 'FORM') + Object.extend(methods, Form.Methods); + if (['INPUT', 'TEXTAREA', 'SELECT'].include(element.tagName)) + Object.extend(methods, Form.Element.Methods); + + Object.extend(methods, Element.Methods.Simulated); + + for (var property in methods) { + var value = methods[property]; + if (typeof value == 'function' && !(property in element)) + element[property] = cache.findOrStore(value); + } + } + + element._extended = true; + return element; +}; + +Element.extend.cache = { + findOrStore: function(value) { + return this[value] = this[value] || function() { + return value.apply(null, [this].concat($A(arguments))); + } + } +}; + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + $(element).style.display = 'none'; + return element; + }, + + show: function(element) { + $(element).style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: function(element, html) { + html = typeof html == 'undefined' ? '' : html.toString(); + $(element).innerHTML = html.stripScripts(); + setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + replace: function(element, html) { + element = $(element); + html = typeof html == 'undefined' ? '' : html.toString(); + if (element.outerHTML) { + element.outerHTML = html.stripScripts(); + } else { + var range = element.ownerDocument.createRange(); + range.selectNodeContents(element); + element.parentNode.replaceChild( + range.createContextualFragment(html.stripScripts()), element); + } + setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + inspect: function(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + $H({'id': 'id', 'className': 'class'}).each(function(pair) { + var property = pair.first(), attribute = pair.last(); + var value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return $(element).recursivelyCollect('parentNode'); + }, + + descendants: function(element) { + return $A($(element).getElementsByTagName('*')); + }, + + immediateDescendants: function(element) { + if (!(element = $(element).firstChild)) return []; + while (element && element.nodeType != 1) element = element.nextSibling; + if (element) return [element].concat($(element).nextSiblings()); + return []; + }, + + previousSiblings: function(element) { + return $(element).recursivelyCollect('previousSibling'); + }, + + nextSiblings: function(element) { + return $(element).recursivelyCollect('nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return element.previousSiblings().reverse().concat(element.nextSiblings()); + }, + + match: function(element, selector) { + if (typeof selector == 'string') + selector = new Selector(selector); + return selector.match($(element)); + }, + + up: function(element, expression, index) { + return Selector.findElement($(element).ancestors(), expression, index); + }, + + down: function(element, expression, index) { + return Selector.findElement($(element).descendants(), expression, index); + }, + + previous: function(element, expression, index) { + return Selector.findElement($(element).previousSiblings(), expression, index); + }, + + next: function(element, expression, index) { + return Selector.findElement($(element).nextSiblings(), expression, index); + }, + + getElementsBySelector: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element, args); + }, + + getElementsByClassName: function(element, className) { + return document.getElementsByClassName(className, element); + }, + + readAttribute: function(element, name) { + element = $(element); + if (document.all && !window.opera) { + var t = Element._attributeTranslations; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + var attribute = element.attributes[name]; + if(attribute) return attribute.nodeValue; + } + return element.getAttribute(name); + }, + + getHeight: function(element) { + return $(element).getDimensions().height; + }, + + getWidth: function(element) { + return $(element).getDimensions().width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + if (elementClassName.length == 0) return false; + if (elementClassName == className || + elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) + return true; + return false; + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).add(className); + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).remove(className); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className); + return element; + }, + + observe: function() { + Event.observe.apply(Event, arguments); + return $A(arguments).first(); + }, + + stopObserving: function() { + Event.stopObserving.apply(Event, arguments); + return $A(arguments).first(); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.match(/^\s*$/); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + while (element = element.parentNode) + if (element == ancestor) return true; + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = Position.cumulativeOffset(element); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + if (['float','cssFloat'].include(style)) + style = (typeof element.style.styleFloat != 'undefined' ? 'styleFloat' : 'cssFloat'); + style = style.camelize(); + var value = element.style[style]; + if (!value) { + if (document.defaultView && document.defaultView.getComputedStyle) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } else if (element.currentStyle) { + value = element.currentStyle[style]; + } + } + + if((value == 'auto') && ['width','height'].include(style) && (element.getStyle('display') != 'none')) + value = element['offset'+style.capitalize()] + 'px'; + + if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) + if (Element.getStyle(element, 'position') == 'static') value = 'auto'; + if(style == 'opacity') { + if(value) return parseFloat(value); + if(value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if(value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + return value == 'auto' ? null : value; + }, + + setStyle: function(element, style) { + element = $(element); + for (var name in style) { + var value = style[name]; + if(name == 'opacity') { + if (value == 1) { + value = (/Gecko/.test(navigator.userAgent) && + !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 0.999999 : 1.0; + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,''); + } else if(value == '') { + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,''); + } else { + if(value < 0.00001) value = 0; + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') + + 'alpha(opacity='+value*100+')'; + } + } else if(['float','cssFloat'].include(name)) name = (typeof element.style.styleFloat != 'undefined') ? 'styleFloat' : 'cssFloat'; + element.style[name.camelize()] = value; + } + return element; + }, + + getDimensions: function(element) { + element = $(element); + var display = $(element).getStyle('display'); + if (display != 'none' && display != null) // Safari bug + return {width: element.offsetWidth, height: element.offsetHeight}; + + // All *Width and *Height properties give 0 on elements with display none, + // so enable the element temporarily + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + var originalDisplay = els.display; + els.visibility = 'hidden'; + els.position = 'absolute'; + els.display = 'block'; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = originalDisplay; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + // Opera returns the offset relative to the positioning context, when an + // element is position relative but top and left have not been defined + if (window.opera) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = element.style.overflow || 'auto'; + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + } +}; + +Object.extend(Element.Methods, {childOf: Element.Methods.descendantOf}); + +Element._attributeTranslations = {}; + +Element._attributeTranslations.names = { + colspan: "colSpan", + rowspan: "rowSpan", + valign: "vAlign", + datetime: "dateTime", + accesskey: "accessKey", + tabindex: "tabIndex", + enctype: "encType", + maxlength: "maxLength", + readonly: "readOnly", + longdesc: "longDesc" +}; + +Element._attributeTranslations.values = { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + + title: function(element) { + var node = element.getAttributeNode('title'); + return node.specified ? node.nodeValue : null; + } +}; + +Object.extend(Element._attributeTranslations.values, { + href: Element._attributeTranslations.values._getAttr, + src: Element._attributeTranslations.values._getAttr, + disabled: Element._attributeTranslations.values._flag, + checked: Element._attributeTranslations.values._flag, + readonly: Element._attributeTranslations.values._flag, + multiple: Element._attributeTranslations.values._flag +}); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + var t = Element._attributeTranslations; + attribute = t.names[attribute] || attribute; + return $(element).getAttributeNode(attribute).specified; + } +}; + +// IE is missing .innerHTML support for TABLE-related elements +if (document.all && !window.opera){ + Element.Methods.update = function(element, html) { + element = $(element); + html = typeof html == 'undefined' ? '' : html.toString(); + var tagName = element.tagName.toUpperCase(); + if (['THEAD','TBODY','TR','TD'].include(tagName)) { + var div = document.createElement('div'); + switch (tagName) { + case 'THEAD': + case 'TBODY': + div.innerHTML = '' + html.stripScripts() + '
    '; + depth = 2; + break; + case 'TR': + div.innerHTML = '' + html.stripScripts() + '
    '; + depth = 3; + break; + case 'TD': + div.innerHTML = '
    ' + html.stripScripts() + '
    '; + depth = 4; + } + $A(element.childNodes).each(function(node){ + element.removeChild(node) + }); + depth.times(function(){ div = div.firstChild }); + + $A(div.childNodes).each( + function(node){ element.appendChild(node) }); + } else { + element.innerHTML = html.stripScripts(); + } + setTimeout(function() {html.evalScripts()}, 10); + return element; + } +}; + +Object.extend(Element, Element.Methods); + +var _nativeExtensions = false; + +if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + ['', 'Form', 'Input', 'TextArea', 'Select'].each(function(tag) { + var className = 'HTML' + tag + 'Element'; + if(window[className]) return; + var klass = window[className] = {}; + klass.prototype = document.createElement(tag ? tag.toLowerCase() : 'div').__proto__; + }); + +Element.addMethods = function(methods) { + Object.extend(Element.Methods, methods || {}); + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + var cache = Element.extend.cache; + for (var property in methods) { + var value = methods[property]; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = cache.findOrStore(value); + } + } + + if (typeof HTMLElement != 'undefined') { + copy(Element.Methods, HTMLElement.prototype); + copy(Element.Methods.Simulated, HTMLElement.prototype, true); + copy(Form.Methods, HTMLFormElement.prototype); + [HTMLInputElement, HTMLTextAreaElement, HTMLSelectElement].each(function(klass) { + copy(Form.Element.Methods, klass.prototype); + }); + _nativeExtensions = true; + } +} + +var Toggle = new Object(); +Toggle.display = Element.toggle; + +/*--------------------------------------------------------------------------*/ + +Abstract.Insertion = function(adjacency) { + this.adjacency = adjacency; +} + +Abstract.Insertion.prototype = { + initialize: function(element, content) { + this.element = $(element); + this.content = content.stripScripts(); + + if (this.adjacency && this.element.insertAdjacentHTML) { + try { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } catch (e) { + var tagName = this.element.tagName.toUpperCase(); + if (['TBODY', 'TR'].include(tagName)) { + this.insertContent(this.contentFromAnonymousTable()); + } else { + throw e; + } + } + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.insertContent([this.range.createContextualFragment(this.content)]); + } + + setTimeout(function() {content.evalScripts()}, 10); + }, + + contentFromAnonymousTable: function() { + var div = document.createElement('div'); + div.innerHTML = '' + this.content + '
    '; + return $A(div.childNodes[0].childNodes[0].childNodes); + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, this.element); + }).bind(this)); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function(fragments) { + fragments.reverse(false).each((function(fragment) { + this.element.insertBefore(fragment, this.element.firstChild); + }).bind(this)); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.appendChild(fragment); + }).bind(this)); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, + this.element.nextSibling); + }).bind(this)); + } +}); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set($A(this).concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set($A(this).without(classNameToRemove).join(' ')); + }, + + toString: function() { + return $A(this).join(' '); + } +}; + +Object.extend(Element.ClassNames.prototype, Enumerable); +var Selector = Class.create(); +Selector.prototype = { + initialize: function(expression) { + this.params = {classNames: []}; + this.expression = expression.toString().strip(); + this.parseExpression(); + this.compileMatcher(); + }, + + parseExpression: function() { + function abort(message) { throw 'Parse error in selector: ' + message; } + + if (this.expression == '') abort('empty expression'); + + var params = this.params, expr = this.expression, match, modifier, clause, rest; + while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) { + params.attributes = params.attributes || []; + params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''}); + expr = match[1]; + } + + if (expr == '*') return this.params.wildcard = true; + + while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) { + modifier = match[1], clause = match[2], rest = match[3]; + switch (modifier) { + case '#': params.id = clause; break; + case '.': params.classNames.push(clause); break; + case '': + case undefined: params.tagName = clause.toUpperCase(); break; + default: abort(expr.inspect()); + } + expr = rest; + } + + if (expr.length > 0) abort(expr.inspect()); + }, + + buildMatchExpression: function() { + var params = this.params, conditions = [], clause; + + if (params.wildcard) + conditions.push('true'); + if (clause = params.id) + conditions.push('element.readAttribute("id") == ' + clause.inspect()); + if (clause = params.tagName) + conditions.push('element.tagName.toUpperCase() == ' + clause.inspect()); + if ((clause = params.classNames).length > 0) + for (var i = 0, length = clause.length; i < length; i++) + conditions.push('element.hasClassName(' + clause[i].inspect() + ')'); + if (clause = params.attributes) { + clause.each(function(attribute) { + var value = 'element.readAttribute(' + attribute.name.inspect() + ')'; + var splitValueBy = function(delimiter) { + return value + ' && ' + value + '.split(' + delimiter.inspect() + ')'; + } + + switch (attribute.operator) { + case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break; + case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break; + case '|=': conditions.push( + splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect() + ); break; + case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break; + case '': + case undefined: conditions.push('element.hasAttribute(' + attribute.name.inspect() + ')'); break; + default: throw 'Unknown operator ' + attribute.operator + ' in selector'; + } + }); + } + + return conditions.join(' && '); + }, + + compileMatcher: function() { + this.match = new Function('element', 'if (!element.tagName) return false; \ + element = $(element); \ + return ' + this.buildMatchExpression()); + }, + + findElements: function(scope) { + var element; + + if (element = $(this.params.id)) + if (this.match(element)) + if (!scope || Element.childOf(element, scope)) + return [element]; + + scope = (scope || document).getElementsByTagName(this.params.tagName || '*'); + + var results = []; + for (var i = 0, length = scope.length; i < length; i++) + if (this.match(element = scope[i])) + results.push(Element.extend(element)); + + return results; + }, + + toString: function() { + return this.expression; + } +} + +Object.extend(Selector, { + matchElements: function(elements, expression) { + var selector = new Selector(expression); + return elements.select(selector.match.bind(selector)).map(Element.extend); + }, + + findElement: function(elements, expression, index) { + if (typeof expression == 'number') index = expression, expression = false; + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + return expressions.map(function(expression) { + return expression.match(/[^\s"]+(?:"[^"]*"[^\s"]+)*/g).inject([null], function(results, expr) { + var selector = new Selector(expr); + return results.inject([], function(elements, result) { + return elements.concat(selector.findElements(result || element)); + }); + }); + }).flatten(); + } +}); + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} +var Form = { + reset: function(form) { + $(form).reset(); + return form; + }, + + serializeElements: function(elements, getHash) { + var data = elements.inject({}, function(result, element) { + if (!element.disabled && element.name) { + var key = element.name, value = $(element).getValue(); + if (value != undefined) { + if (result[key]) { + if (result[key].constructor != Array) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return getHash ? data : Hash.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, getHash) { + return Form.serializeElements(Form.getElements(form), getHash); + }, + + getElements: function(form) { + return $A($(form).getElementsByTagName('*')).inject([], + function(elements, child) { + if (Form.Element.Serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + } + ); + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + form.getElements().each(function(element) { + element.blur(); + element.disabled = 'true'; + }); + return form; + }, + + enable: function(form) { + form = $(form); + form.getElements().each(function(element) { + element.disabled = ''; + }); + return form; + }, + + findFirstElement: function(form) { + return $(form).getElements().find(function(element) { + return element.type != 'hidden' && !element.disabled && + ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + } +} + +Object.extend(Form, Form.Methods); + +/*--------------------------------------------------------------------------*/ + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +} + +Form.Element.Methods = { + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = {}; + pair[element.name] = value; + return Hash.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + element.focus(); + if (element.select && ( element.tagName.toLowerCase() != 'input' || + !['button', 'reset', 'submit'].include(element.type) ) ) + element.select(); + return element; + }, + + disable: function(element) { + element = $(element); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.blur(); + element.disabled = false; + return element; + } +} + +Object.extend(Form.Element, Form.Element.Methods); +var Field = Form.Element; +var $F = Form.Element.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + default: + return Form.Element.Serializers.textarea(element); + } + }, + + inputSelector: function(element) { + return element.checked ? element.value : null; + }, + + textarea: function(element) { + return element.value; + }, + + select: function(element) { + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + // extend element because hasAttribute may not be native + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +} + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = function() {} +Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { + this.frequency = frequency; + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + var value = this.getValue(); + var changed = ('string' == typeof this.lastValue && 'string' == typeof value + ? this.lastValue != value : String(this.lastValue) != String(value)); + if (changed) { + this.callback(this.element, value); + this.lastValue = value; + } + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = function() {} +Abstract.EventObserver.prototype = { + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback.bind(this)); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +} + +Form.Element.EventObserver = Class.create(); +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(); +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) { + var Event = new Object(); +} + +Object.extend(Event, { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + + element: function(event) { + return event.target || event.srcElement; + }, + + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + pointerX: function(event) { + return event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)); + }, + + pointerY: function(event) { + return event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)); + }, + + stop: function(event) { + if (event.preventDefault) { + event.preventDefault(); + event.stopPropagation(); + } else { + event.returnValue = false; + event.cancelBubble = true; + } + }, + + // find the first node with the given tagName, starting from the + // node the event was triggered on; traverses the DOM upwards + findElement: function(event, tagName) { + var element = Event.element(event); + while (element.parentNode && (!element.tagName || + (element.tagName.toUpperCase() != tagName.toUpperCase()))) + element = element.parentNode; + return element; + }, + + observers: false, + + _observeAndCache: function(element, name, observer, useCapture) { + if (!this.observers) this.observers = []; + if (element.addEventListener) { + this.observers.push([element, name, observer, useCapture]); + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + this.observers.push([element, name, observer, useCapture]); + element.attachEvent('on' + name, observer); + } + }, + + unloadCache: function() { + if (!Event.observers) return; + for (var i = 0, length = Event.observers.length; i < length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.attachEvent)) + name = 'keydown'; + + Event._observeAndCache(element, name, observer, useCapture); + }, + + stopObserving: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.detachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + try { + element.detachEvent('on' + name, observer); + } catch (e) {} + } + } +}); + +/* prevent memory leaks in IE */ +if (navigator.appVersion.match(/\bMSIE\b/)) + Event.observe(window, 'unload', Event.unloadCache, false); +var Position = { + // set to true if needed, warning: firefox performance problems + // NOT neeeded for page scrolling, only if draggable contained in + // scrollable elements + includeScrollOffsets: false, + + // must be called before calling withinIncludingScrolloffset, every time the + // page is scrolled + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + realOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return [valueL, valueT]; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return [valueL, valueT]; + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if(element.tagName=='BODY') break; + var p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return [valueL, valueT]; + }, + + offsetParent: function(element) { + if (element.offsetParent) return element.offsetParent; + if (element == document.body) return element; + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return element; + + return document.body; + }, + + // caches x/y coordinate pair to use with overlap + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = this.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = this.realOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = this.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + // within must be called directly before + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + page: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent==document.body) + if (Element.getStyle(element,'position')=='absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!window.opera || element.tagName=='BODY') { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return [valueL, valueT]; + }, + + clone: function(source, target) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || {}) + + // find page position of source + source = $(source); + var p = Position.page(source); + + // find coordinate system to use + target = $(target); + var delta = [0, 0]; + var parent = null; + // delta [0,0] will do fine with position: fixed elements, + // position:absolute needs offsetParent deltas + if (Element.getStyle(target,'position') == 'absolute') { + parent = Position.offsetParent(target); + delta = Position.page(parent); + } + + // correct by body offsets (fixes Safari) + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + // set position + if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if(options.setWidth) target.style.width = source.offsetWidth + 'px'; + if(options.setHeight) target.style.height = source.offsetHeight + 'px'; + }, + + absolutize: function(element) { + element = $(element); + if (element.style.position == 'absolute') return; + Position.prepare(); + + var offsets = Position.positionedOffset(element); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + }, + + relativize: function(element) { + element = $(element); + if (element.style.position == 'relative') return; + Position.prepare(); + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + } +} + +// Safari returns margins on body which is incorrect if the child is absolutely +// positioned. For performance reasons, redefine Position.cumulativeOffset for +// KHTML/WebKit only. +if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { + Position.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return [valueL, valueT]; + } +} + +Element.addMethods(); \ No newline at end of file diff --git a/web/log/merb.4000.pid b/web/log/merb.4000.pid new file mode 100644 index 000000000..52b85a992 --- /dev/null +++ b/web/log/merb.4000.pid @@ -0,0 +1 @@ +31261 \ No newline at end of file diff --git a/web/merb/merb-auth/setup.rb b/web/merb/merb-auth/setup.rb new file mode 100644 index 000000000..612f01d5a --- /dev/null +++ b/web/merb/merb-auth/setup.rb @@ -0,0 +1,44 @@ +# This file is specifically setup for use with the merb-auth plugin. +# This file should be used to setup and configure your authentication stack. +# It is not required and may safely be deleted. +# +# To change the parameter names for the password or login field you may set either of these two options +# +# Merb::Plugins.config[:"merb-auth"][:login_param] = :email +# Merb::Plugins.config[:"merb-auth"][:password_param] = :my_password_field_name + +begin + # Sets the default class ofr authentication. This is primarily used for + # Plugins and the default strategies + Merb::Authentication.user_class = User + + + # Mixin the salted user mixin + require 'merb-auth-more/mixins/salted_user' + Merb::Authentication.user_class.class_eval{ include Merb::Authentication::Mixins::SaltedUser } + + # Setup the session serialization + class Merb::Authentication + + def fetch_user(session_user_id) + Merb::Authentication.user_class.get(session_user_id) + end + + def store_user(user) + user.nil? ? user : user.id + end + end + +rescue + Merb.logger.error <<-TEXT + + You need to setup some kind of user class with merb-auth. + Merb::Authentication.user_class = User + + If you want to fully customize your authentication you should use merb-core directly. + + See merb/merb-auth/setup.rb and strategies.rb to customize your setup + + TEXT +end + diff --git a/web/merb/merb-auth/strategies.rb b/web/merb/merb-auth/strategies.rb new file mode 100644 index 000000000..fd6f20a0c --- /dev/null +++ b/web/merb/merb-auth/strategies.rb @@ -0,0 +1,11 @@ +# This file is specifically for you to define your strategies +# +# You should declare you strategies directly and/or use +# Merb::Authentication.activate!(:label_of_strategy) +# +# To load and set the order of strategy processing + +Merb::Slices::config[:"merb-auth-slice-password"][:no_default_strategies] = true + +Merb::Authentication.activate!(:default_password_form) +Merb::Authentication.activate!(:default_basic_auth) \ No newline at end of file diff --git a/web/merb/session/session.rb b/web/merb/session/session.rb new file mode 100644 index 000000000..5d0531635 --- /dev/null +++ b/web/merb/session/session.rb @@ -0,0 +1,9 @@ +module Merb + module Session + + # The Merb::Session module gets mixed into Merb::SessionContainer to allow + # app-level functionality; it will be included and methods will be available + # through request.session as instance methods. + + end +end \ No newline at end of file diff --git a/web/public/.htaccess b/web/public/.htaccess new file mode 100644 index 000000000..455e706f9 --- /dev/null +++ b/web/public/.htaccess @@ -0,0 +1,17 @@ +# Sets the default handler for FastCGI scripts +AddHandler fastcgi-script .fcgi + +# If Apache2 is used together with mod_fcgid, +# uncomment the line below and comment in the line +# above to set the correct script handler +#AddHandler fcgid-script .fcgi + +RewriteEngine On + +RewriteRule ^$ index.html [QSA] +RewriteRule ^([^.]+)$ $1.html [QSA] +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^(.*)$ merb.fcgi [QSA,L] + + +ErrorDocument 500 "

    Application Error

    Merb could not be reached" diff --git a/web/public/favicon.ico b/web/public/favicon.ico new file mode 100644 index 000000000..c908d63b9 Binary files /dev/null and b/web/public/favicon.ico differ diff --git a/web/public/images/merb.jpg b/web/public/images/merb.jpg new file mode 100644 index 000000000..a19dcf404 Binary files /dev/null and b/web/public/images/merb.jpg differ diff --git a/web/public/javascripts/application.js b/web/public/javascripts/application.js new file mode 100644 index 000000000..246a8be4c --- /dev/null +++ b/web/public/javascripts/application.js @@ -0,0 +1 @@ +// Common JavaScript code across your application goes here. \ No newline at end of file diff --git a/web/public/javascripts/jquery.js b/web/public/javascripts/jquery.js new file mode 100644 index 000000000..396646c84 --- /dev/null +++ b/web/public/javascripts/jquery.js @@ -0,0 +1,19 @@ +/* + * jQuery JavaScript Library v1.3 + * http://jquery.com/ + * + * Copyright (c) 2009 John Resig + * Dual licensed under the MIT and GPL licenses. + * http://docs.jquery.com/License + * + * Date: 2009-01-13 12:50:31 -0500 (Tue, 13 Jan 2009) + * Revision: 6104 + */ +(function(){var l=this,g,x=l.jQuery,o=l.$,n=l.jQuery=l.$=function(D,E){return new n.fn.init(D,E)},C=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;n.fn=n.prototype={init:function(D,G){D=D||document;if(D.nodeType){this[0]=D;this.length=1;this.context=D;return this}if(typeof D==="string"){var F=C.exec(D);if(F&&(F[1]||!G)){if(F[1]){D=n.clean([F[1]],G)}else{var H=document.getElementById(F[3]);if(H){if(H.id!=F[3]){return n().find(D)}var E=n(H);E.context=document;E.selector=D;return E}D=[]}}else{return n(G).find(D)}}else{if(n.isFunction(D)){return n(document).ready(D)}}if(D.selector&&D.context){this.selector=D.selector;this.context=D.context}return this.setArray(n.makeArray(D))},selector:"",jquery:"1.3",size:function(){return this.length},get:function(D){return D===g?n.makeArray(this):this[D]},pushStack:function(E,G,D){var F=n(E);F.prevObject=this;F.context=this.context;if(G==="find"){F.selector=this.selector+(this.selector?" ":"")+D}else{if(G){F.selector=this.selector+"."+G+"("+D+")"}}return F},setArray:function(D){this.length=0;Array.prototype.push.apply(this,D);return this},each:function(E,D){return n.each(this,E,D)},index:function(D){return n.inArray(D&&D.jquery?D[0]:D,this)},attr:function(E,G,F){var D=E;if(typeof E==="string"){if(G===g){return this[0]&&n[F||"attr"](this[0],E)}else{D={};D[E]=G}}return this.each(function(H){for(E in D){n.attr(F?this.style:this,E,n.prop(this,D[E],F,H,E))}})},css:function(D,E){if((D=="width"||D=="height")&&parseFloat(E)<0){E=g}return this.attr(D,E,"curCSS")},text:function(E){if(typeof E!=="object"&&E!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(E))}var D="";n.each(E||this,function(){n.each(this.childNodes,function(){if(this.nodeType!=8){D+=this.nodeType!=1?this.nodeValue:n.fn.text([this])}})});return D},wrapAll:function(D){if(this[0]){var E=n(D,this[0].ownerDocument).clone();if(this[0].parentNode){E.insertBefore(this[0])}E.map(function(){var F=this;while(F.firstChild){F=F.firstChild}return F}).append(this)}return this},wrapInner:function(D){return this.each(function(){n(this).contents().wrapAll(D)})},wrap:function(D){return this.each(function(){n(this).wrapAll(D)})},append:function(){return this.domManip(arguments,true,function(D){if(this.nodeType==1){this.appendChild(D)}})},prepend:function(){return this.domManip(arguments,true,function(D){if(this.nodeType==1){this.insertBefore(D,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(D){this.parentNode.insertBefore(D,this)})},after:function(){return this.domManip(arguments,false,function(D){this.parentNode.insertBefore(D,this.nextSibling)})},end:function(){return this.prevObject||n([])},push:[].push,find:function(D){if(this.length===1&&!/,/.test(D)){var F=this.pushStack([],"find",D);F.length=0;n.find(D,this[0],F);return F}else{var E=n.map(this,function(G){return n.find(D,G)});return this.pushStack(/[^+>] [^+>]/.test(D)?n.unique(E):E,"find",D)}},clone:function(E){var D=this.map(function(){if(!n.support.noCloneEvent&&!n.isXMLDoc(this)){var H=this.cloneNode(true),G=document.createElement("div");G.appendChild(H);return n.clean([G.innerHTML])[0]}else{return this.cloneNode(true)}});var F=D.find("*").andSelf().each(function(){if(this[h]!==g){this[h]=null}});if(E===true){this.find("*").andSelf().each(function(H){if(this.nodeType==3){return}var G=n.data(this,"events");for(var J in G){for(var I in G[J]){n.event.add(F[H],J,G[J][I],G[J][I].data)}}})}return D},filter:function(D){return this.pushStack(n.isFunction(D)&&n.grep(this,function(F,E){return D.call(F,E)})||n.multiFilter(D,n.grep(this,function(E){return E.nodeType===1})),"filter",D)},closest:function(D){var E=n.expr.match.POS.test(D)?n(D):null;return this.map(function(){var F=this;while(F&&F.ownerDocument){if(E?E.index(F)>-1:n(F).is(D)){return F}F=F.parentNode}})},not:function(D){if(typeof D==="string"){if(f.test(D)){return this.pushStack(n.multiFilter(D,this,true),"not",D)}else{D=n.multiFilter(D,this)}}var E=D.length&&D[D.length-1]!==g&&!D.nodeType;return this.filter(function(){return E?n.inArray(this,D)<0:this!=D})},add:function(D){return this.pushStack(n.unique(n.merge(this.get(),typeof D==="string"?n(D):n.makeArray(D))))},is:function(D){return !!D&&n.multiFilter(D,this).length>0},hasClass:function(D){return !!D&&this.is("."+D)},val:function(J){if(J===g){var D=this[0];if(D){if(n.nodeName(D,"option")){return(D.attributes.value||{}).specified?D.value:D.text}if(n.nodeName(D,"select")){var H=D.selectedIndex,K=[],L=D.options,G=D.type=="select-one";if(H<0){return null}for(var E=G?H:0,I=G?H+1:L.length;E=0||n.inArray(this.name,J)>=0)}else{if(n.nodeName(this,"select")){var M=n.makeArray(J);n("option",this).each(function(){this.selected=(n.inArray(this.value,M)>=0||n.inArray(this.text,M)>=0)});if(!M.length){this.selectedIndex=-1}}else{this.value=J}}})},html:function(D){return D===g?(this[0]?this[0].innerHTML:null):this.empty().append(D)},replaceWith:function(D){return this.after(D).remove()},eq:function(D){return this.slice(D,+D+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(D){return this.pushStack(n.map(this,function(F,E){return D.call(F,E,F)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=n.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild,D=this.length>1?I.cloneNode(true):I;if(H){for(var G=0,E=this.length;G0?D.cloneNode(true):I)}}if(F){n.each(F,y)}}return this;function K(N,O){return M&&n.nodeName(N,"table")&&n.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};n.fn.init.prototype=n.fn;function y(D,E){if(E.src){n.ajax({url:E.src,async:false,dataType:"script"})}else{n.globalEval(E.text||E.textContent||E.innerHTML||"")}if(E.parentNode){E.parentNode.removeChild(E)}}function e(){return +new Date}n.extend=n.fn.extend=function(){var I=arguments[0]||{},G=1,H=arguments.length,D=false,F;if(typeof I==="boolean"){D=I;I=arguments[1]||{};G=2}if(typeof I!=="object"&&!n.isFunction(I)){I={}}if(H==G){I=this;--G}for(;G-1}},swap:function(G,F,H){var D={};for(var E in F){D[E]=G.style[E];G.style[E]=F[E]}H.call(G);for(var E in F){G.style[E]=D[E]}},css:function(F,D,H){if(D=="width"||D=="height"){var J,E={position:"absolute",visibility:"hidden",display:"block"},I=D=="width"?["Left","Right"]:["Top","Bottom"];function G(){J=D=="width"?F.offsetWidth:F.offsetHeight;var L=0,K=0;n.each(I,function(){L+=parseFloat(n.curCSS(F,"padding"+this,true))||0;K+=parseFloat(n.curCSS(F,"border"+this+"Width",true))||0});J-=Math.round(L+K)}if(n(F).is(":visible")){G()}else{n.swap(F,E,G)}return Math.max(0,J)}return n.curCSS(F,D,H)},curCSS:function(H,E,F){var K,D=H.style;if(E=="opacity"&&!n.support.opacity){K=n.attr(D,"opacity");return K==""?"1":K}if(E.match(/float/i)){E=v}if(!F&&D&&D[E]){K=D[E]}else{if(p.getComputedStyle){if(E.match(/float/i)){E="float"}E=E.replace(/([A-Z])/g,"-$1").toLowerCase();var L=p.getComputedStyle(H,null);if(L){K=L.getPropertyValue(E)}if(E=="opacity"&&K==""){K="1"}}else{if(H.currentStyle){var I=E.replace(/\-(\w)/g,function(M,N){return N.toUpperCase()});K=H.currentStyle[E]||H.currentStyle[I];if(!/^\d+(px)?$/i.test(K)&&/^\d/.test(K)){var G=D.left,J=H.runtimeStyle.left;H.runtimeStyle.left=H.currentStyle.left;D.left=K||0;K=D.pixelLeft+"px";D.left=G;H.runtimeStyle.left=J}}}}return K},clean:function(E,J,H){J=J||document;if(typeof J.createElement==="undefined"){J=J.ownerDocument||J[0]&&J[0].ownerDocument||document}if(!H&&E.length===1&&typeof E[0]==="string"){var G=/^<(\w+)\s*\/?>$/.exec(E[0]);if(G){return[J.createElement(G[1])]}}var F=[],D=[],K=J.createElement("div");n.each(E,function(O,Q){if(typeof Q==="number"){Q+=""}if(!Q){return}if(typeof Q==="string"){Q=Q.replace(/(<(\w+)[^>]*?)\/>/g,function(S,T,R){return R.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?S:T+">"});var N=n.trim(Q).toLowerCase();var P=!N.indexOf("",""]||!N.indexOf("",""]||N.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
    "]||!N.indexOf("",""]||(!N.indexOf("",""]||!N.indexOf("",""]||!n.support.htmlSerialize&&[1,"div
    ","
    "]||[0,"",""];K.innerHTML=P[1]+Q+P[2];while(P[0]--){K=K.lastChild}if(!n.support.tbody){var M=!N.indexOf(""&&N.indexOf("=0;--L){if(n.nodeName(M[L],"tbody")&&!M[L].childNodes.length){M[L].parentNode.removeChild(M[L])}}}if(!n.support.leadingWhitespace&&/^\s/.test(Q)){K.insertBefore(J.createTextNode(Q.match(/^\s*/)[0]),K.firstChild)}Q=n.makeArray(K.childNodes)}if(Q.nodeType){F.push(Q)}else{F=n.merge(F,Q)}});if(H){for(var I=0;F[I];I++){if(n.nodeName(F[I],"script")&&(!F[I].type||F[I].type.toLowerCase()==="text/javascript")){D.push(F[I].parentNode?F[I].parentNode.removeChild(F[I]):F[I])}else{if(F[I].nodeType===1){F.splice.apply(F,[I+1,0].concat(n.makeArray(F[I].getElementsByTagName("script"))))}H.appendChild(F[I])}}return D}return F},attr:function(I,F,J){if(!I||I.nodeType==3||I.nodeType==8){return g}var G=!n.isXMLDoc(I),K=J!==g;F=G&&n.props[F]||F;if(I.tagName){var E=/href|src|style/.test(F);if(F=="selected"&&I.parentNode){I.parentNode.selectedIndex}if(F in I&&G&&!E){if(K){if(F=="type"&&n.nodeName(I,"input")&&I.parentNode){throw"type property can't be changed"}I[F]=J}if(n.nodeName(I,"form")&&I.getAttributeNode(F)){return I.getAttributeNode(F).nodeValue}if(F=="tabIndex"){var H=I.getAttributeNode("tabIndex");return H&&H.specified?H.value:I.nodeName.match(/^(a|area|button|input|object|select|textarea)$/i)?0:g}return I[F]}if(!n.support.style&&G&&F=="style"){return n.attr(I.style,"cssText",J)}if(K){I.setAttribute(F,""+J)}var D=!n.support.hrefNormalized&&G&&E?I.getAttribute(F,2):I.getAttribute(F);return D===null?g:D}if(!n.support.opacity&&F=="opacity"){if(K){I.zoom=1;I.filter=(I.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(J)+""=="NaN"?"":"alpha(opacity="+J*100+")")}return I.filter&&I.filter.indexOf("opacity=")>=0?(parseFloat(I.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}F=F.replace(/-([a-z])/ig,function(L,M){return M.toUpperCase()});if(K){I[F]=J}return I[F]},trim:function(D){return(D||"").replace(/^\s+|\s+$/g,"")},makeArray:function(F){var D=[];if(F!=null){var E=F.length;if(E==null||typeof F==="string"||n.isFunction(F)||F.setInterval){D[0]=F}else{while(E){D[--E]=F[E]}}}return D},inArray:function(F,G){for(var D=0,E=G.length;D*",this).remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(D,E){n.fn[D]=function(){return this.each(E,arguments)}});function j(D,E){return D[0]&&parseInt(n.curCSS(D[0],E,true),10)||0}var h="jQuery"+e(),u=0,z={};n.extend({cache:{},data:function(E,D,F){E=E==l?z:E;var G=E[h];if(!G){G=E[h]=++u}if(D&&!n.cache[G]){n.cache[G]={}}if(F!==g){n.cache[G][D]=F}return D?n.cache[G][D]:G},removeData:function(E,D){E=E==l?z:E;var G=E[h];if(D){if(n.cache[G]){delete n.cache[G][D];D="";for(D in n.cache[G]){break}if(!D){n.removeData(E)}}}else{try{delete E[h]}catch(F){if(E.removeAttribute){E.removeAttribute(h)}}delete n.cache[G]}},queue:function(E,D,G){if(E){D=(D||"fx")+"queue";var F=n.data(E,D);if(!F||n.isArray(G)){F=n.data(E,D,n.makeArray(G))}else{if(G){F.push(G)}}}return F},dequeue:function(G,F){var D=n.queue(G,F),E=D.shift();if(!F||F==="fx"){E=D[0]}if(E!==g){E.call(G)}}});n.fn.extend({data:function(D,F){var G=D.split(".");G[1]=G[1]?"."+G[1]:"";if(F===g){var E=this.triggerHandler("getData"+G[1]+"!",[G[0]]);if(E===g&&this.length){E=n.data(this[0],D)}return E===g&&G[1]?this.data(G[0]):E}else{return this.trigger("setData"+G[1]+"!",[G[0],F]).each(function(){n.data(this,D,F)})}},removeData:function(D){return this.each(function(){n.removeData(this,D)})},queue:function(D,E){if(typeof D!=="string"){E=D;D="fx"}if(E===g){return n.queue(this[0],D)}return this.each(function(){var F=n.queue(this,D,E);if(D=="fx"&&F.length==1){F[0].call(this)}})},dequeue:function(D){return this.each(function(){n.dequeue(this,D)})}}); +/* + * Sizzle CSS Selector Engine - v0.9.1 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){var N=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|[^[\]]+)+\]|\\.|[^ >+~,(\[]+)+|[>+~])(\s*,\s*)?/g,I=0,F=Object.prototype.toString;var E=function(ae,S,aa,V){aa=aa||[];S=S||document;if(S.nodeType!==1&&S.nodeType!==9){return[]}if(!ae||typeof ae!=="string"){return aa}var ab=[],ac,Y,ah,ag,Z,R,Q=true;N.lastIndex=0;while((ac=N.exec(ae))!==null){ab.push(ac[1]);if(ac[2]){R=RegExp.rightContext;break}}if(ab.length>1&&G.match.POS.exec(ae)){if(ab.length===2&&G.relative[ab[0]]){var U="",X;while((X=G.match.POS.exec(ae))){U+=X[0];ae=ae.replace(G.match.POS,"")}Y=E.filter(U,E(/\s$/.test(ae)?ae+"*":ae,S))}else{Y=G.relative[ab[0]]?[S]:E(ab.shift(),S);while(ab.length){var P=[];ae=ab.shift();if(G.relative[ae]){ae+=ab.shift()}for(var af=0,ad=Y.length;af0){ah=D(Y)}else{Q=false}while(ab.length){var T=ab.pop(),W=T;if(!G.relative[T]){T=""}else{W=ab.pop()}if(W==null){W=S}G.relative[T](ah,W,M(S))}}if(!ah){ah=Y}if(!ah){throw"Syntax error, unrecognized expression: "+(T||ae)}if(F.call(ah)==="[object Array]"){if(!Q){aa.push.apply(aa,ah)}else{if(S.nodeType===1){for(var af=0;ah[af]!=null;af++){if(ah[af]&&(ah[af]===true||ah[af].nodeType===1&&H(S,ah[af]))){aa.push(Y[af])}}}else{for(var af=0;ah[af]!=null;af++){if(ah[af]&&ah[af].nodeType===1){aa.push(Y[af])}}}}}else{D(ah,aa)}if(R){E(R,S,aa,V)}return aa};E.matches=function(P,Q){return E(P,null,null,Q)};E.find=function(V,S){var W,Q;if(!V){return[]}for(var R=0,P=G.order.length;R":function(U,Q,V){if(typeof Q==="string"&&!/\W/.test(Q)){Q=V?Q:Q.toUpperCase();for(var R=0,P=U.length;R=0){if(!R){P.push(Q[T])}}else{if(R){Q[T]=false}}}return false},ID:function(P){return P[1].replace(/\\/g,"")},TAG:function(Q,P){for(var R=0;!P[R];R++){}return M(P[R])?Q[1]:Q[1].toUpperCase()},CHILD:function(P){if(P[1]=="nth"){var Q=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(P[2]=="even"&&"2n"||P[2]=="odd"&&"2n+1"||!/\D/.test(P[2])&&"0n+"+P[2]||P[2]);P[2]=(Q[1]+(Q[2]||1))-0;P[3]=Q[3]-0}P[0]="done"+(I++);return P},ATTR:function(Q){var P=Q[1];if(G.attrMap[P]){Q[1]=G.attrMap[P]}if(Q[2]==="~="){Q[4]=" "+Q[4]+" "}return Q},PSEUDO:function(T,Q,R,P,U){if(T[1]==="not"){if(T[3].match(N).length>1){T[3]=E(T[3],null,null,Q)}else{var S=E.filter(T[3],Q,R,true^U);if(!R){P.push.apply(P,S)}return false}}else{if(G.match.POS.test(T[0])){return true}}return T},POS:function(P){P.unshift(true);return P}},filters:{enabled:function(P){return P.disabled===false&&P.type!=="hidden"},disabled:function(P){return P.disabled===true},checked:function(P){return P.checked===true},selected:function(P){P.parentNode.selectedIndex;return P.selected===true},parent:function(P){return !!P.firstChild},empty:function(P){return !P.firstChild},has:function(R,Q,P){return !!E(P[3],R).length},header:function(P){return/h\d/i.test(P.nodeName)},text:function(P){return"text"===P.type},radio:function(P){return"radio"===P.type},checkbox:function(P){return"checkbox"===P.type},file:function(P){return"file"===P.type},password:function(P){return"password"===P.type},submit:function(P){return"submit"===P.type},image:function(P){return"image"===P.type},reset:function(P){return"reset"===P.type},button:function(P){return"button"===P.type||P.nodeName.toUpperCase()==="BUTTON"},input:function(P){return/input|select|textarea|button/i.test(P.nodeName)}},setFilters:{first:function(Q,P){return P===0},last:function(R,Q,P,S){return Q===S.length-1},even:function(Q,P){return P%2===0},odd:function(Q,P){return P%2===1},lt:function(R,Q,P){return QP[3]-0},nth:function(R,Q,P){return P[3]-0==Q},eq:function(R,Q,P){return P[3]-0==Q}},filter:{CHILD:function(P,S){var V=S[1],W=P.parentNode;var U="child"+W.childNodes.length;if(W&&(!W[U]||!P.nodeIndex)){var T=1;for(var Q=W.firstChild;Q;Q=Q.nextSibling){if(Q.nodeType==1){Q.nodeIndex=T++}}W[U]=T-1}if(V=="first"){return P.nodeIndex==1}else{if(V=="last"){return P.nodeIndex==W[U]}else{if(V=="only"){return W[U]==1}else{if(V=="nth"){var Y=false,R=S[2],X=S[3];if(R==1&&X==0){return true}if(R==0){if(P.nodeIndex==X){Y=true}}else{if((P.nodeIndex-X)%R==0&&(P.nodeIndex-X)/R>=0){Y=true}}return Y}}}}},PSEUDO:function(V,R,S,W){var Q=R[1],T=G.filters[Q];if(T){return T(V,S,R,W)}else{if(Q==="contains"){return(V.textContent||V.innerText||"").indexOf(R[3])>=0}else{if(Q==="not"){var U=R[3];for(var S=0,P=U.length;S=0:S==="~="?(" "+U+" ").indexOf(Q)>=0:!R[4]?P:S==="!="?U!=Q:S==="^="?U.indexOf(Q)===0:S==="$="?U.substr(U.length-Q.length)===Q:S==="|="?U===Q||U.substr(0,Q.length+1)===Q+"-":false},POS:function(T,Q,R,U){var P=Q[2],S=G.setFilters[P];if(S){return S(T,R,Q,U)}}}};for(var K in G.match){G.match[K]=RegExp(G.match[K].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var D=function(Q,P){Q=Array.prototype.slice.call(Q);if(P){P.push.apply(P,Q);return P}return Q};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(J){D=function(T,S){var Q=S||[];if(F.call(T)==="[object Array]"){Array.prototype.push.apply(Q,T)}else{if(typeof T.length==="number"){for(var R=0,P=T.length;R";var P=document.documentElement;P.insertBefore(Q,P.firstChild);if(!!document.getElementById(R)){G.find.ID=function(T,U){if(U.getElementById){var S=U.getElementById(T[1]);return S?S.id===T[1]||S.getAttributeNode&&S.getAttributeNode("id").nodeValue===T[1]?[S]:g:[]}};G.filter.ID=function(U,S){var T=U.getAttributeNode&&U.getAttributeNode("id");return U.nodeType===1&&T&&T.nodeValue===S}}P.removeChild(Q)})();(function(){var P=document.createElement("div");P.appendChild(document.createComment(""));if(P.getElementsByTagName("*").length>0){G.find.TAG=function(Q,U){var T=U.getElementsByTagName(Q[1]);if(Q[1]==="*"){var S=[];for(var R=0;T[R];R++){if(T[R].nodeType===1){S.push(T[R])}}T=S}return T}}P.innerHTML="";if(P.firstChild.getAttribute("href")!=="#"){G.attrHandle.href=function(Q){return Q.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var P=E;E=function(T,S,Q,R){S=S||document;if(!R&&S.nodeType===9){try{return D(S.querySelectorAll(T),Q)}catch(U){}}return P(T,S,Q,R)};E.find=P.find;E.filter=P.filter;E.selectors=P.selectors;E.matches=P.matches})()}if(document.documentElement.getElementsByClassName){G.order.splice(1,0,"CLASS");G.find.CLASS=function(P,Q){return Q.getElementsByClassName(P[1])}}function L(Q,W,V,Z,X,Y){for(var T=0,R=Z.length;T0){T=P;break}}}P=P[Q]}Y[S]=T}}}var H=document.compareDocumentPosition?function(Q,P){return Q.compareDocumentPosition(P)&16}:function(Q,P){return Q!==P&&(Q.contains?Q.contains(P):true)};var M=function(P){return P.documentElement&&!P.body||P.tagName&&P.ownerDocument&&!P.ownerDocument.body};n.find=E;n.filter=E.filter;n.expr=E.selectors;n.expr[":"]=n.expr.filters;E.selectors.filters.hidden=function(P){return"hidden"===P.type||n.css(P,"display")==="none"||n.css(P,"visibility")==="hidden"};E.selectors.filters.visible=function(P){return"hidden"!==P.type&&n.css(P,"display")!=="none"&&n.css(P,"visibility")!=="hidden"};E.selectors.filters.animated=function(P){return n.grep(n.timers,function(Q){return P===Q.elem}).length};n.multiFilter=function(R,P,Q){if(Q){R=":not("+R+")"}return E.matches(R,P)};n.dir=function(R,Q){var P=[],S=R[Q];while(S&&S!=document){if(S.nodeType==1){P.push(S)}S=S[Q]}return P};n.nth=function(T,P,R,S){P=P||1;var Q=0;for(;T;T=T[R]){if(T.nodeType==1&&++Q==P){break}}return T};n.sibling=function(R,Q){var P=[];for(;R;R=R.nextSibling){if(R.nodeType==1&&R!=Q){P.push(R)}}return P};return;l.Sizzle=E})();n.event={add:function(H,E,G,J){if(H.nodeType==3||H.nodeType==8){return}if(H.setInterval&&H!=l){H=l}if(!G.guid){G.guid=this.guid++}if(J!==g){var F=G;G=this.proxy(F);G.data=J}var D=n.data(H,"events")||n.data(H,"events",{}),I=n.data(H,"handle")||n.data(H,"handle",function(){return typeof n!=="undefined"&&!n.event.triggered?n.event.handle.apply(arguments.callee.elem,arguments):g});I.elem=H;n.each(E.split(/\s+/),function(L,M){var N=M.split(".");M=N.shift();G.type=N.slice().sort().join(".");var K=D[M];if(n.event.specialAll[M]){n.event.specialAll[M].setup.call(H,J,N)}if(!K){K=D[M]={};if(!n.event.special[M]||n.event.special[M].setup.call(H,J,N)===false){if(H.addEventListener){H.addEventListener(M,I,false)}else{if(H.attachEvent){H.attachEvent("on"+M,I)}}}}K[G.guid]=G;n.event.global[M]=true});H=null},guid:1,global:{},remove:function(J,G,I){if(J.nodeType==3||J.nodeType==8){return}var F=n.data(J,"events"),E,D;if(F){if(G===g||(typeof G==="string"&&G.charAt(0)==".")){for(var H in F){this.remove(J,H+(G||""))}}else{if(G.type){I=G.handler;G=G.type}n.each(G.split(/\s+/),function(L,N){var P=N.split(".");N=P.shift();var M=RegExp("(^|\\.)"+P.slice().sort().join(".*\\.")+"(\\.|$)");if(F[N]){if(I){delete F[N][I.guid]}else{for(var O in F[N]){if(M.test(F[N][O].type)){delete F[N][O]}}}if(n.event.specialAll[N]){n.event.specialAll[N].teardown.call(J,P)}for(E in F[N]){break}if(!E){if(!n.event.special[N]||n.event.special[N].teardown.call(J,P)===false){if(J.removeEventListener){J.removeEventListener(N,n.data(J,"handle"),false)}else{if(J.detachEvent){J.detachEvent("on"+N,n.data(J,"handle"))}}}E=null;delete F[N]}}})}for(E in F){break}if(!E){var K=n.data(J,"handle");if(K){K.elem=null}n.removeData(J,"events");n.removeData(J,"handle")}}},trigger:function(H,J,G,D){var F=H.type||H;if(!D){H=typeof H==="object"?H[h]?H:n.extend(n.Event(F),H):n.Event(F);if(F.indexOf("!")>=0){H.type=F=F.slice(0,-1);H.exclusive=true}if(!G){H.stopPropagation();if(this.global[F]){n.each(n.cache,function(){if(this.events&&this.events[F]){n.event.trigger(H,J,this.handle.elem)}})}}if(!G||G.nodeType==3||G.nodeType==8){return g}H.result=g;H.target=G;J=n.makeArray(J);J.unshift(H)}H.currentTarget=G;var I=n.data(G,"handle");if(I){I.apply(G,J)}if((!G[F]||(n.nodeName(G,"a")&&F=="click"))&&G["on"+F]&&G["on"+F].apply(G,J)===false){H.result=false}if(!D&&G[F]&&!H.isDefaultPrevented()&&!(n.nodeName(G,"a")&&F=="click")){this.triggered=true;try{G[F]()}catch(K){}}this.triggered=false;if(!H.isPropagationStopped()){var E=G.parentNode||G.ownerDocument;if(E){n.event.trigger(H,J,E,true)}}},handle:function(J){var I,D;J=arguments[0]=n.event.fix(J||l.event);var K=J.type.split(".");J.type=K.shift();I=!K.length&&!J.exclusive;var H=RegExp("(^|\\.)"+K.slice().sort().join(".*\\.")+"(\\.|$)");D=(n.data(this,"events")||{})[J.type];for(var F in D){var G=D[F];if(I||H.test(G.type)){J.handler=G;J.data=G.data;var E=G.apply(this,arguments);if(E!==g){J.result=E;if(E===false){J.preventDefault();J.stopPropagation()}}if(J.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(G){if(G[h]){return G}var E=G;G=n.Event(E);for(var F=this.props.length,I;F;){I=this.props[--F];G[I]=E[I]}if(!G.target){G.target=G.srcElement||document}if(G.target.nodeType==3){G.target=G.target.parentNode}if(!G.relatedTarget&&G.fromElement){G.relatedTarget=G.fromElement==G.target?G.toElement:G.fromElement}if(G.pageX==null&&G.clientX!=null){var H=document.documentElement,D=document.body;G.pageX=G.clientX+(H&&H.scrollLeft||D&&D.scrollLeft||0)-(H.clientLeft||0);G.pageY=G.clientY+(H&&H.scrollTop||D&&D.scrollTop||0)-(H.clientTop||0)}if(!G.which&&((G.charCode||G.charCode===0)?G.charCode:G.keyCode)){G.which=G.charCode||G.keyCode}if(!G.metaKey&&G.ctrlKey){G.metaKey=G.ctrlKey}if(!G.which&&G.button){G.which=(G.button&1?1:(G.button&2?3:(G.button&4?2:0)))}return G},proxy:function(E,D){D=D||function(){return E.apply(this,arguments)};D.guid=E.guid=E.guid||D.guid||this.guid++;return D},special:{ready:{setup:A,teardown:function(){}}},specialAll:{live:{setup:function(D,E){n.event.add(this,E[0],c)},teardown:function(F){if(F.length){var D=0,E=RegExp("(^|\\.)"+F[0]+"(\\.|$)");n.each((n.data(this,"events").live||{}),function(){if(E.test(this.type)){D++}});if(D<1){n.event.remove(this,F[0],c)}}}}}};n.Event=function(D){if(!this.preventDefault){return new n.Event(D)}if(D&&D.type){this.originalEvent=D;this.type=D.type;this.timeStamp=D.timeStamp}else{this.type=D}if(!this.timeStamp){this.timeStamp=e()}this[h]=true};function k(){return false}function t(){return true}n.Event.prototype={preventDefault:function(){this.isDefaultPrevented=t;var D=this.originalEvent;if(!D){return}if(D.preventDefault){D.preventDefault()}D.returnValue=false},stopPropagation:function(){this.isPropagationStopped=t;var D=this.originalEvent;if(!D){return}if(D.stopPropagation){D.stopPropagation()}D.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=t;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(E){var D=E.relatedTarget;while(D&&D!=this){try{D=D.parentNode}catch(F){D=this}}if(D!=this){E.type=E.data;n.event.handle.apply(this,arguments)}};n.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(E,D){n.event.special[D]={setup:function(){n.event.add(this,E,a,D)},teardown:function(){n.event.remove(this,E,a)}}});n.fn.extend({bind:function(E,F,D){return E=="unload"?this.one(E,F,D):this.each(function(){n.event.add(this,E,D||F,D&&F)})},one:function(F,G,E){var D=n.event.proxy(E||G,function(H){n(this).unbind(H,D);return(E||G).apply(this,arguments)});return this.each(function(){n.event.add(this,F,D,E&&G)})},unbind:function(E,D){return this.each(function(){n.event.remove(this,E,D)})},trigger:function(D,E){return this.each(function(){n.event.trigger(D,E,this)})},triggerHandler:function(D,F){if(this[0]){var E=n.Event(D);E.preventDefault();E.stopPropagation();n.event.trigger(E,F,this[0]);return E.result}},toggle:function(F){var D=arguments,E=1;while(E=0){var D=F.slice(H,F.length);F=F.slice(0,H)}var G="GET";if(I){if(n.isFunction(I)){J=I;I=null}else{if(typeof I==="object"){I=n.param(I);G="POST"}}}var E=this;n.ajax({url:F,type:G,dataType:"html",data:I,complete:function(L,K){if(K=="success"||K=="notmodified"){E.html(D?n("
    ").append(L.responseText.replace(//g,"")).find(D):L.responseText)}if(J){E.each(J,[L.responseText,K,L])}}});return this},serialize:function(){return n.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?n.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type))}).map(function(D,E){var F=n(this).val();return F==null?null:n.isArray(F)?n.map(F,function(H,G){return{name:E.name,value:H}}):{name:E.name,value:F}}).get()}});n.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(D,E){n.fn[E]=function(F){return this.bind(E,F)}});var q=e();n.extend({get:function(D,F,G,E){if(n.isFunction(F)){G=F;F=null}return n.ajax({type:"GET",url:D,data:F,success:G,dataType:E})},getScript:function(D,E){return n.get(D,null,E,"script")},getJSON:function(D,E,F){return n.get(D,E,F,"json")},post:function(D,F,G,E){if(n.isFunction(F)){G=F;F={}}return n.ajax({type:"POST",url:D,data:F,success:G,dataType:E})},ajaxSetup:function(D){n.extend(n.ajaxSettings,D)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(L){L=n.extend(true,L,n.extend(true,{},n.ajaxSettings,L));var V,E=/=\?(&|$)/g,Q,U,F=L.type.toUpperCase();if(L.data&&L.processData&&typeof L.data!=="string"){L.data=n.param(L.data)}if(L.dataType=="jsonp"){if(F=="GET"){if(!L.url.match(E)){L.url+=(L.url.match(/\?/)?"&":"?")+(L.jsonp||"callback")+"=?"}}else{if(!L.data||!L.data.match(E)){L.data=(L.data?L.data+"&":"")+(L.jsonp||"callback")+"=?"}}L.dataType="json"}if(L.dataType=="json"&&(L.data&&L.data.match(E)||L.url.match(E))){V="jsonp"+q++;if(L.data){L.data=(L.data+"").replace(E,"="+V+"$1")}L.url=L.url.replace(E,"="+V+"$1");L.dataType="script";l[V]=function(W){U=W;H();K();l[V]=g;try{delete l[V]}catch(X){}if(G){G.removeChild(S)}}}if(L.dataType=="script"&&L.cache==null){L.cache=false}if(L.cache===false&&F=="GET"){var D=e();var T=L.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+D+"$2");L.url=T+((T==L.url)?(L.url.match(/\?/)?"&":"?")+"_="+D:"")}if(L.data&&F=="GET"){L.url+=(L.url.match(/\?/)?"&":"?")+L.data;L.data=null}if(L.global&&!n.active++){n.event.trigger("ajaxStart")}var P=/^(\w+:)?\/\/([^\/?#]+)/.exec(L.url);if(L.dataType=="script"&&F=="GET"&&P&&(P[1]&&P[1]!=location.protocol||P[2]!=location.host)){var G=document.getElementsByTagName("head")[0];var S=document.createElement("script");S.src=L.url;if(L.scriptCharset){S.charset=L.scriptCharset}if(!V){var N=false;S.onload=S.onreadystatechange=function(){if(!N&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){N=true;H();K();G.removeChild(S)}}}G.appendChild(S);return g}var J=false;var I=L.xhr();if(L.username){I.open(F,L.url,L.async,L.username,L.password)}else{I.open(F,L.url,L.async)}try{if(L.data){I.setRequestHeader("Content-Type",L.contentType)}if(L.ifModified){I.setRequestHeader("If-Modified-Since",n.lastModified[L.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}I.setRequestHeader("X-Requested-With","XMLHttpRequest");I.setRequestHeader("Accept",L.dataType&&L.accepts[L.dataType]?L.accepts[L.dataType]+", */*":L.accepts._default)}catch(R){}if(L.beforeSend&&L.beforeSend(I,L)===false){if(L.global&&!--n.active){n.event.trigger("ajaxStop")}I.abort();return false}if(L.global){n.event.trigger("ajaxSend",[I,L])}var M=function(W){if(I.readyState==0){if(O){clearInterval(O);O=null;if(L.global&&!--n.active){n.event.trigger("ajaxStop")}}}else{if(!J&&I&&(I.readyState==4||W=="timeout")){J=true;if(O){clearInterval(O);O=null}Q=W=="timeout"?"timeout":!n.httpSuccess(I)?"error":L.ifModified&&n.httpNotModified(I,L.url)?"notmodified":"success";if(Q=="success"){try{U=n.httpData(I,L.dataType,L)}catch(Y){Q="parsererror"}}if(Q=="success"){var X;try{X=I.getResponseHeader("Last-Modified")}catch(Y){}if(L.ifModified&&X){n.lastModified[L.url]=X}if(!V){H()}}else{n.handleError(L,I,Q)}K();if(L.async){I=null}}}};if(L.async){var O=setInterval(M,13);if(L.timeout>0){setTimeout(function(){if(I){if(!J){M("timeout")}if(I){I.abort()}}},L.timeout)}}try{I.send(L.data)}catch(R){n.handleError(L,I,null,R)}if(!L.async){M()}function H(){if(L.success){L.success(U,Q)}if(L.global){n.event.trigger("ajaxSuccess",[I,L])}}function K(){if(L.complete){L.complete(I,Q)}if(L.global){n.event.trigger("ajaxComplete",[I,L])}if(L.global&&!--n.active){n.event.trigger("ajaxStop")}}return I},handleError:function(E,G,D,F){if(E.error){E.error(G,D,F)}if(E.global){n.event.trigger("ajaxError",[G,E,F])}},active:0,httpSuccess:function(E){try{return !E.status&&location.protocol=="file:"||(E.status>=200&&E.status<300)||E.status==304||E.status==1223}catch(D){}return false},httpNotModified:function(F,D){try{var G=F.getResponseHeader("Last-Modified");return F.status==304||G==n.lastModified[D]}catch(E){}return false},httpData:function(I,G,F){var E=I.getResponseHeader("content-type"),D=G=="xml"||!G&&E&&E.indexOf("xml")>=0,H=D?I.responseXML:I.responseText;if(D&&H.documentElement.tagName=="parsererror"){throw"parsererror"}if(F&&F.dataFilter){H=F.dataFilter(H,G)}if(typeof H==="string"){if(G=="script"){n.globalEval(H)}if(G=="json"){H=l["eval"]("("+H+")")}}return H},param:function(D){var F=[];function G(H,I){F[F.length]=encodeURIComponent(H)+"="+encodeURIComponent(I)}if(n.isArray(D)||D.jquery){n.each(D,function(){G(this.name,this.value)})}else{for(var E in D){if(n.isArray(D[E])){n.each(D[E],function(){G(E,this)})}else{G(E,n.isFunction(D[E])?D[E]():D[E])}}}return F.join("&").replace(/%20/g,"+")}});var m={},d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function s(E,D){var F={};n.each(d.concat.apply([],d.slice(0,D)),function(){F[this]=E});return F}n.fn.extend({show:function(I,K){if(I){return this.animate(s("show",3),I,K)}else{for(var G=0,E=this.length;G").appendTo("body");J=H.css("display");if(J==="none"){J="block"}H.remove();m[F]=J}this[G].style.display=n.data(this[G],"olddisplay",J)}}return this}},hide:function(G,H){if(G){return this.animate(s("hide",3),G,H)}else{for(var F=0,E=this.length;F=0;G--){if(F[G].elem==this){if(D){F[G](true)}F.splice(G,1)}}});if(!D){this.dequeue()}return this}});n.each({slideDown:s("show",1),slideUp:s("hide",1),slideToggle:s("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(D,E){n.fn[D]=function(F,G){return this.animate(E,F,G)}});n.extend({speed:function(F,G,E){var D=typeof F==="object"?F:{complete:E||!E&&G||n.isFunction(F)&&F,duration:F,easing:E&&G||G&&!n.isFunction(G)&&G};D.duration=n.fx.off?0:typeof D.duration==="number"?D.duration:n.fx.speeds[D.duration]||n.fx.speeds._default;D.old=D.complete;D.complete=function(){if(D.queue!==false){n(this).dequeue()}if(n.isFunction(D.old)){D.old.call(this)}};return D},easing:{linear:function(F,G,D,E){return D+E*F},swing:function(F,G,D,E){return((-Math.cos(F*Math.PI)/2)+0.5)*E+D}},timers:[],timerId:null,fx:function(E,D,F){this.options=D;this.elem=E;this.prop=F;if(!D.orig){D.orig={}}}});n.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(n.fx.step[this.prop]||n.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(E){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var D=parseFloat(n.css(this.elem,this.prop,E));return D&&D>-10000?D:parseFloat(n.curCSS(this.elem,this.prop))||0},custom:function(H,G,F){this.startTime=e();this.start=H;this.end=G;this.unit=F||this.unit||"px";this.now=this.start;this.pos=this.state=0;var D=this;function E(I){return D.step(I)}E.elem=this.elem;n.timers.push(E);if(E()&&n.timerId==null){n.timerId=setInterval(function(){var J=n.timers;for(var I=0;I=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var D=true;for(var E in this.options.curAnim){if(this.options.curAnim[E]!==true){D=false}}if(D){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(n.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){n(this.elem).hide()}if(this.options.hide||this.options.show){for(var H in this.options.curAnim){n.attr(this.elem.style,H,this.options.orig[H])}}}if(D){this.options.complete.call(this.elem)}return false}else{var I=F-this.startTime;this.state=I/this.options.duration;this.pos=n.easing[this.options.easing||(n.easing.swing?"swing":"linear")](this.state,I,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};n.extend(n.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(D){n.attr(D.elem.style,"opacity",D.now)},_default:function(D){if(D.elem.style&&D.elem.style[D.prop]!=null){D.elem.style[D.prop]=D.now+D.unit}else{D.elem[D.prop]=D.now}}}});if(document.documentElement.getBoundingClientRect){n.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return n.offset.bodyOffset(this[0])}var F=this[0].getBoundingClientRect(),I=this[0].ownerDocument,E=I.body,D=I.documentElement,K=D.clientTop||E.clientTop||0,J=D.clientLeft||E.clientLeft||0,H=F.top+(self.pageYOffset||n.boxModel&&D.scrollTop||E.scrollTop)-K,G=F.left+(self.pageXOffset||n.boxModel&&D.scrollLeft||E.scrollLeft)-J;return{top:H,left:G}}}else{n.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return n.offset.bodyOffset(this[0])}n.offset.initialized||n.offset.initialize();var I=this[0],F=I.offsetParent,E=I,N=I.ownerDocument,L,G=N.documentElement,J=N.body,K=N.defaultView,D=K.getComputedStyle(I,null),M=I.offsetTop,H=I.offsetLeft;while((I=I.parentNode)&&I!==J&&I!==G){L=K.getComputedStyle(I,null);M-=I.scrollTop,H-=I.scrollLeft;if(I===F){M+=I.offsetTop,H+=I.offsetLeft;if(n.offset.doesNotAddBorder&&!(n.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(I.tagName))){M+=parseInt(L.borderTopWidth,10)||0,H+=parseInt(L.borderLeftWidth,10)||0}E=F,F=I.offsetParent}if(n.offset.subtractsBorderForOverflowNotVisible&&L.overflow!=="visible"){M+=parseInt(L.borderTopWidth,10)||0,H+=parseInt(L.borderLeftWidth,10)||0}D=L}if(D.position==="relative"||D.position==="static"){M+=J.offsetTop,H+=J.offsetLeft}if(D.position==="fixed"){M+=Math.max(G.scrollTop,J.scrollTop),H+=Math.max(G.scrollLeft,J.scrollLeft)}return{top:M,left:H}}}n.offset={initialize:function(){if(this.initialized){return}var K=document.body,E=document.createElement("div"),G,F,M,H,L,D,I=K.style.marginTop,J='
    ';L={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(D in L){E.style[D]=L[D]}E.innerHTML=J;K.insertBefore(E,K.firstChild);G=E.firstChild,F=G.firstChild,H=G.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(F.offsetTop!==5);this.doesAddBorderForTableAndCells=(H.offsetTop===5);G.style.overflow="hidden",G.style.position="relative";this.subtractsBorderForOverflowNotVisible=(F.offsetTop===-5);K.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(K.offsetTop===0);K.style.marginTop=I;K.removeChild(E);this.initialized=true},bodyOffset:function(D){n.offset.initialized||n.offset.initialize();var F=D.offsetTop,E=D.offsetLeft;if(n.offset.doesNotIncludeMarginInBodyOffset){F+=parseInt(n.curCSS(D,"marginTop",true),10)||0,E+=parseInt(n.curCSS(D,"marginLeft",true),10)||0}return{top:F,left:E}}};n.fn.extend({position:function(){var H=0,G=0,E;if(this[0]){var F=this.offsetParent(),I=this.offset(),D=/^body|html$/i.test(F[0].tagName)?{top:0,left:0}:F.offset();I.top-=j(this,"marginTop");I.left-=j(this,"marginLeft");D.top+=j(F,"borderTopWidth");D.left+=j(F,"borderLeftWidth");E={top:I.top-D.top,left:I.left-D.left}}return E},offsetParent:function(){var D=this[0].offsetParent||document.body;while(D&&(!/^body|html$/i.test(D.tagName)&&n.css(D,"position")=="static")){D=D.offsetParent}return n(D)}});n.each(["Left","Top"],function(E,D){var F="scroll"+D;n.fn[F]=function(G){if(!this[0]){return null}return G!==g?this.each(function(){this==l||this==document?l.scrollTo(!E?G:n(l).scrollLeft(),E?G:n(l).scrollTop()):this[F]=G}):this[0]==l||this[0]==document?self[E?"pageYOffset":"pageXOffset"]||n.boxModel&&document.documentElement[F]||document.body[F]:this[0][F]}});n.each(["Height","Width"],function(G,E){var D=G?"Left":"Top",F=G?"Right":"Bottom";n.fn["inner"+E]=function(){return this[E.toLowerCase()]()+j(this,"padding"+D)+j(this,"padding"+F)};n.fn["outer"+E]=function(I){return this["inner"+E]()+j(this,"border"+D+"Width")+j(this,"border"+F+"Width")+(I?j(this,"margin"+D)+j(this,"margin"+F):0)};var H=E.toLowerCase();n.fn[H]=function(I){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+E]||document.body["client"+E]:this[0]==document?Math.max(document.documentElement["client"+E],document.body["scroll"+E],document.documentElement["scroll"+E],document.body["offset"+E],document.documentElement["offset"+E]):I===g?(this.length?n.css(this[0],H):null):this.css(H,typeof I==="string"?I:I+"px")}})})(); \ No newline at end of file diff --git a/web/public/merb.fcgi b/web/public/merb.fcgi new file mode 100755 index 000000000..9804e0f36 --- /dev/null +++ b/web/public/merb.fcgi @@ -0,0 +1,22 @@ +#!/usr/bin/env ruby + +require 'rubygems' +require 'merb-core' + +# this is Merb.root, change this if you have some funky setup. +merb_root = File.expand_path(File.dirname(__FILE__) / '../') + +# If the fcgi process runs as apache, make sure +# we have an inlinedir set for Rubyinline action-args to work +unless ENV["INLINEDIR"] || ENV["HOME"] + tmpdir = merb_root / "tmp" + unless File.directory?(tmpdir) + Dir.mkdir(tmpdir) + end + ENV["INLINEDIR"] = tmpdir +end + +# start merb with the fcgi adapter, add options or change the log dir here +Merb.start(:adapter => 'fcgi', + :merb_root => merb_root, + :log_file => merb_root /'log'/'merb.log') \ No newline at end of file diff --git a/web/public/robots.txt b/web/public/robots.txt new file mode 100644 index 000000000..f85a11b3b --- /dev/null +++ b/web/public/robots.txt @@ -0,0 +1,5 @@ +# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file +# +# To ban all spiders from the entire site uncomment the next two lines: +# User-Agent: * +# Disallow: / \ No newline at end of file diff --git a/web/public/stylesheets/master.css b/web/public/stylesheets/master.css new file mode 100644 index 000000000..c4fa67604 --- /dev/null +++ b/web/public/stylesheets/master.css @@ -0,0 +1,119 @@ +body { + font-family: Arial, Verdana, sans-serif; + font-size: 12px; + background-color: #fff; +} +* { + margin: 0px; + padding: 0px; + text-decoration: none; +} +html { + height: 100%; + margin-bottom: 1px; +} +#container { + width: 80%; + text-align: left; + background-color: #fff; + margin-right: auto; + margin-left: auto; +} +#header-container { + width: 100%; + padding-top: 15px; +} +#header-container h1, #header-container h2 { + margin-left: 6px; + margin-bottom: 6px; +} +.spacer { + width: 100%; + height: 15px; +} +hr { + border: 0px; + color: #ccc; + background-color: #cdcdcd; + height: 1px; + width: 100%; + text-align: left; +} +h1 { + font-size: 28px; + color: #c55; + background-color: #fff; + font-family: Arial, Verdana, sans-serif; + font-weight: 300; +} +h2 { + font-size: 15px; + color: #999; + font-family: Arial, Verdana, sans-serif; + font-weight: 300; + background-color: #fff; +} +h3 { + color: #4d9b12; + font-size: 15px; + text-align: left; + font-weight: 300; + padding: 5px; + margin-top: 5px; +} + +#left-container { + float: left; + width: 250px; + background-color: #FFFFFF; + color: black; +} + +#left-container h3 { + color: #c55; +} + +#main-container { + margin: 5px 5px 5px 260px; + padding: 15px; + border-left: 1px solid silver; + min-height: 400px; +} +p { + color: #000; + background-color: #fff; + line-height: 20px; + padding: 5px; +} +a { + color: #4d9b12; + background-color: #fff; + text-decoration: none; +} +a:hover { + color: #4d9b12; + background-color: #fff; + text-decoration: underline; +} +#footer-container { + clear: both; + font-size: 12px; + font-family: Verdana, Arial, sans-serif; +} +.right { + float: right; + font-size: 100%; + margin-top: 5px; + color: #999; + background-color: #fff; +} +.left { + float: left; + font-size: 100%; + margin-top: 5px; + color: #999; + background-color: #fff; +} +#main-container ul { + margin-left: 3.0em; +} \ No newline at end of file diff --git a/web/spec/requests/search_spec.rb b/web/spec/requests/search_spec.rb new file mode 100644 index 000000000..cda43c17e --- /dev/null +++ b/web/spec/requests/search_spec.rb @@ -0,0 +1,7 @@ +require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb') + +describe "/search" do + before(:each) do + @response = request("/search") + end +end \ No newline at end of file diff --git a/web/spec/spec.opts b/web/spec/spec.opts new file mode 100644 index 000000000..e69de29bb diff --git a/web/spec/spec_helper.rb b/web/spec/spec_helper.rb new file mode 100644 index 000000000..36ebcd57e --- /dev/null +++ b/web/spec/spec_helper.rb @@ -0,0 +1,25 @@ +require "rubygems" + +# Add the local gems dir if found within the app root; any dependencies loaded +# hereafter will try to load from the local gems before loading system gems. +if (local_gem_dir = File.join(File.dirname(__FILE__), '..', 'gems')) && $BUNDLE.nil? + $BUNDLE = true; Gem.clear_paths; Gem.path.unshift(local_gem_dir) +end + +require "merb-core" +require "spec" # Satisfies Autotest and anyone else not using the Rake tasks + +# this loads all plugins required in your init file so don't add them +# here again, Merb will do it for you +Merb.start_environment(:testing => true, :adapter => 'runner', :environment => ENV['MERB_ENV'] || 'test') + +Spec::Runner.configure do |config| + config.include(Merb::Test::ViewHelper) + config.include(Merb::Test::RouteHelper) + config.include(Merb::Test::ControllerHelper) + + config.before(:all) do + DataMapper.auto_migrate! if Merb.orm == :datamapper + end + +end diff --git a/web/tasks/doc.thor b/web/tasks/doc.thor new file mode 100644 index 000000000..77e322271 --- /dev/null +++ b/web/tasks/doc.thor @@ -0,0 +1,149 @@ +$: << File.join("doc") +require 'rubygems' +require 'rdoc/rdoc' +require 'fileutils' +require 'erb' + +module Merb + + class GemNotFoundException < Exception + end + + module DocMethods + def setup_gem_path + if File.directory?(gems_dir = File.join(File.dirname(__FILE__), 'gems')) + $BUNDLE = true; Gem.clear_paths; Gem.path.unshift(gems_dir) + end + end + + def get_more + libs = [] + more_library = find_library("merb-more") + File.open("#{more_library}/lib/merb-more.rb").read.each_line do |line| + if line['require'] + libs << line.gsub("require '", '').gsub("'\n", '') + end + end + return libs + end + + def generate_documentation(file_list, destination, arguments = []) + output_dir = File.join("/../doc", "rdoc", destination) + FileUtils.rm_rf(output_dir) + + arguments += [ + "--fmt", "merb", + "--op", output_dir + ] + RDoc::RDoc.new.document(arguments + file_list) + AdvancedDoc.new.index + end + + def find_library(directory_snippet) + gem_dir = nil + Gem.path.find do |path| + dir = Dir.glob("#{path}/gems/#{directory_snippet}*") + dir.empty? ? false : gem_dir = dir.last + end + raise GemNotFoundException if gem_dir.nil? + return gem_dir + end + + def get_file_list(directory_snippet) + gem_dir = find_library(directory_snippet) + files = Dir.glob("#{gem_dir}/**/lib/**/*.rb") + files += ["#{gem_dir}/README"] if File.exists?("#{gem_dir}/README") + return files + end + end + + class AdvancedDoc < Thor + + group 'core' + include DocMethods + + def initialize + super + setup_gem_path + end + + desc 'index', "Regenerate the index file for your framework documentation" + def index + @directories = Dir.entries(File.join(File.dirname(__FILE__) + "/../", "doc", "rdoc")) + @directories.delete(".") + @directories.delete("..") + @directories.delete("generators") + @directories.delete("index.html") + index_template = File.read(File.join("doc", "rdoc", "generators", "template", "merb", "index.html.erb")) + + File.open(File.join("doc", "rdoc", "index.html"), "w") do |file| + file.write(ERB.new(index_template).result(binding)) + end + end + + desc 'plugins', 'Generate the rdoc for each merb-plugins seperatly' + def plugins + libs = ["merb_activerecord", "merb_builder", "merb_jquery", "merb_laszlo", "merb_parts", "merb_screw_unit", "merb_sequel", "merb_stories", "merb_test_unit"] + + libs.each do |lib| + options[:gem] = lib + gem + end + end + + desc 'more', 'Generate the rdoc for each merb-more gem seperatly' + def more + libs = get_more + libs.each do |lib| + options[:gem] = lib + gem + end + end + + desc 'core', 'Generate the rdoc for merb-core' + def core + options[:gem] = "merb-core" + gem + end + + desc 'gem', 'Generate the rdoc for a specific gem' + method_options "--gem" => :required + def gem + file_list = get_file_list(options[:gem]) + readme = File.join(find_library("merb-core"), "README") + generate_documentation(file_list, options[:gem], ["-m", readme]) + rescue GemNotFoundException + puts "Can not find the gem in the gem path #{options[:gem]}" + end + + end + + class Doc < Thor + + include DocMethods + + def initialize + super + setup_gem_path + + end + + desc 'stack', 'Generate the rdoc for merb-core, merb-more merged together' + def stack + libs = ["merb"] + + file_list = [] + libs.each do |gem_name| + begin + file_list += get_file_list(gem_name) + rescue GemNotFoundException + puts "Could not find #{gem_name} in #{Gem.path}. Continuing with out it." + end + end + readme = File.join(find_library("merb"), "README") + generate_documentation(file_list, "stack", ["-m", readme]) + end + + end + +end \ No newline at end of file diff --git a/web/tasks/merb.thor/app_script.rb b/web/tasks/merb.thor/app_script.rb new file mode 100644 index 000000000..fb0e1163f --- /dev/null +++ b/web/tasks/merb.thor/app_script.rb @@ -0,0 +1,31 @@ +#!/usr/bin/env ruby + +# This was added by Merb's bundler + +require "rubygems" +require File.join(File.dirname(__FILE__), "common") + +gems_dir = File.join(File.dirname(__FILE__), '..', 'gems') + +if File.directory?(gems_dir) + $BUNDLE = true + Gem.clear_paths + Gem.path.replace([File.expand_path(gems_dir)]) + ENV["PATH"] = "#{File.dirname(__FILE__)}:#{ENV["PATH"]}" + + gem_file = File.join(gems_dir, "specifications", "<%= spec.name %>-*.gemspec") + + if local_gem = Dir[gem_file].last + version = File.basename(local_gem)[/-([\.\d]+)\.gemspec$/, 1] + end +end + +version ||= "<%= Gem::Requirement.default %>" + +if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then + version = $1 + ARGV.shift +end + +gem '<%= @spec.name %>', version +load '<%= bin_file_name %>' \ No newline at end of file diff --git a/web/tasks/merb.thor/common.rb b/web/tasks/merb.thor/common.rb new file mode 100644 index 000000000..9b722a194 --- /dev/null +++ b/web/tasks/merb.thor/common.rb @@ -0,0 +1,68 @@ +# This was added via Merb's bundler + +require "rubygems" +require "rubygems/source_index" + +module Gem + BUNDLED_SPECS = File.join(Dir.pwd, "gems", "specifications") + MAIN_INDEX = Gem::SourceIndex.from_gems_in(BUNDLED_SPECS) + FALLBACK_INDEX = Gem::SourceIndex.from_installed_gems + + def self.source_index + MultiSourceIndex.new + end + + def self.searcher + MultiPathSearcher.new + end + + class ArbitrarySearcher < GemPathSearcher + def initialize(source_index) + @source_index = source_index + super() + end + + def init_gemspecs + @source_index.map { |_, spec| spec }.sort { |a,b| + (a.name <=> b.name).nonzero? || (b.version <=> a.version) + } + end + end + + class MultiPathSearcher + def initialize + @main_searcher = ArbitrarySearcher.new(MAIN_INDEX) + @fallback_searcher = ArbitrarySearcher.new(FALLBACK_INDEX) + end + + def find(path) + try = @main_searcher.find(path) + return try if try + @fallback_searcher.find(path) + end + + def find_all(path) + try = @main_searcher.find_all(path) + return try unless try.empty? + @fallback_searcher.find_all(path) + end + end + + class MultiSourceIndex + # Used by merb.thor to confirm; not needed when MSI is in use + def load_gems_in(*args) + end + + def search(*args) + try = MAIN_INDEX.search(*args) + return try unless try.empty? + FALLBACK_INDEX.search(*args) + end + + def find_name(*args) + try = MAIN_INDEX.find_name(*args) + return try unless try.empty? + FALLBACK_INDEX.find_name(*args) + end + end +end \ No newline at end of file diff --git a/web/tasks/merb.thor/gem_ext.rb b/web/tasks/merb.thor/gem_ext.rb new file mode 100644 index 000000000..6b605d7c8 --- /dev/null +++ b/web/tasks/merb.thor/gem_ext.rb @@ -0,0 +1,125 @@ +require "erb" + +Gem.pre_install_hooks.push(proc do |installer| + unless File.file?(installer.bin_dir / "common.rb") + FileUtils.mkdir_p(installer.bin_dir) + FileUtils.cp(File.dirname(__FILE__) / "common.rb", installer.bin_dir / "common.rb") + end + + include ColorfulMessages + name = installer.spec.name + if $GEMS && versions = ($GEMS.assoc(name) || [])[1] + dep = Gem::Dependency.new(name, versions) + unless dep.version_requirements.satisfied_by?(installer.spec.version) + error "Cannot install #{installer.spec.full_name} " \ + "for #{$INSTALLING}; " \ + "you required #{dep}" + ::Thor::Tasks::Merb::Gem.rollback_trans + exit! + end + end + success "Installing #{installer.spec.full_name}" +end) + +class ::Gem::Uninstaller + def self._with_silent_ui + + ui = Gem::DefaultUserInteraction.ui + def ui.say(str) + puts "- #{str}" + end + + yield + + class << Gem::DefaultUserInteraction.ui + remove_method :say + end + end + + def self._uninstall(source_index, name, op, version) + unless source_index.find_name(name, "#{op} #{version}").empty? + uninstaller = Gem::Uninstaller.new( + name, + :version => "#{op} #{version}", + :install_dir => Dir.pwd / "gems", + :all => true, + :ignore => true + ) + _with_silent_ui { uninstaller.uninstall } + end + end + + def self._uninstall_others(source_index, name, version) + _uninstall(source_index, name, "<", version) + _uninstall(source_index, name, ">", version) + end +end + +Gem.post_install_hooks.push(proc do |installer| + source_index = installer.instance_variable_get("@source_index") + ::Gem::Uninstaller._uninstall_others( + source_index, installer.spec.name, installer.spec.version + ) +end) + +class ::Gem::DependencyInstaller + alias old_fg find_gems_with_sources + + def find_gems_with_sources(dep) + if @source_index.any? { |_, installed_spec| + installed_spec.satisfies_requirement?(dep) + } + return [] + end + + old_fg(dep) + end +end + +class ::Gem::SpecFetcher + alias old_fetch fetch + def fetch(dependency, all = false, matching_platform = true) + idx = Gem::SourceIndex.from_installed_gems + + reqs = dependency.version_requirements.requirements + + if reqs.size == 1 && reqs[0][0] == "=" + dep = idx.search(dependency).sort.last + end + + if dep + file = dep.loaded_from.dup + file.gsub!(/specifications/, "cache") + file.gsub!(/gemspec$/, "gem") + spec = ::Gem::Format.from_file_by_path(file).spec + [[spec, file]] + else + old_fetch(dependency, all, matching_platform) + end + end +end + +class ::Gem::Installer + def app_script_text(bin_file_name) + template = File.read(File.dirname(__FILE__) / "app_script.rb") + erb = ERB.new(template) + erb.result(binding) + end +end + +class ::Gem::Specification + def recursive_dependencies(from, index = Gem.source_index) + specs = self.runtime_dependencies.map do |dep| + spec = index.search(dep).last + unless spec + from_name = from.is_a?(::Gem::Specification) ? from.full_name : from.to_s + wider_net = index.find_name(dep.name).last + ThorUI.error "Needed #{dep} for #{from_name}, but could not find it" + ThorUI.error "Found #{wider_net.full_name}" if wider_net + ::Thor::Tasks::Merb::Gem.rollback_trans + end + spec + end + specs + specs.map {|s| s.recursive_dependencies(self, index)}.flatten.uniq + end +end \ No newline at end of file diff --git a/web/tasks/merb.thor/main.thor b/web/tasks/merb.thor/main.thor new file mode 100644 index 000000000..a4c5e1033 --- /dev/null +++ b/web/tasks/merb.thor/main.thor @@ -0,0 +1,150 @@ +require "rubygems" +require "rubygems/source_index" +require "rubygems/dependency_installer" +require "rubygems/uninstaller" +require "fileutils" +require File.join(File.dirname(__FILE__), "utils") +require File.join(File.dirname(__FILE__), "gem_ext") +require File.join(File.dirname(__FILE__), "ops") + +$INSTALLING = [] + +module Merb + + class Gem < Thor + extend ColorfulMessages + + def initialize + dirs = [Dir.pwd, File.dirname(__FILE__) / ".."] + root = dirs.find {|d| File.file?(d / "config" / "dependencies.rb")} + + if root + @depsrb = root / "config" / "dependencies.rb" + else + self.class.error "dependencies.rb was not found" + exit! + end + + FileUtils.mkdir_p(Dir.pwd / "gems") + + @list = Collector.collect(File.read(@depsrb)) + @idx = ::Gem::SourceIndex.new.load_gems_in("gems/specifications") + end + + def list + require "pp" + pp @list + end + + desc "redeploy", "Syncs up gems/cache with gems/gems. All gems in the cache " \ + "that are not already installed will be installed from the " \ + "cache. All installed gems that are not in the cache will " \ + "be uninstalled." + def redeploy + gem_dir = Dir.pwd / "gems" / "gems" + cache_dir = Dir.pwd / "gems" / "cache" + + gems = Dir[gem_dir / "*"].map! {|n| File.basename(n)} + cache = Dir[cache_dir / "*.gem"].map! {|n| File.basename(n, ".gem")} + new_gems = cache - gems + outdated = gems - cache + idx = ::Gem::SourceIndex.new + idx.load_gems_in(Dir.pwd / "gems" / "specifications") + + new_gems.each do |g| + installer = ::Gem::Installer.new(cache_dir / "#{g}.gem", + :bin_dir => Dir.pwd / "bin", + :install_dir => Dir.pwd / "gems", + :ignore_dependencies => true, + :user_install => false, + :wrappers => true, + :source_index => idx) + + installer.install + end + + outdated.each do |g| + /(.*)\-(.*)/ =~ g + name, version = $1, $2 + uninstaller = ::Gem::Uninstaller.new(name, + :version => version, + :bin_dir => Dir.pwd / "bin", + :install_dir => Dir.pwd / "gems", + :ignore => true, + :executables => true + ) + uninstaller.uninstall + end + end + + desc "confirm", "Confirm the current setup. merb:gem:install will " \ + "automatically run this task before committing the " \ + "changes it makes." + def confirm(gems = @list) + ::Gem.path.replace([Dir.pwd / "gems"]) + ::Gem.source_index.load_gems_in(Dir.pwd / "gems" / "specifications") + + self.class.info "Confirming configuration..." + + ::Gem.loaded_specs.clear + + begin + gems.each do |name, versions| + versions ||= [] + ::Gem.activate name, *versions + end + rescue ::Gem::LoadError => e + self.class.error "Configuration could not be confirmed: #{e.message}" + self.class.rollback_trans + end + self.class.info "Confirmed" + end + + desc 'install', 'Sync up your bundled gems with the list in config/dependencies.rb' + def install(*gems) + if gems.empty? + gems = @list + else + gems = gems.map {|desc| name, *versions = desc.split(" ") } + end + + $GEMS = gems + + self.class.begin_trans + + gems.each do |name, versions| + dep = ::Gem::Dependency.new(name, versions || []) + unless @idx.search(dep).empty? + next + end + + rescue_failures do + $INSTALLING = dep + _install(dep) + end + end + + gem_dir = Dir.pwd / "gems" / "gems" + installed_gems = Dir[gem_dir / "*"].map! {|n| File.basename(n)} + + list = full_list.map {|x| x.full_name}.compact + + (installed_gems - list).each do |g| + /^(.*)\-(.*)$/ =~ g + name, version = $1, $2 + uninstaller = ::Gem::Uninstaller.new(name, + :version => version, + :bin_dir => (Dir.pwd / "bin").to_s, + :install_dir => (Dir.pwd / "gems").to_s, + :ignore => true, + :executables => true + ) + uninstaller.uninstall + end + + confirm(gems) + + self.class.commit_trans + end + end +end \ No newline at end of file diff --git a/web/tasks/merb.thor/ops.rb b/web/tasks/merb.thor/ops.rb new file mode 100644 index 000000000..c758af23a --- /dev/null +++ b/web/tasks/merb.thor/ops.rb @@ -0,0 +1,93 @@ +module Thor::Tasks + module Merb + class Collector + attr_reader :dependencies + + def self.collect(str) + collector = new + collector.instance_eval(str) + collector.dependencies + end + + def initialize + @dependencies = [] + end + + def dependency(name, *versions) + versions.pop if versions.last.is_a?(Hash) + @dependencies << [name, versions] + end + end + + class Gem < Thor + def full_list + @idx.load_gems_in("gems/specifications") + + @list.map do |name, versions| + dep = ::Gem::Dependency.new(name, versions) + spec = @idx.search(dep).last + unless spec + self.class.error "A required dependency #{dep} was not found" + self.class.rollback_trans + end + deps = spec.recursive_dependencies(dep, @idx) + [spec] + deps + end.flatten.uniq + end + + def rescue_failures(error = StandardError, prc = nil) + begin + yield + rescue error => e + if prc + prc.call(e) + else + puts e.message + puts e.backtrace + end + self.class.rollback_trans + end + end + + def self.begin_trans + note "Beginning transaction" + FileUtils.cp_r(Dir.pwd / "gems", Dir.pwd / ".original_gems") + end + + def self.commit_trans + note "Committing transaction" + FileUtils.rm_rf(Dir.pwd / ".original_gems") + end + + def self.rollback_trans + if File.exist?(Dir.pwd / ".original_gems") + note "Rolling back transaction" + FileUtils.rm_rf(Dir.pwd / "gems") + FileUtils.mv(Dir.pwd / ".original_gems", Dir.pwd / "gems") + end + exit! + end + + private + def _install(dep) + @idx.load_gems_in("gems/specifications") + return if @idx.search(dep).last + + installer = ::Gem::DependencyInstaller.new( + :bin_dir => Dir.pwd / "bin", + :install_dir => Dir.pwd / "gems", + :user_install => false) + + begin + installer.install dep.name, dep.version_requirements + rescue ::Gem::GemNotFoundException => e + puts "Cannot find #{dep}" + rescue ::Gem::RemoteFetcher::FetchError => e + puts e.message + puts "Retrying..." + retry + end + end + end + end +end \ No newline at end of file diff --git a/web/tasks/merb.thor/utils.rb b/web/tasks/merb.thor/utils.rb new file mode 100644 index 000000000..1b1caa697 --- /dev/null +++ b/web/tasks/merb.thor/utils.rb @@ -0,0 +1,40 @@ +class String + def /(other) + (Pathname.new(self) + other).to_s + end +end + +module ColorfulMessages + + # red + def error(*messages) + puts messages.map { |msg| "\033[1;31m#{msg}\033[0m" } + end + + # yellow + def warning(*messages) + puts messages.map { |msg| "\033[1;33m#{msg}\033[0m" } + end + + # green + def success(*messages) + puts messages.map { |msg| "\033[1;32m#{msg}\033[0m" } + end + + alias_method :message, :success + + # magenta + def note(*messages) + puts messages.map { |msg| "\033[1;35m#{msg}\033[0m" } + end + + # blue + def info(*messages) + puts messages.map { |msg| "\033[1;34m#{msg}\033[0m" } + end + +end + +module ThorUI + extend ColorfulMessages +end