mirror of
https://github.com/TracksApp/tracks.git
synced 2026-02-19 05:38:08 +01:00
Updated to svn tags/tracks-1.6
This commit is contained in:
parent
103fcb8049
commit
02496f2d44
2274 changed files with 0 additions and 0 deletions
204
vendor/gems/highline-1.4.0/CHANGELOG
vendored
Normal file
204
vendor/gems/highline-1.4.0/CHANGELOG
vendored
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
= Change Log
|
||||
|
||||
Below is a complete listing of changes for each revision of HighLine.
|
||||
|
||||
== 1.4.0
|
||||
|
||||
* Made the code grabbing terminal size a little more cross-platform by
|
||||
adding support for Solaris. (patch by Ronald Braswell and Coey Minear)
|
||||
|
||||
== 1.2.9
|
||||
|
||||
* Additional work on the backspacing issue. (patch by Jeremy Hinegardner)
|
||||
* Fixed Readline prompt bug. (patch by Jeremy Hinegardner)
|
||||
|
||||
== 1.2.8
|
||||
|
||||
* Fixed backspacing past the prompt and interrupting a prompt bugs.
|
||||
(patch by Jeremy Hinegardner)
|
||||
|
||||
== 1.2.7
|
||||
|
||||
* Fixed the stty indent bug.
|
||||
* Fixed the echo backspace bug.
|
||||
* Added HighLine::track_eof=() setting to work are threaded eof?() calls.
|
||||
|
||||
== 1.2.6
|
||||
|
||||
Patch by Jeremy Hinegardner:
|
||||
|
||||
* Added ColorScheme support.
|
||||
* Added HighLine::Question.overwrite mode.
|
||||
* Various documentation fixes.
|
||||
|
||||
== 1.2.5
|
||||
|
||||
* Really fixed the bug I tried to fix in 1.2.4.
|
||||
|
||||
== 1.2.4
|
||||
|
||||
* Fixed a crash causing bug when using menus, reported by Patrick Hof.
|
||||
|
||||
== 1.2.3
|
||||
|
||||
* Treat Cygwin like a Posix OS, instead of a native Windows environment.
|
||||
|
||||
== 1.2.2
|
||||
|
||||
* Minor documentation corrections.
|
||||
* Applied Thomas Werschleiln's patch to fix termio buffering on Solaris.
|
||||
* Applied Justin Bailey's patch to allow canceling paged output.
|
||||
* Fixed a documentation bug in the description of character case settings.
|
||||
* Added a notice about termios in HighLine::Question#echo.
|
||||
* Finally working around the infamous "fast typing" bug
|
||||
|
||||
== 1.2.1
|
||||
|
||||
* Applied Justin Bailey's fix for the page_print() infinite loop bug.
|
||||
* Made a SystemExtensions module to expose OS level functionality other
|
||||
libraries may want to access.
|
||||
* Publicly exposed the get_character() method, per user requests.
|
||||
* Added terminal_size(), output_cols(), and output_rows() methods.
|
||||
* Added :auto setting for warp_at=() and page_at=().
|
||||
|
||||
== 1.2.0
|
||||
|
||||
* Improved RubyForge and gem spec project descriptions.
|
||||
* Added basic examples to README.
|
||||
* Added a VERSION constant.
|
||||
* Added support for hidden menu commands.
|
||||
* Added Object.or_ask() when using highline/import.
|
||||
|
||||
== 1.0.4
|
||||
|
||||
* Moved the HighLine project to Subversion.
|
||||
* HighLine's color escapes can now be disabled.
|
||||
* Fixed EOF bug introduced in the last release.
|
||||
* Updated HighLine web page.
|
||||
* Moved to a forked development/stable version numbering.
|
||||
|
||||
== 1.0.2
|
||||
|
||||
* Removed old and broken help tests.
|
||||
* Fixed test case typo found by David A. Black.
|
||||
* Added ERb escapes processing to lists, for coloring list items. Color escapes
|
||||
do not add to list element size.
|
||||
* HighLine now throws EOFError when input is exhausted.
|
||||
|
||||
== 1.0.1
|
||||
|
||||
* Minor bug fix: Moved help initialization to before response building, so help
|
||||
would show up in the default responses.
|
||||
|
||||
== 1.0.0
|
||||
|
||||
* Fixed documentation typo pointed out by Gavin Kistner.
|
||||
* Added <tt>gather = ...</tt> option to question for fetching entire Arrays or
|
||||
Hashes filled with answers. You can set +gather+ to a count of answers to
|
||||
collect, a String or Regexp matching the end of input, or a Hash where each
|
||||
key can be used in a new question.
|
||||
* Added File support to HighLine.ask(). You can specify a _directory_ and a
|
||||
_glob_ pattern that combine into a list of file choices the user can select
|
||||
from. You can choose to receive the user's answer as an open filehandle or as
|
||||
a Pathname object.
|
||||
* Added Readline support for history and editing.
|
||||
* Added tab completion for menu and file selection selection (requires
|
||||
Readline).
|
||||
* Added an optional character limit for input.
|
||||
* Added a complete help system to HighLine's shell menu creation tools.
|
||||
|
||||
== 0.6.1
|
||||
|
||||
* Removed termios dependancy in gem, to fix Windows' install.
|
||||
|
||||
== 0.6.0
|
||||
|
||||
* Implemented HighLine.choose() for menu handling.
|
||||
* Provided shortcut <tt>choose(item1, item2, ...)</tt> for simple menus.
|
||||
* Allowed Ruby code to be attached to each menu item, to create a complete
|
||||
menu solution.
|
||||
* Provided for total customization of the menu layout.
|
||||
* Allowed for menu selection by index, name or both.
|
||||
* Added a _shell_ mode to allow menu selection with additional details
|
||||
following the name.
|
||||
* Added a list() utility method that can be invoked just like color(). It can
|
||||
layout Arrays for you in any output in the modes <tt>:columns_across</tt>,
|
||||
<tt>:columns_down</tt>, <tt>:inline</tt> and <tt>:rows</tt>
|
||||
* Added support for <tt>echo = "*"</tt> style settings. User code can now
|
||||
choose the echo character this way.
|
||||
* Modified HighLine to user the "termios" library for character input, if
|
||||
available. Will return to old behavior (using "stty"), if "termios" cannot be
|
||||
loaded.
|
||||
* Improved "stty" state restoring code.
|
||||
* Fixed "stty" code to handle interrupt signals.
|
||||
* Improved the default auto-complete error message and exposed this message
|
||||
through the +responses+ interface as <tt>:no_completion</tt>.
|
||||
|
||||
== 0.5.0
|
||||
|
||||
* Implemented <tt>echo = false</tt> for HighLine::Question objects, primarily to
|
||||
make fetching passwords trivial.
|
||||
* Fixed an auto-complete bug that could cause a crash when the user gave an
|
||||
answer that didn't complete to any valid choice.
|
||||
* Implemented +case+ for HighLine::Question objects to provide character case
|
||||
conversions on given answers. Can be set to <tt>:up</tt>, <tt>:down</tt>, or
|
||||
<tt>:capitalize</tt>.
|
||||
* Exposed <tt>@answer</tt> to the response system, to allow response that are
|
||||
aware of incorrect input.
|
||||
* Implemented +confirm+ for HighLine::Question objects to allow for verification
|
||||
for sensitive user choices. If set to +true+, user will have to answer an
|
||||
"Are you sure? " question. Can also be set to the question to confirm with
|
||||
the user.
|
||||
|
||||
== 0.4.0
|
||||
|
||||
* Added <tt>@wrap_at</tt> and <tt>@page_at</tt> settings and accessors to
|
||||
HighLine, to control text flow.
|
||||
* Implemented line wrapping with adjustable limit.
|
||||
* Implemented paged printing with adjustable limit.
|
||||
|
||||
== 0.3.0
|
||||
|
||||
* Added support for installing with setup.rb.
|
||||
* All output is now treated as an ERb sequence, allowing Ruby code to be
|
||||
embedded in output strings.
|
||||
* Added support for ANSI color sequences in say(). (And everything else
|
||||
by extension.)
|
||||
* Added whitespace handling for answers. Can be set to <tt>:strip</tt>,
|
||||
<tt>:chomp</tt>, <tt>:collapse</tt>, <tt>:strip_and_collapse</tt>,
|
||||
<tt>:chomp_and_collapse</tt>, <tt>:remove</tt>, or <tt>:none</tt>.
|
||||
* Exposed question details to ERb completion through @question, to allow for
|
||||
intelligent responses.
|
||||
* Simplified HighLine internals using @question.
|
||||
* Added support for fetching single character input either with getc() or
|
||||
HighLine's own cross-platform terminal input routine.
|
||||
* Improved type conversion to handle user defined classes.
|
||||
|
||||
== 0.2.0
|
||||
|
||||
* Added Unit Tests to cover an already fixed output bug in the future.
|
||||
* Added Rakefile and setup test action (default).
|
||||
* Renamed HighLine::Answer to HighLine::Question to better illustrate its role.
|
||||
* Renamed fetch_line() to get_response() to better define its goal.
|
||||
* Simplified explain_error in terms of the Question object.
|
||||
* Renamed accept?() to in_range?() to better define purpose.
|
||||
* Reworked valid?() into valid_answer?() to better fit Question object.
|
||||
* Reworked <tt>@member</tt> into <tt>@in</tt>, to make it easier to remember and
|
||||
switched implementation to include?().
|
||||
* Added range checks for @above and @below.
|
||||
* Fixed the bug causing ask() to swallow NoMethodErrors.
|
||||
* Rolled ask_on_error() into responses.
|
||||
* Redirected imports to Kernel from Object.
|
||||
* Added support for <tt>validate = lambda { ... }</tt>.
|
||||
* Added default answer support.
|
||||
* Fixed bug that caused ask() to die with an empty question.
|
||||
* Added complete documentation.
|
||||
* Improve the implemetation of agree() to be the intended "yes" or "no" only
|
||||
question.
|
||||
* Added Rake tasks for documentation and packaging.
|
||||
* Moved project to RubyForge.
|
||||
|
||||
== 0.1.0
|
||||
|
||||
* Initial release as the solution to
|
||||
{Ruby Quiz #29}[http://www.rubyquiz.com/quiz29.html].
|
||||
35
vendor/gems/highline-1.4.0/INSTALL
vendored
Normal file
35
vendor/gems/highline-1.4.0/INSTALL
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
= Installing HighLine
|
||||
|
||||
RubyGems is the preferred easy install method for HighLine. However, you can
|
||||
install HighLine manually as described below.
|
||||
|
||||
== Installing the Gem
|
||||
|
||||
HighLine is intended to be installed via the
|
||||
RubyGems[http://rubyforge.org/projects/rubygems/] system. To get the latest
|
||||
version, simply enter the following into your command prompt:
|
||||
|
||||
$ sudo gem install highline
|
||||
|
||||
You must have RubyGems[http://rubyforge.org/projects/rubygems/] installed for
|
||||
the above to work.
|
||||
|
||||
== Installing Manually
|
||||
|
||||
Download the latest version of HighLine from the
|
||||
{RubyForge project page}[http://rubyforge.org/frs/?group_id=683]. Navigate to
|
||||
the root project directory and enter:
|
||||
|
||||
$ sudo ruby setup.rb
|
||||
|
||||
== Using termios
|
||||
|
||||
While not a requirement, HighLine will take advantage of the termios library if
|
||||
installed (on Unix). This slightly improves HighLine's character reading
|
||||
capabilities and thus is recommended for all Unix users.
|
||||
|
||||
If using the HighLine gem, you should be able to add termios as easily as:
|
||||
|
||||
$ sudo gem install termios
|
||||
|
||||
For manual installs, consult the termios documentation.
|
||||
7
vendor/gems/highline-1.4.0/LICENSE
vendored
Normal file
7
vendor/gems/highline-1.4.0/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
= License Terms
|
||||
|
||||
Distributed under the user's choice of the {GPL Version 2}[http://www.gnu.org/licenses/old-licenses/gpl-2.0.html] (see COPYING for details) or the
|
||||
{Ruby software license}[http://www.ruby-lang.org/en/LICENSE.txt] by
|
||||
James Edward Gray II and Greg Brown.
|
||||
|
||||
Please email James[mailto:james@grayproductions.net] with any questions.
|
||||
63
vendor/gems/highline-1.4.0/README
vendored
Normal file
63
vendor/gems/highline-1.4.0/README
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
= Read Me
|
||||
|
||||
by James Edward Gray II
|
||||
|
||||
== Description
|
||||
|
||||
Welcome to HighLine.
|
||||
|
||||
HighLine was designed to ease the tedious tasks of doing console input and
|
||||
output with low-level methods like gets() and puts(). HighLine provides a
|
||||
robust system for requesting data from a user, without needing to code all the
|
||||
error checking and validation rules and without needing to convert the typed
|
||||
Strings into what your program really needs. Just tell HighLine what you're
|
||||
after, and let it do all the work.
|
||||
|
||||
== Documentation
|
||||
|
||||
See HighLine and HighLine::Question for documentation.
|
||||
|
||||
== Examples
|
||||
|
||||
Basic usage:
|
||||
|
||||
ask("Company? ") { |q| q.default = "none" }
|
||||
|
||||
Validation:
|
||||
|
||||
ask("Age? ", Integer) { |q| q.in = 0..105 }
|
||||
ask("Name? (last, first) ") { |q| q.validate = /\A\w+, ?\w+\Z/ }
|
||||
|
||||
Type conversion for answers:
|
||||
|
||||
ask("Birthday? ", Date)
|
||||
ask("Interests? (comma sep list) ", lambda { |str| str.split(/,\s*/) })
|
||||
|
||||
Reading passwords:
|
||||
|
||||
ask("Enter your password: ") { |q| q.echo = false }
|
||||
ask("Enter your password: ") { |q| q.echo = "x" }
|
||||
|
||||
ERb based output (with HighLine's ANSI color tools):
|
||||
|
||||
say("This should be <%= color('bold', BOLD) %>!")
|
||||
|
||||
Menus:
|
||||
|
||||
choose do |menu|
|
||||
menu.prompt = "Please choose your favorite programming language? "
|
||||
|
||||
menu.choice(:ruby) { say("Good choice!") }
|
||||
menu.choices(:python, :perl) { say("Not from around here, are you?") }
|
||||
end
|
||||
|
||||
For more examples see the examples/ directory of this project.
|
||||
|
||||
== Installing
|
||||
|
||||
See the INSTALL file for instructions.
|
||||
|
||||
== Questions and/or Comments
|
||||
|
||||
Feel free to email {James Edward Gray II}[mailto:james@grayproductions.net] or
|
||||
{Gregory Brown}[mailto:gregory.t.brown@gmail.com] with any questions.
|
||||
82
vendor/gems/highline-1.4.0/Rakefile
vendored
Normal file
82
vendor/gems/highline-1.4.0/Rakefile
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
require "rake/rdoctask"
|
||||
require "rake/testtask"
|
||||
require "rake/gempackagetask"
|
||||
|
||||
require "rubygems"
|
||||
|
||||
dir = File.dirname(__FILE__)
|
||||
lib = File.join(dir, "lib", "highline.rb")
|
||||
version = File.read(lib)[/^\s*VERSION\s*=\s*(['"])(\d\.\d\.\d)\1/, 2]
|
||||
|
||||
task :default => [:test]
|
||||
|
||||
Rake::TestTask.new do |test|
|
||||
test.libs << "test"
|
||||
test.test_files = [ "test/ts_all.rb" ]
|
||||
test.verbose = true
|
||||
end
|
||||
|
||||
Rake::RDocTask.new do |rdoc|
|
||||
rdoc.rdoc_files.include( "README", "INSTALL",
|
||||
"TODO", "CHANGELOG",
|
||||
"AUTHORS", "COPYING",
|
||||
"LICENSE", "lib/" )
|
||||
rdoc.main = "README"
|
||||
rdoc.rdoc_dir = "doc/html"
|
||||
rdoc.title = "HighLine Documentation"
|
||||
end
|
||||
|
||||
desc "Upload current documentation to Rubyforge"
|
||||
task :upload_docs => [:rdoc] do
|
||||
sh "scp -r doc/html/* " +
|
||||
"bbazzarrakk@rubyforge.org:/var/www/gforge-projects/highline/doc/"
|
||||
sh "scp -r site/* " +
|
||||
"bbazzarrakk@rubyforge.org:/var/www/gforge-projects/highline/"
|
||||
end
|
||||
|
||||
spec = Gem::Specification.new do |spec|
|
||||
spec.name = "highline"
|
||||
spec.version = version
|
||||
spec.platform = Gem::Platform::RUBY
|
||||
spec.summary = "HighLine is a high-level command-line IO library."
|
||||
spec.files = Dir.glob("{examples,lib,test}/**/*.rb").
|
||||
delete_if { |item| item.include?("CVS") } +
|
||||
["Rakefile", "setup.rb"]
|
||||
|
||||
spec.test_suite_file = "test/ts_all.rb"
|
||||
spec.has_rdoc = true
|
||||
spec.extra_rdoc_files = %w{README INSTALL TODO CHANGELOG LICENSE}
|
||||
spec.rdoc_options << '--title' << 'HighLine Documentation' <<
|
||||
'--main' << 'README'
|
||||
|
||||
spec.require_path = 'lib'
|
||||
|
||||
spec.author = "James Edward Gray II"
|
||||
spec.email = "james@grayproductions.net"
|
||||
spec.rubyforge_project = "highline"
|
||||
spec.homepage = "http://highline.rubyforge.org"
|
||||
spec.description = <<END_DESC
|
||||
A high-level IO library that provides validation, type conversion, and more for
|
||||
command-line interfaces. HighLine also includes a complete menu system that can
|
||||
crank out anything from simple list selection to complete shells with just
|
||||
minutes of work.
|
||||
END_DESC
|
||||
end
|
||||
|
||||
Rake::GemPackageTask.new(spec) do |pkg|
|
||||
pkg.need_zip = true
|
||||
pkg.need_tar = true
|
||||
end
|
||||
|
||||
desc "Show library's code statistics"
|
||||
task :stats do
|
||||
require 'code_statistics'
|
||||
CodeStatistics.new( ["HighLine", "lib"],
|
||||
["Functionals", "examples"],
|
||||
["Units", "test"] ).to_s
|
||||
end
|
||||
|
||||
desc "Add new files to Subversion"
|
||||
task :add_to_svn do
|
||||
sh %Q{svn status | ruby -nae 'system "svn add \#{$F[1]}" if $F[0] == "?"' }
|
||||
end
|
||||
6
vendor/gems/highline-1.4.0/TODO
vendored
Normal file
6
vendor/gems/highline-1.4.0/TODO
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
= To Do List
|
||||
|
||||
The following is a list of planned expansions for HighLine, in no particular
|
||||
order.
|
||||
|
||||
* Rent this space.
|
||||
38
vendor/gems/highline-1.4.0/examples/ansi_colors.rb
vendored
Normal file
38
vendor/gems/highline-1.4.0/examples/ansi_colors.rb
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
|
||||
# ansi_colors.rb
|
||||
#
|
||||
# Created by James Edward Gray II on 2005-05-03.
|
||||
# Copyright 2005 Gray Productions. All rights reserved.
|
||||
|
||||
require "rubygems"
|
||||
require "highline/import"
|
||||
|
||||
# Supported color sequences.
|
||||
colors = %w{black red green yellow blue magenta cyan white}
|
||||
|
||||
# Using color() with symbols.
|
||||
colors.each_with_index do |c, i|
|
||||
say("This should be <%= color('#{c}', :#{c}) %>!")
|
||||
if i == 0
|
||||
say( "This should be " +
|
||||
"<%= color('white on #{c}', :white, :on_#{c}) %>!")
|
||||
else
|
||||
say( "This should be " +
|
||||
"<%= color( '#{colors[i - 1]} on #{c}',
|
||||
:#{colors[i - 1]}, :on_#{c} ) %>!")
|
||||
end
|
||||
end
|
||||
|
||||
# Using color with constants.
|
||||
say("This should be <%= color('bold', BOLD) %>!")
|
||||
say("This should be <%= color('underlined', UNDERLINE) %>!")
|
||||
|
||||
# Using constants only.
|
||||
say("This might even <%= BLINK %>blink<%= CLEAR %>!")
|
||||
|
||||
# It even works with list wrapping.
|
||||
erb_digits = %w{Zero One Two Three Four} +
|
||||
["<%= color('Five', :blue) %%>"] +
|
||||
%w{Six Seven Eight Nine}
|
||||
say("<%= list(#{erb_digits.inspect}, :columns_down, 3) %>")
|
||||
18
vendor/gems/highline-1.4.0/examples/asking_for_arrays.rb
vendored
Normal file
18
vendor/gems/highline-1.4.0/examples/asking_for_arrays.rb
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
|
||||
# asking_for_arrays.rb
|
||||
#
|
||||
# Created by James Edward Gray II on 2005-07-05.
|
||||
# Copyright 2005 Gray Productions. All rights reserved.
|
||||
|
||||
require "rubygems"
|
||||
require "highline/import"
|
||||
require "pp"
|
||||
|
||||
grades = ask( "Enter test scores (or a blank line to quit):",
|
||||
lambda { |ans| ans =~ /^-?\d+$/ ? Integer(ans) : ans} ) do |q|
|
||||
q.gather = ""
|
||||
end
|
||||
|
||||
say("Grades:")
|
||||
pp grades
|
||||
75
vendor/gems/highline-1.4.0/examples/basic_usage.rb
vendored
Normal file
75
vendor/gems/highline-1.4.0/examples/basic_usage.rb
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
|
||||
# basic_usage.rb
|
||||
#
|
||||
# Created by James Edward Gray II on 2005-04-28.
|
||||
# Copyright 2005 Gray Productions. All rights reserved.
|
||||
|
||||
require "rubygems"
|
||||
require "highline/import"
|
||||
require "yaml"
|
||||
|
||||
contacts = [ ]
|
||||
|
||||
class NameClass
|
||||
def self.parse( string )
|
||||
if string =~ /^\s*(\w+),\s*(\w+)\s*$/
|
||||
self.new($2, $1)
|
||||
else
|
||||
raise ArgumentError, "Invalid name format."
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(first, last)
|
||||
@first, @last = first, last
|
||||
end
|
||||
|
||||
attr_reader :first, :last
|
||||
end
|
||||
|
||||
begin
|
||||
entry = Hash.new
|
||||
|
||||
# basic output
|
||||
say("Enter a contact:")
|
||||
|
||||
# basic input
|
||||
entry[:name] = ask("Name? (last, first) ", NameClass) do |q|
|
||||
q.validate = /\A\w+, ?\w+\Z/
|
||||
end
|
||||
entry[:company] = ask("Company? ") { |q| q.default = "none" }
|
||||
entry[:address] = ask("Address? ")
|
||||
entry[:city] = ask("City? ")
|
||||
entry[:state] = ask("State? ") do |q|
|
||||
q.case = :up
|
||||
q.validate = /\A[A-Z]{2}\Z/
|
||||
end
|
||||
entry[:zip] = ask("Zip? ") do |q|
|
||||
q.validate = /\A\d{5}(?:-?\d{4})?\Z/
|
||||
end
|
||||
entry[:phone] = ask( "Phone? ",
|
||||
lambda { |p| p.delete("^0-9").
|
||||
sub(/\A(\d{3})/, '(\1) ').
|
||||
sub(/(\d{4})\Z/, '-\1') } ) do |q|
|
||||
q.validate = lambda { |p| p.delete("^0-9").length == 10 }
|
||||
q.responses[:not_valid] = "Enter a phone numer with area code."
|
||||
end
|
||||
entry[:age] = ask("Age? ", Integer) { |q| q.in = 0..105 }
|
||||
entry[:birthday] = ask("Birthday? ", Date)
|
||||
entry[:interests] = ask( "Interests? (comma separated list) ",
|
||||
lambda { |str| str.split(/,\s*/) } )
|
||||
entry[:description] = ask("Enter a description for this contact.") do |q|
|
||||
q.whitespace = :strip_and_collapse
|
||||
end
|
||||
|
||||
contacts << entry
|
||||
# shortcut for yes and no questions
|
||||
end while agree("Enter another contact? ", true)
|
||||
|
||||
if agree("Save these contacts? ", true)
|
||||
file_name = ask("Enter a file name: ") do |q|
|
||||
q.validate = /\A\w+\Z/
|
||||
q.confirm = true
|
||||
end
|
||||
File.open("#{file_name}.yaml", "w") { |file| YAML.dump(contacts, file) }
|
||||
end
|
||||
32
vendor/gems/highline-1.4.0/examples/color_scheme.rb
vendored
Normal file
32
vendor/gems/highline-1.4.0/examples/color_scheme.rb
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/env ruby -w
|
||||
|
||||
# color_scheme.rb
|
||||
#
|
||||
# Created by Jeremy Hinegardner on 2007-01-24
|
||||
# Copyright 2007 Jeremy Hinegardner. All rights reserved
|
||||
|
||||
require 'rubygems'
|
||||
require 'highline/import'
|
||||
|
||||
# Create a color scheme, naming color patterns with symbol names.
|
||||
ft = HighLine::ColorScheme.new do |cs|
|
||||
cs[:headline] = [ :bold, :yellow, :on_black ]
|
||||
cs[:horizontal_line] = [ :bold, :white, :on_blue]
|
||||
cs[:even_row] = [ :green ]
|
||||
cs[:odd_row] = [ :magenta ]
|
||||
end
|
||||
|
||||
# Assign that color scheme to HighLine...
|
||||
HighLine.color_scheme = ft
|
||||
|
||||
# ...and use it.
|
||||
say("<%= color('Headline', :headline) %>")
|
||||
say("<%= color('-'*20, :horizontal_line) %>")
|
||||
|
||||
# Setup a toggle for rows.
|
||||
i = true
|
||||
("A".."D").each do |row|
|
||||
row_color = i ? :even_row : :odd_row
|
||||
say("<%= color('#{row}', '#{row_color}') %>")
|
||||
i = !i
|
||||
end
|
||||
65
vendor/gems/highline-1.4.0/examples/menus.rb
vendored
Normal file
65
vendor/gems/highline-1.4.0/examples/menus.rb
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
|
||||
require "rubygems"
|
||||
require "highline/import"
|
||||
|
||||
# The old way, using ask() and say()...
|
||||
choices = %w{ruby python perl}
|
||||
say("This is the old way using ask() and say()...")
|
||||
say("Please choose your favorite programming language:")
|
||||
say(choices.map { |c| " #{c}\n" }.join)
|
||||
|
||||
case ask("? ", choices)
|
||||
when "ruby"
|
||||
say("Good choice!")
|
||||
else
|
||||
say("Not from around here, are you?")
|
||||
end
|
||||
|
||||
# The new and improved choose()...
|
||||
say("\nThis is the new mode (default)...")
|
||||
choose do |menu|
|
||||
menu.prompt = "Please choose your favorite programming language? "
|
||||
|
||||
menu.choice :ruby do say("Good choice!") end
|
||||
menu.choices(:python, :perl) do say("Not from around here, are you?") end
|
||||
end
|
||||
|
||||
say("\nThis is letter indexing...")
|
||||
choose do |menu|
|
||||
menu.index = :letter
|
||||
menu.index_suffix = ") "
|
||||
|
||||
menu.prompt = "Please choose your favorite programming language? "
|
||||
|
||||
menu.choice :ruby do say("Good choice!") end
|
||||
menu.choices(:python, :perl) do say("Not from around here, are you?") end
|
||||
end
|
||||
|
||||
say("\nThis is with a different layout...")
|
||||
choose do |menu|
|
||||
menu.layout = :one_line
|
||||
|
||||
menu.header = "Languages"
|
||||
menu.prompt = "Favorite? "
|
||||
|
||||
menu.choice :ruby do say("Good choice!") end
|
||||
menu.choices(:python, :perl) do say("Not from around here, are you?") end
|
||||
end
|
||||
|
||||
say("\nYou can even build shells...")
|
||||
loop do
|
||||
choose do |menu|
|
||||
menu.layout = :menu_only
|
||||
|
||||
menu.shell = true
|
||||
|
||||
menu.choice(:load, "Load a file.") do |command, details|
|
||||
say("Loading file with options: #{details}...")
|
||||
end
|
||||
menu.choice(:save, "Save a file.") do |command, details|
|
||||
say("Saving file with options: #{details}...")
|
||||
end
|
||||
menu.choice(:quit, "Exit program.") { exit }
|
||||
end
|
||||
end
|
||||
19
vendor/gems/highline-1.4.0/examples/overwrite.rb
vendored
Normal file
19
vendor/gems/highline-1.4.0/examples/overwrite.rb
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
|
||||
# overwrite.rb
|
||||
#
|
||||
# Created by Jeremy Hinegardner on 2007-01-24
|
||||
# Copyright 2007 Jeremy Hinegardner. All rights reserved
|
||||
|
||||
require 'rubygems'
|
||||
require 'highline/import'
|
||||
|
||||
prompt = "here is your password:"
|
||||
ask(
|
||||
"#{prompt} <%= color('mypassword', RED, BOLD) %> (Press Any Key to blank) "
|
||||
) do |q|
|
||||
q.overwrite = true
|
||||
q.echo = false # overwrite works best when echo is false.
|
||||
q.character = true # if this is set to :getc then overwrite does not work
|
||||
end
|
||||
say("<%= color('Look! blanked out!', GREEN) %>")
|
||||
322
vendor/gems/highline-1.4.0/examples/page_and_wrap.rb
vendored
Normal file
322
vendor/gems/highline-1.4.0/examples/page_and_wrap.rb
vendored
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
|
||||
# page_and_wrap.rb
|
||||
#
|
||||
# Created by James Edward Gray II on 2005-05-07.
|
||||
# Copyright 2005 Gray Productions. All rights reserved.
|
||||
|
||||
require "rubygems"
|
||||
require "highline/import"
|
||||
|
||||
$terminal.wrap_at = 80
|
||||
$terminal.page_at = 22
|
||||
|
||||
say(<<END)
|
||||
THE UNITED STATES CONSTITUTION
|
||||
|
||||
We the People of the United States, in Order to form a more perfect Union, establish Justice, insure domestic Tranquility, provide for the common defence, promote the general Welfare, and secure the Blessings of Liberty to ourselves and our Posterity, do ordain and establish this Constitution for the United States of America.
|
||||
|
||||
Article. I.
|
||||
|
||||
Section 1.
|
||||
|
||||
All legislative Powers herein granted shall be vested in a Congress of the United States, which shall consist of a Senate and House of Representatives.
|
||||
|
||||
Section. 2.
|
||||
|
||||
Clause 1: The House of Representatives shall be composed of Members chosen every second Year by the People of the several States, and the Electors in each State shall have the Qualifications requisite for Electors of the most numerous Branch of the State Legislature.
|
||||
|
||||
Clause 2: No Person shall be a Representative who shall not have attained to the Age of twenty five Years, and been seven Years a Citizen of the United States, and who shall not, when elected, be an Inhabitant of that State in which he shall be chosen.
|
||||
|
||||
Clause 3: Representatives and direct Taxes shall be apportioned among the several States which may be included within this Union, according to their respective Numbers, which shall be determined by adding to the whole Number of free Persons, including those bound to Service for a Term of Years, and excluding Indians not taxed, three fifths of all other Persons. (See Note 2) The actual Enumeration shall be made within three Years after the first Meeting of the Congress of the United States, and within every subsequent Term of ten Years, in such Manner as they shall by Law direct. The Number of Representatives shall not exceed one for every thirty Thousand, but each State shall have at Least one Representative; and until such enumeration shall be made, the State of New Hampshire shall be entitled to chuse three, Massachusetts eight, Rhode-Island and Providence Plantations one, Connecticut five, New-York six, New Jersey four, Pennsylvania eight, Delaware one, Maryland six, Virginia ten, North Carolina five, South Carolina five, and Georgia three.
|
||||
|
||||
Clause 4: When vacancies happen in the Representation from any State, the Executive Authority thereof shall issue Writs of Election to fill such Vacancies.
|
||||
|
||||
Clause 5: The House of Representatives shall chuse their Speaker and other Officers; and shall have the sole Power of Impeachment.
|
||||
|
||||
Section. 3.
|
||||
|
||||
Clause 1: The Senate of the United States shall be composed of two Senators from each State, chosen by the Legislature thereof, (See Note 3) for six Years; and each Senator shall have one Vote.
|
||||
|
||||
Clause 2: Immediately after they shall be assembled in Consequence of the first Election, they shall be divided as equally as may be into three Classes. The Seats of the Senators of the first Class shall be vacated at the Expiration of the second Year, of the second Class at the Expiration of the fourth Year, and of the third Class at the Expiration of the sixth Year, so that one third may be chosen every second Year; and if Vacancies happen by Resignation, or otherwise, during the Recess of the Legislature of any State, the Executive thereof may make temporary Appointments until the next Meeting of the Legislature, which shall then fill such Vacancies. (See Note 4)
|
||||
|
||||
Clause 3: No Person shall be a Senator who shall not have attained to the Age of thirty Years, and been nine Years a Citizen of the United States, and who shall not, when elected, be an Inhabitant of that State for which he shall be chosen.
|
||||
|
||||
Clause 4: The Vice President of the United States shall be President of the Senate, but shall have no Vote, unless they be equally divided.
|
||||
|
||||
Clause 5: The Senate shall chuse their other Officers, and also a President pro tempore, in the Absence of the Vice President, or when he shall exercise the Office of President of the United States.
|
||||
|
||||
Clause 6: The Senate shall have the sole Power to try all Impeachments. When sitting for that Purpose, they shall be on Oath or Affirmation. When the President of the United States is tried, the Chief Justice shall preside: And no Person shall be convicted without the Concurrence of two thirds of the Members present.
|
||||
|
||||
Clause 7: Judgment in Cases of Impeachment shall not extend further than to removal from Office, and disqualification to hold and enjoy any Office of honor, Trust or Profit under the United States: but the Party convicted shall nevertheless be liable and subject to Indictment, Trial, Judgment and Punishment, according to Law.
|
||||
|
||||
Section. 4.
|
||||
|
||||
Clause 1: The Times, Places and Manner of holding Elections for Senators and Representatives, shall be prescribed in each State by the Legislature thereof; but the Congress may at any time by Law make or alter such Regulations, except as to the Places of chusing Senators.
|
||||
|
||||
Clause 2: The Congress shall assemble at least once in every Year, and such Meeting shall be on the first Monday in December, (See Note 5) unless they shall by Law appoint a different Day.
|
||||
|
||||
Section. 5.
|
||||
|
||||
Clause 1: Each House shall be the Judge of the Elections, Returns and Qualifications of its own Members, and a Majority of each shall constitute a Quorum to do Business; but a smaller Number may adjourn from day to day, and may be authorized to compel the Attendance of absent Members, in such Manner, and under such Penalties as each House may provide.
|
||||
|
||||
Clause 2: Each House may determine the Rules of its Proceedings, punish its Members for disorderly Behaviour, and, with the Concurrence of two thirds, expel a Member.
|
||||
|
||||
Clause 3: Each House shall keep a Journal of its Proceedings, and from time to time publish the same, excepting such Parts as may in their Judgment require Secrecy; and the Yeas and Nays of the Members of either House on any question shall, at the Desire of one fifth of those Present, be entered on the Journal.
|
||||
|
||||
Clause 4: Neither House, during the Session of Congress, shall, without the Consent of the other, adjourn for more than three days, nor to any other Place than that in which the two Houses shall be sitting.
|
||||
|
||||
Section. 6.
|
||||
|
||||
Clause 1: The Senators and Representatives shall receive a Compensation for their Services, to be ascertained by Law, and paid out of the Treasury of the United States. (See Note 6) They shall in all Cases, except Treason, Felony and Breach of the Peace, beprivileged from Arrest during their Attendance at the Session of their respective Houses, and in going to and returning from the same; and for any Speech or Debate in either House, they shall not be questioned in any other Place.
|
||||
|
||||
Clause 2: No Senator or Representative shall, during the Time for which he was elected, be appointed to any civil Office under the Authority of the United States, which shall have been created, or the Emoluments whereof shall have been encreased during such time; and no Person holding any Office under the United States, shall be a Member of either House during his Continuance in Office.
|
||||
|
||||
Section. 7.
|
||||
|
||||
Clause 1: All Bills for raising Revenue shall originate in the House of Representatives; but the Senate may propose or concur with Amendments as on other Bills.
|
||||
|
||||
Clause 2: Every Bill which shall have passed the House of Representatives and the Senate, shall, before it become a Law, be presented to the President of the United States; If he approve he shall sign it, but if not he shall return it, with his Objections to that House in which it shall have originated, who shall enter the Objections at large on their Journal, and proceed to reconsider it. If after such Reconsideration two thirds of that House shall agree to pass the Bill, it shall be sent, together with the Objections, to the other House, by which it shall likewise be reconsidered, and if approved by two thirds of that House, it shall become a Law. But in all such Cases the Votes of both Houses shall be determined by yeas and Nays, and the Names of the Persons voting for and against the Bill shall be entered on the Journal of each House respectively. If any Bill shall not be returned by the President within ten Days (Sundays excepted) after it shall have been presented to him, the Same shall be a Law, in like Manner as if he had signed it, unless the Congress by their Adjournment prevent its Return, in which Case it shall not be a Law.
|
||||
|
||||
Clause 3: Every Order, Resolution, or Vote to which the Concurrence of the Senate and House of Representatives may be necessary (except on a question of Adjournment) shall be presented to the President of the United States; and before the Same shall take Effect, shall be approved by him, or being disapproved by him, shall be repassed by two thirds of the Senate and House of Representatives, according to the Rules and Limitations prescribed in the Case of a Bill.
|
||||
|
||||
Section. 8.
|
||||
|
||||
Clause 1: The Congress shall have Power To lay and collect Taxes, Duties, Imposts and Excises, to pay the Debts and provide for the common Defence and general Welfare of the United States; but all Duties, Imposts and Excises shall be uniform throughout the United States;
|
||||
|
||||
Clause 2: To borrow Money on the credit of the United States;
|
||||
|
||||
Clause 3: To regulate Commerce with foreign Nations, and among the several States, and with the Indian Tribes;
|
||||
|
||||
Clause 4: To establish an uniform Rule of Naturalization, and uniform Laws on the subject of Bankruptcies throughout the United States;
|
||||
|
||||
Clause 5: To coin Money, regulate the Value thereof, and of foreign Coin, and fix the Standard of Weights and Measures;
|
||||
|
||||
Clause 6: To provide for the Punishment of counterfeiting the Securities and current Coin of the United States;
|
||||
|
||||
Clause 7: To establish Post Offices and post Roads;
|
||||
|
||||
Clause 8: To promote the Progress of Science and useful Arts, by securing for limited Times to Authors and Inventors the exclusive Right to their respective Writings and Discoveries;
|
||||
|
||||
Clause 9: To constitute Tribunals inferior to the supreme Court;
|
||||
|
||||
Clause 10: To define and punish Piracies and Felonies committed on the high Seas, and Offences against the Law of Nations;
|
||||
|
||||
Clause 11: To declare War, grant Letters of Marque and Reprisal, and make Rules concerning Captures on Land and Water;
|
||||
|
||||
Clause 12: To raise and support Armies, but no Appropriation of Money to that Use shall be for a longer Term than two Years;
|
||||
|
||||
Clause 13: To provide and maintain a Navy;
|
||||
|
||||
Clause 14: To make Rules for the Government and Regulation of the land and naval Forces;
|
||||
|
||||
Clause 15: To provide for calling forth the Militia to execute the Laws of the Union, suppress Insurrections and repel Invasions;
|
||||
|
||||
Clause 16: To provide for organizing, arming, and disciplining, the Militia, and for governing such Part of them as may be employed in the Service of the United States, reserving to the States respectively, the Appointment of the Officers, and the Authority of training the Militia according to the discipline prescribed by Congress;
|
||||
|
||||
Clause 17: To exercise exclusive Legislation in all Cases whatsoever, over such District (not exceeding ten Miles square) as may, byCession of particular States, and the Acceptance of Congress, become the Seat of the Government of the United States, and to exercise like Authority over all Places purchased by the Consent of the Legislature of the State in which the Same shall be, for the Erection of Forts, Magazines, Arsenals, dock-Yards, and other needful Buildings;--And
|
||||
|
||||
Clause 18: To make all Laws which shall be necessary and proper for carrying into Execution the foregoing Powers, and all other Powers vested by this Constitution in the Government of the United States, or in any Department or Officer thereof.
|
||||
|
||||
Section. 9.
|
||||
|
||||
Clause 1: The Migration or Importation of such Persons as any of the States now existing shall think proper to admit, shall not be prohibited by the Congress prior to the Year one thousand eight hundred and eight, but a Tax or duty may be imposed on such Importation, not exceeding ten dollars for each Person.
|
||||
|
||||
Clause 2: The Privilege of the Writ of Habeas Corpus shall not be suspended, unless when in Cases of Rebellion or Invasion the public Safety may require it.
|
||||
|
||||
Clause 3: No Bill of Attainder or ex post facto Law shall be passed.
|
||||
|
||||
Clause 4: No Capitation, or other direct, Tax shall be laid, unless in Proportion to the Census or Enumeration herein before directed to be taken. (See Note 7)
|
||||
|
||||
Clause 5: No Tax or Duty shall be laid on Articles exported from any State.
|
||||
|
||||
Clause 6: No Preference shall be given by any Regulation of Commerce or Revenue to the Ports of one State over those of another: nor shall Vessels bound to, or from, one State, be obliged to enter, clear, or pay Duties in another.
|
||||
|
||||
Clause 7: No Money shall be drawn from the Treasury, but in Consequence of Appropriations made by Law; and a regular Statement and Account of the Receipts and Expenditures of all public Money shall be published from time to time.
|
||||
|
||||
Clause 8: No Title of Nobility shall be granted by the United States: And no Person holding any Office of Profit or Trust under them, shall, without the Consent of the Congress, accept of any present, Emolument, Office, or Title, of any kind whatever, from any King, Prince, or foreign State.
|
||||
|
||||
Section. 10.
|
||||
|
||||
Clause 1: No State shall enter into any Treaty, Alliance, or Confederation; grant Letters of Marque and Reprisal; coin Money; emit Bills of Credit; make any Thing but gold and silver Coin a Tender in Payment of Debts; pass any Bill of Attainder, ex post facto Law, or Law impairing the Obligation of Contracts, or grant any Title of Nobility.
|
||||
|
||||
Clause 2: No State shall, without the Consent of the Congress, lay any Imposts or Duties on Imports or Exports, except what may be absolutely necessary for executing it's inspection Laws: and the net Produce of all Duties and Imposts, laid by any State on Imports or Exports, shall be for the Use of the Treasury of the United States; and all such Laws shall be subject to the Revision and Controul of the Congress.
|
||||
|
||||
Clause 3: No State shall, without the Consent of Congress, lay any Duty of Tonnage, keep Troops, or Ships of War in time of Peace, enter into any Agreement or Compact with another State, or with a foreign Power, or engage in War, unless actually invaded, or in such imminent Danger as will not admit of delay.
|
||||
|
||||
Article. II.
|
||||
|
||||
Section. 1.
|
||||
|
||||
Clause 1: The executive Power shall be vested in a President of the United States of America. He shall hold his Office during the Term of four Years, and, together with the Vice President, chosen for the same Term, be elected, as follows
|
||||
|
||||
Clause 2: Each State shall appoint, in such Manner as the Legislature thereof may direct, a Number of Electors, equal to the whole Number of Senators and Representatives to which the State may be entitled in the Congress: but no Senator or Representative, or Person holding an Office of Trust or Profit under the United States, shall be appointed an Elector.
|
||||
|
||||
Clause 3: The Electors shall meet in their respective States, and vote by Ballot for two Persons, of whom one at least shall not be an Inhabitant of the same State with themselves. And they shall make a List of all the Persons voted for, and of the Number of Votes for each; which List they shall sign and certify, and transmit sealed to the Seat of the Government of the United States, directed to the President of the Senate. The President of the Senate shall, in the Presence of the Senate and House of Representatives, open all the Certificates, and the Votes shall then be counted. The Person having the greatest Number of Votes shall be the President, if such Number be a Majority of the whole Number of Electors appointed; and if there be more than one who have such Majority, and have an equal Number of Votes, then the House of Representatives shall immediately chuse by Ballot one of them for President; and if no Person have a Majority, then from the five highest on the List the said House shall in like Manner chuse the President. But in chusing the President, the Votes shall be taken by States, the Representation from each State having one Vote; A quorum for this Purpose shall consist of a Member or Members from two thirds of the States, and a Majority of all the States shall be necessary to a Choice. In every Case, after the Choice of the President, the Person having the greatest Number of Votes of the Electors shall be the Vice President. But if there should remain two or more who have equal Votes, the Senate shall chuse from them by Ballot the Vice President. (See Note 8)
|
||||
|
||||
Clause 4: The Congress may determine the Time of chusing the Electors, and the Day on which they shall give their Votes; which Day shall be the same throughout the United States.
|
||||
|
||||
Clause 5: No Person except a natural born Citizen, or a Citizen of the United States, at the time of the Adoption of this Constitution, shall be eligible to the Office of President; neither shall any Person be eligible to that Office who shall not have attained to the Age of thirty five Years, and been fourteen Years a Resident within the United States.
|
||||
|
||||
Clause 6: In Case of the Removal of the President from Office, or of his Death, Resignation, or Inability to discharge the Powers and Duties of the said Office, (See Note 9) the Same shall devolve on the VicePresident, and the Congress may by Law provide for the Case of Removal, Death, Resignation or Inability, both of the President and Vice President, declaring what Officer shall then act as President, and such Officer shall act accordingly, until the Disability be removed, or a President shall be elected.
|
||||
|
||||
Clause 7: The President shall, at stated Times, receive for his Services, a Compensation, which shall neither be encreased nor diminished during the Period for which he shall have been elected, and he shall not receive within that Period any other Emolument from the United States, or any of them.
|
||||
|
||||
Clause 8: Before he enter on the Execution of his Office, he shall take the following Oath or Affirmation:--"I do solemnly swear (or affirm) that I will faithfully execute the Office of President of the United States, and will to the best of my Ability, preserve, protect and defend the Constitution of the United States."
|
||||
|
||||
Section. 2.
|
||||
|
||||
Clause 1: The President shall be Commander in Chief of the Army and Navy of the United States, and of the Militia of the several States, when called into the actual Service of the United States; he may require the Opinion, in writing, of the principal Officer in each of the executive Departments, upon any Subject relating to the Duties of their respective Offices, and he shall have Power to grant Reprieves and Pardons for Offences against the United States, except in Cases of Impeachment.
|
||||
|
||||
Clause 2: He shall have Power, by and with the Advice and Consent of the Senate, to make Treaties, provided two thirds of the Senators present concur; and he shall nominate, and by and with the Advice and Consent of the Senate, shall appoint Ambassadors, other public Ministers and Consuls, Judges of the supreme Court, and all other Officers of the United States, whose Appointments are not herein otherwise provided for, and which shall be established by Law: but the Congress may by Law vest the Appointment of such inferior Officers, as they think proper, in the President alone, in the Courts of Law, or in the Heads of Departments.
|
||||
|
||||
Clause 3: The President shall have Power to fill up all Vacancies that may happen during the Recess of the Senate, by granting Commissions which shall expire at the End of their next Session.
|
||||
|
||||
Section. 3.
|
||||
|
||||
He shall from time to time give to the Congress Information of the State of the Union, and recommend to their Consideration such Measures as he shall judge necessary and expedient; he may, on extraordinary Occasions, convene both Houses, or either of them, and in Case of Disagreement between them, with Respect to the Time of Adjournment, he may adjourn them to such Time as he shall think proper; he shall receive Ambassadors and other public Ministers; he shall take Care that the Laws be faithfully executed, and shall Commission all the Officers of the United States.
|
||||
|
||||
Section. 4.
|
||||
|
||||
The President, Vice President and all civil Officers of the United States, shall be removed from Office on Impeachment for, and Conviction of, Treason, Bribery, or other high Crimes and Misdemeanors.
|
||||
|
||||
Article. III.
|
||||
|
||||
Section. 1.
|
||||
|
||||
The judicial Power of the United States, shall be vested in one supreme Court, and in such inferior Courts as the Congress may from time to time ordain and establish. The Judges, both of the supreme and inferior Courts, shall hold their Offices during good Behaviour, and shall, at stated Times, receive for their Services, a Compensation, which shall not be diminished during their Continuance in Office.
|
||||
|
||||
Section. 2.
|
||||
|
||||
Clause 1: The judicial Power shall extend to all Cases, in Law and Equity, arising under this Constitution, the Laws of the United States, and Treaties made, or which shall be made, under their Authority;--to all Cases affecting Ambassadors, other public Ministers and Consuls;--to all Cases of admiralty and maritime Jurisdiction;--to Controversies to which the United States shall be a Party;--to Controversies between two or more States;--between a State and Citizens of another State; (See Note 10)--between Citizens of different States, --between Citizens of the same State claiming Lands under Grants of different States, and between a State, or the Citizens thereof, and foreign States, Citizens or Subjects.
|
||||
|
||||
Clause 2: In all Cases affecting Ambassadors, other public Ministers and Consuls, and those in which a State shall be Party, the supreme Court shall have original Jurisdiction. In all the other Cases before mentioned, the supreme Court shall have appellate Jurisdiction, both as to Law and Fact, with such Exceptions, and under such Regulations as the Congress shall make.
|
||||
|
||||
Clause 3: The Trial of all Crimes, except in Cases of Impeachment, shall be by Jury; and such Trial shall be held in the State where the said Crimes shall have been committed; but when not committed within any State, the Trial shall be at such Place or Places as the Congress may by Law have directed.
|
||||
|
||||
Section. 3.
|
||||
|
||||
Clause 1: Treason against the United States, shall consist only in levying War against them, or in adhering to their Enemies, giving them Aid and Comfort. No Person shall be convicted of Treason unless on the Testimony of two Witnesses to the same overt Act, or on Confession in open Court.
|
||||
|
||||
Clause 2: The Congress shall have Power to declare the Punishment of Treason, but no Attainder of Treason shall work Corruption of Blood, or Forfeiture except during the Life of the Person attainted.
|
||||
|
||||
Article. IV.
|
||||
|
||||
Section. 1.
|
||||
|
||||
Full Faith and Credit shall be given in each State to the public Acts, Records, and judicial Proceedings of every other State. And the Congress may by general Laws prescribe the Manner in which such Acts, Records and Proceedings shall be proved, and the Effect thereof.
|
||||
|
||||
Section. 2.
|
||||
|
||||
Clause 1: The Citizens of each State shall be entitled to all Privileges and Immunities of Citizens in the several States.
|
||||
|
||||
Clause 2: A Person charged in any State with Treason, Felony, or other Crime, who shall flee from Justice, and be found in another State, shall on Demand of the executive Authority of the State from which he fled, be delivered up, to be removed to the State having Jurisdiction of the Crime.
|
||||
|
||||
Clause 3: No Person held to Service or Labour in one State, under the Laws thereof, escaping into another, shall, in Consequence of any Law or Regulation therein, be discharged from such Service or Labour, but shall be delivered up on Claim of the Party to whom such Service or Labour may be due. (See Note 11)
|
||||
|
||||
Section. 3.
|
||||
|
||||
Clause 1: New States may be admitted by the Congress into this Union; but no new State shall be formed or erected within the Jurisdiction of any other State; nor any State be formed by the Junction of two or more States, or Parts of States, without the Consent of the Legislatures of the States concerned as well as of the Congress.
|
||||
|
||||
Clause 2: The Congress shall have Power to dispose of and make all needful Rules and Regulations respecting the Territory or other Property belonging to the United States; and nothing in this Constitution shall be so construed as to Prejudice any Claims of the United States, or of any particular State.
|
||||
|
||||
Section. 4.
|
||||
|
||||
The United States shall guarantee to every State in this Union a Republican Form of Government, and shall protect each of them against Invasion; and on Application of the Legislature, or of the Executive (when the Legislature cannot be convened) against domestic Violence.
|
||||
|
||||
Article. V.
|
||||
|
||||
The Congress, whenever two thirds of both Houses shall deem it necessary, shall propose Amendments to this Constitution, or, on the Application of the Legislatures of two thirds of the several States, shall call a Convention for proposing Amendments, which, in either Case, shall be valid to all Intents and Purposes, as Part of this Constitution, when ratified by the Legislatures of three fourths of the several States, or by Conventions in three fourths thereof, as the one or the other Mode of Ratification may be proposed by the Congress; Provided that no Amendment which may be made prior to the Year One thousand eight hundred and eight shall in any Manner affect the first and fourth Clauses in the Ninth Section of the first Article; and that no State, without its Consent, shall be deprived of its equal Suffrage in the Senate.
|
||||
|
||||
Article. VI.
|
||||
|
||||
Clause 1: All Debts contracted and Engagements entered into, before the Adoption of this Constitution, shall be as valid against the United States under this Constitution, as under the Confederation.
|
||||
|
||||
Clause 2: This Constitution, and the Laws of the United States which shall be made in Pursuance thereof; and all Treaties made, or which shall be made, under the Authority of the United States, shall be the supreme Law of the Land; and the Judges in every State shall be bound thereby, any Thing in the Constitution or Laws of any State to the Contrary notwithstanding.
|
||||
|
||||
Clause 3: The Senators and Representatives before mentioned, and the Members of the several State Legislatures, and all executive and judicial Officers, both of the United States and of the several States, shall be bound by Oath or Affirmation, to support this Constitution; but no religious Test shall ever be required as a Qualification to any Office or public Trust under the United States.
|
||||
|
||||
Article. VII.
|
||||
|
||||
The Ratification of the Conventions of nine States, shall be sufficient for the Establishment of this Constitution between the States so ratifying the Same.
|
||||
done in Convention by the Unanimous Consent of the States present the Seventeenth Day of September in the Year of our Lord one thousand seven hundred and Eighty seven and of the Independence of the United States of America the Twelfth In witness whereof We have hereunto subscribed our Names,
|
||||
|
||||
GO WASHINGTON--Presidt. and deputy from Virginia
|
||||
|
||||
[Signed also by the deputies of twelve States.]
|
||||
|
||||
Delaware
|
||||
|
||||
Geo: Read
|
||||
Gunning Bedford jun
|
||||
John Dickinson
|
||||
Richard Bassett
|
||||
Jaco: Broom
|
||||
|
||||
Maryland
|
||||
|
||||
James MCHenry
|
||||
Dan of ST ThoS. Jenifer
|
||||
DanL Carroll.
|
||||
|
||||
Virginia
|
||||
|
||||
John Blair--
|
||||
James Madison Jr.
|
||||
|
||||
North Carolina
|
||||
|
||||
WM Blount
|
||||
RichD. Dobbs Spaight.
|
||||
Hu Williamson
|
||||
|
||||
South Carolina
|
||||
|
||||
J. Rutledge
|
||||
Charles 1ACotesworth Pinckney
|
||||
Charles Pinckney
|
||||
Pierce Butler.
|
||||
|
||||
Georgia
|
||||
|
||||
William Few
|
||||
Abr Baldwin
|
||||
|
||||
New Hampshire
|
||||
|
||||
John Langdon
|
||||
Nicholas Gilman
|
||||
|
||||
Massachusetts
|
||||
|
||||
Nathaniel Gorham
|
||||
Rufus King
|
||||
|
||||
Connecticut
|
||||
WM. SamL. Johnson
|
||||
Roger Sherman
|
||||
|
||||
New York
|
||||
|
||||
Alexander Hamilton
|
||||
|
||||
New Jersey
|
||||
|
||||
Wil: Livingston
|
||||
David Brearley.
|
||||
WM. Paterson.
|
||||
Jona: Dayton
|
||||
|
||||
Pennsylvania
|
||||
|
||||
B Franklin
|
||||
Thomas Mifflin
|
||||
RobT Morris
|
||||
Geo. Clymer
|
||||
ThoS. FitzSimons
|
||||
Jared Ingersoll
|
||||
James Wilson.
|
||||
Gouv Morris
|
||||
|
||||
Attest William Jackson Secretary
|
||||
END
|
||||
7
vendor/gems/highline-1.4.0/examples/password.rb
vendored
Normal file
7
vendor/gems/highline-1.4.0/examples/password.rb
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
|
||||
require "rubygems"
|
||||
require "highline/import"
|
||||
|
||||
pass = ask("Enter your password: ") { |q| q.echo = false }
|
||||
puts "Your password is #{pass}!"
|
||||
22
vendor/gems/highline-1.4.0/examples/trapping_eof.rb
vendored
Normal file
22
vendor/gems/highline-1.4.0/examples/trapping_eof.rb
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
|
||||
# trapping_eof.rb
|
||||
#
|
||||
# Created by James Edward Gray II on 2006-02-20.
|
||||
# Copyright 2006 Gray Productions. All rights reserved.
|
||||
|
||||
require "rubygems"
|
||||
require "highline/import"
|
||||
|
||||
loop do
|
||||
begin
|
||||
name = ask("What's your name?")
|
||||
break if name == "exit"
|
||||
puts "Hello, #{name}!"
|
||||
rescue EOFError # HighLine throws this if @input.eof?
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
puts "Goodbye, dear friend."
|
||||
exit
|
||||
17
vendor/gems/highline-1.4.0/examples/using_readline.rb
vendored
Normal file
17
vendor/gems/highline-1.4.0/examples/using_readline.rb
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
|
||||
# using_readline.rb
|
||||
#
|
||||
# Created by James Edward Gray II on 2005-07-06.
|
||||
# Copyright 2005 Gray Productions. All rights reserved.
|
||||
|
||||
require "rubygems"
|
||||
require "highline/import"
|
||||
|
||||
loop do
|
||||
cmd = ask("Enter command: ", %w{save load reset quit}) do |q|
|
||||
q.readline = true
|
||||
end
|
||||
say("Executing \"#{cmd}\"...")
|
||||
break if cmd == "quit"
|
||||
end
|
||||
744
vendor/gems/highline-1.4.0/lib/highline.rb
vendored
Normal file
744
vendor/gems/highline-1.4.0/lib/highline.rb
vendored
Normal file
|
|
@ -0,0 +1,744 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
|
||||
# highline.rb
|
||||
#
|
||||
# Created by James Edward Gray II on 2005-04-26.
|
||||
# Copyright 2005 Gray Productions. All rights reserved.
|
||||
#
|
||||
# See HighLine for documentation.
|
||||
#
|
||||
# This is Free Software. See LICENSE and COPYING for details.
|
||||
|
||||
require "highline/system_extensions"
|
||||
require "highline/question"
|
||||
require "highline/menu"
|
||||
require "highline/color_scheme"
|
||||
require "erb"
|
||||
require "optparse"
|
||||
require "stringio"
|
||||
require "abbrev"
|
||||
|
||||
#
|
||||
# A HighLine object is a "high-level line oriented" shell over an input and an
|
||||
# output stream. HighLine simplifies common console interaction, effectively
|
||||
# replacing puts() and gets(). User code can simply specify the question to ask
|
||||
# and any details about user interaction, then leave the rest of the work to
|
||||
# HighLine. When HighLine.ask() returns, you'll have the answer you requested,
|
||||
# even if HighLine had to ask many times, validate results, perform range
|
||||
# checking, convert types, etc.
|
||||
#
|
||||
class HighLine
|
||||
# The version of the installed library.
|
||||
VERSION = "1.4.0".freeze
|
||||
|
||||
# An internal HighLine error. User code does not need to trap this.
|
||||
class QuestionError < StandardError
|
||||
# do nothing, just creating a unique error type
|
||||
end
|
||||
|
||||
# The setting used to disable color output.
|
||||
@@use_color = true
|
||||
|
||||
# Pass +false+ to _setting_ to turn off HighLine's color escapes.
|
||||
def self.use_color=( setting )
|
||||
@@use_color = setting
|
||||
end
|
||||
|
||||
# Returns true if HighLine is currently using color escapes.
|
||||
def self.use_color?
|
||||
@@use_color
|
||||
end
|
||||
|
||||
# The setting used to disable EOF tracking.
|
||||
@@track_eof = true
|
||||
|
||||
# Pass +false+ to _setting_ to turn off HighLine's EOF tracking.
|
||||
def self.track_eof=( setting )
|
||||
@@track_eof = setting
|
||||
end
|
||||
|
||||
# Returns true if HighLine is currently tracking EOF for input.
|
||||
def self.track_eof?
|
||||
@@track_eof
|
||||
end
|
||||
|
||||
# The setting used to control color schemes.
|
||||
@@color_scheme = nil
|
||||
|
||||
# Pass ColorScheme to _setting_ to turn set a HighLine color scheme.
|
||||
def self.color_scheme=( setting )
|
||||
@@color_scheme = setting
|
||||
end
|
||||
|
||||
# Returns the current color scheme.
|
||||
def self.color_scheme
|
||||
@@color_scheme
|
||||
end
|
||||
|
||||
# Returns +true+ if HighLine is currently using a color scheme.
|
||||
def self.using_color_scheme?
|
||||
not @@color_scheme.nil?
|
||||
end
|
||||
|
||||
#
|
||||
# Embed in a String to clear all previous ANSI sequences. This *MUST* be
|
||||
# done before the program exits!
|
||||
#
|
||||
CLEAR = "\e[0m"
|
||||
# An alias for CLEAR.
|
||||
RESET = CLEAR
|
||||
# Erase the current line of terminal output.
|
||||
ERASE_LINE = "\e[K"
|
||||
# Erase the character under the cursor.
|
||||
ERASE_CHAR = "\e[P"
|
||||
# The start of an ANSI bold sequence.
|
||||
BOLD = "\e[1m"
|
||||
# The start of an ANSI dark sequence. (Terminal support uncommon.)
|
||||
DARK = "\e[2m"
|
||||
# The start of an ANSI underline sequence.
|
||||
UNDERLINE = "\e[4m"
|
||||
# An alias for UNDERLINE.
|
||||
UNDERSCORE = UNDERLINE
|
||||
# The start of an ANSI blink sequence. (Terminal support uncommon.)
|
||||
BLINK = "\e[5m"
|
||||
# The start of an ANSI reverse sequence.
|
||||
REVERSE = "\e[7m"
|
||||
# The start of an ANSI concealed sequence. (Terminal support uncommon.)
|
||||
CONCEALED = "\e[8m"
|
||||
|
||||
# Set the terminal's foreground ANSI color to black.
|
||||
BLACK = "\e[30m"
|
||||
# Set the terminal's foreground ANSI color to red.
|
||||
RED = "\e[31m"
|
||||
# Set the terminal's foreground ANSI color to green.
|
||||
GREEN = "\e[32m"
|
||||
# Set the terminal's foreground ANSI color to yellow.
|
||||
YELLOW = "\e[33m"
|
||||
# Set the terminal's foreground ANSI color to blue.
|
||||
BLUE = "\e[34m"
|
||||
# Set the terminal's foreground ANSI color to magenta.
|
||||
MAGENTA = "\e[35m"
|
||||
# Set the terminal's foreground ANSI color to cyan.
|
||||
CYAN = "\e[36m"
|
||||
# Set the terminal's foreground ANSI color to white.
|
||||
WHITE = "\e[37m"
|
||||
|
||||
# Set the terminal's background ANSI color to black.
|
||||
ON_BLACK = "\e[40m"
|
||||
# Set the terminal's background ANSI color to red.
|
||||
ON_RED = "\e[41m"
|
||||
# Set the terminal's background ANSI color to green.
|
||||
ON_GREEN = "\e[42m"
|
||||
# Set the terminal's background ANSI color to yellow.
|
||||
ON_YELLOW = "\e[43m"
|
||||
# Set the terminal's background ANSI color to blue.
|
||||
ON_BLUE = "\e[44m"
|
||||
# Set the terminal's background ANSI color to magenta.
|
||||
ON_MAGENTA = "\e[45m"
|
||||
# Set the terminal's background ANSI color to cyan.
|
||||
ON_CYAN = "\e[46m"
|
||||
# Set the terminal's background ANSI color to white.
|
||||
ON_WHITE = "\e[47m"
|
||||
|
||||
#
|
||||
# Create an instance of HighLine, connected to the streams _input_
|
||||
# and _output_.
|
||||
#
|
||||
def initialize( input = $stdin, output = $stdout,
|
||||
wrap_at = nil, page_at = nil )
|
||||
@input = input
|
||||
@output = output
|
||||
|
||||
self.wrap_at = wrap_at
|
||||
self.page_at = page_at
|
||||
|
||||
@question = nil
|
||||
@answer = nil
|
||||
@menu = nil
|
||||
@header = nil
|
||||
@prompt = nil
|
||||
@gather = nil
|
||||
@answers = nil
|
||||
@key = nil
|
||||
end
|
||||
|
||||
include HighLine::SystemExtensions
|
||||
|
||||
# The current column setting for wrapping output.
|
||||
attr_reader :wrap_at
|
||||
# The current row setting for paging output.
|
||||
attr_reader :page_at
|
||||
|
||||
#
|
||||
# A shortcut to HighLine.ask() a question that only accepts "yes" or "no"
|
||||
# answers ("y" and "n" are allowed) and returns +true+ or +false+
|
||||
# (+true+ for "yes"). If provided a +true+ value, _character_ will cause
|
||||
# HighLine to fetch a single character response.
|
||||
#
|
||||
# Raises EOFError if input is exhausted.
|
||||
#
|
||||
def agree( yes_or_no_question, character = nil )
|
||||
ask(yes_or_no_question, lambda { |yn| yn.downcase[0] == ?y}) do |q|
|
||||
q.validate = /\Ay(?:es)?|no?\Z/i
|
||||
q.responses[:not_valid] = 'Please enter "yes" or "no".'
|
||||
q.responses[:ask_on_error] = :question
|
||||
q.character = character
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# This method is the primary interface for user input. Just provide a
|
||||
# _question_ to ask the user, the _answer_type_ you want returned, and
|
||||
# optionally a code block setting up details of how you want the question
|
||||
# handled. See HighLine.say() for details on the format of _question_, and
|
||||
# HighLine::Question for more information about _answer_type_ and what's
|
||||
# valid in the code block.
|
||||
#
|
||||
# If <tt>@question</tt> is set before ask() is called, parameters are
|
||||
# ignored and that object (must be a HighLine::Question) is used to drive
|
||||
# the process instead.
|
||||
#
|
||||
# Raises EOFError if input is exhausted.
|
||||
#
|
||||
def ask( question, answer_type = String, &details ) # :yields: question
|
||||
@question ||= Question.new(question, answer_type, &details)
|
||||
|
||||
return gather if @question.gather
|
||||
|
||||
# readline() needs to handle it's own output, but readline only supports
|
||||
# full line reading. Therefore if @question.echo is anything but true,
|
||||
# the prompt will not be issued. And we have to account for that now.
|
||||
say(@question) unless (@question.readline and @question.echo == true)
|
||||
begin
|
||||
@answer = @question.answer_or_default(get_response)
|
||||
unless @question.valid_answer?(@answer)
|
||||
explain_error(:not_valid)
|
||||
raise QuestionError
|
||||
end
|
||||
|
||||
@answer = @question.convert(@answer)
|
||||
|
||||
if @question.in_range?(@answer)
|
||||
if @question.confirm
|
||||
# need to add a layer of scope to ask a question inside a
|
||||
# question, without destroying instance data
|
||||
context_change = self.class.new(@input, @output, @wrap_at, @page_at)
|
||||
if @question.confirm == true
|
||||
confirm_question = "Are you sure? "
|
||||
else
|
||||
# evaluate ERb under initial scope, so it will have
|
||||
# access to @question and @answer
|
||||
template = ERB.new(@question.confirm, nil, "%")
|
||||
confirm_question = template.result(binding)
|
||||
end
|
||||
unless context_change.agree(confirm_question)
|
||||
explain_error(nil)
|
||||
raise QuestionError
|
||||
end
|
||||
end
|
||||
|
||||
@answer
|
||||
else
|
||||
explain_error(:not_in_range)
|
||||
raise QuestionError
|
||||
end
|
||||
rescue QuestionError
|
||||
retry
|
||||
rescue ArgumentError
|
||||
explain_error(:invalid_type)
|
||||
retry
|
||||
rescue Question::NoAutoCompleteMatch
|
||||
explain_error(:no_completion)
|
||||
retry
|
||||
rescue NameError
|
||||
raise if $!.is_a?(NoMethodError)
|
||||
explain_error(:ambiguous_completion)
|
||||
retry
|
||||
ensure
|
||||
@question = nil # Reset Question object.
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# This method is HighLine's menu handler. For simple usage, you can just
|
||||
# pass all the menu items you wish to display. At that point, choose() will
|
||||
# build and display a menu, walk the user through selection, and return
|
||||
# their choice amoung the provided items. You might use this in a case
|
||||
# statement for quick and dirty menus.
|
||||
#
|
||||
# However, choose() is capable of much more. If provided, a block will be
|
||||
# passed a HighLine::Menu object to configure. Using this method, you can
|
||||
# customize all the details of menu handling from index display, to building
|
||||
# a complete shell-like menuing system. See HighLine::Menu for all the
|
||||
# methods it responds to.
|
||||
#
|
||||
# Raises EOFError if input is exhausted.
|
||||
#
|
||||
def choose( *items, &details )
|
||||
@menu = @question = Menu.new(&details)
|
||||
@menu.choices(*items) unless items.empty?
|
||||
|
||||
# Set _answer_type_ so we can double as the Question for ask().
|
||||
@menu.answer_type = if @menu.shell
|
||||
lambda do |command| # shell-style selection
|
||||
first_word = command.to_s.split.first || ""
|
||||
|
||||
options = @menu.options
|
||||
options.extend(OptionParser::Completion)
|
||||
answer = options.complete(first_word)
|
||||
|
||||
if answer.nil?
|
||||
raise Question::NoAutoCompleteMatch
|
||||
end
|
||||
|
||||
[answer.last, command.sub(/^\s*#{first_word}\s*/, "")]
|
||||
end
|
||||
else
|
||||
@menu.options # normal menu selection, by index or name
|
||||
end
|
||||
|
||||
# Provide hooks for ERb layouts.
|
||||
@header = @menu.header
|
||||
@prompt = @menu.prompt
|
||||
|
||||
if @menu.shell
|
||||
selected = ask("Ignored", @menu.answer_type)
|
||||
@menu.select(self, *selected)
|
||||
else
|
||||
selected = ask("Ignored", @menu.answer_type)
|
||||
@menu.select(self, selected)
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# This method provides easy access to ANSI color sequences, without the user
|
||||
# needing to remember to CLEAR at the end of each sequence. Just pass the
|
||||
# _string_ to color, followed by a list of _colors_ you would like it to be
|
||||
# affected by. The _colors_ can be HighLine class constants, or symbols
|
||||
# (:blue for BLUE, for example). A CLEAR will automatically be embedded to
|
||||
# the end of the returned String.
|
||||
#
|
||||
# This method returns the original _string_ unchanged if HighLine::use_color?
|
||||
# is +false+.
|
||||
#
|
||||
def color( string, *colors )
|
||||
return string unless self.class.use_color?
|
||||
|
||||
colors.map! do |c|
|
||||
if self.class.using_color_scheme? and self.class.color_scheme.include? c
|
||||
self.class.color_scheme[c]
|
||||
elsif c.is_a? Symbol
|
||||
self.class.const_get(c.to_s.upcase)
|
||||
else
|
||||
c
|
||||
end
|
||||
end
|
||||
"#{colors.flatten.join}#{string}#{CLEAR}"
|
||||
end
|
||||
|
||||
#
|
||||
# This method is a utility for quickly and easily laying out lists. It can
|
||||
# be accessed within ERb replacements of any text that will be sent to the
|
||||
# user.
|
||||
#
|
||||
# The only required parameter is _items_, which should be the Array of items
|
||||
# to list. A specified _mode_ controls how that list is formed and _option_
|
||||
# has different effects, depending on the _mode_. Recognized modes are:
|
||||
#
|
||||
# <tt>:columns_across</tt>:: _items_ will be placed in columns, flowing
|
||||
# from left to right. If given, _option_ is the
|
||||
# number of columns to be used. When absent,
|
||||
# columns will be determined based on _wrap_at_
|
||||
# or a default of 80 characters.
|
||||
# <tt>:columns_down</tt>:: Identical to <tt>:columns_across</tt>, save
|
||||
# flow goes down.
|
||||
# <tt>:inline</tt>:: All _items_ are placed on a single line. The
|
||||
# last two _items_ are separated by _option_ or
|
||||
# a default of " or ". All other _items_ are
|
||||
# separated by ", ".
|
||||
# <tt>:rows</tt>:: The default mode. Each of the _items_ is
|
||||
# placed on it's own line. The _option_
|
||||
# parameter is ignored in this mode.
|
||||
#
|
||||
# Each member of the _items_ Array is passed through ERb and thus can contain
|
||||
# their own expansions. Color escape expansions do not contribute to the
|
||||
# final field width.
|
||||
#
|
||||
def list( items, mode = :rows, option = nil )
|
||||
items = items.to_ary.map do |item|
|
||||
ERB.new(item, nil, "%").result(binding)
|
||||
end
|
||||
|
||||
case mode
|
||||
when :inline
|
||||
option = " or " if option.nil?
|
||||
|
||||
case items.size
|
||||
when 0
|
||||
""
|
||||
when 1
|
||||
items.first
|
||||
when 2
|
||||
"#{items.first}#{option}#{items.last}"
|
||||
else
|
||||
items[0..-2].join(", ") + "#{option}#{items.last}"
|
||||
end
|
||||
when :columns_across, :columns_down
|
||||
max_length = actual_length(
|
||||
items.max { |a, b| actual_length(a) <=> actual_length(b) }
|
||||
)
|
||||
|
||||
if option.nil?
|
||||
limit = @wrap_at || 80
|
||||
option = (limit + 2) / (max_length + 2)
|
||||
end
|
||||
|
||||
items = items.map do |item|
|
||||
pad = max_length + (item.length - actual_length(item))
|
||||
"%-#{pad}s" % item
|
||||
end
|
||||
row_count = (items.size / option.to_f).ceil
|
||||
|
||||
if mode == :columns_across
|
||||
rows = Array.new(row_count) { Array.new }
|
||||
items.each_with_index do |item, index|
|
||||
rows[index / option] << item
|
||||
end
|
||||
|
||||
rows.map { |row| row.join(" ") + "\n" }.join
|
||||
else
|
||||
columns = Array.new(option) { Array.new }
|
||||
items.each_with_index do |item, index|
|
||||
columns[index / row_count] << item
|
||||
end
|
||||
|
||||
list = ""
|
||||
columns.first.size.times do |index|
|
||||
list << columns.map { |column| column[index] }.
|
||||
compact.join(" ") + "\n"
|
||||
end
|
||||
list
|
||||
end
|
||||
else
|
||||
items.map { |i| "#{i}\n" }.join
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# The basic output method for HighLine objects. If the provided _statement_
|
||||
# ends with a space or tab character, a newline will not be appended (output
|
||||
# will be flush()ed). All other cases are passed straight to Kernel.puts().
|
||||
#
|
||||
# The _statement_ parameter is processed as an ERb template, supporting
|
||||
# embedded Ruby code. The template is evaluated with a binding inside
|
||||
# the HighLine instance, providing easy access to the ANSI color constants
|
||||
# and the HighLine.color() method.
|
||||
#
|
||||
def say( statement )
|
||||
statement = statement.to_str
|
||||
return unless statement.length > 0
|
||||
|
||||
template = ERB.new(statement, nil, "%")
|
||||
statement = template.result(binding)
|
||||
|
||||
statement = wrap(statement) unless @wrap_at.nil?
|
||||
statement = page_print(statement) unless @page_at.nil?
|
||||
|
||||
if statement[-1, 1] == " " or statement[-1, 1] == "\t"
|
||||
@output.print(statement)
|
||||
@output.flush
|
||||
else
|
||||
@output.puts(statement)
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Set to an integer value to cause HighLine to wrap output lines at the
|
||||
# indicated character limit. When +nil+, the default, no wrapping occurs. If
|
||||
# set to <tt>:auto</tt>, HighLine will attempt to determing the columns
|
||||
# available for the <tt>@output</tt> or use a sensible default.
|
||||
#
|
||||
def wrap_at=( setting )
|
||||
@wrap_at = setting == :auto ? output_cols : setting
|
||||
end
|
||||
|
||||
#
|
||||
# Set to an integer value to cause HighLine to page output lines over the
|
||||
# indicated line limit. When +nil+, the default, no paging occurs. If
|
||||
# set to <tt>:auto</tt>, HighLine will attempt to determing the rows available
|
||||
# for the <tt>@output</tt> or use a sensible default.
|
||||
#
|
||||
def page_at=( setting )
|
||||
@page_at = setting == :auto ? output_rows : setting
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the number of columns for the console, or a default it they cannot
|
||||
# be determined.
|
||||
#
|
||||
def output_cols
|
||||
return 80 unless @output.tty?
|
||||
terminal_size.first
|
||||
rescue
|
||||
return 80
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the number of rows for the console, or a default if they cannot be
|
||||
# determined.
|
||||
#
|
||||
def output_rows
|
||||
return 24 unless @output.tty?
|
||||
terminal_size.last
|
||||
rescue
|
||||
return 24
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
#
|
||||
# A helper method for sending the output stream and error and repeat
|
||||
# of the question.
|
||||
#
|
||||
def explain_error( error )
|
||||
say(@question.responses[error]) unless error.nil?
|
||||
if @question.responses[:ask_on_error] == :question
|
||||
say(@question)
|
||||
elsif @question.responses[:ask_on_error]
|
||||
say(@question.responses[:ask_on_error])
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Collects an Array/Hash full of answers as described in
|
||||
# HighLine::Question.gather().
|
||||
#
|
||||
# Raises EOFError if input is exhausted.
|
||||
#
|
||||
def gather( )
|
||||
@gather = @question.gather
|
||||
@answers = [ ]
|
||||
original_question = @question
|
||||
|
||||
@question.gather = false
|
||||
|
||||
case @gather
|
||||
when Integer
|
||||
@answers << ask(@question)
|
||||
@gather -= 1
|
||||
|
||||
original_question.question = ""
|
||||
until @gather.zero?
|
||||
@question = original_question
|
||||
@answers << ask(@question)
|
||||
@gather -= 1
|
||||
end
|
||||
when String, Regexp
|
||||
@answers << ask(@question)
|
||||
|
||||
original_question.question = ""
|
||||
until (@gather.is_a?(String) and @answers.last.to_s == @gather) or
|
||||
(@gather.is_a?(Regexp) and @answers.last.to_s =~ @gather)
|
||||
@question = original_question
|
||||
@answers << ask(@question)
|
||||
end
|
||||
|
||||
@answers.pop
|
||||
when Hash
|
||||
@answers = { }
|
||||
@gather.keys.sort.each do |key|
|
||||
@question = original_question
|
||||
@key = key
|
||||
@answers[key] = ask(@question)
|
||||
end
|
||||
end
|
||||
|
||||
@answers
|
||||
end
|
||||
|
||||
#
|
||||
# Read a line of input from the input stream and process whitespace as
|
||||
# requested by the Question object.
|
||||
#
|
||||
# If Question's _readline_ property is set, that library will be used to
|
||||
# fetch input. *WARNING*: This ignores the currently set input stream.
|
||||
#
|
||||
# Raises EOFError if input is exhausted.
|
||||
#
|
||||
def get_line( )
|
||||
if @question.readline
|
||||
require "readline" # load only if needed
|
||||
|
||||
# capture say()'s work in a String to feed to readline()
|
||||
old_output = @output
|
||||
@output = StringIO.new
|
||||
say(@question)
|
||||
question = @output.string
|
||||
@output = old_output
|
||||
|
||||
# prep auto-completion
|
||||
completions = @question.selection.abbrev
|
||||
Readline.completion_proc = lambda { |string| completions[string] }
|
||||
|
||||
# work-around ugly readline() warnings
|
||||
old_verbose = $VERBOSE
|
||||
$VERBOSE = nil
|
||||
answer = @question.change_case(
|
||||
@question.remove_whitespace(
|
||||
Readline.readline(question, true) ) )
|
||||
$VERBOSE = old_verbose
|
||||
|
||||
answer
|
||||
else
|
||||
raise EOFError, "The input stream is exhausted." if @@track_eof and
|
||||
@input.eof?
|
||||
|
||||
@question.change_case(@question.remove_whitespace(@input.gets))
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Return a line or character of input, as requested for this question.
|
||||
# Character input will be returned as a single character String,
|
||||
# not an Integer.
|
||||
#
|
||||
# This question's _first_answer_ will be returned instead of input, if set.
|
||||
#
|
||||
# Raises EOFError if input is exhausted.
|
||||
#
|
||||
def get_response( )
|
||||
return @question.first_answer if @question.first_answer?
|
||||
|
||||
if @question.character.nil?
|
||||
if @question.echo == true and @question.limit.nil?
|
||||
get_line
|
||||
else
|
||||
raw_no_echo_mode if stty = CHARACTER_MODE == "stty"
|
||||
|
||||
line = ""
|
||||
backspace_limit = 0
|
||||
begin
|
||||
|
||||
while character = (stty ? @input.getc : get_character(@input))
|
||||
# honor backspace and delete
|
||||
if character == 127 or character == 8
|
||||
line.slice!(-1, 1)
|
||||
backspace_limit -= 1
|
||||
else
|
||||
line << character.chr
|
||||
backspace_limit = line.size
|
||||
end
|
||||
# looking for carriage return (decimal 13) or
|
||||
# newline (decimal 10) in raw input
|
||||
break if character == 13 or character == 10 or
|
||||
(@question.limit and line.size == @question.limit)
|
||||
if @question.echo != false
|
||||
if character == 127 or character == 8
|
||||
# only backspace if we have characters on the line to
|
||||
# eliminate, otherwise we'll tromp over the prompt
|
||||
if backspace_limit >= 0 then
|
||||
@output.print("\b#{ERASE_CHAR}")
|
||||
else
|
||||
# do nothing
|
||||
end
|
||||
else
|
||||
@output.print(@question.echo)
|
||||
end
|
||||
@output.flush
|
||||
end
|
||||
end
|
||||
ensure
|
||||
restore_mode if stty
|
||||
end
|
||||
if @question.overwrite
|
||||
@output.print("\r#{ERASE_LINE}")
|
||||
@output.flush
|
||||
else
|
||||
say("\n")
|
||||
end
|
||||
|
||||
@question.change_case(@question.remove_whitespace(line))
|
||||
end
|
||||
elsif @question.character == :getc
|
||||
@question.change_case(@input.getc.chr)
|
||||
else
|
||||
response = get_character(@input).chr
|
||||
if @question.overwrite
|
||||
@output.print("\r#{ERASE_LINE}")
|
||||
@output.flush
|
||||
else
|
||||
echo = if @question.echo == true
|
||||
response
|
||||
elsif @question.echo != false
|
||||
@question.echo
|
||||
else
|
||||
""
|
||||
end
|
||||
say("#{echo}\n")
|
||||
end
|
||||
@question.change_case(response)
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Page print a series of at most _page_at_ lines for _output_. After each
|
||||
# page is printed, HighLine will pause until the user presses enter/return
|
||||
# then display the next page of data.
|
||||
#
|
||||
# Note that the final page of _output_ is *not* printed, but returned
|
||||
# instead. This is to support any special handling for the final sequence.
|
||||
#
|
||||
def page_print( output )
|
||||
lines = output.scan(/[^\n]*\n?/)
|
||||
while lines.size > @page_at
|
||||
@output.puts lines.slice!(0...@page_at).join
|
||||
@output.puts
|
||||
# Return last line if user wants to abort paging
|
||||
return (["...\n"] + lines.slice(-2,1)).join unless continue_paging?
|
||||
end
|
||||
return lines.join
|
||||
end
|
||||
|
||||
#
|
||||
# Ask user if they wish to continue paging output. Allows them to type "q" to
|
||||
# cancel the paging process.
|
||||
#
|
||||
def continue_paging?
|
||||
command = HighLine.new(@input, @output).ask(
|
||||
"-- press enter/return to continue or q to stop -- "
|
||||
) { |q| q.character = true }
|
||||
command !~ /\A[qQ]\Z/ # Only continue paging if Q was not hit.
|
||||
end
|
||||
|
||||
#
|
||||
# Wrap a sequence of _lines_ at _wrap_at_ characters per line. Existing
|
||||
# newlines will not be affected by this process, but additional newlines
|
||||
# may be added.
|
||||
#
|
||||
def wrap( lines )
|
||||
wrapped = [ ]
|
||||
lines.each do |line|
|
||||
while line =~ /([^\n]{#{@wrap_at + 1},})/
|
||||
search = $1.dup
|
||||
replace = $1.dup
|
||||
if index = replace.rindex(" ", @wrap_at)
|
||||
replace[index, 1] = "\n"
|
||||
replace.sub!(/\n[ \t]+/, "\n")
|
||||
line.sub!(search, replace)
|
||||
else
|
||||
line[@wrap_at, 0] = "\n"
|
||||
end
|
||||
end
|
||||
wrapped << line
|
||||
end
|
||||
return wrapped.join
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the length of the passed +string_with_escapes+, minus and color
|
||||
# sequence escapes.
|
||||
#
|
||||
def actual_length( string_with_escapes )
|
||||
string_with_escapes.gsub(/\e\[\d{1,2}m/, "").length
|
||||
end
|
||||
end
|
||||
120
vendor/gems/highline-1.4.0/lib/highline/color_scheme.rb
vendored
Normal file
120
vendor/gems/highline-1.4.0/lib/highline/color_scheme.rb
vendored
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
|
||||
# color_scheme.rb
|
||||
#
|
||||
# Created by Jeremy Hinegardner on 2007-01-24
|
||||
# Copyright 2007. All rights reserved
|
||||
#
|
||||
# This is Free Software. See LICENSE and COPYING for details
|
||||
|
||||
require 'highline'
|
||||
|
||||
class HighLine
|
||||
#
|
||||
# ColorScheme objects encapsulate a named set of colors to be used in the
|
||||
# HighLine.colors() method call. For example, by applying a ColorScheme that
|
||||
# has a <tt>:warning</tt> color then the following could be used:
|
||||
#
|
||||
# colors("This is a warning", :warning)
|
||||
#
|
||||
# A ColorScheme contains named sets of HighLine color constants.
|
||||
#
|
||||
# Example: Instantiating a color scheme, applying it to HighLine,
|
||||
# and using it:
|
||||
#
|
||||
# ft = HighLine::ColorScheme.new do |cs|
|
||||
# cs[:headline] = [ :bold, :yellow, :on_black ]
|
||||
# cs[:horizontal_line] = [ :bold, :white ]
|
||||
# cs[:even_row] = [ :green ]
|
||||
# cs[:odd_row] = [ :magenta ]
|
||||
# end
|
||||
#
|
||||
# HighLine.color_scheme = ft
|
||||
# say("<%= color('Headline', :headline) %>")
|
||||
# say("<%= color('-'*20, :horizontal_line) %>")
|
||||
# i = true
|
||||
# ("A".."D").each do |row|
|
||||
# if i then
|
||||
# say("<%= color('#{row}', :even_row ) %>")
|
||||
# else
|
||||
# say("<%= color('#{row}', :odd_row) %>")
|
||||
# end
|
||||
# i = !i
|
||||
# end
|
||||
#
|
||||
#
|
||||
class ColorScheme
|
||||
#
|
||||
# Create an instance of HighLine::ColorScheme. The customization can
|
||||
# happen as a passed in Hash or via the yielded block. Key's are
|
||||
# converted to <tt>:symbols</tt> and values are converted to HighLine
|
||||
# constants.
|
||||
#
|
||||
def initialize( h = nil )
|
||||
@scheme = Hash.new
|
||||
load_from_hash(h) unless h.nil?
|
||||
yield self if block_given?
|
||||
end
|
||||
|
||||
# Load multiple colors from key/value pairs.
|
||||
def load_from_hash( h )
|
||||
h.each_pair do |color_tag, constants|
|
||||
self[color_tag] = constants
|
||||
end
|
||||
end
|
||||
|
||||
# Does this color scheme include the given tag name?
|
||||
def include?( color_tag )
|
||||
@scheme.keys.include?(to_symbol(color_tag))
|
||||
end
|
||||
|
||||
# Allow the scheme to be accessed like a Hash.
|
||||
def []( color_tag )
|
||||
@scheme[to_symbol(color_tag)]
|
||||
end
|
||||
|
||||
# Allow the scheme to be set like a Hash.
|
||||
def []=( color_tag, constants )
|
||||
@scheme[to_symbol(color_tag)] = constants.map { |c| to_constant(c) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Return a normalized representation of a color name.
|
||||
def to_symbol( t )
|
||||
t.to_s.downcase
|
||||
end
|
||||
|
||||
# Return a normalized representation of a color setting.
|
||||
def to_constant( v )
|
||||
v = v.to_s if v.is_a?(Symbol)
|
||||
if v.is_a?(String) then
|
||||
HighLine.const_get(v.upcase)
|
||||
else
|
||||
v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# A sample ColorScheme.
|
||||
class SampleColorScheme < ColorScheme
|
||||
#
|
||||
# Builds the sample scheme with settings for <tt>:critical</tt>,
|
||||
# <tt>:error</tt>, <tt>:warning</tt>, <tt>:notice</tt>, <tt>:info</tt>,
|
||||
# <tt>:debug</tt>, <tt>:row_even</tt>, and <tt>:row_odd</tt> colors.
|
||||
#
|
||||
def initialize( h = nil )
|
||||
scheme = {
|
||||
:critical => [ :yellow, :on_red ],
|
||||
:error => [ :bold, :red ],
|
||||
:warning => [ :bold, :yellow ],
|
||||
:notice => [ :bold, :magenta ],
|
||||
:info => [ :bold, :cyan ],
|
||||
:debug => [ :bold, :green ],
|
||||
:row_even => [ :cyan ],
|
||||
:row_odd => [ :magenta ]
|
||||
}
|
||||
super(scheme)
|
||||
end
|
||||
end
|
||||
end
|
||||
43
vendor/gems/highline-1.4.0/lib/highline/import.rb
vendored
Normal file
43
vendor/gems/highline-1.4.0/lib/highline/import.rb
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
|
||||
# import.rb
|
||||
#
|
||||
# Created by James Edward Gray II on 2005-04-26.
|
||||
# Copyright 2005 Gray Productions. All rights reserved.
|
||||
#
|
||||
# This is Free Software. See LICENSE and COPYING for details.
|
||||
|
||||
require "highline"
|
||||
require "forwardable"
|
||||
|
||||
$terminal = HighLine.new
|
||||
|
||||
#
|
||||
# <tt>require "highline/import"</tt> adds shortcut methods to Kernel, making
|
||||
# agree(), ask(), choose() and say() globally available. This is handy for
|
||||
# quick and dirty input and output. These methods use the HighLine object in
|
||||
# the global variable <tt>$terminal</tt>, which is initialized to used
|
||||
# <tt>$stdin</tt> and <tt>$stdout</tt> (you are free to change this).
|
||||
# Otherwise, these methods are identical to their HighLine counterparts, see that
|
||||
# class for detailed explanations.
|
||||
#
|
||||
module Kernel
|
||||
extend Forwardable
|
||||
def_delegators :$terminal, :agree, :ask, :choose, :say
|
||||
end
|
||||
|
||||
class Object
|
||||
#
|
||||
# Tries this object as a _first_answer_ for a HighLine::Question. See that
|
||||
# attribute for details.
|
||||
#
|
||||
# *Warning*: This Object will be passed to String() before set.
|
||||
#
|
||||
def or_ask( *args, &details )
|
||||
ask(*args) do |question|
|
||||
question.first_answer = String(self) unless nil?
|
||||
|
||||
details.call(question) unless details.nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
395
vendor/gems/highline-1.4.0/lib/highline/menu.rb
vendored
Normal file
395
vendor/gems/highline-1.4.0/lib/highline/menu.rb
vendored
Normal file
|
|
@ -0,0 +1,395 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
|
||||
# menu.rb
|
||||
#
|
||||
# Created by Gregory Thomas Brown on 2005-05-10.
|
||||
# Copyright 2005. All rights reserved.
|
||||
#
|
||||
# This is Free Software. See LICENSE and COPYING for details.
|
||||
|
||||
require "highline/question"
|
||||
|
||||
class HighLine
|
||||
#
|
||||
# Menu objects encapsulate all the details of a call to HighLine.choose().
|
||||
# Using the accessors and Menu.choice() and Menu.choices(), the block passed
|
||||
# to HighLine.choose() can detail all aspects of menu display and control.
|
||||
#
|
||||
class Menu < Question
|
||||
#
|
||||
# Create an instance of HighLine::Menu. All customization is done
|
||||
# through the passed block, which should call accessors and choice() and
|
||||
# choices() as needed to define the Menu. Note that Menus are also
|
||||
# Questions, so all that functionality is available to the block as
|
||||
# well.
|
||||
#
|
||||
def initialize( )
|
||||
#
|
||||
# Initialize Question objects with ignored values, we'll
|
||||
# adjust ours as needed.
|
||||
#
|
||||
super("Ignored", [ ], &nil) # avoiding passing the block along
|
||||
|
||||
@items = [ ]
|
||||
@hidden_items = [ ]
|
||||
@help = Hash.new("There's no help for that topic.")
|
||||
|
||||
@index = :number
|
||||
@index_suffix = ". "
|
||||
@select_by = :index_or_name
|
||||
@flow = :rows
|
||||
@list_option = nil
|
||||
@header = nil
|
||||
@prompt = "? "
|
||||
@layout = :list
|
||||
@shell = false
|
||||
@nil_on_handled = false
|
||||
|
||||
# Override Questions responses, we'll set our own.
|
||||
@responses = { }
|
||||
# Context for action code.
|
||||
@highline = nil
|
||||
|
||||
yield self if block_given?
|
||||
|
||||
init_help if @shell and not @help.empty?
|
||||
update_responses # rebuild responses based on our settings
|
||||
end
|
||||
|
||||
#
|
||||
# An _index_ to append to each menu item in display. See
|
||||
# Menu.index=() for details.
|
||||
#
|
||||
attr_reader :index
|
||||
#
|
||||
# The String placed between an _index_ and a menu item. Defaults to
|
||||
# ". ". Switches to " ", when _index_ is set to a String (like "-").
|
||||
#
|
||||
attr_accessor :index_suffix
|
||||
#
|
||||
# The _select_by_ attribute controls how the user is allowed to pick a
|
||||
# menu item. The available choices are:
|
||||
#
|
||||
# <tt>:index</tt>:: The user is allowed to type the numerical
|
||||
# or alphetical index for their selection.
|
||||
# <tt>:index_or_name</tt>:: Allows both methods from the
|
||||
# <tt>:index</tt> option and the
|
||||
# <tt>:name</tt> option.
|
||||
# <tt>:name</tt>:: Menu items are selected by typing a portion
|
||||
# of the item name that will be
|
||||
# auto-completed.
|
||||
#
|
||||
attr_accessor :select_by
|
||||
#
|
||||
# This attribute is passed directly on as the mode to HighLine.list() by
|
||||
# all the preset layouts. See that method for appropriate settings.
|
||||
#
|
||||
attr_accessor :flow
|
||||
#
|
||||
# This setting is passed on as the third parameter to HighLine.list()
|
||||
# by all the preset layouts. See that method for details of its
|
||||
# effects. Defaults to +nil+.
|
||||
#
|
||||
attr_accessor :list_option
|
||||
#
|
||||
# Used by all the preset layouts to display title and/or introductory
|
||||
# information, when set. Defaults to +nil+.
|
||||
#
|
||||
attr_accessor :header
|
||||
#
|
||||
# Used by all the preset layouts to ask the actual question to fetch a
|
||||
# menu selection from the user. Defaults to "? ".
|
||||
#
|
||||
attr_accessor :prompt
|
||||
#
|
||||
# An ERb _layout_ to use when displaying this Menu object. See
|
||||
# Menu.layout=() for details.
|
||||
#
|
||||
attr_reader :layout
|
||||
#
|
||||
# When set to +true+, responses are allowed to be an entire line of
|
||||
# input, including details beyond the command itself. Only the first
|
||||
# "word" of input will be matched against the menu choices, but both the
|
||||
# command selected and the rest of the line will be passed to provided
|
||||
# action blocks. Defaults to +false+.
|
||||
#
|
||||
attr_accessor :shell
|
||||
#
|
||||
# When +true+, any selected item handled by provided action code, will
|
||||
# return +nil+, instead of the results to the action code. This may
|
||||
# prove handy when dealing with mixed menus where only the names of
|
||||
# items without any code (and +nil+, of course) will be returned.
|
||||
# Defaults to +false+.
|
||||
#
|
||||
attr_accessor :nil_on_handled
|
||||
|
||||
#
|
||||
# Adds _name_ to the list of available menu items. Menu items will be
|
||||
# displayed in the order they are added.
|
||||
#
|
||||
# An optional _action_ can be associated with this name and if provided,
|
||||
# it will be called if the item is selected. The result of the method
|
||||
# will be returned, unless _nil_on_handled_ is set (when you would get
|
||||
# +nil+ instead). In _shell_ mode, a provided block will be passed the
|
||||
# command chosen and any details that followed the command. Otherwise,
|
||||
# just the command is passed. The <tt>@highline</tt> variable is set to
|
||||
# the current HighLine context before the action code is called and can
|
||||
# thus be used for adding output and the like.
|
||||
#
|
||||
def choice( name, help = nil, &action )
|
||||
@items << [name, action]
|
||||
|
||||
@help[name.to_s.downcase] = help unless help.nil?
|
||||
end
|
||||
|
||||
#
|
||||
# A shortcut for multiple calls to the sister method choice(). <b>Be
|
||||
# warned:</b> An _action_ set here will apply to *all* provided
|
||||
# _names_. This is considered to be a feature, so you can easily
|
||||
# hand-off interface processing to a different chunk of code.
|
||||
#
|
||||
def choices( *names, &action )
|
||||
names.each { |n| choice(n, &action) }
|
||||
end
|
||||
|
||||
# Identical to choice(), but the item will not be listed for the user.
|
||||
def hidden( name, help = nil, &action )
|
||||
@hidden_items << [name, action]
|
||||
|
||||
@help[name.to_s.downcase] = help unless help.nil?
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the indexing style for this Menu object. Indexes are appended to
|
||||
# menu items, when displayed in list form. The available settings are:
|
||||
#
|
||||
# <tt>:number</tt>:: Menu items will be indexed numerically, starting
|
||||
# with 1. This is the default method of indexing.
|
||||
# <tt>:letter</tt>:: Items will be indexed alphabetically, starting
|
||||
# with a.
|
||||
# <tt>:none</tt>:: No index will be appended to menu items.
|
||||
# <i>any String</i>:: Will be used as the literal _index_.
|
||||
#
|
||||
# Setting the _index_ to <tt>:none</tt> a literal String, also adjusts
|
||||
# _index_suffix_ to a single space and _select_by_ to <tt>:none</tt>.
|
||||
# Because of this, you should make a habit of setting the _index_ first.
|
||||
#
|
||||
def index=( style )
|
||||
@index = style
|
||||
|
||||
# Default settings.
|
||||
if @index == :none or @index.is_a?(String)
|
||||
@index_suffix = " "
|
||||
@select_by = :name
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Initializes the help system by adding a <tt>:help</tt> choice, some
|
||||
# action code, and the default help listing.
|
||||
#
|
||||
def init_help( )
|
||||
return if @items.include?(:help)
|
||||
|
||||
topics = @help.keys.sort
|
||||
help_help = @help.include?("help") ? @help["help"] :
|
||||
"This command will display helpful messages about " +
|
||||
"functionality, like this one. To see the help for " +
|
||||
"a specific topic enter:\n\thelp [TOPIC]\nTry asking " +
|
||||
"for help on any of the following:\n\n" +
|
||||
"<%= list(#{topics.inspect}, :columns_across) %>"
|
||||
choice(:help, help_help) do |command, topic|
|
||||
topic.strip!
|
||||
topic.downcase!
|
||||
if topic.empty?
|
||||
@highline.say(@help["help"])
|
||||
else
|
||||
@highline.say("= #{topic}\n\n#{@help[topic]}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Used to set help for arbitrary topics. Use the topic <tt>"help"</tt>
|
||||
# to override the default message.
|
||||
#
|
||||
def help( topic, help )
|
||||
@help[topic] = help
|
||||
end
|
||||
|
||||
#
|
||||
# Setting a _layout_ with this method also adjusts some other attributes
|
||||
# of the Menu object, to ideal defaults for the chosen _layout_. To
|
||||
# account for that, you probably want to set a _layout_ first in your
|
||||
# configuration block, if needed.
|
||||
#
|
||||
# Accepted settings for _layout_ are:
|
||||
#
|
||||
# <tt>:list</tt>:: The default _layout_. The _header_ if set
|
||||
# will appear at the top on its own line with
|
||||
# a trailing colon. Then the list of menu
|
||||
# items will follow. Finally, the _prompt_
|
||||
# will be used as the ask()-like question.
|
||||
# <tt>:one_line</tt>:: A shorter _layout_ that fits on one line.
|
||||
# The _header_ comes first followed by a
|
||||
# colon and spaces, then the _prompt_ with menu
|
||||
# items between trailing parenthesis.
|
||||
# <tt>:menu_only</tt>:: Just the menu items, followed up by a likely
|
||||
# short _prompt_.
|
||||
# <i>any ERb String</i>:: Will be taken as the literal _layout_. This
|
||||
# String can access <tt>@header</tt>,
|
||||
# <tt>@menu</tt> and <tt>@prompt</tt>, but is
|
||||
# otherwise evaluated in the typical HighLine
|
||||
# context, to provide access to utilities like
|
||||
# HighLine.list() primarily.
|
||||
#
|
||||
# If set to either <tt>:one_line</tt>, or <tt>:menu_only</tt>, _index_
|
||||
# will default to <tt>:none</tt> and _flow_ will default to
|
||||
# <tt>:inline</tt>.
|
||||
#
|
||||
def layout=( new_layout )
|
||||
@layout = new_layout
|
||||
|
||||
# Default settings.
|
||||
case @layout
|
||||
when :one_line, :menu_only
|
||||
self.index = :none
|
||||
@flow = :inline
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# This method returns all possible options for auto-completion, based
|
||||
# on the settings of _index_ and _select_by_.
|
||||
#
|
||||
def options( )
|
||||
# add in any hidden menu commands
|
||||
@items.concat(@hidden_items)
|
||||
|
||||
by_index = if @index == :letter
|
||||
l_index = "`"
|
||||
@items.map { "#{l_index.succ!}" }
|
||||
else
|
||||
(1 .. @items.size).collect { |s| String(s) }
|
||||
end
|
||||
by_name = @items.collect { |c| c.first }
|
||||
|
||||
case @select_by
|
||||
when :index then
|
||||
by_index
|
||||
when :name
|
||||
by_name
|
||||
else
|
||||
by_index + by_name
|
||||
end
|
||||
ensure
|
||||
# make sure the hidden items are removed, before we return
|
||||
@items.slice!(@items.size - @hidden_items.size, @hidden_items.size)
|
||||
end
|
||||
|
||||
#
|
||||
# This method processes the auto-completed user selection, based on the
|
||||
# rules for this Menu object. If an action was provided for the
|
||||
# selection, it will be executed as described in Menu.choice().
|
||||
#
|
||||
def select( highline_context, selection, details = nil )
|
||||
# add in any hidden menu commands
|
||||
@items.concat(@hidden_items)
|
||||
|
||||
# Find the selected action.
|
||||
name, action = if selection =~ /^\d+$/
|
||||
@items[selection.to_i - 1]
|
||||
else
|
||||
l_index = "`"
|
||||
index = @items.map { "#{l_index.succ!}" }.index(selection)
|
||||
@items.find { |c| c.first == selection } or @items[index]
|
||||
end
|
||||
|
||||
# Run or return it.
|
||||
if not @nil_on_handled and not action.nil?
|
||||
@highline = highline_context
|
||||
if @shell
|
||||
action.call(name, details)
|
||||
else
|
||||
action.call(name)
|
||||
end
|
||||
elsif action.nil?
|
||||
name
|
||||
else
|
||||
nil
|
||||
end
|
||||
ensure
|
||||
# make sure the hidden items are removed, before we return
|
||||
@items.slice!(@items.size - @hidden_items.size, @hidden_items.size)
|
||||
end
|
||||
|
||||
#
|
||||
# Allows Menu objects to pass as Arrays, for use with HighLine.list().
|
||||
# This method returns all menu items to be displayed, complete with
|
||||
# indexes.
|
||||
#
|
||||
def to_ary( )
|
||||
case @index
|
||||
when :number
|
||||
@items.map { |c| "#{@items.index(c) + 1}#{@index_suffix}#{c.first}" }
|
||||
when :letter
|
||||
l_index = "`"
|
||||
@items.map { |c| "#{l_index.succ!}#{@index_suffix}#{c.first}" }
|
||||
when :none
|
||||
@items.map { |c| "#{c.first}" }
|
||||
else
|
||||
@items.map { |c| "#{index}#{@index_suffix}#{c.first}" }
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Allows Menu to behave as a String, just like Question. Returns the
|
||||
# _layout_ to be rendered, which is used by HighLine.say().
|
||||
#
|
||||
def to_str( )
|
||||
case @layout
|
||||
when :list
|
||||
'<%= if @header.nil? then '' else "#{@header}:\n" end %>' +
|
||||
"<%= list( @menu, #{@flow.inspect},
|
||||
#{@list_option.inspect} ) %>" +
|
||||
"<%= @prompt %>"
|
||||
when :one_line
|
||||
'<%= if @header.nil? then '' else "#{@header}: " end %>' +
|
||||
"<%= @prompt %>" +
|
||||
"(<%= list( @menu, #{@flow.inspect},
|
||||
#{@list_option.inspect} ) %>)" +
|
||||
"<%= @prompt[/\s*$/] %>"
|
||||
when :menu_only
|
||||
"<%= list( @menu, #{@flow.inspect},
|
||||
#{@list_option.inspect} ) %><%= @prompt %>"
|
||||
else
|
||||
@layout
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# This method will update the intelligent responses to account for
|
||||
# Menu specific differences. This overrides the work done by
|
||||
# Question.build_responses().
|
||||
#
|
||||
def update_responses( )
|
||||
append_default unless default.nil?
|
||||
@responses = { :ambiguous_completion =>
|
||||
"Ambiguous choice. " +
|
||||
"Please choose one of #{options.inspect}.",
|
||||
:ask_on_error =>
|
||||
"? ",
|
||||
:invalid_type =>
|
||||
"You must enter a valid #{options}.",
|
||||
:no_completion =>
|
||||
"You must choose one of " +
|
||||
"#{options.inspect}.",
|
||||
:not_in_range =>
|
||||
"Your answer isn't within the expected range " +
|
||||
"(#{expected_range}).",
|
||||
:not_valid =>
|
||||
"Your answer isn't valid (must match " +
|
||||
"#{@validate.inspect})." }.merge(@responses)
|
||||
end
|
||||
end
|
||||
end
|
||||
462
vendor/gems/highline-1.4.0/lib/highline/question.rb
vendored
Normal file
462
vendor/gems/highline-1.4.0/lib/highline/question.rb
vendored
Normal file
|
|
@ -0,0 +1,462 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
|
||||
# question.rb
|
||||
#
|
||||
# Created by James Edward Gray II on 2005-04-26.
|
||||
# Copyright 2005 Gray Productions. All rights reserved.
|
||||
#
|
||||
# This is Free Software. See LICENSE and COPYING for details.
|
||||
|
||||
require "optparse"
|
||||
require "date"
|
||||
require "pathname"
|
||||
|
||||
class HighLine
|
||||
#
|
||||
# Question objects contain all the details of a single invocation of
|
||||
# HighLine.ask(). The object is initialized by the parameters passed to
|
||||
# HighLine.ask() and then queried to make sure each step of the input
|
||||
# process is handled according to the users wishes.
|
||||
#
|
||||
class Question
|
||||
# An internal HighLine error. User code does not need to trap this.
|
||||
class NoAutoCompleteMatch < StandardError
|
||||
# do nothing, just creating a unique error type
|
||||
end
|
||||
|
||||
#
|
||||
# Create an instance of HighLine::Question. Expects a _question_ to ask
|
||||
# (can be <tt>""</tt>) and an _answer_type_ to convert the answer to.
|
||||
# The _answer_type_ parameter must be a type recongnized by
|
||||
# Question.convert(). If given, a block is yeilded the new Question
|
||||
# object to allow custom initializaion.
|
||||
#
|
||||
def initialize( question, answer_type )
|
||||
# initialize instance data
|
||||
@question = question
|
||||
@answer_type = answer_type
|
||||
|
||||
@character = nil
|
||||
@limit = nil
|
||||
@echo = true
|
||||
@readline = false
|
||||
@whitespace = :strip
|
||||
@case = nil
|
||||
@default = nil
|
||||
@validate = nil
|
||||
@above = nil
|
||||
@below = nil
|
||||
@in = nil
|
||||
@confirm = nil
|
||||
@gather = false
|
||||
@first_answer = nil
|
||||
@directory = Pathname.new(File.expand_path(File.dirname($0)))
|
||||
@glob = "*"
|
||||
@responses = Hash.new
|
||||
@overwrite = false
|
||||
|
||||
# allow block to override settings
|
||||
yield self if block_given?
|
||||
|
||||
# finalize responses based on settings
|
||||
build_responses
|
||||
end
|
||||
|
||||
# The ERb template of the question to be asked.
|
||||
attr_accessor :question
|
||||
# The type that will be used to convert this answer.
|
||||
attr_accessor :answer_type
|
||||
#
|
||||
# Can be set to +true+ to use HighLine's cross-platform character reader
|
||||
# instead of fetching an entire line of input. (Note: HighLine's
|
||||
# character reader *ONLY* supports STDIN on Windows and Unix.) Can also
|
||||
# be set to <tt>:getc</tt> to use that method on the input stream.
|
||||
#
|
||||
# *WARNING*: The _echo_ and _overwrite_ attributes for a question are
|
||||
# ignored when using the <tt>:getc</tt> method.
|
||||
#
|
||||
attr_accessor :character
|
||||
#
|
||||
# Allows you to set a character limit for input.
|
||||
#
|
||||
# *WARNING*: This option forces a character by character read.
|
||||
#
|
||||
attr_accessor :limit
|
||||
#
|
||||
# Can be set to +true+ or +false+ to control whether or not input will
|
||||
# be echoed back to the user. A setting of +true+ will cause echo to
|
||||
# match input, but any other true value will be treated as to String to
|
||||
# echo for each character typed.
|
||||
#
|
||||
# This requires HighLine's character reader. See the _character_
|
||||
# attribute for details.
|
||||
#
|
||||
# *Note*: When using HighLine to manage echo on Unix based systems, we
|
||||
# recommend installing the termios gem. Without it, it's possible to type
|
||||
# fast enough to have letters still show up (when reading character by
|
||||
# character only).
|
||||
#
|
||||
attr_accessor :echo
|
||||
#
|
||||
# Use the Readline library to fetch input. This allows input editing as
|
||||
# well as keeping a history. In addition, tab will auto-complete
|
||||
# within an Array of choices or a file listing.
|
||||
#
|
||||
# *WARNING*: This option is incompatible with all of HighLine's
|
||||
# character reading modes and it causes HighLine to ignore the
|
||||
# specified _input_ stream.
|
||||
#
|
||||
attr_accessor :readline
|
||||
#
|
||||
# Used to control whitespace processing for the answer to this question.
|
||||
# See HighLine::Question.remove_whitespace() for acceptable settings.
|
||||
#
|
||||
attr_accessor :whitespace
|
||||
#
|
||||
# Used to control character case processing for the answer to this question.
|
||||
# See HighLine::Question.change_case() for acceptable settings.
|
||||
#
|
||||
attr_accessor :case
|
||||
# Used to provide a default answer to this question.
|
||||
attr_accessor :default
|
||||
#
|
||||
# If set to a Regexp, the answer must match (before type conversion).
|
||||
# Can also be set to a Proc which will be called with the provided
|
||||
# answer to validate with a +true+ or +false+ return.
|
||||
#
|
||||
attr_accessor :validate
|
||||
# Used to control range checks for answer.
|
||||
attr_accessor :above, :below
|
||||
# If set, answer must pass an include?() check on this object.
|
||||
attr_accessor :in
|
||||
#
|
||||
# Asks a yes or no confirmation question, to ensure a user knows what
|
||||
# they have just agreed to. If set to +true+ the question will be,
|
||||
# "Are you sure? " Any other true value for this attribute is assumed
|
||||
# to be the question to ask. When +false+ or +nil+ (the default),
|
||||
# answers are not confirmed.
|
||||
#
|
||||
attr_accessor :confirm
|
||||
#
|
||||
# When set, the user will be prompted for multiple answers which will
|
||||
# be collected into an Array or Hash and returned as the final answer.
|
||||
#
|
||||
# You can set _gather_ to an Integer to have an Array of exactly that
|
||||
# many answers collected, or a String/Regexp to match an end input which
|
||||
# will not be returned in the Array.
|
||||
#
|
||||
# Optionally _gather_ can be set to a Hash. In this case, the question
|
||||
# will be asked once for each key and the answers will be returned in a
|
||||
# Hash, mapped by key. The <tt>@key</tt> variable is set before each
|
||||
# question is evaluated, so you can use it in your question.
|
||||
#
|
||||
attr_accessor :gather
|
||||
#
|
||||
# When set to a non *nil* value, this will be tried as an answer to the
|
||||
# question. If this answer passes validations, it will become the result
|
||||
# without the user ever being prompted. Otherwise this value is discarded,
|
||||
# and this Question is resolved as a normal call to HighLine.ask().
|
||||
#
|
||||
attr_writer :first_answer
|
||||
#
|
||||
# The directory from which a user will be allowed to select files, when
|
||||
# File or Pathname is specified as an _answer_type_. Initially set to
|
||||
# <tt>Pathname.new(File.expand_path(File.dirname($0)))</tt>.
|
||||
#
|
||||
attr_accessor :directory
|
||||
#
|
||||
# The glob pattern used to limit file selection when File or Pathname is
|
||||
# specified as an _answer_type_. Initially set to <tt>"*"</tt>.
|
||||
#
|
||||
attr_accessor :glob
|
||||
#
|
||||
# A Hash that stores the various responses used by HighLine to notify
|
||||
# the user. The currently used responses and their purpose are as
|
||||
# follows:
|
||||
#
|
||||
# <tt>:ambiguous_completion</tt>:: Used to notify the user of an
|
||||
# ambiguous answer the auto-completion
|
||||
# system cannot resolve.
|
||||
# <tt>:ask_on_error</tt>:: This is the question that will be
|
||||
# redisplayed to the user in the event
|
||||
# of an error. Can be set to
|
||||
# <tt>:question</tt> to repeat the
|
||||
# original question.
|
||||
# <tt>:invalid_type</tt>:: The error message shown when a type
|
||||
# conversion fails.
|
||||
# <tt>:no_completion</tt>:: Used to notify the user that their
|
||||
# selection does not have a valid
|
||||
# auto-completion match.
|
||||
# <tt>:not_in_range</tt>:: Used to notify the user that a
|
||||
# provided answer did not satisfy
|
||||
# the range requirement tests.
|
||||
# <tt>:not_valid</tt>:: The error message shown when
|
||||
# validation checks fail.
|
||||
#
|
||||
attr_reader :responses
|
||||
#
|
||||
# When set to +true+ the question is asked, but output does not progress to
|
||||
# the next line. The Cursor is moved back to the beginning of the question
|
||||
# line and it is cleared so that all the contents of the line disappear from
|
||||
# the screen.
|
||||
#
|
||||
attr_accessor :overwrite
|
||||
|
||||
#
|
||||
# Returns the provided _answer_string_ or the default answer for this
|
||||
# Question if a default was set and the answer is empty.
|
||||
#
|
||||
def answer_or_default( answer_string )
|
||||
if answer_string.length == 0 and not @default.nil?
|
||||
@default
|
||||
else
|
||||
answer_string
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Called late in the initialization process to build intelligent
|
||||
# responses based on the details of this Question object.
|
||||
#
|
||||
def build_responses( )
|
||||
### WARNING: This code is quasi-duplicated in ###
|
||||
### Menu.update_responses(). Check there too when ###
|
||||
### making changes! ###
|
||||
append_default unless default.nil?
|
||||
@responses = { :ambiguous_completion =>
|
||||
"Ambiguous choice. " +
|
||||
"Please choose one of #{@answer_type.inspect}.",
|
||||
:ask_on_error =>
|
||||
"? ",
|
||||
:invalid_type =>
|
||||
"You must enter a valid #{@answer_type}.",
|
||||
:no_completion =>
|
||||
"You must choose one of " +
|
||||
"#{@answer_type.inspect}.",
|
||||
:not_in_range =>
|
||||
"Your answer isn't within the expected range " +
|
||||
"(#{expected_range}).",
|
||||
:not_valid =>
|
||||
"Your answer isn't valid (must match " +
|
||||
"#{@validate.inspect})." }.merge(@responses)
|
||||
### WARNING: This code is quasi-duplicated in ###
|
||||
### Menu.update_responses(). Check there too when ###
|
||||
### making changes! ###
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the provided _answer_string_ after changing character case by
|
||||
# the rules of this Question. Valid settings for whitespace are:
|
||||
#
|
||||
# +nil+:: Do not alter character case.
|
||||
# (Default.)
|
||||
# <tt>:up</tt>:: Calls upcase().
|
||||
# <tt>:upcase</tt>:: Calls upcase().
|
||||
# <tt>:down</tt>:: Calls downcase().
|
||||
# <tt>:downcase</tt>:: Calls downcase().
|
||||
# <tt>:capitalize</tt>:: Calls capitalize().
|
||||
#
|
||||
# An unrecognized choice (like <tt>:none</tt>) is treated as +nil+.
|
||||
#
|
||||
def change_case( answer_string )
|
||||
if [:up, :upcase].include?(@case)
|
||||
answer_string.upcase
|
||||
elsif [:down, :downcase].include?(@case)
|
||||
answer_string.downcase
|
||||
elsif @case == :capitalize
|
||||
answer_string.capitalize
|
||||
else
|
||||
answer_string
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Transforms the given _answer_string_ into the expected type for this
|
||||
# Question. Currently supported conversions are:
|
||||
#
|
||||
# <tt>[...]</tt>:: Answer must be a member of the passed Array.
|
||||
# Auto-completion is used to expand partial
|
||||
# answers.
|
||||
# <tt>lambda {...}</tt>:: Answer is passed to lambda for conversion.
|
||||
# Date:: Date.parse() is called with answer.
|
||||
# DateTime:: DateTime.parse() is called with answer.
|
||||
# File:: The entered file name is auto-completed in
|
||||
# terms of _directory_ + _glob_, opened, and
|
||||
# returned.
|
||||
# Float:: Answer is converted with Kernel.Float().
|
||||
# Integer:: Answer is converted with Kernel.Integer().
|
||||
# +nil+:: Answer is left in String format. (Default.)
|
||||
# Pathname:: Same as File, save that a Pathname object is
|
||||
# returned.
|
||||
# String:: Answer is converted with Kernel.String().
|
||||
# Regexp:: Answer is fed to Regexp.new().
|
||||
# Symbol:: The method to_sym() is called on answer and
|
||||
# the result returned.
|
||||
# <i>any other Class</i>:: The answer is passed on to
|
||||
# <tt>Class.parse()</tt>.
|
||||
#
|
||||
# This method throws ArgumentError, if the conversion cannot be
|
||||
# completed for any reason.
|
||||
#
|
||||
def convert( answer_string )
|
||||
if @answer_type.nil?
|
||||
answer_string
|
||||
elsif [Float, Integer, String].include?(@answer_type)
|
||||
Kernel.send(@answer_type.to_s.to_sym, answer_string)
|
||||
elsif @answer_type == Symbol
|
||||
answer_string.to_sym
|
||||
elsif @answer_type == Regexp
|
||||
Regexp.new(answer_string)
|
||||
elsif @answer_type.is_a?(Array) or [File, Pathname].include?(@answer_type)
|
||||
# cheating, using OptionParser's Completion module
|
||||
choices = selection
|
||||
choices.extend(OptionParser::Completion)
|
||||
answer = choices.complete(answer_string)
|
||||
if answer.nil?
|
||||
raise NoAutoCompleteMatch
|
||||
end
|
||||
if @answer_type.is_a?(Array)
|
||||
answer.last
|
||||
elsif @answer_type == File
|
||||
File.open(File.join(@directory.to_s, answer.last))
|
||||
else
|
||||
Pathname.new(File.join(@directory.to_s, answer.last))
|
||||
end
|
||||
elsif [Date, DateTime].include?(@answer_type) or @answer_type.is_a?(Class)
|
||||
@answer_type.parse(answer_string)
|
||||
elsif @answer_type.is_a?(Proc)
|
||||
@answer_type[answer_string]
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a english explination of the current range settings.
|
||||
def expected_range( )
|
||||
expected = [ ]
|
||||
|
||||
expected << "above #{@above}" unless @above.nil?
|
||||
expected << "below #{@below}" unless @below.nil?
|
||||
expected << "included in #{@in.inspect}" unless @in.nil?
|
||||
|
||||
case expected.size
|
||||
when 0 then ""
|
||||
when 1 then expected.first
|
||||
when 2 then expected.join(" and ")
|
||||
else expected[0..-2].join(", ") + ", and #{expected.last}"
|
||||
end
|
||||
end
|
||||
|
||||
# Returns _first_answer_, which will be unset following this call.
|
||||
def first_answer( )
|
||||
@first_answer
|
||||
ensure
|
||||
@first_answer = nil
|
||||
end
|
||||
|
||||
# Returns true if _first_answer_ is set.
|
||||
def first_answer?( )
|
||||
not @first_answer.nil?
|
||||
end
|
||||
|
||||
#
|
||||
# Returns +true+ if the _answer_object_ is greater than the _above_
|
||||
# attribute, less than the _below_ attribute and included?()ed in the
|
||||
# _in_ attribute. Otherwise, +false+ is returned. Any +nil+ attributes
|
||||
# are not checked.
|
||||
#
|
||||
def in_range?( answer_object )
|
||||
(@above.nil? or answer_object > @above) and
|
||||
(@below.nil? or answer_object < @below) and
|
||||
(@in.nil? or @in.include?(answer_object))
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the provided _answer_string_ after processing whitespace by
|
||||
# the rules of this Question. Valid settings for whitespace are:
|
||||
#
|
||||
# +nil+:: Do not alter whitespace.
|
||||
# <tt>:strip</tt>:: Calls strip(). (Default.)
|
||||
# <tt>:chomp</tt>:: Calls chomp().
|
||||
# <tt>:collapse</tt>:: Collapses all whitspace runs to a
|
||||
# single space.
|
||||
# <tt>:strip_and_collapse</tt>:: Calls strip(), then collapses all
|
||||
# whitspace runs to a single space.
|
||||
# <tt>:chomp_and_collapse</tt>:: Calls chomp(), then collapses all
|
||||
# whitspace runs to a single space.
|
||||
# <tt>:remove</tt>:: Removes all whitespace.
|
||||
#
|
||||
# An unrecognized choice (like <tt>:none</tt>) is treated as +nil+.
|
||||
#
|
||||
# This process is skipped, for single character input.
|
||||
#
|
||||
def remove_whitespace( answer_string )
|
||||
if @whitespace.nil?
|
||||
answer_string
|
||||
elsif [:strip, :chomp].include?(@whitespace)
|
||||
answer_string.send(@whitespace)
|
||||
elsif @whitespace == :collapse
|
||||
answer_string.gsub(/\s+/, " ")
|
||||
elsif [:strip_and_collapse, :chomp_and_collapse].include?(@whitespace)
|
||||
result = answer_string.send(@whitespace.to_s[/^[a-z]+/])
|
||||
result.gsub(/\s+/, " ")
|
||||
elsif @whitespace == :remove
|
||||
answer_string.gsub(/\s+/, "")
|
||||
else
|
||||
answer_string
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Returns an Array of valid answers to this question. These answers are
|
||||
# only known when _answer_type_ is set to an Array of choices, File, or
|
||||
# Pathname. Any other time, this method will return an empty Array.
|
||||
#
|
||||
def selection( )
|
||||
if @answer_type.is_a?(Array)
|
||||
@answer_type
|
||||
elsif [File, Pathname].include?(@answer_type)
|
||||
Dir[File.join(@directory.to_s, @glob)].map do |file|
|
||||
File.basename(file)
|
||||
end
|
||||
else
|
||||
[ ]
|
||||
end
|
||||
end
|
||||
|
||||
# Stringifies the question to be asked.
|
||||
def to_str( )
|
||||
@question
|
||||
end
|
||||
|
||||
#
|
||||
# Returns +true+ if the provided _answer_string_ is accepted by the
|
||||
# _validate_ attribute or +false+ if it's not.
|
||||
#
|
||||
# It's important to realize that an answer is validated after whitespace
|
||||
# and case handling.
|
||||
#
|
||||
def valid_answer?( answer_string )
|
||||
@validate.nil? or
|
||||
(@validate.is_a?(Regexp) and answer_string =~ @validate) or
|
||||
(@validate.is_a?(Proc) and @validate[answer_string])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
#
|
||||
# Adds the default choice to the end of question between <tt>|...|</tt>.
|
||||
# Trailing whitespace is preserved so the function of HighLine.say() is
|
||||
# not affected.
|
||||
#
|
||||
def append_default( )
|
||||
if @question =~ /([\t ]+)\Z/
|
||||
@question << "|#{@default}|#{$1}"
|
||||
elsif @question == ""
|
||||
@question << "|#{@default}| "
|
||||
elsif @question[-1, 1] == "\n"
|
||||
@question[-2, 0] = " |#{@default}|"
|
||||
else
|
||||
@question << " |#{@default}|"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
130
vendor/gems/highline-1.4.0/lib/highline/system_extensions.rb
vendored
Normal file
130
vendor/gems/highline-1.4.0/lib/highline/system_extensions.rb
vendored
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
|
||||
# system_extensions.rb
|
||||
#
|
||||
# Created by James Edward Gray II on 2006-06-14.
|
||||
# Copyright 2006 Gray Productions. All rights reserved.
|
||||
#
|
||||
# This is Free Software. See LICENSE and COPYING for details.
|
||||
|
||||
class HighLine
|
||||
module SystemExtensions
|
||||
module_function
|
||||
|
||||
#
|
||||
# This section builds character reading and terminal size functions
|
||||
# to suit the proper platform we're running on. Be warned: Here be
|
||||
# dragons!
|
||||
#
|
||||
begin
|
||||
# Cygwin will look like Windows, but we want to treat it like a Posix OS:
|
||||
raise LoadError, "Cygwin is a Posix OS." if RUBY_PLATFORM =~ /\bcygwin\b/i
|
||||
|
||||
require "Win32API" # See if we're on Windows.
|
||||
|
||||
CHARACTER_MODE = "Win32API" # For Debugging purposes only.
|
||||
|
||||
#
|
||||
# Windows savvy getc().
|
||||
#
|
||||
# *WARNING*: This method ignores <tt>input</tt> and reads one
|
||||
# character from +STDIN+!
|
||||
#
|
||||
def get_character( input = STDIN )
|
||||
Win32API.new("crtdll", "_getch", [ ], "L").Call
|
||||
end
|
||||
|
||||
# A Windows savvy method to fetch the console columns, and rows.
|
||||
def terminal_size
|
||||
m_GetStdHandle = Win32API.new( 'kernel32',
|
||||
'GetStdHandle',
|
||||
['L'],
|
||||
'L' )
|
||||
m_GetConsoleScreenBufferInfo = Win32API.new(
|
||||
'kernel32', 'GetConsoleScreenBufferInfo', ['L', 'P'], 'L'
|
||||
)
|
||||
|
||||
format = 'SSSSSssssSS'
|
||||
buf = ([0] * format.size).pack(format)
|
||||
stdout_handle = m_GetStdHandle.call(0xFFFFFFF5)
|
||||
|
||||
m_GetConsoleScreenBufferInfo.call(stdout_handle, buf)
|
||||
bufx, bufy, curx, cury, wattr,
|
||||
left, top, right, bottom, maxx, maxy = buf.unpack(format)
|
||||
return right - left + 1, bottom - top + 1
|
||||
end
|
||||
rescue LoadError # If we're not on Windows try...
|
||||
begin
|
||||
require "termios" # Unix, first choice.
|
||||
|
||||
CHARACTER_MODE = "termios" # For Debugging purposes only.
|
||||
|
||||
#
|
||||
# Unix savvy getc(). (First choice.)
|
||||
#
|
||||
# *WARNING*: This method requires the "termios" library!
|
||||
#
|
||||
def get_character( input = STDIN )
|
||||
old_settings = Termios.getattr(input)
|
||||
|
||||
new_settings = old_settings.dup
|
||||
new_settings.c_lflag &= ~(Termios::ECHO | Termios::ICANON)
|
||||
new_settings.c_cc[Termios::VMIN] = 1
|
||||
|
||||
begin
|
||||
Termios.setattr(input, Termios::TCSANOW, new_settings)
|
||||
input.getc
|
||||
ensure
|
||||
Termios.setattr(input, Termios::TCSANOW, old_settings)
|
||||
end
|
||||
end
|
||||
rescue LoadError # If our first choice fails, default.
|
||||
CHARACTER_MODE = "stty" # For Debugging purposes only.
|
||||
|
||||
#
|
||||
# Unix savvy getc(). (Second choice.)
|
||||
#
|
||||
# *WARNING*: This method requires the external "stty" program!
|
||||
#
|
||||
def get_character( input = STDIN )
|
||||
raw_no_echo_mode
|
||||
|
||||
begin
|
||||
input.getc
|
||||
ensure
|
||||
restore_mode
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Switched the input mode to raw and disables echo.
|
||||
#
|
||||
# *WARNING*: This method requires the external "stty" program!
|
||||
#
|
||||
def raw_no_echo_mode
|
||||
@state = `stty -g`
|
||||
system "stty raw -echo cbreak isig"
|
||||
end
|
||||
|
||||
#
|
||||
# Restores a previously saved input mode.
|
||||
#
|
||||
# *WARNING*: This method requires the external "stty" program!
|
||||
#
|
||||
def restore_mode
|
||||
system "stty #{@state}"
|
||||
end
|
||||
end
|
||||
|
||||
# A Unix savvy method to fetch the console columns, and rows.
|
||||
def terminal_size
|
||||
if /solaris/ =~ RUBY_PLATFORM and
|
||||
`stty` =~ /\brows = (\d+).*\bcolumns = (\d+)/
|
||||
[$2, $1].map { |c| x.to_i }
|
||||
else
|
||||
`stty size`.split.map { |x| x.to_i }.reverse
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
1360
vendor/gems/highline-1.4.0/setup.rb
vendored
Normal file
1360
vendor/gems/highline-1.4.0/setup.rb
vendored
Normal file
File diff suppressed because it is too large
Load diff
56
vendor/gems/highline-1.4.0/test/tc_color_scheme.rb
vendored
Normal file
56
vendor/gems/highline-1.4.0/test/tc_color_scheme.rb
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
|
||||
# tc_color_scheme.rb
|
||||
#
|
||||
# Created by Jeremy Hinegardner on 2007-01-24.
|
||||
# Copyright 2007 Jeremy Hinegardner. All rights reserved.
|
||||
#
|
||||
# This is Free Software. See LICENSE and COPYING for details.
|
||||
|
||||
require "test/unit"
|
||||
|
||||
require "highline"
|
||||
require "stringio"
|
||||
|
||||
class TestColorScheme < Test::Unit::TestCase
|
||||
def setup
|
||||
@input = StringIO.new
|
||||
@output = StringIO.new
|
||||
@terminal = HighLine.new(@input, @output)
|
||||
|
||||
@old_color_scheme = HighLine.color_scheme
|
||||
end
|
||||
|
||||
def teardown
|
||||
HighLine.color_scheme = @old_color_scheme
|
||||
end
|
||||
|
||||
def test_using_color_scheme
|
||||
assert_equal(false,HighLine.using_color_scheme?)
|
||||
|
||||
HighLine.color_scheme = HighLine::ColorScheme.new
|
||||
assert_equal(true,HighLine.using_color_scheme?)
|
||||
end
|
||||
|
||||
def test_scheme
|
||||
HighLine.color_scheme = HighLine::SampleColorScheme.new
|
||||
|
||||
@terminal.say("This should be <%= color('warning yellow', :warning) %>.")
|
||||
assert_equal("This should be \e[1m\e[33mwarning yellow\e[0m.\n",@output.string)
|
||||
@output.rewind
|
||||
|
||||
@terminal.say("This should be <%= color('warning yellow', 'warning') %>.")
|
||||
assert_equal("This should be \e[1m\e[33mwarning yellow\e[0m.\n",@output.string)
|
||||
@output.rewind
|
||||
|
||||
@terminal.say("This should be <%= color('warning yellow', 'WarNing') %>.")
|
||||
assert_equal("This should be \e[1m\e[33mwarning yellow\e[0m.\n",@output.string)
|
||||
@output.rewind
|
||||
|
||||
# turn it back off, should raise an exception
|
||||
HighLine.color_scheme = @old_color_scheme
|
||||
assert_raises(NameError) {
|
||||
@terminal.say("This should be <%= color('nothing at all', :error) %>.")
|
||||
}
|
||||
end
|
||||
end
|
||||
815
vendor/gems/highline-1.4.0/test/tc_highline.rb
vendored
Normal file
815
vendor/gems/highline-1.4.0/test/tc_highline.rb
vendored
Normal file
|
|
@ -0,0 +1,815 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
|
||||
# tc_highline.rb
|
||||
#
|
||||
# Created by James Edward Gray II on 2005-04-26.
|
||||
# Copyright 2005 Gray Productions. All rights reserved.
|
||||
#
|
||||
# This is Free Software. See LICENSE and COPYING for details.
|
||||
|
||||
require "test/unit"
|
||||
|
||||
require "highline"
|
||||
require "stringio"
|
||||
|
||||
if HighLine::CHARACTER_MODE == "Win32API"
|
||||
class HighLine
|
||||
# Override Windows' character reading so it's not tied to STDIN.
|
||||
def get_character( input = STDIN )
|
||||
input.getc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class TestHighLine < Test::Unit::TestCase
|
||||
def setup
|
||||
@input = StringIO.new
|
||||
@output = StringIO.new
|
||||
@terminal = HighLine.new(@input, @output)
|
||||
end
|
||||
|
||||
def test_agree
|
||||
@input << "y\nyes\nYES\nHell no!\nNo\n"
|
||||
@input.rewind
|
||||
|
||||
assert_equal(true, @terminal.agree("Yes or no? "))
|
||||
assert_equal(true, @terminal.agree("Yes or no? "))
|
||||
assert_equal(true, @terminal.agree("Yes or no? "))
|
||||
assert_equal(false, @terminal.agree("Yes or no? "))
|
||||
|
||||
@input.truncate(@input.rewind)
|
||||
@input << "yellow"
|
||||
@input.rewind
|
||||
|
||||
assert_equal(true, @terminal.agree("Yes or no? ", :getc))
|
||||
end
|
||||
|
||||
def test_ask
|
||||
name = "James Edward Gray II"
|
||||
@input << name << "\n"
|
||||
@input.rewind
|
||||
|
||||
assert_equal(name, @terminal.ask("What is your name? "))
|
||||
|
||||
assert_raise(EOFError) { @terminal.ask("Any input left? ") }
|
||||
end
|
||||
|
||||
def test_bug_fixes
|
||||
# auto-complete bug
|
||||
@input << "ruby\nRuby\n"
|
||||
@input.rewind
|
||||
|
||||
languages = [:Perl, :Python, :Ruby]
|
||||
answer = @terminal.ask( "What is your favorite programming language? ",
|
||||
languages )
|
||||
assert_equal(languages.last, answer)
|
||||
|
||||
@input.truncate(@input.rewind)
|
||||
@input << "ruby\n"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask( "What is your favorite programming language? ",
|
||||
languages ) do |q|
|
||||
q.case = :capitalize
|
||||
end
|
||||
assert_equal(languages.last, answer)
|
||||
|
||||
# poor auto-complete error message
|
||||
@input.truncate(@input.rewind)
|
||||
@input << "lisp\nruby\n"
|
||||
@input.rewind
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
answer = @terminal.ask( "What is your favorite programming language? ",
|
||||
languages ) do |q|
|
||||
q.case = :capitalize
|
||||
end
|
||||
assert_equal(languages.last, answer)
|
||||
assert_equal( "What is your favorite programming language? " +
|
||||
"You must choose one of [:Perl, :Python, :Ruby].\n" +
|
||||
"? ", @output.string )
|
||||
end
|
||||
|
||||
def test_case_changes
|
||||
@input << "jeg2\n"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Enter your initials ") do |q|
|
||||
q.case = :up
|
||||
end
|
||||
assert_equal("JEG2", answer)
|
||||
|
||||
@input.truncate(@input.rewind)
|
||||
@input << "cRaZY\n"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Enter a search string: ") do |q|
|
||||
q.case = :down
|
||||
end
|
||||
assert_equal("crazy", answer)
|
||||
end
|
||||
|
||||
def test_character_echo
|
||||
@input << "password\r"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Please enter your password: ") do |q|
|
||||
q.echo = "*"
|
||||
end
|
||||
assert_equal("password", answer)
|
||||
assert_equal("Please enter your password: ********\n", @output.string)
|
||||
|
||||
@input.truncate(@input.rewind)
|
||||
@input << "2"
|
||||
@input.rewind
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
answer = @terminal.ask( "Select an option (1, 2 or 3): ",
|
||||
Integer ) do |q|
|
||||
q.echo = "*"
|
||||
q.character = true
|
||||
end
|
||||
assert_equal(2, answer)
|
||||
assert_equal("Select an option (1, 2 or 3): *\n", @output.string)
|
||||
end
|
||||
|
||||
def test_backspace_does_not_enter_prompt
|
||||
@input << "\b\b"
|
||||
@input.rewind
|
||||
answer = @terminal.ask("Please enter your password: ") do |q|
|
||||
q.echo = "*"
|
||||
end
|
||||
assert_equal("", answer)
|
||||
assert_equal("Please enter your password: \n",@output.string)
|
||||
end
|
||||
|
||||
def test_readline_on_non_echo_question_has_prompt
|
||||
@input << "you can't see me"
|
||||
@input.rewind
|
||||
answer = @terminal.ask("Please enter some hidden text: ") do |q|
|
||||
q.readline = true
|
||||
q.echo = "*"
|
||||
end
|
||||
assert_equal("you can't see me", answer)
|
||||
assert_equal("Please enter some hidden text: ****************\n",@output.string)
|
||||
end
|
||||
|
||||
def test_character_reading
|
||||
# WARNING: This method does NOT cover Unix and Windows savvy testing!
|
||||
@input << "12345"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Enter a single digit: ", Integer) do |q|
|
||||
q.character = :getc
|
||||
end
|
||||
assert_equal(1, answer)
|
||||
end
|
||||
|
||||
def test_color
|
||||
@terminal.say("This should be <%= BLUE %>blue<%= CLEAR %>!")
|
||||
assert_equal("This should be \e[34mblue\e[0m!\n", @output.string)
|
||||
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
@terminal.say( "This should be " +
|
||||
"<%= BOLD + ON_WHITE %>bold on white<%= CLEAR %>!" )
|
||||
assert_equal( "This should be \e[1m\e[47mbold on white\e[0m!\n",
|
||||
@output.string )
|
||||
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
@terminal.say("This should be <%= color('cyan', CYAN) %>!")
|
||||
assert_equal("This should be \e[36mcyan\e[0m!\n", @output.string)
|
||||
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
@terminal.say( "This should be " +
|
||||
"<%= color('blinking on red', :blink, :on_red) %>!" )
|
||||
assert_equal( "This should be \e[5m\e[41mblinking on red\e[0m!\n",
|
||||
@output.string )
|
||||
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
# turn off color
|
||||
old_setting = HighLine.use_color?
|
||||
assert_nothing_raised(Exception) { HighLine.use_color = false }
|
||||
@terminal.say("This should be <%= color('cyan', CYAN) %>!")
|
||||
assert_equal("This should be cyan!\n", @output.string)
|
||||
HighLine.use_color = old_setting
|
||||
end
|
||||
|
||||
def test_confirm
|
||||
@input << "junk.txt\nno\nsave.txt\ny\n"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Enter a filename: ") do |q|
|
||||
q.confirm = "Are you sure you want to overwrite <%= @answer %>? "
|
||||
q.responses[:ask_on_error] = :question
|
||||
end
|
||||
assert_equal("save.txt", answer)
|
||||
assert_equal( "Enter a filename: " +
|
||||
"Are you sure you want to overwrite junk.txt? " +
|
||||
"Enter a filename: " +
|
||||
"Are you sure you want to overwrite save.txt? ",
|
||||
@output.string )
|
||||
|
||||
@input.truncate(@input.rewind)
|
||||
@input << "junk.txt\nyes\nsave.txt\nn\n"
|
||||
@input.rewind
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
answer = @terminal.ask("Enter a filename: ") do |q|
|
||||
q.confirm = "Are you sure you want to overwrite <%= @answer %>? "
|
||||
end
|
||||
assert_equal("junk.txt", answer)
|
||||
assert_equal( "Enter a filename: " +
|
||||
"Are you sure you want to overwrite junk.txt? ",
|
||||
@output.string )
|
||||
end
|
||||
|
||||
def test_defaults
|
||||
@input << "\nNo Comment\n"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Are you sexually active? ") do |q|
|
||||
q.validate = /\Ay(?:es)?|no?|no comment\Z/i
|
||||
end
|
||||
assert_equal("No Comment", answer)
|
||||
|
||||
@input.truncate(@input.rewind)
|
||||
@input << "\nYes\n"
|
||||
@input.rewind
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
answer = @terminal.ask("Are you sexually active? ") do |q|
|
||||
q.default = "No Comment"
|
||||
q.validate = /\Ay(?:es)?|no?|no comment\Z/i
|
||||
end
|
||||
assert_equal("No Comment", answer)
|
||||
assert_equal( "Are you sexually active? |No Comment| ",
|
||||
@output.string )
|
||||
end
|
||||
|
||||
def test_empty
|
||||
@input << "\n"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("") do |q|
|
||||
q.default = "yes"
|
||||
q.validate = /\Ay(?:es)?|no?\Z/i
|
||||
end
|
||||
assert_equal("yes", answer)
|
||||
end
|
||||
|
||||
def test_erb
|
||||
@terminal.say( "The integers from 1 to 10 are:\n" +
|
||||
"% (1...10).each do |n|\n" +
|
||||
"\t<%= n %>,\n" +
|
||||
"% end\n" +
|
||||
"\tand 10" )
|
||||
assert_equal( "The integers from 1 to 10 are:\n" +
|
||||
"\t1,\n\t2,\n\t3,\n\t4,\n\t5,\n" +
|
||||
"\t6,\n\t7,\n\t8,\n\t9,\n\tand 10\n",
|
||||
@output.string )
|
||||
end
|
||||
|
||||
def test_files
|
||||
@input << "#{File.basename(__FILE__)[0, 5]}\n"
|
||||
@input.rewind
|
||||
|
||||
file = @terminal.ask("Select a file: ", File) do |q|
|
||||
q.directory = File.expand_path(File.dirname(__FILE__))
|
||||
q.glob = "*.rb"
|
||||
end
|
||||
assert_instance_of(File, file)
|
||||
assert_equal("#!/usr/local/bin/ruby -w\n", file.gets)
|
||||
assert_equal("\n", file.gets)
|
||||
assert_equal("# tc_highline.rb\n", file.gets)
|
||||
file.close
|
||||
|
||||
@input.rewind
|
||||
|
||||
pathname = @terminal.ask("Select a file: ", Pathname) do |q|
|
||||
q.directory = File.expand_path(File.dirname(__FILE__))
|
||||
q.glob = "*.rb"
|
||||
end
|
||||
assert_instance_of(Pathname, pathname)
|
||||
assert_equal(File.size(__FILE__), pathname.size)
|
||||
end
|
||||
|
||||
def test_gather
|
||||
@input << "James\nDana\nStorm\nGypsy\n\n"
|
||||
@input.rewind
|
||||
|
||||
answers = @terminal.ask("Enter four names:") do |q|
|
||||
q.gather = 4
|
||||
end
|
||||
assert_equal(%w{James Dana Storm Gypsy}, answers)
|
||||
assert_equal("\n", @input.gets)
|
||||
assert_equal("Enter four names:\n", @output.string)
|
||||
|
||||
@input.rewind
|
||||
|
||||
answers = @terminal.ask("Enter four names:") do |q|
|
||||
q.gather = ""
|
||||
end
|
||||
assert_equal(%w{James Dana Storm Gypsy}, answers)
|
||||
|
||||
@input.rewind
|
||||
|
||||
answers = @terminal.ask("Enter four names:") do |q|
|
||||
q.gather = /^\s*$/
|
||||
end
|
||||
assert_equal(%w{James Dana Storm Gypsy}, answers)
|
||||
|
||||
@input.truncate(@input.rewind)
|
||||
@input << "29\n49\n30\n"
|
||||
@input.rewind
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
answers = @terminal.ask("<%= @key %>: ", Integer) do |q|
|
||||
q.gather = { "Age" => 0, "Wife's Age" => 0, "Father's Age" => 0}
|
||||
end
|
||||
assert_equal( { "Age" => 29, "Wife's Age" => 30, "Father's Age" => 49},
|
||||
answers )
|
||||
assert_equal("Age: Father's Age: Wife's Age: ", @output.string)
|
||||
end
|
||||
|
||||
def test_lists
|
||||
digits = %w{Zero One Two Three Four Five Six Seven Eight Nine}
|
||||
erb_digits = digits.dup
|
||||
erb_digits[erb_digits.index("Five")] = "<%= color('Five', :blue) %%>"
|
||||
|
||||
@terminal.say("<%= list(#{digits.inspect}) %>")
|
||||
assert_equal(digits.map { |d| "#{d}\n" }.join, @output.string)
|
||||
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
@terminal.say("<%= list(#{digits.inspect}, :inline) %>")
|
||||
assert_equal( digits[0..-2].join(", ") + " or #{digits.last}\n",
|
||||
@output.string )
|
||||
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
@terminal.say("<%= list(#{digits.inspect}, :inline, ' and ') %>")
|
||||
assert_equal( digits[0..-2].join(", ") + " and #{digits.last}\n",
|
||||
@output.string )
|
||||
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
@terminal.say("<%= list(#{digits.inspect}, :columns_down, 3) %>")
|
||||
assert_equal( "Zero Four Eight\n" +
|
||||
"One Five Nine \n" +
|
||||
"Two Six \n" +
|
||||
"Three Seven\n",
|
||||
@output.string )
|
||||
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
@terminal.say("<%= list(#{erb_digits.inspect}, :columns_down, 3) %>")
|
||||
assert_equal( "Zero Four Eight\n" +
|
||||
"One \e[34mFive\e[0m Nine \n" +
|
||||
"Two Six \n" +
|
||||
"Three Seven\n",
|
||||
@output.string )
|
||||
|
||||
colums_of_twenty = ["12345678901234567890"] * 5
|
||||
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
@terminal.say("<%= list(#{colums_of_twenty.inspect}, :columns_down) %>")
|
||||
assert_equal( "12345678901234567890 12345678901234567890 " +
|
||||
"12345678901234567890\n" +
|
||||
"12345678901234567890 12345678901234567890\n",
|
||||
@output.string )
|
||||
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
@terminal.say("<%= list(#{digits.inspect}, :columns_across, 3) %>")
|
||||
assert_equal( "Zero One Two \n" +
|
||||
"Three Four Five \n" +
|
||||
"Six Seven Eight\n" +
|
||||
"Nine \n",
|
||||
@output.string )
|
||||
|
||||
colums_of_twenty.pop
|
||||
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
@terminal.say("<%= list( #{colums_of_twenty.inspect}, :columns_across ) %>")
|
||||
assert_equal( "12345678901234567890 12345678901234567890 " +
|
||||
"12345678901234567890\n" +
|
||||
"12345678901234567890\n",
|
||||
@output.string )
|
||||
end
|
||||
|
||||
def test_mode
|
||||
assert(%w[Win32API termios stty].include?(HighLine::CHARACTER_MODE))
|
||||
end
|
||||
|
||||
class NameClass
|
||||
def self.parse( string )
|
||||
if string =~ /^\s*(\w+),\s*(\w+)\s+(\w+)\s*$/
|
||||
self.new($2, $3, $1)
|
||||
else
|
||||
raise ArgumentError, "Invalid name format."
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(first, middle, last)
|
||||
@first, @middle, @last = first, middle, last
|
||||
end
|
||||
|
||||
attr_reader :first, :middle, :last
|
||||
end
|
||||
|
||||
def test_my_class_conversion
|
||||
@input << "Gray, James Edward\n"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Your name? ", NameClass) do |q|
|
||||
q.validate = lambda do |name|
|
||||
names = name.split(/,\s*/)
|
||||
return false unless names.size == 2
|
||||
return false if names.first =~ /\s/
|
||||
names.last.split.size == 2
|
||||
end
|
||||
end
|
||||
assert_instance_of(NameClass, answer)
|
||||
assert_equal("Gray", answer.last)
|
||||
assert_equal("James", answer.first)
|
||||
assert_equal("Edward", answer.middle)
|
||||
end
|
||||
|
||||
def test_no_echo
|
||||
@input << "password\r"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Please enter your password: ") do |q|
|
||||
q.echo = false
|
||||
end
|
||||
assert_equal("password", answer)
|
||||
assert_equal("Please enter your password: \n", @output.string)
|
||||
|
||||
@input.rewind
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
answer = @terminal.ask("Pick a letter or number: ") do |q|
|
||||
q.character = true
|
||||
q.echo = false
|
||||
end
|
||||
assert_equal("p", answer)
|
||||
assert_equal("a", @input.getc.chr)
|
||||
assert_equal("Pick a letter or number: \n", @output.string)
|
||||
end
|
||||
|
||||
def test_paging
|
||||
@terminal.page_at = 22
|
||||
|
||||
@input << "\n\n"
|
||||
@input.rewind
|
||||
|
||||
@terminal.say((1..50).map { |n| "This is line #{n}.\n"}.join)
|
||||
assert_equal( (1..22).map { |n| "This is line #{n}.\n"}.join +
|
||||
"\n-- press enter/return to continue or q to stop -- \n\n" +
|
||||
(23..44).map { |n| "This is line #{n}.\n"}.join +
|
||||
"\n-- press enter/return to continue or q to stop -- \n\n" +
|
||||
(45..50).map { |n| "This is line #{n}.\n"}.join,
|
||||
@output.string )
|
||||
end
|
||||
|
||||
def test_range_requirements
|
||||
@input << "112\n-541\n28\n"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Tell me your age.", Integer) do |q|
|
||||
q.in = 0..105
|
||||
end
|
||||
assert_equal(28, answer)
|
||||
assert_equal( "Tell me your age.\n" +
|
||||
"Your answer isn't within the expected range " +
|
||||
"(included in 0..105).\n" +
|
||||
"? " +
|
||||
"Your answer isn't within the expected range " +
|
||||
"(included in 0..105).\n" +
|
||||
"? ", @output.string )
|
||||
|
||||
@input.truncate(@input.rewind)
|
||||
@input << "1\n-541\n28\n"
|
||||
@input.rewind
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
answer = @terminal.ask("Tell me your age.", Integer) do |q|
|
||||
q.above = 3
|
||||
end
|
||||
assert_equal(28, answer)
|
||||
assert_equal( "Tell me your age.\n" +
|
||||
"Your answer isn't within the expected range " +
|
||||
"(above 3).\n" +
|
||||
"? " +
|
||||
"Your answer isn't within the expected range " +
|
||||
"(above 3).\n" +
|
||||
"? ", @output.string )
|
||||
|
||||
@input.truncate(@input.rewind)
|
||||
@input << "1\n28\n-541\n"
|
||||
@input.rewind
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
answer = @terminal.ask("Lowest numer you can think of?", Integer) do |q|
|
||||
q.below = 0
|
||||
end
|
||||
assert_equal(-541, answer)
|
||||
assert_equal( "Lowest numer you can think of?\n" +
|
||||
"Your answer isn't within the expected range " +
|
||||
"(below 0).\n" +
|
||||
"? " +
|
||||
"Your answer isn't within the expected range " +
|
||||
"(below 0).\n" +
|
||||
"? ", @output.string )
|
||||
|
||||
@input.truncate(@input.rewind)
|
||||
@input << "1\n-541\n6\n"
|
||||
@input.rewind
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
answer = @terminal.ask("Enter a low even number: ", Integer) do |q|
|
||||
q.above = 0
|
||||
q.below = 10
|
||||
q.in = [2, 4, 6, 8]
|
||||
end
|
||||
assert_equal(6, answer)
|
||||
assert_equal( "Enter a low even number: " +
|
||||
"Your answer isn't within the expected range " +
|
||||
"(above 0, below 10, and included in [2, 4, 6, 8]).\n" +
|
||||
"? " +
|
||||
"Your answer isn't within the expected range " +
|
||||
"(above 0, below 10, and included in [2, 4, 6, 8]).\n" +
|
||||
"? ", @output.string )
|
||||
end
|
||||
|
||||
def test_reask
|
||||
number = 61676
|
||||
@input << "Junk!\n" << number << "\n"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Favorite number? ", Integer)
|
||||
assert_kind_of(Integer, number)
|
||||
assert_instance_of(Fixnum, number)
|
||||
assert_equal(number, answer)
|
||||
assert_equal( "Favorite number? " +
|
||||
"You must enter a valid Integer.\n" +
|
||||
"? ", @output.string )
|
||||
|
||||
@input.rewind
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
answer = @terminal.ask("Favorite number? ", Integer) do |q|
|
||||
q.responses[:ask_on_error] = :question
|
||||
q.responses[:invalid_type] = "Not a valid number!"
|
||||
end
|
||||
assert_kind_of(Integer, number)
|
||||
assert_instance_of(Fixnum, number)
|
||||
assert_equal(number, answer)
|
||||
assert_equal( "Favorite number? " +
|
||||
"Not a valid number!\n" +
|
||||
"Favorite number? ", @output.string )
|
||||
|
||||
@input.truncate(@input.rewind)
|
||||
@input << "gen\ngene\n"
|
||||
@input.rewind
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
answer = @terminal.ask("Select a mode: ", [:generate, :gentle])
|
||||
assert_instance_of(Symbol, answer)
|
||||
assert_equal(:generate, answer)
|
||||
assert_equal( "Select a mode: " +
|
||||
"Ambiguous choice. " +
|
||||
"Please choose one of [:generate, :gentle].\n" +
|
||||
"? ", @output.string )
|
||||
end
|
||||
|
||||
def test_response_embedding
|
||||
@input << "112\n-541\n28\n"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Tell me your age.", Integer) do |q|
|
||||
q.in = 0..105
|
||||
q.responses[:not_in_range] = "Need a <%= @question.answer_type %>" +
|
||||
" <%= @question.expected_range %>."
|
||||
end
|
||||
assert_equal(28, answer)
|
||||
assert_equal( "Tell me your age.\n" +
|
||||
"Need a Integer included in 0..105.\n" +
|
||||
"? " +
|
||||
"Need a Integer included in 0..105.\n" +
|
||||
"? ", @output.string )
|
||||
end
|
||||
|
||||
def test_say
|
||||
@terminal.say("This will have a newline.")
|
||||
assert_equal("This will have a newline.\n", @output.string)
|
||||
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
@terminal.say("This will also have one newline.\n")
|
||||
assert_equal("This will also have one newline.\n", @output.string)
|
||||
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
@terminal.say("This will not have a newline. ")
|
||||
assert_equal("This will not have a newline. ", @output.string)
|
||||
end
|
||||
|
||||
def test_type_conversion
|
||||
number = 61676
|
||||
@input << number << "\n"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Favorite number? ", Integer)
|
||||
assert_kind_of(Integer, answer)
|
||||
assert_instance_of(Fixnum, answer)
|
||||
assert_equal(number, answer)
|
||||
|
||||
@input.truncate(@input.rewind)
|
||||
number = 1_000_000_000_000_000_000_000_000_000_000
|
||||
@input << number << "\n"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Favorite number? ", Integer)
|
||||
assert_kind_of(Integer, answer)
|
||||
assert_instance_of(Bignum, answer)
|
||||
assert_equal(number, answer)
|
||||
|
||||
@input.truncate(@input.rewind)
|
||||
number = 10.5002
|
||||
@input << number << "\n"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask( "Favorite number? ",
|
||||
lambda { |n| n.to_f.abs.round } )
|
||||
assert_kind_of(Integer, answer)
|
||||
assert_instance_of(Fixnum, answer)
|
||||
assert_equal(11, answer)
|
||||
|
||||
@input.truncate(@input.rewind)
|
||||
animal = :dog
|
||||
@input << animal << "\n"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Favorite animal? ", Symbol)
|
||||
assert_instance_of(Symbol, answer)
|
||||
assert_equal(animal, answer)
|
||||
|
||||
@input.truncate(@input.rewind)
|
||||
@input << "6/16/76\n"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Enter your birthday.", Date)
|
||||
assert_instance_of(Date, answer)
|
||||
assert_equal(16, answer.day)
|
||||
assert_equal(6, answer.month)
|
||||
assert_equal(76, answer.year)
|
||||
|
||||
@input.truncate(@input.rewind)
|
||||
pattern = "^yes|no$"
|
||||
@input << pattern << "\n"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Give me a pattern to match with: ", Regexp)
|
||||
assert_instance_of(Regexp, answer)
|
||||
assert_equal(/#{pattern}/, answer)
|
||||
|
||||
@input.truncate(@input.rewind)
|
||||
@input << "gen\n"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Select a mode: ", [:generate, :run])
|
||||
assert_instance_of(Symbol, answer)
|
||||
assert_equal(:generate, answer)
|
||||
end
|
||||
|
||||
def test_validation
|
||||
@input << "system 'rm -rf /'\n105\n0b101_001\n"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Enter a binary number: ") do |q|
|
||||
q.validate = /\A(?:0b)?[01_]+\Z/
|
||||
end
|
||||
assert_equal("0b101_001", answer)
|
||||
assert_equal( "Enter a binary number: " +
|
||||
"Your answer isn't valid " +
|
||||
"(must match /\\A(?:0b)?[01_]+\\Z/).\n" +
|
||||
"? " +
|
||||
"Your answer isn't valid " +
|
||||
"(must match /\\A(?:0b)?[01_]+\\Z/).\n" +
|
||||
"? ", @output.string )
|
||||
|
||||
@input.truncate(@input.rewind)
|
||||
@input << "Gray II, James Edward\n" +
|
||||
"Gray, Dana Ann Leslie\n" +
|
||||
"Gray, James Edward\n"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Your name? ") do |q|
|
||||
q.validate = lambda do |name|
|
||||
names = name.split(/,\s*/)
|
||||
return false unless names.size == 2
|
||||
return false if names.first =~ /\s/
|
||||
names.last.split.size == 2
|
||||
end
|
||||
end
|
||||
assert_equal("Gray, James Edward", answer)
|
||||
end
|
||||
|
||||
def test_whitespace
|
||||
@input << " A lot\tof \t space\t \there! \n"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Enter a whitespace filled string: ") do |q|
|
||||
q.whitespace = :chomp
|
||||
end
|
||||
assert_equal(" A lot\tof \t space\t \there! ", answer)
|
||||
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Enter a whitespace filled string: ")
|
||||
assert_equal("A lot\tof \t space\t \there!", answer)
|
||||
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Enter a whitespace filled string: ") do |q|
|
||||
q.whitespace = :strip_and_collapse
|
||||
end
|
||||
assert_equal("A lot of space here!", answer)
|
||||
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Enter a whitespace filled string: ") do |q|
|
||||
q.whitespace = :remove
|
||||
end
|
||||
assert_equal("Alotofspacehere!", answer)
|
||||
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.ask("Enter a whitespace filled string: ") do |q|
|
||||
q.whitespace = :none
|
||||
end
|
||||
assert_equal(" A lot\tof \t space\t \there! \n", answer)
|
||||
end
|
||||
|
||||
def test_wrap
|
||||
@terminal.wrap_at = 80
|
||||
|
||||
@terminal.say("This is a very short line.")
|
||||
assert_equal("This is a very short line.\n", @output.string)
|
||||
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
@terminal.say( "This is a long flowing paragraph meant to span " +
|
||||
"several lines. This text should definitely be " +
|
||||
"wrapped at the set limit, in the result. Your code " +
|
||||
"does well with things like this.\n\n" +
|
||||
" * This is a simple embedded list.\n" +
|
||||
" * You're code should not mess with this...\n" +
|
||||
" * Because it's already formatted correctly and " +
|
||||
"does not\n" +
|
||||
" exceed the limit!" )
|
||||
assert_equal( "This is a long flowing paragraph meant to span " +
|
||||
"several lines. This text should\n" +
|
||||
"definitely be wrapped at the set limit, in the " +
|
||||
"result. Your code does well with\n" +
|
||||
"things like this.\n\n" +
|
||||
" * This is a simple embedded list.\n" +
|
||||
" * You're code should not mess with this...\n" +
|
||||
" * Because it's already formatted correctly and does " +
|
||||
"not\n" +
|
||||
" exceed the limit!\n", @output.string )
|
||||
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
@terminal.say("-=" * 50)
|
||||
assert_equal(("-=" * 40 + "\n") + ("-=" * 10 + "\n"), @output.string)
|
||||
end
|
||||
|
||||
def test_track_eof
|
||||
assert_raise(EOFError) { @terminal.ask("Any input left? ") }
|
||||
|
||||
# turn EOF tracking
|
||||
old_setting = HighLine.track_eof?
|
||||
assert_nothing_raised(Exception) { HighLine.track_eof = false }
|
||||
begin
|
||||
@terminal.ask("And now? ") # this will still blow up, nothing available
|
||||
rescue
|
||||
assert_not_equal(EOFError, $!.class) # but HighLine's safe guards are off
|
||||
end
|
||||
HighLine.track_eof = old_setting
|
||||
end
|
||||
|
||||
def test_version
|
||||
assert_not_nil(HighLine::VERSION)
|
||||
assert_instance_of(String, HighLine::VERSION)
|
||||
assert(HighLine::VERSION.frozen?)
|
||||
assert_match(/\A\d\.\d\.\d\Z/, HighLine::VERSION)
|
||||
end
|
||||
end
|
||||
54
vendor/gems/highline-1.4.0/test/tc_import.rb
vendored
Normal file
54
vendor/gems/highline-1.4.0/test/tc_import.rb
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
|
||||
# tc_import.rb
|
||||
#
|
||||
# Created by James Edward Gray II on 2005-04-26.
|
||||
# Copyright 2005 Gray Productions. All rights reserved.
|
||||
#
|
||||
# This is Free Software. See LICENSE and COPYING for details.
|
||||
|
||||
require "test/unit"
|
||||
|
||||
require "highline/import"
|
||||
require "stringio"
|
||||
|
||||
class TestImport < Test::Unit::TestCase
|
||||
def test_import
|
||||
assert_respond_to(self, :agree)
|
||||
assert_respond_to(self, :ask)
|
||||
assert_respond_to(self, :choose)
|
||||
assert_respond_to(self, :say)
|
||||
end
|
||||
|
||||
def test_or_ask
|
||||
old_terminal = $terminal
|
||||
|
||||
input = StringIO.new
|
||||
output = StringIO.new
|
||||
$terminal = HighLine.new(input, output)
|
||||
|
||||
input << "10\n"
|
||||
input.rewind
|
||||
|
||||
assert_equal(10, nil.or_ask("How much? ", Integer))
|
||||
|
||||
input.rewind
|
||||
|
||||
assert_equal(20, "20".or_ask("How much? ", Integer))
|
||||
assert_equal(20, 20.or_ask("How much? ", Integer))
|
||||
|
||||
assert_equal(10, 20.or_ask("How much? ", Integer) { |q| q.in = 1..10 })
|
||||
ensure
|
||||
$terminal = old_terminal
|
||||
end
|
||||
|
||||
def test_redirection
|
||||
old_terminal = $terminal
|
||||
|
||||
$terminal = HighLine.new(nil, (output = StringIO.new))
|
||||
say("Testing...")
|
||||
assert_equal("Testing...\n", output.string)
|
||||
ensure
|
||||
$terminal = old_terminal
|
||||
end
|
||||
end
|
||||
429
vendor/gems/highline-1.4.0/test/tc_menu.rb
vendored
Normal file
429
vendor/gems/highline-1.4.0/test/tc_menu.rb
vendored
Normal file
|
|
@ -0,0 +1,429 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
|
||||
# tc_menu.rb
|
||||
#
|
||||
# Created by Gregory Thomas Brown on 2005-05-10.
|
||||
# Copyright 2005. All rights reserved.
|
||||
#
|
||||
# This is Free Software. See LICENSE and COPYING for details.
|
||||
|
||||
require "test/unit"
|
||||
|
||||
require "highline"
|
||||
require "stringio"
|
||||
|
||||
class TestMenu < Test::Unit::TestCase
|
||||
def setup
|
||||
@input = StringIO.new
|
||||
@output = StringIO.new
|
||||
@terminal = HighLine.new(@input, @output)
|
||||
end
|
||||
|
||||
def test_choices
|
||||
@input << "2\n"
|
||||
@input.rewind
|
||||
|
||||
output = @terminal.choose do |menu|
|
||||
menu.choices("Sample1", "Sample2", "Sample3")
|
||||
end
|
||||
assert_equal("Sample2", output)
|
||||
end
|
||||
|
||||
def test_flow
|
||||
@input << "Sample1\n"
|
||||
@input.rewind
|
||||
|
||||
@terminal.choose do |menu|
|
||||
# Default: menu.flow = :rows
|
||||
|
||||
menu.choice "Sample1"
|
||||
menu.choice "Sample2"
|
||||
menu.choice "Sample3"
|
||||
end
|
||||
assert_equal("1. Sample1\n2. Sample2\n3. Sample3\n? ", @output.string)
|
||||
|
||||
@output.truncate(@output.rewind)
|
||||
@input.rewind
|
||||
|
||||
@terminal.choose do |menu|
|
||||
menu.flow = :columns_across
|
||||
|
||||
menu.choice "Sample1"
|
||||
menu.choice "Sample2"
|
||||
menu.choice "Sample3"
|
||||
end
|
||||
assert_equal("1. Sample1 2. Sample2 3. Sample3\n? ", @output.string)
|
||||
|
||||
@output.truncate(@output.rewind)
|
||||
@input.rewind
|
||||
|
||||
@terminal.choose do |menu|
|
||||
menu.flow = :inline
|
||||
menu.index = :none
|
||||
|
||||
menu.choice "Sample1"
|
||||
menu.choice "Sample2"
|
||||
menu.choice "Sample3"
|
||||
end
|
||||
assert_equal("Sample1, Sample2 or Sample3? ", @output.string)
|
||||
end
|
||||
|
||||
def test_help
|
||||
@input << "help\nhelp load\nhelp rules\nhelp missing\n"
|
||||
@input.rewind
|
||||
|
||||
4.times do
|
||||
@terminal.choose do |menu|
|
||||
menu.shell = true
|
||||
|
||||
menu.choice(:load, "Load a file.")
|
||||
menu.choice(:save, "Save data in file.")
|
||||
menu.choice(:quit, "Exit program.")
|
||||
|
||||
menu.help("rules", "The rules of this system are as follows...")
|
||||
end
|
||||
end
|
||||
assert_equal( "1. load\n2. save\n3. quit\n4. help\n? " +
|
||||
"This command will display helpful messages about " +
|
||||
"functionality, like this one. To see the help for a " +
|
||||
"specific topic enter:\n" +
|
||||
"\thelp [TOPIC]\n" +
|
||||
"Try asking for help on any of the following:\n" +
|
||||
"\nload quit rules save \n" +
|
||||
"1. load\n2. save\n3. quit\n4. help\n? " +
|
||||
"= load\n\n" +
|
||||
"Load a file.\n" +
|
||||
"1. load\n2. save\n3. quit\n4. help\n? " +
|
||||
"= rules\n\n" +
|
||||
"The rules of this system are as follows...\n" +
|
||||
"1. load\n2. save\n3. quit\n4. help\n? " +
|
||||
"= missing\n\n" +
|
||||
"There's no help for that topic.\n", @output.string )
|
||||
end
|
||||
|
||||
def test_index
|
||||
@input << "Sample1\n"
|
||||
@input.rewind
|
||||
|
||||
@terminal.choose do |menu|
|
||||
# Default: menu.index = :number
|
||||
|
||||
menu.choice "Sample1"
|
||||
menu.choice "Sample2"
|
||||
menu.choice "Sample3"
|
||||
end
|
||||
assert_equal("1. Sample1\n2. Sample2\n3. Sample3\n? ", @output.string)
|
||||
|
||||
@output.truncate(@output.rewind)
|
||||
@input.rewind
|
||||
|
||||
@terminal.choose do |menu|
|
||||
menu.index = :letter
|
||||
menu.index_suffix = ") "
|
||||
|
||||
menu.choice "Sample1"
|
||||
menu.choice "Sample2"
|
||||
menu.choice "Sample3"
|
||||
end
|
||||
assert_equal("a) Sample1\nb) Sample2\nc) Sample3\n? ", @output.string)
|
||||
|
||||
@output.truncate(@output.rewind)
|
||||
@input.rewind
|
||||
|
||||
@terminal.choose do |menu|
|
||||
menu.index = :none
|
||||
|
||||
menu.choice "Sample1"
|
||||
menu.choice "Sample2"
|
||||
menu.choice "Sample3"
|
||||
end
|
||||
assert_equal("Sample1\nSample2\nSample3\n? ", @output.string)
|
||||
|
||||
@output.truncate(@output.rewind)
|
||||
@input.rewind
|
||||
|
||||
@terminal.choose do |menu|
|
||||
menu.index = "*"
|
||||
|
||||
menu.choice "Sample1"
|
||||
menu.choice "Sample2"
|
||||
menu.choice "Sample3"
|
||||
end
|
||||
assert_equal("* Sample1\n* Sample2\n* Sample3\n? ", @output.string)
|
||||
end
|
||||
|
||||
def test_layouts
|
||||
@input << "save\n"
|
||||
@input.rewind
|
||||
|
||||
@terminal.choose(:load, :save, :quit) # Default: layout = :list
|
||||
assert_equal("1. load\n2. save\n3. quit\n? ", @output.string)
|
||||
|
||||
@input.rewind
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
@terminal.choose(:load, :save, :quit) do |menu|
|
||||
menu.header = "File Menu"
|
||||
end
|
||||
assert_equal( "File Menu:\n" +
|
||||
"1. load\n2. save\n3. quit\n? ", @output.string )
|
||||
|
||||
@input.rewind
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
@terminal.choose(:load, :save, :quit) do |menu|
|
||||
menu.layout = :one_line
|
||||
menu.header = "File Menu"
|
||||
menu.prompt = "Operation? "
|
||||
end
|
||||
assert_equal( "File Menu: Operation? " +
|
||||
"(load, save or quit) ", @output.string )
|
||||
|
||||
@input.rewind
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
@terminal.choose(:load, :save, :quit) do |menu|
|
||||
menu.layout = :menu_only
|
||||
end
|
||||
assert_equal("load, save or quit? ", @output.string)
|
||||
|
||||
@input.rewind
|
||||
@output.truncate(@output.rewind)
|
||||
|
||||
@terminal.choose(:load, :save, :quit) do |menu|
|
||||
menu.layout = '<%= list(@menu) %>File Menu: '
|
||||
end
|
||||
assert_equal("1. load\n2. save\n3. quit\nFile Menu: ", @output.string)
|
||||
end
|
||||
|
||||
def test_list_option
|
||||
@input << "l\n"
|
||||
@input.rewind
|
||||
|
||||
@terminal.choose(:load, :save, :quit) do |menu|
|
||||
menu.layout = :menu_only
|
||||
menu.list_option = ", or "
|
||||
end
|
||||
assert_equal("load, save, or quit? ", @output.string)
|
||||
end
|
||||
|
||||
def test_nil_on_handled
|
||||
@input << "3\n3\n2\n"
|
||||
@input.rewind
|
||||
|
||||
# Shows that by default proc results are returned.
|
||||
output = @terminal.choose do |menu|
|
||||
menu.choice "Sample1" do "output1" end
|
||||
menu.choice "Sample2" do "output2" end
|
||||
menu.choice "Sample3" do "output3" end
|
||||
end
|
||||
assert_equal("output3", output)
|
||||
|
||||
#
|
||||
# Shows that they can be replaced with +nil+ by setting
|
||||
# _nil_on_handled to +true+.
|
||||
#
|
||||
output = @terminal.choose do |menu|
|
||||
menu.nil_on_handled = true
|
||||
menu.choice "Sample1" do "output1" end
|
||||
menu.choice "Sample2" do "output2" end
|
||||
menu.choice "Sample3" do "output3" end
|
||||
end
|
||||
assert_equal(nil, output)
|
||||
|
||||
# Shows that a menu item without a proc will be returned no matter what.
|
||||
output = @terminal.choose do |menu|
|
||||
menu.choice "Sample1"
|
||||
menu.choice "Sample2"
|
||||
menu.choice "Sample3"
|
||||
end
|
||||
assert_equal("Sample2", output)
|
||||
end
|
||||
|
||||
def test_passed_command
|
||||
@input << "q\n"
|
||||
@input.rewind
|
||||
|
||||
selected = nil
|
||||
@terminal.choose do |menu|
|
||||
menu.choices(:load, :save, :quit) { |command| selected = command }
|
||||
end
|
||||
assert_equal(:quit, selected)
|
||||
end
|
||||
|
||||
def test_question_options
|
||||
@input << "save\n"
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.choose(:Load, :Save, :Quit) do |menu|
|
||||
menu.case = :capitalize
|
||||
end
|
||||
assert_equal(:Save, answer)
|
||||
|
||||
@input.rewind
|
||||
|
||||
answer = @terminal.choose(:Load, :Save, :Quit) do |menu|
|
||||
menu.case = :capitalize
|
||||
menu.character = :getc
|
||||
end
|
||||
assert_equal(:Save, answer)
|
||||
assert_equal(?a, @input.getc)
|
||||
end
|
||||
|
||||
def test_select_by
|
||||
@input << "Sample1\n2\n"
|
||||
@input.rewind
|
||||
|
||||
selected = @terminal.choose do |menu|
|
||||
menu.choice "Sample1"
|
||||
menu.choice "Sample2"
|
||||
menu.choice "Sample3"
|
||||
end
|
||||
assert_equal("Sample1", selected)
|
||||
|
||||
@input.rewind
|
||||
|
||||
selected = @terminal.choose do |menu|
|
||||
menu.select_by = :index
|
||||
|
||||
menu.choice "Sample1"
|
||||
menu.choice "Sample2"
|
||||
menu.choice "Sample3"
|
||||
end
|
||||
assert_equal("Sample2", selected)
|
||||
|
||||
@input.rewind
|
||||
|
||||
selected = @terminal.choose do |menu|
|
||||
menu.select_by = :name
|
||||
|
||||
menu.choice "Sample1"
|
||||
menu.choice "Sample2"
|
||||
menu.choice "Sample3"
|
||||
end
|
||||
assert_equal("Sample1", selected)
|
||||
end
|
||||
|
||||
def test_hidden
|
||||
@input << "Hidden\n4\n"
|
||||
@input.rewind
|
||||
|
||||
selected = @terminal.choose do |menu|
|
||||
menu.choice "Sample1"
|
||||
menu.choice "Sample2"
|
||||
menu.choice "Sample3"
|
||||
menu.hidden "Hidden!"
|
||||
end
|
||||
assert_equal("Hidden!", selected)
|
||||
assert_equal("1. Sample1\n2. Sample2\n3. Sample3\n? ", @output.string)
|
||||
|
||||
@input.rewind
|
||||
|
||||
selected = @terminal.choose do |menu|
|
||||
menu.select_by = :index
|
||||
|
||||
menu.choice "Sample1"
|
||||
menu.choice "Sample2"
|
||||
menu.choice "Sample3"
|
||||
menu.hidden "Hidden!"
|
||||
end
|
||||
assert_equal("Hidden!", selected)
|
||||
|
||||
@input.rewind
|
||||
|
||||
selected = @terminal.choose do |menu|
|
||||
menu.select_by = :name
|
||||
|
||||
menu.choice "Sample1"
|
||||
menu.choice "Sample2"
|
||||
menu.choice "Sample3"
|
||||
menu.hidden "Hidden!"
|
||||
end
|
||||
assert_equal("Hidden!", selected)
|
||||
|
||||
@input.rewind
|
||||
end
|
||||
|
||||
def test_select_by_letter
|
||||
@input << "b\n"
|
||||
@input.rewind
|
||||
|
||||
selected = @terminal.choose do |menu|
|
||||
menu.index = :letter
|
||||
menu.choice :save
|
||||
menu.choice :load
|
||||
menu.choice :quit
|
||||
end
|
||||
assert_equal(:load, selected)
|
||||
end
|
||||
|
||||
def test_shell
|
||||
@input << "save --some-option my_file.txt\n"
|
||||
@input.rewind
|
||||
|
||||
selected = nil
|
||||
options = nil
|
||||
answer = @terminal.choose do |menu|
|
||||
menu.choices(:load, :quit)
|
||||
menu.choice(:save) do |command, details|
|
||||
selected = command
|
||||
options = details
|
||||
|
||||
"Saved!"
|
||||
end
|
||||
menu.shell = true
|
||||
end
|
||||
assert_equal("Saved!", answer)
|
||||
assert_equal(:save, selected)
|
||||
assert_equal("--some-option my_file.txt", options)
|
||||
end
|
||||
|
||||
def test_simple_menu_shortcut
|
||||
@input << "3\n"
|
||||
@input.rewind
|
||||
|
||||
selected = @terminal.choose(:save, :load, :quit)
|
||||
assert_equal(:quit, selected)
|
||||
end
|
||||
|
||||
def test_symbols
|
||||
@input << "3\n"
|
||||
@input.rewind
|
||||
|
||||
selected = @terminal.choose do |menu|
|
||||
menu.choices(:save, :load, :quit)
|
||||
end
|
||||
assert_equal(:quit, selected)
|
||||
end
|
||||
|
||||
def test_paged_print_infinite_loop_bug
|
||||
@terminal.page_at = 5
|
||||
# Will page twice, so start with two new lines
|
||||
@input << "\n\n3\n"
|
||||
@input.rewind
|
||||
|
||||
# Sadly this goes into an infinite loop without the fix to page_print
|
||||
selected = @terminal.choose(* 1..10)
|
||||
assert_equal(selected, 3)
|
||||
end
|
||||
|
||||
|
||||
def test_cancel_paging
|
||||
# Tests that paging can be cancelled halfway through
|
||||
@terminal.page_at = 5
|
||||
# Will page twice, so stop after first page and make choice 3
|
||||
@input << "q\n3\n"
|
||||
@input.rewind
|
||||
|
||||
selected = @terminal.choose(* 1..10)
|
||||
assert_equal(selected, 3)
|
||||
|
||||
# Make sure paging message appeared
|
||||
assert( @output.string.index('press enter/return to continue or q to stop'),
|
||||
"Paging message did not appear." )
|
||||
|
||||
# Make sure it only appeared once
|
||||
assert( @output.string !~ /q to stop.*q to stop/m,
|
||||
"Paging message appeared more than once." )
|
||||
end
|
||||
end
|
||||
15
vendor/gems/highline-1.4.0/test/ts_all.rb
vendored
Normal file
15
vendor/gems/highline-1.4.0/test/ts_all.rb
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
|
||||
# ts_all.rb
|
||||
#
|
||||
# Created by James Edward Gray II on 2005-04-26.
|
||||
# Copyright 2005 Gray Productions. All rights reserved.
|
||||
#
|
||||
# This is Free Software. See LICENSE and COPYING for details.
|
||||
|
||||
require "test/unit"
|
||||
|
||||
require "tc_highline"
|
||||
require "tc_import"
|
||||
require "tc_menu"
|
||||
require "tc_color_scheme"
|
||||
23
vendor/plugins/acts_as_list/README
vendored
Normal file
23
vendor/plugins/acts_as_list/README
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
ActsAsList
|
||||
==========
|
||||
|
||||
This acts_as extension provides the capabilities for sorting and reordering a number of objects in a list. The class that has this specified needs to have a +position+ column defined as an integer on the mapped database table.
|
||||
|
||||
|
||||
Example
|
||||
=======
|
||||
|
||||
class TodoList < ActiveRecord::Base
|
||||
has_many :todo_items, :order => "position"
|
||||
end
|
||||
|
||||
class TodoItem < ActiveRecord::Base
|
||||
belongs_to :todo_list
|
||||
acts_as_list :scope => :todo_list
|
||||
end
|
||||
|
||||
todo_list.first.move_to_bottom
|
||||
todo_list.last.move_higher
|
||||
|
||||
|
||||
Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license
|
||||
3
vendor/plugins/acts_as_list/init.rb
vendored
Normal file
3
vendor/plugins/acts_as_list/init.rb
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
$:.unshift "#{File.dirname(__FILE__)}/lib"
|
||||
require 'active_record/acts/list'
|
||||
ActiveRecord::Base.class_eval { include ActiveRecord::Acts::List }
|
||||
256
vendor/plugins/acts_as_list/lib/active_record/acts/list.rb
vendored
Normal file
256
vendor/plugins/acts_as_list/lib/active_record/acts/list.rb
vendored
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
module ActiveRecord
|
||||
module Acts #:nodoc:
|
||||
module List #:nodoc:
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
# This +acts_as+ extension provides the capabilities for sorting and reordering a number of objects in a list.
|
||||
# The class that has this specified needs to have a +position+ column defined as an integer on
|
||||
# the mapped database table.
|
||||
#
|
||||
# Todo list example:
|
||||
#
|
||||
# class TodoList < ActiveRecord::Base
|
||||
# has_many :todo_items, :order => "position"
|
||||
# end
|
||||
#
|
||||
# class TodoItem < ActiveRecord::Base
|
||||
# belongs_to :todo_list
|
||||
# acts_as_list :scope => :todo_list
|
||||
# end
|
||||
#
|
||||
# todo_list.first.move_to_bottom
|
||||
# todo_list.last.move_higher
|
||||
module ClassMethods
|
||||
# Configuration options are:
|
||||
#
|
||||
# * +column+ - specifies the column name to use for keeping the position integer (default: +position+)
|
||||
# * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach <tt>_id</tt>
|
||||
# (if it hasn't already been added) and use that as the foreign key restriction. It's also possible
|
||||
# to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
|
||||
# Example: <tt>acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
|
||||
def acts_as_list(options = {})
|
||||
configuration = { :column => "position", :scope => "1 = 1" }
|
||||
configuration.update(options) if options.is_a?(Hash)
|
||||
|
||||
configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
|
||||
|
||||
if configuration[:scope].is_a?(Symbol)
|
||||
scope_condition_method = %(
|
||||
def scope_condition
|
||||
if #{configuration[:scope].to_s}.nil?
|
||||
"#{configuration[:scope].to_s} IS NULL"
|
||||
else
|
||||
"#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}"
|
||||
end
|
||||
end
|
||||
)
|
||||
else
|
||||
scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end"
|
||||
end
|
||||
|
||||
class_eval <<-EOV
|
||||
include ActiveRecord::Acts::List::InstanceMethods
|
||||
|
||||
def acts_as_list_class
|
||||
::#{self.name}
|
||||
end
|
||||
|
||||
def position_column
|
||||
'#{configuration[:column]}'
|
||||
end
|
||||
|
||||
#{scope_condition_method}
|
||||
|
||||
before_destroy :remove_from_list
|
||||
before_create :add_to_list_bottom
|
||||
EOV
|
||||
end
|
||||
end
|
||||
|
||||
# All the methods available to a record that has had <tt>acts_as_list</tt> specified. Each method works
|
||||
# by assuming the object to be the item in the list, so <tt>chapter.move_lower</tt> would move that chapter
|
||||
# lower in the list of all chapters. Likewise, <tt>chapter.first?</tt> would return +true+ if that chapter is
|
||||
# the first in the list of all chapters.
|
||||
module InstanceMethods
|
||||
# Insert the item at the given position (defaults to the top position of 1).
|
||||
def insert_at(position = 1)
|
||||
insert_at_position(position)
|
||||
end
|
||||
|
||||
# Swap positions with the next lower item, if one exists.
|
||||
def move_lower
|
||||
return unless lower_item
|
||||
|
||||
acts_as_list_class.transaction do
|
||||
lower_item.decrement_position
|
||||
increment_position
|
||||
end
|
||||
end
|
||||
|
||||
# Swap positions with the next higher item, if one exists.
|
||||
def move_higher
|
||||
return unless higher_item
|
||||
|
||||
acts_as_list_class.transaction do
|
||||
higher_item.increment_position
|
||||
decrement_position
|
||||
end
|
||||
end
|
||||
|
||||
# Move to the bottom of the list. If the item is already in the list, the items below it have their
|
||||
# position adjusted accordingly.
|
||||
def move_to_bottom
|
||||
return unless in_list?
|
||||
acts_as_list_class.transaction do
|
||||
decrement_positions_on_lower_items
|
||||
assume_bottom_position
|
||||
end
|
||||
end
|
||||
|
||||
# Move to the top of the list. If the item is already in the list, the items above it have their
|
||||
# position adjusted accordingly.
|
||||
def move_to_top
|
||||
return unless in_list?
|
||||
acts_as_list_class.transaction do
|
||||
increment_positions_on_higher_items
|
||||
assume_top_position
|
||||
end
|
||||
end
|
||||
|
||||
# Removes the item from the list.
|
||||
def remove_from_list
|
||||
if in_list?
|
||||
decrement_positions_on_lower_items
|
||||
update_attribute position_column, nil
|
||||
end
|
||||
end
|
||||
|
||||
# Increase the position of this item without adjusting the rest of the list.
|
||||
def increment_position
|
||||
return unless in_list?
|
||||
update_attribute position_column, self.send(position_column).to_i + 1
|
||||
end
|
||||
|
||||
# Decrease the position of this item without adjusting the rest of the list.
|
||||
def decrement_position
|
||||
return unless in_list?
|
||||
update_attribute position_column, self.send(position_column).to_i - 1
|
||||
end
|
||||
|
||||
# Return +true+ if this object is the first in the list.
|
||||
def first?
|
||||
return false unless in_list?
|
||||
self.send(position_column) == 1
|
||||
end
|
||||
|
||||
# Return +true+ if this object is the last in the list.
|
||||
def last?
|
||||
return false unless in_list?
|
||||
self.send(position_column) == bottom_position_in_list
|
||||
end
|
||||
|
||||
# Return the next higher item in the list.
|
||||
def higher_item
|
||||
return nil unless in_list?
|
||||
acts_as_list_class.find(:first, :conditions =>
|
||||
"#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}"
|
||||
)
|
||||
end
|
||||
|
||||
# Return the next lower item in the list.
|
||||
def lower_item
|
||||
return nil unless in_list?
|
||||
acts_as_list_class.find(:first, :conditions =>
|
||||
"#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}"
|
||||
)
|
||||
end
|
||||
|
||||
# Test if this record is in a list
|
||||
def in_list?
|
||||
!send(position_column).nil?
|
||||
end
|
||||
|
||||
private
|
||||
def add_to_list_top
|
||||
increment_positions_on_all_items
|
||||
end
|
||||
|
||||
def add_to_list_bottom
|
||||
self[position_column] = bottom_position_in_list.to_i + 1
|
||||
end
|
||||
|
||||
# Overwrite this method to define the scope of the list changes
|
||||
def scope_condition() "1" end
|
||||
|
||||
# Returns the bottom position number in the list.
|
||||
# bottom_position_in_list # => 2
|
||||
def bottom_position_in_list(except = nil)
|
||||
item = bottom_item(except)
|
||||
item ? item.send(position_column) : 0
|
||||
end
|
||||
|
||||
# Returns the bottom item
|
||||
def bottom_item(except = nil)
|
||||
conditions = scope_condition
|
||||
conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except
|
||||
acts_as_list_class.find(:first, :conditions => conditions, :order => "#{position_column} DESC")
|
||||
end
|
||||
|
||||
# Forces item to assume the bottom position in the list.
|
||||
def assume_bottom_position
|
||||
update_attribute(position_column, bottom_position_in_list(self).to_i + 1)
|
||||
end
|
||||
|
||||
# Forces item to assume the top position in the list.
|
||||
def assume_top_position
|
||||
update_attribute(position_column, 1)
|
||||
end
|
||||
|
||||
# This has the effect of moving all the higher items up one.
|
||||
def decrement_positions_on_higher_items(position)
|
||||
acts_as_list_class.update_all(
|
||||
"#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} <= #{position}"
|
||||
)
|
||||
end
|
||||
|
||||
# This has the effect of moving all the lower items up one.
|
||||
def decrement_positions_on_lower_items
|
||||
return unless in_list?
|
||||
acts_as_list_class.update_all(
|
||||
"#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}"
|
||||
)
|
||||
end
|
||||
|
||||
# This has the effect of moving all the higher items down one.
|
||||
def increment_positions_on_higher_items
|
||||
return unless in_list?
|
||||
acts_as_list_class.update_all(
|
||||
"#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column).to_i}"
|
||||
)
|
||||
end
|
||||
|
||||
# This has the effect of moving all the lower items down one.
|
||||
def increment_positions_on_lower_items(position)
|
||||
acts_as_list_class.update_all(
|
||||
"#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{position}"
|
||||
)
|
||||
end
|
||||
|
||||
# Increments position (<tt>position_column</tt>) of all items in the list.
|
||||
def increment_positions_on_all_items
|
||||
acts_as_list_class.update_all(
|
||||
"#{position_column} = (#{position_column} + 1)", "#{scope_condition}"
|
||||
)
|
||||
end
|
||||
|
||||
def insert_at_position(position)
|
||||
remove_from_list
|
||||
increment_positions_on_lower_items(position)
|
||||
self.update_attribute(position_column, position)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
332
vendor/plugins/acts_as_list/test/list_test.rb
vendored
Normal file
332
vendor/plugins/acts_as_list/test/list_test.rb
vendored
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
require 'test/unit'
|
||||
|
||||
require 'rubygems'
|
||||
gem 'activerecord', '>= 1.15.4.7794'
|
||||
require 'active_record'
|
||||
|
||||
require "#{File.dirname(__FILE__)}/../init"
|
||||
|
||||
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
|
||||
|
||||
def setup_db
|
||||
ActiveRecord::Schema.define(:version => 1) do
|
||||
create_table :mixins do |t|
|
||||
t.column :pos, :integer
|
||||
t.column :parent_id, :integer
|
||||
t.column :created_at, :datetime
|
||||
t.column :updated_at, :datetime
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def teardown_db
|
||||
ActiveRecord::Base.connection.tables.each do |table|
|
||||
ActiveRecord::Base.connection.drop_table(table)
|
||||
end
|
||||
end
|
||||
|
||||
class Mixin < ActiveRecord::Base
|
||||
end
|
||||
|
||||
class ListMixin < Mixin
|
||||
acts_as_list :column => "pos", :scope => :parent
|
||||
|
||||
def self.table_name() "mixins" end
|
||||
end
|
||||
|
||||
class ListMixinSub1 < ListMixin
|
||||
end
|
||||
|
||||
class ListMixinSub2 < ListMixin
|
||||
end
|
||||
|
||||
class ListWithStringScopeMixin < ActiveRecord::Base
|
||||
acts_as_list :column => "pos", :scope => 'parent_id = #{parent_id}'
|
||||
|
||||
def self.table_name() "mixins" end
|
||||
end
|
||||
|
||||
|
||||
class ListTest < Test::Unit::TestCase
|
||||
|
||||
def setup
|
||||
setup_db
|
||||
(1..4).each { |counter| ListMixin.create! :pos => counter, :parent_id => 5 }
|
||||
end
|
||||
|
||||
def teardown
|
||||
teardown_db
|
||||
end
|
||||
|
||||
def test_reordering
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(2).move_lower
|
||||
assert_equal [1, 3, 2, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(2).move_higher
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(1).move_to_bottom
|
||||
assert_equal [2, 3, 4, 1], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(1).move_to_top
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(2).move_to_bottom
|
||||
assert_equal [1, 3, 4, 2], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(4).move_to_top
|
||||
assert_equal [4, 1, 3, 2], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
end
|
||||
|
||||
def test_move_to_bottom_with_next_to_last_item
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
ListMixin.find(3).move_to_bottom
|
||||
assert_equal [1, 2, 4, 3], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
end
|
||||
|
||||
def test_next_prev
|
||||
assert_equal ListMixin.find(2), ListMixin.find(1).lower_item
|
||||
assert_nil ListMixin.find(1).higher_item
|
||||
assert_equal ListMixin.find(3), ListMixin.find(4).higher_item
|
||||
assert_nil ListMixin.find(4).lower_item
|
||||
end
|
||||
|
||||
def test_injection
|
||||
item = ListMixin.new(:parent_id => 1)
|
||||
assert_equal "parent_id = 1", item.scope_condition
|
||||
assert_equal "pos", item.position_column
|
||||
end
|
||||
|
||||
def test_insert
|
||||
new = ListMixin.create(:parent_id => 20)
|
||||
assert_equal 1, new.pos
|
||||
assert new.first?
|
||||
assert new.last?
|
||||
|
||||
new = ListMixin.create(:parent_id => 20)
|
||||
assert_equal 2, new.pos
|
||||
assert !new.first?
|
||||
assert new.last?
|
||||
|
||||
new = ListMixin.create(:parent_id => 20)
|
||||
assert_equal 3, new.pos
|
||||
assert !new.first?
|
||||
assert new.last?
|
||||
|
||||
new = ListMixin.create(:parent_id => 0)
|
||||
assert_equal 1, new.pos
|
||||
assert new.first?
|
||||
assert new.last?
|
||||
end
|
||||
|
||||
def test_insert_at
|
||||
new = ListMixin.create(:parent_id => 20)
|
||||
assert_equal 1, new.pos
|
||||
|
||||
new = ListMixin.create(:parent_id => 20)
|
||||
assert_equal 2, new.pos
|
||||
|
||||
new = ListMixin.create(:parent_id => 20)
|
||||
assert_equal 3, new.pos
|
||||
|
||||
new4 = ListMixin.create(:parent_id => 20)
|
||||
assert_equal 4, new4.pos
|
||||
|
||||
new4.insert_at(3)
|
||||
assert_equal 3, new4.pos
|
||||
|
||||
new.reload
|
||||
assert_equal 4, new.pos
|
||||
|
||||
new.insert_at(2)
|
||||
assert_equal 2, new.pos
|
||||
|
||||
new4.reload
|
||||
assert_equal 4, new4.pos
|
||||
|
||||
new5 = ListMixin.create(:parent_id => 20)
|
||||
assert_equal 5, new5.pos
|
||||
|
||||
new5.insert_at(1)
|
||||
assert_equal 1, new5.pos
|
||||
|
||||
new4.reload
|
||||
assert_equal 5, new4.pos
|
||||
end
|
||||
|
||||
def test_delete_middle
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(2).destroy
|
||||
|
||||
assert_equal [1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
assert_equal 1, ListMixin.find(1).pos
|
||||
assert_equal 2, ListMixin.find(3).pos
|
||||
assert_equal 3, ListMixin.find(4).pos
|
||||
|
||||
ListMixin.find(1).destroy
|
||||
|
||||
assert_equal [3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
assert_equal 1, ListMixin.find(3).pos
|
||||
assert_equal 2, ListMixin.find(4).pos
|
||||
end
|
||||
|
||||
def test_with_string_based_scope
|
||||
new = ListWithStringScopeMixin.create(:parent_id => 500)
|
||||
assert_equal 1, new.pos
|
||||
assert new.first?
|
||||
assert new.last?
|
||||
end
|
||||
|
||||
def test_nil_scope
|
||||
new1, new2, new3 = ListMixin.create, ListMixin.create, ListMixin.create
|
||||
new2.move_higher
|
||||
assert_equal [new2, new1, new3], ListMixin.find(:all, :conditions => 'parent_id IS NULL', :order => 'pos')
|
||||
end
|
||||
|
||||
|
||||
def test_remove_from_list_should_then_fail_in_list?
|
||||
assert_equal true, ListMixin.find(1).in_list?
|
||||
ListMixin.find(1).remove_from_list
|
||||
assert_equal false, ListMixin.find(1).in_list?
|
||||
end
|
||||
|
||||
def test_remove_from_list_should_set_position_to_nil
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(2).remove_from_list
|
||||
|
||||
assert_equal [2, 1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
assert_equal 1, ListMixin.find(1).pos
|
||||
assert_equal nil, ListMixin.find(2).pos
|
||||
assert_equal 2, ListMixin.find(3).pos
|
||||
assert_equal 3, ListMixin.find(4).pos
|
||||
end
|
||||
|
||||
def test_remove_before_destroy_does_not_shift_lower_items_twice
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(2).remove_from_list
|
||||
ListMixin.find(2).destroy
|
||||
|
||||
assert_equal [1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
assert_equal 1, ListMixin.find(1).pos
|
||||
assert_equal 2, ListMixin.find(3).pos
|
||||
assert_equal 3, ListMixin.find(4).pos
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class ListSubTest < Test::Unit::TestCase
|
||||
|
||||
def setup
|
||||
setup_db
|
||||
(1..4).each { |i| ((i % 2 == 1) ? ListMixinSub1 : ListMixinSub2).create! :pos => i, :parent_id => 5000 }
|
||||
end
|
||||
|
||||
def teardown
|
||||
teardown_db
|
||||
end
|
||||
|
||||
def test_reordering
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(2).move_lower
|
||||
assert_equal [1, 3, 2, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(2).move_higher
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(1).move_to_bottom
|
||||
assert_equal [2, 3, 4, 1], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(1).move_to_top
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(2).move_to_bottom
|
||||
assert_equal [1, 3, 4, 2], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(4).move_to_top
|
||||
assert_equal [4, 1, 3, 2], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
end
|
||||
|
||||
def test_move_to_bottom_with_next_to_last_item
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
ListMixin.find(3).move_to_bottom
|
||||
assert_equal [1, 2, 4, 3], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
end
|
||||
|
||||
def test_next_prev
|
||||
assert_equal ListMixin.find(2), ListMixin.find(1).lower_item
|
||||
assert_nil ListMixin.find(1).higher_item
|
||||
assert_equal ListMixin.find(3), ListMixin.find(4).higher_item
|
||||
assert_nil ListMixin.find(4).lower_item
|
||||
end
|
||||
|
||||
def test_injection
|
||||
item = ListMixin.new("parent_id"=>1)
|
||||
assert_equal "parent_id = 1", item.scope_condition
|
||||
assert_equal "pos", item.position_column
|
||||
end
|
||||
|
||||
def test_insert_at
|
||||
new = ListMixin.create("parent_id" => 20)
|
||||
assert_equal 1, new.pos
|
||||
|
||||
new = ListMixinSub1.create("parent_id" => 20)
|
||||
assert_equal 2, new.pos
|
||||
|
||||
new = ListMixinSub2.create("parent_id" => 20)
|
||||
assert_equal 3, new.pos
|
||||
|
||||
new4 = ListMixin.create("parent_id" => 20)
|
||||
assert_equal 4, new4.pos
|
||||
|
||||
new4.insert_at(3)
|
||||
assert_equal 3, new4.pos
|
||||
|
||||
new.reload
|
||||
assert_equal 4, new.pos
|
||||
|
||||
new.insert_at(2)
|
||||
assert_equal 2, new.pos
|
||||
|
||||
new4.reload
|
||||
assert_equal 4, new4.pos
|
||||
|
||||
new5 = ListMixinSub1.create("parent_id" => 20)
|
||||
assert_equal 5, new5.pos
|
||||
|
||||
new5.insert_at(1)
|
||||
assert_equal 1, new5.pos
|
||||
|
||||
new4.reload
|
||||
assert_equal 5, new4.pos
|
||||
end
|
||||
|
||||
def test_delete_middle
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(2).destroy
|
||||
|
||||
assert_equal [1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
|
||||
assert_equal 1, ListMixin.find(1).pos
|
||||
assert_equal 2, ListMixin.find(3).pos
|
||||
assert_equal 3, ListMixin.find(4).pos
|
||||
|
||||
ListMixin.find(1).destroy
|
||||
|
||||
assert_equal [3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
|
||||
assert_equal 1, ListMixin.find(3).pos
|
||||
assert_equal 2, ListMixin.find(4).pos
|
||||
end
|
||||
|
||||
end
|
||||
13
vendor/plugins/acts_as_state_machine/CHANGELOG
vendored
Normal file
13
vendor/plugins/acts_as_state_machine/CHANGELOG
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
* trunk *
|
||||
break with true value [Kaspar Schiess]
|
||||
|
||||
* 2.1 *
|
||||
After actions [Saimon Moore]
|
||||
|
||||
* 2.0 * (2006-01-20 15:26:28 -0500)
|
||||
Enter / Exit actions
|
||||
Transition guards
|
||||
Guards and actions can be a symbol pointing to a method or a Proc
|
||||
|
||||
* 1.0 * (2006-01-15 12:16:55 -0500)
|
||||
Initial Release
|
||||
20
vendor/plugins/acts_as_state_machine/MIT-LICENSE
vendored
Normal file
20
vendor/plugins/acts_as_state_machine/MIT-LICENSE
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
Copyright (c) 2006 Scott Barron
|
||||
|
||||
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.
|
||||
33
vendor/plugins/acts_as_state_machine/README
vendored
Normal file
33
vendor/plugins/acts_as_state_machine/README
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
= Acts As State Machine
|
||||
|
||||
This act gives an Active Record model the ability to act as a finite state
|
||||
machine (FSM).
|
||||
|
||||
Acquire via subversion at:
|
||||
|
||||
http://elitists.textdriven.com/svn/plugins/acts_as_state_machine/trunk
|
||||
|
||||
If prompted, use the user/pass anonymous/anonymous.
|
||||
|
||||
== Example
|
||||
|
||||
class Order < ActiveRecord::Base
|
||||
acts_as_state_machine :initial => :opened
|
||||
|
||||
state :opened
|
||||
state :closed, :enter => Proc.new {|o| Mailer.send_notice(o)}
|
||||
state :returned
|
||||
|
||||
event :close do
|
||||
transitions :to => :closed, :from => :opened
|
||||
end
|
||||
|
||||
event :return do
|
||||
transitions :to => :returned, :from => :closed
|
||||
end
|
||||
end
|
||||
|
||||
o = Order.create
|
||||
o.close! # notice is sent by mailer
|
||||
o.return!
|
||||
|
||||
28
vendor/plugins/acts_as_state_machine/Rakefile
vendored
Normal file
28
vendor/plugins/acts_as_state_machine/Rakefile
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
|
||||
desc 'Default: run unit tests.'
|
||||
task :default => [:clean_db, :test]
|
||||
|
||||
desc 'Remove the stale db file'
|
||||
task :clean_db do
|
||||
`rm -f #{File.dirname(__FILE__)}/test/state_machine.sqlite.db`
|
||||
end
|
||||
|
||||
desc 'Test the acts as state machine plugin.'
|
||||
Rake::TestTask.new(:test) do |t|
|
||||
t.libs << 'lib'
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
desc 'Generate documentation for the acts as state machine plugin.'
|
||||
Rake::RDocTask.new(:rdoc) do |rdoc|
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
rdoc.title = 'Acts As State Machine'
|
||||
rdoc.options << '--line-numbers --inline-source'
|
||||
rdoc.rdoc_files.include('README')
|
||||
rdoc.rdoc_files.include('TODO')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
end
|
||||
11
vendor/plugins/acts_as_state_machine/TODO
vendored
Normal file
11
vendor/plugins/acts_as_state_machine/TODO
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
* Currently invalid events are ignored, create an option so that they can be
|
||||
ignored or raise an exception.
|
||||
|
||||
* Query for a list of possible next states.
|
||||
|
||||
* Make listing states optional since they can be inferred from the events.
|
||||
Only required to list a state if you want to define a transition block for it.
|
||||
|
||||
* Real transition actions
|
||||
|
||||
* Default states
|
||||
5
vendor/plugins/acts_as_state_machine/init.rb
vendored
Normal file
5
vendor/plugins/acts_as_state_machine/init.rb
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
require 'acts_as_state_machine'
|
||||
|
||||
ActiveRecord::Base.class_eval do
|
||||
include ScottBarron::Acts::StateMachine
|
||||
end
|
||||
268
vendor/plugins/acts_as_state_machine/lib/acts_as_state_machine.rb
vendored
Normal file
268
vendor/plugins/acts_as_state_machine/lib/acts_as_state_machine.rb
vendored
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
module ScottBarron #:nodoc:
|
||||
module Acts #:nodoc:
|
||||
module StateMachine #:nodoc:
|
||||
class InvalidState < Exception #:nodoc:
|
||||
end
|
||||
class NoInitialState < Exception #:nodoc:
|
||||
end
|
||||
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend ActMacro
|
||||
end
|
||||
|
||||
module SupportingClasses
|
||||
class State
|
||||
attr_reader :name
|
||||
|
||||
def initialize(name, opts)
|
||||
@name, @opts = name, opts
|
||||
end
|
||||
|
||||
def entering(record)
|
||||
enteract = @opts[:enter]
|
||||
record.send(:run_transition_action, enteract) if enteract
|
||||
end
|
||||
|
||||
def entered(record)
|
||||
afteractions = @opts[:after]
|
||||
return unless afteractions
|
||||
Array(afteractions).each do |afteract|
|
||||
record.send(:run_transition_action, afteract)
|
||||
end
|
||||
end
|
||||
|
||||
def exited(record)
|
||||
exitact = @opts[:exit]
|
||||
record.send(:run_transition_action, exitact) if exitact
|
||||
end
|
||||
end
|
||||
|
||||
class StateTransition
|
||||
attr_reader :from, :to, :opts
|
||||
|
||||
def initialize(opts)
|
||||
@from, @to, @guard = opts[:from], opts[:to], opts[:guard]
|
||||
@opts = opts
|
||||
end
|
||||
|
||||
def guard(obj)
|
||||
@guard ? obj.send(:run_transition_action, @guard) : true
|
||||
end
|
||||
|
||||
def perform(record)
|
||||
return false unless guard(record)
|
||||
loopback = record.current_state == to
|
||||
states = record.class.read_inheritable_attribute(:states)
|
||||
next_state = states[to]
|
||||
old_state = states[record.current_state]
|
||||
|
||||
next_state.entering(record) unless loopback
|
||||
|
||||
record.update_attribute(record.class.state_column, to.to_s)
|
||||
|
||||
next_state.entered(record) unless loopback
|
||||
old_state.exited(record) unless loopback
|
||||
true
|
||||
end
|
||||
|
||||
def ==(obj)
|
||||
@from == obj.from && @to == obj.to
|
||||
end
|
||||
end
|
||||
|
||||
class Event
|
||||
attr_reader :name
|
||||
attr_reader :transitions
|
||||
attr_reader :opts
|
||||
|
||||
def initialize(name, opts, transition_table, &block)
|
||||
@name = name.to_sym
|
||||
@transitions = transition_table[@name] = []
|
||||
instance_eval(&block) if block
|
||||
@opts = opts
|
||||
@opts.freeze
|
||||
@transitions.freeze
|
||||
freeze
|
||||
end
|
||||
|
||||
def next_states(record)
|
||||
@transitions.select { |t| t.from == record.current_state }
|
||||
end
|
||||
|
||||
def fire(record)
|
||||
next_states(record).each do |transition|
|
||||
break true if transition.perform(record)
|
||||
end
|
||||
end
|
||||
|
||||
def transitions(trans_opts)
|
||||
Array(trans_opts[:from]).each do |s|
|
||||
@transitions << SupportingClasses::StateTransition.new(trans_opts.merge({:from => s.to_sym}))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ActMacro
|
||||
# Configuration options are
|
||||
#
|
||||
# * +column+ - specifies the column name to use for keeping the state (default: state)
|
||||
# * +initial+ - specifies an initial state for newly created objects (required)
|
||||
def acts_as_state_machine(opts)
|
||||
self.extend(ClassMethods)
|
||||
raise NoInitialState unless opts[:initial]
|
||||
|
||||
write_inheritable_attribute :states, {}
|
||||
write_inheritable_attribute :initial_state, opts[:initial]
|
||||
write_inheritable_attribute :transition_table, {}
|
||||
write_inheritable_attribute :event_table, {}
|
||||
write_inheritable_attribute :state_column, opts[:column] || 'state'
|
||||
|
||||
class_inheritable_reader :initial_state
|
||||
class_inheritable_reader :state_column
|
||||
class_inheritable_reader :transition_table
|
||||
class_inheritable_reader :event_table
|
||||
|
||||
self.send(:include, ScottBarron::Acts::StateMachine::InstanceMethods)
|
||||
|
||||
before_create :set_initial_state
|
||||
after_create :run_initial_state_actions
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
def set_initial_state #:nodoc:
|
||||
write_attribute self.class.state_column, self.class.initial_state.to_s
|
||||
end
|
||||
|
||||
def run_initial_state_actions
|
||||
initial = self.class.read_inheritable_attribute(:states)[self.class.initial_state.to_sym]
|
||||
initial.entering(self)
|
||||
initial.entered(self)
|
||||
end
|
||||
|
||||
# Returns the current state the object is in, as a Ruby symbol.
|
||||
def current_state
|
||||
self.send(self.class.state_column).to_sym
|
||||
end
|
||||
|
||||
# Returns what the next state for a given event would be, as a Ruby symbol.
|
||||
def next_state_for_event(event)
|
||||
ns = next_states_for_event(event)
|
||||
ns.empty? ? nil : ns.first.to
|
||||
end
|
||||
|
||||
def next_states_for_event(event)
|
||||
self.class.read_inheritable_attribute(:transition_table)[event.to_sym].select do |s|
|
||||
s.from == current_state
|
||||
end
|
||||
end
|
||||
|
||||
def run_transition_action(action)
|
||||
Symbol === action ? self.method(action).call : action.call(self)
|
||||
end
|
||||
private :run_transition_action
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Returns an array of all known states.
|
||||
def states
|
||||
read_inheritable_attribute(:states).keys
|
||||
end
|
||||
|
||||
# Define an event. This takes a block which describes all valid transitions
|
||||
# for this event.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# class Order < ActiveRecord::Base
|
||||
# acts_as_state_machine :initial => :open
|
||||
#
|
||||
# state :open
|
||||
# state :closed
|
||||
#
|
||||
# event :close_order do
|
||||
# transitions :to => :closed, :from => :open
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# +transitions+ takes a hash where <tt>:to</tt> is the state to transition
|
||||
# to and <tt>:from</tt> is a state (or Array of states) from which this
|
||||
# event can be fired.
|
||||
#
|
||||
# This creates an instance method used for firing the event. The method
|
||||
# created is the name of the event followed by an exclamation point (!).
|
||||
# Example: <tt>order.close_order!</tt>.
|
||||
def event(event, opts={}, &block)
|
||||
tt = read_inheritable_attribute(:transition_table)
|
||||
|
||||
et = read_inheritable_attribute(:event_table)
|
||||
e = et[event.to_sym] = SupportingClasses::Event.new(event, opts, tt, &block)
|
||||
define_method("#{event.to_s}!") { e.fire(self) }
|
||||
end
|
||||
|
||||
# Define a state of the system. +state+ can take an optional Proc object
|
||||
# which will be executed every time the system transitions into that
|
||||
# state. The proc will be passed the current object.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# class Order < ActiveRecord::Base
|
||||
# acts_as_state_machine :initial => :open
|
||||
#
|
||||
# state :open
|
||||
# state :closed, Proc.new { |o| Mailer.send_notice(o) }
|
||||
# end
|
||||
def state(name, opts={})
|
||||
state = SupportingClasses::State.new(name.to_sym, opts)
|
||||
read_inheritable_attribute(:states)[name.to_sym] = state
|
||||
|
||||
define_method("#{state.name}?") { current_state == state.name }
|
||||
end
|
||||
|
||||
# Wraps ActiveRecord::Base.find to conveniently find all records in
|
||||
# a given state. Options:
|
||||
#
|
||||
# * +number+ - This is just :first or :all from ActiveRecord +find+
|
||||
# * +state+ - The state to find
|
||||
# * +args+ - The rest of the args are passed down to ActiveRecord +find+
|
||||
def find_in_state(number, state, *args)
|
||||
with_state_scope state do
|
||||
find(number, *args)
|
||||
end
|
||||
end
|
||||
|
||||
# Wraps ActiveRecord::Base.count to conveniently count all records in
|
||||
# a given state. Options:
|
||||
#
|
||||
# * +state+ - The state to find
|
||||
# * +args+ - The rest of the args are passed down to ActiveRecord +find+
|
||||
def count_in_state(state, *args)
|
||||
with_state_scope state do
|
||||
count(*args)
|
||||
end
|
||||
end
|
||||
|
||||
# Wraps ActiveRecord::Base.calculate to conveniently calculate all records in
|
||||
# a given state. Options:
|
||||
#
|
||||
# * +state+ - The state to find
|
||||
# * +args+ - The rest of the args are passed down to ActiveRecord +calculate+
|
||||
def calculate_in_state(state, *args)
|
||||
with_state_scope state do
|
||||
calculate(*args)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def with_state_scope(state)
|
||||
raise InvalidState unless states.include?(state)
|
||||
|
||||
with_scope :find => {:conditions => ["#{table_name}.#{state_column} = ?", state.to_s]} do
|
||||
yield if block_given?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
224
vendor/plugins/acts_as_state_machine/test/acts_as_state_machine_test.rb
vendored
Normal file
224
vendor/plugins/acts_as_state_machine/test/acts_as_state_machine_test.rb
vendored
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
require File.dirname(__FILE__) + '/test_helper'
|
||||
|
||||
include ScottBarron::Acts::StateMachine
|
||||
|
||||
class ActsAsStateMachineTest < Test::Unit::TestCase
|
||||
fixtures :conversations
|
||||
|
||||
def test_no_initial_value_raises_exception
|
||||
assert_raise(NoInitialState) {
|
||||
Person.acts_as_state_machine({})
|
||||
}
|
||||
end
|
||||
|
||||
def test_initial_state_value
|
||||
assert_equal :needs_attention, Conversation.initial_state
|
||||
end
|
||||
|
||||
def test_column_was_set
|
||||
assert_equal 'state_machine', Conversation.state_column
|
||||
end
|
||||
|
||||
def test_initial_state
|
||||
c = Conversation.create
|
||||
assert_equal :needs_attention, c.current_state
|
||||
assert c.needs_attention?
|
||||
end
|
||||
|
||||
def test_states_were_set
|
||||
[:needs_attention, :read, :closed, :awaiting_response, :junk].each do |s|
|
||||
assert Conversation.states.include?(s)
|
||||
end
|
||||
end
|
||||
|
||||
def test_event_methods_created
|
||||
c = Conversation.create
|
||||
%w(new_message! view! reply! close! junk! unjunk!).each do |event|
|
||||
assert c.respond_to?(event)
|
||||
end
|
||||
end
|
||||
|
||||
def test_query_methods_created
|
||||
c = Conversation.create
|
||||
%w(needs_attention? read? closed? awaiting_response? junk?).each do |event|
|
||||
assert c.respond_to?(event)
|
||||
end
|
||||
end
|
||||
|
||||
def test_transition_table
|
||||
tt = Conversation.transition_table
|
||||
|
||||
assert tt[:new_message].include?(SupportingClasses::StateTransition.new(:from => :read, :to => :needs_attention))
|
||||
assert tt[:new_message].include?(SupportingClasses::StateTransition.new(:from => :closed, :to => :needs_attention))
|
||||
assert tt[:new_message].include?(SupportingClasses::StateTransition.new(:from => :awaiting_response, :to => :needs_attention))
|
||||
end
|
||||
|
||||
def test_next_state_for_event
|
||||
c = Conversation.create
|
||||
assert_equal :read, c.next_state_for_event(:view)
|
||||
end
|
||||
|
||||
def test_change_state
|
||||
c = Conversation.create
|
||||
c.view!
|
||||
assert c.read?
|
||||
end
|
||||
|
||||
def test_can_go_from_read_to_closed_because_guard_passes
|
||||
c = Conversation.create
|
||||
c.can_close = true
|
||||
c.view!
|
||||
c.reply!
|
||||
c.close!
|
||||
assert_equal :closed, c.current_state
|
||||
end
|
||||
|
||||
def test_cannot_go_from_read_to_closed_because_of_guard
|
||||
c = Conversation.create
|
||||
c.can_close = false
|
||||
c.view!
|
||||
c.reply!
|
||||
c.close!
|
||||
assert_equal :read, c.current_state
|
||||
end
|
||||
|
||||
def test_ignore_invalid_events
|
||||
c = Conversation.create
|
||||
c.view!
|
||||
c.junk!
|
||||
|
||||
# This is the invalid event
|
||||
c.new_message!
|
||||
assert_equal :junk, c.current_state
|
||||
end
|
||||
|
||||
def test_entry_action_executed
|
||||
c = Conversation.create
|
||||
c.read_enter = false
|
||||
c.view!
|
||||
assert c.read_enter
|
||||
end
|
||||
|
||||
def test_after_actions_executed
|
||||
c = Conversation.create
|
||||
|
||||
c.read_after_first = false
|
||||
c.read_after_second = false
|
||||
c.closed_after = false
|
||||
|
||||
c.view!
|
||||
assert c.read_after_first
|
||||
assert c.read_after_second
|
||||
|
||||
c.can_close = true
|
||||
c.close!
|
||||
|
||||
assert c.closed_after
|
||||
assert_equal :closed, c.current_state
|
||||
end
|
||||
|
||||
def test_after_actions_not_run_on_loopback_transition
|
||||
c = Conversation.create
|
||||
|
||||
c.view!
|
||||
c.read_after_first = false
|
||||
c.read_after_second = false
|
||||
c.view!
|
||||
|
||||
assert !c.read_after_first
|
||||
assert !c.read_after_second
|
||||
|
||||
c.can_close = true
|
||||
|
||||
c.close!
|
||||
c.closed_after = false
|
||||
c.close!
|
||||
|
||||
assert !c.closed_after
|
||||
end
|
||||
|
||||
def test_exit_action_executed
|
||||
c = Conversation.create
|
||||
c.read_exit = false
|
||||
c.view!
|
||||
c.junk!
|
||||
assert c.read_exit
|
||||
end
|
||||
|
||||
def test_entry_and_exit_not_run_on_loopback_transition
|
||||
c = Conversation.create
|
||||
c.view!
|
||||
c.read_enter = false
|
||||
c.read_exit = false
|
||||
c.view!
|
||||
assert !c.read_enter
|
||||
assert !c.read_exit
|
||||
end
|
||||
|
||||
def test_entry_and_after_actions_called_for_initial_state
|
||||
c = Conversation.create
|
||||
assert c.needs_attention_enter
|
||||
assert c.needs_attention_after
|
||||
end
|
||||
|
||||
def test_run_transition_action_is_private
|
||||
c = Conversation.create
|
||||
assert_raise(NoMethodError) { c.run_transition_action :foo }
|
||||
end
|
||||
|
||||
def test_find_all_in_state
|
||||
cs = Conversation.find_in_state(:all, :read)
|
||||
|
||||
assert_equal 2, cs.size
|
||||
end
|
||||
|
||||
def test_find_first_in_state
|
||||
c = Conversation.find_in_state(:first, :read)
|
||||
|
||||
assert_equal conversations(:first).id, c.id
|
||||
end
|
||||
|
||||
def test_find_all_in_state_with_conditions
|
||||
cs = Conversation.find_in_state(:all, :read, :conditions => ['subject = ?', conversations(:second).subject])
|
||||
|
||||
assert_equal 1, cs.size
|
||||
assert_equal conversations(:second).id, cs.first.id
|
||||
end
|
||||
|
||||
def test_find_first_in_state_with_conditions
|
||||
c = Conversation.find_in_state(:first, :read, :conditions => ['subject = ?', conversations(:second).subject])
|
||||
assert_equal conversations(:second).id, c.id
|
||||
end
|
||||
|
||||
def test_count_in_state
|
||||
cnt0 = Conversation.count(['state_machine = ?', 'read'])
|
||||
cnt = Conversation.count_in_state(:read)
|
||||
|
||||
assert_equal cnt0, cnt
|
||||
end
|
||||
|
||||
def test_count_in_state_with_conditions
|
||||
cnt0 = Conversation.count(['state_machine = ? AND subject = ?', 'read', 'Foo'])
|
||||
cnt = Conversation.count_in_state(:read, ['subject = ?', 'Foo'])
|
||||
|
||||
assert_equal cnt0, cnt
|
||||
end
|
||||
|
||||
def test_find_in_invalid_state_raises_exception
|
||||
assert_raise(InvalidState) {
|
||||
Conversation.find_in_state(:all, :dead)
|
||||
}
|
||||
end
|
||||
|
||||
def test_count_in_invalid_state_raises_exception
|
||||
assert_raise(InvalidState) {
|
||||
Conversation.count_in_state(:dead)
|
||||
}
|
||||
end
|
||||
|
||||
def test_can_access_events_via_event_table
|
||||
event = Conversation.event_table[:junk]
|
||||
assert_equal :junk, event.name
|
||||
assert_equal "finished", event.opts[:note]
|
||||
end
|
||||
end
|
||||
18
vendor/plugins/acts_as_state_machine/test/database.yml
vendored
Normal file
18
vendor/plugins/acts_as_state_machine/test/database.yml
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
sqlite:
|
||||
:adapter: sqlite
|
||||
:dbfile: state_machine.sqlite.db
|
||||
sqlite3:
|
||||
:adapter: sqlite3
|
||||
:dbfile: state_machine.sqlite3.db
|
||||
postgresql:
|
||||
:adapter: postgresql
|
||||
:username: postgres
|
||||
:password: postgres
|
||||
:database: state_machine_test
|
||||
:min_messages: ERROR
|
||||
mysql:
|
||||
:adapter: mysql
|
||||
:host: localhost
|
||||
:username: rails
|
||||
:password:
|
||||
:database: state_machine_test
|
||||
67
vendor/plugins/acts_as_state_machine/test/fixtures/conversation.rb
vendored
Normal file
67
vendor/plugins/acts_as_state_machine/test/fixtures/conversation.rb
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
class Conversation < ActiveRecord::Base
|
||||
attr_writer :can_close
|
||||
attr_accessor :read_enter, :read_exit, :read_after_first, :read_after_second,
|
||||
:closed_after, :needs_attention_enter, :needs_attention_after
|
||||
|
||||
acts_as_state_machine :initial => :needs_attention, :column => 'state_machine'
|
||||
|
||||
state :needs_attention, :enter => Proc.new { |o| o.needs_attention_enter = true },
|
||||
:after => Proc.new { |o| o.needs_attention_after = true }
|
||||
|
||||
state :read, :enter => :read_enter_action,
|
||||
:exit => Proc.new { |o| o.read_exit = true },
|
||||
:after => [:read_after_first_action, :read_after_second_action]
|
||||
|
||||
state :closed, :after => :closed_after_action
|
||||
state :awaiting_response
|
||||
state :junk
|
||||
|
||||
event :new_message do
|
||||
transitions :to => :needs_attention, :from => [:read, :closed, :awaiting_response]
|
||||
end
|
||||
|
||||
event :view do
|
||||
transitions :to => :read, :from => [:needs_attention, :read]
|
||||
end
|
||||
|
||||
event :reply do
|
||||
transitions :to => :awaiting_response, :from => [:read, :closed]
|
||||
end
|
||||
|
||||
event :close do
|
||||
transitions :to => :closed, :from => [:read, :awaiting_response], :guard => Proc.new {|o| o.can_close?}
|
||||
transitions :to => :read, :from => [:read, :awaiting_response], :guard => :always_true
|
||||
end
|
||||
|
||||
event :junk, :note => "finished" do
|
||||
transitions :to => :junk, :from => [:read, :closed, :awaiting_response]
|
||||
end
|
||||
|
||||
event :unjunk do
|
||||
transitions :to => :closed, :from => :junk
|
||||
end
|
||||
|
||||
def can_close?
|
||||
@can_close
|
||||
end
|
||||
|
||||
def read_enter_action
|
||||
self.read_enter = true
|
||||
end
|
||||
|
||||
def always_true
|
||||
true
|
||||
end
|
||||
|
||||
def read_after_first_action
|
||||
self.read_after_first = true
|
||||
end
|
||||
|
||||
def read_after_second_action
|
||||
self.read_after_second = true
|
||||
end
|
||||
|
||||
def closed_after_action
|
||||
self.closed_after = true
|
||||
end
|
||||
end
|
||||
11
vendor/plugins/acts_as_state_machine/test/fixtures/conversations.yml
vendored
Normal file
11
vendor/plugins/acts_as_state_machine/test/fixtures/conversations.yml
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
first:
|
||||
id: 1
|
||||
state_machine: read
|
||||
subject: This is a test
|
||||
closed: false
|
||||
|
||||
second:
|
||||
id: 2
|
||||
state_machine: read
|
||||
subject: Foo
|
||||
closed: false
|
||||
2
vendor/plugins/acts_as_state_machine/test/fixtures/person.rb
vendored
Normal file
2
vendor/plugins/acts_as_state_machine/test/fixtures/person.rb
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
class Person < ActiveRecord::Base
|
||||
end
|
||||
11
vendor/plugins/acts_as_state_machine/test/schema.rb
vendored
Normal file
11
vendor/plugins/acts_as_state_machine/test/schema.rb
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
ActiveRecord::Schema.define(:version => 1) do
|
||||
create_table :conversations, :force => true do |t|
|
||||
t.column :state_machine, :string
|
||||
t.column :subject, :string
|
||||
t.column :closed, :boolean
|
||||
end
|
||||
|
||||
create_table :people, :force => true do |t|
|
||||
t.column :name, :string
|
||||
end
|
||||
end
|
||||
38
vendor/plugins/acts_as_state_machine/test/test_helper.rb
vendored
Normal file
38
vendor/plugins/acts_as_state_machine/test/test_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
||||
RAILS_ROOT = File.dirname(__FILE__)
|
||||
|
||||
require 'rubygems'
|
||||
require 'test/unit'
|
||||
require 'active_record'
|
||||
require 'active_record/fixtures'
|
||||
require 'active_support/binding_of_caller'
|
||||
require 'active_support/breakpoint'
|
||||
require "#{File.dirname(__FILE__)}/../init"
|
||||
|
||||
|
||||
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
||||
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
|
||||
ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite'])
|
||||
|
||||
load(File.dirname(__FILE__) + "/schema.rb") if File.exist?(File.dirname(__FILE__) + "/schema.rb")
|
||||
|
||||
Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/"
|
||||
$LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path)
|
||||
|
||||
class Test::Unit::TestCase #:nodoc:
|
||||
def create_fixtures(*table_names)
|
||||
if block_given?
|
||||
Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) { yield }
|
||||
else
|
||||
Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names)
|
||||
end
|
||||
end
|
||||
|
||||
# Turn off transactional fixtures if you're working with MyISAM tables in MySQL
|
||||
self.use_transactional_fixtures = true
|
||||
|
||||
# Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david)
|
||||
self.use_instantiated_fixtures = false
|
||||
|
||||
# Add more helper methods to be used by all tests here...
|
||||
end
|
||||
28
vendor/plugins/arts/README
vendored
Normal file
28
vendor/plugins/arts/README
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
ARTS is Another RJS Test System
|
||||
|
||||
For a complete tutorial, see http://glu.ttono.us/articles/2006/05/29/guide-test-driven-rjs-with-arts.
|
||||
|
||||
Usage:
|
||||
assert_rjs :alert, 'Hi!'
|
||||
assert_rjs :assign, 'a', '2'
|
||||
assert_rjs :call, 'foo', 'bar', 'baz'
|
||||
assert_rjs :draggable, 'draggable_item'
|
||||
assert_rjs :drop_receiving, 'receiving_item'
|
||||
assert_rjs :hide, "post_1", "post_2", "post_3"
|
||||
assert_rjs :insert_html, :bottom, 'posts'
|
||||
assert_rjs :redirect_to, :action => 'list'
|
||||
assert_rjs :remove, "post_1", "post_2", "post_3"
|
||||
assert_rjs :replace, 'completely_replaced_div'
|
||||
assert_rjs :replace, 'completely_replaced_div', '<p>This replaced the div</p>'
|
||||
assert_rjs :replace, 'completely_replaced_div', /replaced the div/
|
||||
assert_rjs :replace_html, 'replaceable_div', "This goes inside the div"
|
||||
assert_rjs :show, "post_1", "post_2", "post_3"
|
||||
assert_rjs :sortable, 'sortable_item'
|
||||
assert_rjs :toggle, "post_1", "post_2", "post_3"
|
||||
assert_rjs :visual_effect, :highlight, "posts", :duration => '1.0'
|
||||
|
||||
For the square bracket syntax (page['some_id'].toggle) use :page followed by the id and then subsequent method calls. Assignment requires a '=' at the end of the method name followed by the value.
|
||||
|
||||
assert_rjs :page, 'some_id', :toggle
|
||||
assert_rjs :page, 'some_id', :style, :color=, 'red'
|
||||
|
||||
7
vendor/plugins/arts/about.yml
vendored
Normal file
7
vendor/plugins/arts/about.yml
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
author: Kevin Clark
|
||||
summary: RJS Assertion Plugin
|
||||
homepage: http://glu.ttono.us
|
||||
plugin:
|
||||
version: 0.6
|
||||
license: MIT
|
||||
rails_version: 1.1.2+
|
||||
3
vendor/plugins/arts/init.rb
vendored
Normal file
3
vendor/plugins/arts/init.rb
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Give testing some culture
|
||||
require 'test/unit/testcase'
|
||||
Test::Unit::TestCase.send :include, Arts
|
||||
1
vendor/plugins/arts/install.rb
vendored
Normal file
1
vendor/plugins/arts/install.rb
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
puts IO.read(File.join(File.dirname(__FILE__), 'README'))
|
||||
133
vendor/plugins/arts/lib/arts.rb
vendored
Normal file
133
vendor/plugins/arts/lib/arts.rb
vendored
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
module Arts
|
||||
include ActionView::Helpers::PrototypeHelper
|
||||
include ActionView::Helpers::ScriptaculousHelper
|
||||
include ActionView::Helpers::JavaScriptHelper
|
||||
|
||||
include ActionView::Helpers::UrlHelper
|
||||
include ActionView::Helpers::TagHelper
|
||||
|
||||
def assert_rjs(action, *args, &block)
|
||||
respond_to?("assert_rjs_#{action}") ?
|
||||
send("assert_rjs_#{action}", *args) :
|
||||
assert_response_contains(create_generator.send(action, *args, &block),
|
||||
generic_error(action, args))
|
||||
end
|
||||
|
||||
def assert_no_rjs(action, *args, &block)
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_rjs(action, *args, &block) }
|
||||
end
|
||||
|
||||
def assert_rjs_insert_html(*args)
|
||||
position = args.shift
|
||||
item_id = args.shift
|
||||
|
||||
content = extract_matchable_content(args)
|
||||
|
||||
unless content.blank?
|
||||
case content
|
||||
when Regexp
|
||||
assert_match Regexp.new("new Insertion\.#{position.to_s.camelize}(.*#{item_id}.*,.*#{content.source}.*);"),
|
||||
@response.body
|
||||
when String
|
||||
assert_response_contains("new Insertion.#{position.to_s.camelize}(\"#{item_id}\", #{content});",
|
||||
"No insert_html call found for \n" +
|
||||
" position: '#{position}' id: '#{item_id}' \ncontent: \n" +
|
||||
"#{content}\n" +
|
||||
"in response:\n#{@response.body}")
|
||||
else
|
||||
raise "Invalid content type"
|
||||
end
|
||||
else
|
||||
assert_match Regexp.new("new Insertion\.#{position.to_s.camelize}(.*#{item_id}.*,.*?);"),
|
||||
@response.body
|
||||
end
|
||||
end
|
||||
|
||||
def assert_rjs_replace_html(*args)
|
||||
div = args.shift
|
||||
content = extract_matchable_content(args)
|
||||
|
||||
unless content.blank?
|
||||
case content
|
||||
when Regexp
|
||||
assert_match Regexp.new("Element.update(.*#{div}.*,.*#{content.source}.*);"),
|
||||
@response.body
|
||||
when String
|
||||
assert_response_contains("Element.update(\"#{div}\", #{content});",
|
||||
"No replace_html call found on div: '#{div}' and content: \n#{content}\n" +
|
||||
"in response:\n#{@response.body}")
|
||||
else
|
||||
raise "Invalid content type"
|
||||
end
|
||||
else
|
||||
assert_match Regexp.new("Element.update(.*#{div}.*,.*?);"), @response.body
|
||||
end
|
||||
end
|
||||
|
||||
def assert_rjs_replace(*args)
|
||||
div = args.shift
|
||||
content = extract_matchable_content(args)
|
||||
|
||||
unless content.blank?
|
||||
case content
|
||||
when Regexp
|
||||
assert_match Regexp.new("Element.replace(.*#{div}.*,.*#{content.source}.*);"),
|
||||
@response.body
|
||||
when String
|
||||
assert_response_contains("Element.replace(\"#{div}\", #{content});",
|
||||
"No replace call found on div: '#{div}' and content: \n#{content}\n" +
|
||||
"in response:\n#{@response.body}")
|
||||
else
|
||||
raise "Invalid content type"
|
||||
end
|
||||
else
|
||||
assert_match Regexp.new("Element.replace(.*#{div}.*,.*?);"), @response.body
|
||||
end
|
||||
end
|
||||
|
||||
# To deal with [] syntax. I hate JavaScriptProxy so.. SO very much
|
||||
def assert_rjs_page(*args)
|
||||
content = build_method_chain!(args)
|
||||
assert_match Regexp.new(Regexp.escape(content)), @response.body,
|
||||
"Content did not include:\n #{content.to_s}"
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def assert_response_contains(str, message)
|
||||
assert @response.body.to_s.index(str), message
|
||||
end
|
||||
|
||||
def build_method_chain!(args)
|
||||
content = create_generator.send(:[], args.shift) # start $('some_id')....
|
||||
|
||||
while !args.empty?
|
||||
if (method = args.shift.to_s) =~ /(.*)=$/
|
||||
content = content.__send__(method, args.shift)
|
||||
break
|
||||
else
|
||||
content = content.__send__(method)
|
||||
content = content.__send__(:function_chain).first if args.empty?
|
||||
end
|
||||
end
|
||||
|
||||
content
|
||||
end
|
||||
|
||||
def create_generator
|
||||
block = Proc.new { |*args| yield *args if block_given? }
|
||||
JavaScriptGenerator.new self, &block
|
||||
end
|
||||
|
||||
def generic_error(action, args)
|
||||
"#{action} with args [#{args.join(" ")}] does not show up in response:\n#{@response.body}"
|
||||
end
|
||||
|
||||
def extract_matchable_content(args)
|
||||
if args.size == 1 and args.first.is_a? Regexp
|
||||
return args.first
|
||||
else
|
||||
return create_generator.send(:arguments_for_call, args)
|
||||
end
|
||||
end
|
||||
end
|
||||
402
vendor/plugins/arts/test/arts_test.rb
vendored
Normal file
402
vendor/plugins/arts/test/arts_test.rb
vendored
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
||||
|
||||
require File.dirname(__FILE__) + '/../../../../config/environment'
|
||||
require 'test/unit'
|
||||
require 'rubygems'
|
||||
require 'breakpoint'
|
||||
|
||||
require 'action_controller/test_process'
|
||||
|
||||
ActionController::Base.logger = nil
|
||||
ActionController::Base.ignore_missing_templates = false
|
||||
ActionController::Routing::Routes.reload rescue nil
|
||||
|
||||
class ArtsController < ActionController::Base
|
||||
def alert
|
||||
render :update do |page|
|
||||
page.alert 'This is an alert'
|
||||
end
|
||||
end
|
||||
|
||||
def assign
|
||||
render :update do |page|
|
||||
page.assign 'a', '2'
|
||||
end
|
||||
end
|
||||
|
||||
def call
|
||||
render :update do |page|
|
||||
page.call 'foo', 'bar', 'baz'
|
||||
end
|
||||
end
|
||||
|
||||
def draggable
|
||||
render :update do |page|
|
||||
page.draggable 'my_image', :revert => true
|
||||
end
|
||||
end
|
||||
|
||||
def drop_receiving
|
||||
render :update do |page|
|
||||
page.drop_receiving "my_cart", :url => { :controller => "cart", :action => "add" }
|
||||
end
|
||||
end
|
||||
|
||||
def hide
|
||||
render :update do |page|
|
||||
page.hide 'some_div'
|
||||
end
|
||||
end
|
||||
|
||||
def insert_html
|
||||
render :update do |page|
|
||||
page.insert_html :bottom, 'content', 'Stuff in the content div'
|
||||
end
|
||||
end
|
||||
|
||||
def redirect
|
||||
render :update do |page|
|
||||
page.redirect_to :controller => 'sample', :action => 'index'
|
||||
end
|
||||
end
|
||||
|
||||
def remove
|
||||
render :update do |page|
|
||||
page.remove 'offending_div'
|
||||
end
|
||||
end
|
||||
|
||||
def replace
|
||||
render :update do |page|
|
||||
page.replace 'person_45', '<div>This replaces person_45</div>'
|
||||
end
|
||||
end
|
||||
|
||||
def replace_html
|
||||
render :update do |page|
|
||||
page.replace_html 'person_45', 'This goes inside person_45'
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
render :update do |page|
|
||||
page.show 'post_1', 'post_2', 'post_3'
|
||||
end
|
||||
end
|
||||
|
||||
def sortable
|
||||
render :update do |page|
|
||||
page.sortable 'sortable_item'
|
||||
end
|
||||
end
|
||||
|
||||
def toggle
|
||||
render :update do |page|
|
||||
page.toggle "post_1", "post_2", "post_3"
|
||||
end
|
||||
end
|
||||
|
||||
def visual_effect
|
||||
render :update do |page|
|
||||
page.visual_effect :highlight, "posts", :duration => '1.0'
|
||||
end
|
||||
end
|
||||
|
||||
def page_with_one_chained_method
|
||||
render :update do |page|
|
||||
page['some_id'].toggle
|
||||
end
|
||||
end
|
||||
|
||||
def page_with_assignment
|
||||
render :update do |page|
|
||||
page['some_id'].style.color = 'red'
|
||||
end
|
||||
end
|
||||
|
||||
def rescue_errors(e) raise e end
|
||||
|
||||
end
|
||||
|
||||
class ArtsTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@controller = ArtsController.new
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
end
|
||||
|
||||
def test_alert
|
||||
get :alert
|
||||
|
||||
assert_nothing_raised { assert_rjs :alert, 'This is an alert' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_rjs :alert, 'This is not an alert'
|
||||
end
|
||||
|
||||
assert_nothing_raised { assert_no_rjs :alert, 'This is not an alert' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :alert, 'This is an alert'
|
||||
end
|
||||
end
|
||||
|
||||
def test_assign
|
||||
get :assign
|
||||
|
||||
assert_nothing_raised { assert_rjs :assign, 'a', '2' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_rjs :assign, 'a', '3'
|
||||
end
|
||||
|
||||
assert_nothing_raised { assert_no_rjs :assign, 'a', '3' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :assign, 'a', '2'
|
||||
end
|
||||
end
|
||||
|
||||
def test_call
|
||||
get :call
|
||||
|
||||
assert_nothing_raised { assert_rjs :call, 'foo', 'bar', 'baz' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_rjs :call, 'foo', 'bar'
|
||||
end
|
||||
|
||||
assert_nothing_raised { assert_no_rjs :call, 'foo', 'bar' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :call, 'foo', 'bar', 'baz'
|
||||
end
|
||||
end
|
||||
|
||||
def test_draggable
|
||||
get :draggable
|
||||
|
||||
assert_nothing_raised { assert_rjs :draggable, 'my_image', :revert => true }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_rjs :draggable, 'not_my_image'
|
||||
end
|
||||
|
||||
assert_nothing_raised { assert_no_rjs :draggable, 'not_my_image' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :draggable, 'my_image', :revert => true
|
||||
end
|
||||
end
|
||||
|
||||
def test_drop_receiving
|
||||
get :drop_receiving
|
||||
|
||||
assert_nothing_raised { assert_rjs :drop_receiving, "my_cart", :url => { :controller => "cart", :action => "add" } }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_rjs :drop_receiving, "my_cart"
|
||||
end
|
||||
|
||||
assert_nothing_raised { assert_no_rjs :drop_receiving, "my_cart" }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :drop_receiving, "my_cart", :url => { :controller => "cart", :action => "add" }
|
||||
end
|
||||
end
|
||||
|
||||
def test_hide
|
||||
get :hide
|
||||
|
||||
assert_nothing_raised { assert_rjs :hide, 'some_div' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_rjs :hide, 'some_other_div'
|
||||
end
|
||||
|
||||
assert_nothing_raised { assert_no_rjs :hide, 'not_some_div' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :hide, 'some_div'
|
||||
end
|
||||
end
|
||||
|
||||
def test_insert_html
|
||||
get :insert_html
|
||||
|
||||
|
||||
assert_nothing_raised do
|
||||
# No content matching
|
||||
assert_rjs :insert_html, :bottom, 'content'
|
||||
# Exact content matching
|
||||
assert_rjs :insert_html, :bottom, 'content', 'Stuff in the content div'
|
||||
# Regex matching
|
||||
assert_rjs :insert_html, :bottom, 'content', /in.*content/
|
||||
|
||||
assert_no_rjs :insert_html, :bottom, 'not_our_div'
|
||||
|
||||
assert_no_rjs :insert_html, :bottom, 'content', /in.*no content/
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :insert_html, :bottom, 'content'
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_rjs :insert_html, :bottom, 'no_content'
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :insert_html, :bottom, 'content', /in the/
|
||||
end
|
||||
end
|
||||
|
||||
def test_redirect_to
|
||||
get :redirect
|
||||
|
||||
assert_nothing_raised do
|
||||
assert_rjs :redirect_to, :controller => 'sample', :action => 'index'
|
||||
assert_no_rjs :redirect_to, :controller => 'sample', :action => 'show'
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_rjs :redirect_to, :controller => 'doesnt', :action => 'exist'
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :redirect_to, :controller => 'sample', :action => 'index'
|
||||
end
|
||||
end
|
||||
|
||||
def test_remove
|
||||
get :remove
|
||||
|
||||
assert_nothing_raised do
|
||||
assert_rjs :remove, 'offending_div'
|
||||
assert_no_rjs :remove, 'dancing_happy_div'
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_rjs :remove, 'dancing_happy_div'
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :remove, 'offending_div'
|
||||
end
|
||||
end
|
||||
|
||||
def test_replace
|
||||
get :replace
|
||||
|
||||
assert_nothing_raised do
|
||||
# No content matching
|
||||
assert_rjs :replace, 'person_45'
|
||||
# String content matching
|
||||
assert_rjs :replace, 'person_45', '<div>This replaces person_45</div>'
|
||||
# regexp content matching
|
||||
assert_rjs :replace, 'person_45', /<div>.*person_45.*<\/div>/
|
||||
|
||||
assert_no_rjs :replace, 'person_45', '<div>This replaces person_46</div>'
|
||||
|
||||
assert_no_rjs :replace, 'person_45', /person_46/
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_no_rjs :replace, 'person_45' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_no_rjs :replace, 'person_45', /person_45/ }
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_rjs :replace, 'person_46' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_rjs :replace, 'person_45', 'bad stuff' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_rjs :replace, 'person_45', /not there/}
|
||||
end
|
||||
|
||||
def test_replace_html
|
||||
get :replace_html
|
||||
|
||||
assert_nothing_raised do
|
||||
# No content matching
|
||||
assert_rjs :replace_html, 'person_45'
|
||||
# String content matching
|
||||
assert_rjs :replace_html, 'person_45', 'This goes inside person_45'
|
||||
# Regexp content matching
|
||||
assert_rjs :replace_html, 'person_45', /goes inside/
|
||||
|
||||
assert_no_rjs :replace_html, 'person_46'
|
||||
|
||||
assert_no_rjs :replace_html, 'person_45', /doesn't go inside/
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_no_rjs :replace_html, 'person_45' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_no_rjs :replace_html, 'person_45', /goes/ }
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_rjs :replace_html, 'person_46' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_rjs :replace_html, 'person_45', /gos inside/ }
|
||||
end
|
||||
|
||||
def test_show
|
||||
get :show
|
||||
assert_nothing_raised do
|
||||
assert_rjs :show, "post_1", "post_2", "post_3"
|
||||
assert_no_rjs :show, 'post_4'
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_rjs :show, 'post_4' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :show, "post_1", "post_2", "post_3"
|
||||
end
|
||||
end
|
||||
|
||||
def test_sortable
|
||||
get :sortable
|
||||
assert_nothing_raised do
|
||||
assert_rjs :sortable, 'sortable_item'
|
||||
assert_no_rjs :sortable, 'non-sortable-item'
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_rjs :sortable, 'non-sortable-item' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_no_rjs :sortable, 'sortable_item' }
|
||||
end
|
||||
|
||||
def test_toggle
|
||||
get :toggle
|
||||
assert_nothing_raised do
|
||||
assert_rjs :toggle, "post_1", "post_2", "post_3"
|
||||
assert_no_rjs :toggle, 'post_4'
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_rjs :toggle, 'post_4' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :toggle, "post_1", "post_2", "post_3"
|
||||
end
|
||||
end
|
||||
|
||||
def test_visual_effect
|
||||
get :visual_effect
|
||||
assert_nothing_raised do
|
||||
assert_rjs :visual_effect, :highlight, "posts", :duration => '1.0'
|
||||
assert_no_rjs :visual_effect, :highlight, "lists"
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_rjs :visual_effect, :highlight, "lists"
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :visual_effect, :highlight, "posts", :duration => '1.0'
|
||||
end
|
||||
end
|
||||
|
||||
# [] support
|
||||
|
||||
def test_page_with_one_chained_method
|
||||
get :page_with_one_chained_method
|
||||
assert_nothing_raised do
|
||||
assert_rjs :page, 'some_id', :toggle
|
||||
assert_no_rjs :page, 'some_other_id', :toggle
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_rjs :page, 'some_other_id', :toggle
|
||||
assert_no_rjs :page, 'some_id', :toggle
|
||||
end
|
||||
end
|
||||
|
||||
def test_page_with_assignment
|
||||
get :page_with_assignment
|
||||
|
||||
assert_nothing_raised do
|
||||
assert_rjs :page, 'some_id', :style, :color=, 'red'
|
||||
assert_no_rjs :page, 'some_id', :color=, 'red'
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :page, 'some_id', :style, :color=, 'red'
|
||||
assert_rjs :page, 'some_other_id', :style, :color=, 'red'
|
||||
end
|
||||
end
|
||||
end
|
||||
122
vendor/plugins/asset_packager/CHANGELOG
vendored
Normal file
122
vendor/plugins/asset_packager/CHANGELOG
vendored
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
------------------------------------------------------------------------
|
||||
r52 | sbecker | 2007-11-04 01:38:21 -0400 (Sun, 04 Nov 2007) | 3 lines
|
||||
|
||||
* Allow configuration of which environments the helpers should merge scripts with the Synthesis::AssetPackage.merge_environments variable.
|
||||
* Refactored tests so they can all run together, and not depend on what the RAILS_ENV constant is.
|
||||
* Only add file extension if it was explicitly passed in, fixes other helpers in rails.
|
||||
------------------------------------------------------------------------
|
||||
r51 | sbecker | 2007-10-26 16:24:48 -0400 (Fri, 26 Oct 2007) | 1 line
|
||||
|
||||
* Updated jsmin.rb to latest version from 2007-07-20
|
||||
------------------------------------------------------------------------
|
||||
r50 | sbecker | 2007-10-23 23:16:07 -0400 (Tue, 23 Oct 2007) | 1 line
|
||||
|
||||
Updated CHANGELOG
|
||||
|
||||
------------------------------------------------------------------------
|
||||
r49 | sbecker | 2007-10-23 23:13:27 -0400 (Tue, 23 Oct 2007) | 1 line
|
||||
|
||||
* Finally committed the subdirectory patch. (Thanks James Coglan!)
|
||||
------------------------------------------------------------------------
|
||||
r48 | sbecker | 2007-10-15 15:10:43 -0400 (Mon, 15 Oct 2007) | 1 line
|
||||
|
||||
* Speed up rake tasks and remove rails environment dependencies
|
||||
------------------------------------------------------------------------
|
||||
r43 | sbecker | 2007-07-02 15:30:29 -0400 (Mon, 02 Jul 2007) | 1 line
|
||||
|
||||
* Updated the docs regarding testing.
|
||||
------------------------------------------------------------------------
|
||||
r42 | sbecker | 2007-07-02 15:27:00 -0400 (Mon, 02 Jul 2007) | 1 line
|
||||
|
||||
* For production helper test, build packages once - on first setup.
|
||||
------------------------------------------------------------------------
|
||||
r41 | sbecker | 2007-07-02 15:14:13 -0400 (Mon, 02 Jul 2007) | 1 line
|
||||
|
||||
* Put build_all in test setup and delete_all in test teardown so all tests will pass the on first run of test suite.
|
||||
------------------------------------------------------------------------
|
||||
r40 | sbecker | 2007-07-02 14:55:28 -0400 (Mon, 02 Jul 2007) | 1 line
|
||||
|
||||
* Fix quotes, add contact info
|
||||
------------------------------------------------------------------------
|
||||
r39 | sbecker | 2007-07-02 14:53:52 -0400 (Mon, 02 Jul 2007) | 1 line
|
||||
|
||||
* Add note on how to run the tests for asset packager.
|
||||
------------------------------------------------------------------------
|
||||
r38 | sbecker | 2007-01-25 15:36:42 -0500 (Thu, 25 Jan 2007) | 1 line
|
||||
|
||||
added CHANGELOG w/ subversion log entries
|
||||
------------------------------------------------------------------------
|
||||
r37 | sbecker | 2007-01-25 15:34:39 -0500 (Thu, 25 Jan 2007) | 1 line
|
||||
|
||||
updated jsmin with new version from 2007-01-23
|
||||
------------------------------------------------------------------------
|
||||
r35 | sbecker | 2007-01-15 19:22:16 -0500 (Mon, 15 Jan 2007) | 1 line
|
||||
|
||||
require synthesis/asset_package in rake tasks, as Rails 1.2 seems to necessitate
|
||||
------------------------------------------------------------------------
|
||||
r34 | sbecker | 2007-01-05 12:22:09 -0500 (Fri, 05 Jan 2007) | 1 line
|
||||
|
||||
do a require before including in action view, because when running migrations, the plugin lib files don't automatically get required, causing the include to error out
|
||||
------------------------------------------------------------------------
|
||||
r33 | sbecker | 2006-12-23 02:03:41 -0500 (Sat, 23 Dec 2006) | 1 line
|
||||
|
||||
updating readme with various tweaks
|
||||
------------------------------------------------------------------------
|
||||
r32 | sbecker | 2006-12-23 02:03:12 -0500 (Sat, 23 Dec 2006) | 1 line
|
||||
|
||||
updating readme with various tweaks
|
||||
------------------------------------------------------------------------
|
||||
r31 | sbecker | 2006-12-23 01:52:25 -0500 (Sat, 23 Dec 2006) | 1 line
|
||||
|
||||
updated readme to show how to use different media for stylesheets
|
||||
------------------------------------------------------------------------
|
||||
r28 | sbecker | 2006-11-27 21:02:14 -0500 (Mon, 27 Nov 2006) | 1 line
|
||||
|
||||
updated compute_public_path, added check for images
|
||||
------------------------------------------------------------------------
|
||||
r27 | sbecker | 2006-11-10 18:28:29 -0500 (Fri, 10 Nov 2006) | 1 line
|
||||
|
||||
tolerate extra periods in source asset names. fixed subversion revision checking to be file specific, instead of repository specific.
|
||||
------------------------------------------------------------------------
|
||||
r26 | sbecker | 2006-06-24 17:04:27 -0400 (Sat, 24 Jun 2006) | 1 line
|
||||
|
||||
convert asset_packages_yml var to a class var
|
||||
------------------------------------------------------------------------
|
||||
r25 | sbecker | 2006-06-24 12:37:47 -0400 (Sat, 24 Jun 2006) | 1 line
|
||||
|
||||
Added ability to include assets by package name. In development, include all uncompressed asset files. In production, include the single compressed asset.
|
||||
------------------------------------------------------------------------
|
||||
r24 | sbecker | 2006-06-19 21:57:23 -0400 (Mon, 19 Jun 2006) | 1 line
|
||||
|
||||
Updates to README and about.yml
|
||||
------------------------------------------------------------------------
|
||||
r23 | sbecker | 2006-06-19 14:55:39 -0400 (Mon, 19 Jun 2006) | 2 lines
|
||||
|
||||
Modifying about.yml and README
|
||||
|
||||
------------------------------------------------------------------------
|
||||
r21 | sbecker | 2006-06-19 12:18:32 -0400 (Mon, 19 Jun 2006) | 2 lines
|
||||
|
||||
added "formerly known as MergeJS"
|
||||
|
||||
------------------------------------------------------------------------
|
||||
r20 | sbecker | 2006-06-19 12:14:46 -0400 (Mon, 19 Jun 2006) | 2 lines
|
||||
|
||||
Updating docs
|
||||
|
||||
------------------------------------------------------------------------
|
||||
r19 | sbecker | 2006-06-19 11:26:08 -0400 (Mon, 19 Jun 2006) | 2 lines
|
||||
|
||||
removing compiled test assets from subversion
|
||||
|
||||
------------------------------------------------------------------------
|
||||
r18 | sbecker | 2006-06-19 11:19:59 -0400 (Mon, 19 Jun 2006) | 2 lines
|
||||
|
||||
Initial import.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
r17 | sbecker | 2006-06-19 11:18:56 -0400 (Mon, 19 Jun 2006) | 2 lines
|
||||
|
||||
Creating directory.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
173
vendor/plugins/asset_packager/README
vendored
Normal file
173
vendor/plugins/asset_packager/README
vendored
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
= AssetPackager
|
||||
|
||||
JavaScript and CSS Asset Compression for Production Rails Apps
|
||||
|
||||
== Description
|
||||
|
||||
When it comes time to deploy your new web application, instead of
|
||||
sending down a dozen JavaScript and CSS files full of formatting
|
||||
and comments, this Rails plugin makes it simple to merge and
|
||||
compress JavaScript and CSS down into one or more files, increasing
|
||||
speed and saving bandwidth.
|
||||
|
||||
When in development, it allows you to use your original versions
|
||||
and retain formatting and comments for readability and debugging.
|
||||
|
||||
Because not all browsers will dependably cache JavaScript and CSS
|
||||
files with query string parameters, AssetPackager writes a timestamp
|
||||
or subversion revision stamp (if available) into the merged file names.
|
||||
Therefore files are correctly cached by the browser AND your users
|
||||
always get the latest version when you re-deploy.
|
||||
|
||||
This code is released under the MIT license (like Ruby). You’re free
|
||||
to rip it up, enhance it, etc. And if you make any enhancements,
|
||||
I’d like to know so I can add them back in. Thanks!
|
||||
|
||||
* Formerly known as MergeJS.
|
||||
|
||||
== Credit
|
||||
|
||||
This Rails Plugin was inspired by Cal Henderson's article
|
||||
"Serving JavaScript Fast" on Vitamin:
|
||||
http://www.thinkvitamin.com/features/webapps/serving-javascript-fast
|
||||
|
||||
It also uses the Ruby JavaScript Minifier created by
|
||||
Douglas Crockford.
|
||||
http://www.crockford.com/javascript/jsmin.html
|
||||
|
||||
== Key Features
|
||||
|
||||
* Merges and compresses JavaScript and CSS when running in production.
|
||||
* Uses uncompressed originals when running in development.
|
||||
* Handles caching correctly. (No querystring parameters - filename timestamps)
|
||||
* Versions each package individually. Updates to files in one won't re-trigger downloading the others.
|
||||
* Uses subversion revision numbers instead of timestamps if within a subversion controlled directory.
|
||||
* Guarantees new version will get downloaded the next time you deploy.
|
||||
|
||||
== Components
|
||||
|
||||
* Rake Task for merging and compressing JavaScript and CSS files.
|
||||
* Helper functions for including these JavaScript and CSS files in your views.
|
||||
* YAML configuration file for mapping JavaScript and CSS files to merged versions.
|
||||
* Rake Task for auto-generating the YAML file from your existing JavaScript files.
|
||||
|
||||
== How to Use:
|
||||
|
||||
1. Download and install the plugin:
|
||||
./script/plugin install http://sbecker.net/shared/plugins/asset_packager
|
||||
|
||||
2. Run the rake task "asset:packager:create_yml" to generate the /config/asset_packages.yml
|
||||
file the first time. You will need to reorder files under 'base' so dependencies are loaded
|
||||
in correct order. Feel free to rename or create new file packages.
|
||||
|
||||
IMPORTANT: JavaScript files can break once compressed if each statement doesn't end with a semi-colon.
|
||||
The minifier puts multiple statements on one line, so if the semi-colon is missing, the statement may no
|
||||
longer makes sense and cause a syntax error.
|
||||
|
||||
Example from a fresh rails app after running the rake task. (Stylesheets is blank because a
|
||||
default rails app has no stylesheets yet.):
|
||||
|
||||
---
|
||||
javascripts:
|
||||
- base:
|
||||
- prototype
|
||||
- effects
|
||||
- dragdrop
|
||||
- controls
|
||||
- application
|
||||
stylesheets:
|
||||
- base: []
|
||||
|
||||
Example with multiple merged files:
|
||||
|
||||
---
|
||||
javascripts:
|
||||
- base:
|
||||
- prototype
|
||||
- effects
|
||||
- controls
|
||||
- dragdrop
|
||||
- application
|
||||
- secondary:
|
||||
- foo
|
||||
- bar
|
||||
stylesheets:
|
||||
- base:
|
||||
- screen
|
||||
- header
|
||||
- secondary:
|
||||
- foo
|
||||
- bar
|
||||
|
||||
3. Run the rake task "asset:packager:build_all" to generate the compressed, merged versions
|
||||
for each package. Whenever you rearrange the yaml file, you'll need to run this task again.
|
||||
Merging and compressing is expensive, so this is something we want to do once, not every time
|
||||
your app starts. Thats why its a rake task.
|
||||
|
||||
4. Use the helper functions whenever including these files in your application. See below for examples.
|
||||
|
||||
5. Potential warning: css compressor function currently removes CSS comments. This might blow
|
||||
away some CSS hackery. To disable comment removal, comment out /lib/synthesis/asset_package.rb line 176.
|
||||
|
||||
== JavaScript Examples
|
||||
|
||||
Example call:
|
||||
<%= javascript_include_merged 'prototype', 'effects', 'controls', 'dragdrop', 'application', 'foo', 'bar' %>
|
||||
|
||||
In development, this generates:
|
||||
<script type="text/javascript" src="/javascripts/prototype.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/effects.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/controls.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/dragdrop.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/application.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/foo.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/bar.js"></script>
|
||||
|
||||
In production, this generates:
|
||||
<script type="text/javascript" src="/javascripts/base_1150571523.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/secondary_1150729166.js"></script>
|
||||
|
||||
Now supports symbols and :defaults as well:
|
||||
<%= javascript_include_merged :defaults %>
|
||||
<%= javascript_include_merged :foo, :bar %>
|
||||
|
||||
== Stylesheet Examples
|
||||
|
||||
Example call:
|
||||
<%= stylesheet_link_merged 'screen', 'header' %>
|
||||
|
||||
In development, this generates:
|
||||
<link href="/stylesheets/screen.css" media="screen" rel="Stylesheet" type="text/css" />
|
||||
<link href="/stylesheets/header.css" media="screen" rel="Stylesheet" type="text/css" />
|
||||
|
||||
In production this generates:
|
||||
<link href="/stylesheets/base_1150729166.css" media="screen" rel="Stylesheet" type="text/css" />
|
||||
|
||||
== Different CSS Media
|
||||
|
||||
All options for stylesheet_link_tag still work, so if you want to specify a different media type:
|
||||
<%= stylesheet_link_merged :secondary, 'media' => 'print' %>
|
||||
|
||||
== Running the tests
|
||||
|
||||
So you want to run the tests eh? Ok, then listen:
|
||||
|
||||
This plugin has a full suite of tests. But since they
|
||||
depend on rails, it has to be run in the context of a
|
||||
rails app, in the vendor/plugins directory. Observe:
|
||||
|
||||
> rails newtestapp
|
||||
> cd newtestapp
|
||||
> ./script/plugin install http://sbecker.net/shared/plugins/asset_packager
|
||||
> cd vendor/plugins/asset_packager/
|
||||
> rake # all tests pass
|
||||
|
||||
== License
|
||||
Copyright (c) 2006 Scott Becker - http://synthesis.sbecker.net
|
||||
Contact Email: becker.scott@gmail.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
22
vendor/plugins/asset_packager/Rakefile
vendored
Normal file
22
vendor/plugins/asset_packager/Rakefile
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
|
||||
desc 'Default: run unit tests.'
|
||||
task :default => :test
|
||||
|
||||
desc 'Test the asset_packager plugin.'
|
||||
Rake::TestTask.new(:test) do |t|
|
||||
t.libs << 'lib'
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
desc 'Generate documentation for the asset_packager plugin.'
|
||||
Rake::RDocTask.new(:rdoc) do |rdoc|
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
rdoc.title = 'AssetPackager'
|
||||
rdoc.options << '--line-numbers' << '--inline-source'
|
||||
rdoc.rdoc_files.include('README')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
end
|
||||
8
vendor/plugins/asset_packager/about.yml
vendored
Normal file
8
vendor/plugins/asset_packager/about.yml
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
author: Scott Becker
|
||||
name: AssetPackager
|
||||
summary: JavaScript and CSS Asset Compression for Production Rails Apps
|
||||
homepage: http://synthesis.sbecker.net/pages/asset_packager
|
||||
plugin: http://sbecker.net/shared/plugins/asset_packager
|
||||
license: MIT
|
||||
version: 0.2
|
||||
rails_version: 1.1.2+
|
||||
2
vendor/plugins/asset_packager/init.rb
vendored
Normal file
2
vendor/plugins/asset_packager/init.rb
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
require 'synthesis/asset_package_helper'
|
||||
ActionView::Base.send :include, Synthesis::AssetPackageHelper
|
||||
1
vendor/plugins/asset_packager/install.rb
vendored
Normal file
1
vendor/plugins/asset_packager/install.rb
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
# Install hook code here
|
||||
205
vendor/plugins/asset_packager/lib/jsmin.rb
vendored
Normal file
205
vendor/plugins/asset_packager/lib/jsmin.rb
vendored
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
#!/usr/bin/ruby
|
||||
# jsmin.rb 2007-07-20
|
||||
# Author: Uladzislau Latynski
|
||||
# This work is a translation from C to Ruby of jsmin.c published by
|
||||
# Douglas Crockford. Permission is hereby granted to use the Ruby
|
||||
# version under the same conditions as the jsmin.c on which it is
|
||||
# based.
|
||||
#
|
||||
# /* jsmin.c
|
||||
# 2003-04-21
|
||||
#
|
||||
# Copyright (c) 2002 Douglas Crockford (www.crockford.com)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
# this software and associated documentation files (the "Software"), to deal in
|
||||
# the Software without restriction, including without limitation the rights to
|
||||
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
# of the Software, and to permit persons to whom the Software is furnished to do
|
||||
# so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# The Software shall be used for Good, not Evil.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
EOF = -1
|
||||
$theA = ""
|
||||
$theB = ""
|
||||
|
||||
# isAlphanum -- return true if the character is a letter, digit, underscore,
|
||||
# dollar sign, or non-ASCII character
|
||||
def isAlphanum(c)
|
||||
return false if !c || c == EOF
|
||||
return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
|
||||
(c >= 'A' && c <= 'Z') || c == '_' || c == '$' ||
|
||||
c == '\\' || c[0] > 126)
|
||||
end
|
||||
|
||||
# get -- return the next character from stdin. Watch out for lookahead. If
|
||||
# the character is a control character, translate it to a space or linefeed.
|
||||
def get()
|
||||
c = $stdin.getc
|
||||
return EOF if(!c)
|
||||
c = c.chr
|
||||
return c if (c >= " " || c == "\n" || c.unpack("c") == EOF)
|
||||
return "\n" if (c == "\r")
|
||||
return " "
|
||||
end
|
||||
|
||||
# Get the next character without getting it.
|
||||
def peek()
|
||||
lookaheadChar = $stdin.getc
|
||||
$stdin.ungetc(lookaheadChar)
|
||||
return lookaheadChar.chr
|
||||
end
|
||||
|
||||
# mynext -- get the next character, excluding comments.
|
||||
# peek() is used to see if a '/' is followed by a '/' or '*'.
|
||||
def mynext()
|
||||
c = get
|
||||
if (c == "/")
|
||||
if(peek == "/")
|
||||
while(true)
|
||||
c = get
|
||||
if (c <= "\n")
|
||||
return c
|
||||
end
|
||||
end
|
||||
end
|
||||
if(peek == "*")
|
||||
get
|
||||
while(true)
|
||||
case get
|
||||
when "*"
|
||||
if (peek == "/")
|
||||
get
|
||||
return " "
|
||||
end
|
||||
when EOF
|
||||
raise "Unterminated comment"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return c
|
||||
end
|
||||
|
||||
|
||||
# action -- do something! What you do is determined by the argument: 1
|
||||
# Output A. Copy B to A. Get the next B. 2 Copy B to A. Get the next B.
|
||||
# (Delete A). 3 Get the next B. (Delete B). action treats a string as a
|
||||
# single character. Wow! action recognizes a regular expression if it is
|
||||
# preceded by ( or , or =.
|
||||
def action(a)
|
||||
if(a==1)
|
||||
$stdout.write $theA
|
||||
end
|
||||
if(a==1 || a==2)
|
||||
$theA = $theB
|
||||
if ($theA == "\'" || $theA == "\"")
|
||||
while (true)
|
||||
$stdout.write $theA
|
||||
$theA = get
|
||||
break if ($theA == $theB)
|
||||
raise "Unterminated string literal" if ($theA <= "\n")
|
||||
if ($theA == "\\")
|
||||
$stdout.write $theA
|
||||
$theA = get
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if(a==1 || a==2 || a==3)
|
||||
$theB = mynext
|
||||
if ($theB == "/" && ($theA == "(" || $theA == "," || $theA == "=" ||
|
||||
$theA == ":" || $theA == "[" || $theA == "!" ||
|
||||
$theA == "&" || $theA == "|" || $theA == "?" ||
|
||||
$theA == "{" || $theA == "}" || $theA == ";" ||
|
||||
$theA == "\n"))
|
||||
$stdout.write $theA
|
||||
$stdout.write $theB
|
||||
while (true)
|
||||
$theA = get
|
||||
if ($theA == "/")
|
||||
break
|
||||
elsif ($theA == "\\")
|
||||
$stdout.write $theA
|
||||
$theA = get
|
||||
elsif ($theA <= "\n")
|
||||
raise "Unterminated RegExp Literal"
|
||||
end
|
||||
$stdout.write $theA
|
||||
end
|
||||
$theB = mynext
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# jsmin -- Copy the input to the output, deleting the characters which are
|
||||
# insignificant to JavaScript. Comments will be removed. Tabs will be
|
||||
# replaced with spaces. Carriage returns will be replaced with linefeeds.
|
||||
# Most spaces and linefeeds will be removed.
|
||||
def jsmin
|
||||
$theA = "\n"
|
||||
action(3)
|
||||
while ($theA != EOF)
|
||||
case $theA
|
||||
when " "
|
||||
if (isAlphanum($theB))
|
||||
action(1)
|
||||
else
|
||||
action(2)
|
||||
end
|
||||
when "\n"
|
||||
case ($theB)
|
||||
when "{","[","(","+","-"
|
||||
action(1)
|
||||
when " "
|
||||
action(3)
|
||||
else
|
||||
if (isAlphanum($theB))
|
||||
action(1)
|
||||
else
|
||||
action(2)
|
||||
end
|
||||
end
|
||||
else
|
||||
case ($theB)
|
||||
when " "
|
||||
if (isAlphanum($theA))
|
||||
action(1)
|
||||
else
|
||||
action(3)
|
||||
end
|
||||
when "\n"
|
||||
case ($theA)
|
||||
when "}","]",")","+","-","\"","\\", "'", '"'
|
||||
action(1)
|
||||
else
|
||||
if (isAlphanum($theA))
|
||||
action(1)
|
||||
else
|
||||
action(3)
|
||||
end
|
||||
end
|
||||
else
|
||||
action(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ARGV.each do |anArg|
|
||||
$stdout.write "// #{anArg}\n"
|
||||
end
|
||||
|
||||
jsmin
|
||||
233
vendor/plugins/asset_packager/lib/synthesis/asset_package.rb
vendored
Normal file
233
vendor/plugins/asset_packager/lib/synthesis/asset_package.rb
vendored
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
require 'yaml'
|
||||
|
||||
module Synthesis
|
||||
class AssetPackage
|
||||
|
||||
# class variables
|
||||
@@asset_packages_yml = $asset_packages_yml ||
|
||||
(File.exists?("#{RAILS_ROOT}/config/asset_packages.yml") ? YAML.load_file("#{RAILS_ROOT}/config/asset_packages.yml") : nil)
|
||||
|
||||
# singleton methods
|
||||
class << self
|
||||
|
||||
def merge_environments=(environments)
|
||||
@@merge_environments = environments
|
||||
end
|
||||
|
||||
def merge_environments
|
||||
@@merge_environments ||= ["production"]
|
||||
end
|
||||
|
||||
def parse_path(path)
|
||||
/^(?:(.*)\/)?([^\/]+)$/.match(path).to_a
|
||||
end
|
||||
|
||||
def find_by_type(asset_type)
|
||||
@@asset_packages_yml[asset_type].map { |p| self.new(asset_type, p) }
|
||||
end
|
||||
|
||||
def find_by_target(asset_type, target)
|
||||
package_hash = @@asset_packages_yml[asset_type].find {|p| p.keys.first == target }
|
||||
package_hash ? self.new(asset_type, package_hash) : nil
|
||||
end
|
||||
|
||||
def find_by_source(asset_type, source)
|
||||
path_parts = parse_path(source)
|
||||
package_hash = @@asset_packages_yml[asset_type].find do |p|
|
||||
key = p.keys.first
|
||||
p[key].include?(path_parts[2]) && (parse_path(key)[1] == path_parts[1])
|
||||
end
|
||||
package_hash ? self.new(asset_type, package_hash) : nil
|
||||
end
|
||||
|
||||
def targets_from_sources(asset_type, sources)
|
||||
package_names = Array.new
|
||||
sources.each do |source|
|
||||
package = find_by_target(asset_type, source) || find_by_source(asset_type, source)
|
||||
package_names << (package ? package.current_file : source)
|
||||
end
|
||||
package_names.uniq
|
||||
end
|
||||
|
||||
def sources_from_targets(asset_type, targets)
|
||||
source_names = Array.new
|
||||
targets.each do |target|
|
||||
package = find_by_target(asset_type, target)
|
||||
source_names += (package ? package.sources.collect do |src|
|
||||
package.target_dir.gsub(/^(.+)$/, '\1/') + src
|
||||
end : target.to_a)
|
||||
end
|
||||
source_names.uniq
|
||||
end
|
||||
|
||||
def build_all
|
||||
@@asset_packages_yml.keys.each do |asset_type|
|
||||
@@asset_packages_yml[asset_type].each { |p| self.new(asset_type, p).build }
|
||||
end
|
||||
end
|
||||
|
||||
def delete_all
|
||||
@@asset_packages_yml.keys.each do |asset_type|
|
||||
@@asset_packages_yml[asset_type].each { |p| self.new(asset_type, p).delete_all_builds }
|
||||
end
|
||||
end
|
||||
|
||||
def create_yml
|
||||
unless File.exists?("#{RAILS_ROOT}/config/asset_packages.yml")
|
||||
asset_yml = Hash.new
|
||||
|
||||
asset_yml['javascripts'] = [{"base" => build_file_list("#{RAILS_ROOT}/public/javascripts", "js")}]
|
||||
asset_yml['stylesheets'] = [{"base" => build_file_list("#{RAILS_ROOT}/public/stylesheets", "css")}]
|
||||
|
||||
File.open("#{RAILS_ROOT}/config/asset_packages.yml", "w") do |out|
|
||||
YAML.dump(asset_yml, out)
|
||||
end
|
||||
|
||||
log "config/asset_packages.yml example file created!"
|
||||
log "Please reorder files under 'base' so dependencies are loaded in correct order."
|
||||
else
|
||||
log "config/asset_packages.yml already exists. Aborting task..."
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# instance methods
|
||||
attr_accessor :asset_type, :target, :target_dir, :sources
|
||||
|
||||
def initialize(asset_type, package_hash)
|
||||
target_parts = self.class.parse_path(package_hash.keys.first)
|
||||
@target_dir = target_parts[1].to_s
|
||||
@target = target_parts[2].to_s
|
||||
@sources = package_hash[package_hash.keys.first]
|
||||
@asset_type = asset_type
|
||||
@asset_path = ($asset_base_path ? "#{$asset_base_path}/" : "#{RAILS_ROOT}/public/") +
|
||||
"#{@asset_type}#{@target_dir.gsub(/^(.+)$/, '/\1')}"
|
||||
@extension = get_extension
|
||||
@match_regex = Regexp.new("\\A#{@target}_\\d+.#{@extension}\\z")
|
||||
end
|
||||
|
||||
def current_file
|
||||
@target_dir.gsub(/^(.+)$/, '\1/') +
|
||||
Dir.new(@asset_path).entries.delete_if { |x| ! (x =~ @match_regex) }.sort.reverse[0].chomp(".#{@extension}")
|
||||
end
|
||||
|
||||
def build
|
||||
delete_old_builds
|
||||
create_new_build
|
||||
end
|
||||
|
||||
def delete_old_builds
|
||||
Dir.new(@asset_path).entries.delete_if { |x| ! (x =~ @match_regex) }.each do |x|
|
||||
File.delete("#{@asset_path}/#{x}") unless x.index(revision.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
def delete_all_builds
|
||||
Dir.new(@asset_path).entries.delete_if { |x| ! (x =~ @match_regex) }.each do |x|
|
||||
File.delete("#{@asset_path}/#{x}")
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def revision
|
||||
unless @revision
|
||||
revisions = [1]
|
||||
@sources.each do |source|
|
||||
revisions << get_file_revision("#{@asset_path}/#{source}.#{@extension}")
|
||||
end
|
||||
@revision = revisions.max
|
||||
end
|
||||
@revision
|
||||
end
|
||||
|
||||
def get_file_revision(path)
|
||||
if File.exists?(path)
|
||||
begin
|
||||
`svn info #{path}`[/Last Changed Rev: (.*?)\n/][/(\d+)/].to_i
|
||||
rescue # use filename timestamp if not in subversion
|
||||
File.mtime(path).to_i
|
||||
end
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def create_new_build
|
||||
if File.exists?("#{@asset_path}/#{@target}_#{revision}.#{@extension}")
|
||||
log "Latest version already exists: #{@asset_path}/#{@target}_#{revision}.#{@extension}"
|
||||
else
|
||||
File.open("#{@asset_path}/#{@target}_#{revision}.#{@extension}", "w") {|f| f.write(compressed_file) }
|
||||
log "Created #{@asset_path}/#{@target}_#{revision}.#{@extension}"
|
||||
end
|
||||
end
|
||||
|
||||
def merged_file
|
||||
merged_file = ""
|
||||
@sources.each {|s|
|
||||
File.open("#{@asset_path}/#{s}.#{@extension}", "r") { |f|
|
||||
merged_file += f.read + "\n"
|
||||
}
|
||||
}
|
||||
merged_file
|
||||
end
|
||||
|
||||
def compressed_file
|
||||
case @asset_type
|
||||
when "javascripts" then compress_js(merged_file)
|
||||
when "stylesheets" then compress_css(merged_file)
|
||||
end
|
||||
end
|
||||
|
||||
def compress_js(source)
|
||||
jsmin_path = "#{RAILS_ROOT}/vendor/plugins/asset_packager/lib"
|
||||
tmp_path = "#{RAILS_ROOT}/tmp/#{@target}_#{revision}"
|
||||
|
||||
# write out to a temp file
|
||||
File.open("#{tmp_path}_uncompressed.js", "w") {|f| f.write(source) }
|
||||
|
||||
# compress file with JSMin library
|
||||
`ruby #{jsmin_path}/jsmin.rb <#{tmp_path}_uncompressed.js >#{tmp_path}_compressed.js \n`
|
||||
|
||||
# read it back in and trim it
|
||||
result = ""
|
||||
File.open("#{tmp_path}_compressed.js", "r") { |f| result += f.read.strip }
|
||||
|
||||
# delete temp files if they exist
|
||||
File.delete("#{tmp_path}_uncompressed.js") if File.exists?("#{tmp_path}_uncompressed.js")
|
||||
File.delete("#{tmp_path}_compressed.js") if File.exists?("#{tmp_path}_compressed.js")
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def compress_css(source)
|
||||
source.gsub!(/\s+/, " ") # collapse space
|
||||
source.gsub!(/\/\*(.*?)\*\/ /, "") # remove comments - caution, might want to remove this if using css hacks
|
||||
source.gsub!(/\} /, "}\n") # add line breaks
|
||||
source.gsub!(/\n$/, "") # remove last break
|
||||
source.gsub!(/ \{ /, " {") # trim inside brackets
|
||||
source.gsub!(/; \}/, "}") # trim inside brackets
|
||||
source
|
||||
end
|
||||
|
||||
def get_extension
|
||||
case @asset_type
|
||||
when "javascripts" then "js"
|
||||
when "stylesheets" then "css"
|
||||
end
|
||||
end
|
||||
|
||||
def log(message)
|
||||
puts message
|
||||
end
|
||||
|
||||
def self.build_file_list(path, extension)
|
||||
re = Regexp.new(".#{extension}\\z")
|
||||
file_list = Dir.new(path).entries.delete_if { |x| ! (x =~ re) }.map {|x| x.chomp(".#{extension}")}
|
||||
# reverse javascript entries so prototype comes first on a base rails app
|
||||
file_list.reverse! if extension == "js"
|
||||
file_list
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
67
vendor/plugins/asset_packager/lib/synthesis/asset_package_helper.rb
vendored
Normal file
67
vendor/plugins/asset_packager/lib/synthesis/asset_package_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
module Synthesis
|
||||
module AssetPackageHelper
|
||||
|
||||
def should_merge?
|
||||
AssetPackage.merge_environments.include?(RAILS_ENV)
|
||||
end
|
||||
|
||||
def javascript_include_merged(*sources)
|
||||
options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }
|
||||
|
||||
if sources.include?(:defaults)
|
||||
sources = sources[0..(sources.index(:defaults))] +
|
||||
['prototype', 'effects', 'dragdrop', 'controls'] +
|
||||
(File.exists?("#{RAILS_ROOT}/public/javascripts/application.js") ? ['application'] : []) +
|
||||
sources[(sources.index(:defaults) + 1)..sources.length]
|
||||
sources.delete(:defaults)
|
||||
end
|
||||
|
||||
sources.collect!{|s| s.to_s}
|
||||
sources = (should_merge? ?
|
||||
AssetPackage.targets_from_sources("javascripts", sources) :
|
||||
AssetPackage.sources_from_targets("javascripts", sources))
|
||||
|
||||
sources.collect {|source| javascript_include_tag(source, options) }.join("\n")
|
||||
end
|
||||
|
||||
def stylesheet_link_merged(*sources)
|
||||
options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }
|
||||
|
||||
sources.collect!{|s| s.to_s}
|
||||
sources = (should_merge? ?
|
||||
AssetPackage.targets_from_sources("stylesheets", sources) :
|
||||
AssetPackage.sources_from_targets("stylesheets", sources))
|
||||
|
||||
sources.collect { |source|
|
||||
source = stylesheet_path(source)
|
||||
tag("link", { "rel" => "Stylesheet", "type" => "text/css", "media" => "screen", "href" => source }.merge(options))
|
||||
}.join("\n")
|
||||
end
|
||||
|
||||
private
|
||||
# rewrite compute_public_path to allow us to not include the query string timestamp
|
||||
# used by ActionView::Helpers::AssetTagHelper
|
||||
def compute_public_path(source, dir, ext=nil, add_asset_id=true)
|
||||
source = source.dup
|
||||
source << ".#{ext}" if File.extname(source).blank? && ext
|
||||
unless source =~ %r{^[-a-z]+://}
|
||||
source = "/#{dir}/#{source}" unless source[0] == ?/
|
||||
asset_id = rails_asset_id(source)
|
||||
source << '?' + asset_id if defined?(RAILS_ROOT) and add_asset_id and not asset_id.blank?
|
||||
source = "#{ActionController::Base.asset_host}#{@controller.request.relative_url_root}#{source}"
|
||||
end
|
||||
source
|
||||
end
|
||||
|
||||
# rewrite javascript path function to not include query string timestamp
|
||||
def javascript_path(source)
|
||||
compute_public_path(source, 'javascripts', 'js', false)
|
||||
end
|
||||
|
||||
# rewrite stylesheet path function to not include query string timestamp
|
||||
def stylesheet_path(source)
|
||||
compute_public_path(source, 'stylesheets', 'css', false)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
22
vendor/plugins/asset_packager/tasks/asset_packager_tasks.rake
vendored
Normal file
22
vendor/plugins/asset_packager/tasks/asset_packager_tasks.rake
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
require File.dirname(__FILE__) + '/../lib/synthesis/asset_package'
|
||||
|
||||
namespace :asset do
|
||||
namespace :packager do
|
||||
|
||||
desc "Merge and compress assets"
|
||||
task :build_all do
|
||||
Synthesis::AssetPackage.build_all
|
||||
end
|
||||
|
||||
desc "Delete all asset builds"
|
||||
task :delete_all do
|
||||
Synthesis::AssetPackage.delete_all
|
||||
end
|
||||
|
||||
desc "Generate asset_packages.yml from existing assets"
|
||||
task :create_yml do
|
||||
Synthesis::AssetPackage.create_yml
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
107
vendor/plugins/asset_packager/test/asset_package_helper_development_test.rb
vendored
Normal file
107
vendor/plugins/asset_packager/test/asset_package_helper_development_test.rb
vendored
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
||||
|
||||
ENV['RAILS_ENV'] = "development"
|
||||
require File.dirname(__FILE__) + '/../../../../config/environment'
|
||||
require 'test/unit'
|
||||
require 'rubygems'
|
||||
require 'mocha'
|
||||
|
||||
require 'action_controller/test_process'
|
||||
|
||||
ActionController::Base.logger = nil
|
||||
ActionController::Base.ignore_missing_templates = false
|
||||
ActionController::Routing::Routes.reload rescue nil
|
||||
|
||||
$asset_packages_yml = YAML.load_file("#{RAILS_ROOT}/vendor/plugins/asset_packager/test/asset_packages.yml")
|
||||
$asset_base_path = "#{RAILS_ROOT}/vendor/plugins/asset_packager/test/assets"
|
||||
|
||||
class AssetPackageHelperProductionTest < Test::Unit::TestCase
|
||||
include ActionView::Helpers::TagHelper
|
||||
include ActionView::Helpers::AssetTagHelper
|
||||
include Synthesis::AssetPackageHelper
|
||||
|
||||
def setup
|
||||
Synthesis::AssetPackage.any_instance.stubs(:log)
|
||||
|
||||
@controller = Class.new do
|
||||
attr_reader :request
|
||||
def initialize
|
||||
@request = Class.new do
|
||||
def relative_url_root
|
||||
""
|
||||
end
|
||||
end.new
|
||||
end
|
||||
|
||||
end.new
|
||||
end
|
||||
|
||||
def build_js_expected_string(*sources)
|
||||
sources.map {|s| %(<script src="/javascripts/#{s}.js" type="text/javascript"></script>) }.join("\n")
|
||||
end
|
||||
|
||||
def build_css_expected_string(*sources)
|
||||
sources.map {|s| %(<link href="/stylesheets/#{s}.css" rel="Stylesheet" type="text/css" media="screen" />) }.join("\n")
|
||||
end
|
||||
|
||||
def test_js_basic
|
||||
assert_dom_equal build_js_expected_string("prototype"),
|
||||
javascript_include_merged("prototype")
|
||||
end
|
||||
|
||||
def test_js_multiple_packages
|
||||
assert_dom_equal build_js_expected_string("prototype", "foo"),
|
||||
javascript_include_merged("prototype", "foo")
|
||||
end
|
||||
|
||||
def test_js_unpackaged_file
|
||||
assert_dom_equal build_js_expected_string("prototype", "foo", "not_part_of_a_package"),
|
||||
javascript_include_merged("prototype", "foo", "not_part_of_a_package")
|
||||
end
|
||||
|
||||
def test_js_multiple_from_same_package
|
||||
assert_dom_equal build_js_expected_string("prototype", "effects", "controls", "not_part_of_a_package", "foo"),
|
||||
javascript_include_merged("prototype", "effects", "controls", "not_part_of_a_package", "foo")
|
||||
end
|
||||
|
||||
def test_js_by_package_name
|
||||
assert_dom_equal build_js_expected_string("prototype", "effects", "controls", "dragdrop"),
|
||||
javascript_include_merged(:base)
|
||||
end
|
||||
|
||||
def test_js_multiple_package_names
|
||||
assert_dom_equal build_js_expected_string("prototype", "effects", "controls", "dragdrop", "foo", "bar", "application"),
|
||||
javascript_include_merged(:base, :secondary)
|
||||
end
|
||||
|
||||
def test_css_basic
|
||||
assert_dom_equal build_css_expected_string("screen"),
|
||||
stylesheet_link_merged("screen")
|
||||
end
|
||||
|
||||
def test_css_multiple_packages
|
||||
assert_dom_equal build_css_expected_string("screen", "foo", "subdir/bar"),
|
||||
stylesheet_link_merged("screen", "foo", "subdir/bar")
|
||||
end
|
||||
|
||||
def test_css_unpackaged_file
|
||||
assert_dom_equal build_css_expected_string("screen", "foo", "not_part_of_a_package", "subdir/bar"),
|
||||
stylesheet_link_merged("screen", "foo", "not_part_of_a_package", "subdir/bar")
|
||||
end
|
||||
|
||||
def test_css_multiple_from_same_package
|
||||
assert_dom_equal build_css_expected_string("screen", "header", "not_part_of_a_package", "foo", "bar", "subdir/foo", "subdir/bar"),
|
||||
stylesheet_link_merged("screen", "header", "not_part_of_a_package", "foo", "bar", "subdir/foo", "subdir/bar")
|
||||
end
|
||||
|
||||
def test_css_by_package_name
|
||||
assert_dom_equal build_css_expected_string("screen", "header"),
|
||||
stylesheet_link_merged(:base)
|
||||
end
|
||||
|
||||
def test_css_multiple_package_names
|
||||
assert_dom_equal build_css_expected_string("screen", "header", "foo", "bar", "subdir/foo", "subdir/bar"),
|
||||
stylesheet_link_merged(:base, :secondary, "subdir/styles")
|
||||
end
|
||||
|
||||
end
|
||||
153
vendor/plugins/asset_packager/test/asset_package_helper_production_test.rb
vendored
Normal file
153
vendor/plugins/asset_packager/test/asset_package_helper_production_test.rb
vendored
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
||||
|
||||
require File.dirname(__FILE__) + '/../../../../config/environment'
|
||||
require 'test/unit'
|
||||
require 'rubygems'
|
||||
require 'mocha'
|
||||
|
||||
require 'action_controller/test_process'
|
||||
|
||||
ActionController::Base.logger = nil
|
||||
ActionController::Base.ignore_missing_templates = false
|
||||
ActionController::Routing::Routes.reload rescue nil
|
||||
|
||||
$asset_packages_yml = YAML.load_file("#{RAILS_ROOT}/vendor/plugins/asset_packager/test/asset_packages.yml")
|
||||
$asset_base_path = "#{RAILS_ROOT}/vendor/plugins/asset_packager/test/assets"
|
||||
|
||||
class AssetPackageHelperProductionTest < Test::Unit::TestCase
|
||||
include ActionView::Helpers::TagHelper
|
||||
include ActionView::Helpers::AssetTagHelper
|
||||
include Synthesis::AssetPackageHelper
|
||||
|
||||
cattr_accessor :packages_built
|
||||
|
||||
def setup
|
||||
Synthesis::AssetPackage.any_instance.stubs(:log)
|
||||
self.stubs(:should_merge?).returns(true)
|
||||
|
||||
@controller = Class.new do
|
||||
|
||||
attr_reader :request
|
||||
def initialize
|
||||
@request = Class.new do
|
||||
def relative_url_root
|
||||
""
|
||||
end
|
||||
end.new
|
||||
end
|
||||
|
||||
end.new
|
||||
|
||||
build_packages_once
|
||||
end
|
||||
|
||||
def build_packages_once
|
||||
unless @@packages_built
|
||||
Synthesis::AssetPackage.build_all
|
||||
@@packages_built = true
|
||||
end
|
||||
end
|
||||
|
||||
def build_js_expected_string(*sources)
|
||||
sources.map {|s| %(<script src="/javascripts/#{s}.js" type="text/javascript"></script>) }.join("\n")
|
||||
end
|
||||
|
||||
def build_css_expected_string(*sources)
|
||||
sources.map {|s| %(<link href="/stylesheets/#{s}.css" rel="Stylesheet" type="text/css" media="screen" />) }.join("\n")
|
||||
end
|
||||
|
||||
def test_js_basic
|
||||
current_file = Synthesis::AssetPackage.find_by_source("javascripts", "prototype").current_file
|
||||
assert_dom_equal build_js_expected_string(current_file),
|
||||
javascript_include_merged("prototype")
|
||||
end
|
||||
|
||||
def test_js_multiple_packages
|
||||
current_file1 = Synthesis::AssetPackage.find_by_source("javascripts", "prototype").current_file
|
||||
current_file2 = Synthesis::AssetPackage.find_by_source("javascripts", "foo").current_file
|
||||
|
||||
assert_dom_equal build_js_expected_string(current_file1, current_file2),
|
||||
javascript_include_merged("prototype", "foo")
|
||||
end
|
||||
|
||||
def test_js_unpackaged_file
|
||||
current_file1 = Synthesis::AssetPackage.find_by_source("javascripts", "prototype").current_file
|
||||
current_file2 = Synthesis::AssetPackage.find_by_source("javascripts", "foo").current_file
|
||||
|
||||
assert_dom_equal build_js_expected_string(current_file1, current_file2, "not_part_of_a_package"),
|
||||
javascript_include_merged("prototype", "foo", "not_part_of_a_package")
|
||||
end
|
||||
|
||||
def test_js_multiple_from_same_package
|
||||
current_file1 = Synthesis::AssetPackage.find_by_source("javascripts", "prototype").current_file
|
||||
current_file2 = Synthesis::AssetPackage.find_by_source("javascripts", "foo").current_file
|
||||
|
||||
assert_dom_equal build_js_expected_string(current_file1, "not_part_of_a_package", current_file2),
|
||||
javascript_include_merged("prototype", "effects", "controls", "not_part_of_a_package", "foo")
|
||||
end
|
||||
|
||||
def test_js_by_package_name
|
||||
package_name = Synthesis::AssetPackage.find_by_target("javascripts", "base").current_file
|
||||
assert_dom_equal build_js_expected_string(package_name),
|
||||
javascript_include_merged(:base)
|
||||
end
|
||||
|
||||
def test_js_multiple_package_names
|
||||
package_name1 = Synthesis::AssetPackage.find_by_target("javascripts", "base").current_file
|
||||
package_name2 = Synthesis::AssetPackage.find_by_target("javascripts", "secondary").current_file
|
||||
assert_dom_equal build_js_expected_string(package_name1, package_name2),
|
||||
javascript_include_merged(:base, :secondary)
|
||||
end
|
||||
|
||||
def test_css_basic
|
||||
current_file = Synthesis::AssetPackage.find_by_source("stylesheets", "screen").current_file
|
||||
assert_dom_equal build_css_expected_string(current_file),
|
||||
stylesheet_link_merged("screen")
|
||||
end
|
||||
|
||||
def test_css_multiple_packages
|
||||
current_file1 = Synthesis::AssetPackage.find_by_source("stylesheets", "screen").current_file
|
||||
current_file2 = Synthesis::AssetPackage.find_by_source("stylesheets", "foo").current_file
|
||||
current_file3 = Synthesis::AssetPackage.find_by_source("stylesheets", "subdir/bar").current_file
|
||||
|
||||
assert_dom_equal build_css_expected_string(current_file1, current_file2, current_file3),
|
||||
stylesheet_link_merged("screen", "foo", "subdir/bar")
|
||||
end
|
||||
|
||||
def test_css_unpackaged_file
|
||||
current_file1 = Synthesis::AssetPackage.find_by_source("stylesheets", "screen").current_file
|
||||
current_file2 = Synthesis::AssetPackage.find_by_source("stylesheets", "foo").current_file
|
||||
|
||||
assert_dom_equal build_css_expected_string(current_file1, current_file2, "not_part_of_a_package"),
|
||||
stylesheet_link_merged("screen", "foo", "not_part_of_a_package")
|
||||
end
|
||||
|
||||
def test_css_multiple_from_same_package
|
||||
current_file1 = Synthesis::AssetPackage.find_by_source("stylesheets", "screen").current_file
|
||||
current_file2 = Synthesis::AssetPackage.find_by_source("stylesheets", "foo").current_file
|
||||
current_file3 = Synthesis::AssetPackage.find_by_source("stylesheets", "subdir/bar").current_file
|
||||
|
||||
assert_dom_equal build_css_expected_string(current_file1, "not_part_of_a_package", current_file2, current_file3),
|
||||
stylesheet_link_merged("screen", "header", "not_part_of_a_package", "foo", "bar", "subdir/foo", "subdir/bar")
|
||||
end
|
||||
|
||||
def test_css_by_package_name
|
||||
package_name = Synthesis::AssetPackage.find_by_target("stylesheets", "base").current_file
|
||||
assert_dom_equal build_css_expected_string(package_name),
|
||||
stylesheet_link_merged(:base)
|
||||
end
|
||||
|
||||
def test_css_multiple_package_names
|
||||
package_name1 = Synthesis::AssetPackage.find_by_target("stylesheets", "base").current_file
|
||||
package_name2 = Synthesis::AssetPackage.find_by_target("stylesheets", "secondary").current_file
|
||||
package_name3 = Synthesis::AssetPackage.find_by_target("stylesheets", "subdir/styles").current_file
|
||||
assert_dom_equal build_css_expected_string(package_name1, package_name2, package_name3),
|
||||
stylesheet_link_merged(:base, :secondary, "subdir/styles")
|
||||
end
|
||||
|
||||
def test_image_tag
|
||||
timestamp = rails_asset_id("images/rails.png")
|
||||
assert_dom_equal %(<img alt="Rails" src="/images/rails.png?#{timestamp}" />), image_tag("rails")
|
||||
end
|
||||
|
||||
end
|
||||
92
vendor/plugins/asset_packager/test/asset_packager_test.rb
vendored
Normal file
92
vendor/plugins/asset_packager/test/asset_packager_test.rb
vendored
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
require File.dirname(__FILE__) + '/../../../../config/environment'
|
||||
require 'test/unit'
|
||||
require 'mocha'
|
||||
|
||||
$asset_packages_yml = YAML.load_file("#{RAILS_ROOT}/vendor/plugins/asset_packager/test/asset_packages.yml")
|
||||
$asset_base_path = "#{RAILS_ROOT}/vendor/plugins/asset_packager/test/assets"
|
||||
|
||||
class AssetPackagerTest < Test::Unit::TestCase
|
||||
include Synthesis
|
||||
|
||||
def setup
|
||||
Synthesis::AssetPackage.any_instance.stubs(:log)
|
||||
Synthesis::AssetPackage.build_all
|
||||
end
|
||||
|
||||
def teardown
|
||||
Synthesis::AssetPackage.delete_all
|
||||
end
|
||||
|
||||
def test_find_by_type
|
||||
js_asset_packages = Synthesis::AssetPackage.find_by_type("javascripts")
|
||||
assert_equal 2, js_asset_packages.length
|
||||
assert_equal "base", js_asset_packages[0].target
|
||||
assert_equal ["prototype", "effects", "controls", "dragdrop"], js_asset_packages[0].sources
|
||||
end
|
||||
|
||||
def test_find_by_target
|
||||
package = Synthesis::AssetPackage.find_by_target("javascripts", "base")
|
||||
assert_equal "base", package.target
|
||||
assert_equal ["prototype", "effects", "controls", "dragdrop"], package.sources
|
||||
end
|
||||
|
||||
def test_find_by_source
|
||||
package = Synthesis::AssetPackage.find_by_source("javascripts", "controls")
|
||||
assert_equal "base", package.target
|
||||
assert_equal ["prototype", "effects", "controls", "dragdrop"], package.sources
|
||||
end
|
||||
|
||||
def test_delete_and_build
|
||||
Synthesis::AssetPackage.delete_all
|
||||
js_package_names = Dir.new("#{$asset_base_path}/javascripts").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.js/) }
|
||||
css_package_names = Dir.new("#{$asset_base_path}/stylesheets").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.css/) }
|
||||
css_subdir_package_names = Dir.new("#{$asset_base_path}/stylesheets/subdir").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.css/) }
|
||||
|
||||
assert_equal 0, js_package_names.length
|
||||
assert_equal 0, css_package_names.length
|
||||
assert_equal 0, css_subdir_package_names.length
|
||||
|
||||
Synthesis::AssetPackage.build_all
|
||||
js_package_names = Dir.new("#{$asset_base_path}/javascripts").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.js/) }.sort
|
||||
css_package_names = Dir.new("#{$asset_base_path}/stylesheets").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.css/) }.sort
|
||||
css_subdir_package_names = Dir.new("#{$asset_base_path}/stylesheets/subdir").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.css/) }.sort
|
||||
|
||||
assert_equal 2, js_package_names.length
|
||||
assert_equal 2, css_package_names.length
|
||||
assert_equal 1, css_subdir_package_names.length
|
||||
assert js_package_names[0].match(/\Abase_\d+.js\z/)
|
||||
assert js_package_names[1].match(/\Asecondary_\d+.js\z/)
|
||||
assert css_package_names[0].match(/\Abase_\d+.css\z/)
|
||||
assert css_package_names[1].match(/\Asecondary_\d+.css\z/)
|
||||
assert css_subdir_package_names[0].match(/\Astyles_\d+.css\z/)
|
||||
end
|
||||
|
||||
def test_js_names_from_sources
|
||||
package_names = Synthesis::AssetPackage.targets_from_sources("javascripts", ["prototype", "effects", "noexist1", "controls", "foo", "noexist2"])
|
||||
assert_equal 4, package_names.length
|
||||
assert package_names[0].match(/\Abase_\d+\z/)
|
||||
assert_equal package_names[1], "noexist1"
|
||||
assert package_names[2].match(/\Asecondary_\d+\z/)
|
||||
assert_equal package_names[3], "noexist2"
|
||||
end
|
||||
|
||||
def test_css_names_from_sources
|
||||
package_names = Synthesis::AssetPackage.targets_from_sources("stylesheets", ["header", "screen", "noexist1", "foo", "noexist2"])
|
||||
assert_equal 4, package_names.length
|
||||
assert package_names[0].match(/\Abase_\d+\z/)
|
||||
assert_equal package_names[1], "noexist1"
|
||||
assert package_names[2].match(/\Asecondary_\d+\z/)
|
||||
assert_equal package_names[3], "noexist2"
|
||||
end
|
||||
|
||||
def test_should_return_merge_environments_when_set
|
||||
Synthesis::AssetPackage.merge_environments = ["staging", "production"]
|
||||
assert_equal ["staging", "production"], Synthesis::AssetPackage.merge_environments
|
||||
end
|
||||
|
||||
def test_should_only_return_production_merge_environment_when_not_set
|
||||
assert_equal ["production"], Synthesis::AssetPackage.merge_environments
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
20
vendor/plugins/asset_packager/test/asset_packages.yml
vendored
Normal file
20
vendor/plugins/asset_packager/test/asset_packages.yml
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
javascripts:
|
||||
- base:
|
||||
- prototype
|
||||
- effects
|
||||
- controls
|
||||
- dragdrop
|
||||
- secondary:
|
||||
- foo
|
||||
- bar
|
||||
- application
|
||||
stylesheets:
|
||||
- base:
|
||||
- screen
|
||||
- header
|
||||
- secondary:
|
||||
- foo
|
||||
- bar
|
||||
- subdir/styles:
|
||||
- foo
|
||||
- bar
|
||||
2
vendor/plugins/asset_packager/test/assets/javascripts/application.js
vendored
Executable file
2
vendor/plugins/asset_packager/test/assets/javascripts/application.js
vendored
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
// Place your application-specific JavaScript functions and classes here
|
||||
// This file is automatically included by javascript_include_tag :defaults
|
||||
4
vendor/plugins/asset_packager/test/assets/javascripts/bar.js
vendored
Executable file
4
vendor/plugins/asset_packager/test/assets/javascripts/bar.js
vendored
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
bar bar bar
|
||||
bar bar bar
|
||||
bar bar bar
|
||||
|
||||
815
vendor/plugins/asset_packager/test/assets/javascripts/controls.js
vendored
Executable file
815
vendor/plugins/asset_packager/test/assets/javascripts/controls.js
vendored
Executable file
|
|
@ -0,0 +1,815 @@
|
|||
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
|
||||
// (c) 2005 Jon Tirsen (http://www.tirsen.com)
|
||||
// Contributors:
|
||||
// Richard Livsey
|
||||
// Rahul Bhargava
|
||||
// Rob Wills
|
||||
//
|
||||
// See scriptaculous.js for full license.
|
||||
|
||||
// Autocompleter.Base handles all the autocompletion functionality
|
||||
// that's independent of the data source for autocompletion. This
|
||||
// includes drawing the autocompletion menu, observing keyboard
|
||||
// and mouse events, and similar.
|
||||
//
|
||||
// Specific autocompleters need to provide, at the very least,
|
||||
// a getUpdatedChoices function that will be invoked every time
|
||||
// the text inside the monitored textbox changes. This method
|
||||
// should get the text for which to provide autocompletion by
|
||||
// invoking this.getToken(), NOT by directly accessing
|
||||
// this.element.value. This is to allow incremental tokenized
|
||||
// autocompletion. Specific auto-completion logic (AJAX, etc)
|
||||
// belongs in getUpdatedChoices.
|
||||
//
|
||||
// Tokenized incremental autocompletion is enabled automatically
|
||||
// when an autocompleter is instantiated with the 'tokens' option
|
||||
// in the options parameter, e.g.:
|
||||
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
|
||||
// will incrementally autocomplete with a comma as the token.
|
||||
// Additionally, ',' in the above example can be replaced with
|
||||
// a token array, e.g. { tokens: [',', '\n'] } which
|
||||
// enables autocompletion on multiple tokens. This is most
|
||||
// useful when one of the tokens is \n (a newline), as it
|
||||
// allows smart autocompletion after linebreaks.
|
||||
|
||||
var Autocompleter = {}
|
||||
Autocompleter.Base = function() {};
|
||||
Autocompleter.Base.prototype = {
|
||||
baseInitialize: function(element, update, options) {
|
||||
this.element = $(element);
|
||||
this.update = $(update);
|
||||
this.hasFocus = false;
|
||||
this.changed = false;
|
||||
this.active = false;
|
||||
this.index = 0;
|
||||
this.entryCount = 0;
|
||||
|
||||
if (this.setOptions)
|
||||
this.setOptions(options);
|
||||
else
|
||||
this.options = options || {};
|
||||
|
||||
this.options.paramName = this.options.paramName || this.element.name;
|
||||
this.options.tokens = this.options.tokens || [];
|
||||
this.options.frequency = this.options.frequency || 0.4;
|
||||
this.options.minChars = this.options.minChars || 1;
|
||||
this.options.onShow = this.options.onShow ||
|
||||
function(element, update){
|
||||
if(!update.style.position || update.style.position=='absolute') {
|
||||
update.style.position = 'absolute';
|
||||
Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
|
||||
}
|
||||
Effect.Appear(update,{duration:0.15});
|
||||
};
|
||||
this.options.onHide = this.options.onHide ||
|
||||
function(element, update){ new Effect.Fade(update,{duration:0.15}) };
|
||||
|
||||
if (typeof(this.options.tokens) == 'string')
|
||||
this.options.tokens = new Array(this.options.tokens);
|
||||
|
||||
this.observer = null;
|
||||
|
||||
this.element.setAttribute('autocomplete','off');
|
||||
|
||||
Element.hide(this.update);
|
||||
|
||||
Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
|
||||
Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
|
||||
},
|
||||
|
||||
show: function() {
|
||||
if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
|
||||
if(!this.iefix &&
|
||||
(navigator.appVersion.indexOf('MSIE')>0) &&
|
||||
(navigator.userAgent.indexOf('Opera')<0) &&
|
||||
(Element.getStyle(this.update, 'position')=='absolute')) {
|
||||
new Insertion.After(this.update,
|
||||
'<iframe id="' + this.update.id + '_iefix" '+
|
||||
'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
|
||||
'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
|
||||
this.iefix = $(this.update.id+'_iefix');
|
||||
}
|
||||
if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
|
||||
},
|
||||
|
||||
fixIEOverlapping: function() {
|
||||
Position.clone(this.update, this.iefix);
|
||||
this.iefix.style.zIndex = 1;
|
||||
this.update.style.zIndex = 2;
|
||||
Element.show(this.iefix);
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
this.stopIndicator();
|
||||
if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
|
||||
if(this.iefix) Element.hide(this.iefix);
|
||||
},
|
||||
|
||||
startIndicator: function() {
|
||||
if(this.options.indicator) Element.show(this.options.indicator);
|
||||
},
|
||||
|
||||
stopIndicator: function() {
|
||||
if(this.options.indicator) Element.hide(this.options.indicator);
|
||||
},
|
||||
|
||||
onKeyPress: function(event) {
|
||||
if(this.active)
|
||||
switch(event.keyCode) {
|
||||
case Event.KEY_TAB:
|
||||
case Event.KEY_RETURN:
|
||||
this.selectEntry();
|
||||
Event.stop(event);
|
||||
case Event.KEY_ESC:
|
||||
this.hide();
|
||||
this.active = false;
|
||||
Event.stop(event);
|
||||
return;
|
||||
case Event.KEY_LEFT:
|
||||
case Event.KEY_RIGHT:
|
||||
return;
|
||||
case Event.KEY_UP:
|
||||
this.markPrevious();
|
||||
this.render();
|
||||
if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
|
||||
return;
|
||||
case Event.KEY_DOWN:
|
||||
this.markNext();
|
||||
this.render();
|
||||
if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
|
||||
return;
|
||||
}
|
||||
else
|
||||
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
|
||||
(navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return;
|
||||
|
||||
this.changed = true;
|
||||
this.hasFocus = true;
|
||||
|
||||
if(this.observer) clearTimeout(this.observer);
|
||||
this.observer =
|
||||
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
|
||||
},
|
||||
|
||||
activate: function() {
|
||||
this.changed = false;
|
||||
this.hasFocus = true;
|
||||
this.getUpdatedChoices();
|
||||
},
|
||||
|
||||
onHover: function(event) {
|
||||
var element = Event.findElement(event, 'LI');
|
||||
if(this.index != element.autocompleteIndex)
|
||||
{
|
||||
this.index = element.autocompleteIndex;
|
||||
this.render();
|
||||
}
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
onClick: function(event) {
|
||||
var element = Event.findElement(event, 'LI');
|
||||
this.index = element.autocompleteIndex;
|
||||
this.selectEntry();
|
||||
this.hide();
|
||||
},
|
||||
|
||||
onBlur: function(event) {
|
||||
// needed to make click events working
|
||||
setTimeout(this.hide.bind(this), 250);
|
||||
this.hasFocus = false;
|
||||
this.active = false;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if(this.entryCount > 0) {
|
||||
for (var i = 0; i < this.entryCount; i++)
|
||||
this.index==i ?
|
||||
Element.addClassName(this.getEntry(i),"selected") :
|
||||
Element.removeClassName(this.getEntry(i),"selected");
|
||||
|
||||
if(this.hasFocus) {
|
||||
this.show();
|
||||
this.active = true;
|
||||
}
|
||||
} else {
|
||||
this.active = false;
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
markPrevious: function() {
|
||||
if(this.index > 0) this.index--
|
||||
else this.index = this.entryCount-1;
|
||||
},
|
||||
|
||||
markNext: function() {
|
||||
if(this.index < this.entryCount-1) this.index++
|
||||
else this.index = 0;
|
||||
},
|
||||
|
||||
getEntry: function(index) {
|
||||
return this.update.firstChild.childNodes[index];
|
||||
},
|
||||
|
||||
getCurrentEntry: function() {
|
||||
return this.getEntry(this.index);
|
||||
},
|
||||
|
||||
selectEntry: function() {
|
||||
this.active = false;
|
||||
this.updateElement(this.getCurrentEntry());
|
||||
},
|
||||
|
||||
updateElement: function(selectedElement) {
|
||||
if (this.options.updateElement) {
|
||||
this.options.updateElement(selectedElement);
|
||||
return;
|
||||
}
|
||||
var value = '';
|
||||
if (this.options.select) {
|
||||
var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
|
||||
if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
|
||||
} else
|
||||
value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
|
||||
|
||||
var lastTokenPos = this.findLastToken();
|
||||
if (lastTokenPos != -1) {
|
||||
var newValue = this.element.value.substr(0, lastTokenPos + 1);
|
||||
var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
|
||||
if (whitespace)
|
||||
newValue += whitespace[0];
|
||||
this.element.value = newValue + value;
|
||||
} else {
|
||||
this.element.value = value;
|
||||
}
|
||||
this.element.focus();
|
||||
|
||||
if (this.options.afterUpdateElement)
|
||||
this.options.afterUpdateElement(this.element, selectedElement);
|
||||
},
|
||||
|
||||
updateChoices: function(choices) {
|
||||
if(!this.changed && this.hasFocus) {
|
||||
this.update.innerHTML = choices;
|
||||
Element.cleanWhitespace(this.update);
|
||||
Element.cleanWhitespace(this.update.firstChild);
|
||||
|
||||
if(this.update.firstChild && this.update.firstChild.childNodes) {
|
||||
this.entryCount =
|
||||
this.update.firstChild.childNodes.length;
|
||||
for (var i = 0; i < this.entryCount; i++) {
|
||||
var entry = this.getEntry(i);
|
||||
entry.autocompleteIndex = i;
|
||||
this.addObservers(entry);
|
||||
}
|
||||
} else {
|
||||
this.entryCount = 0;
|
||||
}
|
||||
|
||||
this.stopIndicator();
|
||||
|
||||
this.index = 0;
|
||||
this.render();
|
||||
}
|
||||
},
|
||||
|
||||
addObservers: function(element) {
|
||||
Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
|
||||
Event.observe(element, "click", this.onClick.bindAsEventListener(this));
|
||||
},
|
||||
|
||||
onObserverEvent: function() {
|
||||
this.changed = false;
|
||||
if(this.getToken().length>=this.options.minChars) {
|
||||
this.startIndicator();
|
||||
this.getUpdatedChoices();
|
||||
} else {
|
||||
this.active = false;
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
getToken: function() {
|
||||
var tokenPos = this.findLastToken();
|
||||
if (tokenPos != -1)
|
||||
var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
|
||||
else
|
||||
var ret = this.element.value;
|
||||
|
||||
return /\n/.test(ret) ? '' : ret;
|
||||
},
|
||||
|
||||
findLastToken: function() {
|
||||
var lastTokenPos = -1;
|
||||
|
||||
for (var i=0; i<this.options.tokens.length; i++) {
|
||||
var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
|
||||
if (thisTokenPos > lastTokenPos)
|
||||
lastTokenPos = thisTokenPos;
|
||||
}
|
||||
return lastTokenPos;
|
||||
}
|
||||
}
|
||||
|
||||
Ajax.Autocompleter = Class.create();
|
||||
Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
|
||||
initialize: function(element, update, url, options) {
|
||||
this.baseInitialize(element, update, options);
|
||||
this.options.asynchronous = true;
|
||||
this.options.onComplete = this.onComplete.bind(this);
|
||||
this.options.defaultParams = this.options.parameters || null;
|
||||
this.url = url;
|
||||
},
|
||||
|
||||
getUpdatedChoices: function() {
|
||||
entry = encodeURIComponent(this.options.paramName) + '=' +
|
||||
encodeURIComponent(this.getToken());
|
||||
|
||||
this.options.parameters = this.options.callback ?
|
||||
this.options.callback(this.element, entry) : entry;
|
||||
|
||||
if(this.options.defaultParams)
|
||||
this.options.parameters += '&' + this.options.defaultParams;
|
||||
|
||||
new Ajax.Request(this.url, this.options);
|
||||
},
|
||||
|
||||
onComplete: function(request) {
|
||||
this.updateChoices(request.responseText);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// The local array autocompleter. Used when you'd prefer to
|
||||
// inject an array of autocompletion options into the page, rather
|
||||
// than sending out Ajax queries, which can be quite slow sometimes.
|
||||
//
|
||||
// The constructor takes four parameters. The first two are, as usual,
|
||||
// the id of the monitored textbox, and id of the autocompletion menu.
|
||||
// The third is the array you want to autocomplete from, and the fourth
|
||||
// is the options block.
|
||||
//
|
||||
// Extra local autocompletion options:
|
||||
// - choices - How many autocompletion choices to offer
|
||||
//
|
||||
// - partialSearch - If false, the autocompleter will match entered
|
||||
// text only at the beginning of strings in the
|
||||
// autocomplete array. Defaults to true, which will
|
||||
// match text at the beginning of any *word* in the
|
||||
// strings in the autocomplete array. If you want to
|
||||
// search anywhere in the string, additionally set
|
||||
// the option fullSearch to true (default: off).
|
||||
//
|
||||
// - fullSsearch - Search anywhere in autocomplete array strings.
|
||||
//
|
||||
// - partialChars - How many characters to enter before triggering
|
||||
// a partial match (unlike minChars, which defines
|
||||
// how many characters are required to do any match
|
||||
// at all). Defaults to 2.
|
||||
//
|
||||
// - ignoreCase - Whether to ignore case when autocompleting.
|
||||
// Defaults to true.
|
||||
//
|
||||
// It's possible to pass in a custom function as the 'selector'
|
||||
// option, if you prefer to write your own autocompletion logic.
|
||||
// In that case, the other options above will not apply unless
|
||||
// you support them.
|
||||
|
||||
Autocompleter.Local = Class.create();
|
||||
Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
|
||||
initialize: function(element, update, array, options) {
|
||||
this.baseInitialize(element, update, options);
|
||||
this.options.array = array;
|
||||
},
|
||||
|
||||
getUpdatedChoices: function() {
|
||||
this.updateChoices(this.options.selector(this));
|
||||
},
|
||||
|
||||
setOptions: function(options) {
|
||||
this.options = Object.extend({
|
||||
choices: 10,
|
||||
partialSearch: true,
|
||||
partialChars: 2,
|
||||
ignoreCase: true,
|
||||
fullSearch: false,
|
||||
selector: function(instance) {
|
||||
var ret = []; // Beginning matches
|
||||
var partial = []; // Inside matches
|
||||
var entry = instance.getToken();
|
||||
var count = 0;
|
||||
|
||||
for (var i = 0; i < instance.options.array.length &&
|
||||
ret.length < instance.options.choices ; i++) {
|
||||
|
||||
var elem = instance.options.array[i];
|
||||
var foundPos = instance.options.ignoreCase ?
|
||||
elem.toLowerCase().indexOf(entry.toLowerCase()) :
|
||||
elem.indexOf(entry);
|
||||
|
||||
while (foundPos != -1) {
|
||||
if (foundPos == 0 && elem.length != entry.length) {
|
||||
ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
|
||||
elem.substr(entry.length) + "</li>");
|
||||
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("<li>" + elem.substr(0, foundPos) + "<strong>" +
|
||||
elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
|
||||
foundPos + entry.length) + "</li>");
|
||||
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 "<ul>" + ret.join('') + "</ul>";
|
||||
}
|
||||
}, options || {});
|
||||
}
|
||||
});
|
||||
|
||||
// AJAX in-place editor
|
||||
//
|
||||
// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
|
||||
|
||||
// Use this if you notice weird scrolling problems on some browsers,
|
||||
// the DOM might be a bit confused when this gets called so do this
|
||||
// waits 1 ms (with setTimeout) until it does the activation
|
||||
Field.scrollFreeActivate = function(field) {
|
||||
setTimeout(function() {
|
||||
Field.activate(field);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
Ajax.InPlaceEditor = Class.create();
|
||||
Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
|
||||
Ajax.InPlaceEditor.prototype = {
|
||||
initialize: function(element, url, options) {
|
||||
this.url = url;
|
||||
this.element = $(element);
|
||||
|
||||
this.options = Object.extend({
|
||||
okButton: true,
|
||||
okText: "ok",
|
||||
cancelLink: true,
|
||||
cancelText: "cancel",
|
||||
savingText: "Saving...",
|
||||
clickToEditText: "Click to edit",
|
||||
okText: "ok",
|
||||
rows: 1,
|
||||
onComplete: function(transport, element) {
|
||||
new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
|
||||
},
|
||||
onFailure: function(transport) {
|
||||
alert("Error communicating with the server: " + transport.responseText.stripTags());
|
||||
},
|
||||
callback: function(form) {
|
||||
return Form.serialize(form);
|
||||
},
|
||||
handleLineBreaks: true,
|
||||
loadingText: 'Loading...',
|
||||
savingClassName: 'inplaceeditor-saving',
|
||||
loadingClassName: 'inplaceeditor-loading',
|
||||
formClassName: 'inplaceeditor-form',
|
||||
highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
|
||||
highlightendcolor: "#FFFFFF",
|
||||
externalControl: null,
|
||||
submitOnBlur: false,
|
||||
ajaxOptions: {},
|
||||
evalScripts: false
|
||||
}, options || {});
|
||||
|
||||
if(!this.options.formId && this.element.id) {
|
||||
this.options.formId = this.element.id + "-inplaceeditor";
|
||||
if ($(this.options.formId)) {
|
||||
// there's already a form with that name, don't specify an id
|
||||
this.options.formId = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.externalControl) {
|
||||
this.options.externalControl = $(this.options.externalControl);
|
||||
}
|
||||
|
||||
this.originalBackground = Element.getStyle(this.element, 'background-color');
|
||||
if (!this.originalBackground) {
|
||||
this.originalBackground = "transparent";
|
||||
}
|
||||
|
||||
this.element.title = this.options.clickToEditText;
|
||||
|
||||
this.onclickListener = this.enterEditMode.bindAsEventListener(this);
|
||||
this.mouseoverListener = this.enterHover.bindAsEventListener(this);
|
||||
this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
|
||||
Event.observe(this.element, 'click', this.onclickListener);
|
||||
Event.observe(this.element, 'mouseover', this.mouseoverListener);
|
||||
Event.observe(this.element, 'mouseout', this.mouseoutListener);
|
||||
if (this.options.externalControl) {
|
||||
Event.observe(this.options.externalControl, 'click', this.onclickListener);
|
||||
Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
|
||||
Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
|
||||
}
|
||||
},
|
||||
enterEditMode: function(evt) {
|
||||
if (this.saving) return;
|
||||
if (this.editing) return;
|
||||
this.editing = true;
|
||||
this.onEnterEditMode();
|
||||
if (this.options.externalControl) {
|
||||
Element.hide(this.options.externalControl);
|
||||
}
|
||||
Element.hide(this.element);
|
||||
this.createForm();
|
||||
this.element.parentNode.insertBefore(this.form, this.element);
|
||||
Field.scrollFreeActivate(this.editField);
|
||||
// stop the event to avoid a page refresh in Safari
|
||||
if (evt) {
|
||||
Event.stop(evt);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
createForm: function() {
|
||||
this.form = document.createElement("form");
|
||||
this.form.id = this.options.formId;
|
||||
Element.addClassName(this.form, this.options.formClassName)
|
||||
this.form.onsubmit = this.onSubmit.bind(this);
|
||||
|
||||
this.createEditField();
|
||||
|
||||
if (this.options.textarea) {
|
||||
var br = document.createElement("br");
|
||||
this.form.appendChild(br);
|
||||
}
|
||||
|
||||
if (this.options.okButton) {
|
||||
okButton = document.createElement("input");
|
||||
okButton.type = "submit";
|
||||
okButton.value = this.options.okText;
|
||||
okButton.className = 'editor_ok_button';
|
||||
this.form.appendChild(okButton);
|
||||
}
|
||||
|
||||
if (this.options.cancelLink) {
|
||||
cancelLink = document.createElement("a");
|
||||
cancelLink.href = "#";
|
||||
cancelLink.appendChild(document.createTextNode(this.options.cancelText));
|
||||
cancelLink.onclick = this.onclickCancel.bind(this);
|
||||
cancelLink.className = 'editor_cancel';
|
||||
this.form.appendChild(cancelLink);
|
||||
}
|
||||
},
|
||||
hasHTMLLineBreaks: function(string) {
|
||||
if (!this.options.handleLineBreaks) return false;
|
||||
return string.match(/<br/i) || string.match(/<p>/i);
|
||||
},
|
||||
convertHTMLLineBreaks: function(string) {
|
||||
return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
|
||||
},
|
||||
createEditField: function() {
|
||||
var text;
|
||||
if(this.options.loadTextURL) {
|
||||
text = this.options.loadingText;
|
||||
} else {
|
||||
text = this.getText();
|
||||
}
|
||||
|
||||
var obj = this;
|
||||
|
||||
if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
|
||||
this.options.textarea = false;
|
||||
var textField = document.createElement("input");
|
||||
textField.obj = this;
|
||||
textField.type = "text";
|
||||
textField.name = "value";
|
||||
textField.value = text;
|
||||
textField.style.backgroundColor = this.options.highlightcolor;
|
||||
textField.className = 'editor_field';
|
||||
var size = this.options.size || this.options.cols || 0;
|
||||
if (size != 0) textField.size = size;
|
||||
if (this.options.submitOnBlur)
|
||||
textField.onblur = this.onSubmit.bind(this);
|
||||
this.editField = textField;
|
||||
} else {
|
||||
this.options.textarea = true;
|
||||
var textArea = document.createElement("textarea");
|
||||
textArea.obj = this;
|
||||
textArea.name = "value";
|
||||
textArea.value = this.convertHTMLLineBreaks(text);
|
||||
textArea.rows = this.options.rows;
|
||||
textArea.cols = this.options.cols || 40;
|
||||
textArea.className = 'editor_field';
|
||||
if (this.options.submitOnBlur)
|
||||
textArea.onblur = this.onSubmit.bind(this);
|
||||
this.editField = textArea;
|
||||
}
|
||||
|
||||
if(this.options.loadTextURL) {
|
||||
this.loadExternalText();
|
||||
}
|
||||
this.form.appendChild(this.editField);
|
||||
},
|
||||
getText: function() {
|
||||
return this.element.innerHTML;
|
||||
},
|
||||
loadExternalText: function() {
|
||||
Element.addClassName(this.form, this.options.loadingClassName);
|
||||
this.editField.disabled = true;
|
||||
new Ajax.Request(
|
||||
this.options.loadTextURL,
|
||||
Object.extend({
|
||||
asynchronous: true,
|
||||
onComplete: this.onLoadedExternalText.bind(this)
|
||||
}, this.options.ajaxOptions)
|
||||
);
|
||||
},
|
||||
onLoadedExternalText: function(transport) {
|
||||
Element.removeClassName(this.form, this.options.loadingClassName);
|
||||
this.editField.disabled = false;
|
||||
this.editField.value = transport.responseText.stripTags();
|
||||
},
|
||||
onclickCancel: function() {
|
||||
this.onComplete();
|
||||
this.leaveEditMode();
|
||||
return false;
|
||||
},
|
||||
onFailure: function(transport) {
|
||||
this.options.onFailure(transport);
|
||||
if (this.oldInnerHTML) {
|
||||
this.element.innerHTML = this.oldInnerHTML;
|
||||
this.oldInnerHTML = null;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
onSubmit: function() {
|
||||
// onLoading resets these so we need to save them away for the Ajax call
|
||||
var form = this.form;
|
||||
var value = this.editField.value;
|
||||
|
||||
// do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
|
||||
// which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
|
||||
// to be displayed indefinitely
|
||||
this.onLoading();
|
||||
|
||||
if (this.options.evalScripts) {
|
||||
new Ajax.Request(
|
||||
this.url, Object.extend({
|
||||
parameters: this.options.callback(form, value),
|
||||
onComplete: this.onComplete.bind(this),
|
||||
onFailure: this.onFailure.bind(this),
|
||||
asynchronous:true,
|
||||
evalScripts:true
|
||||
}, this.options.ajaxOptions));
|
||||
} else {
|
||||
new Ajax.Updater(
|
||||
{ success: this.element,
|
||||
// don't update on failure (this could be an option)
|
||||
failure: null },
|
||||
this.url, Object.extend({
|
||||
parameters: this.options.callback(form, value),
|
||||
onComplete: this.onComplete.bind(this),
|
||||
onFailure: this.onFailure.bind(this)
|
||||
}, this.options.ajaxOptions));
|
||||
}
|
||||
// stop the event to avoid a page refresh in Safari
|
||||
if (arguments.length > 1) {
|
||||
Event.stop(arguments[0]);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
onLoading: function() {
|
||||
this.saving = true;
|
||||
this.removeForm();
|
||||
this.leaveHover();
|
||||
this.showSaving();
|
||||
},
|
||||
showSaving: function() {
|
||||
this.oldInnerHTML = this.element.innerHTML;
|
||||
this.element.innerHTML = this.options.savingText;
|
||||
Element.addClassName(this.element, this.options.savingClassName);
|
||||
this.element.style.backgroundColor = this.originalBackground;
|
||||
Element.show(this.element);
|
||||
},
|
||||
removeForm: function() {
|
||||
if(this.form) {
|
||||
if (this.form.parentNode) Element.remove(this.form);
|
||||
this.form = null;
|
||||
}
|
||||
},
|
||||
enterHover: function() {
|
||||
if (this.saving) return;
|
||||
this.element.style.backgroundColor = this.options.highlightcolor;
|
||||
if (this.effect) {
|
||||
this.effect.cancel();
|
||||
}
|
||||
Element.addClassName(this.element, this.options.hoverClassName)
|
||||
},
|
||||
leaveHover: function() {
|
||||
if (this.options.backgroundColor) {
|
||||
this.element.style.backgroundColor = this.oldBackground;
|
||||
}
|
||||
Element.removeClassName(this.element, this.options.hoverClassName)
|
||||
if (this.saving) return;
|
||||
this.effect = new Effect.Highlight(this.element, {
|
||||
startcolor: this.options.highlightcolor,
|
||||
endcolor: this.options.highlightendcolor,
|
||||
restorecolor: this.originalBackground
|
||||
});
|
||||
},
|
||||
leaveEditMode: function() {
|
||||
Element.removeClassName(this.element, this.options.savingClassName);
|
||||
this.removeForm();
|
||||
this.leaveHover();
|
||||
this.element.style.backgroundColor = this.originalBackground;
|
||||
Element.show(this.element);
|
||||
if (this.options.externalControl) {
|
||||
Element.show(this.options.externalControl);
|
||||
}
|
||||
this.editing = false;
|
||||
this.saving = false;
|
||||
this.oldInnerHTML = null;
|
||||
this.onLeaveEditMode();
|
||||
},
|
||||
onComplete: function(transport) {
|
||||
this.leaveEditMode();
|
||||
this.options.onComplete.bind(this)(transport, this.element);
|
||||
},
|
||||
onEnterEditMode: function() {},
|
||||
onLeaveEditMode: function() {},
|
||||
dispose: function() {
|
||||
if (this.oldInnerHTML) {
|
||||
this.element.innerHTML = this.oldInnerHTML;
|
||||
}
|
||||
this.leaveEditMode();
|
||||
Event.stopObserving(this.element, 'click', this.onclickListener);
|
||||
Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
|
||||
Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
|
||||
if (this.options.externalControl) {
|
||||
Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
|
||||
Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
|
||||
Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ajax.InPlaceCollectionEditor = Class.create();
|
||||
Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
|
||||
Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
|
||||
createEditField: function() {
|
||||
if (!this.cached_selectTag) {
|
||||
var selectTag = document.createElement("select");
|
||||
var collection = this.options.collection || [];
|
||||
var optionTag;
|
||||
collection.each(function(e,i) {
|
||||
optionTag = document.createElement("option");
|
||||
optionTag.value = (e instanceof Array) ? e[0] : e;
|
||||
if(this.options.value==optionTag.value) optionTag.selected = true;
|
||||
optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
|
||||
selectTag.appendChild(optionTag);
|
||||
}.bind(this));
|
||||
this.cached_selectTag = selectTag;
|
||||
}
|
||||
|
||||
this.editField = this.cached_selectTag;
|
||||
if(this.options.loadTextURL) this.loadExternalText();
|
||||
this.form.appendChild(this.editField);
|
||||
this.options.callback = function(form, value) {
|
||||
return "value=" + encodeURIComponent(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Delayed observer, like Form.Element.Observer,
|
||||
// but waits for delay after last key input
|
||||
// Ideal for live-search fields
|
||||
|
||||
Form.Element.DelayedObserver = Class.create();
|
||||
Form.Element.DelayedObserver.prototype = {
|
||||
initialize: function(element, delay, callback) {
|
||||
this.delay = delay || 0.5;
|
||||
this.element = $(element);
|
||||
this.callback = callback;
|
||||
this.timer = null;
|
||||
this.lastValue = $F(this.element);
|
||||
Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
|
||||
},
|
||||
delayedListener: function(event) {
|
||||
if(this.lastValue == $F(this.element)) return;
|
||||
if(this.timer) clearTimeout(this.timer);
|
||||
this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
|
||||
this.lastValue = $F(this.element);
|
||||
},
|
||||
onTimerEvent: function() {
|
||||
this.timer = null;
|
||||
this.callback(this.element, $F(this.element));
|
||||
}
|
||||
};
|
||||
913
vendor/plugins/asset_packager/test/assets/javascripts/dragdrop.js
vendored
Executable file
913
vendor/plugins/asset_packager/test/assets/javascripts/dragdrop.js
vendored
Executable file
|
|
@ -0,0 +1,913 @@
|
|||
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
|
||||
//
|
||||
// See scriptaculous.js for full license.
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
var Droppables = {
|
||||
drops: [],
|
||||
|
||||
remove: function(element) {
|
||||
this.drops = this.drops.reject(function(d) { return d.element==$(element) });
|
||||
},
|
||||
|
||||
add: function(element) {
|
||||
element = $(element);
|
||||
var options = Object.extend({
|
||||
greedy: true,
|
||||
hoverclass: null,
|
||||
tree: false
|
||||
}, arguments[1] || {});
|
||||
|
||||
// cache containers
|
||||
if(options.containment) {
|
||||
options._containers = [];
|
||||
var containment = options.containment;
|
||||
if((typeof containment == 'object') &&
|
||||
(containment.constructor == Array)) {
|
||||
containment.each( function(c) { options._containers.push($(c)) });
|
||||
} else {
|
||||
options._containers.push($(containment));
|
||||
}
|
||||
}
|
||||
|
||||
if(options.accept) options.accept = [options.accept].flatten();
|
||||
|
||||
Element.makePositioned(element); // fix IE
|
||||
options.element = element;
|
||||
|
||||
this.drops.push(options);
|
||||
},
|
||||
|
||||
findDeepestChild: function(drops) {
|
||||
deepest = drops[0];
|
||||
|
||||
for (i = 1; i < drops.length; ++i)
|
||||
if (Element.isParent(drops[i].element, deepest.element))
|
||||
deepest = drops[i];
|
||||
|
||||
return deepest;
|
||||
},
|
||||
|
||||
isContained: function(element, drop) {
|
||||
var containmentNode;
|
||||
if(drop.tree) {
|
||||
containmentNode = element.treeNode;
|
||||
} else {
|
||||
containmentNode = element.parentNode;
|
||||
}
|
||||
return drop._containers.detect(function(c) { return containmentNode == c });
|
||||
},
|
||||
|
||||
isAffected: function(point, element, drop) {
|
||||
return (
|
||||
(drop.element!=element) &&
|
||||
((!drop._containers) ||
|
||||
this.isContained(element, drop)) &&
|
||||
((!drop.accept) ||
|
||||
(Element.classNames(element).detect(
|
||||
function(v) { return drop.accept.include(v) } ) )) &&
|
||||
Position.within(drop.element, point[0], point[1]) );
|
||||
},
|
||||
|
||||
deactivate: function(drop) {
|
||||
if(drop.hoverclass)
|
||||
Element.removeClassName(drop.element, drop.hoverclass);
|
||||
this.last_active = null;
|
||||
},
|
||||
|
||||
activate: function(drop) {
|
||||
if(drop.hoverclass)
|
||||
Element.addClassName(drop.element, drop.hoverclass);
|
||||
this.last_active = drop;
|
||||
},
|
||||
|
||||
show: function(point, element) {
|
||||
if(!this.drops.length) return;
|
||||
var affected = [];
|
||||
|
||||
if(this.last_active) this.deactivate(this.last_active);
|
||||
this.drops.each( function(drop) {
|
||||
if(Droppables.isAffected(point, element, drop))
|
||||
affected.push(drop);
|
||||
});
|
||||
|
||||
if(affected.length>0) {
|
||||
drop = Droppables.findDeepestChild(affected);
|
||||
Position.within(drop.element, point[0], point[1]);
|
||||
if(drop.onHover)
|
||||
drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
|
||||
|
||||
Droppables.activate(drop);
|
||||
}
|
||||
},
|
||||
|
||||
fire: function(event, element) {
|
||||
if(!this.last_active) return;
|
||||
Position.prepare();
|
||||
|
||||
if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
|
||||
if (this.last_active.onDrop)
|
||||
this.last_active.onDrop(element, this.last_active.element, event);
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
if(this.last_active)
|
||||
this.deactivate(this.last_active);
|
||||
}
|
||||
}
|
||||
|
||||
var Draggables = {
|
||||
drags: [],
|
||||
observers: [],
|
||||
|
||||
register: function(draggable) {
|
||||
if(this.drags.length == 0) {
|
||||
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
|
||||
this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
|
||||
this.eventKeypress = this.keyPress.bindAsEventListener(this);
|
||||
|
||||
Event.observe(document, "mouseup", this.eventMouseUp);
|
||||
Event.observe(document, "mousemove", this.eventMouseMove);
|
||||
Event.observe(document, "keypress", this.eventKeypress);
|
||||
}
|
||||
this.drags.push(draggable);
|
||||
},
|
||||
|
||||
unregister: function(draggable) {
|
||||
this.drags = this.drags.reject(function(d) { return d==draggable });
|
||||
if(this.drags.length == 0) {
|
||||
Event.stopObserving(document, "mouseup", this.eventMouseUp);
|
||||
Event.stopObserving(document, "mousemove", this.eventMouseMove);
|
||||
Event.stopObserving(document, "keypress", this.eventKeypress);
|
||||
}
|
||||
},
|
||||
|
||||
activate: function(draggable) {
|
||||
window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
|
||||
this.activeDraggable = draggable;
|
||||
},
|
||||
|
||||
deactivate: function() {
|
||||
this.activeDraggable = null;
|
||||
},
|
||||
|
||||
updateDrag: function(event) {
|
||||
if(!this.activeDraggable) return;
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
// Mozilla-based browsers fire successive mousemove events with
|
||||
// the same coordinates, prevent needless redrawing (moz bug?)
|
||||
if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
|
||||
this._lastPointer = pointer;
|
||||
this.activeDraggable.updateDrag(event, pointer);
|
||||
},
|
||||
|
||||
endDrag: function(event) {
|
||||
if(!this.activeDraggable) return;
|
||||
this._lastPointer = null;
|
||||
this.activeDraggable.endDrag(event);
|
||||
this.activeDraggable = null;
|
||||
},
|
||||
|
||||
keyPress: function(event) {
|
||||
if(this.activeDraggable)
|
||||
this.activeDraggable.keyPress(event);
|
||||
},
|
||||
|
||||
addObserver: function(observer) {
|
||||
this.observers.push(observer);
|
||||
this._cacheObserverCallbacks();
|
||||
},
|
||||
|
||||
removeObserver: function(element) { // element instead of observer fixes mem leaks
|
||||
this.observers = this.observers.reject( function(o) { return o.element==element });
|
||||
this._cacheObserverCallbacks();
|
||||
},
|
||||
|
||||
notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
|
||||
if(this[eventName+'Count'] > 0)
|
||||
this.observers.each( function(o) {
|
||||
if(o[eventName]) o[eventName](eventName, draggable, event);
|
||||
});
|
||||
},
|
||||
|
||||
_cacheObserverCallbacks: function() {
|
||||
['onStart','onEnd','onDrag'].each( function(eventName) {
|
||||
Draggables[eventName+'Count'] = Draggables.observers.select(
|
||||
function(o) { return o[eventName]; }
|
||||
).length;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
var Draggable = Class.create();
|
||||
Draggable.prototype = {
|
||||
initialize: function(element) {
|
||||
var options = Object.extend({
|
||||
handle: false,
|
||||
starteffect: function(element) {
|
||||
new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7});
|
||||
},
|
||||
reverteffect: function(element, top_offset, left_offset) {
|
||||
var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
|
||||
element._revert = new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur});
|
||||
},
|
||||
endeffect: function(element) {
|
||||
new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});
|
||||
},
|
||||
zindex: 1000,
|
||||
revert: false,
|
||||
scroll: false,
|
||||
scrollSensitivity: 20,
|
||||
scrollSpeed: 15,
|
||||
snap: false // false, or xy or [x,y] or function(x,y){ return [x,y] }
|
||||
}, arguments[1] || {});
|
||||
|
||||
this.element = $(element);
|
||||
|
||||
if(options.handle && (typeof options.handle == 'string')) {
|
||||
var h = Element.childrenWithClassName(this.element, options.handle, true);
|
||||
if(h.length>0) this.handle = h[0];
|
||||
}
|
||||
if(!this.handle) this.handle = $(options.handle);
|
||||
if(!this.handle) this.handle = this.element;
|
||||
|
||||
if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML)
|
||||
options.scroll = $(options.scroll);
|
||||
|
||||
Element.makePositioned(this.element); // fix IE
|
||||
|
||||
this.delta = this.currentDelta();
|
||||
this.options = options;
|
||||
this.dragging = false;
|
||||
|
||||
this.eventMouseDown = this.initDrag.bindAsEventListener(this);
|
||||
Event.observe(this.handle, "mousedown", this.eventMouseDown);
|
||||
|
||||
Draggables.register(this);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
|
||||
Draggables.unregister(this);
|
||||
},
|
||||
|
||||
currentDelta: function() {
|
||||
return([
|
||||
parseInt(Element.getStyle(this.element,'left') || '0'),
|
||||
parseInt(Element.getStyle(this.element,'top') || '0')]);
|
||||
},
|
||||
|
||||
initDrag: function(event) {
|
||||
if(Event.isLeftClick(event)) {
|
||||
// abort on form elements, fixes a Firefox issue
|
||||
var src = Event.element(event);
|
||||
if(src.tagName && (
|
||||
src.tagName=='INPUT' ||
|
||||
src.tagName=='SELECT' ||
|
||||
src.tagName=='OPTION' ||
|
||||
src.tagName=='BUTTON' ||
|
||||
src.tagName=='TEXTAREA')) return;
|
||||
|
||||
if(this.element._revert) {
|
||||
this.element._revert.cancel();
|
||||
this.element._revert = null;
|
||||
}
|
||||
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
var pos = Position.cumulativeOffset(this.element);
|
||||
this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
|
||||
|
||||
Draggables.activate(this);
|
||||
Event.stop(event);
|
||||
}
|
||||
},
|
||||
|
||||
startDrag: function(event) {
|
||||
this.dragging = true;
|
||||
|
||||
if(this.options.zindex) {
|
||||
this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
|
||||
this.element.style.zIndex = this.options.zindex;
|
||||
}
|
||||
|
||||
if(this.options.ghosting) {
|
||||
this._clone = this.element.cloneNode(true);
|
||||
Position.absolutize(this.element);
|
||||
this.element.parentNode.insertBefore(this._clone, this.element);
|
||||
}
|
||||
|
||||
if(this.options.scroll) {
|
||||
if (this.options.scroll == window) {
|
||||
var where = this._getWindowScroll(this.options.scroll);
|
||||
this.originalScrollLeft = where.left;
|
||||
this.originalScrollTop = where.top;
|
||||
} else {
|
||||
this.originalScrollLeft = this.options.scroll.scrollLeft;
|
||||
this.originalScrollTop = this.options.scroll.scrollTop;
|
||||
}
|
||||
}
|
||||
|
||||
Draggables.notify('onStart', this, event);
|
||||
if(this.options.starteffect) this.options.starteffect(this.element);
|
||||
},
|
||||
|
||||
updateDrag: function(event, pointer) {
|
||||
if(!this.dragging) this.startDrag(event);
|
||||
Position.prepare();
|
||||
Droppables.show(pointer, this.element);
|
||||
Draggables.notify('onDrag', this, event);
|
||||
this.draw(pointer);
|
||||
if(this.options.change) this.options.change(this);
|
||||
|
||||
if(this.options.scroll) {
|
||||
this.stopScrolling();
|
||||
|
||||
var p;
|
||||
if (this.options.scroll == window) {
|
||||
with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
|
||||
} else {
|
||||
p = Position.page(this.options.scroll);
|
||||
p[0] += this.options.scroll.scrollLeft;
|
||||
p[1] += this.options.scroll.scrollTop;
|
||||
p.push(p[0]+this.options.scroll.offsetWidth);
|
||||
p.push(p[1]+this.options.scroll.offsetHeight);
|
||||
}
|
||||
var speed = [0,0];
|
||||
if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
|
||||
if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
|
||||
if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
|
||||
if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
|
||||
this.startScrolling(speed);
|
||||
}
|
||||
|
||||
// fix AppleWebKit rendering
|
||||
if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
|
||||
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
finishDrag: function(event, success) {
|
||||
this.dragging = false;
|
||||
|
||||
if(this.options.ghosting) {
|
||||
Position.relativize(this.element);
|
||||
Element.remove(this._clone);
|
||||
this._clone = null;
|
||||
}
|
||||
|
||||
if(success) Droppables.fire(event, this.element);
|
||||
Draggables.notify('onEnd', this, event);
|
||||
|
||||
var revert = this.options.revert;
|
||||
if(revert && typeof revert == 'function') revert = revert(this.element);
|
||||
|
||||
var d = this.currentDelta();
|
||||
if(revert && this.options.reverteffect) {
|
||||
this.options.reverteffect(this.element,
|
||||
d[1]-this.delta[1], d[0]-this.delta[0]);
|
||||
} else {
|
||||
this.delta = d;
|
||||
}
|
||||
|
||||
if(this.options.zindex)
|
||||
this.element.style.zIndex = this.originalZ;
|
||||
|
||||
if(this.options.endeffect)
|
||||
this.options.endeffect(this.element);
|
||||
|
||||
Draggables.deactivate(this);
|
||||
Droppables.reset();
|
||||
},
|
||||
|
||||
keyPress: function(event) {
|
||||
if(event.keyCode!=Event.KEY_ESC) return;
|
||||
this.finishDrag(event, false);
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
endDrag: function(event) {
|
||||
if(!this.dragging) return;
|
||||
this.stopScrolling();
|
||||
this.finishDrag(event, true);
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
draw: function(point) {
|
||||
var pos = Position.cumulativeOffset(this.element);
|
||||
var d = this.currentDelta();
|
||||
pos[0] -= d[0]; pos[1] -= d[1];
|
||||
|
||||
if(this.options.scroll && (this.options.scroll != window)) {
|
||||
pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
|
||||
pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
|
||||
}
|
||||
|
||||
var p = [0,1].map(function(i){
|
||||
return (point[i]-pos[i]-this.offset[i])
|
||||
}.bind(this));
|
||||
|
||||
if(this.options.snap) {
|
||||
if(typeof this.options.snap == 'function') {
|
||||
p = this.options.snap(p[0],p[1]);
|
||||
} else {
|
||||
if(this.options.snap instanceof Array) {
|
||||
p = p.map( function(v, i) {
|
||||
return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
|
||||
} else {
|
||||
p = p.map( function(v) {
|
||||
return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
|
||||
}
|
||||
}}
|
||||
|
||||
var style = this.element.style;
|
||||
if((!this.options.constraint) || (this.options.constraint=='horizontal'))
|
||||
style.left = p[0] + "px";
|
||||
if((!this.options.constraint) || (this.options.constraint=='vertical'))
|
||||
style.top = p[1] + "px";
|
||||
if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
|
||||
},
|
||||
|
||||
stopScrolling: function() {
|
||||
if(this.scrollInterval) {
|
||||
clearInterval(this.scrollInterval);
|
||||
this.scrollInterval = null;
|
||||
Draggables._lastScrollPointer = null;
|
||||
}
|
||||
},
|
||||
|
||||
startScrolling: function(speed) {
|
||||
this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
|
||||
this.lastScrolled = new Date();
|
||||
this.scrollInterval = setInterval(this.scroll.bind(this), 10);
|
||||
},
|
||||
|
||||
scroll: function() {
|
||||
var current = new Date();
|
||||
var delta = current - this.lastScrolled;
|
||||
this.lastScrolled = current;
|
||||
if(this.options.scroll == window) {
|
||||
with (this._getWindowScroll(this.options.scroll)) {
|
||||
if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
|
||||
var d = delta / 1000;
|
||||
this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
|
||||
this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
|
||||
}
|
||||
|
||||
Position.prepare();
|
||||
Droppables.show(Draggables._lastPointer, this.element);
|
||||
Draggables.notify('onDrag', this);
|
||||
Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
|
||||
Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
|
||||
Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
|
||||
if (Draggables._lastScrollPointer[0] < 0)
|
||||
Draggables._lastScrollPointer[0] = 0;
|
||||
if (Draggables._lastScrollPointer[1] < 0)
|
||||
Draggables._lastScrollPointer[1] = 0;
|
||||
this.draw(Draggables._lastScrollPointer);
|
||||
|
||||
if(this.options.change) this.options.change(this);
|
||||
},
|
||||
|
||||
_getWindowScroll: function(w) {
|
||||
var T, L, W, H;
|
||||
with (w.document) {
|
||||
if (w.document.documentElement && documentElement.scrollTop) {
|
||||
T = documentElement.scrollTop;
|
||||
L = documentElement.scrollLeft;
|
||||
} else if (w.document.body) {
|
||||
T = body.scrollTop;
|
||||
L = body.scrollLeft;
|
||||
}
|
||||
if (w.innerWidth) {
|
||||
W = w.innerWidth;
|
||||
H = w.innerHeight;
|
||||
} else if (w.document.documentElement && documentElement.clientWidth) {
|
||||
W = documentElement.clientWidth;
|
||||
H = documentElement.clientHeight;
|
||||
} else {
|
||||
W = body.offsetWidth;
|
||||
H = body.offsetHeight
|
||||
}
|
||||
}
|
||||
return { top: T, left: L, width: W, height: H };
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
var SortableObserver = Class.create();
|
||||
SortableObserver.prototype = {
|
||||
initialize: function(element, observer) {
|
||||
this.element = $(element);
|
||||
this.observer = observer;
|
||||
this.lastValue = Sortable.serialize(this.element);
|
||||
},
|
||||
|
||||
onStart: function() {
|
||||
this.lastValue = Sortable.serialize(this.element);
|
||||
},
|
||||
|
||||
onEnd: function() {
|
||||
Sortable.unmark();
|
||||
if(this.lastValue != Sortable.serialize(this.element))
|
||||
this.observer(this.element)
|
||||
}
|
||||
}
|
||||
|
||||
var Sortable = {
|
||||
sortables: {},
|
||||
|
||||
_findRootElement: function(element) {
|
||||
while (element.tagName != "BODY") {
|
||||
if(element.id && Sortable.sortables[element.id]) return element;
|
||||
element = element.parentNode;
|
||||
}
|
||||
},
|
||||
|
||||
options: function(element) {
|
||||
element = Sortable._findRootElement($(element));
|
||||
if(!element) return;
|
||||
return Sortable.sortables[element.id];
|
||||
},
|
||||
|
||||
destroy: function(element){
|
||||
var s = Sortable.options(element);
|
||||
|
||||
if(s) {
|
||||
Draggables.removeObserver(s.element);
|
||||
s.droppables.each(function(d){ Droppables.remove(d) });
|
||||
s.draggables.invoke('destroy');
|
||||
|
||||
delete Sortable.sortables[s.element.id];
|
||||
}
|
||||
},
|
||||
|
||||
create: function(element) {
|
||||
element = $(element);
|
||||
var options = Object.extend({
|
||||
element: element,
|
||||
tag: 'li', // assumes li children, override with tag: 'tagname'
|
||||
dropOnEmpty: false,
|
||||
tree: false,
|
||||
treeTag: 'ul',
|
||||
overlap: 'vertical', // one of 'vertical', 'horizontal'
|
||||
constraint: 'vertical', // one of 'vertical', 'horizontal', false
|
||||
containment: element, // also takes array of elements (or id's); or false
|
||||
handle: false, // or a CSS class
|
||||
only: false,
|
||||
hoverclass: null,
|
||||
ghosting: false,
|
||||
scroll: false,
|
||||
scrollSensitivity: 20,
|
||||
scrollSpeed: 15,
|
||||
format: /^[^_]*_(.*)$/,
|
||||
onChange: Prototype.emptyFunction,
|
||||
onUpdate: Prototype.emptyFunction
|
||||
}, arguments[1] || {});
|
||||
|
||||
// clear any old sortable with same element
|
||||
this.destroy(element);
|
||||
|
||||
// build options for the draggables
|
||||
var options_for_draggable = {
|
||||
revert: true,
|
||||
scroll: options.scroll,
|
||||
scrollSpeed: options.scrollSpeed,
|
||||
scrollSensitivity: options.scrollSensitivity,
|
||||
ghosting: options.ghosting,
|
||||
constraint: options.constraint,
|
||||
handle: options.handle };
|
||||
|
||||
if(options.starteffect)
|
||||
options_for_draggable.starteffect = options.starteffect;
|
||||
|
||||
if(options.reverteffect)
|
||||
options_for_draggable.reverteffect = options.reverteffect;
|
||||
else
|
||||
if(options.ghosting) options_for_draggable.reverteffect = function(element) {
|
||||
element.style.top = 0;
|
||||
element.style.left = 0;
|
||||
};
|
||||
|
||||
if(options.endeffect)
|
||||
options_for_draggable.endeffect = options.endeffect;
|
||||
|
||||
if(options.zindex)
|
||||
options_for_draggable.zindex = options.zindex;
|
||||
|
||||
// build options for the droppables
|
||||
var options_for_droppable = {
|
||||
overlap: options.overlap,
|
||||
containment: options.containment,
|
||||
tree: options.tree,
|
||||
hoverclass: options.hoverclass,
|
||||
onHover: Sortable.onHover
|
||||
//greedy: !options.dropOnEmpty
|
||||
}
|
||||
|
||||
var options_for_tree = {
|
||||
onHover: Sortable.onEmptyHover,
|
||||
overlap: options.overlap,
|
||||
containment: options.containment,
|
||||
hoverclass: options.hoverclass
|
||||
}
|
||||
|
||||
// fix for gecko engine
|
||||
Element.cleanWhitespace(element);
|
||||
|
||||
options.draggables = [];
|
||||
options.droppables = [];
|
||||
|
||||
// drop on empty handling
|
||||
if(options.dropOnEmpty || options.tree) {
|
||||
Droppables.add(element, options_for_tree);
|
||||
options.droppables.push(element);
|
||||
}
|
||||
|
||||
(this.findElements(element, options) || []).each( function(e) {
|
||||
// handles are per-draggable
|
||||
var handle = options.handle ?
|
||||
Element.childrenWithClassName(e, options.handle)[0] : e;
|
||||
options.draggables.push(
|
||||
new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
|
||||
Droppables.add(e, options_for_droppable);
|
||||
if(options.tree) e.treeNode = element;
|
||||
options.droppables.push(e);
|
||||
});
|
||||
|
||||
if(options.tree) {
|
||||
(Sortable.findTreeElements(element, options) || []).each( function(e) {
|
||||
Droppables.add(e, options_for_tree);
|
||||
e.treeNode = element;
|
||||
options.droppables.push(e);
|
||||
});
|
||||
}
|
||||
|
||||
// keep reference
|
||||
this.sortables[element.id] = options;
|
||||
|
||||
// for onupdate
|
||||
Draggables.addObserver(new SortableObserver(element, options.onUpdate));
|
||||
|
||||
},
|
||||
|
||||
// return all suitable-for-sortable elements in a guaranteed order
|
||||
findElements: function(element, options) {
|
||||
return Element.findChildren(
|
||||
element, options.only, options.tree ? true : false, options.tag);
|
||||
},
|
||||
|
||||
findTreeElements: function(element, options) {
|
||||
return Element.findChildren(
|
||||
element, options.only, options.tree ? true : false, options.treeTag);
|
||||
},
|
||||
|
||||
onHover: function(element, dropon, overlap) {
|
||||
if(Element.isParent(dropon, element)) return;
|
||||
|
||||
if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
|
||||
return;
|
||||
} else if(overlap>0.5) {
|
||||
Sortable.mark(dropon, 'before');
|
||||
if(dropon.previousSibling != element) {
|
||||
var oldParentNode = element.parentNode;
|
||||
element.style.visibility = "hidden"; // fix gecko rendering
|
||||
dropon.parentNode.insertBefore(element, dropon);
|
||||
if(dropon.parentNode!=oldParentNode)
|
||||
Sortable.options(oldParentNode).onChange(element);
|
||||
Sortable.options(dropon.parentNode).onChange(element);
|
||||
}
|
||||
} else {
|
||||
Sortable.mark(dropon, 'after');
|
||||
var nextElement = dropon.nextSibling || null;
|
||||
if(nextElement != element) {
|
||||
var oldParentNode = element.parentNode;
|
||||
element.style.visibility = "hidden"; // fix gecko rendering
|
||||
dropon.parentNode.insertBefore(element, nextElement);
|
||||
if(dropon.parentNode!=oldParentNode)
|
||||
Sortable.options(oldParentNode).onChange(element);
|
||||
Sortable.options(dropon.parentNode).onChange(element);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onEmptyHover: function(element, dropon, overlap) {
|
||||
var oldParentNode = element.parentNode;
|
||||
var droponOptions = Sortable.options(dropon);
|
||||
|
||||
if(!Element.isParent(dropon, element)) {
|
||||
var index;
|
||||
|
||||
var children = Sortable.findElements(dropon, {tag: droponOptions.tag});
|
||||
var child = null;
|
||||
|
||||
if(children) {
|
||||
var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
|
||||
|
||||
for (index = 0; index < children.length; index += 1) {
|
||||
if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
|
||||
offset -= Element.offsetSize (children[index], droponOptions.overlap);
|
||||
} else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
|
||||
child = index + 1 < children.length ? children[index + 1] : null;
|
||||
break;
|
||||
} else {
|
||||
child = children[index];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dropon.insertBefore(element, child);
|
||||
|
||||
Sortable.options(oldParentNode).onChange(element);
|
||||
droponOptions.onChange(element);
|
||||
}
|
||||
},
|
||||
|
||||
unmark: function() {
|
||||
if(Sortable._marker) Element.hide(Sortable._marker);
|
||||
},
|
||||
|
||||
mark: function(dropon, position) {
|
||||
// mark on ghosting only
|
||||
var sortable = Sortable.options(dropon.parentNode);
|
||||
if(sortable && !sortable.ghosting) return;
|
||||
|
||||
if(!Sortable._marker) {
|
||||
Sortable._marker = $('dropmarker') || document.createElement('DIV');
|
||||
Element.hide(Sortable._marker);
|
||||
Element.addClassName(Sortable._marker, 'dropmarker');
|
||||
Sortable._marker.style.position = 'absolute';
|
||||
document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
|
||||
}
|
||||
var offsets = Position.cumulativeOffset(dropon);
|
||||
Sortable._marker.style.left = offsets[0] + 'px';
|
||||
Sortable._marker.style.top = offsets[1] + 'px';
|
||||
|
||||
if(position=='after')
|
||||
if(sortable.overlap == 'horizontal')
|
||||
Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px';
|
||||
else
|
||||
Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
|
||||
|
||||
Element.show(Sortable._marker);
|
||||
},
|
||||
|
||||
_tree: function(element, options, parent) {
|
||||
var children = Sortable.findElements(element, options) || [];
|
||||
|
||||
for (var i = 0; i < children.length; ++i) {
|
||||
var match = children[i].id.match(options.format);
|
||||
|
||||
if (!match) continue;
|
||||
|
||||
var child = {
|
||||
id: encodeURIComponent(match ? match[1] : null),
|
||||
element: element,
|
||||
parent: parent,
|
||||
children: new Array,
|
||||
position: parent.children.length,
|
||||
container: Sortable._findChildrenElement(children[i], options.treeTag.toUpperCase())
|
||||
}
|
||||
|
||||
/* Get the element containing the children and recurse over it */
|
||||
if (child.container)
|
||||
this._tree(child.container, options, child)
|
||||
|
||||
parent.children.push (child);
|
||||
}
|
||||
|
||||
return parent;
|
||||
},
|
||||
|
||||
/* Finds the first element of the given tag type within a parent element.
|
||||
Used for finding the first LI[ST] within a L[IST]I[TEM].*/
|
||||
_findChildrenElement: function (element, containerTag) {
|
||||
if (element && element.hasChildNodes)
|
||||
for (var i = 0; i < element.childNodes.length; ++i)
|
||||
if (element.childNodes[i].tagName == containerTag)
|
||||
return element.childNodes[i];
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
tree: function(element) {
|
||||
element = $(element);
|
||||
var sortableOptions = this.options(element);
|
||||
var options = Object.extend({
|
||||
tag: sortableOptions.tag,
|
||||
treeTag: sortableOptions.treeTag,
|
||||
only: sortableOptions.only,
|
||||
name: element.id,
|
||||
format: sortableOptions.format
|
||||
}, arguments[1] || {});
|
||||
|
||||
var root = {
|
||||
id: null,
|
||||
parent: null,
|
||||
children: new Array,
|
||||
container: element,
|
||||
position: 0
|
||||
}
|
||||
|
||||
return Sortable._tree (element, options, root);
|
||||
},
|
||||
|
||||
/* Construct a [i] index for a particular node */
|
||||
_constructIndex: function(node) {
|
||||
var index = '';
|
||||
do {
|
||||
if (node.id) index = '[' + node.position + ']' + index;
|
||||
} while ((node = node.parent) != null);
|
||||
return index;
|
||||
},
|
||||
|
||||
sequence: function(element) {
|
||||
element = $(element);
|
||||
var options = Object.extend(this.options(element), arguments[1] || {});
|
||||
|
||||
return $(this.findElements(element, options) || []).map( function(item) {
|
||||
return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
|
||||
});
|
||||
},
|
||||
|
||||
setSequence: function(element, new_sequence) {
|
||||
element = $(element);
|
||||
var options = Object.extend(this.options(element), arguments[2] || {});
|
||||
|
||||
var nodeMap = {};
|
||||
this.findElements(element, options).each( function(n) {
|
||||
if (n.id.match(options.format))
|
||||
nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
|
||||
n.parentNode.removeChild(n);
|
||||
});
|
||||
|
||||
new_sequence.each(function(ident) {
|
||||
var n = nodeMap[ident];
|
||||
if (n) {
|
||||
n[1].appendChild(n[0]);
|
||||
delete nodeMap[ident];
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
serialize: function(element) {
|
||||
element = $(element);
|
||||
var options = Object.extend(Sortable.options(element), arguments[1] || {});
|
||||
var name = encodeURIComponent(
|
||||
(arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
|
||||
|
||||
if (options.tree) {
|
||||
return Sortable.tree(element, arguments[1]).children.map( function (item) {
|
||||
return [name + Sortable._constructIndex(item) + "=" +
|
||||
encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
|
||||
}).flatten().join('&');
|
||||
} else {
|
||||
return Sortable.sequence(element, arguments[1]).map( function(item) {
|
||||
return name + "[]=" + encodeURIComponent(item);
|
||||
}).join('&');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns true if child is contained within element */
|
||||
Element.isParent = function(child, element) {
|
||||
if (!child.parentNode || child == element) return false;
|
||||
|
||||
if (child.parentNode == element) return true;
|
||||
|
||||
return Element.isParent(child.parentNode, element);
|
||||
}
|
||||
|
||||
Element.findChildren = function(element, only, recursive, tagName) {
|
||||
if(!element.hasChildNodes()) return null;
|
||||
tagName = tagName.toUpperCase();
|
||||
if(only) only = [only].flatten();
|
||||
var elements = [];
|
||||
$A(element.childNodes).each( function(e) {
|
||||
if(e.tagName && e.tagName.toUpperCase()==tagName &&
|
||||
(!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
|
||||
elements.push(e);
|
||||
if(recursive) {
|
||||
var grandchildren = Element.findChildren(e, only, recursive, tagName);
|
||||
if(grandchildren) elements.push(grandchildren);
|
||||
}
|
||||
});
|
||||
|
||||
return (elements.length>0 ? elements.flatten() : []);
|
||||
}
|
||||
|
||||
Element.offsetSize = function (element, type) {
|
||||
if (type == 'vertical' || type == 'height')
|
||||
return element.offsetHeight;
|
||||
else
|
||||
return element.offsetWidth;
|
||||
}
|
||||
958
vendor/plugins/asset_packager/test/assets/javascripts/effects.js
vendored
Executable file
958
vendor/plugins/asset_packager/test/assets/javascripts/effects.js
vendored
Executable file
|
|
@ -0,0 +1,958 @@
|
|||
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// Contributors:
|
||||
// Justin Palmer (http://encytemedia.com/)
|
||||
// Mark Pilgrim (http://diveintomark.org/)
|
||||
// Martin Bialasinki
|
||||
//
|
||||
// See scriptaculous.js for full license.
|
||||
|
||||
// converts rgb() and #xxx to #xxxxxx format,
|
||||
// returns self (or first argument) if not convertable
|
||||
String.prototype.parseColor = function() {
|
||||
var color = '#';
|
||||
if(this.slice(0,4) == 'rgb(') {
|
||||
var cols = this.slice(4,this.length-1).split(',');
|
||||
var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
|
||||
} else {
|
||||
if(this.slice(0,1) == '#') {
|
||||
if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
|
||||
if(this.length==7) color = this.toLowerCase();
|
||||
}
|
||||
}
|
||||
return(color.length==7 ? color : (arguments[0] || this));
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
Element.collectTextNodes = function(element) {
|
||||
return $A($(element).childNodes).collect( function(node) {
|
||||
return (node.nodeType==3 ? node.nodeValue :
|
||||
(node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
|
||||
}).flatten().join('');
|
||||
}
|
||||
|
||||
Element.collectTextNodesIgnoreClass = function(element, className) {
|
||||
return $A($(element).childNodes).collect( function(node) {
|
||||
return (node.nodeType==3 ? node.nodeValue :
|
||||
((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
|
||||
Element.collectTextNodesIgnoreClass(node, className) : ''));
|
||||
}).flatten().join('');
|
||||
}
|
||||
|
||||
Element.setContentZoom = function(element, percent) {
|
||||
element = $(element);
|
||||
Element.setStyle(element, {fontSize: (percent/100) + 'em'});
|
||||
if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
|
||||
}
|
||||
|
||||
Element.getOpacity = function(element){
|
||||
var opacity;
|
||||
if (opacity = Element.getStyle(element, 'opacity'))
|
||||
return parseFloat(opacity);
|
||||
if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/))
|
||||
if(opacity[1]) return parseFloat(opacity[1]) / 100;
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
Element.setOpacity = function(element, value){
|
||||
element= $(element);
|
||||
if (value == 1){
|
||||
Element.setStyle(element, { opacity:
|
||||
(/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ?
|
||||
0.999999 : null });
|
||||
if(/MSIE/.test(navigator.userAgent))
|
||||
Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});
|
||||
} else {
|
||||
if(value < 0.00001) value = 0;
|
||||
Element.setStyle(element, {opacity: value});
|
||||
if(/MSIE/.test(navigator.userAgent))
|
||||
Element.setStyle(element,
|
||||
{ filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
|
||||
'alpha(opacity='+value*100+')' });
|
||||
}
|
||||
}
|
||||
|
||||
Element.getInlineOpacity = function(element){
|
||||
return $(element).style.opacity || '';
|
||||
}
|
||||
|
||||
Element.childrenWithClassName = function(element, className, findFirst) {
|
||||
var classNameRegExp = new RegExp("(^|\\s)" + className + "(\\s|$)");
|
||||
var results = $A($(element).getElementsByTagName('*'))[findFirst ? 'detect' : 'select']( function(c) {
|
||||
return (c.className && c.className.match(classNameRegExp));
|
||||
});
|
||||
if(!results) results = [];
|
||||
return results;
|
||||
}
|
||||
|
||||
Element.forceRerendering = function(element) {
|
||||
try {
|
||||
element = $(element);
|
||||
var n = document.createTextNode(' ');
|
||||
element.appendChild(n);
|
||||
element.removeChild(n);
|
||||
} catch(e) { }
|
||||
};
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
Array.prototype.call = function() {
|
||||
var args = arguments;
|
||||
this.each(function(f){ f.apply(this, args) });
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
var Effect = {
|
||||
tagifyText: function(element) {
|
||||
var tagifyStyle = 'position:relative';
|
||||
if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1';
|
||||
element = $(element);
|
||||
$A(element.childNodes).each( function(child) {
|
||||
if(child.nodeType==3) {
|
||||
child.nodeValue.toArray().each( function(character) {
|
||||
element.insertBefore(
|
||||
Builder.node('span',{style: tagifyStyle},
|
||||
character == ' ' ? String.fromCharCode(160) : character),
|
||||
child);
|
||||
});
|
||||
Element.remove(child);
|
||||
}
|
||||
});
|
||||
},
|
||||
multiple: function(element, effect) {
|
||||
var elements;
|
||||
if(((typeof element == 'object') ||
|
||||
(typeof element == 'function')) &&
|
||||
(element.length))
|
||||
elements = element;
|
||||
else
|
||||
elements = $(element).childNodes;
|
||||
|
||||
var options = Object.extend({
|
||||
speed: 0.1,
|
||||
delay: 0.0
|
||||
}, arguments[2] || {});
|
||||
var masterDelay = options.delay;
|
||||
|
||||
$A(elements).each( function(element, index) {
|
||||
new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
|
||||
});
|
||||
},
|
||||
PAIRS: {
|
||||
'slide': ['SlideDown','SlideUp'],
|
||||
'blind': ['BlindDown','BlindUp'],
|
||||
'appear': ['Appear','Fade']
|
||||
},
|
||||
toggle: function(element, effect) {
|
||||
element = $(element);
|
||||
effect = (effect || 'appear').toLowerCase();
|
||||
var options = Object.extend({
|
||||
queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
|
||||
}, arguments[2] || {});
|
||||
Effect[element.visible() ?
|
||||
Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
|
||||
}
|
||||
};
|
||||
|
||||
var Effect2 = Effect; // deprecated
|
||||
|
||||
/* ------------- transitions ------------- */
|
||||
|
||||
Effect.Transitions = {}
|
||||
|
||||
Effect.Transitions.linear = function(pos) {
|
||||
return pos;
|
||||
}
|
||||
Effect.Transitions.sinoidal = function(pos) {
|
||||
return (-Math.cos(pos*Math.PI)/2) + 0.5;
|
||||
}
|
||||
Effect.Transitions.reverse = function(pos) {
|
||||
return 1-pos;
|
||||
}
|
||||
Effect.Transitions.flicker = function(pos) {
|
||||
return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
|
||||
}
|
||||
Effect.Transitions.wobble = function(pos) {
|
||||
return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
|
||||
}
|
||||
Effect.Transitions.pulse = function(pos) {
|
||||
return (Math.floor(pos*10) % 2 == 0 ?
|
||||
(pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
|
||||
}
|
||||
Effect.Transitions.none = function(pos) {
|
||||
return 0;
|
||||
}
|
||||
Effect.Transitions.full = function(pos) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ------------- core effects ------------- */
|
||||
|
||||
Effect.ScopedQueue = Class.create();
|
||||
Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
|
||||
initialize: function() {
|
||||
this.effects = [];
|
||||
this.interval = null;
|
||||
},
|
||||
_each: function(iterator) {
|
||||
this.effects._each(iterator);
|
||||
},
|
||||
add: function(effect) {
|
||||
var timestamp = new Date().getTime();
|
||||
|
||||
var position = (typeof effect.options.queue == 'string') ?
|
||||
effect.options.queue : effect.options.queue.position;
|
||||
|
||||
switch(position) {
|
||||
case 'front':
|
||||
// move unstarted effects after this effect
|
||||
this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
|
||||
e.startOn += effect.finishOn;
|
||||
e.finishOn += effect.finishOn;
|
||||
});
|
||||
break;
|
||||
case 'end':
|
||||
// start effect after last queued effect has finished
|
||||
timestamp = this.effects.pluck('finishOn').max() || timestamp;
|
||||
break;
|
||||
}
|
||||
|
||||
effect.startOn += timestamp;
|
||||
effect.finishOn += timestamp;
|
||||
|
||||
if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
|
||||
this.effects.push(effect);
|
||||
|
||||
if(!this.interval)
|
||||
this.interval = setInterval(this.loop.bind(this), 40);
|
||||
},
|
||||
remove: function(effect) {
|
||||
this.effects = this.effects.reject(function(e) { return e==effect });
|
||||
if(this.effects.length == 0) {
|
||||
clearInterval(this.interval);
|
||||
this.interval = null;
|
||||
}
|
||||
},
|
||||
loop: function() {
|
||||
var timePos = new Date().getTime();
|
||||
this.effects.invoke('loop', timePos);
|
||||
}
|
||||
});
|
||||
|
||||
Effect.Queues = {
|
||||
instances: $H(),
|
||||
get: function(queueName) {
|
||||
if(typeof queueName != 'string') return queueName;
|
||||
|
||||
if(!this.instances[queueName])
|
||||
this.instances[queueName] = new Effect.ScopedQueue();
|
||||
|
||||
return this.instances[queueName];
|
||||
}
|
||||
}
|
||||
Effect.Queue = Effect.Queues.get('global');
|
||||
|
||||
Effect.DefaultOptions = {
|
||||
transition: Effect.Transitions.sinoidal,
|
||||
duration: 1.0, // seconds
|
||||
fps: 25.0, // max. 25fps due to Effect.Queue implementation
|
||||
sync: false, // true for combining
|
||||
from: 0.0,
|
||||
to: 1.0,
|
||||
delay: 0.0,
|
||||
queue: 'parallel'
|
||||
}
|
||||
|
||||
Effect.Base = function() {};
|
||||
Effect.Base.prototype = {
|
||||
position: null,
|
||||
start: function(options) {
|
||||
this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {});
|
||||
this.currentFrame = 0;
|
||||
this.state = 'idle';
|
||||
this.startOn = this.options.delay*1000;
|
||||
this.finishOn = this.startOn + (this.options.duration*1000);
|
||||
this.event('beforeStart');
|
||||
if(!this.options.sync)
|
||||
Effect.Queues.get(typeof this.options.queue == 'string' ?
|
||||
'global' : this.options.queue.scope).add(this);
|
||||
},
|
||||
loop: function(timePos) {
|
||||
if(timePos >= this.startOn) {
|
||||
if(timePos >= this.finishOn) {
|
||||
this.render(1.0);
|
||||
this.cancel();
|
||||
this.event('beforeFinish');
|
||||
if(this.finish) this.finish();
|
||||
this.event('afterFinish');
|
||||
return;
|
||||
}
|
||||
var pos = (timePos - this.startOn) / (this.finishOn - this.startOn);
|
||||
var frame = Math.round(pos * this.options.fps * this.options.duration);
|
||||
if(frame > this.currentFrame) {
|
||||
this.render(pos);
|
||||
this.currentFrame = frame;
|
||||
}
|
||||
}
|
||||
},
|
||||
render: function(pos) {
|
||||
if(this.state == 'idle') {
|
||||
this.state = 'running';
|
||||
this.event('beforeSetup');
|
||||
if(this.setup) this.setup();
|
||||
this.event('afterSetup');
|
||||
}
|
||||
if(this.state == 'running') {
|
||||
if(this.options.transition) pos = this.options.transition(pos);
|
||||
pos *= (this.options.to-this.options.from);
|
||||
pos += this.options.from;
|
||||
this.position = pos;
|
||||
this.event('beforeUpdate');
|
||||
if(this.update) this.update(pos);
|
||||
this.event('afterUpdate');
|
||||
}
|
||||
},
|
||||
cancel: function() {
|
||||
if(!this.options.sync)
|
||||
Effect.Queues.get(typeof this.options.queue == 'string' ?
|
||||
'global' : this.options.queue.scope).remove(this);
|
||||
this.state = 'finished';
|
||||
},
|
||||
event: function(eventName) {
|
||||
if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
|
||||
if(this.options[eventName]) this.options[eventName](this);
|
||||
},
|
||||
inspect: function() {
|
||||
return '#<Effect:' + $H(this).inspect() + ',options:' + $H(this.options).inspect() + '>';
|
||||
}
|
||||
}
|
||||
|
||||
Effect.Parallel = Class.create();
|
||||
Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
|
||||
initialize: function(effects) {
|
||||
this.effects = effects || [];
|
||||
this.start(arguments[1]);
|
||||
},
|
||||
update: function(position) {
|
||||
this.effects.invoke('render', position);
|
||||
},
|
||||
finish: function(position) {
|
||||
this.effects.each( function(effect) {
|
||||
effect.render(1.0);
|
||||
effect.cancel();
|
||||
effect.event('beforeFinish');
|
||||
if(effect.finish) effect.finish(position);
|
||||
effect.event('afterFinish');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Effect.Opacity = Class.create();
|
||||
Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
|
||||
initialize: function(element) {
|
||||
this.element = $(element);
|
||||
// make this work on IE on elements without 'layout'
|
||||
if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout))
|
||||
this.element.setStyle({zoom: 1});
|
||||
var options = Object.extend({
|
||||
from: this.element.getOpacity() || 0.0,
|
||||
to: 1.0
|
||||
}, arguments[1] || {});
|
||||
this.start(options);
|
||||
},
|
||||
update: function(position) {
|
||||
this.element.setOpacity(position);
|
||||
}
|
||||
});
|
||||
|
||||
Effect.Move = Class.create();
|
||||
Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
|
||||
initialize: function(element) {
|
||||
this.element = $(element);
|
||||
var options = Object.extend({
|
||||
x: 0,
|
||||
y: 0,
|
||||
mode: 'relative'
|
||||
}, arguments[1] || {});
|
||||
this.start(options);
|
||||
},
|
||||
setup: function() {
|
||||
// Bug in Opera: Opera returns the "real" position of a static element or
|
||||
// relative element that does not have top/left explicitly set.
|
||||
// ==> Always set top and left for position relative elements in your stylesheets
|
||||
// (to 0 if you do not need them)
|
||||
this.element.makePositioned();
|
||||
this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
|
||||
this.originalTop = parseFloat(this.element.getStyle('top') || '0');
|
||||
if(this.options.mode == 'absolute') {
|
||||
// absolute movement, so we need to calc deltaX and deltaY
|
||||
this.options.x = this.options.x - this.originalLeft;
|
||||
this.options.y = this.options.y - this.originalTop;
|
||||
}
|
||||
},
|
||||
update: function(position) {
|
||||
this.element.setStyle({
|
||||
left: this.options.x * position + this.originalLeft + 'px',
|
||||
top: this.options.y * position + this.originalTop + 'px'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// for backwards compatibility
|
||||
Effect.MoveBy = function(element, toTop, toLeft) {
|
||||
return new Effect.Move(element,
|
||||
Object.extend({ x: toLeft, y: toTop }, arguments[3] || {}));
|
||||
};
|
||||
|
||||
Effect.Scale = Class.create();
|
||||
Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
|
||||
initialize: function(element, percent) {
|
||||
this.element = $(element)
|
||||
var options = Object.extend({
|
||||
scaleX: true,
|
||||
scaleY: true,
|
||||
scaleContent: true,
|
||||
scaleFromCenter: false,
|
||||
scaleMode: 'box', // 'box' or 'contents' or {} with provided values
|
||||
scaleFrom: 100.0,
|
||||
scaleTo: percent
|
||||
}, arguments[2] || {});
|
||||
this.start(options);
|
||||
},
|
||||
setup: function() {
|
||||
this.restoreAfterFinish = this.options.restoreAfterFinish || false;
|
||||
this.elementPositioning = this.element.getStyle('position');
|
||||
|
||||
this.originalStyle = {};
|
||||
['top','left','width','height','fontSize'].each( function(k) {
|
||||
this.originalStyle[k] = this.element.style[k];
|
||||
}.bind(this));
|
||||
|
||||
this.originalTop = this.element.offsetTop;
|
||||
this.originalLeft = this.element.offsetLeft;
|
||||
|
||||
var fontSize = this.element.getStyle('font-size') || '100%';
|
||||
['em','px','%'].each( function(fontSizeType) {
|
||||
if(fontSize.indexOf(fontSizeType)>0) {
|
||||
this.fontSize = parseFloat(fontSize);
|
||||
this.fontSizeType = fontSizeType;
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
|
||||
|
||||
this.dims = null;
|
||||
if(this.options.scaleMode=='box')
|
||||
this.dims = [this.element.offsetHeight, this.element.offsetWidth];
|
||||
if(/^content/.test(this.options.scaleMode))
|
||||
this.dims = [this.element.scrollHeight, this.element.scrollWidth];
|
||||
if(!this.dims)
|
||||
this.dims = [this.options.scaleMode.originalHeight,
|
||||
this.options.scaleMode.originalWidth];
|
||||
},
|
||||
update: function(position) {
|
||||
var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
|
||||
if(this.options.scaleContent && this.fontSize)
|
||||
this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
|
||||
this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
|
||||
},
|
||||
finish: function(position) {
|
||||
if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
|
||||
},
|
||||
setDimensions: function(height, width) {
|
||||
var d = {};
|
||||
if(this.options.scaleX) d.width = width + 'px';
|
||||
if(this.options.scaleY) d.height = height + 'px';
|
||||
if(this.options.scaleFromCenter) {
|
||||
var topd = (height - this.dims[0])/2;
|
||||
var leftd = (width - this.dims[1])/2;
|
||||
if(this.elementPositioning == 'absolute') {
|
||||
if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
|
||||
if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
|
||||
} else {
|
||||
if(this.options.scaleY) d.top = -topd + 'px';
|
||||
if(this.options.scaleX) d.left = -leftd + 'px';
|
||||
}
|
||||
}
|
||||
this.element.setStyle(d);
|
||||
}
|
||||
});
|
||||
|
||||
Effect.Highlight = Class.create();
|
||||
Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
|
||||
initialize: function(element) {
|
||||
this.element = $(element);
|
||||
var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
|
||||
this.start(options);
|
||||
},
|
||||
setup: function() {
|
||||
// Prevent executing on elements not in the layout flow
|
||||
if(this.element.getStyle('display')=='none') { this.cancel(); return; }
|
||||
// Disable background image during the effect
|
||||
this.oldStyle = {
|
||||
backgroundImage: this.element.getStyle('background-image') };
|
||||
this.element.setStyle({backgroundImage: 'none'});
|
||||
if(!this.options.endcolor)
|
||||
this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
|
||||
if(!this.options.restorecolor)
|
||||
this.options.restorecolor = this.element.getStyle('background-color');
|
||||
// init color calculations
|
||||
this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
|
||||
this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
|
||||
},
|
||||
update: function(position) {
|
||||
this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
|
||||
return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
|
||||
},
|
||||
finish: function() {
|
||||
this.element.setStyle(Object.extend(this.oldStyle, {
|
||||
backgroundColor: this.options.restorecolor
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
Effect.ScrollTo = Class.create();
|
||||
Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
|
||||
initialize: function(element) {
|
||||
this.element = $(element);
|
||||
this.start(arguments[1] || {});
|
||||
},
|
||||
setup: function() {
|
||||
Position.prepare();
|
||||
var offsets = Position.cumulativeOffset(this.element);
|
||||
if(this.options.offset) offsets[1] += this.options.offset;
|
||||
var max = window.innerHeight ?
|
||||
window.height - window.innerHeight :
|
||||
document.body.scrollHeight -
|
||||
(document.documentElement.clientHeight ?
|
||||
document.documentElement.clientHeight : document.body.clientHeight);
|
||||
this.scrollStart = Position.deltaY;
|
||||
this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
|
||||
},
|
||||
update: function(position) {
|
||||
Position.prepare();
|
||||
window.scrollTo(Position.deltaX,
|
||||
this.scrollStart + (position*this.delta));
|
||||
}
|
||||
});
|
||||
|
||||
/* ------------- combination effects ------------- */
|
||||
|
||||
Effect.Fade = function(element) {
|
||||
element = $(element);
|
||||
var oldOpacity = element.getInlineOpacity();
|
||||
var options = Object.extend({
|
||||
from: element.getOpacity() || 1.0,
|
||||
to: 0.0,
|
||||
afterFinishInternal: function(effect) {
|
||||
if(effect.options.to!=0) return;
|
||||
effect.element.hide();
|
||||
effect.element.setStyle({opacity: oldOpacity});
|
||||
}}, arguments[1] || {});
|
||||
return new Effect.Opacity(element,options);
|
||||
}
|
||||
|
||||
Effect.Appear = function(element) {
|
||||
element = $(element);
|
||||
var options = Object.extend({
|
||||
from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
|
||||
to: 1.0,
|
||||
// force Safari to render floated elements properly
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.forceRerendering();
|
||||
},
|
||||
beforeSetup: function(effect) {
|
||||
effect.element.setOpacity(effect.options.from);
|
||||
effect.element.show();
|
||||
}}, arguments[1] || {});
|
||||
return new Effect.Opacity(element,options);
|
||||
}
|
||||
|
||||
Effect.Puff = function(element) {
|
||||
element = $(element);
|
||||
var oldStyle = { opacity: element.getInlineOpacity(), position: element.getStyle('position') };
|
||||
return new Effect.Parallel(
|
||||
[ new Effect.Scale(element, 200,
|
||||
{ sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
|
||||
new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
|
||||
Object.extend({ duration: 1.0,
|
||||
beforeSetupInternal: function(effect) {
|
||||
effect.effects[0].element.setStyle({position: 'absolute'}); },
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.effects[0].element.hide();
|
||||
effect.effects[0].element.setStyle(oldStyle); }
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
}
|
||||
|
||||
Effect.BlindUp = function(element) {
|
||||
element = $(element);
|
||||
element.makeClipping();
|
||||
return new Effect.Scale(element, 0,
|
||||
Object.extend({ scaleContent: false,
|
||||
scaleX: false,
|
||||
restoreAfterFinish: true,
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.hide();
|
||||
effect.element.undoClipping();
|
||||
}
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
}
|
||||
|
||||
Effect.BlindDown = function(element) {
|
||||
element = $(element);
|
||||
var elementDimensions = element.getDimensions();
|
||||
return new Effect.Scale(element, 100,
|
||||
Object.extend({ scaleContent: false,
|
||||
scaleX: false,
|
||||
scaleFrom: 0,
|
||||
scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
|
||||
restoreAfterFinish: true,
|
||||
afterSetup: function(effect) {
|
||||
effect.element.makeClipping();
|
||||
effect.element.setStyle({height: '0px'});
|
||||
effect.element.show();
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.undoClipping();
|
||||
}
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
}
|
||||
|
||||
Effect.SwitchOff = function(element) {
|
||||
element = $(element);
|
||||
var oldOpacity = element.getInlineOpacity();
|
||||
return new Effect.Appear(element, {
|
||||
duration: 0.4,
|
||||
from: 0,
|
||||
transition: Effect.Transitions.flicker,
|
||||
afterFinishInternal: function(effect) {
|
||||
new Effect.Scale(effect.element, 1, {
|
||||
duration: 0.3, scaleFromCenter: true,
|
||||
scaleX: false, scaleContent: false, restoreAfterFinish: true,
|
||||
beforeSetup: function(effect) {
|
||||
effect.element.makePositioned();
|
||||
effect.element.makeClipping();
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.hide();
|
||||
effect.element.undoClipping();
|
||||
effect.element.undoPositioned();
|
||||
effect.element.setStyle({opacity: oldOpacity});
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Effect.DropOut = function(element) {
|
||||
element = $(element);
|
||||
var oldStyle = {
|
||||
top: element.getStyle('top'),
|
||||
left: element.getStyle('left'),
|
||||
opacity: element.getInlineOpacity() };
|
||||
return new Effect.Parallel(
|
||||
[ new Effect.Move(element, {x: 0, y: 100, sync: true }),
|
||||
new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
|
||||
Object.extend(
|
||||
{ duration: 0.5,
|
||||
beforeSetup: function(effect) {
|
||||
effect.effects[0].element.makePositioned();
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.effects[0].element.hide();
|
||||
effect.effects[0].element.undoPositioned();
|
||||
effect.effects[0].element.setStyle(oldStyle);
|
||||
}
|
||||
}, arguments[1] || {}));
|
||||
}
|
||||
|
||||
Effect.Shake = function(element) {
|
||||
element = $(element);
|
||||
var oldStyle = {
|
||||
top: element.getStyle('top'),
|
||||
left: element.getStyle('left') };
|
||||
return new Effect.Move(element,
|
||||
{ x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
|
||||
new Effect.Move(effect.element,
|
||||
{ x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
|
||||
new Effect.Move(effect.element,
|
||||
{ x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
|
||||
new Effect.Move(effect.element,
|
||||
{ x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
|
||||
new Effect.Move(effect.element,
|
||||
{ x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
|
||||
new Effect.Move(effect.element,
|
||||
{ x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
|
||||
effect.element.undoPositioned();
|
||||
effect.element.setStyle(oldStyle);
|
||||
}}) }}) }}) }}) }}) }});
|
||||
}
|
||||
|
||||
Effect.SlideDown = function(element) {
|
||||
element = $(element);
|
||||
element.cleanWhitespace();
|
||||
// SlideDown need to have the content of the element wrapped in a container element with fixed height!
|
||||
var oldInnerBottom = $(element.firstChild).getStyle('bottom');
|
||||
var elementDimensions = element.getDimensions();
|
||||
return new Effect.Scale(element, 100, Object.extend({
|
||||
scaleContent: false,
|
||||
scaleX: false,
|
||||
scaleFrom: 0,
|
||||
scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
|
||||
restoreAfterFinish: true,
|
||||
afterSetup: function(effect) {
|
||||
effect.element.makePositioned();
|
||||
effect.element.firstChild.makePositioned();
|
||||
if(window.opera) effect.element.setStyle({top: ''});
|
||||
effect.element.makeClipping();
|
||||
effect.element.setStyle({height: '0px'});
|
||||
effect.element.show(); },
|
||||
afterUpdateInternal: function(effect) {
|
||||
effect.element.firstChild.setStyle({bottom:
|
||||
(effect.dims[0] - effect.element.clientHeight) + 'px' });
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.undoClipping();
|
||||
// IE will crash if child is undoPositioned first
|
||||
if(/MSIE/.test(navigator.userAgent)){
|
||||
effect.element.undoPositioned();
|
||||
effect.element.firstChild.undoPositioned();
|
||||
}else{
|
||||
effect.element.firstChild.undoPositioned();
|
||||
effect.element.undoPositioned();
|
||||
}
|
||||
effect.element.firstChild.setStyle({bottom: oldInnerBottom}); }
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
}
|
||||
|
||||
Effect.SlideUp = function(element) {
|
||||
element = $(element);
|
||||
element.cleanWhitespace();
|
||||
var oldInnerBottom = $(element.firstChild).getStyle('bottom');
|
||||
return new Effect.Scale(element, 0,
|
||||
Object.extend({ scaleContent: false,
|
||||
scaleX: false,
|
||||
scaleMode: 'box',
|
||||
scaleFrom: 100,
|
||||
restoreAfterFinish: true,
|
||||
beforeStartInternal: function(effect) {
|
||||
effect.element.makePositioned();
|
||||
effect.element.firstChild.makePositioned();
|
||||
if(window.opera) effect.element.setStyle({top: ''});
|
||||
effect.element.makeClipping();
|
||||
effect.element.show(); },
|
||||
afterUpdateInternal: function(effect) {
|
||||
effect.element.firstChild.setStyle({bottom:
|
||||
(effect.dims[0] - effect.element.clientHeight) + 'px' }); },
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.hide();
|
||||
effect.element.undoClipping();
|
||||
effect.element.firstChild.undoPositioned();
|
||||
effect.element.undoPositioned();
|
||||
effect.element.setStyle({bottom: oldInnerBottom}); }
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
}
|
||||
|
||||
// Bug in opera makes the TD containing this element expand for a instance after finish
|
||||
Effect.Squish = function(element) {
|
||||
return new Effect.Scale(element, window.opera ? 1 : 0,
|
||||
{ restoreAfterFinish: true,
|
||||
beforeSetup: function(effect) {
|
||||
effect.element.makeClipping(effect.element); },
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.hide(effect.element);
|
||||
effect.element.undoClipping(effect.element); }
|
||||
});
|
||||
}
|
||||
|
||||
Effect.Grow = function(element) {
|
||||
element = $(element);
|
||||
var options = Object.extend({
|
||||
direction: 'center',
|
||||
moveTransition: Effect.Transitions.sinoidal,
|
||||
scaleTransition: Effect.Transitions.sinoidal,
|
||||
opacityTransition: Effect.Transitions.full
|
||||
}, arguments[1] || {});
|
||||
var oldStyle = {
|
||||
top: element.style.top,
|
||||
left: element.style.left,
|
||||
height: element.style.height,
|
||||
width: element.style.width,
|
||||
opacity: element.getInlineOpacity() };
|
||||
|
||||
var dims = element.getDimensions();
|
||||
var initialMoveX, initialMoveY;
|
||||
var moveX, moveY;
|
||||
|
||||
switch (options.direction) {
|
||||
case 'top-left':
|
||||
initialMoveX = initialMoveY = moveX = moveY = 0;
|
||||
break;
|
||||
case 'top-right':
|
||||
initialMoveX = dims.width;
|
||||
initialMoveY = moveY = 0;
|
||||
moveX = -dims.width;
|
||||
break;
|
||||
case 'bottom-left':
|
||||
initialMoveX = moveX = 0;
|
||||
initialMoveY = dims.height;
|
||||
moveY = -dims.height;
|
||||
break;
|
||||
case 'bottom-right':
|
||||
initialMoveX = dims.width;
|
||||
initialMoveY = dims.height;
|
||||
moveX = -dims.width;
|
||||
moveY = -dims.height;
|
||||
break;
|
||||
case 'center':
|
||||
initialMoveX = dims.width / 2;
|
||||
initialMoveY = dims.height / 2;
|
||||
moveX = -dims.width / 2;
|
||||
moveY = -dims.height / 2;
|
||||
break;
|
||||
}
|
||||
|
||||
return new Effect.Move(element, {
|
||||
x: initialMoveX,
|
||||
y: initialMoveY,
|
||||
duration: 0.01,
|
||||
beforeSetup: function(effect) {
|
||||
effect.element.hide();
|
||||
effect.element.makeClipping();
|
||||
effect.element.makePositioned();
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
new Effect.Parallel(
|
||||
[ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
|
||||
new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
|
||||
new Effect.Scale(effect.element, 100, {
|
||||
scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
|
||||
sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
|
||||
], Object.extend({
|
||||
beforeSetup: function(effect) {
|
||||
effect.effects[0].element.setStyle({height: '0px'});
|
||||
effect.effects[0].element.show();
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.effects[0].element.undoClipping();
|
||||
effect.effects[0].element.undoPositioned();
|
||||
effect.effects[0].element.setStyle(oldStyle);
|
||||
}
|
||||
}, options)
|
||||
)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Effect.Shrink = function(element) {
|
||||
element = $(element);
|
||||
var options = Object.extend({
|
||||
direction: 'center',
|
||||
moveTransition: Effect.Transitions.sinoidal,
|
||||
scaleTransition: Effect.Transitions.sinoidal,
|
||||
opacityTransition: Effect.Transitions.none
|
||||
}, arguments[1] || {});
|
||||
var oldStyle = {
|
||||
top: element.style.top,
|
||||
left: element.style.left,
|
||||
height: element.style.height,
|
||||
width: element.style.width,
|
||||
opacity: element.getInlineOpacity() };
|
||||
|
||||
var dims = element.getDimensions();
|
||||
var moveX, moveY;
|
||||
|
||||
switch (options.direction) {
|
||||
case 'top-left':
|
||||
moveX = moveY = 0;
|
||||
break;
|
||||
case 'top-right':
|
||||
moveX = dims.width;
|
||||
moveY = 0;
|
||||
break;
|
||||
case 'bottom-left':
|
||||
moveX = 0;
|
||||
moveY = dims.height;
|
||||
break;
|
||||
case 'bottom-right':
|
||||
moveX = dims.width;
|
||||
moveY = dims.height;
|
||||
break;
|
||||
case 'center':
|
||||
moveX = dims.width / 2;
|
||||
moveY = dims.height / 2;
|
||||
break;
|
||||
}
|
||||
|
||||
return new Effect.Parallel(
|
||||
[ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
|
||||
new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
|
||||
new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
|
||||
], Object.extend({
|
||||
beforeStartInternal: function(effect) {
|
||||
effect.effects[0].element.makePositioned();
|
||||
effect.effects[0].element.makeClipping(); },
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.effects[0].element.hide();
|
||||
effect.effects[0].element.undoClipping();
|
||||
effect.effects[0].element.undoPositioned();
|
||||
effect.effects[0].element.setStyle(oldStyle); }
|
||||
}, options)
|
||||
);
|
||||
}
|
||||
|
||||
Effect.Pulsate = function(element) {
|
||||
element = $(element);
|
||||
var options = arguments[1] || {};
|
||||
var oldOpacity = element.getInlineOpacity();
|
||||
var transition = options.transition || Effect.Transitions.sinoidal;
|
||||
var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) };
|
||||
reverser.bind(transition);
|
||||
return new Effect.Opacity(element,
|
||||
Object.extend(Object.extend({ duration: 3.0, from: 0,
|
||||
afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
|
||||
}, options), {transition: reverser}));
|
||||
}
|
||||
|
||||
Effect.Fold = function(element) {
|
||||
element = $(element);
|
||||
var oldStyle = {
|
||||
top: element.style.top,
|
||||
left: element.style.left,
|
||||
width: element.style.width,
|
||||
height: element.style.height };
|
||||
Element.makeClipping(element);
|
||||
return new Effect.Scale(element, 5, Object.extend({
|
||||
scaleContent: false,
|
||||
scaleX: false,
|
||||
afterFinishInternal: function(effect) {
|
||||
new Effect.Scale(element, 1, {
|
||||
scaleContent: false,
|
||||
scaleY: false,
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.hide();
|
||||
effect.element.undoClipping();
|
||||
effect.element.setStyle(oldStyle);
|
||||
} });
|
||||
}}, arguments[1] || {}));
|
||||
};
|
||||
|
||||
['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom',
|
||||
'collectTextNodes','collectTextNodesIgnoreClass','childrenWithClassName'].each(
|
||||
function(f) { Element.Methods[f] = Element[f]; }
|
||||
);
|
||||
|
||||
Element.Methods.visualEffect = function(element, effect, options) {
|
||||
s = effect.gsub(/_/, '-').camelize();
|
||||
effect_class = s.charAt(0).toUpperCase() + s.substring(1);
|
||||
new Effect[effect_class](element, options);
|
||||
return $(element);
|
||||
};
|
||||
|
||||
Element.addMethods();
|
||||
4
vendor/plugins/asset_packager/test/assets/javascripts/foo.js
vendored
Executable file
4
vendor/plugins/asset_packager/test/assets/javascripts/foo.js
vendored
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
foo foo foo
|
||||
foo foo foo
|
||||
foo foo foo
|
||||
|
||||
2006
vendor/plugins/asset_packager/test/assets/javascripts/prototype.js
vendored
Executable file
2006
vendor/plugins/asset_packager/test/assets/javascripts/prototype.js
vendored
Executable file
File diff suppressed because it is too large
Load diff
16
vendor/plugins/asset_packager/test/assets/stylesheets/bar.css
vendored
Normal file
16
vendor/plugins/asset_packager/test/assets/stylesheets/bar.css
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#bar1 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#bar2 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#bar3 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
16
vendor/plugins/asset_packager/test/assets/stylesheets/foo.css
vendored
Normal file
16
vendor/plugins/asset_packager/test/assets/stylesheets/foo.css
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#foo1 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#foo2 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#foo3 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
16
vendor/plugins/asset_packager/test/assets/stylesheets/header.css
vendored
Executable file
16
vendor/plugins/asset_packager/test/assets/stylesheets/header.css
vendored
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
#header1 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#header2 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#header3 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
16
vendor/plugins/asset_packager/test/assets/stylesheets/screen.css
vendored
Executable file
16
vendor/plugins/asset_packager/test/assets/stylesheets/screen.css
vendored
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
#screen1 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#screen2 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#screen3 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
16
vendor/plugins/asset_packager/test/assets/stylesheets/subdir/bar.css
vendored
Normal file
16
vendor/plugins/asset_packager/test/assets/stylesheets/subdir/bar.css
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#bar1 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#bar2 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#bar3 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
16
vendor/plugins/asset_packager/test/assets/stylesheets/subdir/foo.css
vendored
Normal file
16
vendor/plugins/asset_packager/test/assets/stylesheets/subdir/foo.css
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#foo1 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#foo2 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#foo3 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
152
vendor/plugins/classic_pagination/CHANGELOG
vendored
Normal file
152
vendor/plugins/classic_pagination/CHANGELOG
vendored
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
* Exported the changelog of Pagination code for historical reference.
|
||||
|
||||
* Imported some patches from Rails Trac (others closed as "wontfix"):
|
||||
#8176, #7325, #7028, #4113. Documentation is much cleaner now and there
|
||||
are some new unobtrusive features!
|
||||
|
||||
* Extracted Pagination from Rails trunk (r6795)
|
||||
|
||||
#
|
||||
# ChangeLog for /trunk/actionpack/lib/action_controller/pagination.rb
|
||||
#
|
||||
# Generated by Trac 0.10.3
|
||||
# 05/20/07 23:48:02
|
||||
#
|
||||
|
||||
09/03/06 23:28:54 david [4953]
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
Docs and deprecation
|
||||
|
||||
08/07/06 12:40:14 bitsweat [4715]
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
Deprecate direct usage of @params. Update ActionView::Base for
|
||||
instance var deprecation.
|
||||
|
||||
06/21/06 02:16:11 rick [4476]
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
Fix indent in pagination documentation. Closes #4990. [Kevin Clark]
|
||||
|
||||
04/25/06 17:42:48 marcel [4268]
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
Remove all remaining references to @params in the documentation.
|
||||
|
||||
03/16/06 06:38:08 rick [3899]
|
||||
* trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified)
|
||||
trivial documentation patch for #pagination_links [Francois
|
||||
Beausoleil] closes #4258
|
||||
|
||||
02/20/06 03:15:22 david [3620]
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
* trunk/actionpack/test/activerecord/pagination_test.rb (modified)
|
||||
* trunk/activerecord/CHANGELOG (modified)
|
||||
* trunk/activerecord/lib/active_record/base.rb (modified)
|
||||
* trunk/activerecord/test/base_test.rb (modified)
|
||||
Added :count option to pagination that'll make it possible for the
|
||||
ActiveRecord::Base.count call to using something else than * for the
|
||||
count. Especially important for count queries using DISTINCT #3839
|
||||
[skaes]. Added :select option to Base.count that'll allow you to
|
||||
select something else than * to be counted on. Especially important
|
||||
for count queries using DISTINCT (closes #3839) [skaes].
|
||||
|
||||
02/09/06 09:17:40 nzkoz [3553]
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
* trunk/actionpack/test/active_record_unit.rb (added)
|
||||
* trunk/actionpack/test/activerecord (added)
|
||||
* trunk/actionpack/test/activerecord/active_record_assertions_test.rb (added)
|
||||
* trunk/actionpack/test/activerecord/pagination_test.rb (added)
|
||||
* trunk/actionpack/test/controller/active_record_assertions_test.rb (deleted)
|
||||
* trunk/actionpack/test/fixtures/companies.yml (added)
|
||||
* trunk/actionpack/test/fixtures/company.rb (added)
|
||||
* trunk/actionpack/test/fixtures/db_definitions (added)
|
||||
* trunk/actionpack/test/fixtures/db_definitions/sqlite.sql (added)
|
||||
* trunk/actionpack/test/fixtures/developer.rb (added)
|
||||
* trunk/actionpack/test/fixtures/developers_projects.yml (added)
|
||||
* trunk/actionpack/test/fixtures/developers.yml (added)
|
||||
* trunk/actionpack/test/fixtures/project.rb (added)
|
||||
* trunk/actionpack/test/fixtures/projects.yml (added)
|
||||
* trunk/actionpack/test/fixtures/replies.yml (added)
|
||||
* trunk/actionpack/test/fixtures/reply.rb (added)
|
||||
* trunk/actionpack/test/fixtures/topic.rb (added)
|
||||
* trunk/actionpack/test/fixtures/topics.yml (added)
|
||||
* Fix pagination problems when using include
|
||||
* Introduce Unit Tests for pagination
|
||||
* Allow count to work with :include by using count distinct.
|
||||
|
||||
[Kevin Clark & Jeremy Hopple]
|
||||
|
||||
11/05/05 02:10:29 bitsweat [2878]
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
Update paginator docs. Closes #2744.
|
||||
|
||||
10/16/05 15:42:03 minam [2649]
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
Update/clean up AP documentation (rdoc)
|
||||
|
||||
08/31/05 00:13:10 ulysses [2078]
|
||||
* trunk/actionpack/CHANGELOG (modified)
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
Add option to specify the singular name used by pagination. Closes
|
||||
#1960
|
||||
|
||||
08/23/05 14:24:15 minam [2041]
|
||||
* trunk/actionpack/CHANGELOG (modified)
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
Add support for :include with pagination (subject to existing
|
||||
constraints for :include with :limit and :offset) #1478
|
||||
[michael@schubert.cx]
|
||||
|
||||
07/15/05 20:27:38 david [1839]
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
* trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified)
|
||||
More pagination speed #1334 [Stefan Kaes]
|
||||
|
||||
07/14/05 08:02:01 david [1832]
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
* trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified)
|
||||
* trunk/actionpack/test/controller/addresses_render_test.rb (modified)
|
||||
Made pagination faster #1334 [Stefan Kaes]
|
||||
|
||||
04/13/05 05:40:22 david [1159]
|
||||
* trunk/actionpack/CHANGELOG (modified)
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
* trunk/activerecord/lib/active_record/base.rb (modified)
|
||||
Fixed pagination to work with joins #1034 [scott@sigkill.org]
|
||||
|
||||
04/02/05 09:11:17 david [1067]
|
||||
* trunk/actionpack/CHANGELOG (modified)
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
* trunk/actionpack/lib/action_controller/scaffolding.rb (modified)
|
||||
* trunk/actionpack/lib/action_controller/templates/scaffolds/list.rhtml (modified)
|
||||
* trunk/railties/lib/rails_generator/generators/components/scaffold/templates/controller.rb (modified)
|
||||
* trunk/railties/lib/rails_generator/generators/components/scaffold/templates/view_list.rhtml (modified)
|
||||
Added pagination for scaffolding (10 items per page) #964
|
||||
[mortonda@dgrmm.net]
|
||||
|
||||
03/31/05 14:46:11 david [1048]
|
||||
* trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified)
|
||||
Improved the message display on the exception handler pages #963
|
||||
[Johan Sorensen]
|
||||
|
||||
03/27/05 00:04:07 david [1017]
|
||||
* trunk/actionpack/CHANGELOG (modified)
|
||||
* trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified)
|
||||
Fixed that pagination_helper would ignore :params #947 [Sebastian
|
||||
Kanthak]
|
||||
|
||||
03/22/05 13:09:44 david [976]
|
||||
* trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified)
|
||||
Fixed documentation and prepared for 0.11.0 release
|
||||
|
||||
03/21/05 14:35:36 david [967]
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
* trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified)
|
||||
Tweaked the documentation
|
||||
|
||||
03/20/05 23:12:05 david [949]
|
||||
* trunk/actionpack/CHANGELOG (modified)
|
||||
* trunk/actionpack/lib/action_controller.rb (modified)
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (added)
|
||||
* trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (added)
|
||||
* trunk/activesupport/lib/active_support/core_ext/kernel.rb (added)
|
||||
Added pagination support through both a controller and helper add-on
|
||||
#817 [Sam Stephenson]
|
||||
18
vendor/plugins/classic_pagination/README
vendored
Normal file
18
vendor/plugins/classic_pagination/README
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
Pagination
|
||||
==========
|
||||
|
||||
To install:
|
||||
|
||||
script/plugin install svn://errtheblog.com/svn/plugins/classic_pagination
|
||||
|
||||
This code was extracted from Rails trunk after the release 1.2.3.
|
||||
WARNING: this code is dead. It is unmaintained, untested and full of cruft.
|
||||
|
||||
There is a much better pagination plugin called will_paginate.
|
||||
Install it like this and glance through the README:
|
||||
|
||||
script/plugin install svn://errtheblog.com/svn/plugins/will_paginate
|
||||
|
||||
It doesn't have the same API, but is in fact much nicer. You can
|
||||
have both plugins installed until you change your controller/view code that
|
||||
handles pagination. Then, simply uninstall classic_pagination.
|
||||
22
vendor/plugins/classic_pagination/Rakefile
vendored
Normal file
22
vendor/plugins/classic_pagination/Rakefile
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
|
||||
desc 'Default: run unit tests.'
|
||||
task :default => :test
|
||||
|
||||
desc 'Test the classic_pagination plugin.'
|
||||
Rake::TestTask.new(:test) do |t|
|
||||
t.libs << 'lib'
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
desc 'Generate documentation for the classic_pagination plugin.'
|
||||
Rake::RDocTask.new(:rdoc) do |rdoc|
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
rdoc.title = 'Pagination'
|
||||
rdoc.options << '--line-numbers' << '--inline-source'
|
||||
rdoc.rdoc_files.include('README')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
end
|
||||
33
vendor/plugins/classic_pagination/init.rb
vendored
Normal file
33
vendor/plugins/classic_pagination/init.rb
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#--
|
||||
# Copyright (c) 2004-2006 David Heinemeier Hansson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#++
|
||||
|
||||
require 'pagination'
|
||||
require 'pagination_helper'
|
||||
|
||||
ActionController::Base.class_eval do
|
||||
include ActionController::Pagination
|
||||
end
|
||||
|
||||
ActionView::Base.class_eval do
|
||||
include ActionView::Helpers::PaginationHelper
|
||||
end
|
||||
1
vendor/plugins/classic_pagination/install.rb
vendored
Normal file
1
vendor/plugins/classic_pagination/install.rb
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
puts "\n\n" + File.read(File.dirname(__FILE__) + '/README')
|
||||
405
vendor/plugins/classic_pagination/lib/pagination.rb
vendored
Normal file
405
vendor/plugins/classic_pagination/lib/pagination.rb
vendored
Normal file
|
|
@ -0,0 +1,405 @@
|
|||
module ActionController
|
||||
# === Action Pack pagination for Active Record collections
|
||||
#
|
||||
# The Pagination module aids in the process of paging large collections of
|
||||
# Active Record objects. It offers macro-style automatic fetching of your
|
||||
# model for multiple views, or explicit fetching for single actions. And if
|
||||
# the magic isn't flexible enough for your needs, you can create your own
|
||||
# paginators with a minimal amount of code.
|
||||
#
|
||||
# The Pagination module can handle as much or as little as you wish. In the
|
||||
# controller, have it automatically query your model for pagination; or,
|
||||
# if you prefer, create Paginator objects yourself.
|
||||
#
|
||||
# Pagination is included automatically for all controllers.
|
||||
#
|
||||
# For help rendering pagination links, see
|
||||
# ActionView::Helpers::PaginationHelper.
|
||||
#
|
||||
# ==== Automatic pagination for every action in a controller
|
||||
#
|
||||
# class PersonController < ApplicationController
|
||||
# model :person
|
||||
#
|
||||
# paginate :people, :order => 'last_name, first_name',
|
||||
# :per_page => 20
|
||||
#
|
||||
# # ...
|
||||
# end
|
||||
#
|
||||
# Each action in this controller now has access to a <tt>@people</tt>
|
||||
# instance variable, which is an ordered collection of model objects for the
|
||||
# current page (at most 20, sorted by last name and first name), and a
|
||||
# <tt>@person_pages</tt> Paginator instance. The current page is determined
|
||||
# by the <tt>params[:page]</tt> variable.
|
||||
#
|
||||
# ==== Pagination for a single action
|
||||
#
|
||||
# def list
|
||||
# @person_pages, @people =
|
||||
# paginate :people, :order => 'last_name, first_name'
|
||||
# end
|
||||
#
|
||||
# Like the previous example, but explicitly creates <tt>@person_pages</tt>
|
||||
# and <tt>@people</tt> for a single action, and uses the default of 10 items
|
||||
# per page.
|
||||
#
|
||||
# ==== Custom/"classic" pagination
|
||||
#
|
||||
# def list
|
||||
# @person_pages = Paginator.new self, Person.count, 10, params[:page]
|
||||
# @people = Person.find :all, :order => 'last_name, first_name',
|
||||
# :limit => @person_pages.items_per_page,
|
||||
# :offset => @person_pages.current.offset
|
||||
# end
|
||||
#
|
||||
# Explicitly creates the paginator from the previous example and uses
|
||||
# Paginator#to_sql to retrieve <tt>@people</tt> from the model.
|
||||
#
|
||||
module Pagination
|
||||
unless const_defined?(:OPTIONS)
|
||||
# A hash holding options for controllers using macro-style pagination
|
||||
OPTIONS = Hash.new
|
||||
|
||||
# The default options for pagination
|
||||
DEFAULT_OPTIONS = {
|
||||
:class_name => nil,
|
||||
:singular_name => nil,
|
||||
:per_page => 10,
|
||||
:conditions => nil,
|
||||
:order_by => nil,
|
||||
:order => nil,
|
||||
:join => nil,
|
||||
:joins => nil,
|
||||
:count => nil,
|
||||
:include => nil,
|
||||
:select => nil,
|
||||
:group => nil,
|
||||
:parameter => 'page'
|
||||
}
|
||||
else
|
||||
DEFAULT_OPTIONS[:group] = nil
|
||||
end
|
||||
|
||||
def self.included(base) #:nodoc:
|
||||
super
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
def self.validate_options!(collection_id, options, in_action) #:nodoc:
|
||||
options.merge!(DEFAULT_OPTIONS) {|key, old, new| old}
|
||||
|
||||
valid_options = DEFAULT_OPTIONS.keys
|
||||
valid_options << :actions unless in_action
|
||||
|
||||
unknown_option_keys = options.keys - valid_options
|
||||
raise ActionController::ActionControllerError,
|
||||
"Unknown options: #{unknown_option_keys.join(', ')}" unless
|
||||
unknown_option_keys.empty?
|
||||
|
||||
options[:singular_name] ||= Inflector.singularize(collection_id.to_s)
|
||||
options[:class_name] ||= Inflector.camelize(options[:singular_name])
|
||||
end
|
||||
|
||||
# Returns a paginator and a collection of Active Record model instances
|
||||
# for the paginator's current page. This is designed to be used in a
|
||||
# single action; to automatically paginate multiple actions, consider
|
||||
# ClassMethods#paginate.
|
||||
#
|
||||
# +options+ are:
|
||||
# <tt>:singular_name</tt>:: the singular name to use, if it can't be inferred by singularizing the collection name
|
||||
# <tt>:class_name</tt>:: the class name to use, if it can't be inferred by
|
||||
# camelizing the singular name
|
||||
# <tt>:per_page</tt>:: the maximum number of items to include in a
|
||||
# single page. Defaults to 10
|
||||
# <tt>:conditions</tt>:: optional conditions passed to Model.find(:all, *params) and
|
||||
# Model.count
|
||||
# <tt>:order</tt>:: optional order parameter passed to Model.find(:all, *params)
|
||||
# <tt>:order_by</tt>:: (deprecated, used :order) optional order parameter passed to Model.find(:all, *params)
|
||||
# <tt>:joins</tt>:: optional joins parameter passed to Model.find(:all, *params)
|
||||
# and Model.count
|
||||
# <tt>:join</tt>:: (deprecated, used :joins or :include) optional join parameter passed to Model.find(:all, *params)
|
||||
# and Model.count
|
||||
# <tt>:include</tt>:: optional eager loading parameter passed to Model.find(:all, *params)
|
||||
# and Model.count
|
||||
# <tt>:select</tt>:: :select parameter passed to Model.find(:all, *params)
|
||||
#
|
||||
# <tt>:count</tt>:: parameter passed as :select option to Model.count(*params)
|
||||
#
|
||||
# <tt>:group</tt>:: :group parameter passed to Model.find(:all, *params). It forces the use of DISTINCT instead of plain COUNT to come up with the total number of records
|
||||
#
|
||||
def paginate(collection_id, options={})
|
||||
Pagination.validate_options!(collection_id, options, true)
|
||||
paginator_and_collection_for(collection_id, options)
|
||||
end
|
||||
|
||||
# These methods become class methods on any controller
|
||||
module ClassMethods
|
||||
# Creates a +before_filter+ which automatically paginates an Active
|
||||
# Record model for all actions in a controller (or certain actions if
|
||||
# specified with the <tt>:actions</tt> option).
|
||||
#
|
||||
# +options+ are the same as PaginationHelper#paginate, with the addition
|
||||
# of:
|
||||
# <tt>:actions</tt>:: an array of actions for which the pagination is
|
||||
# active. Defaults to +nil+ (i.e., every action)
|
||||
def paginate(collection_id, options={})
|
||||
Pagination.validate_options!(collection_id, options, false)
|
||||
module_eval do
|
||||
before_filter :create_paginators_and_retrieve_collections
|
||||
OPTIONS[self] ||= Hash.new
|
||||
OPTIONS[self][collection_id] = options
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create_paginators_and_retrieve_collections #:nodoc:
|
||||
Pagination::OPTIONS[self.class].each do |collection_id, options|
|
||||
next unless options[:actions].include? action_name if
|
||||
options[:actions]
|
||||
|
||||
paginator, collection =
|
||||
paginator_and_collection_for(collection_id, options)
|
||||
|
||||
paginator_name = "@#{options[:singular_name]}_pages"
|
||||
self.instance_variable_set(paginator_name, paginator)
|
||||
|
||||
collection_name = "@#{collection_id.to_s}"
|
||||
self.instance_variable_set(collection_name, collection)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the total number of items in the collection to be paginated for
|
||||
# the +model+ and given +conditions+. Override this method to implement a
|
||||
# custom counter.
|
||||
def count_collection_for_pagination(model, options)
|
||||
model.count(:conditions => options[:conditions],
|
||||
:joins => options[:join] || options[:joins],
|
||||
:include => options[:include],
|
||||
:select => (options[:group] ? "DISTINCT #{options[:group]}" : options[:count]))
|
||||
end
|
||||
|
||||
# Returns a collection of items for the given +model+ and +options[conditions]+,
|
||||
# ordered by +options[order]+, for the current page in the given +paginator+.
|
||||
# Override this method to implement a custom finder.
|
||||
def find_collection_for_pagination(model, options, paginator)
|
||||
model.find(:all, :conditions => options[:conditions],
|
||||
:order => options[:order_by] || options[:order],
|
||||
:joins => options[:join] || options[:joins], :include => options[:include],
|
||||
:select => options[:select], :limit => options[:per_page],
|
||||
:group => options[:group], :offset => paginator.current.offset)
|
||||
end
|
||||
|
||||
protected :create_paginators_and_retrieve_collections,
|
||||
:count_collection_for_pagination,
|
||||
:find_collection_for_pagination
|
||||
|
||||
def paginator_and_collection_for(collection_id, options) #:nodoc:
|
||||
klass = options[:class_name].constantize
|
||||
page = params[options[:parameter]]
|
||||
count = count_collection_for_pagination(klass, options)
|
||||
paginator = Paginator.new(self, count, options[:per_page], page)
|
||||
collection = find_collection_for_pagination(klass, options, paginator)
|
||||
|
||||
return paginator, collection
|
||||
end
|
||||
|
||||
private :paginator_and_collection_for
|
||||
|
||||
# A class representing a paginator for an Active Record collection.
|
||||
class Paginator
|
||||
include Enumerable
|
||||
|
||||
# Creates a new Paginator on the given +controller+ for a set of items
|
||||
# of size +item_count+ and having +items_per_page+ items per page.
|
||||
# Raises ArgumentError if items_per_page is out of bounds (i.e., less
|
||||
# than or equal to zero). The page CGI parameter for links defaults to
|
||||
# "page" and can be overridden with +page_parameter+.
|
||||
def initialize(controller, item_count, items_per_page, current_page=1)
|
||||
raise ArgumentError, 'must have at least one item per page' if
|
||||
items_per_page <= 0
|
||||
|
||||
@controller = controller
|
||||
@item_count = item_count || 0
|
||||
@items_per_page = items_per_page
|
||||
@pages = {}
|
||||
|
||||
self.current_page = current_page
|
||||
end
|
||||
attr_reader :controller, :item_count, :items_per_page
|
||||
|
||||
# Sets the current page number of this paginator. If +page+ is a Page
|
||||
# object, its +number+ attribute is used as the value; if the page does
|
||||
# not belong to this Paginator, an ArgumentError is raised.
|
||||
def current_page=(page)
|
||||
if page.is_a? Page
|
||||
raise ArgumentError, 'Page/Paginator mismatch' unless
|
||||
page.paginator == self
|
||||
end
|
||||
page = page.to_i
|
||||
@current_page_number = has_page_number?(page) ? page : 1
|
||||
end
|
||||
|
||||
# Returns a Page object representing this paginator's current page.
|
||||
def current_page
|
||||
@current_page ||= self[@current_page_number]
|
||||
end
|
||||
alias current :current_page
|
||||
|
||||
# Returns a new Page representing the first page in this paginator.
|
||||
def first_page
|
||||
@first_page ||= self[1]
|
||||
end
|
||||
alias first :first_page
|
||||
|
||||
# Returns a new Page representing the last page in this paginator.
|
||||
def last_page
|
||||
@last_page ||= self[page_count]
|
||||
end
|
||||
alias last :last_page
|
||||
|
||||
# Returns the number of pages in this paginator.
|
||||
def page_count
|
||||
@page_count ||= @item_count.zero? ? 1 :
|
||||
(q,r=@item_count.divmod(@items_per_page); r==0? q : q+1)
|
||||
end
|
||||
|
||||
alias length :page_count
|
||||
|
||||
# Returns true if this paginator contains the page of index +number+.
|
||||
def has_page_number?(number)
|
||||
number >= 1 and number <= page_count
|
||||
end
|
||||
|
||||
# Returns a new Page representing the page with the given index
|
||||
# +number+.
|
||||
def [](number)
|
||||
@pages[number] ||= Page.new(self, number)
|
||||
end
|
||||
|
||||
# Successively yields all the paginator's pages to the given block.
|
||||
def each(&block)
|
||||
page_count.times do |n|
|
||||
yield self[n+1]
|
||||
end
|
||||
end
|
||||
|
||||
# A class representing a single page in a paginator.
|
||||
class Page
|
||||
include Comparable
|
||||
|
||||
# Creates a new Page for the given +paginator+ with the index
|
||||
# +number+. If +number+ is not in the range of valid page numbers or
|
||||
# is not a number at all, it defaults to 1.
|
||||
def initialize(paginator, number)
|
||||
@paginator = paginator
|
||||
@number = number.to_i
|
||||
@number = 1 unless @paginator.has_page_number? @number
|
||||
end
|
||||
attr_reader :paginator, :number
|
||||
alias to_i :number
|
||||
|
||||
# Compares two Page objects and returns true when they represent the
|
||||
# same page (i.e., their paginators are the same and they have the
|
||||
# same page number).
|
||||
def ==(page)
|
||||
return false if page.nil?
|
||||
@paginator == page.paginator and
|
||||
@number == page.number
|
||||
end
|
||||
|
||||
# Compares two Page objects and returns -1 if the left-hand page comes
|
||||
# before the right-hand page, 0 if the pages are equal, and 1 if the
|
||||
# left-hand page comes after the right-hand page. Raises ArgumentError
|
||||
# if the pages do not belong to the same Paginator object.
|
||||
def <=>(page)
|
||||
raise ArgumentError unless @paginator == page.paginator
|
||||
@number <=> page.number
|
||||
end
|
||||
|
||||
# Returns the item offset for the first item in this page.
|
||||
def offset
|
||||
@paginator.items_per_page * (@number - 1)
|
||||
end
|
||||
|
||||
# Returns the number of the first item displayed.
|
||||
def first_item
|
||||
offset + 1
|
||||
end
|
||||
|
||||
# Returns the number of the last item displayed.
|
||||
def last_item
|
||||
[@paginator.items_per_page * @number, @paginator.item_count].min
|
||||
end
|
||||
|
||||
# Returns true if this page is the first page in the paginator.
|
||||
def first?
|
||||
self == @paginator.first
|
||||
end
|
||||
|
||||
# Returns true if this page is the last page in the paginator.
|
||||
def last?
|
||||
self == @paginator.last
|
||||
end
|
||||
|
||||
# Returns a new Page object representing the page just before this
|
||||
# page, or nil if this is the first page.
|
||||
def previous
|
||||
if first? then nil else @paginator[@number - 1] end
|
||||
end
|
||||
|
||||
# Returns a new Page object representing the page just after this
|
||||
# page, or nil if this is the last page.
|
||||
def next
|
||||
if last? then nil else @paginator[@number + 1] end
|
||||
end
|
||||
|
||||
# Returns a new Window object for this page with the specified
|
||||
# +padding+.
|
||||
def window(padding=2)
|
||||
Window.new(self, padding)
|
||||
end
|
||||
|
||||
# Returns the limit/offset array for this page.
|
||||
def to_sql
|
||||
[@paginator.items_per_page, offset]
|
||||
end
|
||||
|
||||
def to_param #:nodoc:
|
||||
@number.to_s
|
||||
end
|
||||
end
|
||||
|
||||
# A class for representing ranges around a given page.
|
||||
class Window
|
||||
# Creates a new Window object for the given +page+ with the specified
|
||||
# +padding+.
|
||||
def initialize(page, padding=2)
|
||||
@paginator = page.paginator
|
||||
@page = page
|
||||
self.padding = padding
|
||||
end
|
||||
attr_reader :paginator, :page
|
||||
|
||||
# Sets the window's padding (the number of pages on either side of the
|
||||
# window page).
|
||||
def padding=(padding)
|
||||
@padding = padding < 0 ? 0 : padding
|
||||
# Find the beginning and end pages of the window
|
||||
@first = @paginator.has_page_number?(@page.number - @padding) ?
|
||||
@paginator[@page.number - @padding] : @paginator.first
|
||||
@last = @paginator.has_page_number?(@page.number + @padding) ?
|
||||
@paginator[@page.number + @padding] : @paginator.last
|
||||
end
|
||||
attr_reader :padding, :first, :last
|
||||
|
||||
# Returns an array of Page objects in the current window.
|
||||
def pages
|
||||
(@first.number..@last.number).to_a.collect! {|n| @paginator[n]}
|
||||
end
|
||||
alias to_a :pages
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
135
vendor/plugins/classic_pagination/lib/pagination_helper.rb
vendored
Normal file
135
vendor/plugins/classic_pagination/lib/pagination_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
module ActionView
|
||||
module Helpers
|
||||
# Provides methods for linking to ActionController::Pagination objects using a simple generator API. You can optionally
|
||||
# also build your links manually using ActionView::Helpers::AssetHelper#link_to like so:
|
||||
#
|
||||
# <%= link_to "Previous page", { :page => paginator.current.previous } if paginator.current.previous %>
|
||||
# <%= link_to "Next page", { :page => paginator.current.next } if paginator.current.next %>
|
||||
module PaginationHelper
|
||||
unless const_defined?(:DEFAULT_OPTIONS)
|
||||
DEFAULT_OPTIONS = {
|
||||
:name => :page,
|
||||
:window_size => 2,
|
||||
:always_show_anchors => true,
|
||||
:link_to_current_page => false,
|
||||
:params => {}
|
||||
}
|
||||
end
|
||||
|
||||
# Creates a basic HTML link bar for the given +paginator+. Links will be created
|
||||
# for the next and/or previous page and for a number of other pages around the current
|
||||
# pages position. The +html_options+ hash is passed to +link_to+ when the links are created.
|
||||
#
|
||||
# ==== Options
|
||||
# <tt>:name</tt>:: the routing name for this paginator
|
||||
# (defaults to +page+)
|
||||
# <tt>:prefix</tt>:: prefix for pagination links
|
||||
# (i.e. Older Pages: 1 2 3 4)
|
||||
# <tt>:suffix</tt>:: suffix for pagination links
|
||||
# (i.e. 1 2 3 4 <- Older Pages)
|
||||
# <tt>:window_size</tt>:: the number of pages to show around
|
||||
# the current page (defaults to <tt>2</tt>)
|
||||
# <tt>:always_show_anchors</tt>:: whether or not the first and last
|
||||
# pages should always be shown
|
||||
# (defaults to +true+)
|
||||
# <tt>:link_to_current_page</tt>:: whether or not the current page
|
||||
# should be linked to (defaults to
|
||||
# +false+)
|
||||
# <tt>:params</tt>:: any additional routing parameters
|
||||
# for page URLs
|
||||
#
|
||||
# ==== Examples
|
||||
# # We'll assume we have a paginator setup in @person_pages...
|
||||
#
|
||||
# pagination_links(@person_pages)
|
||||
# # => 1 <a href="/?page=2/">2</a> <a href="/?page=3/">3</a> ... <a href="/?page=10/">10</a>
|
||||
#
|
||||
# pagination_links(@person_pages, :link_to_current_page => true)
|
||||
# # => <a href="/?page=1/">1</a> <a href="/?page=2/">2</a> <a href="/?page=3/">3</a> ... <a href="/?page=10/">10</a>
|
||||
#
|
||||
# pagination_links(@person_pages, :always_show_anchors => false)
|
||||
# # => 1 <a href="/?page=2/">2</a> <a href="/?page=3/">3</a>
|
||||
#
|
||||
# pagination_links(@person_pages, :window_size => 1)
|
||||
# # => 1 <a href="/?page=2/">2</a> ... <a href="/?page=10/">10</a>
|
||||
#
|
||||
# pagination_links(@person_pages, :params => { :viewer => "flash" })
|
||||
# # => 1 <a href="/?page=2&viewer=flash/">2</a> <a href="/?page=3&viewer=flash/">3</a> ...
|
||||
# # <a href="/?page=10&viewer=flash/">10</a>
|
||||
def pagination_links(paginator, options={}, html_options={})
|
||||
name = options[:name] || DEFAULT_OPTIONS[:name]
|
||||
params = (options[:params] || DEFAULT_OPTIONS[:params]).clone
|
||||
|
||||
prefix = options[:prefix] || ''
|
||||
suffix = options[:suffix] || ''
|
||||
|
||||
pagination_links_each(paginator, options, prefix, suffix) do |n|
|
||||
params[name] = n
|
||||
link_to(n.to_s, params, html_options)
|
||||
end
|
||||
end
|
||||
|
||||
# Iterate through the pages of a given +paginator+, invoking a
|
||||
# block for each page number that needs to be rendered as a link.
|
||||
#
|
||||
# ==== Options
|
||||
# <tt>:window_size</tt>:: the number of pages to show around
|
||||
# the current page (defaults to +2+)
|
||||
# <tt>:always_show_anchors</tt>:: whether or not the first and last
|
||||
# pages should always be shown
|
||||
# (defaults to +true+)
|
||||
# <tt>:link_to_current_page</tt>:: whether or not the current page
|
||||
# should be linked to (defaults to
|
||||
# +false+)
|
||||
#
|
||||
# ==== Example
|
||||
# # Turn paginated links into an Ajax call
|
||||
# pagination_links_each(paginator, page_options) do |link|
|
||||
# options = { :url => {:action => 'list'}, :update => 'results' }
|
||||
# html_options = { :href => url_for(:action => 'list') }
|
||||
#
|
||||
# link_to_remote(link.to_s, options, html_options)
|
||||
# end
|
||||
def pagination_links_each(paginator, options, prefix = nil, suffix = nil)
|
||||
options = DEFAULT_OPTIONS.merge(options)
|
||||
link_to_current_page = options[:link_to_current_page]
|
||||
always_show_anchors = options[:always_show_anchors]
|
||||
|
||||
current_page = paginator.current_page
|
||||
window_pages = current_page.window(options[:window_size]).pages
|
||||
return if window_pages.length <= 1 unless link_to_current_page
|
||||
|
||||
first, last = paginator.first, paginator.last
|
||||
|
||||
html = ''
|
||||
|
||||
html << prefix if prefix
|
||||
|
||||
if always_show_anchors and not (wp_first = window_pages[0]).first?
|
||||
html << yield(first.number)
|
||||
html << ' ... ' if wp_first.number - first.number > 1
|
||||
html << ' '
|
||||
end
|
||||
|
||||
window_pages.each do |page|
|
||||
if current_page == page && !link_to_current_page
|
||||
html << page.number.to_s
|
||||
else
|
||||
html << yield(page.number)
|
||||
end
|
||||
html << ' '
|
||||
end
|
||||
|
||||
if always_show_anchors and not (wp_last = window_pages[-1]).last?
|
||||
html << ' ... ' if last.number - wp_last.number > 1
|
||||
html << yield(last.number)
|
||||
end
|
||||
|
||||
html << suffix if suffix
|
||||
|
||||
html
|
||||
end
|
||||
|
||||
end # PaginationHelper
|
||||
end # Helpers
|
||||
end # ActionView
|
||||
24
vendor/plugins/classic_pagination/test/fixtures/companies.yml
vendored
Normal file
24
vendor/plugins/classic_pagination/test/fixtures/companies.yml
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
thirty_seven_signals:
|
||||
id: 1
|
||||
name: 37Signals
|
||||
rating: 4
|
||||
|
||||
TextDrive:
|
||||
id: 2
|
||||
name: TextDrive
|
||||
rating: 4
|
||||
|
||||
PlanetArgon:
|
||||
id: 3
|
||||
name: Planet Argon
|
||||
rating: 4
|
||||
|
||||
Google:
|
||||
id: 4
|
||||
name: Google
|
||||
rating: 4
|
||||
|
||||
Ionist:
|
||||
id: 5
|
||||
name: Ioni.st
|
||||
rating: 4
|
||||
9
vendor/plugins/classic_pagination/test/fixtures/company.rb
vendored
Normal file
9
vendor/plugins/classic_pagination/test/fixtures/company.rb
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
class Company < ActiveRecord::Base
|
||||
attr_protected :rating
|
||||
set_sequence_name :companies_nonstd_seq
|
||||
|
||||
validates_presence_of :name
|
||||
def validate
|
||||
errors.add('rating', 'rating should not be 2') if rating == 2
|
||||
end
|
||||
end
|
||||
7
vendor/plugins/classic_pagination/test/fixtures/developer.rb
vendored
Normal file
7
vendor/plugins/classic_pagination/test/fixtures/developer.rb
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
class Developer < ActiveRecord::Base
|
||||
has_and_belongs_to_many :projects
|
||||
end
|
||||
|
||||
class DeVeLoPeR < ActiveRecord::Base
|
||||
set_table_name "developers"
|
||||
end
|
||||
21
vendor/plugins/classic_pagination/test/fixtures/developers.yml
vendored
Normal file
21
vendor/plugins/classic_pagination/test/fixtures/developers.yml
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
david:
|
||||
id: 1
|
||||
name: David
|
||||
salary: 80000
|
||||
|
||||
jamis:
|
||||
id: 2
|
||||
name: Jamis
|
||||
salary: 150000
|
||||
|
||||
<% for digit in 3..10 %>
|
||||
dev_<%= digit %>:
|
||||
id: <%= digit %>
|
||||
name: fixture_<%= digit %>
|
||||
salary: 100000
|
||||
<% end %>
|
||||
|
||||
poor_jamis:
|
||||
id: 11
|
||||
name: Jamis
|
||||
salary: 9000
|
||||
13
vendor/plugins/classic_pagination/test/fixtures/developers_projects.yml
vendored
Normal file
13
vendor/plugins/classic_pagination/test/fixtures/developers_projects.yml
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
david_action_controller:
|
||||
developer_id: 1
|
||||
project_id: 2
|
||||
joined_on: 2004-10-10
|
||||
|
||||
david_active_record:
|
||||
developer_id: 1
|
||||
project_id: 1
|
||||
joined_on: 2004-10-10
|
||||
|
||||
jamis_active_record:
|
||||
developer_id: 2
|
||||
project_id: 1
|
||||
3
vendor/plugins/classic_pagination/test/fixtures/project.rb
vendored
Normal file
3
vendor/plugins/classic_pagination/test/fixtures/project.rb
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
class Project < ActiveRecord::Base
|
||||
has_and_belongs_to_many :developers, :uniq => true
|
||||
end
|
||||
7
vendor/plugins/classic_pagination/test/fixtures/projects.yml
vendored
Normal file
7
vendor/plugins/classic_pagination/test/fixtures/projects.yml
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
action_controller:
|
||||
id: 2
|
||||
name: Active Controller
|
||||
|
||||
active_record:
|
||||
id: 1
|
||||
name: Active Record
|
||||
13
vendor/plugins/classic_pagination/test/fixtures/replies.yml
vendored
Normal file
13
vendor/plugins/classic_pagination/test/fixtures/replies.yml
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
witty_retort:
|
||||
id: 1
|
||||
topic_id: 1
|
||||
content: Birdman is better!
|
||||
created_at: <%= 6.hours.ago.to_s(:db) %>
|
||||
updated_at: nil
|
||||
|
||||
another:
|
||||
id: 2
|
||||
topic_id: 2
|
||||
content: Nuh uh!
|
||||
created_at: <%= 1.hour.ago.to_s(:db) %>
|
||||
updated_at: nil
|
||||
5
vendor/plugins/classic_pagination/test/fixtures/reply.rb
vendored
Normal file
5
vendor/plugins/classic_pagination/test/fixtures/reply.rb
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
class Reply < ActiveRecord::Base
|
||||
belongs_to :topic, :include => [:replies]
|
||||
|
||||
validates_presence_of :content
|
||||
end
|
||||
42
vendor/plugins/classic_pagination/test/fixtures/schema.sql
vendored
Normal file
42
vendor/plugins/classic_pagination/test/fixtures/schema.sql
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
CREATE TABLE 'companies' (
|
||||
'id' INTEGER PRIMARY KEY NOT NULL,
|
||||
'name' TEXT DEFAULT NULL,
|
||||
'rating' INTEGER DEFAULT 1
|
||||
);
|
||||
|
||||
CREATE TABLE 'replies' (
|
||||
'id' INTEGER PRIMARY KEY NOT NULL,
|
||||
'content' text,
|
||||
'created_at' datetime,
|
||||
'updated_at' datetime,
|
||||
'topic_id' integer
|
||||
);
|
||||
|
||||
CREATE TABLE 'topics' (
|
||||
'id' INTEGER PRIMARY KEY NOT NULL,
|
||||
'title' varchar(255),
|
||||
'subtitle' varchar(255),
|
||||
'content' text,
|
||||
'created_at' datetime,
|
||||
'updated_at' datetime
|
||||
);
|
||||
|
||||
CREATE TABLE 'developers' (
|
||||
'id' INTEGER PRIMARY KEY NOT NULL,
|
||||
'name' TEXT DEFAULT NULL,
|
||||
'salary' INTEGER DEFAULT 70000,
|
||||
'created_at' DATETIME DEFAULT NULL,
|
||||
'updated_at' DATETIME DEFAULT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE 'projects' (
|
||||
'id' INTEGER PRIMARY KEY NOT NULL,
|
||||
'name' TEXT DEFAULT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE 'developers_projects' (
|
||||
'developer_id' INTEGER NOT NULL,
|
||||
'project_id' INTEGER NOT NULL,
|
||||
'joined_on' DATE DEFAULT NULL,
|
||||
'access_level' INTEGER DEFAULT 1
|
||||
);
|
||||
3
vendor/plugins/classic_pagination/test/fixtures/topic.rb
vendored
Normal file
3
vendor/plugins/classic_pagination/test/fixtures/topic.rb
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
class Topic < ActiveRecord::Base
|
||||
has_many :replies, :include => [:user], :dependent => :destroy
|
||||
end
|
||||
22
vendor/plugins/classic_pagination/test/fixtures/topics.yml
vendored
Normal file
22
vendor/plugins/classic_pagination/test/fixtures/topics.yml
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
futurama:
|
||||
id: 1
|
||||
title: Isnt futurama awesome?
|
||||
subtitle: It really is, isnt it.
|
||||
content: I like futurama
|
||||
created_at: <%= 1.day.ago.to_s(:db) %>
|
||||
updated_at:
|
||||
|
||||
harvey_birdman:
|
||||
id: 2
|
||||
title: Harvey Birdman is the king of all men
|
||||
subtitle: yup
|
||||
content: It really is
|
||||
created_at: <%= 2.hours.ago.to_s(:db) %>
|
||||
updated_at:
|
||||
|
||||
rails:
|
||||
id: 3
|
||||
title: Rails is nice
|
||||
subtitle: It makes me happy
|
||||
content: except when I have to hack internals to fix pagination. even then really.
|
||||
created_at: <%= 20.minutes.ago.to_s(:db) %>
|
||||
117
vendor/plugins/classic_pagination/test/helper.rb
vendored
Normal file
117
vendor/plugins/classic_pagination/test/helper.rb
vendored
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
require 'test/unit'
|
||||
|
||||
unless defined?(ActiveRecord)
|
||||
plugin_root = File.join(File.dirname(__FILE__), '..')
|
||||
|
||||
# first look for a symlink to a copy of the framework
|
||||
if framework_root = ["#{plugin_root}/rails", "#{plugin_root}/../../rails"].find { |p| File.directory? p }
|
||||
puts "found framework root: #{framework_root}"
|
||||
# this allows for a plugin to be tested outside an app
|
||||
$:.unshift "#{framework_root}/activesupport/lib", "#{framework_root}/activerecord/lib", "#{framework_root}/actionpack/lib"
|
||||
else
|
||||
# is the plugin installed in an application?
|
||||
app_root = plugin_root + '/../../..'
|
||||
|
||||
if File.directory? app_root + '/config'
|
||||
puts 'using config/boot.rb'
|
||||
ENV['RAILS_ENV'] = 'test'
|
||||
require File.expand_path(app_root + '/config/boot')
|
||||
else
|
||||
# simply use installed gems if available
|
||||
puts 'using rubygems'
|
||||
require 'rubygems'
|
||||
gem 'actionpack'; gem 'activerecord'
|
||||
end
|
||||
end
|
||||
|
||||
%w(action_pack active_record action_controller active_record/fixtures action_controller/test_process).each {|f| require f}
|
||||
|
||||
Dependencies.load_paths.unshift "#{plugin_root}/lib"
|
||||
end
|
||||
|
||||
# Define the connector
|
||||
class ActiveRecordTestConnector
|
||||
cattr_accessor :able_to_connect
|
||||
cattr_accessor :connected
|
||||
|
||||
# Set our defaults
|
||||
self.connected = false
|
||||
self.able_to_connect = true
|
||||
|
||||
class << self
|
||||
def setup
|
||||
unless self.connected || !self.able_to_connect
|
||||
setup_connection
|
||||
load_schema
|
||||
require_fixture_models
|
||||
self.connected = true
|
||||
end
|
||||
rescue Exception => e # errors from ActiveRecord setup
|
||||
$stderr.puts "\nSkipping ActiveRecord assertion tests: #{e}"
|
||||
#$stderr.puts " #{e.backtrace.join("\n ")}\n"
|
||||
self.able_to_connect = false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def setup_connection
|
||||
if Object.const_defined?(:ActiveRecord)
|
||||
defaults = { :database => ':memory:' }
|
||||
begin
|
||||
options = defaults.merge :adapter => 'sqlite3', :timeout => 500
|
||||
ActiveRecord::Base.establish_connection(options)
|
||||
ActiveRecord::Base.configurations = { 'sqlite3_ar_integration' => options }
|
||||
ActiveRecord::Base.connection
|
||||
rescue Exception # errors from establishing a connection
|
||||
$stderr.puts 'SQLite 3 unavailable; trying SQLite 2.'
|
||||
options = defaults.merge :adapter => 'sqlite'
|
||||
ActiveRecord::Base.establish_connection(options)
|
||||
ActiveRecord::Base.configurations = { 'sqlite2_ar_integration' => options }
|
||||
ActiveRecord::Base.connection
|
||||
end
|
||||
|
||||
Object.send(:const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name('type')) unless Object.const_defined?(:QUOTED_TYPE)
|
||||
else
|
||||
raise "Can't setup connection since ActiveRecord isn't loaded."
|
||||
end
|
||||
end
|
||||
|
||||
# Load actionpack sqlite tables
|
||||
def load_schema
|
||||
File.read(File.dirname(__FILE__) + "/fixtures/schema.sql").split(';').each do |sql|
|
||||
ActiveRecord::Base.connection.execute(sql) unless sql.blank?
|
||||
end
|
||||
end
|
||||
|
||||
def require_fixture_models
|
||||
Dir.glob(File.dirname(__FILE__) + "/fixtures/*.rb").each {|f| require f}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Test case for inheritance
|
||||
class ActiveRecordTestCase < Test::Unit::TestCase
|
||||
# Set our fixture path
|
||||
if ActiveRecordTestConnector.able_to_connect
|
||||
self.fixture_path = "#{File.dirname(__FILE__)}/fixtures/"
|
||||
self.use_transactional_fixtures = false
|
||||
end
|
||||
|
||||
def self.fixtures(*args)
|
||||
super if ActiveRecordTestConnector.connected
|
||||
end
|
||||
|
||||
def run(*args)
|
||||
super if ActiveRecordTestConnector.connected
|
||||
end
|
||||
|
||||
# Default so Test::Unit::TestCase doesn't complain
|
||||
def test_truth
|
||||
end
|
||||
end
|
||||
|
||||
ActiveRecordTestConnector.setup
|
||||
ActionController::Routing::Routes.reload rescue nil
|
||||
ActionController::Routing::Routes.draw do |map|
|
||||
map.connect ':controller/:action/:id'
|
||||
end
|
||||
38
vendor/plugins/classic_pagination/test/pagination_helper_test.rb
vendored
Normal file
38
vendor/plugins/classic_pagination/test/pagination_helper_test.rb
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
require File.dirname(__FILE__) + '/helper'
|
||||
require File.dirname(__FILE__) + '/../init'
|
||||
|
||||
class PaginationHelperTest < Test::Unit::TestCase
|
||||
include ActionController::Pagination
|
||||
include ActionView::Helpers::PaginationHelper
|
||||
include ActionView::Helpers::UrlHelper
|
||||
include ActionView::Helpers::TagHelper
|
||||
|
||||
def setup
|
||||
@controller = Class.new do
|
||||
attr_accessor :url, :request
|
||||
def url_for(options, *parameters_for_method_reference)
|
||||
url
|
||||
end
|
||||
end
|
||||
@controller = @controller.new
|
||||
@controller.url = "http://www.example.com"
|
||||
end
|
||||
|
||||
def test_pagination_links
|
||||
total, per_page, page = 30, 10, 1
|
||||
output = pagination_links Paginator.new(@controller, total, per_page, page)
|
||||
assert_equal "1 <a href=\"http://www.example.com\">2</a> <a href=\"http://www.example.com\">3</a> ", output
|
||||
end
|
||||
|
||||
def test_pagination_links_with_prefix
|
||||
total, per_page, page = 30, 10, 1
|
||||
output = pagination_links Paginator.new(@controller, total, per_page, page), :prefix => 'Newer '
|
||||
assert_equal "Newer 1 <a href=\"http://www.example.com\">2</a> <a href=\"http://www.example.com\">3</a> ", output
|
||||
end
|
||||
|
||||
def test_pagination_links_with_suffix
|
||||
total, per_page, page = 30, 10, 1
|
||||
output = pagination_links Paginator.new(@controller, total, per_page, page), :suffix => 'Older'
|
||||
assert_equal "1 <a href=\"http://www.example.com\">2</a> <a href=\"http://www.example.com\">3</a> Older", output
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue