From fdba99cb93237f1a324b41155f2202c0a2794171 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 1 Aug 2005 03:20:36 +0000 Subject: [PATCH 01/84] Branching out instiki-ar (for building an ActiveRecord backend) From 21adee88d42e299c524672fb128911e557c19a49 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 2 Aug 2005 07:58:22 +0000 Subject: [PATCH 02/84] Initial database definitions --- config/database.yml | 30 ++++++++++++++++++++++++++++++ db/pages.erbsql | 6 ++++++ db/revisions.erbsql | 7 +++++++ db/webs.erbsql | 7 +++++++ lib/db_structure.rb | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+) create mode 100644 config/database.yml create mode 100644 db/pages.erbsql create mode 100644 db/revisions.erbsql create mode 100644 db/webs.erbsql create mode 100644 lib/db_structure.rb diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 00000000..c21e3f65 --- /dev/null +++ b/config/database.yml @@ -0,0 +1,30 @@ +# SQLite is enabled by default. Remember to change the dbfile path. +production: + adapter: sqlite + dbfile: /tmp/instiki_prod.db + +# Uncomment this section for MySQL: +# production: +# adapter: mysql +# host: localhost +# database: instiki +# username: instiki +# password: pass + +# Uncomment this section for PostgreSQL: +# production: +# adapter: postgresql +# host: localhost +# database: instiki +# username: instiki +# password: pass + +# The following settings are only used for testing and development. +development: + adapter: sqlite + dbfile: /tmp/instiki_dev.db + +test: + adapter: sqlite + dbfile: /tmp/instiki_test.db + diff --git a/db/pages.erbsql b/db/pages.erbsql new file mode 100644 index 00000000..afc57484 --- /dev/null +++ b/db/pages.erbsql @@ -0,0 +1,6 @@ +CREATE TABLE pages ( + id <%= @pk %>, + created_at <%= @datetime %> NOT NULL, + updated_at <%= @datetime %> NOT NULL, + web_id INTEGER NOT NULL +) <%= create_options %>; diff --git a/db/revisions.erbsql b/db/revisions.erbsql new file mode 100644 index 00000000..1639cf2e --- /dev/null +++ b/db/revisions.erbsql @@ -0,0 +1,7 @@ +CREATE TABLE revisions ( + id <%= @pk %>, + created_at <%= @datetime %> NOT NULL, + updated_at <%= @datetime %> NOT NULL, + page_id INTEGER NOT NULL, + content TEXT NOT NULL +) <%= create_options %>; diff --git a/db/webs.erbsql b/db/webs.erbsql new file mode 100644 index 00000000..24ebc04a --- /dev/null +++ b/db/webs.erbsql @@ -0,0 +1,7 @@ +CREATE TABLE pages ( + id <%= @pk %>, + created_at <%= @datetime %> NOT NULL, + updated_at <%= @datetime %> NOT NULL, + name VARCHAR(60) NOT NULL, + address VARCHAR(60) NOT NULL +) <%= create_options %>; diff --git a/lib/db_structure.rb b/lib/db_structure.rb new file mode 100644 index 00000000..0a2c4beb --- /dev/null +++ b/lib/db_structure.rb @@ -0,0 +1,32 @@ +require 'erb' + +def create_options + if @db == 'mysql' + 'ENGINE = ' + (mysql_engine rescue @mysql_engine) + end +end + +def db_structure(db) + db.downcase! + @db = db + case db + when 'postgresql' + @pk = 'SERIAL PRIMARY KEY' + @datetime = 'TIMESTAMP' + when 'sqlite', 'sqlite3' + @pk = 'INTEGER PRIMARY KEY' + @datetime = 'DATETIME' + when 'mysql' + @pk = 'INTEGER UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY' + @datetime = 'DATETIME' + @mysql_engine = 'InnoDB' + else + raise "Unknown db type #{db}" + end + + s = '' + Dir['db/*.erbsql'].each do |filename| + s += ERB.new(File.read(filename)).result + end + s +end From 6d8190c096f7884fa8c998979218b9915c68cbaa Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 2 Aug 2005 07:59:22 +0000 Subject: [PATCH 03/84] Moved old tests and app/model out of the way --- app/{models => models-old}/author.rb | 0 app/{models => models-old}/chunks/category.rb | 0 app/{models => models-old}/chunks/chunk.rb | 0 app/{models => models-old}/chunks/engines.rb | 0 app/{models => models-old}/chunks/include.rb | 0 app/{models => models-old}/chunks/literal.rb | 0 app/{models => models-old}/chunks/nowiki.rb | 0 app/{models => models-old}/chunks/test.rb | 0 app/{models => models-old}/chunks/uri.rb | 0 app/{models => models-old}/chunks/wiki.rb | 0 app/{models => models-old}/file_yard.rb | 0 app/{models => models-old}/page.rb | 0 app/{models => models-old}/page_lock.rb | 0 app/{models => models-old}/page_set.rb | 0 app/{models => models-old}/revision.rb | 0 app/{models => models-old}/web.rb | 0 app/{models => models-old}/wiki_content.rb | 0 app/{models => models-old}/wiki_service.rb | 0 app/{models => models-old}/wiki_words.rb | 0 {test => test-old}/all_tests.rb | 0 {test => test-old}/fixtures/rails.gif | Bin .../functional/admin_controller_test.rb | 0 {test => test-old}/functional/application_test.rb | 0 .../functional/file_controller_test.rb | 0 {test => test-old}/functional/routes_test.rb | 0 .../functional/wiki_controller_test.rb | 0 {test => test-old}/test_helper.rb | 0 {test => test-old}/unit/chunks/category_test.rb | 0 {test => test-old}/unit/chunks/nowiki_test.rb | 0 {test => test-old}/unit/chunks/wiki_test.rb | 0 {test => test-old}/unit/diff_test.rb | 0 {test => test-old}/unit/file_yard_test.rb | 0 {test => test-old}/unit/page_test.rb | 0 {test => test-old}/unit/redcloth_for_tex_test.rb | 0 {test => test-old}/unit/revision_test.rb | 0 {test => test-old}/unit/uri_test.rb | 0 {test => test-old}/unit/web_test.rb | 0 {test => test-old}/unit/wiki_service_test.rb | 0 {test => test-old}/unit/wiki_words_test.rb | 0 {test => test-old}/watir/e2e.rb | 0 40 files changed, 0 insertions(+), 0 deletions(-) rename app/{models => models-old}/author.rb (100%) rename app/{models => models-old}/chunks/category.rb (100%) rename app/{models => models-old}/chunks/chunk.rb (100%) rename app/{models => models-old}/chunks/engines.rb (100%) rename app/{models => models-old}/chunks/include.rb (100%) rename app/{models => models-old}/chunks/literal.rb (100%) rename app/{models => models-old}/chunks/nowiki.rb (100%) rename app/{models => models-old}/chunks/test.rb (100%) rename app/{models => models-old}/chunks/uri.rb (100%) rename app/{models => models-old}/chunks/wiki.rb (100%) rename app/{models => models-old}/file_yard.rb (100%) rename app/{models => models-old}/page.rb (100%) rename app/{models => models-old}/page_lock.rb (100%) rename app/{models => models-old}/page_set.rb (100%) rename app/{models => models-old}/revision.rb (100%) rename app/{models => models-old}/web.rb (100%) rename app/{models => models-old}/wiki_content.rb (100%) rename app/{models => models-old}/wiki_service.rb (100%) rename app/{models => models-old}/wiki_words.rb (100%) rename {test => test-old}/all_tests.rb (100%) rename {test => test-old}/fixtures/rails.gif (100%) rename {test => test-old}/functional/admin_controller_test.rb (100%) rename {test => test-old}/functional/application_test.rb (100%) rename {test => test-old}/functional/file_controller_test.rb (100%) rename {test => test-old}/functional/routes_test.rb (100%) rename {test => test-old}/functional/wiki_controller_test.rb (100%) rename {test => test-old}/test_helper.rb (100%) rename {test => test-old}/unit/chunks/category_test.rb (100%) rename {test => test-old}/unit/chunks/nowiki_test.rb (100%) rename {test => test-old}/unit/chunks/wiki_test.rb (100%) rename {test => test-old}/unit/diff_test.rb (100%) rename {test => test-old}/unit/file_yard_test.rb (100%) rename {test => test-old}/unit/page_test.rb (100%) rename {test => test-old}/unit/redcloth_for_tex_test.rb (100%) rename {test => test-old}/unit/revision_test.rb (100%) rename {test => test-old}/unit/uri_test.rb (100%) rename {test => test-old}/unit/web_test.rb (100%) rename {test => test-old}/unit/wiki_service_test.rb (100%) rename {test => test-old}/unit/wiki_words_test.rb (100%) rename {test => test-old}/watir/e2e.rb (100%) diff --git a/app/models/author.rb b/app/models-old/author.rb similarity index 100% rename from app/models/author.rb rename to app/models-old/author.rb diff --git a/app/models/chunks/category.rb b/app/models-old/chunks/category.rb similarity index 100% rename from app/models/chunks/category.rb rename to app/models-old/chunks/category.rb diff --git a/app/models/chunks/chunk.rb b/app/models-old/chunks/chunk.rb similarity index 100% rename from app/models/chunks/chunk.rb rename to app/models-old/chunks/chunk.rb diff --git a/app/models/chunks/engines.rb b/app/models-old/chunks/engines.rb similarity index 100% rename from app/models/chunks/engines.rb rename to app/models-old/chunks/engines.rb diff --git a/app/models/chunks/include.rb b/app/models-old/chunks/include.rb similarity index 100% rename from app/models/chunks/include.rb rename to app/models-old/chunks/include.rb diff --git a/app/models/chunks/literal.rb b/app/models-old/chunks/literal.rb similarity index 100% rename from app/models/chunks/literal.rb rename to app/models-old/chunks/literal.rb diff --git a/app/models/chunks/nowiki.rb b/app/models-old/chunks/nowiki.rb similarity index 100% rename from app/models/chunks/nowiki.rb rename to app/models-old/chunks/nowiki.rb diff --git a/app/models/chunks/test.rb b/app/models-old/chunks/test.rb similarity index 100% rename from app/models/chunks/test.rb rename to app/models-old/chunks/test.rb diff --git a/app/models/chunks/uri.rb b/app/models-old/chunks/uri.rb similarity index 100% rename from app/models/chunks/uri.rb rename to app/models-old/chunks/uri.rb diff --git a/app/models/chunks/wiki.rb b/app/models-old/chunks/wiki.rb similarity index 100% rename from app/models/chunks/wiki.rb rename to app/models-old/chunks/wiki.rb diff --git a/app/models/file_yard.rb b/app/models-old/file_yard.rb similarity index 100% rename from app/models/file_yard.rb rename to app/models-old/file_yard.rb diff --git a/app/models/page.rb b/app/models-old/page.rb similarity index 100% rename from app/models/page.rb rename to app/models-old/page.rb diff --git a/app/models/page_lock.rb b/app/models-old/page_lock.rb similarity index 100% rename from app/models/page_lock.rb rename to app/models-old/page_lock.rb diff --git a/app/models/page_set.rb b/app/models-old/page_set.rb similarity index 100% rename from app/models/page_set.rb rename to app/models-old/page_set.rb diff --git a/app/models/revision.rb b/app/models-old/revision.rb similarity index 100% rename from app/models/revision.rb rename to app/models-old/revision.rb diff --git a/app/models/web.rb b/app/models-old/web.rb similarity index 100% rename from app/models/web.rb rename to app/models-old/web.rb diff --git a/app/models/wiki_content.rb b/app/models-old/wiki_content.rb similarity index 100% rename from app/models/wiki_content.rb rename to app/models-old/wiki_content.rb diff --git a/app/models/wiki_service.rb b/app/models-old/wiki_service.rb similarity index 100% rename from app/models/wiki_service.rb rename to app/models-old/wiki_service.rb diff --git a/app/models/wiki_words.rb b/app/models-old/wiki_words.rb similarity index 100% rename from app/models/wiki_words.rb rename to app/models-old/wiki_words.rb diff --git a/test/all_tests.rb b/test-old/all_tests.rb similarity index 100% rename from test/all_tests.rb rename to test-old/all_tests.rb diff --git a/test/fixtures/rails.gif b/test-old/fixtures/rails.gif similarity index 100% rename from test/fixtures/rails.gif rename to test-old/fixtures/rails.gif diff --git a/test/functional/admin_controller_test.rb b/test-old/functional/admin_controller_test.rb similarity index 100% rename from test/functional/admin_controller_test.rb rename to test-old/functional/admin_controller_test.rb diff --git a/test/functional/application_test.rb b/test-old/functional/application_test.rb similarity index 100% rename from test/functional/application_test.rb rename to test-old/functional/application_test.rb diff --git a/test/functional/file_controller_test.rb b/test-old/functional/file_controller_test.rb similarity index 100% rename from test/functional/file_controller_test.rb rename to test-old/functional/file_controller_test.rb diff --git a/test/functional/routes_test.rb b/test-old/functional/routes_test.rb similarity index 100% rename from test/functional/routes_test.rb rename to test-old/functional/routes_test.rb diff --git a/test/functional/wiki_controller_test.rb b/test-old/functional/wiki_controller_test.rb similarity index 100% rename from test/functional/wiki_controller_test.rb rename to test-old/functional/wiki_controller_test.rb diff --git a/test/test_helper.rb b/test-old/test_helper.rb similarity index 100% rename from test/test_helper.rb rename to test-old/test_helper.rb diff --git a/test/unit/chunks/category_test.rb b/test-old/unit/chunks/category_test.rb similarity index 100% rename from test/unit/chunks/category_test.rb rename to test-old/unit/chunks/category_test.rb diff --git a/test/unit/chunks/nowiki_test.rb b/test-old/unit/chunks/nowiki_test.rb similarity index 100% rename from test/unit/chunks/nowiki_test.rb rename to test-old/unit/chunks/nowiki_test.rb diff --git a/test/unit/chunks/wiki_test.rb b/test-old/unit/chunks/wiki_test.rb similarity index 100% rename from test/unit/chunks/wiki_test.rb rename to test-old/unit/chunks/wiki_test.rb diff --git a/test/unit/diff_test.rb b/test-old/unit/diff_test.rb similarity index 100% rename from test/unit/diff_test.rb rename to test-old/unit/diff_test.rb diff --git a/test/unit/file_yard_test.rb b/test-old/unit/file_yard_test.rb similarity index 100% rename from test/unit/file_yard_test.rb rename to test-old/unit/file_yard_test.rb diff --git a/test/unit/page_test.rb b/test-old/unit/page_test.rb similarity index 100% rename from test/unit/page_test.rb rename to test-old/unit/page_test.rb diff --git a/test/unit/redcloth_for_tex_test.rb b/test-old/unit/redcloth_for_tex_test.rb similarity index 100% rename from test/unit/redcloth_for_tex_test.rb rename to test-old/unit/redcloth_for_tex_test.rb diff --git a/test/unit/revision_test.rb b/test-old/unit/revision_test.rb similarity index 100% rename from test/unit/revision_test.rb rename to test-old/unit/revision_test.rb diff --git a/test/unit/uri_test.rb b/test-old/unit/uri_test.rb similarity index 100% rename from test/unit/uri_test.rb rename to test-old/unit/uri_test.rb diff --git a/test/unit/web_test.rb b/test-old/unit/web_test.rb similarity index 100% rename from test/unit/web_test.rb rename to test-old/unit/web_test.rb diff --git a/test/unit/wiki_service_test.rb b/test-old/unit/wiki_service_test.rb similarity index 100% rename from test/unit/wiki_service_test.rb rename to test-old/unit/wiki_service_test.rb diff --git a/test/unit/wiki_words_test.rb b/test-old/unit/wiki_words_test.rb similarity index 100% rename from test/unit/wiki_words_test.rb rename to test-old/unit/wiki_words_test.rb diff --git a/test/watir/e2e.rb b/test-old/watir/e2e.rb similarity index 100% rename from test/watir/e2e.rb rename to test-old/watir/e2e.rb From 1b8baa3d03c1ca13bdefe1f24794998c169704d7 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 2 Aug 2005 08:56:09 +0000 Subject: [PATCH 04/84] Some infrastructure for AR and accompanying unit tests --- app/model/page.rb | 4 ++ app/model/revision.rb | 3 + app/model/web.rb | 3 + config/database.yml | 6 +- config/environment.rb | 115 ++++++++++++++++++++---------------- config/environments/test.rb | 2 - db/webs.erbsql | 2 +- lib/active_record_stub.rb | 31 ---------- lib/db_structure.rb | 2 +- script/create_db | 24 ++++++++ script/debug_storage | 97 ------------------------------ test/fixtures/pages.yml | 2 + test/fixtures/revisions.yml | 2 + test/fixtures/webs.yml | 2 + test/test_helper.rb | 26 ++++++++ test/unit/page_test.rb | 7 +++ test/unit/revision_test.rb | 7 +++ test/unit/web_test.rb | 7 +++ 18 files changed, 155 insertions(+), 187 deletions(-) create mode 100644 app/model/page.rb create mode 100644 app/model/revision.rb create mode 100644 app/model/web.rb delete mode 100644 lib/active_record_stub.rb create mode 100644 script/create_db delete mode 100644 script/debug_storage create mode 100644 test/fixtures/pages.yml create mode 100644 test/fixtures/revisions.yml create mode 100644 test/fixtures/webs.yml create mode 100644 test/test_helper.rb create mode 100644 test/unit/page_test.rb create mode 100644 test/unit/revision_test.rb create mode 100644 test/unit/web_test.rb diff --git a/app/model/page.rb b/app/model/page.rb new file mode 100644 index 00000000..5f85c8ab --- /dev/null +++ b/app/model/page.rb @@ -0,0 +1,4 @@ +class Page < ActiveRecord::Base + belongs_to :web + has_many :pages +end \ No newline at end of file diff --git a/app/model/revision.rb b/app/model/revision.rb new file mode 100644 index 00000000..017b3543 --- /dev/null +++ b/app/model/revision.rb @@ -0,0 +1,3 @@ +class Revision < ActiveRecord::Base + belongs_to :page +end \ No newline at end of file diff --git a/app/model/web.rb b/app/model/web.rb new file mode 100644 index 00000000..2c31bf8f --- /dev/null +++ b/app/model/web.rb @@ -0,0 +1,3 @@ +class Web < ActiveRecord::Base + has_many :pages +end \ No newline at end of file diff --git a/config/database.yml b/config/database.yml index c21e3f65..1233b7e3 100644 --- a/config/database.yml +++ b/config/database.yml @@ -1,6 +1,6 @@ # SQLite is enabled by default. Remember to change the dbfile path. production: - adapter: sqlite + adapter: sqlite3 dbfile: /tmp/instiki_prod.db # Uncomment this section for MySQL: @@ -21,10 +21,10 @@ production: # The following settings are only used for testing and development. development: - adapter: sqlite + adapter: sqlite3 dbfile: /tmp/instiki_dev.db test: - adapter: sqlite + adapter: sqlite3 dbfile: /tmp/instiki_test.db diff --git a/config/environment.rb b/config/environment.rb index bc3ac725..589025c8 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -1,5 +1,10 @@ -if RUBY_VERSION < '1.8.1' - puts 'Instiki requires Ruby 1.8.1+' +# Load the Rails framework and configure your application. +# You can include your own configuration at the end of this file. +# +# Be sure to restart your webserver when you modify this file. + +if RUBY_VERSION < '1.8.2' + puts 'Instiki requires Ruby 1.8.2+' exit end @@ -7,76 +12,82 @@ end $KCODE = 'u' require 'jcode' -RAILS_ROOT = File.expand_path(File.dirname(__FILE__) + '/../') unless defined? RAILS_ROOT -RAILS_ENV = ENV['RAILS_ENV'] || 'production' unless defined? RAILS_ENV +# The path to the root directory of your application. +RAILS_ROOT = File.expand_path(File.dirname(__FILE__) + '/../') -unless defined? ADDITIONAL_LOAD_PATHS - # Mocks first. - ADDITIONAL_LOAD_PATHS = ["#{RAILS_ROOT}/test/mocks/#{RAILS_ENV}"] +# The environment your application is currently running. Don't set +# this here; put it in your webserver's configuration as the RAILS_ENV +# environment variable instead. +# +# See config/environments/*.rb for environment-specific configuration. +RAILS_ENV = ENV['RAILS_ENV'] || 'development' - # Then model subdirectories. - ADDITIONAL_LOAD_PATHS.concat(Dir["#{RAILS_ROOT}/app/models/[_a-z]*"]) - ADDITIONAL_LOAD_PATHS.concat(Dir["#{RAILS_ROOT}/components/[_a-z]*"]) +# Load the Rails framework. Mock classes for testing come first. +ADDITIONAL_LOAD_PATHS = ["#{RAILS_ROOT}/test/mocks/#{RAILS_ENV}"] - # Followed by the standard includes. - ADDITIONAL_LOAD_PATHS.concat %w( - app - app/models - app/controllers - app/helpers - app/apis - components - config - lib - vendor - vendor/rails/railties - vendor/rails/railties/lib - vendor/rails/actionpack/lib - vendor/rails/activesupport/lib - vendor/rails/activerecord/lib - vendor/rails/actionmailer/lib - vendor/rails/actionwebservice/lib - vendor/madeleine-0.7.1/lib - vendor/RedCloth-3.0.3/lib - vendor/rubyzip-0.5.8/lib - ).map { |dir| "#{File.expand_path(File.join(RAILS_ROOT, dir))}" - }.delete_if { |dir| not File.exist?(dir) } +# Then model subdirectories. +ADDITIONAL_LOAD_PATHS.concat(Dir["#{RAILS_ROOT}/app/models/[_a-z]*"]) +ADDITIONAL_LOAD_PATHS.concat(Dir["#{RAILS_ROOT}/components/[_a-z]*"]) - # Prepend to $LOAD_PATH - ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } -end +# Followed by the standard includes. +ADDITIONAL_LOAD_PATHS.concat %w( + app + app/models + app/controllers + app/helpers + app/apis + components + config + lib + vendor + vendor/rails/railties + vendor/rails/railties/lib + vendor/rails/actionpack/lib + vendor/rails/activesupport/lib + vendor/rails/activerecord/lib + vendor/rails/actionmailer/lib + vendor/rails/actionwebservice/lib + vendor/RedCloth-3.0.3/lib + vendor/rubyzip-0.5.8/lib +).map { |dir| "#{RAILS_ROOT}/#{dir}" }.select { |dir| File.directory?(dir) } + +# Prepend to $LOAD_PATH +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } # Require Rails libraries. require 'rubygems' unless File.directory?("#{RAILS_ROOT}/vendor/rails") require 'active_support' +require 'active_record' require 'action_controller' -require_dependency 'instiki_errors' -require_dependency 'active_record_stub' - -# Environment specific configuration +# Environment-specific configuration. require_dependency "environments/#{RAILS_ENV}" +ActiveRecord::Base.configurations = File.open("#{RAILS_ROOT}/config/database.yml") { |f| YAML::load(f) } +ActiveRecord::Base.establish_connection # Configure defaults if the included environment did not. -unless defined? RAILS_DEFAULT_LOGGER +begin + RAILS_DEFAULT_LOGGER = Logger.new("#{RAILS_ROOT}/log/#{RAILS_ENV}.log") + RAILS_DEFAULT_LOGGER.level = (RAILS_ENV == 'production' ? Logger::INFO : Logger::DEBUG) +rescue StandardError RAILS_DEFAULT_LOGGER = Logger.new(STDERR) - ActionController::Base.logger ||= RAILS_DEFAULT_LOGGER - if $instiki_debug_logging - RAILS_DEFAULT_LOGGER.level = Logger::DEBUG - ActionController::Base.logger.level = Logger::DEBUG - else - RAILS_DEFAULT_LOGGER.level = Logger::INFO - ActionController::Base.logger.level = Logger::INFO - end + RAILS_DEFAULT_LOGGER.level = Logger::WARN + RAILS_DEFAULT_LOGGER.warn( + "Rails Error: Unable to access log file. Please ensure that log/#{RAILS_ENV}.log exists and is chmod 0666. " + + "The log level has been raised to WARN and the output directed to STDERR until the problem is fixed." + ) end -ActionController::Base.template_root ||= "#{RAILS_ROOT}/app/views/" +[ActiveRecord, ActionController, ActionMailer].each { |mod| mod::Base.logger ||= RAILS_DEFAULT_LOGGER } +[ActionController, ActionMailer].each { |mod| mod::Base.template_root ||= "#{RAILS_ROOT}/app/views/" } + +# Set up routes. ActionController::Routing::Routes.reload + Controllers = Dependencies::LoadingModule.root( File.join(RAILS_ROOT, 'app', 'controllers'), File.join(RAILS_ROOT, 'components') ) -require 'wiki_service' -Socket.do_not_reverse_lookup = true +require_dependency 'instiki_errors' diff --git a/config/environments/test.rb b/config/environments/test.rb index ece6e9cf..fac509a1 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -12,6 +12,4 @@ unless defined? TEST_LOGGER TEST_LOGGER = ActionController::Base.logger = Logger.new(log_name) $instiki_debug_logging = true - - WikiService.storage_path = RAILS_ROOT + '/storage/test/' end diff --git a/db/webs.erbsql b/db/webs.erbsql index 24ebc04a..bbb9758c 100644 --- a/db/webs.erbsql +++ b/db/webs.erbsql @@ -1,4 +1,4 @@ -CREATE TABLE pages ( +CREATE TABLE webs ( id <%= @pk %>, created_at <%= @datetime %> NOT NULL, updated_at <%= @datetime %> NOT NULL, diff --git a/lib/active_record_stub.rb b/lib/active_record_stub.rb deleted file mode 100644 index 126beb0b..00000000 --- a/lib/active_record_stub.rb +++ /dev/null @@ -1,31 +0,0 @@ -# This project uses Railties, which has an external dependency on ActiveRecord -# Since ActiveRecord may not be present in Instiki runtime environment, this -# file provides a stub replacement for it - -module ActiveRecord - class Base - - # dependency in railties/lib/dispatcher.rb - def self.reset_column_information_and_inheritable_attributes_for_all_subclasses - # noop - end - - # dependency in actionpack/lib/action_controller/benchmarking.rb - def self.connected? - false - end - - # dependency in actionpack/lib/action_controller/benchmarking.rb - def self.connection - return ConnectionStub - end - - end - - module ConnectionStub - def self.reset_runtime - 0 - end - end - -end diff --git a/lib/db_structure.rb b/lib/db_structure.rb index 0a2c4beb..c7b58c33 100644 --- a/lib/db_structure.rb +++ b/lib/db_structure.rb @@ -25,7 +25,7 @@ def db_structure(db) end s = '' - Dir['db/*.erbsql'].each do |filename| + Dir[RAILS_ROOT + '/db/*.erbsql'].each do |filename| s += ERB.new(File.read(filename)).result end s diff --git a/script/create_db b/script/create_db new file mode 100644 index 00000000..b421fb8c --- /dev/null +++ b/script/create_db @@ -0,0 +1,24 @@ +#!/usr/bin/env ruby + +APP_ROOT = File.expand_path(File.dirname(__FILE__)) + '/../' + +require APP_ROOT + 'config/environment' +require 'db_structure' + +config = ActiveRecord::Base.configurations + +['production', 'test', 'development'].each do |target| + begin + ENV['RAILS_ENV'] = target + load APP_ROOT + 'config/environment.rb' + puts "Creating tables for #{target}..." + + db_structure(config[target]['adapter']).split(/\s*;\s*/).each do |sql| + ActiveRecord::Base.connection.execute(sql) + end + + puts "done." + rescue => e + puts "failed: " + e.inspect + end +end diff --git a/script/debug_storage b/script/debug_storage deleted file mode 100644 index 8b0060f0..00000000 --- a/script/debug_storage +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/ruby - -=begin -The purpose of this script is to help people poke around in the Madeleine storage. - -Two caveats: -1. You MUST be a reasonably good Ruby programmer to use it successfully for anything non-trivial. -2. It's very easy to screw up something by poking in the storage internals. If you do, please - undo your changes by deleting the most recent snapshot(s) and don't ask for help. - -Usage example: - -E:\eclipse\workspace\instiki\script>irb -irb(main):001:0> load 'debug_storage' -Enter path to storage [E:/eclipse/workspace/instiki/storage/2500]: -Loading storage from the default storage path (E:/eclipse/workspace/instiki/storage/2500) -Instiki storage from E:/eclipse/workspace/instiki/storage/2500 is loaded. -Access it via global variable $wiki. -Happy poking! -=> true -irb(main):003:0> $wiki.system -=> {"password"=>"foo"} -irb(main):005:0> $wiki.system['password'] = 'bar' -=> "bar" -irb(main):006:0> $wiki.webs.keys -=> ["wiki1", "wiki2"] -irb(main):007:0> $wiki.webs['wiki1'].password = 'the_password' -=> "the_password" -irb(main):008:0> WikiService::snapshot -=> [] - - -Things that are possible: - -# cleaning old revisions -$wiki.webs['wiki'].pages['HomePage'].revisions = $wiki.webs['wiki'].pages['HomePage'].revisions[-1..-1] - -# Changing contents of a revision -$wiki.webs['wiki'].pages['HomePage'].revisions[-1] = 'new content' - -# Checking that all pages can be rendered by the markup engine -$wiki.webs['wiki'].pages.each_pair do |name, page| - page.revisions.each_with_index do |revision, i| - begin - revision.display_content - rescue => - puts "Error when rendering revision ##{i} of page #{name.inspect}:" - puts e.message - puts e.backtrace.join("\n") - end -end -=end - -require 'fileutils' -require 'optparse' -require 'webrick' - -default_storage_path = File.expand_path(File.dirname(__FILE__) + "/../storage/2500") - -print "Enter path to storage [#{default_storage_path}]: " -storage_path = gets.chomp -if storage_path.empty? - storage_path = default_storage_path - puts "Loading storage from the default storage path (#{storage_path})" -else - puts "Loading storage from the path you entered (#{storage_path})" -end - -unless File.directory?(storage_path) and not - (Dir["#{storage_path}/*.snapshot"] + Dir["#{storage_path}/*.command_log"]).empty? - raise "Found no storage at #{storage_path}" -end - -RAILS_ROOT = File.expand_path(File.dirname(__FILE__) + '/../') unless defined? RAILS_ROOT - -unless defined? ADDITIONAL_LOAD_PATHS - ADDITIONAL_LOAD_PATHS = %w( - app/models - lib - vendor/madeleine-0.7.1/lib - vendor/RedCloth-3.0.3/lib - vendor/rubyzip-0.5.8/lib - ).map { |dir| "#{File.expand_path(File.join(RAILS_ROOT, dir))}" - }.delete_if { |dir| not File.exist?(dir) } - - # Prepend to $LOAD_PATH - ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } -end - -require 'wiki_service' - -WikiService.storage_path = storage_path -$wiki = WikiService.instance -puts "Instiki storage from #{storage_path} is loaded." -puts 'Access it via global variable $wiki.' -puts 'Happy poking!' -nil diff --git a/test/fixtures/pages.yml b/test/fixtures/pages.yml new file mode 100644 index 00000000..9f856f2a --- /dev/null +++ b/test/fixtures/pages.yml @@ -0,0 +1,2 @@ +home_page: + id: 1 diff --git a/test/fixtures/revisions.yml b/test/fixtures/revisions.yml new file mode 100644 index 00000000..6538429d --- /dev/null +++ b/test/fixtures/revisions.yml @@ -0,0 +1,2 @@ +home_page_first_revision: + id: 1 diff --git a/test/fixtures/webs.yml b/test/fixtures/webs.yml new file mode 100644 index 00000000..8c4a2778 --- /dev/null +++ b/test/fixtures/webs.yml @@ -0,0 +1,2 @@ +test_wiki: + id: 1 diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 00000000..19219ce7 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,26 @@ +ENV["RAILS_ENV"] = "test" + +# Expand the path to environment so that Ruby does not load it multiple times +# File.expand_path can be removed if Ruby 1.9 is in use. +require File.expand_path(File.dirname(__FILE__) + "/../config/environment") +require 'application' + +require 'test/unit' +require 'active_record/fixtures' +require 'action_controller/test_process' +require 'action_web_service/test_invoke' +require 'breakpoint' + +Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/" + +class Test::Unit::TestCase + # Turn these on to use transactional fixtures with table_name(:fixture_name) instantiation of fixtures + # self.use_transactional_fixtures = true + # self.use_instantiated_fixtures = false + + def create_fixtures(*table_names) + Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures", table_names) + end + + # Add more helper methods to be used by all tests here... +end \ No newline at end of file diff --git a/test/unit/page_test.rb b/test/unit/page_test.rb new file mode 100644 index 00000000..8af94246 --- /dev/null +++ b/test/unit/page_test.rb @@ -0,0 +1,7 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class PageTest < Test::Unit::TestCase + + fixtures 'webs', 'pages', 'revisions' + +end diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb new file mode 100644 index 00000000..0024c6b3 --- /dev/null +++ b/test/unit/revision_test.rb @@ -0,0 +1,7 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class RevisionTest < Test::Unit::TestCase + + fixtures 'webs', 'pages', 'revisions' + +end diff --git a/test/unit/web_test.rb b/test/unit/web_test.rb new file mode 100644 index 00000000..2e504e0c --- /dev/null +++ b/test/unit/web_test.rb @@ -0,0 +1,7 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class WebTest < Test::Unit::TestCase + + fixtures 'webs', 'pages', 'revisions' + +end From 125cc37135dabc6cc8d3f8ef1c0ea12a0b2372ff Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 2 Aug 2005 08:59:57 +0000 Subject: [PATCH 05/84] Added non-nullable fields to fixtures --- test/fixtures/pages.yml | 3 +++ test/fixtures/revisions.yml | 4 ++++ test/fixtures/webs.yml | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/test/fixtures/pages.yml b/test/fixtures/pages.yml index 9f856f2a..2c62113c 100644 --- a/test/fixtures/pages.yml +++ b/test/fixtures/pages.yml @@ -1,2 +1,5 @@ home_page: id: 1 + created_at: 2004-08-01 + updated_at: 2005-08-01 + web_id: 1 \ No newline at end of file diff --git a/test/fixtures/revisions.yml b/test/fixtures/revisions.yml index 6538429d..9d43eb26 100644 --- a/test/fixtures/revisions.yml +++ b/test/fixtures/revisions.yml @@ -1,2 +1,6 @@ home_page_first_revision: id: 1 + created_at: 2004-08-01 + updated_at: 2005-08-01 + page_id: 1 + content: some text \ No newline at end of file diff --git a/test/fixtures/webs.yml b/test/fixtures/webs.yml index 8c4a2778..7276bb65 100644 --- a/test/fixtures/webs.yml +++ b/test/fixtures/webs.yml @@ -1,2 +1,6 @@ test_wiki: id: 1 + created_at: 2004-08-01 + updated_at: 2005-08-01 + name: wiki + address: wiki \ No newline at end of file From 6fb0102a5ad9c4430aa7bfee7c15c649d0d2fd6d Mon Sep 17 00:00:00 2001 From: Rick Okin Date: Mon, 8 Aug 2005 04:32:03 +0000 Subject: [PATCH 06/84] Just checking authentication --- README | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README b/README index adbaaaf1..b5df8534 100755 --- a/README +++ b/README @@ -66,3 +66,5 @@ Make sure that you always launch Instiki from the same working directory, or spe Author:: David Heinemeier Hansson Email:: david@loudthinking.com Weblog:: http://www.loudthinking.com + + From e4ecb406bf3535639d03285092548b3276937d30 Mon Sep 17 00:00:00 2001 From: Rick Okin Date: Tue, 9 Aug 2005 01:18:00 +0000 Subject: [PATCH 07/84] renamed model directory --- app/{model => models}/page.rb | 0 app/{model => models}/revision.rb | 0 app/{model => models}/web.rb | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename app/{model => models}/page.rb (100%) rename app/{model => models}/revision.rb (100%) rename app/{model => models}/web.rb (100%) diff --git a/app/model/page.rb b/app/models/page.rb similarity index 100% rename from app/model/page.rb rename to app/models/page.rb diff --git a/app/model/revision.rb b/app/models/revision.rb similarity index 100% rename from app/model/revision.rb rename to app/models/revision.rb diff --git a/app/model/web.rb b/app/models/web.rb similarity index 100% rename from app/model/web.rb rename to app/models/web.rb From 2eb01cd5756912e5da973ecd6ebe249d468bc934 Mon Sep 17 00:00:00 2001 From: Rick Okin Date: Tue, 9 Aug 2005 01:23:11 +0000 Subject: [PATCH 08/84] moved some tests over --- test-old/functional/routes_test.rb => test/functional | 0 test-old/unit/diff_test.rb => test/uni | 0 {test-old => test}/unit/chunks/category_test.rb | 0 {test-old => test}/unit/chunks/nowiki_test.rb | 0 {test-old => test}/unit/chunks/wiki_test.rb | 0 {test-old => test}/unit/file_yard_test.rb | 0 {test-old => test}/unit/redcloth_for_tex_test.rb | 0 {test-old => test}/unit/uri_test.rb | 0 {test-old => test}/unit/wiki_words_test.rb | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename test-old/functional/routes_test.rb => test/functional (100%) rename test-old/unit/diff_test.rb => test/uni (100%) rename {test-old => test}/unit/chunks/category_test.rb (100%) rename {test-old => test}/unit/chunks/nowiki_test.rb (100%) rename {test-old => test}/unit/chunks/wiki_test.rb (100%) rename {test-old => test}/unit/file_yard_test.rb (100%) rename {test-old => test}/unit/redcloth_for_tex_test.rb (100%) rename {test-old => test}/unit/uri_test.rb (100%) rename {test-old => test}/unit/wiki_words_test.rb (100%) diff --git a/test-old/functional/routes_test.rb b/test/functional similarity index 100% rename from test-old/functional/routes_test.rb rename to test/functional diff --git a/test-old/unit/diff_test.rb b/test/uni similarity index 100% rename from test-old/unit/diff_test.rb rename to test/uni diff --git a/test-old/unit/chunks/category_test.rb b/test/unit/chunks/category_test.rb similarity index 100% rename from test-old/unit/chunks/category_test.rb rename to test/unit/chunks/category_test.rb diff --git a/test-old/unit/chunks/nowiki_test.rb b/test/unit/chunks/nowiki_test.rb similarity index 100% rename from test-old/unit/chunks/nowiki_test.rb rename to test/unit/chunks/nowiki_test.rb diff --git a/test-old/unit/chunks/wiki_test.rb b/test/unit/chunks/wiki_test.rb similarity index 100% rename from test-old/unit/chunks/wiki_test.rb rename to test/unit/chunks/wiki_test.rb diff --git a/test-old/unit/file_yard_test.rb b/test/unit/file_yard_test.rb similarity index 100% rename from test-old/unit/file_yard_test.rb rename to test/unit/file_yard_test.rb diff --git a/test-old/unit/redcloth_for_tex_test.rb b/test/unit/redcloth_for_tex_test.rb similarity index 100% rename from test-old/unit/redcloth_for_tex_test.rb rename to test/unit/redcloth_for_tex_test.rb diff --git a/test-old/unit/uri_test.rb b/test/unit/uri_test.rb similarity index 100% rename from test-old/unit/uri_test.rb rename to test/unit/uri_test.rb diff --git a/test-old/unit/wiki_words_test.rb b/test/unit/wiki_words_test.rb similarity index 100% rename from test-old/unit/wiki_words_test.rb rename to test/unit/wiki_words_test.rb From 64b9a5c747733662e1f1236c4b569e79109a9ad6 Mon Sep 17 00:00:00 2001 From: Rick Okin Date: Tue, 9 Aug 2005 01:24:44 +0000 Subject: [PATCH 09/84] fixing bad filename --- test/{functional => routes_test.rb} | 0 test/{uni => unit/diff_test.rb} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename test/{functional => routes_test.rb} (100%) rename test/{uni => unit/diff_test.rb} (100%) diff --git a/test/functional b/test/routes_test.rb similarity index 100% rename from test/functional rename to test/routes_test.rb diff --git a/test/uni b/test/unit/diff_test.rb similarity index 100% rename from test/uni rename to test/unit/diff_test.rb From 8c331d101975769ed8b66c4df29fc0eee196a587 Mon Sep 17 00:00:00 2001 From: Rick Okin Date: Tue, 9 Aug 2005 01:28:19 +0000 Subject: [PATCH 10/84] moved old test stuff --- test-old/all_tests.rb | 9 - test-old/test_helper.rb | 129 ------- test-old/unit/page_test.rb | 89 ----- test-old/unit/revision_test.rb | 335 ------------------ test-old/unit/web_test.rb | 165 --------- test-old/unit/wiki_service_test.rb | 129 ------- {test-old => test}/fixtures/rails.gif | Bin .../functional/admin_controller_test.rb | 0 .../functional/application_test.rb | 0 .../functional/file_controller_test.rb | 0 test/{ => functional}/routes_test.rb | 0 .../functional/wiki_controller_test.rb | 0 {test-old => test}/watir/e2e.rb | 0 13 files changed, 856 deletions(-) delete mode 100755 test-old/all_tests.rb delete mode 100644 test-old/test_helper.rb delete mode 100755 test-old/unit/page_test.rb delete mode 100755 test-old/unit/revision_test.rb delete mode 100755 test-old/unit/web_test.rb delete mode 100755 test-old/unit/wiki_service_test.rb rename {test-old => test}/fixtures/rails.gif (100%) rename {test-old => test}/functional/admin_controller_test.rb (100%) rename {test-old => test}/functional/application_test.rb (100%) rename {test-old => test}/functional/file_controller_test.rb (100%) rename test/{ => functional}/routes_test.rb (100%) rename {test-old => test}/functional/wiki_controller_test.rb (100%) rename {test-old => test}/watir/e2e.rb (100%) diff --git a/test-old/all_tests.rb b/test-old/all_tests.rb deleted file mode 100755 index 023ee389..00000000 --- a/test-old/all_tests.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'test_helper' -require 'find' - -test_root = File.dirname(__FILE__) -Find.find(test_root) { |path| - if File.file?(path) and path =~ /.*_test\.rb$/ - load path - end -} diff --git a/test-old/test_helper.rb b/test-old/test_helper.rb deleted file mode 100644 index fe8c1e29..00000000 --- a/test-old/test_helper.rb +++ /dev/null @@ -1,129 +0,0 @@ -ENV['RAILS_ENV'] = 'test' -require File.expand_path(File.dirname(__FILE__) + '/../config/environment') -require 'application' -require 'test/unit' -require 'breakpoint' -require 'action_controller/test_process' - -# Uncomment this variable to have assert_success check that response bodies are valid XML -$validate_xml_in_assert_success = true - -# Convenient setup method for Test::Unit::TestCase -class Test::Unit::TestCase - - private - - def setup_controller_test(controller_class = nil, host = nil) - if controller_class - @controller = controller_class.new - elsif self.class.to_s =~ /^(\w+Controller)Test$/ - @controller = Object::const_get($1).new - else - raise "Cannot derive the name of controller under test from class name #{self.class}" - end - @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new - @request.host = host || 'localhost' - return @request, @response - end - - # Wiki fixture for tests - - def setup_test_wiki - @wiki = ApplicationController.wiki = WikiServiceWithNoPersistence.new - @web = @wiki.create_web('Test Wiki 1', 'wiki1') - @home = @wiki.write_page('wiki1', 'HomePage', 'First revision of the HomePage end', Time.now, - Author.new('AnAuthor', '127.0.0.1')) - end - - def setup_wiki_with_three_pages - @oak = @wiki.write_page('wiki1', 'Oak', - "All about oak.\n" + - "category: trees", - 5.minutes.ago, Author.new('TreeHugger', '127.0.0.2')) - @elephant = @wiki.write_page('wiki1', 'Elephant', - "All about elephants.\n" + - "category: animals", - 10.minutes.ago, Author.new('Guest', '127.0.0.2')) - end - - def setup_wiki_with_30_pages - (1..30).each { |i| - @wiki.write_page('wiki1', "page#{i}", "Test page #{i}\ncategory: test", - Time.local(1976, 10, i, 12, 00, 00), Author.new('Dema', '127.0.0.2')) - } - end - - def tear_down_wiki - ApplicationController.wiki = nil - end - -end - -class WikiServiceWithNoPersistence - include AbstractWikiService - def initialize - init_wiki_service - end - - def storage_path - RAILS_ROOT + '/storage/test/' - end -end - - -# This module is to be included in unit tests that involve matching chunks. -# It provides a easy way to test whether a chunk matches a particular string -# and any the values of any fields that should be set after a match. -class ContentStub < String - include ChunkManager - def initialize(str) - super - init_chunk_manager - end - def page_link(*); end -end - -module ChunkMatch - - # Asserts a number of tests for the given type and text. - def match(chunk_type, test_text, expected_chunk_state) - if chunk_type.respond_to? :pattern - assert_match(chunk_type.pattern, test_text) - end - - content = ContentStub.new(test_text) - chunk_type.apply_to(content) - - # Test if requested parts are correct. - expected_chunk_state.each_pair do |a_method, expected_value| - assert content.chunks.last.kind_of?(chunk_type) - assert_respond_to(content.chunks.last, a_method) - assert_equal(expected_value, content.chunks.last.send(a_method.to_sym), - "Wrong #{a_method} value") - end - end - - # Asserts that test_text doesn't match the chunk_type - def no_match(chunk_type, test_text) - if chunk_type.respond_to? :pattern - assert_no_match(chunk_type.pattern, test_text) - end - end -end - -if defined? $validate_xml_in_assert_success and $validate_xml_in_assert_success == true - module Test - module Unit - module Assertions - unless method_defined? :__assert_success_before_ovverride_by_instiki - alias :__assert_success_before_ovverride_by_instiki :assert_success - end - def assert_success - __assert_success_before_ovverride_by_instiki - if @response.body.kind_of?(Proc) then # it's a file download, not an HTML content - else assert_nothing_raised(@response.body) { REXML::Document.new(@response.body) } end - end - end - end - end -end diff --git a/test-old/unit/page_test.rb b/test-old/unit/page_test.rb deleted file mode 100755 index 050bc8cd..00000000 --- a/test-old/unit/page_test.rb +++ /dev/null @@ -1,89 +0,0 @@ -#!/bin/env ruby -w - -require File.dirname(__FILE__) + '/../test_helper' -require 'web' -require 'page' - -class PageTest < Test::Unit::TestCase - - class MockWeb < Web - def initialize() super(nil, 'test','test') end - def [](wiki_word) %w( MyWay ThatWay SmartEngine ).include?(wiki_word) end - def refresh_pages_with_references(name) end - end - - def setup - @page = Page.new(MockWeb.new, "FirstPage") - @page.revise("HisWay would be MyWay in kinda ThatWay in HisWay though MyWay \\OverThere -- see SmartEngine in that SmartEngineGUI", - Time.local(2004, 4, 4, 16, 50), - "DavidHeinemeierHansson") - end - - def test_lock - assert !@page.locked?(Time.local(2004, 4, 4, 16, 50)) - - @page.lock(Time.local(2004, 4, 4, 16, 30), "DavidHeinemeierHansson") - - assert @page.locked?(Time.local(2004, 4, 4, 16, 50)) - assert !@page.locked?(Time.local(2004, 4, 4, 17, 1)) - - @page.unlock - - assert !@page.locked?(Time.local(2004, 4, 4, 16, 50)) - end - - def test_lock_duration - @page.lock(Time.local(2004, 4, 4, 16, 30), "DavidHeinemeierHansson") - - assert_equal 15, @page.lock_duration(Time.local(2004, 4, 4, 16, 45)) - end - - def test_plain_name - assert_equal "First Page", @page.plain_name - end - - def test_revise - @page.revise('HisWay would be MyWay in kinda lame', Time.local(2004, 4, 4, 16, 55), 'MarianneSyhler') - assert_equal 2, @page.revisions.length, 'Should have two revisions' - assert_equal 'MarianneSyhler', @page.author, 'Mary should be the author now' - assert_equal 'DavidHeinemeierHansson', @page.revisions.first.author, 'David was the first author' - end - - def test_revise_continous_revision - @page.revise('HisWay would be MyWay in kinda lame', Time.local(2004, 4, 4, 16, 55), 'MarianneSyhler') - assert_equal 2, @page.revisions.length - - @page.revise('HisWay would be MyWay in kinda update', Time.local(2004, 4, 4, 16, 57), 'MarianneSyhler') - assert_equal 2, @page.revisions.length - assert_equal 'HisWay would be MyWay in kinda update', @page.revisions.last.content - assert_equal Time.local(2004, 4, 4, 16, 57), @page.revisions.last.created_at - - @page.revise('HisWay would be MyWay in the house', Time.local(2004, 4, 4, 16, 58), 'DavidHeinemeierHansson') - assert_equal 3, @page.revisions.length - assert_equal 'HisWay would be MyWay in the house', @page.revisions.last.content - - @page.revise('HisWay would be MyWay in my way', Time.local(2004, 4, 4, 17, 30), 'DavidHeinemeierHansson') - assert_equal 4, @page.revisions.length - end - - def test_revise_content_unchanged - last_revision_before = @page.revisions.last - revisions_number_before = @page.revisions.size - - assert_raises(Instiki::ValidationError) { - @page.revise(@page.revisions.last.content.dup, Time.now, 'AlexeyVerkhovsky') - } - - assert_same last_revision_before, @page.revisions.last - assert_equal revisions_number_before, @page.revisions.size - end - - def test_rollback - @page.revise("spot two", Time.now, "David") - @page.revise("spot three", Time.now + 2000, "David") - assert_equal 3, @page.revisions.length, "Should have three revisions" - @page.rollback(1, Time.now) - assert_equal "spot two", @page.content - end - -end diff --git a/test-old/unit/revision_test.rb b/test-old/unit/revision_test.rb deleted file mode 100755 index bd84e660..00000000 --- a/test-old/unit/revision_test.rb +++ /dev/null @@ -1,335 +0,0 @@ -#!/bin/env ruby -w - -require File.dirname(__FILE__) + '/../test_helper' -require 'web' -require 'revision' -require 'fileutils' - -class RevisionTest < Test::Unit::TestCase - - def setup - setup_test_wiki - @web.markup = :textile - - @page = @wiki.read_page('wiki1', 'HomePage') - ['MyWay', 'SmartEngine', 'ThatWay'].each do |page| - @wiki.write_page('wiki1', page, page, Time.now, 'Me') - end - @wiki.write_page('wiki1','NoWikiWord', 'hey you', Time.now, 'Me') - - @revision = Revision.new(@page, 1, - 'HisWay would be MyWay in kinda ThatWay in HisWay though MyWay \OverThere -- ' + - 'see SmartEngine in that SmartEngineGUI', - Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson') - end - - def test_wiki_words - assert_equal %w( HisWay MyWay SmartEngine SmartEngineGUI ThatWay ), @revision.wiki_words.sort - - @wiki.write_page('wiki1', 'NoWikiWord', 'hey you', Time.now, 'Me') - assert_equal [], @wiki.read_page('wiki1', 'NoWikiWord').wiki_words - end - - def test_existing_pages - assert_equal %w( MyWay SmartEngine ThatWay ), @revision.existing_pages.sort - end - - def test_unexisting_pages - assert_equal %w( HisWay SmartEngineGUI ), @revision.unexisting_pages.sort - end - - def test_content_with_wiki_links - assert_equal '

His Way? ' + - 'would be My Way in kinda ' + - 'That Way in ' + - 'His Way? ' + - 'though My Way OverThere—see ' + - 'Smart Engine in that ' + - 'Smart Engine GUI' + - '?

', - @revision.display_content - end - - def test_markdown - @web.markup = :markdown - - assert_markup_parsed_as( - %{

My Headline

\n\n

that } + - %{Smart Engine GUI?

}, - "My Headline\n===========\n\nthat SmartEngineGUI") - - code_block = [ - 'This is a code block:', - '', - ' def a_method(arg)', - ' return ThatWay', - '', - 'Nice!' - ].join("\n") - - assert_markup_parsed_as( - %{

This is a code block:

\n\n
def a_method(arg)\n} +
-	    %{return ThatWay\n
\n\n

Nice!

}, - code_block) - end - - def test_markdown_hyperlink_with_slash - # in response to a bug, see http://dev.instiki.org/attachment/ticket/177 - @web.markup = :markdown - - assert_markup_parsed_as( - '

text

', - '[text](http://example/with/slash)') - end - - def test_mixed_formatting - - textile_and_markdown = [ - 'Markdown heading', - '================', - '', - 'h2. Textile heading', - '', - '*some* **text** _with_ -styles-', - '', - '* list 1', - '* list 2' - ].join("\n") - - @web.markup = :markdown - assert_markup_parsed_as( - "

Markdown heading

\n\n" + - "

h2. Textile heading

\n\n" + - "

some text with -styles-

\n\n" + - "
    \n
  • list 1
  • \n
  • list 2
  • \n
", - textile_and_markdown) - - @web.markup = :textile - assert_markup_parsed_as( - "

Markdown heading
================

\n\n\n\t

Textile heading

" + - "\n\n\n\t

some text with styles

" + - "\n\n\n\t
    \n\t
  • list 1
  • \n\t\t
  • list 2
  • \n\t
", - textile_and_markdown) - - @web.markup = :mixed - assert_markup_parsed_as( - "

Markdown heading

\n\n\n\t

Textile heading

\n\n\n\t" + - "

some text with styles

\n\n\n\t" + - "
    \n\t
  • list 1
  • \n\t\t
  • list 2
  • \n\t
", - textile_and_markdown) - end - - def test_rdoc - @web.markup = :rdoc - - @revision = Revision.new(@page, 1, '+hello+ that SmartEngineGUI', - Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson') - - assert_equal "hello that Smart Engine GUI" + - "?\n\n", @revision.display_content - end - - def test_content_with_auto_links - assert_markup_parsed_as( - '

http://www.loudthinking.com/ ' + - 'points to That Way from ' + - 'david@loudthinking.com

', - 'http://www.loudthinking.com/ points to ThatWay from david@loudthinking.com') - - end - - def test_content_with_aliased_links - assert_markup_parsed_as( - '

Would a clever motor' + - ' go by any other name?

', - 'Would a [[SmartEngine|clever motor]] go by any other name?') - end - - def test_content_with_wikiword_in_em - assert_markup_parsed_as( - '

should we go ' + - 'That Way or This Way?' + - '

', - '_should we go ThatWay or ThisWay _') - end - - def test_content_with_wikiword_in_tag - assert_markup_parsed_as( - '

That is some Stylish Emphasis

', - 'That is some Stylish Emphasis') - end - - def test_content_with_escaped_wikiword - # there should be no wiki link - assert_markup_parsed_as('

WikiWord

', '\WikiWord') - end - - def test_content_with_pre_blocks - assert_markup_parsed_as( - '

A class SmartEngine end would not mark up

CodeBlocks

', - 'A class SmartEngine end would not mark up
CodeBlocks
') - end - - def test_content_with_autolink_in_parentheses - assert_markup_parsed_as( - '

The W3C body (' + - 'http://www.w3c.org) sets web standards

', - 'The W3C body (http://www.w3c.org) sets web standards') - end - - def test_content_with_link_in_parentheses - assert_markup_parsed_as( - '

(What is a wiki?)

', - '("What is a wiki?":http://wiki.org/wiki.cgi?WhatIsWiki)') - end - - def test_content_with_image_link - assert_markup_parsed_as( - '

This is a Textile image link.

', - 'This !http://hobix.com/sample.jpg! is a Textile image link.') - end - - def test_content_with_inlined_img_tag - assert_markup_parsed_as( - '

This is an inline image link.

', - 'This is an inline image link.') - assert_markup_parsed_as( - '

This is an inline image link.

', - 'This is an inline image link.') - end - - def test_nowiki_tag - assert_markup_parsed_as( - '

Do not mark up [[this text]] or http://www.thislink.com.

', - 'Do not mark up [[this text]] ' + - 'or http://www.thislink.com.') - end - - def test_multiline_nowiki_tag - assert_markup_parsed_as( - "

Do not mark \n up [[this text]] \nand http://this.url.com but markup " + - 'this?

', - "Do not mark \n up [[this text]] \n" + - "and http://this.url.com but markup [[this]]") - end - - def test_content_with_bracketted_wiki_word - @web.brackets_only = true - assert_markup_parsed_as( - '

This is a WikiWord and a tricky name ' + - 'Sperberg-McQueen?.

', - 'This is a WikiWord and a tricky name [[Sperberg-McQueen]].') - end - - def test_content_for_export - assert_equal '

His Way would be ' + - 'My Way in kinda ' + - 'That Way in ' + - 'His Way though ' + - 'My Way OverThere—see ' + - 'Smart Engine in that ' + - 'Smart Engine GUI

', - @revision.display_content_for_export - end - - def test_double_replacing - @revision.content = "VersionHistory\r\n\r\ncry VersionHistory" - assert_equal '

Version History' + - "?

\n\n\n\t

cry " + - 'Version History?' + - '

', - @revision.display_content - - @revision.clear_display_cache - - @revision.content = "f\r\nVersionHistory\r\n\r\ncry VersionHistory" - assert_equal "

f
Version History" + - "?

\n\n\n\t

cry " + - "Version History?" + - "

", - @revision.display_content - end - - def test_difficult_wiki_words - @revision.content = "[[It's just awesome GUI!]]" - assert_equal "

It's just awesome GUI!" + - "?

", - @revision.display_content - end - - def test_revisions_diff - - @page.revisions = [ - Revision.new(@page, 0, 'What a blue and lovely morning', - Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson'), - Revision.new(@page, 1, 'What a red and lovely morning today', - Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson') - ] - - assert_equal "

What a blue red " + - "and lovely morningmorning " + - "today

", @page.revisions.last.display_diff - end - - def test_link_to_file - assert_markup_parsed_as( - '

doc.pdf?

', - '[[doc.pdf:file]]') - end - - def test_link_to_pic - FileUtils.mkdir_p "#{RAILS_ROOT}/storage/test/wiki1" - FileUtils.rm(Dir["#{RAILS_ROOT}/storage/test/wiki1/*"]) - @wiki.file_yard(@web).upload_file('square.jpg', StringIO.new('')) - assert_markup_parsed_as( - '

Square

', - '[[square.jpg|Square:pic]]') - assert_markup_parsed_as( - '

square.jpg

', - '[[square.jpg:pic]]') - end - - def test_link_to_non_existant_pic - assert_markup_parsed_as( - '

NonExistant?' + - '

', - '[[NonExistant.jpg|NonExistant:pic]]') - assert_markup_parsed_as( - '

NonExistant.jpg?' + - '

', - '[[NonExistant.jpg:pic]]') - end - - def test_wiki_link_with_colon - assert_markup_parsed_as( - '

With:Colon?

', - '[[With:Colon]]') - end - - # TODO Remove the leading underscores from this test when upgrading to RedCloth 3.0.1; - # also add a test for the "Unhappy Face" problem (another interesting RedCloth bug) - def test_list_with_tildas - list_with_tildas = <<-EOL - * "a":~b - * c~ d - EOL - - assert_markup_parsed_as( - "
    \n\t
  • a
  • \n\t\t
  • c~ d
  • \n\t
", - list_with_tildas) - end - - def test_textile_image_in_mixed_wiki - @web.markup = :mixed - assert_markup_parsed_as( - "

\"\"\nss

", - "!http://google.com!\r\nss") - end - - - def assert_markup_parsed_as(expected_output, input) - revision = Revision.new(@page, 1, input, Time.local(2004, 4, 4, 16, 50), 'AnAuthor') - assert_equal expected_output, revision.display_content, 'Textile output not as expected' - end - -end diff --git a/test-old/unit/web_test.rb b/test-old/unit/web_test.rb deleted file mode 100755 index 4cd15411..00000000 --- a/test-old/unit/web_test.rb +++ /dev/null @@ -1,165 +0,0 @@ -#!/bin/env ruby -w - -require File.dirname(__FILE__) + '/../test_helper' -require 'wiki_service' - -class WebTest < Test::Unit::TestCase - def setup - @web = Web.new nil, 'Instiki', 'instiki' - end - - def test_wiki_word_linking - @web.add_page('SecondPage', 'Yo, yo. Have you EverBeenHated', - Time.now, 'DavidHeinemeierHansson') - - assert_equal('

Yo, yo. Have you Ever Been Hated' + - '?

', - @web.pages["SecondPage"].display_content) - - @web.add_page('EverBeenHated', 'Yo, yo. Have you EverBeenHated', Time.now, - 'DavidHeinemeierHansson') - assert_equal('

Yo, yo. Have you Ever Been Hated

', - @web.pages['SecondPage'].display_content) - end - - def test_pages_by_revision - add_sample_pages - assert_equal 'EverBeenHated', @web.select.by_revision.first.name - end - - def test_pages_by_match - add_sample_pages - assert_equal 2, @web.select { |page| page.content =~ /me/i }.length - assert_equal 1, @web.select { |page| page.content =~ /Who/i }.length - assert_equal 0, @web.select { |page| page.content =~ /none/i }.length - end - - def test_references - add_sample_pages - assert_equal 1, @web.select.pages_that_reference('EverBeenHated').length - assert_equal 0, @web.select.pages_that_reference('EverBeenInLove').length - end - - def test_delete - add_sample_pages - assert_equal 2, @web.pages.length - @web.remove_pages([ @web.pages['EverBeenInLove'] ]) - assert_equal 1, @web.pages.length - end - - def test_make_link - add_sample_pages - - existing_page_wiki_url = - 'Ever Been In Love' - existing_page_published_url = - 'Ever Been In Love' - existing_page_static_url = - 'Ever Been In Love' - new_page_wiki_url = - 'Unknown Word?' - new_page_published_url = - new_page_static_url = - 'Unknown Word' - - # no options - assert_equal existing_page_wiki_url, @web.make_link('EverBeenInLove') - - # :mode => :export - assert_equal existing_page_static_url, @web.make_link('EverBeenInLove', nil, :mode => :export) - - # :mode => :publish - assert_equal existing_page_published_url, - @web.make_link('EverBeenInLove', nil, :mode => :publish) - - # new page, no options - assert_equal new_page_wiki_url, @web.make_link('UnknownWord') - - # new page, :mode => :export - assert_equal new_page_static_url, @web.make_link('UnknownWord', nil, :mode => :export) - - # new page, :mode => :publish - assert_equal new_page_published_url, @web.make_link('UnknownWord', nil, :mode => :publish) - - # Escaping special characters in the name - assert_equal( - 'Smith & Wesson?', - @web.make_link('Smith & Wesson')) - - # optionally using text as the link text - assert_equal( - existing_page_published_url.sub(/>Ever Been In LoveHaven't you ever been in love?<"), - @web.make_link('EverBeenInLove', "Haven't you ever been in love?", :mode => :publish)) - - end - - def test_initialize - wiki_stub = Object.new - - web = Web.new(wiki_stub, 'Wiki2', 'wiki2', '123') - - assert_equal wiki_stub, web.wiki - assert_equal 'Wiki2', web.name - assert_equal 'wiki2', web.address - assert_equal '123', web.password - - # new web should be set for maximum features enabled - assert_equal :textile, web.markup - assert_equal '008B26', web.color - assert !web.safe_mode - assert_equal({}, web.pages) - assert web.allow_uploads - assert_equal wiki_stub, web.wiki - assert_nil web.additional_style - assert !web.published - assert !web.brackets_only - assert !web.count_pages - assert web.allow_uploads - assert_equal 100, web.max_upload_size - end - - def test_initialize_invalid_name - wiki_stub = Object.new - assert_raises(Instiki::ValidationError) { - Web.new(wiki_stub, 'Wiki2', "wiki\234", '123') - } - end - - def test_new_page_linked_from_mother_page - # this was a bug in revision 204 - home = @web.add_page('HomePage', 'This page refers to AnotherPage', - Time.local(2004, 4, 4, 16, 50), 'Alexey Verkhovsky') - @web.add_page('AnotherPage', 'This is \AnotherPage', - Time.local(2004, 4, 4, 16, 51), 'Alexey Verkhovsky') - - assert_equal [home], @web.select.pages_that_link_to('AnotherPage') - end - - def test_orphaned_pages - add_sample_pages - home = @web.add_page('HomePage', - 'This is a home page, it should not be an orphan', - Time.local(2004, 4, 4, 16, 50), 'AlexeyVerkhovsky') - author = @web.add_page('AlexeyVerkhovsky', - 'This is an author page, it should not be an orphan', - Time.local(2004, 4, 4, 16, 50), 'AlexeyVerkhovsky') - self_linked = @web.add_page('SelfLinked', - 'I am me SelfLinked and link to EverBeenInLove', - Time.local(2004, 4, 4, 16, 50), 'AnonymousCoward') - - # page that links to itself, and nobody else links to it must be an orphan - assert_equal ['EverBeenHated', 'SelfLinked'], - @web.select.orphaned_pages.collect{ |page| page.name }.sort - end - - - private - - def add_sample_pages - @in_love = @web.add_page('EverBeenInLove', 'Who am I me', - Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson') - @hated = @web.add_page('EverBeenHated', 'I am me EverBeenHated', - Time.local(2004, 4, 4, 16, 51), 'DavidHeinemeierHansson') - end -end diff --git a/test-old/unit/wiki_service_test.rb b/test-old/unit/wiki_service_test.rb deleted file mode 100755 index c02c61ff..00000000 --- a/test-old/unit/wiki_service_test.rb +++ /dev/null @@ -1,129 +0,0 @@ -#!/bin/env ruby -w - -require File.dirname(__FILE__) + '/../test_helper' -require 'wiki_service' -require 'fileutils' - -class WikiServiceTest < Test::Unit::TestCase - - # Clean the test storage directory before the run - unless defined? @@storage_cleaned - FileUtils.rm(Dir[RAILS_ROOT + '/storage/test/*.command_log']) - FileUtils.rm(Dir[RAILS_ROOT + '/storage/test/*.snapshot']) - FileUtils.rm(Dir[RAILS_ROOT + '/storage/test/*.tex']) - FileUtils.rm(Dir[RAILS_ROOT + '/storage/test/*.zip']) - FileUtils.rm(Dir[RAILS_ROOT + '/storage/test/*.pdf']) - FileUtils.rm(Dir[RAILS_ROOT + '/storage/test/instiki/*']) - @@cleaned_storage = true - WikiService.instance.setup('pswd', 'Wiki', 'wiki') - end - - def setup - @s = WikiService.instance - @s.create_web 'Instiki', 'instiki' - @web = @s.webs['instiki'] - end - - def teardown - @s.delete_web 'instiki' - end - - def test_read_write_page - @s.write_page 'instiki', 'FirstPage', "Electric shocks, I love 'em", - Time.now, 'DavidHeinemeierHansson' - assert_equal "Electric shocks, I love 'em", @s.read_page('instiki', 'FirstPage').content - end - - def test_read_only_operations - @s.write_page 'instiki', 'TestReadOnlyOperations', 'Read only operations dont change the' + - 'state of any object, and therefore should not be logged by Madeleine!', - Time.now, 'AlexeyVerkhovsky' - - assert_doesnt_change_state_or_log :authenticate, 'pswd' - assert_doesnt_change_state_or_log :read_page, 'instiki', 'TestReadOnlyOperations' - assert_doesnt_change_state_or_log :setup? - assert_doesnt_change_state_or_log :webs - - @s.write_page 'instiki', 'FirstPage', "Electric shocks, I love 'em", - Time.now, 'DavidHeinemeierHansson' - assert_equal "Electric shocks, I love 'em", @s.read_page('instiki', 'FirstPage').content - end - - def test_aborted_transaction - @s.write_page 'instiki', 'FirstPage', "Electric shocks, I love 'em", - 10.minutes.ago, 'DavidHeinemeierHansson' - - assert_doesnt_change_state('revise_page with unchanged content') { - begin - @s.revise_page 'instiki', 'FirstPage', "Electric shocks, I love 'em", - Time.now, 'DavidHeinemeierHansson' - fail 'Expected Instiki::ValidationError not raised' - rescue Instiki::ValidationError - end - } - end - - def test_file_yard - file_yard = @s.file_yard(@web) - assert_equal FileYard, file_yard.class - assert_equal(@s.storage_path + '/instiki', file_yard.files_path) - end - - def test_edit_web_validations - another_web = @s.create_web 'Another', 'another' - - # try to rename instiki web to another (which is the name of an already existing one) - assert_raises(Instiki::ValidationError) { - @s.edit_web('instiki', 'another', @web.name, @web.markup, @web.color, @web.additional_style) - } - - assert_raises(Instiki::ValidationError) { - @s.edit_web('nonexistant', 'another', @web.name, @web.markup, @web.color, @web.additional_style) - } - end - - - # Checks that a method call or a block doesn;t change the persisted state of the wiki - # Usage: - # assert_doesnt_change_state :read_page, 'instiki', 'TestReadOnlyOperations' - # or - # assert_doesnt_change_state {|wiki| wiki.webs} - - def assert_doesnt_change_state(method, *args, &block) - _assert_doesnt_change_state(including_command_log = false, method, *args, &block) - end - - # Same as assert_doesnt_change_state, but also asserts that no vommand log is generated - def assert_doesnt_change_state_or_log(method, *args, &block) - _assert_doesnt_change_state(including_command_log = true, method, *args, &block) - end - - private - - def _assert_doesnt_change_state(including_log, method, *args) - WikiService.snapshot - last_snapshot_before = last_snapshot - - if block_given? - yield @s - else - @s.send(method, *args) - end - - if including_log - command_logs = Dir[RAILS_ROOT + 'storage/test/*.command_log'] - assert command_logs.empty?, "Calls to #{method} should not be logged" - end - - last_snapshot_after = last_snapshot - assert last_snapshot_before == last_snapshot_after, - 'Calls to #{method} should not change the state of any persisted object' - end - - def last_snapshot - snapshots = Dir[RAILS_ROOT + '/storage/test/*.snapshot'] - assert !snapshots.empty?, "No snapshots found at #{RAILS_ROOT}/storage/test/" - File.read(snapshots.last) - end - -end diff --git a/test-old/fixtures/rails.gif b/test/fixtures/rails.gif similarity index 100% rename from test-old/fixtures/rails.gif rename to test/fixtures/rails.gif diff --git a/test-old/functional/admin_controller_test.rb b/test/functional/admin_controller_test.rb similarity index 100% rename from test-old/functional/admin_controller_test.rb rename to test/functional/admin_controller_test.rb diff --git a/test-old/functional/application_test.rb b/test/functional/application_test.rb similarity index 100% rename from test-old/functional/application_test.rb rename to test/functional/application_test.rb diff --git a/test-old/functional/file_controller_test.rb b/test/functional/file_controller_test.rb similarity index 100% rename from test-old/functional/file_controller_test.rb rename to test/functional/file_controller_test.rb diff --git a/test/routes_test.rb b/test/functional/routes_test.rb similarity index 100% rename from test/routes_test.rb rename to test/functional/routes_test.rb diff --git a/test-old/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb similarity index 100% rename from test-old/functional/wiki_controller_test.rb rename to test/functional/wiki_controller_test.rb diff --git a/test-old/watir/e2e.rb b/test/watir/e2e.rb similarity index 100% rename from test-old/watir/e2e.rb rename to test/watir/e2e.rb From c4b7b2d9f23267406906e1f009d89fa06ac9efbe Mon Sep 17 00:00:00 2001 From: Rick Okin Date: Tue, 9 Aug 2005 02:07:39 +0000 Subject: [PATCH 11/84] added chunks --- lib/chunks/category.rb | 33 ++++++++ lib/chunks/chunk.rb | 86 +++++++++++++++++++ lib/chunks/engines.rb | 61 ++++++++++++++ lib/chunks/include.rb | 41 ++++++++++ lib/chunks/literal.rb | 31 +++++++ lib/chunks/nowiki.rb | 28 +++++++ lib/chunks/test.rb | 18 ++++ lib/chunks/uri.rb | 182 +++++++++++++++++++++++++++++++++++++++++ lib/chunks/wiki.rb | 141 +++++++++++++++++++++++++++++++ 9 files changed, 621 insertions(+) create mode 100644 lib/chunks/category.rb create mode 100644 lib/chunks/chunk.rb create mode 100644 lib/chunks/engines.rb create mode 100644 lib/chunks/include.rb create mode 100644 lib/chunks/literal.rb create mode 100644 lib/chunks/nowiki.rb create mode 100644 lib/chunks/test.rb create mode 100644 lib/chunks/uri.rb create mode 100644 lib/chunks/wiki.rb diff --git a/lib/chunks/category.rb b/lib/chunks/category.rb new file mode 100644 index 00000000..d08d8636 --- /dev/null +++ b/lib/chunks/category.rb @@ -0,0 +1,33 @@ +require 'chunks/chunk' + +# The category chunk looks for "category: news" on a line by +# itself and parses the terms after the ':' as categories. +# Other classes can search for Category chunks within +# rendered content to find out what categories this page +# should be in. +# +# Category lines can be hidden using ':category: news', for example +class Category < Chunk::Abstract + CATEGORY_PATTERN = /^(:)?category\s*:(.*)$/i + def self.pattern() CATEGORY_PATTERN end + + attr_reader :hidden, :list + +def initialize(match_data, content) + super(match_data, content) + @hidden = match_data[1] + @list = match_data[2].split(',').map { |c| c.strip } + @unmask_text = '' + if @hidden + @unmask_text = '' + else + category_urls = @list.map { |category| url(category) }.join(', ') + @unmask_text = '
category: ' + category_urls + '
' + end + end + + # TODO move presentation of page metadata to controller/view + def url(category) + %{#{category}} + end +end diff --git a/lib/chunks/chunk.rb b/lib/chunks/chunk.rb new file mode 100644 index 00000000..9ba3cc04 --- /dev/null +++ b/lib/chunks/chunk.rb @@ -0,0 +1,86 @@ +require 'uri/common' + +# A chunk is a pattern of text that can be protected +# and interrogated by a renderer. Each Chunk class has a +# +pattern+ that states what sort of text it matches. +# Chunks are initalized by passing in the result of a +# match by its pattern. + +module Chunk + class Abstract + + # automatically construct the array of derivatives of Chunk::Abstract + @derivatives = [] + + class << self + attr_reader :derivatives + end + + def self::inherited( klass ) + Abstract::derivatives << klass + end + + # the class name part of the mask strings + def self.mask_string + self.to_s.delete(':').downcase + end + + # a regexp that matches all chunk_types masks + def Abstract::mask_re(chunk_types) + tmp = chunk_types.map{|klass| klass.mask_string}.join("|") + Regexp.new("chunk([0-9a-f]+n\\d+)(#{tmp})chunk") + end + + attr_reader :text, :unmask_text, :unmask_mode + + def initialize(match_data, content) + @text = match_data[0] + @content = content + @unmask_mode = :normal + end + + # Find all the chunks of the given type in content + # Each time the pattern is matched, create a new + # chunk for it, and replace the occurance of the chunk + # in this content with its mask. + def self.apply_to(content) + content.gsub!( self.pattern ) do |match| + new_chunk = self.new($~, content) + content.add_chunk(new_chunk) + new_chunk.mask + end + end + + # should contain only [a-z0-9] + def mask + @mask ||="chunk#{@id}#{self.class.mask_string}chunk" + end + + # We should not use object_id because object_id is not guarantied + # to be unique when we restart the wiki (new object ids can equal old ones + # that were restored from madeleine storage) + def id + @id ||= "#{@content.page_id}n#{@content.chunk_id}" + end + + def unmask + @content.sub!(mask, @unmask_text) + end + + def rendered? + @unmask_mode == :normal + end + + def escaped? + @unmask_mode == :escape + end + + def revert + @content.sub!(mask, @text) + # unregister + @content.delete_chunk(self) + end + + end + +end diff --git a/lib/chunks/engines.rb b/lib/chunks/engines.rb new file mode 100644 index 00000000..fe5a96a8 --- /dev/null +++ b/lib/chunks/engines.rb @@ -0,0 +1,61 @@ +$: << File.dirname(__FILE__) + "../../lib" + +require 'redcloth' +require 'bluecloth_tweaked' +require 'rdocsupport' +require 'chunks/chunk' + +# The markup engines are Chunks that call the one of RedCloth +# or RDoc to convert text. This markup occurs when the chunk is required +# to mask itself. +module Engines + class AbstractEngine < Chunk::Abstract + + # Create a new chunk for the whole content and replace it with its mask. + def self.apply_to(content) + new_chunk = self.new(content) + content.replace(new_chunk.mask) + end + + private + + # Never create engines by constructor - use apply_to instead + def initialize(content) + @content = content + end + + end + + class Textile < AbstractEngine + def mask + redcloth = RedCloth.new(@content, [:hard_breaks] + @content.options[:engine_opts]) + redcloth.filter_html = false + redcloth.no_span_caps = false + redcloth.to_html(:textile) + end + end + + class Markdown < AbstractEngine + def mask + BlueCloth.new(@content, @content.options[:engine_opts]).to_html + end + end + + class Mixed < AbstractEngine + def mask + redcloth = RedCloth.new(@content, @content.options[:engine_opts]) + redcloth.filter_html = false + redcloth.no_span_caps = false + redcloth.to_html + end + end + + class RDoc < AbstractEngine + def mask + RDocSupport::RDocFormatter.new(@content).to_html + end + end + + MAP = { :textile => Textile, :markdown => Markdown, :mixed => Mixed, :rdoc => RDoc } + MAP.default = Textile +end diff --git a/lib/chunks/include.rb b/lib/chunks/include.rb new file mode 100644 index 00000000..370093cc --- /dev/null +++ b/lib/chunks/include.rb @@ -0,0 +1,41 @@ +require 'chunks/wiki' + +# Includes the contents of another page for rendering. +# The include command looks like this: "[[!include PageName]]". +# It is a WikiReference since it refers to another page (PageName) +# and the wiki content using this command must be notified +# of changes to that page. +# If the included page could not be found, a warning is displayed. + +class Include < WikiChunk::WikiReference + + INCLUDE_PATTERN = /\[\[!include\s+(.*?)\]\]\s*/i + def self.pattern() INCLUDE_PATTERN end + + + def initialize(match_data, content) + super + @page_name = match_data[1].strip + @unmask_text = get_unmask_text_avoiding_recursion_loops + end + + private + + def get_unmask_text_avoiding_recursion_loops + if refpage then + refpage.clear_display_cache + if refpage.wiki_includes.include?(@content.page_name) + # this will break the recursion + @content.delete_chunk(self) + return "Recursive include detected; #{@page_name} --> #{@content.page_name} " + + "--> #{@page_name}\n" + else + @content.merge_chunks(refpage.display_content) + return refpage.display_content.pre_rendered + end + else + return "Could not include #{@page_name}\n" + end + end + +end diff --git a/lib/chunks/literal.rb b/lib/chunks/literal.rb new file mode 100644 index 00000000..09da4005 --- /dev/null +++ b/lib/chunks/literal.rb @@ -0,0 +1,31 @@ +require 'chunks/chunk' + +# These are basic chunks that have a pattern and can be protected. +# They are used by rendering process to prevent wiki rendering +# occuring within literal areas such as and
 blocks
+# and within HTML tags.
+module Literal
+
+  class AbstractLiteral < Chunk::Abstract
+
+    def initialize(match_data, content)
+      super
+      @unmask_text = @text
+    end
+
+  end
+
+  # A literal chunk that protects 'code' and 'pre' tags from wiki rendering.
+  class Pre < AbstractLiteral
+    PRE_BLOCKS = "a|pre|code"
+    PRE_PATTERN = Regexp.new('<('+PRE_BLOCKS+')\b[^>]*?>.*?', Regexp::MULTILINE)
+    def self.pattern() PRE_PATTERN end
+  end 
+
+  # A literal chunk that protects HTML tags from wiki rendering.
+  class Tags < AbstractLiteral
+    TAGS = "a|img|em|strong|div|span|table|td|th|ul|ol|li|dl|dt|dd"
+    TAGS_PATTERN = Regexp.new('<(?:'+TAGS+')[^>]*?>', Regexp::MULTILINE) 
+    def self.pattern() TAGS_PATTERN  end
+  end
+end
diff --git a/lib/chunks/nowiki.rb b/lib/chunks/nowiki.rb
new file mode 100644
index 00000000..ef99ec0b
--- /dev/null
+++ b/lib/chunks/nowiki.rb
@@ -0,0 +1,28 @@
+require 'chunks/chunk'
+
+# This chunks allows certain parts of a wiki page to be hidden from the
+# rest of the rendering pipeline. It should be run at the beginning
+# of the pipeline in `wiki_content.rb`.
+#
+# An example use of this chunk is to markup double brackets or
+# auto URI links:
+#  Here are [[double brackets]] and a URI: www.uri.org
+#
+# The contents of the chunks will not be processed by any other chunk
+# so the `www.uri.org` and the double brackets will appear verbatim.
+#
+# Author: Mark Reid 
+# Created: 8th June 2004
+class NoWiki < Chunk::Abstract
+
+  NOWIKI_PATTERN = Regexp.new('(.*?)', Regexp::MULTILINE)
+  def self.pattern() NOWIKI_PATTERN end
+
+  attr_reader :plain_text
+
+  def initialize(match_data, content)
+    super
+    @plain_text = @unmask_text = match_data[1]
+  end
+
+end
diff --git a/lib/chunks/test.rb b/lib/chunks/test.rb
new file mode 100644
index 00000000..edf77d14
--- /dev/null
+++ b/lib/chunks/test.rb
@@ -0,0 +1,18 @@
+require 'test/unit'
+
+class ChunkTest < Test::Unit::TestCase
+
+  # Asserts a number of tests for the given type and text.
+  def match(type, test_text, expected)
+	pattern = type.pattern
+    assert_match(pattern, test_text)
+    pattern =~ test_text   # Previous assertion guarantees match
+    chunk = type.new($~)
+    
+    # Test if requested parts are correct.
+    for method_sym, value in expected do
+      assert_respond_to(chunk, method_sym)
+      assert_equal(value, chunk.method(method_sym).call, "Checking value of '#{method_sym}'")
+    end
+  end
+end
diff --git a/lib/chunks/uri.rb b/lib/chunks/uri.rb
new file mode 100644
index 00000000..1a208535
--- /dev/null
+++ b/lib/chunks/uri.rb
@@ -0,0 +1,182 @@
+require 'chunks/chunk'
+
+# This wiki chunk matches arbitrary URIs, using patterns from the Ruby URI modules.
+# It parses out a variety of fields that could be used by renderers to format
+# the links in various ways (shortening domain names, hiding email addresses)
+# It matches email addresses and host.com.au domains without schemes (http://)
+# but adds these on as required.
+#
+# The heuristic used to match a URI is designed to err on the side of caution.
+# That is, it is more likely to not autolink a URI than it is to accidently
+# autolink something that is not a URI. The reason behind this is it is easier
+# to force a URI link by prefixing 'http://' to it than it is to escape and
+# incorrectly marked up non-URI.
+#
+# I'm using a part of the [ISO 3166-1 Standard][iso3166] for country name suffixes.
+# The generic names are from www.bnoack.com/data/countrycode2.html)
+#   [iso3166]: http://geotags.com/iso3166/
+
+class URIChunk < Chunk::Abstract
+  include URI::REGEXP::PATTERN
+
+  # this condition is to get rid of pesky warnings in tests
+  unless defined? URIChunk::INTERNET_URI_REGEXP
+
+    GENERIC = 'aero|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org'
+    
+    COUNTRY = 'ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|az|ba|bb|bd|be|' + 
+      'bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cf|cd|cg|ch|ci|ck|cl|' + 
+      'cm|cn|co|cr|cs|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|fi|' + 
+      'fj|fk|fm|fo|fr|fx|ga|gb|gd|ge|gf|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|' + 
+      'hk|hm|hn|hr|ht|hu|id|ie|il|in|io|iq|ir|is|it|jm|jo|jp|ke|kg|kh|ki|km|kn|' + 
+      'kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|mg|mh|mk|ml|mm|' + 
+      'mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nt|' + 
+      'nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|pt|pw|py|qa|re|ro|ru|rw|sa|sb|sc|' + 
+      'sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|' + 
+      'tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|' + 
+      'ws|ye|yt|yu|za|zm|zr|zw'
+    # These are needed otherwise HOST will match almost anything
+    TLDS = "(?:#{GENERIC}|#{COUNTRY})"
+    
+    # Redefine USERINFO so that it must have non-zero length
+    USERINFO = "(?:[#{UNRESERVED};:&=+$,]|#{ESCAPED})+"
+  
+    # unreserved_no_ending = alphanum | mark, but URI_ENDING [)!] excluded
+    UNRESERVED_NO_ENDING = "-_.~*'(#{ALNUM}"  
+
+    # this ensures that query or fragment do not end with URI_ENDING
+    # and enable us to use a much simpler self.pattern Regexp
+
+    # uric_no_ending = reserved | unreserved_no_ending | escaped
+    URIC_NO_ENDING = "(?:[#{UNRESERVED_NO_ENDING}#{RESERVED}]|#{ESCAPED})"
+    # query = *uric
+    QUERY = "#{URIC_NO_ENDING}*"
+    # fragment = *uric
+    FRAGMENT = "#{URIC_NO_ENDING}*"
+
+    # DOMLABEL is defined in the ruby uri library, TLDS is defined above
+    INTERNET_HOSTNAME = "(?:#{DOMLABEL}\\.)+#{TLDS}" 
+
+    # Correct a typo bug in ruby 1.8.x lib/uri/common.rb 
+    PORT = '\\d*'
+
+    INTERNET_URI =
+        "(?:(#{SCHEME}):/{0,2})?" +   # Optional scheme:        (\1)
+        "(?:(#{USERINFO})@)?" +       # Optional userinfo@      (\2)
+        "(#{INTERNET_HOSTNAME})" +    # Mandatory hostname      (\3)
+        "(?::(#{PORT}))?" +           # Optional :port          (\4)
+        "(#{ABS_PATH})?"  +           # Optional absolute path  (\5)
+        "(?:\\?(#{QUERY}))?" +        # Optional ?query         (\6)
+        "(?:\\#(#{FRAGMENT}))?"  +    # Optional #fragment      (\7)
+        '(?=\.?(?:\s|\)|\z))'         # ends only with optional dot + space or ")" 
+                                      # or end of the string
+
+    SUSPICIOUS_PRECEDING_CHARACTER = '(!|\"\:|\"|\\\'|\]\()?'  # any of !, ":, ", ', ](
+  
+    INTERNET_URI_REGEXP = 
+        Regexp.new(SUSPICIOUS_PRECEDING_CHARACTER + INTERNET_URI, Regexp::EXTENDED, 'N')
+
+  end
+
+  def URIChunk.pattern
+    INTERNET_URI_REGEXP
+  end
+
+  attr_reader :user, :host, :port, :path, :query, :fragment, :link_text
+  
+  def self.apply_to(content)
+    content.gsub!( self.pattern ) do |matched_text|
+      chunk = self.new($~, content)
+      if chunk.avoid_autolinking?
+        # do not substitute nor register the chunk
+        matched_text
+      else
+        content.add_chunk(chunk)
+        chunk.mask
+      end
+    end
+  end
+
+  def initialize(match_data, content)
+    super
+    @link_text = match_data[0]
+    @suspicious_preceding_character = match_data[1]
+    @original_scheme, @user, @host, @port, @path, @query, @fragment = match_data[2..-1]
+    treat_trailing_character
+    @unmask_text = "#{link_text}"
+  end
+
+  def avoid_autolinking?
+    not @suspicious_preceding_character.nil?
+  end
+
+  def treat_trailing_character
+    # If the last character matched by URI pattern is in ! or ), this may be part of the markup,
+    # not a URL. We should handle it as such. It is possible to do it by a regexp, but 
+    # much easier to do programmatically
+    last_char = @link_text[-1..-1]
+    if last_char == ')' or last_char == '!'
+      @trailing_punctuation = last_char
+      @link_text.chop!
+      [@original_scheme, @user, @host, @port, @path, @query, @fragment].compact.last.chop!
+    else 
+      @trailing_punctuation = nil
+    end
+  end
+
+  def scheme
+    @original_scheme or (@user ? 'mailto' : 'http')
+  end
+
+  def scheme_delimiter
+    scheme == 'mailto' ? ':' : '://'
+  end
+
+  def user_delimiter
+     '@' unless @user.nil?
+  end
+
+  def port_delimiter
+     ':' unless @port.nil?
+  end
+
+  def query_delimiter
+     '?' unless @query.nil?
+  end
+
+  def uri
+    [scheme, scheme_delimiter, user, user_delimiter, host, port_delimiter, port, path, 
+      query_delimiter, query].compact.join
+  end
+
+end
+
+# uri with mandatory scheme but less restrictive hostname, like
+# http://localhost:2500/blah.html
+class LocalURIChunk < URIChunk
+
+  unless defined? LocalURIChunk::LOCAL_URI_REGEXP
+    # hostname can be just a simple word like 'localhost'
+    ANY_HOSTNAME = "(?:#{DOMLABEL}\\.)*#{TOPLABEL}\\.?"
+    
+    # The basic URI expression as a string
+    # Scheme and hostname are mandatory
+    LOCAL_URI =
+        "(?:(#{SCHEME})://)+" +       # Mandatory scheme://     (\1)
+        "(?:(#{USERINFO})@)?" +       # Optional userinfo@      (\2)
+        "(#{ANY_HOSTNAME})" +         # Mandatory hostname      (\3)
+        "(?::(#{PORT}))?" +           # Optional :port          (\4)
+        "(#{ABS_PATH})?"  +           # Optional absolute path  (\5)
+        "(?:\\?(#{QUERY}))?" +        # Optional ?query         (\6)
+        "(?:\\#(#{FRAGMENT}))?" +     # Optional #fragment      (\7)
+        '(?=\.?(?:\s|\)|\z))'         # ends only with optional dot + space or ")" 
+                                      # or end of the string
+  
+    LOCAL_URI_REGEXP = Regexp.new(SUSPICIOUS_PRECEDING_CHARACTER + LOCAL_URI, Regexp::EXTENDED, 'N')
+  end
+
+  def LocalURIChunk.pattern
+    LOCAL_URI_REGEXP
+  end
+
+end
diff --git a/lib/chunks/wiki.rb b/lib/chunks/wiki.rb
new file mode 100644
index 00000000..840f644a
--- /dev/null
+++ b/lib/chunks/wiki.rb
@@ -0,0 +1,141 @@
+require 'wiki_words'
+require 'chunks/chunk'
+require 'chunks/wiki'
+require 'cgi'
+
+# Contains all the methods for finding and replacing wiki related links.
+module WikiChunk
+  include Chunk
+
+  # A wiki reference is the top-level class for anything that refers to
+  # another wiki page.
+  class WikiReference < Chunk::Abstract
+
+    # Name of the referenced page
+    attr_reader :page_name
+    
+    # the referenced page
+    def refpage
+      @content.web.pages[@page_name]
+    end
+  
+  end
+
+  # A wiki link is the top-level class for links that refers to
+  # another wiki page.
+  class WikiLink < WikiReference
+ 
+    attr_reader :link_text, :link_type
+
+    def initialize(match_data, content)
+      super
+      @link_type = :show
+    end
+
+    def self.apply_to(content)
+      content.gsub!( self.pattern ) do |matched_text|
+        chunk = self.new($~, content)
+        if chunk.textile_url?
+          # do not substitute
+          matched_text
+        else
+          content.add_chunk(chunk)
+          chunk.mask
+        end
+      end
+    end
+
+    # the referenced page
+    def refpage
+      @content.web.pages[@page_name]
+    end
+
+    def textile_url?
+      not @textile_link_suffix.nil?
+    end
+
+  end
+
+  # This chunk matches a WikiWord. WikiWords can be escaped
+  # by prepending a '\'. When this is the case, the +escaped_text+
+  # method will return the WikiWord instead of the usual +nil+.
+  # The +page_name+ method returns the matched WikiWord.
+  class Word < WikiLink
+
+    attr_reader :escaped_text
+    
+    unless defined? WIKI_WORD
+      WIKI_WORD = Regexp.new('(":)?(\\\\)?(' + WikiWords::WIKI_WORD_PATTERN + ')\b', 0, "utf-8")
+    end
+
+    def self.pattern
+      WIKI_WORD
+    end
+
+    def initialize(match_data, content)
+      super
+      @textile_link_suffix, @escape, @page_name = match_data[1..3]
+      if @escape 
+        @unmask_mode = :escape
+        @escaped_text = @page_name
+      else
+        @escaped_text = nil
+      end
+      @link_text = WikiWords.separate(@page_name)
+      @unmask_text = (@escaped_text || @content.page_link(@page_name, @link_text, @link_type))
+    end
+
+  end
+
+  # This chunk handles [[bracketted wiki words]] and 
+  # [[AliasedWords|aliased wiki words]]. The first part of an
+  # aliased wiki word must be a WikiWord. If the WikiWord
+  # is aliased, the +link_text+ field will contain the
+  # alias, otherwise +link_text+ will contain the entire
+  # contents within the double brackets.
+  #
+  # NOTE: This chunk must be tested before WikiWord since
+  #       a WikiWords can be a substring of a WikiLink. 
+  class Link < WikiLink
+    
+    unless defined? WIKI_LINK
+      WIKI_LINK = /(":)?\[\[\s*([^\]\s][^\]]+?)\s*\]\]/
+      LINK_TYPE_SEPARATION = Regexp.new('^(.+):((file)|(pic))$', 0, 'utf-8')
+      ALIAS_SEPARATION = Regexp.new('^(.+)\|(.+)$', 0, 'utf-8')
+    end    
+        
+    def self.pattern() WIKI_LINK end
+
+    def initialize(match_data, content)
+      super
+      @textile_link_suffix, @page_name = match_data[1..2]
+      @link_text = @page_name
+      separate_link_type
+      separate_alias
+      @unmask_text = @content.page_link(@page_name, @link_text, @link_type)
+    end
+
+    private
+
+    # if link wihin the brackets has a form of [[filename:file]] or [[filename:pic]], 
+    # this means a link to a picture or a file
+    def separate_link_type
+      link_type_match = LINK_TYPE_SEPARATION.match(@page_name)
+      if link_type_match
+        @link_text = @page_name = link_type_match[1]
+        @link_type = link_type_match[2..3].compact[0].to_sym
+      end
+    end
+
+    # link text may be different from page name. this will look like [[actual page|link text]]
+    def separate_alias
+      alias_match = ALIAS_SEPARATION.match(@page_name)
+      if alias_match
+        @page_name, @link_text = alias_match[1..2]
+      end
+      # note that [[filename|link text:file]] is also supported
+    end  
+  
+  end
+  
+end

From 26c046cdfa1d62151c4fe271590b37f97037ec0a Mon Sep 17 00:00:00 2001
From: Rick Okin 
Date: Tue, 9 Aug 2005 02:20:28 +0000
Subject: [PATCH 12/84] move to AR

---
 Rakefile                                 | 202 +++++++++++++++
 app/controllers/admin_controller.rb      |   2 +-
 app/controllers/application.rb           |   6 +-
 app/controllers/file_controller.rb       |   2 +-
 app/controllers/wiki_controller.rb       |   7 +-
 app/models/page.rb                       | 125 ++++++++-
 app/models/revision.rb                   | 121 ++++++++-
 app/models/system.rb                     |   4 +
 app/models/web.rb                        | 174 ++++++++++++-
 app/views/wiki/list.rhtml                |   2 +-
 app/views/wiki/page.rhtml                |   2 +-
 config/database.yml                      |  81 ++++--
 config/environment.rb                    |   2 +
 config/environments/development.rb       |   1 -
 config/environments/test.rb              |  13 +-
 db/development_structure.sql             |  40 +++
 db/pages.erbsql                          |   7 +-
 db/revisions.erbsql                      |  15 +-
 db/schema.postgre.sql                    |  43 ++++
 db/system.erbsql                         |   4 +
 db/webs.erbsql                           |  14 +-
 lib/author.rb                            |  18 ++
 lib/chunks/test.rb                       |   2 +-
 lib/db_structure.rb                      |  14 +
 lib/file_yard.rb                         |  59 +++++
 lib/page_set.rb                          |  89 +++++++
 lib/wiki.rb                              |  96 +++++++
 lib/wiki_content.rb                      | 206 +++++++++++++++
 lib/wiki_words.rb                        |  23 ++
 rakefile.rb                              | 134 ----------
 script/console                           |  23 ++
 script/create_db                         |   2 +-
 script/server                            | 100 ++------
 test/fixtures/pages.yml                  |  56 +++-
 test/fixtures/revisions.yml              |  84 +++++-
 test/fixtures/system.yml                 |   2 +
 test/fixtures/webs.yml                   |  13 +-
 test/functional/admin_controller_test.rb | 113 ++++----
 test/functional/application_test.rb      |  23 +-
 test/functional/file_controller_test.rb  |  39 +--
 test/functional/routes_test.rb           |   2 +-
 test/functional/wiki_controller_test.rb  | 242 ++++++++----------
 test/test_helper.rb                      |  90 ++++++-
 test/unit/diff_test.rb                   |   4 +-
 test/unit/file_yard_test.rb              |   4 +-
 test/unit/page_test.rb                   |  77 +++++-
 test/unit/redcloth_for_tex_test.rb       |   4 +-
 test/unit/revision_test.rb               | 312 ++++++++++++++++++++++-
 test/unit/uri_test.rb                    |   2 +-
 test/unit/web_test.rb                    | 157 +++++++++++-
 test/unit/wiki_words_test.rb             |   4 +-
 51 files changed, 2345 insertions(+), 516 deletions(-)
 create mode 100644 Rakefile
 create mode 100644 app/models/system.rb
 create mode 100644 db/development_structure.sql
 create mode 100644 db/schema.postgre.sql
 create mode 100644 db/system.erbsql
 create mode 100644 lib/author.rb
 create mode 100644 lib/file_yard.rb
 create mode 100644 lib/page_set.rb
 create mode 100644 lib/wiki.rb
 create mode 100644 lib/wiki_content.rb
 create mode 100644 lib/wiki_words.rb
 create mode 100644 script/console
 create mode 100644 test/fixtures/system.yml

diff --git a/Rakefile b/Rakefile
new file mode 100644
index 00000000..5407e140
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,202 @@
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+$VERBOSE = nil
+TEST_CHANGES_SINCE = Time.now - 600
+
+desc "Run all the tests on a fresh test database"
+task :default => [ :test_units, :test_functional ]
+
+
+desc 'Require application environment.'
+task :environment do
+  unless defined? RAILS_ROOT
+    require File.dirname(__FILE__) + '/config/environment'
+  end
+end
+
+desc "Generate API documentation, show coding stats"
+task :doc => [ :appdoc, :stats ]
+
+
+# Look up tests for recently modified sources.
+def recent_tests(source_pattern, test_path, touched_since = 10.minutes.ago)
+  FileList[source_pattern].map do |path|
+    if File.mtime(path) > touched_since
+      test = "#{test_path}/#{File.basename(path, '.rb')}_test.rb"
+      test if File.exists?(test)
+    end
+  end.compact
+end
+
+desc 'Test recent changes.'
+Rake::TestTask.new(:recent => [ :clone_structure_to_test ]) do |t|
+  since = TEST_CHANGES_SINCE
+  touched = FileList['test/**/*_test.rb'].select { |path| File.mtime(path) > since } +
+    recent_tests('app/models/*.rb', 'test/unit', since) +
+    recent_tests('app/controllers/*.rb', 'test/functional', since)
+
+  t.libs << 'test'
+  t.verbose = true
+  t.test_files = touched.uniq
+end
+task :test_recent => [ :clone_structure_to_test ]
+
+desc "Run the unit tests in test/unit"
+Rake::TestTask.new("test_units") { |t|
+  t.libs << "test"
+  t.pattern = 'test/unit/**/*_test.rb'
+  t.verbose = true
+}
+task :test_units => [ :clone_structure_to_test ]
+
+desc "Run the functional tests in test/functional"
+Rake::TestTask.new("test_functional") { |t|
+  t.libs << "test"
+  t.pattern = 'test/functional/**/*_test.rb'
+  t.verbose = true
+}
+task :test_functional => [ :clone_structure_to_test ]
+
+desc "Generate documentation for the application"
+Rake::RDocTask.new("appdoc") { |rdoc|
+  rdoc.rdoc_dir = 'doc/app'
+  rdoc.title    = "Rails Application Documentation"
+  rdoc.options << '--line-numbers --inline-source'
+  rdoc.rdoc_files.include('doc/README_FOR_APP')
+  rdoc.rdoc_files.include('app/**/*.rb')
+}
+
+desc "Generate documentation for the Rails framework"
+Rake::RDocTask.new("apidoc") { |rdoc|
+  rdoc.rdoc_dir = 'doc/api'
+  rdoc.template = "#{ENV['template']}.rb" if ENV['template']
+  rdoc.title    = "Rails Framework Documentation"
+  rdoc.options << '--line-numbers --inline-source'
+  rdoc.rdoc_files.include('README')
+  rdoc.rdoc_files.include('CHANGELOG')
+  rdoc.rdoc_files.include('vendor/rails/railties/CHANGELOG')
+  rdoc.rdoc_files.include('vendor/rails/railties/MIT-LICENSE')
+  rdoc.rdoc_files.include('vendor/rails/activerecord/README')
+  rdoc.rdoc_files.include('vendor/rails/activerecord/CHANGELOG')
+  rdoc.rdoc_files.include('vendor/rails/activerecord/lib/active_record/**/*.rb')
+  rdoc.rdoc_files.exclude('vendor/rails/activerecord/lib/active_record/vendor/*')
+  rdoc.rdoc_files.include('vendor/rails/actionpack/README')
+  rdoc.rdoc_files.include('vendor/rails/actionpack/CHANGELOG')
+  rdoc.rdoc_files.include('vendor/rails/actionpack/lib/action_controller/**/*.rb')
+  rdoc.rdoc_files.include('vendor/rails/actionpack/lib/action_view/**/*.rb')
+  rdoc.rdoc_files.include('vendor/rails/actionmailer/README')
+  rdoc.rdoc_files.include('vendor/rails/actionmailer/CHANGELOG')
+  rdoc.rdoc_files.include('vendor/rails/actionmailer/lib/action_mailer/base.rb')
+  rdoc.rdoc_files.include('vendor/rails/actionwebservice/README')
+  rdoc.rdoc_files.include('vendor/rails/actionwebservice/CHANGELOG')
+  rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service.rb')
+  rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/*.rb')
+  rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/api/*.rb')
+  rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/client/*.rb')
+  rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/container/*.rb')
+  rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/dispatcher/*.rb')
+  rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/protocol/*.rb')
+  rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/support/*.rb')
+  rdoc.rdoc_files.include('vendor/rails/activesupport/README')
+  rdoc.rdoc_files.include('vendor/rails/activesupport/CHANGELOG')
+  rdoc.rdoc_files.include('vendor/rails/activesupport/lib/active_support/**/*.rb')
+}
+
+desc "Report code statistics (KLOCs, etc) from the application"
+task :stats => [ :environment ] do
+  require 'code_statistics'
+  CodeStatistics.new(
+    ["Helpers", "app/helpers"], 
+    ["Controllers", "app/controllers"], 
+    ["APIs", "app/apis"],
+    ["Components", "components"],
+    ["Functionals", "test/functional"],
+    ["Models", "app/models"],
+    ["Units", "test/unit"]
+  ).to_s
+end
+
+desc "Recreate the test databases from the development structure"
+task :clone_structure_to_test => [ :db_structure_dump, :purge_test_database ] do
+  abcs = ActiveRecord::Base.configurations
+  case abcs["test"]["adapter"]
+    when  "mysql"
+      ActiveRecord::Base.establish_connection(:test)
+      ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0')
+      IO.readlines("db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |table|
+        ActiveRecord::Base.connection.execute(table)
+      end
+    when "postgresql"
+      ENV['PGHOST']     = abcs["test"]["host"] if abcs["test"]["host"]
+      ENV['PGPORT']     = abcs["test"]["port"].to_s if abcs["test"]["port"]
+      ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"]
+      `psql -U "#{abcs["test"]["username"]}" -f db/#{RAILS_ENV}_structure.sql #{abcs["test"]["database"]}`
+    when "sqlite", "sqlite3"
+      `#{abcs[RAILS_ENV]["adapter"]} #{abcs["test"]["dbfile"]} < db/#{RAILS_ENV}_structure.sql`
+    when "sqlserver"
+      `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql`
+    else 
+      raise "Unknown database adapter '#{abcs["test"]["adapter"]}'"
+  end
+end
+
+desc "Dump the database structure to a SQL file"
+task :db_structure_dump => :environment do
+  abcs = ActiveRecord::Base.configurations
+  case abcs[RAILS_ENV]["adapter"] 
+    when "mysql"
+      ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
+      File.open("db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
+    when "postgresql"
+      ENV['PGHOST']     = abcs[RAILS_ENV]["host"] if abcs[RAILS_ENV]["host"]
+      ENV['PGPORT']     = abcs[RAILS_ENV]["port"].to_s if abcs[RAILS_ENV]["port"]
+      ENV['PGPASSWORD'] = abcs[RAILS_ENV]["password"].to_s if abcs[RAILS_ENV]["password"]
+      `pg_dump -U "#{abcs[RAILS_ENV]["username"]}" -s -x -O -f db/#{RAILS_ENV}_structure.sql #{abcs[RAILS_ENV]["database"]}`
+    when "sqlite", "sqlite3"
+      `#{abcs[RAILS_ENV]["adapter"]} #{abcs[RAILS_ENV]["dbfile"]} .schema > db/#{RAILS_ENV}_structure.sql`
+    when "sqlserver"
+      `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /f db\\#{RAILS_ENV}_structure.sql /q /A /r`
+      `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /F db\ /q /A /r`
+    else 
+      raise "Unknown database adapter '#{abcs["test"]["adapter"]}'"
+  end
+end
+
+desc "Empty the test database"
+task :purge_test_database => :environment do
+  abcs = ActiveRecord::Base.configurations
+  case abcs["test"]["adapter"]
+    when "mysql"
+      ActiveRecord::Base.establish_connection(:test)
+      ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"])
+    when "postgresql"
+      ENV['PGHOST']     = abcs["test"]["host"] if abcs["test"]["host"]
+      ENV['PGPORT']     = abcs["test"]["port"].to_s if abcs["test"]["port"]
+      ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"]
+      `dropdb -U "#{abcs["test"]["username"]}" #{abcs["test"]["database"]}`
+      `createdb -T template0 -U "#{abcs["test"]["username"]}" #{abcs["test"]["database"]}`
+    when "sqlite","sqlite3"
+      File.delete(abcs["test"]["dbfile"]) if File.exist?(abcs["test"]["dbfile"])
+    when "sqlserver"
+      dropfkscript = "#{abcs["test"]["host"]}.#{abcs["test"]["database"]}.DP1".gsub(/\\/,'-')
+      `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{dropfkscript}`
+      `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql`
+    else 
+      raise "Unknown database adapter '#{abcs["test"]["adapter"]}'"
+  end
+end
+
+desc "Clears all *.log files in log/"
+task :clear_logs => :environment do
+  FileList["log/*.log"].each do |log_file|
+    f = File.open(log_file, "w")
+    f.close
+  end
+end
+
+desc "Migrate the database according to the migrate scripts in db/migrate (only supported on PG/MySQL). A specific version can be targetted with VERSION=x"
+task :migrate => :environment do
+  ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/db/migrate/', ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
+end
\ No newline at end of file
diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb
index 6bca0f15..a52b2005 100644
--- a/app/controllers/admin_controller.rb
+++ b/app/controllers/admin_controller.rb
@@ -46,7 +46,6 @@ class AdminController < ApplicationController
   end
 
   def edit_web
-
     system_password = @params['system_password']
     if system_password
       # form submitted
@@ -67,6 +66,7 @@ class AdminController < ApplicationController
           flash[:info] = "Web '#{@params['address']}' was successfully updated"
           redirect_home(@params['address'])
         rescue Instiki::ValidationError => e
+          logger.warn e.message
           @error = e.message
           # and re-render the same template again
         end
diff --git a/app/controllers/application.rb b/app/controllers/application.rb
index 1e2af4e6..7f608e49 100644
--- a/app/controllers/application.rb
+++ b/app/controllers/application.rb
@@ -10,12 +10,12 @@ class ApplicationController < ActionController::Base
     # a global variable is used here because Rails reloads controller and model classes in the 
     # development environment; therefore, storing it as a class variable does not work
     # class variable is, anyway, not much different from a global variable
-    $instiki_wiki_service = the_wiki
+    #$instiki_wiki_service = the_wiki
     logger.debug("Wiki service: #{the_wiki.to_s}")
   end
 
   def self.wiki
-    $instiki_wiki_service
+    Wiki.new
   end
 
   protected
@@ -146,7 +146,7 @@ class ApplicationController < ActionController::Base
   end
 
   def wiki
-    $instiki_wiki_service
+    self.class.wiki
   end
 
   def needs_authorization?(action)
diff --git a/app/controllers/file_controller.rb b/app/controllers/file_controller.rb
index bf0ee964..e67ee3a2 100644
--- a/app/controllers/file_controller.rb
+++ b/app/controllers/file_controller.rb
@@ -78,7 +78,7 @@ class FileController < ApplicationController
       return false
     end
 
-    unless @web.allow_uploads
+    unless @web.allow_uploads?
       render_text 'File uploads are blocked by the webmaster', '403 Forbidden'
       return false
     end
diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb
index 43d55b2e..c274e209 100644
--- a/app/controllers/wiki_controller.rb
+++ b/app/controllers/wiki_controller.rb
@@ -1,4 +1,3 @@
-require 'application'
 require 'fileutils'
 require 'redcloth_for_tex'
 require 'parsedate'
@@ -156,7 +155,7 @@ class WikiController < ApplicationController
   end
 
   def published
-    if @web.published
+    if @web.published?
       @page = wiki.read_page(@web_name, @page_name || 'HomePage') 
     else 
       redirect_home
@@ -270,7 +269,7 @@ class WikiController < ApplicationController
   end
 
   def export_web_to_tex(file_path)
-    @tex_content = table_of_contents(@web.pages['HomePage'].content, render_tex_web)
+    @tex_content = table_of_contents(@web.page('HomePage').content, render_tex_web)
     File.open(file_path, 'w') { |f| f.write(render_to_string('wiki/tex_web')) }
   end
 
@@ -342,7 +341,7 @@ class WikiController < ApplicationController
   end
   
   def rss_with_content_allowed?
-    @web.password.nil? or @web.published
+    @web.password.nil? or @web.published?
   end
   
   def truncate(text, length = 30, truncate_string = '...')
diff --git a/app/models/page.rb b/app/models/page.rb
index 5f85c8ab..6baa5802 100644
--- a/app/models/page.rb
+++ b/app/models/page.rb
@@ -1,4 +1,125 @@
 class Page < ActiveRecord::Base
   belongs_to :web
-  has_many :pages
-end
\ No newline at end of file
+  has_many :revisions, :order => 'number'
+  has_one :current_revision, :class_name => 'Revision', :order => 'number DESC'
+    
+  def revise(content, created_at, author)
+    revisions_size = new_record? ? 0 : revisions.size
+    if (revisions_size > 0) and content == current_revision.content
+      raise Instiki::ValidationError.new(
+          "You have tried to save page '#{name}' without changing its content")
+    end
+    
+    author = Author.new(author.to_s) unless author.is_a?(Author)
+
+    # Try to render content to make sure that markup engine can take it,
+    # before addin a revision to the page
+    Revision.new(:page => self, :content => content, :created_at => created_at, :author => author).force_rendering
+
+    # A user may change a page, look at it and make some more changes - several times.
+    # Not to record every such iteration as a new revision, if the previous revision was done 
+    # by the same author, not more than 30 minutes ago, then update the last revision instead of
+    # creating a new one
+    if (revisions_size > 0) && continous_revision?(created_at, author)
+      current_revision.update_attributes(:created_at => created_at, :content => content)
+    else
+      Revision.create(:page => self, :content => content, :created_at => created_at, :author => author)
+    end
+    
+    self.created_at = created_at
+    save
+    web.refresh_pages_with_references(name) if revisions_size == 0
+    
+    self
+  end
+
+  def rollback(revision_number, created_at, author_ip = nil)
+    roll_back_revision = Revision.find(:first, :conditions => ['page_id = ? AND number = ?', id, revision_number])
+    revise(roll_back_revision.content, created_at, Author.new(roll_back_revision.author, author_ip))
+  end
+  
+  def revisions?
+    revisions.size > 1
+  end
+
+  def revised_on
+    created_on
+  end
+
+  def in_category?(cat)
+    cat.nil? || cat.empty? || categories.include?(cat)
+  end
+
+  def categories
+    display_content.find_chunks(Category).map { |cat| cat.list }.flatten
+  end
+
+  def authors
+    revisions.collect { |rev| rev.author }
+  end
+
+  def references
+    web.select.pages_that_reference(name)
+  end
+
+  def linked_from
+    web.select.pages_that_link_to(name)
+  end
+
+  def included_from
+    web.select.pages_that_include(name)
+  end
+
+  # Returns the original wiki-word name as separate words, so "MyPage" becomes "My Page".
+  def plain_name
+    web.brackets_only? ? name : WikiWords.separate(name)
+  end
+
+  # used to build chunk ids. 
+  #def id
+  #  @id ||= name.unpack('H*').first
+  #end
+
+  def link(options = {})
+    web.make_link(name, nil, options)
+  end
+
+  def author_link(options = {})
+    web.make_link(author, nil, options)
+  end
+
+  LOCKING_PERIOD = 30.minutes
+
+  def lock(time, locked_by)
+    update_attributes(:locked_at => time, :locked_by => locked_by)
+  end
+  
+  def lock_duration(time)
+    ((time - locked_at) / 60).to_i unless locked_at.nil?
+  end
+  
+  def unlock
+    update_attribute(:locked_at, nil)
+  end
+  
+  def locked?(comparison_time)
+    locked_at + LOCKING_PERIOD > comparison_time unless locked_at.nil?
+  end
+
+  private
+
+    def continous_revision?(created_at, author)
+      current_revision.author == author && current_revision.created_at + 30.minutes > created_at
+    end
+
+    # Forward method calls to the current revision, so the page responds to all revision calls
+    def method_missing(method_id, *args, &block)
+      method_name = method_id.to_s
+      # Perform a hand-off to AR::Base#method_missing
+      if @attributes.include?(method_name) or md = /(=|\?|_before_type_cast)$/.match(method_name)
+        super(method_id, *args, &block)
+      else
+        current_revision.send(method_id)
+      end
+    end
+end
diff --git a/app/models/revision.rb b/app/models/revision.rb
index 017b3543..ca886245 100644
--- a/app/models/revision.rb
+++ b/app/models/revision.rb
@@ -1,3 +1,122 @@
+require 'diff'
 class Revision < ActiveRecord::Base
   belongs_to :page
-end
\ No newline at end of file
+  composed_of :author, :mapping => [ %w(author name), %w(ip ip) ]
+
+  def created_on
+    created_at.to_date
+  end
+
+  def pretty_created_at
+    # Must use DateTime because Time doesn't support %e on at least some platforms
+    DateTime.new(
+      created_at.year, created_at.mon, created_at.day, created_at.hour, created_at.min
+    ).strftime "%B %e, %Y %H:%M" 
+  end
+
+  # todo: drop next_revision, previuous_revision and number from here - unused code
+  def next_revision
+    Revision.find_by_number_and_page_id(number+1, page_id)
+  end
+
+  def previous_revision
+    @previous_revions ||= number > 0 ? Revision.find_by_number_and_page_id(number-1, page_id) : nil
+  end
+
+  # Returns an array of all the WikiIncludes present in the content of this revision.
+  def wiki_includes
+    unless @wiki_includes_cache 
+      chunks = display_content.find_chunks(Include)
+      @wiki_includes_cache = chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq
+    end
+    @wiki_includes_cache
+  end  
+
+  # Returns an array of all the WikiReferences present in the content of this revision.
+  def wiki_references
+    unless @wiki_references_cache 
+      chunks = display_content.find_chunks(WikiChunk::WikiReference)
+      @wiki_references_cache = chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq
+    end
+    @wiki_references_cache
+  end  
+
+  # Returns an array of all the WikiWords present in the content of this revision.
+  def wiki_words
+    unless @wiki_words_cache
+      wiki_chunks = display_content.find_chunks(WikiChunk::WikiLink)
+      @wiki_words_cache = wiki_chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq
+    end
+    @wiki_words_cache
+  end
+
+  # Returns an array of all the WikiWords present in the content of this revision.
+  # that already exists as a page in the web.
+  def existing_pages
+    wiki_words.select { |wiki_word| page.web.page(wiki_word) }
+  end
+
+  # Returns an array of all the WikiWords present in the content of this revision
+  # that *doesn't* already exists as a page in the web.
+  def unexisting_pages
+    wiki_words - existing_pages
+  end  
+
+  # Explicit check for new type of display cache with chunks_by_type method.
+  # Ensures new version works with older snapshots.
+  def display_content
+    unless @display_cache && @display_cache.respond_to?(:chunks_by_type)
+      @display_cache = WikiContent.new(self)
+      @display_cache.render!
+    end
+    @display_cache
+  end
+
+  def display_diff
+    previous_revision ? HTMLDiff.diff(previous_revision.display_content, display_content) : display_content
+  end
+
+  def clear_display_cache
+    @wiki_words_cache = @published_cache = @display_cache = @wiki_includes_cache = 
+      @wiki_references_cache = nil
+  end
+
+  def display_published
+    unless @published_cache && @published_cache.respond_to?(:chunks_by_type)
+      @published_cache = WikiContent.new(self, {:mode => :publish})
+      @published_cache.render!
+    end
+    @published_cache
+  end
+
+  def display_content_for_export
+    WikiContent.new(self, {:mode => :export} ).render!
+  end
+  
+  def force_rendering
+    begin
+      display_content.render!
+    rescue => e
+      logger.error "Failed rendering page #{@name}"
+      logger.error e
+      message = e.message
+      # substitute content with an error message
+      self.content = <<-EOL
+          

Markup engine has failed to render this page, raising the following error:

+

#{message}

+
#{self.content}
+ EOL + clear_display_cache + raise e + end + end + + protected + before_create :set_revision_number + after_create :force_rendering + after_save :clear_display_cache + + def set_revision_number + self.number = self.class.count(['page_id = ?', page_id]) + 1 + end +end diff --git a/app/models/system.rb b/app/models/system.rb new file mode 100644 index 00000000..7ac1ad08 --- /dev/null +++ b/app/models/system.rb @@ -0,0 +1,4 @@ +class System < ActiveRecord::Base + set_table_name 'system' + validates_presence_of :password +end \ No newline at end of file diff --git a/app/models/web.rb b/app/models/web.rb index 2c31bf8f..5ca077d5 100644 --- a/app/models/web.rb +++ b/app/models/web.rb @@ -1,3 +1,173 @@ +require 'cgi' + class Web < ActiveRecord::Base - has_many :pages -end \ No newline at end of file + has_many :pages#, :include => [:current_revision, :web] + + def wiki + Wiki.new + end + + def file_yard + @file_yard ||= FileYard.new("#{Wiki.storage_path}/#{address}", max_upload_size) + end + + def settings_changed?(markup, safe_mode, brackets_only) + self.markup != markup || + self.safe_mode != safe_mode || + self.brackets_only != brackets_only + end + + def add_page(name, content, created_at, author) + page = page(name) || Page.new(:web => self, :name => name) + page.revise(content, created_at, author) + end + + def authors + select.authors + end + + def categories + select.map { |page| page.categories }.flatten.uniq.sort + end + + def page(name) + pages.find(:first, :conditions => ['name = ?', name]) + end + + def has_page?(name) + Page.count(['web_id = ? AND name = ?', id, name]) > 0 + end + + def has_file?(name) + wiki.file_yard(self).has_file?(name) + end + + def markup + read_attribute('markup').to_sym + end + + def make_file_link(mode, name, text, base_url) + link = CGI.escape(name) + case mode + when :export + if has_file?(name) then "#{text}" + else "#{text}" end + when :publish + if has_file?(name) then "#{text}" + else "#{text}" end + else + if has_file?(name) + "#{text}" + else + "#{text}?" + end + end + end + + # Create a link for the given page name and link text based + # on the render mode in options and whether the page exists + # in the this web. + # The links a relative, and will work only if displayed on another WikiPage. + # It should not be used in menus, templates and such - instead, use link_to_page helper + def make_link(name, text = nil, options = {}) + text = CGI.escapeHTML(text || WikiWords.separate(name)) + mode = options[:mode] || :show + base_url = options[:base_url] || '..' + link_type = options[:link_type] || :show + case link_type.to_sym + when :show + make_page_link(mode, name, text, base_url) + when :file + make_file_link(mode, name, text, base_url) + when :pic + make_pic_link(mode, name, text, base_url) + else + raise "Unknown link type: #{link_type}" + end + end + + def make_page_link(mode, name, text, base_url) + link = CGI.escape(name) + case mode.to_sym + when :export + if has_page?(name) then %{#{text}} + else %{#{text}} end + when :publish + if has_page?(name) then %{#{text}} + else %{#{text}} end + else + if has_page?(name) + %{#{text}} + else + %{#{text}?} + end + end + end + + def make_pic_link(mode, name, text, base_url) + link = CGI.escape(name) + case mode.to_sym + when :export + if has_file?(name) then %{#{text}} + else %{#{text}} end + when :publish + if has_file?(name) then %{#{text}} + else %{#{text}} end + else + if has_file?(name) then %{#{text}} + else %{#{text}?} end + end + end + + # Clears the display cache for all the pages with references to + def refresh_pages_with_references(page_name) + #select.pages_that_reference(page_name).each { |page| + # page.revisions.each { |revision| revision.clear_display_cache } + #} + end + + def refresh_revisions + select.each { |page| page.revisions.each { |revision| revision.clear_display_cache } } + end + + def remove_pages(pages_to_be_removed) + pages_to_be_removed.each { |p| p.destroy } + end + + def revised_on + select.most_recent_revision + end + + def select(&condition) + PageSet.new(self, pages, condition) + end + + private + + # Returns an array of all the wiki words in any current revision + def wiki_words + pages.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq + end + + # Returns an array of all the page names on this web + def page_names + pages.map { |p| p.name } + end + + protected + before_save :sanitize_markup + before_validation :validate_address + validates_uniqueness_of :address + validates_length_of :color, :in => 3..6 + + def sanitize_markup + self.markup = markup.to_s + end + + def validate_address + unless address == CGI.escape(address) + self.errors.add(:address, 'should contain only valid URI characters') + raise Instiki::ValidationError.new("#{self.class.human_attribute_name('address')} #{errors.on(:address)}") + end + end +end diff --git a/app/views/wiki/list.rhtml b/app/views/wiki/list.rhtml index 34a93798..d891f5a2 100644 --- a/app/views/wiki/list.rhtml +++ b/app/views/wiki/list.rhtml @@ -17,7 +17,7 @@ <% end %> -<% if @web.count_pages %> +<% if @web.count_pages? %> <% total_chars = @pages_in_category.characters %>

All content: <%= total_chars %> chars / <%= sprintf("%-.1f", (total_chars / 2275 )) %> pages

<% end %> diff --git a/app/views/wiki/page.rhtml b/app/views/wiki/page.rhtml index 8e9bc474..28763b78 100644 --- a/app/views/wiki/page.rhtml +++ b/app/views/wiki/page.rhtml @@ -25,7 +25,7 @@ <%= @page.revisions? ? "Revised" : "Created" %> on <%= @page.pretty_created_at %> by <%= @page.author_link %> <%= "(#{@page.author.ip})" if @page.author.respond_to?(:ip) %> - <% if @web.count_pages %> + <% if @web.count_pages? %> <% total_chars = @page.content.length %> (<%= total_chars %> characters / <%= sprintf("%-.1f", (total_chars / 2275 rescue 0)) %> pages) <% end %> diff --git a/config/database.yml b/config/database.yml index 1233b7e3..3dd03157 100644 --- a/config/database.yml +++ b/config/database.yml @@ -1,30 +1,65 @@ -# SQLite is enabled by default. Remember to change the dbfile path. production: adapter: sqlite3 - dbfile: /tmp/instiki_prod.db - -# Uncomment this section for MySQL: -# production: -# adapter: mysql -# host: localhost -# database: instiki -# username: instiki -# password: pass - -# Uncomment this section for PostgreSQL: -# production: -# adapter: postgresql -# host: localhost -# database: instiki -# username: instiki -# password: pass - -# The following settings are only used for testing and development. + dbfile: db/prod.db development: adapter: sqlite3 - dbfile: /tmp/instiki_dev.db - + dbfile: db/dev.db test: adapter: sqlite3 - dbfile: /tmp/instiki_test.db + dbfile: db/test.db + +# Sample Sqlite 2 Config +#production: +# adapter: sqlite +# dbfile: db/prod.db +#development: +# adapter: sqlite +# dbfile: db/dev.db +#test: +# adapter: sqlite +# dbfile: db/test.db + +# Sample Postgresql Config +#production: +# adapter: postgresql +# host: localhost +# database: instiki_prod +# username: user +# password: pass +# +#development: +# adapter: postgresql +# host: localhost +# database: instiki_dev +# username: user +# password: pass +# +#test: +# adapter: postgresql +# host: localhost +# database: instiki_test +# username: user +# password: pass + +# Sample MySQL Config +#production: +# adapter: mysql +# host: localhost +# database: instiki_prod +# username: user +# password: pass +# +#development: +# adapter: mysql +# host: localhost +# database: instiki_dev +# username: user +# password: pass +# +#test: +# adapter: mysql +# host: localhost +# database: instiki_test +# username: user +# password: pass diff --git a/config/environment.rb b/config/environment.rb index 589025c8..509f4ac0 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -60,6 +60,8 @@ require 'rubygems' unless File.directory?("#{RAILS_ROOT}/vendor/rails") require 'active_support' require 'active_record' require 'action_controller' +require 'action_mailer' +require 'action_web_service' # Environment-specific configuration. require_dependency "environments/#{RAILS_ENV}" diff --git a/config/environments/development.rb b/config/environments/development.rb index 61ce9fec..3f0868e6 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -2,4 +2,3 @@ Dependencies.mechanism = :require ActionController::Base.consider_all_requests_local = true ActionController::Base.perform_caching = false BREAKPOINT_SERVER_PORT = 42531 -$instiki_debug_logging = true diff --git a/config/environments/test.rb b/config/environments/test.rb index fac509a1..a5813f5d 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,15 +1,4 @@ Dependencies.mechanism = :require ActionController::Base.consider_all_requests_local = true ActionController::Base.perform_caching = false - -require 'fileutils' -FileUtils.mkdir_p(RAILS_ROOT + "/log") - -unless defined? TEST_LOGGER - timestamp = Time.now.strftime('%Y%m%d%H%M%S') - log_name = RAILS_ROOT + "/log/instiki_test.#{timestamp}.log" - $stderr.puts "To see the Rails log:\n less #{log_name}" - - TEST_LOGGER = ActionController::Base.logger = Logger.new(log_name) - $instiki_debug_logging = true -end +FileYard.restrict_upload_access = false \ No newline at end of file diff --git a/db/development_structure.sql b/db/development_structure.sql new file mode 100644 index 00000000..61d49c31 --- /dev/null +++ b/db/development_structure.sql @@ -0,0 +1,40 @@ +CREATE TABLE pages ( + id INTEGER PRIMARY KEY, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + web_id INTEGER NOT NULL, + locked_by VARCHAR(60), + name VARCHAR(60), + locked_at DATETIME +); +CREATE TABLE revisions ( + id INTEGER PRIMARY KEY, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + page_id INTEGER NOT NULL, + content TEXT NOT NULL, + author VARCHAR(60), + ip VARCHAR(60), + number INTEGER +); +CREATE TABLE system ( + id INTEGER PRIMARY KEY, + 'password' VARCHAR(60) +); +CREATE TABLE webs ( + id INTEGER PRIMARY KEY, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + name VARCHAR(60) NOT NULL, + address VARCHAR(60) NOT NULL, + 'password' VARCHAR(60), + additional_style VARCHAR(255), + allow_uploads INTEGER DEFAULT '1', + published INTEGER DEFAULT '0', + count_pages INTEGER DEFAULT '0', + markup VARCHAR(50) DEFAULT 'textile', + color VARCHAR(6) DEFAULT '008B26', + max_upload_size INTEGER DEFAULT 100, + safe_mode INTEGER DEFAULT '0', + brackets_only INTEGER DEFAULT '0' +); diff --git a/db/pages.erbsql b/db/pages.erbsql index afc57484..5c2dd6d5 100644 --- a/db/pages.erbsql +++ b/db/pages.erbsql @@ -2,5 +2,8 @@ CREATE TABLE pages ( id <%= @pk %>, created_at <%= @datetime %> NOT NULL, updated_at <%= @datetime %> NOT NULL, - web_id INTEGER NOT NULL -) <%= create_options %>; + web_id INTEGER NOT NULL, + locked_by VARCHAR(60), + name VARCHAR(60), + locked_at <%= @datetime %> +) <%= create_options %>; \ No newline at end of file diff --git a/db/revisions.erbsql b/db/revisions.erbsql index 1639cf2e..28034555 100644 --- a/db/revisions.erbsql +++ b/db/revisions.erbsql @@ -1,7 +1,10 @@ CREATE TABLE revisions ( - id <%= @pk %>, - created_at <%= @datetime %> NOT NULL, - updated_at <%= @datetime %> NOT NULL, - page_id INTEGER NOT NULL, - content TEXT NOT NULL -) <%= create_options %>; + id <%= @pk %>, + created_at <%= @datetime %> NOT NULL, + updated_at <%= @datetime %> NOT NULL, + page_id INTEGER NOT NULL, + content TEXT NOT NULL, + author VARCHAR(60), + ip VARCHAR(60), + number INTEGER +) <%= create_options %>; \ No newline at end of file diff --git a/db/schema.postgre.sql b/db/schema.postgre.sql new file mode 100644 index 00000000..5191d9e4 --- /dev/null +++ b/db/schema.postgre.sql @@ -0,0 +1,43 @@ +CREATE TABLE pages ( + id serial primary key, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + web_id integer NOT NULL, + locked_by character varying(60), + name character varying(60), + locked_at timestamp without time zone +); + +CREATE TABLE revisions ( + id serial primary key, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + page_id integer NOT NULL, + content text NOT NULL, + author character varying(60), + ip character varying(60), + number integer +); + +CREATE TABLE system ( + id serial primary key, + "password" character varying(60) +); + +CREATE TABLE webs ( + id serial primary key, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + name character varying(60) NOT NULL, + address character varying(60) NOT NULL, + "password" character varying(60), + additional_style character varying(255), + allow_uploads boolean DEFAULT true, + published boolean DEFAULT false, + count_pages boolean DEFAULT false, + markup character varying(50) DEFAULT 'textile'::character varying, + color character varying(6) DEFAULT '008B26'::character varying, + max_upload_size integer DEFAULT 100, + safe_mode boolean DEFAULT false, + brackets_only boolean DEFAULT false +); diff --git a/db/system.erbsql b/db/system.erbsql new file mode 100644 index 00000000..e0a55a33 --- /dev/null +++ b/db/system.erbsql @@ -0,0 +1,4 @@ +CREATE TABLE system ( + id <%= @pk %>, + <%= db_quote('password') %> VARCHAR(60) +) <%= create_options %>; \ No newline at end of file diff --git a/db/webs.erbsql b/db/webs.erbsql index bbb9758c..c29728cd 100644 --- a/db/webs.erbsql +++ b/db/webs.erbsql @@ -3,5 +3,15 @@ CREATE TABLE webs ( created_at <%= @datetime %> NOT NULL, updated_at <%= @datetime %> NOT NULL, name VARCHAR(60) NOT NULL, - address VARCHAR(60) NOT NULL -) <%= create_options %>; + address VARCHAR(60) NOT NULL, + <%= db_quote('password') %> VARCHAR(60), + additional_style VARCHAR(255), + allow_uploads <%= @boolean %> DEFAULT '1', + published <%= @boolean %> DEFAULT '0', + count_pages <%= @boolean %> DEFAULT '0', + markup VARCHAR(50) DEFAULT 'textile', + color VARCHAR(6) DEFAULT '008B26', + max_upload_size INTEGER DEFAULT 100, + safe_mode <%= @boolean %> DEFAULT '0', + brackets_only <%= @boolean %> DEFAULT '0' +) <%= create_options %>; \ No newline at end of file diff --git a/lib/author.rb b/lib/author.rb new file mode 100644 index 00000000..be8a5cf7 --- /dev/null +++ b/lib/author.rb @@ -0,0 +1,18 @@ +class Author < String + attr_accessor :ip + attr_reader :name + def initialize(name, ip = nil) + @ip = ip + super(name) + end + + def name=(value) + self.gsub!(/.+/, value) + end + + alias_method :name, :to_s + + def <=>(other) + name <=> other.to_s + end +end \ No newline at end of file diff --git a/lib/chunks/test.rb b/lib/chunks/test.rb index edf77d14..73af8142 100644 --- a/lib/chunks/test.rb +++ b/lib/chunks/test.rb @@ -4,7 +4,7 @@ class ChunkTest < Test::Unit::TestCase # Asserts a number of tests for the given type and text. def match(type, test_text, expected) - pattern = type.pattern + pattern = type.pattern assert_match(pattern, test_text) pattern =~ test_text # Previous assertion guarantees match chunk = type.new($~) diff --git a/lib/db_structure.rb b/lib/db_structure.rb index c7b58c33..09c212da 100644 --- a/lib/db_structure.rb +++ b/lib/db_structure.rb @@ -6,6 +6,17 @@ def create_options end end +def db_quote(column) + case @db + when 'postgresql' + return "\"#{column}\"" + when 'sqlite', 'sqlite3' + return "'#{column}'" + when 'mysql' + return "`#{column}`" + end +end + def db_structure(db) db.downcase! @db = db @@ -13,12 +24,15 @@ def db_structure(db) when 'postgresql' @pk = 'SERIAL PRIMARY KEY' @datetime = 'TIMESTAMP' + @boolean = "BOOLEAN" when 'sqlite', 'sqlite3' @pk = 'INTEGER PRIMARY KEY' @datetime = 'DATETIME' + @boolean = "INTEGER" when 'mysql' @pk = 'INTEGER UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY' @datetime = 'DATETIME' + @boolean = "TINYINT" @mysql_engine = 'InnoDB' else raise "Unknown db type #{db}" diff --git a/lib/file_yard.rb b/lib/file_yard.rb new file mode 100644 index 00000000..f9402c17 --- /dev/null +++ b/lib/file_yard.rb @@ -0,0 +1,59 @@ +require 'fileutils' +require 'instiki_errors' + +class FileYard + cattr_accessor :restrict_upload_access + restrict_upload_access = true + attr_reader :files_path + + def initialize(files_path, max_upload_size) + @files_path, @max_upload_size = files_path, max_upload_size + FileUtils.mkdir_p(@files_path) unless File.exist?(@files_path) + @files = Dir["#{@files_path}/*"].collect{|path| File.basename(path) if File.file?(path) }.compact + end + + def upload_file(name, io) + sanitize_file_name(name) + if io.kind_of?(Tempfile) + io.close + check_upload_size(io.size) + File.chmod(600, file_path(name)) if File.exists? file_path(name) + FileUtils.mv(io.path, file_path(name)) + else + content = io.read + check_upload_size(content.length) + File.open(file_path(name), 'wb') { |f| f.write(content) } + end + # just in case, restrict read access and prohibit write access to the uploaded file + FileUtils.chmod(0440, file_path(name)) if restrict_upload_access + end + + def files + Dir["#{files_path}/*"].collect{|path| File.basename(path) if File.file?(path)}.compact + end + + def has_file?(name) + files.include?(name) + end + + def file_path(name) + "#{files_path}/#{name}" + end + + SANE_FILE_NAME = /[a-zA-Z0-9\-_\. ]{1,255}/ + + def sanitize_file_name(name) + unless name =~ SANE_FILE_NAME or name == '.' or name == '..' + raise Instiki::ValidationError.new("Invalid file name: '#{name}'.\n" + + "Only latin characters, digits, dots, underscores, dashes and spaces are accepted.") + end + end + + def check_upload_size(actual_upload_size) + if actual_upload_size > @max_upload_size.kilobytes + raise Instiki::ValidationError.new("Uploaded file size (#{actual_upload_size / 1024} " + + "kbytes) exceeds the maximum (#{@max_upload_size} kbytes) set for this wiki") + end + end + +end diff --git a/lib/page_set.rb b/lib/page_set.rb new file mode 100644 index 00000000..c50abe80 --- /dev/null +++ b/lib/page_set.rb @@ -0,0 +1,89 @@ +# Container for a set of pages with methods for manipulation. + +class PageSet < Array + attr_reader :web + + def initialize(web, pages = nil, condition = nil) + @web = web + # if pages is not specified, make a list of all pages in the web + if pages.nil? + super(web.pages) + # otherwise use specified pages and condition to produce a set of pages + elsif condition.nil? + super(pages) + else + super(pages.select { |page| condition[page] }) + end + end + + def most_recent_revision + self.map { |page| page.created_at }.max || Time.at(0) + end + + + def by_name + PageSet.new(@web, sort_by { |page| page.name }) + end + + alias :sort :by_name + + def by_revision + PageSet.new(@web, sort_by { |page| page.created_at }).reverse + end + + def pages_that_reference(page_name) + self.select { |page| page.wiki_references.include?(page_name) } + end + + def pages_that_link_to(page_name) + self.select { |page| page.wiki_words.include?(page_name) } + end + + def pages_that_include(page_name) + self.select { |page| page.wiki_includes.include?(page_name) } + end + + def pages_authored_by(author) + self.select { |page| page.authors.include?(author) } + end + + def characters + self.inject(0) { |chars,page| chars += page.content.size } + end + + # Returns all the orphaned pages in this page set. That is, + # pages in this set for which there is no reference in the web. + # The HomePage and author pages are always assumed to have + # references and so cannot be orphans + # Pages that refer to themselves and have no links from outside are oprphans. + def orphaned_pages + never_orphans = web.select.authors + ['HomePage'] + self.select { |page| + if never_orphans.include? page.name + false + else + references = pages_that_reference(page.name) + references.empty? or references == [page] + end + } + end + + # Returns all the wiki words in this page set for which + # there are no pages in this page set's web + def wanted_pages + wiki_words - web.select.names + end + + def names + self.map { |page| page.name } + end + + def wiki_words + self.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq + end + + def authors + self.inject([]) { |authors, page| authors << page.authors }.flatten.uniq.sort + end + +end diff --git a/lib/wiki.rb b/lib/wiki.rb new file mode 100644 index 00000000..50093193 --- /dev/null +++ b/lib/wiki.rb @@ -0,0 +1,96 @@ +class Wiki + cattr_accessor :storage_path, :logger + self.storage_path = "#{RAILS_ROOT}/storage/" + self.logger = RAILS_DEFAULT_LOGGER + + def authenticate(password) + password == (system.password || 'instiki') + end + + def create_web(name, address, password = nil) + @webs = nil + Web.create(:name => name, :address => address, :password => password) + end + + def delete_web(address) + web = Web.find_by_address(address) + unless web.nil? + web.destroy + @webs = nil + end + end + + def file_yard(web) + web.file_yard + end + + def edit_web(old_address, new_address, name, markup, color, additional_style, safe_mode = false, + password = nil, published = false, brackets_only = false, count_pages = false, + allow_uploads = true, max_upload_size = nil) + + if not (web = Web.find_by_address(old_address)) + raise Instiki::ValidationError.new("Web with address '#{old_address}' does not exist") + end + + web.refresh_revisions if web.settings_changed?(markup, safe_mode, brackets_only) + web.update_attributes(:address => new_address, :name => name, :markup => markup, :color => color, + :additional_style => additional_style, :safe_mode => safe_mode, :password => password, :published => published, + :brackets_only => brackets_only, :count_pages => count_pages, :allow_uploads => allow_uploads, :max_upload_size => max_upload_size) + @webs = nil + raise Instiki::ValidationError.new("There is already a web with address '#{new_address}'") unless web.errors.on(:address).nil? + web + end + + def read_page(web_address, page_name) + self.class.logger.debug "Reading page '#{page_name}' from web '#{web_address}'" + web = Web.find_by_address(web_address) + if web.nil? + self.class.logger.debug "Web '#{web_address}' not found" + return nil + else + page = web.pages.find(:first, :conditions => ['name = ?', page_name]) + self.class.logger.debug "Page '#{page_name}' #{page.nil? ? 'not' : ''} found" + return page + end + end + + def remove_orphaned_pages(web_address) + web = Web.find_by_address(web_address) + web.remove_pages(web.select.orphaned_pages) + end + + def revise_page(web_address, page_name, content, revised_on, author) + page = read_page(web_address, page_name) + page.revise(content, revised_on, author) + end + + def rollback_page(web_address, page_name, revision_number, created_at, author_id = nil) + page = read_page(web_address, page_name) + page.rollback(revision_number, created_at, author_id) + end + + def setup(password, web_name, web_address) + system.update_attribute(:password, password) + create_web(web_name, web_address) + end + + def system + @system ||= (System.find(:first) || System.create) + end + + def setup? + Web.count > 0 + end + + def webs + @webs ||= Web.find(:all).inject({}) { |webs, web| webs.merge(web.address => web) } + end + + def storage_path + self.class.storage_path + end + + def write_page(web_address, page_name, content, written_on, author) + Web.find_by_address(web_address).add_page(page_name, content, written_on, author) + end +end \ No newline at end of file diff --git a/lib/wiki_content.rb b/lib/wiki_content.rb new file mode 100644 index 00000000..dcc89ef9 --- /dev/null +++ b/lib/wiki_content.rb @@ -0,0 +1,206 @@ +require 'cgi' +require 'chunks/engines' +require 'chunks/category' +require 'chunks/include' +require 'chunks/wiki' +require 'chunks/literal' +require 'chunks/uri' +require 'chunks/nowiki' + +# Wiki content is just a string that can process itself with a chain of +# actions. The actions can modify wiki content so that certain parts of +# it are protected from being rendered by later actions. +# +# When wiki content is rendered, it can be interrogated to find out +# which chunks were rendered. This means things like categories, wiki +# links, can be determined. +# +# Exactly how wiki content is rendered is determined by a number of +# settings that are optionally passed in to a constructor. The current +# options are: +# * :engine +# => The structural markup engine to use (Textile, Markdown, RDoc) +# * :engine_opts +# => A list of options to pass to the markup engines (safe modes, etc) +# * :pre_engine_actions +# => A list of render actions or chunks to be processed before the +# markup engine is applied. By default this is: +# Category, Include, URIChunk, WikiChunk::Link, WikiChunk::Word +# * :post_engine_actions +# => A list of render actions or chunks to apply after the markup +# engine. By default these are: +# Literal::Pre, Literal::Tags +# * :mode +# => How should the content be rendered? For normal display (show), +# publishing (:publish) or export (:export)? + +module ChunkManager + attr_reader :chunks_by_type, :chunks_by_id, :chunks, :chunk_id + + ACTIVE_CHUNKS = [ NoWiki, Category, WikiChunk::Link, URIChunk, LocalURIChunk, + WikiChunk::Word ] + + HIDE_CHUNKS = [ Literal::Pre, Literal::Tags ] + + MASK_RE = { + ACTIVE_CHUNKS => Chunk::Abstract.mask_re(ACTIVE_CHUNKS), + HIDE_CHUNKS => Chunk::Abstract.mask_re(HIDE_CHUNKS) + } + + def init_chunk_manager + @chunks_by_type = Hash.new + Chunk::Abstract::derivatives.each{|chunk_type| + @chunks_by_type[chunk_type] = Array.new + } + @chunks_by_id = Hash.new + @chunks = [] + @chunk_id = 0 + end + + def add_chunk(c) + @chunks_by_type[c.class] << c + @chunks_by_id[c.id] = c + @chunks << c + @chunk_id += 1 + end + + def delete_chunk(c) + @chunks_by_type[c.class].delete(c) + @chunks_by_id.delete(c.id) + @chunks.delete(c) + end + + def merge_chunks(other) + other.chunks.each{|c| add_chunk(c)} + end + + def scan_chunkid(text) + text.scan(MASK_RE[ACTIVE_CHUNKS]){|a| yield a[0] } + end + + def find_chunks(chunk_type) + @chunks.select { |chunk| chunk.kind_of?(chunk_type) and chunk.rendered? } + end + + # for testing and WikiContentStub; we need a page_id even if we have no page + def page_id + 0 + end + +end + +# A simplified version of WikiContent. Useful to avoid recursion problems in +# WikiContent.new +class WikiContentStub < String + attr_reader :options + include ChunkManager + def initialize(content, options) + super(content) + @options = options + init_chunk_manager + end + + # Detects the mask strings contained in the text of chunks of type chunk_types + # and yields the corresponding chunk ids + # example: content = "chunk123categorychunk
chunk456categorychunk
" + # inside_chunks(Literal::Pre) ==> yield 456 + def inside_chunks(chunk_types) + chunk_types.each{|chunk_type| chunk_type.apply_to(self) } + + chunk_types.each{|chunk_type| @chunks_by_type[chunk_type].each{|hide_chunk| + scan_chunkid(hide_chunk.text){|id| yield id } + } + } + end +end + +class WikiContent < String + + include ChunkManager + + DEFAULT_OPTS = { + :active_chunks => ACTIVE_CHUNKS, + :engine => Engines::Textile, + :engine_opts => [], + :mode => :show + }.freeze + + attr_reader :web, :options, :revision, :not_rendered, :pre_rendered + + # Create a new wiki content string from the given one. + # The options are explained at the top of this file. + def initialize(revision, options = {}) + @revision = revision + @web = @revision.page.web + + @options = DEFAULT_OPTS.dup.merge(options) + @options[:engine] = Engines::MAP[@web.markup] + @options[:engine_opts] = [:filter_html, :filter_styles] if @web.safe_mode? + @options[:active_chunks] = (ACTIVE_CHUNKS - [WikiChunk::Word] ) if @web.brackets_only? + + @not_rendered = @pre_rendered = nil + + super(@revision.content) + init_chunk_manager + build_chunks + @not_rendered = String.new(self) + end + + # Call @web.page_link using current options. + def page_link(name, text, link_type) + @options[:link_type] = (link_type || :show) + @web.make_link(name, text, @options) + end + + def build_chunks + # create and mask Includes and "active_chunks" chunks + Include.apply_to(self) + @options[:active_chunks].each{|chunk_type| chunk_type.apply_to(self)} + + # Handle hiding contexts like "pre" and "code" etc.. + # The markup (textile, rdoc etc) can produce such contexts with its own syntax. + # To reveal them, we work on a copy of the content. + # The copy is rendered and used to detect the chunks that are inside protecting context + # These chunks are reverted on the original content string. + + copy = WikiContentStub.new(self, @options) + @options[:engine].apply_to(copy) + + copy.inside_chunks(HIDE_CHUNKS) do |id| + @chunks_by_id[id].revert + end + end + + def pre_render! + unless @pre_rendered + @chunks_by_type[Include].each{|chunk| chunk.unmask } + @pre_rendered = String.new(self) + end + @pre_rendered + end + + def render! + pre_render! + @options[:engine].apply_to(self) + # unmask in one go. $~[1] is the chunk id + gsub!(MASK_RE[ACTIVE_CHUNKS]){ + if chunk = @chunks_by_id[$~[1]] + chunk.unmask_text + # if we match a chunkmask that existed in the original content string + # just keep it as it is + else + $~[0] + end} + self + end + + def page_name + @revision.page.name + end + + def page_id + @revision.page.id + end + +end + diff --git a/lib/wiki_words.rb b/lib/wiki_words.rb new file mode 100644 index 00000000..8f2b154f --- /dev/null +++ b/lib/wiki_words.rb @@ -0,0 +1,23 @@ +# Contains all the methods for finding and replacing wiki words +module WikiWords + # In order of appearance: Latin, greek, cyrillian, armenian + I18N_HIGHER_CASE_LETTERS = + "ÀÃ?ÂÃÄÅĀĄĂÆÇĆČĈĊĎÄ?ÈÉÊËĒĘĚĔĖĜĞĠĢĤĦÌÃ?ÃŽÃ?ĪĨĬĮİIJĴĶÅ?ĽĹĻĿÑŃŇŅŊÒÓÔÕÖØŌÅ?ŎŒŔŘŖŚŠŞŜȘŤŢŦȚÙÚÛÜŪŮŰŬŨŲŴÃ?ŶŸŹŽŻ" + + "ΑΒΓΔΕΖΗΘΙΚΛΜÎ?ΞΟΠΡΣΤΥΦΧΨΩ" + + "ΆΈΉΊΌΎÎ?ѠѢѤѦѨѪѬѮѰѲѴѶѸѺѼѾҀҊҌҎÒ?ҒҔҖҘҚҜҞҠҢҤҦҨҪҬҮҰҲҴҶҸҺҼҾÓ?ÓƒÓ…Ó‡Ó‰Ó‹Ó?Ó?ӒӔӖӘӚӜӞӠӢӤӦӨӪӬӮӰӲӴӸЖ" + + "Ô±Ô²Ô³Ô´ÔµÔ¶Ô·Ô¸Ô¹ÔºÔ»Ô¼Ô½Ô¾Ô¿Õ€Õ?Õ‚ÕƒÕ„Õ…Õ†Õ‡ÕˆÕ‰ÕŠÕ‹ÕŒÕ?Õ?Õ?Õ‘Õ’Õ“Õ”Õ•Õ–" + + I18N_LOWER_CASE_LETTERS = + "àáâãäåÄ?ąăæçćÄ?ĉċÄ?đèéêëēęěĕėƒÄ?ğġģĥħìíîïīĩĭįıijĵķĸłľĺļŀñńňņʼnŋòóôõöøÅ?Å‘Å?œŕřŗśšşÅ?șťţŧțùúûüūůűŭũųŵýÿŷžżźÞþßſÃ?ð" + + "άέήίΰαβγδεζηθικλμνξοπÏ?ςστυφχψωϊϋόÏ?ÏŽÎ?" + + "абвгдежзийклмнопрÑ?туфхцчшщъыьÑ?ÑŽÑ?Ñ?ёђѓєѕіїјљћќÑ?ўџѡѣѥѧѩѫѭѯѱѳѵѷѹѻѽѿÒ?Ò‹Ò?Ò?Ò‘Ò“Ò•Ò—Ò™Ò›Ò?ҟҡңҥҧҩҫҭүұҳҵҷҹһҽҿӀӂӄӆӈӊӌӎӑӓӕӗәӛÓ?ÓŸÓ¡Ó£Ó¥Ó§Ó©Ó«Ó­Ó¯Ó±Ó³ÓµÓ¹" + + "Õ¡Õ¢Õ£Õ¤Õ¥Õ¦Õ§Õ¨Õ©ÕªÕ«Õ¬Õ­Õ®Õ¯Õ°Õ±Õ²Õ³Õ´ÕµÕ¶Õ·Õ¸Õ¹ÕºÕ»Õ¼Õ½Õ¾Õ¿Ö€Ö?Ö‚ÖƒÖ„Ö…Ö†Ö‡" + + WIKI_WORD_PATTERN = '[A-Z' + I18N_HIGHER_CASE_LETTERS + '][a-z' + I18N_LOWER_CASE_LETTERS + ']+[A-Z' + I18N_HIGHER_CASE_LETTERS + ']\w+' + CAMEL_CASED_WORD_BORDER = /([a-z#{I18N_LOWER_CASE_LETTERS}])([A-Z#{I18N_HIGHER_CASE_LETTERS}])/u + + def self.separate(wiki_word) + wiki_word.gsub(CAMEL_CASED_WORD_BORDER, '\1 \2') + end + +end diff --git a/rakefile.rb b/rakefile.rb index 1495f84e..e69de29b 100755 --- a/rakefile.rb +++ b/rakefile.rb @@ -1,134 +0,0 @@ -require 'rake' -require 'rake/clean' -require 'rake/testtask' -require 'rake/rdoctask' -require 'rake/packagetask' - -$VERBOSE = nil - -# Standard Rails tasks - -desc 'Run all tests' -task :default => [:test_units, :test_functional] - -desc 'Require application environment.' -task :environment do - unless defined? RAILS_ROOT - require File.dirname(__FILE__) + '/config/environment' - end -end - -desc 'Generate API documentatio, show coding stats' -task :doc => [ :appdoc, :stats ] - -desc 'Run the unit tests in test/unit' -Rake::TestTask.new('test_units') { |t| - t.libs << 'test' - t.pattern = 'test/unit/**/*_test.rb' - t.verbose = true -} - -desc 'Run the functional tests in test/functional' -Rake::TestTask.new('test_functional') { |t| - t.libs << 'test' - t.pattern = 'test/functional/**/*_test.rb' - t.verbose = true -} - -desc 'Generate documentation for the application' -Rake::RDocTask.new('appdoc') { |rdoc| - rdoc.rdoc_dir = 'doc/app' - rdoc.title = 'Rails Application Documentation' - rdoc.options << '--line-numbers --inline-source' - rdoc.rdoc_files.include('doc/README_FOR_APP') - rdoc.rdoc_files.include('app/**/*.rb') -} - -desc 'Generate documentation for the Rails framework' -Rake::RDocTask.new("apidoc") { |rdoc| - rdoc.rdoc_dir = 'doc/api' - rdoc.title = 'Rails Framework Documentation' - rdoc.options << '--line-numbers --inline-source' - rdoc.rdoc_files.include('README') - rdoc.rdoc_files.include('CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/railties/CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/railties/MIT-LICENSE') - rdoc.rdoc_files.include('vendor/rails/activerecord/README') - rdoc.rdoc_files.include('vendor/rails/activerecord/CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/activerecord/lib/active_record/**/*.rb') - rdoc.rdoc_files.exclude('vendor/rails/activerecord/lib/active_record/vendor/*') - rdoc.rdoc_files.include('vendor/rails/actionpack/README') - rdoc.rdoc_files.include('vendor/rails/actionpack/CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/actionpack/lib/action_controller/**/*.rb') - rdoc.rdoc_files.include('vendor/rails/actionpack/lib/action_view/**/*.rb') - rdoc.rdoc_files.include('vendor/rails/actionmailer/README') - rdoc.rdoc_files.include('vendor/rails/actionmailer/CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/actionmailer/lib/action_mailer/base.rb') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/README') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/ChangeLog') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/**/*.rb') - rdoc.rdoc_files.include('vendor/rails/activesupport/README') - rdoc.rdoc_files.include('vendor/rails/activesupport/lib/active_support/**/*.rb') -} - -desc 'Report code statistics (KLOCs, etc) from the application' -task :stats => [ :environment ] do - require 'code_statistics' - CodeStatistics.new( - ['Helpers', 'app/helpers'], - ['Controllers', 'app/controllers'], - ['Functionals', 'test/functional'], - ['Models', 'app/models'], - ['Units', 'test/unit'], - ['Miscellaneous (lib)', 'lib'] - ).to_s -end - -# Additional tasks (not standard Rails) - -CLEAN << 'pkg' << 'storage' << 'doc' << 'html' - -begin - require 'rubygems' - require 'rake/gempackagetask' -rescue Exception => e - nil -end - -if defined? Rake::GemPackageTask - gemspec = eval(File.read('instiki.gemspec')) - Rake::GemPackageTask.new(gemspec) do |p| - p.gem_spec = gemspec - p.need_tar = true - p.need_zip = true - end - - Rake::PackageTask.new('instiki', gemspec.version) do |p| - p.need_tar = true - p.need_zip = true - # the list of glob expressions for files comes from instiki.gemspec - p.package_files.include($__instiki_source_patterns) - end - - # Create a task to build the RDOC documentation tree. - rd = Rake::RDocTask.new("rdoc") { |rdoc| - rdoc.rdoc_dir = 'html' - rdoc.title = 'Instiki -- The Wiki' - rdoc.options << '--line-numbers --inline-source --main README' - rdoc.rdoc_files.include(gemspec.files) - rdoc.main = 'README' - } -else - puts 'Warning: without Rubygems packaging tasks are not available' -end - -# Shorthand aliases -desc 'Shorthand for test_units' -task :tu => :test_units -desc 'Shorthand for test_units' -task :ut => :test_units - -desc 'Shorthand for test_functional' -task :tf => :test_functional -desc 'Shorthand for test_functional' -task :ft => :test_functional diff --git a/script/console b/script/console new file mode 100644 index 00000000..eece24a9 --- /dev/null +++ b/script/console @@ -0,0 +1,23 @@ +#!/usr/local/bin/ruby +irb = RUBY_PLATFORM =~ /mswin32/ ? 'irb.bat' : 'irb' + +require 'optparse' +options = { :sandbox => false, :irb => irb } +OptionParser.new do |opt| + opt.on('-s', '--sandbox', 'Rollback database modifications on exit.') { |options[:sandbox]| } + opt.on("--irb=[#{irb}]", 'Invoke a different irb.') { |options[:irb]| } + opt.parse!(ARGV) +end + +libs = " -r irb/completion" +libs << " -r #{File.dirname(__FILE__)}/../config/environment" +libs << " -r console_sandbox" if options[:sandbox] + +ENV['RAILS_ENV'] = ARGV.first || 'development' +if options[:sandbox] + puts "Loading #{ENV['RAILS_ENV']} environment in sandbox." + puts "Any modifications you make will be rolled back on exit." +else + puts "Loading #{ENV['RAILS_ENV']} environment." +end +exec "#{options[:irb]} #{libs} --prompt-mode simple" diff --git a/script/create_db b/script/create_db index b421fb8c..642b0d03 100644 --- a/script/create_db +++ b/script/create_db @@ -12,7 +12,7 @@ config = ActiveRecord::Base.configurations ENV['RAILS_ENV'] = target load APP_ROOT + 'config/environment.rb' puts "Creating tables for #{target}..." - + db_structure(config[target]['adapter']).split(/\s*;\s*/).each do |sql| ActiveRecord::Base.connection.execute(sql) end diff --git a/script/server b/script/server index 0da05085..487d1fd9 100755 --- a/script/server +++ b/script/server @@ -1,93 +1,49 @@ -#!/usr/bin/ruby +#!/usr/local/bin/ruby require 'webrick' require 'optparse' -require 'fileutils' - -pwd = File.expand_path(File.dirname(__FILE__) + "/..") OPTIONS = { - # Overridable options - :port => 2500, - :ip => '0.0.0.0', - :environment => 'production', - :server_root => File.expand_path(File.dirname(__FILE__) + '/../public/'), - :server_type => WEBrick::SimpleServer, - :storage => "#{File.expand_path(FileUtils.pwd)}/storage", + :port => 3000, + :ip => "0.0.0.0", + :environment => "development", + :server_root => File.expand_path(File.dirname(__FILE__) + "/../public/"), + :server_type => WEBrick::SimpleServer } ARGV.options do |opts| script_name = File.basename($0) opts.banner = "Usage: ruby #{script_name} [options]" - opts.separator '' + opts.separator "" - opts.on('-p', '--port=port', Integer, - 'Runs Instiki on the specified port.', - 'Default: 2500') { |OPTIONS[:port]| } - opts.on('-b', '--binding=ip', String, - 'Binds Rails to the specified ip.', - 'Default: 0.0.0.0') { |OPTIONS[:ip]| } - opts.on('-e', '--environment=name', String, - 'Specifies the environment to run this server under (test/development/production).', - 'Default: production') { |OPTIONS[:environment]| } - opts.on('-d', '--daemon', - 'Make Instiki run as a Daemon (only works if fork is available -- meaning on *nix).' + opts.on("-p", "--port=port", Integer, + "Runs Rails on the specified port.", + "Default: 3000") { |OPTIONS[:port]| } + opts.on("-b", "--binding=ip", String, + "Binds Rails to the specified ip.", + "Default: 0.0.0.0") { |OPTIONS[:ip]| } + opts.on("-e", "--environment=name", String, + "Specifies the environment to run this server under (test/development/production).", + "Default: development") { |OPTIONS[:environment]| } + opts.on("-d", "--daemon", + "Make Rails run as a Daemon (only works if fork is available -- meaning on *nix)." ) { OPTIONS[:server_type] = WEBrick::Daemon } - opts.on('-s', '--simple', '--simple-server', - '[deprecated] Forces Instiki not to run as a Daemon if fork is available.', - 'Since version 0.10.0 this option is ignored.' - ) { puts "Warning: -s (--simple) option is deprecated. See instiki --help for details." } - opts.on('-t', '--storage=storage', String, - 'Makes Instiki use the specified directory for storage.', - 'Default: ./storage/[port]') { |OPTIONS[:storage]| } - opts.on('-x', '--notex', - 'Blocks wiki exports to TeX and PDF, even when pdflatex is available.' - ) { |OPTIONS[:notex]| } - opts.on('-v', '--verbose', - 'Enables debug-level logging' - ) { OPTIONS[:verbose] = true } - opts.separator '' + opts.separator "" - opts.on('-h', '--help', - 'Show this help message.') { puts opts; exit } + opts.on("-h", "--help", + "Show this help message.") { puts opts; exit } opts.parse! end -if OPTIONS[:environment] == 'production' - storage_path = "#{OPTIONS[:storage]}/#{OPTIONS[:port]}" -else - storage_path = "#{OPTIONS[:storage]}/#{OPTIONS[:environment]}/#{OPTIONS[:port]}" -end -FileUtils.mkdir_p(storage_path) +ENV["RAILS_ENV"] = OPTIONS[:environment] +require File.dirname(__FILE__) + "/../config/environment" +require 'webrick_server' -ENV['RAILS_ENV'] = OPTIONS[:environment] -$instiki_debug_logging = OPTIONS[:verbose] -require File.expand_path(File.dirname(__FILE__) + '/../config/environment') -WikiService.storage_path = storage_path +OPTIONS['working_directory'] = File.expand_path(RAILS_ROOT) -if OPTIONS[:notex] - OPTIONS[:pdflatex] = false -else - begin - OPTIONS[:pdflatex] = system "pdflatex -version" - rescue Errno::ENOENT - OPTIONS[:pdflatex] = false - end -end - -if defined? INSTIKI_BATCH_JOB - require 'application' -else - puts "=> Starting Instiki on http://#{OPTIONS[:ip]}:#{OPTIONS[:port]}" - puts "=> Data files are stored in #{storage_path}" - - require 'webrick_server' - require_dependency 'application' - - OPTIONS[:index_controller] = 'wiki' - ApplicationController.wiki = WikiService.instance - DispatchServlet.dispatch(OPTIONS) -end +puts "=> Rails application started on http://#{OPTIONS[:ip]}:#{OPTIONS[:port]}" +puts "=> Ctrl-C to shutdown server; call with --help for options" if OPTIONS[:server_type] == WEBrick::SimpleServer +DispatchServlet.dispatch(OPTIONS) diff --git a/test/fixtures/pages.yml b/test/fixtures/pages.yml index 2c62113c..f7add425 100644 --- a/test/fixtures/pages.yml +++ b/test/fixtures/pages.yml @@ -1,5 +1,55 @@ home_page: id: 1 - created_at: 2004-08-01 - updated_at: 2005-08-01 - web_id: 1 \ No newline at end of file + created_at: <%= Time.local(2004, 4, 4, 16, 50).to_formatted_s(:db) %> + updated_at: <%= Time.local(2004, 4, 4, 16, 50).to_formatted_s(:db) %> + web_id: 1 + name: HomePage + +my_way: + id: 2 + created_at: <%= 9.days.ago.to_formatted_s(:db) %> + updated_at: <%= 9.days.ago.to_formatted_s(:db) %> + web_id: 1 + name: MyWay + +smart_engine: + id: 3 + created_at: <%= 8.days.ago.to_formatted_s(:db) %> + updated_at: <%= 8.days.ago.to_formatted_s(:db) %> + web_id: 1 + name: SmartEngine + +that_way: + id: 4 + created_at: <%= 7.days.ago.to_formatted_s(:db) %> + updated_at: <%= 7.days.ago.to_formatted_s(:db) %> + web_id: 1 + name: ThatWay + +no_wiki_word: + id: 5 + created_at: <%= 6.days.ago.to_formatted_s(:db) %> + updated_at: <%= 6.days.ago.to_formatted_s(:db) %> + web_id: 1 + name: NoWikiWord + +first_page: + id: 6 + created_at: <%= Time.local(2004, 4, 4, 16, 55).to_formatted_s(:db) %> + updated_at: <%= Time.local(2004, 4, 4, 16, 55).to_formatted_s(:db) %> + web_id: 1 + name: FirstPage + +oak: + id: 7 + created_at: <%= 5.days.ago.to_formatted_s(:db) %> + updated_at: <%= 5.days.ago.to_formatted_s(:db) %> + web_id: 1 + name: Oak + +elephant: + id: 8 + created_at: <%= 10.minutes.ago.to_formatted_s(:db) %> + updated_at: <%= 10.minutes.ago.to_formatted_s(:db) %> + web_id: 1 + name: Elephant \ No newline at end of file diff --git a/test/fixtures/revisions.yml b/test/fixtures/revisions.yml index 9d43eb26..eb5eff5b 100644 --- a/test/fixtures/revisions.yml +++ b/test/fixtures/revisions.yml @@ -1,6 +1,84 @@ home_page_first_revision: id: 1 - created_at: 2004-08-01 - updated_at: 2005-08-01 + created_at: <%= Time.local(2004, 4, 4, 15, 50).to_formatted_s(:db) %> + updated_at: <%= Time.local(2004, 4, 4, 15, 50).to_formatted_s(:db) %> page_id: 1 - content: some text \ No newline at end of file + number: 1 + content: First revision of the HomePage end + author: AnAuthor + ip: 127.0.0.1 + +my_way_first_revision: + id: 2 + created_at: <%= 9.days.ago.to_formatted_s(:db) %> + updated_at: <%= 9.days.ago.to_formatted_s(:db) %> + page_id: 2 + number: 1 + content: MyWay + author: Me + +smart_engine_first_revision: + id: 3 + created_at: <%= 8.days.ago.to_formatted_s(:db) %> + updated_at: <%= 8.days.ago.to_formatted_s(:db) %> + page_id: 3 + number: 1 + content: SmartEngine + author: Me + +that_way_first_revision: + id: 4 + created_at: <%= 7.days.ago.to_formatted_s(:db) %> + updated_at: <%= 7.days.ago.to_formatted_s(:db) %> + page_id: 4 + number: 1 + content: ThatWay + author: Me + +no_wiki_word_first_revision: + id: 5 + created_at: <%= 6.days.ago.to_formatted_s(:db) %> + updated_at: <%= 6.days.ago.to_formatted_s(:db) %> + page_id: 5 + number: 1 + content: hey you + author: Me + +home_page_second_revision: + id: 6 + created_at: <%= Time.local(2004, 4, 4, 16, 50).to_formatted_s(:db) %> + updated_at: <%= Time.local(2004, 4, 4, 16, 50).to_formatted_s(:db) %> + page_id: 1 + number: 2 + content: HisWay would be MyWay in kinda ThatWay in HisWay though MyWay \OverThere -- see SmartEngine in that SmartEngineGUI + author: DavidHeinemeierHansson + +first_page_first_revision: + id: 7 + created_at: <%= Time.local(2004, 4, 4, 16, 55).to_formatted_s(:db) %> + updated_at: <%= Time.local(2004, 4, 4, 16, 55).to_formatted_s(:db) %> + page_id: 6 + number: 1 + content: HisWay would be MyWay in kinda ThatWay in HisWay though MyWay \\OverThere -- see SmartEngine in that SmartEngineGUI + author: DavidHeinemeierHansson + +oak_first_revision: + id: 8 + created_at: <%= 5.days.ago.to_formatted_s(:db) %> + updated_at: <%= 5.days.ago.to_formatted_s(:db) %> + page_id: 7 + number: 1 + content: "All about oak.\ncategory: trees" + author: TreeHugger + ip: 127.0.0.2 + +elephant_first_revision: + id: 9 + created_at: <%= 10.minutes.ago.to_formatted_s(:db) %> + updated_at: <%= 10.minutes.ago.to_formatted_s(:db) %> + page_id: 8 + number: 1 + content: "All about elephants.\ncategory: animals" + author: Guest + ip: 127.0.0.2 + diff --git a/test/fixtures/system.yml b/test/fixtures/system.yml new file mode 100644 index 00000000..1b17f2fc --- /dev/null +++ b/test/fixtures/system.yml @@ -0,0 +1,2 @@ +system: + password: test_password diff --git a/test/fixtures/webs.yml b/test/fixtures/webs.yml index 7276bb65..29c89566 100644 --- a/test/fixtures/webs.yml +++ b/test/fixtures/webs.yml @@ -2,5 +2,14 @@ test_wiki: id: 1 created_at: 2004-08-01 updated_at: 2005-08-01 - name: wiki - address: wiki \ No newline at end of file + name: wiki1 + address: wiki1 + markup: textile + +instiki: + id: 2 + created_at: 2004-08-01 + updated_at: 2005-08-01 + name: Instiki + address: instiki + markup: textile \ No newline at end of file diff --git a/test/functional/admin_controller_test.rb b/test/functional/admin_controller_test.rb index 7aed5c70..a24cf8c0 100644 --- a/test/functional/admin_controller_test.rb +++ b/test/functional/admin_controller_test.rb @@ -1,70 +1,73 @@ -#!/bin/env ruby -w +#!/bin/env ruby -require File.dirname(__FILE__) + '/../test_helper' +require File.expand_path(File.dirname(__FILE__) + '/../test_helper') require 'admin_controller' # Raise errors beyond the default web-based presentation class AdminController; def rescue_action(e) logger.error(e); raise e end; end class AdminControllerTest < Test::Unit::TestCase + fixtures :webs, :pages, :revisions, :system def setup - setup_test_wiki - setup_controller_test + @controller = AdminController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @wiki = Wiki.new + @oak = pages(:oak) + @elephant = pages(:elephant) + @web = webs(:test_wiki) + @home = @page = pages(:home_page) end - def tear_down - tear_down_wiki - end - - def test_create_system_form_displayed - ApplicationController.wiki = WikiServiceWithNoPersistence.new + use_blank_wiki process('create_system') - assert_success + assert_response :success end def test_create_system_form_submitted - ApplicationController.wiki = WikiServiceWithNoPersistence.new - assert !ApplicationController.wiki.setup? + use_blank_wiki + assert !@wiki.setup? process('create_system', 'password' => 'a_password', 'web_name' => 'My Wiki', 'web_address' => 'my_wiki') assert_redirected_to :web => 'my_wiki', :controller => 'wiki', :action => 'new', :id => 'HomePage' - assert ApplicationController.wiki.setup? - assert_equal 'a_password', ApplicationController.wiki.system[:password] - assert_equal 1, ApplicationController.wiki.webs.size - new_web = ApplicationController.wiki.webs['my_wiki'] + assert @wiki.setup? + assert_equal 'a_password', @wiki.system[:password] + assert_equal 1, @wiki.webs.size + new_web = @wiki.webs['my_wiki'] assert_equal 'My Wiki', new_web.name assert_equal 'my_wiki', new_web.address end def test_create_system_form_submitted_and_wiki_already_initialized - wiki_before = ApplicationController.wiki - assert ApplicationController.wiki.setup? + wiki_before = @wiki + old_size = @wiki.webs.size + assert @wiki.setup? process 'create_system', 'password' => 'a_password', 'web_name' => 'My Wiki', 'web_address' => 'my_wiki' - assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage' - assert_equal wiki_before, ApplicationController.wiki + assert_redirected_to :web => @wiki.webs.keys.first, :action => 'show', :id => 'HomePage' + assert_equal wiki_before, @wiki # and no new web should be created either - assert_equal 1, ApplicationController.wiki.webs.size + assert_equal old_size, @wiki.webs.size assert_flash_has :error end def test_create_system_no_form_and_wiki_already_initialized assert @wiki.setup? process('create_system') - assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage' + assert_redirected_to :web => @wiki.webs.keys.first, :action => 'show', :id => 'HomePage' assert_flash_has :error end def test_create_web - @wiki.system[:password] = 'pswd' + @wiki.system.update_attribute(:password, 'pswd') process 'create_web', 'system_password' => 'pswd', 'name' => 'Wiki Two', 'address' => 'wiki2' @@ -76,7 +79,7 @@ class AdminControllerTest < Test::Unit::TestCase end def test_create_web_default_password - @wiki.system[:password] = nil + @wiki.system.update_attribute(:password, nil) process 'create_web', 'system_password' => 'instiki', 'name' => 'Wiki Two', 'address' => 'wiki2' @@ -84,7 +87,7 @@ class AdminControllerTest < Test::Unit::TestCase end def test_create_web_failed_authentication - @wiki.system[:password] = 'pswd' + @wiki.system.update_attribute(:password, 'pswd') process 'create_web', 'system_password' => 'wrong', 'name' => 'Wiki Two', 'address' => 'wiki2' @@ -93,20 +96,20 @@ class AdminControllerTest < Test::Unit::TestCase end def test_create_web_no_form_submitted - @wiki.system[:password] = 'pswd' + @wiki.system.update_attribute(:password, 'pswd') process 'create_web' - assert_success + assert_response :success end def test_edit_web_no_form process 'edit_web', 'web' => 'wiki1' # this action simply renders a form - assert_success + assert_response :success end def test_edit_web_form_submitted - @wiki.system[:password] = 'pswd' + @wiki.system.update_attribute(:password, 'pswd') process('edit_web', 'system_password' => 'pswd', 'web' => 'wiki1', 'address' => 'renamed_wiki1', 'name' => 'Renamed Wiki1', @@ -116,21 +119,22 @@ class AdminControllerTest < Test::Unit::TestCase 'max_upload_size' => '300') assert_redirected_to :web => 'renamed_wiki1', :action => 'show', :id => 'HomePage' + @web = Web.find(@web.id) assert_equal 'renamed_wiki1', @web.address assert_equal 'Renamed Wiki1', @web.name assert_equal :markdown, @web.markup assert_equal 'blue', @web.color - assert @web.safe_mode + assert @web.safe_mode? assert_equal 'new_password', @web.password - assert @web.published - assert @web.brackets_only - assert @web.count_pages - assert @web.allow_uploads + assert @web.published? + assert @web.brackets_only? + assert @web.count_pages? + assert @web.allow_uploads? assert_equal 300, @web.max_upload_size end def test_edit_web_opposite_values - @wiki.system[:password] = 'pswd' + @wiki.system.update_attribute(:password, 'pswd') process('edit_web', 'system_password' => 'pswd', 'web' => 'wiki1', 'address' => 'renamed_wiki1', 'name' => 'Renamed Wiki1', @@ -140,11 +144,12 @@ class AdminControllerTest < Test::Unit::TestCase # and should become false assert_redirected_to :web => 'renamed_wiki1', :action => 'show', :id => 'HomePage' - assert !@web.safe_mode - assert !@web.published - assert !@web.brackets_only - assert !@web.count_pages - assert !@web.allow_uploads + @web = Web.find(@web.id) + assert !@web.safe_mode? + assert !@web.published? + assert !@web.brackets_only? + assert !@web.count_pages? + assert !@web.allow_uploads? end def test_edit_web_wrong_password @@ -154,12 +159,12 @@ class AdminControllerTest < Test::Unit::TestCase 'password' => 'new_password') #returns to the same form - assert_success + assert_response :success assert @response.has_template_object?('error') end def test_edit_web_rename_to_already_existing_web_name - @wiki.system[:password] = 'pswd' + @wiki.system.update_attribute(:password, 'pswd') @wiki.create_web('Another', 'another') process('edit_web', 'system_password' => 'pswd', @@ -168,7 +173,7 @@ class AdminControllerTest < Test::Unit::TestCase 'password' => 'new_password') #returns to the same form - assert_success + assert_response :success assert @response.has_template_object?('error') end @@ -179,15 +184,15 @@ class AdminControllerTest < Test::Unit::TestCase 'password' => 'new_password') #returns to the same form - assert_success + assert_response :success assert @response.has_template_object?('error') end def test_remove_orphaned_pages - setup_wiki_with_three_pages - @wiki.system[:password] = 'pswd' - orhan_page_linking_to_oak = @wiki.write_page('wiki1', 'Pine', + @wiki.system.update_attribute(:password, 'pswd') + page_order = [@home, pages(:my_way), @oak, pages(:smart_engine), pages(:that_way)] + orphan_page_linking_to_oak = @wiki.write_page('wiki1', 'Pine', "Refers to [[Oak]].\n" + "category: trees", Time.now, Author.new('TreeHugger', '127.0.0.2')) @@ -195,25 +200,28 @@ class AdminControllerTest < Test::Unit::TestCase r = process('remove_orphaned_pages', 'web' => 'wiki1', 'system_password_orphaned' => 'pswd') assert_redirected_to :controller => 'wiki', :web => 'wiki1', :action => 'list' - assert_equal [@home, @oak], @web.select.sort, + @web.pages(true) + assert_equal page_order, @web.select.sort, "Pages are not as expected: #{@web.select.sort.map {|p| p.name}.inspect}" # Oak is now orphan, second pass should remove it r = process('remove_orphaned_pages', 'web' => 'wiki1', 'system_password_orphaned' => 'pswd') assert_redirected_to :controller => 'wiki', :web => 'wiki1', :action => 'list' - assert_equal [@home], @web.select.sort, + @web.pages(true) + page_order.delete(@oak) + assert_equal page_order, @web.select.sort, "Pages are not as expected: #{@web.select.sort.map {|p| p.name}.inspect}" # third pass does not destroy HomePage r = process('remove_orphaned_pages', 'web' => 'wiki1', 'system_password_orphaned' => 'pswd') assert_redirected_to :action => 'list' - assert_equal [@home], @web.select.sort, + @web.pages(true) + assert_equal page_order, @web.select.sort, "Pages are not as expected: #{@web.select.sort.map {|p| p.name}.inspect}" end def test_remove_orphaned_pages_empty_or_wrong_password - setup_wiki_with_three_pages @wiki.system[:password] = 'pswd' process('remove_orphaned_pages', 'web' => 'wiki1') @@ -224,5 +232,4 @@ class AdminControllerTest < Test::Unit::TestCase assert_redirected_to(:controller => 'admin', :action => 'edit_web', :web => 'wiki1') assert @response.flash[:error] end - end diff --git a/test/functional/application_test.rb b/test/functional/application_test.rb index 3a33df74..c32f8b23 100755 --- a/test/functional/application_test.rb +++ b/test/functional/application_test.rb @@ -8,24 +8,23 @@ require 'rexml/document' class WikiController; def rescue_action(e) logger.error(e); raise e end; end class ApplicationTest < Test::Unit::TestCase - + fixtures :webs, :pages, :revisions, :system + def setup - setup_test_wiki - setup_controller_test(WikiController) + @controller = WikiController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @wiki = Wiki.new end - - def tear_down - tear_down_wiki - end - + def test_utf8_header - r = process('show', 'web' => 'wiki1', 'id' => 'HomePage') - assert_equal 'text/html; charset=UTF-8', r.headers['Content-Type'] + get :show, :web => 'wiki1', :id => 'HomePage' + assert_equal 'text/html; charset=UTF-8', @response.headers['Content-Type'] end def test_connect_to_model_unknown_wiki - r = process('show', 'web' => 'unknown_wiki', 'id' => 'HomePage') - assert_equal 404, r.response_code + get :show, :web => 'unknown_wiki', :id => 'HomePage' + assert_response :missing end end diff --git a/test/functional/file_controller_test.rb b/test/functional/file_controller_test.rb index 23f18c42..cea3aa81 100755 --- a/test/functional/file_controller_test.rb +++ b/test/functional/file_controller_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby -w +#!/bin/env ruby require File.dirname(__FILE__) + '/../test_helper' require 'file_controller' @@ -8,24 +8,26 @@ require 'fileutils' class FileController; def rescue_action(e) logger.error(e); raise e end; end class FileControllerTest < Test::Unit::TestCase + fixtures :webs, :pages, :revisions, :system - FILE_AREA = RAILS_ROOT + '/storage/test/wiki1' + Wiki.storage_path += "test/" + FILE_AREA = Wiki.storage_path + 'wiki1' FileUtils.mkdir_p(FILE_AREA) unless File.directory?(FILE_AREA) FileUtils.rm(Dir["#{FILE_AREA}/*"]) def setup - setup_test_wiki - setup_controller_test - end - - def tear_down - tear_down_wiki + @controller = FileController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @wiki = Wiki.new + @web = webs(:test_wiki) + @home = @page = pages(:home_page) end def test_file process 'file', 'web' => 'wiki1', 'id' => 'foo.tgz' - assert_success + assert_response :success assert_rendered_file 'file/file' end @@ -34,7 +36,7 @@ class FileControllerTest < Test::Unit::TestCase r = process 'file', 'web' => 'wiki1', 'id' => 'foo.txt' - assert_success + assert_response :success assert_equal "aaa\nbbb\n", r.binary_content assert_equal 'text/plain', r.headers['Content-Type'] end @@ -44,7 +46,7 @@ class FileControllerTest < Test::Unit::TestCase r = process 'file', 'web' => 'wiki1', 'id' => 'foo.pdf' - assert_success + assert_response :success assert_equal "aaa\nbbb\n", r.binary_content assert_equal 'application/pdf', r.headers['Content-Type'] end @@ -54,14 +56,14 @@ class FileControllerTest < Test::Unit::TestCase r = process 'pic', 'web' => 'wiki1', 'id' => 'rails.gif' - assert_success + assert_response :success assert_equal File.size("#{FILE_AREA}/rails.gif"), r.binary_content.size end def test_pic_unknown_pic r = process 'pic', 'web' => 'wiki1', 'id' => 'non-existant.gif' - assert_success + assert_response :success assert_rendered_file 'file/file' end @@ -74,7 +76,7 @@ class FileControllerTest < Test::Unit::TestCase # rails-e2e.gif is unknown to the system, so pic action goes to the file [upload] form r = process 'pic', 'web' => 'wiki1', 'id' => 'rails-e2e.gif' - assert_success + assert_response :success assert_rendered_file 'file/file' # User uploads the picture @@ -98,7 +100,7 @@ class FileControllerTest < Test::Unit::TestCase # rails-e2e.gif is unknown to the system, so pic action goes to the file [upload] form r = process 'file', 'web' => 'wiki1', 'id' => 'instiki-e2e.txt' - assert_success + assert_response :success assert_rendered_file 'file/file' # User uploads the picture @@ -109,17 +111,18 @@ class FileControllerTest < Test::Unit::TestCase assert_equal(file, File.read("#{RAILS_ROOT}/storage/test/wiki1/instiki-e2e.txt")) # this should refresh the page display content (cached) + @home = Page.find(@home.id) assert_equal "

" + "instiki-e2e.txt

", @home.display_content end def test_uploads_blocking - @web.allow_uploads = true + set_web_property :allow_uploads, true r = process 'file', 'web' => 'wiki1', 'id' => 'filename' - assert_success + assert_response :success - @web.allow_uploads = false + set_web_property :allow_uploads, false r = process 'file', 'web' => 'wiki1', 'id' => 'filename' assert_equal '403 Forbidden', r.headers['Status'] end diff --git a/test/functional/routes_test.rb b/test/functional/routes_test.rb index 2e338bd0..c2334ef7 100644 --- a/test/functional/routes_test.rb +++ b/test/functional/routes_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby -w +#!/bin/env ruby require File.dirname(__FILE__) + '/../test_helper' diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index bc79a8bf..b799ad3b 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -1,10 +1,10 @@ -#!/bin/env ruby -w +#!/bin/env ruby # Uncomment the line below to enable pdflatex tests; don't forget to comment them again # commiting to SVN # $INSTIKI_TEST_PDFLATEX = true -require File.dirname(__FILE__) + '/../test_helper' +require File.expand_path(File.dirname(__FILE__) + '/../test_helper') require 'wiki_controller' require 'rexml/document' require 'tempfile' @@ -14,63 +14,60 @@ require 'zip/zipfilesystem' class WikiController; def rescue_action(e) logger.error(e); raise e end; end class WikiControllerTest < Test::Unit::TestCase - + fixtures :webs, :pages, :revisions, :system + def setup - setup_test_wiki - setup_controller_test + @controller = WikiController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @wiki = Wiki.new + @web = webs(:test_wiki) + @home = @page = pages(:home_page) + @oak = pages(:oak) + @elephant = pages(:elephant) end - def tear_down - tear_down_wiki - end - - def test_authenticate - @web.password = 'pswd' + set_web_property :password, 'pswd' - r = process('authenticate', 'web' => 'wiki1', 'password' => 'pswd') + get :authenticate, :web => 'wiki1', :password => 'pswd' assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage' - assert_equal ['pswd'], r.cookies['web_address'] + assert_equal ['pswd'], @response.cookies['web_address'] end def test_authenticate_wrong_password - @web.password = 'pswd' + set_web_property :password, 'pswd' r = process('authenticate', 'web' => 'wiki1', 'password' => 'wrong password') assert_redirected_to :action => 'login', :web => 'wiki1' assert_nil r.cookies['web_address'] end - def test_authors - setup_wiki_with_three_pages @wiki.write_page('wiki1', 'BreakSortingOrder', "This page breaks the accidentally correct sorting order of authors", Time.now, Author.new('BreakingTheOrder', '127.0.0.2')) r = process('authors', 'web' => 'wiki1') - assert_success - assert_equal ['AnAuthor', 'BreakingTheOrder', 'Guest', 'TreeHugger'], + assert_response :success + assert_equal %w(AnAuthor BreakingTheOrder DavidHeinemeierHansson Guest Me TreeHugger), r.template_objects['authors'] end - def test_cancel_edit - setup_wiki_with_three_pages @oak.lock(Time.now, 'Locky') assert @oak.locked?(Time.now) r = process('cancel_edit', 'web' => 'wiki1', 'id' => 'Oak') assert_redirected_to :action => 'show', :id => 'Oak' - assert !@oak.locked?(Time.now) + assert !Page.find(@oak.id).locked?(Time.now) end - def test_edit r = process 'edit', 'web' => 'wiki1', 'id' => 'HomePage' - assert_success + assert_response :success assert_equal @wiki.read_page('wiki1', 'HomePage'), r.template_objects['page'] end @@ -83,7 +80,8 @@ class WikiControllerTest < Test::Unit::TestCase def test_edit_page_break_lock @home.lock(Time.now, 'Locky') process 'edit', 'web' => 'wiki1', 'id' => 'HomePage', 'break_lock' => 'y' - assert_success + assert_response :success + @home = Page.find(@home.id) assert @home.locked?(Time.now) end @@ -99,19 +97,17 @@ class WikiControllerTest < Test::Unit::TestCase Time.now, Author.new('Special', '127.0.0.3')) r = process 'edit', 'web' => 'wiki1', 'id' => 'With : Special /> symbols' - assert_success + assert_response :success xml = REXML::Document.new(r.body) form = REXML::XPath.first(xml, '//form') assert_equal '/wiki1/save/With+%3A+Special+%2F%3E+symbols', form.attributes['action'] end - def test_export_html - setup_wiki_with_three_pages - + @home.rollback(1, Time.now, 'Rick') # much simpler regex statement to match r = process 'export_html', 'web' => 'wiki1' - assert_success + assert_response :success assert_equal 'application/zip', r.headers['Content-Type'] assert_match /attachment; filename="wiki1-html-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.zip"/, r.headers['Content-Disposition'] @@ -124,7 +120,7 @@ class WikiControllerTest < Test::Unit::TestCase begin File.open(@tempfile_path, 'wb') { |f| f.write(content); @exported_file = f.path } Zip::ZipFile.open(@exported_file) do |zip| - assert_equal %w(Elephant.html HomePage.html Oak.html index.html), zip.dir.entries('.').sort + assert_equal %w(Elephant.html FirstPage.html HomePage.html MyWay.html NoWikiWord.html Oak.html SmartEngine.html ThatWay.html index.html), zip.dir.entries('.').sort assert_match /.*/, zip.file.read('Elephant.html').gsub(/\s+/, ' ') assert_match /.*/, @@ -138,12 +134,10 @@ class WikiControllerTest < Test::Unit::TestCase end end - def test_export_html_no_layout - setup_wiki_with_three_pages - + def test_export_html_no_layout r = process 'export_html', 'web' => 'wiki1', 'layout' => 'no' - assert_success + assert_response :success assert_equal 'application/zip', r.headers['Content-Type'] assert_match /attachment; filename="wiki1-html-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.zip"/, r.headers['Content-Disposition'] @@ -155,7 +149,7 @@ class WikiControllerTest < Test::Unit::TestCase def test_export_markup r = process 'export_markup', 'web' => 'wiki1' - assert_success + assert_response :success assert_equal 'application/zip', r.headers['Content-Type'] assert_match /attachment; filename="wiki1-textile-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.zip"/, r.headers['Content-Disposition'] @@ -168,7 +162,7 @@ class WikiControllerTest < Test::Unit::TestCase def test_export_pdf r = process 'export_pdf', 'web' => 'wiki1' - assert_success + assert_response :success assert_equal 'application/pdf', r.headers['Content-Type'] assert_match /attachment; filename="wiki1-tex-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.pdf"/, r.headers['Content-Disposition'] @@ -183,13 +177,10 @@ class WikiControllerTest < Test::Unit::TestCase puts ' $INSTIKI_TEST_PDFLATEX to enable them.' end - - def test_export_tex - setup_wiki_with_three_pages - + def test_export_tex r = process 'export_tex', 'web' => 'wiki1' - assert_success + assert_response :success assert_equal 'application/octet-stream', r.headers['Content-Type'] assert_match /attachment; filename="wiki1-tex-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.tex"/, r.headers['Content-Disposition'] @@ -202,6 +193,8 @@ class WikiControllerTest < Test::Unit::TestCase end def test_index + # delete extra web fixture + webs(:instiki).destroy process('index') assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage' end @@ -219,41 +212,39 @@ class WikiControllerTest < Test::Unit::TestCase end def test_index_wiki_not_initialized - ApplicationController.wiki = WikiServiceWithNoPersistence.new + use_blank_wiki process('index') assert_redirected_to :controller => 'admin', :action => 'create_system' end def test_list - setup_wiki_with_three_pages - r = process('list', 'web' => 'wiki1') assert_equal ['animals', 'trees'], r.template_objects['categories'] assert_nil r.template_objects['category'] - assert_equal [@elephant, @home, @oak], r.template_objects['pages_in_category'] + assert_equal [@elephant, pages(:first_page), @home, pages(:my_way), pages(:no_wiki_word), @oak, pages(:smart_engine), pages(:that_way)], r.template_objects['pages_in_category'] end def test_locked @home.lock(Time.now, 'Locky') r = process('locked', 'web' => 'wiki1', 'id' => 'HomePage') - assert_success + assert_response :success assert_equal @home, r.template_objects['page'] end def test_login r = process 'login', 'web' => 'wiki1' - assert_success + assert_response :success # this action goes straight to the templates end def test_new r = process('new', 'id' => 'NewPage', 'web' => 'wiki1') - assert_success + assert_response :success assert_equal 'AnonymousCoward', r.template_objects['author'] assert_equal 'NewPage', r.template_objects['page_name'] end @@ -264,7 +255,7 @@ class WikiControllerTest < Test::Unit::TestCase def test_pdf assert RedClothForTex.available?, 'Cannot do test_pdf when pdflatex is not available' r = process('pdf', 'web' => 'wiki1', 'id' => 'HomePage') - assert_success + assert_response :success content = r.binary_content @@ -282,23 +273,23 @@ class WikiControllerTest < Test::Unit::TestCase def test_print r = process('print', 'web' => 'wiki1', 'id' => 'HomePage') - assert_success + assert_response :success assert_equal :show, r.template_objects['link_mode'] end def test_published - @web.published = true + set_web_property :published, true r = process('published', 'web' => 'wiki1', 'id' => 'HomePage') - assert_success + assert_response :success assert_equal @home, r.template_objects['page'] end def test_published_web_not_published - @web.published = false + set_web_property :published, false r = process('published', 'web' => 'wiki1', 'id' => 'HomePage') @@ -308,11 +299,11 @@ class WikiControllerTest < Test::Unit::TestCase def test_recently_revised r = process('recently_revised', 'web' => 'wiki1') - assert_success + assert_response :success - assert_equal [], r.template_objects['categories'] + assert_equal %w(animals trees), r.template_objects['categories'] assert_nil r.template_objects['category'] - assert_equal [@home], r.template_objects['pages_in_category'] + assert_equal [@elephant, pages(:first_page), @home, pages(:my_way), pages(:no_wiki_word), @oak, pages(:smart_engine), pages(:that_way)], r.template_objects['pages_in_category'] assert_equal 'the web', r.template_objects['set_name'] end @@ -323,37 +314,33 @@ class WikiControllerTest < Test::Unit::TestCase Time.now, Author.new('AnotherAuthor', '127.0.0.2')) r = process('recently_revised', 'web' => 'wiki1') - assert_success + assert_response :success - assert_equal ['categorized'], r.template_objects['categories'] + assert_equal %w(animals categorized trees), r.template_objects['categories'] # no category is specified in params assert_nil r.template_objects['category'] - assert_equal [@home, page2], r.template_objects['pages_in_category'], + assert_equal [@elephant, pages(:first_page), @home, pages(:my_way), pages(:no_wiki_word), @oak, page2, pages(:smart_engine), pages(:that_way)], r.template_objects['pages_in_category'], "Pages are not as expected: " + r.template_objects['pages_in_category'].map {|p| p.name}.inspect assert_equal 'the web', r.template_objects['set_name'] end def test_recently_revised_with_categorized_page_multiple_categories - setup_wiki_with_three_pages - r = process('recently_revised', 'web' => 'wiki1') - assert_success + assert_response :success assert_equal ['animals', 'trees'], r.template_objects['categories'] # no category is specified in params assert_nil r.template_objects['category'] - assert_equal [@elephant, @home, @oak], r.template_objects['pages_in_category'], + assert_equal [@elephant, pages(:first_page), @home, pages(:my_way), pages(:no_wiki_word), @oak, pages(:smart_engine), pages(:that_way)], r.template_objects['pages_in_category'], "Pages are not as expected: " + r.template_objects['pages_in_category'].map {|p| p.name}.inspect assert_equal 'the web', r.template_objects['set_name'] end def test_recently_revised_with_specified_category - setup_wiki_with_three_pages - r = process('recently_revised', 'web' => 'wiki1', 'category' => 'animals') - assert_success + assert_response :success assert_equal ['animals', 'trees'], r.template_objects['categories'] # no category is specified in params @@ -366,7 +353,7 @@ class WikiControllerTest < Test::Unit::TestCase def test_revision r = process 'revision', 'web' => 'wiki1', 'id' => 'HomePage', 'rev' => '0' - assert_success + assert_response :success assert_equal @home, r.template_objects['page'] assert_equal @home.revisions[0], r.template_objects['revision'] end @@ -377,27 +364,24 @@ class WikiControllerTest < Test::Unit::TestCase # its assigns the same as or revision r = process 'rollback', 'web' => 'wiki1', 'id' => 'HomePage', 'rev' => '0' - assert_success + assert_response :success assert_equal @home, r.template_objects['page'] assert_equal @home.revisions[0], r.template_objects['revision'] end def test_rss_with_content - setup_wiki_with_three_pages - r = process 'rss_with_content', 'web' => 'wiki1' - assert_success + assert_response :success pages = r.template_objects['pages_by_revision'] - assert_equal [@home, @oak, @elephant], pages, + assert_equal [@elephant, @oak, pages(:no_wiki_word), pages(:that_way), pages(:smart_engine), pages(:my_way), pages(:first_page), @home], pages, "Pages are not as expected: #{pages.map {|p| p.name}.inspect}" assert !r.template_objects['hide_description'] end def test_rss_with_content_when_blocked - setup_wiki_with_three_pages - @web.password = 'aaa' - @web.published = false + @web.update_attributes(:password => 'aaa', :published => false) + @web = Web.find(@web.id) r = process 'rss_with_content', 'web' => 'wiki1' @@ -406,7 +390,6 @@ class WikiControllerTest < Test::Unit::TestCase def test_rss_with_headlines - setup_wiki_with_three_pages @title_with_spaces = @wiki.write_page('wiki1', 'Title With Spaces', 'About spaces', 1.hour.ago, Author.new('TreeHugger', '127.0.0.2')) @@ -415,19 +398,24 @@ class WikiControllerTest < Test::Unit::TestCase r = process 'rss_with_headlines', 'web' => 'wiki1' - assert_success + assert_response :success pages = r.template_objects['pages_by_revision'] - assert_equal [@home, @oak, @elephant, @title_with_spaces], pages, - "Pages are not as expected: #{pages.map {|p| p.name}.inspect}" + assert_equal [@elephant, @title_with_spaces, @oak, pages(:no_wiki_word), pages(:that_way), pages(:smart_engine), pages(:my_way), pages(:first_page), @home], pages, "Pages are not as expected: #{pages.map {|p| p.name}.inspect}" assert r.template_objects['hide_description'] xml = REXML::Document.new(r.body) expected_page_links = - ['http://localhost:8080/wiki1/show/HomePage', + ['http://localhost:8080/wiki1/show/Elephant', + 'http://localhost:8080/wiki1/show/Title+With+Spaces', 'http://localhost:8080/wiki1/show/Oak', - 'http://localhost:8080/wiki1/show/Elephant', - 'http://localhost:8080/wiki1/show/Title+With+Spaces'] + 'http://localhost:8080/wiki1/show/NoWikiWord', + 'http://localhost:8080/wiki1/show/ThatWay', + 'http://localhost:8080/wiki1/show/SmartEngine', + 'http://localhost:8080/wiki1/show/MyWay', + 'http://localhost:8080/wiki1/show/FirstPage', + 'http://localhost:8080/wiki1/show/HomePage', + ] assert_template_xpath_match '/rss/channel/link', 'http://localhost:8080/wiki1/show/HomePage' @@ -436,22 +424,26 @@ class WikiControllerTest < Test::Unit::TestCase end def test_rss_switch_links_to_published - setup_wiki_with_three_pages - @web.password = 'aaa' - @web.published = true + @web.update_attributes(:password => 'aaa', :published => true) + @web = Web.find(@web.id) @request.host = 'foo.bar.info' @request.port = 80 r = process 'rss_with_headlines', 'web' => 'wiki1' - assert_success + assert_response :success xml = REXML::Document.new(r.body) expected_page_links = - ['http://foo.bar.info/wiki1/published/HomePage', + ['http://foo.bar.info/wiki1/published/Elephant', 'http://foo.bar.info/wiki1/published/Oak', - 'http://foo.bar.info/wiki1/published/Elephant'] + 'http://foo.bar.info/wiki1/published/NoWikiWord', + 'http://foo.bar.info/wiki1/published/ThatWay', + 'http://foo.bar.info/wiki1/published/SmartEngine', + 'http://foo.bar.info/wiki1/published/MyWay', + 'http://foo.bar.info/wiki1/published/FirstPage', + 'http://foo.bar.info/wiki1/published/HomePage'] assert_template_xpath_match '/rss/channel/link', 'http://foo.bar.info/wiki1/published/HomePage' @@ -463,45 +455,43 @@ class WikiControllerTest < Test::Unit::TestCase setup_wiki_with_30_pages r = process 'rss_with_headlines', 'web' => 'wiki1' - assert_success + assert_response :success pages = r.template_objects['pages_by_revision'] assert_equal 15, pages.size, 15 r = process 'rss_with_headlines', 'web' => 'wiki1', 'limit' => '5' - assert_success + assert_response :success pages = r.template_objects['pages_by_revision'] assert_equal 5, pages.size r = process 'rss_with_headlines', 'web' => 'wiki1', 'limit' => '25' - assert_success + assert_response :success pages = r.template_objects['pages_by_revision'] assert_equal 25, pages.size r = process 'rss_with_headlines', 'web' => 'wiki1', 'limit' => 'all' - assert_success + assert_response :success pages = r.template_objects['pages_by_revision'] - assert_equal 31, pages.size + assert_equal 38, pages.size r = process 'rss_with_headlines', 'web' => 'wiki1', 'start' => '1976-10-16' - assert_success + assert_response :success pages = r.template_objects['pages_by_revision'] - assert_equal 16, pages.size + assert_equal 23, pages.size r = process 'rss_with_headlines', 'web' => 'wiki1', 'end' => '1976-10-16' - assert_success + assert_response :success pages = r.template_objects['pages_by_revision'] assert_equal 15, pages.size r = process 'rss_with_headlines', 'web' => 'wiki1', 'start' => '1976-10-01', 'end' => '1976-10-06' - assert_success + assert_response :success pages = r.template_objects['pages_by_revision'] assert_equal 5, pages.size end def test_rss_title_with_ampersand - # was ticket:143 - setup_wiki_with_three_pages - + # was ticket:143 @wiki.write_page('wiki1', 'Title&With&Ampersands', 'About spaces', 1.hour.ago, Author.new('NitPicker', '127.0.0.3')) @@ -511,15 +501,12 @@ class WikiControllerTest < Test::Unit::TestCase assert r.body.include?('Title&With&Ampersands') end - def test_rss_timestamp - setup_wiki_with_three_pages - + def test_rss_timestamp new_page = @wiki.write_page('wiki1', 'PageCreatedAtTheBeginningOfCtime', 'Created on 1 Jan 1970 at 0:00:00 Z', Time.at(0), Author.new('NitPicker', '127.0.0.3')) r = process 'rss_with_headlines', 'web' => 'wiki1' - - assert_template_xpath_match '/rss/channel/item/pubDate[4]', "Thu, 01 Jan 1970 00:00:00 Z" + assert_template_xpath_match '/rss/channel/item/pubDate[9]', "Thu, 01 Jan 1970 00:00:00 Z" end def test_save @@ -535,6 +522,7 @@ class WikiControllerTest < Test::Unit::TestCase def test_save_new_revision_of_existing_page @home.lock(Time.now, 'Batman') + current_revisions = @home.revisions.size r = process 'save', 'web' => 'wiki1', 'id' => 'HomePage', 'content' => 'Revised HomePage', 'author' => 'Batman' @@ -542,8 +530,7 @@ class WikiControllerTest < Test::Unit::TestCase assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage' assert_equal ['Batman'], r.cookies['author'].value home_page = @wiki.read_page('wiki1', 'HomePage') - assert_equal [home_page], @web.pages.values - assert_equal 2, home_page.revisions.size + assert_equal current_revisions+1, home_page.revisions.size assert_equal 'Revised HomePage', home_page.content assert_equal 'Batman', home_page.author assert !home_page.locked?(Time.now) @@ -563,57 +550,47 @@ class WikiControllerTest < Test::Unit::TestCase revisions_after = @home.revisions.size assert_equal revisions_before, revisions_after + @home = Page.find(@home.id) assert !@home.locked?(Time.now), 'HomePage should be unlocked if an edit was unsuccessful' end def test_search - setup_wiki_with_three_pages - r = process 'search', 'web' => 'wiki1', 'query' => '\s[A-Z]ak' assert_redirected_to :action => 'show', :id => 'Oak' end def test_search_multiple_results - setup_wiki_with_three_pages - r = process 'search', 'web' => 'wiki1', 'query' => 'All about' - assert_success + assert_response :success assert_equal 'All about', r.template_objects['query'] assert_equal [@elephant, @oak], r.template_objects['results'] assert_equal [], r.template_objects['title_results'] end def test_search_by_content_and_title - setup_wiki_with_three_pages - r = process 'search', 'web' => 'wiki1', 'query' => '(Oak|Elephant)' - assert_success + assert_response :success assert_equal '(Oak|Elephant)', r.template_objects['query'] assert_equal [@elephant, @oak], r.template_objects['results'] assert_equal [@elephant, @oak], r.template_objects['title_results'] end def test_search_zero_results - setup_wiki_with_three_pages - r = process 'search', 'web' => 'wiki1', 'query' => 'non-existant text' - assert_success + assert_response :success assert_equal [], r.template_objects['results'] assert_equal [], r.template_objects['title_results'] end - - - def test_show_page - r = process('show', 'id' => 'HomePage', 'web' => 'wiki1') - assert_success - assert_match /First revision of the end/, r.body + r = process('show', 'id' => 'Oak', 'web' => 'wiki1') + assert_response :success + assert_tag :content => /All about oak/ end def test_show_page_with_multiple_revisions @@ -622,7 +599,7 @@ class WikiControllerTest < Test::Unit::TestCase r = process('show', 'id' => 'HomePage', 'web' => 'wiki1') - assert_success + assert_response :success assert_match /Second revision of the end/, r.body end @@ -633,24 +610,25 @@ class WikiControllerTest < Test::Unit::TestCase def test_show_no_page r = process('show', 'id' => '', 'web' => 'wiki1') - assert_equal 404, r.response_code + assert_response :missing r = process('show', 'web' => 'wiki1') - assert_equal 404, r.response_code + assert_response :missing end def test_tex r = process('tex', 'web' => 'wiki1', 'id' => 'HomePage') - assert_success + assert_response :success assert_equal "\\documentclass[12pt,titlepage]{article}\n\n\\usepackage[danish]{babel} " + "%danske tekster\n\\usepackage[OT1]{fontenc} %rigtige danske bogstaver...\n" + "\\usepackage{a4}\n\\usepackage{graphicx}\n\\usepackage{ucs}\n\\usepackage[utf8x]" + "{inputenc}\n\\input epsf \n\n%----------------------------------------------------" + "---------------\n\n\\begin{document}\n\n\\sloppy\n\n%-----------------------------" + - "--------------------------------------\n\n\\section*{HomePage}\n\nFirst revision of " + - "the HomePage end\n\n\\end{document}", r.body + "--------------------------------------\n\n\\section*{HomePage}\n\nHisWay would be " + + "MyWay in kinda ThatWay in HisWay though MyWay \\OverThere -- see SmartEngine in that " + + "SmartEngineGUI\n\n\\end{document}", r.body end @@ -659,8 +637,8 @@ class WikiControllerTest < Test::Unit::TestCase r = process('web_list') - assert_success - assert_equal [another_wiki, @web], r.template_objects['webs'] + assert_response :success + assert_equal [another_wiki, webs(:instiki), @web], r.template_objects['webs'] end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 19219ce7..0b29f757 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -10,17 +10,97 @@ require 'active_record/fixtures' require 'action_controller/test_process' require 'action_web_service/test_invoke' require 'breakpoint' +require 'wiki_content' +# Uncomment these and hang on, because the tests will be FAST +#Test::Unit::TestCase.pre_loaded_fixtures = false +#Test::Unit::TestCase.use_transactional_fixtures = true + +Test::Unit::TestCase.use_instantiated_fixtures = false Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/" class Test::Unit::TestCase - # Turn these on to use transactional fixtures with table_name(:fixture_name) instantiation of fixtures - # self.use_transactional_fixtures = true - # self.use_instantiated_fixtures = false - def create_fixtures(*table_names) Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures", table_names) end # Add more helper methods to be used by all tests here... -end \ No newline at end of file + def set_web_property(property, value) + @web.update_attribute(property, value) + @page = Page.find(@page.id) + @wiki.webs[@web.name] = @web + end + + def setup_wiki_with_30_pages + ActiveRecord::Base.silence do + (1..30).each do |i| + @wiki.write_page('wiki1', "page#{i}", "Test page #{i}\ncategory: test", + Time.local(1976, 10, i, 12, 00, 00), Author.new('Dema', '127.0.0.2')) + end + end + @web = Web.find(@web.id) + end + + def use_blank_wiki + Revision.destroy_all + Page.destroy_all + Web.destroy_all + end +end + +# This module is to be included in unit tests that involve matching chunks. +# It provides a easy way to test whether a chunk matches a particular string +# and any the values of any fields that should be set after a match. +class ContentStub < String + include ChunkManager + def initialize(str) + super + init_chunk_manager + end + def page_link(*); end +end + +module ChunkMatch + + # Asserts a number of tests for the given type and text. + def match(chunk_type, test_text, expected_chunk_state) + if chunk_type.respond_to? :pattern + assert_match(chunk_type.pattern, test_text) + end + + content = ContentStub.new(test_text) + chunk_type.apply_to(content) + + # Test if requested parts are correct. + expected_chunk_state.each_pair do |a_method, expected_value| + assert content.chunks.last.kind_of?(chunk_type) + assert_respond_to(content.chunks.last, a_method) + assert_equal(expected_value, content.chunks.last.send(a_method.to_sym), + "Wrong #{a_method} value") + end + end + + # Asserts that test_text doesn't match the chunk_type + def no_match(chunk_type, test_text) + if chunk_type.respond_to? :pattern + assert_no_match(chunk_type.pattern, test_text) + end + end +end + +if defined? $validate_xml_in_assert_success and $validate_xml_in_assert_success == true + module Test + module Unit + module Assertions + unless method_defined? :__assert_success_before_override_by_instiki + alias :__assert_success_before_override_by_instiki :assert_success + end + def assert_success + __assert_success_before_override_by_instiki + if @response.body.kind_of?(Proc) then # it's a file download, not an HTML content + else assert_nothing_raised(@response.body) { REXML::Document.new(@response.body) } end + end + end + end + end +end diff --git a/test/unit/diff_test.rb b/test/unit/diff_test.rb index 3a0f35a5..90d0e79c 100755 --- a/test/unit/diff_test.rb +++ b/test/unit/diff_test.rb @@ -1,6 +1,6 @@ -#!/bin/env ruby -w +#!/bin/env ruby -require File.dirname(__FILE__) + '/../test_helper' +require File.expand_path(File.dirname(__FILE__) + '/../test_helper') require 'diff' include Diff diff --git a/test/unit/file_yard_test.rb b/test/unit/file_yard_test.rb index f40c0588..9d76bd7c 100755 --- a/test/unit/file_yard_test.rb +++ b/test/unit/file_yard_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby -w +#!/bin/env ruby require File.dirname(__FILE__) + '/../test_helper' require 'fileutils' @@ -67,4 +67,4 @@ class FileYardTest < Test::Unit::TestCase "#{RAILS_ROOT}/storage/test/instiki" end -end \ No newline at end of file +end diff --git a/test/unit/page_test.rb b/test/unit/page_test.rb index 8af94246..02380175 100644 --- a/test/unit/page_test.rb +++ b/test/unit/page_test.rb @@ -1,7 +1,78 @@ -require File.dirname(__FILE__) + '/../test_helper' +require File.expand_path(File.dirname(__FILE__) + '/../test_helper') class PageTest < Test::Unit::TestCase - - fixtures 'webs', 'pages', 'revisions' + fixtures :webs, :pages, :revisions, :system + def setup + @page = pages(:first_page) + end + + def test_lock + assert !@page.locked?(Time.local(2004, 4, 4, 16, 50)) + + @page.lock(Time.local(2004, 4, 4, 16, 30), "DavidHeinemeierHansson") + + assert @page.locked?(Time.local(2004, 4, 4, 16, 50)) + assert !@page.locked?(Time.local(2004, 4, 4, 17, 1)) + + @page.unlock + + assert !@page.locked?(Time.local(2004, 4, 4, 16, 50)) + end + + def test_lock_duration + @page.lock(Time.local(2004, 4, 4, 16, 30), "DavidHeinemeierHansson") + + assert_equal 15, @page.lock_duration(Time.local(2004, 4, 4, 16, 45)) + end + + def test_plain_name + assert_equal "First Page", @page.plain_name + end + + def test_revise + @page.revise('HisWay would be MyWay in kinda lame', Time.local(2004, 4, 4, 16, 55), 'MarianneSyhler') + assert_equal 2, @page.revisions(true).length, 'Should have two revisions' + assert_equal 'MarianneSyhler', @page.current_revision(true).author.to_s, 'Mary should be the author now' + assert_equal 'DavidHeinemeierHansson', @page.revisions.first.author.to_s, 'David was the first author' + end + + def test_revise_continous_revision + @page.revise('HisWay would be MyWay in kinda lame', Time.local(2004, 4, 4, 16, 55), 'MarianneSyhler') + assert_equal 2, @page.revisions(true).length + + @page.current_revision(true) + @page.revise('HisWay would be MyWay in kinda update', Time.local(2004, 4, 4, 16, 57), 'MarianneSyhler') + assert_equal 2, @page.revisions(true).length + assert_equal 'HisWay would be MyWay in kinda update', @page.revisions.last.content + assert_equal Time.local(2004, 4, 4, 16, 57), @page.revisions.last.created_at + + @page.revise('HisWay would be MyWay in the house', Time.local(2004, 4, 4, 16, 58), 'DavidHeinemeierHansson') + assert_equal 3, @page.revisions(true).length + assert_equal 'HisWay would be MyWay in the house', @page.revisions.last.content + + @page.revise('HisWay would be MyWay in my way', Time.local(2004, 4, 4, 17, 30), 'DavidHeinemeierHansson') + assert_equal 4, @page.revisions(true).length + end + + def test_revise_content_unchanged + last_revision_before = @page.current_revision + revisions_number_before = @page.revisions.size + + assert_raises(Instiki::ValidationError) { + @page.revise(@page.current_revision.content, Time.now, 'AlexeyVerkhovsky') + } + + assert_equal last_revision_before, @page.current_revision(true) + assert_equal revisions_number_before, @page.revisions.size + end + + def test_rollback + @page.revise("spot two", Time.now, "David") + @page.revise("spot three", Time.now + 2000, "David") + assert_equal 3, @page.revisions(true).length, "Should have three revisions" + @page.current_revision(true) + @page.rollback(1, Time.now) + assert_equal "HisWay would be MyWay in kinda ThatWay in HisWay though MyWay \\\\OverThere -- see SmartEngine in that SmartEngineGUI", @page.current_revision(true).content + end end diff --git a/test/unit/redcloth_for_tex_test.rb b/test/unit/redcloth_for_tex_test.rb index 82270303..d15071a0 100755 --- a/test/unit/redcloth_for_tex_test.rb +++ b/test/unit/redcloth_for_tex_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby -w +#!/bin/env ruby require File.dirname(__FILE__) + '/../test_helper' require 'redcloth_for_tex' @@ -66,4 +66,4 @@ EOL def test_subsection_depth assert_equal "\\subsubsection*{Hello}", RedClothForTex.new("h4. Hello").to_tex end -end \ No newline at end of file +end diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb index 0024c6b3..c3f588fa 100644 --- a/test/unit/revision_test.rb +++ b/test/unit/revision_test.rb @@ -1,7 +1,315 @@ -require File.dirname(__FILE__) + '/../test_helper' +require File.expand_path(File.dirname(__FILE__) + '/../test_helper') class RevisionTest < Test::Unit::TestCase + fixtures :webs, :pages, :revisions, :system - fixtures 'webs', 'pages', 'revisions' + def setup + @wiki = Wiki.new + @web = webs(:test_wiki) + @page = pages(:home_page) + @revision = revisions(:home_page_second_revision) + end + + def test_wiki_words + assert_equal %w( HisWay MyWay SmartEngine SmartEngineGUI ThatWay ), @revision.wiki_words.sort + + @wiki.write_page('wiki1', 'NoWikiWord', 'hey you!', Time.now, 'Me') + assert_equal [], @wiki.read_page('wiki1', 'NoWikiWord').wiki_words + end + def test_existing_pages + assert_equal %w( MyWay SmartEngine ThatWay ), @revision.existing_pages.sort + end + + def test_unexisting_pages + assert_equal %w( HisWay SmartEngineGUI ), @revision.unexisting_pages.sort + end + + def test_content_with_wiki_links + assert_equal '

His Way? ' + + 'would be My Way in kinda ' + + 'That Way in ' + + 'His Way? ' + + 'though My Way OverThere—see ' + + 'Smart Engine in that ' + + 'Smart Engine GUI' + + '?

', + @revision.display_content + end + + def test_markdown + set_web_property :markup, :markdown + + assert_markup_parsed_as( + %{

My Headline

\n\n

that } + + %{Smart Engine GUI?

}, + "My Headline\n===========\n\nthat SmartEngineGUI") + + code_block = [ + 'This is a code block:', + '', + ' def a_method(arg)', + ' return ThatWay', + '', + 'Nice!' + ].join("\n") + + assert_markup_parsed_as( + %{

This is a code block:

\n\n
def a_method(arg)\n} +
+        %{return ThatWay\n
\n\n

Nice!

}, + code_block) + end + + def test_markdown_hyperlink_with_slash + # in response to a bug, see http://dev.instiki.org/attachment/ticket/177 + set_web_property :markup, :markdown + + assert_markup_parsed_as( + '

text

', + '[text](http://example/with/slash)') + end + + def test_mixed_formatting + textile_and_markdown = [ + 'Markdown heading', + '================', + '', + 'h2. Textile heading', + '', + '*some* **text** _with_ -styles-', + '', + '* list 1', + '* list 2' + ].join("\n") + + set_web_property :markup, :markdown + assert_markup_parsed_as( + "

Markdown heading

\n\n" + + "

h2. Textile heading

\n\n" + + "

some text with -styles-

\n\n" + + "
    \n
  • list 1
  • \n
  • list 2
  • \n
", + textile_and_markdown) + + set_web_property :markup, :textile + assert_markup_parsed_as( + "

Markdown heading
================

\n\n\n\t

Textile heading

" + + "\n\n\n\t

some text with styles

" + + "\n\n\n\t
    \n\t
  • list 1
  • \n\t\t
  • list 2
  • \n\t
", + textile_and_markdown) + + set_web_property :markup, :mixed + assert_markup_parsed_as( + "

Markdown heading

\n\n\n\t

Textile heading

\n\n\n\t" + + "

some text with styles

\n\n\n\t" + + "
    \n\t
  • list 1
  • \n\t\t
  • list 2
  • \n\t
", + textile_and_markdown) + end + + def test_rdoc + set_web_property :markup, :rdoc + + @revision = Revision.new(:page => @page, :content => '+hello+ that SmartEngineGUI', + :author => Author.new('DavidHeinemeierHansson')) + + assert_equal "hello that Smart Engine GUI" + + "?\n\n", @revision.display_content + end + + def test_content_with_auto_links + assert_markup_parsed_as( + '

http://www.loudthinking.com/ ' + + 'points to That Way from ' + + 'david@loudthinking.com

', + 'http://www.loudthinking.com/ points to ThatWay from david@loudthinking.com') + + end + + def test_content_with_aliased_links + assert_markup_parsed_as( + '

Would a clever motor' + + ' go by any other name?

', + 'Would a [[SmartEngine|clever motor]] go by any other name?') + end + + def test_content_with_wikiword_in_em + assert_markup_parsed_as( + '

should we go ' + + 'That Way or This Way?' + + '

', + '_should we go ThatWay or ThisWay _') + end + + def test_content_with_wikiword_in_tag + assert_markup_parsed_as( + '

That is some Stylish Emphasis

', + 'That is some Stylish Emphasis') + end + + def test_content_with_escaped_wikiword + # there should be no wiki link + assert_markup_parsed_as('

WikiWord

', '\WikiWord') + end + + def test_content_with_pre_blocks + assert_markup_parsed_as( + '

A class SmartEngine end would not mark up

CodeBlocks

', + 'A class SmartEngine end would not mark up
CodeBlocks
') + end + + def test_content_with_autolink_in_parentheses + assert_markup_parsed_as( + '

The W3C body (' + + 'http://www.w3c.org) sets web standards

', + 'The W3C body (http://www.w3c.org) sets web standards') + end + + def test_content_with_link_in_parentheses + assert_markup_parsed_as( + '

(What is a wiki?)

', + '("What is a wiki?":http://wiki.org/wiki.cgi?WhatIsWiki)') + end + + def test_content_with_image_link + assert_markup_parsed_as( + '

This is a Textile image link.

', + 'This !http://hobix.com/sample.jpg! is a Textile image link.') + end + + def test_content_with_inlined_img_tag + assert_markup_parsed_as( + '

This is an inline image link.

', + 'This is an inline image link.') + + assert_markup_parsed_as( + '

This is an inline image link.

', + 'This is an inline image link.') + end + + def test_nowiki_tag + assert_markup_parsed_as( + '

Do not mark up [[this text]] or http://www.thislink.com.

', + 'Do not mark up [[this text]] ' + + 'or http://www.thislink.com.') + end + + def test_multiline_nowiki_tag + assert_markup_parsed_as( + "

Do not mark \n up [[this text]] \nand http://this.url.com but markup " + + 'this?

', + "Do not mark \n up [[this text]] \n" + + "and http://this.url.com but markup [[this]]") + end + + def test_content_with_bracketted_wiki_word + set_web_property :brackets_only, true + assert_markup_parsed_as( + '

This is a WikiWord and a tricky name ' + + 'Sperberg-McQueen?.

', + 'This is a WikiWord and a tricky name [[Sperberg-McQueen]].') + end + + def test_content_for_export + assert_equal '

His Way would be ' + + 'My Way in kinda ' + + 'That Way in ' + + 'His Way though ' + + 'My Way OverThere—see ' + + 'Smart Engine in that ' + + 'Smart Engine GUI

', + @revision.display_content_for_export + end + + def test_double_replacing + @revision.content = "VersionHistory\r\n\r\ncry VersionHistory" + assert_equal '

Version History' + + "?

\n\n\n\t

cry " + + 'Version History?' + + '

', + @revision.display_content + + @revision.clear_display_cache + + @revision.content = "f\r\nVersionHistory\r\n\r\ncry VersionHistory" + assert_equal "

f
Version History" + + "?

\n\n\n\t

cry " + + "Version History?" + + "

", + @revision.display_content + end + + def test_difficult_wiki_words + @revision.content = "[[It's just awesome GUI!]]" + assert_equal "

It's just awesome GUI!" + + "?

", + @revision.display_content + end + + def test_revisions_diff + Revision.create(:page => @page, :content => 'What a blue and lovely morning', :author => Author.new('DavidHeinemeierHansson')) + Revision.create(:page => @page, :content => 'What a red and lovely morning today', :author => Author.new('DavidHeinemeierHansson')) + + assert_equal "

What a blue red " + + "and lovely morningmorning " + + "today

", @page.revisions.last.display_diff + end + + def test_link_to_file + assert_markup_parsed_as( + '

doc.pdf?

', + '[[doc.pdf:file]]') + end + + def test_link_to_pic + FileUtils.mkdir_p "#{RAILS_ROOT}/storage/test/wiki1" + FileUtils.rm(Dir["#{RAILS_ROOT}/storage/test/wiki1/*"]) + @wiki.file_yard(@web).upload_file('square.jpg', StringIO.new('')) + assert_markup_parsed_as( + '

Square

', + '[[square.jpg|Square:pic]]') + assert_markup_parsed_as( + '

square.jpg

', + '[[square.jpg:pic]]') + end + + def test_link_to_non_existant_pic + assert_markup_parsed_as( + '

NonExistant?' + + '

', + '[[NonExistant.jpg|NonExistant:pic]]') + assert_markup_parsed_as( + '

NonExistant.jpg?' + + '

', + '[[NonExistant.jpg:pic]]') + end + + def test_wiki_link_with_colon + assert_markup_parsed_as( + '

With:Colon?

', + '[[With:Colon]]') + end + + # TODO Remove the leading underscores from this test when upgrading to RedCloth 3.0.1; + # also add a test for the "Unhappy Face" problem (another interesting RedCloth bug) + def test_list_with_tildas + list_with_tildas = <<-EOL + * "a":~b + * c~ d + EOL + + assert_markup_parsed_as( + "
    \n\t
  • a
  • \n\t\t
  • c~ d
  • \n\t
", + list_with_tildas) + end + + def test_textile_image_in_mixed_wiki + set_web_property :markup, :mixed + assert_markup_parsed_as( + "

\"\"\nss

", + "!http://google.com!\r\nss") + end + + def assert_markup_parsed_as(expected_output, input) + revision = Revision.new(:page => @page, :content => input, :author => Author.new('AnAuthor')) + assert_equal expected_output, revision.display_content, 'Textile output not as expected' + end end diff --git a/test/unit/uri_test.rb b/test/unit/uri_test.rb index 9cef4a23..4affbd60 100755 --- a/test/unit/uri_test.rb +++ b/test/unit/uri_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby -w +#!/bin/env ruby require File.dirname(__FILE__) + '/../test_helper' require 'chunks/uri' diff --git a/test/unit/web_test.rb b/test/unit/web_test.rb index 2e504e0c..433b579f 100644 --- a/test/unit/web_test.rb +++ b/test/unit/web_test.rb @@ -1,7 +1,158 @@ -require File.dirname(__FILE__) + '/../test_helper' +require File.expand_path(File.dirname(__FILE__) + '/../test_helper') class WebTest < Test::Unit::TestCase - - fixtures 'webs', 'pages', 'revisions' + fixtures :webs, :pages, :revisions, :system + def setup + @web = webs(:instiki) + end + + def test_wiki_word_linking + @web.add_page('SecondPage', 'Yo, yo. Have you EverBeenHated', + Time.now, 'DavidHeinemeierHansson') + + assert_equal('

Yo, yo. Have you Ever Been Hated' + + '?

', + @web.page("SecondPage").display_content) + + @web.add_page('EverBeenHated', 'Yo, yo. Have you EverBeenHated', Time.now, + 'DavidHeinemeierHansson') + assert_equal('

Yo, yo. Have you Ever Been Hated

', + @web.page("SecondPage").display_content) + end + + def test_pages_by_revision + add_sample_pages + assert_equal 'EverBeenHated', @web.select.by_revision.first.name + end + + def test_pages_by_match + add_sample_pages + assert_equal 2, @web.select { |page| page.content =~ /me/i }.length + assert_equal 1, @web.select { |page| page.content =~ /Who/i }.length + assert_equal 0, @web.select { |page| page.content =~ /none/i }.length + end + + def test_references + add_sample_pages + assert_equal 1, @web.select.pages_that_reference('EverBeenHated').length + assert_equal 0, @web.select.pages_that_reference('EverBeenInLove').length + end + + def test_delete + add_sample_pages + assert_equal 2, @web.pages.length + @web.remove_pages([ @web.page('EverBeenInLove') ]) + assert_equal 1, @web.pages(true).length + end + + def test_make_link + add_sample_pages + + existing_page_wiki_url = + 'Ever Been In Love' + existing_page_published_url = + 'Ever Been In Love' + existing_page_static_url = + 'Ever Been In Love' + new_page_wiki_url = + 'Unknown Word?' + new_page_published_url = + new_page_static_url = + 'Unknown Word' + + # no options + assert_equal existing_page_wiki_url, @web.make_link('EverBeenInLove') + + # :mode => :export + assert_equal existing_page_static_url, @web.make_link('EverBeenInLove', nil, :mode => :export) + + # :mode => :publish + assert_equal existing_page_published_url, + @web.make_link('EverBeenInLove', nil, :mode => :publish) + + # new page, no options + assert_equal new_page_wiki_url, @web.make_link('UnknownWord') + + # new page, :mode => :export + assert_equal new_page_static_url, @web.make_link('UnknownWord', nil, :mode => :export) + + # new page, :mode => :publish + assert_equal new_page_published_url, @web.make_link('UnknownWord', nil, :mode => :publish) + + # Escaping special characters in the name + assert_equal( + 'Smith & Wesson?', + @web.make_link('Smith & Wesson')) + + # optionally using text as the link text + assert_equal( + existing_page_published_url.sub(/>Ever Been In LoveHaven't you ever been in love?<"), + @web.make_link('EverBeenInLove', "Haven't you ever been in love?", :mode => :publish)) + + end + + def test_initialize + web = Web.new(:name => 'Wiki2', :address => 'wiki2', :password => '123') + + assert_equal 'Wiki2', web.name + assert_equal 'wiki2', web.address + assert_equal '123', web.password + + # new web should be set for maximum features enabled + assert_equal :textile, web.markup + assert_equal '008B26', web.color + assert !web.safe_mode? + assert_equal([], web.pages) + assert web.allow_uploads? + assert_nil web.additional_style + assert !web.published? + assert !web.brackets_only? + assert !web.count_pages? + assert_equal 100, web.max_upload_size + end + + def test_initialize_invalid_name + assert_raises(Instiki::ValidationError) { + Web.create(:name => 'Wiki2', :address => "wiki\234", :password => '123') + } + end + + def test_new_page_linked_from_mother_page + # this was a bug in revision 204 + home = @web.add_page('HomePage', 'This page refers to AnotherPage', + Time.local(2004, 4, 4, 16, 50), 'Alexey Verkhovsky') + @web.add_page('AnotherPage', 'This is \AnotherPage', + Time.local(2004, 4, 4, 16, 51), 'Alexey Verkhovsky') + + @web.pages(true) + assert_equal [home], @web.select.pages_that_link_to('AnotherPage') + end + + def test_orphaned_pages + add_sample_pages + home = @web.add_page('HomePage', + 'This is a home page, it should not be an orphan', + Time.local(2004, 4, 4, 16, 50), 'AlexeyVerkhovsky') + author = @web.add_page('AlexeyVerkhovsky', + 'This is an author page, it should not be an orphan', + Time.local(2004, 4, 4, 16, 50), 'AlexeyVerkhovsky') + self_linked = @web.add_page('SelfLinked', + 'I am me SelfLinked and link to EverBeenInLove', + Time.local(2004, 4, 4, 16, 50), 'AnonymousCoward') + + # page that links to itself, and nobody else links to it must be an orphan + assert_equal ['EverBeenHated', 'SelfLinked'], + @web.select.orphaned_pages.collect{ |page| page.name }.sort + end + + private + + def add_sample_pages + @in_love = @web.add_page('EverBeenInLove', 'Who am I me', + Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson') + @hated = @web.add_page('EverBeenHated', 'I am me EverBeenHated', + Time.local(2004, 4, 4, 16, 51), 'DavidHeinemeierHansson') + end end diff --git a/test/unit/wiki_words_test.rb b/test/unit/wiki_words_test.rb index a1aa1ff9..93bc5d12 100755 --- a/test/unit/wiki_words_test.rb +++ b/test/unit/wiki_words_test.rb @@ -1,6 +1,6 @@ -#!/bin/env ruby -w +#!/bin/env ruby -require File.dirname(__FILE__) + '/../test_helper' +require File.expand_path(File.dirname(__FILE__) + '/../test_helper') require 'wiki_words' class WikiWordsTest < Test::Unit::TestCase From 6832b2edf9edde74819c562eb1265a938d12f7f3 Mon Sep 17 00:00:00 2001 From: Rick Okin Date: Tue, 9 Aug 2005 02:20:50 +0000 Subject: [PATCH 13/84] removed old models' --- app/models-old/author.rb | 4 - app/models-old/chunks/category.rb | 33 ----- app/models-old/chunks/chunk.rb | 86 ----------- app/models-old/chunks/engines.rb | 61 -------- app/models-old/chunks/include.rb | 41 ------ app/models-old/chunks/literal.rb | 31 ---- app/models-old/chunks/nowiki.rb | 28 ---- app/models-old/chunks/test.rb | 18 --- app/models-old/chunks/uri.rb | 182 ------------------------ app/models-old/chunks/wiki.rb | 141 ------------------ app/models-old/file_yard.rb | 58 -------- app/models-old/page.rb | 120 ---------------- app/models-old/page_lock.rb | 23 --- app/models-old/page_set.rb | 89 ------------ app/models-old/revision.rb | 127 ----------------- app/models-old/web.rb | 184 ------------------------ app/models-old/wiki_content.rb | 205 -------------------------- app/models-old/wiki_service.rb | 229 ------------------------------ app/models-old/wiki_words.rb | 23 --- 19 files changed, 1683 deletions(-) delete mode 100644 app/models-old/author.rb delete mode 100644 app/models-old/chunks/category.rb delete mode 100644 app/models-old/chunks/chunk.rb delete mode 100644 app/models-old/chunks/engines.rb delete mode 100644 app/models-old/chunks/include.rb delete mode 100644 app/models-old/chunks/literal.rb delete mode 100644 app/models-old/chunks/nowiki.rb delete mode 100644 app/models-old/chunks/test.rb delete mode 100644 app/models-old/chunks/uri.rb delete mode 100644 app/models-old/chunks/wiki.rb delete mode 100644 app/models-old/file_yard.rb delete mode 100644 app/models-old/page.rb delete mode 100644 app/models-old/page_lock.rb delete mode 100644 app/models-old/page_set.rb delete mode 100644 app/models-old/revision.rb delete mode 100644 app/models-old/web.rb delete mode 100644 app/models-old/wiki_content.rb delete mode 100644 app/models-old/wiki_service.rb delete mode 100644 app/models-old/wiki_words.rb diff --git a/app/models-old/author.rb b/app/models-old/author.rb deleted file mode 100644 index 258cc2b8..00000000 --- a/app/models-old/author.rb +++ /dev/null @@ -1,4 +0,0 @@ -class Author < String - attr_accessor :ip - def initialize(name, ip) @ip = ip; super(name) end -end \ No newline at end of file diff --git a/app/models-old/chunks/category.rb b/app/models-old/chunks/category.rb deleted file mode 100644 index d08d8636..00000000 --- a/app/models-old/chunks/category.rb +++ /dev/null @@ -1,33 +0,0 @@ -require 'chunks/chunk' - -# The category chunk looks for "category: news" on a line by -# itself and parses the terms after the ':' as categories. -# Other classes can search for Category chunks within -# rendered content to find out what categories this page -# should be in. -# -# Category lines can be hidden using ':category: news', for example -class Category < Chunk::Abstract - CATEGORY_PATTERN = /^(:)?category\s*:(.*)$/i - def self.pattern() CATEGORY_PATTERN end - - attr_reader :hidden, :list - -def initialize(match_data, content) - super(match_data, content) - @hidden = match_data[1] - @list = match_data[2].split(',').map { |c| c.strip } - @unmask_text = '' - if @hidden - @unmask_text = '' - else - category_urls = @list.map { |category| url(category) }.join(', ') - @unmask_text = '
category: ' + category_urls + '
' - end - end - - # TODO move presentation of page metadata to controller/view - def url(category) - %{#{category}} - end -end diff --git a/app/models-old/chunks/chunk.rb b/app/models-old/chunks/chunk.rb deleted file mode 100644 index 9ba3cc04..00000000 --- a/app/models-old/chunks/chunk.rb +++ /dev/null @@ -1,86 +0,0 @@ -require 'uri/common' - -# A chunk is a pattern of text that can be protected -# and interrogated by a renderer. Each Chunk class has a -# +pattern+ that states what sort of text it matches. -# Chunks are initalized by passing in the result of a -# match by its pattern. - -module Chunk - class Abstract - - # automatically construct the array of derivatives of Chunk::Abstract - @derivatives = [] - - class << self - attr_reader :derivatives - end - - def self::inherited( klass ) - Abstract::derivatives << klass - end - - # the class name part of the mask strings - def self.mask_string - self.to_s.delete(':').downcase - end - - # a regexp that matches all chunk_types masks - def Abstract::mask_re(chunk_types) - tmp = chunk_types.map{|klass| klass.mask_string}.join("|") - Regexp.new("chunk([0-9a-f]+n\\d+)(#{tmp})chunk") - end - - attr_reader :text, :unmask_text, :unmask_mode - - def initialize(match_data, content) - @text = match_data[0] - @content = content - @unmask_mode = :normal - end - - # Find all the chunks of the given type in content - # Each time the pattern is matched, create a new - # chunk for it, and replace the occurance of the chunk - # in this content with its mask. - def self.apply_to(content) - content.gsub!( self.pattern ) do |match| - new_chunk = self.new($~, content) - content.add_chunk(new_chunk) - new_chunk.mask - end - end - - # should contain only [a-z0-9] - def mask - @mask ||="chunk#{@id}#{self.class.mask_string}chunk" - end - - # We should not use object_id because object_id is not guarantied - # to be unique when we restart the wiki (new object ids can equal old ones - # that were restored from madeleine storage) - def id - @id ||= "#{@content.page_id}n#{@content.chunk_id}" - end - - def unmask - @content.sub!(mask, @unmask_text) - end - - def rendered? - @unmask_mode == :normal - end - - def escaped? - @unmask_mode == :escape - end - - def revert - @content.sub!(mask, @text) - # unregister - @content.delete_chunk(self) - end - - end - -end diff --git a/app/models-old/chunks/engines.rb b/app/models-old/chunks/engines.rb deleted file mode 100644 index fe5a96a8..00000000 --- a/app/models-old/chunks/engines.rb +++ /dev/null @@ -1,61 +0,0 @@ -$: << File.dirname(__FILE__) + "../../lib" - -require 'redcloth' -require 'bluecloth_tweaked' -require 'rdocsupport' -require 'chunks/chunk' - -# The markup engines are Chunks that call the one of RedCloth -# or RDoc to convert text. This markup occurs when the chunk is required -# to mask itself. -module Engines - class AbstractEngine < Chunk::Abstract - - # Create a new chunk for the whole content and replace it with its mask. - def self.apply_to(content) - new_chunk = self.new(content) - content.replace(new_chunk.mask) - end - - private - - # Never create engines by constructor - use apply_to instead - def initialize(content) - @content = content - end - - end - - class Textile < AbstractEngine - def mask - redcloth = RedCloth.new(@content, [:hard_breaks] + @content.options[:engine_opts]) - redcloth.filter_html = false - redcloth.no_span_caps = false - redcloth.to_html(:textile) - end - end - - class Markdown < AbstractEngine - def mask - BlueCloth.new(@content, @content.options[:engine_opts]).to_html - end - end - - class Mixed < AbstractEngine - def mask - redcloth = RedCloth.new(@content, @content.options[:engine_opts]) - redcloth.filter_html = false - redcloth.no_span_caps = false - redcloth.to_html - end - end - - class RDoc < AbstractEngine - def mask - RDocSupport::RDocFormatter.new(@content).to_html - end - end - - MAP = { :textile => Textile, :markdown => Markdown, :mixed => Mixed, :rdoc => RDoc } - MAP.default = Textile -end diff --git a/app/models-old/chunks/include.rb b/app/models-old/chunks/include.rb deleted file mode 100644 index 370093cc..00000000 --- a/app/models-old/chunks/include.rb +++ /dev/null @@ -1,41 +0,0 @@ -require 'chunks/wiki' - -# Includes the contents of another page for rendering. -# The include command looks like this: "[[!include PageName]]". -# It is a WikiReference since it refers to another page (PageName) -# and the wiki content using this command must be notified -# of changes to that page. -# If the included page could not be found, a warning is displayed. - -class Include < WikiChunk::WikiReference - - INCLUDE_PATTERN = /\[\[!include\s+(.*?)\]\]\s*/i - def self.pattern() INCLUDE_PATTERN end - - - def initialize(match_data, content) - super - @page_name = match_data[1].strip - @unmask_text = get_unmask_text_avoiding_recursion_loops - end - - private - - def get_unmask_text_avoiding_recursion_loops - if refpage then - refpage.clear_display_cache - if refpage.wiki_includes.include?(@content.page_name) - # this will break the recursion - @content.delete_chunk(self) - return "Recursive include detected; #{@page_name} --> #{@content.page_name} " + - "--> #{@page_name}\n" - else - @content.merge_chunks(refpage.display_content) - return refpage.display_content.pre_rendered - end - else - return "Could not include #{@page_name}\n" - end - end - -end diff --git a/app/models-old/chunks/literal.rb b/app/models-old/chunks/literal.rb deleted file mode 100644 index 09da4005..00000000 --- a/app/models-old/chunks/literal.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'chunks/chunk' - -# These are basic chunks that have a pattern and can be protected. -# They are used by rendering process to prevent wiki rendering -# occuring within literal areas such as and
 blocks
-# and within HTML tags.
-module Literal
-
-  class AbstractLiteral < Chunk::Abstract
-
-    def initialize(match_data, content)
-      super
-      @unmask_text = @text
-    end
-
-  end
-
-  # A literal chunk that protects 'code' and 'pre' tags from wiki rendering.
-  class Pre < AbstractLiteral
-    PRE_BLOCKS = "a|pre|code"
-    PRE_PATTERN = Regexp.new('<('+PRE_BLOCKS+')\b[^>]*?>.*?', Regexp::MULTILINE)
-    def self.pattern() PRE_PATTERN end
-  end 
-
-  # A literal chunk that protects HTML tags from wiki rendering.
-  class Tags < AbstractLiteral
-    TAGS = "a|img|em|strong|div|span|table|td|th|ul|ol|li|dl|dt|dd"
-    TAGS_PATTERN = Regexp.new('<(?:'+TAGS+')[^>]*?>', Regexp::MULTILINE) 
-    def self.pattern() TAGS_PATTERN  end
-  end
-end
diff --git a/app/models-old/chunks/nowiki.rb b/app/models-old/chunks/nowiki.rb
deleted file mode 100644
index ef99ec0b..00000000
--- a/app/models-old/chunks/nowiki.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require 'chunks/chunk'
-
-# This chunks allows certain parts of a wiki page to be hidden from the
-# rest of the rendering pipeline. It should be run at the beginning
-# of the pipeline in `wiki_content.rb`.
-#
-# An example use of this chunk is to markup double brackets or
-# auto URI links:
-#  Here are [[double brackets]] and a URI: www.uri.org
-#
-# The contents of the chunks will not be processed by any other chunk
-# so the `www.uri.org` and the double brackets will appear verbatim.
-#
-# Author: Mark Reid 
-# Created: 8th June 2004
-class NoWiki < Chunk::Abstract
-
-  NOWIKI_PATTERN = Regexp.new('(.*?)', Regexp::MULTILINE)
-  def self.pattern() NOWIKI_PATTERN end
-
-  attr_reader :plain_text
-
-  def initialize(match_data, content)
-    super
-    @plain_text = @unmask_text = match_data[1]
-  end
-
-end
diff --git a/app/models-old/chunks/test.rb b/app/models-old/chunks/test.rb
deleted file mode 100644
index edf77d14..00000000
--- a/app/models-old/chunks/test.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-require 'test/unit'
-
-class ChunkTest < Test::Unit::TestCase
-
-  # Asserts a number of tests for the given type and text.
-  def match(type, test_text, expected)
-	pattern = type.pattern
-    assert_match(pattern, test_text)
-    pattern =~ test_text   # Previous assertion guarantees match
-    chunk = type.new($~)
-    
-    # Test if requested parts are correct.
-    for method_sym, value in expected do
-      assert_respond_to(chunk, method_sym)
-      assert_equal(value, chunk.method(method_sym).call, "Checking value of '#{method_sym}'")
-    end
-  end
-end
diff --git a/app/models-old/chunks/uri.rb b/app/models-old/chunks/uri.rb
deleted file mode 100644
index 1a208535..00000000
--- a/app/models-old/chunks/uri.rb
+++ /dev/null
@@ -1,182 +0,0 @@
-require 'chunks/chunk'
-
-# This wiki chunk matches arbitrary URIs, using patterns from the Ruby URI modules.
-# It parses out a variety of fields that could be used by renderers to format
-# the links in various ways (shortening domain names, hiding email addresses)
-# It matches email addresses and host.com.au domains without schemes (http://)
-# but adds these on as required.
-#
-# The heuristic used to match a URI is designed to err on the side of caution.
-# That is, it is more likely to not autolink a URI than it is to accidently
-# autolink something that is not a URI. The reason behind this is it is easier
-# to force a URI link by prefixing 'http://' to it than it is to escape and
-# incorrectly marked up non-URI.
-#
-# I'm using a part of the [ISO 3166-1 Standard][iso3166] for country name suffixes.
-# The generic names are from www.bnoack.com/data/countrycode2.html)
-#   [iso3166]: http://geotags.com/iso3166/
-
-class URIChunk < Chunk::Abstract
-  include URI::REGEXP::PATTERN
-
-  # this condition is to get rid of pesky warnings in tests
-  unless defined? URIChunk::INTERNET_URI_REGEXP
-
-    GENERIC = 'aero|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org'
-    
-    COUNTRY = 'ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|az|ba|bb|bd|be|' + 
-      'bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cf|cd|cg|ch|ci|ck|cl|' + 
-      'cm|cn|co|cr|cs|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|fi|' + 
-      'fj|fk|fm|fo|fr|fx|ga|gb|gd|ge|gf|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|' + 
-      'hk|hm|hn|hr|ht|hu|id|ie|il|in|io|iq|ir|is|it|jm|jo|jp|ke|kg|kh|ki|km|kn|' + 
-      'kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|mg|mh|mk|ml|mm|' + 
-      'mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nt|' + 
-      'nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|pt|pw|py|qa|re|ro|ru|rw|sa|sb|sc|' + 
-      'sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|' + 
-      'tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|' + 
-      'ws|ye|yt|yu|za|zm|zr|zw'
-    # These are needed otherwise HOST will match almost anything
-    TLDS = "(?:#{GENERIC}|#{COUNTRY})"
-    
-    # Redefine USERINFO so that it must have non-zero length
-    USERINFO = "(?:[#{UNRESERVED};:&=+$,]|#{ESCAPED})+"
-  
-    # unreserved_no_ending = alphanum | mark, but URI_ENDING [)!] excluded
-    UNRESERVED_NO_ENDING = "-_.~*'(#{ALNUM}"  
-
-    # this ensures that query or fragment do not end with URI_ENDING
-    # and enable us to use a much simpler self.pattern Regexp
-
-    # uric_no_ending = reserved | unreserved_no_ending | escaped
-    URIC_NO_ENDING = "(?:[#{UNRESERVED_NO_ENDING}#{RESERVED}]|#{ESCAPED})"
-    # query = *uric
-    QUERY = "#{URIC_NO_ENDING}*"
-    # fragment = *uric
-    FRAGMENT = "#{URIC_NO_ENDING}*"
-
-    # DOMLABEL is defined in the ruby uri library, TLDS is defined above
-    INTERNET_HOSTNAME = "(?:#{DOMLABEL}\\.)+#{TLDS}" 
-
-    # Correct a typo bug in ruby 1.8.x lib/uri/common.rb 
-    PORT = '\\d*'
-
-    INTERNET_URI =
-        "(?:(#{SCHEME}):/{0,2})?" +   # Optional scheme:        (\1)
-        "(?:(#{USERINFO})@)?" +       # Optional userinfo@      (\2)
-        "(#{INTERNET_HOSTNAME})" +    # Mandatory hostname      (\3)
-        "(?::(#{PORT}))?" +           # Optional :port          (\4)
-        "(#{ABS_PATH})?"  +           # Optional absolute path  (\5)
-        "(?:\\?(#{QUERY}))?" +        # Optional ?query         (\6)
-        "(?:\\#(#{FRAGMENT}))?"  +    # Optional #fragment      (\7)
-        '(?=\.?(?:\s|\)|\z))'         # ends only with optional dot + space or ")" 
-                                      # or end of the string
-
-    SUSPICIOUS_PRECEDING_CHARACTER = '(!|\"\:|\"|\\\'|\]\()?'  # any of !, ":, ", ', ](
-  
-    INTERNET_URI_REGEXP = 
-        Regexp.new(SUSPICIOUS_PRECEDING_CHARACTER + INTERNET_URI, Regexp::EXTENDED, 'N')
-
-  end
-
-  def URIChunk.pattern
-    INTERNET_URI_REGEXP
-  end
-
-  attr_reader :user, :host, :port, :path, :query, :fragment, :link_text
-  
-  def self.apply_to(content)
-    content.gsub!( self.pattern ) do |matched_text|
-      chunk = self.new($~, content)
-      if chunk.avoid_autolinking?
-        # do not substitute nor register the chunk
-        matched_text
-      else
-        content.add_chunk(chunk)
-        chunk.mask
-      end
-    end
-  end
-
-  def initialize(match_data, content)
-    super
-    @link_text = match_data[0]
-    @suspicious_preceding_character = match_data[1]
-    @original_scheme, @user, @host, @port, @path, @query, @fragment = match_data[2..-1]
-    treat_trailing_character
-    @unmask_text = "#{link_text}"
-  end
-
-  def avoid_autolinking?
-    not @suspicious_preceding_character.nil?
-  end
-
-  def treat_trailing_character
-    # If the last character matched by URI pattern is in ! or ), this may be part of the markup,
-    # not a URL. We should handle it as such. It is possible to do it by a regexp, but 
-    # much easier to do programmatically
-    last_char = @link_text[-1..-1]
-    if last_char == ')' or last_char == '!'
-      @trailing_punctuation = last_char
-      @link_text.chop!
-      [@original_scheme, @user, @host, @port, @path, @query, @fragment].compact.last.chop!
-    else 
-      @trailing_punctuation = nil
-    end
-  end
-
-  def scheme
-    @original_scheme or (@user ? 'mailto' : 'http')
-  end
-
-  def scheme_delimiter
-    scheme == 'mailto' ? ':' : '://'
-  end
-
-  def user_delimiter
-     '@' unless @user.nil?
-  end
-
-  def port_delimiter
-     ':' unless @port.nil?
-  end
-
-  def query_delimiter
-     '?' unless @query.nil?
-  end
-
-  def uri
-    [scheme, scheme_delimiter, user, user_delimiter, host, port_delimiter, port, path, 
-      query_delimiter, query].compact.join
-  end
-
-end
-
-# uri with mandatory scheme but less restrictive hostname, like
-# http://localhost:2500/blah.html
-class LocalURIChunk < URIChunk
-
-  unless defined? LocalURIChunk::LOCAL_URI_REGEXP
-    # hostname can be just a simple word like 'localhost'
-    ANY_HOSTNAME = "(?:#{DOMLABEL}\\.)*#{TOPLABEL}\\.?"
-    
-    # The basic URI expression as a string
-    # Scheme and hostname are mandatory
-    LOCAL_URI =
-        "(?:(#{SCHEME})://)+" +       # Mandatory scheme://     (\1)
-        "(?:(#{USERINFO})@)?" +       # Optional userinfo@      (\2)
-        "(#{ANY_HOSTNAME})" +         # Mandatory hostname      (\3)
-        "(?::(#{PORT}))?" +           # Optional :port          (\4)
-        "(#{ABS_PATH})?"  +           # Optional absolute path  (\5)
-        "(?:\\?(#{QUERY}))?" +        # Optional ?query         (\6)
-        "(?:\\#(#{FRAGMENT}))?" +     # Optional #fragment      (\7)
-        '(?=\.?(?:\s|\)|\z))'         # ends only with optional dot + space or ")" 
-                                      # or end of the string
-  
-    LOCAL_URI_REGEXP = Regexp.new(SUSPICIOUS_PRECEDING_CHARACTER + LOCAL_URI, Regexp::EXTENDED, 'N')
-  end
-
-  def LocalURIChunk.pattern
-    LOCAL_URI_REGEXP
-  end
-
-end
diff --git a/app/models-old/chunks/wiki.rb b/app/models-old/chunks/wiki.rb
deleted file mode 100644
index 840f644a..00000000
--- a/app/models-old/chunks/wiki.rb
+++ /dev/null
@@ -1,141 +0,0 @@
-require 'wiki_words'
-require 'chunks/chunk'
-require 'chunks/wiki'
-require 'cgi'
-
-# Contains all the methods for finding and replacing wiki related links.
-module WikiChunk
-  include Chunk
-
-  # A wiki reference is the top-level class for anything that refers to
-  # another wiki page.
-  class WikiReference < Chunk::Abstract
-
-    # Name of the referenced page
-    attr_reader :page_name
-    
-    # the referenced page
-    def refpage
-      @content.web.pages[@page_name]
-    end
-  
-  end
-
-  # A wiki link is the top-level class for links that refers to
-  # another wiki page.
-  class WikiLink < WikiReference
- 
-    attr_reader :link_text, :link_type
-
-    def initialize(match_data, content)
-      super
-      @link_type = :show
-    end
-
-    def self.apply_to(content)
-      content.gsub!( self.pattern ) do |matched_text|
-        chunk = self.new($~, content)
-        if chunk.textile_url?
-          # do not substitute
-          matched_text
-        else
-          content.add_chunk(chunk)
-          chunk.mask
-        end
-      end
-    end
-
-    # the referenced page
-    def refpage
-      @content.web.pages[@page_name]
-    end
-
-    def textile_url?
-      not @textile_link_suffix.nil?
-    end
-
-  end
-
-  # This chunk matches a WikiWord. WikiWords can be escaped
-  # by prepending a '\'. When this is the case, the +escaped_text+
-  # method will return the WikiWord instead of the usual +nil+.
-  # The +page_name+ method returns the matched WikiWord.
-  class Word < WikiLink
-
-    attr_reader :escaped_text
-    
-    unless defined? WIKI_WORD
-      WIKI_WORD = Regexp.new('(":)?(\\\\)?(' + WikiWords::WIKI_WORD_PATTERN + ')\b', 0, "utf-8")
-    end
-
-    def self.pattern
-      WIKI_WORD
-    end
-
-    def initialize(match_data, content)
-      super
-      @textile_link_suffix, @escape, @page_name = match_data[1..3]
-      if @escape 
-        @unmask_mode = :escape
-        @escaped_text = @page_name
-      else
-        @escaped_text = nil
-      end
-      @link_text = WikiWords.separate(@page_name)
-      @unmask_text = (@escaped_text || @content.page_link(@page_name, @link_text, @link_type))
-    end
-
-  end
-
-  # This chunk handles [[bracketted wiki words]] and 
-  # [[AliasedWords|aliased wiki words]]. The first part of an
-  # aliased wiki word must be a WikiWord. If the WikiWord
-  # is aliased, the +link_text+ field will contain the
-  # alias, otherwise +link_text+ will contain the entire
-  # contents within the double brackets.
-  #
-  # NOTE: This chunk must be tested before WikiWord since
-  #       a WikiWords can be a substring of a WikiLink. 
-  class Link < WikiLink
-    
-    unless defined? WIKI_LINK
-      WIKI_LINK = /(":)?\[\[\s*([^\]\s][^\]]+?)\s*\]\]/
-      LINK_TYPE_SEPARATION = Regexp.new('^(.+):((file)|(pic))$', 0, 'utf-8')
-      ALIAS_SEPARATION = Regexp.new('^(.+)\|(.+)$', 0, 'utf-8')
-    end    
-        
-    def self.pattern() WIKI_LINK end
-
-    def initialize(match_data, content)
-      super
-      @textile_link_suffix, @page_name = match_data[1..2]
-      @link_text = @page_name
-      separate_link_type
-      separate_alias
-      @unmask_text = @content.page_link(@page_name, @link_text, @link_type)
-    end
-
-    private
-
-    # if link wihin the brackets has a form of [[filename:file]] or [[filename:pic]], 
-    # this means a link to a picture or a file
-    def separate_link_type
-      link_type_match = LINK_TYPE_SEPARATION.match(@page_name)
-      if link_type_match
-        @link_text = @page_name = link_type_match[1]
-        @link_type = link_type_match[2..3].compact[0].to_sym
-      end
-    end
-
-    # link text may be different from page name. this will look like [[actual page|link text]]
-    def separate_alias
-      alias_match = ALIAS_SEPARATION.match(@page_name)
-      if alias_match
-        @page_name, @link_text = alias_match[1..2]
-      end
-      # note that [[filename|link text:file]] is also supported
-    end  
-  
-  end
-  
-end
diff --git a/app/models-old/file_yard.rb b/app/models-old/file_yard.rb
deleted file mode 100644
index b35dda2e..00000000
--- a/app/models-old/file_yard.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-require 'fileutils'
-require 'instiki_errors'
-
-class FileYard
-
-  attr_reader :files_path
-
-  def initialize(files_path, max_upload_size)
-    @files_path, @max_upload_size = files_path, max_upload_size
-    FileUtils.mkdir_p(@files_path) unless File.exist?(@files_path)
-    @files = Dir["#{@files_path}/*"].collect{|path| File.basename(path) if File.file?(path) }.compact
-  end
-
-  def upload_file(name, io)
-    sanitize_file_name(name)
-    if io.kind_of?(Tempfile)
-      io.close
-      check_upload_size(io.size)
-      File.chmod(600, file_path(name)) if File.exists? file_path(name)
-      FileUtils.mv(io.path, file_path(name))
-    else
-      content = io.read
-      check_upload_size(content.length)
-      File.open(file_path(name), 'wb') { |f| f.write(content) }
-    end
-    # just in case, restrict read access and prohibit write access to the uploaded file
-    FileUtils.chmod(0440, file_path(name))
-  end
-
-  def files
-    Dir["#{files_path}/*"].collect{|path| File.basename(path) if File.file?(path)}.compact
-  end
-
-  def has_file?(name)
-    files.include?(name)
-  end
-
-  def file_path(name)
-    "#{files_path}/#{name}"
-  end
-
-  SANE_FILE_NAME = /[a-zA-Z0-9\-_\. ]{1,255}/
-
-  def sanitize_file_name(name)
-    unless name =~ SANE_FILE_NAME or name == '.' or name == '..'
-      raise Instiki::ValidationError.new("Invalid file name: '#{name}'.\n" +
-            "Only latin characters, digits, dots, underscores, dashes and spaces are accepted.")
-    end
-  end
-  
-  def check_upload_size(actual_upload_size)
-    if actual_upload_size > @max_upload_size.kilobytes
-      raise Instiki::ValidationError.new("Uploaded file size (#{actual_upload_size / 1024} " + 
-              "kbytes) exceeds the maximum (#{@max_upload_size} kbytes) set for this wiki")
-    end
-  end
-
-end
diff --git a/app/models-old/page.rb b/app/models-old/page.rb
deleted file mode 100644
index 5926fb85..00000000
--- a/app/models-old/page.rb
+++ /dev/null
@@ -1,120 +0,0 @@
-require 'date'
-require 'page_lock'
-require 'revision'
-require 'wiki_words'
-require 'chunks/wiki'
-
-class Page
-  include PageLock
-
-  attr_reader :name, :web
-  attr_accessor :revisions
-  
-  def initialize(web, name)
-    raise 'nil web' if web.nil?
-    raise 'nil name' if name.nil?
-    @web, @name, @revisions = web, name, []
-  end
-
-  def revise(content, created_at, author)
-
-    if not @revisions.empty? and content == @revisions.last.content
-      raise Instiki::ValidationError.new(
-          "You have tried to save page '#{name}' without changing its content")
-    end
-
-    # Try to render content to make sure that markup engine can take it,
-    # before addin a revision to the page
-    Revision.new(self, @revisions.length, content, created_at, author).force_rendering
-
-    # A user may change a page, look at it and make some more changes - several times.
-    # Not to record every such iteration as a new revision, if the previous revision was done 
-    # by the same author, not more than 30 minutes ago, then update the last revision instead of
-    # creating a new one
-    if !@revisions.empty? && continous_revision?(created_at, author)
-      @revisions.last.created_at = created_at
-      @revisions.last.content = content
-      @revisions.last.clear_display_cache
-    else
-      @revisions << Revision.new(self, @revisions.length, content, created_at, author)
-    end
-
-    self.revisions.last.force_rendering
-    # at this point the page may not be inserted in the web yet, and therefore 
-    # references to the page itself are rendered as "unresolved". Clearing the cache allows 
-    # the page to re-render itself once again, hopefully _after_ it is inserted in the web
-    self.revisions.last.clear_display_cache
-    
-    @web.refresh_pages_with_references(@name) if @revisions.length == 1
-    
-    self
-    
-  end
-
-  def rollback(revision_number, created_at, author_ip = nil)
-    roll_back_revision = @revisions[revision_number].dup
-    revise(roll_back_revision.content, created_at, Author.new(roll_back_revision.author, author_ip))
-  end
-  
-  def revisions?
-    revisions.length > 1
-  end
-
-  def revised_on
-    created_on
-  end
-
-  def in_category?(cat)
-    cat.nil? || cat.empty? || categories.include?(cat)
-  end
-
-  def categories
-    display_content.find_chunks(Category).map { |cat| cat.list }.flatten
-  end
-
-  def authors
-    revisions.collect { |rev| rev.author }
-  end
-
-  def references
-    @web.select.pages_that_reference(name)
-  end
-
-  def linked_from
-    @web.select.pages_that_link_to(name)
-  end
-
-  def included_from
-    @web.select.pages_that_include(name)
-  end
-
-  # Returns the original wiki-word name as separate words, so "MyPage" becomes "My Page".
-  def plain_name
-    @web.brackets_only ? name : WikiWords.separate(name)
-  end
-
-  # used to build chunk ids. 
-  def id
-    @id ||= name.unpack('H*').first
-  end
-
-  def link(options = {})
-    @web.make_link(name, nil, options)
-  end
-
-  def author_link(options = {})
-    @web.make_link(author, nil, options)
-  end
-
-  private
-
-  def continous_revision?(created_at, author)
-    @revisions.last.author == author && @revisions.last.created_at + 30.minutes > created_at
-  end
-
-  # Forward method calls to the current revision, so the page responds to all revision calls
-  def method_missing(method_symbol)
-    revisions.last.send(method_symbol)
-  end
-
-end
diff --git a/app/models-old/page_lock.rb b/app/models-old/page_lock.rb
deleted file mode 100644
index 276274e6..00000000
--- a/app/models-old/page_lock.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# Contains all the lock methods to be mixed in with the page
-module PageLock
-  LOCKING_PERIOD = 30 * 60 # 30 minutes
-
-  attr_reader :locked_by
-
-  def lock(time, locked_by)
-    @locked_at, @locked_by = time, locked_by
-  end
-  
-  def lock_duration(time)
-    ((time - @locked_at) / 60).to_i unless @locked_at.nil?
-  end
-  
-  def unlock
-    @locked_at = nil
-  end
-  
-  def locked?(comparison_time)
-    @locked_at + LOCKING_PERIOD > comparison_time unless @locked_at.nil?
-  end
-
-end
\ No newline at end of file
diff --git a/app/models-old/page_set.rb b/app/models-old/page_set.rb
deleted file mode 100644
index 5b298ea6..00000000
--- a/app/models-old/page_set.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-# Container for a set of pages with methods for manipulation.
-
-class PageSet < Array
-  attr_reader :web
-
-  def initialize(web, pages = nil, condition = nil)
-    @web = web
-    # if pages is not specified, make a list of all pages in the web
-    if pages.nil?
-      super(web.pages.values)
-    # otherwise use specified pages and condition to produce a set of pages
-    elsif condition.nil?
-      super(pages)
-    else
-      super(pages.select { |page| condition[page] })
-    end
-  end
-
-  def most_recent_revision
-    self.map { |page| page.created_at }.max || Time.at(0)
-  end
-
-
-  def by_name
-    PageSet.new(@web, sort_by { |page| page.name })
-  end
-
-  alias :sort :by_name
-
-  def by_revision
-    PageSet.new(@web, sort_by { |page| page.created_at }).reverse 
-  end
-  
-  def pages_that_reference(page_name)
-    self.select { |page| page.wiki_references.include?(page_name) }
-  end
-  
-  def pages_that_link_to(page_name)
-    self.select { |page| page.wiki_words.include?(page_name) }
-  end
-
-  def pages_that_include(page_name)
-    self.select { |page| page.wiki_includes.include?(page_name) }
-  end
-
-  def pages_authored_by(author)
-    self.select { |page| page.authors.include?(author) }
-  end
-
-  def characters
-    self.inject(0) { |chars,page| chars += page.content.size }
-  end
-
-  # Returns all the orphaned pages in this page set. That is,
-  # pages in this set for which there is no reference in the web.
-  # The HomePage and author pages are always assumed to have
-  # references and so cannot be orphans
-  # Pages that refer to themselves and have no links from outside are oprphans.
-  def orphaned_pages
-    never_orphans = web.select.authors + ['HomePage']
-    self.select { |page|
-      if never_orphans.include? page.name
-        false
-      else
-        references = pages_that_reference(page.name)
-        references.empty? or references == [page]
-      end
-    }
-  end
-
-  # Returns all the wiki words in this page set for which
-  # there are no pages in this page set's web
-  def wanted_pages
-    wiki_words - web.select.names
-  end
-
-  def names
-    self.map { |page| page.name }
-  end
-
-  def wiki_words
-    self.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq
-  end
-
-  def authors
-    self.inject([]) { |authors, page| authors << page.authors }.flatten.uniq.sort
-  end
-
-end
diff --git a/app/models-old/revision.rb b/app/models-old/revision.rb
deleted file mode 100644
index c5f0eb8a..00000000
--- a/app/models-old/revision.rb
+++ /dev/null
@@ -1,127 +0,0 @@
-require 'diff'
-require 'wiki_content'
-require 'chunks/wiki'
-require 'date'
-require 'author'
-require 'page'
-
-class Revision
-
-  attr_accessor :page, :number, :content, :created_at, :author
-
-  def initialize(page, number, content, created_at, author)
-    @page, @number, @created_at, @author = page, number, created_at, author
-    self.content = content
-    @display_cache = nil
-  end
-
-  def created_on
-    Date.new(@created_at.year, @created_at.mon, @created_at.day)
-  end
-
-  def pretty_created_at
-    # Must use DateTime because Time doesn't support %e on at least some platforms
-    DateTime.new(
-      @created_at.year, @created_at.mon, @created_at.day, @created_at.hour, @created_at.min
-    ).strftime "%B %e, %Y %H:%M" 
-  end
-
-
-# todo: drop next_revision, previuous_revision and number from here - unused code
-  def next_revision
-    page.revisions[number + 1]
-  end
-
-  def previous_revision
-    number > 0 ? page.revisions[number - 1] : nil
-  end
-
-  # Returns an array of all the WikiIncludes present in the content of this revision.
-  def wiki_includes
-    unless @wiki_includes_cache 
-      chunks = display_content.find_chunks(Include)
-      @wiki_includes_cache = chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq
-    end
-    @wiki_includes_cache
-  end  
-
-  # Returns an array of all the WikiReferences present in the content of this revision.
-  def wiki_references
-    unless @wiki_references_cache 
-      chunks = display_content.find_chunks(WikiChunk::WikiReference)
-      @wiki_references_cache = chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq
-    end
-    @wiki_references_cache
-  end  
-
-  # Returns an array of all the WikiWords present in the content of this revision.
-  def wiki_words
-    unless @wiki_words_cache
-      wiki_chunks = display_content.find_chunks(WikiChunk::WikiLink)
-      @wiki_words_cache = wiki_chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq
-    end
-    @wiki_words_cache
-  end
-
-  # Returns an array of all the WikiWords present in the content of this revision.
-  # that already exists as a page in the web.
-  def existing_pages
-    wiki_words.select { |wiki_word| page.web.pages[wiki_word] }
-  end
-
-  # Returns an array of all the WikiWords present in the content of this revision
-  # that *doesn't* already exists as a page in the web.
-  def unexisting_pages
-    wiki_words - existing_pages
-  end  
-
-  # Explicit check for new type of display cache with chunks_by_type method.
-  # Ensures new version works with older snapshots.
-  def display_content
-    unless @display_cache && @display_cache.respond_to?(:chunks_by_type)
-      @display_cache = WikiContent.new(self)
-      @display_cache.render!
-    end
-    @display_cache
-  end
-
-  def display_diff
-    previous_revision ? HTMLDiff.diff(previous_revision.display_content, display_content) : display_content
-  end
-
-  def clear_display_cache
-    @wiki_words_cache = @published_cache = @display_cache = @wiki_includes_cache = 
-      @wiki_references_cache = nil
-  end
-
-  def display_published
-    unless @published_cache && @published_cache.respond_to?(:chunks_by_type)
-      @published_cache = WikiContent.new(self, {:mode => :publish})
-      @published_cache.render!
-    end
-    @published_cache
-  end
-
-  def display_content_for_export
-    WikiContent.new(self, {:mode => :export} ).render!
-  end
-  
-  def force_rendering
-    begin
-      display_content.render!
-    rescue => e
-      ApplicationController.logger.error "Failed rendering page #{@name}"
-      ApplicationController.logger.error e
-      message = e.message
-      # substitute content with an error message
-      self.content = <<-EOL
-          

Markup engine has failed to render this page, raising the following error:

-

#{message}

-
#{self.content}
- EOL - clear_display_cache - raise e - end - end - -end diff --git a/app/models-old/web.rb b/app/models-old/web.rb deleted file mode 100644 index 0e21fc80..00000000 --- a/app/models-old/web.rb +++ /dev/null @@ -1,184 +0,0 @@ -require 'cgi' -require 'page' -require 'page_set' -require 'wiki_words' -require 'zip/zip' - -class Web - attr_accessor :name, :password, :safe_mode, :pages - attr_accessor :additional_style, :allow_uploads, :published - attr_reader :address - - # there are getters for all these attributes, too - attr_writer :markup, :color, :brackets_only, :count_pages, :max_upload_size - - def initialize(parent_wiki, name, address, password = nil) - self.address = address - @wiki, @name, @password = parent_wiki, name, password - - set_compatible_defaults - - @pages = {} - @allow_uploads = true - @additional_style = nil - @published = false - @count_pages = false - end - - # Explicitly sets value of some web attributes to defaults, unless they are already set - def set_compatible_defaults - @markup = markup() - @color = color() - @safe_mode = safe_mode() - @brackets_only = brackets_only() - @max_upload_size = max_upload_size() - @wiki = wiki - end - - # All below getters know their default values. This is necessary to ensure compatibility with - # 0.9 storages, where they were not defined. - def brackets_only() @brackets_only || false end - def color() @color ||= '008B26' end - def count_pages() @count_pages || false end - def markup() @markup ||= :textile end - def max_upload_size() @max_upload_size || 100; end - def wiki() @wiki ||= WikiService.instance; end - - def add_page(name, content, created_at, author) - page = Page.new(self, name) - page.revise(content, created_at, author) - @pages[page.name] = page - end - - def address=(the_address) - if the_address != CGI.escape(the_address) - raise Instiki::ValidationError.new('Web name should contain only valid URI characters') - end - @address = the_address - end - - def authors - select.authors - end - - def categories - select.map { |page| page.categories }.flatten.uniq.sort - end - - def has_page?(name) - pages[name] - end - - def has_file?(name) - wiki.file_yard(self).has_file?(name) - end - - def make_file_link(mode, name, text, base_url) - link = CGI.escape(name) - case mode - when :export - if has_file?(name) then "#{text}" - else "#{text}" end - when :publish - if has_file?(name) then "#{text}" - else "#{text}" end - else - if has_file?(name) - "#{text}" - else - "#{text}?" - end - end - end - - # Create a link for the given page name and link text based - # on the render mode in options and whether the page exists - # in the this web. - # The links a relative, and will work only if displayed on another WikiPage. - # It should not be used in menus, templates and such - instead, use link_to_page helper - def make_link(name, text = nil, options = {}) - text = CGI.escapeHTML(text || WikiWords.separate(name)) - mode = options[:mode] || :show - base_url = options[:base_url] || '..' - link_type = options[:link_type] || :show - case link_type.to_sym - when :show - make_page_link(mode, name, text, base_url) - when :file - make_file_link(mode, name, text, base_url) - when :pic - make_pic_link(mode, name, text, base_url) - else - raise "Unknown link type: #{link_type}" - end - end - - def make_page_link(mode, name, text, base_url) - link = CGI.escape(name) - case mode.to_sym - when :export - if has_page?(name) then %{#{text}} - else %{#{text}} end - when :publish - if has_page?(name) then %{#{text}} - else %{#{text}} end - else - if has_page?(name) - %{#{text}} - else - %{#{text}?} - end - end - end - - def make_pic_link(mode, name, text, base_url) - link = CGI.escape(name) - case mode.to_sym - when :export - if has_file?(name) then %{#{text}} - else %{#{text}} end - when :publish - if has_file?(name) then %{#{text}} - else %{#{text}} end - else - if has_file?(name) then %{#{text}} - else %{#{text}?} end - end - end - - # Clears the display cache for all the pages with references to - def refresh_pages_with_references(page_name) - select.pages_that_reference(page_name).each { |page| - page.revisions.each { |revision| revision.clear_display_cache } - } - end - - def refresh_revisions - select.each { |page| page.revisions.each { |revision| revision.clear_display_cache } } - end - - def remove_pages(pages_to_be_removed) - pages.delete_if { |page_name, page| pages_to_be_removed.include?(page) } - end - - def revised_on - select.most_recent_revision - end - - def select(&condition) - PageSet.new(self, @pages.values, condition) - end - - private - - # Returns an array of all the wiki words in any current revision - def wiki_words - pages.values.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq - end - - # Returns an array of all the page names on this web - def page_names - pages.keys - end - -end diff --git a/app/models-old/wiki_content.rb b/app/models-old/wiki_content.rb deleted file mode 100644 index 89cd8c45..00000000 --- a/app/models-old/wiki_content.rb +++ /dev/null @@ -1,205 +0,0 @@ -require 'cgi' -require 'chunks/engines' -require 'chunks/category' -require 'chunks/include' -require 'chunks/wiki' -require 'chunks/literal' -require 'chunks/uri' -require 'chunks/nowiki' - -# Wiki content is just a string that can process itself with a chain of -# actions. The actions can modify wiki content so that certain parts of -# it are protected from being rendered by later actions. -# -# When wiki content is rendered, it can be interrogated to find out -# which chunks were rendered. This means things like categories, wiki -# links, can be determined. -# -# Exactly how wiki content is rendered is determined by a number of -# settings that are optionally passed in to a constructor. The current -# options are: -# * :engine -# => The structural markup engine to use (Textile, Markdown, RDoc) -# * :engine_opts -# => A list of options to pass to the markup engines (safe modes, etc) -# * :pre_engine_actions -# => A list of render actions or chunks to be processed before the -# markup engine is applied. By default this is: -# Category, Include, URIChunk, WikiChunk::Link, WikiChunk::Word -# * :post_engine_actions -# => A list of render actions or chunks to apply after the markup -# engine. By default these are: -# Literal::Pre, Literal::Tags -# * :mode -# => How should the content be rendered? For normal display (show), -# publishing (:publish) or export (:export)? - -module ChunkManager - attr_reader :chunks_by_type, :chunks_by_id, :chunks, :chunk_id - - ACTIVE_CHUNKS = [ NoWiki, Category, WikiChunk::Link, URIChunk, LocalURIChunk, - WikiChunk::Word ] - - HIDE_CHUNKS = [ Literal::Pre, Literal::Tags ] - - MASK_RE = { - ACTIVE_CHUNKS => Chunk::Abstract.mask_re(ACTIVE_CHUNKS), - HIDE_CHUNKS => Chunk::Abstract.mask_re(HIDE_CHUNKS) - } - - def init_chunk_manager - @chunks_by_type = Hash.new - Chunk::Abstract::derivatives.each{|chunk_type| - @chunks_by_type[chunk_type] = Array.new - } - @chunks_by_id = Hash.new - @chunks = [] - @chunk_id = 0 - end - - def add_chunk(c) - @chunks_by_type[c.class] << c - @chunks_by_id[c.id] = c - @chunks << c - @chunk_id += 1 - end - - def delete_chunk(c) - @chunks_by_type[c.class].delete(c) - @chunks_by_id.delete(c.id) - @chunks.delete(c) - end - - def merge_chunks(other) - other.chunks.each{|c| add_chunk(c)} - end - - def scan_chunkid(text) - text.scan(MASK_RE[ACTIVE_CHUNKS]){|a| yield a[0] } - end - - def find_chunks(chunk_type) - @chunks.select { |chunk| chunk.kind_of?(chunk_type) and chunk.rendered? } - end - - # for testing and WikiContentStub; we need a page_id even if we have no page - def page_id - 0 - end - -end - -# A simplified version of WikiContent. Useful to avoid recursion problems in -# WikiContent.new -class WikiContentStub < String - attr_reader :options - include ChunkManager - def initialize(content, options) - super(content) - @options = options - init_chunk_manager - end - - # Detects the mask strings contained in the text of chunks of type chunk_types - # and yields the corresponding chunk ids - # example: content = "chunk123categorychunk
chunk456categorychunk
" - # inside_chunks(Literal::Pre) ==> yield 456 - def inside_chunks(chunk_types) - chunk_types.each{|chunk_type| chunk_type.apply_to(self) } - - chunk_types.each{|chunk_type| @chunks_by_type[chunk_type].each{|hide_chunk| - scan_chunkid(hide_chunk.text){|id| yield id } - } - } - end -end - -class WikiContent < String - - include ChunkManager - - DEFAULT_OPTS = { - :active_chunks => ACTIVE_CHUNKS, - :engine => Engines::Textile, - :engine_opts => [], - :mode => :show - }.freeze - - attr_reader :web, :options, :revision, :not_rendered, :pre_rendered - - # Create a new wiki content string from the given one. - # The options are explained at the top of this file. - def initialize(revision, options = {}) - @revision = revision - @web = @revision.page.web - - @options = DEFAULT_OPTS.dup.merge(options) - @options[:engine] = Engines::MAP[@web.markup] - @options[:engine_opts] = [:filter_html, :filter_styles] if @web.safe_mode - @options[:active_chunks] = (ACTIVE_CHUNKS - [WikiChunk::Word] ) if @web.brackets_only - - @not_rendered = @pre_rendered = nil - - super(@revision.content) - init_chunk_manager - build_chunks - @not_rendered = String.new(self) - end - - # Call @web.page_link using current options. - def page_link(name, text, link_type) - @options[:link_type] = (link_type || :show) - @web.make_link(name, text, @options) - end - - def build_chunks - # create and mask Includes and "active_chunks" chunks - Include.apply_to(self) - @options[:active_chunks].each{|chunk_type| chunk_type.apply_to(self)} - - # Handle hiding contexts like "pre" and "code" etc.. - # The markup (textile, rdoc etc) can produce such contexts with its own syntax. - # To reveal them, we work on a copy of the content. - # The copy is rendered and used to detect the chunks that are inside protecting context - # These chunks are reverted on the original content string. - - copy = WikiContentStub.new(self, @options) - @options[:engine].apply_to(copy) - - copy.inside_chunks(HIDE_CHUNKS) do |id| - @chunks_by_id[id].revert - end - end - - def pre_render! - unless @pre_rendered - @chunks_by_type[Include].each{|chunk| chunk.unmask } - @pre_rendered = String.new(self) - end - @pre_rendered - end - - def render! - pre_render! - @options[:engine].apply_to(self) - # unmask in one go. $~[1] is the chunk id - gsub!(MASK_RE[ACTIVE_CHUNKS]){ - if chunk = @chunks_by_id[$~[1]] - chunk.unmask_text - # if we match a chunkmask that existed in the original content string - # just keep it as it is - else - $~[0] - end} - self - end - - def page_name - @revision.page.name - end - - def page_id - @revision.page.id - end - -end diff --git a/app/models-old/wiki_service.rb b/app/models-old/wiki_service.rb deleted file mode 100644 index c7f8d515..00000000 --- a/app/models-old/wiki_service.rb +++ /dev/null @@ -1,229 +0,0 @@ -require 'open-uri' -require 'yaml' -require 'madeleine' -require 'madeleine/automatic' -require 'madeleine/zmarshal' - -require 'web' -require 'page' -require 'author' -require 'file_yard' -require 'instiki_errors' - -module AbstractWikiService - - attr_reader :webs, :system - - def authenticate(password) - # system['password'] variant is for compatibility with storages from older versions - password == (@system[:password] || @system['password'] || 'instiki') - end - - def create_web(name, address, password = nil) - @webs[address] = Web.new(self, name, address, password) unless @webs[address] - end - - def delete_web(address) - @webs[address] = nil - end - - def file_yard(web) - raise "Web #{@web.name} does not belong to this wiki service" unless @webs.values.include?(web) - # TODO cache FileYards - FileYard.new("#{self.storage_path}/#{web.address}", web.max_upload_size) - end - - def init_wiki_service - @webs = {} - @system = {} - end - - def edit_web(old_address, new_address, name, markup, color, additional_style, safe_mode = false, - password = nil, published = false, brackets_only = false, count_pages = false, - allow_uploads = true, max_upload_size = nil) - - if not @webs.key? old_address - raise Instiki::ValidationError.new("Web with address '#{old_address}' does not exist") - end - - if old_address != new_address - if @webs.key? new_address - raise Instiki::ValidationError.new("There is already a web with address '#{new_address}'") - end - @webs[new_address] = @webs[old_address] - @webs.delete(old_address) - @webs[new_address].address = new_address - end - - web = @webs[new_address] - web.refresh_revisions if settings_changed?(web, markup, safe_mode, brackets_only) - - web.name, web.markup, web.color, web.additional_style, web.safe_mode = - name, markup, color, additional_style, safe_mode - - web.password, web.published, web.brackets_only, web.count_pages = - password, published, brackets_only, count_pages, allow_uploads - web.allow_uploads, web.max_upload_size = allow_uploads, max_upload_size.to_i - end - - def read_page(web_address, page_name) - ApplicationController.logger.debug "Reading page '#{page_name}' from web '#{web_address}'" - web = @webs[web_address] - if web.nil? - ApplicationController.logger.debug "Web '#{web_address}' not found" - return nil - else - page = web.pages[page_name] - ApplicationController.logger.debug "Page '#{page_name}' #{page.nil? ? 'not' : ''} found" - return page - end - end - - def remove_orphaned_pages(web_address) - @webs[web_address].remove_pages(@webs[web_address].select.orphaned_pages) - end - - def revise_page(web_address, page_name, content, revised_on, author) - page = read_page(web_address, page_name) - page.revise(content, revised_on, author) - end - - def rollback_page(web_address, page_name, revision_number, created_at, author_id = nil) - page = read_page(web_address, page_name) - page.rollback(revision_number, created_at, author_id) - end - - def setup(password, web_name, web_address) - @system[:password] = password - create_web(web_name, web_address) - end - - def setup? - not (@webs.empty?) - end - - def storage_path - self.class.storage_path - end - - def write_page(web_address, page_name, content, written_on, author) - @webs[web_address].add_page(page_name, content, written_on, author) - end - - private - def settings_changed?(web, markup, safe_mode, brackets_only) - web.markup != markup || - web.safe_mode != safe_mode || - web.brackets_only != brackets_only - end -end - -class WikiService - - include AbstractWikiService - include Madeleine::Automatic::Interceptor - - # These methods do not change the state of persistent objects, and - # should not be logged by Madeleine - automatic_read_only :authenticate, :read_page, :setup?, :webs, :storage_path, :file_yard - - @@storage_path = './storage/' - - class << self - - def storage_path=(storage_path) - @@storage_path = storage_path - end - - def storage_path - @@storage_path - end - - def clean_storage - MadeleineServer.clean_storage(self) - end - - def instance - @madeleine ||= MadeleineServer.new(self) - @system = @madeleine.system - return @system - end - - def snapshot - @madeleine.snapshot - end - - end - - def initialize - init_wiki_service - end - -end - -class MadeleineServer - - attr_reader :storage_path - - # Clears all the command_log and snapshot files located in the storage directory, so the - # database is essentially dropped and recreated as blank - def self.clean_storage(service) - begin - Dir.foreach(service.storage_path) do |file| - if file =~ /(command_log|snapshot)$/ - File.delete(File.join(service.storage_path, file)) - end - end - rescue - Dir.mkdir(service.storage_path) - end - end - - def initialize(service) - @storage_path = service.storage_path - @server = Madeleine::Automatic::AutomaticSnapshotMadeleine.new(service.storage_path, - Madeleine::ZMarshal.new) { - service.new - } - start_snapshot_thread - end - - def command_log_present? - not Dir[storage_path + '/*.command_log'].empty? - end - - def snapshot - @server.take_snapshot - end - - def start_snapshot_thread - Thread.new(@server) { - hours_since_last_snapshot = 0 - while true - begin - hours_since_last_snapshot += 1 - # Take a snapshot if there is a command log, or 24 hours - # have passed since the last snapshot - if command_log_present? or hours_since_last_snapshot >= 24 - ActionController::Base.logger.info "[#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}] " + - 'Taking a Madeleine snapshot' - snapshot - hours_since_last_snapshot = 0 - end - sleep(1.hour) - rescue => e - ActionController::Base.logger.error(e) - # wait for a minute (not to spoof the log with the same error) - # and go back into the loop, to keep trying - sleep(1.minute) - ActionController::Base.logger.info("Retrying to save a snapshot") - end - end - } - end - - def system - @server.system - end - -end diff --git a/app/models-old/wiki_words.rb b/app/models-old/wiki_words.rb deleted file mode 100644 index 8f2b154f..00000000 --- a/app/models-old/wiki_words.rb +++ /dev/null @@ -1,23 +0,0 @@ -# Contains all the methods for finding and replacing wiki words -module WikiWords - # In order of appearance: Latin, greek, cyrillian, armenian - I18N_HIGHER_CASE_LETTERS = - "ÀÃ?ÂÃÄÅĀĄĂÆÇĆČĈĊĎÄ?ÈÉÊËĒĘĚĔĖĜĞĠĢĤĦÌÃ?ÃŽÃ?ĪĨĬĮİIJĴĶÅ?ĽĹĻĿÑŃŇŅŊÒÓÔÕÖØŌÅ?ŎŒŔŘŖŚŠŞŜȘŤŢŦȚÙÚÛÜŪŮŰŬŨŲŴÃ?ŶŸŹŽŻ" + - "ΑΒΓΔΕΖΗΘΙΚΛΜÎ?ΞΟΠΡΣΤΥΦΧΨΩ" + - "ΆΈΉΊΌΎÎ?ѠѢѤѦѨѪѬѮѰѲѴѶѸѺѼѾҀҊҌҎÒ?ҒҔҖҘҚҜҞҠҢҤҦҨҪҬҮҰҲҴҶҸҺҼҾÓ?ÓƒÓ…Ó‡Ó‰Ó‹Ó?Ó?ӒӔӖӘӚӜӞӠӢӤӦӨӪӬӮӰӲӴӸЖ" + - "Ô±Ô²Ô³Ô´ÔµÔ¶Ô·Ô¸Ô¹ÔºÔ»Ô¼Ô½Ô¾Ô¿Õ€Õ?Õ‚ÕƒÕ„Õ…Õ†Õ‡ÕˆÕ‰ÕŠÕ‹ÕŒÕ?Õ?Õ?Õ‘Õ’Õ“Õ”Õ•Õ–" - - I18N_LOWER_CASE_LETTERS = - "àáâãäåÄ?ąăæçćÄ?ĉċÄ?đèéêëēęěĕėƒÄ?ğġģĥħìíîïīĩĭįıijĵķĸłľĺļŀñńňņʼnŋòóôõöøÅ?Å‘Å?œŕřŗśšşÅ?șťţŧțùúûüūůűŭũųŵýÿŷžżźÞþßſÃ?ð" + - "άέήίΰαβγδεζηθικλμνξοπÏ?ςστυφχψωϊϋόÏ?ÏŽÎ?" + - "абвгдежзийклмнопрÑ?туфхцчшщъыьÑ?ÑŽÑ?Ñ?ёђѓєѕіїјљћќÑ?ўџѡѣѥѧѩѫѭѯѱѳѵѷѹѻѽѿÒ?Ò‹Ò?Ò?Ò‘Ò“Ò•Ò—Ò™Ò›Ò?ҟҡңҥҧҩҫҭүұҳҵҷҹһҽҿӀӂӄӆӈӊӌӎӑӓӕӗәӛÓ?ÓŸÓ¡Ó£Ó¥Ó§Ó©Ó«Ó­Ó¯Ó±Ó³ÓµÓ¹" + - "Õ¡Õ¢Õ£Õ¤Õ¥Õ¦Õ§Õ¨Õ©ÕªÕ«Õ¬Õ­Õ®Õ¯Õ°Õ±Õ²Õ³Õ´ÕµÕ¶Õ·Õ¸Õ¹ÕºÕ»Õ¼Õ½Õ¾Õ¿Ö€Ö?Ö‚ÖƒÖ„Ö…Ö†Ö‡" - - WIKI_WORD_PATTERN = '[A-Z' + I18N_HIGHER_CASE_LETTERS + '][a-z' + I18N_LOWER_CASE_LETTERS + ']+[A-Z' + I18N_HIGHER_CASE_LETTERS + ']\w+' - CAMEL_CASED_WORD_BORDER = /([a-z#{I18N_LOWER_CASE_LETTERS}])([A-Z#{I18N_HIGHER_CASE_LETTERS}])/u - - def self.separate(wiki_word) - wiki_word.gsub(CAMEL_CASED_WORD_BORDER, '\1 \2') - end - -end From 17833e23a5b5b257d8697d6c47c6012fa0720f27 Mon Sep 17 00:00:00 2001 From: Ben Bleything Date: Tue, 9 Aug 2005 03:45:09 +0000 Subject: [PATCH 14/84] Add executable bit to script/console and script/create_db --- script/console | 0 script/create_db | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 script/console mode change 100644 => 100755 script/create_db diff --git a/script/console b/script/console old mode 100644 new mode 100755 diff --git a/script/create_db b/script/create_db old mode 100644 new mode 100755 From fffe1b897e061a172f7248525dcbb6e3113ca869 Mon Sep 17 00:00:00 2001 From: Rick Okin Date: Tue, 9 Aug 2005 03:52:54 +0000 Subject: [PATCH 15/84] added log directory, set default database.yml --- config/{database.yml => database.default.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename config/{database.yml => database.default.yml} (100%) diff --git a/config/database.yml b/config/database.default.yml similarity index 100% rename from config/database.yml rename to config/database.default.yml From b94559bc4c92d9d3f0a6c4a7ae7b5c21ab39ffdb Mon Sep 17 00:00:00 2001 From: Ben Bleything Date: Tue, 9 Aug 2005 04:40:55 +0000 Subject: [PATCH 16/84] remove development_structure.sql and ignore it in the future. We're going to be hurting if we're developing on multiple db engines and keep this file around. --- db/development_structure.sql | 40 ------------------------------------ 1 file changed, 40 deletions(-) delete mode 100644 db/development_structure.sql diff --git a/db/development_structure.sql b/db/development_structure.sql deleted file mode 100644 index 61d49c31..00000000 --- a/db/development_structure.sql +++ /dev/null @@ -1,40 +0,0 @@ -CREATE TABLE pages ( - id INTEGER PRIMARY KEY, - created_at DATETIME NOT NULL, - updated_at DATETIME NOT NULL, - web_id INTEGER NOT NULL, - locked_by VARCHAR(60), - name VARCHAR(60), - locked_at DATETIME -); -CREATE TABLE revisions ( - id INTEGER PRIMARY KEY, - created_at DATETIME NOT NULL, - updated_at DATETIME NOT NULL, - page_id INTEGER NOT NULL, - content TEXT NOT NULL, - author VARCHAR(60), - ip VARCHAR(60), - number INTEGER -); -CREATE TABLE system ( - id INTEGER PRIMARY KEY, - 'password' VARCHAR(60) -); -CREATE TABLE webs ( - id INTEGER PRIMARY KEY, - created_at DATETIME NOT NULL, - updated_at DATETIME NOT NULL, - name VARCHAR(60) NOT NULL, - address VARCHAR(60) NOT NULL, - 'password' VARCHAR(60), - additional_style VARCHAR(255), - allow_uploads INTEGER DEFAULT '1', - published INTEGER DEFAULT '0', - count_pages INTEGER DEFAULT '0', - markup VARCHAR(50) DEFAULT 'textile', - color VARCHAR(6) DEFAULT '008B26', - max_upload_size INTEGER DEFAULT 100, - safe_mode INTEGER DEFAULT '0', - brackets_only INTEGER DEFAULT '0' -); From 2c7a2779c7f7e8cae10c113cb8af4ca04453d579 Mon Sep 17 00:00:00 2001 From: Rick Okin Date: Wed, 10 Aug 2005 05:28:05 +0000 Subject: [PATCH 17/84] Deleting Madeleine... with pleasure (it is cool, but not for wiki data) --- instiki.gemspec | 4 +- vendor/madeleine-0.7.1/.cvsignore | 2 - vendor/madeleine-0.7.1/COPYING | 31 - vendor/madeleine-0.7.1/NEWS | 55 - vendor/madeleine-0.7.1/README | 78 -- vendor/madeleine-0.7.1/TODO | 23 - vendor/madeleine-0.7.1/contrib/batched.rb | 298 ----- vendor/madeleine-0.7.1/contrib/benchmark.rb | 35 - .../madeleine-0.7.1/contrib/test_batched.rb | 245 ---- .../contrib/test_scalability.rb | 248 ---- .../contrib/threaded_benchmark.rb | 44 - vendor/madeleine-0.7.1/docs/.cvsignore | 1 - vendor/madeleine-0.7.1/docs/designRules.html | 87 -- vendor/madeleine-0.7.1/docs/docs.css | 28 - vendor/madeleine-0.7.1/generate_rdoc.rb | 3 - vendor/madeleine-0.7.1/install.rb | 1098 ----------------- vendor/madeleine-0.7.1/lib/madeleine.rb | 420 ------- .../lib/madeleine/automatic.rb | 418 ------- vendor/madeleine-0.7.1/lib/madeleine/clock.rb | 94 -- vendor/madeleine-0.7.1/lib/madeleine/files.rb | 19 - .../madeleine-0.7.1/lib/madeleine/zmarshal.rb | 60 - vendor/madeleine-0.7.1/madeleine.gemspec | 23 - vendor/madeleine-0.7.1/samples/.cvsignore | 3 - vendor/madeleine-0.7.1/samples/clock_click.rb | 73 -- .../samples/dictionary_client.rb | 23 - .../samples/dictionary_server.rb | 94 -- vendor/madeleine-0.7.1/samples/painter.rb | 60 - vendor/madeleine-0.7.1/test/test.rb | 320 ----- vendor/madeleine-0.7.1/test/test_automatic.rb | 559 --------- vendor/madeleine-0.7.1/test/test_clocked.rb | 94 -- .../madeleine-0.7.1/test/test_command_log.rb | 110 -- vendor/madeleine-0.7.1/test/test_executer.rb | 54 - .../madeleine-0.7.1/test/test_persistence.rb | 169 --- vendor/madeleine-0.7.1/test/test_platforms.rb | 65 - vendor/madeleine-0.7.1/test/test_zmarshal.rb | 52 - 35 files changed, 2 insertions(+), 4988 deletions(-) delete mode 100755 vendor/madeleine-0.7.1/.cvsignore delete mode 100755 vendor/madeleine-0.7.1/COPYING delete mode 100755 vendor/madeleine-0.7.1/NEWS delete mode 100755 vendor/madeleine-0.7.1/README delete mode 100755 vendor/madeleine-0.7.1/TODO delete mode 100755 vendor/madeleine-0.7.1/contrib/batched.rb delete mode 100755 vendor/madeleine-0.7.1/contrib/benchmark.rb delete mode 100755 vendor/madeleine-0.7.1/contrib/test_batched.rb delete mode 100755 vendor/madeleine-0.7.1/contrib/test_scalability.rb delete mode 100755 vendor/madeleine-0.7.1/contrib/threaded_benchmark.rb delete mode 100755 vendor/madeleine-0.7.1/docs/.cvsignore delete mode 100755 vendor/madeleine-0.7.1/docs/designRules.html delete mode 100755 vendor/madeleine-0.7.1/docs/docs.css delete mode 100755 vendor/madeleine-0.7.1/generate_rdoc.rb delete mode 100755 vendor/madeleine-0.7.1/install.rb delete mode 100755 vendor/madeleine-0.7.1/lib/madeleine.rb delete mode 100755 vendor/madeleine-0.7.1/lib/madeleine/automatic.rb delete mode 100755 vendor/madeleine-0.7.1/lib/madeleine/clock.rb delete mode 100755 vendor/madeleine-0.7.1/lib/madeleine/files.rb delete mode 100755 vendor/madeleine-0.7.1/lib/madeleine/zmarshal.rb delete mode 100755 vendor/madeleine-0.7.1/madeleine.gemspec delete mode 100755 vendor/madeleine-0.7.1/samples/.cvsignore delete mode 100755 vendor/madeleine-0.7.1/samples/clock_click.rb delete mode 100755 vendor/madeleine-0.7.1/samples/dictionary_client.rb delete mode 100755 vendor/madeleine-0.7.1/samples/dictionary_server.rb delete mode 100755 vendor/madeleine-0.7.1/samples/painter.rb delete mode 100755 vendor/madeleine-0.7.1/test/test.rb delete mode 100755 vendor/madeleine-0.7.1/test/test_automatic.rb delete mode 100755 vendor/madeleine-0.7.1/test/test_clocked.rb delete mode 100755 vendor/madeleine-0.7.1/test/test_command_log.rb delete mode 100755 vendor/madeleine-0.7.1/test/test_executer.rb delete mode 100755 vendor/madeleine-0.7.1/test/test_persistence.rb delete mode 100755 vendor/madeleine-0.7.1/test/test_platforms.rb delete mode 100755 vendor/madeleine-0.7.1/test/test_zmarshal.rb diff --git a/instiki.gemspec b/instiki.gemspec index 06b2a10f..adf1dd74 100755 --- a/instiki.gemspec +++ b/instiki.gemspec @@ -7,7 +7,7 @@ spec = Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'instiki' s.version = "0.10.2" - s.summary = 'Easy to install WikiClone running on WEBrick and Madeleine' + s.summary = 'Easy to install WikiClone running on WEBrick and SQLite' s.description = <<-EOF Instiki is a Wiki Clone written in Ruby that ships with an embedded webserver. You can setup up an Instiki in just a few steps. @@ -24,10 +24,10 @@ spec = Gem::Specification.new do |s| s.has_rdoc = false - s.add_dependency('madeleine', '= 0.7.1') s.add_dependency('RedCloth', '= 3.0.3') s.add_dependency('rubyzip', '= 0.5.8') s.add_dependency('rails', '= 0.13.1') + s.add_dependency('sqlite3-ruby', '= 1.1.0') s.requirements << 'none' s.require_path = 'lib' diff --git a/vendor/madeleine-0.7.1/.cvsignore b/vendor/madeleine-0.7.1/.cvsignore deleted file mode 100755 index c3c960b4..00000000 --- a/vendor/madeleine-0.7.1/.cvsignore +++ /dev/null @@ -1,2 +0,0 @@ -PrevalenceBase -*.gem diff --git a/vendor/madeleine-0.7.1/COPYING b/vendor/madeleine-0.7.1/COPYING deleted file mode 100755 index 19d570ec..00000000 --- a/vendor/madeleine-0.7.1/COPYING +++ /dev/null @@ -1,31 +0,0 @@ - - Copyright (c) 2003-2004, Anders Bengtsson - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. The names of its contributors may not be used to endorse or promote - products derived from this software without specific prior written - permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS - BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - SUCH DAMAGE. diff --git a/vendor/madeleine-0.7.1/NEWS b/vendor/madeleine-0.7.1/NEWS deleted file mode 100755 index 8fd9d325..00000000 --- a/vendor/madeleine-0.7.1/NEWS +++ /dev/null @@ -1,55 +0,0 @@ - -Madeleine 0.7.1 (August 22, 2004): - - * ZMarshal changed to work around Zlib bug. - * automatic_read_only fixed when intercepted class is inherited from - -Madeleine 0.7 (July 23, 2004): - - * Broken clock unit test on win32 fixed. - * AutomaticSnapshotMadeleine detects snapshot format on recovery - * Snapshot compression with Madeleine::ZMarshal - * YAML snapshots supported for automatic commands - * SOAP snapshots supported for automatic commands - * Read-only methods for automatic commands - -Madeleine 0.6.1 (March 30, 2004): - - * Bug fix: Use binary mode for I/O, fixes log replay - on mswin32 port of Ruby (Patch from Martin Tampe) - -Madeleine 0.6 (March 28, 2004): - - * Changed license to BSD - * Added a RubyGem specification - * Re-designed initialization (but still backward-compatible) - * Bug fix: Fixed use of finalized object's id in AutomaticSnapshotMadeleine - -Madeleine 0.5 (August 31, 2003): - - * Bug fix: Log order on recovery was wrong on some platforms - (Reported by IIMA Susumu) - * No longer requires the system clock to always increase - * Shared locks for queries - -Madeleine 0.4 (July 4, 2003): - - * Deprecated ClockedSnapshotMadeleine - * Added execute_query() - * API documentation in RDoc format - -Madeleine 0.3 (May 15, 2003): - - * Automatic commands - * Some classes exported to the default module - * Clock support not loaded by default (require 'madeleine/clock') - * Bug fix: Error handling when replaying logged commands. - * New system through block instead of argument (API change) - * Works in $SAFE = 1 - -Madeleine 0.2: - - * Supports custom marshalling implementations. - * Changed interface for ClockedSystem and Clock. - * Some documentation added, including API docs. - diff --git a/vendor/madeleine-0.7.1/README b/vendor/madeleine-0.7.1/README deleted file mode 100755 index 3fb6ce17..00000000 --- a/vendor/madeleine-0.7.1/README +++ /dev/null @@ -1,78 +0,0 @@ - -Madeleine is a Ruby implementation of Object Prevalence: Transparent -persistence of business objects using command logging and complete -system snapshots. - - - -Madeleine's design is based on Prevayler, the original Java -prevalence layer. - -Learn more about object prevalence at . - - -Installation: - - Typical installation procedure is: - $ ruby install.rb config - $ ruby install.rb setup - # ruby install.rb install (may require root privilege) - Try 'ruby install.rb --help' for detailed usage. - - [From the documentation of Minero Aoki's 'install.rb'] - -Usage: - - require 'madeleine' - - # Create an application as a prevalent system - - madeleine = SnapshotMadeleine.new("my_example_storage") { - SomeExampleApplication.new() - } - - # Do modifications of the system by sending commands through - # the Madeleine instance. A command is an object with a suitable - # "execute(system)" method. - - madeleine.execute_command(command) - - -Requirements: - - * Ruby 1.8.1 or later - - Additionaly, some of the sample code also uses ruby/tk. - - -Known problems: - - * Won't run in some Windows-ports of Ruby due to missing - fsync() call. - -Contact: - - Homepage: - - - Questions, bug reports, patches, complaints? Use the mailing list: - - -License: - - BSD (see the file COPYING) - -Credits: - - Anders Bengtsson - Prevalence core impl. - Stephen Sykes - Automatic commands impl. - - With the help of patches, testing and feedback from: - - Steve Conover, David Heinemeier Hansson, Johan Lind, Håkan Råberg, - IIMA Susumu, Martin Tampe and Jon Tirsén - - Thanks to Klaus Wuestefeld and the Prevayler developers for the - model of this software; to Minero Aoki for the installer; to Matz and - the core developers for the Ruby language! - diff --git a/vendor/madeleine-0.7.1/TODO b/vendor/madeleine-0.7.1/TODO deleted file mode 100755 index d2e14f7b..00000000 --- a/vendor/madeleine-0.7.1/TODO +++ /dev/null @@ -1,23 +0,0 @@ - - -- Fix broken time-dependent unit test -* Rolling snapshots, with age limit -- Compressed snapshots -- Full support for YAML snapshots -- SOAP marshalling -* Configurable log marshaller (or use the snapshot marshaller?) -* Write a document about the different marshallers, for app. developers. - -* Move all default implementations into a "Default" module -* Introduce an object representing a log directory -* Move recovery out of DefaultSnapshotMadeleine entirely -* Write an example with a web server - -* Replace filesystem with mock objects for unit testing. -* ClockCommand -* Integrate batched-writes in SnapshotMadeleine -* More sample code -* More documentation -* DRb integration -* Rollback -* Handle broken logs? diff --git a/vendor/madeleine-0.7.1/contrib/batched.rb b/vendor/madeleine-0.7.1/contrib/batched.rb deleted file mode 100755 index 2532cd64..00000000 --- a/vendor/madeleine-0.7.1/contrib/batched.rb +++ /dev/null @@ -1,298 +0,0 @@ -# Batched writes for Madeleine -# -# Copyright(c) Håkan Råberg 2003 -# -# -# This is an experimental implementation of batched log writes to mininize -# calls to fsync. It uses a Shared/Exclusive-Lock, implemented in sync.rb, -# which is included in Ruby 1.8. -# -# Writes are batched for a specified amount of time, before written to disk and -# then executed. -# -# For a detailed discussion about the problem, see -# http://www.prevayler.org/wiki.jsp?topic=OvercomingTheWriteBottleneck -# -# -# Usage is identical to normal SnapshotMadeleine, and it can also be used as -# persister for AutomaticSnapshotMadeleine. (One difference: the log isn't -# visible on disk until any commands are executed.) -# -# You can also use the execute_query method for shared synchronzied queries, -# for eaay coarse-grained locking of the system. -# -# The exclusive lock is only locked during the actual execution of commands and -# while closing. -# -# Keeping both log writes and executes of commands in the originating thread -# is needed by AutomaticSnapshotPrevayler. Hence the strange SimplisticPipe -# class. -# -# Todo: -# - It seems like Sync (sync.rb) prefers shared locks. This should probably -# be changed. -# -# -# Madeleine - Ruby Object Prevalence -# -# Copyright(c) Anders Bengtsson 2003 -# - -require 'madeleine' -require 'madeleine/clock' - -include Madeleine::Clock - -module Madeleine - module Batch - class BatchedSnapshotMadeleine < SnapshotMadeleine - - def initialize(directory_name, marshaller=Marshal, &new_system_block) - super(directory_name, marshaller, &new_system_block) - @log_actor = LogActor.launch(self) - end - - def execute_command(command) - verify_command_sane(command) - queued_command = QueuedCommand.new(command) - @lock.synchronize(:SH) do - raise "closed" if @closed - @logger.store(queued_command) - end - queued_command.wait_for - end - - def execute_query(query) - verify_command_sane(query) - @lock.synchronize(:SH) do - execute_without_storing(query) - end - end - - def close - @log_actor.destroy - @lock.synchronize do - @logger.close - @closed = true - end - end - - def flush - @lock.synchronize do - @logger.flush - end - end - - def take_snapshot - @lock.synchronize(:SH) do - @lock.synchronize do - @logger.close - end - Snapshot.new(@directory_name, system, @marshaller).take - @logger.reset - end - end - - private - - def create_lock - Sync.new - end - - def create_logger(directory_name, log_factory) - BatchedLogger.new(directory_name, log_factory, self.system) - end - - def log_factory - BatchedLogFactory.new - end - end - - private - - class LogActor - def self.launch(madeleine, delay=0.01) - result = new(madeleine, delay) - result - end - - def destroy - @is_destroyed = true - if @thread.alive? - @thread.wakeup - @thread.join - end - end - - private - - def initialize(madeleine, delay) - @is_destroyed = false - - madeleine.flush - @thread = Thread.new { - until @is_destroyed - sleep(delay) - madeleine.flush - end - } - end - end - - class BatchedLogFactory - def create_log(directory_name) - BatchedLog.new(directory_name) - end - end - - class BatchedLogger < Logger - def initialize(directory_name, log_factory, system) - super(directory_name, log_factory) - @buffer = [] - @system = system - end - - def store(queued_command) - @buffer << queued_command - end - - def close - return if @log.nil? - flush - @log.close - @log = nil - end - - def flush - return if @buffer.empty? - - open_new_log if @log.nil? - - if @system.kind_of?(ClockedSystem) - @buffer.unshift(QueuedTick.new) - end - - @buffer.each do |queued_command| - queued_command.store(@log) - end - - @log.flush - - @buffer.each do |queued_command| - queued_command.execute(@system) - end - - @buffer.clear - end - end - - class BatchedLog < CommandLog - def store(command) - Marshal.dump(command, @file) - end - - def flush - @file.flush - @file.fsync - end - end - - class QueuedCommand - def initialize(command) - @command = command - @pipe = SimplisticPipe.new - end - - def store(log) - @pipe.write(log) - end - - def execute(system) - @pipe.write(system) - end - - def wait_for - @pipe.read do |log| - log.store(@command) - end - - @pipe.read do |system| - return @command.execute(system) - end - end - end - - class QueuedTick - def initialize - @tick = Tick.new(Time.now) - end - - def store(log) - log.store(@tick) - end - - def execute(system) - @tick.execute(system) - end - end - - class SimplisticPipe - def initialize - @receive_lock = Mutex.new.lock - @consume_lock = Mutex.new.lock - @message = nil - end - - def read - begin - wait_for_message_received - - if block_given? - yield @message - else - return @message - end - - ensure - message_consumed - end - end - - def write(message) - raise WriteBlockedException unless can_write? - - @message = message - message_received - wait_for_message_consumed - @message = nil - end - - def can_write? - @message.nil? - end - - private - - def message_received - @receive_lock.unlock - end - - def wait_for_message_received - @receive_lock.lock - end - - def message_consumed - @consume_lock.unlock - end - - def wait_for_message_consumed - @consume_lock.lock - end - end - - class WriteBlockedException < Exception - end - end -end - -BatchedSnapshotMadeleine = Madeleine::Batch::BatchedSnapshotMadeleine diff --git a/vendor/madeleine-0.7.1/contrib/benchmark.rb b/vendor/madeleine-0.7.1/contrib/benchmark.rb deleted file mode 100755 index 6bb89b42..00000000 --- a/vendor/madeleine-0.7.1/contrib/benchmark.rb +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/local/bin/ruby -w - -$LOAD_PATH.unshift("../lib") - -require 'madeleine' -require 'batched' - -class BenchmarkCommand - def initialize(value) - @value = value - end - - def execute(system) - # do nothing - end -end - -madeleine = BatchedSnapshotMadeleine.new("benchmark-base") { :the_system } - -RUNS = 2000 - -GC.start -GC.disable - -t0 = Time.now -RUNS.times { - madeleine.execute_command(BenchmarkCommand.new(1234)) -} -t1 = Time.now - -GC.enable - -tps = RUNS/(t1 - t0) - -puts "#{tps.to_i} transactions/s" diff --git a/vendor/madeleine-0.7.1/contrib/test_batched.rb b/vendor/madeleine-0.7.1/contrib/test_batched.rb deleted file mode 100755 index 7011634e..00000000 --- a/vendor/madeleine-0.7.1/contrib/test_batched.rb +++ /dev/null @@ -1,245 +0,0 @@ -#!/usr/local/bin/ruby -w -# -# Copyright(c) 2003 Håkan Råberg -# -# Some components taken from test_persistence.rb -# Copyright(c) 2003 Anders Bengtsson -# - -$LOAD_PATH.unshift("../lib") - -require 'batched' -require 'test/unit' -require 'madeleine/clock' - - -module Madeleine::Batch - class BatchedSnapshotMadeleineTest < Test::Unit::TestCase - - class ArraySystem < Array - include Madeleine::Clock::ClockedSystem - end - - class PushCommand - def initialize(value) - @value = value - end - - def execute(system) - system << @value - end - end - - class ArrayQuery - def initialize - @time = [] - end - - def execute(system) - length = system.length - @time << system.clock.time - - a = 1 - system.each do |n| - a *= n - end - - raise "inconsistent read" unless length == system.length - raise "inconsistent read" unless @time.last == system.clock.time - end - end - - def test_live_snapshot - system = ArraySystem.new - w, r = [], [] - going = true - - madeleine = BatchedSnapshotMadeleine.new(prevalence_base) { system } - - i = 0 - 10.times do |n| - w[n] = Thread.new { - while going - madeleine.execute_command(PushCommand.new(i)) - i += 1 - sleep(0.1) - end - } - end - - q = 0 - query = ArrayQuery.new - 100.times do |n| - r[n] = Thread.new { - while going - begin - madeleine.execute_query(query) - q += 1 - rescue - fail("Query blocks writing") - end - sleep(0.1) - end - } - end - - s = 0 - snapshot = Thread.new { - while going - madeleine.take_snapshot - s += 1 - sleep(0.01) - end - } - - sleep(1) - - going = false - - r.each do |t| - t.join - end - - w.each do |t| - t.join - end - - snapshot.join - - madeleine.close - - madeleine2 = SnapshotMadeleine.new(prevalence_base) - assert_equal(madeleine.system, madeleine2.system, "Take system snapshots while accessing") - end - - def prevalence_base - "BatchedSnapshot" - end - - def teardown - delete_directory(prevalence_base) - end - end - - class BatchedLogTest < Test::Unit::TestCase - - class MockMadeleine - def initialize(logger) - @logger = logger - end - - def flush - @logger.flush - end - end - - class MockCommand - attr_reader :text - - def initialize(text) - @text = text - end - - def execute(system) - end - - def ==(o) - o.text == @text - end - end - - module BufferInspector - def buffer_size - @buffer.size - end - end - - def setup - @target = BatchedLogger.new(".", BatchedLogFactory.new, nil) - @target.extend(BufferInspector) - @madeleine = MockMadeleine.new(@target) - @messages = [] - end - - def test_logging - actor = LogActor.launch(@madeleine, 0.1) - - append("Hello") - sleep(0.01) - append("World") - sleep(0.01) - - assert_equal(2, @target.buffer_size, "Batched command queue") - assert(!File.exist?(expected_file_name), "Batched commands not on disk") - - sleep(0.2) - - assert_equal(0, @target.buffer_size, "Queue emptied by batched write") - file_size = File.size(expected_file_name) - assert(file_size > 0, "Queue written to disk") - - append("Again") - sleep(0.2) - - assert(File.size(expected_file_name) > file_size, "Command written to disk") - - f = File.new(expected_file_name) - - @messages.each do |message| - assert_equal(message, Marshal.load(f), "Commands logged in order") - end - - f.close - - actor.destroy - @target.flush - @target.close - - end - - def append(text) - Thread.new { - message = MockCommand.new(text) - @messages << message - queued_command = QueuedCommand.new(message) - @target.store(queued_command) - queued_command.wait_for - } - end - - def expected_file_name - "000000000000000000001.command_log" - end - - def teardown - assert(File.delete(expected_file_name) == 1) - end - - end - - def delete_directory(directory_name) - Dir.foreach(directory_name) do |file| - next if file == "." - next if file == ".." - assert(File.delete(directory_name + File::SEPARATOR + file) == 1, - "Unable to delete #{file}") - end - Dir.delete(directory_name) - end -end - - include Madeleine::Batch - -def add_batched_tests(suite) - suite << BatchedSnapshotMadeleineTest.suite - suite << BatchedLogTest.suite -end - -if __FILE__ == $0 - suite = Test::Unit::TestSuite.new("BatchedLogTest") - add_batched_tests(suite) - - require 'test/unit/ui/console/testrunner' - Thread.abort_on_exception = true - Test::Unit::UI::Console::TestRunner.run(suite) -end diff --git a/vendor/madeleine-0.7.1/contrib/test_scalability.rb b/vendor/madeleine-0.7.1/contrib/test_scalability.rb deleted file mode 100755 index 99b7dc5d..00000000 --- a/vendor/madeleine-0.7.1/contrib/test_scalability.rb +++ /dev/null @@ -1,248 +0,0 @@ -#!/usr/local/bin/ruby -w -# -# Copyright(c) 2003 Håkan Råberg -# -# This test is based on Prevaylers TransactionTestRun, -# Copyright(c) 2001-2003 Klaus Wuestefeld. -# - -$LOAD_PATH.unshift("../lib") - -require 'madeleine' -require 'madeleine/clock' -require 'batched' - -module ScalabilityTest - - class TransactionTestRun - MIN_THREADS = 20 - MAX_THREADS = 20 - NUMBER_OF_OBJECTS = 100000 - ROUND_DURATION = 20 - DIR = "ScalabilityBase" - - def initialize - @system = TransactionSystem.new - @madeleine = BatchedSnapshotMadeleine.new(DIR) { @system } - - @system.replace_all_records(create_records(NUMBER_OF_OBJECTS)) - - @is_round_finished = false - - @best_round_ops_per_s = 0 - @best_round_threads = 0 - @operation_count = 0 - @last_operation = 0 - @active_round_threads = 0 - - @half_of_the_objects = NUMBER_OF_OBJECTS / 2 - - @connection_cache = [] - @connection_cache_lock = Mutex.new - - ObjectSpace.garbage_collect - - puts "========= Running " + name + " (" + (MAX_THREADS - MIN_THREADS + 1).to_s + " rounds). Subject: " + subject_name + "..." - puts "Each round will take approx. " + ROUND_DURATION.to_s + " seconds to run..." - perform_test - puts "----------- BEST ROUND: " + result_string(@best_round_ops_per_s, @best_round_threads) - - @madeleine.close - delete_directory(DIR) - end - - def name - "Transaction Test" - end - - def subject_name - "Madeleine" - end - - def result_string(ops_per_s, threads) - ops_per_s.to_s + " operations/second (" + threads.to_s + " threads)" - end - - def perform_test - for threads in MIN_THREADS..MAX_THREADS - ops_per_s = perform_round(threads) - if ops_per_s > @best_round_ops_per_s - @best_round_ops_per_s = ops_per_s - @best_round_threads = threads - end - end - end - - def perform_round(threads) - initial_operation_count = @operation_count - start_time = Time.now.to_f - - start_threads(threads) - sleep(ROUND_DURATION) - stop_threads - - seconds_ellapsed = Time.now.to_f - start_time - ops_per_second = (@operation_count - initial_operation_count) / seconds_ellapsed - - puts - puts "Seconds ellapsed: " + seconds_ellapsed.to_s - puts "--------- Round Result: " + result_string(ops_per_second, threads) - - ops_per_second - end - - def start_threads(threads) - @is_round_finished = false - for i in 1..threads - start_thread(@last_operation + i, threads) - end - end - - def start_thread(starting_operation, operation_increment) - Thread.new { - connection = accquire_connection - - operation = starting_operation - while not @is_round_finished - # puts "Operation " + operation.to_s - execute_operation(connection, operation) - operation += operation_increment - end - - @connection_cache_lock.synchronize do - @connection_cache << connection - @operation_count += (operation - starting_operation) / operation_increment - @last_operation = operation if @last_operation < operation - @active_round_threads -= 1 - end - } - @active_round_threads += 1 - end - - def execute_operation(connection, operation) - record_to_insert = Record.new(NUMBER_OF_OBJECTS + operation) - id_to_delete = spread_id(operation) - record_to_update = Record.new(@half_of_the_objects + id_to_delete) - - connection.perform_transaction(record_to_insert, record_to_update, id_to_delete) - end - - def spread_id(id) - (id / @half_of_the_objects) * @half_of_the_objects + ((id * 16807) % @half_of_the_objects) - end - - def create_test_connection - TransactionConnection.new(@madeleine) - end - - def accquire_connection - @connection_cache_lock.synchronize do - return @connection_cache.empty? ? create_test_connection : @connection_cache.shift - end - end - - def stop_threads - @is_round_finished = true - while @active_round_threads != 0 - sleep(0.001) - end - end - - def create_records(number_of_objects) - result = [] - for i in 0..number_of_objects - result << Record.new(i) - end - result - end - - - def delete_directory(directory_name) - Dir.foreach(directory_name) do |file| - next if file == "." - next if file == ".." - File.delete(directory_name + File::SEPARATOR + file) - end - Dir.delete(directory_name) - end - end - - class TransactionSystem - include Madeleine::Clock::ClockedSystem - - def initialize - @records_by_id = Hash.new - @transaction_lock = Mutex.new - end - - def perform_transaction(record_to_insert, record_to_update, id_to_delete) - @transaction_lock.synchronize do - put(record_to_insert) - put(record_to_update) - @records_by_id.delete(id_to_delete) - end - end - - def put(new_record) - @records_by_id[new_record.id] = new_record - end - - def replace_all_records(new_records) - @records_by_id.clear - new_records.each do |record| - put(record) - end - end - end - - class TransactionConnection - def initialize(madeleine) - @madeleine = madeleine - end - - def perform_transaction(record_to_insert, record_to_update, id_to_delete) - @madeleine.execute_command(TestTransaction.new(record_to_insert, record_to_update, id_to_delete)) - end - end - - class TestTransaction - def initialize(record_to_insert, record_to_update, id_to_delete) - @record_to_insert = record_to_insert - @record_to_update = record_to_update - @id_to_delete = id_to_delete - end - - def execute(system) - system.perform_transaction(@record_to_insert, @record_to_update, @id_to_delete) - end - end - - class Record - attr_reader :id, :name, :string_1, :date_1, :date_2 - - def initialize(id) - @id = id - @name = "NAME" + (id % 10000).to_s - @string_1 = (id % 10000).to_s == 0 ? Record.large_string + id : nil; - @date_1 = Record.random_date - @date_2 = Record.random_date - end - - def self.large_string - [].fill("A", 1..980).to_s - end - - def self.random_date - rand(10000000) - end - end -end - -if __FILE__ == $0 - puts "Madeleine Scalability Test" - puts "Based on Prevaylers Scalability Test" - puts - - Thread.abort_on_exception = true - ScalabilityTest::TransactionTestRun.new -end diff --git a/vendor/madeleine-0.7.1/contrib/threaded_benchmark.rb b/vendor/madeleine-0.7.1/contrib/threaded_benchmark.rb deleted file mode 100755 index 5e8f88a7..00000000 --- a/vendor/madeleine-0.7.1/contrib/threaded_benchmark.rb +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/local/bin/ruby -w - -$LOAD_PATH.unshift("../lib") - -require 'madeleine' -require 'batched' - -class BenchmarkCommand - def initialize(value) - @value = value - end - - def execute(system) - # do nothing - end -end - -madeleine = BattchedSnapshotMadeleine.new("benchmark-base") { :the_system } - -RUNS = 200 -THREADS = 10 - -GC.start -GC.disable - -t0 = Time.now - -threads = [] -THREADS.times { - threads << Thread.new { - RUNS.times { - madeleine.execute_command(BenchmarkCommand.new(1234)) - } - } -} -threads.each {|t| t.join } -t1 = Time.now - -GC.enable - -tps = (THREADS * RUNS)/(t1 - t0) - -puts "#{tps.to_i} transactions/s" - diff --git a/vendor/madeleine-0.7.1/docs/.cvsignore b/vendor/madeleine-0.7.1/docs/.cvsignore deleted file mode 100755 index eedd89b4..00000000 --- a/vendor/madeleine-0.7.1/docs/.cvsignore +++ /dev/null @@ -1 +0,0 @@ -api diff --git a/vendor/madeleine-0.7.1/docs/designRules.html b/vendor/madeleine-0.7.1/docs/designRules.html deleted file mode 100755 index c6db4546..00000000 --- a/vendor/madeleine-0.7.1/docs/designRules.html +++ /dev/null @@ -1,87 +0,0 @@ - - - -Design rules - Madeleine - - - - - -

Design rules

- -

This is a summary of the design rules your application has to -follow to work with Madeleine. - - -

The Prevalent System

- -

Your objects have to fit into memory

- -

All of them. At the same time. - -

Your objects have to be marshallable

- -

Snapshots are taken of the system by marshalling the whole system to a -file. If your classes can't be marshalled/unmarshalled then Madeleine -won't be able to store/restore the system. - -

Your objects have to be deterministic

- -

Deterministic means that, given the same commands, they have -to always give the same results. - -

For the much of your code this won't -be a problem, but there are a few common issues: - -

The system clock

-

You can't use the system clock (see instead ClockedSystem and TimeActor). - -

Random numbers

-

Kernel.rand() uses the system clock internally by -default. Use Kernel.srand() to seed the random number -generator before using rand(). - -

Files, network and other IO

-

You generally can't access the outside world from within your -prevalent system. Instead do IO outside of the prevalent system and -call into the system when needed. - -

Changes to the system have to be done through command -objects

- -

Everything that modifies the prevalent system must be done through a -command object sent to the Madeleine instance, using -execute_command(aCommand). Queries that don't modify the -system can be done either through direct method calls or through -command objects. - -

Command Objects

- -

A command object is an object that implements the method -execute(system). They are an example of the "Command" -design pattern. - -

The command objects also have to be marshallable

- -

Madeleine keeps track of changes between snapshots by logging -marshalled commands. - -

The command must raise errors before modifying the system

- -

Unlike a RDBMS, Madeleine can't roll back a command (yet). This means -that your commands will have to do their error checking and raise any -errors before modifying the system. Failing to do this will cause an -inconsistent command log. - -

Command objects can't hold references to the system's objects

- -

Unmarshalling such a command would create clones of the original -objects, which would then be modified instead of the real -objects. The commands must find the objects to modify. - -


- -$Id: designRules.html,v 1.1 2005/01/07 23:03:27 alexeyv Exp $ - - - diff --git a/vendor/madeleine-0.7.1/docs/docs.css b/vendor/madeleine-0.7.1/docs/docs.css deleted file mode 100755 index 0c70ccde..00000000 --- a/vendor/madeleine-0.7.1/docs/docs.css +++ /dev/null @@ -1,28 +0,0 @@ -body { - background-color: #FFFFF0; -} -p { - width: 70ex -} -h1 { - font-family: verdana,arial,helvetica,sans-serif; -} -h2 { - font-family: verdana,arial,helvetica,sans-serif; - background: #EEEEE0; -} -h3 { - font-family: verdana,arial,helvetica,sans-serif; -} -h4 { - font-family: verdana,arial,helvetica,sans-serif; -} -.classMethod { - font-family: courier,monospace; - font-weight: bold; - background: #EEEEE0; -} -.instanceMethod { - font-family: courier,sans-serif; - background: #EEEEE0; -} diff --git a/vendor/madeleine-0.7.1/generate_rdoc.rb b/vendor/madeleine-0.7.1/generate_rdoc.rb deleted file mode 100755 index b4e0ba7b..00000000 --- a/vendor/madeleine-0.7.1/generate_rdoc.rb +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/local/bin/ruby - -`rdoc lib --op docs/api` diff --git a/vendor/madeleine-0.7.1/install.rb b/vendor/madeleine-0.7.1/install.rb deleted file mode 100755 index e624774e..00000000 --- a/vendor/madeleine-0.7.1/install.rb +++ /dev/null @@ -1,1098 +0,0 @@ -# -# This file is automatically generated. DO NOT MODIFY! -# -# install.rb -# -# Copyright (c) 2000-2003 Minero Aoki -# -# This program is free software. -# You can distribute/modify this program under the terms of -# the GNU Lesser General Public License version 2. -# - -### begin compat.rb - -module Enumerable - methods = instance_methods() - - unless methods.include?('map') - alias map collect - end - - unless methods.include?('select') - alias select find_all - end - - unless methods.include?('reject') - def reject - result = [] - each do |i| - result.push i unless yield(i) - end - result - end - end - - unless methods.include?('inject') - def inject( result ) - each do |i| - result = yield(result, i) - end - result - end - end - - unless methods.include?('any?') - def any? - each do |i| - return true if yield(i) - end - false - end - end -end - -def File.read_all( fname ) - File.open(fname, 'rb') {|f| return f.read } -end - -def File.write( fname, str ) - File.open(fname, 'wb') {|f| f.write str } -end - -### end compat.rb -### begin config.rb - -if i = ARGV.index(/\A--rbconfig=/) - file = $' - ARGV.delete_at(i) - require file -else - require 'rbconfig' -end - - -class ConfigTable - - c = ::Config::CONFIG - - rubypath = c['bindir'] + '/' + c['ruby_install_name'] - - major = c['MAJOR'].to_i - minor = c['MINOR'].to_i - teeny = c['TEENY'].to_i - version = "#{major}.#{minor}" - - # ruby ver. >= 1.4.4? - newpath_p = ((major >= 2) or - ((major == 1) and - ((minor >= 5) or - ((minor == 4) and (teeny >= 4))))) - - re = Regexp.new('\A' + Regexp.quote(c['prefix'])) - subprefix = lambda {|path| - re === path and path.sub(re, '$prefix') - } - - if c['rubylibdir'] - # V < 1.6.3 - stdruby = subprefix.call(c['rubylibdir']) - siteruby = subprefix.call(c['sitedir']) - versite = subprefix.call(c['sitelibdir']) - sodir = subprefix.call(c['sitearchdir']) - elsif newpath_p - # 1.4.4 <= V <= 1.6.3 - stdruby = "$prefix/lib/ruby/#{version}" - siteruby = subprefix.call(c['sitedir']) - versite = siteruby + '/' + version - sodir = "$site-ruby/#{c['arch']}" - else - # V < 1.4.4 - stdruby = "$prefix/lib/ruby/#{version}" - siteruby = "$prefix/lib/ruby/#{version}/site_ruby" - versite = siteruby - sodir = "$site-ruby/#{c['arch']}" - end - - DESCRIPTER = [ - [ 'prefix', [ c['prefix'], - 'path', - 'path prefix of target environment' ] ], - [ 'std-ruby', [ stdruby, - 'path', - 'the directory for standard ruby libraries' ] ], - [ 'site-ruby-common', [ siteruby, - 'path', - 'the directory for version-independent non-standard ruby libraries' ] ], - [ 'site-ruby', [ versite, - 'path', - 'the directory for non-standard ruby libraries' ] ], - [ 'bin-dir', [ '$prefix/bin', - 'path', - 'the directory for commands' ] ], - [ 'rb-dir', [ '$site-ruby', - 'path', - 'the directory for ruby scripts' ] ], - [ 'so-dir', [ sodir, - 'path', - 'the directory for ruby extentions' ] ], - [ 'data-dir', [ '$prefix/share', - 'path', - 'the directory for shared data' ] ], - [ 'ruby-path', [ rubypath, - 'path', - 'path to set to #! line' ] ], - [ 'ruby-prog', [ rubypath, - 'name', - 'the ruby program using for installation' ] ], - [ 'make-prog', [ 'make', - 'name', - 'the make program to compile ruby extentions' ] ], - [ 'without-ext', [ 'no', - 'yes/no', - 'does not compile/install ruby extentions' ] ] - ] - - SAVE_FILE = 'config.save' - - def ConfigTable.each_name( &block ) - keys().each(&block) - end - - def ConfigTable.keys - DESCRIPTER.map {|k,*dummy| k } - end - - def ConfigTable.each_definition( &block ) - DESCRIPTER.each(&block) - end - - def ConfigTable.get_entry( name ) - name, ent = DESCRIPTER.assoc(name) - ent - end - - def ConfigTable.get_entry!( name ) - get_entry(name) or raise ArgumentError, "no such config: #{name}" - end - - def ConfigTable.add_entry( name, vals ) - ConfigTable::DESCRIPTER.push [name,vals] - end - - def ConfigTable.remove_entry( name ) - get_entry name or raise ArgumentError, "no such config: #{name}" - DESCRIPTER.delete_if {|n,arr| n == name } - end - - def ConfigTable.config_key?( name ) - get_entry(name) ? true : false - end - - def ConfigTable.bool_config?( name ) - ent = get_entry(name) or return false - ent[1] == 'yes/no' - end - - def ConfigTable.value_config?( name ) - ent = get_entry(name) or return false - ent[1] != 'yes/no' - end - - def ConfigTable.path_config?( name ) - ent = get_entry(name) or return false - ent[1] == 'path' - end - - - class << self - alias newobj new - - def new - c = newobj() - c.__send__ :init - c - end - - def load - c = newobj() - raise InstallError, "#{File.basename $0} config first"\ - unless FileTest.file?(SAVE_FILE) - File.foreach(SAVE_FILE) do |line| - k, v = line.split(/=/, 2) - c.instance_eval { - @table[k] = v.strip - } - end - c - end - end - - def initialize - @table = {} - end - - def init - DESCRIPTER.each do |k, (default, vname, desc, default2)| - @table[k] = default - end - end - private :init - - def save - File.open(SAVE_FILE, 'w') {|f| - @table.each do |k, v| - f.printf "%s=%s\n", k, v if v - end - } - end - - def []=( k, v ) - ConfigTable.config_key? k or raise InstallError, "unknown config option #{k}" - if ConfigTable.path_config? k - @table[k] = (v[0,1] != '$') ? File.expand_path(v) : v - else - @table[k] = v - end - end - - def []( key ) - @table[key] or return nil - @table[key].gsub(%r<\$([^/]+)>) { self[$1] } - end - - def set_raw( key, val ) - @table[key] = val - end - - def get_raw( key ) - @table[key] - end - -end - - -module MetaConfigAPI - - def eval_file_ifexist( fname ) - instance_eval File.read_all(fname), fname, 1 if FileTest.file?(fname) - end - - def config_names - ConfigTable.keys - end - - def config?( name ) - ConfigTable.config_key? name - end - - def bool_config?( name ) - ConfigTable.bool_config? name - end - - def value_config?( name ) - ConfigTable.value_config? name - end - - def path_config?( name ) - ConfigTable.path_config? name - end - - def add_config( name, argname, default, desc ) - ConfigTable.add_entry name,[default,argname,desc] - end - - def add_path_config( name, default, desc ) - add_config name, 'path', default, desc - end - - def add_bool_config( name, default, desc ) - add_config name, 'yes/no', default ? 'yes' : 'no', desc - end - - def set_config_default( name, default ) - if bool_config? name - ConfigTable.get_entry!(name)[0] = default ? 'yes' : 'no' - else - ConfigTable.get_entry!(name)[0] = default - end - end - - def remove_config( name ) - ent = ConfigTable.get_entry(name) - ConfigTable.remove_entry name - ent - end - -end - -### end config.rb -### begin fileop.rb - -module FileOperations - - def mkdir_p( dname, prefix = nil ) - dname = prefix + dname if prefix - $stderr.puts "mkdir -p #{dname}" if verbose? - return if no_harm? - - # does not check '/'... it's too abnormal case - dirs = dname.split(%r<(?=/)>) - if /\A[a-z]:\z/i === dirs[0] - disk = dirs.shift - dirs[0] = disk + dirs[0] - end - dirs.each_index do |idx| - path = dirs[0..idx].join('') - Dir.mkdir path unless dir? path - end - end - - def rm_f( fname ) - $stderr.puts "rm -f #{fname}" if verbose? - return if no_harm? - - if File.exist? fname or File.symlink? fname - File.chmod 0777, fname - File.unlink fname - end - end - - def rm_rf( dn ) - $stderr.puts "rm -rf #{dn}" if verbose? - return if no_harm? - - Dir.chdir dn - Dir.foreach('.') do |fn| - next if fn == '.' - next if fn == '..' - if dir? fn - verbose_off { - rm_rf fn - } - else - verbose_off { - rm_f fn - } - end - end - Dir.chdir '..' - Dir.rmdir dn - end - - def mv( src, dest ) - rm_f dest - begin - File.link src, dest - rescue - File.write dest, File.read_all(src) - File.chmod File.stat(src).mode, dest - end - rm_f src - end - - def install( from, dest, mode, prefix = nil ) - $stderr.puts "install #{from} #{dest}" if verbose? - return if no_harm? - - realdest = prefix + dest if prefix - if dir? realdest - realdest += '/' + File.basename(from) - end - str = File.read_all(from) - if diff? str, realdest - verbose_off { - rm_f realdest if File.exist? realdest - } - File.write realdest, str - File.chmod mode, realdest - - File.open(objdir + '/InstalledFiles', 'a') {|f| f.puts realdest } - end - end - - def diff?( orig, targ ) - return true unless File.exist? targ - orig != File.read_all(targ) - end - - def command( str ) - $stderr.puts str if verbose? - system str or raise RuntimeError, "'system #{str}' failed" - end - - def ruby( str ) - command config('ruby-prog') + ' ' + str - end - - def dir?( dname ) - # for corrupted windows stat() - File.directory?((dname[-1,1] == '/') ? dname : dname + '/') - end - - def all_files_in( dname ) - Dir.open(dname) {|d| - return d.select {|n| FileTest.file? "#{dname}/#{n}" } - } - end - - REJECT_DIRS = %w( - CVS SCCS RCS CVS.adm - ) - - def all_dirs_in( dname ) - Dir.open(dname) {|d| - return d.select {|n| dir? "#{dname}/#{n}" } - %w(. ..) - REJECT_DIRS - } - end - -end - -### end fileop.rb -### begin base.rb - -class InstallError < StandardError; end - - -class Installer - - Version = '3.1.4' - Copyright = 'Copyright (c) 2000-2003 Minero Aoki' - - - @toplevel = nil - - def self.declare_toplevel_installer( inst ) - raise ArgumentError, 'two toplevel installers declared' if @toplevel - @toplevel = inst - end - - def self.toplevel_installer - @toplevel - end - - - FILETYPES = %w( bin lib ext data ) - - include FileOperations - - def initialize( config, opt, srcroot, objroot ) - @config = config - @options = opt - @srcdir = File.expand_path(srcroot) - @objdir = File.expand_path(objroot) - @currdir = '.' - end - - def inspect - "#<#{self.class} #{__id__}>" - end - - # - # configs/options - # - - def get_config( key ) - @config[key] - end - - alias config get_config - - def set_config( key, val ) - @config[key] = val - end - - def no_harm? - @options['no-harm'] - end - - def verbose? - @options['verbose'] - end - - def verbose_off - save, @options['verbose'] = @options['verbose'], false - yield - @options['verbose'] = save - end - - # - # srcdir/objdir - # - - attr_reader :srcdir - alias srcdir_root srcdir - alias package_root srcdir - - def curr_srcdir - "#{@srcdir}/#{@currdir}" - end - - attr_reader :objdir - alias objdir_root objdir - - def curr_objdir - "#{@objdir}/#{@currdir}" - end - - def srcfile( path ) - curr_srcdir + '/' + path - end - - def srcexist?( path ) - File.exist? srcfile(path) - end - - def srcdirectory?( path ) - dir? srcfile(path) - end - - def srcfile?( path ) - FileTest.file? srcfile(path) - end - - def srcentries( path = '.' ) - Dir.open(curr_srcdir + '/' + path) {|d| - return d.to_a - %w(. ..) - hookfilenames - } - end - - def srcfiles( path = '.' ) - srcentries(path).select {|fname| - FileTest.file? File.join(curr_srcdir, path, fname) - } - end - - def srcdirectories( path = '.' ) - srcentries(path).select {|fname| - dir? File.join(curr_srcdir, path, fname) - } - end - - def dive_into( rel ) - return unless dir?("#{@srcdir}/#{rel}") - - dir = File.basename(rel) - Dir.mkdir dir unless dir?(dir) - prevdir = Dir.pwd - Dir.chdir dir - $stderr.puts '---> ' + rel if verbose? - @currdir = rel - yield - Dir.chdir prevdir - $stderr.puts '<--- ' + rel if verbose? - @currdir = File.dirname(rel) - end - - # - # TASK config - # - - def exec_config - exec_task_traverse 'config' - end - - def config_dir_bin( rel ) - end - - def config_dir_lib( rel ) - end - - def config_dir_ext( rel ) - extconf if extdir? curr_srcdir - end - - def extconf - opt = @options['config-opt'].join(' ') - command "#{config('ruby-prog')} #{curr_srcdir}/extconf.rb #{opt}" - end - - def config_dir_data( rel ) - end - - # - # TASK setup - # - - def exec_setup - exec_task_traverse 'setup' - end - - def setup_dir_bin( relpath ) - all_files_in(curr_srcdir()).each do |fname| - add_rubypath "#{curr_srcdir}/#{fname}" - end - end - - SHEBANG_RE = /\A\#!\s*\S*ruby\S*/ - - def add_rubypath( path ) - $stderr.puts %Q if verbose? - return if no_harm? - - tmpfile = File.basename(path) + '.tmp' - begin - File.open(path) {|r| - File.open(tmpfile, 'w') {|w| - first = r.gets - return unless SHEBANG_RE === first # reject '/usr/bin/env ruby' - - w.print first.sub(SHEBANG_RE, '#!' + config('ruby-path')) - w.write r.read - } } - mv tmpfile, File.basename(path) - ensure - rm_f tmpfile if File.exist? tmpfile - end - end - - def setup_dir_lib( relpath ) - end - - def setup_dir_ext( relpath ) - make if extdir?(curr_srcdir) - end - - def setup_dir_data( relpath ) - end - - # - # TASK install - # - - def exec_install - exec_task_traverse 'install' - end - - def install_dir_bin( rel ) - install_files target_filenames(), config('bin-dir') + '/' + rel, 0755 - end - - def install_dir_lib( rel ) - install_files target_filenames(), config('rb-dir') + '/' + rel, 0644 - end - - def install_dir_ext( rel ) - install_dir_ext_main File.dirname(rel) if extdir?(curr_srcdir) - end - - def install_dir_ext_main( rel ) - install_files allext('.'), config('so-dir') + '/' + rel, 0555 - end - - def install_dir_data( rel ) - install_files target_filenames(), config('data-dir') + '/' + rel, 0644 - end - - def install_files( list, dest, mode ) - mkdir_p dest, @options['install-prefix'] - list.each do |fname| - install fname, dest, mode, @options['install-prefix'] - end - end - - def target_filenames - if FileTest.file? "#{curr_srcdir()}/MANIFEST" - mapdir(target_filenames_MANIFEST()) - else - mapdir(target_filenames_AUTO()) - end - end - - def mapdir( filelist ) - filelist.map {|fname| - if File.exist? fname # current objdir == '.' - fname - else - File.join(curr_srcdir(), fname) - end - } - end - - def target_filenames_MANIFEST - File.read_all("#{curr_srcdir()}/MANIFEST").split - end - - # picked up many entries from cvs-1.11.1/src/ignore.c - REJECT_PATTERNS = %w( - core RCSLOG tags TAGS .make.state - .nse_depinfo #* .#* cvslog.* ,* .del-* *.a *.olb *.o *.obj - *.so *.Z *~ *.old *.elc *.ln *.bak *.BAK *.orig *.rej *.exe _$* *$ - - *.org *.in .* - ).map {|pattern| - Regexp.compile('\A' + pattern.gsub(/[\.\$]/) {|s| '\\' + s }.gsub(/\*/, '.*') + '\z') - } - - def target_filenames_AUTO - (existfiles() - hookfiles()).reject {|fname| - REJECT_PATTERNS.any? {|re| re === fname } - } - end - - def existfiles - all_files_in(curr_srcdir()) | all_files_in(curr_objdir()) - end - - def hookfiles - %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| - %w( config setup install clean ).map {|t| sprintf(fmt, t) } - }.flatten - end - - def allext( dir ) - _allext(dir) or raise InstallError, - "no extention exists: Have you done 'ruby #{$0} setup' ?" - end - - DLEXT = /\.#{ ::Config::CONFIG['DLEXT'] }\z/ - - def _allext( dir ) - Dir.open(dir) {|d| - return d.select {|fname| DLEXT === fname } - } - end - - # - # TASK clean - # - - def exec_clean - exec_task_traverse 'clean' - rm_f 'config.save' - rm_f 'InstalledFiles' - end - - def clean_dir_bin( rel ) - end - - def clean_dir_lib( rel ) - end - - def clean_dir_ext( rel ) - make 'clean' if FileTest.file?('Makefile') - end - - def clean_dir_data( rel ) - end - - # - # TASK distclean - # - - def exec_distclean - exec_task_traverse 'distclean' - rm_f 'config.save' - rm_f 'InstalledFiles' - end - - def distclean_dir_bin( rel ) - end - - def distclean_dir_lib( rel ) - end - - def distclean_dir_ext( rel ) - make 'distclean' if FileTest.file?('Makefile') - end - - # - # lib - # - - def make( task = '' ) - command config('make-prog') + ' ' + task - end - - def exec_task_traverse( task ) - run_hook 'pre-' + task - FILETYPES.each do |type| - if config('without-ext') == 'yes' and type == 'ext' - $stderr.puts 'skipping ext/* by user option' if verbose? - next - end - traverse task, type, task + '_dir_' + type - end - run_hook 'post-' + task - end - - def traverse( task, rel, mid ) - dive_into(rel) { - run_hook 'pre-' + task - __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') - all_dirs_in(curr_srcdir()).each do |d| - traverse task, rel + '/' + d, mid - end - run_hook 'post-' + task - } - end - - def run_hook( name ) - try_run_hook curr_srcdir + '/' + name or - try_run_hook curr_srcdir + '/' + name + '.rb' - end - - def try_run_hook( fname ) - return false unless FileTest.file?(fname) - - env = self.dup - begin - env.instance_eval File.read_all(fname), fname, 1 - rescue - raise InstallError, "hook #{fname} failed:\n" + $!.message - end - true - end - - def extdir?( dir ) - File.exist? dir + '/MANIFEST' - end - -end - -### end base.rb -### begin toplevel.rb - -class ToplevelInstaller < Installer - - def self.invoke - new(File.dirname($0)).invoke - end - - - TASKS = [ - [ 'config', 'saves your configurations' ], - [ 'show', 'shows current configuration' ], - [ 'setup', 'compiles extention or else' ], - [ 'install', 'installs files' ], - [ 'clean', "does `make clean' for each extention" ], - [ 'distclean',"does `make distclean' for each extention" ] - ] - - - def initialize( root ) - super nil, {'verbose' => true}, root, '.' - Installer.declare_toplevel_installer self - end - - - def invoke - run_metaconfigs - - case task = parsearg_global() - when 'config' - @config = ConfigTable.new - else - @config = ConfigTable.load - end - parsearg_TASK task - - exectask task - end - - include MetaConfigAPI - - def run_metaconfigs - eval_file_ifexist "#{srcdir_root()}/metaconfig" - end - - - def exectask( task ) - if task == 'show' - exec_show - else - try task - end - end - - def try( task ) - $stderr.printf "#{File.basename $0}: entering %s phase...\n", task if verbose? - begin - __send__ 'exec_' + task - rescue - $stderr.printf "%s failed\n", task - raise - end - $stderr.printf "#{File.basename $0}: %s done.\n", task if verbose? - end - - # - # processing arguments - # - - def parsearg_global - task_re = /\A(?:#{TASKS.map {|i| i[0] }.join '|'})\z/ - - while arg = ARGV.shift - case arg - when /\A\w+\z/ - task_re === arg or raise InstallError, "wrong task: #{arg}" - return arg - - when '-q', '--quiet' - @options['verbose'] = false - - when '--verbose' - @options['verbose'] = true - - when '-h', '--help' - print_usage $stdout - exit 0 - - when '-v', '--version' - puts "#{File.basename $0} version #{Version}" - exit 0 - - when '--copyright' - puts Copyright - exit 0 - - else - raise InstallError, "unknown global option '#{arg}'" - end - end - - raise InstallError, "No task or global option given. -Typical installation procedure is: - $ ruby #{File.basename $0} config - $ ruby #{File.basename $0} setup - # ruby #{File.basename $0} install (may require root privilege) -" - end - - - def parsearg_TASK( task ) - mid = "parsearg_#{task}" - if respond_to? mid, true - __send__ mid - else - ARGV.empty? or - raise InstallError, "#{task}: unknown options: #{ARGV.join ' '}" - end - end - - def parsearg_config - re = /\A--(#{ConfigTable.keys.join '|'})(?:=(.*))?\z/ - @options['config-opt'] = [] - - while i = ARGV.shift - if /\A--?\z/ === i - @options['config-opt'] = ARGV.dup - break - end - m = re.match(i) or raise InstallError, "config: unknown option #{i}" - name, value = m.to_a[1,2] - if value - if ConfigTable.bool_config?(name) - /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i === value or raise InstallError, "config: --#{name} allows only yes/no for argument" - value = (/\Ay(es)?|\At(rue)/i === value) ? 'yes' : 'no' - end - else - ConfigTable.bool_config?(name) or raise InstallError, "config: --#{name} requires argument" - value = 'yes' - end - @config[name] = value - end - end - - def parsearg_install - @options['no-harm'] = false - @options['install-prefix'] = '' - while a = ARGV.shift - case a - when /\A--no-harm\z/ - @options['no-harm'] = true - when /\A--prefix=(.*)\z/ - path = $1 - path = File.expand_path(path) unless path[0,1] == '/' - @options['install-prefix'] = path - else - raise InstallError, "install: unknown option #{a}" - end - end - end - - - def print_usage( out ) - out.puts 'Typical Installation Procedure:' - out.puts " $ ruby #{File.basename $0} config" - out.puts " $ ruby #{File.basename $0} setup" - out.puts " # ruby #{File.basename $0} install (may require root privilege)" - out.puts - out.puts 'Detailed Usage:' - out.puts " ruby #{File.basename $0} " - out.puts " ruby #{File.basename $0} [] []" - - fmt = " %-20s %s\n" - out.puts - out.puts 'Global options:' - out.printf fmt, '-q,--quiet', 'suppress message outputs' - out.printf fmt, ' --verbose', 'output messages verbosely' - out.printf fmt, '-h,--help', 'print this message' - out.printf fmt, '-v,--version', 'print version and quit' - out.printf fmt, ' --copyright', 'print copyright and quit' - - out.puts - out.puts 'Tasks:' - TASKS.each do |name, desc| - out.printf " %-10s %s\n", name, desc - end - - out.puts - out.puts 'Options for config:' - ConfigTable.each_definition do |name, (default, arg, desc, default2)| - out.printf " %-20s %s [%s]\n", - '--'+ name + (ConfigTable.bool_config?(name) ? '' : '='+arg), - desc, - default2 || default - end - out.printf " %-20s %s [%s]\n", - '--rbconfig=path', 'your rbconfig.rb to load', "running ruby's" - - out.puts - out.puts 'Options for install:' - out.printf " %-20s %s [%s]\n", - '--no-harm', 'only display what to do if given', 'off' - out.printf " %-20s %s [%s]\n", - '--prefix', 'install path prefix', '$prefix' - - out.puts - end - - # - # config - # - - def exec_config - super - @config.save - end - - # - # show - # - - def exec_show - ConfigTable.each_name do |k| - v = @config.get_raw(k) - if not v or v.empty? - v = '(not specified)' - end - printf "%-10s %s\n", k, v - end - end - -end - -### end toplevel.rb - -if $0 == __FILE__ - begin - ToplevelInstaller.invoke - rescue - raise if $DEBUG - $stderr.puts $!.message - $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." - exit 1 - end -end diff --git a/vendor/madeleine-0.7.1/lib/madeleine.rb b/vendor/madeleine-0.7.1/lib/madeleine.rb deleted file mode 100755 index 3b6f8fdb..00000000 --- a/vendor/madeleine-0.7.1/lib/madeleine.rb +++ /dev/null @@ -1,420 +0,0 @@ -# -# Madeleine - Ruby Object Prevalence -# -# Author:: Anders Bengtsson -# Copyright:: Copyright (c) 2003-2004 -# -# Usage: -# -# require 'madeleine' -# -# madeleine = SnapshotMadeleine.new("my_example_storage") { -# SomeExampleApplication.new() -# } -# -# madeleine.execute_command(command) -# - -module Madeleine - - require 'thread' - require 'sync' - require 'madeleine/files' - - MADELEINE_VERSION = "0.7.1" - - class SnapshotMadeleine - - # Builds a new Madeleine instance. If there is a snapshot available - # then the system will be created from that, otherwise - # new_system will be used. The state of the system will - # then be restored from the command logs. - # - # You can provide your own snapshot marshaller, for instance using - # YAML or SOAP, instead of Ruby's built-in marshaller. The - # snapshot_marshaller must respond to - # load(stream) and dump(object, stream). You - # must use the same marshaller every time for a system. - # - # See: DefaultSnapshotMadeleine - # - # * directory_name - Storage directory to use. Will be created if needed. - # * snapshot_marshaller - Marshaller to use for system snapshots. (Optional) - # * new_system_block - Block to create a new system (if no stored system was found). - def self.new(directory_name, snapshot_marshaller=Marshal, &new_system_block) - log_factory = DefaultLogFactory.new - logger = Logger.new(directory_name, - log_factory) - snapshotter = Snapshotter.new(directory_name, - snapshot_marshaller) - lock = DefaultLock.new - recoverer = Recoverer.new(directory_name, - snapshot_marshaller) - system = recoverer.recover_snapshot(new_system_block) - - executer = Executer.new(system) - recoverer.recover_logs(executer) - DefaultSnapshotMadeleine.new(system, logger, snapshotter, lock, executer) - end - end - - class DefaultSnapshotMadeleine - - # The prevalent system - attr_reader :system - - def initialize(system, logger, snapshotter, lock, executer) - @system = system - @logger = logger - @snapshotter = snapshotter - @lock = lock - @executer = executer - - @closed = false - end - - # Execute a command on the prevalent system. - # - # Commands must have a method execute(aSystem). - # Otherwise an error, Madeleine::InvalidCommandException, - # will be raised. - # - # The return value from the command's execute() method is returned. - # - # * command - The command to execute on the system. - def execute_command(command) - verify_command_sane(command) - @lock.synchronize { - raise "closed" if @closed - @logger.store(command) - @executer.execute(command) - } - end - - # Execute a query on the prevalent system. - # - # Only differs from execute_command in that the command/query isn't logged, and - # therefore isn't allowed to modify the system. A shared lock is held, preventing others - # from modifying the system while the query is running. - # - # * query - The query command to execute - def execute_query(query) - @lock.synchronize_shared { - @executer.execute(query) - } - end - - # Take a snapshot of the current system. - # - # You need to regularly take a snapshot of a running system, - # otherwise the logs will grow big and restarting the system will take a - # long time. Your backups must also be done from the snapshot files, - # since you can't make a consistent backup of a live log. - # - # A practical way of doing snapshots is a timer thread: - # - # Thread.new(madeleine) {|madeleine| - # while true - # sleep(60 * 60 * 24) # 24 hours - # madeleine.take_snapshot - # end - # } - def take_snapshot - @lock.synchronize { - @logger.close - @snapshotter.take(@system) - @logger.reset - } - end - - # Close the system. - # - # The log file is closed and no new commands can be received - # by this Madeleine. - def close - @lock.synchronize { - @logger.close - @closed = true - } - end - - private - - def verify_command_sane(command) - unless command.respond_to?(:execute) - raise InvalidCommandException.new("Commands must have an 'execute' method") - end - end - end - - class InvalidCommandException < Exception - end - - # - # Internal classes below - # - - FILE_COUNTER_SIZE = 21 #:nodoc: - - class DefaultLock #:nodoc: - - def initialize - @lock = Sync.new - end - - def synchronize(&block) - @lock.synchronize(&block) - end - - def synchronize_shared(&block) - @lock.synchronize(:SH, &block) - end - end - - class Executer #:nodoc: - - def initialize(system) - @system = system - @in_recovery = false - end - - def execute(command) - begin - command.execute(@system) - rescue - raise unless @in_recovery - end - end - - def recovery - begin - @in_recovery = true - yield - ensure - @in_recovery = false - end - end - end - - class Recoverer #:nodoc: - - def initialize(directory_name, marshaller) - @directory_name, @marshaller = directory_name, marshaller - end - - def recover_snapshot(new_system_block) - system = nil - id = SnapshotFile.highest_id(@directory_name) - if id > 0 - snapshot_file = SnapshotFile.new(@directory_name, id).name - open(snapshot_file, "rb") {|snapshot| - system = @marshaller.load(snapshot) - } - else - system = new_system_block.call - end - system - end - - def recover_logs(executer) - executer.recovery { - CommandLog.log_file_names(@directory_name, FileService.new).each {|file_name| - open(@directory_name + File::SEPARATOR + file_name, "rb") {|log| - recover_log(executer, log) - } - } - } - end - - private - - def recover_log(executer, log) - while ! log.eof? - command = Marshal.load(log) - executer.execute(command) - end - end - end - - class NumberedFile #:nodoc: - - def initialize(path, name, id) - @path, @name, @id = path, name, id - end - - def name - result = @path - result += File::SEPARATOR - result += sprintf("%0#{FILE_COUNTER_SIZE}d", @id) - result += '.' - result += @name - end - end - - class CommandLog #:nodoc: - - def self.log_file_names(directory_name, file_service) - return [] unless file_service.exist?(directory_name) - result = file_service.dir_entries(directory_name).select {|name| - name =~ /^\d{#{FILE_COUNTER_SIZE}}\.command_log$/ - } - result.each {|name| name.untaint } - result.sort! - result - end - - def initialize(path, file_service) - id = self.class.highest_log(path, file_service) + 1 - numbered_file = NumberedFile.new(path, "command_log", id) - @file = file_service.open(numbered_file.name, 'wb') - end - - def close - @file.close - end - - def store(command) - Marshal.dump(command, @file) - @file.flush - @file.fsync - end - - def self.highest_log(directory_name, file_service) - highest = 0 - log_file_names(directory_name, file_service).each {|file_name| - match = /^(\d{#{FILE_COUNTER_SIZE}})/.match(file_name) - n = match[1].to_i - if n > highest - highest = n - end - } - highest - end - end - - class DefaultLogFactory #:nodoc: - def create_log(directory_name) - CommandLog.new(directory_name, FileService.new) - end - end - - class Logger #:nodoc: - - def initialize(directory_name, log_factory) - @directory_name = directory_name - @log_factory = log_factory - @log = nil - @pending_tick = nil - ensure_directory_exists - end - - def ensure_directory_exists - if ! File.exist?(@directory_name) - Dir.mkdir(@directory_name) - end - end - - def reset - close - delete_log_files - end - - def store(command) - if command.kind_of?(Madeleine::Clock::Tick) - @pending_tick = command - else - if @pending_tick - internal_store(@pending_tick) - @pending_tick = nil - end - internal_store(command) - end - end - - def internal_store(command) - if @log.nil? - open_new_log - end - @log.store(command) - end - - def close - return if @log.nil? - @log.close - @log = nil - end - - private - - def delete_log_files - Dir.glob(@directory_name + File::SEPARATOR + "*.command_log").each {|name| - name.untaint - File.delete(name) - } - end - - def open_new_log - @log = @log_factory.create_log(@directory_name) - end - end - - class SnapshotFile < NumberedFile #:nodoc: - - def self.highest_id(directory_name) - return 0 unless File.exist?(directory_name) - suffix = "snapshot" - highest = 0 - Dir.foreach(directory_name) {|file_name| - match = /^(\d{#{FILE_COUNTER_SIZE}}\.#{suffix}$)/.match(file_name) - next unless match - n = match[1].to_i - if n > highest - highest = n - end - } - highest - end - - def self.next(directory_name) - new(directory_name, highest_id(directory_name) + 1) - end - - def initialize(directory_name, id) - super(directory_name, "snapshot", id) - end - end - - class Snapshotter #:nodoc: - - def initialize(directory_name, marshaller) - @directory_name, @marshaller = directory_name, marshaller - end - - def take(system) - numbered_file = SnapshotFile.next(@directory_name) - name = numbered_file.name - open(name + '.tmp', 'wb') {|snapshot| - @marshaller.dump(system, snapshot) - snapshot.flush - snapshot.fsync - } - File.rename(name + '.tmp', name) - end - end - - module Clock #:nodoc: - class Tick #:nodoc: - - def initialize(time) - @time = time - end - - def execute(system) - system.clock.forward_to(@time) - end - end - end -end - -SnapshotMadeleine = Madeleine::SnapshotMadeleine - diff --git a/vendor/madeleine-0.7.1/lib/madeleine/automatic.rb b/vendor/madeleine-0.7.1/lib/madeleine/automatic.rb deleted file mode 100755 index 447d5ec3..00000000 --- a/vendor/madeleine-0.7.1/lib/madeleine/automatic.rb +++ /dev/null @@ -1,418 +0,0 @@ -require 'yaml' -require 'madeleine/zmarshal' -require 'soap/marshal' - -module Madeleine - -# Automatic commands for Madeleine -# -# Author:: Stephen Sykes -# Copyright:: Copyright (C) 2003-2004 -# Version:: 0.41 -# -# This module provides a way of automatically generating command objects for madeleine to -# store. It works by making a proxy object for all objects of any classes in which it is included. -# Method calls to these objects are intercepted, and stored as a command before being -# passed on to the real receiver. The command objects remember which object the command was -# destined for by using a pair of internal ids that are contained in each of the proxy objects. -# -# There is also a mechanism for specifying which methods not to intercept calls to by using -# automatic_read_only, and its opposite automatic_read_write. -# -# Should you require it, the snapshots can be stored as yaml, and can be compressed. Just pass -# the marshaller you want to use as the second argument to AutomaticSnapshotMadeleine.new. -# If the passed marshaller did not successfully deserialize the latest snapshot, the system -# will try to automatically detect and read either Marshal, YAML, SOAP, or their corresponding -# compressed versions. -# -# This module is designed to work correctly in the case there are multiple madeleine systems in use by -# a single program, and is also safe to use with threads. -# -# Usage: -# -# require 'madeleine' -# require 'madeleine/automatic' -# -# class A -# include Madeleine::Automatic::Interceptor -# attr_reader :foo -# automatic_read_only :foo -# def initialize(param1, ...) -# ... -# end -# def some_method(paramA, ...) -# ... -# end -# automatic_read_only -# def bigfoo -# foo.upcase -# end -# end -# -# mad = AutomaticSnapshotMadeleine.new("storage_directory") { A.new(param1, ...) } -# -# mad.system.some_method(paramA, ...) # logged as a command by madeleine -# print mad.foo # not logged -# print mad.bigfoo # not logged -# mad.take_snapshot -# - - module Automatic -# -# This module should be included (at the top) in any classes that are to be persisted. -# It will intercept method calls and make sure they are converted into commands that are logged by Madeleine. -# It does this by returning a Prox object that is a proxy for the real object. -# -# It also handles automatic_read_only and automatic_read_write, allowing user specification of which methods -# should be made into commands -# - module Interceptor -# -# When included, redefine new so that we can return a Prox object instead, and define methods to handle -# keeping track of which methods are read only -# - def self.included(klass) - class < "") - x - end - - end - -# -# The AutomaticSnapshotMadeleine class contains an instance of the persister -# (default is SnapshotMadeleine) and provides additional automatic functionality. -# -# The class is instantiated the same way as SnapshotMadeleine: -# madeleine_sys = AutomaticSnapshotMadeleine.new("storage_directory") { A.new(param1, ...) } -# The second initialisation parameter is the persister. Supported persisters are: -# -# * Marshal (default) -# * YAML -# * SOAP::Marshal -# * Madeleine::ZMarshal.new(Marshal) -# * Madeleine::ZMarshal.new(YAML) -# * Madeleine::ZMarshal.new(SOAP::Marshal) -# -# The class keeps a record of all the systems that currently exist. -# Each instance of the class keeps a record of Prox objects in that system by internal id (myid). -# -# We also add functionality to take_snapshot in order to set things up so that the custom Prox object -# marshalling will work correctly. -# - class AutomaticSnapshotMadeleine - attr_accessor :marshaller - attr_reader :list, :sysid - - def initialize(directory_name, marshaller=Marshal, persister=SnapshotMadeleine, &new_system_block) - @sysid ||= Time.now.to_f.to_s + Thread.current.object_id.to_s # Gererate a new sysid - @myid_count = 0 - @list = {} - Thread.current[:system] = self # during system startup system should not create commands - Thread.critical = true - @@systems ||= {} # holds systems by sysid - @@systems[@sysid] = self - Thread.critical = false - @marshaller = marshaller # until attrb - begin - @persister = persister.new(directory_name, Automatic_marshaller, &new_system_block) - @list.delete_if {|k,v| # set all the prox objects that now exist to have the right sysid - begin - ObjectSpace._id2ref(v).sysid = @sysid - false - rescue RangeError - true # Id was to a GC'd object, delete it - end - } - ensure - Thread.current[:system] = false - end - end -# -# Add a proxy object to the list, return the myid for that object -# - def add(proxo) - @list[@myid_count += 1] = proxo.object_id - @myid_count - end -# -# Restore a marshalled proxy object to list - myid_count is increased as required. -# If the object already exists in the system then the existing object must be used. -# - def restore(proxo) - if (@list[proxo.myid]) - proxo = myid2ref(proxo.myid) - else - @list[proxo.myid] = proxo.object_id - @myid_count = proxo.myid if (@myid_count < proxo.myid) - end - proxo - end -# -# Returns a reference to the object indicated by the internal id supplied. -# - def myid2ref(myid) - raise "Internal id #{myid} not found" unless objid = @list[myid] - ObjectSpace._id2ref(objid) - end -# -# Take a snapshot of the system. -# - def take_snapshot - begin - Thread.current[:system] = self - Thread.current[:snapshot_memory] = {} - @persister.take_snapshot - ensure - Thread.current[:snapshot_memory] = nil - Thread.current[:system] = false - end - end -# -# Returns the hash containing the systems. -# - def AutomaticSnapshotMadeleine.systems - @@systems - end -# -# Close method changes the sysid for Prox objects so they can't be mistaken for real ones in a new -# system before GC gets them -# - def close - begin - @list.each_key {|k| myid2ref(k).sysid = nil} - rescue RangeError - # do nothing - end - @persister.close - end - -# -# Pass on any other calls to the persister -# - def method_missing(symbol, *args, &block) - @persister.send(symbol, *args, &block) - end - end - - - module Deserialize #:nodoc: -# -# Detect format of an io stream. Leave it rewound. -# - def Deserialize.detect(io) - c = io.getc - c1 = io.getc - io.rewind - if (c == Marshal::MAJOR_VERSION && c1 <= Marshal::MINOR_VERSION) - Marshal - elsif (c == 31 && c1 == 139) # gzip magic numbers - ZMarshal - else - while (s = io.gets) - break if (s !~ /^\s*$/) # ignore blank lines - end - io.rewind - if (s && s =~ /^\s*<\?[xX][mM][lL]/) # " e - io.rewind - detected_marshaller = detect(io) - if (detected_marshaller == ZMarshal) - zio = Zlib::GzipReader.new(io) - detected_zmarshaller = detect(zio) - zio.finish - io.rewind - if (detected_zmarshaller) - ZMarshal.new(detected_zmarshaller).load(io) - else - raise e - end - elsif (detected_marshaller) - detected_marshaller.load(io) - else - raise e - end - end - end - end - - end -end - -AutomaticSnapshotMadeleine = Madeleine::Automatic::AutomaticSnapshotMadeleine diff --git a/vendor/madeleine-0.7.1/lib/madeleine/clock.rb b/vendor/madeleine-0.7.1/lib/madeleine/clock.rb deleted file mode 100755 index 012c6f27..00000000 --- a/vendor/madeleine-0.7.1/lib/madeleine/clock.rb +++ /dev/null @@ -1,94 +0,0 @@ -# -# Copyright(c) Anders Bengtsson 2003 -# - -require 'madeleine' - -module Madeleine - module Clock - - # Deprecated. Use SnapshotMadeleine instead. - class ClockedSnapshotMadeleine < ::Madeleine::SnapshotMadeleine # :nodoc: - end - - # Let your system extend this module if you need to access the - # machine time. Used together with a TimeActor that keeps - # the clock current. - module ClockedSystem - - # Returns this system's Clock. - def clock - unless defined? @clock - @clock = Clock.new - end - @clock - end - end - - # Sends clock ticks to update a ClockedSystem, so that time can be - # dealt with in a deterministic way. - class TimeActor - - # Create and launch a new TimeActor - # - # * madeleine - The SnapshotMadeleine instance to work on. - # * delay - Delay between ticks in seconds (Optional). - def self.launch(madeleine, delay=0.1) - result = new(madeleine, delay) - result - end - - # Stops the TimeActor. - def destroy - @is_destroyed = true - @thread.wakeup - @thread.join - end - - private_class_method :new - - private - - def initialize(madeleine, delay) #:nodoc: - @madeleine = madeleine - @is_destroyed = false - send_tick - @thread = Thread.new { - until @is_destroyed - sleep(delay) - send_tick - end - } - end - - def send_tick - @madeleine.execute_command(Tick.new(Time.now)) - end - end - - # Keeps track of time in a ClockedSystem. - class Clock - # Returns the system's time as a Ruby Time. - attr_reader :time - - def initialize - @time = Time.at(0) - end - - def forward_to(newTime) - @time = newTime - end - end - - # - # Internal classes below - # - - # Deprecated. Merged into default implementation. - class TimeOptimizingLogger < ::Madeleine::Logger # :nodoc: - end - - end -end - -ClockedSnapshotMadeleine = Madeleine::Clock::ClockedSnapshotMadeleine diff --git a/vendor/madeleine-0.7.1/lib/madeleine/files.rb b/vendor/madeleine-0.7.1/lib/madeleine/files.rb deleted file mode 100755 index 4276d8d5..00000000 --- a/vendor/madeleine-0.7.1/lib/madeleine/files.rb +++ /dev/null @@ -1,19 +0,0 @@ -# -# Wrapper for Ruby's file services, replaced during testing -# so we can run tests without touching a real filesystem. -# - -class FileService - - def open(*args) - super(*args) - end - - def exist?(name) - File.exist?(name) - end - - def dir_entries(name) - Dir.entries(name) - end -end diff --git a/vendor/madeleine-0.7.1/lib/madeleine/zmarshal.rb b/vendor/madeleine-0.7.1/lib/madeleine/zmarshal.rb deleted file mode 100755 index b01210ec..00000000 --- a/vendor/madeleine-0.7.1/lib/madeleine/zmarshal.rb +++ /dev/null @@ -1,60 +0,0 @@ -# -# Author:: Anders Bengtsson -# Copyright:: Copyright (c) 2004 -# - -require 'zlib' - -module Madeleine - # - # Snapshot marshaller for compressed snapshots. - # - # Compresses the snapshots created by another marshaller. Uses either - # Marshal (the default) or another supplied marshaller. - # - # Uses zlib to do on-the-fly compression/decompression. - # - # ZMarshal works with Ruby's own Marshal and YAML, but not with SOAP - # marshalling. - # - # Usage: - # - # require 'madeleine' - # require 'madeleine/zmarshal' - # - # marshaller = Madeleine::ZMarshal.new(YAML) - # madeleine = SnapshotMadeleine.new("my_example_storage", marshaller) { - # SomeExampleApplication.new() - # } - # - class ZMarshal - - def initialize(marshaller=Marshal) - @marshaller = marshaller - end - - def load(stream) - zstream = Zlib::GzipReader.new(stream) - begin - # Buffer into a string first, since GzipReader can't handle - # Marshal's 0-sized reads and SOAP can't handle streams at all. - # In a bright future we can revert to reading directly from the - # stream again. - buffer = zstream.read - return @marshaller.load(buffer) - ensure - zstream.finish - end - end - - def dump(system, stream) - zstream = Zlib::GzipWriter.new(stream) - begin - @marshaller.dump(system, zstream) - ensure - zstream.finish - end - nil - end - end -end diff --git a/vendor/madeleine-0.7.1/madeleine.gemspec b/vendor/madeleine-0.7.1/madeleine.gemspec deleted file mode 100755 index cd5d38f8..00000000 --- a/vendor/madeleine-0.7.1/madeleine.gemspec +++ /dev/null @@ -1,23 +0,0 @@ - -require 'rubygems' - -spec = Gem::Specification.new do |s| - s.name = 'madeleine' - s.version = '0.7.1' - s.platform = Gem::Platform::RUBY - s.required_ruby_version = ">= 1.8.1" - s.summary = "Madeleine is a Ruby implementation of Object Prevalence" - s.require_path = 'lib' - s.autorequire = 'madeleine' - s.author = "Anders Bengtsson" - s.email = "ndrsbngtssn@yahoo.se" - s.homepage = "http://madeleine.sourceforge.net" - s.files = Dir.glob("lib/**/*.rb") - s.files += Dir.glob("samples/**/*.rb") - s.files += Dir.glob("contrib/**/*.rb") - s.files += ['README', 'NEWS', 'COPYING'] -end - -if $0 == __FILE__ - Gem::Builder.new(spec).build -end diff --git a/vendor/madeleine-0.7.1/samples/.cvsignore b/vendor/madeleine-0.7.1/samples/.cvsignore deleted file mode 100755 index 5ca652ac..00000000 --- a/vendor/madeleine-0.7.1/samples/.cvsignore +++ /dev/null @@ -1,3 +0,0 @@ -dictionary-base -painter-demo -clock-demo diff --git a/vendor/madeleine-0.7.1/samples/clock_click.rb b/vendor/madeleine-0.7.1/samples/clock_click.rb deleted file mode 100755 index c11d16a3..00000000 --- a/vendor/madeleine-0.7.1/samples/clock_click.rb +++ /dev/null @@ -1,73 +0,0 @@ -# -# Simple example of using time with Madeleine. -# - -$LOAD_PATH.unshift(".." + File::SEPARATOR + "lib") - -require 'madeleine/clock' -require 'tk' - -# The Clicker keeps track of when it was last clicked. -# -# To access the time it extends ClockedSystem, which provides -# it with the 'clock' attribute. -# -class Clicker - include Madeleine::Clock::ClockedSystem - - def initialize - @last_clicked = nil - end - - def click - @last_clicked = clock.time - end - - def last_clicked - return '-' if @last_clicked.nil? - @last_clicked.to_s - end -end - -# A command to update the Clicker with. -# -class Click - def execute(system) - system.click - end -end - -# Launch a ClockedSnapshotMadeleine. -# -# ClockedSnapshotMadeleine works like the regular SnapshotMadeleine, but -# optimizes away redundant commands from TimeActor. -# -madeleine = ClockedSnapshotMadeleine.new("clock-demo") { Clicker.new } - -# Launch the TimeActor. -# -# This provides time commands, without which the system's time would stand still. -# -Madeleine::Clock::TimeActor.launch(madeleine) - -clicker = madeleine.system - -# The GUI - -root = TkRoot.new() { title "Madeleine Clock Example" } -label = TkLabel.new(root) { - text "Last clicked " + clicker.last_clicked - width 40 - pack -} -button = TkButton.new(root) { - text 'Click' - command proc { - madeleine.execute_command(Click.new) - label.text("Last clicked " + clicker.last_clicked) - } - pack -} - -Tk.mainloop - diff --git a/vendor/madeleine-0.7.1/samples/dictionary_client.rb b/vendor/madeleine-0.7.1/samples/dictionary_client.rb deleted file mode 100755 index 596eab5f..00000000 --- a/vendor/madeleine-0.7.1/samples/dictionary_client.rb +++ /dev/null @@ -1,23 +0,0 @@ -# -# Dictionary client -# -# See dictionary_server.rb for details -# - -require 'drb' - -DRb.start_service -dictionary = DRbObject.new(nil, "druby://localhost:1234") - -if ARGV.length == 1 - puts dictionary.lookup(ARGV[0]) -elsif ARGV.length == 2 - dictionary.add(ARGV[0], ARGV[1]) - puts "Stored" -else - puts "Usage: dictionary_client []" -end - - - - diff --git a/vendor/madeleine-0.7.1/samples/dictionary_server.rb b/vendor/madeleine-0.7.1/samples/dictionary_server.rb deleted file mode 100755 index 76c8e925..00000000 --- a/vendor/madeleine-0.7.1/samples/dictionary_server.rb +++ /dev/null @@ -1,94 +0,0 @@ -# -# A dictionary server using Distributed Ruby (DRb). -# -# All modifications to the dictionary are done as commands, -# while read-only queries (i.e 'lookup') are done directly. -# -# First launch this server in the background, then use -# dictionary_client.rb to look up and add items to the -# dictionary. -# You can kill the server at any time. The contents of the -# dictionary will still be there when you restart it. -# -# DRb is available at http://raa.ruby-lang.org/list.rhtml?name=druby -# - -$LOAD_PATH.unshift(".." + File::SEPARATOR + "lib") -require 'madeleine' - -require 'drb' - - -class Dictionary - def initialize - @data = {} - end - - def add(key, value) - @data[key] = value - end - - def lookup(key) - @data[key] - end -end - - -class Addition - def initialize(key, value) - @key, @value = key, value - end - - def execute(system) - system.add(@key, @value) - end -end - - -class Lookup - def initialize(key) - @key = key - end - - def execute(system) - system.lookup(@key) - end -end - - -class DictionaryServer - - def initialize(madeleine) - @madeleine = madeleine - @dictionary = madeleine.system - end - - def add(key, value) - # When adding a new key-value pair we modify the system, so - # this operation has to be done through a command. - @madeleine.execute_command(Addition.new(key, value)) - end - - def lookup(key) - # A lookup is a read-only operation, so we can do it as a non-logged - # query. If we weren't worried about concurrency problems we could - # have just called @dictionary.lookup(key) directly instead. - @madeleine.execute_query(Lookup.new(key)) - end -end - - -madeleine = SnapshotMadeleine.new("dictionary-base") { Dictionary.new } - -Thread.new(madeleine) { - puts "Taking snapshot every 30 seconds." - while true - sleep(30) - madeleine.take_snapshot - end -} - -DRb.start_service("druby://localhost:1234", - DictionaryServer.new(madeleine)) -DRb.thread.join - diff --git a/vendor/madeleine-0.7.1/samples/painter.rb b/vendor/madeleine-0.7.1/samples/painter.rb deleted file mode 100755 index b9d21658..00000000 --- a/vendor/madeleine-0.7.1/samples/painter.rb +++ /dev/null @@ -1,60 +0,0 @@ -# -# Simple drawing program to show Madeleine's logging feature. -# -# When you restart the program, your old artwork is still there. -# -# (Note: The GUI components used here aren't marshal-able, -# so in a real app you would have to do custom marshaling for -# the Painter class to get working snapshots. Then again, in a real -# app you wouldn't use the GUI components to hold the app's data, -# would you?) -# - -$LOAD_PATH.unshift(".." + File::SEPARATOR + "lib") - -require 'madeleine' - -require 'tkclass' - -class Painter - - def initialize(canvas) - @canvas = canvas - end - - def draw(x, y) - line = Line.new(@canvas, x, y, x + 1, y + 1) - line.fill('black') - end -end - -class PaintCommand - - def initialize(x, y) - @x, @y = x, y - end - - def execute(system) - system.draw(@x, @y) - end -end - -root = TkRoot.new() { title "Madeleine Painter" } -canvas = Canvas.new(root) -canvas.pack - -$madeleine = Madeleine::SnapshotMadeleine.new("painter-demo") { Painter.new(canvas) } - -canvas.bind("1", - proc {|x, y| - $madeleine.execute_command(PaintCommand.new(x, y)) - }, - "%x %y") -canvas.bind("B1-Motion", - proc {|x, y| - $madeleine.execute_command(PaintCommand.new(x, y)) - }, - "%x %y") - -Tk.mainloop - diff --git a/vendor/madeleine-0.7.1/test/test.rb b/vendor/madeleine-0.7.1/test/test.rb deleted file mode 100755 index e252a53f..00000000 --- a/vendor/madeleine-0.7.1/test/test.rb +++ /dev/null @@ -1,320 +0,0 @@ -#!/usr/bin/env ruby -# - -$LOAD_PATH.unshift("lib") -$LOAD_PATH.unshift("test") - -require 'madeleine' -require 'test/unit' - - -class Append - def initialize(value) - @value = value - end - - def execute(system) - system << @value - end -end - - -module TestUtils - def delete_directory(directory_name) - return unless File.exists?(directory_name) - Dir.foreach(directory_name) do |file| - next if file == "." - next if file == ".." - assert(File.delete(directory_name + File::SEPARATOR + file) == 1, - "Unable to delete #{file}") - end - Dir.delete(directory_name) - end -end - - -class SnapshotMadeleineTest < Test::Unit::TestCase - include TestUtils - - def teardown - delete_directory(persistence_base) - end - - def persistence_base - "closing-test" - end - - def test_closing - madeleine = SnapshotMadeleine.new(persistence_base) { "hello" } - madeleine.close - assert_raises(RuntimeError) do - madeleine.execute_command(Append.new("world")) - end - end -end - -class NumberedFileTest < Test::Unit::TestCase - - def test_main - target = Madeleine::NumberedFile.new(File::SEPARATOR + "foo", "bar", 321) - assert_equal(File::SEPARATOR + "foo" + File::SEPARATOR + - "000000000000000000321.bar", - target.name) - end -end - - -class LoggerTest < Test::Unit::TestCase - include TestUtils - - def teardown - delete_directory("whoah") - end - - def test_creation - @log = Object.new - def @log.store(command) - unless defined? @commands - @commands = [] - end - @commands << command - end - def @log.commands - @commands - end - - log_factory = self - target = Madeleine::Logger.new("whoah", log_factory) - target.store(:foo) - assert(@log.commands.include?(:foo)) - end - - # Self-shunt - def create_log(directory_name) - @log - end -end - -class CommandVerificationTest < Test::Unit::TestCase - - def teardown - Dir.delete("foo") - end - - def test_broken_command - target = SnapshotMadeleine.new("foo") { :a_system } - assert_raises(Madeleine::InvalidCommandException) do - target.execute_command(:not_a_command) - end - end -end - - -class CustomMarshallerTest < Test::Unit::TestCase - include TestUtils - - def teardown - delete_directory(prevalence_base) - end - - def prevalence_base - "custom-marshaller-test" - end - - def madeleine_class - SnapshotMadeleine - end - - def test_changing_marshaller - @log = "" - marshaller = self - target = madeleine_class.new(prevalence_base, marshaller) { "hello world" } - target.take_snapshot - assert_equal("dump ", @log) - target = nil - - madeleine_class.new(prevalence_base, marshaller) { flunk() } - assert_equal("dump load ", @log) - end - - def load(io) - @log << "load " - assert_equal("dump data", io.read()) - end - - def dump(system, io) - @log << "dump " - assert_equal("hello world", system) - io.write("dump data") - end -end - - -class ErrorRaisingCommand - def execute(system) - raise "this is an exception from a command" - end -end - -class ErrorHandlingTest < Test::Unit::TestCase - include TestUtils - - def teardown - delete_directory(prevalence_base) - end - - def prevalence_base - "error-handling-base" - end - - def test_exception_in_command - madeleine = SnapshotMadeleine.new(prevalence_base) { "hello" } - assert_raises(RuntimeError) do - madeleine.execute_command(ErrorRaisingCommand.new) - end - madeleine.close - madeleine = SnapshotMadeleine.new(prevalence_base) { "hello" } - madeleine.close - end -end - -class QueryTest < Test::Unit::TestCase - include TestUtils - - def teardown - delete_directory(prevalence_base) - end - - def prevalence_base - "query-base" - end - - def test_querying - madeleine = SnapshotMadeleine.new(prevalence_base) { "hello" } - query = Object.new - def query.execute(system) - system.size - end - # 'query' is an un-marshallable singleton, so we implicitly test - # that querys aren't stored. - assert_equal(5, madeleine.execute_query(query)) - # TODO: assert that no logging was done - # TODO: assert that lock was held - end -end - - -class TimeOptimizingLoggerTest < Test::Unit::TestCase - include TestUtils - - def setup - @target = Madeleine::Logger.new("some_directory", self) - @log = [] - def @log.store(command) - self << command - end - end - - def teardown - delete_directory("some_directory") - end - - def test_optimizing_ticks - assert_equal(0, @log.size) - @target.store(Madeleine::Clock::Tick.new(Time.at(3))) - assert_equal(0, @log.size) - @target.store(Madeleine::Clock::Tick.new(Time.at(22))) - assert_equal(0, @log.size) - @target.store(Addition.new(100)) - assert_kind_of(Madeleine::Clock::Tick, @log[0]) - assert_equal(22, value_of_tick(@log[0])) - assert_equal(100, @log[1].value) - assert_equal(2, @log.size) - end - - def value_of_tick(tick) - @clock = Object.new - def @clock.forward_to(time) - @value = time.to_i - end - def @clock.value - @value - end - tick.execute(self) - @clock.value - end - - # Self-shunt - def create_log(directory_name) - assert_equal("some_directory", directory_name) - @log - end - - # Self-shunt - def clock - @clock - end -end - - -class SharedLockQueryTest < Test::Unit::TestCase - include TestUtils - - def prevalence_base - "shared_lock_test" - end - - def teardown - delete_directory(prevalence_base) - end - - def test_query - madeleine = SnapshotMadeleine.new(prevalence_base) { "hello" } - lock = Object.new - madeleine.instance_eval { @lock = lock } # FIXME: The horror, the horror - - $shared = false - $was_shared = false - def lock.synchronize_shared(&block) - $shared = true - block.call - $shared = false - end - query = Object.new - def query.execute(system) - $was_shared = $shared - end - madeleine.execute_query(query) - assert($was_shared) - end -end - -suite = Test::Unit::TestSuite.new("Madeleine") - -suite << SnapshotMadeleineTest.suite -suite << NumberedFileTest.suite -require 'test_command_log' -suite << CommandLogTest.suite -suite << LoggerTest.suite -suite << CommandVerificationTest.suite -suite << CustomMarshallerTest.suite -suite << ErrorHandlingTest.suite -suite << QueryTest.suite -suite << TimeOptimizingLoggerTest.suite -suite << SharedLockQueryTest.suite -require 'test_executer' -suite << ExecuterTest.suite - -require 'test_clocked' -add_clocked_tests(suite) -require 'test_automatic' -add_automatic_tests(suite) -require 'test_persistence' -add_persistence_tests(suite) -require 'test_platforms' -add_platforms_tests(suite) -require 'test_zmarshal' -add_zmarshal_tests(suite) - -require 'test/unit/ui/console/testrunner' -Test::Unit::UI::Console::TestRunner.run(suite) diff --git a/vendor/madeleine-0.7.1/test/test_automatic.rb b/vendor/madeleine-0.7.1/test/test_automatic.rb deleted file mode 100755 index da0c1719..00000000 --- a/vendor/madeleine-0.7.1/test/test_automatic.rb +++ /dev/null @@ -1,559 +0,0 @@ -#!/usr/local/bin/ruby -w -# -# Copyright(c) 2003-2004 Stephen Sykes -# Copyright(c) 2003-2004 Anders Bengtsson -# - -$LOAD_PATH.unshift("lib") - -require 'madeleine' -require 'madeleine/automatic' -require 'test/unit' -#require 'contrib/batched.rb' # uncomment if testing batched - -class A - include Madeleine::Automatic::Interceptor - attr_accessor :z,:k - def initialize - @k=1 - end -end - -class B - include Madeleine::Automatic::Interceptor - attr_accessor :yy, :s - def initialize(a) - @yy = C.new(a) - end -end - -class C - include Madeleine::Automatic::Interceptor - attr_accessor :x, :a - def initialize(x) - @x = x - @a ||= D.new - end -end - -# direct changes in this class are not saved, except at snapshot -class D - attr_accessor :w -end - -class F - include Madeleine::Automatic::Interceptor - attr_accessor :z,:a - def plus1 - @z += 1 - end -end - -class G - include Madeleine::Automatic::Interceptor - attr_accessor :yy,:a - def initialize - @yy = H.new - end -end - -class H - include Madeleine::Automatic::Interceptor - attr_accessor :w - def minus1 - @w -= 1 - end -end - -class I - include Madeleine::Automatic::Interceptor - def initialize - @x = J.new - end - def testyield - r = false - @x.yielder {|c| r = true if c == 1} - r - end -end - -class J - include Madeleine::Automatic::Interceptor - def yielder - yield 1 - end -end - -class K - include Madeleine::Automatic::Interceptor - attr_accessor :k - def initialize - @k=1 - end - def seven - @k=7 - end - def fourteen - @k=14 - end - automatic_read_only :fourteen - automatic_read_only - def twentyone - @k=21 - end -end - -class L - include Madeleine::Automatic::Interceptor - attr_reader :x - def initialize - @x = M.new(self) - end -end - -class M - include Madeleine::Automatic::Interceptor - attr_reader :yy - def initialize(yy) - @yy = yy - end -end - -class AutoTest < Test::Unit::TestCase - - def persister - SnapshotMadeleine - end - - def delete_directory(directory_name) - return unless File.exist?(directory_name) - Dir.foreach(directory_name) do |file| - next if file == "." - next if file == ".." - assert(File.delete(directory_name + File::SEPARATOR + file) == 1, - "Unable to delete #{file}") - end - Dir.delete(directory_name) - end - - def create_new_system(klass, dir, *arg) - delete_directory(dir) - Thread.critical = true - @system_bases << dir - Thread.critical = false - make_system(dir) { klass.new(*arg) } - end - - def make_system(dir, marshaller=Marshal, &block) - AutomaticSnapshotMadeleine.new(dir, marshaller, persister, &block) - end - - def prevalence_base - "AutoPrevalenceTestBase" + self.class.to_s - end - - def setup - @system_bases = [] - end - - def teardown - @system_bases.each {|dir| - delete_directory(dir) - } - end - - def simpletest(n) - pb = prevalence_base + n.to_s - mad_a = create_new_system(A, pb) - mad_a.close - mad_a1 = make_system(pb) { A.new } - assert_equal(1, mad_a1.system.k, "No commands or snapshot") - mad_a1.system.z = 0 - mad_a1.system.z += 1 - assert_equal(1, mad_a1.system.z, "Object changes") - mad_a1.system.z -= 10 - assert_equal(-9, mad_a1.system.z, "Object changes") - mad_a1.close - mad_a2 = make_system(pb) { A.new } - assert_equal(-9, mad_a2.system.z, "Commands but no snapshot") - mad_a2.take_snapshot - mad_a2.close - mad_a3 = make_system(pb) { A.new } - assert_equal(-9, mad_a3.system.z, "Snapshot but no commands") - mad_a3.system.z -= 6 - mad_a3.system.z -= 3 - mad_a3.close - mad_a4 = make_system(pb) { A.new } - assert_equal(-18, mad_a4.system.z, "Snapshot and commands") - mad_a4.close - end -end - -# Basic test, and that system works in SAFE level 1 -class BasicTest < AutoTest - def test_main - simpletest(1) - end - - def test_main_in_safe_level_one - thread = Thread.new { - $SAFE = 1 - test_main - } - thread.join - end -end - -class ObjectOutsideTest < AutoTest - def test_main - mad = create_new_system(A, prevalence_base) - assert_raises(RuntimeError) { - mad.system.z = A.new # app object created outside system - } - mad.close - end -end - -# Passing a block when it would generate a command is not allowed because blocks cannot -# be serialised. However, block passing/yielding inside the application is ok. -class BlockGivenTest < AutoTest - def test_main - mad = create_new_system(J, prevalence_base) - assert_raises(RuntimeError) { - mad.system.yielder {|a| a} - } - mad.close - mad2 = create_new_system(I, prevalence_base+"2") - assert(mad2.system.testyield, "Internal block passing") - mad2.close - end -end - -class NonPersistedObjectTest < AutoTest - def test_main - mad_b = create_new_system(B, prevalence_base, 0) - mad_b.system.yy.x -= 1 - assert_equal(-1, mad_b.system.yy.x, "Direct change of object inside main object") - - mad_b.system.yy.a.w ||= "hello" # not saved - mad_b.system.yy.a.w += " again" # not saved - - assert_equal("hello again", mad_b.system.yy.a.w, "Non persisted object before close") - - mad_b.close - mad_b2 = make_system(prevalence_base) { B.new(0) } - assert_equal(nil, mad_b2.system.yy.a.w, "Non persisted object after restart, no snapshot") - mad_b2.system.yy.a.w ||= "hello" # not saved - mad_b2.system.yy.a.w += " again" # not saved - mad_b2.take_snapshot # NOW saved - mad_b2.system.yy.a.w += " again" # not saved - assert_equal("hello again again", mad_b2.system.yy.a.w, "Non persisted object after take_snapshot and 1 change") - - mad_b2.close - mad_b3 = make_system(prevalence_base) { B.new(0) } - assert_equal("hello again", mad_b3.system.yy.a.w, "Non persisted object after restore (back to snapshotted state)") - mad_b3.close - end -end - -class RefInExternalObjTest < AutoTest - def test_main - mad_c = create_new_system(B, prevalence_base, 0) - x = D.new - x.w = mad_c.system.yy - mad_c.system.s = x # pass in an external object that contains a ref to obj in ths system - - mad_c.system.s.w.x += 1 # Increment counter via external obj - assert_equal(1, mad_c.system.yy.x, "Change via external object") - mad_c.system.yy.x += 1 # Increment counter directly - assert_equal(2, mad_c.system.s.w.x, "Direct change") - mad_c.close - - mad_c2 = make_system(prevalence_base) { B.new(0) } - assert_equal(2, mad_c2.system.s.w.x, "Value via external object after commands/restore") - assert_equal(2, mad_c2.system.yy.x, "Direct value after restore") - mad_c2.take_snapshot - mad_c2.close - - mad_c3 = make_system(prevalence_base) { B.new(0) } - assert_equal(2, mad_c3.system.s.w.x, "Value via external object after snapshot/restore") - assert_equal(2, mad_c3.system.yy.x, "Direct value after snapshot/restore") - - mad_c3.system.s.w.x += 1 # Increment counter via external obj - mad_c3.system.yy.x += 1 # Increment counter directly - mad_c3.close - - mad_c4 = make_system(prevalence_base) { B.new(0) } - assert_equal(4, mad_c4.system.s.w.x, "Value via external object after snapshot+commands/restore") - assert_equal(4, mad_c4.system.yy.x, "Direct value after snapshot+commands/restore") - mad_c4.close - end -end - -class BasicThreadSafetyTest < AutoTest - def test_main - x = Thread.new { - simpletest(1) - } - y = Thread.new { - simpletest(2) - } - x.join - y.join - end -end - -class ThreadConfidenceTest < AutoTest - def test_main - mad_d = create_new_system(F, prevalence_base) - mad_d.system.z = 0 - mad_e = create_new_system(G, prevalence_base+"2") - mad_e.system.yy.w = 0 - - x = [] - 25.times {|n| - x[n] = Thread.new { - 5.times { - sleep(rand/10) - mad_d.system.plus1 - mad_e.system.yy.minus1 - } - } - } - 25.times {|n| - x[n].join - } - assert_equal(125, mad_d.system.z, "125 commands") - assert_equal(-125, mad_e.system.yy.w, "125 commands") - - mad_e.close - mad_e2 = make_system(prevalence_base+"2") { G.new } - - 25.times {|n| - x[n] = Thread.new { - 5.times { - sleep(rand/10) - mad_d.system.plus1 - mad_e2.system.yy.minus1 - } - } - } - 25.times {|n| - x[n].join - } - assert_equal(250, mad_d.system.z, "restore/125 commands") - assert_equal(-250, mad_e2.system.yy.w, "restore/125 commands") - mad_d.close - mad_e2.close - end -end - -class InvalidMethodTest < AutoTest - def test_main - mad_f = create_new_system(A, prevalence_base) - mad_f.system.z = -1 - assert_raises(NoMethodError) { - mad_f.system.not_a_method - } - assert_equal(-1, mad_f.system.z, "System functions after NoMethodError") - mad_f.close - end -end - -class CircularReferenceTest < AutoTest - def test_main - mad_g = create_new_system(G, prevalence_base) - mad_g.system.yy.w = mad_g.system - mad_g.close - mad_g2 = make_system(prevalence_base) { G.new } - assert(mad_g2.system == mad_g2.system.yy.w.yy.w.yy.w, "Circular reference after command/restore") - mad_g2.take_snapshot - mad_g2.close - mad_g3 = make_system(prevalence_base) { G.new } - assert(mad_g3.system == mad_g3.system.yy.w.yy.w.yy.w, "Circular reference after snapshot/restore") - mad_g3.system.yy.w.yy.w.yy.w.a = 1 - assert_equal(1, mad_g3.system.a, "Circular reference change") - mad_g3.close - mad_g4 = make_system(prevalence_base) { G.new } - assert_equal(1, mad_g4.system.yy.w.yy.w.yy.w.a, "Circular reference after snapshot+commands/restore") - mad_g4.close -# The following tests would fail, cannot pass self (from class L to class M during init) -# self is the proxied object itself, not the Prox object it needs to be - mad_l = create_new_system(L, prevalence_base) -# assert_equal(mad_l.system, mad_l.system.x.yy, "Circular ref before snapshot/restore, passed self") - mad_l.take_snapshot - mad_l.close - mad_l = make_system(prevalence_base) { L.new } -# assert_equal(mad_l.system, mad_l.system.x.yy, "Circular ref after snapshot/restore, passed self") - mad_l.close - end -end - -class AutomaticCustomMarshallerTest < AutoTest - def test_main - custom_m(YAML) - custom_m(SOAP::Marshal) - custom_m(Madeleine::ZMarshal.new) - custom_m(Madeleine::ZMarshal.new(YAML)) - custom_m(Madeleine::ZMarshal.new(SOAP::Marshal)) - end - - def custom_m(marshaller) - dir = prevalence_base - delete_directory(dir) - @system_bases << dir - mad_h = make_system(dir) { G.new } - mad_h.system.yy.w = "abc" - mad_h.take_snapshot - mad_h.system.yy.w += "d" - assert_equal("abcd", mad_h.system.yy.w, "Custom marshalling after snapshot+commands with normal marshaller") - mad_h.close - mad_h = make_system(dir, marshaller) { G.new } - assert_equal("abcd", mad_h.system.yy.w, "Custom marshalling after snapshot+commands with normal marshaller, read with custom as marshaller") - mad_h.close - mad_h = make_system(dir) { G.new } - mad_h.marshaller = marshaller - mad_h.system.yy.w += "e" - assert_equal("abcde", mad_h.system.yy.w, "Custom marshalling after snapshot+commands+change marshaller+commands") - mad_h.take_snapshot - mad_h.close - if (marshaller == YAML) - File.open(dir + "/000000000000000000002.snapshot", "r") {|f| - assert_equal(f.gets, "--- !ruby/object:Madeleine::Automatic::Prox \n", "Custom marshalling marshaller change check") - } - end - mad_h = make_system(dir, marshaller) { G.new } - assert_equal("abcde", mad_h.system.yy.w, - "Custom marshalling after snapshot+commands+change marshaller+commands+snapshot+restore with normal marshaller") - mad_h.system.yy.w += "f" - mad_h.close - mad_h = make_system(dir) { G.new } - assert_equal("abcdef", mad_h.system.yy.w, "Custom marshalling snapshot custom+commands+restore normal") - mad_h.take_snapshot - mad_h.close - mad_h = make_system(dir, marshaller) { G.new } - assert_equal("abcdef", mad_h.system.yy.w, "Custom marshalling snapshot+restore custom") - mad_h.take_snapshot - mad_h.system.yy.w += "g" - mad_h.close - mad_h = make_system(dir, marshaller) { G.new } - assert_equal("abcdefg", mad_h.system.yy.w, "Custom marshalling after restore normal snapshot custom+commands+restore custom") - mad_h.system.yy.w = "abc" - mad_h.close - mad_h2 = make_system(dir, marshaller) { G.new } - assert_equal("abc", mad_h2.system.yy.w, "Custom marshalling after commands/restore") - mad_h2.take_snapshot - mad_h2.close - mad_h3 = make_system(dir, marshaller) { G.new } - assert_equal("abc", mad_h3.system.yy.w, "Custom marshalling after snapshot/restore") - mad_h3.system.yy.w += "d" - mad_h3.close - mad_h4 = make_system(dir, marshaller) { G.new } - assert_equal("abcd", mad_h4.system.yy.w, "Custom marshalling after snapshot+commands/restore") - mad_h4.close - mad_h = make_system(dir, marshaller) { G.new } - mad_h.system.yy.w = mad_h.system - mad_h.close - mad_h2 = make_system(dir, marshaller) { G.new } - assert_equal(mad_h2.system, mad_h2.system.yy.w, "Custom marshalling after commands/restore, circular ref") - mad_h2.take_snapshot - mad_h2.close - mad_h3 = make_system(dir, marshaller) { G.new } - assert_equal(mad_h3.system, mad_h3.system.yy.w, "Custom marshalling after snapshot/restore, circular ref") - mad_h3.system.yy.w = "sss" - mad_h3.system.yy.w = mad_h3.system - mad_h3.close - mad_h4 = make_system(dir, marshaller) { G.new } - assert_equal(mad_h4.system, mad_h4.system.yy.w, "Custom marshalling after snapshot+commands/restore, circular ref") - mad_h4.close - end -end - -# tests thread safety during system creation, particularly that different system ids are generated -class ThreadedStartupTest < AutoTest - def test_main - x,mad = [],[] - 20.times {|n| - x[n] = Thread.new { - sleep(rand/100) - mad[n] = create_new_system(F, prevalence_base+n.to_s) - mad[n].system.z = n - assert_equal(n, mad[n].system.z, "object change mad[#{n}].z") - mad[n].close - } - } - 20.times {|n| - x[n].join - mad_i = make_system(prevalence_base+n.to_s) { F.new } - assert_equal(n, mad_i.system.z, "restored mad[#{n}].z") - mad_i.close - } - end -end - -# tests restoring when objects get unreferenced and GC'd during restore -class FinalisedTest < AutoTest - def test_main - mad = create_new_system(B, prevalence_base, 0) - mad.system.yy = Array.new(200000) # make ruby run GC - mad.system.yy = Array.new(200000) # must be a better way, but running GC.start from inside - mad.system.yy = Array.new(50000) # class B didn't work for me - mad.close - mad2 = make_system(prevalence_base) { B.new(0) } - mad2.close - end -end - -class DontInterceptTest < AutoTest - def test_main - mad = create_new_system(K, prevalence_base) - mad.system.seven - assert_equal(7, mad.system.k, "Object changes") - mad.system.fourteen - assert_equal(14, mad.system.k, "Object changes, not intercepted") - mad.system.twentyone - assert_equal(21, mad.system.k, "Object changes, not intercepted") - mad.close - mad_1 = make_system(prevalence_base) { K.new } - assert_equal(7, mad_1.system.k, "Commands but no snapshot") - mad_1.take_snapshot - mad_1.close - mad_2 = make_system(prevalence_base) { K.new } - assert_equal(7, mad_2.system.k, "Snapshot but no commands") - mad_2.system.k -= 6 - mad_2.system.k -= 3 - mad_2.system.fourteen - mad_2.close - mad_3 = make_system(prevalence_base) { K.new } - assert_equal(-2, mad_3.system.k, "Snapshot and commands") - mad_3.close - end -end - -def add_automatic_tests(suite) - suite << BasicTest.suite - suite << ObjectOutsideTest.suite - suite << BlockGivenTest.suite - suite << NonPersistedObjectTest.suite - suite << RefInExternalObjTest.suite - suite << InvalidMethodTest.suite - suite << CircularReferenceTest.suite - suite << BasicThreadSafetyTest.suite - suite << FinalisedTest.suite - suite << DontInterceptTest.suite - suite << AutomaticCustomMarshallerTest.suite -end - -def add_slow_automatic_tests(suite) - suite << ThreadConfidenceTest.suite - suite << ThreadedStartupTest.suite -end - -if __FILE__ == $0 - slowsuite = Test::Unit::TestSuite.new("AutomaticMadeleine (including slow tests)") - add_automatic_tests(slowsuite) - add_slow_automatic_tests(slowsuite) - - require 'test/unit/ui/console/testrunner' - Test::Unit::UI::Console::TestRunner.run(slowsuite) -end diff --git a/vendor/madeleine-0.7.1/test/test_clocked.rb b/vendor/madeleine-0.7.1/test/test_clocked.rb deleted file mode 100755 index fcbd02d1..00000000 --- a/vendor/madeleine-0.7.1/test/test_clocked.rb +++ /dev/null @@ -1,94 +0,0 @@ - -require 'madeleine/clock' - -class ClockedAddingSystem - include Madeleine::Clock::ClockedSystem - - attr_reader :total - - def initialize - @total = 0 - end - - def add(value) - @total += value - @total - end -end - -class TimeTest < Test::Unit::TestCase - - def test_clock - target = Madeleine::Clock::Clock.new - assert_equal(0, target.time.to_i) - assert_equal(0, target.time.usec) - - t1 = Time.at(10000) - target.forward_to(t1) - assert_equal(t1, target.time) - t2 = Time.at(20000) - target.forward_to(t2) - assert_equal(t2, target.time) - - assert_nothing_raised() { - target.forward_to(t2) - } - end - - def test_time_actor - @forward_calls = 0 - @last_time = Time.at(0) - - target = Madeleine::Clock::TimeActor.launch(self, 0.01) - - # When launch() has returned it should have sent - # one synchronous clock-tick before it went to sleep - assert_equal(1, @forward_calls) - - sleep(0.1) - assert(@forward_calls > 1) - target.destroy - - @forward_calls = 0 - sleep(0.1) - assert_equal(0, @forward_calls) - end - - # Self-shunt - def execute_command(command) - mock_system = self - command.execute(mock_system) - end - - # Self-shunt (system) - def clock - self - end - - # Self-shunt (clock) - def forward_to(time) - if time < @last_time - raise "non-monotonous time" - end - @last_time = time - @forward_calls += 1 - end - - def test_clocked_system - target = Object.new - target.extend(Madeleine::Clock::ClockedSystem) - t1 = Time.at(10000) - target.clock.forward_to(t1) - assert_equal(t1, target.clock.time) - t2 = Time.at(20000) - target.clock.forward_to(t2) - assert_equal(t2, target.clock.time) - reloaded_target = Marshal.load(Marshal.dump(target)) - assert_equal(t2, reloaded_target.clock.time) - end -end - - -def add_clocked_tests(suite) - suite << TimeTest.suite -end diff --git a/vendor/madeleine-0.7.1/test/test_command_log.rb b/vendor/madeleine-0.7.1/test/test_command_log.rb deleted file mode 100755 index 9f0961ca..00000000 --- a/vendor/madeleine-0.7.1/test/test_command_log.rb +++ /dev/null @@ -1,110 +0,0 @@ - -unless $LOAD_PATH.include?("lib") - $LOAD_PATH.unshift("lib") -end -unless $LOAD_PATH.include?("test") - $LOAD_PATH.unshift("test") -end - -require 'madeleine' -require 'test/unit' -require 'stringio' - -class ExampleCommand - attr :value - - def initialize(value) - @value = value - end - - def execute(system) - system.add(@value) - end -end - -class CommandLogTest < Test::Unit::TestCase - - class MockFile < StringIO - def fsync - @was_fsynced = true - super - end - - attr :was_fsynced - end - - def test_file_opening - file_service = Object.new - def file_service.exist?(path) - [ - ["some", "path"].join(File::SEPARATOR), - ["some", "path", "000000000000000000001.command_log"].join(File::SEPARATOR), - ["some", "path", "000000000000000000002.command_log"].join(File::SEPARATOR), - ["some", "path", "000000000000000000003.command_log"].join(File::SEPARATOR), - ].include?(path) - end - def file_service.dir_entries(path, &block) - if path != ["some", "path"].join(File::SEPARATOR) - raise "wrong path" - end - [ - "000000000000000000001.command_log", - "000000000000000000003.command_log", - "000000000000000000002.command_log", - ] - end - def file_service.open(path, flags) - @was_open_called = true - if path != ["some", "path", "000000000000000000004.command_log"].join(File::SEPARATOR) - raise "wrong file id" - end - if flags != "wb" - raise "wrong flags" - end - MockFile.new - end - def file_service.was_open_called - @was_open_called - end - - target = Madeleine::CommandLog.new("some/path", file_service) - assert(file_service.was_open_called) - end - - def test_writing_command - file_service = Object.new - def file_service.exist?(path) - [ - ["some", "path"].join(File::SEPARATOR), - ].include?(path) - end - def file_service.dir_entries(path, &block) - if path != ["some", "path"].join(File::SEPARATOR) - raise "wrong path" - end - [] - end - def file_service.open(path, flags) - @file = MockFile.new - @file - end - def file_service.file - @file - end - def file_service.verify - unless @file.was_fsynced - raise "file wasn't fsynced" - end - end - command = ExampleCommand.new(1234) - - target = Madeleine::CommandLog.new("some/path", file_service) - target.store(command) - - file_service.verify - - file_service.file.rewind - assert_equal(Marshal.dump(command), file_service.file.read) - end -end - diff --git a/vendor/madeleine-0.7.1/test/test_executer.rb b/vendor/madeleine-0.7.1/test/test_executer.rb deleted file mode 100755 index 27d2f062..00000000 --- a/vendor/madeleine-0.7.1/test/test_executer.rb +++ /dev/null @@ -1,54 +0,0 @@ - -unless $LOAD_PATH.include?("lib") - $LOAD_PATH.unshift("lib") -end -unless $LOAD_PATH.include?("test") - $LOAD_PATH.unshift("test") -end - -require 'test/unit' -require 'madeleine' - -class ExecuterTest < Test::Unit::TestCase - - def test_executer - system = Object.new - command = self - executer = Madeleine::Executer.new(system) - @executed_with = nil - executer.execute(command) - assert_same(system, @executed_with) - end - - # Self-shunt - def execute(system) - @executed_with = system - end - - def test_execute_with_exception - system = Object.new - command = Object.new - def command.execute(system) - raise "this is an exception from a command" - end - executer = Madeleine::Executer.new(system) - assert_raises(RuntimeError) { - executer.execute(command) - } - end - - def test_exception_in_recovery - system = Object.new - command = Object.new - def command.execute(system) - raise "this is an exception from a command" - end - executer = Madeleine::Executer.new(system) - executer.recovery { - executer.execute(command) - } - assert_raises(RuntimeError) { - executer.execute(command) - } - end -end diff --git a/vendor/madeleine-0.7.1/test/test_persistence.rb b/vendor/madeleine-0.7.1/test/test_persistence.rb deleted file mode 100755 index c0e3a639..00000000 --- a/vendor/madeleine-0.7.1/test/test_persistence.rb +++ /dev/null @@ -1,169 +0,0 @@ -#!/usr/local/bin/ruby -w -# -# Copyright(c) 2003 Anders Bengtsson -# -# PersistenceTest is based on the unit tests from Prevayler, -# Copyright(c) 2001-2003 Klaus Wuestefeld. -# - -$LOAD_PATH.unshift("lib") - -require 'madeleine' -require 'test/unit' - -class AddingSystem - attr_reader :total - - def initialize - @total = 0 - end - - def add(value) - @total += value - @total - end -end - - -class Addition - - attr_reader :value - - def initialize(value) - @value = value - end - - def execute(system) - system.add(@value) - end -end - - -class PersistenceTest < Test::Unit::TestCase - - def setup - @madeleine = nil - end - - def teardown - delete_prevalence_files(prevalence_base) - Dir.delete(prevalence_base) - end - - def verify(expected_total) - assert_equal(expected_total, prevalence_system().total(), "Total") - end - - def prevalence_system - @madeleine.system - end - - def prevalence_base - "PrevalenceBase" - end - - def clear_prevalence_base - @madeleine.close unless @madeleine.nil? - delete_prevalence_files(prevalence_base()) - end - - def delete_prevalence_files(directory_name) - return unless File.exist?(directory_name) - Dir.foreach(directory_name) {|file_name| - next if file_name == '.' - next if file_name == '..' - file_name.untaint - assert(File.delete(directory_name + File::SEPARATOR + file_name) == 1, - "Unable to delete #{file_name}") - } - end - - def crash_recover - @madeleine.close unless @madeleine.nil? - @madeleine = create_madeleine() - end - - def create_madeleine - SnapshotMadeleine.new(prevalence_base()) { AddingSystem.new } - end - - def snapshot - @madeleine.take_snapshot - end - - def add(value, expected_total) - total = @madeleine.execute_command(Addition.new(value)) - assert_equal(expected_total, total, "Total") - end - - def verify_snapshots(expected_count) - count = 0 - Dir.foreach(prevalence_base) {|name| - if name =~ /\.snapshot$/ - count += 1 - end - } - assert_equal(expected_count, count, "snapshots") - end - - def test_main - clear_prevalence_base - - # There is nothing to recover at first. - # A new system will be created. - crash_recover - - crash_recover - add(40,40) - add(30,70) - verify(70) - - crash_recover - verify(70) - - add(20,90) - add(15,105) - verify_snapshots(0) - snapshot - verify_snapshots(1) - snapshot - verify_snapshots(2) - verify(105) - - crash_recover - snapshot - add(10,115) - snapshot - add(5,120) - add(4,124) - verify(124) - - crash_recover - add(3,127) - verify(127) - - verify_snapshots(4) - - clear_prevalence_base - snapshot - - crash_recover - add(10,137) - add(2,139) - crash_recover - verify(139) - end - - def test_main_in_safe_level_one - thread = Thread.new { - $SAFE = 1 - test_main - } - thread.join - end -end - - -def add_persistence_tests(suite) - suite << PersistenceTest.suite -end diff --git a/vendor/madeleine-0.7.1/test/test_platforms.rb b/vendor/madeleine-0.7.1/test/test_platforms.rb deleted file mode 100755 index b3ee03b1..00000000 --- a/vendor/madeleine-0.7.1/test/test_platforms.rb +++ /dev/null @@ -1,65 +0,0 @@ - -class AddCommand - def initialize(obj) - @obj = obj - end - - def execute(system) - system[@obj.myid] = @obj - end -end - -class Foo - attr_accessor :myid -end - - -# Checks for a strange marshalling or IO bug observed in the -# native win32-port of Ruby on WinXP. -# -# Test case provided by Steve Conover. - -class WierdWin32CorruptionTest < Test::Unit::TestCase - include TestUtils - - def teardown - (1..5).each {|i| - delete_directory("corruption_test#{i}") - } - end - - def doCorruptionTest(idstr, storagenumber) - m = SnapshotMadeleine.new("corruption_test" + storagenumber) { Hash.new() } - - f = Foo.new() - f.myid = idstr - - m.execute_command(AddCommand.new(f)) - m.close() - m = SnapshotMadeleine.new("corruption_test" + storagenumber) { Hash.new() } - end - - def testErrorOne - doCorruptionTest("123456789012345678901", "1") - end - - def testErrorTwo - doCorruptionTest("aaaaaaaaaaaaaaaaaaaaa", "2") - end - - def testNoErrorOne - doCorruptionTest("12345678901234567890", "3") - end - - def testNoErrorTwo - doCorruptionTest("1234567890123456789012", "4") - end - - def testWhiteSpace - doCorruptionTest("\n\r\t \r\n", "5") - end -end - -def add_platforms_tests(suite) - suite << WierdWin32CorruptionTest.suite -end diff --git a/vendor/madeleine-0.7.1/test/test_zmarshal.rb b/vendor/madeleine-0.7.1/test/test_zmarshal.rb deleted file mode 100755 index 80f46b90..00000000 --- a/vendor/madeleine-0.7.1/test/test_zmarshal.rb +++ /dev/null @@ -1,52 +0,0 @@ - -require 'madeleine/zmarshal' - -require 'stringio' -require 'yaml' - -class ZMarshalTest < Test::Unit::TestCase - - def test_full_circle_marshal - target = Madeleine::ZMarshal.new(Marshal) - object = ["foo", "bar"] - stream = StringIO.new - - target.dump(object, stream) - stream.rewind - result = target.load(stream) - - assert_equal(object, result) - end - - def test_full_circle_yaml - target = Madeleine::ZMarshal.new(YAML) - object = ["foo", "bar"] - stream = StringIO.new - - target.dump(object, stream) - stream.rewind - result = target.load(stream) - - assert_equal(object, result) - end - - def test_compression - target = Madeleine::ZMarshal.new(Marshal) - object = "x" * 1000 - - stream = StringIO.new - Marshal.dump(object, stream) - reference_size = stream.size - - stream = StringIO.new - target.dump(object, stream) - compressed_size = stream.size - - assert(compressed_size < reference_size) - end -end - - -def add_zmarshal_tests(suite) - suite << ZMarshalTest.suite -end From fa82bfdb9c5e3c8c26546983465764a096205cad Mon Sep 17 00:00:00 2001 From: Rick Okin Date: Wed, 10 Aug 2005 05:58:18 +0000 Subject: [PATCH 18/84] Without Madeleine, chunks can again use their object_id as a unique identifier, instead of an artificial one. This speeds up rendering somewhat, and eliminates the last mention of Madeleine from the codebase :) --- lib/chunks/chunk.rb | 13 +++---------- lib/wiki_content.rb | 29 ++++++++++++----------------- 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/lib/chunks/chunk.rb b/lib/chunks/chunk.rb index 9ba3cc04..ad0fb148 100644 --- a/lib/chunks/chunk.rb +++ b/lib/chunks/chunk.rb @@ -27,8 +27,8 @@ module Chunk # a regexp that matches all chunk_types masks def Abstract::mask_re(chunk_types) - tmp = chunk_types.map{|klass| klass.mask_string}.join("|") - Regexp.new("chunk([0-9a-f]+n\\d+)(#{tmp})chunk") + chunk_classes = chunk_types.map{|klass| klass.mask_string}.join("|") + /chunk(\d+)(#{chunk_classes})chunk/ end attr_reader :text, :unmask_text, :unmask_mode @@ -53,14 +53,7 @@ module Chunk # should contain only [a-z0-9] def mask - @mask ||="chunk#{@id}#{self.class.mask_string}chunk" - end - - # We should not use object_id because object_id is not guarantied - # to be unique when we restart the wiki (new object ids can equal old ones - # that were restored from madeleine storage) - def id - @id ||= "#{@content.page_id}n#{@content.chunk_id}" + @mask ||= "chunk#{self.object_id}#{self.class.mask_string}chunk" end def unmask diff --git a/lib/wiki_content.rb b/lib/wiki_content.rb index dcc89ef9..6fcc4a85 100644 --- a/lib/wiki_content.rb +++ b/lib/wiki_content.rb @@ -59,14 +59,14 @@ module ChunkManager def add_chunk(c) @chunks_by_type[c.class] << c - @chunks_by_id[c.id] = c + @chunks_by_id[c.object_id] = c @chunks << c @chunk_id += 1 end def delete_chunk(c) @chunks_by_type[c.class].delete(c) - @chunks_by_id.delete(c.id) + @chunks_by_id.delete(c.object_id) @chunks.delete(c) end @@ -82,18 +82,15 @@ module ChunkManager @chunks.select { |chunk| chunk.kind_of?(chunk_type) and chunk.rendered? } end - # for testing and WikiContentStub; we need a page_id even if we have no page - def page_id - 0 - end - end # A simplified version of WikiContent. Useful to avoid recursion problems in # WikiContent.new class WikiContentStub < String + attr_reader :options include ChunkManager + def initialize(content, options) super(content) @options = options @@ -167,7 +164,7 @@ class WikiContent < String @options[:engine].apply_to(copy) copy.inside_chunks(HIDE_CHUNKS) do |id| - @chunks_by_id[id].revert + @chunks_by_id[id.to_i].revert end end @@ -183,14 +180,16 @@ class WikiContent < String pre_render! @options[:engine].apply_to(self) # unmask in one go. $~[1] is the chunk id - gsub!(MASK_RE[ACTIVE_CHUNKS]){ - if chunk = @chunks_by_id[$~[1]] - chunk.unmask_text + gsub!(MASK_RE[ACTIVE_CHUNKS]) do + chunk = @chunks_by_id[$~[1].to_i] + if chunk.nil? # if we match a chunkmask that existed in the original content string # just keep it as it is - else $~[0] - end} + else + chunk.unmask_text + end + end self end @@ -198,9 +197,5 @@ class WikiContent < String @revision.page.name end - def page_id - @revision.page.id - end - end From a91bd946b334f9b1ca3f092422fa9dbc6ae512ea Mon Sep 17 00:00:00 2001 From: Rick Okin Date: Wed, 10 Aug 2005 05:59:42 +0000 Subject: [PATCH 19/84] Added db/*.db to svn:ignore From 503aa99c636e695e9a226b9c42dcb47abeb12650 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Wed, 10 Aug 2005 06:16:15 +0000 Subject: [PATCH 20/84] Set default port to 2500 --- script/server | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/script/server b/script/server index 487d1fd9..c6bdbb6f 100755 --- a/script/server +++ b/script/server @@ -4,9 +4,9 @@ require 'webrick' require 'optparse' OPTIONS = { - :port => 3000, + :port => 2500, :ip => "0.0.0.0", - :environment => "development", + :environment => "production", :server_root => File.expand_path(File.dirname(__FILE__) + "/../public/"), :server_type => WEBrick::SimpleServer } @@ -18,8 +18,8 @@ ARGV.options do |opts| opts.separator "" opts.on("-p", "--port=port", Integer, - "Runs Rails on the specified port.", - "Default: 3000") { |OPTIONS[:port]| } + "Runs Instiki on the specified port.", + "Default: 2500") { |OPTIONS[:port]| } opts.on("-b", "--binding=ip", String, "Binds Rails to the specified ip.", "Default: 0.0.0.0") { |OPTIONS[:ip]| } @@ -44,6 +44,6 @@ require 'webrick_server' OPTIONS['working_directory'] = File.expand_path(RAILS_ROOT) -puts "=> Rails application started on http://#{OPTIONS[:ip]}:#{OPTIONS[:port]}" -puts "=> Ctrl-C to shutdown server; call with --help for options" if OPTIONS[:server_type] == WEBrick::SimpleServer +puts "=> Instiki started on http://#{OPTIONS[:ip]}:#{OPTIONS[:port]}" +puts "=> Ctrl-C to shutdown; call with --help for options" if OPTIONS[:server_type] == WEBrick::SimpleServer DispatchServlet.dispatch(OPTIONS) From 50343b79e8fc57a5f40b89da0e11f2273bdd4b7c Mon Sep 17 00:00:00 2001 From: Ben Bleything Date: Thu, 11 Aug 2005 05:36:35 +0000 Subject: [PATCH 21/84] Finish Rails-ifying the tree. Adds missing files and directories and brings a few miscellaneous files up to date. --- public/.htaccess | 11 +- public/404.html | 2 + public/500.html | 2 + public/dispatch.cgi | 10 + public/dispatch.fcgi | 24 + public/favicon.ico | Bin 4710 -> 0 bytes public/javascripts/controls.js | 446 ++++++++++ public/javascripts/dragdrop.js | 537 ++++++++++++ public/javascripts/effects.js | 612 ++++++++++++++ public/javascripts/prototype.js | 1374 +++++++++++++++++++++++-------- script/benchmarker | 19 + script/destroy | 7 + script/generate | 7 + script/profiler | 34 + script/runner | 29 + 15 files changed, 2776 insertions(+), 338 deletions(-) create mode 100755 public/dispatch.cgi create mode 100755 public/dispatch.fcgi create mode 100644 public/javascripts/controls.js create mode 100644 public/javascripts/dragdrop.js create mode 100644 public/javascripts/effects.js create mode 100755 script/benchmarker create mode 100755 script/destroy create mode 100755 script/generate create mode 100755 script/profiler create mode 100755 script/runner diff --git a/public/.htaccess b/public/.htaccess index bf245115..7eb56e8b 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -3,6 +3,13 @@ AddHandler fastcgi-script .fcgi AddHandler cgi-script .cgi Options +FollowSymLinks +ExecCGI +# If you don't want Rails to look in certain directories, +# use the following rewrite rules so that Apache won't rewrite certain requests +# +# Example: +# RewriteCond %{REQUEST_URI} ^/notrails.* +# RewriteRule .* - [L] + # Redirect all requests not available on the filesystem to Rails # By default the cgi dispatcher is used which is very slow # @@ -14,7 +21,7 @@ RewriteEngine On RewriteRule ^$ index.html [QSA] RewriteRule ^([^.]+)$ $1.html [QSA] RewriteCond %{REQUEST_FILENAME} !-f -RewriteRule ^(.*)$ dispatch.cgi [QSA,L] +RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] # In case Rails experiences terminal errors # Instead of displaying this message you can supply a file here which will be rendered instead @@ -22,4 +29,4 @@ RewriteRule ^(.*)$ dispatch.cgi [QSA,L] # Example: # ErrorDocument 500 /500.html -ErrorDocument 500 "

Application error

Rails application failed to start properly" \ No newline at end of file +ErrorDocument 500 "

Application error

Rails application failed to start properly" diff --git a/public/404.html b/public/404.html index edbc89bf..0e184561 100644 --- a/public/404.html +++ b/public/404.html @@ -1,3 +1,5 @@ +

File not found

diff --git a/public/500.html b/public/500.html index ee0c919c..a1001a00 100644 --- a/public/500.html +++ b/public/500.html @@ -1,3 +1,5 @@ +

Application error (Apache)

diff --git a/public/dispatch.cgi b/public/dispatch.cgi new file mode 100755 index 00000000..3848806d --- /dev/null +++ b/public/dispatch.cgi @@ -0,0 +1,10 @@ +#!/usr/bin/ruby1.8 + +require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) + +# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: +# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch \ No newline at end of file diff --git a/public/dispatch.fcgi b/public/dispatch.fcgi new file mode 100755 index 00000000..3169ba26 --- /dev/null +++ b/public/dispatch.fcgi @@ -0,0 +1,24 @@ +#!/usr/bin/ruby1.8 +# +# You may specify the path to the FastCGI crash log (a log of unhandled +# exceptions which forced the FastCGI instance to exit, great for debugging) +# and the number of requests to process before running garbage collection. +# +# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log +# and the GC period is nil (turned off). A reasonable number of requests +# could range from 10-100 depending on the memory footprint of your app. +# +# Example: +# # Default log path, normal GC behavior. +# RailsFCGIHandler.process! +# +# # Default log path, 50 requests between GC. +# RailsFCGIHandler.process! nil, 50 +# +# # Custom log path, normal GC behavior. +# RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log' +# +require File.dirname(__FILE__) + "/../config/environment" +require 'fcgi_handler' + +RailsFCGIHandler.process! diff --git a/public/favicon.ico b/public/favicon.ico index e61366585aa57740c9ca3d84a74e84be8f8bf3cb..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 4710 zcmeHKA#WQo6n^LA*6Y%xIW%ct3#_=+D^TvygWaDpP%h_&(WeA?s+e{maV+0KG43`0fUjz~<)b}P%QRk>^R5$)m=b3K#b$IX2Cu)$vmaBr|6(lX^ULgXJdsDk?{xCvoOYjoqUpt44F}&;fA5RR z^PB^wH=uSNwzFbBeoj31bwxS@IYCx!Om|;Qy-A->v^!tubMYKHoW+&k^r5NuWaF&! z^_Ad^x8p)P3pgD2w#7a>TjVsqv}|+>iXUs(s#$Vb{*kQf|ul+sv2R%}}N&o-= diff --git a/public/javascripts/controls.js b/public/javascripts/controls.js new file mode 100644 index 00000000..cece0a91 --- /dev/null +++ b/public/javascripts/controls.js @@ -0,0 +1,446 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Element.collectTextNodesIgnoreClass = function(element, ignoreclass) { + var children = $(element).childNodes; + var text = ""; + var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i"); + + for (var i = 0; i < children.length; i++) { + if(children[i].nodeType==3) { + text+=children[i].nodeValue; + } else { + if((!children[i].className.match(classtest)) && children[i].hasChildNodes()) + text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass); + } + } + + return text; +} + +// Autocompleter.Base handles all the autocompletion functionality +// that's independent of the data source for autocompletion. This +// includes drawing the autocompletion menu, observing keyboard +// and mouse events, and similar. +// +// Specific autocompleters need to provide, at the very least, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getEntry(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: new Array (',', '\n') } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +var Autocompleter = {} +Autocompleter.Base = function() {}; +Autocompleter.Base.prototype = { + base_initialize: function(element, update, options) { + this.element = $(element); + this.update = $(update); + this.has_focus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entry_count = 0; + + if (this.setOptions) + this.setOptions(options); + else + this.options = {} + + this.options.tokens = this.options.tokens || new Array(); + this.options.frequency = this.options.frequency || 0.4; + this.options.min_chars = this.options.min_chars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + var offsets = Position.cumulativeOffset(element); + update.style.left = offsets[0] + 'px'; + update.style.top = (offsets[1] + element.offsetHeight) + 'px'; + update.style.width = element.offsetWidth + 'px'; + } + new Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + if(this.options.indicator) + this.indicator = $(this.options.indicator); + + if (typeof(this.options.tokens) == 'string') + this.options.tokens = new Array(this.options.tokens); + + this.observer = null; + + Element.hide(this.update); + + Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); + }, + + show: function() { + if(this.update.style.display=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && this.update.style.position=='absolute') { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + if(this.iefix) { + Position.clone(this.update, this.iefix); + this.iefix.style.zIndex = 1; + this.update.style.zIndex = 2; + Element.show(this.iefix); + } + }, + + hide: function() { + if(this.update.style.display=='') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.indicator) Element.show(this.indicator); + }, + + stopIndicator: function() { + if(this.indicator) Element.hide(this.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.select_entry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.mark_previous(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + case Event.KEY_DOWN: + this.mark_next(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN) + return; + + this.changed = true; + this.has_focus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + this.index = element.autocompleteIndex; + this.render(); + } + Event.stop(event); + }, + + onClick: function(event) { + var element = Event.findElement(event, 'LI'); + this.index = element.autocompleteIndex; + this.select_entry(); + Event.stop(event); + }, + + onBlur: function(event) { + // needed to make click events working + setTimeout(this.hide.bind(this), 250); + this.has_focus = false; + this.active = false; + }, + + render: function() { + if(this.entry_count > 0) { + for (var i = 0; i < this.entry_count; i++) + this.index==i ? + Element.addClassName(this.get_entry(i),"selected") : + Element.removeClassName(this.get_entry(i),"selected"); + + if(this.has_focus) { + if(this.get_current_entry().scrollIntoView) + this.get_current_entry().scrollIntoView(false); + + this.show(); + this.active = true; + } + } else this.hide(); + }, + + mark_previous: function() { + if(this.index > 0) this.index-- + else this.index = this.entry_count-1; + }, + + mark_next: function() { + if(this.index < this.entry_count-1) this.index++ + else this.index = 0; + }, + + get_entry: function(index) { + return this.update.firstChild.childNodes[index]; + }, + + get_current_entry: function() { + return this.get_entry(this.index); + }, + + select_entry: function() { + this.active = false; + value = Element.collectTextNodesIgnoreClass(this.get_current_entry(), 'informal').unescapeHTML(); + this.updateElement(value); + this.element.focus(); + }, + + updateElement: function(value) { + var last_token_pos = this.findLastToken(); + if (last_token_pos != -1) { + var new_value = this.element.value.substr(0, last_token_pos + 1); + var whitespace = this.element.value.substr(last_token_pos + 1).match(/^\s+/); + if (whitespace) + new_value += whitespace[0]; + this.element.value = new_value + value; + } else { + this.element.value = value; + } + }, + + updateChoices: function(choices) { + if(!this.changed && this.has_focus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.firstChild); + + if(this.update.firstChild && this.update.firstChild.childNodes) { + this.entry_count = + this.update.firstChild.childNodes.length; + for (var i = 0; i < this.entry_count; i++) { + entry = this.get_entry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entry_count = 0; + } + + this.stopIndicator(); + + this.index = 0; + this.render(); + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + if(this.getEntry().length>=this.options.min_chars) { + this.startIndicator(); + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + }, + + getEntry: function() { + var token_pos = this.findLastToken(); + if (token_pos != -1) + var ret = this.element.value.substr(token_pos + 1).replace(/^\s+/,'').replace(/\s+$/,''); + else + var ret = this.element.value; + + return /\n/.test(ret) ? '' : ret; + }, + + findLastToken: function() { + var last_token_pos = -1; + + for (var i=0; i last_token_pos) + last_token_pos = this_token_pos; + } + return last_token_pos; + } +} + +Ajax.Autocompleter = Class.create(); +Ajax.Autocompleter.prototype = Object.extend(new Autocompleter.Base(), +Object.extend(new Ajax.Base(), { + initialize: function(element, update, url, options) { + this.base_initialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this) + this.options.method = 'post'; + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + entry = encodeURIComponent(this.element.name) + '=' + + encodeURIComponent(this.getEntry()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } + +})); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partial_search - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option full_search to true (default: off). +// +// - full_search - Search anywhere in autocomplete array strings. +// +// - partial_chars - How many characters to enter before triggering +// a partial match (unlike min_chars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignore_case - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +Autocompleter.Local = Class.create(); +Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { + initialize: function(element, update, array, options) { + this.base_initialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partial_search: true, + partial_chars: 2, + ignore_case: true, + full_search: false, + selector: function(instance) { + var ret = new Array(); // Beginning matches + var partial = new Array(); // Inside matches + var entry = instance.getEntry(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + var elem = instance.options.array[i]; + var found_pos = instance.options.ignore_case ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (found_pos != -1) { + if (found_pos == 0 && elem.length != entry.length) { + ret.push("
  • " + elem.substr(0, entry.length) + "" + + elem.substr(entry.length) + "
  • "); + break; + } else if (entry.length >= instance.options.partial_chars && + instance.options.partial_search && found_pos != -1) { + if (instance.options.full_search || /\s/.test(elem.substr(found_pos-1,1))) { + partial.push("
  • " + elem.substr(0, found_pos) + "" + + elem.substr(found_pos, entry.length) + "" + elem.substr( + found_pos + entry.length) + "
  • "); + break; + } + } + + found_pos = instance.options.ignore_case ? + elem.toLowerCase().indexOf(entry.toLowerCase(), found_pos + 1) : + elem.indexOf(entry, found_pos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) + return "
      " + ret.join('') + "
    "; + } + }, options || {}); + } +}); diff --git a/public/javascripts/dragdrop.js b/public/javascripts/dragdrop.js new file mode 100644 index 00000000..c0fd1d1e --- /dev/null +++ b/public/javascripts/dragdrop.js @@ -0,0 +1,537 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// Element.Class part Copyright (c) 2005 by Rick Olson +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Element.Class = { + // Element.toggleClass(element, className) toggles the class being on/off + // Element.toggleClass(element, className1, className2) toggles between both classes, + // defaulting to className1 if neither exist + toggle: function(element, className) { + if(Element.Class.has(element, className)) { + Element.Class.remove(element, className); + if(arguments.length == 3) Element.Class.add(element, arguments[2]); + } else { + Element.Class.add(element, className); + if(arguments.length == 3) Element.Class.remove(element, arguments[2]); + } + }, + + // gets space-delimited classnames of an element as an array + get: function(element) { + element = $(element); + return element.className.split(' '); + }, + + // functions adapted from original functions by Gavin Kistner + remove: function(element) { + element = $(element); + var regEx; + for(var i = 1; i < arguments.length; i++) { + regEx = new RegExp("^" + arguments[i] + "\\b\\s*|\\s*\\b" + arguments[i] + "\\b", 'g'); + element.className = element.className.replace(regEx, '') + } + }, + + add: function(element) { + element = $(element); + for(var i = 1; i < arguments.length; i++) { + Element.Class.remove(element, arguments[i]); + element.className += (element.className.length > 0 ? ' ' : '') + arguments[i]; + } + }, + + // returns true if all given classes exist in said element + has: function(element) { + element = $(element); + if(!element || !element.className) return false; + var regEx; + for(var i = 1; i < arguments.length; i++) { + regEx = new RegExp("\\b" + arguments[i] + "\\b"); + if(!regEx.test(element.className)) return false; + } + return true; + }, + + // expects arrays of strings and/or strings as optional paramters + // Element.Class.has_any(element, ['classA','classB','classC'], 'classD') + has_any: function(element) { + element = $(element); + if(!element || !element.className) return false; + var regEx; + for(var i = 1; i < arguments.length; i++) { + if((typeof arguments[i] == 'object') && + (arguments[i].constructor == Array)) { + for(var j = 0; j < arguments[i].length; j++) { + regEx = new RegExp("\\b" + arguments[i][j] + "\\b"); + if(regEx.test(element.className)) return true; + } + } else { + regEx = new RegExp("\\b" + arguments[i] + "\\b"); + if(regEx.test(element.className)) return true; + } + } + return false; + }, + + childrenWith: function(element, className) { + var children = $(element).getElementsByTagName('*'); + var elements = new Array(); + + for (var i = 0; i < children.length; i++) { + if (Element.Class.has(children[i], className)) { + elements.push(children[i]); + break; + } + } + + return elements; + } +} + +/*--------------------------------------------------------------------------*/ + +var Droppables = { + drops: false, + + remove: function(element) { + for(var i = 0; i < this.drops.length; i++) + if(this.drops[i].element == element) + this.drops.splice(i,1); + }, + + add: function(element) { + var element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null + }, arguments[1] || {}); + + // cache containers + if(options.containment) { + options._containers = new Array(); + var containment = options.containment; + if((typeof containment == 'object') && + (containment.constructor == Array)) { + for(var i=0; i0) window.scrollBy(0,0); + + Event.stop(event); + } + } +} + +/*--------------------------------------------------------------------------*/ + +SortableObserver = Class.create(); +SortableObserver.prototype = { + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + onEnd: function() { + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +} + +Sortable = { + sortables: new Array(), + options: function(element){ + var element = $(element); + for(var i=0;i0.5) { + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode && oldParentNode.sortable) + oldParentNode.sortable.onChange(element); + if(dropon.parentNode.sortable) + dropon.parentNode.sortable.onChange(element); + } + } else { + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode && oldParentNode.sortable) + oldParentNode.sortable.onChange(element); + if(dropon.parentNode.sortable) + dropon.parentNode.sortable.onChange(element); + } + } + } + } + + // fix for gecko engine + Element.cleanWhitespace(element); + + options.draggables = []; + options.droppables = []; + + // make it so + var elements = element.childNodes; + for (var i = 0; i < elements.length; i++) + if(elements[i].tagName && elements[i].tagName==options.tag.toUpperCase() && + (!options.only || (Element.Class.has(elements[i], options.only)))) { + + // handles are per-draggable + var handle = options.handle ? + Element.Class.childrenWith(elements[i], options.handle)[0] : elements[i]; + + options.draggables.push(new Draggable(elements[i], Object.extend(options_for_draggable, { handle: handle }))); + + Droppables.add(elements[i], options_for_droppable); + options.droppables.push(elements[i]); + + } + + // keep reference + this.sortables.push(options); + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + serialize: function(element) { + var element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + only: sortableOptions.only, + name: element.id + }, arguments[1] || {}); + + var items = $(element).childNodes; + var queryComponents = new Array(); + + for(var i=0; i= this.finishOn) { + this.render(this.options.to); + if(this.finish) this.finish(); + if(this.options.afterFinish) this.options.afterFinish(this); + return; + } + var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); + var frame = Math.round(pos * this.options.fps * this.options.duration); + if(frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + this.timeout = setTimeout(this.loop.bind(this), 10); + }, + render: function(pos) { + if(this.options.transition) pos = this.options.transition(pos); + pos *= (this.options.to-this.options.from); + pos += this.options.from; + if(this.options.beforeUpdate) this.options.beforeUpdate(this); + if(this.update) this.update(pos); + if(this.options.afterUpdate) this.options.afterUpdate(this); + }, + cancel: function() { + if(this.timeout) clearTimeout(this.timeout); + } +} + +Effect.Parallel = Class.create(); +Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + for (var i = 0; i < this.effects.length; i++) + this.effects[i].render(position); + }, + finish: function(position) { + for (var i = 0; i < this.effects.length; i++) + if(this.effects[i].finish) this.effects[i].finish(position); + } +}); + +// Internet Explorer caveat: works only on elements the have +// a 'layout', meaning having a given width or height. +// There is no way to safely set this automatically. +Effect.Opacity = Class.create(); +Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + options = Object.extend({ + from: 0.0, + to: 1.0 + }, arguments[1] || {}); + this.start(options); + }, + update: function(position) { + this.setOpacity(position); + }, + setOpacity: function(opacity) { + opacity = (opacity == 1) ? 0.99999 : opacity; + this.element.style.opacity = opacity; + this.element.style.filter = "alpha(opacity:"+opacity*100+")"; + } +}); + +Effect.MoveBy = Class.create(); +Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), { + initialize: function(element, toTop, toLeft) { + this.element = $(element); + this.originalTop = parseFloat(this.element.style.top || '0'); + this.originalLeft = parseFloat(this.element.style.left || '0'); + this.toTop = toTop; + this.toLeft = toLeft; + Element.makePositioned(this.element); + this.start(arguments[3]); + }, + update: function(position) { + topd = this.toTop * position + this.originalTop; + leftd = this.toLeft * position + this.originalLeft; + this.setPosition(topd, leftd); + }, + setPosition: function(topd, leftd) { + this.element.style.top = topd + "px"; + this.element.style.left = leftd + "px"; + } +}); + +Effect.Scale = Class.create(); +Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { + initialize: function(element, percent) { + this.element = $(element) + options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or {} with provided values + scaleFrom: 100.0 + }, arguments[2] || {}); + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + if(this.element.style.fontSize=="") this.sizeEm = 1.0; + if(this.element.style.fontSize && this.element.style.fontSize.indexOf("em")>0) + this.sizeEm = parseFloat(this.element.style.fontSize); + this.factor = (percent/100.0) - (options.scaleFrom/100.0); + if(options.scaleMode=='box') { + this.originalHeight = this.element.clientHeight; + this.originalWidth = this.element.clientWidth; + } else + if(options.scaleMode=='contents') { + this.originalHeight = this.element.scrollHeight; + this.originalWidth = this.element.scrollWidth; + } else { + this.originalHeight = options.scaleMode.originalHeight; + this.originalWidth = options.scaleMode.originalWidth; + } + this.start(options); + }, + + update: function(position) { + currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if(this.options.scaleContent && this.sizeEm) + this.element.style.fontSize = this.sizeEm*currentScale + "em"; + this.setDimensions( + this.originalWidth * currentScale, + this.originalHeight * currentScale); + }, + + setDimensions: function(width, height) { + if(this.options.scaleX) this.element.style.width = width + 'px'; + if(this.options.scaleY) this.element.style.height = height + 'px'; + if(this.options.scaleFromCenter) { + topd = (height - this.originalHeight)/2; + leftd = (width - this.originalWidth)/2; + if(this.element.style.position=='absolute') { + if(this.options.scaleY) this.element.style.top = this.originalTop-topd + "px"; + if(this.options.scaleX) this.element.style.left = this.originalLeft-leftd + "px"; + } else { + if(this.options.scaleY) this.element.style.top = -topd + "px"; + if(this.options.scaleX) this.element.style.left = -leftd + "px"; + } + } + } +}); + +Effect.Highlight = Class.create(); +Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + + // try to parse current background color as default for endcolor + // browser stores this as: "rgb(255, 255, 255)", convert to "#ffffff" format + var endcolor = "#ffffff"; + var current = this.element.style.backgroundColor; + if(current && current.slice(0,4) == "rgb(") { + endcolor = "#"; + var cols = current.slice(4,current.length-1).split(','); + var i=0; do { endcolor += parseInt(cols[i]).toColorPart() } while (++i<3); } + + var options = Object.extend({ + startcolor: "#ffff99", + endcolor: endcolor, + restorecolor: current + }, arguments[1] || {}); + + // init color calculations + this.colors_base = [ + parseInt(options.startcolor.slice(1,3),16), + parseInt(options.startcolor.slice(3,5),16), + parseInt(options.startcolor.slice(5),16) ]; + this.colors_delta = [ + parseInt(options.endcolor.slice(1,3),16)-this.colors_base[0], + parseInt(options.endcolor.slice(3,5),16)-this.colors_base[1], + parseInt(options.endcolor.slice(5),16)-this.colors_base[2] ]; + + this.start(options); + }, + update: function(position) { + var colors = [ + Math.round(this.colors_base[0]+(this.colors_delta[0]*position)), + Math.round(this.colors_base[1]+(this.colors_delta[1]*position)), + Math.round(this.colors_base[2]+(this.colors_delta[2]*position)) ]; + this.element.style.backgroundColor = "#" + + colors[0].toColorPart() + colors[1].toColorPart() + colors[2].toColorPart(); + }, + finish: function() { + this.element.style.backgroundColor = this.options.restorecolor; + } +}); + +Effect.ScrollTo = Class.create(); +Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + Position.prepare(); + var offsets = Position.cumulativeOffset(this.element); + var max = window.innerHeight ? + window.height - window.innerHeight : + document.body.scrollHeight - + (document.documentElement.clientHeight ? + document.documentElement.clientHeight : document.body.clientHeight); + this.scrollStart = Position.deltaY; + this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; + this.start(arguments[1] || {}); + }, + update: function(position) { + Position.prepare(); + window.scrollTo(Position.deltaX, + this.scrollStart + (position*this.delta)); + } +}); + +/* ------------- prepackaged effects ------------- */ + +Effect.Fade = function(element) { + options = Object.extend({ + from: 1.0, + to: 0.0, + afterFinish: function(effect) + { Element.hide(effect.element); + effect.setOpacity(1); } + }, arguments[1] || {}); + new Effect.Opacity(element,options); +} + +Effect.Appear = function(element) { + options = Object.extend({ + from: 0.0, + to: 1.0, + beforeStart: function(effect) + { effect.setOpacity(0); + Element.show(effect.element); }, + afterUpdate: function(effect) + { Element.show(effect.element); } + }, arguments[1] || {}); + new Effect.Opacity(element,options); +} + +Effect.Puff = function(element) { + new Effect.Parallel( + [ new Effect.Scale(element, 200, { sync: true, scaleFromCenter: true }), + new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ], + { duration: 1.0, + afterUpdate: function(effect) + { effect.effects[0].element.style.position = 'absolute'; }, + afterFinish: function(effect) + { Element.hide(effect.effects[0].element); } + } + ); +} + +Effect.BlindUp = function(element) { + Element.makeClipping(element); + new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + afterFinish: function(effect) + { + Element.hide(effect.element); + Element.undoClipping(effect.element); + } + }, arguments[1] || {}) + ); +} + +Effect.BlindDown = function(element) { + $(element).style.height = '0px'; + Element.makeClipping(element); + Element.show(element); + new Effect.Scale(element, 100, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'contents', + scaleFrom: 0, + afterFinish: function(effect) { + Element.undoClipping(effect.element); + } + }, arguments[1] || {}) + ); +} + +Effect.SwitchOff = function(element) { + new Effect.Appear(element, + { duration: 0.4, + transition: Effect.Transitions.flicker, + afterFinish: function(effect) + { effect.element.style.overflow = 'hidden'; + new Effect.Scale(effect.element, 1, + { duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, + afterUpdate: function(effect) { + if(effect.element.style.position=="") + effect.element.style.position = 'relative'; }, + afterFinish: function(effect) { Element.hide(effect.element); } + } ) + } + } ); +} + +Effect.DropOut = function(element) { + new Effect.Parallel( + [ new Effect.MoveBy(element, 100, 0, { sync: true }), + new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ], + { duration: 0.5, + afterFinish: function(effect) + { Element.hide(effect.effects[0].element); } + }); +} + +Effect.Shake = function(element) { + new Effect.MoveBy(element, 0, 20, + { duration: 0.05, afterFinish: function(effect) { + new Effect.MoveBy(effect.element, 0, -40, + { duration: 0.1, afterFinish: function(effect) { + new Effect.MoveBy(effect.element, 0, 40, + { duration: 0.1, afterFinish: function(effect) { + new Effect.MoveBy(effect.element, 0, -40, + { duration: 0.1, afterFinish: function(effect) { + new Effect.MoveBy(effect.element, 0, 40, + { duration: 0.1, afterFinish: function(effect) { + new Effect.MoveBy(effect.element, 0, -20, + { duration: 0.05, afterFinish: function(effect) { + }}) }}) }}) }}) }}) }}); +} + +Effect.SlideDown = function(element) { + element = $(element); + element.style.height = '0px'; + Element.makeClipping(element); + Element.cleanWhitespace(element); + Element.makePositioned(element.firstChild); + Element.show(element); + new Effect.Scale(element, 100, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'contents', + scaleFrom: 0, + afterUpdate: function(effect) + { effect.element.firstChild.style.bottom = + (effect.originalHeight - effect.element.clientHeight) + 'px'; }, + afterFinish: function(effect) + { Element.undoClipping(effect.element); } + }, arguments[1] || {}) + ); +} + +Effect.SlideUp = function(element) { + element = $(element); + Element.makeClipping(element); + Element.cleanWhitespace(element); + Element.makePositioned(element.firstChild); + Element.show(element); + new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + afterUpdate: function(effect) + { effect.element.firstChild.style.bottom = + (effect.originalHeight - effect.element.clientHeight) + 'px'; }, + afterFinish: function(effect) + { + Element.hide(effect.element); + Element.undoClipping(effect.element); + } + }, arguments[1] || {}) + ); +} + +Effect.Squish = function(element) { + new Effect.Scale(element, 0, + { afterFinish: function(effect) { Element.hide(effect.element); } }); +} + +Effect.Grow = function(element) { + element = $(element); + var options = arguments[1] || {}; + + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + element.style.overflow = 'hidden'; + Element.show(element); + + var direction = options.direction || 'center'; + var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; + var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal; + var opacityTransition = options.opacityTransition || Effect.Transitions.full; + + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = originalWidth; + initialMoveY = moveY = 0; + moveX = -originalWidth; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = originalHeight; + moveY = -originalHeight; + break; + case 'bottom-right': + initialMoveX = originalWidth; + initialMoveY = originalHeight; + moveX = -originalWidth; + moveY = -originalHeight; + break; + case 'center': + initialMoveX = originalWidth / 2; + initialMoveY = originalHeight / 2; + moveX = -originalWidth / 2; + moveY = -originalHeight / 2; + break; + } + + new Effect.MoveBy(element, initialMoveY, initialMoveX, { + duration: 0.01, + beforeUpdate: function(effect) { $(element).style.height = '0px'; }, + afterFinish: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }), + new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition }), + new Effect.Scale(element, 100, { + scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth }, + sync: true, scaleFrom: 0, scaleTo: 100, transition: scaleTransition })], + options); } + }); +} + +Effect.Shrink = function(element) { + element = $(element); + var options = arguments[1] || {}; + + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + element.style.overflow = 'hidden'; + Element.show(element); + + var direction = options.direction || 'center'; + var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; + var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal; + var opacityTransition = options.opacityTransition || Effect.Transitions.none; + + var moveX, moveY; + + switch (direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = originalWidth; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = originalHeight; + break; + case 'bottom-right': + moveX = originalWidth; + moveY = originalHeight; + break; + case 'center': + moveX = originalWidth / 2; + moveY = originalHeight / 2; + break; + } + + new Effect.Parallel( + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: opacityTransition }), + new Effect.Scale(element, 0, { sync: true, transition: moveTransition }), + new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: scaleTransition }) ], + options); +} + +Effect.Pulsate = function(element) { + var options = arguments[1] || {}; + var transition = options.transition || Effect.Transitions.sinoidal; + var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) }; + reverser.bind(transition); + new Effect.Opacity(element, + Object.extend(Object.extend({ duration: 3.0, + afterFinish: function(effect) { Element.show(effect.element); } + }, options), {transition: reverser})); +} + +Effect.Fold = function(element) { + $(element).style.overflow = 'hidden'; + new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleTo: 100, + scaleX: false, + afterFinish: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleTo: 0, + scaleY: false, + afterFinish: function(effect) { Element.hide(effect.element) } }); + }}, arguments[1] || {})); +} + +// old: new Effect.ContentZoom(element, percent) +// new: Element.setContentZoom(element, percent) + +Element.setContentZoom = function(element, percent) { + var element = $(element); + element.style.fontSize = (percent/100) + "em"; + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); +} diff --git a/public/javascripts/prototype.js b/public/javascripts/prototype.js index 62bbc98e..37635ccf 100644 --- a/public/javascripts/prototype.js +++ b/public/javascripts/prototype.js @@ -1,336 +1,1038 @@ -/* Prototype: an object-oriented Javascript library, version 1.0.1 - * (c) 2005 Sam Stephenson - * - * Prototype is freely distributable under the terms of an MIT-style license. - * For details, see http://prototype.conio.net/ - */ - -Prototype = { - Version: '1.0.1' -} - -Class = { - create: function() { - return function() { - this.initialize.apply(this, arguments); - } - } -} - -Abstract = new Object(); - -Object.prototype.extend = function(object) { - for (property in object) { - this[property] = object[property]; - } - return this; -} - -Function.prototype.bind = function(object) { - var method = this; - return function() { - method.apply(object, arguments); - } -} - -Function.prototype.bindAsEventListener = function(object) { - var method = this; - return function(event) { - method.call(object, event || window.event); - } -} - -Try = { - these: function() { - var returnValue; - - for (var i = 0; i < arguments.length; i++) { - var lambda = arguments[i]; - try { - returnValue = lambda(); - break; - } catch (e) {} - } - - return returnValue; - } -} - -Toggle = { - display: function() { - for (var i = 0; i < elements.length; i++) { - var element = $(elements[i]); - element.style.display = - (element.style.display == 'none' ? '' : 'none'); - } - } -} - -/*--------------------------------------------------------------------------*/ - -function $() { - var elements = new Array(); - - for (var i = 0; i < arguments.length; i++) { - var element = arguments[i]; - if (typeof element == 'string') - element = document.getElementById(element); - - if (arguments.length == 1) - return element; - - elements.push(element); - } - - return elements; -} - -function getElementsByClassName(className, element) { - var children = (element || document).getElementsByTagName('*'); - var elements = new Array(); - - for (var i = 0; i < children.length; i++) { - var child = children[i]; - var classNames = child.className.split(' '); - for (var j = 0; j < classNames.length; j++) { - if (classNames[j] == className) { - elements.push(child); - break; - } - } - } - - return elements; -} - -/*--------------------------------------------------------------------------*/ - -Ajax = { - getTransport: function() { - return Try.these( - function() {return new ActiveXObject('Msxml2.XMLHTTP')}, - function() {return new ActiveXObject('Microsoft.XMLHTTP')}, - function() {return new XMLHttpRequest()} - ) || false; - }, - - emptyFunction: function() {} -} - -Ajax.Base = function() {}; -Ajax.Base.prototype = { - setOptions: function(options) { - this.options = { - method: 'post', - asynchronous: true, - parameters: '' - }.extend(options || {}); - } -} - -Ajax.Request = Class.create(); -Ajax.Request.Events = - ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; - -Ajax.Request.prototype = (new Ajax.Base()).extend({ - initialize: function(url, options) { - this.transport = Ajax.getTransport(); - this.setOptions(options); - - try { - if (this.options.method == 'get') - url += '?' + this.options.parameters + '&_='; - - this.transport.open(this.options.method, url, true); - - if (this.options.asynchronous) { - this.transport.onreadystatechange = this.onStateChange.bind(this); - setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); - } - - if (this.options.method == 'post') { - this.transport.setRequestHeader('Connection', 'close'); - this.transport.setRequestHeader('Content-type', - 'application/x-www-form-urlencoded'); - } - - this.transport.send(this.options.method == 'post' ? - this.options.parameters + '&_=' : null); - - } catch (e) { - } - }, - - onStateChange: function() { - var readyState = this.transport.readyState; - if (readyState != 1) - this.respondToReadyState(this.transport.readyState); - }, - - respondToReadyState: function(readyState) { - var event = Ajax.Request.Events[readyState]; - (this.options['on' + event] || Ajax.emptyFunction)(this.transport); - } -}); - -Ajax.Updater = Class.create(); -Ajax.Updater.prototype = (new Ajax.Base()).extend({ - initialize: function(container, url, options) { - this.container = $(container); - this.setOptions(options); - - if (this.options.asynchronous) { - this.onComplete = this.options.onComplete; - this.options.onComplete = this.updateContent.bind(this); - } - - this.request = new Ajax.Request(url, this.options); - - if (!this.options.asynchronous) - this.updateContent(); - }, - - updateContent: function() { - this.container.innerHTML = this.request.transport.responseText; - if (this.onComplete) this.onComplete(this.request); - } -}); - -/*--------------------------------------------------------------------------*/ - -Field = { - clear: function() { - for (var i = 0; i < arguments.length; i++) - $(arguments[i]).value = ''; - }, - - focus: function(element) { - $(element).focus(); - }, - - present: function() { - for (var i = 0; i < arguments.length; i++) - if ($(arguments[i]).value == '') return false; - return true; - } -} - -/*--------------------------------------------------------------------------*/ - -Form = { - serialize: function(form) { - var elements = Form.getElements($(form)); - var queryComponents = new Array(); - - for (var i = 0; i < elements.length; i++) { - var queryComponent = Form.Element.serialize(elements[i]); - if (queryComponent) - queryComponents.push(queryComponent); - } - - return queryComponents.join('&'); - }, - - getElements: function(form) { - form = $(form); - var elements = new Array(); - - for (tagName in Form.Element.Serializers) { - var tagElements = form.getElementsByTagName(tagName); - for (var j = 0; j < tagElements.length; j++) - elements.push(tagElements[j]); - } - return elements; - } -} - -Form.Element = { - serialize: function(element) { - element = $(element); - var method = element.tagName.toLowerCase(); - var parameter = Form.Element.Serializers[method](element); - - if (parameter) - return encodeURIComponent(parameter[0]) + '=' + - encodeURIComponent(parameter[1]); - }, - - getValue: function(element) { - element = $(element); - var method = element.tagName.toLowerCase(); - var parameter = Form.Element.Serializers[method](element); - - if (parameter) - return parameter[1]; - } -} - -Form.Element.Serializers = { - input: function(element) { - switch (element.type.toLowerCase()) { - case 'hidden': - case 'text': - return Form.Element.Serializers.textarea(element); - case 'checkbox': - case 'radio': - return Form.Element.Serializers.inputSelector(element); - } - }, - - inputSelector: function(element) { - if (element.checked) - return [element.name, element.value]; - }, - - textarea: function(element) { - return [element.name, element.value]; - }, - - select: function(element) { - var index = element.selectedIndex; - return [element.name, (index >= 0) ? element.options[index].value : '']; - } -} - -/*--------------------------------------------------------------------------*/ - -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() { - setTimeout(this.onTimerEvent.bind(this), this.frequency * 1000); - }, - - onTimerEvent: function() { - var value = this.getValue(); - if (this.lastValue != value) { - this.callback(this.element, value); - this.lastValue = value; - } - - this.registerCallback(); - } -} - -Form.Element.Observer = Class.create(); -Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({ - getValue: function() { - return Form.Element.getValue(this.element); - } -}); - -Form.Observer = Class.create(); -Form.Observer.prototype = (new Abstract.TimedObserver()).extend({ - getValue: function() { - return Form.serialize(this.element); - } -}); - +/* Prototype JavaScript framework, version 1.3.1 + * (c) 2005 Sam Stephenson + * + * THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff + * against the source tree, available from the Prototype darcs repository. + * + * 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.3.1', + emptyFunction: function() {} +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.extend = function(destination, source) { + for (property in source) { + destination[property] = source[property]; + } + return destination; +} + +Object.prototype.extend = function(object) { + return Object.extend.apply(this, [this, object]); +} + +Function.prototype.bind = function(object) { + var __method = this; + return function() { + __method.apply(object, arguments); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var __method = this; + return function(event) { + __method.call(object, event || window.event); + } +} + +Number.prototype.toColorPart = function() { + var digits = this.toString(16); + if (this < 16) return '0' + digits; + return digits; +} + +var Try = { + these: function() { + var returnValue; + + for (var i = 0; i < arguments.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() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(); + } finally { + this.currentlyExecuting = false; + } + } + } +} + +/*--------------------------------------------------------------------------*/ + +function $() { + var elements = new Array(); + + for (var i = 0; i < arguments.length; i++) { + var element = arguments[i]; + if (typeof element == 'string') + element = document.getElementById(element); + + if (arguments.length == 1) + return element; + + elements.push(element); + } + + return elements; +} + +if (!Array.prototype.push) { + Array.prototype.push = function() { + var startLength = this.length; + for (var i = 0; i < arguments.length; i++) + this[startLength + i] = arguments[i]; + return this.length; + } +} + +if (!Function.prototype.apply) { + // Based on code from http://www.youngpup.net/ + Function.prototype.apply = function(object, parameters) { + var parameterStrings = new Array(); + if (!object) object = window; + if (!parameters) parameters = new Array(); + + for (var i = 0; i < parameters.length; i++) + parameterStrings[i] = 'parameters[' + i + ']'; + + object.__apply__ = this; + var result = eval('object.__apply__(' + + parameterStrings[i].join(', ') + ')'); + object.__apply__ = null; + + return result; + } +} + +String.prototype.extend({ + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + 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].nodeValue; + } +}); + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')}, + function() {return new XMLHttpRequest()} + ) || false; + } +} + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + parameters: '' + }.extend(options || {}); + }, + + responseIsSuccess: function() { + return this.transport.status == undefined + || this.transport.status == 0 + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + responseIsFailure: function() { + return !this.responseIsSuccess(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = (new Ajax.Base()).extend({ + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + var parameters = this.options.parameters || ''; + if (parameters.length > 0) parameters += '&_='; + + try { + if (this.options.method == 'get') + url += '?' + parameters; + + this.transport.open(this.options.method, url, + this.options.asynchronous); + + if (this.options.asynchronous) { + this.transport.onreadystatechange = this.onStateChange.bind(this); + setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); + } + + this.setRequestHeaders(); + + var body = this.options.postBody ? this.options.postBody : parameters; + this.transport.send(this.options.method == 'post' ? body : null); + + } catch (e) { + } + }, + + setRequestHeaders: function() { + var requestHeaders = + ['X-Requested-With', 'XMLHttpRequest', + 'X-Prototype-Version', Prototype.Version]; + + if (this.options.method == 'post') { + requestHeaders.push('Content-type', + 'application/x-www-form-urlencoded'); + + /* Force "Connection: close" for Mozilla browsers to work around + * a bug where XMLHttpReqeuest sends an incorrect Content-length + * header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType) + requestHeaders.push('Connection', 'close'); + } + + if (this.options.requestHeaders) + requestHeaders.push.apply(requestHeaders, this.options.requestHeaders); + + for (var i = 0; i < requestHeaders.length; i += 2) + this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]); + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState != 1) + this.respondToReadyState(this.transport.readyState); + }, + + respondToReadyState: function(readyState) { + var event = Ajax.Request.Events[readyState]; + + if (event == 'Complete') + (this.options['on' + this.transport.status] + || this.options['on' + this.responseIsSuccess() ? 'Success' : 'Failure'] + || Prototype.emptyFunction)(this.transport); + + (this.options['on' + event] || Prototype.emptyFunction)(this.transport); + + /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ + if (event == 'Complete') + this.transport.onreadystatechange = Prototype.emptyFunction; + } +}); + +Ajax.Updater = Class.create(); +Ajax.Updater.ScriptFragment = '(?:)((\n|.)*?)(?:<\/script>)'; + +Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({ + initialize: function(container, url, options) { + this.containers = { + success: container.success ? $(container.success) : $(container), + failure: 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() { + this.updateContent(); + onComplete(this.transport); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.responseIsSuccess() ? + this.containers.success : this.containers.failure; + + var match = new RegExp(Ajax.Updater.ScriptFragment, 'img'); + var response = this.transport.responseText.replace(match, ''); + var scripts = this.transport.responseText.match(match); + + if (receiver) { + if (this.options.insertion) { + new this.options.insertion(receiver, response); + } else { + receiver.innerHTML = response; + } + } + + if (this.responseIsSuccess()) { + if (this.onComplete) + setTimeout((function() {this.onComplete( + this.transport)}).bind(this), 10); + } + + if (this.options.evalScripts && scripts) { + match = new RegExp(Ajax.Updater.ScriptFragment, 'im'); + setTimeout((function() { + for (var i = 0; i < scripts.length; i++) + eval(scripts[i].match(match)[1]); + }).bind(this), 10); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = (new Ajax.Base()).extend({ + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.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.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Ajax.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); + } +}); + +document.getElementsByClassName = function(className) { + var children = document.getElementsByTagName('*') || document.all; + var elements = new Array(); + + for (var i = 0; i < children.length; i++) { + var child = children[i]; + var classNames = child.className.split(' '); + for (var j = 0; j < classNames.length; j++) { + if (classNames[j] == className) { + elements.push(child); + break; + } + } + } + + return elements; +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) { + var Element = new Object(); +} + +Object.extend(Element, { + toggle: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = + (element.style.display == 'none' ? '' : 'none'); + } + }, + + hide: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = 'none'; + } + }, + + show: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = ''; + } + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + }, + + getHeight: function(element) { + element = $(element); + return element.offsetHeight; + }, + + hasClassName: function(element, className) { + element = $(element); + if (!element) + return; + var a = element.className.split(' '); + for (var i = 0; i < a.length; i++) { + if (a[i] == className) + return true; + } + return false; + }, + + addClassName: function(element, className) { + element = $(element); + Element.removeClassName(element, className); + element.className += ' ' + className; + }, + + removeClassName: function(element, className) { + element = $(element); + if (!element) + return; + var newClassName = ''; + var a = element.className.split(' '); + for (var i = 0; i < a.length; i++) { + if (a[i] != className) { + if (i > 0) + newClassName += ' '; + newClassName += a[i]; + } + } + element.className = newClassName; + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + var element = $(element); + for (var i = 0; i < element.childNodes.length; i++) { + var node = element.childNodes[i]; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + Element.remove(node); + } + } +}); + +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; + + if (this.adjacency && this.element.insertAdjacentHTML) { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.fragment = this.range.createContextualFragment(this.content); + this.insertContent(); + } + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = (new Abstract.Insertion('beforeBegin')).extend({ + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function() { + this.element.parentNode.insertBefore(this.fragment, this.element); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = (new Abstract.Insertion('afterBegin')).extend({ + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function() { + this.element.insertBefore(this.fragment, this.element.firstChild); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = (new Abstract.Insertion('beforeEnd')).extend({ + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function() { + this.element.appendChild(this.fragment); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = (new Abstract.Insertion('afterEnd')).extend({ + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function() { + this.element.parentNode.insertBefore(this.fragment, + this.element.nextSibling); + } +}); + +var Field = { + clear: function() { + for (var i = 0; i < arguments.length; i++) + $(arguments[i]).value = ''; + }, + + focus: function(element) { + $(element).focus(); + }, + + present: function() { + for (var i = 0; i < arguments.length; i++) + if ($(arguments[i]).value == '') return false; + return true; + }, + + select: function(element) { + $(element).select(); + }, + + activate: function(element) { + $(element).focus(); + $(element).select(); + } +} + +/*--------------------------------------------------------------------------*/ + +var Form = { + serialize: function(form) { + var elements = Form.getElements($(form)); + var queryComponents = new Array(); + + for (var i = 0; i < elements.length; i++) { + var queryComponent = Form.Element.serialize(elements[i]); + if (queryComponent) + queryComponents.push(queryComponent); + } + + return queryComponents.join('&'); + }, + + getElements: function(form) { + var form = $(form); + var elements = new Array(); + + for (tagName in Form.Element.Serializers) { + var tagElements = form.getElementsByTagName(tagName); + for (var j = 0; j < tagElements.length; j++) + elements.push(tagElements[j]); + } + return elements; + }, + + getInputs: function(form, typeName, name) { + var form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) + return inputs; + + var matchingInputs = new Array(); + for (var i = 0; i < inputs.length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || + (name && input.name != name)) + continue; + matchingInputs.push(input); + } + + return matchingInputs; + }, + + disable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.blur(); + element.disabled = 'true'; + } + }, + + enable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.disabled = ''; + } + }, + + focusFirstElement: function(form) { + var form = $(form); + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + if (element.type != 'hidden' && !element.disabled) { + Field.activate(element); + break; + } + } + }, + + reset: function(form) { + $(form).reset(); + } +} + +Form.Element = { + serialize: function(element) { + var element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) + return encodeURIComponent(parameter[0]) + '=' + + encodeURIComponent(parameter[1]); + }, + + getValue: function(element) { + var element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) + return parameter[1]; + } +} + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'submit': + case 'hidden': + case 'password': + case 'text': + return Form.Element.Serializers.textarea(element); + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + } + return false; + }, + + inputSelector: function(element) { + if (element.checked) + return [element.name, element.value]; + }, + + textarea: function(element) { + return [element.name, element.value]; + }, + + select: function(element) { + var value = ''; + if (element.type == 'select-one') { + var index = element.selectedIndex; + if (index >= 0) + value = element.options[index].value || element.options[index].text; + } else { + value = new Array(); + for (var i = 0; i < element.length; i++) { + var opt = element.options[i]; + if (opt.selected) + value.push(opt.value || opt.text); + } + } + return [element.name, value]; + } +} + +/*--------------------------------------------------------------------------*/ + +var $F = Form.Element.getValue; + +/*--------------------------------------------------------------------------*/ + +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(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({ + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = (new Abstract.TimedObserver()).extend({ + 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() { + var elements = Form.getElements(this.element); + for (var i = 0; i < elements.length; i++) + this.registerCallback(elements[i]); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + element.target = this; + element.prev_onclick = element.onclick || Prototype.emptyFunction; + element.onclick = function() { + this.prev_onclick(); + this.target.onElementEvent(); + } + break; + case 'password': + case 'text': + case 'textarea': + case 'select-one': + case 'select-multiple': + element.target = this; + element.prev_onchange = element.onchange || Prototype.emptyFunction; + element.onchange = function() { + this.prev_onchange(); + this.target.onElementEvent(); + } + break; + } + } + } +} + +Form.Element.EventObserver = Class.create(); +Form.Element.EventObserver.prototype = (new Abstract.EventObserver()).extend({ + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(); +Form.EventObserver.prototype = (new Abstract.EventObserver()).extend({ + 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, + + 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; + } + }, + + // 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; i < Event.observers.length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + var element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + ((navigator.appVersion.indexOf('AppleWebKit') > 0) + || element.attachEvent)) + name = 'keydown'; + + this._observeAndCache(element, name, observer, useCapture); + }, + + stopObserving: function(element, name, observer, useCapture) { + var element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + ((navigator.appVersion.indexOf('AppleWebKit') > 0) + || element.detachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + element.detachEvent('on' + name, observer); + } + } +}); + +/* prevent memory leaks in IE */ +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]; + }, + + // 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; + }, + + clone: function(source, target) { + source = $(source); + target = $(target); + target.style.position = 'absolute'; + var offsets = this.cumulativeOffset(source); + target.style.top = offsets[1] + 'px'; + target.style.left = offsets[0] + 'px'; + target.style.width = source.offsetWidth + 'px'; + target.style.height = source.offsetHeight + 'px'; + } +} diff --git a/script/benchmarker b/script/benchmarker new file mode 100755 index 00000000..2e323b66 --- /dev/null +++ b/script/benchmarker @@ -0,0 +1,19 @@ +#!/usr/bin/ruby1.8 + +if ARGV.empty? + puts "Usage: benchmarker times 'Person.expensive_way' 'Person.another_expensive_way' ..." + exit +end + +require File.dirname(__FILE__) + '/../config/environment' +require 'benchmark' +include Benchmark + +# Don't include compilation in the benchmark +ARGV[1..-1].each { |expression| eval(expression) } + +bm(6) do |x| + ARGV[1..-1].each_with_index do |expression, idx| + x.report("##{idx + 1}") { ARGV[0].to_i.times { eval(expression) } } + end +end \ No newline at end of file diff --git a/script/destroy b/script/destroy new file mode 100755 index 00000000..624049da --- /dev/null +++ b/script/destroy @@ -0,0 +1,7 @@ +#!/usr/bin/ruby1.8 +require File.dirname(__FILE__) + '/../config/environment' +require 'rails_generator' +require 'rails_generator/scripts/destroy' + +ARGV.shift if ['--help', '-h'].include?(ARGV[0]) +Rails::Generator::Scripts::Destroy.new.run(ARGV) diff --git a/script/generate b/script/generate new file mode 100755 index 00000000..a104fc94 --- /dev/null +++ b/script/generate @@ -0,0 +1,7 @@ +#!/usr/bin/ruby1.8 +require File.dirname(__FILE__) + '/../config/environment' +require 'rails_generator' +require 'rails_generator/scripts/generate' + +ARGV.shift if ['--help', '-h'].include?(ARGV[0]) +Rails::Generator::Scripts::Generate.new.run(ARGV) diff --git a/script/profiler b/script/profiler new file mode 100755 index 00000000..92cf8247 --- /dev/null +++ b/script/profiler @@ -0,0 +1,34 @@ +#!/usr/bin/ruby1.8 +if ARGV.empty? + $stderr.puts "Usage: profiler 'Person.expensive_method(10)' [times]" + exit(1) +end + +# Keep the expensive require out of the profile. +$stderr.puts 'Loading Rails...' +require File.dirname(__FILE__) + '/../config/environment' + +# Define a method to profile. +if ARGV[1] and ARGV[1].to_i > 1 + eval "def profile_me() #{ARGV[1]}.times { #{ARGV[0]} } end" +else + eval "def profile_me() #{ARGV[0]} end" +end + +# Use the ruby-prof extension if available. Fall back to stdlib profiler. +begin + require 'prof' + $stderr.puts 'Using the ruby-prof extension.' + Prof.clock_mode = Prof::GETTIMEOFDAY + Prof.start + profile_me + results = Prof.stop + require 'rubyprof_ext' + Prof.print_profile(results, $stderr) +rescue LoadError + $stderr.puts 'Using the standard Ruby profiler.' + Profiler__.start_profile + profile_me + Profiler__.stop_profile + Profiler__.print_profile($stderr) +end diff --git a/script/runner b/script/runner new file mode 100755 index 00000000..a9f705c1 --- /dev/null +++ b/script/runner @@ -0,0 +1,29 @@ +#!/usr/bin/ruby1.8 +require 'optparse' + +options = { :environment => "development" } + +ARGV.options do |opts| + script_name = File.basename($0) + opts.banner = "Usage: runner 'puts Person.find(1).name' [options]" + + opts.separator "" + + opts.on("-e", "--environment=name", String, + "Specifies the environment for the runner to operate under (test/development/production).", + "Default: development") { |options[:environment]| } + + opts.separator "" + + opts.on("-h", "--help", + "Show this help message.") { puts opts; exit } + + opts.parse! +end + +ENV["RAILS_ENV"] = options[:environment] + +#!/usr/local/bin/ruby + +require File.dirname(__FILE__) + '/../config/environment' +eval(ARGV.first) \ No newline at end of file From f07d0e32c6295ab3d4076b36e8047aa7eef69a9f Mon Sep 17 00:00:00 2001 From: Ben Bleything Date: Thu, 11 Aug 2005 05:46:38 +0000 Subject: [PATCH 22/84] Fix shebangs. Consistentlicious! --- instiki | 2 +- instiki.rb | 2 +- lib/bluecloth_tweaked.rb | 2 +- public/dispatch.cgi | 2 +- public/dispatch.fcgi | 2 +- public/dispatch.rb | 2 +- script/benchmarker | 2 +- script/breakpointer | 2 +- script/console | 2 +- script/destroy | 2 +- script/generate | 2 +- script/profiler | 2 +- script/runner | 4 ++-- script/server | 2 +- test/functional/admin_controller_test.rb | 2 +- test/functional/file_controller_test.rb | 2 +- test/functional/routes_test.rb | 2 +- test/functional/wiki_controller_test.rb | 2 +- test/unit/chunks/category_test.rb | 2 +- test/unit/chunks/nowiki_test.rb | 2 +- test/unit/chunks/wiki_test.rb | 2 +- test/unit/diff_test.rb | 2 +- test/unit/file_yard_test.rb | 2 +- test/unit/redcloth_for_tex_test.rb | 2 +- test/unit/uri_test.rb | 2 +- test/unit/wiki_words_test.rb | 2 +- 26 files changed, 27 insertions(+), 27 deletions(-) diff --git a/instiki b/instiki index c6a5592f..bf720a1c 100755 --- a/instiki +++ b/instiki @@ -1,4 +1,4 @@ -#!/usr/bin/ruby +#!/usr/bin/env ruby # Executable file for a gem # must be same as ./instiki.rb diff --git a/instiki.rb b/instiki.rb index e5637f96..f26498e4 100755 --- a/instiki.rb +++ b/instiki.rb @@ -1,3 +1,3 @@ -#!/usr/bin/ruby +#!/usr/bin/env ruby load File.dirname(__FILE__) + '/script/server' diff --git a/lib/bluecloth_tweaked.rb b/lib/bluecloth_tweaked.rb index 468b4fea..b91622f1 100644 --- a/lib/bluecloth_tweaked.rb +++ b/lib/bluecloth_tweaked.rb @@ -1,4 +1,4 @@ -#!/usr/bin/ruby +#!/usr/bin/env ruby # # Bluecloth is a Ruby implementation of Markdown, a text-to-HTML conversion # tool. diff --git a/public/dispatch.cgi b/public/dispatch.cgi index 3848806d..ce705d36 100755 --- a/public/dispatch.cgi +++ b/public/dispatch.cgi @@ -1,4 +1,4 @@ -#!/usr/bin/ruby1.8 +#!/usr/bin/env ruby require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) diff --git a/public/dispatch.fcgi b/public/dispatch.fcgi index 3169ba26..664dbbbe 100755 --- a/public/dispatch.fcgi +++ b/public/dispatch.fcgi @@ -1,4 +1,4 @@ -#!/usr/bin/ruby1.8 +#!/usr/bin/env ruby # # You may specify the path to the FastCGI crash log (a log of unhandled # exceptions which forced the FastCGI instance to exit, great for debugging) diff --git a/public/dispatch.rb b/public/dispatch.rb index 7095803c..ce705d36 100755 --- a/public/dispatch.rb +++ b/public/dispatch.rb @@ -1,4 +1,4 @@ -#!e:/ruby/bin/ruby +#!/usr/bin/env ruby require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) diff --git a/script/benchmarker b/script/benchmarker index 2e323b66..4a0ea231 100755 --- a/script/benchmarker +++ b/script/benchmarker @@ -1,4 +1,4 @@ -#!/usr/bin/ruby1.8 +#!/usr/bin/env ruby if ARGV.empty? puts "Usage: benchmarker times 'Person.expensive_way' 'Person.another_expensive_way' ..." diff --git a/script/breakpointer b/script/breakpointer index 89e286dd..6375616b 100755 --- a/script/breakpointer +++ b/script/breakpointer @@ -1,4 +1,4 @@ -#!e:/ruby/bin/ruby +#!/usr/bin/env ruby require 'rubygems' require_gem 'rails' require 'breakpoint_client' diff --git a/script/console b/script/console index eece24a9..e23abda3 100755 --- a/script/console +++ b/script/console @@ -1,4 +1,4 @@ -#!/usr/local/bin/ruby +#!/usr/bin/env ruby irb = RUBY_PLATFORM =~ /mswin32/ ? 'irb.bat' : 'irb' require 'optparse' diff --git a/script/destroy b/script/destroy index 624049da..46cc786e 100755 --- a/script/destroy +++ b/script/destroy @@ -1,4 +1,4 @@ -#!/usr/bin/ruby1.8 +#!/usr/bin/env ruby require File.dirname(__FILE__) + '/../config/environment' require 'rails_generator' require 'rails_generator/scripts/destroy' diff --git a/script/generate b/script/generate index a104fc94..26447804 100755 --- a/script/generate +++ b/script/generate @@ -1,4 +1,4 @@ -#!/usr/bin/ruby1.8 +#!/usr/bin/env ruby require File.dirname(__FILE__) + '/../config/environment' require 'rails_generator' require 'rails_generator/scripts/generate' diff --git a/script/profiler b/script/profiler index 92cf8247..77c9fbef 100755 --- a/script/profiler +++ b/script/profiler @@ -1,4 +1,4 @@ -#!/usr/bin/ruby1.8 +#!/usr/bin/env ruby if ARGV.empty? $stderr.puts "Usage: profiler 'Person.expensive_method(10)' [times]" exit(1) diff --git a/script/runner b/script/runner index a9f705c1..9c8bbb13 100755 --- a/script/runner +++ b/script/runner @@ -1,4 +1,4 @@ -#!/usr/bin/ruby1.8 +#!/usr/bin/env ruby require 'optparse' options = { :environment => "development" } @@ -23,7 +23,7 @@ end ENV["RAILS_ENV"] = options[:environment] -#!/usr/local/bin/ruby +#!/usr/bin/env ruby require File.dirname(__FILE__) + '/../config/environment' eval(ARGV.first) \ No newline at end of file diff --git a/script/server b/script/server index c6bdbb6f..0044d3ef 100755 --- a/script/server +++ b/script/server @@ -1,4 +1,4 @@ -#!/usr/local/bin/ruby +#!/usr/bin/env ruby require 'webrick' require 'optparse' diff --git a/test/functional/admin_controller_test.rb b/test/functional/admin_controller_test.rb index a24cf8c0..3270b08e 100644 --- a/test/functional/admin_controller_test.rb +++ b/test/functional/admin_controller_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby +#!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../test_helper') require 'admin_controller' diff --git a/test/functional/file_controller_test.rb b/test/functional/file_controller_test.rb index cea3aa81..77040c60 100755 --- a/test/functional/file_controller_test.rb +++ b/test/functional/file_controller_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby +#!/usr/bin/env ruby require File.dirname(__FILE__) + '/../test_helper' require 'file_controller' diff --git a/test/functional/routes_test.rb b/test/functional/routes_test.rb index c2334ef7..b07a6438 100644 --- a/test/functional/routes_test.rb +++ b/test/functional/routes_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby +#!/usr/bin/env ruby require File.dirname(__FILE__) + '/../test_helper' diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index b799ad3b..984bf4cd 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby +#!/usr/bin/env ruby # Uncomment the line below to enable pdflatex tests; don't forget to comment them again # commiting to SVN diff --git a/test/unit/chunks/category_test.rb b/test/unit/chunks/category_test.rb index 163075f0..6bc7627f 100755 --- a/test/unit/chunks/category_test.rb +++ b/test/unit/chunks/category_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby +#!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../test_helper' require 'chunks/category' diff --git a/test/unit/chunks/nowiki_test.rb b/test/unit/chunks/nowiki_test.rb index 2fcad789..8af5a645 100755 --- a/test/unit/chunks/nowiki_test.rb +++ b/test/unit/chunks/nowiki_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby +#!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../test_helper' require 'chunks/nowiki' diff --git a/test/unit/chunks/wiki_test.rb b/test/unit/chunks/wiki_test.rb index b73a88cc..09871729 100755 --- a/test/unit/chunks/wiki_test.rb +++ b/test/unit/chunks/wiki_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby +#!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../test_helper' require 'chunks/wiki' diff --git a/test/unit/diff_test.rb b/test/unit/diff_test.rb index 90d0e79c..9981305e 100755 --- a/test/unit/diff_test.rb +++ b/test/unit/diff_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby +#!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../test_helper') require 'diff' diff --git a/test/unit/file_yard_test.rb b/test/unit/file_yard_test.rb index 9d76bd7c..b1859ec2 100755 --- a/test/unit/file_yard_test.rb +++ b/test/unit/file_yard_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby +#!/usr/bin/env ruby require File.dirname(__FILE__) + '/../test_helper' require 'fileutils' diff --git a/test/unit/redcloth_for_tex_test.rb b/test/unit/redcloth_for_tex_test.rb index d15071a0..3556beaf 100755 --- a/test/unit/redcloth_for_tex_test.rb +++ b/test/unit/redcloth_for_tex_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby +#!/usr/bin/env ruby require File.dirname(__FILE__) + '/../test_helper' require 'redcloth_for_tex' diff --git a/test/unit/uri_test.rb b/test/unit/uri_test.rb index 4affbd60..29326c3b 100755 --- a/test/unit/uri_test.rb +++ b/test/unit/uri_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby +#!/usr/bin/env ruby require File.dirname(__FILE__) + '/../test_helper' require 'chunks/uri' diff --git a/test/unit/wiki_words_test.rb b/test/unit/wiki_words_test.rb index 93bc5d12..f90a8d12 100755 --- a/test/unit/wiki_words_test.rb +++ b/test/unit/wiki_words_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby +#!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../test_helper') require 'wiki_words' From 4f7e5bca30a10320a33501ad8148331c9da70653 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 14 Aug 2005 04:06:31 +0000 Subject: [PATCH 23/84] Changes in Watir test to accomodate the AR backend --- test/watir/e2e.rb | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/test/watir/e2e.rb b/test/watir/e2e.rb index 90cb9af3..8db2c36e 100644 --- a/test/watir/e2e.rb +++ b/test/watir/e2e.rb @@ -307,9 +307,9 @@ class InstikiController startup_info = [68].pack('lx64') process_info = [0, 0, 0, 0].pack('llll') + clear_database startup_command = - "ruby #{RAILS_ROOT}/instiki.rb --storage #{prepare_storage} " + - " --port #{INSTIKI_PORT} --environment development" + "ruby #{RAILS_ROOT}/instiki.rb --port #{INSTIKI_PORT} --environment development" result = Win32API.new('kernel32.dll', 'CreateProcess', 'pplllllppp', 'L').call( nil, @@ -323,11 +323,10 @@ class InstikiController return self.new(process_id) end - def self.prepare_storage - storage_path = INSTIKI_ROOT + '/storage/e2e' - FileUtils.rm_rf(storage_path) if File.exists? storage_path - FileUtils.mkdir_p(storage_path) - storage_path + def self.clear_database + ENV['RAILS_ENV'] = 'development' + require INSTIKI_ROOT + '/config/environment.rb' + [Revision, Page, Web, System].each { |entity| entity.delete_all } end def initialize(pid) From b4ae0b3065132ed1ea7c90e5bc892cfe1550ac2c Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 14 Aug 2005 04:42:19 +0000 Subject: [PATCH 24/84] Moving back some model classes to where they belong. Model classes need not map to database entities to be model classes --- {lib => app/models}/author.rb | 0 {lib => app/models}/page_set.rb | 0 {lib => app/models}/wiki.rb | 0 script/server | 4 ++-- 4 files changed, 2 insertions(+), 2 deletions(-) rename {lib => app/models}/author.rb (100%) rename {lib => app/models}/page_set.rb (100%) rename {lib => app/models}/wiki.rb (100%) diff --git a/lib/author.rb b/app/models/author.rb similarity index 100% rename from lib/author.rb rename to app/models/author.rb diff --git a/lib/page_set.rb b/app/models/page_set.rb similarity index 100% rename from lib/page_set.rb rename to app/models/page_set.rb diff --git a/lib/wiki.rb b/app/models/wiki.rb similarity index 100% rename from lib/wiki.rb rename to app/models/wiki.rb diff --git a/script/server b/script/server index 0044d3ef..27a7989a 100755 --- a/script/server +++ b/script/server @@ -21,13 +21,13 @@ ARGV.options do |opts| "Runs Instiki on the specified port.", "Default: 2500") { |OPTIONS[:port]| } opts.on("-b", "--binding=ip", String, - "Binds Rails to the specified ip.", + "Binds Instiki to the specified ip.", "Default: 0.0.0.0") { |OPTIONS[:ip]| } opts.on("-e", "--environment=name", String, "Specifies the environment to run this server under (test/development/production).", "Default: development") { |OPTIONS[:environment]| } opts.on("-d", "--daemon", - "Make Rails run as a Daemon (only works if fork is available -- meaning on *nix)." + "Make Instiki run as a Daemon (only works if fork is available -- meaning on *nix)." ) { OPTIONS[:server_type] = WEBrick::Daemon } opts.separator "" From 476d7810f6ee7049165d00f4c3ba66b92fd33564 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 14 Aug 2005 18:58:36 +0000 Subject: [PATCH 25/84] Added Revision.timestamp attribute, which is a timestamp to the precision of msec. Intention is to get rid of the revision.number and use this one for sotrting etc. The problem with created_at / updated_at in this role is that trhey have precision of seconds, which is not good enough for some of the purposes. --- app/models/revision.rb | 9 ++++++++- db/revisions.erbsql | 2 ++ test/fixtures/revisions.yml | 10 +++++++++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/app/models/revision.rb b/app/models/revision.rb index ca886245..9d2c0ba1 100644 --- a/app/models/revision.rb +++ b/app/models/revision.rb @@ -112,11 +112,18 @@ class Revision < ActiveRecord::Base end protected - before_create :set_revision_number + before_create :set_revision_number, :set_timestamp after_create :force_rendering after_save :clear_display_cache + # TODO Refactor this away. Revisions collection should not rely on the revision number for + # sorting etc - revisions must be easy to delete (this helps fighting wiki spam) def set_revision_number self.number = self.class.count(['page_id = ?', page_id]) + 1 end + + def set_timestamp + self.timestamp = (Time.now.to_f * 1000).to_i.to_s + end + end diff --git a/db/revisions.erbsql b/db/revisions.erbsql index 28034555..e8461d2b 100644 --- a/db/revisions.erbsql +++ b/db/revisions.erbsql @@ -2,6 +2,8 @@ CREATE TABLE revisions ( id <%= @pk %>, created_at <%= @datetime %> NOT NULL, updated_at <%= @datetime %> NOT NULL, + + timestamp CHAR(13) NOT NULL, page_id INTEGER NOT NULL, content TEXT NOT NULL, author VARCHAR(60), diff --git a/test/fixtures/revisions.yml b/test/fixtures/revisions.yml index eb5eff5b..d0491c9e 100644 --- a/test/fixtures/revisions.yml +++ b/test/fixtures/revisions.yml @@ -2,6 +2,7 @@ home_page_first_revision: id: 1 created_at: <%= Time.local(2004, 4, 4, 15, 50).to_formatted_s(:db) %> updated_at: <%= Time.local(2004, 4, 4, 15, 50).to_formatted_s(:db) %> + timestamp: <%= (Time.local(2004, 4, 4, 15, 50).to_f * 1000).to_i %> page_id: 1 number: 1 content: First revision of the HomePage end @@ -12,6 +13,7 @@ my_way_first_revision: id: 2 created_at: <%= 9.days.ago.to_formatted_s(:db) %> updated_at: <%= 9.days.ago.to_formatted_s(:db) %> + timestamp: <%= (9.days.ago.to_f * 1000).to_i %> page_id: 2 number: 1 content: MyWay @@ -21,6 +23,7 @@ smart_engine_first_revision: id: 3 created_at: <%= 8.days.ago.to_formatted_s(:db) %> updated_at: <%= 8.days.ago.to_formatted_s(:db) %> + timestamp: <%= (8.days.ago.to_f * 1000).to_i %> page_id: 3 number: 1 content: SmartEngine @@ -30,6 +33,7 @@ that_way_first_revision: id: 4 created_at: <%= 7.days.ago.to_formatted_s(:db) %> updated_at: <%= 7.days.ago.to_formatted_s(:db) %> + timestamp: <%= (7.days.ago.to_f * 1000).to_i %> page_id: 4 number: 1 content: ThatWay @@ -39,6 +43,7 @@ no_wiki_word_first_revision: id: 5 created_at: <%= 6.days.ago.to_formatted_s(:db) %> updated_at: <%= 6.days.ago.to_formatted_s(:db) %> + timestamp: <%= (6.days.ago.to_f * 1000).to_i %> page_id: 5 number: 1 content: hey you @@ -48,6 +53,7 @@ home_page_second_revision: id: 6 created_at: <%= Time.local(2004, 4, 4, 16, 50).to_formatted_s(:db) %> updated_at: <%= Time.local(2004, 4, 4, 16, 50).to_formatted_s(:db) %> + timestamp: <%= (Time.local(2004, 4, 4, 16, 50).to_f * 1000).to_i %> page_id: 1 number: 2 content: HisWay would be MyWay in kinda ThatWay in HisWay though MyWay \OverThere -- see SmartEngine in that SmartEngineGUI @@ -57,6 +63,7 @@ first_page_first_revision: id: 7 created_at: <%= Time.local(2004, 4, 4, 16, 55).to_formatted_s(:db) %> updated_at: <%= Time.local(2004, 4, 4, 16, 55).to_formatted_s(:db) %> + timestamp: <%= (Time.local(2004, 4, 4, 16, 55).to_f * 1000).to_i %> page_id: 6 number: 1 content: HisWay would be MyWay in kinda ThatWay in HisWay though MyWay \\OverThere -- see SmartEngine in that SmartEngineGUI @@ -66,6 +73,7 @@ oak_first_revision: id: 8 created_at: <%= 5.days.ago.to_formatted_s(:db) %> updated_at: <%= 5.days.ago.to_formatted_s(:db) %> + timestamp: <%= (5.days.ago.to_f * 1000).to_i %> page_id: 7 number: 1 content: "All about oak.\ncategory: trees" @@ -76,9 +84,9 @@ elephant_first_revision: id: 9 created_at: <%= 10.minutes.ago.to_formatted_s(:db) %> updated_at: <%= 10.minutes.ago.to_formatted_s(:db) %> + timestamp: <%= (10.minutes.ago.to_f * 1000).to_i %> page_id: 8 number: 1 content: "All about elephants.\ncategory: animals" author: Guest ip: 127.0.0.2 - From 052754b068aedca032de5a72d570cbc0913fc061 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 14 Aug 2005 22:26:54 +0000 Subject: [PATCH 26/84] Dropped number from revision table. Also dropped timestamp. We will rely on autoincremented ID for sorting, and will for now store the time of last edit of the revision in revised_at. Later we will refactor content into a separate table (so as not to load the whole 300 kb of text and cached HTML every time we need page.revisions in code). Rake tests all pass, but watir tests indicate that some revision traversing links are still broken --- app/controllers/wiki_controller.rb | 9 ++--- app/models/page.rb | 44 ++++++++++++++++--------- app/models/page_set.rb | 5 ++- app/models/revision.rb | 43 ++++++++---------------- app/models/web.rb | 4 +-- app/models/wiki.rb | 4 +-- app/views/wiki/page.rhtml | 4 +-- app/views/wiki/print.rhtml | 2 +- app/views/wiki/recently_revised.rhtml | 2 +- app/views/wiki/revision.rhtml | 26 +++++++-------- app/views/wiki/rollback.rhtml | 2 +- app/views/wiki/rss_feed.rhtml | 2 +- db/revisions.erbsql | 10 ++++-- test/fixtures/revisions.yml | 27 +++++---------- test/functional/wiki_controller_test.rb | 4 +-- test/unit/page_test.rb | 32 +++++++++++------- test/unit/revision_test.rb | 6 ++-- test/watir/e2e.rb | 2 +- 18 files changed, 116 insertions(+), 112 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index c274e209..3e43eaf6 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -140,7 +140,7 @@ class WikiController < ApplicationController def pdf page = wiki.read_page(@web_name, @page_name) safe_page_name = @page.name.gsub(/\W/, '') - file_name = "#{safe_page_name}-#{@web.address}-#{@page.created_at.strftime('%Y-%m-%d-%H-%M-%S')}" + file_name = "#{safe_page_name}-#{@web.address}-#{@page.revised_on.strftime('%Y-%m-%d-%H-%M-%S')}" file_path = File.join(@wiki.storage_path, file_name) export_page_to_tex("#{file_path}.tex") unless FileTest.exists?("#{file_path}.tex") @@ -274,7 +274,8 @@ class WikiController < ApplicationController end def get_page_and_revision - @revision = @page.revisions[@params['rev'].to_i] + @revision_number = @params['rev'].to_i + @revision = @page.revisions[@revision_number] end def parse_category @@ -312,8 +313,8 @@ class WikiController < ApplicationController @pages_by_revision = @web.select.by_revision.first(limit) else @pages_by_revision = @web.select.by_revision - @pages_by_revision.reject! { |page| page.created_at < start_date } if start_date - @pages_by_revision.reject! { |page| page.created_at > end_date } if end_date + @pages_by_revision.reject! { |page| page.revised_on < start_date } if start_date + @pages_by_revision.reject! { |page| page.revised_on > end_date } if end_date end @hide_description = hide_description diff --git a/app/models/page.rb b/app/models/page.rb index 6baa5802..97f11879 100644 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -1,9 +1,9 @@ class Page < ActiveRecord::Base belongs_to :web - has_many :revisions, :order => 'number' - has_one :current_revision, :class_name => 'Revision', :order => 'number DESC' + has_many :revisions, :order => 'id' + has_one :current_revision, :class_name => 'Revision', :order => 'id DESC' - def revise(content, created_at, author) + def revise(content, time, author) revisions_size = new_record? ? 0 : revisions.size if (revisions_size > 0) and content == current_revision.content raise Instiki::ValidationError.new( @@ -13,37 +13,49 @@ class Page < ActiveRecord::Base author = Author.new(author.to_s) unless author.is_a?(Author) # Try to render content to make sure that markup engine can take it, - # before addin a revision to the page - Revision.new(:page => self, :content => content, :created_at => created_at, :author => author).force_rendering + Revision.new(:page => self, :content => content, :author => author, :revised_at => time).force_rendering # A user may change a page, look at it and make some more changes - several times. # Not to record every such iteration as a new revision, if the previous revision was done # by the same author, not more than 30 minutes ago, then update the last revision instead of # creating a new one - if (revisions_size > 0) && continous_revision?(created_at, author) - current_revision.update_attributes(:created_at => created_at, :content => content) + if (revisions_size > 1) && continous_revision?(time, author) + current_revision.update_attributes(:content => content, :revised_at => time) else - Revision.create(:page => self, :content => content, :created_at => created_at, :author => author) + Revision.create(:page => self, :content => content, :author => author, :revised_at => time) end - self.created_at = created_at save web.refresh_pages_with_references(name) if revisions_size == 0 self end - def rollback(revision_number, created_at, author_ip = nil) - roll_back_revision = Revision.find(:first, :conditions => ['page_id = ? AND number = ?', id, revision_number]) - revise(roll_back_revision.content, created_at, Author.new(roll_back_revision.author, author_ip)) + def rollback(revision_number, time, author_ip = nil) + roll_back_revision = self.revisions[revision_number] + if roll_back_revision.nil? + raise Instiki::ValidationError.new("Revision #{revision_number} not found") + end + revise(roll_back_revision.content, time, Author.new(roll_back_revision.author, author_ip)) end def revisions? revisions.size > 1 end - def revised_on - created_on + def previous_revision(revision) + revision_index = revisions.each_with_index do |rev, index| + if rev.id == revision.id + break index + else + nil + end + end + if revision_index.nil? or revision_index == 0 + nil + else + revisions[revision_index - 1] + end end def in_category?(cat) @@ -108,8 +120,8 @@ class Page < ActiveRecord::Base private - def continous_revision?(created_at, author) - current_revision.author == author && current_revision.created_at + 30.minutes > created_at + def continous_revision?(time, author) + (current_revision.author == author) && (revised_on + 30.minutes > time) end # Forward method calls to the current revision, so the page responds to all revision calls diff --git a/app/models/page_set.rb b/app/models/page_set.rb index c50abe80..47e1d4e9 100644 --- a/app/models/page_set.rb +++ b/app/models/page_set.rb @@ -17,10 +17,9 @@ class PageSet < Array end def most_recent_revision - self.map { |page| page.created_at }.max || Time.at(0) + self.map { |page| page.revised_on }.max || Time.at(0) end - def by_name PageSet.new(@web, sort_by { |page| page.name }) end @@ -28,7 +27,7 @@ class PageSet < Array alias :sort :by_name def by_revision - PageSet.new(@web, sort_by { |page| page.created_at }).reverse + PageSet.new(@web, sort_by { |page| page.revised_on }).reverse end def pages_that_reference(page_name) diff --git a/app/models/revision.rb b/app/models/revision.rb index 9d2c0ba1..9eecbcf1 100644 --- a/app/models/revision.rb +++ b/app/models/revision.rb @@ -3,24 +3,13 @@ class Revision < ActiveRecord::Base belongs_to :page composed_of :author, :mapping => [ %w(author name), %w(ip ip) ] - def created_on - created_at.to_date + def revised_on + revised_at end - def pretty_created_at - # Must use DateTime because Time doesn't support %e on at least some platforms - DateTime.new( - created_at.year, created_at.mon, created_at.day, created_at.hour, created_at.min - ).strftime "%B %e, %Y %H:%M" - end - - # todo: drop next_revision, previuous_revision and number from here - unused code - def next_revision - Revision.find_by_number_and_page_id(number+1, page_id) - end - - def previous_revision - @previous_revions ||= number > 0 ? Revision.find_by_number_and_page_id(number-1, page_id) : nil + # TODO this method belongs in the view helpers (only views use it) + def pretty_created_on + revised_on.to_date.strftime "%B %e, %Y %H:%M:%S" end # Returns an array of all the WikiIncludes present in the content of this revision. @@ -30,7 +19,7 @@ class Revision < ActiveRecord::Base @wiki_includes_cache = chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq end @wiki_includes_cache - end + end # Returns an array of all the WikiReferences present in the content of this revision. def wiki_references @@ -72,8 +61,14 @@ class Revision < ActiveRecord::Base @display_cache end + # TODO this probably doesn't belong in revision (because it has to call back the page) def display_diff - previous_revision ? HTMLDiff.diff(previous_revision.display_content, display_content) : display_content + previous_revision = page.previous_revision(self) + if previous_revision + HTMLDiff.diff(previous_revision.display_content, display_content) + else + display_content + end end def clear_display_cache @@ -112,18 +107,8 @@ class Revision < ActiveRecord::Base end protected - before_create :set_revision_number, :set_timestamp + after_create :force_rendering after_save :clear_display_cache - - # TODO Refactor this away. Revisions collection should not rely on the revision number for - # sorting etc - revisions must be easy to delete (this helps fighting wiki spam) - def set_revision_number - self.number = self.class.count(['page_id = ?', page_id]) + 1 - end - - def set_timestamp - self.timestamp = (Time.now.to_f * 1000).to_i.to_s - end end diff --git a/app/models/web.rb b/app/models/web.rb index 5ca077d5..a568e6a1 100644 --- a/app/models/web.rb +++ b/app/models/web.rb @@ -17,9 +17,9 @@ class Web < ActiveRecord::Base self.brackets_only != brackets_only end - def add_page(name, content, created_at, author) + def add_page(name, content, time, author) page = page(name) || Page.new(:web => self, :name => name) - page.revise(content, created_at, author) + page.revise(content, time, author) end def authors diff --git a/app/models/wiki.rb b/app/models/wiki.rb index 50093193..e1390e31 100644 --- a/app/models/wiki.rb +++ b/app/models/wiki.rb @@ -64,9 +64,9 @@ class Wiki page.revise(content, revised_on, author) end - def rollback_page(web_address, page_name, revision_number, created_at, author_id = nil) + def rollback_page(web_address, page_name, revision_number, time, author_id = nil) page = read_page(web_address, page_name) - page.rollback(revision_number, created_at, author_id) + page.rollback(revision_number, time, author_id) end def setup(password, web_name, web_address) diff --git a/app/views/wiki/page.rhtml b/app/views/wiki/page.rhtml index 28763b78..a48cb7c3 100644 --- a/app/views/wiki/page.rhtml +++ b/app/views/wiki/page.rhtml @@ -12,7 +12,7 @@
    -Or "download RedCloth":http://rubyforge.org/frs/download.php/2852/RedCloth-3.0.2.tar.gz and simply run the install.rb like so: +Or "download RedCloth":http://rubyforge.org/frs/download.php/2896/RedCloth-3.0.3.tar.gz and simply run the install.rb like so:
       ruby install.rb config
    diff --git a/vendor/RedCloth-3.0.3/doc/REFERENCE b/vendor/RedCloth-3.0.4/doc/REFERENCE
    similarity index 98%
    rename from vendor/RedCloth-3.0.3/doc/REFERENCE
    rename to vendor/RedCloth-3.0.4/doc/REFERENCE
    index a3e5bec0..c7236e89 100644
    --- a/vendor/RedCloth-3.0.3/doc/REFERENCE
    +++ b/vendor/RedCloth-3.0.4/doc/REFERENCE
    @@ -24,9 +24,9 @@
         if it's found in a @pre@ or @code@ block.
       - !!example "I am very serious.\n\n
    \n  I am very serious.\n
    " - h4. Line Breaks - - Line breaks are converted to HTML breaks. + - Line breaks are ignored. - !!example "I spoke.\nAnd none replied." - - Line breaks can be disabled in RedCloth by turning on @fold_lines@. + - Line breaks can be converted to HTML breaks by setting @hard_breaks@. - h4. Entities - Single- and double-quotes around words or phrases are converted to curly quotations, much easier on the eye. - !!example '"Observe!"' @@ -78,9 +78,9 @@ - Pluses around a passage indicate its insertion. - !!example "You are a +pleasant+ child." - To superscript a phrase, surround with carets. - - !!example "a ^2^ + b ^2^ = c ^2^" + - !!example "a^2^ + b^2^ = c^2^" - To subscript, surround with tildes. - - !!example "log ~2~ x" + - !!example "log~2~x" - h4. HTML-Specific - Lastly, if you find yourself needing to customize the style of a passage, use percent symbols to translate the passage as an HTML span. diff --git a/vendor/RedCloth-3.0.3/doc/make.rb b/vendor/RedCloth-3.0.4/doc/make.rb similarity index 100% rename from vendor/RedCloth-3.0.3/doc/make.rb rename to vendor/RedCloth-3.0.4/doc/make.rb diff --git a/vendor/RedCloth-3.0.3/lib/redcloth.rb b/vendor/RedCloth-3.0.4/lib/redcloth.rb similarity index 94% rename from vendor/RedCloth-3.0.3/lib/redcloth.rb rename to vendor/RedCloth-3.0.4/lib/redcloth.rb index 03df12b3..1228af6e 100644 --- a/vendor/RedCloth-3.0.3/lib/redcloth.rb +++ b/vendor/RedCloth-3.0.4/lib/redcloth.rb @@ -166,7 +166,7 @@ class RedCloth < String - VERSION = '3.0.3' + VERSION = '3.0.4' DEFAULT_RULES = [:textile, :markdown] # @@ -193,6 +193,18 @@ class RedCloth < String # attr_accessor :hard_breaks + # Accessor for toggling lite mode. + # + # In lite mode, block-level rules are ignored. This means + # that tables, paragraphs, lists, and such aren't available. + # Only the inline markup for bold, italics, entities and so on. + # + # r = RedCloth.new( "And then? She *fell*!", [:lite_mode] ) + # r.to_html + # #=> "And then? She fell!" + # + attr_accessor :lite_mode + # # Accessor for toggling span caps. # @@ -219,7 +231,7 @@ class RedCloth < String # inline_textile_image:: Textile inline images # inline_textile_link:: Textile inline links # inline_textile_span:: Textile inline spans - # inline_textile_glyphs:: Textile entities (such as em-dashes and smart quotes) + # glyphs_textile:: Textile entities (such as em-dashes and smart quotes) # # == Markdown # @@ -260,7 +272,7 @@ class RedCloth < String @shelf = [] textile_rules = [:refs_textile, :block_textile_table, :block_textile_lists, :block_textile_prefix, :inline_textile_image, :inline_textile_link, - :inline_textile_code, :inline_textile_glyphs, :inline_textile_span] + :inline_textile_code, :inline_textile_span, :glyphs_textile] markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule, :block_markdown_bq, :block_markdown_lists, :inline_markdown_reflink, :inline_markdown_link] @@ -278,14 +290,16 @@ class RedCloth < String # standard clean up incoming_entities text clean_white_space text - no_textile text # start processor @pre_list = [] rip_offtags text - hard_break text - refs text - blocks text + no_textile text + hard_break text + unless @lite_mode + refs text + blocks text + end inline text smooth_offtags text @@ -333,6 +347,8 @@ class RedCloth < String C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)" # PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' ) PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' ) + PUNCT_NOQ = Regexp::quote( '!"#$&\',./:;=?@\\`|' ) + PUNCT_Q = Regexp::quote( '*-_+^~%' ) HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)' # Text markup tags, don't conflict with block tags @@ -342,41 +358,6 @@ class RedCloth < String 'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo' ] - # Elements to handle - GLYPHS = [ - # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1’\2' ], # single closing - [ /([^\s\[{(>])\'/, '\1’' ], # single closing - [ /\'(?=\s|s\b|[#{PUNCT}])/, '’' ], # single closing - [ /\'/, '‘' ], # single opening - # [ /([^\s\[{(])?"(\s|:|$)/, '\1”\2' ], # double closing - [ /([^\s\[{(>])"/, '\1”' ], # double closing - [ /"(?=\s|[#{PUNCT}])/, '”' ], # double closing - [ /"/, '“' ], # double opening - [ /\b( )?\.{3}/, '\1…' ], # ellipsis - [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '\1' ], # 3+ uppercase acronym - [ /(^|[^"][>\s])([A-Z][A-Z0-9 ]{2,})([^\2\3', :no_span_caps ], # 3+ uppercase caps - [ /(\.\s)?\s?--\s?/, '\1—' ], # em dash - [ /\s->\s/, ' → ' ], # right arrow - [ /\s-\s/, ' – ' ], # en dash - [ /(\d+) ?x ?(\d+)/, '\1×\2' ], # dimension sign - [ /\b ?[(\[]TM[\])]/i, '™' ], # trademark - [ /\b ?[(\[]R[\])]/i, '®' ], # registered - [ /\b ?[(\[]C[\])]/i, '©' ] # copyright - ] - - H_ALGN_VALS = { - '<' => 'left', - '=' => 'center', - '>' => 'right', - '<>' => 'justify' - } - - V_ALGN_VALS = { - '^' => 'top', - '-' => 'middle', - '~' => 'bottom' - } - QTAGS = [ ['**', 'b'], ['*', 'strong'], @@ -398,19 +379,56 @@ class RedCloth < String (#{rcq}) (#{C}) (?::(\S+?))? - (.+?) + (\S.*?\S|\S) #{rcq} (?=\W)/x else /(#{rcq}) (#{C}) - (?::(\S+?))? - (.+?) + (?::(\S+))? + (\S.*?\S|\S) #{rcq}/xm end [rc, ht, re, rtype] end + # Elements to handle + GLYPHS = [ + # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1’\2' ], # single closing + [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1’' ], # single closing + [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '’' ], # single closing + [ /\'/, '‘' ], # single opening + [ //, '>' ], # greater-than + # [ /([^\s\[{(])?"(\s|:|$)/, '\1”\2' ], # double closing + [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1”' ], # double closing + [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '”' ], # double closing + [ /"/, '“' ], # double opening + [ /\b( )?\.{3}/, '\1…' ], # ellipsis + [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '\1' ], # 3+ uppercase acronym + [ /(^|[^"][>\s])([A-Z][A-Z0-9 ]+[A-Z0-9])([^\2\3', :no_span_caps ], # 3+ uppercase caps + [ /(\.\s)?\s?--\s?/, '\1—' ], # em dash + [ /\s->\s/, ' → ' ], # right arrow + [ /\s-\s/, ' – ' ], # en dash + [ /(\d+) ?x ?(\d+)/, '\1×\2' ], # dimension sign + [ /\b ?[(\[]TM[\])]/i, '™' ], # trademark + [ /\b ?[(\[]R[\])]/i, '®' ], # registered + [ /\b ?[(\[]C[\])]/i, '©' ] # copyright + ] + + H_ALGN_VALS = { + '<' => 'left', + '=' => 'center', + '>' => 'right', + '<>' => 'justify' + } + + V_ALGN_VALS = { + '^' => 'top', + '-' => 'middle', + '~' => 'bottom' + } + # # Flexible HTML escaping # @@ -530,7 +548,7 @@ class RedCloth < String depth.pop end end - if depth.last.length == tl.length + if depth.last and depth.last.length == tl.length lines[line_id - 1] << '' end end @@ -577,7 +595,7 @@ class RedCloth < String end def hard_break( text ) - text.gsub!( /(.)\n(?! *[#*\s|]|$)/, "\\1
    " ) if hard_breaks + text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1
    " ) if hard_breaks end BLOCKS_GROUP_RE = /\n{2,}(?! )/m @@ -705,9 +723,9 @@ class RedCloth < String end end - MARKDOWN_RULE_RE = /^#{ + MARKDOWN_RULE_RE = /^(#{ ['*', '-', '_'].collect { |ch| '( ?' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' ) - }$/ + })$/ def block_markdown_rule( text ) text.gsub!( MARKDOWN_RULE_RE ) do |blk| @@ -719,9 +737,6 @@ class RedCloth < String def block_markdown_lists( text ) end - def inline_markdown_link( text ) - end - def inline_textile_span( text ) QTAGS.each do |qtag_rc, ht, qtag_re, rtype| text.gsub!( qtag_re ) do |m| @@ -903,12 +918,12 @@ class RedCloth < String def shelve( val ) @shelf << val - " <#{ @shelf.length }>" + " :redsh##{ @shelf.length }:" end def retrieve( text ) @shelf.each_with_index do |r, i| - text.gsub!( " <#{ i + 1 }>", r ) + text.gsub!( " :redsh##{ i + 1 }:", r ) end end @@ -965,7 +980,7 @@ class RedCloth < String HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m - def inline_textile_glyphs( text, level = 0 ) + def glyphs_textile( text, level = 0 ) if text !~ HASTAG_MATCH pgl text footnote_ref text @@ -981,11 +996,11 @@ class RedCloth < String codepre = 0 if codepre < 0 end elsif codepre.zero? - inline_textile_glyphs( line, level + 1 ) + glyphs_textile( line, level + 1 ) else htmlesc( line, :NoQuotes ) end - ## p [level, codepre, orig_line, line] + # p [level, codepre, line] line end @@ -1033,8 +1048,10 @@ class RedCloth < String end def inline( text ) - @rules.each do |rule_name| - method( rule_name ).call( text ) if rule_name.to_s.match /^inline_/ + [/^inline_/, /^glyphs_/].each do |meth_re| + @rules.each do |rule_name| + method( rule_name ).call( text ) if rule_name.to_s.match( meth_re ) + end end end @@ -1097,7 +1114,7 @@ class RedCloth < String q2 = ( q != '' ? q : '\s' ) if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i attrv = $1 - next if prop == 'src' and attrv !~ /^http/ + next if prop == 'src' and attrv =~ %r{^(?!http)\w+:} pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\"" break end diff --git a/vendor/RedCloth-3.0.3/run-tests.rb b/vendor/RedCloth-3.0.4/run-tests.rb similarity index 83% rename from vendor/RedCloth-3.0.3/run-tests.rb rename to vendor/RedCloth-3.0.4/run-tests.rb index 65b5c969..1f267b64 100644 --- a/vendor/RedCloth-3.0.3/run-tests.rb +++ b/vendor/RedCloth-3.0.4/run-tests.rb @@ -5,7 +5,9 @@ require 'yaml' Dir["tests/*.yml"].each do |testfile| YAML::load_documents( File.open( testfile ) ) do |doc| if doc['in'] and doc['out'] - red = RedCloth.new( doc['in'] ) + opts = [] + opts << :hard_breaks if testfile =~ /hard_breaks/ + red = RedCloth.new( doc['in'], opts ) html = if testfile =~ /markdown/ red.to_html( :markdown ) else diff --git a/vendor/RedCloth-3.0.4/setup.rb b/vendor/RedCloth-3.0.4/setup.rb new file mode 100644 index 00000000..462522b5 --- /dev/null +++ b/vendor/RedCloth-3.0.4/setup.rb @@ -0,0 +1,1376 @@ +# +# setup.rb +# +# Copyright (c) 2000-2004 Minero Aoki +# +# This program is free software. +# You can distribute/modify this program under the terms of +# the GNU LGPL, Lesser General Public License version 2.1. +# + +unless Enumerable.method_defined?(:map) # Ruby 1.4.6 + module Enumerable + alias map collect + end +end + +unless File.respond_to?(:read) # Ruby 1.6 + def File.read(fname) + open(fname) {|f| + return f.read + } + end +end + +def File.binread(fname) + open(fname, 'rb') {|f| + return f.read + } +end + +# for corrupted windows stat(2) +def File.dir?(path) + File.directory?((path[-1,1] == '/') ? path : path + '/') +end + + +class SetupError < StandardError; end + +def setup_rb_error(msg) + raise SetupError, msg +end + +# +# Config +# + +if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } + ARGV.delete(arg) + require arg.split(/=/, 2)[1] + $".push 'rbconfig.rb' +else + require 'rbconfig' +end + +def multipackage_install? + FileTest.directory?(File.dirname($0) + '/packages') +end + + +class ConfigItem + def initialize(name, template, default, desc) + @name = name.freeze + @template = template + @value = default + @default = default.dup.freeze + @description = desc + end + + attr_reader :name + attr_reader :description + + attr_accessor :default + alias help_default default + + def help_opt + "--#{@name}=#{@template}" + end + + def value + @value + end + + def eval(table) + @value.gsub(%r<\$([^/]+)>) { table[$1] } + end + + def set(val) + @value = check(val) + end + + private + + def check(val) + setup_rb_error "config: --#{name} requires argument" unless val + val + end +end + +class BoolItem < ConfigItem + def config_type + 'bool' + end + + def help_opt + "--#{@name}" + end + + private + + def check(val) + return 'yes' unless val + unless /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i =~ val + setup_rb_error "config: --#{@name} accepts only yes/no for argument" + end + (/\Ay(es)?|\At(rue)/i =~ value) ? 'yes' : 'no' + end +end + +class PathItem < ConfigItem + def config_type + 'path' + end + + private + + def check(path) + setup_rb_error "config: --#{@name} requires argument" unless path + path[0,1] == '$' ? path : File.expand_path(path) + end +end + +class ProgramItem < ConfigItem + def config_type + 'program' + end +end + +class SelectItem < ConfigItem + def initialize(name, template, default, desc) + super + @ok = template.split('/') + end + + def config_type + 'select' + end + + private + + def check(val) + unless @ok.include?(val.strip) + setup_rb_error "config: use --#{@name}=#{@template} (#{val})" + end + val.strip + end +end + +class PackageSelectionItem < ConfigItem + def initialize(name, template, default, help_default, desc) + super name, template, default, desc + @help_default = help_default + end + + attr_reader :help_default + + def config_type + 'package' + end + + private + + def check(val) + unless File.dir?("packages/#{val}") + setup_rb_error "config: no such package: #{val}" + end + val + end +end + +class ConfigTable_class + + def initialize(items) + @items = items + @table = {} + items.each do |i| + @table[i.name] = i + end + ALIASES.each do |ali, name| + @table[ali] = @table[name] + end + end + + include Enumerable + + def each(&block) + @items.each(&block) + end + + def key?(name) + @table.key?(name) + end + + def lookup(name) + @table[name] or raise ArgumentError, "no such config item: #{name}" + end + + def add(item) + @items.push item + @table[item.name] = item + end + + def remove(name) + item = lookup(name) + @items.delete_if {|i| i.name == name } + @table.delete_if {|name, i| i.name == name } + item + end + + def new + dup() + end + + def savefile + '.config' + end + + def load + begin + t = dup() + File.foreach(savefile()) do |line| + k, v = *line.split(/=/, 2) + t[k] = v.strip + end + t + rescue Errno::ENOENT + setup_rb_error $!.message + "#{File.basename($0)} config first" + end + end + + def save + @items.each {|i| i.value } + File.open(savefile(), 'w') {|f| + @items.each do |i| + f.printf "%s=%s\n", i.name, i.value if i.value + end + } + end + + def [](key) + lookup(key).eval(self) + end + + def []=(key, val) + lookup(key).set val + end + +end + +c = ::Config::CONFIG + +rubypath = c['bindir'] + '/' + c['ruby_install_name'] + +major = c['MAJOR'].to_i +minor = c['MINOR'].to_i +teeny = c['TEENY'].to_i +version = "#{major}.#{minor}" + +# ruby ver. >= 1.4.4? +newpath_p = ((major >= 2) or + ((major == 1) and + ((minor >= 5) or + ((minor == 4) and (teeny >= 4))))) + +if c['rubylibdir'] + # V < 1.6.3 + _stdruby = c['rubylibdir'] + _siteruby = c['sitedir'] + _siterubyver = c['sitelibdir'] + _siterubyverarch = c['sitearchdir'] +elsif newpath_p + # 1.4.4 <= V <= 1.6.3 + _stdruby = "$prefix/lib/ruby/#{version}" + _siteruby = c['sitedir'] + _siterubyver = "$siteruby/#{version}" + _siterubyverarch = "$siterubyver/#{c['arch']}" +else + # V < 1.4.4 + _stdruby = "$prefix/lib/ruby/#{version}" + _siteruby = "$prefix/lib/ruby/#{version}/site_ruby" + _siterubyver = _siteruby + _siterubyverarch = "$siterubyver/#{c['arch']}" +end +libdir = '-* dummy libdir *-' +stdruby = '-* dummy rubylibdir *-' +siteruby = '-* dummy site_ruby *-' +siterubyver = '-* dummy site_ruby version *-' +parameterize = lambda {|path| + path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')\ + .sub(/\A#{Regexp.quote(libdir)}/, '$libdir')\ + .sub(/\A#{Regexp.quote(stdruby)}/, '$stdruby')\ + .sub(/\A#{Regexp.quote(siteruby)}/, '$siteruby')\ + .sub(/\A#{Regexp.quote(siterubyver)}/, '$siterubyver') +} +libdir = parameterize.call(c['libdir']) +stdruby = parameterize.call(_stdruby) +siteruby = parameterize.call(_siteruby) +siterubyver = parameterize.call(_siterubyver) +siterubyverarch = parameterize.call(_siterubyverarch) + +if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } + makeprog = arg.sub(/'/, '').split(/=/, 2)[1] +else + makeprog = 'make' +end + +common_conf = [ + PathItem.new('prefix', 'path', c['prefix'], + 'path prefix of target environment'), + PathItem.new('bindir', 'path', parameterize.call(c['bindir']), + 'the directory for commands'), + PathItem.new('libdir', 'path', libdir, + 'the directory for libraries'), + PathItem.new('datadir', 'path', parameterize.call(c['datadir']), + 'the directory for shared data'), + PathItem.new('mandir', 'path', parameterize.call(c['mandir']), + 'the directory for man pages'), + PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), + 'the directory for man pages'), + PathItem.new('stdruby', 'path', stdruby, + 'the directory for standard ruby libraries'), + PathItem.new('siteruby', 'path', siteruby, + 'the directory for version-independent aux ruby libraries'), + PathItem.new('siterubyver', 'path', siterubyver, + 'the directory for aux ruby libraries'), + PathItem.new('siterubyverarch', 'path', siterubyverarch, + 'the directory for aux ruby binaries'), + PathItem.new('rbdir', 'path', '$siterubyver', + 'the directory for ruby scripts'), + PathItem.new('sodir', 'path', '$siterubyverarch', + 'the directory for ruby extentions'), + PathItem.new('rubypath', 'path', rubypath, + 'the path to set to #! line'), + ProgramItem.new('rubyprog', 'name', rubypath, + 'the ruby program using for installation'), + ProgramItem.new('makeprog', 'name', makeprog, + 'the make program to compile ruby extentions'), + SelectItem.new('shebang', 'all/ruby/never', 'ruby', + 'shebang line (#!) editing mode'), + BoolItem.new('without-ext', 'yes/no', 'no', + 'does not compile/install ruby extentions') +] +class ConfigTable_class # open again + ALIASES = { + 'std-ruby' => 'stdruby', + 'site-ruby-common' => 'siteruby', # For backward compatibility + 'site-ruby' => 'siterubyver', # For backward compatibility + 'bin-dir' => 'bindir', + 'bin-dir' => 'bindir', + 'rb-dir' => 'rbdir', + 'so-dir' => 'sodir', + 'data-dir' => 'datadir', + 'ruby-path' => 'rubypath', + 'ruby-prog' => 'rubyprog', + 'ruby' => 'rubyprog', + 'make-prog' => 'makeprog', + 'make' => 'makeprog' + } +end +multipackage_conf = [ + PackageSelectionItem.new('with', 'name,name...', '', 'ALL', + 'package names that you want to install'), + PackageSelectionItem.new('without', 'name,name...', '', 'NONE', + 'package names that you do not want to install') +] +if multipackage_install? + ConfigTable = ConfigTable_class.new(common_conf + multipackage_conf) +else + ConfigTable = ConfigTable_class.new(common_conf) +end + + +module MetaConfigAPI + + def eval_file_ifexist(fname) + instance_eval File.read(fname), fname, 1 if File.file?(fname) + end + + def config_names + ConfigTable.map {|i| i.name } + end + + def config?(name) + ConfigTable.key?(name) + end + + def bool_config?(name) + ConfigTable.lookup(name).config_type == 'bool' + end + + def path_config?(name) + ConfigTable.lookup(name).config_type == 'path' + end + + def value_config?(name) + case ConfigTable.lookup(name).config_type + when 'bool', 'path' + true + else + false + end + end + + def add_config(item) + ConfigTable.add item + end + + def add_bool_config(name, default, desc) + ConfigTable.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) + end + + def add_path_config(name, default, desc) + ConfigTable.add PathItem.new(name, 'path', default, desc) + end + + def set_config_default(name, default) + ConfigTable.lookup(name).default = default + end + + def remove_config(name) + ConfigTable.remove(name) + end + +end + + +# +# File Operations +# + +module FileOperations + + def mkdir_p(dirname, prefix = nil) + dirname = prefix + File.expand_path(dirname) if prefix + $stderr.puts "mkdir -p #{dirname}" if verbose? + return if no_harm? + + # does not check '/'... it's too abnormal case + dirs = File.expand_path(dirname).split(%r<(?=/)>) + if /\A[a-z]:\z/i =~ dirs[0] + disk = dirs.shift + dirs[0] = disk + dirs[0] + end + dirs.each_index do |idx| + path = dirs[0..idx].join('') + Dir.mkdir path unless File.dir?(path) + end + end + + def rm_f(fname) + $stderr.puts "rm -f #{fname}" if verbose? + return if no_harm? + + if File.exist?(fname) or File.symlink?(fname) + File.chmod 0777, fname + File.unlink fname + end + end + + def rm_rf(dn) + $stderr.puts "rm -rf #{dn}" if verbose? + return if no_harm? + + Dir.chdir dn + Dir.foreach('.') do |fn| + next if fn == '.' + next if fn == '..' + if File.dir?(fn) + verbose_off { + rm_rf fn + } + else + verbose_off { + rm_f fn + } + end + end + Dir.chdir '..' + Dir.rmdir dn + end + + def move_file(src, dest) + File.unlink dest if File.exist?(dest) + begin + File.rename src, dest + rescue + File.open(dest, 'wb') {|f| f.write File.binread(src) } + File.chmod File.stat(src).mode, dest + File.unlink src + end + end + + def install(from, dest, mode, prefix = nil) + $stderr.puts "install #{from} #{dest}" if verbose? + return if no_harm? + + realdest = prefix ? prefix + File.expand_path(dest) : dest + realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) + str = File.binread(from) + if diff?(str, realdest) + verbose_off { + rm_f realdest if File.exist?(realdest) + } + File.open(realdest, 'wb') {|f| + f.write str + } + File.chmod mode, realdest + + File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| + if prefix + f.puts realdest.sub(prefix, '') + else + f.puts realdest + end + } + end + end + + def diff?(new_content, path) + return true unless File.exist?(path) + new_content != File.binread(path) + end + + def command(str) + $stderr.puts str if verbose? + system str or raise RuntimeError, "'system #{str}' failed" + end + + def ruby(str) + command config('rubyprog') + ' ' + str + end + + def make(task = '') + command config('makeprog') + ' ' + task + end + + def extdir?(dir) + File.exist?(dir + '/MANIFEST') + end + + def all_files_in(dirname) + Dir.open(dirname) {|d| + return d.select {|ent| File.file?("#{dirname}/#{ent}") } + } + end + + REJECT_DIRS = %w( + CVS SCCS RCS CVS.adm .svn + ) + + def all_dirs_in(dirname) + Dir.open(dirname) {|d| + return d.select {|n| File.dir?("#{dirname}/#{n}") } - %w(. ..) - REJECT_DIRS + } + end + +end + + +# +# Main Installer +# + +module HookUtils + + def run_hook(name) + try_run_hook "#{curr_srcdir()}/#{name}" or + try_run_hook "#{curr_srcdir()}/#{name}.rb" + end + + def try_run_hook(fname) + return false unless File.file?(fname) + begin + instance_eval File.read(fname), fname, 1 + rescue + setup_rb_error "hook #{fname} failed:\n" + $!.message + end + true + end + +end + + +module HookScriptAPI + + def get_config(key) + @config[key] + end + + alias config get_config + + def set_config(key, val) + @config[key] = val + end + + # + # srcdir/objdir (works only in the package directory) + # + + #abstract srcdir_root + #abstract objdir_root + #abstract relpath + + def curr_srcdir + "#{srcdir_root()}/#{relpath()}" + end + + def curr_objdir + "#{objdir_root()}/#{relpath()}" + end + + def srcfile(path) + "#{curr_srcdir()}/#{path}" + end + + def srcexist?(path) + File.exist?(srcfile(path)) + end + + def srcdirectory?(path) + File.dir?(srcfile(path)) + end + + def srcfile?(path) + File.file? srcfile(path) + end + + def srcentries(path = '.') + Dir.open("#{curr_srcdir()}/#{path}") {|d| + return d.to_a - %w(. ..) + } + end + + def srcfiles(path = '.') + srcentries(path).select {|fname| + File.file?(File.join(curr_srcdir(), path, fname)) + } + end + + def srcdirectories(path = '.') + srcentries(path).select {|fname| + File.dir?(File.join(curr_srcdir(), path, fname)) + } + end + +end + + +class ToplevelInstaller + + Version = '3.3.1' + Copyright = 'Copyright (c) 2000-2004 Minero Aoki' + + TASKS = [ + [ 'all', 'do config, setup, then install' ], + [ 'config', 'saves your configurations' ], + [ 'show', 'shows current configuration' ], + [ 'setup', 'compiles ruby extentions and others' ], + [ 'install', 'installs files' ], + [ 'clean', "does `make clean' for each extention" ], + [ 'distclean',"does `make distclean' for each extention" ] + ] + + def ToplevelInstaller.invoke + instance().invoke + end + + @singleton = nil + + def ToplevelInstaller.instance + @singleton ||= new(File.dirname($0)) + @singleton + end + + include MetaConfigAPI + + def initialize(ardir_root) + @config = nil + @options = { 'verbose' => true } + @ardir = File.expand_path(ardir_root) + end + + def inspect + "#<#{self.class} #{__id__()}>" + end + + def invoke + run_metaconfigs + case task = parsearg_global() + when nil, 'all' + @config = load_config('config') + parsearg_config + init_installers + exec_config + exec_setup + exec_install + else + @config = load_config(task) + __send__ "parsearg_#{task}" + init_installers + __send__ "exec_#{task}" + end + end + + def run_metaconfigs + eval_file_ifexist "#{@ardir}/metaconfig" + end + + def load_config(task) + case task + when 'config' + ConfigTable.new + when 'clean', 'distclean' + if File.exist?(ConfigTable.savefile) + then ConfigTable.load + else ConfigTable.new + end + else + ConfigTable.load + end + end + + def init_installers + @installer = Installer.new(@config, @options, @ardir, File.expand_path('.')) + end + + # + # Hook Script API bases + # + + def srcdir_root + @ardir + end + + def objdir_root + '.' + end + + def relpath + '.' + end + + # + # Option Parsing + # + + def parsearg_global + valid_task = /\A(?:#{TASKS.map {|task,desc| task }.join '|'})\z/ + + while arg = ARGV.shift + case arg + when /\A\w+\z/ + setup_rb_error "invalid task: #{arg}" unless valid_task =~ arg + return arg + + when '-q', '--quiet' + @options['verbose'] = false + + when '--verbose' + @options['verbose'] = true + + when '-h', '--help' + print_usage $stdout + exit 0 + + when '-v', '--version' + puts "#{File.basename($0)} version #{Version}" + exit 0 + + when '--copyright' + puts Copyright + exit 0 + + else + setup_rb_error "unknown global option '#{arg}'" + end + end + + nil + end + + + def parsearg_no_options + unless ARGV.empty? + setup_rb_error "#{task}: unknown options: #{ARGV.join ' '}" + end + end + + alias parsearg_show parsearg_no_options + alias parsearg_setup parsearg_no_options + alias parsearg_clean parsearg_no_options + alias parsearg_distclean parsearg_no_options + + def parsearg_config + re = /\A--(#{ConfigTable.map {|i| i.name }.join('|')})(?:=(.*))?\z/ + @options['config-opt'] = [] + + while i = ARGV.shift + if /\A--?\z/ =~ i + @options['config-opt'] = ARGV.dup + break + end + m = re.match(i) or setup_rb_error "config: unknown option #{i}" + name, value = *m.to_a[1,2] + @config[name] = value + end + end + + def parsearg_install + @options['no-harm'] = false + @options['install-prefix'] = '' + while a = ARGV.shift + case a + when /\A--no-harm\z/ + @options['no-harm'] = true + when /\A--prefix=(.*)\z/ + path = $1 + path = File.expand_path(path) unless path[0,1] == '/' + @options['install-prefix'] = path + else + setup_rb_error "install: unknown option #{a}" + end + end + end + + def print_usage(out) + out.puts 'Typical Installation Procedure:' + out.puts " $ ruby #{File.basename $0} config" + out.puts " $ ruby #{File.basename $0} setup" + out.puts " # ruby #{File.basename $0} install (may require root privilege)" + out.puts + out.puts 'Detailed Usage:' + out.puts " ruby #{File.basename $0} " + out.puts " ruby #{File.basename $0} [] []" + + fmt = " %-24s %s\n" + out.puts + out.puts 'Global options:' + out.printf fmt, '-q,--quiet', 'suppress message outputs' + out.printf fmt, ' --verbose', 'output messages verbosely' + out.printf fmt, '-h,--help', 'print this message' + out.printf fmt, '-v,--version', 'print version and quit' + out.printf fmt, ' --copyright', 'print copyright and quit' + out.puts + out.puts 'Tasks:' + TASKS.each do |name, desc| + out.printf fmt, name, desc + end + + fmt = " %-24s %s [%s]\n" + out.puts + out.puts 'Options for CONFIG or ALL:' + ConfigTable.each do |item| + out.printf fmt, item.help_opt, item.description, item.help_default + end + out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" + out.puts + out.puts 'Options for INSTALL:' + out.printf fmt, '--no-harm', 'only display what to do if given', 'off' + out.printf fmt, '--prefix=path', 'install path prefix', '$prefix' + out.puts + end + + # + # Task Handlers + # + + def exec_config + @installer.exec_config + @config.save # must be final + end + + def exec_setup + @installer.exec_setup + end + + def exec_install + @installer.exec_install + end + + def exec_show + ConfigTable.each do |i| + printf "%-20s %s\n", i.name, i.value + end + end + + def exec_clean + @installer.exec_clean + end + + def exec_distclean + @installer.exec_distclean + end + +end + + +class ToplevelInstallerMulti < ToplevelInstaller + + include HookUtils + include HookScriptAPI + include FileOperations + + def initialize(ardir) + super + @packages = all_dirs_in("#{@ardir}/packages") + raise 'no package exists' if @packages.empty? + end + + def run_metaconfigs + eval_file_ifexist "#{@ardir}/metaconfig" + @packages.each do |name| + eval_file_ifexist "#{@ardir}/packages/#{name}/metaconfig" + end + end + + def init_installers + @installers = {} + @packages.each do |pack| + @installers[pack] = Installer.new(@config, @options, + "#{@ardir}/packages/#{pack}", + "packages/#{pack}") + end + + with = extract_selection(config('with')) + without = extract_selection(config('without')) + @selected = @installers.keys.select {|name| + (with.empty? or with.include?(name)) \ + and not without.include?(name) + } + end + + def extract_selection(list) + a = list.split(/,/) + a.each do |name| + setup_rb_error "no such package: #{name}" unless @installers.key?(name) + end + a + end + + def print_usage(f) + super + f.puts 'Inluded packages:' + f.puts ' ' + @packages.sort.join(' ') + f.puts + end + + # + # multi-package metaconfig API + # + + attr_reader :packages + + def declare_packages(list) + raise 'package list is empty' if list.empty? + list.each do |name| + raise "directory packages/#{name} does not exist"\ + unless File.dir?("#{@ardir}/packages/#{name}") + end + @packages = list + end + + # + # Task Handlers + # + + def exec_config + run_hook 'pre-config' + each_selected_installers {|inst| inst.exec_config } + run_hook 'post-config' + @config.save # must be final + end + + def exec_setup + run_hook 'pre-setup' + each_selected_installers {|inst| inst.exec_setup } + run_hook 'post-setup' + end + + def exec_install + run_hook 'pre-install' + each_selected_installers {|inst| inst.exec_install } + run_hook 'post-install' + end + + def exec_clean + rm_f ConfigTable.savefile + run_hook 'pre-clean' + each_selected_installers {|inst| inst.exec_clean } + run_hook 'post-clean' + end + + def exec_distclean + rm_f ConfigTable.savefile + run_hook 'pre-distclean' + each_selected_installers {|inst| inst.exec_distclean } + run_hook 'post-distclean' + end + + # + # lib + # + + def each_selected_installers + Dir.mkdir 'packages' unless File.dir?('packages') + @selected.each do |pack| + $stderr.puts "Processing the package `#{pack}' ..." if @options['verbose'] + Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") + Dir.chdir "packages/#{pack}" + yield @installers[pack] + Dir.chdir '../..' + end + end + + def verbose? + @options['verbose'] + end + + def no_harm? + @options['no-harm'] + end + +end + + +class Installer + + FILETYPES = %w( bin lib ext share ) + + include HookScriptAPI + include HookUtils + include FileOperations + + def initialize(config, opt, srcroot, objroot) + @config = config + @options = opt + @srcdir = File.expand_path(srcroot) + @objdir = File.expand_path(objroot) + @currdir = '.' + end + + def inspect + "#<#{self.class} #{File.basename(@srcdir)}>" + end + + # + # Hook Script API base methods + # + + def srcdir_root + @srcdir + end + + def objdir_root + @objdir + end + + def relpath + @currdir + end + + # + # configs/options + # + + def no_harm? + @options['no-harm'] + end + + def verbose? + @options['verbose'] + end + + def verbose_off + begin + save, @options['verbose'] = @options['verbose'], false + yield + ensure + @options['verbose'] = save + end + end + + # + # TASK config + # + + def exec_config + exec_task_traverse 'config' + end + + def config_dir_bin(rel) + end + + def config_dir_lib(rel) + end + + def config_dir_ext(rel) + extconf if extdir?(curr_srcdir()) + end + + def extconf + opt = @options['config-opt'].join(' ') + command "#{config('rubyprog')} #{curr_srcdir()}/extconf.rb #{opt}" + end + + def config_dir_share(rel) + end + + # + # TASK setup + # + + def exec_setup + exec_task_traverse 'setup' + end + + def setup_dir_bin(rel) + all_files_in(curr_srcdir()).each do |fname| + adjust_shebang "#{curr_srcdir()}/#{fname}" + end + end + + def adjust_shebang(path) + return if no_harm? + tmpfile = File.basename(path) + '.tmp' + begin + File.open(path, 'rb') {|r| + first = r.gets + return unless File.basename(config('rubypath')) == 'ruby' + return unless File.basename(first.sub(/\A\#!/, '').split[0]) == 'ruby' + $stderr.puts "adjusting shebang: #{File.basename(path)}" if verbose? + File.open(tmpfile, 'wb') {|w| + w.print first.sub(/\A\#!\s*\S+/, '#! ' + config('rubypath')) + w.write r.read + } + move_file tmpfile, File.basename(path) + } + ensure + File.unlink tmpfile if File.exist?(tmpfile) + end + end + + def setup_dir_lib(rel) + end + + def setup_dir_ext(rel) + make if extdir?(curr_srcdir()) + end + + def setup_dir_share(rel) + end + + # + # TASK install + # + + def exec_install + rm_f 'InstalledFiles' + exec_task_traverse 'install' + end + + def install_dir_bin(rel) + install_files collect_filenames_auto(), "#{config('bindir')}/#{rel}", 0755 + end + + def install_dir_lib(rel) + install_files ruby_scripts(), "#{config('rbdir')}/#{rel}", 0644 + return unless rel.empty? + begin + require 'rdoc/rdoc' + ri_site = true + if RDOC_VERSION =~ /^0\./ + require 'rdoc/options' + unless Options::OptionList::OPTION_LIST.assoc('--ri-site') + ri_site = false + end + end rescue nil + if ri_site + r = RDoc::RDoc.new + r.document(%w{--ri-site}) + end + rescue + puts "** Unable to install Ri documentation for RedCloth **" + end + end + + def install_dir_ext(rel) + return unless extdir?(curr_srcdir()) + install_files ruby_extentions('.'), + "#{config('sodir')}/#{File.dirname(rel)}", + 0555 + end + + def install_dir_share(rel) + end + + def install_files(list, dest, mode) + mkdir_p dest, @options['install-prefix'] + list.each do |fname| + install fname, dest, mode, @options['install-prefix'] + end + end + + def ruby_scripts + collect_filenames_auto().select {|n| /\.rb\z/ =~ n } + end + + # picked up many entries from cvs-1.11.1/src/ignore.c + reject_patterns = %w( + core RCSLOG tags TAGS .make.state + .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb + *~ *.old *.bak *.BAK *.orig *.rej _$* *$ + + *.org *.in .* + ) + mapping = { + '.' => '\.', + '$' => '\$', + '#' => '\#', + '*' => '.*' + } + REJECT_PATTERNS = Regexp.new('\A(?:' + + reject_patterns.map {|pat| + pat.gsub(/[\.\$\#\*]/) {|ch| mapping[ch] } + }.join('|') + + ')\z') + + def collect_filenames_auto + mapdir((existfiles() - hookfiles()).reject {|fname| + REJECT_PATTERNS =~ fname + }) + end + + def existfiles + all_files_in(curr_srcdir()) | all_files_in('.') + end + + def hookfiles + %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| + %w( config setup install clean ).map {|t| sprintf(fmt, t) } + }.flatten + end + + def mapdir(filelist) + filelist.map {|fname| + if File.exist?(fname) # objdir + fname + else # srcdir + File.join(curr_srcdir(), fname) + end + } + end + + def ruby_extentions(dir) + Dir.open(dir) {|d| + ents = d.select {|fname| /\.#{::Config::CONFIG['DLEXT']}\z/ =~ fname } + if ents.empty? + setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" + end + return ents + } + end + + # + # TASK clean + # + + def exec_clean + exec_task_traverse 'clean' + rm_f ConfigTable.savefile + rm_f 'InstalledFiles' + end + + def clean_dir_bin(rel) + end + + def clean_dir_lib(rel) + end + + def clean_dir_ext(rel) + return unless extdir?(curr_srcdir()) + make 'clean' if File.file?('Makefile') + end + + def clean_dir_share(rel) + end + + # + # TASK distclean + # + + def exec_distclean + exec_task_traverse 'distclean' + rm_f ConfigTable.savefile + rm_f 'InstalledFiles' + end + + def distclean_dir_bin(rel) + end + + def distclean_dir_lib(rel) + end + + def distclean_dir_ext(rel) + return unless extdir?(curr_srcdir()) + make 'distclean' if File.file?('Makefile') + end + + # + # lib + # + + def exec_task_traverse(task) + run_hook "pre-#{task}" + FILETYPES.each do |type| + if config('without-ext') == 'yes' and type == 'ext' + $stderr.puts 'skipping ext/* by user option' if verbose? + next + end + traverse task, type, "#{task}_dir_#{type}" + end + run_hook "post-#{task}" + end + + def traverse(task, rel, mid) + dive_into(rel) { + run_hook "pre-#{task}" + __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') + all_dirs_in(curr_srcdir()).each do |d| + traverse task, "#{rel}/#{d}", mid + end + run_hook "post-#{task}" + } + end + + def dive_into(rel) + return unless File.dir?("#{@srcdir}/#{rel}") + + dir = File.basename(rel) + Dir.mkdir dir unless File.dir?(dir) + prevdir = Dir.pwd + Dir.chdir dir + $stderr.puts '---> ' + rel if verbose? + @currdir = rel + yield + Dir.chdir prevdir + $stderr.puts '<--- ' + rel if verbose? + @currdir = File.dirname(rel) + end + +end + + +if $0 == __FILE__ + begin + if multipackage_install? + ToplevelInstallerMulti.invoke + else + ToplevelInstaller.invoke + end + rescue SetupError + raise if $DEBUG + $stderr.puts $!.message + $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." + exit 1 + end +end diff --git a/vendor/RedCloth-3.0.3/tests/code.yml b/vendor/RedCloth-3.0.4/tests/code.yml similarity index 100% rename from vendor/RedCloth-3.0.3/tests/code.yml rename to vendor/RedCloth-3.0.4/tests/code.yml diff --git a/vendor/RedCloth-3.0.4/tests/hard_breaks.yml b/vendor/RedCloth-3.0.4/tests/hard_breaks.yml new file mode 100644 index 00000000..0b1fc099 --- /dev/null +++ b/vendor/RedCloth-3.0.4/tests/hard_breaks.yml @@ -0,0 +1,26 @@ +--- +in: | + |This|is|a|row| + {background:#ddd}. |This|is|grey|row| + |This|is|another|row| +out: |- + + + + + + + + + + + + + + + + + + + +
    Thisisarow
    Thisisgreyrow
    Thisisanotherrow
    diff --git a/vendor/RedCloth-3.0.3/tests/images.yml b/vendor/RedCloth-3.0.4/tests/images.yml similarity index 100% rename from vendor/RedCloth-3.0.3/tests/images.yml rename to vendor/RedCloth-3.0.4/tests/images.yml diff --git a/vendor/RedCloth-3.0.3/tests/instiki.yml b/vendor/RedCloth-3.0.4/tests/instiki.yml similarity index 100% rename from vendor/RedCloth-3.0.3/tests/instiki.yml rename to vendor/RedCloth-3.0.4/tests/instiki.yml diff --git a/vendor/RedCloth-3.0.3/tests/links.yml b/vendor/RedCloth-3.0.4/tests/links.yml similarity index 100% rename from vendor/RedCloth-3.0.3/tests/links.yml rename to vendor/RedCloth-3.0.4/tests/links.yml diff --git a/vendor/RedCloth-3.0.3/tests/lists.yml b/vendor/RedCloth-3.0.4/tests/lists.yml similarity index 100% rename from vendor/RedCloth-3.0.3/tests/lists.yml rename to vendor/RedCloth-3.0.4/tests/lists.yml diff --git a/vendor/RedCloth-3.0.3/tests/markdown.yml b/vendor/RedCloth-3.0.4/tests/markdown.yml similarity index 100% rename from vendor/RedCloth-3.0.3/tests/markdown.yml rename to vendor/RedCloth-3.0.4/tests/markdown.yml diff --git a/vendor/RedCloth-3.0.3/tests/poignant.yml b/vendor/RedCloth-3.0.4/tests/poignant.yml similarity index 100% rename from vendor/RedCloth-3.0.3/tests/poignant.yml rename to vendor/RedCloth-3.0.4/tests/poignant.yml diff --git a/vendor/RedCloth-3.0.3/tests/table.yml b/vendor/RedCloth-3.0.4/tests/table.yml similarity index 95% rename from vendor/RedCloth-3.0.3/tests/table.yml rename to vendor/RedCloth-3.0.4/tests/table.yml index bf5059e1..3ce974dd 100644 --- a/vendor/RedCloth-3.0.3/tests/table.yml +++ b/vendor/RedCloth-3.0.4/tests/table.yml @@ -39,28 +39,28 @@ out: |- 11/18/04 11/18/04 070 - XML spec complete + XML spec complete 11/29/04 11/29/04 011 - XML spec complete (KH is on schedule) + XML spec complete (KH is on schedule) 11/29/04 11/29/04 051 - XML spec complete (KH is on schedule) + XML spec complete (KH is on schedule) 11/29/04 11/29/04 081 - XML spec complete (KH is on schedule) + XML spec complete (KH is on schedule) diff --git a/vendor/RedCloth-3.0.3/tests/textism.yml b/vendor/RedCloth-3.0.4/tests/textism.yml similarity index 97% rename from vendor/RedCloth-3.0.3/tests/textism.yml rename to vendor/RedCloth-3.0.4/tests/textism.yml index 5489c04d..1e6f8d6b 100644 --- a/vendor/RedCloth-3.0.3/tests/textism.yml +++ b/vendor/RedCloth-3.0.4/tests/textism.yml @@ -71,6 +71,12 @@ out:

    a phrase

    in: '**a phrase**' out:

    a phrase

    --- +in: '*(a)* a' +out:

    (a) a

    +--- +in: '*(a)* *' +out:

    (a) *

    +--- in: Nabokov's ??Pnin?? out:

    Nabokov’s Pnin

    --- @@ -395,3 +401,6 @@ out: |-
  • We must act
  • +--- +in: '"test":http://foo.com/b---ar' +out:

    test

    From 16454549fee5eb0fc827e825ce392877b118cda4 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 27 Sep 2005 03:30:01 +0000 Subject: [PATCH 61/84] Fixed a nasty bug that caused Instiki to go into an endless loop on call to /wiki/print/ --- app/controllers/revision_sweeper.rb | 4 ++++ app/controllers/wiki_controller.rb | 3 +++ 2 files changed, 7 insertions(+) diff --git a/app/controllers/revision_sweeper.rb b/app/controllers/revision_sweeper.rb index 1ab4676b..879274e3 100644 --- a/app/controllers/revision_sweeper.rb +++ b/app/controllers/revision_sweeper.rb @@ -23,5 +23,9 @@ class RevisionSweeper < ActionController::Caching::Sweeper :action => %w(authors recently_revised list) expire_fragment :controller => 'wiki', :web => web.address, :action => %w(rss_with_headlines rss_with_content) + WikiReference.pages_that_reference(page.name).each do |ref| + expire_action :controller => 'wiki', :web => web.address, + :action => %w(show published), :id => ref.page.name + end end end diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index fdd83d21..787f8c6a 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -156,6 +156,9 @@ class WikiController < ApplicationController end def print + if @page.nil? + redirect_home + end @link_mode ||= :show @renderer = PageRenderer.new(@page.revisions.last) # to template From 5b1778a7f1dc9b545d10c2b043021c0901f1a5fb Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 27 Sep 2005 03:30:42 +0000 Subject: [PATCH 62/84] Fixed handling of Windows EOLs in the import_storage --- script/import_storage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/import_storage b/script/import_storage index 0f6d6b21..a999d0ca 100755 --- a/script/import_storage +++ b/script/import_storage @@ -124,7 +124,7 @@ def sql_insert(table, hash) else raise "Unsupported database option #{OPTIONS[:database]}" end - "'#{escaped_value}'" + "'#{escaped_value.gsub("\r\n", "\n")}'" end end From 9816c395c5b6e27d3b2bb772af919a5a2bcbffed Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 27 Sep 2005 03:53:29 +0000 Subject: [PATCH 63/84] Expire caches for referencing pages on saves and deletes; fixed date formatting in recently_revised --- app/controllers/revision_sweeper.rb | 13 +++++++------ app/helpers/application_helper.rb | 11 ++++++++--- app/views/wiki/recently_revised.rhtml | 4 ++-- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/app/controllers/revision_sweeper.rb b/app/controllers/revision_sweeper.rb index 879274e3..28284ce6 100644 --- a/app/controllers/revision_sweeper.rb +++ b/app/controllers/revision_sweeper.rb @@ -17,15 +17,16 @@ class RevisionSweeper < ActionController::Caching::Sweeper def expire_caches(page) web = page.web - expire_action :controller => 'wiki', :web => web.address, - :action => %w(show published), :id => page.name + + ([page.name] + WikiReference.pages_that_reference(page.name)).uniq.each do |page_name| + expire_action :controller => 'wiki', :web => web.address, + :action => %w(show published), :id => page_name + end + expire_action :controller => 'wiki', :web => web.address, :action => %w(authors recently_revised list) expire_fragment :controller => 'wiki', :web => web.address, :action => %w(rss_with_headlines rss_with_content) - WikiReference.pages_that_reference(page.name).each do |ref| - expire_action :controller => 'wiki', :web => web.address, - :action => %w(show published), :id => ref.page.name - end end + end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index f72c572d..3056541e 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -77,10 +77,15 @@ module ApplicationHelper h(text).gsub(/\n/, '
    ') end - def format_date(date) + def format_date(date, include_time = true) # Must use DateTime because Time doesn't support %e on at least some platforms - DateTime.new(date.year, date.mon, date.day, date.hour, date.min, - date.sec).strftime("%B %e, %Y %H:%M:%S") + date_time = DateTime.new(date.year, date.mon, date.day, date.hour, date.min, + date.sec) + if include_time + return date_time.strftime("%B %e, %Y %H:%M:%S") + else + return date_time.strftime("%B %e, %Y") + end end def rendered_content(page) diff --git a/app/views/wiki/recently_revised.rhtml b/app/views/wiki/recently_revised.rhtml index 0460c7b7..f4ca669c 100644 --- a/app/views/wiki/recently_revised.rhtml +++ b/app/views/wiki/recently_revised.rhtml @@ -4,13 +4,13 @@ <% unless @pages_by_revision.empty? %> <% revision_date = @pages_by_revision.first.revised_at %> -

    <%= revision_date.strftime('%B %e, %Y') %>

    +

    <%= format_date(revision_date, include_time = false) %>

      <% for page in @pages_by_revision %> <% if page.revised_at < revision_date %> <% revision_date = page.revised_at %>
    -

    <%= revision_date.strftime('%B %e, %Y') %>

    +

    <%= format_date(revision_date, include_time = false) %>

      <% end %>
    • From 5447b82ed9aba3c6143033e69202f37327d76bee Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 27 Sep 2005 05:26:19 +0000 Subject: [PATCH 64/84] A bit of caching in the page_renderer --- lib/page_renderer.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/page_renderer.rb b/lib/page_renderer.rb index 07b6a3fb..374da3f3 100644 --- a/lib/page_renderer.rb +++ b/lib/page_renderer.rb @@ -24,7 +24,7 @@ class PageRenderer end def display_content - render + @display_content ||= render end def display_content_for_export @@ -32,12 +32,12 @@ class PageRenderer end def display_published - render :mode => :publish + @display_published ||= render(:mode => :publish) end def display_diff previous_revision = @revision.page.previous_revision(@revision) - if previous_revision + if previous_revision rendered_previous_revision = WikiContent.new(previous_revision, @@url_generator).render! HTMLDiff.diff(rendered_previous_revision, display_content) else From 223a1f9de33ffd0baa1e654afc3dad5ab8e83822 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 27 Sep 2005 13:46:02 +0000 Subject: [PATCH 65/84] Speeding up some stuff --- app/controllers/wiki_controller.rb | 3 ++- app/models/page.rb | 6 +----- app/models/page_set.rb | 9 ++++----- app/models/web.rb | 21 ++++++++++++++++++++- app/models/wiki.rb | 1 + app/models/wiki_reference.rb | 3 ++- app/views/wiki/authors.rhtml | 2 +- lib/page_renderer.rb | 25 +++++++++++++++++-------- script/reset_references | 2 +- test/functional/wiki_controller_test.rb | 3 +++ test/unit/web_test.rb | 8 ++++++++ 11 files changed, 60 insertions(+), 23 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 787f8c6a..13d76598 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -46,7 +46,8 @@ class WikiController < ApplicationController # Within a single web --------------------------------------------------------- def authors - @authors = @web.select.authors.sort + @page_names_by_author = @web.page_names_by_author + @authors = @page_names_by_author.keys.sort end def export_html diff --git a/app/models/page.rb b/app/models/page.rb index a44b5558..e45b609e 100644 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -16,7 +16,7 @@ class Page < ActiveRecord::Base # Try to render content to make sure that markup engine can take it, renderer.revision = Revision.new( :page => self, :content => content, :author => author, :revised_at => time) - renderer.display_content + renderer.display_content(update_references = true) # A user may change a page, look at it and make some more changes - several times. # Not to record every such iteration as a new revision, if the previous revision was done @@ -59,10 +59,6 @@ class Page < ActiveRecord::Base end end - def authors - revisions.collect { |rev| rev.author } - end - def references web.select.pages_that_reference(name) end diff --git a/app/models/page_set.rb b/app/models/page_set.rb index 873e4c49..9589b36c 100644 --- a/app/models/page_set.rb +++ b/app/models/page_set.rb @@ -46,6 +46,9 @@ class PageSet < Array end def pages_authored_by(author) + all_pages_authored_by_the_author = + Page.connection.select_all(sanitize_sql([ + "SELECT page_id FROM revision WHERE author = '?'", author])) self.select { |page| page.authors.include?(author) } end @@ -59,7 +62,7 @@ class PageSet < Array # references and so cannot be orphans # Pages that refer to themselves and have no links from outside are oprphans. def orphaned_pages - never_orphans = web.select.authors + ['HomePage'] + never_orphans = web.authors + ['HomePage'] self.select { |page| if never_orphans.include? page.name false @@ -88,8 +91,4 @@ class PageSet < Array }.flatten.uniq end - def authors - self.inject([]) { |authors, page| authors << page.authors }.flatten.uniq.sort - end - end diff --git a/app/models/web.rb b/app/models/web.rb index 65a97832..c38c8061 100644 --- a/app/models/web.rb +++ b/app/models/web.rb @@ -21,7 +21,11 @@ class Web < ActiveRecord::Base end def authors - select.authors + connection.select_all( + 'SELECT DISTINCT r.author AS author ' + + 'FROM revisions r ' + + 'JOIN pages p ON p.id = r.page_id ' + + 'ORDER by 1').collect { |row| row['author'] } end def categories @@ -44,6 +48,21 @@ class Web < ActiveRecord::Base read_attribute('markup').to_sym end + def page_names_by_author + connection.select_all( + 'SELECT DISTINCT r.author AS author, p.name AS page_name ' + + 'FROM revisions r ' + + 'JOIN pages p ON r.page_id = p.id ' + + "WHERE p.web_id = #{self.id} " + + 'ORDER by p.name' + ).inject({}) { |result, row| + author, page_name = row['author'], row['page_name'] + result[author] = [] unless result.has_key?(author) + result[author] << page_name + result + } + end + def remove_pages(pages_to_be_removed) pages_to_be_removed.each { |p| p.destroy } end diff --git a/app/models/wiki.rb b/app/models/wiki.rb index 773fd011..e0da8644 100644 --- a/app/models/wiki.rb +++ b/app/models/wiki.rb @@ -1,4 +1,5 @@ class Wiki + cattr_accessor :storage_path, :logger self.storage_path = "#{RAILS_ROOT}/storage/" self.logger = RAILS_DEFAULT_LOGGER diff --git a/app/models/wiki_reference.rb b/app/models/wiki_reference.rb index 6ebe1d55..9f4534a7 100644 --- a/app/models/wiki_reference.rb +++ b/app/models/wiki_reference.rb @@ -4,9 +4,10 @@ class WikiReference < ActiveRecord::Base WANTED_PAGE = 'W' INCLUDED_PAGE = 'I' CATEGORY = 'C' + AUTHOR = 'A' belongs_to :page - validates_inclusion_of :link_type, :in => [LINKED_PAGE, WANTED_PAGE, INCLUDED_PAGE, CATEGORY] + validates_inclusion_of :link_type, :in => [LINKED_PAGE, WANTED_PAGE, INCLUDED_PAGE, CATEGORY, AUTHOR] # FIXME all finders below MUST restrict their results to pages belonging to a particular web diff --git a/app/views/wiki/authors.rhtml b/app/views/wiki/authors.rhtml index 97eb5c9d..57f529ee 100644 --- a/app/views/wiki/authors.rhtml +++ b/app/views/wiki/authors.rhtml @@ -5,7 +5,7 @@
    • <%= link_to_page author %> co- or authored: - <%= @web.select.pages_authored_by(author).collect { |page| link_to_page(page.name) }.sort.join ', ' %> + <%= @page_names_by_author[author].collect { |page_name| link_to_page(page_name) }.sort.join ', ' %>
    • <% end %>
    diff --git a/lib/page_renderer.rb b/lib/page_renderer.rb index 374da3f3..f47238f4 100644 --- a/lib/page_renderer.rb +++ b/lib/page_renderer.rb @@ -20,11 +20,12 @@ class PageRenderer def revision=(r) @revision = r - @wiki_words_cache = @wiki_includes_cache = @wiki_references_cache = nil + @display_content = @display_published = @wiki_words_cache = @wiki_includes_cache = + @wiki_references_cache = nil end - def display_content - @display_content ||= render + def display_content(update_references = false) + @display_content ||= render(:update_references => update_references) end def display_content_for_export @@ -87,12 +88,21 @@ class PageRenderer private def render(options = {}) - result = WikiContent.new(@revision, @@url_generator, options).render! + + rendering_result = WikiContent.new(@revision, @@url_generator, options).render! + + if options[:update_references] + update_references(rendering_result) + end + rendering_result + end + + def update_references(rendering_result) WikiReference.delete_all ['page_id = ?', @revision.page_id] references = @revision.page.wiki_references - wiki_word_chunks = result.find_chunks(WikiChunk::WikiLink) + wiki_word_chunks = rendering_result.find_chunks(WikiChunk::WikiLink) wiki_words = wiki_word_chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq wiki_words.each do |referenced_name| @@ -105,17 +115,16 @@ class PageRenderer references.create :referenced_name => referenced_name, :link_type => link_type end - include_chunks = result.find_chunks(Include) + include_chunks = rendering_result.find_chunks(Include) includes = include_chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq includes.each do |included_page_name| references.create :referenced_name => included_page_name, :link_type => WikiReference::INCLUDED_PAGE end - categories = result.find_chunks(Category).map { |cat| cat.list }.flatten + categories = rendering_result.find_chunks(Category).map { |cat| cat.list }.flatten categories.each do |category| references.create :referenced_name => category, :link_type => WikiReference::CATEGORY end - result end end diff --git a/script/reset_references b/script/reset_references index 0e21b0c9..176f3430 100644 --- a/script/reset_references +++ b/script/reset_references @@ -16,7 +16,7 @@ Web.find_all.each do |web| web.pages.find(:all, :order => 'name').each do |page| $stderr.puts "Processing page '#{page.name}'" begin - PageRenderer.new(page.current_revision).display_content + PageRenderer.new(page.current_revision).display_content(update_references = true) rescue => e puts e puts e.backtrace diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 8be34cb4..9d39fa23 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -53,6 +53,9 @@ class WikiControllerTest < Test::Unit::TestCase assert_success assert_equal %w(AnAuthor BreakingTheOrder DavidHeinemeierHansson Guest Me TreeHugger), r.template_objects['authors'] + page_names_by_author = r.template_objects['page_names_by_author'] + assert_equal r.template_objects['authors'], page_names_by_author.keys.sort + assert_equal %w(FirstPage HomePage), page_names_by_author['DavidHeinemeierHansson'] end def test_cancel_edit diff --git a/test/unit/web_test.rb b/test/unit/web_test.rb index 875c0bb3..57f47ea7 100644 --- a/test/unit/web_test.rb +++ b/test/unit/web_test.rb @@ -86,6 +86,14 @@ class WebTest < Test::Unit::TestCase @web.select.orphaned_pages.collect{ |page| page.name }.sort end + def test_page_names_by_author + page_names_by_author = webs(:test_wiki).page_names_by_author + assert_equal %w(AnAuthor DavidHeinemeierHansson Guest Me TreeHugger), + page_names_by_author.keys.sort + assert_equal %w(FirstPage HomePage), page_names_by_author['DavidHeinemeierHansson'] + assert_equal %w(Oak), page_names_by_author['TreeHugger'] + end + private def add_sample_pages From 61776995b6a3a15a368278b7d7712f687eb35ffd Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Wed, 28 Sep 2005 02:03:28 +0000 Subject: [PATCH 66/84] Fixes for MySQL compatibility --- db/wiki_references.erbsql | 2 +- script/import_storage | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/db/wiki_references.erbsql b/db/wiki_references.erbsql index 5dbe72d6..6b7115ce 100644 --- a/db/wiki_references.erbsql +++ b/db/wiki_references.erbsql @@ -4,6 +4,6 @@ CREATE TABLE wiki_references ( updated_at <%= @datetime %> NOT NULL, page_id INTEGER NOT NULL, - referenced_name VARCHAR NOT NULL, + referenced_name VARCHAR(60) NOT NULL, link_type CHAR(1) NOT NULL ) <%= create_options %>; \ No newline at end of file diff --git a/script/import_storage b/script/import_storage index a999d0ca..690a192e 100755 --- a/script/import_storage +++ b/script/import_storage @@ -183,7 +183,7 @@ File.open(OPTIONS[:outfile], 'w') { |outfile| puts "Web #{web_name} has #{web.pages.keys.size} pages" web.pages.each_pair do |page_name, page| - outfile.puts "BEGIN TRANSACTION;" + outfile.puts "BEGIN;" outfile.puts sql_insert(:pages, { :id => next_id(:page), From 9ea6e6ae65dc96fde00819a93fa6dad3be272726 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Wed, 28 Sep 2005 04:12:11 +0000 Subject: [PATCH 67/84] Downgrading RedCloth back to 3.0.3 (3.0.4 is said to be buggy) --- CHANGELOG | 3 +- instiki.gemspec | 2 +- rakefile.rb | 71 - test/test_helper.rb | 4 +- vendor/RedCloth-3.0.3/RedCloth.gemspec | 52 + .../bin/redcloth | 2 +- .../doc/CHANGELOG | 11 - .../doc/COPYING | 0 .../doc/README | 6 +- .../doc/REFERENCE | 8 +- .../doc/make.rb | 0 vendor/RedCloth-3.0.3/install.rb | 1032 +++++++++++++ .../lib/redcloth.rb | 137 +- .../run-tests.rb | 4 +- .../tests/code.yml | 0 .../tests/images.yml | 0 .../tests/instiki.yml | 0 .../tests/links.yml | 0 .../tests/lists.yml | 0 .../tests/markdown.yml | 0 .../tests/poignant.yml | 0 .../tests/table.yml | 8 +- .../tests/textism.yml | 9 - vendor/RedCloth-3.0.4/setup.rb | 1376 ----------------- vendor/RedCloth-3.0.4/tests/hard_breaks.yml | 26 - 25 files changed, 1162 insertions(+), 1589 deletions(-) create mode 100644 vendor/RedCloth-3.0.3/RedCloth.gemspec rename vendor/{RedCloth-3.0.4 => RedCloth-3.0.3}/bin/redcloth (70%) rename vendor/{RedCloth-3.0.4 => RedCloth-3.0.3}/doc/CHANGELOG (92%) rename vendor/{RedCloth-3.0.4 => RedCloth-3.0.3}/doc/COPYING (100%) rename vendor/{RedCloth-3.0.4 => RedCloth-3.0.3}/doc/README (91%) rename vendor/{RedCloth-3.0.4 => RedCloth-3.0.3}/doc/REFERENCE (98%) rename vendor/{RedCloth-3.0.4 => RedCloth-3.0.3}/doc/make.rb (100%) create mode 100644 vendor/RedCloth-3.0.3/install.rb rename vendor/{RedCloth-3.0.4 => RedCloth-3.0.3}/lib/redcloth.rb (94%) rename vendor/{RedCloth-3.0.4 => RedCloth-3.0.3}/run-tests.rb (83%) rename vendor/{RedCloth-3.0.4 => RedCloth-3.0.3}/tests/code.yml (100%) rename vendor/{RedCloth-3.0.4 => RedCloth-3.0.3}/tests/images.yml (100%) rename vendor/{RedCloth-3.0.4 => RedCloth-3.0.3}/tests/instiki.yml (100%) rename vendor/{RedCloth-3.0.4 => RedCloth-3.0.3}/tests/links.yml (100%) rename vendor/{RedCloth-3.0.4 => RedCloth-3.0.3}/tests/lists.yml (100%) rename vendor/{RedCloth-3.0.4 => RedCloth-3.0.3}/tests/markdown.yml (100%) rename vendor/{RedCloth-3.0.4 => RedCloth-3.0.3}/tests/poignant.yml (100%) rename vendor/{RedCloth-3.0.4 => RedCloth-3.0.3}/tests/table.yml (95%) rename vendor/{RedCloth-3.0.4 => RedCloth-3.0.3}/tests/textism.yml (97%) delete mode 100644 vendor/RedCloth-3.0.4/setup.rb delete mode 100644 vendor/RedCloth-3.0.4/tests/hard_breaks.yml diff --git a/CHANGELOG b/CHANGELOG index d093c887..6b2dcbda 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,5 @@ - * instiki-ar + * instiki-ar: SQL-based backend (ActiveRecord) - Upgrade to RedCloth 3.0.4 Replaced internal link generator with routing Fixed --daemon option diff --git a/instiki.gemspec b/instiki.gemspec index caeee714..c65ac414 100755 --- a/instiki.gemspec +++ b/instiki.gemspec @@ -24,7 +24,7 @@ spec = Gem::Specification.new do |s| s.has_rdoc = false - s.add_dependency('RedCloth', '= 3.0.4') + s.add_dependency('RedCloth', '= 3.0.3') s.add_dependency('rubyzip', '= 0.5.8') s.add_dependency('rails', '= 0.13.1') s.add_dependency('sqlite3-ruby', '= 1.1.0') diff --git a/rakefile.rb b/rakefile.rb index af9c997f..033b052f 100755 --- a/rakefile.rb +++ b/rakefile.rb @@ -20,36 +20,12 @@ desc "Generate API documentation, show coding stats" task :doc => [ :appdoc, :stats ] -# Look up tests for recently modified sources. -def recent_tests(source_pattern, test_path, touched_since = 10.minutes.ago) - FileList[source_pattern].map do |path| - if File.mtime(path) > touched_since - test = "#{test_path}/#{File.basename(path, '.rb')}_test.rb" - test if File.exists?(test) - end - end.compact -end - -desc 'Test recent changes.' -Rake::TestTask.new(:recent => [ :clone_structure_to_test ]) do |t| - since = TEST_CHANGES_SINCE - touched = FileList['test/**/*_test.rb'].select { |path| File.mtime(path) > since } + - recent_tests('app/models/*.rb', 'test/unit', since) + - recent_tests('app/controllers/*.rb', 'test/functional', since) - - t.libs << 'test' - t.verbose = true - t.test_files = touched.uniq -end -task :test_recent => [ :clone_structure_to_test ] - desc "Run the unit tests in test/unit" Rake::TestTask.new("test_units") { |t| t.libs << "test" t.pattern = 'test/unit/**/*_test.rb' t.verbose = true } -task :test_units => [ :clone_structure_to_test ] desc "Run the functional tests in test/functional" Rake::TestTask.new("test_functional") { |t| @@ -57,7 +33,6 @@ Rake::TestTask.new("test_functional") { |t| t.pattern = 'test/functional/**/*_test.rb' t.verbose = true } -task :test_functional => [ :clone_structure_to_test ] desc "Generate documentation for the application" Rake::RDocTask.new("appdoc") { |rdoc| @@ -118,52 +93,6 @@ task :stats => [ :environment ] do ).to_s end -desc "Recreate the test databases from the development structure" -task :clone_structure_to_test => [ :db_structure_dump, :purge_test_database ] do - abcs = ActiveRecord::Base.configurations - case abcs["test"]["adapter"] - when "mysql" - ActiveRecord::Base.establish_connection(:test) - ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0') - IO.readlines("db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |table| - ActiveRecord::Base.connection.execute(table) - end - when "postgresql" - ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"] - ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"] - ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"] - `psql -U "#{abcs["test"]["username"]}" -f db/#{RAILS_ENV}_structure.sql #{abcs["test"]["database"]}` - when "sqlite", "sqlite3" - `#{abcs[RAILS_ENV]["adapter"]} #{abcs["test"]["dbfile"]} < db/#{RAILS_ENV}_structure.sql` - when "sqlserver" - `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql` - else - raise "Unknown database adapter '#{abcs["test"]["adapter"]}'" - end -end - -desc "Dump the database structure to a SQL file" -task :db_structure_dump => :environment do - abcs = ActiveRecord::Base.configurations - case abcs[RAILS_ENV]["adapter"] - when "mysql" - ActiveRecord::Base.establish_connection(abcs[RAILS_ENV]) - File.open("db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump } - when "postgresql" - ENV['PGHOST'] = abcs[RAILS_ENV]["host"] if abcs[RAILS_ENV]["host"] - ENV['PGPORT'] = abcs[RAILS_ENV]["port"].to_s if abcs[RAILS_ENV]["port"] - ENV['PGPASSWORD'] = abcs[RAILS_ENV]["password"].to_s if abcs[RAILS_ENV]["password"] - `pg_dump -U "#{abcs[RAILS_ENV]["username"]}" -s -x -O -f db/#{RAILS_ENV}_structure.sql #{abcs[RAILS_ENV]["database"]}` - when "sqlite", "sqlite3" - `#{abcs[RAILS_ENV]["adapter"]} #{abcs[RAILS_ENV]["dbfile"]} .schema > db/#{RAILS_ENV}_structure.sql` - when "sqlserver" - `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /f db\\#{RAILS_ENV}_structure.sql /q /A /r` - `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /F db\ /q /A /r` - else - raise "Unknown database adapter '#{abcs["test"]["adapter"]}'" - end -end - desc "Empty the test database" task :purge_test_database => :environment do abcs = ActiveRecord::Base.configurations diff --git a/test/test_helper.rb b/test/test_helper.rb index adf78cf2..f23bebeb 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,8 +1,8 @@ -ENV["RAILS_ENV"] = "test" +ENV['RAILS_ENV'] = 'test' # Expand the path to environment so that Ruby does not load it multiple times # File.expand_path can be removed if Ruby 1.9 is in use. -require File.expand_path(File.dirname(__FILE__) + "/../config/environment") +require File.expand_path(File.dirname(__FILE__) + '/../config/environment') require 'application' require 'test/unit' diff --git a/vendor/RedCloth-3.0.3/RedCloth.gemspec b/vendor/RedCloth-3.0.3/RedCloth.gemspec new file mode 100644 index 00000000..ca64b780 --- /dev/null +++ b/vendor/RedCloth-3.0.3/RedCloth.gemspec @@ -0,0 +1,52 @@ +require 'rubygems' +spec = Gem::Specification.new do |s| + + ## Basic Information + + s.name = 'RedCloth' + s.version = "3.0.3" + s.platform = Gem::Platform::RUBY + s.summary = <<-TXT + RedCloth is a module for using Textile and Markdown in Ruby. Textile and Markdown are text formats. + A very simple text format. Another stab at making readable text that can be converted to HTML. + TXT + s.description = <<-TXT + No need to use verbose HTML to build your docs, your blogs, your pages. Textile gives you readable text while you're writing and beautiful text for your readers. And if you need to break out into HTML, Textile will allow you to do so. + + Textile also handles some subtleties of formatting which will enhance your document's readability: + + * Single- and double-quotes around words or phrases are converted to curly quotations, much easier on + the eye. "Observe!" + + * Double hyphens are replaced with an em-dash. Observe -- very nice! + + * Single hyphens are replaced with en-dashes. Observe - so cute! + + * Triplets of periods become an ellipsis. Observe... + + * The letter 'x' becomes a dimension sign when used alone. Observe: 2 x 2. + + * Conversion of ==(TM)== to (TM), ==(R)== to (R), ==(C)== to (C). + + For more on Textile's language, hop over to "A Textile Reference":http://hobix.com/textile/. For more + on Markdown, see "Daring Fireball's page":http://daringfireball.net/projects/markdown/. + TXT + + ## Include tests, libs, docs + + s.files = ['bin/**/*', 'tests/**/*', 'lib/**/*', 'docs/**/*', 'run-tests.rb'].collect do |dirglob| + Dir.glob(dirglob) + end.flatten.delete_if {|item| item.include?("CVS")} + + ## Load-time details + + s.require_path = 'lib' + s.autorequire = 'redcloth' + + ## Author and project details + + s.author = "Why the Lucky Stiff" + s.email = "why@ruby-lang.org" + s.rubyforge_project = "redcloth" + s.homepage = "http://www.whytheluckystiff.net/ruby/redcloth/" +end diff --git a/vendor/RedCloth-3.0.4/bin/redcloth b/vendor/RedCloth-3.0.3/bin/redcloth similarity index 70% rename from vendor/RedCloth-3.0.4/bin/redcloth rename to vendor/RedCloth-3.0.3/bin/redcloth index ae210b6a..81abf7db 100644 --- a/vendor/RedCloth-3.0.4/bin/redcloth +++ b/vendor/RedCloth-3.0.3/bin/redcloth @@ -1,3 +1,3 @@ -#!/usr/bin/ruby18 +#!/usr/local/bin/ruby18 require 'redcloth' puts RedCloth.new( ARGF.read ).to_html diff --git a/vendor/RedCloth-3.0.4/doc/CHANGELOG b/vendor/RedCloth-3.0.3/doc/CHANGELOG similarity index 92% rename from vendor/RedCloth-3.0.4/doc/CHANGELOG rename to vendor/RedCloth-3.0.3/doc/CHANGELOG index 0581447c..7b5f9cf3 100644 --- a/vendor/RedCloth-3.0.4/doc/CHANGELOG +++ b/vendor/RedCloth-3.0.3/doc/CHANGELOG @@ -1,15 +1,4 @@ --- %YAML:1.0 -- version: 3.0.4 - date: 2005-02-18 - changes: - - The caps class doesn't swallow spaces. - - Horizontal rules required to be on an empty line. - - Hard breaks don't screw with Markdown headers any longer. - - Fixed error triggered by complex lists. - - Inline markups need to be butted up against enclosing text, no spaces. - - Fixed problem with intermingled single and double quotes. - - Brought back lite_mode. - - version: 3.0.3 date: 2005-02-06 changes: diff --git a/vendor/RedCloth-3.0.4/doc/COPYING b/vendor/RedCloth-3.0.3/doc/COPYING similarity index 100% rename from vendor/RedCloth-3.0.4/doc/COPYING rename to vendor/RedCloth-3.0.3/doc/COPYING diff --git a/vendor/RedCloth-3.0.4/doc/README b/vendor/RedCloth-3.0.3/doc/README similarity index 91% rename from vendor/RedCloth-3.0.4/doc/README rename to vendor/RedCloth-3.0.3/doc/README index 1c0b0f38..1ae4560c 100644 --- a/vendor/RedCloth-3.0.4/doc/README +++ b/vendor/RedCloth-3.0.3/doc/README @@ -4,7 +4,9 @@ p=. !redcloth3-title.png! h4. Get RedCloth 3 -p(example1). *Stable version:* "3.0.3":http://rubyforge.org/frs/download.php/2896/RedCloth-3.0.3.tar.gz +p(example1). *Stable version:* "2.0.11":http://rubyforge.org/frs/download.php/698/redcloth-2.0.11.tar.gz + +p(example1). *Unstable version:* "3.0.2":http://rubyforge.org/frs/download.php/2852/RedCloth-3.0.2.tar.gz Take a complete tour of Textile at "A Textile Reference":http://hobix.com/textile/. @@ -89,7 +91,7 @@ To install RedCloth via RubyGems: gem install RedCloth
    -Or "download RedCloth":http://rubyforge.org/frs/download.php/2896/RedCloth-3.0.3.tar.gz and simply run the install.rb like so: +Or "download RedCloth":http://rubyforge.org/frs/download.php/2852/RedCloth-3.0.2.tar.gz and simply run the install.rb like so:
       ruby install.rb config
    diff --git a/vendor/RedCloth-3.0.4/doc/REFERENCE b/vendor/RedCloth-3.0.3/doc/REFERENCE
    similarity index 98%
    rename from vendor/RedCloth-3.0.4/doc/REFERENCE
    rename to vendor/RedCloth-3.0.3/doc/REFERENCE
    index c7236e89..a3e5bec0 100644
    --- a/vendor/RedCloth-3.0.4/doc/REFERENCE
    +++ b/vendor/RedCloth-3.0.3/doc/REFERENCE
    @@ -24,9 +24,9 @@
         if it's found in a @pre@ or @code@ block.
       - !!example "I am very serious.\n\n
    \n  I am very serious.\n
    " - h4. Line Breaks - - Line breaks are ignored. + - Line breaks are converted to HTML breaks. - !!example "I spoke.\nAnd none replied." - - Line breaks can be converted to HTML breaks by setting @hard_breaks@. + - Line breaks can be disabled in RedCloth by turning on @fold_lines@. - h4. Entities - Single- and double-quotes around words or phrases are converted to curly quotations, much easier on the eye. - !!example '"Observe!"' @@ -78,9 +78,9 @@ - Pluses around a passage indicate its insertion. - !!example "You are a +pleasant+ child." - To superscript a phrase, surround with carets. - - !!example "a^2^ + b^2^ = c^2^" + - !!example "a ^2^ + b ^2^ = c ^2^" - To subscript, surround with tildes. - - !!example "log~2~x" + - !!example "log ~2~ x" - h4. HTML-Specific - Lastly, if you find yourself needing to customize the style of a passage, use percent symbols to translate the passage as an HTML span. diff --git a/vendor/RedCloth-3.0.4/doc/make.rb b/vendor/RedCloth-3.0.3/doc/make.rb similarity index 100% rename from vendor/RedCloth-3.0.4/doc/make.rb rename to vendor/RedCloth-3.0.3/doc/make.rb diff --git a/vendor/RedCloth-3.0.3/install.rb b/vendor/RedCloth-3.0.3/install.rb new file mode 100644 index 00000000..2313f9e9 --- /dev/null +++ b/vendor/RedCloth-3.0.3/install.rb @@ -0,0 +1,1032 @@ +#!/usr/local/bin/ruby +# +# This file is automatically generated. DO NOT MODIFY! +# +# install.rb +# +# Copyright (c) 2000-2002 Minero Aoki +# +# This program is free software. +# You can distribute/modify this program under the terms of +# the GNU Lesser General Public License version 2. +# + +### begin compat.rb + +unless Enumerable.instance_methods.include? 'inject' then +module Enumerable + def inject( result ) + each do |i| + result = yield(result, i) + end + result + end +end +end + +def File.read_all( fname ) + File.open(fname, 'rb') {|f| return f.read } +end + +def File.write( fname, str ) + File.open(fname, 'wb') {|f| f.write str } +end + +### end compat.rb +### begin config.rb + +if i = ARGV.index(/\A--rbconfig=/) then + file = $' + ARGV.delete_at(i) + require file +else + require 'rbconfig' +end + + +class ConfigTable + + c = ::Config::CONFIG + + rubypath = c['bindir'] + '/' + c['ruby_install_name'] + + major = c['MAJOR'].to_i + minor = c['MINOR'].to_i + teeny = c['TEENY'].to_i + version = "#{major}.#{minor}" + + # ruby ver. >= 1.4.4? + newpath_p = ((major >= 2) or + ((major == 1) and + ((minor >= 5) or + ((minor == 4) and (teeny >= 4))))) + + re = Regexp.new('\A' + Regexp.quote(c['prefix'])) + subprefix = lambda {|path| + re === path and path.sub(re, '$prefix') + } + + if c['rubylibdir'] then + # 1.6.3 < V + stdruby = subprefix.call(c['rubylibdir']) + siteruby = subprefix.call(c['sitedir']) + versite = subprefix.call(c['sitelibdir']) + sodir = subprefix.call(c['sitearchdir']) + elsif newpath_p then + # 1.4.4 <= V <= 1.6.3 + stdruby = "$prefix/lib/ruby/#{version}" + siteruby = subprefix.call(c['sitedir']) + versite = siteruby + '/' + version + sodir = "$site-ruby/#{c['arch']}" + else + # V < 1.4.4 + stdruby = "$prefix/lib/ruby/#{version}" + siteruby = "$prefix/lib/ruby/#{version}/site_ruby" + versite = siteruby + sodir = "$site-ruby/#{c['arch']}" + end + + DESCRIPTER = [ + [ 'prefix', [ c['prefix'], + 'path', + 'path prefix of target environment' ] ], + [ 'std-ruby', [ stdruby, + 'path', + 'the directory for standard ruby libraries' ] ], + [ 'site-ruby-common', [ siteruby, + 'path', + 'the directory for version-independent non-standard ruby libraries' ] ], + [ 'site-ruby', [ versite, + 'path', + 'the directory for non-standard ruby libraries' ] ], + [ 'bin-dir', [ '$prefix/bin', + 'path', + 'the directory for commands' ] ], + [ 'rb-dir', [ '$site-ruby', + 'path', + 'the directory for ruby scripts' ] ], + [ 'so-dir', [ sodir, + 'path', + 'the directory for ruby extentions' ] ], + [ 'data-dir', [ '$prefix/share', + 'path', + 'the directory for shared data' ] ], + [ 'ruby-path', [ rubypath, + 'path', + 'path to set to #! line' ] ], + [ 'ruby-prog', [ rubypath, + 'name', + 'the ruby program using for installation' ] ], + [ 'make-prog', [ 'make', + 'name', + 'the make program to compile ruby extentions' ] ], + [ 'without-ext', [ 'no', + 'yes/no', + 'does not compile/install ruby extentions' ] ] + ] + + SAVE_FILE = 'config.save' + + def ConfigTable.each_name( &block ) + keys().each( &block ) + end + + def ConfigTable.keys + DESCRIPTER.collect {|k,*dummy| k } + end + + def ConfigTable.each_definition( &block ) + DESCRIPTER.each( &block ) + end + + def ConfigTable.get_entry( name ) + name, ent = DESCRIPTER.assoc(name) + ent + end + + def ConfigTable.get_entry!( name ) + get_entry(name) or raise ArgumentError, "no such config: #{name}" + end + + def ConfigTable.add_entry( name, vals ) + ConfigTable::DESCRIPTER.push [name,vals] + end + + def ConfigTable.remove_entry( name ) + get_entry name or raise ArgumentError, "no such config: #{name}" + DESCRIPTER.delete_if {|n,arr| n == name } + end + + def ConfigTable.config_key?( name ) + get_entry(name) ? true : false + end + + def ConfigTable.bool_config?( name ) + ent = get_entry(name) or return false + ent[1] == 'yes/no' + end + + def ConfigTable.value_config?( name ) + ent = get_entry(name) or return false + ent[1] != 'yes/no' + end + + def ConfigTable.path_config?( name ) + ent = get_entry(name) or return false + ent[1] == 'path' + end + + + class << self + + alias newobj new + + def new + c = newobj() + c.__send__ :init + c + end + + def load + c = newobj() + File.file? SAVE_FILE or + raise InstallError, "#{File.basename $0} config first" + File.foreach( SAVE_FILE ) do |line| + k, v = line.split( '=', 2 ) + c.instance_eval { + @table[k] = v.strip + } + end + c + end + + end + + def initialize + @table = {} + end + + def init + DESCRIPTER.each do |k, (default, vname, desc, default2)| + @table[k] = default + end + end + private :init + + def save + File.open( SAVE_FILE, 'w' ) {|f| + @table.each do |k, v| + f.printf "%s=%s\n", k, v if v + end + } + end + + def []=( k, v ) + ConfigTable.config_key? k or raise InstallError, "unknown config option #{k}" + if ConfigTable.path_config? k then + @table[k] = (v[0,1] != '$') ? File.expand_path(v) : v + else + @table[k] = v + end + end + + def []( key ) + @table[key] or return nil + @table[key].gsub( %r<\$([^/]+)> ) { self[$1] } + end + + def set_raw( key, val ) + @table[key] = val + end + + def get_raw( key ) + @table[key] + end + +end + + +class MetaConfigEnvironment + + def self.eval_file( file ) + return unless File.file? file + new.instance_eval File.read_all(file), file, 1 + end + + private + + def config_names + ConfigTable.keys + end + + def config?( name ) + ConfigTable.config_key? name + end + + def bool_config?( name ) + ConfigTable.bool_config? name + end + + def value_config?( name ) + ConfigTable.value_config? name + end + + def path_config?( name ) + ConfigTable.path_config? name + end + + def add_config( name, argname, default, desc ) + ConfigTable.add_entry name,[default,argname,desc] + end + + def add_path_config( name, default, desc ) + add_config name, 'path', default, desc + end + + def add_bool_config( name, default, desc ) + add_config name, 'yes/no', default ? 'yes' : 'no', desc + end + + def set_config_default( name, default ) + if bool_config? name then + ConfigTable.get_entry!(name)[0] = default ? 'yes' : 'no' + else + ConfigTable.get_entry!(name)[0] = default + end + end + + def remove_config( name ) + ent = ConfigTable.get_entry(name) + ConfigTable.remove_entry name + ent + end + +end + +### end config.rb +### begin fileop.rb + +module FileOperations + + def mkdir_p( dname, prefix = nil ) + dname = prefix + dname if prefix + $stderr.puts "mkdir -p #{dname}" if verbose? + return if no_harm? + + # does not check '/'... it's too abnormal case + dirs = dname.split(%r_(?=/)_) + if /\A[a-z]:\z/i === dirs[0] then + disk = dirs.shift + dirs[0] = disk + dirs[0] + end + dirs.each_index do |idx| + path = dirs[0..idx].join('') + Dir.mkdir path unless dir? path + end + end + + def rm_f( fname ) + $stderr.puts "rm -f #{fname}" if verbose? + return if no_harm? + + if File.exist? fname or File.symlink? fname then + File.chmod 0777, fname + File.unlink fname + end + end + + def rm_rf( dn ) + $stderr.puts "rm -rf #{dn}" if verbose? + return if no_harm? + + Dir.chdir dn + Dir.foreach('.') do |fn| + next if fn == '.' + next if fn == '..' + if dir? fn then + verbose_off { + rm_rf fn + } + else + verbose_off { + rm_f fn + } + end + end + Dir.chdir '..' + Dir.rmdir dn + end + + def mv( src, dest ) + rm_f dest + begin + File.link src, dest + rescue + File.write dest, File.read_all(src) + File.chmod File.stat(src).mode, dest + end + rm_f src + end + + def install( from, dest, mode, prefix = nil ) + $stderr.puts "install #{from} #{dest}" if verbose? + return if no_harm? + + realdest = prefix + dest if prefix + if dir? realdest then + realdest += '/' + File.basename(from) + end + str = File.read_all(from) + if diff? str, realdest then + verbose_off { + rm_f realdest if File.exist? realdest + } + File.write realdest, str + File.chmod mode, realdest + + File.open( objdir + '/InstalledFiles', 'a' ) {|f| f.puts realdest } + end + end + + def diff?( orig, targ ) + return true unless File.exist? targ + orig != File.read_all(targ) + end + + def command( str ) + $stderr.puts str if verbose? + system str or raise RuntimeError, "'system #{str}' failed" + end + + def ruby( str ) + command config('ruby-prog') + ' ' + str + end + + def dir?( dname ) + # for corrupted windows stat() + File.directory?( (dname[-1,1] == '/') ? dname : dname + '/' ) + end + + def all_files( dname ) + Dir.open( dname ) {|d| + return d.find_all {|n| File.file? "#{dname}/#{n}" } + } + end + + def all_dirs( dname ) + Dir.open( dname ) {|d| + return d.find_all {|n| dir? "#{dname}/#{n}" } - %w(. ..) + } + end + +end + +### end fileop.rb +### begin base.rb + +class InstallError < StandardError; end + + +class Installer + + Version = '3.1.2' + Copyright = 'Copyright (c) 2000-2002 Minero Aoki' + + + @toplevel = nil + + def self.declear_toplevel_installer( inst ) + @toplevel and + raise ArgumentError, 'more than one toplevel installer decleared' + @toplevel = inst + end + + def self.toplevel_installer + @toplevel + end + + + FILETYPES = %w( bin lib ext data ) + + include FileOperations + + def initialize( config, opt, srcroot, objroot ) + @config = config + @options = opt + @srcdir = File.expand_path(srcroot) + @objdir = File.expand_path(objroot) + @currdir = '.' + end + + def inspect + "#<#{type} #{__id__}>" + end + + # + # configs/options + # + + def get_config( key ) + @config[key] + end + + alias config get_config + + def set_config( key, val ) + @config[key] = val + end + + def no_harm? + @options['no-harm'] + end + + def verbose? + @options['verbose'] + end + + def verbose_off + save, @options['verbose'] = @options['verbose'], false + yield + @options['verbose'] = save + end + + # + # srcdir/objdir + # + + attr_reader :srcdir + alias srcdir_root srcdir + alias package_root srcdir + + def curr_srcdir + "#{@srcdir}/#{@currdir}" + end + + attr_reader :objdir + alias objdir_root objdir + + def curr_objdir + "#{@objdir}/#{@currdir}" + end + + def srcfile( path ) + curr_srcdir + '/' + path + end + + def srcexist?( path ) + File.exist? srcfile(path) + end + + def srcdirectory?( path ) + dir? srcfile(path) + end + + def srcfile?( path ) + File.file? srcfile(path) + end + + def srcentries( path = '.' ) + Dir.open( curr_srcdir + '/' + path ) {|d| + return d.to_a - %w(. ..) - hookfilenames + } + end + + def srcfiles( path = '.' ) + srcentries(path).find_all {|fname| + File.file? File.join(curr_srcdir, path, fname) + } + end + + def srcdirectories( path = '.' ) + srcentries(path).find_all {|fname| + dir? File.join(curr_srcdir, path, fname) + } + end + + def dive_into( rel ) + return unless dir? "#{@srcdir}/#{rel}" + + dir = File.basename(rel) + Dir.mkdir dir unless dir? dir + save = Dir.pwd + Dir.chdir dir + $stderr.puts '---> ' + rel if verbose? + @currdir = rel + yield + Dir.chdir save + $stderr.puts '<--- ' + rel if verbose? + @currdir = File.dirname(rel) + end + + # + # config + # + + def exec_config + exec_task_traverse 'config' + end + + def config_dir_bin( rel ) + end + + def config_dir_lib( rel ) + end + + def config_dir_ext( rel ) + extconf if extdir? curr_srcdir + end + + def extconf + opt = @options['config-opt'].join(' ') + command "#{config('ruby-prog')} #{curr_srcdir}/extconf.rb #{opt}" + end + + def config_dir_data( rel ) + end + + # + # setup + # + + def exec_setup + exec_task_traverse 'setup' + end + + def setup_dir_bin( relpath ) + all_files( curr_srcdir ).each do |fname| + add_rubypath "#{curr_srcdir}/#{fname}" + end + end + + SHEBANG_RE = /\A\#!\s*\S*ruby\S*/ + + def add_rubypath( path ) + $stderr.puts %Q if verbose? + return if no_harm? + + tmpfile = File.basename(path) + '.tmp' + begin + File.open( path ) {|r| + File.open( tmpfile, 'w' ) {|w| + first = r.gets + return unless SHEBANG_RE === first # reject '/usr/bin/env ruby' + + w.print first.sub( SHEBANG_RE, '#!' + config('ruby-path') ) + w.write r.read + } } + mv tmpfile, File.basename(path) + ensure + rm_f tmpfile if File.exist? tmpfile + end + end + + def setup_dir_lib( relpath ) + end + + def setup_dir_ext( relpath ) + if extdir? curr_srcdir then + make + end + end + + def make + command config('make-prog') + end + + def setup_dir_data( relpath ) + end + + # + # install + # + + def exec_install + exec_task_traverse 'install' + end + + def install_dir_bin( rel ) + install_files targfiles, config('bin-dir') + '/' + rel, 0755 + end + + def install_dir_lib( rel ) + install_files targfiles, config('rb-dir') + '/' + rel, 0644 + begin + require 'rdoc/rdoc' + ri_site = true + if RDOC_VERSION =~ /^0\./ + require 'rdoc/options' + unless Options::OptionList::OPTION_LIST.assoc('--ri-site') + ri_site = false + end + end + if ri_site + r = RDoc::RDoc.new + r.document(%w{--ri-site}) + end + rescue + puts "** Unable to install Ri documentation for RedCloth **" + end + end + + def install_dir_ext( rel ) + if extdir? curr_srcdir then + install_dir_ext_main File.dirname(rel) + end + end + + def install_dir_ext_main( rel ) + install_files allext('.'), config('so-dir') + '/' + rel, 0555 + end + + def install_dir_data( rel ) + install_files targfiles, config('data-dir') + '/' + rel, 0644 + end + + def install_files( list, dest, mode ) + mkdir_p dest, @options['install-prefix'] + list.each do |fname| + install fname, dest, mode, @options['install-prefix'] + end + end + + def targfiles + (targfilenames() - hookfilenames()).collect {|fname| + File.exist?(fname) ? fname : File.join(curr_srcdir(), fname) + } + end + + def targfilenames + [ curr_srcdir(), '.' ].inject([]) {|ret, dir| + ret | all_files(dir) + } + end + + def hookfilenames + %w( pre-%s post-%s pre-%s.rb post-%s.rb ).collect {|fmt| + %w( config setup install clean ).collect {|t| sprintf fmt, t } + }.flatten + end + + def allext( dir ) + _allext(dir) or raise InstallError, + "no extention exists: Have you done 'ruby #{$0} setup' ?" + end + + DLEXT = /\.#{ ::Config::CONFIG['DLEXT'] }\z/ + + def _allext( dir ) + Dir.open( dir ) {|d| + return d.find_all {|fname| DLEXT === fname } + } + end + + # + # clean + # + + def exec_clean + exec_task_traverse 'clean' + rm_f 'config.save' + rm_f 'InstalledFiles' + end + + def clean_dir_bin( rel ) + end + + def clean_dir_lib( rel ) + end + + def clean_dir_ext( rel ) + clean + end + + def clean + command config('make-prog') + ' clean' if File.file? 'Makefile' + end + + def clean_dir_data( rel ) + end + + # + # lib + # + + def exec_task_traverse( task ) + run_hook 'pre-' + task + FILETYPES.each do |type| + if config('without-ext') == 'yes' and type == 'ext' then + $stderr.puts 'skipping ext/* by user option' if verbose? + next + end + traverse task, type, task + '_dir_' + type + end + run_hook 'post-' + task + end + + def traverse( task, rel, mid ) + dive_into( rel ) { + run_hook 'pre-' + task + __send__ mid, rel.sub( %r_\A.*?(?:/|\z)_, '' ) + all_dirs( curr_srcdir ).each do |d| + traverse task, rel + '/' + d, mid + end + run_hook 'post-' + task + } + end + + def run_hook( name ) + try_run_hook curr_srcdir + '/' + name or + try_run_hook curr_srcdir + '/' + name + '.rb' + end + + def try_run_hook( fname ) + return false unless File.file? fname + + env = self.dup + begin + env.instance_eval File.read_all(fname), fname, 1 + rescue + raise InstallError, "hook #{fname} failed:\n" + $!.message + end + true + end + + def extdir?( dir ) + File.exist? dir + '/MANIFEST' + end + +end + +### end base.rb +### begin toplevel.rb + +class ToplevelInstaller < Installer + + TASKS = [ + [ 'config', 'saves your configurations' ], + [ 'show', 'shows current configuration' ], + [ 'setup', 'compiles extention or else' ], + [ 'install', 'installs files' ], + [ 'clean', "does `make clean' for each extention" ] + ] + + + def initialize( root ) + super nil, {'verbose' => true}, root, '.' + Installer.declear_toplevel_installer self + end + + + def execute + run_metaconfigs + + case task = parsearg_global() + when 'config' + @config = ConfigTable.new + else + @config = ConfigTable.load + end + parsearg_TASK task + + exectask task + end + + + def run_metaconfigs + MetaConfigEnvironment.eval_file "#{srcdir_root}/#{metaconfig}" + end + + def metaconfig + 'metaconfig' + end + + + def exectask( task ) + if task == 'show' then + exec_show + else + try task + end + end + + def try( task ) + $stderr.printf "#{File.basename $0}: entering %s phase...\n", task if verbose? + begin + __send__ 'exec_' + task + rescue + $stderr.printf "%s failed\n", task + raise + end + $stderr.printf "#{File.basename $0}: %s done.\n", task if verbose? + end + + # + # processing arguments + # + + def parsearg_global + task_re = /\A(?:#{TASKS.collect {|i| i[0] }.join '|'})\z/ + + while arg = ARGV.shift do + case arg + when /\A\w+\z/ + task_re === arg or raise InstallError, "wrong task: #{arg}" + return arg + + when '-q', '--quiet' + @options['verbose'] = false + + when '--verbose' + @options['verbose'] = true + + when '-h', '--help' + print_usage $stdout + exit 0 + + when '-v', '--version' + puts "#{File.basename $0} version #{Version}" + exit 0 + + when '--copyright' + puts Copyright + exit 0 + + else + raise InstallError, "unknown global option '#{arg}'" + end + end + + raise InstallError, 'no task or global option given' + end + + + def parsearg_TASK( task ) + mid = "parsearg_#{task}" + if respond_to? mid, true then + __send__ mid + else + ARGV.empty? or + raise InstallError, "#{task}: unknown options: #{ARGV.join ' '}" + end + end + + def parsearg_config + re = /\A--(#{ConfigTable.keys.join '|'})(?:=(.*))?\z/ + @options['config-opt'] = [] + + while i = ARGV.shift do + if /\A--?\z/ === i then + @options['config-opt'] = ARGV.dup + break + end + m = re.match(i) or raise InstallError, "config: unknown option #{i}" + name, value = m.to_a[1,2] + if value then + if ConfigTable.bool_config?(name) then + /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i === value or raise InstallError, "config: --#{name} allows only yes/no for argument" + value = (/\Ay(es)?|\At(rue)/i === value) ? 'yes' : 'no' + end + else + ConfigTable.bool_config?(name) or raise InstallError, "config: --#{name} requires argument" + value = 'yes' + end + @config[name] = value + end + end + + def parsearg_install + @options['no-harm'] = false + @options['install-prefix'] = '' + while a = ARGV.shift do + case a + when /\A--no-harm\z/ + @options['no-harm'] = true + when /\A--prefix=(.*)\z/ + path = $1 + path = File.expand_path(path) unless path[0,1] == '/' + @options['install-prefix'] = path + else + raise InstallError, "install: unknown option #{a}" + end + end + end + + + def print_usage( out ) + out.puts + out.puts 'Usage:' + out.puts " ruby #{File.basename $0} " + out.puts " ruby #{File.basename $0} [] []" + + fmt = " %-20s %s\n" + out.puts + out.puts 'Global options:' + out.printf fmt, '-q,--quiet', 'suppress message outputs' + out.printf fmt, ' --verbose', 'output messages verbosely' + out.printf fmt, '-h,--help', 'print this message' + out.printf fmt, '-v,--version', 'print version and quit' + out.printf fmt, '--copyright', 'print copyright and quit' + + out.puts + out.puts 'Tasks:' + TASKS.each do |name, desc| + out.printf " %-10s %s\n", name, desc + end + + out.puts + out.puts 'Options for config:' + ConfigTable.each_definition do |name, (default, arg, desc, default2)| + out.printf " %-20s %s [%s]\n", + '--'+ name + (ConfigTable.bool_config?(name) ? '' : '='+arg), + desc, + default2 || default + end + out.printf " %-20s %s [%s]\n", + '--rbconfig=path', 'your rbconfig.rb to load', "running ruby's" + + out.puts + out.puts 'Options for install:' + out.printf " %-20s %s [%s]\n", + '--no-harm', 'only display what to do if given', 'off' + + out.puts + end + + # + # config + # + + def exec_config + super + @config.save + end + + # + # show + # + + def exec_show + ConfigTable.each_name do |k| + v = @config.get_raw(k) + if not v or v.empty? then + v = '(not specified)' + end + printf "%-10s %s\n", k, v + end + end + +end + +### end toplevel.rb + +if $0 == __FILE__ then + begin + installer = ToplevelInstaller.new( Dir.pwd ) + installer.execute + rescue + raise if $DEBUG + $stderr.puts $!.message + $stderr.puts "try 'ruby #{$0} --help' for usage" + exit 1 + end +end diff --git a/vendor/RedCloth-3.0.4/lib/redcloth.rb b/vendor/RedCloth-3.0.3/lib/redcloth.rb similarity index 94% rename from vendor/RedCloth-3.0.4/lib/redcloth.rb rename to vendor/RedCloth-3.0.3/lib/redcloth.rb index 1228af6e..03df12b3 100644 --- a/vendor/RedCloth-3.0.4/lib/redcloth.rb +++ b/vendor/RedCloth-3.0.3/lib/redcloth.rb @@ -166,7 +166,7 @@ class RedCloth < String - VERSION = '3.0.4' + VERSION = '3.0.3' DEFAULT_RULES = [:textile, :markdown] # @@ -193,18 +193,6 @@ class RedCloth < String # attr_accessor :hard_breaks - # Accessor for toggling lite mode. - # - # In lite mode, block-level rules are ignored. This means - # that tables, paragraphs, lists, and such aren't available. - # Only the inline markup for bold, italics, entities and so on. - # - # r = RedCloth.new( "And then? She *fell*!", [:lite_mode] ) - # r.to_html - # #=> "And then? She fell!" - # - attr_accessor :lite_mode - # # Accessor for toggling span caps. # @@ -231,7 +219,7 @@ class RedCloth < String # inline_textile_image:: Textile inline images # inline_textile_link:: Textile inline links # inline_textile_span:: Textile inline spans - # glyphs_textile:: Textile entities (such as em-dashes and smart quotes) + # inline_textile_glyphs:: Textile entities (such as em-dashes and smart quotes) # # == Markdown # @@ -272,7 +260,7 @@ class RedCloth < String @shelf = [] textile_rules = [:refs_textile, :block_textile_table, :block_textile_lists, :block_textile_prefix, :inline_textile_image, :inline_textile_link, - :inline_textile_code, :inline_textile_span, :glyphs_textile] + :inline_textile_code, :inline_textile_glyphs, :inline_textile_span] markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule, :block_markdown_bq, :block_markdown_lists, :inline_markdown_reflink, :inline_markdown_link] @@ -290,16 +278,14 @@ class RedCloth < String # standard clean up incoming_entities text clean_white_space text + no_textile text # start processor @pre_list = [] rip_offtags text - no_textile text - hard_break text - unless @lite_mode - refs text - blocks text - end + hard_break text + refs text + blocks text inline text smooth_offtags text @@ -347,8 +333,6 @@ class RedCloth < String C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)" # PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' ) PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' ) - PUNCT_NOQ = Regexp::quote( '!"#$&\',./:;=?@\\`|' ) - PUNCT_Q = Regexp::quote( '*-_+^~%' ) HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)' # Text markup tags, don't conflict with block tags @@ -358,6 +342,41 @@ class RedCloth < String 'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo' ] + # Elements to handle + GLYPHS = [ + # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1’\2' ], # single closing + [ /([^\s\[{(>])\'/, '\1’' ], # single closing + [ /\'(?=\s|s\b|[#{PUNCT}])/, '’' ], # single closing + [ /\'/, '‘' ], # single opening + # [ /([^\s\[{(])?"(\s|:|$)/, '\1”\2' ], # double closing + [ /([^\s\[{(>])"/, '\1”' ], # double closing + [ /"(?=\s|[#{PUNCT}])/, '”' ], # double closing + [ /"/, '“' ], # double opening + [ /\b( )?\.{3}/, '\1…' ], # ellipsis + [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '\1' ], # 3+ uppercase acronym + [ /(^|[^"][>\s])([A-Z][A-Z0-9 ]{2,})([^\2\3', :no_span_caps ], # 3+ uppercase caps + [ /(\.\s)?\s?--\s?/, '\1—' ], # em dash + [ /\s->\s/, ' → ' ], # right arrow + [ /\s-\s/, ' – ' ], # en dash + [ /(\d+) ?x ?(\d+)/, '\1×\2' ], # dimension sign + [ /\b ?[(\[]TM[\])]/i, '™' ], # trademark + [ /\b ?[(\[]R[\])]/i, '®' ], # registered + [ /\b ?[(\[]C[\])]/i, '©' ] # copyright + ] + + H_ALGN_VALS = { + '<' => 'left', + '=' => 'center', + '>' => 'right', + '<>' => 'justify' + } + + V_ALGN_VALS = { + '^' => 'top', + '-' => 'middle', + '~' => 'bottom' + } + QTAGS = [ ['**', 'b'], ['*', 'strong'], @@ -379,56 +398,19 @@ class RedCloth < String (#{rcq}) (#{C}) (?::(\S+?))? - (\S.*?\S|\S) + (.+?) #{rcq} (?=\W)/x else /(#{rcq}) (#{C}) - (?::(\S+))? - (\S.*?\S|\S) + (?::(\S+?))? + (.+?) #{rcq}/xm end [rc, ht, re, rtype] end - # Elements to handle - GLYPHS = [ - # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1’\2' ], # single closing - [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1’' ], # single closing - [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '’' ], # single closing - [ /\'/, '‘' ], # single opening - [ //, '>' ], # greater-than - # [ /([^\s\[{(])?"(\s|:|$)/, '\1”\2' ], # double closing - [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1”' ], # double closing - [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '”' ], # double closing - [ /"/, '“' ], # double opening - [ /\b( )?\.{3}/, '\1…' ], # ellipsis - [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '\1' ], # 3+ uppercase acronym - [ /(^|[^"][>\s])([A-Z][A-Z0-9 ]+[A-Z0-9])([^\2\3', :no_span_caps ], # 3+ uppercase caps - [ /(\.\s)?\s?--\s?/, '\1—' ], # em dash - [ /\s->\s/, ' → ' ], # right arrow - [ /\s-\s/, ' – ' ], # en dash - [ /(\d+) ?x ?(\d+)/, '\1×\2' ], # dimension sign - [ /\b ?[(\[]TM[\])]/i, '™' ], # trademark - [ /\b ?[(\[]R[\])]/i, '®' ], # registered - [ /\b ?[(\[]C[\])]/i, '©' ] # copyright - ] - - H_ALGN_VALS = { - '<' => 'left', - '=' => 'center', - '>' => 'right', - '<>' => 'justify' - } - - V_ALGN_VALS = { - '^' => 'top', - '-' => 'middle', - '~' => 'bottom' - } - # # Flexible HTML escaping # @@ -548,7 +530,7 @@ class RedCloth < String depth.pop end end - if depth.last and depth.last.length == tl.length + if depth.last.length == tl.length lines[line_id - 1] << '' end end @@ -595,7 +577,7 @@ class RedCloth < String end def hard_break( text ) - text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1
    " ) if hard_breaks + text.gsub!( /(.)\n(?! *[#*\s|]|$)/, "\\1
    " ) if hard_breaks end BLOCKS_GROUP_RE = /\n{2,}(?! )/m @@ -723,9 +705,9 @@ class RedCloth < String end end - MARKDOWN_RULE_RE = /^(#{ + MARKDOWN_RULE_RE = /^#{ ['*', '-', '_'].collect { |ch| '( ?' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' ) - })$/ + }$/ def block_markdown_rule( text ) text.gsub!( MARKDOWN_RULE_RE ) do |blk| @@ -737,6 +719,9 @@ class RedCloth < String def block_markdown_lists( text ) end + def inline_markdown_link( text ) + end + def inline_textile_span( text ) QTAGS.each do |qtag_rc, ht, qtag_re, rtype| text.gsub!( qtag_re ) do |m| @@ -918,12 +903,12 @@ class RedCloth < String def shelve( val ) @shelf << val - " :redsh##{ @shelf.length }:" + " <#{ @shelf.length }>" end def retrieve( text ) @shelf.each_with_index do |r, i| - text.gsub!( " :redsh##{ i + 1 }:", r ) + text.gsub!( " <#{ i + 1 }>", r ) end end @@ -980,7 +965,7 @@ class RedCloth < String HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m - def glyphs_textile( text, level = 0 ) + def inline_textile_glyphs( text, level = 0 ) if text !~ HASTAG_MATCH pgl text footnote_ref text @@ -996,11 +981,11 @@ class RedCloth < String codepre = 0 if codepre < 0 end elsif codepre.zero? - glyphs_textile( line, level + 1 ) + inline_textile_glyphs( line, level + 1 ) else htmlesc( line, :NoQuotes ) end - # p [level, codepre, line] + ## p [level, codepre, orig_line, line] line end @@ -1048,10 +1033,8 @@ class RedCloth < String end def inline( text ) - [/^inline_/, /^glyphs_/].each do |meth_re| - @rules.each do |rule_name| - method( rule_name ).call( text ) if rule_name.to_s.match( meth_re ) - end + @rules.each do |rule_name| + method( rule_name ).call( text ) if rule_name.to_s.match /^inline_/ end end @@ -1114,7 +1097,7 @@ class RedCloth < String q2 = ( q != '' ? q : '\s' ) if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i attrv = $1 - next if prop == 'src' and attrv =~ %r{^(?!http)\w+:} + next if prop == 'src' and attrv !~ /^http/ pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\"" break end diff --git a/vendor/RedCloth-3.0.4/run-tests.rb b/vendor/RedCloth-3.0.3/run-tests.rb similarity index 83% rename from vendor/RedCloth-3.0.4/run-tests.rb rename to vendor/RedCloth-3.0.3/run-tests.rb index 1f267b64..65b5c969 100644 --- a/vendor/RedCloth-3.0.4/run-tests.rb +++ b/vendor/RedCloth-3.0.3/run-tests.rb @@ -5,9 +5,7 @@ require 'yaml' Dir["tests/*.yml"].each do |testfile| YAML::load_documents( File.open( testfile ) ) do |doc| if doc['in'] and doc['out'] - opts = [] - opts << :hard_breaks if testfile =~ /hard_breaks/ - red = RedCloth.new( doc['in'], opts ) + red = RedCloth.new( doc['in'] ) html = if testfile =~ /markdown/ red.to_html( :markdown ) else diff --git a/vendor/RedCloth-3.0.4/tests/code.yml b/vendor/RedCloth-3.0.3/tests/code.yml similarity index 100% rename from vendor/RedCloth-3.0.4/tests/code.yml rename to vendor/RedCloth-3.0.3/tests/code.yml diff --git a/vendor/RedCloth-3.0.4/tests/images.yml b/vendor/RedCloth-3.0.3/tests/images.yml similarity index 100% rename from vendor/RedCloth-3.0.4/tests/images.yml rename to vendor/RedCloth-3.0.3/tests/images.yml diff --git a/vendor/RedCloth-3.0.4/tests/instiki.yml b/vendor/RedCloth-3.0.3/tests/instiki.yml similarity index 100% rename from vendor/RedCloth-3.0.4/tests/instiki.yml rename to vendor/RedCloth-3.0.3/tests/instiki.yml diff --git a/vendor/RedCloth-3.0.4/tests/links.yml b/vendor/RedCloth-3.0.3/tests/links.yml similarity index 100% rename from vendor/RedCloth-3.0.4/tests/links.yml rename to vendor/RedCloth-3.0.3/tests/links.yml diff --git a/vendor/RedCloth-3.0.4/tests/lists.yml b/vendor/RedCloth-3.0.3/tests/lists.yml similarity index 100% rename from vendor/RedCloth-3.0.4/tests/lists.yml rename to vendor/RedCloth-3.0.3/tests/lists.yml diff --git a/vendor/RedCloth-3.0.4/tests/markdown.yml b/vendor/RedCloth-3.0.3/tests/markdown.yml similarity index 100% rename from vendor/RedCloth-3.0.4/tests/markdown.yml rename to vendor/RedCloth-3.0.3/tests/markdown.yml diff --git a/vendor/RedCloth-3.0.4/tests/poignant.yml b/vendor/RedCloth-3.0.3/tests/poignant.yml similarity index 100% rename from vendor/RedCloth-3.0.4/tests/poignant.yml rename to vendor/RedCloth-3.0.3/tests/poignant.yml diff --git a/vendor/RedCloth-3.0.4/tests/table.yml b/vendor/RedCloth-3.0.3/tests/table.yml similarity index 95% rename from vendor/RedCloth-3.0.4/tests/table.yml rename to vendor/RedCloth-3.0.3/tests/table.yml index 3ce974dd..bf5059e1 100644 --- a/vendor/RedCloth-3.0.4/tests/table.yml +++ b/vendor/RedCloth-3.0.3/tests/table.yml @@ -39,28 +39,28 @@ out: |- 11/18/04 11/18/04 070 - XML spec complete + XML spec complete 11/29/04 11/29/04 011 - XML spec complete (KH is on schedule) + XML spec complete (KH is on schedule) 11/29/04 11/29/04 051 - XML spec complete (KH is on schedule) + XML spec complete (KH is on schedule) 11/29/04 11/29/04 081 - XML spec complete (KH is on schedule) + XML spec complete (KH is on schedule) diff --git a/vendor/RedCloth-3.0.4/tests/textism.yml b/vendor/RedCloth-3.0.3/tests/textism.yml similarity index 97% rename from vendor/RedCloth-3.0.4/tests/textism.yml rename to vendor/RedCloth-3.0.3/tests/textism.yml index 1e6f8d6b..5489c04d 100644 --- a/vendor/RedCloth-3.0.4/tests/textism.yml +++ b/vendor/RedCloth-3.0.3/tests/textism.yml @@ -71,12 +71,6 @@ out:

    a phrase

    in: '**a phrase**' out:

    a phrase

    --- -in: '*(a)* a' -out:

    (a) a

    ---- -in: '*(a)* *' -out:

    (a) *

    ---- in: Nabokov's ??Pnin?? out:

    Nabokov’s Pnin

    --- @@ -401,6 +395,3 @@ out: |-
  • We must act
  • ---- -in: '"test":http://foo.com/b---ar' -out:

    test

    diff --git a/vendor/RedCloth-3.0.4/setup.rb b/vendor/RedCloth-3.0.4/setup.rb deleted file mode 100644 index 462522b5..00000000 --- a/vendor/RedCloth-3.0.4/setup.rb +++ /dev/null @@ -1,1376 +0,0 @@ -# -# setup.rb -# -# Copyright (c) 2000-2004 Minero Aoki -# -# This program is free software. -# You can distribute/modify this program under the terms of -# the GNU LGPL, Lesser General Public License version 2.1. -# - -unless Enumerable.method_defined?(:map) # Ruby 1.4.6 - module Enumerable - alias map collect - end -end - -unless File.respond_to?(:read) # Ruby 1.6 - def File.read(fname) - open(fname) {|f| - return f.read - } - end -end - -def File.binread(fname) - open(fname, 'rb') {|f| - return f.read - } -end - -# for corrupted windows stat(2) -def File.dir?(path) - File.directory?((path[-1,1] == '/') ? path : path + '/') -end - - -class SetupError < StandardError; end - -def setup_rb_error(msg) - raise SetupError, msg -end - -# -# Config -# - -if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } - ARGV.delete(arg) - require arg.split(/=/, 2)[1] - $".push 'rbconfig.rb' -else - require 'rbconfig' -end - -def multipackage_install? - FileTest.directory?(File.dirname($0) + '/packages') -end - - -class ConfigItem - def initialize(name, template, default, desc) - @name = name.freeze - @template = template - @value = default - @default = default.dup.freeze - @description = desc - end - - attr_reader :name - attr_reader :description - - attr_accessor :default - alias help_default default - - def help_opt - "--#{@name}=#{@template}" - end - - def value - @value - end - - def eval(table) - @value.gsub(%r<\$([^/]+)>) { table[$1] } - end - - def set(val) - @value = check(val) - end - - private - - def check(val) - setup_rb_error "config: --#{name} requires argument" unless val - val - end -end - -class BoolItem < ConfigItem - def config_type - 'bool' - end - - def help_opt - "--#{@name}" - end - - private - - def check(val) - return 'yes' unless val - unless /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i =~ val - setup_rb_error "config: --#{@name} accepts only yes/no for argument" - end - (/\Ay(es)?|\At(rue)/i =~ value) ? 'yes' : 'no' - end -end - -class PathItem < ConfigItem - def config_type - 'path' - end - - private - - def check(path) - setup_rb_error "config: --#{@name} requires argument" unless path - path[0,1] == '$' ? path : File.expand_path(path) - end -end - -class ProgramItem < ConfigItem - def config_type - 'program' - end -end - -class SelectItem < ConfigItem - def initialize(name, template, default, desc) - super - @ok = template.split('/') - end - - def config_type - 'select' - end - - private - - def check(val) - unless @ok.include?(val.strip) - setup_rb_error "config: use --#{@name}=#{@template} (#{val})" - end - val.strip - end -end - -class PackageSelectionItem < ConfigItem - def initialize(name, template, default, help_default, desc) - super name, template, default, desc - @help_default = help_default - end - - attr_reader :help_default - - def config_type - 'package' - end - - private - - def check(val) - unless File.dir?("packages/#{val}") - setup_rb_error "config: no such package: #{val}" - end - val - end -end - -class ConfigTable_class - - def initialize(items) - @items = items - @table = {} - items.each do |i| - @table[i.name] = i - end - ALIASES.each do |ali, name| - @table[ali] = @table[name] - end - end - - include Enumerable - - def each(&block) - @items.each(&block) - end - - def key?(name) - @table.key?(name) - end - - def lookup(name) - @table[name] or raise ArgumentError, "no such config item: #{name}" - end - - def add(item) - @items.push item - @table[item.name] = item - end - - def remove(name) - item = lookup(name) - @items.delete_if {|i| i.name == name } - @table.delete_if {|name, i| i.name == name } - item - end - - def new - dup() - end - - def savefile - '.config' - end - - def load - begin - t = dup() - File.foreach(savefile()) do |line| - k, v = *line.split(/=/, 2) - t[k] = v.strip - end - t - rescue Errno::ENOENT - setup_rb_error $!.message + "#{File.basename($0)} config first" - end - end - - def save - @items.each {|i| i.value } - File.open(savefile(), 'w') {|f| - @items.each do |i| - f.printf "%s=%s\n", i.name, i.value if i.value - end - } - end - - def [](key) - lookup(key).eval(self) - end - - def []=(key, val) - lookup(key).set val - end - -end - -c = ::Config::CONFIG - -rubypath = c['bindir'] + '/' + c['ruby_install_name'] - -major = c['MAJOR'].to_i -minor = c['MINOR'].to_i -teeny = c['TEENY'].to_i -version = "#{major}.#{minor}" - -# ruby ver. >= 1.4.4? -newpath_p = ((major >= 2) or - ((major == 1) and - ((minor >= 5) or - ((minor == 4) and (teeny >= 4))))) - -if c['rubylibdir'] - # V < 1.6.3 - _stdruby = c['rubylibdir'] - _siteruby = c['sitedir'] - _siterubyver = c['sitelibdir'] - _siterubyverarch = c['sitearchdir'] -elsif newpath_p - # 1.4.4 <= V <= 1.6.3 - _stdruby = "$prefix/lib/ruby/#{version}" - _siteruby = c['sitedir'] - _siterubyver = "$siteruby/#{version}" - _siterubyverarch = "$siterubyver/#{c['arch']}" -else - # V < 1.4.4 - _stdruby = "$prefix/lib/ruby/#{version}" - _siteruby = "$prefix/lib/ruby/#{version}/site_ruby" - _siterubyver = _siteruby - _siterubyverarch = "$siterubyver/#{c['arch']}" -end -libdir = '-* dummy libdir *-' -stdruby = '-* dummy rubylibdir *-' -siteruby = '-* dummy site_ruby *-' -siterubyver = '-* dummy site_ruby version *-' -parameterize = lambda {|path| - path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')\ - .sub(/\A#{Regexp.quote(libdir)}/, '$libdir')\ - .sub(/\A#{Regexp.quote(stdruby)}/, '$stdruby')\ - .sub(/\A#{Regexp.quote(siteruby)}/, '$siteruby')\ - .sub(/\A#{Regexp.quote(siterubyver)}/, '$siterubyver') -} -libdir = parameterize.call(c['libdir']) -stdruby = parameterize.call(_stdruby) -siteruby = parameterize.call(_siteruby) -siterubyver = parameterize.call(_siterubyver) -siterubyverarch = parameterize.call(_siterubyverarch) - -if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } - makeprog = arg.sub(/'/, '').split(/=/, 2)[1] -else - makeprog = 'make' -end - -common_conf = [ - PathItem.new('prefix', 'path', c['prefix'], - 'path prefix of target environment'), - PathItem.new('bindir', 'path', parameterize.call(c['bindir']), - 'the directory for commands'), - PathItem.new('libdir', 'path', libdir, - 'the directory for libraries'), - PathItem.new('datadir', 'path', parameterize.call(c['datadir']), - 'the directory for shared data'), - PathItem.new('mandir', 'path', parameterize.call(c['mandir']), - 'the directory for man pages'), - PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), - 'the directory for man pages'), - PathItem.new('stdruby', 'path', stdruby, - 'the directory for standard ruby libraries'), - PathItem.new('siteruby', 'path', siteruby, - 'the directory for version-independent aux ruby libraries'), - PathItem.new('siterubyver', 'path', siterubyver, - 'the directory for aux ruby libraries'), - PathItem.new('siterubyverarch', 'path', siterubyverarch, - 'the directory for aux ruby binaries'), - PathItem.new('rbdir', 'path', '$siterubyver', - 'the directory for ruby scripts'), - PathItem.new('sodir', 'path', '$siterubyverarch', - 'the directory for ruby extentions'), - PathItem.new('rubypath', 'path', rubypath, - 'the path to set to #! line'), - ProgramItem.new('rubyprog', 'name', rubypath, - 'the ruby program using for installation'), - ProgramItem.new('makeprog', 'name', makeprog, - 'the make program to compile ruby extentions'), - SelectItem.new('shebang', 'all/ruby/never', 'ruby', - 'shebang line (#!) editing mode'), - BoolItem.new('without-ext', 'yes/no', 'no', - 'does not compile/install ruby extentions') -] -class ConfigTable_class # open again - ALIASES = { - 'std-ruby' => 'stdruby', - 'site-ruby-common' => 'siteruby', # For backward compatibility - 'site-ruby' => 'siterubyver', # For backward compatibility - 'bin-dir' => 'bindir', - 'bin-dir' => 'bindir', - 'rb-dir' => 'rbdir', - 'so-dir' => 'sodir', - 'data-dir' => 'datadir', - 'ruby-path' => 'rubypath', - 'ruby-prog' => 'rubyprog', - 'ruby' => 'rubyprog', - 'make-prog' => 'makeprog', - 'make' => 'makeprog' - } -end -multipackage_conf = [ - PackageSelectionItem.new('with', 'name,name...', '', 'ALL', - 'package names that you want to install'), - PackageSelectionItem.new('without', 'name,name...', '', 'NONE', - 'package names that you do not want to install') -] -if multipackage_install? - ConfigTable = ConfigTable_class.new(common_conf + multipackage_conf) -else - ConfigTable = ConfigTable_class.new(common_conf) -end - - -module MetaConfigAPI - - def eval_file_ifexist(fname) - instance_eval File.read(fname), fname, 1 if File.file?(fname) - end - - def config_names - ConfigTable.map {|i| i.name } - end - - def config?(name) - ConfigTable.key?(name) - end - - def bool_config?(name) - ConfigTable.lookup(name).config_type == 'bool' - end - - def path_config?(name) - ConfigTable.lookup(name).config_type == 'path' - end - - def value_config?(name) - case ConfigTable.lookup(name).config_type - when 'bool', 'path' - true - else - false - end - end - - def add_config(item) - ConfigTable.add item - end - - def add_bool_config(name, default, desc) - ConfigTable.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) - end - - def add_path_config(name, default, desc) - ConfigTable.add PathItem.new(name, 'path', default, desc) - end - - def set_config_default(name, default) - ConfigTable.lookup(name).default = default - end - - def remove_config(name) - ConfigTable.remove(name) - end - -end - - -# -# File Operations -# - -module FileOperations - - def mkdir_p(dirname, prefix = nil) - dirname = prefix + File.expand_path(dirname) if prefix - $stderr.puts "mkdir -p #{dirname}" if verbose? - return if no_harm? - - # does not check '/'... it's too abnormal case - dirs = File.expand_path(dirname).split(%r<(?=/)>) - if /\A[a-z]:\z/i =~ dirs[0] - disk = dirs.shift - dirs[0] = disk + dirs[0] - end - dirs.each_index do |idx| - path = dirs[0..idx].join('') - Dir.mkdir path unless File.dir?(path) - end - end - - def rm_f(fname) - $stderr.puts "rm -f #{fname}" if verbose? - return if no_harm? - - if File.exist?(fname) or File.symlink?(fname) - File.chmod 0777, fname - File.unlink fname - end - end - - def rm_rf(dn) - $stderr.puts "rm -rf #{dn}" if verbose? - return if no_harm? - - Dir.chdir dn - Dir.foreach('.') do |fn| - next if fn == '.' - next if fn == '..' - if File.dir?(fn) - verbose_off { - rm_rf fn - } - else - verbose_off { - rm_f fn - } - end - end - Dir.chdir '..' - Dir.rmdir dn - end - - def move_file(src, dest) - File.unlink dest if File.exist?(dest) - begin - File.rename src, dest - rescue - File.open(dest, 'wb') {|f| f.write File.binread(src) } - File.chmod File.stat(src).mode, dest - File.unlink src - end - end - - def install(from, dest, mode, prefix = nil) - $stderr.puts "install #{from} #{dest}" if verbose? - return if no_harm? - - realdest = prefix ? prefix + File.expand_path(dest) : dest - realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) - str = File.binread(from) - if diff?(str, realdest) - verbose_off { - rm_f realdest if File.exist?(realdest) - } - File.open(realdest, 'wb') {|f| - f.write str - } - File.chmod mode, realdest - - File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| - if prefix - f.puts realdest.sub(prefix, '') - else - f.puts realdest - end - } - end - end - - def diff?(new_content, path) - return true unless File.exist?(path) - new_content != File.binread(path) - end - - def command(str) - $stderr.puts str if verbose? - system str or raise RuntimeError, "'system #{str}' failed" - end - - def ruby(str) - command config('rubyprog') + ' ' + str - end - - def make(task = '') - command config('makeprog') + ' ' + task - end - - def extdir?(dir) - File.exist?(dir + '/MANIFEST') - end - - def all_files_in(dirname) - Dir.open(dirname) {|d| - return d.select {|ent| File.file?("#{dirname}/#{ent}") } - } - end - - REJECT_DIRS = %w( - CVS SCCS RCS CVS.adm .svn - ) - - def all_dirs_in(dirname) - Dir.open(dirname) {|d| - return d.select {|n| File.dir?("#{dirname}/#{n}") } - %w(. ..) - REJECT_DIRS - } - end - -end - - -# -# Main Installer -# - -module HookUtils - - def run_hook(name) - try_run_hook "#{curr_srcdir()}/#{name}" or - try_run_hook "#{curr_srcdir()}/#{name}.rb" - end - - def try_run_hook(fname) - return false unless File.file?(fname) - begin - instance_eval File.read(fname), fname, 1 - rescue - setup_rb_error "hook #{fname} failed:\n" + $!.message - end - true - end - -end - - -module HookScriptAPI - - def get_config(key) - @config[key] - end - - alias config get_config - - def set_config(key, val) - @config[key] = val - end - - # - # srcdir/objdir (works only in the package directory) - # - - #abstract srcdir_root - #abstract objdir_root - #abstract relpath - - def curr_srcdir - "#{srcdir_root()}/#{relpath()}" - end - - def curr_objdir - "#{objdir_root()}/#{relpath()}" - end - - def srcfile(path) - "#{curr_srcdir()}/#{path}" - end - - def srcexist?(path) - File.exist?(srcfile(path)) - end - - def srcdirectory?(path) - File.dir?(srcfile(path)) - end - - def srcfile?(path) - File.file? srcfile(path) - end - - def srcentries(path = '.') - Dir.open("#{curr_srcdir()}/#{path}") {|d| - return d.to_a - %w(. ..) - } - end - - def srcfiles(path = '.') - srcentries(path).select {|fname| - File.file?(File.join(curr_srcdir(), path, fname)) - } - end - - def srcdirectories(path = '.') - srcentries(path).select {|fname| - File.dir?(File.join(curr_srcdir(), path, fname)) - } - end - -end - - -class ToplevelInstaller - - Version = '3.3.1' - Copyright = 'Copyright (c) 2000-2004 Minero Aoki' - - TASKS = [ - [ 'all', 'do config, setup, then install' ], - [ 'config', 'saves your configurations' ], - [ 'show', 'shows current configuration' ], - [ 'setup', 'compiles ruby extentions and others' ], - [ 'install', 'installs files' ], - [ 'clean', "does `make clean' for each extention" ], - [ 'distclean',"does `make distclean' for each extention" ] - ] - - def ToplevelInstaller.invoke - instance().invoke - end - - @singleton = nil - - def ToplevelInstaller.instance - @singleton ||= new(File.dirname($0)) - @singleton - end - - include MetaConfigAPI - - def initialize(ardir_root) - @config = nil - @options = { 'verbose' => true } - @ardir = File.expand_path(ardir_root) - end - - def inspect - "#<#{self.class} #{__id__()}>" - end - - def invoke - run_metaconfigs - case task = parsearg_global() - when nil, 'all' - @config = load_config('config') - parsearg_config - init_installers - exec_config - exec_setup - exec_install - else - @config = load_config(task) - __send__ "parsearg_#{task}" - init_installers - __send__ "exec_#{task}" - end - end - - def run_metaconfigs - eval_file_ifexist "#{@ardir}/metaconfig" - end - - def load_config(task) - case task - when 'config' - ConfigTable.new - when 'clean', 'distclean' - if File.exist?(ConfigTable.savefile) - then ConfigTable.load - else ConfigTable.new - end - else - ConfigTable.load - end - end - - def init_installers - @installer = Installer.new(@config, @options, @ardir, File.expand_path('.')) - end - - # - # Hook Script API bases - # - - def srcdir_root - @ardir - end - - def objdir_root - '.' - end - - def relpath - '.' - end - - # - # Option Parsing - # - - def parsearg_global - valid_task = /\A(?:#{TASKS.map {|task,desc| task }.join '|'})\z/ - - while arg = ARGV.shift - case arg - when /\A\w+\z/ - setup_rb_error "invalid task: #{arg}" unless valid_task =~ arg - return arg - - when '-q', '--quiet' - @options['verbose'] = false - - when '--verbose' - @options['verbose'] = true - - when '-h', '--help' - print_usage $stdout - exit 0 - - when '-v', '--version' - puts "#{File.basename($0)} version #{Version}" - exit 0 - - when '--copyright' - puts Copyright - exit 0 - - else - setup_rb_error "unknown global option '#{arg}'" - end - end - - nil - end - - - def parsearg_no_options - unless ARGV.empty? - setup_rb_error "#{task}: unknown options: #{ARGV.join ' '}" - end - end - - alias parsearg_show parsearg_no_options - alias parsearg_setup parsearg_no_options - alias parsearg_clean parsearg_no_options - alias parsearg_distclean parsearg_no_options - - def parsearg_config - re = /\A--(#{ConfigTable.map {|i| i.name }.join('|')})(?:=(.*))?\z/ - @options['config-opt'] = [] - - while i = ARGV.shift - if /\A--?\z/ =~ i - @options['config-opt'] = ARGV.dup - break - end - m = re.match(i) or setup_rb_error "config: unknown option #{i}" - name, value = *m.to_a[1,2] - @config[name] = value - end - end - - def parsearg_install - @options['no-harm'] = false - @options['install-prefix'] = '' - while a = ARGV.shift - case a - when /\A--no-harm\z/ - @options['no-harm'] = true - when /\A--prefix=(.*)\z/ - path = $1 - path = File.expand_path(path) unless path[0,1] == '/' - @options['install-prefix'] = path - else - setup_rb_error "install: unknown option #{a}" - end - end - end - - def print_usage(out) - out.puts 'Typical Installation Procedure:' - out.puts " $ ruby #{File.basename $0} config" - out.puts " $ ruby #{File.basename $0} setup" - out.puts " # ruby #{File.basename $0} install (may require root privilege)" - out.puts - out.puts 'Detailed Usage:' - out.puts " ruby #{File.basename $0} " - out.puts " ruby #{File.basename $0} [] []" - - fmt = " %-24s %s\n" - out.puts - out.puts 'Global options:' - out.printf fmt, '-q,--quiet', 'suppress message outputs' - out.printf fmt, ' --verbose', 'output messages verbosely' - out.printf fmt, '-h,--help', 'print this message' - out.printf fmt, '-v,--version', 'print version and quit' - out.printf fmt, ' --copyright', 'print copyright and quit' - out.puts - out.puts 'Tasks:' - TASKS.each do |name, desc| - out.printf fmt, name, desc - end - - fmt = " %-24s %s [%s]\n" - out.puts - out.puts 'Options for CONFIG or ALL:' - ConfigTable.each do |item| - out.printf fmt, item.help_opt, item.description, item.help_default - end - out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" - out.puts - out.puts 'Options for INSTALL:' - out.printf fmt, '--no-harm', 'only display what to do if given', 'off' - out.printf fmt, '--prefix=path', 'install path prefix', '$prefix' - out.puts - end - - # - # Task Handlers - # - - def exec_config - @installer.exec_config - @config.save # must be final - end - - def exec_setup - @installer.exec_setup - end - - def exec_install - @installer.exec_install - end - - def exec_show - ConfigTable.each do |i| - printf "%-20s %s\n", i.name, i.value - end - end - - def exec_clean - @installer.exec_clean - end - - def exec_distclean - @installer.exec_distclean - end - -end - - -class ToplevelInstallerMulti < ToplevelInstaller - - include HookUtils - include HookScriptAPI - include FileOperations - - def initialize(ardir) - super - @packages = all_dirs_in("#{@ardir}/packages") - raise 'no package exists' if @packages.empty? - end - - def run_metaconfigs - eval_file_ifexist "#{@ardir}/metaconfig" - @packages.each do |name| - eval_file_ifexist "#{@ardir}/packages/#{name}/metaconfig" - end - end - - def init_installers - @installers = {} - @packages.each do |pack| - @installers[pack] = Installer.new(@config, @options, - "#{@ardir}/packages/#{pack}", - "packages/#{pack}") - end - - with = extract_selection(config('with')) - without = extract_selection(config('without')) - @selected = @installers.keys.select {|name| - (with.empty? or with.include?(name)) \ - and not without.include?(name) - } - end - - def extract_selection(list) - a = list.split(/,/) - a.each do |name| - setup_rb_error "no such package: #{name}" unless @installers.key?(name) - end - a - end - - def print_usage(f) - super - f.puts 'Inluded packages:' - f.puts ' ' + @packages.sort.join(' ') - f.puts - end - - # - # multi-package metaconfig API - # - - attr_reader :packages - - def declare_packages(list) - raise 'package list is empty' if list.empty? - list.each do |name| - raise "directory packages/#{name} does not exist"\ - unless File.dir?("#{@ardir}/packages/#{name}") - end - @packages = list - end - - # - # Task Handlers - # - - def exec_config - run_hook 'pre-config' - each_selected_installers {|inst| inst.exec_config } - run_hook 'post-config' - @config.save # must be final - end - - def exec_setup - run_hook 'pre-setup' - each_selected_installers {|inst| inst.exec_setup } - run_hook 'post-setup' - end - - def exec_install - run_hook 'pre-install' - each_selected_installers {|inst| inst.exec_install } - run_hook 'post-install' - end - - def exec_clean - rm_f ConfigTable.savefile - run_hook 'pre-clean' - each_selected_installers {|inst| inst.exec_clean } - run_hook 'post-clean' - end - - def exec_distclean - rm_f ConfigTable.savefile - run_hook 'pre-distclean' - each_selected_installers {|inst| inst.exec_distclean } - run_hook 'post-distclean' - end - - # - # lib - # - - def each_selected_installers - Dir.mkdir 'packages' unless File.dir?('packages') - @selected.each do |pack| - $stderr.puts "Processing the package `#{pack}' ..." if @options['verbose'] - Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") - Dir.chdir "packages/#{pack}" - yield @installers[pack] - Dir.chdir '../..' - end - end - - def verbose? - @options['verbose'] - end - - def no_harm? - @options['no-harm'] - end - -end - - -class Installer - - FILETYPES = %w( bin lib ext share ) - - include HookScriptAPI - include HookUtils - include FileOperations - - def initialize(config, opt, srcroot, objroot) - @config = config - @options = opt - @srcdir = File.expand_path(srcroot) - @objdir = File.expand_path(objroot) - @currdir = '.' - end - - def inspect - "#<#{self.class} #{File.basename(@srcdir)}>" - end - - # - # Hook Script API base methods - # - - def srcdir_root - @srcdir - end - - def objdir_root - @objdir - end - - def relpath - @currdir - end - - # - # configs/options - # - - def no_harm? - @options['no-harm'] - end - - def verbose? - @options['verbose'] - end - - def verbose_off - begin - save, @options['verbose'] = @options['verbose'], false - yield - ensure - @options['verbose'] = save - end - end - - # - # TASK config - # - - def exec_config - exec_task_traverse 'config' - end - - def config_dir_bin(rel) - end - - def config_dir_lib(rel) - end - - def config_dir_ext(rel) - extconf if extdir?(curr_srcdir()) - end - - def extconf - opt = @options['config-opt'].join(' ') - command "#{config('rubyprog')} #{curr_srcdir()}/extconf.rb #{opt}" - end - - def config_dir_share(rel) - end - - # - # TASK setup - # - - def exec_setup - exec_task_traverse 'setup' - end - - def setup_dir_bin(rel) - all_files_in(curr_srcdir()).each do |fname| - adjust_shebang "#{curr_srcdir()}/#{fname}" - end - end - - def adjust_shebang(path) - return if no_harm? - tmpfile = File.basename(path) + '.tmp' - begin - File.open(path, 'rb') {|r| - first = r.gets - return unless File.basename(config('rubypath')) == 'ruby' - return unless File.basename(first.sub(/\A\#!/, '').split[0]) == 'ruby' - $stderr.puts "adjusting shebang: #{File.basename(path)}" if verbose? - File.open(tmpfile, 'wb') {|w| - w.print first.sub(/\A\#!\s*\S+/, '#! ' + config('rubypath')) - w.write r.read - } - move_file tmpfile, File.basename(path) - } - ensure - File.unlink tmpfile if File.exist?(tmpfile) - end - end - - def setup_dir_lib(rel) - end - - def setup_dir_ext(rel) - make if extdir?(curr_srcdir()) - end - - def setup_dir_share(rel) - end - - # - # TASK install - # - - def exec_install - rm_f 'InstalledFiles' - exec_task_traverse 'install' - end - - def install_dir_bin(rel) - install_files collect_filenames_auto(), "#{config('bindir')}/#{rel}", 0755 - end - - def install_dir_lib(rel) - install_files ruby_scripts(), "#{config('rbdir')}/#{rel}", 0644 - return unless rel.empty? - begin - require 'rdoc/rdoc' - ri_site = true - if RDOC_VERSION =~ /^0\./ - require 'rdoc/options' - unless Options::OptionList::OPTION_LIST.assoc('--ri-site') - ri_site = false - end - end rescue nil - if ri_site - r = RDoc::RDoc.new - r.document(%w{--ri-site}) - end - rescue - puts "** Unable to install Ri documentation for RedCloth **" - end - end - - def install_dir_ext(rel) - return unless extdir?(curr_srcdir()) - install_files ruby_extentions('.'), - "#{config('sodir')}/#{File.dirname(rel)}", - 0555 - end - - def install_dir_share(rel) - end - - def install_files(list, dest, mode) - mkdir_p dest, @options['install-prefix'] - list.each do |fname| - install fname, dest, mode, @options['install-prefix'] - end - end - - def ruby_scripts - collect_filenames_auto().select {|n| /\.rb\z/ =~ n } - end - - # picked up many entries from cvs-1.11.1/src/ignore.c - reject_patterns = %w( - core RCSLOG tags TAGS .make.state - .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb - *~ *.old *.bak *.BAK *.orig *.rej _$* *$ - - *.org *.in .* - ) - mapping = { - '.' => '\.', - '$' => '\$', - '#' => '\#', - '*' => '.*' - } - REJECT_PATTERNS = Regexp.new('\A(?:' + - reject_patterns.map {|pat| - pat.gsub(/[\.\$\#\*]/) {|ch| mapping[ch] } - }.join('|') + - ')\z') - - def collect_filenames_auto - mapdir((existfiles() - hookfiles()).reject {|fname| - REJECT_PATTERNS =~ fname - }) - end - - def existfiles - all_files_in(curr_srcdir()) | all_files_in('.') - end - - def hookfiles - %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| - %w( config setup install clean ).map {|t| sprintf(fmt, t) } - }.flatten - end - - def mapdir(filelist) - filelist.map {|fname| - if File.exist?(fname) # objdir - fname - else # srcdir - File.join(curr_srcdir(), fname) - end - } - end - - def ruby_extentions(dir) - Dir.open(dir) {|d| - ents = d.select {|fname| /\.#{::Config::CONFIG['DLEXT']}\z/ =~ fname } - if ents.empty? - setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" - end - return ents - } - end - - # - # TASK clean - # - - def exec_clean - exec_task_traverse 'clean' - rm_f ConfigTable.savefile - rm_f 'InstalledFiles' - end - - def clean_dir_bin(rel) - end - - def clean_dir_lib(rel) - end - - def clean_dir_ext(rel) - return unless extdir?(curr_srcdir()) - make 'clean' if File.file?('Makefile') - end - - def clean_dir_share(rel) - end - - # - # TASK distclean - # - - def exec_distclean - exec_task_traverse 'distclean' - rm_f ConfigTable.savefile - rm_f 'InstalledFiles' - end - - def distclean_dir_bin(rel) - end - - def distclean_dir_lib(rel) - end - - def distclean_dir_ext(rel) - return unless extdir?(curr_srcdir()) - make 'distclean' if File.file?('Makefile') - end - - # - # lib - # - - def exec_task_traverse(task) - run_hook "pre-#{task}" - FILETYPES.each do |type| - if config('without-ext') == 'yes' and type == 'ext' - $stderr.puts 'skipping ext/* by user option' if verbose? - next - end - traverse task, type, "#{task}_dir_#{type}" - end - run_hook "post-#{task}" - end - - def traverse(task, rel, mid) - dive_into(rel) { - run_hook "pre-#{task}" - __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') - all_dirs_in(curr_srcdir()).each do |d| - traverse task, "#{rel}/#{d}", mid - end - run_hook "post-#{task}" - } - end - - def dive_into(rel) - return unless File.dir?("#{@srcdir}/#{rel}") - - dir = File.basename(rel) - Dir.mkdir dir unless File.dir?(dir) - prevdir = Dir.pwd - Dir.chdir dir - $stderr.puts '---> ' + rel if verbose? - @currdir = rel - yield - Dir.chdir prevdir - $stderr.puts '<--- ' + rel if verbose? - @currdir = File.dirname(rel) - end - -end - - -if $0 == __FILE__ - begin - if multipackage_install? - ToplevelInstallerMulti.invoke - else - ToplevelInstaller.invoke - end - rescue SetupError - raise if $DEBUG - $stderr.puts $!.message - $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." - exit 1 - end -end diff --git a/vendor/RedCloth-3.0.4/tests/hard_breaks.yml b/vendor/RedCloth-3.0.4/tests/hard_breaks.yml deleted file mode 100644 index 0b1fc099..00000000 --- a/vendor/RedCloth-3.0.4/tests/hard_breaks.yml +++ /dev/null @@ -1,26 +0,0 @@ ---- -in: | - |This|is|a|row| - {background:#ddd}. |This|is|grey|row| - |This|is|another|row| -out: |- - - - - - - - - - - - - - - - - - - - -
    Thisisarow
    Thisisgreyrow
    Thisisanotherrow
    From 50b2cbd6935e64d52ddbf17fea0d9340331c5dc8 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 20 Oct 2005 01:18:15 +0000 Subject: [PATCH 68/84] Fix to #255 - Author cookie expiry --- app/controllers/wiki_controller.rb | 2 +- test/functional/wiki_controller_test.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 13d76598..633c2288 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -185,7 +185,7 @@ class WikiController < ApplicationController def save redirect_home if @page_name.nil? - cookies['author'] = @params['author'] + cookies['author'] = { :value => @params['author'], :expires => Time.utc(2030) } begin if @page diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 9d39fa23..e5646da5 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -513,6 +513,7 @@ class WikiControllerTest < Test::Unit::TestCase assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'NewPage' assert_equal ['AuthorOfNewPage'], r.cookies['author'].value + assert_equal Time.utc(2030), r.cookies['author'].expires new_page = @wiki.read_page('wiki1', 'NewPage') assert_equal 'Contents of a new page', new_page.content assert_equal 'AuthorOfNewPage', new_page.author From 44d09c45f828f91b2053922669dbe2f30a0e5f9a Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 20 Oct 2005 01:23:31 +0000 Subject: [PATCH 69/84] Fixed file permissions mask in file_yard --- lib/file_yard.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/file_yard.rb b/lib/file_yard.rb index f9402c17..1654c90e 100644 --- a/lib/file_yard.rb +++ b/lib/file_yard.rb @@ -17,7 +17,7 @@ class FileYard if io.kind_of?(Tempfile) io.close check_upload_size(io.size) - File.chmod(600, file_path(name)) if File.exists? file_path(name) + File.chmod(0600, file_path(name)) if File.exists? file_path(name) FileUtils.mv(io.path, file_path(name)) else content = io.read From d28be2b0ef1adbbadc556b5073dcac0d84967560 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Wed, 26 Oct 2005 01:06:41 +0000 Subject: [PATCH 70/84] [Breaks build] Upgraded Rails to 0.14.1 --- instiki.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instiki.gemspec b/instiki.gemspec index c65ac414..a119b097 100755 --- a/instiki.gemspec +++ b/instiki.gemspec @@ -26,7 +26,7 @@ spec = Gem::Specification.new do |s| s.add_dependency('RedCloth', '= 3.0.3') s.add_dependency('rubyzip', '= 0.5.8') - s.add_dependency('rails', '= 0.13.1') + s.add_dependency('rails', '= 0.14.1') s.add_dependency('sqlite3-ruby', '= 1.1.0') s.requirements << 'none' s.require_path = 'lib' From af25237a906b5c45dfed52f7a234e5db40f6aa41 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 27 Oct 2005 05:29:24 +0000 Subject: [PATCH 71/84] Render HTML pages for ExportHTML 'manually' --- app/controllers/wiki_controller.rb | 52 +++++++++++++++++-------- app/models/page.rb | 4 ++ app/models/web.rb | 4 ++ test/functional/wiki_controller_test.rb | 2 - 4 files changed, 44 insertions(+), 18 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 633c2288..c9aa46ec 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -51,11 +51,41 @@ class WikiController < ApplicationController end def export_html + stylesheet = File.read(File.join(RAILS_ROOT, 'public', 'stylesheets', 'instiki.css')) export_pages_as_zip('html') do |page| - @page = page - @renderer = PageRenderer.new(page.revisions.last) - @link_mode = :export - render_to_string('wiki/print', use_layout = (@params['layout'] != 'no')) + + renderer = PageRenderer.new(page.revisions.last) + rendered_page = <<-EOL + + + + #{page.plain_name} in #{@web.name} + + + + + + + #{renderer.display_content_for_export} + + + + EOL + rendered_page end end @@ -256,7 +286,7 @@ class WikiController < ApplicationController def export_page_to_tex(file_path) tex - File.open(file_path, 'w') { |f| f.write(render_to_string('wiki/tex')) } + File.open(file_path, 'w') { |f| f.write(render_to_string(:template => 'wiki/tex', :layout => nil)) } end def export_pages_as_zip(file_type, &block) @@ -285,7 +315,7 @@ class WikiController < ApplicationController def export_web_to_tex(file_path) @tex_content = table_of_contents(@web.page('HomePage').content, render_tex_web) - File.open(file_path, 'w') { |f| f.write(render_to_string('wiki/tex_web')) } + File.open(file_path, 'w') { |f| f.write(render_to_string(:template => 'wiki/tex_web', :layout => nil)) } end def get_page_and_revision @@ -346,16 +376,6 @@ class WikiController < ApplicationController end end - def render_to_string(template_name, with_layout = false) - add_variables_to_assigns - self.assigns['content_for_layout'] = @template.render_file(template_name) - if with_layout - @template.render_file('layouts/default') - else - self.assigns['content_for_layout'] - end - end - def rss_with_content_allowed? @web.password.nil? or @web.published? end diff --git a/app/models/page.rb b/app/models/page.rb index e45b609e..1896ac5f 100644 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -94,6 +94,10 @@ class Page < ActiveRecord::Base locked_at + LOCKING_PERIOD > comparison_time unless locked_at.nil? end + def to_param + name + end + private def continous_revision?(time, author) diff --git a/app/models/web.rb b/app/models/web.rb index c38c8061..a53fc25b 100644 --- a/app/models/web.rb +++ b/app/models/web.rb @@ -79,6 +79,10 @@ class Web < ActiveRecord::Base PageSet.new(self, pages, nil) end + def to_param + address + end + private # Returns an array of all the wiki words in any current revision diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index e5646da5..70654b50 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -116,7 +116,6 @@ class WikiControllerTest < Test::Unit::TestCase assert_match /attachment; filename="wiki1-html-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.zip"/, r.headers['Content-Disposition'] assert_equal 'PK', r.body[0..1], 'Content is not a zip file' - assert_equal :export, r.template_objects['link_mode'] # Tempfile doesn't know how to open files with binary flag, hence the two-step process Tempfile.open('instiki_export_file') { |f| @tempfile_path = f.path } @@ -145,7 +144,6 @@ class WikiControllerTest < Test::Unit::TestCase assert_match /attachment; filename="wiki1-html-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.zip"/, r.headers['Content-Disposition'] assert_equal 'PK', r.body[0..1], 'Content is not a zip file' - assert_equal :export, r.template_objects['link_mode'] end def test_export_markup From 9e7306fb0a8fb6ababbcafb936d9c19b4c01c311 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 28 Oct 2005 14:14:31 +0000 Subject: [PATCH 72/84] Outdated TODO deleted --- app/controllers/wiki_controller.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index c9aa46ec..cbb0fb3c 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -5,7 +5,6 @@ require 'zip/zip' class WikiController < ApplicationController - # TODO implement cache sweeping caches_action :show, :published, :authors, :recently_revised, :list cache_sweeper :revision_sweeper From d3b25c8a1987c3b899a0f27d9d4defee1e9fe5d8 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 1 Nov 2005 07:31:44 +0000 Subject: [PATCH 73/84] Added meta robots tag to the default layout; added error handling to published pages --- app/controllers/application.rb | 10 +++++++++- app/controllers/wiki_controller.rb | 13 +++++++------ app/views/layouts/default.rhtml | 3 ++- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/app/controllers/application.rb b/app/controllers/application.rb index a4133850..9306b9c3 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -2,7 +2,7 @@ # Likewise will all the methods added be available for all controllers. class ApplicationController < ActionController::Base - before_filter :connect_to_model, :setup_url_generator, :set_content_type_header + before_filter :connect_to_model, :setup_url_generator, :set_content_type_header, :set_robots_metatag after_filter :remember_location, :teardown_url_generator observer :page_observer @@ -152,6 +152,14 @@ class ApplicationController < ActionController::Base end end + def set_robots_metatag + if controller_name == 'wiki' and %w(show published).include? action_name + @robots_metatag_value = 'index,follow' + else + @robots_metatag_value = 'noindex,nofollow' + end + end + def setup_url_generator PageRenderer.setup_url_generator(UrlGenerator.new(self)) end diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index cbb0fb3c..d2bec178 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -195,12 +195,13 @@ class WikiController < ApplicationController end def published - if @web.published? - page = wiki.read_page(@web_name, @page_name || 'HomePage') - @renderer = PageRenderer.new(page.revisions.last) - else - redirect_home - end + render_text("Published version of web '#{@web_name}' is not available", 404) and return if not @web.published? + + page_name = @page_name || 'HomePage' + page = wiki.read_page(@web_name, page_name) + render_text("Page '#{page_name}' not found", 404) and return unless page + + @renderer = PageRenderer.new(page.revisions.last) end def revision diff --git a/app/views/layouts/default.rhtml b/app/views/layouts/default.rhtml index eb1472aa..fb5e550e 100644 --- a/app/views/layouts/default.rhtml +++ b/app/views/layouts/default.rhtml @@ -14,7 +14,8 @@ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - + + - - - HTML - puts RedCloth.new( File.open( file_name ).read ).to_html - puts "" - puts "" -when 'QUICK-REFERENCE' - YAML::add_private_type( "example" ) do |type, val| - esc = val.dup - esc.htmlesc!( :NoQuotes ) - [ :example, esc.gsub( /\n/, '
    ' ), - RedCloth.new( val ).to_html ] - end - - content = YAML::load( File.open( 'REFERENCE' ) ) - - sections = content.collect { |c| c.keys.first } - sections.shift - - puts <<-HTML - - - - - Textile Quick Reference - - - - - - - HTML - - ct = 0 - content.each do |section| - section.each do |header, parags| - puts "" if ct.nonzero? - parags.each do |p| - if p.is_a?( Array ) and p[0] == :example - puts "" + - "" - end - end - end - ct += 1 - end - puts "

    Textile Quick Reference

    Sections: #{ sections.collect { |s| "#{ s.gsub( /\s/, ' ' ) }" }.join( ' | ' ) }
    #{ header }
    #{ p[1] }
    #{ p[2] }
    " - puts "" - puts "" - -when 'REFERENCE' - YAML::add_private_type( "example" ) do |type, val| - esc = val.dup - esc.htmlesc!( :NoQuotes ) - [ esc.gsub( /\n/, '
    ' ), - RedCloth.new( val ).to_html. - gsub( /;(\w)/, '; \1' ). - htmlesc!( :NoQuotes ). - gsub( /\n/, '
    ' ), - RedCloth.new( val ).to_html ] - end - - content = YAML::load( File.open( file_name ) ) - - sections = content.collect { |c| c.keys.first } - sections.shift - - puts <<-HTML - - - - - Textile Reference - - - - - - HTML - - ct = 0 - content.each do |section| - section.each do |header, parags| - if ct.zero? - puts "" - puts "" - else - puts "" - end - parags.each do |p| - if p.is_a? Array - puts "" + - "" + - "" - else - puts "" - end - end - unless ct.zero? - puts "" - end - end - ct += 1 - end - puts "

    #{ header }

    Sections: #{ sections.collect { |s| "#{ s.gsub( /\s/, ' ' ) }" }.join( ' | ' ) }
    #{ ct }.
    #{ header }
    #{ p[0] }

    #{ p[1] }

    #{ p[2] }
    " - puts RedCloth.new( p ).to_html - puts "
    " - puts "" - puts "" -end diff --git a/vendor/RedCloth-3.0.3/install.rb b/vendor/RedCloth-3.0.3/install.rb deleted file mode 100644 index 2313f9e9..00000000 --- a/vendor/RedCloth-3.0.3/install.rb +++ /dev/null @@ -1,1032 +0,0 @@ -#!/usr/local/bin/ruby -# -# This file is automatically generated. DO NOT MODIFY! -# -# install.rb -# -# Copyright (c) 2000-2002 Minero Aoki -# -# This program is free software. -# You can distribute/modify this program under the terms of -# the GNU Lesser General Public License version 2. -# - -### begin compat.rb - -unless Enumerable.instance_methods.include? 'inject' then -module Enumerable - def inject( result ) - each do |i| - result = yield(result, i) - end - result - end -end -end - -def File.read_all( fname ) - File.open(fname, 'rb') {|f| return f.read } -end - -def File.write( fname, str ) - File.open(fname, 'wb') {|f| f.write str } -end - -### end compat.rb -### begin config.rb - -if i = ARGV.index(/\A--rbconfig=/) then - file = $' - ARGV.delete_at(i) - require file -else - require 'rbconfig' -end - - -class ConfigTable - - c = ::Config::CONFIG - - rubypath = c['bindir'] + '/' + c['ruby_install_name'] - - major = c['MAJOR'].to_i - minor = c['MINOR'].to_i - teeny = c['TEENY'].to_i - version = "#{major}.#{minor}" - - # ruby ver. >= 1.4.4? - newpath_p = ((major >= 2) or - ((major == 1) and - ((minor >= 5) or - ((minor == 4) and (teeny >= 4))))) - - re = Regexp.new('\A' + Regexp.quote(c['prefix'])) - subprefix = lambda {|path| - re === path and path.sub(re, '$prefix') - } - - if c['rubylibdir'] then - # 1.6.3 < V - stdruby = subprefix.call(c['rubylibdir']) - siteruby = subprefix.call(c['sitedir']) - versite = subprefix.call(c['sitelibdir']) - sodir = subprefix.call(c['sitearchdir']) - elsif newpath_p then - # 1.4.4 <= V <= 1.6.3 - stdruby = "$prefix/lib/ruby/#{version}" - siteruby = subprefix.call(c['sitedir']) - versite = siteruby + '/' + version - sodir = "$site-ruby/#{c['arch']}" - else - # V < 1.4.4 - stdruby = "$prefix/lib/ruby/#{version}" - siteruby = "$prefix/lib/ruby/#{version}/site_ruby" - versite = siteruby - sodir = "$site-ruby/#{c['arch']}" - end - - DESCRIPTER = [ - [ 'prefix', [ c['prefix'], - 'path', - 'path prefix of target environment' ] ], - [ 'std-ruby', [ stdruby, - 'path', - 'the directory for standard ruby libraries' ] ], - [ 'site-ruby-common', [ siteruby, - 'path', - 'the directory for version-independent non-standard ruby libraries' ] ], - [ 'site-ruby', [ versite, - 'path', - 'the directory for non-standard ruby libraries' ] ], - [ 'bin-dir', [ '$prefix/bin', - 'path', - 'the directory for commands' ] ], - [ 'rb-dir', [ '$site-ruby', - 'path', - 'the directory for ruby scripts' ] ], - [ 'so-dir', [ sodir, - 'path', - 'the directory for ruby extentions' ] ], - [ 'data-dir', [ '$prefix/share', - 'path', - 'the directory for shared data' ] ], - [ 'ruby-path', [ rubypath, - 'path', - 'path to set to #! line' ] ], - [ 'ruby-prog', [ rubypath, - 'name', - 'the ruby program using for installation' ] ], - [ 'make-prog', [ 'make', - 'name', - 'the make program to compile ruby extentions' ] ], - [ 'without-ext', [ 'no', - 'yes/no', - 'does not compile/install ruby extentions' ] ] - ] - - SAVE_FILE = 'config.save' - - def ConfigTable.each_name( &block ) - keys().each( &block ) - end - - def ConfigTable.keys - DESCRIPTER.collect {|k,*dummy| k } - end - - def ConfigTable.each_definition( &block ) - DESCRIPTER.each( &block ) - end - - def ConfigTable.get_entry( name ) - name, ent = DESCRIPTER.assoc(name) - ent - end - - def ConfigTable.get_entry!( name ) - get_entry(name) or raise ArgumentError, "no such config: #{name}" - end - - def ConfigTable.add_entry( name, vals ) - ConfigTable::DESCRIPTER.push [name,vals] - end - - def ConfigTable.remove_entry( name ) - get_entry name or raise ArgumentError, "no such config: #{name}" - DESCRIPTER.delete_if {|n,arr| n == name } - end - - def ConfigTable.config_key?( name ) - get_entry(name) ? true : false - end - - def ConfigTable.bool_config?( name ) - ent = get_entry(name) or return false - ent[1] == 'yes/no' - end - - def ConfigTable.value_config?( name ) - ent = get_entry(name) or return false - ent[1] != 'yes/no' - end - - def ConfigTable.path_config?( name ) - ent = get_entry(name) or return false - ent[1] == 'path' - end - - - class << self - - alias newobj new - - def new - c = newobj() - c.__send__ :init - c - end - - def load - c = newobj() - File.file? SAVE_FILE or - raise InstallError, "#{File.basename $0} config first" - File.foreach( SAVE_FILE ) do |line| - k, v = line.split( '=', 2 ) - c.instance_eval { - @table[k] = v.strip - } - end - c - end - - end - - def initialize - @table = {} - end - - def init - DESCRIPTER.each do |k, (default, vname, desc, default2)| - @table[k] = default - end - end - private :init - - def save - File.open( SAVE_FILE, 'w' ) {|f| - @table.each do |k, v| - f.printf "%s=%s\n", k, v if v - end - } - end - - def []=( k, v ) - ConfigTable.config_key? k or raise InstallError, "unknown config option #{k}" - if ConfigTable.path_config? k then - @table[k] = (v[0,1] != '$') ? File.expand_path(v) : v - else - @table[k] = v - end - end - - def []( key ) - @table[key] or return nil - @table[key].gsub( %r<\$([^/]+)> ) { self[$1] } - end - - def set_raw( key, val ) - @table[key] = val - end - - def get_raw( key ) - @table[key] - end - -end - - -class MetaConfigEnvironment - - def self.eval_file( file ) - return unless File.file? file - new.instance_eval File.read_all(file), file, 1 - end - - private - - def config_names - ConfigTable.keys - end - - def config?( name ) - ConfigTable.config_key? name - end - - def bool_config?( name ) - ConfigTable.bool_config? name - end - - def value_config?( name ) - ConfigTable.value_config? name - end - - def path_config?( name ) - ConfigTable.path_config? name - end - - def add_config( name, argname, default, desc ) - ConfigTable.add_entry name,[default,argname,desc] - end - - def add_path_config( name, default, desc ) - add_config name, 'path', default, desc - end - - def add_bool_config( name, default, desc ) - add_config name, 'yes/no', default ? 'yes' : 'no', desc - end - - def set_config_default( name, default ) - if bool_config? name then - ConfigTable.get_entry!(name)[0] = default ? 'yes' : 'no' - else - ConfigTable.get_entry!(name)[0] = default - end - end - - def remove_config( name ) - ent = ConfigTable.get_entry(name) - ConfigTable.remove_entry name - ent - end - -end - -### end config.rb -### begin fileop.rb - -module FileOperations - - def mkdir_p( dname, prefix = nil ) - dname = prefix + dname if prefix - $stderr.puts "mkdir -p #{dname}" if verbose? - return if no_harm? - - # does not check '/'... it's too abnormal case - dirs = dname.split(%r_(?=/)_) - if /\A[a-z]:\z/i === dirs[0] then - disk = dirs.shift - dirs[0] = disk + dirs[0] - end - dirs.each_index do |idx| - path = dirs[0..idx].join('') - Dir.mkdir path unless dir? path - end - end - - def rm_f( fname ) - $stderr.puts "rm -f #{fname}" if verbose? - return if no_harm? - - if File.exist? fname or File.symlink? fname then - File.chmod 0777, fname - File.unlink fname - end - end - - def rm_rf( dn ) - $stderr.puts "rm -rf #{dn}" if verbose? - return if no_harm? - - Dir.chdir dn - Dir.foreach('.') do |fn| - next if fn == '.' - next if fn == '..' - if dir? fn then - verbose_off { - rm_rf fn - } - else - verbose_off { - rm_f fn - } - end - end - Dir.chdir '..' - Dir.rmdir dn - end - - def mv( src, dest ) - rm_f dest - begin - File.link src, dest - rescue - File.write dest, File.read_all(src) - File.chmod File.stat(src).mode, dest - end - rm_f src - end - - def install( from, dest, mode, prefix = nil ) - $stderr.puts "install #{from} #{dest}" if verbose? - return if no_harm? - - realdest = prefix + dest if prefix - if dir? realdest then - realdest += '/' + File.basename(from) - end - str = File.read_all(from) - if diff? str, realdest then - verbose_off { - rm_f realdest if File.exist? realdest - } - File.write realdest, str - File.chmod mode, realdest - - File.open( objdir + '/InstalledFiles', 'a' ) {|f| f.puts realdest } - end - end - - def diff?( orig, targ ) - return true unless File.exist? targ - orig != File.read_all(targ) - end - - def command( str ) - $stderr.puts str if verbose? - system str or raise RuntimeError, "'system #{str}' failed" - end - - def ruby( str ) - command config('ruby-prog') + ' ' + str - end - - def dir?( dname ) - # for corrupted windows stat() - File.directory?( (dname[-1,1] == '/') ? dname : dname + '/' ) - end - - def all_files( dname ) - Dir.open( dname ) {|d| - return d.find_all {|n| File.file? "#{dname}/#{n}" } - } - end - - def all_dirs( dname ) - Dir.open( dname ) {|d| - return d.find_all {|n| dir? "#{dname}/#{n}" } - %w(. ..) - } - end - -end - -### end fileop.rb -### begin base.rb - -class InstallError < StandardError; end - - -class Installer - - Version = '3.1.2' - Copyright = 'Copyright (c) 2000-2002 Minero Aoki' - - - @toplevel = nil - - def self.declear_toplevel_installer( inst ) - @toplevel and - raise ArgumentError, 'more than one toplevel installer decleared' - @toplevel = inst - end - - def self.toplevel_installer - @toplevel - end - - - FILETYPES = %w( bin lib ext data ) - - include FileOperations - - def initialize( config, opt, srcroot, objroot ) - @config = config - @options = opt - @srcdir = File.expand_path(srcroot) - @objdir = File.expand_path(objroot) - @currdir = '.' - end - - def inspect - "#<#{type} #{__id__}>" - end - - # - # configs/options - # - - def get_config( key ) - @config[key] - end - - alias config get_config - - def set_config( key, val ) - @config[key] = val - end - - def no_harm? - @options['no-harm'] - end - - def verbose? - @options['verbose'] - end - - def verbose_off - save, @options['verbose'] = @options['verbose'], false - yield - @options['verbose'] = save - end - - # - # srcdir/objdir - # - - attr_reader :srcdir - alias srcdir_root srcdir - alias package_root srcdir - - def curr_srcdir - "#{@srcdir}/#{@currdir}" - end - - attr_reader :objdir - alias objdir_root objdir - - def curr_objdir - "#{@objdir}/#{@currdir}" - end - - def srcfile( path ) - curr_srcdir + '/' + path - end - - def srcexist?( path ) - File.exist? srcfile(path) - end - - def srcdirectory?( path ) - dir? srcfile(path) - end - - def srcfile?( path ) - File.file? srcfile(path) - end - - def srcentries( path = '.' ) - Dir.open( curr_srcdir + '/' + path ) {|d| - return d.to_a - %w(. ..) - hookfilenames - } - end - - def srcfiles( path = '.' ) - srcentries(path).find_all {|fname| - File.file? File.join(curr_srcdir, path, fname) - } - end - - def srcdirectories( path = '.' ) - srcentries(path).find_all {|fname| - dir? File.join(curr_srcdir, path, fname) - } - end - - def dive_into( rel ) - return unless dir? "#{@srcdir}/#{rel}" - - dir = File.basename(rel) - Dir.mkdir dir unless dir? dir - save = Dir.pwd - Dir.chdir dir - $stderr.puts '---> ' + rel if verbose? - @currdir = rel - yield - Dir.chdir save - $stderr.puts '<--- ' + rel if verbose? - @currdir = File.dirname(rel) - end - - # - # config - # - - def exec_config - exec_task_traverse 'config' - end - - def config_dir_bin( rel ) - end - - def config_dir_lib( rel ) - end - - def config_dir_ext( rel ) - extconf if extdir? curr_srcdir - end - - def extconf - opt = @options['config-opt'].join(' ') - command "#{config('ruby-prog')} #{curr_srcdir}/extconf.rb #{opt}" - end - - def config_dir_data( rel ) - end - - # - # setup - # - - def exec_setup - exec_task_traverse 'setup' - end - - def setup_dir_bin( relpath ) - all_files( curr_srcdir ).each do |fname| - add_rubypath "#{curr_srcdir}/#{fname}" - end - end - - SHEBANG_RE = /\A\#!\s*\S*ruby\S*/ - - def add_rubypath( path ) - $stderr.puts %Q if verbose? - return if no_harm? - - tmpfile = File.basename(path) + '.tmp' - begin - File.open( path ) {|r| - File.open( tmpfile, 'w' ) {|w| - first = r.gets - return unless SHEBANG_RE === first # reject '/usr/bin/env ruby' - - w.print first.sub( SHEBANG_RE, '#!' + config('ruby-path') ) - w.write r.read - } } - mv tmpfile, File.basename(path) - ensure - rm_f tmpfile if File.exist? tmpfile - end - end - - def setup_dir_lib( relpath ) - end - - def setup_dir_ext( relpath ) - if extdir? curr_srcdir then - make - end - end - - def make - command config('make-prog') - end - - def setup_dir_data( relpath ) - end - - # - # install - # - - def exec_install - exec_task_traverse 'install' - end - - def install_dir_bin( rel ) - install_files targfiles, config('bin-dir') + '/' + rel, 0755 - end - - def install_dir_lib( rel ) - install_files targfiles, config('rb-dir') + '/' + rel, 0644 - begin - require 'rdoc/rdoc' - ri_site = true - if RDOC_VERSION =~ /^0\./ - require 'rdoc/options' - unless Options::OptionList::OPTION_LIST.assoc('--ri-site') - ri_site = false - end - end - if ri_site - r = RDoc::RDoc.new - r.document(%w{--ri-site}) - end - rescue - puts "** Unable to install Ri documentation for RedCloth **" - end - end - - def install_dir_ext( rel ) - if extdir? curr_srcdir then - install_dir_ext_main File.dirname(rel) - end - end - - def install_dir_ext_main( rel ) - install_files allext('.'), config('so-dir') + '/' + rel, 0555 - end - - def install_dir_data( rel ) - install_files targfiles, config('data-dir') + '/' + rel, 0644 - end - - def install_files( list, dest, mode ) - mkdir_p dest, @options['install-prefix'] - list.each do |fname| - install fname, dest, mode, @options['install-prefix'] - end - end - - def targfiles - (targfilenames() - hookfilenames()).collect {|fname| - File.exist?(fname) ? fname : File.join(curr_srcdir(), fname) - } - end - - def targfilenames - [ curr_srcdir(), '.' ].inject([]) {|ret, dir| - ret | all_files(dir) - } - end - - def hookfilenames - %w( pre-%s post-%s pre-%s.rb post-%s.rb ).collect {|fmt| - %w( config setup install clean ).collect {|t| sprintf fmt, t } - }.flatten - end - - def allext( dir ) - _allext(dir) or raise InstallError, - "no extention exists: Have you done 'ruby #{$0} setup' ?" - end - - DLEXT = /\.#{ ::Config::CONFIG['DLEXT'] }\z/ - - def _allext( dir ) - Dir.open( dir ) {|d| - return d.find_all {|fname| DLEXT === fname } - } - end - - # - # clean - # - - def exec_clean - exec_task_traverse 'clean' - rm_f 'config.save' - rm_f 'InstalledFiles' - end - - def clean_dir_bin( rel ) - end - - def clean_dir_lib( rel ) - end - - def clean_dir_ext( rel ) - clean - end - - def clean - command config('make-prog') + ' clean' if File.file? 'Makefile' - end - - def clean_dir_data( rel ) - end - - # - # lib - # - - def exec_task_traverse( task ) - run_hook 'pre-' + task - FILETYPES.each do |type| - if config('without-ext') == 'yes' and type == 'ext' then - $stderr.puts 'skipping ext/* by user option' if verbose? - next - end - traverse task, type, task + '_dir_' + type - end - run_hook 'post-' + task - end - - def traverse( task, rel, mid ) - dive_into( rel ) { - run_hook 'pre-' + task - __send__ mid, rel.sub( %r_\A.*?(?:/|\z)_, '' ) - all_dirs( curr_srcdir ).each do |d| - traverse task, rel + '/' + d, mid - end - run_hook 'post-' + task - } - end - - def run_hook( name ) - try_run_hook curr_srcdir + '/' + name or - try_run_hook curr_srcdir + '/' + name + '.rb' - end - - def try_run_hook( fname ) - return false unless File.file? fname - - env = self.dup - begin - env.instance_eval File.read_all(fname), fname, 1 - rescue - raise InstallError, "hook #{fname} failed:\n" + $!.message - end - true - end - - def extdir?( dir ) - File.exist? dir + '/MANIFEST' - end - -end - -### end base.rb -### begin toplevel.rb - -class ToplevelInstaller < Installer - - TASKS = [ - [ 'config', 'saves your configurations' ], - [ 'show', 'shows current configuration' ], - [ 'setup', 'compiles extention or else' ], - [ 'install', 'installs files' ], - [ 'clean', "does `make clean' for each extention" ] - ] - - - def initialize( root ) - super nil, {'verbose' => true}, root, '.' - Installer.declear_toplevel_installer self - end - - - def execute - run_metaconfigs - - case task = parsearg_global() - when 'config' - @config = ConfigTable.new - else - @config = ConfigTable.load - end - parsearg_TASK task - - exectask task - end - - - def run_metaconfigs - MetaConfigEnvironment.eval_file "#{srcdir_root}/#{metaconfig}" - end - - def metaconfig - 'metaconfig' - end - - - def exectask( task ) - if task == 'show' then - exec_show - else - try task - end - end - - def try( task ) - $stderr.printf "#{File.basename $0}: entering %s phase...\n", task if verbose? - begin - __send__ 'exec_' + task - rescue - $stderr.printf "%s failed\n", task - raise - end - $stderr.printf "#{File.basename $0}: %s done.\n", task if verbose? - end - - # - # processing arguments - # - - def parsearg_global - task_re = /\A(?:#{TASKS.collect {|i| i[0] }.join '|'})\z/ - - while arg = ARGV.shift do - case arg - when /\A\w+\z/ - task_re === arg or raise InstallError, "wrong task: #{arg}" - return arg - - when '-q', '--quiet' - @options['verbose'] = false - - when '--verbose' - @options['verbose'] = true - - when '-h', '--help' - print_usage $stdout - exit 0 - - when '-v', '--version' - puts "#{File.basename $0} version #{Version}" - exit 0 - - when '--copyright' - puts Copyright - exit 0 - - else - raise InstallError, "unknown global option '#{arg}'" - end - end - - raise InstallError, 'no task or global option given' - end - - - def parsearg_TASK( task ) - mid = "parsearg_#{task}" - if respond_to? mid, true then - __send__ mid - else - ARGV.empty? or - raise InstallError, "#{task}: unknown options: #{ARGV.join ' '}" - end - end - - def parsearg_config - re = /\A--(#{ConfigTable.keys.join '|'})(?:=(.*))?\z/ - @options['config-opt'] = [] - - while i = ARGV.shift do - if /\A--?\z/ === i then - @options['config-opt'] = ARGV.dup - break - end - m = re.match(i) or raise InstallError, "config: unknown option #{i}" - name, value = m.to_a[1,2] - if value then - if ConfigTable.bool_config?(name) then - /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i === value or raise InstallError, "config: --#{name} allows only yes/no for argument" - value = (/\Ay(es)?|\At(rue)/i === value) ? 'yes' : 'no' - end - else - ConfigTable.bool_config?(name) or raise InstallError, "config: --#{name} requires argument" - value = 'yes' - end - @config[name] = value - end - end - - def parsearg_install - @options['no-harm'] = false - @options['install-prefix'] = '' - while a = ARGV.shift do - case a - when /\A--no-harm\z/ - @options['no-harm'] = true - when /\A--prefix=(.*)\z/ - path = $1 - path = File.expand_path(path) unless path[0,1] == '/' - @options['install-prefix'] = path - else - raise InstallError, "install: unknown option #{a}" - end - end - end - - - def print_usage( out ) - out.puts - out.puts 'Usage:' - out.puts " ruby #{File.basename $0} " - out.puts " ruby #{File.basename $0} [] []" - - fmt = " %-20s %s\n" - out.puts - out.puts 'Global options:' - out.printf fmt, '-q,--quiet', 'suppress message outputs' - out.printf fmt, ' --verbose', 'output messages verbosely' - out.printf fmt, '-h,--help', 'print this message' - out.printf fmt, '-v,--version', 'print version and quit' - out.printf fmt, '--copyright', 'print copyright and quit' - - out.puts - out.puts 'Tasks:' - TASKS.each do |name, desc| - out.printf " %-10s %s\n", name, desc - end - - out.puts - out.puts 'Options for config:' - ConfigTable.each_definition do |name, (default, arg, desc, default2)| - out.printf " %-20s %s [%s]\n", - '--'+ name + (ConfigTable.bool_config?(name) ? '' : '='+arg), - desc, - default2 || default - end - out.printf " %-20s %s [%s]\n", - '--rbconfig=path', 'your rbconfig.rb to load', "running ruby's" - - out.puts - out.puts 'Options for install:' - out.printf " %-20s %s [%s]\n", - '--no-harm', 'only display what to do if given', 'off' - - out.puts - end - - # - # config - # - - def exec_config - super - @config.save - end - - # - # show - # - - def exec_show - ConfigTable.each_name do |k| - v = @config.get_raw(k) - if not v or v.empty? then - v = '(not specified)' - end - printf "%-10s %s\n", k, v - end - end - -end - -### end toplevel.rb - -if $0 == __FILE__ then - begin - installer = ToplevelInstaller.new( Dir.pwd ) - installer.execute - rescue - raise if $DEBUG - $stderr.puts $!.message - $stderr.puts "try 'ruby #{$0} --help' for usage" - exit 1 - end -end diff --git a/vendor/RedCloth-3.0.3/run-tests.rb b/vendor/RedCloth-3.0.3/run-tests.rb deleted file mode 100644 index 65b5c969..00000000 --- a/vendor/RedCloth-3.0.3/run-tests.rb +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env ruby -require 'lib/redcloth' -require 'yaml' - -Dir["tests/*.yml"].each do |testfile| - YAML::load_documents( File.open( testfile ) ) do |doc| - if doc['in'] and doc['out'] - red = RedCloth.new( doc['in'] ) - html = if testfile =~ /markdown/ - red.to_html( :markdown ) - else - red.to_html - end - puts "---" - - html.gsub!( /\n+/, "\n" ) - doc['out'].gsub!( /\n+/, "\n" ) - if html == doc['out'] - puts "success: true" - else - puts "out: "; p html - puts "expected: "; p doc['out'] - end - end - end -end diff --git a/vendor/RedCloth-3.0.3/tests/code.yml b/vendor/RedCloth-3.0.3/tests/code.yml deleted file mode 100644 index bc7bd2cc..00000000 --- a/vendor/RedCloth-3.0.3/tests/code.yml +++ /dev/null @@ -1,105 +0,0 @@ ---- -in: 'This is an empty dictionary: @{}@' -out: '

    This is an empty dictionary: {}

    ' ---- -in: |- - Testing nested pre tags... - -
    -  
    -    Good code here.
    -
    -    
    -      a = 1
    -    
    - - Bad code here. - - -
    -
    - -out: |- -

    Testing nested pre tags…

    - - -
    -  
    -    Good code here.
    -  
    -    <pre>
    -      a = 1
    -    </pre>
    -  
    -    Bad code here.
    -  
    -    <script language="JavaScript">
    -      window.open( "about:blank" );
    -    </script>
    -  
    -  
    ---- -in: |- -
    -  *** test
    -  
    -out: |- -
    -  *** test
    -  
    ---- -in: |- - - *** test - -out: |- - *** test ---- -in: '*this is strong*' -out: '

    this is strong

    ' ---- -in: '*this test is strong*' -out: '

    this test is strong

    ' ---- -in:
     __inline__
    -out:
     __inline__
    ---- -in: |- - * @foo@ - * @bar@ - * and @x@ is also. -out: "
      \n\t
    • foo
    • \n\t\t
    • bar
    • \n\t\t
    • and x is also.
    • \n\t
    " ---- -in: |- -
      
    -
      
    -out: |- -
     <hello> 
    -
     <hello> 
    ---- -in: | - Test of Markdown-style indented code. - - a = [1, 2, 3] - a.each do |x| - puts "test number", x, - "and more!" - end - - Paragraph 2. - - Paragraph 3. -out: |- -

    Test of Markdown-style indented code.

    - -
    a = [1, 2, 3]
    -  a.each do |x|
    -    puts "test number", x,
    -      "and more!" 
    -  end
    - -

    Paragraph 2.

    - -

    Paragraph 3.

    diff --git a/vendor/RedCloth-3.0.3/tests/images.yml b/vendor/RedCloth-3.0.3/tests/images.yml deleted file mode 100644 index d097e0fb..00000000 --- a/vendor/RedCloth-3.0.3/tests/images.yml +++ /dev/null @@ -1,171 +0,0 @@ ---- -in: This is an !image.jpg! -out:

    This is an

    ---- -in: This is an !image.jpg(with alt text)! -out:

    This is an with alt text

    ---- -in: This is an !http://example.com/i/image.jpg! -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg#a1! -out:

    This is an

    ---- -in: This is an !image.jpg!. -out:

    This is an .

    ---- -in: This is an !image.jpg(with alt text)!. -out:

    This is an with alt text.

    ---- -in: This is an !http://example.com/i/image.jpg!. -out:

    This is an .

    ---- -in: This is an !http://example.com/i/image.jpg#a1!. -out:

    This is an .

    ---- -in: This is not an image!!! -out:

    This is not an image!!!

    ---- -in: This is an !http://example.com/i/image.jpg!:#1 -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:#a -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:#a1 -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:#a10 -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:index.html -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:index.html#1 -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:index.html#a1 -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:index.html#a10 -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:index.html?foo=bar -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:index.html?foo=bar#1 -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:index.html?foo=bar#a -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:index.html?foo=bar#a1 -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:index.html?foo=bar#a10 -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/ -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/#1 -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/#a -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/#a1 -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/#a10 -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html#1 -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html#a -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html#a1 -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html#a10 -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar#1 -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar#a -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar#a1 -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar#a10 -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#1 -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a1 -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a10 -out:

    This is an

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b. -out:

    This is an .

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#1. -out:

    This is an .

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a. -out:

    This is an .

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a1. -out:

    This is an .

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a10. -out:

    This is an .

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b, but this is not. -out:

    This is an , but this is not.

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#1, but this is not. -out:

    This is an , but this is not.

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a, but this is not. -out:

    This is an , but this is not.

    ---- -in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a1, but this is not. -out:

    This is an , but this is not.

    ---- -in: (This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a10) This is not. -out:

    (This is an ) This is not.

    ---- -in: (This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b) This is not. -out:

    (This is an ) This is not.

    ---- -in: (This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#1) This is not. -out:

    (This is an ) This is not.

    ---- -in: (This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a) This is not. -out:

    (This is an ) This is not.

    ---- -in: (This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a1) This is not. -out:

    (This is an ) This is not.

    ---- -in: (This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a10) This is not. -out:

    (This is an ) This is not.

    diff --git a/vendor/RedCloth-3.0.3/tests/instiki.yml b/vendor/RedCloth-3.0.3/tests/instiki.yml deleted file mode 100644 index 89b8ec6b..00000000 --- a/vendor/RedCloth-3.0.3/tests/instiki.yml +++ /dev/null @@ -1,39 +0,0 @@ ---- # Bugs filed at http://www.instiki.org/show/BugReports -in: |- - _Hi, Joe Bob?, this should all be in italic!_ -out: |- -

    Hi, Joe Bob?, this should all be in italic!

    ---- -in: '*this span is strong*' -out: '

    this span is strong

    ' ---- -in: '*this Camel Thing? is strong*' -out: '

    this Camel Thing? is strong

    ' ---- -in: '_this span is italic_' -out: '

    this span is italic

    ' ---- -in: '%{color:red}nested span because of Camel Word?%' -out: '

    nested span because of Camel Word?

    ' ---- -in: |- - h2. Version History - - * "Version - 0.0":http://www.threewordslong.com/render-0-8-9b.patch - Early version using MD5 hashes. - * "Version - 0.1":http://www.threewordslong.com/chunk-0-1.patch.gz - First cut of new system. Much cleaner. - * "Version 0.2":http://www.threewordslong.com/chunk-0-2.patch.gz - Fixed problem with "authors" page and some tests. -out: |- -

    Version History

    - -
      -
    • Version - 0.0 – Early version using MD5 hashes.
    • -
    • Version - 0.1 – First cut of new system. Much cleaner.
    • -
    • Version 0.2 – Fixed problem with “authors” page and some tests.
    • -
    ---- -in: "--richSeymour --whyTheLuckyStiff" -out: "

    —richSeymour—whyTheLuckyStiff

    " diff --git a/vendor/RedCloth-3.0.3/tests/links.yml b/vendor/RedCloth-3.0.3/tests/links.yml deleted file mode 100644 index 16b63331..00000000 --- a/vendor/RedCloth-3.0.3/tests/links.yml +++ /dev/null @@ -1,155 +0,0 @@ ---- -in: '"link text":#1' -out:

    link text

    ---- -in: '"link text":#a' -out:

    link text

    ---- -in: '"link text":#a1' -out:

    link text

    ---- -in: '"link text":#a10' -out:

    link text

    ---- -in: '"link text":index.html' -out:

    link text

    ---- -in: '"link text":index.html#1' -out:

    link text

    ---- -in: '"link text":index.html#a' -out:

    link text

    ---- -in: '"link text":index.html#a1' -out:

    link text

    ---- -in: '"link text":index.html#a10' -out:

    link text

    ---- -in: '"link text":http://example.com/' -out:

    link text

    ---- -in: '"link text":http://example.com/#1' -out:

    link text

    ---- -in: '"link text":http://example.com/#a' -out:

    link text

    ---- -in: '"link text":http://example.com/#a1' -out:

    link text

    ---- -in: '"link text":http://example.com/#a10' -out:

    link text

    ---- -in: '"link text":http://example.com/index.html' -out:

    link text

    ---- -in: '"link text":http://example.com/index.html#a' -out:

    link text

    ---- -in: '"link text":http://example.com/index.html#1' -out:

    link text

    ---- -in: '"link text":http://example.com/index.html#a1' -out:

    link text

    ---- -in: '"link text":http://example.com/index.html#a10' -out:

    link text

    ---- -in: '"link text":http://example.com/?foo=bar' -out:

    link text

    ---- -in: '"link text":http://example.com/?foo=bar#a' -out:

    link text

    ---- -in: '"link text":http://example.com/?foo=bar#1' -out:

    link text

    ---- -in: '"link text":http://example.com/?foo=bar#a1' -out:

    link text

    ---- -in: '"link text":http://example.com/?foo=bar#a10' -out:

    link text

    ---- -in: '"link text":http://example.com/?foo=bar&a=b' -out:

    link text

    ---- -in: '"link text":http://example.com/?foo=bar&a=b#1' -out:

    link text

    ---- -in: '"link text":http://example.com/?foo=bar&a=b#a' -out:

    link text

    ---- -in: '"link text":http://example.com/?foo=bar&a=b#a1' -out:

    link text

    ---- -in: '"link text":http://example.com/?foo=bar&a=b#a10' -out:

    link text

    ---- -in: 'This is a "link":http://example.com/' -out:

    This is a link

    ---- -in: 'This is a "link":http://example.com/.' -out:

    This is a link.

    ---- -in: 'This is a "link":http://example.com/index.html.' -out:

    This is a link.

    ---- -in: 'This is a "link":http://example.com/index.html#a.' -out:

    This is a link.

    ---- -in: 'This is a "link":http://example.com/index.html#1.' -out:

    This is a link.

    ---- -in: 'This is a "link":http://example.com/index.html#a1.' -out:

    This is a link.

    ---- -in: 'This is a "link":http://example.com/index.html#a10.' -out:

    This is a link.

    ---- -in: 'This is a "link":http://example.com/?foo=bar.' -out:

    This is a link.

    ---- -in: 'This is a "link":http://example.com/?foo=bar#1.' -out:

    This is a link.

    ---- -in: 'This is a "link":http://example.com/?foo=bar#a.' -out:

    This is a link.

    ---- -in: 'This is a "link":http://example.com/?foo=bar#a1.' -out:

    This is a link.

    ---- -in: 'This is a "link":http://example.com/?foo=bar#a10.' -out:

    This is a link.

    ---- -in: 'This is a "link":http://example.com/?foo=bar#a10, but this is not.' -out:

    This is a link, but this is not.

    ---- -in: '(This is a "link":http://example.com/?foo=bar#a10) but this is not.' -out:

    (This is a link) but this is not.

    ---- -in: '"link text(link title)":http://example.com/' -out:

    link text

    -# --- -# in: '"link text(link title) ":http://example.com/' -# out:

    “link text(link title) “:http://example.com/

    -# comments: this is a real test and should pass ---- -in: '"(link) text(link title)":http://example.com/' -out:

    text

    -comments: link text can not contain parentheses ---- -in: '"Dive Into XML":http://www.xml.com/pub/au/164' -out:

    Dive Into XML

    ---- -in: '"Lab Exercises":../lab/exercises/exercises.html.' -out:

    Lab Exercises.

    ---- -in: 'Go to "discuss":http://www.dreammoods.com/cgibin/cutecast/cutecast.pl?forum=1&thread=26627 to discuss.' -out:

    Go to discuss to discuss.

    ---- -in: '* "rubylang":http://www.ruby-lang.org/en/' -out: "" ---- -in: 'The ION coding style document found at "IONCodingStyleGuide.doc":http://perforce:8081/@md=d&cd=//&c=82E@//depot/systest/system/main/pub/doc/IONCodingStyleGuide.doc?ac=22 codifies a couple of rules to ensure reasonably consistent code and documentation of libraries in ION. Test text' -out:

    The ION coding style document found at IONCodingStyleGuide.doc codifies a couple of rules to ensure reasonably consistent code and documentation of libraries in ION. Test text

    diff --git a/vendor/RedCloth-3.0.3/tests/lists.yml b/vendor/RedCloth-3.0.3/tests/lists.yml deleted file mode 100644 index cf8938f0..00000000 --- a/vendor/RedCloth-3.0.3/tests/lists.yml +++ /dev/null @@ -1,77 +0,0 @@ ---- # Bret Pettichord, Thanks. -in: |- - * first line - * second - line - * third line -out: |- -
      -
    • first line
    • -
    • second - line
    • -
    • third line
    • -
    ---- -in: |- - p. start - - * one - and one - * two - and two - * three - - p. end -out: |- -

    start

    -
      -
    • one - and one
    • -
    • two - and two
    • -
    • three
    • -
    - -

    end

    ---- -in: |- - Funky: - - * Testing - *# number - *##* bullet - *# number - *# number - yeah number - #* bullet - *** okay - ****# what - - -out: |- -

    Funky:

    -
      -
    • Testing -
        -
      1. number -
          -
        • bullet
        • -
        -
      2. -
      3. number
      4. -
      5. number - yeah number
      6. -
          -
        • bullet -
            -
          • okay -
              -
            1. what
            2. -
        • -
    • -
    - - ---- -in: "* command run: @time ruby run-tests.rb > toto@" -out: "
      \n\t
    • command run: time ruby run-tests.rb > toto
    • \n\t
    " diff --git a/vendor/RedCloth-3.0.3/tests/markdown.yml b/vendor/RedCloth-3.0.3/tests/markdown.yml deleted file mode 100644 index a053ea39..00000000 --- a/vendor/RedCloth-3.0.3/tests/markdown.yml +++ /dev/null @@ -1,218 +0,0 @@ -in: | - This is a regular paragraph. - - - - - -
    Foo
    - - This is another regular paragraph. -out: |- -

    This is a regular paragraph.

    - - - - - - -
    Foo
    -

    This is another regular paragraph.

    ---- -in: '"Larry Bird":http://images.google.com/images?num=30&q=larry+bird' -out: '

    "Larry Bird":http://images.google.com/images?num=30&q=larry+bird

    ' ---- -in: '©' -out:

    ©

    ---- -in: AT&T -out:

    AT&T

    - -# We don't do this. -# --- -# in: 4 < 5 -# out: 4 < 5 ---- -in: | - This is an H1 - ============= - - This is an H2 - ------------- -out: |- -

    This is an H1

    - -

    This is an H2

    ---- -in: | - # This is an H1 - - ## This is an H2 - - ###### This is an H6 -out: |- -

    This is an H1

    - -

    This is an H2

    - -
    This is an H6
    ---- -in: | - > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, - > consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. - > Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. - > - > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse - > id sem consectetuer libero luctus adipiscing. -out: |- -
    -

    This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, - consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. - Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.

    - -

    Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse - id sem consectetuer libero luctus adipiscing.

    - -
    ---- -in: | - > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, - consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. - Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. - > - > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse - id sem consectetuer libero luctus adipiscing. -out: |- -
    -

    This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, - consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. - Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.

    - -

    Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse - id sem consectetuer libero luctus adipiscing.

    - -
    ---- -in: | - > This is the first level of quoting. - > - > > This is nested blockquote. - > - > Back to the first level. -out: |- -
    -

    This is the first level of quoting.

    -
    -

    This is nested blockquote.

    - -
    - - - -

    Back to the first level.

    - -
    ---- -in: | - > ## This is a header. - > - > 1. This is the first list item. - > 2. This is the second list item. - > - > Here's some example code: - > - > return shell_exec("echo $input | $markdown_script"); -out: |- -
    -

    This is a header.

    - - - -

    1. This is the first list item. - 2. This is the second list item.

    - -

    Here's some example code:

    -
    return shell_exec("echo $input | $markdown_script");
    - -
    ---- -in: | - * * * - - *** - - ***** - - - - - - - --------------------------------------- - - _ _ _ -out: |- -
    - -
    - -
    - -
    - -
    - -
    ---- -in: | - This is [an example](http://example.com/ "Title") inline link. - - [This link](http://example.net/) has no title attribute. -out: |- -

    This is an example inline link.

    - -

    This link has no title attribute.

    ---- -in: See my [About](/about/) page for details. -out:

    See my About page for details.

    ---- -in: | - This is [an example][id] reference-style link. - - This is [an example] [id] reference-style link. - - [id]: http://example.com/ "Optional Title Here" -out: |- -

    This is an example reference-style link.

    - -

    This is an example reference-style link.

    ---- -in: | - [Google][] - [Google]: http://google.com/ -out:

    Google

    ---- -in: | - Visit [Daring Fireball][] for more information. - [Daring Fireball]: http://daringfireball.net/ -out:

    Visit Daring Fireball for more information.

    ---- -in: | - I get 10 times more traffic from [Google] [1] than from - [Yahoo] [2] or [MSN] [3]. - - [1]: http://google.com/ "Google" - [2]: http://search.yahoo.com/ "Yahoo Search" - [3]: http://search.msn.com/ "MSN Search" - -out: |- -

    I get 10 times more traffic from Google than from - Yahoo or MSN.

    ---- -in: | - I get 10 times more traffic from [Google][] than from - [Yahoo][] or [MSN][]. - - [google]: http://google.com/ "Google" - [yahoo]: http://search.yahoo.com/ "Yahoo Search" - [msn]: http://search.msn.com/ "MSN Search" -out: |- -

    I get 10 times more traffic from Google than from - Yahoo or MSN.

    diff --git a/vendor/RedCloth-3.0.3/tests/poignant.yml b/vendor/RedCloth-3.0.3/tests/poignant.yml deleted file mode 100644 index 1a0f6942..00000000 --- a/vendor/RedCloth-3.0.3/tests/poignant.yml +++ /dev/null @@ -1,64 +0,0 @@ ---- # Tests from the (Poignant Guide) -in: > - h3. False - - - ! - if plastic_cup - print "Plastic cup is on the up 'n' up!" - end -
    - - - If @plastic_cup@ contains either @nil@ or @false@, you won't see anything print - to the screen. They're not on the @if@ guest list. So @if@ isn't going to run - any of the code it's protecting. - - - But @nil@ and @false@ need not walk away in shame. They may be of questionable - character, but @unless@ runs a smaller establishment that caters to the bedraggled. - The @unless@ keyword has a policy of only allowing those with a negative charge in. - Who are: @nil@ and @false@. - - -
    -    unless plastic_cup
    -      print "Plastic cup is on the down low."
    -    end
    -  
    - - - You can also use @if@ and @unless@ at the end of a single line of code, if that's - all that is being protected. - - -
    -    print "Yeah, plastic cup is up again!" if plastic_cup
    -    print "Hardly. It's down." unless plastic_cup
    -  
    - - - Now that you've met @false@, I'm sure you can see what's on next. - -out: "

    False

    \n\n\t

    \"Shape

    \n\n\t

    The cat Trady Blix. Frozen in emptiness. Immaculate whiskers rigid. Placid eyes of lake. Tail of warm icicle. Sponsored by a Very Powerful Pause Button.

    \n\n\t

    The darkness surrounding Blix can be called negative space. Hang on to that phrase. Let it suggest that the emptiness has a negative connotation. In a similar way, nil has a slightly sour note that it whistles.

    \n\n\t

    Generally speaking, everything in Ruby has a positive charge to it. This spark flows through strings, numbers, regexps, all of it. Only two keywords wear a shady cloak: nil and false draggin us down.

    \n\n\t

    You can test that charge with an if keyword. It looks very much like the do blocks we saw in the last chapter, in that both end with an end.

    \n\n\n
    \n  if plastic_cup\n    print \"Plastic cup is on the up 'n' up!\" \n  end\n
    \n\t

    If plastic_cup contains either nil or false, you won’t see anything print to the screen. They’re not on the if guest list. So if isn’t going to run any of the code it’s protecting.

    \n\n\t

    But nil and false need not walk away in shame. They may be of questionable character, but unless runs a smaller establishment that caters to the bedraggled. The unless keyword has a policy of only allowing those with a negative charge in. Who are: nil and false.

    \n\n\n
    \n  unless plastic_cup\n    print \"Plastic cup is on the down low.\" \n  end\n
    \n\t

    You can also use if and unless at the end of a single line of code, if that’s all that is being protected.

    \n\n\n
    \n  print \"Yeah, plastic cup is up again!\" if plastic_cup\n  print \"Hardly. It's down.\" unless plastic_cup\n
    \n\t

    Now that you’ve met false, I’m sure you can see what’s on next.

    " diff --git a/vendor/RedCloth-3.0.3/tests/table.yml b/vendor/RedCloth-3.0.3/tests/table.yml deleted file mode 100644 index bf5059e1..00000000 --- a/vendor/RedCloth-3.0.3/tests/table.yml +++ /dev/null @@ -1,198 +0,0 @@ -in: | - {background:#ddd}. |S|Target|Complete|App|Milestone| - |!/i/g.gif!|11/18/04|11/18/04|070|XML spec complete| - |!/i/g.gif!|11/29/04|11/29/04|011|XML spec complete (KH is on schedule)| - |!/i/g.gif!|11/29/04|11/29/04|051|XML spec complete (KH is on schedule)| - |!/i/g.gif!|11/29/04|11/29/04|081|XML spec complete (KH is on schedule)| - |!/i/g.gif!|11/19/04|11/22/04|070|Preprocessor complete| - |!/i/g.gif!|11/22/04|11/22/04|070|Dialog pass 1 builds an index file| - |!/i/g.gif!|11/24/04|11/24/04|070|Dialog pass 2 98% complete| - |!/i/g.gif!|11/30/04|11/30/04|070|Feature complete. Passes end-to-end smoke test.| - |!/i/g.gif!|11/30/04|11/30/04|011|Preprocessor updates complete| - |!/i/g.gif!|11/30/04|11/30/04|051|Preprocessor updates complete| - |!/i/g.gif!|11/30/04|11/29/04|081|Preprocessor updates complete| - |!/i/w.gif!|12/02/04|.|011|Dialog pass 1 and 2 complete (98+%)| - |!/i/w.gif!|12/02/04|.|051|Dialog pass 1 and 2 complete (98+%)| - |!/i/w.gif!|12/02/04|.|081|Dialog pass 1 and 2 complete (98+%)| - |!/i/w.gif!|12/03/04|.|011|Feature complete| - |!/i/w.gif!|12/03/04|.|051|Feature complete| - |!/i/w.gif!|12/03/04|.|081|Feature complete| - |!/i/w.gif!|12/10/04|.|011|Deployed to Napa test workstation. Passes smoke test.| - |!/i/w.gif!|12/10/04|.|051|Deployed to Napa test workstation. Passes smoke test.| - |!/i/w.gif!|12/10/04|.|081|Deployed to Napa test workstation. Passes smoke test.| - |!/i/w.gif!|12/10/04|.|070|Deployed to Napa test workstation. Passes smoke test.| - |!/i/w.gif!|12/17/04|.|011|System testing complete. Begin testing with live customer data.| - |!/i/w.gif!|12/17/04|.|051|System testing complete. Begin testing with live customer data.| - |!/i/w.gif!|12/17/04|.|081|System testing complete. Begin testing with live customer data.| - |!/i/w.gif!|12/17/04|.|070|System testing complete. Begin testing with live customer data.| -out: |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    STargetCompleteAppMilestone
    11/18/0411/18/04070XML spec complete
    11/29/0411/29/04011XML spec complete (KH is on schedule)
    11/29/0411/29/04051XML spec complete (KH is on schedule)
    11/29/0411/29/04081XML spec complete (KH is on schedule)
    11/19/0411/22/04070Preprocessor complete
    11/22/0411/22/04070Dialog pass 1 builds an index file
    11/24/0411/24/04070Dialog pass 2 98% complete
    11/30/0411/30/04070Feature complete. Passes end-to-end smoke test.
    11/30/0411/30/04011Preprocessor updates complete
    11/30/0411/30/04051Preprocessor updates complete
    11/30/0411/29/04081Preprocessor updates complete
    12/02/04011Dialog pass 1 and 2 complete (98+%)
    12/02/04051Dialog pass 1 and 2 complete (98+%)
    12/02/04081Dialog pass 1 and 2 complete (98+%)
    12/03/04011Feature complete
    12/03/04051Feature complete
    12/03/04081Feature complete
    12/10/04011Deployed to Napa test workstation. Passes smoke test.
    12/10/04051Deployed to Napa test workstation. Passes smoke test.
    12/10/04081Deployed to Napa test workstation. Passes smoke test.
    12/10/04070Deployed to Napa test workstation. Passes smoke test.
    12/17/04011System testing complete. Begin testing with live customer data.
    12/17/04051System testing complete. Begin testing with live customer data.
    12/17/04081System testing complete. Begin testing with live customer data.
    12/17/04070System testing complete. Begin testing with live customer data.
    diff --git a/vendor/RedCloth-3.0.3/tests/textism.yml b/vendor/RedCloth-3.0.3/tests/textism.yml deleted file mode 100644 index 5489c04d..00000000 --- a/vendor/RedCloth-3.0.3/tests/textism.yml +++ /dev/null @@ -1,397 +0,0 @@ ---- -in: h1. Header 1 -out:

    Header 1

    ---- -in: h2. Header 2 -out:

    Header 2

    ---- -in: h3. Header 3 -out:

    Header 3

    ---- -in: |- - Any old text. - - bq. A block quotation. - - Any old text. - -out: |- -

    Any old text.

    - -
    -

    A block quotation.

    -
    - -

    Any old text.

    - ---- -in: This is covered elsewhere[1]. -out:

    This is covered elsewhere1.

    ---- -in: fn1. Down here, in fact. -out:

    1 Down here, in fact.

    ---- -in: |- - # A first item - # A second item - # A third item - # A fourth item -out: |- -
      -
    1. A first item
    2. -
    3. A second item
    4. -
    5. A third item
    6. -
    7. A fourth item
    8. -
    ---- -in: |- - * A first item - * A second item - * A third item - * A fourth item - -out: |- -
      -
    • A first item
    • -
    • A second item
    • -
    • A third item
    • -
    • A fourth item
    • -
    - ---- -in: _a phrase_ -out:

    a phrase

    ---- -in: __a phrase__ -out:

    a phrase

    ---- -in: '*a phrase*' -out:

    a phrase

    ---- -in: '**a phrase**' -out:

    a phrase

    ---- -in: Nabokov's ??Pnin?? -out:

    Nabokov’s Pnin

    ---- -in: -a phrase- -out:

    a phrase

    ---- -in: +a phrase+ -out:

    a phrase

    ---- -in: ^a phrase^ -out:

    a phrase

    ---- -in: ~a phrase~ -out:

    a phrase

    -# --- -# in: %(caps)SPAN% -# out:

    SPAN ---- -in: %{color:red}red% -out:

    red

    ---- -in: %[fr]rouge% -out:

    rouge

    ---- -in: _(big)red_ -out:

    red

    ---- -in: p(bob). A paragraph -out:

    A paragraph

    ---- -in: p{color:#ddd}. A paragraph -out:

    A paragraph

    ---- -in: p[fr]. A paragraph -out:

    A paragraph

    ---- -in: h2()>. right-aligned header2, indented 1em both side -out:

    right-aligned header2, indented 1em both side

    ---- -in: h3=. centered header -out:

    centered header

    ---- -in: '!>/image.gif! right-aligned image' -out:

    right-aligned image

    ---- -in: p[no]{color:red}. A Norse of a different colour. -out:

    A Norse of a different colour.

    ---- -in: |- - |This|is|a|simple|table| - |This|is|a|simple|row| -out: |- - - - - - - - - - - - - - - - -
    Thisisasimpletable
    Thisisasimplerow
    ---- -in: |- - table{border:1px solid black}. - |This|is|a|row| - |This|is|a|row| -out: |- - - - - - - - - - - - - - -
    Thisisarow
    Thisisarow
    ---- -in: '{background:#ddd}. |This|is|a|row|' -out: |- - - - - - - - -
    Thisisarow
    ---- -in: |- - |{background:#ddd}. Cell with gray background| - |\2. Cell spanning 2 columns| - |/3. Cell spanning 3 rows| - |>. Right-aligned cell| -out: |- - - - - - - - - - - - - - -
    Cell with gray background
    Cell spanning 2 columns
    Cell spanning 3 rows
    Right-aligned cell
    -# --- -# in: |- -# This is a "link":bob to Bob's website. -# -# [bob]http://itsbob.com/index.html ---- -in: ACLU(American Civil Liberties Union) -out:

    ACLU

    ---- -in: |- - h2{color:green}. This is a title - - h3. This is a subhead - - p{color:red}. This is some text of dubious character. Isn't the use of "quotes" just lazy writing -- and theft of 'intellectual property' besides? I think the time has come to see a block quote. - - bq[fr]. This is a block quote. I'll admit it's not the most exciting block quote ever devised. - - Simple list: - - #{color:blue} one - # two - # three - - Multi-level list: - - # one - ## aye - ## bee - ## see - # two - ## x - ## y - # three - - Mixed list: - - * Point one - * Point two - ## Step 1 - ## Step 2 - ## Step 3 - * Point three - ** Sub point 1 - ** Sub point 2 - - - Well, that went well. How about we insert an old-fashioned hypertext link? Will the quote marks in the tags get messed up? No! - - "This is a link (optional title)":http://www.textism.com - - table{border:1px solid black}. - |_. this|_. is|_. a|_. header| - <{background:gray}. |\2. this is|{background:red;width:200px}. a|^<>{height:200px}. row| - |this|<>{padding:10px}. is|^. another|(bob#bob). row| - - An image: - - !/common/textist.gif(optional alt text)! - - # Librarians rule - # Yes they do - # But you knew that - - Some more text of dubious character. Here is a noisome string of CAPITAL letters. Here is something we want to _emphasize_. - That was a linebreak. And something to indicate *strength*. Of course I could use my own HTML tags if I felt like it. - - h3. Coding - - This is some code, "isn't it". Watch those quote marks! Now for some preformatted text: - -
    -  
    -      $text = str_replace("

    %::%

    ","",$text); - $text = str_replace("%::%

    ","",$text); - $text = str_replace("%::%","",$text); - -
    -
    - - This isn't code. - - - So you see, my friends: - - * The time is now - * The time is not later - * The time is not yesterday - * We must act - -out: |- -

    This is a title

    - -

    This is a subhead

    - -

    This is some text of dubious character. Isn’t the use of “quotes” just lazy writing—and theft of ‘intellectual property’ besides? I think the time has come to see a block quote.

    - -
    -

    This is a block quote. I’ll admit it’s not the most exciting block quote ever devised.

    -
    - -

    Simple list:

    -
      -
    1. one
    2. -
    3. two
    4. -
    5. three
    6. -
    - -

    Multi-level list:

    -
      -
    1. one -
        -
      1. aye
      2. -
      3. bee
      4. -
      5. see
      6. -
      -
    2. -
    3. two -
        -
      1. x
      2. -
      3. y
      4. -
      -
    4. -
    5. three
    6. -
    - -

    Mixed list:

    -
      -
    • Point one
    • -
    • Point two -
        -
      1. Step 1
      2. -
      3. Step 2
      4. -
      5. Step 3
      6. -
      -
    • -
    • Point three -
        -
      • Sub point 1
      • -
      • Sub point 2
      • -
    • -
    - -

    Well, that went well. How about we insert an old-fashioned hypertext link? Will the quote marks in the tags get messed up? No!

    - -

    This is a link

    - - - - - - - - - - - - - - - - - - - -
    thisisaheader
    this isarow
    thisisanotherrow
    - - - -

    An image:

    - -

    optional alt text

    -
      -
    1. Librarians rule
    2. -
    3. Yes they do
    4. -
    5. But you knew that
    6. -
    - -

    Some more text of dubious character. Here is a noisome string of CAPITAL letters. Here is something we want to emphasize. - That was a linebreak. And something to indicate strength. Of course I could use my own HTML tags if I felt like it.

    - -

    Coding

    - -

    This is some code, "isn't it". Watch those quote marks! Now for some preformatted text:

    - - -
    -  
    -      $text = str_replace("<p>%::%</p>","",$text);
    -      $text = str_replace("%::%</p>","",$text);
    -      $text = str_replace("%::%","",$text);
    -  
    -  
    -  
    -

    This isn’t code.

    - -

    So you see, my friends:

    -
      -
    • The time is now
    • -
    • The time is not later
    • -
    • The time is not yesterday
    • -
    • We must act
    • -
    - diff --git a/vendor/rubyzip-0.5.8/ChangeLog b/vendor/rubyzip-0.5.8/ChangeLog deleted file mode 100644 index 1bb67582..00000000 --- a/vendor/rubyzip-0.5.8/ChangeLog +++ /dev/null @@ -1,1273 +0,0 @@ -2005-03-17 18:11 thomas - - * NEWS, README, lib/zip/zip.rb: [no log message] - -2005-03-17 18:04 thomas - - * install.rb: Fixed install.rb - -2005-03-03 18:38 thomas - - * Rakefile: [no log message] - -2005-02-27 16:23 thomas - - * lib/zip/ziprequire.rb: Added documentation to ziprequire - -2005-02-27 16:17 thomas - - * README, TODO, lib/zip/ziprequire.rb: Added documentation to - ziprequire - -2005-02-27 15:02 thomas - - * Rakefile, test/ziptest.rb: [no log message] - -2005-02-19 21:30 thomas - - * lib/zip/ioextras.rb, lib/zip/stdrubyext.rb, - lib/zip/tempfile_bugfixed.rb, lib/zip/zip.rb, - lib/zip/ziprequire.rb, test/ioextrastest.rb, - test/stdrubyexttest.rb, test/zipfilesystemtest.rb, - test/ziprequiretest.rb, test/ziptest.rb: Added more rdoc and - changed the remaining tests to Test::Unit - -2005-02-19 20:28 thomas - - * lib/zip/: ioextras.rb, zip.rb: Added documentation to - ZipInputStream and ZipOutputStream - -2005-02-18 10:27 thomas - - * README: [no log message] - -2005-02-17 23:21 thomas - - * README, Rakefile: Added ppackage (publish package) task to - Rakefile - -2005-02-17 22:49 thomas - - * README, Rakefile, TODO: Added pdoc (publish doc) task to Rakefile - -2005-02-17 21:27 thomas - - * README, Rakefile, TODO, lib/zip/stdrubyext.rb, lib/zip/zip.rb, - lib/zip/zipfilesystem.rb: Added a bunch of documentation - -2005-02-17 09:47 thomas - - * test/ziptest.rb: [no log message] - -2005-02-16 20:04 thomas - - * NEWS, README, Rakefile: Improved documentation and added rdoc - task to Rakefile - -2005-02-16 19:01 thomas - - * NEWS, Rakefile, lib/zip/zip.rb: [no log message] - -2005-02-16 18:47 thomas - - * Rakefile, samples/example.rb, samples/example_filesystem.rb, - samples/gtkRubyzip.rb, samples/write_simple.rb, - samples/zipfind.rb, test/.cvsignore, test/gentestfiles.rb: - Improvements to Rakefile - -2005-02-15 23:35 thomas - - * NEWS, TODO: [no log message] - -2005-02-15 23:26 thomas - - * Rakefile, rubyzip.gemspec: Now uses Rake to build gem - -2005-02-15 22:52 thomas - - * Rakefile: [no log message] - -2005-02-15 22:39 thomas - - * lib/zip/zip.rb, test/.cvsignore, test/ziptest.rb, NEWS: Fixed - compatibility issue with ruby 1.8.2. Migrated test suite to - Test::Unit - -2005-02-15 22:10 thomas - - * NEWS, lib/zip/ioextras.rb, lib/zip/stdrubyext.rb, - lib/zip/tempfile_bugfixed.rb, lib/zip/zip.rb, - lib/zip/zipfilesystem.rb, lib/zip/ziprequire.rb, test/.cvsignore, - test/file1.txt, test/file1.txt.deflatedData, test/file2.txt, - test/gentestfiles.rb, test/ioextrastest.rb, - test/notzippedruby.rb, test/rubycode.zip, test/rubycode2.zip, - test/stdrubyexttest.rb, test/testDirectory.bin, - test/zipWithDirs.zip, test/zipfilesystemtest.rb, - test/ziprequiretest.rb, test/ziptest.rb, test/data/.cvsignore, - test/data/file1.txt, test/data/file1.txt.deflatedData, - test/data/file2.txt, test/data/notzippedruby.rb, - test/data/rubycode.zip, test/data/rubycode2.zip, - test/data/testDirectory.bin, test/data/zipWithDirs.zip: Changed - directory structure - -2005-02-13 22:44 thomas - - * Rakefile, TODO: [no log message] - -2005-02-13 22:38 thomas - - * rubyzip.gemspec: [no log message] - -2005-02-13 21:53 thomas - - * install.rb: Made install.rb independent of the current path - (fixes bug reported by Drew Robinson) - -2004-12-12 11:22 thomas - - * NEWS, TODO, samples/write_simple.rb: Fixed 'version needed to - extract'-field wrong in local headers - -2004-05-02 15:17 thomas - - * rubyzip.gemspec: Added gemspec contributed by Chad Fowler - -2004-04-02 07:25 thomas - - * NEWS: Fix for FreeBSD 4.9 - -2004-03-29 00:28 thomas - - * NEWS: [no log message] - -2004-03-28 17:59 thomas - - * NEWS: [no log message] - -2004-03-27 16:09 thomas - - * test/stdrubyexttest.rb: Patch for stdrubyext.rb from Nobu Nakada - -2004-03-27 15:30 thomas - - * test/: ioextrastest.rb, stdrubyexttest.rb: converted some files - to unix line-endings - -2004-03-25 16:34 thomas - - * NEWS, install.rb: Significantly reduced memory footprint when - modifying zip files - -2004-03-16 18:20 thomas - - * install.rb, test/alltests.rb, test/ioextrastest.rb, - test/stdrubyexttest.rb, test/ziptest.rb: IO utility classes moved - to new file ioextras.rb. Tests moved to new file ioextrastest.rb - -2004-02-27 13:21 thomas - - * NEWS: Optimization to avoid decompression and recompression - -2004-01-30 16:17 thomas - - * NEWS: [no log message] - -2004-01-30 16:07 thomas - - * README, test/zipfilesystemtest.rb, test/ziptest.rb: Applied - extra-field patch - -2003-12-13 16:57 thomas - - * TODO: [no log message] - -2003-12-10 00:25 thomas - - * test/ziptest.rb: (Temporary) fix to bug reported by Takashi Sano - -2003-08-23 09:42 thomas - - * test/ziptest.rb, NEWS: Fixed ZipFile.get_ouput_stream bug - data - was never written to zip - -2003-08-21 16:05 thomas - - * install.rb: [no log message] - -2003-08-21 16:01 thomas - - * alltests.rb, stdrubyexttest.rb, zipfilesystemtest.rb, - ziprequiretest.rb, ziptest.rb, test/alltests.rb, - test/stdrubyexttest.rb, test/zipfilesystemtest.rb, - test/ziprequiretest.rb, test/ziptest.rb: Moved all test ruby - files to test/ - -2003-08-21 15:54 thomas - - * NEWS, install.rb, stdrubyext.rb, stdrubyexttest.rb, zip.rb, - zipfilesystem.rb, zipfilesystemtest.rb, ziprequire.rb, - ziprequiretest.rb, ziptest.rb, samples/example.rb, - samples/example_filesystem.rb, samples/gtkRubyzip.rb, - samples/zipfind.rb: Moved all production source files to zip/ so - they are in the same dir as when they are installed - -2003-08-21 15:31 thomas - - * NEWS, TODO, alltests.rb: [no log message] - -2003-08-21 15:26 thomas - - * filearchive.rb, filearchivetest.rb, fileutils.rb: Removed - filearchive.rb, filearchivetest.rb and fileutils.rb - -2003-08-21 15:24 thomas - - * samples/.cvsignore, samples/example_filesystem.rb, zip.rb: Added - samples/example_filesystem.rb. Fixed Tempfile creation for - entries created with get_output_stream where entries were in a - subdirectory - -2003-08-21 15:15 thomas - - * zip.rb, ziptest.rb: Fixed mkdir bug. ZipFile.mkdir didn't work if - the zipfile doesn't exist already - -2003-08-21 15:05 thomas - - * ziptest.rb: [no log message] - -2003-08-21 14:53 thomas - - * TODO, zipfilesystemtest.rb: Globbing test placeholder commented - out - -2003-08-21 14:32 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented ZipFsDir.new - and open - -2003-08-21 14:19 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented DirFsIterator - and tests - -2003-08-20 22:50 thomas - - * NEWS, TODO: [no log message] - -2003-08-20 22:45 thomas - - * zipfilesystemtest.rb: [no log message] - -2003-08-20 22:44 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsDir.foreach, ZipFsDir.entries now reimplemented in terms of - it - -2003-08-20 22:25 thomas - - * README: [no log message] - -2003-08-20 18:08 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: [no log message] - -2003-08-20 17:30 thomas - - * zipfilesystem.rb: All access from ZipFsFile and ZipFsDir to - ZipFile is now routed through ZipFileNameMapper which has the - single responsibility of mapping entry/filenames - -2003-08-20 17:18 thomas - - * alltests.rb, stdrubyext.rb, stdrubyexttest.rb: Added - stdrubyexttest.rb and added test test_select_map - -2003-08-20 16:10 thomas - - * zipfilesystem.rb: ZipFsDir was in the wrong module. ZipFileSystem - now has a ctor that creates ZipFsDir and ZipFsFile instances, - instead of creating them lazily. It then passes the dir instance - to the file instance and vice versa - -2003-08-20 15:55 thomas - - * zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: ZipFsFile.open - honours chdir - -2003-08-20 15:39 thomas - - * stdrubyext.rb, zip.rb, zipfilesystem.rb, zipfilesystemtest.rb, - ziptest.rb: Fixed ZipEntry::parent_as_string. Implemented - ZipFsDir.chdir, pwd and entries including test - -2003-08-19 15:44 thomas - - * zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsDir.mkdir - -2003-08-19 15:07 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsDir.delete (and aliases rmdir and unlink) - -2003-08-19 14:33 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Another dummy - implementation and commented out a test for select() which can be - added later - -2003-08-18 20:40 thomas - - * ziptest.rb: Honoured 1.8.0 Object.to_a deprecation warning - -2003-08-18 20:30 thomas - - * zip.rb, ziptest.rb, samples/example.rb, samples/zipfind.rb: - Converted a few more names to ruby underscore style that I missed - with the automated processing the first time around - -2003-08-18 18:39 thomas - - * zip.rb, zipfilesystem.rb, zipfilesystemtest.rb, ziptest.rb: - Implemented Zip::ZipFile.get_output_stream - -2003-08-17 18:28 thomas - - * README, install.rb, stdrubyext.rb, zipfilesystem.rb, - zipfilesystemtest.rb: Updated README with Documentation section. - Updated install.rb. Fixed three tests that failed on 1.8.0. - -2003-08-14 05:40 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Added empty - implementations of atime and ctime - -2003-08-13 17:08 thomas - - * simpledist.rb: Moved simpledist to a separate repository called - 'misc' - -2003-08-13 16:31 thomas - - * NEWS: [no log message] - -2003-08-13 16:29 thomas - - * stdrubyext.rb, zip.rb, zipfilesystem.rb, zipfilesystemtest.rb, - ziprequire.rb, ziprequiretest.rb, ziptest.rb, samples/example.rb, - samples/gtkRubyzip.rb, samples/zipfind.rb: Changed all method - names to the ruby convention underscore style - -2003-08-13 15:18 thomas - - * alltests.rb, zipfilesystem.rb, zipfilesystemtest.rb: Implemented - a lot more of the stat methods. Mostly with dummy implementations - that return values that indicate that these features aren't - supported - -2003-08-13 11:44 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented more methods - and tests in zipfilesystem. Mostly empty methods as permissions - and file types other than files and directories are not supported - -2003-08-13 11:29 thomas - - * install.rb, stdrubyext.rb, zip.rb, zipfilesystem.rb, - zipfilesystemtest.rb: Addd file stdrubyext.rb and moved the - modifications to std ruby classes to it. Refactored the ZipFsStat - tests and ZipFsStat. Added Module.forwardMessages and used it to - implement the forwarding of calls in ZipFsStat - -2003-08-13 10:39 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Added - Zip::ZipFsFile::ZipFsStat and started implementing it and its - methods - -2003-08-13 10:02 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb, ziptest.rb: Updated and - added missing copyright notices - -2003-08-13 10:00 thomas - - * zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: zipfilesystem.rb - is becoming big and not everyone will want to use that code. - Therefore zip.rb no longer requires it. Instead you must require - zipfilesystem.rb itself if you want to use it - -2003-08-13 09:51 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented dummy - permission test methods - -2003-08-13 06:37 thomas - - * TODO, zip.rb, ziptest.rb: Merged from patch from Kristoffer - Lunden. Fixed more 1.8.0 incompatibilites - tests run on 1.8.0 - now - -2003-08-12 19:18 thomas - - * zip.rb: Get rid of 1.8.0 warning - -2003-08-12 19:14 thomas - - * ziptest.rb: ruby 1.8.0 compatibility fix - -2003-08-12 19:13 thomas - - * NEWS, zip.rb: ruby-zlib 0.6.0 compatibility fix - -2002-12-22 20:12 thomas - - * zip.rb: [no log message] - -2002-09-16 22:11 thomas - - * NEWS: [no log message] - -2002-09-15 17:16 thomas - - * samples/zipfind.rb: [no log message] - -2002-09-15 00:02 thomas - - * samples/zipfind.rb: [no log message] - -2002-09-14 22:59 thomas - - * samples/zipfind.rb: Added simple zipfind script - -2002-09-13 23:53 thomas - - * TODO: Added TODO about openmode for zip entries binary/ascii - -2002-09-13 20:54 thomas - - * NEWS: ziptest now runs without errors with ruby-1.7.2-4 (Andy's - latest build) - -2002-09-13 20:51 thomas - - * zip.rb, ziprequiretest.rb, ziptest.rb: ziptest now runs without - errors with ruby-1.7.2-4 (Andy's latest build) - -2002-09-12 00:20 thomas - - * zipfilesystemtest.rb: Improved ZipFsFile.delete/unlink test - -2002-09-12 00:12 thomas - - * test/.cvsignore: [no log message] - -2002-09-12 00:10 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.delete/unlink - -2002-09-11 22:22 thomas - - * alltests.rb: [no log message] - -2002-09-11 22:18 thomas - - * NEWS, zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: Fixed - AbstractInputStream.each_line ignored its aSeparator argument. - Implemented more ZipFsFile methods - -2002-09-11 21:28 thomas - - * zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: ZipFileSystem is - now a module instead of a class, and is mixed into ZipFile, - instead of being made available as a property fileSystem - -2002-09-10 23:45 thomas - - * NEWS: Updated NEWS file - -2002-09-10 23:26 thomas - - * zip.rb: [no log message] - -2002-09-10 22:39 thomas - - * NEWS, zip.rb, ziptest.rb: Fix bug: rewind should reset lineno. - Fix bug: Deflater.read uses separate buffer from produceInput - (feeding gets/readline etc) - -2002-09-09 23:48 thomas - - * .cvsignore: [no log message] - -2002-09-09 22:55 uid26649 - - * zip.rb, ziptest.rb: Implemented ZipInputStream.rewind and - AbstractInputStream.lineno. Tests for both - -2002-09-09 20:31 thomas - - * zip.rb, ziptest.rb: ZipInputStream and ZipOutstream (thru their - AbstractInputStream and AbstractOutputStream now lie about being - kind_of?(IO) - -2002-09-08 16:38 thomas - - * zipfilesystemtest.rb: [no log message] - -2002-09-08 16:07 thomas - - * filearchive.rb, filearchivetest.rb, zip.rb, ziptest.rb: Moved - String additions from filearchive.rb to zip.rb (and moved tests - along too to ziptest.rb). Added ZipEntry.parentAsString and - ZipEntrySet.parent - -2002-09-08 15:28 thomas - - * ziptest.rb: Implemented ZipEntrySetTest.testDup and testCompound - -2002-09-08 15:17 thomas - - * TODO, zip.rb, ziptest.rb: Replaced Array with EntrySet for - keeping entries in a zip file. Tagged repository before this - commit, so this change can be rolled back, if it stinks - -2002-09-07 20:21 thomas - - * zip.rb, ziptest.rb: Implemented ZipEntry.<=> - -2002-09-07 14:48 thomas - - * ziptest.rb: Removed unused code - -2002-08-11 15:14 thomas - - * zip.rb, ziptest.rb: Made some changes to accomodate ruby 1.7.2 - -2002-07-27 15:25 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented ZipFsFile.new - -2002-07-27 00:30 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.pipe - -2002-07-27 00:25 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.link - -2002-07-27 00:23 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.symlink - -2002-07-27 00:20 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.readlink, wrapped ZipFileSystem class in Zip module - -2002-07-27 00:14 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.zero? - -2002-07-27 00:01 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented test for - ZipFsFile.directory? - -2002-07-26 23:56 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.socket? - -2002-07-26 23:50 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.join - -2002-07-26 23:32 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.ftype - -2002-07-26 23:19 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.blockdev? - -2002-07-26 23:12 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.size? (slightly different from size) - -2002-07-26 23:03 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.split - -2002-07-26 23:00 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.symlink? - -2002-07-26 22:58 thomas - - * alltests.rb, zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: - Implemented ZipFsFile.mtime - -2002-07-26 17:08 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implement ZipFsFile.file? - -2002-07-26 17:06 thomas - - * zip.rb, ziptest.rb: Implemented ZipEntry.file? - -2002-07-26 16:57 thomas - - * alltests.rb, filearchive.rb, filearchivetest.rb, zip.rb, - zipfilesystem.rb, zipfilesystemtest.rb, ziprequire.rb, - ziptest.rb: Implemented ZipFileSystem::ZipFsFile.size - -2002-07-26 16:41 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: [no log message] - -2002-07-26 16:40 thomas - - * test/zipWithDirs.zip: Changed zipWithDirs.zip so all the entries - in it have unix file endings - -2002-07-26 16:12 thomas - - * alltests.rb, zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: - Started implementing ZipFileSystem - -2002-07-26 15:56 thomas - - * test/zipWithDirs.zip: Added a zip file for testing with a - directory structure - -2002-07-22 21:40 thomas - - * TODO: [no log message] - -2002-07-22 17:49 thomas - - * TODO: [no log message] - -2002-07-21 18:20 thomas - - * NEWS: [no log message] - -2002-07-21 18:12 thomas - - * TODO: Updated TODO with a refactoring idea for FileArchive - -2002-07-21 17:59 thomas - - * filearchive.rb, filearchivetest.rb: Added some FileArchiveAdd - tests and cleaned up some of the FileArchive tests. extract and - add now have individual test fixtures. - -2002-07-21 16:02 thomas - - * filearchive.rb, filearchivetest.rb: Added tests for extract - called with regex src arg and Enumerable src arg - -2002-07-21 15:37 thomas - - * filearchivetest.rb: Added test for continueOnExistsProc when - extracting from a file archive - -2002-07-20 17:13 thomas - - * TODO, filearchivetest.rb, fileutils.rb, ziptest.rb, - test/.cvsignore: Added (failing) tests for FileArchive.add, added - code for creating test files for FileArchive.add tests. Added - fileutils.rb, which is borrowed from ruby 1.7.2 - -2002-07-20 16:07 thomas - - * filearchive.rb, filearchivetest.rb: [no log message] - -2002-07-20 16:05 thomas - - * filearchivetest.rb: Added tests for String extensions - -2002-07-20 02:20 thomas - - * alltests.rb, ziprequiretest.rb, ziptest.rb: [no log message] - -2002-07-20 00:42 thomas - - * install.rb: [no log message] - -2002-07-20 00:42 thomas - - * TODO: Updated TODO - -2002-07-20 00:35 thomas - - * filearchive.rb, filearchivetest.rb: All FileArchive.extract tests - run - -2002-07-19 23:11 thomas - - * filearchive.rb, filearchivetest.rb: [no log message] - -2002-07-19 19:41 thomas - - * filearchivetest.rb: [no log message] - -2002-07-19 19:06 thomas - - * filearchive.rb, filearchivetest.rb: [no log message] - -2002-07-19 18:48 thomas - - * filearchive.rb, filearchivetest.rb, zip.rb: [no log message] - -2002-07-08 13:41 thomas - - * TODO: [no log message] - -2002-06-11 19:47 thomas - - * filearchive.rb, filearchivetest.rb, zip.rb, ziptest.rb: [no log - message] - -2002-05-25 00:41 thomas - - * simpledist.rb: Added hackish script for creating dist files - -2002-04-30 21:22 thomas - - * TODO: [no log message] - -2002-04-30 21:16 thomas - - * filearchive.rb, filearchivetest.rb: [no log message] - -2002-04-30 20:40 thomas - - * filearchive.rb, filearchivetest.rb: Improved testing and wrote - some of the skeleton of extract. Still to do: Fix glob, so it - returns a hashmap instead of a list. The map will need to map the - full entry name to the last part of the name (which is only - really interesting for recursively extracted entries, otherwise - it is just the name). Glob.expandPathList should also output - directories with a trailing slash, which is doesn't right now. - -2002-04-30 19:52 thomas - - * filearchive.rb, filearchivetest.rb: Implemented the first few - tests for FileArchive - -2002-04-24 22:06 thomas - - * ziprequire.rb, ziprequiretest.rb: Appended copyright message to - ziprequire.rb and ziprequiretest.rb - -2002-04-24 20:59 thomas - - * zip.rb: Made ZipEntry tolerate invalid dates - -2002-04-21 00:57 thomas - - * NEWS, TODO, zip.rb, ziptest.rb: Read and write entry modification - date/time correctly - -2002-04-20 02:44 thomas - - * ziprequiretest.rb, test/rubycode2.zip: improved ZipRequireTest - -2002-04-20 02:39 thomas - - * ziprequire.rb: Made a warning go away - -2002-04-20 02:38 thomas - - * ziprequire.rb, ziprequiretest.rb, test/notzippedruby.rb, - test/rubycode.zip: Fixed a bug in ziprequire. Added - ziprequiretest.rb and test data files - -2002-04-19 22:43 thomas - - * zip.rb, ziptest.rb: Added recursion support to Glob module - -2002-04-18 21:37 thomas - - * NEWS, TODO, zip.rb, ziptest.rb: Added Glob module and GlobTest - unit test suite. This module provides the functionality to expand - a 'glob pattern' given a list of files - Next step is to use this - module in ZipFile - -2002-04-01 22:55 thomas - - * NEWS: [no log message] - -2002-04-01 21:16 thomas - - * TODO, zip.rb, ziprequire.rb: Added ziprequire.rb which contains a - proof-of-concept implementation of a require implementation that - can load ruby modules from a zip file. Needs unit tests and - polish. - -2002-03-31 01:13 thomas - - * README: [no log message] - -2002-03-30 16:14 thomas - - * TODO: [no log message] - -2002-03-30 01:52 thomas - - * .cvsignore, README, zip.rb: Added rdoc markup (only #:nodoc:all - modifiers) to zip.rb. Made README 'RDoc compliant' - -2002-03-29 23:29 thomas - - * TODO: [no log message] - -2002-03-29 23:26 thomas - - * example.rb, samples/.cvsignore, samples/example.rb, - samples/gtkRubyzip.rb: Moved example.rb to samples/. Added - another sample gtkRubyzip.rb - -2002-03-29 20:12 thomas - - * NEWS, TODO: [no log message] - -2002-03-29 20:06 thomas - - * .cvsignore, file1.txt, file1.txt.deflatedData, testDirectory.bin, - ziptest.rb, test/.cvsignore, test/file1.txt, - test/file1.txt.deflatedData, test/file2.txt, - test/testDirectory.bin: Added test/ directory and moved the - manually created test data files into it. Changed ziptest.rb so - it runs in test/ directory - -2002-03-29 19:43 thomas - - * TODO: [no log message] - -2002-03-29 18:15 thomas - - * NEWS, zip.rb, ziptest.rb: Don't decompress and recompress zip - entries when changing zip file - -2002-03-29 17:50 thomas - - * zip.rb: Performance optimization: Only write new ZipFile, if it - has been changed. The test suite runs in half the time now. - -2002-03-28 22:12 thomas - - * TODO: [no log message] - -2002-03-23 17:31 thomas - - * TODO: [no log message] - -2002-03-22 22:47 thomas - - * NEWS: [no log message] - -2002-03-22 22:25 thomas - - * NEWS, TODO: [no log message] - -2002-03-22 22:18 thomas - - * ziptest.rb: Found the tests that didn't use blocks to make sure - input streams are closed as soon as they arent used anymore and - got rid of the GC.start - -2002-03-22 22:12 thomas - - * ziptest.rb: All tests run on windows ruby 1.6.6 - -2002-03-22 10:38 thomas - - * zip.rb, ziptest.rb: Windows fixes: Fixed ZipFile.initialize which - needed to open zipfile file in binary mode. Added another - workaround for the return value from File.open(name) where name - is the name of a directory - ruby returns different exceptions in - linux, win/cygwin and windows. A number of tests failed because - in windows you cant delete a file that is open. Fixed by changing - ziptest.rb to use ZipInputStream.getInputStream with blocks a few - places. There is a hack in CommanZipFileFixture.setup where the - GC is explicitly invoked. Should be fixed with blocks instead. - The only currently failing test fails because the test data - creation fails to add a comment to 4entry.zip, because echo eats - the remainder of the line including the pipe character and the - following zip -z 4 entry.zip command - -2002-03-21 22:18 thomas - - * NEWS: [no log message] - -2002-03-21 22:12 thomas - - * NEWS, README, TODO, install.rb: Added install.rb - -2002-03-21 21:45 thomas - - * ziptest.rb: [no log message] - -2002-03-21 20:54 thomas - - * NEWS, TODO: [no log message] - -2002-03-21 20:34 thomas - - * .cvsignore, TODO, zip.rb, ziptest.rb: Added - test_extractDirectoryExistsAsFileOverwrite and fixed to pass - -2002-03-21 20:22 thomas - - * zip.rb, ziptest.rb: Extraction of directory entries is now - supported - -2002-03-20 21:59 thomas - - * NEWS: [no log message] - -2002-03-20 21:24 thomas - - * COPYING, README, README.txt: Removed COPYING, renamed README.txt - to README. Updated README - -2002-03-20 21:18 thomas - - * example.rb: Fixed example.rb added example that shows zip file - manipulation with Zip::ZipFile - -2002-03-20 21:00 thomas - - * .cvsignore: [no log message] - -2002-03-20 20:56 thomas - - * TODO, zip.rb, ziptest.rb: Directories can now be added (not - recursively, the directory entry itself. Directories are - recognized by a empty entries with a trailing /. The purpose of - storing them explicitly in the zip file is to be able to store - permission and ownership information - -2002-03-20 20:08 thomas - - * TODO, zip.rb, ziptest.rb: zip.rb depended on ftools but it was - only included in ziptest.rb - -2002-03-20 19:07 thomas - - * zip.rb, ziptest.rb: ZipError is now a subclass of StandardError - instead of RuntimeError. ZipError now has several subclasses. - -2002-03-19 22:26 thomas - - * TODO: [no log message] - -2002-03-19 22:19 thomas - - * TODO, ziptest.rb: Unit test ZipFile.getInputStream with block - -2002-03-19 22:11 thomas - - * TODO, zip.rb, ziptest.rb: Unit test for adding new entry with - name that already exists in archive, and fixed to pass test - -2002-03-19 21:40 thomas - - * TODO, zip.rb, ziptest.rb: Added unit tests for rename to existing - entry - -2002-03-19 20:42 thomas - - * TODO: [no log message] - -2002-03-19 20:40 thomas - - * TODO, zip.rb, ziptest.rb: Unit test calling ZipFile.extract with - block - -2002-03-18 21:06 thomas - - * TODO: [no log message] - -2002-03-18 21:05 thomas - - * zip.rb, ziptest.rb: ZipFile#commit now reinitializes ZipFile. - -2002-03-18 20:42 thomas - - * TODO, zip.rb, ziptest.rb: Refactoring: - - Collapsed ZipEntry and ZipStreamableZipEntry into ZipEntry. - - Collapsed BasicZipFile and ZipFile into ZipFile. - -2002-03-18 18:05 thomas - - * zip.rb: Removed method that was never called - -2002-03-17 22:33 thomas - - * TODO: [no log message] - -2002-03-17 22:25 thomas - - * ziptest.rb: Run tests with =true as default - -2002-03-17 22:22 thomas - - * NEWS, TODO, zip.rb, ziptest.rb: Now runs with -w switch without - warnings - -2002-03-17 21:10 thomas - - * .cvsignore: [no log message] - -2002-03-17 21:04 thomas - - * zip.rb, ziptest.rb: Down to one failing test - -2002-03-17 20:36 thomas - - * zip.rb, ziptest.rb: [no log message] - -2002-03-17 17:22 thomas - - * TODO, zip.rb, ziptest.rb: [no log message] - -2002-02-25 19:42 thomas - - * TODO: Added more todos - -2002-02-23 15:51 thomas - - * zip.rb: [no log message] - -2002-02-23 15:30 thomas - - * zip.rb, ziptest.rb: [no log message] - -2002-02-23 14:16 thomas - - * zip.rb, ziptest.rb: [no log message] - -2002-02-03 18:47 thomas - - * ziptest.rb: [no log message] - -2002-02-02 15:58 thomas - - * example.rb, zip.rb, ziptest.rb: [no log message] - -2002-02-02 00:16 thomas - - * .cvsignore: [no log message] - -2002-02-02 00:14 thomas - - * example.rb, zip.rb, ziptest.rb: Renamed SimpleZipFile to - BasicZipFile - -2002-02-02 00:09 thomas - - * TODO: [no log message] - -2002-02-02 00:01 thomas - - * ziptest.rb: More test cases - all of them failing, so now there - are 18 failing test cases. Three more test cases to implement, - then it is time for the production code - -2002-02-01 21:49 thomas - - * ziptest.rb: [no log message] - -2002-02-01 21:34 thomas - - * ziptest.rb: Also run SimpleZipFile tests for ZipFile. - -2002-02-01 20:11 thomas - - * example.rb, zip.rb, ziptest.rb: ZipFile renamed to SimpleZipFile. - The new ZipFile will have many more methods that are useful for - managing archives. - -2002-01-29 20:30 thomas - - * TODO: [no log message] - -2002-01-26 00:18 thomas - - * NEWS: [no log message] - -2002-01-26 00:14 thomas - - * ziptest.rb: In unit test: work around ruby/cygwin weirdness. You - get an Errno::EEXISTS instead of an Errno::EISDIR if you try to - open a file for writing that is a directory. - -2002-01-26 00:02 thomas - - * ziptest.rb: Fixed test that failed on windows because of CRLF - line ending - -2002-01-25 23:58 thomas - - * ziptest.rb: [no log message] - -2002-01-25 23:29 thomas - - * .cvsignore, example.rb, zip.rb: Fixed bug reading from empty - deflated entry in zip file - -2002-01-25 23:01 thomas - - * .cvsignore: [no log message] - -2002-01-25 22:56 thomas - - * ziptest.rb: [no log message] - -2002-01-25 22:51 thomas - - * NEWS, README.txt, zip.rb, ziptest.rb: Zip write support is now - fully functional in the form of ZipOutputStream. - -2002-01-25 21:12 thomas - - * zip.rb, ziptest.rb: [no log message] - -2002-01-25 20:37 thomas - - * zip.rb, ziptest.rb: [no log message] - -2002-01-20 16:00 thomas - - * zip.rb, ziptest.rb: Added Deflater and DeflaterTest. - -2002-01-20 00:39 thomas - - * .cvsignore: [no log message] - -2002-01-20 00:23 thomas - - * .cvsignore: Added .cvsignore file - -2002-01-20 00:09 thomas - - * zip.rb, ziptest.rb: Added ZipEntry.writeCDirEntry and misc minor - fixes - -2002-01-19 23:28 thomas - - * example.rb, zip.rb, ziptest.rb: NOTICE: Not all tests run!! - - ZipOutputStream in progress - - Wrapped rubyzip in namespace module Zip. - -2002-01-17 18:52 thomas - - * ziptest.rb: Fail nicely if the user doesn't have info-zip - compatible zip in the path - -2002-01-10 18:02 thomas - - * zip.rb: Adjusted chunk size to 32k after a few perf measurements - -2002-01-09 22:10 thomas - - * README.txt: License now same as rubys, not just GPL - -2002-01-06 00:19 thomas - - * README.txt: [no log message] - -2002-01-05 23:09 thomas - - * NEWS, README.txt: Updated NEWS file - -2002-01-05 23:05 thomas - - * README.txt, zip.rb, ziptest.rb, zlib.c.diff: Added tests for - decompressors and a tests for ZipLocalEntry, - ZipCentralDirectoryEntry and ZipCentralDirectory for handling of - corrupt data - -2002-01-05 22:21 thomas - - * file1.txt.deflatedData: deflated data extracted from a zip file. - contains file1.txt - -2002-01-05 20:05 thomas - - * zip.rb: Changed references to Inflate to Zlib::inflate for - compatibility with ruby-zlib-0.5 - -2002-01-05 19:28 thomas - - * README.txt, zip.rb, ziptest.rb: [no log message] - -2002-01-05 01:52 thomas - - * example.rb, NEWS: [no log message] - -2002-01-05 01:37 thomas - - * COPYING, README.txt: [no log message] - -2002-01-05 01:31 thomas - - * ziptest.rb: Fixed problem with test file creation - -2002-01-05 01:15 thomas - - * README.txt: Updated README.txt - -2002-01-05 01:13 thomas - - * zip.rb, ziptest.rb: ZipFile now works - -2002-01-04 21:51 thomas - - * testDirectory.bin, zip.rb, ziptest.rb: - ZipCentralDirectoryEntryTest now runs - -2002-01-04 18:40 thomas - - * ziptest.rb: Changed - ZIpLocalNEtryTest::test_ReadLocalEntryHeaderOfFirstTestZipEntry - so it works on both unix too. It only worked on windows because - the test made assumptions about the compressed size and crc of an - entry, but that differs depending on the OS because of the CRLF - thing. - -2002-01-04 18:37 thomas - - * README.txt: Added note about zlib.c patch - -2002-01-02 18:48 thomas - - * README.txt, example.rb, file1.txt, zip.rb, ziptest.rb, - zlib.c.diff: Initial revision - -2002-01-02 18:48 thomas - - * README.txt, example.rb, file1.txt, zip.rb, ziptest.rb, - zlib.c.diff: initial - diff --git a/vendor/rubyzip-0.5.8/NEWS b/vendor/rubyzip-0.5.8/NEWS deleted file mode 100644 index 37911fdb..00000000 --- a/vendor/rubyzip-0.5.8/NEWS +++ /dev/null @@ -1,110 +0,0 @@ -= Version 0.5.8 - -Fixed install script. - -= Version 0.5.7 - -install.rb no longer assumes it is being run from the toplevel source -dir. Directory structure changed to reflect common ruby library -project structure. Migrated from RubyUnit to Test::Unit format. Now -uses Rake to build source packages and gems and run unit tests. - -= Version 0.5.6 - -Fix for FreeBSD 4.9 which returns Errno::EFBIG instead of -Errno::EINVAL for some invalid seeks. Fixed 'version needed to -extract'-field incorrect in local headers. - -= Version 0.5.5 - -Fix for a problem with writing zip files that concerns only ruby 1.8.1. - -= Version 0.5.4 - -Significantly reduced memory footprint when modifying zip files. - -= Version 0.5.3 - -Added optimization to avoid decompressing and recompressing individual -entries when modifying a zip archive. - -= Version 0.5.2 - -Fixed ZipFile corruption bug in ZipFile class. Added basic unix -extra-field support. - -= Version 0.5.1 - -Fixed ZipFile.get_output_stream bug. - -= Version 0.5.0 - -List of changes: -* Ruby 1.8.0 and ruby-zlib 0.6.0 compatibility -* Changed method names from camelCase to rubys underscore style. -* Installs to zip/ subdir instead of directly to site_ruby -* Added ZipFile.directory and ZipFile.file - each method return an -object that can be used like Dir and File only for the contents of the -zip file. -* Added sample application zipfind which works like Find.find, only -Zip::ZipFind.find traverses into zip archives too. - -Bug fixes: -* AbstractInputStream.each_line with non-default separator - - -= Version 0.5.0a - -Source reorganized. Added ziprequire, which can be used to load ruby -modules from a zip file, in a fashion similar to jar files in -Java. Added gtkRubyzip, another sample application. Implemented -ZipInputStream.lineno and ZipInputStream.rewind - -Bug fixes: - -* Read and write date and time information correctly for zip entries. -* Fixed read() using separate buffer, causing mix of gets/readline/read to -cause problems. - -= Version 0.4.2 - -Performance optimizations. Test suite runs in half the time. - -= Version 0.4.1 - -Windows compatibility fixes. - -= Version 0.4.0 - -Zip::ZipFile is now mutable and provides a more convenient way of -modifying zip archives than Zip::ZipOutputStream. Operations for -adding, extracting, renaming, replacing and removing entries to zip -archives are now available. - -Runs without warnings with -w switch. - -Install script install.rb added. - - -= Version 0.3.1 - -Rudimentary support for writing zip archives. - - -= Version 0.2.2 - -Fixed and extended unit test suite. Updated to work with ruby/zlib -0.5. It doesn't work with earlier versions of ruby/zlib. - - -= Version 0.2.0 - -Class ZipFile added. Where ZipInputStream is used to read the -individual entries in a zip file, ZipFile reads the central directory -in the zip archive, so you can get to any entry in the zip archive -without having to skipping through all the preceeding entries. - - -= Version 0.1.0 - -First working version of ZipInputStream. diff --git a/vendor/rubyzip-0.5.8/README b/vendor/rubyzip-0.5.8/README deleted file mode 100644 index 6ed15a92..00000000 --- a/vendor/rubyzip-0.5.8/README +++ /dev/null @@ -1,70 +0,0 @@ -= rubyzip - -rubyzip is a ruby library for reading and writing zip files. - -= Install - -If you have rubygems you can install rubyzip directly from the gem -repository - - gem install rubyzip - -Otherwise obtain the source (see below) and run - - ruby install.rb - -To run the unit tests you need to have test::unit installed - - rake test - - -= Documentation - -There is more than one way to access or create a zip archive with -rubyzip. The basic API is modeled after the classes in -java.util.zip from the Java SDK. This means there are classes such -as Zip::ZipInputStream, Zip::ZipOutputStream and -Zip::ZipFile. Zip::ZipInputStream provides a basic interface for -iterating through the entries in a zip archive and reading from the -entries in the same way as from a regular File or IO -object. ZipOutputStream is the corresponding basic output -facility. Zip::ZipFile provides a mean for accessing the archives -central directory and provides means for accessing any entry without -having to iterate through the archive. Unlike Java's -java.util.zip.ZipFile rubyzip's Zip::ZipFile is mutable, which means -it can be used to change zip files as well. - -Another way to access a zip archive with rubyzip is to use rubyzip's -Zip::ZipFileSystem API. Using this API files can be read from and -written to the archive in much the same manner as ruby's builtin -classes allows files to be read from and written to the file system. - -rubyzip also features the -zip/ziprequire.rb[link:files/lib/zip/ziprequire_rb.html] module which -allows ruby to load ruby modules from zip archives. - -For details about the specific behaviour of classes and methods refer -to the test suite. Finally you can generate the rdoc documentation or -visit http://rubyzip.sourceforge.net. - -= License - -rubyzip is distributed under the same license as ruby. See -http://www.ruby-lang.org/en/LICENSE.txt - - -= Website and Project Home - -http://rubyzip.sourceforge.net - -http://sourceforge.net/projects/rubyzip - -== Download (tarballs and gems) - -http://sourceforge.net/project/showfiles.php?group_id=43107&package_id=35377 - -= Authors - -Thomas Sondergaard (thomas at sondergaard.cc) - -extra-field support contributed by Tatsuki Sugiura (sugi at nemui.org) diff --git a/vendor/rubyzip-0.5.8/Rakefile b/vendor/rubyzip-0.5.8/Rakefile deleted file mode 100644 index 03b65c2d..00000000 --- a/vendor/rubyzip-0.5.8/Rakefile +++ /dev/null @@ -1,110 +0,0 @@ -# Rakefile for RubyGems -*- ruby -*- - -require 'rubygems' -require 'rake/clean' -require 'rake/testtask' -require 'rake/packagetask' -require 'rake/gempackagetask' -require 'rake/rdoctask' -require 'rake/contrib/sshpublisher' -require 'net/ftp' - -PKG_NAME = 'rubyzip' -PKG_VERSION = File.read('lib/zip/zip.rb').match(/\s+VERSION\s*=\s*'(.*)'/)[1] - -PKG_FILES = FileList.new - -PKG_FILES.add %w{ README NEWS TODO ChangeLog install.rb Rakefile } -PKG_FILES.add %w{ samples/*.rb } -PKG_FILES.add %w{ test/*.rb } -PKG_FILES.add %w{ test/data/* } -PKG_FILES.exclude "test/data/generated" -PKG_FILES.add %w{ lib/**/*.rb } - -def clobberFromCvsIgnore(path) - CLOBBER.add File.readlines(path+'/.cvsignore').map { - |f| File.join(path, f.chomp) - } -end - -clobberFromCvsIgnore '.' -clobberFromCvsIgnore 'samples' -clobberFromCvsIgnore 'test' -clobberFromCvsIgnore 'test/data' - -task :default => [:test] - -desc "Run unit tests" -task :test do - ruby %{-C test alltests.rb} -end - -# Shortcuts for test targets -task :ut => [:test] - -spec = Gem::Specification.new do |s| - s.name = PKG_NAME - s.version = PKG_VERSION - s.author = "Thomas Sondergaard" - s.email = "thomas(at)sondergaard.cc" - s.homepage = "http://rubyzip.sourceforge.net/" - s.platform = Gem::Platform::RUBY - s.summary = "rubyzip is a ruby module for reading and writing zip files" - s.files = PKG_FILES.to_a #Dir.glob("{samples,lib,test,docs}/**/*").delete_if {|item| item.include?("CVS") || item.include?("rdoc") || item =~ /~$/ } - s.require_path = 'lib' - s.autorequire = 'zip/zip' -end - -Rake::GemPackageTask.new(spec) do |pkg| - pkg.need_zip = true - pkg.need_tar = true -end - -Rake::RDocTask.new do |rd| - rd.main = "README" - rd.rdoc_files.add %W{ lib/zip/*.rb README NEWS TODO ChangeLog } - rd.options << "--title 'rubyzip documentation' --webcvs http://cvs.sourceforge.net/viewcvs.py/rubyzip/rubyzip/" -# rd.options << "--all" -end - -desc "Publish documentation" -task :pdoc => [:rdoc] do - Rake::SshFreshDirPublisher. - new("thomas@rubyzip.sourceforge.net", "rubyzip/htdocs", "html").upload -end - -desc "Publish package" -task :ppackage => [:package] do - Net::FTP.open("upload.sourceforge.net", - "ftp", - ENV['USER']+"@"+ENV['HOSTNAME']) { - |ftpclient| - ftpclient.chdir "incoming" - Dir['pkg/*.{tgz,zip,gem}'].each { - |e| - ftpclient.putbinaryfile(e, File.basename(e)) - } - } -end - -desc "Generate the ChangeLog file" -task :ChangeLog do - puts "Updating ChangeLog" - system %{cvs2cl} -end - -desc "Make a release" -task :release => [:tag_release, :pdoc, :ppackage] do -end - -desc "Make a release tag" -task :tag_release do - tag = "release-#{PKG_VERSION.gsub('.','-')}" - - puts "Checking for tag '#{tag}'" - if (Regexp.new("^\\s+#{tag}") =~ `cvs log README`) - abort "Tag '#{tag}' already exists" - end - puts "Tagging module with '#{tag}'" - system("cvs tag #{tag}") -end diff --git a/vendor/rubyzip-0.5.8/TODO b/vendor/rubyzip-0.5.8/TODO deleted file mode 100644 index 457298c6..00000000 --- a/vendor/rubyzip-0.5.8/TODO +++ /dev/null @@ -1,9 +0,0 @@ - -* Fix problem with mixing AbstractInputStream::gets and AbstractInputStream::read -* Implement ZipFsDir.glob -* ZipFile.checkIntegrity method -* non-MSDOS permission attributes -** See mail from Ned Konz to ruby-talk subj. "Re: SV: [ANN] Archive 0.2" -* Packager version, required unpacker version in zip headers -** See mail from Ned Konz to ruby-talk subj. "Re: SV: [ANN] Archive 0.2" -* implement storing attributes and ownership information diff --git a/vendor/rubyzip-0.5.8/install.rb b/vendor/rubyzip-0.5.8/install.rb deleted file mode 100644 index 405e2b0b..00000000 --- a/vendor/rubyzip-0.5.8/install.rb +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -require 'rbconfig' -require 'find' -require 'ftools' - -include Config - -files = %w{ stdrubyext.rb ioextras.rb zip.rb zipfilesystem.rb ziprequire.rb tempfile_bugfixed.rb } - -INSTALL_DIR = File.join(CONFIG["sitelibdir"], "zip") -File.makedirs(INSTALL_DIR) - -SOURCE_DIR = File.join(File.dirname($0), "lib/zip") - -files.each { - |filename| - installPath = File.join(INSTALL_DIR, filename) - File::install(File.join(SOURCE_DIR, filename), installPath, 0644, true) -} diff --git a/vendor/rubyzip-0.5.8/samples/example.rb b/vendor/rubyzip-0.5.8/samples/example.rb deleted file mode 100644 index 741afa76..00000000 --- a/vendor/rubyzip-0.5.8/samples/example.rb +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env ruby - -$: << "../lib" -system("zip example.zip example.rb gtkRubyzip.rb") - -require 'zip/zip' - -####### Using ZipInputStream alone: ####### - -Zip::ZipInputStream.open("example.zip") { - |zis| - entry = zis.get_next_entry - print "First line of '#{entry.name} (#{entry.size} bytes): " - puts "'#{zis.gets.chomp}'" - entry = zis.get_next_entry - print "First line of '#{entry.name} (#{entry.size} bytes): " - puts "'#{zis.gets.chomp}'" -} - - -####### Using ZipFile to read the directory of a zip file: ####### - -zf = Zip::ZipFile.new("example.zip") -zf.each_with_index { - |entry, index| - - puts "entry #{index} is #{entry.name}, size = #{entry.size}, compressed size = #{entry.compressed_size}" - # use zf.get_input_stream(entry) to get a ZipInputStream for the entry - # entry can be the ZipEntry object or any object which has a to_s method that - # returns the name of the entry. -} - - -####### Using ZipOutputStream to write a zip file: ####### - -Zip::ZipOutputStream.open("exampleout.zip") { - |zos| - zos.put_next_entry("the first little entry") - zos.puts "Hello hello hello hello hello hello hello hello hello" - - zos.put_next_entry("the second little entry") - zos.puts "Hello again" - - # Use rubyzip or your zip client of choice to verify - # the contents of exampleout.zip -} - -####### Using ZipFile to change a zip file: ####### - -Zip::ZipFile.open("exampleout.zip") { - |zf| - zf.add("thisFile.rb", "example.rb") - zf.rename("thisFile.rb", "ILikeThisName.rb") - zf.add("Again", "example.rb") -} - -# Lets check -Zip::ZipFile.open("exampleout.zip") { - |zf| - puts "Changed zip file contains: #{zf.entries.join(', ')}" - zf.remove("Again") - puts "Without 'Again': #{zf.entries.join(', ')}" -} - -# For other examples, look at zip.rb and ziptest.rb - -# Copyright (C) 2002 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/vendor/rubyzip-0.5.8/samples/example_filesystem.rb b/vendor/rubyzip-0.5.8/samples/example_filesystem.rb deleted file mode 100644 index 867e8d4f..00000000 --- a/vendor/rubyzip-0.5.8/samples/example_filesystem.rb +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env ruby - -$: << "../lib" - -require 'zip/zipfilesystem' -require 'ftools' - -EXAMPLE_ZIP = "filesystem.zip" - -File.delete(EXAMPLE_ZIP) if File.exists?(EXAMPLE_ZIP) - -Zip::ZipFile.open(EXAMPLE_ZIP, Zip::ZipFile::CREATE) { - |zf| - zf.file.open("file1.txt", "w") { |os| os.write "first file1.txt" } - zf.dir.mkdir("dir1") - zf.dir.chdir("dir1") - zf.file.open("file1.txt", "w") { |os| os.write "second file1.txt" } - puts zf.file.read("file1.txt") - puts zf.file.read("../file1.txt") - zf.dir.chdir("..") - zf.file.open("file2.txt", "w") { |os| os.write "first file2.txt" } - puts "Entries: #{zf.entries.join(', ')}" -} - -Zip::ZipFile.open(EXAMPLE_ZIP) { - |zf| - puts "Entries from reloaded zip: #{zf.entries.join(', ')}" -} - -# For other examples, look at zip.rb and ziptest.rb - -# Copyright (C) 2003 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/vendor/rubyzip-0.5.8/samples/gtkRubyzip.rb b/vendor/rubyzip-0.5.8/samples/gtkRubyzip.rb deleted file mode 100644 index 5d91829d..00000000 --- a/vendor/rubyzip-0.5.8/samples/gtkRubyzip.rb +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env ruby - -$: << "../lib" - -$VERBOSE = true - -require 'gtk' -require 'zip/zip' - -class MainApp < Gtk::Window - def initialize - super() - set_usize(400, 256) - set_title("rubyzip") - signal_connect(Gtk::Window::SIGNAL_DESTROY) { Gtk.main_quit } - - box = Gtk::VBox.new(false, 0) - add(box) - - @zipfile = nil - @buttonPanel = ButtonPanel.new - @buttonPanel.openButton.signal_connect(Gtk::Button::SIGNAL_CLICKED) { - show_file_selector - } - @buttonPanel.extractButton.signal_connect(Gtk::Button::SIGNAL_CLICKED) { - puts "Not implemented!" - } - box.pack_start(@buttonPanel, false, false, 0) - - sw = Gtk::ScrolledWindow.new - sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC) - box.pack_start(sw, true, true, 0) - - @clist = Gtk::CList.new(["Name", "Size", "Compression"]) - @clist.set_selection_mode(Gtk::SELECTION_BROWSE) - @clist.set_column_width(0, 120) - @clist.set_column_width(1, 120) - @clist.signal_connect(Gtk::CList::SIGNAL_SELECT_ROW) { - |w, row, column, event| - @selected_row = row - } - sw.add(@clist) - end - - class ButtonPanel < Gtk::HButtonBox - attr_reader :openButton, :extractButton - def initialize - super - set_layout(Gtk::BUTTONBOX_START) - set_spacing(0) - @openButton = Gtk::Button.new("Open archive") - @extractButton = Gtk::Button.new("Extract entry") - pack_start(@openButton) - pack_start(@extractButton) - end - end - - def show_file_selector - @fileSelector = Gtk::FileSelection.new("Open zip file") - @fileSelector.show - @fileSelector.ok_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) { - open_zip(@fileSelector.filename) - @fileSelector.destroy - } - @fileSelector.cancel_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) { - @fileSelector.destroy - } - end - - def open_zip(filename) - @zipfile = Zip::ZipFile.open(filename) - @clist.clear - @zipfile.each { - |entry| - @clist.append([ entry.name, - entry.size.to_s, - (100.0*entry.compressedSize/entry.size).to_s+"%" ]) - } - end -end - -mainApp = MainApp.new() - -mainApp.show_all - -Gtk.main diff --git a/vendor/rubyzip-0.5.8/samples/write_simple.rb b/vendor/rubyzip-0.5.8/samples/write_simple.rb deleted file mode 100644 index 648989a2..00000000 --- a/vendor/rubyzip-0.5.8/samples/write_simple.rb +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env ruby - -$: << "../lib" - -require 'zip/zip' - -include Zip - -ZipOutputStream.open('simple.zip') { - |zos| - ze = zos.put_next_entry 'entry.txt' - zos.puts "Hello world" -} diff --git a/vendor/rubyzip-0.5.8/samples/zipfind.rb b/vendor/rubyzip-0.5.8/samples/zipfind.rb deleted file mode 100644 index 54ad936e..00000000 --- a/vendor/rubyzip-0.5.8/samples/zipfind.rb +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -$: << "../lib" - -require 'zip/zip' -require 'find' - -module Zip - module ZipFind - def self.find(path, zipFilePattern = /\.zip$/i) - Find.find(path) { - |fileName| - yield(fileName) - if zipFilePattern.match(fileName) && File.file?(fileName) - begin - Zip::ZipFile.foreach(fileName) { - |zipEntry| - yield(fileName + File::SEPARATOR + zipEntry.to_s) - } - rescue Errno::EACCES => ex - puts ex - end - end - } - end - - def self.find_file(path, fileNamePattern, zipFilePattern = /\.zip$/i) - self.find(path, zipFilePattern) { - |fileName| - yield(fileName) if fileNamePattern.match(fileName) - } - end - - end -end - -if __FILE__ == $0 - module ZipFindConsoleRunner - - PATH_ARG_INDEX = 0; - FILENAME_PATTERN_ARG_INDEX = 1; - ZIPFILE_PATTERN_ARG_INDEX = 2; - - def self.run(args) - check_args(args) - Zip::ZipFind.find_file(args[PATH_ARG_INDEX], - args[FILENAME_PATTERN_ARG_INDEX], - args[ZIPFILE_PATTERN_ARG_INDEX]) { - |fileName| - report_entry_found fileName - } - end - - def self.check_args(args) - if (args.size != 3) - usage - exit - end - end - - def self.usage - puts "Usage: #{$0} PATH ZIPFILENAME_PATTERN FILNAME_PATTERN" - end - - def self.report_entry_found(fileName) - puts fileName - end - - end - - ZipFindConsoleRunner.run(ARGV) -end diff --git a/vendor/rubyzip-0.5.8/test/alltests.rb b/vendor/rubyzip-0.5.8/test/alltests.rb deleted file mode 100644 index 691349af..00000000 --- a/vendor/rubyzip-0.5.8/test/alltests.rb +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -require 'stdrubyexttest' -require 'ioextrastest' -require 'ziptest' -require 'zipfilesystemtest' -require 'ziprequiretest' diff --git a/vendor/rubyzip-0.5.8/test/data/file1.txt b/vendor/rubyzip-0.5.8/test/data/file1.txt deleted file mode 100644 index 23ea2f73..00000000 --- a/vendor/rubyzip-0.5.8/test/data/file1.txt +++ /dev/null @@ -1,46 +0,0 @@ - -AUTOMAKE_OPTIONS = gnu - -EXTRA_DIST = test.zip - -CXXFLAGS= -g - -noinst_LIBRARIES = libzipios.a - -bin_PROGRAMS = test_zip test_izipfilt test_izipstream -# test_flist - -libzipios_a_SOURCES = backbuffer.h fcol.cpp fcol.h \ - fcol_common.h fcolexceptions.cpp fcolexceptions.h \ - fileentry.cpp fileentry.h flist.cpp \ - flist.h flistentry.cpp flistentry.h \ - flistscanner.h ifiltstreambuf.cpp ifiltstreambuf.h \ - inflatefilt.cpp inflatefilt.h izipfilt.cpp \ - izipfilt.h izipstream.cpp izipstream.h \ - zipfile.cpp zipfile.h ziphead.cpp \ - ziphead.h flistscanner.ll - -# test_flist_SOURCES = test_flist.cpp - -test_izipfilt_SOURCES = test_izipfilt.cpp - -test_izipstream_SOURCES = test_izipstream.cpp - -test_zip_SOURCES = test_zip.cpp - -# Notice that libzipios.a is not specified as -L. -lzipios -# If it was, automake would not include it as a dependency. - -# test_flist_LDADD = libzipios.a - -test_izipfilt_LDADD = libzipios.a -lz - -test_zip_LDADD = libzipios.a -lz - -test_izipstream_LDADD = libzipios.a -lz - - - -flistscanner.cc : flistscanner.ll - $(LEX) -+ -PFListScanner -o$@ $^ - diff --git a/vendor/rubyzip-0.5.8/test/data/file1.txt.deflatedData b/vendor/rubyzip-0.5.8/test/data/file1.txt.deflatedData deleted file mode 100644 index bfbb4f42c009154e9e3304855c31813275b709d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 482 zcmV<80UiE@RMBpmFcf{>{fa9!51TglfJv3cnzTZrO$4cwhiS+$rdV}s6dQHj*U#Vt z3=5f`xX0%l?mad@^t@d^Mn6{hdb5q!PZ{3gi);W^yKNff%Q)Lw#4v5bKfDIG+wJa? z=pnns-~~V`F15*%_4Ca zj^EboH)XZqO6tya0#)-~Treih@%_}yQ1_j5gZaJAdUeEVT>IuDsQSN`rbJ4Y7;mF@ zf!i26zX>!yBbTKhhP8Aj^y*W$=fmwAo%K2sJ)!HNmwM3k8J!jDh3C2&Q7T4?A^j^} z9r3Ik8+;@B3zEjD19@fmrW#RnN-n8r3f5ArlwiSXCJQF% zdpJoZSw_p{^uI6; YT71LBFMz*PQb9>fNlr&oR8>Ys3VC(y$N&HU diff --git a/vendor/rubyzip-0.5.8/test/data/file2.txt b/vendor/rubyzip-0.5.8/test/data/file2.txt deleted file mode 100644 index cc9ef6ad..00000000 --- a/vendor/rubyzip-0.5.8/test/data/file2.txt +++ /dev/null @@ -1,1504 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -require 'rubyunit' -require 'zip' - -include Zip - -Dir.chdir "test" - -class AbstractInputStreamTest < RUNIT::TestCase - # AbstractInputStream subclass that provides a read method - - TEST_LINES = [ "Hello world#{$/}", - "this is the second line#{$/}", - "this is the last line"] - TEST_STRING = TEST_LINES.join - class TestAbstractInputStream - include AbstractInputStream - def initialize(aString) - @contents = aString - @readPointer = 0 - end - - def read(charsToRead) - retVal=@contents[@readPointer, charsToRead] - @readPointer+=charsToRead - return retVal - end - - def produceInput - read(100) - end - - def inputFinished? - @contents[@readPointer] == nil - end - end - - def setup - @io = TestAbstractInputStream.new(TEST_STRING) - end - - def test_gets - assert_equals(TEST_LINES[0], @io.gets) - assert_equals(TEST_LINES[1], @io.gets) - assert_equals(TEST_LINES[2], @io.gets) - assert_equals(nil, @io.gets) - end - - def test_getsMultiCharSeperator - assert_equals("Hell", @io.gets("ll")) - assert_equals("o world#{$/}this is the second l", @io.gets("d l")) - end - - def test_each_line - lineNumber=0 - @io.each_line { - |line| - assert_equals(TEST_LINES[lineNumber], line) - lineNumber+=1 - } - end - - def test_readlines - assert_equals(TEST_LINES, @io.readlines) - end - - def test_readline - test_gets - begin - @io.readline - fail "EOFError expected" - rescue EOFError - end - end -end - -class ZipEntryTest < RUNIT::TestCase - TEST_ZIPFILE = "someZipFile.zip" - TEST_COMMENT = "a comment" - TEST_COMPRESSED_SIZE = 1234 - TEST_CRC = 325324 - TEST_EXTRA = "Some data here" - TEST_COMPRESSIONMETHOD = ZipEntry::DEFLATED - TEST_NAME = "entry name" - TEST_SIZE = 8432 - TEST_ISDIRECTORY = false - - def test_constructorAndGetters - entry = ZipEntry.new(TEST_ZIPFILE, - TEST_NAME, - TEST_COMMENT, - TEST_EXTRA, - TEST_COMPRESSED_SIZE, - TEST_CRC, - TEST_COMPRESSIONMETHOD, - TEST_SIZE) - - assert_equals(TEST_COMMENT, entry.comment) - assert_equals(TEST_COMPRESSED_SIZE, entry.compressedSize) - assert_equals(TEST_CRC, entry.crc) - assert_equals(TEST_EXTRA, entry.extra) - assert_equals(TEST_COMPRESSIONMETHOD, entry.compressionMethod) - assert_equals(TEST_NAME, entry.name) - assert_equals(TEST_SIZE, entry.size) - assert_equals(TEST_ISDIRECTORY, entry.isDirectory) - end - - def test_equality - entry1 = ZipEntry.new("file.zip", "name", "isNotCompared", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry2 = ZipEntry.new("file.zip", "name", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry3 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry4 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry5 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 1234, - ZipEntry::DEFLATED, 10000) - entry6 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::DEFLATED, 10000) - entry7 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 10000) - entry8 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 100000) - - assert_equals(entry1, entry1) - assert_equals(entry1, entry2) - - assert(entry2 != entry3) - assert(entry3 != entry4) - assert(entry4 != entry5) - assert(entry5 != entry6) - assert(entry6 != entry7) - assert(entry7 != entry8) - - assert(entry7 != "hello") - assert(entry7 != 12) - end -end - -module IOizeString - attr_reader :tell - - def read(count = nil) - @tell ||= 0 - count = size unless count - retVal = slice(@tell, count) - @tell += count - return retVal - end - - def seek(index, offset) - @tell ||= 0 - case offset - when IO::SEEK_END - newPos = size + index - when IO::SEEK_SET - newPos = index - when IO::SEEK_CUR - newPos = @tell + index - else - raise "Error in test method IOizeString::seek" - end - if (newPos < 0 || newPos >= size) - raise Errno::EINVAL - else - @tell=newPos - end - end - - def reset - @tell = 0 - end -end - -class ZipLocalEntryTest < RUNIT::TestCase - def test_readLocalEntryHeaderOfFirstTestZipEntry - File.open(TestZipFile::TEST_ZIP3.zipName) { - |file| - entry = ZipEntry.readLocalEntry(file) - - assert_equal("", entry.comment) - # Differs from windows and unix because of CR LF - # assert_equal(480, entry.compressedSize) - # assert_equal(0x2a27930f, entry.crc) - # extra field is 21 bytes long - # probably contains some unix attrutes or something - # disabled: assert_equal(nil, entry.extra) - assert_equal(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equal(TestZipFile::TEST_ZIP3.entryNames[0], entry.name) - assert_equal(File.size(TestZipFile::TEST_ZIP3.entryNames[0]), entry.size) - assert(! entry.isDirectory) - } - end - - def test_readLocalEntryFromNonZipFile - File.open("ziptest.rb") { - |file| - assert_equals(nil, ZipEntry.readLocalEntry(file)) - } - end - - def test_readLocalEntryFromTruncatedZipFile - zipFragment="" - File.open(TestZipFile::TEST_ZIP2.zipName) { |f| zipFragment = f.read(12) } # local header is at least 30 bytes - zipFragment.extend(IOizeString).reset - entry = ZipEntry.new - entry.readLocalEntry(zipFragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeEntry - entry = ZipEntry.new("file.zip", "entryName", "my little comment", - "thisIsSomeExtraInformation", 100, 987654, - ZipEntry::DEFLATED, 400) - writeToFile("localEntryHeader.bin", "centralEntryHeader.bin", entry) - entryReadLocal, entryReadCentral = readFromFile("localEntryHeader.bin", "centralEntryHeader.bin") - compareLocalEntryHeaders(entry, entryReadLocal) - compareCDirEntryHeaders(entry, entryReadCentral) - end - - private - def compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.compressedSize , entry2.compressedSize) - assert_equals(entry1.crc , entry2.crc) - assert_equals(entry1.extra , entry2.extra) - assert_equals(entry1.compressionMethod, entry2.compressionMethod) - assert_equals(entry1.name , entry2.name) - assert_equals(entry1.size , entry2.size) - assert_equals(entry1.localHeaderOffset, entry2.localHeaderOffset) - end - - def compareCDirEntryHeaders(entry1, entry2) - compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.comment, entry2.comment) - end - - def writeToFile(localFileName, centralFileName, entry) - File.open(localFileName, "wb") { |f| entry.writeLocalEntry(f) } - File.open(centralFileName, "wb") { |f| entry.writeCDirEntry(f) } - end - - def readFromFile(localFileName, centralFileName) - localEntry = nil - cdirEntry = nil - File.open(localFileName, "rb") { |f| localEntry = ZipEntry.readLocalEntry(f) } - File.open(centralFileName, "rb") { |f| cdirEntry = ZipEntry.readCDirEntry(f) } - return [localEntry, cdirEntry] - end -end - - -module DecompressorTests - # expects @refText and @decompressor - - def test_readEverything - assert_equals(@refText, @decompressor.read) - end - - def test_readInChunks - chunkSize = 5 - while (decompressedChunk = @decompressor.read(chunkSize)) - assert_equals(@refText.slice!(0, chunkSize), decompressedChunk) - end - assert_equals(0, @refText.size) - end -end - -class InflaterTest < RUNIT::TestCase - include DecompressorTests - - def setup - @file = File.new("file1.txt.deflatedData", "rb") - @refText="" - File.open("file1.txt") { |f| @refText = f.read } - @decompressor = Inflater.new(@file) - end - - def teardown - @file.close - end -end - - -class PassThruDecompressorTest < RUNIT::TestCase - include DecompressorTests - TEST_FILE="file1.txt" - def setup - @file = File.new(TEST_FILE) - @refText="" - File.open(TEST_FILE) { |f| @refText = f.read } - @decompressor = PassThruDecompressor.new(@file, File.size(TEST_FILE)) - end - - def teardown - @file.close - end -end - - -module AssertEntry - def assertNextEntry(filename, zis) - assertEntry(filename, zis, zis.getNextEntry.name) - end - - def assertEntry(filename, zis, entryName) - assert_equals(filename, entryName) - assertEntryContentsForStream(filename, zis, entryName) - end - - def assertEntryContentsForStream(filename, zis, entryName) - File.open(filename, "rb") { - |file| - expected = file.read - actual = zis.read - if (expected != actual) - if (expected.length > 400 || actual.length > 400) - zipEntryFilename=entryName+".zipEntry" - File.open(zipEntryFilename, "wb") { |file| file << actual } - fail("File '#{filename}' is different from '#{zipEntryFilename}'") - else - assert_equals(expected, actual) - end - end - } - end - - def AssertEntry.assertContents(filename, aString) - fileContents = "" - File.open(filename, "rb") { |f| fileContents = f.read } - if (fileContents != aString) - if (expected.length > 400 || actual.length > 400) - stringFile = filename + ".other" - File.open(stringFile, "wb") { |f| f << aString } - fail("File '#{filename}' is different from contents of string stored in '#{stringFile}'") - else - assert_equals(expected, actual) - end - end - end - - def assertStreamContents(zis, testZipFile) - assert(zis != nil) - testZipFile.entryNames.each { - |entryName| - assertNextEntry(entryName, zis) - } - assert_equals(nil, zis.getNextEntry) - end - - def assertTestZipContents(testZipFile) - ZipInputStream.open(testZipFile.zipName) { - |zis| - assertStreamContents(zis, testZipFile) - } - end - - def assertEntryContents(zipFile, entryName, filename = entryName.to_s) - zis = zipFile.getInputStream(entryName) - assertEntryContentsForStream(filename, zis, entryName) - ensure - zis.close if zis - end -end - - - -class ZipInputStreamTest < RUNIT::TestCase - include AssertEntry - - def test_new - zis = ZipInputStream.new(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - zis.close - end - - def test_openWithBlock - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - } - end - - def test_openWithoutBlock - zis = ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - end - - def test_incompleteReads - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[0], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[1], entry.name) - assert_equals(0, entry.size) - assert_equals(nil, zis.gets) - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[2], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[3], entry.name) - assert zis.gets.length > 0 - } - end - -end - -class TestFiles - RANDOM_ASCII_FILE1 = "randomAscii1.txt" - RANDOM_ASCII_FILE2 = "randomAscii2.txt" - RANDOM_ASCII_FILE3 = "randomAscii3.txt" - RANDOM_BINARY_FILE1 = "randomBinary1.bin" - RANDOM_BINARY_FILE2 = "randomBinary2.bin" - - EMPTY_TEST_DIR = "emptytestdir" - - ASCII_TEST_FILES = [ RANDOM_ASCII_FILE1, RANDOM_ASCII_FILE2, RANDOM_ASCII_FILE3 ] - BINARY_TEST_FILES = [ RANDOM_BINARY_FILE1, RANDOM_BINARY_FILE2 ] - TEST_DIRECTORIES = [ EMPTY_TEST_DIR ] - TEST_FILES = [ ASCII_TEST_FILES, BINARY_TEST_FILES, EMPTY_TEST_DIR ].flatten! - - def TestFiles.createTestFiles(recreate) - if (recreate || - ! (TEST_FILES.inject(true) { |accum, element| accum && File.exists?(element) })) - - ASCII_TEST_FILES.each_with_index { - |filename, index| - createRandomAscii(filename, 1E4 * (index+1)) - } - - BINARY_TEST_FILES.each_with_index { - |filename, index| - createRandomBinary(filename, 1E4 * (index+1)) - } - - ensureDir(EMPTY_TEST_DIR) - end - end - - private - def TestFiles.createRandomAscii(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand - end - } - end - - def TestFiles.createRandomBinary(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand.to_a.pack("V") - end - } - end - - def TestFiles.ensureDir(name) - if File.exists?(name) - return if File.stat(name).directory? - File.delete(name) - end - Dir.mkdir(name) - end - -end - -# For representation and creation of -# test data -class TestZipFile - attr_accessor :zipName, :entryNames, :comment - - def initialize(zipName, entryNames, comment = "") - @zipName=zipName - @entryNames=entryNames - @comment = comment - end - - def TestZipFile.createTestZips(recreate) - files = Dir.entries(".") - if (recreate || - ! (files.index(TEST_ZIP1.zipName) && - files.index(TEST_ZIP2.zipName) && - files.index(TEST_ZIP3.zipName) && - files.index(TEST_ZIP4.zipName) && - files.index("empty.txt") && - files.index("short.txt") && - files.index("longAscii.txt") && - files.index("longBinary.bin") )) - raise "failed to create test zip '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} ziptest.rb") - raise "failed to remove entry from '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} -d ziptest.rb") - - File.open("empty.txt", "w") {} - - File.open("short.txt", "w") { |file| file << "ABCDEF" } - ziptestTxt="" - File.open("ziptest.rb") { |file| ziptestTxt=file.read } - File.open("longAscii.txt", "w") { - |file| - while (file.tell < 1E5) - file << ziptestTxt - end - } - - testBinaryPattern="" - File.open("empty.zip") { |file| testBinaryPattern=file.read } - testBinaryPattern *= 4 - - File.open("longBinary.bin", "wb") { - |file| - while (file.tell < 3E5) - file << testBinaryPattern << rand - end - } - raise "failed to create test zip '#{TEST_ZIP2.zipName}'" unless - system("zip #{TEST_ZIP2.zipName} #{TEST_ZIP2.entryNames.join(' ')}") - - # without bash system interprets everything after echo as parameters to - # echo including | zip -z ... - raise "failed to add comment to test zip '#{TEST_ZIP2.zipName}'" unless - system("bash -c \"echo #{TEST_ZIP2.comment} | zip -z #{TEST_ZIP2.zipName}\"") - - raise "failed to create test zip '#{TEST_ZIP3.zipName}'" unless - system("zip #{TEST_ZIP3.zipName} #{TEST_ZIP3.entryNames.join(' ')}") - - raise "failed to create test zip '#{TEST_ZIP4.zipName}'" unless - system("zip #{TEST_ZIP4.zipName} #{TEST_ZIP4.entryNames.join(' ')}") - end - rescue - raise $!.to_s + - "\n\nziptest.rb requires the Info-ZIP program 'zip' in the path\n" + - "to create test data. If you don't have it you can download\n" + - "the necessary test files at http://sf.net/projects/rubyzip." - end - - TEST_ZIP1 = TestZipFile.new("empty.zip", []) - TEST_ZIP2 = TestZipFile.new("4entry.zip", %w{ longAscii.txt empty.txt short.txt longBinary.bin}, - "my zip comment") - TEST_ZIP3 = TestZipFile.new("test1.zip", %w{ file1.txt }) - TEST_ZIP4 = TestZipFile.new("zipWithDir.zip", [ "file1.txt", - TestFiles::EMPTY_TEST_DIR]) -end - - -class AbstractOutputStreamTest < RUNIT::TestCase - class TestOutputStream - include AbstractOutputStream - - attr_accessor :buffer - - def initialize - @buffer = "" - end - - def << (data) - @buffer << data - self - end - end - - def setup - @outputStream = TestOutputStream.new - - @origCommaSep = $, - @origOutputSep = $\ - end - - def teardown - $, = @origCommaSep - $\ = @origOutputSep - end - - def test_write - count = @outputStream.write("a little string") - assert_equals("a little string", @outputStream.buffer) - assert_equals("a little string".length, count) - - count = @outputStream.write(". a little more") - assert_equals("a little string. a little more", @outputStream.buffer) - assert_equals(". a little more".length, count) - end - - def test_print - $\ = nil # record separator set to nil - @outputStream.print("hello") - assert_equals("hello", @outputStream.buffer) - - @outputStream.print(" world.") - assert_equals("hello world.", @outputStream.buffer) - - @outputStream.print(" You ok ", "out ", "there?") - assert_equals("hello world. You ok out there?", @outputStream.buffer) - - $\ = "\n" - @outputStream.print - assert_equals("hello world. You ok out there?\n", @outputStream.buffer) - - @outputStream.print("I sure hope so!") - assert_equals("hello world. You ok out there?\nI sure hope so!\n", @outputStream.buffer) - - $, = "X" - @outputStream.buffer = "" - @outputStream.print("monkey", "duck", "zebra") - assert_equals("monkeyXduckXzebra\n", @outputStream.buffer) - - $\ = nil - @outputStream.buffer = "" - @outputStream.print(20) - assert_equals("20", @outputStream.buffer) - end - - def test_printf - @outputStream.printf("%d %04x", 123, 123) - assert_equals("123 007b", @outputStream.buffer) - end - - def test_putc - @outputStream.putc("A") - assert_equals("A", @outputStream.buffer) - @outputStream.putc(65) - assert_equals("AA", @outputStream.buffer) - end - - def test_puts - @outputStream.puts - assert_equals("\n", @outputStream.buffer) - - @outputStream.puts("hello", "world") - assert_equals("\nhello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts("hello\n", "world\n") - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"]) - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"], "bingo") - assert_equals("hello\nworld\nbingo\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(16, 20, 50, "hello") - assert_equals("16\n20\n50\nhello\n", @outputStream.buffer) - end -end - - -module CrcTest - def runCrcTest(compressorClass) - str = "Here's a nice little text to compute the crc for! Ho hum, it is nice nice nice nice indeed." - fakeOut = AbstractOutputStreamTest::TestOutputStream.new - - deflater = compressorClass.new(fakeOut) - deflater << str - assert_equals(0x919920fc, deflater.crc) - end -end - - - -class PassThruCompressorTest < RUNIT::TestCase - include CrcTest - - def test_size - File.open("dummy.txt", "wb") { - |file| - compressor = PassThruCompressor.new(file) - - assert_equals(0, compressor.size) - - t1 = "hello world" - t2 = "" - t3 = "bingo" - - compressor << t1 - assert_equals(compressor.size, t1.size) - - compressor << t2 - assert_equals(compressor.size, t1.size + t2.size) - - compressor << t3 - assert_equals(compressor.size, t1.size + t2.size + t3.size) - } - end - - def test_crc - runCrcTest(PassThruCompressor) - end -end - -class DeflaterTest < RUNIT::TestCase - include CrcTest - - def test_outputOperator - txt = loadFile("ziptest.rb") - deflate(txt, "deflatertest.bin") - inflatedTxt = inflate("deflatertest.bin") - assert_equals(txt, inflatedTxt) - end - - private - def loadFile(fileName) - txt = nil - File.open(fileName, "rb") { |f| txt = f.read } - end - - def deflate(data, fileName) - File.open(fileName, "wb") { - |file| - deflater = Deflater.new(file) - deflater << data - deflater.finish - assert_equals(deflater.size, data.size) - file << "trailing data for zlib with -MAX_WBITS" - } - end - - def inflate(fileName) - txt = nil - File.open(fileName, "rb") { - |file| - inflater = Inflater.new(file) - txt = inflater.read - } - end - - def test_crc - runCrcTest(Deflater) - end -end - -class ZipOutputStreamTest < RUNIT::TestCase - include AssertEntry - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "output.zip" - - def test_new - zos = ZipOutputStream.new(TEST_ZIP.zipName) - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - zos.close - assertTestZipContents(TEST_ZIP) - end - - def test_open - ZipOutputStream.open(TEST_ZIP.zipName) { - |zos| - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - } - assertTestZipContents(TEST_ZIP) - end - - def test_writingToClosedStream - assertIOErrorInClosedStream { |zos| zos << "hello world" } - assertIOErrorInClosedStream { |zos| zos.puts "hello world" } - assertIOErrorInClosedStream { |zos| zos.write "hello world" } - end - - def test_cannotOpenFile - name = TestFiles::EMPTY_TEST_DIR - begin - zos = ZipOutputStream.open(name) - rescue Exception - assert($!.kind_of?(Errno::EISDIR) || # Linux - $!.kind_of?(Errno::EEXIST) || # Windows/cygwin - $!.kind_of?(Errno::EACCES), # Windows - "Expected Errno::EISDIR (or on win/cygwin: Errno::EEXIST), but was: #{$!.type}") - end - end - - def assertIOErrorInClosedStream - assert_exception(IOError) { - zos = ZipOutputStream.new("test_putOnClosedStream.zip") - zos.close - yield zos - } - end - - def writeTestZip(zos) - TEST_ZIP.entryNames.each { - |entryName| - zos.putNextEntry(entryName) - File.open(entryName, "rb") { |f| zos.write(f.read) } - } - end -end - - - -module Enumerable - def compareEnumerables(otherEnumerable) - otherAsArray = otherEnumerable.to_a - index=0 - each_with_index { - |element, index| - return false unless yield(element, otherAsArray[index]) - } - return index+1 == otherAsArray.size - end -end - - -class ZipCentralDirectoryEntryTest < RUNIT::TestCase - - def test_readFromStream - File.open("testDirectory.bin", "rb") { - |file| - entry = ZipEntry.readCDirEntry(file) - - assert_equals("longAscii.txt", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(106490, entry.size) - assert_equals(3784, entry.compressedSize) - assert_equals(0xfcd1799c, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("empty.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(0, entry.size) - assert_equals(0, entry.compressedSize) - assert_equals(0x0, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("short.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(6, entry.size) - assert_equals(6, entry.compressedSize) - assert_equals(0xbb76fe69, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("longBinary.bin", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(1000024, entry.size) - assert_equals(70847, entry.compressedSize) - assert_equals(0x10da7d59, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals(nil, entry) -# Fields that are not check by this test: -# version made by 2 bytes -# version needed to extract 2 bytes -# general purpose bit flag 2 bytes -# last mod file time 2 bytes -# last mod file date 2 bytes -# compressed size 4 bytes -# uncompressed size 4 bytes -# disk number start 2 bytes -# internal file attributes 2 bytes -# external file attributes 4 bytes -# relative offset of local header 4 bytes - -# file name (variable size) -# extra field (variable size) -# file comment (variable size) - - } - end - - def test_ReadEntryFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read(12) } # cdir entry header is at least 46 bytes - fragment.extend(IOizeString) - entry = ZipEntry.new - entry.readCDirEntry(fragment) - fail "ZipError expected" - rescue ZipError - end - -end - -class ZipCentralDirectoryTest < RUNIT::TestCase - - def test_readFromStream - File.open(TestZipFile::TEST_ZIP2.zipName, "rb") { - |zipFile| - cdir = ZipCentralDirectory.readFromStream(zipFile) - - assert_equals(TestZipFile::TEST_ZIP2.entryNames.size, cdir.size) - assert(cdir.compareEnumerables(TestZipFile::TEST_ZIP2.entryNames) { - |cdirEntry, testEntryName| - cdirEntry.name == testEntryName - }) - assert_equals(TestZipFile::TEST_ZIP2.comment, cdir.comment) - } - end - - def test_readFromInvalidStream - File.open("ziptest.rb", "rb") { - |zipFile| - cdir = ZipCentralDirectory.new - cdir.readFromStream(zipFile) - } - fail "ZipError expected!" - rescue ZipError - end - - def test_ReadFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read } - fragment.slice!(12) # removed part of first cdir entry. eocd structure still complete - fragment.extend(IOizeString) - entry = ZipCentralDirectory.new - entry.readFromStream(fragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeToStream - entries = [ ZipEntry.new("file.zip", "flimse", "myComment", "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt", "Has a comment too") ] - cdir = ZipCentralDirectory.new(entries, "my zip comment") - File.open("cdirtest.bin", "wb") { |f| cdir.writeToStream(f) } - cdirReadback = ZipCentralDirectory.new - File.open("cdirtest.bin", "rb") { |f| cdirReadback.readFromStream(f) } - - assert_equals(cdir.entries, cdirReadback.entries) - end - - def test_equality - cdir1 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir2 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir3 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - cdir4 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - assert_equals(cdir1, cdir1) - assert_equals(cdir1, cdir2) - - assert(cdir1 != cdir3) - assert(cdir2 != cdir3) - assert(cdir2 != cdir3) - assert(cdir3 != cdir4) - - assert(cdir3 != "hello") - end -end - - -class BasicZipFileTest < RUNIT::TestCase - include AssertEntry - - def setup - @zipFile = ZipFile.new(TestZipFile::TEST_ZIP2.zipName) - @testEntryNameIndex=0 - end - - def nextTestEntryName - retVal=TestZipFile::TEST_ZIP2.entryNames[@testEntryNameIndex] - @testEntryNameIndex+=1 - return retVal - end - - def test_entries - assert_equals(TestZipFile::TEST_ZIP2.entryNames, @zipFile.entries.map {|e| e.name} ) - end - - def test_each - @zipFile.each { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_foreach - ZipFile.foreach(TestZipFile::TEST_ZIP2.zipName) { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStream - @zipFile.each { - |entry| - assertEntry(nextTestEntryName, @zipFile.getInputStream(entry), - entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStreamBlock - fileAndEntryName = @zipFile.entries.first.name - @zipFile.getInputStream(fileAndEntryName) { - |zis| - assertEntryContentsForStream(fileAndEntryName, - zis, - fileAndEntryName) - } - end -end - -class CommonZipFileFixture < RUNIT::TestCase - include AssertEntry - - EMPTY_FILENAME = "emptyZipFile.zip" - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "4entry_copy.zip" - - def setup - File.delete(EMPTY_FILENAME) if File.exists?(EMPTY_FILENAME) - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - end -end - -class ZipFileTest < CommonZipFileFixture - - def test_createFromScratch - comment = "a short comment" - - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.comment = comment - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals(comment, zfRead.comment) - assert_equals(0, zfRead.entries.length) - end - - def test_add - srcFile = "ziptest.rb" - entryName = "newEntryName.rb" - assert(File.exists? srcFile) - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.add(entryName, srcFile) - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals("", zfRead.comment) - assert_equals(1, zfRead.entries.length) - assert_equals(entryName, zfRead.entries.first.name) - AssertEntry.assertContents(srcFile, - zfRead.getInputStream(entryName) { |zis| zis.read }) - end - - def test_addExistingEntryName - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(zf.entries.first.name, "ziptest.rb") - } - } - end - - def test_addExistingEntryNameReplace - gotCalled = false - replacedEntry = nil - ZipFile.open(TEST_ZIP.zipName) { - |zf| - replacedEntry = zf.entries.first.name - zf.add(replacedEntry, "ziptest.rb") { gotCalled = true; true } - } - assert(gotCalled) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assertContains(zf, replacedEntry, "ziptest.rb") - } - end - - def test_addDirectory - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(TestFiles::EMPTY_TEST_DIR, TestFiles::EMPTY_TEST_DIR) - } - ZipFile.open(TEST_ZIP.zipName) { - |zf| - dirEntry = zf.entries.detect { |e| e.name == TestFiles::EMPTY_TEST_DIR+"/" } - assert(dirEntry.isDirectory) - } - end - - def test_remove - entryToRemove, *remainingEntries = TEST_ZIP.entryNames - - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include?(entryToRemove)) - zf.remove(entryToRemove) - assert(! zf.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zf.entries.map {|x| x.name }.sort, remainingEntries.sort) - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(! zfRead.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zfRead.entries.map {|x| x.name }.sort, remainingEntries.sort) - zfRead.close - end - - - def test_rename - entryToRename, *remainingEntries = TEST_ZIP.entryNames - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include? entryToRename) - - newName = "changed name" - assert(! zf.entries.map { |e| e.name }.include?(newName)) - - zf.rename(entryToRename, newName) - assert(zf.entries.map { |e| e.name }.include? newName) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.map { |e| e.name }.include? newName) - zfRead.close - end - - def test_renameToExistingEntry - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) - } - } - - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, zf.entries.map{ |e| e.name }) - } - end - - def test_renameToExistingEntryOverwrite - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) { gotCalled = true; true } - } - - assert(gotCalled) - oldEntries.delete_at(0) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, - zf.entries.map{ |e| e.name }) - } - end - - def test_renameNonEntry - nonEntry = "bogusEntry" - targetEntry = "targetEntryName" - zf = ZipFile.new(TEST_ZIP.zipName) - assert(! zf.entries.include?(nonEntry)) - assert_exception(ZipNoSuchEntryError) { - zf.rename(nonEntry, targetEntry) - } - zf.commit - assert(! zf.entries.include?(targetEntry)) - ensure - zf.close - end - - def test_renameEntryToExistingEntry - entry1, entry2, *remaining = TEST_ZIP.entryNames - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipEntryExistsError) { - zf.rename(entry1, entry2) - } - ensure - zf.close - end - - def test_replace - unchangedEntries = TEST_ZIP.entryNames.dup - entryToReplace = unchangedEntries.delete_at(2) - newEntrySrcFilename = "ziptest.rb" - - zf = ZipFile.new(TEST_ZIP.zipName) - zf.replace(entryToReplace, newEntrySrcFilename) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - AssertEntry::assertContents(newEntrySrcFilename, - zfRead.getInputStream(entryToReplace) { |is| is.read }) - zfRead.close - end - - def test_replaceNonEntry - entryToReplace = "nonExistingEntryname" - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_exception(ZipNoSuchEntryError) { - zf.replace(entryToReplace, "ziptest.rb") - } - } - end - - def test_commit - newName = "renamedFirst" - zf = ZipFile.new(TEST_ZIP.zipName) - oldName = zf.entries.first - zf.rename(oldName, newName) - zf.commit - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.detect { |e| e.name == newName } != nil) - assert(zfRead.entries.detect { |e| e.name == oldName } == nil) - zfRead.close - - zf.close - end - - # This test tests that after commit, you - # can delete the file you used to add the entry to the zip file - # with - def test_commitUseZipEntry - File.copy(TestFiles::RANDOM_ASCII_FILE1, "okToDelete.txt") - zf = ZipFile.open(TEST_ZIP.zipName) - zf.add("okToDelete.txt", "okToDelete.txt") - assertContains(zf, "okToDelete.txt") - zf.commit - File.move("okToDelete.txt", "okToDeleteMoved.txt") - assertContains(zf, "okToDelete.txt", "okToDeleteMoved.txt") - end - -# def test_close -# zf = ZipFile.new(TEST_ZIP.zipName) -# zf.close -# assert_exception(IOError) { -# zf.extract(TEST_ZIP.entryNames.first, "hullubullu") -# } -# end - - def test_compound1 - renamedName = "renamedName" - originalEntries = [] - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - assertNotContains(zf, TestFiles::RANDOM_ASCII_FILE1) - zf.add(TestFiles::RANDOM_ASCII_FILE1, - TestFiles::RANDOM_ASCII_FILE1) - assertContains(zf, TestFiles::RANDOM_ASCII_FILE1) - - zf.rename(zf.entries[0], renamedName) - assertContains(zf, renamedName) - - TestFiles::BINARY_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - - assertContains(zf, originalEntries.last.to_s) - zf.remove(originalEntries.last.to_s) - assertNotContains(zf, originalEntries.last.to_s) - - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - assertContains(zfRead, TestFiles::RANDOM_ASCII_FILE1) - assertContains(zfRead, renamedName) - TestFiles::BINARY_TEST_FILES.each { - |filename| - assertContains(zfRead, filename) - } - assertNotContains(zfRead, originalEntries.last.to_s) - ensure - zfRead.close - end - end - - def test_compound2 - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - originalEntries.each { - |entry| - zf.remove(entry) - assertNotContains(zf, entry) - } - assert(zf.entries.empty?) - - TestFiles::ASCII_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - assert_equals(zf.entries.map { |e| e.name }, TestFiles::ASCII_TEST_FILES) - - zf.rename(TestFiles::ASCII_TEST_FILES[0], "newName") - assertNotContains(zf, TestFiles::ASCII_TEST_FILES[0]) - assertContains(zf, "newName") - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - asciiTestFiles = TestFiles::ASCII_TEST_FILES.dup - asciiTestFiles.shift - asciiTestFiles.each { - |filename| - assertContains(zf, filename) - } - - assertContains(zf, "newName") - ensure - zfRead.close - end - end - - private - def assertContains(zf, entryName, filename = entryName) - assert(zf.entries.detect { |e| e.name == entryName} != nil, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") - assertEntryContents(zf, entryName, filename) if File.exists?(filename) - end - - def assertNotContains(zf, entryName) - assert(zf.entries.detect { |e| e.name == entryName} == nil, "entry #{entryName} in #{zf.entries.join(', ')} in zip file #{zf}") - end -end - -class ZipFileExtractTest < CommonZipFileFixture - EXTRACTED_FILENAME = "extEntry" - ENTRY_TO_EXTRACT, *REMAINING_ENTRIES = TEST_ZIP.entryNames.reverse - - def setup - super - File.delete(EXTRACTED_FILENAME) if File.exists?(EXTRACTED_FILENAME) - end - - def test_extract - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(ENTRY_TO_EXTRACT, EXTRACTED_FILENAME) - - assert(File.exists? EXTRACTED_FILENAME) - AssertEntry::assertContents(EXTRACTED_FILENAME, - zf.getInputStream(ENTRY_TO_EXTRACT) { |is| is.read }) - } - end - - def test_extractExists - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - assert_exception(ZipDestinationFileExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) - } - } - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert_equals(writtenText, f.read) - } - end - - def test_extractExistsOverwrite - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) { gotCalled = true; true } - } - - assert(gotCalled) - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert(writtenText != f.read) - } - end - - def test_extractNonEntry - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipNoSuchEntryError) { zf.extract("nonExistingEntry", "nonExistingEntry") } - ensure - zf.close if zf - end - - def test_extractNonEntry2 - outFile = "outfile" - assert_exception(ZipNoSuchEntryError) { - zf = ZipFile.new(TEST_ZIP.zipName) - nonEntry = "hotdog-diddelidoo" - assert(! zf.entries.include?(nonEntry)) - zf.extract(nonEntry, outFile) - zf.close - } - assert(! File.exists?(outFile)) - end - -end - -class ZipFileExtractDirectoryTest < CommonZipFileFixture - TEST_OUT_NAME = "emptyOutDir" - - def openZip(&aProc) - assert(aProc != nil) - ZipFile.open(TestZipFile::TEST_ZIP4.zipName, &aProc) - end - - def extractTestDir(&aProc) - openZip { - |zf| - zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &aProc) - } - end - - def setup - super - - Dir.rmdir(TEST_OUT_NAME) if File.directory? TEST_OUT_NAME - File.delete(TEST_OUT_NAME) if File.exists? TEST_OUT_NAME - end - - def test_extractDirectory - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsDir - Dir.mkdir TEST_OUT_NAME - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsFile - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - assert_exception(ZipDestinationFileExistsError) { extractTestDir } - end - - def test_extractDirectoryExistsAsFileOverwrite - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - gotCalled = false - extractTestDir { - |entry, destPath| - gotCalled = true - assert_equals(TEST_OUT_NAME, destPath) - assert(entry.isDirectory) - true - } - assert(gotCalled) - assert(File.directory? TEST_OUT_NAME) - end -end - - -TestFiles::createTestFiles(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -TestZipFile::createTestZips(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -exit if ARGV.index("recreateonly") != nil - -#require 'runit/cui/testrunner' -#RUNIT::CUI::TestRunner.run(ZipFileTest.suite) - -# Copyright (C) 2002 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/vendor/rubyzip-0.5.8/test/data/notzippedruby.rb b/vendor/rubyzip-0.5.8/test/data/notzippedruby.rb deleted file mode 100644 index 036d25e9..00000000 --- a/vendor/rubyzip-0.5.8/test/data/notzippedruby.rb +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env ruby - -class NotZippedRuby - def returnTrue - true - end -end diff --git a/vendor/rubyzip-0.5.8/test/data/rubycode.zip b/vendor/rubyzip-0.5.8/test/data/rubycode.zip deleted file mode 100644 index 8a68560e638088df79cd3bf68973013e5421a138..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 617 zcmWIWW@Zs#U|`^2*ef_ihj&#AyDyLz4#a#6q6}4;1qG=oMWsoVhI&Owp&^_M%*Qqz zusOT^fK6xx3qz{5pk{E`6@6X3Oa7XQ6aq^YrF$udom<7Oc6lM6}=RDLT(4ef%v0bc!4h^8y1W zF(|b-zqBYhRj;I?1ROS-fZ>81HlI)HpFDBKJKSTf$2lEFL!-|k4N6D3GG|(@>;ig~ zkx85xSJ3kU?ONSo@jDa*vNRhlwoTaWDdGpXl_l7YYQ5=Tcy zrKJm6mDneFo%MeBlu4K&z?+dtoEeupd4aBFU| zA4oGE-6BAHIKZ|xRwx*G19_o9EQG2%Ei)(8(9jU>1duaeP7pnEkh8&nhxJ1B@*nf6 zHmC3$*O)cQq4xsI(%Y*)R4Bgs(PD5T$>)hP`&4@^Uu)sOHmP%+3H(JZ*Tj$A;8upFq4-#D)-_!rOo zz!GmJQzzjm|Ki#?1CQIcEI2yj#PYsPrf+proq4ajTwpGhS;Rkm%D0A)zZ8EJcxCc?6yCYU#-$Z+V)|>29qyQgrU)ZKDMG0hH6=mA&`2McB8;FZB0rGW z^aM>2Fx?OquacTrK4QqJHwn1X`eeKbzSH2tf ze~@$y7Fr=VYr&WAVTm&&(+zp%a4xxL^+-`ycmB2e%C1^V4FwljSkE~#@%Fi*%OZ!K zJ;>Ryr7T6HD!}#Rvky$Dk$dY_Uj ZipEntry.new("zf.zip", "a"))) - assert_equal(1, (ZipEntry.new("zf.zip", "b") <=> ZipEntry.new("zf.zip", "a"))) - assert_equal(-1, (ZipEntry.new("zf.zip", "a") <=> ZipEntry.new("zf.zip", "b"))) - - entries = [ - ZipEntry.new("zf.zip", "5"), - ZipEntry.new("zf.zip", "1"), - ZipEntry.new("zf.zip", "3"), - ZipEntry.new("zf.zip", "4"), - ZipEntry.new("zf.zip", "0"), - ZipEntry.new("zf.zip", "2") - ] - - entries.sort! - assert_equal("0", entries[0].to_s) - assert_equal("1", entries[1].to_s) - assert_equal("2", entries[2].to_s) - assert_equal("3", entries[3].to_s) - assert_equal("4", entries[4].to_s) - assert_equal("5", entries[5].to_s) - end - - def test_parentAsString - entry1 = ZipEntry.new("zf.zip", "aa") - entry2 = ZipEntry.new("zf.zip", "aa/") - entry3 = ZipEntry.new("zf.zip", "aa/bb") - entry4 = ZipEntry.new("zf.zip", "aa/bb/") - entry5 = ZipEntry.new("zf.zip", "aa/bb/cc") - entry6 = ZipEntry.new("zf.zip", "aa/bb/cc/") - - assert_equal(nil, entry1.parent_as_string) - assert_equal(nil, entry2.parent_as_string) - assert_equal("aa/", entry3.parent_as_string) - assert_equal("aa/", entry4.parent_as_string) - assert_equal("aa/bb/", entry5.parent_as_string) - assert_equal("aa/bb/", entry6.parent_as_string) - end - - def test_entry_name_cannot_start_with_slash - assert_raise(ZipEntryNameError) { ZipEntry.new("zf.zip", "/hej/der") } - end -end - -module IOizeString - attr_reader :tell - - def read(count = nil) - @tell ||= 0 - count = size unless count - retVal = slice(@tell, count) - @tell += count - return retVal - end - - def seek(index, offset) - @tell ||= 0 - case offset - when IO::SEEK_END - newPos = size + index - when IO::SEEK_SET - newPos = index - when IO::SEEK_CUR - newPos = @tell + index - else - raise "Error in test method IOizeString::seek" - end - if (newPos < 0 || newPos >= size) - raise Errno::EINVAL - else - @tell=newPos - end - end - - def reset - @tell = 0 - end -end - -class ZipLocalEntryTest < Test::Unit::TestCase - def test_read_local_entryHeaderOfFirstTestZipEntry - File.open(TestZipFile::TEST_ZIP3.zip_name, "rb") { - |file| - entry = ZipEntry.read_local_entry(file) - - assert_equal("", entry.comment) - # Differs from windows and unix because of CR LF - # assert_equal(480, entry.compressed_size) - # assert_equal(0x2a27930f, entry.crc) - # extra field is 21 bytes long - # probably contains some unix attrutes or something - # disabled: assert_equal(nil, entry.extra) - assert_equal(ZipEntry::DEFLATED, entry.compression_method) - assert_equal(TestZipFile::TEST_ZIP3.entry_names[0], entry.name) - assert_equal(File.size(TestZipFile::TEST_ZIP3.entry_names[0]), entry.size) - assert(! entry.is_directory) - } - end - - def test_readDateTime - File.open("data/rubycode.zip", "rb") { - |file| - entry = ZipEntry.read_local_entry(file) - assert_equal("zippedruby1.rb", entry.name) - assert_equal(Time.at(1019261638), entry.time) - } - end - - def test_read_local_entryFromNonZipFile - File.open("data/file2.txt") { - |file| - assert_equal(nil, ZipEntry.read_local_entry(file)) - } - end - - def test_read_local_entryFromTruncatedZipFile - zipFragment="" - File.open(TestZipFile::TEST_ZIP2.zip_name) { |f| zipFragment = f.read(12) } # local header is at least 30 bytes - zipFragment.extend(IOizeString).reset - entry = ZipEntry.new - entry.read_local_entry(zipFragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeEntry - entry = ZipEntry.new("file.zip", "entryName", "my little comment", - "thisIsSomeExtraInformation", 100, 987654, - ZipEntry::DEFLATED, 400) - write_to_file("localEntryHeader.bin", "centralEntryHeader.bin", entry) - entryReadLocal, entryReadCentral = read_from_file("localEntryHeader.bin", "centralEntryHeader.bin") - compare_local_entry_headers(entry, entryReadLocal) - compare_c_dir_entry_headers(entry, entryReadCentral) - end - - private - def compare_local_entry_headers(entry1, entry2) - assert_equal(entry1.compressed_size , entry2.compressed_size) - assert_equal(entry1.crc , entry2.crc) - assert_equal(entry1.extra , entry2.extra) - assert_equal(entry1.compression_method, entry2.compression_method) - assert_equal(entry1.name , entry2.name) - assert_equal(entry1.size , entry2.size) - assert_equal(entry1.localHeaderOffset, entry2.localHeaderOffset) - end - - def compare_c_dir_entry_headers(entry1, entry2) - compare_local_entry_headers(entry1, entry2) - assert_equal(entry1.comment, entry2.comment) - end - - def write_to_file(localFileName, centralFileName, entry) - File.open(localFileName, "wb") { |f| entry.write_local_entry(f) } - File.open(centralFileName, "wb") { |f| entry.write_c_dir_entry(f) } - end - - def read_from_file(localFileName, centralFileName) - localEntry = nil - cdirEntry = nil - File.open(localFileName, "rb") { |f| localEntry = ZipEntry.read_local_entry(f) } - File.open(centralFileName, "rb") { |f| cdirEntry = ZipEntry.read_c_dir_entry(f) } - return [localEntry, cdirEntry] - end -end - - -module DecompressorTests - # expects @refText, @refLines and @decompressor - - TEST_FILE="data/file1.txt" - - def setup - @refText="" - File.open(TEST_FILE) { |f| @refText = f.read } - @refLines = @refText.split($/) - end - - def test_readEverything - assert_equal(@refText, @decompressor.read) - end - - def test_readInChunks - chunkSize = 5 - while (decompressedChunk = @decompressor.read(chunkSize)) - assert_equal(@refText.slice!(0, chunkSize), decompressedChunk) - end - assert_equal(0, @refText.size) - end - - def test_mixingReadsAndProduceInput - # Just some preconditions to make sure we have enough data for this test - assert(@refText.length > 1000) - assert(@refLines.length > 40) - - - assert_equal(@refText[0...100], @decompressor.read(100)) - - assert(! @decompressor.input_finished?) - buf = @decompressor.produce_input - assert_equal(@refText[100...(100+buf.length)], buf) - end -end - -class InflaterTest < Test::Unit::TestCase - include DecompressorTests - - def setup - super - @file = File.new("data/file1.txt.deflatedData", "rb") - @decompressor = Inflater.new(@file) - end - - def teardown - @file.close - end -end - - -class PassThruDecompressorTest < Test::Unit::TestCase - include DecompressorTests - def setup - super - @file = File.new(TEST_FILE) - @decompressor = PassThruDecompressor.new(@file, File.size(TEST_FILE)) - end - - def teardown - @file.close - end -end - - -module AssertEntry - def assert_next_entry(filename, zis) - assert_entry(filename, zis, zis.get_next_entry.name) - end - - def assert_entry(filename, zis, entryName) - assert_equal(filename, entryName) - assert_entryContentsForStream(filename, zis, entryName) - end - - def assert_entryContentsForStream(filename, zis, entryName) - File.open(filename, "rb") { - |file| - expected = file.read - actual = zis.read - if (expected != actual) - if ((expected && actual) && (expected.length > 400 || actual.length > 400)) - zipEntryFilename=entryName+".zipEntry" - File.open(zipEntryFilename, "wb") { |file| file << actual } - fail("File '#{filename}' is different from '#{zipEntryFilename}'") - else - assert_equal(expected, actual) - end - end - } - end - - def AssertEntry.assert_contents(filename, aString) - fileContents = "" - File.open(filename, "rb") { |f| fileContents = f.read } - if (fileContents != aString) - if (fileContents.length > 400 || aString.length > 400) - stringFile = filename + ".other" - File.open(stringFile, "wb") { |f| f << aString } - fail("File '#{filename}' is different from contents of string stored in '#{stringFile}'") - else - assert_equal(fileContents, aString) - end - end - end - - def assert_stream_contents(zis, testZipFile) - assert(zis != nil) - testZipFile.entry_names.each { - |entryName| - assert_next_entry(entryName, zis) - } - assert_equal(nil, zis.get_next_entry) - end - - def assert_test_zip_contents(testZipFile) - ZipInputStream.open(testZipFile.zip_name) { - |zis| - assert_stream_contents(zis, testZipFile) - } - end - - def assert_entryContents(zipFile, entryName, filename = entryName.to_s) - zis = zipFile.get_input_stream(entryName) - assert_entryContentsForStream(filename, zis, entryName) - ensure - zis.close if zis - end -end - - - -class ZipInputStreamTest < Test::Unit::TestCase - include AssertEntry - - def test_new - zis = ZipInputStream.new(TestZipFile::TEST_ZIP2.zip_name) - assert_stream_contents(zis, TestZipFile::TEST_ZIP2) - zis.close - end - - def test_openWithBlock - ZipInputStream.open(TestZipFile::TEST_ZIP2.zip_name) { - |zis| - assert_stream_contents(zis, TestZipFile::TEST_ZIP2) - } - end - - def test_openWithoutBlock - zis = ZipInputStream.open(TestZipFile::TEST_ZIP2.zip_name) - assert_stream_contents(zis, TestZipFile::TEST_ZIP2) - end - - def test_incompleteReads - ZipInputStream.open(TestZipFile::TEST_ZIP2.zip_name) { - |zis| - entry = zis.get_next_entry - assert_equal(TestZipFile::TEST_ZIP2.entry_names[0], entry.name) - assert zis.gets.length > 0 - entry = zis.get_next_entry - assert_equal(TestZipFile::TEST_ZIP2.entry_names[1], entry.name) - assert_equal(0, entry.size) - assert_equal(nil, zis.gets) - entry = zis.get_next_entry - assert_equal(TestZipFile::TEST_ZIP2.entry_names[2], entry.name) - assert zis.gets.length > 0 - entry = zis.get_next_entry - assert_equal(TestZipFile::TEST_ZIP2.entry_names[3], entry.name) - assert zis.gets.length > 0 - } - end - - def test_rewind - ZipInputStream.open(TestZipFile::TEST_ZIP2.zip_name) { - |zis| - e = zis.get_next_entry - assert_equal(TestZipFile::TEST_ZIP2.entry_names[0], e.name) - - # Do a little reading - buf = "" - buf << zis.read(100) - buf << (zis.gets || "") - buf << (zis.gets || "") - - zis.rewind - - buf2 = "" - buf2 << zis.read(100) - buf2 << (zis.gets || "") - buf2 << (zis.gets || "") - - assert_equal(buf, buf2) - - zis.rewind - - assert_entry(e.name, zis, e.name) - } - end - -end - - -module CrcTest - - class TestOutputStream - include IOExtras::AbstractOutputStream - - attr_accessor :buffer - - def initialize - @buffer = "" - end - - def << (data) - @buffer << data - self - end - end - - def run_crc_test(compressorClass) - str = "Here's a nice little text to compute the crc for! Ho hum, it is nice nice nice nice indeed." - fakeOut = TestOutputStream.new - - deflater = compressorClass.new(fakeOut) - deflater << str - assert_equal(0x919920fc, deflater.crc) - end -end - - - -class PassThruCompressorTest < Test::Unit::TestCase - include CrcTest - - def test_size - File.open("dummy.txt", "wb") { - |file| - compressor = PassThruCompressor.new(file) - - assert_equal(0, compressor.size) - - t1 = "hello world" - t2 = "" - t3 = "bingo" - - compressor << t1 - assert_equal(compressor.size, t1.size) - - compressor << t2 - assert_equal(compressor.size, t1.size + t2.size) - - compressor << t3 - assert_equal(compressor.size, t1.size + t2.size + t3.size) - } - end - - def test_crc - run_crc_test(PassThruCompressor) - end -end - -class DeflaterTest < Test::Unit::TestCase - include CrcTest - - def test_outputOperator - txt = load_file("data/file2.txt") - deflate(txt, "deflatertest.bin") - inflatedTxt = inflate("deflatertest.bin") - assert_equal(txt, inflatedTxt) - end - - private - def load_file(fileName) - txt = nil - File.open(fileName, "rb") { |f| txt = f.read } - end - - def deflate(data, fileName) - File.open(fileName, "wb") { - |file| - deflater = Deflater.new(file) - deflater << data - deflater.finish - assert_equal(deflater.size, data.size) - file << "trailing data for zlib with -MAX_WBITS" - } - end - - def inflate(fileName) - txt = nil - File.open(fileName, "rb") { - |file| - inflater = Inflater.new(file) - txt = inflater.read - } - end - - def test_crc - run_crc_test(Deflater) - end -end - -class ZipOutputStreamTest < Test::Unit::TestCase - include AssertEntry - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zip_name = "output.zip" - - def test_new - zos = ZipOutputStream.new(TEST_ZIP.zip_name) - zos.comment = TEST_ZIP.comment - write_test_zip(zos) - zos.close - assert_test_zip_contents(TEST_ZIP) - end - - def test_open - ZipOutputStream.open(TEST_ZIP.zip_name) { - |zos| - zos.comment = TEST_ZIP.comment - write_test_zip(zos) - } - assert_test_zip_contents(TEST_ZIP) - end - - def test_writingToClosedStream - assert_i_o_error_in_closed_stream { |zos| zos << "hello world" } - assert_i_o_error_in_closed_stream { |zos| zos.puts "hello world" } - assert_i_o_error_in_closed_stream { |zos| zos.write "hello world" } - end - - def test_cannotOpenFile - name = TestFiles::EMPTY_TEST_DIR - begin - zos = ZipOutputStream.open(name) - rescue Exception - assert($!.kind_of?(Errno::EISDIR) || # Linux - $!.kind_of?(Errno::EEXIST) || # Windows/cygwin - $!.kind_of?(Errno::EACCES), # Windows - "Expected Errno::EISDIR (or on win/cygwin: Errno::EEXIST), but was: #{$!.class}") - end - end - - def assert_i_o_error_in_closed_stream - assert_raise(IOError) { - zos = ZipOutputStream.new("test_putOnClosedStream.zip") - zos.close - yield zos - } - end - - def write_test_zip(zos) - TEST_ZIP.entry_names.each { - |entryName| - zos.put_next_entry(entryName) - File.open(entryName, "rb") { |f| zos.write(f.read) } - } - end -end - - - -module Enumerable - def compare_enumerables(otherEnumerable) - otherAsArray = otherEnumerable.to_a - index=0 - each_with_index { - |element, index| - return false unless yield(element, otherAsArray[index]) - } - return index+1 == otherAsArray.size - end -end - - -class ZipCentralDirectoryEntryTest < Test::Unit::TestCase - - def test_read_from_stream - File.open("data/testDirectory.bin", "rb") { - |file| - entry = ZipEntry.read_c_dir_entry(file) - - assert_equal("longAscii.txt", entry.name) - assert_equal(ZipEntry::DEFLATED, entry.compression_method) - assert_equal(106490, entry.size) - assert_equal(3784, entry.compressed_size) - assert_equal(0xfcd1799c, entry.crc) - assert_equal("", entry.comment) - - entry = ZipEntry.read_c_dir_entry(file) - assert_equal("empty.txt", entry.name) - assert_equal(ZipEntry::STORED, entry.compression_method) - assert_equal(0, entry.size) - assert_equal(0, entry.compressed_size) - assert_equal(0x0, entry.crc) - assert_equal("", entry.comment) - - entry = ZipEntry.read_c_dir_entry(file) - assert_equal("short.txt", entry.name) - assert_equal(ZipEntry::STORED, entry.compression_method) - assert_equal(6, entry.size) - assert_equal(6, entry.compressed_size) - assert_equal(0xbb76fe69, entry.crc) - assert_equal("", entry.comment) - - entry = ZipEntry.read_c_dir_entry(file) - assert_equal("longBinary.bin", entry.name) - assert_equal(ZipEntry::DEFLATED, entry.compression_method) - assert_equal(1000024, entry.size) - assert_equal(70847, entry.compressed_size) - assert_equal(0x10da7d59, entry.crc) - assert_equal("", entry.comment) - - entry = ZipEntry.read_c_dir_entry(file) - assert_equal(nil, entry) -# Fields that are not check by this test: -# version made by 2 bytes -# version needed to extract 2 bytes -# general purpose bit flag 2 bytes -# last mod file time 2 bytes -# last mod file date 2 bytes -# compressed size 4 bytes -# uncompressed size 4 bytes -# disk number start 2 bytes -# internal file attributes 2 bytes -# external file attributes 4 bytes -# relative offset of local header 4 bytes - -# file name (variable size) -# extra field (variable size) -# file comment (variable size) - - } - end - - def test_ReadEntryFromTruncatedZipFile - fragment="" - File.open("data/testDirectory.bin") { |f| fragment = f.read(12) } # cdir entry header is at least 46 bytes - fragment.extend(IOizeString) - entry = ZipEntry.new - entry.read_c_dir_entry(fragment) - fail "ZipError expected" - rescue ZipError - end - -end - - -class ZipEntrySetTest < Test::Unit::TestCase - ZIP_ENTRIES = [ - ZipEntry.new("zipfile.zip", "name1", "comment1"), - ZipEntry.new("zipfile.zip", "name2", "comment1"), - ZipEntry.new("zipfile.zip", "name3", "comment1"), - ZipEntry.new("zipfile.zip", "name4", "comment1"), - ZipEntry.new("zipfile.zip", "name5", "comment1"), - ZipEntry.new("zipfile.zip", "name6", "comment1") - ] - - def setup - @zipEntrySet = ZipEntrySet.new(ZIP_ENTRIES) - end - - def test_include - assert(@zipEntrySet.include?(ZIP_ENTRIES.first)) - assert(! @zipEntrySet.include?(ZipEntry.new("different.zip", "different", "aComment"))) - end - - def test_size - assert_equal(ZIP_ENTRIES.size, @zipEntrySet.size) - assert_equal(ZIP_ENTRIES.size, @zipEntrySet.length) - @zipEntrySet << ZipEntry.new("a", "b", "c") - assert_equal(ZIP_ENTRIES.size + 1, @zipEntrySet.length) - end - - def test_add - zes = ZipEntrySet.new - entry1 = ZipEntry.new("zf.zip", "name1") - entry2 = ZipEntry.new("zf.zip", "name2") - zes << entry1 - assert(zes.include?(entry1)) - zes.push(entry2) - assert(zes.include?(entry2)) - end - - def test_delete - assert_equal(ZIP_ENTRIES.size, @zipEntrySet.size) - entry = @zipEntrySet.delete(ZIP_ENTRIES.first) - assert_equal(ZIP_ENTRIES.size - 1, @zipEntrySet.size) - assert_equal(ZIP_ENTRIES.first, entry) - - entry = @zipEntrySet.delete(ZIP_ENTRIES.first) - assert_equal(ZIP_ENTRIES.size - 1, @zipEntrySet.size) - assert_nil(entry) - end - - def test_each - # Tested indirectly via each_with_index - count = 0 - @zipEntrySet.each_with_index { - |entry, index| - assert(ZIP_ENTRIES.include?(entry)) - count = count.succ - } - assert_equal(ZIP_ENTRIES.size, count) - end - - def test_entries - assert_equal(ZIP_ENTRIES.sort, @zipEntrySet.entries.sort) - end - - def test_compound - newEntry = ZipEntry.new("zf.zip", "new entry", "new entry's comment") - assert_equal(ZIP_ENTRIES.size, @zipEntrySet.size) - @zipEntrySet << newEntry - assert_equal(ZIP_ENTRIES.size + 1, @zipEntrySet.size) - assert(@zipEntrySet.include?(newEntry)) - - @zipEntrySet.delete(newEntry) - assert_equal(ZIP_ENTRIES.size, @zipEntrySet.size) - end - - def test_dup - copy = @zipEntrySet.dup - assert_equal(@zipEntrySet, copy) - - # demonstrate that this is a deep copy - copy.entries[0].name = "a totally different name" - assert(@zipEntrySet != copy) - end - - def test_parent - entries = [ - ZipEntry.new("zf.zip", "a"), - ZipEntry.new("zf.zip", "a/"), - ZipEntry.new("zf.zip", "a/b"), - ZipEntry.new("zf.zip", "a/b/"), - ZipEntry.new("zf.zip", "a/b/c"), - ZipEntry.new("zf.zip", "a/b/c/") - ] - entrySet = ZipEntrySet.new(entries) - - assert_equal(nil, entrySet.parent(entries[0])) - assert_equal(nil, entrySet.parent(entries[1])) - assert_equal(entries[1], entrySet.parent(entries[2])) - assert_equal(entries[1], entrySet.parent(entries[3])) - assert_equal(entries[3], entrySet.parent(entries[4])) - assert_equal(entries[3], entrySet.parent(entries[5])) - end -end - - -class ZipCentralDirectoryTest < Test::Unit::TestCase - - def test_read_from_stream - File.open(TestZipFile::TEST_ZIP2.zip_name, "rb") { - |zipFile| - cdir = ZipCentralDirectory.read_from_stream(zipFile) - - assert_equal(TestZipFile::TEST_ZIP2.entry_names.size, cdir.size) - assert(cdir.entries.sort.compare_enumerables(TestZipFile::TEST_ZIP2.entry_names.sort) { - |cdirEntry, testEntryName| - cdirEntry.name == testEntryName - }) - assert_equal(TestZipFile::TEST_ZIP2.comment, cdir.comment) - } - end - - def test_readFromInvalidStream - File.open("data/file2.txt", "rb") { - |zipFile| - cdir = ZipCentralDirectory.new - cdir.read_from_stream(zipFile) - } - fail "ZipError expected!" - rescue ZipError - end - - def test_ReadFromTruncatedZipFile - fragment="" - File.open("data/testDirectory.bin") { |f| fragment = f.read } - fragment.slice!(12) # removed part of first cdir entry. eocd structure still complete - fragment.extend(IOizeString) - entry = ZipCentralDirectory.new - entry.read_from_stream(fragment) - fail "ZipError expected" - rescue ZipError - end - - def test_write_to_stream - entries = [ ZipEntry.new("file.zip", "flimse", "myComment", "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt", "Has a comment too") ] - cdir = ZipCentralDirectory.new(entries, "my zip comment") - File.open("cdirtest.bin", "wb") { |f| cdir.write_to_stream(f) } - cdirReadback = ZipCentralDirectory.new - File.open("cdirtest.bin", "rb") { |f| cdirReadback.read_from_stream(f) } - - assert_equal(cdir.entries.sort, cdirReadback.entries.sort) - end - - def test_equality - cdir1 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir2 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir3 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - cdir4 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - assert_equal(cdir1, cdir1) - assert_equal(cdir1, cdir2) - - assert(cdir1 != cdir3) - assert(cdir2 != cdir3) - assert(cdir2 != cdir3) - assert(cdir3 != cdir4) - - assert(cdir3 != "hello") - end -end - - -class BasicZipFileTest < Test::Unit::TestCase - include AssertEntry - - def setup - @zipFile = ZipFile.new(TestZipFile::TEST_ZIP2.zip_name) - @testEntryNameIndex=0 - end - - def test_entries - assert_equal(TestZipFile::TEST_ZIP2.entry_names.sort, - @zipFile.entries.entries.sort.map {|e| e.name} ) - end - - def test_each - count = 0 - visited = {} - @zipFile.each { - |entry| - assert(TestZipFile::TEST_ZIP2.entry_names.include?(entry.name)) - assert(! visited.include?(entry.name)) - visited[entry.name] = nil - count = count.succ - } - assert_equal(TestZipFile::TEST_ZIP2.entry_names.length, count) - end - - def test_foreach - count = 0 - visited = {} - ZipFile.foreach(TestZipFile::TEST_ZIP2.zip_name) { - |entry| - assert(TestZipFile::TEST_ZIP2.entry_names.include?(entry.name)) - assert(! visited.include?(entry.name)) - visited[entry.name] = nil - count = count.succ - } - assert_equal(TestZipFile::TEST_ZIP2.entry_names.length, count) - end - - def test_get_input_stream - count = 0 - visited = {} - @zipFile.each { - |entry| - assert_entry(entry.name, @zipFile.get_input_stream(entry), entry.name) - assert(! visited.include?(entry.name)) - visited[entry.name] = nil - count = count.succ - } - assert_equal(TestZipFile::TEST_ZIP2.entry_names.length, count) - end - - def test_get_input_streamBlock - fileAndEntryName = @zipFile.entries.first.name - @zipFile.get_input_stream(fileAndEntryName) { - |zis| - assert_entryContentsForStream(fileAndEntryName, - zis, - fileAndEntryName) - } - end -end - -module CommonZipFileFixture - include AssertEntry - - EMPTY_FILENAME = "emptyZipFile.zip" - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zip_name = "4entry_copy.zip" - - def setup - File.delete(EMPTY_FILENAME) if File.exists?(EMPTY_FILENAME) - File.copy(TestZipFile::TEST_ZIP2.zip_name, TEST_ZIP.zip_name) - end -end - -class ZipFileTest < Test::Unit::TestCase - include CommonZipFileFixture - - def test_createFromScratch - comment = "a short comment" - - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.get_output_stream("myFile") { |os| os.write "myFile contains just this" } - zf.mkdir("dir1") - zf.comment = comment - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equal(comment, zfRead.comment) - assert_equal(2, zfRead.entries.length) - end - - def test_get_output_stream - entryCount = nil - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - entryCount = zf.size - zf.get_output_stream('newEntry.txt') { - |os| - os.write "Putting stuff in newEntry.txt" - } - assert_equal(entryCount+1, zf.size) - assert_equal("Putting stuff in newEntry.txt", zf.read("newEntry.txt")) - - zf.get_output_stream(zf.get_entry('data/generated/empty.txt')) { - |os| - os.write "Putting stuff in data/generated/empty.txt" - } - assert_equal(entryCount+1, zf.size) - assert_equal("Putting stuff in data/generated/empty.txt", zf.read("data/generated/empty.txt")) - - } - - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - assert_equal(entryCount+1, zf.size) - assert_equal("Putting stuff in newEntry.txt", zf.read("newEntry.txt")) - assert_equal("Putting stuff in data/generated/empty.txt", zf.read("data/generated/empty.txt")) - } - end - - def test_add - srcFile = "data/file2.txt" - entryName = "newEntryName.rb" - assert(File.exists?(srcFile)) - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.add(entryName, srcFile) - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equal("", zfRead.comment) - assert_equal(1, zfRead.entries.length) - assert_equal(entryName, zfRead.entries.first.name) - AssertEntry.assert_contents(srcFile, - zfRead.get_input_stream(entryName) { |zis| zis.read }) - end - - def test_addExistingEntryName - assert_raise(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.add(zf.entries.first.name, "data/file2.txt") - } - } - end - - def test_addExistingEntryNameReplace - gotCalled = false - replacedEntry = nil - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - replacedEntry = zf.entries.first.name - zf.add(replacedEntry, "data/file2.txt") { gotCalled = true; true } - } - assert(gotCalled) - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - assert_contains(zf, replacedEntry, "data/file2.txt") - } - end - - def test_addDirectory - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.add(TestFiles::EMPTY_TEST_DIR, TestFiles::EMPTY_TEST_DIR) - } - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - dirEntry = zf.entries.detect { |e| e.name == TestFiles::EMPTY_TEST_DIR+"/" } - assert(dirEntry.is_directory) - } - end - - def test_remove - entryToRemove, *remainingEntries = TEST_ZIP.entry_names - - File.copy(TestZipFile::TEST_ZIP2.zip_name, TEST_ZIP.zip_name) - - zf = ZipFile.new(TEST_ZIP.zip_name) - assert(zf.entries.map { |e| e.name }.include?(entryToRemove)) - zf.remove(entryToRemove) - assert(! zf.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equal(zf.entries.map {|x| x.name }.sort, remainingEntries.sort) - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zip_name) - assert(! zfRead.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equal(zfRead.entries.map {|x| x.name }.sort, remainingEntries.sort) - zfRead.close - end - - - def test_rename - entryToRename, *remainingEntries = TEST_ZIP.entry_names - - zf = ZipFile.new(TEST_ZIP.zip_name) - assert(zf.entries.map { |e| e.name }.include?(entryToRename)) - - newName = "changed name" - assert(! zf.entries.map { |e| e.name }.include?(newName)) - - zf.rename(entryToRename, newName) - assert(zf.entries.map { |e| e.name }.include?(newName)) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zip_name) - assert(zfRead.entries.map { |e| e.name }.include?(newName)) - zfRead.close - end - - def test_renameToExistingEntry - oldEntries = nil - ZipFile.open(TEST_ZIP.zip_name) { |zf| oldEntries = zf.entries } - - assert_raise(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) - } - } - - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - assert_equal(oldEntries.sort.map{ |e| e.name }, zf.entries.sort.map{ |e| e.name }) - } - end - - def test_renameToExistingEntryOverwrite - oldEntries = nil - ZipFile.open(TEST_ZIP.zip_name) { |zf| oldEntries = zf.entries } - - gotCalled = false - renamedEntryName = nil - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - renamedEntryName = zf.entries[0].name - zf.rename(zf.entries[0], zf.entries[1].name) { gotCalled = true; true } - } - - assert(gotCalled) - oldEntries.delete_if { |e| e.name == renamedEntryName } - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - assert_equal(oldEntries.sort.map{ |e| e.name }, - zf.entries.sort.map{ |e| e.name }) - } - end - - def test_renameNonEntry - nonEntry = "bogusEntry" - target_entry = "target_entryName" - zf = ZipFile.new(TEST_ZIP.zip_name) - assert(! zf.entries.include?(nonEntry)) - assert_raise(Errno::ENOENT) { - zf.rename(nonEntry, target_entry) - } - zf.commit - assert(! zf.entries.include?(target_entry)) - ensure - zf.close - end - - def test_renameEntryToExistingEntry - entry1, entry2, *remaining = TEST_ZIP.entry_names - zf = ZipFile.new(TEST_ZIP.zip_name) - assert_raise(ZipEntryExistsError) { - zf.rename(entry1, entry2) - } - ensure - zf.close - end - - def test_replace - entryToReplace = TEST_ZIP.entry_names[2] - newEntrySrcFilename = "data/file2.txt" - zf = ZipFile.new(TEST_ZIP.zip_name) - zf.replace(entryToReplace, newEntrySrcFilename) - - zf.close - zfRead = ZipFile.new(TEST_ZIP.zip_name) - AssertEntry::assert_contents(newEntrySrcFilename, - zfRead.get_input_stream(entryToReplace) { |is| is.read }) - AssertEntry::assert_contents(TEST_ZIP.entry_names[0], - zfRead.get_input_stream(TEST_ZIP.entry_names[0]) { |is| is.read }) - AssertEntry::assert_contents(TEST_ZIP.entry_names[1], - zfRead.get_input_stream(TEST_ZIP.entry_names[1]) { |is| is.read }) - AssertEntry::assert_contents(TEST_ZIP.entry_names[3], - zfRead.get_input_stream(TEST_ZIP.entry_names[3]) { |is| is.read }) - zfRead.close - end - - def test_replaceNonEntry - entryToReplace = "nonExistingEntryname" - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - assert_raise(Errno::ENOENT) { - zf.replace(entryToReplace, "data/file2.txt") - } - } - end - - def test_commit - newName = "renamedFirst" - zf = ZipFile.new(TEST_ZIP.zip_name) - oldName = zf.entries.first - zf.rename(oldName, newName) - zf.commit - - zfRead = ZipFile.new(TEST_ZIP.zip_name) - assert(zfRead.entries.detect { |e| e.name == newName } != nil) - assert(zfRead.entries.detect { |e| e.name == oldName } == nil) - zfRead.close - - zf.close - end - - # This test tests that after commit, you - # can delete the file you used to add the entry to the zip file - # with - def test_commitUseZipEntry - File.copy(TestFiles::RANDOM_ASCII_FILE1, "okToDelete.txt") - zf = ZipFile.open(TEST_ZIP.zip_name) - zf.add("okToDelete.txt", "okToDelete.txt") - assert_contains(zf, "okToDelete.txt") - zf.commit - File.move("okToDelete.txt", "okToDeleteMoved.txt") - assert_contains(zf, "okToDelete.txt", "okToDeleteMoved.txt") - end - -# def test_close -# zf = ZipFile.new(TEST_ZIP.zip_name) -# zf.close -# assert_raise(IOError) { -# zf.extract(TEST_ZIP.entry_names.first, "hullubullu") -# } -# end - - def test_compound1 - renamedName = "renamedName" - originalEntries = [] - begin - zf = ZipFile.new(TEST_ZIP.zip_name) - originalEntries = zf.entries.dup - - assert_not_contains(zf, TestFiles::RANDOM_ASCII_FILE1) - zf.add(TestFiles::RANDOM_ASCII_FILE1, - TestFiles::RANDOM_ASCII_FILE1) - assert_contains(zf, TestFiles::RANDOM_ASCII_FILE1) - - zf.rename(zf.entries[0], renamedName) - assert_contains(zf, renamedName) - - TestFiles::BINARY_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assert_contains(zf, filename) - } - - assert_contains(zf, originalEntries.last.to_s) - zf.remove(originalEntries.last.to_s) - assert_not_contains(zf, originalEntries.last.to_s) - - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zip_name) - assert_contains(zfRead, TestFiles::RANDOM_ASCII_FILE1) - assert_contains(zfRead, renamedName) - TestFiles::BINARY_TEST_FILES.each { - |filename| - assert_contains(zfRead, filename) - } - assert_not_contains(zfRead, originalEntries.last.to_s) - ensure - zfRead.close - end - end - - def test_compound2 - begin - zf = ZipFile.new(TEST_ZIP.zip_name) - originalEntries = zf.entries.dup - - originalEntries.each { - |entry| - zf.remove(entry) - assert_not_contains(zf, entry) - } - assert(zf.entries.empty?) - - TestFiles::ASCII_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assert_contains(zf, filename) - } - assert_equal(zf.entries.sort.map { |e| e.name }, TestFiles::ASCII_TEST_FILES) - - zf.rename(TestFiles::ASCII_TEST_FILES[0], "newName") - assert_not_contains(zf, TestFiles::ASCII_TEST_FILES[0]) - assert_contains(zf, "newName") - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zip_name) - asciiTestFiles = TestFiles::ASCII_TEST_FILES.dup - asciiTestFiles.shift - asciiTestFiles.each { - |filename| - assert_contains(zf, filename) - } - - assert_contains(zf, "newName") - ensure - zfRead.close - end - end - - private - def assert_contains(zf, entryName, filename = entryName) - assert(zf.entries.detect { |e| e.name == entryName} != nil, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") - assert_entryContents(zf, entryName, filename) if File.exists?(filename) - end - - def assert_not_contains(zf, entryName) - assert(zf.entries.detect { |e| e.name == entryName} == nil, "entry #{entryName} in #{zf.entries.join(', ')} in zip file #{zf}") - end -end - -class ZipFileExtractTest < Test::Unit::TestCase - include CommonZipFileFixture - EXTRACTED_FILENAME = "extEntry" - ENTRY_TO_EXTRACT, *REMAINING_ENTRIES = TEST_ZIP.entry_names.reverse - - def setup - super - File.delete(EXTRACTED_FILENAME) if File.exists?(EXTRACTED_FILENAME) - end - - def test_extract - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.extract(ENTRY_TO_EXTRACT, EXTRACTED_FILENAME) - - assert(File.exists?(EXTRACTED_FILENAME)) - AssertEntry::assert_contents(EXTRACTED_FILENAME, - zf.get_input_stream(ENTRY_TO_EXTRACT) { |is| is.read }) - } - end - - def test_extractExists - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - assert_raise(ZipDestinationFileExistsError) { - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) - } - } - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert_equal(writtenText, f.read) - } - end - - def test_extractExistsOverwrite - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - gotCalledCorrectly = false - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) { - |entry, extractLoc| - gotCalledCorrectly = zf.entries.first == entry && - extractLoc == EXTRACTED_FILENAME - true - } - } - - assert(gotCalledCorrectly) - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert(writtenText != f.read) - } - end - - def test_extractNonEntry - zf = ZipFile.new(TEST_ZIP.zip_name) - assert_raise(Errno::ENOENT) { zf.extract("nonExistingEntry", "nonExistingEntry") } - ensure - zf.close if zf - end - - def test_extractNonEntry2 - outFile = "outfile" - assert_raise(Errno::ENOENT) { - zf = ZipFile.new(TEST_ZIP.zip_name) - nonEntry = "hotdog-diddelidoo" - assert(! zf.entries.include?(nonEntry)) - zf.extract(nonEntry, outFile) - zf.close - } - assert(! File.exists?(outFile)) - end - -end - -class ZipFileExtractDirectoryTest < Test::Unit::TestCase - include CommonZipFileFixture - TEST_OUT_NAME = "emptyOutDir" - - def open_zip(&aProc) - assert(aProc != nil) - ZipFile.open(TestZipFile::TEST_ZIP4.zip_name, &aProc) - end - - def extract_test_dir(&aProc) - open_zip { - |zf| - zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &aProc) - } - end - - def setup - super - - Dir.rmdir(TEST_OUT_NAME) if File.directory? TEST_OUT_NAME - File.delete(TEST_OUT_NAME) if File.exists? TEST_OUT_NAME - end - - def test_extractDirectory - extract_test_dir - assert(File.directory?(TEST_OUT_NAME)) - end - - def test_extractDirectoryExistsAsDir - Dir.mkdir TEST_OUT_NAME - extract_test_dir - assert(File.directory?(TEST_OUT_NAME)) - end - - def test_extractDirectoryExistsAsFile - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - assert_raise(ZipDestinationFileExistsError) { extract_test_dir } - end - - def test_extractDirectoryExistsAsFileOverwrite - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - gotCalled = false - extract_test_dir { - |entry, destPath| - gotCalled = true - assert_equal(TEST_OUT_NAME, destPath) - assert(entry.is_directory) - true - } - assert(gotCalled) - assert(File.directory?(TEST_OUT_NAME)) - end -end - -class ZipStreamableFileTest < Test::Unit::TestCase - def test_equality - zipEntry1 = ZipEntry.new("zf.zip", "entryname1", "comment") - zipEntry2 = ZipEntry.new("zf.zip", "entryname2", "comment") - - zipStreamableFile1 = ZipStreamableFile.new(zipEntry1, "path") - zipStreamableFile2 = ZipStreamableFile.new(zipEntry1, "path") - zipStreamableFile3 = ZipStreamableFile.new(zipEntry1, "anotherPath") - zipStreamableFile4 = ZipStreamableFile.new(zipEntry2, "path") - - assert_equal(zipStreamableFile1, zipStreamableFile1) - assert_equal(zipStreamableFile1, zipStreamableFile2) - assert(zipStreamableFile1 != zipStreamableFile3) - assert(zipStreamableFile1 != zipStreamableFile4) - assert(zipStreamableFile1 != zipEntry1) - assert(zipStreamableFile1 != "hej") - end -end - -class ZipExtraFieldTest < Test::Unit::TestCase - def test_new - extra_pure = ZipExtraField.new("") - extra_withstr = ZipExtraField.new("foo") - assert_instance_of(ZipExtraField, extra_pure) - assert_instance_of(ZipExtraField, extra_withstr) - end - - def test_unknownfield - extra = ZipExtraField.new("foo") - assert_equal(extra["Unknown"], "foo") - extra.merge("a") - assert_equal(extra["Unknown"], "fooa") - extra.merge("barbaz") - assert_equal(extra.to_s, "fooabarbaz") - end - - - def test_merge - str = "UT\x5\0\x3\250$\r@Ux\0\0" - extra1 = ZipExtraField.new("") - extra2 = ZipExtraField.new(str) - assert(! extra1.member?("UniversalTime")) - assert(extra2.member?("UniversalTime")) - extra1.merge(str) - assert_equal(extra1["UniversalTime"].mtime, extra2["UniversalTime"].mtime) - end - - def test_length - str = "UT\x5\0\x3\250$\r@Ux\0\0Te\0\0testit" - extra = ZipExtraField.new(str) - assert_equal(extra.local_length, extra.to_local_bin.length) - assert_equal(extra.c_dir_length, extra.to_c_dir_bin.length) - extra.merge("foo") - assert_equal(extra.local_length, extra.to_local_bin.length) - assert_equal(extra.c_dir_length, extra.to_c_dir_bin.length) - end - - - def test_to_s - str = "UT\x5\0\x3\250$\r@Ux\0\0Te\0\0testit" - extra = ZipExtraField.new(str) - assert_instance_of(String, extra.to_s) - - s = extra.to_s - extra.merge("foo") - assert_equal(s.length + 3, extra.to_s.length) - end - - def test_equality - str = "UT\x5\0\x3\250$\r@" - extra1 = ZipExtraField.new(str) - extra2 = ZipExtraField.new(str) - extra3 = ZipExtraField.new(str) - assert_equal(extra1, extra2) - - extra2["UniversalTime"].mtime = Time.now - assert(extra1 != extra2) - - extra3.create("IUnix") - assert(extra1 != extra3) - - extra1.create("IUnix") - assert_equal(extra1, extra3) - end - -end - -# Copyright (C) 2002, 2003 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. From c6ea47d6deeec7e4d44f419352bd69ba710e137f Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Wed, 2 Nov 2005 05:36:11 +0000 Subject: [PATCH 77/84] Dropping .erbsql files (we now have schema.rb instead) --- db/pages.erbsql | 9 -------- db/revisions.erbsql | 16 --------------- db/schema.postgre.sql | 43 --------------------------------------- db/system.erbsql | 4 ---- db/webs.erbsql | 17 ---------------- db/wiki_references.erbsql | 9 -------- 6 files changed, 98 deletions(-) delete mode 100644 db/pages.erbsql delete mode 100644 db/revisions.erbsql delete mode 100644 db/schema.postgre.sql delete mode 100644 db/system.erbsql delete mode 100644 db/webs.erbsql delete mode 100644 db/wiki_references.erbsql diff --git a/db/pages.erbsql b/db/pages.erbsql deleted file mode 100644 index 5c2dd6d5..00000000 --- a/db/pages.erbsql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE TABLE pages ( - id <%= @pk %>, - created_at <%= @datetime %> NOT NULL, - updated_at <%= @datetime %> NOT NULL, - web_id INTEGER NOT NULL, - locked_by VARCHAR(60), - name VARCHAR(60), - locked_at <%= @datetime %> -) <%= create_options %>; \ No newline at end of file diff --git a/db/revisions.erbsql b/db/revisions.erbsql deleted file mode 100644 index 2b8e6e0e..00000000 --- a/db/revisions.erbsql +++ /dev/null @@ -1,16 +0,0 @@ -CREATE TABLE revisions ( - id <%= @pk %>, - created_at <%= @datetime %> NOT NULL, - updated_at <%= @datetime %> NOT NULL, - - -- note that continuous edits change the existing last revision, instead of creating one - -- revised_at is the timestamp of last edit of this revision - -- unlike updated_at is can be set explicitly in a call to Revision.create - -- besides, it should not be updated when Revision row changes for any other reason than - -- changing content - revised_at <%= @datetime %> NOT NULL, - page_id INTEGER NOT NULL, - content TEXT NOT NULL, - author VARCHAR(60), - ip VARCHAR(60) -) <%= create_options %>; \ No newline at end of file diff --git a/db/schema.postgre.sql b/db/schema.postgre.sql deleted file mode 100644 index 5191d9e4..00000000 --- a/db/schema.postgre.sql +++ /dev/null @@ -1,43 +0,0 @@ -CREATE TABLE pages ( - id serial primary key, - created_at timestamp without time zone NOT NULL, - updated_at timestamp without time zone NOT NULL, - web_id integer NOT NULL, - locked_by character varying(60), - name character varying(60), - locked_at timestamp without time zone -); - -CREATE TABLE revisions ( - id serial primary key, - created_at timestamp without time zone NOT NULL, - updated_at timestamp without time zone NOT NULL, - page_id integer NOT NULL, - content text NOT NULL, - author character varying(60), - ip character varying(60), - number integer -); - -CREATE TABLE system ( - id serial primary key, - "password" character varying(60) -); - -CREATE TABLE webs ( - id serial primary key, - created_at timestamp without time zone NOT NULL, - updated_at timestamp without time zone NOT NULL, - name character varying(60) NOT NULL, - address character varying(60) NOT NULL, - "password" character varying(60), - additional_style character varying(255), - allow_uploads boolean DEFAULT true, - published boolean DEFAULT false, - count_pages boolean DEFAULT false, - markup character varying(50) DEFAULT 'textile'::character varying, - color character varying(6) DEFAULT '008B26'::character varying, - max_upload_size integer DEFAULT 100, - safe_mode boolean DEFAULT false, - brackets_only boolean DEFAULT false -); diff --git a/db/system.erbsql b/db/system.erbsql deleted file mode 100644 index e0a55a33..00000000 --- a/db/system.erbsql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TABLE system ( - id <%= @pk %>, - <%= db_quote('password') %> VARCHAR(60) -) <%= create_options %>; \ No newline at end of file diff --git a/db/webs.erbsql b/db/webs.erbsql deleted file mode 100644 index c29728cd..00000000 --- a/db/webs.erbsql +++ /dev/null @@ -1,17 +0,0 @@ -CREATE TABLE webs ( - id <%= @pk %>, - created_at <%= @datetime %> NOT NULL, - updated_at <%= @datetime %> NOT NULL, - name VARCHAR(60) NOT NULL, - address VARCHAR(60) NOT NULL, - <%= db_quote('password') %> VARCHAR(60), - additional_style VARCHAR(255), - allow_uploads <%= @boolean %> DEFAULT '1', - published <%= @boolean %> DEFAULT '0', - count_pages <%= @boolean %> DEFAULT '0', - markup VARCHAR(50) DEFAULT 'textile', - color VARCHAR(6) DEFAULT '008B26', - max_upload_size INTEGER DEFAULT 100, - safe_mode <%= @boolean %> DEFAULT '0', - brackets_only <%= @boolean %> DEFAULT '0' -) <%= create_options %>; \ No newline at end of file diff --git a/db/wiki_references.erbsql b/db/wiki_references.erbsql deleted file mode 100644 index 6b7115ce..00000000 --- a/db/wiki_references.erbsql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE TABLE wiki_references ( - id <%= @pk %>, - created_at <%= @datetime %> NOT NULL, - updated_at <%= @datetime %> NOT NULL, - - page_id INTEGER NOT NULL, - referenced_name VARCHAR(60) NOT NULL, - link_type CHAR(1) NOT NULL -) <%= create_options %>; \ No newline at end of file From 26a5338764d46c31275d3df138c4cef9a56407be Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Wed, 2 Nov 2005 05:59:49 +0000 Subject: [PATCH 78/84] FIX BUILD --- config/environment.rb | 3 --- test/functional/file_controller_test.rb | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/config/environment.rb b/config/environment.rb index 4d19868a..19c69a79 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -16,9 +16,6 @@ Rails::Initializer.run do |config| # Activate observers that should always be running # config.active_record.observers = :cacher, :garbage_collector - # Make Active Record use UTC-base instead of local time - config.active_record.default_timezone = :utc - # Use Active Record's schema dumper instead of SQL when creating the test database # (enables use of different database adapters for development and test environments) config.active_record.schema_format = :ruby diff --git a/test/functional/file_controller_test.rb b/test/functional/file_controller_test.rb index 102ff8d5..24fc2125 100755 --- a/test/functional/file_controller_test.rb +++ b/test/functional/file_controller_test.rb @@ -3,6 +3,7 @@ require File.dirname(__FILE__) + '/../test_helper' require 'file_controller' require 'fileutils' +require 'stringio' # Raise errors beyond the default web-based presentation class FileController; def rescue_action(e) logger.error(e); raise e end; end From 7f8d3adfb5421e0164f4aebf906e6aedf7c3d257 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Wed, 2 Nov 2005 07:34:11 +0000 Subject: [PATCH 79/84] Copied over 0.14.2 ./public contents --- app/controllers/application.rb | 2 - config/environment.rb | 2 +- db/schema.rb | 8 + public/.htaccess | 12 +- public/dispatch.cgi | 18 +- public/dispatch.fcgi | 48 +- public/dispatch.rb | 2 +- public/javascripts/controls.js | 1154 ++++++----- public/javascripts/dragdrop.js | 1053 +++++----- public/javascripts/effects.js | 1713 +++++++++++------ public/javascripts/prototype.js | 2762 +++++++++++++++++---------- public/javascripts/scriptaculous.js | 47 + public/javascripts/slider.js | 258 +++ public/robots.txt | 1 + 14 files changed, 4408 insertions(+), 2672 deletions(-) create mode 100644 public/javascripts/scriptaculous.js create mode 100644 public/javascripts/slider.js create mode 100644 public/robots.txt diff --git a/app/controllers/application.rb b/app/controllers/application.rb index 9306b9c3..9a601b72 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -5,8 +5,6 @@ class ApplicationController < ActionController::Base before_filter :connect_to_model, :setup_url_generator, :set_content_type_header, :set_robots_metatag after_filter :remember_location, :teardown_url_generator - observer :page_observer - # For injecting a different wiki model implementation. Intended for use in tests def self.wiki=(the_wiki) # a global variable is used here because Rails reloads controller and model classes in the diff --git a/config/environment.rb b/config/environment.rb index 19c69a79..cb7c9b42 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -14,7 +14,7 @@ Rails::Initializer.run do |config| # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache" # Activate observers that should always be running - # config.active_record.observers = :cacher, :garbage_collector + config.active_record.observers = :page_observer # Use Active Record's schema dumper instead of SQL when creating the test database # (enables use of different database adapters for development and test environments) diff --git a/db/schema.rb b/db/schema.rb index 4eb50fdf..1bf4af3a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -23,6 +23,14 @@ ActiveRecord::Schema.define() do t.column "ip", :string, :limit => 60 end + create_table "sessions", :force => true do |t| + t.column "session_id", :string + t.column "data", :text + t.column "updated_at", :datetime + end + + add_index "sessions", ["session_id"], :name => "sessions_session_id_index" + create_table "system", :force => true do |t| t.column "password", :string, :limit => 60 end diff --git a/public/.htaccess b/public/.htaccess index 7eb56e8b..d3c99834 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -18,10 +18,18 @@ Options +FollowSymLinks +ExecCGI # Example: # RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] RewriteEngine On + +# If your Rails application is accessed via an Alias directive, +# then you MUST also set the RewriteBase in this htaccess file. +# +# Example: +# Alias /myrailsapp /path/to/myrailsapp/public +# RewriteBase /myrailsapp + RewriteRule ^$ index.html [QSA] RewriteRule ^([^.]+)$ $1.html [QSA] RewriteCond %{REQUEST_FILENAME} !-f -RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] +RewriteRule ^(.*)$ dispatch.cgi [QSA,L] # In case Rails experiences terminal errors # Instead of displaying this message you can supply a file here which will be rendered instead @@ -29,4 +37,4 @@ RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] # Example: # ErrorDocument 500 /500.html -ErrorDocument 500 "

    Application error

    Rails application failed to start properly" +ErrorDocument 500 "

    Application error

    Rails application failed to start properly" \ No newline at end of file diff --git a/public/dispatch.cgi b/public/dispatch.cgi index ce705d36..d89311e3 100755 --- a/public/dispatch.cgi +++ b/public/dispatch.cgi @@ -1,10 +1,10 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) - -# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: -# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired -require "dispatcher" - -ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +#!c:/ruby/bin/ruby + +require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) + +# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: +# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) Dispatcher.dispatch \ No newline at end of file diff --git a/public/dispatch.fcgi b/public/dispatch.fcgi index 664dbbbe..cc87e0bd 100755 --- a/public/dispatch.fcgi +++ b/public/dispatch.fcgi @@ -1,24 +1,24 @@ -#!/usr/bin/env ruby -# -# You may specify the path to the FastCGI crash log (a log of unhandled -# exceptions which forced the FastCGI instance to exit, great for debugging) -# and the number of requests to process before running garbage collection. -# -# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log -# and the GC period is nil (turned off). A reasonable number of requests -# could range from 10-100 depending on the memory footprint of your app. -# -# Example: -# # Default log path, normal GC behavior. -# RailsFCGIHandler.process! -# -# # Default log path, 50 requests between GC. -# RailsFCGIHandler.process! nil, 50 -# -# # Custom log path, normal GC behavior. -# RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log' -# -require File.dirname(__FILE__) + "/../config/environment" -require 'fcgi_handler' - -RailsFCGIHandler.process! +#!c:/ruby/bin/ruby +# +# You may specify the path to the FastCGI crash log (a log of unhandled +# exceptions which forced the FastCGI instance to exit, great for debugging) +# and the number of requests to process before running garbage collection. +# +# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log +# and the GC period is nil (turned off). A reasonable number of requests +# could range from 10-100 depending on the memory footprint of your app. +# +# Example: +# # Default log path, normal GC behavior. +# RailsFCGIHandler.process! +# +# # Default log path, 50 requests between GC. +# RailsFCGIHandler.process! nil, 50 +# +# # Custom log path, normal GC behavior. +# RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log' +# +require File.dirname(__FILE__) + "/../config/environment" +require 'fcgi_handler' + +RailsFCGIHandler.process! diff --git a/public/dispatch.rb b/public/dispatch.rb index ce705d36..c584d668 100755 --- a/public/dispatch.rb +++ b/public/dispatch.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env ruby +#!c:/ruby/bin/ruby require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) diff --git a/public/javascripts/controls.js b/public/javascripts/controls.js index cece0a91..b9ce4727 100644 --- a/public/javascripts/controls.js +++ b/public/javascripts/controls.js @@ -1,446 +1,708 @@ -// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan) -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Element.collectTextNodesIgnoreClass = function(element, ignoreclass) { - var children = $(element).childNodes; - var text = ""; - var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i"); - - for (var i = 0; i < children.length; i++) { - if(children[i].nodeType==3) { - text+=children[i].nodeValue; - } else { - if((!children[i].className.match(classtest)) && children[i].hasChildNodes()) - text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass); - } - } - - return text; -} - -// Autocompleter.Base handles all the autocompletion functionality -// that's independent of the data source for autocompletion. This -// includes drawing the autocompletion menu, observing keyboard -// and mouse events, and similar. -// -// Specific autocompleters need to provide, at the very least, -// a getUpdatedChoices function that will be invoked every time -// the text inside the monitored textbox changes. This method -// should get the text for which to provide autocompletion by -// invoking this.getEntry(), NOT by directly accessing -// this.element.value. This is to allow incremental tokenized -// autocompletion. Specific auto-completion logic (AJAX, etc) -// belongs in getUpdatedChoices. -// -// Tokenized incremental autocompletion is enabled automatically -// when an autocompleter is instantiated with the 'tokens' option -// in the options parameter, e.g.: -// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); -// will incrementally autocomplete with a comma as the token. -// Additionally, ',' in the above example can be replaced with -// a token array, e.g. { tokens: new Array (',', '\n') } which -// enables autocompletion on multiple tokens. This is most -// useful when one of the tokens is \n (a newline), as it -// allows smart autocompletion after linebreaks. - -var Autocompleter = {} -Autocompleter.Base = function() {}; -Autocompleter.Base.prototype = { - base_initialize: function(element, update, options) { - this.element = $(element); - this.update = $(update); - this.has_focus = false; - this.changed = false; - this.active = false; - this.index = 0; - this.entry_count = 0; - - if (this.setOptions) - this.setOptions(options); - else - this.options = {} - - this.options.tokens = this.options.tokens || new Array(); - this.options.frequency = this.options.frequency || 0.4; - this.options.min_chars = this.options.min_chars || 1; - this.options.onShow = this.options.onShow || - function(element, update){ - if(!update.style.position || update.style.position=='absolute') { - update.style.position = 'absolute'; - var offsets = Position.cumulativeOffset(element); - update.style.left = offsets[0] + 'px'; - update.style.top = (offsets[1] + element.offsetHeight) + 'px'; - update.style.width = element.offsetWidth + 'px'; - } - new Effect.Appear(update,{duration:0.15}); - }; - this.options.onHide = this.options.onHide || - function(element, update){ new Effect.Fade(update,{duration:0.15}) }; - - if(this.options.indicator) - this.indicator = $(this.options.indicator); - - if (typeof(this.options.tokens) == 'string') - this.options.tokens = new Array(this.options.tokens); - - this.observer = null; - - Element.hide(this.update); - - Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); - Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); - }, - - show: function() { - if(this.update.style.display=='none') this.options.onShow(this.element, this.update); - if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && this.update.style.position=='absolute') { - new Insertion.After(this.update, - ''); - this.iefix = $(this.update.id+'_iefix'); - } - if(this.iefix) { - Position.clone(this.update, this.iefix); - this.iefix.style.zIndex = 1; - this.update.style.zIndex = 2; - Element.show(this.iefix); - } - }, - - hide: function() { - if(this.update.style.display=='') this.options.onHide(this.element, this.update); - if(this.iefix) Element.hide(this.iefix); - }, - - startIndicator: function() { - if(this.indicator) Element.show(this.indicator); - }, - - stopIndicator: function() { - if(this.indicator) Element.hide(this.indicator); - }, - - onKeyPress: function(event) { - if(this.active) - switch(event.keyCode) { - case Event.KEY_TAB: - case Event.KEY_RETURN: - this.select_entry(); - Event.stop(event); - case Event.KEY_ESC: - this.hide(); - this.active = false; - return; - case Event.KEY_LEFT: - case Event.KEY_RIGHT: - return; - case Event.KEY_UP: - this.mark_previous(); - this.render(); - if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); - return; - case Event.KEY_DOWN: - this.mark_next(); - this.render(); - if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); - return; - } - else - if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN) - return; - - this.changed = true; - this.has_focus = true; - - if(this.observer) clearTimeout(this.observer); - this.observer = - setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); - }, - - onHover: function(event) { - var element = Event.findElement(event, 'LI'); - if(this.index != element.autocompleteIndex) - { - this.index = element.autocompleteIndex; - this.render(); - } - Event.stop(event); - }, - - onClick: function(event) { - var element = Event.findElement(event, 'LI'); - this.index = element.autocompleteIndex; - this.select_entry(); - Event.stop(event); - }, - - onBlur: function(event) { - // needed to make click events working - setTimeout(this.hide.bind(this), 250); - this.has_focus = false; - this.active = false; - }, - - render: function() { - if(this.entry_count > 0) { - for (var i = 0; i < this.entry_count; i++) - this.index==i ? - Element.addClassName(this.get_entry(i),"selected") : - Element.removeClassName(this.get_entry(i),"selected"); - - if(this.has_focus) { - if(this.get_current_entry().scrollIntoView) - this.get_current_entry().scrollIntoView(false); - - this.show(); - this.active = true; - } - } else this.hide(); - }, - - mark_previous: function() { - if(this.index > 0) this.index-- - else this.index = this.entry_count-1; - }, - - mark_next: function() { - if(this.index < this.entry_count-1) this.index++ - else this.index = 0; - }, - - get_entry: function(index) { - return this.update.firstChild.childNodes[index]; - }, - - get_current_entry: function() { - return this.get_entry(this.index); - }, - - select_entry: function() { - this.active = false; - value = Element.collectTextNodesIgnoreClass(this.get_current_entry(), 'informal').unescapeHTML(); - this.updateElement(value); - this.element.focus(); - }, - - updateElement: function(value) { - var last_token_pos = this.findLastToken(); - if (last_token_pos != -1) { - var new_value = this.element.value.substr(0, last_token_pos + 1); - var whitespace = this.element.value.substr(last_token_pos + 1).match(/^\s+/); - if (whitespace) - new_value += whitespace[0]; - this.element.value = new_value + value; - } else { - this.element.value = value; - } - }, - - updateChoices: function(choices) { - if(!this.changed && this.has_focus) { - this.update.innerHTML = choices; - Element.cleanWhitespace(this.update); - Element.cleanWhitespace(this.update.firstChild); - - if(this.update.firstChild && this.update.firstChild.childNodes) { - this.entry_count = - this.update.firstChild.childNodes.length; - for (var i = 0; i < this.entry_count; i++) { - entry = this.get_entry(i); - entry.autocompleteIndex = i; - this.addObservers(entry); - } - } else { - this.entry_count = 0; - } - - this.stopIndicator(); - - this.index = 0; - this.render(); - } - }, - - addObservers: function(element) { - Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); - Event.observe(element, "click", this.onClick.bindAsEventListener(this)); - }, - - onObserverEvent: function() { - this.changed = false; - if(this.getEntry().length>=this.options.min_chars) { - this.startIndicator(); - this.getUpdatedChoices(); - } else { - this.active = false; - this.hide(); - } - }, - - getEntry: function() { - var token_pos = this.findLastToken(); - if (token_pos != -1) - var ret = this.element.value.substr(token_pos + 1).replace(/^\s+/,'').replace(/\s+$/,''); - else - var ret = this.element.value; - - return /\n/.test(ret) ? '' : ret; - }, - - findLastToken: function() { - var last_token_pos = -1; - - for (var i=0; i last_token_pos) - last_token_pos = this_token_pos; - } - return last_token_pos; - } -} - -Ajax.Autocompleter = Class.create(); -Ajax.Autocompleter.prototype = Object.extend(new Autocompleter.Base(), -Object.extend(new Ajax.Base(), { - initialize: function(element, update, url, options) { - this.base_initialize(element, update, options); - this.options.asynchronous = true; - this.options.onComplete = this.onComplete.bind(this) - this.options.method = 'post'; - this.options.defaultParams = this.options.parameters || null; - this.url = url; - }, - - getUpdatedChoices: function() { - entry = encodeURIComponent(this.element.name) + '=' + - encodeURIComponent(this.getEntry()); - - this.options.parameters = this.options.callback ? - this.options.callback(this.element, entry) : entry; - - if(this.options.defaultParams) - this.options.parameters += '&' + this.options.defaultParams; - - new Ajax.Request(this.url, this.options); - }, - - onComplete: function(request) { - this.updateChoices(request.responseText); - } - -})); - -// The local array autocompleter. Used when you'd prefer to -// inject an array of autocompletion options into the page, rather -// than sending out Ajax queries, which can be quite slow sometimes. -// -// The constructor takes four parameters. The first two are, as usual, -// the id of the monitored textbox, and id of the autocompletion menu. -// The third is the array you want to autocomplete from, and the fourth -// is the options block. -// -// Extra local autocompletion options: -// - choices - How many autocompletion choices to offer -// -// - partial_search - If false, the autocompleter will match entered -// text only at the beginning of strings in the -// autocomplete array. Defaults to true, which will -// match text at the beginning of any *word* in the -// strings in the autocomplete array. If you want to -// search anywhere in the string, additionally set -// the option full_search to true (default: off). -// -// - full_search - Search anywhere in autocomplete array strings. -// -// - partial_chars - How many characters to enter before triggering -// a partial match (unlike min_chars, which defines -// how many characters are required to do any match -// at all). Defaults to 2. -// -// - ignore_case - Whether to ignore case when autocompleting. -// Defaults to true. -// -// It's possible to pass in a custom function as the 'selector' -// option, if you prefer to write your own autocompletion logic. -// In that case, the other options above will not apply unless -// you support them. - -Autocompleter.Local = Class.create(); -Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { - initialize: function(element, update, array, options) { - this.base_initialize(element, update, options); - this.options.array = array; - }, - - getUpdatedChoices: function() { - this.updateChoices(this.options.selector(this)); - }, - - setOptions: function(options) { - this.options = Object.extend({ - choices: 10, - partial_search: true, - partial_chars: 2, - ignore_case: true, - full_search: false, - selector: function(instance) { - var ret = new Array(); // Beginning matches - var partial = new Array(); // Inside matches - var entry = instance.getEntry(); - var count = 0; - - for (var i = 0; i < instance.options.array.length && - ret.length < instance.options.choices ; i++) { - var elem = instance.options.array[i]; - var found_pos = instance.options.ignore_case ? - elem.toLowerCase().indexOf(entry.toLowerCase()) : - elem.indexOf(entry); - - while (found_pos != -1) { - if (found_pos == 0 && elem.length != entry.length) { - ret.push("
  • " + elem.substr(0, entry.length) + "" + - elem.substr(entry.length) + "
  • "); - break; - } else if (entry.length >= instance.options.partial_chars && - instance.options.partial_search && found_pos != -1) { - if (instance.options.full_search || /\s/.test(elem.substr(found_pos-1,1))) { - partial.push("
  • " + elem.substr(0, found_pos) + "" + - elem.substr(found_pos, entry.length) + "" + elem.substr( - found_pos + entry.length) + "
  • "); - break; - } - } - - found_pos = instance.options.ignore_case ? - elem.toLowerCase().indexOf(entry.toLowerCase(), found_pos + 1) : - elem.indexOf(entry, found_pos + 1); - - } - } - if (partial.length) - ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) - return "
      " + ret.join('') + "
    "; - } - }, options || {}); - } -}); +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005 Jon Tirsen (http://www.tirsen.com) +// Contributors: +// Richard Livsey +// Rahul Bhargava +// Rob Wills +// +// See scriptaculous.js for full license. + +// Autocompleter.Base handles all the autocompletion functionality +// that's independent of the data source for autocompletion. This +// includes drawing the autocompletion menu, observing keyboard +// and mouse events, and similar. +// +// Specific autocompleters need to provide, at the very least, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +var Autocompleter = {} +Autocompleter.Base = function() {}; +Autocompleter.Base.prototype = { + baseInitialize: function(element, update, options) { + this.element = $(element); + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entryCount = 0; + + if (this.setOptions) + this.setOptions(options); + else + this.options = options || {}; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight}); + } + Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + if (typeof(this.options.tokens) == 'string') + this.options.tokens = new Array(this.options.tokens); + + this.observer = null; + + this.element.setAttribute('autocomplete','off'); + + Element.hide(this.update); + + Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); + }, + + fixIEOverlapping: function() { + Position.clone(this.update, this.iefix); + this.iefix.style.zIndex = 1; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN) + return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + this.index = element.autocompleteIndex; + this.render(); + } + Event.stop(event); + }, + + onClick: function(event) { + var element = Event.findElement(event, 'LI'); + this.index = element.autocompleteIndex; + this.selectEntry(); + this.hide(); + }, + + onBlur: function(event) { + // needed to make click events working + setTimeout(this.hide.bind(this), 250); + this.hasFocus = false; + this.active = false; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else this.hide(); + }, + + markPrevious: function() { + if(this.index > 0) this.index-- + else this.index = this.entryCount-1; + }, + + markNext: function() { + if(this.index < this.entryCount-1) this.index++ + else this.index = 0; + }, + + getEntry: function(index) { + return this.update.firstChild.childNodes[index]; + }, + + getCurrentEntry: function() { + return this.getEntry(this.index); + }, + + selectEntry: function() { + this.active = false; + this.updateElement(this.getCurrentEntry()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + + var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + var lastTokenPos = this.findLastToken(); + if (lastTokenPos != -1) { + var newValue = this.element.value.substr(0, lastTokenPos + 1); + var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value; + } else { + this.element.value = value; + } + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.firstChild); + + if(this.update.firstChild && this.update.firstChild.childNodes) { + this.entryCount = + this.update.firstChild.childNodes.length; + for (var i = 0; i < this.entryCount; i++) { + var entry = this.getEntry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entryCount = 0; + } + + this.stopIndicator(); + + this.index = 0; + this.render(); + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + if(this.getToken().length>=this.options.minChars) { + this.startIndicator(); + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + }, + + getToken: function() { + var tokenPos = this.findLastToken(); + if (tokenPos != -1) + var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); + else + var ret = this.element.value; + + return /\n/.test(ret) ? '' : ret; + }, + + findLastToken: function() { + var lastTokenPos = -1; + + for (var i=0; i lastTokenPos) + lastTokenPos = thisTokenPos; + } + return lastTokenPos; + } +} + +Ajax.Autocompleter = Class.create(); +Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } + +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +Autocompleter.Local = Class.create(); +Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + var foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (foundPos != -1) { + if (foundPos == 0 && elem.length != entry.length) { + ret.push("
  • " + elem.substr(0, entry.length) + "" + + elem.substr(entry.length) + "
  • "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
  • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  • "); + break; + } + } + + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + elem.indexOf(entry, foundPos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) + return "
      " + ret.join('') + "
    "; + } + }, options || {}); + } +}); + +// AJAX in-place editor +// +// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor + +Ajax.InPlaceEditor = Class.create(); +Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; +Ajax.InPlaceEditor.prototype = { + initialize: function(element, url, options) { + this.url = url; + this.element = $(element); + + this.options = Object.extend({ + okText: "ok", + cancelText: "cancel", + savingText: "Saving...", + clickToEditText: "Click to edit", + okText: "ok", + rows: 1, + onComplete: function(transport, element) { + new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); + }, + onFailure: function(transport) { + alert("Error communicating with the server: " + transport.responseText.stripTags()); + }, + callback: function(form) { + return Form.serialize(form); + }, + handleLineBreaks: true, + loadingText: 'Loading...', + savingClassName: 'inplaceeditor-saving', + loadingClassName: 'inplaceeditor-loading', + formClassName: 'inplaceeditor-form', + highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, + highlightendcolor: "#FFFFFF", + externalControl: null, + ajaxOptions: {} + }, options || {}); + + if(!this.options.formId && this.element.id) { + this.options.formId = this.element.id + "-inplaceeditor"; + if ($(this.options.formId)) { + // there's already a form with that name, don't specify an id + this.options.formId = null; + } + } + + if (this.options.externalControl) { + this.options.externalControl = $(this.options.externalControl); + } + + this.originalBackground = Element.getStyle(this.element, 'background-color'); + if (!this.originalBackground) { + this.originalBackground = "transparent"; + } + + this.element.title = this.options.clickToEditText; + + this.onclickListener = this.enterEditMode.bindAsEventListener(this); + this.mouseoverListener = this.enterHover.bindAsEventListener(this); + this.mouseoutListener = this.leaveHover.bindAsEventListener(this); + Event.observe(this.element, 'click', this.onclickListener); + Event.observe(this.element, 'mouseover', this.mouseoverListener); + Event.observe(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.observe(this.options.externalControl, 'click', this.onclickListener); + Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + }, + enterEditMode: function() { + if (this.saving) return; + if (this.editing) return; + this.editing = true; + this.onEnterEditMode(); + if (this.options.externalControl) { + Element.hide(this.options.externalControl); + } + Element.hide(this.element); + this.createForm(); + this.element.parentNode.insertBefore(this.form, this.element); + Field.focus(this.editField); + // stop the event to avoid a page refresh in Safari + if (arguments.length > 1) { + Event.stop(arguments[0]); + } + }, + createForm: function() { + this.form = document.createElement("form"); + this.form.id = this.options.formId; + Element.addClassName(this.form, this.options.formClassName) + this.form.onsubmit = this.onSubmit.bind(this); + + this.createEditField(); + + if (this.options.textarea) { + var br = document.createElement("br"); + this.form.appendChild(br); + } + + okButton = document.createElement("input"); + okButton.type = "submit"; + okButton.value = this.options.okText; + this.form.appendChild(okButton); + + cancelLink = document.createElement("a"); + cancelLink.href = "#"; + cancelLink.appendChild(document.createTextNode(this.options.cancelText)); + cancelLink.onclick = this.onclickCancel.bind(this); + this.form.appendChild(cancelLink); + }, + hasHTMLLineBreaks: function(string) { + if (!this.options.handleLineBreaks) return false; + return string.match(/
    /i); + }, + convertHTMLLineBreaks: function(string) { + return string.replace(/
    /gi, "\n").replace(//gi, "\n").replace(/<\/p>/gi, "\n").replace(/

    /gi, ""); + }, + createEditField: function() { + var text; + if(this.options.loadTextURL) { + text = this.options.loadingText; + } else { + text = this.getText(); + } + + if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { + this.options.textarea = false; + var textField = document.createElement("input"); + textField.type = "text"; + textField.name = "value"; + textField.value = text; + textField.style.backgroundColor = this.options.highlightcolor; + var size = this.options.size || this.options.cols || 0; + if (size != 0) textField.size = size; + this.editField = textField; + } else { + this.options.textarea = true; + var textArea = document.createElement("textarea"); + textArea.name = "value"; + textArea.value = this.convertHTMLLineBreaks(text); + textArea.rows = this.options.rows; + textArea.cols = this.options.cols || 40; + this.editField = textArea; + } + + if(this.options.loadTextURL) { + this.loadExternalText(); + } + this.form.appendChild(this.editField); + }, + getText: function() { + return this.element.innerHTML; + }, + loadExternalText: function() { + Element.addClassName(this.form, this.options.loadingClassName); + this.editField.disabled = true; + new Ajax.Request( + this.options.loadTextURL, + Object.extend({ + asynchronous: true, + onComplete: this.onLoadedExternalText.bind(this) + }, this.options.ajaxOptions) + ); + }, + onLoadedExternalText: function(transport) { + Element.removeClassName(this.form, this.options.loadingClassName); + this.editField.disabled = false; + this.editField.value = transport.responseText.stripTags(); + }, + onclickCancel: function() { + this.onComplete(); + this.leaveEditMode(); + return false; + }, + onFailure: function(transport) { + this.options.onFailure(transport); + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + this.oldInnerHTML = null; + } + return false; + }, + onSubmit: function() { + // onLoading resets these so we need to save them away for the Ajax call + var form = this.form; + var value = this.editField.value; + + // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... + // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... + // to be displayed indefinitely + this.onLoading(); + + new Ajax.Updater( + { + success: this.element, + // don't update on failure (this could be an option) + failure: null + }, + this.url, + Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this) + }, this.options.ajaxOptions) + ); + // stop the event to avoid a page refresh in Safari + if (arguments.length > 1) { + Event.stop(arguments[0]); + } + return false; + }, + onLoading: function() { + this.saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + showSaving: function() { + this.oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + Element.addClassName(this.element, this.options.savingClassName); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + }, + removeForm: function() { + if(this.form) { + if (this.form.parentNode) Element.remove(this.form); + this.form = null; + } + }, + enterHover: function() { + if (this.saving) return; + this.element.style.backgroundColor = this.options.highlightcolor; + if (this.effect) { + this.effect.cancel(); + } + Element.addClassName(this.element, this.options.hoverClassName) + }, + leaveHover: function() { + if (this.options.backgroundColor) { + this.element.style.backgroundColor = this.oldBackground; + } + Element.removeClassName(this.element, this.options.hoverClassName) + if (this.saving) return; + this.effect = new Effect.Highlight(this.element, { + startcolor: this.options.highlightcolor, + endcolor: this.options.highlightendcolor, + restorecolor: this.originalBackground + }); + }, + leaveEditMode: function() { + Element.removeClassName(this.element, this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + if (this.options.externalControl) { + Element.show(this.options.externalControl); + } + this.editing = false; + this.saving = false; + this.oldInnerHTML = null; + this.onLeaveEditMode(); + }, + onComplete: function(transport) { + this.leaveEditMode(); + this.options.onComplete.bind(this)(transport, this.element); + }, + onEnterEditMode: function() {}, + onLeaveEditMode: function() {}, + dispose: function() { + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + } + this.leaveEditMode(); + Event.stopObserving(this.element, 'click', this.onclickListener); + Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); + Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + } +}; \ No newline at end of file diff --git a/public/javascripts/dragdrop.js b/public/javascripts/dragdrop.js index c0fd1d1e..c676e6f1 100644 --- a/public/javascripts/dragdrop.js +++ b/public/javascripts/dragdrop.js @@ -1,537 +1,516 @@ -// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// -// Element.Class part Copyright (c) 2005 by Rick Olson -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Element.Class = { - // Element.toggleClass(element, className) toggles the class being on/off - // Element.toggleClass(element, className1, className2) toggles between both classes, - // defaulting to className1 if neither exist - toggle: function(element, className) { - if(Element.Class.has(element, className)) { - Element.Class.remove(element, className); - if(arguments.length == 3) Element.Class.add(element, arguments[2]); - } else { - Element.Class.add(element, className); - if(arguments.length == 3) Element.Class.remove(element, arguments[2]); - } - }, - - // gets space-delimited classnames of an element as an array - get: function(element) { - element = $(element); - return element.className.split(' '); - }, - - // functions adapted from original functions by Gavin Kistner - remove: function(element) { - element = $(element); - var regEx; - for(var i = 1; i < arguments.length; i++) { - regEx = new RegExp("^" + arguments[i] + "\\b\\s*|\\s*\\b" + arguments[i] + "\\b", 'g'); - element.className = element.className.replace(regEx, '') - } - }, - - add: function(element) { - element = $(element); - for(var i = 1; i < arguments.length; i++) { - Element.Class.remove(element, arguments[i]); - element.className += (element.className.length > 0 ? ' ' : '') + arguments[i]; - } - }, - - // returns true if all given classes exist in said element - has: function(element) { - element = $(element); - if(!element || !element.className) return false; - var regEx; - for(var i = 1; i < arguments.length; i++) { - regEx = new RegExp("\\b" + arguments[i] + "\\b"); - if(!regEx.test(element.className)) return false; - } - return true; - }, - - // expects arrays of strings and/or strings as optional paramters - // Element.Class.has_any(element, ['classA','classB','classC'], 'classD') - has_any: function(element) { - element = $(element); - if(!element || !element.className) return false; - var regEx; - for(var i = 1; i < arguments.length; i++) { - if((typeof arguments[i] == 'object') && - (arguments[i].constructor == Array)) { - for(var j = 0; j < arguments[i].length; j++) { - regEx = new RegExp("\\b" + arguments[i][j] + "\\b"); - if(regEx.test(element.className)) return true; - } - } else { - regEx = new RegExp("\\b" + arguments[i] + "\\b"); - if(regEx.test(element.className)) return true; - } - } - return false; - }, - - childrenWith: function(element, className) { - var children = $(element).getElementsByTagName('*'); - var elements = new Array(); - - for (var i = 0; i < children.length; i++) { - if (Element.Class.has(children[i], className)) { - elements.push(children[i]); - break; - } - } - - return elements; - } -} - -/*--------------------------------------------------------------------------*/ - -var Droppables = { - drops: false, - - remove: function(element) { - for(var i = 0; i < this.drops.length; i++) - if(this.drops[i].element == element) - this.drops.splice(i,1); - }, - - add: function(element) { - var element = $(element); - var options = Object.extend({ - greedy: true, - hoverclass: null - }, arguments[1] || {}); - - // cache containers - if(options.containment) { - options._containers = new Array(); - var containment = options.containment; - if((typeof containment == 'object') && - (containment.constructor == Array)) { - for(var i=0; i0) window.scrollBy(0,0); - - Event.stop(event); - } - } -} - -/*--------------------------------------------------------------------------*/ - -SortableObserver = Class.create(); -SortableObserver.prototype = { - initialize: function(element, observer) { - this.element = $(element); - this.observer = observer; - this.lastValue = Sortable.serialize(this.element); - }, - onStart: function() { - this.lastValue = Sortable.serialize(this.element); - }, - onEnd: function() { - if(this.lastValue != Sortable.serialize(this.element)) - this.observer(this.element) - } -} - -Sortable = { - sortables: new Array(), - options: function(element){ - var element = $(element); - for(var i=0;i0.5) { - if(dropon.previousSibling != element) { - var oldParentNode = element.parentNode; - element.style.visibility = "hidden"; // fix gecko rendering - dropon.parentNode.insertBefore(element, dropon); - if(dropon.parentNode!=oldParentNode && oldParentNode.sortable) - oldParentNode.sortable.onChange(element); - if(dropon.parentNode.sortable) - dropon.parentNode.sortable.onChange(element); - } - } else { - var nextElement = dropon.nextSibling || null; - if(nextElement != element) { - var oldParentNode = element.parentNode; - element.style.visibility = "hidden"; // fix gecko rendering - dropon.parentNode.insertBefore(element, nextElement); - if(dropon.parentNode!=oldParentNode && oldParentNode.sortable) - oldParentNode.sortable.onChange(element); - if(dropon.parentNode.sortable) - dropon.parentNode.sortable.onChange(element); - } - } - } - } - - // fix for gecko engine - Element.cleanWhitespace(element); - - options.draggables = []; - options.droppables = []; - - // make it so - var elements = element.childNodes; - for (var i = 0; i < elements.length; i++) - if(elements[i].tagName && elements[i].tagName==options.tag.toUpperCase() && - (!options.only || (Element.Class.has(elements[i], options.only)))) { - - // handles are per-draggable - var handle = options.handle ? - Element.Class.childrenWith(elements[i], options.handle)[0] : elements[i]; - - options.draggables.push(new Draggable(elements[i], Object.extend(options_for_draggable, { handle: handle }))); - - Droppables.add(elements[i], options_for_droppable); - options.droppables.push(elements[i]); - - } - - // keep reference - this.sortables.push(options); - - // for onupdate - Draggables.addObserver(new SortableObserver(element, options.onUpdate)); - - }, - serialize: function(element) { - var element = $(element); - var sortableOptions = this.options(element); - var options = Object.extend({ - tag: sortableOptions.tag, - only: sortableOptions.only, - name: element.id - }, arguments[1] || {}); - - var items = $(element).childNodes; - var queryComponents = new Array(); - - for(var i=0; i0) window.scrollBy(0,0); + + Event.stop(event); + } + } +} + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create(); +SortableObserver.prototype = { + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + onEnd: function() { + Sortable.unmark(); + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +} + +var Sortable = { + sortables: new Array(), + options: function(element){ + element = $(element); + return this.sortables.detect(function(s) { return s.element == element }); + }, + destroy: function(element){ + element = $(element); + this.sortables.findAll(function(s) { return s.element == element }).each(function(s){ + Draggables.removeObserver(s.element); + s.droppables.each(function(d){ Droppables.remove(d) }); + s.draggables.invoke('destroy'); + }); + this.sortables = this.sortables.reject(function(s) { return s.element == element }); + }, + create: function(element) { + element = $(element); + var options = Object.extend({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, // fixme: unimplemented + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + hoverclass: null, + ghosting: false, + format: null, + onChange: function() {}, + onUpdate: function() {} + }, arguments[1] || {}); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // build options for the droppables + var options_for_droppable = { + overlap: options.overlap, + containment: options.containment, + hoverclass: options.hoverclass, + onHover: Sortable.onHover, + greedy: !options.dropOnEmpty + } + + // fix for gecko engine + Element.cleanWhitespace(element); + + options.draggables = []; + options.droppables = []; + + // make it so + + // drop on empty handling + if(options.dropOnEmpty) { + Droppables.add(element, + {containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false}); + options.droppables.push(element); + } + + (this.findElements(element, options) || []).each( function(e) { + // handles are per-draggable + var handle = options.handle ? + Element.Class.childrenWith(e, options.handle)[0] : e; + options.draggables.push( + new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); + Droppables.add(e, options_for_droppable); + options.droppables.push(e); + }); + + // keep reference + this.sortables.push(options); + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + if(!element.hasChildNodes()) return null; + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName==options.tag.toUpperCase() && + (!options.only || (Element.Class.has(e, options.only)))) + elements.push(e); + if(options.tree) { + var grandchildren = this.findElements(e, options); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : null); + }, + + onHover: function(element, dropon, overlap) { + if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon) { + if(element.parentNode!=dropon) { + dropon.appendChild(element); + } + }, + + unmark: function() { + if(Sortable._marker) Element.hide(Sortable._marker); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = $('dropmarker') || document.createElement('DIV'); + Element.hide(Sortable._marker); + Element.Class.add(Sortable._marker, 'dropmarker'); + Sortable._marker.style.position = 'absolute'; + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.style.top = offsets[1] + 'px'; + if(position=='after') Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px'; + Sortable._marker.style.left = offsets[0] + 'px'; + Element.show(Sortable._marker); + }, + + serialize: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + only: sortableOptions.only, + name: element.id, + format: sortableOptions.format || /^[^_]*_(.*)$/ + }, arguments[1] || {}); + return $(this.findElements(element, options) || []).collect( function(item) { + return (encodeURIComponent(options.name) + "[]=" + + encodeURIComponent(item.id.match(options.format) ? item.id.match(options.format)[1] : '')); + }).join("&"); + } +} \ No newline at end of file diff --git a/public/javascripts/effects.js b/public/javascripts/effects.js index a8735f50..760f25e7 100644 --- a/public/javascripts/effects.js +++ b/public/javascripts/effects.js @@ -1,612 +1,1101 @@ -// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// -// Parts (c) 2005 Justin Palmer (http://encytemedia.com/) -// Parts (c) 2005 Mark Pilgrim (http://diveintomark.org/) -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - -Effect = {} -Effect2 = Effect; // deprecated - -/* ------------- transitions ------------- */ - -Effect.Transitions = {} - -Effect.Transitions.linear = function(pos) { - return pos; -} -Effect.Transitions.sinoidal = function(pos) { - return (-Math.cos(pos*Math.PI)/2) + 0.5; -} -Effect.Transitions.reverse = function(pos) { - return 1-pos; -} -Effect.Transitions.flicker = function(pos) { - return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random(0.25); -} -Effect.Transitions.wobble = function(pos) { - return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; -} -Effect.Transitions.pulse = function(pos) { - return (Math.floor(pos*10) % 2 == 0 ? - (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10))); -} -Effect.Transitions.none = function(pos) { - return 0; -} -Effect.Transitions.full = function(pos) { - return 1; -} - -/* ------------- element ext -------------- */ - -Element.makePositioned = function(element) { - element = $(element); - if(element.style.position == "") - element.style.position = "relative"; -} - -Element.makeClipping = function(element) { - element = $(element); - element._overflow = element.style.overflow || 'visible'; - if(element._overflow!='hidden') element.style.overflow = 'hidden'; -} - -Element.undoClipping = function(element) { - element = $(element); - if(element._overflow!='hidden') element.style.overflow = element._overflow; -} - -/* ------------- core effects ------------- */ - -Effect.Base = function() {}; -Effect.Base.prototype = { - setOptions: function(options) { - this.options = Object.extend({ - transition: Effect.Transitions.sinoidal, - duration: 1.0, // seconds - fps: 25.0, // max. 100fps - sync: false, // true for combining - from: 0.0, - to: 1.0 - }, options || {}); - }, - start: function(options) { - this.setOptions(options || {}); - this.currentFrame = 0; - this.startOn = new Date().getTime(); - this.finishOn = this.startOn + (this.options.duration*1000); - if(this.options.beforeStart) this.options.beforeStart(this); - if(!this.options.sync) this.loop(); - }, - loop: function() { - var timePos = new Date().getTime(); - if(timePos >= this.finishOn) { - this.render(this.options.to); - if(this.finish) this.finish(); - if(this.options.afterFinish) this.options.afterFinish(this); - return; - } - var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); - var frame = Math.round(pos * this.options.fps * this.options.duration); - if(frame > this.currentFrame) { - this.render(pos); - this.currentFrame = frame; - } - this.timeout = setTimeout(this.loop.bind(this), 10); - }, - render: function(pos) { - if(this.options.transition) pos = this.options.transition(pos); - pos *= (this.options.to-this.options.from); - pos += this.options.from; - if(this.options.beforeUpdate) this.options.beforeUpdate(this); - if(this.update) this.update(pos); - if(this.options.afterUpdate) this.options.afterUpdate(this); - }, - cancel: function() { - if(this.timeout) clearTimeout(this.timeout); - } -} - -Effect.Parallel = Class.create(); -Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { - initialize: function(effects) { - this.effects = effects || []; - this.start(arguments[1]); - }, - update: function(position) { - for (var i = 0; i < this.effects.length; i++) - this.effects[i].render(position); - }, - finish: function(position) { - for (var i = 0; i < this.effects.length; i++) - if(this.effects[i].finish) this.effects[i].finish(position); - } -}); - -// Internet Explorer caveat: works only on elements the have -// a 'layout', meaning having a given width or height. -// There is no way to safely set this automatically. -Effect.Opacity = Class.create(); -Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { - initialize: function(element) { - this.element = $(element); - options = Object.extend({ - from: 0.0, - to: 1.0 - }, arguments[1] || {}); - this.start(options); - }, - update: function(position) { - this.setOpacity(position); - }, - setOpacity: function(opacity) { - opacity = (opacity == 1) ? 0.99999 : opacity; - this.element.style.opacity = opacity; - this.element.style.filter = "alpha(opacity:"+opacity*100+")"; - } -}); - -Effect.MoveBy = Class.create(); -Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), { - initialize: function(element, toTop, toLeft) { - this.element = $(element); - this.originalTop = parseFloat(this.element.style.top || '0'); - this.originalLeft = parseFloat(this.element.style.left || '0'); - this.toTop = toTop; - this.toLeft = toLeft; - Element.makePositioned(this.element); - this.start(arguments[3]); - }, - update: function(position) { - topd = this.toTop * position + this.originalTop; - leftd = this.toLeft * position + this.originalLeft; - this.setPosition(topd, leftd); - }, - setPosition: function(topd, leftd) { - this.element.style.top = topd + "px"; - this.element.style.left = leftd + "px"; - } -}); - -Effect.Scale = Class.create(); -Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { - initialize: function(element, percent) { - this.element = $(element) - options = Object.extend({ - scaleX: true, - scaleY: true, - scaleContent: true, - scaleFromCenter: false, - scaleMode: 'box', // 'box' or 'contents' or {} with provided values - scaleFrom: 100.0 - }, arguments[2] || {}); - this.originalTop = this.element.offsetTop; - this.originalLeft = this.element.offsetLeft; - if(this.element.style.fontSize=="") this.sizeEm = 1.0; - if(this.element.style.fontSize && this.element.style.fontSize.indexOf("em")>0) - this.sizeEm = parseFloat(this.element.style.fontSize); - this.factor = (percent/100.0) - (options.scaleFrom/100.0); - if(options.scaleMode=='box') { - this.originalHeight = this.element.clientHeight; - this.originalWidth = this.element.clientWidth; - } else - if(options.scaleMode=='contents') { - this.originalHeight = this.element.scrollHeight; - this.originalWidth = this.element.scrollWidth; - } else { - this.originalHeight = options.scaleMode.originalHeight; - this.originalWidth = options.scaleMode.originalWidth; - } - this.start(options); - }, - - update: function(position) { - currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); - if(this.options.scaleContent && this.sizeEm) - this.element.style.fontSize = this.sizeEm*currentScale + "em"; - this.setDimensions( - this.originalWidth * currentScale, - this.originalHeight * currentScale); - }, - - setDimensions: function(width, height) { - if(this.options.scaleX) this.element.style.width = width + 'px'; - if(this.options.scaleY) this.element.style.height = height + 'px'; - if(this.options.scaleFromCenter) { - topd = (height - this.originalHeight)/2; - leftd = (width - this.originalWidth)/2; - if(this.element.style.position=='absolute') { - if(this.options.scaleY) this.element.style.top = this.originalTop-topd + "px"; - if(this.options.scaleX) this.element.style.left = this.originalLeft-leftd + "px"; - } else { - if(this.options.scaleY) this.element.style.top = -topd + "px"; - if(this.options.scaleX) this.element.style.left = -leftd + "px"; - } - } - } -}); - -Effect.Highlight = Class.create(); -Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { - initialize: function(element) { - this.element = $(element); - - // try to parse current background color as default for endcolor - // browser stores this as: "rgb(255, 255, 255)", convert to "#ffffff" format - var endcolor = "#ffffff"; - var current = this.element.style.backgroundColor; - if(current && current.slice(0,4) == "rgb(") { - endcolor = "#"; - var cols = current.slice(4,current.length-1).split(','); - var i=0; do { endcolor += parseInt(cols[i]).toColorPart() } while (++i<3); } - - var options = Object.extend({ - startcolor: "#ffff99", - endcolor: endcolor, - restorecolor: current - }, arguments[1] || {}); - - // init color calculations - this.colors_base = [ - parseInt(options.startcolor.slice(1,3),16), - parseInt(options.startcolor.slice(3,5),16), - parseInt(options.startcolor.slice(5),16) ]; - this.colors_delta = [ - parseInt(options.endcolor.slice(1,3),16)-this.colors_base[0], - parseInt(options.endcolor.slice(3,5),16)-this.colors_base[1], - parseInt(options.endcolor.slice(5),16)-this.colors_base[2] ]; - - this.start(options); - }, - update: function(position) { - var colors = [ - Math.round(this.colors_base[0]+(this.colors_delta[0]*position)), - Math.round(this.colors_base[1]+(this.colors_delta[1]*position)), - Math.round(this.colors_base[2]+(this.colors_delta[2]*position)) ]; - this.element.style.backgroundColor = "#" + - colors[0].toColorPart() + colors[1].toColorPart() + colors[2].toColorPart(); - }, - finish: function() { - this.element.style.backgroundColor = this.options.restorecolor; - } -}); - -Effect.ScrollTo = Class.create(); -Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { - initialize: function(element) { - this.element = $(element); - Position.prepare(); - var offsets = Position.cumulativeOffset(this.element); - var max = window.innerHeight ? - window.height - window.innerHeight : - document.body.scrollHeight - - (document.documentElement.clientHeight ? - document.documentElement.clientHeight : document.body.clientHeight); - this.scrollStart = Position.deltaY; - this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; - this.start(arguments[1] || {}); - }, - update: function(position) { - Position.prepare(); - window.scrollTo(Position.deltaX, - this.scrollStart + (position*this.delta)); - } -}); - -/* ------------- prepackaged effects ------------- */ - -Effect.Fade = function(element) { - options = Object.extend({ - from: 1.0, - to: 0.0, - afterFinish: function(effect) - { Element.hide(effect.element); - effect.setOpacity(1); } - }, arguments[1] || {}); - new Effect.Opacity(element,options); -} - -Effect.Appear = function(element) { - options = Object.extend({ - from: 0.0, - to: 1.0, - beforeStart: function(effect) - { effect.setOpacity(0); - Element.show(effect.element); }, - afterUpdate: function(effect) - { Element.show(effect.element); } - }, arguments[1] || {}); - new Effect.Opacity(element,options); -} - -Effect.Puff = function(element) { - new Effect.Parallel( - [ new Effect.Scale(element, 200, { sync: true, scaleFromCenter: true }), - new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ], - { duration: 1.0, - afterUpdate: function(effect) - { effect.effects[0].element.style.position = 'absolute'; }, - afterFinish: function(effect) - { Element.hide(effect.effects[0].element); } - } - ); -} - -Effect.BlindUp = function(element) { - Element.makeClipping(element); - new Effect.Scale(element, 0, - Object.extend({ scaleContent: false, - scaleX: false, - afterFinish: function(effect) - { - Element.hide(effect.element); - Element.undoClipping(effect.element); - } - }, arguments[1] || {}) - ); -} - -Effect.BlindDown = function(element) { - $(element).style.height = '0px'; - Element.makeClipping(element); - Element.show(element); - new Effect.Scale(element, 100, - Object.extend({ scaleContent: false, - scaleX: false, - scaleMode: 'contents', - scaleFrom: 0, - afterFinish: function(effect) { - Element.undoClipping(effect.element); - } - }, arguments[1] || {}) - ); -} - -Effect.SwitchOff = function(element) { - new Effect.Appear(element, - { duration: 0.4, - transition: Effect.Transitions.flicker, - afterFinish: function(effect) - { effect.element.style.overflow = 'hidden'; - new Effect.Scale(effect.element, 1, - { duration: 0.3, scaleFromCenter: true, - scaleX: false, scaleContent: false, - afterUpdate: function(effect) { - if(effect.element.style.position=="") - effect.element.style.position = 'relative'; }, - afterFinish: function(effect) { Element.hide(effect.element); } - } ) - } - } ); -} - -Effect.DropOut = function(element) { - new Effect.Parallel( - [ new Effect.MoveBy(element, 100, 0, { sync: true }), - new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ], - { duration: 0.5, - afterFinish: function(effect) - { Element.hide(effect.effects[0].element); } - }); -} - -Effect.Shake = function(element) { - new Effect.MoveBy(element, 0, 20, - { duration: 0.05, afterFinish: function(effect) { - new Effect.MoveBy(effect.element, 0, -40, - { duration: 0.1, afterFinish: function(effect) { - new Effect.MoveBy(effect.element, 0, 40, - { duration: 0.1, afterFinish: function(effect) { - new Effect.MoveBy(effect.element, 0, -40, - { duration: 0.1, afterFinish: function(effect) { - new Effect.MoveBy(effect.element, 0, 40, - { duration: 0.1, afterFinish: function(effect) { - new Effect.MoveBy(effect.element, 0, -20, - { duration: 0.05, afterFinish: function(effect) { - }}) }}) }}) }}) }}) }}); -} - -Effect.SlideDown = function(element) { - element = $(element); - element.style.height = '0px'; - Element.makeClipping(element); - Element.cleanWhitespace(element); - Element.makePositioned(element.firstChild); - Element.show(element); - new Effect.Scale(element, 100, - Object.extend({ scaleContent: false, - scaleX: false, - scaleMode: 'contents', - scaleFrom: 0, - afterUpdate: function(effect) - { effect.element.firstChild.style.bottom = - (effect.originalHeight - effect.element.clientHeight) + 'px'; }, - afterFinish: function(effect) - { Element.undoClipping(effect.element); } - }, arguments[1] || {}) - ); -} - -Effect.SlideUp = function(element) { - element = $(element); - Element.makeClipping(element); - Element.cleanWhitespace(element); - Element.makePositioned(element.firstChild); - Element.show(element); - new Effect.Scale(element, 0, - Object.extend({ scaleContent: false, - scaleX: false, - afterUpdate: function(effect) - { effect.element.firstChild.style.bottom = - (effect.originalHeight - effect.element.clientHeight) + 'px'; }, - afterFinish: function(effect) - { - Element.hide(effect.element); - Element.undoClipping(effect.element); - } - }, arguments[1] || {}) - ); -} - -Effect.Squish = function(element) { - new Effect.Scale(element, 0, - { afterFinish: function(effect) { Element.hide(effect.element); } }); -} - -Effect.Grow = function(element) { - element = $(element); - var options = arguments[1] || {}; - - var originalWidth = element.clientWidth; - var originalHeight = element.clientHeight; - element.style.overflow = 'hidden'; - Element.show(element); - - var direction = options.direction || 'center'; - var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; - var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal; - var opacityTransition = options.opacityTransition || Effect.Transitions.full; - - var initialMoveX, initialMoveY; - var moveX, moveY; - - switch (direction) { - case 'top-left': - initialMoveX = initialMoveY = moveX = moveY = 0; - break; - case 'top-right': - initialMoveX = originalWidth; - initialMoveY = moveY = 0; - moveX = -originalWidth; - break; - case 'bottom-left': - initialMoveX = moveX = 0; - initialMoveY = originalHeight; - moveY = -originalHeight; - break; - case 'bottom-right': - initialMoveX = originalWidth; - initialMoveY = originalHeight; - moveX = -originalWidth; - moveY = -originalHeight; - break; - case 'center': - initialMoveX = originalWidth / 2; - initialMoveY = originalHeight / 2; - moveX = -originalWidth / 2; - moveY = -originalHeight / 2; - break; - } - - new Effect.MoveBy(element, initialMoveY, initialMoveX, { - duration: 0.01, - beforeUpdate: function(effect) { $(element).style.height = '0px'; }, - afterFinish: function(effect) { - new Effect.Parallel( - [ new Effect.Opacity(element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }), - new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition }), - new Effect.Scale(element, 100, { - scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth }, - sync: true, scaleFrom: 0, scaleTo: 100, transition: scaleTransition })], - options); } - }); -} - -Effect.Shrink = function(element) { - element = $(element); - var options = arguments[1] || {}; - - var originalWidth = element.clientWidth; - var originalHeight = element.clientHeight; - element.style.overflow = 'hidden'; - Element.show(element); - - var direction = options.direction || 'center'; - var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; - var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal; - var opacityTransition = options.opacityTransition || Effect.Transitions.none; - - var moveX, moveY; - - switch (direction) { - case 'top-left': - moveX = moveY = 0; - break; - case 'top-right': - moveX = originalWidth; - moveY = 0; - break; - case 'bottom-left': - moveX = 0; - moveY = originalHeight; - break; - case 'bottom-right': - moveX = originalWidth; - moveY = originalHeight; - break; - case 'center': - moveX = originalWidth / 2; - moveY = originalHeight / 2; - break; - } - - new Effect.Parallel( - [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: opacityTransition }), - new Effect.Scale(element, 0, { sync: true, transition: moveTransition }), - new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: scaleTransition }) ], - options); -} - -Effect.Pulsate = function(element) { - var options = arguments[1] || {}; - var transition = options.transition || Effect.Transitions.sinoidal; - var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) }; - reverser.bind(transition); - new Effect.Opacity(element, - Object.extend(Object.extend({ duration: 3.0, - afterFinish: function(effect) { Element.show(effect.element); } - }, options), {transition: reverser})); -} - -Effect.Fold = function(element) { - $(element).style.overflow = 'hidden'; - new Effect.Scale(element, 5, Object.extend({ - scaleContent: false, - scaleTo: 100, - scaleX: false, - afterFinish: function(effect) { - new Effect.Scale(element, 1, { - scaleContent: false, - scaleTo: 0, - scaleY: false, - afterFinish: function(effect) { Element.hide(effect.element) } }); - }}, arguments[1] || {})); -} - -// old: new Effect.ContentZoom(element, percent) -// new: Element.setContentZoom(element, percent) - -Element.setContentZoom = function(element, percent) { - var element = $(element); - element.style.fontSize = (percent/100) + "em"; - if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); -} +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Contributors: +// Justin Palmer (http://encytemedia.com/) +// Mark Pilgrim (http://diveintomark.org/) +// Martin Bialasinki +// +// See scriptaculous.js for full license. + +Object.debug = function(obj) { + var info = []; + + if(typeof obj in ["string","number"]) { + return obj; + } else { + for(property in obj) + if(typeof obj[property]!="function") + info.push(property + ' => ' + + (typeof obj[property] == "string" ? + '"' + obj[property] + '"' : + obj[property])); + } + + return ("'" + obj + "' #" + typeof obj + + ": {" + info.join(", ") + "}"); +} + + +/*--------------------------------------------------------------------------*/ + +var Builder = { + NODEMAP: { + AREA: 'map', + CAPTION: 'table', + COL: 'table', + COLGROUP: 'table', + LEGEND: 'fieldset', + OPTGROUP: 'select', + OPTION: 'select', + PARAM: 'object', + TBODY: 'table', + TD: 'table', + TFOOT: 'table', + TH: 'table', + THEAD: 'table', + TR: 'table' + }, + // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken, + // due to a Firefox bug + node: function(elementName) { + elementName = elementName.toUpperCase(); + + // try innerHTML approach + var parentTag = this.NODEMAP[elementName] || 'div'; + var parentElement = document.createElement(parentTag); + parentElement.innerHTML = "<" + elementName + ">"; + var element = parentElement.firstChild || null; + + // see if browser added wrapping tags + if(element && (element.tagName != elementName)) + element = element.getElementsByTagName(elementName)[0]; + + // fallback to createElement approach + if(!element) element = document.createElement(elementName); + + // abort if nothing could be created + if(!element) return; + + // attributes (or text) + if(arguments[1]) + if(this._isStringOrNumber(arguments[1]) || + (arguments[1] instanceof Array)) { + this._children(element, arguments[1]); + } else { + var attrs = this._attributes(arguments[1]); + if(attrs.length) { + parentElement.innerHTML = "<" +elementName + " " + + attrs + ">"; + element = parentElement.firstChild || null; + // workaround firefox 1.0.X bug + if(!element) { + element = document.createElement(elementName); + for(attr in arguments[1]) + element[attr == 'class' ? 'className' : attr] = arguments[1][attr]; + } + if(element.tagName != elementName) + element = parentElement.getElementsByTagName(elementName)[0]; + } + } + + // text, or array of children + if(arguments[2]) + this._children(element, arguments[2]); + + return element; + }, + _text: function(text) { + return document.createTextNode(text); + }, + _attributes: function(attributes) { + var attrs = []; + for(attribute in attributes) + attrs.push((attribute=='className' ? 'class' : attribute) + + '="' + attributes[attribute].toString().escapeHTML() + '"'); + return attrs.join(" "); + }, + _children: function(element, children) { + if(typeof children=='object') { // array can hold nodes and text + children.flatten().each( function(e) { + if(typeof e=='object') + element.appendChild(e) + else + if(Builder._isStringOrNumber(e)) + element.appendChild(Builder._text(e)); + }); + } else + if(Builder._isStringOrNumber(children)) + element.appendChild(Builder._text(children)); + }, + _isStringOrNumber: function(param) { + return(typeof param=='string' || typeof param=='number'); + } +} + +/* ------------- element ext -------------- */ + +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { + color = "#"; + if(this.slice(0,4) == "rgb(") { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if(this.slice(0,1) == '#') { + if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if(this.length==7) color = this.toLowerCase(); + } + } + return(color.length==7 ? color : (arguments[0] || this)); +} + +Element.collectTextNodesIgnoreClass = function(element, ignoreclass) { + var children = $(element).childNodes; + var text = ""; + var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i"); + + for (var i = 0; i < children.length; i++) { + if(children[i].nodeType==3) { + text+=children[i].nodeValue; + } else { + if((!children[i].className.match(classtest)) && children[i].hasChildNodes()) + text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass); + } + } + + return text; +} + +Element.setContentZoom = function(element, percent) { + element = $(element); + element.style.fontSize = (percent/100) + "em"; + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); +} + +Element.getOpacity = function(element){ + var opacity; + if (opacity = Element.getStyle(element, "opacity")) + return parseFloat(opacity); + if (opacity = (Element.getStyle(element, "filter") || '').match(/alpha\(opacity=(.*)\)/)) + if(opacity[1]) return parseFloat(opacity[1]) / 100; + return 1.0; +} + +Element.setOpacity = function(element, value){ + element= $(element); + var els = element.style; + if (value == 1){ + els.opacity = '0.999999'; + if(/MSIE/.test(navigator.userAgent)) + els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,''); + } else { + if(value < 0.00001) value = 0; + els.opacity = value; + if(/MSIE/.test(navigator.userAgent)) + els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') + + "alpha(opacity="+value*100+")"; + } +} + +Element.getInlineOpacity = function(element){ + element= $(element); + var op; + op = element.style.opacity; + if (typeof op != "undefined" && op != "") return op; + return ""; +} + +Element.setInlineOpacity = function(element, value){ + element= $(element); + var els = element.style; + els.opacity = value; +} + +/*--------------------------------------------------------------------------*/ + +Element.Class = { + // Element.toggleClass(element, className) toggles the class being on/off + // Element.toggleClass(element, className1, className2) toggles between both classes, + // defaulting to className1 if neither exist + toggle: function(element, className) { + if(Element.Class.has(element, className)) { + Element.Class.remove(element, className); + if(arguments.length == 3) Element.Class.add(element, arguments[2]); + } else { + Element.Class.add(element, className); + if(arguments.length == 3) Element.Class.remove(element, arguments[2]); + } + }, + + // gets space-delimited classnames of an element as an array + get: function(element) { + return $(element).className.split(' '); + }, + + // functions adapted from original functions by Gavin Kistner + remove: function(element) { + element = $(element); + var removeClasses = arguments; + $R(1,arguments.length-1).each( function(index) { + element.className = + element.className.split(' ').reject( + function(klass) { return (klass == removeClasses[index]) } ).join(' '); + }); + }, + + add: function(element) { + element = $(element); + for(var i = 1; i < arguments.length; i++) { + Element.Class.remove(element, arguments[i]); + element.className += (element.className.length > 0 ? ' ' : '') + arguments[i]; + } + }, + + // returns true if all given classes exist in said element + has: function(element) { + element = $(element); + if(!element || !element.className) return false; + var regEx; + for(var i = 1; i < arguments.length; i++) { + if((typeof arguments[i] == 'object') && + (arguments[i].constructor == Array)) { + for(var j = 0; j < arguments[i].length; j++) { + regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)"); + if(!regEx.test(element.className)) return false; + } + } else { + regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)"); + if(!regEx.test(element.className)) return false; + } + } + return true; + }, + + // expects arrays of strings and/or strings as optional paramters + // Element.Class.has_any(element, ['classA','classB','classC'], 'classD') + has_any: function(element) { + element = $(element); + if(!element || !element.className) return false; + var regEx; + for(var i = 1; i < arguments.length; i++) { + if((typeof arguments[i] == 'object') && + (arguments[i].constructor == Array)) { + for(var j = 0; j < arguments[i].length; j++) { + regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)"); + if(regEx.test(element.className)) return true; + } + } else { + regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)"); + if(regEx.test(element.className)) return true; + } + } + return false; + }, + + childrenWith: function(element, className) { + var children = $(element).getElementsByTagName('*'); + var elements = new Array(); + + for (var i = 0; i < children.length; i++) + if (Element.Class.has(children[i], className)) + elements.push(children[i]); + + return elements; + } +} + +/*--------------------------------------------------------------------------*/ + +var Effect = { + tagifyText: function(element) { + var tagifyStyle = "position:relative"; + if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ";zoom:1"; + element = $(element); + $A(element.childNodes).each( function(child) { + if(child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + Builder.node('span',{style: tagifyStyle}, + character == " " ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if(((typeof element == 'object') || + (typeof element == 'function')) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || {}); + var speed = options.speed; + var delay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: delay + index * speed })); + }); + } +}; + +var Effect2 = Effect; // deprecated + +/* ------------- transitions ------------- */ + +Effect.Transitions = {} + +Effect.Transitions.linear = function(pos) { + return pos; +} +Effect.Transitions.sinoidal = function(pos) { + return (-Math.cos(pos*Math.PI)/2) + 0.5; +} +Effect.Transitions.reverse = function(pos) { + return 1-pos; +} +Effect.Transitions.flicker = function(pos) { + return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; +} +Effect.Transitions.wobble = function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; +} +Effect.Transitions.pulse = function(pos) { + return (Math.floor(pos*10) % 2 == 0 ? + (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10))); +} +Effect.Transitions.none = function(pos) { + return 0; +} +Effect.Transitions.full = function(pos) { + return 1; +} + +/* ------------- core effects ------------- */ + +Effect.Queue = { + effects: [], + interval: null, + add: function(effect) { + var timestamp = new Date().getTime(); + + switch(effect.options.queue) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'end': + // start effect after last queued effect has finished + timestamp = this.effects.pluck('finishOn').max() || timestamp; + break; + } + + effect.startOn += timestamp; + effect.finishOn += timestamp; + this.effects.push(effect); + if(!this.interval) + this.interval = setInterval(this.loop.bind(this), 40); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if(this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + this.effects.invoke('loop', timePos); + } +} + +Effect.Base = function() {}; +Effect.Base.prototype = { + position: null, + setOptions: function(options) { + this.options = Object.extend({ + transition: Effect.Transitions.sinoidal, + duration: 1.0, // seconds + fps: 25.0, // max. 25fps due to Effect.Queue implementation + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' + }, options || {}); + }, + start: function(options) { + this.setOptions(options || {}); + this.currentFrame = 0; + this.state = 'idle'; + this.startOn = this.options.delay*1000; + this.finishOn = this.startOn + (this.options.duration*1000); + this.event('beforeStart'); + if(!this.options.sync) Effect.Queue.add(this); + }, + loop: function(timePos) { + if(timePos >= this.startOn) { + if(timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if(this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); + var frame = Math.round(pos * this.options.fps * this.options.duration); + if(frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + render: function(pos) { + if(this.state == 'idle') { + this.state = 'running'; + this.event('beforeSetup'); + if(this.setup) this.setup(); + this.event('afterSetup'); + } + if(this.options.transition) pos = this.options.transition(pos); + pos *= (this.options.to-this.options.from); + pos += this.options.from; + this.position = pos; + this.event('beforeUpdate'); + if(this.update) this.update(pos); + this.event('afterUpdate'); + }, + cancel: function() { + if(!this.options.sync) Effect.Queue.remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if(this.options[eventName]) this.options[eventName](this); + } +} + +Effect.Parallel = Class.create(); +Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if(effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Opacity = Class.create(); +Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + // make this work on IE on elements without 'layout' + if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout)) + this.element.style.zoom = 1; + var options = Object.extend({ + from: Element.getOpacity(this.element) || 0.0, + to: 1.0 + }, arguments[1] || {}); + this.start(options); + }, + update: function(position) { + Element.setOpacity(this.element, position); + } +}); + +Effect.MoveBy = Class.create(); +Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), { + initialize: function(element, toTop, toLeft) { + this.element = $(element); + this.toTop = toTop; + this.toLeft = toLeft; + this.start(arguments[3]); + }, + setup: function() { + // Bug in Opera: Opera returns the "real" position of a static element or + // relative element that does not have top/left explicitly set. + // ==> Always set top and left for position relative elements in your stylesheets + // (to 0 if you do not need them) + + Element.makePositioned(this.element); + this.originalTop = parseFloat(Element.getStyle(this.element,'top') || '0'); + this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0'); + }, + update: function(position) { + var topd = this.toTop * position + this.originalTop; + var leftd = this.toLeft * position + this.originalLeft; + this.setPosition(topd, leftd); + }, + setPosition: function(topd, leftd) { + this.element.style.top = topd + "px"; + this.element.style.left = leftd + "px"; + } +}); + +Effect.Scale = Class.create(); +Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { + initialize: function(element, percent) { + this.element = $(element) + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or {} with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || {}); + this.start(options); + }, + setup: function() { + var effect = this; + + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = Element.getStyle(this.element,'position'); + + effect.originalStyle = {}; + ['top','left','width','height','fontSize'].each( function(k) { + effect.originalStyle[k] = effect.element.style[k]; + }); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = Element.getStyle(this.element,'font-size') || "100%"; + ['em','px','%'].each( function(fontSizeType) { + if(fontSize.indexOf(fontSizeType)>0) { + effect.fontSize = parseFloat(fontSize); + effect.fontSizeType = fontSizeType; + } + }); + + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + + this.dims = null; + if(this.options.scaleMode=='box') + this.dims = [this.element.clientHeight, this.element.clientWidth]; + if(this.options.scaleMode=='content') + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if(!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if(this.options.scaleContent && this.fontSize) + this.element.style.fontSize = this.fontSize*currentScale + this.fontSizeType; + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if (this.restoreAfterFinish) { + var effect = this; + ['top','left','width','height','fontSize'].each( function(k) { + effect.element.style[k] = effect.originalStyle[k]; + }); + } + }, + setDimensions: function(height, width) { + var els = this.element.style; + if(this.options.scaleX) els.width = width + 'px'; + if(this.options.scaleY) els.height = height + 'px'; + if(this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if(this.elementPositioning == 'absolute') { + if(this.options.scaleY) els.top = this.originalTop-topd + "px"; + if(this.options.scaleX) els.left = this.originalLeft-leftd + "px"; + } else { + if(this.options.scaleY) els.top = -topd + "px"; + if(this.options.scaleX) els.left = -leftd + "px"; + } + } + } +}); + +Effect.Highlight = Class.create(); +Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + var options = Object.extend({ + startcolor: "#ffff99" + }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Disable background image during the effect + this.oldBgImage = this.element.style.backgroundImage; + this.element.style.backgroundImage = "none"; + if(!this.options.endcolor) + this.options.endcolor = Element.getStyle(this.element, 'background-color').parseColor('#ffffff'); + if (typeof this.options.restorecolor == "undefined") + this.options.restorecolor = this.element.style.backgroundColor; + // init color calculations + this.colors_base = [ + parseInt(this.options.startcolor.slice(1,3),16), + parseInt(this.options.startcolor.slice(3,5),16), + parseInt(this.options.startcolor.slice(5),16) ]; + this.colors_delta = [ + parseInt(this.options.endcolor.slice(1,3),16)-this.colors_base[0], + parseInt(this.options.endcolor.slice(3,5),16)-this.colors_base[1], + parseInt(this.options.endcolor.slice(5),16)-this.colors_base[2]]; + }, + update: function(position) { + var effect = this; var colors = $R(0,2).map( function(i){ + return Math.round(effect.colors_base[i]+(effect.colors_delta[i]*position)) + }); + this.element.style.backgroundColor = "#" + + colors[0].toColorPart() + colors[1].toColorPart() + colors[2].toColorPart(); + }, + finish: function() { + this.element.style.backgroundColor = this.options.restorecolor; + this.element.style.backgroundImage = this.oldBgImage; + } +}); + +Effect.ScrollTo = Class.create(); +Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + this.start(arguments[1] || {}); + }, + setup: function() { + Position.prepare(); + var offsets = Position.cumulativeOffset(this.element); + var max = window.innerHeight ? + window.height - window.innerHeight : + document.body.scrollHeight - + (document.documentElement.clientHeight ? + document.documentElement.clientHeight : document.body.clientHeight); + this.scrollStart = Position.deltaY; + this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; + }, + update: function(position) { + Position.prepare(); + window.scrollTo(Position.deltaX, + this.scrollStart + (position*this.delta)); + } +}); + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + var oldOpacity = Element.getInlineOpacity(element); + var options = Object.extend({ + from: Element.getOpacity(element) || 1.0, + to: 0.0, + afterFinishInternal: function(effect) + { if (effect.options.to == 0) { + Element.hide(effect.element); + Element.setInlineOpacity(effect.element, oldOpacity); + } + } + }, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Appear = function(element) { + var options = Object.extend({ + from: (Element.getStyle(element, "display") == "none" ? 0.0 : Element.getOpacity(element) || 0.0), + to: 1.0, + beforeSetup: function(effect) + { Element.setOpacity(effect.element, effect.options.from); + Element.show(effect.element); } + }, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Puff = function(element) { + element = $(element); + var oldOpacity = Element.getInlineOpacity(element); + var oldPosition = element.style.position; + return new Effect.Parallel( + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, + beforeSetupInternal: function(effect) + { effect.effects[0].element.style.position = 'absolute'; }, + afterFinishInternal: function(effect) + { Element.hide(effect.effects[0].element); + effect.effects[0].element.style.position = oldPosition; + Element.setInlineOpacity(effect.effects[0].element, oldOpacity); } + }, arguments[1] || {}) + ); +} + +Effect.BlindUp = function(element) { + element = $(element); + Element.makeClipping(element); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) + { + Element.hide(effect.element); + Element.undoClipping(effect.element); + } + }, arguments[1] || {}) + ); +} + +Effect.BlindDown = function(element) { + element = $(element); + var oldHeight = element.style.height; + var elementDimensions = Element.getDimensions(element); + return new Effect.Scale(element, 100, + Object.extend({ scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + Element.makeClipping(effect.element); + effect.element.style.height = "0px"; + Element.show(effect.element); + }, + afterFinishInternal: function(effect) { + Element.undoClipping(effect.element); + effect.element.style.height = oldHeight; + } + }, arguments[1] || {}) + ); +} + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = Element.getInlineOpacity(element); + return new Effect.Appear(element, { + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + Element.makePositioned(effect.element); + Element.makeClipping(effect.element); + }, + afterFinishInternal: function(effect) { + Element.hide(effect.element); + Element.undoClipping(effect.element); + Element.undoPositioned(effect.element); + Element.setInlineOpacity(effect.element, oldOpacity); + } + }) + } + }); +} + +Effect.DropOut = function(element) { + element = $(element); + var oldTop = element.style.top; + var oldLeft = element.style.left; + var oldOpacity = Element.getInlineOpacity(element); + return new Effect.Parallel( + [ new Effect.MoveBy(element, 100, 0, { sync: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 }) ], + Object.extend( + { duration: 0.5, + beforeSetup: function(effect) { + Element.makePositioned(effect.effects[0].element); }, + afterFinishInternal: function(effect) { + Element.hide(effect.effects[0].element); + Element.undoPositioned(effect.effects[0].element); + effect.effects[0].element.style.left = oldLeft; + effect.effects[0].element.style.top = oldTop; + Element.setInlineOpacity(effect.effects[0].element, oldOpacity); } + }, arguments[1] || {})); +} + +Effect.Shake = function(element) { + element = $(element); + var oldTop = element.style.top; + var oldLeft = element.style.left; + return new Effect.MoveBy(element, 0, 20, + { duration: 0.05, afterFinishInternal: function(effect) { + new Effect.MoveBy(effect.element, 0, -40, + { duration: 0.1, afterFinishInternal: function(effect) { + new Effect.MoveBy(effect.element, 0, 40, + { duration: 0.1, afterFinishInternal: function(effect) { + new Effect.MoveBy(effect.element, 0, -40, + { duration: 0.1, afterFinishInternal: function(effect) { + new Effect.MoveBy(effect.element, 0, 40, + { duration: 0.1, afterFinishInternal: function(effect) { + new Effect.MoveBy(effect.element, 0, -20, + { duration: 0.05, afterFinishInternal: function(effect) { + Element.undoPositioned(effect.element); + effect.element.style.left = oldLeft; + effect.element.style.top = oldTop; + }}) }}) }}) }}) }}) }}); +} + +Effect.SlideDown = function(element) { + element = $(element); + Element.cleanWhitespace(element); + // SlideDown need to have the content of the element wrapped in a container element with fixed height! + var oldInnerBottom = element.firstChild.style.bottom; + var elementDimensions = Element.getDimensions(element); + return new Effect.Scale(element, 100, + Object.extend({ scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + Element.makePositioned(effect.element.firstChild); + if (window.opera) effect.element.firstChild.style.top = ""; + Element.makeClipping(effect.element); + element.style.height = '0'; + Element.show(element); + }, + afterUpdateInternal: function(effect) { + effect.element.firstChild.style.bottom = + (effect.originalHeight - effect.element.clientHeight) + 'px'; }, + afterFinishInternal: function(effect) { + Element.undoClipping(effect.element); + Element.undoPositioned(effect.element.firstChild); + effect.element.firstChild.style.bottom = oldInnerBottom; } + }, arguments[1] || {}) + ); +} + +Effect.SlideUp = function(element) { + element = $(element); + Element.cleanWhitespace(element); + var oldInnerBottom = element.firstChild.style.bottom; + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + restoreAfterFinish: true, + beforeStartInternal: function(effect) { + Element.makePositioned(effect.element.firstChild); + if (window.opera) effect.element.firstChild.style.top = ""; + Element.makeClipping(effect.element); + Element.show(element); + }, + afterUpdateInternal: function(effect) { + effect.element.firstChild.style.bottom = + (effect.originalHeight - effect.element.clientHeight) + 'px'; }, + afterFinishInternal: function(effect) { + Element.hide(effect.element); + Element.undoClipping(effect.element); + Element.undoPositioned(effect.element.firstChild); + effect.element.firstChild.style.bottom = oldInnerBottom; } + }, arguments[1] || {}) + ); +} + +Effect.Squish = function(element) { + // Bug in opera makes the TD containing this element expand for a instance after finish + return new Effect.Scale(element, window.opera ? 1 : 0, + { restoreAfterFinish: true, + beforeSetup: function(effect) { + Element.makeClipping(effect.element); }, + afterFinishInternal: function(effect) { + Element.hide(effect.element); + Element.undoClipping(effect.element); } + }); +} + +Effect.Grow = function(element) { + element = $(element); + var options = arguments[1] || {}; + + var elementDimensions = Element.getDimensions(element); + var originalWidth = elementDimensions.width; + var originalHeight = elementDimensions.height; + var oldTop = element.style.top; + var oldLeft = element.style.left; + var oldHeight = element.style.height; + var oldWidth = element.style.width; + var oldOpacity = Element.getInlineOpacity(element); + + var direction = options.direction || 'center'; + var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; + var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal; + var opacityTransition = options.opacityTransition || Effect.Transitions.full; + + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = originalWidth; + initialMoveY = moveY = 0; + moveX = -originalWidth; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = originalHeight; + moveY = -originalHeight; + break; + case 'bottom-right': + initialMoveX = originalWidth; + initialMoveY = originalHeight; + moveX = -originalWidth; + moveY = -originalHeight; + break; + case 'center': + initialMoveX = originalWidth / 2; + initialMoveY = originalHeight / 2; + moveX = -originalWidth / 2; + moveY = -originalHeight / 2; + break; + } + + return new Effect.MoveBy(element, initialMoveY, initialMoveX, { + duration: 0.01, + beforeSetup: function(effect) { + Element.hide(effect.element); + Element.makeClipping(effect.element); + Element.makePositioned(effect.element); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }), + new Effect.MoveBy(effect.element, moveY, moveX, { sync: true, transition: moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.style.height = 0; + Element.show(effect.effects[0].element); + }, + afterFinishInternal: function(effect) { + var el = effect.effects[0].element; + var els = el.style; + Element.undoClipping(el); + Element.undoPositioned(el); + els.top = oldTop; + els.left = oldLeft; + els.height = oldHeight; + els.width = originalWidth; + Element.setInlineOpacity(el, oldOpacity); + } + }, options) + ) + } + }); +} + +Effect.Shrink = function(element) { + element = $(element); + var options = arguments[1] || {}; + + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + var oldTop = element.style.top; + var oldLeft = element.style.left; + var oldHeight = element.style.height; + var oldWidth = element.style.width; + var oldOpacity = Element.getInlineOpacity(element); + + var direction = options.direction || 'center'; + var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; + var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal; + var opacityTransition = options.opacityTransition || Effect.Transitions.none; + + var moveX, moveY; + + switch (direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = originalWidth; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = originalHeight; + break; + case 'bottom-right': + moveX = originalWidth; + moveY = originalHeight; + break; + case 'center': + moveX = originalWidth / 2; + moveY = originalHeight / 2; + break; + } + + return new Effect.Parallel( + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: opacityTransition }), + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: scaleTransition, restoreAfterFinish: true}), + new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition }) + ], Object.extend({ + beforeStartInternal: function(effect) { + Element.makePositioned(effect.effects[0].element); + Element.makeClipping(effect.effects[0].element); + }, + afterFinishInternal: function(effect) { + var el = effect.effects[0].element; + var els = el.style; + Element.hide(el); + Element.undoClipping(el); + Element.undoPositioned(el); + els.top = oldTop; + els.left = oldLeft; + els.height = oldHeight; + els.width = oldWidth; + Element.setInlineOpacity(el, oldOpacity); + } + }, options) + ); +} + +Effect.Pulsate = function(element) { + element = $(element); + var options = arguments[1] || {}; + var oldOpacity = Element.getInlineOpacity(element); + var transition = options.transition || Effect.Transitions.sinoidal; + var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) }; + reverser.bind(transition); + return new Effect.Opacity(element, + Object.extend(Object.extend({ duration: 3.0, from: 0, + afterFinishInternal: function(effect) { Element.setInlineOpacity(effect.element, oldOpacity); } + }, options), {transition: reverser})); +} + +Effect.Fold = function(element) { + element = $(element); + var originalTop = element.style.top; + var originalLeft = element.style.left; + var originalWidth = element.style.width; + var originalHeight = element.style.height; + Element.makeClipping(element); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + Element.hide(effect.element); + Element.undoClipping(effect.element); + effect.element.style.top = originalTop; + effect.element.style.left = originalLeft; + effect.element.style.width = originalWidth; + effect.element.style.height = originalHeight; + } }); + }}, arguments[1] || {})); +} diff --git a/public/javascripts/prototype.js b/public/javascripts/prototype.js index 37635ccf..5f304380 100644 --- a/public/javascripts/prototype.js +++ b/public/javascripts/prototype.js @@ -1,1038 +1,1724 @@ -/* Prototype JavaScript framework, version 1.3.1 - * (c) 2005 Sam Stephenson - * - * THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff - * against the source tree, available from the Prototype darcs repository. - * - * 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.3.1', - emptyFunction: function() {} -} - -var Class = { - create: function() { - return function() { - this.initialize.apply(this, arguments); - } - } -} - -var Abstract = new Object(); - -Object.extend = function(destination, source) { - for (property in source) { - destination[property] = source[property]; - } - return destination; -} - -Object.prototype.extend = function(object) { - return Object.extend.apply(this, [this, object]); -} - -Function.prototype.bind = function(object) { - var __method = this; - return function() { - __method.apply(object, arguments); - } -} - -Function.prototype.bindAsEventListener = function(object) { - var __method = this; - return function(event) { - __method.call(object, event || window.event); - } -} - -Number.prototype.toColorPart = function() { - var digits = this.toString(16); - if (this < 16) return '0' + digits; - return digits; -} - -var Try = { - these: function() { - var returnValue; - - for (var i = 0; i < arguments.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() { - setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); - }, - - onTimerEvent: function() { - if (!this.currentlyExecuting) { - try { - this.currentlyExecuting = true; - this.callback(); - } finally { - this.currentlyExecuting = false; - } - } - } -} - -/*--------------------------------------------------------------------------*/ - -function $() { - var elements = new Array(); - - for (var i = 0; i < arguments.length; i++) { - var element = arguments[i]; - if (typeof element == 'string') - element = document.getElementById(element); - - if (arguments.length == 1) - return element; - - elements.push(element); - } - - return elements; -} - -if (!Array.prototype.push) { - Array.prototype.push = function() { - var startLength = this.length; - for (var i = 0; i < arguments.length; i++) - this[startLength + i] = arguments[i]; - return this.length; - } -} - -if (!Function.prototype.apply) { - // Based on code from http://www.youngpup.net/ - Function.prototype.apply = function(object, parameters) { - var parameterStrings = new Array(); - if (!object) object = window; - if (!parameters) parameters = new Array(); - - for (var i = 0; i < parameters.length; i++) - parameterStrings[i] = 'parameters[' + i + ']'; - - object.__apply__ = this; - var result = eval('object.__apply__(' + - parameterStrings[i].join(', ') + ')'); - object.__apply__ = null; - - return result; - } -} - -String.prototype.extend({ - stripTags: function() { - return this.replace(/<\/?[^>]+>/gi, ''); - }, - - 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].nodeValue; - } -}); - -var Ajax = { - getTransport: function() { - return Try.these( - function() {return new ActiveXObject('Msxml2.XMLHTTP')}, - function() {return new ActiveXObject('Microsoft.XMLHTTP')}, - function() {return new XMLHttpRequest()} - ) || false; - } -} - -Ajax.Base = function() {}; -Ajax.Base.prototype = { - setOptions: function(options) { - this.options = { - method: 'post', - asynchronous: true, - parameters: '' - }.extend(options || {}); - }, - - responseIsSuccess: function() { - return this.transport.status == undefined - || this.transport.status == 0 - || (this.transport.status >= 200 && this.transport.status < 300); - }, - - responseIsFailure: function() { - return !this.responseIsSuccess(); - } -} - -Ajax.Request = Class.create(); -Ajax.Request.Events = - ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; - -Ajax.Request.prototype = (new Ajax.Base()).extend({ - initialize: function(url, options) { - this.transport = Ajax.getTransport(); - this.setOptions(options); - this.request(url); - }, - - request: function(url) { - var parameters = this.options.parameters || ''; - if (parameters.length > 0) parameters += '&_='; - - try { - if (this.options.method == 'get') - url += '?' + parameters; - - this.transport.open(this.options.method, url, - this.options.asynchronous); - - if (this.options.asynchronous) { - this.transport.onreadystatechange = this.onStateChange.bind(this); - setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); - } - - this.setRequestHeaders(); - - var body = this.options.postBody ? this.options.postBody : parameters; - this.transport.send(this.options.method == 'post' ? body : null); - - } catch (e) { - } - }, - - setRequestHeaders: function() { - var requestHeaders = - ['X-Requested-With', 'XMLHttpRequest', - 'X-Prototype-Version', Prototype.Version]; - - if (this.options.method == 'post') { - requestHeaders.push('Content-type', - 'application/x-www-form-urlencoded'); - - /* Force "Connection: close" for Mozilla browsers to work around - * a bug where XMLHttpReqeuest sends an incorrect Content-length - * header. See Mozilla Bugzilla #246651. - */ - if (this.transport.overrideMimeType) - requestHeaders.push('Connection', 'close'); - } - - if (this.options.requestHeaders) - requestHeaders.push.apply(requestHeaders, this.options.requestHeaders); - - for (var i = 0; i < requestHeaders.length; i += 2) - this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]); - }, - - onStateChange: function() { - var readyState = this.transport.readyState; - if (readyState != 1) - this.respondToReadyState(this.transport.readyState); - }, - - respondToReadyState: function(readyState) { - var event = Ajax.Request.Events[readyState]; - - if (event == 'Complete') - (this.options['on' + this.transport.status] - || this.options['on' + this.responseIsSuccess() ? 'Success' : 'Failure'] - || Prototype.emptyFunction)(this.transport); - - (this.options['on' + event] || Prototype.emptyFunction)(this.transport); - - /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ - if (event == 'Complete') - this.transport.onreadystatechange = Prototype.emptyFunction; - } -}); - -Ajax.Updater = Class.create(); -Ajax.Updater.ScriptFragment = '(?:)((\n|.)*?)(?:<\/script>)'; - -Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({ - initialize: function(container, url, options) { - this.containers = { - success: container.success ? $(container.success) : $(container), - failure: 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() { - this.updateContent(); - onComplete(this.transport); - }).bind(this); - - this.request(url); - }, - - updateContent: function() { - var receiver = this.responseIsSuccess() ? - this.containers.success : this.containers.failure; - - var match = new RegExp(Ajax.Updater.ScriptFragment, 'img'); - var response = this.transport.responseText.replace(match, ''); - var scripts = this.transport.responseText.match(match); - - if (receiver) { - if (this.options.insertion) { - new this.options.insertion(receiver, response); - } else { - receiver.innerHTML = response; - } - } - - if (this.responseIsSuccess()) { - if (this.onComplete) - setTimeout((function() {this.onComplete( - this.transport)}).bind(this), 10); - } - - if (this.options.evalScripts && scripts) { - match = new RegExp(Ajax.Updater.ScriptFragment, 'im'); - setTimeout((function() { - for (var i = 0; i < scripts.length; i++) - eval(scripts[i].match(match)[1]); - }).bind(this), 10); - } - } -}); - -Ajax.PeriodicalUpdater = Class.create(); -Ajax.PeriodicalUpdater.prototype = (new Ajax.Base()).extend({ - initialize: function(container, url, options) { - this.setOptions(options); - this.onComplete = this.options.onComplete; - - this.frequency = (this.options.frequency || 2); - this.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.onComplete = undefined; - clearTimeout(this.timer); - (this.onComplete || Ajax.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); - } -}); - -document.getElementsByClassName = function(className) { - var children = document.getElementsByTagName('*') || document.all; - var elements = new Array(); - - for (var i = 0; i < children.length; i++) { - var child = children[i]; - var classNames = child.className.split(' '); - for (var j = 0; j < classNames.length; j++) { - if (classNames[j] == className) { - elements.push(child); - break; - } - } - } - - return elements; -} - -/*--------------------------------------------------------------------------*/ - -if (!window.Element) { - var Element = new Object(); -} - -Object.extend(Element, { - toggle: function() { - for (var i = 0; i < arguments.length; i++) { - var element = $(arguments[i]); - element.style.display = - (element.style.display == 'none' ? '' : 'none'); - } - }, - - hide: function() { - for (var i = 0; i < arguments.length; i++) { - var element = $(arguments[i]); - element.style.display = 'none'; - } - }, - - show: function() { - for (var i = 0; i < arguments.length; i++) { - var element = $(arguments[i]); - element.style.display = ''; - } - }, - - remove: function(element) { - element = $(element); - element.parentNode.removeChild(element); - }, - - getHeight: function(element) { - element = $(element); - return element.offsetHeight; - }, - - hasClassName: function(element, className) { - element = $(element); - if (!element) - return; - var a = element.className.split(' '); - for (var i = 0; i < a.length; i++) { - if (a[i] == className) - return true; - } - return false; - }, - - addClassName: function(element, className) { - element = $(element); - Element.removeClassName(element, className); - element.className += ' ' + className; - }, - - removeClassName: function(element, className) { - element = $(element); - if (!element) - return; - var newClassName = ''; - var a = element.className.split(' '); - for (var i = 0; i < a.length; i++) { - if (a[i] != className) { - if (i > 0) - newClassName += ' '; - newClassName += a[i]; - } - } - element.className = newClassName; - }, - - // removes whitespace-only text node children - cleanWhitespace: function(element) { - var element = $(element); - for (var i = 0; i < element.childNodes.length; i++) { - var node = element.childNodes[i]; - if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) - Element.remove(node); - } - } -}); - -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; - - if (this.adjacency && this.element.insertAdjacentHTML) { - this.element.insertAdjacentHTML(this.adjacency, this.content); - } else { - this.range = this.element.ownerDocument.createRange(); - if (this.initializeRange) this.initializeRange(); - this.fragment = this.range.createContextualFragment(this.content); - this.insertContent(); - } - } -} - -var Insertion = new Object(); - -Insertion.Before = Class.create(); -Insertion.Before.prototype = (new Abstract.Insertion('beforeBegin')).extend({ - initializeRange: function() { - this.range.setStartBefore(this.element); - }, - - insertContent: function() { - this.element.parentNode.insertBefore(this.fragment, this.element); - } -}); - -Insertion.Top = Class.create(); -Insertion.Top.prototype = (new Abstract.Insertion('afterBegin')).extend({ - initializeRange: function() { - this.range.selectNodeContents(this.element); - this.range.collapse(true); - }, - - insertContent: function() { - this.element.insertBefore(this.fragment, this.element.firstChild); - } -}); - -Insertion.Bottom = Class.create(); -Insertion.Bottom.prototype = (new Abstract.Insertion('beforeEnd')).extend({ - initializeRange: function() { - this.range.selectNodeContents(this.element); - this.range.collapse(this.element); - }, - - insertContent: function() { - this.element.appendChild(this.fragment); - } -}); - -Insertion.After = Class.create(); -Insertion.After.prototype = (new Abstract.Insertion('afterEnd')).extend({ - initializeRange: function() { - this.range.setStartAfter(this.element); - }, - - insertContent: function() { - this.element.parentNode.insertBefore(this.fragment, - this.element.nextSibling); - } -}); - -var Field = { - clear: function() { - for (var i = 0; i < arguments.length; i++) - $(arguments[i]).value = ''; - }, - - focus: function(element) { - $(element).focus(); - }, - - present: function() { - for (var i = 0; i < arguments.length; i++) - if ($(arguments[i]).value == '') return false; - return true; - }, - - select: function(element) { - $(element).select(); - }, - - activate: function(element) { - $(element).focus(); - $(element).select(); - } -} - -/*--------------------------------------------------------------------------*/ - -var Form = { - serialize: function(form) { - var elements = Form.getElements($(form)); - var queryComponents = new Array(); - - for (var i = 0; i < elements.length; i++) { - var queryComponent = Form.Element.serialize(elements[i]); - if (queryComponent) - queryComponents.push(queryComponent); - } - - return queryComponents.join('&'); - }, - - getElements: function(form) { - var form = $(form); - var elements = new Array(); - - for (tagName in Form.Element.Serializers) { - var tagElements = form.getElementsByTagName(tagName); - for (var j = 0; j < tagElements.length; j++) - elements.push(tagElements[j]); - } - return elements; - }, - - getInputs: function(form, typeName, name) { - var form = $(form); - var inputs = form.getElementsByTagName('input'); - - if (!typeName && !name) - return inputs; - - var matchingInputs = new Array(); - for (var i = 0; i < inputs.length; i++) { - var input = inputs[i]; - if ((typeName && input.type != typeName) || - (name && input.name != name)) - continue; - matchingInputs.push(input); - } - - return matchingInputs; - }, - - disable: function(form) { - var elements = Form.getElements(form); - for (var i = 0; i < elements.length; i++) { - var element = elements[i]; - element.blur(); - element.disabled = 'true'; - } - }, - - enable: function(form) { - var elements = Form.getElements(form); - for (var i = 0; i < elements.length; i++) { - var element = elements[i]; - element.disabled = ''; - } - }, - - focusFirstElement: function(form) { - var form = $(form); - var elements = Form.getElements(form); - for (var i = 0; i < elements.length; i++) { - var element = elements[i]; - if (element.type != 'hidden' && !element.disabled) { - Field.activate(element); - break; - } - } - }, - - reset: function(form) { - $(form).reset(); - } -} - -Form.Element = { - serialize: function(element) { - var element = $(element); - var method = element.tagName.toLowerCase(); - var parameter = Form.Element.Serializers[method](element); - - if (parameter) - return encodeURIComponent(parameter[0]) + '=' + - encodeURIComponent(parameter[1]); - }, - - getValue: function(element) { - var element = $(element); - var method = element.tagName.toLowerCase(); - var parameter = Form.Element.Serializers[method](element); - - if (parameter) - return parameter[1]; - } -} - -Form.Element.Serializers = { - input: function(element) { - switch (element.type.toLowerCase()) { - case 'submit': - case 'hidden': - case 'password': - case 'text': - return Form.Element.Serializers.textarea(element); - case 'checkbox': - case 'radio': - return Form.Element.Serializers.inputSelector(element); - } - return false; - }, - - inputSelector: function(element) { - if (element.checked) - return [element.name, element.value]; - }, - - textarea: function(element) { - return [element.name, element.value]; - }, - - select: function(element) { - var value = ''; - if (element.type == 'select-one') { - var index = element.selectedIndex; - if (index >= 0) - value = element.options[index].value || element.options[index].text; - } else { - value = new Array(); - for (var i = 0; i < element.length; i++) { - var opt = element.options[i]; - if (opt.selected) - value.push(opt.value || opt.text); - } - } - return [element.name, value]; - } -} - -/*--------------------------------------------------------------------------*/ - -var $F = Form.Element.getValue; - -/*--------------------------------------------------------------------------*/ - -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(); - if (this.lastValue != value) { - this.callback(this.element, value); - this.lastValue = value; - } - } -} - -Form.Element.Observer = Class.create(); -Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({ - getValue: function() { - return Form.Element.getValue(this.element); - } -}); - -Form.Observer = Class.create(); -Form.Observer.prototype = (new Abstract.TimedObserver()).extend({ - 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() { - var elements = Form.getElements(this.element); - for (var i = 0; i < elements.length; i++) - this.registerCallback(elements[i]); - }, - - registerCallback: function(element) { - if (element.type) { - switch (element.type.toLowerCase()) { - case 'checkbox': - case 'radio': - element.target = this; - element.prev_onclick = element.onclick || Prototype.emptyFunction; - element.onclick = function() { - this.prev_onclick(); - this.target.onElementEvent(); - } - break; - case 'password': - case 'text': - case 'textarea': - case 'select-one': - case 'select-multiple': - element.target = this; - element.prev_onchange = element.onchange || Prototype.emptyFunction; - element.onchange = function() { - this.prev_onchange(); - this.target.onElementEvent(); - } - break; - } - } - } -} - -Form.Element.EventObserver = Class.create(); -Form.Element.EventObserver.prototype = (new Abstract.EventObserver()).extend({ - getValue: function() { - return Form.Element.getValue(this.element); - } -}); - -Form.EventObserver = Class.create(); -Form.EventObserver.prototype = (new Abstract.EventObserver()).extend({ - 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, - - 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; - } - }, - - // 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; i < Event.observers.length; i++) { - Event.stopObserving.apply(this, Event.observers[i]); - Event.observers[i][0] = null; - } - Event.observers = false; - }, - - observe: function(element, name, observer, useCapture) { - var element = $(element); - useCapture = useCapture || false; - - if (name == 'keypress' && - ((navigator.appVersion.indexOf('AppleWebKit') > 0) - || element.attachEvent)) - name = 'keydown'; - - this._observeAndCache(element, name, observer, useCapture); - }, - - stopObserving: function(element, name, observer, useCapture) { - var element = $(element); - useCapture = useCapture || false; - - if (name == 'keypress' && - ((navigator.appVersion.indexOf('AppleWebKit') > 0) - || element.detachEvent)) - name = 'keydown'; - - if (element.removeEventListener) { - element.removeEventListener(name, observer, useCapture); - } else if (element.detachEvent) { - element.detachEvent('on' + name, observer); - } - } -}); - -/* prevent memory leaks in IE */ -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]; - }, - - // 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; - }, - - clone: function(source, target) { - source = $(source); - target = $(target); - target.style.position = 'absolute'; - var offsets = this.cumulativeOffset(source); - target.style.top = offsets[1] + 'px'; - target.style.left = offsets[0] + 'px'; - target.style.width = source.offsetWidth + 'px'; - target.style.height = source.offsetHeight + 'px'; - } -} +/* Prototype JavaScript framework, version 1.4.0_rc0 + * (c) 2005 Sam Stephenson + * + * THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff + * against the source tree, available from the Prototype darcs repository. + * + * 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.4.0_rc0', + + 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 (property in source) { + destination[property] = source[property]; + } + return destination; +} + +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; + } +} + +Function.prototype.bind = function(object) { + var __method = this; + return function() { + return __method.apply(object, arguments); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var __method = this; + return function(event) { + return __method.call(object, event || window.event); + } +} + +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; i < arguments.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() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(); + } finally { + this.currentlyExecuting = false; + } + } + } +} + +/*--------------------------------------------------------------------------*/ + +function $() { + var elements = new Array(); + + for (var i = 0; i < arguments.length; i++) { + var element = arguments[i]; + if (typeof element == 'string') + element = document.getElementById(element); + + if (arguments.length == 1) + return element; + + elements.push(element); + } + + return elements; +} +Object.extend(String.prototype, { + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + 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[0].nodeValue : ''; + }, + + toQueryParams: function() { + var pairs = this.match(/^\??(.*)$/)[1].split('&'); + return pairs.inject({}, function(params, pairString) { + var pair = pairString.split('='); + params[pair[0]] = pair[1]; + return params; + }); + }, + + toArray: function() { + return this.split(''); + }, + + camelize: function() { + var oStringList = this.split('-'); + if (oStringList.length == 1) return oStringList[0]; + + var camelizedString = this.indexOf('-') == 0 + ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) + : oStringList[0]; + + for (var i = 1, len = oStringList.length; i < len; i++) { + var s = oStringList[i]; + camelizedString += s.charAt(0).toUpperCase() + s.substring(1); + } + + return camelizedString; + }, + + inspect: function() { + return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'"; + } +}); + +String.prototype.parseQuery = String.prototype.toQueryParams; + +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; + } + }, + + all: function(iterator) { + var result = true; + this.each(function(value, index) { + if (!(result &= (iterator || Prototype.K)(value, index))) + throw $break; + }); + return result; + }, + + any: function(iterator) { + var result = true; + 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(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; + }, + + 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.collect(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 (value >= (result || value)) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (value <= (result || value)) + 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.collect(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.collect(Prototype.K); + }, + + 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) { + iterator(value = collections.pluck(index)); + return value; + }); + }, + + 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.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0; i < iterable.length; i++) + results.push(iterable[i]); + return results; + } +} + +Object.extend(Array.prototype, Enumerable); + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0; i < this.length; i++) + iterator(this[i]); + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != undefined || value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(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; i < this.length; i++) + if (this[i] == object) return i; + return false; + }, + + reverse: function() { + var result = []; + for (var i = this.length; i > 0; i--) + result.push(this[i-1]); + return result; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } +}); +var Hash = { + _each: function(iterator) { + for (key in this) { + var value = this[key]; + if (typeof value == 'function') 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($H(this), function(mergedHash, pair) { + mergedHash[pair.key] = pair.value; + return mergedHash; + }); + }, + + toQueryString: function() { + return this.map(function(pair) { + return pair.map(encodeURIComponent).join('='); + }).join('&'); + }, + + inspect: function() { + return '#'; + } +} + +function $H(object) { + var hash = Object.extend({}, object || {}); + Object.extend(hash, Enumerable); + Object.extend(hash, Hash); + return hash; +} +var Range = Class.create(); +Object.extend(Range.prototype, Enumerable); +Object.extend(Range.prototype, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + do { + iterator(value); + value = value.succ(); + } while (this.include(value)); + }, + + 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 Range(start, end, exclusive); +} + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')}, + function() {return new XMLHttpRequest()} + ) || false; + }, + + activeRequestCount: 0 +} + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responderToAdd) { + if (!this.include(responderToAdd)) + this.responders.push(responderToAdd); + }, + + unregister: function(responderToRemove) { + this.responders = this.responders.without(responderToRemove); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (responder[callback] && 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, + parameters: '' + } + Object.extend(this.options, options || {}); + }, + + responseIsSuccess: function() { + return this.transport.status == undefined + || this.transport.status == 0 + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + responseIsFailure: function() { + return !this.responseIsSuccess(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + var parameters = this.options.parameters || ''; + if (parameters.length > 0) parameters += '&_='; + + try { + this.url = url; + if (this.options.method == 'get') + this.url += '?' + parameters; + + Ajax.Responders.dispatch('onCreate', this, this.transport); + + this.transport.open(this.options.method, this.url, + this.options.asynchronous); + + if (this.options.asynchronous) { + this.transport.onreadystatechange = this.onStateChange.bind(this); + setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); + } + + this.setRequestHeaders(); + + var body = this.options.postBody ? this.options.postBody : parameters; + this.transport.send(this.options.method == 'post' ? body : null); + + } catch (e) { + } + }, + + setRequestHeaders: function() { + var requestHeaders = + ['X-Requested-With', 'XMLHttpRequest', + 'X-Prototype-Version', Prototype.Version]; + + if (this.options.method == 'post') { + requestHeaders.push('Content-type', + 'application/x-www-form-urlencoded'); + + /* Force "Connection: close" for Mozilla browsers to work around + * a bug where XMLHttpReqeuest sends an incorrect Content-length + * header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType) + requestHeaders.push('Connection', 'close'); + } + + if (this.options.requestHeaders) + requestHeaders.push.apply(requestHeaders, this.options.requestHeaders); + + for (var i = 0; i < requestHeaders.length; i += 2) + this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]); + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState != 1) + this.respondToReadyState(this.transport.readyState); + }, + + evalJSON: function() { + try { + var json = this.transport.getResponseHeader('X-JSON'), object; + object = eval(json); + return object; + } catch (e) { + } + }, + + respondToReadyState: function(readyState) { + var event = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + + if (event == 'Complete') + (this.options['on' + this.transport.status] + || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + + (this.options['on' + event] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + event, this, transport, json); + + /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ + if (event == 'Complete') + this.transport.onreadystatechange = Prototype.emptyFunction; + } +}); + +Ajax.Updater = Class.create(); +Ajax.Updater.ScriptFragment = '(?:)((\n|.)*?)(?:<\/script>)'; + +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { + initialize: function(container, url, options) { + this.containers = { + success: container.success ? $(container.success) : $(container), + failure: 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, object) { + this.updateContent(); + onComplete(transport, object); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.responseIsSuccess() ? + this.containers.success : this.containers.failure; + + var match = new RegExp(Ajax.Updater.ScriptFragment, 'img'); + var response = this.transport.responseText.replace(match, ''); + var scripts = this.transport.responseText.match(match); + + if (receiver) { + if (this.options.insertion) { + new this.options.insertion(receiver, response); + } else { + receiver.innerHTML = response; + } + } + + if (this.responseIsSuccess()) { + if (this.onComplete) + setTimeout(this.onComplete.bind(this), 10); + } + + if (this.options.evalScripts && scripts) { + match = new RegExp(Ajax.Updater.ScriptFragment, 'im'); + setTimeout((function() { + for (var i = 0; i < scripts.length; i++) + eval(scripts[i].match(match)[1]); + }).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.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); + } +}); +document.getElementsByClassName = function(className, parentElement) { + var children = (document.body || $(parentElement)).getElementsByTagName('*'); + return $A(children).inject([], function(elements, child) { + if (Element.hasClassName(child, className)) + elements.push(child); + return elements; + }); +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) { + var Element = new Object(); +} + +Object.extend(Element, { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + Element[Element.visible(element) ? 'hide' : 'show'](element); + } + }, + + hide: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = 'none'; + } + }, + + show: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = ''; + } + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + }, + + getHeight: function(element) { + element = $(element); + return element.offsetHeight; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).include(className); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).add(className); + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).remove(className); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + for (var i = 0; i < element.childNodes.length; i++) { + var node = element.childNodes[i]; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + Element.remove(node); + } + }, + + empty: function(element) { + return $(element).innerHTML.match(/^\s*$/); + }, + + scrollTo: function(element) { + element = $(element); + var x = element.x ? element.x : element.offsetLeft, + y = element.y ? element.y : element.offsetTop; + window.scrollTo(x, y); + }, + + getStyle: function(element, style) { + element = $(element); + var value = element.style[style.camelize()]; + if (!value) { + if (document.defaultView && document.defaultView.getComputedStyle) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css.getPropertyValue(style) : null; + } else if (element.currentStyle) { + value = element.currentStyle[style.camelize()]; + } + } + + if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) + if (Element.getStyle(element, 'position') == 'static') value = 'auto'; + + return value == 'auto' ? null : value; + }, + + getDimensions: function(element) { + element = $(element); + if (Element.getStyle(element, 'display') != 'none') + 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; + els.visibility = 'hidden'; + els.position = 'absolute'; + els.display = ''; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = 'none'; + 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; + } + } + }, + + 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 = ''; + } + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return; + element._overflow = element.style.overflow; + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element.style.overflow = 'hidden'; + }, + + undoClipping: function(element) { + element = $(element); + if (element._overflow) return; + element.style.overflow = element._overflow; + element._overflow = undefined; + } +}); + +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; + + if (this.adjacency && this.element.insertAdjacentHTML) { + try { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } catch (e) { + if (this.element.tagName.toLowerCase() == 'tbody') { + 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)]); + } + }, + + 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().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(this.toArray().concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set(this.select(function(className) { + return className != classNameToRemove; + })); + }, + + toString: function() { + return this.toArray().join(' '); + } +} + +Object.extend(Element.ClassNames.prototype, Enumerable); +var Field = { + clear: function() { + for (var i = 0; i < arguments.length; i++) + $(arguments[i]).value = ''; + }, + + focus: function(element) { + $(element).focus(); + }, + + present: function() { + for (var i = 0; i < arguments.length; i++) + if ($(arguments[i]).value == '') return false; + return true; + }, + + select: function(element) { + $(element).select(); + }, + + activate: function(element) { + $(element).focus(); + $(element).select(); + } +} + +/*--------------------------------------------------------------------------*/ + +var Form = { + serialize: function(form) { + var elements = Form.getElements($(form)); + var queryComponents = new Array(); + + for (var i = 0; i < elements.length; i++) { + var queryComponent = Form.Element.serialize(elements[i]); + if (queryComponent) + queryComponents.push(queryComponent); + } + + return queryComponents.join('&'); + }, + + getElements: function(form) { + var form = $(form); + var elements = new Array(); + + for (tagName in Form.Element.Serializers) { + var tagElements = form.getElementsByTagName(tagName); + for (var j = 0; j < tagElements.length; j++) + elements.push(tagElements[j]); + } + return elements; + }, + + getInputs: function(form, typeName, name) { + var form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) + return inputs; + + var matchingInputs = new Array(); + for (var i = 0; i < inputs.length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || + (name && input.name != name)) + continue; + matchingInputs.push(input); + } + + return matchingInputs; + }, + + disable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.blur(); + element.disabled = 'true'; + } + }, + + enable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.disabled = ''; + } + }, + + focusFirstElement: function(form) { + var form = $(form); + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + if (element.type != 'hidden' && !element.disabled) { + Field.activate(element); + break; + } + } + }, + + reset: function(form) { + $(form).reset(); + } +} + +Form.Element = { + serialize: function(element) { + var element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) + return encodeURIComponent(parameter[0]) + '=' + + encodeURIComponent(parameter[1]); + }, + + getValue: function(element) { + var element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) + return parameter[1]; + } +} + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'submit': + case 'hidden': + case 'password': + case 'text': + return Form.Element.Serializers.textarea(element); + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + } + return false; + }, + + inputSelector: function(element) { + if (element.checked) + return [element.name, element.value]; + }, + + textarea: function(element) { + return [element.name, element.value]; + }, + + select: function(element) { + return Form.Element.Serializers[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { + var value = '', opt, index = element.selectedIndex; + if (index >= 0) { + opt = element.options[index]; + value = opt.value; + if (!value && !('value' in opt)) + value = opt.text; + } + return [element.name, value]; + }, + + selectMany: function(element) { + var value = new Array(); + for (var i = 0; i < element.length; i++) { + var opt = element.options[i]; + if (opt.selected) { + var optValue = opt.value; + if (!optValue && !('value' in opt)) + optValue = opt.text; + value.push(optValue); + } + } + return [element.name, value]; + } +} + +/*--------------------------------------------------------------------------*/ + +var $F = Form.Element.getValue; + +/*--------------------------------------------------------------------------*/ + +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(); + if (this.lastValue != value) { + 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() { + var elements = Form.getElements(this.element); + for (var i = 0; i < elements.length; i++) + this.registerCallback(elements[i]); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + element.target = this; + element.prev_onclick = element.onclick || Prototype.emptyFunction; + element.onclick = function() { + this.prev_onclick(); + this.target.onElementEvent(); + } + break; + case 'password': + case 'text': + case 'textarea': + case 'select-one': + case 'select-multiple': + element.target = this; + element.prev_onchange = element.onchange || Prototype.emptyFunction; + element.onchange = function() { + this.prev_onchange(); + this.target.onElementEvent(); + } + 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, + + 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; i < Event.observers.length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + var element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.attachEvent)) + name = 'keydown'; + + this._observeAndCache(element, name, observer, useCapture); + }, + + stopObserving: function(element, name, observer, useCapture) { + var 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) { + element.detachEvent('on' + name, observer); + } + } +}); + +/* prevent memory leaks in IE */ +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) { + 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; + }, + + clone: function(source, target) { + source = $(source); + target = $(target); + target.style.position = 'absolute'; + var offsets = this.cumulativeOffset(source); + target.style.top = offsets[1] + 'px'; + target.style.left = offsets[0] + 'px'; + target.style.width = source.offsetWidth + 'px'; + target.style.height = source.offsetHeight + 'px'; + }, + + 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 { + 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]; + } +} \ No newline at end of file diff --git a/public/javascripts/scriptaculous.js b/public/javascripts/scriptaculous.js new file mode 100644 index 00000000..0941eafa --- /dev/null +++ b/public/javascripts/scriptaculous.js @@ -0,0 +1,47 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +var Scriptaculous = { + Version: '1.5_rc3', + require: function(libraryName) { + // inserting via DOM fails in Safari 2.0, so brute force approach + document.write(''); + }, + load: function() { + if((typeof Prototype=='undefined') || + parseFloat(Prototype.Version.split(".")[0] + "." + + Prototype.Version.split(".")[1]) < 1.4) + throw("script.aculo.us requires the Prototype JavaScript framework >= 1.4.0"); + var scriptTags = document.getElementsByTagName("script"); + for(var i=0;i this.maximum) sliderValue = this.maximum; + if(sliderValue < this.minimum) sliderValue = this.minimum; + var offsetDiff = (sliderValue - (this.value||this.minimum)) * this.increment; + + if(this.isVertical()){ + this.setCurrentTop(offsetDiff + this.currentTop()); + } else { + this.setCurrentLeft(offsetDiff + this.currentLeft()); + } + this.value = sliderValue; + this.updateFinished(); + }, + minimumOffset: function(){ + return(this.isVertical() ? + this.trackTop() + this.alignY : + this.trackLeft() + this.alignX); + }, + maximumOffset: function(){ + return(this.isVertical() ? + this.trackTop() + this.alignY + (this.maximum - this.minimum) * this.increment : + this.trackLeft() + this.alignX + (this.maximum - this.minimum) * this.increment); + }, + isVertical: function(){ + return (this.axis == 'vertical'); + }, + startDrag: function(event) { + if(Event.isLeftClick(event)) { + if(!this.disabled){ + this.active = true; + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var offsets = Position.cumulativeOffset(this.handle); + this.offsetX = (pointer[0] - offsets[0]); + this.offsetY = (pointer[1] - offsets[1]); + this.originalLeft = this.currentLeft(); + this.originalTop = this.currentTop(); + } + Event.stop(event); + } + }, + update: function(event) { + if(this.active) { + if(!this.dragging) { + var style = this.handle.style; + this.dragging = true; + if(style.position=="") style.position = "relative"; + style.zIndex = this.options.zindex; + } + this.draw(event); + // fix AppleWebKit rendering + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + Event.stop(event); + } + }, + draw: function(event) { + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var offsets = Position.cumulativeOffset(this.handle); + + offsets[0] -= this.currentLeft(); + offsets[1] -= this.currentTop(); + + // Adjust for the pointer's position on the handle + pointer[0] -= this.offsetX; + pointer[1] -= this.offsetY; + var style = this.handle.style; + + if(this.isVertical()){ + if(pointer[1] > this.maximumOffset()) + pointer[1] = this.maximumOffset(); + if(pointer[1] < this.minimumOffset()) + pointer[1] = this.minimumOffset(); + + // Increment by values + if(this.values){ + this.value = this.getNearestValue(Math.round((pointer[1] - this.minimumOffset()) / this.increment) + this.minimum); + pointer[1] = this.trackTop() + this.alignY + (this.value - this.minimum) * this.increment; + } else { + this.value = Math.round((pointer[1] - this.minimumOffset()) / this.increment) + this.minimum; + } + style.top = pointer[1] - offsets[1] + "px"; + } else { + if(pointer[0] > this.maximumOffset()) pointer[0] = this.maximumOffset(); + if(pointer[0] < this.minimumOffset()) pointer[0] = this.minimumOffset(); + // Increment by values + if(this.values){ + this.value = this.getNearestValue(Math.round((pointer[0] - this.minimumOffset()) / this.increment) + this.minimum); + pointer[0] = this.trackLeft() + this.alignX + (this.value - this.minimum) * this.increment; + } else { + this.value = Math.round((pointer[0] - this.minimumOffset()) / this.increment) + this.minimum; + } + style.left = (pointer[0] - offsets[0]) + "px"; + } + if(this.options.onSlide) this.options.onSlide(this.value); + }, + endDrag: function(event) { + if(this.active && this.dragging) { + this.finishDrag(event, true); + Event.stop(event); + } + this.active = false; + this.dragging = false; + }, + finishDrag: function(event, success) { + this.active = false; + this.dragging = false; + this.handle.style.zIndex = this.originalZ; + this.originalLeft = this.currentLeft(); + this.originalTop = this.currentTop(); + this.updateFinished(); + }, + updateFinished: function() { + if(this.options.onChange) this.options.onChange(this.value); + }, + keyPress: function(event) { + if(this.active && !this.disabled) { + switch(event.keyCode) { + case Event.KEY_ESC: + this.finishDrag(event, false); + Event.stop(event); + break; + } + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + } + } +} diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 00000000..4ab9e89f --- /dev/null +++ b/public/robots.txt @@ -0,0 +1 @@ +# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file \ No newline at end of file From 35b77f6440b7b02331d38cfa093d527ff0ed28b7 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Wed, 2 Nov 2005 09:04:53 +0000 Subject: [PATCH 80/84] A bit of spit and polish --- app/controllers/application.rb | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/app/controllers/application.rb b/app/controllers/application.rb index 9a601b72..b3789604 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -28,7 +28,7 @@ class ApplicationController < ActionController::Base end def check_authorization - if in_a_web? and needs_authorization?(@action_name) and not authorized? and + if in_a_web? and authorization_needed? and not authorized? and redirect_to :controller => 'wiki', :action => 'login', :web => @web_name return false end @@ -98,28 +98,26 @@ class ApplicationController < ActionController::Base def redirect_to_page(page_name = @page_name, web = @web_name) redirect_to :web => web, :controller => 'wiki', :action => 'show', - :id => (page_name || 'HomePage') + :id => (page_name or 'HomePage') end - @@REMEMBER_NOT = ['locked', 'save', 'back', 'file', 'pic', 'import'] def remember_location - if @response.headers['Status'] == '200 OK' - unless @@REMEMBER_NOT.include? action_name or @request.method != :get - @session[:return_to] = @request.request_uri - logger.debug("Session ##{session.object_id}: remembered URL '#{@session[:return_to]}'") - end + if @request.method == :get and + @response.headers['Status'] == '200 OK' and not + %w(locked save back file pic import).include?(action_name) + @session[:return_to] = @request.request_uri + logger.debug "Session ##{session.object_id}: remembered URL '#{@session[:return_to]}'" end end def rescue_action_in_public(exception) - message = <<-EOL + render :status => 500, :text => <<-EOL -

    Internal Error 500

    +

    Internal Error

    An application error occurred while processing your request.

    - + EOL - render_text message, 'Internal Error 500' end def return_to_last_remembered @@ -170,8 +168,8 @@ class ApplicationController < ActionController::Base self.class.wiki end - def needs_authorization?(action) - not %w( login authenticate published rss_with_content rss_with_headlines ).include?(action) + def authorization_needed? + not %w( login authenticate published rss_with_content rss_with_headlines ).include?(action_name) end end From dea8d70c48b85e94c3875b06918c173895605264 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 4 Nov 2005 05:23:34 +0000 Subject: [PATCH 81/84] Improved behavior of JavaScript in the author field [from I2 patch by court3nay] --- app/controllers/application.rb | 30 ++++++++++++------------- app/controllers/file_controller.rb | 18 ++------------- app/controllers/wiki_controller.rb | 4 ++-- app/views/wiki/edit.rhtml | 5 +++-- app/views/wiki/new.rhtml | 4 +++- test/functional/file_controller_test.rb | 6 ++--- 6 files changed, 28 insertions(+), 39 deletions(-) diff --git a/app/controllers/application.rb b/app/controllers/application.rb index b3789604..6fc309b7 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -2,7 +2,7 @@ # Likewise will all the methods added be available for all controllers. class ApplicationController < ActionController::Base - before_filter :connect_to_model, :setup_url_generator, :set_content_type_header, :set_robots_metatag + before_filter :connect_to_model, :check_authorization, :setup_url_generator, :set_content_type_header, :set_robots_metatag after_filter :remember_location, :teardown_url_generator # For injecting a different wiki model implementation. Intended for use in tests @@ -20,15 +20,8 @@ class ApplicationController < ActionController::Base protected - def authorized? - @web.nil? || - @web.password.nil? || - cookies['web_address'] == @web.password || - password_check(@params['password']) - end - def check_authorization - if in_a_web? and authorization_needed? and not authorized? and + if in_a_web? and authorization_needed? and not authorized? redirect_to :controller => 'wiki', :action => 'login', :web => @web_name return false end @@ -41,14 +34,13 @@ class ApplicationController < ActionController::Base if @web_name @web = @wiki.webs[@web_name] if @web.nil? - render_text "Unknown web '#{@web_name}'", '404 Not Found' + render :status => 404, :text => "Unknown web '#{@web_name}'" return false end end @page_name = @file_name = @params['id'] @page = @wiki.read_page(@web_name, @page_name) unless @page_name.nil? @author = cookies['author'] || 'AnonymousCoward' - check_authorization end FILE_TYPES = { @@ -67,10 +59,6 @@ class ApplicationController < ActionController::Base super(file, options) end - def in_a_web? - not @web_name.nil? - end - def password_check(password) if password == @web.password cookies['web_address'] = password @@ -168,8 +156,20 @@ class ApplicationController < ActionController::Base self.class.wiki end + private + + def in_a_web? + not @web_name.nil? + end + def authorization_needed? not %w( login authenticate published rss_with_content rss_with_headlines ).include?(action_name) end + def authorized? + @web.password.nil? or + cookies['web_address'] == @web.password or + password_check(@params['password']) + end + end diff --git a/app/controllers/file_controller.rb b/app/controllers/file_controller.rb index e8356fb5..61e5f000 100644 --- a/app/controllers/file_controller.rb +++ b/app/controllers/file_controller.rb @@ -1,9 +1,4 @@ -require 'fileutils' -require 'application' -require 'instiki_errors' - -# Controller that is responsible for serving files and pictures. -# Disabled in version 0.10 +# Controller responsible for serving files and pictures. class FileController < ApplicationController @@ -46,8 +41,6 @@ class FileController < ApplicationController end def import - return if file_uploads_disabled? - check_authorization if @params['file'] @problems = [] @@ -69,15 +62,8 @@ class FileController < ApplicationController protected def check_allow_uploads - - # TODO enable file uploads again after 0.10 release - unless RAILS_ENV == 'test' - render_text 'File uploads are not ready for general use in Instiki 0.10', '403 Forbidden' - return false - end - unless @web.allow_uploads? - render_text 'File uploads are blocked by the webmaster', '403 Forbidden' + render :status => 403, :text => 'File uploads are blocked by the webmaster' return false end end diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 712c4640..2e7eb3b7 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -217,9 +217,9 @@ class WikiController < ApplicationController end def save - redirect_home if @page_name.nil? - cookies['author'] = { :value => @params['author'], :expires => Time.utc(2030) } + render(:status => 404, :text => 'Undefined page name') and return if @page_name.nil? + cookies['author'] = { :value => @params['author'], :expires => Time.utc(2030) } begin if @page wiki.revise_page(@web_name, @page_name, @params['content'], Time.now, diff --git a/app/views/wiki/edit.rhtml b/app/views/wiki/edit.rhtml index cdf3d5d1..270d5bec 100644 --- a/app/views/wiki/edit.rhtml +++ b/app/views/wiki/edit.rhtml @@ -18,8 +18,9 @@

    as - + <%= text_field_tag :author, @author, + :onfocus => "this.value == 'AnonymousCoward' ? this.value = '' : true;", + :onblur => "this.value == '' ? this.value = 'AnonymousCoward' : true" %> | <%= link_to('Cancel', {:web => @web.address, :action => 'cancel_edit', :id => @page.name}, {:accesskey => 'c'}) diff --git a/app/views/wiki/new.rhtml b/app/views/wiki/new.rhtml index d7202268..e7a73bca 100644 --- a/app/views/wiki/new.rhtml +++ b/app/views/wiki/new.rhtml @@ -18,7 +18,9 @@

    as - + <%= text_field_tag :author, @author, + :onfocus => "this.value == 'AnonymousCoward' ? this.value = '' : true;", + :onblur => "this.value == '' ? this.value = 'AnonymousCoward' : true" %>

    <%= end_form_tag %> diff --git a/test/functional/file_controller_test.rb b/test/functional/file_controller_test.rb index 24fc2125..80b9099e 100755 --- a/test/functional/file_controller_test.rb +++ b/test/functional/file_controller_test.rb @@ -121,12 +121,12 @@ class FileControllerTest < Test::Unit::TestCase def test_uploads_blocking set_web_property :allow_uploads, true - r = process 'file', 'web' => 'wiki1', 'id' => 'filename' + process 'file', 'web' => 'wiki1', 'id' => 'filename' assert_success set_web_property :allow_uploads, false - r = process 'file', 'web' => 'wiki1', 'id' => 'filename' - assert_equal '403 Forbidden', r.headers['Status'] + process 'file', 'web' => 'wiki1', 'id' => 'filename' + assert_response 403 end end From 0c412e4a26c854ab66493c4c4dfdfc0b57e069bb Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 4 Nov 2005 05:26:32 +0000 Subject: [PATCH 82/84] CHANGELOG update. Previous commit also included: reenabled FileController, refactoring of authorization code and some other tweaks --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 6b2dcbda..d8e49998 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,8 @@ SQL-based backend (ActiveRecord) Replaced internal link generator with routing Fixed --daemon option + Upgraded to Rails 0.14.2 + Re-enabled file uploads * 0.10.2: Upgraded to Rails 0.13.1 From 4779a4fa4eefc6aec061f93dfd032844a3a1f6e8 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 4 Nov 2005 05:57:31 +0000 Subject: [PATCH 83/84] Diocumented installation and data migration in README --- README | 53 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/README b/README index 09d29a9f..ae268ea1 100755 --- a/README +++ b/README @@ -4,10 +4,20 @@ Admitted, it's YetAnotherWikiClone[http://c2.com/cgi/wiki?WikiWikiClones], but w on simplicity of installation and running: Step 1. Download - Step 2. Run "instiki" -Step 3. Chuckle... "There's no step three!" (TM) +Here it should say: "Step 3. Chuckle... "There's no step three!" (TM)" +... but this is a beta version that introduces an SQL-based backend, so: + +3. Kill 'instiki' +4. Install SQLite 3 database engine from http://www.sqlite.org/ +5. Install SQLite 3 driver for Ruby from http://sqlite-ruby.rubyforge.org/ +6. Install Rake from http://rake.rubyforge.org/ +7. Execute 'rake db_schema_import create_sessions_table' +8. Make an embarrassed sigh (as I do while writing this) +9. Run 'instiki' again +10. Pat yourself on the shoulder for being such a talented geek +11. At least, there is no step eleven! (TM) You're now running a perfectly suitable wiki on port 2500 that'll present you with one-step setup, followed by a textarea for the home page @@ -38,21 +48,35 @@ gathering. ===Missing: * File attachments -===Install from gem: -* Install rubygems -* Run "gem install instiki" -* Change to a directory where you want Instiki to keep its data files (for example, ~/instiki/) -* Run "instiki" - this will create a "storage" directory (for example, ~/instiki/storage), and start a new Wiki service - -Make sure that you always launch Instiki from the same working directory, or specify the storage directory in runtime parameters, such as: - instiki --storage ~/instiki/storage - ===Command-line options: * Run "instiki --help" ===History: * See CHANGELOG +===Migrating Instiki 0.10.2 storage to Instiki-AR database +1. Install Instiki-AR and check that it works (you should be able to create a web, edit and save a HomePage) +2. Execute + ruby script\import_storage \ + -t /full/path/to/instiki0.10/storage \ + -i /full/path/to/instiki0.10/installation \ + -d sqlite (or mysql, or postgres, depending on what you use) \ + -o instiki_import.sql + for example: + ruby script\import_storage -t c:\instiki-0.10.2\storage\2500 -i c:\instiki-0.10.2 -d sqlite -o instiki_import.sql +3. This will produce instiki_import.sql file in the current working directory. + Open it in a text editor and inspect carefully. +4. Connect to your production database (e.g., 'sqlite3 db\prod.db'), + and have it execute instiki_import.sql (e.g., '.read instiki_import.sql') +5. Execute ruby script\reset_references + (this script parses all pages for crosslinks between them, so it may take a few minutes) +6. Restart Instiki +7. Go over some pages, especially those with a lot of complex markup, and see if anything is broken. + +The most common migration problem is this: +If you open All Pages screen and see a lot of orphaned pages, +you forgot to run ruby script\reset_references after importing the data. + ===Download latest from: * http://rubyforge.org/project/showfiles.php?group_id=186 @@ -63,8 +87,11 @@ Make sure that you always launch Instiki from the same working directory, or spe * same as Ruby's --- -Author:: David Heinemeier Hansson +Authors:: + +Versions 0.1 to 0.9.1:: David Heinemeier Hansson Email:: david@loudthinking.com Weblog:: http://www.loudthinking.com - +From 0.9.2 onwards:: Alexey Verkhovsky +Email:: alex@verk.info From c7295287a4f72dab610398684a186c0407961a1d Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 4 Nov 2005 06:19:10 +0000 Subject: [PATCH 84/84] Extract the inbound links list from page.rhtml and revision.rhtml into a partial --- app/views/wiki/_inbound_links.rhtml | 13 +++++++++++++ app/views/wiki/page.rhtml | 23 +++-------------------- app/views/wiki/revision.rhtml | 13 ++----------- 3 files changed, 18 insertions(+), 31 deletions(-) create mode 100644 app/views/wiki/_inbound_links.rhtml diff --git a/app/views/wiki/_inbound_links.rhtml b/app/views/wiki/_inbound_links.rhtml new file mode 100644 index 00000000..4c5e4e12 --- /dev/null +++ b/app/views/wiki/_inbound_links.rhtml @@ -0,0 +1,13 @@ +<% unless @page.linked_from.empty? %> + + | Linked from: + <%= @page.linked_from.collect { |referring_page| link_to_existing_page referring_page }.join(", ") %> + +<% end %> + +<% unless @page.included_from.empty? %> + + | Included from: + <%= @page.included_from.collect { |referring_page| link_to_existing_page referring_page }.join(", ") %> + +<% end %> diff --git a/app/views/wiki/page.rhtml b/app/views/wiki/page.rhtml index 432f688b..b7292e35 100644 --- a/app/views/wiki/page.rhtml +++ b/app/views/wiki/page.rhtml @@ -71,8 +71,8 @@ | Views: <%= link_to('Print', - {:web => @web.address, :action => 'print', :id => @page.name}, - {:accesskey => 'p', :name => 'view_print'}) %> + { :web => @web.address, :action => 'print', :id => @page.name }, + { :accesskey => 'p', :name => 'view_print' }) %> <% if defined? RedClothForTex and RedClothForTex.available? and @web.markup == :textile %> | <%= link_to 'TeX', {:web => @web.address, :action => 'tex', :id => @page.name}, @@ -83,24 +83,7 @@ <% end %> - <% unless @page.linked_from.empty? %> - - | Linked from: - <%= @page.linked_from.collect { |referring_page| - link_to_existing_page referring_page - }.join(", ") - %> - - <% end %> - - <% if @page.included_from.length > 0 %> - - | Included from: <%= @page.included_from.collect { |referring_page| - link_to_existing_page referring_page - }.join(", ") - %> - - <% end %> + <%= render :partial => 'inbound_links' %>