pax_global_header00006660000000000000000000000064147543351400014520gustar00rootroot0000000000000052 comment=de6b6188f1f1cd7645948b266e3861f82c9c58a1 thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/000077500000000000000000000000001475433514000173605ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/.github/000077500000000000000000000000001475433514000207205ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/.github/FUNDING.yml000066400000000000000000000001661475433514000225400ustar00rootroot00000000000000# These are supported funding model platforms custom: https://github.com/socketry/community/#funding github: ioquatix thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/.github/workflows/000077500000000000000000000000001475433514000227555ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/.github/workflows/test.yml000066400000000000000000000030641475433514000244620ustar00rootroot00000000000000name: Test on: push: branches: - master pull_request: branches: - master permissions: contents: read jobs: test: name: ${{matrix.ruby}} ${{matrix.gemfile}} runs-on: ${{matrix.os}}-latest continue-on-error: ${{matrix.experimental}} env: BUNDLE_GEMFILE: ${{matrix.gemfile}} strategy: matrix: experimental: [false] os: - ubuntu ruby: - 2.6 - 2.7 - "3.0" - 3.1 - 3.2 - 3.3 - 3.4 gemfile: - gems/rack-v1.rb - gems/rack-v2.rb - gems/rack-v3.rb include: - experimental: false os: macos ruby: 3.4 gemfile: gems/rack-v2.rb - experimental: false os: macos ruby: 3.4 gemfile: gems/rack-v3.rb - experimental: true os: ubuntu ruby: head gemfile: gems/rack-v2.rb - experimental: true os: ubuntu ruby: head gemfile: gems/rack-v3.rb - experimental: true os: ubuntu ruby: 3.4 gemfile: gems/rack-head.rb exclude: - { ruby: 3.3, gemfile: gems/rack-v1.rb } - { ruby: 3.4, gemfile: gems/rack-v1.rb } steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} bundler-cache: true - name: Run tests timeout-minutes: 10 run: bundle exec rake thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/.gitignore000066400000000000000000000005451475433514000213540ustar00rootroot00000000000000ext/thin_parser/Makefile ext/thin_parser/conftest.dSYM/* ext/thin_parser/*.log ext/thin_parser/*.o ext/thin_parser/*.bundle ext/thin_parser/*.obj ext/thin_parser/*mswin32* ext/thin_parser/vc60.pdb ext/thin_parser/*.so *.bundle *.so *.gem log spec/tmp spec/rails_app/log /vendor doc/rdoc/* tmp/* pkg/* nbproject/* .DS_Store daemon_test.log Gemfile.lock .ideathin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/CHANGELOG000066400000000000000000000467431475433514000206100ustar00rootroot00000000000000== 1.8.2 Ruby Razor * Ruby 3.2 support. == 1.8.1 Infinite Smoothie * Fix possible HTTP Response Splitting == 1.8.0 Possessed Pickle * Many things == 1.7.2 Bachmanity * Add config support for ssl_version and ssl_cipher_list [frameworked] == 1.7.1 Muffin Mode * Ruby 2.4 support (Fixnum deprecation) [nimish-mehta] * Allow ERB templates in config files [markets] == 1.7.0 Dunder Mifflin * Rack 2 support * Ensure Response body.close is called in the same thread Fixes issues with ActiveRecord connection management [#307] * Fix TCP/IP Backend reports incorrect port when asked to bind to 0 [meschbach] * Work with ruby 2.3's --enable-frozen-string-literal [jeremyevans] == 1.6.4 Gob Bluth * Increase REQUEST_PATH to 2048 symbols [X2rdas] * Fix warning in logger [tenderlove] * Add :timeout option for Rack::Server.new [sugitak] * When restarting, exit on a next tick so we can send response back to a client [rsamoilov] * Check for empty PID files [z1dane] * Update Event Machine version to 1.0.4, Ruby 2.2.0 fix [freemanoid] == 1.6.3 Protein Powder * Add HTTP 422 status code [rajcybage] * Add warning about EM reactor still running when stopping. * Remove version number from "Server" HTTP header. [benbasson] * Adding `--ssl-disable-verify` to allow disabling of client cert requests when SSL enabled [brucek] * Ensure Tempfiles created by a large request are closed and deleted. [Tonkpils] == 1.6.2 Doc Brown * No longer replace response's body on HEAD request. Ensuring body.close will be called. * Remove `---ssl-verify` option as EventMachine doesn't verify the certificate. * Fix env['rack.peer_cert'] to return SSL certifcate. == 1.6.1 Death Proof * Regression: Default logger to STDOUT when using outside of CLI. * Regression: Downgrade Rack required version back to 1.0 to work w/ prior Rails versions. == 1.6.0 Greek Yogurt * Accept absolute URL in request line, eg.: 'GET http://site.com/he/lo HTTP/1.1'. * HEAD request no longer return a body in the response. * No longer stop EventMachine's reactor loop unless it was started by Thin. * Make request env keys upcasing locale-agnostic. * Use Ruby's `Logger` for logging. [Akshay Moghe]. The logger can now be set using `Thin::Logging.logger=`. Tracing of request is handled by a second logger, `Thin::Logging.trace_logger=`. * Add --threadpool-size option to configure EM's thread pool size (default: 20). * Pipelining is no longer supported. == 1.5.1 Straight Razor * Fix issue when running as another user/group without a PID file. * Allow overriding Connection & Server response headers. * Update vlad example [Mathieu Lemoine] * Keep connections in a Hash to speedup deletion [slivu] * Force kill using already known pid. Prevents "thin stop" from leaving a process that removed its pid file, but is still running (e.g. hung on some at_exit callback) [Michal Kwiatkowski] == 1.5.0 Knife * Fix compilation under Ubuntu 12.04 with -Werror=format-security option. * Raise an error when no PID file. * Prevent duplicate response headers. * Make proper response on exception [MasterLambaster]. * Automatically close idling pipeline connections on server stop [MasterLambaster]. == 1.4.1 Chromeo Fix * Fix error when sending USR1 signal and no log file is supplied. == 1.4.0 Chromeo * kill -USR1 $PID for log rotation [catwell]. * Fix HUP signal being reseted after deamonization [atotic]. * Fix error with nil addresses in Connection#socket_address. == 1.3.2 Low-bar Squat * Remove mack and halcyon Rack adapters from automatic detection. == 1.3.1 Triple Espresso * Fix service not working pre 1.9. == 1.3.0 Double Espresso * BREAKING CHANGE: Thin no longer ships with fat Windows binaries. From now on, to install on Windows, install https://github.com/oneclick/rubyinstaller/wiki/Development-Kit. * BREAKING CHANGE: Remove automatic Content-Length setting. It is now the responsibility of the app (or a middleware) to set the Content-Length. * Log errors to STDERR [textgoeshere] * Shut down gracefully when receiving SIGTERM [ddollar] Processes are allowed a chance to shut down gracefully when receiving SIGTERM (http://en.wikipedia.org/wiki/SIGTERM). On Heroku, when shutting down a process, we send a SIGTERM followed 10 seconds later with a SIGKILL, similar to the behavior of the init daemon on most Unix systems. This patch will allow Heroku apps to shut down gracefully when they need to be terminated / moved. == 1.2.11 Bat-Shit Crazy * Fix pure Ruby gem to not include binary. == 1.2.10 I'm dumb (BAD RELEASE, DON'T USE) * I really am (bad release fix) == 1.2.9 Black Keys Extra Plus Wow (BAD RELEASE, DON'T USE) * Improve fat binary loading. == 1.2.8 Black Keys * Allow the connection to remain open for 1xx statuses [timshadel] Both the 100 and 101 status codes require that the connection to the server remain open. The 100 status code is used to tell the client that the server is still receiving its request, and will continue to read request input on the connection. The 101 status code is used to upgrade the existing connection to another protocol, and specifically is NOT used to upgrade a separate connection. Therefore, the connection must remain open after this response in order to facilitate that. * Accept IE7 badly encoded URL (eg.: %uEEEE) * Fix gemspec to work w/ Bundler [smparkes] * Add SSL support [tmm1] * Catch Errno::EPERM in Process.running? [Tony Kemp] On some systems (e.g. OpenBSD) you receive an EPERM exception if you try to Process.getpgid on a process you do not own (even if you are root). But it does mean that the process exists, so return true. * Fix Rails version check that select which Rack adapter to use. Was using CGI adapter in Rails 3. * Ignore SIGHUP when no restart block is given * Add SSL options to thin command line tool [goldmann] --ssl Enables SSL --ssl-key-file PATH Path to private key --ssl-cert-file PATH Path to certificate --ssl-verify Enables SSL certificate verification * Expose peer SSL certificate in env (rack.peer_cert) [fd] * Adjusting unix socket permissions to be more open [mbj] == 1.2.7 No Hup * Support multiple Ruby version (fat binaries under windows) * Do not trap unsupported HUP signal on Windows == 1.2.6 Crazy Delicious * Make work with Rails 3 out-of-the-box. * Auto-detect and load config.ru files on start. Makes Rails 3 work. * Fix signals being ignored under 1.9 when daemonized. == 1.2.5 This Is Not A Web Server * Add rolling restart support (--onebyone option) [sikachu] * Force external_encoding of request's body to ASCII_8BIT [jeremyz] * Ensure Rack base API is used in Rails adapter only if version >= 2.3.2 [#111 state:resolved] == 1.2.4 Flaming Astroboy * Fix a few issues in thin to make it a better "gem citizen" [josh] * Fix test for rack based Rails in adapter under Ruby >= 1.8.7 [#109 state:resolved] * Fix Remote address spoofing vulnerability in Connection#remote_address [Alexey Borzenkov] * Fix uninitialized constant ActionController::Dispatcher error with Rails 1.2.3 [Chris Anderton] [#103 state:resolved] == 1.2.2 I Find Your Lack of Sauce Disturbing release * Fix force kill under 1.9 [Alexey Chebotar] * Fix regression when --only option is used w/ --socket. * Add process name 'tag' functionality. Easier to distinguish thin daemons from eachother in process listing [ctcherry] == 1.2.1 Asynctilicious Ultra Supreme release * Require Rack 1.0.0 * Require EventMachine 0.12.6 * Use Rails Rack based dispatcher when available * Allow String for response body * Require openssl before eventmachine to prevent crash in 1.9 == 1.2.0 Asynctilicious Supreme release * Add support for Windows mingw Ruby distro [Juan C. Rodriguez] * Add async response support, see example/async_*.ru [raggi] == 1.1.1 Super Disco Power Plus release * Fix bug when running with only options [hasimo] == 1.1.0 Super Disco Power release * Require EventMachine 0.12.4 * Remove Thin handler, now part of Rack 0.9.1 * Fix Rack protocol version to 0.1 in environment hash. * Fix error when passing no_epoll option to a cluster. * Omit parsing #defined strings [Jérémy Zurcher] * Defaults SERVER_NAME to localhost like webrick does [#87 state:resolved] * Namespace parser to prevent error when mongrel is required [cliffmoon] * Set RACK_ENV based on environment option when loading rackup file [Curtis Summers] [#83 state:resolved] * Fixes a warning RE relative_url_root when using a prefix with Rails 2.1.1 [seriph] [#85 state:resolved] * --only can work as a sequence number (if < 80) or a port number (if >= 80) [jmay] [#81 state:resolved] == 1.0.0 That's What She Said release * Fixed vlad.rake to allow TCP or socket [hellekin] * Updated Mack adapter to handle both <0.8.0 and >0.8.0 [Mark Bates] * rails rack adapter uses File.readable_real? so it recognizes ACL permissions [Ricardo Chimal] * Log a warning if Rack application returns nil body [Michael S. Klishin] * Handle nil and Time header values correctly [#76 state:resolved] [tmm1] * Add Content-Length header to response automatically when possible [#74 state:resolved] [dkubb] * Runner now remembers -r, -D and -V parameters so that clustered servers inherit those and `restart` keep your parameters. * Make Set-Cookie header, in Rails adapter, compatible with current Rack spec [Pedro Belo] [#73, state:resolved] * Add --no-epoll option to disable epoll usage on Linux [#61 state:resolved] * Add --force (-f) option to force stopping of a daemonized server [#72 state:resolved] * Update halycon adapter loader [mtodd] == 0.8.2 Double Margarita release * Require EventMachine 0.12.0 * [bug] Fix timeout handling when running command * [bug] Fix hanging when restarting and no process is running in single server move, fixes #67 * Added Mack adapter [markbates] * Allow rackup .rb files by getting a conventionally named constant as the app [bmizerany] == 0.8.1 Rebel Porpoise release * [bug] Rescue all types of errors when processing request, fixes #62 * [bug] Use Swiftiply backend when -y option is specified, fixes #63 and #64 * Allow passing port as a string in Server.new * Define deferred?(env) in your Rack application to set if a request is handled in a thread (return true) or not (return false). == 0.8.0 Dodgy Dentist release * [bug] Fix server crash when header too large. * Add --require (-r) option to require a library, before executing your script. * Rename --rackup short option to -R, warn and load as rackup when file ends with .ru. * List supported adapters in command usage. * Add file adapter to built-in adapter, serve static files in current directory. * Allow disabling signal handling in Server with :signals => false * Make Server.new arguments more flexible, can now specify any of host, port, app or hash options. * Add --backend option to specified which backend to use, closes #55 * [bug] Serve static file only on GET and HEAD requests in Rails adapter, fixes #58 * Add threaded option to run server in threaded mode, calling the application in a thread allowing for concurrency in the Rack adapter, closes #46 * Guess which adapter to use from directory (chdir option) or use specified one in 'adapter' option, re #47. == 0.7.1 Fancy Pants release * Clean stale PID files when starting as daemon, fixes #53 [Chu Yeow] * Require EventMachine 0.11.0 for UNIX domain sockets. Until it's released, install from: gem install eventmachine --source http://code.macournoyer.com * Ruby 1.8.5 compatibility, closes #49 [Wincent Colaiuta] * Move all EventMachine stuff out of Server, you can now create a Thin Backend that does not depend on EventMachine. * Rename Connector to Backend. Extend Thin::Backends::Base to implement your own. * Fix high memory usage with big POST body, fixes #48 == 0.7.0 Spherical Cow release * Add --max-persistent-conns option to sets the maximum number of persistent connections. Set to 0 to disable Keep-Alive. * INT signal now force stop and QUIT signal gracefully stops. * Warn when descriptors table size can't be set as high as expected. * Eval Rackup config file using top level bindings. * Remove daemons gem dependency on Windows plateform, fixes #45. * Change default timeout from 60 to 30 seconds. * Add --max-conns option to sets the maximum number of file or socket descriptors that your process may open, defaults to 1024. * Tail logfile when stopping and restarting a demonized server, fixes #26. * Wrap application in a Rack::CommonLogger adapter in debug mode. * --debug (-D) option no longer set $DEBUG so logging will be less verbose and Ruby won't be too strict, fixes #36. * Deprecate Server#silent in favour of Logging.silent. * Persistent connection (keep-alive) support. * Fix -s option not being included in generated config file, fixes #37. * Add Swiftiply support. Use w/ the --swiftiply (-y) option in the thin script, closes #28 [Alex MacCaw] == 0.6.4 Sexy Lobster release * Fix error when stopping server on UNIX domain socket, fixes #42 * Rescue errors in Connection#get_peername more gracefully, setting REMOTE_ADDR to nil, fixes #43 == 0.6.3 Ninja Cookie release * Add tasks for Vlad the Deployer in example/vlad.rake [cnantais] * Add Ramaze Rackup config file in example dir [tmm1] Use like this from you Ramaze app dir: thin start -r /path/to/thin/example/ramaze.ru * Add the --rackup option to load a Rack config file instead of the Rails adapter. So you can use any framework with the thin script and start cluster and stuff like that. A Rack config file is one that is usable through the rackup command and looks like this: use Rack::CommonLogger run MyCrazyRackAdapter.new(:uterly, 'cool') Then use it with thin like this: thin start --rackup config.ru * thin config --chrdir ... -C thin/yml do not change current directory anymore, fixes #33. * Add a better sample god config file in example/thin.god that loads all info from config files in /etc/thin. Drop-in replacement for the thin runlevel service [Gump]. * Add support for specifying a custom Connector to the server and add more doc about Server configuration. * Add a script to run thin as a runlevel service that can start at startup, closes #31 [Gump] Setup the service like this: sudo thin install /etc/thin This will install the boot script under /etc/init.d/thin. Then copy your config files to /etc/thin. Works only under Linux. * Set process name to 'thin server (0.0.0.0:3000)' when running as a daemon, closes #32. * Make sure chdir option from config file is used when present. * Raise an error when starting a server as a daemon and pid file already exist, fixes #27. == 0.6.2 Rambo release * Server now let current connections finish before stopping, fixes #18 * Fix uploading hanging bug when body is moved to a tempfile, also delete the tempfile properly upon completion, fixes #25 * 'thin restart' now sends HUP signals rather then stopping & starting, closes #17 * HUP signal now launches a new process with the same options. * Add PID and more info from the last request to the Stats adapter mostly taken from Rack::ShowException. * pid and log files in cluster are no longer required to be relative to the app directory (chdir option), fixes #24 * Revert to using #each when building response headers under Ruby 1.8, solves an issue w/ Camping adapter, fixes #22 * Restructure thin script options in 3 sections: server, daemon and cluster * Add --only (-o) option to control only one server of a cluster. * Stylize stats page and make the url configurable from the thin script. * Raise error if attempting to use unix sockets on windows. * Add example config files for http://www.tildeslash.com/monit usage. Include the example file using "include /path/to/thin/monit/file" in your monitrc file. The group settings let you do this to manage your clusters: sudo monit -g blog restart all There are examples of thin listening on sockets and thin listening on unix sockets. == 0.6.1 Cheesecake release * Remove socket file when server stops. * Set back cluster to use 'thin' command to launch servers. == 0.6.0 Big Pony release * Add support for connection through UNIX domain socket. Use the --socket (-S) option w/ the thin script to configure the socket filename. Nginx support binding to a UNIX socket like this: upstream backend { server unix:/tmp/thin.0.sock; server unix:/tmp/thin.1.sock; server unix:/tmp/thin.2.sock; } Start your servers like this: thin start -s3 -S/tmp/thin.sock * Remove Server#listen! method. Use Server#start instead. * Server can now yield a Rack::Builder to allow building an app in one call: Server.start '0.0.0.0', 3000 do use Rack::CommonLogger use Rack::ShowExceptions map "/lobster" do use Rack::Lint run Rack::Lobster.new end end * Add a very basic stats page through Stats adapter, load w/ --stats and browse to /stats. * Add --trace (-V) option to trace request/response and get backtrace w/out all Ruby debug stuff. * Add --config (-C) option to load options from a config file in thin script [Matt Todd]. * Alter response headers to output directly to a string. * Improve specs stability. * Move request body to a Tempfile if too big (> 112 KB) * Remove useless check for max header size in Request (already done in the parser) == 0.5.4 Flying Mustard release * Don't read the full body, use direct streaming when sending response. See: Response#each As a result, the Content-Length can not be calculated anymore. You have to do set this in your adapter. All frameworks do it anyway. It improve memory usage and boost speed for low concurrency. Thanks to Kent Sibilev and Ezra for their help on that one. * Add 'Server' response header * Fix --user and --group option not changing daemon process privileges == 0.5.3 Purple Yogurt release * win32 pre-compiled gem now available * change rake task configuration to allow win32 gem build * Add prefix option to thin script to mount app under a given path. == 0.5.2 Cheezburger release * Add cluster support through the -s option in the thin script, start 3 thins like this: thin start -s3 -p3000 3 thin servers will be started on port 3000, 3001, 3002, also the port number will be injected in the pid and log filenames. * Fix IOError when writing to logger when starting server as a daemon. * Really change directory when the -c option is specified. * Add restart command to thin script. * Fix typos in thin script usage message and expand chdir path. * Rename thin script options to be the same as mongrel_rails script [thronedrk]: -o --host => -a --address --log-file => --log --pid-file => --pid --env => --environment == 0.5.1 LOLCAT release * Add URL rewriting to Rails adapter so that page caching works and / fetches index.html if present. * Fix bug in multiline response header parsing. * Add specs for the Rails adapter. * Fix Set-Cookie headers in Rails adapter to handle multiple values correctly. * Fix Ruby 1.9 incompatibility in Response#headers= and Rakefile. * Fix parser to be Ruby 1.9 compatible [Aman Gupta] * Set gemspec to use EventMachine version 0.8.1 as it's the latest one having precompiled windows binaries. [Francis Cianfrocca]. * Add -D option to thin script to set debugging on. * Output incoming data and response when debugging is on. == 0.5.0 * Full rewrite to use EventMachine, Rack and Mongrel parser == 0.4.1 * Fix Rails environment option not being used in thin script. == 0.4.0 * First alphaish release as a gem. thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/Gemfile000066400000000000000000000003151475433514000206520ustar00rootroot00000000000000source "https://rubygems.org" gemspec group :development do gem "rake-compiler" gem "rdoc" gem "benchmark" end group :test do gem "rake", ">= 12.3.3" gem "rspec", "~> 3.5" gem "ostruct" end thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/README.md000066400000000000000000000033031475433514000206360ustar00rootroot00000000000000# Thin A small and fast Ruby web server ## Installation ``` gem install thin ``` Or add `thin` to your `Gemfile`: ```ruby gem 'thin' ``` ## Usage A +thin+ script offers an easy way to start your Rack application: ``` thin start ``` Browse the `example` directory for sample applications. ### CLI Use a rackup (config.ru) file and bind to localhost port 8080: ``` thin -R config.ru -a 127.0.0.1 -p 8080 start ``` Store the server process ID, log to a file and daemonize: ``` thin -p 9292 -P tmp/pids/thin.pid -l logs/thin.log -d start ``` Thin is quite flexible in that many options can be specified at the command line (see `thin -h` for more). ### Configuration files You can create a configuration file using `thin config -C config/thin.yml`. You can then use it with all commands, such as: `thin start -C config/thin.yml`. Here is an example config file: ```yaml --- user: www-data group: www-data pid: tmp/pids/thin.pid timeout: 30 wait: 30 log: log/thin.log max_conns: 1024 require: [] environment: production max_persistent_conns: 512 servers: 1 threaded: true no-epoll: true daemonize: true socket: tmp/sockets/thin.sock chdir: /path/to/your/apps/root tag: a-name-to-show-up-in-ps aux ``` ## License Ruby License, http://www.ruby-lang.org/en/LICENSE.txt. ## Credits The parser was originally from Mongrel http://mongrel.rubyforge.org by Zed Shaw. Mongrel is copyright 2007 Zed A. Shaw and contributors. It is licensed under the Ruby license and the GPL2. Thin is copyright Marc-Andre Cournoyer Get help at http://groups.google.com/group/thin-ruby/ Report bugs at https://github.com/macournoyer/thin/issues and major security issues directly to me at macournoyer@gmail.com. thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/Rakefile000066400000000000000000000007041475433514000210260ustar00rootroot00000000000000require 'rake' require 'rake/clean' load 'thin.gemspec' # Load tasks in tasks/ Dir['tasks/**/*.rake'].each { |rake| load rake } task :default => :spec desc "Build gem packages" task :build do sh "gem build thin.gemspec" end desc "Push gem packages" task :push => :build do sh "gem push thin-*.gem" end task :install => :build do sh "gem install thin-*.gem" end desc "Release version #{Thin::VERSION::STRING}" task :release => [:tag, :push] thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/benchmark/000077500000000000000000000000001475433514000213125ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/benchmark/abc000077500000000000000000000033751475433514000217750ustar00rootroot00000000000000#!/usr/bin/env ruby # Automate benchmarking with ab with various concurrency levels. require 'optparse' options = { :address => '0.0.0.0', :port => 3000, :requests => 1000, :start => 1, :end => 100, :step => 10 } OptionParser.new do |opts| opts.banner = "Usage: #{$PROGRAM_NAME} [options]" opts.on("-n", "--requests NUM", "Number of requests") { |num| options[:requests] = num } opts.on("-a", "--address HOST", "Address (default: 0.0.0.0)") { |host| options[:address] = host } opts.on("-p", "--port PORT", "use PORT (default: 3000)") { |port| options[:port] = port.to_i } opts.on("-s", "--start N", "First concurrency level") { |n| options[:start] = n.to_i } opts.on("-e", "--end N", "Last concurrency level") { |n| options[:end] = n.to_i } opts.on("-S", "--step N", "Concurrency level step") { |n| options[:step] = n.to_i } opts.on("-u", "--uri PATH", "Path to send to") { |u| options[:uri] = u } opts.on("-k", "--keep-alive", "Use Keep-Alive") { options[:keep_alive] = true } opts.on_tail("-h", "--help", "Show this message") { puts opts; exit } end.parse!(ARGV) puts 'request concurrency req/s failures' puts '=' * 42 c = options[:start] until c >= options[:end] sleep 0.5 out = `nice -n20 ab #{'-k' if options[:keep_alive]} -c #{c} -n #{options[:requests]} #{options[:address]}:#{options[:port]}/#{options[:uri]} 2> /dev/null` r = if requests = out.match(/^Requests.+?(\d+\.\d+)/) requests[1].to_i else 0 end f = if requests = out.match(/^Failed requests.+?(\d+)/) requests[1].to_i else 0 end puts "#{options[:requests].to_s.ljust(9)} #{c.to_s.ljust(13)} #{r.to_s.ljust(8)} #{f}" c += options[:step] endthin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/benchmark/benchmarker.rb000066400000000000000000000033611475433514000241230ustar00rootroot00000000000000require 'rack/lobster' class Benchmarker PORT = 7000 ADDRESS = '0.0.0.0' attr_accessor :requests, :concurrencies, :servers, :keep_alive def initialize @servers = %w(Mongrel EMongrel Thin) @requests = 1000 @concurrencies = [1, 10, 100] end def writer(&block) @writer = block end def run! @concurrencies.each do |concurrency| @servers.each do |server| req_sec, failed = run_one(server, concurrency) @writer.call(server, @requests, concurrency, req_sec, failed) end end end private def start_server(handler_name) @server = fork do [STDOUT, STDERR].each { |o| o.reopen "/dev/null" } case handler_name when 'EMongrel' require 'swiftcore/evented_mongrel' handler_name = 'Mongrel' end app = proc do |env| [200, {'content-type' => 'text/html', 'content-length' => '11'}, ['hello world']] end handler = Rack::Handler.const_get(handler_name) handler.run app, :Host => ADDRESS, :Port => PORT end sleep 2 end def stop_server Process.kill('SIGKILL', @server) Process.wait end def run_ab(concurrency) `nice -n20 ab -c #{concurrency} -n #{@requests} #{@keep_alive ? '-k' : ''} #{ADDRESS}:#{PORT}/ 2> /dev/null` end def run_one(handler_name, concurrency) start_server(handler_name) out = run_ab(concurrency) stop_server req_sec = if matches = out.match(/^Requests.+?(\d+\.\d+)/) matches[1].to_i else 0 end failed = if matches = out.match(/^Failed requests.+?(\d+)/) matches[1].to_i else 0 end [req_sec, failed] end endthin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/benchmark/runner000077500000000000000000000045411475433514000225550ustar00rootroot00000000000000#!/usr/bin/env ruby # Simple benchmark to compare Thin performance against # other webservers supported by Rack. # # Run with: # # ruby simple.rb [num of request] [print|graph] [concurrency levels] # $: << File.join(File.dirname(__FILE__), '..', 'lib') require 'thin' require File.dirname(__FILE__) + '/benchmarker' require 'optparse' options = { :requests => 1000, :concurrencies => [1, 10, 100], :keep_alive => false, :output => :table } OptionParser.new do |opts| opts.banner = "Usage: #{$PROGRAM_NAME} [options]" opts.on("-n", "--requests NUM", "Number of requests") { |num| options[:requests] = num.to_i } opts.on("-c", "--concurrencies EXP", "Concurrency levels") { |exp| options[:concurrencies] = eval(exp).to_a } opts.on("-k", "--keep-alive", "Use persistent connections") { options[:keep_alive] = true } opts.on("-t", "--table", "Output as text table") { options[:output] = :table } opts.on("-g", "--graph", "Output as graph") { options[:output] = :graph } opts.on_tail("-h", "--help", "Show this message") { puts opts; exit } end.parse!(ARGV) # benchmark output_type, %w(WEBrick Mongrel EMongrel Thin), request, levels b = Benchmarker.new b.requests = options[:requests] b.concurrencies = options[:concurrencies] b.keep_alive = options[:keep_alive] case options[:output] when :table puts 'server request concurrency req/s failures' puts '=' * 52 b.writer do |server, requests, concurrency, req_sec, failed| puts "#{server.ljust(8)} #{requests} #{concurrency.to_s.ljust(4)} #{req_sec.to_s.ljust(8)} #{failed}" end b.run! when :graph require '/usr/local/lib/ruby/gems/1.8/gems/gruff-0.2.9/lib/gruff' g = Gruff::Area.new g.title = "#{options[:requests]} requests" g.title << ' w/ Keep-Alive' if options[:keep_alive] g.x_axis_label = 'Concurrency' g.y_axis_label = 'Requests / sec' g.maximum_value = 0 g.minimum_value = 0 g.labels = {} b.concurrencies.each_with_index { |c, i| g.labels[i] = c.to_s } results = {} b.writer do |server, requests, concurrency, req_sec, failed| print '.' results[server] ||= [] results[server] << req_sec end b.run! puts results.each do |server, concurrencies| g.data(server, concurrencies) end g.write('bench.png') `open bench.png` end thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/bin/000077500000000000000000000000001475433514000201305ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/bin/thin000077500000000000000000000002201475433514000210120ustar00rootroot00000000000000#!/usr/bin/env ruby # Thin command line interface script. # Run thin -h to get more usage. require 'thin' Thin::Runner.new(ARGV).run! thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/example/000077500000000000000000000000001475433514000210135ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/example/adapter.rb000066400000000000000000000012261475433514000227610ustar00rootroot00000000000000# Run with: ruby adapter.rb # Then browse to http://localhost:3000/test # and http://localhost:3000/files/adapter.rb require 'thin' class SimpleAdapter def call(env) body = ["hello!"] [ 200, { 'content-type' => 'text/plain' }, body ] end end Thin::Server.start('0.0.0.0', 3000) do use Rack::CommonLogger map '/test' do run SimpleAdapter.new end map '/files' do run Rack::Files.new('.') end end # You could also start the server like this: # # app = Rack::URLMap.new('/test' => SimpleAdapter.new, # '/files' => Rack::Files.new('.')) # Thin::Server.start('0.0.0.0', 3000, app) # thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/example/async_app.ru000077500000000000000000000074151475433514000233520ustar00rootroot00000000000000#!/usr/bin/env rackup -s thin # # async_app.ru # raggi/thin # # A second demo app for async rack + thin app processing! # Now using http status code 100 instead. # # Created by James Tucker on 2008-06-17. # Copyright 2008 James Tucker . # #-- # Benchmark Results: # # raggi@mbk:~$ ab -c 100 -n 500 http://127.0.0.1:3000/ # This is ApacheBench, Version 2.0.40-dev <$Revision: 1.146 $> apache-2.0 # Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ # Copyright 2006 The Apache Software Foundation, http://www.apache.org/ # # Benchmarking 127.0.0.1 (be patient) # Completed 100 requests # Completed 200 requests # Completed 300 requests # Completed 400 requests # Finished 500 requests # # # Server Software: thin # Server Hostname: 127.0.0.1 # Server Port: 3000 # # Document Path: / # Document Length: 12 bytes # # Concurrency Level: 100 # Time taken for tests: 5.263089 seconds # Complete requests: 500 # Failed requests: 0 # Write errors: 0 # Total transferred: 47000 bytes # HTML transferred: 6000 bytes # Requests per second: 95.00 [#/sec] (mean) # Time per request: 1052.618 [ms] (mean) # Time per request: 10.526 [ms] (mean, across all concurrent requests) # Transfer rate: 8.55 [Kbytes/sec] received # # Connection Times (ms) # min mean[+/-sd] median max # Connect: 0 3 2.2 3 8 # Processing: 1042 1046 3.1 1046 1053 # Waiting: 1037 1042 3.6 1041 1050 # Total: 1045 1049 3.1 1049 1057 # # Percentage of the requests served within a certain time (ms) # 50% 1049 # 66% 1051 # 75% 1053 # 80% 1053 # 90% 1054 # 95% 1054 # 98% 1056 # 99% 1057 # 100% 1057 (longest request) class DeferrableBody include EventMachine::Deferrable def call(body) body.each do |chunk| @body_callback.call(chunk) end end def each &blk @body_callback = blk end end class AsyncApp # This is a template async response. N.B. Can't use string for body on 1.9 AsyncResponse = [-1, {}, []].freeze def call(env) body = DeferrableBody.new # Get the headers out there asap, let the client know we're alive... EventMachine::next_tick { env['async.callback'].call [200, {'content-type' => 'text/plain'}, body] } # Semi-emulate a long db request, instead of a timer, in reality we'd be # waiting for the response data. Whilst this happens, other connections # can be serviced. # This could be any callback based thing though, a deferrable waiting on # IO data, a db request, an http request, an smtp send, whatever. EventMachine::add_timer(1) { body.call ["Woah, async!\n"] EventMachine::next_tick { # This could actually happen any time, you could spawn off to new # threads, pause as a good looking lady walks by, whatever. # Just shows off how we can defer chunks of data in the body, you can # even call this many times. body.call ["Cheers then!"] body.succeed } } # throw :async # Still works for supporting non-async frameworks... AsyncResponse # May end up in Rack :-) end end # The additions to env for async.connection and async.callback absolutely # destroy the speed of the request if Lint is doing it's checks on env. # It is also important to note that an async response will not pass through # any further middleware, as the async response notification has been passed # right up to the webserver, and the callback goes directly there too. # Middleware could possibly catch :async, and also provide a different # async.connection and async.callback. # use Rack::Lint run AsyncApp.new thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/example/async_chat.ru000077500000000000000000000137761475433514000235200ustar00rootroot00000000000000#!/usr/bin/env rackup -s thin # # async_chat.ru # raggi/thin # # Created by James Tucker on 2008-06-19. # Copyright 2008 James Tucker . # Uncomment if appropriate for you.. EM.epoll # EM.kqueue # bug on OS X in 0.12? class DeferrableBody include EventMachine::Deferrable def initialize @queue = [] end def schedule_dequeue return unless @body_callback EventMachine::next_tick do next unless body = @queue.shift body.each do |chunk| @body_callback.call(chunk) end schedule_dequeue unless @queue.empty? end end def call(body) @queue << body schedule_dequeue end def each &blk @body_callback = blk schedule_dequeue end end class Chat module UserBody attr_accessor :username end def initialize @users = {} end def render_page [] << <<-EOPAGE Async Chat
Your first message will become your nickname! Users: #{@users.map{|k,u|u.username}.join(', ')}
EOPAGE end def register_user(user_id, renderer) body = create_user(user_id) body.call render_page body.errback { delete_user user_id } body.callback { delete_user user_id } EventMachine::next_tick do renderer.call [200, {'content-type' => 'text/html'}, body] end end def new_message(user_id, message) return unless @users[user_id] if @users[user_id].username == :anonymous username = unique_username(message) log "User: #{user_id} is #{username}" @users[user_id].username = message message = "-> #{username} signed on." end username ||= @users[user_id].username log "User: #{username} sent: #{message}" @users.each do |id, body| EventMachine::next_tick { body.call [js_message(username, message)] } end end private def unique_username(name) name.concat('_') while @users.any? { |id,u| name == u.username } name end def log(str) print str, "\n" end def add_user(id, body) @users[id] = body end def delete_user(id) message = "User: #{id} - #{@users[id].username if @users[id]} disconnected." log message new_message(id, message) @users.delete id end def js_message(username, message) %() end def create_user(id) message = "User: #{id} connected." log message new_message(id, message) body = DeferrableBody.new body.extend UserBody body.username = :anonymous add_user(id, body) body end end class AsyncChat AsyncResponse = [-1, {}, []].freeze AjaxResponse = [200, {}, []].freeze def initialize @chat = Chat.new end def call(env) request = Rack::Request.new(env) # TODO - cookie me, baby user_id = request.env['REMOTE_ADDR'] if request.xhr? message = request['message'] @chat.new_message(user_id, Rack::Utils.escape_html(message)) AjaxResponse else renderer = request.env['async.callback'] @chat.register_user(user_id, renderer) AsyncResponse end end end run AsyncChat.new thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/example/async_tailer.ru000077500000000000000000000035031475433514000240440ustar00rootroot00000000000000#!/usr/bin/env rackup -s thin # # async_tailer.ru # raggi/thin # # Tested with 150 spawned tails on OS X # # Created by James Tucker on 2008-06-18. # Copyright 2008 James Tucker . # Uncomment if appropriate for you.. # EM.epoll # EM.kqueue class DeferrableBody include EventMachine::Deferrable def initialize @queue = [] # make sure to flush out the queue before closing the connection callback{ until @queue.empty? @queue.shift.each{|chunk| @body_callback.call(chunk) } end } end def schedule_dequeue return unless @body_callback EventMachine::next_tick do next unless body = @queue.shift body.each do |chunk| @body_callback.call(chunk) end schedule_dequeue unless @queue.empty? end end def call(body) @queue << body schedule_dequeue end def each &blk @body_callback = blk schedule_dequeue end end module TailRenderer attr_accessor :callback def receive_data(data) @callback.call([data]) end def unbind @callback.succeed end end class AsyncTailer AsyncResponse = [-1, {}, []].freeze def call(env) body = DeferrableBody.new EventMachine::next_tick do env['async.callback'].call [200, {'content-type' => 'text/html'}, body] body.call ["

Async Tailer

"]
      
    end
    
    EventMachine::popen('tail -f /var/log/system.log', TailRenderer) do |t|
      
      t.callback = body
      
      # If for some reason we 'complete' body, close the tail.
      body.callback do
        t.close_connection
      end
      
      # If for some reason the client disconnects, close the tail.
      body.errback do
        t.close_connection
      end
      
    end
    
    AsyncResponse
  end
  
end

run AsyncTailer.new
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/example/config.ru000066400000000000000000000012501475433514000226260ustar00rootroot00000000000000# Run with: rackup -s thin
# then browse to http://localhost:9292
# Or with: thin start -R config.ru
# then browse to http://localhost:3000
# 
# Check Rack::Builder doc for more details on this file format:
#  http://rack.rubyforge.org/doc/classes/Rack/Builder.html
require 'thin'

app = proc do |env|
  # Response body has to respond to each and yield strings
  # See Rack specs for more info: http://rack.rubyforge.org/doc/files/SPEC.html
  body = ['hi!']
  
  [
    200,                                        # Status code
    { 'content-type' => 'text/html' },          # Reponse headers
    body                                        # Body of the response
  ]
end

run appthin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/example/monit_sockets000066400000000000000000000017621475433514000236250ustar00rootroot00000000000000check process blog1
    with pidfile /u/apps/blog/shared/pids/thin.14000.pid
    start program = "ruby thin start -d -e production -u nobody -g nobody -p 14000 -a 127.0.0.1 -P tmp/pids/thin.14000.pid -c /u/apps/blog/current"
    stop program  = "ruby thin stop -P /u/apps/blog/shared/pids/thin.14000.pid"
    if totalmem > 90.0 MB for 5 cycles then restart
    if failed port 14000 then restart
    if cpu usage > 95% for 3 cycles then restart
    if 5 restarts within 5 cycles then timeout
		group blog

check process blog2
    with pidfile /u/apps/blog/shared/pids/thin.14001.pid
    start program = "ruby thin start -d -e production -u nobody -g nobody -p 14001 -a 127.0.0.1 -P tmp/pids/thin.14001.pid -c /u/apps/blog/current"
    stop program  = "ruby thin stop -P /u/apps/blog/shared/pids/thin.14001.pid"
    if totalmem > 90.0 MB for 5 cycles then restart
    if failed port 14001 then restart
    if cpu usage > 95% for 3 cycles then restart
    if 5 restarts within 5 cycles then timeout
		group blog

thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/example/monit_unixsock000066400000000000000000000020341475433514000240060ustar00rootroot00000000000000check process blog1
    with pidfile /u/apps/blog/shared/pids/thin.1.pid
    start program = "ruby thin start -d -e production -S /u/apps/blog/shared/pids/thin.1.sock -P tmp/pids/thin.1.pid -c /u/apps/blog/current"
    stop program  = "ruby thin stop -P /u/apps/blog/shared/pids/thin.1.pid"
    if totalmem > 90.0 MB for 5 cycles then restart
		if failed unixsocket /u/apps/blog/shared/pids/thin.1.sock then restart
    if cpu usage > 95% for 3 cycles then restart
    if 5 restarts within 5 cycles then timeout
		group blog

check process blog2
    with pidfile /u/apps/blog/shared/pids/thin.2.pid
    start program = "ruby thin start -d -e production -S /u/apps/blog/shared/pids/thin.2.sock -P tmp/pids/thin.2.pid -c /u/apps/blog/current"
    stop program  = "ruby thin stop -P /u/apps/blog/shared/pids/thin.2.pid"
    if totalmem > 90.0 MB for 5 cycles then restart
		if failed unixsocket /u/apps/blog/shared/pids/thin.2.sock then restart
    if cpu usage > 95% for 3 cycles then restart
    if 5 restarts within 5 cycles then timeout
		group blog

thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/example/myapp.rb000066400000000000000000000000651475433514000224670ustar00rootroot00000000000000Myapp = lambda { |env| [200, {}, 'this is my app!'] }thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/example/ramaze.ru000066400000000000000000000003361475433514000226440ustar00rootroot00000000000000# Ramaze Rackup config file.
# by tmm1
# Use with --rackup option:
# 
#   thin start -r ramaze.ru
# 
require 'start'

Ramaze.trait[:essentials].delete Ramaze::Adapter
Ramaze.start :force => true

run Ramaze::Adapter::Base
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/example/thin.god000066400000000000000000000042361475433514000224550ustar00rootroot00000000000000# == God config file
# http://god.rubyforge.org/
# Authors: Gump and michael@glauche.de
#
# Config file for god that configures watches for each instance of a thin server for
# each thin configuration file found in /etc/thin.
# In order to get it working on Ubuntu, I had to make a change to god as noted at
# the following blog:
# http://blog.alexgirard.com/ruby-one-line-to-save-god/
#
require 'yaml'

config_path = "/etc/thin"

Dir[config_path + "/*.yml"].each do |file|
  config = YAML.load_file(file)
  num_servers = config["servers"] ||= 1

  (0...num_servers).each do |i|
    # UNIX socket cluster use number 0 to 2 (for 3 servers)
    # and tcp cluster use port number 3000 to 3002.
    number = config['socket'] ? i : (config['port'] + i)
    
    God.watch do |w|
      w.group = "thin-" + File.basename(file, ".yml")
      w.name = w.group + "-#{number}"
      
      w.interval = 30.seconds
      
      w.uid = config["user"]
      w.gid = config["group"]
      
      w.start = "thin start -C #{file} -o #{number}"
      w.start_grace = 10.seconds
      
      w.stop = "thin stop -C #{file} -o #{number}"
      w.stop_grace = 10.seconds
      
      w.restart = "thin restart -C #{file} -o #{number}"

      pid_path = config["chdir"] + "/" + config["pid"]
      ext = File.extname(pid_path)

      w.pid_file = pid_path.gsub(/#{ext}$/, ".#{number}#{ext}")
      
      w.behavior(:clean_pid_file)

      w.start_if do |start|
        start.condition(:process_running) do |c|
          c.interval = 5.seconds
          c.running = false
        end
      end

      w.restart_if do |restart|
        restart.condition(:memory_usage) do |c|
          c.above = 150.megabytes
          c.times = [3,5] # 3 out of 5 intervals
        end

        restart.condition(:cpu_usage) do |c|
          c.above = 50.percent
          c.times = 5
        end
      end

      w.lifecycle do |on|
        on.condition(:flapping) do |c|
          c.to_state = [:start, :restart]
          c.times = 5
          c.within = 5.minutes
          c.transition = :unmonitored
          c.retry_in = 10.minutes
          c.retry_times = 5
          c.retry_within = 2.hours
        end
      end
    end
  end
endthin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/example/thin_solaris_smf.erb000066400000000000000000000035331475433514000250540ustar00rootroot00000000000000


  
    
    
      
    
    
      
      
      
    
    <% 0.upto(thin_max_instances - 1) do |instance| %>
    
    
      
      
        
      
      
        
          
          
            
          
        
      
      
    
    <% end %>
  


thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/example/thin_solaris_smf.readme.txt000066400000000000000000000121411475433514000263520ustar00rootroot00000000000000Using Thin with Solaris' SMF Monitoring Framework
- - - - - - - - - - - - - - - - - - - - - - - - -

Solaris uses the Service Management Framework (SMF) at the OS level to manage, monitor, and restart long running processes.  This replaces init scripts, and tools like monit and god.

The sample XML file (thin_solaris_smf.erb) is an example SMF manifest which I use on a Joyent accelerator which runs on OpenSolaris.

This setup will:

- ensure the right dependencies are loaded
- start n instances of Thin, and monitor each individually.  If any single one dies it will be restarted instantly (test it by killing a single thin instance and it will be back alive before you can type 'ps -ef').

This is better than using clustering since if you start the cluster with SMF it will only notice a problem and restart individual Thin's if ALL of them are dead, at which point it will restart the whole cluster.  This approach makes sure that all of your Thins start together and are monitored and managed independant of each other.  This problem likely exists if you are using god or monit to monitor only the start of the master cluster, and don't then monitor the individual processes started.

This example is in .erb format instead of plain XML since I dynamically generate this file as part of a Capistrano deployment.  In my deploy.rb file I define the variables found in this erb.  Of course you don't need to use this with Capistrano.  Just replace the few ERB variables from the xml file, change its extension, and load that directly in Solaris if you prefer.

Here are some examples for usage to get you started with Capistrano, and Thin:

FILE : config/deploy.rb
--

  require 'config/accelerator/accelerator_tasks'

  set :application, "yourapp"
  set :svcadm_bin, "/usr/sbin/svcadm"
  set :svccfg_bin, "/usr/sbin/svccfg"
  set :svcs_bin, "/usr/bin/svcs"

  # gets the list of remote service SMF names that we need to start
  # like (depending on thin_max_instances settings):
  # svc:/network/thin/yourapp-production:i_0
  # svc:/network/thin/yourapp-production:i_1
  # svc:/network/thin/yourapp-production:i_2
  set :service_list, "`svcs -H -o FMRI svc:network/thin/#{application}-production`"

  # how many Thin instances should be setup to run?
  # this affects the generated thin smf file, and the nginx vhost conf
  # need to re-run setup for thin smf and nginx vhost conf when changed
  set :thin_max_instances, 3

  # OVERRIDE STANDARD TASKS
  desc "Restart the entire application"
  deploy.task :restart do
    accelerator.thin.restart
    accelerator.nginx.restart
  end

  desc "Start the entire application"
  deploy.task :start do
    accelerator.thin.restart
    accelerator.nginx.restart
  end

  desc "Stop the entire application"
  deploy.task :stop do
    accelerator.thin.disable
    accelerator.nginx.disable
  end


FILE : config/accelerator/accelerator_tasks.rb
--

    desc "Create and deploy Thin SMF config"
    task :create_thin_smf, :roles => :app do
      service_name = application
      working_directory = current_path
      template = File.read("config/accelerator/thin_solaris_smf.erb")
      buffer = ERB.new(template).result(binding)
      put buffer, "#{shared_path}/#{application}-thin-smf.xml"
      sudo "#{svccfg_bin} import #{shared_path}/#{application}-thin-smf.xml"
    end

    desc "Delete Thin SMF config"
    task :delete_thin_smf, :roles => :app do
      accelerator.thin.disable
      sudo "#{svccfg_bin} delete /network/thin/#{application}-production"
    end

    desc "Show all SMF services"
    task :svcs do
      run "#{svcs_bin} -a" do |ch, st, data|
        puts data
      end
    end

    desc "Shows all non-functional SMF services"
    task :svcs_broken do
      run "#{svcs_bin} -vx" do |ch, st, data|
        puts data
      end
    end


    namespace :thin do

      desc "Disable all Thin servers"
      task :disable, :roles => :app do
        # temporarily disable, until next reboot (-t)
        sudo "#{svcadm_bin} disable -t #{service_list}"
      end

      desc "Enable all Thin servers"
      task :enable, :roles => :app do
        # start the app with all recursive dependencies
        sudo "#{svcadm_bin} enable -r #{service_list}"
      end

      desc "Restart all Thin servers"
      task :restart, :roles => :app do
        # svcadm restart doesn't seem to work right, so we'll brute force it
        disable
        enable
      end

    end # namespace thin


FILE : config/thin.yml
--

---
pid: tmp/pids/thin.pid
socket: /tmp/thin.sock
log: log/thin.log
max_conns: 1024
timeout: 30
chdir: /your/app/dir/rails/root
environment: production
max_persistent_conns: 512
daemonize: true
servers: 3


FILE : config/accelerator/thin_solaris_smf.erb
--
This is of course an example.  It works for me, but YMMV

You may need to change this line to match your environment and config:
  exec='/opt/csw/bin/thin -C config/thin.yml --only <%= instance.to_s %> start'


CONTRIBUTE:

If you see problems or enhancements for this approach please send me an email at glenn [at] rempe dot us.  Sadly, I won't be able to provide support for this example as time and my limited Solaris admin skills won't allow.

Cheers,

Glenn Rempe
2008/03/20
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/example/vlad.rake000066400000000000000000000041371475433514000226120ustar00rootroot00000000000000# $GEM_HOME/gems/vlad-1.2.0/lib/vlad/thin.rb
# Thin tasks for Vlad the Deployer
# By cnantais
require 'vlad'

namespace :vlad do
  ##
  # Thin app server

  set :thin_address,       nil
  set :thin_command,       "thin"
  set(:thin_conf)          { "#{shared_path}/thin_cluster.conf" }
  set :thin_environment,   "production"
  set :thin_group,         nil
  set :thin_log_file,      nil
  set :thin_pid_file,      nil
  set :thin_port,          nil
  set :thin_socket,        nil
  set :thin_prefix,        nil
  set :thin_servers,       2
  set :thin_user,          nil
  
  set :thin_uses_bundler,  true

  desc "Prepares application servers for deployment. thin
configuration is set via the thin_* variables.".cleanup

  remote_task :setup_app, :roles => :app do
  
    raise(ArgumentError, "Please provide either thin_socket or thin_port") if thin_port.nil? && thin_socket.nil?
  
    cmd = [
           "config",
           (%(-s "#{thin_servers}") if thin_servers),
           (%(-S "#{thin_socket}") if thin_socket),
           (%(-e "#{thin_environment}") if thin_environment),
           (%(-a "#{thin_address}") if thin_address),
           %(-c "#{current_path}"),
           (%(-C "#{thin_conf}") if thin_conf),
           (%(-P "#{thin_pid_file}") if thin_pid_file),
           (%(-l "#{thin_log_file}") if thin_log_file),
           (%(--user "#{thin_user}") if thin_user),
           (%(--group "#{thin_group}") if thin_group),
           (%(--prefix "#{thin_prefix}") if thin_prefix),
           (%(-p "#{thin_port}") if thin_port),
          ].compact.join ' '

    thin(cmd)
  end

  def thin(cmd) # :nodoc:
    command = if thin_uses_bundler
      %(BUNDLE_GEMFILE="#{current_path}/Gemfile" bundle exec #{thin_command} #{cmd} -C "#{thin_conf}")
    else
      %(#{thin_command} #{cmd} -C "#{thin_conf}")
    end

    %(cd "#{current_path}" && #{command})
  end

  desc "Restart the app servers"

  remote_task :start_app, :roles => :app do
    run thin(%(restart -O -s "#{thin_servers}"))
  end

  desc "Stop the app servers"

  remote_task :stop_app, :roles => :app do
    run thin(%(stop -s "#{thin_servers}"))
  end
end
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/ext/000077500000000000000000000000001475433514000201605ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/ext/thin_parser/000077500000000000000000000000001475433514000224765ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/ext/thin_parser/common.rl000066400000000000000000000040621475433514000243270ustar00rootroot00000000000000%%{
  
  machine http_parser_common;

#### HTTP PROTOCOL GRAMMAR
# line endings
  CRLF = "\r\n";

# character types
  CTL = (cntrl | 127);
  safe = ("$" | "-" | "_" | ".");
  extra = ("!" | "*" | "'" | "(" | ")" | ",");
  reserved = (";" | "/" | "?" | ":" | "@" | "&" | "=" | "+");
  sorta_safe = ("\"" | "<" | ">");
  unsafe = (CTL | " " | "#" | "%" | sorta_safe);
  national = any -- (alpha | digit | reserved | extra | safe | unsafe);
  unreserved = (alpha | digit | safe | extra | national);
  escape = ("%" "u"? xdigit xdigit);
  uchar = (unreserved | escape | sorta_safe);
  pchar = (uchar | ":" | "@" | "&" | "=" | "+");
  tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t");

# elements
  token = (ascii -- (CTL | tspecials));

# URI schemes and absolute paths
  scheme = ( "http"i ("s"i)? );
  hostname = ((alnum | "-" | "." | "_")+ | ("[" (":" | xdigit)+ "]"));
  host_with_port = (hostname (":" digit*)?);
  userinfo = ((unreserved | escape | ";" | ":" | "&" | "=" | "+")+ "@")*;

  path = ( pchar+ ( "/" pchar* )* ) ;
  query = ( uchar | reserved )* %query_string ;
  param = ( pchar | "/" )* ;
  params = ( param ( ";" param )* ) ;
  rel_path = (path? (";" params)? %request_path) ("?" %start_query query)?;
  absolute_path = ( "/"+ rel_path );
  path_uri = absolute_path > mark %request_uri;
  Absolute_URI = (scheme "://" userinfo host_with_port path_uri);

  Request_URI = ((absolute_path | "*") >mark %request_uri) | Absolute_URI;
  Fragment = ( uchar | reserved )* >mark %fragment;
  Method = ( upper | digit | safe ){1,20} >mark %request_method;

  http_number = ( digit+ "." digit+ ) ;
  HTTP_Version = ( "HTTP/" http_number ) >mark %request_http_version ;
  Request_Line = ( Method " " Request_URI ("#" Fragment){0,1} " " HTTP_Version CRLF ) ;

  field_name = ( token -- ":" )+ >start_field %write_field;

  field_value = any* >start_value %write_value;

  message_header = field_name ":" " "* field_value :> CRLF;

  Request = Request_Line ( message_header )* ( CRLF @done );

main := Request;

}%%
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/ext/thin_parser/ext_help.h000066400000000000000000000007511475433514000244620ustar00rootroot00000000000000#ifndef ext_help_h
#define ext_help_h

#define RAISE_NOT_NULL(T) if(T == NULL) rb_raise(rb_eArgError, "NULL found for " # T " when shouldn't be.");
#define DATA_GET(from,type,name) Data_Get_Struct(from,type,name); RAISE_NOT_NULL(name);
#define REQUIRE_TYPE(V, T) if(TYPE(V) != T) rb_raise(rb_eTypeError, "Wrong argument type for " # V " required " # T);

#ifdef DEBUG
#define TRACE()  fprintf(stderr, "> %s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__)
#else
#define TRACE() 
#endif

#endif
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/ext/thin_parser/extconf.rb000066400000000000000000000001441475433514000244700ustar00rootroot00000000000000require 'mkmf'

dir_config("thin_parser")
have_library("c", "main")

create_makefile("thin_parser")
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/ext/thin_parser/parser.c000066400000000000000000000656221475433514000241510ustar00rootroot00000000000000
#line 1 "parser.rl"
/**
 * Copyright (c) 2005 Zed A. Shaw
 * You can redistribute it and/or modify it under the same terms as Ruby.
 */
#include "parser.h"
#include 
#include 
#include 
#include 
#include 

#define LEN(AT, FPC) (FPC - buffer - parser->AT)
#define MARK(M,FPC) (parser->M = (FPC) - buffer)
#define PTR_TO(F) (buffer + parser->F)

/** Machine **/


#line 81 "parser.rl"


/** Data **/

#line 27 "parser.c"
static const int http_parser_start = 1;
static const int http_parser_first_final = 70;
static const int http_parser_error = 0;

static const int http_parser_en_main = 1;


#line 85 "parser.rl"

int thin_http_parser_init(http_parser *parser)  {
  int cs = 0;
  
#line 40 "parser.c"
	{
	cs = http_parser_start;
	}

#line 89 "parser.rl"
  parser->cs = cs;
  parser->body_start = 0;
  parser->content_len = 0;
  parser->mark = 0;
  parser->nread = 0;
  parser->field_len = 0;
  parser->field_start = 0;    

  return(1);
}


/** exec **/
size_t thin_http_parser_execute(http_parser *parser, const char *buffer, size_t len, size_t off)  {
  const char *p, *pe;
  int cs = parser->cs;

  assert(off <= len && "offset past end of buffer");

  p = buffer+off;
  pe = buffer+len;

  assert(*pe == '\0' && "pointer does not end on NUL");
  assert(pe - p == (long)(len - off) && "pointers aren't same distance");


  
#line 73 "parser.c"
	{
	if ( p == pe )
		goto _test_eof;
	switch ( cs )
	{
case 1:
	switch( (*p) ) {
		case 36: goto tr0;
		case 95: goto tr0;
	}
	if ( (*p) < 48 ) {
		if ( 45 <= (*p) && (*p) <= 46 )
			goto tr0;
	} else if ( (*p) > 57 ) {
		if ( 65 <= (*p) && (*p) <= 90 )
			goto tr0;
	} else
		goto tr0;
	goto st0;
st0:
cs = 0;
	goto _out;
tr0:
#line 22 "parser.rl"
	{MARK(mark, p); }
	goto st2;
st2:
	if ( ++p == pe )
		goto _test_eof2;
case 2:
#line 104 "parser.c"
	switch( (*p) ) {
		case 32: goto tr2;
		case 36: goto st51;
		case 95: goto st51;
	}
	if ( (*p) < 48 ) {
		if ( 45 <= (*p) && (*p) <= 46 )
			goto st51;
	} else if ( (*p) > 57 ) {
		if ( 65 <= (*p) && (*p) <= 90 )
			goto st51;
	} else
		goto st51;
	goto st0;
tr2:
#line 36 "parser.rl"
	{ 
    if (parser->request_method != NULL) {
      parser->request_method(parser->data, PTR_TO(mark), LEN(mark, p));
    }
  }
	goto st3;
st3:
	if ( ++p == pe )
		goto _test_eof3;
case 3:
#line 131 "parser.c"
	switch( (*p) ) {
		case 42: goto tr4;
		case 47: goto tr5;
		case 72: goto st34;
		case 104: goto st34;
	}
	goto st0;
tr4:
#line 22 "parser.rl"
	{MARK(mark, p); }
	goto st4;
st4:
	if ( ++p == pe )
		goto _test_eof4;
case 4:
#line 147 "parser.c"
	switch( (*p) ) {
		case 32: goto tr7;
		case 35: goto tr8;
	}
	goto st0;
tr7:
#line 41 "parser.rl"
	{
    if (parser->request_uri != NULL) {
      parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p));
    }
  }
	goto st5;
tr30:
#line 22 "parser.rl"
	{MARK(mark, p); }
#line 46 "parser.rl"
	{ 
    if (parser->fragment != NULL) {
      parser->fragment(parser->data, PTR_TO(mark), LEN(mark, p));
    }
  }
	goto st5;
tr33:
#line 46 "parser.rl"
	{ 
    if (parser->fragment != NULL) {
      parser->fragment(parser->data, PTR_TO(mark), LEN(mark, p));
    }
  }
	goto st5;
tr38:
#line 65 "parser.rl"
	{
    if (parser->request_path != NULL) {
      parser->request_path(parser->data, PTR_TO(mark), LEN(mark,p));
    }
  }
#line 41 "parser.rl"
	{
    if (parser->request_uri != NULL) {
      parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p));
    }
  }
	goto st5;
tr45:
#line 52 "parser.rl"
	{MARK(query_start, p); }
#line 53 "parser.rl"
	{ 
    if (parser->query_string != NULL) {
      parser->query_string(parser->data, PTR_TO(query_start), LEN(query_start, p));
    }
  }
#line 41 "parser.rl"
	{
    if (parser->request_uri != NULL) {
      parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p));
    }
  }
	goto st5;
tr49:
#line 53 "parser.rl"
	{ 
    if (parser->query_string != NULL) {
      parser->query_string(parser->data, PTR_TO(query_start), LEN(query_start, p));
    }
  }
#line 41 "parser.rl"
	{
    if (parser->request_uri != NULL) {
      parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p));
    }
  }
	goto st5;
st5:
	if ( ++p == pe )
		goto _test_eof5;
case 5:
#line 227 "parser.c"
	if ( (*p) == 72 )
		goto tr9;
	goto st0;
tr9:
#line 22 "parser.rl"
	{MARK(mark, p); }
	goto st6;
st6:
	if ( ++p == pe )
		goto _test_eof6;
case 6:
#line 239 "parser.c"
	if ( (*p) == 84 )
		goto st7;
	goto st0;
st7:
	if ( ++p == pe )
		goto _test_eof7;
case 7:
	if ( (*p) == 84 )
		goto st8;
	goto st0;
st8:
	if ( ++p == pe )
		goto _test_eof8;
case 8:
	if ( (*p) == 80 )
		goto st9;
	goto st0;
st9:
	if ( ++p == pe )
		goto _test_eof9;
case 9:
	if ( (*p) == 47 )
		goto st10;
	goto st0;
st10:
	if ( ++p == pe )
		goto _test_eof10;
case 10:
	if ( 48 <= (*p) && (*p) <= 57 )
		goto st11;
	goto st0;
st11:
	if ( ++p == pe )
		goto _test_eof11;
case 11:
	if ( (*p) == 46 )
		goto st12;
	if ( 48 <= (*p) && (*p) <= 57 )
		goto st11;
	goto st0;
st12:
	if ( ++p == pe )
		goto _test_eof12;
case 12:
	if ( 48 <= (*p) && (*p) <= 57 )
		goto st13;
	goto st0;
st13:
	if ( ++p == pe )
		goto _test_eof13;
case 13:
	if ( (*p) == 13 )
		goto tr17;
	if ( 48 <= (*p) && (*p) <= 57 )
		goto st13;
	goto st0;
tr17:
#line 59 "parser.rl"
	{	
    if (parser->request_http_version != NULL) {
      parser->request_http_version(parser->data, PTR_TO(mark), LEN(mark, p));
    }
  }
	goto st14;
tr25:
#line 30 "parser.rl"
	{ MARK(mark, p); }
#line 31 "parser.rl"
	{ 
    if (parser->http_field != NULL) {
      parser->http_field(parser->data, PTR_TO(field_start), parser->field_len, PTR_TO(mark), LEN(mark, p));
    }
  }
	goto st14;
tr28:
#line 31 "parser.rl"
	{ 
    if (parser->http_field != NULL) {
      parser->http_field(parser->data, PTR_TO(field_start), parser->field_len, PTR_TO(mark), LEN(mark, p));
    }
  }
	goto st14;
st14:
	if ( ++p == pe )
		goto _test_eof14;
case 14:
#line 326 "parser.c"
	if ( (*p) == 10 )
		goto st15;
	goto st0;
st15:
	if ( ++p == pe )
		goto _test_eof15;
case 15:
	switch( (*p) ) {
		case 13: goto st16;
		case 33: goto tr20;
		case 124: goto tr20;
		case 126: goto tr20;
	}
	if ( (*p) < 45 ) {
		if ( (*p) > 39 ) {
			if ( 42 <= (*p) && (*p) <= 43 )
				goto tr20;
		} else if ( (*p) >= 35 )
			goto tr20;
	} else if ( (*p) > 46 ) {
		if ( (*p) < 65 ) {
			if ( 48 <= (*p) && (*p) <= 57 )
				goto tr20;
		} else if ( (*p) > 90 ) {
			if ( 94 <= (*p) && (*p) <= 122 )
				goto tr20;
		} else
			goto tr20;
	} else
		goto tr20;
	goto st0;
st16:
	if ( ++p == pe )
		goto _test_eof16;
case 16:
	if ( (*p) == 10 )
		goto tr21;
	goto st0;
tr21:
#line 71 "parser.rl"
	{ 
    parser->body_start = p - buffer + 1; 
    if (parser->header_done != NULL) {
      parser->header_done(parser->data, p + 1, pe - p - 1);
    }
    {p++; cs = 70; goto _out;}
  }
	goto st70;
st70:
	if ( ++p == pe )
		goto _test_eof70;
case 70:
#line 379 "parser.c"
	goto st0;
tr20:
#line 25 "parser.rl"
	{ MARK(field_start, p); }
	goto st17;
st17:
	if ( ++p == pe )
		goto _test_eof17;
case 17:
#line 389 "parser.c"
	switch( (*p) ) {
		case 33: goto st17;
		case 58: goto tr23;
		case 124: goto st17;
		case 126: goto st17;
	}
	if ( (*p) < 45 ) {
		if ( (*p) > 39 ) {
			if ( 42 <= (*p) && (*p) <= 43 )
				goto st17;
		} else if ( (*p) >= 35 )
			goto st17;
	} else if ( (*p) > 46 ) {
		if ( (*p) < 65 ) {
			if ( 48 <= (*p) && (*p) <= 57 )
				goto st17;
		} else if ( (*p) > 90 ) {
			if ( 94 <= (*p) && (*p) <= 122 )
				goto st17;
		} else
			goto st17;
	} else
		goto st17;
	goto st0;
tr23:
#line 26 "parser.rl"
	{ 
    parser->field_len = LEN(field_start, p);
  }
	goto st18;
tr26:
#line 30 "parser.rl"
	{ MARK(mark, p); }
	goto st18;
st18:
	if ( ++p == pe )
		goto _test_eof18;
case 18:
#line 428 "parser.c"
	switch( (*p) ) {
		case 13: goto tr25;
		case 32: goto tr26;
	}
	goto tr24;
tr24:
#line 30 "parser.rl"
	{ MARK(mark, p); }
	goto st19;
st19:
	if ( ++p == pe )
		goto _test_eof19;
case 19:
#line 442 "parser.c"
	if ( (*p) == 13 )
		goto tr28;
	goto st19;
tr8:
#line 41 "parser.rl"
	{
    if (parser->request_uri != NULL) {
      parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p));
    }
  }
	goto st20;
tr39:
#line 65 "parser.rl"
	{
    if (parser->request_path != NULL) {
      parser->request_path(parser->data, PTR_TO(mark), LEN(mark,p));
    }
  }
#line 41 "parser.rl"
	{
    if (parser->request_uri != NULL) {
      parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p));
    }
  }
	goto st20;
tr46:
#line 52 "parser.rl"
	{MARK(query_start, p); }
#line 53 "parser.rl"
	{ 
    if (parser->query_string != NULL) {
      parser->query_string(parser->data, PTR_TO(query_start), LEN(query_start, p));
    }
  }
#line 41 "parser.rl"
	{
    if (parser->request_uri != NULL) {
      parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p));
    }
  }
	goto st20;
tr50:
#line 53 "parser.rl"
	{ 
    if (parser->query_string != NULL) {
      parser->query_string(parser->data, PTR_TO(query_start), LEN(query_start, p));
    }
  }
#line 41 "parser.rl"
	{
    if (parser->request_uri != NULL) {
      parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p));
    }
  }
	goto st20;
st20:
	if ( ++p == pe )
		goto _test_eof20;
case 20:
#line 502 "parser.c"
	switch( (*p) ) {
		case 32: goto tr30;
		case 35: goto st0;
		case 37: goto tr31;
		case 127: goto st0;
	}
	if ( 0 <= (*p) && (*p) <= 31 )
		goto st0;
	goto tr29;
tr29:
#line 22 "parser.rl"
	{MARK(mark, p); }
	goto st21;
st21:
	if ( ++p == pe )
		goto _test_eof21;
case 21:
#line 520 "parser.c"
	switch( (*p) ) {
		case 32: goto tr33;
		case 35: goto st0;
		case 37: goto st22;
		case 127: goto st0;
	}
	if ( 0 <= (*p) && (*p) <= 31 )
		goto st0;
	goto st21;
tr31:
#line 22 "parser.rl"
	{MARK(mark, p); }
	goto st22;
st22:
	if ( ++p == pe )
		goto _test_eof22;
case 22:
#line 538 "parser.c"
	if ( (*p) == 117 )
		goto st24;
	if ( (*p) < 65 ) {
		if ( 48 <= (*p) && (*p) <= 57 )
			goto st23;
	} else if ( (*p) > 70 ) {
		if ( 97 <= (*p) && (*p) <= 102 )
			goto st23;
	} else
		goto st23;
	goto st0;
st23:
	if ( ++p == pe )
		goto _test_eof23;
case 23:
	if ( (*p) < 65 ) {
		if ( 48 <= (*p) && (*p) <= 57 )
			goto st21;
	} else if ( (*p) > 70 ) {
		if ( 97 <= (*p) && (*p) <= 102 )
			goto st21;
	} else
		goto st21;
	goto st0;
st24:
	if ( ++p == pe )
		goto _test_eof24;
case 24:
	if ( (*p) < 65 ) {
		if ( 48 <= (*p) && (*p) <= 57 )
			goto st23;
	} else if ( (*p) > 70 ) {
		if ( 97 <= (*p) && (*p) <= 102 )
			goto st23;
	} else
		goto st23;
	goto st0;
tr5:
#line 22 "parser.rl"
	{MARK(mark, p); }
	goto st25;
st25:
	if ( ++p == pe )
		goto _test_eof25;
case 25:
#line 584 "parser.c"
	switch( (*p) ) {
		case 32: goto tr38;
		case 35: goto tr39;
		case 37: goto st26;
		case 63: goto tr41;
		case 127: goto st0;
	}
	if ( 0 <= (*p) && (*p) <= 31 )
		goto st0;
	goto st25;
st26:
	if ( ++p == pe )
		goto _test_eof26;
case 26:
	if ( (*p) == 117 )
		goto st28;
	if ( (*p) < 65 ) {
		if ( 48 <= (*p) && (*p) <= 57 )
			goto st27;
	} else if ( (*p) > 70 ) {
		if ( 97 <= (*p) && (*p) <= 102 )
			goto st27;
	} else
		goto st27;
	goto st0;
st27:
	if ( ++p == pe )
		goto _test_eof27;
case 27:
	if ( (*p) < 65 ) {
		if ( 48 <= (*p) && (*p) <= 57 )
			goto st25;
	} else if ( (*p) > 70 ) {
		if ( 97 <= (*p) && (*p) <= 102 )
			goto st25;
	} else
		goto st25;
	goto st0;
st28:
	if ( ++p == pe )
		goto _test_eof28;
case 28:
	if ( (*p) < 65 ) {
		if ( 48 <= (*p) && (*p) <= 57 )
			goto st27;
	} else if ( (*p) > 70 ) {
		if ( 97 <= (*p) && (*p) <= 102 )
			goto st27;
	} else
		goto st27;
	goto st0;
tr41:
#line 65 "parser.rl"
	{
    if (parser->request_path != NULL) {
      parser->request_path(parser->data, PTR_TO(mark), LEN(mark,p));
    }
  }
	goto st29;
st29:
	if ( ++p == pe )
		goto _test_eof29;
case 29:
#line 648 "parser.c"
	switch( (*p) ) {
		case 32: goto tr45;
		case 35: goto tr46;
		case 37: goto tr47;
		case 127: goto st0;
	}
	if ( 0 <= (*p) && (*p) <= 31 )
		goto st0;
	goto tr44;
tr44:
#line 52 "parser.rl"
	{MARK(query_start, p); }
	goto st30;
st30:
	if ( ++p == pe )
		goto _test_eof30;
case 30:
#line 666 "parser.c"
	switch( (*p) ) {
		case 32: goto tr49;
		case 35: goto tr50;
		case 37: goto st31;
		case 127: goto st0;
	}
	if ( 0 <= (*p) && (*p) <= 31 )
		goto st0;
	goto st30;
tr47:
#line 52 "parser.rl"
	{MARK(query_start, p); }
	goto st31;
st31:
	if ( ++p == pe )
		goto _test_eof31;
case 31:
#line 684 "parser.c"
	if ( (*p) == 117 )
		goto st33;
	if ( (*p) < 65 ) {
		if ( 48 <= (*p) && (*p) <= 57 )
			goto st32;
	} else if ( (*p) > 70 ) {
		if ( 97 <= (*p) && (*p) <= 102 )
			goto st32;
	} else
		goto st32;
	goto st0;
st32:
	if ( ++p == pe )
		goto _test_eof32;
case 32:
	if ( (*p) < 65 ) {
		if ( 48 <= (*p) && (*p) <= 57 )
			goto st30;
	} else if ( (*p) > 70 ) {
		if ( 97 <= (*p) && (*p) <= 102 )
			goto st30;
	} else
		goto st30;
	goto st0;
st33:
	if ( ++p == pe )
		goto _test_eof33;
case 33:
	if ( (*p) < 65 ) {
		if ( 48 <= (*p) && (*p) <= 57 )
			goto st32;
	} else if ( (*p) > 70 ) {
		if ( 97 <= (*p) && (*p) <= 102 )
			goto st32;
	} else
		goto st32;
	goto st0;
st34:
	if ( ++p == pe )
		goto _test_eof34;
case 34:
	switch( (*p) ) {
		case 84: goto st35;
		case 116: goto st35;
	}
	goto st0;
st35:
	if ( ++p == pe )
		goto _test_eof35;
case 35:
	switch( (*p) ) {
		case 84: goto st36;
		case 116: goto st36;
	}
	goto st0;
st36:
	if ( ++p == pe )
		goto _test_eof36;
case 36:
	switch( (*p) ) {
		case 80: goto st37;
		case 112: goto st37;
	}
	goto st0;
st37:
	if ( ++p == pe )
		goto _test_eof37;
case 37:
	switch( (*p) ) {
		case 58: goto st38;
		case 83: goto st50;
		case 115: goto st50;
	}
	goto st0;
st38:
	if ( ++p == pe )
		goto _test_eof38;
case 38:
	if ( (*p) == 47 )
		goto st39;
	goto st0;
st39:
	if ( ++p == pe )
		goto _test_eof39;
case 39:
	if ( (*p) == 47 )
		goto st40;
	goto st0;
st40:
	if ( ++p == pe )
		goto _test_eof40;
case 40:
	switch( (*p) ) {
		case 37: goto st42;
		case 47: goto st0;
		case 60: goto st0;
		case 91: goto st47;
		case 95: goto st45;
		case 127: goto st0;
	}
	if ( (*p) < 45 ) {
		if ( (*p) > 32 ) {
			if ( 34 <= (*p) && (*p) <= 35 )
				goto st0;
		} else if ( (*p) >= 0 )
			goto st0;
	} else if ( (*p) > 57 ) {
		if ( (*p) < 65 ) {
			if ( 62 <= (*p) && (*p) <= 64 )
				goto st0;
		} else if ( (*p) > 90 ) {
			if ( 97 <= (*p) && (*p) <= 122 )
				goto st45;
		} else
			goto st45;
	} else
		goto st45;
	goto st41;
st41:
	if ( ++p == pe )
		goto _test_eof41;
case 41:
	switch( (*p) ) {
		case 37: goto st42;
		case 47: goto st0;
		case 60: goto st0;
		case 64: goto st40;
		case 127: goto st0;
	}
	if ( (*p) < 34 ) {
		if ( 0 <= (*p) && (*p) <= 32 )
			goto st0;
	} else if ( (*p) > 35 ) {
		if ( 62 <= (*p) && (*p) <= 63 )
			goto st0;
	} else
		goto st0;
	goto st41;
st42:
	if ( ++p == pe )
		goto _test_eof42;
case 42:
	if ( (*p) == 117 )
		goto st44;
	if ( (*p) < 65 ) {
		if ( 48 <= (*p) && (*p) <= 57 )
			goto st43;
	} else if ( (*p) > 70 ) {
		if ( 97 <= (*p) && (*p) <= 102 )
			goto st43;
	} else
		goto st43;
	goto st0;
st43:
	if ( ++p == pe )
		goto _test_eof43;
case 43:
	if ( (*p) < 65 ) {
		if ( 48 <= (*p) && (*p) <= 57 )
			goto st41;
	} else if ( (*p) > 70 ) {
		if ( 97 <= (*p) && (*p) <= 102 )
			goto st41;
	} else
		goto st41;
	goto st0;
st44:
	if ( ++p == pe )
		goto _test_eof44;
case 44:
	if ( (*p) < 65 ) {
		if ( 48 <= (*p) && (*p) <= 57 )
			goto st43;
	} else if ( (*p) > 70 ) {
		if ( 97 <= (*p) && (*p) <= 102 )
			goto st43;
	} else
		goto st43;
	goto st0;
st45:
	if ( ++p == pe )
		goto _test_eof45;
case 45:
	switch( (*p) ) {
		case 37: goto st42;
		case 47: goto tr5;
		case 58: goto st46;
		case 60: goto st0;
		case 64: goto st40;
		case 95: goto st45;
		case 127: goto st0;
	}
	if ( (*p) < 45 ) {
		if ( (*p) > 32 ) {
			if ( 34 <= (*p) && (*p) <= 35 )
				goto st0;
		} else if ( (*p) >= 0 )
			goto st0;
	} else if ( (*p) > 57 ) {
		if ( (*p) < 65 ) {
			if ( 62 <= (*p) && (*p) <= 63 )
				goto st0;
		} else if ( (*p) > 90 ) {
			if ( 97 <= (*p) && (*p) <= 122 )
				goto st45;
		} else
			goto st45;
	} else
		goto st45;
	goto st41;
st46:
	if ( ++p == pe )
		goto _test_eof46;
case 46:
	switch( (*p) ) {
		case 37: goto st42;
		case 47: goto tr5;
		case 60: goto st0;
		case 64: goto st40;
		case 127: goto st0;
	}
	if ( (*p) < 34 ) {
		if ( 0 <= (*p) && (*p) <= 32 )
			goto st0;
	} else if ( (*p) > 35 ) {
		if ( (*p) > 57 ) {
			if ( 62 <= (*p) && (*p) <= 63 )
				goto st0;
		} else if ( (*p) >= 48 )
			goto st46;
	} else
		goto st0;
	goto st41;
st47:
	if ( ++p == pe )
		goto _test_eof47;
case 47:
	switch( (*p) ) {
		case 37: goto st42;
		case 47: goto st0;
		case 60: goto st0;
		case 64: goto st40;
		case 127: goto st0;
	}
	if ( (*p) < 48 ) {
		if ( (*p) > 32 ) {
			if ( 34 <= (*p) && (*p) <= 35 )
				goto st0;
		} else if ( (*p) >= 0 )
			goto st0;
	} else if ( (*p) > 58 ) {
		if ( (*p) < 65 ) {
			if ( 62 <= (*p) && (*p) <= 63 )
				goto st0;
		} else if ( (*p) > 70 ) {
			if ( 97 <= (*p) && (*p) <= 102 )
				goto st48;
		} else
			goto st48;
	} else
		goto st48;
	goto st41;
st48:
	if ( ++p == pe )
		goto _test_eof48;
case 48:
	switch( (*p) ) {
		case 37: goto st42;
		case 47: goto st0;
		case 60: goto st0;
		case 64: goto st40;
		case 93: goto st49;
		case 127: goto st0;
	}
	if ( (*p) < 48 ) {
		if ( (*p) > 32 ) {
			if ( 34 <= (*p) && (*p) <= 35 )
				goto st0;
		} else if ( (*p) >= 0 )
			goto st0;
	} else if ( (*p) > 58 ) {
		if ( (*p) < 65 ) {
			if ( 62 <= (*p) && (*p) <= 63 )
				goto st0;
		} else if ( (*p) > 70 ) {
			if ( 97 <= (*p) && (*p) <= 102 )
				goto st48;
		} else
			goto st48;
	} else
		goto st48;
	goto st41;
st49:
	if ( ++p == pe )
		goto _test_eof49;
case 49:
	switch( (*p) ) {
		case 37: goto st42;
		case 47: goto tr5;
		case 58: goto st46;
		case 60: goto st0;
		case 64: goto st40;
		case 127: goto st0;
	}
	if ( (*p) < 34 ) {
		if ( 0 <= (*p) && (*p) <= 32 )
			goto st0;
	} else if ( (*p) > 35 ) {
		if ( 62 <= (*p) && (*p) <= 63 )
			goto st0;
	} else
		goto st0;
	goto st41;
st50:
	if ( ++p == pe )
		goto _test_eof50;
case 50:
	if ( (*p) == 58 )
		goto st38;
	goto st0;
st51:
	if ( ++p == pe )
		goto _test_eof51;
case 51:
	switch( (*p) ) {
		case 32: goto tr2;
		case 36: goto st52;
		case 95: goto st52;
	}
	if ( (*p) < 48 ) {
		if ( 45 <= (*p) && (*p) <= 46 )
			goto st52;
	} else if ( (*p) > 57 ) {
		if ( 65 <= (*p) && (*p) <= 90 )
			goto st52;
	} else
		goto st52;
	goto st0;
st52:
	if ( ++p == pe )
		goto _test_eof52;
case 52:
	switch( (*p) ) {
		case 32: goto tr2;
		case 36: goto st53;
		case 95: goto st53;
	}
	if ( (*p) < 48 ) {
		if ( 45 <= (*p) && (*p) <= 46 )
			goto st53;
	} else if ( (*p) > 57 ) {
		if ( 65 <= (*p) && (*p) <= 90 )
			goto st53;
	} else
		goto st53;
	goto st0;
st53:
	if ( ++p == pe )
		goto _test_eof53;
case 53:
	switch( (*p) ) {
		case 32: goto tr2;
		case 36: goto st54;
		case 95: goto st54;
	}
	if ( (*p) < 48 ) {
		if ( 45 <= (*p) && (*p) <= 46 )
			goto st54;
	} else if ( (*p) > 57 ) {
		if ( 65 <= (*p) && (*p) <= 90 )
			goto st54;
	} else
		goto st54;
	goto st0;
st54:
	if ( ++p == pe )
		goto _test_eof54;
case 54:
	switch( (*p) ) {
		case 32: goto tr2;
		case 36: goto st55;
		case 95: goto st55;
	}
	if ( (*p) < 48 ) {
		if ( 45 <= (*p) && (*p) <= 46 )
			goto st55;
	} else if ( (*p) > 57 ) {
		if ( 65 <= (*p) && (*p) <= 90 )
			goto st55;
	} else
		goto st55;
	goto st0;
st55:
	if ( ++p == pe )
		goto _test_eof55;
case 55:
	switch( (*p) ) {
		case 32: goto tr2;
		case 36: goto st56;
		case 95: goto st56;
	}
	if ( (*p) < 48 ) {
		if ( 45 <= (*p) && (*p) <= 46 )
			goto st56;
	} else if ( (*p) > 57 ) {
		if ( 65 <= (*p) && (*p) <= 90 )
			goto st56;
	} else
		goto st56;
	goto st0;
st56:
	if ( ++p == pe )
		goto _test_eof56;
case 56:
	switch( (*p) ) {
		case 32: goto tr2;
		case 36: goto st57;
		case 95: goto st57;
	}
	if ( (*p) < 48 ) {
		if ( 45 <= (*p) && (*p) <= 46 )
			goto st57;
	} else if ( (*p) > 57 ) {
		if ( 65 <= (*p) && (*p) <= 90 )
			goto st57;
	} else
		goto st57;
	goto st0;
st57:
	if ( ++p == pe )
		goto _test_eof57;
case 57:
	switch( (*p) ) {
		case 32: goto tr2;
		case 36: goto st58;
		case 95: goto st58;
	}
	if ( (*p) < 48 ) {
		if ( 45 <= (*p) && (*p) <= 46 )
			goto st58;
	} else if ( (*p) > 57 ) {
		if ( 65 <= (*p) && (*p) <= 90 )
			goto st58;
	} else
		goto st58;
	goto st0;
st58:
	if ( ++p == pe )
		goto _test_eof58;
case 58:
	switch( (*p) ) {
		case 32: goto tr2;
		case 36: goto st59;
		case 95: goto st59;
	}
	if ( (*p) < 48 ) {
		if ( 45 <= (*p) && (*p) <= 46 )
			goto st59;
	} else if ( (*p) > 57 ) {
		if ( 65 <= (*p) && (*p) <= 90 )
			goto st59;
	} else
		goto st59;
	goto st0;
st59:
	if ( ++p == pe )
		goto _test_eof59;
case 59:
	switch( (*p) ) {
		case 32: goto tr2;
		case 36: goto st60;
		case 95: goto st60;
	}
	if ( (*p) < 48 ) {
		if ( 45 <= (*p) && (*p) <= 46 )
			goto st60;
	} else if ( (*p) > 57 ) {
		if ( 65 <= (*p) && (*p) <= 90 )
			goto st60;
	} else
		goto st60;
	goto st0;
st60:
	if ( ++p == pe )
		goto _test_eof60;
case 60:
	switch( (*p) ) {
		case 32: goto tr2;
		case 36: goto st61;
		case 95: goto st61;
	}
	if ( (*p) < 48 ) {
		if ( 45 <= (*p) && (*p) <= 46 )
			goto st61;
	} else if ( (*p) > 57 ) {
		if ( 65 <= (*p) && (*p) <= 90 )
			goto st61;
	} else
		goto st61;
	goto st0;
st61:
	if ( ++p == pe )
		goto _test_eof61;
case 61:
	switch( (*p) ) {
		case 32: goto tr2;
		case 36: goto st62;
		case 95: goto st62;
	}
	if ( (*p) < 48 ) {
		if ( 45 <= (*p) && (*p) <= 46 )
			goto st62;
	} else if ( (*p) > 57 ) {
		if ( 65 <= (*p) && (*p) <= 90 )
			goto st62;
	} else
		goto st62;
	goto st0;
st62:
	if ( ++p == pe )
		goto _test_eof62;
case 62:
	switch( (*p) ) {
		case 32: goto tr2;
		case 36: goto st63;
		case 95: goto st63;
	}
	if ( (*p) < 48 ) {
		if ( 45 <= (*p) && (*p) <= 46 )
			goto st63;
	} else if ( (*p) > 57 ) {
		if ( 65 <= (*p) && (*p) <= 90 )
			goto st63;
	} else
		goto st63;
	goto st0;
st63:
	if ( ++p == pe )
		goto _test_eof63;
case 63:
	switch( (*p) ) {
		case 32: goto tr2;
		case 36: goto st64;
		case 95: goto st64;
	}
	if ( (*p) < 48 ) {
		if ( 45 <= (*p) && (*p) <= 46 )
			goto st64;
	} else if ( (*p) > 57 ) {
		if ( 65 <= (*p) && (*p) <= 90 )
			goto st64;
	} else
		goto st64;
	goto st0;
st64:
	if ( ++p == pe )
		goto _test_eof64;
case 64:
	switch( (*p) ) {
		case 32: goto tr2;
		case 36: goto st65;
		case 95: goto st65;
	}
	if ( (*p) < 48 ) {
		if ( 45 <= (*p) && (*p) <= 46 )
			goto st65;
	} else if ( (*p) > 57 ) {
		if ( 65 <= (*p) && (*p) <= 90 )
			goto st65;
	} else
		goto st65;
	goto st0;
st65:
	if ( ++p == pe )
		goto _test_eof65;
case 65:
	switch( (*p) ) {
		case 32: goto tr2;
		case 36: goto st66;
		case 95: goto st66;
	}
	if ( (*p) < 48 ) {
		if ( 45 <= (*p) && (*p) <= 46 )
			goto st66;
	} else if ( (*p) > 57 ) {
		if ( 65 <= (*p) && (*p) <= 90 )
			goto st66;
	} else
		goto st66;
	goto st0;
st66:
	if ( ++p == pe )
		goto _test_eof66;
case 66:
	switch( (*p) ) {
		case 32: goto tr2;
		case 36: goto st67;
		case 95: goto st67;
	}
	if ( (*p) < 48 ) {
		if ( 45 <= (*p) && (*p) <= 46 )
			goto st67;
	} else if ( (*p) > 57 ) {
		if ( 65 <= (*p) && (*p) <= 90 )
			goto st67;
	} else
		goto st67;
	goto st0;
st67:
	if ( ++p == pe )
		goto _test_eof67;
case 67:
	switch( (*p) ) {
		case 32: goto tr2;
		case 36: goto st68;
		case 95: goto st68;
	}
	if ( (*p) < 48 ) {
		if ( 45 <= (*p) && (*p) <= 46 )
			goto st68;
	} else if ( (*p) > 57 ) {
		if ( 65 <= (*p) && (*p) <= 90 )
			goto st68;
	} else
		goto st68;
	goto st0;
st68:
	if ( ++p == pe )
		goto _test_eof68;
case 68:
	switch( (*p) ) {
		case 32: goto tr2;
		case 36: goto st69;
		case 95: goto st69;
	}
	if ( (*p) < 48 ) {
		if ( 45 <= (*p) && (*p) <= 46 )
			goto st69;
	} else if ( (*p) > 57 ) {
		if ( 65 <= (*p) && (*p) <= 90 )
			goto st69;
	} else
		goto st69;
	goto st0;
st69:
	if ( ++p == pe )
		goto _test_eof69;
case 69:
	if ( (*p) == 32 )
		goto tr2;
	goto st0;
	}
	_test_eof2: cs = 2; goto _test_eof; 
	_test_eof3: cs = 3; goto _test_eof; 
	_test_eof4: cs = 4; goto _test_eof; 
	_test_eof5: cs = 5; goto _test_eof; 
	_test_eof6: cs = 6; goto _test_eof; 
	_test_eof7: cs = 7; goto _test_eof; 
	_test_eof8: cs = 8; goto _test_eof; 
	_test_eof9: cs = 9; goto _test_eof; 
	_test_eof10: cs = 10; goto _test_eof; 
	_test_eof11: cs = 11; goto _test_eof; 
	_test_eof12: cs = 12; goto _test_eof; 
	_test_eof13: cs = 13; goto _test_eof; 
	_test_eof14: cs = 14; goto _test_eof; 
	_test_eof15: cs = 15; goto _test_eof; 
	_test_eof16: cs = 16; goto _test_eof; 
	_test_eof70: cs = 70; goto _test_eof; 
	_test_eof17: cs = 17; goto _test_eof; 
	_test_eof18: cs = 18; goto _test_eof; 
	_test_eof19: cs = 19; goto _test_eof; 
	_test_eof20: cs = 20; goto _test_eof; 
	_test_eof21: cs = 21; goto _test_eof; 
	_test_eof22: cs = 22; goto _test_eof; 
	_test_eof23: cs = 23; goto _test_eof; 
	_test_eof24: cs = 24; goto _test_eof; 
	_test_eof25: cs = 25; goto _test_eof; 
	_test_eof26: cs = 26; goto _test_eof; 
	_test_eof27: cs = 27; goto _test_eof; 
	_test_eof28: cs = 28; goto _test_eof; 
	_test_eof29: cs = 29; goto _test_eof; 
	_test_eof30: cs = 30; goto _test_eof; 
	_test_eof31: cs = 31; goto _test_eof; 
	_test_eof32: cs = 32; goto _test_eof; 
	_test_eof33: cs = 33; goto _test_eof; 
	_test_eof34: cs = 34; goto _test_eof; 
	_test_eof35: cs = 35; goto _test_eof; 
	_test_eof36: cs = 36; goto _test_eof; 
	_test_eof37: cs = 37; goto _test_eof; 
	_test_eof38: cs = 38; goto _test_eof; 
	_test_eof39: cs = 39; goto _test_eof; 
	_test_eof40: cs = 40; goto _test_eof; 
	_test_eof41: cs = 41; goto _test_eof; 
	_test_eof42: cs = 42; goto _test_eof; 
	_test_eof43: cs = 43; goto _test_eof; 
	_test_eof44: cs = 44; goto _test_eof; 
	_test_eof45: cs = 45; goto _test_eof; 
	_test_eof46: cs = 46; goto _test_eof; 
	_test_eof47: cs = 47; goto _test_eof; 
	_test_eof48: cs = 48; goto _test_eof; 
	_test_eof49: cs = 49; goto _test_eof; 
	_test_eof50: cs = 50; goto _test_eof; 
	_test_eof51: cs = 51; goto _test_eof; 
	_test_eof52: cs = 52; goto _test_eof; 
	_test_eof53: cs = 53; goto _test_eof; 
	_test_eof54: cs = 54; goto _test_eof; 
	_test_eof55: cs = 55; goto _test_eof; 
	_test_eof56: cs = 56; goto _test_eof; 
	_test_eof57: cs = 57; goto _test_eof; 
	_test_eof58: cs = 58; goto _test_eof; 
	_test_eof59: cs = 59; goto _test_eof; 
	_test_eof60: cs = 60; goto _test_eof; 
	_test_eof61: cs = 61; goto _test_eof; 
	_test_eof62: cs = 62; goto _test_eof; 
	_test_eof63: cs = 63; goto _test_eof; 
	_test_eof64: cs = 64; goto _test_eof; 
	_test_eof65: cs = 65; goto _test_eof; 
	_test_eof66: cs = 66; goto _test_eof; 
	_test_eof67: cs = 67; goto _test_eof; 
	_test_eof68: cs = 68; goto _test_eof; 
	_test_eof69: cs = 69; goto _test_eof; 

	_test_eof: {}
	_out: {}
	}

#line 116 "parser.rl"

  parser->cs = cs;
  parser->nread += p - (buffer + off);

  assert(p <= pe && "buffer overflow after parsing execute");
  assert(parser->nread <= len && "nread longer than length");
  assert(parser->body_start <= len && "body starts after buffer end");
  assert(parser->mark < len && "mark is after buffer end");
  assert(parser->field_len <= len && "field has length longer than whole buffer");
  assert(parser->field_start < len && "field starts after buffer end");

  if(parser->body_start) {
    /* final \r\n combo encountered so stop right here */
    parser->nread++;
  }

  return(parser->nread);
}

int thin_http_parser_has_error(http_parser *parser) {
  return parser->cs == http_parser_error;
}

int thin_http_parser_is_finished(http_parser *parser) {
  return parser->cs == http_parser_first_final;
}

int thin_http_parser_finish(http_parser *parser)
{
  if (thin_http_parser_has_error(parser) ) {
    return -1;
  } else if (thin_http_parser_is_finished(parser) ) {
    return 1;
  } else {
    return 0;
  }
}
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/ext/thin_parser/parser.h000066400000000000000000000022601475433514000241430ustar00rootroot00000000000000/**
 * Copyright (c) 2005 Zed A. Shaw
 * You can redistribute it and/or modify it under the same terms as Ruby.
 */

#ifndef http11_parser_h
#define http11_parser_h

#include 

#if defined(_WIN32)
#include 
#endif

typedef void (*element_cb)(void *data, const char *at, size_t length);
typedef void (*field_cb)(void *data, const char *field, size_t flen, const char *value, size_t vlen);

typedef struct http_parser { 
  int cs;
  size_t body_start;
  int content_len;
  size_t nread;
  size_t mark;
  size_t field_start;
  size_t field_len;
  size_t query_start;

  void *data;

  field_cb http_field;
  element_cb request_method;
  element_cb request_uri;
  element_cb fragment;
  element_cb request_path;
  element_cb query_string;
  element_cb request_http_version;
  element_cb header_done;
  
} http_parser;

int thin_http_parser_init(http_parser *parser);
int thin_http_parser_finish(http_parser *parser);
size_t thin_http_parser_execute(http_parser *parser, const char *data, size_t len, size_t off);
int thin_http_parser_has_error(http_parser *parser);
int thin_http_parser_is_finished(http_parser *parser);

#define http_parser_nread(parser) (parser)->nread 

#endif
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/ext/thin_parser/parser.rl000066400000000000000000000072131475433514000243340ustar00rootroot00000000000000/**
 * Copyright (c) 2005 Zed A. Shaw
 * You can redistribute it and/or modify it under the same terms as Ruby.
 */
#include "parser.h"
#include 
#include 
#include 
#include 
#include 

#define LEN(AT, FPC) (FPC - buffer - parser->AT)
#define MARK(M,FPC) (parser->M = (FPC) - buffer)
#define PTR_TO(F) (buffer + parser->F)

/** Machine **/

%%{
  
  machine http_parser;

  action mark {MARK(mark, fpc); }


  action start_field { MARK(field_start, fpc); }
  action write_field { 
    parser->field_len = LEN(field_start, fpc);
  }

  action start_value { MARK(mark, fpc); }
  action write_value { 
    if (parser->http_field != NULL) {
      parser->http_field(parser->data, PTR_TO(field_start), parser->field_len, PTR_TO(mark), LEN(mark, fpc));
    }
  }
  action request_method { 
    if (parser->request_method != NULL) {
      parser->request_method(parser->data, PTR_TO(mark), LEN(mark, fpc));
    }
  }
  action request_uri {
    if (parser->request_uri != NULL) {
      parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, fpc));
    }
  }
  action fragment { 
    if (parser->fragment != NULL) {
      parser->fragment(parser->data, PTR_TO(mark), LEN(mark, fpc));
    }
  }

  action start_query {MARK(query_start, fpc); }
  action query_string { 
    if (parser->query_string != NULL) {
      parser->query_string(parser->data, PTR_TO(query_start), LEN(query_start, fpc));
    }
  }

  action request_http_version {
    if (parser->request_http_version != NULL) {
      parser->request_http_version(parser->data, PTR_TO(mark), LEN(mark, fpc));
    }
  }

  action request_path {
    if (parser->request_path != NULL) {
      parser->request_path(parser->data, PTR_TO(mark), LEN(mark,fpc));
    }
  }

  action done { 
    parser->body_start = fpc - buffer + 1; 
    if (parser->header_done != NULL) {
      parser->header_done(parser->data, fpc + 1, pe - fpc - 1);
    }
    fbreak;
  }

  include http_parser_common "common.rl";

}%%

/** Data **/
%% write data;

int thin_http_parser_init(http_parser *parser)  {
  int cs = 0;
  %% write init;
  parser->cs = cs;
  parser->body_start = 0;
  parser->content_len = 0;
  parser->mark = 0;
  parser->nread = 0;
  parser->field_len = 0;
  parser->field_start = 0;    

  return(1);
}


/** exec **/
size_t thin_http_parser_execute(http_parser *parser, const char *buffer, size_t len, size_t off)  {
  const char *p, *pe;
  int cs = parser->cs;

  assert(off <= len && "offset past end of buffer");

  p = buffer+off;
  pe = buffer+len;

  assert(*pe == '\0' && "pointer does not end on NUL");
  assert(pe - p == (long)(len - off) && "pointers aren't same distance");


  %% write exec;

  parser->cs = cs;
  parser->nread += p - (buffer + off);

  assert(p <= pe && "buffer overflow after parsing execute");
  assert(parser->nread <= len && "nread longer than length");
  assert(parser->body_start <= len && "body starts after buffer end");
  assert(parser->mark < len && "mark is after buffer end");
  assert(parser->field_len <= len && "field has length longer than whole buffer");
  assert(parser->field_start < len && "field starts after buffer end");

  if(parser->body_start) {
    /* final \r\n combo encountered so stop right here */
    parser->nread++;
  }

  return(parser->nread);
}

int thin_http_parser_has_error(http_parser *parser) {
  return parser->cs == http_parser_error;
}

int thin_http_parser_is_finished(http_parser *parser) {
  return parser->cs == http_parser_first_final;
}

int thin_http_parser_finish(http_parser *parser)
{
  if (thin_http_parser_has_error(parser) ) {
    return -1;
  } else if (thin_http_parser_is_finished(parser) ) {
    return 1;
  } else {
    return 0;
  }
}
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/ext/thin_parser/thin.c000066400000000000000000000306061475433514000236110ustar00rootroot00000000000000/**
 * Mongrel Parser adpated to Thin and to play more nicely with Rack specs.
 * 
 * Orignal version Copyright (c) 2005 Zed A. Shaw
 * You can redistribute it and/or modify it under the same terms as Ruby.
 */
#include "ruby.h"
#include "ext_help.h"
#include 
#include 
#include "parser.h"
#include 

static VALUE mThin;
static VALUE cHttpParser;
static VALUE eHttpParserError;

static VALUE global_empty;
static VALUE global_http_prefix;
static VALUE global_request_method;
static VALUE global_request_uri;
static VALUE global_fragment;
static VALUE global_query_string;
static VALUE global_request_http_version;
static VALUE global_content_length;
static VALUE global_http_content_length;
static VALUE global_request_path;
static VALUE global_content_type;
static VALUE global_http_content_type;
static VALUE global_gateway_interface;
static VALUE global_gateway_interface_value;
static VALUE global_server_name;
static VALUE global_server_port;
static VALUE global_server_protocol;
static VALUE global_server_protocol_value;
static VALUE global_http_host;
static VALUE global_port_80;
static VALUE global_http_body;
static VALUE global_url_scheme;
static VALUE global_url_scheme_value;
static VALUE global_script_name;
static VALUE global_path_info;

#define TRIE_INCREASE 30

/** Defines common length and error messages for input length validation. */
#define DEF_MAX_LENGTH(N,length) const size_t MAX_##N##_LENGTH = length; const char *MAX_##N##_LENGTH_ERR = "HTTP element " # N  " is longer than the " # length " allowed length."

/** Validates the max length of given input and throws an HttpParserError exception if over. */
#define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { rb_raise(eHttpParserError, "%s", MAX_##N##_LENGTH_ERR); }

/** Defines global strings in the init method. */
#define DEF_GLOBAL(N, val)   global_##N = rb_obj_freeze(rb_str_new2(val)); rb_global_variable(&global_##N)

/* for compatibility with Ruby 1.8.5, which doesn't declare RSTRING_PTR */
#ifndef RSTRING_PTR
#define RSTRING_PTR(s) (RSTRING(s)->ptr)
#endif

/* for compatibility with Ruby 1.8.5, which doesn't declare RSTRING_LEN */
#ifndef RSTRING_LEN
#define RSTRING_LEN(s) (RSTRING(s)->len)
#endif

/* Defines the maximum allowed lengths for various input elements.*/
DEF_MAX_LENGTH(FIELD_NAME, 256);
DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
DEF_MAX_LENGTH(REQUEST_PATH, 2048);
DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));

static void http_field(void *data, const char *field, size_t flen, const char *value, size_t vlen)
{
  char *ch, *end;
  VALUE req = (VALUE)data;
  VALUE v = Qnil;
  VALUE f = Qnil;

  VALIDATE_MAX_LENGTH(flen, FIELD_NAME);
  VALIDATE_MAX_LENGTH(vlen, FIELD_VALUE);

  v = rb_str_new(value, vlen);
  f = rb_str_dup(global_http_prefix);
  f = rb_str_buf_cat(f, field, flen); 

  for(ch = RSTRING_PTR(f) + RSTRING_LEN(global_http_prefix), end = RSTRING_PTR(f) + RSTRING_LEN(f); ch < end; ch++) {
    if (*ch >= 'a' && *ch <= 'z') {
      *ch &= ~0x20; // upcase
    } else if (*ch == '-') {
      *ch = '_';
    }
  }

  rb_hash_aset(req, f, v);
}

static void request_method(void *data, const char *at, size_t length)
{
  VALUE req = (VALUE)data;
  VALUE val = Qnil;

  val = rb_str_new(at, length);
  rb_hash_aset(req, global_request_method, val);
}

static void request_uri(void *data, const char *at, size_t length)
{
  VALUE req = (VALUE)data;
  VALUE val = Qnil;

  VALIDATE_MAX_LENGTH(length, REQUEST_URI);

  val = rb_str_new(at, length);
  rb_hash_aset(req, global_request_uri, val);
}

static void fragment(void *data, const char *at, size_t length)
{
  VALUE req = (VALUE)data;
  VALUE val = Qnil;

  VALIDATE_MAX_LENGTH(length, FRAGMENT);

  val = rb_str_new(at, length);
  rb_hash_aset(req, global_fragment, val);
}

static void request_path(void *data, const char *at, size_t length)
{
  VALUE req = (VALUE)data;
  VALUE val = Qnil;

  VALIDATE_MAX_LENGTH(length, REQUEST_PATH);

  val = rb_str_new(at, length);
  rb_hash_aset(req, global_request_path, val);
  rb_hash_aset(req, global_path_info, val);
}

static void query_string(void *data, const char *at, size_t length)
{
  VALUE req = (VALUE)data;
  VALUE val = Qnil;

  VALIDATE_MAX_LENGTH(length, QUERY_STRING);

  val = rb_str_new(at, length);
  rb_hash_aset(req, global_query_string, val);
}

static void request_http_version(void *data, const char *at, size_t length)
{
  VALUE req = (VALUE)data;
  VALUE val = rb_str_new(at, length);
  rb_hash_aset(req, global_request_http_version, val);
  rb_hash_aset(req, global_server_protocol, val);
}

/** Finalizes the request header to have a bunch of stuff that's
  needed. */

static void header_done(void *data, const char *at, size_t length)
{
  VALUE req = (VALUE)data;
  VALUE temp = Qnil;
  VALUE ctype = Qnil;
  VALUE clen = Qnil;
  VALUE body = Qnil;
  char *colon = NULL;

  clen = rb_hash_aref(req, global_http_content_length);
  if(clen != Qnil) {
    rb_hash_aset(req, global_content_length, clen);
    rb_hash_delete(req, global_http_content_length);
  }

  ctype = rb_hash_aref(req, global_http_content_type);
  if(ctype != Qnil) {
    rb_hash_aset(req, global_content_type, ctype);
    rb_hash_delete(req, global_http_content_type);
  }

  rb_hash_aset(req, global_gateway_interface, global_gateway_interface_value);
  if((temp = rb_hash_aref(req, global_http_host)) != Qnil) {
    /* ruby better close strings off with a '\0' dammit */
    colon = strchr(RSTRING_PTR(temp), ':');
    if(colon != NULL) {
      rb_hash_aset(req, global_server_name, rb_str_substr(temp, 0, colon - RSTRING_PTR(temp)));
      rb_hash_aset(req, global_server_port, 
          rb_str_substr(temp, colon - RSTRING_PTR(temp)+1, 
            RSTRING_LEN(temp)));
    } else {
      rb_hash_aset(req, global_server_name, temp);
      rb_hash_aset(req, global_server_port, global_port_80);
    }
  }

  /* grab the initial body and stuff it into the hash */
  if(length > 0) {
    body = rb_hash_aref(req, global_http_body);
    rb_io_write(body, rb_str_new(at, length));
  }
  
  /* according to Rack specs, query string must be empty string if none */
  if (rb_hash_aref(req, global_query_string) == Qnil) {
    rb_hash_aset(req, global_query_string, global_empty);
  }
  if (rb_hash_aref(req, global_path_info) == Qnil) {
    rb_hash_aset(req, global_path_info, global_empty);
  }
  
  /* set some constants */
  rb_hash_aset(req, global_url_scheme, global_url_scheme_value);
  rb_hash_aset(req, global_script_name, global_empty);
}


void Thin_HttpParser_free(void *data) {
  TRACE();

  if(data) {
    free(data);
  }
}


VALUE Thin_HttpParser_alloc(VALUE klass)
{
  VALUE obj;
  http_parser *hp = ALLOC_N(http_parser, 1);
  TRACE();
  hp->http_field = http_field;
  hp->request_method = request_method;
  hp->request_uri = request_uri;
  hp->fragment = fragment;
  hp->request_path = request_path;
  hp->query_string = query_string;
  hp->request_http_version = request_http_version;
  hp->header_done = header_done;
  thin_http_parser_init(hp);

  obj = Data_Wrap_Struct(klass, NULL, Thin_HttpParser_free, hp);

  return obj;
}


/**
 * call-seq:
 *    parser.new -> parser
 *
 * Creates a new parser.
 */
VALUE Thin_HttpParser_init(VALUE self)
{
  http_parser *http = NULL;
  DATA_GET(self, http_parser, http);
  thin_http_parser_init(http);

  return self;
}


/**
 * call-seq:
 *    parser.reset -> nil
 *
 * Resets the parser to it's initial state so that you can reuse it
 * rather than making new ones.
 */
VALUE Thin_HttpParser_reset(VALUE self)
{
  http_parser *http = NULL;
  DATA_GET(self, http_parser, http);
  thin_http_parser_init(http);

  return Qnil;
}


/**
 * call-seq:
 *    parser.finish -> true/false
 *
 * Finishes a parser early which could put in a "good" or bad state.
 * You should call reset after finish it or bad things will happen.
 */
VALUE Thin_HttpParser_finish(VALUE self)
{
  http_parser *http = NULL;
  DATA_GET(self, http_parser, http);
  thin_http_parser_finish(http);

  return thin_http_parser_is_finished(http) ? Qtrue : Qfalse;
}


/**
 * call-seq:
 *    parser.execute(req_hash, data, start) -> Integer
 *
 * Takes a Hash and a String of data, parses the String of data filling in the Hash
 * returning an Integer to indicate how much of the data has been read.  No matter
 * what the return value, you should call HttpParser#finished? and HttpParser#error?
 * to figure out if it's done parsing or there was an error.
 * 
 * This function now throws an exception when there is a parsing error.  This makes 
 * the logic for working with the parser much easier.  You can still test for an 
 * error, but now you need to wrap the parser with an exception handling block.
 *
 * The third argument allows for parsing a partial request and then continuing
 * the parsing from that position.  It needs all of the original data as well 
 * so you have to append to the data buffer as you read.
 */
VALUE Thin_HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
{
  http_parser *http = NULL;
  int from = 0;
  char *dptr = NULL;
  long dlen = 0;

  DATA_GET(self, http_parser, http);

  from = FIX2INT(start);
  dptr = RSTRING_PTR(data);
  dlen = RSTRING_LEN(data);

  if(from >= dlen) {
    rb_raise(eHttpParserError, "%s", "Requested start is after data buffer end.");
  } else {
    http->data = (void *)req_hash;
    thin_http_parser_execute(http, dptr, dlen, from);

    VALIDATE_MAX_LENGTH(http_parser_nread(http), HEADER);

    if(thin_http_parser_has_error(http)) {
      rb_raise(eHttpParserError, "%s", "Invalid HTTP format, parsing fails.");
    } else {
      return INT2FIX(http_parser_nread(http));
    }
  }
}



/**
 * call-seq:
 *    parser.error? -> true/false
 *
 * Tells you whether the parser is in an error state.
 */
VALUE Thin_HttpParser_has_error(VALUE self)
{
  http_parser *http = NULL;
  DATA_GET(self, http_parser, http);

  return thin_http_parser_has_error(http) ? Qtrue : Qfalse;
}


/**
 * call-seq:
 *    parser.finished? -> true/false
 *
 * Tells you whether the parser is finished or not and in a good state.
 */
VALUE Thin_HttpParser_is_finished(VALUE self)
{
  http_parser *http = NULL;
  DATA_GET(self, http_parser, http);

  return thin_http_parser_is_finished(http) ? Qtrue : Qfalse;
}


/**
 * call-seq:
 *    parser.nread -> Integer
 *
 * Returns the amount of data processed so far during this processing cycle.  It is
 * set to 0 on initialize or reset calls and is incremented each time execute is called.
 */
VALUE Thin_HttpParser_nread(VALUE self)
{
  http_parser *http = NULL;
  DATA_GET(self, http_parser, http);

  return INT2FIX(http->nread);
}

void Init_thin_parser()
{

  mThin = rb_define_module("Thin");

  DEF_GLOBAL(empty, "");
  DEF_GLOBAL(http_prefix, "HTTP_");
  DEF_GLOBAL(request_method, "REQUEST_METHOD");
  DEF_GLOBAL(request_uri, "REQUEST_URI");
  DEF_GLOBAL(fragment, "FRAGMENT");
  DEF_GLOBAL(query_string, "QUERY_STRING");
  DEF_GLOBAL(request_http_version, "thin.request_http_version");
  DEF_GLOBAL(request_path, "REQUEST_PATH");
  DEF_GLOBAL(content_length, "CONTENT_LENGTH");
  DEF_GLOBAL(http_content_length, "HTTP_CONTENT_LENGTH");
  DEF_GLOBAL(content_type, "CONTENT_TYPE");
  DEF_GLOBAL(http_content_type, "HTTP_CONTENT_TYPE");
  DEF_GLOBAL(gateway_interface, "GATEWAY_INTERFACE");
  DEF_GLOBAL(gateway_interface_value, "CGI/1.2");
  DEF_GLOBAL(server_name, "SERVER_NAME");
  DEF_GLOBAL(server_port, "SERVER_PORT");
  DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL");
  DEF_GLOBAL(server_protocol_value, "HTTP/1.1");
  DEF_GLOBAL(http_host, "HTTP_HOST");
  DEF_GLOBAL(port_80, "80");
  DEF_GLOBAL(http_body, "rack.input");
  DEF_GLOBAL(url_scheme, "rack.url_scheme");
  DEF_GLOBAL(url_scheme_value, "http");
  DEF_GLOBAL(script_name, "SCRIPT_NAME");
  DEF_GLOBAL(path_info, "PATH_INFO");

  eHttpParserError = rb_define_class_under(mThin, "InvalidRequest", rb_eIOError);

  cHttpParser = rb_define_class_under(mThin, "HttpParser", rb_cObject);
  rb_define_alloc_func(cHttpParser, Thin_HttpParser_alloc);
  rb_define_method(cHttpParser, "initialize", Thin_HttpParser_init,0);
  rb_define_method(cHttpParser, "reset", Thin_HttpParser_reset,0);
  rb_define_method(cHttpParser, "finish", Thin_HttpParser_finish,0);
  rb_define_method(cHttpParser, "execute", Thin_HttpParser_execute,3);
  rb_define_method(cHttpParser, "error?", Thin_HttpParser_has_error,0);
  rb_define_method(cHttpParser, "finished?", Thin_HttpParser_is_finished,0);
  rb_define_method(cHttpParser, "nread", Thin_HttpParser_nread,0);
}
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/gems/000077500000000000000000000000001475433514000203135ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/gems/rack-head.rb000066400000000000000000000001471475433514000224610ustar00rootroot00000000000000# frozen_string_literal: true

eval_gemfile "../Gemfile"

gem 'rack', github: 'rack/rack'
gem 'rackup'
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/gems/rack-v1.rb000066400000000000000000000001171475433514000221030ustar00rootroot00000000000000# frozen_string_literal: true

eval_gemfile "../Gemfile"

gem 'rack', '~> 1.0'
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/gems/rack-v2.rb000066400000000000000000000001171475433514000221040ustar00rootroot00000000000000# frozen_string_literal: true

eval_gemfile "../Gemfile"

gem 'rack', "~> 2.0"
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/gems/rack-v3.rb000066400000000000000000000001341475433514000221040ustar00rootroot00000000000000# frozen_string_literal: true

eval_gemfile "../Gemfile"

gem 'rack', "~> 3.0"
gem 'rackup'
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/000077500000000000000000000000001475433514000201265ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/rack/000077500000000000000000000000001475433514000210465ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/rack/adapter/000077500000000000000000000000001475433514000224665ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/rack/adapter/loader.rb000066400000000000000000000046041475433514000242650ustar00rootroot00000000000000module Rack
  class AdapterNotFound < RuntimeError; end
  
  # Mapping used to guess which adapter to use in Adapter.for.
  # Framework  =>  in order they will
  # be tested.
  # +nil+ for value to never guess.
  # NOTE: If a framework has a file that is not unique, make sure to place
  # it at the end.
  ADAPTERS = [
    [:rack,    'config.ru'],
    [:rails,   'config/environment.rb'],
    [:ramaze,  'start.rb'],
    [:merb,    'config/init.rb'],
    [:file,    nil]
  ]
  
  # Rack v1 compatibility...
  unless const_defined?(:Files)
    require 'rack/file'
    
    Files = File
  end
  
  module Adapter
    # Guess which adapter to use based on the directory structure
    # or file content.
    # Returns a symbol representing the name of the adapter to use
    # to load the application under dir/.
    def self.guess(dir)
      ADAPTERS.each do |adapter, file|
        return adapter if file && ::File.exist?(::File.join(dir, file))
      end
      raise AdapterNotFound, "No adapter found for #{dir}"
    end
    
    # Load a Rack application from a Rack config file (.ru).
    def self.load(config)
      rackup_code = ::File.read(config)
      eval("Rack::Builder.new {( #{rackup_code}\n )}.to_app", TOPLEVEL_BINDING, config)
    end
    
    # Loads an adapter identified by +name+ using +options+ hash.
    def self.for(name, options={})
      ENV['RACK_ENV'] = options[:environment]
      
      case name.to_sym
      when :rack
        return load(::File.join(options[:chdir], "config.ru"))
        
      when :rails
        return Rails.new(options.merge(:root => options[:chdir]))
      
      when :ramaze
        require "#{options[:chdir]}/start"
        
        Ramaze.trait[:essentials].delete Ramaze::Adapter
        Ramaze.start :force => true
        
        return Ramaze::Adapter::Base
        
      when :merb
        require 'merb-core'
        
        Merb::Config.setup(:merb_root   => options[:chdir],
                           :environment => options[:environment])
        Merb.environment = Merb::Config[:environment]
        Merb.root = Merb::Config[:merb_root]
        Merb::BootLoader.run
        
        return Merb::Rack::Application.new
        
      when :file
        return Rack::Files.new(options[:chdir])
        
      else
        raise AdapterNotFound, "Adapter not found: #{name}"
        
      end
    end
  end
endthin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/rack/adapter/rails.rb000066400000000000000000000135351475433514000241340ustar00rootroot00000000000000require 'cgi'

# Adapter to run a Rails app with any supported Rack handler.
# By default it will try to load the Rails application in the
# current directory in the development environment.
#
# Options:
#  root: Root directory of the Rails app
#  environment: Rails environment to run in (development [default], production or test)
#  prefix: Set the relative URL root.
#
# Based on http://fuzed.rubyforge.org/ Rails adapter
module Rack
  module Adapter
    class Rails
      FILE_METHODS = %w(GET HEAD).freeze

      def initialize(options = {})
        @root   = options[:root]        || Dir.pwd
        @env    = options[:environment] || 'development'
        @prefix = options[:prefix]

        load_application

        @rails_app = self.class.rack_based? ? ActionController::Dispatcher.new : CgiApp.new
        @file_app  = Rack::Files.new(::File.join(RAILS_ROOT, "public"))
      end

      def load_application
        ENV['RAILS_ENV'] = @env

        require "#{@root}/config/environment"
        require 'dispatcher'

        if @prefix
          if ActionController::Base.respond_to?(:relative_url_root=)
            ActionController::Base.relative_url_root = @prefix # Rails 2.1.1
          else
            ActionController::AbstractRequest.relative_url_root = @prefix
          end
        end
      end

      def file_exist?(path)
        full_path = ::File.join(@file_app.root, Utils.unescape(path))
        ::File.file?(full_path) && ::File.readable_real?(full_path)
      end

      def call(env)
        path        = env['PATH_INFO'].chomp('/')
        method      = env['REQUEST_METHOD']
        cached_path = (path.empty? ? 'index' : path) + ActionController::Base.page_cache_extension

        if FILE_METHODS.include?(method)
          if file_exist?(path)              # Serve the file if it's there
            return @file_app.call(env)
          elsif file_exist?(cached_path)    # Serve the page cache if it's there
            env['PATH_INFO'] = cached_path
            return @file_app.call(env)
          end
        end

        # No static file, let Rails handle it
        @rails_app.call(env)
      end

      def self.rack_based?
        rails_version = ::Rails::VERSION
        return false if rails_version::MAJOR < 2
        return false if rails_version::MAJOR == 2 && rails_version::MINOR < 2
        return false if rails_version::MAJOR == 2 && rails_version::MINOR == 2 && rails_version::TINY < 3
        true # >= 2.2.3
      end

      protected
        # For Rails pre Rack (2.3)
        class CgiApp
          def call(env)
            request         = Request.new(env)
            response        = Response.new
            session_options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
            cgi             = CGIWrapper.new(request, response)

            Dispatcher.dispatch(cgi, session_options, response)

            response.finish
          end
        end

        class CGIWrapper < ::CGI
          def initialize(request, response, *args)
            @request  = request
            @response = response
            @args     = *args
            @input    = request.body

            super *args
          end

          def header(options = 'text/html')
            if options.is_a?(String)
              @response['Content-Type']     = options unless @response['Content-Type']
            else
              @response['Content-Length']   = options.delete('Content-Length').to_s if options['Content-Length']

              @response['Content-Type']     = options.delete('type') || "text/html"
              @response['Content-Type']    += '; charset=' + options.delete('charset') if options['charset']

              @response['Content-Language'] = options.delete('language') if options['language']
              @response['Expires']          = options.delete('expires') if options['expires']

              @response.status              = options.delete('Status') if options['Status']

              # Convert 'cookie' header to 'Set-Cookie' headers.
              # Because Set-Cookie header can appear more the once in the response body,
              # we store it in a line break seperated string that will be translated to
              # multiple Set-Cookie header by the handler.
              if cookie = options.delete('cookie')
                cookies = []

                case cookie
                  when Array then cookie.each { |c| cookies << c.to_s }
                  when Hash  then cookie.each { |_, c| cookies << c.to_s }
                  else            cookies << cookie.to_s
                end

                @output_cookies.each { |c| cookies << c.to_s } if @output_cookies

                @response['Set-Cookie'] = [@response['Set-Cookie'], cookies].compact
                # See http://groups.google.com/group/rack-devel/browse_thread/thread/e8759b91a82c5a10/a8dbd4574fe97d69?#a8dbd4574fe97d69
                if Thin.ruby_18?
                  @response['Set-Cookie'].flatten!
                else
                  @response['Set-Cookie'] = @response['Set-Cookie'].join("\n")
                end
              end

              options.each { |k, v| @response[k] = v }
            end

            ''
          end

          def params
            @params ||= @request.params
          end

          def cookies
            @request.cookies
          end

          def query_string
            @request.query_string
          end

          # Used to wrap the normal args variable used inside CGI.
          def args
            @args
          end

          # Used to wrap the normal env_table variable used inside CGI.
          def env_table
            @request.env
          end

          # Used to wrap the normal stdinput variable used inside CGI.
          def stdinput
            @input
          end

          def stdoutput
            STDERR.puts 'stdoutput should not be used.'
            @response.body
          end
      end
    end
  end
end
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/rack/handler/000077500000000000000000000000001475433514000224635ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/rack/handler/thin.rb000066400000000000000000000003331475433514000237510ustar00rootroot00000000000000# frozen_string_literal: true

require 'rack/handler'
require_relative '../../thin/rackup/handler'

module Rack
  module Handler
    class Thin < ::Thin::Rackup::Handler
    end

    register :thin, Thin.to_s
  end
end
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/rackup/000077500000000000000000000000001475433514000214135ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/rackup/handler/000077500000000000000000000000001475433514000230305ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/rackup/handler/thin.rb000066400000000000000000000003321475433514000243150ustar00rootroot00000000000000# frozen_string_literal: true

require 'rackup/handler'
require_relative '../../thin/rackup/handler'

module Rackup
  module Handler
    class Thin < ::Thin::Rackup::Handler
    end

    register :thin, Thin
  end
end
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin.rb000066400000000000000000000024211475433514000214140ustar00rootroot00000000000000require 'fileutils'
require 'timeout'
require 'stringio'
require 'time'
require 'forwardable'
require 'openssl'
require 'eventmachine'
require 'rack'

module Thin
  autoload :Command,            "thin/command"
  autoload :Connection,         "thin/connection"
  autoload :Daemonizable,       "thin/daemonizing"
  autoload :Logging,            "thin/logging"
  autoload :Headers,            "thin/headers"
  autoload :Request,            "thin/request"
  autoload :Response,           "thin/response"
  autoload :Runner,             "thin/runner"
  autoload :Server,             "thin/server"
  autoload :Stats,              "thin/stats"
  
  module Backends
    autoload :Base,             "thin/backends/base"
    autoload :SwiftiplyClient,  "thin/backends/swiftiply_client"
    autoload :TcpServer,        "thin/backends/tcp_server"
    autoload :UnixServer,       "thin/backends/unix_server"
  end
  
  module Controllers
    autoload :Cluster,          "thin/controllers/cluster"
    autoload :Controller,       "thin/controllers/controller"
    autoload :Service,          "thin/controllers/service"
  end
end

require "thin/version"
require "thin/statuses"
require "rack/adapter/loader"
require "thin_parser"

module Rack
  module Adapter
    autoload :Rails, "rack/adapter/rails"
  end
end
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/000077500000000000000000000000001475433514000210705ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/backends/000077500000000000000000000000001475433514000226425ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/backends/base.rb000066400000000000000000000130701475433514000241020ustar00rootroot00000000000000module Thin
  module Backends
    # A Backend connects the server to the client. It handles:
    # * connection/disconnection to the server
    # * initialization of the connections
    # * monitoring of the active connections.
    #
    # == Implementing your own backend
    # You can create your own minimal backend by inheriting this class and
    # defining the +connect+ and +disconnect+ method.
    # If your backend is not based on EventMachine you also need to redefine
    # the +start+, +stop+, stop! and +config+ methods.
    class Base
      # Server serving the connections throught the backend
      attr_accessor :server
      
      # Maximum time for incoming data to arrive
      attr_accessor :timeout
      
      # Maximum number of file or socket descriptors that the server may open.
      attr_accessor :maximum_connections
      
      # Maximum number of connections that can be persistent
      attr_accessor :maximum_persistent_connections

      #allows setting of the eventmachine threadpool size
      attr_reader :threadpool_size
      def threadpool_size=(size)
        @threadpool_size = size
        EventMachine.threadpool_size = size
      end

      # Allow using threads in the backend.
      attr_writer :threaded
      def threaded?; @threaded end
            
      # Allow using SSL in the backend.
      attr_writer :ssl, :ssl_options
      def ssl?; @ssl end
      
      # Number of persistent connections currently opened
      attr_accessor :persistent_connection_count
      
      # Disable the use of epoll under Linux
      attr_accessor :no_epoll
      
      def initialize
        @connections                    = {}
        @timeout                        = Server::DEFAULT_TIMEOUT
        @persistent_connection_count    = 0
        @maximum_connections            = Server::DEFAULT_MAXIMUM_CONNECTIONS
        @maximum_persistent_connections = Server::DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS
        @no_epoll                       = false
        @running                        = false
        @ssl                            = nil
        @started_reactor                = false
        @stopping                       = false
        @threaded                       = nil
      end
      
      # Start the backend and connect it.
      def start
        @stopping = false
        starter   = proc do
          connect
          yield if block_given?
          @running = true
        end
        
        # Allow for early run up of eventmachine.
        if EventMachine.reactor_running?
          starter.call
        else
          @started_reactor = true
          EventMachine.run(&starter)
        end
      end
      
      # Stop of the backend from accepting new connections.
      def stop
        @running  = false
        @stopping = true
        
        # Do not accept anymore connection
        disconnect
        # Close idle persistent connections
        @connections.each_value { |connection| connection.close_connection if connection.idle? }
        stop! if @connections.empty?
      end
      
      # Force stop of the backend NOW, too bad for the current connections.
      def stop!
        @running  = false
        @stopping = false
        
        EventMachine.stop if @started_reactor && EventMachine.reactor_running?
        @connections.each_value { |connection| connection.close_connection }
        close
      end
      
      # Configure the backend. This method will be called before droping superuser privileges,
      # so you can do crazy stuff that require godlike powers here.
      def config
        # See http://rubyeventmachine.com/pub/rdoc/files/EPOLL.html
        EventMachine.epoll unless @no_epoll
        
        # Set the maximum number of socket descriptors that the server may open.
        # The process needs to have required privilege to set it higher the 1024 on
        # some systems.
        @maximum_connections = EventMachine.set_descriptor_table_size(@maximum_connections) unless Thin.win?
      end
      
      # Free up resources used by the backend.
      def close
      end
      
      # Returns +true+ if the backend is connected and running.
      def running?
        @running
      end

      def started_reactor?
        @started_reactor
      end
            
      # Called by a connection when it's unbinded.
      def connection_finished(connection)
        @persistent_connection_count -= 1 if connection.can_persist?
        @connections.delete(connection.__id__)
        
        # Finalize gracefull stop if there's no more active connection.
        stop! if @stopping && @connections.empty?
      end
      
      # Returns +true+ if no active connection.
      def empty?
        @connections.empty?
      end
      
      # Number of active connections.
      def size
        @connections.size
      end
      
      protected
        # Initialize a new connection to a client.
        def initialize_connection(connection)
          connection.backend                 = self
          connection.app                     = @server.app
          connection.comm_inactivity_timeout = @timeout
          connection.threaded                = @threaded
          
          if @ssl
            connection.start_tls(@ssl_options)
          end

          # We control the number of persistent connections by keeping
          # a count of the total one allowed yet.
          if @persistent_connection_count < @maximum_persistent_connections
            connection.can_persist!
            @persistent_connection_count += 1
          end

          @connections[connection.__id__] = connection
        end
      
    end
  end
end
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/backends/swiftiply_client.rb000066400000000000000000000032021475433514000265540ustar00rootroot00000000000000module Thin
  module Backends
    # Backend to act as a Swiftiply client (http://swiftiply.swiftcore.org).
    class SwiftiplyClient < Base
      attr_accessor :key

      attr_accessor :host, :port

      def initialize(host, port, options = {})
        @host = host
        @port = port.to_i
        @key  = options[:swiftiply].to_s
        super()
      end

      # Connect the server
      def connect
        EventMachine.connect(@host, @port, SwiftiplyConnection, &method(:initialize_connection))
      end

      # Stops the server
      def disconnect
        EventMachine.stop
      end

      def to_s
        "#{@host}:#{@port} swiftiply"
      end
    end
  end

  class SwiftiplyConnection < Connection
    def connection_completed
      send_data swiftiply_handshake(@backend.key)
    end

    def persistent?
      true
    end

    def unbind
      super
      EventMachine.add_timer(rand(2)) { reconnect(@backend.host, @backend.port) } if @backend.running?
    end

    protected
      def swiftiply_handshake(key)
        'swiftclient' << host_ip.collect { |x| sprintf('%02x', x.to_i) }.join << sprintf('%04x', @backend.port) << sprintf('%02x', key.length) << key
      end

      # For some reason Swiftiply request the current host
      def host_ip
        begin
          if defined?(Addrinfo)
            # ruby 2.0+
            # TODO: ipv6 support here?
            Addrinfo.getaddrinfo(@backend.host, @backend.port, :PF_INET, :STREAM).first.ip_address.split('.').map(&:to_i)
          else
            Socket.gethostbyname(@backend.host)[3].unpack('CCCC')
          end
        rescue
          [0, 0, 0, 0]
        end
      end
  end
endthin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/backends/tcp_server.rb000066400000000000000000000015201475433514000253410ustar00rootroot00000000000000module Thin
  module Backends
    # Backend to act as a TCP socket server.
    class TcpServer < Base
      # Address and port on which the server is listening for connections.
      attr_accessor :host, :port

      def initialize(host, port)
        @host = host
        @port = port
        super()
      end

      # Connect the server
      def connect
        @signature = EventMachine.start_server(@host, @port, Connection, &method(:initialize_connection))
        binary_name = EventMachine.get_sockname( @signature )
        port_name = Socket.unpack_sockaddr_in( binary_name )
        @port = port_name[0]
        @host = port_name[1]
        @signature
      end

      # Stops the server
      def disconnect
        EventMachine.stop_server(@signature)
      end

      def to_s
        "#{@host}:#{@port}"
      end
    end
  end
end
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/backends/unix_server.rb000066400000000000000000000030661475433514000255450ustar00rootroot00000000000000module Thin
  module Backends
    # Backend to act as a UNIX domain socket server.
    class UnixServer < Base
      # UNIX domain socket on which the server is listening for connections.
      attr_accessor :socket
      
      def initialize(socket)
        raise PlatformNotSupported, 'UNIX domain sockets not available on Windows' if Thin.win?
        @socket = socket
        super()
      end
      
      # Connect the server
      def connect
        at_exit { remove_socket_file } # In case it crashes
        old_umask = File.umask(0)
        begin
          EventMachine.start_unix_domain_server(@socket, UnixConnection, &method(:initialize_connection))
          # HACK EventMachine.start_unix_domain_server doesn't return the connection signature
          #      so we have to go in the internal stuff to find it.
        @signature = EventMachine.instance_eval{@acceptors.keys.first}
        ensure
          File.umask(old_umask)
        end
      end
      
      # Stops the server
      def disconnect
        EventMachine.stop_server(@signature)
      end
      
      # Free up resources used by the backend.
      def close
        remove_socket_file
      end
      
      def to_s
        @socket
      end
      
      protected
        def remove_socket_file
          File.delete(@socket) if @socket && File.exist?(@socket)
        end
    end    
  end

  # Connection through a UNIX domain socket.
  class UnixConnection < Connection
    protected
      def socket_address        
        '127.0.0.1' # Unix domain sockets can only be local
      end
  end
end
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/command.rb000066400000000000000000000027611475433514000230410ustar00rootroot00000000000000require 'open3'

module Thin
  # Run a command through the +thin+ command-line script.
  class Command
    include Logging
    
    class << self
      # Path to the +thin+ script used to control the servers.
      # Leave this to default to use the one in the path.
      attr_accessor :script
    end
    
    def initialize(name, options={})
      @name    = name
      @options = options
    end
    
    def self.run(*args)
      new(*args).run
    end
    
    # Send the command to the +thin+ script
    def run
      shell_cmd = shellify
      trace shell_cmd
      trap('INT') {} # Ignore INT signal to pass CTRL+C to subprocess
      Open3.popen3(shell_cmd) do |stdin, stdout, stderr|
        log_info stdout.gets until stdout.eof?
        log_info stderr.gets until stderr.eof?
      end
    end
    
    # Turn into a runnable shell command
    def shellify
      shellified_options = @options.inject([]) do |args, (name, value)|
        option_name = name.to_s.tr("_", "-")
        case value
        when NilClass,
             TrueClass then args << "--#{option_name}"
        when FalseClass
        when Array     then value.each { |v| args << "--#{option_name}=#{v.inspect}" }
        else                args << "--#{option_name}=#{value.inspect}"
        end
        args
      end
      
      raise ArgumentError, "Path to thin script can't be found, set Command.script" unless self.class.script
      
      "#{self.class.script} #{@name} #{shellified_options.compact.join(' ')}"
    end
  end
end
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/connection.rb000066400000000000000000000150251475433514000235570ustar00rootroot00000000000000require 'socket'

module Thin
  # Connection between the server and client.
  # This class is instanciated by EventMachine on each new connection
  # that is opened.
  class Connection < EventMachine::Connection
    include Logging

    # This is a template async response. N.B. Can't use string for body on 1.9
    AsyncResponse = [-1, {}, []].freeze

    # Rack application (adapter) served by this connection.
    attr_accessor :app

    # Backend to the server
    attr_accessor :backend

    # Current request served by the connection
    attr_accessor :request

    # Next response sent through the connection
    attr_accessor :response

    # Calling the application in a threaded allowing
    # concurrent processing of requests.
    attr_writer :threaded

    # Get the connection ready to process a request.
    def post_init
      @request  = Request.new
      @response = Response.new
    end

    # Called when data is received from the client.
    def receive_data(data)
      @idle = false
      trace data
      process if @request.parse(data)
    rescue InvalidRequest => e
      log_error("Invalid request", e)
      post_process Response::BAD_REQUEST
    end

    # Called when all data was received and the request
    # is ready to be processed.
    def process
      if threaded?
        @request.threaded = true
        EventMachine.defer { post_process(pre_process) }
      else
        @request.threaded = false
        post_process(pre_process)
      end
    end

    def ssl_verify_peer(cert)
      # In order to make the cert available later we have to have made at least
      # a show of verifying it.
      true
    end

    def pre_process
      # Add client info to the request env
      @request.remote_address = remote_address

      # Connection may be closed unless the App#call response was a [-1, ...]
      # It should be noted that connection objects will linger until this
      # callback is no longer referenced, so be tidy!
      @request.async_callback = method(:post_process)

      if @backend.ssl?
        @request.env["rack.url_scheme"] = "https"

        if @backend.respond_to?(:port)
          @request.env['SERVER_PORT'] = @backend.port.to_s
        end

        if cert = get_peer_cert
          @request.env['rack.peer_cert'] = cert
        end
      end

      # When we're under a non-async framework like rails, we can still spawn
      # off async responses using the callback info, so there's little point
      # in removing this.
      response = AsyncResponse
      catch(:async) do
        # Process the request calling the Rack adapter
        response = @app.call(@request.env)
      end
      response
    rescue Exception => e
      unexpected_error(e)
      # Pass through error response
      can_persist? && @request.persistent? ? Response::PERSISTENT_ERROR : Response::ERROR
    end

    def post_process(result)
      return unless result
      result = result.to_a

      # Status code -1 indicates that we're going to respond later (async).
      return if result.first == AsyncResponse.first

      @response.status, @response.headers, @response.body = *result

      log_error("Rack application returned nil body. " \
                "Probably you wanted it to be an empty string?") if @response.body.nil?

      # HEAD requests should not return a body.
      @response.skip_body! if @request.head?

      # Make the response persistent if requested by the client
      @response.persistent! if @request.persistent?

      # Send the response
      @response.each do |chunk|
        trace chunk
        send_data chunk
      end

    rescue Exception => e
      unexpected_error(e)
      # Close connection since we can't handle response gracefully
      close_connection
    ensure
      # If the body is being deferred, then terminate afterward.
      if @response.body.respond_to?(:callback) && @response.body.respond_to?(:errback)
        @response.body.callback { terminate_request }
        @response.body.errback  { terminate_request }
      else
        # Don't terminate the response if we're going async.
        terminate_request unless result && result.first == AsyncResponse.first
      end
    end

    # Logs information about an unexpected exceptional condition
    def unexpected_error(e)
      log_error("Unexpected error while processing request", e)
    end

    def close_request_response
      @request.async_close.succeed if @request.async_close
      @request.close  rescue nil
      @response.close rescue nil
    end

    # Does request and response cleanup (closes open IO streams and
    # deletes created temporary files).
    # Re-initializes response and request if client supports persistent
    # connection.
    def terminate_request
      unless persistent?
        close_connection_after_writing rescue nil
        close_request_response
      else
        close_request_response
        # Connection become idle but it's still open
        @idle = true
        # Prepare the connection for another request if the client
        # supports HTTP pipelining (persistent connection).
        post_init
      end
    end

    # Called when the connection is unbinded from the socket
    # and can no longer be used to process requests.
    def unbind
      @request.async_close.succeed if @request.async_close
      @response.body.fail if @response.body.respond_to?(:fail)
      @backend.connection_finished(self)
    end

    # Allows this connection to be persistent.
    def can_persist!
      @can_persist = true
    end

    # Return +true+ if this connection is allowed to stay open and be persistent.
    def can_persist?
      @can_persist
    end

    # Return +true+ if the connection must be left open
    # and ready to be reused for another request.
    def persistent?
      @can_persist && @response.persistent?
    end

    # Return +true+ if the connection is open but is not
    # processing any user requests
    def idle?
      @idle
    end

    # +true+ if app.call will be called inside a thread.
    # You can set all requests as threaded setting Connection#threaded=true
    # or on a per-request case returning +true+ in app.deferred?.
    def threaded?
      @threaded || (@app.respond_to?(:deferred?) && @app.deferred?(@request.env))
    end

    # IP Address of the remote client.
    def remote_address
      socket_address
    rescue Exception => e
      log_error('Could not infer remote address', e)
      nil
    end

    protected
      # Returns IP address of peer as a string.
      def socket_address
        peer = get_peername
        Socket.unpack_sockaddr_in(peer)[1] if peer
      end
  end
end
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/controllers/000077500000000000000000000000001475433514000234365ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/controllers/cluster.rb000066400000000000000000000123051475433514000254450ustar00rootroot00000000000000require 'socket'

module Thin
  # An exception class to handle the event that server didn't start on time
  class RestartTimeout < RuntimeError; end
  
  module Controllers
    # Control a set of servers.
    # * Generate start and stop commands and run them.
    # * Inject the port or socket number in the pid and log filenames.
    # Servers are started throught the +thin+ command-line script.
    class Cluster < Controller
      # Cluster only options that should not be passed in the command sent
      # to the indiviual servers.
      CLUSTER_OPTIONS = [:servers, :only, :onebyone, :wait]
      
      # Maximum wait time for the server to be restarted
      DEFAULT_WAIT_TIME = 30    # seconds
      
      # Create a new cluster of servers launched using +options+.
      def initialize(options)
        super
        # Cluster can only contain daemonized servers
        @options.merge!(:daemonize => true)
      end
      
      def first_port; @options[:port]     end
      def address;    @options[:address]  end
      def socket;     @options[:socket]   end
      def pid_file;   @options[:pid]      end
      def log_file;   @options[:log]      end
      def size;       @options[:servers]  end
      def only;       @options[:only]     end
      def onebyone;   @options[:onebyone] end
      def wait;       @options[:wait]     end
      
      def swiftiply?
        @options.has_key?(:swiftiply)
      end
    
      # Start the servers
      def start
        with_each_server { |n| start_server n }
      end
    
      # Start a single server
      def start_server(number)
        log_info "Starting server on #{server_id(number)} ... "
      
        run :start, number
      end
  
      # Stop the servers
      def stop
        with_each_server { |n| stop_server n }
      end
    
      # Stop a single server
      def stop_server(number)
        log_info "Stopping server on #{server_id(number)} ... "
      
        run :stop, number
      end
    
      # Stop and start the servers.
      def restart
        unless onebyone
          # Let's do a normal restart by defaults
          stop
          sleep 0.1 # Let's breath a bit shall we ?
          start
        else
          with_each_server do |n| 
            stop_server(n)
            sleep 0.1 # Let's breath a bit shall we ?
            start_server(n)
            wait_until_server_started(n)
          end
        end
      end
      
      def test_socket(number)
        if socket
          UNIXSocket.new(socket_for(number))
        else
          TCPSocket.new(address, number)
        end
      rescue
        nil
      end
      
      # Make sure the server is running before moving on to the next one.
      def wait_until_server_started(number)
        log_info "Waiting for server to start ..."
        STDOUT.flush # Need this to make sure user got the message
        
        tries = 0
        loop do
          if test_socket = test_socket(number)
            test_socket.close
            break
          elsif tries < wait
            sleep 1
            tries += 1
          else
            raise RestartTimeout, "The server didn't start in time. Please look at server's log file " +
                                  "for more information, or set the value of 'wait' in your config " +
                                  "file to be higher (defaults: 30)."
          end
        end
      end
    
      def server_id(number)
        if socket
          socket_for(number)
        elsif swiftiply?
          [address, first_port, number].join(':')
        else
          [address, number].join(':')
        end
      end
    
      def log_file_for(number)
        include_server_number log_file, number
      end
    
      def pid_file_for(number)
        include_server_number pid_file, number
      end
    
      def socket_for(number)
        include_server_number socket, number
      end
    
      def pid_for(number)
        File.read(pid_file_for(number)).chomp.to_i
      end
      
      private
        # Send the command to the +thin+ script
        def run(cmd, number)
          cmd_options = @options.reject { |option, value| CLUSTER_OPTIONS.include?(option) }
          cmd_options.merge!(:pid => pid_file_for(number), :log => log_file_for(number))
          if socket
            cmd_options.merge!(:socket => socket_for(number))
          elsif swiftiply?
            cmd_options.merge!(:port => first_port)
          else
            cmd_options.merge!(:port => number)
          end
          Command.run(cmd, cmd_options)
        end
      
        def with_each_server
          if only
            if first_port && only < 80
              # interpret +only+ as a sequence number
              yield first_port + only
            else
              # interpret +only+ as an absolute port number
              yield only
            end
          elsif socket || swiftiply?
            size.times { |n| yield n }
          else
            size.times { |n| yield first_port + n }
          end
        end
      
        # Add the server port or number in the filename
        # so each instance get its own file
        def include_server_number(path, number)
          ext = File.extname(path)
          path.gsub(/#{ext}$/, ".#{number}#{ext}")
        end
    end
  end
end
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/controllers/controller.rb000066400000000000000000000150731475433514000261540ustar00rootroot00000000000000require 'yaml'

module Thin
  # Error raised that will abort the process and print not backtrace.
  class RunnerError < RuntimeError; end
  
  # Raised when a mandatory option is missing to run a command.
  class OptionRequired < RunnerError
    def initialize(option)
      super("#{option} option required")
    end
  end
  
  # Raised when an option is not valid.
  class InvalidOption < RunnerError; end
  
  # Build and control Thin servers.
  # Hey Controller pattern is not only for web apps yo!
  module Controllers  
    # Controls one Thin server.
    # Allow to start, stop, restart and configure a single thin server.
    class Controller
      include Logging
    
      # Command line options passed to the thin script
      attr_accessor :options
    
      def initialize(options)
        @options = options
        
        if @options[:socket]
          @options.delete(:address)
          @options.delete(:port)
        end
      end
    
      def start
        # Constantize backend class
        @options[:backend] = eval(@options[:backend], TOPLEVEL_BINDING) if @options[:backend]

        server = Server.new(@options[:socket] || @options[:address], # Server detects kind of socket
                            @options[:port],                         # Port ignored on UNIX socket
                            @options)
        
        # Set options
        server.pid_file                       = @options[:pid]
        server.log_file                       = @options[:log]
        server.timeout                        = @options[:timeout]
        server.maximum_connections            = @options[:max_conns]
        server.maximum_persistent_connections = @options[:max_persistent_conns]
        server.threaded                       = @options[:threaded]
        server.no_epoll                       = @options[:no_epoll] if server.backend.respond_to?(:no_epoll=)
        server.threadpool_size                = @options[:threadpool_size] if server.threaded?

        # ssl support
        if @options[:ssl]
          server.ssl = true
          server.ssl_options = { :private_key_file => @options[:ssl_key_file], :cert_chain_file => @options[:ssl_cert_file], :verify_peer => !@options[:ssl_disable_verify], :ssl_version => @options[:ssl_version], :cipher_list => @options[:ssl_cipher_list]}
        end

        # Detach the process, after this line the current process returns
        server.daemonize if @options[:daemonize]

        # +config+ must be called before changing privileges since it might require superuser power.
        server.config
        
        server.change_privilege @options[:user], @options[:group] if @options[:user] && @options[:group]

        # If a Rack config file is specified we eval it inside a Rack::Builder block to create
        # a Rack adapter from it. Or else we guess which adapter to use and load it.
        if @options[:rackup]
          server.app = load_rackup_config
        else
          server.app = load_adapter
        end

        # If a prefix is required, wrap in Rack URL mapper
        server.app = Rack::URLMap.new(@options[:prefix] => server.app) if @options[:prefix]

        # If a stats URL is specified, wrap in Stats adapter
        server.app = Stats::Adapter.new(server.app, @options[:stats]) if @options[:stats]

        # Register restart procedure which just start another process with same options,
        # so that's why this is done here.
        server.on_restart { Command.run(:start, @options) }

        server.start
      end
    
      def stop
        raise OptionRequired, :pid unless @options[:pid]
      
        tail_log(@options[:log]) do
          if Server.kill(@options[:pid], @options[:force] ? 0 : (@options[:timeout] || 60))
            wait_for_file :deletion, @options[:pid]
          end
        end
      end
    
      def restart
        raise OptionRequired, :pid unless @options[:pid]
        
        tail_log(@options[:log]) do
          if Server.restart(@options[:pid])
            wait_for_file :creation, @options[:pid]
          end
        end
      end
    
      def config
        config_file = @options.delete(:config) || raise(OptionRequired, :config)

        # Stringify keys
        @options.keys.each { |o| @options[o.to_s] = @options.delete(o) }

        File.open(config_file, 'w') { |f| f << @options.to_yaml }
        log_info "Wrote configuration to #{config_file}"
      end
      
      protected
        # Wait for a pid file to either be created or deleted.
        def wait_for_file(state, file)
          Timeout.timeout(@options[:timeout] || 30) do
            case state
            when :creation then sleep 0.1 until File.exist?(file)
            when :deletion then sleep 0.1 while File.exist?(file)
            end
          end
        end
        
        # Tail the log file of server +number+ during the execution of the block.        
        def tail_log(log_file)
          if log_file
            tail_thread = tail(log_file)
            yield
            tail_thread.kill
          else
            yield
          end
        end
        
        # Acts like GNU tail command. Taken from Rails.
        def tail(file)
          cursor = File.exist?(file) ? File.size(file) : 0
          last_checked = Time.now
          tail_thread = Thread.new do
            Thread.pass until File.exist?(file)
            File.open(file, 'r') do |f|
              loop do
                f.seek cursor
                if f.mtime > last_checked
                  last_checked = f.mtime
                  contents = f.read
                  cursor += contents.length
                  print contents
                  STDOUT.flush
                end
                sleep 0.1
              end
            end
          end
          sleep 1 if File.exist?(file) # HACK Give the thread a little time to open the file
          tail_thread
        end

      private
        def load_adapter
          adapter = @options[:adapter] || Rack::Adapter.guess(@options[:chdir])
          log_info "Using #{adapter} adapter"
          Rack::Adapter.for(adapter, @options)
        rescue Rack::AdapterNotFound => e
          raise InvalidOption, e.message
        end
        
        def load_rackup_config
          ENV['RACK_ENV'] = @options[:environment]
          case @options[:rackup]
          when /\.rb$/
            Kernel.load(@options[:rackup])
            Object.const_get(File.basename(@options[:rackup], '.rb').capitalize.to_sym)
          when /\.ru$/
            Rack::Adapter.load(@options[:rackup])
          else
            raise "Invalid rackup file.  please specify either a .ru or .rb file"
          end
        end
    end
  end
end
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/controllers/service.rb000066400000000000000000000043231475433514000254250ustar00rootroot00000000000000require 'erb'

module Thin
  module Controllers
    # System service controller to launch all servers which
    # config files are in a directory.
    class Service < Controller
      INITD_PATH          = File.directory?('/etc/rc.d') ? '/etc/rc.d/thin' : '/etc/init.d/thin'
      DEFAULT_CONFIG_PATH = '/etc/thin'
      TEMPLATE            = File.dirname(__FILE__) + '/service.sh.erb'
    
      def initialize(options)
        super
      
        raise PlatformNotSupported, 'Running as a service only supported on Linux' unless Thin.linux?
      end
    
      def config_path
        @options[:all] || DEFAULT_CONFIG_PATH
      end
    
      def start
        run :start
      end
    
      def stop
        run :stop
      end
    
      def restart
        run :restart
      end
    
      def install(config_files_path=DEFAULT_CONFIG_PATH)
        if File.exist?(INITD_PATH)
          log_info "Thin service already installed at #{INITD_PATH}"
        else
          log_info "Installing thin service at #{INITD_PATH} ..."
          sh "mkdir -p #{File.dirname(INITD_PATH)}"
          log_info "writing #{INITD_PATH}"        
          File.open(INITD_PATH, 'w') do |f|
            f << ERB.new(File.read(TEMPLATE)).result(binding)
          end
          sh "chmod +x #{INITD_PATH}" # Make executable
        end
      
        sh "mkdir -p #{config_files_path}"

        log_info ''
        log_info "To configure thin to start at system boot:"
        log_info "on RedHat like systems:"
        log_info "  sudo /sbin/chkconfig --level 345 #{NAME} on"
        log_info "on Debian-like systems (Ubuntu):"
        log_info "  sudo /usr/sbin/update-rc.d -f #{NAME} defaults"
        log_info "on Gentoo:"
        log_info "  sudo rc-update add #{NAME} default"
        log_info ''
        log_info "Then put your config files in #{config_files_path}"
      end
    
      private
        def run(command)
          Dir[config_path + '/*'].each do |config|
            next if config.end_with?("~")
            log_info "[#{command}] #{config} ..."
            Command.run(command, :config => config, :daemonize => true)
          end
        end
      
        def sh(cmd)
          log_info cmd
          system(cmd)
        end
    end
  end
end
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/controllers/service.sh.erb000066400000000000000000000013371475433514000262050ustar00rootroot00000000000000#!/bin/sh
### BEGIN INIT INFO
# Provides:          thin
# Required-Start:    $local_fs $remote_fs
# Required-Stop:     $local_fs $remote_fs
# Default-Start:     2 3 4 5
# Default-Stop:      S 0 1 6
# Short-Description: thin initscript
# Description:       thin
### END INIT INFO

# Original author: Forrest Robertson

# Do NOT "set -e"

DAEMON=<%= Command.script %>
SCRIPT_NAME=<%= INITD_PATH %>
CONFIG_PATH=<%= config_files_path %>

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

case "$1" in
  start)
	$DAEMON start --all $CONFIG_PATH
	;;
  stop)
	$DAEMON stop --all $CONFIG_PATH
	;;
  restart)
	$DAEMON restart --all $CONFIG_PATH
	;;
  *)
	echo "Usage: $SCRIPT_NAME {start|stop|restart}" >&2
	exit 3
	;;
esac

:
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/daemonizing.rb000066400000000000000000000134721475433514000237300ustar00rootroot00000000000000require 'etc'
require 'daemons' unless Thin.win?

module Process
  # Returns +true+ the process identied by +pid+ is running.
  def running?(pid)
    Process.getpgid(pid) != -1
  rescue Errno::EPERM
    true
  rescue Errno::ESRCH
    false
  end
  module_function :running?
end

module Thin
  # Raised when the pid file already exist starting as a daemon.
  class PidFileExist < RuntimeError; end
  class PidFileNotFound < RuntimeError; end
  
  # Module included in classes that can be turned into a daemon.
  # Handle stuff like:
  # * storing the PID in a file
  # * redirecting output to the log file
  # * changing process privileges
  # * killing the process gracefully
  module Daemonizable
    attr_accessor :pid_file, :log_file
    
    def self.included(base)
      base.extend ClassMethods
    end

    def pid
      File.exist?(pid_file) && !File.zero?(pid_file) ? open(pid_file).read.to_i : nil
    end

    def kill(timeout = 60)
      if File.exist?(@pid_file)
        self.class.kill(@pid_file, timeout)
      end
    end

    # Turns the current script into a daemon process that detaches from the console.
    def daemonize
      raise PlatformNotSupported, 'Daemonizing is not supported on Windows'     if Thin.win?
      raise ArgumentError,        'You must specify a pid_file to daemonize' unless @pid_file
      
      remove_stale_pid_file
      
      pwd = Dir.pwd # Current directory is changed during daemonization, so store it
      
      # HACK we need to create the directory before daemonization to prevent a bug under 1.9
      #      ignoring all signals when the directory is created after daemonization.
      FileUtils.mkdir_p File.dirname(@pid_file)
      FileUtils.mkdir_p File.dirname(@log_file)
      
      Daemonize.daemonize(File.expand_path(@log_file), name)
      
      Dir.chdir(pwd)
      
      write_pid_file

      at_exit do
        log_info "Exiting!"
        remove_pid_file
      end
    end

    # Change privileges of the process
    # to the specified user and group.
    def change_privilege(user, group=user)
      log_info "Changing process privilege to #{user}:#{group}"
      
      uid, gid = Process.euid, Process.egid
      target_uid = Etc.getpwnam(user).uid
      target_gid = Etc.getgrnam(group).gid

      if uid != target_uid || gid != target_gid
        # Change PID file ownership
        File.chown(target_uid, target_gid, @pid_file) if File.exist?(@pid_file)

        # Change process ownership
        Process.initgroups(user, target_gid)
        Process::GID.change_privilege(target_gid)
        Process::UID.change_privilege(target_uid)

        # Correct environment variables
        ENV.store('USER', user)
        ENV.store('HOME', File.expand_path("~#{user}"))
      end
    rescue Errno::EPERM => e
      log_info "Couldn't change user and group to #{user}:#{group}: #{e}"
    end
    
    # Register a proc to be called to restart the server.
    def on_restart(&block)
      @on_restart = block
    end
    
    # Restart the server.
    def restart
      if @on_restart
        log_info 'Restarting ...'
        stop
        remove_pid_file
        @on_restart.call
        EM.next_tick { exit! }
      end
    end
    
    module ClassMethods
      # Send a QUIT or INT (if timeout is +0+) signal the process which
      # PID is stored in +pid_file+.
      # If the process is still running after +timeout+, KILL signal is
      # sent.
      def kill(pid_file, timeout=60)
        if timeout == 0
          send_signal('INT', pid_file, timeout)
        else
          send_signal('QUIT', pid_file, timeout)
        end
      end
      
      # Restart the server by sending HUP signal.
      def restart(pid_file)
        send_signal('HUP', pid_file)
      end

      def monotonic_time
        Process.clock_gettime(Process::CLOCK_MONOTONIC)
      end

      # Send a +signal+ to the process which PID is stored in +pid_file+.
      def send_signal(signal, pid_file, timeout=60)
        if pid = read_pid_file(pid_file)
          Logging.log_info "Sending #{signal} signal to process #{pid} ... "

          Process.kill(signal, pid)

          # This loop seems kind of racy to me...
          started_at = monotonic_time
          while Process.running?(pid)
            sleep 0.1
            raise Timeout::Error if (monotonic_time - started_at) > timeout
          end
        else
          raise PidFileNotFound, "Can't stop process, no PID found in #{pid_file}"
        end
      rescue Timeout::Error
        Logging.log_info "Timeout!"
        force_kill(pid, pid_file)
      rescue Interrupt
        force_kill(pid, pid_file)
      rescue Errno::ESRCH # No such process
        Logging.log_info "process not found!"
        force_kill(pid, pid_file)
      end
      
      def force_kill(pid, pid_file)
        Logging.log_info "Sending KILL signal to process #{pid} ... "
        Process.kill("KILL", pid)
        File.delete(pid_file) if File.exist?(pid_file)
      end
      
      def read_pid_file(file)
        if File.file?(file) && pid = File.read(file)
          pid.to_i
        else
          nil
        end
      end
    end
    
    protected
      def remove_pid_file
        File.delete(@pid_file) if @pid_file && File.exist?(@pid_file)
      end
    
      def write_pid_file
        log_info "Writing PID to #{@pid_file}"
        open(@pid_file,"w") { |f| f.write(Process.pid) }
        File.chmod(0644, @pid_file)
      end
      
      # If PID file is stale, remove it.
      def remove_stale_pid_file
        if File.exist?(@pid_file)
          if pid && Process.running?(pid)
            raise PidFileExist, "#{@pid_file} already exists, seems like it's already running (process ID: #{pid}). " +
                                "Stop the process or delete #{@pid_file}."
          else
            log_info "Deleting stale PID file #{@pid_file}"
            remove_pid_file
          end
        end
      end
  end
end
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/env.rb000066400000000000000000000011061475433514000222030ustar00rootroot00000000000000
module Thin
  module Env
    def self.with_defaults(env)
      if ::Rack.release >= "3"
        rack_env_class = Rack3
      else
        rack_env_class = Rack2
      end

      rack_env_class.env.merge(env)
    end
  end

  private

  class Rack2
    def self.env
      {
        ::Thin::Request::RACK_VERSION      => ::Thin::VERSION::RACK,
        ::Thin::Request::RACK_MULTITHREAD  => false,
        ::Thin::Request::RACK_MULTIPROCESS => false,
        ::Thin::Request::RACK_RUN_ONCE     => false
      }
    end
  end

  class Rack3
    def self.env
      {}
    end
  end
end
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/headers.rb000066400000000000000000000024111475433514000230260ustar00rootroot00000000000000module Thin
  # Raised when an header is not valid
  # and the server can not process it.
  class InvalidHeader < StandardError; end

  # Store HTTP header name-value pairs direcly to a string
  # and allow duplicated entries on some names.
  class Headers
    HEADER_FORMAT      = "%s: %s\r\n".freeze
    ALLOWED_DUPLICATES = %w(set-cookie set-cookie2 warning www-authenticate).freeze
    CR_OR_LF           = /[\r\n]/.freeze
    
    def initialize
      @sent = {}
      @out = []
    end
    
    # Add key: value pair to the headers.
    # Ignore if already sent and no duplicates are allowed
    # for this +key+.
    def []=(key, value)
      downcase_key = key.downcase
      if !@sent.has_key?(downcase_key) || ALLOWED_DUPLICATES.include?(downcase_key)
        @sent[downcase_key] = true
        value = case value
                when Time
                  value.httpdate
                when NilClass
                  return
                when CR_OR_LF
                  raise InvalidHeader, "Header contains CR or LF"
                else
                  value.to_s
                end
        @out << HEADER_FORMAT % [key, value]
      end
    end
    
    def has_key?(key)
      @sent[key.downcase]
    end
    
    def to_s
      @out.join
    end
  end
end
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/logging.rb000066400000000000000000000103751475433514000230510ustar00rootroot00000000000000require 'logger'

module Thin
  # To be included in classes to allow some basic logging
  # that can be silenced (Logging.silent=) or made
  # more verbose.
  # Logging.trace=:  log all raw request and response and
  #                           messages logged with +trace+.
  # Logging.silent=: silence all log all log messages
  #                           altogether.
  module Logging
    # Simple formatter which only displays the message.
    # Taken from ActiveSupport
    class SimpleFormatter < Logger::Formatter
      def call(severity, timestamp, progname, msg)
        "#{timestamp} #{String === msg ? msg : msg.inspect}\n"
      end
    end

    @trace_logger = nil

    class << self
      attr_reader :logger
      attr_reader :trace_logger

      def trace=(enabled)
        if enabled
          @trace_logger ||= Logger.new(STDOUT)
        else
          @trace_logger = nil
        end
      end

      def trace?
        !@trace_logger.nil?
      end

      def silent=(shh)
        if shh
          @logger = nil
        else
          @logger ||= Logger.new(STDOUT)
        end
      end

      def silent?
        !@logger.nil?
      end

      def level
        @logger ? @logger.level : nil # or 'silent'
      end

      def level=(value)
        # If logging has been silenced, then re-enable logging
        @logger = Logger.new(STDOUT) if @logger.nil?
        @logger.level = value
      end

      # Allow user to specify a custom logger to use.
      # This object must respond to:
      # +level+, +level=+ and +debug+, +info+, +warn+, +error+, +fatal+
      def logger=(custom_logger)
        [ :level   ,
          :level=  ,
          :debug   ,
          :info    ,
          :warn    ,
          :error   ,
          :fatal   ,
          :unknown ,
        ].each do |method|
          if not custom_logger.respond_to?(method)
            raise ArgumentError, "logger must respond to #{method}"
          end
        end

        @logger = custom_logger
      end

      def trace_logger=(custom_tracer)
        [ :level   ,
          :level=  ,
          :debug   ,
          :info    ,
          :warn    ,
          :error   ,
          :fatal   ,
          :unknown ,
        ].each do |method|
          if not custom_tracer.respond_to?(method)
            raise ArgumentError, "trace logger must respond to #{method}"
          end
        end

        @trace_logger = custom_tracer
      end

      def log_msg(msg, level=Logger::INFO)
        return unless @logger
        @logger.add(level, msg)
      end

      def trace_msg(msg)
        return unless @trace_logger
        @trace_logger.info(msg)
      end

      # Provided for backwards compatibility.
      # Callers should be using the +level+ (on the +Logging+ module
      # or on the instance) to figure out what the log level is.
      def debug?
        self.level == Logger::DEBUG
      end
      def debug=(val)
        self.level = (val ? Logger::DEBUG : Logger::INFO)
      end

    end # module methods

    # Default logger to stdout.
    self.logger           = Logger.new(STDOUT)
    self.logger.level     = Logger::INFO
    self.logger.formatter = Logging::SimpleFormatter.new

    def silent
      Logging.silent?
    end

    def silent=(value)
      Logging.silent = value
    end

    # Log a message if tracing is activated
    def trace(msg=nil)
      Logging.trace_msg(msg) if msg
    end
    module_function :trace
    public :trace

    # Log a message at DEBUG level
    def log_debug(msg=nil)
      Logging.log_msg(msg || yield, Logger::DEBUG)
    end
    module_function :log_debug
    public :log_debug

    # Log a message at INFO level
    def log_info(msg)
      Logging.log_msg(msg || yield, Logger::INFO)
    end
    module_function :log_info
    public :log_info

    # Log a message at ERROR level (and maybe a backtrace)
    def log_error(msg, e=nil)
      log_msg = msg
      if e
        log_msg += ": #{e}\n\t" + e.backtrace.join("\n\t") + "\n"
      end
      Logging.log_msg(log_msg, Logger::ERROR)
    end
    module_function :log_error
    public :log_error

    # For backwards compatibility
    def log msg
      STDERR.puts('#log has been deprecated, please use the ' \
                  'log_level function instead (e.g. - log_info).')
      log_info(msg)
    end

  end
end
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/rackup/000077500000000000000000000000001475433514000223555ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/rackup/handler.rb000066400000000000000000000015241475433514000243210ustar00rootroot00000000000000# frozen_string_literal: true

module Thin
  module Rackup
    class Handler
      def self.run(app, **options)
        environment  = ENV['RACK_ENV'] || 'development'
        default_host = environment == 'development' ? 'localhost' : '0.0.0.0'

        host = options.delete(:Host) || default_host
        port = options.delete(:Port) || 8080
        args = [host, port, app, options]

        server = ::Thin::Server.new(*args)
        yield server if block_given?

        server.start
      end

      def self.valid_options
        environment  = ENV['RACK_ENV'] || 'development'
        default_host = environment == 'development' ? 'localhost' : '0.0.0.0'

        {
          "Host=HOST" => "Hostname to listen on (default: #{default_host})",
          "Port=PORT" => "Port to listen on (default: 8080)",
        }
      end
    end
  end
end
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/request.rb000066400000000000000000000116461475433514000231150ustar00rootroot00000000000000require 'tempfile'
require_relative './env'

module Thin
  # Raised when an incoming request is not valid
  # and the server can not process it.
  class InvalidRequest < IOError; end

  # A request sent by the client to the server.
  class Request
    # Maximum request body size before it is moved out of memory
    # and into a tempfile for reading.
    MAX_BODY          = 1024 * (80 + 32)
    BODY_TMPFILE      = 'thin-body'.freeze
    MAX_HEADER        = 1024 * (80 + 32)

    INITIAL_BODY      = String.new
    # Force external_encoding of request's body to ASCII_8BIT
    INITIAL_BODY.encode!(Encoding::ASCII_8BIT) if INITIAL_BODY.respond_to?(:encode!) && defined?(Encoding::ASCII_8BIT)

    # Freeze some HTTP header names & values
    SERVER_SOFTWARE   = 'SERVER_SOFTWARE'.freeze
    SERVER_NAME       = 'SERVER_NAME'.freeze
    REQUEST_METHOD    = 'REQUEST_METHOD'.freeze
    LOCALHOST         = 'localhost'.freeze
    REQUEST_HTTP_VERSION      = 'thin.request_http_version'.freeze
    HTTP_1_0          = 'HTTP/1.0'.freeze
    REMOTE_ADDR       = 'REMOTE_ADDR'.freeze
    CONTENT_LENGTH    = 'CONTENT_LENGTH'.freeze
    CONNECTION        = 'HTTP_CONNECTION'.freeze
    KEEP_ALIVE_REGEXP = /\bkeep-alive\b/i.freeze
    CLOSE_REGEXP      = /\bclose\b/i.freeze
    HEAD              = 'HEAD'.freeze

    # Freeze some Rack header names
    RACK_INPUT        = 'rack.input'.freeze
    RACK_VERSION      = 'rack.version'.freeze
    RACK_ERRORS       = 'rack.errors'.freeze
    RACK_MULTITHREAD  = 'rack.multithread'.freeze
    RACK_MULTIPROCESS = 'rack.multiprocess'.freeze
    RACK_RUN_ONCE     = 'rack.run_once'.freeze
    ASYNC_CALLBACK    = 'async.callback'.freeze
    ASYNC_CLOSE       = 'async.close'.freeze

    # CGI-like request environment variables
    attr_reader :env

    # Unparsed data of the request
    attr_reader :data

    # Request body
    attr_reader :body

    def initialize
      @parser   = Thin::HttpParser.new
      @data     = String.new
      @nparsed  = 0
      @body     = StringIO.new(INITIAL_BODY.dup)
      @env      = Env.with_defaults({
        SERVER_SOFTWARE   => SERVER,
        SERVER_NAME       => LOCALHOST,

        # Rack stuff
        RACK_INPUT        => @body,
        RACK_ERRORS       => STDERR,
      })
    end

    # Parse a chunk of data into the request environment
    # Raises an +InvalidRequest+ if invalid.
    # Returns +true+ if the parsing is complete.
    def parse(data)
      if data.size > 0 && finished? # headers and body already fully satisfied. more data is erroneous.
        raise InvalidRequest, 'Content longer than specified'
      elsif @parser.finished?  # Header finished, can only be some more body
        @body << data
      else                  # Parse more header using the super parser
        @data << data
        raise InvalidRequest, 'Header longer than allowed' if @data.size > MAX_HEADER

        @nparsed = @parser.execute(@env, @data, @nparsed)

        # Transfer to a tempfile if body is very big
        move_body_to_tempfile if @parser.finished? && content_length > MAX_BODY
      end


      if finished?   # Check if header and body are complete
        @data = nil
        @body.rewind
        true         # Request is fully parsed
      else
        false        # Not finished, need more data
      end
    end

    # +true+ if headers and body are finished parsing
    def finished?
      @parser.finished? && @body.size >= content_length
    end

    # Expected size of the body
    def content_length
      @env[CONTENT_LENGTH].to_i
    end

    # Returns +true+ if the client expects the connection to be persistent.
    def persistent?
      # Clients and servers SHOULD NOT assume that a persistent connection
      # is maintained for HTTP versions less than 1.1 unless it is explicitly
      # signaled. (http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html)
      if @env[REQUEST_HTTP_VERSION] == HTTP_1_0
        @env[CONNECTION] =~ KEEP_ALIVE_REGEXP

      # HTTP/1.1 client intends to maintain a persistent connection unless
      # a Connection header including the connection-token "close" was sent
      # in the request
      else
        @env[CONNECTION].nil? || @env[CONNECTION] !~ CLOSE_REGEXP
      end
    end

    def remote_address=(address)
      @env[REMOTE_ADDR] = address
    end

    def threaded=(value)
      @env[RACK_MULTITHREAD] = value
    end

    def async_callback=(callback)
      @env[ASYNC_CALLBACK] = callback
      @env[ASYNC_CLOSE] = EventMachine::DefaultDeferrable.new
    end

    def async_close
      @async_close ||= @env[ASYNC_CLOSE]
    end

    def head?
      @env[REQUEST_METHOD] == HEAD
    end

    # Close any resource used by the request
    def close
      @body.close! if @body.class == Tempfile
    end

    private
      def move_body_to_tempfile
        current_body = @body
        current_body.rewind
        @body = Tempfile.new(BODY_TMPFILE)
        @body.binmode
        @body << current_body.read
        @env[RACK_INPUT] = @body
      end
  end
end
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/response.rb000066400000000000000000000101531475433514000232530ustar00rootroot00000000000000module Thin
  # A response sent to the client.
  class Response
    class Stream
      def initialize(writer)
        @read_closed = true
        @write_closed = false
        @writer = writer
      end

      def read(length = nil, outbuf = nil)
        raise ::IOError, 'not opened for reading' if @read_closed
      end

      def write(chunk)
        raise ::IOError, 'not opened for writing' if @write_closed

        @writer.call(chunk)
      end

      alias :<< :write

      def close
        @read_closed = @write_closed = true

        nil
      end

      def closed?
        @read_closed && @write_closed
      end

      def close_read
        @read_closed = true

        nil
      end

      def close_write
        @write_closed = true

        nil
      end

      def flush
        self
      end
    end

    CONNECTION     = 'connection'.freeze
    CLOSE          = 'close'.freeze
    KEEP_ALIVE     = 'keep-alive'.freeze
    SERVER         = 'server'.freeze
    CONTENT_LENGTH = 'content-length'.freeze

    PERSISTENT_STATUSES  = [100, 101].freeze

    #Error Responses
    ERROR            = [500, {'content-type' => 'text/plain'}, ['Internal server error']].freeze
    PERSISTENT_ERROR = [500, {'content-type' => 'text/plain', 'connection' => 'keep-alive', 'content-length' => "21"}, ['Internal server error']].freeze
    BAD_REQUEST      = [400, {'content-type' => 'text/plain'}, ['Bad Request']].freeze

    # Status code
    attr_accessor :status

    # Response body, must respond to +each+.
    attr_accessor :body

    # Headers key-value hash
    attr_reader   :headers

    def initialize
      @headers    = Headers.new
      @status     = 200
      @persistent = false
      @skip_body  = false
    end

    # String representation of the headers
    # to be sent in the response.
    def headers_output
      # Set default headers
      @headers[CONNECTION] = persistent? ? KEEP_ALIVE : CLOSE unless @headers.has_key?(CONNECTION)
      @headers[SERVER]     = Thin::NAME unless @headers.has_key?(SERVER)

      @headers.to_s
    end

    # Top header of the response,
    # containing the status code and response headers.
    def head
      "HTTP/1.1 #{@status} #{HTTP_STATUS_CODES[@status.to_i]}\r\n#{headers_output}\r\n"
    end

    if Thin.ruby_18?

      # Ruby 1.8 implementation.
      # Respects Rack specs.
      #
      # See http://rack.rubyforge.org/doc/files/SPEC.html
      def headers=(key_value_pairs)
        key_value_pairs.each do |k, vs|
          vs.each { |v| @headers[k] = v.chomp } if vs
        end if key_value_pairs
      end

    else

      # Ruby 1.9 doesn't have a String#each anymore.
      # Rack spec doesn't take care of that yet, for now we just use
      # +each+ but fallback to +each_line+ on strings.
      # I wish we could remove that condition.
      # To be reviewed when a new Rack spec comes out.
      def headers=(key_value_pairs)
        key_value_pairs.each do |k, vs|
          next unless vs
          if vs.is_a?(String)
            vs.each_line { |v| @headers[k] = v.chomp }
          else
            vs.each { |v| @headers[k] = v.chomp }
          end
        end if key_value_pairs
      end

    end

    # Close any resource used by the response
    def close
      @body.close if @body.respond_to?(:close)
    end

    # Yields each chunk of the response.
    # To control the size of each chunk
    # define your own +each+ method on +body+.
    def each(&block)
      yield head

      unless @skip_body
        if @body.is_a?(String)
          yield @body
        elsif @body.respond_to?(:each)
          @body.each { |chunk| yield chunk }
        else
          @body.call(Stream.new(block))
        end
      end
    end

    # Tell the client the connection should stay open
    def persistent!
      @persistent = true
    end

    # Persistent connection must be requested as keep-alive
    # from the server and have a content-length, or the response
    # status must require that the connection remain open.
    def persistent?
      (@persistent && @headers.has_key?(CONTENT_LENGTH)) || PERSISTENT_STATUSES.include?(@status)
    end

    def skip_body!
      @skip_body = true
    end
  end
end
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/runner.rb000066400000000000000000000262511475433514000227340ustar00rootroot00000000000000require 'logger'
require 'optparse'
require 'yaml'
require 'erb'

module Thin
  # CLI runner.
  # Parse options and send command to the correct Controller.
  class Runner
    COMMANDS            = %w(start stop restart config)
    LINUX_ONLY_COMMANDS = %w(install)

    # Commands that wont load options from the config file
    CONFIGLESS_COMMANDS = %w(config install)

    # Parsed options
    attr_accessor :options

    # Name of the command to be runned.
    attr_accessor :command

    # Arguments to be passed to the command.
    attr_accessor :arguments

    # Return all available commands
    def self.commands
      commands  = COMMANDS
      commands += LINUX_ONLY_COMMANDS if Thin.linux?
      commands
    end

    def initialize(argv)
      @argv = argv

      # Default options values
      @options = {
        :chdir                => Dir.pwd,
        :environment          => ENV['RACK_ENV'] || 'development',
        :address              => '0.0.0.0',
        :port                 => Server::DEFAULT_PORT,
        :timeout              => Server::DEFAULT_TIMEOUT,
        :log                  => File.join(Dir.pwd, 'log/thin.log'),
        :pid                  => 'tmp/pids/thin.pid',
        :max_conns            => Server::DEFAULT_MAXIMUM_CONNECTIONS,
        :max_persistent_conns => Server::DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS,
        :require              => [],
        :wait                 => Controllers::Cluster::DEFAULT_WAIT_TIME,
        :threadpool_size      => 20
      }

      parse!
    end

    def parser
      # NOTE: If you add an option here make sure the key in the +options+ hash is the
      # same as the name of the command line option.
      # +option+ keys are used to build the command line to launch other processes,
      # see lib/thin/command.rb.
      @parser ||= OptionParser.new do |opts|
        opts.banner = "Usage: thin [options] #{self.class.commands.join('|')}"

        opts.separator ""
        opts.separator "Server options:"

        opts.on("-a", "--address HOST", "bind to HOST address " +
                                        "(default: #{@options[:address]})")             { |host| @options[:address] = host }
        opts.on("-p", "--port PORT", "use PORT (default: #{@options[:port]})")          { |port| @options[:port] = port.to_i }
        opts.on("-S", "--socket FILE", "bind to unix domain socket")                    { |file| @options[:socket] = file }
        opts.on("-y", "--swiftiply [KEY]", "Run using swiftiply")                       { |key| @options[:swiftiply] = key }
        opts.on("-A", "--adapter NAME", "Rack adapter to use (default: autodetect)",
                                        "(#{Rack::ADAPTERS.map{|(a,b)|a}.join(', ')})") { |name| @options[:adapter] = name }
        opts.on("-R", "--rackup FILE", "Load a Rack config file instead of " +
                                       "Rack adapter")                                  { |file| @options[:rackup] = file }
        opts.on("-c", "--chdir DIR", "Change to dir before starting")                   { |dir| @options[:chdir] = File.expand_path(dir) }
        opts.on(      "--stats PATH", "Mount the Stats adapter under PATH")             { |path| @options[:stats] = path }

        opts.separator ""
        opts.separator "SSL options:"

        opts.on(      "--ssl", "Enables SSL")                                           { @options[:ssl] = true }
        opts.on(      "--ssl-key-file PATH", "Path to private key")                     { |path| @options[:ssl_key_file] = path }
        opts.on(      "--ssl-cert-file PATH", "Path to certificate")                    { |path| @options[:ssl_cert_file] = path }
        opts.on(      "--ssl-disable-verify", "Disables (optional) client cert requests") { @options[:ssl_disable_verify] = true }
        opts.on(      "--ssl-version VERSION", "TLSv1, TLSv1_1, TLSv1_2")               { |version| @options[:ssl_version] = version }
        opts.on(      "--ssl-cipher-list STRING", "Example: HIGH:!ADH:!RC4:-MEDIUM:-LOW:-EXP:-CAMELLIA") { |cipher| @options[:ssl_cipher_list] = cipher }

        opts.separator ""
        opts.separator "Adapter options:"
        opts.on("-e", "--environment ENV", "Framework environment " +
                                           "(default: #{@options[:environment]})")      { |env| @options[:environment] = env }
        opts.on(      "--prefix PATH", "Mount the app under PATH (start with /)")       { |path| @options[:prefix] = path }

        unless Thin.win? # Daemonizing not supported on Windows
          opts.separator ""
          opts.separator "Daemon options:"

          opts.on("-d", "--daemonize", "Run daemonized in the background")              { @options[:daemonize] = true }
          opts.on("-l", "--log FILE", "File to redirect output " +
                                      "(default: #{@options[:log]})")                   { |file| @options[:log] = file }
          opts.on("-P", "--pid FILE", "File to store PID " +
                                      "(default: #{@options[:pid]})")                   { |file| @options[:pid] = file }
          opts.on("-u", "--user NAME", "User to run daemon as (use with -g)")           { |user| @options[:user] = user }
          opts.on("-g", "--group NAME", "Group to run daemon as (use with -u)")         { |group| @options[:group] = group }
          opts.on(      "--tag NAME", "Additional text to display in process listing")  { |tag| @options[:tag] = tag }

          opts.separator ""
          opts.separator "Cluster options:"

          opts.on("-s", "--servers NUM", "Number of servers to start")                  { |num| @options[:servers] = num.to_i }
          opts.on("-o", "--only NUM", "Send command to only one server of the cluster") { |only| @options[:only] = only.to_i }
          opts.on("-C", "--config FILE", "Load options from config file")               { |file| @options[:config] = file }
          opts.on(      "--all [DIR]", "Send command to each config files in DIR")      { |dir| @options[:all] = dir } if Thin.linux?
          opts.on("-O", "--onebyone", "Restart the cluster one by one (only works with restart command)") { @options[:onebyone] = true }
          opts.on("-w", "--wait NUM", "Maximum wait time for server to be started in seconds (use with -O)") { |time| @options[:wait] = time.to_i }
        end

        opts.separator ""
        opts.separator "Tuning options:"

        opts.on("-b", "--backend CLASS", "Backend to use, full classname")              { |name| @options[:backend] = name }
        opts.on("-t", "--timeout SEC", "Request or command timeout in sec " +
                                       "(default: #{@options[:timeout]})")              { |sec| @options[:timeout] = sec.to_i }
        opts.on("-f", "--force", "Force the execution of the command")                  { @options[:force] = true }
        opts.on(      "--max-conns NUM", "Maximum number of open file descriptors " +
                                         "(default: #{@options[:max_conns]})",
                                         "Might require sudo to set higher than 1024")  { |num| @options[:max_conns] = num.to_i } unless Thin.win?
        opts.on(      "--max-persistent-conns NUM",
                                       "Maximum number of persistent connections",
                                       "(default: #{@options[:max_persistent_conns]})") { |num| @options[:max_persistent_conns] = num.to_i }
        opts.on(      "--threaded", "Call the Rack application in threads " +
                                    "[experimental]")                                   { @options[:threaded] = true }
        opts.on(      "--threadpool-size NUM", "Sets the size of the EventMachine threadpool.",
                                       "(default: #{@options[:threadpool_size]})") { |num| @options[:threadpool_size] = num.to_i }
        opts.on(      "--no-epoll", "Disable the use of epoll")                         { @options[:no_epoll] = true } if Thin.linux?

        opts.separator ""
        opts.separator "Common options:"

        opts.on_tail("-r", "--require FILE", "require the library")                     { |file| @options[:require] << file }
        opts.on_tail("-q", "--quiet", "Silence all logging")                            { @options[:quiet] = true }
        opts.on_tail("-D", "--debug", "Enable debug logging")                           { @options[:debug] = true }
        opts.on_tail("-V", "--trace", "Set tracing on (log raw request/response)")      { @options[:trace] = true }
        opts.on_tail("-h", "--help", "Show this message")                               { puts opts; exit }
        opts.on_tail('-v', '--version', "Show version")                                 { puts Thin::SERVER; exit }
      end
    end

    # Parse the options.
    def parse!
      parser.parse! @argv
      @command   = @argv.shift
      @arguments = @argv
    end

    # Parse the current shell arguments and run the command.
    # Exits on error.
    def run!
      if self.class.commands.include?(@command)
        run_command
      elsif @command.nil?
        puts "Command required"
        puts @parser
        exit 1
      else
        abort "Unknown command: #{@command}. Use one of #{self.class.commands.join(', ')}"
      end
    end

    # Send the command to the controller: single instance or cluster.
    def run_command
      load_options_from_config_file! unless CONFIGLESS_COMMANDS.include?(@command)

      # PROGRAM_NAME is relative to the current directory, so make sure
      # we store and expand it before changing directory.
      Command.script = File.expand_path($PROGRAM_NAME)

      # Change the current directory ASAP so that all relative paths are
      # relative to this one.
      Dir.chdir(@options[:chdir]) unless CONFIGLESS_COMMANDS.include?(@command)

      @options[:require].each { |r| ruby_require r }

      # Setup the logger
      if @options[:quiet]
        Logging.silent = true
      else
        Logging.level = Logger::DEBUG if @options[:debug]
      end

      if @options[:trace]
        # Trace raw requests/responses
        Logging.trace_logger = Logging.logger
      end

      controller = case
      when cluster? then Controllers::Cluster.new(@options)
      when service? then Controllers::Service.new(@options)
      else               Controllers::Controller.new(@options)
      end

      if controller.respond_to?(@command)
        begin
          controller.send(@command, *@arguments)
        rescue RunnerError => e
          abort e.message
        end
      else
        abort "Invalid options for command: #{@command}"
      end
    end

    # +true+ if we're controlling a cluster.
    def cluster?
      @options[:only] || @options[:servers] || @options[:config]
    end

    # +true+ if we're acting a as system service.
    def service?
      @options.has_key?(:all) || @command == 'install'
    end

    private
      def load_options_from_config_file!
        if file = @options.delete(:config)
          YAML.load(ERB.new(File.read(file)).result).each { |key, value| @options[key.to_sym] = value }
        end
      end

      def ruby_require(file)
        if File.extname(file) == '.ru'
          warn 'WARNING: Use the -R option to load a Rack config file'
          @options[:rackup] = file
        else
          require file
        end
      end
  end
end
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/server.rb000066400000000000000000000233621475433514000227310ustar00rootroot00000000000000module Thin
  # The utterly famous Thin HTTP server.
  # It listens for incoming requests through a given +backend+
  # and forwards all requests to +app+.
  #
  # == TCP server
  # Create a new TCP server bound to host:port by specifiying +host+
  # and +port+ as the first 2 arguments.
  #
  #   Thin::Server.start('0.0.0.0', 3000, app)
  #
  # == UNIX domain server
  # Create a new UNIX domain socket bound to +socket+ file by specifiying a filename
  # as the first argument. Eg.: /tmp/thin.sock. If the first argument contains a /
  # it will be assumed to be a UNIX socket. 
  #
  #   Thin::Server.start('/tmp/thin.sock', app)
  #
  # == Using a custom backend
  # You can implement your own way to connect the server to its client by creating your
  # own Backend class and passing it as the :backend option.
  #
  #   Thin::Server.start('galaxy://faraway', 1345, app, :backend => Thin::Backends::MyFancyBackend)
  #
  # == Rack application (+app+)
  # All requests will be processed through +app+, which must be a valid Rack adapter.
  # A valid Rack adapter (application) must respond to call(env#Hash) and
  # return an array of [status, headers, body].
  #
  # == Building an app in place
  # If a block is passed, a Rack::Builder instance
  # will be passed to build the +app+. So you can do cool stuff like this:
  # 
  #   Thin::Server.start('0.0.0.0', 3000) do
  #     use Rack::CommonLogger
  #     use Rack::ShowExceptions
  #     map "/lobster" do
  #       use Rack::Lint
  #       run Rack::Lobster.new
  #     end
  #   end
  #
  # == Controlling with signals
  # * INT and TERM: Force shutdown (see Server#stop!)
  # * TERM & QUIT calls +stop+ to shutdown gracefully.
  # * HUP calls +restart+ to ... surprise, restart!
  # * USR1 reopen log files.
  # Signals are processed at one second intervals.
  # Disable signals by passing :signals => false.
  # 
  class Server
    include Logging
    include Daemonizable
    extend  Forwardable
    
    # Default values
    DEFAULT_TIMEOUT                        = 30 #sec
    DEFAULT_HOST                           = '0.0.0.0'
    DEFAULT_PORT                           = 3000
    DEFAULT_MAXIMUM_CONNECTIONS            = 1024
    DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS = 100
        
    # Application (Rack adapter) called with the request that produces the response.
    attr_accessor :app

    # A tag that will show in the process listing
    attr_accessor :tag

    # Backend handling the connections to the clients.
    attr_accessor :backend
    
    # Maximum number of seconds for incoming data to arrive before the connection
    # is dropped.
    def_delegators :backend, :timeout, :timeout=
    
    # Maximum number of file or socket descriptors that the server may open.
    def_delegators :backend, :maximum_connections, :maximum_connections=
    
    # Maximum number of connections that can be persistent at the same time.
    # Most browsers never close the connection so most of the time they are closed
    # when the timeout occurs. If we don't control the number of persistent connections,
    # it would be very easy to overflow the server for a DoS attack.
    def_delegators :backend, :maximum_persistent_connections, :maximum_persistent_connections=
    
    # Allow using threads in the backend.
    def_delegators :backend, :threaded?, :threaded=, :threadpool_size, :threadpool_size=
    
    # Allow using SSL in the backend.
    def_delegators :backend, :ssl?, :ssl=, :ssl_options=
    
    # Address and port on which the server is listening for connections.
    def_delegators :backend, :host, :port
    
    # UNIX domain socket on which the server is listening for connections.
    def_delegator :backend, :socket
    
    # Disable the use of epoll under Linux
    def_delegators :backend, :no_epoll, :no_epoll=
    
    def initialize(*args, &block)
      host, port, options = DEFAULT_HOST, DEFAULT_PORT, {}
      
      # Guess each parameter by its type so they can be
      # received in any order.
      args.each do |arg|
        case arg
        when 0.class, /^\d+$/ then port    = arg.to_i
        when String           then host    = arg
        when Hash             then options = arg
        else
          @app = arg if arg.respond_to?(:call)
        end
      end
      
      # Set tag if needed
      self.tag = options[:tag]

      # Try to intelligently select which backend to use.
      @backend = select_backend(host, port, options)
      
      load_cgi_multipart_eof_fix
      
      @backend.server = self
      
      # Set defaults
      @backend.maximum_connections            = DEFAULT_MAXIMUM_CONNECTIONS
      @backend.maximum_persistent_connections = DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS
      @backend.timeout                        = options[:timeout] || DEFAULT_TIMEOUT
      
      # Allow using Rack builder as a block
      @app = Rack::Builder.new(&block).to_app if block
      
      # If in debug mode, wrap in logger adapter
      @app = Rack::CommonLogger.new(@app) if Logging.debug?
      
      @setup_signals = options[:signals] != false
    end
    
    # Lil' shortcut to turn this:
    # 
    #   Server.new(...).start
    # 
    # into this:
    # 
    #   Server.start(...)
    # 
    def self.start(*args, &block)
      new(*args, &block).start!
    end
        
    # Start the server and listen for connections.
    def start
      raise ArgumentError, 'app required' unless @app
      
      log_info  "Thin web server (v#{VERSION::STRING} codename #{VERSION::CODENAME})"
      log_debug "Debugging ON"
      trace     "Tracing ON"
      
      log_info "Maximum connections set to #{@backend.maximum_connections}"
      log_info "Listening on #{@backend}, CTRL+C to stop"

      @backend.start { setup_signals if @setup_signals }
    end
    alias :start! :start
    
    # == Gracefull shutdown
    # Stops the server after processing all current connections.
    # As soon as this method is called, the server stops accepting
    # new requests and waits for all current connections to finish.
    # Calling twice is the equivalent of calling stop!.
    def stop
      if running?
        @backend.stop
        unless @backend.empty?
          log_info "Waiting for #{@backend.size} connection(s) to finish, "\
                   "can take up to #{timeout} sec, CTRL+C to stop now"
        end
      else
        stop!
      end
    end
    
    # == Force shutdown
    # Stops the server closing all current connections right away.
    # This doesn't wait for connection to finish their work and send data.
    # All current requests will be dropped.
    def stop!
      if @backend.started_reactor?
        log_info "Stopping ..."
      else
        log_info "Stopping Thin ..."
        log_info "Thin was started inside an existing EventMachine.run block."
        log_info "Call `EventMachine.stop` to stop the reactor and quit the process."
      end

      @backend.stop!
    end
    
    # == Reopen log file.
    # Reopen the log file and redirect STDOUT and STDERR to it.
    def reopen_log
      return unless log_file
      file = File.expand_path(log_file)
      log_info "Reopening log file: #{file}"
      Daemonize.redirect_io(file)
    end
    
    # == Configure the server
    # The process might need to have superuser privilege to configure
    # server with optimal options.
    def config
      @backend.config
    end
    
    # Name of the server and type of backend used.
    # This is also the name of the process in which Thin is running as a daemon.
    def name
      "thin server (#{@backend})" + (tag ? " [#{tag}]" : "")
    end
    alias :to_s :name
    
    # Return +true+ if the server is running and ready to receive requests.
    # Note that the server might still be running and return +false+ when
    # shuting down and waiting for active connections to complete.
    def running?
      @backend.running?
    end
    
    protected
      def setup_signals
        # Queue up signals so they are processed in non-trap context
        # using a EM timer.
        @signal_queue ||= []

        %w( INT TERM ).each do |signal|
          trap(signal) { @signal_queue.push signal }
        end
        # *nix only signals
        %w( QUIT HUP USR1 ).each do |signal|
          trap(signal) { @signal_queue.push signal }
        end unless Thin.win?

        # Signals are processed at one second intervals.
        @signal_timer ||= EM.add_periodic_timer(1) { handle_signals }
      end

      def handle_signals
        case @signal_queue.shift
        when 'INT'
          stop!
        when 'TERM', 'QUIT'
          stop
        when 'HUP'
          restart
        when 'USR1'
          reopen_log
        end
        EM.next_tick { handle_signals } unless @signal_queue.empty?
      end
      
      def select_backend(host, port, options)
        case
        when options.has_key?(:backend)
          raise ArgumentError, ":backend must be a class" unless options[:backend].is_a?(Class)
          options[:backend].new(host, port, options)
        when options.has_key?(:swiftiply)
          Backends::SwiftiplyClient.new(host, port, options)
        when host.include?('/')
          Backends::UnixServer.new(host)
        else
          Backends::TcpServer.new(host, port)
        end
      end
      
      # Taken from Mongrel cgi_multipart_eof_fix
      # Ruby 1.8.5 has a security bug in cgi.rb, we need to patch it.
      def load_cgi_multipart_eof_fix
        version = RUBY_VERSION.split('.').map { |i| i.to_i }
        
        if version[0] <= 1 && version[1] <= 8 && version[2] <= 5 && RUBY_PLATFORM !~ /java/
          begin
            require 'cgi_multipart_eof_fix'
          rescue LoadError
            log_error "Ruby 1.8.5 is not secure please install cgi_multipart_eof_fix:"
            log_error "gem install cgi_multipart_eof_fix"
          end
        end
      end
  end
end
thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/stats.html.erb000066400000000000000000000142761475433514000236750ustar00rootroot00000000000000<%#
# Taken from Rack::ShowException
# adapted from Django 
# Copyright (c) 2005, the Lawrence Journal-World
# Used under the modified BSD license:
# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
%>



  
  
  Thin Stats
  



Server stats

<%= Thin::SERVER %>

Uptime <%= Time.now - @start_time %> sec
PID <%=h Process.pid %>
<% if @last_request %>

Jump to:

<% end %>

Requests

Stats

Requests <%= @requests %>
Finished <%= @requests_finished %>
Errors <%= @requests - @requests_finished %>
Last request <%= @last_request_time %> sec
<% if @last_request %>

Last Request information

GET

<% unless @last_request.GET.empty? %> <% @last_request.GET.sort_by { |k, v| k.to_s }.each { |key, val| %> <% } %>
Variable Value
<%=h key %>
<%=h val.inspect %>
<% else %>

No GET data.

<% end %>

POST

<% unless @last_request.POST.empty? %> <% @last_request.POST.sort_by { |k, v| k.to_s }.each { |key, val| %> <% } %>
Variable Value
<%=h key %>
<%=h val.inspect %>
<% else %>

No POST data.

<% end %> <% unless @last_request.cookies.empty? %> <% @last_request.cookies.each { |key, val| %> <% } %>
Variable Value
<%=h key %>
<%=h val.inspect %>
<% else %>

No cookie data.

<% end %>

Rack ENV

<% @last_request.env.sort_by { |k, v| k.to_s }.each { |key, val| %> <% } %>
Variable Value
<%=h key %>
<%=h val %>
<% end %>

You're seeing this page because you use Thin::Stats.

thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/stats.rb000066400000000000000000000021241475433514000225520ustar00rootroot00000000000000require 'erb' module Thin module Stats # Rack adapter to log stats about a Rack application. class Adapter include ERB::Util def initialize(app, path='/stats') @app = app @path = path @template = ERB.new(File.read(File.dirname(__FILE__) + '/stats.html.erb')) @requests = 0 @requests_finished = 0 @start_time = Time.now end def call(env) if env['PATH_INFO'].index(@path) == 0 serve(env) else log(env) { @app.call(env) } end end def log(env) @requests += 1 @last_request = Rack::Request.new(env) request_started_at = Time.now response = yield @requests_finished += 1 @last_request_time = Time.now - request_started_at response end def serve(env) body = @template.result(binding) [ 200, { 'content-type' => 'text/html' }, [body] ] end end end end thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/statuses.rb000066400000000000000000000027431475433514000232760ustar00rootroot00000000000000module Thin # Every standard HTTP code mapped to the appropriate message. # Stolent from Mongrel. HTTP_STATUS_CODES = { 100 => 'Continue', 101 => 'Switching Protocols', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Moved Temporarily', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 422 => 'Unprocessable Entity', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported', 511 => 'Network Authentication Required' } end thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/lib/thin/version.rb000066400000000000000000000012141475433514000231000ustar00rootroot00000000000000module Thin # Raised when a feature is not supported on the # current platform. class PlatformNotSupported < RuntimeError; end module VERSION #:nodoc: MAJOR = 1 MINOR = 8 TINY = 2 STRING = [MAJOR, MINOR, TINY].join('.') CODENAME = "Ruby Razor".freeze RACK = [1, 0].freeze # Rack protocol version end NAME = 'thin'.freeze SERVER = "#{NAME} #{VERSION::STRING} codename #{VERSION::CODENAME}".freeze def self.win? RUBY_PLATFORM =~ /mswin|mingw/ end def self.linux? RUBY_PLATFORM =~ /linux/ end def self.ruby_18? RUBY_VERSION =~ /^1\.8/ end end thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/script/000077500000000000000000000000001475433514000206645ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/script/bleak000077500000000000000000000047021475433514000216730ustar00rootroot00000000000000#!/usr/bin/env ruby-bleak-house # Script to launch thin with Bleak House # http://blog.evanweaver.com/files/doc/fauna/bleak_house/files/README.html # # Will dump data to log/memlog # Analyze the dump w/: # # bleak log/memlog # module Kernel def alias_method_chain(target, feature) # Strip out punctuation on predicates or bang methods since # e.g. target?_without_feature is not a valid method name. aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1 yield(aliased_target, punctuation) if block_given? with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuation}", "#{aliased_target}_without_#{feature}#{punctuation}" alias_method without_method, target alias_method target, with_method case when public_method_defined?(without_method) public target when protected_method_defined?(without_method) protected target when private_method_defined?(without_method) private target end end end module BleakInstruments module Connection def self.included(base) base.class_eval do alias_method_chain :receive_data, :instrument alias_method_chain :process, :instrument end end def receive_data_with_instrument(data) receive_data_without_instrument(data) $memlogger.snapshot($logfile, "connection/receive_data", false, 0.1) end def process_with_instrument process_without_instrument $memlogger.snapshot($logfile, "connection/process", false, 0.1) end end module Backend def self.included(base) base.class_eval do alias_method_chain :connect, :instrument alias_method_chain :initialize_connection, :instrument end end def connect_with_instrument connect_without_instrument $memlogger.snapshot($logfile, "backend/connect", false, 0.1) end def initialize_connection_with_instrument(connection) initialize_connection_without_instrument(connection) $memlogger.snapshot($logfile, "backend/initialize_connection", false, 0.1) end end end require 'rubygems' require 'bleak_house' $: << File.join(File.dirname(__FILE__), '..', 'lib') require 'thin' Thin::Connection.send :include, BleakInstruments::Connection Thin::Backends::TcpServer.send :include, BleakInstruments::Backend $memlogger = BleakHouse::Logger.new File.delete($logfile = File.expand_path("log/memlog")) rescue nil load 'bin/thin' thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/script/profile000077500000000000000000000010661475433514000222550ustar00rootroot00000000000000#!/usr/bin/env ruby # Script to profile thin using ruby-prof. # Takes the same arguments as the thin script. require 'rubygems' require 'ruby-prof' $: << File.join(File.dirname(__FILE__), '..', 'lib') require 'thin' class Adapter def call(env) [200, {'content-type' => 'text/html', 'content-length' => '11'}, ['hello world']] end end # Profile the code result = RubyProf.profile do Thin::Server.start('0.0.0.0', 3000) do run Adapter.new end end # Print a graph profile to text printer = RubyProf::GraphPrinter.new(result) printer.print(STDOUT, 0)thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/script/valgrind000077500000000000000000000002041475433514000224140ustar00rootroot00000000000000#!/usr/bin/env bash valgrind --tool=memcheck --leak-check=yes --show-reachable=no --num-callers=15 --track-fds=yes ruby bin/thin $@thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/site/000077500000000000000000000000001475433514000203245ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/site/images/000077500000000000000000000000001475433514000215715ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/site/images/bullet.gif000077500000000000000000000000701475433514000235470ustar00rootroot00000000000000GIF89a ppp!, `VsHm;thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/site/images/logo.gif000066400000000000000000000032611475433514000232220ustar00rootroot00000000000000GIF89a(_PPP111AAApppFFF;;;ZZZ"""___***%%%000]]]TTTvvv'''CCCsssﰰkkk{{{333QQQ!!!mmmͼhhh---߉888㮮OOOWWWLLLeee>>>bbbIIIyyyʝ~~~```555!_,(_^*__:^*0^ʋ)_^ڜτHΏ᭯߃ <&ʹyC*xI`a! eb9AP"!=hmV m).ʔV@bã !tWLSF0pv"O5@ѥdDDR$ EI4RAd-P'ez8p <8!D# @92@ ;Ј3 (#<8y w ˜#0]`(`aHQЈArxqt p f?ηH^"L*#$TI& aiq 5 I'$PB8QLI x:kh#B„~D1f,O0.H e5_̾x8:;&rD<@=or AW@"$` аTt 7XC b`''0@36R 359R)X:ѣP%%1)`ؤ'IR;thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/site/images/logo.psd000066400000000000000000005625751475433514000232650ustar00rootroot000000000000008BPSd|w8BIM%8BIM$9 application/vnd.adobe.photoshop Adobe Photoshop CS3 Macintosh 2008-05-01T20:46:25-04:00 2008-05-01T20:58:43-04:00 2008-05-01T20:58:43-04:00 uuid:C64D1CBC5119DD11B157912A980BBDE9 uuid:B03E6C8A5319DD11B157912A980BBDE9 1 720000/10000 720000/10000 2 256,257,258,259,262,274,277,284,530,531,282,283,296,301,318,319,529,532,306,270,271,272,305,315,33432;1C893CA1290170D3DAEDBB5B41F5112A 380 100 1 36864,40960,40961,37121,37122,40962,40963,37510,40964,36867,36868,33434,33437,34850,34852,34855,34856,37377,37378,37379,37380,37381,37382,37383,37384,37385,37386,37396,41483,41484,41486,41487,41488,41492,41493,41495,41728,41729,41730,41985,41986,41987,41988,41989,41990,41991,41992,41993,41994,41995,41996,42016,0,2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,20,22,23,24,25,26,27,28,30;339D9AC0BA18D57A6A51DB42B68A2DDF 3 sRGB IEC61966-2.1 8BIM com.apple.print.PageFormat.PMHorizontalRes com.apple.print.ticket.creator com.apple.jobticket com.apple.print.ticket.itemArray com.apple.print.PageFormat.PMHorizontalRes 72 com.apple.print.ticket.stateFlag 0 com.apple.print.PageFormat.PMOrientation com.apple.print.ticket.creator com.apple.jobticket com.apple.print.ticket.itemArray com.apple.print.PageFormat.PMOrientation 1 com.apple.print.ticket.stateFlag 0 com.apple.print.PageFormat.PMScaling com.apple.print.ticket.creator com.apple.jobticket com.apple.print.ticket.itemArray com.apple.print.PageFormat.PMScaling 1 com.apple.print.ticket.stateFlag 0 com.apple.print.PageFormat.PMVerticalRes com.apple.print.ticket.creator com.apple.jobticket com.apple.print.ticket.itemArray com.apple.print.PageFormat.PMVerticalRes 72 com.apple.print.ticket.stateFlag 0 com.apple.print.PageFormat.PMVerticalScaling com.apple.print.ticket.creator com.apple.jobticket com.apple.print.ticket.itemArray com.apple.print.PageFormat.PMVerticalScaling 1 com.apple.print.ticket.stateFlag 0 com.apple.print.subTicket.paper_info_ticket PMPPDPaperCodeName com.apple.print.ticket.creator com.apple.jobticket com.apple.print.ticket.itemArray PMPPDPaperCodeName Letter com.apple.print.ticket.stateFlag 0 PMTiogaPaperName com.apple.print.ticket.creator com.apple.jobticket com.apple.print.ticket.itemArray PMTiogaPaperName na-letter com.apple.print.ticket.stateFlag 0 com.apple.print.PageFormat.PMAdjustedPageRect com.apple.print.ticket.creator com.apple.jobticket com.apple.print.ticket.itemArray com.apple.print.PageFormat.PMAdjustedPageRect 0.0 0.0 734 576 com.apple.print.ticket.stateFlag 0 com.apple.print.PageFormat.PMAdjustedPaperRect com.apple.print.ticket.creator com.apple.jobticket com.apple.print.ticket.itemArray com.apple.print.PageFormat.PMAdjustedPaperRect -18 -18 774 594 com.apple.print.ticket.stateFlag 0 com.apple.print.PaperInfo.PMPaperName com.apple.print.ticket.creator com.apple.jobticket com.apple.print.ticket.itemArray com.apple.print.PaperInfo.PMPaperName na-letter com.apple.print.ticket.stateFlag 0 com.apple.print.PaperInfo.PMUnadjustedPageRect com.apple.print.ticket.creator com.apple.jobticket com.apple.print.ticket.itemArray com.apple.print.PaperInfo.PMUnadjustedPageRect 0.0 0.0 734 576 com.apple.print.ticket.stateFlag 0 com.apple.print.PaperInfo.PMUnadjustedPaperRect com.apple.print.ticket.creator com.apple.jobticket com.apple.print.ticket.itemArray com.apple.print.PaperInfo.PMUnadjustedPaperRect -18 -18 774 594 com.apple.print.ticket.stateFlag 0 com.apple.print.PaperInfo.ppd.PMPaperName com.apple.print.ticket.creator com.apple.jobticket com.apple.print.ticket.itemArray com.apple.print.PaperInfo.ppd.PMPaperName US Letter com.apple.print.ticket.stateFlag 0 com.apple.print.ticket.APIVersion 00.20 com.apple.print.ticket.type com.apple.print.PaperInfoTicket com.apple.print.ticket.APIVersion 00.20 com.apple.print.ticket.type com.apple.print.PageFormatTicket 8BIMHH8BIM&?8BIM x8BIM8BIM 8BIM 8BIM' 8BIMH/fflff/ff2Z5-8BIMp8BIM8BIM8BIM08BIM-8BIM@@8BIM8BIMnullbaseNameTEXTUserboundsObjcRct1Top longLeftlongBtomlongdRghtlong|slicesVlLsObjcslicesliceIDlonggroupIDlongoriginenum ESliceOrigin autoGeneratedTypeenum ESliceTypeImg boundsObjcRct1Top longLeftlongBtomlongdRghtlong|urlTEXTnullTEXTMsgeTEXTaltTagTEXTcellTextIsHTMLboolcellTextTEXT horzAlignenumESliceHorzAligndefault vertAlignenumESliceVertAligndefault bgColorTypeenumESliceBGColorTypeNone topOutsetlong leftOutsetlong bottomOutsetlong rightOutsetlong8BIM( ?8BIM H HLinomntrRGB XYZ  1acspMSFTIEC sRGB-HP cprtP3desclwtptbkptrXYZgXYZ,bXYZ@dmndTpdmddvuedLview$lumimeas $tech0 rTRC< gTRC< bTRC< textCopyright (c) 1998 Hewlett-Packard CompanydescsRGB IEC61966-2.1sRGB IEC61966-2.1XYZ QXYZ XYZ o8XYZ bXYZ $descIEC http://www.iec.chIEC http://www.iec.chdesc.IEC 61966-2.1 Default RGB colour space - sRGB.IEC 61966-2.1 Default RGB colour space - sRGBdesc,Reference Viewing Condition in IEC61966-2.1,Reference Viewing Condition in IEC61966-2.1view_. \XYZ L VPWmeassig CRT curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km8BIM 8BIM *N xJFIFHH Adobe_CMAdobed            *" ?   3!1AQa"q2B#$Rb34rC%Scs5&DTdE£t6UeuF'Vfv7GWgw5!1AQaq"2B#R3$brCScs4%&5DTdEU6teuFVfv'7GWgw ?TI%)%d.G֬Pm5_Xʮ?wF~;OGIJ\;:ٺv7gfE s=1FVEmw7ѿЦ-i irJ|YK]v̻ҩs?s2Ϯ'<>Օm;\S% vЍYt_O.{Z=!v#jJy[~^щҪ̯#4} )4:sI^C5?}ѿq)I!dUnMm4X&k79I?3?\Y̒ӱۛ[RSq$IJI$RIr'a^#!bd7]۱[I$S:wJWQȯe Xr>ESi8ZY{A?uxs&>B7z\љT5\;i ZW=63%Ծ}K=HCXwIICK1Z*cE9[ԪwFO*m}k_z*9z=ޞGxYstfcc:5m>Q]G2=փV3.Ʀk[eI>gt<{_Uȧѩ.ׁmj/n쿢\_\Ι+-FCnƺ~wE_%:f$k[쪽o C׳պ~001ksZK^\EmSǟ]I%G{j?ǟ]I%G{jJ|jחkI[Ux=Hkr/sh^չ/ֿؘ=] wuL7}U_te2YF߭zSd B._gJi4?J][r#Y2 ʙcW鴻T?ſUfE {lv.#h2YvGv~I)^]GOvvןN^9ݒvSʪ{޴suϫ^zoC=O WH}UTk,ߏvkc;ߑ}IO|:9W9+}r2.v;Ki`cs+eoս_Μl5 ,vu{wm7/+_S"3* ]Kl#n{n-=Τ{_[wtoU:ZՋ[ϥKXl>^ߤO}VǷ~5Y~6粗klTU5cG=A׾W>j/1Ȧ5z~{d΋/꘷f=>Ѻ[i7SuT{3g:IOI:ϭ/#/}b\KfM;{N7=olfWtY{2q6VdiK:oԟ[N{A|v;~nۺ.7}U۲r~nHk}ko+zOUվt>,Ue?9P[({W%KI$^7֬>CVְ~sOyNpbeU7>~=c?a$I%>I:r[VCvKm=-?l.Ww#`]obSM5aRl5h c_r.?ɟH7_Owo羓G-%>a3Ү"*m{KXd]6ǵk?z8=ݍk.m4ick&7?}'}9~q%> /ParagraphRun << /DefaultRunData << /ParagraphSheet << /DefaultStyleSheet 0 /Properties << >> >> /Adjustments << /Axis [ 1.0 0.0 1.0 ] /XY [ 0.0 0.0 ] >> >> /RunArray [ << /ParagraphSheet << /DefaultStyleSheet 0 /Properties << /Justification 2 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> /Adjustments << /Axis [ 1.0 0.0 1.0 ] /XY [ 0.0 0.0 ] >> >> ] /RunLengthArray [ 5 ] /IsJoinable 1 >> /StyleRun << /DefaultRunData << /StyleSheet << /StyleSheetData << >> >> >> /RunArray [ << /StyleSheet << /StyleSheetData << /Font 0 /FontSize 60.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 72.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 1 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst false /YUnderline 1 /OutlineWidth 1.0 >> >> >> ] /RunLengthArray [ 5 ] /IsJoinable 2 >> /GridInfo << /GridIsOn false /ShowGrid false /GridSize 18.0 /GridLeading 22.0 /GridColor << /Type 1 /Values [ 0.0 0.0 0.0 1.0 ] >> /GridLeadingFillColor << /Type 1 /Values [ 0.0 0.0 0.0 1.0 ] >> /AlignLineHeightToGridFlags false >> /AntiAlias 4 /UseFractionalGlyphWidths true /Rendered << /Version 1 /Shapes << /WritingDirection 0 /Children [ << /ShapeType 0 /Procession 0 /Lines << /WritingDirection 0 /Children [ ] >> /Cookie << /Photoshop << /ShapeType 0 /PointBase [ 0.0 0.0 ] /Base << /ShapeType 0 /TransformPoint0 [ 1.0 0.0 ] /TransformPoint1 [ 0.0 1.0 ] /TransformPoint2 [ 0.0 0.0 ] >> >> >> >> ] >> >> >> /ResourceDict << /KinsokuSet [ << /Name (PhotoshopKinsokuHard) /NoStart (00 00    0=]0 0 0 00000000A0C0E0G0I0c000000000000000000?!\)]},.:;!!  0) /NoEnd (  0;[00 0 00\([{ 0) /Keep (  %) /Hanging (00.,) >> << /Name (PhotoshopKinsokuSoft) /NoStart (00 0   0=]0 0 0 0000000) /NoEnd (  0;[00 0 00) /Keep (  %) /Hanging (00.,) >> ] /MojiKumiSet [ << /InternalName (Photoshop6MojiKumiSet1) >> << /InternalName (Photoshop6MojiKumiSet2) >> << /InternalName (Photoshop6MojiKumiSet3) >> << /InternalName (Photoshop6MojiKumiSet4) >> ] /TheNormalStyleSheet 0 /TheNormalParagraphSheet 0 /ParagraphSheetSet [ << /Name (Normal RGB) /DefaultStyleSheet 0 /Properties << /Justification 0 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> ] /StyleSheetSet [ << /Name (Normal RGB) /StyleSheetData << /Font 1 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst true /YUnderline 1 /OutlineWidth 1.0 >> >> ] /FontSet [ << /Name (MonaLisaSolidITCTT) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (MyriadPro-Regular) /Script 0 /FontType 0 /Synthetic 0 >> << /Name (AdobeInvisFont) /Script 0 /FontType 0 /Synthetic 0 >> ] /SuperscriptSize .583 /SuperscriptPosition .333 /SubscriptSize .583 /SubscriptPosition .333 /SmallCapSize .7 >> /DocumentResources << /KinsokuSet [ << /Name (PhotoshopKinsokuHard) /NoStart (00 00    0=]0 0 0 00000000A0C0E0G0I0c000000000000000000?!\)]},.:;!!  0) /NoEnd (  0;[00 0 00\([{ 0) /Keep (  %) /Hanging (00.,) >> << /Name (PhotoshopKinsokuSoft) /NoStart (00 0   0=]0 0 0 0000000) /NoEnd (  0;[00 0 00) /Keep (  %) /Hanging (00.,) >> ] /MojiKumiSet [ << /InternalName (Photoshop6MojiKumiSet1) >> << /InternalName (Photoshop6MojiKumiSet2) >> << /InternalName (Photoshop6MojiKumiSet3) >> << /InternalName (Photoshop6MojiKumiSet4) >> ] /TheNormalStyleSheet 0 /TheNormalParagraphSheet 0 /ParagraphSheetSet [ << /Name (Normal RGB) /DefaultStyleSheet 0 /Properties << /Justification 0 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 8 /Zone 36.0 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /AutoLeading 1.2 /LeadingType 0 /Hanging false /Burasagari false /KinsokuOrder 0 /EveryLineComposer false >> >> ] /StyleSheetSet [ << /Name (Normal RGB) /StyleSheetData << /Font 1 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /AutoKerning true /Kerning 0 /BaselineShift 0.0 /FontCaps 0 /FontBaseline 0 /Underline false /Strikethrough false /Ligatures true /DLigatures false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /NoBreak false /FillColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /StrokeColor << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> /FillFlag true /StrokeFlag false /FillFirst true /YUnderline 1 /OutlineWidth 1.0 >> >> ] /FontSet [ << /Name (MonaLisaSolidITCTT) /Script 0 /FontType 1 /Synthetic 0 >> << /Name (MyriadPro-Regular) /Script 0 /FontType 0 /Synthetic 0 >> << /Name (AdobeInvisFont) /Script 0 /FontType 0 /Synthetic 0 >> ] /SuperscriptSize .583 /SuperscriptPosition .333 /SubscriptSize .583 /SubscriptPosition .333 /SmallCapSize .7 >> >>warp warpStyleenum warpStylewarpNone warpValuedoubwarpPerspectivedoubwarpPerspectiveOtherdoub warpRotateenumOrntHrzn8BIMluni Thin8BIMlnsrrend8BIMlyid8BIMclbl8BIMinfx8BIMknko8BIMlspf8BIMlclr8BIMfxrpM6:TUy^w   8BIMnorm(Shape 18BIMluniShape 18BIMlnsrshap8BIMlyid 8BIMclbl8BIMinfx8BIMknko8BIMlspf8BIMlclr8BIMfxrp@4S^a   8BIMnorm( Shape 1 copy8BIMluni Shape 1 copy8BIMlnsrshap8BIMlyid 8BIMclbl8BIMinfx8BIMknko8BIMlspf8BIMlclr8BIMfxrp@S # &4=J=;9763/,,,,,,,,,,,,,,,,,,,,,..@@@@p@@@@P p@0@ @@ P@P@P@0@@@@@@@@@@@@@pP0`P0 `@@@@@@@@`Ѐ`@@p@`@P`P@p` 0@@@ 0@`@`@P`00@00@@0@0p@p@@ @@ @`@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ $*-,) (,/,# 0 Z6Zx 9BBr]x'`i  N66`-!?`6*! T3 B<`N* {6Zl6]lfƙZ K{uQ o!B!!~$iH96fWZ3ŕ90{<EN햨] > >> >> << /Resource << /StreamTag /CoolTypeFont /Identifier << /Name (MyriadPro-Regular) /Type 0 >> >> >> << /Resource << /StreamTag /CoolTypeFont /Identifier << /Name (AdobeInvisFont) /Type 0 >> >> >> ] >> /MojiKumiCodeToClassSet << /Resources [ << /Resource << /Name () /Members << /ClassMappings [ << /R (55) /C 1 >> << /R (77) /C 1 >> << /R (99) /C 1 >> << /R (;;) /C 1 >> << /R (==) /C 1 >> << /R (??) /C 1 >> << /R (AA) /C 1 >> << /R (CC) /C 1 >> << /R () /C 1 >> << /R (;;) /C 1 >> << /R ([[) /C 1 >> << /R (  ) /C 1 >> << /R (00) /C 1 >> << /R (0 0 ) /C 1 >> << /R (0 0 ) /C 1 >> << /R (00) /C 1 >> << /R (00) /C 1 >> << /R (00) /C 1 >> << /R (66) /C 2 >> << /R (88) /C 2 >> << /R (::) /C 2 >> << /R (<<) /C 2 >> << /R (>>) /C 2 >> << /R (@@) /C 2 >> << /R (BB) /C 2 >> << /R (DD) /C 2 >> << /R ( ) /C 2 >> << /R (==) /C 2 >> << /R (]]) /C 2 >> << /R (  ) /C 2 >> << /R (0 0 ) /C 2 >> << /R (0 0 ) /C 2 >> << /R (0 0 ) /C 2 >> << /R (00) /C 2 >> << /R (00) /C 2 >> << /R (00) /C 2 >> << /R () /C 3 >> << /R (^^) /C 3 >> << /R (0A0A) /C 3 >> << /R (0C0C) /C 3 >> << /R (0E0E) /C 3 >> << /R (0G0G) /C 3 >> << /R (0I0I) /C 3 >> << /R (0c0c) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R (00) /C 3 >> << /R () /C 4 >> << /R () /C 4 >> << /R () /C 5 >> << /R (00) /C 5 >> << /R (  ) /C 5 >> << /R () /C 6 >> << /R (00) /C 6 >> << /R ( ) /C 7 >> << /R (00) /C 7 >> << /R (  ) /C 8 >> << /R ( % &) /C 8 >> << /R () /C 9 >> << /R () /C 9 >> << /R () /C 9 >> << /R () /C 10 >> << /R () /C 10 >> << /R () /C 10 >> << /R ( 0 0) /C 10 >> << /R ( 2 4) /C 10 >> << /R (00) /C 11 >> << /R (0B0B) /C 12 >> << /R (0D0D) /C 12 >> << /R (0F0F) /C 12 >> << /R (0H0H) /C 12 >> << /R (0J0b) /C 12 >> << /R (0d0) /C 12 >> << /R (00) /C 12 >> << /R (00) /C 12 >> << /R (00) /C 12 >> << /R () /C 13 >> << /R (00) /C 14 >> << /R (00) /C 14 >> << /R (00) /C 14 >> << /R (00) /C 14 >> << /R (00) /C 14 >> << /R (00) /C 14 >> << /R (00) /C 14 >> << /R (00) /C 14 >> << /R (00) /C 14 >> << /R (2 2C) /C 14 >> << /R (22) /C 14 >> << /R (22) /C 14 >> << /R (33W) /C 14 >> << /R (3q3v) /C 14 >> << /R (33) /C 14 >> << /R (N) /C 14 >> << /R (09) /C 15 >> << /R (!~) /C 16 >> << /R () /C 16 >> << /R (  ) /C 16 >> << /R (  ) /C 16 >> ] >> >> >> ] /DisplayList [ << /Resource 0 >> ] >> /MojiKumiTableSet << /Resources [ << /Resource << /Name (Photoshop6MojiKumiSet4) /Members << /CodeToClass 0 /AutoTsume << /TsumeMappings [ << /Before -.5 /Code () >> << /Before -.5 /Code (;) >> << /Before -.5 /Code ([) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (=) >> << /After -.5 /Code (]) >> << /After -.5 /Code ( ) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code () >> << /After -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0) >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code (0) >> << /Before -.25 /After -.25 /Code ( ) >> ] >> /Table << /DataArray << /SparseArray [ << /Index 1 /Elements [ << /P [ 1 5 ] /Data << /A << /R [ .25 .25 .25 ] /P 1 >> >> >> ] >> << /Index 2 /Elements [ << /P [ 2 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 2 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 2 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 2 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 11 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 17 ] /Data << /B << /R [ .5 .5 .5 ] >> >> >> ] >> << /Index 3 /Elements [ << /P [ 3 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 3 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 3 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 3 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 15 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 3 16 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 4 /Elements [ << /P [ 4 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 4 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 4 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 4 15 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 4 16 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> ] >> << /Index 5 /Elements [ << /P [ 5 1 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 2 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 3 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 4 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 5 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 1 >> >> >> << /P [ 5 6 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 7 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 8 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 9 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 10 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 11 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 12 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 13 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 14 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 15 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 16 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 17 ] /Data << /B << /R [ .25 .25 .25 ] >> >> >> ] >> << /Index 6 /Elements [ << /P [ 6 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 6 3 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 4 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 5 ] /Data << /B << /R [ 0.0 .75 .75 ] /P 1 >> >> >> << /P [ 6 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 6 8 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 9 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 10 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 11 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 12 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 13 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 14 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> << /P [ 6 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> << /P [ 6 17 ] /Data << /B << /R [ .5 .5 .5 ] >> >> >> ] >> << /Index 7 /Elements [ << /P [ 7 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 7 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 7 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 17 ] /Data << /B << /R [ .5 .5 .5 ] >> >> >> ] >> << /Index 8 /Elements [ << /P [ 8 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 8 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 8 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 8 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 8 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 9 /Elements [ << /P [ 9 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 9 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 9 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 9 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 9 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 10 /Elements [ << /P [ 10 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 10 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 10 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 10 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 10 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 11 /Elements [ << /P [ 11 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 11 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 11 15 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> << /P [ 11 16 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> ] >> << /Index 12 /Elements [ << /P [ 12 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 12 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 12 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 12 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 12 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 13 /Elements [ << /P [ 13 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 13 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 13 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 13 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 13 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 14 /Elements [ << /P [ 14 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 14 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 14 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 14 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 14 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 15 /Elements [ << /P [ 15 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 15 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 15 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 15 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 15 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 15 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 16 /Elements [ << /P [ 16 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 16 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 16 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 16 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 16 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 16 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 17 /Elements [ << /P [ 17 1 ] /Data << /A << /R [ .5 .5 .5 ] >> >> >> ] >> << /Index 18 /Elements [ << /P [ 18 1 ] /Data << /A << /R [ .5 .5 .5 ] >> >> >> ] >> ] >> >> /PredefinedTag 2 >> >> >> << /Resource << /Name (Photoshop6MojiKumiSet3) /Members << /CodeToClass 0 /AutoTsume << /TsumeMappings [ << /Before -.5 /Code () >> << /Before -.5 /Code (;) >> << /Before -.5 /Code ([) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (=) >> << /After -.5 /Code (]) >> << /After -.5 /Code ( ) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code () >> << /After -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0) >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code (0) >> << /Before -.25 /After -.25 /Code ( ) >> ] >> /Table << /DataArray << /SparseArray [ << /Index 1 /Elements [ << /P [ 1 5 ] /Data << /A << /R [ .25 .25 .25 ] /P 1 >> >> >> ] >> << /Index 2 /Elements [ << /P [ 2 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 2 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 2 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 2 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 11 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 17 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> ] >> << /Index 3 /Elements [ << /P [ 3 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 3 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 3 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 3 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 15 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 3 16 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 4 /Elements [ << /P [ 4 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 4 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 4 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 4 15 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 4 16 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> ] >> << /Index 5 /Elements [ << /P [ 5 1 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 2 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 3 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 4 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 5 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 1 >> >> >> << /P [ 5 6 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 7 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 8 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 9 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 10 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 11 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 12 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 13 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 14 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 15 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 16 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> ] >> << /Index 6 /Elements [ << /P [ 6 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 6 3 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 4 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 5 ] /Data << /B << /R [ 0.0 .75 .75 ] /P 1 >> >> >> << /P [ 6 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 6 8 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 9 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 10 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 11 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 12 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 13 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 14 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> << /P [ 6 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> << /P [ 6 17 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> ] >> << /Index 7 /Elements [ << /P [ 7 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 7 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 7 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 17 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> ] >> << /Index 8 /Elements [ << /P [ 8 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 8 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 8 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 8 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 8 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 9 /Elements [ << /P [ 9 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 9 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 9 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 9 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 9 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 10 /Elements [ << /P [ 10 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 10 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 10 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 10 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 10 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 11 /Elements [ << /P [ 11 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 11 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 11 15 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> << /P [ 11 16 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> ] >> << /Index 12 /Elements [ << /P [ 12 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 12 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 12 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 12 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 12 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 13 /Elements [ << /P [ 13 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 13 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 13 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 13 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 13 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 14 /Elements [ << /P [ 14 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 14 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 14 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 14 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 14 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 15 /Elements [ << /P [ 15 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 15 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 15 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 15 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 15 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 15 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 16 /Elements [ << /P [ 16 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 16 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 16 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 16 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 16 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 16 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> ] >> >> /PredefinedTag 4 >> >> >> << /Resource << /Name (Photoshop6MojiKumiSet2) /Members << /CodeToClass 0 /AutoTsume << /TsumeMappings [ << /Before -.5 /Code () >> << /Before -.5 /Code (;) >> << /Before -.5 /Code ([) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (=) >> << /After -.5 /Code (]) >> << /After -.5 /Code ( ) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code () >> << /After -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0) >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code (0) >> << /Before -.25 /After -.25 /Code ( ) >> ] >> /Table << /DataArray << /SparseArray [ << /Index 1 /Elements [ << /P [ 1 5 ] /Data << /A << /R [ .25 .25 .25 ] /P 1 >> >> >> ] >> << /Index 2 /Elements [ << /P [ 2 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 2 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 2 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 2 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 11 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> ] >> << /Index 3 /Elements [ << /P [ 3 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 3 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 3 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 3 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 15 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 3 16 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 4 /Elements [ << /P [ 4 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 4 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 4 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 4 15 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 4 16 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> ] >> << /Index 5 /Elements [ << /P [ 5 1 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 2 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 3 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 4 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 5 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 1 >> >> >> << /P [ 5 6 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 7 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 8 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 9 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 10 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 11 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 12 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 13 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 14 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 15 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 16 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> ] >> << /Index 6 /Elements [ << /P [ 6 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 6 3 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 4 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 5 ] /Data << /B << /R [ 0.0 .75 .75 ] /P 1 >> >> >> << /P [ 6 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 6 8 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 9 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 10 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 11 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 12 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 13 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 14 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> << /P [ 6 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> ] >> << /Index 7 /Elements [ << /P [ 7 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 7 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 7 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> ] >> << /Index 8 /Elements [ << /P [ 8 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 8 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 8 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 8 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 8 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 9 /Elements [ << /P [ 9 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 9 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 9 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 9 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 9 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 10 /Elements [ << /P [ 10 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 10 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 10 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 10 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 10 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 11 /Elements [ << /P [ 11 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 11 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 11 15 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> << /P [ 11 16 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> ] >> << /Index 12 /Elements [ << /P [ 12 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 12 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 12 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 12 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 12 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 13 /Elements [ << /P [ 13 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 13 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 13 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 13 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 13 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 14 /Elements [ << /P [ 14 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 14 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 14 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 14 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 15 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 14 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 15 /Elements [ << /P [ 15 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 15 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 15 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 15 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 15 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 15 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 16 /Elements [ << /P [ 16 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 16 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 16 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 16 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 16 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 16 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> ] >> >> /PredefinedTag 3 >> >> >> << /Resource << /Name (Photoshop6MojiKumiSet1) /Members << /CodeToClass 0 /AutoTsume << /TsumeMappings [ << /Before -.5 /Code () >> << /Before -.5 /Code (;) >> << /Before -.5 /Code ([) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (=) >> << /After -.5 /Code (]) >> << /After -.5 /Code ( ) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code () >> << /After -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0) >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code (0) >> << /Before -.25 /After -.25 /Code ( ) >> ] >> /Table << /DataArray << /SparseArray [ << /Index 1 /Elements [ << /P [ 1 5 ] /Data << /A << /R [ .25 .25 .25 ] /P 1 >> >> >> ] >> << /Index 2 /Elements [ << /P [ 2 1 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 2 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 3 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 4 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 2 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 2 8 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 9 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 10 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 11 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 12 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 13 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 14 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> ] >> << /Index 3 /Elements [ << /P [ 3 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 3 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 3 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 3 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 15 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 3 16 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 4 /Elements [ << /P [ 4 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 4 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 4 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 4 15 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 4 16 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> ] >> << /Index 5 /Elements [ << /P [ 5 1 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 2 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 3 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 4 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 5 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 1 >> >> >> << /P [ 5 6 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 7 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 8 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 9 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 10 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 11 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 12 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 13 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 14 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 15 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 16 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> ] >> << /Index 6 /Elements [ << /P [ 6 1 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 6 3 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 4 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 6 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 6 8 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 9 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 10 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 11 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 12 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 13 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 14 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 6 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 7 /Elements [ << /P [ 7 1 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 3 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 4 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 7 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 7 8 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 9 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 10 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 12 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 13 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 14 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> ] >> << /Index 8 /Elements [ << /P [ 8 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 8 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 8 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 8 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 8 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 9 /Elements [ << /P [ 9 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 9 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 9 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 9 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 9 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 10 /Elements [ << /P [ 10 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 10 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 10 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 10 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 10 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 11 /Elements [ << /P [ 11 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 11 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 11 15 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> << /P [ 11 16 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> ] >> << /Index 12 /Elements [ << /P [ 12 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 12 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 12 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 12 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 12 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 13 /Elements [ << /P [ 13 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 13 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 13 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 13 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 13 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 14 /Elements [ << /P [ 14 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 14 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 14 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 14 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 14 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 15 /Elements [ << /P [ 15 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 15 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 15 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 15 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 15 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 15 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 16 /Elements [ << /P [ 16 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 16 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 16 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 16 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 16 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 16 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> ] >> >> /PredefinedTag 1 >> >> >> << /Resource << /Name (YakumonoHankaku) /Members << /CodeToClass 0 /AutoTsume << /TsumeMappings [ << /Before -.5 /Code () >> << /Before -.5 /Code (;) >> << /Before -.5 /Code ([) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (=) >> << /After -.5 /Code (]) >> << /After -.5 /Code ( ) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code () >> << /After -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0) >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code (0) >> << /Before -.25 /After -.25 /Code ( ) >> ] >> /Table << /DataArray << /SparseArray [ << /Index 1 /Elements [ << /P [ 1 5 ] /Data << /A << /R [ .25 .25 .25 ] /P 1 >> >> >> ] >> << /Index 2 /Elements [ << /P [ 2 1 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 2 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 3 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 4 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 2 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 2 8 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 9 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 10 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 11 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 12 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 13 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 14 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 2 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> ] >> << /Index 3 /Elements [ << /P [ 3 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 3 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 3 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 3 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 15 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 3 16 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 4 /Elements [ << /P [ 4 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 4 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 4 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 4 15 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 4 16 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> ] >> << /Index 5 /Elements [ << /P [ 5 1 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 2 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 3 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 4 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 5 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 1 >> >> >> << /P [ 5 6 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 7 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 8 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 9 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 10 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 11 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 12 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 13 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 14 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 15 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 16 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> ] >> << /Index 6 /Elements [ << /P [ 6 1 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 6 3 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 4 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 6 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 6 8 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 9 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 10 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 11 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 12 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 13 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 14 ] /Data << /B << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 6 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 6 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 7 /Elements [ << /P [ 7 1 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 3 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 4 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 7 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 7 8 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 9 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 10 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 12 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 13 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 14 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> << /P [ 7 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 2 >> >> >> ] >> << /Index 8 /Elements [ << /P [ 8 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 8 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 8 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 8 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 8 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 9 /Elements [ << /P [ 9 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 9 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 9 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 9 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 9 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 10 /Elements [ << /P [ 10 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 10 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 10 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 10 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 10 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 11 /Elements [ << /P [ 11 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 11 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 11 15 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> << /P [ 11 16 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> ] >> << /Index 12 /Elements [ << /P [ 12 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 12 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 12 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 12 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 12 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 13 /Elements [ << /P [ 13 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 13 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 13 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 13 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 13 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 14 /Elements [ << /P [ 14 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 14 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 14 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 14 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 14 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 15 /Elements [ << /P [ 15 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 15 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 15 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 15 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 15 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 15 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 16 /Elements [ << /P [ 16 1 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 3 >> >> >> << /P [ 16 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 16 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 16 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 16 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 16 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> ] >> >> /PredefinedTag 1 >> >> >> << /Resource << /Name (GyomatsuYakumonoHankaku) /Members << /CodeToClass 0 /AutoTsume << /TsumeMappings [ << /Before -.5 /Code () >> << /Before -.5 /Code (;) >> << /Before -.5 /Code ([) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (=) >> << /After -.5 /Code (]) >> << /After -.5 /Code ( ) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code () >> << /After -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0) >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code (0) >> << /Before -.25 /After -.25 /Code ( ) >> ] >> /Table << /DataArray << /SparseArray [ << /Index 1 /Elements [ << /P [ 1 5 ] /Data << /A << /R [ .25 .25 .25 ] /P 1 >> >> >> ] >> << /Index 2 /Elements [ << /P [ 2 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 2 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 2 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 2 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 11 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> ] >> << /Index 3 /Elements [ << /P [ 3 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 3 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 3 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 3 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 15 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 3 16 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 4 /Elements [ << /P [ 4 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 4 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 4 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 4 15 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 4 16 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> ] >> << /Index 5 /Elements [ << /P [ 5 1 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 2 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 3 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 4 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 5 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 1 >> >> >> << /P [ 5 6 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 7 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 8 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 9 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 10 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 11 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 12 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 13 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 14 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 15 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 16 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> ] >> << /Index 6 /Elements [ << /P [ 6 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 6 3 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 4 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 5 ] /Data << /B << /R [ 0.0 .75 .75 ] /P 1 >> >> >> << /P [ 6 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 6 8 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 9 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 10 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 11 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 12 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 13 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 14 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> << /P [ 6 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> ] >> << /Index 7 /Elements [ << /P [ 7 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 7 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 7 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> ] >> << /Index 8 /Elements [ << /P [ 8 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 8 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 8 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 8 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 8 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 9 /Elements [ << /P [ 9 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 9 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 9 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 9 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 9 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 10 /Elements [ << /P [ 10 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 10 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 10 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 10 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 10 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 11 /Elements [ << /P [ 11 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 11 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 11 15 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> << /P [ 11 16 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> ] >> << /Index 12 /Elements [ << /P [ 12 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 12 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 12 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 12 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 12 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 13 /Elements [ << /P [ 13 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 13 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 13 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 13 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 13 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 14 /Elements [ << /P [ 14 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 14 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 14 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 14 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 15 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 14 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 15 /Elements [ << /P [ 15 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 15 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 15 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 15 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 15 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 15 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 16 /Elements [ << /P [ 16 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 16 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 16 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 16 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 16 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 16 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> ] >> >> /PredefinedTag 3 >> >> >> << /Resource << /Name (GyomatsuYakumonoZenkaku) /Members << /CodeToClass 0 /AutoTsume << /TsumeMappings [ << /Before -.5 /Code () >> << /Before -.5 /Code (;) >> << /Before -.5 /Code ([) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (=) >> << /After -.5 /Code (]) >> << /After -.5 /Code ( ) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code () >> << /After -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0) >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code (0) >> << /Before -.25 /After -.25 /Code ( ) >> ] >> /Table << /DataArray << /SparseArray [ << /Index 1 /Elements [ << /P [ 1 5 ] /Data << /A << /R [ .25 .25 .25 ] /P 1 >> >> >> ] >> << /Index 2 /Elements [ << /P [ 2 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 2 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 2 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 2 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 11 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 17 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> ] >> << /Index 3 /Elements [ << /P [ 3 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 3 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 3 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 3 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 15 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 3 16 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 4 /Elements [ << /P [ 4 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 4 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 4 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 4 15 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 4 16 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> ] >> << /Index 5 /Elements [ << /P [ 5 1 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 2 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 3 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 4 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 5 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 1 >> >> >> << /P [ 5 6 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 7 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 8 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 9 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 10 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 11 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 12 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 13 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 14 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 15 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 16 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> ] >> << /Index 6 /Elements [ << /P [ 6 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 6 3 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 4 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 5 ] /Data << /B << /R [ 0.0 .75 .75 ] /P 1 >> >> >> << /P [ 6 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 6 8 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 9 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 10 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 11 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 12 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 13 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 14 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> << /P [ 6 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> << /P [ 6 17 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> ] >> << /Index 7 /Elements [ << /P [ 7 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 7 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 7 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 17 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> ] >> << /Index 8 /Elements [ << /P [ 8 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 8 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 8 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 8 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 8 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 9 /Elements [ << /P [ 9 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 9 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 9 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 9 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 9 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 10 /Elements [ << /P [ 10 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 10 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 10 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 10 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 10 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 11 /Elements [ << /P [ 11 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 11 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 11 15 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> << /P [ 11 16 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> ] >> << /Index 12 /Elements [ << /P [ 12 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 12 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 12 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 12 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 12 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 13 /Elements [ << /P [ 13 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 13 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 13 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 13 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 13 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 14 /Elements [ << /P [ 14 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 14 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 14 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 14 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 14 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 15 /Elements [ << /P [ 15 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 15 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 15 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 15 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 15 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 15 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 16 /Elements [ << /P [ 16 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 16 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 16 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 16 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 16 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 16 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> ] >> >> /PredefinedTag 4 >> >> >> << /Resource << /Name (YakumonoZenkaku) /Members << /CodeToClass 0 /AutoTsume << /TsumeMappings [ << /Before -.5 /Code () >> << /Before -.5 /Code (;) >> << /Before -.5 /Code ([) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code ( ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0 ) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /Before -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (=) >> << /After -.5 /Code (]) >> << /After -.5 /Code ( ) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0 ) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code (0) >> << /After -.5 /Code () >> << /After -.5 /Code (0) >> << /After -.5 /Code ( ) >> << /After -.5 /Code (0) >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code () >> << /Before -.25 /After -.25 /Code (0) >> << /Before -.25 /After -.25 /Code ( ) >> ] >> /Table << /DataArray << /SparseArray [ << /Index 1 /Elements [ << /P [ 1 5 ] /Data << /A << /R [ .25 .25 .25 ] /P 1 >> >> >> ] >> << /Index 2 /Elements [ << /P [ 2 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 2 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 2 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 2 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 11 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 3 >> >> >> << /P [ 2 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 2 17 ] /Data << /B << /R [ .5 .5 .5 ] >> >> >> ] >> << /Index 3 /Elements [ << /P [ 3 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 3 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 3 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 3 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 3 15 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 3 16 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 4 /Elements [ << /P [ 4 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 4 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 4 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 4 15 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 4 16 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> ] >> << /Index 5 /Elements [ << /P [ 5 1 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 2 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 3 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 4 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 5 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 1 >> >> >> << /P [ 5 6 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 7 ] /Data << /B << /R [ .25 .25 .25 ] /P 1 >> >> >> << /P [ 5 8 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 9 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 10 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 11 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 12 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 13 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 14 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 15 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 16 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 5 17 ] /Data << /B << /R [ .25 .25 .25 ] >> >> >> ] >> << /Index 6 /Elements [ << /P [ 6 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 6 3 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 4 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 5 ] /Data << /B << /R [ 0.0 .75 .75 ] /P 1 >> >> >> << /P [ 6 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 6 8 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 9 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 10 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 11 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 12 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 13 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 14 ] /Data << /B << /R [ 0.0 .5 .5 ] >> >> >> << /P [ 6 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> << /P [ 6 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 4 >> >> >> << /P [ 6 17 ] /Data << /B << /R [ .5 .5 .5 ] >> >> >> ] >> << /Index 7 /Elements [ << /P [ 7 1 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 3 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 4 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 5 ] /Data << /B << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 7 7 ] /Data << /B << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 7 8 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 9 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 10 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 12 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 13 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 14 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 15 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 16 ] /Data << /B << /R [ 0.0 .5 .5 ] /P 2 >> >> >> << /P [ 7 17 ] /Data << /B << /R [ .5 .5 .5 ] >> >> >> ] >> << /Index 8 /Elements [ << /P [ 8 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 8 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 8 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 8 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 8 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 8 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 9 /Elements [ << /P [ 9 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 9 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 9 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 9 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 9 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 9 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 10 /Elements [ << /P [ 10 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 10 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 10 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 10 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 10 15 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 10 16 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 11 /Elements [ << /P [ 11 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 11 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 11 15 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> << /P [ 11 16 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 4 >> >> >> ] >> << /Index 12 /Elements [ << /P [ 12 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 12 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 12 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 12 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 12 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 12 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 13 /Elements [ << /P [ 13 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 13 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 13 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 13 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 13 15 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 13 16 ] /Data << /B << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> ] >> << /Index 14 /Elements [ << /P [ 14 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 14 3 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 14 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 14 8 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 9 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 10 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 12 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 13 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 14 ] /Data << /A << /R [ 0.0 0.0 1.0 ] >> >> >> << /P [ 14 15 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 14 16 ] /Data << /B << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 15 /Elements [ << /P [ 15 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 15 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 15 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 15 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 15 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 15 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 15 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 15 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 16 /Elements [ << /P [ 16 1 ] /Data << /A << /R [ 0.0 .5 .5 ] /P 3 >> >> >> << /P [ 16 3 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 4 ] /Data << /A << /R [ .125 .25 .25 ] /P 4 >> >> >> << /P [ 16 5 ] /Data << /A << /R [ 0.0 .25 .25 ] /P 1 >> >> >> << /P [ 16 7 ] /Data << /A << /R [ 0.0 0.0 0.0 ] /P 2 >> >> >> << /P [ 16 8 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 9 ] /Data << /A << /R [ 0.0 0.0 .5 ] /P 4 >> >> >> << /P [ 16 10 ] /Data << /A << /R [ 0.0 0.0 .5 ] >> >> >> << /P [ 16 12 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> << /P [ 16 14 ] /Data << /A << /R [ .125 .25 .5 ] /P 4 >> >> >> ] >> << /Index 17 /Elements [ << /P [ 17 1 ] /Data << /A << /R [ .5 .5 .5 ] >> >> >> ] >> << /Index 18 /Elements [ << /P [ 18 1 ] /Data << /A << /R [ .5 .5 .5 ] >> >> >> ] >> ] >> >> /PredefinedTag 2 >> >> >> ] /DisplayList [ << /Resource 0 >> << /Resource 1 >> << /Resource 2 >> << /Resource 3 >> << /Resource 4 >> << /Resource 5 >> << /Resource 6 >> << /Resource 7 >> ] >> /KinsokuSet << /Resources [ << /Resource << /Name (None) /Data << /NoStart () /NoEnd () /Keep () /Hanging () /PredefinedTag 0 >> >> >> << /Resource << /Name (PhotoshopKinsokuHard) /Data << /NoStart (!\),.:;?]}    0!! 0000 0 0 0000A0C0E0G0I0c000000000000000000000000 =]) /NoEnd (\([{  00 0 0000 ;[) /Keep (  % &) /Hanging (00 ) /PredefinedTag 1 >> >> >> << /Resource << /Name (PhotoshopKinsokuSoft) /Data << /NoStart (  0000 0 0 00000000 =]) /NoEnd (  00 0 000;[) /Keep (  % &) /Hanging (00 ) /PredefinedTag 2 >> >> >> << /Resource << /Name (Hard) /Data << /NoStart (!\),.:;?]}    0!! 0000 0 0 0000A0C0E0G0I0c000000000000000000000000 =]) /NoEnd (\([{  00 0 0000 ;[) /Keep (  % &) /Hanging (00 ) /PredefinedTag 1 >> >> >> << /Resource << /Name (Soft) /Data << /NoStart (  0000 0 0 00000000 =]) /NoEnd (  00 0 000;[) /Keep (  % &) /Hanging (00 ) /PredefinedTag 2 >> >> >> ] /DisplayList [ << /Resource 0 >> << /Resource 1 >> << /Resource 2 >> << /Resource 3 >> << /Resource 4 >> ] >> /StyleSheetSet << /Resources [ << /Resource << /Name (Normal RGB) /Features << /Font 1 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /BaselineShift 0.0 /CharacterRotation 0.0 /AutoKern 1 /FontCaps 0 /FontBaseline 0 /FontOTPosition 0 /StrikethroughPosition 0 /UnderlinePosition 0 /UnderlineOffset 0.0 /Ligatures true /DiscretionaryLigatures false /ContextualLigatures false /AlternateLigatures false /OldStyle false /Fractions false /Ordinals false /Swash false /Titling false /ConnectionForms false /StylisticAlternates false /Ornaments false /FigureStyle 0 /ProportionalMetrics false /Kana false /Italics false /Ruby false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /JapaneseAlternateFeature 0 /EnableWariChu false /WariChuLineCount 2 /WariChuLineGap 0 /WariChuSubLineAmount << /WariChuSubLineScale .5 >> /WariChuWidowAmount 2 /WariChuOrphanAmount 2 /WariChuJustification 7 /TCYUpDownAdjustment 0 /TCYLeftRightAdjustment 0 /LeftAki -1.0 /RightAki -1.0 /JiDori 0 /NoBreak false /FillColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /StrokeColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /Blend << /StreamTag /SimpleBlender >> /FillFlag true /StrokeFlag false /FillFirst true /FillOverPrint false /StrokeOverPrint false /LineCap 0 /LineJoin 0 /LineWidth 1.0 /MiterLimit 4.0 /LineDashOffset 0.0 /LineDashArray [ ] /Type1EncodingNames [ ] >> >> >> ] /DisplayList [ << /Resource 0 >> ] >> /ParagraphSheetSet << /Resources [ << /Resource << /Name (Normal RGB) /Features << /Justification 0 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /DropCaps 1 /AutoLeading 1.2 /LeadingType 0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 0 /Zone 36.0 /HyphenateCapitalized true /HyphenationPreference .5 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /SingleWordJustification 6 /Hanging false /AutoTCY 0 /KeepTogether true /BurasagariType 0 /KinsokuOrder 0 /KurikaeshiMojiShori false /Kinsoku /nil /MojiKumiTable /nil /EveryLineComposer false /TabStops << >> /DefaultTabWidth 36.0 /DefaultStyle << >> >> >> >> ] /DisplayList [ << /Resource 0 >> ] >> /TextFrameSet << /Resources [ << /Resource << /Bezier << /Points [ 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ] >> /Data << /Type 0 /LineOrientation 0 /TextOnPathTRange [ -1.0 -1.0 ] /RowGutter 0.0 /ColumnGutter 0.0 /FirstBaselineAlignment << /Flag 1 /Min 0.0 >> /PathData << /Spacing -1 >> >> >> >> ] >> >> /DocumentObjects << /DocumentSettings << /HiddenGlyphFont << /AlternateGlyphFont 2 /WhitespaceCharacterMapping [ << /WhitespaceCharacter ( ) /AlternateCharacter (1) >> << /WhitespaceCharacter ( ) /AlternateCharacter (6) >> << /WhitespaceCharacter ( ) /AlternateCharacter (0) >> << /WhitespaceCharacter ( \)) /AlternateCharacter (5) >> << /WhitespaceCharacter () /AlternateCharacter (5) >> << /WhitespaceCharacter (0) /AlternateCharacter (1) >> << /WhitespaceCharacter () /AlternateCharacter (3) >> ] >> /NormalStyleSheet 0 /NormalParagraphSheet 0 /SuperscriptSize .583 /SuperscriptPosition .333 /SubscriptSize .583 /SubscriptPosition .333 /SmallCapSize .7 /UseSmartQuotes true /SmartQuoteSets [ << /Language 0 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 1 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 2 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 3 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 4 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 5 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 6 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote ( 9) /CloseSingleQuote ( :) >> << /Language 7 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 8 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 9 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 10 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 11 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 12 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 13 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 14 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 15 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 16 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 17 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 18 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 19 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 20 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 21 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 22 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 23 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 24 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 25 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote ( 9) /CloseSingleQuote ( :) >> << /Language 26 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 27 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 28 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 29 /OpenDoubleQuote (0) /CloseDoubleQuote (0) >> << /Language 30 /OpenDoubleQuote (0 ) /CloseDoubleQuote (0 ) >> << /Language 31 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 33 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 34 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 35 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 36 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 37 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 38 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 40 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 42 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 43 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> << /Language 44 /OpenDoubleQuote () /CloseDoubleQuote () /OpenSingleQuote ( 9) /CloseSingleQuote ( :) >> << /Language 45 /OpenDoubleQuote ( ) /CloseDoubleQuote ( ) /OpenSingleQuote ( ) /CloseSingleQuote ( ) >> ] >> /TextObjects [ << /Model << /Text (Thin ) /ParagraphRun << /RunArray [ << /RunData << /ParagraphSheet << /Name () /Features << /Justification 2 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /DropCaps 1 /AutoLeading 1.2 /LeadingType 0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 0 /Zone 36.0 /HyphenateCapitalized true /HyphenationPreference .5 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /SingleWordJustification 6 /Hanging false /AutoTCY 1 /KeepTogether true /BurasagariType 0 /KinsokuOrder 0 /KurikaeshiMojiShori false /Kinsoku /nil /MojiKumiTable /nil /EveryLineComposer false /TabStops << >> /DefaultTabWidth 36.0 /DefaultStyle << /Font 1 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /BaselineShift 0.0 /CharacterRotation 0.0 /FontCaps 0 /FontBaseline 0 /FontOTPosition 0 /StrikethroughPosition 0 /UnderlinePosition 0 /UnderlineOffset 0.0 /Ligatures true /DiscretionaryLigatures false /ContextualLigatures false /AlternateLigatures false /OldStyle false /Fractions false /Ordinals false /Swash false /Titling false /ConnectionForms false /StylisticAlternates false /Ornaments false /FigureStyle 0 /ProportionalMetrics false /Kana false /Italics false /Ruby false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /EnableWariChu false /WariChuLineCount 2 /WariChuLineGap 0 /WariChuSubLineAmount << /WariChuSubLineScale .5 >> /WariChuWidowAmount 2 /WariChuOrphanAmount 2 /WariChuJustification 7 /TCYUpDownAdjustment 0 /TCYLeftRightAdjustment 0 /LeftAki -1.0 /RightAki -1.0 /JiDori 0 /NoBreak false /FillColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /StrokeColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /FillFlag true /StrokeFlag false /FillFirst true /FillOverPrint false /StrokeOverPrint false /LineCap 0 /LineJoin 0 /LineWidth 1.0 /MiterLimit 4.0 /LineDashOffset 0.0 /LineDashArray [ ] /Type1EncodingNames [ ] >> >> /Parent 0 >> >> /Length 5 >> ] >> /StyleRun << /RunArray [ << /RunData << /StyleSheet << /Name () /Parent 0 /Features << /Font 0 /FontSize 60.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 72.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /BaselineShift 0.0 /CharacterRotation 0.0 /AutoKern 1 /FontCaps 0 /FontBaseline 0 /FontOTPosition 0 /StrikethroughPosition 0 /UnderlinePosition 0 /UnderlineOffset 0.0 /Ligatures true /DiscretionaryLigatures false /ContextualLigatures true /AlternateLigatures false /OldStyle false /Fractions false /Ordinals false /Swash false /Titling false /ConnectionForms true /StylisticAlternates false /Ornaments false /FigureStyle 0 /ProportionalMetrics false /Kana false /Italics false /Ruby false /BaselineDirection 1 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /JapaneseAlternateFeature 0 /EnableWariChu false /WariChuLineCount 2 /WariChuLineGap 0 /WariChuSubLineAmount << /WariChuSubLineScale .5 >> /WariChuWidowAmount 2 /WariChuOrphanAmount 2 /WariChuJustification 7 /TCYUpDownAdjustment 0 /TCYLeftRightAdjustment 0 /LeftAki -1.0 /RightAki -1.0 /JiDori 0 /NoBreak false /FillColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /StrokeColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /Blend << /StreamTag /SimpleBlender >> /FillFlag true /StrokeFlag false /FillFirst false /FillOverPrint false /StrokeOverPrint false /LineCap 0 /LineJoin 0 /LineWidth 1.0 /MiterLimit 4.0 /LineDashOffset 0.0 /LineDashArray [ ] /Type1EncodingNames [ ] >> >> >> /Length 5 >> ] >> /KernRun << /RunArray [ << /RunData << >> /Length 5 >> ] >> /AlternateGlyphRun << /RunArray [ << /RunData << >> /Length 5 >> ] >> /StorySheet << /AntiAlias 4 /UseFractionalGlyphWidths true >> >> /View << /Frames [ << /Resource 0 >> ] /RenderedData << /RunArray [ << /RunData << /LineCount 1 >> /Length 5 >> ] >> /Strikes [ << /StreamTag /PathSelectGroupCharacter /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 0 /Children [ << /StreamTag /FrameStrike /Frame 0 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 2 /Children [ << /StreamTag /RowColStrike /RowColIndex 0 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 1 /Children [ << /StreamTag /RowColStrike /RowColIndex 0 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 2 /Children [ << /StreamTag /LineStrike /Baseline 0.0 /Leading 0.0 /EMHeight 0.0 /DHeight 0.0 /SelectionAscent -54.35944 /SelectionDescent 16.3797 /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 -54.35944 0.0 16.3797 ] /ChildProcession 1 /Children [ << /StreamTag /Segment /Mapping << /CharacterCount 5 >> /FirstCharacterIndexInSegment 0 /Transform << /Origin [ -42.74918 0.0 ] >> /Bounds [ 0.0 0.0 0.0 0.0 ] /ChildProcession 1 /Children [ << /StreamTag /GlyphStrike /Transform << /Origin [ 0.0 0.0 ] >> /Bounds [ 0.0 -54.35944 85.49835 16.3797 ] /Glyphs [ 55 75 76 81 3 ] /GlyphAdjustments << /Data [ << >> ] /RunLengths [ 5 ] >> /VisualBounds [ -42.74918 -54.35944 42.74918 16.3797 ] /RenderedBounds [ -42.74918 -54.35944 42.74918 16.3797 ] /Invalidation [ -42.74918 -54.35944 71.54892 16.3797 ] /ShadowStylesRun << /Data [ << /Index 0 /Font 0 /Scale [ 1.0 1.0 ] /Orientation 0 /BaselineDirection 2 /BaselineShift 0.0 /KernType 1 >> ] /RunLengths [ 5 ] >> /EndsInCR true /SelectionAscent -54.35944 /SelectionDescent 16.3797 >> ] >> ] >> ] >> ] >> ] >> ] >> ] >> /OpticalAlignment false >> ] /OriginalNormalStyleFeatures << /Font 1 /FontSize 12.0 /FauxBold false /FauxItalic false /AutoLeading true /Leading 0.0 /HorizontalScale 1.0 /VerticalScale 1.0 /Tracking 0 /BaselineShift 0.0 /CharacterRotation 0.0 /AutoKern 1 /FontCaps 0 /FontBaseline 0 /FontOTPosition 0 /StrikethroughPosition 0 /UnderlinePosition 0 /UnderlineOffset 0.0 /Ligatures true /DiscretionaryLigatures false /ContextualLigatures false /AlternateLigatures false /OldStyle false /Fractions false /Ordinals false /Swash false /Titling false /ConnectionForms false /StylisticAlternates false /Ornaments false /FigureStyle 0 /ProportionalMetrics false /Kana false /Italics false /Ruby false /BaselineDirection 2 /Tsume 0.0 /StyleRunAlignment 2 /Language 0 /JapaneseAlternateFeature 0 /EnableWariChu false /WariChuLineCount 2 /WariChuLineGap 0 /WariChuSubLineAmount << /WariChuSubLineScale .5 >> /WariChuWidowAmount 2 /WariChuOrphanAmount 2 /WariChuJustification 7 /TCYUpDownAdjustment 0 /TCYLeftRightAdjustment 0 /LeftAki -1.0 /RightAki -1.0 /JiDori 0 /NoBreak false /FillColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /StrokeColor << /StreamTag /SimplePaint /Color << /Type 1 /Values [ 1.0 0.0 0.0 0.0 ] >> >> /Blend << /StreamTag /SimpleBlender >> /FillFlag true /StrokeFlag false /FillFirst true /FillOverPrint false /StrokeOverPrint false /LineCap 0 /LineJoin 0 /LineWidth 1.0 /MiterLimit 4.0 /LineDashOffset 0.0 /LineDashArray [ ] /Type1EncodingNames [ ] >> /OriginalNormalParagraphFeatures << /Justification 0 /FirstLineIndent 0.0 /StartIndent 0.0 /EndIndent 0.0 /SpaceBefore 0.0 /SpaceAfter 0.0 /DropCaps 1 /AutoLeading 1.2 /LeadingType 0 /AutoHyphenate true /HyphenatedWordSize 6 /PreHyphen 2 /PostHyphen 2 /ConsecutiveHyphens 0 /Zone 36.0 /HyphenateCapitalized true /HyphenationPreference .5 /WordSpacing [ .8 1.0 1.33 ] /LetterSpacing [ 0.0 0.0 0.0 ] /GlyphSpacing [ 1.0 1.0 1.0 ] /SingleWordJustification 6 /Hanging false /AutoTCY 0 /KeepTogether true /BurasagariType 0 /KinsokuOrder 0 /KurikaeshiMojiShori false /Kinsoku /nil /MojiKumiTable /nil /EveryLineComposer false /TabStops << >> /DefaultTabWidth 36.0 /DefaultStyle << >> >> >>8BIMFMsk 2"'$$$" $&$&,..**,8<<>CAAAACED\hnlfRJN.$_l}{d_[XVTVVTTPL@>>B@@B@B@<:22"'$$$" $&$&,..**,8<<>CAAAACED\hnlfRJN.$_l}{d_[XVTVVTTPL@>>B@@B@B@<:22"'$$$" $&$&,..**,8<<>CAAAACED\hnlfRJN.$_l}{d_[XVTVVTTPL@>>B@@B@B@<:22???????O/???_ߏ_??_???O??o?????o???????????????????????????Zϸ?Z6<?<6? !?! `?`* ? *l]?]l??_?W?Wu?/u`??o`؇Q???Q WZi?iZW Z?i?i?Z$-{?/{-${ ?/? {' ??o '`-?o-`N!`?`!Nr` ? `r' - ? - '{u?u{3'???'3H f9@%1 gen_into_index(@files), 'allclasses' => gen_into_index(@classes), "initial_page" => main_url, 'realtitle' => CGI.escapeHTML(@options.title), 'charset' => @options.charset } # the individual descriptions for files and classes gen_into(@files) gen_into(@classes) gen_main_index # this method is defined in the template file write_extra_pages if defined? write_extra_pages end def gen_into(list) hsh = @files_and_classes.dup list.each do |item| if item.document_self op_file = item.path hsh['root'] = item.path.split("/").map { ".." }[1..-1].join("/") item.instance_variable_set("@values", hsh) File.makedirs(File.dirname(op_file)) File.open(op_file, "w") { |file| item.write_on(file) } end end end def gen_into_index(list) res = [] list.each do |item| hsh = item.value_hash hsh['href'] = item.path hsh['name'] = item.index_name res << hsh end res end def gen_main_index template = TemplatePage.new(RDoc::Page::INDEX) File.open("index.html", "w") do |f| values = @files_and_classes.dup if @options.inline_source values['inline_source'] = true end template.write_html_on(f, values) end ['logo.gif'].each do |img| ipath = File.join(SITE_DIR, 'images', img) File.copy(ipath, img) end end end end module RDoc module Page ###################################################################### # # The following is used for the -1 option # FONTS = "verdana,arial,'Bitstream Vera Sans',helvetica,sans-serif" STYLE = File.read(SITE_DIR + '/style.css') CONTENTS_XML = %{ IF:description %description% ENDIF:description IF:requires Requires: START:requires IF:aref %name% ENDIF:aref IFNOT:aref %name% ENDIF:aref END:requires ENDIF:requires IF:attributes

Attributes

START:attributes END:attributes
%name%%rw%%a_desc%
ENDIF:attributes IF:includes

Includes

    START:includes IF:aref
  • %name%
  • ENDIF:aref IFNOT:aref
  • %name%
  • ENDIF:aref END:includes
ENDIF:includes START:sections IF:method_list

Methods

START:method_list IF:methods START:methods

%type% %category% IF:callseq %callseq% ENDIF:callseq IFNOT:callseq %name%%params% ENDIF:callseq

IF:m_desc %m_desc% ENDIF:m_desc IF:sourcecode
%sourcecode%
ENDIF:sourcecode END:methods ENDIF:methods END:method_list ENDIF:method_list END:sections } ############################################################################ BODY = %{ IF:title %realtitle% » %title% ENDIF:title IFNOT:title %realtitle% ENDIF:title

%title%

!INCLUDE!
} ############################################################################### FILE_PAGE = <<_FILE_PAGE_
%full_path% / %dtm_modified%
#{CONTENTS_XML}
_FILE_PAGE_ ################################################################### CLASS_PAGE = %{
IF:parent

%classmod% %full_name% < HREF:par_url:parent:

ENDIF:parent IFNOT:parent

%classmod% %full_name%

ENDIF:parent IF:infiles (in files START:infiles HREF:full_path_url:full_path: END:infiles ) ENDIF:infiles } + CONTENTS_XML + %{
} ################################################################### METHOD_LIST = %{ IF:includes
Included modules

START:includes HREF:aref:name: END:includes
ENDIF:includes IF:method_list START:method_list IF:methods
%type% %category% methods
START:methods
IF:callseq %callseq% ENDIF:callseq IFNOT:callseq %name%%params% ENDIF:callseq IF:codeurl src ENDIF:codeurl
IF:m_desc
%m_desc%
ENDIF:m_desc IF:aka
This method is also aliased as START:aka %name% END:aka
ENDIF:aka IF:sourcecode
%sourcecode%
ENDIF:sourcecode END:methods ENDIF:methods END:method_list ENDIF:method_list } ########################## Index ################################ FR_INDEX_BODY = %{ !INCLUDE! } FILE_INDEX = %{ START:entries %name%
END:entries } CLASS_INDEX = FILE_INDEX METHOD_INDEX = FILE_INDEX INDEX = %{ %realtitle% Click here to open the Thin docs. } end endthin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/site/style.css000066400000000000000000000042421475433514000222000ustar00rootroot00000000000000body { margin:0; padding:0; font: 12px Times, "Times New Roman"; text-align: center } img { border:0 } a { color: #666; text-decoration: none; border-bottom: solid 1px #ccc; } a:hover { color: #333; border-bottom: solid 1px #aaa; } acronym { border-bottom: dotted 1px #ccc; cursor: help; } pre { font: 10px Courier, "Courier New"; background: #111; color: #fff; padding: 3px; border: solid 1px #ccc; } hr { background: url(/thin/images/split.gif) no-repeat; height: 30px; width: 100px; border: 0; margin: 40px auto; } input, select, textarea { font: 9px Tahoma, Arial; } label { font-size: 11px; } h1, h2, h3, h4 { margin: 12px 0 8px 0; padding: 0; } h3 { font-size: 14px; } h4 { font-size: 13px; } ul#menu { padding: 4px; margin:0; background: #111; list-style: none; border-bottom: solid 1px #ccc; } ul#menu li { margin:0; display: inline; } ul#menu li a { color: #fff; text-decoration: none; padding: 6px; border: 0; } ul#menu li a:hover { text-decoration: underline; } #container { width: 300px; margin: 0 auto; } #header { padding: 30px 0 10px 0; } #header #logo { padding-bottom: 10px; } #header #tag_line { margin: 4px 0; font-size: 12px; letter-spacing: -1px; color: #333; } #content { line-height: 16px; text-align: left; } #content h1 { margin: 40px 0 10px 0; padding: 0; text-align: center; } #content h2 { margin: 40px 0 10px 0; padding: 0; text-align: center; } #content h3 { margin: 40px 0 10px 0; padding: 0; text-align: center; } #content ul { list-style-image: url(/thin/images/bullet.gif); margin: 0; padding: 0 20px; } #content li { padding: 1px 0; } #footer { margin: 60px 0 20px 0; font-size: 10px; color: #666; } #sidebar { position: absolute; right: 0; width: 400px; text-align: right; padding: 10px; } ul.list { list-style: none; } ul.list li { margin: 0; padding: 1px 0; } #content div.graph h3 { margin: 6px 0; } #content div.graph { text-align: center; } #content em.filename { display: block; text-align: right; margin-bottom: -8px; } .clear { clear: both; } /*RDoc*/ .dyn-source { display: none; } .path { text-align: center; font: 9px Tahoma, Arial; } .method-type { font: 9px Tahoma, Arial; color: #fff; background: #111; border: solid 1px #ccc; padding: 1px; } thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/site/thin.rb000066400000000000000000000245141475433514000216210ustar00rootroot00000000000000require 'rubygems' require 'atchoum' class Thin < Atchoum::Website ROOT = ENV['SITE_ROOT'] || '' def layout xhtml_html do head do title 'Thin - yet another web server' link :rel => 'stylesheet', :href => "#{ROOT}/style.css", :type => 'text/css' end body do ul.menu! do li { a 'about', :href => "#{ROOT}/" } li { a 'download', :href => "#{ROOT}/download/" } li { a 'usage', :href => "#{ROOT}/usage/" } li { a 'doc', :href => "#{ROOT}/doc/" } li { a 'code', :href => "http://github.com/macournoyer/thin/" } li { a 'bugs', :href => "http://github.com/macournoyer/thin/issues" } li { a 'users', :href => "#{ROOT}/users/" } li { a 'community', :href => "http://groups.google.com/group/thin-ruby/" } end div.container! do div.header! do a(:href => :index, :title => 'home') do img.logo! :src => "#{ROOT}/images/logo.gif", :alt => 'Thin' end h2.tag_line! "A fast and very simple Ruby web server" end div.content! do self << yield end div.footer! do hr text "© " a(:href => 'http://macournoyer.com') { 'Marc-André Cournoyer' } end end end end end def index_page h2 'What' p 'Thin is a Ruby web server that glues together 3 of the best Ruby libraries in web history:' ul do li do text "the #{a 'Mongrel parser', :href => 'http://www.zedshaw.com/essays/ragel_state_charts.html'}" text ", the root of #{a 'Mongrel', :href => 'http://rubyforge.org/projects/mongrel/'} speed and security" end li do a "Event Machine", :href => 'http://rubyeventmachine.com/' text ", a network I/O library with extremely high scalability, performance and stability" end li do a "Rack", :href => 'http://rack.github.com/' text ", a minimal interface between webservers and Ruby frameworks" end end p <<-EOS Which makes it, with all humility, the most secure, stable, fast and extensible Ruby web server bundled in an easy to use gem for your own pleasure. EOS h2 'Why' div.graph do h3 'Request / seconds comparison' img :alt => 'Chart', :title => 'Benchmarks', :src => chart_url([ ['WEBrick', [297.37, 296.65, 297.16]], ['Mongrel', [556.67, 622.90, 428.23]], ['Evented M.', [517.97, 657.89, 656.17]], ['Thin', [719.53, 782.11, 776.40]] ], :min => 200, :scale => 6.5, :size => '350x150', :width => 16, :colors => %w(000000 666666 cccccc), :legends => ['1 c req.', '10 c req.', '100 c req.']) em 'c req. = Concurrency Level (number of simultaneous requests)' end h2 'How' p "Add thin to your Gemfile" pre "gem 'thin'" p "Start Thin" pre "bundle exec thin start" end def download_page h2 'Install the Gem' p 'To install the latest stable gem' pre 'gem install thin' h2 'Install from source' p { "Clone the #{a 'Git repository', :href => 'http://github.com/macournoyer/thin/'}" } pre "git clone git://github.com/macournoyer/thin.git" p 'Hack the code, patch it, whatever, build the Gem and install' pre "cd thin\nrake install" end def usage_page h2 'Using with Rails' p "Add thin to your Gemfile" pre "gem 'thin'" p "Start Thin" pre "bundle exec thin start" h2 'Using with anything, ANYTHING!' p "But Thin can also load Rack config file so you can use it with any framework that " + "supports Rack. Even your own that is, like, soooo much better then Rails, rly!" em.filename 'fart.ru' pre <<-EOS.gsub(/^\s{6}/, '') app = proc do |env| [ 200, # Status code { # Response headers 'content-type' => 'text/html', 'content-length' => '2', }, ['hi'] # Response body ] end # You can install Rack middlewares # to do some crazy stuff like logging, # filtering, auth or build your own. use Rack::CommonLogger run app EOS pre 'thin start -R fart.ru' p { "See #{a 'Rack doc', :href => 'http://rack.rubyforge.org/doc/'} for more." } hr h2 'Deploying' p 'Deploying a cluster of Thins is super easy. Just specify the number of servers you ' + 'want to launch.' pre 'thin start --servers 3' p 'You can also install Thin as a runlevel script (under /etc/init.d/thin) that will start all your servers after boot.' pre 'sudo thin install' p 'and setup a config file for each app you want to start:' pre 'thin config -C /etc/thin/myapp.yml -c /var/...' p { "Run #{code 'thin -h'} to get all options." } h2 'Behind Nginx' p { "Check out this #{a 'sample Nginx config', :href => 'http://brainspl.at/nginx.conf.txt'} " + "file to proxy requests to a Thin backend." } p 'then start your Thin cluster like this:' pre 'thin start -s3 -p 5000' p 'You can also setup a Thin config file and use it to control your cluster:' pre "thin config -C myapp.yml -s3 -p 5000\nthin start -C myapp.yml" p 'To connect to Nginx using UNIX domain sockets edit the upstream block in your nginx config file:' em.filename 'nginx.conf' pre <<-EOS.gsub(/^\s{6}/, '') upstream backend { server unix:/tmp/thin.0.sock; server unix:/tmp/thin.1.sock; server unix:/tmp/thin.2.sock; } EOS p 'and start your cluster like this:' pre 'thin start -s3 --socket /tmp/thin.sock' end def users_page h2 'Users' p "Who's using Thin ?" ul do li { a 'Heroku', :href => 'http://heroku.com/' } li { a 'HostingRails', :href => 'http://www.hostingrails.com/mongrel_and_thin_hosting' } li { a "Audiofarm.org", :href => 'http://audiofarm.org'} li { a "What The Movie", :href => 'http://whatthemovie.com/'} li { a 'Mobile Defense', :href => 'https://www.mobiledefense.com' } li { a 'RefactorMyCode.com', :href => 'http://refactormycode.com/' } li { a "Standout Jobs", :href => 'http://standoutjobs.com/'} li { a "Gitorious", :href => 'http://gitorious.org/' } li { a "Kevin Williams Blog", :href => 'http://www.almostserio.us/articles/tag/thin' } li { a "Dinooz", :href => 'http://www.nicomoayudarte.com/' } li { a "Mobile Dyne Systems", :href => 'http://www.mobiledyne.com/' } li { a "feelfree.homelinux.com", :href => 'http://feelfree.homelinux.com' } li { a "to2posts.com", :href => 'http://to2blogs.com/' } li { a "James on Software", :href => 'http://jamesgolick.com/' } li { a "FlashDen", :href => 'http://flashden.net/' } li { a "Calico Web Development", :href => 'http://www.calicowebdev.com/' } li { a "Cornerstone Web", :href => 'http://www.cornerstoneweb.org/' } li { a "Osmos", :href => 'http://www.getosmos.com/' } li { a "Lipomics", :href => 'http://www.lipomics.com/' } li { a "Joao Pedrosa's Blog", :href => 'http://www.deze9.com/jp/blog/post?p=enabling-thin-support-for-this-site-replacing' } li { a "RaPlanet", :href => 'http://planet.zhekov.net/' } li { a "Ninja Hideout blog", :href => 'http://blog.ninjahideout.com/' } li { a "Hoodows Blog", :href => 'http://blog.hoodow.de/articles/2008/02/09/thin' } li { a "moonitor.org", :href => 'http://moonitor.org' } li { a "Daniel Fischer's Blog", :href => 'http://www.danielfischer.com/' } li { a "RoundHaus", :href => 'http://www.roundhaus.com/' } li { a "Socks and Sandals", :href => 'http://blog.cbcg.net/' } li { a "indiagoes", :href => 'http://www.indiagoes.com/' } li { a "ajaxwhois", :href => 'http://ajaxwhois.com/' } li { a "Sproglogs", :href => 'http://sproglogs.com/' } li { a "Look to the Stars", :href => 'http://www.looktothestars.org/' } li { a "kluster", :href => 'http://kluster.com'} li { a "SocialSpark", :href => 'http://socialspark.com'} li { a "Tanga.com", :href => 'http://tanga.com'} li { a "Opening Times", :href => 'http://opening-times.co.uk/'} li { a "p0pulist", :href => 'http://p0pulist.com/'} li { a "pickhost.eu", :href => 'http://pickhost.eu'} li { a "HowFlow", :href => 'http://howflow.com/'} li { a "boo-widgets", :href => 'http://boo-box.com/site/en/widget/get'} li { a "BigCity.cz", :href => 'http://www.bigcity.cz/'} li { a "yabadaba.ru", :href => 'http://yabadaba.ru/'} li { a "Freebootr.com", :href => 'http://freebootr.com/'} li { a "Hacknight", :href => 'http://hacknight.gangplankhq.com/'} li { a "Sizzix", :href => 'http://www.sizzix.com/'} li { a "Ellison Retailers", :href => 'http://www.ellisonretailers.com/'} li { a "ShareMeme", :href => 'http://sharememe.com/'} li { a "ylastic", :href => 'http://ylastic.com/'} li { a "hello2morrow", :href => 'http://www.hello2morrow.com/'} li { a "TimmyOnTime", :href => 'http://www.timmyontime.com/'} li { a "Generous", :href => 'http://generous.org.uk/'} li { a "Catapult Magazine", :href => 'http://www.catapultmagazine.com/'} li { a "Good-Tutorials", :href => 'http://www.good-tutorials.com/'} li { a "audioreload", :href => 'http://www.audioreload.com/'} end p { "If you'd like to have your site listed here, #{a 'drop me an email', :href => 'mailto:macournoyer@gmail.com'}" } end private def chart_url(data, options={}) size = options[:size] || '250x150' width = options[:width] || 50 min = options[:min] || 0 scale = options[:scale] || 1.0 colors = options[:colors] || '000000' legends = options[:legends] data_matrix = data.collect { |d| Array(d[1]) } chart_matrix = data_matrix[0].zip(*data_matrix[1..-1]) # transpose the matrix chart_data = chart_matrix.collect { |c| c.collect { |d| (d - min) / scale }.join(',') }.join('|') labels = data.collect{|d|d[0]}.join('|') "http://chart.apis.google.com/chart?cht=bvg&chd=t:#{chart_data}&chbh=#{width}&chs=#{size}&chl=#{labels}&chco=#{colors.join(',')}&chdl=#{legends.join('|')}" end end thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/000077500000000000000000000000001475433514000203125ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/backends/000077500000000000000000000000001475433514000220645ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/backends/swiftiply_client_spec.rb000066400000000000000000000033531475433514000270170ustar00rootroot00000000000000require 'spec_helper' describe Backends::SwiftiplyClient do before do @backend = Backends::SwiftiplyClient.new('0.0.0.0', 3333) @backend.server = double('server').as_null_object end it "should connect" do EventMachine.run do @backend.connect EventMachine.stop end end it "should disconnect" do EventMachine.run do @backend.connect @backend.disconnect EventMachine.stop end end end describe SwiftiplyConnection do before do @connection = SwiftiplyConnection.new(nil) @connection.backend = Backends::SwiftiplyClient.new('0.0.0.0', 3333) @connection.backend.server = double('server').as_null_object end it do expect(@connection).to be_persistent end it "should send handshake on connection_completed" do expect(@connection).to receive(:send_data).with('swiftclient000000000d0500') @connection.connection_completed end it "should reconnect on unbind" do allow(@connection.backend).to receive(:running?) { true } allow(@connection).to receive(:rand) { 0 } # Make sure we don't wait expect(@connection).to receive(:reconnect).with('0.0.0.0', 3333) EventMachine.run do @connection.unbind EventMachine.add_timer(0) { EventMachine.stop } end end it "should not reconnect when not running" do allow(@connection.backend).to receive(:running?) { false } expect(EventMachine).not_to receive(:add_timer) @connection.unbind end it "should have a host_ip" do expect(@connection.send(:host_ip)).to eq([0, 0, 0, 0]) end it "should generate swiftiply_handshake based on key" do expect(@connection.send(:swiftiply_handshake, 'key')).to eq('swiftclient000000000d0503key') end end thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/backends/tcp_server_spec.rb000066400000000000000000000011611475433514000255760ustar00rootroot00000000000000require 'spec_helper' describe Backends::TcpServer do before do @backend = Backends::TcpServer.new('0.0.0.0', 3333) end it "should not use epoll" do @backend.no_epoll = true expect(EventMachine).not_to receive(:epoll) @backend.config end it "should use epoll" do expect(EventMachine).to receive(:epoll) @backend.config end it "should connect" do EventMachine.run do @backend.connect EventMachine.stop end end it "should disconnect" do EventMachine.run do @backend.connect @backend.disconnect EventMachine.stop end end end thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/backends/unix_server_spec.rb000066400000000000000000000013441475433514000257760ustar00rootroot00000000000000require 'spec_helper' describe Backends::UnixServer do before do @backend = Backends::UnixServer.new('/tmp/thin-test.sock') end it "should connect" do EventMachine.run do @backend.connect EventMachine.stop end end it "should disconnect" do EventMachine.run do @backend.connect @backend.disconnect EventMachine.stop end end it "should remove socket file on close" do @backend.close expect(File.exist?('/tmp/thin-test.sock')).to be_falsey end end describe UnixConnection do before do @connection = UnixConnection.new(nil) end it "should return 127.0.0.1 as remote_address" do expect(@connection.remote_address).to eq('127.0.0.1') end end thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/command_spec.rb000066400000000000000000000013631475433514000232720ustar00rootroot00000000000000require 'spec_helper' describe Command do before do Command.script = 'thin' @command = Command.new(:start, :port => 3000, :daemonize => true, :log => 'hi.log', :require => %w(rubygems thin), :no_epoll => true) end it 'should shellify command' do out = @command.shellify expect(out).to include('--port=3000', '--daemonize', '--log="hi.log"', 'thin start --') expect(out).not_to include('--pid') end it 'should shellify Array argument to multiple parameters' do out = @command.shellify expect(out).to include('--require="rubygems"', '--require="thin"') end it 'should convert _ to - in option name' do out = @command.shellify expect(out).to include('--no-epoll') end endthin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/configs/000077500000000000000000000000001475433514000217425ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/configs/cluster.yml000066400000000000000000000003541475433514000241500ustar00rootroot00000000000000--- environment: production timeout: 60 port: 5000 chdir: spec/rails_app log: ../../log/thin.log # relative to the chdir option pid: ../../tmp/pids/thin.pid # relative to the chdir option servers: 3 address: 127.0.0.1 thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/configs/single.yml000066400000000000000000000002261475433514000237460ustar00rootroot00000000000000--- pid: tmp/pids/thin.pid log: log/thin.log timeout: 60 port: 6000 chdir: spec/rails_app environment: production daemonize: true address: 127.0.0.1 thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/configs/with_erb.yml000066400000000000000000000001121475433514000242620ustar00rootroot00000000000000--- timeout: <%= 30 %> port: <%= 4000 %> environment: <%= "production" %> thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/connection_spec.rb000066400000000000000000000121661475433514000240160ustar00rootroot00000000000000require 'spec_helper' describe Connection do before do allow(EventMachine).to receive(:send_data) @connection = Connection.new(double('EM').as_null_object) @connection.post_init @connection.backend = double("backend", :ssl? => false) @connection.app = proc do |env| [200, {}, ['body']] end end it "should parse on receive_data" do expect(@connection.request).to receive(:parse).with('GET') @connection.receive_data('GET') end it "should make a valid response on bad request" do allow(@connection.request).to receive(:parse).and_raise(InvalidRequest) expect(@connection).to receive(:post_process).with(Response::BAD_REQUEST) @connection.receive_data('') end it "should close connection on InvalidRequest error in receive_data" do allow(@connection.request).to receive(:parse).and_raise(InvalidRequest) allow(@connection.response).to receive(:persistent?) { false } @connection.can_persist! expect(@connection).to receive(:terminate_request) @connection.receive_data('') end it "should process when parsing complete" do expect(@connection.request).to receive(:parse).and_return(true) expect(@connection).to receive(:process) @connection.receive_data('GET') end it "should process at most once when request is larger than expected" do expect(@connection).to receive(:process).at_most(1) @connection.receive_data("POST / HTTP/1.1\r\nHost: localhost:3000\r\nContent-Length: 300\r\n\r\n") 10.times { @connection.receive_data('X' * 1_000) } end it "should process" do @connection.process end it "should rescue error in process" do expect(@connection.app).to receive(:call).and_raise(StandardError) allow(@connection.response).to receive(:persistent?) { false } expect(@connection).to receive(:terminate_request) @connection.process end it "should make response on error" do expect(@connection.app).to receive(:call).and_raise(StandardError) expect(@connection).to receive(:post_process).with(Response::ERROR) @connection.process end it "should not close persistent connection on error" do expect(@connection.app).to receive(:call).and_raise(StandardError) allow(@connection.response).to receive(:persistent?) { true } @connection.can_persist! expect(@connection).to receive(:teminate_request).never @connection.process end it "should rescue Timeout error in process" do expect(@connection.app).to receive(:call).and_raise(Timeout::Error.new("timeout error not rescued")) @connection.process end it "should not return HTTP_X_FORWARDED_FOR as remote_address" do @connection.request.env['HTTP_X_FORWARDED_FOR'] = '1.2.3.4' allow(@connection).to receive(:socket_address) { "127.0.0.1" } expect(@connection.remote_address).to eq("127.0.0.1") end it "should return nil on error retrieving remote_address" do allow(@connection).to receive(:get_peername).and_raise(RuntimeError) expect(@connection.remote_address).to be_nil end it "should return nil on nil get_peername" do allow(@connection).to receive(:get_peername) { nil } expect(@connection.remote_address).to be_nil end it "should return nil on empty get_peername" do allow(@connection).to receive(:get_peername) { '' } expect(@connection.remote_address).to be_nil end it "should return remote_address" do allow(@connection).to receive(:get_peername) do Socket.pack_sockaddr_in(3000, '127.0.0.1') end expect(@connection.remote_address).to eq('127.0.0.1') end it "should not be persistent" do expect(@connection).not_to be_persistent end it "should be persistent when response is and allowed" do allow(@connection.response).to receive(:persistent?) { true } @connection.can_persist! expect(@connection).to be_persistent end it "should not be persistent when response is but not allowed" do @connection.response.persistent! expect(@connection).not_to be_persistent end it "should return empty body on HEAD request" do expect(@connection.request).to receive(:head?).and_return(true) expect(@connection).to receive(:send_data).once # Only once for the headers @connection.process end it "should set request env as rack.multithread" do expect(EventMachine).to receive(:defer) @connection.threaded = true @connection.process expect(@connection.request.env["rack.multithread"]).to eq(true) end it "should set as threaded when app.deferred? is true" do expect(@connection.app).to receive(:deferred?).and_return(true) expect(@connection).to be_threaded end it "should not set as threaded when app.deferred? is false" do expect(@connection.app).to receive(:deferred?).and_return(false) expect(@connection).not_to be_threaded end it "should not set as threaded when app do not respond to deferred?" do expect(@connection).not_to be_threaded end it "should have correct SERVER_PORT when using ssl" do @connection.backend = double("backend", :ssl? => true, :port => 443) @connection.process expect(@connection.request.env["SERVER_PORT"]).to eq("443") end end thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/controllers/000077500000000000000000000000001475433514000226605ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/controllers/cluster_spec.rb000066400000000000000000000212351475433514000257030ustar00rootroot00000000000000require 'spec_helper' include Controllers describe Cluster, "with host and port" do before do @cluster = Cluster.new(:chdir => '/rails_app', :address => '0.0.0.0', :port => 3000, :servers => 3, :timeout => 10, :log => 'thin.log', :pid => 'thin.pid' ) end it 'should include port number in file names' do expect(@cluster.send(:include_server_number, 'thin.log', 3000)).to eq('thin.3000.log') expect(@cluster.send(:include_server_number, 'thin.pid', 3000)).to eq('thin.3000.pid') end it 'should call each server' do calls = [] @cluster.send(:with_each_server) do |port| calls << port end expect(calls).to eq([3000, 3001, 3002]) end it 'should start on each port' do expect(Command).to receive(:run).with(:start, options_for_port(3000)) expect(Command).to receive(:run).with(:start, options_for_port(3001)) expect(Command).to receive(:run).with(:start, options_for_port(3002)) @cluster.start end it 'should stop on each port' do expect(Command).to receive(:run).with(:stop, options_for_port(3000)) expect(Command).to receive(:run).with(:stop, options_for_port(3001)) expect(Command).to receive(:run).with(:stop, options_for_port(3002)) @cluster.stop end private def options_for_port(port) { :daemonize => true, :log => "thin.#{port}.log", :timeout => 10, :address => "0.0.0.0", :port => port, :pid => "thin.#{port}.pid", :chdir => "/rails_app" } end end describe Cluster, "with UNIX socket" do before do @cluster = Cluster.new(:chdir => '/rails_app', :socket => '/tmp/thin.sock', :address => '0.0.0.0', :port => 3000, :servers => 3, :timeout => 10, :log => 'thin.log', :pid => 'thin.pid' ) end it 'should include socket number in file names' do expect(@cluster.send(:include_server_number, 'thin.sock', 0)).to eq('thin.0.sock') expect(@cluster.send(:include_server_number, 'thin', 0)).to eq('thin.0') end it "should exclude :address and :port options" do expect(@cluster.options).not_to have_key(:address) expect(@cluster.options).not_to have_key(:port) end it 'should call each server' do calls = [] @cluster.send(:with_each_server) do |n| calls << n end expect(calls).to eq([0, 1, 2]) end it 'should start each server' do expect(Command).to receive(:run).with(:start, options_for_socket(0)) expect(Command).to receive(:run).with(:start, options_for_socket(1)) expect(Command).to receive(:run).with(:start, options_for_socket(2)) @cluster.start end it 'should stop each server' do expect(Command).to receive(:run).with(:stop, options_for_socket(0)) expect(Command).to receive(:run).with(:stop, options_for_socket(1)) expect(Command).to receive(:run).with(:stop, options_for_socket(2)) @cluster.stop end private def options_for_socket(number) { :daemonize => true, :log => "thin.#{number}.log", :timeout => 10, :socket => "/tmp/thin.#{number}.sock", :pid => "thin.#{number}.pid", :chdir => "/rails_app" } end end describe Cluster, "controlling only one server" do before do @cluster = Cluster.new(:chdir => '/rails_app', :address => '0.0.0.0', :port => 3000, :servers => 3, :timeout => 10, :log => 'thin.log', :pid => 'thin.pid', :only => 3001 ) end it 'should call only specified server' do calls = [] @cluster.send(:with_each_server) do |n| calls << n end expect(calls).to eq([3001]) end it "should start only specified server" do expect(Command).to receive(:run).with(:start, options_for_port(3001)) @cluster.start end private def options_for_port(port) { :daemonize => true, :log => "thin.#{port}.log", :timeout => 10, :address => "0.0.0.0", :port => port, :pid => "thin.#{port}.pid", :chdir => "/rails_app" } end end describe Cluster, "controlling only one server with UNIX socket" do before do @cluster = Cluster.new(:chdir => '/rails_app', :socket => '/tmp/thin.sock', :address => '0.0.0.0', :port => 3000, :servers => 3, :timeout => 10, :log => 'thin.log', :pid => 'thin.pid', :only => 1 ) end it 'should call only specified server' do calls = [] @cluster.send(:with_each_server) do |n| calls << n end expect(calls).to eq([1]) end end describe Cluster, "controlling only one server, by sequence number" do before do @cluster = Cluster.new(:chdir => '/rails_app', :address => '0.0.0.0', :port => 3000, :servers => 3, :timeout => 10, :log => 'thin.log', :pid => 'thin.pid', :only => 1 ) end it 'should call only specified server' do calls = [] @cluster.send(:with_each_server) do |n| calls << n end expect(calls).to eq([3001]) end it "should start only specified server" do expect(Command).to receive(:run).with(:start, options_for_port(3001)) @cluster.start end private def options_for_port(port) { :daemonize => true, :log => "thin.#{port}.log", :timeout => 10, :address => "0.0.0.0", :port => port, :pid => "thin.#{port}.pid", :chdir => "/rails_app" } end end describe Cluster, "with Swiftiply" do before do @cluster = Cluster.new(:chdir => '/rails_app', :address => '0.0.0.0', :port => 3000, :servers => 3, :timeout => 10, :log => 'thin.log', :pid => 'thin.pid', :swiftiply => true ) end it 'should call each server' do calls = [] @cluster.send(:with_each_server) do |n| calls << n end expect(calls).to eq([0, 1, 2]) end it 'should start each server' do expect(Command).to receive(:run).with(:start, options_for_swiftiply(0)) expect(Command).to receive(:run).with(:start, options_for_swiftiply(1)) expect(Command).to receive(:run).with(:start, options_for_swiftiply(2)) @cluster.start end it 'should stop each server' do expect(Command).to receive(:run).with(:stop, options_for_swiftiply(0)) expect(Command).to receive(:run).with(:stop, options_for_swiftiply(1)) expect(Command).to receive(:run).with(:stop, options_for_swiftiply(2)) @cluster.stop end private def options_for_swiftiply(number) { :address => '0.0.0.0', :port => 3000, :daemonize => true, :log => "thin.#{number}.log", :timeout => 10, :pid => "thin.#{number}.pid", :chdir => "/rails_app", :swiftiply => true } end end describe Cluster, "rolling restart" do before do @cluster = Cluster.new(:chdir => '/rails_app', :address => '0.0.0.0', :port => 3000, :servers => 2, :timeout => 10, :log => 'thin.log', :pid => 'thin.pid', :onebyone => true, :wait => 30 ) end it "should restart servers one by one" do expect(Command).to receive(:run).with(:stop, options_for_port(3000)) expect(Command).to receive(:run).with(:start, options_for_port(3000)) expect(@cluster).to receive(:wait_until_server_started).with(3000) expect(Command).to receive(:run).with(:stop, options_for_port(3001)) expect(Command).to receive(:run).with(:start, options_for_port(3001)) expect(@cluster).to receive(:wait_until_server_started).with(3001) @cluster.restart end private def options_for_port(port) { :daemonize => true, :log => "thin.#{port}.log", :timeout => 10, :address => "0.0.0.0", :port => port, :pid => "thin.#{port}.pid", :chdir => "/rails_app" } end endthin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/controllers/controller_spec.rb000066400000000000000000000073761475433514000264170ustar00rootroot00000000000000require 'spec_helper' require 'ostruct' include Controllers describe Controller, 'start' do before do @controller = Controller.new(:address => '0.0.0.0', :port => 3000, :pid => 'thin.pid', :log => 'thin.log', :timeout => 60, :max_conns => 2000, :max_persistent_conns => 1000, :adapter => 'rails') @server = OpenStruct.new @adapter = OpenStruct.new expect(Server).to receive(:new).with('0.0.0.0', 3000, @controller.options).and_return(@server) expect(@server).to receive(:config) allow(Rack::Adapter::Rails).to receive(:new) { @adapter } end it "should configure server" do @controller.start expect(@server.app).to eq(@adapter) expect(@server.pid_file).to eq('thin.pid') expect(@server.log_file).to eq('thin.log') expect(@server.maximum_connections).to eq(2000) expect(@server.maximum_persistent_connections).to eq(1000) end it "should start as daemon" do @controller.options[:daemonize] = true @controller.options[:user] = true @controller.options[:group] = true expect(@server).to receive(:daemonize) expect(@server).to receive(:change_privilege) @controller.start end it "should configure Rails adapter" do expect(Rack::Adapter::Rails).to receive(:new).with(@controller.options.merge(:root => nil)) @controller.start end it "should mount app under :prefix" do @controller.options[:prefix] = '/app' @controller.start expect(@server.app.class).to eq(Rack::URLMap) end it "should mount Stats adapter under :stats" do @controller.options[:stats] = '/stats' @controller.start expect(@server.app.class).to eq(Stats::Adapter) end it "should load app from Rack config" do @controller.options[:rackup] = File.dirname(__FILE__) + '/../../example/config.ru' @controller.start expect(@server.app.class).to eq(Proc) end it "should load app from ruby file" do @controller.options[:rackup] = File.dirname(__FILE__) + '/../../example/myapp.rb' @controller.start expect(@server.app).to eq(Myapp) end it "should throwup if rackup is not a .ru or .rb file" do expect do @controller.options[:rackup] = File.dirname(__FILE__) + '/../../example/myapp.foo' @controller.start end.to raise_error(RuntimeError, /please/) end it "should set server as threaded" do @controller.options[:threaded] = true @controller.start expect(@server.threaded).to be_truthy end it "should set RACK_ENV" do @controller.options[:rackup] = File.dirname(__FILE__) + '/../../example/config.ru' @controller.options[:environment] = "lolcat" @controller.start expect(ENV['RACK_ENV']).to eq("lolcat") end end describe Controller do before do @controller = Controller.new(:pid => 'thin.pid', :timeout => 10) allow(@controller).to receive(:wait_for_file) end it "should stop" do expect(Server).to receive(:kill).with('thin.pid', 10) @controller.stop end it "should restart" do expect(Server).to receive(:restart).with('thin.pid') @controller.restart end it "should write configuration file" do silence_stream(STDOUT) do Controller.new(:config => 'test.yml', :port => 5000, :address => '127.0.0.1').config end expect(File.read('test.yml')).to include('port: 5000', 'address: 127.0.0.1') expect(File.read('test.yml')).not_to include('config: ') File.delete('test.yml') end end thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/controllers/service_spec.rb000066400000000000000000000033421475433514000256610ustar00rootroot00000000000000require 'spec_helper' include Controllers describe Service do before(:all) do silence_warnings do Service::INITD_PATH = 'tmp/sandbox' + Service::INITD_PATH Service::DEFAULT_CONFIG_PATH = 'tmp/sandbox' + Service::DEFAULT_CONFIG_PATH end end before do allow(Thin).to receive(:linux?) { true } FileUtils.mkdir_p 'tmp/sandbox' @service = Service.new(:all => 'spec/configs') end it "should call command for each config file" do expect(Command).to receive(:run).with(:start, :config => 'spec/configs/cluster.yml', :daemonize => true) expect(Command).to receive(:run).with(:start, :config => 'spec/configs/single.yml', :daemonize => true) expect(Command).to receive(:run).with(:start, :config => 'spec/configs/with_erb.yml', :daemonize => true) @service.start end it "should create /etc/init.d/thin file when calling install" do @service.install expect(File.exist?(Service::INITD_PATH)).to be_truthy script_name = File.directory?('/etc/rc.d') ? '/etc/rc.d/thin' : '/etc/init.d/thin' expect(File.read(Service::INITD_PATH)).to include('CONFIG_PATH=tmp/sandbox/etc/thin', 'SCRIPT_NAME=tmp/sandbox' + script_name, 'DAEMON=' + Command.script) end it "should create /etc/thin dir when calling install" do @service.install expect(File.directory?(Service::DEFAULT_CONFIG_PATH)).to be_truthy end it "should include specified path in /etc/init.d/thin script" do @service.install('tmp/sandbox/usr/thin') expect(File.read(Service::INITD_PATH)).to include('CONFIG_PATH=tmp/sandbox/usr/thin') end after do FileUtils.rm_rf 'tmp/sandbox' end end thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/daemonizing_spec.rb000066400000000000000000000077341475433514000241700ustar00rootroot00000000000000require 'spec_helper' require 'timeout' require "tmpdir" class TestServer include Logging include Daemonizable def stop end def name 'Thin test server' end end describe 'Daemonizing' do let(:log_file) {File.join(@root, "test_server.log")} let(:pid_file) {File.join(@root, "test.pid")} around do |example| Dir.mktmpdir do |root| @root = root # Ensure the log file exists with the correct permissions: File.open(log_file, "w+") {} example.run @root = nil end end subject(:server) do raise "No root directory" unless @root TestServer.new.tap do |server| server.log_file = log_file server.pid_file = pid_file end end it 'should have a pid file' do expect(subject).to respond_to(:pid_file) expect(subject).to respond_to(:pid_file=) end it 'should create a pid file' do fork do subject.daemonize sleep end wait_for_server_to_start subject.kill end it 'should redirect stdio to a log file' do pid = fork do subject.daemonize puts "simple puts" STDERR.puts "STDERR.puts" STDOUT.puts "STDOUT.puts" sleep end wait_for_server_to_start log = File.read(log_file) expect(log).to include('simple puts', 'STDERR.puts', 'STDOUT.puts') server.kill end it 'should change privilege' do pid = fork do subject.daemonize subject.change_privilege('root', 'admin') end _, status = Process.wait2(pid) expect(status).to be_a_success end it 'should kill process in pid file' do expect(File.exist?(subject.pid_file)).to be_falsey pid = fork do subject.daemonize sleep end wait_for_server_to_start expect(File.exist?(subject.pid_file)).to be_truthy silence_stream STDOUT do subject.kill(1) end Process.wait(pid) expect(File.exist?(subject.pid_file)).to be_falsey end it 'should force kill process in pid file' do fork do subject.daemonize sleep end wait_for_server_to_start subject.kill(0) expect(File.exist?(subject.pid_file)).to be_falsey end it 'should send kill signal if timeout' do fork do subject.daemonize sleep end wait_for_server_to_start pid = subject.pid subject.kill(10) expect(File.exist?(subject.pid_file)).to be_falsey expect(Process.running?(pid)).to be_falsey end it "should restart" do fork do subject.on_restart {} subject.daemonize sleep 5 end wait_for_server_to_start silence_stream STDOUT do TestServer.restart(subject.pid_file) end expect { sleep 0.1 while File.exist?(subject.pid_file) }.to take_less_then(20) end it "should ignore if no restart block specified" do subject.restart end it "should not restart when not running" do silence_stream STDOUT do subject.restart end end it "should exit and raise if pid file already exist" do fork do subject.daemonize sleep 5 end wait_for_server_to_start expect { subject.daemonize }.to raise_error(PidFileExist) expect(File.exist?(subject.pid_file)).to be_truthy end it "should raise if no pid file" do expect do TestServer.kill("donotexist", 0) end.to raise_error(PidFileNotFound) end it "should should delete pid file if stale" do # Create a file w/ a PID that does not exist File.open(subject.pid_file, 'w') { |f| f << 999999999 } subject.send(:remove_stale_pid_file) expect(File.exist?(subject.pid_file)).to be_falsey end private def wait_for_server_to_start count = 1 while true break if File.exist?(subject.pid_file) sleep(count * 0.1) if count > 10 $stderr.puts "Dumping log file #{subject.log_file}:" File.foreach(subject.log_file) do |line| $stderr.puts line end raise "Server did not start" end count += 1 end end end thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/headers_spec.rb000066400000000000000000000032071475433514000232660ustar00rootroot00000000000000require 'spec_helper' describe Headers do before do @headers = Headers.new end it 'should allow duplicate on some fields' do @headers['Set-Cookie'] = 'twice' @headers['Set-Cookie'] = 'is cooler the once' expect(@headers.to_s).to eq("Set-Cookie: twice\r\nSet-Cookie: is cooler the once\r\n") end it 'should overwrite value on non duplicate fields' do @headers['Host'] = 'this is unique' @headers['Host'] = 'so is this' expect(@headers.to_s).to eq("Host: this is unique\r\n") end it 'should output to string' do @headers['Host'] = 'localhost:3000' @headers['Set-Cookie'] = 'twice' @headers['Set-Cookie'] = 'is cooler the once' expect(@headers.to_s).to eq("Host: localhost:3000\r\nSet-Cookie: twice\r\nSet-Cookie: is cooler the once\r\n") end it 'should ignore nil values' do @headers['Something'] = nil expect(@headers.to_s).not_to include('Something: ') end it 'should format Time values correctly' do time = Time.now @headers['Modified-At'] = time expect(@headers.to_s).to include("Modified-At: #{time.httpdate}") end it 'should format Integer values correctly' do @headers['X-Number'] = 32 expect(@headers.to_s).to include("X-Number: 32") end it 'should not allow CRLF' do expect { @headers['Bad'] = "a\r\nSet-Cookie: injected=value" }.to raise_error(InvalidHeader) end it 'should not allow CR' do expect { @headers['Bad'] = "a\rSet-Cookie: injected=value" }.to raise_error(InvalidHeader) end it 'should not allow LF' do expect { @headers['Bad'] = "a\nSet-Cookie: injected=value" }.to raise_error(InvalidHeader) end endthin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/logging_spec.rb000066400000000000000000000100661475433514000233020ustar00rootroot00000000000000require 'spec_helper' ## # Dummy class, so that we can mix in the Logging module and test it. # class TestLogging include Logging end describe Logging do subject {TestLogging.new} after do Logging.silent = true Logging.debug = false Logging.trace = false end describe "when setting a custom logger" do it "should not accept a logger object that is not sane" do expect { Logging.logger = "" }.to raise_error(ArgumentError) end it "should accept a legit custom logger object" do expect { Logging.logger = Logger.new(STDOUT) }.to_not raise_error end end describe "logging routines (with a custom logger)" do before :each do @readpipe, @writepipe = IO.pipe @custom_logger = Logger.new(@writepipe) Logging.logger = @custom_logger Logging.level = Logger::INFO end after :each do [@readpipe, @writepipe].each do |pipe| pipe.close if pipe end end # # it "at log level DEBUG should output logs at debug level" do Logging.debug = true subject.log_debug("hi") str = nil expect { str = @readpipe.read_nonblock(512) }.to_not raise_error expect(str).not_to be_nil end # # it "at log level NOT DEBUG should NOT output logs at debug level" do Logging.debug = false subject.log_debug("hiya") expect do @readpipe.read_nonblock(512) end.to raise_error(IO::EAGAINWaitReadable) end # # it "should be usable (at the module level) for logging" do expect(@custom_logger).to receive(:add) Logging.log_msg("hey") end # These should be the last test we run for the 'log' functionality # it "should not log messages if silenced via module method" do Logging.silent = true subject.log_info("hola") expect do @readpipe.read_nonblock(512) end.to raise_error(IO::EAGAINWaitReadable) end it "should not log anything if silenced via module methods" do Logging.silent = true Logging.log_msg("hi") expect do @readpipe.read_nonblock(512) end.to raise_error(IO::EAGAINWaitReadable) end it "should not log anything if silenced via instance methods" do subject.silent = true subject.log_info("hello") expect do @readpipe.read_nonblock(512) end.to raise_error(IO::EAGAINWaitReadable) end end # Logging tests (with custom logger) describe "logging routines (with NO custom logger)" do it "should log at debug level if debug logging is enabled " do Logging.debug = true out = with_redirected_stdout do subject.log_debug("Hey") end expect(out.include?("Hey")).to be_truthy expect(out.include?("DEBUG")).to be_truthy end it "should be usable (at the module level) for logging" do out = with_redirected_stdout do Logging.log_msg("Hey") end expect(out.include?("Hey")).to be_truthy end end describe "trace routines (with custom trace logger)" do before :each do @custom_tracer = Logger.new(STDERR) Logging.trace_logger = @custom_tracer end it "should NOT emit trace messages if tracing is disabled" do Logging.trace = false expect(@custom_tracer).not_to receive(:info) subject.trace("howdy") end it "should emit trace messages when tracing is enabled" do Logging.trace = true expect(@custom_tracer).to receive(:info) subject.trace("aloha") end end # Tracer tests (with custom tracer) describe "tracing routines (with NO custom logger)" do it "should emit trace messages if tracing is enabled " do Logging.trace = true out = with_redirected_stdout do subject.trace("Hey") end expect(out.include?("Hey")).to be_truthy end it "should be usable (at the module level) for logging" do Logging.trace = true out = with_redirected_stdout do Logging.trace_msg("hey") end expect(out.include?("hey")).to be_truthy end end # tracer tests (no custom logger) end thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/perf/000077500000000000000000000000001475433514000212465ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/perf/request_perf_spec.rb000066400000000000000000000032121475433514000253070ustar00rootroot00000000000000require 'spec_helper' describe Request, 'performance' do it "should be faster then #{max_parsing_time = 0.0002} RubySeconds" do body = <<-EOS.chomp.gsub("\n", "\r\n") POST /postit HTTP/1.1 Host: localhost:3000 User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.9) Gecko/20071025 Firefox/2.0.0.9 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Content-Type: text/html Content-Length: 37 hi=there&name=marc&email=macournoyer@gmail.com EOS expect { R(body) }.to be_faster_then(max_parsing_time) end it 'should be comparable to Mongrel parser' do require 'http11' body = <<-EOS.chomp.gsub("\n", "\r\n") POST /postit HTTP/1.1 Host: localhost:3000 User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.9) Gecko/20071025 Firefox/2.0.0.9 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Content-Type: text/html Content-Length: 37 hi=there&name=marc&email=macournoyer@gmail.com EOS tests = 10_000 puts Benchmark.bmbm(10) do |results| results.report("mongrel:") { tests.times { Mongrel::HttpParser.new.execute({}, body.dup, 0) } } results.report("thin:") { tests.times { Thin::HttpParser.new.execute({'rack.input' => StringIO.new}, body.dup, 0) } } end end if ENV['BM'] endthin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/perf/response_perf_spec.rb000066400000000000000000000006071475433514000254620ustar00rootroot00000000000000require 'spec_helper' describe Response, 'performance' do before do @response = Response.new @response.body = '' end it "should be fast" do @response.body << <<-EOS Dir listing

Listing stuff

    #{'
  • Hi!
  • ' * 100}
EOS expect { @response.each { |l| l } }.to be_faster_then(0.00011) end endthin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/perf/server_perf_spec.rb000066400000000000000000000017471475433514000251400ustar00rootroot00000000000000require 'spec_helper' describe Server, 'performance' do before do start_server do |env| body = env.inspect + env['rack.input'].read [200, { 'content-length' => body.size.to_s }, body] end end it "should handle GET in less then #{get_request_time = 0.0045} RubySecond" do expect { get('/') }.to be_faster_then(get_request_time) end it "should handle POST in less then #{post_request_time = 0.007} RubySecond" do expect { post('/', :file => 'X' * 1000) }.to be_faster_then(post_request_time) end after do stop_server end end describe Server, 'UNIX socket performance' do before do start_server('/tmp/thin_test.sock') do |env| body = env.inspect + env['rack.input'].read [200, { 'content-length' => body.size.to_s }, body] end end it "should handle GET in less then #{get_request_time = 0.002} RubySecond" do expect { get('/') }.to be_faster_then(get_request_time) end after do stop_server end endthin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rack/000077500000000000000000000000001475433514000212325ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rack/loader_spec.rb000066400000000000000000000023111475433514000240340ustar00rootroot00000000000000require 'spec_helper' describe Rack::Adapter do before do @config_ru_path = File.dirname(__FILE__) + '/../../example' @rails_path = File.dirname(__FILE__) + '/../rails_app' end it "should load Rack app from config" do expect(Rack::Adapter.load(@config_ru_path + '/config.ru').class).to eq(Proc) end it "should guess Rack app from dir" do expect(Rack::Adapter.guess(@config_ru_path)).to eq(:rack) end it "should guess rails app from dir" do expect(Rack::Adapter.guess(@rails_path)).to eq(:rails) end it "should return nil when can't guess from dir" do expect { Rack::Adapter.guess('.') }.to raise_error(Rack::AdapterNotFound) end it "should load Rack adapter" do expect(Rack::Adapter.for(:rack, :chdir => @config_ru_path).class).to eq(Proc) end it "should load Rails adapter" do expect(Rack::Adapter::Rails).to receive(:new) Rack::Adapter.for(:rails, :chdir => @rails_path) end it "should load File adapter" do expect(Rack::Files).to receive(:new) Rack::Adapter.for(:file) end it "should raise error when adapter can't be found" do expect { Rack::Adapter.for(:fart, {}) }.to raise_error(Rack::AdapterNotFound) end endthin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rack/rails_adapter_spec.rb000066400000000000000000000113551475433514000254100ustar00rootroot00000000000000require 'spec_helper' require 'rack/mock' begin gem 'rails', '= 2.0.2' # We could freeze Rails in the rails_app dir to remove this describe Rack::Adapter::Rails do before do @rails_app_path = File.dirname(__FILE__) + '/../rails_app' @request = Rack::MockRequest.new(Rack::Adapter::Rails.new(:root => @rails_app_path)) end it "should handle simple GET request" do res = @request.get("/simple", :lint => true) expect(res).to be_ok expect(res["Content-Type"]).to include("text/html") expect(res.body).to include('Simple#index') end it "should handle POST parameters" do data = "foo=bar" res = @request.post("/simple/post_form", :input => data, 'CONTENT_LENGTH' => data.size.to_s, :lint => true) expect(res).to be_ok expect(res["Content-Type"]).to include("text/html") expect(res["Content-Length"]).not_to be_nil expect(res.body).to include('foo: bar') end it "should serve static files" do res = @request.get("/index.html", :lint => true) expect(res).to be_ok expect(res["Content-Type"]).to include("text/html") end it "should serve root with index.html if present" do res = @request.get("/", :lint => true) expect(res).to be_ok expect(res["Content-Length"].to_i).to eq(File.size(@rails_app_path + '/public/index.html')) end it "should serve page cache if present" do res = @request.get("/simple/cached?value=cached", :lint => true) expect(res).to be_ok expect(res.body).to eq('cached') res = @request.get("/simple/cached?value=notcached") expect(res).to be_ok expect(res.body).to eq('cached') end it "should not serve page cache on POST request" do res = @request.get("/simple/cached?value=cached", :lint => true) expect(res).to be_ok expect(res.body).to eq('cached') res = @request.post("/simple/cached?value=notcached") expect(res).to be_ok expect(res.body).to eq('notcached') end it "handles multiple cookies" do res = @request.get('/simple/set_cookie?name=a&value=1', :lint => true) expect(res).to be_ok expect(res.original_headers['Set-Cookie'].size).to eq(2) expect(res.original_headers['Set-Cookie'].first).to include('a=1; path=/') expect(res.original_headers['Set-Cookie'].last).to include('_rails_app_session') end after do FileUtils.rm_rf @rails_app_path + '/public/simple' end end describe Rack::Adapter::Rails, 'with prefix' do before do @rails_app_path = File.dirname(__FILE__) + '/../rails_app' @prefix = '/nowhere' @request = Rack::MockRequest.new( Rack::URLMap.new( @prefix => Rack::Adapter::Rails.new(:root => @rails_app_path, :prefix => @prefix))) end it "should handle simple GET request" do res = @request.get("#{@prefix}/simple", :lint => true) expect(res).to be_ok expect(res["Content-Type"]).to include("text/html") expect(res.body).to include('Simple#index') end end rescue Gem::LoadError warn 'Rails 2.0.2 is required to run the Rails adapter specs' end module RailsMock module VERSION MAJOR = 0 MINOR = 0 TINY = 0 end end describe Rack::Adapter::Rails, "Adapter version" do before do unless defined?(::Rails) ::Rails = RailsMock end end it "should use Rack based adapter when Rails = 2.2.3" do with_rails_version(2, 2, 3) do expect(Rack::Adapter::Rails).to be_rack_based end end it "should not use Rack based adapter when Rails < 2.2.3" do with_rails_version(2, 2, 2) do expect(Rack::Adapter::Rails).not_to be_rack_based end end it "should not use Rack based adapter when Rails = 1.2.3" do with_rails_version(1, 2, 2) do expect(Rack::Adapter::Rails).not_to be_rack_based end end it "should use Rack based adapter when Rails = 3.0.0" do with_rails_version(3, 0, 0) do expect(Rack::Adapter::Rails).to be_rack_based end end def with_rails_version(major, minor, tiny) old_major = ::Rails::VERSION::MAJOR old_minor = ::Rails::VERSION::MINOR old_tiny = ::Rails::VERSION::TINY silence_warnings do ::Rails::VERSION.const_set :MAJOR, major ::Rails::VERSION.const_set :MINOR, minor ::Rails::VERSION.const_set :TINY, tiny end yield silence_warnings do ::Rails::VERSION.const_set :MAJOR, old_major ::Rails::VERSION.const_set :MINOR, old_minor ::Rails::VERSION.const_set :TINY, old_tiny end end def silence_warnings old_verbose, $VERBOSE = $VERBOSE, nil yield ensure $VERBOSE = old_verbose end unless method_defined?(:silence_warnings) end thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/000077500000000000000000000000001475433514000222645ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/app/000077500000000000000000000000001475433514000230445ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/app/controllers/000077500000000000000000000000001475433514000254125ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/app/controllers/application.rb000066400000000000000000000007251475433514000302460ustar00rootroot00000000000000# Filters added to this controller apply to all controllers in the application. # Likewise, all the methods added will be available for all controllers. class ApplicationController < ActionController::Base helper :all # include all helpers, all the time # See ActionController::RequestForgeryProtection for details # Uncomment the :secret if you're not using the cookie session store # protect_from_forgery # :secret => 'a8af303b8dabf2d2d8f1a7912ac04d7d' end thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/app/controllers/simple_controller.rb000066400000000000000000000005161475433514000314750ustar00rootroot00000000000000class SimpleController < ApplicationController caches_page :cached def index end def post_form render :text => params.to_yaml end def set_cookie cookies[params[:name]] = params[:value] if params[:name] render :text => cookies.to_yaml end def cached render :text => params[:value] end end thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/app/helpers/000077500000000000000000000000001475433514000245065ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/app/helpers/application_helper.rb000066400000000000000000000001631475433514000306750ustar00rootroot00000000000000# Methods added to this helper will be available to all templates in the application. module ApplicationHelper end thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/app/views/000077500000000000000000000000001475433514000242015ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/app/views/simple/000077500000000000000000000000001475433514000254725ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/app/views/simple/index.html.erb000066400000000000000000000003651475433514000302420ustar00rootroot00000000000000

Simple#index

ENV

<%= request.env.to_yaml %>

Cookies

<%= request.cookies.to_yaml %>

Params

<%= params.to_yaml %> <% form_tag '/simple' do %> <%= text_field_tag :a %> <%= submit_tag 'Submit' %> <% end %>thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/config/000077500000000000000000000000001475433514000235315ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/config/boot.rb000066400000000000000000000051761475433514000250320ustar00rootroot00000000000000# Don't change this file! # Configure your app in config/environment.rb and config/environments/*.rb RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) module Rails class << self def boot! unless booted? preinitialize pick_boot.run end end def booted? defined? Rails::Initializer end def pick_boot (vendor_rails? ? VendorBoot : GemBoot).new end def vendor_rails? File.exist?("#{RAILS_ROOT}/vendor/rails") end # FIXME : Ruby 1.9 def preinitialize load(preinitializer_path) if File.exist?(preinitializer_path) end def preinitializer_path "#{RAILS_ROOT}/config/preinitializer.rb" end end class Boot def run load_initializer Rails::Initializer.run(:set_load_path) end end class VendorBoot < Boot def load_initializer require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" end end class GemBoot < Boot def load_initializer self.class.load_rubygems load_rails_gem require 'initializer' end def load_rails_gem if version = self.class.gem_version gem 'rails', version else gem 'rails' end rescue Gem::LoadError => load_error $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.) exit 1 end class << self def rubygems_version Gem::RubyGemsVersion if defined? Gem::RubyGemsVersion end def gem_version if defined? RAILS_GEM_VERSION RAILS_GEM_VERSION elsif ENV.include?('RAILS_GEM_VERSION') ENV['RAILS_GEM_VERSION'] else parse_gem_version(read_environment_rb) end end def load_rubygems require 'rubygems' unless rubygems_version >= '0.9.4' $stderr.puts %(Rails requires RubyGems >= 0.9.4 (you have #{rubygems_version}). Please `gem update --system` and try again.) exit 1 end rescue LoadError $stderr.puts %(Rails requires RubyGems >= 0.9.4. Please install RubyGems and try again: http://rubygems.rubyforge.org) exit 1 end def parse_gem_version(text) $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/ end private def read_environment_rb File.read("#{RAILS_ROOT}/config/environment.rb") end end end end # All that for this: Rails.boot! thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/config/environment.rb000066400000000000000000000056401475433514000264270ustar00rootroot00000000000000# Be sure to restart your server when you modify this file # Uncomment below to force Rails into production mode when # you don't control web/app server and can't set it the proper way # ENV['RAILS_ENV'] ||= 'production' # Specifies gem version of Rails to use when vendor/rails is not present RAILS_GEM_VERSION = '2.0.2' unless defined? RAILS_GEM_VERSION # Bootstrap the Rails environment, frameworks, and default configuration require File.join(File.dirname(__FILE__), 'boot') Rails::Initializer.run do |config| # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. # See Rails::Configuration for more options. # Skip frameworks you're not going to use (only works if using vendor/rails). # To use Rails without a database, you must remove the Active Record framework config.frameworks -= [ :active_record, :active_resource, :action_mailer ] # Only load the plugins named here, in the order given. By default, all plugins # in vendor/plugins are loaded in alphabetical order. # :all can be used as a placeholder for all plugins not explicitly named # config.plugins = [ :exception_notification, :ssl_requirement, :all ] # Add additional load paths for your own custom dirs # config.load_paths += %W( #{RAILS_ROOT}/extras ) # No need for log files config.logger = Logger.new(nil) # Force all environments to use the same logger level # (by default production uses :info, the others :debug) # config.log_level = :debug # Your secret key for verifying cookie session data integrity. # If you change this key, all old sessions will become invalid! # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. config.action_controller.session = { :session_key => '_rails_app_session', :secret => 'cb7141365b4443eff37e7122473e704ceae95146a4028930b21300965fe6abec51e3e93b2670a914b3b65d06058b81aadfe6b240d63e7d7713db044b42a6e1c1' } config.action_controller.allow_forgery_protection = false # Use the database for sessions instead of the cookie-based default, # which shouldn't be used to store highly confidential information # (create the session table with 'rake db:sessions:create') # config.action_controller.session_store = :active_record_store # Use SQL instead of Active Record's schema dumper when creating the test database. # This is necessary if your schema can't be completely dumped by the schema dumper, # like if you have constraints or database-specific column types # config.active_record.schema_format = :sql # Activate observers that should always be running # config.active_record.observers = :cacher, :garbage_collector # Make Active Record use UTC-base instead of local time # config.active_record.default_timezone = :utc endthin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/config/environments/000077500000000000000000000000001475433514000262605ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/config/environments/development.rb000066400000000000000000000014471475433514000311350ustar00rootroot00000000000000# Settings specified here will take precedence over those in config/environment.rb # In the development environment your application's code is reloaded on # every request. This slows down response time but is perfect for development # since you don't have to restart the webserver when you make code changes. config.cache_classes = false # Log error messages when you accidentally call methods on nil. config.whiny_nils = true # Show full error reports and disable caching config.action_controller.consider_all_requests_local = true config.action_view.debug_rjs = true config.action_controller.perform_caching = true config.action_view.cache_template_extensions = false # Don't care if the mailer can't send config.action_mailer.raise_delivery_errors = falsethin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/config/environments/production.rb000066400000000000000000000014671475433514000310030ustar00rootroot00000000000000# Settings specified here will take precedence over those in config/environment.rb # The production environment is meant for finished, "live" apps. # Code is not reloaded between requests config.cache_classes = true # Use a different logger for distributed setups # config.logger = SyslogLogger.new # Full error reports are disabled and caching is turned on config.action_controller.consider_all_requests_local = false config.action_controller.perform_caching = true config.action_view.cache_template_loading = true # Enable serving of images, stylesheets, and javascripts from an asset server # config.action_controller.asset_host = "http://assets.example.com" # Disable delivery errors, bad email addresses will be ignored # config.action_mailer.raise_delivery_errors = false thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/config/environments/test.rb000066400000000000000000000017151475433514000275700ustar00rootroot00000000000000# Settings specified here will take precedence over those in config/environment.rb # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped # and recreated between test runs. Don't rely on the data there! config.cache_classes = true # Log error messages when you accidentally call methods on nil. config.whiny_nils = true # Show full error reports and disable caching config.action_controller.consider_all_requests_local = true config.action_controller.perform_caching = false # Disable request forgery protection in test environment config.action_controller.allow_forgery_protection = false # Tell ActionMailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/config/initializers/000077500000000000000000000000001475433514000262375ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/config/initializers/inflections.rb000066400000000000000000000005521475433514000311030ustar00rootroot00000000000000# Be sure to restart your server when you modify this file. # Add new inflection rules using the following format # (all these examples are active by default): # Inflector.inflections do |inflect| # inflect.plural /^(ox)$/i, '\1en' # inflect.singular /^(ox)en/i, '\1' # inflect.irregular 'person', 'people' # inflect.uncountable %w( fish sheep ) # end thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/config/initializers/mime_types.rb000066400000000000000000000003151475433514000307360ustar00rootroot00000000000000# Be sure to restart your server when you modify this file. # Add new mime types for use in respond_to blocks: # Mime::Type.register "text/richtext", :rtf # Mime::Type.register_alias "text/html", :iphone thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/config/routes.rb000066400000000000000000000027241475433514000254040ustar00rootroot00000000000000ActionController::Routing::Routes.draw do |map| # The priority is based upon order of creation: first created -> highest priority. # Sample of regular route: # map.connect 'products/:id', :controller => 'catalog', :action => 'view' # Keep in mind you can assign values other than :controller and :action # Sample of named route: # map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase' # This route can be invoked with purchase_url(:id => product.id) # Sample resource route (maps HTTP verbs to controller actions automatically): # map.resources :products # Sample resource route with options: # map.resources :products, :member => { :short => :get, :toggle => :post }, :collection => { :sold => :get } # Sample resource route with sub-resources: # map.resources :products, :has_many => [ :comments, :sales ], :has_one => :seller # Sample resource route within a namespace: # map.namespace :admin do |admin| # # Directs /admin/products/* to Admin::ProductsController (app/controllers/admin/products_controller.rb) # admin.resources :products # end # You can have the root of your site routed with map.root -- just remember to delete public/index.html. # map.root :controller => "welcome" # See how all your routes lay out with "rake routes" # Install the default routes as the lowest priority. map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format' end thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/public/000077500000000000000000000000001475433514000235425ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/public/.htaccess000066400000000000000000000023431475433514000253420ustar00rootroot00000000000000# General Apache options AddHandler fastcgi-script .fcgi AddHandler cgi-script .cgi Options +FollowSymLinks +ExecCGI # If you don't want Rails to look in certain directories, # use the following rewrite rules so that Apache won't rewrite certain requests # # Example: # RewriteCond %{REQUEST_URI} ^/notrails.* # RewriteRule .* - [L] # Redirect all requests not available on the filesystem to Rails # By default the cgi dispatcher is used which is very slow # # For better performance replace the dispatcher with the fastcgi one # # Example: # RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] RewriteEngine On # If your Rails application is accessed via an Alias directive, # then you MUST also set the RewriteBase in this htaccess file. # # Example: # Alias /myrailsapp /path/to/myrailsapp/public # RewriteBase /myrailsapp RewriteRule ^$ index.html [QSA] RewriteRule ^([^.]+)$ $1.html [QSA] RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ dispatch.cgi [QSA,L] # In case Rails experiences terminal errors # Instead of displaying this message you can supply a file here which will be rendered instead # # Example: # ErrorDocument 500 /500.html ErrorDocument 500 "

Application error

Rails application failed to start properly" thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/public/404.html000066400000000000000000000016631475433514000247450ustar00rootroot00000000000000 The page you were looking for doesn't exist (404)

The page you were looking for doesn't exist.

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

thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/public/422.html000066400000000000000000000016421475433514000247420ustar00rootroot00000000000000 The change you wanted was rejected (422)

The change you wanted was rejected.

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

thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/public/500.html000066400000000000000000000016631475433514000247420ustar00rootroot00000000000000 We're sorry, but something went wrong (500)

We're sorry, but something went wrong.

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

thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/public/dispatch.cgi000077500000000000000000000007361475433514000260360ustar00rootroot00000000000000#!/usr/bin/env ruby require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) # If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: # "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired require "dispatcher" ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) Dispatcher.dispatch thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/public/dispatch.fcgi000077500000000000000000000015331475433514000262000ustar00rootroot00000000000000#!/usr/bin/env ruby # # You may specify the path to the FastCGI crash log (a log of unhandled # exceptions which forced the FastCGI instance to exit, great for debugging) # and the number of requests to process before running garbage collection. # # By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log # and the GC period is nil (turned off). A reasonable number of requests # could range from 10-100 depending on the memory footprint of your app. # # Example: # # Default log path, normal GC behavior. # RailsFCGIHandler.process! # # # Default log path, 50 requests between GC. # RailsFCGIHandler.process! nil, 50 # # # Custom log path, normal GC behavior. # RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log' # require File.dirname(__FILE__) + "/../config/environment" require 'fcgi_handler' RailsFCGIHandler.process! thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/public/dispatch.rb000077500000000000000000000007361475433514000256770ustar00rootroot00000000000000#!/usr/bin/env ruby require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) # If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: # "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired require "dispatcher" ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) Dispatcher.dispatch thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/public/favicon.ico000066400000000000000000000000001475433514000256510ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/public/images/000077500000000000000000000000001475433514000250075ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/public/images/rails.png000066400000000000000000000033731475433514000266350ustar00rootroot00000000000000PNG  IHDR2@o IgAMAOX2tEXtSoftwareAdobe ImageReadyqe<PLTEI8Ͱ·w|"%ϕUH"۰'+6Btjڤ;4 (ЫiH*,zx|Vվ޶QOסrGH2/΍aƤ<5FC.7rLߵf]I=Ӥ\XġaD[UX>ú00%,±eiPKae" /pIDATxڔ[cJ `xEEDET`=If@ypw-_33(@Pt]UU]7?U'_.I* T CՕaqe?+ٶ$ _L@y3d-5ϛOW$W ?,M.G%oA@R<$a$;Ncܜß~+"6d(0$ !!%մbWV ]ysD Zl\}X#!1`eO+ M xCKN)> iM(AR8f?{gNqKp6/eF JK4Yz :sS&l\ >(Ӂ/G$̜N4ey!@L39Mgx !II@ Af 94*dO^#gapdYgE@$|>'KHj/ @Kis(=XHn6Daqqs} LjA!< GE πX< #nK+A9mLr̒/mؑCAH3$%fK[=Im:3ϛIY40H7YnAS[s 7>Y<Ӱs]MB^} JvX5邏Ɗ!͙QQ7o,Q9I_!|K M;HsY(JN[nF|W,@~=?y]엯,"+>>_H(-YA[M~~X=%.O!F E`V@(E!8]rA߈\{4+'Q|#&iHCJύ +:Sgtf,ɎM۩f yI@;]v$r#Q[i3^T|,աNLCDjl($띄Wg1NG"Vt.;t!+@`ZmHU>>JC.D6f C \W}L⭛P;dD6Fsr^BT'xbםA^b<47aEY ~ I/׋(aHs a-\+lIH$c5[Ixp*DbC>;_Dڀ˚hH^?^QOxt$vHIC٧'krH ;N?=@]1r{h黆,^8i;dC!! BR$$'][k$r M.6N4#RwEK~u b6És`EuX柒N$£EwyR+FυxDx [[_%BKj(;WTO,#yM+\c$[m_؉p!SIv+pH M 3eIENDB`thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/public/index.html000066400000000000000000000166051475433514000255470ustar00rootroot00000000000000 Ruby on Rails: Welcome aboard

Getting started

Here’s how to get rolling:

  1. Create your databases and edit config/database.yml

    Rails needs to know your login and password.

  2. Use script/generate to create your models and controllers

    To see all available options, run it without parameters.

  3. Set up a default route and remove or rename this file

    Routes are set up in config/routes.rb.

thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/public/javascripts/000077500000000000000000000000001475433514000260735ustar00rootroot00000000000000thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/public/javascripts/application.js000066400000000000000000000002241475433514000307320ustar00rootroot00000000000000// Place your application-specific JavaScript functions and classes here // This file is automatically included by javascript_include_tag :defaults thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/public/javascripts/controls.js000066400000000000000000001037551475433514000303070ustar00rootroot00000000000000// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan) // (c) 2005-2007 Jon Tirsen (http://www.tirsen.com) // Contributors: // Richard Livsey // Rahul Bhargava // Rob Wills // // script.aculo.us is freely distributable under the terms of an MIT-style license. // For details, see the script.aculo.us web site: http://script.aculo.us/ // Autocompleter.Base handles all the autocompletion functionality // that's independent of the data source for autocompletion. This // includes drawing the autocompletion menu, observing keyboard // and mouse events, and similar. // // Specific autocompleters need to provide, at the very least, // a getUpdatedChoices function that will be invoked every time // the text inside the monitored textbox changes. This method // should get the text for which to provide autocompletion by // invoking this.getToken(), NOT by directly accessing // this.element.value. This is to allow incremental tokenized // autocompletion. Specific auto-completion logic (AJAX, etc) // belongs in getUpdatedChoices. // // Tokenized incremental autocompletion is enabled automatically // when an autocompleter is instantiated with the 'tokens' option // in the options parameter, e.g.: // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); // will incrementally autocomplete with a comma as the token. // Additionally, ',' in the above example can be replaced with // a token array, e.g. { tokens: [',', '\n'] } which // enables autocompletion on multiple tokens. This is most // useful when one of the tokens is \n (a newline), as it // allows smart autocompletion after linebreaks. if(typeof Effect == 'undefined') throw("controls.js requires including script.aculo.us' effects.js library"); var Autocompleter = { } Autocompleter.Base = Class.create({ baseInitialize: function(element, update, options) { element = $(element) this.element = element; this.update = $(update); this.hasFocus = false; this.changed = false; this.active = false; this.index = 0; this.entryCount = 0; this.oldElementValue = this.element.value; if(this.setOptions) this.setOptions(options); else this.options = options || { }; this.options.paramName = this.options.paramName || this.element.name; this.options.tokens = this.options.tokens || []; this.options.frequency = this.options.frequency || 0.4; this.options.minChars = this.options.minChars || 1; this.options.onShow = this.options.onShow || function(element, update){ if(!update.style.position || update.style.position=='absolute') { update.style.position = 'absolute'; Position.clone(element, update, { setHeight: false, offsetTop: element.offsetHeight }); } Effect.Appear(update,{duration:0.15}); }; this.options.onHide = this.options.onHide || function(element, update){ new Effect.Fade(update,{duration:0.15}) }; if(typeof(this.options.tokens) == 'string') this.options.tokens = new Array(this.options.tokens); // Force carriage returns as token delimiters anyway if (!this.options.tokens.include('\n')) this.options.tokens.push('\n'); this.observer = null; this.element.setAttribute('autocomplete','off'); Element.hide(this.update); Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this)); }, show: function() { if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); if(!this.iefix && (Prototype.Browser.IE) && (Element.getStyle(this.update, 'position')=='absolute')) { new Insertion.After(this.update, ''); this.iefix = $(this.update.id+'_iefix'); } if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); }, fixIEOverlapping: function() { Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); this.iefix.style.zIndex = 1; this.update.style.zIndex = 2; Element.show(this.iefix); }, hide: function() { this.stopIndicator(); if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); if(this.iefix) Element.hide(this.iefix); }, startIndicator: function() { if(this.options.indicator) Element.show(this.options.indicator); }, stopIndicator: function() { if(this.options.indicator) Element.hide(this.options.indicator); }, onKeyPress: function(event) { if(this.active) switch(event.keyCode) { case Event.KEY_TAB: case Event.KEY_RETURN: this.selectEntry(); Event.stop(event); case Event.KEY_ESC: this.hide(); this.active = false; Event.stop(event); return; case Event.KEY_LEFT: case Event.KEY_RIGHT: return; case Event.KEY_UP: this.markPrevious(); this.render(); Event.stop(event); return; case Event.KEY_DOWN: this.markNext(); this.render(); Event.stop(event); return; } else if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; this.changed = true; this.hasFocus = true; if(this.observer) clearTimeout(this.observer); this.observer = setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); }, activate: function() { this.changed = false; this.hasFocus = true; this.getUpdatedChoices(); }, onHover: function(event) { var element = Event.findElement(event, 'LI'); if(this.index != element.autocompleteIndex) { this.index = element.autocompleteIndex; this.render(); } Event.stop(event); }, onClick: function(event) { var element = Event.findElement(event, 'LI'); this.index = element.autocompleteIndex; this.selectEntry(); this.hide(); }, onBlur: function(event) { // needed to make click events working setTimeout(this.hide.bind(this), 250); this.hasFocus = false; this.active = false; }, render: function() { if(this.entryCount > 0) { for (var i = 0; i < this.entryCount; i++) this.index==i ? Element.addClassName(this.getEntry(i),"selected") : Element.removeClassName(this.getEntry(i),"selected"); if(this.hasFocus) { this.show(); this.active = true; } } else { this.active = false; this.hide(); } }, markPrevious: function() { if(this.index > 0) this.index-- else this.index = this.entryCount-1; this.getEntry(this.index).scrollIntoView(true); }, markNext: function() { if(this.index < this.entryCount-1) this.index++ else this.index = 0; this.getEntry(this.index).scrollIntoView(false); }, getEntry: function(index) { return this.update.firstChild.childNodes[index]; }, getCurrentEntry: function() { return this.getEntry(this.index); }, selectEntry: function() { this.active = false; this.updateElement(this.getCurrentEntry()); }, updateElement: function(selectedElement) { if (this.options.updateElement) { this.options.updateElement(selectedElement); return; } var value = ''; if (this.options.select) { var nodes = $(selectedElement).select('.' + this.options.select) || []; if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); } else value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); var bounds = this.getTokenBounds(); if (bounds[0] != -1) { var newValue = this.element.value.substr(0, bounds[0]); var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/); if (whitespace) newValue += whitespace[0]; this.element.value = newValue + value + this.element.value.substr(bounds[1]); } else { this.element.value = value; } this.oldElementValue = this.element.value; this.element.focus(); if (this.options.afterUpdateElement) this.options.afterUpdateElement(this.element, selectedElement); }, updateChoices: function(choices) { if(!this.changed && this.hasFocus) { this.update.innerHTML = choices; Element.cleanWhitespace(this.update); Element.cleanWhitespace(this.update.down()); if(this.update.firstChild && this.update.down().childNodes) { this.entryCount = this.update.down().childNodes.length; for (var i = 0; i < this.entryCount; i++) { var entry = this.getEntry(i); entry.autocompleteIndex = i; this.addObservers(entry); } } else { this.entryCount = 0; } this.stopIndicator(); this.index = 0; if(this.entryCount==1 && this.options.autoSelect) { this.selectEntry(); this.hide(); } else { this.render(); } } }, addObservers: function(element) { Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); Event.observe(element, "click", this.onClick.bindAsEventListener(this)); }, onObserverEvent: function() { this.changed = false; this.tokenBounds = null; if(this.getToken().length>=this.options.minChars) { this.getUpdatedChoices(); } else { this.active = false; this.hide(); } this.oldElementValue = this.element.value; }, getToken: function() { var bounds = this.getTokenBounds(); return this.element.value.substring(bounds[0], bounds[1]).strip(); }, getTokenBounds: function() { if (null != this.tokenBounds) return this.tokenBounds; var value = this.element.value; if (value.strip().empty()) return [-1, 0]; var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue); var offset = (diff == this.oldElementValue.length ? 1 : 0); var prevTokenPos = -1, nextTokenPos = value.length; var tp; for (var index = 0, l = this.options.tokens.length; index < l; ++index) { tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1); if (tp > prevTokenPos) prevTokenPos = tp; tp = value.indexOf(this.options.tokens[index], diff + offset); if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp; } return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]); } }); Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) { var boundary = Math.min(newS.length, oldS.length); for (var index = 0; index < boundary; ++index) if (newS[index] != oldS[index]) return index; return boundary; }; Ajax.Autocompleter = Class.create(Autocompleter.Base, { initialize: function(element, update, url, options) { this.baseInitialize(element, update, options); this.options.asynchronous = true; this.options.onComplete = this.onComplete.bind(this); this.options.defaultParams = this.options.parameters || null; this.url = url; }, getUpdatedChoices: function() { this.startIndicator(); var entry = encodeURIComponent(this.options.paramName) + '=' + encodeURIComponent(this.getToken()); this.options.parameters = this.options.callback ? this.options.callback(this.element, entry) : entry; if(this.options.defaultParams) this.options.parameters += '&' + this.options.defaultParams; new Ajax.Request(this.url, this.options); }, onComplete: function(request) { this.updateChoices(request.responseText); } }); // The local array autocompleter. Used when you'd prefer to // inject an array of autocompletion options into the page, rather // than sending out Ajax queries, which can be quite slow sometimes. // // The constructor takes four parameters. The first two are, as usual, // the id of the monitored textbox, and id of the autocompletion menu. // The third is the array you want to autocomplete from, and the fourth // is the options block. // // Extra local autocompletion options: // - choices - How many autocompletion choices to offer // // - partialSearch - If false, the autocompleter will match entered // text only at the beginning of strings in the // autocomplete array. Defaults to true, which will // match text at the beginning of any *word* in the // strings in the autocomplete array. If you want to // search anywhere in the string, additionally set // the option fullSearch to true (default: off). // // - fullSsearch - Search anywhere in autocomplete array strings. // // - partialChars - How many characters to enter before triggering // a partial match (unlike minChars, which defines // how many characters are required to do any match // at all). Defaults to 2. // // - ignoreCase - Whether to ignore case when autocompleting. // Defaults to true. // // It's possible to pass in a custom function as the 'selector' // option, if you prefer to write your own autocompletion logic. // In that case, the other options above will not apply unless // you support them. Autocompleter.Local = Class.create(Autocompleter.Base, { initialize: function(element, update, array, options) { this.baseInitialize(element, update, options); this.options.array = array; }, getUpdatedChoices: function() { this.updateChoices(this.options.selector(this)); }, setOptions: function(options) { this.options = Object.extend({ choices: 10, partialSearch: true, partialChars: 2, ignoreCase: true, fullSearch: false, selector: function(instance) { var ret = []; // Beginning matches var partial = []; // Inside matches var entry = instance.getToken(); var count = 0; for (var i = 0; i < instance.options.array.length && ret.length < instance.options.choices ; i++) { var elem = instance.options.array[i]; var foundPos = instance.options.ignoreCase ? elem.toLowerCase().indexOf(entry.toLowerCase()) : elem.indexOf(entry); while (foundPos != -1) { if (foundPos == 0 && elem.length != entry.length) { ret.push("
  • " + elem.substr(0, entry.length) + "" + elem.substr(entry.length) + "
  • "); break; } else if (entry.length >= instance.options.partialChars && instance.options.partialSearch && foundPos != -1) { if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { partial.push("
  • " + elem.substr(0, foundPos) + "" + elem.substr(foundPos, entry.length) + "" + elem.substr( foundPos + entry.length) + "
  • "); break; } } foundPos = instance.options.ignoreCase ? elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : elem.indexOf(entry, foundPos + 1); } } if (partial.length) ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) return "
      " + ret.join('') + "
    "; } }, options || { }); } }); // AJAX in-place editor and collection editor // Full rewrite by Christophe Porteneuve (April 2007). // Use this if you notice weird scrolling problems on some browsers, // the DOM might be a bit confused when this gets called so do this // waits 1 ms (with setTimeout) until it does the activation Field.scrollFreeActivate = function(field) { setTimeout(function() { Field.activate(field); }, 1); } Ajax.InPlaceEditor = Class.create({ initialize: function(element, url, options) { this.url = url; this.element = element = $(element); this.prepareOptions(); this._controls = { }; arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!! Object.extend(this.options, options || { }); if (!this.options.formId && this.element.id) { this.options.formId = this.element.id + '-inplaceeditor'; if ($(this.options.formId)) this.options.formId = ''; } if (this.options.externalControl) this.options.externalControl = $(this.options.externalControl); if (!this.options.externalControl) this.options.externalControlOnly = false; this._originalBackground = this.element.getStyle('background-color') || 'transparent'; this.element.title = this.options.clickToEditText; this._boundCancelHandler = this.handleFormCancellation.bind(this); this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this); this._boundFailureHandler = this.handleAJAXFailure.bind(this); this._boundSubmitHandler = this.handleFormSubmission.bind(this); this._boundWrapperHandler = this.wrapUp.bind(this); this.registerListeners(); }, checkForEscapeOrReturn: function(e) { if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return; if (Event.KEY_ESC == e.keyCode) this.handleFormCancellation(e); else if (Event.KEY_RETURN == e.keyCode) this.handleFormSubmission(e); }, createControl: function(mode, handler, extraClasses) { var control = this.options[mode + 'Control']; var text = this.options[mode + 'Text']; if ('button' == control) { var btn = document.createElement('input'); btn.type = 'submit'; btn.value = text; btn.className = 'editor_' + mode + '_button'; if ('cancel' == mode) btn.onclick = this._boundCancelHandler; this._form.appendChild(btn); this._controls[mode] = btn; } else if ('link' == control) { var link = document.createElement('a'); link.href = '#'; link.appendChild(document.createTextNode(text)); link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler; link.className = 'editor_' + mode + '_link'; if (extraClasses) link.className += ' ' + extraClasses; this._form.appendChild(link); this._controls[mode] = link; } }, createEditField: function() { var text = (this.options.loadTextURL ? this.options.loadingText : this.getText()); var fld; if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) { fld = document.createElement('input'); fld.type = 'text'; var size = this.options.size || this.options.cols || 0; if (0 < size) fld.size = size; } else { fld = document.createElement('textarea'); fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows); fld.cols = this.options.cols || 40; } fld.name = this.options.paramName; fld.value = text; // No HTML breaks conversion anymore fld.className = 'editor_field'; if (this.options.submitOnBlur) fld.onblur = this._boundSubmitHandler; this._controls.editor = fld; if (this.options.loadTextURL) this.loadExternalText(); this._form.appendChild(this._controls.editor); }, createForm: function() { var ipe = this; function addText(mode, condition) { var text = ipe.options['text' + mode + 'Controls']; if (!text || condition === false) return; ipe._form.appendChild(document.createTextNode(text)); }; this._form = $(document.createElement('form')); this._form.id = this.options.formId; this._form.addClassName(this.options.formClassName); this._form.onsubmit = this._boundSubmitHandler; this.createEditField(); if ('textarea' == this._controls.editor.tagName.toLowerCase()) this._form.appendChild(document.createElement('br')); if (this.options.onFormCustomization) this.options.onFormCustomization(this, this._form); addText('Before', this.options.okControl || this.options.cancelControl); this.createControl('ok', this._boundSubmitHandler); addText('Between', this.options.okControl && this.options.cancelControl); this.createControl('cancel', this._boundCancelHandler, 'editor_cancel'); addText('After', this.options.okControl || this.options.cancelControl); }, destroy: function() { if (this._oldInnerHTML) this.element.innerHTML = this._oldInnerHTML; this.leaveEditMode(); this.unregisterListeners(); }, enterEditMode: function(e) { if (this._saving || this._editing) return; this._editing = true; this.triggerCallback('onEnterEditMode'); if (this.options.externalControl) this.options.externalControl.hide(); this.element.hide(); this.createForm(); this.element.parentNode.insertBefore(this._form, this.element); if (!this.options.loadTextURL) this.postProcessEditField(); if (e) Event.stop(e); }, enterHover: function(e) { if (this.options.hoverClassName) this.element.addClassName(this.options.hoverClassName); if (this._saving) return; this.triggerCallback('onEnterHover'); }, getText: function() { return this.element.innerHTML; }, handleAJAXFailure: function(transport) { this.triggerCallback('onFailure', transport); if (this._oldInnerHTML) { this.element.innerHTML = this._oldInnerHTML; this._oldInnerHTML = null; } }, handleFormCancellation: function(e) { this.wrapUp(); if (e) Event.stop(e); }, handleFormSubmission: function(e) { var form = this._form; var value = $F(this._controls.editor); this.prepareSubmission(); var params = this.options.callback(form, value) || ''; if (Object.isString(params)) params = params.toQueryParams(); params.editorId = this.element.id; if (this.options.htmlResponse) { var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions); Object.extend(options, { parameters: params, onComplete: this._boundWrapperHandler, onFailure: this._boundFailureHandler }); new Ajax.Updater({ success: this.element }, this.url, options); } else { var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); Object.extend(options, { parameters: params, onComplete: this._boundWrapperHandler, onFailure: this._boundFailureHandler }); new Ajax.Request(this.url, options); } if (e) Event.stop(e); }, leaveEditMode: function() { this.element.removeClassName(this.options.savingClassName); this.removeForm(); this.leaveHover(); this.element.style.backgroundColor = this._originalBackground; this.element.show(); if (this.options.externalControl) this.options.externalControl.show(); this._saving = false; this._editing = false; this._oldInnerHTML = null; this.triggerCallback('onLeaveEditMode'); }, leaveHover: function(e) { if (this.options.hoverClassName) this.element.removeClassName(this.options.hoverClassName); if (this._saving) return; this.triggerCallback('onLeaveHover'); }, loadExternalText: function() { this._form.addClassName(this.options.loadingClassName); this._controls.editor.disabled = true; var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); Object.extend(options, { parameters: 'editorId=' + encodeURIComponent(this.element.id), onComplete: Prototype.emptyFunction, onSuccess: function(transport) { this._form.removeClassName(this.options.loadingClassName); var text = transport.responseText; if (this.options.stripLoadedTextTags) text = text.stripTags(); this._controls.editor.value = text; this._controls.editor.disabled = false; this.postProcessEditField(); }.bind(this), onFailure: this._boundFailureHandler }); new Ajax.Request(this.options.loadTextURL, options); }, postProcessEditField: function() { var fpc = this.options.fieldPostCreation; if (fpc) $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate'](); }, prepareOptions: function() { this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions); Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks); [this._extraDefaultOptions].flatten().compact().each(function(defs) { Object.extend(this.options, defs); }.bind(this)); }, prepareSubmission: function() { this._saving = true; this.removeForm(); this.leaveHover(); this.showSaving(); }, registerListeners: function() { this._listeners = { }; var listener; $H(Ajax.InPlaceEditor.Listeners).each(function(pair) { listener = this[pair.value].bind(this); this._listeners[pair.key] = listener; if (!this.options.externalControlOnly) this.element.observe(pair.key, listener); if (this.options.externalControl) this.options.externalControl.observe(pair.key, listener); }.bind(this)); }, removeForm: function() { if (!this._form) return; this._form.remove(); this._form = null; this._controls = { }; }, showSaving: function() { this._oldInnerHTML = this.element.innerHTML; this.element.innerHTML = this.options.savingText; this.element.addClassName(this.options.savingClassName); this.element.style.backgroundColor = this._originalBackground; this.element.show(); }, triggerCallback: function(cbName, arg) { if ('function' == typeof this.options[cbName]) { this.options[cbName](this, arg); } }, unregisterListeners: function() { $H(this._listeners).each(function(pair) { if (!this.options.externalControlOnly) this.element.stopObserving(pair.key, pair.value); if (this.options.externalControl) this.options.externalControl.stopObserving(pair.key, pair.value); }.bind(this)); }, wrapUp: function(transport) { this.leaveEditMode(); // Can't use triggerCallback due to backward compatibility: requires // binding + direct element this._boundComplete(transport, this.element); } }); Object.extend(Ajax.InPlaceEditor.prototype, { dispose: Ajax.InPlaceEditor.prototype.destroy }); Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, { initialize: function($super, element, url, options) { this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions; $super(element, url, options); }, createEditField: function() { var list = document.createElement('select'); list.name = this.options.paramName; list.size = 1; this._controls.editor = list; this._collection = this.options.collection || []; if (this.options.loadCollectionURL) this.loadCollection(); else this.checkForExternalText(); this._form.appendChild(this._controls.editor); }, loadCollection: function() { this._form.addClassName(this.options.loadingClassName); this.showLoadingText(this.options.loadingCollectionText); var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); Object.extend(options, { parameters: 'editorId=' + encodeURIComponent(this.element.id), onComplete: Prototype.emptyFunction, onSuccess: function(transport) { var js = transport.responseText.strip(); if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check throw 'Server returned an invalid collection representation.'; this._collection = eval(js); this.checkForExternalText(); }.bind(this), onFailure: this.onFailure }); new Ajax.Request(this.options.loadCollectionURL, options); }, showLoadingText: function(text) { this._controls.editor.disabled = true; var tempOption = this._controls.editor.firstChild; if (!tempOption) { tempOption = document.createElement('option'); tempOption.value = ''; this._controls.editor.appendChild(tempOption); tempOption.selected = true; } tempOption.update((text || '').stripScripts().stripTags()); }, checkForExternalText: function() { this._text = this.getText(); if (this.options.loadTextURL) this.loadExternalText(); else this.buildOptionList(); }, loadExternalText: function() { this.showLoadingText(this.options.loadingText); var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); Object.extend(options, { parameters: 'editorId=' + encodeURIComponent(this.element.id), onComplete: Prototype.emptyFunction, onSuccess: function(transport) { this._text = transport.responseText.strip(); this.buildOptionList(); }.bind(this), onFailure: this.onFailure }); new Ajax.Request(this.options.loadTextURL, options); }, buildOptionList: function() { this._form.removeClassName(this.options.loadingClassName); this._collection = this._collection.map(function(entry) { return 2 === entry.length ? entry : [entry, entry].flatten(); }); var marker = ('value' in this.options) ? this.options.value : this._text; var textFound = this._collection.any(function(entry) { return entry[0] == marker; }.bind(this)); this._controls.editor.update(''); var option; this._collection.each(function(entry, index) { option = document.createElement('option'); option.value = entry[0]; option.selected = textFound ? entry[0] == marker : 0 == index; option.appendChild(document.createTextNode(entry[1])); this._controls.editor.appendChild(option); }.bind(this)); this._controls.editor.disabled = false; Field.scrollFreeActivate(this._controls.editor); } }); //**** DEPRECATION LAYER FOR InPlace[Collection]Editor! **** //**** This only exists for a while, in order to let **** //**** users adapt to the new API. Read up on the new **** //**** API and convert your code to it ASAP! **** Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) { if (!options) return; function fallback(name, expr) { if (name in options || expr === undefined) return; options[name] = expr; }; fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' : options.cancelLink == options.cancelButton == false ? false : undefined))); fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' : options.okLink == options.okButton == false ? false : undefined))); fallback('highlightColor', options.highlightcolor); fallback('highlightEndColor', options.highlightendcolor); }; Object.extend(Ajax.InPlaceEditor, { DefaultOptions: { ajaxOptions: { }, autoRows: 3, // Use when multi-line w/ rows == 1 cancelControl: 'link', // 'link'|'button'|false cancelText: 'cancel', clickToEditText: 'Click to edit', externalControl: null, // id|elt externalControlOnly: false, fieldPostCreation: 'activate', // 'activate'|'focus'|false formClassName: 'inplaceeditor-form', formId: null, // id|elt highlightColor: '#ffff99', highlightEndColor: '#ffffff', hoverClassName: '', htmlResponse: true, loadingClassName: 'inplaceeditor-loading', loadingText: 'Loading...', okControl: 'button', // 'link'|'button'|false okText: 'ok', paramName: 'value', rows: 1, // If 1 and multi-line, uses autoRows savingClassName: 'inplaceeditor-saving', savingText: 'Saving...', size: 0, stripLoadedTextTags: false, submitOnBlur: false, textAfterControls: '', textBeforeControls: '', textBetweenControls: '' }, DefaultCallbacks: { callback: function(form) { return Form.serialize(form); }, onComplete: function(transport, element) { // For backward compatibility, this one is bound to the IPE, and passes // the element directly. It was too often customized, so we don't break it. new Effect.Highlight(element, { startcolor: this.options.highlightColor, keepBackgroundImage: true }); }, onEnterEditMode: null, onEnterHover: function(ipe) { ipe.element.style.backgroundColor = ipe.options.highlightColor; if (ipe._effect) ipe._effect.cancel(); }, onFailure: function(transport, ipe) { alert('Error communication with the server: ' + transport.responseText.stripTags()); }, onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls. onLeaveEditMode: null, onLeaveHover: function(ipe) { ipe._effect = new Effect.Highlight(ipe.element, { startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor, restorecolor: ipe._originalBackground, keepBackgroundImage: true }); } }, Listeners: { click: 'enterEditMode', keydown: 'checkForEscapeOrReturn', mouseover: 'enterHover', mouseout: 'leaveHover' } }); Ajax.InPlaceCollectionEditor.DefaultOptions = { loadingCollectionText: 'Loading options...' }; // Delayed observer, like Form.Element.Observer, // but waits for delay after last key input // Ideal for live-search fields Form.Element.DelayedObserver = Class.create({ initialize: function(element, delay, callback) { this.delay = delay || 0.5; this.element = $(element); this.callback = callback; this.timer = null; this.lastValue = $F(this.element); Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); }, delayedListener: function(event) { if(this.lastValue == $F(this.element)) return; if(this.timer) clearTimeout(this.timer); this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); this.lastValue = $F(this.element); }, onTimerEvent: function() { this.timer = null; this.callback(this.element, $F(this.element)); } }); thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/public/javascripts/dragdrop.js000066400000000000000000000754561475433514000302540ustar00rootroot00000000000000// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) // // script.aculo.us is freely distributable under the terms of an MIT-style license. // For details, see the script.aculo.us web site: http://script.aculo.us/ if(Object.isUndefined(Effect)) throw("dragdrop.js requires including script.aculo.us' effects.js library"); var Droppables = { drops: [], remove: function(element) { this.drops = this.drops.reject(function(d) { return d.element==$(element) }); }, add: function(element) { element = $(element); var options = Object.extend({ greedy: true, hoverclass: null, tree: false }, arguments[1] || { }); // cache containers if(options.containment) { options._containers = []; var containment = options.containment; if(Object.isArray(containment)) { containment.each( function(c) { options._containers.push($(c)) }); } else { options._containers.push($(containment)); } } if(options.accept) options.accept = [options.accept].flatten(); Element.makePositioned(element); // fix IE options.element = element; this.drops.push(options); }, findDeepestChild: function(drops) { deepest = drops[0]; for (i = 1; i < drops.length; ++i) if (Element.isParent(drops[i].element, deepest.element)) deepest = drops[i]; return deepest; }, isContained: function(element, drop) { var containmentNode; if(drop.tree) { containmentNode = element.treeNode; } else { containmentNode = element.parentNode; } return drop._containers.detect(function(c) { return containmentNode == c }); }, isAffected: function(point, element, drop) { return ( (drop.element!=element) && ((!drop._containers) || this.isContained(element, drop)) && ((!drop.accept) || (Element.classNames(element).detect( function(v) { return drop.accept.include(v) } ) )) && Position.within(drop.element, point[0], point[1]) ); }, deactivate: function(drop) { if(drop.hoverclass) Element.removeClassName(drop.element, drop.hoverclass); this.last_active = null; }, activate: function(drop) { if(drop.hoverclass) Element.addClassName(drop.element, drop.hoverclass); this.last_active = drop; }, show: function(point, element) { if(!this.drops.length) return; var drop, affected = []; this.drops.each( function(drop) { if(Droppables.isAffected(point, element, drop)) affected.push(drop); }); if(affected.length>0) drop = Droppables.findDeepestChild(affected); if(this.last_active && this.last_active != drop) this.deactivate(this.last_active); if (drop) { Position.within(drop.element, point[0], point[1]); if(drop.onHover) drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); if (drop != this.last_active) Droppables.activate(drop); } }, fire: function(event, element) { if(!this.last_active) return; Position.prepare(); if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) if (this.last_active.onDrop) { this.last_active.onDrop(element, this.last_active.element, event); return true; } }, reset: function() { if(this.last_active) this.deactivate(this.last_active); } } var Draggables = { drags: [], observers: [], register: function(draggable) { if(this.drags.length == 0) { this.eventMouseUp = this.endDrag.bindAsEventListener(this); this.eventMouseMove = this.updateDrag.bindAsEventListener(this); this.eventKeypress = this.keyPress.bindAsEventListener(this); Event.observe(document, "mouseup", this.eventMouseUp); Event.observe(document, "mousemove", this.eventMouseMove); Event.observe(document, "keypress", this.eventKeypress); } this.drags.push(draggable); }, unregister: function(draggable) { this.drags = this.drags.reject(function(d) { return d==draggable }); if(this.drags.length == 0) { Event.stopObserving(document, "mouseup", this.eventMouseUp); Event.stopObserving(document, "mousemove", this.eventMouseMove); Event.stopObserving(document, "keypress", this.eventKeypress); } }, activate: function(draggable) { if(draggable.options.delay) { this._timeout = setTimeout(function() { Draggables._timeout = null; window.focus(); Draggables.activeDraggable = draggable; }.bind(this), draggable.options.delay); } else { window.focus(); // allows keypress events if window isn't currently focused, fails for Safari this.activeDraggable = draggable; } }, deactivate: function() { this.activeDraggable = null; }, updateDrag: function(event) { if(!this.activeDraggable) return; var pointer = [Event.pointerX(event), Event.pointerY(event)]; // Mozilla-based browsers fire successive mousemove events with // the same coordinates, prevent needless redrawing (moz bug?) if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; this._lastPointer = pointer; this.activeDraggable.updateDrag(event, pointer); }, endDrag: function(event) { if(this._timeout) { clearTimeout(this._timeout); this._timeout = null; } if(!this.activeDraggable) return; this._lastPointer = null; this.activeDraggable.endDrag(event); this.activeDraggable = null; }, keyPress: function(event) { if(this.activeDraggable) this.activeDraggable.keyPress(event); }, addObserver: function(observer) { this.observers.push(observer); this._cacheObserverCallbacks(); }, removeObserver: function(element) { // element instead of observer fixes mem leaks this.observers = this.observers.reject( function(o) { return o.element==element }); this._cacheObserverCallbacks(); }, notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' if(this[eventName+'Count'] > 0) this.observers.each( function(o) { if(o[eventName]) o[eventName](eventName, draggable, event); }); if(draggable.options[eventName]) draggable.options[eventName](draggable, event); }, _cacheObserverCallbacks: function() { ['onStart','onEnd','onDrag'].each( function(eventName) { Draggables[eventName+'Count'] = Draggables.observers.select( function(o) { return o[eventName]; } ).length; }); } } /*--------------------------------------------------------------------------*/ var Draggable = Class.create({ initialize: function(element) { var defaults = { handle: false, reverteffect: function(element, top_offset, left_offset) { var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, queue: {scope:'_draggable', position:'end'} }); }, endeffect: function(element) { var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, queue: {scope:'_draggable', position:'end'}, afterFinish: function(){ Draggable._dragging[element] = false } }); }, zindex: 1000, revert: false, quiet: false, scroll: false, scrollSensitivity: 20, scrollSpeed: 15, snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } delay: 0 }; if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) Object.extend(defaults, { starteffect: function(element) { element._opacity = Element.getOpacity(element); Draggable._dragging[element] = true; new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); } }); var options = Object.extend(defaults, arguments[1] || { }); this.element = $(element); if(options.handle && Object.isString(options.handle)) this.handle = this.element.down('.'+options.handle, 0); if(!this.handle) this.handle = $(options.handle); if(!this.handle) this.handle = this.element; if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { options.scroll = $(options.scroll); this._isScrollChild = Element.childOf(this.element, options.scroll); } Element.makePositioned(this.element); // fix IE this.options = options; this.dragging = false; this.eventMouseDown = this.initDrag.bindAsEventListener(this); Event.observe(this.handle, "mousedown", this.eventMouseDown); Draggables.register(this); }, destroy: function() { Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); Draggables.unregister(this); }, currentDelta: function() { return([ parseInt(Element.getStyle(this.element,'left') || '0'), parseInt(Element.getStyle(this.element,'top') || '0')]); }, initDrag: function(event) { if(!Object.isUndefined(Draggable._dragging[this.element]) && Draggable._dragging[this.element]) return; if(Event.isLeftClick(event)) { // abort on form elements, fixes a Firefox issue var src = Event.element(event); if((tag_name = src.tagName.toUpperCase()) && ( tag_name=='INPUT' || tag_name=='SELECT' || tag_name=='OPTION' || tag_name=='BUTTON' || tag_name=='TEXTAREA')) return; var pointer = [Event.pointerX(event), Event.pointerY(event)]; var pos = 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.delta) this.delta = this.currentDelta(); if(this.options.zindex) { this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); this.element.style.zIndex = this.options.zindex; } if(this.options.ghosting) { this._clone = this.element.cloneNode(true); this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); if (!this.element._originallyAbsolute) Position.absolutize(this.element); this.element.parentNode.insertBefore(this._clone, this.element); } if(this.options.scroll) { if (this.options.scroll == window) { var where = this._getWindowScroll(this.options.scroll); this.originalScrollLeft = where.left; this.originalScrollTop = where.top; } else { this.originalScrollLeft = this.options.scroll.scrollLeft; this.originalScrollTop = this.options.scroll.scrollTop; } } Draggables.notify('onStart', this, event); if(this.options.starteffect) this.options.starteffect(this.element); }, updateDrag: function(event, pointer) { if(!this.dragging) this.startDrag(event); if(!this.options.quiet){ Position.prepare(); Droppables.show(pointer, this.element); } Draggables.notify('onDrag', this, event); this.draw(pointer); if(this.options.change) this.options.change(this); if(this.options.scroll) { this.stopScrolling(); var p; if (this.options.scroll == window) { with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } } else { p = Position.page(this.options.scroll); p[0] += this.options.scroll.scrollLeft + Position.deltaX; p[1] += this.options.scroll.scrollTop + Position.deltaY; p.push(p[0]+this.options.scroll.offsetWidth); p.push(p[1]+this.options.scroll.offsetHeight); } var speed = [0,0]; if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); this.startScrolling(speed); } // fix AppleWebKit rendering if(Prototype.Browser.WebKit) window.scrollBy(0,0); Event.stop(event); }, finishDrag: function(event, success) { this.dragging = false; if(this.options.quiet){ Position.prepare(); var pointer = [Event.pointerX(event), Event.pointerY(event)]; Droppables.show(pointer, this.element); } if(this.options.ghosting) { if (!this.element._originallyAbsolute) Position.relativize(this.element); delete this.element._originallyAbsolute; Element.remove(this._clone); this._clone = null; } var dropped = false; if(success) { dropped = Droppables.fire(event, this.element); if (!dropped) dropped = false; } if(dropped && this.options.onDropped) this.options.onDropped(this.element); Draggables.notify('onEnd', this, event); var revert = this.options.revert; if(revert && Object.isFunction(revert)) revert = revert(this.element); var d = this.currentDelta(); if(revert && this.options.reverteffect) { if (dropped == 0 || revert != 'failure') this.options.reverteffect(this.element, d[1]-this.delta[1], d[0]-this.delta[0]); } else { this.delta = d; } if(this.options.zindex) this.element.style.zIndex = this.originalZ; if(this.options.endeffect) this.options.endeffect(this.element); Draggables.deactivate(this); Droppables.reset(); }, keyPress: function(event) { if(event.keyCode!=Event.KEY_ESC) return; this.finishDrag(event, false); Event.stop(event); }, endDrag: function(event) { if(!this.dragging) return; this.stopScrolling(); this.finishDrag(event, true); Event.stop(event); }, draw: function(point) { var pos = Position.cumulativeOffset(this.element); if(this.options.ghosting) { var r = Position.realOffset(this.element); pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; } var d = this.currentDelta(); pos[0] -= d[0]; pos[1] -= d[1]; if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; } var p = [0,1].map(function(i){ return (point[i]-pos[i]-this.offset[i]) }.bind(this)); if(this.options.snap) { if(Object.isFunction(this.options.snap)) { p = this.options.snap(p[0],p[1],this); } else { if(Object.isArray(this.options.snap)) { p = p.map( function(v, i) { return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)) } else { p = p.map( function(v) { return (v/this.options.snap).round()*this.options.snap }.bind(this)) } }} var style = this.element.style; if((!this.options.constraint) || (this.options.constraint=='horizontal')) style.left = p[0] + "px"; if((!this.options.constraint) || (this.options.constraint=='vertical')) style.top = p[1] + "px"; if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering }, stopScrolling: function() { if(this.scrollInterval) { clearInterval(this.scrollInterval); this.scrollInterval = null; Draggables._lastScrollPointer = null; } }, startScrolling: function(speed) { if(!(speed[0] || speed[1])) return; this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; this.lastScrolled = new Date(); this.scrollInterval = setInterval(this.scroll.bind(this), 10); }, scroll: function() { var current = new Date(); var delta = current - this.lastScrolled; this.lastScrolled = current; if(this.options.scroll == window) { with (this._getWindowScroll(this.options.scroll)) { if (this.scrollSpeed[0] || this.scrollSpeed[1]) { var d = delta / 1000; this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); } } } else { this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; } Position.prepare(); Droppables.show(Draggables._lastPointer, this.element); Draggables.notify('onDrag', this); if (this._isScrollChild) { Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; if (Draggables._lastScrollPointer[0] < 0) Draggables._lastScrollPointer[0] = 0; if (Draggables._lastScrollPointer[1] < 0) Draggables._lastScrollPointer[1] = 0; this.draw(Draggables._lastScrollPointer); } if(this.options.change) this.options.change(this); }, _getWindowScroll: function(w) { var T, L, W, H; with (w.document) { if (w.document.documentElement && documentElement.scrollTop) { T = documentElement.scrollTop; L = documentElement.scrollLeft; } else if (w.document.body) { T = body.scrollTop; L = body.scrollLeft; } if (w.innerWidth) { W = w.innerWidth; H = w.innerHeight; } else if (w.document.documentElement && documentElement.clientWidth) { W = documentElement.clientWidth; H = documentElement.clientHeight; } else { W = body.offsetWidth; H = body.offsetHeight } } return { top: T, left: L, width: W, height: H }; } }); Draggable._dragging = { }; /*--------------------------------------------------------------------------*/ var SortableObserver = Class.create({ initialize: function(element, observer) { this.element = $(element); this.observer = observer; this.lastValue = Sortable.serialize(this.element); }, onStart: function() { this.lastValue = Sortable.serialize(this.element); }, onEnd: function() { Sortable.unmark(); if(this.lastValue != Sortable.serialize(this.element)) this.observer(this.element) } }); var Sortable = { SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, sortables: { }, _findRootElement: function(element) { while (element.tagName.toUpperCase() != "BODY") { if(element.id && Sortable.sortables[element.id]) return element; element = element.parentNode; } }, options: function(element) { element = Sortable._findRootElement($(element)); if(!element) return; return Sortable.sortables[element.id]; }, destroy: function(element){ var s = Sortable.options(element); if(s) { Draggables.removeObserver(s.element); s.droppables.each(function(d){ Droppables.remove(d) }); s.draggables.invoke('destroy'); delete Sortable.sortables[s.element.id]; } }, create: function(element) { element = $(element); var options = Object.extend({ element: element, tag: 'li', // assumes li children, override with tag: 'tagname' dropOnEmpty: false, tree: false, treeTag: 'ul', overlap: 'vertical', // one of 'vertical', 'horizontal' constraint: 'vertical', // one of 'vertical', 'horizontal', false containment: element, // also takes array of elements (or id's); or false handle: false, // or a CSS class only: false, delay: 0, hoverclass: null, ghosting: false, quiet: false, scroll: false, scrollSensitivity: 20, scrollSpeed: 15, format: this.SERIALIZE_RULE, // these take arrays of elements or ids and can be // used for better initialization performance elements: false, handles: false, onChange: Prototype.emptyFunction, onUpdate: Prototype.emptyFunction }, arguments[1] || { }); // clear any old sortable with same element this.destroy(element); // build options for the draggables var options_for_draggable = { revert: true, quiet: options.quiet, scroll: options.scroll, scrollSpeed: options.scrollSpeed, scrollSensitivity: options.scrollSensitivity, delay: options.delay, ghosting: options.ghosting, constraint: options.constraint, handle: options.handle }; if(options.starteffect) options_for_draggable.starteffect = options.starteffect; if(options.reverteffect) options_for_draggable.reverteffect = options.reverteffect; else if(options.ghosting) options_for_draggable.reverteffect = function(element) { element.style.top = 0; element.style.left = 0; }; if(options.endeffect) options_for_draggable.endeffect = options.endeffect; if(options.zindex) options_for_draggable.zindex = options.zindex; // build options for the droppables var options_for_droppable = { overlap: options.overlap, containment: options.containment, tree: options.tree, hoverclass: options.hoverclass, onHover: Sortable.onHover } var options_for_tree = { onHover: Sortable.onEmptyHover, overlap: options.overlap, containment: options.containment, hoverclass: options.hoverclass } // fix for gecko engine Element.cleanWhitespace(element); options.draggables = []; options.droppables = []; // drop on empty handling if(options.dropOnEmpty || options.tree) { Droppables.add(element, options_for_tree); options.droppables.push(element); } (options.elements || this.findElements(element, options) || []).each( function(e,i) { var handle = options.handles ? $(options.handles[i]) : (options.handle ? $(e).select('.' + options.handle)[0] : e); options.draggables.push( new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); Droppables.add(e, options_for_droppable); if(options.tree) e.treeNode = element; options.droppables.push(e); }); if(options.tree) { (Sortable.findTreeElements(element, options) || []).each( function(e) { Droppables.add(e, options_for_tree); e.treeNode = element; options.droppables.push(e); }); } // keep reference this.sortables[element.id] = options; // for onupdate Draggables.addObserver(new SortableObserver(element, options.onUpdate)); }, // return all suitable-for-sortable elements in a guaranteed order findElements: function(element, options) { return Element.findChildren( element, options.only, options.tree ? true : false, options.tag); }, findTreeElements: function(element, options) { return Element.findChildren( element, options.only, options.tree ? true : false, options.treeTag); }, onHover: function(element, dropon, overlap) { if(Element.isParent(dropon, element)) return; if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { return; } else if(overlap>0.5) { Sortable.mark(dropon, 'before'); if(dropon.previousSibling != element) { var oldParentNode = element.parentNode; element.style.visibility = "hidden"; // fix gecko rendering dropon.parentNode.insertBefore(element, dropon); if(dropon.parentNode!=oldParentNode) Sortable.options(oldParentNode).onChange(element); Sortable.options(dropon.parentNode).onChange(element); } } else { Sortable.mark(dropon, 'after'); var nextElement = dropon.nextSibling || null; if(nextElement != element) { var oldParentNode = element.parentNode; element.style.visibility = "hidden"; // fix gecko rendering dropon.parentNode.insertBefore(element, nextElement); if(dropon.parentNode!=oldParentNode) Sortable.options(oldParentNode).onChange(element); Sortable.options(dropon.parentNode).onChange(element); } } }, onEmptyHover: function(element, dropon, overlap) { var oldParentNode = element.parentNode; var droponOptions = Sortable.options(dropon); if(!Element.isParent(dropon, element)) { var index; var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); var child = null; if(children) { var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); for (index = 0; index < children.length; index += 1) { if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { offset -= Element.offsetSize (children[index], droponOptions.overlap); } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { child = index + 1 < children.length ? children[index + 1] : null; break; } else { child = children[index]; break; } } } dropon.insertBefore(element, child); Sortable.options(oldParentNode).onChange(element); droponOptions.onChange(element); } }, unmark: function() { if(Sortable._marker) Sortable._marker.hide(); }, mark: function(dropon, position) { // mark on ghosting only var sortable = Sortable.options(dropon.parentNode); if(sortable && !sortable.ghosting) return; if(!Sortable._marker) { Sortable._marker = ($('dropmarker') || Element.extend(document.createElement('DIV'))). hide().addClassName('dropmarker').setStyle({position:'absolute'}); document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); } var offsets = Position.cumulativeOffset(dropon); Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); if(position=='after') if(sortable.overlap == 'horizontal') Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); else Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); Sortable._marker.show(); }, _tree: function(element, options, parent) { var children = Sortable.findElements(element, options) || []; for (var i = 0; i < children.length; ++i) { var match = children[i].id.match(options.format); if (!match) continue; var child = { id: encodeURIComponent(match ? match[1] : null), element: element, parent: parent, children: [], position: parent.children.length, container: $(children[i]).down(options.treeTag) } /* Get the element containing the children and recurse over it */ if (child.container) this._tree(child.container, options, child) parent.children.push (child); } return parent; }, tree: function(element) { element = $(element); var sortableOptions = this.options(element); var options = Object.extend({ tag: sortableOptions.tag, treeTag: sortableOptions.treeTag, only: sortableOptions.only, name: element.id, format: sortableOptions.format }, arguments[1] || { }); var root = { id: null, parent: null, children: [], container: element, position: 0 } return Sortable._tree(element, options, root); }, /* Construct a [i] index for a particular node */ _constructIndex: function(node) { var index = ''; do { if (node.id) index = '[' + node.position + ']' + index; } while ((node = node.parent) != null); return index; }, sequence: function(element) { element = $(element); var options = Object.extend(this.options(element), arguments[1] || { }); return $(this.findElements(element, options) || []).map( function(item) { return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; }); }, setSequence: function(element, new_sequence) { element = $(element); var options = Object.extend(this.options(element), arguments[2] || { }); var nodeMap = { }; this.findElements(element, options).each( function(n) { if (n.id.match(options.format)) nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; n.parentNode.removeChild(n); }); new_sequence.each(function(ident) { var n = nodeMap[ident]; if (n) { n[1].appendChild(n[0]); delete nodeMap[ident]; } }); }, serialize: function(element) { element = $(element); var options = Object.extend(Sortable.options(element), arguments[1] || { }); var name = encodeURIComponent( (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); if (options.tree) { return Sortable.tree(element, arguments[1]).children.map( function (item) { return [name + Sortable._constructIndex(item) + "[id]=" + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); }).flatten().join('&'); } else { return Sortable.sequence(element, arguments[1]).map( function(item) { return name + "[]=" + encodeURIComponent(item); }).join('&'); } } } // Returns true if child is contained within element Element.isParent = function(child, element) { if (!child.parentNode || child == element) return false; if (child.parentNode == element) return true; return Element.isParent(child.parentNode, element); } Element.findChildren = function(element, only, recursive, tagName) { if(!element.hasChildNodes()) return null; tagName = tagName.toUpperCase(); if(only) only = [only].flatten(); var elements = []; $A(element.childNodes).each( function(e) { if(e.tagName && e.tagName.toUpperCase()==tagName && (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) elements.push(e); if(recursive) { var grandchildren = Element.findChildren(e, only, recursive, tagName); if(grandchildren) elements.push(grandchildren); } }); return (elements.length>0 ? elements.flatten() : []); } Element.offsetSize = function (element, type) { return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; } thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/public/javascripts/effects.js000066400000000000000000001140041475433514000300500ustar00rootroot00000000000000// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // Contributors: // Justin Palmer (http://encytemedia.com/) // Mark Pilgrim (http://diveintomark.org/) // Martin Bialasinki // // script.aculo.us is freely distributable under the terms of an MIT-style license. // For details, see the script.aculo.us web site: http://script.aculo.us/ // converts rgb() and #xxx to #xxxxxx format, // returns self (or first argument) if not convertable String.prototype.parseColor = function() { var color = '#'; if (this.slice(0,4) == 'rgb(') { var cols = this.slice(4,this.length-1).split(','); var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); } else { if (this.slice(0,1) == '#') { if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); if (this.length==7) color = this.toLowerCase(); } } return (color.length==7 ? color : (arguments[0] || this)); }; /*--------------------------------------------------------------------------*/ Element.collectTextNodes = function(element) { return $A($(element).childNodes).collect( function(node) { return (node.nodeType==3 ? node.nodeValue : (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); }).flatten().join(''); }; Element.collectTextNodesIgnoreClass = function(element, className) { return $A($(element).childNodes).collect( function(node) { return (node.nodeType==3 ? node.nodeValue : ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? Element.collectTextNodesIgnoreClass(node, className) : '')); }).flatten().join(''); }; Element.setContentZoom = function(element, percent) { element = $(element); element.setStyle({fontSize: (percent/100) + 'em'}); if (Prototype.Browser.WebKit) window.scrollBy(0,0); return element; }; Element.getInlineOpacity = function(element){ return $(element).style.opacity || ''; }; Element.forceRerendering = function(element) { try { element = $(element); var n = document.createTextNode(' '); element.appendChild(n); element.removeChild(n); } catch(e) { } }; /*--------------------------------------------------------------------------*/ var Effect = { _elementDoesNotExistError: { name: 'ElementDoesNotExistError', message: 'The specified DOM element does not exist, but is required for this effect to operate' }, Transitions: { linear: Prototype.K, sinoidal: function(pos) { return (-Math.cos(pos*Math.PI)/2) + 0.5; }, reverse: function(pos) { return 1-pos; }, flicker: function(pos) { var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; return pos > 1 ? 1 : pos; }, wobble: function(pos) { return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; }, pulse: function(pos, pulses) { pulses = pulses || 5; return ( ((pos % (1/pulses)) * pulses).round() == 0 ? ((pos * pulses * 2) - (pos * pulses * 2).floor()) : 1 - ((pos * pulses * 2) - (pos * pulses * 2).floor()) ); }, spring: function(pos) { return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); }, none: function(pos) { return 0; }, full: function(pos) { return 1; } }, DefaultOptions: { duration: 1.0, // seconds fps: 100, // 100= assume 66fps max. sync: false, // true for combining from: 0.0, to: 1.0, delay: 0.0, queue: 'parallel' }, tagifyText: function(element) { var tagifyStyle = 'position:relative'; if (Prototype.Browser.IE) tagifyStyle += ';zoom:1'; element = $(element); $A(element.childNodes).each( function(child) { if (child.nodeType==3) { child.nodeValue.toArray().each( function(character) { element.insertBefore( new Element('span', {style: tagifyStyle}).update( character == ' ' ? String.fromCharCode(160) : character), child); }); Element.remove(child); } }); }, multiple: function(element, effect) { var elements; if (((typeof element == 'object') || Object.isFunction(element)) && (element.length)) elements = element; else elements = $(element).childNodes; var options = Object.extend({ speed: 0.1, delay: 0.0 }, arguments[2] || { }); var masterDelay = options.delay; $A(elements).each( function(element, index) { new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); }); }, PAIRS: { 'slide': ['SlideDown','SlideUp'], 'blind': ['BlindDown','BlindUp'], 'appear': ['Appear','Fade'] }, toggle: function(element, effect) { 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); } }; Effect.DefaultOptions.transition = Effect.Transitions.sinoidal; /* ------------- core effects ------------- */ Effect.ScopedQueue = Class.create(Enumerable, { initialize: function() { this.effects = []; this.interval = null; }, _each: function(iterator) { this.effects._each(iterator); }, add: function(effect) { var timestamp = new Date().getTime(); var position = Object.isString(effect.options.queue) ? effect.options.queue : effect.options.queue.position; switch(position) { case 'front': // move unstarted effects after this effect this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { e.startOn += effect.finishOn; e.finishOn += effect.finishOn; }); break; case 'with-last': timestamp = this.effects.pluck('startOn').max() || timestamp; break; case 'end': // start effect after last queued effect has finished timestamp = this.effects.pluck('finishOn').max() || timestamp; break; } effect.startOn += timestamp; effect.finishOn += timestamp; if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) this.effects.push(effect); if (!this.interval) this.interval = setInterval(this.loop.bind(this), 15); }, remove: function(effect) { this.effects = this.effects.reject(function(e) { return e==effect }); if (this.effects.length == 0) { clearInterval(this.interval); this.interval = null; } }, loop: function() { var timePos = new Date().getTime(); for(var i=0, len=this.effects.length;i= this.startOn) { if (timePos >= this.finishOn) { this.render(1.0); this.cancel(); this.event('beforeFinish'); if (this.finish) this.finish(); this.event('afterFinish'); return; } var pos = (timePos - this.startOn) / this.totalTime, frame = (pos * this.totalFrames).round(); if (frame > this.currentFrame) { this.render(pos); this.currentFrame = frame; } } }, cancel: function() { if (!this.options.sync) Effect.Queues.get(Object.isString(this.options.queue) ? 'global' : this.options.queue.scope).remove(this); this.state = 'finished'; }, event: function(eventName) { if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); if (this.options[eventName]) this.options[eventName](this); }, inspect: function() { var data = $H(); for(property in this) if (!Object.isFunction(this[property])) data.set(property, this[property]); return '#'; } }); Effect.Parallel = Class.create(Effect.Base, { initialize: function(effects) { this.effects = effects || []; this.start(arguments[1]); }, update: function(position) { this.effects.invoke('render', position); }, finish: function(position) { this.effects.each( function(effect) { effect.render(1.0); effect.cancel(); effect.event('beforeFinish'); if (effect.finish) effect.finish(position); effect.event('afterFinish'); }); } }); Effect.Tween = Class.create(Effect.Base, { initialize: function(object, from, to) { object = Object.isString(object) ? $(object) : object; var args = $A(arguments), method = args.last(), options = args.length == 5 ? args[3] : null; this.method = Object.isFunction(method) ? method.bind(object) : Object.isFunction(object[method]) ? object[method].bind(object) : function(value) { object[method] = value }; this.start(Object.extend({ from: from, to: to }, options || { })); }, update: function(position) { this.method(position); } }); Effect.Event = Class.create(Effect.Base, { initialize: function() { this.start(Object.extend({ duration: 0 }, arguments[0] || { })); }, update: Prototype.emptyFunction }); Effect.Opacity = Class.create(Effect.Base, { initialize: function(element) { this.element = $(element); if (!this.element) throw(Effect._elementDoesNotExistError); // make this work on IE on elements without 'layout' if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) this.element.setStyle({zoom: 1}); var options = Object.extend({ from: this.element.getOpacity() || 0.0, to: 1.0 }, arguments[1] || { }); this.start(options); }, update: function(position) { this.element.setOpacity(position); } }); Effect.Move = Class.create(Effect.Base, { initialize: function(element) { this.element = $(element); if (!this.element) throw(Effect._elementDoesNotExistError); var options = Object.extend({ x: 0, y: 0, mode: 'relative' }, arguments[1] || { }); this.start(options); }, setup: function() { this.element.makePositioned(); this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); this.originalTop = parseFloat(this.element.getStyle('top') || '0'); if (this.options.mode == 'absolute') { this.options.x = this.options.x - this.originalLeft; this.options.y = this.options.y - this.originalTop; } }, update: function(position) { this.element.setStyle({ left: (this.options.x * position + this.originalLeft).round() + 'px', top: (this.options.y * position + this.originalTop).round() + 'px' }); } }); // for backwards compatibility Effect.MoveBy = function(element, toTop, toLeft) { return new Effect.Move(element, Object.extend({ x: toLeft, y: toTop }, arguments[3] || { })); }; Effect.Scale = Class.create(Effect.Base, { initialize: function(element, percent) { this.element = $(element); if (!this.element) throw(Effect._elementDoesNotExistError); var options = Object.extend({ scaleX: true, scaleY: true, scaleContent: true, scaleFromCenter: false, scaleMode: 'box', // 'box' or 'contents' or { } with provided values scaleFrom: 100.0, scaleTo: percent }, arguments[2] || { }); this.start(options); }, setup: function() { this.restoreAfterFinish = this.options.restoreAfterFinish || false; this.elementPositioning = this.element.getStyle('position'); this.originalStyle = { }; ['top','left','width','height','fontSize'].each( function(k) { this.originalStyle[k] = this.element.style[k]; }.bind(this)); this.originalTop = this.element.offsetTop; this.originalLeft = this.element.offsetLeft; var fontSize = this.element.getStyle('font-size') || '100%'; ['em','px','%','pt'].each( function(fontSizeType) { if (fontSize.indexOf(fontSizeType)>0) { this.fontSize = parseFloat(fontSize); this.fontSizeType = fontSizeType; } }.bind(this)); this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; this.dims = null; if (this.options.scaleMode=='box') this.dims = [this.element.offsetHeight, this.element.offsetWidth]; if (/^content/.test(this.options.scaleMode)) this.dims = [this.element.scrollHeight, this.element.scrollWidth]; if (!this.dims) this.dims = [this.options.scaleMode.originalHeight, this.options.scaleMode.originalWidth]; }, update: function(position) { var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); if (this.options.scaleContent && this.fontSize) this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); }, finish: function(position) { if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); }, setDimensions: function(height, width) { var d = { }; if (this.options.scaleX) d.width = width.round() + 'px'; if (this.options.scaleY) d.height = height.round() + 'px'; if (this.options.scaleFromCenter) { var topd = (height - this.dims[0])/2; var leftd = (width - this.dims[1])/2; if (this.elementPositioning == 'absolute') { if (this.options.scaleY) d.top = this.originalTop-topd + 'px'; if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; } else { if (this.options.scaleY) d.top = -topd + 'px'; if (this.options.scaleX) d.left = -leftd + 'px'; } } this.element.setStyle(d); } }); Effect.Highlight = Class.create(Effect.Base, { initialize: function(element) { this.element = $(element); if (!this.element) throw(Effect._elementDoesNotExistError); var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { }); this.start(options); }, setup: function() { // Prevent executing on elements not in the layout flow if (this.element.getStyle('display')=='none') { this.cancel(); return; } // Disable background image during the effect this.oldStyle = { }; if (!this.options.keepBackgroundImage) { this.oldStyle.backgroundImage = this.element.getStyle('background-image'); this.element.setStyle({backgroundImage: 'none'}); } if (!this.options.endcolor) this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); if (!this.options.restorecolor) this.options.restorecolor = this.element.getStyle('background-color'); // init color calculations this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); }, update: function(position) { this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) }); }, finish: function() { this.element.setStyle(Object.extend(this.oldStyle, { backgroundColor: this.options.restorecolor })); } }); Effect.ScrollTo = function(element) { var options = arguments[1] || { }, scrollOffsets = document.viewport.getScrollOffsets(), elementOffsets = $(element).cumulativeOffset(), max = (window.height || document.body.scrollHeight) - document.viewport.getHeight(); if (options.offset) elementOffsets[1] += options.offset; return new Effect.Tween(null, scrollOffsets.top, elementOffsets[1] > max ? max : elementOffsets[1], options, function(p){ scrollTo(scrollOffsets.left, p.round()) } ); }; /* ------------- combination effects ------------- */ Effect.Fade = function(element) { element = $(element); var oldOpacity = element.getInlineOpacity(); var options = Object.extend({ from: element.getOpacity() || 1.0, to: 0.0, afterFinishInternal: function(effect) { if (effect.options.to!=0) return; effect.element.hide().setStyle({opacity: oldOpacity}); } }, arguments[1] || { }); return new Effect.Opacity(element,options); }; Effect.Appear = function(element) { element = $(element); var options = Object.extend({ from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), to: 1.0, // force Safari to render floated elements properly afterFinishInternal: function(effect) { effect.element.forceRerendering(); }, beforeSetup: function(effect) { effect.element.setOpacity(effect.options.from).show(); }}, arguments[1] || { }); return new Effect.Opacity(element,options); }; Effect.Puff = function(element) { element = $(element); var oldStyle = { opacity: element.getInlineOpacity(), position: element.getStyle('position'), top: element.style.top, left: element.style.left, width: element.style.width, height: element.style.height }; return new Effect.Parallel( [ new Effect.Scale(element, 200, { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], Object.extend({ duration: 1.0, beforeSetupInternal: function(effect) { Position.absolutize(effect.effects[0].element) }, afterFinishInternal: function(effect) { effect.effects[0].element.hide().setStyle(oldStyle); } }, arguments[1] || { }) ); }; Effect.BlindUp = function(element) { element = $(element); element.makeClipping(); return new Effect.Scale(element, 0, Object.extend({ scaleContent: false, scaleX: false, restoreAfterFinish: true, afterFinishInternal: function(effect) { effect.element.hide().undoClipping(); } }, arguments[1] || { }) ); }; Effect.BlindDown = function(element) { element = $(element); var elementDimensions = element.getDimensions(); return new Effect.Scale(element, 100, Object.extend({ scaleContent: false, scaleX: false, scaleFrom: 0, scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, restoreAfterFinish: true, afterSetup: function(effect) { effect.element.makeClipping().setStyle({height: '0px'}).show(); }, afterFinishInternal: function(effect) { effect.element.undoClipping(); } }, arguments[1] || { })); }; Effect.SwitchOff = function(element) { element = $(element); var oldOpacity = element.getInlineOpacity(); return new Effect.Appear(element, Object.extend({ duration: 0.4, from: 0, transition: Effect.Transitions.flicker, afterFinishInternal: function(effect) { new Effect.Scale(effect.element, 1, { duration: 0.3, scaleFromCenter: true, scaleX: false, scaleContent: false, restoreAfterFinish: true, beforeSetup: function(effect) { effect.element.makePositioned().makeClipping(); }, afterFinishInternal: function(effect) { effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); } }) } }, arguments[1] || { })); }; Effect.DropOut = function(element) { element = $(element); var oldStyle = { top: element.getStyle('top'), left: element.getStyle('left'), opacity: element.getInlineOpacity() }; return new Effect.Parallel( [ new Effect.Move(element, {x: 0, y: 100, sync: true }), new Effect.Opacity(element, { sync: true, to: 0.0 }) ], Object.extend( { duration: 0.5, beforeSetup: function(effect) { effect.effects[0].element.makePositioned(); }, afterFinishInternal: function(effect) { effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); } }, arguments[1] || { })); }; Effect.Shake = function(element) { element = $(element); var options = Object.extend({ distance: 20, duration: 0.5 }, arguments[1] || {}); var distance = parseFloat(options.distance); var split = parseFloat(options.duration) / 10.0; var oldStyle = { top: element.getStyle('top'), left: element.getStyle('left') }; return new Effect.Move(element, { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) { new Effect.Move(effect.element, { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { new Effect.Move(effect.element, { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { new Effect.Move(effect.element, { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { new Effect.Move(effect.element, { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { new Effect.Move(effect.element, { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) { effect.element.undoPositioned().setStyle(oldStyle); }}) }}) }}) }}) }}) }}); }; Effect.SlideDown = function(element) { element = $(element).cleanWhitespace(); // SlideDown need to have the content of the element wrapped in a container element with fixed height! var oldInnerBottom = element.down().getStyle('bottom'); var elementDimensions = element.getDimensions(); return new Effect.Scale(element, 100, Object.extend({ scaleContent: false, scaleX: false, scaleFrom: window.opera ? 0 : 1, scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, restoreAfterFinish: true, afterSetup: function(effect) { effect.element.makePositioned(); effect.element.down().makePositioned(); if (window.opera) effect.element.setStyle({top: ''}); effect.element.makeClipping().setStyle({height: '0px'}).show(); }, afterUpdateInternal: function(effect) { effect.element.down().setStyle({bottom: (effect.dims[0] - effect.element.clientHeight) + 'px' }); }, afterFinishInternal: function(effect) { effect.element.undoClipping().undoPositioned(); effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } }, arguments[1] || { }) ); }; Effect.SlideUp = function(element) { element = $(element).cleanWhitespace(); var oldInnerBottom = element.down().getStyle('bottom'); var elementDimensions = element.getDimensions(); return new Effect.Scale(element, window.opera ? 0 : 1, Object.extend({ scaleContent: false, scaleX: false, scaleMode: 'box', scaleFrom: 100, scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, restoreAfterFinish: true, afterSetup: function(effect) { effect.element.makePositioned(); effect.element.down().makePositioned(); if (window.opera) effect.element.setStyle({top: ''}); effect.element.makeClipping().show(); }, afterUpdateInternal: function(effect) { effect.element.down().setStyle({bottom: (effect.dims[0] - effect.element.clientHeight) + 'px' }); }, afterFinishInternal: function(effect) { effect.element.hide().undoClipping().undoPositioned(); effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } }, arguments[1] || { }) ); }; // Bug in opera makes the TD containing this element expand for a instance after finish Effect.Squish = function(element) { return new Effect.Scale(element, window.opera ? 1 : 0, { restoreAfterFinish: true, beforeSetup: function(effect) { effect.element.makeClipping(); }, afterFinishInternal: function(effect) { effect.element.hide().undoClipping(); } }); }; Effect.Grow = function(element) { element = $(element); var options = Object.extend({ direction: 'center', moveTransition: Effect.Transitions.sinoidal, scaleTransition: Effect.Transitions.sinoidal, opacityTransition: Effect.Transitions.full }, arguments[1] || { }); var oldStyle = { top: element.style.top, left: element.style.left, height: element.style.height, width: element.style.width, opacity: element.getInlineOpacity() }; var dims = element.getDimensions(); var initialMoveX, initialMoveY; var moveX, moveY; switch (options.direction) { case 'top-left': initialMoveX = initialMoveY = moveX = moveY = 0; break; case 'top-right': initialMoveX = dims.width; initialMoveY = moveY = 0; moveX = -dims.width; break; case 'bottom-left': initialMoveX = moveX = 0; initialMoveY = dims.height; moveY = -dims.height; break; case 'bottom-right': initialMoveX = dims.width; initialMoveY = dims.height; moveX = -dims.width; moveY = -dims.height; break; case 'center': initialMoveX = dims.width / 2; initialMoveY = dims.height / 2; moveX = -dims.width / 2; moveY = -dims.height / 2; break; } return new Effect.Move(element, { x: initialMoveX, y: initialMoveY, duration: 0.01, beforeSetup: function(effect) { effect.element.hide().makeClipping().makePositioned(); }, afterFinishInternal: function(effect) { new Effect.Parallel( [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), new Effect.Scale(effect.element, 100, { scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) ], Object.extend({ beforeSetup: function(effect) { effect.effects[0].element.setStyle({height: '0px'}).show(); }, afterFinishInternal: function(effect) { effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); } }, options) ) } }); }; Effect.Shrink = function(element) { element = $(element); var options = Object.extend({ direction: 'center', moveTransition: Effect.Transitions.sinoidal, scaleTransition: Effect.Transitions.sinoidal, opacityTransition: Effect.Transitions.none }, arguments[1] || { }); var oldStyle = { top: element.style.top, left: element.style.left, height: element.style.height, width: element.style.width, opacity: element.getInlineOpacity() }; var dims = element.getDimensions(); var moveX, moveY; switch (options.direction) { case 'top-left': moveX = moveY = 0; break; case 'top-right': moveX = dims.width; moveY = 0; break; case 'bottom-left': moveX = 0; moveY = dims.height; break; case 'bottom-right': moveX = dims.width; moveY = dims.height; break; case 'center': moveX = dims.width / 2; moveY = dims.height / 2; break; } return new Effect.Parallel( [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) ], Object.extend({ beforeStartInternal: function(effect) { effect.effects[0].element.makePositioned().makeClipping(); }, afterFinishInternal: function(effect) { effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } }, options) ); }; Effect.Pulsate = function(element) { element = $(element); var options = arguments[1] || { }; var oldOpacity = element.getInlineOpacity(); var transition = options.transition || Effect.Transitions.sinoidal; var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) }; reverser.bind(transition); return new Effect.Opacity(element, Object.extend(Object.extend({ duration: 2.0, from: 0, afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } }, options), {transition: reverser})); }; Effect.Fold = function(element) { element = $(element); var oldStyle = { top: element.style.top, left: element.style.left, width: element.style.width, height: element.style.height }; element.makeClipping(); return new Effect.Scale(element, 5, Object.extend({ scaleContent: false, scaleX: false, afterFinishInternal: function(effect) { new Effect.Scale(element, 1, { scaleContent: false, scaleY: false, afterFinishInternal: function(effect) { effect.element.hide().undoClipping().setStyle(oldStyle); } }); }}, arguments[1] || { })); }; Effect.Morph = Class.create(Effect.Base, { initialize: function(element) { this.element = $(element); if (!this.element) throw(Effect._elementDoesNotExistError); var options = Object.extend({ style: { } }, arguments[1] || { }); if (!Object.isString(options.style)) this.style = $H(options.style); else { if (options.style.include(':')) this.style = options.style.parseStyle(); else { this.element.addClassName(options.style); this.style = $H(this.element.getStyles()); this.element.removeClassName(options.style); var css = this.element.getStyles(); this.style = this.style.reject(function(style) { return style.value == css[style.key]; }); options.afterFinishInternal = function(effect) { effect.element.addClassName(effect.options.style); effect.transforms.each(function(transform) { effect.element.style[transform.style] = ''; }); } } } this.start(options); }, setup: function(){ function parseColor(color){ if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; color = color.parseColor(); return $R(0,2).map(function(i){ return parseInt( color.slice(i*2+1,i*2+3), 16 ) }); } this.transforms = this.style.map(function(pair){ var property = pair[0], value = pair[1], unit = null; if (value.parseColor('#zzzzzz') != '#zzzzzz') { value = value.parseColor(); unit = 'color'; } else if (property == 'opacity') { value = parseFloat(value); if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) this.element.setStyle({zoom: 1}); } else if (Element.CSS_LENGTH.test(value)) { var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/); value = parseFloat(components[1]); unit = (components.length == 3) ? components[2] : null; } var originalValue = this.element.getStyle(property); return { style: property.camelize(), originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), targetValue: unit=='color' ? parseColor(value) : value, unit: unit }; }.bind(this)).reject(function(transform){ return ( (transform.originalValue == transform.targetValue) || ( transform.unit != 'color' && (isNaN(transform.originalValue) || isNaN(transform.targetValue)) ) ) }); }, update: function(position) { var style = { }, transform, i = this.transforms.length; while(i--) style[(transform = this.transforms[i]).style] = transform.unit=='color' ? '#'+ (Math.round(transform.originalValue[0]+ (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() + (Math.round(transform.originalValue[1]+ (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() + (Math.round(transform.originalValue[2]+ (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() : (transform.originalValue + (transform.targetValue - transform.originalValue) * position).toFixed(3) + (transform.unit === null ? '' : transform.unit); this.element.setStyle(style, true); } }); Effect.Transform = Class.create({ initialize: function(tracks){ this.tracks = []; this.options = arguments[1] || { }; this.addTracks(tracks); }, addTracks: function(tracks){ tracks.each(function(track){ track = $H(track); var data = track.values().first(); this.tracks.push($H({ ids: track.keys().first(), effect: Effect.Morph, options: { style: data } })); }.bind(this)); return this; }, play: function(){ return new Effect.Parallel( this.tracks.map(function(track){ var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options'); var elements = [$(ids) || $$(ids)].flatten(); return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) }); }).flatten(), this.options ); } }); Element.CSS_PROPERTIES = $w( 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' + 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' + 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' + 'fontSize fontWeight height left letterSpacing lineHeight ' + 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+ 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' + 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' + 'right textIndent top width wordSpacing zIndex'); Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; String.__parseStyleElement = document.createElement('div'); String.prototype.parseStyle = function(){ var style, styleRules = $H(); if (Prototype.Browser.WebKit) style = new Element('div',{style:this}).style; else { String.__parseStyleElement.innerHTML = '
    '; style = String.__parseStyleElement.childNodes[0].style; } Element.CSS_PROPERTIES.each(function(property){ if (style[property]) styleRules.set(property, style[property]); }); if (Prototype.Browser.IE && this.include('opacity')) styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]); return styleRules; }; if (document.defaultView && document.defaultView.getComputedStyle) { Element.getStyles = function(element) { var css = document.defaultView.getComputedStyle($(element), null); return Element.CSS_PROPERTIES.inject({ }, function(styles, property) { styles[property] = css[property]; return styles; }); }; } else { Element.getStyles = function(element) { element = $(element); var css = element.currentStyle, styles; styles = Element.CSS_PROPERTIES.inject({ }, function(hash, property) { hash.set(property, css[property]); return hash; }); if (!styles.opacity) styles.set('opacity', element.getOpacity()); return styles; }; }; Effect.Methods = { morph: function(element, style) { element = $(element); new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { })); return element; }, visualEffect: function(element, effect, options) { element = $(element) var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1); new Effect[klass](element, options); return element; }, highlight: function(element, options) { element = $(element); new Effect.Highlight(element, options); return element; } }; $w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+ 'pulsate shake puff squish switchOff dropOut').each( function(effect) { Effect.Methods[effect] = function(element, options){ element = $(element); Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options); return element; } } ); $w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( function(f) { Effect.Methods[f] = Element[f]; } ); Element.addMethods(Effect.Methods); thin-de6b6188f1f1cd7645948b266e3861f82c9c58a1/spec/rails_app/public/javascripts/prototype.js000066400000000000000000003652451475433514000305150ustar00rootroot00000000000000/* Prototype JavaScript framework, version 1.6.0.1 * (c) 2005-2007 Sam Stephenson * * Prototype is freely distributable under the terms of an MIT-style license. * For details, see the Prototype web site: http://www.prototypejs.org/ * *--------------------------------------------------------------------------*/ var Prototype = { Version: '1.6.0.1', Browser: { IE: !!(window.attachEvent && !window.opera), Opera: !!window.opera, WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1, MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/) }, BrowserFeatures: { XPath: !!document.evaluate, ElementExtensions: !!window.HTMLElement, SpecificElementExtensions: document.createElement('div').__proto__ && document.createElement('div').__proto__ !== document.createElement('form').__proto__ }, ScriptFragment: ']*>([\\S\\s]*?)<\/script>', JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, emptyFunction: function() { }, K: function(x) { return x } }; if (Prototype.Browser.MobileSafari) Prototype.BrowserFeatures.SpecificElementExtensions = false; /* Based on Alex Arnell's inheritance implementation. */ var Class = { create: function() { var parent = null, properties = $A(arguments); if (Object.isFunction(properties[0])) parent = properties.shift(); function klass() { this.initialize.apply(this, arguments); } Object.extend(klass, Class.Methods); klass.superclass = parent; klass.subclasses = []; if (parent) { var subclass = function() { }; subclass.prototype = parent.prototype; klass.prototype = new subclass; parent.subclasses.push(klass); } for (var i = 0; i < properties.length; i++) klass.addMethods(properties[i]); if (!klass.prototype.initialize) klass.prototype.initialize = Prototype.emptyFunction; klass.prototype.constructor = klass; return klass; } }; Class.Methods = { addMethods: function(source) { var ancestor = this.superclass && this.superclass.prototype; var properties = Object.keys(source); if (!Object.keys({ toString: true }).length) properties.push("toString", "valueOf"); for (var i = 0, length = properties.length; i < length; i++) { var property = properties[i], value = source[property]; if (ancestor && Object.isFunction(value) && value.argumentNames().first() == "$super") { var method = value, value = Object.extend((function(m) { return function() { return ancestor[m].apply(this, arguments) }; })(property).wrap(method), { valueOf: function() { return method }, toString: function() { return method.toString() } }); } this.prototype[property] = value; } return this; } }; var Abstract = { }; Object.extend = function(destination, source) { for (var property in source) destination[property] = source[property]; return destination; }; Object.extend(Object, { inspect: function(object) { try { if (Object.isUndefined(object)) return 'undefined'; if (object === null) return 'null'; return object.inspect ? object.inspect() : object.toString(); } catch (e) { if (e instanceof RangeError) return '...'; throw e; } }, toJSON: function(object) { var type = typeof object; switch (type) { case 'undefined': case 'function': case 'unknown': return; case 'boolean': return object.toString(); } if (object === null) return 'null'; if (object.toJSON) return object.toJSON(); if (Object.isElement(object)) return; var results = []; for (var property in object) { var value = Object.toJSON(object[property]); if (!Object.isUndefined(value)) results.push(property.toJSON() + ': ' + value); } return '{' + results.join(', ') + '}'; }, toQueryString: function(object) { return $H(object).toQueryString(); }, toHTML: function(object) { return object && object.toHTML ? object.toHTML() : String.interpret(object); }, keys: function(object) { var keys = []; for (var property in object) keys.push(property); return keys; }, values: function(object) { var values = []; for (var property in object) values.push(object[property]); return values; }, clone: function(object) { return Object.extend({ }, object); }, isElement: function(object) { return object && object.nodeType == 1; }, isArray: function(object) { return object && object.constructor === Array; }, isHash: function(object) { return object instanceof Hash; }, isFunction: function(object) { return typeof object == "function"; }, isString: function(object) { return typeof object == "string"; }, isNumber: function(object) { return typeof object == "number"; }, isUndefined: function(object) { return typeof object == "undefined"; } }); Object.extend(Function.prototype, { argumentNames: function() { var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip"); return names.length == 1 && !names[0] ? [] : names; }, bind: function() { if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; var __method = this, args = $A(arguments), object = args.shift(); return function() { return __method.apply(object, args.concat($A(arguments))); } }, bindAsEventListener: function() { var __method = this, args = $A(arguments), object = args.shift(); return function(event) { return __method.apply(object, [event || window.event].concat(args)); } }, curry: function() { if (!arguments.length) return this; var __method = this, args = $A(arguments); return function() { return __method.apply(this, args.concat($A(arguments))); } }, delay: function() { var __method = this, args = $A(arguments), timeout = args.shift() * 1000; return window.setTimeout(function() { return __method.apply(__method, args); }, timeout); }, wrap: function(wrapper) { var __method = this; return function() { return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); } }, methodize: function() { if (this._methodized) return this._methodized; var __method = this; return this._methodized = function() { return __method.apply(null, [this].concat($A(arguments))); }; } }); Function.prototype.defer = Function.prototype.delay.curry(0.01); Date.prototype.toJSON = function() { return '"' + this.getUTCFullYear() + '-' + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + this.getUTCDate().toPaddedString(2) + 'T' + this.getUTCHours().toPaddedString(2) + ':' + this.getUTCMinutes().toPaddedString(2) + ':' + this.getUTCSeconds().toPaddedString(2) + 'Z"'; }; var Try = { these: function() { var returnValue; for (var i = 0, length = arguments.length; i < length; i++) { var lambda = arguments[i]; try { returnValue = lambda(); break; } catch (e) { } } return returnValue; } }; RegExp.prototype.match = RegExp.prototype.test; RegExp.escape = function(str) { return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); }; /*--------------------------------------------------------------------------*/ var PeriodicalExecuter = Class.create({ initialize: function(callback, frequency) { this.callback = callback; this.frequency = frequency; this.currentlyExecuting = false; this.registerCallback(); }, registerCallback: function() { this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); }, execute: function() { this.callback(this); }, stop: function() { if (!this.timer) return; clearInterval(this.timer); this.timer = null; }, onTimerEvent: function() { if (!this.currentlyExecuting) { try { this.currentlyExecuting = true; this.execute(); } finally { this.currentlyExecuting = false; } } } }); Object.extend(String, { interpret: function(value) { return value == null ? '' : String(value); }, specialChar: { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '\\': '\\\\' } }); Object.extend(String.prototype, { gsub: function(pattern, replacement) { var result = '', source = this, match; replacement = arguments.callee.prepareReplacement(replacement); while (source.length > 0) { if (match = source.match(pattern)) { result += source.slice(0, match.index); result += String.interpret(replacement(match)); source = source.slice(match.index + match[0].length); } else { result += source, source = ''; } } return result; }, sub: function(pattern, replacement, count) { replacement = this.gsub.prepareReplacement(replacement); count = Object.isUndefined(count) ? 1 : count; return this.gsub(pattern, function(match) { if (--count < 0) return match[0]; return replacement(match); }); }, scan: function(pattern, iterator) { this.gsub(pattern, iterator); return String(this); }, truncate: function(length, truncation) { length = length || 30; truncation = Object.isUndefined(truncation) ? '...' : truncation; return this.length > length ? this.slice(0, length - truncation.length) + truncation : String(this); }, strip: function() { return this.replace(/^\s+/, '').replace(/\s+$/, ''); }, stripTags: function() { return this.replace(/<\/?[^>]+>/gi, ''); }, stripScripts: function() { return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); }, extractScripts: function() { var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); return (this.match(matchAll) || []).map(function(scriptTag) { return (scriptTag.match(matchOne) || ['', ''])[1]; }); }, evalScripts: function() { return this.extractScripts().map(function(script) { return eval(script) }); }, escapeHTML: function() { var self = arguments.callee; self.text.data = this; return self.div.innerHTML; }, unescapeHTML: function() { var div = new Element('div'); div.innerHTML = this.stripTags(); return div.childNodes[0] ? (div.childNodes.length > 1 ? $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : div.childNodes[0].nodeValue) : ''; }, toQueryParams: function(separator) { var match = this.strip().match(/([^?#]*)(#.*)?$/); if (!match) return { }; return match[1].split(separator || '&').inject({ }, function(hash, pair) { if ((pair = pair.split('='))[0]) { var key = decodeURIComponent(pair.shift()); var value = pair.length > 1 ? pair.join('=') : pair[0]; if (value != undefined) value = decodeURIComponent(value); if (key in hash) { if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; hash[key].push(value); } else hash[key] = value; } return hash; }); }, toArray: function() { return this.split(''); }, succ: function() { return this.slice(0, this.length - 1) + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); }, times: function(count) { return count < 1 ? '' : new Array(count + 1).join(this); }, camelize: function() { var parts = this.split('-'), len = parts.length; if (len == 1) return parts[0]; var camelized = this.charAt(0) == '-' ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) : parts[0]; for (var i = 1; i < len; i++) camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); return camelized; }, capitalize: function() { return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); }, underscore: function() { return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); }, dasherize: function() { return this.gsub(/_/,'-'); }, inspect: function(useDoubleQuotes) { var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { var character = String.specialChar[match[0]]; return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); }); if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; return "'" + escapedString.replace(/'/g, '\\\'') + "'"; }, toJSON: function() { return this.inspect(true); }, unfilterJSON: function(filter) { return this.sub(filter || Prototype.JSONFilter, '#{1}'); }, isJSON: function() { var str = this; if (str.blank()) return false; str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); }, evalJSON: function(sanitize) { var json = this.unfilterJSON(); try { if (!sanitize || json.isJSON()) return eval('(' + json + ')'); } catch (e) { } throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); }, include: function(pattern) { return this.indexOf(pattern) > -1; }, startsWith: function(pattern) { return this.indexOf(pattern) === 0; }, endsWith: function(pattern) { var d = this.length - pattern.length; return d >= 0 && this.lastIndexOf(pattern) === d; }, empty: function() { return this == ''; }, blank: function() { return /^\s*$/.test(this); }, interpolate: function(object, pattern) { return new Template(this, pattern).evaluate(object); } }); if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { escapeHTML: function() { return this.replace(/&/g,'&').replace(//g,'>'); }, unescapeHTML: function() { return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); } }); String.prototype.gsub.prepareReplacement = function(replacement) { if (Object.isFunction(replacement)) return replacement; var template = new Template(replacement); return function(match) { return template.evaluate(match) }; }; String.prototype.parseQuery = String.prototype.toQueryParams; Object.extend(String.prototype.escapeHTML, { div: document.createElement('div'), text: document.createTextNode('') }); with (String.prototype.escapeHTML) div.appendChild(text); var Template = Class.create({ initialize: function(template, pattern) { this.template = template.toString(); this.pattern = pattern || Template.Pattern; }, evaluate: function(object) { if (Object.isFunction(object.toTemplateReplacements)) object = object.toTemplateReplacements(); return this.template.gsub(this.pattern, function(match) { if (object == null) return ''; var before = match[1] || ''; if (before == '\\') return match[2]; var ctx = object, expr = match[3]; var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; match = pattern.exec(expr); if (match == null) return before; while (match != null) { var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; ctx = ctx[comp]; if (null == ctx || '' == match[3]) break; expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); match = pattern.exec(expr); } return before + String.interpret(ctx); }.bind(this)); } }); Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; var $break = { }; var Enumerable = { each: function(iterator, context) { var index = 0; iterator = iterator.bind(context); try { this._each(function(value) { iterator(value, index++); }); } catch (e) { if (e != $break) throw e; } return this; }, eachSlice: function(number, iterator, context) { iterator = iterator ? iterator.bind(context) : Prototype.K; var index = -number, slices = [], array = this.toArray(); while ((index += number) < array.length) slices.push(array.slice(index, index+number)); return slices.collect(iterator, context); }, all: function(iterator, context) { iterator = iterator ? iterator.bind(context) : Prototype.K; var result = true; this.each(function(value, index) { result = result && !!iterator(value, index); if (!result) throw $break; }); return result; }, any: function(iterator, context) { iterator = iterator ? iterator.bind(context) : Prototype.K; var result = false; this.each(function(value, index) { if (result = !!iterator(value, index)) throw $break; }); return result; }, collect: function(iterator, context) { iterator = iterator ? iterator.bind(context) : Prototype.K; var results = []; this.each(function(value, index) { results.push(iterator(value, index)); }); return results; }, detect: function(iterator, context) { iterator = iterator.bind(context); var result; this.each(function(value, index) { if (iterator(value, index)) { result = value; throw $break; } }); return result; }, findAll: function(iterator, context) { iterator = iterator.bind(context); var results = []; this.each(function(value, index) { if (iterator(value, index)) results.push(value); }); return results; }, grep: function(filter, iterator, context) { iterator = iterator ? iterator.bind(context) : Prototype.K; var results = []; if (Object.isString(filter)) filter = new RegExp(filter); this.each(function(value, index) { if (filter.match(value)) results.push(iterator(value, index)); }); return results; }, include: function(object) { if (Object.isFunction(this.indexOf)) if (this.indexOf(object) != -1) return true; var found = false; this.each(function(value) { if (value == object) { found = true; throw $break; } }); return found; }, inGroupsOf: function(number, fillWith) { fillWith = Object.isUndefined(fillWith) ? null : fillWith; return this.eachSlice(number, function(slice) { while(slice.length < number) slice.push(fillWith); return slice; }); }, inject: function(memo, iterator, context) { iterator = iterator.bind(context); this.each(function(value, index) { memo = iterator(memo, value, index); }); return memo; }, invoke: function(method) { var args = $A(arguments).slice(1); return this.map(function(value) { return value[method].apply(value, args); }); }, max: function(iterator, context) { iterator = iterator ? iterator.bind(context) : Prototype.K; var result; this.each(function(value, index) { value = iterator(value, index); if (result == null || value >= result) result = value; }); return result; }, min: function(iterator, context) { iterator = iterator ? iterator.bind(context) : Prototype.K; var result; this.each(function(value, index) { value = iterator(value, index); if (result == null || value < result) result = value; }); return result; }, partition: function(iterator, context) { iterator = iterator ? iterator.bind(context) : Prototype.K; var trues = [], falses = []; this.each(function(value, index) { (iterator(value, index) ? trues : falses).push(value); }); return [trues, falses]; }, pluck: function(property) { var results = []; this.each(function(value) { results.push(value[property]); }); return results; }, reject: function(iterator, context) { iterator = iterator.bind(context); var results = []; this.each(function(value, index) { if (!iterator(value, index)) results.push(value); }); return results; }, sortBy: function(iterator, context) { iterator = iterator.bind(context); return this.map(function(value, index) { return {value: value, criteria: iterator(value, index)}; }).sort(function(left, right) { var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; }).pluck('value'); }, toArray: function() { return this.map(); }, zip: function() { var iterator = Prototype.K, args = $A(arguments); if (Object.isFunction(args.last())) iterator = args.pop(); var collections = [this].concat(args).map($A); return this.map(function(value, index) { return iterator(collections.pluck(index)); }); }, size: function() { return this.toArray().length; }, inspect: function() { return '#'; } }; Object.extend(Enumerable, { map: Enumerable.collect, find: Enumerable.detect, select: Enumerable.findAll, filter: Enumerable.findAll, member: Enumerable.include, entries: Enumerable.toArray, every: Enumerable.all, some: Enumerable.any }); function $A(iterable) { if (!iterable) return []; if (iterable.toArray) return iterable.toArray(); var length = iterable.length, results = new Array(length); while (length--) results[length] = iterable[length]; return results; } if (Prototype.Browser.WebKit) { function $A(iterable) { if (!iterable) return []; if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') && iterable.toArray) return iterable.toArray(); var length = iterable.length, results = new Array(length); while (length--) results[length] = iterable[length]; return results; } } Array.from = $A; Object.extend(Array.prototype, Enumerable); if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse; Object.extend(Array.prototype, { _each: function(iterator) { for (var i = 0, length = this.length; i < length; i++) iterator(this[i]); }, clear: function() { this.length = 0; return this; }, first: function() { return this[0]; }, last: function() { return this[this.length - 1]; }, compact: function() { return this.select(function(value) { return value != null; }); }, flatten: function() { return this.inject([], function(array, value) { return array.concat(Object.isArray(value) ? value.flatten() : [value]); }); }, without: function() { var values = $A(arguments); return this.select(function(value) { return !values.include(value); }); }, reverse: function(inline) { return (inline !== false ? this : this.toArray())._reverse(); }, reduce: function() { return this.length > 1 ? this : this[0]; }, uniq: function(sorted) { return this.inject([], function(array, value, index) { if (0 == index || (sorted ? array.last() != value : !array.include(value))) array.push(value); return array; }); }, intersect: function(array) { return this.uniq().findAll(function(item) { return array.detect(function(value) { return item === value }); }); }, clone: function() { return [].concat(this); }, size: function() { return this.length; }, inspect: function() { return '[' + this.map(Object.inspect).join(', ') + ']'; }, toJSON: function() { var results = []; this.each(function(object) { var value = Object.toJSON(object); if (!Object.isUndefined(value)) results.push(value); }); return '[' + results.join(', ') + ']'; } }); // use native browser JS 1.6 implementation if available if (Object.isFunction(Array.prototype.forEach)) Array.prototype._each = Array.prototype.forEach; if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) { i || (i = 0); var length = this.length; if (i < 0) i = length + i; for (; i < length; i++) if (this[i] === item) return i; return -1; }; if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) { i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; var n = this.slice(0, i).reverse().indexOf(item); return (n < 0) ? n : i - n - 1; }; Array.prototype.toArray = Array.prototype.clone; function $w(string) { if (!Object.isString(string)) return []; string = string.strip(); return string ? string.split(/\s+/) : []; } if (Prototype.Browser.Opera){ Array.prototype.concat = function() { var array = []; for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); for (var i = 0, length = arguments.length; i < length; i++) { if (Object.isArray(arguments[i])) { for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) array.push(arguments[i][j]); } else { array.push(arguments[i]); } } return array; }; } Object.extend(Number.prototype, { toColorPart: function() { return this.toPaddedString(2, 16); }, succ: function() { return this + 1; }, times: function(iterator) { $R(0, this, true).each(iterator); return this; }, toPaddedString: function(length, radix) { var string = this.toString(radix || 10); return '0'.times(length - string.length) + string; }, toJSON: function() { return isFinite(this) ? this.toString() : 'null'; } }); $w('abs round ceil floor').each(function(method){ Number.prototype[method] = Math[method].methodize(); }); function $H(object) { return new Hash(object); }; var Hash = Class.create(Enumerable, (function() { function toQueryPair(key, value) { if (Object.isUndefined(value)) return key; return key + '=' + encodeURIComponent(String.interpret(value)); } return { initialize: function(object) { this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); }, _each: function(iterator) { for (var key in this._object) { var value = this._object[key], pair = [key, value]; pair.key = key; pair.value = value; iterator(pair); } }, set: function(key, value) { return this._object[key] = value; }, get: function(key) { return this._object[key]; }, unset: function(key) { var value = this._object[key]; delete this._object[key]; return value; }, toObject: function() { return Object.clone(this._object); }, keys: function() { return this.pluck('key'); }, values: function() { return this.pluck('value'); }, index: function(value) { var match = this.detect(function(pair) { return pair.value === value; }); return match && match.key; }, merge: function(object) { return this.clone().update(object); }, update: function(object) { return new Hash(object).inject(this, function(result, pair) { result.set(pair.key, pair.value); return result; }); }, toQueryString: function() { return this.map(function(pair) { var key = encodeURIComponent(pair.key), values = pair.value; if (values && typeof values == 'object') { if (Object.isArray(values)) return values.map(toQueryPair.curry(key)).join('&'); } return toQueryPair(key, values); }).join('&'); }, inspect: function() { return '#'; }, toJSON: function() { return Object.toJSON(this.toObject()); }, clone: function() { return new Hash(this); } } })()); Hash.prototype.toTemplateReplacements = Hash.prototype.toObject; Hash.from = $H; var ObjectRange = Class.create(Enumerable, { initialize: function(start, end, exclusive) { this.start = start; this.end = end; this.exclusive = exclusive; }, _each: function(iterator) { var value = this.start; while (this.include(value)) { iterator(value); value = value.succ(); } }, include: function(value) { if (value < this.start) return false; if (this.exclusive) return value < this.end; return value <= this.end; } }); var $R = function(start, end, exclusive) { return new ObjectRange(start, end, exclusive); }; var Ajax = { getTransport: function() { return Try.these( function() {return new XMLHttpRequest()}, function() {return new ActiveXObject('Msxml2.XMLHTTP')}, function() {return new ActiveXObject('Microsoft.XMLHTTP')} ) || false; }, activeRequestCount: 0 }; Ajax.Responders = { responders: [], _each: function(iterator) { this.responders._each(iterator); }, register: function(responder) { if (!this.include(responder)) this.responders.push(responder); }, unregister: function(responder) { this.responders = this.responders.without(responder); }, dispatch: function(callback, request, transport, json) { this.each(function(responder) { if (Object.isFunction(responder[callback])) { try { responder[callback].apply(responder, [request, transport, json]); } catch (e) { } } }); } }; Object.extend(Ajax.Responders, Enumerable); Ajax.Responders.register({ onCreate: function() { Ajax.activeRequestCount++ }, onComplete: function() { Ajax.activeRequestCount-- } }); Ajax.Base = Class.create({ initialize: function(options) { this.options = { method: 'post', asynchronous: true, contentType: 'application/x-www-form-urlencoded', encoding: 'UTF-8', parameters: '', evalJSON: true, evalJS: true }; Object.extend(this.options, options || { }); this.options.method = this.options.method.toLowerCase(); if (Object.isString(this.options.parameters)) this.options.parameters = this.options.parameters.toQueryParams(); else if (Object.isHash(this.options.parameters)) this.options.parameters = this.options.parameters.toObject(); } }); Ajax.Request = Class.create(Ajax.Base, { _complete: false, initialize: function($super, url, options) { $super(options); this.transport = Ajax.getTransport(); this.request(url); }, request: function(url) { this.url = url; this.method = this.options.method; var params = Object.clone(this.options.parameters); if (!['get', 'post'].include(this.method)) { // simulate other verbs over post params['_method'] = this.method; this.method = 'post'; } this.parameters = params; if (params = Object.toQueryString(params)) { // when GET, append parameters to URL if (this.method == 'get') this.url += (this.url.include('?') ? '&' : '?') + params; else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_='; } try { var response = new Ajax.Response(this); if (this.options.onCreate) this.options.onCreate(response); Ajax.Responders.dispatch('onCreate', this, response); this.transport.open(this.method.toUpperCase(), this.url, this.options.asynchronous); if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); this.transport.onreadystatechange = this.onStateChange.bind(this); this.setRequestHeaders(); this.body = this.method == 'post' ? (this.options.postBody || params) : null; this.transport.send(this.body); /* Force Firefox to handle ready state 4 for synchronous requests */ if (!this.options.asynchronous && this.transport.overrideMimeType) this.onStateChange(); } catch (e) { this.dispatchException(e); } }, onStateChange: function() { var readyState = this.transport.readyState; if (readyState > 1 && !((readyState == 4) && this._complete)) this.respondToReadyState(this.transport.readyState); }, setRequestHeaders: function() { var headers = { 'X-Requested-With': 'XMLHttpRequest', 'X-Prototype-Version': Prototype.Version, 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' }; if (this.method == 'post') { headers['Content-type'] = this.options.contentType + (this.options.encoding ? '; charset=' + this.options.encoding : ''); /* Force "Connection: close" for older Mozilla browsers to work * around a bug where XMLHttpRequest sends an incorrect * Content-length header. See Mozilla Bugzilla #246651. */ if (this.transport.overrideMimeType && (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) headers['Connection'] = 'close'; } // user-defined headers if (typeof this.options.requestHeaders == 'object') { var extras = this.options.requestHeaders; if (Object.isFunction(extras.push)) for (var i = 0, length = extras.length; i < length; i += 2) headers[extras[i]] = extras[i+1]; else $H(extras).each(function(pair) { headers[pair.key] = pair.value }); } for (var name in headers) this.transport.setRequestHeader(name, headers[name]); }, success: function() { var status = this.getStatus(); return !status || (status >= 200 && status < 300); }, getStatus: function() { try { return this.transport.status || 0; } catch (e) { return 0 } }, respondToReadyState: function(readyState) { var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); if (state == 'Complete') { try { this._complete = true; (this.options['on' + response.status] || this.options['on' + (this.success() ? 'Success' : 'Failure')] || Prototype.emptyFunction)(response, response.headerJSON); } catch (e) { this.dispatchException(e); } var contentType = response.getHeader('Content-type'); if (this.options.evalJS == 'force' || (this.options.evalJS && contentType && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) this.evalResponse(); } try { (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); } catch (e) { this.dispatchException(e); } if (state == 'Complete') { // avoid memory leak in MSIE: clean up this.transport.onreadystatechange = Prototype.emptyFunction; } }, getHeader: function(name) { try { return this.transport.getResponseHeader(name); } catch (e) { return null } }, evalResponse: function() { try { return eval((this.transport.responseText || '').unfilterJSON()); } catch (e) { this.dispatchException(e); } }, dispatchException: function(exception) { (this.options.onException || Prototype.emptyFunction)(this, exception); Ajax.Responders.dispatch('onException', this, exception); } }); Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; Ajax.Response = Class.create({ initialize: function(request){ this.request = request; var transport = this.transport = request.transport, readyState = this.readyState = transport.readyState; if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { this.status = this.getStatus(); this.statusText = this.getStatusText(); this.responseText = String.interpret(transport.responseText); this.headerJSON = this._getHeaderJSON(); } if(readyState == 4) { var xml = transport.responseXML; this.responseXML = Object.isUndefined(xml) ? null : xml; this.responseJSON = this._getResponseJSON(); } }, status: 0, statusText: '', getStatus: Ajax.Request.prototype.getStatus, getStatusText: function() { try { return this.transport.statusText || ''; } catch (e) { return '' } }, getHeader: Ajax.Request.prototype.getHeader, getAllHeaders: function() { try { return this.getAllResponseHeaders(); } catch (e) { return null } }, getResponseHeader: function(name) { return this.transport.getResponseHeader(name); }, getAllResponseHeaders: function() { return this.transport.getAllResponseHeaders(); }, _getHeaderJSON: function() { var json = this.getHeader('X-JSON'); if (!json) return null; json = decodeURIComponent(escape(json)); try { return json.evalJSON(this.request.options.sanitizeJSON); } catch (e) { this.request.dispatchException(e); } }, _getResponseJSON: function() { var options = this.request.options; if (!options.evalJSON || (options.evalJSON != 'force' && !(this.getHeader('Content-type') || '').include('application/json')) || this.responseText.blank()) return null; try { return this.responseText.evalJSON(options.sanitizeJSON); } catch (e) { this.request.dispatchException(e); } } }); Ajax.Updater = Class.create(Ajax.Request, { initialize: function($super, container, url, options) { this.container = { success: (container.success || container), failure: (container.failure || (container.success ? null : container)) }; options = Object.clone(options); var onComplete = options.onComplete; options.onComplete = (function(response, json) { this.updateContent(response.responseText); if (Object.isFunction(onComplete)) onComplete(response, json); }).bind(this); $super(url, options); }, updateContent: function(responseText) { var receiver = this.container[this.success() ? 'success' : 'failure'], options = this.options; if (!options.evalScripts) responseText = responseText.stripScripts(); if (receiver = $(receiver)) { if (options.insertion) { if (Object.isString(options.insertion)) { var insertion = { }; insertion[options.insertion] = responseText; receiver.insert(insertion); } else options.insertion(receiver, responseText); } else receiver.update(responseText); } } }); Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { initialize: function($super, container, url, options) { $super(options); this.onComplete = this.options.onComplete; this.frequency = (this.options.frequency || 2); this.decay = (this.options.decay || 1); this.updater = { }; this.container = container; this.url = url; this.start(); }, start: function() { this.options.onComplete = this.updateComplete.bind(this); this.onTimerEvent(); }, stop: function() { this.updater.options.onComplete = undefined; clearTimeout(this.timer); (this.onComplete || Prototype.emptyFunction).apply(this, arguments); }, updateComplete: function(response) { if (this.options.decay) { this.decay = (response.responseText == this.lastText ? this.decay * this.options.decay : 1); this.lastText = response.responseText; } this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); }, onTimerEvent: function() { this.updater = new Ajax.Updater(this.container, this.url, this.options); } }); function $(element) { if (arguments.length > 1) { for (var i = 0, elements = [], length = arguments.length; i < length; i++) elements.push($(arguments[i])); return elements; } if (Object.isString(element)) element = document.getElementById(element); return Element.extend(element); } if (Prototype.BrowserFeatures.XPath) { document._getElementsByXPath = function(expression, parentElement) { var results = []; var query = document.evaluate(expression, $(parentElement) || document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for (var i = 0, length = query.snapshotLength; i < length; i++) results.push(Element.extend(query.snapshotItem(i))); return results; }; } /*--------------------------------------------------------------------------*/ if (!window.Node) var Node = { }; if (!Node.ELEMENT_NODE) { // DOM level 2 ECMAScript Language Binding Object.extend(Node, { ELEMENT_NODE: 1, ATTRIBUTE_NODE: 2, TEXT_NODE: 3, CDATA_SECTION_NODE: 4, ENTITY_REFERENCE_NODE: 5, ENTITY_NODE: 6, PROCESSING_INSTRUCTION_NODE: 7, COMMENT_NODE: 8, DOCUMENT_NODE: 9, DOCUMENT_TYPE_NODE: 10, DOCUMENT_FRAGMENT_NODE: 11, NOTATION_NODE: 12 }); } (function() { var element = this.Element; this.Element = function(tagName, attributes) { attributes = attributes || { }; tagName = tagName.toLowerCase(); var cache = Element.cache; if (Prototype.Browser.IE && attributes.name) { tagName = '<' + tagName + ' name="' + attributes.name + '">'; delete attributes.name; return Element.writeAttribute(document.createElement(tagName), attributes); } if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); }; Object.extend(this.Element, element || { }); }).call(window); Element.cache = { }; Element.Methods = { visible: function(element) { return $(element).style.display != 'none'; }, toggle: function(element) { element = $(element); Element[Element.visible(element) ? 'hide' : 'show'](element); return element; }, hide: function(element) { $(element).style.display = 'none'; return element; }, show: function(element) { $(element).style.display = ''; return element; }, remove: function(element) { element = $(element); element.parentNode.removeChild(element); return element; }, update: function(element, content) { element = $(element); if (content && content.toElement) content = content.toElement(); if (Object.isElement(content)) return element.update().insert(content); content = Object.toHTML(content); element.innerHTML = content.stripScripts(); content.evalScripts.bind(content).defer(); return element; }, replace: function(element, content) { element = $(element); if (content && content.toElement) content = content.toElement(); else if (!Object.isElement(content)) { content = Object.toHTML(content); var range = element.ownerDocument.createRange(); range.selectNode(element); content.evalScripts.bind(content).defer(); content = range.createContextualFragment(content.stripScripts()); } element.parentNode.replaceChild(content, element); return element; }, insert: function(element, insertions) { element = $(element); if (Object.isString(insertions) || Object.isNumber(insertions) || Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) insertions = {bottom:insertions}; var content, t, range; for (position in insertions) { content = insertions[position]; position = position.toLowerCase(); t = Element._insertionTranslations[position]; if (content && content.toElement) content = content.toElement(); if (Object.isElement(content)) { t.insert(element, content); continue; } content = Object.toHTML(content); range = element.ownerDocument.createRange(); t.initializeRange(element, range); t.insert(element, range.createContextualFragment(content.stripScripts())); content.evalScripts.bind(content).defer(); } return element; }, wrap: function(element, wrapper, attributes) { element = $(element); if (Object.isElement(wrapper)) $(wrapper).writeAttribute(attributes || { }); else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); else wrapper = new Element('div', wrapper); if (element.parentNode) element.parentNode.replaceChild(wrapper, element); wrapper.appendChild(element); return wrapper; }, inspect: function(element) { element = $(element); var result = '<' + element.tagName.toLowerCase(); $H({'id': 'id', 'className': 'class'}).each(function(pair) { var property = pair.first(), attribute = pair.last(); var value = (element[property] || '').toString(); if (value) result += ' ' + attribute + '=' + value.inspect(true); }); return result + '>'; }, recursivelyCollect: function(element, property) { element = $(element); var elements = []; while (element = element[property]) if (element.nodeType == 1) elements.push(Element.extend(element)); return elements; }, ancestors: function(element) { return $(element).recursivelyCollect('parentNode'); }, descendants: function(element) { return $(element).getElementsBySelector("*"); }, firstDescendant: function(element) { element = $(element).firstChild; while (element && element.nodeType != 1) element = element.nextSibling; return $(element); }, immediateDescendants: function(element) { if (!(element = $(element).firstChild)) return []; while (element && element.nodeType != 1) element = element.nextSibling; if (element) return [element].concat($(element).nextSiblings()); return []; }, previousSiblings: function(element) { return $(element).recursivelyCollect('previousSibling'); }, nextSiblings: function(element) { return $(element).recursivelyCollect('nextSibling'); }, siblings: function(element) { element = $(element); return element.previousSiblings().reverse().concat(element.nextSiblings()); }, match: function(element, selector) { if (Object.isString(selector)) selector = new Selector(selector); return selector.match($(element)); }, up: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(element.parentNode); var ancestors = element.ancestors(); return expression ? Selector.findElement(ancestors, expression, index) : ancestors[index || 0]; }, down: function(element, expression, index) { element = $(element); if (arguments.length == 1) return element.firstDescendant(); var descendants = element.descendants(); return expression ? Selector.findElement(descendants, expression, index) : descendants[index || 0]; }, previous: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); var previousSiblings = element.previousSiblings(); return expression ? Selector.findElement(previousSiblings, expression, index) : previousSiblings[index || 0]; }, next: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); var nextSiblings = element.nextSiblings(); return expression ? Selector.findElement(nextSiblings, expression, index) : nextSiblings[index || 0]; }, select: function() { var args = $A(arguments), element = $(args.shift()); return Selector.findChildElements(element, args); }, adjacent: function() { var args = $A(arguments), element = $(args.shift()); return Selector.findChildElements(element.parentNode, args).without(element); }, identify: function(element) { element = $(element); var id = element.readAttribute('id'), self = arguments.callee; if (id) return id; do { id = 'anonymous_element_' + self.counter++ } while ($(id)); element.writeAttribute('id', id); return id; }, readAttribute: function(element, name) { element = $(element); if (Prototype.Browser.IE) { var t = Element._attributeTranslations.read; if (t.values[name]) return t.values[name](element, name); if (t.names[name]) name = t.names[name]; if (name.include(':')) { return (!element.attributes || !element.attributes[name]) ? null : element.attributes[name].value; } } return element.getAttribute(name); }, writeAttribute: function(element, name, value) { element = $(element); var attributes = { }, t = Element._attributeTranslations.write; if (typeof name == 'object') attributes = name; else attributes[name] = Object.isUndefined(value) ? true : value; for (var attr in attributes) { name = t.names[attr] || attr; value = attributes[attr]; if (t.values[attr]) name = t.values[attr](element, value); if (value === false || value === null) element.removeAttribute(name); else if (value === true) element.setAttribute(name, name); else element.setAttribute(name, value); } return element; }, getHeight: function(element) { return $(element).getDimensions().height; }, getWidth: function(element) { return $(element).getDimensions().width; }, classNames: function(element) { return new Element.ClassNames(element); }, hasClassName: function(element, className) { if (!(element = $(element))) return; var elementClassName = element.className; return (elementClassName.length > 0 && (elementClassName == className || new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); }, addClassName: function(element, className) { if (!(element = $(element))) return; if (!element.hasClassName(className)) element.className += (element.className ? ' ' : '') + className; return element; }, removeClassName: function(element, className) { if (!(element = $(element))) return; element.className = element.className.replace( new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); return element; }, toggleClassName: function(element, className) { if (!(element = $(element))) return; return element[element.hasClassName(className) ? 'removeClassName' : 'addClassName'](className); }, // removes whitespace-only text node children cleanWhitespace: function(element) { element = $(element); var node = element.firstChild; while (node) { var nextNode = node.nextSibling; if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) element.removeChild(node); node = nextNode; } return element; }, empty: function(element) { return $(element).innerHTML.blank(); }, descendantOf: function(element, ancestor) { element = $(element), ancestor = $(ancestor); var originalAncestor = ancestor; if (element.compareDocumentPosition) return (element.compareDocumentPosition(ancestor) & 8) === 8; if (element.sourceIndex && !Prototype.Browser.Opera) { var e = element.sourceIndex, a = ancestor.sourceIndex, nextAncestor = ancestor.nextSibling; if (!nextAncestor) { do { ancestor = ancestor.parentNode; } while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode); } if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex); } while (element = element.parentNode) if (element == originalAncestor) return true; return false; }, scrollTo: function(element) { element = $(element); var pos = element.cumulativeOffset(); window.scrollTo(pos[0], pos[1]); return element; }, getStyle: function(element, style) { element = $(element); style = style == 'float' ? 'cssFloat' : style.camelize(); var value = element.style[style]; if (!value) { var css = document.defaultView.getComputedStyle(element, null); value = css ? css[style] : null; } if (style == 'opacity') return value ? parseFloat(value) : 1.0; return value == 'auto' ? null : value; }, getOpacity: function(element) { return $(element).getStyle('opacity'); }, setStyle: function(element, styles) { element = $(element); var elementStyle = element.style, match; if (Object.isString(styles)) { element.style.cssText += ';' + styles; return styles.include('opacity') ? element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; } for (var property in styles) if (property == 'opacity') element.setOpacity(styles[property]); else elementStyle[(property == 'float' || property == 'cssFloat') ? (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : property] = styles[property]; return element; }, setOpacity: function(element, value) { element = $(element); element.style.opacity = (value == 1 || value === '') ? '' : (value < 0.00001) ? 0 : value; return element; }, getDimensions: function(element) { element = $(element); var display = $(element).getStyle('display'); if (display != 'none' && display != null) // Safari bug return {width: element.offsetWidth, height: element.offsetHeight}; // All *Width and *Height properties give 0 on elements with display none, // so enable the element temporarily var els = element.style; var originalVisibility = els.visibility; var originalPosition = els.position; var originalDisplay = els.display; els.visibility = 'hidden'; els.position = 'absolute'; els.display = 'block'; var originalWidth = element.clientWidth; var originalHeight = element.clientHeight; els.display = originalDisplay; els.position = originalPosition; els.visibility = originalVisibility; return {width: originalWidth, height: originalHeight}; }, makePositioned: function(element) { element = $(element); var pos = Element.getStyle(element, 'position'); if (pos == 'static' || !pos) { element._madePositioned = true; element.style.position = 'relative'; // Opera returns the offset relative to the positioning context, when an // element is position relative but top and left have not been defined if (window.opera) { element.style.top = 0; element.style.left = 0; } } return element; }, undoPositioned: function(element) { element = $(element); if (element._madePositioned) { element._madePositioned = undefined; element.style.position = element.style.top = element.style.left = element.style.bottom = element.style.right = ''; } return element; }, makeClipping: function(element) { element = $(element); if (element._overflow) return element; element._overflow = Element.getStyle(element, 'overflow') || 'auto'; if (element._overflow !== 'hidden') element.style.overflow = 'hidden'; return element; }, undoClipping: function(element) { element = $(element); if (!element._overflow) return element; element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; element._overflow = null; return element; }, cumulativeOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; } while (element); return Element._returnOffset(valueL, valueT); }, positionedOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; if (element) { if (element.tagName == 'BODY') break; var p = Element.getStyle(element, 'position'); if (p == 'relative' || p == 'absolute') break; } } while (element); return Element._returnOffset(valueL, valueT); }, absolutize: function(element) { element = $(element); if (element.getStyle('position') == 'absolute') return; // Position.prepare(); // To be done manually by Scripty when it needs it. var offsets = element.positionedOffset(); var top = offsets[1]; var left = offsets[0]; var width = element.clientWidth; var height = element.clientHeight; element._originalLeft = left - parseFloat(element.style.left || 0); element._originalTop = top - parseFloat(element.style.top || 0); element._originalWidth = element.style.width; element._originalHeight = element.style.height; element.style.position = 'absolute'; element.style.top = top + 'px'; element.style.left = left + 'px'; element.style.width = width + 'px'; element.style.height = height + 'px'; return element; }, relativize: function(element) { element = $(element); if (element.getStyle('position') == 'relative') return; // Position.prepare(); // To be done manually by Scripty when it needs it. element.style.position = 'relative'; var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); element.style.top = top + 'px'; element.style.left = left + 'px'; element.style.height = element._originalHeight; element.style.width = element._originalWidth; return element; }, cumulativeScrollOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.scrollTop || 0; valueL += element.scrollLeft || 0; element = element.parentNode; } while (element); return Element._returnOffset(valueL, valueT); }, getOffsetParent: function(element) { if (element.offsetParent) return $(element.offsetParent); if (element == document.body) return $(element); while ((element = element.parentNode) && element != document.body) if (Element.getStyle(element, 'position') != 'static') return $(element); return $(document.body); }, viewportOffset: function(forElement) { var valueT = 0, valueL = 0; var element = forElement; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; // Safari fix if (element.offsetParent == document.body && Element.getStyle(element, 'position') == 'absolute') break; } while (element = element.offsetParent); element = forElement; do { if (!Prototype.Browser.Opera || element.tagName == 'BODY') { valueT -= element.scrollTop || 0; valueL -= element.scrollLeft || 0; } } while (element = element.parentNode); return Element._returnOffset(valueL, valueT); }, clonePosition: function(element, source) { var options = Object.extend({ setLeft: true, setTop: true, setWidth: true, setHeight: true, offsetTop: 0, offsetLeft: 0 }, arguments[2] || { }); // find page position of source source = $(source); var p = source.viewportOffset(); // find coordinate system to use element = $(element); var delta = [0, 0]; var parent = null; // delta [0,0] will do fine with position: fixed elements, // position:absolute needs offsetParent deltas if (Element.getStyle(element, 'position') == 'absolute') { parent = element.getOffsetParent(); delta = parent.viewportOffset(); } // correct by body offsets (fixes Safari) if (parent == document.body) { delta[0] -= document.body.offsetLeft; delta[1] -= document.body.offsetTop; } // set position if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; if (options.setWidth) element.style.width = source.offsetWidth + 'px'; if (options.setHeight) element.style.height = source.offsetHeight + 'px'; return element; } }; Element.Methods.identify.counter = 1; Object.extend(Element.Methods, { getElementsBySelector: Element.Methods.select, childElements: Element.Methods.immediateDescendants }); Element._attributeTranslations = { write: { names: { className: 'class', htmlFor: 'for' }, values: { } } }; if (!document.createRange || Prototype.Browser.Opera) { Element.Methods.insert = function(element, insertions) { element = $(element); if (Object.isString(insertions) || Object.isNumber(insertions) || Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) insertions = { bottom: insertions }; var t = Element._insertionTranslations, content, position, pos, tagName; for (position in insertions) { content = insertions[position]; position = position.toLowerCase(); pos = t[position]; if (content && content.toElement) content = content.toElement(); if (Object.isElement(content)) { pos.insert(element, content); continue; } content = Object.toHTML(content); tagName = ((position == 'before' || position == 'after') ? element.parentNode : element).tagName.toUpperCase(); if (t.tags[tagName]) { var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); if (position == 'top' || position == 'after') fragments.reverse(); fragments.each(pos.insert.curry(element)); } else element.insertAdjacentHTML(pos.adjacency, content.stripScripts()); content.evalScripts.bind(content).defer(); } return element; }; } if (Prototype.Browser.Opera) { Element.Methods.getStyle = Element.Methods.getStyle.wrap( function(proceed, element, style) { switch (style) { case 'left': case 'top': case 'right': case 'bottom': if (proceed(element, 'position') === 'static') return null; case 'height': case 'width': // returns '0px' for hidden elements; we want it to return null if (!Element.visible(element)) return null; // returns the border-box dimensions rather than the content-box // dimensions, so we subtract padding and borders from the value var dim = parseInt(proceed(element, style), 10); if (dim !== element['offset' + style.capitalize()]) return dim + 'px'; var properties; if (style === 'height') { properties = ['border-top-width', 'padding-top', 'padding-bottom', 'border-bottom-width']; } else { properties = ['border-left-width', 'padding-left', 'padding-right', 'border-right-width']; } return properties.inject(dim, function(memo, property) { var val = proceed(element, property); return val === null ? memo : memo - parseInt(val, 10); }) + 'px'; default: return proceed(element, style); } } ); Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( function(proceed, element, attribute) { if (attribute === 'title') return element.title; return proceed(element, attribute); } ); } else if (Prototype.Browser.IE) { $w('positionedOffset getOffsetParent viewportOffset').each(function(method) { Element.Methods[method] = Element.Methods[method].wrap( function(proceed, element) { element = $(element); var position = element.getStyle('position'); if (position != 'static') return proceed(element); element.setStyle({ position: 'relative' }); var value = proceed(element); element.setStyle({ position: position }); return value; } ); }); Element.Methods.getStyle = function(element, style) { element = $(element); style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); var value = element.style[style]; if (!value && element.currentStyle) value = element.currentStyle[style]; if (style == 'opacity') { if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) if (value[1]) return parseFloat(value[1]) / 100; return 1.0; } if (value == 'auto') { if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) return element['offset' + style.capitalize()] + 'px'; return null; } return value; }; Element.Methods.setOpacity = function(element, value) { function stripAlpha(filter){ return filter.replace(/alpha\([^\)]*\)/gi,''); } element = $(element); var currentStyle = element.currentStyle; if ((currentStyle && !currentStyle.hasLayout) || (!currentStyle && element.style.zoom == 'normal')) element.style.zoom = 1; var filter = element.getStyle('filter'), style = element.style; if (value == 1 || value === '') { (filter = stripAlpha(filter)) ? style.filter = filter : style.removeAttribute('filter'); return element; } else if (value < 0.00001) value = 0; style.filter = stripAlpha(filter) + 'alpha(opacity=' + (value * 100) + ')'; return element; }; Element._attributeTranslations = { read: { names: { 'class': 'className', 'for': 'htmlFor' }, values: { _getAttr: function(element, attribute) { return element.getAttribute(attribute, 2); }, _getAttrNode: function(element, attribute) { var node = element.getAttributeNode(attribute); return node ? node.value : ""; }, _getEv: function(element, attribute) { attribute = element.getAttribute(attribute); return attribute ? attribute.toString().slice(23, -2) : null; }, _flag: function(element, attribute) { return $(element).hasAttribute(attribute) ? attribute : null; }, style: function(element) { return element.style.cssText.toLowerCase(); }, title: function(element) { return element.title; } } } }; Element._attributeTranslations.write = { names: Object.clone(Element._attributeTranslations.read.names), values: { checked: function(element, value) { element.checked = !!value; }, style: function(element, value) { element.style.cssText = value ? value : ''; } } }; Element._attributeTranslations.has = {}; $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + 'encType maxLength readOnly longDesc').each(function(attr) { Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; Element._attributeTranslations.has[attr.toLowerCase()] = attr; }); (function(v) { Object.extend(v, { href: v._getAttr, src: v._getAttr, type: v._getAttr, action: v._getAttrNode, disabled: v._flag, checked: v._flag, readonly: v._flag, multiple: v._flag, onload: v._getEv, onunload: v._getEv, onclick: v._getEv, ondblclick: v._getEv, onmousedown: v._getEv, onmouseup: v._getEv, onmouseover: v._getEv, onmousemove: v._getEv, onmouseout: v._getEv, onfocus: v._getEv, onblur: v._getEv, onkeypress: v._getEv, onkeydown: v._getEv, onkeyup: v._getEv, onsubmit: v._getEv, onreset: v._getEv, onselect: v._getEv, onchange: v._getEv }); })(Element._attributeTranslations.read.values); } else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { Element.Methods.setOpacity = function(element, value) { element = $(element); element.style.opacity = (value == 1) ? 0.999999 : (value === '') ? '' : (value < 0.00001) ? 0 : value; return element; }; } else if (Prototype.Browser.WebKit) { Element.Methods.setOpacity = function(element, value) { element = $(element); element.style.opacity = (value == 1 || value === '') ? '' : (value < 0.00001) ? 0 : value; if (value == 1) if(element.tagName == 'IMG' && element.width) { element.width++; element.width--; } else try { var n = document.createTextNode(' '); element.appendChild(n); element.removeChild(n); } catch (e) { } return element; }; // Safari returns margins on body which is incorrect if the child is absolutely // positioned. For performance reasons, redefine Element#cumulativeOffset for // KHTML/WebKit only. Element.Methods.cumulativeOffset = function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; if (element.offsetParent == document.body) if (Element.getStyle(element, 'position') == 'absolute') break; element = element.offsetParent; } while (element); return Element._returnOffset(valueL, valueT); }; } if (Prototype.Browser.IE || Prototype.Browser.Opera) { // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements Element.Methods.update = function(element, content) { element = $(element); if (content && content.toElement) content = content.toElement(); if (Object.isElement(content)) return element.update().insert(content); content = Object.toHTML(content); var tagName = element.tagName.toUpperCase(); if (tagName in Element._insertionTranslations.tags) { $A(element.childNodes).each(function(node) { element.removeChild(node) }); Element._getContentFromAnonymousElement(tagName, content.stripScripts()) .each(function(node) { element.appendChild(node) }); } else element.innerHTML = content.stripScripts(); content.evalScripts.bind(content).defer(); return element; }; } if (document.createElement('div').outerHTML) { Element.Methods.replace = function(element, content) { element = $(element); if (content && content.toElement) content = content.toElement(); if (Object.isElement(content)) { element.parentNode.replaceChild(content, element); return element; } content = Object.toHTML(content); var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); if (Element._insertionTranslations.tags[tagName]) { var nextSibling = element.next(); var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); parent.removeChild(element); if (nextSibling) fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); else fragments.each(function(node) { parent.appendChild(node) }); } else element.outerHTML = content.stripScripts(); content.evalScripts.bind(content).defer(); return element; }; } Element._returnOffset = function(l, t) { var result = [l, t]; result.left = l; result.top = t; return result; }; Element._getContentFromAnonymousElement = function(tagName, html) { var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; div.innerHTML = t[0] + html + t[1]; t[2].times(function() { div = div.firstChild }); return $A(div.childNodes); }; Element._insertionTranslations = { before: { adjacency: 'beforeBegin', insert: function(element, node) { element.parentNode.insertBefore(node, element); }, initializeRange: function(element, range) { range.setStartBefore(element); } }, top: { adjacency: 'afterBegin', insert: function(element, node) { element.insertBefore(node, element.firstChild); }, initializeRange: function(element, range) { range.selectNodeContents(element); range.collapse(true); } }, bottom: { adjacency: 'beforeEnd', insert: function(element, node) { element.appendChild(node); } }, after: { adjacency: 'afterEnd', insert: function(element, node) { element.parentNode.insertBefore(node, element.nextSibling); }, initializeRange: function(element, range) { range.setStartAfter(element); } }, tags: { TABLE: ['', '
    ', 1], TBODY: ['', '
    ', 2], TR: ['', '
    ', 3], TD: ['
    ', '
    ', 4], SELECT: ['', 1] } }; (function() { this.bottom.initializeRange = this.top.initializeRange; Object.extend(this.tags, { THEAD: this.tags.TBODY, TFOOT: this.tags.TBODY, TH: this.tags.TD }); }).call(Element._insertionTranslations); Element.Methods.Simulated = { hasAttribute: function(element, attribute) { attribute = Element._attributeTranslations.has[attribute] || attribute; var node = $(element).getAttributeNode(attribute); return node && node.specified; } }; Element.Methods.ByTag = { }; Object.extend(Element, Element.Methods); if (!Prototype.BrowserFeatures.ElementExtensions && document.createElement('div').__proto__) { window.HTMLElement = { }; window.HTMLElement.prototype = document.createElement('div').__proto__; Prototype.BrowserFeatures.ElementExtensions = true; } Element.extend = (function() { if (Prototype.BrowserFeatures.SpecificElementExtensions) return Prototype.K; var Methods = { }, ByTag = Element.Methods.ByTag; var extend = Object.extend(function(element) { if (!element || element._extendedByPrototype || element.nodeType != 1 || element == window) return element; var methods = Object.clone(Methods), tagName = element.tagName, property, value; // extend methods for specific tags if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); for (property in methods) { value = methods[property]; if (Object.isFunction(value) && !(property in element)) element[property] = value.methodize(); } element._extendedByPrototype = Prototype.emptyFunction; return element; }, { refresh: function() { // extend methods for all tags (Safari doesn't need this) if (!Prototype.BrowserFeatures.ElementExtensions) { Object.extend(Methods, Element.Methods); Object.extend(Methods, Element.Methods.Simulated); } } }); extend.refresh(); return extend; })(); Element.hasAttribute = function(element, attribute) { if (element.hasAttribute) return element.hasAttribute(attribute); return Element.Methods.Simulated.hasAttribute(element, attribute); }; Element.addMethods = function(methods) { var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; if (!methods) { Object.extend(Form, Form.Methods); Object.extend(Form.Element, Form.Element.Methods); Object.extend(Element.Methods.ByTag, { "FORM": Object.clone(Form.Methods), "INPUT": Object.clone(Form.Element.Methods), "SELECT": Object.clone(Form.Element.Methods), "TEXTAREA": Object.clone(Form.Element.Methods) }); } if (arguments.length == 2) { var tagName = methods; methods = arguments[1]; } if (!tagName) Object.extend(Element.Methods, methods || { }); else { if (Object.isArray(tagName)) tagName.each(extend); else extend(tagName); } function extend(tagName) { tagName = tagName.toUpperCase(); if (!Element.Methods.ByTag[tagName]) Element.Methods.ByTag[tagName] = { }; Object.extend(Element.Methods.ByTag[tagName], methods); } function copy(methods, destination, onlyIfAbsent) { onlyIfAbsent = onlyIfAbsent || false; for (var property in methods) { var value = methods[property]; if (!Object.isFunction(value)) continue; if (!onlyIfAbsent || !(property in destination)) destination[property] = value.methodize(); } } function findDOMClass(tagName) { var klass; var trans = { "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": "FrameSet", "IFRAME": "IFrame" }; if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; if (window[klass]) return window[klass]; klass = 'HTML' + tagName + 'Element'; if (window[klass]) return window[klass]; klass = 'HTML' + tagName.capitalize() + 'Element'; if (window[klass]) return window[klass]; window[klass] = { }; window[klass].prototype = document.createElement(tagName).__proto__; return window[klass]; } if (F.ElementExtensions) { copy(Element.Methods, HTMLElement.prototype); copy(Element.Methods.Simulated, HTMLElement.prototype, true); } if (F.SpecificElementExtensions) { for (var tag in Element.Methods.ByTag) { var klass = findDOMClass(tag); if (Object.isUndefined(klass)) continue; copy(T[tag], klass.prototype); } } Object.extend(Element, Element.Methods); delete Element.ByTag; if (Element.extend.refresh) Element.extend.refresh(); Element.cache = { }; }; document.viewport = { getDimensions: function() { var dimensions = { }; var B = Prototype.Browser; $w('width height').each(function(d) { var D = d.capitalize(); dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] : (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D]; }); return dimensions; }, getWidth: function() { return this.getDimensions().width; }, getHeight: function() { return this.getDimensions().height; }, getScrollOffsets: function() { return Element._returnOffset( window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); } }; /* Portions of the Selector class are derived from Jack Slocum’s DomQuery, * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style * license. Please see http://www.yui-ext.com/ for more information. */ var Selector = Class.create({ initialize: function(expression) { this.expression = expression.strip(); this.compileMatcher(); }, shouldUseXPath: function() { if (!Prototype.BrowserFeatures.XPath) return false; var e = this.expression; // Safari 3 chokes on :*-of-type and :empty if (Prototype.Browser.WebKit && (e.include("-of-type") || e.include(":empty"))) return false; // XPath can't do namespaced attributes, nor can it read // the "checked" property from DOM nodes if ((/(\[[\w-]*?:|:checked)/).test(this.expression)) return false; return true; }, compileMatcher: function() { if (this.shouldUseXPath()) return this.compileXPathMatcher(); var e = this.expression, ps = Selector.patterns, h = Selector.handlers, c = Selector.criteria, le, p, m; if (Selector._cache[e]) { this.matcher = Selector._cache[e]; return; } this.matcher = ["this.matcher = function(root) {", "var r = root, h = Selector.handlers, c = false, n;"]; while (e && le != e && (/\S/).test(e)) { le = e; for (var i in ps) { p = ps[i]; if (m = e.match(p)) { this.matcher.push(Object.isFunction(c[i]) ? c[i](m) : new Template(c[i]).evaluate(m)); e = e.replace(m[0], ''); break; } } } this.matcher.push("return h.unique(n);\n}"); eval(this.matcher.join('\n')); Selector._cache[this.expression] = this.matcher; }, compileXPathMatcher: function() { var e = this.expression, ps = Selector.patterns, x = Selector.xpath, le, m; if (Selector._cache[e]) { this.xpath = Selector._cache[e]; return; } this.matcher = ['.//*']; while (e && le != e && (/\S/).test(e)) { le = e; for (var i in ps) { if (m = e.match(ps[i])) { this.matcher.push(Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m)); e = e.replace(m[0], ''); break; } } } this.xpath = this.matcher.join(''); Selector._cache[this.expression] = this.xpath; }, findElements: function(root) { root = root || document; if (this.xpath) return document._getElementsByXPath(this.xpath, root); return this.matcher(root); }, match: function(element) { this.tokens = []; var e = this.expression, ps = Selector.patterns, as = Selector.assertions; var le, p, m; while (e && le !== e && (/\S/).test(e)) { le = e; for (var i in ps) { p = ps[i]; if (m = e.match(p)) { // use the Selector.assertions methods unless the selector // is too complex. if (as[i]) { this.tokens.push([i, Object.clone(m)]); e = e.replace(m[0], ''); } else { // reluctantly do a document-wide search // and look for a match in the array return this.findElements(document).include(element); } } } } var match = true, name, matches; for (var i = 0, token; token = this.tokens[i]; i++) { name = token[0], matches = token[1]; if (!Selector.assertions[name](element, matches)) { match = false; break; } } return match; }, toString: function() { return this.expression; }, inspect: function() { return "#"; } }); Object.extend(Selector, { _cache: { }, xpath: { descendant: "//*", child: "/*", adjacent: "/following-sibling::*[1]", laterSibling: '/following-sibling::*', tagName: function(m) { if (m[1] == '*') return ''; return "[local-name()='" + m[1].toLowerCase() + "' or local-name()='" + m[1].toUpperCase() + "']"; }, className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", id: "[@id='#{1}']", attrPresence: function(m) { m[1] = m[1].toLowerCase(); return new Template("[@#{1}]").evaluate(m); }, attr: function(m) { m[1] = m[1].toLowerCase(); m[3] = m[5] || m[6]; return new Template(Selector.xpath.operators[m[2]]).evaluate(m); }, pseudo: function(m) { var h = Selector.xpath.pseudos[m[1]]; if (!h) return ''; if (Object.isFunction(h)) return h(m); return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); }, operators: { '=': "[@#{1}='#{3}']", '!=': "[@#{1}!='#{3}']", '^=': "[starts-with(@#{1}, '#{3}')]", '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", '*=': "[contains(@#{1}, '#{3}')]", '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" }, pseudos: { 'first-child': '[not(preceding-sibling::*)]', 'last-child': '[not(following-sibling::*)]', 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]", 'checked': "[@checked]", 'disabled': "[@disabled]", 'enabled': "[not(@disabled)]", 'not': function(m) { var e = m[6], p = Selector.patterns, x = Selector.xpath, le, v; var exclusion = []; while (e && le != e && (/\S/).test(e)) { le = e; for (var i in p) { if (m = e.match(p[i])) { v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m); exclusion.push("(" + v.substring(1, v.length - 1) + ")"); e = e.replace(m[0], ''); break; } } } return "[not(" + exclusion.join(" and ") + ")]"; }, 'nth-child': function(m) { return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); }, 'nth-last-child': function(m) { return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); }, 'nth-of-type': function(m) { return Selector.xpath.pseudos.nth("position() ", m); }, 'nth-last-of-type': function(m) { return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); }, 'first-of-type': function(m) { m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); }, 'last-of-type': function(m) { m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); }, 'only-of-type': function(m) { var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); }, nth: function(fragment, m) { var mm, formula = m[6], predicate; if (formula == 'even') formula = '2n+0'; if (formula == 'odd') formula = '2n+1'; if (mm = formula.match(/^(\d+)$/)) // digit only return '[' + fragment + "= " + mm[1] + ']'; if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b if (mm[1] == "-") mm[1] = -1; var a = mm[1] ? Number(mm[1]) : 1; var b = mm[2] ? Number(mm[2]) : 0; predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + "((#{fragment} - #{b}) div #{a} >= 0)]"; return new Template(predicate).evaluate({ fragment: fragment, a: a, b: b }); } } } }, criteria: { tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', className: 'n = h.className(n, r, "#{1}", c); c = false;', id: 'n = h.id(n, r, "#{1}", c); c = false;', attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;', attr: function(m) { m[3] = (m[5] || m[6]); return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m); }, pseudo: function(m) { if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); }, descendant: 'c = "descendant";', child: 'c = "child";', adjacent: 'c = "adjacent";', laterSibling: 'c = "laterSibling";' }, patterns: { // combinators must be listed first // (and descendant needs to be last combinator) laterSibling: /^\s*~\s*/, child: /^\s*>\s*/, adjacent: /^\s*\+\s*/, descendant: /^\s/, // selectors follow tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, id: /^#([\w\-\*]+)(\b|$)/, className: /^\.([\w\-\*]+)(\b|$)/, pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/, attrPresence: /^\[([\w]+)\]/, attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ }, // for Selector.match and Element#match assertions: { tagName: function(element, matches) { return matches[1].toUpperCase() == element.tagName.toUpperCase(); }, className: function(element, matches) { return Element.hasClassName(element, matches[1]); }, id: function(element, matches) { return element.id === matches[1]; }, attrPresence: function(element, matches) { return Element.hasAttribute(element, matches[1]); }, attr: function(element, matches) { var nodeValue = Element.readAttribute(element, matches[1]); return Selector.operators[matches[2]](nodeValue, matches[3]); } }, handlers: { // UTILITY FUNCTIONS // joins two collections concat: function(a, b) { for (var i = 0, node; node = b[i]; i++) a.push(node); return a; }, // marks an array of nodes for counting mark: function(nodes) { for (var i = 0, node; node = nodes[i]; i++) node._counted = true; return nodes; }, unmark: function(nodes) { for (var i = 0, node; node = nodes[i]; i++) node._counted = undefined; return nodes; }, // mark each child node with its position (for nth calls) // "ofType" flag indicates whether we're indexing for nth-of-type // rather than nth-child index: function(parentNode, reverse, ofType) { parentNode._counted = true; if (reverse) { for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { var node = nodes[i]; if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; } } else { for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; } }, // filters out duplicates and extends all nodes unique: function(nodes) { if (nodes.length == 0) return nodes; var results = [], n; for (var i = 0, l = nodes.length; i < l; i++) if (!(n = nodes[i])._counted) { n._counted = true; results.push(Element.extend(n)); } return Selector.handlers.unmark(results); }, // COMBINATOR FUNCTIONS descendant: function(nodes) { var h = Selector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) h.concat(results, node.getElementsByTagName('*')); return results; }, child: function(nodes) { var h = Selector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) { for (var j = 0, child; child = node.childNodes[j]; j++) if (child.nodeType == 1 && child.tagName != '!') results.push(child); } return results; }, adjacent: function(nodes) { for (var i = 0, results = [], node; node = nodes[i]; i++) { var next = this.nextElementSibling(node); if (next) results.push(next); } return results; }, laterSibling: function(nodes) { var h = Selector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) h.concat(results, Element.nextSiblings(node)); return results; }, nextElementSibling: function(node) { while (node = node.nextSibling) if (node.nodeType == 1) return node; return null; }, previousElementSibling: function(node) { while (node = node.previousSibling) if (node.nodeType == 1) return node; return null; }, // TOKEN FUNCTIONS tagName: function(nodes, root, tagName, combinator) { tagName = tagName.toUpperCase(); var results = [], h = Selector.handlers; if (nodes) { if (combinator) { // fastlane for ordinary descendant combinators if (combinator == "descendant") { for (var i = 0, node; node = nodes[i]; i++) h.concat(results, node.getElementsByTagName(tagName)); return results; } else nodes = this[combinator](nodes); if (tagName == "*") return nodes; } for (var i = 0, node; node = nodes[i]; i++) if (node.tagName.toUpperCase() == tagName) results.push(node); return results; } else return root.getElementsByTagName(tagName); }, id: function(nodes, root, id, combinator) { var targetNode = $(id), h = Selector.handlers; if (!targetNode) return []; if (!nodes && root == document) return [targetNode]; if (nodes) { if (combinator) { if (combinator == 'child') { for (var i = 0, node; node = nodes[i]; i++) if (targetNode.parentNode == node) return [targetNode]; } else if (combinator == 'descendant') { for (var i = 0, node; node = nodes[i]; i++) if (Element.descendantOf(targetNode, node)) return [targetNode]; } else if (combinator == 'adjacent') { for (var i = 0, node; node = nodes[i]; i++) if (Selector.handlers.previousElementSibling(targetNode) == node) return [targetNode]; } else nodes = h[combinator](nodes); } for (var i = 0, node; node = nodes[i]; i++) if (node == targetNode) return [targetNode]; return []; } return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; }, className: function(nodes, root, className, combinator) { if (nodes && combinator) nodes = this[combinator](nodes); return Selector.handlers.byClassName(nodes, root, className); }, byClassName: function(nodes, root, className) { if (!nodes) nodes = Selector.handlers.descendant([root]); var needle = ' ' + className + ' '; for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { nodeClassName = node.className; if (nodeClassName.length == 0) continue; if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) results.push(node); } return results; }, attrPresence: function(nodes, root, attr) { if (!nodes) nodes = root.getElementsByTagName("*"); var results = []; for (var i = 0, node; node = nodes[i]; i++) if (Element.hasAttribute(node, attr)) results.push(node); return results; }, attr: function(nodes, root, attr, value, operator) { if (!nodes) nodes = root.getElementsByTagName("*"); var handler = Selector.operators[operator], results = []; for (var i = 0, node; node = nodes[i]; i++) { var nodeValue = Element.readAttribute(node, attr); if (nodeValue === null) continue; if (handler(nodeValue, value)) results.push(node); } return results; }, pseudo: function(nodes, name, value, root, combinator) { if (nodes && combinator) nodes = this[combinator](nodes); if (!nodes) nodes = root.getElementsByTagName("*"); return Selector.pseudos[name](nodes, value, root); } }, pseudos: { 'first-child': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) { if (Selector.handlers.previousElementSibling(node)) continue; results.push(node); } return results; }, 'last-child': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) { if (Selector.handlers.nextElementSibling(node)) continue; results.push(node); } return results; }, 'only-child': function(nodes, value, root) { var h = Selector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) results.push(node); return results; }, 'nth-child': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, formula, root); }, 'nth-last-child': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, formula, root, true); }, 'nth-of-type': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, formula, root, false, true); }, 'nth-last-of-type': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, formula, root, true, true); }, 'first-of-type': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, "1", root, false, true); }, 'last-of-type': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, "1", root, true, true); }, 'only-of-type': function(nodes, formula, root) { var p = Selector.pseudos; return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); }, // handles the an+b logic getIndices: function(a, b, total) { if (a == 0) return b > 0 ? [b] : []; return $R(1, total).inject([], function(memo, i) { if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); return memo; }); }, // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type nth: function(nodes, formula, root, reverse, ofType) { if (nodes.length == 0) return []; if (formula == 'even') formula = '2n+0'; if (formula == 'odd') formula = '2n+1'; var h = Selector.handlers, results = [], indexed = [], m; h.mark(nodes); for (var i = 0, node; node = nodes[i]; i++) { if (!node.parentNode._counted) { h.index(node.parentNode, reverse, ofType); indexed.push(node.parentNode); } } if (formula.match(/^\d+$/)) { // just a number formula = Number(formula); for (var i = 0, node; node = nodes[i]; i++) if (node.nodeIndex == formula) results.push(node); } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b if (m[1] == "-") m[1] = -1; var a = m[1] ? Number(m[1]) : 1; var b = m[2] ? Number(m[2]) : 0; var indices = Selector.pseudos.getIndices(a, b, nodes.length); for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { for (var j = 0; j < l; j++) if (node.nodeIndex == indices[j]) results.push(node); } } h.unmark(nodes); h.unmark(indexed); return results; }, 'empty': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) { // IE treats comments as element nodes if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue; results.push(node); } return results; }, 'not': function(nodes, selector, root) { var h = Selector.handlers, selectorType, m; var exclusions = new Selector(selector).findElements(root); h.mark(exclusions); for (var i = 0, results = [], node; node = nodes[i]; i++) if (!node._counted) results.push(node); h.unmark(exclusions); return results; }, 'enabled': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) if (!node.disabled) results.push(node); return results; }, 'disabled': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) if (node.disabled) results.push(node); return results; }, 'checked': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) if (node.checked) results.push(node); return results; } }, operators: { '=': function(nv, v) { return nv == v; }, '!=': function(nv, v) { return nv != v; }, '^=': function(nv, v) { return nv.startsWith(v); }, '$=': function(nv, v) { return nv.endsWith(v); }, '*=': function(nv, v) { return nv.include(v); }, '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); } }, matchElements: function(elements, expression) { var matches = new Selector(expression).findElements(), h = Selector.handlers; h.mark(matches); for (var i = 0, results = [], element; element = elements[i]; i++) if (element._counted) results.push(element); h.unmark(matches); return results; }, findElement: function(elements, expression, index) { if (Object.isNumber(expression)) { index = expression; expression = false; } return Selector.matchElements(elements, expression || '*')[index || 0]; }, findChildElements: function(element, expressions) { var exprs = expressions.join(','); expressions = []; exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { expressions.push(m[1].strip()); }); var results = [], h = Selector.handlers; for (var i = 0, l = expressions.length, selector; i < l; i++) { selector = new Selector(expressions[i].strip()); h.concat(results, selector.findElements(element)); } return (l > 1) ? h.unique(results) : results; } }); if (Prototype.Browser.IE) { // IE returns comment nodes on getElementsByTagName("*"). // Filter them out. Selector.handlers.concat = function(a, b) { for (var i = 0, node; node = b[i]; i++) if (node.tagName !== "!") a.push(node); return a; }; } function $$() { return Selector.findChildElements(document, $A(arguments)); } var Form = { reset: function(form) { $(form).reset(); return form; }, serializeElements: function(elements, options) { if (typeof options != 'object') options = { hash: !!options }; else if (Object.isUndefined(options.hash)) options.hash = true; var key, value, submitted = false, submit = options.submit; var data = elements.inject({ }, function(result, element) { if (!element.disabled && element.name) { key = element.name; value = $(element).getValue(); if (value != null && (element.type != 'submit' || (!submitted && submit !== false && (!submit || key == submit) && (submitted = true)))) { if (key in result) { // a key is already present; construct an array of values if (!Object.isArray(result[key])) result[key] = [result[key]]; result[key].push(value); } else result[key] = value; } } return result; }); return options.hash ? data : Object.toQueryString(data); } }; Form.Methods = { serialize: function(form, options) { return Form.serializeElements(Form.getElements(form), options); }, getElements: function(form) { return $A($(form).getElementsByTagName('*')).inject([], function(elements, child) { if (Form.Element.Serializers[child.tagName.toLowerCase()]) elements.push(Element.extend(child)); return elements; } ); }, getInputs: function(form, typeName, name) { form = $(form); var inputs = form.getElementsByTagName('input'); if (!typeName && !name) return $A(inputs).map(Element.extend); for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { var input = inputs[i]; if ((typeName && input.type != typeName) || (name && input.name != name)) continue; matchingInputs.push(Element.extend(input)); } return matchingInputs; }, disable: function(form) { form = $(form); Form.getElements(form).invoke('disable'); return form; }, enable: function(form) { form = $(form); Form.getElements(form).invoke('enable'); return form; }, findFirstElement: function(form) { var elements = $(form).getElements().findAll(function(element) { return 'hidden' != element.type && !element.disabled; }); var firstByIndex = elements.findAll(function(element) { return element.hasAttribute('tabIndex') && element.tabIndex >= 0; }).sortBy(function(element) { return element.tabIndex }).first(); return firstByIndex ? firstByIndex : elements.find(function(element) { return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); }); }, focusFirstElement: function(form) { form = $(form); form.findFirstElement().activate(); return form; }, request: function(form, options) { form = $(form), options = Object.clone(options || { }); var params = options.parameters, action = form.readAttribute('action') || ''; if (action.blank()) action = window.location.href; options.parameters = form.serialize(true); if (params) { if (Object.isString(params)) params = params.toQueryParams(); Object.extend(options.parameters, params); } if (form.hasAttribute('method') && !options.method) options.method = form.method; return new Ajax.Request(action, options); } }; /*--------------------------------------------------------------------------*/ Form.Element = { focus: function(element) { $(element).focus(); return element; }, select: function(element) { $(element).select(); return element; } }; Form.Element.Methods = { serialize: function(element) { element = $(element); if (!element.disabled && element.name) { var value = element.getValue(); if (value != undefined) { var pair = { }; pair[element.name] = value; return Object.toQueryString(pair); } } return ''; }, getValue: function(element) { element = $(element); var method = element.tagName.toLowerCase(); return Form.Element.Serializers[method](element); }, setValue: function(element, value) { element = $(element); var method = element.tagName.toLowerCase(); Form.Element.Serializers[method](element, value); return element; }, clear: function(element) { $(element).value = ''; return element; }, present: function(element) { return $(element).value != ''; }, activate: function(element) { element = $(element); try { element.focus(); if (element.select && (element.tagName.toLowerCase() != 'input' || !['button', 'reset', 'submit'].include(element.type))) element.select(); } catch (e) { } return element; }, disable: function(element) { element = $(element); element.blur(); element.disabled = true; return element; }, enable: function(element) { element = $(element); element.disabled = false; return element; } }; /*--------------------------------------------------------------------------*/ var Field = Form.Element; var $F = Form.Element.Methods.getValue; /*--------------------------------------------------------------------------*/ Form.Element.Serializers = { input: function(element, value) { switch (element.type.toLowerCase()) { case 'checkbox': case 'radio': return Form.Element.Serializers.inputSelector(element, value); default: return Form.Element.Serializers.textarea(element, value); } }, inputSelector: function(element, value) { if (Object.isUndefined(value)) return element.checked ? element.value : null; else element.checked = !!value; }, textarea: function(element, value) { if (Object.isUndefined(value)) return element.value; else element.value = value; }, select: function(element, index) { if (Object.isUndefined(index)) return this[element.type == 'select-one' ? 'selectOne' : 'selectMany'](element); else { var opt, value, single = !Object.isArray(index); for (var i = 0, length = element.length; i < length; i++) { opt = element.options[i]; value = this.optionValue(opt); if (single) { if (value == index) { opt.selected = true; return; } } else opt.selected = index.include(value); } } }, selectOne: function(element) { var index = element.selectedIndex; return index >= 0 ? this.optionValue(element.options[index]) : null; }, selectMany: function(element) { var values, length = element.length; if (!length) return null; for (var i = 0, values = []; i < length; i++) { var opt = element.options[i]; if (opt.selected) values.push(this.optionValue(opt)); } return values; }, optionValue: function(opt) { // extend element because hasAttribute may not be native return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; } }; /*--------------------------------------------------------------------------*/ Abstract.TimedObserver = Class.create(PeriodicalExecuter, { initialize: function($super, element, frequency, callback) { $super(callback, frequency); this.element = $(element); this.lastValue = this.getValue(); }, execute: function() { var value = this.getValue(); if (Object.isString(this.lastValue) && Object.isString(value) ? this.lastValue != value : String(this.lastValue) != String(value)) { this.callback(this.element, value); this.lastValue = value; } } }); Form.Element.Observer = Class.create(Abstract.TimedObserver, { getValue: function() { return Form.Element.getValue(this.element); } }); Form.Observer = Class.create(Abstract.TimedObserver, { getValue: function() { return Form.serialize(this.element); } }); /*--------------------------------------------------------------------------*/ Abstract.EventObserver = Class.create({ initialize: function(element, callback) { this.element = $(element); this.callback = callback; this.lastValue = this.getValue(); if (this.element.tagName.toLowerCase() == 'form') this.registerFormCallbacks(); else this.registerCallback(this.element); }, onElementEvent: function() { var value = this.getValue(); if (this.lastValue != value) { this.callback(this.element, value); this.lastValue = value; } }, registerFormCallbacks: function() { Form.getElements(this.element).each(this.registerCallback, this); }, registerCallback: function(element) { if (element.type) { switch (element.type.toLowerCase()) { case 'checkbox': case 'radio': Event.observe(element, 'click', this.onElementEvent.bind(this)); break; default: Event.observe(element, 'change', this.onElementEvent.bind(this)); break; } } } }); Form.Element.EventObserver = Class.create(Abstract.EventObserver, { getValue: function() { return Form.Element.getValue(this.element); } }); Form.EventObserver = Class.create(Abstract.EventObserver, { getValue: function() { return Form.serialize(this.element); } }); if (!window.Event) var Event = { }; Object.extend(Event, { KEY_BACKSPACE: 8, KEY_TAB: 9, KEY_RETURN: 13, KEY_ESC: 27, KEY_LEFT: 37, KEY_UP: 38, KEY_RIGHT: 39, KEY_DOWN: 40, KEY_DELETE: 46, KEY_HOME: 36, KEY_END: 35, KEY_PAGEUP: 33, KEY_PAGEDOWN: 34, KEY_INSERT: 45, cache: { }, relatedTarget: function(event) { var element; switch(event.type) { case 'mouseover': element = event.fromElement; break; case 'mouseout': element = event.toElement; break; default: return null; } return Element.extend(element); } }); Event.Methods = (function() { var isButton; if (Prototype.Browser.IE) { var buttonMap = { 0: 1, 1: 4, 2: 2 }; isButton = function(event, code) { return event.button == buttonMap[code]; }; } else if (Prototype.Browser.WebKit) { isButton = function(event, code) { switch (code) { case 0: return event.which == 1 && !event.metaKey; case 1: return event.which == 1 && event.metaKey; default: return false; } }; } else { isButton = function(event, code) { return event.which ? (event.which === code + 1) : (event.button === code); }; } return { isLeftClick: function(event) { return isButton(event, 0) }, isMiddleClick: function(event) { return isButton(event, 1) }, isRightClick: function(event) { return isButton(event, 2) }, element: function(event) { var node = Event.extend(event).target; return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node); }, findElement: function(event, expression) { var element = Event.element(event); if (!expression) return element; var elements = [element].concat(element.ancestors()); return Selector.findElement(elements, expression, 0); }, pointer: function(event) { return { x: event.pageX || (event.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft)), y: event.pageY || (event.clientY + (document.documentElement.scrollTop || document.body.scrollTop)) }; }, pointerX: function(event) { return Event.pointer(event).x }, pointerY: function(event) { return Event.pointer(event).y }, stop: function(event) { Event.extend(event); event.preventDefault(); event.stopPropagation(); event.stopped = true; } }; })(); Event.extend = (function() { var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { m[name] = Event.Methods[name].methodize(); return m; }); if (Prototype.Browser.IE) { Object.extend(methods, { stopPropagation: function() { this.cancelBubble = true }, preventDefault: function() { this.returnValue = false }, inspect: function() { return "[object Event]" } }); return function(event) { if (!event) return false; if (event._extendedByPrototype) return event; event._extendedByPrototype = Prototype.emptyFunction; var pointer = Event.pointer(event); Object.extend(event, { target: event.srcElement, relatedTarget: Event.relatedTarget(event), pageX: pointer.x, pageY: pointer.y }); return Object.extend(event, methods); }; } else { Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__; Object.extend(Event.prototype, methods); return Prototype.K; } })(); Object.extend(Event, (function() { var cache = Event.cache; function getEventID(element) { if (element._eventID) return element._eventID; arguments.callee.id = arguments.callee.id || 1; return element._eventID = ++arguments.callee.id; } function getDOMEventName(eventName) { if (eventName && eventName.include(':')) return "dataavailable"; return eventName; } function getCacheForID(id) { return cache[id] = cache[id] || { }; } function getWrappersForEventName(id, eventName) { var c = getCacheForID(id); return c[eventName] = c[eventName] || []; } function createWrapper(element, eventName, handler) { var id = getEventID(element); var c = getWrappersForEventName(id, eventName); if (c.pluck("handler").include(handler)) return false; var wrapper = function(event) { if (!Event || !Event.extend || (event.eventName && event.eventName != eventName)) return false; Event.extend(event); handler.call(element, event) }; wrapper.handler = handler; c.push(wrapper); return wrapper; } function findWrapper(id, eventName, handler) { var c = getWrappersForEventName(id, eventName); return c.find(function(wrapper) { return wrapper.handler == handler }); } function destroyWrapper(id, eventName, handler) { var c = getCacheForID(id); if (!c[eventName]) return false; c[eventName] = c[eventName].without(findWrapper(id, eventName, handler)); } function destroyCache() { for (var id in cache) for (var eventName in cache[id]) cache[id][eventName] = null; } if (window.attachEvent) { window.attachEvent("onunload", destroyCache); } return { observe: function(element, eventName, handler) { element = $(element); var name = getDOMEventName(eventName); var wrapper = createWrapper(element, eventName, handler); if (!wrapper) return element; if (element.addEventListener) { element.addEventListener(name, wrapper, false); } else { element.attachEvent("on" + name, wrapper); } return element; }, stopObserving: function(element, eventName, handler) { element = $(element); var id = getEventID(element), name = getDOMEventName(eventName); if (!handler && eventName) { getWrappersForEventName(id, eventName).each(function(wrapper) { element.stopObserving(eventName, wrapper.handler); }); return element; } else if (!eventName) { Object.keys(getCacheForID(id)).each(function(eventName) { element.stopObserving(eventName); }); return element; } var wrapper = findWrapper(id, eventName, handler); if (!wrapper) return element; if (element.removeEventListener) { element.removeEventListener(name, wrapper, false); } else { element.detachEvent("on" + name, wrapper); } destroyWrapper(id, eventName, handler); return element; }, fire: function(element, eventName, memo) { element = $(element); if (element == document && document.createEvent && !element.dispatchEvent) element = document.documentElement; if (document.createEvent) { var event = document.createEvent("HTMLEvents"); event.initEvent("dataavailable", true, true); } else { var event = document.createEventObject(); event.eventType = "ondataavailable"; } event.eventName = eventName; event.memo = memo || { }; if (document.createEvent) { element.dispatchEvent(event); } else { element.fireEvent(event.eventType, event); } return Event.extend(event); } }; })()); Object.extend(Event, Event.Methods); Element.addMethods({ fire: Event.fire, observe: Event.observe, stopObserving: Event.stopObserving }); Object.extend(document, { fire: Element.Methods.fire.methodize(), observe: Element.Methods.observe.methodize(), stopObserving: Element.Methods.stopObserving.methodize() }); (function() { /* Support for the DOMContentLoaded event is based on work by Dan Webb, Matthias Miller, Dean Edwards and John Resig. */ var timer, fired = false; function fireContentLoadedEvent() { if (fired) return; if (timer) window.clearInterval(timer); document.fire("dom:loaded"); fired = true; } if (document.addEventListener) { if (Prototype.Browser.WebKit) { timer = window.setInterval(function() { if (/loaded|complete/.test(document.readyState)) fireContentLoadedEvent(); }, 0); Event.observe(window, "load", fireContentLoadedEvent); } else { document.addEventListener("DOMContentLoaded", fireContentLoadedEvent, false); } } else { document.write("