diff --git a/Gemfile b/Gemfile
index 0012fa39..f4229a35 100644
--- a/Gemfile
+++ b/Gemfile
@@ -34,6 +34,7 @@ gem 'sprockets'
gem 'tinymce-rails'
gem 'therubyracer' if RUBY_PLATFORM.downcase.include?("linux")
+gem "impressionist", :require => "impressionist", :path => "vendor/impressionist"
# Gems used only for assets and not required
# in production environments by default.
diff --git a/Gemfile.lock b/Gemfile.lock
index 4e085437..d67004b9 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -7,6 +7,13 @@ GIT
activesupport (>= 3.0.0)
railties (>= 3.0.0)
+PATH
+ remote: vendor/impressionist
+ specs:
+ impressionist (1.1.1)
+ httpclient (~> 2.2)
+ nokogiri (~> 1.5)
+
GEM
remote: http://rubygems.org/
specs:
@@ -92,6 +99,7 @@ GEM
hike (1.2.1)
hoe (2.16.1)
rake (~> 0.8)
+ httpclient (2.2.5)
i18n (0.6.0)
jquery-rails (1.0.19)
railties (~> 3.0)
@@ -277,6 +285,7 @@ DEPENDENCIES
exception_notification
execjs
factory_girl_rails
+ impressionist!
jquery-rails
jquery-ui-rails
kaminari!
diff --git a/app/assets/javascripts/inc/search.js b/app/assets/javascripts/inc/search.js
new file mode 100644
index 00000000..50f3835f
--- /dev/null
+++ b/app/assets/javascripts/inc/search.js
@@ -0,0 +1,36 @@
+// JavaScript Document
+
+// can copy code to any of ur desired javascsript
+
+//extended jquery to search fast.
+$.extend($.expr[':'], {
+ 'containsi': function (elem, i, match, array) {
+ return (elem.textContent || elem.innerText || '').toLowerCase().indexOf((match[3] || "").toLowerCase()) >= 0;
+ }
+});
+var interval,sval;
+$(document).ready(function(){
+ $("#user_filter").keyup(function(e){
+ if((e.which>96 && e.which<123) || (e.which>64 && e.which<92) || (e.which == 32) || (e.which == 8)){
+ sval = $(this).val();
+ $(".checkbox").popover("hide");
+ $("div.checkblock").hide();
+ clearInterval(interval);
+ interval = setInterval(waitForSearch,1000);
+ }
+ })
+})
+var waitForSearch = function(){
+ if(sval){
+ var totalfoundbyname = $("div#users_checkbox_ary label.member-name:containsi("+sval+")").length
+ if(totalfoundbyname!=0){
+ $("div#users_checkbox_ary label.member-name:containsi("+sval+")").parent().parent().show();
+ }else if(totalfoundbyname==0){
+ $("div#users_checkbox_ary div.for_unit:containsi("+sval+")").parent().show();
+ }
+ }else{
+ $(".checkbox").popover('hide');
+ $("div.checkblock").show();
+ }
+ clearInterval(interval);
+}
\ No newline at end of file
diff --git a/app/controllers/admin/dashboards_controller.rb b/app/controllers/admin/dashboards_controller.rb
index 6bfb91ba..50bbd1aa 100644
--- a/app/controllers/admin/dashboards_controller.rb
+++ b/app/controllers/admin/dashboards_controller.rb
@@ -7,6 +7,7 @@ class Admin::DashboardsController < ApplicationController
def index
@module_app_contents, @module_app_contents_total = get_module_app_count('bulletin', 'news_bulletin', 'page_context', 'web_link')
@recent_updated = get_recently_updated('bulletin', 'news_bulletin', 'page_context', 'web_link')
+ @most_visited = get_most_visited('bulletin', 'news_bulletin', 'page_context')
end
protected
@@ -35,5 +36,18 @@ class Admin::DashboardsController < ApplicationController
sorted_objects = a.sort {|a,b| b[1]<=>a[1]}
sorted_objects[0..9]
end
+
+ def get_most_visited(*args)
+ a = {}
+ args.each do |module_app|
+ module_app_class = module_app.classify.constantize
+ objects = module_app_class.order_by(:view_count, :desc).limit(10)
+ objects.each do |object|
+ a.merge!(object => object.view_count) if object.view_count > 0
+ end
+ end
+ sorted_objects = a.sort {|a,b| b[1]<=>a[1]}
+ sorted_objects[0..9]
+ end
end
diff --git a/app/controllers/admin/object_auths_new_interface_controller.rb b/app/controllers/admin/object_auths_new_interface_controller.rb
index 6c33ff71..6018413b 100644
--- a/app/controllers/admin/object_auths_new_interface_controller.rb
+++ b/app/controllers/admin/object_auths_new_interface_controller.rb
@@ -1,9 +1,7 @@
class Admin::ObjectAuthsNewInterfaceController < OrbitBackendController
include OrbitCoreLib::PermissionUnility
- layout "new_admin"
- before_filter :force_order
- layout "new_admin"
+ before_filter :force_order
def setting
diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb
index 45299450..8758563e 100644
--- a/app/controllers/pages_controller.rb
+++ b/app/controllers/pages_controller.rb
@@ -7,6 +7,7 @@ class PagesController < ApplicationController
def index
@item = Page.find_by_name('home')
if @item
+ impressionist(@item)
render_page
else
render :text => 'You need a home page'
@@ -14,18 +15,19 @@ class PagesController < ApplicationController
end
def show
- #begin
- @item = Item.first(:conditions => {:path => params[:page_name]})
- if @item && @item.is_published && (@item.enabled_for.nil? ? true : @item.enabled_for.include?(I18n.locale.to_s))
- case @item._type
- when 'Page'
- render_page
- when 'Link'
- redirect_to "http://#{@item[:url]}"
- end
- else
- render :file => "#{Rails.root}/public/404.html", :status => :not_found
- end
+ #begin
+ @item = Item.first(:conditions => {:path => params[:page_name]})
+ if @item && @item.is_published && (@item.enabled_for.nil? ? true : @item.enabled_for.include?(I18n.locale.to_s))
+ impressionist(@item)
+ case @item._type
+ when 'Page'
+ render_page
+ when 'Link'
+ redirect_to @item[:url]
+ end
+ else
+ render :file => "#{Rails.root}/public/404.html", :status => :not_found
+ end
#rescue
# render :file => "#{Rails.root}/public/404.html", :status => :not_found
#end
@@ -55,11 +57,7 @@ class PagesController < ApplicationController
def get_item
module_app = ModuleApp.first(:conditions => {:key => params[:app_name]})
- # if params[:category_id]
- # @item = Item.first(:conditions => {:module_app_id => module_app.id, :app_frontend_url => params[:app_action], :category => params[:category_id]})
- # else
- @item = Item.first(:conditions => {:module_app_id => module_app.id, :app_frontend_url => params[:app_action]})
- # end
+ @item = Item.first(:conditions => {:module_app_id => module_app.id, :app_frontend_url => params[:app_action]})
end
end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index a27f2841..74dce24a 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -1,7 +1,8 @@
class SessionsController < Devise::SessionsController
prepend_before_filter :require_no_authentication, :only => [ :new, :create ]
include Devise::Controllers::InternalHelpers
-
+ MiddleSiteConnection.establish
+ NccuLdapConnection.establish
# POST /resource/sign_in
def create
@@ -10,42 +11,52 @@ class SessionsController < Devise::SessionsController
login_password = params[:user][:password]
login_uid = params[:user][:nccu_ldap_uid]
result = false
- ldap = Net::LDAP.new
- #ldap.port = '8001'
- #ldap.host = '127.0.0.1'
- ldap.port = '389'
- ldap.host = '140.119.166.23'
ldap_filter = "(uid=#{login_uid})"
- ldap_base = 'ou=People,dc=nccu,dc=edu,dc=tw'
- ldap.authenticate("cn=uccn,ou=profile,dc=nccu,dc=edu,dc=tw","nccu2ucc")
- if ldap.bind && login_password!='' #need to block password empty
- result = ldap.bind_as(:base => ldap_base,:filter => ldap_filter,:password=> login_password)
- if result
+ if $nccu_ldap_connection.bind
+ logger.info "=LDAP Binded password ok..."
+ result =check_auth_with_ldap(login_uid,login_password)
+ if result && login_password!=''
+ logger.info "==LDAP password passed..."
nccu_id = get_nccu_id_from_mid_site(login_uid)
resource = nccu_id.nil? ? nil : (User.first(conditions:{ nccu_id: nccu_id }))
# resource = env['warden'].authenticate!(:check_nccu_ldap)
# resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new")
set_flash_message(:notice, :signed_in) if is_navigational_format?
if (resource.nil? || nccu_id.nil?)
+ logger.error "===LDAP passed local block... resource:#{resource.inspect}\n nccu_id:#{nccu_id} \t login_uid:#{login_uid}"
flash[:notice] = t('devise.failure.ldap_pass_but_account_not_in_orbit')
render :action => "new"
else
+ logger.info "===ALL passed"
resource_name = resource._type.downcase
sign_in(resource_name, resource)
respond_with resource, :location => redirect_location(resource_name, resource)
end
+ elsif resource = User.first(conditions:{email: login_uid})
+
+ resource_name = resource._type.downcase
+ sign_in(resource_name, resource)
+ respond_with resource, :location => redirect_location(resource_name, resource)
else
+ logger.error "==password LDAP fail..."
flash[:notice] = t('devise.failure.ldap_invalid')
render :action => "new"
end
else
+ logger.error "=LDAP fail..."
flash[:notice] = t('devise.failure.ldap_connection_failed')
render :action => "new"
end
+ logger.info "=======End Debugging======"
end
private
+ def check_auth_with_ldap(login_uid,login_password)
+ ldap_filter = "(uid=#{login_uid})"
+ $nccu_ldap_connection.bind_as(:base => NccuLdapConnection::BASE,:filter => ldap_filter,:password=> login_password) rescue false
+ end
+
def get_nccu_id_from_mid_site(ldap_id)
- nccu_id = MID_CLIENT.query("SELECT nccu_id FROM rss_aaldap_view WHERE ldap_id='#{ldap_id}' LIMIT 1").first['nccu_id'] rescue nil
+ nccu_id = $mid_site_connection.query("SELECT nccu_id FROM rss_aaldap_view WHERE ldap_id='#{ldap_id}' LIMIT 1").first['nccu_id'] rescue nil
#
# if nccu_id.nil?
# #show_error
@@ -53,7 +64,7 @@ private
# #should return?
# end
# # User.first(conditions: { })
- # rss_pautlst_ut = MID_CLIENT.query("SELECT * FROM rss_pautlst_ut WHERE nccu_id='#{nccu_id}' LIMIT 1").first rescue nil
+ # rss_pautlst_ut = $mid_site_connection.query("SELECT * FROM rss_pautlst_ut WHERE nccu_id='#{nccu_id}' LIMIT 1").first rescue nil
# # rss_paunit = client.query("SELECT * FROM rss_paunit LIMIT 1").first rescue nil
# user = User.find_or_create_by(:nccu_id => nccu_id)
# p user
diff --git a/app/helpers/admin/dashboard_helper.rb b/app/helpers/admin/dashboard_helper.rb
index 4b750fb8..f69185f3 100644
--- a/app/helpers/admin/dashboard_helper.rb
+++ b/app/helpers/admin/dashboard_helper.rb
@@ -3,26 +3,26 @@ module Admin::DashboardHelper
def get_link(title)
case title
when 'bulletin'
- panel_announcement_back_end_bulletins_path
+ panel_announcement_front_end_bulletins_path
when 'news_bulletin'
- panel_news_back_end_news_bulletins_path
+ panel_news_front_end_news_bulletins_path
when'page_context'
- panel_page_content_back_end_page_contexts_path
+ panel_page_content_front_end_page_contexts_path
when'web_link'
- panel_web_resource_back_end_web_links_path
+ panel_web_resource_front_end_web_links_path
end
end
def get_link_to_object(object)
case object._type.underscore
when 'bulletin'
- panel_announcement_back_end_bulletin_path(object)
+ panel_announcement_front_end_bulletin_path(object)
when 'news_bulletin'
- panel_news_back_end_news_bulletin_path(object)
+ panel_news_front_end_news_bulletin_path(object)
when'page_context'
- panel_page_content_back_end_page_context_path(object)
+ panel_page_content_front_end_page_context_path(object)
when'web_link'
- panel_web_resource_back_end_web_link_path(object)
+ panel_web_resource_front_end_web_link_path(object)
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index d04d8c59..5f693965 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -96,7 +96,6 @@ module ApplicationHelper
end
def visible_for_controllers(*controller_names)
- puts controller_names
(controller_names.include?(controller.controller_name) || controller_names.include?(request.fullpath)) ? '' : 'hide'
end
@@ -173,4 +172,28 @@ module ApplicationHelper
locale.to_sym == I18n.locale ? 'active in': ''
end
+ def dislpay_view_count(object)
+ "#{t(:view_count)}: #{object.view_count}"
+ end
+
+ def display_visitors(options={})
+ Impression.where(options).distinct(:session_hash).count
+ end
+
+ def display_visitors_today
+ display_visitors(created_at: {'$gte' => Date.today.beginning_of_day, '$lte' => Date.today.end_of_day})
+ end
+
+ def display_visitors_this_week
+ display_visitors(created_at: {'$gte' => Date.today.beginning_of_week, '$lte' => Date.today.end_of_week})
+ end
+
+ def display_visitors_this_month
+ display_visitors(created_at: {'$gte' => Date.today.beginning_of_month, '$lte' => Date.today.end_of_month})
+ end
+
+ def display_visitors_this_year
+ display_visitors(created_at: {'$gte' => Date.today.beginning_of_year, '$lte' => Date.today.end_of_year})
+ end
+
end
diff --git a/app/jobs/nccu_calendar.rb b/app/jobs/nccu_calendar.rb
index 1848c691..ffd89ca8 100644
--- a/app/jobs/nccu_calendar.rb
+++ b/app/jobs/nccu_calendar.rb
@@ -1,12 +1,15 @@
class NccuCalendar
require 'open-uri'
+ require 'tempfile'
+
@queue = :high
def self.perform()
- File.open(File.join(Rails.root, 'public/static', 'nccu_calendar.rss'),'w') do |file|
- file << open('http://events.nccu.edu.tw/Feed').read
- CronMail.time_check("NCCU Calendar synced").deliver
- end
+ temp_file = Tempfile.new('new_cal')
+ temp_file << open('http://events.nccu.edu.tw/Month').read
+
+ FileUtils.mv(temp_file, File.join(Rails.root, 'public/static', 'nccu_calendar.xml'))
+
puts "NccuCalendar Synced"
end
end
diff --git a/app/models/ad_banner.rb b/app/models/ad_banner.rb
index e1013c2f..09aa79c8 100644
--- a/app/models/ad_banner.rb
+++ b/app/models/ad_banner.rb
@@ -1,8 +1,8 @@
class AdBanner
- include OrbitCoreLib::ObjectAuthable
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::MultiParameterAttributes
+ include OrbitCoreLib::ObjectAuthable
field :title
field :transition_msec,type: Integer
diff --git a/app/models/link.rb b/app/models/link.rb
index 4301ead4..5b8c15a6 100644
--- a/app/models/link.rb
+++ b/app/models/link.rb
@@ -2,10 +2,20 @@ class Link < Item
field :url
- validates_presence_of :url
+ validates :url, :presence => true, :format => /^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?$/ix
+
+ before_validation :add_http
def link
ApplicationController.helpers.link_to(self.name, self.url)
end
+
+ protected
+
+ def add_http
+ unless self.url[/^http?s:\/\//]
+ self.url = 'http://' + self.url
+ end
+ end
end
diff --git a/app/models/page.rb b/app/models/page.rb
index 4bcb49a6..970df4cf 100644
--- a/app/models/page.rb
+++ b/app/models/page.rb
@@ -1,10 +1,14 @@
class Page < Item
+ include Impressionist::Impressionable
+
+ is_impressionable :counter_cache => { :column_name => :view_count }
field :content
field :app_frontend_url
field :theme_id, :type => BSON::ObjectId, :default => nil
field :category
field :tag
+ field :view_count, :type => Integer, :default => 0
belongs_to :design
belongs_to :module_app
diff --git a/app/views/admin/dashboards/index.html.erb b/app/views/admin/dashboards/index.html.erb
index ee0bc920..dbca5aa6 100644
--- a/app/views/admin/dashboards/index.html.erb
+++ b/app/views/admin/dashboards/index.html.erb
@@ -380,49 +380,7 @@
-
diff --git a/vendor/built_in_modules/page_content/app/controllers/panel/page_content/front_end/page_contexts_controller.rb b/vendor/built_in_modules/page_content/app/controllers/panel/page_content/front_end/page_contexts_controller.rb
index b8842169..e1e98c76 100644
--- a/vendor/built_in_modules/page_content/app/controllers/panel/page_content/front_end/page_contexts_controller.rb
+++ b/vendor/built_in_modules/page_content/app/controllers/panel/page_content/front_end/page_contexts_controller.rb
@@ -6,10 +6,10 @@ class Panel::PageContent::FrontEnd::PageContextsController < OrbitWidgetControll
end
def index
-
- # @page_context = PageContext.where("page_id" => params[:page_id], :archived => false)
- @page_context = PageContext.first(conditions: { page_id: params[:page_id], :archived => false })
-
+ # @page_context = PageContext.where("page_id" => params[:page_id], :archived => false)
+ @page_context = PageContext.first(conditions: { page_id: params[:page_id], :archived => false })
+ impressionist(@page_context)
+
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @page_contexts }
diff --git a/vendor/built_in_modules/page_content/app/models/page_context.rb b/vendor/built_in_modules/page_content/app/models/page_context.rb
index dfbfd4a9..be60db27 100644
--- a/vendor/built_in_modules/page_content/app/models/page_context.rb
+++ b/vendor/built_in_modules/page_content/app/models/page_context.rb
@@ -4,15 +4,17 @@ class PageContext
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::MultiParameterAttributes
-
+ include Impressionist::Impressionable
include OrbitCoreLib::ObjectAuthable
-
+ is_impressionable :counter_cache => { :column_name => :view_count }
+
has_one :context, :class_name => "I18nVariable", :as => :language_value, :autosave => true, :dependent => :destroy
field :create_user_id
field :update_user_id
field :version, :type => Integer , :default => 0
+ field :view_count, :type => Integer, :default => 0
field :archived, :type => Boolean, :default => false
# field :current, :type => Boolean, :default => false
diff --git a/vendor/built_in_modules/page_content/app/views/panel/page_content/back_end/page_contexts/_page_context.html.erb b/vendor/built_in_modules/page_content/app/views/panel/page_content/back_end/page_contexts/_page_context.html.erb
index d2f5cfa1..036cb6e0 100644
--- a/vendor/built_in_modules/page_content/app/views/panel/page_content/back_end/page_contexts/_page_context.html.erb
+++ b/vendor/built_in_modules/page_content/app/views/panel/page_content/back_end/page_contexts/_page_context.html.erb
@@ -1,23 +1,23 @@
-
-
-
- <%= page_context.page.path %>
-
-
- <%if is_manager? || is_admin? || page_context.authed_users(:edit).include?(current_user)%>
- - <%= link_to t('admin.page_context.edit'), edit_panel_page_content_back_end_page_context_path(page_context) %>
- <%if (is_manager? || is_admin?) %>
- - <%=show_page_context_edit_auth_link page_context%>
- <% end%>
- <% end -%>
-
-
- |
-
- <%if is_manager? || is_admin? || page_context.authed_users(:edit).include?(current_user)%>
- <%= link_to page_context.version, panel_page_content_back_end_view_path(page_context.page_id) %>
- <%end -%>
- |
- <%= page_context.updated_at.strftime("%Y-%m-%d %H:%I:%S") %> |
- <%= User.find(page_context.create_user_id).name %> |
-
\ No newline at end of file
+
+
+ <%= page_context.page.path %>
+ <%= page_context.page.i18n_variable[I18n.locale] %>
+
+
+ <%if is_manager? || is_admin? || page_context.authed_users(:edit).include?(current_user)%>
+ - <%= link_to t('admin.page_context.edit'), edit_panel_page_content_back_end_page_context_path(page_context) %>
+ <%if (is_manager? || is_admin?) %>
+ - <%=show_page_context_edit_auth_link page_context%>
+ <% end%>
+ <% end -%>
+
+
+ |
+
+ <%if is_manager? || is_admin? || page_context.authed_users(:edit).include?(current_user)%>
+ <%= link_to page_context.version, panel_page_content_back_end_view_path(page_context.page_id) %>
+ <%end -%>
+ |
+ <%= page_context.updated_at.strftime("%Y-%m-%d %H:%I:%S") %> |
+ <%= User.find(page_context.create_user_id).name %> |
+
\ No newline at end of file
diff --git a/vendor/built_in_modules/page_content/app/views/panel/page_content/front_end/page_contexts/index.html.erb b/vendor/built_in_modules/page_content/app/views/panel/page_content/front_end/page_contexts/index.html.erb
index 4f196d59..a27a56e7 100644
--- a/vendor/built_in_modules/page_content/app/views/panel/page_content/front_end/page_contexts/index.html.erb
+++ b/vendor/built_in_modules/page_content/app/views/panel/page_content/front_end/page_contexts/index.html.erb
@@ -6,3 +6,5 @@
<%= @page_context.page.i18n_variable[I18n.locale] rescue nil %>
<%= @page_context.context[I18n.locale].html_safe rescue nil %>
+
+
<%= dislpay_view_count(@page_context) %>
diff --git a/vendor/impressionist/.gitignore b/vendor/impressionist/.gitignore
new file mode 100644
index 00000000..685130c7
--- /dev/null
+++ b/vendor/impressionist/.gitignore
@@ -0,0 +1,10 @@
+*~
+/coverage
+/pkg
+/rdoc
+/test_app/db/schema.rb
+/test_app/db/migrate/*_create_impressions_table.rb
+/test_app/doc
+/test_app/test
+/test_app/vendor
+Gemfile.lock
diff --git a/vendor/impressionist/.rspec b/vendor/impressionist/.rspec
new file mode 100644
index 00000000..4e1e0d2f
--- /dev/null
+++ b/vendor/impressionist/.rspec
@@ -0,0 +1 @@
+--color
diff --git a/vendor/impressionist/.travis.yml b/vendor/impressionist/.travis.yml
new file mode 100644
index 00000000..b7ae9000
--- /dev/null
+++ b/vendor/impressionist/.travis.yml
@@ -0,0 +1,11 @@
+before_install: gem install bundler
+before_script: "cd test_app && bundle install && ./script/rails generate impressionist && bundle exec rake db:migrate && cd .."
+language: ruby
+rvm:
+ - rbx-18mode
+ - rbx-19mode
+ - jruby-18mode
+ - 1.8.7
+ - 1.9.2
+ - 1.9.3
+ - ruby-head
diff --git a/vendor/impressionist/CHANGELOG.rdoc b/vendor/impressionist/CHANGELOG.rdoc
new file mode 100644
index 00000000..c3378b22
--- /dev/null
+++ b/vendor/impressionist/CHANGELOG.rdoc
@@ -0,0 +1,19 @@
+== 0.4.0 (2011-06-03)
+* Fix postgres bug
+* New impression count method that accepts options for filter, start_date and end_date
+* Add referrer to Impression model. YOU MUST RUN THE UPGRADE MIGRATION IF YOU ARE UPGRADING FROM 0.3.0
+* UPGRADE MIGRATION = impressionist/upgrade_migrations/version_0_4_0.rb
+* NOTE IF YOU ARE UPGRADING FROM 0.2.5 OR BELOW, YOU MUST RUN BOTH UPGRADE MIGRATIONS
+
+== 0.3.0 (2011-03-06)
+* added session_hash to impression model
+* migration template updated to add session_hash
+* new count instance method for impressionable model - unique_impression_count_session
+* NOTE: if you are upgrading from 0.2.5, then run the migration in the 'upgrade_migrations' dir
+
+== 0.2.5 (2011-02-17)
+* New model method - @widget.unique_impression_count_ip - This gives you unique impression account filtered by IP (and in turn request_hash since they have same IPs)
+* @widget.unique_impression_count now uses request_hash. This was incorrectly stated in the README, since it was using ip_address. The README is correct as a result of the method change.
+
+== 0.2.4 (2011-02-17)
+* Fix issue #1 - action_name and controller_name were not being logged for impressionist method inside action
diff --git a/vendor/impressionist/Gemfile b/vendor/impressionist/Gemfile
new file mode 100644
index 00000000..e6cc394d
--- /dev/null
+++ b/vendor/impressionist/Gemfile
@@ -0,0 +1,13 @@
+source 'https://rubygems.org'
+
+platforms :jruby do
+ gem 'activerecord-jdbcsqlite3-adapter'
+ gem 'jdbc-sqlite3'
+ gem 'jruby-openssl'
+end
+
+platforms :ruby, :mswin, :mingw do
+ # gem 'sqlite3'
+end
+
+gemspec
diff --git a/vendor/impressionist/LICENSE.txt b/vendor/impressionist/LICENSE.txt
new file mode 100644
index 00000000..67e3f5c6
--- /dev/null
+++ b/vendor/impressionist/LICENSE.txt
@@ -0,0 +1,20 @@
+Copyright (c) 2011 cowboycoded
+
+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/impressionist/README.md b/vendor/impressionist/README.md
new file mode 100644
index 00000000..40774e74
--- /dev/null
+++ b/vendor/impressionist/README.md
@@ -0,0 +1,198 @@
+![Impressionist Logo](https://github.com/charlotte-ruby/impressionist/raw/master/logo.png)
+
+[![Build Status](https://secure.travis-ci.org/charlotte-ruby/impressionist.png?branch=master)](http://travis-ci.org/charlotte-ruby/impressionist)
+
+impressionist
+=============
+
+A lightweight plugin that logs impressions per action or manually per model
+
+--------------------------------------------------------------------------------
+
+What does this thing do?
+------------------------
+Logs an impression... and I use that term loosely. It can log page impressions
+(technically action impressions), but it is not limited to that. You can log
+impressions multiple times per request. And you can also attach it to a model.
+The goal of this project is to provide customizable stats that are immediately
+accessible in your application as opposed to using Google Analytics and pulling
+data using their API. You can attach custom messages to impressions. No
+reporting yet.. this thingy just creates the data.
+
+What about bots?
+----------------
+They are ignored. 1200 known bots have been added to the ignore list as of
+February 1, 2011. Impressionist uses this list:
+http://www.user-agents.org/allagents.xml
+
+Installation
+------------
+Add it to your Gemfile
+
+ gem 'impressionist'
+
+Install with Bundler
+
+ bundle install
+
+Generate the impressions table migration
+
+ rails g impressionist
+
+Run the migration
+
+ rake db:migrate
+
+The following fields are provided in the migration:
+
+ t.string "impressionable_type" # model type: Widget
+ t.integer "impressionable_id" # model instance ID: @widget.id
+ t.integer "user_id" # automatically logs @current_user.id
+ t.string "controller_name" # logs the controller name
+ t.string "action_name" # logs the action_name
+ t.string "view_name" # TODO: log individual views (as well as partials and nested partials)
+ t.string "request_hash" # unique ID per request, in case you want to log multiple impressions and group them
+ t.string "session_hash" # logs the rails session
+ t.string "ip_address" # request.remote_ip
+ t.string "referrer" # request.referer
+ t.string "message" # custom message you can add
+ t.datetime "created_at" # I am not sure what this is.... Any clue?
+ t.datetime "updated_at" # never seen this one before either.... Your guess is as good as mine??
+
+Usage
+-----
+
+1. Log all actions in a controller
+
+ WidgetsController < ApplicationController
+ impressionist
+ end
+
+2. Specify actions you want logged in a controller
+
+ WidgetsController < ApplicationController
+ impressionist :actions=>[:show,:index]
+ end
+
+3. Make your models impressionable. This allows you to attach impressions to
+ an AR model instance. Impressionist will automatically log the Model name
+ (based on action_name) and the id (based on params[:id]), but in order to
+ get the count of impressions (example: @widget.impression_count), you will
+ need to make your model impressionalble
+
+ class Widget < ActiveRecord::Base
+ is_impressionable
+ end
+
+4. Log an impression per model instance in your controller. Note that it is
+ not necessary to specify "impressionist" (usage #1) in the top of you
+ controller if you are using this method. If you add "impressionist" to the
+ top of your controller and also use this method in your action, it will
+ result in 2 impressions being logged (but associated with one request_hash)
+
+ def show
+ @widget = Widget.find
+ impressionist(@widget,message:"wtf is a widget?") #message is optional
+ end
+
+5. Get unique impression count from a model. This groups impressions by
+ request_hash, so if you logged multiple impressions per request, it will
+ only count them one time. This unique impression count will not filter out
+ unique users, only unique requests
+
+ @widget.impressionist_count
+ @widget.impressionist_count(:start_date=>"2011-01-01",:end_date=>"2011-01-05")
+ @widget.impressionist_count(:start_date=>"2011-01-01") #specify start date only, end date = now
+
+6. Get the unique impression count from a model filtered by IP address. This
+ in turn will give you impressions with unique request_hash, since rows with
+ the same request_hash will have the same IP address.
+
+ @widget.impressionist_count(:filter=>:ip_address)
+
+7. Get the unique impression count from a model filtered by session hash. Same
+ as #6 regarding request hash. This may be more desirable than filtering by
+ IP address depending on your situation, since filtering by IP may ignore
+ visitors that use the same IP. The downside to this filtering is that a
+ user could clear session data in their browser and skew the results.
+
+ @widget.impressionist_count(:filter=>:session_hash)
+
+8. Get total impression count. This may return more than 1 impression per http
+ request, depending on how you are logging impressions
+
+ @widget.impressionist_count(:filter=>:all)
+
+Logging impressions for authenticated users happens automatically. If you have
+a current_user helper or use @current_user in your before_filter to set your
+authenticated user, current_user.id will be written to the user_id field in the
+impressions table.
+
+Adding a counter cache
+----------------------
+Impressionist makes it easy to add a `counter_cache` column to your model. The
+most basic configuration looks like:
+
+ is_impressionable :counter_cache => true
+
+This will automatically increment the `impressions_count` column in the
+included model. Note: You'll need to add that column to your model. If you'd
+like specific a different column name, you can:
+
+ is_impressionable :counter_cache => { :column_name => :my_column }
+
+If you'd like to include only unique impressions in your count:
+
+ is_impressionable :counter_cache => { :column_name => :my_column, :unique => true }
+
+What if I only want to record unique impressions?
+-------------------------------------------------
+Maybe you only care about unique impressions and would like to avoid
+unnecessary database records. You can specify conditions for recording
+impressions in your controller:
+
+ # only record impression if the request has a unique combination of type, id, and session
+ impressionist :unique => [:impressionable_type, :impressionable_id, :session_hash]
+
+ # only record impression if the request has a unique combination of controller, action, and session
+ impressionist :unique => [:controller_name, :action_name, :session_hash]
+
+ # only record impression if session is unique
+ impressionist :unique => [:session_hash]
+
+Or you can use the `impressionist` method directly:
+
+ impressionist(impressionable, "some message", :unique => [:session_hash])
+
+Development Roadmap
+-------------------
+* Automatic impression logging in views. For example, log initial view, and
+ any partials called from initial view
+* Customizable black list for user-agents or IP addresses. Impressions will be
+ ignored. Web admin as part of the Engine.
+* Reporting engine
+* AB testing integration
+
+Contributing to impressionist
+-----------------------------
+* Check out the latest master to make sure the feature hasn't been implemented
+ or the bug hasn't been fixed yet
+* Check out the issue tracker to make sure someone already hasn't requested it
+ and/or contributed it
+* Fork the project
+* Start a feature/bugfix branch
+* Commit and push until you are happy with your contribution
+* Make sure to add rpsec tests for it. Patches or features without tests will
+ be ignored. Also, try to write better tests than I do ;-)
+* If adding engine controller or view functionality, use HAML and Inherited
+ Resources.
+* All testing is done inside a small Rails app (test_app). You will find specs
+ within this app.
+
+Contributors
+------------
+* [johnmcaliley](https://github.com/johnmcaliley)
+* [coryschires](https://github.com/coryschires)
+* [georgmittendorfer](https://github.com/georgmittendorfer)
+
+Copyright (c) 2011 John McAliley. See LICENSE.txt for further details.
diff --git a/vendor/impressionist/Rakefile b/vendor/impressionist/Rakefile
new file mode 100644
index 00000000..d808f163
--- /dev/null
+++ b/vendor/impressionist/Rakefile
@@ -0,0 +1,21 @@
+require 'bundler/setup'
+require 'rspec/core/rake_task'
+
+Bundler::GemHelper.install_tasks
+
+RSpec::Core::RakeTask.new do |task|
+ task.rspec_opts = "-I ./test_app/spec"
+ task.pattern = "./test_app/spec/**/*_spec.rb"
+end
+
+task :test => :spec
+task :default => :spec
+
+namespace :impressionist do
+ require File.dirname(__FILE__) + "/lib/impressionist/bots"
+
+ desc "output the list of bots from http://www.user-agents.org/"
+ task :bots do
+ p Impressionist::Bots.consume
+ end
+end
diff --git a/vendor/impressionist/app/controllers/impressionist_controller.rb b/vendor/impressionist/app/controllers/impressionist_controller.rb
new file mode 100644
index 00000000..05a39768
--- /dev/null
+++ b/vendor/impressionist/app/controllers/impressionist_controller.rb
@@ -0,0 +1,103 @@
+require 'digest/sha2'
+
+module ImpressionistController
+ module ClassMethods
+ def impressionist(opts={})
+ before_filter { |c| c.impressionist_subapp_filter(opts[:actions], opts[:unique])}
+ end
+ end
+
+ module InstanceMethods
+ def self.included(base)
+ base.before_filter :impressionist_app_filter
+ end
+
+ def impressionist(obj,message=nil,opts={})
+ unless bypass
+ if obj.respond_to?("impressionable?")
+ if unique_instance?(obj, opts[:unique])
+ obj.impressions.create(associative_create_statement({:message => message}))
+ end
+ else
+ # we could create an impression anyway. for classes, too. why not?
+ raise "#{obj.class.to_s} is not impressionable!"
+ end
+ end
+ end
+
+ def impressionist_app_filter
+ @impressionist_hash = Digest::SHA2.hexdigest(Time.now.to_f.to_s+rand(10000).to_s)
+ end
+
+ def impressionist_subapp_filter(actions=nil,unique_opts=nil)
+ unless bypass
+ actions.collect!{|a|a.to_s} unless actions.blank?
+ if (actions.blank? || actions.include?(action_name)) && unique?(unique_opts)
+ Impression.create(direct_create_statement)
+ end
+ end
+ end
+
+ private
+
+ def bypass
+ Impressionist::Bots.bot?(request.user_agent)
+ end
+
+ def unique_instance?(impressionable, unique_opts)
+ return unique_opts.blank? || !impressionable.impressions.where(unique_query(unique_opts)).exists?
+ end
+
+ def unique?(unique_opts)
+ return unique_opts.blank? || !Impression.where(unique_query(unique_opts)).exists?
+ end
+
+ # creates the query to check for uniqueness
+ def unique_query(unique_opts)
+ full_statement = direct_create_statement
+ # reduce the full statement to the params we need for the specified unique options
+ unique_opts.reduce({}) do |query, param|
+ query[param] = full_statement[param]
+ query
+ end
+ end
+
+ # creates a statment hash that contains default values for creating an impression via an AR relation.
+ def associative_create_statement(query_params={})
+ query_params.reverse_merge!(
+ :controller_name => controller_name,
+ :action_name => action_name,
+ :user_id => user_id,
+ :request_hash => @impressionist_hash,
+ :session_hash => session_hash,
+ :ip_address => request.remote_ip,
+ :referrer => request.referer
+ )
+ end
+
+ # creates a statment hash that contains default values for creating an impression.
+ def direct_create_statement(query_params={})
+ query_params.reverse_merge!(
+ :impressionable_type => controller_name.singularize.camelize,
+ :impressionable_id=> params[:id]
+ )
+ associative_create_statement(query_params)
+ end
+
+ def session_hash
+ # # careful: request.session_options[:id] encoding in rspec test was ASCII-8BIT
+ # # that broke the database query for uniqueness. not sure if this is a testing only issue.
+ # str = request.session_options[:id]
+ # logger.debug "Encoding: #{str.encoding.inspect}"
+ # # request.session_options[:id].encode("ISO-8859-1")
+ request.session_options[:id]
+ end
+
+ #use both @current_user and current_user helper
+ def user_id
+ user_id = @current_user ? @current_user.id : nil rescue nil
+ user_id = current_user ? current_user.id : nil rescue nil if user_id.blank?
+ user_id
+ end
+ end
+end
diff --git a/vendor/impressionist/app/models/impression.rb b/vendor/impressionist/app/models/impression.rb
new file mode 100644
index 00000000..b4d81ecb
--- /dev/null
+++ b/vendor/impressionist/app/models/impression.rb
@@ -0,0 +1,3 @@
+class Impression
+ belongs_to :impressionable, :polymorphic=>true
+end
diff --git a/vendor/impressionist/app/models/impressionist/bots.rb b/vendor/impressionist/app/models/impressionist/bots.rb
new file mode 100644
index 00000000..aee7cf9c
--- /dev/null
+++ b/vendor/impressionist/app/models/impressionist/bots.rb
@@ -0,0 +1,1468 @@
+module Impressionist
+ module Bots
+
+ def self.bot?(user_agent = nil)
+ return false if user_agent.nil?
+ WILD_CARDS.any? { |wc| user_agent.downcase.include?(wc) } || LIST.include?(user_agent)
+ end
+
+ WILD_CARDS = ["bot","yahoo","slurp","google","msn","crawler"]
+
+ LIST = ["
UnChaos From Chaos To Order Hybrid Web Search Engine.(vadim_gonchar@unchaos.com)",
+ "
UnChaos Bot Hybrid Web Search Engine. (vadim_gonchar@unchaos.com)",
+ "
UnChaosBot From Chaos To Order UnChaos Hybrid Web Search Engine at www.unchaos.com (info@unchaos.com)",
+ "
http://www.sygol.com",
+ "*/Nutch-0.9-dev",
+ "+SitiDi.net/SitiDiBot/1.0 (+Have Good Day)",
+ "-DIE-KRAEHE- META-SEARCH-ENGINE/1.1 http://www.die-kraehe.de",
+ "192.comAgent",
+ "4anything.com LinkChecker v2.0",
+ "8484 Boston Project v 1.0",
+ ":robot/1.0 (linux) ( admin e-mail: undefined http://www.neofonie.de/loesungen/search/robot.html )",
+ "A-Online Search",
+ "A1 Sitemap Generator/1.0 (+http://www.micro-sys.dk/products/sitemap-generator/) miggibot/2006.01.24",
+ "aardvark-crawler",
+ "AbachoBOT",
+ "AbachoBOT (Mozilla compatible)",
+ "ABCdatos BotLink/5.xx.xxx#BBL",
+ "Aberja Checkomat",
+ "abot/0.1 (abot; http://www.abot.com; abot@abot.com)",
+ "About/0.1libwww-perl/5.47",
+ "Accelatech RSSCrawler/0.4",
+ "accoona",
+ "Accoona-AI-Agent/1.1.1 (crawler at accoona dot com)",
+ "Accoona-AI-Agent/1.1.2 (aicrawler at accoonabot dot com)",
+ "Ack (http://www.ackerm.com/)",
+ "AcoiRobot",
+ "Acoon Robot v1.50.001",
+ "Acoon Robot v1.52 (http://www.acoon.de)",
+ "Acoon-Robot 4.0.x.[xx] (http://www.acoon.de)",
+ "Acoon-Robot v3.xx (http://www.acoon.de and http://www.acoon.com)",
+ "Acorn/Nutch-0.9 (Non-Profit Search Engine; acorn.isara.org; acorn at isara dot org)",
+ "AESOP_com_SpiderMan",
+ "agadine/1.x.x (+http://www.agada.de)",
+ "Agent-SharewarePlazaFileCheckBot/2.0+(+http://www.SharewarePlaza.com)",
+ "AgentName/0.1 libwww-perl/5.48",
+ "AIBOT/2.1 By +(www.21seek.com A Real artificial intelligence search engine China)",
+ "aipbot/1.0 (aipbot; http://www.aipbot.com; aipbot@aipbot.com)",
+ "aipbot/2-beta (aipbot dev; http://aipbot.com; aipbot@aipbot.com)",
+ "Aladin/3.324",
+ "Aleksika Spider/1.0 (+http://www.aleksika.com/)",
+ "AlkalineBOT/1.3",
+ "AlkalineBOT/1.4 (1.4.0326.0 RTM)",
+ "Allesklar/0.1 libwww-perl/5.46",
+ "Allrati/1.1 (+)",
+ "AltaVista Intranet V2.0 AVS EVAL search@freeit.com",
+ "AltaVista Intranet V2.0 Compaq Altavista Eval sveand@altavista.net",
+ "AltaVista Intranet V2.0 evreka.com crawler@evreka.com",
+ "AltaVista V2.0B crawler@evreka.com",
+ "AmfibiBOT",
+ "Amfibibot/0.06 (Amfibi Web Search; http://www.amfibi.com; agent@amfibi.com)",
+ "Amfibibot/0.07 (Amfibi Robot; http://www.amfibi.com; agent@amfibi.com)",
+ "amibot",
+ "AnnoMille spider 0.1 alpha - http://www.annomille.it",
+ "AnswerBus (http://www.answerbus.com/)",
+ "antibot-V1.1.5/i586-linux-2.2",
+ "AnzwersCrawl/2.0 (anzwerscrawl@anzwers.com.au;Engine)",
+ "Apexoo Spider 1.x",
+ "Aport",
+ "appie 1.1 (www.walhello.com)",
+ "ArabyBot (compatible; Mozilla/5.0; GoogleBot; FAST Crawler 6.4; http://www.araby.com;)",
+ "ArachBot",
+ "Arachnoidea (arachnoidea@euroseek.com)",
+ "ArchitextSpider",
+ "archive.org_bot",
+ "Arikus_Spider",
+ "Arquivo-web-crawler (compatible; heritrix/1.12.1 +http://arquivo-web.fccn.pt)",
+ "ASAHA Search Engine Turkey V.001 (http://www.asaha.com/)",
+ "Asahina-Antenna/1.x",
+ "Asahina-Antenna/1.x (libhina.pl/x.x ; libtime.pl/x.x)",
+ "ask.24x.info",
+ "AskAboutOil/0.06-rcp (Nutch; http://www.nutch.org/docs/en/bot.html; nutch-agent@askaboutoil.com)",
+ "asked/Nutch-0.8 (web crawler; http://asked.jp; epicurus at gmail dot com)",
+ "ASPSeek/1.2.5",
+ "ASPseek/1.2.9d",
+ "ASPSeek/1.2.x",
+ "ASPSeek/1.2.xa",
+ "ASPseek/1.2.xx",
+ "ASPSeek/1.2.xxpre",
+ "ASSORT/0.10",
+ "asterias/2.0",
+ "AtlocalBot/1.1 +(http://www.atlocal.com/local-web-site-owner.html)",
+ "Atomic_Email_Hunter/4.0",
+ "Atomz/1.0",
+ "atSpider/1.0",
+ "Attentio/Nutch-0.9-dev (Attentio's beta blog crawler; www.attentio.com; info@attentio.com)",
+ "augurfind",
+ "augurnfind V-1.x",
+ "autoemailspider",
+ "autowebdir 1.1 (www.autowebdir.com)",
+ "AV Fetch 1.0",
+ "AVSearch-1.0(peter.turney@nrc.ca)",
+ "AVSearch-3.0(AltaVista/AVC)",
+ "axadine/ (Axadine Crawler; http://www.axada.de/; )",
+ "AxmoRobot - Crawling your site for better indexing on www.axmo.com search engine.",
+ "BabalooSpider/1.3 (BabalooSpider; http://www.babaloo.si; spider@babaloo.si)",
+ "BaboomBot/1.x.x (+http://www.baboom.us)",
+ "BaiduImagespider+(+http://www.baidu.jp/search/s308.html)",
+ "BaiDuSpider",
+ "Baiduspider+(+http://help.baidu.jp/system/05.html)",
+ "Baiduspider+(+http://www.baidu.com/search/spider.htm)",
+ "Baiduspider+(+http://www.baidu.com/search/spider_jp.html)",
+ "Balihoo/Nutch-1.0-dev (Crawler for Balihoo.com search engine - obeys robots.txt and robots meta tags ; http://balihoo.com/index.aspx; robot at balihoo dot com)",
+ "BarraHomeCrawler (albertof@barrahome.org)",
+ "bdcindexer_2.6.2 (research@bdc)",
+ "BDFetch",
+ "BDNcentral Crawler v2.3 [en] (http://www.bdncentral.com/robot.html) (X11; I; Linux 2.0.44 i686)",
+ "beautybot/1.0 (+http://www.uchoose.de/crawler/beautybot/)",
+ "BebopBot/2.5.1 ( crawler http://www.apassion4jazz.net/bebopbot.html )",
+ "BigCliqueBOT/1.03-dev (bigclicbot; http://www.bigclique.com; bot@bigclique.com)",
+ "BIGLOTRON (Beta 2;GNU/Linux)",
+ "Bigsearch.ca/Nutch-x.x-dev (Bigsearch.ca Internet Spider; http://www.bigsearch.ca/; info@enhancededge.com)",
+ "BilgiBetaBot/0.8-dev (bilgi.com (Beta) ; http://lucene.apache.org/nutch/bot.html; nutch-agent@lucene.apache.org)",
+ "BilgiBot/1.0(beta) (http://www.bilgi.com/; bilgi at bilgi dot com)",
+ "Bitacle bot/1.1",
+ "Bitacle Robot (V:1.0;) (http://www.bitacle.com)",
+ "BlackWidow",
+ "Blaiz-Bee/1.0 (+http://www.blaiz.net)",
+ "Blaiz-Bee/2.00.8222 (BE Internet Search Engine http://www.rawgrunt.com)",
+ "Blaiz-Bee/2.00.xxxx (+http://www.blaiz.net)",
+ "BlitzBOT@tricus.net",
+ "BlitzBOT@tricus.net (Mozilla compatible)",
+ "BlogBot/1.x",
+ "Bloglines Title Fetch/1.0 (http://www.bloglines.com)",
+ "Bloglines-Images/0.1 (http://www.bloglines.com)",
+ "Bloglines/3.1 (http://www.bloglines.com)",
+ "Blogpulse (info@blogpulse.com)",
+ "BlogPulseLive (support@blogpulse.com)",
+ "BlogSearch/1.x +http://www.icerocket.com/",
+ "blogsearchbot-pumpkin-3",
+ "BlogsNowBot, V 2.01 (+http://www.blogsnow.com/)",
+ "BlogVibeBot-v1.1 (spider@blogvibe.nl)",
+ "blogWatcher_Spider/0.1 (http://www.lr.pi.titech.ac.jp/blogWatcher/)",
+ "BlogzIce/1.0 (+http://icerocket.com; rhodes@icerocket.com)",
+ "BlogzIce/1.0 +http://www.icerocket.com/",
+ "BloobyBot",
+ "Bloodhound/Nutch-0.9 (Testing Crawler for Research - obeys robots.txt and robots meta tags ; http://balihoo.com/index.aspx; robot at balihoo dot com)",
+ "boitho.com-dc/0.xx (http://www.boitho.com/dcbot.html)",
+ "boitho.com-robot/1.x",
+ "boitho.com-robot/1.x (http://www.boitho.com/bot.html)",
+ "BPImageWalker/2.0 (www.bdbrandprotect.com)",
+ "BravoBrian SpiderEngine MarcoPolo",
+ "BruinBot (+http://webarchive.cs.ucla.edu/bruinbot.html) ",
+ "BSDSeek/1.0",
+ "BTbot/0.x (+http://www.btbot.com/btbot.html)",
+ "BuildCMS crawler (http://www.buildcms.com/crawler)",
+ "BullsEye",
+ "bumblebee@relevare.com",
+ "BurstFindCrawler/1.1 (crawler.burstfind.com; http://crawler.burstfind.com; crawler@burstfind.com)",
+ "Buscaplus Robi/1.0 (http://www.buscaplus.com/robi/)",
+ "bwh3_user_agent",
+ "Cabot/Nutch-0.9 (Amfibi's web-crawling robot; http://www.amfibi.com/cabot/; agent@amfibi.com)",
+ "Cabot/Nutch-1.0-dev (Amfibi's web-crawling robot; http://www.amfibi.com/cabot/; agent@amfibi.com)",
+ "carleson/1.0",
+ "Carnegie_Mellon_University_Research_WebBOT-->PLEASE READ-->http://www.andrew.cmu.edu/~brgordon/webbot/index.html http://www.andrew.cmu.edu/~brgordon/webbot/index.html",
+ "Carnegie_Mellon_University_WebCrawler http://www.andrew.cmu.edu/~brgordon/webbot/index.html",
+ "Catall Spider",
+ "CazoodleBot/CazoodleBot-0.1 (CazoodleBot Crawler; http://www.cazoodle.com/cazoodlebot; cazoodlebot@cazoodle.com)",
+ "CCBot/1.0 (+http://www.commoncrawl.org/bot.html)",
+ "ccubee/x.x",
+ "Ceramic Tile Installation Guide (http://www.floorstransformed.com)",
+ "cfetch/1.0",
+ "China Local Browse 2.6",
+ "ChristCRAWLER 2.0",
+ "CipinetBot (http://www.cipinet.com/bot.html)",
+ "ClariaBot/1.0",
+ "Claymont.com",
+ "CloakDetect/0.9 (+http://fulltext.seznam.cz/)",
+ "Clushbot/2.x (+http://www.clush.com/bot.html)",
+ "Clushbot/3.x-BinaryFury (+http://www.clush.com/bot.html)",
+ "Clushbot/3.xx-Ajax (+http://www.clush.com/bot.html)",
+ "Clushbot/3.xx-Hector (+http://www.clush.com/bot.html)",
+ "Clushbot/3.xx-Peleus (+http://www.clush.com/bot.html)",
+ "Cogentbot/1.X (+http://www.cogentsoftwaresolutions.com/bot.html)",
+ "combine/0.0",
+ "Combine/2.0 http://combine.it.lth.se/",
+ "Combine/3 http://combine.it.lth.se/",
+ "Combine/x.0",
+ "cometrics-bot, http://www.cometrics.de",
+ "Computer_and_Automation_Research_Institute_Crawler crawler@ilab.sztaki.hu",
+ "Comrite/0.7.1 (Nutch; http://lucene.apache.org/nutch/bot.html; nutch-agent@lucene.apache.org)",
+ "ContactBot/0.2",
+ "ContentSmartz",
+ "Convera Internet Spider V6.x",
+ "ConveraCrawler/0.2",
+ "ConveraCrawler/0.9d (+http://www.authoritativeweb.com/crawl)",
+ "ConveraMultiMediaCrawler/0.1 (+http://www.authoritativeweb.com/crawl)",
+ "CoolBot",
+ "cosmos/0.8_(robot@xyleme.com)",
+ "cosmos/0.9_(robot@xyleme.com)",
+ "CougarSearch/0.x (+http://www.cougarsearch.com/faq.shtml)",
+ "Covac TexAs Arachbot",
+ "Cowbot-0.1 (NHN Corp. / +82-2-3011-1954 / nhnbot@naver.com)",
+ "Cowbot-0.1.x (NHN Corp. / +82-2-3011-1954 / nhnbot@naver.com)",
+ "CrawlConvera0.1 (CrawlConvera@yahoo.com)",
+ "Crawler (cometsearch@cometsystems.com)",
+ "Crawler admin@crawler.de",
+ "Crawler V 0.2.x admin@crawler.de",
+ "crawler@alexa.com",
+ "CrawlerBoy Pinpoint.com",
+ "Crawllybot/0.1 (Crawllybot; +http://www.crawlly.com; crawler@crawlly.com)",
+ "CreativeCommons/0.06-dev (Nutch; http://www.nutch.org/docs/en/bot.html; nutch-agent@lists.sourceforge.net)",
+ "CrocCrawler vx.3 [en] (http://www.croccrawler.com) (X11; I; Linux 2.0.44 i686)",
+ "csci_b659/0.13",
+ "Cuasarbot/0.9b http://www.cuasar.com/spider_beta/ ",
+ "CurryGuide SiteScan 1.1",
+ "Custom Spider www.bisnisseek.com /1.0",
+ "CyberPatrol SiteCat Webbot (http://www.cyberpatrol.com/cyberpatrolcrawler.asp)",
+ "CydralSpider/1.x (Cydral Web Image Search; http://www.cydral.com)",
+ "CydralSpider/3.0 (Cydral Image Search; http://www.cydral.com)",
+ "DataCha0s/2.0",
+ "DataCha0s/2.0",
+ "DataFountains/DMOZ Downloader",
+ "DataFountains/Dmoz Downloader (http://ivia.ucr.edu/useragents.shtml)",
+ "DataFountains/DMOZ Feature Vector Corpus Creator (http://ivia.ucr.edu/useragents.shtml)",
+ "DataparkSearch/4.47 (+http://dataparksearch.org/bot)",
+ "DataparkSearch/4.xx (http://www.dataparksearch.org/)",
+ "DataSpear/1.0 (Spider; http://www.dataspear.com/spider.html; spider@dataspear.com)",
+ "DataSpearSpiderBot/0.2 (DataSpear Spider Bot; http://dssb.dataspear.com/bot.html; dssb@dataspear.com)",
+ "DatenBot( http://www.sicher-durchs-netz.de/bot.html)",
+ "DaviesBot/1.7 (www.wholeweb.net)",
+ "daypopbot/0.x",
+ "dbDig(http://www.prairielandconsulting.com)",
+ "DBrowse 1.4b",
+ "DBrowse 1.4d",
+ "dCSbot/1.1",
+ "de.searchengine.comBot 1.2 (http://de.searchengine.com/spider)",
+ "deepak-USC/ISI",
+ "DeepIndex",
+ "DeepIndex ( http://www.zetbot.com )",
+ "DeepIndex (www.en.deepindex.com)",
+ "DeepIndexer.ca",
+ "Demo Bot DOT 16b",
+ "Demo Bot Z 16b",
+ "Denmex websearch (http://search.denmex.com)",
+ "dev-spider2.searchpsider.com/1.3b",
+ "DiaGem/1.1 (http://www.skyrocket.gr.jp/diagem.html)",
+ "Diamond/x.0",
+ "DiamondBot",
+ "Digger/1.0 JDK/1.3.0rc3",
+ "DigOut4U",
+ "DIIbot/1.2",
+ "disco/Nutch-0.9 (experimental crawler; www.discoveryengine.com; disco-crawl@discoveryengine.com)",
+ "disco/Nutch-1.0-dev (experimental crawler; www.discoveryengine.com; disco-crawl@discoveryengine.com)",
+ "DittoSpyder",
+ "dloader(NaverRobot)/1.0",
+ "DoCoMo/1.0/Nxxxi/c10",
+ "DoCoMo/1.0/Nxxxi/c10/TB",
+ "DoCoMo/2.0 P900iV(c100;TB;W24H11) ",
+ "DoCoMo/2.0 SH902i (compatible; Y!J-SRD/1.0; http://help.yahoo.co.jp/help/jp/search/indexing/indexing-27.html)",
+ "DoCoMo/2.0/SO502i (compatible; Y!J-SRD/1.0; http://help.yahoo.co.jp/help/jp/search/indexing/indexing-27.html)",
+ "dodgebot/experimental",
+ "Download-Tipp Linkcheck (http://download-tipp.de/)",
+ "Drecombot/1.0 (http://career.drecom.jp/bot.html)",
+ "DSurf15a 01",
+ "DSurf15a 71",
+ "DSurf15a 81",
+ "DSurf15a VA",
+ "dtSearchSpider",
+ "DuckDuckBot/1.0; (+http://duckduckgo.com/duckduckbot.html)",
+ "Dumbot(version 0.1 beta - dumbfind.com)",
+ "Dumbot(version 0.1 beta - http://www.dumbfind.com/dumbot.html)",
+ "Dumbot(version 0.1 beta)",
+ "e-sense 1.0 ea(www.vigiltech.com/esensedisclaim.html)",
+ "e-SocietyRobot(http://www.yama.info.waseda.ac.jp/~yamana/es/)",
+ "eApolloBot/2.0 (compatible; heritrix/2.0.0-SNAPSHOT-20071024.170148 +http://www.eapollo-opto.com)",
+ "EARTHCOM.info/1.x [www.earthcom.info]",
+ "EARTHCOM.info/1.xbeta [www.earthcom.info]",
+ "EasyDL/3.xx",
+ "EasyDL/3.xx http://keywen.com/Encyclopedia/Bot",
+ "EBrowse 1.4b",
+ "EchO!/2.0",
+ "Educate Search VxB",
+ "egothor/3.0a (+http://www.xdefine.org/robot.html)",
+ "EgotoBot/4.8 (+http://www.egoto.com/about.htm)",
+ "ejupiter.com",
+ "elfbot/1.0 (+http://www.uchoose.de/crawler/elfbot/)",
+ "ELI/20070402:2.0 (DAUM RSS Robot, Daum Communications Corp.; +http://ws.daum.net/aboutkr.html)",
+ "EmailSiphon",
+ "EmailSpider",
+ "EmailWolf 1.00",
+ "EMPAS_ROBOT",
+ "EnaBot/1.x (http://www.enaball.com/crawler.html)",
+ "Enfish Tracker",
+ "Enterprise_Search/1.0",
+ "Enterprise_Search/1.0.xxx",
+ "Enterprise_Search/1.00.xxx;MSSQL (http://www.innerprise.net/es-spider.asp)",
+ "envolk/1.7 (+http://www.envolk.com/envolkspiderinfo.php)",
+ "envolk[ITS]spider/1.6(+http://www.envolk.com/envolkspider.html)",
+ "EroCrawler",
+ "ES.NET_Crawler/2.0 (http://search.innerprise.net/)",
+ "eseek-larbin_2.6.2 (crawler@exactseek.com)",
+ "ESISmartSpider",
+ "eStyleSearch 4 (compatible; MSIE 6.0; Windows NT 5.0)",
+ "ESurf15a 15",
+ "EuripBot/0.x (+http://www.eurip.com) GetFile",
+ "EuripBot/0.x (+http://www.eurip.com) GetRobots",
+ "EuripBot/0.x (+http://www.eurip.com) PreCheck",
+ "Eurobot/1.0 (http://www.ayell.eu)",
+ "EvaalSE - bot@evaal.com",
+ "eventax/1.3 (eventax; http://www.eventax.de/; info@eventax.de)",
+ "Everest-Vulcan Inc./0.1 (R&D project; host=e-1-24; http://everest.vulcan.com/crawlerhelp)",
+ "Everest-Vulcan Inc./0.1 (R&D project; http://everest.vulcan.com/crawlerhelp)",
+ "Exabot-Images/1.0",
+ "Exabot-Test/1.0",
+ "Exabot/2.0",
+ "Exabot/3.0",
+ "ExactSeek Crawler/0.1",
+ "exactseek-crawler-2.63 (crawler@exactseek.com)",
+ "exactseek-pagereaper-2.63 (crawler@exactseek.com)",
+ "exactseek.com",
+ "Exalead NG/MimeLive Client (convert/http/0.120)",
+ "Excalibur Internet Spider V6.5.4",
+ "Execrawl/1.0 (Execrawl; http://www.execrawl.com/; bot@execrawl.com)",
+ "exooba crawler/exooba crawler (crawler for exooba.com; http://www.exooba.com/; info at exooba dot com)",
+ "exooba/exooba crawler (exooba; exooba)",
+ "ExperimentalHenrytheMiragoRobot",
+ "ExtractorPro",
+ "EyeCatcher (Download-tipp.de)/1.0",
+ "Factbot 1.09 (see http://www.factbites.com/webmasters.php)",
+ "factbot : http://www.factbites.com/robots",
+ "Fast Crawler Gold Edition",
+ "FAST Enterprise Crawler 6 (Experimental)",
+ "FAST Enterprise Crawler 6 / Scirus scirus-crawler@fast.no; http://www.scirus.com/srsapp/contactus/",
+ "FAST Enterprise Crawler 6 used by Cobra Development (admin@fastsearch.com)",
+ "FAST Enterprise Crawler 6 used by Comperio AS (sts@comperio.no)",
+ "FAST Enterprise Crawler 6 used by FAST (FAST)",
+ "FAST Enterprise Crawler 6 used by Pages Jaunes (pvincent@pagesjaunes.fr)",
+ "FAST Enterprise Crawler 6 used by Sensis.com.au Web Crawler (search_comments\\at\\sensis\\dot\\com\\dot\\au)",
+ "FAST Enterprise Crawler 6 used by Singapore Press Holdings (crawler@sphsearch.sg)",
+ "FAST Enterprise Crawler/6 (www.fastsearch.com)",
+ "FAST Enterprise Crawler/6.4 (helpdesk at fast.no)",
+ "FAST FirstPage retriever (compatible; MSIE 5.5; Mozilla/4.0)",
+ "FAST MetaWeb Crawler (helpdesk at fastsearch dot com)",
+ "Fast PartnerSite Crawler",
+ "FAST-WebCrawler/2.2.10 (Multimedia Search) (crawler@fast.no; http://www.fast.no/faq/faqfastwebsearch/faqfastwebcrawler.html)",
+ "FAST-WebCrawler/2.2.6 (crawler@fast.no; http://www.fast.no/faq/faqfastwebsearch/faqfastwebcrawler.html)",
+ "FAST-WebCrawler/2.2.7 (crawler@fast.no; http://www.fast.no/faq/faqfastwebsearch/faqfastwebcrawler.html)http://www.fast.no",
+ "FAST-WebCrawler/2.2.8 (crawler@fast.no; http://www.fast.no/faq/faqfastwebsearch/faqfastwebcrawler.html)http://www.fast.no",
+ "FAST-WebCrawler/3.2 test",
+ "FAST-WebCrawler/3.3 (crawler@fast.no; http://fast.no/support.php?c=faqs/crawler)",
+ "FAST-WebCrawler/3.4/Nirvana (crawler@fast.no; http://fast.no/support.php?c=faqs/crawler)",
+ "FAST-WebCrawler/3.4/PartnerSite (crawler@fast.no; http://fast.no/support.php?c=faqs/crawler)",
+ "FAST-WebCrawler/3.5 (atw-crawler at fast dot no; http://fast.no/support.php?c=faqs/crawler)",
+ "FAST-WebCrawler/3.6 (atw-crawler at fast dot no; http://fast.no/support/crawler.asp)",
+ "FAST-WebCrawler/3.6/FirstPage (crawler@fast.no; http://fast.no/support.php?c=faqs/crawler)",
+ "FAST-WebCrawler/3.7 (atw-crawler at fast dot no; http://fast.no/support/crawler.asp)",
+ "FAST-WebCrawler/3.7/FirstPage (atw-crawler at fast dot no;http://fast.no/support/crawler.asp)",
+ "FAST-WebCrawler/3.8 (atw-crawler at fast dot no; http://fast.no/support/crawler.asp)",
+ "FAST-WebCrawler/3.8/Fresh (atw-crawler at fast dot no; http://fast.no/support/crawler.asp)",
+ "FAST-WebCrawler/3.x Multimedia",
+ "FAST-WebCrawler/3.x Multimedia (mm dash crawler at fast dot no)",
+ "fastbot crawler beta 2.0 (+http://www.fastbot.de)",
+ "FastBug http://www.ay-up.com",
+ "FastCrawler 3.0.1 (crawler@1klik.dk)",
+ "FastSearch Web Crawler for Verizon SuperPages (kevin.watters@fastsearch.com)",
+ "Favcollector/2.0 (info@favcollector.com http://www.favcollector.com/)",
+ "favo.eu crawler/0.6 (http://www.favo.eu)",
+ "Faxobot/1.0",
+ "Feed Seeker Bot (RSS Feed Seeker http://www.MyNewFavoriteThing.com/fsb.php)",
+ "Feed24.com",
+ "FeedChecker/0.01",
+ "Feedfetcher-Google; (+http://www.google.com/feedfetcher.html)",
+ "FeedHub FeedDiscovery/1.0 (http://www.feedhub.com)",
+ "FeedHub MetaDataFetcher/1.0 (http://www.feedhub.com)",
+ "Feedjit Favicon Crawler 1.0",
+ "Feedster Crawler/3.0; Feedster, Inc.",
+ "Felix - Mixcat Crawler (+http://mixcat.com)",
+ "FFC Trap Door Spider",
+ "Filtrbox/1.0",
+ "Findexa Crawler (http://www.findexa.no/gulesider/article26548.ece)",
+ "findlinks/x.xxx (+http://wortschatz.uni-leipzig.de/findlinks/) ",
+ "FineBot",
+ "Firefly/1.0",
+ "Firefly/1.0 (compatible; Mozilla 4.0; MSIE 5.5)",
+ "Firefox (kastaneta03@hotmail.com)",
+ "Firefox_1.0.6 (kasparek@naparek.cz)",
+ "FirstGov.gov Search - POC:firstgov.webmasters@gsa.gov",
+ "firstsbot",
+ "Flapbot/0.7.2 (Flaptor Crawler; http://www.flaptor.com; crawler at flaptor period com)",
+ "Flexum spider",
+ "Flexum/2.0",
+ "FlickBot 2.0 RPT-HTTPClient/0.3-3",
+ "flunky",
+ "FnooleBot/2.5.2 (+http://www.fnoole.com/addurl.html)",
+ "FocusedSampler/1.0",
+ "Folkd.com Spider/0.1 beta 1 (www.folkd.com)",
+ "Fooky.com/ScorpionBot/ScoutOut; http://www.fooky.com/scorpionbots",
+ "Francis/1.0 (francis@neomo.de http://www.neomo.de/)",
+ "Franklin Locator 1.8",
+ "FreeFind.com-SiteSearchEngine/1.0 (http://freefind.com; spiderinfo@freefind.com)",
+ "FreshNotes crawler< report problems to crawler-at-freshnotes-dot-com",
+ "FSurf15a 01",
+ "FTB-Bot http://www.findthebest.co.uk/",
+ "Full Web Bot 0416B",
+ "Full Web Bot 0516B",
+ "Full Web Bot 2816B",
+ "FuseBulb.Com",
+ "FyberSpider (+http://www.fybersearch.com/fyberspider.php)",
+ "GAIS Robot/1.0B2",
+ "Gaisbot/3.0 (indexer@gais.cs.ccu.edu.tw; http://gais.cs.ccu.edu.tw/robot.php)",
+ "Gaisbot/3.0+(robot06@gais.cs.ccu.edu.tw;+http://gais.cs.ccu.edu.tw/robot.php)",
+ "GalaxyBot/1.0 (http://www.galaxy.com/galaxybot.html)",
+ "Gallent Search Spider v1.4 Robot 2 (http://robot.GallentSearch.com)",
+ "gamekitbot/1.0 (+http://www.uchoose.de/crawler/gamekitbot/)",
+ "GammaSpider/1.0",
+ "gazz/x.x (gazz@nttrd.com)",
+ "generic_crawler/01.0217/",
+ "genieBot (http://64.5.245.11/faq/faq.html)",
+ "geniebot wgao@genieknows.com",
+ "GeonaBot 1.x; http://www.geona.com/",
+ "gigabaz/3.1x (baz@gigabaz.com; http://gigabaz.com/gigabaz/)",
+ "Gigabot/2.0 (gigablast.com)",
+ "Gigabot/2.0/gigablast.com/spider.html",
+ "Gigabot/2.0; http://www.gigablast.com/spider.html",
+ "Gigabot/2.0att",
+ "Gigabot/3.0 (http://www.gigablast.com/spider.html)",
+ "Gigabot/x.0",
+ "GigabotSiteSearch/2.0 (sitesearch.gigablast.com)",
+ "GNODSPIDER (www.gnod.net)",
+ "Goblin/0.9 (http://www.goguides.org/)",
+ "Goblin/0.9.x (http://www.goguides.org/goblin-info.html)",
+ "GoForIt.com",
+ "GOFORITBOT ( http://www.goforit.com/about/ )",
+ "gonzo1[P] +http://www.suchen.de/popups/faq.jsp",
+ "gonzo2[P] +http://www.suchen.de/faq.html",
+ "Goofer/0.2",
+ "Googlebot-Image/1.0",
+ "Googlebot-Image/1.0 ( http://www.googlebot.com/bot.html)",
+ "Googlebot/2.1 ( http://www.google.com/bot.html)",
+ "Googlebot/2.1 ( http://www.googlebot.com/bot.html)",
+ "Googlebot/Test ( http://www.googlebot.com/bot.html)",
+ "GrapeFX/0.3 libwww/5.4.0",
+ "great-plains-web-spider/flatlandbot (Flatland Industries Web Spider; http://www.flatlandindustries.com/flatlandbot.php; jason@flatlandindustries.com)",
+ "GrigorBot 0.8 (http://www.grigor.biz/bot.html)",
+ "Gromit/1.0",
+ "grub crawler(http://www.grub.org)",
+ "grub-client",
+ "gsa-crawler (Enterprise; GID-01422; jplastiras@google.com)",
+ "gsa-crawler (Enterprise; GID-01742;gsatesting@rediffmail.com)",
+ "gsa-crawler (Enterprise; GIX-02057; dm@enhesa.com)",
+ "gsa-crawler (Enterprise; GIX-03519; cknuetter@stubhub.com)",
+ "gsa-crawler (Enterprise; GIX-0xxxx; enterprise-training@google.com)",
+ "Guestbook Auto Submitter",
+ "Gulliver/1.3",
+ "Gulper Web Bot 0.2.4 (www.ecsl.cs.sunysb.edu/~maxim/cgi-bin/Link/GulperBot)",
+ "Gungho/0.08004 (http://code.google.com/p/gungho-crawler/wiki/Index)",
+ "GurujiBot/1.0 (+http://www.guruji.com/WebmasterFAQ.html)",
+ "GurujiImageBot/1.0 (+http://www.guruji.com/en/WebmasterFAQ.html)",
+ "HappyFunBot/1.1",
+ "Harvest-NG/1.0.2",
+ "Hatena Antenna/0.4 (http://a.hatena.ne.jp/help#robot)",
+ "Hatena Pagetitle Agent/1.0",
+ "Hatena RSS/0.3 (http://r.hatena.ne.jp)",
+ "hbtronix.spider.2 -- http://hbtronix.de/spider.php",
+ "HeinrichderMiragoRobot",
+ "HeinrichderMiragoRobot (http://www.miragorobot.com/scripts/deinfo.asp)",
+ "Helix/1.x ( http://www.sitesearch.ca/helix/)",
+ "HenriLeRobotMirago (http://www.miragorobot.com/scripts/frinfo.asp)",
+ "HenrytheMiragoRobot",
+ "HenryTheMiragoRobot (http://www.miragorobot.com/scripts/mrinfo.asp)",
+ "Hi! I'm CsCrawler my homepage: http://www.kde.cs.uni-kassel.de/lehre/ss2005/googlespam/crawler.html RPT-HTTPClient/0.3-3",
+ "Hippias/0.9 Beta",
+ "HitList",
+ "Hitwise Spider v1.0 http://www.hitwise.com",
+ "holmes/3.11 (http://morfeo.centrum.cz/bot)",
+ "holmes/3.9 (onet.pl)",
+ "holmes/3.xx (OnetSzukaj/5.0; +http://szukaj.onet.pl)",
+ "holmes/x.x",
+ "HolmesBot (http://holmes.ge)",
+ "HomePageSearch(hpsearch.uni-trier.de)",
+ "Homerbot: www.homerweb.com",
+ "Honda-Search/0.7.2 (Nutch; http://lucene.apache.org/nutch/bot.html; search@honda-search.com)",
+ "HooWWWer/2.1.3 (debugging run) (+http://cosco.hiit.fi/search/hoowwwer/ | mailto:crawler-info
hiit.fi)",
+ "HooWWWer/2.1.x ( http://cosco.hiit.fi/search/hoowwwer/ | mailto:crawler-infohiit.fi)",
+ "HPL/Nutch-0.9 -",
+ "htdig/3.1.6 (http://computerorgs.com)",
+ "htdig/3.1.6 (unconfigured@htdig.searchengine.maintainer)",
+ "htdig/3.1.x (root@localhost)",
+ "http://Ask.24x.Info/ (http://narres.it/)",
+ "http://hilfe.acont.de/bot.html ACONTBOT",
+ "http://www.almaden.ibm.com/cs/crawler",
+ "http://www.almaden.ibm.com/cs/crawler [rc1.wf.ibm.com]",
+ "http://www.almaden.ibm.com/cs/crawler [wf216]",
+ "http://www.istarthere.com_spider@istarthere.com",
+ "http://www.monogol.de",
+ "http://www.trendtech.dk/spider.asp)",
+ "i1searchbot/2.0 (i1search web crawler; http://www.i1search.com; crawler@i1search.com)",
+ "IAArchiver-1.0",
+ "iaskspider2 (iask@staff.sina.com.cn)",
+ "ia_archiver",
+ "ia_archiver-web.archive.org",
+ "ia_archiver/1.6",
+ "ICC-Crawler(Mozilla-compatible; http://kc.nict.go.jp/icc/crawl.html; icc-crawl(at)ml(dot)nict(dot)go(dot)jp)",
+ "ICC-Crawler(Mozilla-compatible;http://kc.nict.go.jp/icc/crawl.html;icc-crawl-contact(at)ml(dot)nict(dot)go(dot)jp)",
+ "iCCrawler (http://www.iccenter.net)",
+ "ICCrawler - ICjobs (http://www.icjobs.de/bot.htm)",
+ "ichiro/x.0 (http://help.goo.ne.jp/door/crawler.html)",
+ "ichiro/x.0 (ichiro@nttr.co.jp)",
+ "IconSurf/2.0 favicon finder (see http://iconsurf.com/robot.html)",
+ "IconSurf/2.0 favicon monitor (see http://iconsurf.com/robot.html)",
+ "ICRA_label_spider/x.0",
+ "icsbot-0.1",
+ "ideare - SignSite/1.x",
+ "iFeed.jp/2.0 (www.psychedelix.com/agents/agents.rss; 0 subscribers)",
+ "igdeSpyder (compatible; igde.ru; +http://igde.ru/doc/tech.html)",
+ "IIITBOT/1.1 (Indian Language Web Search Engine; http://webkhoj.iiit.net; pvvpr at iiit dot ac dot in)",
+ "ilial/Nutch-0.9 (Ilial, Inc. is a Los Angeles based Internet startup company. For more information please visit http://www.ilial.com/crawler; http://www.ilial.com/crawler; crawl@ilial.com)",
+ "ilial/Nutch-0.9-dev",
+ "IlseBot/1.x",
+ "IlTrovatore-Setaccio ( http://www.iltrovatore.it)",
+ "Iltrovatore-Setaccio/0.3-dev (Indexing; http://www.iltrovatore.it/bot.html; info@iltrovatore.it)",
+ "IlTrovatore-Setaccio/1.2 ( http://www.iltrovatore.it/aiuto/faq.html)",
+ "Iltrovatore-Setaccio/1.2 (It-bot; http://www.iltrovatore.it/bot.html; info@iltrovatore.it)",
+ "iltrovatore-setaccio/1.2-dev (spidering; http://www.iltrovatore.it/aiuto/.....)",
+ "IlTrovatore/1.2 (IlTrovatore; http://www.iltrovatore.it/bot.html; bot@iltrovatore.it)",
+ "ImageWalker/2.0 (www.bdbrandprotect.com)",
+ "IncyWincy data gatherer(webmaster@loopimprovements.com",
+ "IncyWincy page crawler(webmaster@loopimprovements.com",
+ "IncyWincy(http://www.look.com)",
+ "IncyWincy(http://www.loopimprovements.com/robot.html)",
+ "IncyWincy/2.1(loopimprovements.com/robot.html)",
+ "IndexTheWeb.com Crawler7",
+ "Industry Program 1.0.x",
+ "Inet library",
+ "info@pubblisito.com- (http://www.pubblisito.com) il Sud dei Motori di Ricerca",
+ "InfoFly/1.0 (http://www.versions-project.org/)",
+ "INFOMINE/8.0 Adders",
+ "INFOMINE/8.0 RemoteServices",
+ "INFOMINE/8.0 VLCrawler (http://infomine.ucr.edu/useragents)",
+ "InfoNaviRobot(F107)",
+ "InfoSeek Sidewinder/0.9",
+ "InfoSeek Sidewinder/1.0A",
+ "InfoSeek Sidewinder/1.1A",
+ "Infoseek SideWinder/1.45 (Compatible; MSIE 10.0; UNIX)",
+ "Infoseek SideWinder/2.0B (Linux 2.4 i686)",
+ "INGRID/3.0 MT (webcrawler@NOSPAMexperimental.net; http://webmaster.ilse.nl/jsp/webmaster.jsp)",
+ "Inktomi Search",
+ "InnerpriseBot/1.0 (http://www.innerprise.com/)",
+ "Insitor.com search and find world wide!",
+ "Insitornaut",
+ "Internet Ninja x.0",
+ "InternetArchive/0.8-dev(Nutch;http://lucene.apache.org/nutch/bot.html;nutch-agent@lucene.apache",
+ "InternetSeer.com",
+ "IOI/2.0 (ISC Open Index crawler; http://index.isc.org/; bot@index.isc.org)",
+ "IPiumBot laurion(dot)com",
+ "IpselonBot/0.xx-beta (Ipselon; http://www.ipselon.com; ipselonbot@ipselon.com)",
+ "IRLbot/1.0 ( http://irl.cs.tamu.edu/crawler)",
+ "IRLbot/3.0 (compatible; MSIE 6.0; http://irl.cs.tamu.edu/crawler/)",
+ "ISC Systems iRc Search 2.1",
+ "IUPUI Research Bot v 1.9a",
+ "IWAgent/ 1.0 - www.brandprotect.com",
+ "Jabot/6.x (http://odin.ingrid.org/)",
+ "Jabot/7.x.x (http://odin.ingrid.org/)",
+ "Jack",
+ "Jambot/0.1.x (Jambot; http://www.jambot.com/blog; crawler@jambot.com)",
+ "Jambot/0.2.1 (Jambot; http://www.jambot.com/blog/static.php?page=webmaster-robot; crawler@jambot.com)",
+ "Jayde Crawler. http://www.jayde.com",
+ "Jetbot/1.0",
+ "JobSpider_BA/1.1",
+ "Jyxobot/x",
+ "k2spider",
+ "KAIST AITrc Crawler",
+ "KakleBot - www.kakle.com/0.1 (KakleBot - www.kakle.com; http:// www.kakle.com/bot.html; support@kakle.com)",
+ "kalooga/kalooga-4.0-dev-datahouse (Kalooga; http://www.kalooga.com; info@kalooga.com)",
+ "kalooga/KaloogaBot (Kalooga; http://www.kalooga.com/info.html?page=crawler; crawler@kalooga.com)",
+ "Kenjin Spider",
+ "Kevin http://dznet.com/kevin/",
+ "Kevin http://websitealert.net/kevin/",
+ "KE_1.0/2.0 libwww/5.2.8",
+ "KFSW-Bot (Version: 1.01 powered by KFSW www.kfsw.de)",
+ "kinja-imagebot (http://www.kinja.com/)",
+ "kinjabot (http://www.kinja.com)",
+ "KIT-Fireball/2.0",
+ "KIT-Fireball/2.0 (compatible; Mozilla 4.0; MSIE 5.5)",
+ "KnowItAll(knowitall@cs.washington.edu)",
+ "Knowledge.com/0.x",
+ "Krugle/Krugle,Nutch/0.8+ (Krugle web crawler; http://www.krugle.com/crawler/info.html; webcrawler@krugle.com)",
+ "KSbot/1.0 (KnowledgeStorm crawler; http://www.knowledgestorm.com/resources/content/crawler/index.html; crawleradmin@knowledgestorm.com)",
+ "kuloko-bot/0.x",
+ "kulokobot www.kuloko.com kuloko@backweave.com",
+ "kulturarw3/0.1",
+ "LapozzBot/1.4 ( http://robot.lapozz.com)",
+ "LapozzBot/1.5 (+http://robot.lapozz.hu)",
+ "larbin (samualt9@bigfoot.com)",
+ "LARBIN-EXPERIMENTAL (efp@gmx.net)",
+ "larbin_2.1.1 larbin2.1.1@somewhere.com",
+ "larbin_2.2.0 (crawl@compete.com)",
+ "larbin_2.2.1_de_Viennot (Laurent.Viennot@inria.fr)",
+ "larbin_2.2.2 (sugayama@lab7.kuis.kyoto-u.ac.jp)",
+ "larbin_2.2.2_guillaume (guillaume@liafa.jussieu.fr)",
+ "larbin_2.6.0 (larbin2.6.0@unspecified.mail)",
+ "larbin_2.6.1 (larbin2.6.1@unspecified.mail)",
+ "larbin_2.6.2 (hamasaki@grad.nii.ac.jp)",
+ "larbin_2.6.2 (larbin2.6.2@unspecified.mail)",
+ "larbin_2.6.2 (listonATccDOTgatechDOTedu)",
+ "larbin_2.6.2 (pimenas@systems.tuc.gr)",
+ "larbin_2.6.2 (tom@lemurconsulting.com)",
+ "larbin_2.6.2 (vitalbox1@hotmail.com)",
+ "larbin_2.6.3 (ltaa_web_crawler@groupes.epfl.ch)",
+ "larbin_2.6.3 (wgao@genieknows.com)",
+ "larbin_2.6.3_for_(http://cosco.hiit.fi/search/) tsilande@hiit.fi",
+ "larbin_2.6_basileocaml (basile.starynkevitch@cea.fr)",
+ "larbin_devel (http://pauillac.inria.fr/~ailleret/prog/larbin/)",
+ "lawinfo-crawler/Nutch-0.9-dev (Crawler for lawinfo.com pages; http://www.lawinfo.com; webmaster@lawinfo.com)",
+ "LECodeChecker/3.0 libgetdoc/1.0",
+ "LEIA/2.90",
+ "LEIA/3.01pr (LEIAcrawler; [SNIP])",
+ "LetsCrawl.com/1.0 +http://letscrawl.com/",
+ "LexiBot/1.00",
+ "Libby_1.1/libwww-perl/5.47",
+ "LibertyW (+http://www.lw01.com)",
+ "libWeb/clsHTTP -- hiongun@kt.co.kr",
+ "libwww-perl/5.41",
+ "libwww-perl/5.45",
+ "libwww-perl/5.48",
+ "libwww-perl/5.52 FP/2.1",
+ "libwww-perl/5.52 FP/4.0",
+ "libwww-perl/5.65",
+ "libwww-perl/5.800",
+ "libwww/5.3.2",
+ "LijitSpider/Nutch-0.9 (Reports crawler; http://www.lijit.com/; info(a)lijit(d)com)",
+ "Lincoln State Web Browser",
+ "linkbot",
+ "linknzbot",
+ "Links 2.0 (http://gossamer-threads.com/scripts/links/)",
+ "Links SQL (http://gossamer-threads.com/scripts/links-sql/)",
+ "LinkScan/11.0beta2 UnixShareware robot from Elsop.com (used by Indiafocus/Indiainfo)",
+ "LinkScan/9.0g Unix",
+ "LinkScan/x.x Unix",
+ "LiveTrans/Nutch-0.9 (maintainer: cobain at iis dot sinica dot edu dot tw; http://wkd.iis.sinica.edu.tw/LiveTrans/)",
+ "Llaut/1.0 (http://mnm.uib.es/~gallir/llaut/bot.html)",
+ "LMQueueBot/0.2",
+ "lmspider (lmspider@scansoft.com)",
+ "LNSpiderguy",
+ "LocalBot/1.0 ( http://www.localbot.co.uk/)",
+ "LocalcomBot/1.2.x ( http://www.local.com/bot.htm)",
+ "Lockstep Spider/1.0",
+ "Look.com",
+ "Lovel as 1.0 ( +http://www.everatom.com)",
+ "LTI/LemurProject Nutch Spider/Nutch-1.0-dev (lti crawler for CMU; http://www.lti.cs.cmu.edu; changkuk at cmu dot edu)",
+ "LTI/LemurProject Nutch Spider/Nutch-1.0-dev (Research spider using Nutch; http://www.lemurproject.org; mhoy@cs.cmu.edu)",
+ "lwp-trivial/1.32",
+ "lwp-trivial/1.34",
+ "lwp-trivial/1.34",
+ "LWP::Simple/5.22",
+ "LWP::Simple/5.36",
+ "LWP::Simple/5.48",
+ "LWP::Simple/5.50",
+ "LWP::Simple/5.51",
+ "LWP::Simple/5.53",
+ "LWP::Simple/5.63",
+ "LWP::Simple/5.803",
+ "Lycos_Spider_(modspider)",
+ "Lycos_Spider_(T-Rex)",
+ "Lynx/2.8.4rel.1 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/0.9.6c (human-guided@lerly.net)",
+ "Mac Finder 1.0.xx",
+ "Mackster( http://www.ukwizz.com )",
+ "Mahiti.Com/Mahiti Crawler-1.0 (Mahiti.Com; http://mahiti.com ; mahiti.com)",
+ "Mail.Ru/1.0",
+ "mailto:webcraft@bea.com",
+ "mammoth/1.0 ( http://www.sli-systems.com/)",
+ "MantraAgent",
+ "MapoftheInternet.com ( http://MapoftheInternet.com)",
+ "Mariner/5.1b [de] (Win95; I ;Kolibri gncwebbot)",
+ "Marketwave Hit List",
+ "Martini",
+ "MARTINI",
+ "Marvin v0.3",
+ "MaSagool/1.0 (MaSagool; http://sagool.jp/; info@sagool.jp)",
+ "MasterSeek",
+ "Mata Hari/2.00 ",
+ "Matrix S.p.A. - FAST Enterprise Crawler 6 (Unknown admin e-mail address)",
+ "maxomobot/dev-20051201 (maxomo; http://67.102.134.34:4047/MAXOMO/MAXOMObot.html; maxomobot@maxomo.com)",
+ "MDbot/1.0 (+http://www.megadownload.net/bot.html)",
+ "MediaCrawler-1.0 (Experimental)",
+ "Mediapartners-Google/2.1 ( http://www.googlebot.com/bot.html)",
+ "MediaSearch/0.1",
+ "MegaSheep v1.0 (www.searchuk.com internet sheep)",
+ "Megite2.0 (http://www.megite.com)",
+ "Mercator-1.x",
+ "Mercator-2.0",
+ "Mercator-Scrub-1.1",
+ "Metaeuro Web Crawler/0.2 (MetaEuro Web Search Clustering Engine; http://www.metaeuro.com; crawler at metaeuro dot com)",
+ "MetaGer-LinkChecker",
+ "MetagerBot/0.8-dev (MetagerBot; http://metager.de; )",
+ "MetaGer_PreChecker0.1",
+ "Metaspinner/0.01 (Metaspinner; http://www.meta-spinner.de/; support@meta-spinner.de/)",
+ "metatagsdir/0.7 (+http://metatagsdir.com/directory/)",
+ "MFC Foundation Class Library 4.0",
+ "MicroBaz",
+ "Microsoft Small Business Indexer",
+ "Microsoft URL Control - 6.00.8xxx",
+ "MicrosoftPrototypeCrawler (How's my crawling? mailto:newbiecrawler@hotmail.com)",
+ "Missauga Locate 1.0.0",
+ "Missigua Locator 1.9",
+ "Missouri College Browse",
+ "Misterbot-Nutch/0.7.1 (Misterbot-Nutch; http://www.misterbot.fr; admin@misterbot.fr)",
+ "Miva (AlgoFeedback@miva.com)",
+ "Mizzu Labs 2.2",
+ "MJ12bot/vx.x.x (http://majestic12.co.uk/bot.php?+)",
+ "MJ12bot/vx.x.x (http://www.majestic12.co.uk/projects/dsearch/mj12bot.php)",
+ "MJBot (SEO assessment)",
+ "MLBot (www.metadatalabs.com)",
+ "MnogoSearch/3.2.xx",
+ "Mo College 1.9",
+ "moget/x.x (moget@goo.ne.jp)",
+ "mogimogi/1.0",
+ "MojeekBot/0.x (archi; http://www.mojeek.com/bot.html)",
+ "Morris - Mixcat Crawler ( http://mixcat.com)",
+ "Mouse-House/7.4 (spider_monkey spider info at www.mobrien.com/sm.shtml)",
+ "mozDex/0.xx-dev (mozDex; http://www.mozdex.com/en/bot.html; spider@mozdex.com)",
+ "Mozilla (Mozilla@somewhere.com)",
+ "Mozilla 4.0(compatible; BotSeer/1.0; +http://botseer.ist.psu.edu)",
+ "Mozilla/2.0 (compatible; Ask Jeeves)",
+ "Mozilla/2.0 (compatible; Ask Jeeves/Teoma)",
+ "Mozilla/2.0 (compatible; Ask Jeeves/Teoma; http://about.ask.com/en/docs/about/webmasters.shtml) ",
+ "Mozilla/2.0 (compatible; Ask Jeeves/Teoma; http://sp.ask.com/docs/about/tech_crawling.html)",
+ "Mozilla/2.0 (compatible; EZResult -- Internet Search Engine)",
+ "Mozilla/2.0 (compatible; NEWT ActiveX; Win32)",
+ "Mozilla/2.0 (compatible; T-H-U-N-D-E-R-S-T-O-N-E)",
+ "Mozilla/3.0 (compatible; Fluffy the spider; http://www.searchhippo.com/; info@searchhippo.com)",
+ "Mozilla/3.0 (compatible; Indy Library)",
+ "Mozilla/3.0 (compatible; MuscatFerret/1.5.4; claude@euroferret.com)",
+ "Mozilla/3.0 (compatible; MuscatFerret/1.5; olly@muscat.co.uk)",
+ "Mozilla/3.0 (compatible; MuscatFerret/1.6.x; claude@euroferret.com)",
+ "Mozilla/3.0 (compatible; scan4mail (advanced version) http://www.peterspages.net/?scan4mail)",
+ "Mozilla/3.0 (compatible; ScollSpider; http://www.webwobot.com)",
+ "Mozilla/3.0 (compatible; Webinator-DEV01.home.iprospect.com/2.56)",
+ "Mozilla/3.0 (compatible; Webinator-indexer.cyberalert.com/2.56)",
+ "Mozilla/3.0 (INGRID/3.0 MT; webcrawler@NOSPAMexperimental.net; http://aanmelden.ilse.nl/?aanmeld_mode=webhints)",
+ "Mozilla/3.0 (Slurp.so/Goo; slurp@inktomi.com; http://www.inktomi.com/slurp.html)",
+ "Mozilla/3.0 (Slurp/cat; slurp@inktomi.com; http://www.inktomi.com/slurp.html)",
+ "Mozilla/3.0 (Slurp/si; slurp@inktomi.com; http://www.inktomi.com/slurp.html)",
+ "Mozilla/3.0 (Vagabondo/1.1 MT; webcrawler@NOSPAMwise-guys.nl; http://webagent.wise-guys.nl/)",
+ "Mozilla/3.0 (Vagabondo/1.x MT; webagent@wise-guys.nl; http://webagent.wise-guys.nl/)",
+ "Mozilla/3.0 (Vagabondo/2.0 MT; webcrawler@NOSPAMexperimental.net; http://aanmelden.ilse.nl/?aanmeld_mode=webhints)",
+ "Mozilla/3.0 (Vagabondo/2.0 MT; webcrawler@NOSPAMwise-guys.nl; http://webagent.wise-guys.nl/)",
+ "Mozilla/3.01 (Compatible; Links2Go Similarity Engine)",
+ "Mozilla/4.0",
+ "Mozilla/4.0 (agadine3.0) www.agada.de",
+ "Mozilla/4.0 (compatible: AstraSpider V.2.1 : astrafind.com)",
+ "Mozilla/4.0 (compatible; Vagabondo/2.2; webcrawler at wise-guys dot nl; http://webagent.wise-guys.nl/)",
+ "Mozilla/4.0 (compatible; Vagabondo/4.0Beta; webcrawler at wise-guys dot nl; http://webagent.wise-guys.nl/)",
+ "Mozilla/4.0 (compatible; Advanced Email Extractor v2.xx)",
+ "Mozilla/4.0 (compatible; B_L_I_T_Z_B_O_T)",
+ "Mozilla/4.0 (compatible; ChristCrawler.com ChristCrawler@ChristCENTRAL.com)",
+ "Mozilla/4.0 (compatible; crawlx, crawler@trd.overture.com)",
+ "Mozilla/4.0 (compatible; DAUMOA-video; +http://ws.daum.net/aboutkr.html)",
+ "Mozilla/4.0 (compatible; FastCrawler3 support-fastcrawler3@fast.no)",
+ "Mozilla/4.0 (compatible; FDSE robot)",
+ "Mozilla/4.0 (compatible; GPU p2p crawler http://gpu.sourceforge.net/search_engine.php)",
+ "Mozilla/4.0 (compatible; grub-client-0.2.x; Crawl your stuff with http://grub.org)",
+ "Mozilla/4.0 (compatible; grub-client-0.3.x; Crawl your own stuff with http://grub.org)",
+ "Mozilla/4.0 (compatible; grub-client-2.x)",
+ "Mozilla/4.0 (compatible; Iplexx Spider/1.0 http://www.iplexx.at)",
+ "Mozilla/4.0 (compatible; MSIE 4.01; Vonna.com b o t)",
+ "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; PPC; 240x320; SPV M700; OpVer 19.123.2.733) OrangeBot-Mobile 2008.0 (mobilesearch.support@orange-ftgroup.com)",
+ "Mozilla/4.0 (compatible; MSIE 4.0; Windows NT; Site Server 3.0 Robot) Indonesia Interactive",
+ "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0) (samualt9@bigfoot.com)",
+ "Mozilla/4.0 (compatible; MSIE 5.0; NetNose-Crawler 2.0; A New Search Experience: http://www.netnose.com)",
+ "Mozilla/4.0 (compatible; MSIE 5.0; Windows 95) TrueRobot; 1.5",
+ "Mozilla/4.0 (compatible; MSIE 5.0; Windows 95) VoilaBot BETA 1.2 (http://www.voila.com/)",
+ "Mozilla/4.0 (compatible; MSIE 5.0; Windows 95) VoilaBot; 1.6",
+ "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt; DTS Agent",
+ "Mozilla/4.0 (compatible; MSIE 5.0; www.galaxy.com; www.psychedelix.com)",
+ "Mozilla/4.0 (compatible; MSIE 5.0; www.galaxy.com; www.psychedelix.com/; http://www.galaxy.com/info/crawler.html)",
+ "Mozilla/4.0 (compatible; MSIE 5.0; YANDEX)",
+ "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 4.0; obot)",
+ "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 4.0; QXW03018)",
+ "Mozilla/4.0 (compatible; MSIE 6.0 compatible; Asterias Crawler v4; +http://www.singingfish.com/help/spider.html; webmaster@singingfish.com); SpiderThread Revision: 3.10",
+ "Mozilla/4.0 (compatible; MSIE 6.0; MSIE 5.5; Windows NT 5.1) Skampy/0.9.x [en]",
+ "Mozilla/4.0 (compatible; MSIE 6.0; TargetSeek/1.0; +http://www.targetgroups.net/TargetSeek.html)",
+ "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; ODP entries t_st; http://tuezilla.de/t_st-odp-entries-agent.html)",
+ "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; ODP links test; http://tuezilla.de/test-odp-links-agent.html)",
+ "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; ZoomSpider.net bot; .NET CLR 1.1.4322)",
+ "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; heritrix/1.3.0 http://www.cs.washington.edu/research/networking/websys/)",
+ "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; QihooBot 1.0 qihoobot@qihoo.net)",
+ "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT; MS Search 4.0 Robot)",
+ "Mozilla/4.0 (compatible; MSIE enviable; DAUMOA 2.0; DAUM Web Robot; Daum Communications Corp., Korea; +http://ws.daum.net/aboutkr.html)",
+ "Mozilla/4.0 (compatible; MSIE is not me; DAUMOA/1.0.1; DAUM Web Robot; Daum Communications Corp., Korea)",
+ "Mozilla/4.0 (compatible; NaverBot/1.0; http://help.naver.com/delete_main.asp)",
+ "Mozilla/4.0 (compatible; SpeedySpider; www.entireweb.com)",
+ "Mozilla/4.0 (compatible; www.galaxy.com)",
+ "Mozilla/4.0 (compatible; Y!J; for robot study; keyoshid)",
+ "Mozilla/4.0 (compatible; Yahoo Japan; for robot study; kasugiya)",
+ "Mozilla/4.0 (JemmaTheTourist;http://www.activtourist.com)",
+ "Mozilla/4.0 (MobilePhone SCP-5500/US/1.0) NetFront/3.0 MMP/2.0 (compatible; Googlebot/2.1; http://www.google.com/bot.html)",
+ "Mozilla/4.0 (MobilePhone SCP-5500/US/1.0) NetFront/3.0 MMP/2.0 FAKE (compatible; Googlebot/2.1; http://www.google.com/bot.html)",
+ "Mozilla/4.0 (Mozilla; http://www.mozilla.org/docs/en/bot.html; master@mozilla.com)",
+ "Mozilla/4.0 (Sleek Spider/1.2)",
+ "Mozilla/4.0 compatible FurlBot/Furl Search 2.0 (FurlBot; http://www.furl.net; wn.furlbot@looksmart.net)",
+ "Mozilla/4.0 compatible ZyBorg/1.0 (wn.zyborg@looksmart.net; http://www.WISEnutbot.com)",
+ "Mozilla/4.0 compatible ZyBorg/1.0 (ZyBorg@WISEnutbot.com; http://www.WISEnutbot.com)",
+ "Mozilla/4.0 compatible ZyBorg/1.0 Dead Link Checker (wn.zyborg@looksmart.net; http://www.WISEnutbot.com)",
+ "Mozilla/4.0 compatible ZyBorg/1.0 for Homepage (ZyBorg@WISEnutbot.com; http://www.WISEnutbot.com)",
+ "Mozilla/4.0 efp@gmx.net",
+ "Mozilla/4.0 [en] (Ask Jeeves Corporate Spider)",
+ "Mozilla/4.0(compatible; Zealbot 1.0)",
+ "Mozilla/4.04 (compatible; Dulance bot; +http://www.dulance.com/bot.jsp)",
+ "Mozilla/4.0_(compatible;_MSIE_5.0;_Windows_95)_TrueRobot/1.4 libwww/5.2.8",
+ "Mozilla/4.0_(compatible;_MSIE_5.0;_Windows_95)_VoilaBot/1.6 libwww/5.3.2",
+ "Mozilla/4.6 [en] (http://www.cnet.com/)",
+ "Mozilla/4.7",
+ "Mozilla/4.7 (compatible; http://eidetica.com/spider)",
+ "Mozilla/4.7 (compatible; Intelliseek; http://www.intelliseek.com)",
+ "Mozilla/4.7 (compatible; Whizbang)",
+ "Mozilla/4.7 (compatible; WhizBang; http://www.whizbang.com/crawler)",
+ "Mozilla/4.7 [en](BecomeBot@exava.com)",
+ "Mozilla/4.7 [en](Exabot@exava.com)",
+ "Mozilla/4.72 [en] (BACS http://www.ba.be)",
+ "Mozilla/5.0",
+ "Mozilla/5.0 (+http://www.eurekster.com/mammoth) Mammoth/0.1",
+ "Mozilla/5.0 (+http://www.sli-systems.com/) Mammoth/0.1",
+ "Mozilla/5.0 (Clustered-Search-Bot/1.0; support@clush.com; http://www.clush.com/)",
+ "Mozilla/5.0 (compatible; +http://www.evri.com/evrinid)",
+ "Mozilla/5.0 (compatible; 008/0.83; http://www.80legs.com/spider.html;) Gecko/2008032620",
+ "Mozilla/5.0 (compatible; Abonti/0.8 - http://www.abonti.com)",
+ "Mozilla/5.0 (compatible; aiHitBot/1.0; +http://www.aihit.com/)",
+ "Mozilla/5.0 (compatible; AnsearchBot/1.x; +http://www.ansearch.com.au/)",
+ "Mozilla/5.0 (compatible; archive.org_bot/1.10.0 +http://www.loc.gov/minerva/crawl.html)",
+ "Mozilla/5.0 (compatible; archive.org_bot/1.13.1x http://crawler.archive.org)",
+ "Mozilla/5.0 (compatible; archive.org_bot/1.5.0-200506132127 http://crawler.archive.org) Hurricane Katrina",
+ "Mozilla/5.0 (compatible; Ask Jeeves/Teoma; http://about.ask.com/en/docs/about/webmasters.shtml)",
+ "Mozilla/5.0 (compatible; BecomeBot/1.23; http://www.become.com/webmasters.html)",
+ "Mozilla/5.0 (compatible; BecomeBot/1.xx; MSIE 6.0 compatible; http://www.become.com/webmasters.html)",
+ "Mozilla/5.0 (compatible; BecomeBot/2.0beta; http://www.become.com/webmasters.html)",
+ "Mozilla/5.0 (compatible; BecomeBot/2.x; MSIE 6.0 compatible; http://www.become.com/site_owners.html)",
+ "Mozilla/5.0 (compatible; BecomeJPBot/2.3; MSIE 6.0 compatible; +http://www.become.co.jp/site_owners.html)",
+ "Mozilla/5.0 (compatible; BlogRefsBot/0.1; http://www.blogrefs.com/about/bloggers)",
+ "Mozilla/5.0 (compatible; Bot; +http://pressemitteilung.ws/spamfilter",
+ "Mozilla/5.0 (compatible; BuzzRankingBot/1.0; +http://www.buzzrankingbot.com/)",
+ "Mozilla/5.0 (compatible; Charlotte/1.0b; charlotte@betaspider.com)",
+ "Mozilla/5.0 (compatible; Charlotte/1.0b; http://www.searchme.com/support/)",
+ "Mozilla/5.0 (compatible; Crawling jpeg; http://www.yama.info.waseda.ac.jp)",
+ "Mozilla/5.0 (compatible; de/1.13.2 +http://www.de.com)",
+ "Mozilla/5.0 (compatible; Diffbot/0.1; +http://www.diffbot.com)",
+ "Mozilla/5.0 (compatible; DNS-Digger-Explorer/1.0; +http://www.dnsdigger.com)",
+ "Mozilla/5.0 (compatible; DNS-Digger/1.0; +http://www.dnsdigger.com)",
+ "Mozilla/5.0 (compatible; EARTHCOM.info/2.01; http://www.earthcom.info)",
+ "Mozilla/5.0 (compatible; EARTHCOM/2.2; +http://enter4u.eu)",
+ "Mozilla/5.0 (compatible; Exabot Test/3.0; +http://www.exabot.com/go/robot)",
+ "Mozilla/5.0 (compatible; FatBot 2.0; http://www.thefind.com/main/CrawlerFAQs.fhtml)",
+ "Mozilla/5.0 (compatible; Galbot/1.0; +http://www.galbot.com/bot.html)",
+ "mozilla/5.0 (compatible; genevabot http://www.healthdash.com)",
+ "Mozilla/5.0 (compatible; Googlebot/2.1; http://www.google.com/bot.html)",
+ "mozilla/5.0 (compatible; heritrix/1.0.4 http://innovationblog.com)",
+ "Mozilla/5.0 (compatible; heritrix/1.10.2 +http://i.stanford.edu/)",
+ "Mozilla/5.0 (compatible; heritrix/1.12.1 +http://newstin.com/)",
+ "Mozilla/5.0 (compatible; heritrix/1.12.1 +http://www.page-store.com)",
+ "Mozilla/5.0 (compatible; heritrix/1.12.1 +http://www.page-store.com) [email:paul@page-store.com]",
+ "mozilla/5.0 (compatible; heritrix/1.3.0 http://archive.crawler.org)",
+ "Mozilla/5.0 (compatible; heritrix/1.4.0 +http://www.chepi.net)",
+ "Mozilla/5.0 (compatible; heritrix/1.4t http://www.truveo.com/)",
+ "Mozilla/5.0 (compatible; heritrix/1.5.0 http://www.l3s.de/~kohlschuetter/projects/crawling/)",
+ "Mozilla/5.0 (compatible; heritrix/1.5.0-200506231921 http://pandora.nla.gov.au/crawl.html)",
+ "Mozilla/5.0 (compatible; heritrix/1.6.0 http://www.worio.com/)",
+ "Mozilla/5.0 (compatible; heritrix/1.7.0 +http://www.greaterera.com/)",
+ "Mozilla/5.0 (compatible; heritrix/1.x.x +http://www.accelobot.com)",
+ "Mozilla/5.0 (compatible; heritrix/2.0.0-RC1 +http://www.aol.com)",
+ "Mozilla/5.0 (compatible; Hermit Search. Com; +http://www.hermitsearch.com)",
+ "Mozilla/5.0 (compatible; HyperixScoop/1.3; +http://www.hyperix.com)",
+ "Mozilla/5.0 (compatible; IDBot/1.0; +http://www.id-search.org/bot.html)",
+ "Mozilla/5.0 (compatible; InterseekWeb/3.x)",
+ "Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.5 (like Gecko) (Exabot-Thumbnails)",
+ "Mozilla/5.0 (compatible; LemSpider 0.1)",
+ "Mozilla/5.0 (compatible; MojeekBot/2.0; http://www.mojeek.com/bot.html)",
+ "Mozilla/5.0 (compatible; MSIE 6.0; Podtech Network; crawler_admin@podtech.net)",
+ "Mozilla/5.0 (compatible; OnetSzukaj/5.0; http://szukaj.onet.pl)",
+ "Mozilla/5.0 (compatible; PalmeraBot; http://www.links24h.com/help/palmera) Version 0.001",
+ "Mozilla/5.0 (compatible; pogodak.ba/3.x)",
+ "Mozilla/5.0 (compatible; Pogodak.hr/3.1)",
+ "Mozilla/5.0 (compatible; PWeBot/3.1; http://www.programacionweb.net/robot.php)",
+ "Mozilla/5.0 (compatible; Quantcastbot/1.0; www.quantcast.com)",
+ "Mozilla/5.0 (compatible; ScoutJet; +http://www.scoutjet.com/)",
+ "Mozilla/5.0 (compatible; Scrubby/2.2; http://www.scrubtheweb.com/)",
+ "Mozilla/5.0 (compatible; ShunixBot/1.x.x +http://www.shunix.com/robot.htm)",
+ "Mozilla/5.0 (compatible; ShunixBot/1.x; http://www.shunix.com/bot.htm)",
+ "Mozilla/5.0 (compatible; SkreemRBot +http://skreemr.com)",
+ "Mozilla/5.0 (compatible; SummizeBot +http://www.summize.com)",
+ "Mozilla/5.0 (compatible; Synoobot/0.9; http://www.synoo.com/search/bot.html)",
+ "Mozilla/5.0 (compatible; Theophrastus/x.x; http://users.cs.cf.ac.uk/N.A.Smith/theophrastus.php)",
+ "Mozilla/5.0 (compatible; TridentSpider/3.1)",
+ "Mozilla/5.0 (compatible; Vagabondo/2.1; webcrawler at wise-guys dot nl; http://webagent.wise-guys.nl/)",
+ "Mozilla/5.0 (compatible; Webduniabot/1.0; +http://search.webdunia.com/bot.aspx)",
+ "Mozilla/5.0 (compatible; worio bot heritrix/1.10.0 +http://worio.com)",
+ "Mozilla/5.0 (compatible; WoW Lemmings Kathune/2.0;http://www.wowlemmings.com/kathune.html)",
+ "Mozilla/5.0 (compatible; Yahoo! DE Slurp; http://help.yahoo.com/help/us/ysearch/slurp)",
+ "Mozilla/5.0 (compatible; Yahoo! Slurp China; http://misc.yahoo.com.cn/help.html)",
+ "Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp)",
+ "Mozilla/5.0 (compatible; Yoono; http://www.yoono.com/)",
+ "Mozilla/5.0 (compatible; YoudaoBot/1.0; http://www.youdao.com/help/webmaster/spider/; )",
+ "Mozilla/5.0 (compatible; Zenbot/1.3; +http://zen.co.za/webmasters/)",
+ "Mozilla/5.0 (compatible; zermelo +http://www.powerset.com) [email:paul@page-store.com,crawl@powerset.com]",
+ "Mozilla/5.0 (compatible;archive.org_bot/1.7.1; collectionId=316; Archive-It; +http://www.archive-it.org)",
+ "Mozilla/5.0 (compatible;archive.org_bot/heritrix-1.9.0-200608171144 +http://pandora.nla.gov.au/crawl.html)",
+ "Mozilla/5.0 (compatible;MAINSEEK_BOT)",
+ "Mozilla/5.0 (Slurp/cat; slurp@inktomi.com; http://www.inktomi.com/slurp.html)",
+ "Mozilla/5.0 (Slurp/si; slurp@inktomi.com; http://www.inktomi.com/slurp.html)",
+ "Mozilla/5.0 (Twiceler-0.9 http://www.cuill.com/twiceler/robot.html)",
+ "Mozilla/5.0 (Version: xxxx Type:xx)",
+ "Mozilla/5.0 (wgao@genieknows.com)",
+ "Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.7.7) NimbleCrawler 1.11 obeys UserAgent NimbleCrawler For problems contact: crawler_at_dataalchemy.com",
+ "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.1) VoilaBot BETA 1.2 (support.voilabot@orange-ftgroup.com)",
+ "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.1) VoilaBot BETA 1.2 (support.voilabot@orange-ftgroup.com)",
+ "Mozilla/5.0 (Windows;) NimbleCrawler 1.12 obeys UserAgent NimbleCrawler For problems contact: crawler@health",
+ "Mozilla/5.0 (Windows;) NimbleCrawler 1.12 obeys UserAgent NimbleCrawler For problems contact: crawler@healthline.com",
+ "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.2.1; aggregator:Spinn3r (Spinn3r 3.1); http://spinn3r.com/robot) Gecko/20021130",
+ "Mozilla/5.0 URL-Spider",
+ "Mozilla/5.0 usww.com-Spider-for-w8.net",
+ "Mozilla/5.0 wgao@genieknows.com",
+ "Mozilla/5.0 [en] (compatible; Gulper Web Bot 0.2.4 www.ecsl.cs.sunysb.edu/~maxim/cgi-bin/Link/GulperBot)",
+ "MQbot metaquerier.cs.uiuc.edu/crawler",
+ "MQBOT/Nutch-0.9-dev (MQBOT Nutch Crawler; http://falcon.cs.uiuc.edu; mqbot@cs.uiuc.edu)",
+ "msnbot-media/1.0 (+http://search.msn.com/msnbot.htm)",
+ "msnbot-Products/1.0 (+http://search.msn.com/msnbot.htm)",
+ "MSNBOT/0.xx (http://search.msn.com/msnbot.htm)",
+ "msnbot/x.xx ( http://search.msn.com/msnbot.htm)",
+ "MSNBOT_Mobile MSMOBOT Mozilla/2.0 (compatible; MSIE 4.02; Windows CE; Default)",
+ "MSNPTC/1.0",
+ "MSRBOT (http://research.microsoft.com/research/sv/msrbot)",
+ "multicrawler ( http://sw.deri.org/2006/04/multicrawler/robots.html)",
+ "MultiText/0.1",
+ "MusicWalker2.0 ( http://www.somusical.com)",
+ "MVAClient",
+ "Mylinea.com Crawler 2.0",
+ "Naamah 1.0.1/Blogbot (http://blogbot.de/)",
+ "Naamah 1.0a/Blogbot (http://blogbot.de/)",
+ "NABOT/5.0",
+ "nabot_1.0",
+ "NameOfAgent (CMS Spider)",
+ "NASA Search 1.0",
+ "NationalDirectory-WebSpider/1.3",
+ "NationalDirectoryAddURL/1.0",
+ "NaverBot-1.0 (NHN Corp. / +82-2-3011-1954 / nhnbot@naver.com)",
+ "NaverBot_dloader/1.5",
+ "NavissoBot",
+ "NavissoBot/1.7 (+http://navisso.com/)",
+ "NCSA Beta 1 (http://vias.ncsa.uiuc.edu/viasarchivinginformation.html)",
+ "Nebullabot/2.2 (http://bot.nebulla.info)",
+ "NEC Research Agent -- compuman at research.nj.nec.com",
+ "Net-Seekr Bot/Net-Seekr Bot V1 (http://www.net-seekr.com)",
+ "NetinfoBot/1.0 (http://netinfo.bg/netinfobot.html)",
+ "NetLookout/2.24",
+ "Netluchs/0.8-dev ( ; http://www.netluchs.de/; ___don't___spam_me_@netluchs.de)",
+ "NetNoseCrawler/v1.0",
+ "Netprospector JavaCrawler",
+ "NetResearchServer(http://www.look.com)",
+ "NetResearchServer/x.x(loopimprovements.com/robot.html)",
+ "NetSeer/Nutch-0.9 (NetSeer Crawler; http://www.netseer.com; crawler@netseer.com)",
+ "NetSprint -- 2.0",
+ "NetWhatCrawler/0.06-dev (NetWhatCrawler from NetWhat.com; http://www.netwhat.com; support@netwhat.com)",
+ "NetZippy",
+ "NextGenSearchBot 1 (for information visit http://www.eliyon.com/NextGenSearchBot)",
+ "NextopiaBOT (+http://www.nextopia.com) distributed crawler client beta v0.x",
+ "NG-Search/0.90 (NG-SearchBot; http://www.ng-search.com; )",
+ "NG/1.0",
+ "NG/4.0.1229",
+ "NITLE Blog Spider/0.01",
+ "Noago Spider",
+ "Nokia-WAPToolkit/1.2 googlebot(at)googlebot.com",
+ "Nokia6610/1.0 (3.09) Profile/MIDP-1.0 Configuration/CLDC-1.0 (compatible;YahooSeeker/M1A1-R2D2; http://help.yahoo.com/help/us/ysearch/crawling/crawling-01.html)",
+ "NokodoBot/1.x (+http://nokodo.com/bot.htm)",
+ "Norbert the Spider(Burf.com)",
+ "noxtrumbot/1.0 (crawler@noxtrum.com)",
+ "noyona_0_1",
+ "NP/0.1 (NP; http://www.nameprotect.com; npbot@nameprotect.com)",
+ "NPBot (http://www.nameprotect.com/botinfo.html)",
+ "NPBot-1/2.0",
+ "Nsauditor/1.x",
+ "nsyght.com/Nutch-1.0-dev (nsyght.com; Nsyght.com)",
+ "nsyght.com/Nutch-x.x (nsyght.com; search.nsyght.com)",
+ "nttdirectory_robot/0.9 (super-robot@super.navi.ocn.ne.jp)",
+ "nuSearch Spider www.nusearch.com (compatible; MSIE 4.01)",
+ "NuSearch Spider (compatible; MSIE 6.0)",
+ "NuSearch Spider www.nusearch.com",
+ "Nutch",
+ "Nutch crawler/Nutch-0.9 (picapage.com; admin@picapage.com)",
+ "Nutch/Nutch-0.9 (Eurobot; http://www.ayell.eu )",
+ "NutchCVS/0.0x-dev (Nutch; http://www.nutch.org/docs/bot.html; nutch-agent@lists.sourceforge.net)",
+ "NutchCVS/0.7.1 (Nutch running at UW; http://www.nutch.org/docs/en/bot.html; sycrawl@cs.washington.edu)",
+ "NutchEC2Test/Nutch-0.9-dev (Testing Nutch on Amazon EC2.; http://lucene.apache.org/nutch/bot.html; ec2test at lucene.com)",
+ "NutchOrg/0.0x-dev (Nutch; http://www.nutch.org/docs/bot.html; nutch-agent@lists.sourceforge.net)",
+ "nutchsearch/Nutch-0.9 (Nutch Search 1.0; herceg_novi at yahoo dot com)",
+ "NutchVinegarCrawl/Nutch-0.8.1 (Vinegar; http://www.cs.washington.edu; eytanadar at gmail dot com)",
+ "obidos-bot (just looking for books.)",
+ "ObjectsSearch/0.01-dev (ObjectsSearch;http://www.ObjectsSearch.com/bot.html; support@thesoftwareobjects.com)",
+ "ObjectsSearch/0.0x (ObjectsSearch; http://www.ObjectsSearch.com/bot.html; support@thesoftwareobjects.com)",
+ "oBot ((compatible;Win32))",
+ "Ocelli/1.x (http://www.globalspec.com/Ocelli)",
+ "Octora Beta - www.octora.com",
+ "Octora Beta Bot - www.octora.com",
+ "OmniExplorer_Bot/1.0x (+http://www.omni-explorer.com) Internet CategorizerOmniExplorer http://www.omni-explorer.com/ car & shopping search (64.62.175.xxx)",
+ "OmniExplorer_Bot/1.0x (+http://www.omni-explorer.com) Job Crawler",
+ "OmniExplorer_Bot/1.1x (+http://www.omni-explorer.com) Torrent Crawler",
+ "OmniExplorer_Bot/x.xx (+http://www.omni-explorer.com) WorldIndexer",
+ "Onet.pl SA- http://szukaj.onet.pl",
+ "OntoSpider/1.0 libwww-perl/5.65",
+ "OOZBOT/0.20 ( http://www.setooz.com/oozbot.html ; agentname at setooz dot_com )",
+ "OpenAcoon v4.0.x (www.openacoon.de)",
+ "Openbot/3.0+(robot-response@openfind.com.tw;+http://www.openfind.com.tw/robot.html)",
+ "Openfind data gatherer- Openbot/3.0+(robot-response@openfind.com.tw;+http://www.openfind.com.tw/robot.html)",
+ "Openfind Robot/1.1A2",
+ "OpenISearch/1.x (www.openisearch.com)",
+ "OpenTaggerBot (http://www.opentagger.com/opentaggerbot.htm)",
+ "OpenTextSiteCrawler/2.9.2",
+ "OpenWebSpider/0.x.x (http://www.openwebspider.org)",
+ "OpenWebSpider/x",
+ "OpidooBOT (larbin2.6.3@unspecified.mail)",
+ "Oracle Ultra Search",
+ "OrangeSpider",
+ "Orbiter/T-2.0 (+http://www.dailyorbit.com/bot.htm)",
+ "Overture-WebCrawler/3.8/Fresh (atw-crawler at fast dot no; http://fast.no/support/crawler.asp)",
+ "ozelot/2.7.3 (Search engine indexer; www.flying-cat.de/ozelot; ozelot@flying-cat.de)",
+ "PADLibrary Spider",
+ "PageBitesHyperBot/600 (http://www.pagebites.com/)",
+ "Pagebull http://www.pagebull.com/",
+ "page_verifier (http://www.securecomputing.com/goto/pv)",
+ "parallelContextFocusCrawler1.1parallelContextFocusCrawler1.1",
+ "ParaSite/1.0b (http://www.ianett.com/parasite/)",
+ "Patwebbot (http://www.herz-power.de/technik.html)",
+ "PBrowse 1.4b",
+ "pd02_1.0.0 pd02_1.0.0@dzimi@post.sk",
+ "PEERbot www.peerbot.com",
+ "PEval 1.4b",
+ "PicoSearch/1.0",
+ "Piffany_Web_Scraper_v0.x",
+ "Piffany_Web_Spider_v0.x",
+ "pipeLiner/0.3a (PipeLine Spider;http://www.pipeline-search.com/webmaster.html; webmaster'at'pipeline-search.com)",
+ "pipeLiner/0.xx (PipeLine Spider; http://www.pipeline-search.com/webmaster.html)",
+ "Pita",
+ "PJspider/3.0 (pjspider@portaljuice.com; http://www.portaljuice.com)",
+ "PlagiarBot/1.0",
+ "PluckFeedCrawler/2.0 (compatible; Mozilla 4.0; MSIE 5.5; http://www.pluck.com; 1 subscribers)",
+ "Pluggd/Nutch-0.9 (automated crawler http://www.pluggd.com;support at pluggd dot com)",
+ "Poirot",
+ "polybot 1.0 (http://cis.poly.edu/polybot/)",
+ "Pompos/1.x http://dir.com/pompos.html",
+ "Pompos/1.x pompos@iliad.fr",
+ "Popdexter/1.0",
+ "Port Huron Labs",
+ "PortalBSpider/2.0 (spider@portalb.com)",
+ "potbot 1.0",
+ "PRCrawler/Nutch-0.9 (data mining development project; crawler@projectrialto.com)",
+ "PrivacyFinder Cache Bot v1.0",
+ "PrivacyFinder/1.1",
+ "Production Bot 0116B",
+ "Production Bot 2016B",
+ "Production Bot DOT 3016B",
+ "Program Shareware 1.0.2",
+ "Project XP5 [2.03.07-111203]",
+ "PROve AnswerBot 4.0",
+ "ProWebGuide Link Checker (http://www.prowebguide.com)",
+ "psbot/0.1 (+http://www.picsearch.com/bot.html)",
+ "PSurf15a 11",
+ "PSurf15a 51",
+ "PSurf15a VA",
+ "psycheclone",
+ "PubCrawl (pubcrawl.stanford.edu)",
+ "pulseBot (pulse Web Miner)",
+ "PWeBot/1.2 Inspector (http://www.programacionweb.net/robot.php)",
+ "PycURL",
+ "Python-urllib/1.1x",
+ "Python-urllib/2.0a1",
+ "Qango.com Web Directory (http://www.qango.com/)",
+ "QEAVis Agent/Nutch-0.9 (Quantitative Evaluation of Academic Websites Visibility; http://nlp.uned.es/qeavis",
+ "QPCreep Test Rig ( We are not indexing- just testing )",
+ "QuepasaCreep ( crawler@quepasacorp.com )",
+ "QuepasaCreep v0.9.1x",
+ "QueryN Metasearch",
+ "QweeryBot/3.01 ( http://qweerybot.qweery.nl)",
+ "Qweery_robot.txt_CheckBot/3.01 (http://qweerybot.qweery.com)",
+ "R6_CommentReader_(www.radian6.com/crawler)",
+ "R6_FeedFetcher_(www.radian6.com/crawler)",
+ "rabaz (rabaz at gigabaz dot com)",
+ "RaBot/1.0 Agent-admin/phortse@hanmail.net",
+ "ramBot xtreme x.x",
+ "RAMPyBot - www.giveRAMP.com/0.1 (RAMPyBot - www.giveRAMP.com; http://www.giveramp.com/bot.html; support@giveRAMP.com)",
+ "RAMPyBot/0.8-dev (Nutch; http://lucene.apache.org/nutch/bot.html; nutch-agent@lucene.apache.org)",
+ "Rankivabot/3.2 (www.rankiva.com; 3.2; vzmxikn)",
+ "Rational SiteCheck (Windows NT)",
+ "Reaper [2.03.10-031204] (http://www.sitesearch.ca/reaper/)",
+ "Reaper/2.0x (+http://www.sitesearch.ca/reaper)",
+ "RedCarpet/1.2 (http://www.redcarpet-inc.com/robots.html)",
+ "RedCell/0.1 (InfoSec Search Bot (Coming Soon); http://www.telegenetic.net/bot.html; lhall@telegenetic.net)",
+ "RedCell/0.1 (RedCell; telegenetic.net/bot.html; lhall_at_telegenetic.net)",
+ "RedKernel WWW-Spider 2/0 (+http://www-spider.redkernel-softwares.com/)",
+ "rico/0.1",
+ "RixBot (http://babelserver.org/rix)",
+ "RoboCrawl (http://www.canadiancontent.net)",
+ "RoboCrawl (www.canadiancontent.net)",
+ "RoboPal (http://www.findpal.com/)",
+ "Robot/www.pj-search.com",
+ "Robot: NutchCrawler- Owner: wdavies@acm.org",
+ "Robot@SuperSnooper.Com",
+ "Robozilla/1.0",
+ "Rotondo/3.1 libwww/5.3.1",
+ "RRC (crawler_admin@bigfoot.com)",
+ "RSSMicro.com RSS/Atom Feed Robot",
+ "RSurf15a 41",
+ "RSurf15a 51",
+ "RSurf15a 81",
+ "RufusBot (Rufus Web Miner; http://64.124.122.252/feedback.html)",
+ "RufusBot (Rufus Web Miner; http://www.webaroo.com/rooSiteOwners.html)",
+ "sait/Nutch-0.9 (SAIT Research; http://www.samsung.com)",
+ "SandCrawler - Compatibility Testing",
+ "SapphireWebCrawler/1.0 (Sapphire Web Crawler using Nutch; http://boston.lti.cs.cmu.edu/crawler/; mhoy@cs.cmu.edu)",
+ "SapphireWebCrawler/Nutch-1.0-dev (Sapphire Web Crawler using Nutch; http://boston.lti.cs.cmu.edu/crawler/; mhoy@cs.cmu.edu)",
+ "savvybot/0.2",
+ "SBIder/0.7 (SBIder; http://www.sitesell.com/sbider.html; http://support.sitesell.com/contact-support.html)",
+ "SBIder/0.8-dev (SBIder; http://www.sitesell.com/sbider.html; http://support.sitesell.com/contact-support.html)",
+ "ScanWeb",
+ "ScholarUniverse/0.8 (Nutch;+http://scholaruniverse.com/bot.jsp; fetch-agent@scholaruniverse.com)",
+ "schwarzmann.biz-Spider_for_paddel.org+(http://www.innerprise.net/usp-spider.asp)",
+ "ScollSpider/2.0 (+http://www.webwobot.com/ScollSpider.php)",
+ "Scooter-3.0.EU",
+ "Scooter-3.0.FS",
+ "Scooter-3.0.HD",
+ "Scooter-3.0.VNS",
+ "Scooter-3.0QI",
+ "Scooter-3.2",
+ "Scooter-3.2.BT",
+ "Scooter-3.2.DIL",
+ "Scooter-3.2.EX",
+ "Scooter-3.2.JT",
+ "Scooter-3.2.NIV",
+ "Scooter-3.2.SF0",
+ "Scooter-3.2.snippet",
+ "Scooter-3.3dev",
+ "Scooter-ARS-1.1",
+ "Scooter-ARS-1.1-ih",
+ "scooter-venus-3.0.vns",
+ "Scooter-W3-1.0",
+ "Scooter-W3.1.2",
+ "Scooter/1.0",
+ "Scooter/1.0 scooter@pa.dec.com",
+ "Scooter/1.1 (custom)",
+ "Scooter/2.0 G.R.A.B. V1.1.0",
+ "Scooter/2.0 G.R.A.B. X2.0",
+ "Scooter/3.3",
+ "Scooter/3.3.QA.pczukor",
+ "Scooter/3.3.vscooter",
+ "Scooter/3.3_SF",
+ "Scooter2_Mercator_x-x.0",
+ "Scooter_bh0-3.0.3",
+ "Scooter_trk3-3.0.3",
+ "ScoutAbout",
+ "ScoutAnt/0.1; +http://www.ant.com/what_is_ant.com/",
+ "scoutmaster",
+ "Scrubby/2.x (http://www.scrubtheweb.com/)",
+ "Scrubby/3.0 (+http://www.scrubtheweb.com/help/technology.html)",
+ "Search+",
+ "Search-Engine-Studio",
+ "search.ch V1.4",
+ "search.ch V1.4.2 (spiderman@search.ch; http://www.search.ch)",
+ "Search/1.0 (http://www.innerprise.net/es-spider.asp)",
+ "searchbot admin@google.com",
+ "SearchByUsa/2 (SearchByUsa; http://www.SearchByUsa.com/bot.html; info@SearchByUsa.com)",
+ "SearchdayBot",
+ "SearchExpress Spider0.99",
+ "SearchGuild/DMOZ/Experiment (searchguild@gmail.com)",
+ "SearchGuild_DMOZ_Experiment (chris@searchguild.com)",
+ "Searchit-Now Robot/2.2 (+http://www.searchit-now.co.uk)",
+ "Searchmee! Spider v0.98a",
+ "SearchSight/2.0 (http://SearchSight.com/)",
+ "SearchSpider.com/1.1",
+ "Searchspider/1.2 (SearchSpider; http://www.searchspider.com; webmaster@searchspider.com)",
+ "SearchTone2.0 - IDEARE",
+ "Seekbot/1.0 (http://www.seekbot.net/bot.html) HTTPFetcher/0.3",
+ "Seekbot/1.0 (http://www.seekbot.net/bot.html) RobotsTxtFetcher/1.0 (XDF)",
+ "Seekbot/1.0 (http://www.seekbot.net/bot.html) RobotsTxtFetcher/1.2",
+ "Seeker.lookseek.com",
+ "Semager/1.1 (http://www.semager.de/blog/semager-bots/)",
+ "Semager/1.x (http://www.semager.de)",
+ "Sensis Web Crawler (search_comments\\at\\sensis\\dot\\com\\dot\\au)",
+ "Sensis.com.au Web Crawler (search_comments\\at\\sensis\\dot\\com\\dot\\au)",
+ "SeznamBot/1.0",
+ "SeznamBot/1.0 (+http://fulltext.seznam.cz/)",
+ "SeznamBot/2.0-test (+http://fulltext.sblog.cz/)",
+ "ShablastBot 1.0",
+ "Shim Crawler",
+ "Shim-Crawler(Mozilla-compatible; http://www.logos.ic.i.u-tokyo.ac.jp/crawler/; crawl@logos.ic.i.u-tokyo.ac.jp)",
+ "ShopWiki/1.0 ( +http://www.shopwiki.com/)",
+ "ShopWiki/1.0 ( +http://www.shopwiki.com/wiki/Help:Bot)",
+ "Shoula.com Crawler 2.0",
+ "SietsCrawler/1.1 (+http://www.siets.biz)",
+ "Sigram/Nutch-1.0-dev (Test agent for Nutch development; http://www.sigram.com/bot.html; bot at sigram dot com)",
+ "Siigle Orumcex v.001 Turkey (http://www.siigle.com)",
+ "silk/1.0",
+ "silk/1.0 (+http://www.slider.com/silk.htm)/3.7",
+ "Sirketcebot/v.01 (http://www.sirketce.com/bot.html)",
+ "SiteSpider +(http://www.SiteSpider.com/)",
+ "SiteTruth.com site rating system",
+ "SiteXpert",
+ "Skampy/0.9.x (http://www.skaffe.com/skampy-info.html)",
+ "Skimpy/0.x (http://www.skaffe.com/skampy-info.html)",
+ "Skywalker/0.1 (Skywalker; anonymous; anonymous)",
+ "Slarp/0.1",
+ "Slider_Search_v1-de",
+ "Slurp/2.0 (slurp@inktomi.com; http://www.inktomi.com/slurp.html)",
+ "Slurp/2.0-KiteWeekly (slurp@inktomi.com; http://www.inktomi.com/slurp.html)",
+ "Slurp/si (slurp@inktomi.com; http://www.inktomi.com/slurp.html)",
+ "Slurpy Verifier/1.0",
+ "SlySearch (slysearch@slysearch.com)",
+ "SlySearch/1.0 http://www.plagiarism.org/crawler/robotinfo.html",
+ "SlySearch/1.x http://www.slysearch.com",
+ "smartwit.com",
+ "SmiffyDCMetaSpider/1.0",
+ "snap.com beta crawler v0",
+ "Snapbot/1.0",
+ "Snapbot/1.0 (Snap Shots, +http://www.snap.com)",
+ "SnykeBot/0.6 (http://www.snyke.com)",
+ "SocSciBot ()",
+ "SoftHypermarketFileCheckBot/1.0+(+http://www.softhypermaket.com)",
+ "sogou develop spider",
+ "Sogou Orion spider/3.0(+http://www.sogou.com/docs/help/webmasters.htm#07)",
+ "sogou spider",
+ "Sogou web spider/3.0(+http://www.sogou.com/docs/help/webmasters.htm#07)",
+ "sohu agent",
+ "sohu-search",
+ "Sosospider+(+http://help.soso.com/webspider.htm)",
+ "speedfind ramBot xtreme 8.1",
+ "Speedy Spider (Beta/x.x; speedy@entireweb.com)",
+ "Speedy Spider (Entireweb; Beta/1.0; http://www.entireweb.com/about/search_tech/speedyspider/)",
+ "Speedy_Spider (http://www.entireweb.com)",
+ "Sphere Scout&v4.0 - scout at sphere dot com",
+ "Sphider",
+ "Spida/0.1",
+ "Spider-Sleek/2.0 (+http://search-info.com/linktous.html)",
+ "spider.batsch.com",
+ "spider.yellopet.com - www.yellopet.com",
+ "Spider/maxbot.com admin@maxbot.com",
+ "SpiderKU/0.x",
+ "SpiderMan",
+ "SpiderMonkey/7.0x (SpiderMonkey.ca info at http://spidermonkey.ca/sm.shtml)",
+ "Spinne/2.0",
+ "Spinne/2.0 med",
+ "Spinne/2.0 med_AH",
+ "Spock Crawler (http://www.spock.com/crawler)",
+ "sportsuchmaschine.de-Robot (Version: 1.02- powered by www.sportsuchmaschine.de)",
+ "sproose/0.1-alpha (sproose crawler; http://www.sproose.com/bot.html; crawler@sproose.com)",
+ "Sqworm/2.9.81-BETA (beta_release; 20011102-760; i686-pc-linux-gnu)",
+ "Sqworm/2.9.85-BETA (beta_release; 20011115-775; i686-pc-linux-gnu)",
+ "SSurf15a 11 ",
+ "StackRambler/x.x ",
+ "stat statcrawler@gmail.com",
+ "Steeler/1.x (http://www.tkl.iis.u-tokyo.ac.jp/~crawler/)",
+ "Steeler/3.3 (http://www.tkl.iis.u-tokyo.ac.jp/~crawler/)",
+ "Strategic Board Bot (+http://www.strategicboard.com)",
+ "Strategic Board Bot (+http://www.strategicboard.com)",
+ "Submission Spider at surfsafely.com",
+ "suchbaer.de",
+ "suchbaer.de (CrawlerAgent v0.103)",
+ "suchbot",
+ "Suchknecht.at-Robot",
+ "suchpadbot/1.0 (+http://www.suchpad.de)",
+ "SurferF3 1/0",
+ "suzuran",
+ "Swooglebot/2.0. (+http://swoogle.umbc.edu/swooglebot.htm)",
+ "SWSBot-Images/1.2 http://www.smartwaresoft.com/swsbot12.html",
+ "SygolBot http://www.sygol.net",
+ "SynoBot",
+ "Syntryx ANT Scout Chassis Pheromone; Mozilla/4.0 compatible crawler",
+ "Szukacz/1.x",
+ "Szukacz/1.x (robot; www.szukacz.pl/jakdzialarobot.html; szukacz@proszynski.pl)",
+ "tags2dir.com/0.8 (+http://tags2dir.com/directory/)",
+ "Tagword (http://tagword.com/dmoz_survey.php)",
+ "Talkro Web-Shot/1.0 (E-mail: webshot@daumsoft.com- Home: http://222.122.15.190/webshot)",
+ "TCDBOT/Nutch-0.8 (PhD student research;http://www.tcd.ie; mcgettrs at t c d dot IE)",
+ "TECOMAC-Crawler/0.x",
+ "Tecomi Bot (http://www.tecomi.com/bot.htm)",
+ "Teemer (NetSeer, Inc. is a Los Angeles based Internet startup company.; http://www.netseer.com/crawler.html; crawler@netseer.com)",
+ "Teoma MP",
+ "teomaagent crawler-admin@teoma.com",
+ "teomaagent1 [crawler-admin@teoma.com]",
+ "teoma_agent1",
+ "Teradex Mapper; mapper@teradex.com; http://www.teradex.com",
+ "terraminds-bot/1.0 (support@terraminds.de)",
+ "TerrawizBot/1.0 (+http://www.terrawiz.com/bot.html)",
+ "Test spider",
+ "TestCrawler/Nutch-0.9 (Testing Crawler for Research ; http://balihoo.com/index.aspx; tgautier at balihoo dot com)",
+ "TheRarestParser/0.2a (http://therarestwords.com/)",
+ "TheSuBot/0.1 (www.thesubot.de)",
+ "thumbshots-de-Bot (Version: 1.02- powered by www.thumbshots.de)",
+ "timboBot/0.9 http://www.breakingblogs.com/timbo_bot.html",
+ "TinEye/1.1 (http://tineye.com/crawler.html)",
+ "tivraSpider/1.0 (crawler@tivra.com)",
+ "TJG/Spider",
+ "Tkensaku/x.x(http://www.tkensaku.com/q.html)",
+ "Topodia/1.2-dev (Topodia - Crawler for HTTP content indexing; http://www.topodia.com/; support@topodia.com)",
+ "Toutatis x-xx.x (hoppa.com)",
+ "Toutatis x.x (hoppa.com)",
+ "Toutatis x.x-x",
+ "traazibot/testengine (+http://www.traazi.de)",
+ "Trampelpfad-Spider",
+ "Trampelpfad-Spider-v0.1",
+ "TSurf15a 11",
+ "Tumblr/1.0 RSS syndication (+http://www.tumblr.com/) (support@tumblr.com)",
+ "TurnitinBot/x.x (http://www.turnitin.com/robot/crawlerinfo.html)",
+ "Turnpike Emporium LinkChecker/0.1",
+ "TutorGig/1.5 (+http://www.tutorgig.com/crawler)",
+ "Tutorial Crawler 1.4 (http://www.tutorgig.com/crawler)",
+ "Twiceler www.cuill.com/robots.html",
+ "Twiceler-0.9 http://www.cuill.com/twiceler/robot.html",
+ "Tycoon Agent/Nutch-1.0-dev",
+ "TygoBot",
+ "TygoProwler",
+ "UIowaCrawler/1.0",
+ "UKWizz/Nutch-0.8.1 (UKWizz Nutch crawler; http://www.ukwizz.com/)",
+ "Ultraseek",
+ "Under the Rainbow 2.2",
+ "UofTDB_experiment (leehyun@cs.toronto.edu)",
+ "updated/0.1-alpha (updated crawler; http://www.updated.com; crawler@updated.com)",
+ "updated/0.1beta (updated.com; http://www.updated.com; crawler@updated.om)",
+ "Uptimebot",
+ "UptimeBot(www.uptimebot.com)",
+ "URL Spider Pro/x.xx (innerprise.net)",
+ "urlfan-bot/1.0; +http://www.urlfan.com/site/bot/350.html",
+ "URL_Spider_Pro/x.x",
+ "URL_Spider_Pro/x.x+(http://www.innerprise.net/usp-spider.asp)",
+ "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)",
+ "User-Agent: Mozilla/4.0 (SKIZZLE! Distributed Internet Spider v1.0 - www.SKIZZLE.com)",
+ "USyd-NLP-Spider (http://www.it.usyd.edu.au/~vinci/bot.html)",
+ "VadixBot",
+ "Vagabondo-WAP/2.0 (webcrawler at wise-guys dot nl; http://webagent.wise-guys.nl/)/1.0 Profile",
+ "Vagabondo/1.x MT (webagent@wise-guys.nl)",
+ "Vagabondo/2.0 MT",
+ "Vagabondo/2.0 MT (webagent at wise-guys dot nl)",
+ "Vagabondo/2.0 MT (webagent@NOSPAMwise-guys.nl)",
+ "Vagabondo/3.0 (webagent at wise-guys dot nl)",
+ "Vakes/0.01 (Vakes; http://www.vakes.com/; search@vakes.com)",
+ "versus 0.2 (+http://versus.integis.ch)",
+ "versus crawler eda.baykan@epfl.ch",
+ "VeryGoodSearch.com.DaddyLongLegs",
+ "verzamelgids.nl - Networking4all Bot/x.x",
+ "Verzamelgids/2.2 (http://www.verzamelgids.nl)",
+ "Vespa Crawler",
+ "VisBot/2.0 (Visvo.com Crawler; http://www.visvo.com/bot.html; bot@visvo.com)",
+ "Vision Research Lab image spider at vision.ece.ucsb.edu",
+ "VMBot/0.x.x (VMBot; http://www.VerticalMatch.com/; vmbot@tradedot.com)",
+ "Vortex/2.2 (+http://marty.anstey.ca/robots/vortex/)",
+ "voyager-hc/1.0",
+ "voyager/1.0",
+ "voyager/2.0 (http://www.kosmix.com/html/crawler.html)",
+ "VSE/1.0 (testcrawler@hotmail.com)",
+ "VSE/1.0 (testcrawler@vivisimo.com)",
+ "vspider",
+ "vspider/3.x",
+ "VWBOT/Nutch-0.9-dev (VWBOT Nutch Crawler; http://vwbot.cs.uiuc.edu;+vwbot@cs.uiuc.edu",
+ "W3SiteSearch Crawler_v1.1 http://www.w3sitesearch.de",
+ "wadaino.jp-crawler 0.2 (http://wadaino.jp/)",
+ "Wavefire/0.8-dev (Wavefire; http://www.wavefire.com; info@wavefire.com)",
+ "Waypath development crawler - info at waypath dot com",
+ "Waypath Scout v2.x - info at waypath dot com",
+ "Web Snooper",
+ "web2express.org/Nutch-0.9-dev (leveled playing field; http://web2express.org/; info at web2express.org)",
+ "WebAlta Crawler/1.2.1 (http://www.webalta.ru/bot.html)",
+ "WebarooBot (Webaroo Bot; http://64.124.122.252/feedback.html)",
+ "WebarooBot (Webaroo Bot; http://www.webaroo.com/rooSiteOwners.html)",
+ "webbandit/4.xx.0",
+ "Webclipping.com",
+ "WebCompass 2.0",
+ "WebCorp/1.0",
+ "webcrawl.net",
+ "WebFindBot(http://www.web-find.com)",
+ "Webglimpse 2.xx.x (http://webglimpse.net)",
+ "Weblog Attitude Diffusion 1.0",
+ "webmeasurement-bot, http://rvs.informatik.uni-leipzig.de",
+ "WebRankSpider/1.37 (+http://ulm191.server4you.de/crawler/)",
+ "WebSearch.COM.AU/3.0.1 (The Australian Search Engine; http://WebSearch.COM.AU; Search@WebSearch.COM.AU)",
+ "WebSearchBench WebCrawler v0.1(Experimental)",
+ "WebsiteWorth v1.0",
+ "Webspinne/1.0 webmaster@webspinne.de",
+ "Websquash.com (Add url robot)",
+ "WebStat/1.0 (Unix; beta; 20040314)",
+ "Webster v0.3 ( http://webster.healeys.net/ )",
+ "WebVac (webmaster@pita.stanford.edu)",
+ "Webverzeichnis.de - Telefon: 01908 / 26005",
+ "WebVulnCrawl.unknown/1.0 libwww-perl/5.803",
+ "Wells Search II",
+ "WEP Search 00",
+ "WFARC",
+ "whatUseek_winona/3.0",
+ "WhizBang! Lab",
+ "Willow Internet Crawler by Twotrees V2.1",
+ "WinHTTP Example/1.0",
+ "WinkBot/0.06 (Wink.com search engine web crawler; http://www.wink.com/Wink:WinkBot; winkbot@wink.com)",
+ "WIRE/0.11 (Linux; i686; Bot,Robot,Spider,Crawler,aromano@cli.di.unipi.it)",
+ "WIRE/0.x (Linux; i686; Bot,Robot,Spider,Crawler)",
+ "WISEbot/1.0 (WISEbot@koreawisenut.com; http://wisebot.koreawisenut.com)",
+ "worio heritrix bot (+http://worio.com/)",
+ "woriobot ( http://www.worio.com/)",
+ "WorldLight",
+ "Wotbox/alpha0.6 (bot@wotbox.com; http://www.wotbox.com)",
+ "Wotbox/alpha0.x.x (bot@wotbox.com; http://www.wotbox.com) Java/1.4.1_02",
+ "WSB WebCrawler V1.0 (Beta)- cl@cs.uni-dortmund.de",
+ "WSB, http://websearchbench.cs.uni-dortmund.de",
+ "wume_crawler/1.1 (http://wume.cse.lehigh.edu/~xiq204/crawler/)",
+ "Wwlib/Linux",
+ "www.arianna.it",
+ "WWWeasel Robot v1.00 (http://wwweasel.de)",
+ "wwwster/1.x (Beta- mailto:gue@cis.uni-muenchen.de)",
+ "X-Crawler ",
+ "xirq/0.1-beta (xirq; http://www.xirq.com; xirq@xirq.com)",
+ "xyro_(xcrawler@cosmos.inria.fr)",
+ "Y!J-BSC/1.0 (http://help.yahoo.co.jp/help/jp/search/indexing/indexing-15.html)",
+ "Y!J-SRD/1.0",
+ "Y!J/1.0 (http://help.yahoo.co.jp/help/jp/search/indexing/indexing-15.html)",
+ "yacy (www.yacy.net; v20040602; i386 Linux 2.4.26-gentoo-r13; java 1.4.2_06; MET/en)",
+ "yacybot (x86 Windows XP 5.1; java 1.5.0_06; Europe/de) yacy.net",
+ "Yahoo Pipes 1.0",
+ "Yahoo! Mindset",
+ "Yahoo-Blogs/v3.9 (compatible; Mozilla 4.0; MSIE 5.5; http://help.yahoo.com/help/us/ysearch/crawling/crawling-02.html )",
+ "Yahoo-MMAudVid/1.0 (mms dash mmaudvidcrawler dash support at yahoo dash inc dot com)",
+ "Yahoo-MMAudVid/2.0(mms dash mm aud vid crawler dash support at yahoo dash inc.com ;Mozilla 4.0 compatible; MSIE 7.0;Windows NT 5.0; .NET CLR 2.0)",
+ "Yahoo-MMCrawler/3.x (mm dash crawler at trd dot overture dot com)",
+ "Yahoo-Test/4.0",
+ "Yahoo-VerticalCrawler-FormerWebCrawler/3.9 crawler at trd dot overture dot com; http://www.alltheweb.com/help/webmaster/crawler",
+ "YahooFeedSeeker/2.0 (compatible; Mozilla 4.0; MSIE 5.5; http://publisher.yahoo.com/rssguide)",
+ "YahooSeeker-Testing/v3.9 (compatible; Mozilla 4.0; MSIE 5.5; http://search.yahoo.com/)",
+ "YahooSeeker/1.0 (compatible; Mozilla 4.0; MSIE 5.5; http://help.yahoo.com/help/us/shop/merchant/)",
+ "YahooSeeker/1.0 (compatible; Mozilla 4.0; MSIE 5.5; http://search.yahoo.com/yahooseeker.html)",
+ "YahooSeeker/1.1 (compatible; Mozilla 4.0; MSIE 5.5; http://help.yahoo.com/help/us/shop/merchant/)",
+ "YahooSeeker/bsv3.9 (compatible; Mozilla 4.0; MSIE 5.5; http://help.yahoo.com/help/us/ysearch/crawling/crawling-02.html )",
+ "YahooSeeker/CafeKelsa-dev (compatible; Konqueror/3.2; FreeBSD ;cafekelsa-dev-webmaster@yahoo-inc.com )",
+ "Yandex/1.01.001 (compatible; Win16; I)",
+ "Yanga WorldSearch Bot v1.1/beta (http://www.yanga.co.uk/)",
+ "yarienavoir.net/0.2",
+ "Yeti",
+ "Yeti/0.01 (nhn/1noon, yetibot@naver.com, check robots.txt daily and follows it)",
+ "Yeti/1.0 (NHN Corp.; http://help.naver.com/robots/)",
+ "yggdrasil/Nutch-0.9 (yggdrasil biorelated search engine; www dot biotec dot tu minus dresden do de slash schroeder; heiko dot dietze at biotec dot tu minus dresden dot de)",
+ "YodaoBot/1.0 (http://www.yodao.com/help/webmaster/spider/; )",
+ "yoofind/yoofind-0.1-dev (yoono webcrawler; http://www.yoono.com ; MyEmail)",
+ "yoogliFetchAgent/0.1",
+ "yoono/1.0 web-crawler/1.0",
+ "YottaCars_Bot/4.12 (+http://www.yottacars.com) Car Search Engine ",
+ "YottaShopping_Bot/4.12 (+http://www.yottashopping.com) Shopping Search Engine",
+ "Zao-Crawler",
+ "Zao-Crawler 0.2b",
+ "Zao/0.1 (http://www.kototoi.org/zao/)",
+ "ZBot/1.00 (icaulfield@zeus.com)",
+ "Zearchit",
+ "ZeBot_lseek.net (bot@ze.bz)",
+ "ZeBot_www.ze.bz (ze.bz@hotmail.com)",
+ "zedzo.digest/0.1 (http://www.zedzo.com/)",
+ "zermelo Mozilla/5.0 compatible; heritrix/1.12.1 (+http://www.powerset.com) [email:crawl@powerset.com,email:paul@page-store.com]",
+ "zerxbot/Version 0.6 libwww-perl/5.79",
+ "Zeus ThemeSite Viewer Webster Pro V2.9 Win32",
+ "Zeus xxxxx Webster Pro V2.9 Win32",
+ "Zeusbot/0.07 (Ulysseek's web-crawling robot; http://www.zeusbot.com; agent@zeusbot.com)",
+ "ZipppBot/0.xx (ZipppBot; http://www.zippp.net; webmaster@zippp.net)",
+ "ZIPPPCVS/0.xx (ZipppBot/.xx;http://www.zippp.net; webmaster@zippp.net)",
+ "Zippy v2.0 - Zippyfinder.com",
+ "ZoomSpider - wrensoft.com",
+ "zspider/0.9-dev http://feedback.redkolibri.com/",
+ "ZyBorg/1.0 (ZyBorg@WISEnut.com; http://www.WISEnut.com)"]
+ end
+end
diff --git a/vendor/impressionist/app/models/impressionist/impressionable.rb b/vendor/impressionist/app/models/impressionist/impressionable.rb
new file mode 100644
index 00000000..f5cbd3b0
--- /dev/null
+++ b/vendor/impressionist/app/models/impressionist/impressionable.rb
@@ -0,0 +1,64 @@
+module Impressionist
+ module Impressionable
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ attr_accessor :impressionist_cache_options
+ @impressionist_cache_options = nil
+
+ def impressionist_counter_cache_options
+ if @impressionist_cache_options
+ options = { :column_name => :impressions_count, :unique => false }
+ options.merge!(@impressionist_cache_options) if @impressionist_cache_options.is_a?(Hash)
+ options
+ end
+ end
+
+ def impressionist_counter_caching?
+ impressionist_counter_cache_options.present?
+ end
+
+ def counter_caching?
+ ::ActiveSupport::Deprecation.warn("#counter_caching? is deprecated; please use #impressionist_counter_caching? instead")
+ impressionist_counter_caching?
+ end
+ end
+
+ def impressionable?
+ true
+ end
+
+ def impressionist_count(options={})
+ # options.reverse_merge!(:filter=>:request_hash, :start_date=>nil, :end_date=>Time.now)
+ # imps = options[:start_date].blank? ? impressions : impressions.where("created_at>=? and created_at<=?",options[:start_date],options[:end_date])
+ # options[:filter] == :all ? imps.count : imps.count(options[:filter], :distinct => true)
+ options.reverse_merge!(:filter => :request_hash, :start_date => nil, :end_date => Time.now)
+ imps = options[:start_date].blank? ? impressions : impressions.where(:created_at.gte => options[:start_date], :created_at.lte => options[:end_date])
+ options[:filter] == :all ? imps.count : imps.distinct(options[:filter]).count
+ end
+
+ def update_impressionist_counter_cache
+ cache_options = self.class.impressionist_counter_cache_options
+ column_name = cache_options[:column_name].to_sym
+ count = cache_options[:unique] ? impressionist_count(:filter => :ip_address) : impressionist_count
+ update_attribute(column_name, count)
+ end
+
+ # OLD METHODS - DEPRECATE IN V0.5
+ def impression_count(start_date=nil,end_date=Time.now)
+ impressionist_count({:start_date=>start_date, :end_date=>end_date, :filter=>:all})
+ end
+
+ def unique_impression_count(start_date=nil,end_date=Time.now)
+ impressionist_count({:start_date=>start_date, :end_date=>end_date, :filter=> :request_hash})
+ end
+
+ def unique_impression_count_ip(start_date=nil,end_date=Time.now)
+ impressionist_count({:start_date=>start_date, :end_date=>end_date, :filter=> :ip_address})
+ end
+
+ def unique_impression_count_session(start_date=nil,end_date=Time.now)
+ impressionist_count({:start_date=>start_date, :end_date=>end_date, :filter=> :session_hash})
+ end
+ end
+end
diff --git a/vendor/impressionist/impressionist.gemspec b/vendor/impressionist/impressionist.gemspec
new file mode 100644
index 00000000..e462fe73
--- /dev/null
+++ b/vendor/impressionist/impressionist.gemspec
@@ -0,0 +1,27 @@
+# encoding: utf-8
+require File.expand_path('../lib/impressionist/version', __FILE__)
+
+Gem::Specification.new do |s|
+ s.add_dependency 'httpclient', '~> 2.2'
+ s.add_dependency 'nokogiri', '~> 1.5'
+ s.add_development_dependency 'capybara'
+ s.add_development_dependency 'rake', '>= 0.9'
+ s.add_development_dependency 'rails', '~>3.1'
+ s.add_development_dependency 'rdoc', '>= 2.4.2'
+ s.add_development_dependency 'rspec-rails'
+ s.add_development_dependency 'simplecov'
+ s.add_development_dependency 'sqlite3'
+ s.add_development_dependency 'systemu'
+ s.authors = ["johnmcaliley"]
+ s.description = "Log impressions from controller actions or from a model"
+ s.email = "john.mcaliley@gmail.com"
+ s.files = `git ls-files`.split("\n")
+ s.homepage = "https://github.com/charlotte-ruby/impressionist"
+ s.licenses = ["MIT"]
+ s.name = "impressionist"
+ s.require_paths = ["lib"]
+ s.required_rubygems_version = Gem::Requirement.new('>= 1.3.6') if s.respond_to? :required_rubygems_version=
+ s.summary = "Easy way to log impressions"
+ s.test_files = `git ls-files -- test_app/*`.split("\n")
+ s.version = Impressionist::VERSION
+end
diff --git a/vendor/impressionist/lib/generators/active_record/impressionist_generator.rb b/vendor/impressionist/lib/generators/active_record/impressionist_generator.rb
new file mode 100644
index 00000000..31f4c1a4
--- /dev/null
+++ b/vendor/impressionist/lib/generators/active_record/impressionist_generator.rb
@@ -0,0 +1,21 @@
+module ActiveRecord
+ module Generators
+ class ImpressionistGenerator < Rails::Generators::Base
+ include Rails::Generators::Migration
+ source_root File.join(File.dirname(__FILE__), 'templates')
+
+ def self.next_migration_number(dirname)
+ sleep 1
+ if ActiveRecord::Base.timestamped_migrations
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
+ else
+ "%.3d" % (current_migration_number(dirname) + 1)
+ end
+ end
+
+ def create_migration_file
+ migration_template 'create_impressions_table.rb', 'db/migrate/create_impressions_table.rb'
+ end
+ end
+ end
+end
diff --git a/vendor/impressionist/lib/generators/active_record/templates/create_impressions_table.rb b/vendor/impressionist/lib/generators/active_record/templates/create_impressions_table.rb
new file mode 100644
index 00000000..5282380c
--- /dev/null
+++ b/vendor/impressionist/lib/generators/active_record/templates/create_impressions_table.rb
@@ -0,0 +1,30 @@
+class CreateImpressionsTable < ActiveRecord::Migration
+ def self.up
+ create_table :impressions, :force => true do |t|
+ t.string :impressionable_type
+ t.integer :impressionable_id
+ t.integer :user_id
+ t.string :controller_name
+ t.string :action_name
+ t.string :view_name
+ t.string :request_hash
+ t.string :ip_address
+ t.string :session_hash
+ t.text :message
+ t.text :referrer
+ t.timestamps
+ end
+ add_index :impressions, [:impressionable_type, :message, :impressionable_id], :name => "impressionable_type_message_index", :unique => false
+ add_index :impressions, [:impressionable_type, :impressionable_id, :request_hash], :name => "poly_request_index", :unique => false
+ add_index :impressions, [:impressionable_type, :impressionable_id, :ip_address], :name => "poly_ip_index", :unique => false
+ add_index :impressions, [:impressionable_type, :impressionable_id, :session_hash], :name => "poly_session_index", :unique => false
+ add_index :impressions, [:controller_name,:action_name,:request_hash], :name => "controlleraction_request_index", :unique => false
+ add_index :impressions, [:controller_name,:action_name,:ip_address], :name => "controlleraction_ip_index", :unique => false
+ add_index :impressions, [:controller_name,:action_name,:session_hash], :name => "controlleraction_session_index", :unique => false
+ add_index :impressions, :user_id
+ end
+
+ def self.down
+ drop_table :impressions
+ end
+end
diff --git a/vendor/impressionist/lib/generators/impressionist_generator.rb b/vendor/impressionist/lib/generators/impressionist_generator.rb
new file mode 100644
index 00000000..30290af2
--- /dev/null
+++ b/vendor/impressionist/lib/generators/impressionist_generator.rb
@@ -0,0 +1,12 @@
+module Impressionist
+ module Generators
+ class ImpressionistGenerator < Rails::Generators::Base
+ hook_for :orm
+ source_root File.expand_path('../templates', __FILE__)
+
+ def copy_config_file
+ template 'impression.rb', 'config/initializers/impression.rb'
+ end
+ end
+ end
+end
diff --git a/vendor/impressionist/lib/generators/mongo_mapper/impressionist_generator.rb b/vendor/impressionist/lib/generators/mongo_mapper/impressionist_generator.rb
new file mode 100644
index 00000000..eaa1b65c
--- /dev/null
+++ b/vendor/impressionist/lib/generators/mongo_mapper/impressionist_generator.rb
@@ -0,0 +1,8 @@
+module MongoMapper
+ module Generators
+ class ImpressionistGenerator < Rails::Generators::Base
+ # Empty for now, need it for generating the config file without
+ # triggering other ORM's generators.
+ end
+ end
+end
diff --git a/vendor/impressionist/lib/generators/mongoid/impressionist_generator.rb b/vendor/impressionist/lib/generators/mongoid/impressionist_generator.rb
new file mode 100644
index 00000000..e56bf904
--- /dev/null
+++ b/vendor/impressionist/lib/generators/mongoid/impressionist_generator.rb
@@ -0,0 +1,8 @@
+module Mongoid
+ module Generators
+ class ImpressionistGenerator < Rails::Generators::Base
+ # Empty for now, need it for generating the config file without
+ # triggering other ORM's generators.
+ end
+ end
+end
diff --git a/vendor/impressionist/lib/generators/templates/impression.rb b/vendor/impressionist/lib/generators/templates/impression.rb
new file mode 100644
index 00000000..fbdee915
--- /dev/null
+++ b/vendor/impressionist/lib/generators/templates/impression.rb
@@ -0,0 +1,5 @@
+# Use this hook to configure impressionist parameters
+Impressionist.setup do |config|
+ # Define ORM. Could be :active_record (default) and :mongo_mapper
+ # config.orm = :active_record
+end
diff --git a/vendor/impressionist/lib/impressionist.rb b/vendor/impressionist/lib/impressionist.rb
new file mode 100644
index 00000000..7ead8fe9
--- /dev/null
+++ b/vendor/impressionist/lib/impressionist.rb
@@ -0,0 +1,12 @@
+require "impressionist/engine.rb"
+
+module Impressionist
+ # Define ORM
+ mattr_accessor :orm
+ @@orm = :active_record
+
+ # Load configuration from initializer
+ def self.setup
+ yield self
+ end
+end
diff --git a/vendor/impressionist/lib/impressionist/bots.rb b/vendor/impressionist/lib/impressionist/bots.rb
new file mode 100644
index 00000000..10d3fd64
--- /dev/null
+++ b/vendor/impressionist/lib/impressionist/bots.rb
@@ -0,0 +1,18 @@
+require 'httpclient'
+require 'nokogiri'
+
+module Impressionist
+ module Bots
+ LIST_URL = "http://www.user-agents.org/allagents.xml"
+ def self.consume
+ response = HTTPClient.new.get_content(LIST_URL)
+ doc = Nokogiri::XML(response)
+ list = []
+ doc.xpath('//user-agent').each do |agent|
+ type = agent.xpath("Type").text
+ list << agent.xpath("String").text.gsub("<","<") if ["R","S"].include?(type) #gsub hack for badly formatted data
+ end
+ list
+ end
+ end
+end
diff --git a/vendor/impressionist/lib/impressionist/engine.rb b/vendor/impressionist/lib/impressionist/engine.rb
new file mode 100644
index 00000000..8f953d75
--- /dev/null
+++ b/vendor/impressionist/lib/impressionist/engine.rb
@@ -0,0 +1,30 @@
+require "impressionist"
+require "rails"
+
+module Impressionist
+ class Engine < Rails::Engine
+ initializer 'impressionist.model' do |app|
+ require "#{root}/app/models/impressionist/impressionable.rb"
+ if Impressionist.orm == :active_record
+ require "impressionist/models/active_record/impression.rb"
+ require "impressionist/models/active_record/impressionist/impressionable.rb"
+ ActiveRecord::Base.send(:include, Impressionist::Impressionable)
+ elsif Impressionist.orm == :mongo_mapper
+ require "impressionist/models/mongo_mapper/impression.rb"
+ require "impressionist/models/mongo_mapper/impressionist/impressionable.rb"
+ MongoMapper::Document.plugin Impressionist::Impressionable
+ elsif Impressionist.orm == :mongoid
+ require "impressionist/models/mongoid/impression.rb"
+ require "impressionist/models/mongoid/impressionist/impressionable.rb"
+ # Mongoid::Document Impressionist::Impressionable
+ end
+ end
+
+ initializer 'impressionist.controller' do
+ ActiveSupport.on_load(:action_controller) do
+ include ImpressionistController::InstanceMethods
+ extend ImpressionistController::ClassMethods
+ end
+ end
+ end
+end
diff --git a/vendor/impressionist/lib/impressionist/models/active_record/impression.rb b/vendor/impressionist/lib/impressionist/models/active_record/impression.rb
new file mode 100644
index 00000000..94c2710c
--- /dev/null
+++ b/vendor/impressionist/lib/impressionist/models/active_record/impression.rb
@@ -0,0 +1,18 @@
+class Impression < ActiveRecord::Base
+ attr_accessible :impressionable_type, :impressionable_id, :user_id,
+ :controller_name, :action_name, :view_name, :request_hash, :ip_address,
+ :session_hash, :message, :referrer
+
+ after_save :update_impressions_counter_cache
+
+ private
+
+ def update_impressions_counter_cache
+ impressionable_class = self.impressionable_type.constantize
+
+ if impressionable_class.impressionist_counter_cache_options
+ resouce = impressionable_class.find(self.impressionable_id)
+ resouce.try(:update_impressionist_counter_cache)
+ end
+ end
+end
diff --git a/vendor/impressionist/lib/impressionist/models/active_record/impressionist/impressionable.rb b/vendor/impressionist/lib/impressionist/models/active_record/impressionist/impressionable.rb
new file mode 100644
index 00000000..6100b886
--- /dev/null
+++ b/vendor/impressionist/lib/impressionist/models/active_record/impressionist/impressionable.rb
@@ -0,0 +1,12 @@
+module Impressionist
+ module Impressionable
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def is_impressionable(options={})
+ has_many :impressions, :as => :impressionable, :dependent => :destroy
+ @impressionist_cache_options = options[:counter_cache]
+ end
+ end
+ end
+end
diff --git a/vendor/impressionist/lib/impressionist/models/mongo_mapper/impression.rb b/vendor/impressionist/lib/impressionist/models/mongo_mapper/impression.rb
new file mode 100644
index 00000000..a7d5f64c
--- /dev/null
+++ b/vendor/impressionist/lib/impressionist/models/mongo_mapper/impression.rb
@@ -0,0 +1,16 @@
+class Impression
+ include MongoMapper::Document
+
+ key :impressionable_type, String
+ key :impressionable_id, String
+ key :user_id, String
+ key :controller_name, String
+ key :action_name, String
+ key :view_name, String
+ key :request_hash, String
+ key :ip_address, String
+ key :session_hash, String
+ key :message, String
+ key :referrer, String
+ timestamps!
+end
diff --git a/vendor/impressionist/lib/impressionist/models/mongo_mapper/impressionist/impressionable.rb b/vendor/impressionist/lib/impressionist/models/mongo_mapper/impressionist/impressionable.rb
new file mode 100644
index 00000000..9e0dd524
--- /dev/null
+++ b/vendor/impressionist/lib/impressionist/models/mongo_mapper/impressionist/impressionable.rb
@@ -0,0 +1,12 @@
+module Impressionist
+ module Impressionable
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def is_impressionable(options={})
+ many :impressions, :as => :impressionable, :dependent => :destroy
+ @cache_options = options[:counter_cache]
+ end
+ end
+ end
+end
diff --git a/vendor/impressionist/lib/impressionist/models/mongoid/impression.rb b/vendor/impressionist/lib/impressionist/models/mongoid/impression.rb
new file mode 100644
index 00000000..5edc2edd
--- /dev/null
+++ b/vendor/impressionist/lib/impressionist/models/mongoid/impression.rb
@@ -0,0 +1,37 @@
+class Impression
+ include Mongoid::Document
+ include Mongoid::Timestamps
+
+ field :impressionable_type
+ field :impressionable_id
+ field :user_id
+ field :controller_name
+ field :action_name
+ field :view_name
+ field :request_hash
+ field :ip_address
+ field :session_hash
+ field :message
+ field :referrer
+
+ belongs_to :impressionable, :polymorphic => true
+
+ after_save :update_impressions_counter_cache
+
+ def self.impressionist_count(options={})
+ options.reverse_merge!(:filter => :request_hash, :start_date => nil, :end_date => Time.now)
+ imps = options[:start_date].blank? ? impressions : impressions.where(:created_at.gte => options[:start_date], :created_at.lte => options[:end_date])
+ options[:filter] == :all ? imps.count : imps.distinct(options[:filter]).count
+ end
+
+ private
+
+ def update_impressions_counter_cache
+ impressionable_class = self.impressionable_type.constantize
+
+ if impressionable_class.impressionist_counter_cache_options
+ resouce = impressionable_class.find(self.impressionable_id)
+ resouce.try(:update_impressionist_counter_cache)
+ end
+ end
+end
diff --git a/vendor/impressionist/lib/impressionist/models/mongoid/impressionist/impressionable.rb b/vendor/impressionist/lib/impressionist/models/mongoid/impressionist/impressionable.rb
new file mode 100644
index 00000000..6100b886
--- /dev/null
+++ b/vendor/impressionist/lib/impressionist/models/mongoid/impressionist/impressionable.rb
@@ -0,0 +1,12 @@
+module Impressionist
+ module Impressionable
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def is_impressionable(options={})
+ has_many :impressions, :as => :impressionable, :dependent => :destroy
+ @impressionist_cache_options = options[:counter_cache]
+ end
+ end
+ end
+end
diff --git a/vendor/impressionist/lib/impressionist/version.rb b/vendor/impressionist/lib/impressionist/version.rb
new file mode 100644
index 00000000..cc644dcd
--- /dev/null
+++ b/vendor/impressionist/lib/impressionist/version.rb
@@ -0,0 +1,3 @@
+module Impressionist
+ VERSION = "1.1.1"
+end
diff --git a/vendor/impressionist/logo.png b/vendor/impressionist/logo.png
new file mode 100644
index 00000000..9106036b
Binary files /dev/null and b/vendor/impressionist/logo.png differ
diff --git a/vendor/impressionist/test_app/.gitignore b/vendor/impressionist/test_app/.gitignore
new file mode 100644
index 00000000..351f6382
--- /dev/null
+++ b/vendor/impressionist/test_app/.gitignore
@@ -0,0 +1,17 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+#
+# If you find yourself ignoring temporary files generated by your text editor
+# or operating system, you probably want to add a global ignore instead:
+# git config --global core.excludesfile ~/.gitignore_global
+
+# Ignore bundler config
+/.bundle
+/Gemfile.lock
+
+# Ignore the default SQLite database.
+/db/*.sqlite3
+
+# Ignore all logfiles and tempfiles.
+/coverage
+/log/*.log
+/tmp
diff --git a/vendor/impressionist/test_app/.rspec b/vendor/impressionist/test_app/.rspec
new file mode 100644
index 00000000..4e1e0d2f
--- /dev/null
+++ b/vendor/impressionist/test_app/.rspec
@@ -0,0 +1 @@
+--color
diff --git a/vendor/impressionist/test_app/Gemfile b/vendor/impressionist/test_app/Gemfile
new file mode 100644
index 00000000..67f8bb24
--- /dev/null
+++ b/vendor/impressionist/test_app/Gemfile
@@ -0,0 +1,59 @@
+source 'https://rubygems.org'
+
+gem 'rails', '3.2.2'
+
+gem 'impressionist', :path => '../'
+
+platforms :jruby do
+ gem 'activerecord-jdbcsqlite3-adapter'
+ gem 'jdbc-sqlite3'
+ gem 'jruby-openssl'
+end
+
+platforms :ruby, :mswin, :mingw do
+ gem 'pg'
+ gem 'sqlite3'
+end
+
+gem 'json'
+
+# Gems used only for assets and not required
+# in production environments by default.
+group :assets do
+ gem 'sass-rails', '~> 3.2.3'
+ gem 'coffee-rails', '~> 3.2.1'
+
+ # See https://github.com/sstephenson/execjs#readme for more supported runtimes
+ # gem 'therubyracer'
+
+ gem 'uglifier', '>= 1.0.3'
+end
+
+group :development, :test do
+ gem 'autotest-notification'
+ gem 'rspec-rails'
+ gem 'spork'
+end
+
+group :test do
+ gem 'capybara'
+ gem 'simplecov'
+ gem 'systemu'
+end
+
+gem 'jquery-rails'
+
+# To use ActiveModel has_secure_password
+# gem 'bcrypt-ruby', '~> 3.0.0'
+
+# To use Jbuilder templates for JSON
+# gem 'jbuilder'
+
+# Use unicorn as the app server
+# gem 'unicorn'
+
+# Deploy with Capistrano
+# gem 'capistrano'
+
+# To use debugger
+# gem 'ruby-debug'
diff --git a/vendor/impressionist/test_app/README b/vendor/impressionist/test_app/README
new file mode 100644
index 00000000..fe7013d5
--- /dev/null
+++ b/vendor/impressionist/test_app/README
@@ -0,0 +1,256 @@
+== Welcome to Rails
+
+Rails is a web-application framework that includes everything needed to create
+database-backed web applications according to the Model-View-Control pattern.
+
+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, create a new Rails application:
+ rails new myapp (where myapp is the application name)
+
+2. Change directory to myapp and start the web server:
+ cd myapp; rails server (run with --help for options)
+
+3. Go to http://localhost:3000/ and you'll see:
+ "Welcome aboard: You're riding Ruby on Rails!"
+
+4. Follow the guidelines to start developing your application. You can find
+the following resources handy:
+
+* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html
+* Ruby on Rails Tutorial Book: http://www.railstutorial.org/
+
+
+== 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/. There are
+several books available online as well:
+
+* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe)
+* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)
+
+These two 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! You need to install ruby-debug to run the server in debugging
+mode. With gems, use sudo gem install ruby-debug. 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", "body"=>"Only ten..", "id"=>"2"}>]"
+ >> @posts.first.title = "hello from a debugger"
+ => "hello from a debugger"
+
+...and even better, 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 can enter "cont".
+
+
+== Console
+
+The console is a Ruby shell, which allows you to interact with your
+application's domain model. 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.
+
+To start the console, run rails console from the application
+directory.
+
+Options:
+
+* Passing the -s, --sandbox argument will rollback any modifications
+ made to the database.
+* Passing an environment name as an argument will load the corresponding
+ environment. Example: rails console production.
+
+To reload your controllers and models after launching the console run
+reload!
+
+More information about irb can be found at:
+link:http://www.rubycentral.com/pickaxe/irb.html
+
+
+== dbconsole
+
+You can go to the command line of your database directly through rails
+dbconsole. You would be connected to the database with the credentials
+defined in database.yml. Starting the script without arguments will connect you
+to the development database. Passing an argument will connect you to a different
+database, like rails dbconsole production. Currently works for MySQL,
+PostgreSQL and SQLite 3.
+
+== Description of Contents
+
+The default directory structure of a generated Ruby on Rails application:
+
+ |-- app
+ | |-- controllers
+ | |-- helpers
+ | |-- mailers
+ | |-- models
+ | `-- views
+ | `-- layouts
+ |-- config
+ | |-- environments
+ | |-- initializers
+ | `-- locales
+ |-- db
+ |-- doc
+ |-- lib
+ | `-- tasks
+ |-- log
+ |-- public
+ | |-- images
+ | |-- javascripts
+ | `-- stylesheets
+ |-- script
+ |-- test
+ | |-- fixtures
+ | |-- functional
+ | |-- integration
+ | |-- performance
+ | `-- unit
+ |-- tmp
+ | |-- cache
+ | |-- pids
+ | |-- sessions
+ | `-- sockets
+ `-- vendor
+ `-- plugins
+
+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. Models descend from
+ ActiveRecord::Base by default.
+
+app/views
+ Holds the template files for the view that should be named like
+ weblogs/index.html.erb for the WeblogsController#index action. All views use
+ eRuby syntax by default.
+
+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.html.erb.
+ Inside default.html.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 generators 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 rails generate
+ command, 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. If the app has frozen rails, those gems also go here, under
+ vendor/rails/. This directory is in the load path.
diff --git a/vendor/impressionist/test_app/README.rdoc b/vendor/impressionist/test_app/README.rdoc
new file mode 100644
index 00000000..7c36f235
--- /dev/null
+++ b/vendor/impressionist/test_app/README.rdoc
@@ -0,0 +1,261 @@
+== Welcome to Rails
+
+Rails is a web-application framework that includes everything needed to create
+database-backed web applications according to the Model-View-Control pattern.
+
+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, create a new Rails application:
+ rails new myapp (where myapp is the application name)
+
+2. Change directory to myapp and start the web server:
+ cd myapp; rails server (run with --help for options)
+
+3. Go to http://localhost:3000/ and you'll see:
+ "Welcome aboard: You're riding Ruby on Rails!"
+
+4. Follow the guidelines to start developing your application. You can find
+the following resources handy:
+
+* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html
+* Ruby on Rails Tutorial Book: http://www.railstutorial.org/
+
+
+== 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/. There are
+several books available online as well:
+
+* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe)
+* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)
+
+These two 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! You need to install ruby-debug to run the server in debugging
+mode. With gems, use sudo gem install ruby-debug. Example:
+
+ class WeblogController < ActionController::Base
+ def index
+ @posts = Post.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", "body"=>"Only ten..", "id"=>"2"}>]"
+ >> @posts.first.title = "hello from a debugger"
+ => "hello from a debugger"
+
+...and even better, 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 can enter "cont".
+
+
+== Console
+
+The console is a Ruby shell, which allows you to interact with your
+application's domain model. 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.
+
+To start the console, run rails console from the application
+directory.
+
+Options:
+
+* Passing the -s, --sandbox argument will rollback any modifications
+ made to the database.
+* Passing an environment name as an argument will load the corresponding
+ environment. Example: rails console production.
+
+To reload your controllers and models after launching the console run
+reload!
+
+More information about irb can be found at:
+link:http://www.rubycentral.org/pickaxe/irb.html
+
+
+== dbconsole
+
+You can go to the command line of your database directly through rails
+dbconsole. You would be connected to the database with the credentials
+defined in database.yml. Starting the script without arguments will connect you
+to the development database. Passing an argument will connect you to a different
+database, like rails dbconsole production. Currently works for MySQL,
+PostgreSQL and SQLite 3.
+
+== Description of Contents
+
+The default directory structure of a generated Ruby on Rails application:
+
+ |-- app
+ | |-- assets
+ | |-- images
+ | |-- javascripts
+ | `-- stylesheets
+ | |-- controllers
+ | |-- helpers
+ | |-- mailers
+ | |-- models
+ | `-- views
+ | `-- layouts
+ |-- config
+ | |-- environments
+ | |-- initializers
+ | `-- locales
+ |-- db
+ |-- doc
+ |-- lib
+ | `-- tasks
+ |-- log
+ |-- public
+ |-- script
+ |-- test
+ | |-- fixtures
+ | |-- functional
+ | |-- integration
+ | |-- performance
+ | `-- unit
+ |-- tmp
+ | |-- cache
+ | |-- pids
+ | |-- sessions
+ | `-- sockets
+ `-- vendor
+ |-- assets
+ `-- stylesheets
+ `-- plugins
+
+app
+ Holds all the code that's specific to this particular application.
+
+app/assets
+ Contains subdirectories for images, stylesheets, and JavaScript files.
+
+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. Models descend from
+ ActiveRecord::Base by default.
+
+app/views
+ Holds the template files for the view that should be named like
+ weblogs/index.html.erb for the WeblogsController#index action. All views use
+ eRuby syntax by default.
+
+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.html.erb.
+ Inside default.html.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 generators 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. 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 rails generate
+ command, 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. If the app has frozen rails, those gems also go here, under
+ vendor/rails/. This directory is in the load path.
diff --git a/vendor/impressionist/test_app/Rakefile b/vendor/impressionist/test_app/Rakefile
new file mode 100644
index 00000000..9946cea6
--- /dev/null
+++ b/vendor/impressionist/test_app/Rakefile
@@ -0,0 +1,7 @@
+#!/usr/bin/env rake
+# Add your own tasks in files placed in lib/tasks ending in .rake,
+# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
+
+require File.expand_path('../config/application', __FILE__)
+
+TestApp::Application.load_tasks
diff --git a/vendor/impressionist/test_app/app/assets/images/rails.png b/vendor/impressionist/test_app/app/assets/images/rails.png
new file mode 100644
index 00000000..d5edc04e
Binary files /dev/null and b/vendor/impressionist/test_app/app/assets/images/rails.png differ
diff --git a/vendor/impressionist/test_app/app/assets/javascripts/application.js b/vendor/impressionist/test_app/app/assets/javascripts/application.js
new file mode 100644
index 00000000..9097d830
--- /dev/null
+++ b/vendor/impressionist/test_app/app/assets/javascripts/application.js
@@ -0,0 +1,15 @@
+// This is a manifest file that'll be compiled into application.js, which will include all the files
+// listed below.
+//
+// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
+// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
+//
+// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+// the compiled file.
+//
+// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
+// GO AFTER THE REQUIRES BELOW.
+//
+//= require jquery
+//= require jquery_ujs
+//= require_tree .
diff --git a/vendor/impressionist/test_app/app/assets/stylesheets/application.css b/vendor/impressionist/test_app/app/assets/stylesheets/application.css
new file mode 100644
index 00000000..3b5cc664
--- /dev/null
+++ b/vendor/impressionist/test_app/app/assets/stylesheets/application.css
@@ -0,0 +1,13 @@
+/*
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
+ * listed below.
+ *
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
+ *
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
+ * compiled file, but it's generally better to create a new file per style scope.
+ *
+ *= require_self
+ *= require_tree .
+*/
diff --git a/vendor/impressionist/test_app/app/controllers/application_controller.rb b/vendor/impressionist/test_app/app/controllers/application_controller.rb
new file mode 100644
index 00000000..1ce61fa1
--- /dev/null
+++ b/vendor/impressionist/test_app/app/controllers/application_controller.rb
@@ -0,0 +1,8 @@
+class ApplicationController < ActionController::Base
+ protect_from_forgery
+ before_filter :secondary_before_filter
+
+ def secondary_before_filter
+ @test_secondary_before_filter = "this is a test"
+ end
+end
diff --git a/vendor/impressionist/test_app/app/controllers/articles_controller.rb b/vendor/impressionist/test_app/app/controllers/articles_controller.rb
new file mode 100644
index 00000000..367b460f
--- /dev/null
+++ b/vendor/impressionist/test_app/app/controllers/articles_controller.rb
@@ -0,0 +1,18 @@
+class ArticlesController < ApplicationController
+ before_filter :test_current_user_var
+
+ def test_current_user_var
+ if session[:user_id]
+ @current_user = User.new
+ @current_user.id = session[:user_id]
+ end
+ end
+
+ def index
+ impressionist(Article.first,"this is a test article impression")
+ end
+
+ def show
+ impressionist(Article.first)
+ end
+end
diff --git a/vendor/impressionist/test_app/app/controllers/dummy_controller.rb b/vendor/impressionist/test_app/app/controllers/dummy_controller.rb
new file mode 100644
index 00000000..86664a67
--- /dev/null
+++ b/vendor/impressionist/test_app/app/controllers/dummy_controller.rb
@@ -0,0 +1,6 @@
+# This controller imports the impressionist module to make the modules methods available for testing
+class DummyController < ActionController::Base
+
+ impressionist
+
+end
diff --git a/vendor/impressionist/test_app/app/controllers/posts_controller.rb b/vendor/impressionist/test_app/app/controllers/posts_controller.rb
new file mode 100644
index 00000000..35262709
--- /dev/null
+++ b/vendor/impressionist/test_app/app/controllers/posts_controller.rb
@@ -0,0 +1,23 @@
+class PostsController < ApplicationController
+ helper_method :current_user
+ impressionist
+ def index
+
+ end
+
+ def show
+
+ end
+
+ def edit
+
+ end
+
+ def current_user
+ if session[:user_id]
+ user = User.new
+ user.id = session[:user_id]
+ @current_user ||= user
+ end
+ end
+end
diff --git a/vendor/impressionist/test_app/app/controllers/widgets_controller.rb b/vendor/impressionist/test_app/app/controllers/widgets_controller.rb
new file mode 100644
index 00000000..b6b99a72
--- /dev/null
+++ b/vendor/impressionist/test_app/app/controllers/widgets_controller.rb
@@ -0,0 +1,12 @@
+class WidgetsController < ApplicationController
+ impressionist :actions=>[:show,:index], :unique => [:controller_name,:action_name,:impressionable_id]
+
+ def show
+ end
+
+ def index
+ end
+
+ def new
+ end
+end
diff --git a/vendor/impressionist/test_app/app/helpers/application_helper.rb b/vendor/impressionist/test_app/app/helpers/application_helper.rb
new file mode 100644
index 00000000..de6be794
--- /dev/null
+++ b/vendor/impressionist/test_app/app/helpers/application_helper.rb
@@ -0,0 +1,2 @@
+module ApplicationHelper
+end
diff --git a/tmp/restart.txt b/vendor/impressionist/test_app/app/mailers/.gitkeep
similarity index 100%
rename from tmp/restart.txt
rename to vendor/impressionist/test_app/app/mailers/.gitkeep
diff --git a/vendor/impressionist/test_app/app/models/.gitkeep b/vendor/impressionist/test_app/app/models/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/vendor/impressionist/test_app/app/models/article.rb b/vendor/impressionist/test_app/app/models/article.rb
new file mode 100644
index 00000000..0a18546d
--- /dev/null
+++ b/vendor/impressionist/test_app/app/models/article.rb
@@ -0,0 +1,3 @@
+class Article < ActiveRecord::Base
+ is_impressionable
+end
diff --git a/vendor/impressionist/test_app/app/models/dummy.rb b/vendor/impressionist/test_app/app/models/dummy.rb
new file mode 100644
index 00000000..745c60aa
--- /dev/null
+++ b/vendor/impressionist/test_app/app/models/dummy.rb
@@ -0,0 +1,7 @@
+# We don't really care about this model. It's just being used to test the uniqueness controller
+# specs. Nevertheless, we need a model because the counter caching functionality expects it.
+#
+class Dummy < ActiveRecord::Base
+ self.abstract_class = true # doesn't need to be backed by an actual table
+ is_impressionable
+end
diff --git a/vendor/impressionist/test_app/app/models/post.rb b/vendor/impressionist/test_app/app/models/post.rb
new file mode 100644
index 00000000..ccc43dac
--- /dev/null
+++ b/vendor/impressionist/test_app/app/models/post.rb
@@ -0,0 +1,3 @@
+class Post < ActiveRecord::Base
+ is_impressionable
+end
diff --git a/vendor/impressionist/test_app/app/models/user.rb b/vendor/impressionist/test_app/app/models/user.rb
new file mode 100644
index 00000000..6da0f650
--- /dev/null
+++ b/vendor/impressionist/test_app/app/models/user.rb
@@ -0,0 +1,3 @@
+class User
+ attr_accessor :id
+end
diff --git a/vendor/impressionist/test_app/app/models/widget.rb b/vendor/impressionist/test_app/app/models/widget.rb
new file mode 100644
index 00000000..51bab18d
--- /dev/null
+++ b/vendor/impressionist/test_app/app/models/widget.rb
@@ -0,0 +1,3 @@
+class Widget < ActiveRecord::Base
+ is_impressionable :counter_cache => true
+end
diff --git a/vendor/impressionist/test_app/app/views/articles/index.html.erb b/vendor/impressionist/test_app/app/views/articles/index.html.erb
new file mode 100644
index 00000000..97e10f82
--- /dev/null
+++ b/vendor/impressionist/test_app/app/views/articles/index.html.erb
@@ -0,0 +1 @@
+<%=@impressionist_hash==nil%>
diff --git a/vendor/impressionist/test_app/app/views/articles/show.html.erb b/vendor/impressionist/test_app/app/views/articles/show.html.erb
new file mode 100644
index 00000000..7dc920f3
--- /dev/null
+++ b/vendor/impressionist/test_app/app/views/articles/show.html.erb
@@ -0,0 +1 @@
+<%=link_to "Same Page", article_url(Article.first)%>
diff --git a/vendor/impressionist/test_app/app/views/layouts/application.html.erb b/vendor/impressionist/test_app/app/views/layouts/application.html.erb
new file mode 100644
index 00000000..d4557d87
--- /dev/null
+++ b/vendor/impressionist/test_app/app/views/layouts/application.html.erb
@@ -0,0 +1,14 @@
+
+
+
+ TestApp
+ <%= stylesheet_link_tag "application", :media => "all" %>
+ <%= javascript_include_tag "application" %>
+ <%= csrf_meta_tags %>
+
+
+
+<%= yield %>
+
+
+
diff --git a/vendor/impressionist/test_app/app/views/posts/edit.html.erb b/vendor/impressionist/test_app/app/views/posts/edit.html.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/vendor/impressionist/test_app/app/views/posts/index.html.erb b/vendor/impressionist/test_app/app/views/posts/index.html.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/vendor/impressionist/test_app/app/views/posts/show.html.erb b/vendor/impressionist/test_app/app/views/posts/show.html.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/vendor/impressionist/test_app/app/views/widgets/index.html.erb b/vendor/impressionist/test_app/app/views/widgets/index.html.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/vendor/impressionist/test_app/app/views/widgets/new.html.erb b/vendor/impressionist/test_app/app/views/widgets/new.html.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/vendor/impressionist/test_app/app/views/widgets/show.html.erb b/vendor/impressionist/test_app/app/views/widgets/show.html.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/vendor/impressionist/test_app/config.ru b/vendor/impressionist/test_app/config.ru
new file mode 100644
index 00000000..86a587d0
--- /dev/null
+++ b/vendor/impressionist/test_app/config.ru
@@ -0,0 +1,4 @@
+# This file is used by Rack-based servers to start the application.
+
+require ::File.expand_path('../config/environment', __FILE__)
+run TestApp::Application
diff --git a/vendor/impressionist/test_app/config/application.rb b/vendor/impressionist/test_app/config/application.rb
new file mode 100644
index 00000000..d0d0095d
--- /dev/null
+++ b/vendor/impressionist/test_app/config/application.rb
@@ -0,0 +1,59 @@
+require File.expand_path('../boot', __FILE__)
+
+require 'rails/all'
+
+if defined?(Bundler)
+ # If you precompile assets before deploying to production, use this line
+ Bundler.require(*Rails.groups(:assets => %w(development test)))
+ # If you want your assets lazily compiled in production, use this line
+ # Bundler.require(:default, :assets, Rails.env)
+end
+
+module TestApp
+ class Application < Rails::Application
+ # 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.
+
+ # Custom directories with classes and modules you want to be autoloadable.
+ # config.autoload_paths += %W(#{config.root}/extras)
+
+ # Only load the plugins named here, in the order given (default is alphabetical).
+ # :all can be used as a placeholder for all plugins not explicitly named.
+ # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
+
+ # Activate observers that should always be running.
+ # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
+
+ # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
+ # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
+ # config.time_zone = 'Central Time (US & Canada)'
+
+ # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
+ # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
+ # config.i18n.default_locale = :de
+
+ # Configure the default encoding used in templates for Ruby 1.9.
+ config.encoding = "utf-8"
+
+ # Configure sensitive parameters which will be filtered from the log file.
+ config.filter_parameters += [:password]
+
+ # Use SQL instead of Active Record's schema dumper when creating the 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
+
+ # Enforce whitelist mode for mass assignment.
+ # This will create an empty whitelist of attributes available for mass-assignment for all models
+ # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
+ # parameters by using an attr_accessible or attr_protected declaration.
+ # config.active_record.whitelist_attributes = true
+
+ # Enable the asset pipeline
+ config.assets.enabled = true
+
+ # Version of your assets, change this if you want to expire all your assets
+ config.assets.version = '1.0'
+ end
+end
diff --git a/vendor/impressionist/test_app/config/boot.rb b/vendor/impressionist/test_app/config/boot.rb
new file mode 100644
index 00000000..4489e586
--- /dev/null
+++ b/vendor/impressionist/test_app/config/boot.rb
@@ -0,0 +1,6 @@
+require 'rubygems'
+
+# Set up gems listed in the Gemfile.
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
+
+require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
diff --git a/vendor/impressionist/test_app/config/cucumber.yml b/vendor/impressionist/test_app/config/cucumber.yml
new file mode 100644
index 00000000..6f304dfa
--- /dev/null
+++ b/vendor/impressionist/test_app/config/cucumber.yml
@@ -0,0 +1,8 @@
+<%
+rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : ""
+rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}"
+std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} --strict --tags ~@wip"
+%>
+default: --drb <%= std_opts %> features
+wip: --drb --tags @wip:3 --wip features
+rerun: --drb <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip
diff --git a/vendor/impressionist/test_app/config/database.yml b/vendor/impressionist/test_app/config/database.yml
new file mode 100644
index 00000000..45fed06f
--- /dev/null
+++ b/vendor/impressionist/test_app/config/database.yml
@@ -0,0 +1,30 @@
+# SQLite version 3.x
+# gem install sqlite3-ruby (not necessary on OS X Leopard)
+development:
+ adapter: sqlite3
+ database: db/development.sqlite3
+ pool: 5
+ timeout: 5000
+
+test: &test
+ adapter: sqlite3
+ database: db/test.sqlite3
+ pool: 5
+ timeout: 5000
+
+#pg_test:
+# adapter: postgresql
+# database: impressionist_test
+# username: johnmcaliley
+# password:
+# host: localhost
+# encoding: UTF8
+
+production:
+ adapter: sqlite3
+ database: db/production.sqlite3
+ pool: 5
+ timeout: 5000
+
+cucumber:
+ <<: *test
diff --git a/vendor/impressionist/test_app/config/environment.rb b/vendor/impressionist/test_app/config/environment.rb
new file mode 100644
index 00000000..f4cc1e44
--- /dev/null
+++ b/vendor/impressionist/test_app/config/environment.rb
@@ -0,0 +1,5 @@
+# Load the rails application
+require File.expand_path('../application', __FILE__)
+
+# Initialize the rails application
+TestApp::Application.initialize!
diff --git a/vendor/impressionist/test_app/config/environments/development.rb b/vendor/impressionist/test_app/config/environments/development.rb
new file mode 100644
index 00000000..b5306b32
--- /dev/null
+++ b/vendor/impressionist/test_app/config/environments/development.rb
@@ -0,0 +1,37 @@
+TestApp::Application.configure do
+ # Settings specified here will take precedence over those in config/application.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 web server when you make code changes.
+ config.cache_classes = false
+
+ # Log error messages when you accidentally call methods on nil.
+ config.whiny_nils = true
+
+ # Show full error reports and disable caching
+ config.consider_all_requests_local = true
+ config.action_controller.perform_caching = false
+
+ # Don't care if the mailer can't send
+ config.action_mailer.raise_delivery_errors = false
+
+ # Print deprecation notices to the Rails logger
+ config.active_support.deprecation = :log
+
+ # Only use best-standards-support built into browsers
+ config.action_dispatch.best_standards_support = :builtin
+
+ # Raise exception on mass assignment protection for Active Record models
+ config.active_record.mass_assignment_sanitizer = :strict
+
+ # Log the query plan for queries taking more than this (works
+ # with SQLite, MySQL, and PostgreSQL)
+ config.active_record.auto_explain_threshold_in_seconds = 0.5
+
+ # Do not compress assets
+ config.assets.compress = false
+
+ # Expands the lines which load the assets
+ config.assets.debug = true
+end
diff --git a/vendor/impressionist/test_app/config/environments/pg_test.rb b/vendor/impressionist/test_app/config/environments/pg_test.rb
new file mode 100644
index 00000000..d17c1aeb
--- /dev/null
+++ b/vendor/impressionist/test_app/config/environments/pg_test.rb
@@ -0,0 +1,35 @@
+TestApp::Application.configure do
+ # Settings specified here will take precedence over those in config/application.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.consider_all_requests_local = true
+ config.action_controller.perform_caching = false
+
+ # Raise exceptions instead of rendering exception templates
+ config.action_dispatch.show_exceptions = false
+
+ # Disable request forgery protection in test environment
+ config.action_controller.allow_forgery_protection = false
+
+ # Tell Action Mailer 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
+
+ # 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
+
+ # Print deprecation notices to the stderr
+ config.active_support.deprecation = :stderr
+end
diff --git a/vendor/impressionist/test_app/config/environments/production.rb b/vendor/impressionist/test_app/config/environments/production.rb
new file mode 100644
index 00000000..c33789b2
--- /dev/null
+++ b/vendor/impressionist/test_app/config/environments/production.rb
@@ -0,0 +1,67 @@
+TestApp::Application.configure do
+ # Settings specified here will take precedence over those in config/application.rb
+
+ # Code is not reloaded between requests
+ config.cache_classes = true
+
+ # Full error reports are disabled and caching is turned on
+ config.consider_all_requests_local = false
+ config.action_controller.perform_caching = true
+
+ # Disable Rails's static asset server (Apache or nginx will already do this)
+ config.serve_static_assets = false
+
+ # Compress JavaScripts and CSS
+ config.assets.compress = true
+
+ # Don't fallback to assets pipeline if a precompiled asset is missed
+ config.assets.compile = false
+
+ # Generate digests for assets URLs
+ config.assets.digest = true
+
+ # Defaults to Rails.root.join("public/assets")
+ # config.assets.manifest = YOUR_PATH
+
+ # Specifies the header that your server uses for sending files
+ # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
+ # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
+
+ # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
+ # config.force_ssl = true
+
+ # See everything in the log (default is :info)
+ # config.log_level = :debug
+
+ # Prepend all log lines with the following tags
+ # config.log_tags = [ :subdomain, :uuid ]
+
+ # Use a different logger for distributed setups
+ # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
+
+ # Use a different cache store in production
+ # config.cache_store = :mem_cache_store
+
+ # Enable serving of images, stylesheets, and JavaScripts from an asset server
+ # config.action_controller.asset_host = "http://assets.example.com"
+
+ # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
+ # config.assets.precompile += %w( search.js )
+
+ # Disable delivery errors, bad email addresses will be ignored
+ # config.action_mailer.raise_delivery_errors = false
+
+ # Enable threaded mode
+ # config.threadsafe!
+
+ # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
+ # the I18n.default_locale when a translation can not be found)
+ config.i18n.fallbacks = true
+
+ # Send deprecation notices to registered listeners
+ config.active_support.deprecation = :notify
+
+ # Log the query plan for queries taking more than this (works
+ # with SQLite, MySQL, and PostgreSQL)
+ # config.active_record.auto_explain_threshold_in_seconds = 0.5
+end
diff --git a/vendor/impressionist/test_app/config/environments/test.rb b/vendor/impressionist/test_app/config/environments/test.rb
new file mode 100644
index 00000000..21ec8e47
--- /dev/null
+++ b/vendor/impressionist/test_app/config/environments/test.rb
@@ -0,0 +1,37 @@
+TestApp::Application.configure do
+ # Settings specified here will take precedence over those in config/application.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
+
+ # Configure static asset server for tests with Cache-Control for performance
+ config.serve_static_assets = true
+ config.static_cache_control = "public, max-age=3600"
+
+ # Log error messages when you accidentally call methods on nil
+ config.whiny_nils = true
+
+ # Show full error reports and disable caching
+ config.consider_all_requests_local = true
+ config.action_controller.perform_caching = false
+
+ # Raise exceptions instead of rendering exception templates
+ config.action_dispatch.show_exceptions = false
+
+ # Disable request forgery protection in test environment
+ config.action_controller.allow_forgery_protection = false
+
+ # Tell Action Mailer 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
+
+ # Raise exception on mass assignment protection for Active Record models
+ config.active_record.mass_assignment_sanitizer = :strict
+
+ # Print deprecation notices to the stderr
+ config.active_support.deprecation = :stderr
+end
diff --git a/vendor/impressionist/test_app/config/initializers/backtrace_silencers.rb b/vendor/impressionist/test_app/config/initializers/backtrace_silencers.rb
new file mode 100644
index 00000000..59385cdf
--- /dev/null
+++ b/vendor/impressionist/test_app/config/initializers/backtrace_silencers.rb
@@ -0,0 +1,7 @@
+# Be sure to restart your server when you modify this file.
+
+# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
+# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
+
+# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
+# Rails.backtrace_cleaner.remove_silencers!
diff --git a/vendor/impressionist/test_app/config/initializers/impression.rb b/vendor/impressionist/test_app/config/initializers/impression.rb
new file mode 100644
index 00000000..fbdee915
--- /dev/null
+++ b/vendor/impressionist/test_app/config/initializers/impression.rb
@@ -0,0 +1,5 @@
+# Use this hook to configure impressionist parameters
+Impressionist.setup do |config|
+ # Define ORM. Could be :active_record (default) and :mongo_mapper
+ # config.orm = :active_record
+end
diff --git a/vendor/impressionist/test_app/config/initializers/inflections.rb b/vendor/impressionist/test_app/config/initializers/inflections.rb
new file mode 100644
index 00000000..5d8d9be2
--- /dev/null
+++ b/vendor/impressionist/test_app/config/initializers/inflections.rb
@@ -0,0 +1,15 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new inflection rules using the following format
+# (all these examples are active by default):
+# ActiveSupport::Inflector.inflections do |inflect|
+# inflect.plural /^(ox)$/i, '\1en'
+# inflect.singular /^(ox)en/i, '\1'
+# inflect.irregular 'person', 'people'
+# inflect.uncountable %w( fish sheep )
+# end
+#
+# These inflection rules are supported but not enabled by default:
+# ActiveSupport::Inflector.inflections do |inflect|
+# inflect.acronym 'RESTful'
+# end
diff --git a/vendor/impressionist/test_app/config/initializers/mime_types.rb b/vendor/impressionist/test_app/config/initializers/mime_types.rb
new file mode 100644
index 00000000..72aca7e4
--- /dev/null
+++ b/vendor/impressionist/test_app/config/initializers/mime_types.rb
@@ -0,0 +1,5 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new mime types for use in respond_to blocks:
+# Mime::Type.register "text/richtext", :rtf
+# Mime::Type.register_alias "text/html", :iphone
diff --git a/vendor/impressionist/test_app/config/initializers/secret_token.rb b/vendor/impressionist/test_app/config/initializers/secret_token.rb
new file mode 100644
index 00000000..30f90d45
--- /dev/null
+++ b/vendor/impressionist/test_app/config/initializers/secret_token.rb
@@ -0,0 +1,7 @@
+# Be sure to restart your server when you modify this file.
+
+# Your secret key for verifying the integrity of signed cookies.
+# If you change this key, all old signed cookies 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.
+TestApp::Application.config.secret_token = '4a6fd2eb397985331d209be32073259ed7c25aef4fafcabb00e483ee548e592322277eb15459bdb257b65f31146eda92684b3e7a98ea1b2dfad9b0d08ab62e10'
diff --git a/vendor/impressionist/test_app/config/initializers/session_store.rb b/vendor/impressionist/test_app/config/initializers/session_store.rb
new file mode 100644
index 00000000..8188ba2d
--- /dev/null
+++ b/vendor/impressionist/test_app/config/initializers/session_store.rb
@@ -0,0 +1,8 @@
+# Be sure to restart your server when you modify this file.
+
+TestApp::Application.config.session_store :cookie_store, :key => '_test_app_session'
+
+# 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 "rails generate session_migration")
+# TestApp::Application.config.session_store :active_record_store
diff --git a/vendor/impressionist/test_app/config/initializers/wrap_parameters.rb b/vendor/impressionist/test_app/config/initializers/wrap_parameters.rb
new file mode 100644
index 00000000..da4fb076
--- /dev/null
+++ b/vendor/impressionist/test_app/config/initializers/wrap_parameters.rb
@@ -0,0 +1,14 @@
+# Be sure to restart your server when you modify this file.
+#
+# This file contains settings for ActionController::ParamsWrapper which
+# is enabled by default.
+
+# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
+ActiveSupport.on_load(:action_controller) do
+ wrap_parameters :format => [:json]
+end
+
+# Disable root element in JSON by default.
+ActiveSupport.on_load(:active_record) do
+ self.include_root_in_json = false
+end
diff --git a/vendor/impressionist/test_app/config/locales/en.yml b/vendor/impressionist/test_app/config/locales/en.yml
new file mode 100644
index 00000000..179c14ca
--- /dev/null
+++ b/vendor/impressionist/test_app/config/locales/en.yml
@@ -0,0 +1,5 @@
+# Sample localization file for English. Add more files in this directory for other locales.
+# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
+
+en:
+ hello: "Hello world"
diff --git a/vendor/impressionist/test_app/config/routes.rb b/vendor/impressionist/test_app/config/routes.rb
new file mode 100644
index 00000000..7bcb78ea
--- /dev/null
+++ b/vendor/impressionist/test_app/config/routes.rb
@@ -0,0 +1,3 @@
+TestApp::Application.routes.draw do
+ resources :articles, :posts, :widgets
+end
diff --git a/vendor/impressionist/test_app/db/migrate/20110201153144_create_articles.rb b/vendor/impressionist/test_app/db/migrate/20110201153144_create_articles.rb
new file mode 100644
index 00000000..66f46ef4
--- /dev/null
+++ b/vendor/impressionist/test_app/db/migrate/20110201153144_create_articles.rb
@@ -0,0 +1,13 @@
+class CreateArticles < ActiveRecord::Migration
+ def self.up
+ create_table :articles do |t|
+ t.string :name
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :articles
+ end
+end
diff --git a/vendor/impressionist/test_app/db/migrate/20110210205028_create_posts.rb b/vendor/impressionist/test_app/db/migrate/20110210205028_create_posts.rb
new file mode 100644
index 00000000..f018e1ce
--- /dev/null
+++ b/vendor/impressionist/test_app/db/migrate/20110210205028_create_posts.rb
@@ -0,0 +1,13 @@
+class CreatePosts < ActiveRecord::Migration
+ def self.up
+ create_table :posts do |t|
+ t.string :name
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :posts
+ end
+end
diff --git a/vendor/impressionist/test_app/db/migrate/20111127184039_create_widgets.rb b/vendor/impressionist/test_app/db/migrate/20111127184039_create_widgets.rb
new file mode 100644
index 00000000..5cb1fe2c
--- /dev/null
+++ b/vendor/impressionist/test_app/db/migrate/20111127184039_create_widgets.rb
@@ -0,0 +1,15 @@
+class CreateWidgets < ActiveRecord::Migration
+ def self.up
+ create_table :widgets do |t|
+ t.string :name
+ t.integer :impressions_count
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :widgets
+ end
+end
+
diff --git a/vendor/impressionist/test_app/db/seeds.rb b/vendor/impressionist/test_app/db/seeds.rb
new file mode 100644
index 00000000..d34dfa02
--- /dev/null
+++ b/vendor/impressionist/test_app/db/seeds.rb
@@ -0,0 +1,7 @@
+# This file should contain all the record creation needed to seed the database with its default values.
+# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
+#
+# Examples:
+#
+# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
+# Mayor.create(:name => 'Emanuel', :city => cities.first)
diff --git a/vendor/impressionist/test_app/lib/assets/.gitkeep b/vendor/impressionist/test_app/lib/assets/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/vendor/impressionist/test_app/lib/tasks/.gitkeep b/vendor/impressionist/test_app/lib/tasks/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/vendor/impressionist/test_app/lib/tasks/cucumber.rake b/vendor/impressionist/test_app/lib/tasks/cucumber.rake
new file mode 100644
index 00000000..982054e2
--- /dev/null
+++ b/vendor/impressionist/test_app/lib/tasks/cucumber.rake
@@ -0,0 +1,53 @@
+# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
+# It is recommended to regenerate this file in the future when you upgrade to a
+# newer version of cucumber-rails. Consider adding your own code to a new file
+# instead of editing this one. Cucumber will automatically load all features/**/*.rb
+# files.
+
+
+unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks
+
+vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first
+$LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil?
+
+begin
+ require 'cucumber/rake/task'
+
+ namespace :cucumber do
+ Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t|
+ t.binary = vendored_cucumber_bin # If nil, the gem's binary is used.
+ t.fork = true # You may get faster startup if you set this to false
+ t.profile = 'default'
+ end
+
+ Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t|
+ t.binary = vendored_cucumber_bin
+ t.fork = true # You may get faster startup if you set this to false
+ t.profile = 'wip'
+ end
+
+ Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t|
+ t.binary = vendored_cucumber_bin
+ t.fork = true # You may get faster startup if you set this to false
+ t.profile = 'rerun'
+ end
+
+ desc 'Run all features'
+ task :all => [:ok, :wip]
+ end
+ desc 'Alias for cucumber:ok'
+ task :cucumber => 'cucumber:ok'
+
+ task :default => :cucumber
+
+ task :features => :cucumber do
+ STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***"
+ end
+rescue LoadError
+ desc 'cucumber rake task not available (cucumber not installed)'
+ task :cucumber do
+ abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin'
+ end
+end
+
+end
diff --git a/vendor/impressionist/test_app/log/.gitkeep b/vendor/impressionist/test_app/log/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/vendor/impressionist/test_app/public/404.html b/vendor/impressionist/test_app/public/404.html
new file mode 100644
index 00000000..9a48320a
--- /dev/null
+++ b/vendor/impressionist/test_app/public/404.html
@@ -0,0 +1,26 @@
+
+
+
+ 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.
+
+
+
diff --git a/vendor/impressionist/test_app/public/422.html b/vendor/impressionist/test_app/public/422.html
new file mode 100644
index 00000000..83660ab1
--- /dev/null
+++ b/vendor/impressionist/test_app/public/422.html
@@ -0,0 +1,26 @@
+
+
+
+ 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.
+
+
+
diff --git a/vendor/impressionist/test_app/public/500.html b/vendor/impressionist/test_app/public/500.html
new file mode 100644
index 00000000..f3648a0d
--- /dev/null
+++ b/vendor/impressionist/test_app/public/500.html
@@ -0,0 +1,25 @@
+
+
+
+ We're sorry, but something went wrong (500)
+
+
+
+
+
+
+
We're sorry, but something went wrong.
+
+
+
diff --git a/vendor/impressionist/test_app/public/favicon.ico b/vendor/impressionist/test_app/public/favicon.ico
new file mode 100644
index 00000000..e69de29b
diff --git a/vendor/impressionist/test_app/public/images/rails.png b/vendor/impressionist/test_app/public/images/rails.png
new file mode 100644
index 00000000..d5edc04e
Binary files /dev/null and b/vendor/impressionist/test_app/public/images/rails.png differ
diff --git a/vendor/impressionist/test_app/public/index.html b/vendor/impressionist/test_app/public/index.html
new file mode 100644
index 00000000..a1d50995
--- /dev/null
+++ b/vendor/impressionist/test_app/public/index.html
@@ -0,0 +1,241 @@
+
+
+
+ Ruby on Rails: Welcome aboard
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Getting started
+
Here’s how to get rolling:
+
+
+ -
+
Use rails generate
to create your models and controllers
+ To see all available options, run it without parameters.
+
+
+ -
+
Set up a default route and remove public/index.html
+ Routes are set up in config/routes.rb.
+
+
+ -
+
Create your database
+ Run rake db:create
to create your database. If you're not using SQLite (the default), edit config/database.yml with your username and password.
+
+
+
+
+
+
+
+
+
diff --git a/vendor/impressionist/test_app/public/javascripts/application.js b/vendor/impressionist/test_app/public/javascripts/application.js
new file mode 100644
index 00000000..fe457769
--- /dev/null
+++ b/vendor/impressionist/test_app/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/impressionist/test_app/public/javascripts/controls.js b/vendor/impressionist/test_app/public/javascripts/controls.js
new file mode 100644
index 00000000..75d1bed7
--- /dev/null
+++ b/vendor/impressionist/test_app/public/javascripts/controls.js
@@ -0,0 +1,965 @@
+// script.aculo.us controls.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
+
+// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// (c) 2005-2009 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
+// (c) 2005-2009 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 = Class.create({
+ 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;
+ this.oldElementValue = this.element.value;
+
+ 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);
+ // Force carriage returns as token delimiters anyway
+ if (!this.options.tokens.include('\n'))
+ this.options.tokens.push('\n');
+
+ 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, 'keydown', this.onKeyPress.bindAsEventListener(this));
+ },
+
+ 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();
+ Event.stop(event);
+ return;
+ case Event.KEY_DOWN:
+ this.markNext();
+ this.render();
+ 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 = $(selectedElement).select('.' + this.options.select) || [];
+ if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
+ } else
+ value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
+
+ var bounds = this.getTokenBounds();
+ if (bounds[0] != -1) {
+ var newValue = this.element.value.substr(0, bounds[0]);
+ var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
+ if (whitespace)
+ newValue += whitespace[0];
+ this.element.value = newValue + value + this.element.value.substr(bounds[1]);
+ } else {
+ this.element.value = value;
+ }
+ this.oldElementValue = this.element.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;
+ this.tokenBounds = null;
+ if(this.getToken().length>=this.options.minChars) {
+ this.getUpdatedChoices();
+ } else {
+ this.active = false;
+ this.hide();
+ }
+ this.oldElementValue = this.element.value;
+ },
+
+ getToken: function() {
+ var bounds = this.getTokenBounds();
+ return this.element.value.substring(bounds[0], bounds[1]).strip();
+ },
+
+ getTokenBounds: function() {
+ if (null != this.tokenBounds) return this.tokenBounds;
+ var value = this.element.value;
+ if (value.strip().empty()) return [-1, 0];
+ var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
+ var offset = (diff == this.oldElementValue.length ? 1 : 0);
+ var prevTokenPos = -1, nextTokenPos = value.length;
+ var tp;
+ for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
+ tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
+ if (tp > prevTokenPos) prevTokenPos = tp;
+ tp = value.indexOf(this.options.tokens[index], diff + offset);
+ if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
+ }
+ return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
+ }
+});
+
+Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
+ var boundary = Math.min(newS.length, oldS.length);
+ for (var index = 0; index < boundary; ++index)
+ if (newS[index] != oldS[index])
+ return index;
+ return boundary;
+};
+
+Ajax.Autocompleter = Class.create(Autocompleter.Base, {
+ 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.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 "";
+ }
+ }, options || { });
+ }
+});
+
+// AJAX in-place editor and collection editor
+// Full rewrite by Christophe Porteneuve (April 2007).
+
+// 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({
+ initialize: function(element, url, options) {
+ this.url = url;
+ this.element = element = $(element);
+ this.prepareOptions();
+ this._controls = { };
+ arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
+ Object.extend(this.options, options || { });
+ if (!this.options.formId && this.element.id) {
+ this.options.formId = this.element.id + '-inplaceeditor';
+ if ($(this.options.formId))
+ this.options.formId = '';
+ }
+ if (this.options.externalControl)
+ this.options.externalControl = $(this.options.externalControl);
+ if (!this.options.externalControl)
+ this.options.externalControlOnly = false;
+ this._originalBackground = this.element.getStyle('background-color') || 'transparent';
+ this.element.title = this.options.clickToEditText;
+ this._boundCancelHandler = this.handleFormCancellation.bind(this);
+ this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
+ this._boundFailureHandler = this.handleAJAXFailure.bind(this);
+ this._boundSubmitHandler = this.handleFormSubmission.bind(this);
+ this._boundWrapperHandler = this.wrapUp.bind(this);
+ this.registerListeners();
+ },
+ checkForEscapeOrReturn: function(e) {
+ if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
+ if (Event.KEY_ESC == e.keyCode)
+ this.handleFormCancellation(e);
+ else if (Event.KEY_RETURN == e.keyCode)
+ this.handleFormSubmission(e);
+ },
+ createControl: function(mode, handler, extraClasses) {
+ var control = this.options[mode + 'Control'];
+ var text = this.options[mode + 'Text'];
+ if ('button' == control) {
+ var btn = document.createElement('input');
+ btn.type = 'submit';
+ btn.value = text;
+ btn.className = 'editor_' + mode + '_button';
+ if ('cancel' == mode)
+ btn.onclick = this._boundCancelHandler;
+ this._form.appendChild(btn);
+ this._controls[mode] = btn;
+ } else if ('link' == control) {
+ var link = document.createElement('a');
+ link.href = '#';
+ link.appendChild(document.createTextNode(text));
+ link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
+ link.className = 'editor_' + mode + '_link';
+ if (extraClasses)
+ link.className += ' ' + extraClasses;
+ this._form.appendChild(link);
+ this._controls[mode] = link;
+ }
+ },
+ createEditField: function() {
+ var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
+ var fld;
+ if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
+ fld = document.createElement('input');
+ fld.type = 'text';
+ var size = this.options.size || this.options.cols || 0;
+ if (0 < size) fld.size = size;
+ } else {
+ fld = document.createElement('textarea');
+ fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
+ fld.cols = this.options.cols || 40;
+ }
+ fld.name = this.options.paramName;
+ fld.value = text; // No HTML breaks conversion anymore
+ fld.className = 'editor_field';
+ if (this.options.submitOnBlur)
+ fld.onblur = this._boundSubmitHandler;
+ this._controls.editor = fld;
+ if (this.options.loadTextURL)
+ this.loadExternalText();
+ this._form.appendChild(this._controls.editor);
+ },
+ createForm: function() {
+ var ipe = this;
+ function addText(mode, condition) {
+ var text = ipe.options['text' + mode + 'Controls'];
+ if (!text || condition === false) return;
+ ipe._form.appendChild(document.createTextNode(text));
+ };
+ this._form = $(document.createElement('form'));
+ this._form.id = this.options.formId;
+ this._form.addClassName(this.options.formClassName);
+ this._form.onsubmit = this._boundSubmitHandler;
+ this.createEditField();
+ if ('textarea' == this._controls.editor.tagName.toLowerCase())
+ this._form.appendChild(document.createElement('br'));
+ if (this.options.onFormCustomization)
+ this.options.onFormCustomization(this, this._form);
+ addText('Before', this.options.okControl || this.options.cancelControl);
+ this.createControl('ok', this._boundSubmitHandler);
+ addText('Between', this.options.okControl && this.options.cancelControl);
+ this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
+ addText('After', this.options.okControl || this.options.cancelControl);
+ },
+ destroy: function() {
+ if (this._oldInnerHTML)
+ this.element.innerHTML = this._oldInnerHTML;
+ this.leaveEditMode();
+ this.unregisterListeners();
+ },
+ enterEditMode: function(e) {
+ if (this._saving || this._editing) return;
+ this._editing = true;
+ this.triggerCallback('onEnterEditMode');
+ if (this.options.externalControl)
+ this.options.externalControl.hide();
+ this.element.hide();
+ this.createForm();
+ this.element.parentNode.insertBefore(this._form, this.element);
+ if (!this.options.loadTextURL)
+ this.postProcessEditField();
+ if (e) Event.stop(e);
+ },
+ enterHover: function(e) {
+ if (this.options.hoverClassName)
+ this.element.addClassName(this.options.hoverClassName);
+ if (this._saving) return;
+ this.triggerCallback('onEnterHover');
+ },
+ getText: function() {
+ return this.element.innerHTML.unescapeHTML();
+ },
+ handleAJAXFailure: function(transport) {
+ this.triggerCallback('onFailure', transport);
+ if (this._oldInnerHTML) {
+ this.element.innerHTML = this._oldInnerHTML;
+ this._oldInnerHTML = null;
+ }
+ },
+ handleFormCancellation: function(e) {
+ this.wrapUp();
+ if (e) Event.stop(e);
+ },
+ handleFormSubmission: function(e) {
+ var form = this._form;
+ var value = $F(this._controls.editor);
+ this.prepareSubmission();
+ var params = this.options.callback(form, value) || '';
+ if (Object.isString(params))
+ params = params.toQueryParams();
+ params.editorId = this.element.id;
+ if (this.options.htmlResponse) {
+ var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
+ Object.extend(options, {
+ parameters: params,
+ onComplete: this._boundWrapperHandler,
+ onFailure: this._boundFailureHandler
+ });
+ new Ajax.Updater({ success: this.element }, this.url, options);
+ } else {
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+ Object.extend(options, {
+ parameters: params,
+ onComplete: this._boundWrapperHandler,
+ onFailure: this._boundFailureHandler
+ });
+ new Ajax.Request(this.url, options);
+ }
+ if (e) Event.stop(e);
+ },
+ leaveEditMode: function() {
+ this.element.removeClassName(this.options.savingClassName);
+ this.removeForm();
+ this.leaveHover();
+ this.element.style.backgroundColor = this._originalBackground;
+ this.element.show();
+ if (this.options.externalControl)
+ this.options.externalControl.show();
+ this._saving = false;
+ this._editing = false;
+ this._oldInnerHTML = null;
+ this.triggerCallback('onLeaveEditMode');
+ },
+ leaveHover: function(e) {
+ if (this.options.hoverClassName)
+ this.element.removeClassName(this.options.hoverClassName);
+ if (this._saving) return;
+ this.triggerCallback('onLeaveHover');
+ },
+ loadExternalText: function() {
+ this._form.addClassName(this.options.loadingClassName);
+ this._controls.editor.disabled = true;
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+ Object.extend(options, {
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
+ onComplete: Prototype.emptyFunction,
+ onSuccess: function(transport) {
+ this._form.removeClassName(this.options.loadingClassName);
+ var text = transport.responseText;
+ if (this.options.stripLoadedTextTags)
+ text = text.stripTags();
+ this._controls.editor.value = text;
+ this._controls.editor.disabled = false;
+ this.postProcessEditField();
+ }.bind(this),
+ onFailure: this._boundFailureHandler
+ });
+ new Ajax.Request(this.options.loadTextURL, options);
+ },
+ postProcessEditField: function() {
+ var fpc = this.options.fieldPostCreation;
+ if (fpc)
+ $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
+ },
+ prepareOptions: function() {
+ this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
+ Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
+ [this._extraDefaultOptions].flatten().compact().each(function(defs) {
+ Object.extend(this.options, defs);
+ }.bind(this));
+ },
+ prepareSubmission: function() {
+ this._saving = true;
+ this.removeForm();
+ this.leaveHover();
+ this.showSaving();
+ },
+ registerListeners: function() {
+ this._listeners = { };
+ var listener;
+ $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
+ listener = this[pair.value].bind(this);
+ this._listeners[pair.key] = listener;
+ if (!this.options.externalControlOnly)
+ this.element.observe(pair.key, listener);
+ if (this.options.externalControl)
+ this.options.externalControl.observe(pair.key, listener);
+ }.bind(this));
+ },
+ removeForm: function() {
+ if (!this._form) return;
+ this._form.remove();
+ this._form = null;
+ this._controls = { };
+ },
+ showSaving: function() {
+ this._oldInnerHTML = this.element.innerHTML;
+ this.element.innerHTML = this.options.savingText;
+ this.element.addClassName(this.options.savingClassName);
+ this.element.style.backgroundColor = this._originalBackground;
+ this.element.show();
+ },
+ triggerCallback: function(cbName, arg) {
+ if ('function' == typeof this.options[cbName]) {
+ this.options[cbName](this, arg);
+ }
+ },
+ unregisterListeners: function() {
+ $H(this._listeners).each(function(pair) {
+ if (!this.options.externalControlOnly)
+ this.element.stopObserving(pair.key, pair.value);
+ if (this.options.externalControl)
+ this.options.externalControl.stopObserving(pair.key, pair.value);
+ }.bind(this));
+ },
+ wrapUp: function(transport) {
+ this.leaveEditMode();
+ // Can't use triggerCallback due to backward compatibility: requires
+ // binding + direct element
+ this._boundComplete(transport, this.element);
+ }
+});
+
+Object.extend(Ajax.InPlaceEditor.prototype, {
+ dispose: Ajax.InPlaceEditor.prototype.destroy
+});
+
+Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
+ initialize: function($super, element, url, options) {
+ this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
+ $super(element, url, options);
+ },
+
+ createEditField: function() {
+ var list = document.createElement('select');
+ list.name = this.options.paramName;
+ list.size = 1;
+ this._controls.editor = list;
+ this._collection = this.options.collection || [];
+ if (this.options.loadCollectionURL)
+ this.loadCollection();
+ else
+ this.checkForExternalText();
+ this._form.appendChild(this._controls.editor);
+ },
+
+ loadCollection: function() {
+ this._form.addClassName(this.options.loadingClassName);
+ this.showLoadingText(this.options.loadingCollectionText);
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+ Object.extend(options, {
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
+ onComplete: Prototype.emptyFunction,
+ onSuccess: function(transport) {
+ var js = transport.responseText.strip();
+ if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
+ throw('Server returned an invalid collection representation.');
+ this._collection = eval(js);
+ this.checkForExternalText();
+ }.bind(this),
+ onFailure: this.onFailure
+ });
+ new Ajax.Request(this.options.loadCollectionURL, options);
+ },
+
+ showLoadingText: function(text) {
+ this._controls.editor.disabled = true;
+ var tempOption = this._controls.editor.firstChild;
+ if (!tempOption) {
+ tempOption = document.createElement('option');
+ tempOption.value = '';
+ this._controls.editor.appendChild(tempOption);
+ tempOption.selected = true;
+ }
+ tempOption.update((text || '').stripScripts().stripTags());
+ },
+
+ checkForExternalText: function() {
+ this._text = this.getText();
+ if (this.options.loadTextURL)
+ this.loadExternalText();
+ else
+ this.buildOptionList();
+ },
+
+ loadExternalText: function() {
+ this.showLoadingText(this.options.loadingText);
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+ Object.extend(options, {
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
+ onComplete: Prototype.emptyFunction,
+ onSuccess: function(transport) {
+ this._text = transport.responseText.strip();
+ this.buildOptionList();
+ }.bind(this),
+ onFailure: this.onFailure
+ });
+ new Ajax.Request(this.options.loadTextURL, options);
+ },
+
+ buildOptionList: function() {
+ this._form.removeClassName(this.options.loadingClassName);
+ this._collection = this._collection.map(function(entry) {
+ return 2 === entry.length ? entry : [entry, entry].flatten();
+ });
+ var marker = ('value' in this.options) ? this.options.value : this._text;
+ var textFound = this._collection.any(function(entry) {
+ return entry[0] == marker;
+ }.bind(this));
+ this._controls.editor.update('');
+ var option;
+ this._collection.each(function(entry, index) {
+ option = document.createElement('option');
+ option.value = entry[0];
+ option.selected = textFound ? entry[0] == marker : 0 == index;
+ option.appendChild(document.createTextNode(entry[1]));
+ this._controls.editor.appendChild(option);
+ }.bind(this));
+ this._controls.editor.disabled = false;
+ Field.scrollFreeActivate(this._controls.editor);
+ }
+});
+
+//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
+//**** This only exists for a while, in order to let ****
+//**** users adapt to the new API. Read up on the new ****
+//**** API and convert your code to it ASAP! ****
+
+Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
+ if (!options) return;
+ function fallback(name, expr) {
+ if (name in options || expr === undefined) return;
+ options[name] = expr;
+ };
+ fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
+ options.cancelLink == options.cancelButton == false ? false : undefined)));
+ fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
+ options.okLink == options.okButton == false ? false : undefined)));
+ fallback('highlightColor', options.highlightcolor);
+ fallback('highlightEndColor', options.highlightendcolor);
+};
+
+Object.extend(Ajax.InPlaceEditor, {
+ DefaultOptions: {
+ ajaxOptions: { },
+ autoRows: 3, // Use when multi-line w/ rows == 1
+ cancelControl: 'link', // 'link'|'button'|false
+ cancelText: 'cancel',
+ clickToEditText: 'Click to edit',
+ externalControl: null, // id|elt
+ externalControlOnly: false,
+ fieldPostCreation: 'activate', // 'activate'|'focus'|false
+ formClassName: 'inplaceeditor-form',
+ formId: null, // id|elt
+ highlightColor: '#ffff99',
+ highlightEndColor: '#ffffff',
+ hoverClassName: '',
+ htmlResponse: true,
+ loadingClassName: 'inplaceeditor-loading',
+ loadingText: 'Loading...',
+ okControl: 'button', // 'link'|'button'|false
+ okText: 'ok',
+ paramName: 'value',
+ rows: 1, // If 1 and multi-line, uses autoRows
+ savingClassName: 'inplaceeditor-saving',
+ savingText: 'Saving...',
+ size: 0,
+ stripLoadedTextTags: false,
+ submitOnBlur: false,
+ textAfterControls: '',
+ textBeforeControls: '',
+ textBetweenControls: ''
+ },
+ DefaultCallbacks: {
+ callback: function(form) {
+ return Form.serialize(form);
+ },
+ onComplete: function(transport, element) {
+ // For backward compatibility, this one is bound to the IPE, and passes
+ // the element directly. It was too often customized, so we don't break it.
+ new Effect.Highlight(element, {
+ startcolor: this.options.highlightColor, keepBackgroundImage: true });
+ },
+ onEnterEditMode: null,
+ onEnterHover: function(ipe) {
+ ipe.element.style.backgroundColor = ipe.options.highlightColor;
+ if (ipe._effect)
+ ipe._effect.cancel();
+ },
+ onFailure: function(transport, ipe) {
+ alert('Error communication with the server: ' + transport.responseText.stripTags());
+ },
+ onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
+ onLeaveEditMode: null,
+ onLeaveHover: function(ipe) {
+ ipe._effect = new Effect.Highlight(ipe.element, {
+ startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
+ restorecolor: ipe._originalBackground, keepBackgroundImage: true
+ });
+ }
+ },
+ Listeners: {
+ click: 'enterEditMode',
+ keydown: 'checkForEscapeOrReturn',
+ mouseover: 'enterHover',
+ mouseout: 'leaveHover'
+ }
+});
+
+Ajax.InPlaceCollectionEditor.DefaultOptions = {
+ loadingCollectionText: 'Loading options...'
+};
+
+// Delayed observer, like Form.Element.Observer,
+// but waits for delay after last key input
+// Ideal for live-search fields
+
+Form.Element.DelayedObserver = Class.create({
+ 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/impressionist/test_app/public/javascripts/dragdrop.js b/vendor/impressionist/test_app/public/javascripts/dragdrop.js
new file mode 100644
index 00000000..579715ec
--- /dev/null
+++ b/vendor/impressionist/test_app/public/javascripts/dragdrop.js
@@ -0,0 +1,974 @@
+// script.aculo.us dragdrop.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
+
+// Copyright (c) 2005-2009 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/
+
+if(Object.isUndefined(Effect))
+ 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(Object.isArray(containment)) {
+ 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 drop, affected = [];
+
+ this.drops.each( function(drop) {
+ if(Droppables.isAffected(point, element, drop))
+ affected.push(drop);
+ });
+
+ if(affected.length>0)
+ drop = Droppables.findDeepestChild(affected);
+
+ if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
+ if (drop) {
+ Position.within(drop.element, point[0], point[1]);
+ if(drop.onHover)
+ drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
+
+ if (drop != this.last_active) 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({
+ 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 = Object.isNumber(element._opacity) ? 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] || Object.isUndefined(arguments[1].endeffect))
+ 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 && Object.isString(options.handle))
+ 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.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(!Object.isUndefined(Draggable._dragging[this.element]) &&
+ 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 = this.element.cumulativeOffset();
+ 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.delta)
+ this.delta = this.currentDelta();
+
+ 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);
+ this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
+ if (!this._originallyAbsolute)
+ 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) {
+ if (!this._originallyAbsolute)
+ Position.relativize(this.element);
+ delete this._originallyAbsolute;
+ 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 && Object.isFunction(revert)) 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 = this.element.cumulativeOffset();
+ 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(Object.isFunction(this.options.snap)) {
+ p = this.options.snap(p[0],p[1],this);
+ } else {
+ if(Object.isArray(this.options.snap)) {
+ p = p.map( function(v, i) {
+ return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));
+ } else {
+ p = p.map( function(v) {
+ return (v/this.options.snap).round()*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 };
+ }
+});
+
+Draggable._dragging = { };
+
+/*--------------------------------------------------------------------------*/
+
+var SortableObserver = Class.create({
+ 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){
+ element = $(element);
+ var s = Sortable.sortables[element.id];
+
+ 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).select('.' + 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.identify()] = 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 = dropon.cumulativeOffset();
+ 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/impressionist/test_app/public/javascripts/effects.js b/vendor/impressionist/test_app/public/javascripts/effects.js
new file mode 100644
index 00000000..c977462f
--- /dev/null
+++ b/vendor/impressionist/test_app/public/javascripts/effects.js
@@ -0,0 +1,1123 @@
+// script.aculo.us effects.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
+
+// Copyright (c) 2005-2009 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) { }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Effect = {
+ _elementDoesNotExistError: {
+ name: 'ElementDoesNotExistError',
+ message: 'The specified DOM element does not exist, but is required for this effect to operate'
+ },
+ Transitions: {
+ linear: Prototype.K,
+ sinoidal: function(pos) {
+ return (-Math.cos(pos*Math.PI)/2) + .5;
+ },
+ reverse: function(pos) {
+ return 1-pos;
+ },
+ flicker: function(pos) {
+ var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4;
+ return pos > 1 ? 1 : pos;
+ },
+ wobble: function(pos) {
+ return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5;
+ },
+ pulse: function(pos, pulses) {
+ return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;
+ },
+ spring: function(pos) {
+ return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
+ },
+ none: function(pos) {
+ return 0;
+ },
+ full: function(pos) {
+ return 1;
+ }
+ },
+ DefaultOptions: {
+ duration: 1.0, // seconds
+ fps: 100, // 100= assume 66fps max.
+ sync: false, // true for combining
+ from: 0.0,
+ to: 1.0,
+ delay: 0.0,
+ queue: 'parallel'
+ },
+ tagifyText: function(element) {
+ 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(
+ new Element('span', {style: tagifyStyle}).update(
+ character == ' ' ? String.fromCharCode(160) : character),
+ child);
+ });
+ Element.remove(child);
+ }
+ });
+ },
+ multiple: function(element, effect) {
+ var elements;
+ if (((typeof element == 'object') ||
+ Object.isFunction(element)) &&
+ (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, options) {
+ element = $(element);
+ effect = (effect || 'appear').toLowerCase();
+
+ return Effect[ Effect.PAIRS[ effect ][ element.visible() ? 1 : 0 ] ](element, Object.extend({
+ queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
+ }, options || {}));
+ }
+};
+
+Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
+
+/* ------------- core effects ------------- */
+
+Effect.ScopedQueue = Class.create(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 = Object.isString(effect.options.queue) ?
+ 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 = (pos * this.totalFrames).round();
+ if (frame > this.currentFrame) {
+ this.render(pos);
+ this.currentFrame = frame;
+ }
+ }
+ },
+ cancel: function() {
+ if (!this.options.sync)
+ Effect.Queues.get(Object.isString(this.options.queue) ?
+ '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 (!Object.isFunction(this[property])) data.set(property, this[property]);
+ return '#';
+ }
+});
+
+Effect.Parallel = Class.create(Effect.Base, {
+ 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.Tween = Class.create(Effect.Base, {
+ initialize: function(object, from, to) {
+ object = Object.isString(object) ? $(object) : object;
+ var args = $A(arguments), method = args.last(),
+ options = args.length == 5 ? args[3] : null;
+ this.method = Object.isFunction(method) ? method.bind(object) :
+ Object.isFunction(object[method]) ? object[method].bind(object) :
+ function(value) { object[method] = value };
+ this.start(Object.extend({ from: from, to: to }, options || { }));
+ },
+ update: function(position) {
+ this.method(position);
+ }
+});
+
+Effect.Event = Class.create(Effect.Base, {
+ initialize: function() {
+ this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
+ },
+ update: Prototype.emptyFunction
+});
+
+Effect.Opacity = Class.create(Effect.Base, {
+ 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(Effect.Base, {
+ 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() {
+ this.element.makePositioned();
+ this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
+ this.originalTop = parseFloat(this.element.getStyle('top') || '0');
+ if (this.options.mode == 'absolute') {
+ 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).round() + 'px',
+ top: (this.options.y * position + this.originalTop).round() + '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(Effect.Base, {
+ 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 = width.round() + 'px';
+ if (this.options.scaleY) d.height = height.round() + '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(Effect.Base, {
+ 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+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
+ },
+ finish: function() {
+ this.element.setStyle(Object.extend(this.oldStyle, {
+ backgroundColor: this.options.restorecolor
+ }));
+ }
+});
+
+Effect.ScrollTo = function(element) {
+ var options = arguments[1] || { },
+ scrollOffsets = document.viewport.getScrollOffsets(),
+ elementOffsets = $(element).cumulativeOffset();
+
+ if (options.offset) elementOffsets[1] += options.offset;
+
+ return new Effect.Tween(null,
+ scrollOffsets.top,
+ elementOffsets[1],
+ options,
+ function(p){ scrollTo(scrollOffsets.left, p.round()); }
+ );
+};
+
+/* ------------- 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 options = Object.extend({
+ distance: 20,
+ duration: 0.5
+ }, arguments[1] || {});
+ var distance = parseFloat(options.distance);
+ var split = parseFloat(options.duration) / 10.0;
+ var oldStyle = {
+ top: element.getStyle('top'),
+ left: element.getStyle('left') };
+ return new Effect.Move(element,
+ { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) {
+ new Effect.Move(effect.element,
+ { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
+ new Effect.Move(effect.element,
+ { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
+ new Effect.Move(effect.element,
+ { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
+ new Effect.Move(effect.element,
+ { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
+ new Effect.Move(effect.element,
+ { x: -distance, y: 0, duration: split, 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');
+ var elementDimensions = element.getDimensions();
+ return new Effect.Scale(element, window.opera ? 0 : 1,
+ Object.extend({ scaleContent: false,
+ scaleX: false,
+ scaleMode: 'box',
+ scaleFrom: 100,
+ 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().show();
+ },
+ afterUpdateInternal: function(effect) {
+ effect.element.down().setStyle({bottom:
+ (effect.dims[0] - effect.element.clientHeight) + 'px' });
+ },
+ afterFinishInternal: function(effect) {
+ effect.element.hide().undoClipping().undoPositioned();
+ effect.element.down().undoPositioned().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();
+ },
+ 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] || { },
+ oldOpacity = element.getInlineOpacity(),
+ transition = options.transition || Effect.Transitions.linear,
+ reverser = function(pos){
+ return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5);
+ };
+
+ 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(Effect.Base, {
+ initialize: function(element) {
+ this.element = $(element);
+ if (!this.element) throw(Effect._elementDoesNotExistError);
+ var options = Object.extend({
+ style: { }
+ }, arguments[1] || { });
+
+ if (!Object.isString(options.style)) this.style = $H(options.style);
+ else {
+ if (options.style.include(':'))
+ this.style = options.style.parseStyle();
+ else {
+ this.element.addClassName(options.style);
+ this.style = $H(this.element.getStyles());
+ this.element.removeClassName(options.style);
+ var css = this.element.getStyles();
+ this.style = this.style.reject(function(style) {
+ return style.value == css[style.key];
+ });
+ options.afterFinishInternal = function(effect) {
+ effect.element.addClassName(effect.options.style);
+ effect.transforms.each(function(transform) {
+ effect.element.style[transform.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 +
+ (transform.targetValue - transform.originalValue) * position).toFixed(3) +
+ (transform.unit === null ? '' : transform.unit);
+ this.element.setStyle(style, true);
+ }
+});
+
+Effect.Transform = Class.create({
+ initialize: function(tracks){
+ this.tracks = [];
+ this.options = arguments[1] || { };
+ this.addTracks(tracks);
+ },
+ addTracks: function(tracks){
+ tracks.each(function(track){
+ track = $H(track);
+ var data = track.values().first();
+ this.tracks.push($H({
+ ids: 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 ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
+ var elements = [$(ids) || $$(ids)].flatten();
+ return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, 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.__parseStyleElement = document.createElement('div');
+String.prototype.parseStyle = function(){
+ var style, styleRules = $H();
+ if (Prototype.Browser.WebKit)
+ style = new Element('div',{style:this}).style;
+ else {
+ String.__parseStyleElement.innerHTML = '';
+ style = String.__parseStyleElement.childNodes[0].style;
+ }
+
+ Element.CSS_PROPERTIES.each(function(property){
+ if (style[property]) styleRules.set(property, style[property]);
+ });
+
+ if (Prototype.Browser.IE && this.include('opacity'))
+ styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
+
+ return styleRules;
+};
+
+if (document.defaultView && document.defaultView.getComputedStyle) {
+ Element.getStyles = function(element) {
+ var css = document.defaultView.getComputedStyle($(element), null);
+ return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
+ styles[property] = css[property];
+ return styles;
+ });
+ };
+} else {
+ Element.getStyles = function(element) {
+ element = $(element);
+ var css = element.currentStyle, styles;
+ styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
+ results[property] = css[property];
+ return results;
+ });
+ if (!styles.opacity) styles.opacity = element.getOpacity();
+ return styles;
+ };
+}
+
+Effect.Methods = {
+ morph: function(element, style) {
+ element = $(element);
+ new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
+ return element;
+ },
+ visualEffect: function(element, effect, options) {
+ element = $(element);
+ var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
+ new Effect[klass](element, options);
+ return element;
+ },
+ highlight: function(element, options) {
+ element = $(element);
+ new Effect.Highlight(element, options);
+ return element;
+ }
+};
+
+$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
+ 'pulsate shake puff squish switchOff dropOut').each(
+ function(effect) {
+ Effect.Methods[effect] = function(element, options){
+ element = $(element);
+ Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
+ return element;
+ };
+ }
+);
+
+$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
+ function(f) { Effect.Methods[f] = Element[f]; }
+);
+
+Element.addMethods(Effect.Methods);
diff --git a/vendor/impressionist/test_app/public/javascripts/prototype.js b/vendor/impressionist/test_app/public/javascripts/prototype.js
new file mode 100644
index 00000000..06249a6a
--- /dev/null
+++ b/vendor/impressionist/test_app/public/javascripts/prototype.js
@@ -0,0 +1,6001 @@
+/* Prototype JavaScript framework, version 1.7_rc2
+ * (c) 2005-2010 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.7_rc2',
+
+ Browser: (function(){
+ var ua = navigator.userAgent;
+ var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';
+ return {
+ IE: !!window.attachEvent && !isOpera,
+ Opera: isOpera,
+ WebKit: ua.indexOf('AppleWebKit/') > -1,
+ Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1,
+ MobileSafari: /Apple.*Mobile/.test(ua)
+ }
+ })(),
+
+ BrowserFeatures: {
+ XPath: !!document.evaluate,
+
+ SelectorsAPI: !!document.querySelector,
+
+ ElementExtensions: (function() {
+ var constructor = window.Element || window.HTMLElement;
+ return !!(constructor && constructor.prototype);
+ })(),
+ SpecificElementExtensions: (function() {
+ if (typeof window.HTMLDivElement !== 'undefined')
+ return true;
+
+ var div = document.createElement('div'),
+ form = document.createElement('form'),
+ isSupported = false;
+
+ if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) {
+ isSupported = true;
+ }
+
+ div = form = null;
+
+ return isSupported;
+ })()
+ },
+
+ ScriptFragment: '