diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 index eb3489a..38c4877 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,7 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. -# -# If you find yourself ignoring temporary files generated by your text editor -# or operating system, you probably want to add a global ignore instead: -# git config --global core.excludesfile ~/.gitignore_global - -# Ignore bundler config -/.bundle - -# Ignore the default SQLite database. -/db/*.sqlite3 - -# Ignore all logfiles and tempfiles. -/log/*.log -/tmp +log +config/database.yml +.*.sw? +config/site.rb +tmp +mail_temp +config/site.rb diff --git a/AUTHORS.markdown b/AUTHORS.markdown deleted file mode 100755 index 2bdfb37..0000000 --- a/AUTHORS.markdown +++ /dev/null @@ -1,7 +0,0 @@ -## Authors - -* Luben Manolov -* Nick Penkov -* Eugene Korbut -* Emilio Blanco -* Wojciech Todryk diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..18b4288 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,718 @@ +*0.14.2 (RC3)* (October 26th, 2005) + +* Constants set in the development/test/production environment file are set in Object + +* Scaffold generator pays attention to the controller name. #2562 [self@mattmower.com] + +* Include tasks from vendor/plugins/*/tasks in the Rakefile #2545 [Rick Olson] + + +*0.14.1 (RC2)* (October 19th, 2005) + +* Don't clean RAILS_ROOT on windows + +* Remove trailing '/' from RAILS_ROOT [Nicholas Seckar] + +* Upgraded to Active Record 1.12.1 and Action Pack 1.10.1 + + +*0.14.0 (RC1)* (October 16th, 2005) + +* Moved generator folder from RAILS_ROOT/generators to RAILS_ROOT/lib/generators [Tobias Luetke] + +* Fix rake dev and related commands [Nicholas Seckar] + +* The rails command tries to deduce your MySQL socket by running `mysql_config +--socket`. If it fails, default to /path/to/your/mysql.sock + +* Made the rails command use the application name for database names in the tailored database.yml file. Example: "rails ~/projects/blog" will use "blog_development" instead of "rails_development". [Florian Weber] + +* Added Rails framework freezing tasks: freeze_gems (freeze to current gems), freeze_edge (freeze to Rails SVN trunk), unfreeze_rails (float with newest gems on system) + +* Added update_javascripts task which will fetch all the latest js files from your current rails install. Use after updating rails. [Tobias Luetke] + +* Added cleaning of RAILS_ROOT to useless elements such as '../non-dot-dot/'. Provides cleaner backtraces and error messages. [Nicholas Seckar] + +* Made the instantiated/transactional fixtures settings be controlled through Rails::Initializer. Transactional and non-instantiated fixtures are default from now on. [Florian Weber] + +* Support using different database adapters for development and test with ActiveRecord::Base.schema_format = :ruby [Sam Stephenson] + +* Make webrick work with session(:off) + +* Add --version, -v option to the Rails command. Closes #1840. [stancell] + +* Update Prototype to V1.4.0_pre11, script.aculo.us to V1.5_rc3 [2504] and fix the rails generator to include the new .js files [Thomas Fuchs] + +* Make the generator skip a file if it already exists and is identical to the new file. + +* Add experimental plugin support #2335 + +* Made Rakefile aware of new .js files in script.aculo.us [Thomas Fuchs] + +* Make table_name and controller_name in generators honor AR::Base.pluralize_table_names. #1216 #2213 [kazuhiko@fdiary.net] + +* Clearly label functional and unit tests in rake stats output. #2297 [lasse.koskela@gmail.com] + +* Make the migration generator only check files ending in *.rb when calculating the next file name #2317 [Chad Fowler] + +* Added prevention of duplicate migrations from the generator #2240 [fbeausoleil@ftml.net] + +* Add db_schema_dump and db_schema_import rake tasks to work with the new ActiveRecord::SchemaDumper (for dumping a schema to and reading a schema from a ruby file). + +* Reformed all the config/environments/* files to conform to the new Rails::Configuration approach. Fully backwards compatible. + +* Added create_sessions_table, drop_sessions_table, and purge_sessions_table as rake tasks for databases that supports migrations (MySQL, PostgreSQL, SQLite) to get a table for use with CGI::Session::ActiveRecordStore + +* Added dump of schema version to the db_structure_dump task for databases that support migrations #1835 [Rick Olson] + +* Fixed script/profiler for Ruby 1.8.2 #1863 [Rick Olson] + +* Fixed clone_structure_to_test task for SQLite #1864 [jon@burningbush.us] + +* Added -m/--mime-types option to the WEBrick server, so you can specify a Apache-style mime.types file to load #2059 [ask@develooper.com] + +* Added -c/--svn option to the generator that'll add new files and remove destroyed files using svn add/revert/remove as appropriate #2064 [kevin.clark@gmail.com] + +* Added -c/--charset option to WEBrick server, so you can specify a default charset (which without changes is UTF-8) #2084 [wejn@box.cz] + +* Make the default stats task extendable by modifying the STATS_DIRECTORIES constant + +* Allow the selected environment to define RAILS_DEFAULT_LOGGER, and have Rails::Initializer use it if it exists. + +* Moved all the shared tasks from Rakefile into Rails, so that the Rakefile is empty and doesn't require updating. + +* Added Rails::Initializer and Rails::Configuration to abstract all of the common setup out of config/environment.rb (uses config/boot.rb to bootstrap the initializer and paths) + +* Fixed the scaffold generator to fail right away if the database isn't accessible instead of in mid-air #1169 [Chad Fowler] + +* Corrected project-local generator location in scripts.rb #2010 [Michael Schuerig] + +* Don't require the environment just to clear the logs #2093 [Scott Barron] + +* Make the default rakefile read *.rake files from config/tasks (for easy extension of the rakefile by e.g. generators) + +* Only load breakpoint in development mode and when BREAKPOINT_SERVER_PORT is defined. + +* Allow the --toggle-spin switch on process/reaper to be negated + +* Replace render_partial with render :partial in scaffold generator [Nicholas Seckar] + +* Added -w flag to ps in process/reaper #1934 [Scott Barron] + +* Allow ERb in the database.yml file (just like with fixtures), so you can pull out the database configuration in environment variables #1822 [Duane Johnson] + +* Added convenience controls for FCGI processes (especially when managed remotely): spinner, spawner, and reaper. They reside in script/process. More details can be had by calling them with -h/--help. + +* Added load_fixtures task to the Rakefile, which will load all the fixtures into the database for the current environment #1791 [Marcel Molina] + +* Added an empty robots.txt to public/, so that web servers asking for it won't trigger a dynamic call, like favicon.ico #1738 [michael@schubert] + +* Dropped the 'immediate close-down' of FCGI processes since it didn't work consistently and produced bad responses when it didn't. So now a TERM ensures exit after the next request (just as if the process is handling a request when it receives the signal). This means that you'll have to 'nudge' all FCGI processes with a request in order to ensure that they have all reloaded. This can be done by something like ./script/process/repear --nudge 'http://www.myapp.com' --instances 10, which will load the myapp site 10 times (and thus hit all of the 10 FCGI processes once, enough to shut down). + + +*0.13.1* (11 July, 2005) + +* Look for app-specific generators in RAILS_ROOT/generators rather than the clunky old RAILS_ROOT/script/generators. Nobody really uses this feature except for the unit tests, so it's a negligible-impact change. If you want to work with third-party generators, drop them in ~/.rails/generators or simply install gems. + +* Fixed that each request with the WEBrick adapter would open a new database connection #1685 [Sam Stephenson] + +* Added support for SQL Server in the database rake tasks #1652 [ken.barker@gmail.com] Note: osql and scptxfr may need to be installed on your development environment. This involves getting the .exes and a .rll (scptxfr) from a production SQL Server (not developer level SQL Server). Add their location to your Environment PATH and you are all set. + +* Added a VERSION parameter to the migrate task that allows you to do "rake migrate VERSION=34" to migrate to the 34th version traveling up or down depending on the current version + +* Extend Ruby version check to include RUBY_RELEASE_DATE >= '2005-12-25', the final Ruby 1.8.2 release #1674 [court3nay@gmail.com] + +* Improved documentation for environment config files #1625 [court3nay@gmail.com] + + +*0.13.0* (6 July, 2005) + +* Changed the default logging level in config/environment.rb to INFO for production (so SQL statements won't be logged) + +* Added migration generator: ./script/generate migration add_system_settings + +* Added "migrate" as rake task to execute all the pending migrations from db/migrate + +* Fixed that model generator would make fixtures plural, even if ActiveRecord::Base.pluralize_table_names was false #1185 [Marcel Molina] + +* Added a DOCTYPE of HTML transitional to the HTML files generated by Rails #1124 [Michael Koziarski] + +* SIGTERM also gracefully exits dispatch.fcgi. Ignore SIGUSR1 on Windows. + +* Add the option to manually manage garbage collection in the FastCGI dispatcher. Set the number of requests between GC runs in your public/dispatch.fcgi [skaes@web.de] + +* Allow dynamic application reloading for dispatch.fcgi processes by sending a SIGHUP. If the process is currently handling a request, the request will be allowed to complete first. This allows production fcgi's to be reloaded without having to restart them. + +* RailsFCGIHandler (dispatch.fcgi) no longer tries to explicitly flush $stdout (CgiProcess#out always calls flush) + +* Fixed rakefile actions against PostgreSQL when the password is all numeric #1462 [michael@schubert.cx] + +* ActionMailer::Base subclasses are reloaded with the other rails components #1262 + +* Made the WEBrick adapter not use a mutex around action performance if ActionController::Base.allow_concurrency is true (default is false) + +* Fixed that mailer generator generated fixtures/plural while units expected fixtures/singular #1457 [Scott Barron] + +* Added a 'whiny nil' that's aim to ensure that when users pass nil to methods where that isn't appropriate, instead of NoMethodError? and the name of some method used by the framework users will see a message explaining what type of object was expected. Only active in test and development environments by default #1209 [Michael Koziarski] + +* Fixed the test_helper.rb to be safe for requiring controllers from multiple spots, like app/controllers/article_controller.rb and app/controllers/admin/article_controller.rb, without reloading the environment twice #1390 [Nicholas Seckar] + +* Fixed Webrick to escape + characters in URL's the same way that lighttpd and apache do #1397 [Nicholas Seckar] + +* Added -e/--environment option to script/runner #1408 [fbeausoleil@ftml.net] + +* Modernize the scaffold generator to use the simplified render and test methods and to change style from @params["id"] to params[:id]. #1367 + +* Added graceful exit from pressing CTRL-C during the run of the rails command #1150 [Caleb Tennis] + +* Allow graceful exits for dispatch.fcgi processes by sending a SIGUSR1. If the process is currently handling a request, the request will be allowed to complete and then will terminate itself. If a request is not being handled, the process is terminated immediately (via #exit). This basically works like restart graceful on Apache. [Jamis Buck] + +* Made dispatch.fcgi more robust by catching fluke errors and retrying unless its a permanent condition. [Jamis Buck] + +* Added console --profile for profiling an IRB session #1154 [Jeremy Kemper] + +* Changed console_sandbox into console --sandbox #1154 [Jeremy Kemper] + + +*0.12.1* (20th April, 2005) + +* Upgraded to Active Record 1.10.1, Action Pack 1.8.1, Action Mailer 0.9.1, Action Web Service 0.7.1 + + +*0.12.0* (19th April, 2005) + +* Fixed that purge_test_database would use database settings from the development environment when recreating the test database #1122 [rails@cogentdude.com] + +* Added script/benchmarker to easily benchmark one or more statement a number of times from within the environment. Examples: + + # runs the one statement 10 times + script/benchmarker 10 'Person.expensive_method(10)' + + # pits the two statements against each other with 50 runs each + script/benchmarker 50 'Person.expensive_method(10)' 'Person.cheap_method(10)' + +* Added script/profiler to easily profile a single statement from within the environment. Examples: + + script/profiler 'Person.expensive_method(10)' + script/profiler 'Person.expensive_method(10)' 10 # runs the statement 10 times + +* Added Rake target clear_logs that'll truncate all the *.log files in log/ to zero #1079 [Lucas Carlson] + +* Added lazy typing for generate, such that ./script/generate cn == ./script/generate controller and the likes #1051 [k@v2studio.com] + +* Fixed that ownership is brought over in pg_dump during tests for PostgreSQL #1060 [pburleson@gmail.com] + +* Upgraded to Active Record 1.10.0, Action Pack 1.8.0, Action Mailer 0.9.0, Action Web Service 0.7.0, Active Support 1.0.4 + + +*0.11.1* (27th March, 2005) + +* Fixed the dispatch.fcgi use of a logger + +* Upgraded to Active Record 1.9.1, Action Pack 1.7.0, Action Mailer 0.8.1, Action Web Service 0.6.2, Active Support 1.0.3 + + +*0.11.0* (22th March, 2005) + +* Removed SCRIPT_NAME from the WEBrick environment to prevent conflicts with PATH_INFO #896 [Nicholas Seckar] + +* Removed ?$1 from the dispatch.f/cgi redirect line to get rid of 'complete/path/from/request.html' => nil being in the @params now that the ENV["REQUEST_URI"] is used to determine the path #895 [dblack/Nicholas Seckar] + +* Added additional error handling to the FastCGI dispatcher to catch even errors taking down the entire process + +* Improved the generated scaffold code a lot to take advantage of recent Rails developments #882 [Tobias Luetke] + +* Combined the script/environment.rb used for gems and regular files version. If vendor/rails/* has all the frameworks, then files version is used, otherwise gems #878 [Nicholas Seckar] + +* Changed .htaccess to allow dispatch.* to be called from a sub-directory as part of the push with Action Pack to make Rails work on non-vhost setups #826 [Nicholas Seckar/Tobias Luetke] + +* Added script/runner which can be used to run code inside the environment by eval'ing the first parameter. Examples: + + ./script/runner 'ReminderService.deliver' + ./script/runner 'Mailer.receive(STDIN.read)' + + This makes it easier to do CRON and postfix scripts without actually making a script just to trigger 1 line of code. + +* Fixed webrick_server cookie handling to allow multiple cookes to be set at once #800, #813 [dave@cherryville.org] + +* Fixed the Rakefile's interaction with postgresql to: + + 1. Use PGPASSWORD and PGHOST in the environment to fix prompting for + passwords when connecting to a remote db and local socket connections. + 2. Add a '-x' flag to pg_dump which stops it dumping privileges #807 [rasputnik] + 3. Quote the user name and use template0 when dumping so the functions doesn't get dumped too #855 [pburleson] + 4. Use the port if available #875 [madrobby] + +* Upgraded to Active Record 1.9.0, Action Pack 1.6.0, Action Mailer 0.8.0, Action Web Service 0.6.1, Active Support 1.0.2 + + +*0.10.1* (7th March, 2005) + +* Fixed rake stats to ignore editor backup files like model.rb~ #791 [skanthak] + +* Added exception shallowing if the DRb server can't be started (not worth making a fuss about to distract new users) #779 [Tobias Luetke] + +* Added an empty favicon.ico file to the public directory of new applications (so the logs are not spammed by its absence) + +* Fixed that scaffold generator new template should use local variable instead of instance variable #778 [Dan Peterson] + +* Allow unit tests to run on a remote server for PostgreSQL #781 [adamm@galacticasoftware.com] + +* Added web_service generator (run ./script/generate web_service for help) #776 [Leon Bredt] + +* Added app/apis and components to code statistics report #729 [Scott Barron] + +* Fixed WEBrick server to use ABSOLUTE_RAILS_ROOT instead of working_directory #687 [Nicholas Seckar] + +* Fixed rails_generator to be usable without RubyGems #686 [Cristi BALAN] + +* Fixed -h/--help for generate and destroy generators #331 + +* Added begin/rescue around the FCGI dispatcher so no uncaught exceptions can bubble up to kill the process (logs to log/fastcgi.crash.log) + +* Fixed that association#count would produce invalid sql when called sequentialy #659 [kanis@comcard.de] + +* Fixed test/mocks/testing to the correct test/mocks/test #740 + +* Added early failure if the Ruby version isn't 1.8.2 or above #735 + +* Removed the obsolete -i/--index option from the WEBrick servlet #743 + +* Upgraded to Active Record 1.8.0, Action Pack 1.5.1, Action Mailer 0.7.1, Action Web Service 0.6.0, Active Support 1.0.1 + + +*0.10.0* (24th February, 2005) + +* Changed default IP binding for WEBrick from 127.0.0.1 to 0.0.0.0 so that the server is accessible both locally and remotely #696 [Marcel] + +* Fixed that script/server -d was broken so daemon mode couldn't be used #687 [Nicholas Seckar] + +* Upgraded to breakpoint 92 which fixes: + + * overload IRB.parse_opts(), fixes #443 + => breakpoints in tests work even when running them via rake + * untaint handlers, might fix an issue discussed on the Rails ML + * added verbose mode to breakpoint_client + * less noise caused by breakpoint_client by default + * ignored TerminateLineInput exception in signal handler + => quiet exit on Ctrl-C + +* Added support for independent components residing in /components. Example: + + Controller: components/list/items_controller.rb + (holds a List::ItemsController class with uses_component_template_root called) + + Model : components/list/item.rb + (namespace is still shared, so an Item model in app/models will take precedence) + + Views : components/list/items/show.rhtml + + +* Added --sandbox option to script/console that'll roll back all changes made to the database when you quit #672 [Jeremy Kemper] + +* Added 'recent' as a rake target that'll run tests for files that changed in the last 10 minutes #612 [Jeremy Kemper] + +* Changed script/console to default to development environment and drop --no-inspect #650 [Jeremy Kemper] + +* Added that the 'fixture :posts' syntax can be used for has_and_belongs_to_many fixtures where a model doesn't exist #572 [Jeremy Kemper] + +* Added that running test_units and test_functional now performs the clone_structure_to_test as well #566 [rasputnik] + +* Added new generator framework that informs about its doings on generation and enables updating and destruction of generated artifacts. See the new script/destroy and script/update for more details #487 [Jeremy Kemper] + +* Added Action Web Service as a new add-on framework for Action Pack [Leon Bredt] + +* Added Active Support as an independent utility and standard library extension bundle + +* Upgraded to Active Record 1.7.0, Action Pack 1.5.0, Action Mailer 0.7.0 + + +*0.9.5* (January 25th, 2005) + +* Fixed dependency reloading by switching to a remove_const approach where all Active Records, Active Record Observers, and Action Controllers are reloading by undefining their classes. This enables you to remove methods in all three types and see the change reflected immediately and it fixes #539. This also means that only those three types of classes will benefit from the const_missing and reloading approach. If you want other classes (like some in lib/) to reload, you must use require_dependency to do it. + +* Added Florian Gross' latest version of Breakpointer and friends that fixes a variaty of bugs #441 [Florian Gross] + +* Fixed skeleton Rakefile to work with sqlite3 out of the box #521 [rasputnik] + +* Fixed that script/breakpointer didn't get the Ruby path rewritten as the other scripts #523 [brandt@kurowski.net] + +* Fixed handling of syntax errors in models that had already been succesfully required once in the current interpreter + +* Fixed that models that weren't referenced in associations weren't being reloaded in the development mode by reinstating the reload + +* Fixed that generate scaffold would produce bad functional tests + +* Fixed that FCGI can also display SyntaxErrors + +* Upgraded to Active Record 1.6.0, Action Pack 1.4.0 + + +*0.9.4.1* (January 18th, 2005) + +* Added 5-second timeout to WordNet alternatives on creating reserved-word models #501 [Marcel Molina] + +* Fixed binding of caller #496 [Alexey] + +* Upgraded to Active Record 1.5.1, Action Pack 1.3.1, Action Mailer 0.6.1 + + +*0.9.4* (January 17th, 2005) + +* Added that ApplicationController will catch a ControllerNotFound exception if someone attempts to access a url pointing to an unexisting controller [Tobias Luetke] + +* Flipped code-to-test ratio around to be more readable #468 [Scott Baron] + +* Fixed log file permissions to be 666 instead of 777 (so they're not executable) #471 [Lucas Carlson] + +* Fixed that auto reloading would some times not work or would reload the models twice #475 [Tobias Luetke] + +* Added rewrite rules to deal with caching to public/.htaccess + +* Added the option to specify a controller name to "generate scaffold" and made the default controller name the plural form of the model. + +* Added that rake clone_structure_to_test, db_structure_dump, and purge_test_database tasks now pick up the source database to use from + RAILS_ENV instead of just forcing development #424 [Tobias Luetke] + +* Fixed script/console to work with Windows (that requires the use of irb.bat) #418 [octopod] + +* Fixed WEBrick servlet slowdown over time by restricting the load path reloading to mod_ruby + +* Removed Fancy Indexing as a default option on the WEBrick servlet as it made it harder to use various caching schemes + +* Upgraded to Active Record 1.5, Action Pack 1.3, Action Mailer 0.6 + + +*0.9.3* (January 4th, 2005) + +* Added support for SQLite in the auto-dumping/importing of schemas for development -> test #416 + +* Added automated rewriting of the shebang lines on installs through the gem rails command #379 [Manfred Stienstra] + +* Added ActionMailer::Base.deliver_method = :test to the test environment so that mail objects are available in ActionMailer::Base.deliveries + for functional testing. + +* Added protection for creating a model through the generators with a name of an existing class, like Thread or Date. + It'll even offer you a synonym using wordnet.princeton.edu as a look-up. No, I'm not kidding :) [Florian Gross] + +* Fixed dependency management to happen in a unified fashion for Active Record and Action Pack using the new Dependencies module. This means that + the environment options needs to change from: + + Before in development.rb: + ActionController::Base.reload_dependencies = true   + ActiveRecord::Base.reload_associations     = true + + Now in development.rb: + Dependencies.mechanism = :load + + Before in production.rb and test.rb: + ActionController::Base.reload_dependencies = false + ActiveRecord::Base.reload_associations     = false + + Now in production.rb and test.rb: + Dependencies.mechanism = :require + +* Fixed problems with dependency caching and controller hierarchies on Ruby 1.8.2 in development mode #351 + +* Fixed that generated action_mailers doesnt need to require the action_mailer since thats already done in the environment #382 [Lucas Carlson] + +* Upgraded to Action Pack 1.2.0 and Active Record 1.4.0 + + +*0.9.2* + +* Fixed CTRL-C exists from the Breakpointer to be a clean affair without error dumping [Kent Sibilev] + +* Fixed "rake stats" to work with sub-directories in models and controllers and to report the code to test ration [Scott Baron] + +* Added that Active Record associations are now reloaded instead of cleared to work with the new const_missing hook in Active Record. + +* Added graceful handling of an inaccessible log file by redirecting output to STDERR with a warning #330 [rainmkr] + +* Added support for a -h/--help parameter in the generator #331 [Ulysses] + +* Fixed that File.expand_path in config/environment.rb would fail when dealing with symlinked public directories [mjobin] + +* Upgraded to Action Pack 1.1.0 and Active Record 1.3.0 + + +*0.9.1* + +* Upgraded to Action Pack 1.0.1 for important bug fix + +* Updated gem dependencies + + +*0.9.0* + +* Renamed public/dispatch.servlet to script/server -- it wasn't really dispatching anyway as its delegating calls to public/dispatch.rb + +* Renamed AbstractApplicationController and abstract_application.rb to ApplicationController and application.rb, so that it will be possible + for the framework to automatically pick up on app/views/layouts/application.rhtml and app/helpers/application.rb + +* Added script/console that makes it even easier to start an IRB session for interacting with the domain model. Run with no-args to + see help. + +* Added breakpoint support through the script/breakpointer client. This means that you can break out of execution at any point in + the code, investigate and change the model, AND then resume execution! Example: + + class WeblogController < ActionController::Base + def index + @posts = Post.find_all + breakpoint "Breaking out from the list" + end + end + + So the controller will accept the action, run the first line, then present you with a IRB prompt in the breakpointer window. + Here you can do things like: + + Executing breakpoint "Breaking out from the list" at .../webrick_server.rb:16 in 'breakpoint' + + >> @posts.inspect + => "[#nil, \"body\"=>nil, \"id\"=>\"1\"}>, + #\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]" + >> @posts.first.title = "hello from a breakpoint" + => "hello from a breakpoint" + + ...and even better is that you can examine how your runtime objects actually work: + + >> f = @posts.first + => #nil, "body"=>nil, "id"=>"1"}> + >> f. + Display all 152 possibilities? (y or n) + + Finally, when you're ready to resume execution, you press CTRL-D + +* Changed environments to be configurable through an environment variable. By default, the environment is "development", but you + can change that and set your own by configuring the Apache vhost with a string like (mod_env must be available on the server): + + SetEnv RAILS_ENV production + + ...if you're using WEBrick, you can pick the environment to use with the command-line parameters -e/--environment, like this: + + ruby public/dispatcher.servlet -e production + +* Added a new default environment called "development", which leaves the production environment to be tuned exclusively for that. + +* Added a start_server in the root of the Rails application to make it even easier to get started + +* Fixed public/.htaccess to use RewriteBase and share the same rewrite rules for all the dispatch methods + +* Fixed webrick_server to handle requests in a serialized manner (the Rails reloading infrastructure is not thread-safe) + +* Added support for controllers in directories. So you can have: + + app/controllers/account_controller.rb # URL: /account/ + app/controllers/admin/account_controller.rb # URL: /admin/account/ + + NOTE: You need to update your public/.htaccess with the new rules to pick it up + +* Added reloading for associations and dependencies under cached environments like FastCGI and mod_ruby. This makes it possible to use + those environments for development. This is turned on by default, but can be turned off with + ActiveRecord::Base.reload_associations = false and ActionController::Base.reload_dependencies = false in production environments. + +* Added support for sub-directories in app/models. So now you can have something like Basecamp with: + + app/models/accounting + app/models/project + app/models/participants + app/models/settings + + It's poor man's namespacing, but only for file-system organization. You still require files just like before. + Nothing changes inside the files themselves. + + +* Fixed a few references in the tests generated by new_mailer [Jeremy Kemper] + +* Added support for mocks in testing with test/mocks + +* Cleaned up the environments a bit and added global constant RAILS_ROOT + + +*0.8.5* (9) + +* Made dev-util available to all tests, so you can insert breakpoints in any test case to get an IRB prompt at that point [Jeremy Kemper]: + + def test_complex_stuff + @david.projects << @new_project + breakpoint "Let's have a closer look at @david" + end + + You need to install dev-utils yourself for this to work ("gem install dev-util"). + +* Added shared generator behavior so future upgrades should be possible without manually copying over files [Jeremy Kemper] + +* Added the new helper style to both controller and helper templates [Jeremy Kemper] + +* Added new_crud generator for creating a model and controller at the same time with explicit scaffolding [Jeremy Kemper] + +* Added configuration of Test::Unit::TestCase.fixture_path to test_helper to concide with the new AR fixtures style + +* Fixed that new_model was generating singular table/fixture names + +* Upgraded to Action Mailer 0.4.0 + +* Upgraded to Action Pack 0.9.5 + +* Upgraded to Active Record 1.1.0 + + +*0.8.0 (15)* + +* Removed custom_table_name option for new_model now that the Inflector is as powerful as it is + +* Changed the default rake action to just do testing and separate API generation and coding statistics into a "doc" task. + +* Fixed WEBrick dispatcher to handle missing slashes in the URLs gracefully [alexey] + +* Added user option for all postgresql tool calls in the rakefile [elvstone] + +* Fixed problem with running "ruby public/dispatch.servlet" instead of "cd public; ruby dispatch.servlet" [alexey] + +* Fixed WEBrick server so that it no longer hardcodes the ruby interpreter used to "ruby" but will get the one used based + on the Ruby runtime configuration. [Marcel Molina Jr.] + +* Fixed Dispatcher so it'll route requests to magic_beans to MagicBeansController/magic_beans_controller.rb [Caio Chassot] + +* "new_controller MagicBeans" and "new_model SubscriptionPayments" will now both behave properly as they use the new Inflector. + +* Fixed problem with MySQL foreign key constraint checks in Rake :clone_production_structure_to_test target [Andreas Schwarz] + +* Changed WEBrick server to by default be auto-reloading, which is slower but makes source changes instant. + Class compilation cache can be turned on with "-c" or "--cache-classes". + +* Added "-b/--binding" option to WEBrick dispatcher to bind the server to a specific IP address (default: 127.0.0.1) [Kevin Temp] + +* dispatch.fcgi now DOESN'T set FCGI_PURE_RUBY as it was slowing things down for now reason [Andreas Schwarz] + +* Added new_mailer generator to work with Action Mailer + +* Included new framework: Action Mailer 0.3 + +* Upgraded to Action Pack 0.9.0 + +* Upgraded to Active Record 1.0.0 + + +*0.7.0* + +* Added an optional second argument to the new_model script that allows the programmer to specify the table name, + which will used to generate a custom table_name method in the model and will also be used in the creation of fixtures. + [Kevin Radloff] + +* script/new_model now turns AccountHolder into account_holder instead of accountholder [Kevin Radloff] + +* Fixed the faulty handleing of static files with WEBrick [Andreas Schwarz] + +* Unified function_test_helper and unit_test_helper into test_helper + +* Fixed bug with the automated production => test database dropping on PostgreSQL [dhawkins] + +* create_fixtures in both the functional and unit test helper now turns off the log during fixture generation + and can generate more than one fixture at a time. Which makes it possible for assignments like: + + @people, @projects, @project_access, @companies, @accounts = + create_fixtures "people", "projects", "project_access", "companies", "accounts" + +* Upgraded to Action Pack 0.8.5 (locally-scoped variables, partials, advanced send_file) + +* Upgraded to Active Record 0.9.5 (better table_name guessing, cloning, find_all_in_collection) + + +*0.6.5* + +* No longer specifies a template for rdoc, so it'll use whatever is default (you can change it in the rakefile) + +* The new_model generator will now use the same rules for plural wordings as Active Record + (so Category will give categories, not categorys) [Kevin Radloff] + +* dispatch.fcgi now sets FCGI_PURE_RUBY to true to ensure that it's the Ruby version that's loaded [danp] + +* Made the GEM work with Windows + +* Fixed bug where mod_ruby would "forget" the load paths added when switching between controllers + +* PostgreSQL are now supported for the automated production => test database dropping [Kevin Radloff] + +* Errors thrown by the dispatcher are now properly handled in FCGI. + +* Upgraded to Action Pack 0.8.0 (lots and lots and lots of fixes) + +* Upgraded to Active Record 0.9.4 (a bunch of fixes) + + +*0.6.0* + +* Added AbstractionApplicationController as a superclass for all controllers generated. This class can be used + to carry filters and methods that are to be shared by all. It has an accompanying ApplicationHelper that all + controllers will also automatically have available. + +* Added environments that can be included from any script to get the full Active Record and Action Controller + context running. This can be used by maintenance scripts or to interact with the model through IRB. Example: + + require 'config/environments/production' + + for account in Account.find_all + account.recalculate_interests + end + + A short migration script for an account model that had it's interest calculation strategy changed. + +* Accessing the index of a controller with "/weblog" will now redirect to "/weblog/" (only on Apache, not WEBrick) + +* Simplified the default Apache config so even remote requests are served off CGI as a default. + You'll now have to do something specific to activate mod_ruby and FCGI (like using the force urls). + This should make it easier for new comers that start on an external server. + +* Added more of the necessary Apache options to .htaccess to make it easier to setup + +* Upgraded to Action Pack 0.7.9 (lots of fixes) + +* Upgraded to Active Record 0.9.3 (lots of fixes) + + +*0.5.7* + +* Fixed bug in the WEBrick dispatcher that prevented it from getting parameters from the URL + (through GET requests or otherwise) + +* Added lib in root as a place to store app specific libraries + +* Added lib and vendor to load_path, so anything store within can be loaded directly. + Hence lib/redcloth.rb can be loaded with require "redcloth" + +* Upgraded to Action Pack 0.7.8 (lots of fixes) + +* Upgraded to Active Record 0.9.2 (minor upgrade) + + +*0.5.6* + +* Upgraded to Action Pack 0.7.7 (multipart form fix) + +* Updated the generated template stubs to valid XHTML files + +* Ensure that controllers generated are capitalized, so "new_controller TodoLists" + gives the same as "new_controller Todolists" and "new_controller todolists". + + +*0.5.5* + +* Works on Windows out of the box! (Dropped symlinks) + +* Added webrick dispatcher: Try "ruby public/dispatch.servlet --help" [Florian Gross] + +* Report errors about initialization to browser (instead of attempting to use uninitialized logger) + +* Upgraded to Action Pack 0.7.6 + +* Upgraded to Active Record 0.9.1 + +* Added distinct 500.html instead of reusing 404.html + +* Added MIT license + + +*0.5.0* + +* First public release diff --git a/CHANGES.markdown b/CHANGES.markdown deleted file mode 100755 index 50e4d0f..0000000 --- a/CHANGES.markdown +++ /dev/null @@ -1,47 +0,0 @@ -## Changes - -#### 0.9.5 - - * favicon added - -#### 0.9.4 - - * bump gems - -#### 0.9.3 - - * handle Cc & Bcc adresses fix - -#### 0.9.2 - - * fixes in handling draft folder - -#### 0.9.1 - - * nowrap to edit column in contacts & links - * decoded changed in mail gem - * fixes in pl locale - -#### 0.9.0 - - * switch to Rails 3.2.x - * Tweeter Bootstrap as default theme - * many fixes - -#### 0.8.6 - - * new calendar view - -#### 0.8.5 - - * servers view - * identity modification - -#### 0.8.4 - - * calendar view as separate gem - * adding bluecloth for rendering markdown text - -#### 0.8.3 - - * export, imports of contact diff --git a/Gemfile b/Gemfile deleted file mode 100755 index 31e4d9d..0000000 --- a/Gemfile +++ /dev/null @@ -1,47 +0,0 @@ -source 'https://rubygems.org' - -gem 'rails', '>= 3.2.11' - -# Bundle edge Rails instead: -# gem 'rails', :git => 'git://github.com/rails/rails.git' - -gem 'mysql2' - -gem 'json', '>= 1.7.6' - -# Gems used only for assets and not required -# in production environments by default. -group :assets do - gem 'sass-rails' - gem 'coffee-rails' - - # See https://github.com/sstephenson/execjs#readme for more supported runtimes - # gem 'therubyracer' - - gem 'uglifier' -end - -gem 'jquery-rails' - -# To use ActiveModel has_secure_password -# gem 'bcrypt-ruby', '~> 3.0.0' - -# To use Jbuilder templates for JSON -# gem 'jbuilder' - -# Use unicorn as the app server -# gem 'unicorn' - -# Deploy with Capistrano -# gem 'capistrano' - -# To use debugger -# gem 'ruby-debug' - -gem 'will_paginate' -gem "ezcrypto" -gem 'calendar_view' -gem 'bluecloth' -gem 'sass' -gem 'haml' -#gem 'twitter_bootstrap_form_for' diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index ed43337..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,125 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - actionmailer (3.2.11) - actionpack (= 3.2.11) - mail (~> 2.4.4) - actionpack (3.2.11) - activemodel (= 3.2.11) - activesupport (= 3.2.11) - builder (~> 3.0.0) - erubis (~> 2.7.0) - journey (~> 1.0.4) - rack (~> 1.4.0) - rack-cache (~> 1.2) - rack-test (~> 0.6.1) - sprockets (~> 2.2.1) - activemodel (3.2.11) - activesupport (= 3.2.11) - builder (~> 3.0.0) - activerecord (3.2.11) - activemodel (= 3.2.11) - activesupport (= 3.2.11) - arel (~> 3.0.2) - tzinfo (~> 0.3.29) - activeresource (3.2.11) - activemodel (= 3.2.11) - activesupport (= 3.2.11) - activesupport (3.2.11) - i18n (~> 0.6) - multi_json (~> 1.0) - arel (3.0.2) - bluecloth (2.2.0) - builder (3.0.4) - calendar_view (0.0.7) - rails (>= 3.0.0) - coffee-rails (3.2.2) - coffee-script (>= 2.2.0) - railties (~> 3.2.0) - coffee-script (2.2.0) - coffee-script-source - execjs - coffee-script-source (1.4.0) - erubis (2.7.0) - execjs (1.4.0) - multi_json (~> 1.0) - ezcrypto (0.7.2) - haml (3.1.7) - hike (1.2.1) - i18n (0.6.1) - journey (1.0.4) - jquery-rails (2.2.0) - railties (>= 3.0, < 5.0) - thor (>= 0.14, < 2.0) - json (1.7.6) - mail (2.4.4) - i18n (>= 0.4.0) - mime-types (~> 1.16) - treetop (~> 1.4.8) - mime-types (1.19) - multi_json (1.5.0) - mysql2 (0.3.11) - polyglot (0.3.3) - rack (1.4.4) - rack-cache (1.2) - rack (>= 0.4) - rack-ssl (1.3.3) - rack - rack-test (0.6.2) - rack (>= 1.0) - rails (3.2.11) - actionmailer (= 3.2.11) - actionpack (= 3.2.11) - activerecord (= 3.2.11) - activeresource (= 3.2.11) - activesupport (= 3.2.11) - bundler (~> 1.0) - railties (= 3.2.11) - railties (3.2.11) - actionpack (= 3.2.11) - activesupport (= 3.2.11) - rack-ssl (~> 1.3.2) - rake (>= 0.8.7) - rdoc (~> 3.4) - thor (>= 0.14.6, < 2.0) - rake (10.0.3) - rdoc (3.12) - json (~> 1.4) - sass (3.2.5) - sass-rails (3.2.6) - railties (~> 3.2.0) - sass (>= 3.1.10) - tilt (~> 1.3) - sprockets (2.2.2) - hike (~> 1.2) - multi_json (~> 1.0) - rack (~> 1.0) - tilt (~> 1.1, != 1.3.0) - thor (0.17.0) - tilt (1.3.3) - treetop (1.4.12) - polyglot - polyglot (>= 0.3.1) - tzinfo (0.3.35) - uglifier (1.3.0) - execjs (>= 0.3.0) - multi_json (~> 1.0, >= 1.0.2) - will_paginate (3.0.4) - -PLATFORMS - ruby - -DEPENDENCIES - bluecloth - calendar_view - coffee-rails - ezcrypto - haml - jquery-rails - json (>= 1.7.6) - mysql2 - rails (>= 3.2.11) - sass - sass-rails - uglifier - will_paginate diff --git a/MIT-LICENSE b/MIT-LICENSE new file mode 100644 index 0000000..67625d0 --- /dev/null +++ b/MIT-LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2005, 2006 VibrantPlanet Ltd. +Copyright (c) 2005, 2006 Luben Manolov +Copyright (c) 2005, 2006 Nick Penkov + +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. + diff --git a/README b/README new file mode 100644 index 0000000..c41a035 --- /dev/null +++ b/README @@ -0,0 +1,34 @@ + +Installation Guide +Requirements + + * Ruby 1.8.7 + * Rails 2.3.2 + +Installation + + 1. Checkout the source code + 2. If you need to override some of the default constants used in the application take a look at config/default_site.rb. Then create config/site.rb that contains only the keys which you want to override. Example content of config/site.rb is: + + module CDF + + LOCALCONFIG = { + :imap_server => 'your.imap.server' + } + end + + 3. Configure SMTP settings + # initializers/smtp_settings.rb + ActionMailer::Base.smtp_settings = { + :address => "mail.example.com.py", + :port => 26, + :authentication => :plain, + :enable_starttls_auto => true, + :user_name => "emilio@example.com.py", + :password => "yourpass" + } + + 4. Prepare config/database.yml file (see config/database.yml.example) + 5. Migrate database (rake db:migrate) + + 6. Use it diff --git a/README.markdown b/README.markdown deleted file mode 100755 index 06c49cf..0000000 --- a/README.markdown +++ /dev/null @@ -1,38 +0,0 @@ -[![Dependency Status](https://gemnasium.com/musashimm/mailr.png)](https://gemnasium.com/musashimm/mailr) - -## Introduction -_MailR_ is a IMAP mail client based on _Ruby on Rails_ platform. - -**NOTE** All path and filenames are based on _Rails.root_ directory. - -## Requirements - -In _Rails 3_ and above all dependencies should be defined in file _Gemfile_. All needed gems can be installed using bundler. - -## Installation procedure - -* Checkout the source code. -* Install all dependiences. Check if proper gems (sqlite3/mysql/postgresql) are defined in _Gemfile_ and installed. Use _bundler_ for that: - -```shell -bundle install -``` - -* Check _config/settings.yml_ for proper values. (see _config/settings.yml.example_). -* Prepare config/database.yml file (see _config/database.yml.example_). -* Migrate database (rake db:migrate) -* Start rails server if applicable -* Point your browser to application URL: - For local access: http://localhost:3000 - For remote access: http://some_url/mailr -* Using browser do basic setup. If You make a mistake delete all data from DB using rake task: - -```shell -rake db:clear_data -``` - -* Use it. - -## Specific configuration - -None diff --git a/Rakefile b/Rakefile old mode 100755 new mode 100644 index c1d2ddb..cffd19f --- a/Rakefile +++ b/Rakefile @@ -1,7 +1,10 @@ -#!/usr/bin/env rake # Add your own tasks in files placed in lib/tasks ending in .rake, -# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. +# for example lib/tasks/switchtower.rake, and they will automatically be available to Rake. -require File.expand_path('../config/application', __FILE__) +require(File.join(File.dirname(__FILE__), 'config', 'boot')) -Mailr::Application.load_tasks +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +require 'tasks/rails' \ No newline at end of file diff --git a/TODO.markdown b/TODO.markdown deleted file mode 100755 index f1d3348..0000000 --- a/TODO.markdown +++ /dev/null @@ -1,21 +0,0 @@ -## Todo - - * add themes - -app/controllers/folders_controller.rb: - - * [ 29] [TODO] recreate local copy of folders - * [ 98] [TODO] save system folders - -app/controllers/messages_controller.rb: - - * [101] [FIXME] missing fields and support arrays - -app/controllers/messages_ops_controller.rb: - - * [261] [FIXME] edit does not support attachments - * [325] [TODO] check if email address is valid if not get address from contacts - -app/models/prefs.rb: - - * [ 21] [TODO] move refresh to prefs and make refresh page with messages diff --git a/UNLICENSE.markdown b/UNLICENSE.markdown deleted file mode 100755 index d5d5664..0000000 --- a/UNLICENSE.markdown +++ /dev/null @@ -1,26 +0,0 @@ -## License - -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -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 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. - -For more information, please refer to diff --git a/app/assets/images/glyphicons-halflings-white.png b/app/assets/images/glyphicons-halflings-white.png deleted file mode 100755 index a20760b..0000000 Binary files a/app/assets/images/glyphicons-halflings-white.png and /dev/null differ diff --git a/app/assets/images/glyphicons-halflings.png b/app/assets/images/glyphicons-halflings.png deleted file mode 100755 index 92d4445..0000000 Binary files a/app/assets/images/glyphicons-halflings.png and /dev/null differ diff --git a/app/assets/images/logo.png b/app/assets/images/logo.png deleted file mode 100755 index 28b9079..0000000 Binary files a/app/assets/images/logo.png and /dev/null differ diff --git a/app/assets/images/rails.png b/app/assets/images/rails.png deleted file mode 100755 index d5edc04..0000000 Binary files a/app/assets/images/rails.png and /dev/null differ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js deleted file mode 100755 index 9097d83..0000000 --- a/app/assets/javascripts/application.js +++ /dev/null @@ -1,15 +0,0 @@ -// This is a manifest file that'll be compiled into application.js, which will include all the files -// listed below. -// -// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, -// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. -// -// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the -// the compiled file. -// -// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD -// GO AFTER THE REQUIRES BELOW. -// -//= require jquery -//= require jquery_ujs -//= require_tree . diff --git a/app/assets/javascripts/bootstrap.min.js b/app/assets/javascripts/bootstrap.min.js deleted file mode 100755 index 97dc88e..0000000 --- a/app/assets/javascripts/bootstrap.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(a){a(function(){"use strict",a.support.transition=function(){var b=document.body||document.documentElement,c=b.style,d=c.transition!==undefined||c.WebkitTransition!==undefined||c.MozTransition!==undefined||c.MsTransition!==undefined||c.OTransition!==undefined;return d&&{end:function(){var b="TransitionEnd";return a.browser.webkit?b="webkitTransitionEnd":a.browser.mozilla?b="transitionend":a.browser.opera&&(b="oTransitionEnd"),b}()}}()})}(window.jQuery),!function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype={constructor:c,close:function(b){function f(){e.trigger("closed").remove()}var c=a(this),d=c.attr("data-target"),e;d||(d=c.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),e=a(d),e.trigger("close"),b&&b.preventDefault(),e.length||(e=c.hasClass("alert")?c:c.parent()),e.trigger("close").removeClass("in"),a.support.transition&&e.hasClass("fade")?e.on(a.support.transition.end,f):f()}},a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("alert");e||d.data("alert",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.alert.Constructor=c,a(function(){a("body").on("click.alert.data-api",b,c.prototype.close)})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.button.defaults,c)};b.prototype={constructor:b,setState:function(a){var b="disabled",c=this.$element,d=c.data(),e=c.is("input")?"val":"html";a+="Text",d.resetText||c.data("resetText",c[e]()),c[e](d[a]||this.options[a]),setTimeout(function(){a=="loadingText"?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},toggle:function(){var a=this.$element.parent('[data-toggle="buttons-radio"]');a&&a.find(".active").removeClass("active"),this.$element.toggleClass("active")}},a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("button"),f=typeof c=="object"&&c;e||d.data("button",e=new b(this,f)),c=="toggle"?e.toggle():c&&e.setState(c)})},a.fn.button.defaults={loadingText:"loading..."},a.fn.button.Constructor=b,a(function(){a("body").on("click.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle")})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.carousel.defaults,c),this.options.slide&&this.slide(this.options.slide)};b.prototype={cycle:function(){return this.interval=setInterval(a.proxy(this.next,this),this.options.interval),this},to:function(b){var c=this.$element.find(".active"),d=c.parent().children(),e=d.index(c),f=this;if(b>d.length-1||b<0)return;return this.sliding?this.$element.one("slid",function(){f.to(b)}):e==b?this.pause().cycle():this.slide(b>e?"next":"prev",a(d[b]))},pause:function(){return clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(b,c){var d=this.$element.find(".active"),e=c||d[b](),f=this.interval,g=b=="next"?"left":"right",h=b=="next"?"first":"last",i=this;if(!e.length)return;return this.sliding=!0,f&&this.pause(),e=e.length?e:this.$element.find(".item")[h](),!a.support.transition&&this.$element.hasClass("slide")?(this.$element.trigger("slide"),d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")):(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),this.$element.trigger("slide"),this.$element.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)})),f&&this.cycle(),this}},a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("carousel"),f=typeof c=="object"&&c;e||d.data("carousel",e=new b(this,f)),typeof c=="number"?e.to(c):typeof c=="string"||(c=f.slide)?e[c]():e.cycle()})},a.fn.carousel.defaults={interval:5e3},a.fn.carousel.Constructor=b,a(function(){a("body").on("click.carousel.data-api","[data-slide]",function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=!e.data("modal")&&a.extend({},e.data(),c.data());e.carousel(f),b.preventDefault()})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.collapse.defaults,c),this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.prototype={constructor:b,dimension:function(){var a=this.$element.hasClass("width");return a?"width":"height"},show:function(){var b=this.dimension(),c=a.camelCase(["scroll",b].join("-")),d=this.$parent&&this.$parent.find(".in"),e;d&&d.length&&(e=d.data("collapse"),d.collapse("hide"),e||d.data("collapse",null)),this.$element[b](0),this.transition("addClass","show","shown"),this.$element[b](this.$element[0][c])},hide:function(){var a=this.dimension();this.reset(this.$element[a]()),this.transition("removeClass","hide","hidden"),this.$element[a](0)},reset:function(a){var b=this.dimension();this.$element.removeClass("collapse")[b](a||"auto")[0].offsetWidth,this.$element.addClass("collapse")},transition:function(b,c,d){var e=this,f=function(){c=="show"&&e.reset(),e.$element.trigger(d)};this.$element.trigger(c)[b]("in"),a.support.transition&&this.$element.hasClass("collapse")?this.$element.one(a.support.transition.end,f):f()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}},a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("collapse"),f=typeof c=="object"&&c;e||d.data("collapse",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.collapse.defaults={toggle:!0},a.fn.collapse.Constructor=b,a(function(){a("body").on("click.collapse.data-api","[data-toggle=collapse]",function(b){var c=a(this),d,e=c.attr("data-target")||b.preventDefault()||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),f=a(e).data("collapse")?"toggle":c.data();a(e).collapse(f)})})}(window.jQuery),!function(a){function d(){a(b).parent().removeClass("open")}"use strict";var b='[data-toggle="dropdown"]',c=function(b){var c=a(b).on("click.dropdown.data-api",this.toggle);a("html").on("click.dropdown.data-api",function(){c.parent().removeClass("open")})};c.prototype={constructor:c,toggle:function(b){var c=a(this),e=c.attr("data-target"),f,g;return e||(e=c.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,"")),f=a(e),f.length||(f=c.parent()),g=f.hasClass("open"),d(),!g&&f.toggleClass("open"),!1}},a.fn.dropdown=function(b){return this.each(function(){var d=a(this),e=d.data("dropdown");e||d.data("dropdown",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.dropdown.Constructor=c,a(function(){a("html").on("click.dropdown.data-api",d),a("body").on("click.dropdown.data-api",b,c.prototype.toggle)})}(window.jQuery),!function(a){function c(){var b=this,c=setTimeout(function(){b.$element.off(a.support.transition.end),d.call(b)},500);this.$element.one(a.support.transition.end,function(){clearTimeout(c),d.call(b)})}function d(a){this.$element.hide().trigger("hidden"),e.call(this)}function e(b){var c=this,d=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var e=a.support.transition&&d;this.$backdrop=a(''; + + div.innerHTML = html; + + document.body.appendChild(div); + + tinyMCE._currentDialog = id; + } + /*}*/ + } +}; + +TinyMCE.prototype.closeDialog = function() { + // Remove div or close window + if (tinyMCE.settings["dialog_type"] == "div") { + var div = document.getElementById(tinyMCE._currentDialog); + if (div) + div.parentNode.removeChild(div); + } else + window.close(); +}; + +TinyMCE.prototype.getVisualAidClass = function(class_name, state) { + var aidClass = tinyMCE.settings['visual_table_class']; + + if (typeof(state) == "undefined") + state = tinyMCE.settings['visual']; + + // Split + var classNames = new Array(); + var ar = class_name.split(' '); + for (var i=0; i 0) + className += " "; + + className += classNames[i]; + } + + return className; +}; + +TinyMCE.prototype.handleVisualAid = function(element, deep, state) { + if (!element) + return; + + var tableElement = null; + + switch (element.nodeName.toLowerCase()) { + case "table": + var oldW = element.style.width; + var oldH = element.style.height; + + element.className = tinyMCE.getVisualAidClass(element.className, state && element.getAttribute("border") == 0); + + element.style.width = oldW; + element.style.height = oldH; + + for (var y=0; y'; + return; + } + + break;*/ + } + + if (deep && element.hasChildNodes()) { + for (var i=0; i

breaks runtime? + if (tinyMCE.isMSIE) { + var re = new RegExp('


', 'g'); + html_content = html_content.replace(re, "
"); + } + + doc.body.innerHTML = html_content; + + // Content duplication bug fix + if (tinyMCE.isMSIE && tinyMCE.settings['fix_content_duplication']) { + // Remove P elements in P elements + var paras = doc.getElementsByTagName("P"); + for (var i=0; i<\/o:p>", "
"); + html = tinyMCE.regexpReplace(html, " <\/o:p>", ""); + html = tinyMCE.regexpReplace(html, "", ""); + html = tinyMCE.regexpReplace(html, "

<\/p>", ""); + html = tinyMCE.regexpReplace(html, "

<\/p>\r\n

<\/p>", ""); + html = tinyMCE.regexpReplace(html, "

 <\/p>", "
"); + html = tinyMCE.regexpReplace(html, "

\s*(

\s*)?", "

"); + html = tinyMCE.regexpReplace(html, "<\/p>\s*(<\/p>\s*)?", "

"); + } + + // Always set the htmlText output + doc.body.innerHTML = html; + } +}; + +TinyMCE.prototype.getImageSrc = function(str) { + var pos = -1; + + if (!str) + return ""; + + if ((pos = str.indexOf('this.src=')) != -1) { + var src = str.substring(pos + 10); + + src = src.substring(0, src.indexOf('\'')); + + return src; + } + + return ""; +}; + +TinyMCE.prototype._getElementById = function(element_id) { + var elm = document.getElementById(element_id); + if (!elm) { + // Check for element in forms + for (var j=0; j 0) { + var csses = null; + + // Just ignore any errors + eval("try {var csses = tinyMCE.isMSIE ? doc.styleSheets(0).rules : doc.styleSheets[0].cssRules;} catch(e) {}"); + if (!csses) + return new Array(); + + for (var i=0; i 0) + tinyMCE.cssClasses = output; + + return output; +}; + +TinyMCE.prototype.regexpReplace = function(in_str, reg_exp, replace_str, opts) { + if (typeof(opts) == "undefined") + opts = 'g'; + + var re = new RegExp(reg_exp, opts); + return in_str.replace(re, replace_str); +}; + +TinyMCE.prototype.cleanupEventStr = function(str) { + str = "" + str; + str = str.replace('function anonymous()\n{\n', ''); + str = str.replace('\n}', ''); + + return str; +}; + +TinyMCE.prototype.getAbsPosition = function(node) { + var pos = new Object(); + + pos.absLeft = pos.absTop = 0; + + var parentNode = node; + while (parentNode) { + pos.absLeft += parentNode.offsetLeft; + pos.absTop += parentNode.offsetTop; + + parentNode = parentNode.offsetParent; + } + + return pos; +}; + +TinyMCE.prototype.openFileBrowser = function(field_name, url, type, win) { + var cb = tinyMCE.getParam("file_browser_callback"); + + this.setWindowArg("window", win); + + // Call to external callback + if(eval('typeof('+cb+')') == "undefined") + alert("Callback function: " + cb + " could not be found."); + else + eval(cb + "(field_name, url, type, win);"); +}; + +TinyMCE.prototype.getControlHTML = function(control_name) { + var themePlugins = tinyMCE.getParam('plugins', '', true, ','); + var templateFunction; + + // Is it defined in any plugins + for (var i=themePlugins.length; i>=0; i--) { + templateFunction = 'TinyMCE_' + themePlugins[i] + "_getControlHTML"; + if (eval("typeof(" + templateFunction + ")") != 'undefined') { + var html = eval(templateFunction + "('" + control_name + "');"); + if (html != "") + return tinyMCE.replaceVar(html, "pluginurl", tinyMCE.baseURL + "/plugins/" + themePlugins[i]); + } + } + + return eval('TinyMCE_' + tinyMCE.settings['theme'] + "_getControlHTML" + "('" + control_name + "');"); +}; + +TinyMCE.prototype._themeExecCommand = function(editor_id, element, command, user_interface, value) { + var themePlugins = tinyMCE.getParam('plugins', '', true, ','); + var templateFunction; + + // Is it defined in any plugins + for (var i=themePlugins.length; i>=0; i--) { + templateFunction = 'TinyMCE_' + themePlugins[i] + "_execCommand"; + if (eval("typeof(" + templateFunction + ")") != 'undefined') { + if (eval(templateFunction + "(editor_id, element, command, user_interface, value);")) + return true; + } + } + + // Theme funtion + templateFunction = 'TinyMCE_' + tinyMCE.settings['theme'] + "_execCommand"; + if (eval("typeof(" + templateFunction + ")") != 'undefined') + return eval(templateFunction + "(editor_id, element, command, user_interface, value);"); + + // Pass to normal + return false; +}; + +TinyMCE.prototype._getThemeFunction = function(suffix, skip_plugins) { + if (skip_plugins) + return 'TinyMCE_' + tinyMCE.settings['theme'] + suffix; + + var themePlugins = tinyMCE.getParam('plugins', '', true, ','); + var templateFunction; + + // Is it defined in any plugins + for (var i=themePlugins.length; i>=0; i--) { + templateFunction = 'TinyMCE_' + themePlugins[i] + suffix; + if (eval("typeof(" + templateFunction + ")") != 'undefined') + return templateFunction; + } + + return 'TinyMCE_' + tinyMCE.settings['theme'] + suffix; +}; + + +TinyMCE.prototype.isFunc = function(func_name) { + if (func_name == null || func_name == "") + return false; + + return eval("typeof(" + func_name + ")") != "undefined"; +}; + +TinyMCE.prototype.exec = function(func_name, args) { + var str = func_name + '('; + + // Add all arguments + for (var i=3; i 1 && tinyMCE.currentConfig != this.settings['index']) { + tinyMCE.settings = this.settings; + tinyMCE.currentConfig = this.settings['index']; + } +}; + +TinyMCEControl.prototype.fixBrokenURLs = function() { + var body = this.getBody(); + + var elms = body.getElementsByTagName("img"); + for (var i=0; i 0) + rng.selectNodeContents(nodes[0]); + else + rng.selectNodeContents(node); + } else + rng.selectNode(node); + + if (collapse) { + // Special treatment of textnode collapse + if (!to_start && node.nodeType == 3) { + rng.setStart(node, node.nodeValue.length); + rng.setEnd(node, node.nodeValue.length); + } else + rng.collapse(to_start); + } + + sel.removeAllRanges(); + sel.addRange(rng); + } + + this.scrollToNode(node); + + // Set selected element + tinyMCE.selectedElement = null; + if (node.nodeType == 1) + tinyMCE.selectedElement = node; +}; + +TinyMCEControl.prototype.scrollToNode = function(node) { + // Scroll to node position + var pos = tinyMCE.getAbsPosition(node); + var doc = this.getDoc(); + var scrollX = doc.body.scrollLeft + doc.documentElement.scrollLeft; + var scrollY = doc.body.scrollTop + doc.documentElement.scrollTop; + var height = tinyMCE.isMSIE ? document.getElementById(this.editorId).style.pixelHeight : this.targetElement.clientHeight; + + // Only scroll if out of visible area + if (!tinyMCE.settings['auto_resize'] && !(node.absTop > scrollY && node.absTop < (scrollY - 25 + height))) + this.contentWindow.scrollTo(pos.absLeft, pos.absTop - height + 25); +}; + +TinyMCEControl.prototype.getBody = function() { + return this.getDoc().body; +}; + +TinyMCEControl.prototype.getDoc = function() { + return this.contentWindow.document; +}; + +TinyMCEControl.prototype.getWin = function() { + return this.contentWindow; +}; + +TinyMCEControl.prototype.getSel = function() { + if (tinyMCE.isMSIE) + return this.getDoc().selection; + + var sel = this.contentWindow.getSelection(); + + // Fake getRangeAt + if (tinyMCE.isSafari && !sel.getRangeAt) { + var newSel = new Object(); + var doc = this.getDoc(); + + function getRangeAt(idx) { + var rng = new Object(); + + rng.startContainer = this.focusNode; + rng.endContainer = this.anchorNode; + rng.commonAncestorContainer = this.focusNode; + rng.createContextualFragment = function (html) { + // Seems to be a tag + if (html.charAt(0) == '<') { + var elm = doc.createElement("div"); + + elm.innerHTML = html; + + return elm.firstChild; + } + + return doc.createTextNode("UNSUPPORTED, DUE TO LIMITATIONS IN SAFARI!"); + }; + + rng.deleteContents = function () { + doc.execCommand("Delete", false, ""); + }; + + return rng; + } + + // Patch selection + + newSel.focusNode = sel.baseNode; + newSel.focusOffset = sel.baseOffset; + newSel.anchorNode = sel.extentNode; + newSel.anchorOffset = sel.extentOffset; + newSel.getRangeAt = getRangeAt; + newSel.text = "" + sel; + newSel.realSelection = sel; + + newSel.toString = function () {return this.text;}; + + return newSel; + } + + return sel; +}; + +TinyMCEControl.prototype.getRng = function() { + var sel = this.getSel(); + if (sel == null) + return null; + + if (tinyMCE.isMSIE) + return sel.createRange(); + + return this.getSel().getRangeAt(0); +}; + +TinyMCEControl.prototype._insertPara = function(e) { + function isEmpty(para) { + function isEmptyHTML(html) { + return html.replace(new RegExp('[ \t\r\n]+', 'g'), '').toLowerCase() == ""; + } + + // Check for images + if (para.getElementsByTagName("img").length > 0) + return false; + + // Check for tables + if (para.getElementsByTagName("table").length > 0) + return false; + + // Check for HRs + if (para.getElementsByTagName("hr").length > 0) + return false; + + // Check all textnodes + var nodes = tinyMCE.getNodeTree(para, new Array(), 3); + for (var i=0; i <" + blockName + "> "; + paraAfter = body.childNodes[1]; + } + + this.selectNode(paraAfter, true, true); + + return true; + } + + // Place first part within new paragraph + if (startChop.nodeName == blockName) + rngBefore.setStart(startChop, 0); + else + rngBefore.setStartBefore(startChop); + rngBefore.setEnd(startNode, startOffset); + paraBefore.appendChild(rngBefore.cloneContents()); + + // Place secound part within new paragraph + rngAfter.setEndAfter(endChop); + rngAfter.setStart(endNode, endOffset); + var contents = rngAfter.cloneContents(); + if (contents.firstChild && contents.firstChild.nodeName == blockName) { + var nodes = contents.firstChild.childNodes; + for (var i=0; i 0) + rng.pasteHTML('
' + rng.htmlText + "
"); + + tinyMCE.triggerNodeChange(); + return; + } + } + } + + switch (command) { + case "mceSelectNode": + this.selectNode(value); + tinyMCE.triggerNodeChange(); + tinyMCE.selectedNode = value; + break; + + case "FormatBlock": + if (value == null || value == "") { + var elm = tinyMCE.getParentElement(this.getFocusElement(), "p,div,h1,h2,h3,h4,h5,h6,pre,address"); + + if (elm) + this.execCommand("mceRemoveNode", false, elm); + } else + this.getDoc().execCommand("FormatBlock", false, value); + + tinyMCE.triggerNodeChange(); + + break; + + case "mceRemoveNode": + if (!value) + value = tinyMCE.getParentElement(this.getFocusElement()); + + if (tinyMCE.isMSIE) { + value.outerHTML = value.innerHTML; + } else { + var rng = value.ownerDocument.createRange(); + rng.setStartBefore(value); + rng.setEndAfter(value); + rng.deleteContents(); + rng.insertNode(rng.createContextualFragment(value.innerHTML)); + } + + tinyMCE.triggerNodeChange(); + + break; + + case "mceSelectNodeDepth": + var parentNode = this.getFocusElement(); + for (var i=0; parentNode; i++) { + if (parentNode.nodeName.toLowerCase() == "body") + break; + + if (parentNode.nodeName.toLowerCase() == "#text") { + i--; + parentNode = parentNode.parentNode; + continue; + } + + if (i == value) { + this.selectNode(parentNode, false); + tinyMCE.triggerNodeChange(); + tinyMCE.selectedNode = parentNode; + return; + } + + parentNode = parentNode.parentNode; + } + + break; + + case "HiliteColor": + if (tinyMCE.isGecko) { + this.getDoc().execCommand("useCSS", false, false); + this.getDoc().execCommand('hilitecolor', false, value); + this.getDoc().execCommand("useCSS", false, true); + } else + this.getDoc().execCommand('BackColor', false, value); + + break; + + case "Cut": + case "Copy": + case "Paste": + var cmdFailed = false; + + // Try executing command + eval('try {this.getDoc().execCommand(command, user_interface, value);} catch (e) {cmdFailed = true;}'); + + // Alert error in gecko if command failed + if (tinyMCE.isGecko && cmdFailed) { + // Confirm more info + if (confirm(tinyMCE.getLang('lang_clipboard_msg'))) + window.open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', 'mceExternal'); + + return; + } else + tinyMCE.triggerNodeChange(); + break; + + case "mceSetContent": + if (!value) + value = ""; + + // Call custom cleanup code + value = tinyMCE._customCleanup("insert_to_editor", value); + tinyMCE._setHTML(doc, value); + doc.body.innerHTML = tinyMCE._cleanupHTML(doc, tinyMCE.settings, doc.body); + tinyMCE.handleVisualAid(doc.body, true, this.visualAid); + return true; + + case "mceLink": + var selectedText = ""; + + if (tinyMCE.isMSIE) { + var rng = doc.selection.createRange(); + selectedText = rng.text; + } else + selectedText = this.getSel().toString(); + + if (!tinyMCE.linkElement) { + if ((tinyMCE.selectedElement.nodeName.toLowerCase() != "img") && (selectedText.length <= 0)) + return; + } + + var href = "", target = "", title = "", onclick = "", action = "insert", style_class = ""; + + if (tinyMCE.selectedElement.nodeName.toLowerCase() == "a") + tinyMCE.linkElement = tinyMCE.selectedElement; + + // Is anchor not a link + if (tinyMCE.linkElement != null && tinyMCE.getAttrib(tinyMCE.linkElement, 'href') == "") + tinyMCE.linkElement = null; + + if (tinyMCE.linkElement) { + href = tinyMCE.getAttrib(tinyMCE.linkElement, 'href'); + target = tinyMCE.getAttrib(tinyMCE.linkElement, 'target'); + title = tinyMCE.getAttrib(tinyMCE.linkElement, 'title'); + onclick = tinyMCE.getAttrib(tinyMCE.linkElement, 'mce_onclick'); + style_class = tinyMCE.getAttrib(tinyMCE.linkElement, 'class'); + + // Try old onclick to if copy/pasted content + if (onclick == "") + onclick = tinyMCE.getAttrib(tinyMCE.linkElement, 'onclick'); + + onclick = tinyMCE.cleanupEventStr(onclick); + + // Fix for drag-drop/copy paste bug in Mozilla + mceRealHref = tinyMCE.getAttrib(tinyMCE.linkElement, 'mce_real_href'); + if (mceRealHref != "") + href = mceRealHref; + + href = eval(tinyMCE.settings['urlconverter_callback'] + "(href, tinyMCE.linkElement, true);"); + action = "update"; + } + + if (this.settings['insertlink_callback']) { + var returnVal = eval(this.settings['insertlink_callback'] + "(href, target, title, onclick, action, style_class);"); + if (returnVal && returnVal['href']) + tinyMCE.insertLink(returnVal['href'], returnVal['target'], returnVal['title'], returnVal['onclick'], returnVal['style_class']); + } else { + tinyMCE.openWindow(this.insertLinkTemplate, {href : href, target : target, title : title, onclick : onclick, action : action, className : style_class}); + } + break; + + case "mceAttachment": + var selectedText = ""; + + if (tinyMCE.isMSIE) { + var rng = doc.selection.createRange(); + selectedText = rng.text; + } else + selectedText = this.getSel().toString(); + + if (!tinyMCE.linkElement) { + if ((tinyMCE.selectedElement.nodeName.toLowerCase() != "img") && (selectedText.length <= 0)) + return; + } + + var href = "", target = "", title = "", onclick = "", action = "insert"; + + if (tinyMCE.selectedElement.nodeName.toLowerCase() == "a") + tinyMCE.linkElement = tinyMCE.selectedElement; + + // Is anchor not a link + if (tinyMCE.linkElement != null && tinyMCE.getAttrib(tinyMCE.linkElement, 'href') == "") + tinyMCE.linkElement = null; + + if (tinyMCE.linkElement) { + href = tinyMCE.getAttrib(tinyMCE.linkElement, 'href'); + target = tinyMCE.getAttrib(tinyMCE.linkElement, 'target'); + title = tinyMCE.getAttrib(tinyMCE.linkElement, 'title'); + onclick = tinyMCE.getAttrib(tinyMCE.linkElement, 'mce_onclick'); + + // Try old onclick to if copy/pasted content + if (onclick == "") + onclick = tinyMCE.getAttrib(tinyMCE.linkElement, 'onclick'); + + onclick = tinyMCE.cleanupEventStr(onclick); + + // Fix for drag-drop/copy paste bug in Mozilla + mceRealHref = tinyMCE.getAttrib(tinyMCE.linkElement, 'mce_real_href'); + if (mceRealHref != "") + href = mceRealHref; + + href = eval(tinyMCE.settings['urlconverter_callback'] + "(href, tinyMCE.linkElement, true);"); + action = "update"; + } + + if (this.settings['insertlink_callback']) { + var returnVal = eval(this.settings['insertlink_callback'] + "(href, target, title, onclick, action);"); + if (returnVal && returnVal['href']) + tinyMCE.insertLink(returnVal['href'], returnVal['target'], returnVal['title'], returnVal['onclick']); + } else { + tinyMCE.openWindow(this.insertAttachmentTemplate, {href : href, target : target, title : title, onclick : onclick, action : action}); + } + break; + + case "mceImage": + var src = "", alt = "", border = "", hspace = "", vspace = "", width = "", height = "", align = ""; + var title = "", onmouseover = "", onmouseout = "", action = "insert"; + var img = tinyMCE.imgElement; + + if (tinyMCE.selectedElement != null && tinyMCE.selectedElement.nodeName.toLowerCase() == "img") { + img = tinyMCE.selectedElement; + tinyMCE.imgElement = img; + } + + if (img) { + // Is it a internal MCE visual aid image, then skip this one. + if (tinyMCE.getAttrib(img, 'name').indexOf('mce_') == 0) + return; + + src = tinyMCE.getAttrib(img, 'src'); + alt = tinyMCE.getAttrib(img, 'alt'); + + // Try polling out the title + if (alt == "") + alt = tinyMCE.getAttrib(img, 'title'); + + // Fix width/height attributes if the styles is specified + if (tinyMCE.isGecko) { + var w = img.style.width; + if (w != null && w != "") + img.setAttribute("width", w); + + var h = img.style.height; + if (h != null && h != "") + img.setAttribute("height", h); + } + + border = tinyMCE.getAttrib(img, 'border'); + hspace = tinyMCE.getAttrib(img, 'hspace'); + vspace = tinyMCE.getAttrib(img, 'vspace'); + width = tinyMCE.getAttrib(img, 'width'); + height = tinyMCE.getAttrib(img, 'height'); + align = tinyMCE.getAttrib(img, 'align'); + onmouseover = tinyMCE.getAttrib(img, 'onmouseover'); + onmouseout = tinyMCE.getAttrib(img, 'onmouseout'); + title = tinyMCE.getAttrib(img, 'title'); + + // Is realy specified? + if (tinyMCE.isMSIE) { + width = img.attributes['width'].specified ? width : ""; + height = img.attributes['height'].specified ? height : ""; + } + + onmouseover = tinyMCE.getImageSrc(tinyMCE.cleanupEventStr(onmouseover)); + onmouseout = tinyMCE.getImageSrc(tinyMCE.cleanupEventStr(onmouseout)); + + // Fix for drag-drop/copy paste bug in Mozilla + mceRealSrc = tinyMCE.getAttrib(img, 'mce_real_src'); + if (mceRealSrc != "") + src = mceRealSrc; + + src = eval(tinyMCE.settings['urlconverter_callback'] + "(src, img, true);"); + + if (onmouseover != "") + onmouseover = eval(tinyMCE.settings['urlconverter_callback'] + "(onmouseover, img, true);"); + + if (onmouseout != "") + onmouseout = eval(tinyMCE.settings['urlconverter_callback'] + "(onmouseout, img, true);"); + + action = "update"; + } + + if (this.settings['insertimage_callback']) { + var returnVal = eval(this.settings['insertimage_callback'] + "(src, alt, border, hspace, vspace, width, height, align, title, onmouseover, onmouseout, action);"); + if (returnVal && returnVal['src']) + tinyMCE.insertImage(returnVal['src'], returnVal['alt'], returnVal['border'], returnVal['hspace'], returnVal['vspace'], returnVal['width'], returnVal['height'], returnVal['align'], returnVal['title'], returnVal['onmouseover'], returnVal['onmouseout']); + } else + tinyMCE.openWindow(this.insertImageTemplate, {src : src, alt : alt, border : border, hspace : hspace, vspace : vspace, width : width, height : height, align : align, title : title, onmouseover : onmouseover, onmouseout : onmouseout, action : action}); + break; + + case "mceCleanupWord": + if (tinyMCE.isMSIE) { + var html = this.getBody().createTextRange().htmlText; + + if (html.indexOf('="mso') != -1) { + tinyMCE._setHTML(this.contentDocument, this.getBody().innerHTML); + html = tinyMCE._cleanupHTML(this.contentDocument, this.settings, this.getBody(), this.visualAid); + } + + this.getBody().innerHTML = html; + } + break; + + case "mceCleanup": + tinyMCE._setHTML(this.contentDocument, this.getBody().innerHTML); + this.getBody().innerHTML = tinyMCE._cleanupHTML(this.contentDocument, this.settings, this.getBody(), this.visualAid); + tinyMCE.handleVisualAid(this.getBody(), true, this.visualAid); + this.repaint(); + tinyMCE.triggerNodeChange(); + break; + + case "mceAnchor": + if (!user_interface) { + var aElm = tinyMCE.getParentElement(this.getFocusElement(), "a", "name"); + if (aElm) { + if (value == null || value == "") { + if (tinyMCE.isMSIE) { + aElm.outerHTML = aElm.innerHTML; + } else { + var rng = aElm.ownerDocument.createRange(); + rng.setStartBefore(aElm); + rng.setEndAfter(aElm); + rng.deleteContents(); + rng.insertNode(rng.createContextualFragment(aElm.innerHTML)); + } + } else + aElm.setAttribute('name', value); + } else { + this.getDoc().execCommand("fontname", false, "#mce_temp_font#"); + var elementArray = tinyMCE.getElementsByAttributeValue(this.getBody(), "font", "face", "#mce_temp_font#"); + for (var x=0; x 0) { + value = tinyMCE.replaceVar(value, "selection", selectedText); + tinyMCE.execCommand('mceInsertContent', false, value); + } + + tinyMCE.triggerNodeChange(); + break; + + case "mceSetAttribute": + if (typeof(value) == 'object') { + var targetElms = (typeof(value['targets']) == "undefined") ? "p,img,span,div,td,h1,h2,h3,h4,h5,h6,pre,address" : value['targets']; + var targetNode = tinyMCE.getParentElement(this.getFocusElement(), targetElms); + + if (targetNode) { + targetNode.setAttribute(value['name'], value['value']); + tinyMCE.triggerNodeChange(); + } + } + break; + + case "mceSetCSSClass": + var selectedText = false; + + if (tinyMCE.isMSIE) { + var rng = doc.selection.createRange(); + selectedText = (rng.text && rng.text.length > 0); + } else + selectedText = (this.getSel().toString().length > 0); + + // Use selectedNode instead if defined + if (tinyMCE.selectedNode) + tinyMCE.selectedElement = tinyMCE.selectedNode; + + if (selectedText && !tinyMCE.selectedNode) { + this.getDoc().execCommand("RemoveFormat", false, null); + if (value == null) + return this.execCommand("RemoveFormat", false, null); + + this.getDoc().execCommand("fontname", false, "#mce_temp_font#"); + var elementArray = tinyMCE.getElementsByAttributeValue(this.getBody(), "font", "face", "#mce_temp_font#"); + + // Change them all + for (var x=0; x customUndoLevels) { + for (var i=0; i 0) { + this.undoIndex--; + this.getBody().innerHTML = this.undoLevels[this.undoIndex]; + } + + // debug("Undo - undo levels:" + this.undoLevels.length + ", undo index: " + this.undoIndex); + tinyMCE.triggerNodeChange(); + } else + this.getDoc().execCommand(command, user_interface, value); + break; + + case "Redo": + if (tinyMCE.settings['custom_undo_redo']) { + if (this.undoIndex < (this.undoLevels.length-1)) { + this.undoIndex++; + this.getBody().innerHTML = this.undoLevels[this.undoIndex]; + // debug("Redo - undo levels:" + this.undoLevels.length + ", undo index: " + this.undoIndex); + } + + tinyMCE.triggerNodeChange(); + } else + this.getDoc().execCommand(command, user_interface, value); + break; + + case "mceToggleVisualAid": + this.visualAid = !this.visualAid; + tinyMCE.handleVisualAid(this.getBody(), true, this.visualAid); + tinyMCE.triggerNodeChange(); + break; + + case "removeformat": + var text = this.getSelectedText(); + + if (tinyMCE.isMSIE) { + try { + win.focus(); + var rng = doc.selection.createRange(); + rng.execCommand("RemoveFormat", false, null); + rng.pasteHTML(rng.text); + } catch (e) { + // Do nothing + } + } else + this.getDoc().execCommand(command, user_interface, value); + + // Remove class + if (text.length == 0) + this.execCommand("mceSetCSSClass", false, ""); + + tinyMCE.triggerNodeChange(); + break; + + default: + this.getDoc().execCommand(command, user_interface, value); + tinyMCE.triggerNodeChange(); + } +}; + +TinyMCEControl.prototype.queryCommandValue = function(command) { + return this.getDoc().queryCommandValue(command); +}; + +TinyMCEControl.prototype.queryCommandState = function(command) { + return this.getDoc().queryCommandState(command); +}; + +TinyMCEControl.prototype.onAdd = function(replace_element, form_element_name, target_document) { + var targetDoc = target_document ? target_document : document; + + this.targetDoc = targetDoc; + + tinyMCE.themeURL = tinyMCE.baseURL + "/themes/" + this.settings['theme']; + this.settings['themeurl'] = tinyMCE.themeURL; + + if (!replace_element) { + alert("Error: Could not find the target element."); + return false; + } + + var templateFunction = tinyMCE._getThemeFunction('_getInsertLinkTemplate'); + if (eval("typeof(" + templateFunction + ")") != 'undefined') + this.insertLinkTemplate = eval(templateFunction + '(this.settings);'); + + var templateFunction = tinyMCE._getThemeFunction('_getInsertAttachmentTemplate'); + if (eval("typeof(" + templateFunction + ")") != 'undefined') + this.insertAttachmentTemplate = eval(templateFunction + '(this.settings);'); + + var templateFunction = tinyMCE._getThemeFunction('_getInsertImageTemplate'); + if (eval("typeof(" + templateFunction + ")") != 'undefined') + this.insertImageTemplate = eval(templateFunction + '(this.settings);'); + + var templateFunction = tinyMCE._getThemeFunction('_getEditorTemplate'); + if (eval("typeof(" + templateFunction + ")") == 'undefined') { + alert("Error: Could not find the template function: " + templateFunction); + return false; + } + + var editorTemplate = eval(templateFunction + '(this.settings, this.editorId);'); + + var deltaWidth = editorTemplate['delta_width'] ? editorTemplate['delta_width'] : 0; + var deltaHeight = editorTemplate['delta_height'] ? editorTemplate['delta_height'] : 0; + var html = '' + editorTemplate['html']; + + var templateFunction = tinyMCE._getThemeFunction('_handleNodeChange', true); + if (eval("typeof(" + templateFunction + ")") != 'undefined') + this.settings['handleNodeChangeCallback'] = templateFunction; + + html = tinyMCE.replaceVar(html, "editor_id", this.editorId); + html = tinyMCE.replaceVar(html, "default_document", tinyMCE.baseURL + "/blank.htm"); + this.settings['default_document'] = tinyMCE.baseURL + "/blank.htm"; + + this.settings['old_width'] = this.settings['width']; + this.settings['old_height'] = this.settings['height']; + + // Set default width, height + if (this.settings['width'] == -1) + this.settings['width'] = replace_element.offsetWidth; + + if (this.settings['height'] == -1) + this.settings['height'] = replace_element.offsetHeight; + + // Try the style width + if (this.settings['width'] == 0) + this.settings['width'] = replace_element.style.width; + + // Try the style height + if (this.settings['height'] == 0) + this.settings['height'] = replace_element.style.height; + + // If no width/height then default to 320x240, better than nothing + if (this.settings['width'] == 0) + this.settings['width'] = 320; + + if (this.settings['height'] == 0) + this.settings['height'] = 240; + + this.settings['area_width'] = parseInt(this.settings['width']); + this.settings['area_height'] = parseInt(this.settings['height']); + this.settings['area_width'] += deltaWidth; + this.settings['area_height'] += deltaHeight; + + // Special % handling + if (("" + this.settings['width']).indexOf('%') != -1) + this.settings['area_width'] = "100%"; + + if (("" + this.settings['height']).indexOf('%') != -1) + this.settings['area_height'] = "100%"; + + if (("" + replace_element.style.width).indexOf('%') != -1) { + this.settings['width'] = replace_element.style.width; + this.settings['area_width'] = "100%"; + } + + if (("" + replace_element.style.height).indexOf('%') != -1) { + this.settings['height'] = replace_element.style.height; + this.settings['area_height'] = "100%"; + } + + html = tinyMCE.applyTemplate(html); + + this.settings['width'] = this.settings['old_width']; + this.settings['height'] = this.settings['old_height']; + + this.visualAid = this.settings['visual']; + this.formTargetElementId = form_element_name; + + // Get replace_element contents + if (replace_element.nodeName.toLowerCase() == "textarea") + this.startContent = replace_element.value; + else + this.startContent = replace_element.innerHTML; + + // If not text area + if (replace_element.nodeName.toLowerCase() != "textarea") { + this.oldTargetElement = replace_element.cloneNode(true); + + // Debug mode + if (tinyMCE.settings['debug']) + html += ''; + else + html += ''; + + html += ''; + + // Output HTML and set editable + if (!tinyMCE.isMSIE) { + var rng = replace_element.ownerDocument.createRange(); + rng.setStartBefore(replace_element); + + var fragment = rng.createContextualFragment(html); + replace_element.parentNode.replaceChild(fragment, replace_element); + } else + replace_element.outerHTML = html; + } else { + html += ''; + + // Just hide the textarea element + this.oldTargetElement = replace_element; + + if (!tinyMCE.settings['debug']) + this.oldTargetElement.style.display = "none"; + + // Output HTML and set editable + if (!tinyMCE.isMSIE) { + var rng = replace_element.ownerDocument.createRange(); + rng.setStartBefore(replace_element); + + var fragment = rng.createContextualFragment(html); + replace_element.parentNode.insertBefore(fragment, replace_element); + } else + replace_element.insertAdjacentHTML("beforeBegin", html); + } + + // Setup iframe + var dynamicIFrame = false; + var tElm = targetDoc.getElementById(this.editorId); + + if (!tinyMCE.isMSIE) { + if (tElm && tElm.nodeName.toLowerCase() == "span") { + tElm = tinyMCE._createIFrame(tElm); + dynamicIFrame = true; + } + + this.targetElement = tElm; + this.iframeElement = tElm; + this.contentDocument = tElm.contentDocument; + this.contentWindow = tElm.contentWindow; + + //this.getDoc().designMode = "on"; + } else { + if (tElm && tElm.nodeName.toLowerCase() == "span") + tElm = tinyMCE._createIFrame(tElm); + else + tElm = targetDoc.frames[this.editorId]; + + this.targetElement = tElm; + this.iframeElement = targetDoc.getElementById(this.editorId); + this.contentDocument = tElm.window.document; + this.contentWindow = tElm.window; + this.getDoc().designMode = "on"; + } + + // Setup base HTML + var doc = this.contentDocument; + if (dynamicIFrame) { + var html = "" + + '' + + '' + + '' + + '' + + 'blank_page' + + '' + + '' + + '' + + '' + + ''; + + try { + this.getDoc().designMode = "on"; + doc.open(); + doc.write(html); + doc.close(); + } catch (e) { + // Failed Mozilla 1.3 + this.getDoc().location.href = tinyMCE.baseURL + "/blank.htm"; + } + } + + // This timeout is needed in MSIE 5.5 for some odd reason + // it seems that the document.frames isn't initialized yet? + if (tinyMCE.isMSIE) + window.setTimeout("TinyMCE.prototype.addEventHandlers('" + this.editorId + "');", 1); + + tinyMCE.setupContent(this.editorId, true); + + return true; +}; + +TinyMCEControl.prototype.getFocusElement = function() { + if (tinyMCE.isMSIE) { + var doc = this.getDoc(); + var rng = doc.selection.createRange(); + + if (rng.collapse) + rng.collapse(true); + + var elm = rng.item ? rng.item(0) : rng.parentElement(); + } else { + var sel = this.getSel(); + var elm = (sel && sel.anchorNode) ? sel.anchorNode : null; + + if (tinyMCE.selectedElement != null && tinyMCE.selectedElement.nodeName.toLowerCase() == "img") + elm = tinyMCE.selectedElement; + } + + return elm; +}; + +// Global instances +var tinyMCE = new TinyMCE(); +var tinyMCELang = new Array(); + +function debug() { + var msg = ""; + + var elm = document.getElementById("tinymce_debug"); + if (!elm) { + var debugDiv = document.createElement("div"); + debugDiv.setAttribute("className", "debugger"); + debugDiv.className = "debugger"; + debugDiv.innerHTML = '\ + Debug output:\ + '; + + document.body.appendChild(debugDiv); + elm = document.getElementById("tinymce_debug"); + } + + var args = this.debug.arguments; + for (var i=0; i 0 +end + +puts "Done - #{total_replacements} total replacements made." diff --git a/script/localize b/script/localize new file mode 100755 index 0000000..1757ecf --- /dev/null +++ b/script/localize @@ -0,0 +1,48 @@ +#!/usr/bin/env ruby + +# we need fileutils for easy file access +require 'fileutils' +require 'rubygems' +include FileUtils +# we will need the modified rgettext.rb that can read erb templates +require File.dirname(__FILE__) + '/rgettext' + + +# RAILS_ROOT is just one up +RAILS_ROOT = File.expand_path(File.dirname(__FILE__) + '/..') +# $DEBUG = true +# goto RAILS_ROOT so we can address all files relatively from there +Dir.chdir(RAILS_ROOT) + +# the potfile will hold the temporary data before it is merged; note the +# filename .messages.pot (if you don't prepend a dot to the filename Dir.glob +# will get confused later on) +potfile = "#{RAILS_ROOT}/locale/.messages.pot" + +# if the potfile exists from the previous run, delete it +rm_f potfile + +# directories and extensions to harvest +dirpattern = '{app,components,config,custom,lib}' +extpattern = 'r{b,html,xml}' +files = Dir.glob("#{dirpattern}/**/*.#{extpattern}") + +# run the harvester on the collected filenames and output to potfile +RGettext.new.start files, potfile + +# now iterate through all locale dirs and update/merge +Dir.glob('locale/*').each do |dir| + # check if every dir has a pofile to begin with, else msmerge will fail + # if not, use the potfile and don't merge + pofile = "#{RAILS_ROOT}/#{dir}/LC_MESSAGES/messages.po" + if File.exists?(pofile) + print "Updating pofile #{pofile} " + system "msgmerge --force-po --no-location --update #{pofile} #{potfile}" + else + print "The pofile '#{pofile}' does not exist. I will create it for you " + path_to_pofile = File.dirname(pofile) + mkdir path_to_pofile unless File.exists?(path_to_pofile) + cp potfile, pofile + puts ' .... done.' + end +end diff --git a/script/performance/benchmarker b/script/performance/benchmarker new file mode 100755 index 0000000..462cae1 --- /dev/null +++ b/script/performance/benchmarker @@ -0,0 +1,3 @@ +#!/usr/bin/ruby18 +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/benchmarker' diff --git a/script/performance/profiler b/script/performance/profiler new file mode 100755 index 0000000..8b3a633 --- /dev/null +++ b/script/performance/profiler @@ -0,0 +1,3 @@ +#!/usr/bin/ruby18 +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/profiler' diff --git a/script/performance/request b/script/performance/request new file mode 100755 index 0000000..ae3f38c --- /dev/null +++ b/script/performance/request @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/request' diff --git a/script/plugin b/script/plugin new file mode 100755 index 0000000..87cd207 --- /dev/null +++ b/script/plugin @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/plugin' diff --git a/script/process/inspector b/script/process/inspector new file mode 100755 index 0000000..bf25ad8 --- /dev/null +++ b/script/process/inspector @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/inspector' diff --git a/script/process/reaper b/script/process/reaper new file mode 100755 index 0000000..af0b34d --- /dev/null +++ b/script/process/reaper @@ -0,0 +1,3 @@ +#!/usr/bin/ruby18 +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/reaper' diff --git a/script/process/spawner b/script/process/spawner new file mode 100755 index 0000000..ffb55ae --- /dev/null +++ b/script/process/spawner @@ -0,0 +1,3 @@ +#!/usr/bin/ruby18 +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/spawner' diff --git a/script/process/spinner b/script/process/spinner new file mode 100755 index 0000000..c1ce730 --- /dev/null +++ b/script/process/spinner @@ -0,0 +1,3 @@ +#!/usr/bin/ruby18 +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/spinner' diff --git a/script/profiler b/script/profiler new file mode 100755 index 0000000..77c9fbe --- /dev/null +++ b/script/profiler @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby +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/rails b/script/rails deleted file mode 100755 index b97de07..0000000 --- a/script/rails +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env ruby18 -# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. - -APP_PATH = File.expand_path('../../config/application', __FILE__) -require File.expand_path('../../config/boot', __FILE__) -require 'rails/commands' diff --git a/script/rgettext.rb b/script/rgettext.rb new file mode 100755 index 0000000..3702f47 --- /dev/null +++ b/script/rgettext.rb @@ -0,0 +1,215 @@ +#!/usr/bin/env ruby +=begin + rgettext - ruby version of xgettext + Copyright (C) 2005 Sascha Ebach + Copyright (C) 2003,2004 Masao Mutoh + Copyright (C) 2001,2002 Yasushi Shoji, Masao Mutoh + + Yasushi Shoji + Masao Mutoh + Sascha Ebach + + You may redistribute it and/or modify it under the same + license terms as Ruby. + + 2005-03-12: Added support for eruby templates (Sascha Ebach) + 2005-03-20: Added second parameter to RGetext.start to allow different + output when not called from the command line. Pulled out + RGetext.set_output(). + +=end + +require 'gettext/parser/ruby' +require 'gettext/parser/glade' + +require 'getoptlong' +require 'gettext' + +require 'tempfile' +require 'erb' + +class RGettext + include GetText + + # constant values + VERSION = %w($Revision: 1.15 $)[1].scan(/\d+/).collect {|s| s.to_i} + DATE = %w($Date: 2004/11/05 18:19:08 $)[1] + MAX_LINE_LEN = 70 + + def start(files=ARGV, output = nil) + opt = check_options + opt['output'] = output unless output.nil? + set_output(opt) + + + if files.empty? + print_help + exit + end + + ary = [] + files.each do |file| + begin + $stderr.puts "Processing #{file}" + if glade_file?(file) + ary = GladeParser.parse(file, ary) + elsif erb_file?(file) + content = File.open(file, 'r') {|f| f.read } + tf = Tempfile.new('erb-gettext') + tf.puts ERB.new(content).src + tf.close + old_index = ary.size - 1 + ary = GetText::RubyParser.parse(tf.path, ary) + #replace tokens with /tmp/... with real file names + for i in old_index..ary.size-1 + for j in 0..ary[i].size-1 + ary[i][j] = ary[i][j].gsub("#{tf.path}", file) + end + end + tf.close true + else + ary = RubyParser.parse(file, ary) + end + rescue + puts $! + exit 1 + end + end + generate_pot_header + generate_pot(ary) + @out.close + end + + # following methods are + private + XML_RE = /<\?xml/ + GLADE_RE = /glade-2.0.dtd/ + + def erb_file?(file) + File.extname(file) == '.rhtml' + end + + def glade_file?(file) + data = IO.readlines(file) + if XML_RE =~ data[0] + if GLADE_RE =~ data[1] + return true + else + raise _("%s is not glade-2.0 format.") % [file] + end + else + return false + end + end + + def initialize + bindtextdomain("rgettext") + end + + def generate_pot_header + time = Time.now.strftime("%Y-%m-%d %H:%M%z") + @out << "# SOME DESCRIPTIVE TITLE.\n" + @out << "# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n" + @out << "# This file is distributed under the same license as the PACKAGE package.\n" + @out << "# FIRST AUTHOR , YEAR.\n" + @out << "#\n" + @out << "#, fuzzy\n" + @out << "msgid \"\"\n" + @out << "msgstr \"\"\n" + @out << "\"Project-Id-Version: PACKAGE VERSION\\n\"\n" + @out << "\"POT-Creation-Date: #{time}\\n\"\n" + @out << "\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n" + @out << "\"Last-Translator: FULL NAME \\n\"\n" + @out << "\"Language-Team: LANGUAGE \\n\"\n" + @out << "\"MIME-Version: 1.0\\n\"\n" + @out << "\"Content-Type: text/plain; charset=UTF-8\\n\"\n" + @out << "\"Content-Transfer-Encoding: 8bit\\n\"\n" + @out << "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n" + end + + def generate_pot(ary) + result = Array.new + ary.each do |key| + msgid = key.shift + curr_pos = MAX_LINE_LEN + key.each do |e| + if curr_pos + e.size > MAX_LINE_LEN + @out << "\n#:" + curr_pos = 3 + else + curr_pos += (e.size + 1) + end + @out << " " << e + end + msgid.gsub!(/"/, '\"') + msgid.gsub!(/\r/, '') + if msgid.include?("\000") + ids = msgid.split(/\000/) + @out << "\nmsgid \"" << ids[0] << "\"\n" + @out << "msgid_plural \"" << ids[1] << "\"\n" + @out << "msgstr[0] \"\"\n" + @out << "msgstr[1] \"\"\n" + else + @out << "\nmsgid \"" << msgid << "\"\n" + @out << "msgstr \"\"\n" + end + end + end + + def print_help + printf _("Usage: %s input.rb -o output.pot\n"), $0 + print _("Extract translatable strings from given input files.\n\n") + end + + def check_options + command_options = [ + ['--help', '-h', GetoptLong::NO_ARGUMENT], #'print this help and exit'], + ['--version', '-v', GetoptLong::NO_ARGUMENT], #'print version info and exit'], + ['--output', '-o', GetoptLong::REQUIRED_ARGUMENT]#, ['FILE', 'write output to specified file']] + ] + + parser = GetoptLong.new + parser.set_options(*command_options) + + opt = Hash.new + parser.each do |name, arg| + opt.store(name.sub(/^--/, ""), arg || true) + end + + if opt['version'] + print "#{$0} #{VERSION.join('.')} \(#{DATE}\)\n\n" + exit + end + + if opt['help'] + print_help + exit + end + + opt + end + + def set_output(opt) + if opt['output'] + unless FileTest.exist? opt['output'] + @out = File.new(File.expand_path(opt['output']), "w+") + else + if $>.tty? + # FIXME + printf $stderr, "File '#{opt['output']}' already exists\n" + exit 1 + else + printf $stderr, "File '#{opt['output']}' already exists" + exit 1 + end + end + else + @out = STDOUT + end + end +end # class RGettext + +if __FILE__ == $0 # in case we want to start it from somewhere else + rgettext = RGettext.new + rgettext.start +end diff --git a/script/runner b/script/runner new file mode 100755 index 0000000..57211c6 --- /dev/null +++ b/script/runner @@ -0,0 +1,3 @@ +#!/usr/bin/ruby18 +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/runner' \ No newline at end of file diff --git a/script/scgi_rails b/script/scgi_rails new file mode 100755 index 0000000..dc470d8 --- /dev/null +++ b/script/scgi_rails @@ -0,0 +1,339 @@ +#!/usr/bin/env ruby + +require 'stringio' +require 'yaml' +require 'digest/sha1' +require 'logger' +require 'fileutils' +require 'socket' +require 'cgi' +require 'rubygems' +require 'cmdparse' +require 'monitor' + +def log(msg) + $stderr.print msg,"\n" +end + +def error(msg, exc=nil) + if exc + $stderr.print "ERROR: #{msg}: #{exc}\n" + $stderr.puts exc.backtrace + else + $stderr.print "ERROR: #{msg}\n" + end +end + + +# Modifies CGI so that we can use it. +class SCGIFixed < ::CGI + public :env_table + + def initialize(params, data, out, *args) + @env_table = params + @args = *args + @input = StringIO.new(data) + @out = out + super(*args) + end + def args + @args + end + def env_table + @env_table + end + def stdinput + @input + end + def stdoutput + @out + end +end + + +class SCGIProcessor < Monitor + + def initialize(settings) + @env = settings[:env] || "development" + @debug = settings[:debug] || false + @host = settings[:host] || "127.0.0.1" + @port = settings[:port] || "9999" + @children = settings[:children] || 1 + @pid_file = settings[:pid_file] || "children.yaml" + @status_dir = settings[:status_dir] || "/tmp" + @log_file = settings[:logfile] || "log/scgi.log" + @maxconns = settings[:maxconns] + @busy_msg = settings[:busy_msg] || "BUSY" + @settings = settings + @started = Time.now + @conns = 0 + @total_conns = 0 + @errors = 0 + + if @maxconns + @maxconns = @maxconns.to_i + else + @maxconns = 2**30-1 + end + + if settings[:conns_second] + @throttle_sleep = 1.0/settings[:conns_second].to_i + end + + super() + end + + def run + ENV['RAILS_ENV'] = @env + + begin + require_gem 'rails' + require "config/environment" + rescue Object + error("loading rails environment", $!) + end + + server = TCPServer.new(@host, @port) + + if @debug + log("Listening for connections on #@host:#@port") + listen(server) + else + childpids = [] + @children.to_i.times do + # fork each child listening to the same port. very simple yet effective way to spread the load + # to multiple processes without using threads and still using high performance libevent + begin + pid = fork do + $stderr = open(@log_file,"w") + $stderr.sync = false + listen(server) + end + childpids << pid + Process.detach(pid) + rescue Object + error("Could not fork child processes. Your system might not support fork. Use -D instead.", $!) + end + end + + # tell the user what the sha1 is so they can check for modification later + log("#@pid_file will have SHA1 #{Digest::SHA1.hexdigest(YAML.dump(childpids))}") + log("Record this somewhere so you know if it was modified later by someone else.") + # all children forked and the pids are now ready to write to the pid file + open(@pid_file,"w") { |f| f.write(YAML.dump(childpids)) } + end + end + + + def listen(socket) + thread = Thread.new do + while true + handle_client(socket.accept) + sleep @throttle_sleep if @throttle_sleep + + @total_conns += 1 + end + end + + begin + thread.join + rescue Interrupt + log("Shutting down from SIGINT.") + rescue Object + error("while listening for connections on #@host:#@port", $!) + end + end + + + def handle_client(socket) + Thread.new do + begin + synchronize { @conns += 1} + + len = "" + # we only read 10 bytes of the length. any request longer than this is invalid + while len.length <= 10 + c = socket.read(1) + if c == ':' + # found the terminal, len now has a length in it so read the payload + break + else + len << c + end + end + + # we should now either have a payload length to get + payload = socket.read(len.to_i) + if (c = socket.read(1)) != ',' + error("Malformed request, does not end with ','") + else + read_header(socket, payload, @conns) + end + rescue IOError + error("received IOError #$! when handling client. Your web server doesn't like me.") + rescue Object + @errors += 1 + error("after accepting client #@host:#@port -- #{$!.class}", $!) + ensure + synchronize { @conns -= 1} + socket.close if not socket.closed? + end + end + + end + + + def read_header(socket, payload, conns) + return if socket.closed? + request = split_body(payload) + if request and request["CONTENT_LENGTH"] + length = request["CONTENT_LENGTH"].to_i + if length > 0 + body = socket.read(length) + else + body = "" + end + + if @conns > @maxconns + socket.write("Content-type: text/plain\r\n\r\n") + socket.write(@busy_msg) + else + process_request(request, body, socket) + end + end + end + + + def process_request(request, body, socket) + return if socket.closed? + cgi = SCGIFixed.new(request, body, socket) + begin + synchronize do + # unfortuneatly, the dependencies.rb file is not thread safe and will throw exceptions + # claiming that Dispatcher is not defined, or that other classes are missing. We have + # to sync the dispatch call to get around this. + Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput) + end + rescue IOError + error("received IOError #$! when handling client. Your web server doesn't like me.") + rescue Object => rails_error + error("calling Dispatcher.dispatch", rails_error) + end + end + + + def split_body(data) + result = {} + el = data.split("\0") + i = 0 + len = el.length + while i < len + result[el[i]] = el[i+1] + i += 1 + end + + return result + end + + def status + pid = Process.pid + open("#@status_dir/scgi_rails-status.#{pid}","w") do |f| + status = { + 'time' => Time.now, 'pid' => pid, 'settings' => @settings, + 'env' => @env, 'status_dir' => @status_dir, 'started' => @started, + 'max_conns' => @maxconns, 'total_conns' => @total_conns, + 'conns' => @conns, 'errors' => @errors, 'systimes' => Process.times + } + f.write(YAML.dump(status)) + end + end +end + + +def signal_children(pidfile, signal) + if not File.exists? pidfile + log("No #{pidfile} as specified. Probably nothing running or wrong path.") + exit 1 + end + + childpids = YAML.load_file(pidfile) + childpids.each do |pid| + begin + log("Signaling pid #{pid}") + Process.kill(signal, pid) + rescue Object + log("Couldn't send #{signal} signal to #{pid} pid.") + end + end +end + + +def make_command(parent, name, desc, options) + cmd = CmdParse::Command.new(name, false ) + cmd.short_desc = desc + settings = {} + cmd.options = CmdParse::OptionParserWrapper.new do |opt| + options.each do |short, long, info, symbol| + opt.on(short, long, info) {|val| settings[symbol] = val} + end + end + cmd.set_execution_block do |args| + yield(settings, args) + end + parent.add_command(cmd) +end + + +cmd = CmdParse::CommandParser.new( true ) +cmd.program_name = "scgi_rails" +cmd.program_version = [0, 2, 1] +cmd.options = CmdParse::OptionParserWrapper.new do |opt| + opt.separator "Global options:" + opt.on("--verbose", "Be verbose when outputting info") {|t| $verbose = true } +end + +cmd.add_command( CmdParse::HelpCommand.new ) +cmd.add_command( CmdParse::VersionCommand.new ) + +make_command(cmd, 'start', "Start Rails Application", +[['-e','--env STRING','Rails environment', :env], +['-D','--[no-]debug', 'Do not fork children, stay in foreground.', :debug], +['-h','--host STRING', 'IP address to bind as server', :host], +['-p','--port NUMBER', 'Port to bind to', :port], +['-c','--children NUMBER', 'Number of children to start (not win32)', :children], +['-f','--pid-file PATH', 'Where to read the list of running children', :pid_file], +['-l','--log-file PATH', 'Use a different log from from log/scgi.log', :logfile], +['-t','--throttle NUMBER', 'Max conn/second to allow.', :conns_second], +['-m','--max-conns NUMBER', 'Max simultaneous connections before the busy message', :maxconns], +['-b','--busy-msg', 'Busy message given to clients over the max connections ("busy")', :busy_msg], +['-s','--status-dir PATH', 'Where to put the status files', :status_dir]]) do |settings, args| + scgi = SCGIProcessor.new(settings) + begin + trap("HUP") { scgi.status } + rescue Object + error("Could not setup a SIGHUP handler. You won't be able to get status.") + end + + scgi.run +end + + +make_command(cmd, 'status', "Get status from all running children", +[['-s','--status-dir PATH', 'Where to put the status files', :status_dir], +['-f','--pid-file PATH', 'Where to read the list of running children', :pid_file]]) do |settings, args| + signal_children(settings[:pid_file] || 'children.yaml', "HUP") + log("Status files for each child should show up in the configured status directory (/tmp by default).") +end + +make_command(cmd, 'stop', "Stop all running children", +[['-s','--sig SIGNAL', 'Where to put the status files', :signal], +['-n','--[no-]delete', 'Keep the children.yaml file rather than delete', :nodelete], +['-f','--pid-file PATH', 'Where to read the list of running children', :pid_file]]) do |settings, args| + pid_file = settings[:pid_file] || "children.yaml" + signal_children(pid_file, settings[:signal] || "INT") + if not settings[:nodelete] and File.exist?(pid_file) + File.unlink(pid_file) + end +end + +cmd.parse \ No newline at end of file diff --git a/script/server b/script/server new file mode 100755 index 0000000..9436fde --- /dev/null +++ b/script/server @@ -0,0 +1,3 @@ +#!/usr/bin/ruby18 +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/server' \ No newline at end of file diff --git a/test/fixtures/.gitkeep b/test/fixtures/.gitkeep deleted file mode 100755 index e69de29..0000000 diff --git a/test/fixtures/expressions.yml b/test/fixtures/expressions.yml new file mode 100644 index 0000000..e3fa03c --- /dev/null +++ b/test/fixtures/expressions.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first_expression: + id: 1 +another_expression: + id: 2 diff --git a/test/fixtures/filters.yml b/test/fixtures/filters.yml new file mode 100644 index 0000000..b36dd3b --- /dev/null +++ b/test/fixtures/filters.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first_filter: + id: 1 +another_filter: + id: 2 diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml new file mode 100644 index 0000000..c884985 --- /dev/null +++ b/test/fixtures/users.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first_user: + id: 1 +another_user: + id: 2 diff --git a/test/functional/.gitkeep b/test/functional/.gitkeep deleted file mode 100755 index e69de29..0000000 diff --git a/test/integration/.gitkeep b/test/integration/.gitkeep deleted file mode 100755 index e69de29..0000000 diff --git a/test/performance/browsing_test.rb b/test/performance/browsing_test.rb deleted file mode 100755 index 3fea27b..0000000 --- a/test/performance/browsing_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'test_helper' -require 'rails/performance_test_help' - -class BrowsingTest < ActionDispatch::PerformanceTest - # Refer to the documentation for all available options - # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory] - # :output => 'tmp/performance', :formats => [:flat] } - - def test_homepage - get '/' - end -end diff --git a/test/test_helper.rb b/test/test_helper.rb old mode 100755 new mode 100644 index 8bf1192..0a4be59 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,13 +1,13 @@ ENV["RAILS_ENV"] = "test" -require File.expand_path('../../config/environment', __FILE__) -require 'rails/test_help' +require File.expand_path(File.dirname(__FILE__) + "/../config/environment") +require 'test_help' -class ActiveSupport::TestCase - # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. - # - # Note: You'll currently still have to declare fixtures explicitly in integration tests - # -- they do not yet inherit this setting - fixtures :all +class Test::Unit::TestCase + # Turn off transactional fixtures if you're working with MyISAM tables in MySQL + self.use_transactional_fixtures = true + + # Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david) + self.use_instantiated_fixtures = false # Add more helper methods to be used by all tests here... -end +end \ No newline at end of file diff --git a/test/unit/.gitkeep b/test/unit/.gitkeep deleted file mode 100755 index e69de29..0000000 diff --git a/test/unit/expression_test.rb b/test/unit/expression_test.rb new file mode 100644 index 0000000..94ff355 --- /dev/null +++ b/test/unit/expression_test.rb @@ -0,0 +1,14 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class ExpressionTest < Test::Unit::TestCase + fixtures :expressions + + def setup + @expression = Expression.find(1) + end + + # Replace this with your real tests. + def test_truth + assert_kind_of Expression, @expression + end +end diff --git a/test/unit/filter_test.rb b/test/unit/filter_test.rb new file mode 100644 index 0000000..019aec9 --- /dev/null +++ b/test/unit/filter_test.rb @@ -0,0 +1,14 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class FilterTest < Test::Unit::TestCase + fixtures :filters + + def setup + @filter = Filter.find(1) + end + + # Replace this with your real tests. + def test_truth + assert_kind_of Filter, @filter + end +end diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb new file mode 100644 index 0000000..24c6258 --- /dev/null +++ b/test/unit/user_test.rb @@ -0,0 +1,14 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class UserTest < Test::Unit::TestCase + fixtures :users + + def setup + @user = User.find(1) + end + + # Replace this with your real tests. + def test_truth + assert_kind_of User, @user + end +end diff --git a/vendor/assets/javascripts/.gitkeep b/vendor/assets/javascripts/.gitkeep deleted file mode 100755 index e69de29..0000000 diff --git a/vendor/assets/stylesheets/.gitkeep b/vendor/assets/stylesheets/.gitkeep deleted file mode 100755 index e69de29..0000000 diff --git a/vendor/ezcrypto-0.1.1/._README b/vendor/ezcrypto-0.1.1/._README new file mode 100644 index 0000000..a0c1410 Binary files /dev/null and b/vendor/ezcrypto-0.1.1/._README differ diff --git a/vendor/ezcrypto-0.1.1/._rakefile b/vendor/ezcrypto-0.1.1/._rakefile new file mode 100644 index 0000000..8d0ff5d Binary files /dev/null and b/vendor/ezcrypto-0.1.1/._rakefile differ diff --git a/vendor/ezcrypto-0.1.1/MIT-LICENSE b/vendor/ezcrypto-0.1.1/MIT-LICENSE new file mode 100644 index 0000000..26f55e7 --- /dev/null +++ b/vendor/ezcrypto-0.1.1/MIT-LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2004 David Heinemeier Hansson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/vendor/ezcrypto-0.1.1/README b/vendor/ezcrypto-0.1.1/README new file mode 100644 index 0000000..e360dc6 --- /dev/null +++ b/vendor/ezcrypto-0.1.1/README @@ -0,0 +1,130 @@ += EzCrypto - Easy to use Crypto for Ruby + +EzCrypto is an easy to use wrapper around the poorly documented OpenSSL ruby library. + +== Features + +* Defaults to AES 128 CBC +* Will use the systems OpenSSL library for transparent hardware crypto support +* Single class object oriented access to most commonly used features +* Ruby like + +== Simple examples + +==== To encrypt: + +Generate a key using a password and a salt. Use the keys encrypt method to encrypt a strings worth of data: + + @key=EzCrypto::Key.with_password "password", "system salt" + @encrypted=@key.encrypt "Top secret should not be revealed" + +==== To decrypt: + +Same procedure as encrypt. Generate a key using a password and a salt. Use the keys decrypt method to decrypt a strings worth of data: + + @key=EzCrypto::Key.with_password "password", "system salt" + @key.decrypt @encrypted + +==== One liners: + +These simple examples use one line each: + + @encrypted=EzCrypto::Key.encrypt_with_password "password", @salt,"Top secret should not be revealed" + + EzCrypto::Key.decrypt_with_password "password", @salt,@encrypted + +== Keys + +The only class you need to know for most uses og EzCrypto is the Key class. You don't need understand ciphers or the encryption life cycle. + +==== Generating a random key + +The most secure type of key is the randomly generated key: + + @key=EzCrypto::Key.generate + +==== Initializing a key with raw key data + +If you already have a key from some other source, you simply have to call the constructor with the raw data: + + @key=EzCrypto::Key.new @binarykey + +==== Initializing a Key with a Base64 encoded key + +As seen above you can create a key from a password. This should be used if you don't want the key to be stored on disk for example: + + @key=EzCrypto::Key.with_password "Secret password" + +==== Initializing a Key with a Base64 encoded key + +If you already have a key from some other source in the popular Base64 encoded format, you use the decode class method: + + @key=EzCrypto::Key.decode @binarykey + +==== Exporting the key + +To export or save a key use the encode method (or to_s) method for a Base64 encoded key or raw as the raw binary data. + + puts @key.encode + puts @key.raw + +The raw method could be used for storing in a database using a tinyblob column. + +== Encryption and Decryption + +EzCrypto is optimized for simple encryption and decryption of strings. There are encrypt/decrypt pairs for normal binary use as well as for Base64 encoded use. + +==== Regular raw use + +Assuming you have generated a key using one of the above methods: + + @encrypted=@key.encrypt("clear text") + @decrypted=@key.decrypt(@encrypted) + assert "clear text", @decrypted + +==== Base64 encoded use + +This uses the encrypt64 and decrypt64 methods. Otherwise it is all the same: + + @encrypted=@key.encrypt64("clear text") + @decrypted=@key.decrypt64(@encrypted) + assert "clear text", @decrypted + +== FAQ + +=== What algorithm does this use? + +It uses as the default algorithm the AES 128 bit standard. This is a very fast and highly secure algorithm specified as the national standard in the US. For more information see: + +http://en.wikipedia.org/wiki/AES + +=== Only 128 bits. Is that enough? + +While it might sound like more would make it more secure, there is really no real security advantage for most commercial applications to use more than 128 bit AES. + +=== What is Base64 encoding? + +This is the most efficient and commonly used encoding scheme for binary data. This is used amongst other things for email attachments. It is also very common to use it for encrypted data. + +=== What is a Salt? + +A salt is just a piece of data we hash in with the password to create the key. If it is a server based application you could use store a salt within your source file. The salt must be the same for both encryption and decryption. + + +== License + +Action Web Service is released under the MIT license. + + +== Support + +To contact the author, send mail to pelleb@gmail.com + +Also see my blogs at: +http://stakeventures.com and +http://neubia.com + +This project was based on code used in my project StakeItOut, where you can securely share web services with your partners. +https://stakeitout.com + +(C) 2005 Pelle Braendgaard diff --git a/vendor/ezcrypto-0.1.1/lib/ezcrypto.rb b/vendor/ezcrypto-0.1.1/lib/ezcrypto.rb new file mode 100644 index 0000000..37acdff --- /dev/null +++ b/vendor/ezcrypto-0.1.1/lib/ezcrypto.rb @@ -0,0 +1,357 @@ +require 'openssl' +require 'digest/sha2' +require 'digest/sha1' +require 'base64' + +module EzCrypto + + +=begin rdoc +The Key is the only class you need to understand for simple use. + +=== Algorithms + +The crypto algorithms default to aes-128-cbc however on any of the class methods you can change it to one of the standard openssl cipher names using the +optional :algorithm=>alg name parameter. + +Eg. + Key.new @raw, :algorithm=>"des" + Key.generate :algorithm=>"blowfish" + Key.with_password @pwd,@salt,:algorithm=>"aes256" + + +== License + +Action Web Service is released under the MIT license. + + +== Support + +To contact the author, send mail to pelleb@gmail.com + +Also see my blogs at: +http://stakeventures.com and +http://neubia.com + +This project was based on code used in my project StakeItOut, where you can securely share web services with your partners. +https://stakeitout.com + +(C) 2005 Pelle Braendgaard + +=end + + class Key + attr_reader :raw,:algorithm + +=begin rdoc +Initialize the key with raw unencoded binary key data. This needs to be at least +16 bytes long for the default aes-128 algorithm. +=end + def initialize(raw,options = {}) + @raw=raw + @algorithm=options[:algorithm]||"aes-128-cbc" + end + +=begin rdoc +Generate random key. +=end + def self.generate(options = {}) + Key.new(EzCrypto::Digester.generate_key(calculate_key_size(options[:algorithm])),options) + end + +=begin rdoc +Create key generated from the given password and salt +=end + def self.with_password(password,salt,options = {}) + Key.new(EzCrypto::Digester.get_key(password,salt,calculate_key_size(options[:algorithm])),options) + end + +=begin rdoc +Initialize the key with Base64 encoded key data. +=end + def self.decode(encoded,options = {}) + Key.new(Base64.decode64(encoded),options) + end + +=begin rdoc +Encrypts the data with the given password and a salt. Short hand for: + + key=Key.with_password(password,salt,options) + key.encrypt(data) + +=end + def self.encrypt_with_password(password,salt,data,options = {}) + key=Key.with_password(password,salt,options) + key.encrypt(data) + end + +=begin rdoc +Decrypts the data with the given password and a salt. Short hand for: + + key=Key.with_password(password,salt,options) + key.decrypt(data) + + +=end + def self.decrypt_with_password(password,salt,data,options = {}) + key=Key.with_password(password,salt,options) + key.decrypt(data) + end + +=begin rdoc +Given an algorithm this calculates the keysize. This is used by both the generate and with_password methods. This is not yet 100% complete. +=end + def self.calculate_key_size(algorithm) + if !algorithm.nil? + algorithm=~/^([[:alnum:]]+)(-(\d+))?/ + if $3 + size=($3.to_i)/8 + else + case $1 + when "bf" + size = 16 + when "blowfish" + size = 16 + when "des" + size = 8 + when "des3" + size = 24 + when "aes128" + size = 16 + when "aes192" + size = 24 + when "aes256" + size = 32 + when "rc2" + size = 16 + when "rc4" + size = 16 + else + size = 16 + end + end + end + if size.nil? + size = 16 + end + + size + end + +=begin rdoc +returns the Base64 encoded key. +=end + def encode + Base64.encode64 @raw + end + +=begin rdoc +returns the Base64 encoded key. Synonym for encode. +=end + def to_s + encode + end + +=begin rdoc +Encrypts the data and returns it in encrypted binary form. +=end + def encrypt(data) + @cipher=EzCrypto::Encrypter.new(self,"",@algorithm) + @cipher.encrypt(data) + end + +=begin rdoc +Encrypts the data and returns it in encrypted Base64 encoded form. +=end + def encrypt64(data) + Base64.encode64(encrypt(data)) + end + +=begin rdoc +Decrypts the data passed to it in binary format. +=end + def decrypt(data) + @cipher=EzCrypto::Decrypter.new(self,"",@algorithm) + @cipher.gulp(data) + rescue + puts @algorithm + throw $! + end + +=begin rdoc +Decrypts a Base64 formatted string +=end + def decrypt64(data) + decrypt(Base64.decode64(data)) + end + + + end +=begin rdoc +Abstract Wrapper around OpenSSL's Cipher object. Extended by Encrypter and Decrypter. + +You probably should be using the Key class instead. + +Warning! The interface may change. + +=end + class CipherWrapper + +=begin rdoc + +=end + def initialize(key,target,mode,algorithm) + @cipher = OpenSSL::Cipher::Cipher.new(algorithm) + if mode + @cipher.encrypt + else + @cipher.decrypt + end + @cipher.key=key.raw + @cipher.padding=1 + @target=target + @finished=false + end + +=begin rdoc +Process the givend data with the cipher. +=end + def update(data) + reset if @finished + @target<< @cipher.update(data) + end + +=begin rdoc + +=end + def <<(data) + update(data) + end + +=begin rdoc +Finishes up any last bits of data in the cipher and returns the final result. +=end + def final + @target<< @cipher.final + @finished=true + @target + end + +=begin rdoc +Processes the entire data string using update and performs a final on it returning the data. +=end + def gulp(data) + update(data) + final + end + +=begin rdoc + +=end + def reset(target="") + @target=target + @finished=false + end + end + +=begin rdoc +Wrapper around OpenSSL Cipher for Encryption use. + +You probably should be using Key instead. + +Warning! The interface may change. + +=end + class Encrypter [ :test ] + +# Run the unit tests +Rake::TestTask.new { |t| + t.libs << "test" + t.pattern = 'test/*_test.rb' + t.verbose = true +} + + +# Genereate the RDoc documentation +Rake::RDocTask.new { |rdoc| + rdoc.rdoc_dir = 'doc' + rdoc.title = "EzCrypto - Simplified Crypto Library" + rdoc.options << '--line-numbers --inline-source --main README' + rdoc.template = "#{ENV['template']}.rb" if ENV['template'] + rdoc.rdoc_files.include('README') + rdoc.rdoc_files.include('lib/ezcrypto.rb') +# rdoc.rdoc_files.include('lib/ezcrypto/*.rb') +} + + +# Create compressed packages +spec = Gem::Specification.new do |s| + s.platform = Gem::Platform::RUBY + s.name = PKG_NAME + s.summary = "Simplified encryption library." + s.description = %q{Makes it easier and safer to write crypto code.} + s.version = PKG_VERSION + + s.author = "Pelle Braendgaard" + s.email = "pelle@stakeitout.com" + s.rubyforge_project = "ezcrypto" + s.homepage = "http://ezcrypto.rubyforge.org" + + + s.has_rdoc = true + s.requirements << 'none' + s.require_path = 'lib' + + s.files = [ "rakefile", "README", "MIT-LICENSE" ] + s.files = s.files + Dir.glob( "lib/**/*" ).delete_if { |item| item.include?( "\.svn" ) } + s.files = s.files + Dir.glob( "test/**/*" ).delete_if { |item| item.include?( "\.svn" ) } +end + +Rake::GemPackageTask.new(spec) do |p| + p.gem_spec = spec + p.need_tar = true + p.need_zip = true +end + + +desc "Publish the API documentation" +task :pgem => [:package] do + Rake::SshFilePublisher.new("pelleb@rubyforge.org", "/var/www/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload +end + +desc "Publish the API documentation" +task :pdoc => [:rdoc] do + Rake::SshDirPublisher.new("pelleb@rubyforge.org", "/var/www/gforge-projects/ezcrypto", "doc").upload +end + +desc "Publish the release files to RubyForge." +task :release => [:package] do + files = ["gem", "tgz", "zip"].map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" } + + if RUBY_FORGE_PROJECT then + require 'net/http' + require 'open-uri' + + project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/" + project_data = open(project_uri) { |data| data.read } + group_id = project_data[/[?&]group_id=(\d+)/, 1] + raise "Couldn't get group id" unless group_id + + # This echos password to shell which is a bit sucky + if ENV["RUBY_FORGE_PASSWORD"] + password = ENV["RUBY_FORGE_PASSWORD"] + else + print "#{RUBY_FORGE_USER}@rubyforge.org's password: " + password = STDIN.gets.chomp + end + + login_response = Net::HTTP.start("rubyforge.org", 80) do |http| + data = [ + "login=1", + "form_loginname=#{RUBY_FORGE_USER}", + "form_pw=#{password}" + ].join("&") + http.post("/account/login.php", data) + end + + cookie = login_response["set-cookie"] + raise "Login failed" unless cookie + headers = { "Cookie" => cookie } + + release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}" + release_data = open(release_uri, headers) { |data| data.read } + package_id = release_data[/[?&]package_id=(\d+)/, 1] + raise "Couldn't get package id" unless package_id + + first_file = true + release_id = "" + + files.each do |filename| + basename = File.basename(filename) + file_ext = File.extname(filename) + file_data = File.open(filename, "rb") { |file| file.read } + + puts "Releasing #{basename}..." + + release_response = Net::HTTP.start("rubyforge.org", 80) do |http| + release_date = Time.now.strftime("%Y-%m-%d %H:%M") + type_map = { + ".zip" => "3000", + ".tgz" => "3110", + ".gz" => "3110", + ".gem" => "1400" + }; type_map.default = "9999" + type = type_map[file_ext] + boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor" + + query_hash = if first_file then + { + "group_id" => group_id, + "package_id" => package_id, + "release_name" => RELEASE_NAME, + "release_date" => release_date, + "type_id" => type, + "processor_id" => "8000", # Any + "release_notes" => "", + "release_changes" => "", + "preformatted" => "1", + "submit" => "1" + } + else + { + "group_id" => group_id, + "release_id" => release_id, + "package_id" => package_id, + "step2" => "1", + "type_id" => type, + "processor_id" => "8000", # Any + "submit" => "Add This File" + } + end + + query = "?" + query_hash.map do |(name, value)| + [name, URI.encode(value)].join("=") + end.join("&") + + data = [ + "--" + boundary, + "Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"", + "Content-Type: application/octet-stream", + "Content-Transfer-Encoding: binary", + "", file_data, "" + ].join("\x0D\x0A") + + release_headers = headers.merge( + "Content-Type" => "multipart/form-data; boundary=#{boundary}" + ) + + target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php" + http.post(target + query, data, release_headers) + end + + if first_file then + release_id = release_response.body[/release_id=(\d+)/, 1] + raise("Couldn't get release id") unless release_id + end + + first_file = false + end + end +end diff --git a/vendor/ezcrypto-0.1.1/test/ezcrypto_test.rb b/vendor/ezcrypto-0.1.1/test/ezcrypto_test.rb new file mode 100644 index 0000000..cf407c7 --- /dev/null +++ b/vendor/ezcrypto-0.1.1/test/ezcrypto_test.rb @@ -0,0 +1,112 @@ +$:.unshift(File.dirname(__FILE__) + "/../lib/") + +require 'test/unit' +require 'ezcrypto' +require 'base64' + +class EzCryptoTest < Test::Unit::TestCase + + def setup + end + + def test_generate_alg_key + assert_generate_alg_key "aes-128-cbc",16 + assert_generate_alg_key "aes-192-cbc",24 + assert_generate_alg_key "aes-256-cbc",32 + assert_generate_alg_key "rc2-40-cbc",5 + assert_generate_alg_key "rc2-64-cbc",8 + assert_generate_alg_key "rc4-64" ,8 + assert_generate_alg_key "blowfish" ,16 + assert_generate_alg_key "des" ,8 + end + + def test_with_password + assert_with_password "","secret","aes-128-cbc",16 + assert_with_password "test","secret","aes-128-cbc",16 + assert_with_password "password","secret","aes-128-cbc",16 + assert_with_password "asldfad8q534j2l4j24l6j2456","secret","aes-128-cbc",16 + + assert_with_password "","secret","aes-192-cbc",24 + assert_with_password "test","secret","aes-192-cbc",24 + assert_with_password "password","secret","aes-192-cbc",24 + assert_with_password "asldfad8q534j2l4j24l6j2456","secret","aes-192-cbc",24 + + assert_with_password "","secret","aes-256-cbc",32 + assert_with_password "test","secret","aes-256-cbc",32 + assert_with_password "password","secret","aes-256-cbc",32 + assert_with_password "asldfad8q534j2l4j24l6j2456","secret","aes-256-cbc",32 + + end + + def test_encoded + 0.upto 32 do |size| + assert_encoded_keys size + end + end + + def test_encrypt + 0.upto(CLEAR_TEXT.size-1) do |size| + assert_encrypt CLEAR_TEXT[0..size] + end + end + + def test_decrypt + + 0.upto(CLEAR_TEXT.size) do |size| + assert_decrypt CLEAR_TEXT[0..size] + end + end + + def test_decrypt64 + 0.upto(CLEAR_TEXT.size) do |size| + assert_decrypt64 CLEAR_TEXT[0..size] + end + end + + def assert_key_size(size,key) + assert_equal size,key.raw.size + end + + def assert_generate_alg_key(algorithm,size) + key=EzCrypto::Key.generate :algorithm=>algorithm + assert_key_size size,key + end + + def assert_with_password(password,salt,algorithm,size) + key=EzCrypto::Key.with_password password,salt,:algorithm=>algorithm + assert_key_size size,key + assert_equal key.raw,EzCrypto::Key.with_password( password,salt,:algorithm=>algorithm).raw + end + + def assert_encoded_keys(size) + key=EzCrypto::Key.generate size + key2=EzCrypto::Key.decode(key.encode) + assert_equal key.raw, key2.raw + end + + def assert_encrypt(clear) + ALGORITHMS.each do |alg| + key=EzCrypto::Key.generate :algorithm=>alg + encrypted=key.encrypt clear + assert_not_nil encrypted + end + end + + def assert_decrypt(clear) + ALGORITHMS.each do |alg| + key=EzCrypto::Key.generate :algorithm=>alg + encrypted=key.encrypt clear + assert_not_nil encrypted + assert_equal clear,key.decrypt(encrypted) + end + end + def assert_decrypt64(clear) + key=EzCrypto::Key.generate + encrypted=key.encrypt64 clear + assert_not_nil encrypted + assert_equal clear,key.decrypt64(encrypted) + end + ALGORITHMS=["aes128","bf","blowfish","des","des3","rc4","rc2"] + CLEAR_TEXT="Lorem ipsum dolor sit amet, suspendisse id interdum mus leo id. Sapien tempus consequat nullam, platea vitae sociis sed elementum et fermentum, vel praesent eget. Sed blandit augue, molestie mus sed habitant, semper voluptatibus neque, nullam a augue. Aptent imperdiet curabitur, quam quis laoreet. Dolor magna. Quis vestibulum amet eu arcu fringilla nibh, mi urna sunt dictumst nulla, elit quisque purus eros, sem hendrerit. Vulputate tortor rhoncus ac nonummy tortor nulla. Nunc id nunc luctus ligula." +end + diff --git a/vendor/plugins/.gitkeep b/vendor/plugins/.gitkeep deleted file mode 100755 index e69de29..0000000 diff --git a/vendor/plugins/auto_complete/README b/vendor/plugins/auto_complete/README new file mode 100644 index 0000000..e08a815 --- /dev/null +++ b/vendor/plugins/auto_complete/README @@ -0,0 +1,23 @@ +Example: + + # Controller + class BlogController < ApplicationController + auto_complete_for :post, :title + end + + # View + <%= text_field_with_auto_complete :post, title %> + +By default, auto_complete_for limits the results to 10 entries, +and sorts by the given field. + +auto_complete_for takes a third parameter, an options hash to +the find method used to search for the records: + + auto_complete_for :post, :title, :limit => 15, :order => 'created_at DESC' + +For more examples, see script.aculo.us: +* http://script.aculo.us/demos/ajax/autocompleter +* http://script.aculo.us/demos/ajax/autocompleter_customized + +Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license diff --git a/vendor/plugins/auto_complete/Rakefile b/vendor/plugins/auto_complete/Rakefile new file mode 100644 index 0000000..5af4e82 --- /dev/null +++ b/vendor/plugins/auto_complete/Rakefile @@ -0,0 +1,22 @@ +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +desc 'Default: run unit tests.' +task :default => :test + +desc 'Test auto_complete plugin.' +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.pattern = 'test/**/*_test.rb' + t.verbose = true +end + +desc 'Generate documentation for auto_complete plugin.' +Rake::RDocTask.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'Auto Complete' + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README') + rdoc.rdoc_files.include('lib/**/*.rb') +end diff --git a/vendor/plugins/auto_complete/init.rb b/vendor/plugins/auto_complete/init.rb new file mode 100644 index 0000000..87bf027 --- /dev/null +++ b/vendor/plugins/auto_complete/init.rb @@ -0,0 +1,2 @@ +ActionController::Base.send :include, AutoComplete +ActionController::Base.helper AutoCompleteMacrosHelper \ No newline at end of file diff --git a/vendor/plugins/auto_complete/lib/auto_complete.rb b/vendor/plugins/auto_complete/lib/auto_complete.rb new file mode 100644 index 0000000..4afc7c2 --- /dev/null +++ b/vendor/plugins/auto_complete/lib/auto_complete.rb @@ -0,0 +1,47 @@ +module AutoComplete + + def self.included(base) + base.extend(ClassMethods) + end + + # + # Example: + # + # # Controller + # class BlogController < ApplicationController + # auto_complete_for :post, :title + # end + # + # # View + # <%= text_field_with_auto_complete :post, title %> + # + # By default, auto_complete_for limits the results to 10 entries, + # and sorts by the given field. + # + # auto_complete_for takes a third parameter, an options hash to + # the find method used to search for the records: + # + # auto_complete_for :post, :title, :limit => 15, :order => 'created_at DESC' + # + # For help on defining text input fields with autocompletion, + # see ActionView::Helpers::JavaScriptHelper. + # + # For more examples, see script.aculo.us: + # * http://script.aculo.us/demos/ajax/autocompleter + # * http://script.aculo.us/demos/ajax/autocompleter_customized + module ClassMethods + def auto_complete_for(object, method, options = {}) + define_method("auto_complete_for_#{object}_#{method}") do + find_options = { + :conditions => [ "LOWER(#{method}) LIKE ?", '%' + params[object][method].downcase + '%' ], + :order => "#{method} ASC", + :limit => 10 }.merge!(options) + + @items = object.to_s.camelize.constantize.find(:all, find_options) + + render :inline => "<%= auto_complete_result @items, '#{method}' %>" + end + end + end + +end \ No newline at end of file diff --git a/vendor/plugins/auto_complete/lib/auto_complete_macros_helper.rb b/vendor/plugins/auto_complete/lib/auto_complete_macros_helper.rb new file mode 100644 index 0000000..1d25ee4 --- /dev/null +++ b/vendor/plugins/auto_complete/lib/auto_complete_macros_helper.rb @@ -0,0 +1,143 @@ +module AutoCompleteMacrosHelper + # Adds AJAX autocomplete functionality to the text input field with the + # DOM ID specified by +field_id+. + # + # This function expects that the called action returns an HTML
    list, + # or nothing if no entries should be displayed for autocompletion. + # + # You'll probably want to turn the browser's built-in autocompletion off, + # so be sure to include an autocomplete="off" attribute with your text + # input field. + # + # The autocompleter object is assigned to a Javascript variable named field_id_auto_completer. + # This object is useful if you for example want to trigger the auto-complete suggestions through + # other means than user input (for that specific case, call the activate method on that object). + # + # Required +options+ are: + # :url:: URL to call for autocompletion results + # in url_for format. + # + # Addtional +options+ are: + # :update:: Specifies the DOM ID of the element whose + # innerHTML should be updated with the autocomplete + # entries returned by the AJAX request. + # Defaults to field_id + '_auto_complete' + # :with:: A JavaScript expression specifying the + # parameters for the XMLHttpRequest. This defaults + # to 'fieldname=value'. + # :frequency:: Determines the time to wait after the last keystroke + # for the AJAX request to be initiated. + # :indicator:: Specifies the DOM ID of an element which will be + # displayed while autocomplete is running. + # :tokens:: A string or an array of strings containing + # separator tokens for tokenized incremental + # autocompletion. Example: :tokens => ',' would + # allow multiple autocompletion entries, separated + # by commas. + # :min_chars:: The minimum number of characters that should be + # in the input field before an Ajax call is made + # to the server. + # :on_hide:: A Javascript expression that is called when the + # autocompletion div is hidden. The expression + # should take two variables: element and update. + # Element is a DOM element for the field, update + # is a DOM element for the div from which the + # innerHTML is replaced. + # :on_show:: Like on_hide, only now the expression is called + # then the div is shown. + # :after_update_element:: A Javascript expression that is called when the + # user has selected one of the proposed values. + # The expression should take two variables: element and value. + # Element is a DOM element for the field, value + # is the value selected by the user. + # :select:: Pick the class of the element from which the value for + # insertion should be extracted. If this is not specified, + # the entire element is used. + # :method:: Specifies the HTTP verb to use when the autocompletion + # request is made. Defaults to POST. + def auto_complete_field(field_id, options = {}) + function = "var #{field_id}_auto_completer = new Ajax.Autocompleter(" + function << "'#{field_id}', " + function << "'" + (options[:update] || "#{field_id}_auto_complete") + "', " + function << "'#{url_for(options[:url])}'" + + js_options = {} + js_options[:tokens] = array_or_string_for_javascript(options[:tokens]) if options[:tokens] + js_options[:callback] = "function(element, value) { return #{options[:with]} }" if options[:with] + js_options[:indicator] = "'#{options[:indicator]}'" if options[:indicator] + js_options[:select] = "'#{options[:select]}'" if options[:select] + js_options[:paramName] = "'#{options[:param_name]}'" if options[:param_name] + js_options[:frequency] = "#{options[:frequency]}" if options[:frequency] + js_options[:method] = "'#{options[:method].to_s}'" if options[:method] + + { :after_update_element => :afterUpdateElement, + :on_show => :onShow, :on_hide => :onHide, :min_chars => :minChars }.each do |k,v| + js_options[v] = options[k] if options[k] + end + + function << (', ' + options_for_javascript(js_options) + ')') + + javascript_tag(function) + end + + # Use this method in your view to generate a return for the AJAX autocomplete requests. + # + # Example action: + # + # def auto_complete_for_item_title + # @items = Item.find(:all, + # :conditions => [ 'LOWER(description) LIKE ?', + # '%' + request.raw_post.downcase + '%' ]) + # render :inline => "<%= auto_complete_result(@items, 'description') %>" + # end + # + # The auto_complete_result can of course also be called from a view belonging to the + # auto_complete action if you need to decorate it further. + def auto_complete_result(entries, field, phrase = nil) + return unless entries + items = entries.map { |entry| content_tag("li", phrase ? highlight(entry[field], phrase) : h(entry[field])) } + content_tag("ul", items.uniq) + end + + # Wrapper for text_field with added AJAX autocompletion functionality. + # + # In your controller, you'll need to define an action called + # auto_complete_for to respond the AJAX calls, + # + def text_field_with_auto_complete(object, method, tag_options = {}, completion_options = {}) + (completion_options[:skip_style] ? "" : auto_complete_stylesheet) + + text_field(object, method, tag_options) + + content_tag("div", "", :id => "#{object}_#{method}_auto_complete", :class => "auto_complete") + + auto_complete_field("#{object}_#{method}", { :url => { :action => "auto_complete_for_#{object}_#{method}" } }.update(completion_options)) + end + + private + def auto_complete_stylesheet + content_tag('style', <<-EOT, :type => Mime::CSS) + div.auto_complete { + width: 350px; + background: #fff; + } + div.auto_complete ul { + border:1px solid #888; + margin:0; + padding:0; + width:100%; + list-style-type:none; + } + div.auto_complete ul li { + margin:0; + padding:3px; + } + div.auto_complete ul li.selected { + background-color: #ffb; + } + div.auto_complete ul strong.highlight { + color: #800; + margin:0; + padding:0; + } + EOT + end + +end diff --git a/vendor/plugins/auto_complete/test/auto_complete_test.rb b/vendor/plugins/auto_complete/test/auto_complete_test.rb new file mode 100644 index 0000000..dc9a5c9 --- /dev/null +++ b/vendor/plugins/auto_complete/test/auto_complete_test.rb @@ -0,0 +1,67 @@ +require File.expand_path(File.join(File.dirname(__FILE__), '../../../../test/test_helper')) + +class AutoCompleteTest < Test::Unit::TestCase + include AutoComplete + include AutoCompleteMacrosHelper + + include ActionView::Helpers::UrlHelper + include ActionView::Helpers::TagHelper + include ActionView::Helpers::TextHelper + include ActionView::Helpers::FormHelper + include ActionView::Helpers::CaptureHelper + + def setup + @controller = Class.new do + def url_for(options) + url = "http://www.example.com/" + url << options[:action].to_s if options and options[:action] + url + end + end + @controller = @controller.new + end + + + def test_auto_complete_field + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }); + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :tokens => ','); + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :tokens => [',']); + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :min_chars => 3); + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :on_hide => "function(element, update){alert('me');}"); + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :frequency => 2); + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, + :after_update_element => "function(element,value){alert('You have chosen: '+value)}"); + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :param_name => 'huidriwusch'); + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :method => :get); + end + + def test_auto_complete_result + result = [ { :title => 'test1' }, { :title => 'test2' } ] + assert_equal %(
    • test1
    • test2
    ), + auto_complete_result(result, :title) + assert_equal %(
    • test1
    • test2
    ), + auto_complete_result(result, :title, "est") + + resultuniq = [ { :title => 'test1' }, { :title => 'test1' } ] + assert_equal %(
    • test1
    ), + auto_complete_result(resultuniq, :title, "est") + end + + def test_text_field_with_auto_complete + assert_match %( + + +

    Samples of pagination styling for will_paginate

    +

    + Find these styles in "examples/pagination.css" of will_paginate library. + There is a Sass version of it for all you sassy people. +

    +

    + Read about good rules for pagination: + Pagination 101 +

    +

    + Warning: + page links below don't lead anywhere (so don't click on them). +

    +

    + Unstyled pagination (ewww!) +

    +
    + « Previous 1 3 4 5 6 7 8 9 29 30 +
    +

    Digg.com

    +
    + « Previous 1 3 4 5 6 7 8 9 29 30 +
    +

    Digg-style, no page links

    +
    + « Previous +
    +

    Code that renders this:

    +
    +    <%= will_paginate @posts, :page_links => false %>
    +  
    +

    Digg-style, extra content

    +
    +
    + Displaying entries 1 - 6 of 180 in total +
    + « Previous 1 3 4 5 6 7 8 9 29 30 +
    +

    Code that renders this:

    +
    +    <div class="digg_pagination">
    +      <div clas="page_info">
    +        <%= page_entries_info @posts %>
    +      </div>
    +      <%= will_paginate @posts, :container => false %>
    +    </div>
    +  
    +

    Apple.com store

    +
    + « Previous 1 3 4 5 6 7 8 9 29 30 +
    +

    Flickr.com

    +
    + « Previous 1 3 4 5 6 7 8 9 29 30 +
    (118 photos)
    +
    + diff --git a/vendor/plugins/will_paginate/examples/pagination.css b/vendor/plugins/will_paginate/examples/pagination.css new file mode 100644 index 0000000..b55e977 --- /dev/null +++ b/vendor/plugins/will_paginate/examples/pagination.css @@ -0,0 +1,90 @@ +.digg_pagination { + background: white; + /* self-clearing method: */ } + .digg_pagination a, .digg_pagination span { + padding: .2em .5em; + display: block; + float: left; + margin-right: 1px; } + .digg_pagination span.disabled { + color: #999; + border: 1px solid #DDD; } + .digg_pagination span.current { + font-weight: bold; + background: #2E6AB1; + color: white; + border: 1px solid #2E6AB1; } + .digg_pagination a { + text-decoration: none; + color: #105CB6; + border: 1px solid #9AAFE5; } + .digg_pagination a:hover, .digg_pagination a:focus { + color: #003; + border-color: #003; } + .digg_pagination .page_info { + background: #2E6AB1; + color: white; + padding: .4em .6em; + width: 22em; + margin-bottom: .3em; + text-align: center; } + .digg_pagination .page_info b { + color: #003; + background: #6aa6ed; + padding: .1em .25em; } + .digg_pagination:after { + content: "."; + display: block; + height: 0; + clear: both; + visibility: hidden; } + * html .digg_pagination { + height: 1%; } + *:first-child+html .digg_pagination { + overflow: hidden; } + +.apple_pagination { + background: #F1F1F1; + border: 1px solid #E5E5E5; + text-align: center; + padding: 1em; } + .apple_pagination a, .apple_pagination span { + padding: .2em .3em; } + .apple_pagination span.disabled { + color: #AAA; } + .apple_pagination span.current { + font-weight: bold; + background: transparent url(apple-circle.gif) no-repeat 50% 50%; } + .apple_pagination a { + text-decoration: none; + color: black; } + .apple_pagination a:hover, .apple_pagination a:focus { + text-decoration: underline; } + +.flickr_pagination { + text-align: center; + padding: .3em; } + .flickr_pagination a, .flickr_pagination span { + padding: .2em .5em; } + .flickr_pagination span.disabled { + color: #AAA; } + .flickr_pagination span.current { + font-weight: bold; + color: #FF0084; } + .flickr_pagination a { + border: 1px solid #DDDDDD; + color: #0063DC; + text-decoration: none; } + .flickr_pagination a:hover, .flickr_pagination a:focus { + border-color: #003366; + background: #0063DC; + color: white; } + .flickr_pagination .page_info { + color: #aaa; + padding-top: .8em; } + .flickr_pagination .prev_page, .flickr_pagination .next_page { + border-width: 2px; } + .flickr_pagination .prev_page { + margin-right: 1em; } + .flickr_pagination .next_page { + margin-left: 1em; } diff --git a/vendor/plugins/will_paginate/examples/pagination.sass b/vendor/plugins/will_paginate/examples/pagination.sass new file mode 100644 index 0000000..737a97b --- /dev/null +++ b/vendor/plugins/will_paginate/examples/pagination.sass @@ -0,0 +1,91 @@ +.digg_pagination + :background white + a, span + :padding .2em .5em + :display block + :float left + :margin-right 1px + span.disabled + :color #999 + :border 1px solid #DDD + span.current + :font-weight bold + :background #2E6AB1 + :color white + :border 1px solid #2E6AB1 + a + :text-decoration none + :color #105CB6 + :border 1px solid #9AAFE5 + &:hover, &:focus + :color #003 + :border-color #003 + .page_info + :background #2E6AB1 + :color white + :padding .4em .6em + :width 22em + :margin-bottom .3em + :text-align center + b + :color #003 + :background = #2E6AB1 + 60 + :padding .1em .25em + + /* self-clearing method: + &:after + :content "." + :display block + :height 0 + :clear both + :visibility hidden + * html & + :height 1% + *:first-child+html & + :overflow hidden + +.apple_pagination + :background #F1F1F1 + :border 1px solid #E5E5E5 + :text-align center + :padding 1em + a, span + :padding .2em .3em + span.disabled + :color #AAA + span.current + :font-weight bold + :background transparent url(apple-circle.gif) no-repeat 50% 50% + a + :text-decoration none + :color black + &:hover, &:focus + :text-decoration underline + +.flickr_pagination + :text-align center + :padding .3em + a, span + :padding .2em .5em + span.disabled + :color #AAA + span.current + :font-weight bold + :color #FF0084 + a + :border 1px solid #DDDDDD + :color #0063DC + :text-decoration none + &:hover, &:focus + :border-color #003366 + :background #0063DC + :color white + .page_info + :color #aaa + :padding-top .8em + .prev_page, .next_page + :border-width 2px + .prev_page + :margin-right 1em + .next_page + :margin-left 1em diff --git a/vendor/plugins/will_paginate/init.rb b/vendor/plugins/will_paginate/init.rb new file mode 100644 index 0000000..838d30e --- /dev/null +++ b/vendor/plugins/will_paginate/init.rb @@ -0,0 +1 @@ +require 'will_paginate' diff --git a/vendor/plugins/will_paginate/lib/will_paginate.rb b/vendor/plugins/will_paginate/lib/will_paginate.rb new file mode 100644 index 0000000..ee81c5d --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate.rb @@ -0,0 +1,43 @@ +require 'will_paginate/deprecation' + +# = You *will* paginate! +# +# First read about WillPaginate::Finder::ClassMethods, then see +# WillPaginate::ViewHelpers. The magical array you're handling in-between is +# WillPaginate::Collection. +# +# Happy paginating! +module WillPaginate + def self.enable + Deprecation.warn "WillPaginate::enable() doesn't do anything anymore" + end + + # Enable named_scope, a feature of Rails 2.1, even if you have older Rails + # (tested on Rails 2.0.2 and 1.2.6). + # + # You can pass +false+ for +patch+ parameter to skip monkeypatching + # *associations*. Use this if you feel that named_scope broke + # has_many, has_many :through or has_and_belongs_to_many associations in + # your app. By passing +false+, you can still use named_scope in + # your models, but not through associations. + def self.enable_named_scope(patch = true) + return if defined? ActiveRecord::NamedScope + require 'will_paginate/finders/active_record/named_scope' + require 'will_paginate/finders/active_record/named_scope_patch' if patch + + ActiveRecord::Base.send :include, WillPaginate::NamedScope + end +end + +if defined?(Rails) + require 'will_paginate/view_helpers/action_view' if defined?(ActionController) + require 'will_paginate/finders/active_record' if defined?(ActiveRecord) +end + +if defined?(Merb::Plugins) + require 'will_paginate/collection' + require 'will_paginate/view_helpers/base' + require 'will_paginate/view_helpers/link_renderer' + # this only includes will_paginate view stuff in Merb (not finder adapters) + Merb::AbstractController.send(:include, WillPaginate::ViewHelpers::Base) +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/array.rb b/vendor/plugins/will_paginate/lib/will_paginate/array.rb new file mode 100644 index 0000000..1076760 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/array.rb @@ -0,0 +1,33 @@ +require 'will_paginate/collection' + +class Array + # Paginates a static array (extracting a subset of it). The result is a + # WillPaginate::Collection instance, which is an array with few more + # properties about its paginated state. + # + # Parameters: + # * :page - current page, defaults to 1 + # * :per_page - limit of items per page, defaults to 30 + # * :total_entries - total number of items in the array, defaults to + # array.length (obviously) + # + # Example: + # arr = ['a', 'b', 'c', 'd', 'e'] + # paged = arr.paginate(:per_page => 2) #-> ['a', 'b'] + # paged.total_entries #-> 5 + # arr.paginate(:page => 2, :per_page => 2) #-> ['c', 'd'] + # arr.paginate(:page => 3, :per_page => 2) #-> ['e'] + # + # This method was originally {suggested by Desi + # McAdam}[http://www.desimcadam.com/archives/8] and later proved to be the + # most useful method of will_paginate library. + def paginate(options = {}) + raise ArgumentError, "parameter hash expected (got #{options.inspect})" unless Hash === options + + WillPaginate::Collection.create options[:page] || 1, + options[:per_page] || 30, + options[:total_entries] || self.length do |pager| + pager.replace self[pager.offset, pager.per_page].to_a + end + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/collection.rb b/vendor/plugins/will_paginate/lib/will_paginate/collection.rb new file mode 100644 index 0000000..89d992f --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/collection.rb @@ -0,0 +1,145 @@ +module WillPaginate + # = Invalid page number error + # This is an ArgumentError raised in case a page was requested that is either + # zero or negative number. You should decide how do deal with such errors in + # the controller. + # + # If you're using Rails 2, then this error will automatically get handled like + # 404 Not Found. The hook is in "will_paginate.rb": + # + # ActionController::Base.rescue_responses['WillPaginate::InvalidPage'] = :not_found + # + # If you don't like this, use your preffered method of rescuing exceptions in + # public from your controllers to handle this differently. The +rescue_from+ + # method is a nice addition to Rails 2. + # + # This error is *not* raised when a page further than the last page is + # requested. Use WillPaginate::Collection#out_of_bounds? method to + # check for those cases and manually deal with them as you see fit. + class InvalidPage < ArgumentError + def initialize(page, page_num) + super "#{page.inspect} given as value, which translates to '#{page_num}' as page number" + end + end + + # = The key to pagination + # Arrays returned from paginating finds are, in fact, instances of this little + # class. You may think of WillPaginate::Collection as an ordinary array with + # some extra properties. Those properties are used by view helpers to generate + # correct page links. + # + # WillPaginate::Collection also assists in rolling out your own pagination + # solutions: see +create+. + # + # If you are writing a library that provides a collection which you would like + # to conform to this API, you don't have to copy these methods over; simply + # make your plugin/gem dependant on the "will_paginate" gem: + # + # gem 'will_paginate' + # require 'will_paginate/collection' + # + # # now use WillPaginate::Collection directly or subclass it + class Collection < Array + attr_reader :current_page, :per_page, :total_entries, :total_pages + + # Arguments to the constructor are the current page number, per-page limit + # and the total number of entries. The last argument is optional because it + # is best to do lazy counting; in other words, count *conditionally* after + # populating the collection using the +replace+ method. + def initialize(page, per_page, total = nil) + @current_page = page.to_i + raise InvalidPage.new(page, @current_page) if @current_page < 1 + @per_page = per_page.to_i + raise ArgumentError, "`per_page` setting cannot be less than 1 (#{@per_page} given)" if @per_page < 1 + + self.total_entries = total if total + end + + # Just like +new+, but yields the object after instantiation and returns it + # afterwards. This is very useful for manual pagination: + # + # @entries = WillPaginate::Collection.create(1, 10) do |pager| + # result = Post.find(:all, :limit => pager.per_page, :offset => pager.offset) + # # inject the result array into the paginated collection: + # pager.replace(result) + # + # unless pager.total_entries + # # the pager didn't manage to guess the total count, do it manually + # pager.total_entries = Post.count + # end + # end + # + # The possibilities with this are endless. For another example, here is how + # WillPaginate used to define pagination for Array instances: + # + # Array.class_eval do + # def paginate(page = 1, per_page = 15) + # WillPaginate::Collection.create(page, per_page, size) do |pager| + # pager.replace self[pager.offset, pager.per_page].to_a + # end + # end + # end + # + # The Array#paginate API has since then changed, but this still serves as a + # fine example of WillPaginate::Collection usage. + def self.create(page, per_page, total = nil, &block) + pager = new(page, per_page, total) + yield pager + pager + end + + # Helper method that is true when someone tries to fetch a page with a + # larger number than the last page. Can be used in combination with flashes + # and redirecting. + def out_of_bounds? + current_page > total_pages + end + + # Current offset of the paginated collection. If we're on the first page, + # it is always 0. If we're on the 2nd page and there are 30 entries per page, + # the offset is 30. This property is useful if you want to render ordinals + # besides your records: simply start with offset + 1. + def offset + (current_page - 1) * per_page + end + + # current_page - 1 or nil if there is no previous page + def previous_page + current_page > 1 ? (current_page - 1) : nil + end + + # current_page + 1 or nil if there is no next page + def next_page + current_page < total_pages ? (current_page + 1) : nil + end + + def total_entries=(number) + @total_entries = number.to_i + @total_pages = (@total_entries / per_page.to_f).ceil + end + + # This is a magic wrapper for the original Array#replace method. It serves + # for populating the paginated collection after initialization. + # + # Why magic? Because it tries to guess the total number of entries judging + # by the size of given array. If it is shorter than +per_page+ limit, then we + # know we're on the last page. This trick is very useful for avoiding + # unnecessary hits to the database to do the counting after we fetched the + # data for the current page. + # + # However, after using +replace+ you should always test the value of + # +total_entries+ and set it to a proper value if it's +nil+. See the example + # in +create+. + def replace(array) + result = super + + # The collection is shorter then page limit? Rejoice, because + # then we know that we are on the last page! + if total_entries.nil? and length < per_page and (current_page == 1 or length > 0) + self.total_entries = offset + length + end + + result + end + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/core_ext.rb b/vendor/plugins/will_paginate/lib/will_paginate/core_ext.rb new file mode 100644 index 0000000..4601f00 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/core_ext.rb @@ -0,0 +1,58 @@ +require 'set' +require 'will_paginate/array' + +## Everything below blatantly stolen from ActiveSupport :o + +unless Hash.instance_methods.include? 'except' + Hash.class_eval do + # Returns a new hash without the given keys. + def except(*keys) + rejected = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys) + reject { |key,| rejected.include?(key) } + end + + # Replaces the hash without only the given keys. + def except!(*keys) + replace(except(*keys)) + end + end +end + +unless Hash.instance_methods.include? 'slice' + Hash.class_eval do + # Returns a new hash with only the given keys. + def slice(*keys) + allowed = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys) + reject { |key,| !allowed.include?(key) } + end + + # Replaces the hash with only the given keys. + def slice!(*keys) + replace(slice(*keys)) + end + end +end + +unless String.instance_methods.include? 'constantize' + String.class_eval do + def constantize + unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ self + raise NameError, "#{self.inspect} is not a valid constant name!" + end + + Object.module_eval("::#{$1}", __FILE__, __LINE__) + end + end +end + +unless String.instance_methods.include? 'underscore' + String.class_eval do + def underscore + self.to_s.gsub(/::/, '/'). + gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). + gsub(/([a-z\d])([A-Z])/,'\1_\2'). + tr("-", "_"). + downcase + end + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/deprecation.rb b/vendor/plugins/will_paginate/lib/will_paginate/deprecation.rb new file mode 100644 index 0000000..2c44d1e --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/deprecation.rb @@ -0,0 +1,50 @@ +# borrowed from ActiveSupport::Deprecation +module WillPaginate + module Deprecation + def self.debug() @debug; end + def self.debug=(value) @debug = value; end + self.debug = false + + # Choose the default warn behavior according to RAILS_ENV. + # Ignore deprecation warnings in production. + BEHAVIORS = { + 'test' => Proc.new { |message, callstack| + $stderr.puts(message) + $stderr.puts callstack.join("\n ") if debug + }, + 'development' => Proc.new { |message, callstack| + logger = defined?(::RAILS_DEFAULT_LOGGER) ? ::RAILS_DEFAULT_LOGGER : Logger.new($stderr) + logger.warn message + logger.debug callstack.join("\n ") if debug + } + } + + def self.warn(message, callstack = caller) + if behavior + message = 'WillPaginate: ' + message.strip.gsub(/\s+/, ' ') + behavior.call(message, callstack) + end + end + + def self.default_behavior + if defined?(RAILS_ENV) + BEHAVIORS[RAILS_ENV.to_s] + else + BEHAVIORS['test'] + end + end + + # Behavior is a block that takes a message argument. + def self.behavior() @behavior; end + def self.behavior=(value) @behavior = value; end + self.behavior = default_behavior + + def self.silence + old_behavior = self.behavior + self.behavior = nil + yield + ensure + self.behavior = old_behavior + end + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/finders.rb b/vendor/plugins/will_paginate/lib/will_paginate/finders.rb new file mode 100644 index 0000000..ca41f5b --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/finders.rb @@ -0,0 +1,9 @@ +require 'will_paginate/core_ext' + +module WillPaginate + # Database logic for different ORMs + # + # See WillPaginate::Finders::Base + module Finders + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/finders/active_record.rb b/vendor/plugins/will_paginate/lib/will_paginate/finders/active_record.rb new file mode 100644 index 0000000..84c99ff --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/finders/active_record.rb @@ -0,0 +1,204 @@ +require 'will_paginate/finders/base' +require 'active_record' + +module WillPaginate::Finders + # = Paginating finders for ActiveRecord models + # + # WillPaginate adds +paginate+, +per_page+ and other methods to + # ActiveRecord::Base class methods and associations. It also hooks into + # +method_missing+ to intercept pagination calls to dynamic finders such as + # +paginate_by_user_id+ and translate them to ordinary finders + # (+find_all_by_user_id+ in this case). + # + # In short, paginating finders are equivalent to ActiveRecord finders; the + # only difference is that we start with "paginate" instead of "find" and + # that :page is required parameter: + # + # @posts = Post.paginate :all, :page => params[:page], :order => 'created_at DESC' + # + # In paginating finders, "all" is implicit. There is no sense in paginating + # a single record, right? So, you can drop the :all argument: + # + # Post.paginate(...) => Post.find :all + # Post.paginate_all_by_something => Post.find_all_by_something + # Post.paginate_by_something => Post.find_all_by_something + # + # == The importance of the :order parameter + # + # In ActiveRecord finders, :order parameter specifies columns for + # the ORDER BY clause in SQL. It is important to have it, since + # pagination only makes sense with ordered sets. Without the ORDER + # BY clause, databases aren't required to do consistent ordering when + # performing SELECT queries; this is especially true for + # PostgreSQL. + # + # Therefore, make sure you are doing ordering on a column that makes the + # most sense in the current context. Make that obvious to the user, also. + # For perfomance reasons you will also want to add an index to that column. + module ActiveRecord + include WillPaginate::Finders::Base + + # Wraps +find_by_sql+ by simply adding LIMIT and OFFSET to your SQL string + # based on the params otherwise used by paginating finds: +page+ and + # +per_page+. + # + # Example: + # + # @developers = Developer.paginate_by_sql ['select * from developers where salary > ?', 80000], + # :page => params[:page], :per_page => 3 + # + # A query for counting rows will automatically be generated if you don't + # supply :total_entries. If you experience problems with this + # generated SQL, you might want to perform the count manually in your + # application. + # + def paginate_by_sql(sql, options) + WillPaginate::Collection.create(*wp_parse_options(options)) do |pager| + query = sanitize_sql(sql.dup) + original_query = query.dup + # add limit, offset + add_limit! query, :offset => pager.offset, :limit => pager.per_page + # perfom the find + pager.replace find_by_sql(query) + + unless pager.total_entries + count_query = original_query.sub /\bORDER\s+BY\s+[\w`,\s]+$/mi, '' + count_query = "SELECT COUNT(*) FROM (#{count_query})" + + unless ['oracle', 'oci'].include?(self.connection.adapter_name.downcase) + count_query << ' AS count_table' + end + # perform the count query + pager.total_entries = count_by_sql(count_query) + end + end + end + + def respond_to?(method, include_priv = false) #:nodoc: + super(method.to_s.sub(/^paginate/, 'find'), include_priv) + end + + protected + + def method_missing_with_paginate(method, *args, &block) #:nodoc: + # did somebody tried to paginate? if not, let them be + unless method.to_s.index('paginate') == 0 + return method_missing_without_paginate(method, *args, &block) + end + + # paginate finders are really just find_* with limit and offset + finder = method.to_s.sub('paginate', 'find') + finder.sub!('find', 'find_all') if finder.index('find_by_') == 0 + + options = args.pop + raise ArgumentError, 'parameter hash expected' unless options.respond_to? :symbolize_keys + options = options.dup + options[:finder] = finder + args << options + + paginate(*args, &block) + end + + def wp_query(options, pager, args, &block) + finder = (options.delete(:finder) || 'find').to_s + find_options = options.except(:count).update(:offset => pager.offset, :limit => pager.per_page) + + if finder == 'find' + if Array === args.first and !pager.total_entries + pager.total_entries = args.first.size + end + args << :all if args.empty? + end + + args << find_options + pager.replace send(finder, *args, &block) + + unless pager.total_entries + # magic counting + pager.total_entries = wp_count(options, args, finder) + end + end + + # Does the not-so-trivial job of finding out the total number of entries + # in the database. It relies on the ActiveRecord +count+ method. + def wp_count(options, args, finder) + # find out if we are in a model or an association proxy + klass = (@owner and @reflection) ? @reflection.klass : self + count_options = wp_parse_count_options(options, klass) + + # we may have to scope ... + counter = Proc.new { count(count_options) } + + count = if finder.index('find_') == 0 and klass.respond_to?(scoper = finder.sub('find', 'with')) + # scope_out adds a 'with_finder' method which acts like with_scope, if it's present + # then execute the count with the scoping provided by the with_finder + send(scoper, &counter) + elsif finder =~ /^find_(all_by|by)_([_a-zA-Z]\w*)$/ + # extract conditions from calls like "paginate_by_foo_and_bar" + attribute_names = $2.split('_and_') + conditions = construct_attributes_from_arguments(attribute_names, args) + with_scope(:find => { :conditions => conditions }, &counter) + else + counter.call + end + + count.respond_to?(:length) ? count.length : count + end + + def wp_parse_count_options(options, klass) + excludees = [:count, :order, :limit, :offset, :readonly] + + unless ::ActiveRecord::Calculations::CALCULATIONS_OPTIONS.include?(:from) + # :from parameter wasn't supported in count() before this change + excludees << :from + end + + # Use :select from scope if it isn't already present. + options[:select] = scope(:find, :select) unless options[:select] + + if options[:select] and options[:select] =~ /^\s*DISTINCT\b/i + # Remove quoting and check for table_name.*-like statement. + if options[:select].gsub('`', '') =~ /\w+\.\*/ + options[:select] = "DISTINCT #{klass.table_name}.#{klass.primary_key}" + end + else + excludees << :select + end + + # count expects (almost) the same options as find + count_options = options.except *excludees + + # merge the hash found in :count + # this allows you to specify :select, :order, or anything else just for the count query + count_options.update options[:count] if options[:count] + + # forget about includes if they are irrelevant (Rails 2.1) + if count_options[:include] and + klass.private_methods.include?('references_eager_loaded_tables?') and + !klass.send(:references_eager_loaded_tables?, count_options) + count_options.delete :include + end + + count_options + end + end +end + +ActiveRecord::Base.class_eval do + extend WillPaginate::Finders::ActiveRecord + class << self + alias_method_chain :method_missing, :paginate + end +end + +# support pagination on associations +a = ActiveRecord::Associations +returning([ a::AssociationCollection ]) { |classes| + # detect http://dev.rubyonrails.org/changeset/9230 + unless a::HasManyThroughAssociation.superclass == a::HasManyAssociation + classes << a::HasManyThroughAssociation + end +}.each do |klass| + klass.send :include, WillPaginate::Finders::ActiveRecord + klass.class_eval { alias_method_chain :method_missing, :paginate } +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/finders/active_record/named_scope.rb b/vendor/plugins/will_paginate/lib/will_paginate/finders/active_record/named_scope.rb new file mode 100644 index 0000000..21fc168 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/finders/active_record/named_scope.rb @@ -0,0 +1,170 @@ +module WillPaginate + # This is a feature backported from Rails 2.1 because of its usefullness not only with will_paginate, + # but in other aspects when managing complex conditions that you want to be reusable. + module NamedScope + # All subclasses of ActiveRecord::Base have two named_scopes: + # * all, which is similar to a find(:all) query, and + # * scoped, which allows for the creation of anonymous scopes, on the fly: Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions) + # + # These anonymous scopes tend to be useful when procedurally generating complex queries, where passing + # intermediate values (scopes) around as first-class objects is convenient. + def self.included(base) + base.class_eval do + extend ClassMethods + named_scope :scoped, lambda { |scope| scope } + end + end + + module ClassMethods + def scopes + read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {}) + end + + # Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query, + # such as :conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions. + # + # class Shirt < ActiveRecord::Base + # named_scope :red, :conditions => {:color => 'red'} + # named_scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true] + # end + # + # The above calls to named_scope define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red, + # in effect, represents the query Shirt.find(:all, :conditions => {:color => 'red'}). + # + # Unlike Shirt.find(...), however, the object returned by Shirt.red is not an Array; it resembles the association object + # constructed by a has_many declaration. For instance, you can invoke Shirt.red.find(:first), Shirt.red.count, + # Shirt.red.find(:all, :conditions => {:size => 'small'}). Also, just + # as with the association objects, name scopes acts like an Array, implementing Enumerable; Shirt.red.each(&block), + # Shirt.red.first, and Shirt.red.inject(memo, &block) all behave as if Shirt.red really were an Array. + # + # These named scopes are composable. For instance, Shirt.red.dry_clean_only will produce all shirts that are both red and dry clean only. + # Nested finds and calculations also work with these compositions: Shirt.red.dry_clean_only.count returns the number of garments + # for which these criteria obtain. Similarly with Shirt.red.dry_clean_only.average(:thread_count). + # + # All scopes are available as class methods on the ActiveRecord::Base descendent upon which the scopes were defined. But they are also available to + # has_many associations. If, + # + # class Person < ActiveRecord::Base + # has_many :shirts + # end + # + # then elton.shirts.red.dry_clean_only will return all of Elton's red, dry clean + # only shirts. + # + # Named scopes can also be procedural. + # + # class Shirt < ActiveRecord::Base + # named_scope :colored, lambda { |color| + # { :conditions => { :color => color } } + # } + # end + # + # In this example, Shirt.colored('puce') finds all puce shirts. + # + # Named scopes can also have extensions, just as with has_many declarations: + # + # class Shirt < ActiveRecord::Base + # named_scope :red, :conditions => {:color => 'red'} do + # def dom_id + # 'red_shirts' + # end + # end + # end + # + # + # For testing complex named scopes, you can examine the scoping options using the + # proxy_options method on the proxy itself. + # + # class Shirt < ActiveRecord::Base + # named_scope :colored, lambda { |color| + # { :conditions => { :color => color } } + # } + # end + # + # expected_options = { :conditions => { :colored => 'red' } } + # assert_equal expected_options, Shirt.colored('red').proxy_options + def named_scope(name, options = {}, &block) + name = name.to_sym + scopes[name] = lambda do |parent_scope, *args| + Scope.new(parent_scope, case options + when Hash + options + when Proc + options.call(*args) + end, &block) + end + (class << self; self end).instance_eval do + define_method name do |*args| + scopes[name].call(self, *args) + end + end + end + end + + class Scope + attr_reader :proxy_scope, :proxy_options + + [].methods.each do |m| + unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|^find$|count|sum|average|maximum|minimum|paginate|first|last|empty\?|respond_to\?)/ + delegate m, :to => :proxy_found + end + end + + delegate :scopes, :with_scope, :to => :proxy_scope + + def initialize(proxy_scope, options, &block) + [options[:extend]].flatten.each { |extension| extend extension } if options[:extend] + extend Module.new(&block) if block_given? + @proxy_scope, @proxy_options = proxy_scope, options.except(:extend) + end + + def reload + load_found; self + end + + def first(*args) + if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash)) + proxy_found.first(*args) + else + find(:first, *args) + end + end + + def last(*args) + if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash)) + proxy_found.last(*args) + else + find(:last, *args) + end + end + + def empty? + @found ? @found.empty? : count.zero? + end + + def respond_to?(method, include_private = false) + super || @proxy_scope.respond_to?(method, include_private) + end + + protected + def proxy_found + @found || load_found + end + + private + def method_missing(method, *args, &block) + if scopes.include?(method) + scopes[method].call(self, *args) + else + with_scope :find => proxy_options do + proxy_scope.send(method, *args, &block) + end + end + end + + def load_found + @found = find(:all) + end + end + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/finders/active_record/named_scope_patch.rb b/vendor/plugins/will_paginate/lib/will_paginate/finders/active_record/named_scope_patch.rb new file mode 100644 index 0000000..bdc1997 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/finders/active_record/named_scope_patch.rb @@ -0,0 +1,39 @@ +## based on http://dev.rubyonrails.org/changeset/9084 + +ActiveRecord::Associations::AssociationProxy.class_eval do + protected + def with_scope(*args, &block) + @reflection.klass.send :with_scope, *args, &block + end +end + +[ ActiveRecord::Associations::AssociationCollection, + ActiveRecord::Associations::HasManyThroughAssociation ].each do |klass| + klass.class_eval do + protected + alias :method_missing_without_scopes :method_missing_without_paginate + def method_missing_without_paginate(method, *args, &block) + if @reflection.klass.scopes.include?(method) + @reflection.klass.scopes[method].call(self, *args, &block) + else + method_missing_without_scopes(method, *args, &block) + end + end + end +end + +# Rails 1.2.6 +ActiveRecord::Associations::HasAndBelongsToManyAssociation.class_eval do + protected + def method_missing(method, *args, &block) + if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method)) + super + elsif @reflection.klass.scopes.include?(method) + @reflection.klass.scopes[method].call(self, *args) + else + @reflection.klass.with_scope(:find => { :conditions => @finder_sql, :joins => @join_sql, :readonly => false }) do + @reflection.klass.send(method, *args, &block) + end + end + end +end if ActiveRecord::Base.respond_to? :find_first diff --git a/vendor/plugins/will_paginate/lib/will_paginate/finders/active_resource.rb b/vendor/plugins/will_paginate/lib/will_paginate/finders/active_resource.rb new file mode 100644 index 0000000..9ba0236 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/finders/active_resource.rb @@ -0,0 +1,48 @@ +require 'will_paginate/finders/base' +require 'active_resource' + +module WillPaginate::Finders + # Paginate your ActiveResource models. + # + # @posts = Post.paginate :all, :params => { :page => params[:page], :order => 'created_at DESC' } + module ActiveResource + include WillPaginate::Finders::Base + + protected + + def wp_query(options, pager, args, &block) + unless args.empty? or args.first == :all + raise ArgumentError, "finder arguments other than :all are not supported for pagination (#{args.inspect} given)" + end + params = (options[:params] ||= {}) + params[:page] = pager.current_page + params[:per_page] = pager.per_page + + pager.replace find_every(options, &block) + end + + # Takes the format that Hash.from_xml produces out of an unknown type + # (produced by WillPaginate::Collection#to_xml_with_collection_type), + # parses it into a WillPaginate::Collection, + # and forwards the result to the former +instantiate_collection+ method. + # It only does this for hashes that have a :type => "collection". + def instantiate_collection_with_collection(collection, prefix_options = {}) + if collection.is_a?(Hash) && collection["type"] == "collection" + collectables = collection.values.find{ |c| c.is_a?(Hash) || c.is_a?(Array) } + collectables = [collectables].compact unless collectables.kind_of?(Array) + instantiated_collection = WillPaginate::Collection.create(collection["current_page"], collection["per_page"], collection["total_entries"]) do |pager| + pager.replace instantiate_collection_without_collection(collectables, prefix_options) + end + else + instantiate_collection_without_collection(collection, prefix_options) + end + end + end +end + +ActiveResource::Base.class_eval do + extend WillPaginate::Finders::ActiveResource + class << self + # alias_method_chain :instantiate_collection, :collection + end +end \ No newline at end of file diff --git a/vendor/plugins/will_paginate/lib/will_paginate/finders/base.rb b/vendor/plugins/will_paginate/lib/will_paginate/finders/base.rb new file mode 100644 index 0000000..f643244 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/finders/base.rb @@ -0,0 +1,80 @@ +require 'will_paginate/core_ext' + +module WillPaginate + module Finders + # Database-agnostic finder logic + module Base + def per_page + @per_page ||= 30 + end + + def per_page=(limit) + @per_page = limit.to_i + end + + # This is the main paginating finder. + # + # == Special parameters for paginating finders + # * :page -- REQUIRED, but defaults to 1 if false or nil + # * :per_page -- defaults to CurrentModel.per_page (which is 30 if not overridden) + # * :total_entries -- use only if you manually count total entries + # * :count -- additional options that are passed on to +count+ + # * :finder -- name of the finder method to use (default: "find") + # + # All other options (+conditions+, +order+, ...) are forwarded to +find+ + # and +count+ calls. + def paginate(*args, &block) + options = args.pop + page, per_page, total_entries = wp_parse_options(options) + + WillPaginate::Collection.create(page, per_page, total_entries) do |pager| + query_options = options.except :page, :per_page, :total_entries + wp_query(query_options, pager, args, &block) + end + end + + # Iterates through all records by loading one page at a time. This is useful + # for migrations or any other use case where you don't want to load all the + # records in memory at once. + # + # It uses +paginate+ internally; therefore it accepts all of its options. + # You can specify a starting page with :page (default is 1). Default + # :order is "id", override if necessary. + # + # {Jamis Buck describes this}[http://weblog.jamisbuck.org/2007/4/6/faking-cursors-in-activerecord] + # and also uses a more efficient way for MySQL. + def paginated_each(options = {}, &block) + options = { :order => 'id', :page => 1 }.merge options + options[:page] = options[:page].to_i + options[:total_entries] = 0 # skip the individual count queries + total = 0 + + begin + collection = paginate(options) + total += collection.each(&block).size + options[:page] += 1 + end until collection.size < collection.per_page + + total + end + + protected + + def wp_parse_options(options) #:nodoc: + raise ArgumentError, 'parameter hash expected' unless Hash === options + raise ArgumentError, ':page parameter required' unless options.key? :page + + if options[:count] and options[:total_entries] + raise ArgumentError, ':count and :total_entries are mutually exclusive' + end + + page = options[:page] || 1 + per_page = options[:per_page] || self.per_page + total = options[:total_entries] + + return [page, per_page, total] + end + + end + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/finders/data_mapper.rb b/vendor/plugins/will_paginate/lib/will_paginate/finders/data_mapper.rb new file mode 100644 index 0000000..c31c5fb --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/finders/data_mapper.rb @@ -0,0 +1,30 @@ +require 'will_paginate/finders/base' +require 'dm-core' + +module WillPaginate::Finders + module DataMapper + include WillPaginate::Finders::Base + + protected + + def wp_query(options, pager, args, &block) + find_options = options.except(:count).update(:offset => pager.offset, :limit => pager.per_page) + + pager.replace all(find_options, &block) + + unless pager.total_entries + pager.total_entries = wp_count(options) + end + end + + def wp_count(options) + count_options = options.except(:count, :order) + # merge the hash found in :count + count_options.update options[:count] if options[:count] + + count_options.empty?? count() : count(count_options) + end + end +end + +DataMapper::Model.send(:include, WillPaginate::Finders::DataMapper) diff --git a/vendor/plugins/will_paginate/lib/will_paginate/version.rb b/vendor/plugins/will_paginate/lib/will_paginate/version.rb new file mode 100644 index 0000000..ba92b54 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/version.rb @@ -0,0 +1,9 @@ +module WillPaginate #:nodoc: + module VERSION #:nodoc: + MAJOR = 2 + MINOR = 5 + TINY = 0 + + STRING = [MAJOR, MINOR, TINY].join('.') + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/view_helpers.rb b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers.rb new file mode 100644 index 0000000..9917cc5 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers.rb @@ -0,0 +1,39 @@ +require 'will_paginate/deprecation' + +module WillPaginate + # = Will Paginate view helpers + # + # Currently there is only one view helper: +will_paginate+. It renders the + # pagination links for the given collection. The helper itself is lightweight + # and serves only as a wrapper around link renderer instantiation; the + # renderer then does all the hard work of generating the HTML. + # + # == Global options for helpers + # + # Options for pagination helpers are optional and get their default values from the + # WillPaginate::ViewHelpers.pagination_options hash. You can write to this hash to + # override default options on the global level: + # + # WillPaginate::ViewHelpers.pagination_options[:previous_label] = 'Previous page' + # + # By putting this into your environment.rb you can easily translate link texts to previous + # and next pages, as well as override some other defaults to your liking. + module ViewHelpers + def self.pagination_options() @pagination_options; end + def self.pagination_options=(value) @pagination_options = value; end + + self.pagination_options = { + :class => 'pagination', + :previous_label => '« Previous', + :next_label => 'Next »', + :inner_window => 4, # links around the current page + :outer_window => 1, # links around beginning and end + :separator => ' ', # single space is friendly to spiders and non-graphic browsers + :param_name => :page, + :params => nil, + :renderer => 'WillPaginate::ViewHelpers::LinkRenderer', + :page_links => true, + :container => true + } + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/action_view.rb b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/action_view.rb new file mode 100644 index 0000000..51e8525 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/action_view.rb @@ -0,0 +1,82 @@ +require 'will_paginate/view_helpers/base' +require 'action_view' +require 'will_paginate/view_helpers/link_renderer' + +module WillPaginate + module ViewHelpers + # ActionView helpers for Rails integration + module ActionView + include WillPaginate::ViewHelpers::Base + + def will_paginate(collection = nil, options = {}) + options, collection = collection, nil if collection.is_a? Hash + collection ||= infer_collection_from_controller + + super(collection, options.symbolize_keys) + end + + def page_entries_info(collection = nil, options = {}) + options, collection = collection, nil if collection.is_a? Hash + collection ||= infer_collection_from_controller + + super(collection, options.symbolize_keys) + end + + # Wrapper for rendering pagination links at both top and bottom of a block + # of content. + # + # <% paginated_section @posts do %> + #
      + # <% for post in @posts %> + #
    1. ...
    2. + # <% end %> + #
    + # <% end %> + # + # will result in: + # + # + #
      + # ... + #
    + # + # + # Arguments are passed to a will_paginate call, so the same options + # apply. Don't use the :id option; otherwise you'll finish with two + # blocks of pagination links sharing the same ID (which is invalid HTML). + def paginated_section(*args, &block) + pagination = will_paginate(*args).to_s + content = pagination + capture(&block) + pagination + concat content, block.binding + end + + protected + + def infer_collection_from_controller + collection_name = "@#{controller.controller_name}" + collection = instance_variable_get(collection_name) + raise ArgumentError, "The #{collection_name} variable appears to be empty. Did you " + + "forget to pass the collection object for will_paginate?" if collection.nil? + collection + end + end + end +end + +ActionView::Base.send :include, WillPaginate::ViewHelpers::ActionView + +if defined?(ActionController::Base) and ActionController::Base.respond_to? :rescue_responses + ActionController::Base.rescue_responses['WillPaginate::InvalidPage'] = :not_found +end + +WillPaginate::ViewHelpers::LinkRenderer.class_eval do + protected + + def default_url_params + { :escape => false } + end + + def generate_url(params) + @template.url_for(params) + end +end \ No newline at end of file diff --git a/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/base.rb b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/base.rb new file mode 100644 index 0000000..c5a0ecb --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/base.rb @@ -0,0 +1,137 @@ +require 'will_paginate/core_ext' +require 'will_paginate/view_helpers' + +module WillPaginate + module ViewHelpers + module Base + # Renders Digg/Flickr-style pagination for a WillPaginate::Collection + # object. Nil is returned if there is only one page in total; no point in + # rendering the pagination in that case... + # + # ==== Options + # * :class -- CSS class name for the generated DIV (default: "pagination") + # * :previous_label -- default: "« Previous" + # * :next_label -- default: "Next »" + # * :inner_window -- how many links are shown around the current page (default: 4) + # * :outer_window -- how many links are around the first and the last page (default: 1) + # * :separator -- string separator for page HTML elements (default: single space) + # * :param_name -- parameter name for page number in URLs (default: :page) + # * :params -- additional parameters when generating pagination links + # (eg. :controller => "foo", :action => nil) + # * :renderer -- class name, class or instance of a link renderer (default: + # WillPaginate::LinkRenderer) + # * :page_links -- when false, only previous/next links are rendered (default: true) + # * :container -- toggles rendering of the DIV container for pagination links, set to + # false only when you are rendering your own pagination markup (default: true) + # * :id -- HTML ID for the container (default: nil). Pass +true+ to have the ID + # automatically generated from the class name of objects in collection: for example, paginating + # ArticleComment models would yield an ID of "article_comments_pagination". + # + # All options beside listed ones are passed as HTML attributes to the container + # element for pagination links (the DIV). For example: + # + # <%= will_paginate @posts, :id => 'wp_posts' %> + # + # ... will result in: + # + # + # + # ==== Using the helper without arguments + # If the helper is called without passing in the collection object, it will + # try to read from the instance variable inferred by the controller name. + # For example, calling +will_paginate+ while the current controller is + # PostsController will result in trying to read from the @posts + # variable. Example: + # + # <%= will_paginate :id => true %> + # + # ... will result in @post collection getting paginated: + # + # + # + def will_paginate(collection, options = {}) + # early exit if there is nothing to render + return nil unless collection.total_pages > 1 + + options = WillPaginate::ViewHelpers.pagination_options.merge(options) + + if options[:prev_label] + WillPaginate::Deprecation::warn(":prev_label view parameter is now :previous_label; the old name has been deprecated.") + options[:previous_label] = options.delete(:prev_label) + end + + # get the renderer instance + renderer = case options[:renderer] + when String + options[:renderer].constantize.new + when Class + options[:renderer].new + else + options[:renderer] + end + # render HTML for pagination + renderer.prepare collection, options, self + renderer.to_html + end + + # Renders a helpful message with numbers of displayed vs. total entries. + # You can use this as a blueprint for your own, similar helpers. + # + # <%= page_entries_info @posts %> + # #-> Displaying posts 6 - 10 of 26 in total + # + # By default, the message will use the humanized class name of objects + # in collection: for instance, "project types" for ProjectType models. + # Override this to your liking with the :entry_name parameter: + # + # <%= page_entries_info @posts, :entry_name => 'item' %> + # #-> Displaying items 6 - 10 of 26 in total + # + # Entry name is entered in singular and pluralized with + # String#pluralize method from ActiveSupport. If it isn't + # loaded, specify plural with :plural_name parameter: + # + # <%= page_entries_info @posts, :entry_name => 'item', :plural_name => 'items' %> + # + # By default, this method produces HTML output. You can trigger plain + # text output by passing :html => false in options. + def page_entries_info(collection, options = {}) + entry_name = options[:entry_name] || (collection.empty?? 'entry' : + collection.first.class.name.underscore.gsub('_', ' ')) + + plural_name = if options[:plural_name] + options[:plural_name] + elsif entry_name == 'entry' + plural_name = 'entries' + elsif entry_name.respond_to? :pluralize + plural_name = entry_name.pluralize + else + entry_name + 's' + end + + unless options[:html] == false + b = '' + eb = '' + sp = ' ' + else + b = eb = '' + sp = ' ' + end + + if collection.total_pages < 2 + case collection.size + when 0; "No #{plural_name} found" + when 1; "Displaying #{b}1#{eb} #{entry_name}" + else; "Displaying #{b}all #{collection.size}#{eb} #{plural_name}" + end + else + %{Displaying #{plural_name} #{b}%d#{sp}-#{sp}%d#{eb} of #{b}%d#{eb} in total} % [ + collection.offset + 1, + collection.offset + collection.length, + collection.total_entries + ] + end + end + end + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/link_renderer.rb b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/link_renderer.rb new file mode 100644 index 0000000..305155d --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/link_renderer.rb @@ -0,0 +1,177 @@ +require 'cgi' +require 'will_paginate/core_ext' +require 'will_paginate/view_helpers/link_renderer_base' + +module WillPaginate + module ViewHelpers + # This class does the heavy lifting of actually building the pagination + # links. It is used by +will_paginate+ helper internally. + class LinkRenderer < LinkRendererBase + + # * +collection+ is a WillPaginate::Collection instance or any other object + # that conforms to that API + # * +options+ are forwarded from +will_paginate+ view helper + # * +template+ is the reference to the template being rendered + def prepare(collection, options, template) + super(collection, options) + @template = template + @container_attributes = @base_url_params = nil + end + + # Process it! This method returns the complete HTML string which contains + # pagination links. Feel free to subclass LinkRenderer and change this + # method as you see fit. + def to_html + html = pagination.map do |item| + item.is_a?(Fixnum) ? + page_number(item) : + send(item) + end.join(@options[:separator]) + + @options[:container] ? html_container(html) : html + end + + # Returns the subset of +options+ this instance was initialized with that + # represent HTML attributes for the container element of pagination links. + def container_attributes + @container_attributes ||= begin + attributes = @options.except *(WillPaginate::ViewHelpers.pagination_options.keys - [:class]) + # pagination of Post models will have the ID of "posts_pagination" + if @options[:container] and @options[:id] === true + attributes[:id] = @collection.first.class.name.underscore.pluralize + '_pagination' + end + attributes + end + end + + protected + + def page_number(page) + unless page == current_page + link(page, page, :rel => rel_value(page)) + else + tag(:em, page) + end + end + + def gap + '' + end + + def previous_page + previous_or_next_page(@collection.previous_page, @options[:previous_label], 'previous_page') + end + + def next_page + previous_or_next_page(@collection.next_page, @options[:next_label], 'next_page') + end + + def previous_or_next_page(page, text, classname) + if page + link(text, page, :class => classname) + else + tag(:span, text, :class => classname + ' disabled') + end + end + + def html_container(html) + tag(:div, html, container_attributes) + end + + # Returns URL params for +page_link_or_span+, taking the current GET params + # and :params option into account. + def url(page) + @base_url_params ||= begin + url_params = base_url_params + merge_optional_params(url_params) + url_params + end + + url_params = @base_url_params.dup + add_current_page_param(url_params, page) + + generate_url(url_params) + end + + def default_url_params + { } + end + + def base_url_params + url_params = default_url_params + # page links should preserve GET parameters + symbolized_update(url_params, @template.params) if get_request? + url_params + end + + def merge_optional_params(url_params) + symbolized_update(url_params, @options[:params]) if @options[:params] + end + + def add_current_page_param(url_params, page) + unless param_name.index(/[^\w-]/) + url_params[param_name.to_sym] = page + else + page_param = (defined?(CGIMethods) ? CGIMethods : ActionController::AbstractRequest). + parse_query_parameters(param_name + '=' + page.to_s) + + symbolized_update(url_params, page_param) + end + end + + def get_request? + @template.request.get? + end + + def generate_url(params) + @template.url(params) + end + + private + + def link(text, target, attributes = {}) + if target.is_a? Fixnum + attributes[:rel] = rel_value(target) + target = url(target) + end + attributes[:href] = target + tag(:a, text, attributes) + end + + def tag(name, value, attributes = {}) + string_attributes = attributes.inject('') do |attrs, pair| + unless pair.last.nil? + attrs << %( #{pair.first}="#{CGI::escapeHTML(pair.last.to_s)}") + end + attrs + end + "<#{name}#{string_attributes}>#{value}" + end + + def rel_value(page) + case page + when @collection.previous_page; 'prev' + (page == 1 ? ' start' : '') + when @collection.next_page; 'next' + when 1; 'start' + end + end + + def symbolized_update(target, other) + other.each do |key, value| + key = key.to_sym + existing = target[key] + + if value.is_a?(Hash) + target[key] = existing = {} if existing.nil? + if existing.is_a?(Hash) + symbolized_update(existing, value) + return + end + end + + target[key] = value + end + end + end + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/link_renderer_base.rb b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/link_renderer_base.rb new file mode 100644 index 0000000..26f0f72 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers/link_renderer_base.rb @@ -0,0 +1,76 @@ +require 'will_paginate/view_helpers' + +module WillPaginate + module ViewHelpers + # This class does the heavy lifting of actually building the pagination + # links. It is used by +will_paginate+ helper internally. + class LinkRendererBase + + # * +collection+ is a WillPaginate::Collection instance or any other object + # that conforms to that API + # * +options+ are forwarded from +will_paginate+ view helper + def prepare(collection, options) + @collection = collection + @options = options + + # reset values in case we're re-using this instance + @total_pages = @param_name = nil + end + + def pagination + items = @options[:page_links] ? windowed_page_numbers : [] + items.unshift :previous_page + items.push :next_page + end + + protected + + # Calculates visible page numbers using the :inner_window and + # :outer_window options. + def windowed_page_numbers + inner_window, outer_window = @options[:inner_window].to_i, @options[:outer_window].to_i + window_from = current_page - inner_window + window_to = current_page + inner_window + + # adjust lower or upper limit if other is out of bounds + if window_to > total_pages + window_from -= window_to - total_pages + window_to = total_pages + end + if window_from < 1 + window_to += 1 - window_from + window_from = 1 + window_to = total_pages if window_to > total_pages + end + + visible = (1..total_pages).to_a + left_gap = (2 + outer_window)...window_from + right_gap = (window_to + 1)...(total_pages - outer_window) + + # replace page numbers that shouldn't be visible with `:gap` + [right_gap, left_gap].each do |gap| + if (gap.last - gap.first) > 1 + visible -= gap.to_a + visible.insert(gap.first - 1, :gap) + end + end + + visible + end + + private + + def current_page + @collection.current_page + end + + def total_pages + @collection.total_pages + end + + def param_name + @param_name ||= @options[:param_name].to_s + end + end + end +end diff --git a/vendor/plugins/will_paginate/spec/collection_spec.rb b/vendor/plugins/will_paginate/spec/collection_spec.rb new file mode 100644 index 0000000..4d71dc9 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/collection_spec.rb @@ -0,0 +1,147 @@ +require 'will_paginate/array' +require 'spec_helper' + +describe WillPaginate::Collection do + + before :all do + @simple = ('a'..'e').to_a + end + + it "should be a subset of original collection" do + @simple.paginate(:page => 1, :per_page => 3).should == %w( a b c ) + end + + it "can be shorter than per_page if on last page" do + @simple.paginate(:page => 2, :per_page => 3).should == %w( d e ) + end + + it "should include whole collection if per_page permits" do + @simple.paginate(:page => 1, :per_page => 5).should == @simple + end + + it "should be empty if out of bounds" do + @simple.paginate(:page => 2, :per_page => 5).should be_empty + end + + it "should default to 1 as current page and 30 per-page" do + result = (1..50).to_a.paginate + result.current_page.should == 1 + result.size.should == 30 + end + + describe "old API" do + it "should fail with numeric params" do + Proc.new { [].paginate(2) }.should raise_error(ArgumentError) + Proc.new { [].paginate(2, 10) }.should raise_error(ArgumentError) + end + + it "should fail with both options and numeric param" do + Proc.new { [].paginate({}, 5) }.should raise_error(ArgumentError) + end + end + + it "should give total_entries precedence over actual size" do + %w(a b c).paginate(:total_entries => 5).total_entries.should == 5 + end + + it "should be an augmented Array" do + entries = %w(a b c) + collection = create(2, 3, 10) do |pager| + pager.replace(entries).should == entries + end + + collection.should == entries + for method in %w(total_pages each offset size current_page per_page total_entries) + collection.should respond_to(method) + end + collection.should be_kind_of(Array) + collection.entries.should be_instance_of(Array) + # TODO: move to another expectation: + collection.offset.should == 3 + collection.total_pages.should == 4 + collection.should_not be_out_of_bounds + end + + describe "previous/next pages" do + it "should have previous_page nil when on first page" do + collection = create(1, 1, 3) + collection.previous_page.should be_nil + collection.next_page.should == 2 + end + + it "should have both prev/next pages" do + collection = create(2, 1, 3) + collection.previous_page.should == 1 + collection.next_page.should == 3 + end + + it "should have next_page nil when on last page" do + collection = create(3, 1, 3) + collection.previous_page.should == 2 + collection.next_page.should be_nil + end + end + + it "should show out of bounds when page number is too high" do + create(2, 3, 2).should be_out_of_bounds + end + + it "should not show out of bounds when inside collection" do + create(1, 3, 2).should_not be_out_of_bounds + end + + describe "guessing total count" do + it "can guess when collection is shorter than limit" do + collection = create { |p| p.replace array } + collection.total_entries.should == 8 + end + + it "should allow explicit total count to override guessed" do + collection = create(2, 5, 10) { |p| p.replace array } + collection.total_entries.should == 10 + end + + it "should not be able to guess when collection is same as limit" do + collection = create { |p| p.replace array(5) } + collection.total_entries.should be_nil + end + + it "should not be able to guess when collection is empty" do + collection = create { |p| p.replace array(0) } + collection.total_entries.should be_nil + end + + it "should be able to guess when collection is empty and this is the first page" do + collection = create(1) { |p| p.replace array(0) } + collection.total_entries.should == 0 + end + end + + it "should raise WillPaginate::InvalidPage on invalid input" do + for bad_input in [0, -1, nil, '', 'Schnitzel'] + Proc.new { create bad_input }.should raise_error(WillPaginate::InvalidPage) + end + end + + it "should raise Argument error on invalid per_page setting" do + Proc.new { create(1, -1) }.should raise_error(ArgumentError) + end + + it "should not respond to page_count anymore" do + Proc.new { create.page_count }.should raise_error(NoMethodError) + end + + private + + def create(page = 2, limit = 5, total = nil, &block) + if block_given? + WillPaginate::Collection.create(page, limit, total, &block) + else + WillPaginate::Collection.new(page, limit, total) + end + end + + def array(size = 3) + Array.new(size) + end +end diff --git a/vendor/plugins/will_paginate/spec/console b/vendor/plugins/will_paginate/spec/console new file mode 100755 index 0000000..0d3a360 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/console @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb' +libs = [] + +libs << 'irb/completion' +libs << 'console_fixtures' + +exec "#{irb} -Ilib:spec#{libs.map{ |l| " -r #{l}" }.join} --simple-prompt" diff --git a/vendor/plugins/will_paginate/spec/console_fixtures.rb b/vendor/plugins/will_paginate/spec/console_fixtures.rb new file mode 100644 index 0000000..1f0853f --- /dev/null +++ b/vendor/plugins/will_paginate/spec/console_fixtures.rb @@ -0,0 +1,8 @@ +require 'will_paginate/finders/active_record' +require 'finders/activerecord_test_connector' +ActiverecordTestConnector.setup + +# load all fixtures +Fixtures.create_fixtures(ActiverecordTestConnector::FIXTURES_PATH, ActiveRecord::Base.connection.tables) + + diff --git a/vendor/plugins/will_paginate/spec/database.yml b/vendor/plugins/will_paginate/spec/database.yml new file mode 100644 index 0000000..7ef1e73 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/database.yml @@ -0,0 +1,22 @@ +sqlite3: + database: ":memory:" + adapter: sqlite3 + timeout: 500 + +sqlite2: + database: ":memory:" + adapter: sqlite2 + +mysql: + adapter: mysql + username: rails + password: mislav + encoding: utf8 + database: will_paginate_unittest + +postgres: + adapter: postgresql + username: mislav + password: mislav + database: will_paginate_unittest + min_messages: warning diff --git a/vendor/plugins/will_paginate/spec/finders/active_record_spec.rb b/vendor/plugins/will_paginate/spec/finders/active_record_spec.rb new file mode 100644 index 0000000..48866e1 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/finders/active_record_spec.rb @@ -0,0 +1,460 @@ +require 'spec_helper' +require 'will_paginate/finders/active_record' +require File.dirname(__FILE__) + '/activerecord_test_connector' + +require 'will_paginate' +WillPaginate::enable_named_scope + +class ArProject < ActiveRecord::Base + def self.column_names + ["id"] + end + + named_scope :distinct, :select => "DISTINCT #{table_name}.*" +end + +ActiverecordTestConnector.setup + +describe WillPaginate::Finders::ActiveRecord do + + extend ActiverecordTestConnector::FixtureSetup + + it "should integrate with ActiveRecord::Base" do + ActiveRecord::Base.should respond_to(:paginate) + end + + it "should paginate" do + ArProject.expects(:find).with(:all, { :limit => 5, :offset => 0 }).returns([]) + ArProject.paginate(:page => 1, :per_page => 5) + end + + it "should respond to paginate_by_sql" do + ArProject.should respond_to(:paginate_by_sql) + end + + it "should support explicit :all argument" do + ArProject.expects(:find).with(:all, instance_of(Hash)).returns([]) + ArProject.paginate(:all, :page => nil) + end + + it "should put implicit all in dynamic finders" do + ArProject.expects(:find_all_by_foo).returns([]) + ArProject.expects(:count).returns(0) + ArProject.paginate_by_foo :page => 2 + end + + it "should leave extra parameters intact" do + ArProject.expects(:find).with(:all, {:foo => 'bar', :limit => 4, :offset => 0 }).returns(Array.new(5)) + ArProject.expects(:count).with({:foo => 'bar'}).returns(1) + + ArProject.paginate :foo => 'bar', :page => 1, :per_page => 4 + end + + describe "counting" do + it "should ignore nil in :count parameter" do + ArProject.expects(:find).returns([]) + lambda { ArProject.paginate :page => nil, :count => nil }.should_not raise_error + end + + it "should guess the total count" do + ArProject.expects(:find).returns(Array.new(2)) + ArProject.expects(:count).never + + result = ArProject.paginate :page => 2, :per_page => 4 + result.total_entries.should == 6 + end + + it "should guess that there are no records" do + ArProject.expects(:find).returns([]) + ArProject.expects(:count).never + + result = ArProject.paginate :page => 1, :per_page => 4 + result.total_entries.should == 0 + end + end + + it "should not ignore :select parameter when it says DISTINCT" do + ArProject.stubs(:find).returns([]) + ArProject.expects(:count).with(:select => 'DISTINCT salary').returns(0) + ArProject.paginate :select => 'DISTINCT salary', :page => 2 + end + + it "should count with scoped select when :select => DISTINCT" do + ArProject.stubs(:find).returns([]) + ArProject.expects(:count).with(:select => 'DISTINCT ar_projects.id').returns(0) + ArProject.distinct.paginate :page => 2 + end + + it "should use :with_foo for scope-out compatibility" do + ArProject.expects(:find_best).returns(Array.new(5)) + ArProject.expects(:with_best).returns(1) + + ArProject.paginate_best :page => 1, :per_page => 4 + end + + describe "paginate_by_sql" do + it "should paginate" do + ArProject.expects(:find_by_sql).with(regexp_matches(/sql LIMIT 3(,| OFFSET) 3/)).returns([]) + ArProject.expects(:count_by_sql).with('SELECT COUNT(*) FROM (sql) AS count_table').returns(0) + + ArProject.paginate_by_sql 'sql', :page => 2, :per_page => 3 + end + + it "should respect total_entrier setting" do + ArProject.expects(:find_by_sql).returns([]) + ArProject.expects(:count_by_sql).never + + entries = ArProject.paginate_by_sql 'sql', :page => 1, :total_entries => 999 + entries.total_entries.should == 999 + end + + it "should strip the order when counting" do + ArProject.expects(:find_by_sql).returns([]) + ArProject.expects(:count_by_sql).with("SELECT COUNT(*) FROM (sql\n ) AS count_table").returns(0) + + ArProject.paginate_by_sql "sql\n ORDER\nby foo, bar, `baz` ASC", :page => 2 + end + + it "shouldn't change the original query string" do + query = 'SQL QUERY' + original_query = query.dup + ArProject.expects(:find_by_sql).returns([]) + + ArProject.paginate_by_sql(query, :page => 1) + query.should == original_query + end + end + + # TODO: counts would still be wrong! + it "should be able to paginate custom finders" do + # acts_as_taggable defines find_tagged_with(tag, options) + ArProject.expects(:find_tagged_with).with('will_paginate', :offset => 5, :limit => 5).returns([]) + ArProject.expects(:count).with({}).returns(0) + + ArProject.paginate_tagged_with 'will_paginate', :page => 2, :per_page => 5 + end + + it "should not skip count when given an array argument to a finder" do + ids = (1..8).to_a + ArProject.expects(:find_all_by_id).returns([]) + ArProject.expects(:count).returns(0) + + ArProject.paginate_by_id(ids, :per_page => 3, :page => 2, :order => 'id') + end + + # Is this Rails 2.0? Find out by testing find_all which was removed in [6998] + unless ActiveRecord::Base.respond_to? :find_all + it "should paginate array of IDs" do + # AR finders also accept arrays of IDs + # (this was broken in Rails before [6912]) + lambda { + result = Developer.paginate((1..8).to_a, :per_page => 3, :page => 2, :order => 'id') + result.map(&:id).should == (4..6).to_a + result.total_entries.should == 8 + }.should run_queries(1) + end + end + + it "doesn't mangle options" do + ArProject.expects(:find).returns([]) + options = { :page => 1 } + options.expects(:delete).never + options_before = options.dup + + ArProject.paginate(options) + options.should == options_before + end + + if ::ActiveRecord::Calculations::CALCULATIONS_OPTIONS.include?(:from) + # for ActiveRecord 2.1 and newer + it "keeps the :from parameter in count" do + ArProject.expects(:find).returns([1]) + ArProject.expects(:count).with {|options| options.key?(:from) }.returns(0) + ArProject.paginate(:page => 2, :per_page => 1, :from => 'projects') + end + else + it "excludes :from parameter from count" do + ArProject.expects(:find).returns([1]) + ArProject.expects(:count).with {|options| !options.key?(:from) }.returns(0) + ArProject.paginate(:page => 2, :per_page => 1, :from => 'projects') + end + end + + if ActiverecordTestConnector.able_to_connect + fixtures :topics, :replies, :users, :projects, :developers_projects + + it "should get first page of Topics with a single query" do + lambda { + result = Topic.paginate :page => nil + result.current_page.should == 1 + result.total_pages.should == 1 + result.size.should == 4 + }.should run_queries(1) + end + + it "should get second (inexistent) page of Topics, requiring 2 queries" do + lambda { + result = Topic.paginate :page => 2 + result.total_pages.should == 1 + result.should be_empty + }.should run_queries(2) + end + + it "should paginate with :order" do + result = Topic.paginate :page => 1, :order => 'created_at DESC' + result.should == topics(:futurama, :harvey_birdman, :rails, :ar).reverse + result.total_pages.should == 1 + end + + it "should paginate with :conditions" do + result = Topic.paginate :page => 1, :conditions => ["created_at > ?", 30.minutes.ago] + result.should == topics(:rails, :ar) + result.total_pages.should == 1 + end + + it "should paginate with :include and :conditions" do + result = Topic.paginate \ + :page => 1, + :include => :replies, + :conditions => "replies.content LIKE 'Bird%' ", + :per_page => 10 + + expected = Topic.find :all, + :include => 'replies', + :conditions => "replies.content LIKE 'Bird%' ", + :limit => 10 + + result.should == expected + result.total_entries.should == 1 + end + + it "should paginate with :include and :order" do + result = nil + lambda { + result = Topic.paginate \ + :page => 1, + :include => :replies, + :order => 'replies.created_at asc, topics.created_at asc', + :per_page => 10 + }.should run_queries(2) + + expected = Topic.find :all, + :include => 'replies', + :order => 'replies.created_at asc, topics.created_at asc', + :limit => 10 + + result.should == expected + result.total_entries.should == 4 + end + + # detect ActiveRecord 2.1 + if ActiveRecord::Base.private_methods.include?('references_eager_loaded_tables?') + it "should remove :include for count" do + Developer.expects(:find).returns([1]) + Developer.expects(:count).with({}).returns(0) + + Developer.paginate :page => 1, :per_page => 1, :include => :projects + end + + it "should keep :include for count when they are referenced in :conditions" do + Developer.expects(:find).returns([1]) + Developer.expects(:count).with({ :include => :projects, :conditions => 'projects.id > 2' }).returns(0) + + Developer.paginate :page => 1, :per_page => 1, + :include => :projects, :conditions => 'projects.id > 2' + end + end + + describe "associations" do + it "should paginate with include" do + project = projects(:active_record) + + result = project.topics.paginate \ + :page => 1, + :include => :replies, + :conditions => ["replies.content LIKE ?", 'Nice%'], + :per_page => 10 + + expected = Topic.find :all, + :include => 'replies', + :conditions => ["project_id = #{project.id} AND replies.content LIKE ?", 'Nice%'], + :limit => 10 + + result.should == expected + end + + it "should paginate" do + dhh = users(:david) + expected_name_ordered = projects(:action_controller, :active_record) + expected_id_ordered = projects(:active_record, :action_controller) + + lambda { + # with association-specified order + result = dhh.projects.paginate(:page => 1) + result.should == expected_name_ordered + result.total_entries.should == 2 + }.should run_queries(2) + + # with explicit order + result = dhh.projects.paginate(:page => 1, :order => 'projects.id') + result.should == expected_id_ordered + result.total_entries.should == 2 + + lambda { + dhh.projects.find(:all, :order => 'projects.id', :limit => 4) + }.should_not raise_error + + result = dhh.projects.paginate(:page => 1, :order => 'projects.id', :per_page => 4) + result.should == expected_id_ordered + + # has_many with implicit order + topic = Topic.find(1) + expected = replies(:spam, :witty_retort) + # FIXME: wow, this is ugly + topic.replies.paginate(:page => 1).map(&:id).sort.should == expected.map(&:id).sort + topic.replies.paginate(:page => 1, :order => 'replies.id ASC').should == expected.reverse + end + + it "should paginate through association extension" do + project = Project.find(:first) + expected = [replies(:brave)] + + lambda { + result = project.replies.paginate_recent :page => 1 + result.should == expected + }.should run_queries(1) + end + end + + it "should paginate with joins" do + result = nil + join_sql = 'LEFT JOIN developers_projects ON users.id = developers_projects.developer_id' + + lambda { + result = Developer.paginate :page => 1, :joins => join_sql, :conditions => 'project_id = 1' + result.size.should == 2 + developer_names = result.map(&:name) + developer_names.should include('David') + developer_names.should include('Jamis') + }.should run_queries(1) + + lambda { + expected = result.to_a + result = Developer.paginate :page => 1, :joins => join_sql, + :conditions => 'project_id = 1', :count => { :select => "users.id" } + result.should == expected + result.total_entries.should == 2 + }.should run_queries(1) + end + + it "should paginate with group" do + result = nil + lambda { + result = Developer.paginate :page => 1, :per_page => 10, + :group => 'salary', :select => 'salary', :order => 'salary' + }.should run_queries(1) + + expected = users(:david, :jamis, :dev_10, :poor_jamis).map(&:salary).sort + result.map(&:salary).should == expected + end + + it "should paginate with dynamic finder" do + expected = replies(:witty_retort, :spam) + Reply.paginate_by_topic_id(1, :page => 1).should == expected + + result = Developer.paginate :conditions => { :salary => 100000 }, :page => 1, :per_page => 5 + result.total_entries.should == 8 + Developer.paginate_by_salary(100000, :page => 1, :per_page => 5).should == result + end + + it "should paginate with dynamic finder and conditions" do + result = Developer.paginate_by_salary(100000, :page => 1, :conditions => ['id > ?', 6]) + result.total_entries.should == 4 + result.map(&:id).should == (7..10).to_a + end + + it "should raise error when dynamic finder is not recognized" do + lambda { + Developer.paginate_by_inexistent_attribute 100000, :page => 1 + }.should raise_error(NoMethodError) + end + + it "should paginate with_scope" do + result = Developer.with_poor_ones { Developer.paginate :page => 1 } + result.size.should == 2 + result.total_entries.should == 2 + end + + describe "named_scope" do + it "should paginate" do + result = Developer.poor.paginate :page => 1, :per_page => 1 + result.size.should == 1 + result.total_entries.should == 2 + end + + it "should paginate on habtm association" do + project = projects(:active_record) + lambda { + result = project.developers.poor.paginate :page => 1, :per_page => 1 + result.size.should == 1 + result.total_entries.should == 1 + }.should run_queries(2) + end + + it "should paginate on hmt association" do + project = projects(:active_record) + expected = [replies(:brave)] + + lambda { + result = project.replies.recent.paginate :page => 1, :per_page => 1 + result.should == expected + result.total_entries.should == 1 + }.should run_queries(2) + end + + it "should paginate on has_many association" do + project = projects(:active_record) + expected = [topics(:ar)] + + lambda { + result = project.topics.mentions_activerecord.paginate :page => 1, :per_page => 1 + result.should == expected + result.total_entries.should == 1 + }.should run_queries(2) + end + end + + it "should paginate with :readonly option" do + lambda { Developer.paginate :readonly => true, :page => 1 }.should_not raise_error + end + + end + + protected + + def run_queries(num) + QueryCountMatcher.new(num) + end + +end + +class QueryCountMatcher + def initialize(num) + @queries = num + @old_query_count = $query_count + end + + def matches?(block) + block.call + @queries_run = $query_count - @old_query_count + @queries == @queries_run + end + + def failure_message + "expected #{@queries} queries, got #{@queries_run}" + end + + def negative_failure_message + "expected query count not to be #{$queries}" + end +end \ No newline at end of file diff --git a/vendor/plugins/will_paginate/spec/finders/active_resource_spec.rb b/vendor/plugins/will_paginate/spec/finders/active_resource_spec.rb new file mode 100644 index 0000000..e00bb12 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/finders/active_resource_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' +require 'will_paginate/finders/active_resource' +require 'active_resource/http_mock' + +class AresProject < ActiveResource::Base + self.site = 'http://localhost:4000' +end + +describe WillPaginate::Finders::ActiveResource do + + before :all do + # ActiveResource::HttpMock.respond_to do |mock| + # mock.get "/ares_projects.xml?page=1&per_page=5", {}, [].to_xml + # end + end + + it "should integrate with ActiveResource::Base" do + ActiveResource::Base.should respond_to(:paginate) + end + + it "should error when no parameters for #paginate" do + lambda { AresProject.paginate }.should raise_error(ArgumentError) + end + + it "should paginate" do + AresProject.expects(:find_every).with(:params => { :page => 1, :per_page => 5 }).returns([]) + AresProject.paginate(:page => 1, :per_page => 5) + end + + it "should have 30 per_page as default" do + AresProject.expects(:find_every).with(:params => { :page => 1, :per_page => 30 }).returns([]) + AresProject.paginate(:page => 1) + end + + it "should support #paginate(:all)" do + lambda { AresProject.paginate(:all) }.should raise_error(ArgumentError) + end + + it "should error #paginate(:other)" do + lambda { AresProject.paginate(:first) }.should raise_error(ArgumentError) + end + + protected + + def create(page = 2, limit = 5, total = nil, &block) + if block_given? + WillPaginate::Collection.create(page, limit, total, &block) + else + WillPaginate::Collection.new(page, limit, total) + end + end +end diff --git a/vendor/plugins/will_paginate/spec/finders/activerecord_test_connector.rb b/vendor/plugins/will_paginate/spec/finders/activerecord_test_connector.rb new file mode 100644 index 0000000..fdbb233 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/finders/activerecord_test_connector.rb @@ -0,0 +1,107 @@ +require 'active_record' +require 'active_record/version' +require 'active_record/fixtures' + +class ActiverecordTestConnector + cattr_accessor :able_to_connect + cattr_accessor :connected + + FIXTURES_PATH = File.join(File.dirname(__FILE__), '..', 'fixtures') + + # Set our defaults + self.connected = false + self.able_to_connect = true + + def self.setup + unless self.connected || !self.able_to_connect + setup_connection + load_schema + add_load_path FIXTURES_PATH + self.connected = true + end + rescue Exception => e # errors from ActiveRecord setup + $stderr.puts "\nSkipping ActiveRecord tests: #{e}\n\n" + self.able_to_connect = false + end + + private + + def self.add_load_path(path) + dep = defined?(ActiveSupport::Dependencies) ? ActiveSupport::Dependencies : ::Dependencies + dep.load_paths.unshift path + end + + def self.setup_connection + db = ENV['DB'].blank?? 'sqlite3' : ENV['DB'] + + configurations = YAML.load_file(File.join(File.dirname(__FILE__), '..', 'database.yml')) + raise "no configuration for '#{db}'" unless configurations.key? db + configuration = configurations[db] + + ActiveRecord::Base.logger = Logger.new(STDOUT) if $0 == 'irb' + puts "using #{configuration['adapter']} adapter" unless ENV['DB'].blank? + + ActiveRecord::Base.establish_connection(configuration) + ActiveRecord::Base.configurations = { db => configuration } + prepare ActiveRecord::Base.connection + + unless Object.const_defined?(:QUOTED_TYPE) + Object.send :const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name('type') + end + end + + def self.load_schema + ActiveRecord::Base.silence do + ActiveRecord::Migration.verbose = false + load File.join(FIXTURES_PATH, 'schema.rb') + end + end + + def self.prepare(conn) + class << conn + IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SHOW FIELDS /] + + def execute_with_counting(sql, name = nil, &block) + $query_count ||= 0 + $query_count += 1 unless IGNORED_SQL.any? { |r| sql =~ r } + execute_without_counting(sql, name, &block) + end + + alias_method_chain :execute, :counting + end + end + + module FixtureSetup + def fixtures(*tables) + table_names = tables.map { |t| t.to_s } + + fixtures = Fixtures.create_fixtures ActiverecordTestConnector::FIXTURES_PATH, table_names + @@loaded_fixtures = {} + @@fixture_cache = {} + + unless fixtures.nil? + if fixtures.instance_of?(Fixtures) + @@loaded_fixtures[fixtures.table_name] = fixtures + else + fixtures.each { |f| @@loaded_fixtures[f.table_name] = f } + end + end + + table_names.each do |table_name| + define_method(table_name) do |*fixtures| + @@fixture_cache[table_name] ||= {} + + instances = fixtures.map do |fixture| + if @@loaded_fixtures[table_name][fixture.to_s] + @@fixture_cache[table_name][fixture] ||= @@loaded_fixtures[table_name][fixture.to_s].find + else + raise StandardError, "No fixture with name '#{fixture}' found for table '#{table_name}'" + end + end + + instances.size == 1 ? instances.first : instances + end + end + end + end +end diff --git a/vendor/plugins/will_paginate/spec/finders_spec.rb b/vendor/plugins/will_paginate/spec/finders_spec.rb new file mode 100644 index 0000000..0782fbe --- /dev/null +++ b/vendor/plugins/will_paginate/spec/finders_spec.rb @@ -0,0 +1,76 @@ +require 'spec_helper' +require 'will_paginate/finders/base' + +class Model + extend WillPaginate::Finders::Base +end + +describe WillPaginate::Finders::Base do + it "should define default per_page of 30" do + Model.per_page.should == 30 + end + + it "should allow to set custom per_page" do + begin + Model.per_page = 25 + Model.per_page.should == 25 + ensure + Model.per_page = 30 + end + end + + it "should result with WillPaginate::Collection" do + Model.expects(:wp_query) + Model.paginate(:page => nil).should be_instance_of(WillPaginate::Collection) + end + + it "should delegate pagination to wp_query" do + Model.expects(:wp_query).with({}, instance_of(WillPaginate::Collection), []) + Model.paginate :page => nil + end + + it "should complain when no hash parameters given" do + lambda { + Model.paginate + }.should raise_error(ArgumentError, 'parameter hash expected') + end + + it "should complain when no :page parameter present" do + lambda { + Model.paginate :per_page => 6 + }.should raise_error(ArgumentError, ':page parameter required') + end + + it "should complain when both :count and :total_entries are given" do + lambda { + Model.paginate :page => 1, :count => {}, :total_entries => 1 + }.should raise_error(ArgumentError, ':count and :total_entries are mutually exclusive') + end + + it "should never mangle options" do + options = { :page => 1 } + options.expects(:delete).never + options_before = options.dup + + Model.expects(:wp_query) + Model.paginate(options) + + options.should == options_before + end + + it "should provide paginated_each functionality" do + collection = stub('collection', :size => 5, :empty? => false, :per_page => 5) + collection.expects(:each).times(2).returns(collection) + last_collection = stub('collection', :size => 4, :empty? => false, :per_page => 5) + last_collection.expects(:each).returns(last_collection) + + params = { :order => 'id', :total_entries => 0 } + + Model.expects(:paginate).with(params.merge(:page => 2)).returns(collection) + Model.expects(:paginate).with(params.merge(:page => 3)).returns(collection) + Model.expects(:paginate).with(params.merge(:page => 4)).returns(last_collection) + + total = Model.paginated_each(:page => '2') { } + total.should == 14 + end +end diff --git a/vendor/plugins/will_paginate/spec/fixtures/admin.rb b/vendor/plugins/will_paginate/spec/fixtures/admin.rb new file mode 100644 index 0000000..1d5e7f3 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/admin.rb @@ -0,0 +1,3 @@ +class Admin < User + has_many :companies, :finder_sql => 'SELECT * FROM companies' +end diff --git a/vendor/plugins/will_paginate/spec/fixtures/developer.rb b/vendor/plugins/will_paginate/spec/fixtures/developer.rb new file mode 100644 index 0000000..7105355 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/developer.rb @@ -0,0 +1,13 @@ +class Developer < User + has_and_belongs_to_many :projects, :include => :topics, :order => 'projects.name' + + def self.with_poor_ones(&block) + with_scope :find => { :conditions => ['salary <= ?', 80000], :order => 'salary' } do + yield + end + end + + named_scope :poor, :conditions => ['salary <= ?', 80000], :order => 'salary' + + def self.per_page() 10 end +end diff --git a/vendor/plugins/will_paginate/spec/fixtures/developers_projects.yml b/vendor/plugins/will_paginate/spec/fixtures/developers_projects.yml new file mode 100644 index 0000000..cee359c --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/developers_projects.yml @@ -0,0 +1,13 @@ +david_action_controller: + developer_id: 1 + project_id: 2 + joined_on: 2004-10-10 + +david_active_record: + developer_id: 1 + project_id: 1 + joined_on: 2004-10-10 + +jamis_active_record: + developer_id: 2 + project_id: 1 \ No newline at end of file diff --git a/vendor/plugins/will_paginate/spec/fixtures/project.rb b/vendor/plugins/will_paginate/spec/fixtures/project.rb new file mode 100644 index 0000000..0f85ef5 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/project.rb @@ -0,0 +1,15 @@ +class Project < ActiveRecord::Base + has_and_belongs_to_many :developers, :uniq => true + + has_many :topics + # :finder_sql => 'SELECT * FROM topics WHERE (topics.project_id = #{id})', + # :counter_sql => 'SELECT COUNT(*) FROM topics WHERE (topics.project_id = #{id})' + + has_many :replies, :through => :topics do + def find_recent(params = {}) + with_scope :find => { :conditions => ['replies.created_at > ?', 15.minutes.ago] } do + find :all, params + end + end + end +end diff --git a/vendor/plugins/will_paginate/spec/fixtures/projects.yml b/vendor/plugins/will_paginate/spec/fixtures/projects.yml new file mode 100644 index 0000000..74f3c32 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/projects.yml @@ -0,0 +1,6 @@ +active_record: + id: 1 + name: Active Record +action_controller: + id: 2 + name: Active Controller diff --git a/vendor/plugins/will_paginate/spec/fixtures/replies.yml b/vendor/plugins/will_paginate/spec/fixtures/replies.yml new file mode 100644 index 0000000..9a83c00 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/replies.yml @@ -0,0 +1,29 @@ +witty_retort: + id: 1 + topic_id: 1 + content: Birdman is better! + created_at: <%= 6.hours.ago.to_s(:db) %> + +another: + id: 2 + topic_id: 2 + content: Nuh uh! + created_at: <%= 1.hour.ago.to_s(:db) %> + +spam: + id: 3 + topic_id: 1 + content: Nice site! + created_at: <%= 1.hour.ago.to_s(:db) %> + +decisive: + id: 4 + topic_id: 4 + content: "I'm getting to the bottom of this" + created_at: <%= 30.minutes.ago.to_s(:db) %> + +brave: + id: 5 + topic_id: 4 + content: "AR doesn't scare me a bit" + created_at: <%= 10.minutes.ago.to_s(:db) %> diff --git a/vendor/plugins/will_paginate/spec/fixtures/reply.rb b/vendor/plugins/will_paginate/spec/fixtures/reply.rb new file mode 100644 index 0000000..ecaf3c1 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/reply.rb @@ -0,0 +1,7 @@ +class Reply < ActiveRecord::Base + belongs_to :topic, :include => [:replies] + + named_scope :recent, :conditions => ['replies.created_at > ?', 15.minutes.ago] + + validates_presence_of :content +end diff --git a/vendor/plugins/will_paginate/spec/fixtures/schema.rb b/vendor/plugins/will_paginate/spec/fixtures/schema.rb new file mode 100644 index 0000000..8831aad --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/schema.rb @@ -0,0 +1,38 @@ +ActiveRecord::Schema.define do + + create_table "users", :force => true do |t| + t.column "name", :text + t.column "salary", :integer, :default => 70000 + t.column "created_at", :datetime + t.column "updated_at", :datetime + t.column "type", :text + end + + create_table "projects", :force => true do |t| + t.column "name", :text + end + + create_table "developers_projects", :id => false, :force => true do |t| + t.column "developer_id", :integer, :null => false + t.column "project_id", :integer, :null => false + t.column "joined_on", :date + t.column "access_level", :integer, :default => 1 + end + + create_table "topics", :force => true do |t| + t.column "project_id", :integer + t.column "title", :string + t.column "subtitle", :string + t.column "content", :text + t.column "created_at", :datetime + t.column "updated_at", :datetime + end + + create_table "replies", :force => true do |t| + t.column "content", :text + t.column "created_at", :datetime + t.column "updated_at", :datetime + t.column "topic_id", :integer + end + +end diff --git a/vendor/plugins/will_paginate/spec/fixtures/topic.rb b/vendor/plugins/will_paginate/spec/fixtures/topic.rb new file mode 100644 index 0000000..77be0dd --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/topic.rb @@ -0,0 +1,6 @@ +class Topic < ActiveRecord::Base + has_many :replies, :dependent => :destroy, :order => 'replies.created_at DESC' + belongs_to :project + + named_scope :mentions_activerecord, :conditions => ['topics.title LIKE ?', '%ActiveRecord%'] +end diff --git a/vendor/plugins/will_paginate/spec/fixtures/topics.yml b/vendor/plugins/will_paginate/spec/fixtures/topics.yml new file mode 100644 index 0000000..0a26904 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/topics.yml @@ -0,0 +1,30 @@ +futurama: + id: 1 + title: Isnt futurama awesome? + subtitle: It really is, isnt it. + content: I like futurama + created_at: <%= 1.day.ago.to_s(:db) %> + updated_at: + +harvey_birdman: + id: 2 + title: Harvey Birdman is the king of all men + subtitle: yup + content: He really is + created_at: <%= 2.hours.ago.to_s(:db) %> + updated_at: + +rails: + id: 3 + project_id: 1 + title: Rails is nice + subtitle: It makes me happy + content: except when I have to hack internals to fix pagination. even then really. + created_at: <%= 20.minutes.ago.to_s(:db) %> + +ar: + id: 4 + project_id: 1 + title: ActiveRecord sometimes freaks me out + content: "I mean, what's the deal with eager loading?" + created_at: <%= 15.minutes.ago.to_s(:db) %> diff --git a/vendor/plugins/will_paginate/spec/fixtures/user.rb b/vendor/plugins/will_paginate/spec/fixtures/user.rb new file mode 100644 index 0000000..4a57cf0 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/user.rb @@ -0,0 +1,2 @@ +class User < ActiveRecord::Base +end diff --git a/vendor/plugins/will_paginate/spec/fixtures/users.yml b/vendor/plugins/will_paginate/spec/fixtures/users.yml new file mode 100644 index 0000000..ed2c03a --- /dev/null +++ b/vendor/plugins/will_paginate/spec/fixtures/users.yml @@ -0,0 +1,35 @@ +david: + id: 1 + name: David + salary: 80000 + type: Developer + +jamis: + id: 2 + name: Jamis + salary: 150000 + type: Developer + +<% for digit in 3..10 %> +dev_<%= digit %>: + id: <%= digit %> + name: fixture_<%= digit %> + salary: 100000 + type: Developer +<% end %> + +poor_jamis: + id: 11 + name: Jamis + salary: 9000 + type: Developer + +admin: + id: 12 + name: admin + type: Admin + +goofy: + id: 13 + name: Goofy + type: Admin diff --git a/vendor/plugins/will_paginate/spec/rcov.opts b/vendor/plugins/will_paginate/spec/rcov.opts new file mode 100644 index 0000000..6b17c32 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/rcov.opts @@ -0,0 +1,2 @@ +--exclude ^\/,^spec\/,core_ext.rb,deprecation.rb +--no-validator-links \ No newline at end of file diff --git a/vendor/plugins/will_paginate/spec/spec.opts b/vendor/plugins/will_paginate/spec/spec.opts new file mode 100644 index 0000000..14f5f13 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/spec.opts @@ -0,0 +1,2 @@ +--colour +--reverse diff --git a/vendor/plugins/will_paginate/spec/spec_helper.rb b/vendor/plugins/will_paginate/spec/spec_helper.rb new file mode 100644 index 0000000..46a26e5 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/spec_helper.rb @@ -0,0 +1,76 @@ +require 'rubygems' +gem 'rspec', '~> 1.1.4' +require 'spec' + +module MyExtras + protected + + def include_phrase(string) + PhraseMatcher.new(string) + end + + def collection(params = {}) + if params[:total_pages] + params[:per_page] = 1 + params[:total_entries] = params[:total_pages] + end + WillPaginate::Collection.new(params[:page] || 1, params[:per_page] || 30, params[:total_entries]) + end + + def have_deprecation + DeprecationMatcher.new + end +end + +Spec::Runner.configure do |config| + # config.include My::Pony, My::Horse, :type => :farm + config.include MyExtras + # config.predicate_matchers[:swim] = :can_swim? + + config.mock_with :mocha +end + +class PhraseMatcher + def initialize(string) + @string = string + @pattern = /\b#{string}\b/ + end + + def matches?(actual) + @actual = actual.to_s + @actual =~ @pattern + end + + def failure_message + "expected #{@actual.inspect} to contain phrase #{@string.inspect}" + end + + def negative_failure_message + "expected #{@actual.inspect} not to contain phrase #{@string.inspect}" + end +end + +class DeprecationMatcher + def initialize + @old_behavior = WillPaginate::Deprecation.behavior + @messages = [] + WillPaginate::Deprecation.behavior = lambda { |message, callstack| + @messages << message + } + end + + def matches?(block) + block.call + !@messages.empty? + ensure + WillPaginate::Deprecation.behavior = @old_behavior + end + + def failure_message + "expected block to raise a deprecation warning" + end + + def negative_failure_message + "expected block not to raise deprecation warnings, #{@messages.size} raised" + end +end diff --git a/vendor/plugins/will_paginate/spec/tasks.rake b/vendor/plugins/will_paginate/spec/tasks.rake new file mode 100644 index 0000000..cb04366 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/tasks.rake @@ -0,0 +1,34 @@ +require 'spec/rake/spectask' + +spec_opts = 'spec/spec.opts' + +desc 'Run all specs' +Spec::Rake::SpecTask.new(:spec) do |t| + t.libs << 'lib' << 'spec' + t.spec_opts = ['--options', spec_opts] +end + +namespace :spec do + desc 'Analyze spec coverage with RCov' + Spec::Rake::SpecTask.new(:rcov) do |t| + t.libs << 'lib' << 'spec' + t.spec_opts = ['--options', spec_opts] + t.rcov = true + t.rcov_opts = lambda do + IO.readlines('spec/rcov.opts').map { |l| l.chomp.split(" ") }.flatten + end + end + + desc 'Print Specdoc for all specs' + Spec::Rake::SpecTask.new(:doc) do |t| + t.libs << 'lib' << 'spec' + t.spec_opts = ['--format', 'specdoc', '--dry-run'] + end + + desc 'Generate HTML report' + Spec::Rake::SpecTask.new(:html) do |t| + t.libs << 'lib' << 'spec' + t.spec_opts = ['--format', 'html:doc/spec_results.html', '--diff'] + t.fail_on_error = false + end +end diff --git a/vendor/plugins/will_paginate/spec/view_helpers/action_view_spec.rb b/vendor/plugins/will_paginate/spec/view_helpers/action_view_spec.rb new file mode 100644 index 0000000..8b34a1b --- /dev/null +++ b/vendor/plugins/will_paginate/spec/view_helpers/action_view_spec.rb @@ -0,0 +1,343 @@ +require 'spec_helper' +require 'action_controller' +require 'view_helpers/view_example_group' +require 'will_paginate/view_helpers/action_view' +require 'will_paginate/collection' + +ActionController::Routing::Routes.draw do |map| + map.connect 'dummy/page/:page', :controller => 'dummy' + map.connect 'dummy/dots/page.:page', :controller => 'dummy', :action => 'dots' + map.connect 'ibocorp/:page', :controller => 'ibocorp', + :requirements => { :page => /\d+/ }, + :defaults => { :page => 1 } + + map.connect ':controller/:action/:id' +end + +describe WillPaginate::ViewHelpers::ActionView do + before(:each) do + @view = ActionView::Base.new + @view.controller = DummyController.new + @view.request = @view.controller.request + @template = '<%= will_paginate collection, options %>' + end + + def request + @view.request + end + + def render(locals) + @view.render(:inline => @template, :locals => locals) + end + + ## basic pagination ## + + it "should render" do + paginate do |pagination| + assert_select 'a[href]', 3 do |elements| + validate_page_numbers [2,3,2], elements + assert_select elements.last, ':last-child', "Next »" + end + assert_select 'span', 1 + assert_select 'span.disabled:first-child', '« Previous' + assert_select 'em', '1' + pagination.first.inner_text.should == '« Previous 1 2 3 Next »' + end + end + + it "should render nothing when there is only 1 page" do + paginate(:per_page => 30).should be_empty + end + + it "should paginate with options" do + paginate({ :page => 2 }, :class => 'will_paginate', :previous_label => 'Prev', :next_label => 'Next') do + assert_select 'a[href]', 4 do |elements| + validate_page_numbers [1,1,3,3], elements + # test rel attribute values: + assert_select elements[1], 'a', '1' do |link| + link.first['rel'].should == 'prev start' + end + assert_select elements.first, 'a', "Prev" do |link| + link.first['rel'].should == 'prev start' + end + assert_select elements.last, 'a', "Next" do |link| + link.first['rel'].should == 'next' + end + end + assert_select 'em', '2' + end + end + + it "should paginate using a custom renderer class" do + paginate({}, :renderer => AdditionalLinkAttributesRenderer) do + assert_select 'a[default=true]', 3 + end + end + + it "should paginate using a custom renderer instance" do + renderer = WillPaginate::ViewHelpers::LinkRenderer.new + def renderer.gap() '~~' end + + paginate({ :per_page => 2 }, :inner_window => 0, :outer_window => 0, :renderer => renderer) do + assert_select 'span.my-gap', '~~' + end + + renderer = AdditionalLinkAttributesRenderer.new(:title => 'rendered') + paginate({}, :renderer => renderer) do + assert_select 'a[title=rendered]', 3 + end + end + + it "should have classnames on previous/next links" do + paginate do |pagination| + assert_select 'span.disabled.previous_page:first-child' + assert_select 'a.next_page[href]:last-child' + end + end + + it "should warn about :prev_label being deprecated" do + lambda { + paginate({ :page => 2 }, :prev_label => 'Deprecated') do + assert_select 'a[href]:first-child', 'Deprecated' + end + }.should have_deprecation + end + + it "should match expected markup" do + paginate + expected = <<-HTML + + HTML + expected.strip!.gsub!(/\s{2,}/, ' ') + expected_dom = HTML::Document.new(expected).root + + html_document.root.should == expected_dom + end + + it "should output escaped URLs" do + paginate({:page => 1, :per_page => 1, :total_entries => 2}, + :page_links => false, :params => { :tag => '
    ' }) + + assert_select 'a[href]', 1 do |links| + query = links.first['href'].split('?', 2)[1] + query.split('&').sort.should == %w(page=2 tag=%3Cbr%3E) + end + end + + ## advanced options for pagination ## + + it "should be able to render without container" do + paginate({}, :container => false) + assert_select 'div.pagination', 0, 'main DIV present when it shouldn\'t' + assert_select 'a[href]', 3 + end + + it "should be able to render without page links" do + paginate({ :page => 2 }, :page_links => false) do + assert_select 'a[href]', 2 do |elements| + validate_page_numbers [1,3], elements + end + end + end + + it "should have magic HTML ID for the container" do + paginate do |div| + div.first['id'].should be_nil + end + + # magic ID + paginate({}, :id => true) do |div| + div.first['id'].should == 'fixnums_pagination' + end + + # explicit ID + paginate({}, :id => 'custom_id') do |div| + div.first['id'].should == 'custom_id' + end + end + + ## other helpers ## + + it "should render a paginated section" do + @template = <<-ERB + <% paginated_section collection, options do %> + <%= content_tag :div, '', :id => "developers" %> + <% end %> + ERB + + paginate + assert_select 'div.pagination', 2 + assert_select 'div.pagination + div#developers', 1 + end + + ## parameter handling in page links ## + + it "should preserve parameters on GET" do + request.params :foo => { :bar => 'baz' } + paginate + assert_links_match /foo%5Bbar%5D=baz/ + end + + it "should not preserve parameters on POST" do + request.post + request.params :foo => 'bar' + paginate + assert_no_links_match /foo=bar/ + end + + it "should add additional parameters to links" do + paginate({}, :params => { :foo => 'bar' }) + assert_links_match /foo=bar/ + end + + it "should add anchor parameter" do + paginate({}, :params => { :anchor => 'anchor' }) + assert_links_match /#anchor$/ + end + + it "should remove arbitrary parameters" do + request.params :foo => 'bar' + paginate({}, :params => { :foo => nil }) + assert_no_links_match /foo=bar/ + end + + it "should override default route parameters" do + paginate({}, :params => { :controller => 'baz', :action => 'list' }) + assert_links_match %r{\Wbaz/list\W} + end + + it "should paginate with custom page parameter" do + paginate({ :page => 2 }, :param_name => :developers_page) do + assert_select 'a[href]', 4 do |elements| + validate_page_numbers [1,1,3,3], elements, :developers_page + end + end + end + + it "should paginate with complex custom page parameter" do + request.params :developers => { :page => 2 } + + paginate({ :page => 2 }, :param_name => 'developers[page]') do + assert_select 'a[href]', 4 do |links| + assert_links_match /\?developers%5Bpage%5D=\d+$/, links + validate_page_numbers [1,1,3,3], links, 'developers[page]' + end + end + end + + it "should paginate with custom route page parameter" do + request.symbolized_path_parameters.update :controller => 'dummy', :action => nil + paginate :per_page => 2 do + assert_select 'a[href]', 6 do |links| + assert_links_match %r{/page/(\d+)$}, links, [2, 3, 4, 5, 6, 2] + end + end + end + + it "should paginate with custom route with dot separator page parameter" do + request.symbolized_path_parameters.update :controller => 'dummy', :action => 'dots' + paginate :per_page => 2 do + assert_select 'a[href]', 6 do |links| + assert_links_match %r{/page\.(\d+)$}, links, [2, 3, 4, 5, 6, 2] + end + end + end + + it "should paginate with custom route and first page number implicit" do + request.symbolized_path_parameters.update :controller => 'ibocorp', :action => nil + paginate :page => 2, :per_page => 2 do + assert_select 'a[href]', 7 do |links| + assert_links_match %r{/ibocorp(?:/(\d+))?$}, links, [nil, nil, 3, 4, 5, 6, 3] + end + end + end + + ## internal hardcore stuff ## + + it "should be able to guess the collection name" do + collection = mock + collection.expects(:total_pages).returns(1) + + @template = '<%= will_paginate options %>' + @view.controller.controller_name = 'developers' + @view.assigns['developers'] = collection + + paginate(nil) + end + + it "should fail if the inferred collection is nil" do + @template = '<%= will_paginate options %>' + @view.controller.controller_name = 'developers' + + lambda { + paginate(nil) + }.should raise_error(ArgumentError, /@developers/) + end + + if ActionController::Base.respond_to? :rescue_responses + # only on Rails 2 + it "should set rescue response hook" do + ActionController::Base.rescue_responses['WillPaginate::InvalidPage'].should == :not_found + end + end +end + +class AdditionalLinkAttributesRenderer < WillPaginate::ViewHelpers::LinkRenderer + def initialize(link_attributes = nil) + super() + @additional_link_attributes = link_attributes || { :default => 'true' } + end + + def link(text, target, attributes = {}) + super(text, target, attributes.merge(@additional_link_attributes)) + end +end + +class DummyController + attr_reader :request + attr_accessor :controller_name + + def initialize + @request = DummyRequest.new + @url = ActionController::UrlRewriter.new(@request, @request.params) + end + + def params + @request.params + end + + def url_for(params) + @url.rewrite(params) + end +end + +class DummyRequest + attr_accessor :symbolized_path_parameters + + def initialize + @get = true + @params = {} + @symbolized_path_parameters = { :controller => 'foo', :action => 'bar' } + end + + def get? + @get + end + + def post + @get = false + end + + def relative_url_root + '' + end + + def params(more = nil) + @params.update(more) if more + @params + end +end diff --git a/vendor/plugins/will_paginate/spec/view_helpers/base_spec.rb b/vendor/plugins/will_paginate/spec/view_helpers/base_spec.rb new file mode 100644 index 0000000..ed91a9f --- /dev/null +++ b/vendor/plugins/will_paginate/spec/view_helpers/base_spec.rb @@ -0,0 +1,64 @@ +require 'spec_helper' +require 'will_paginate/view_helpers/base' +require 'will_paginate/array' + +describe WillPaginate::ViewHelpers::Base do + + include WillPaginate::ViewHelpers::Base + + describe "will_paginate" do + it "should render" do + collection = WillPaginate::Collection.new(1, 2, 4) + renderer = mock 'Renderer' + renderer.expects(:prepare).with(collection, instance_of(Hash), self) + renderer.expects(:to_html).returns('') + + will_paginate(collection, :renderer => renderer).should == '' + end + + it "should return nil for single-page collections" do + collection = mock 'Collection', :total_pages => 1 + will_paginate(collection).should be_nil + end + end + + describe "page_entries_info" do + before :all do + @array = ('a'..'z').to_a + end + + def info(params, options = {}) + options[:html] ||= false unless options.key?(:html) and options[:html].nil? + collection = Hash === params ? @array.paginate(params) : params + page_entries_info collection, options + end + + it "should display middle results and total count" do + info(:page => 2, :per_page => 5).should == "Displaying strings 6 - 10 of 26 in total" + end + + it "should output HTML by default" do + info({ :page => 2, :per_page => 5 }, :html => nil).should == + "Displaying strings 6 - 10 of 26 in total" + end + + it "should display shortened end results" do + info(:page => 7, :per_page => 4).should include_phrase('strings 25 - 26') + end + + it "should handle longer class names" do + collection = @array.paginate(:page => 2, :per_page => 5) + collection.first.stubs(:class).returns(mock('Class', :name => 'ProjectType')) + info(collection).should include_phrase('project types') + end + + it "should adjust output for single-page collections" do + info(('a'..'d').to_a.paginate(:page => 1, :per_page => 5)).should == "Displaying all 4 strings" + info(['a'].paginate(:page => 1, :per_page => 5)).should == "Displaying 1 string" + end + + it "should display 'no entries found' for empty collections" do + info([].paginate(:page => 1, :per_page => 5)).should == "No entries found" + end + end +end diff --git a/vendor/plugins/will_paginate/spec/view_helpers/link_renderer_base_spec.rb b/vendor/plugins/will_paginate/spec/view_helpers/link_renderer_base_spec.rb new file mode 100644 index 0000000..a240f3f --- /dev/null +++ b/vendor/plugins/will_paginate/spec/view_helpers/link_renderer_base_spec.rb @@ -0,0 +1,84 @@ +require 'spec_helper' +require 'will_paginate/view_helpers/link_renderer_base' +require 'will_paginate/collection' + +describe WillPaginate::ViewHelpers::LinkRendererBase do + + before do + @renderer = WillPaginate::ViewHelpers::LinkRendererBase.new + end + + it "should raise error when unprepared" do + lambda { + @renderer.send :param_name + }.should raise_error + end + + it "should prepare with collection and options" do + prepare({}, :param_name => 'mypage') + @renderer.send(:current_page).should == 1 + @renderer.send(:param_name).should == 'mypage' + end + + it "should have total_pages accessor" do + prepare :total_pages => 42 + lambda { + @renderer.send(:total_pages).should == 42 + }.should_not have_deprecation + end + + it "should clear old cached values when prepared" do + prepare({ :total_pages => 1 }, :param_name => 'foo') + @renderer.send(:total_pages).should == 1 + @renderer.send(:param_name).should == 'foo' + # prepare with different object and options: + prepare({ :total_pages => 2 }, :param_name => 'bar') + @renderer.send(:total_pages).should == 2 + @renderer.send(:param_name).should == 'bar' + end + + it "should have pagination definition" do + prepare({ :total_pages => 1 }, :page_links => true) + @renderer.pagination.should == [:previous_page, 1, :next_page] + end + + describe "visible page numbers" do + it "should calculate windowed visible links" do + prepare({ :page => 6, :total_pages => 11 }, :inner_window => 1, :outer_window => 1) + showing_pages 1, 2, :gap, 5, 6, 7, :gap, 10, 11 + end + + it "should eliminate small gaps" do + prepare({ :page => 6, :total_pages => 11 }, :inner_window => 2, :outer_window => 1) + # pages 4 and 8 appear instead of the gap + showing_pages 1..11 + end + + it "should support having no windows at all" do + prepare({ :page => 4, :total_pages => 7 }, :inner_window => 0, :outer_window => 0) + showing_pages 1, :gap, 4, :gap, 7 + end + + it "should adjust upper limit if lower is out of bounds" do + prepare({ :page => 1, :total_pages => 10 }, :inner_window => 2, :outer_window => 1) + showing_pages 1, 2, 3, 4, 5, :gap, 9, 10 + end + + it "should adjust lower limit if upper is out of bounds" do + prepare({ :page => 10, :total_pages => 10 }, :inner_window => 2, :outer_window => 1) + showing_pages 1, 2, :gap, 6, 7, 8, 9, 10 + end + + def showing_pages(*pages) + pages = pages.first.to_a if Array === pages.first or Range === pages.first + @renderer.send(:windowed_page_numbers).should == pages + end + end + + protected + + def prepare(collection_options, options = {}) + @renderer.prepare(collection(collection_options), options) + end + +end diff --git a/vendor/plugins/will_paginate/spec/view_helpers/view_example_group.rb b/vendor/plugins/will_paginate/spec/view_helpers/view_example_group.rb new file mode 100644 index 0000000..cdeb0b1 --- /dev/null +++ b/vendor/plugins/will_paginate/spec/view_helpers/view_example_group.rb @@ -0,0 +1,111 @@ +unless $:.find { |p| p =~ %r{/html-scanner$} } + unless actionpack_path = $:.find { |p| p =~ %r{/actionpack(-[\d.]+)?/lib$} } + raise "cannot find ActionPack in load paths" + end + html_scanner_path = "#{actionpack_path}/action_controller/vendor/html-scanner" + $:.unshift(html_scanner_path) +end + +require 'action_controller/assertions/selector_assertions' + +class ViewExampleGroup < Spec::Example::ExampleGroup + + include ActionController::Assertions::SelectorAssertions + + def assert(value, message) + raise message unless value + end + + def paginate(collection = {}, options = {}, &block) + if collection.instance_of? Hash + page_options = { :page => 1, :total_entries => 11, :per_page => 4 }.merge(collection) + collection = [1].paginate(page_options) + end + + locals = { :collection => collection, :options => options } + + @render_output = render(locals) + @html_document = nil + + if block_given? + classname = options[:class] || WillPaginate::ViewHelpers.pagination_options[:class] + assert_select("div.#{classname}", 1, 'no main DIV', &block) + end + + @render_output + end + + def html_document + @html_document ||= HTML::Document.new(@render_output, true, false) + end + + def response_from_page_or_rjs + html_document.root + end + + def validate_page_numbers(expected, links, param_name = :page) + param_pattern = /\W#{CGI.escape(param_name.to_s)}=([^&]*)/ + + links.map { |e| + e['href'] =~ param_pattern + $1 ? $1.to_i : $1 + }.should == expected + end + + def assert_links_match(pattern, links = nil, numbers = nil) + links ||= assert_select 'div.pagination a[href]' do |elements| + elements + end + + pages = [] if numbers + + links.each do |el| + el['href'].should =~ pattern + if numbers + el['href'] =~ pattern + pages << ($1.nil?? nil : $1.to_i) + end + end + + pages.should == numbers if numbers + end + + def assert_no_links_match(pattern) + assert_select 'div.pagination a[href]' do |elements| + elements.each do |el| + el['href'] !~ pattern + end + end + end + + def build_message(message, pattern, *args) + built_message = pattern.dup + for value in args + built_message.sub! '?', value.inspect + end + built_message + end + +end + +Spec::Example::ExampleGroupFactory.register(:view_helpers, ViewExampleGroup) + +module HTML + Node.class_eval do + def inner_text + children.map(&:inner_text).join('') + end + end + + Text.class_eval do + def inner_text + self.to_s + end + end + + Tag.class_eval do + def inner_text + childless?? '' : super + end + end +end diff --git a/vendor/plugins/will_paginate/will_paginate.gemspec b/vendor/plugins/will_paginate/will_paginate.gemspec new file mode 100644 index 0000000..ef5f4df --- /dev/null +++ b/vendor/plugins/will_paginate/will_paginate.gemspec @@ -0,0 +1,20 @@ +Gem::Specification.new do |s| + s.name = 'will_paginate' + s.version = '2.5.0' + # s.date = '2008-10-27' + + s.summary = "Most awesome pagination solution for every web app" + s.description = "The will_paginate library provides a simple, yet powerful and extensible API for pagination and rendering of page links in templates." + + s.authors = ['Mislav Marohnić', 'PJ Hyett'] + s.email = 'mislav.marohnic@gmail.com' + s.homepage = 'http://github.com/mislav/will_paginate/wikis' + + s.has_rdoc = true + s.rdoc_options = ['--main', 'README.rdoc'] + s.rdoc_options << '--inline-source' << '--charset=UTF-8' + s.extra_rdoc_files = ['README.rdoc', 'LICENSE', 'CHANGELOG.rdoc'] + + s.files = %w(CHANGELOG.rdoc LICENSE README.rdoc Rakefile examples examples/apple-circle.gif examples/index.haml examples/index.html examples/pagination.css examples/pagination.sass init.rb lib lib/will_paginate lib/will_paginate.rb lib/will_paginate/array.rb lib/will_paginate/collection.rb lib/will_paginate/core_ext.rb lib/will_paginate/deprecation.rb lib/will_paginate/finders lib/will_paginate/finders.rb lib/will_paginate/finders/active_record lib/will_paginate/finders/active_record.rb lib/will_paginate/finders/active_record/named_scope.rb lib/will_paginate/finders/active_record/named_scope_patch.rb lib/will_paginate/finders/active_resource.rb lib/will_paginate/finders/base.rb lib/will_paginate/finders/data_mapper.rb lib/will_paginate/version.rb lib/will_paginate/view_helpers lib/will_paginate/view_helpers.rb lib/will_paginate/view_helpers/action_view.rb lib/will_paginate/view_helpers/base.rb lib/will_paginate/view_helpers/link_renderer.rb lib/will_paginate/view_helpers/link_renderer_base.rb spec spec/collection_spec.rb spec/console spec/console_fixtures.rb spec/database.yml spec/finders spec/finders/active_record_spec.rb spec/finders/active_resource_spec.rb spec/finders/activerecord_test_connector.rb spec/finders_spec.rb spec/fixtures spec/fixtures/admin.rb spec/fixtures/developer.rb spec/fixtures/developers_projects.yml spec/fixtures/project.rb spec/fixtures/projects.yml spec/fixtures/replies.yml spec/fixtures/reply.rb spec/fixtures/schema.rb spec/fixtures/topic.rb spec/fixtures/topics.yml spec/fixtures/user.rb spec/fixtures/users.yml spec/rcov.opts spec/spec.opts spec/spec_helper.rb spec/tasks.rake spec/view_helpers spec/view_helpers/action_view_spec.rb spec/view_helpers/base_spec.rb spec/view_helpers/link_renderer_base_spec.rb spec/view_helpers/view_example_group.rb) + s.test_files = %w(spec/collection_spec.rb spec/console spec/console_fixtures.rb spec/database.yml spec/finders spec/finders/active_record_spec.rb spec/finders/active_resource_spec.rb spec/finders/activerecord_test_connector.rb spec/finders_spec.rb spec/fixtures spec/fixtures/admin.rb spec/fixtures/developer.rb spec/fixtures/developers_projects.yml spec/fixtures/project.rb spec/fixtures/projects.yml spec/fixtures/replies.yml spec/fixtures/reply.rb spec/fixtures/schema.rb spec/fixtures/topic.rb spec/fixtures/topics.yml spec/fixtures/user.rb spec/fixtures/users.yml spec/rcov.opts spec/spec.opts spec/spec_helper.rb spec/tasks.rake spec/view_helpers spec/view_helpers/action_view_spec.rb spec/view_helpers/base_spec.rb spec/view_helpers/link_renderer_base_spec.rb spec/view_helpers/view_example_group.rb) +end \ No newline at end of file